Close inbox projection under one transaction
The old `prepareRun` projection drifted: append-to-shadow, thread-event, and `markAgentInboxDelivered` ran as three independent writes, so a crash between the append and the delivered mark replayed the same inbox row on the next run and double-populated the transcript. v46 installs `agent_message_projections` keyed by (message_id, session_id) as the idempotent gate. The projection closure now wraps the ledger claim, the shadow append, the thread-event write, and the delivered-mark inside one `BEGIN IMMEDIATE` transaction (exposed via `AppDatabase.withTransaction`). `claimProjection` returns false on a replay, so the whole closure short-circuits — no double transcript, no double thread event.
This commit is contained in:
1 parent
213837af1d
commit
cb0ac485da
5 files changed
+133
-26
No files matched your search
@@ -0,0 +1,44 @@
|
||||
import log from "electron-log/main.js";
|
||||
import type { Migration, MigrationDeps } from "./types";
|
||||
|
||||
export function createMigrationsV46V46(deps: MigrationDeps): Migration[] {
|
||||
const { db } = deps;
|
||||
return [
|
||||
{
|
||||
// Ledger for `prepareRun` inbox → session-shadow projection.
|
||||
//
|
||||
// Before v46 the projection sequence — append to the session
|
||||
// shadow, write the thread event, mark the inbox row delivered —
|
||||
// ran as three independent SQL writes. A crash or worker restart
|
||||
// between the append and the delivered mark caused the next run
|
||||
// to replay the same inbox row, duplicating transcript turns and
|
||||
// thread events.
|
||||
//
|
||||
// The ledger is keyed by the natural identity of a projection
|
||||
// attempt: the combination of the inbox message and the session
|
||||
// shadow it lands on. `INSERT OR IGNORE` against that key is the
|
||||
// idempotent gate — the first insert wins, a replay silently
|
||||
// skips. Callers wrap the ledger insert, the shadow append, the
|
||||
// thread-event write, and the delivered-mark in a single
|
||||
// `BEGIN IMMEDIATE` transaction so the four writes commit
|
||||
// together or roll back together.
|
||||
version: 46,
|
||||
name: "agent_message_projections_ledger",
|
||||
fn: () => {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS agent_message_projections (
|
||||
message_id TEXT NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
projected_at TEXT NOT NULL,
|
||||
PRIMARY KEY (message_id, session_id)
|
||||
)
|
||||
`);
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_message_projections_session
|
||||
ON agent_message_projections(session_id, projected_at)
|
||||
`);
|
||||
log.info("[migration v46] agent_message_projections ledger installed");
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
Reference in new issue
Block a user