feat(core): replace instruction checkpoints with value-delta sync (#36254)
This commit is contained in:
parent
768a69bbbd
commit
96a9731947
57 changed files with 2053 additions and 1278 deletions
|
|
@ -23,6 +23,7 @@ import sessionPendingTableMigration from "@opencode-ai/core/database/migration/2
|
|||
import renameInstructionsMigration from "@opencode-ai/core/database/migration/20260705180000_rename_instructions"
|
||||
import addSessionForkMigration from "@opencode-ai/core/database/migration/20260706223930_add-session-fork"
|
||||
import timeSuspendedMigration from "@opencode-ai/core/database/migration/20260709163752_time_suspended"
|
||||
import instructionSyncMigration from "@opencode-ai/core/database/migration/20260710025429_instruction_sync"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
|
|
@ -359,12 +360,12 @@ describe("DatabaseMigration", () => {
|
|||
).toEqual({ name: "session_pending" })
|
||||
expect(
|
||||
yield* db.get(sql`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'instruction_checkpoint'`),
|
||||
).toEqual({ name: "instruction_checkpoint" })
|
||||
expect(
|
||||
yield* db.get(
|
||||
sql`SELECT name FROM pragma_table_info('instruction_checkpoint') WHERE name IN ('agent', 'replacement_seq', 'revision')`,
|
||||
),
|
||||
).toBeUndefined()
|
||||
expect(
|
||||
yield* db.all(
|
||||
sql`SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('instruction_blob', 'instruction_state') ORDER BY name`,
|
||||
),
|
||||
).toEqual([{ name: "instruction_blob" }, { name: "instruction_state" }])
|
||||
expect(yield* db.get(sql`SELECT count(*) as count FROM migration`)).toEqual({ count: migrations.length })
|
||||
expect(
|
||||
yield* db.all(
|
||||
|
|
@ -507,6 +508,91 @@ describe("DatabaseMigration", () => {
|
|||
)
|
||||
})
|
||||
|
||||
test("deletes pre-beta instruction events and projected System messages", async () => {
|
||||
await run(
|
||||
Effect.gen(function* () {
|
||||
const db = yield* makeDb
|
||||
yield* db.run(sql`CREATE TABLE session (id text PRIMARY KEY, fork_session_id text)`)
|
||||
yield* db.run(
|
||||
sql`CREATE TABLE instruction_entry (session_id text NOT NULL, key text NOT NULL, value text NOT NULL, time_created integer NOT NULL, time_updated integer NOT NULL, PRIMARY KEY(session_id, key))`,
|
||||
)
|
||||
yield* db.run(sql`CREATE TABLE instruction_checkpoint (session_id text PRIMARY KEY)`)
|
||||
yield* db.run(sql`CREATE TABLE event_sequence (aggregate_id text PRIMARY KEY, seq integer NOT NULL)`)
|
||||
yield* db.run(
|
||||
sql`CREATE TABLE event (id text PRIMARY KEY, aggregate_id text NOT NULL, seq integer NOT NULL, type text NOT NULL, data text NOT NULL)`,
|
||||
)
|
||||
yield* db.run(sql`CREATE TABLE session_message (id text PRIMARY KEY, type text NOT NULL)`)
|
||||
yield* db.run(sql`INSERT INTO session VALUES ('ses_test', NULL)`)
|
||||
yield* db.run(
|
||||
sql`INSERT INTO event VALUES ('evt_instruction', 'ses_test', 0, 'session.instructions.updated.1', '{"sessionID":"ses_test","text":"changed"}')`,
|
||||
)
|
||||
yield* db.run(
|
||||
sql`INSERT INTO event VALUES ('evt_other', 'ses_test', 1, 'session.synthetic.1', '{"sessionID":"ses_test","text":"keep"}')`,
|
||||
)
|
||||
yield* db.run(
|
||||
sql`INSERT INTO session_message VALUES ('msg_instruction', 'system'), ('msg_other', 'system'), ('msg_user', 'user')`,
|
||||
)
|
||||
yield* db.run(sql`INSERT INTO instruction_entry VALUES ('ses_test', 'plan', '"ready"', 1, 2)`)
|
||||
|
||||
yield* DatabaseMigration.applyOnly(db, [instructionSyncMigration])
|
||||
|
||||
expect(yield* db.all(sql`SELECT id, type FROM event`)).toEqual([
|
||||
{ id: "evt_other", type: "session.synthetic.1" },
|
||||
])
|
||||
expect(yield* db.all(sql`SELECT id, type FROM session_message ORDER BY id`)).toEqual([
|
||||
{ id: "msg_user", type: "user" },
|
||||
])
|
||||
expect(yield* db.get(sql`SELECT * FROM instruction_entry`)).toEqual({
|
||||
session_id: "ses_test",
|
||||
key: "plan",
|
||||
value: '"ready"',
|
||||
removed: 0,
|
||||
time_created: 1,
|
||||
time_updated: 2,
|
||||
})
|
||||
expect(
|
||||
yield* db.get(sql`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'instruction_checkpoint'`),
|
||||
).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test("records the authoritative parent sequence on existing forks", async () => {
|
||||
await run(
|
||||
Effect.gen(function* () {
|
||||
const db = yield* makeDb
|
||||
yield* db.run(sql`CREATE TABLE session (id text PRIMARY KEY, fork_session_id text)`)
|
||||
yield* db.run(
|
||||
sql`CREATE TABLE instruction_entry (session_id text NOT NULL, key text NOT NULL, value text NOT NULL, time_created integer NOT NULL, time_updated integer NOT NULL, PRIMARY KEY(session_id, key))`,
|
||||
)
|
||||
yield* db.run(sql`CREATE TABLE instruction_checkpoint (session_id text PRIMARY KEY)`)
|
||||
yield* db.run(sql`CREATE TABLE session_message (id text PRIMARY KEY, type text NOT NULL)`)
|
||||
yield* db.run(sql`CREATE TABLE event_sequence (aggregate_id text PRIMARY KEY, seq integer NOT NULL)`)
|
||||
yield* db.run(
|
||||
sql`CREATE TABLE event (id text PRIMARY KEY, aggregate_id text NOT NULL, seq integer NOT NULL, type text NOT NULL, data text NOT NULL)`,
|
||||
)
|
||||
yield* db.run(sql`INSERT INTO session VALUES ('ses_child', 'ses_parent')`)
|
||||
yield* db.run(sql`INSERT INTO event_sequence VALUES ('ses_child', 8)`)
|
||||
yield* db.run(
|
||||
sql`INSERT INTO event VALUES ('evt_fork', 'ses_child', 0, 'session.forked.1', '{"sessionID":"ses_child","parentID":"ses_parent"}')`,
|
||||
)
|
||||
yield* db.run(
|
||||
sql`INSERT INTO event VALUES ('evt_instruction', 'ses_child', 5, 'session.instructions.updated.1', '{"sessionID":"ses_child","text":"changed"}')`,
|
||||
)
|
||||
yield* db.run(sql`INSERT INTO event VALUES ('evt_input', 'ses_child', 6, 'session.input.admitted.1', '{}')`)
|
||||
|
||||
yield* DatabaseMigration.applyOnly(db, [instructionSyncMigration])
|
||||
|
||||
expect(yield* db.get(sql`SELECT fork_seq FROM session`)).toEqual({ fork_seq: 4 })
|
||||
expect(yield* db.get(sql`SELECT type, data FROM event WHERE seq = 0`)).toEqual({
|
||||
type: "session.forked.2",
|
||||
data: '{"sessionID":"ses_child","parentID":"ses_parent","parentSeq":4}',
|
||||
})
|
||||
expect(yield* db.get(sql`SELECT id FROM event WHERE id = 'evt_instruction'`)).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test("keeps legacy credential fields nullable", async () => {
|
||||
await run(
|
||||
Effect.gen(function* () {
|
||||
|
|
@ -639,17 +725,14 @@ describe("DatabaseMigration", () => {
|
|||
yield* db.run(
|
||||
sql`INSERT INTO session_message (id, session_id, type, seq, time_created, time_updated, data) VALUES ('projected', 'session', 'user', 9, 1, 1, '{}')`,
|
||||
)
|
||||
yield* db.run(
|
||||
sql`INSERT INTO instruction_checkpoint (session_id, baseline, snapshot, baseline_seq) VALUES ('session', 'baseline', '{}', 9)`,
|
||||
)
|
||||
yield* db.run(sql`ALTER TABLE instruction_checkpoint RENAME TO session_context_epoch`)
|
||||
yield* db.run(sql`CREATE TABLE session_context_epoch (session_id text PRIMARY KEY)`)
|
||||
// The partial compaction index embeds the qualified table name, so it
|
||||
// must drop before the historical rename dance and recreate after.
|
||||
yield* db.run(sql`DROP INDEX session_pending_session_compaction_idx`)
|
||||
yield* db.run(sql`ALTER TABLE session_pending RENAME TO session_input`)
|
||||
yield* db.run(sql`DELETE FROM migration WHERE id = ${simplifySessionPendingMigration.id}`)
|
||||
yield* DatabaseMigration.applyOnly(db, [simplifySessionPendingMigration])
|
||||
yield* db.run(sql`ALTER TABLE session_context_epoch RENAME TO instruction_checkpoint`)
|
||||
yield* db.run(sql`DROP TABLE session_context_epoch`)
|
||||
yield* db.run(sql`ALTER TABLE session_input RENAME TO session_pending`)
|
||||
yield* db.run(
|
||||
sql`CREATE UNIQUE INDEX session_pending_session_compaction_idx ON session_pending (session_id) WHERE "session_pending"."type" = 'compaction'`,
|
||||
|
|
@ -685,7 +768,7 @@ describe("DatabaseMigration", () => {
|
|||
(SELECT COUNT(*) FROM workspace) AS workspaces,
|
||||
(SELECT COUNT(*) FROM session_pending) AS sessionInputs,
|
||||
(SELECT COUNT(*) FROM session_message) AS sessionMessages,
|
||||
(SELECT COUNT(*) FROM instruction_checkpoint) AS instructionCheckpoints,
|
||||
(SELECT COUNT(*) FROM instruction_state) AS instructionStates,
|
||||
(SELECT seq FROM event_sequence WHERE aggregate_id = 'session') AS seq,
|
||||
(SELECT type FROM event WHERE aggregate_id = 'session') AS eventType
|
||||
`),
|
||||
|
|
@ -697,7 +780,7 @@ describe("DatabaseMigration", () => {
|
|||
workspaces: 0,
|
||||
sessionInputs: 0,
|
||||
sessionMessages: 0,
|
||||
instructionCheckpoints: 0,
|
||||
instructionStates: 0,
|
||||
seq: 0,
|
||||
eventType: "session.updated.1",
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue