import { Database } from 'bun:sqlite'; import { buildPairedTurnAttemptId } from './paired-turn-attempts.js'; import { tableHasColumn, tryExecMigration } from './migrations/helpers.js'; // Paired-turn provenance rebuild helpers extracted from the legacy schema // bundle. These remain runtime helpers because v10 replays them during // initialization for pre-versioned and partially-migrated databases. function getTableSql(database: Database, tableName: string): string | null { const row = database .prepare( ` SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ? `, ) .get(tableName) as { sql?: string | null } | undefined; return row?.sql ?? null; } function buildPairedTurnAttemptIdSql( turnIdExpr: string, attemptNoExpr: string, ): string { return `${turnIdExpr} || ':attempt:' || CAST(${attemptNoExpr} AS TEXT)`; } function buildPairedTurnAttemptParentIdSql( turnIdExpr: string, attemptNoExpr: string, ): string { return `CASE WHEN ${attemptNoExpr} <= 1 THEN NULL ELSE ${buildPairedTurnAttemptIdSql(turnIdExpr, `(${attemptNoExpr}) - 1`)} END`; } function buildPairedTurnAttemptParentHandoffMatchSql(args: { parentHandoffIdExpr: string; turnIdExpr: string; parentAttemptIdExpr: string; attemptNoExpr: string; }): string { return `EXISTS ( SELECT 1 FROM service_handoffs WHERE service_handoffs.id = ${args.parentHandoffIdExpr} AND service_handoffs.turn_id = ${args.turnIdExpr} AND service_handoffs.status = 'failed' AND ( service_handoffs.turn_attempt_id = ${args.parentAttemptIdExpr} OR ( service_handoffs.turn_attempt_id IS NULL AND service_handoffs.turn_attempt_no = (${args.attemptNoExpr}) - 1 ) ) )`; } function buildPairedTurnAttemptContinuationHandoffMatchSql(args: { continuationHandoffIdExpr: string; turnIdExpr: string; attemptIdExpr: string; attemptNoExpr: string; }): string { return `EXISTS ( SELECT 1 FROM service_handoffs WHERE service_handoffs.id = ${args.continuationHandoffIdExpr} AND service_handoffs.turn_id = ${args.turnIdExpr} AND ( service_handoffs.turn_attempt_id = ${args.attemptIdExpr} OR ( service_handoffs.turn_attempt_id IS NULL AND service_handoffs.turn_attempt_no = ${args.attemptNoExpr} ) ) )`; } function tableHasForeignKey( database: Database, args: { tableName: string; referencedTable: string; fromColumns: string[]; toColumns: string[]; onDelete?: string; }, ): boolean { const rows = database .prepare(`PRAGMA foreign_key_list(${args.tableName})`) .all() as Array<{ id: number; seq: number; table: string; from: string; to: string; on_delete: string; }>; if (rows.length === 0) { return false; } const byId = new Map(); for (const row of rows) { const group = byId.get(row.id) ?? []; group.push(row); byId.set(row.id, group); } for (const group of byId.values()) { const sorted = [...group].sort((left, right) => left.seq - right.seq); if (sorted.length !== args.fromColumns.length) { continue; } if (sorted[0]!.table !== args.referencedTable) { continue; } if ( args.onDelete && sorted[0]!.on_delete.toUpperCase() !== args.onDelete.toUpperCase() ) { continue; } const matches = sorted.every((row, index) => { return ( row.from === args.fromColumns[index] && row.to === args.toColumns[index] ); }); if (matches) { return true; } } return false; } interface PairedTurnAttemptTimingRow { turn_id: string; attempt_no: number; created_at: string; } interface PairedTurnAttemptTiming { attemptNo: number; createdAtMs: number; } function parseBackfillTimestampMs( value: string | null | undefined, ): number | null { if (!value) { return null; } const trimmed = value.trim(); if (trimmed.length === 0) { return null; } let normalized = trimmed.includes('T') ? trimmed : trimmed.replace(' ', 'T'); if (!/[zZ]|[+-]\d{2}:\d{2}$/.test(normalized)) { normalized = `${normalized}Z`; } const parsed = Date.parse(normalized); return Number.isNaN(parsed) ? null : parsed; } function getPairedTurnAttemptTimingsByTurnId( database: Database, ): Map { const rows = database .prepare( ` SELECT turn_id, attempt_no, created_at FROM paired_turn_attempts WHERE attempt_no >= 1 ORDER BY turn_id ASC, attempt_no ASC `, ) .all() as PairedTurnAttemptTimingRow[]; const timingsByTurnId = new Map(); for (const row of rows) { const createdAtMs = parseBackfillTimestampMs(row.created_at); if (createdAtMs === null) { throw new Error( `paired_turn_attempts(${row.turn_id}, attempt=${row.attempt_no}) has an invalid created_at timestamp`, ); } const timings = timingsByTurnId.get(row.turn_id) ?? []; timings.push({ attemptNo: row.attempt_no, createdAtMs, }); timingsByTurnId.set(row.turn_id, timings); } return timingsByTurnId; } function resolveAttemptNoForBackfill( args: { turnId: string; eventTimestamp: string | null | undefined; rowLabel: string; }, timingsByTurnId: Map, ): number | null { const attemptTimings = timingsByTurnId.get(args.turnId) ?? []; if (attemptTimings.length === 0) { return null; } if (attemptTimings.length === 1) { return attemptTimings[0]!.attemptNo; } const eventAtMs = parseBackfillTimestampMs(args.eventTimestamp); if (eventAtMs === null) { throw new Error( `${args.rowLabel} has turn_id=${args.turnId} but no timestamp that can be mapped to a specific attempt`, ); } const exactMatches = attemptTimings.filter((timing, index) => { const nextTiming = attemptTimings[index + 1]; return ( eventAtMs >= timing.createdAtMs && (nextTiming === undefined || eventAtMs < nextTiming.createdAtMs) ); }); if (exactMatches.length === 1) { return exactMatches[0]!.attemptNo; } if (exactMatches.length > 1) { throw new Error( `${args.rowLabel} has an ambiguous exact timestamp match for turn_id=${args.turnId}`, ); } const eventAtSecond = Math.floor(eventAtMs / 1000); const secondPrecisionMatches = attemptTimings.filter((timing, index) => { const nextTiming = attemptTimings[index + 1]; const timingSecond = Math.floor(timing.createdAtMs / 1000); const nextTimingSecond = nextTiming === undefined ? null : Math.floor(nextTiming.createdAtMs / 1000); return ( eventAtSecond >= timingSecond && (nextTimingSecond === null || eventAtSecond < nextTimingSecond) ); }); if (secondPrecisionMatches.length === 1) { return secondPrecisionMatches[0]!.attemptNo; } if (secondPrecisionMatches.length > 1) { throw new Error( `${args.rowLabel} has an ambiguous second-precision timestamp match for turn_id=${args.turnId}`, ); } throw new Error( `${args.rowLabel} could not be mapped to a specific attempt for turn_id=${args.turnId}`, ); } export function backfillPairedTurnAttemptIds(database: Database): void { if (!tableHasColumn(database, 'paired_turn_attempts', 'attempt_id')) { return; } database.exec(` UPDATE paired_turn_attempts SET attempt_id = ${buildPairedTurnAttemptIdSql('turn_id', 'attempt_no')} WHERE attempt_id IS NULL OR TRIM(attempt_id) = '' `); } export function backfillPairedTurnAttemptParentIds(database: Database): void { if (!tableHasColumn(database, 'paired_turn_attempts', 'parent_attempt_id')) { return; } database.exec(` UPDATE paired_turn_attempts SET parent_attempt_id = ${buildPairedTurnAttemptParentIdSql('turn_id', 'attempt_no')} WHERE attempt_no > 1 AND EXISTS ( SELECT 1 FROM paired_turn_attempts previous_attempt WHERE previous_attempt.turn_id = paired_turn_attempts.turn_id AND previous_attempt.attempt_no = paired_turn_attempts.attempt_no - 1 ) AND ( parent_attempt_id IS NULL OR TRIM(parent_attempt_id) = '' OR parent_attempt_id != ${buildPairedTurnAttemptParentIdSql('turn_id', 'attempt_no')} ) `); database.exec(` UPDATE paired_turn_attempts SET parent_attempt_id = NULL WHERE attempt_no <= 1 AND parent_attempt_id IS NOT NULL `); } export function backfillPairedTurnAttemptActiveRunIds( database: Database, ): void { if (!tableHasColumn(database, 'paired_turn_attempts', 'active_run_id')) { return; } const pairedTurnsActiveRunIdExists = tableHasColumn( database, 'paired_turns', 'active_run_id', ); const leaseTurnIdExists = tableHasColumn( database, 'paired_task_execution_leases', 'turn_id', ); const leaseTurnAttemptNoExists = tableHasColumn( database, 'paired_task_execution_leases', 'turn_attempt_no', ); const leaseActiveRunIdSql = leaseTurnIdExists ? leaseTurnAttemptNoExists ? `COALESCE( ( SELECT paired_task_execution_leases.claimed_run_id FROM paired_task_execution_leases WHERE paired_task_execution_leases.turn_id = paired_turn_attempts.turn_id AND paired_task_execution_leases.turn_attempt_no = paired_turn_attempts.attempt_no ORDER BY paired_task_execution_leases.updated_at DESC, paired_task_execution_leases.claimed_at DESC LIMIT 1 ), ( SELECT paired_task_execution_leases.claimed_run_id FROM paired_task_execution_leases WHERE paired_task_execution_leases.turn_id = paired_turn_attempts.turn_id AND paired_task_execution_leases.turn_attempt_no IS NULL ORDER BY paired_task_execution_leases.updated_at DESC, paired_task_execution_leases.claimed_at DESC LIMIT 1 ) )` : `( SELECT paired_task_execution_leases.claimed_run_id FROM paired_task_execution_leases WHERE paired_task_execution_leases.turn_id = paired_turn_attempts.turn_id ORDER BY paired_task_execution_leases.updated_at DESC, paired_task_execution_leases.claimed_at DESC LIMIT 1 )` : 'NULL'; const legacyTurnActiveRunIdSql = pairedTurnsActiveRunIdExists ? `( SELECT paired_turns.active_run_id FROM paired_turns WHERE paired_turns.turn_id = paired_turn_attempts.turn_id )` : 'NULL'; database.exec(` UPDATE paired_turn_attempts SET active_run_id = COALESCE( active_run_id, CASE WHEN state = 'running' THEN COALESCE( ${leaseActiveRunIdSql}, ${legacyTurnActiveRunIdSql} ) ELSE NULL END ) WHERE state = 'running' AND (active_run_id IS NULL OR TRIM(active_run_id) = '') `); database.exec(` UPDATE paired_turn_attempts SET active_run_id = NULL WHERE state != 'running' AND active_run_id IS NOT NULL `); } export function backfillPairedTurnAttemptEntityIds(database: Database): void { if ( tableHasColumn(database, 'paired_turn_reservations', 'turn_attempt_id') && tableHasColumn(database, 'paired_turn_reservations', 'turn_attempt_no') ) { database.exec(` UPDATE paired_turn_reservations SET turn_attempt_id = ( SELECT paired_turn_attempts.attempt_id FROM paired_turn_attempts WHERE paired_turn_attempts.turn_id = paired_turn_reservations.turn_id AND paired_turn_attempts.attempt_no = paired_turn_reservations.turn_attempt_no ) WHERE turn_attempt_no IS NOT NULL AND (turn_attempt_id IS NULL OR TRIM(turn_attempt_id) = '') `); } if ( tableHasColumn( database, 'paired_task_execution_leases', 'turn_attempt_id', ) && tableHasColumn(database, 'paired_task_execution_leases', 'turn_attempt_no') ) { database.exec(` UPDATE paired_task_execution_leases SET turn_attempt_id = ( SELECT paired_turn_attempts.attempt_id FROM paired_turn_attempts WHERE paired_turn_attempts.turn_id = paired_task_execution_leases.turn_id AND paired_turn_attempts.attempt_no = paired_task_execution_leases.turn_attempt_no ) WHERE turn_attempt_no IS NOT NULL AND (turn_attempt_id IS NULL OR TRIM(turn_attempt_id) = '') `); } if ( tableHasColumn(database, 'service_handoffs', 'turn_attempt_id') && tableHasColumn(database, 'service_handoffs', 'turn_attempt_no') ) { database.exec(` UPDATE service_handoffs SET turn_attempt_id = ( SELECT paired_turn_attempts.attempt_id FROM paired_turn_attempts WHERE paired_turn_attempts.turn_id = service_handoffs.turn_id AND paired_turn_attempts.attempt_no = service_handoffs.turn_attempt_no ) WHERE turn_attempt_no IS NOT NULL AND (turn_attempt_id IS NULL OR TRIM(turn_attempt_id) = '') `); } } export function backfillPairedTurnAttemptProvenance(database: Database): void { const timingsByTurnId = getPairedTurnAttemptTimingsByTurnId(database); if (tableHasColumn(database, 'paired_turn_reservations', 'turn_attempt_no')) { const hasTurnAttemptIdColumn = tableHasColumn( database, 'paired_turn_reservations', 'turn_attempt_id', ); const rows = database .prepare( ` SELECT rowid, turn_id, status, created_at, updated_at, consumed_at FROM paired_turn_reservations WHERE turn_id IS NOT NULL AND turn_attempt_no IS NULL `, ) .all() as Array<{ rowid: number; turn_id: string; status: string; created_at: string | null; updated_at: string | null; consumed_at: string | null; }>; const update = hasTurnAttemptIdColumn ? database.prepare( 'UPDATE paired_turn_reservations SET turn_attempt_id = ?, turn_attempt_no = ? WHERE rowid = ?', ) : database.prepare( 'UPDATE paired_turn_reservations SET turn_attempt_no = ? WHERE rowid = ?', ); for (const row of rows) { if (row.status !== 'completed') { continue; } const attemptNo = resolveAttemptNoForBackfill( { turnId: row.turn_id, eventTimestamp: row.consumed_at ?? row.updated_at ?? row.created_at, rowLabel: `paired_turn_reservations(rowid=${row.rowid})`, }, timingsByTurnId, ); if (attemptNo !== null) { const attemptId = buildPairedTurnAttemptId(row.turn_id, attemptNo); if (hasTurnAttemptIdColumn) { update.run(attemptId, attemptNo, row.rowid); } else { update.run(attemptNo, row.rowid); } } } } if ( tableHasColumn(database, 'paired_task_execution_leases', 'turn_attempt_no') ) { const hasTurnAttemptIdColumn = tableHasColumn( database, 'paired_task_execution_leases', 'turn_attempt_id', ); const rows = database .prepare( ` SELECT rowid, turn_id, claimed_at, updated_at FROM paired_task_execution_leases WHERE turn_id IS NOT NULL AND turn_attempt_no IS NULL `, ) .all() as Array<{ rowid: number; turn_id: string; claimed_at: string | null; updated_at: string | null; }>; const update = hasTurnAttemptIdColumn ? database.prepare( 'UPDATE paired_task_execution_leases SET turn_attempt_id = ?, turn_attempt_no = ? WHERE rowid = ?', ) : database.prepare( 'UPDATE paired_task_execution_leases SET turn_attempt_no = ? WHERE rowid = ?', ); for (const row of rows) { const attemptNo = resolveAttemptNoForBackfill( { turnId: row.turn_id, eventTimestamp: row.updated_at ?? row.claimed_at, rowLabel: `paired_task_execution_leases(rowid=${row.rowid})`, }, timingsByTurnId, ); if (attemptNo !== null) { const attemptId = buildPairedTurnAttemptId(row.turn_id, attemptNo); if (hasTurnAttemptIdColumn) { update.run(attemptId, attemptNo, row.rowid); } else { update.run(attemptNo, row.rowid); } } } } if (tableHasColumn(database, 'service_handoffs', 'turn_attempt_no')) { const hasTurnAttemptIdColumn = tableHasColumn( database, 'service_handoffs', 'turn_attempt_id', ); const rows = database .prepare( ` SELECT id, turn_id, created_at, claimed_at, completed_at FROM service_handoffs WHERE turn_id IS NOT NULL AND turn_attempt_no IS NULL `, ) .all() as Array<{ id: number; turn_id: string; created_at: string | null; claimed_at: string | null; completed_at: string | null; }>; const update = hasTurnAttemptIdColumn ? database.prepare( 'UPDATE service_handoffs SET turn_attempt_id = ?, turn_attempt_no = ? WHERE id = ?', ) : database.prepare( 'UPDATE service_handoffs SET turn_attempt_no = ? WHERE id = ?', ); for (const row of rows) { const attemptNo = resolveAttemptNoForBackfill( { turnId: row.turn_id, eventTimestamp: row.completed_at ?? row.claimed_at ?? row.created_at, rowLabel: `service_handoffs(id=${row.id})`, }, timingsByTurnId, ); if (attemptNo !== null) { const attemptId = buildPairedTurnAttemptId(row.turn_id, attemptNo); if (hasTurnAttemptIdColumn) { update.run(attemptId, attemptNo, row.id); } else { update.run(attemptNo, row.id); } } } } } export function assertPairedTurnAttemptProvenanceIntegrity( database: Database, ): void { if (tableHasColumn(database, 'paired_turn_attempts', 'attempt_id')) { const invalidAttemptIdRow = database .prepare( ` SELECT turn_id, attempt_no, attempt_id FROM paired_turn_attempts WHERE attempt_id IS NULL OR attempt_id != ${buildPairedTurnAttemptIdSql('turn_id', 'attempt_no')} LIMIT 1 `, ) .get() as | { turn_id: string; attempt_no: number; attempt_id: string | null; } | undefined; if (invalidAttemptIdRow) { throw new Error( `paired_turn_attempts(${invalidAttemptIdRow.turn_id}, attempt=${invalidAttemptIdRow.attempt_no}) has invalid attempt_id provenance`, ); } } if (tableHasColumn(database, 'paired_turn_attempts', 'parent_attempt_id')) { const invalidParentAttemptRow = database .prepare( ` SELECT turn_id, attempt_no, parent_attempt_id FROM paired_turn_attempts WHERE ( attempt_no <= 1 AND parent_attempt_id IS NOT NULL ) OR ( attempt_no > 1 AND NOT EXISTS ( SELECT 1 FROM paired_turn_attempts previous_attempt WHERE previous_attempt.turn_id = paired_turn_attempts.turn_id AND previous_attempt.attempt_no = paired_turn_attempts.attempt_no - 1 ) ) OR ( attempt_no > 1 AND ( parent_attempt_id IS NULL OR parent_attempt_id != ${buildPairedTurnAttemptParentIdSql('turn_id', 'attempt_no')} OR NOT EXISTS ( SELECT 1 FROM paired_turn_attempts previous_attempt WHERE COALESCE( previous_attempt.attempt_id, ${buildPairedTurnAttemptIdSql( 'previous_attempt.turn_id', 'previous_attempt.attempt_no', )} ) = paired_turn_attempts.parent_attempt_id AND previous_attempt.turn_id = paired_turn_attempts.turn_id AND previous_attempt.attempt_no = paired_turn_attempts.attempt_no - 1 ) ) ) LIMIT 1 `, ) .get() as | { turn_id: string; attempt_no: number; parent_attempt_id: string | null; } | undefined; if (invalidParentAttemptRow) { throw new Error( `paired_turn_attempts(${invalidParentAttemptRow.turn_id}, attempt=${invalidParentAttemptRow.attempt_no}) has invalid parent_attempt_id provenance`, ); } } if (tableHasColumn(database, 'paired_turn_attempts', 'parent_handoff_id')) { const invalidParentHandoffRow = database .prepare( ` SELECT turn_id, attempt_no, parent_handoff_id FROM paired_turn_attempts WHERE parent_handoff_id IS NOT NULL AND ( attempt_no <= 1 OR NOT ${buildPairedTurnAttemptParentHandoffMatchSql({ parentHandoffIdExpr: 'paired_turn_attempts.parent_handoff_id', turnIdExpr: 'paired_turn_attempts.turn_id', parentAttemptIdExpr: 'paired_turn_attempts.parent_attempt_id', attemptNoExpr: 'paired_turn_attempts.attempt_no', })} ) LIMIT 1 `, ) .get() as | { turn_id: string; attempt_no: number; parent_handoff_id: number | null; } | undefined; if (invalidParentHandoffRow) { throw new Error( `paired_turn_attempts(${invalidParentHandoffRow.turn_id}, attempt=${invalidParentHandoffRow.attempt_no}) has invalid parent_handoff_id provenance`, ); } } if ( tableHasColumn(database, 'paired_turn_attempts', 'continuation_handoff_id') ) { const invalidContinuationHandoffRow = database .prepare( ` SELECT turn_id, attempt_no, continuation_handoff_id FROM paired_turn_attempts WHERE continuation_handoff_id IS NOT NULL AND NOT ${buildPairedTurnAttemptContinuationHandoffMatchSql({ continuationHandoffIdExpr: 'paired_turn_attempts.continuation_handoff_id', turnIdExpr: 'paired_turn_attempts.turn_id', attemptIdExpr: "COALESCE(paired_turn_attempts.attempt_id, paired_turn_attempts.turn_id || ':attempt:' || CAST(paired_turn_attempts.attempt_no AS TEXT))", attemptNoExpr: 'paired_turn_attempts.attempt_no', })} LIMIT 1 `, ) .get() as | { turn_id: string; attempt_no: number; continuation_handoff_id: number | null; } | undefined; if (invalidContinuationHandoffRow) { throw new Error( `paired_turn_attempts(${invalidContinuationHandoffRow.turn_id}, attempt=${invalidContinuationHandoffRow.attempt_no}) has invalid continuation_handoff_id provenance`, ); } } const invalidAttemptRow = database .prepare( ` SELECT turn_id, attempt_no FROM paired_turn_attempts WHERE NOT EXISTS ( SELECT 1 FROM paired_turns WHERE paired_turns.turn_id = paired_turn_attempts.turn_id AND paired_turns.task_id = paired_turn_attempts.task_id AND paired_turns.task_updated_at = paired_turn_attempts.task_updated_at AND paired_turns.role = paired_turn_attempts.role AND paired_turns.intent_kind = paired_turn_attempts.intent_kind ) LIMIT 1 `, ) .get() as | { turn_id: string; attempt_no: number; } | undefined; if (invalidAttemptRow) { throw new Error( `paired_turn_attempts(${invalidAttemptRow.turn_id}, attempt=${invalidAttemptRow.attempt_no}) does not match its paired_turns identity`, ); } if (tableHasColumn(database, 'paired_turn_attempts', 'active_run_id')) { const invalidAttemptActiveRunRow = database .prepare( ` SELECT turn_id, attempt_no FROM paired_turn_attempts WHERE ( state = 'running' AND ( active_run_id IS NULL OR TRIM(active_run_id) = '' ) ) OR ( state != 'running' AND active_run_id IS NOT NULL ) LIMIT 1 `, ) .get() as | { turn_id: string; attempt_no: number; } | undefined; if (invalidAttemptActiveRunRow) { throw new Error( `paired_turn_attempts(${invalidAttemptActiveRunRow.turn_id}, attempt=${invalidAttemptActiveRunRow.attempt_no}) has invalid active_run_id provenance`, ); } } if (tableHasColumn(database, 'paired_turn_reservations', 'turn_attempt_no')) { const invalidReservation = database .prepare( ` SELECT rowid FROM paired_turn_reservations WHERE turn_attempt_no IS NOT NULL AND ( turn_id IS NULL OR (turn_attempt_id IS NULL AND ${tableHasColumn(database, 'paired_turn_reservations', 'turn_attempt_id') ? '1 = 1' : '0 = 1'}) OR NOT EXISTS ( SELECT 1 FROM paired_turn_attempts WHERE paired_turn_attempts.turn_id = paired_turn_reservations.turn_id AND paired_turn_attempts.attempt_no = paired_turn_reservations.turn_attempt_no ${ tableHasColumn( database, 'paired_turn_reservations', 'turn_attempt_id', ) ? 'AND paired_turn_attempts.attempt_id = paired_turn_reservations.turn_attempt_id' : '' } AND paired_turn_attempts.task_id = paired_turn_reservations.task_id AND paired_turn_attempts.task_updated_at = paired_turn_reservations.task_updated_at AND paired_turn_attempts.role = paired_turn_reservations.turn_role AND paired_turn_attempts.intent_kind = paired_turn_reservations.intent_kind ) ) LIMIT 1 `, ) .get() as { rowid: number } | undefined; if (invalidReservation) { throw new Error( `paired_turn_reservations(rowid=${invalidReservation.rowid}) has invalid paired_turn_attempt provenance`, ); } } if ( tableHasColumn(database, 'paired_task_execution_leases', 'turn_attempt_no') ) { const invalidLease = database .prepare( ` SELECT rowid FROM paired_task_execution_leases WHERE turn_attempt_no IS NOT NULL AND ( turn_id IS NULL OR (turn_attempt_id IS NULL AND ${tableHasColumn(database, 'paired_task_execution_leases', 'turn_attempt_id') ? '1 = 1' : '0 = 1'}) OR NOT EXISTS ( SELECT 1 FROM paired_turn_attempts WHERE paired_turn_attempts.turn_id = paired_task_execution_leases.turn_id AND paired_turn_attempts.attempt_no = paired_task_execution_leases.turn_attempt_no ${ tableHasColumn( database, 'paired_task_execution_leases', 'turn_attempt_id', ) ? 'AND paired_turn_attempts.attempt_id = paired_task_execution_leases.turn_attempt_id' : '' } AND paired_turn_attempts.task_id = paired_task_execution_leases.task_id AND paired_turn_attempts.task_updated_at = paired_task_execution_leases.task_updated_at AND paired_turn_attempts.role = paired_task_execution_leases.role AND paired_turn_attempts.intent_kind = paired_task_execution_leases.intent_kind ) ) LIMIT 1 `, ) .get() as { rowid: number } | undefined; if (invalidLease) { throw new Error( `paired_task_execution_leases(rowid=${invalidLease.rowid}) has invalid paired_turn_attempt provenance`, ); } } if (tableHasColumn(database, 'service_handoffs', 'turn_attempt_no')) { const invalidHandoff = database .prepare( ` SELECT id FROM service_handoffs WHERE turn_attempt_no IS NOT NULL AND ( turn_id IS NULL OR (turn_attempt_id IS NULL AND ${tableHasColumn(database, 'service_handoffs', 'turn_attempt_id') ? '1 = 1' : '0 = 1'}) OR paired_task_id IS NULL OR paired_task_updated_at IS NULL OR turn_role IS NULL OR turn_intent_kind IS NULL OR NOT EXISTS ( SELECT 1 FROM paired_turn_attempts WHERE paired_turn_attempts.turn_id = service_handoffs.turn_id AND paired_turn_attempts.attempt_no = service_handoffs.turn_attempt_no ${ tableHasColumn( database, 'service_handoffs', 'turn_attempt_id', ) ? 'AND paired_turn_attempts.attempt_id = service_handoffs.turn_attempt_id' : '' } AND paired_turn_attempts.task_id = service_handoffs.paired_task_id AND paired_turn_attempts.task_updated_at = service_handoffs.paired_task_updated_at AND paired_turn_attempts.role = service_handoffs.turn_role AND paired_turn_attempts.intent_kind = service_handoffs.turn_intent_kind ) ) LIMIT 1 `, ) .get() as { id: number } | undefined; if (invalidHandoff) { throw new Error( `service_handoffs(id=${invalidHandoff.id}) has invalid paired_turn_attempt provenance`, ); } } } function rebuildPairedTurnAttemptsWithForeignKeys(database: Database): void { const tableSql = getTableSql(database, 'paired_turn_attempts') ?? ''; if ( tableHasColumn(database, 'paired_turn_attempts', 'attempt_id') && tableHasColumn(database, 'paired_turn_attempts', 'parent_attempt_id') && tableHasColumn(database, 'paired_turn_attempts', 'parent_handoff_id') && tableHasColumn( database, 'paired_turn_attempts', 'continuation_handoff_id', ) && tableSql.includes('attempt_id TEXT NOT NULL PRIMARY KEY') && tableSql.includes('parent_attempt_id TEXT') && tableSql.includes('parent_handoff_id INTEGER') && tableSql.includes('continuation_handoff_id INTEGER') && tableSql.includes('UNIQUE (turn_id, attempt_no)') && tableHasForeignKey(database, { tableName: 'paired_turn_attempts', referencedTable: 'paired_turn_attempts', fromColumns: ['parent_attempt_id'], toColumns: ['attempt_id'], onDelete: 'CASCADE', }) && tableHasForeignKey(database, { tableName: 'paired_turn_attempts', referencedTable: 'service_handoffs', fromColumns: ['parent_handoff_id'], toColumns: ['id'], onDelete: 'SET NULL', }) && tableHasForeignKey(database, { tableName: 'paired_turn_attempts', referencedTable: 'service_handoffs', fromColumns: ['continuation_handoff_id'], toColumns: ['id'], onDelete: 'SET NULL', }) && tableHasForeignKey(database, { tableName: 'paired_turn_attempts', referencedTable: 'paired_turns', fromColumns: ['turn_id'], toColumns: ['turn_id'], onDelete: 'CASCADE', }) ) { return; } const parentAttemptIdSelectSql = tableHasColumn( database, 'paired_turn_attempts', 'parent_attempt_id', ) ? `COALESCE( parent_attempt_id, CASE WHEN attempt_no > 1 AND EXISTS ( SELECT 1 FROM paired_turn_attempts previous_attempt WHERE previous_attempt.turn_id = paired_turn_attempts.turn_id AND previous_attempt.attempt_no = paired_turn_attempts.attempt_no - 1 ) THEN ${buildPairedTurnAttemptParentIdSql('turn_id', 'attempt_no')} ELSE NULL END )` : `CASE WHEN attempt_no > 1 AND EXISTS ( SELECT 1 FROM paired_turn_attempts previous_attempt WHERE previous_attempt.turn_id = paired_turn_attempts.turn_id AND previous_attempt.attempt_no = paired_turn_attempts.attempt_no - 1 ) THEN ${buildPairedTurnAttemptParentIdSql('turn_id', 'attempt_no')} ELSE NULL END`; const parentHandoffIdSelectSql = tableHasColumn( database, 'paired_turn_attempts', 'parent_handoff_id', ) ? 'parent_handoff_id' : 'NULL'; const continuationHandoffIdSelectSql = tableHasColumn( database, 'paired_turn_attempts', 'continuation_handoff_id', ) ? 'continuation_handoff_id' : 'NULL'; const activeRunIdSelectSql = tableHasColumn( database, 'paired_turn_attempts', 'active_run_id', ) ? `CASE WHEN state = 'running' THEN active_run_id ELSE NULL END` : `CASE WHEN state = 'running' THEN ${ tableHasColumn(database, 'paired_turns', 'active_run_id') ? `COALESCE( ${ tableHasColumn( database, 'paired_task_execution_leases', 'turn_attempt_no', ) ? `COALESCE( ( SELECT paired_task_execution_leases.claimed_run_id FROM paired_task_execution_leases WHERE paired_task_execution_leases.turn_id = paired_turn_attempts.turn_id AND paired_task_execution_leases.turn_attempt_no = paired_turn_attempts.attempt_no ORDER BY paired_task_execution_leases.updated_at DESC, paired_task_execution_leases.claimed_at DESC LIMIT 1 ), ( SELECT paired_task_execution_leases.claimed_run_id FROM paired_task_execution_leases WHERE paired_task_execution_leases.turn_id = paired_turn_attempts.turn_id AND paired_task_execution_leases.turn_attempt_no IS NULL ORDER BY paired_task_execution_leases.updated_at DESC, paired_task_execution_leases.claimed_at DESC LIMIT 1 ) )` : `( SELECT paired_task_execution_leases.claimed_run_id FROM paired_task_execution_leases WHERE paired_task_execution_leases.turn_id = paired_turn_attempts.turn_id ORDER BY paired_task_execution_leases.updated_at DESC, paired_task_execution_leases.claimed_at DESC LIMIT 1 )` }, ( SELECT paired_turns.active_run_id FROM paired_turns WHERE paired_turns.turn_id = paired_turn_attempts.turn_id ) )` : `( ${ tableHasColumn( database, 'paired_task_execution_leases', 'turn_attempt_no', ) ? `COALESCE( ( SELECT paired_task_execution_leases.claimed_run_id FROM paired_task_execution_leases WHERE paired_task_execution_leases.turn_id = paired_turn_attempts.turn_id AND paired_task_execution_leases.turn_attempt_no = paired_turn_attempts.attempt_no ORDER BY paired_task_execution_leases.updated_at DESC, paired_task_execution_leases.claimed_at DESC LIMIT 1 ), ( SELECT paired_task_execution_leases.claimed_run_id FROM paired_task_execution_leases WHERE paired_task_execution_leases.turn_id = paired_turn_attempts.turn_id AND paired_task_execution_leases.turn_attempt_no IS NULL ORDER BY paired_task_execution_leases.updated_at DESC, paired_task_execution_leases.claimed_at DESC LIMIT 1 ) )` : `( SELECT paired_task_execution_leases.claimed_run_id FROM paired_task_execution_leases WHERE paired_task_execution_leases.turn_id = paired_turn_attempts.turn_id ORDER BY paired_task_execution_leases.updated_at DESC, paired_task_execution_leases.claimed_at DESC LIMIT 1 )` }` } ELSE NULL END`; database.transaction(() => { database.exec(` CREATE TABLE paired_turn_attempts_new ( attempt_id TEXT NOT NULL PRIMARY KEY, parent_attempt_id TEXT, parent_handoff_id INTEGER, continuation_handoff_id INTEGER, turn_id TEXT NOT NULL, attempt_no INTEGER NOT NULL, task_id TEXT NOT NULL, task_updated_at TEXT NOT NULL, role TEXT NOT NULL, intent_kind TEXT NOT NULL, state TEXT NOT NULL, executor_service_id TEXT, executor_agent_type TEXT, active_run_id TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, completed_at TEXT, last_error TEXT, UNIQUE (turn_id, attempt_no), FOREIGN KEY (parent_attempt_id) REFERENCES paired_turn_attempts_new(attempt_id) ON DELETE CASCADE, FOREIGN KEY (parent_handoff_id) REFERENCES service_handoffs(id) ON DELETE SET NULL, FOREIGN KEY (continuation_handoff_id) REFERENCES service_handoffs(id) ON DELETE SET NULL, FOREIGN KEY (turn_id) REFERENCES paired_turns(turn_id) ON DELETE CASCADE, CHECK (role IN ('owner', 'reviewer', 'arbiter')), CHECK ( intent_kind IN ( 'owner-turn', 'reviewer-turn', 'arbiter-turn', 'owner-follow-up', 'finalize-owner-turn' ) ), CHECK ( state IN ( 'running', 'delegated', 'completed', 'failed', 'cancelled' ) ), CHECK (executor_agent_type IN ('claude-code', 'codex') OR executor_agent_type IS NULL) ); INSERT INTO paired_turn_attempts_new ( attempt_id, parent_attempt_id, parent_handoff_id, continuation_handoff_id, turn_id, attempt_no, task_id, task_updated_at, role, intent_kind, state, executor_service_id, executor_agent_type, active_run_id, created_at, updated_at, completed_at, last_error ) SELECT COALESCE(attempt_id, ${buildPairedTurnAttemptIdSql('turn_id', 'attempt_no')}), ${parentAttemptIdSelectSql}, ${parentHandoffIdSelectSql}, ${continuationHandoffIdSelectSql}, turn_id, attempt_no, task_id, task_updated_at, role, intent_kind, state, executor_service_id, executor_agent_type, ${activeRunIdSelectSql}, created_at, updated_at, completed_at, last_error FROM paired_turn_attempts ORDER BY turn_id ASC, attempt_no ASC; DROP TABLE paired_turn_attempts; ALTER TABLE paired_turn_attempts_new RENAME TO paired_turn_attempts; CREATE INDEX IF NOT EXISTS idx_paired_turn_attempts_attempt_id ON paired_turn_attempts(attempt_id); CREATE INDEX IF NOT EXISTS idx_paired_turn_attempts_parent_attempt_id ON paired_turn_attempts(parent_attempt_id); CREATE INDEX IF NOT EXISTS idx_paired_turn_attempts_parent_handoff_id ON paired_turn_attempts(parent_handoff_id); CREATE INDEX IF NOT EXISTS idx_paired_turn_attempts_continuation_handoff_id ON paired_turn_attempts(continuation_handoff_id); CREATE INDEX IF NOT EXISTS idx_paired_turn_attempts_turn ON paired_turn_attempts(turn_id, attempt_no); CREATE INDEX IF NOT EXISTS idx_paired_turn_attempts_task ON paired_turn_attempts(task_id, task_updated_at, attempt_no); `); })(); } export function rebuildPairedTurnsWithoutLegacyScratchColumns( database: Database, ): void { if ( !tableHasColumn(database, 'paired_turns', 'next_parent_handoff_id') && !tableHasColumn(database, 'paired_turns', 'active_run_id') && !tableHasColumn(database, 'paired_turns', 'state') && !tableHasColumn(database, 'paired_turns', 'executor_service_id') && !tableHasColumn(database, 'paired_turns', 'executor_agent_type') && !tableHasColumn(database, 'paired_turns', 'attempt_no') && !tableHasColumn(database, 'paired_turns', 'completed_at') && !tableHasColumn(database, 'paired_turns', 'last_error') ) { return; } database.transaction(() => { database.exec(` CREATE TABLE paired_turns_new ( turn_id TEXT PRIMARY KEY, task_id TEXT NOT NULL, task_updated_at TEXT NOT NULL, role TEXT NOT NULL, intent_kind TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, CHECK (role IN ('owner', 'reviewer', 'arbiter')), CHECK ( intent_kind IN ( 'owner-turn', 'reviewer-turn', 'arbiter-turn', 'owner-follow-up', 'finalize-owner-turn' ) ) ); INSERT INTO paired_turns_new ( turn_id, task_id, task_updated_at, role, intent_kind, created_at, updated_at ) SELECT turn_id, task_id, task_updated_at, role, intent_kind, created_at, updated_at FROM paired_turns ORDER BY created_at ASC, turn_id ASC; DROP TABLE paired_turns; ALTER TABLE paired_turns_new RENAME TO paired_turns; CREATE INDEX IF NOT EXISTS idx_paired_turns_task ON paired_turns(task_id, task_updated_at, updated_at); `); })(); } function rebuildPairedTurnReservationsWithForeignKeys( database: Database, ): void { if ( !tableHasColumn(database, 'paired_turn_reservations', 'turn_attempt_no') || (tableHasForeignKey(database, { tableName: 'paired_turn_reservations', referencedTable: 'paired_turn_attempts', fromColumns: ['turn_id', 'turn_attempt_no'], toColumns: ['turn_id', 'attempt_no'], onDelete: 'CASCADE', }) && tableHasColumn(database, 'paired_turn_reservations', 'turn_attempt_id') && tableHasForeignKey(database, { tableName: 'paired_turn_reservations', referencedTable: 'paired_turn_attempts', fromColumns: ['turn_attempt_id'], toColumns: ['attempt_id'], onDelete: 'CASCADE', })) ) { return; } database.transaction(() => { database.exec(` CREATE TABLE paired_turn_reservations_new ( chat_jid TEXT NOT NULL, task_id TEXT NOT NULL, task_status TEXT NOT NULL, round_trip_count INTEGER NOT NULL DEFAULT 0, task_updated_at TEXT NOT NULL, turn_id TEXT NOT NULL, turn_attempt_id TEXT, turn_attempt_no INTEGER, turn_role TEXT NOT NULL, intent_kind TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', scheduled_run_id TEXT, consumed_run_id TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, consumed_at TEXT, PRIMARY KEY (chat_jid, task_id, task_updated_at, intent_kind), FOREIGN KEY (turn_attempt_id) REFERENCES paired_turn_attempts(attempt_id) ON DELETE CASCADE, FOREIGN KEY (turn_id, turn_attempt_no) REFERENCES paired_turn_attempts(turn_id, attempt_no) ON DELETE CASCADE, CHECK (status IN ('pending', 'completed')), CHECK (turn_role IN ('owner', 'reviewer', 'arbiter')), CHECK ( intent_kind IN ( 'owner-turn', 'reviewer-turn', 'arbiter-turn', 'owner-follow-up', 'finalize-owner-turn' ) ) ); INSERT INTO paired_turn_reservations_new ( chat_jid, task_id, task_status, round_trip_count, task_updated_at, turn_id, turn_attempt_id, turn_attempt_no, turn_role, intent_kind, status, scheduled_run_id, consumed_run_id, created_at, updated_at, consumed_at ) SELECT chat_jid, task_id, task_status, round_trip_count, task_updated_at, turn_id, COALESCE( turn_attempt_id, CASE WHEN turn_attempt_no IS NOT NULL THEN ${buildPairedTurnAttemptIdSql('turn_id', 'turn_attempt_no')} ELSE NULL END ), turn_attempt_no, turn_role, intent_kind, status, scheduled_run_id, consumed_run_id, created_at, updated_at, consumed_at FROM paired_turn_reservations; DROP TABLE paired_turn_reservations; ALTER TABLE paired_turn_reservations_new RENAME TO paired_turn_reservations; CREATE INDEX IF NOT EXISTS idx_paired_turn_reservations_task ON paired_turn_reservations(task_id, task_updated_at, status); `); })(); } function rebuildPairedTaskExecutionLeasesWithForeignKeys( database: Database, ): void { if ( !tableHasColumn( database, 'paired_task_execution_leases', 'turn_attempt_no', ) || (tableHasForeignKey(database, { tableName: 'paired_task_execution_leases', referencedTable: 'paired_turn_attempts', fromColumns: ['turn_id', 'turn_attempt_no'], toColumns: ['turn_id', 'attempt_no'], onDelete: 'CASCADE', }) && tableHasColumn( database, 'paired_task_execution_leases', 'turn_attempt_id', ) && tableHasForeignKey(database, { tableName: 'paired_task_execution_leases', referencedTable: 'paired_turn_attempts', fromColumns: ['turn_attempt_id'], toColumns: ['attempt_id'], onDelete: 'CASCADE', })) ) { return; } database.transaction(() => { database.exec(` CREATE TABLE paired_task_execution_leases_new ( task_id TEXT PRIMARY KEY, chat_jid TEXT NOT NULL, role TEXT NOT NULL, turn_id TEXT NOT NULL, turn_attempt_id TEXT, turn_attempt_no INTEGER, intent_kind TEXT NOT NULL, claimed_run_id TEXT NOT NULL, claimed_service_id TEXT NOT NULL, task_status TEXT NOT NULL, task_updated_at TEXT NOT NULL, claimed_at TEXT NOT NULL, updated_at TEXT NOT NULL, expires_at TEXT NOT NULL, FOREIGN KEY (turn_attempt_id) REFERENCES paired_turn_attempts(attempt_id) ON DELETE CASCADE, FOREIGN KEY (turn_id, turn_attempt_no) REFERENCES paired_turn_attempts(turn_id, attempt_no) ON DELETE CASCADE, CHECK (role IN ('owner', 'reviewer', 'arbiter')), CHECK ( intent_kind IN ( 'owner-turn', 'reviewer-turn', 'arbiter-turn', 'owner-follow-up', 'finalize-owner-turn' ) ) ); INSERT INTO paired_task_execution_leases_new ( task_id, chat_jid, role, turn_id, turn_attempt_id, turn_attempt_no, intent_kind, claimed_run_id, claimed_service_id, task_status, task_updated_at, claimed_at, updated_at, expires_at ) SELECT task_id, chat_jid, role, turn_id, COALESCE( turn_attempt_id, CASE WHEN turn_attempt_no IS NOT NULL THEN ${buildPairedTurnAttemptIdSql('turn_id', 'turn_attempt_no')} ELSE NULL END ), turn_attempt_no, intent_kind, claimed_run_id, claimed_service_id, task_status, task_updated_at, claimed_at, updated_at, expires_at FROM paired_task_execution_leases; DROP TABLE paired_task_execution_leases; ALTER TABLE paired_task_execution_leases_new RENAME TO paired_task_execution_leases; CREATE INDEX IF NOT EXISTS idx_paired_task_execution_leases_expires_at ON paired_task_execution_leases(expires_at); `); })(); } function rebuildServiceHandoffsWithForeignKeys(database: Database): void { if ( !tableHasColumn(database, 'service_handoffs', 'turn_attempt_no') || (tableHasForeignKey(database, { tableName: 'service_handoffs', referencedTable: 'paired_turn_attempts', fromColumns: ['turn_id', 'turn_attempt_no'], toColumns: ['turn_id', 'attempt_no'], onDelete: 'CASCADE', }) && tableHasColumn(database, 'service_handoffs', 'turn_attempt_id') && tableHasForeignKey(database, { tableName: 'service_handoffs', referencedTable: 'paired_turn_attempts', fromColumns: ['turn_attempt_id'], toColumns: ['attempt_id'], onDelete: 'CASCADE', })) ) { return; } database.transaction(() => { database.exec(` CREATE TABLE service_handoffs_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, chat_jid TEXT NOT NULL, group_folder TEXT NOT NULL, source_service_id TEXT NOT NULL, target_service_id TEXT NOT NULL, paired_task_id TEXT, paired_task_updated_at TEXT, turn_id TEXT, turn_attempt_id TEXT, turn_attempt_no INTEGER, turn_intent_kind TEXT, turn_role TEXT, source_role TEXT, source_agent_type TEXT, target_role TEXT, target_agent_type TEXT NOT NULL, prompt TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', start_seq INTEGER, end_seq INTEGER, reason TEXT, intended_role TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), claimed_at TEXT, completed_at TEXT, last_error TEXT, FOREIGN KEY (turn_attempt_id) REFERENCES paired_turn_attempts(attempt_id) ON DELETE CASCADE, FOREIGN KEY (turn_id, turn_attempt_no) REFERENCES paired_turn_attempts(turn_id, attempt_no) ON DELETE CASCADE, CHECK (status IN ('pending', 'claimed', 'completed', 'failed')), CHECK (intended_role IN ('owner', 'reviewer', 'arbiter') OR intended_role IS NULL), CHECK ( turn_intent_kind IN ( 'owner-turn', 'reviewer-turn', 'arbiter-turn', 'owner-follow-up', 'finalize-owner-turn' ) OR turn_intent_kind IS NULL ), CHECK (turn_role IN ('owner', 'reviewer', 'arbiter') OR turn_role IS NULL), CHECK (source_role IN ('owner', 'reviewer', 'arbiter') OR source_role IS NULL), CHECK (target_role IN ('owner', 'reviewer', 'arbiter') OR target_role IS NULL) ); INSERT INTO service_handoffs_new ( id, chat_jid, group_folder, source_service_id, target_service_id, paired_task_id, paired_task_updated_at, turn_id, turn_attempt_id, turn_attempt_no, turn_intent_kind, turn_role, source_role, source_agent_type, target_role, target_agent_type, prompt, status, start_seq, end_seq, reason, intended_role, created_at, claimed_at, completed_at, last_error ) SELECT id, chat_jid, group_folder, source_service_id, target_service_id, paired_task_id, paired_task_updated_at, turn_id, COALESCE( turn_attempt_id, CASE WHEN turn_attempt_no IS NOT NULL THEN ${buildPairedTurnAttemptIdSql('turn_id', 'turn_attempt_no')} ELSE NULL END ), turn_attempt_no, turn_intent_kind, turn_role, source_role, source_agent_type, target_role, target_agent_type, prompt, status, start_seq, end_seq, reason, intended_role, created_at, claimed_at, completed_at, last_error FROM service_handoffs; DROP TABLE service_handoffs; ALTER TABLE service_handoffs_new RENAME TO service_handoffs; CREATE INDEX IF NOT EXISTS idx_service_handoffs_target ON service_handoffs(status, target_role, target_agent_type, created_at); `); })(); } export function rebuildPairedTurnAttemptForeignKeyTables( database: Database, ): void { rebuildPairedTurnAttemptsWithForeignKeys(database); rebuildPairedTurnReservationsWithForeignKeys(database); rebuildPairedTaskExecutionLeasesWithForeignKeys(database); rebuildServiceHandoffsWithForeignKeys(database); } export function dropPairedTurnAttemptProvenanceConstraints( database: Database, ): void { database.exec(` DROP TRIGGER IF EXISTS paired_turn_attempts_validate_insert; DROP TRIGGER IF EXISTS paired_turn_attempts_validate_update; DROP TRIGGER IF EXISTS paired_turn_reservations_validate_attempt_insert; DROP TRIGGER IF EXISTS paired_turn_reservations_validate_attempt_update; DROP TRIGGER IF EXISTS paired_task_execution_leases_validate_attempt_insert; DROP TRIGGER IF EXISTS paired_task_execution_leases_validate_attempt_update; DROP TRIGGER IF EXISTS service_handoffs_validate_attempt_insert; DROP TRIGGER IF EXISTS service_handoffs_validate_attempt_update; `); } export function applyPairedTurnAttemptProvenanceConstraints( database: Database, ): void { database.exec(` CREATE TRIGGER IF NOT EXISTS paired_turn_attempts_validate_insert BEFORE INSERT ON paired_turn_attempts BEGIN SELECT RAISE(ABORT, 'paired_turn_attempts attempt_id must match turn_id/attempt_no') WHERE NEW.attempt_id IS NULL OR NEW.attempt_id != ${buildPairedTurnAttemptIdSql('NEW.turn_id', 'NEW.attempt_no')}; SELECT RAISE(ABORT, 'paired_turn_attempts attempt 1 cannot declare parent_attempt_id') WHERE NEW.attempt_no <= 1 AND NEW.parent_attempt_id IS NOT NULL; SELECT RAISE(ABORT, 'paired_turn_attempts must preserve contiguous parent lineage') WHERE NEW.attempt_no > 1 AND NOT EXISTS ( SELECT 1 FROM paired_turn_attempts previous_attempt WHERE previous_attempt.turn_id = NEW.turn_id AND previous_attempt.attempt_no = NEW.attempt_no - 1 ); SELECT RAISE(ABORT, 'paired_turn_attempts must keep parent_attempt_id lineage') WHERE NEW.attempt_no > 1 AND ( NEW.parent_attempt_id IS NULL OR NEW.parent_attempt_id != ${buildPairedTurnAttemptParentIdSql('NEW.turn_id', 'NEW.attempt_no')} ); SELECT RAISE(ABORT, 'paired_turn_attempts parent_attempt_id must point to the previous attempt of the same turn') WHERE NEW.parent_attempt_id IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM paired_turn_attempts previous_attempt WHERE COALESCE( previous_attempt.attempt_id, ${buildPairedTurnAttemptIdSql( 'previous_attempt.turn_id', 'previous_attempt.attempt_no', )} ) = NEW.parent_attempt_id AND previous_attempt.turn_id = NEW.turn_id AND previous_attempt.attempt_no = NEW.attempt_no - 1 ); SELECT RAISE(ABORT, 'paired_turn_attempts attempt 1 cannot declare parent_handoff_id') WHERE NEW.attempt_no <= 1 AND NEW.parent_handoff_id IS NOT NULL; SELECT RAISE(ABORT, 'paired_turn_attempts parent_handoff_id must reference the previous attempt handoff of the same turn') WHERE NEW.parent_handoff_id IS NOT NULL AND NOT ${buildPairedTurnAttemptParentHandoffMatchSql({ parentHandoffIdExpr: 'NEW.parent_handoff_id', turnIdExpr: 'NEW.turn_id', parentAttemptIdExpr: 'NEW.parent_attempt_id', attemptNoExpr: 'NEW.attempt_no', })}; SELECT RAISE(ABORT, 'paired_turn_attempts continuation_handoff_id must reference a handoff of the same attempt') WHERE NEW.continuation_handoff_id IS NOT NULL AND NOT ${buildPairedTurnAttemptContinuationHandoffMatchSql({ continuationHandoffIdExpr: 'NEW.continuation_handoff_id', turnIdExpr: 'NEW.turn_id', attemptIdExpr: 'NEW.attempt_id', attemptNoExpr: 'NEW.attempt_no', })}; SELECT RAISE(ABORT, 'paired_turn_attempts running attempts must declare active_run_id') WHERE NEW.state = 'running' AND ( NEW.active_run_id IS NULL OR TRIM(NEW.active_run_id) = '' ); SELECT RAISE(ABORT, 'paired_turn_attempts only running attempts may declare active_run_id') WHERE NEW.state != 'running' AND NEW.active_run_id IS NOT NULL; SELECT RAISE(ABORT, 'paired_turn_attempts must reference a matching paired_turns row') WHERE NOT EXISTS ( SELECT 1 FROM paired_turns WHERE paired_turns.turn_id = NEW.turn_id AND paired_turns.task_id = NEW.task_id AND paired_turns.task_updated_at = NEW.task_updated_at AND paired_turns.role = NEW.role AND paired_turns.intent_kind = NEW.intent_kind ); END; CREATE TRIGGER IF NOT EXISTS paired_turn_attempts_validate_update BEFORE UPDATE OF attempt_id, parent_attempt_id, parent_handoff_id, continuation_handoff_id, turn_id, attempt_no, task_id, task_updated_at, role, intent_kind, state, executor_service_id, executor_agent_type, active_run_id ON paired_turn_attempts BEGIN SELECT RAISE(ABORT, 'paired_turn_attempts attempt_id must match turn_id/attempt_no') WHERE NEW.attempt_id IS NULL OR NEW.attempt_id != ${buildPairedTurnAttemptIdSql('NEW.turn_id', 'NEW.attempt_no')}; SELECT RAISE(ABORT, 'paired_turn_attempts attempt 1 cannot declare parent_attempt_id') WHERE NEW.attempt_no <= 1 AND NEW.parent_attempt_id IS NOT NULL; SELECT RAISE(ABORT, 'paired_turn_attempts must preserve contiguous parent lineage') WHERE NEW.attempt_no > 1 AND NOT EXISTS ( SELECT 1 FROM paired_turn_attempts previous_attempt WHERE previous_attempt.turn_id = NEW.turn_id AND previous_attempt.attempt_no = NEW.attempt_no - 1 ); SELECT RAISE(ABORT, 'paired_turn_attempts must keep parent_attempt_id lineage') WHERE NEW.attempt_no > 1 AND ( NEW.parent_attempt_id IS NULL OR NEW.parent_attempt_id != ${buildPairedTurnAttemptParentIdSql('NEW.turn_id', 'NEW.attempt_no')} ); SELECT RAISE(ABORT, 'paired_turn_attempts parent_attempt_id must point to the previous attempt of the same turn') WHERE NEW.parent_attempt_id IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM paired_turn_attempts previous_attempt WHERE COALESCE( previous_attempt.attempt_id, ${buildPairedTurnAttemptIdSql( 'previous_attempt.turn_id', 'previous_attempt.attempt_no', )} ) = NEW.parent_attempt_id AND previous_attempt.turn_id = NEW.turn_id AND previous_attempt.attempt_no = NEW.attempt_no - 1 ); SELECT RAISE(ABORT, 'paired_turn_attempts attempt 1 cannot declare parent_handoff_id') WHERE NEW.attempt_no <= 1 AND NEW.parent_handoff_id IS NOT NULL; SELECT RAISE(ABORT, 'paired_turn_attempts parent_handoff_id must reference the previous attempt handoff of the same turn') WHERE NEW.parent_handoff_id IS NOT NULL AND NOT ${buildPairedTurnAttemptParentHandoffMatchSql({ parentHandoffIdExpr: 'NEW.parent_handoff_id', turnIdExpr: 'NEW.turn_id', parentAttemptIdExpr: 'NEW.parent_attempt_id', attemptNoExpr: 'NEW.attempt_no', })}; SELECT RAISE(ABORT, 'paired_turn_attempts continuation_handoff_id must reference a handoff of the same attempt') WHERE NEW.continuation_handoff_id IS NOT NULL AND NOT ${buildPairedTurnAttemptContinuationHandoffMatchSql({ continuationHandoffIdExpr: 'NEW.continuation_handoff_id', turnIdExpr: 'NEW.turn_id', attemptIdExpr: 'NEW.attempt_id', attemptNoExpr: 'NEW.attempt_no', })}; SELECT RAISE(ABORT, 'paired_turn_attempts running attempts must declare active_run_id') WHERE NEW.state = 'running' AND ( NEW.active_run_id IS NULL OR TRIM(NEW.active_run_id) = '' ); SELECT RAISE(ABORT, 'paired_turn_attempts only running attempts may declare active_run_id') WHERE NEW.state != 'running' AND NEW.active_run_id IS NOT NULL; SELECT RAISE(ABORT, 'paired_turn_attempts must reference a matching paired_turns row') WHERE NOT EXISTS ( SELECT 1 FROM paired_turns WHERE paired_turns.turn_id = NEW.turn_id AND paired_turns.task_id = NEW.task_id AND paired_turns.task_updated_at = NEW.task_updated_at AND paired_turns.role = NEW.role AND paired_turns.intent_kind = NEW.intent_kind ); END; `); if (tableHasColumn(database, 'paired_turn_reservations', 'turn_attempt_no')) { database.exec(` CREATE INDEX IF NOT EXISTS idx_paired_turn_reservations_turn_attempt ON paired_turn_reservations(turn_id, turn_attempt_no); CREATE INDEX IF NOT EXISTS idx_paired_turn_reservations_turn_attempt_id ON paired_turn_reservations(turn_attempt_id); CREATE TRIGGER IF NOT EXISTS paired_turn_reservations_validate_attempt_insert BEFORE INSERT ON paired_turn_reservations BEGIN SELECT RAISE(ABORT, 'paired_turn_reservations.turn_attempt_no requires turn_id') WHERE NEW.turn_attempt_no IS NOT NULL AND NEW.turn_id IS NULL; SELECT RAISE(ABORT, 'paired_turn_reservations.turn_attempt provenance must keep attempt_id and attempt_no in sync') WHERE (NEW.turn_attempt_id IS NULL) != (NEW.turn_attempt_no IS NULL); SELECT RAISE(ABORT, 'paired_turn_reservations turn_attempt_no must reference a matching paired_turn_attempts row') WHERE NEW.turn_attempt_id IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM paired_turn_attempts WHERE paired_turn_attempts.attempt_id = NEW.turn_attempt_id AND paired_turn_attempts.turn_id = NEW.turn_id AND paired_turn_attempts.attempt_no = NEW.turn_attempt_no AND paired_turn_attempts.task_id = NEW.task_id AND paired_turn_attempts.task_updated_at = NEW.task_updated_at AND paired_turn_attempts.role = NEW.turn_role AND paired_turn_attempts.intent_kind = NEW.intent_kind ); END; CREATE TRIGGER IF NOT EXISTS paired_turn_reservations_validate_attempt_update BEFORE UPDATE OF turn_id, turn_attempt_id, turn_attempt_no, task_id, task_updated_at, turn_role, intent_kind ON paired_turn_reservations BEGIN SELECT RAISE(ABORT, 'paired_turn_reservations.turn_attempt_no requires turn_id') WHERE NEW.turn_attempt_no IS NOT NULL AND NEW.turn_id IS NULL; SELECT RAISE(ABORT, 'paired_turn_reservations.turn_attempt provenance must keep attempt_id and attempt_no in sync') WHERE (NEW.turn_attempt_id IS NULL) != (NEW.turn_attempt_no IS NULL); SELECT RAISE(ABORT, 'paired_turn_reservations turn_attempt_no must reference a matching paired_turn_attempts row') WHERE NEW.turn_attempt_id IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM paired_turn_attempts WHERE paired_turn_attempts.attempt_id = NEW.turn_attempt_id AND paired_turn_attempts.turn_id = NEW.turn_id AND paired_turn_attempts.attempt_no = NEW.turn_attempt_no AND paired_turn_attempts.task_id = NEW.task_id AND paired_turn_attempts.task_updated_at = NEW.task_updated_at AND paired_turn_attempts.role = NEW.turn_role AND paired_turn_attempts.intent_kind = NEW.intent_kind ); END; `); } if ( tableHasColumn(database, 'paired_task_execution_leases', 'turn_attempt_no') ) { database.exec(` CREATE INDEX IF NOT EXISTS idx_paired_task_execution_leases_turn_attempt ON paired_task_execution_leases(turn_id, turn_attempt_no); CREATE INDEX IF NOT EXISTS idx_paired_task_execution_leases_turn_attempt_id ON paired_task_execution_leases(turn_attempt_id); CREATE TRIGGER IF NOT EXISTS paired_task_execution_leases_validate_attempt_insert BEFORE INSERT ON paired_task_execution_leases BEGIN SELECT RAISE(ABORT, 'paired_task_execution_leases.turn_attempt_no requires turn_id') WHERE NEW.turn_attempt_no IS NOT NULL AND NEW.turn_id IS NULL; SELECT RAISE(ABORT, 'paired_task_execution_leases turn_attempt provenance must keep attempt_id and attempt_no in sync') WHERE (NEW.turn_attempt_id IS NULL) != (NEW.turn_attempt_no IS NULL); SELECT RAISE(ABORT, 'paired_task_execution_leases turn_attempt_no must reference a matching paired_turn_attempts row') WHERE NEW.turn_attempt_id IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM paired_turn_attempts WHERE paired_turn_attempts.attempt_id = NEW.turn_attempt_id AND paired_turn_attempts.turn_id = NEW.turn_id AND paired_turn_attempts.attempt_no = NEW.turn_attempt_no AND paired_turn_attempts.task_id = NEW.task_id AND paired_turn_attempts.task_updated_at = NEW.task_updated_at AND paired_turn_attempts.role = NEW.role AND paired_turn_attempts.intent_kind = NEW.intent_kind ); END; CREATE TRIGGER IF NOT EXISTS paired_task_execution_leases_validate_attempt_update BEFORE UPDATE OF turn_id, turn_attempt_id, turn_attempt_no, task_id, task_updated_at, role, intent_kind ON paired_task_execution_leases BEGIN SELECT RAISE(ABORT, 'paired_task_execution_leases.turn_attempt_no requires turn_id') WHERE NEW.turn_attempt_no IS NOT NULL AND NEW.turn_id IS NULL; SELECT RAISE(ABORT, 'paired_task_execution_leases turn_attempt provenance must keep attempt_id and attempt_no in sync') WHERE (NEW.turn_attempt_id IS NULL) != (NEW.turn_attempt_no IS NULL); SELECT RAISE(ABORT, 'paired_task_execution_leases turn_attempt_no must reference a matching paired_turn_attempts row') WHERE NEW.turn_attempt_id IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM paired_turn_attempts WHERE paired_turn_attempts.attempt_id = NEW.turn_attempt_id AND paired_turn_attempts.turn_id = NEW.turn_id AND paired_turn_attempts.attempt_no = NEW.turn_attempt_no AND paired_turn_attempts.task_id = NEW.task_id AND paired_turn_attempts.task_updated_at = NEW.task_updated_at AND paired_turn_attempts.role = NEW.role AND paired_turn_attempts.intent_kind = NEW.intent_kind ); END; `); } if ( tableHasColumn(database, 'service_handoffs', 'turn_attempt_no') && tableHasColumn(database, 'service_handoffs', 'paired_task_id') && tableHasColumn(database, 'service_handoffs', 'paired_task_updated_at') && tableHasColumn(database, 'service_handoffs', 'turn_role') && tableHasColumn(database, 'service_handoffs', 'turn_intent_kind') ) { database.exec(` CREATE INDEX IF NOT EXISTS idx_service_handoffs_turn_attempt ON service_handoffs(turn_id, turn_attempt_no); CREATE INDEX IF NOT EXISTS idx_service_handoffs_turn_attempt_id ON service_handoffs(turn_attempt_id); CREATE TRIGGER IF NOT EXISTS service_handoffs_validate_attempt_insert BEFORE INSERT ON service_handoffs BEGIN SELECT RAISE(ABORT, 'service_handoffs.turn_attempt_no requires paired turn identity') WHERE NEW.turn_attempt_no IS NOT NULL AND ( NEW.turn_id IS NULL OR NEW.paired_task_id IS NULL OR NEW.paired_task_updated_at IS NULL OR NEW.turn_role IS NULL OR NEW.turn_intent_kind IS NULL ); SELECT RAISE(ABORT, 'service_handoffs turn_attempt provenance must keep attempt_id and attempt_no in sync') WHERE (NEW.turn_attempt_id IS NULL) != (NEW.turn_attempt_no IS NULL); SELECT RAISE(ABORT, 'service_handoffs turn_attempt_no must reference a matching paired_turn_attempts row') WHERE NEW.turn_attempt_id IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM paired_turn_attempts WHERE paired_turn_attempts.attempt_id = NEW.turn_attempt_id AND paired_turn_attempts.turn_id = NEW.turn_id AND paired_turn_attempts.attempt_no = NEW.turn_attempt_no AND paired_turn_attempts.task_id = NEW.paired_task_id AND paired_turn_attempts.task_updated_at = NEW.paired_task_updated_at AND paired_turn_attempts.role = NEW.turn_role AND paired_turn_attempts.intent_kind = NEW.turn_intent_kind ); END; CREATE TRIGGER IF NOT EXISTS service_handoffs_validate_attempt_update BEFORE UPDATE OF turn_id, turn_attempt_id, turn_attempt_no, paired_task_id, paired_task_updated_at, turn_role, turn_intent_kind ON service_handoffs BEGIN SELECT RAISE(ABORT, 'service_handoffs.turn_attempt_no requires paired turn identity') WHERE NEW.turn_attempt_no IS NOT NULL AND ( NEW.turn_id IS NULL OR NEW.paired_task_id IS NULL OR NEW.paired_task_updated_at IS NULL OR NEW.turn_role IS NULL OR NEW.turn_intent_kind IS NULL ); SELECT RAISE(ABORT, 'service_handoffs turn_attempt provenance must keep attempt_id and attempt_no in sync') WHERE (NEW.turn_attempt_id IS NULL) != (NEW.turn_attempt_no IS NULL); SELECT RAISE(ABORT, 'service_handoffs turn_attempt_no must reference a matching paired_turn_attempts row') WHERE NEW.turn_attempt_id IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM paired_turn_attempts WHERE paired_turn_attempts.attempt_id = NEW.turn_attempt_id AND paired_turn_attempts.turn_id = NEW.turn_id AND paired_turn_attempts.attempt_no = NEW.turn_attempt_no AND paired_turn_attempts.task_id = NEW.paired_task_id AND paired_turn_attempts.task_updated_at = NEW.paired_task_updated_at AND paired_turn_attempts.role = NEW.turn_role AND paired_turn_attempts.intent_kind = NEW.turn_intent_kind ); END; `); } }