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
@@ -23,6 +23,7 @@ import { buildKnowledgeMethods } from "./database/knowledge";
|
||||
import { buildWakeupMethods } from "./database/wakeup";
|
||||
import { buildBackupMethods } from "./database/backup";
|
||||
import { buildSessionEntryMethods } from "./database/session-entries";
|
||||
import { buildInboxProjectionMethods } from "./database/inbox-projections";
|
||||
import { buildCredentialMethods } from "./database/credentials";
|
||||
import { buildModelCapabilityMethods } from "./database/model-capabilities-crud";
|
||||
import { MODEL_CAPABILITY_SEED } from "./model-capability-seed";
|
||||
@@ -69,6 +70,7 @@ export class AppDatabaseBase {
|
||||
public readonly wakeup: ReturnType<typeof buildWakeupMethods>;
|
||||
public readonly backupMod: ReturnType<typeof buildBackupMethods>;
|
||||
public readonly sessionEntries: ReturnType<typeof buildSessionEntryMethods>;
|
||||
public readonly inboxProjections: ReturnType<typeof buildInboxProjectionMethods>;
|
||||
public readonly credentialsTable: ReturnType<typeof buildCredentialMethods>;
|
||||
public readonly modelCapabilities: ReturnType<typeof buildModelCapabilityMethods>;
|
||||
|
||||
@@ -143,6 +145,7 @@ export class AppDatabaseBase {
|
||||
this.knowledgeMod = buildKnowledgeMethods(getDb, ra);
|
||||
this.wakeup = buildWakeupMethods(getDb, ra, tx);
|
||||
this.sessionEntries = buildSessionEntryMethods(getDb, ra, tx);
|
||||
this.inboxProjections = buildInboxProjectionMethods(getDb);
|
||||
this.credentialsTable = buildCredentialMethods(getDb, ra, tx);
|
||||
this.modelCapabilities = buildModelCapabilityMethods(getDb, ra, tx);
|
||||
this.backupMod = buildBackupMethods({
|
||||
@@ -443,6 +446,18 @@ export class AppDatabaseBase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run `operation` inside a `BEGIN IMMEDIATE` transaction. Public
|
||||
* surface for callers outside the database module that need to bind
|
||||
* multiple writes into one atomic unit — the inbox projection closure
|
||||
* is the canonical example, where the append to the session shadow,
|
||||
* the thread event, and the delivered-mark all have to commit
|
||||
* together or roll back together.
|
||||
*/
|
||||
withTransaction<T>(operation: () => T): T {
|
||||
return this.transaction(operation);
|
||||
}
|
||||
|
||||
// ─── Methods used by subclass constructor callbacks ────────────────────────
|
||||
|
||||
addActivity(input: { companyId: string; actor: string; action: string; entityType: string; entityId: string; detail: string }) { this.activityMod.addActivity(input); }
|
||||
|
||||
Reference in new issue
Block a user