fix(core): guard model-data migration on valid JSON
This commit is contained in:
parent
0104216d17
commit
54a2dd9235
10 changed files with 77 additions and 95 deletions
|
|
@ -2,7 +2,8 @@ ALTER TABLE `part` ADD `data_model` text;
|
|||
--> statement-breakpoint
|
||||
UPDATE part
|
||||
SET data_model = json_remove(data, '$.state.metadata')
|
||||
WHERE length(CAST(data AS BLOB)) > 65536
|
||||
WHERE json_valid(data)
|
||||
AND length(CAST(data AS BLOB)) > 65536
|
||||
AND json_extract(data, '$.type') = 'tool'
|
||||
AND json_extract(data, '$.state.status') = 'completed'
|
||||
AND length(CAST(json_extract(data, '$.state.metadata') AS BLOB)) > 65536;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ export default {
|
|||
yield* tx.run(`
|
||||
UPDATE part
|
||||
SET data_model = json_remove(data, '$.state.metadata')
|
||||
WHERE length(CAST(data AS BLOB)) > 65536
|
||||
WHERE json_valid(data)
|
||||
AND length(CAST(data AS BLOB)) > 65536
|
||||
AND json_extract(data, '$.type') = 'tool'
|
||||
AND json_extract(data, '$.state.status') = 'completed'
|
||||
AND length(CAST(json_extract(data, '$.state.metadata') AS BLOB)) > 65536
|
||||
|
|
|
|||
|
|
@ -6,10 +6,14 @@ type V1PartData<Data extends SessionV1.Part = SessionV1.Part> = Data extends Ses
|
|||
? Omit<Data, "id" | "sessionID" | "messageID">
|
||||
: never
|
||||
|
||||
export type ModelData = Omit<V1PartData<SessionV1.ToolPart>, "state"> & {
|
||||
state: Omit<SessionV1.ToolStateCompleted, "metadata">
|
||||
}
|
||||
|
||||
export const THRESHOLD = 64 * 1024
|
||||
|
||||
// Strip UI-only metadata only when the stored prompt projection benefits.
|
||||
export function create(data: unknown): V1PartData | null {
|
||||
export function create(data: unknown): ModelData | null {
|
||||
if (!data || typeof data !== "object") return null
|
||||
if (!("type" in data) || data.type !== "tool") return null
|
||||
if (!("state" in data) || !data.state || typeof data.state !== "object") return null
|
||||
|
|
@ -18,5 +22,5 @@ export function create(data: unknown): V1PartData | null {
|
|||
const metadata = JSON.stringify(data.state.metadata)
|
||||
if (!metadata || Buffer.byteLength(metadata) <= THRESHOLD) return null
|
||||
const { metadata: _, ...state } = data.state
|
||||
return { ...data, state } as V1PartData
|
||||
return { ...data, state } as ModelData
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import type { SessionSchema } from "./schema"
|
|||
import type { MessageID, PartID, SessionV1 } from "../v1/session"
|
||||
import { WorkspaceV2 } from "../workspace"
|
||||
import { Timestamps } from "../database/schema.sql"
|
||||
import type { ModelData } from "./model-data"
|
||||
|
||||
type SessionMessageData = Omit<(typeof SessionMessage.Message)["Encoded"], "type" | "id">
|
||||
type V1MessageData = Omit<SessionV1.Info, "id" | "sessionID">
|
||||
|
|
@ -86,7 +87,7 @@ export const PartTable = sqliteTable(
|
|||
...Timestamps,
|
||||
data: text({ mode: "json" }).notNull().$type<V1PartData>(),
|
||||
// Derived prompt projection; data remains canonical.
|
||||
data_model: text({ mode: "json" }).$type<V1PartData>(),
|
||||
data_model: text({ mode: "json" }).$type<ModelData>(),
|
||||
},
|
||||
(table) => [
|
||||
index("part_message_id_id_idx").on(table.message_id, table.id),
|
||||
|
|
|
|||
|
|
@ -86,7 +86,8 @@ describe("DatabaseMigration", () => {
|
|||
const large = JSON.stringify({ type: "tool", state: { status: "completed", metadata: { diff: "x".repeat(70_000) } } })
|
||||
const unicode = JSON.stringify({ type: "tool", state: { status: "completed", metadata: { diff: "😀".repeat(20_000) } } })
|
||||
const small = JSON.stringify({ type: "tool", state: { status: "completed", metadata: { diff: "small" } } })
|
||||
yield* db.run(sql`INSERT INTO part (id, data) VALUES (${"large"}, ${large}), (${"unicode"}, ${unicode}), (${"small"}, ${small})`)
|
||||
const malformed = "{" + "x".repeat(70_000)
|
||||
yield* db.run(sql`INSERT INTO part (id, data) VALUES (${"large"}, ${large}), (${"unicode"}, ${unicode}), (${"small"}, ${small}), (${"malformed"}, ${malformed})`)
|
||||
|
||||
yield* DatabaseMigration.applyOnly(db, [partModelDataMigration])
|
||||
|
||||
|
|
@ -98,6 +99,7 @@ describe("DatabaseMigration", () => {
|
|||
expect(yield* db.get(sql`SELECT data_model FROM part WHERE id = ${"unicode"}`)).toEqual({
|
||||
data_model: JSON.stringify({ type: "tool", state: { status: "completed" } }),
|
||||
})
|
||||
expect(yield* db.get(sql`SELECT data_model FROM part WHERE id = ${"malformed"}`)).toEqual({ data_model: null })
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue