diff --git a/src/codex/reset-credit-operation-ledger.ts b/src/codex/reset-credit-operation-ledger.ts new file mode 100644 index 0000000000..806437d241 --- /dev/null +++ b/src/codex/reset-credit-operation-ledger.ts @@ -0,0 +1,1391 @@ +import { createHash, randomUUID } from "node:crypto"; +import { chmodSync } from "node:fs"; +import { Database } from "bun:sqlite"; +import { NestedConfigMutationError, prepareConfigMutationDatabasePathForWrite } from "../config"; +import { initializeConfigGeneration } from "./generation"; +import { + compareCodexResetCreditRecoveryGenerationOrder, + isCodexResetCreditOperationId, + snapshotCodexResetCreditRecoveryGeneration, + type CodexResetCreditConsumeCode, + type CodexResetCreditRecoveryGeneration, + type CodexReservedOperationId, +} from "./reset-credit-recovery"; +import { isValidCodexAccountId, MAIN_CODEX_ACCOUNT_ID } from "./account-id"; + +export const MAX_RESET_CREDIT_OPERATION_ACCOUNTS = 128; +export const MAX_MANUAL_RESET_CREDIT_OPERATION_IDS = 4_096; +const MANUAL_RESET_CREDIT_HISTORY_HIGH_WATER_MARK = Math.ceil( + MAX_MANUAL_RESET_CREDIT_OPERATION_IDS * 0.9, +); +let reportedManualHistoryLevel = 0; +const ACCOUNT_KEY_PATTERN = /^[0-9a-f]{64}$/; +const TERMINAL_STATE_BY_CODE: Readonly> = Object.freeze({ + reset: "confirmed", + already_redeemed: "confirmed", + nothing_to_reset: "stopped", + no_credit: "stopped", +}); +const STATES: ReadonlySet = new Set(["pending", "ambiguous", "confirmed", "stopped"]); + +type ResetCreditOperationState = "pending" | "ambiguous" | "confirmed" | "stopped"; +type ResetCreditOperationKind = "recovery" | "manual"; + +type ResetCreditOperationRecord = Readonly<{ + accountKey: string; + operationKind: ResetCreditOperationKind; + credentialGeneration?: number; + exhaustionGeneration?: number; + operationId: string; + joinedOperationId?: string; + state: ResetCreditOperationState; + code?: CodexResetCreditConsumeCode; + createdAt: number; + updatedAt: number; +}>; + +type ResetCreditOperationRow = { + account_key: unknown; + operation_kind: unknown; + credential_generation: unknown; + exhaustion_generation: unknown; + operation_id: unknown; + joined_operation_id: unknown; + state: unknown; + code: unknown; + created_at: unknown; + updated_at: unknown; +}; + +type ManualResetCreditOperationIdRecord = Readonly<{ + operationId: string; + accountKey: string; + canonicalOperationId: string; + terminalCode?: CodexResetCreditConsumeCode; + createdAt: number; + updatedAt: number; +}>; + +type ManualResetCreditOperationIdRow = { + operation_id: unknown; + account_key: unknown; + canonical_operation_id: unknown; + terminal_code: unknown; + created_at: unknown; + updated_at: unknown; +}; + +export type OpenResetCreditOperationResult = + | Readonly<{ kind: "execute"; operationId: CodexReservedOperationId; resumed: boolean }> + | Readonly<{ kind: "terminal"; operationId: CodexReservedOperationId; code: CodexResetCreditConsumeCode }> + | Readonly<{ kind: "stale-generation" | "unresolved-prior-generation" | "capacity" | "unavailable" }>; + +export type UpdateResetCreditOperationResult = + | Readonly<{ kind: "updated" }> + | Readonly<{ kind: "mismatch" | "unavailable" }>; + +export type ManualResetCreditOperationIdentity = Readonly<{ + accountId: string; + chatgptAccountId: string; + operationId: string; +}>; + +export type OpenManualResetCreditOperationResult = + | Readonly<{ kind: "execute"; operationId: CodexReservedOperationId; resumed: boolean }> + | Readonly<{ kind: "terminal"; operationId: CodexReservedOperationId; code: CodexResetCreditConsumeCode }> + | Readonly<{ kind: "capacity" | "identity-mismatch" | "unavailable" }>; + +const TABLE_NAME = "reset_credit_operations"; +const CREATE_TABLE = `CREATE TABLE main.reset_credit_operations ( + account_key TEXT PRIMARY KEY, + operation_kind TEXT NOT NULL CHECK (operation_kind IN ('recovery', 'manual')), + credential_generation INTEGER, + exhaustion_generation INTEGER, + operation_id TEXT NOT NULL, + joined_operation_id TEXT, + state TEXT NOT NULL, + code TEXT, + created_at INTEGER NOT NULL CHECK (created_at >= 0), + updated_at INTEGER NOT NULL CHECK (updated_at >= created_at), + CHECK ( + (operation_kind = 'recovery' + AND credential_generation IS NOT NULL AND credential_generation >= 0 + AND exhaustion_generation IS NOT NULL AND exhaustion_generation >= 0 + AND joined_operation_id IS NULL) + OR + (operation_kind = 'manual' + AND credential_generation IS NULL AND exhaustion_generation IS NULL) + ), + CHECK (joined_operation_id IS NULL OR joined_operation_id <> operation_id) + ) STRICT, WITHOUT ROWID`; +const EXPECTED_SCHEMA_SQL = CREATE_TABLE.replace("main.", ""); +const MANUAL_ID_TABLE_NAME = "reset_credit_manual_operation_ids"; +const CREATE_MANUAL_ID_TABLE = `CREATE TABLE main.reset_credit_manual_operation_ids ( + operation_id TEXT PRIMARY KEY, + account_key TEXT NOT NULL, + canonical_operation_id TEXT NOT NULL, + terminal_code TEXT CHECK ( + terminal_code IS NULL OR terminal_code IN ( + 'reset', 'already_redeemed', 'nothing_to_reset', 'no_credit' + ) + ), + created_at INTEGER NOT NULL CHECK (created_at >= 0), + updated_at INTEGER NOT NULL CHECK (updated_at >= created_at) + ) STRICT, WITHOUT ROWID`; +const EXPECTED_MANUAL_ID_SCHEMA_SQL = CREATE_MANUAL_ID_TABLE.replace("main.", ""); +const PRIOR_TABLE_NAME = "reset_credit_operations_legacy_v2"; +const PRIOR_CREATE_TABLE = `CREATE TABLE reset_credit_operations ( + account_key TEXT PRIMARY KEY, + operation_kind TEXT NOT NULL CHECK (operation_kind IN ('recovery', 'manual')), + credential_generation INTEGER, + exhaustion_generation INTEGER, + operation_id TEXT NOT NULL, + state TEXT NOT NULL, + code TEXT, + created_at INTEGER NOT NULL CHECK (created_at >= 0), + updated_at INTEGER NOT NULL CHECK (updated_at >= created_at), + CHECK ( + (operation_kind = 'recovery' + AND credential_generation IS NOT NULL AND credential_generation >= 0 + AND exhaustion_generation IS NOT NULL AND exhaustion_generation >= 0) + OR + (operation_kind = 'manual' + AND credential_generation IS NULL AND exhaustion_generation IS NULL) + ) + ) STRICT, WITHOUT ROWID`; +const LEGACY_TABLE_NAME = "reset_credit_operations_legacy_v1"; +const LEGACY_CREATE_TABLE = `CREATE TABLE reset_credit_operations ( + account_key TEXT PRIMARY KEY, + credential_generation INTEGER NOT NULL CHECK (credential_generation >= 0), + exhaustion_generation INTEGER NOT NULL CHECK (exhaustion_generation >= 0), + operation_id TEXT NOT NULL, + state TEXT NOT NULL, + code TEXT, + created_at INTEGER NOT NULL CHECK (created_at >= 0), + updated_at INTEGER NOT NULL CHECK (updated_at >= created_at) + ) STRICT, WITHOUT ROWID`; +const SELECT_ALL = ` + SELECT account_key, operation_kind, credential_generation, + exhaustion_generation, operation_id, joined_operation_id, + state, code, created_at, updated_at + FROM main.reset_credit_operations + ORDER BY account_key + LIMIT ${MAX_RESET_CREDIT_OPERATION_ACCOUNTS + 1}`; +const SELECT_BY_KEY = ` + SELECT account_key, operation_kind, credential_generation, + exhaustion_generation, operation_id, joined_operation_id, + state, code, created_at, updated_at + FROM main.reset_credit_operations + WHERE account_key = ? + LIMIT 2`; +const SELECT_KEY_BY_OPERATION_ID = ` + SELECT account_key FROM ( + SELECT account_key + FROM main.reset_credit_operations + WHERE operation_id = ? OR joined_operation_id = ? + UNION + SELECT account_key + FROM main.reset_credit_manual_operation_ids + WHERE operation_id = ? + ) + LIMIT 2`; +const SELECT_ALL_MANUAL_IDS = ` + SELECT operation_id, account_key, canonical_operation_id, + terminal_code, created_at, updated_at + FROM main.reset_credit_manual_operation_ids + ORDER BY operation_id + LIMIT ${MAX_MANUAL_RESET_CREDIT_OPERATION_IDS + 1}`; +const SELECT_BOUNDED_MANUAL_ID_COUNT = ` + SELECT COUNT(*) AS count + FROM ( + SELECT 1 + FROM main.reset_credit_manual_operation_ids + LIMIT ${MAX_MANUAL_RESET_CREDIT_OPERATION_IDS + 1} + )`; +const SELECT_DUPLICATE_RECOVERY_MANUAL_ID = ` + SELECT operations.operation_id + FROM main.reset_credit_operations AS operations + JOIN main.reset_credit_manual_operation_ids AS manual_ids + ON manual_ids.operation_id = operations.operation_id + WHERE operations.operation_kind = 'recovery' + LIMIT 1`; +const SELECT_MANUAL_ID = ` + SELECT operation_id, account_key, canonical_operation_id, + terminal_code, created_at, updated_at + FROM main.reset_credit_manual_operation_ids + WHERE operation_id = ? + LIMIT 2`; +const SELECT_MANUAL_IDS_BY_CANONICAL = ` + SELECT operation_id, account_key, canonical_operation_id, + terminal_code, created_at, updated_at + FROM main.reset_credit_manual_operation_ids + WHERE account_key = ? AND canonical_operation_id = ? + ORDER BY operation_id + LIMIT ${MAX_MANUAL_RESET_CREDIT_OPERATION_IDS + 1}`; +const INSERT_MANUAL_ID = ` + INSERT INTO main.reset_credit_manual_operation_ids ( + operation_id, account_key, canonical_operation_id, + terminal_code, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?)`; +const SETTLE_MANUAL_IDS = ` + UPDATE main.reset_credit_manual_operation_ids + SET terminal_code = ?, updated_at = ? + WHERE account_key = ? AND canonical_operation_id = ? + AND (terminal_code IS NULL OR terminal_code = ?)`; +const INSERT_RECORD = ` + INSERT INTO main.reset_credit_operations ( + account_key, operation_kind, credential_generation, exhaustion_generation, + operation_id, joined_operation_id, state, code, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; +const REPLACE_RECORD = ` + UPDATE main.reset_credit_operations + SET operation_kind = ?, credential_generation = ?, exhaustion_generation = ?, + operation_id = ?, joined_operation_id = ?, state = ?, code = ?, + created_at = ?, updated_at = ? + WHERE account_key = ?`; +const UPDATE_RECORD = ` + UPDATE main.reset_credit_operations + SET state = ?, code = ?, updated_at = ? + WHERE account_key = ? AND operation_kind = ? AND operation_id = ? + AND credential_generation IS ? AND exhaustion_generation IS ?`; +const JOIN_MANUAL_OPERATION = ` + UPDATE main.reset_credit_operations + SET joined_operation_id = ?, updated_at = ? + WHERE account_key = ? AND operation_kind = 'manual' AND operation_id = ? + AND joined_operation_id IS NULL AND state IN ('pending', 'ambiguous')`; +const TOUCH_MANUAL_OPERATION = ` + UPDATE main.reset_credit_operations + SET updated_at = ? + WHERE account_key = ? AND operation_kind = 'manual' AND operation_id = ? + AND joined_operation_id IS NOT NULL AND state IN ('pending', 'ambiguous')`; + +type SchemaObjectRow = { + type: unknown; + name: unknown; + tbl_name: unknown; + sql: unknown; +}; + +type TableListRow = { + schema: unknown; + name: unknown; + type: unknown; + ncol: unknown; + wr: unknown; + strict: unknown; +}; + +type TableColumnRow = { + cid: unknown; + name: unknown; + type: unknown; + notnull: unknown; + dflt_value: unknown; + pk: unknown; + hidden: unknown; +}; + +const EXPECTED_COLUMNS = Object.freeze([ + Object.freeze({ name: "account_key", type: "TEXT", notnull: 1, pk: 1 }), + Object.freeze({ name: "operation_kind", type: "TEXT", notnull: 1, pk: 0 }), + Object.freeze({ name: "credential_generation", type: "INTEGER", notnull: 0, pk: 0 }), + Object.freeze({ name: "exhaustion_generation", type: "INTEGER", notnull: 0, pk: 0 }), + Object.freeze({ name: "operation_id", type: "TEXT", notnull: 1, pk: 0 }), + Object.freeze({ name: "joined_operation_id", type: "TEXT", notnull: 0, pk: 0 }), + Object.freeze({ name: "state", type: "TEXT", notnull: 1, pk: 0 }), + Object.freeze({ name: "code", type: "TEXT", notnull: 0, pk: 0 }), + Object.freeze({ name: "created_at", type: "INTEGER", notnull: 1, pk: 0 }), + Object.freeze({ name: "updated_at", type: "INTEGER", notnull: 1, pk: 0 }), +]); + +const MANUAL_ID_COLUMNS = Object.freeze([ + Object.freeze({ name: "operation_id", type: "TEXT", notnull: 1, pk: 1 }), + Object.freeze({ name: "account_key", type: "TEXT", notnull: 1, pk: 0 }), + Object.freeze({ name: "canonical_operation_id", type: "TEXT", notnull: 1, pk: 0 }), + Object.freeze({ name: "terminal_code", type: "TEXT", notnull: 0, pk: 0 }), + Object.freeze({ name: "created_at", type: "INTEGER", notnull: 1, pk: 0 }), + Object.freeze({ name: "updated_at", type: "INTEGER", notnull: 1, pk: 0 }), +]); + +const LEGACY_COLUMNS = Object.freeze([ + Object.freeze({ name: "account_key", type: "TEXT", notnull: 1, pk: 1 }), + Object.freeze({ name: "credential_generation", type: "INTEGER", notnull: 1, pk: 0 }), + Object.freeze({ name: "exhaustion_generation", type: "INTEGER", notnull: 1, pk: 0 }), + Object.freeze({ name: "operation_id", type: "TEXT", notnull: 1, pk: 0 }), + Object.freeze({ name: "state", type: "TEXT", notnull: 1, pk: 0 }), + Object.freeze({ name: "code", type: "TEXT", notnull: 0, pk: 0 }), + Object.freeze({ name: "created_at", type: "INTEGER", notnull: 1, pk: 0 }), + Object.freeze({ name: "updated_at", type: "INTEGER", notnull: 1, pk: 0 }), +]); +const PRIOR_COLUMNS = Object.freeze([ + Object.freeze({ name: "account_key", type: "TEXT", notnull: 1, pk: 1 }), + Object.freeze({ name: "operation_kind", type: "TEXT", notnull: 1, pk: 0 }), + Object.freeze({ name: "credential_generation", type: "INTEGER", notnull: 0, pk: 0 }), + Object.freeze({ name: "exhaustion_generation", type: "INTEGER", notnull: 0, pk: 0 }), + Object.freeze({ name: "operation_id", type: "TEXT", notnull: 1, pk: 0 }), + Object.freeze({ name: "state", type: "TEXT", notnull: 1, pk: 0 }), + Object.freeze({ name: "code", type: "TEXT", notnull: 0, pk: 0 }), + Object.freeze({ name: "created_at", type: "INTEGER", notnull: 1, pk: 0 }), + Object.freeze({ name: "updated_at", type: "INTEGER", notnull: 1, pk: 0 }), +]); + +function accountKey(accountId: string): string { + return createHash("sha256").update(`codex-reset-credit-operation\0${accountId}`).digest("hex"); +} + +function validateManualAccountId(accountId: string): void { + if (accountId !== MAIN_CODEX_ACCOUNT_ID && !isValidCodexAccountId(accountId)) { + throw new TypeError("invalid manual reset-credit account"); + } +} + +function manualPhysicalAccountKey(chatgptAccountId: string): string { + const normalized = chatgptAccountId.trim(); + if (!normalized) throw new TypeError("invalid manual reset-credit credential identity"); + return createHash("sha256") + .update(`codex-reset-credit-manual-physical\0${normalized}`) + .digest("hex"); +} + +function isGenerationNumber(value: unknown): value is number { + return Number.isSafeInteger(value) && Number(value) >= 0; +} + +function parseRecord(row: ResetCreditOperationRow | null): ResetCreditOperationRecord | undefined { + if (!row) return undefined; + const state = row.state; + const code = row.code; + const joinedOperationId = row.joined_operation_id; + if (typeof row.account_key !== "string" || !ACCOUNT_KEY_PATTERN.test(row.account_key) + || (row.operation_kind !== "recovery" && row.operation_kind !== "manual") + || !isCodexResetCreditOperationId(row.operation_id) + || (joinedOperationId !== null + && (!isCodexResetCreditOperationId(joinedOperationId) || joinedOperationId === row.operation_id)) + || typeof state !== "string" || !STATES.has(state) + || !isGenerationNumber(row.created_at) + || !isGenerationNumber(row.updated_at) + || row.updated_at < row.created_at) { + return undefined; + } + const recovery = row.operation_kind === "recovery"; + const manual = row.operation_kind === "manual"; + if (recovery !== (isGenerationNumber(row.credential_generation) + && isGenerationNumber(row.exhaustion_generation)) + || manual !== (row.credential_generation === null + && row.exhaustion_generation === null) + || (recovery && joinedOperationId !== null)) { + return undefined; + } + const terminal = state === "confirmed" || state === "stopped"; + if (!terminal && code !== null) return undefined; + const terminalState = typeof code === "string" + && Object.prototype.hasOwnProperty.call(TERMINAL_STATE_BY_CODE, code) + ? TERMINAL_STATE_BY_CODE[code as CodexResetCreditConsumeCode] + : undefined; + if (terminal !== (terminalState !== undefined)) return undefined; + if (terminal && state !== terminalState) return undefined; + return Object.freeze({ + accountKey: row.account_key, + operationKind: row.operation_kind, + ...(recovery + ? { + credentialGeneration: row.credential_generation as number, + exhaustionGeneration: row.exhaustion_generation as number, + } + : {}), + operationId: row.operation_id, + ...(joinedOperationId === null ? {} : { joinedOperationId }), + state: state as ResetCreditOperationState, + ...(terminal ? { code: code as CodexResetCreditConsumeCode } : {}), + createdAt: row.created_at, + updatedAt: row.updated_at, + }); +} + +function parseManualIdRecord( + row: ManualResetCreditOperationIdRow | null, +): ManualResetCreditOperationIdRecord | undefined { + if (!row) return undefined; + const terminalCode = row.terminal_code; + if (!isCodexResetCreditOperationId(row.operation_id) + || typeof row.account_key !== "string" || !ACCOUNT_KEY_PATTERN.test(row.account_key) + || !isCodexResetCreditOperationId(row.canonical_operation_id) + || (terminalCode !== null + && (typeof terminalCode !== "string" + || !Object.prototype.hasOwnProperty.call(TERMINAL_STATE_BY_CODE, terminalCode))) + || !isGenerationNumber(row.created_at) + || !isGenerationNumber(row.updated_at) + || row.updated_at < row.created_at) { + return undefined; + } + return Object.freeze({ + operationId: row.operation_id, + accountKey: row.account_key, + canonicalOperationId: row.canonical_operation_id, + ...(terminalCode === null ? {} : { terminalCode: terminalCode as CodexResetCreditConsumeCode }), + createdAt: row.created_at, + updatedAt: row.updated_at, + }); +} + +function assertColumnLayout( + database: Database, + tableName: string, + expectedColumns: readonly Readonly<{ name: string; type: string; notnull: number; pk: number }>[], +): void { + const tableRows = database.query("PRAGMA main.table_list").all() + .filter(row => row.name === tableName); + if (tableRows.length !== 1) throw new Error("invalid reset-credit operation ledger table"); + const table = tableRows[0]!; + if (table.schema !== "main" || table.type !== "table" || table.ncol !== expectedColumns.length + || table.wr !== 1 || table.strict !== 1) { + throw new Error("invalid reset-credit operation ledger table"); + } + const columns = database.query( + `PRAGMA main.table_xinfo(${tableName})`, + ).all(); + if (columns.length !== expectedColumns.length) { + throw new Error("invalid reset-credit operation ledger columns"); + } + for (let index = 0; index < expectedColumns.length; index += 1) { + const actual = columns[index]!; + const expected = expectedColumns[index]!; + if (actual.cid !== index || actual.name !== expected.name || actual.type !== expected.type + || actual.notnull !== expected.notnull || actual.dflt_value !== null + || actual.pk !== expected.pk || actual.hidden !== 0) { + throw new Error("invalid reset-credit operation ledger columns"); + } + } +} + +function assertNoLedgerTriggers(database: Database, tableName: string): void { + const mainTrigger = database.query<{ name: unknown }, [string]>(` + SELECT name FROM main.sqlite_schema + WHERE type = 'trigger' AND tbl_name = ? COLLATE NOCASE LIMIT 1 + `).get(tableName); + const tempTrigger = database.query<{ name: unknown }, [string]>(` + SELECT name FROM temp.sqlite_schema + WHERE type = 'trigger' AND tbl_name = ? COLLATE NOCASE LIMIT 1 + `).get(tableName); + if (mainTrigger || tempTrigger) throw new Error("reset-credit operation ledger triggers are forbidden"); +} + +function migrateLegacyTable(database: Database): void { + assertColumnLayout(database, TABLE_NAME, LEGACY_COLUMNS); + assertNoLedgerTriggers(database, TABLE_NAME); + const legacyRows = database.query<{ + account_key: unknown; + credential_generation: unknown; + exhaustion_generation: unknown; + operation_id: unknown; + state: unknown; + code: unknown; + created_at: unknown; + updated_at: unknown; + }, []>(` + SELECT account_key, credential_generation, exhaustion_generation, operation_id, + state, code, created_at, updated_at + FROM main.reset_credit_operations + ORDER BY account_key + LIMIT ${MAX_RESET_CREDIT_OPERATION_ACCOUNTS + 1} + `).all(); + if (legacyRows.length > MAX_RESET_CREDIT_OPERATION_ACCOUNTS) { + throw new Error("invalid reset-credit operation ledger capacity"); + } + const keys = new Set(); + const operations = new Set(); + for (const row of legacyRows) { + const record = parseRecord({ + ...row, + operation_kind: "recovery", + joined_operation_id: null, + }); + if (!record || keys.has(record.accountKey) || operations.has(record.operationId)) { + throw new Error("invalid reset-credit operation ledger state"); + } + keys.add(record.accountKey); + operations.add(record.operationId); + } + database.exec(`ALTER TABLE main.${TABLE_NAME} RENAME TO ${LEGACY_TABLE_NAME}`); + database.exec(CREATE_TABLE); + database.exec(` + INSERT INTO main.${TABLE_NAME} ( + account_key, operation_kind, credential_generation, exhaustion_generation, + operation_id, joined_operation_id, state, code, created_at, updated_at + ) + SELECT account_key, 'recovery', credential_generation, exhaustion_generation, + operation_id, NULL, state, code, created_at, updated_at + FROM main.${LEGACY_TABLE_NAME} + `); + database.exec(`DROP TABLE main.${LEGACY_TABLE_NAME}`); +} + +function migratePriorTable(database: Database): void { + assertColumnLayout(database, TABLE_NAME, PRIOR_COLUMNS); + assertNoLedgerTriggers(database, TABLE_NAME); + const priorRows = database.query, []>(` + SELECT account_key, operation_kind, credential_generation, exhaustion_generation, + operation_id, state, code, created_at, updated_at + FROM main.reset_credit_operations + ORDER BY account_key + LIMIT ${MAX_RESET_CREDIT_OPERATION_ACCOUNTS + 1} + `).all(); + if (priorRows.length > MAX_RESET_CREDIT_OPERATION_ACCOUNTS) { + throw new Error("invalid reset-credit operation ledger capacity"); + } + const keys = new Set(); + const operations = new Set(); + for (const row of priorRows) { + const record = parseRecord({ ...row, joined_operation_id: null }); + if (!record || keys.has(record.accountKey) || operations.has(record.operationId)) { + throw new Error("invalid reset-credit operation ledger state"); + } + keys.add(record.accountKey); + operations.add(record.operationId); + } + database.exec(`ALTER TABLE main.${TABLE_NAME} RENAME TO ${PRIOR_TABLE_NAME}`); + database.exec(CREATE_TABLE); + database.exec(` + INSERT INTO main.${TABLE_NAME} ( + account_key, operation_kind, credential_generation, exhaustion_generation, + operation_id, joined_operation_id, state, code, created_at, updated_at + ) + SELECT account_key, operation_kind, credential_generation, exhaustion_generation, + operation_id, NULL, state, code, created_at, updated_at + FROM main.${PRIOR_TABLE_NAME} + `); + database.exec(`DROP TABLE main.${PRIOR_TABLE_NAME}`); +} + +function isExactLegacySchema(database: Database, schema: SchemaObjectRow): boolean { + if (schema.type !== "table" || schema.name !== TABLE_NAME || schema.tbl_name !== TABLE_NAME + || schema.sql !== LEGACY_CREATE_TABLE) return false; + try { + assertColumnLayout(database, TABLE_NAME, LEGACY_COLUMNS); + assertNoLedgerTriggers(database, TABLE_NAME); + return true; + } catch { + return false; + } +} + +function isExactPriorSchema(database: Database, schema: SchemaObjectRow): boolean { + if (schema.type !== "table" || schema.name !== TABLE_NAME || schema.tbl_name !== TABLE_NAME + || schema.sql !== PRIOR_CREATE_TABLE) return false; + try { + assertColumnLayout(database, TABLE_NAME, PRIOR_COLUMNS); + assertNoLedgerTriggers(database, TABLE_NAME); + return true; + } catch { + return false; + } +} + +type PrimaryTableInitialization = "created" | "migrated" | "existing"; + +function assertCanonicalTable(database: Database): PrimaryTableInitialization { + const schemaRows = database.query(` + SELECT type, name, tbl_name, sql + FROM main.sqlite_schema + WHERE name = ? COLLATE NOCASE OR tbl_name = ? COLLATE NOCASE + ORDER BY type, name + LIMIT 4 + `).all(TABLE_NAME, TABLE_NAME); + let initialization: PrimaryTableInitialization; + if (schemaRows.length === 0) { + database.exec(CREATE_TABLE); + initialization = "created"; + } else if (schemaRows.length === 1 && isExactLegacySchema(database, schemaRows[0]!)) { + migrateLegacyTable(database); + initialization = "migrated"; + } else if (schemaRows.length === 1 && isExactPriorSchema(database, schemaRows[0]!)) { + migratePriorTable(database); + initialization = "migrated"; + } else if (schemaRows.length !== 1 + || schemaRows[0]?.type !== "table" + || schemaRows[0]?.name !== TABLE_NAME + || schemaRows[0]?.tbl_name !== TABLE_NAME + || schemaRows[0]?.sql !== EXPECTED_SCHEMA_SQL) { + throw new Error("invalid reset-credit operation ledger schema"); + } else { + initialization = "existing"; + } + assertColumnLayout(database, TABLE_NAME, EXPECTED_COLUMNS); + assertNoLedgerTriggers(database, TABLE_NAME); + return initialization; +} + +function ensureManualIdTable(database: Database, allowCreate: boolean): boolean { + const schemaRows = database.query(` + SELECT type, name, tbl_name, sql + FROM main.sqlite_schema + WHERE name = ? COLLATE NOCASE OR tbl_name = ? COLLATE NOCASE + ORDER BY type, name + LIMIT 4 + `).all(MANUAL_ID_TABLE_NAME, MANUAL_ID_TABLE_NAME); + let created = false; + if (schemaRows.length === 0) { + if (!allowCreate) { + throw new Error("missing manual reset-credit operation identity schema"); + } + database.exec(CREATE_MANUAL_ID_TABLE); + created = true; + } else if (schemaRows.length !== 1 + || schemaRows[0]?.type !== "table" + || schemaRows[0]?.name !== MANUAL_ID_TABLE_NAME + || schemaRows[0]?.tbl_name !== MANUAL_ID_TABLE_NAME + || schemaRows[0]?.sql !== EXPECTED_MANUAL_ID_SCHEMA_SQL) { + throw new Error("invalid manual reset-credit operation identity schema"); + } + assertColumnLayout(database, MANUAL_ID_TABLE_NAME, MANUAL_ID_COLUMNS); + assertNoLedgerTriggers(database, MANUAL_ID_TABLE_NAME); + return created; +} + +function initializeTable( + database: Database, + validationScope: ResetCreditOperationKind, +): Readonly<{ + recordCount: number; + manualIdCount: number; +}> { + const manualSchemaPresentBefore = database.query<{ present: number }, [string, string]>(` + SELECT 1 AS present + FROM main.sqlite_schema + WHERE name = ? COLLATE NOCASE OR tbl_name = ? COLLATE NOCASE + LIMIT 1 + `).get(MANUAL_ID_TABLE_NAME, MANUAL_ID_TABLE_NAME) !== null; + const primaryInitialization = assertCanonicalTable(database); + if (primaryInitialization !== "existing" && manualSchemaPresentBefore) { + throw new Error("invalid partial reset-credit operation ledger schema"); + } + const rows = database.query(SELECT_ALL).all(); + if (rows.length > MAX_RESET_CREDIT_OPERATION_ACCOUNTS) { + throw new Error("invalid reset-credit operation ledger capacity"); + } + const accountKeys = new Set(); + const operationIds = new Set(); + const records = new Map(); + for (const row of rows) { + const record = parseRecord(row); + const ids = record ? [record.operationId, ...(record.joinedOperationId ? [record.joinedOperationId] : [])] : []; + if (!record || accountKeys.has(record.accountKey) || ids.some(id => operationIds.has(id))) { + throw new Error("invalid reset-credit operation ledger state"); + } + accountKeys.add(record.accountKey); + records.set(record.accountKey, record); + for (const id of ids) operationIds.add(id); + } + + const manualTableCreated = ensureManualIdTable(database, primaryInitialization !== "existing"); + if (manualTableCreated) { + for (const record of records.values()) { + if (record.operationKind !== "manual") continue; + const ids = [record.operationId, ...(record.joinedOperationId ? [record.joinedOperationId] : [])]; + for (const operationId of ids) { + insertManualIdRecord(database, Object.freeze({ + operationId, + accountKey: record.accountKey, + canonicalOperationId: record.operationId, + ...(record.code === undefined ? {} : { terminalCode: record.code }), + createdAt: record.createdAt, + updatedAt: record.updatedAt, + })); + } + } + } + + const manualIdCount = database.query<{ count: unknown }, []>(SELECT_BOUNDED_MANUAL_ID_COUNT) + .get()?.count; + if (typeof manualIdCount !== "number" || !Number.isSafeInteger(manualIdCount) || manualIdCount < 0 + || manualIdCount > MAX_MANUAL_RESET_CREDIT_OPERATION_IDS) { + throw new Error("invalid manual reset-credit operation identity capacity"); + } + if (validationScope === "recovery") { + if (database.query<{ operation_id: unknown }, []>(SELECT_DUPLICATE_RECOVERY_MANUAL_ID).get()) { + throw new Error("duplicate reset-credit operation ids"); + } + return Object.freeze({ recordCount: rows.length, manualIdCount }); + } + + const manualRows = database.query(SELECT_ALL_MANUAL_IDS).all(); + if (manualRows.length !== manualIdCount) throw new Error("invalid manual reset-credit operation identity state"); + const manualIds = new Map(); + for (const row of manualRows) { + const record = parseManualIdRecord(row); + if (!record || manualIds.has(record.operationId)) { + throw new Error("invalid manual reset-credit operation identity state"); + } + manualIds.set(record.operationId, record); + } + for (const record of manualIds.values()) { + const canonical = manualIds.get(record.canonicalOperationId); + if (!canonical || canonical.operationId !== canonical.canonicalOperationId + || canonical.accountKey !== record.accountKey + || canonical.terminalCode !== record.terminalCode) { + throw new Error("invalid manual reset-credit operation identity state"); + } + if (record.terminalCode === undefined) { + const current = records.get(record.accountKey); + if (!current || current.operationKind !== "manual" || isTerminal(current) + || current.operationId !== record.canonicalOperationId) { + throw new Error("invalid manual reset-credit operation identity state"); + } + } + } + for (const record of records.values()) { + if (record.operationKind === "recovery") { + if (manualIds.has(record.operationId)) { + throw new Error("duplicate reset-credit operation ids"); + } + continue; + } + const expectedIds = [record.operationId, ...(record.joinedOperationId ? [record.joinedOperationId] : [])]; + for (const operationId of expectedIds) { + const identity = manualIds.get(operationId); + if (!identity || identity.accountKey !== record.accountKey + || identity.canonicalOperationId !== record.operationId + || identity.terminalCode !== record.code) { + throw new Error("invalid manual reset-credit operation identity state"); + } + } + } + return Object.freeze({ recordCount: rows.length, manualIdCount }); +} + +function readRecord(database: Database, key: string): ResetCreditOperationRecord | undefined { + const rows = database.query(SELECT_BY_KEY).all(key); + if (rows.length > 1) throw new Error("duplicate reset-credit operation records"); + const row = rows[0]; + const record = parseRecord(row ?? null); + if (row && !record) throw new Error("invalid reset-credit operation record"); + return record; +} + +function readManualIdRecord( + database: Database, + operationId: string, +): ManualResetCreditOperationIdRecord | undefined { + const rows = database.query(SELECT_MANUAL_ID) + .all(operationId); + if (rows.length > 1) throw new Error("duplicate manual reset-credit operation ids"); + const row = rows[0]; + const record = parseManualIdRecord(row ?? null); + if (row && !record) throw new Error("invalid manual reset-credit operation identity"); + return record; +} + +function sameManualIdRecord( + left: ManualResetCreditOperationIdRecord, + right: ManualResetCreditOperationIdRecord, +): boolean { + return left.operationId === right.operationId + && left.accountKey === right.accountKey + && left.canonicalOperationId === right.canonicalOperationId + && left.terminalCode === right.terminalCode + && left.createdAt === right.createdAt + && left.updatedAt === right.updatedAt; +} + +function assertStoredManualIdRecord( + database: Database, + expected: ManualResetCreditOperationIdRecord, +): void { + const stored = readManualIdRecord(database, expected.operationId); + if (!stored || !sameManualIdRecord(stored, expected)) { + throw new Error("manual reset-credit operation identity write did not persist"); + } +} + +function insertManualIdRecord( + database: Database, + record: ManualResetCreditOperationIdRecord, +): void { + const result = database.query(INSERT_MANUAL_ID).run( + record.operationId, + record.accountKey, + record.canonicalOperationId, + record.terminalCode ?? null, + record.createdAt, + record.updatedAt, + ); + if (result.changes !== 1) throw new Error("manual reset-credit operation identity insert failed"); + assertStoredManualIdRecord(database, record); +} + +function operationOwner(database: Database, operationId: string): string | undefined { + const rows = database.query<{ account_key: unknown }, [string, string, string]>(SELECT_KEY_BY_OPERATION_ID) + .all(operationId, operationId, operationId); + if (rows.length > 1) throw new Error("duplicate reset-credit operation ids"); + const owner = rows[0]?.account_key; + if (owner !== undefined && (typeof owner !== "string" || !ACCOUNT_KEY_PATTERN.test(owner))) { + throw new Error("invalid reset-credit operation owner"); + } + return owner; +} + +function sameRecord(left: ResetCreditOperationRecord, right: ResetCreditOperationRecord): boolean { + return left.accountKey === right.accountKey + && left.operationKind === right.operationKind + && left.credentialGeneration === right.credentialGeneration + && left.exhaustionGeneration === right.exhaustionGeneration + && left.operationId === right.operationId + && left.joinedOperationId === right.joinedOperationId + && left.state === right.state + && left.code === right.code + && left.createdAt === right.createdAt + && left.updatedAt === right.updatedAt; +} + +function assertStoredRecord( + database: Database, + expected: ResetCreditOperationRecord, +): void { + const stored = readRecord(database, expected.accountKey); + if (!stored || !sameRecord(stored, expected)) { + throw new Error("reset-credit operation write did not persist the expected record"); + } +} + +function compareGeneration( + record: ResetCreditOperationRecord, + generation: CodexResetCreditRecoveryGeneration, +): -1 | 0 | 1 { + return compareCodexResetCreditRecoveryGenerationOrder({ + accountId: generation.accountId, + credentialGeneration: record.credentialGeneration!, + exhaustionGeneration: record.exhaustionGeneration!, + }, generation); +} + +function isTerminal(record: ResetCreditOperationRecord): boolean { + return record.state === "confirmed" || record.state === "stopped"; +} + +function isThenable(value: unknown): boolean { + return (typeof value === "object" && value !== null) || typeof value === "function" + ? typeof (value as { then?: unknown }).then === "function" + : false; +} + +type Synchronous = T extends PromiseLike ? never : T; + +function withLedger(validationScope: ResetCreditOperationKind, operation: ( + database: Database, + recordCount: number, + manualIdCount: number, +) => Synchronous): T { + const path = prepareConfigMutationDatabasePathForWrite(); + let database: Database | undefined; + let transactionOpen = false; + try { + database = new Database(path, { create: true }); + try { chmodSync(path, 0o600); } catch { /* platform may ignore chmod */ } + database.exec("PRAGMA trusted_schema = OFF; PRAGMA busy_timeout = 0; PRAGMA synchronous = FULL; BEGIN IMMEDIATE"); + transactionOpen = true; + initializeConfigGeneration(database); + const counts = initializeTable(database, validationScope); + const value = operation(database, counts.recordCount, counts.manualIdCount); + if (isThenable(value) || !database.inTransaction) { + throw new Error("reset-credit operation ledger work escaped its synchronous transaction"); + } + database.exec("COMMIT"); + transactionOpen = false; + return value; + } catch (error) { + if (transactionOpen) { + try { database?.exec("ROLLBACK"); } catch { /* close still releases the write lock */ } + transactionOpen = false; + } + throw error; + } finally { + try { database?.close(); } catch { /* operation already completed */ } + } +} + +function isLedgerBusyError(error: unknown): boolean { + const code = error && typeof error === "object" && "code" in error + ? String((error as { code?: unknown }).code) + : ""; + const message = error instanceof Error ? error.message : ""; + return code === "SQLITE_BUSY" || code === "SQLITE_LOCKED" + || /database (?:is|table is) locked/i.test(message); +} + +function warnLedgerUnavailable(error: unknown): void { + if (isLedgerBusyError(error)) return; + const nested = error instanceof NestedConfigMutationError; + // Native SQLite and filesystem errors may contain absolute, account-bearing + // paths. Keep this warning categorical rather than forwarding error.message. + console.warn(nested + ? "[opencodex] Reset-credit operation ledger refused a nested config mutation." + : "[opencodex] Reset-credit operation ledger is unavailable."); +} + +function reportManualHistoryCapacity(count: number): void { + const level = count >= MAX_MANUAL_RESET_CREDIT_OPERATION_IDS + ? MAX_MANUAL_RESET_CREDIT_OPERATION_IDS + : count >= MANUAL_RESET_CREDIT_HISTORY_HIGH_WATER_MARK + ? MANUAL_RESET_CREDIT_HISTORY_HIGH_WATER_MARK + : 0; + if (level === 0 || level <= reportedManualHistoryLevel) return; + reportedManualHistoryLevel = level; + try { + console.warn( + `[opencodex] Reset-credit manual operation history is at ${count}/${MAX_MANUAL_RESET_CREDIT_OPERATION_IDS} entries${ + level === MAX_MANUAL_RESET_CREDIT_OPERATION_IDS + ? "; new manual redemptions are disabled until a maintainer expands capacity or applies an approved retirement policy." + : "." + }`, + ); + } catch { + // Count-only operational reporting must never weaken the fail-closed result. + } +} + +/** + * Throws `TypeError` for a malformed generation or timestamp. Runtime storage + * and contention failures are represented by a result kind. + */ +export function openResetCreditOperation( + generation: CodexResetCreditRecoveryGeneration, + now = Date.now(), +): OpenResetCreditOperationResult { + const generationSnapshot = snapshotCodexResetCreditRecoveryGeneration(generation); + if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); + try { + return withLedger("recovery", (database, recordCount) => { + const key = accountKey(generationSnapshot.accountId); + const current = readRecord(database, key); + if (current) { + if (current.operationKind !== "recovery") { + return Object.freeze({ kind: "unresolved-prior-generation" as const }); + } + const comparison = compareGeneration(current, generationSnapshot); + if (comparison > 0) return Object.freeze({ kind: "stale-generation" as const }); + if (comparison === 0) { + if (isTerminal(current)) { + return Object.freeze({ + kind: "terminal" as const, + operationId: current.operationId as CodexReservedOperationId, + code: current.code!, + }); + } + return Object.freeze({ + kind: "execute" as const, + operationId: current.operationId as CodexReservedOperationId, + resumed: true, + }); + } + if (!isTerminal(current)) return Object.freeze({ kind: "unresolved-prior-generation" as const }); + } else if (recordCount >= MAX_RESET_CREDIT_OPERATION_ACCOUNTS) { + return Object.freeze({ kind: "capacity" as const }); + } + + const operationId = randomUUID(); + if (!isCodexResetCreditOperationId(operationId)) throw new Error("runtime generated invalid UUID"); + if (operationOwner(database, operationId) !== undefined) { + throw new Error("duplicate reset-credit operation ids"); + } + const values = [ + "recovery", + generationSnapshot.credentialGeneration, + generationSnapshot.exhaustionGeneration, + operationId, + null, + "pending", + null, + now, + now, + ] as const; + const result = current + ? database.query(REPLACE_RECORD).run(...values, key) + : database.query(INSERT_RECORD).run(key, ...values); + if (result.changes !== 1) throw new Error("reset-credit operation reservation lost ownership"); + assertStoredRecord(database, Object.freeze({ + accountKey: key, + operationKind: "recovery", + credentialGeneration: generationSnapshot.credentialGeneration, + exhaustionGeneration: generationSnapshot.exhaustionGeneration, + operationId, + state: "pending", + createdAt: now, + updatedAt: now, + })); + return Object.freeze({ + kind: "execute" as const, + operationId: operationId as CodexReservedOperationId, + resumed: false, + }); + }); + } catch (error) { + warnLedgerUnavailable(error); + return Object.freeze({ kind: "unavailable" }); + } +} + +function updateOperation( + owner: Readonly<{ + accountKey: string; + operationKind: ResetCreditOperationKind; + credentialGeneration?: number; + exhaustionGeneration?: number; + }>, + operationId: string, + update: (record: ResetCreditOperationRecord) => ResetCreditOperationRecord | undefined, + afterWrite?: (database: Database, updated: ResetCreditOperationRecord) => void, +): UpdateResetCreditOperationResult { + if (!isCodexResetCreditOperationId(operationId)) return Object.freeze({ kind: "mismatch" }); + try { + return withLedger(owner.operationKind, database => { + const current = readRecord(database, owner.accountKey); + if (!current + || current.operationKind !== owner.operationKind + || current.credentialGeneration !== owner.credentialGeneration + || current.exhaustionGeneration !== owner.exhaustionGeneration + || current.operationId !== operationId) { + return Object.freeze({ kind: "mismatch" as const }); + } + const updated = update(current); + if (!updated) return Object.freeze({ kind: "mismatch" as const }); + const result = database.query(UPDATE_RECORD).run( + updated.state, + updated.code ?? null, + updated.updatedAt, + owner.accountKey, + owner.operationKind, + operationId, + owner.credentialGeneration ?? null, + owner.exhaustionGeneration ?? null, + ); + if (result.changes !== 1) throw new Error("reset-credit operation update lost ownership"); + assertStoredRecord(database, updated); + afterWrite?.(database, updated); + return Object.freeze({ kind: "updated" as const }); + }); + } catch (error) { + warnLedgerUnavailable(error); + return Object.freeze({ kind: "unavailable" }); + } +} + +/** + * Throws `TypeError` for a malformed generation or timestamp. An invalid + * operation id returns `mismatch`; runtime storage failures return `unavailable`. + */ +export function markResetCreditOperationAmbiguous( + generation: CodexResetCreditRecoveryGeneration, + operationId: string, + now = Date.now(), +): UpdateResetCreditOperationResult { + if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); + const generationSnapshot = snapshotCodexResetCreditRecoveryGeneration(generation); + return updateOperation({ + accountKey: accountKey(generationSnapshot.accountId), + operationKind: "recovery", + credentialGeneration: generationSnapshot.credentialGeneration, + exhaustionGeneration: generationSnapshot.exhaustionGeneration, + }, operationId, record => { + if (isTerminal(record)) return undefined; + return Object.freeze({ + ...record, + state: "ambiguous", + code: undefined, + updatedAt: Math.max(record.updatedAt, now), + }); + }); +} + +/** + * Throws `TypeError` for a malformed generation or timestamp. An invalid + * operation id or non-terminal code returns `mismatch`; runtime storage + * failures return `unavailable`. + */ +export function settleResetCreditOperation( + generation: CodexResetCreditRecoveryGeneration, + operationId: string, + code: CodexResetCreditConsumeCode, + now = Date.now(), +): UpdateResetCreditOperationResult { + if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); + if (!Object.prototype.hasOwnProperty.call(TERMINAL_STATE_BY_CODE, code)) { + return Object.freeze({ kind: "mismatch" }); + } + const generationSnapshot = snapshotCodexResetCreditRecoveryGeneration(generation); + return updateOperation({ + accountKey: accountKey(generationSnapshot.accountId), + operationKind: "recovery", + credentialGeneration: generationSnapshot.credentialGeneration, + exhaustionGeneration: generationSnapshot.exhaustionGeneration, + }, operationId, record => { + if (isTerminal(record)) return record.code === code ? record : undefined; + return Object.freeze({ + ...record, + state: TERMINAL_STATE_BY_CODE[code], + code, + updatedAt: Math.max(record.updatedAt, now), + }); + }); +} + +function snapshotManualIdentity(identity: ManualResetCreditOperationIdentity): { + accountKey: string; + operationId: string; +} { + if (!identity || typeof identity !== "object" || Array.isArray(identity)) { + throw new TypeError("manual reset-credit identity must be an object"); + } + const value = identity as unknown as Record; + const hasOwn = Object.prototype.hasOwnProperty; + if (!hasOwn.call(value, "accountId") + || !hasOwn.call(value, "chatgptAccountId") + || !hasOwn.call(value, "operationId")) { + throw new TypeError("manual reset-credit identity fields must be own properties"); + } + const accountId = value.accountId; + const chatgptAccountId = value.chatgptAccountId; + const operationId = value.operationId; + if (!isCodexResetCreditOperationId(operationId)) { + throw new TypeError("invalid manual reset-credit operation id"); + } + if (typeof accountId !== "string") throw new TypeError("invalid manual reset-credit account"); + validateManualAccountId(accountId); + if (typeof chatgptAccountId !== "string") { + throw new TypeError("invalid manual reset-credit credential identity"); + } + return Object.freeze({ + accountKey: manualPhysicalAccountKey(chatgptAccountId), + operationId, + }); +} + +/** + * Reserve or restore one explicit manual redemption intent. + * + * Throws `TypeError` for malformed identity fields or `now`; these are caller + * contract violations. Durable-state and runtime failures return a result kind. + */ +export function openManualResetCreditOperation( + identity: ManualResetCreditOperationIdentity, + now = Date.now(), +): OpenManualResetCreditOperationResult { + const owner = snapshotManualIdentity(identity); + if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); + try { + return withLedger("manual", (database, recordCount, manualIdCount) => { + reportManualHistoryCapacity(manualIdCount); + const admitNewCallerId = () => { + if (manualIdCount >= MAX_MANUAL_RESET_CREDIT_OPERATION_IDS) { + return Object.freeze({ kind: "capacity" as const }); + } + const existingOwner = operationOwner(database, owner.operationId); + if (existingOwner !== undefined) { + return Object.freeze({ + kind: existingOwner === owner.accountKey ? "unavailable" as const : "identity-mismatch" as const, + }); + } + return undefined; + }; + + const reserve = (replaceCurrent: boolean): OpenManualResetCreditOperationResult => { + const rejected = admitNewCallerId(); + if (rejected) return rejected; + + const record: ResetCreditOperationRecord = Object.freeze({ + accountKey: owner.accountKey, + operationKind: "manual", + operationId: owner.operationId, + state: "pending", + createdAt: now, + updatedAt: now, + }); + const values = [ + "manual", + null, + null, + owner.operationId, + null, + "pending", + null, + now, + now, + ] as const; + const result = replaceCurrent + ? database.query(REPLACE_RECORD).run(...values, owner.accountKey) + : database.query(INSERT_RECORD).run(owner.accountKey, ...values); + if (result.changes !== 1) throw new Error("manual reset-credit reservation lost ownership"); + assertStoredRecord(database, record); + insertManualIdRecord(database, Object.freeze({ + operationId: owner.operationId, + accountKey: owner.accountKey, + canonicalOperationId: owner.operationId, + createdAt: now, + updatedAt: now, + })); + return Object.freeze({ + kind: "execute" as const, + operationId: owner.operationId as CodexReservedOperationId, + resumed: false, + }); + }; + + const knownIdentity = readManualIdRecord(database, owner.operationId); + if (knownIdentity) { + if (knownIdentity.accountKey !== owner.accountKey) { + return Object.freeze({ kind: "identity-mismatch" as const }); + } + if (knownIdentity.terminalCode !== undefined) { + return Object.freeze({ + kind: "terminal" as const, + operationId: knownIdentity.canonicalOperationId as CodexReservedOperationId, + code: knownIdentity.terminalCode, + }); + } + const current = readRecord(database, owner.accountKey); + if (!current || current.operationKind !== "manual" || isTerminal(current) + || current.operationId !== knownIdentity.canonicalOperationId) { + throw new Error("manual reset-credit operation identity lost its active owner"); + } + return Object.freeze({ + kind: "execute" as const, + operationId: current.operationId as CodexReservedOperationId, + resumed: true, + }); + } + + const current = readRecord(database, owner.accountKey); + if (current) { + if (current.operationKind !== "manual") { + return Object.freeze({ kind: "unavailable" as const }); + } + if (!isTerminal(current)) { + const rejected = admitNewCallerId(); + if (rejected) return rejected; + insertManualIdRecord(database, Object.freeze({ + operationId: owner.operationId, + accountKey: owner.accountKey, + canonicalOperationId: current.operationId, + createdAt: now, + updatedAt: now, + })); + const joined: ResetCreditOperationRecord = Object.freeze({ + ...current, + ...(current.joinedOperationId === undefined + ? { joinedOperationId: owner.operationId } + : {}), + updatedAt: Math.max(current.updatedAt, now), + }); + const result = current.joinedOperationId === undefined + ? database.query(JOIN_MANUAL_OPERATION).run( + owner.operationId, + joined.updatedAt, + owner.accountKey, + current.operationId, + ) + : database.query(TOUCH_MANUAL_OPERATION).run( + joined.updatedAt, + owner.accountKey, + current.operationId, + ); + if (result.changes !== 1) { + throw new Error("manual reset-credit join lost ownership"); + } + assertStoredRecord(database, joined); + // The upstream request keeps the original durable id. Every caller id + // is retained in the identity history; the first alias is also kept on + // the current row for compatibility with the previous schema. + return Object.freeze({ + kind: "execute" as const, + operationId: current.operationId as CodexReservedOperationId, + resumed: true, + }); + } + // Deliberate: a distinct caller id after a settled intent represents a + // new explicit redemption. Prior ids remain immutable in the history, + // so a delayed retry can never be reclassified as this new intent. + return reserve(true); + } + if (recordCount >= MAX_RESET_CREDIT_OPERATION_ACCOUNTS) { + return Object.freeze({ kind: "capacity" as const }); + } + return reserve(false); + }); + } catch (error) { + warnLedgerUnavailable(error); + return Object.freeze({ kind: "unavailable" }); + } +} + +/** + * Mark a reserved manual redemption as ambiguous. + * + * Throws `TypeError` for malformed identity fields or `now`. A missing or + * incompatible durable record returns the existing result kind. + */ +export function markManualResetCreditOperationAmbiguous( + identity: ManualResetCreditOperationIdentity, + now = Date.now(), +): UpdateResetCreditOperationResult { + const owner = snapshotManualIdentity(identity); + if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); + return updateOperation({ accountKey: owner.accountKey, operationKind: "manual" }, owner.operationId, record => { + if (isTerminal(record)) return undefined; + return Object.freeze({ ...record, state: "ambiguous", code: undefined, updatedAt: Math.max(record.updatedAt, now) }); + }); +} + +/** + * Settle a reserved manual redemption with one terminal consume code. + * + * Throws `TypeError` for malformed identity fields or `now`; an unsupported + * code or incompatible durable record returns `mismatch`. + */ +export function settleManualResetCreditOperation( + identity: ManualResetCreditOperationIdentity, + code: CodexResetCreditConsumeCode, + now = Date.now(), +): UpdateResetCreditOperationResult { + const owner = snapshotManualIdentity(identity); + if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); + if (!Object.prototype.hasOwnProperty.call(TERMINAL_STATE_BY_CODE, code)) { + return Object.freeze({ kind: "mismatch" }); + } + return updateOperation({ accountKey: owner.accountKey, operationKind: "manual" }, owner.operationId, record => { + if (isTerminal(record)) return record.code === code ? record : undefined; + return Object.freeze({ + ...record, + state: TERMINAL_STATE_BY_CODE[code], + code, + updatedAt: Math.max(record.updatedAt, now), + }); + }, (database, updated) => { + const result = database.query(SETTLE_MANUAL_IDS).run( + code, + updated.updatedAt, + owner.accountKey, + owner.operationId, + code, + ); + if (result.changes < 1) { + throw new Error("manual reset-credit terminal identity update lost ownership"); + } + const rows = database.query( + SELECT_MANUAL_IDS_BY_CANONICAL, + ).all(owner.accountKey, owner.operationId); + if (rows.length < 1 || rows.length > MAX_MANUAL_RESET_CREDIT_OPERATION_IDS) { + throw new Error("invalid manual reset-credit terminal identity set"); + } + for (const row of rows) { + const stored = parseManualIdRecord(row); + if (!stored || stored.accountKey !== owner.accountKey + || stored.canonicalOperationId !== owner.operationId + || stored.terminalCode !== code + || stored.updatedAt !== updated.updatedAt) { + throw new Error("manual reset-credit terminal identity write did not persist"); + } + } + }); +} diff --git a/src/codex/reset-credit-recovery.ts b/src/codex/reset-credit-recovery.ts index 69771eddd7..2111762369 100644 --- a/src/codex/reset-credit-recovery.ts +++ b/src/codex/reset-credit-recovery.ts @@ -27,6 +27,20 @@ export type CodexResetCreditConsumeCode = | "nothing_to_reset" | "no_credit"; +declare const CODEX_RESERVED_OPERATION_ID_BRAND: unique symbol; + +/** An operation id whose durable reservation was validated by the operation ledger. */ +export type CodexReservedOperationId = string & { + readonly [CODEX_RESERVED_OPERATION_ID_BRAND]: true; +}; + +export const CODEX_RESET_CREDIT_OPERATION_ID_PATTERN = + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; + +export function isCodexResetCreditOperationId(value: unknown): value is string { + return typeof value === "string" && CODEX_RESET_CREDIT_OPERATION_ID_PATTERN.test(value); +} + export type CodexResetCreditRecoveryAuthorization = Readonly<{ enabled: boolean; /** @@ -189,7 +203,9 @@ const RESET_ELIGIBLE_CODES = { insufficient_quota: true, } as const satisfies Record; -function snapshotGeneration(input: unknown): CodexResetCreditRecoveryGeneration { +export function snapshotCodexResetCreditRecoveryGeneration( + input: unknown, +): CodexResetCreditRecoveryGeneration { if (!input || typeof input !== "object" || Array.isArray(input)) { throw new TypeError("generation must be an object"); } @@ -251,6 +267,8 @@ function compareGenerationOrder( return 0; } +export const compareCodexResetCreditRecoveryGenerationOrder = compareGenerationOrder; + function authorizedResetRejection(authorization: CodexResetCreditRecoveryAuthorization): boolean { const hasOwn = Object.prototype.hasOwnProperty; if (!hasOwn.call(authorization, "enabled") @@ -507,7 +525,7 @@ export class CodexResetCreditRecoveryCoordinator { let requestSignal: AbortSignal | undefined; try { requestSignal = snapshotRequestSignal(options); - generationSnapshot = snapshotGeneration(generation); + generationSnapshot = snapshotCodexResetCreditRecoveryGeneration(generation); } catch (error) { rejectAttempt(error); return attempt; diff --git a/src/config.ts b/src/config.ts index 60178f3d4f..b2f8bd57f4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2682,6 +2682,29 @@ function configMutationDatabasePath(): string { return path; } +/** Raised when an independent config-mutation transaction is requested recursively. */ +export class NestedConfigMutationError extends Error { + constructor() { + super("prepareConfigMutationDatabasePathForWrite must not run inside withConfigMutationLockSync"); + this.name = "NestedConfigMutationError"; + } +} + +/** + * Prepare the shared config-mutation database path for an independent top-level + * SQLite transaction. Callers must not invoke this while holding + * {@link withConfigMutationLockSync}; a second `BEGIN IMMEDIATE` deliberately + * fails busy instead of joining an uncommitted transaction. + * + * @throws {NestedConfigMutationError} If a config mutation lock is already held. + */ +export function prepareConfigMutationDatabasePathForWrite(): string { + if (configMutationLockDepth > 0) { + throw new NestedConfigMutationError(); + } + return configMutationDatabasePath(); +} + let configMutationLockDepth = 0; let configMutationDatabase: Database | null = null; diff --git a/tests/codex-reset-credit-operation-ledger.test.ts b/tests/codex-reset-credit-operation-ledger.test.ts new file mode 100644 index 0000000000..d3598d1833 --- /dev/null +++ b/tests/codex-reset-credit-operation-ledger.test.ts @@ -0,0 +1,1256 @@ +import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import { createHash } from "node:crypto"; +import { existsSync, mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { Database } from "bun:sqlite"; +import { join } from "node:path"; +import { + NestedConfigMutationError, + prepareConfigMutationDatabasePathForWrite, + withConfigMutationLockSync, +} from "../src/config"; +import { MAIN_CODEX_ACCOUNT_ID } from "../src/codex/account-id"; +import { + MAX_MANUAL_RESET_CREDIT_OPERATION_IDS, + MAX_RESET_CREDIT_OPERATION_ACCOUNTS, + markManualResetCreditOperationAmbiguous, + markResetCreditOperationAmbiguous, + openManualResetCreditOperation, + openResetCreditOperation, + settleManualResetCreditOperation, + settleResetCreditOperation, + type ManualResetCreditOperationIdentity, +} from "../src/codex/reset-credit-operation-ledger"; +import { + compareCodexResetCreditRecoveryGenerationOrder, + isCodexResetCreditOperationId, + type CodexResetCreditRecoveryGeneration, +} from "../src/codex/reset-credit-recovery"; + +const GENERATION: CodexResetCreditRecoveryGeneration = { + accountId: "pool-a", + credentialGeneration: 4, + exhaustionGeneration: 9, +}; +const CHILD_READY_TIMEOUT_MS = 10_000; +const CHILD_EXIT_TIMEOUT_MS = 5_000; +const CONTENTION_TEST_TIMEOUT_MS = 25_000; +const CONTENTION_FAIL_FAST_MS = 2_000; + +const LEGACY_OPERATION_SCHEMA_SQL = `CREATE TABLE reset_credit_operations ( + account_key TEXT PRIMARY KEY, + credential_generation INTEGER NOT NULL CHECK (credential_generation >= 0), + exhaustion_generation INTEGER NOT NULL CHECK (exhaustion_generation >= 0), + operation_id TEXT NOT NULL, + state TEXT NOT NULL, + code TEXT, + created_at INTEGER NOT NULL CHECK (created_at >= 0), + updated_at INTEGER NOT NULL CHECK (updated_at >= created_at) + ) STRICT, WITHOUT ROWID`; + +const PRIOR_OPERATION_SCHEMA_SQL = `CREATE TABLE reset_credit_operations ( + account_key TEXT PRIMARY KEY, + operation_kind TEXT NOT NULL CHECK (operation_kind IN ('recovery', 'manual')), + credential_generation INTEGER, + exhaustion_generation INTEGER, + operation_id TEXT NOT NULL, + state TEXT NOT NULL, + code TEXT, + created_at INTEGER NOT NULL CHECK (created_at >= 0), + updated_at INTEGER NOT NULL CHECK (updated_at >= created_at), + CHECK ( + (operation_kind = 'recovery' + AND credential_generation IS NOT NULL AND credential_generation >= 0 + AND exhaustion_generation IS NOT NULL AND exhaustion_generation >= 0) + OR + (operation_kind = 'manual' + AND credential_generation IS NULL AND exhaustion_generation IS NULL) + ) + ) STRICT, WITHOUT ROWID`; + +const CURRENT_OPERATION_SCHEMA_SHA256 = + "dec4cc8c4871ab8bce2f259268f2a674a9064aa239441bb04abea91de1b7f9fd"; +const CURRENT_MANUAL_ID_SCHEMA_SHA256 = + "c3ceff1059417c22cd7a3984f49eef54cd125213631882eb998b223567ce741a"; + +function schemaHash(sql: string | undefined): string | undefined { + return sql === undefined ? undefined : createHash("sha256").update(sql).digest("hex"); +} + +const originalOpenCodexHome = process.env.OPENCODEX_HOME; +let isolatedHome: string | undefined; + +beforeAll(() => { + isolatedHome = mkdtempSync(join(tmpdir(), "ocx-reset-credit-ledger-")); + process.env.OPENCODEX_HOME = isolatedHome; +}); + +afterAll(() => { + if (originalOpenCodexHome === undefined) delete process.env.OPENCODEX_HOME; + else process.env.OPENCODEX_HOME = originalOpenCodexHome; + if (isolatedHome) { + Bun.gc(true); + rmSync(isolatedHome, { recursive: true, force: true, maxRetries: 10, retryDelay: 50 }); + } +}); + +function databasePath(): string { + return join(process.env.OPENCODEX_HOME!, "config-mutation.sqlite"); +} + +function corruptFirstRecord(): void { + const database = new Database(databasePath()); + try { + database.run("UPDATE reset_credit_operations SET operation_id = 'not-a-uuid'"); + } finally { + database.close(); + } +} + +function fixtureOperationId(index: number): string { + return `00000000-0000-4000-8000-${index.toString(16).padStart(12, "0")}`; +} + +const MIGRATION_REJECTION_FIXTURES = [ + { + label: "legacy recovery", + kind: "legacy" as const, + schema: LEGACY_OPERATION_SCHEMA_SQL, + backupTable: "reset_credit_operations_legacy_v1", + }, + { + label: "prior manual", + kind: "prior" as const, + schema: PRIOR_OPERATION_SCHEMA_SQL, + backupTable: "reset_credit_operations_legacy_v2", + }, +] as const; +const MIGRATION_REJECTION_CASES = ["malformed row", "duplicate operation id", "over capacity"] as const; + +function seedRejectedMigration( + kind: "legacy" | "prior", + rejection: (typeof MIGRATION_REJECTION_CASES)[number], +): Record[] { + const database = new Database(databasePath(), { create: true }); + try { + database.exec(kind === "legacy" ? LEGACY_OPERATION_SCHEMA_SQL : PRIOR_OPERATION_SCHEMA_SQL); + const insert = kind === "legacy" + ? database.prepare(` + INSERT INTO reset_credit_operations VALUES (?, ?, ?, ?, 'pending', NULL, 1, 1) + `) + : database.prepare(` + INSERT INTO reset_credit_operations VALUES ( + ?, 'manual', NULL, NULL, ?, 'pending', NULL, 1, 1 + ) + `); + const rowCount = rejection === "over capacity" + ? MAX_RESET_CREDIT_OPERATION_ACCOUNTS + 1 + : rejection === "duplicate operation id" ? 2 : 1; + const duplicatedOperationId = fixtureOperationId(0x22000); + database.exec("BEGIN IMMEDIATE"); + for (let index = 0; index < rowCount; index += 1) { + const key = createHash("sha256") + .update(`migration-rejection-${kind}-${rejection}-${index}`) + .digest("hex"); + const operationId = rejection === "malformed row" + ? "not-a-uuid" + : rejection === "duplicate operation id" + ? duplicatedOperationId + : fixtureOperationId(0x23000 + index); + if (kind === "legacy") { + insert.run(key, GENERATION.credentialGeneration, GENERATION.exhaustionGeneration, operationId); + } else { + insert.run(key, operationId); + } + } + database.exec("COMMIT"); + return database.query, []>( + "SELECT * FROM reset_credit_operations ORDER BY account_key", + ).all(); + } catch (error) { + try { database.exec("ROLLBACK"); } catch { /* preserve the fixture error */ } + throw error; + } finally { + database.close(); + } +} + +async function waitForPath(path: string): Promise { + const deadline = Date.now() + CHILD_READY_TIMEOUT_MS; + while (!existsSync(path)) { + if (Date.now() >= deadline) throw new Error(`timed out waiting for ${path}`); + await Bun.sleep(10); + } +} + +async function terminateChild(child: Bun.Subprocess): Promise { + child.kill(); + let exited = await Promise.race([ + child.exited.then(() => true), + Bun.sleep(CHILD_EXIT_TIMEOUT_MS).then(() => false), + ]); + if (!exited) { + child.kill("SIGKILL"); + exited = await Promise.race([ + child.exited.then(() => true), + Bun.sleep(CHILD_EXIT_TIMEOUT_MS).then(() => false), + ]); + } + if (!exited) throw new Error("reset-credit ledger lock child did not exit"); +} + +function createLaxDuplicateLedger(): void { + const database = new Database(databasePath(), { create: true }); + try { + database.exec(` + CREATE TABLE reset_credit_operations ( + account_key TEXT, + credential_generation INTEGER, + exhaustion_generation INTEGER, + operation_id TEXT, + state TEXT, + code TEXT, + created_at INTEGER, + updated_at INTEGER + )`); + const key = createHash("sha256") + .update(`codex-reset-credit-operation\0${GENERATION.accountId}`) + .digest("hex"); + const insert = database.prepare(` + INSERT INTO reset_credit_operations VALUES (?, ?, ?, ?, 'pending', NULL, 1, 1)`); + insert.run(key, GENERATION.credentialGeneration, GENERATION.exhaustionGeneration, + "00000000-0000-4000-8000-000000000001"); + insert.run(key, GENERATION.credentialGeneration, GENERATION.exhaustionGeneration, + "00000000-0000-4000-8000-000000000002"); + } finally { + database.close(); + } +} + +beforeEach(() => { + const database = new Database(databasePath(), { create: true }); + try { + database.exec(` + DROP TABLE IF EXISTS reset_credit_manual_operation_ids; + DROP TABLE IF EXISTS reset_credit_operations; + DROP TABLE IF EXISTS reset_credit_operations_legacy_v1; + DROP TABLE IF EXISTS reset_credit_operations_legacy_v2; + `); + } + finally { database.close(); } +}); + +describe("Codex reset-credit operation ledger", () => { + test("exports strict operation-id, generation-order, and nested-mutation contracts", () => { + const operationId = fixtureOperationId(0xabc); + expect(isCodexResetCreditOperationId(operationId)).toBeTrue(); + expect(isCodexResetCreditOperationId(operationId.toUpperCase())).toBeFalse(); + expect(isCodexResetCreditOperationId("00000000-0000-5000-8000-000000000001")).toBeFalse(); + expect(isCodexResetCreditOperationId("00000000-0000-4000-7000-000000000001")).toBeFalse(); + expect(isCodexResetCreditOperationId(` ${operationId}`)).toBeFalse(); + expect(isCodexResetCreditOperationId("00000000-0000-0000-0000-000000000000")).toBeFalse(); + expect(isCodexResetCreditOperationId(null)).toBeFalse(); + expect(compareCodexResetCreditRecoveryGenerationOrder(GENERATION, GENERATION)).toBe(0); + expect(compareCodexResetCreditRecoveryGenerationOrder( + { ...GENERATION, credentialGeneration: GENERATION.credentialGeneration + 1 }, + GENERATION, + )).toBe(1); + expect(compareCodexResetCreditRecoveryGenerationOrder( + { ...GENERATION, exhaustionGeneration: GENERATION.exhaustionGeneration - 1 }, + GENERATION, + )).toBe(-1); + expect(prepareConfigMutationDatabasePathForWrite()).toBe(databasePath()); + expect(() => withConfigMutationLockSync(() => prepareConfigMutationDatabasePathForWrite())) + .toThrow(NestedConfigMutationError); + }); + + test("bootstraps the canonical ledger when its config directory and database are absent", () => { + const path = databasePath(); + if (!isolatedHome) throw new Error("isolated home was not initialized"); + rmSync(isolatedHome, { recursive: true, force: true, maxRetries: 10, retryDelay: 50 }); + expect(existsSync(isolatedHome)).toBeFalse(); + expect(existsSync(path)).toBeFalse(); + + expect(openResetCreditOperation(GENERATION, 100)) + .toMatchObject({ kind: "execute", resumed: false }); + expect(existsSync(path)).toBeTrue(); + }); + + test("fails closed when the primary ledger table is missing but manual identity state remains", () => { + const opened = openResetCreditOperation(GENERATION, 100); + if (opened.kind !== "execute") throw new Error("reservation failed"); + const database = new Database(databasePath()); + try { + database.exec("DROP TABLE reset_credit_operations"); + } finally { + database.close(); + } + + expect(openResetCreditOperation(GENERATION, 200)).toEqual({ kind: "unavailable" }); + const verifier = new Database(databasePath(), { readonly: true }); + try { + expect(verifier.query<{ name: string }, []>(` + SELECT name FROM main.sqlite_schema + WHERE type = 'table' AND name LIKE 'reset_credit_%' + ORDER BY name + `).all()).toEqual([{ name: "reset_credit_manual_operation_ids" }]); + } finally { + verifier.close(); + } + }); + + test("fails closed when the manual identity table is missing from an existing ledger", () => { + const opened = openResetCreditOperation(GENERATION, 100); + if (opened.kind !== "execute") throw new Error("reservation failed"); + const database = new Database(databasePath()); + try { + database.exec("DROP TABLE reset_credit_manual_operation_ids"); + } finally { + database.close(); + } + + expect(openResetCreditOperation(GENERATION, 200)).toEqual({ kind: "unavailable" }); + const verifier = new Database(databasePath(), { readonly: true }); + try { + expect(verifier.query<{ operation_id: string }, []>( + "SELECT operation_id FROM reset_credit_operations", + ).get()?.operation_id).toBe(opened.operationId); + expect(verifier.query<{ name: string }, []>(` + SELECT name FROM main.sqlite_schema + WHERE type = 'table' AND name = 'reset_credit_manual_operation_ids' + `).get()).toBeNull(); + } finally { + verifier.close(); + } + }); + + test("snapshots recovery generations once and rejects inherited fields", () => { + let credentialReads = 0; + const accessorGeneration = { + accountId: GENERATION.accountId, + get credentialGeneration() { + credentialReads += 1; + return credentialReads === 1 ? GENERATION.credentialGeneration : GENERATION.credentialGeneration + 1; + }, + exhaustionGeneration: GENERATION.exhaustionGeneration, + }; + + const opened = openResetCreditOperation(accessorGeneration, 100); + expect(opened).toMatchObject({ kind: "execute", resumed: false }); + expect(credentialReads).toBe(1); + if (opened.kind !== "execute") throw new Error("reservation failed"); + expect(markResetCreditOperationAmbiguous(accessorGeneration, opened.operationId, 200)) + .toEqual({ kind: "mismatch" }); + expect(credentialReads).toBe(2); + + const inheritedGeneration = Object.create(GENERATION) as CodexResetCreditRecoveryGeneration; + expect(() => openResetCreditOperation(inheritedGeneration, 300)) + .toThrow("generation fields must be own properties"); + expect(() => markResetCreditOperationAmbiguous(inheritedGeneration, opened.operationId, 300)) + .toThrow("generation fields must be own properties"); + expect(() => settleResetCreditOperation(inheritedGeneration, opened.operationId, "reset", 300)) + .toThrow("generation fields must be own properties"); + }); + + test("snapshots manual identities once and rejects inherited fields", () => { + const recovery = openResetCreditOperation(GENERATION, 100); + if (recovery.kind !== "execute") throw new Error("recovery reservation failed"); + const callerOperationId = fixtureOperationId(0xdef); + let accountReads = 0; + let credentialReads = 0; + let operationReads = 0; + const accessorIdentity = { + get accountId() { + accountReads += 1; + return "pool-manual-snapshot"; + }, + get chatgptAccountId() { + credentialReads += 1; + return "chatgpt-manual-snapshot"; + }, + get operationId() { + operationReads += 1; + return operationReads <= 2 ? callerOperationId : recovery.operationId; + }, + }; + + expect(openManualResetCreditOperation(accessorIdentity, 200)).toEqual({ + kind: "execute", + operationId: callerOperationId, + resumed: false, + }); + expect({ accountReads, credentialReads, operationReads }).toEqual({ + accountReads: 1, + credentialReads: 1, + operationReads: 1, + }); + const database = new Database(databasePath(), { readonly: true }); + try { + const rows = database.query<{ operation_id: string }, []>( + "SELECT operation_id FROM reset_credit_operations ORDER BY operation_id", + ).all(); + expect(rows.map(row => row.operation_id)).toEqual([ + callerOperationId, + recovery.operationId, + ].sort()); + } finally { + database.close(); + } + + const inheritedIdentity = Object.create({ + accountId: "pool-manual-inherited", + chatgptAccountId: "chatgpt-manual-inherited", + operationId: fixtureOperationId(0xeee), + }) as ManualResetCreditOperationIdentity; + expect(() => openManualResetCreditOperation(inheritedIdentity, 300)) + .toThrow("manual reset-credit identity fields must be own properties"); + expect(() => markManualResetCreditOperationAmbiguous(inheritedIdentity, 300)) + .toThrow("manual reset-credit identity fields must be own properties"); + expect(() => settleManualResetCreditOperation(inheritedIdentity, "reset", 300)) + .toThrow("manual reset-credit identity fields must be own properties"); + }); + + test("migrates the exact prior recovery schema without changing durable state", () => { + const database = new Database(databasePath(), { create: true }); + const key = createHash("sha256") + .update(`codex-reset-credit-operation\0${GENERATION.accountId}`) + .digest("hex"); + const operationId = fixtureOperationId(699); + try { + database.exec(LEGACY_OPERATION_SCHEMA_SQL); + database.prepare(` + INSERT INTO reset_credit_operations VALUES (?, ?, ?, ?, 'ambiguous', NULL, 100, 200) + `).run(key, GENERATION.credentialGeneration, GENERATION.exhaustionGeneration, operationId); + } finally { + database.close(); + } + + expect(openResetCreditOperation(GENERATION, 300)).toEqual({ + kind: "execute", + operationId, + resumed: true, + }); + const migrated = new Database(databasePath(), { readonly: true }); + try { + expect(schemaHash(migrated.query<{ sql: string }, []>(` + SELECT sql FROM main.sqlite_schema + WHERE type = 'table' AND name = 'reset_credit_operations' + `).get()?.sql)).toBe(CURRENT_OPERATION_SCHEMA_SHA256); + expect(migrated.query<{ name: string }, []>(` + SELECT name FROM main.sqlite_schema + WHERE name = 'reset_credit_operations_legacy_v1' + `).get()).toBeNull(); + expect(migrated.query, []>( + "SELECT * FROM reset_credit_operations", + ).get()).toMatchObject({ + account_key: key, + operation_kind: "recovery", + credential_generation: GENERATION.credentialGeneration, + exhaustion_generation: GENERATION.exhaustionGeneration, + operation_id: operationId, + state: "ambiguous", + created_at: 100, + updated_at: 200, + }); + } finally { + migrated.close(); + } + }); + + test("migrates the prior manual schema before persisting a joined retry id", () => { + const original = fixtureOperationId(690); + const joined = fixtureOperationId(691); + const physicalAccount = "chatgpt-prior-manual"; + const key = createHash("sha256") + .update(`codex-reset-credit-manual-physical\0${physicalAccount}`) + .digest("hex"); + const database = new Database(databasePath(), { create: true }); + try { + database.exec(PRIOR_OPERATION_SCHEMA_SQL); + database.prepare(` + INSERT INTO reset_credit_operations VALUES ( + ?, 'manual', NULL, NULL, ?, 'ambiguous', NULL, 100, 200 + ) + `).run(key, original); + } finally { + database.close(); + } + + expect(openManualResetCreditOperation({ + accountId: "pool-prior-manual", + chatgptAccountId: physicalAccount, + operationId: joined, + }, 300)).toEqual({ kind: "execute", operationId: original, resumed: true }); + + const migrated = new Database(databasePath(), { readonly: true }); + try { + expect(schemaHash(migrated.query<{ sql: string }, []>(` + SELECT sql FROM main.sqlite_schema + WHERE type = 'table' AND name = 'reset_credit_operations' + `).get()?.sql)).toBe(CURRENT_OPERATION_SCHEMA_SHA256); + expect(migrated.query<{ name: string }, []>(` + SELECT name FROM main.sqlite_schema + WHERE name = 'reset_credit_operations_legacy_v2' + `).get()).toBeNull(); + expect(migrated.query<{ operation_id: string; joined_operation_id: string }, []>(` + SELECT operation_id, joined_operation_id FROM reset_credit_operations + `).get()).toEqual({ operation_id: original, joined_operation_id: joined }); + expect(migrated.query<{ + operation_id: string; + canonical_operation_id: string; + terminal_code: string | null; + }, []>(` + SELECT operation_id, canonical_operation_id, terminal_code + FROM reset_credit_manual_operation_ids + ORDER BY operation_id + `).all()).toEqual([ + { operation_id: original, canonical_operation_id: original, terminal_code: null }, + { operation_id: joined, canonical_operation_id: original, terminal_code: null }, + ]); + } finally { + migrated.close(); + } + }); + + for (const fixture of MIGRATION_REJECTION_FIXTURES) { + for (const rejection of MIGRATION_REJECTION_CASES) { + test(`refuses ${fixture.label} migration with ${rejection} without rewriting state`, () => { + const before = seedRejectedMigration(fixture.kind, rejection); + const result = fixture.kind === "legacy" + ? openResetCreditOperation({ ...GENERATION, accountId: "pool-migration-probe" }, 300) + : openManualResetCreditOperation({ + accountId: "pool-migration-probe", + chatgptAccountId: "chatgpt-migration-probe", + operationId: fixtureOperationId(0x24000), + }, 300); + expect(result).toEqual({ kind: "unavailable" }); + + const verifier = new Database(databasePath(), { readonly: true }); + try { + expect(schemaHash(verifier.query<{ sql: string }, []>(` + SELECT sql FROM main.sqlite_schema + WHERE type = 'table' AND name = 'reset_credit_operations' + `).get()?.sql)).toBe(schemaHash(fixture.schema)); + expect(verifier.query, []>( + "SELECT * FROM reset_credit_operations ORDER BY account_key", + ).all()).toEqual(before); + expect(verifier.query<{ name: string }, [string, string]>(` + SELECT name FROM main.sqlite_schema + WHERE name = ? OR name = ? + ORDER BY name + `).all(fixture.backupTable, "reset_credit_manual_operation_ids")).toEqual([]); + } finally { + verifier.close(); + } + }); + } + } + + test("manual operations resume one intent and short-circuit its terminal result", () => { + const identity = { + accountId: "pool-manual", + chatgptAccountId: "chatgpt-manual", + operationId: fixtureOperationId(700), + }; + expect(openManualResetCreditOperation(identity, 100)).toEqual({ + kind: "execute", + operationId: identity.operationId, + resumed: false, + }); + expect(markManualResetCreditOperationAmbiguous(identity, 200)).toEqual({ kind: "updated" }); + expect(openManualResetCreditOperation(identity, 300)).toEqual({ + kind: "execute", + operationId: identity.operationId, + resumed: true, + }); + expect(settleManualResetCreditOperation( + identity, + "not-a-reset-code" as never, + 350, + )).toEqual({ kind: "mismatch" }); + expect(settleManualResetCreditOperation(identity, "already_redeemed", 400)) + .toEqual({ kind: "updated" }); + expect(openManualResetCreditOperation(identity, 500)).toEqual({ + kind: "terminal", + operationId: identity.operationId, + code: "already_redeemed", + }); + }); + + test("a distinct manual id after settlement opens one explicit new intent", () => { + const first = { + accountId: "pool-manual-new-intent", + chatgptAccountId: "chatgpt-new-intent", + operationId: fixtureOperationId(706), + }; + expect(openManualResetCreditOperation(first, 100)).toMatchObject({ kind: "execute" }); + expect(settleManualResetCreditOperation(first, "reset", 200)).toEqual({ kind: "updated" }); + const second = { ...first, operationId: fixtureOperationId(707) }; + expect(openManualResetCreditOperation(second, 300)).toEqual({ + kind: "execute", + operationId: second.operationId, + resumed: false, + }); + expect(openManualResetCreditOperation(second, 400)).toEqual({ + kind: "execute", + operationId: second.operationId, + resumed: true, + }); + }); + + test("an uppercase terminal id cannot reopen as a lowercase retry", () => { + const identity = { + accountId: "pool-manual-uppercase-terminal", + chatgptAccountId: "chatgpt-uppercase-terminal", + operationId: fixtureOperationId(708), + }; + expect(openManualResetCreditOperation(identity, 100)).toMatchObject({ kind: "execute" }); + expect(settleManualResetCreditOperation(identity, "reset", 200)).toEqual({ kind: "updated" }); + const uppercase = identity.operationId.toUpperCase(); + const database = new Database(databasePath()); + try { + database.prepare("UPDATE reset_credit_operations SET operation_id = ?").run(uppercase); + } finally { + database.close(); + } + + expect(openManualResetCreditOperation(identity, 300)).toEqual({ kind: "unavailable" }); + const stored = new Database(databasePath(), { readonly: true }); + try { + expect(stored.query<{ operation_id: string; state: string; code: string }, []>(` + SELECT operation_id, state, code FROM reset_credit_operations + `).get()).toEqual({ operation_id: uppercase, state: "confirmed", code: "reset" }); + } finally { + stored.close(); + } + }); + + test("fails closed when a manual id loses its canonical history mapping", () => { + const identity = { + accountId: "pool-manual-history-corrupt", + chatgptAccountId: "chatgpt-manual-history-corrupt", + operationId: fixtureOperationId(710), + }; + expect(openManualResetCreditOperation(identity, 100)).toMatchObject({ kind: "execute" }); + const missingCanonical = fixtureOperationId(711); + const database = new Database(databasePath()); + try { + database.prepare(` + UPDATE reset_credit_manual_operation_ids + SET canonical_operation_id = ? + WHERE operation_id = ? + `).run(missingCanonical, identity.operationId); + } finally { + database.close(); + } + + expect(openManualResetCreditOperation(identity, 200)).toEqual({ kind: "unavailable" }); + const verifier = new Database(databasePath(), { readonly: true }); + try { + expect(verifier.query<{ canonical_operation_id: string }, [string]>(` + SELECT canonical_operation_id + FROM reset_credit_manual_operation_ids + WHERE operation_id = ? + `).get(identity.operationId)?.canonical_operation_id).toBe(missingCanonical); + } finally { + verifier.close(); + } + }); + + test("manual operations preserve every joined caller id across later terminal intents", () => { + const first = { + accountId: "pool-manual-fence", + chatgptAccountId: "chatgpt-a", + operationId: fixtureOperationId(701), + }; + const joined = { ...first, operationId: fixtureOperationId(702) }; + expect(openManualResetCreditOperation(first, 100)).toMatchObject({ kind: "execute" }); + expect(openManualResetCreditOperation(joined, 200)) + .toEqual({ kind: "execute", operationId: first.operationId, resumed: true }); + const secondJoined = { + ...first, + accountId: "pool-manual-alias", + operationId: fixtureOperationId(703), + }; + expect(openManualResetCreditOperation(secondJoined, 300)) + .toEqual({ kind: "execute", operationId: first.operationId, resumed: true }); + // A third alias advanced durable time to 300. Settlement remains valid if + // the wall clock then moves backwards. + expect(settleManualResetCreditOperation(first, "reset", 250)).toEqual({ kind: "updated" }); + expect(openManualResetCreditOperation(first, 360)).toEqual({ + kind: "terminal", + operationId: first.operationId, + code: "reset", + }); + expect(openManualResetCreditOperation(joined, 370)).toEqual({ + kind: "terminal", + operationId: first.operationId, + code: "reset", + }); + expect(openManualResetCreditOperation(secondJoined, 375)).toEqual({ + kind: "terminal", + operationId: first.operationId, + code: "reset", + }); + const next = { ...first, operationId: fixtureOperationId(709) }; + expect(openManualResetCreditOperation(next, 380)).toEqual({ + kind: "execute", + operationId: next.operationId, + resumed: false, + }); + expect(settleManualResetCreditOperation(next, "no_credit", 390)).toEqual({ kind: "updated" }); + expect(openManualResetCreditOperation(joined, 395)).toEqual({ + kind: "terminal", + operationId: first.operationId, + code: "reset", + }); + expect(openManualResetCreditOperation(secondJoined, 396)).toEqual({ + kind: "terminal", + operationId: first.operationId, + code: "reset", + }); + const otherPhysical = { + ...first, + chatgptAccountId: "chatgpt-b", + operationId: fixtureOperationId(704), + }; + expect(openManualResetCreditOperation(otherPhysical, 400)) + .toEqual({ kind: "execute", operationId: otherPhysical.operationId, resumed: false }); + }); + + test("manual operations reject a caller UUID already owned by another physical account", () => { + const operationId = fixtureOperationId(705); + const first = { + accountId: "pool-manual-first", + chatgptAccountId: "chatgpt-first", + operationId, + }; + const second = { + accountId: "pool-manual-second", + chatgptAccountId: "chatgpt-second", + operationId, + }; + expect(openManualResetCreditOperation(first, 100)).toMatchObject({ kind: "execute" }); + expect(openManualResetCreditOperation(second, 200)).toEqual({ kind: "identity-mismatch" }); + expect(openManualResetCreditOperation(first, 300)).toEqual({ + kind: "execute", + operationId, + resumed: true, + }); + }); + + test("creates the exact canonical SQLite schema", () => { + expect(openResetCreditOperation(GENERATION, 100)) + .toMatchObject({ kind: "execute", resumed: false }); + const database = new Database(databasePath(), { readonly: true }); + try { + expect(schemaHash(database.query<{ sql: string }, []>(` + SELECT sql FROM main.sqlite_schema + WHERE type = 'table' AND name = 'reset_credit_operations' + `).get()?.sql)).toBe(CURRENT_OPERATION_SCHEMA_SHA256); + expect(schemaHash(database.query<{ sql: string }, []>(` + SELECT sql FROM main.sqlite_schema + WHERE type = 'table' AND name = 'reset_credit_manual_operation_ids' + `).get()?.sql)).toBe(CURRENT_MANUAL_ID_SCHEMA_SHA256); + } finally { + database.close(); + } + }); + + test("durably reserves before dispatch and restores the same operation identity", () => { + const first = openResetCreditOperation(GENERATION, 100); + expect(first).toMatchObject({ kind: "execute", resumed: false }); + if (first.kind !== "execute") throw new Error("reservation failed"); + + const restarted = openResetCreditOperation(GENERATION, 200); + expect(restarted).toEqual({ kind: "execute", operationId: first.operationId, resumed: true }); + expect(settleResetCreditOperation(GENERATION, first.operationId, "reset", 400)) + .toEqual({ kind: "updated" }); + expect(openResetCreditOperation(GENERATION, 500)).toEqual({ + kind: "terminal", + operationId: first.operationId, + code: "reset", + }); + }); + + test("supports durable recovery generations for the main account", () => { + const generation = { ...GENERATION, accountId: MAIN_CODEX_ACCOUNT_ID }; + const first = openResetCreditOperation(generation, 100); + expect(first).toMatchObject({ kind: "execute", resumed: false }); + if (first.kind !== "execute") throw new Error("reservation failed"); + + expect(openResetCreditOperation(generation, 200)).toEqual({ + kind: "execute", + operationId: first.operationId, + resumed: true, + }); + expect(settleResetCreditOperation(generation, first.operationId, "already_redeemed", 300)) + .toEqual({ kind: "updated" }); + expect(openResetCreditOperation(generation, 400)).toEqual({ + kind: "terminal", + operationId: first.operationId, + code: "already_redeemed", + }); + }); + + test("retains ambiguous operations and never allocates a replacement id", () => { + const opened = openResetCreditOperation(GENERATION, 100); + if (opened.kind !== "execute") throw new Error("reservation failed"); + expect(markResetCreditOperationAmbiguous(GENERATION, opened.operationId, 150)).toEqual({ kind: "updated" }); + expect(openResetCreditOperation(GENERATION, 200)).toEqual({ + kind: "execute", + operationId: opened.operationId, + resumed: true, + }); + expect(openResetCreditOperation({ ...GENERATION, exhaustionGeneration: 10 })).toEqual({ + kind: "unresolved-prior-generation", + }); + }); + + test("keeps timestamps monotonic when the wall clock rolls back", () => { + const opened = openResetCreditOperation(GENERATION, 200); + if (opened.kind !== "execute") throw new Error("reservation failed"); + expect(markResetCreditOperationAmbiguous(GENERATION, opened.operationId, 100)) + .toEqual({ kind: "updated" }); + expect(openResetCreditOperation(GENERATION, 50)).toEqual({ + kind: "execute", + operationId: opened.operationId, + resumed: true, + }); + expect(settleResetCreditOperation(GENERATION, opened.operationId, "reset", 50)) + .toEqual({ kind: "updated" }); + expect(openResetCreditOperation(GENERATION, 25)).toEqual({ + kind: "terminal", + operationId: opened.operationId, + code: "reset", + }); + }); + + test("returns terminal outcomes without another execution and permits a newer generation", () => { + const opened = openResetCreditOperation(GENERATION, 100); + if (opened.kind !== "execute") throw new Error("reservation failed"); + expect(settleResetCreditOperation(GENERATION, opened.operationId, "already_redeemed", 200)) + .toEqual({ kind: "updated" }); + expect(openResetCreditOperation(GENERATION)).toEqual({ + kind: "terminal", + operationId: opened.operationId, + code: "already_redeemed", + }); + expect(openResetCreditOperation({ ...GENERATION, exhaustionGeneration: 10 })) + .toMatchObject({ kind: "execute", resumed: false }); + }); + + test("persists every recovery terminal code without reopening execution", () => { + for (const [index, code] of (["nothing_to_reset", "no_credit"] as const).entries()) { + const generation = { + accountId: `pool-terminal-code-${index}`, + credentialGeneration: 1, + exhaustionGeneration: 1, + }; + const opened = openResetCreditOperation(generation, 100); + if (opened.kind !== "execute") throw new Error("reservation failed"); + expect(settleResetCreditOperation(generation, opened.operationId, code, 200)) + .toEqual({ kind: "updated" }); + expect(openResetCreditOperation(generation, 300)).toEqual({ + kind: "terminal", + operationId: opened.operationId, + code, + }); + } + }); + + test("terminal recovery and manual operations reject late ambiguity and conflicting settlement", () => { + const recovery = openResetCreditOperation(GENERATION, 100); + if (recovery.kind !== "execute") throw new Error("reservation failed"); + expect(settleResetCreditOperation(GENERATION, recovery.operationId, "reset", 200)) + .toEqual({ kind: "updated" }); + expect(markResetCreditOperationAmbiguous(GENERATION, recovery.operationId, 300)) + .toEqual({ kind: "mismatch" }); + expect(settleResetCreditOperation(GENERATION, recovery.operationId, "no_credit", 400)) + .toEqual({ kind: "mismatch" }); + expect(openResetCreditOperation(GENERATION, 500)).toEqual({ + kind: "terminal", + operationId: recovery.operationId, + code: "reset", + }); + + const manual = { + accountId: "pool-manual-terminal-fence", + chatgptAccountId: "chatgpt-manual-terminal-fence", + operationId: fixtureOperationId(709), + }; + expect(openManualResetCreditOperation(manual, 100)).toMatchObject({ kind: "execute" }); + expect(settleManualResetCreditOperation(manual, "reset", 200)).toEqual({ kind: "updated" }); + expect(markManualResetCreditOperationAmbiguous(manual, 300)).toEqual({ kind: "mismatch" }); + expect(settleManualResetCreditOperation(manual, "no_credit", 400)) + .toEqual({ kind: "mismatch" }); + expect(openManualResetCreditOperation(manual, 500)).toEqual({ + kind: "terminal", + operationId: manual.operationId, + code: "reset", + }); + }); + + test("rejects stale generations and mismatched settlement", () => { + const current = openResetCreditOperation(GENERATION); + if (current.kind !== "execute") throw new Error("reservation failed"); + expect(openResetCreditOperation({ ...GENERATION, exhaustionGeneration: 8 })) + .toEqual({ kind: "stale-generation" }); + const reauthenticated = { + ...GENERATION, + credentialGeneration: GENERATION.credentialGeneration + 1, + exhaustionGeneration: 0, + }; + expect(openResetCreditOperation(reauthenticated)) + .toEqual({ kind: "unresolved-prior-generation" }); + expect(markResetCreditOperationAmbiguous(reauthenticated, current.operationId)) + .toEqual({ kind: "mismatch" }); + expect(settleResetCreditOperation(reauthenticated, current.operationId, "reset")) + .toEqual({ kind: "mismatch" }); + expect(settleResetCreditOperation(GENERATION, "00000000-0000-4000-8000-000000000999", "reset")) + .toEqual({ kind: "mismatch" }); + }); + + test("fails closed for malformed durable rows without overwriting them", () => { + const opened = openResetCreditOperation(GENERATION); + if (opened.kind !== "execute") throw new Error("reservation failed"); + corruptFirstRecord(); + expect(openResetCreditOperation(GENERATION)).toEqual({ kind: "unavailable" }); + expect(markResetCreditOperationAmbiguous(GENERATION, opened.operationId)).toEqual({ kind: "unavailable" }); + expect(settleResetCreditOperation(GENERATION, opened.operationId, "reset")) + .toEqual({ kind: "unavailable" }); + const database = new Database(databasePath(), { readonly: true }); + try { + expect(database.query<{ operation_id: string }, []>( + "SELECT operation_id FROM reset_credit_operations", + ).get()?.operation_id).toBe("not-a-uuid"); + } finally { + database.close(); + } + }); + + test("rejects a nonterminal row carrying any code without overwriting it", () => { + const opened = openResetCreditOperation(GENERATION, 100); + if (opened.kind !== "execute") throw new Error("reservation failed"); + const database = new Database(databasePath()); + try { + database.run("UPDATE reset_credit_operations SET code = 'garbage'"); + } finally { + database.close(); + } + expect(openResetCreditOperation(GENERATION, 200)).toEqual({ kind: "unavailable" }); + expect(markResetCreditOperationAmbiguous(GENERATION, opened.operationId, 300)) + .toEqual({ kind: "unavailable" }); + const stored = new Database(databasePath(), { readonly: true }); + try { + expect(stored.query<{ code: string }, []>( + "SELECT code FROM reset_credit_operations", + ).get()?.code).toBe("garbage"); + } finally { + stored.close(); + } + }); + + test("fails closed for a noncanonical uppercase operation id", () => { + const opened = openResetCreditOperation(GENERATION, 100); + if (opened.kind !== "execute") throw new Error("reservation failed"); + const uppercase = opened.operationId.toUpperCase(); + const database = new Database(databasePath()); + try { + database.prepare("UPDATE reset_credit_operations SET operation_id = ?").run(uppercase); + } finally { + database.close(); + } + expect(openResetCreditOperation(GENERATION, 200)).toEqual({ kind: "unavailable" }); + expect(settleResetCreditOperation(GENERATION, opened.operationId, "reset", 300)) + .toEqual({ kind: "unavailable" }); + const stored = new Database(databasePath(), { readonly: true }); + try { + expect(stored.query<{ operation_id: string }, []>( + "SELECT operation_id FROM reset_credit_operations", + ).get()?.operation_id).toBe(uppercase); + } finally { + stored.close(); + } + }); + + test("refuses a lax duplicate schema without choosing or replacing an operation", () => { + createLaxDuplicateLedger(); + expect(openResetCreditOperation(GENERATION)).toEqual({ kind: "unavailable" }); + expect(markResetCreditOperationAmbiguous( + GENERATION, + "00000000-0000-4000-8000-000000000001", + )).toEqual({ kind: "unavailable" }); + const database = new Database(databasePath(), { readonly: true }); + try { + expect(database.query<{ count: number }, []>( + "SELECT COUNT(*) AS count FROM reset_credit_operations", + ).get()?.count).toBe(2); + } finally { + database.close(); + } + }); + + test("refuses a canonical ledger that reuses an operation id across accounts", () => { + const first = openResetCreditOperation(GENERATION, 100); + if (first.kind !== "execute") throw new Error("reservation failed"); + const database = new Database(databasePath()); + try { + const secondKey = createHash("sha256") + .update("codex-reset-credit-operation\0pool-b") + .digest("hex"); + database.prepare(` + INSERT INTO reset_credit_operations ( + account_key, operation_kind, credential_generation, exhaustion_generation, operation_id, + state, code, created_at, updated_at + ) VALUES (?, 'recovery', ?, ?, ?, 'pending', NULL, 1, 1)`) + .run( + secondKey, + GENERATION.credentialGeneration, + GENERATION.exhaustionGeneration, + first.operationId, + ); + } finally { + database.close(); + } + expect(openResetCreditOperation(GENERATION)).toEqual({ kind: "unavailable" }); + expect(openResetCreditOperation({ ...GENERATION, accountId: "pool-c" })) + .toEqual({ kind: "unavailable" }); + }); + + test("isolates recovery validation from unrelated manual history but rejects cross-table id reuse", () => { + const manual = { + accountId: "pool-manual-isolation", + chatgptAccountId: "chatgpt-manual-isolation", + operationId: fixtureOperationId(0x25000), + }; + expect(openManualResetCreditOperation(manual, 100)).toMatchObject({ kind: "execute" }); + expect(settleManualResetCreditOperation(manual, "no_credit", 200)).toEqual({ kind: "updated" }); + + const database = new Database(databasePath()); + try { + const unrelated = fixtureOperationId(0x25001); + database.prepare(` + INSERT INTO reset_credit_manual_operation_ids ( + operation_id, account_key, canonical_operation_id, + terminal_code, created_at, updated_at + ) VALUES (?, ?, ?, 'no_credit', 1, 1) + `).run( + unrelated, + createHash("sha256").update("unrelated-corrupt-manual-history").digest("hex"), + fixtureOperationId(0x25002), + ); + } finally { + database.close(); + } + + const generation = { ...GENERATION, accountId: "pool-recovery-isolation" }; + const opened = openResetCreditOperation(generation, 300); + expect(opened).toMatchObject({ kind: "execute", resumed: false }); + if (opened.kind !== "execute") throw new Error("recovery reservation failed"); + + const duplicate = new Database(databasePath()); + try { + duplicate.prepare(` + INSERT INTO reset_credit_manual_operation_ids ( + operation_id, account_key, canonical_operation_id, + terminal_code, created_at, updated_at + ) VALUES (?, ?, ?, 'no_credit', 1, 1) + `).run( + opened.operationId, + createHash("sha256").update("cross-table-duplicate-recovery-id").digest("hex"), + opened.operationId, + ); + } finally { + duplicate.close(); + } + expect(openResetCreditOperation(generation, 400)).toEqual({ kind: "unavailable" }); + }); + + test("refuses a trigger without replacing the terminal reservation", () => { + const first = openResetCreditOperation(GENERATION, 100); + if (first.kind !== "execute") throw new Error("reservation failed"); + expect(settleResetCreditOperation(GENERATION, first.operationId, "reset", 200)) + .toEqual({ kind: "updated" }); + const database = new Database(databasePath()); + try { + database.exec(` + CREATE TRIGGER reset_credit_tamper AFTER UPDATE ON reset_credit_operations + BEGIN + DELETE FROM reset_credit_operations WHERE account_key = NEW.account_key; + END`); + } finally { + database.close(); + } + expect(openResetCreditOperation({ ...GENERATION, exhaustionGeneration: 10 }, 300)) + .toEqual({ kind: "unavailable" }); + const verifier = new Database(databasePath(), { readonly: true }); + try { + expect(verifier.query<{ operation_id: string }, []>( + "SELECT operation_id FROM reset_credit_operations", + ).get()?.operation_id).toBe(first.operationId); + } finally { + verifier.close(); + } + }); + + test("fails fast under cross-process mutation contention and recovers after abrupt exit", async () => { + expect(openResetCreditOperation(GENERATION)).toMatchObject({ kind: "execute", resumed: false }); + const readyPath = join(process.env.OPENCODEX_HOME!, "ledger-lock-ready"); + const child = Bun.spawn([process.execPath, "-e", ` + import { writeFileSync } from "node:fs"; + import { Database } from "bun:sqlite"; + const database = new Database(process.env.OCX_LEDGER_DB_PATH); + database.exec("PRAGMA busy_timeout = 0; BEGIN IMMEDIATE"); + writeFileSync(process.env.OCX_LEDGER_READY_PATH, "ready"); + while (true) Bun.sleepSync(50); + `], { + cwd: join(import.meta.dir, ".."), + env: { + ...process.env, + OCX_LEDGER_DB_PATH: databasePath(), + OCX_LEDGER_READY_PATH: readyPath, + }, + stdin: "ignore", + stdout: "pipe", + stderr: "pipe", + }); + try { + await waitForPath(readyPath); + const startedAt = performance.now(); + const blocked = openResetCreditOperation({ ...GENERATION, accountId: "pool-b" }); + const elapsedMs = performance.now() - startedAt; + expect(blocked).toEqual({ kind: "unavailable" }); + expect(elapsedMs).toBeLessThan(CONTENTION_FAIL_FAST_MS); + } finally { + await terminateChild(child); + } + expect(openResetCreditOperation({ ...GENERATION, accountId: "pool-b" })) + .toMatchObject({ kind: "execute", resumed: false }); + }, CONTENTION_TEST_TIMEOUT_MS); + + test("never authorizes execution from inside an uncommitted config transaction", () => { + let nested: unknown; + expect(() => withConfigMutationLockSync(() => { + nested = openResetCreditOperation(GENERATION); + expect(nested).toEqual({ kind: "unavailable" }); + throw new Error("roll back outer config transaction"); + })).toThrow("roll back outer config transaction"); + expect(openResetCreditOperation(GENERATION)) + .toMatchObject({ kind: "execute", resumed: false }); + }); + + test("keeps terminal manual ids immutable and fails closed when identity history is full", () => { + const first = { + accountId: "pool-manual-history-cap", + chatgptAccountId: "chatgpt-manual-history-cap", + operationId: fixtureOperationId(9000), + }; + expect(openManualResetCreditOperation(first, 100)).toMatchObject({ kind: "execute" }); + expect(settleManualResetCreditOperation(first, "reset", 200)).toEqual({ kind: "updated" }); + + const database = new Database(databasePath()); + try { + const insert = database.prepare(` + INSERT INTO reset_credit_manual_operation_ids ( + operation_id, account_key, canonical_operation_id, + terminal_code, created_at, updated_at + ) VALUES (?, ?, ?, 'no_credit', 1, 1) + `); + database.exec("BEGIN IMMEDIATE"); + for (let index = 1; index < MAX_MANUAL_RESET_CREDIT_OPERATION_IDS; index += 1) { + const operationId = fixtureOperationId(9000 + index); + const key = createHash("sha256") + .update(`manual-history-cap-${index}`) + .digest("hex"); + insert.run(operationId, key, operationId); + } + database.exec("COMMIT"); + } catch (error) { + try { database.exec("ROLLBACK"); } catch { /* preserve fixture error */ } + throw error; + } finally { + database.close(); + } + + expect(openManualResetCreditOperation(first, 300)).toEqual({ + kind: "terminal", + operationId: first.operationId, + code: "reset", + }); + expect(openManualResetCreditOperation({ + ...first, + operationId: fixtureOperationId(15000), + }, 400)).toEqual({ kind: "capacity" }); + expect(openResetCreditOperation({ + ...GENERATION, + accountId: "pool-recovery-at-manual-history-cap", + }, 500)).toMatchObject({ kind: "execute", resumed: false }); + const verifier = new Database(databasePath(), { readonly: true }); + try { + expect(verifier.query<{ count: number }, []>( + "SELECT COUNT(*) AS count FROM reset_credit_manual_operation_ids", + ).get()?.count).toBe(MAX_MANUAL_RESET_CREDIT_OPERATION_IDS); + } finally { + verifier.close(); + } + }); + + test("admits existing accounts but refuses a new account at capacity", () => { + expect(openResetCreditOperation({ ...GENERATION, accountId: "pool-0" })) + .toMatchObject({ kind: "execute", resumed: false }); + const database = new Database(databasePath()); + try { + const insert = database.prepare(` + INSERT INTO reset_credit_operations ( + account_key, operation_kind, credential_generation, exhaustion_generation, operation_id, + state, code, created_at, updated_at + ) VALUES (?, 'recovery', ?, ?, ?, 'pending', NULL, 1, 1)`); + database.exec("BEGIN IMMEDIATE"); + for (let index = 1; index < MAX_RESET_CREDIT_OPERATION_ACCOUNTS; index += 1) { + const accountId = `pool-${index}`; + const key = createHash("sha256") + .update(`codex-reset-credit-operation\0${accountId}`) + .digest("hex"); + const operationId = fixtureOperationId(index); + insert.run(key, GENERATION.credentialGeneration, GENERATION.exhaustionGeneration, operationId); + } + database.exec("COMMIT"); + } catch (error) { + try { database.exec("ROLLBACK"); } catch { /* surface the original fixture error */ } + throw error; + } finally { + database.close(); + } + expect(openResetCreditOperation({ ...GENERATION, accountId: "pool-over-cap" })) + .toEqual({ kind: "capacity" }); + expect(openResetCreditOperation({ ...GENERATION, accountId: "pool-0" })) + .toMatchObject({ kind: "execute", resumed: true }); + + const overflow = new Database(databasePath()); + try { + const key = createHash("sha256") + .update("codex-reset-credit-operation\0pool-corrupt-over-cap") + .digest("hex"); + overflow.prepare(` + INSERT INTO reset_credit_operations ( + account_key, operation_kind, credential_generation, exhaustion_generation, operation_id, + state, code, created_at, updated_at + ) VALUES (?, 'recovery', ?, ?, ?, 'pending', NULL, 1, 1)`) + .run( + key, + GENERATION.credentialGeneration, + GENERATION.exhaustionGeneration, + // SELECT_ALL intentionally reads MAX + 1 rows so the corrupt + // over-capacity state cannot be mistaken for an ordinary full ledger. + fixtureOperationId(MAX_RESET_CREDIT_OPERATION_ACCOUNTS + 1), + ); + } finally { + overflow.close(); + } + expect(openResetCreditOperation({ ...GENERATION, accountId: "pool-0" })) + .toEqual({ kind: "unavailable" }); + expect(openResetCreditOperation({ ...GENERATION, accountId: "pool-new" })) + .toEqual({ kind: "unavailable" }); + }); +});