| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import type { |
| CommitToolPreparedInput, |
| CommitToolOutcomeInput, |
| ToolCommitResult, |
| SessionRuntimeEventEntry, |
| ToolOperationRecord, |
| ImmutableRuntimePrefixProofReadBudget, |
| } from './runtime-event-store-contract.js'; |
| export type { |
| CommitToolPreparedInput, |
| CommitToolOutcomeInput, |
| ToolCommitResult, |
| SessionRuntimeEventEntry, |
| ToolOperationRecord, |
| ImmutableRuntimePrefixProofReadBudget, |
| } from './runtime-event-store-contract.js'; |
| |
| import { mkdirSync } from 'node:fs'; |
| import { createRequire } from 'node:module'; |
| import { dirname } from 'node:path'; |
| import type { DatabaseSync, SQLInputValue } from 'node:sqlite'; |
| import { isDeepStrictEqual } from 'node:util'; |
| import { assertHandoffClaimSource } from '@maka/core/runtime-handoff'; |
| import { |
| buildWorkspaceBaselineAuthorityEvents, |
| buildWorkspaceSuccessorAuthorityEvent, |
| scanWorkspaceBaselineAuthority, |
| WORKSPACE_AUTHORITY_SESSION_ID, |
| WORKSPACE_VERSION_AUTHORITY_CAPABILITY_V1, |
| type ScannedWorkspaceBaselineAuthority, |
| type ScannedWorkspaceSuccessorAuthority, |
| type WorkspaceAuthorityLedgerRow, |
| type WorkspaceBaselineAuthorityInput, |
| type WorkspaceBaselineCommitResult, |
| type WorkspaceEpochRecordV1, |
| type WorkspaceHeadRecordV1, |
| type WorkspaceProjectionRebuildResult, |
| type WorkspaceSuccessorAuthorityInput, |
| type WorkspaceVersionAcceptedV1, |
| type WorkspaceVersionRecordV1, |
| } from '@maka/core/workspace-version-authority'; |
| import { |
| decodeRuntimeEvent, |
| decodeRuntimeInvocationOpened, |
| isPartialRuntimeEvent, |
| isTerminalRuntimeEvent, |
| runtimeEventInvocationOpening, |
| type RuntimeEvent, |
| type RuntimeEventManagedWorkspaceMutationV2, |
| type ToolRecoveryMode, |
| } from '@maka/core/runtime-event'; |
| import { |
| RunSealedError, |
| RUNTIME_CONTINUATION_AUTHORITY_V1, |
| TOOL_RECOVERY_BUNDLE_CAPABILITY_V1, |
| type ContinuationClaimResult, |
| type ContinuationClaimStateV1, |
| type RuntimeContinuationAuthorityStore, |
| type RuntimeRecoveryBundleCommit, |
| type RuntimeRecoveryBundleStore, |
| type RuntimeWorkspaceVersionAuthorityStore, |
| } from '@maka/core/runtime-event-store'; |
| import type { |
| RuntimeInvocationPageCursor, |
| RuntimeInvocationPageInput, |
| RuntimeInvocationPageResult, |
| RuntimeInvocationRecord, |
| RuntimeInvocationSearchResult, |
| } from '@maka/core/runtime-invocation'; |
| import type { ToolRecoveryDecisionFact } from '@maka/core/tool-recovery-fact'; |
| import { stableJsonStringify } from '@maka/core/tool-args-identity'; |
| import { |
| type RuntimePartialSnapshot, |
| mergeRuntimePartialSnapshots, |
| groupRuntimePartialSnapshots, |
| compareRuntimePartialSnapshots, |
| partialRuntimeStream, |
| completedPartialRuntimeStreamKey, |
| } from './runtime-partial-values.js'; |
| import { |
| assertPreparedInput, |
| assertOutcomeInput, |
| assertPreparedIdentity, |
| assertOutcomeIdentity, |
| } from './tool-commit-validation.js'; |
| import { encodeCanonicalRuntimeEvent } from '@maka/core/canonical-runtime-event'; |
| import { |
| scanToolLedger, |
| ToolLedgerCorruptionError, |
| ToolLedgerRejectionError, |
| validateGenericToolLedgerAppend, |
| validateToolLedgerTransition, |
| } from '@maka/core/tool-ledger-scanner'; |
| import { |
| buildImmutableRuntimePrefix, |
| buildImmutableRuntimePrefixProof, |
| continuationStartEventMatchesClaim, |
| decodeContinuationClaim, |
| type ContinuationClaimV1, |
| type ImmutableRuntimePrefixV1, |
| type ImmutableRuntimePrefixProofV1, |
| type RuntimeBoundaryDigest, |
| } from '@maka/core/runtime-boundary'; |
| import { |
| assertToolRecoveryEventBundle, |
| interpretScannedToolRecovery, |
| } from '@maka/core/tool-recovery-bundle'; |
| import { |
| configureSqliteRuntimeDatabase, |
| migrateSqliteRuntimeDatabase, |
| readUserVersion, |
| RUNTIME_RECOVERY_AUTHORITY_CAPABILITY, |
| RUNTIME_RECOVERY_AUTHORITY_CAPABILITY_VERSION, |
| RUNTIME_CONTINUATION_AUTHORITY_CAPABILITY, |
| RUNTIME_CONTINUATION_AUTHORITY_CAPABILITY_VERSION, |
| RUNTIME_WORKSPACE_VERSION_AUTHORITY_CAPABILITY, |
| RUNTIME_WORKSPACE_VERSION_AUTHORITY_CAPABILITY_VERSION, |
| runtimeEventKind, |
| SQLITE_RUNTIME_SCHEMA_VERSION, |
| } from './sqlite-runtime-schema.js'; |
| import { |
| registerWorkspaceBaselineAuthorityWriterInternal, |
| type ManagedMutationNoEffectClaimV1, |
| type ManagedMutationTerminalCommitInput, |
| type ManagedMutationTerminalCommitResult, |
| type WorkspaceSuccessorCommitInput, |
| type WorkspaceSuccessorCommitResult, |
| } from './workspace-version-authority-internal.js'; |
| import type { |
| ConversationCopyRuntimeEventBatch, |
| ImmutableSteeringMessageProof, |
| RuntimeEventScanBudget, |
| RuntimeEventScanResult, |
| } from './agent-run-store.js'; |
| import { |
| assertEvidenceReadBudget, |
| measureEvidenceRows, |
| type BoundedEvidenceReadResult, |
| type EvidenceReadBudget, |
| } from './bounded-evidence.js'; |
| import type { OperationalStateDatabaseLease } from './operational-state-store.js'; |
| import { |
| assertFoldedSearchTerm, |
| recallFoldedMatchClause, |
| registerRecallFoldFunction, |
| } from './recall-fold.js'; |
| import { immutableSteeringMessageId, isRuntimeStorageSafeId } from './runtime-event-invariants.js'; |
| import { assertNoReservedWorkspaceAuthorityAppend } from './runtime-event-authority.js'; |
| import { |
| rebuildTranscriptTurnExtents, |
| recordTranscriptTurnExtent, |
| RuntimeTranscriptQuery, |
| TERMINAL_RUNTIME_EVENT_SQL, |
| type RuntimeTranscriptRun, |
| type RuntimeTranscriptRunRequest, |
| type RuntimeTranscriptTurn, |
| type RuntimeTranscriptTurnsRequest, |
| } from './runtime-transcript-query.js'; |
| |
| export { SQLITE_RUNTIME_SCHEMA_VERSION } from './sqlite-runtime-schema.js'; |
| |
| export type { ToolRecoveryMode } from '@maka/core/runtime-event'; |
| |
| const RUNTIME_EVENT_SCAN_BATCH_SIZE = 128; |
| const RUNTIME_PARTIAL_SEGMENT_TARGET_BYTES = 64 * 1024; |
| |
| function assertRuntimeEventScanBudget(budget: RuntimeEventScanBudget): void { |
| for (const [name, value] of Object.entries(budget)) { |
| if (!Number.isSafeInteger(value) || value < 1) { |
| throw new Error(`Invalid RuntimeEvent scan ${name}`); |
| } |
| } |
| } |
| |
| function assertImmutableRuntimePrefixProofReadBudget( |
| budget: ImmutableRuntimePrefixProofReadBudget, |
| ): void { |
| for (const [name, value] of Object.entries(budget)) { |
| if (!Number.isSafeInteger(value) || value < 1) { |
| throw new Error(`Invalid immutable RuntimeEvent prefix proof ${name}`); |
| } |
| } |
| } |
| |
| function requireRuntimeEventScanCount(value: unknown): number { |
| if (!Number.isSafeInteger(value) || (value as number) < 0) { |
| throw new Error('Invalid RuntimeEvent scan measurement'); |
| } |
| return value as number; |
| } |
| |
| const require = createRequire(import.meta.url); |
| |
| function loadDatabaseSync(): typeof import('node:sqlite').DatabaseSync { |
| return (require('node:sqlite') as typeof import('node:sqlite')).DatabaseSync; |
| } |
| |
| function configureSqliteRuntimeReadOnlyDatabase(db: DatabaseSync): void { |
| db.exec('PRAGMA busy_timeout = 5000'); |
| db.exec('PRAGMA foreign_keys = ON'); |
| db.exec('PRAGMA query_only = ON'); |
| } |
| |
| export type ToolJournalState = |
| | 'prepared' |
| | 'reconcile_observed' |
| | 'outcome_committed' |
| | 'recovery_completed' |
| | 'recovery_parked'; |
| |
| export type SqliteRuntimeStoreFailpoint = |
| | 'after_runtime_event_insert' |
| | 'after_journal_event_insert' |
| | 'after_recovery_reconcile' |
| | 'after_recovery_outcome' |
| | 'after_recovery_decision' |
| | 'after_continuation_claim_insert' |
| | 'after_continuation_start_insert' |
| | 'after_workspace_epoch_event_insert' |
| | 'after_workspace_version_event_insert' |
| | 'after_workspace_epoch_projection_insert' |
| | 'after_workspace_version_projection_insert' |
| | 'after_workspace_head_projection_insert' |
| | 'after_workspace_successor_event_insert' |
| | 'after_workspace_successor_projection_insert' |
| | 'after_workspace_successor_head_update' |
| | 'after_workspace_canonical_scan'; |
| |
| export interface SqliteRuntimeStoreOptions { |
| failpoint?: (point: SqliteRuntimeStoreFailpoint) => void; |
| readOnly?: boolean; |
| /** @internal Repository connection supplied by the operational DB owner. */ |
| databaseLease?: OperationalStateDatabaseLease; |
| } |
| |
| export interface RuntimeEventBatchImportResult { |
| created: boolean[]; |
| } |
| |
| export interface ToolProjectionRebuildResult { |
| operations: number; |
| journalEvents: number; |
| } |
| |
| export interface ToolJournalEventRecord { |
| journalEventId: string; |
| operationId: string; |
| invocationId: string; |
| runId: string; |
| turnId: string; |
| state: ToolJournalState; |
| runtimeEventId?: string; |
| canonicalArgsHash?: string; |
| recoveryMode?: ToolRecoveryMode; |
| externalHandle?: string; |
| metadata?: unknown; |
| committedAt: number; |
| } |
| |
| export function createSqliteRuntimeStore( |
| path: string, |
| options: SqliteRuntimeStoreOptions = {}, |
| ): SqliteRuntimeStore { |
| return new SqliteRuntimeStore(path, options); |
| } |
| |
| export class SqliteRuntimeStore |
| implements |
| RuntimeRecoveryBundleStore, |
| RuntimeContinuationAuthorityStore, |
| RuntimeWorkspaceVersionAuthorityStore |
| { |
| readonly durability = 'canonical' as const; |
| readonly toolBoundaryProtocol = 't1_after_preflight_v1' as const; |
| readonly recoveryBundleCapability = TOOL_RECOVERY_BUNDLE_CAPABILITY_V1; |
| readonly continuationAuthorityCapability = RUNTIME_CONTINUATION_AUTHORITY_V1; |
| readonly workspaceVersionAuthorityCapability = WORKSPACE_VERSION_AUTHORITY_CAPABILITY_V1; |
| private readonly db: DatabaseSync; |
| private readonly databaseLease?: OperationalStateDatabaseLease; |
| private toolLedgerHealth: ToolLedgerHealth | undefined; |
| private closed = false; |
| private readonly uncommittedEventSessions = new Set<string>(); |
| private readonly eventCommitListeners = new Set<(sessionId: string) => void>(); |
| |
| constructor( |
| path: string, |
| private readonly options: SqliteRuntimeStoreOptions = {}, |
| ) { |
| if (options.readOnly && options.databaseLease) { |
| throw new Error('Operational state database leases cannot be opened read-only'); |
| } |
| if (path !== ':memory:' && !options.readOnly) mkdirSync(dirname(path), { recursive: true }); |
| if (options.databaseLease) { |
| this.databaseLease = options.databaseLease; |
| this.db = options.databaseLease.database; |
| assertRecoveryAuthorityCapability(this.db); |
| assertContinuationAuthorityCapability(this.db); |
| assertWorkspaceVersionAuthorityCapability(this.db); |
| registerRecallFoldFunction(this.db); |
| if (!options.readOnly) { |
| this.registerWorkspaceBaselineAuthorityWriter(); |
| this.refreshToolLedgerHealth(); |
| } |
| return; |
| } |
| const DatabaseSync = loadDatabaseSync(); |
| this.db = options.readOnly |
| ? new DatabaseSync(path, { readOnly: true }) |
| : new DatabaseSync(path); |
| try { |
| if (options.readOnly) { |
| configureSqliteRuntimeReadOnlyDatabase(this.db); |
| const version = readUserVersion(this.db); |
| if (version !== SQLITE_RUNTIME_SCHEMA_VERSION) { |
| throw new Error( |
| `SQLite runtime schema ${version} cannot be read without upgrading to ${SQLITE_RUNTIME_SCHEMA_VERSION}`, |
| ); |
| } |
| } else { |
| configureSqliteRuntimeDatabase(this.db); |
| migrateSqliteRuntimeDatabase(this.db); |
| } |
| assertRecoveryAuthorityCapability(this.db); |
| assertContinuationAuthorityCapability(this.db); |
| assertWorkspaceVersionAuthorityCapability(this.db); |
| registerRecallFoldFunction(this.db); |
| if (!options.readOnly) { |
| this.registerWorkspaceBaselineAuthorityWriter(); |
| this.refreshToolLedgerHealth(); |
| } |
| } catch (error) { |
| this.db.close(); |
| this.closed = true; |
| throw error; |
| } |
| } |
| |
| schemaVersion(): number { |
| return readUserVersion(this.db); |
| } |
| |
| journalMode(): string { |
| const row = this.db.prepare('PRAGMA journal_mode').get() as |
| | { journal_mode?: unknown } |
| | undefined; |
| return typeof row?.journal_mode === 'string' ? row.journal_mode.toLowerCase() : ''; |
| } |
| |
| foreignKeysEnabled(): boolean { |
| const row = this.db.prepare('PRAGMA foreign_keys').get() as |
| | { foreign_keys?: unknown } |
| | undefined; |
| return row?.foreign_keys === 1; |
| } |
| |
| close(): void { |
| if (this.closed) return; |
| this.closed = true; |
| if (this.databaseLease) this.databaseLease.close(); |
| else this.db.close(); |
| } |
| |
| async appendRuntimeEvent( |
| sessionId: string, |
| runId: string, |
| event: RuntimeEvent, |
| _options: { durable?: boolean } = {}, |
| ): Promise<void> { |
| const canonicalEvent = canonicalizeRuntimeEventForStorage(event); |
| assertNoReservedToolLedgerFact(canonicalEvent); |
| await this.importRuntimeEvent(sessionId, runId, canonicalEvent); |
| } |
| |
| async appendRuntimePartialBatch( |
| sessionId: string, |
| runId: string, |
| events: readonly RuntimeEvent[], |
| ): Promise<void> { |
| if (events.length === 0) return; |
| const canonicalEvents = events.map(canonicalizeRuntimeEventForStorage); |
| for (const event of canonicalEvents) { |
| assertNoReservedToolLedgerFact(event); |
| if (sessionId !== event.sessionId || runId !== event.runId) { |
| throw new Error(`RuntimeEvent store identity does not match event ${event.id}`); |
| } |
| } |
| this.transaction(() => this.importRuntimePartialBatchSync(canonicalEvents)); |
| } |
| |
| async ensureTerminalRuntimeEventDurable( |
| sessionId: string, |
| runId: string, |
| event: RuntimeEvent, |
| ): Promise<void> { |
| const canonicalEvent = canonicalizeRuntimeEventForStorage(event); |
| assertNoReservedToolLedgerFact(canonicalEvent); |
| if (isPartialRuntimeEvent(canonicalEvent) || !isTerminalRuntimeEvent(canonicalEvent)) { |
| throw new Error( |
| 'Only a final terminal RuntimeEvent can cross the terminal durability barrier', |
| ); |
| } |
| const existing = await this.readImmutableRuntimeEvents(sessionId, runId); |
| const matching = existing.filter((candidate) => candidate.id === canonicalEvent.id); |
| if (matching.length > 1) { |
| throw new Error(`RuntimeEvent ${canonicalEvent.id} appears more than once in run ${runId}`); |
| } |
| if (matching.length === 1) { |
| if (!isDeepStrictEqual(matching[0], canonicalEvent)) { |
| throw new Error( |
| `RuntimeEvent ${canonicalEvent.id} does not match the durable ledger record`, |
| ); |
| } |
| const terminalEvents = existing.filter(isTerminalRuntimeEvent); |
| if ( |
| terminalEvents.length !== 1 || |
| terminalEvents[0]?.id !== canonicalEvent.id || |
| existing.at(-1)?.id !== canonicalEvent.id |
| ) { |
| throw new Error('Terminal RuntimeEvent must be the immutable ledger tail'); |
| } |
| return; |
| } |
| const existingTerminal = existing.find(isTerminalRuntimeEvent); |
| if (existingTerminal) { |
| throw new Error(`Run ${runId} already has terminal RuntimeEvent ${existingTerminal.id}`); |
| } |
| await this.importRuntimeEvent(sessionId, runId, canonicalEvent); |
| } |
| |
| async importRuntimeEvent( |
| sessionId: string, |
| runId: string, |
| event: RuntimeEvent, |
| ): Promise<boolean> { |
| const canonicalEvent = canonicalizeRuntimeEventForStorage(event); |
| assertNoReservedToolLedgerFact(canonicalEvent); |
| if (sessionId !== canonicalEvent.sessionId || runId !== canonicalEvent.runId) { |
| throw new Error(`RuntimeEvent store identity does not match event ${canonicalEvent.id}`); |
| } |
| return this.transaction(() => this.importRuntimeEventSync(canonicalEvent)); |
| } |
| |
| async importRuntimeEventsBatch(input: { |
| sessionId: string; |
| runId: string; |
| events: readonly RuntimeEvent[]; |
| }): Promise<RuntimeEventBatchImportResult> { |
| const events = input.events.map(canonicalizeRuntimeEventForStorage); |
| for (const event of events) { |
| assertNoReservedToolLedgerFact(event); |
| if (event.sessionId !== input.sessionId || event.runId !== input.runId) { |
| throw new Error(`RuntimeEvent store identity does not match event ${event.id}`); |
| } |
| } |
| return this.transaction(() => { |
| if (events.some(isToolLedgerBearingEvent)) { |
| this.assertToolLedgerTransition(events, 'generic_append'); |
| } |
| const created = events.map((event) => this.importRuntimeEventSync(event)); |
| return { created }; |
| }); |
| } |
| |
| async importConversationCopyRuntimeEvents( |
| sessionId: string, |
| batches: readonly ConversationCopyRuntimeEventBatch[], |
| ): Promise<void> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| const canonicalBatches = batches.map(({ runId, events }) => { |
| assertRuntimeStorageSafeId(runId, 'Invalid run id'); |
| return { |
| runId, |
| events: events.map(canonicalizeRuntimeEventForStorage), |
| }; |
| }); |
| const canonicalEvents = canonicalBatches.flatMap(({ events }) => events); |
| if (new Set(canonicalEvents.map(({ id }) => id)).size !== canonicalEvents.length) { |
| throw new Error('Conversation copy contains duplicate RuntimeEvents'); |
| } |
| for (const { runId, events } of canonicalBatches) { |
| for (const event of events) { |
| assertNoReservedWorkspaceAuthorityAppend(event); |
| if (isPartialRuntimeEvent(event)) { |
| throw new Error('Conversation copy cannot import partial RuntimeEvents'); |
| } |
| if (event.sessionId !== sessionId || event.runId !== runId) { |
| throw new Error(`RuntimeEvent store identity does not match event ${event.id}`); |
| } |
| } |
| } |
| const scan = scanToolLedger(canonicalEvents); |
| if (scan.hasCorruption) { |
| throw new Error( |
| `Conversation copy RuntimeEvent ledger is corrupt: ${scan.issues[0]?.code ?? 'unknown'}`, |
| ); |
| } |
| this.transaction(() => { |
| const eventsByRun = new Map<string, RuntimeEvent[]>(); |
| for (const { runId, events } of canonicalBatches) { |
| eventsByRun.set(runId, [...(eventsByRun.get(runId) ?? []), ...events]); |
| } |
| const newRunIds = new Set<string>(); |
| for (const [runId, events] of eventsByRun) { |
| const existing = ( |
| this.db |
| .prepare(` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, payload_json |
| FROM runtime_events |
| WHERE session_id = ? AND run_id = ? |
| ORDER BY event_seq ASC, event_id ASC |
| `) |
| .all(sessionId, runId) as unknown as RuntimeEventStorageRow[] |
| ).map(decodeRuntimeEventStorageRow); |
| if (existing.length > 0 && !isDeepStrictEqual(existing, events)) { |
| throw new Error(`Conversation copy RuntimeEvent identity conflict for run ${runId}`); |
| } |
| if (existing.length === 0) newRunIds.add(runId); |
| } |
| for (const { runId, events } of canonicalBatches) { |
| if (!newRunIds.has(runId)) continue; |
| for (const event of events) this.insertRuntimeEvent(event, event.ts, true); |
| } |
| if (canonicalEvents.some(isToolLedgerBearingEvent)) { |
| this.rebuildToolProjectionsFromRuntimeEventsSync(sessionId); |
| } |
| }); |
| } |
| |
| async readRuntimeEvents(sessionId: string, runId: string): Promise<RuntimeEvent[]> { |
| return this.readRuntimeEventsSync(sessionId, runId); |
| } |
| |
| private transcriptQuery(): RuntimeTranscriptQuery { |
| return new RuntimeTranscriptQuery(this.db, (sessionId, invocationId) => { |
| // By invocation rather than by run: both shelves key their opening on it, |
| // so a page's records cost the page instead of the Session's Turns. |
| const opening = this.readInvocationOpeningsSync(sessionId, { |
| direction: 'asc', |
| invocationId, |
| }).at(0); |
| if (!opening) throw new Error(`Transcript invocation ${invocationId} is missing`); |
| return this.completeInvocationRecordSync(opening); |
| }); |
| } |
| |
| async readTranscriptHighWater(sessionId: string): Promise<number | null> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| return this.readTransaction(() => this.transcriptQuery().highWater(sessionId)); |
| } |
| |
| async readTranscriptRun<T>( |
| sessionId: string, |
| request: RuntimeTranscriptRunRequest, |
| project: ( |
| run: RuntimeTranscriptRun, |
| events: Iterable<{ readonly ordinal: number; readonly event: RuntimeEvent }>, |
| ) => T, |
| ): Promise<T | undefined> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| return this.readTransaction(() => this.transcriptQuery().run(sessionId, request, project)); |
| } |
| |
| async readTranscriptTurns( |
| sessionId: string, |
| request: RuntimeTranscriptTurnsRequest, |
| ): Promise<RuntimeTranscriptTurn[]> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| return this.readTransaction(() => this.transcriptQuery().turns(sessionId, request)); |
| } |
| |
| async readTranscriptTurnCrossing(sessionId: string, ordinal: number): Promise<boolean> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| return this.readTransaction(() => this.transcriptQuery().crossing(sessionId, ordinal)); |
| } |
| |
| /** |
| * Enumerate a Session's invocations: the opening fact names each one, and its |
| * highest-sequence event says whether it ended. |
| * |
| * Invocations that predate the opening fact could not be given one without |
| * rewriting an immutable sequence, so the migration parked their openings in |
| * `runtime_legacy_invocation_openings`. Both shelves are merged here and the |
| * result says nothing about which one a record came from: an opening is an |
| * opening, and a consumer that branched on its storage would be encoding the |
| * migration window into its own logic. |
| */ |
| async listSessionInvocations(sessionId: string): Promise<RuntimeInvocationRecord[]> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| return this.readTransaction(() => |
| this.readInvocationOpeningsSync(sessionId, { direction: 'asc' }).map((row) => |
| this.completeInvocationRecordSync(row), |
| ), |
| ); |
| } |
| |
| async readRunInvocation( |
| sessionId: string, |
| runId: string, |
| ): Promise<RuntimeInvocationRecord | undefined> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| assertRuntimeStorageSafeId(runId, 'Invalid run id'); |
| return this.readTransaction(() => { |
| const row = this.readInvocationOpeningsSync(sessionId, { direction: 'asc', runId }).at(0); |
| return row ? this.completeInvocationRecordSync(row) : undefined; |
| }); |
| } |
| |
| /** |
| * The first page of a Session's invocations, plus whether more exist. |
| * |
| * The extra row this reads past the limit is the whole truncation signal, so a |
| * caller never has to count a Session it declined to load. |
| */ |
| async listSessionInvocationsBounded( |
| sessionId: string, |
| limit: number, |
| ): Promise<RuntimeInvocationSearchResult> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| assertInvocationSearchLimit(limit); |
| return this.readTransaction(() => { |
| const rows = this.readInvocationOpeningsSync(sessionId, { |
| direction: 'asc', |
| limit: limit + 1, |
| }); |
| return { |
| invocations: rows.slice(0, limit).map((row) => this.completeInvocationRecordSync(row)), |
| truncated: rows.length > limit, |
| }; |
| }); |
| } |
| |
| /** One newest-first page of a Session's invocations. */ |
| async listSessionInvocationsPage( |
| sessionId: string, |
| input: RuntimeInvocationPageInput, |
| ): Promise<RuntimeInvocationPageResult> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| assertInvocationSearchLimit(input.limit); |
| if (input.before) { |
| assertRuntimeStorageSafeId(input.before.invocationId, 'Invalid invocation page cursor'); |
| if (!Number.isFinite(input.before.openedAt)) { |
| throw new Error('Invalid invocation page cursor'); |
| } |
| } |
| return this.readTransaction(() => { |
| const rows = this.readInvocationOpeningsSync(sessionId, { |
| direction: 'desc', |
| limit: input.limit + 1, |
| ...(input.before ? { before: input.before } : {}), |
| }); |
| const page = rows.slice(0, input.limit); |
| const last = page.at(-1); |
| return { |
| invocations: page.map((row) => this.completeInvocationRecordSync(row)), |
| nextCursor: |
| rows.length > input.limit && last |
| ? { openedAt: last.openedAt, invocationId: last.invocationId } |
| : null, |
| }; |
| }); |
| } |
| |
| /** |
| * One invocation named by its own identity. |
| * |
| * Absence throws rather than returning `undefined`: every caller here holds an |
| * invocation id that some durable fact already handed it, so a missing opening |
| * is corruption and not a branch a reader should be asked to handle. |
| */ |
| async readInvocation(sessionId: string, invocationId: string): Promise<RuntimeInvocationRecord> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| assertRuntimeStorageSafeId(invocationId, 'Invalid invocation id'); |
| return this.readTransaction(() => { |
| const row = this.readInvocationOpeningsSync(sessionId, { |
| direction: 'asc', |
| invocationId, |
| }).at(0); |
| if (!row) throw new Error(`Runtime invocation not found: ${invocationId}`); |
| return this.completeInvocationRecordSync(row); |
| }); |
| } |
| |
| /** |
| * Read invocation openings off both shelves as one ordered sequence. |
| * |
| * Every writer of an opening event stamps `committed_at` with the event's own |
| * timestamp, so that column orders the event shelf by the same value the |
| * record reports as `openedAt` and the legacy shelf keeps under `opened_at`. |
| * Ordering and paging therefore happen in SQL, and a bounded caller decodes |
| * only the openings it asked for. |
| */ |
| private readInvocationOpeningsSync( |
| sessionId: string, |
| options: { |
| direction: 'asc' | 'desc'; |
| limit?: number; |
| before?: RuntimeInvocationPageCursor; |
| invocationId?: string; |
| runId?: string; |
| }, |
| ): Omit<RuntimeInvocationRecord, 'terminalEvent'>[] { |
| const order = options.direction === 'desc' ? 'DESC' : 'ASC'; |
| const rows = this.db |
| .prepare(` |
| SELECT * FROM ( |
| SELECT |
| event_id AS event_id, |
| invocation_id AS invocation_id, |
| run_id AS run_id, |
| turn_id AS turn_id, |
| committed_at AS opened_at, |
| payload_json AS opening_json, |
| 1 AS from_events |
| FROM runtime_events |
| WHERE session_id = :sessionId AND event_kind = 'invocation_opened' |
| ${options.runId === undefined ? '' : 'AND run_id = :runId'} |
| ${options.invocationId === undefined ? '' : 'AND invocation_id = :invocationId'} |
| UNION ALL |
| SELECT |
| NULL, |
| legacy.invocation_id, |
| legacy.run_id, |
| legacy.turn_id, |
| legacy.opened_at, |
| legacy.opening_json, |
| 0 |
| FROM runtime_legacy_invocation_openings AS legacy |
| WHERE legacy.session_id = :sessionId |
| ${options.runId === undefined ? '' : 'AND legacy.run_id = :runId'} |
| ${options.invocationId === undefined ? '' : 'AND legacy.invocation_id = :invocationId'} |
| AND NOT EXISTS ( |
| SELECT 1 FROM runtime_events |
| WHERE runtime_events.invocation_id = legacy.invocation_id |
| AND runtime_events.event_kind = 'invocation_opened' |
| ) |
| ) |
| WHERE ( |
| :beforeOpenedAt IS NULL |
| OR opened_at < :beforeOpenedAt |
| OR (opened_at = :beforeOpenedAt AND invocation_id < :beforeInvocationId) |
| ) |
| ORDER BY opened_at ${order}, invocation_id ${order} |
| LIMIT :limit |
| `) |
| .all({ |
| sessionId, |
| ...(options.invocationId === undefined ? {} : { invocationId: options.invocationId }), |
| ...(options.runId === undefined ? {} : { runId: options.runId }), |
| beforeOpenedAt: options.before?.openedAt ?? null, |
| beforeInvocationId: options.before?.invocationId ?? null, |
| limit: options.limit ?? -1, |
| }) as unknown as Array<{ |
| event_id: string | null; |
| invocation_id: string; |
| run_id: string; |
| turn_id: string; |
| opened_at: number; |
| opening_json: string; |
| from_events: number; |
| }>; |
| return rows.map((row) => { |
| if (row.from_events !== 1) { |
| return { |
| sessionId, |
| invocationId: row.invocation_id, |
| runId: row.run_id, |
| turnId: row.turn_id, |
| openedAt: row.opened_at, |
| opening: decodeRuntimeInvocationOpened(JSON.parse(row.opening_json)), |
| }; |
| } |
| const event = decodeRuntimeEventStorageRow({ |
| event_id: row.event_id ?? '', |
| session_id: sessionId, |
| invocation_id: row.invocation_id, |
| run_id: row.run_id, |
| turn_id: row.turn_id, |
| payload_json: row.opening_json, |
| }); |
| const opening = runtimeEventInvocationOpening(event); |
| if (!opening) { |
| throw new Error(`RuntimeEvent ${event.id} is indexed as an opening fact but is not one`); |
| } |
| return { |
| sessionId: event.sessionId, |
| invocationId: event.invocationId, |
| runId: event.runId, |
| turnId: event.turnId, |
| openedAt: event.ts, |
| opening, |
| }; |
| }); |
| } |
| |
| /** |
| * An invocation's ending is its first terminal event, wherever it sits. |
| * |
| * The store seals a run on that event, so for anything it wrote itself the |
| * first terminal is also the only one and the last event. Ledgers written |
| * before the seal existed can carry a straggler after the terminal, and |
| * reading those as unfinished would contradict every other reader of the same |
| * rule: recovery, the read model and continuation resume all take the first |
| * terminal. A ledger that somehow holds two is corrupt, and saying so is the |
| * job of those readers — this inventory feeds Session lists, so it reports the |
| * ending it can see rather than poisoning the whole Session over one run. |
| */ |
| private completeInvocationRecordSync( |
| record: Omit<RuntimeInvocationRecord, 'terminalEvent'>, |
| ): RuntimeInvocationRecord { |
| const terminalRow = this.db |
| .prepare(` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, payload_json |
| FROM runtime_events |
| WHERE invocation_id = ? |
| AND ${TERMINAL_RUNTIME_EVENT_SQL} |
| ORDER BY event_seq ASC |
| LIMIT 1 |
| `) |
| .get(record.invocationId) as unknown as RuntimeEventStorageRow | undefined; |
| const terminal = terminalRow ? decodeRuntimeEventStorageRow(terminalRow) : undefined; |
| return { |
| ...record, |
| ...(terminal && isTerminalRuntimeEvent(terminal) ? { terminalEvent: terminal } : {}), |
| }; |
| } |
| |
| async scanRuntimeEvents( |
| sessionId: string, |
| runId: string, |
| budget: RuntimeEventScanBudget, |
| visit: (events: readonly RuntimeEvent[]) => void, |
| ): Promise<RuntimeEventScanResult> { |
| assertRuntimeEventScanBudget(budget); |
| return this.readTransaction(() => { |
| if (!this.runtimePartialSnapshotFitsScanBudget(sessionId, runId, budget)) { |
| return { status: 'limit_exceeded' }; |
| } |
| const snapshots = this.readRuntimePartialSnapshotsSync(sessionId, runId); |
| const { leading, afterEvent } = groupRuntimePartialSnapshots(snapshots); |
| if (leading.length > 0) { |
| visit(leading.sort(compareRuntimePartialSnapshots).map(({ event }) => event)); |
| } |
| |
| let afterSequence = 0; |
| let immutableRecords = 0; |
| let immutableBytes = 0; |
| for (;;) { |
| const measured = this.db |
| .prepare( |
| ` |
| SELECT event_seq, length(CAST(payload_json AS BLOB)) AS stored_bytes |
| FROM runtime_events |
| WHERE session_id = ? AND run_id = ? AND event_seq > ? |
| ORDER BY event_seq ASC, event_id ASC |
| LIMIT ? |
| `, |
| ) |
| .all(sessionId, runId, afterSequence, RUNTIME_EVENT_SCAN_BATCH_SIZE) as Array<{ |
| event_seq?: unknown; |
| stored_bytes?: unknown; |
| }>; |
| if (measured.length === 0) break; |
| const sequences: number[] = []; |
| let batchBytes = 0; |
| for (const row of measured) { |
| const sequence = requireRuntimeEventScanCount(row.event_seq); |
| const storedBytes = requireRuntimeEventScanCount(row.stored_bytes); |
| if (storedBytes < 1 || storedBytes > budget.maxRecordBytes) { |
| return { status: 'limit_exceeded' }; |
| } |
| if (sequences.length > 0 && batchBytes + storedBytes > budget.maxBatchBytes) break; |
| if ( |
| immutableRecords + 1 > budget.maxImmutableRecords || |
| immutableBytes + storedBytes > budget.maxImmutableBytes |
| ) { |
| return { status: 'limit_exceeded' }; |
| } |
| sequences.push(sequence); |
| batchBytes += storedBytes; |
| immutableRecords += 1; |
| immutableBytes += storedBytes; |
| if (batchBytes >= budget.maxBatchBytes) break; |
| } |
| const placeholders = sequences.map(() => '?').join(', '); |
| const rows = this.db |
| .prepare( |
| ` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, |
| event_seq, payload_json |
| FROM runtime_events |
| WHERE session_id = ? AND run_id = ? AND event_seq IN (${placeholders}) |
| ORDER BY event_seq ASC, event_id ASC |
| `, |
| ) |
| .all(sessionId, runId, ...sequences) as unknown as Array< |
| RuntimeEventStorageRow & { event_seq: number } |
| >; |
| if (rows.length !== sequences.length) { |
| throw new Error('RuntimeEvent scan changed inside its read transaction'); |
| } |
| const batch: RuntimeEvent[] = []; |
| for (const row of rows) { |
| const event = decodeRuntimeEventStorageRow(row); |
| batch.push(event); |
| const anchored = afterEvent.get(event.id); |
| if (anchored) { |
| batch.push( |
| ...anchored.sort(compareRuntimePartialSnapshots).map((snapshot) => snapshot.event), |
| ); |
| afterEvent.delete(event.id); |
| } |
| } |
| visit(batch); |
| afterSequence = rows.at(-1)!.event_seq; |
| } |
| for (const orphaned of afterEvent.values()) { |
| visit(orphaned.sort(compareRuntimePartialSnapshots).map((snapshot) => snapshot.event)); |
| } |
| return { status: 'complete' }; |
| }); |
| } |
| |
| private runtimePartialSnapshotFitsScanBudget( |
| sessionId: string, |
| runId: string, |
| budget: RuntimeEventScanBudget, |
| ): boolean { |
| const rows = this.db |
| .prepare( |
| ` |
| SELECT |
| length(CAST(snapshot.payload_json AS BLOB)) + |
| length(CAST(snapshot.text_content AS BLOB)) + |
| coalesce(sum(length(CAST(segment.text_content AS BLOB))), 0) + |
| coalesce(length(CAST(snapshot.after_event_id AS BLOB)), 0) AS stored_bytes |
| FROM runtime_partial_snapshots AS snapshot |
| LEFT JOIN runtime_partial_segments AS segment |
| ON segment.stream_key = snapshot.stream_key |
| WHERE snapshot.session_id = ? AND snapshot.run_id = ? |
| GROUP BY snapshot.stream_key |
| LIMIT ? |
| `, |
| ) |
| .all(sessionId, runId, budget.maxPartialRecords + 1) as Array<{ stored_bytes?: unknown }>; |
| if (rows.length > budget.maxPartialRecords) return false; |
| let bytes = 0; |
| for (const row of rows) { |
| const storedBytes = requireRuntimeEventScanCount(row.stored_bytes); |
| if (storedBytes < 1 || storedBytes > budget.maxRecordBytes) return false; |
| bytes += storedBytes; |
| if (bytes > budget.maxPartialBytes) return false; |
| } |
| return true; |
| } |
| |
| async readRuntimeEventsBounded( |
| sessionId: string, |
| runId: string, |
| budget: EvidenceReadBudget, |
| ): Promise<BoundedEvidenceReadResult<RuntimeEvent>> { |
| assertEvidenceReadBudget(budget); |
| const rows = this.db |
| .prepare(` |
| SELECT stored_bytes |
| FROM ( |
| SELECT length(CAST(payload_json AS BLOB)) AS stored_bytes |
| FROM runtime_events |
| WHERE session_id = ? AND run_id = ? |
| UNION ALL |
| SELECT |
| length(CAST(payload_json AS BLOB)) + |
| length(CAST(text_content AS BLOB)) + |
| coalesce(( |
| SELECT sum(length(CAST(segment.text_content AS BLOB))) |
| FROM runtime_partial_segments AS segment |
| WHERE segment.stream_key = runtime_partial_snapshots.stream_key |
| ), 0) + |
| coalesce(length(CAST(after_event_id AS BLOB)), 0) AS stored_bytes |
| FROM runtime_partial_snapshots |
| WHERE session_id = ? AND run_id = ? |
| ) |
| LIMIT ? |
| `) |
| .all(sessionId, runId, sessionId, runId, budget.maxRecords + 1) as Array<{ |
| stored_bytes?: unknown; |
| }>; |
| const measurement = measureEvidenceRows( |
| rows, |
| budget, |
| 'Invalid SQLite RuntimeEvent evidence measurement row', |
| ); |
| if (!measurement) return { status: 'limit_exceeded' }; |
| return { |
| status: 'complete', |
| records: this.readRuntimeEventsSync(sessionId, runId), |
| ...measurement, |
| }; |
| } |
| |
| private readRuntimeEventsSync(sessionId: string, runId: string): RuntimeEvent[] { |
| const immutable = this.readImmutableRuntimeEventsSync(sessionId, runId); |
| return mergeRuntimePartialSnapshots( |
| immutable, |
| this.readRuntimePartialSnapshotsSync(sessionId, runId), |
| ); |
| } |
| |
| private readRuntimePartialSnapshotsSync( |
| sessionId: string, |
| runId: string, |
| ): RuntimePartialSnapshot[] { |
| const partials = this.db |
| .prepare(` |
| SELECT stream_key, session_id, invocation_id, run_id, turn_id, |
| payload_json, text_content, after_event_id |
| FROM runtime_partial_snapshots |
| WHERE session_id = ? AND run_id = ? |
| ORDER BY updated_at ASC, stream_key ASC |
| `) |
| .all(sessionId, runId) as unknown as RuntimePartialStorageRow[]; |
| const segmentText = new Map<string, string[]>(); |
| const segments = this.db |
| .prepare(` |
| SELECT segment.stream_key, segment.text_content |
| FROM runtime_partial_segments AS segment |
| INNER JOIN runtime_partial_snapshots AS snapshot |
| ON snapshot.stream_key = segment.stream_key |
| WHERE snapshot.session_id = ? AND snapshot.run_id = ? |
| ORDER BY segment.stream_key ASC, segment.segment_seq ASC |
| `) |
| .iterate(sessionId, runId) as Iterable<{ stream_key: string; text_content: string }>; |
| let streamKey: string | undefined; |
| let chunks: string[] = []; |
| let tail: string[] = []; |
| let tailBytes = 0; |
| const flushTail = () => { |
| if (tail.length === 0) return; |
| chunks.push(tail.join('')); |
| tail = []; |
| tailBytes = 0; |
| }; |
| const flushStream = () => { |
| if (streamKey === undefined) return; |
| flushTail(); |
| segmentText.set(streamKey, chunks); |
| chunks = []; |
| }; |
| for (const segment of segments) { |
| if (typeof segment.stream_key !== 'string' || typeof segment.text_content !== 'string') { |
| throw new Error('Invalid RuntimeEvent partial segment'); |
| } |
| if (segment.stream_key !== streamKey) { |
| flushStream(); |
| streamKey = segment.stream_key; |
| } |
| const bytes = Buffer.byteLength(segment.text_content, 'utf8'); |
| if (bytes === 0) continue; |
| if (bytes > RUNTIME_PARTIAL_SEGMENT_TARGET_BYTES) { |
| flushTail(); |
| chunks.push(segment.text_content); |
| continue; |
| } |
| if (tailBytes + bytes > RUNTIME_PARTIAL_SEGMENT_TARGET_BYTES) flushTail(); |
| tail.push(segment.text_content); |
| tailBytes += bytes; |
| } |
| flushStream(); |
| return partials.flatMap((row) => { |
| try { |
| const event = decodeRuntimePartialStorageRow(row); |
| if (event.content?.kind === 'text' || event.content?.kind === 'thinking') { |
| event.content = { |
| ...event.content, |
| text: row.text_content + (segmentText.get(row.stream_key)?.join('') ?? ''), |
| }; |
| } |
| return [ |
| { |
| event, |
| ...(row.after_event_id ? { afterEventId: row.after_event_id } : {}), |
| }, |
| ]; |
| } catch { |
| // Mutable partial snapshots are presentation state, never ledger |
| // authority. A corrupt snapshot is skipped without hiding immutable |
| // RuntimeEvents from the same run. |
| return []; |
| } |
| }); |
| } |
| |
| async readImmutableRuntimeEvents(sessionId: string, runId: string): Promise<RuntimeEvent[]> { |
| return this.readImmutableRuntimeEventsSync(sessionId, runId); |
| } |
| |
| private readImmutableRuntimeEventsSync(sessionId: string, runId: string): RuntimeEvent[] { |
| const rows = this.db |
| .prepare(` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, payload_json |
| FROM runtime_events |
| WHERE session_id = ? AND run_id = ? |
| ORDER BY event_seq ASC, event_id ASC |
| `) |
| .all(sessionId, runId) as unknown as RuntimeEventStorageRow[]; |
| return rows.map(decodeRuntimeEventStorageRow); |
| } |
| |
| async readImmutableRuntimePrefix(input: { |
| sessionId: string; |
| runId: string; |
| upToEventSeq?: number; |
| }): Promise<ImmutableRuntimePrefixV1> { |
| return this.readImmutableRuntimePrefixSync(input); |
| } |
| |
| private readImmutableRuntimePrefixSync(input: { |
| sessionId: string; |
| runId: string; |
| upToEventSeq?: number; |
| }): ImmutableRuntimePrefixV1 { |
| if ( |
| input.upToEventSeq !== undefined && |
| (!Number.isSafeInteger(input.upToEventSeq) || input.upToEventSeq <= 0) |
| ) { |
| throw new Error('Invalid immutable RuntimeEvent prefix high-water'); |
| } |
| const highWater = input.upToEventSeq ?? null; |
| const rows = this.db |
| .prepare(` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, event_seq, payload_json |
| FROM runtime_events |
| WHERE session_id = ? AND run_id = ? |
| AND (? IS NULL OR event_seq <= ?) |
| ORDER BY event_seq ASC |
| `) |
| .all( |
| input.sessionId, |
| input.runId, |
| highWater, |
| highWater, |
| ) as unknown as RuntimeEventPrefixStorageRow[]; |
| if (rows.length === 0) { |
| throw new Error('immutable RuntimeEvent prefix is empty'); |
| } |
| const lastEventSeq = rows.at(-1)?.event_seq; |
| if (input.upToEventSeq !== undefined && lastEventSeq !== input.upToEventSeq) { |
| throw new Error( |
| `immutable RuntimeEvent prefix high-water ${input.upToEventSeq} is unavailable`, |
| ); |
| } |
| const decoded = rows.map((row) => ({ |
| eventSeq: row.event_seq, |
| event: decodeRuntimeEventStorageRow(row), |
| })); |
| const first = decoded[0]!.event; |
| return buildImmutableRuntimePrefix( |
| { |
| sessionId: first.sessionId, |
| invocationId: first.invocationId, |
| runId: first.runId, |
| turnId: first.turnId, |
| }, |
| decoded, |
| ); |
| } |
| |
| async readImmutableRuntimePrefixProof( |
| input: { sessionId: string; runId: string; upToEventSeq?: number }, |
| budget: ImmutableRuntimePrefixProofReadBudget, |
| ): Promise<ImmutableRuntimePrefixProofV1> { |
| assertImmutableRuntimePrefixProofReadBudget(budget); |
| if ( |
| input.upToEventSeq !== undefined && |
| (!Number.isSafeInteger(input.upToEventSeq) || input.upToEventSeq <= 0) |
| ) { |
| throw new Error('Invalid immutable RuntimeEvent prefix high-water'); |
| } |
| const highWater = input.upToEventSeq ?? null; |
| const cursor = this.db |
| .prepare(` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, event_seq, |
| length(CAST(payload_json AS BLOB)) AS stored_bytes, |
| CASE WHEN length(CAST(payload_json AS BLOB)) <= ? THEN payload_json END AS payload_json |
| FROM runtime_events |
| WHERE session_id = ? AND run_id = ? |
| AND (? IS NULL OR event_seq <= ?) |
| ORDER BY event_seq ASC |
| `) |
| .iterate(budget.maxRecordBytes, input.sessionId, input.runId, highWater, highWater) |
| [Symbol.iterator]() as Iterator<RuntimeEventPrefixProofStorageRow>; |
| const firstRow = cursor.next(); |
| if (firstRow.done) throw new Error('immutable RuntimeEvent prefix is empty'); |
| let count = 0; |
| let bytes = 0; |
| let lastEventSeq = 0; |
| const decode = function* (): Iterable<{ eventSeq: number; event: RuntimeEvent }> { |
| let step: IteratorResult<RuntimeEventPrefixProofStorageRow> = firstRow; |
| while (!step.done) { |
| const row = step.value; |
| count += 1; |
| if (count > budget.maxEvents) { |
| throw new Error('Immutable RuntimeEvent prefix proof exceeds its event limit'); |
| } |
| const payloadJson = row.payload_json; |
| if (payloadJson === null) { |
| throw new Error('Immutable RuntimeEvent prefix proof exceeds its record byte limit'); |
| } |
| bytes += requireRuntimeEventScanCount(row.stored_bytes); |
| if (bytes > budget.maxBytes) { |
| throw new Error('Immutable RuntimeEvent prefix proof exceeds its byte limit'); |
| } |
| lastEventSeq = requireRuntimeEventScanCount(row.event_seq); |
| yield { |
| eventSeq: lastEventSeq, |
| event: decodeRuntimeEventStorageRow({ ...row, payload_json: payloadJson }), |
| }; |
| step = cursor.next(); |
| } |
| }; |
| const proof = buildImmutableRuntimePrefixProof( |
| { |
| sessionId: firstRow.value.session_id, |
| invocationId: firstRow.value.invocation_id, |
| runId: firstRow.value.run_id, |
| turnId: firstRow.value.turn_id, |
| }, |
| decode(), |
| ); |
| if (input.upToEventSeq !== undefined && lastEventSeq !== input.upToEventSeq) { |
| throw new Error( |
| `immutable RuntimeEvent prefix high-water ${input.upToEventSeq} is unavailable`, |
| ); |
| } |
| return proof; |
| } |
| |
| async claimContinuation(input: { claim: ContinuationClaimV1 }): Promise<ContinuationClaimResult> { |
| const claim = decodeContinuationClaim(input.claim); |
| if ( |
| claim.target.sessionId === WORKSPACE_AUTHORITY_SESSION_ID || |
| claim.boundary.segments.some( |
| (segment) => segment.identity.sessionId === WORKSPACE_AUTHORITY_SESSION_ID, |
| ) |
| ) { |
| throw new Error('Continuation cannot target the reserved workspace authority stream'); |
| } |
| const boundaryJson = stableJsonStringify(claim.boundary); |
| return this.transaction(() => { |
| this.assertContinuationAuthorityIntegrity(); |
| this.assertContinuationBoundaryMatchesLedger(claim); |
| const byBoundary = this.readContinuationClaimRow('boundary_digest = ?', claim.boundaryDigest); |
| if (byBoundary) { |
| const existing = decodeContinuationClaimRow(byBoundary); |
| if (byBoundary.boundary_json !== boundaryJson) { |
| throw new Error('Continuation claim boundary digest has conflicting canonical JSON'); |
| } |
| return { kind: 'existing', claim: existing }; |
| } |
| |
| const source = claim.boundary.segments.at(-1)!; |
| const conflict = this.readContinuationClaimRow( |
| `claim_id = ? |
| OR target_invocation_id = ? |
| OR target_run_id = ? |
| OR (? = 0 AND target_session_id = ? AND target_turn_id = ?) |
| OR ( |
| source_session_id = ? |
| AND source_run_id = ? |
| AND source_event_high_water = ? |
| )`, |
| claim.claimId, |
| claim.target.invocationId, |
| claim.target.runId, |
| claim.targetOpening.source.kind === 'handoff' ? 1 : 0, |
| claim.target.sessionId, |
| claim.target.turnId, |
| source.identity.sessionId, |
| source.identity.runId, |
| source.position.lastEventSeq, |
| ); |
| if (conflict) { |
| return { kind: 'conflict', claim: decodeContinuationClaimRow(conflict) }; |
| } |
| if (this.continuationTargetHasRuntimeState(claim)) { |
| throw new Error('Continuation claim target RuntimeEvent ledger is not empty'); |
| } |
| |
| try { |
| this.db |
| .prepare(` |
| INSERT INTO runtime_continuation_claims ( |
| claim_id, |
| source_session_id, |
| source_invocation_id, |
| source_run_id, |
| source_turn_id, |
| source_event_high_water, |
| source_prefix_digest, |
| boundary_digest, |
| boundary_json, |
| provider_projection_version, |
| provider_replay_digest, |
| target_session_id, |
| target_invocation_id, |
| target_run_id, |
| target_turn_id, |
| target_opening_json, |
| claimed_at, |
| protocol_version |
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1) |
| `) |
| .run( |
| claim.claimId, |
| source.identity.sessionId, |
| source.identity.invocationId, |
| source.identity.runId, |
| source.identity.turnId, |
| source.position.lastEventSeq, |
| source.prefixDigest, |
| claim.boundaryDigest, |
| boundaryJson, |
| claim.providerProjectionVersion, |
| claim.providerReplayDigest, |
| claim.target.sessionId, |
| claim.target.invocationId, |
| claim.target.runId, |
| claim.target.turnId, |
| stableJsonStringify(claim.targetOpening), |
| claim.claimedAt, |
| ); |
| } catch (error) { |
| const raced = |
| this.readContinuationClaimRow('boundary_digest = ?', claim.boundaryDigest) ?? |
| this.readContinuationClaimRow( |
| `claim_id = ? |
| OR target_invocation_id = ? |
| OR target_run_id = ? |
| OR (? = 0 AND target_session_id = ? AND target_turn_id = ?) |
| OR ( |
| source_session_id = ? |
| AND source_run_id = ? |
| AND source_event_high_water = ? |
| )`, |
| claim.claimId, |
| claim.target.invocationId, |
| claim.target.runId, |
| claim.targetOpening.source.kind === 'handoff' ? 1 : 0, |
| claim.target.sessionId, |
| claim.target.turnId, |
| source.identity.sessionId, |
| source.identity.runId, |
| source.position.lastEventSeq, |
| ); |
| if (!raced) throw error; |
| const racedClaim = decodeContinuationClaimRow(raced); |
| return racedClaim.boundaryDigest === claim.boundaryDigest |
| ? { kind: 'existing', claim: racedClaim } |
| : { kind: 'conflict', claim: racedClaim }; |
| } |
| this.options.failpoint?.('after_continuation_claim_insert'); |
| return { kind: 'acquired', claim }; |
| }); |
| } |
| |
| async readContinuationClaimByBoundary( |
| boundaryDigest: RuntimeBoundaryDigest, |
| ): Promise<ContinuationClaimV1 | undefined> { |
| return (await this.readContinuationClaimStateByBoundary(boundaryDigest))?.claim; |
| } |
| |
| async readContinuationClaimStateByBoundary( |
| boundaryDigest: RuntimeBoundaryDigest, |
| ): Promise<ContinuationClaimStateV1 | undefined> { |
| if (!/^sha256:[0-9a-f]{64}$/.test(boundaryDigest)) { |
| throw new Error('Invalid continuation boundary digest'); |
| } |
| const row = this.readContinuationClaimRow('boundary_digest = ?', boundaryDigest); |
| return row ? this.decodeContinuationClaimStateRow(row) : undefined; |
| } |
| |
| async listContinuationClaimsForRecovery(sessionId: string): Promise<ContinuationClaimStateV1[]> { |
| const rows = this.db |
| .prepare(` |
| SELECT |
| claim_id, |
| source_session_id, |
| source_invocation_id, |
| source_run_id, |
| source_turn_id, |
| source_event_high_water, |
| source_prefix_digest, |
| boundary_digest, |
| boundary_json, |
| provider_projection_version, |
| provider_replay_digest, |
| target_session_id, |
| target_invocation_id, |
| target_run_id, |
| target_turn_id, |
| target_opening_json, |
| claimed_at, |
| start_event_id, |
| start_kind, |
| protocol_version |
| FROM runtime_continuation_claims |
| WHERE target_session_id = ? |
| ORDER BY claimed_at ASC, claim_id ASC |
| `) |
| .all(sessionId) as unknown as ContinuationClaimStorageRow[]; |
| return rows.map((row) => this.decodeContinuationClaimStateRow(row)); |
| } |
| |
| async commitContinuationStart(input: { |
| claim: ContinuationClaimV1; |
| event: RuntimeEvent; |
| }): Promise<ToolCommitResult> { |
| return this.commitContinuationStartOfKind(input, 'runtime_admission'); |
| } |
| |
| async commitContinuationRepairStart(input: { |
| claim: ContinuationClaimV1; |
| event: RuntimeEvent; |
| }): Promise<ToolCommitResult> { |
| return this.commitContinuationStartOfKind(input, 'claim_repair'); |
| } |
| |
| private commitContinuationStartOfKind( |
| input: { |
| claim: ContinuationClaimV1; |
| event: RuntimeEvent; |
| }, |
| startKind: 'runtime_admission' | 'claim_repair', |
| ): ToolCommitResult { |
| const claim = decodeContinuationClaim(input.claim); |
| const event = canonicalizeRuntimeEventForStorage(input.event); |
| assertNoReservedWorkspaceAuthorityAppend(event); |
| assertContinuationStartEvent(claim, event, startKind); |
| return this.transaction(() => { |
| const row = this.readContinuationClaimRow('boundary_digest = ?', claim.boundaryDigest); |
| if (!row) { |
| throw new Error('Continuation start requires an acquired durable claim'); |
| } |
| const storedClaim = decodeContinuationClaimRow(row); |
| if (!isDeepStrictEqual(storedClaim, claim)) { |
| throw new Error('Continuation start claim identity conflict'); |
| } |
| if (row.start_event_id) { |
| if (row.start_event_id !== event.id || row.start_kind !== startKind) { |
| throw new Error('Continuation claim already has a different start event'); |
| } |
| assertStoredRuntimeEventEquals(event, this.readRuntimeEventJson(event.id)); |
| return { created: false, runtimeEventSeq: this.runtimeEventSeq(event.id) }; |
| } |
| this.assertInvocationIdentity([event]); |
| const runtimeEventSeq = this.insertRuntimeEvent(event, event.ts, false, claim.claimId); |
| if (runtimeEventSeq !== 1) { |
| throw new Error('Continuation start must be the first target RuntimeEvent'); |
| } |
| this.options.failpoint?.('after_continuation_start_insert'); |
| this.db |
| .prepare(` |
| UPDATE runtime_continuation_claims |
| SET start_event_id = ?, start_kind = ? |
| WHERE claim_id = ? AND start_event_id IS NULL |
| `) |
| .run(event.id, startKind, claim.claimId); |
| return { created: true, runtimeEventSeq }; |
| }); |
| } |
| |
| async readImmutableSteeringMessageProof( |
| sessionId: string, |
| messageId: string, |
| ): Promise<ImmutableSteeringMessageProof | undefined> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| assertRuntimeStorageSafeId(messageId, 'Invalid message id'); |
| const matches = this.readImmutableSessionRuntimeEvents(sessionId).filter( |
| (event) => immutableSteeringMessageId(event) === messageId, |
| ); |
| if (matches.length > 1) { |
| throw new Error(`Immutable steering message identity conflict: ${messageId}`); |
| } |
| return matches[0] ? Object.freeze({ event: matches[0] }) : undefined; |
| } |
| |
| async repairImmutableSteeringMessageProofsForRecovery(sessionId: string): Promise<void> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| const messages = new Map<string, RuntimeEvent>(); |
| for (const event of this.readImmutableSessionRuntimeEvents(sessionId)) { |
| const messageId = immutableSteeringMessageId(event); |
| if (!messageId) continue; |
| const existing = messages.get(messageId); |
| if (existing && !isDeepStrictEqual(existing, event)) { |
| throw new Error(`Immutable steering message identity conflict: ${messageId}`); |
| } |
| messages.set(messageId, event); |
| } |
| } |
| |
| /** |
| * Narrows recall to the Sessions whose ledger could project a message |
| * containing one of the folded terms. The answer is a superset of the true |
| * matches, never an answer: the caller projects each candidate Session and |
| * re-runs the real predicate on the projected, redacted text. |
| * |
| * Every event payload is scanned, whatever its kind: a message's visible |
| * text — a user or model `text`, a `function_call` intent, the string values |
| * of a `function_response` result — is a JSON string value of the event that |
| * carries it, so a term inside the projected text is inside the payload |
| * (escaped forms excepted, which the caller routes around). Kinds that never |
| * project only cost the scan a little work. |
| * |
| * A Session with an in-flight partial stream is offered unconditionally: its |
| * arriving text lives in segments a per-row `instr` could straddle, and the |
| * read model presents that text as settled. |
| */ |
| async listSessionsWithRuntimeEventText( |
| sessionIds: readonly string[], |
| terms: readonly string[], |
| ): Promise<string[]> { |
| if (sessionIds.length === 0 || terms.length === 0) return []; |
| for (const sessionId of sessionIds) assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| for (const term of terms) assertFoldedSearchTerm(term); |
| const sessions = sessionIds.map(() => '?').join(', '); |
| const rows = this.db |
| .prepare( |
| ` |
| SELECT DISTINCT session_id |
| FROM runtime_events |
| WHERE session_id IN (${sessions}) |
| AND (${recallFoldedMatchClause('payload_json', terms.length)}) |
| UNION |
| SELECT DISTINCT session_id |
| FROM runtime_partial_snapshots |
| WHERE session_id IN (${sessions}) |
| `, |
| ) |
| .all(...sessionIds, ...terms, ...sessionIds) as Array<{ session_id?: unknown }>; |
| return rows.map((row) => { |
| if (typeof row.session_id !== 'string') throw new Error('Invalid recall candidate row'); |
| return row.session_id; |
| }); |
| } |
| |
| /** |
| * How many ledger events could project to a searchable message, for |
| * recall's idf term. Counted by event kind rather than by projecting, so it |
| * is cheap and identical whichever path recall takes to find its hits. |
| */ |
| async countRuntimeEventMessages(sessionIds: readonly string[]): Promise<number> { |
| if (sessionIds.length === 0) return 0; |
| for (const sessionId of sessionIds) assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| const sessions = sessionIds.map(() => '?').join(', '); |
| const row = this.db |
| .prepare( |
| ` |
| SELECT count(*) AS total |
| FROM runtime_events |
| WHERE session_id IN (${sessions}) |
| AND event_kind IN ('text', 'function_call', 'function_response') |
| `, |
| ) |
| .get(...sessionIds) as { total?: unknown } | undefined; |
| return typeof row?.total === 'number' ? row.total : 0; |
| } |
| |
| async readSessionRuntimeEvents(sessionId: string): Promise<RuntimeEvent[]> { |
| const rows = this.db |
| .prepare(` |
| SELECT run_id FROM runtime_events WHERE session_id = ? |
| UNION |
| SELECT run_id FROM runtime_partial_snapshots WHERE session_id = ? |
| ORDER BY run_id ASC |
| `) |
| .all(sessionId, sessionId) as Array<{ run_id: string }>; |
| const ordered: Array<{ event: RuntimeEvent; runId: string; eventIndex: number }> = []; |
| for (const row of rows) { |
| const events = await this.readRuntimeEvents(sessionId, row.run_id); |
| for (let eventIndex = 0; eventIndex < events.length; eventIndex += 1) { |
| ordered.push({ event: events[eventIndex]!, runId: row.run_id, eventIndex }); |
| } |
| } |
| ordered.sort( |
| (a, b) => |
| a.event.ts - b.event.ts || |
| a.runId.localeCompare(b.runId) || |
| a.eventIndex - b.eventIndex || |
| a.event.id.localeCompare(b.event.id), |
| ); |
| return ordered.map((item) => item.event); |
| } |
| |
| async readSessionRuntimeEventEntries(sessionId: string): Promise<SessionRuntimeEventEntry[]> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| const rows = this.db |
| .prepare(` |
| SELECT o.ordinal, e.event_id, e.session_id, e.invocation_id, e.run_id, e.turn_id, |
| e.payload_json |
| FROM runtime_session_event_ordinals o |
| JOIN runtime_events e ON e.event_id = o.event_id |
| WHERE o.session_id = ? |
| ORDER BY o.ordinal ASC |
| `) |
| .all(sessionId) as unknown as Array<RuntimeEventStorageRow & { ordinal: unknown }>; |
| return rows.map((row) => { |
| if ( |
| typeof row.ordinal !== 'number' || |
| !Number.isSafeInteger(row.ordinal) || |
| row.ordinal < 1 |
| ) { |
| throw new Error(`Invalid RuntimeEvent Session ordinal for ${sessionId}`); |
| } |
| const event = decodeRuntimeEventStorageRow(row); |
| if (event.sessionId !== sessionId) { |
| throw new Error(`RuntimeEvent Session ordinal identity mismatch for ${event.id}`); |
| } |
| return { ordinal: row.ordinal, event }; |
| }); |
| } |
| |
| async resequenceSessionEventOrdinals(sessionId: string): Promise<void> { |
| assertRuntimeStorageSafeId(sessionId, 'Invalid session id'); |
| this.transaction(() => { |
| // Lifted above the range first: the second statement renumbers into the |
| // space these rows occupy, and (session_id, ordinal) is a primary key. |
| // Shifting up rather than below zero keeps every intermediate value |
| // inside the table's own `ordinal > 0`, and lands them past the 1..N the |
| // renumber assigns, since the count cannot exceed the maximum. |
| const { shift } = this.db |
| .prepare(` |
| SELECT COALESCE(MAX(ordinal), 0) AS shift |
| FROM runtime_session_event_ordinals |
| WHERE session_id = ? |
| `) |
| .get(sessionId) as { shift: number }; |
| this.db |
| .prepare(` |
| UPDATE runtime_session_event_ordinals |
| SET ordinal = ordinal + :shift |
| WHERE session_id = :sessionId |
| `) |
| .run({ sessionId, shift }); |
| this.db |
| .prepare(` |
| WITH opening AS ( |
| SELECT invocation_id, CAST(json_extract(payload_json, '$.ts') AS INTEGER) AS opened_at |
| FROM runtime_events |
| WHERE session_id = :sessionId AND event_kind = 'invocation_opened' |
| ), |
| ordered AS MATERIALIZED ( |
| SELECT |
| o.event_id AS event_id, |
| ROW_NUMBER() OVER ( |
| ORDER BY COALESCE(opening.opened_at, e.committed_at), e.invocation_id, o.ordinal |
| ) AS ordinal |
| FROM runtime_session_event_ordinals o |
| JOIN runtime_events e ON e.event_id = o.event_id |
| LEFT JOIN opening ON opening.invocation_id = e.invocation_id |
| WHERE o.session_id = :sessionId |
| ) |
| UPDATE runtime_session_event_ordinals |
| SET ordinal = ( |
| SELECT ordered.ordinal |
| FROM ordered |
| WHERE ordered.event_id = runtime_session_event_ordinals.event_id |
| ) |
| WHERE session_id = :sessionId |
| `) |
| .run({ sessionId }); |
| rebuildTranscriptTurnExtents(this.db, sessionId); |
| }); |
| } |
| |
| async #commitWorkspaceBaseline( |
| input: WorkspaceBaselineAuthorityInput, |
| rootId: string, |
| ): Promise<WorkspaceBaselineCommitResult> { |
| const events = buildWorkspaceBaselineAuthorityEvents(input); |
| return this.transaction(() => { |
| this.#assertWorkspaceStorageRootBinding(rootId); |
| const existingAuthority = this.readCanonicalWorkspaceAuthoritySync(); |
| const existingBaselines = existingAuthority.baselines; |
| const existing = existingBaselines.find( |
| (candidate) => |
| candidate.epoch.workspaceId === input.epoch.workspaceId && |
| candidate.epoch.workspaceEpochId === input.epoch.workspaceEpochId, |
| ); |
| if (existing) { |
| this.assertWorkspaceProjectionsMatchSync(existingAuthority); |
| if ( |
| !isDeepStrictEqual( |
| [ |
| this.readRequiredRuntimeEvent(existing.epochOpenedEventId), |
| this.readRequiredRuntimeEvent(existing.baselineAcceptedEventId), |
| ], |
| [events.epochOpenedEvent, events.baselineAcceptedEvent], |
| ) |
| ) { |
| throw new Error('Workspace baseline authority conflict'); |
| } |
| const head = existingAuthority.heads.find( |
| (candidate) => candidate.workspaceEpochId === input.epoch.workspaceEpochId, |
| ); |
| if (!head) throw new Error('Workspace baseline authority head is unavailable'); |
| return { created: false, head }; |
| } |
| |
| if (this.workspaceProjectionCountSync() !== 0 || existingBaselines.length !== 0) { |
| this.assertWorkspaceProjectionsMatchSync(existingAuthority); |
| } |
| this.assertWorkspaceAuthorityStreamIsEmpty(events.epochOpenedEvent); |
| this.assertInvocationIdentity([events.epochOpenedEvent, events.baselineAcceptedEvent]); |
| const epochEventSeq = this.insertRuntimeEvent( |
| events.epochOpenedEvent, |
| input.committedAt, |
| false, |
| ); |
| if (epochEventSeq !== 1) { |
| throw new Error('Workspace epoch-opened fact must be authority sequence one'); |
| } |
| this.options.failpoint?.('after_workspace_epoch_event_insert'); |
| const baselineEventSeq = this.insertRuntimeEvent( |
| events.baselineAcceptedEvent, |
| input.committedAt, |
| false, |
| ); |
| if (baselineEventSeq !== 2) { |
| throw new Error('Workspace baseline version fact must be authority sequence two'); |
| } |
| this.options.failpoint?.('after_workspace_version_event_insert'); |
| |
| const scanned = this.readCanonicalWorkspaceAuthoritySync(); |
| const accepted = scanned.baselines.find( |
| (candidate) => candidate.epoch.workspaceEpochId === input.epoch.workspaceEpochId, |
| ); |
| if (!accepted) throw new Error('Workspace baseline authority scan lost the committed epoch'); |
| this.insertWorkspaceEpochProjection(accepted, input.committedAt); |
| this.options.failpoint?.('after_workspace_epoch_projection_insert'); |
| this.insertWorkspaceBaselineVersionProjection(accepted, input.committedAt); |
| this.options.failpoint?.('after_workspace_version_projection_insert'); |
| const acceptedHead = scanned.heads.find( |
| (candidate) => candidate.workspaceEpochId === input.epoch.workspaceEpochId, |
| ); |
| if (!acceptedHead) throw new Error('Workspace baseline authority scan lost its head'); |
| this.insertWorkspaceHeadProjection(acceptedHead); |
| this.options.failpoint?.('after_workspace_head_projection_insert'); |
| this.assertWorkspaceProjectionsMatchSync(scanned); |
| const head = scanned.heads.find( |
| (candidate) => candidate.workspaceEpochId === input.epoch.workspaceEpochId, |
| ); |
| if (!head) throw new Error('Workspace baseline authority scan lost the committed head'); |
| return { created: true, head }; |
| }); |
| } |
| |
| async #commitWorkspaceSuccessor( |
| input: { |
| successor: WorkspaceSuccessorAuthorityInput; |
| toolOutcome: WorkspaceSuccessorCommitInput['toolOutcome']; |
| }, |
| rootId: string, |
| ): Promise<WorkspaceSuccessorCommitResult> { |
| const toolOutcome: CommitToolOutcomeInput = { |
| ...input.toolOutcome, |
| runtimeEvent: canonicalizeRuntimeEventForStorage(input.toolOutcome.runtimeEvent), |
| }; |
| assertNoReservedWorkspaceAuthorityAppend(toolOutcome.runtimeEvent); |
| assertOutcomeInput(toolOutcome); |
| const successorEvent = buildWorkspaceSuccessorAuthorityEvent(input.successor); |
| if ( |
| input.successor.origin.operationId !== toolOutcome.operationId || |
| input.successor.origin.outcomeEventId !== toolOutcome.runtimeEvent.id |
| ) { |
| throw new Error('Workspace successor does not match its tool outcome identity'); |
| } |
| if ( |
| toolOutcome.runtimeEvent.content?.kind !== 'function_response' || |
| toolOutcome.runtimeEvent.content.isError === true |
| ) { |
| throw new Error('Workspace successor requires a successful tool outcome'); |
| } |
| |
| return this.transaction(() => { |
| this.#assertWorkspaceStorageRootBinding(rootId); |
| const before = this.readCanonicalWorkspaceAuthoritySync(); |
| this.assertWorkspaceProjectionsMatchSync(before); |
| const currentHead = before.heads.find( |
| (candidate) => |
| candidate.workspaceId === input.successor.successor.workspaceId && |
| candidate.workspaceEpochId === input.successor.successor.workspaceEpochId, |
| ); |
| if (!currentHead) throw new Error('Workspace successor base head is unavailable'); |
| |
| const existing = before.successors.find( |
| (candidate) => |
| candidate.acceptedEventId === input.successor.acceptedEventId || |
| candidate.successor.workspaceVersionId === input.successor.successor.workspaceVersionId, |
| ); |
| if (existing) { |
| assertStoredRuntimeEventEquals( |
| successorEvent, |
| this.readRuntimeEventJson(successorEvent.id), |
| ); |
| const operation = this.readToolOperationSync(toolOutcome.operationId); |
| if (!operation?.resultEventId) { |
| throw new Error('Workspace successor exists without its tool outcome'); |
| } |
| assertStoredRuntimeEventEquals( |
| toolOutcome.runtimeEvent, |
| this.readRuntimeEventJson(operation.resultEventId), |
| ); |
| return { |
| created: false, |
| committedSuccessor: { |
| repositoryId: existing.successor.repositoryId, |
| workspaceId: existing.successor.workspaceId, |
| workspaceEpochId: existing.successor.workspaceEpochId, |
| workspaceVersionId: existing.successor.workspaceVersionId, |
| acceptedEventId: existing.acceptedEventId, |
| commitOid: existing.successor.commitOid, |
| treeOid: existing.successor.treeOid, |
| revision: existing.successor.baseHeadRevision + 1, |
| }, |
| outcomeRuntimeEventSeq: this.runtimeEventSeq(operation.resultEventId), |
| }; |
| } |
| |
| const successor = input.successor.successor; |
| if ( |
| successor.repositoryId !== currentHead.repositoryId || |
| successor.parentWorkspaceVersionId !== currentHead.workspaceVersionId || |
| successor.baseAcceptedEventId !== currentHead.acceptedEventId || |
| successor.baseHeadRevision !== currentHead.revision |
| ) { |
| throw new Error('Workspace successor compare-and-set base head conflict'); |
| } |
| const operation = this.readToolOperationSync(toolOutcome.operationId); |
| if ( |
| !operation || |
| operation.currentState !== 'prepared' || |
| operation.resultEventId !== undefined || |
| operation.dispatchEventId !== input.successor.origin.dispatchEventId || |
| operation.recoveryMode !== 'reconcile' || |
| (operation.toolName !== 'Write' && operation.toolName !== 'Edit') |
| ) { |
| throw new Error('Workspace successor requires one prepared Write/Edit reconcile operation'); |
| } |
| if (!operation.dispatchEventId) { |
| throw new Error('Workspace successor operation is missing its dispatch event'); |
| } |
| const dispatchJson = this.readRuntimeEventJson(operation.dispatchEventId); |
| const dispatchEvent = dispatchJson |
| ? decodeRuntimeEvent(JSON.parse(dispatchJson) as unknown) |
| : undefined; |
| const mutation = dispatchEvent?.actions?.toolDispatch?.managedMutation; |
| const reservation = this.db |
| .prepare(` |
| SELECT |
| workspace_instance_id, repository_id, workspace_id, workspace_epoch_id, |
| operation_id, dispatch_event_id, base_workspace_version_id, |
| base_accepted_event_id, base_head_revision, base_commit_oid, base_tree_oid, |
| expected_paths_json, execution_profile_digest, protocol_version, reserved_at |
| FROM runtime_managed_mutation_reservations |
| WHERE operation_id = ? |
| `) |
| .get(operation.operationId) as ManagedMutationReservationProjectionRow | undefined; |
| if ( |
| !mutation || |
| !reservation || |
| reservation.workspace_instance_id !== mutation.workspaceInstanceId || |
| reservation.repository_id !== mutation.repositoryId || |
| reservation.workspace_id !== mutation.workspaceId || |
| reservation.workspace_epoch_id !== mutation.workspaceEpochId || |
| reservation.operation_id !== operation.operationId || |
| reservation.dispatch_event_id !== operation.dispatchEventId || |
| reservation.base_workspace_version_id !== mutation.baseWorkspaceVersionId || |
| reservation.base_accepted_event_id !== mutation.baseAcceptedEventId || |
| reservation.base_head_revision !== mutation.baseHeadRevision || |
| reservation.base_commit_oid !== mutation.baseCommitOid || |
| reservation.base_tree_oid !== mutation.baseTreeOid || |
| reservation.execution_profile_digest !== mutation.executionProfileDigest || |
| mutation.repositoryId !== successor.repositoryId || |
| mutation.workspaceId !== successor.workspaceId || |
| mutation.workspaceEpochId !== successor.workspaceEpochId || |
| mutation.objectFormat !== successor.objectFormat || |
| mutation.baseWorkspaceVersionId !== successor.parentWorkspaceVersionId || |
| mutation.baseAcceptedEventId !== successor.baseAcceptedEventId || |
| mutation.baseHeadRevision !== successor.baseHeadRevision || |
| mutation.baseCommitOid !== currentHead.commitOid || |
| mutation.baseTreeOid !== currentHead.treeOid || |
| mutation.executionProfileDigest !== successor.executionProfileDigest |
| ) { |
| throw new Error('Workspace successor requires its exact durable mutation reservation'); |
| } |
| const reservedPaths = JSON.parse(reservation.expected_paths_json) as unknown; |
| if ( |
| !isDeepStrictEqual(reservedPaths, [mutation.expectedPath]) || |
| !isDeepStrictEqual(successor.changedPaths, [mutation.expectedPath]) |
| ) { |
| throw new Error('Managed mutation path authorization conflict'); |
| } |
| |
| const outcomeResult = this.commitToolOutcomeSync(toolOutcome, 'workspace_successor'); |
| const successorSeq = this.insertRuntimeEvent( |
| successorEvent, |
| input.successor.committedAt, |
| false, |
| ); |
| if (successorSeq !== currentHead.revision + 2) { |
| throw new Error('Workspace successor fact is not the next authority event'); |
| } |
| this.options.failpoint?.('after_workspace_successor_event_insert'); |
| |
| const after = this.readCanonicalWorkspaceAuthoritySync(); |
| const accepted = after.successors.find( |
| (candidate) => candidate.acceptedEventId === input.successor.acceptedEventId, |
| ); |
| const nextHead = after.heads.find( |
| (candidate) => |
| candidate.workspaceId === successor.workspaceId && |
| candidate.workspaceEpochId === successor.workspaceEpochId, |
| ); |
| if (!accepted || !nextHead) { |
| throw new Error('Workspace successor authority scan lost the committed version'); |
| } |
| this.insertWorkspaceSuccessorVersionProjection(accepted, input.successor.committedAt); |
| this.options.failpoint?.('after_workspace_successor_projection_insert'); |
| const updated = this.db |
| .prepare(` |
| UPDATE runtime_workspace_heads |
| SET workspace_version_id = ?, accepted_event_id = ?, commit_oid = ?, tree_oid = ?, |
| revision = ? |
| WHERE workspace_id = ? AND workspace_epoch_id = ? |
| AND workspace_version_id = ? AND accepted_event_id = ? AND revision = ? |
| `) |
| .run( |
| nextHead.workspaceVersionId, |
| nextHead.acceptedEventId, |
| nextHead.commitOid, |
| nextHead.treeOid, |
| nextHead.revision, |
| currentHead.workspaceId, |
| currentHead.workspaceEpochId, |
| currentHead.workspaceVersionId, |
| currentHead.acceptedEventId, |
| currentHead.revision, |
| ); |
| if (updated.changes !== 1) { |
| throw new Error('Workspace successor head compare-and-set failed'); |
| } |
| this.options.failpoint?.('after_workspace_successor_head_update'); |
| const released = this.db |
| .prepare(` |
| DELETE FROM runtime_managed_mutation_reservations |
| WHERE workspace_instance_id = ? AND operation_id = ? AND dispatch_event_id = ? |
| `) |
| .run(mutation.workspaceInstanceId, operation.operationId, operation.dispatchEventId); |
| if (released.changes !== 1) { |
| throw new Error('Managed mutation reservation release compare-and-set failed'); |
| } |
| this.assertWorkspaceProjectionsMatchSync(after); |
| return { |
| created: true, |
| committedSuccessor: nextHead, |
| outcomeRuntimeEventSeq: outcomeResult.runtimeEventSeq, |
| }; |
| }); |
| } |
| |
| async #commitManagedMutationTerminal( |
| input: { |
| noEffect: ManagedMutationNoEffectClaimV1; |
| toolOutcome: ManagedMutationTerminalCommitInput['toolOutcome']; |
| }, |
| rootId: string, |
| ): Promise<ManagedMutationTerminalCommitResult> { |
| const toolOutcome: CommitToolOutcomeInput = { |
| ...input.toolOutcome, |
| runtimeEvent: canonicalizeRuntimeEventForStorage(input.toolOutcome.runtimeEvent), |
| }; |
| assertOutcomeInput(toolOutcome); |
| const terminal = toolOutcome.runtimeEvent.actions?.managedMutationTerminal; |
| if (!terminal) throw new Error('Managed mutation terminal fact is missing'); |
| if ( |
| input.noEffect.operationId !== terminal.operationId || |
| input.noEffect.dispatchEventId !== terminal.dispatchEventId || |
| input.noEffect.workspaceInstanceId !== terminal.workspaceInstanceId || |
| input.noEffect.terminalKind !== terminal.terminalKind |
| ) { |
| throw new Error('Managed mutation terminal does not match its owner-issued no-effect proof'); |
| } |
| |
| return this.transaction(() => { |
| this.#assertWorkspaceStorageRootBinding(rootId); |
| const operation = this.readToolOperationSync(toolOutcome.operationId); |
| if ( |
| !operation || |
| !operation.dispatchEventId || |
| operation.dispatchEventId !== terminal.dispatchEventId || |
| terminal.operationId !== operation.operationId || |
| operation.recoveryMode !== 'reconcile' || |
| (operation.toolName !== 'Write' && operation.toolName !== 'Edit') |
| ) { |
| throw new Error('Managed mutation terminal requires its exact prepared operation'); |
| } |
| const dispatchJson = this.readRuntimeEventJson(operation.dispatchEventId); |
| const dispatchEvent = dispatchJson |
| ? decodeRuntimeEvent(JSON.parse(dispatchJson) as unknown) |
| : undefined; |
| const mutation = dispatchEvent?.actions?.toolDispatch?.managedMutation; |
| if (!mutation || mutation.workspaceInstanceId !== terminal.workspaceInstanceId) { |
| throw new Error('Managed mutation terminal requires its exact durable reservation'); |
| } |
| const response = toolOutcome.runtimeEvent.content; |
| if ( |
| response?.kind !== 'function_response' || |
| (terminal.terminalKind === 'no_workspace_change' |
| ? response.isError === true |
| : response.isError !== true) |
| ) { |
| throw new Error('Managed mutation terminal outcome has the wrong success state'); |
| } |
| |
| const result = this.commitToolOutcomeSync(toolOutcome, 'workspace_terminal'); |
| const released = this.db |
| .prepare(` |
| DELETE FROM runtime_managed_mutation_reservations |
| WHERE workspace_instance_id = ? AND operation_id = ? AND dispatch_event_id = ? |
| `) |
| .run(terminal.workspaceInstanceId, operation.operationId, operation.dispatchEventId); |
| if (result.created && released.changes !== 1) { |
| throw new Error('Managed mutation terminal reservation release compare-and-set failed'); |
| } |
| if (!result.created && released.changes !== 0) { |
| throw new Error('Managed mutation terminal exact retry found an active reservation'); |
| } |
| const authority = this.readCanonicalWorkspaceAuthoritySync(); |
| this.assertWorkspaceProjectionsMatchSync(authority); |
| return { created: result.created, outcomeRuntimeEventSeq: result.runtimeEventSeq }; |
| }); |
| } |
| |
| private registerWorkspaceBaselineAuthorityWriter(): void { |
| registerWorkspaceBaselineAuthorityWriterInternal( |
| this, |
| (input, rootId) => this.#commitWorkspaceBaseline(input, rootId), |
| (input, rootId) => this.#commitWorkspaceSuccessor(input, rootId), |
| (input, rootId) => this.#commitManagedMutationTerminal(input, rootId), |
| (rootId) => this.#bindWorkspaceStorageRoot(rootId), |
| (workspaceInstanceId) => this.#readActiveManagedMutation(workspaceInstanceId), |
| ); |
| } |
| |
| async #readActiveManagedMutation( |
| workspaceInstanceId: string, |
| ): Promise< |
| | import('./workspace-version-authority-internal.js').ManagedMutationReservationRecordV1 |
| | undefined |
| > { |
| return this.readTransaction(() => { |
| const authority = this.readCanonicalWorkspaceAuthoritySync(); |
| this.assertWorkspaceProjectionsMatchSync(authority); |
| const reservation = authority.activeManagedMutations.find( |
| (candidate) => candidate.workspace_instance_id === workspaceInstanceId, |
| ); |
| if (!reservation) return undefined; |
| const expectedPaths = JSON.parse(reservation.expected_paths_json) as unknown; |
| if ( |
| !Array.isArray(expectedPaths) || |
| expectedPaths.length !== 1 || |
| typeof expectedPaths[0] !== 'string' |
| ) { |
| throw new Error('Managed mutation reservation has invalid expected paths'); |
| } |
| return { |
| workspaceInstanceId: reservation.workspace_instance_id, |
| repositoryId: reservation.repository_id, |
| workspaceId: reservation.workspace_id, |
| workspaceEpochId: reservation.workspace_epoch_id, |
| operationId: reservation.operation_id, |
| dispatchEventId: reservation.dispatch_event_id, |
| baseWorkspaceVersionId: reservation.base_workspace_version_id, |
| baseAcceptedEventId: reservation.base_accepted_event_id, |
| baseHeadRevision: reservation.base_head_revision, |
| baseCommitOid: reservation.base_commit_oid, |
| baseTreeOid: reservation.base_tree_oid, |
| expectedPath: expectedPaths[0], |
| executionProfileDigest: reservation.execution_profile_digest, |
| reservedAt: reservation.reserved_at, |
| }; |
| }); |
| } |
| |
| #bindWorkspaceStorageRoot(rootId: string): void { |
| this.transaction(() => { |
| const existing = this.#readWorkspaceStorageRootBinding(); |
| if (existing) { |
| if (existing.root_id !== rootId || existing.protocol_version !== 1) { |
| throw new Error( |
| 'Workspace authority database belongs to a different durable storage root', |
| ); |
| } |
| return; |
| } |
| if (this.#databaseHasLogicalStateBeforeRootBinding()) { |
| throw new Error('Unbound operational data require explicit storage-root adoption'); |
| } |
| this.db |
| .prepare(` |
| INSERT INTO runtime_storage_root_binding(singleton, root_id, protocol_version) |
| VALUES (1, ?, 1) |
| `) |
| .run(rootId); |
| }); |
| } |
| |
| #assertWorkspaceStorageRootBinding(rootId: string): void { |
| const existing = this.#readWorkspaceStorageRootBinding(); |
| if (!existing || existing.root_id !== rootId || existing.protocol_version !== 1) { |
| throw new Error('Workspace authority database durable storage-root binding changed'); |
| } |
| } |
| |
| #readWorkspaceStorageRootBinding(): { root_id: string; protocol_version: number } | undefined { |
| return this.db |
| .prepare(` |
| SELECT root_id, protocol_version |
| FROM runtime_storage_root_binding |
| WHERE singleton = 1 |
| `) |
| .get() as { root_id: string; protocol_version: number } | undefined; |
| } |
| |
| #databaseHasLogicalStateBeforeRootBinding(): boolean { |
| const metadataTables = new Set([ |
| 'operational_schema_migrations', |
| 'runtime_capabilities', |
| 'runtime_storage_root_binding', |
| ]); |
| const tables = this.db |
| .prepare(` |
| SELECT name |
| FROM sqlite_master |
| WHERE type = 'table' AND name NOT LIKE 'sqlite_%' |
| ORDER BY name |
| `) |
| .all() as Array<{ name: string }>; |
| for (const { name } of tables) { |
| if (metadataTables.has(name)) continue; |
| const quotedName = `"${name.replaceAll('"', '""')}"`; |
| if (this.db.prepare(`SELECT 1 FROM ${quotedName} LIMIT 1`).get()) return true; |
| } |
| return false; |
| } |
| |
| async readWorkspaceEpoch( |
| workspaceId: string, |
| workspaceEpochId: string, |
| ): Promise<WorkspaceEpochRecordV1 | undefined> { |
| return this.readTransaction(() => { |
| const authority = this.readCanonicalWorkspaceAuthoritySync(); |
| this.assertWorkspaceProjectionsMatchSync(authority); |
| const baseline = authority.baselines.find( |
| (candidate) => |
| candidate.epoch.workspaceId === workspaceId && |
| candidate.epoch.workspaceEpochId === workspaceEpochId, |
| ); |
| return baseline ? workspaceEpochRecord(baseline) : undefined; |
| }); |
| } |
| |
| async readWorkspaceVersion( |
| workspaceVersionId: string, |
| ): Promise<WorkspaceVersionRecordV1 | undefined> { |
| return this.readTransaction(() => { |
| const authority = this.readCanonicalWorkspaceAuthoritySync(); |
| this.assertWorkspaceProjectionsMatchSync(authority); |
| const baseline = authority.baselines.find( |
| (candidate) => candidate.baseline.workspaceVersionId === workspaceVersionId, |
| ); |
| if (baseline) return workspaceBaselineVersionRecord(baseline); |
| const successor = authority.successors.find( |
| (candidate) => candidate.successor.workspaceVersionId === workspaceVersionId, |
| ); |
| return successor ? workspaceSuccessorVersionRecord(successor) : undefined; |
| }); |
| } |
| |
| async readWorkspaceHead( |
| workspaceId: string, |
| workspaceEpochId: string, |
| ): Promise<WorkspaceHeadRecordV1 | undefined> { |
| return this.readTransaction(() => { |
| const authority = this.readCanonicalWorkspaceAuthoritySync(); |
| this.assertWorkspaceProjectionsMatchSync(authority); |
| return authority.heads.find( |
| (candidate) => |
| candidate.workspaceId === workspaceId && candidate.workspaceEpochId === workspaceEpochId, |
| ); |
| }); |
| } |
| |
| async rebuildWorkspaceVersionProjections(): Promise<WorkspaceProjectionRebuildResult> { |
| return this.transaction(() => { |
| const authority = this.readCanonicalWorkspaceAuthoritySync(); |
| this.db.prepare('DELETE FROM runtime_managed_mutation_reservations').run(); |
| this.db.prepare('DELETE FROM runtime_workspace_heads').run(); |
| this.db.prepare('DELETE FROM runtime_workspace_versions').run(); |
| this.db.prepare('DELETE FROM runtime_workspace_epochs').run(); |
| for (const baseline of authority.baselines) { |
| const committedAt = Math.max( |
| this.runtimeEventCommittedAt(baseline.epochOpenedEventId), |
| this.runtimeEventCommittedAt(baseline.baselineAcceptedEventId), |
| ); |
| this.insertWorkspaceEpochProjection(baseline, committedAt); |
| this.insertWorkspaceBaselineVersionProjection(baseline, committedAt); |
| } |
| for (const successor of authority.successors) { |
| this.insertWorkspaceSuccessorVersionProjection( |
| successor, |
| this.runtimeEventCommittedAt(successor.acceptedEventId), |
| ); |
| } |
| for (const head of authority.heads) this.insertWorkspaceHeadProjection(head); |
| for (const reservation of authority.activeManagedMutations) { |
| this.insertManagedMutationReservationProjectionSync(reservation); |
| } |
| this.assertWorkspaceProjectionsMatchSync(authority); |
| return { |
| epochs: authority.baselines.length, |
| versions: authority.baselines.length + authority.successors.length, |
| heads: authority.heads.length, |
| }; |
| }); |
| } |
| |
| private readCanonicalWorkspaceAuthoritySync(): CanonicalWorkspaceAuthority { |
| const partial = this.db |
| .prepare(` |
| SELECT stream_key FROM runtime_partial_snapshots |
| WHERE session_id = ? |
| LIMIT 1 |
| `) |
| .get(WORKSPACE_AUTHORITY_SESSION_ID) as { stream_key: string } | undefined; |
| if (partial) { |
| throw new Error( |
| `Corrupt workspace RuntimeEvent authority: authority_stream_contamination at ${partial.stream_key}`, |
| ); |
| } |
| const rows = this.db |
| .prepare(` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, event_seq, payload_json |
| FROM runtime_events |
| ORDER BY invocation_id ASC, event_seq ASC, event_id ASC |
| `) |
| .all() as unknown as RuntimeEventPrefixStorageRow[]; |
| const events = rows.map(decodeRuntimeEventStorageRow); |
| const authorityRows: WorkspaceAuthorityLedgerRow[] = rows.map((row, index) => ({ |
| event: events[index]!, |
| eventSeq: row.event_seq, |
| })); |
| const scan = scanWorkspaceBaselineAuthority(authorityRows); |
| if (scan.hasCorruption) { |
| const issue = scan.issues[0]!; |
| throw new Error( |
| `Corrupt workspace RuntimeEvent authority: ${issue.code} at ${issue.eventId}`, |
| ); |
| } |
| const toolScan = scanToolLedger(events); |
| for (const accepted of scan.successors) { |
| const origin = accepted.successor.origin; |
| const operation = toolScan.operations.find( |
| (candidate) => candidate.operationId === origin.operationId, |
| ); |
| const dispatch = operation?.dispatchEvent?.actions?.toolDispatch; |
| const response = operation?.responseEvent; |
| const epoch = scan.baselines.find( |
| (candidate) => |
| candidate.epoch.workspaceId === accepted.successor.workspaceId && |
| candidate.epoch.workspaceEpochId === accepted.successor.workspaceEpochId, |
| )?.epoch; |
| const baseHead = workspaceHeadBeforeSuccessor(scan, accepted.successor); |
| if ( |
| !operation || |
| operation.issues.length > 0 || |
| operation.dispatchEvent?.id !== origin.dispatchEventId || |
| !dispatch || |
| dispatch.operationId !== origin.operationId || |
| dispatch.recoveryMode !== 'reconcile' || |
| (dispatch.toolName !== 'Write' && dispatch.toolName !== 'Edit') || |
| !epoch || |
| !baseHead || |
| !managedMutationMatchesAcceptedSuccessor( |
| dispatch.managedMutation, |
| accepted.successor, |
| baseHead, |
| epoch.workspaceInstanceId, |
| ) || |
| !response || |
| response.id !== origin.outcomeEventId || |
| response.content?.kind !== 'function_response' || |
| response.content.isError === true |
| ) { |
| throw new Error( |
| `Corrupt workspace successor tool evidence: identity_conflict at ${accepted.acceptedEventId}`, |
| ); |
| } |
| } |
| const activeManagedMutations = this.scanCanonicalManagedMutationReservationsSync( |
| toolScan, |
| scan, |
| ); |
| this.options.failpoint?.('after_workspace_canonical_scan'); |
| return { ...scan, activeManagedMutations }; |
| } |
| |
| private scanCanonicalManagedMutationReservationsSync( |
| toolScan: ReturnType<typeof scanToolLedger>, |
| authority: ReturnType<typeof scanWorkspaceBaselineAuthority>, |
| ): ManagedMutationReservationProjectionRow[] { |
| const acceptedOperations = new Set( |
| authority.successors.map((candidate) => candidate.successor.origin.operationId), |
| ); |
| const reservations: ManagedMutationReservationProjectionRow[] = []; |
| const occupied = new Set<string>(); |
| for (const operation of toolScan.operations) { |
| const dispatchEvent = operation.dispatchEvent; |
| const dispatch = dispatchEvent?.actions?.toolDispatch; |
| const mutation = dispatch?.managedMutation; |
| if (!mutation) continue; |
| if ( |
| operation.issues.length > 0 || |
| !dispatchEvent || |
| dispatch.operationId !== operation.operationId || |
| dispatch.recoveryMode !== 'reconcile' || |
| (dispatch.toolName !== 'Write' && dispatch.toolName !== 'Edit') |
| ) { |
| throw new Error( |
| `Corrupt managed mutation reservation: identity_conflict at ${dispatchEvent?.id ?? operation.operationId}`, |
| ); |
| } |
| if (acceptedOperations.has(operation.operationId)) continue; |
| if (operation.responseEvent) { |
| const terminal = operation.responseEvent.actions?.managedMutationTerminal; |
| if ( |
| !terminal || |
| terminal.operationId !== operation.operationId || |
| terminal.dispatchEventId !== dispatchEvent.id || |
| terminal.workspaceInstanceId !== mutation.workspaceInstanceId || |
| operation.responseEvent.content?.kind !== 'function_response' || |
| (terminal.terminalKind === 'no_workspace_change' |
| ? operation.responseEvent.content.isError === true |
| : operation.responseEvent.content.isError !== true) |
| ) { |
| throw new Error( |
| `Corrupt managed mutation reservation: generic_outcome at ${operation.responseEvent.id}`, |
| ); |
| } |
| continue; |
| } |
| const epoch = authority.baselines.find( |
| (candidate) => |
| candidate.epoch.workspaceId === mutation.workspaceId && |
| candidate.epoch.workspaceEpochId === mutation.workspaceEpochId, |
| )?.epoch; |
| const head = authority.heads.find( |
| (candidate) => |
| candidate.workspaceId === mutation.workspaceId && |
| candidate.workspaceEpochId === mutation.workspaceEpochId, |
| ); |
| if ( |
| !epoch || |
| !head || |
| epoch.repositoryId !== mutation.repositoryId || |
| epoch.workspaceInstanceId !== mutation.workspaceInstanceId || |
| epoch.objectFormat !== mutation.objectFormat || |
| head.workspaceVersionId !== mutation.baseWorkspaceVersionId || |
| head.acceptedEventId !== mutation.baseAcceptedEventId || |
| head.revision !== mutation.baseHeadRevision || |
| head.commitOid !== mutation.baseCommitOid || |
| head.treeOid !== mutation.baseTreeOid || |
| occupied.has(mutation.workspaceInstanceId) |
| ) { |
| throw new Error( |
| `Corrupt managed mutation reservation: workspace_conflict at ${dispatchEvent.id}`, |
| ); |
| } |
| occupied.add(mutation.workspaceInstanceId); |
| reservations.push({ |
| workspace_instance_id: mutation.workspaceInstanceId, |
| repository_id: mutation.repositoryId, |
| workspace_id: mutation.workspaceId, |
| workspace_epoch_id: mutation.workspaceEpochId, |
| operation_id: operation.operationId, |
| dispatch_event_id: dispatchEvent.id, |
| base_workspace_version_id: mutation.baseWorkspaceVersionId, |
| base_accepted_event_id: mutation.baseAcceptedEventId, |
| base_head_revision: mutation.baseHeadRevision, |
| base_commit_oid: mutation.baseCommitOid, |
| base_tree_oid: mutation.baseTreeOid, |
| expected_paths_json: JSON.stringify([mutation.expectedPath]), |
| execution_profile_digest: mutation.executionProfileDigest, |
| protocol_version: 1, |
| reserved_at: this.runtimeEventCommittedAt(dispatchEvent.id), |
| }); |
| } |
| return reservations.sort((left, right) => |
| left.workspace_instance_id.localeCompare(right.workspace_instance_id), |
| ); |
| } |
| |
| private assertWorkspaceAuthorityStreamIsEmpty(event: RuntimeEvent): void { |
| const row = this.db |
| .prepare(` |
| SELECT event_id FROM runtime_events |
| WHERE invocation_id = ? |
| OR (session_id = ? AND run_id = ?) |
| OR (session_id = ? AND turn_id = ?) |
| LIMIT 1 |
| `) |
| .get(event.invocationId, event.sessionId, event.runId, event.sessionId, event.turnId) as |
| | { event_id: string } |
| | undefined; |
| if (row) throw new Error('Workspace baseline authority conflict'); |
| } |
| |
| private insertWorkspaceEpochProjection( |
| baseline: ReturnType<typeof scanWorkspaceBaselineAuthority>['baselines'][number], |
| committedAt: number, |
| ): void { |
| const { epoch, authority } = baseline; |
| this.db |
| .prepare(` |
| INSERT INTO runtime_workspace_epochs ( |
| workspace_id, |
| workspace_epoch_id, |
| repository_id, |
| workspace_instance_id, |
| mode, |
| object_format, |
| source_commit_oid, |
| source_tree_oid, |
| initial_workspace_version_id, |
| materialization_profile_digest, |
| materialization_semantics, |
| policy_hash, |
| authority_session_id, |
| authority_invocation_id, |
| authority_run_id, |
| authority_turn_id, |
| epoch_opened_event_id, |
| protocol_version, |
| committed_at |
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?) |
| `) |
| .run( |
| epoch.workspaceId, |
| epoch.workspaceEpochId, |
| epoch.repositoryId, |
| epoch.workspaceInstanceId, |
| epoch.mode, |
| epoch.objectFormat, |
| epoch.sourceCommitOid, |
| epoch.sourceTreeOid, |
| epoch.initialWorkspaceVersionId, |
| epoch.materializationProfileDigest, |
| epoch.materializationSemantics, |
| epoch.policyHash, |
| authority.sessionId, |
| authority.invocationId, |
| authority.runId, |
| authority.turnId, |
| baseline.epochOpenedEventId, |
| committedAt, |
| ); |
| } |
| |
| private insertWorkspaceBaselineVersionProjection( |
| accepted: ReturnType<typeof scanWorkspaceBaselineAuthority>['baselines'][number], |
| committedAt: number, |
| ): void { |
| const { baseline } = accepted; |
| this.db |
| .prepare(` |
| INSERT INTO runtime_workspace_versions ( |
| workspace_version_id, |
| repository_id, |
| workspace_id, |
| workspace_epoch_id, |
| object_format, |
| origin_kind, |
| origin_event_id, |
| parents_json, |
| operation_id, |
| dispatch_event_id, |
| outcome_event_id, |
| base_head_revision, |
| execution_profile_digest, |
| commit_oid, |
| tree_oid, |
| policy_hash, |
| tree_delta_digest, |
| changed_paths_json, |
| changed_file_count, |
| deleted_file_count, |
| accepted_event_id, |
| protocol_version, |
| committed_at |
| ) VALUES (?, ?, ?, ?, ?, 'baseline', ?, '[]', NULL, NULL, NULL, NULL, NULL, |
| ?, ?, ?, ?, ?, ?, ?, ?, 1, ?) |
| `) |
| .run( |
| baseline.workspaceVersionId, |
| baseline.repositoryId, |
| baseline.workspaceId, |
| baseline.workspaceEpochId, |
| baseline.objectFormat, |
| baseline.origin.epochOpenedEventId, |
| baseline.commitOid, |
| baseline.treeOid, |
| baseline.policyHash, |
| baseline.treeDeltaDigest, |
| '[]', |
| baseline.changedFileCount, |
| baseline.deletedFileCount, |
| accepted.baselineAcceptedEventId, |
| committedAt, |
| ); |
| } |
| |
| private insertWorkspaceSuccessorVersionProjection( |
| accepted: ScannedWorkspaceSuccessorAuthority, |
| committedAt: number, |
| ): void { |
| const { successor } = accepted; |
| this.db |
| .prepare(` |
| INSERT INTO runtime_workspace_versions ( |
| workspace_version_id, |
| repository_id, |
| workspace_id, |
| workspace_epoch_id, |
| object_format, |
| origin_kind, |
| origin_event_id, |
| parents_json, |
| operation_id, |
| dispatch_event_id, |
| outcome_event_id, |
| base_head_revision, |
| execution_profile_digest, |
| commit_oid, |
| tree_oid, |
| policy_hash, |
| tree_delta_digest, |
| changed_paths_json, |
| changed_file_count, |
| deleted_file_count, |
| accepted_event_id, |
| protocol_version, |
| committed_at |
| ) VALUES (?, ?, ?, ?, ?, 'tool_mutation', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?) |
| `) |
| .run( |
| successor.workspaceVersionId, |
| successor.repositoryId, |
| successor.workspaceId, |
| successor.workspaceEpochId, |
| successor.objectFormat, |
| successor.origin.outcomeEventId, |
| JSON.stringify(successor.parents), |
| successor.origin.operationId, |
| successor.origin.dispatchEventId, |
| successor.origin.outcomeEventId, |
| successor.baseHeadRevision, |
| successor.executionProfileDigest, |
| successor.commitOid, |
| successor.treeOid, |
| successor.policyHash, |
| successor.treeDeltaDigest, |
| JSON.stringify(successor.changedPaths), |
| successor.changedFileCount, |
| successor.deletedFileCount, |
| accepted.acceptedEventId, |
| committedAt, |
| ); |
| } |
| |
| private insertWorkspaceHeadProjection(head: WorkspaceHeadRecordV1): void { |
| this.db |
| .prepare(` |
| INSERT INTO runtime_workspace_heads ( |
| workspace_id, |
| workspace_epoch_id, |
| repository_id, |
| workspace_version_id, |
| accepted_event_id, |
| commit_oid, |
| tree_oid, |
| revision |
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) |
| `) |
| .run( |
| head.workspaceId, |
| head.workspaceEpochId, |
| head.repositoryId, |
| head.workspaceVersionId, |
| head.acceptedEventId, |
| head.commitOid, |
| head.treeOid, |
| head.revision, |
| ); |
| } |
| |
| private assertWorkspaceProjectionsMatchSync(authority: CanonicalWorkspaceAuthority): void { |
| const expectedEpochs = authority.baselines |
| .map(workspaceEpochProjectionRow) |
| .sort(compareWorkspaceEpochRow); |
| const expectedVersions = [ |
| ...authority.baselines.map(workspaceBaselineVersionProjectionRow), |
| ...authority.successors.map(workspaceSuccessorVersionProjectionRow), |
| ].sort(compareWorkspaceVersionRow); |
| const expectedHeads = authority.heads |
| .map(workspaceHeadProjectionRow) |
| .sort(compareWorkspaceHeadRow); |
| const epochs = ( |
| this.db |
| .prepare(` |
| SELECT |
| workspace_id, |
| workspace_epoch_id, |
| repository_id, |
| workspace_instance_id, |
| mode, |
| object_format, |
| source_commit_oid, |
| source_tree_oid, |
| initial_workspace_version_id, |
| materialization_profile_digest, |
| materialization_semantics, |
| policy_hash, |
| authority_session_id, |
| authority_invocation_id, |
| authority_run_id, |
| authority_turn_id, |
| epoch_opened_event_id, |
| protocol_version, |
| committed_at |
| FROM runtime_workspace_epochs |
| ORDER BY workspace_id ASC, workspace_epoch_id ASC |
| `) |
| .all() as unknown as WorkspaceEpochProjectionRow[] |
| ) |
| .map((row) => ({ ...row })) |
| .sort(compareWorkspaceEpochRow); |
| const versions = ( |
| this.db |
| .prepare(` |
| SELECT |
| workspace_version_id, |
| repository_id, |
| workspace_id, |
| workspace_epoch_id, |
| object_format, |
| origin_kind, |
| origin_event_id, |
| parents_json, |
| operation_id, |
| dispatch_event_id, |
| outcome_event_id, |
| base_head_revision, |
| execution_profile_digest, |
| commit_oid, |
| tree_oid, |
| policy_hash, |
| tree_delta_digest, |
| changed_paths_json, |
| changed_file_count, |
| deleted_file_count, |
| accepted_event_id, |
| protocol_version, |
| committed_at |
| FROM runtime_workspace_versions |
| ORDER BY workspace_version_id ASC |
| `) |
| .all() as unknown as WorkspaceVersionProjectionRow[] |
| ) |
| .map((row) => ({ ...row })) |
| .sort(compareWorkspaceVersionRow); |
| const heads = ( |
| this.db |
| .prepare(` |
| SELECT |
| workspace_id, |
| workspace_epoch_id, |
| repository_id, |
| workspace_version_id, |
| accepted_event_id, |
| commit_oid, |
| tree_oid, |
| revision |
| FROM runtime_workspace_heads |
| ORDER BY workspace_id ASC, workspace_epoch_id ASC |
| `) |
| .all() as unknown as WorkspaceHeadProjectionRow[] |
| ) |
| .map((row) => ({ ...row })) |
| .sort(compareWorkspaceHeadRow); |
| const activeManagedMutations = ( |
| this.db |
| .prepare(` |
| SELECT |
| workspace_instance_id, repository_id, workspace_id, workspace_epoch_id, |
| operation_id, dispatch_event_id, base_workspace_version_id, |
| base_accepted_event_id, base_head_revision, base_commit_oid, base_tree_oid, |
| expected_paths_json, execution_profile_digest, protocol_version, reserved_at |
| FROM runtime_managed_mutation_reservations |
| ORDER BY workspace_instance_id ASC |
| `) |
| .all() as unknown as ManagedMutationReservationProjectionRow[] |
| ).map((row) => ({ ...row })); |
| if ( |
| !isDeepStrictEqual(epochs, expectedEpochs) || |
| !isDeepStrictEqual(versions, expectedVersions) || |
| !isDeepStrictEqual(heads, expectedHeads) |
| ) { |
| throw new Error('Workspace version projection is incomplete or inconsistent'); |
| } |
| if (!isDeepStrictEqual(activeManagedMutations, authority.activeManagedMutations)) { |
| throw new Error('Managed mutation reservation projection is incomplete or inconsistent'); |
| } |
| } |
| |
| private workspaceProjectionCountSync(): number { |
| const row = this.db |
| .prepare(` |
| SELECT |
| (SELECT COUNT(*) FROM runtime_workspace_epochs) + |
| (SELECT COUNT(*) FROM runtime_workspace_versions) + |
| (SELECT COUNT(*) FROM runtime_workspace_heads) + |
| (SELECT COUNT(*) FROM runtime_managed_mutation_reservations) AS count |
| `) |
| .get() as { count: number }; |
| return row.count; |
| } |
| |
| private runtimeEventCommittedAt(eventId: string): number { |
| const row = this.db |
| .prepare('SELECT committed_at FROM runtime_events WHERE event_id = ?') |
| .get(eventId) as { committed_at: number } | undefined; |
| if (!row) throw new Error(`Missing RuntimeEvent committed time for ${eventId}`); |
| return row.committed_at; |
| } |
| |
| async commitToolPrepared(input: CommitToolPreparedInput): Promise<ToolCommitResult> { |
| const canonicalInput: CommitToolPreparedInput = { |
| ...input, |
| runtimeEvent: canonicalizeRuntimeEventForStorage(input.runtimeEvent), |
| dispatchRuntimeEvent: canonicalizeRuntimeEventForStorage(input.dispatchRuntimeEvent), |
| }; |
| assertNoReservedWorkspaceAuthorityAppend(canonicalInput.runtimeEvent); |
| assertNoReservedWorkspaceAuthorityAppend(canonicalInput.dispatchRuntimeEvent); |
| assertPreparedInput(canonicalInput); |
| return this.transaction(() => { |
| this.assertToolLedgerTransition( |
| [canonicalInput.runtimeEvent, canonicalInput.dispatchRuntimeEvent], |
| 't1_prepare', |
| ); |
| const existing = this.readToolOperationSync(canonicalInput.operationId); |
| if (existing) { |
| assertPreparedIdentity(existing, canonicalInput); |
| assertStoredRuntimeEventEquals( |
| canonicalInput.runtimeEvent, |
| this.readRuntimeEventJson(canonicalInput.runtimeEvent.id), |
| ); |
| assertStoredRuntimeEventEquals( |
| canonicalInput.dispatchRuntimeEvent, |
| this.readRuntimeEventJson(canonicalInput.dispatchRuntimeEvent.id), |
| ); |
| if (canonicalInput.dispatchRuntimeEvent.actions?.toolDispatch?.managedMutation) { |
| const authority = this.readCanonicalWorkspaceAuthoritySync(); |
| this.assertWorkspaceProjectionsMatchSync(authority); |
| } |
| return { |
| created: false, |
| runtimeEventSeq: this.runtimeEventSeq(canonicalInput.dispatchRuntimeEvent.id), |
| }; |
| } |
| this.assertManagedMutationReservationAvailableSync(canonicalInput); |
| this.insertRuntimeEvent(canonicalInput.runtimeEvent, canonicalInput.committedAt, true); |
| const runtimeEventSeq = this.insertRuntimeEvent( |
| canonicalInput.dispatchRuntimeEvent, |
| canonicalInput.committedAt, |
| false, |
| ); |
| this.options.failpoint?.('after_runtime_event_insert'); |
| this.db |
| .prepare(` |
| INSERT INTO tool_journal_events ( |
| journal_event_id, operation_id, invocation_id, run_id, turn_id, state, |
| runtime_event_id, canonical_args_hash, recovery_mode, committed_at |
| ) VALUES (?, ?, ?, ?, ?, 'prepared', ?, ?, ?, ?) |
| `) |
| .run( |
| canonicalInput.journalEventId, |
| canonicalInput.operationId, |
| canonicalInput.runtimeEvent.invocationId, |
| canonicalInput.runtimeEvent.runId, |
| canonicalInput.runtimeEvent.turnId, |
| canonicalInput.dispatchRuntimeEvent.id, |
| canonicalInput.canonicalArgsHash, |
| canonicalInput.recoveryMode, |
| canonicalInput.committedAt, |
| ); |
| this.options.failpoint?.('after_journal_event_insert'); |
| this.db |
| .prepare(` |
| INSERT INTO tool_operations ( |
| operation_id, invocation_id, run_id, turn_id, provider_tool_call_id, |
| tool_name, canonical_args_hash, recovery_mode, current_state, |
| call_event_id, dispatch_event_id, version |
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'prepared', ?, ?, 1) |
| `) |
| .run( |
| canonicalInput.operationId, |
| canonicalInput.runtimeEvent.invocationId, |
| canonicalInput.runtimeEvent.runId, |
| canonicalInput.runtimeEvent.turnId, |
| canonicalInput.providerToolCallId, |
| canonicalInput.toolName, |
| canonicalInput.canonicalArgsHash, |
| canonicalInput.recoveryMode, |
| canonicalInput.runtimeEvent.id, |
| canonicalInput.dispatchRuntimeEvent.id, |
| ); |
| this.insertManagedMutationReservationSync(canonicalInput); |
| return { created: true, runtimeEventSeq }; |
| }); |
| } |
| |
| private assertManagedMutationReservationAvailableSync(input: CommitToolPreparedInput): void { |
| const mutation = input.dispatchRuntimeEvent.actions?.toolDispatch?.managedMutation; |
| if (!mutation) return; |
| const call = input.runtimeEvent.content; |
| const callArgs = call?.kind === 'function_call' ? call.args : undefined; |
| const callPath = |
| callArgs && typeof callArgs === 'object' && !Array.isArray(callArgs) |
| ? (callArgs as { path?: unknown }).path |
| : undefined; |
| if ( |
| (input.toolName !== 'Write' && input.toolName !== 'Edit') || |
| input.recoveryMode !== 'reconcile' || |
| input.dispatchRuntimeEvent.actions?.toolDispatch?.toolName !== input.toolName |
| ) { |
| throw new Error('Managed mutation reservation requires a reconcile Write operation'); |
| } |
| if (typeof callPath !== 'string' || mutation.expectedPath !== callPath) { |
| throw new Error('Managed mutation path does not match its durable tool call'); |
| } |
| if (!this.#readWorkspaceStorageRootBinding()) { |
| throw new Error('Managed mutation reservation requires a durable storage-root binding'); |
| } |
| const authority = this.readCanonicalWorkspaceAuthoritySync(); |
| this.assertWorkspaceProjectionsMatchSync(authority); |
| const epoch = authority.baselines.find( |
| (candidate) => |
| candidate.epoch.workspaceId === mutation.workspaceId && |
| candidate.epoch.workspaceEpochId === mutation.workspaceEpochId, |
| )?.epoch; |
| const head = authority.heads.find( |
| (candidate) => |
| candidate.workspaceId === mutation.workspaceId && |
| candidate.workspaceEpochId === mutation.workspaceEpochId, |
| ); |
| if ( |
| !epoch || |
| !head || |
| epoch.repositoryId !== mutation.repositoryId || |
| epoch.workspaceInstanceId !== mutation.workspaceInstanceId || |
| epoch.objectFormat !== mutation.objectFormat || |
| head.workspaceVersionId !== mutation.baseWorkspaceVersionId || |
| head.acceptedEventId !== mutation.baseAcceptedEventId || |
| head.revision !== mutation.baseHeadRevision || |
| head.commitOid !== mutation.baseCommitOid || |
| head.treeOid !== mutation.baseTreeOid |
| ) { |
| throw new Error('Managed mutation reservation does not match the canonical workspace head'); |
| } |
| const active = this.db |
| .prepare(` |
| SELECT operation_id FROM runtime_managed_mutation_reservations |
| WHERE workspace_instance_id = ? |
| `) |
| .get(mutation.workspaceInstanceId) as { operation_id: string } | undefined; |
| if (active) { |
| throw new Error( |
| `Managed mutation reservation conflict with operation ${active.operation_id}`, |
| ); |
| } |
| } |
| |
| private insertManagedMutationReservationSync(input: CommitToolPreparedInput): void { |
| const dispatch = input.dispatchRuntimeEvent.actions?.toolDispatch; |
| const mutation = dispatch?.managedMutation; |
| if (!dispatch || !mutation) return; |
| this.insertManagedMutationReservationProjectionSync({ |
| workspace_instance_id: mutation.workspaceInstanceId, |
| repository_id: mutation.repositoryId, |
| workspace_id: mutation.workspaceId, |
| workspace_epoch_id: mutation.workspaceEpochId, |
| operation_id: input.operationId, |
| dispatch_event_id: input.dispatchRuntimeEvent.id, |
| base_workspace_version_id: mutation.baseWorkspaceVersionId, |
| base_accepted_event_id: mutation.baseAcceptedEventId, |
| base_head_revision: mutation.baseHeadRevision, |
| base_commit_oid: mutation.baseCommitOid, |
| base_tree_oid: mutation.baseTreeOid, |
| expected_paths_json: JSON.stringify([mutation.expectedPath]), |
| execution_profile_digest: mutation.executionProfileDigest, |
| protocol_version: 1, |
| reserved_at: input.committedAt, |
| }); |
| } |
| |
| private insertManagedMutationReservationProjectionSync( |
| reservation: ManagedMutationReservationProjectionRow, |
| ): void { |
| this.db |
| .prepare(` |
| INSERT INTO runtime_managed_mutation_reservations ( |
| workspace_instance_id, repository_id, workspace_id, workspace_epoch_id, |
| operation_id, dispatch_event_id, base_workspace_version_id, |
| base_accepted_event_id, base_head_revision, base_commit_oid, base_tree_oid, |
| expected_paths_json, execution_profile_digest, protocol_version, reserved_at |
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?) |
| `) |
| .run( |
| reservation.workspace_instance_id, |
| reservation.repository_id, |
| reservation.workspace_id, |
| reservation.workspace_epoch_id, |
| reservation.operation_id, |
| reservation.dispatch_event_id, |
| reservation.base_workspace_version_id, |
| reservation.base_accepted_event_id, |
| reservation.base_head_revision, |
| reservation.base_commit_oid, |
| reservation.base_tree_oid, |
| reservation.expected_paths_json, |
| reservation.execution_profile_digest, |
| reservation.reserved_at, |
| ); |
| } |
| |
| async commitToolOutcome(input: CommitToolOutcomeInput): Promise<ToolCommitResult> { |
| const canonicalInput: CommitToolOutcomeInput = { |
| ...input, |
| runtimeEvent: canonicalizeRuntimeEventForStorage(input.runtimeEvent), |
| }; |
| assertNoReservedWorkspaceAuthorityAppend(canonicalInput.runtimeEvent); |
| assertOutcomeInput(canonicalInput); |
| return this.transaction(() => this.commitToolOutcomeSync(canonicalInput)); |
| } |
| |
| async commitToolRecoveryBundle(input: RuntimeRecoveryBundleCommit): Promise<void> { |
| const canonicalInput: RuntimeRecoveryBundleCommit = { |
| ...input, |
| reconcileRuntimeEvent: canonicalizeRuntimeEventForStorage(input.reconcileRuntimeEvent), |
| ...(input.outcomeRuntimeEvent |
| ? { outcomeRuntimeEvent: canonicalizeRuntimeEventForStorage(input.outcomeRuntimeEvent) } |
| : {}), |
| decisionRuntimeEvent: canonicalizeRuntimeEventForStorage(input.decisionRuntimeEvent), |
| }; |
| assertNoReservedWorkspaceAuthorityAppend(canonicalInput.reconcileRuntimeEvent); |
| assertNoReservedWorkspaceAuthorityAppend(canonicalInput.decisionRuntimeEvent); |
| if (canonicalInput.outcomeRuntimeEvent) { |
| assertNoReservedWorkspaceAuthorityAppend(canonicalInput.outcomeRuntimeEvent); |
| assertNoReservedRecoveryFact(canonicalInput.outcomeRuntimeEvent); |
| } |
| this.transaction(() => { |
| const operation = this.readToolOperationSync(canonicalInput.operationId); |
| if (!operation) throw new Error(`Unknown tool operation ${canonicalInput.operationId}`); |
| if (!operation.dispatchEventId) { |
| throw new Error('Recovery bundle requires a durable dispatch RuntimeEvent'); |
| } |
| assertToolRecoveryEventBundle({ |
| operation: recoveryOperationIdentity(operation), |
| callEvent: this.readRequiredRuntimeEvent(operation.callEventId), |
| dispatchEvent: this.readRequiredRuntimeEvent(operation.dispatchEventId), |
| reconcileEvent: canonicalInput.reconcileRuntimeEvent, |
| outcomeEvent: canonicalInput.outcomeRuntimeEvent, |
| decisionEvent: canonicalInput.decisionRuntimeEvent, |
| }); |
| assertStrictRuntimeEventOrder([ |
| this.runtimeEventSeq(operation.callEventId), |
| this.runtimeEventSeq(operation.dispatchEventId), |
| ]); |
| this.assertToolLedgerTransition( |
| [ |
| canonicalInput.reconcileRuntimeEvent, |
| ...(canonicalInput.outcomeRuntimeEvent ? [canonicalInput.outcomeRuntimeEvent] : []), |
| canonicalInput.decisionRuntimeEvent, |
| ], |
| 'recovery_bundle', |
| ); |
| if (operation.currentState !== 'prepared' || operation.resultEventId !== undefined) { |
| this.assertExactRecoveryBundleAlreadyCommitted(canonicalInput, operation); |
| return; |
| } |
| |
| this.commitRecoveryFactSync( |
| operation, |
| canonicalInput.reconcileRuntimeEvent, |
| 'reconcile_observed', |
| ); |
| this.options.failpoint?.('after_recovery_reconcile'); |
| if (canonicalInput.outcomeRuntimeEvent) { |
| this.commitToolOutcomeSync({ |
| operationId: canonicalInput.operationId, |
| journalEventId: `${canonicalInput.operationId}_outcome`, |
| runtimeEvent: canonicalInput.outcomeRuntimeEvent, |
| committedAt: canonicalInput.outcomeRuntimeEvent.ts, |
| }); |
| this.options.failpoint?.('after_recovery_outcome'); |
| } |
| |
| const decision = canonicalInput.decisionRuntimeEvent.actions?.toolRecovery; |
| if (!decision || decision.kind !== 'maka.tool.recovery_decision') { |
| throw new Error('Recovery bundle requires a recovery decision'); |
| } |
| const current = this.readToolOperationSync(canonicalInput.operationId); |
| if (!current) throw new Error(`Unknown tool operation ${canonicalInput.operationId}`); |
| this.commitRecoveryFactSync( |
| current, |
| canonicalInput.decisionRuntimeEvent, |
| decision.payload.disposition === 'completed' ? 'recovery_completed' : 'recovery_parked', |
| decision.payload, |
| ); |
| this.options.failpoint?.('after_recovery_decision'); |
| }); |
| } |
| |
| async readToolOperation(operationId: string): Promise<ToolOperationRecord | undefined> { |
| return this.readToolOperationSync(operationId); |
| } |
| |
| async listUnsettledToolOperations(sessionId?: string): Promise<ToolOperationRecord[]> { |
| const query = ` |
| SELECT operation_id, invocation_id, run_id, turn_id, provider_tool_call_id, |
| tool_name, canonical_args_hash, recovery_mode, current_state, |
| call_event_id, dispatch_event_id, result_event_id, version |
| FROM tool_operations |
| WHERE current_state = 'prepared' |
| AND result_event_id IS NULL |
| AND dispatch_event_id IS NOT NULL |
| ${ |
| sessionId === undefined |
| ? '' |
| : 'AND call_event_id IN (SELECT event_id FROM runtime_events WHERE session_id = ?)' |
| } |
| ORDER BY invocation_id ASC, operation_id ASC |
| `; |
| const statement = this.db.prepare(query); |
| const rows = (sessionId === undefined |
| ? statement.all() |
| : statement.all(sessionId)) as unknown as ToolOperationRow[]; |
| return rows.map(toolOperationFromRow); |
| } |
| |
| async readToolJournal(operationId: string): Promise<ToolJournalEventRecord[]> { |
| const rows = this.db |
| .prepare(` |
| SELECT journal_event_id, operation_id, invocation_id, run_id, turn_id, |
| state, runtime_event_id, canonical_args_hash, recovery_mode, |
| external_handle, metadata_json, committed_at |
| FROM tool_journal_events |
| WHERE operation_id = ? |
| ORDER BY journal_seq ASC |
| `) |
| .all(operationId) as unknown as ToolJournalRow[]; |
| return rows.map(toolJournalRecordFromRow); |
| } |
| |
| async rebuildToolProjectionsFromRuntimeEvents(): Promise<ToolProjectionRebuildResult> { |
| return this.transaction(() => this.rebuildToolProjectionsFromRuntimeEventsSync()); |
| } |
| |
| private rebuildToolProjectionsFromRuntimeEventsSync( |
| sessionId?: string, |
| ): ToolProjectionRebuildResult { |
| const statement = this.db.prepare(` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, |
| event_seq, payload_json, committed_at |
| FROM runtime_events |
| ${sessionId === undefined ? '' : 'WHERE session_id = ?'} |
| ORDER BY invocation_id ASC, event_seq ASC, event_id ASC |
| `); |
| const rows = (sessionId === undefined |
| ? statement.all() |
| : statement.all(sessionId)) as unknown as Array< |
| RuntimeEventStorageRow & { event_seq: number; committed_at: number } |
| >; |
| const events = rows.map(decodeRuntimeEventStorageRow); |
| const eventOrder = new Map(events.map((event, index) => [event.id, index] as const)); |
| const committedAt = new Map( |
| rows.map((row, index) => [events[index]!.id, row.committed_at] as const), |
| ); |
| const scan = scanToolLedger(events); |
| if (scan.hasCorruption) { |
| const first = scan.issues[0]; |
| throw new Error( |
| `Corrupt tool RuntimeEvent ledger: ${first?.code ?? 'unknown'} at ${first?.eventId ?? 'unknown'}`, |
| ); |
| } |
| const projected = scan.operations.filter((operation) => operation.dispatchEvent); |
| |
| // Mainline schema 4 can contain pre-authority projections without a |
| // dispatch RuntimeEvent. They remain readable but quarantined from |
| // recovery; only projections backed by canonical T1 facts are rebuilt. |
| if (sessionId === undefined) { |
| this.db.exec(` |
| DELETE FROM tool_journal_events |
| WHERE operation_id IN ( |
| SELECT operation_id FROM tool_operations WHERE dispatch_event_id IS NOT NULL |
| ); |
| DELETE FROM tool_operations WHERE dispatch_event_id IS NOT NULL; |
| `); |
| } else { |
| this.db |
| .prepare(` |
| DELETE FROM tool_journal_events |
| WHERE operation_id IN ( |
| SELECT operation_id |
| FROM tool_operations |
| WHERE dispatch_event_id IS NOT NULL |
| AND call_event_id IN ( |
| SELECT event_id FROM runtime_events WHERE session_id = ? |
| ) |
| ) |
| `) |
| .run(sessionId); |
| this.db |
| .prepare(` |
| DELETE FROM tool_operations |
| WHERE dispatch_event_id IS NOT NULL |
| AND call_event_id IN ( |
| SELECT event_id FROM runtime_events WHERE session_id = ? |
| ) |
| `) |
| .run(sessionId); |
| } |
| let journalEvents = 0; |
| for (const operation of projected) { |
| const call = operation.callEvent; |
| const event = operation.dispatchEvent; |
| const dispatch = event?.actions?.toolDispatch; |
| if (!call || !event || !dispatch) { |
| throw new Error('Tool projection scan produced an incomplete dispatched operation'); |
| } |
| const recovery = interpretScannedToolRecovery(operation, eventOrder); |
| if (recovery.kind === 'corruption') { |
| throw new Error( |
| `Corrupt tool recovery bundle for ${dispatch.operationId}: ${recovery.code}`, |
| ); |
| } |
| const reconcileEvent = recovery.kind === 'valid' ? recovery.reconcileEvent : undefined; |
| const decisionEvent = recovery.kind === 'valid' ? recovery.decisionEvent : undefined; |
| |
| this.db |
| .prepare(` |
| INSERT INTO tool_journal_events ( |
| journal_event_id, operation_id, invocation_id, run_id, turn_id, state, |
| runtime_event_id, canonical_args_hash, recovery_mode, committed_at |
| ) VALUES (?, ?, ?, ?, ?, 'prepared', ?, ?, ?, ?) |
| `) |
| .run( |
| `${dispatch.operationId}_prepared`, |
| dispatch.operationId, |
| event.invocationId, |
| event.runId, |
| event.turnId, |
| event.id, |
| dispatch.canonicalArgsHash, |
| dispatch.recoveryMode, |
| committedAt.get(event.id) ?? event.ts, |
| ); |
| journalEvents += 1; |
| const response = operation.responseEvent; |
| const decision = recovery.kind === 'valid' ? recovery.decision : undefined; |
| const currentState = decision |
| ? decision.disposition === 'completed' |
| ? 'recovery_completed' |
| : 'recovery_parked' |
| : response |
| ? 'outcome_committed' |
| : 'prepared'; |
| const tail = [ |
| ...(reconcileEvent |
| ? [{ event: reconcileEvent, state: 'reconcile_observed' as const }] |
| : []), |
| ...(response ? [{ event: response, state: 'outcome_committed' as const }] : []), |
| ...(decisionEvent |
| ? [ |
| { |
| event: decisionEvent, |
| state: |
| decision?.disposition === 'parked' |
| ? ('recovery_parked' as const) |
| : ('recovery_completed' as const), |
| }, |
| ] |
| : []), |
| ].sort( |
| (a, b) => |
| requireRuntimeEventOrder(eventOrder, a.event.id) - |
| requireRuntimeEventOrder(eventOrder, b.event.id), |
| ); |
| this.db |
| .prepare(` |
| INSERT INTO tool_operations ( |
| operation_id, invocation_id, run_id, turn_id, provider_tool_call_id, |
| tool_name, canonical_args_hash, recovery_mode, current_state, |
| call_event_id, dispatch_event_id, result_event_id, version |
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
| `) |
| .run( |
| dispatch.operationId, |
| event.invocationId, |
| event.runId, |
| event.turnId, |
| dispatch.providerToolCallId, |
| dispatch.toolName, |
| dispatch.canonicalArgsHash, |
| dispatch.recoveryMode, |
| currentState, |
| call.id, |
| event.id, |
| response?.id ?? null, |
| 1 + tail.length, |
| ); |
| for (const item of tail) { |
| this.db |
| .prepare(` |
| INSERT INTO tool_journal_events ( |
| journal_event_id, operation_id, invocation_id, run_id, turn_id, state, |
| runtime_event_id, canonical_args_hash, recovery_mode, metadata_json, committed_at |
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
| `) |
| .run( |
| journalEventIdFor(dispatch.operationId, item.event, item.state), |
| dispatch.operationId, |
| item.event.invocationId, |
| item.event.runId, |
| item.event.turnId, |
| item.state, |
| item.event.id, |
| dispatch.canonicalArgsHash, |
| dispatch.recoveryMode, |
| item.event.actions?.toolRecovery |
| ? JSON.stringify(item.event.actions.toolRecovery) |
| : null, |
| committedAt.get(item.event.id) ?? item.event.ts, |
| ); |
| journalEvents += 1; |
| } |
| } |
| return { operations: projected.length, journalEvents }; |
| } |
| |
| private commitToolOutcomeSync( |
| input: CommitToolOutcomeInput, |
| settlementOwner: 'generic' | 'workspace_successor' | 'workspace_terminal' = 'generic', |
| ): ToolCommitResult { |
| const operation = this.readToolOperationSync(input.operationId); |
| if (!operation) throw new Error(`Unknown tool operation ${input.operationId}`); |
| assertOutcomeIdentity(operation, input.runtimeEvent); |
| this.assertToolLedgerTransition([input.runtimeEvent], 't2_outcome'); |
| if (operation.resultEventId) { |
| if (operation.resultEventId !== input.runtimeEvent.id) { |
| throw new Error(`Tool operation outcome conflict for ${input.operationId}`); |
| } |
| assertStoredRuntimeEventEquals( |
| input.runtimeEvent, |
| this.readRuntimeEventJson(input.runtimeEvent.id), |
| ); |
| return { created: false, runtimeEventSeq: this.runtimeEventSeq(input.runtimeEvent.id) }; |
| } |
| if (!operation.dispatchEventId) { |
| throw new Error(`Tool operation ${input.operationId} is missing its dispatch event`); |
| } |
| const dispatchJson = this.readRuntimeEventJson(operation.dispatchEventId); |
| const dispatchEvent = dispatchJson |
| ? decodeRuntimeEvent(JSON.parse(dispatchJson) as unknown) |
| : undefined; |
| if ( |
| dispatchEvent?.actions?.toolDispatch?.resultProjectionVersion === 1 && |
| input.runtimeEvent.content?.kind === 'function_response' && |
| input.runtimeEvent.content.modelProjection === undefined |
| ) { |
| throw new Error('Projected Tool Result T2 requires its durable model projection'); |
| } |
| if (dispatchEvent?.actions?.toolDispatch?.managedMutation) { |
| const reservation = this.db |
| .prepare(` |
| SELECT operation_id FROM runtime_managed_mutation_reservations |
| WHERE operation_id = ? |
| `) |
| .get(input.operationId) as { operation_id: string } | undefined; |
| if (!reservation) { |
| throw new Error('Managed mutation T1 is missing its durable reservation'); |
| } |
| if (settlementOwner === 'generic') { |
| throw new Error('Managed mutation outcome requires a managed mutation authority writer'); |
| } |
| } |
| const runtimeEventSeq = this.insertRuntimeEvent(input.runtimeEvent, input.committedAt, false); |
| this.options.failpoint?.('after_runtime_event_insert'); |
| this.insertToolJournalEvent( |
| operation, |
| input.runtimeEvent, |
| 'outcome_committed', |
| input.journalEventId, |
| input.committedAt, |
| ); |
| const updated = this.db |
| .prepare(` |
| UPDATE tool_operations |
| SET current_state = 'outcome_committed', result_event_id = ?, version = version + 1 |
| WHERE operation_id = ? AND current_state = 'prepared' AND result_event_id IS NULL |
| `) |
| .run(input.runtimeEvent.id, input.operationId); |
| if (updated.changes !== 1) { |
| throw new Error(`Tool operation compare-and-set failed for ${input.operationId}`); |
| } |
| return { created: true, runtimeEventSeq }; |
| } |
| |
| private commitRecoveryFactSync( |
| operation: ToolOperationRecord, |
| event: RuntimeEvent, |
| state: 'reconcile_observed' | 'recovery_completed' | 'recovery_parked', |
| decision?: ToolRecoveryDecisionFact, |
| ): void { |
| this.insertRuntimeEvent(event, event.ts, false); |
| this.options.failpoint?.('after_runtime_event_insert'); |
| this.insertToolJournalEvent(operation, event, state); |
| if (state === 'reconcile_observed') { |
| const updated = this.db |
| .prepare('UPDATE tool_operations SET version = version + 1 WHERE operation_id = ?') |
| .run(operation.operationId); |
| if (updated.changes !== 1) { |
| throw new Error(`Tool operation compare-and-set failed for ${operation.operationId}`); |
| } |
| return; |
| } |
| |
| if ( |
| state === 'recovery_completed' && |
| (decision?.disposition !== 'completed' || |
| operation.currentState !== 'outcome_committed' || |
| operation.resultEventId !== decision.outcomeEventId) |
| ) { |
| throw new Error('Completed recovery decision does not match the persisted outcome'); |
| } |
| if ( |
| state === 'recovery_parked' && |
| (decision?.disposition !== 'parked' || |
| operation.currentState !== 'prepared' || |
| operation.resultEventId !== undefined) |
| ) { |
| throw new Error('Parked recovery decision does not match the prepared operation'); |
| } |
| const updated = this.db |
| .prepare(` |
| UPDATE tool_operations |
| SET current_state = ?, version = version + 1 |
| WHERE operation_id = ? AND current_state = ? |
| `) |
| .run( |
| state, |
| operation.operationId, |
| state === 'recovery_completed' ? 'outcome_committed' : 'prepared', |
| ); |
| if (updated.changes !== 1) { |
| throw new Error(`Tool operation compare-and-set failed for ${operation.operationId}`); |
| } |
| } |
| |
| private insertToolJournalEvent( |
| operation: ToolOperationRecord, |
| event: RuntimeEvent, |
| state: ToolJournalState, |
| journalEventId = `${event.id}_journal`, |
| committedAt = event.ts, |
| ): void { |
| this.db |
| .prepare(` |
| INSERT INTO tool_journal_events ( |
| journal_event_id, operation_id, invocation_id, run_id, turn_id, state, |
| runtime_event_id, canonical_args_hash, recovery_mode, metadata_json, committed_at |
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
| `) |
| .run( |
| journalEventId, |
| operation.operationId, |
| operation.invocationId, |
| operation.runId, |
| operation.turnId, |
| state, |
| event.id, |
| operation.canonicalArgsHash, |
| operation.recoveryMode, |
| event.actions?.toolRecovery ? JSON.stringify(event.actions.toolRecovery) : null, |
| committedAt, |
| ); |
| this.options.failpoint?.('after_journal_event_insert'); |
| } |
| |
| private assertExactRecoveryBundleAlreadyCommitted( |
| input: RuntimeRecoveryBundleCommit, |
| operation: ToolOperationRecord, |
| ): void { |
| const decision = input.decisionRuntimeEvent.actions?.toolRecovery; |
| const completed = |
| decision?.kind === 'maka.tool.recovery_decision' && |
| decision.payload.disposition === 'completed'; |
| if ( |
| (completed && |
| (!input.outcomeRuntimeEvent || |
| operation.currentState !== 'recovery_completed' || |
| operation.resultEventId !== input.outcomeRuntimeEvent.id)) || |
| (!completed && |
| (input.outcomeRuntimeEvent !== undefined || |
| operation.currentState !== 'recovery_parked' || |
| operation.resultEventId !== undefined)) |
| ) { |
| throw new Error(`Tool operation ${operation.operationId} is already settled`); |
| } |
| for (const event of [ |
| input.reconcileRuntimeEvent, |
| ...(input.outcomeRuntimeEvent ? [input.outcomeRuntimeEvent] : []), |
| input.decisionRuntimeEvent, |
| ]) { |
| const stored = this.readRuntimeEventJson(event.id); |
| if (stored === undefined) { |
| throw new Error(`Tool recovery bundle is incomplete for ${operation.operationId}`); |
| } |
| assertStoredRuntimeEventEquals(event, stored); |
| } |
| } |
| |
| private transaction<T>(operation: () => T): T { |
| if (this.databaseLease) return this.databaseLease.transaction('write', operation); |
| this.db.exec('BEGIN IMMEDIATE'); |
| let result: T; |
| try { |
| result = operation(); |
| this.db.exec('COMMIT'); |
| } catch (error) { |
| try { |
| this.db.exec('ROLLBACK'); |
| } catch { |
| // Preserve the protocol failure that caused rollback. |
| } |
| this.settleEventCommits(false); |
| throw error; |
| } |
| this.settleEventCommits(true); |
| return result; |
| } |
| |
| private noteEventCommit(sessionId: string): void { |
| if (this.uncommittedEventSessions.size === 0 && this.databaseLease) { |
| this.databaseLease.onTransactionSettled((committed) => this.settleEventCommits(committed)); |
| } |
| this.uncommittedEventSessions.add(sessionId); |
| } |
| |
| private settleEventCommits(committed: boolean): void { |
| const sessionIds = [...this.uncommittedEventSessions]; |
| this.uncommittedEventSessions.clear(); |
| if (!committed) return; |
| for (const sessionId of sessionIds) { |
| for (const listener of this.eventCommitListeners) listener(sessionId); |
| } |
| } |
| |
| subscribeRuntimeEventCommits(listener: (sessionId: string) => void): () => void { |
| this.eventCommitListeners.add(listener); |
| return () => this.eventCommitListeners.delete(listener); |
| } |
| |
| private readTransaction<T>(operation: () => T): T { |
| if (this.databaseLease) return this.databaseLease.transaction('read', operation); |
| this.db.exec('BEGIN'); |
| try { |
| const result = operation(); |
| this.db.exec('COMMIT'); |
| return result; |
| } catch (error) { |
| try { |
| this.db.exec('ROLLBACK'); |
| } catch { |
| // Preserve the consistency failure that caused rollback. |
| } |
| throw error; |
| } |
| } |
| |
| private readContinuationClaimRow( |
| predicate: string, |
| ...values: readonly SQLInputValue[] |
| ): ContinuationClaimStorageRow | undefined { |
| return this.db |
| .prepare(` |
| SELECT |
| claim_id, |
| source_session_id, |
| source_invocation_id, |
| source_run_id, |
| source_turn_id, |
| source_event_high_water, |
| source_prefix_digest, |
| boundary_digest, |
| boundary_json, |
| provider_projection_version, |
| provider_replay_digest, |
| target_session_id, |
| target_invocation_id, |
| target_run_id, |
| target_turn_id, |
| target_opening_json, |
| claimed_at, |
| start_event_id, |
| start_kind, |
| protocol_version |
| FROM runtime_continuation_claims |
| WHERE ${predicate} |
| LIMIT 1 |
| `) |
| .get(...values) as ContinuationClaimStorageRow | undefined; |
| } |
| |
| private readContinuationClaimRows(): ContinuationClaimStorageRow[] { |
| return this.db |
| .prepare(` |
| SELECT |
| claim_id, |
| source_session_id, |
| source_invocation_id, |
| source_run_id, |
| source_turn_id, |
| source_event_high_water, |
| source_prefix_digest, |
| boundary_digest, |
| boundary_json, |
| provider_projection_version, |
| provider_replay_digest, |
| target_session_id, |
| target_invocation_id, |
| target_run_id, |
| target_turn_id, |
| target_opening_json, |
| claimed_at, |
| start_event_id, |
| start_kind, |
| protocol_version |
| FROM runtime_continuation_claims |
| ORDER BY claimed_at ASC, claim_id ASC |
| `) |
| .all() as unknown as ContinuationClaimStorageRow[]; |
| } |
| |
| private assertContinuationAuthorityIntegrity(): void { |
| for (const row of this.readContinuationClaimRows()) { |
| this.decodeContinuationClaimStateRow(row); |
| } |
| } |
| |
| private continuationTargetHasRuntimeState(claim: ContinuationClaimV1): boolean { |
| const { target } = claim; |
| const values = [ |
| target.invocationId, |
| target.sessionId, |
| target.runId, |
| claim.targetOpening.source.kind === 'handoff' ? 1 : 0, |
| target.sessionId, |
| target.turnId, |
| ] as const; |
| const runtimeEvent = this.db |
| .prepare(` |
| SELECT 1 AS found |
| FROM runtime_events |
| WHERE invocation_id = ? |
| OR (session_id = ? AND run_id = ?) |
| OR (? = 0 AND session_id = ? AND turn_id = ?) |
| LIMIT 1 |
| `) |
| .get(...values) as { found: number } | undefined; |
| if (runtimeEvent) return true; |
| return ( |
| (this.db |
| .prepare(` |
| SELECT 1 AS found |
| FROM runtime_partial_snapshots |
| WHERE invocation_id = ? |
| OR (session_id = ? AND run_id = ?) |
| OR (? = 0 AND session_id = ? AND turn_id = ?) |
| LIMIT 1 |
| `) |
| .get(...values) as { found: number } | undefined) !== undefined |
| ); |
| } |
| |
| private decodeContinuationClaimStateRow( |
| row: ContinuationClaimStorageRow, |
| ): ContinuationClaimStateV1 { |
| const claim = decodeContinuationClaimRow(row); |
| if (!row.start_event_id) { |
| if (row.start_kind !== null) { |
| throw new Error(`Continuation claim start kind exists without event for ${claim.claimId}`); |
| } |
| return { claim }; |
| } |
| if (row.start_kind !== 'runtime_admission' && row.start_kind !== 'claim_repair') { |
| throw new Error(`Continuation claim start kind is missing for ${claim.claimId}`); |
| } |
| const start = this.readRequiredRuntimeEvent(row.start_event_id); |
| assertContinuationStartEvent(claim, start, row.start_kind); |
| if (start.id !== row.start_event_id || this.runtimeEventSeq(start.id) !== 1) { |
| throw new Error(`Continuation claim start identity mismatch for ${claim.claimId}`); |
| } |
| return { claim, startEventId: row.start_event_id, startKind: row.start_kind }; |
| } |
| |
| private assertContinuationBoundaryMatchesLedger(claim: ContinuationClaimV1): void { |
| const lastIndex = claim.boundary.segments.length - 1; |
| let previousPrefix: ImmutableRuntimePrefixV1 | undefined; |
| for (const [index, segment] of claim.boundary.segments.entries()) { |
| let prefix: ImmutableRuntimePrefixV1; |
| try { |
| prefix = this.readImmutableRuntimePrefixSync({ |
| sessionId: segment.identity.sessionId, |
| runId: segment.identity.runId, |
| ...(index === lastIndex ? {} : { upToEventSeq: segment.position.lastEventSeq }), |
| }); |
| } catch (error) { |
| if ( |
| error instanceof Error && |
| (error.message === 'immutable RuntimeEvent prefix is empty' || |
| error.message.includes('high-water') || |
| error.message.includes('event_seq gap')) |
| ) { |
| throw new Error( |
| index === lastIndex |
| ? 'Continuation source boundary is missing' |
| : `Continuation ancestor boundary is missing for ${segment.identity.runId}`, |
| ); |
| } |
| throw error; |
| } |
| if ( |
| !isDeepStrictEqual(prefix.identity, segment.identity) || |
| !isDeepStrictEqual(prefix.position, segment.position) || |
| prefix.prefixDigest !== segment.prefixDigest |
| ) { |
| throw new Error( |
| index === lastIndex |
| ? 'Continuation source boundary changed' |
| : `Continuation ancestor boundary changed for ${segment.identity.runId}`, |
| ); |
| } |
| const opening = prefix.events[0]?.content; |
| const repeatsTurn = previousPrefix?.identity.turnId === prefix.identity.turnId; |
| if ( |
| repeatsTurn || |
| (opening?.kind === 'invocation_opened' && opening.source.kind === 'handoff') |
| ) { |
| if ( |
| !previousPrefix || |
| opening?.kind !== 'invocation_opened' || |
| opening.source.kind !== 'handoff' |
| ) { |
| throw new Error('Same-turn boundary requires an authenticated handoff edge'); |
| } |
| const row = this.readContinuationClaimRow('claim_id = ?', opening.source.claimId); |
| const state = row && this.decodeContinuationClaimStateRow(row); |
| if ( |
| !state || |
| state.startEventId !== prefix.events[0]?.id || |
| !isDeepStrictEqual( |
| state.claim.boundary.segments, |
| claim.boundary.segments.slice(0, index), |
| ) || |
| !continuationStartEventMatchesClaim(prefix.events[0], state.claim, state.startKind) |
| ) { |
| throw new Error('Same-turn boundary handoff claim does not authenticate its lineage'); |
| } |
| assertHandoffClaimSource(state.claim, previousPrefix); |
| } |
| if (index === lastIndex) { |
| assertHandoffClaimSource(claim, prefix); |
| const terminalEvents = prefix.events.filter(isTerminalRuntimeEvent); |
| const terminal = terminalEvents[0]; |
| if (terminalEvents.length !== 1 || !terminal || prefix.events.at(-1)?.id !== terminal.id) { |
| throw new Error( |
| 'Continuation source boundary must end with exactly one terminal RuntimeEvent', |
| ); |
| } |
| } |
| previousPrefix = prefix; |
| } |
| } |
| |
| private assertToolLedgerTransition( |
| candidateEvents: readonly RuntimeEvent[], |
| expectedTransition: Parameters<typeof validateToolLedgerTransition>[0]['expectedTransition'], |
| ): void { |
| this.assertWorkspaceToolLedgerHealthy(); |
| // Tool-call identity is scoped by invocation. Reading unrelated invocations here turns |
| // concurrent subagents into repeated whole-workspace scans without strengthening the |
| // transition check; event and operation uniqueness remain enforced by SQLite keys. |
| const rows: RuntimeEventStorageRow[] = []; |
| const invocationIds = [...new Set(candidateEvents.map((event) => event.invocationId))].sort(); |
| const readInvocation = this.db.prepare(` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, payload_json |
| FROM runtime_events |
| WHERE invocation_id = ? |
| ORDER BY event_seq ASC, event_id ASC |
| `); |
| for (const invocationId of invocationIds) { |
| rows.push(...(readInvocation.all(invocationId) as unknown as RuntimeEventStorageRow[])); |
| } |
| const validation = validateToolLedgerTransition({ |
| existingEvents: rows.map(decodeRuntimeEventStorageRow), |
| candidateEvents: candidateEvents.map(canonicalizeRuntimeEventForStorage), |
| expectedTransition, |
| }); |
| if (!validation.ok) { |
| throw new ToolLedgerRejectionError(validation.code, validation.eventId); |
| } |
| } |
| |
| private assertWorkspaceToolLedgerHealthy(): void { |
| const dataVersion = this.runtimeDataVersion(); |
| if (!this.toolLedgerHealth || this.toolLedgerHealth.dataVersion !== dataVersion) { |
| this.refreshToolLedgerHealth(); |
| } |
| const health = this.toolLedgerHealth!; |
| if (health.decodeFailure) throw health.decodeFailure.error; |
| if (health.issue) { |
| // Pre-existing damage, not a bad candidate. Note the reach of "refused": |
| // this gate is only ever consulted for tool-bearing events, so a damaged |
| // ledger refuses tool facts and takes everything else. Callers that treat |
| // this as "the store is gone" are overreading it — see the note on the |
| // latch in `AgentRun.enqueueRuntimeEventStore`. |
| throw new ToolLedgerCorruptionError(health.issue.code, health.issue.eventId); |
| } |
| } |
| |
| private refreshToolLedgerHealth(): void { |
| const dataVersion = this.runtimeDataVersion(); |
| try { |
| const rows = this.db |
| .prepare(` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, payload_json |
| FROM runtime_events |
| ORDER BY invocation_id ASC, event_seq ASC, event_id ASC |
| `) |
| .all() as unknown as RuntimeEventStorageRow[]; |
| const scan = scanToolLedger(rows.map(decodeRuntimeEventStorageRow)); |
| this.toolLedgerHealth = { dataVersion, issue: scan.issues[0] }; |
| } catch (error) { |
| this.toolLedgerHealth = { dataVersion, decodeFailure: { error } }; |
| } |
| } |
| |
| private runtimeDataVersion(): number { |
| const row = this.db.prepare('PRAGMA data_version').get() as { data_version: number }; |
| return row.data_version; |
| } |
| |
| private assertInvocationIdentity(events: readonly RuntimeEvent[]): void { |
| const candidates = new Map<string, { sessionId: string; runId: string; turnId: string }>(); |
| const runs = new Map< |
| string, |
| { sessionId: string; invocationId: string; runId: string; turnId: string } |
| >(); |
| for (const event of events) { |
| const identity = { |
| sessionId: event.sessionId, |
| runId: event.runId, |
| turnId: event.turnId, |
| }; |
| const prior = candidates.get(event.invocationId); |
| if ( |
| prior && |
| (prior.sessionId !== identity.sessionId || |
| prior.runId !== identity.runId || |
| prior.turnId !== identity.turnId) |
| ) { |
| throw new Error(`RuntimeEvent invocation identity conflict for ${event.invocationId}`); |
| } |
| candidates.set(event.invocationId, identity); |
| const runKey = `${event.sessionId}\0${event.runId}`; |
| const priorRun = runs.get(runKey); |
| if ( |
| priorRun && |
| (priorRun.invocationId !== event.invocationId || priorRun.turnId !== event.turnId) |
| ) { |
| throw new Error(`RuntimeEvent run identity conflict for ${event.runId}`); |
| } |
| runs.set(runKey, { |
| sessionId: event.sessionId, |
| invocationId: event.invocationId, |
| runId: event.runId, |
| turnId: event.turnId, |
| }); |
| } |
| for (const [invocationId, identity] of candidates) { |
| const rows = this.db |
| .prepare(` |
| SELECT DISTINCT session_id, run_id, turn_id |
| FROM runtime_events |
| WHERE invocation_id = ? |
| UNION |
| SELECT DISTINCT session_id, run_id, turn_id |
| FROM runtime_partial_snapshots |
| WHERE invocation_id = ? |
| `) |
| .all(invocationId, invocationId) as Array<{ |
| session_id: string; |
| run_id: string; |
| turn_id: string; |
| }>; |
| if ( |
| rows.some( |
| (row) => |
| row.session_id !== identity.sessionId || |
| row.run_id !== identity.runId || |
| row.turn_id !== identity.turnId, |
| ) |
| ) { |
| throw new Error(`RuntimeEvent invocation identity conflict for ${invocationId}`); |
| } |
| } |
| for (const identity of runs.values()) { |
| const rows = this.db |
| .prepare(` |
| SELECT DISTINCT invocation_id, turn_id |
| FROM runtime_events |
| WHERE session_id = ? AND run_id = ? |
| UNION |
| SELECT DISTINCT invocation_id, turn_id |
| FROM runtime_partial_snapshots |
| WHERE session_id = ? AND run_id = ? |
| `) |
| .all(identity.sessionId, identity.runId, identity.sessionId, identity.runId) as Array<{ |
| invocation_id: string; |
| turn_id: string; |
| }>; |
| if ( |
| rows.some( |
| (row) => row.invocation_id !== identity.invocationId || row.turn_id !== identity.turnId, |
| ) |
| ) { |
| throw new Error(`RuntimeEvent run identity conflict for ${identity.runId}`); |
| } |
| } |
| } |
| |
| private assertContinuationAuthorityAllowsEvent( |
| event: RuntimeEvent, |
| authorizedPendingClaimId?: string, |
| exactRetry = false, |
| ): void { |
| const rows = this.readContinuationClaimRows(); |
| const ownClaim = rows.find( |
| (row) => |
| row.target_session_id === event.sessionId && |
| row.target_invocation_id === event.invocationId && |
| row.target_run_id === event.runId && |
| row.target_turn_id === event.turnId, |
| ); |
| const ownHandoff = ownClaim && decodeContinuationClaimRow(ownClaim); |
| for (const row of rows) { |
| const claim = decodeContinuationClaimRow(row); |
| const source = claim.boundary.segments.find( |
| (segment) => |
| segment.identity.sessionId === event.sessionId && segment.identity.runId === event.runId, |
| ); |
| if (source && !exactRetry) { |
| throw new Error( |
| `RuntimeEvent source boundary is sealed by continuation claim ${claim.claimId}`, |
| ); |
| } |
| if (source && exactRetry) continue; |
| |
| const target = claim.target; |
| const collidesWithTarget = |
| event.invocationId === target.invocationId || |
| (event.sessionId === target.sessionId && event.runId === target.runId) || |
| (event.sessionId === target.sessionId && |
| event.turnId === target.turnId && |
| !( |
| ownHandoff?.targetOpening.source.kind === 'handoff' && |
| ownHandoff.boundary.segments.some((segment) => segment.identity.runId === target.runId) |
| )); |
| if (!collidesWithTarget) continue; |
| if ( |
| event.sessionId !== target.sessionId || |
| event.invocationId !== target.invocationId || |
| event.runId !== target.runId || |
| event.turnId !== target.turnId |
| ) { |
| throw new Error(`RuntimeEvent continuation target identity conflict for ${claim.claimId}`); |
| } |
| if (!row.start_event_id && authorizedPendingClaimId !== claim.claimId) { |
| throw new Error( |
| `RuntimeEvent target sequence one is reserved for continuation-start by claim ${claim.claimId}`, |
| ); |
| } |
| } |
| } |
| |
| private assertRunNotSealed(event: RuntimeEvent): void { |
| const rows = this.db |
| .prepare(` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, payload_json |
| FROM runtime_events |
| WHERE session_id = ? AND run_id = ? |
| AND ${TERMINAL_RUNTIME_EVENT_SQL} |
| ORDER BY event_seq ASC |
| `) |
| .all(event.sessionId, event.runId) as unknown as RuntimeEventStorageRow[]; |
| const terminal = rows.map(decodeRuntimeEventStorageRow).find(isTerminalRuntimeEvent); |
| if (terminal) { |
| throw new RunSealedError(event.runId); |
| } |
| } |
| |
| private importRuntimeEventSync(event: RuntimeEvent): boolean { |
| const canonicalEvent = canonicalizeRuntimeEventForStorage(event); |
| this.assertInvocationIdentity([canonicalEvent]); |
| const partial = partialRuntimeStream(canonicalEvent); |
| if (partial) { |
| this.assertContinuationAuthorityAllowsEvent(canonicalEvent); |
| this.assertRunNotSealed(canonicalEvent); |
| return this.upsertRuntimePartial(canonicalEvent, partial); |
| } |
| const existing = this.readRuntimeEventJson(canonicalEvent.id) !== undefined; |
| // Seal before tool-ledger semantics, so every post-terminal append |
| // refuses the same way (#2311): a late tool-bearing straggler must read |
| // as the sealed-run boundary it is, not as a producer bug or ledger |
| // corruption. Continuation authority stays ahead of the seal, its |
| // refusals are more specific, and an exact-id retry keeps its dedup |
| // semantics: the event is already inside the seal, so only new events |
| // consult either. |
| if (!existing) { |
| this.assertContinuationAuthorityAllowsEvent(canonicalEvent); |
| this.assertRunNotSealed(canonicalEvent); |
| } |
| if (isToolLedgerBearingEvent(canonicalEvent)) { |
| this.assertToolLedgerTransition([canonicalEvent], 'generic_append'); |
| } |
| this.insertRuntimeEvent(canonicalEvent, canonicalEvent.ts, true); |
| return !existing; |
| } |
| |
| private importRuntimePartialBatchSync(events: readonly RuntimeEvent[]): void { |
| const first = events[0]; |
| if (!first) return; |
| const partials = events.map((event) => partialRuntimeStream(event)); |
| const firstPartial = partials[0]; |
| if (!firstPartial) { |
| throw new Error('Runtime partial batch contains a non-partial event'); |
| } |
| for (let index = 0; index < events.length; index += 1) { |
| const event = events[index]!; |
| const partial = partials[index]; |
| if (!partial) throw new Error('Runtime partial batch contains a non-partial event'); |
| if ( |
| partial.key !== firstPartial.key || |
| event.sessionId !== first.sessionId || |
| event.invocationId !== first.invocationId || |
| event.runId !== first.runId || |
| event.turnId !== first.turnId |
| ) { |
| throw new Error('Runtime partial batch must contain exactly one presentation stream'); |
| } |
| } |
| this.assertInvocationIdentity(events); |
| this.assertContinuationAuthorityAllowsEvent(first); |
| this.assertRunNotSealed(first); |
| const last = events.at(-1)!; |
| this.upsertRuntimePartial(first, { |
| ...firstPartial, |
| text: partials.map((partial) => partial!.text).join(''), |
| updatedAt: last.ts, |
| }); |
| } |
| |
| private assertImmutableSteeringMessageIdentity(event: RuntimeEvent): void { |
| const messageId = immutableSteeringMessageId(event); |
| if (!messageId) return; |
| const matches = this.readImmutableSessionRuntimeEvents(event.sessionId).filter( |
| (candidate) => immutableSteeringMessageId(candidate) === messageId, |
| ); |
| if (matches.some((candidate) => !isDeepStrictEqual(candidate, event))) { |
| throw new Error(`Immutable steering message identity conflict: ${messageId}`); |
| } |
| } |
| |
| private readImmutableSessionRuntimeEvents(sessionId: string): RuntimeEvent[] { |
| const rows = this.db |
| .prepare(` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, payload_json |
| FROM runtime_events |
| WHERE session_id = ? |
| ORDER BY committed_at ASC, event_id ASC |
| `) |
| .all(sessionId) as unknown as RuntimeEventStorageRow[]; |
| return rows.map(decodeRuntimeEventStorageRow); |
| } |
| |
| private insertRuntimeEvent( |
| event: RuntimeEvent, |
| committedAt: number, |
| allowExactDuplicate: boolean, |
| authorizedPendingContinuationClaimId?: string, |
| ): number { |
| const encoding = encodeCanonicalRuntimeEvent(event); |
| const canonicalEvent = encoding.event; |
| this.assertInvocationIdentity([canonicalEvent]); |
| assertRuntimeEventIdentity(canonicalEvent); |
| this.assertImmutableSteeringMessageIdentity(canonicalEvent); |
| const existingJson = this.readRuntimeEventJson(canonicalEvent.id); |
| if (existingJson !== undefined) { |
| assertStoredRuntimeEventEquals(canonicalEvent, existingJson); |
| this.assertContinuationAuthorityAllowsEvent( |
| canonicalEvent, |
| authorizedPendingContinuationClaimId, |
| true, |
| ); |
| this.deleteCompletedPartialSnapshot(canonicalEvent); |
| if (!allowExactDuplicate) { |
| throw new Error( |
| `RuntimeEvent ${canonicalEvent.id} already exists outside this tool transaction`, |
| ); |
| } |
| return this.runtimeEventSeq(canonicalEvent.id); |
| } |
| this.assertContinuationAuthorityAllowsEvent( |
| canonicalEvent, |
| authorizedPendingContinuationClaimId, |
| ); |
| this.assertRunNotSealed(canonicalEvent); |
| const next = this.nextRuntimeEventSeq(canonicalEvent.invocationId); |
| this.db |
| .prepare(` |
| INSERT INTO runtime_events ( |
| event_id, session_id, invocation_id, run_id, turn_id, event_seq, |
| event_kind, payload_json, committed_at |
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) |
| `) |
| .run( |
| canonicalEvent.id, |
| canonicalEvent.sessionId, |
| canonicalEvent.invocationId, |
| canonicalEvent.runId, |
| canonicalEvent.turnId, |
| next, |
| runtimeEventKind(canonicalEvent), |
| encoding.json, |
| committedAt, |
| ); |
| const ordinalRow = this.db |
| .prepare(` |
| SELECT COALESCE(MAX(ordinal), 0) + 1 AS next_ordinal |
| FROM runtime_session_event_ordinals |
| WHERE session_id = ? |
| `) |
| .get(canonicalEvent.sessionId) as { next_ordinal?: unknown }; |
| const ordinal = ordinalRow.next_ordinal; |
| if (typeof ordinal !== 'number' || !Number.isSafeInteger(ordinal) || ordinal < 1) { |
| throw new Error(`Invalid next RuntimeEvent Session ordinal for ${canonicalEvent.sessionId}`); |
| } |
| this.db |
| .prepare(` |
| INSERT INTO runtime_session_event_ordinals(session_id, ordinal, event_id) |
| VALUES (?, ?, ?) |
| `) |
| .run(canonicalEvent.sessionId, ordinal, canonicalEvent.id); |
| recordTranscriptTurnExtent( |
| this.db, |
| { ...canonicalEvent, kind: runtimeEventKind(canonicalEvent) }, |
| ordinal, |
| ); |
| this.noteEventCommit(canonicalEvent.sessionId); |
| this.deleteCompletedPartialSnapshot(canonicalEvent); |
| return next; |
| } |
| |
| private deleteCompletedPartialSnapshot(event: RuntimeEvent): void { |
| const completedPartialKey = completedPartialRuntimeStreamKey(event); |
| if (!completedPartialKey) return; |
| this.db |
| .prepare('DELETE FROM runtime_partial_snapshots WHERE stream_key = ?') |
| .run(completedPartialKey); |
| } |
| |
| private upsertRuntimePartial( |
| event: RuntimeEvent, |
| partial: { key: string; snapshot: RuntimeEvent; text: string; updatedAt?: number }, |
| ): boolean { |
| const existing = this.db |
| .prepare(` |
| SELECT 1 AS found FROM runtime_partial_snapshots WHERE stream_key = ? |
| `) |
| .get(partial.key) as { found: number } | undefined; |
| if (!existing && this.hasCompletedPartialStream(event.sessionId, event.runId, partial.key)) { |
| return false; |
| } |
| const anchor = existing |
| ? undefined |
| : (this.db |
| .prepare(` |
| SELECT event_id FROM runtime_events |
| WHERE session_id = ? AND run_id = ? |
| ORDER BY event_seq DESC LIMIT 1 |
| `) |
| .get(event.sessionId, event.runId) as { event_id: string } | undefined); |
| if (!existing) { |
| this.db |
| .prepare(` |
| INSERT INTO runtime_partial_snapshots ( |
| stream_key, session_id, invocation_id, run_id, turn_id, |
| after_event_id, payload_json, text_content, updated_at |
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) |
| `) |
| .run( |
| partial.key, |
| event.sessionId, |
| event.invocationId, |
| event.runId, |
| event.turnId, |
| anchor?.event_id ?? null, |
| JSON.stringify(partial.snapshot), |
| '', |
| partial.updatedAt ?? event.ts, |
| ); |
| } else { |
| this.db |
| .prepare('UPDATE runtime_partial_snapshots SET updated_at = ? WHERE stream_key = ?') |
| .run(partial.updatedAt ?? event.ts, partial.key); |
| } |
| if (partial.text.length > 0) { |
| this.appendRuntimePartialSegment(partial.key, partial.text, partial.updatedAt ?? event.ts); |
| } |
| return !existing; |
| } |
| |
| private appendRuntimePartialSegment(streamKey: string, text: string, updatedAt: number): void { |
| const tail = this.db |
| .prepare(` |
| SELECT segment_seq, length(CAST(text_content AS BLOB)) AS stored_bytes |
| FROM runtime_partial_segments |
| WHERE stream_key = ? |
| ORDER BY segment_seq DESC |
| LIMIT 1 |
| `) |
| .get(streamKey) as { segment_seq?: unknown; stored_bytes?: unknown } | undefined; |
| if (tail) { |
| const segmentSequence = requireRuntimeEventScanCount(tail.segment_seq); |
| const storedBytes = requireRuntimeEventScanCount(tail.stored_bytes); |
| if (storedBytes + Buffer.byteLength(text, 'utf8') <= RUNTIME_PARTIAL_SEGMENT_TARGET_BYTES) { |
| this.db |
| .prepare(` |
| UPDATE runtime_partial_segments |
| SET text_content = text_content || ?, updated_at = ? |
| WHERE stream_key = ? AND segment_seq = ? |
| `) |
| .run(text, updatedAt, streamKey, segmentSequence); |
| return; |
| } |
| } |
| this.db |
| .prepare(` |
| INSERT INTO runtime_partial_segments(stream_key, segment_seq, text_content, updated_at) |
| VALUES (?, ?, ?, ?) |
| `) |
| .run( |
| streamKey, |
| tail ? requireRuntimeEventScanCount(tail.segment_seq) + 1 : 1, |
| text, |
| updatedAt, |
| ); |
| } |
| |
| private hasCompletedPartialStream(sessionId: string, runId: string, streamKey: string): boolean { |
| const rows = this.db |
| .prepare(` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, payload_json |
| FROM runtime_events |
| WHERE session_id = ? AND run_id = ? |
| `) |
| .all(sessionId, runId) as unknown as RuntimeEventStorageRow[]; |
| return rows.some( |
| (row) => completedPartialRuntimeStreamKey(decodeRuntimeEventStorageRow(row)) === streamKey, |
| ); |
| } |
| |
| private nextRuntimeEventSeq(invocationId: string): number { |
| const row = this.db |
| .prepare(` |
| SELECT COALESCE(MAX(event_seq), 0) + 1 AS next_seq |
| FROM runtime_events |
| WHERE invocation_id = ? |
| `) |
| .get(invocationId) as { next_seq: number }; |
| return row.next_seq; |
| } |
| |
| private runtimeEventSeq(eventId: string): number { |
| const row = this.db |
| .prepare(` |
| SELECT event_seq FROM runtime_events WHERE event_id = ? |
| `) |
| .get(eventId) as { event_seq: number } | undefined; |
| if (!row) throw new Error(`Missing RuntimeEvent ${eventId}`); |
| return row.event_seq; |
| } |
| |
| private readRuntimeEventJson(eventId: string): string | undefined { |
| const row = this.db |
| .prepare(` |
| SELECT event_id, session_id, invocation_id, run_id, turn_id, payload_json |
| FROM runtime_events |
| WHERE event_id = ? |
| `) |
| .get(eventId) as RuntimeEventStorageRow | undefined; |
| if (row) decodeRuntimeEventStorageRow(row); |
| return row?.payload_json; |
| } |
| |
| private readRequiredRuntimeEvent(eventId: string): RuntimeEvent { |
| const stored = this.readRuntimeEventJson(eventId); |
| if (stored === undefined) throw new Error(`Missing RuntimeEvent ${eventId}`); |
| return decodeStoredRuntimeEvent(stored); |
| } |
| |
| private readToolOperationSync(operationId: string): ToolOperationRecord | undefined { |
| const row = this.db |
| .prepare(` |
| SELECT operation_id, invocation_id, run_id, turn_id, provider_tool_call_id, |
| tool_name, canonical_args_hash, recovery_mode, current_state, |
| call_event_id, dispatch_event_id, result_event_id, version |
| FROM tool_operations |
| WHERE operation_id = ? |
| `) |
| .get(operationId) as ToolOperationRow | undefined; |
| return row ? toolOperationFromRow(row) : undefined; |
| } |
| } |
| |
| interface ToolLedgerHealth { |
| dataVersion: number; |
| issue?: ReturnType<typeof scanToolLedger>['issues'][number]; |
| decodeFailure?: { error: unknown }; |
| } |
| |
| interface ToolOperationRow { |
| operation_id: string; |
| invocation_id: string; |
| run_id: string; |
| turn_id: string; |
| provider_tool_call_id: string; |
| tool_name: string; |
| canonical_args_hash: string; |
| recovery_mode: ToolRecoveryMode; |
| current_state: 'prepared' | 'outcome_committed' | 'recovery_completed' | 'recovery_parked'; |
| call_event_id: string; |
| dispatch_event_id: string | null; |
| result_event_id: string | null; |
| version: number; |
| } |
| |
| interface ToolJournalRow { |
| journal_event_id: string; |
| operation_id: string; |
| invocation_id: string; |
| run_id: string; |
| turn_id: string; |
| state: ToolJournalState; |
| runtime_event_id: string | null; |
| canonical_args_hash: string | null; |
| recovery_mode: ToolRecoveryMode | null; |
| external_handle: string | null; |
| metadata_json: string | null; |
| committed_at: number; |
| } |
| |
| function toolOperationFromRow(row: ToolOperationRow): ToolOperationRecord { |
| return { |
| operationId: row.operation_id, |
| invocationId: row.invocation_id, |
| runId: row.run_id, |
| turnId: row.turn_id, |
| providerToolCallId: row.provider_tool_call_id, |
| toolName: row.tool_name, |
| canonicalArgsHash: row.canonical_args_hash, |
| recoveryMode: row.recovery_mode, |
| currentState: row.current_state, |
| callEventId: row.call_event_id, |
| ...(row.dispatch_event_id ? { dispatchEventId: row.dispatch_event_id } : {}), |
| ...(row.result_event_id ? { resultEventId: row.result_event_id } : {}), |
| version: row.version, |
| }; |
| } |
| |
| function toolJournalRecordFromRow(row: ToolJournalRow): ToolJournalEventRecord { |
| return { |
| journalEventId: row.journal_event_id, |
| operationId: row.operation_id, |
| invocationId: row.invocation_id, |
| runId: row.run_id, |
| turnId: row.turn_id, |
| state: row.state, |
| ...(row.runtime_event_id ? { runtimeEventId: row.runtime_event_id } : {}), |
| ...(row.canonical_args_hash ? { canonicalArgsHash: row.canonical_args_hash } : {}), |
| ...(row.recovery_mode ? { recoveryMode: row.recovery_mode } : {}), |
| ...(row.external_handle ? { externalHandle: row.external_handle } : {}), |
| ...(row.metadata_json ? { metadata: JSON.parse(row.metadata_json) } : {}), |
| committedAt: row.committed_at, |
| }; |
| } |
| |
| function assertRuntimeEventIdentity(event: RuntimeEvent): void { |
| decodeRuntimeEvent(event); |
| for (const [field, value] of Object.entries({ |
| id: event.id, |
| sessionId: event.sessionId, |
| invocationId: event.invocationId, |
| runId: event.runId, |
| turnId: event.turnId, |
| })) { |
| if (typeof value !== 'string' || value.length === 0) |
| throw new Error(`Invalid RuntimeEvent ${field}`); |
| } |
| } |
| |
| function assertStoredRuntimeEventEquals(event: RuntimeEvent, storedJson: string | undefined): void { |
| if (storedJson === undefined) return; |
| const stored = decodeStoredRuntimeEvent(storedJson); |
| if (!isDeepStrictEqual(stored, canonicalizeRuntimeEventForStorage(event))) { |
| throw new Error(`RuntimeEvent identity conflict for ${event.id}`); |
| } |
| } |
| |
| function canonicalizeRuntimeEventForStorage(event: RuntimeEvent): RuntimeEvent { |
| return encodeCanonicalRuntimeEvent(event).event; |
| } |
| |
| function assertNoReservedRecoveryFact(event: RuntimeEvent): void { |
| if (event.actions?.toolRecovery !== undefined) { |
| throw new Error('Tool recovery facts require the atomic recovery bundle writer'); |
| } |
| } |
| |
| function assertNoReservedToolLedgerFact(event: RuntimeEvent): void { |
| assertNoReservedWorkspaceAuthorityAppend(event); |
| if (event.actions?.continuationStart !== undefined) { |
| throw new Error('Continuation start facts require the continuation authority writer'); |
| } |
| const validation = validateGenericToolLedgerAppend(event); |
| if (validation.ok) return; |
| if (validation.code === 'reserved_recovery_fact') { |
| throw new Error('Tool recovery facts require the atomic recovery bundle writer'); |
| } |
| if (validation.code === 'reserved_tool_boundary_fact') { |
| throw new Error('Durable tool facts require the atomic tool boundary writer'); |
| } |
| throw new Error(`RuntimeEvent ${event.id} violates its semantic lane`); |
| } |
| |
| function isToolLedgerBearingEvent(event: RuntimeEvent): boolean { |
| return ( |
| event.content?.kind === 'function_call' || |
| event.content?.kind === 'function_response' || |
| event.actions?.toolDispatch !== undefined || |
| event.actions?.toolRecovery !== undefined |
| ); |
| } |
| |
| function recoveryOperationIdentity(operation: ToolOperationRecord) { |
| if (!operation.dispatchEventId) { |
| throw new Error('Recovery bundle requires a durable dispatch RuntimeEvent'); |
| } |
| return { |
| operationId: operation.operationId, |
| invocationId: operation.invocationId, |
| runId: operation.runId, |
| turnId: operation.turnId, |
| providerToolCallId: operation.providerToolCallId, |
| toolName: operation.toolName, |
| canonicalArgsHash: operation.canonicalArgsHash, |
| recoveryMode: operation.recoveryMode, |
| callEventId: operation.callEventId, |
| dispatchEventId: operation.dispatchEventId, |
| }; |
| } |
| |
| function assertStrictRuntimeEventOrder(eventSequences: readonly number[]): void { |
| if ( |
| eventSequences.some( |
| (eventSequence, index) => index > 0 && eventSequence <= (eventSequences[index - 1] ?? -1), |
| ) |
| ) { |
| throw new Error('Recovery facts violate canonical RuntimeEvent causal order'); |
| } |
| } |
| |
| function requireRuntimeEventOrder( |
| eventOrder: ReadonlyMap<string, number>, |
| eventId: string, |
| ): number { |
| const order = eventOrder.get(eventId); |
| if (order === undefined) throw new Error(`Missing RuntimeEvent order for ${eventId}`); |
| return order; |
| } |
| |
| function journalEventIdFor( |
| operationId: string, |
| event: RuntimeEvent, |
| state: Exclude<ToolJournalState, 'prepared'>, |
| ): string { |
| return state === 'outcome_committed' ? `${operationId}_outcome` : `${event.id}_journal`; |
| } |
| |
| function assertRecoveryAuthorityCapability(db: DatabaseSync): void { |
| const row = db |
| .prepare('SELECT version FROM runtime_capabilities WHERE capability = ?') |
| .get(RUNTIME_RECOVERY_AUTHORITY_CAPABILITY) as { version?: unknown } | undefined; |
| if (row?.version !== RUNTIME_RECOVERY_AUTHORITY_CAPABILITY_VERSION) { |
| throw new Error( |
| `SQLite runtime recovery capability ${RUNTIME_RECOVERY_AUTHORITY_CAPABILITY}@${RUNTIME_RECOVERY_AUTHORITY_CAPABILITY_VERSION} is unavailable`, |
| ); |
| } |
| } |
| |
| function assertContinuationStartEvent( |
| claim: ContinuationClaimV1, |
| event: RuntimeEvent, |
| startKind: 'runtime_admission' | 'claim_repair', |
| ): void { |
| if (!continuationStartEventMatchesClaim(event, claim, startKind)) { |
| throw new Error('Invalid continuation-start authority event'); |
| } |
| } |
| |
| function assertContinuationAuthorityCapability(db: DatabaseSync): void { |
| const row = db |
| .prepare('SELECT version FROM runtime_capabilities WHERE capability = ?') |
| .get(RUNTIME_CONTINUATION_AUTHORITY_CAPABILITY) as { version?: unknown } | undefined; |
| if (row?.version !== RUNTIME_CONTINUATION_AUTHORITY_CAPABILITY_VERSION) { |
| throw new Error( |
| `SQLite runtime continuation capability ${RUNTIME_CONTINUATION_AUTHORITY_CAPABILITY}@${RUNTIME_CONTINUATION_AUTHORITY_CAPABILITY_VERSION} is unavailable`, |
| ); |
| } |
| } |
| |
| function assertRuntimeStorageSafeId(value: string, message: string): void { |
| if (!isRuntimeStorageSafeId(value)) throw new Error(message); |
| } |
| |
| function assertInvocationSearchLimit(limit: number): void { |
| if (!Number.isSafeInteger(limit) || limit < 1 || limit > 256) { |
| throw new RangeError('Runtime invocation search limit must be an integer between 1 and 256'); |
| } |
| } |
| |
| interface RuntimeEventStorageRow { |
| event_id: string; |
| session_id: string; |
| invocation_id: string; |
| run_id: string; |
| turn_id: string; |
| payload_json: string; |
| } |
| |
| interface ManagedMutationReservationProjectionRow { |
| workspace_instance_id: string; |
| repository_id: string; |
| workspace_id: string; |
| workspace_epoch_id: string; |
| operation_id: string; |
| dispatch_event_id: string; |
| base_workspace_version_id: string; |
| base_accepted_event_id: string; |
| base_head_revision: number; |
| base_commit_oid: string; |
| base_tree_oid: string; |
| expected_paths_json: string; |
| execution_profile_digest: string; |
| protocol_version: number; |
| reserved_at: number; |
| } |
| |
| type CanonicalWorkspaceAuthority = ReturnType<typeof scanWorkspaceBaselineAuthority> & { |
| activeManagedMutations: ManagedMutationReservationProjectionRow[]; |
| }; |
| |
| function assertWorkspaceVersionAuthorityCapability(db: DatabaseSync): void { |
| const row = db |
| .prepare('SELECT version FROM runtime_capabilities WHERE capability = ?') |
| .get(RUNTIME_WORKSPACE_VERSION_AUTHORITY_CAPABILITY) as { version?: unknown } | undefined; |
| if (row?.version !== RUNTIME_WORKSPACE_VERSION_AUTHORITY_CAPABILITY_VERSION) { |
| throw new Error( |
| `SQLite runtime workspace capability ${RUNTIME_WORKSPACE_VERSION_AUTHORITY_CAPABILITY}@${RUNTIME_WORKSPACE_VERSION_AUTHORITY_CAPABILITY_VERSION} is unavailable`, |
| ); |
| } |
| } |
| |
| interface WorkspaceEpochProjectionRow { |
| workspace_id: string; |
| workspace_epoch_id: string; |
| repository_id: string; |
| workspace_instance_id: string; |
| mode: string; |
| object_format: string; |
| source_commit_oid: string; |
| source_tree_oid: string; |
| initial_workspace_version_id: string; |
| materialization_profile_digest: string; |
| materialization_semantics: string; |
| policy_hash: string; |
| authority_session_id: string; |
| authority_invocation_id: string; |
| authority_run_id: string; |
| authority_turn_id: string; |
| epoch_opened_event_id: string; |
| protocol_version: number; |
| committed_at: number; |
| } |
| |
| interface WorkspaceVersionProjectionRow { |
| workspace_version_id: string; |
| repository_id: string; |
| workspace_id: string; |
| workspace_epoch_id: string; |
| object_format: string; |
| origin_kind: string; |
| origin_event_id: string; |
| parents_json: string; |
| operation_id: string | null; |
| dispatch_event_id: string | null; |
| outcome_event_id: string | null; |
| base_head_revision: number | null; |
| execution_profile_digest: string | null; |
| commit_oid: string; |
| tree_oid: string; |
| policy_hash: string; |
| tree_delta_digest: string; |
| changed_paths_json: string; |
| changed_file_count: number; |
| deleted_file_count: number; |
| accepted_event_id: string; |
| protocol_version: number; |
| committed_at: number; |
| } |
| |
| interface WorkspaceHeadProjectionRow { |
| workspace_id: string; |
| workspace_epoch_id: string; |
| repository_id: string; |
| workspace_version_id: string; |
| accepted_event_id: string; |
| commit_oid: string; |
| tree_oid: string; |
| revision: number; |
| } |
| |
| function workspaceEpochRecord( |
| authority: ScannedWorkspaceBaselineAuthority, |
| ): WorkspaceEpochRecordV1 { |
| return { |
| ...authority.epoch, |
| epochOpenedEventId: authority.epochOpenedEventId, |
| authority: authority.authority, |
| committedAt: authority.epochOpenedAt, |
| }; |
| } |
| |
| function workspaceBaselineVersionRecord(authority: ScannedWorkspaceBaselineAuthority) { |
| return { |
| ...authority.baseline, |
| acceptedEventId: authority.baselineAcceptedEventId, |
| committedAt: authority.baselineAcceptedAt, |
| }; |
| } |
| |
| function workspaceSuccessorVersionRecord(authority: ScannedWorkspaceSuccessorAuthority) { |
| return { |
| ...authority.successor, |
| acceptedEventId: authority.acceptedEventId, |
| committedAt: authority.acceptedAt, |
| }; |
| } |
| |
| function workspaceHeadBeforeSuccessor( |
| authority: ReturnType<typeof scanWorkspaceBaselineAuthority>, |
| successor: WorkspaceVersionAcceptedV1, |
| ): WorkspaceHeadRecordV1 | undefined { |
| const parentId = successor.parents[0]; |
| const baseline = authority.baselines.find( |
| (candidate) => candidate.baseline.workspaceVersionId === parentId, |
| ); |
| if (baseline) { |
| return { |
| repositoryId: baseline.baseline.repositoryId, |
| workspaceId: baseline.baseline.workspaceId, |
| workspaceEpochId: baseline.baseline.workspaceEpochId, |
| workspaceVersionId: baseline.baseline.workspaceVersionId, |
| acceptedEventId: baseline.baselineAcceptedEventId, |
| commitOid: baseline.baseline.commitOid, |
| treeOid: baseline.baseline.treeOid, |
| revision: successor.baseHeadRevision, |
| }; |
| } |
| const prior = authority.successors.find( |
| (candidate) => candidate.successor.workspaceVersionId === parentId, |
| ); |
| if (!prior) return undefined; |
| return { |
| repositoryId: prior.successor.repositoryId, |
| workspaceId: prior.successor.workspaceId, |
| workspaceEpochId: prior.successor.workspaceEpochId, |
| workspaceVersionId: prior.successor.workspaceVersionId, |
| acceptedEventId: prior.acceptedEventId, |
| commitOid: prior.successor.commitOid, |
| treeOid: prior.successor.treeOid, |
| revision: successor.baseHeadRevision, |
| }; |
| } |
| |
| function managedMutationMatchesAcceptedSuccessor( |
| mutation: RuntimeEventManagedWorkspaceMutationV2 | undefined, |
| successor: WorkspaceVersionAcceptedV1, |
| baseHead: WorkspaceHeadRecordV1, |
| workspaceInstanceId: string, |
| ): boolean { |
| return ( |
| mutation?.protocol === 'managed_mutation_v2' && |
| mutation.repositoryId === successor.repositoryId && |
| mutation.workspaceId === successor.workspaceId && |
| mutation.workspaceEpochId === successor.workspaceEpochId && |
| mutation.workspaceInstanceId === workspaceInstanceId && |
| mutation.objectFormat === successor.objectFormat && |
| mutation.baseWorkspaceVersionId === successor.parents[0] && |
| mutation.baseAcceptedEventId === successor.baseAcceptedEventId && |
| mutation.baseHeadRevision === successor.baseHeadRevision && |
| mutation.baseCommitOid === baseHead.commitOid && |
| mutation.baseTreeOid === baseHead.treeOid && |
| mutation.executionProfileDigest === successor.executionProfileDigest && |
| isDeepStrictEqual([mutation.expectedPath], successor.changedPaths) |
| ); |
| } |
| |
| function workspaceEpochProjectionRow( |
| authority: ScannedWorkspaceBaselineAuthority, |
| ): WorkspaceEpochProjectionRow { |
| const record = workspaceEpochRecord(authority); |
| return { |
| workspace_id: record.workspaceId, |
| workspace_epoch_id: record.workspaceEpochId, |
| repository_id: record.repositoryId, |
| workspace_instance_id: record.workspaceInstanceId, |
| mode: record.mode, |
| object_format: record.objectFormat, |
| source_commit_oid: record.sourceCommitOid, |
| source_tree_oid: record.sourceTreeOid, |
| initial_workspace_version_id: record.initialWorkspaceVersionId, |
| materialization_profile_digest: record.materializationProfileDigest, |
| materialization_semantics: record.materializationSemantics, |
| policy_hash: record.policyHash, |
| authority_session_id: record.authority.sessionId, |
| authority_invocation_id: record.authority.invocationId, |
| authority_run_id: record.authority.runId, |
| authority_turn_id: record.authority.turnId, |
| epoch_opened_event_id: record.epochOpenedEventId, |
| protocol_version: 1, |
| committed_at: record.committedAt, |
| }; |
| } |
| |
| function workspaceBaselineVersionProjectionRow( |
| authority: ScannedWorkspaceBaselineAuthority, |
| ): WorkspaceVersionProjectionRow { |
| const record = workspaceBaselineVersionRecord(authority); |
| return { |
| workspace_version_id: record.workspaceVersionId, |
| repository_id: record.repositoryId, |
| workspace_id: record.workspaceId, |
| workspace_epoch_id: record.workspaceEpochId, |
| object_format: record.objectFormat, |
| origin_kind: record.origin.kind, |
| origin_event_id: record.origin.epochOpenedEventId, |
| parents_json: '[]', |
| operation_id: null, |
| dispatch_event_id: null, |
| outcome_event_id: null, |
| base_head_revision: null, |
| execution_profile_digest: null, |
| commit_oid: record.commitOid, |
| tree_oid: record.treeOid, |
| policy_hash: record.policyHash, |
| tree_delta_digest: record.treeDeltaDigest, |
| changed_paths_json: '[]', |
| changed_file_count: record.changedFileCount, |
| deleted_file_count: record.deletedFileCount, |
| accepted_event_id: record.acceptedEventId, |
| protocol_version: 1, |
| committed_at: record.committedAt, |
| }; |
| } |
| |
| function workspaceSuccessorVersionProjectionRow( |
| authority: ScannedWorkspaceSuccessorAuthority, |
| ): WorkspaceVersionProjectionRow { |
| const record = workspaceSuccessorVersionRecord(authority); |
| return { |
| workspace_version_id: record.workspaceVersionId, |
| repository_id: record.repositoryId, |
| workspace_id: record.workspaceId, |
| workspace_epoch_id: record.workspaceEpochId, |
| object_format: record.objectFormat, |
| origin_kind: record.origin.kind, |
| origin_event_id: record.origin.outcomeEventId, |
| parents_json: JSON.stringify(record.parents), |
| operation_id: record.origin.operationId, |
| dispatch_event_id: record.origin.dispatchEventId, |
| outcome_event_id: record.origin.outcomeEventId, |
| base_head_revision: record.baseHeadRevision, |
| execution_profile_digest: record.executionProfileDigest, |
| commit_oid: record.commitOid, |
| tree_oid: record.treeOid, |
| policy_hash: record.policyHash, |
| tree_delta_digest: record.treeDeltaDigest, |
| changed_paths_json: JSON.stringify(record.changedPaths), |
| changed_file_count: record.changedFileCount, |
| deleted_file_count: record.deletedFileCount, |
| accepted_event_id: record.acceptedEventId, |
| protocol_version: 1, |
| committed_at: record.committedAt, |
| }; |
| } |
| |
| function workspaceHeadProjectionRow(record: WorkspaceHeadRecordV1): WorkspaceHeadProjectionRow { |
| return { |
| workspace_id: record.workspaceId, |
| workspace_epoch_id: record.workspaceEpochId, |
| repository_id: record.repositoryId, |
| workspace_version_id: record.workspaceVersionId, |
| accepted_event_id: record.acceptedEventId, |
| commit_oid: record.commitOid, |
| tree_oid: record.treeOid, |
| revision: record.revision, |
| }; |
| } |
| |
| function compareWorkspaceEpochRow( |
| left: WorkspaceEpochProjectionRow, |
| right: WorkspaceEpochProjectionRow, |
| ): number { |
| return ( |
| left.workspace_id.localeCompare(right.workspace_id) || |
| left.workspace_epoch_id.localeCompare(right.workspace_epoch_id) |
| ); |
| } |
| |
| function compareWorkspaceVersionRow( |
| left: WorkspaceVersionProjectionRow, |
| right: WorkspaceVersionProjectionRow, |
| ): number { |
| return left.workspace_version_id.localeCompare(right.workspace_version_id); |
| } |
| |
| function compareWorkspaceHeadRow( |
| left: WorkspaceHeadProjectionRow, |
| right: WorkspaceHeadProjectionRow, |
| ): number { |
| return ( |
| left.workspace_id.localeCompare(right.workspace_id) || |
| left.workspace_epoch_id.localeCompare(right.workspace_epoch_id) |
| ); |
| } |
| |
| interface RuntimeEventPrefixStorageRow extends RuntimeEventStorageRow { |
| event_seq: number; |
| } |
| |
| type RuntimeEventPrefixProofStorageRow = Omit<RuntimeEventPrefixStorageRow, 'payload_json'> & { |
| stored_bytes: number; |
| payload_json: string | null; |
| }; |
| |
| interface ContinuationClaimStorageRow { |
| claim_id: string; |
| source_session_id: string; |
| source_invocation_id: string; |
| source_run_id: string; |
| source_turn_id: string; |
| source_event_high_water: number; |
| source_prefix_digest: string; |
| boundary_digest: string; |
| boundary_json: string; |
| provider_projection_version: number; |
| provider_replay_digest: string; |
| target_session_id: string; |
| target_invocation_id: string; |
| target_run_id: string; |
| target_turn_id: string; |
| target_opening_json: string; |
| claimed_at: number; |
| start_event_id: string | null; |
| start_kind: 'runtime_admission' | 'claim_repair' | null; |
| protocol_version: number; |
| } |
| |
| interface RuntimePartialStorageRow { |
| stream_key: string; |
| session_id: string; |
| invocation_id: string; |
| run_id: string; |
| turn_id: string; |
| payload_json: string; |
| text_content: string; |
| after_event_id: string | null; |
| } |
| |
| function decodeRuntimeEventStorageRow(row: RuntimeEventStorageRow): RuntimeEvent { |
| const event = decodeStoredRuntimeEvent(row.payload_json); |
| if ( |
| event.id !== row.event_id || |
| event.sessionId !== row.session_id || |
| event.invocationId !== row.invocation_id || |
| event.runId !== row.run_id || |
| event.turnId !== row.turn_id |
| ) { |
| throw new Error(`RuntimeEvent row/payload identity mismatch for ${row.event_id}`); |
| } |
| return event; |
| } |
| |
| function decodeRuntimePartialStorageRow(row: RuntimePartialStorageRow): RuntimeEvent { |
| const event = decodeStoredRuntimeEvent(row.payload_json); |
| if ( |
| event.sessionId !== row.session_id || |
| event.invocationId !== row.invocation_id || |
| event.runId !== row.run_id || |
| event.turnId !== row.turn_id || |
| partialRuntimeStream(event)?.key !== row.stream_key |
| ) { |
| throw new Error(`Runtime partial row/payload identity mismatch for ${row.stream_key}`); |
| } |
| return event; |
| } |
| |
| function decodeStoredRuntimeEvent(storedJson: string): RuntimeEvent { |
| return decodeRuntimeEvent(JSON.parse(storedJson)); |
| } |
| |
| function decodeContinuationClaimRow(row: ContinuationClaimStorageRow): ContinuationClaimV1 { |
| if (row.protocol_version !== 1) { |
| throw new Error(`Unsupported continuation claim protocol ${row.protocol_version}`); |
| } |
| const boundary = JSON.parse(row.boundary_json) as unknown; |
| const targetOpening = JSON.parse(row.target_opening_json) as unknown; |
| const claim = decodeContinuationClaim({ |
| protocol: 'continuation_claim_v1', |
| claimId: row.claim_id, |
| boundaryDigest: row.boundary_digest, |
| boundary, |
| providerProjectionVersion: row.provider_projection_version, |
| providerReplayDigest: row.provider_replay_digest, |
| target: { |
| sessionId: row.target_session_id, |
| invocationId: row.target_invocation_id, |
| runId: row.target_run_id, |
| turnId: row.target_turn_id, |
| }, |
| targetOpening, |
| claimedAt: row.claimed_at, |
| }); |
| const source = claim.boundary.segments.at(-1)!; |
| if ( |
| row.source_session_id !== source.identity.sessionId || |
| row.source_invocation_id !== source.identity.invocationId || |
| row.source_run_id !== source.identity.runId || |
| row.source_turn_id !== source.identity.turnId || |
| row.source_event_high_water !== source.position.lastEventSeq || |
| row.source_prefix_digest !== source.prefixDigest |
| ) { |
| throw new Error(`Continuation claim row/payload identity mismatch for ${row.claim_id}`); |
| } |
| return claim; |
| } |