From d6c55984070f7b17ae6e4ef10000332b343ad85a Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Sun, 16 Aug 2026 17:34:53 +0900 Subject: [PATCH 1/6] feat(codex): add durable reset-credit operation ledger --- src/codex/reset-credit-operation-ledger.ts | 1323 +++++++++++++++++ src/codex/reset-credit-recovery.ts | 16 + src/config.ts | 20 + ...odex-reset-credit-operation-ledger.test.ts | 878 +++++++++++ 4 files changed, 2237 insertions(+) create mode 100644 src/codex/reset-credit-operation-ledger.ts create mode 100644 tests/codex-reset-credit-operation-ledger.test.ts diff --git a/src/codex/reset-credit-operation-ledger.ts b/src/codex/reset-credit-operation-ledger.ts new file mode 100644 index 0000000000..194f9ce957 --- /dev/null +++ b/src/codex/reset-credit-operation-ledger.ts @@ -0,0 +1,1323 @@ +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, + 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_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 validateGeneration(generation: CodexResetCreditRecoveryGeneration): void { + if ((generation.accountId !== MAIN_CODEX_ACCOUNT_ID && !isValidCodexAccountId(generation.accountId)) + || !isGenerationNumber(generation.credentialGeneration) + || !isGenerationNumber(generation.exhaustionGeneration)) { + throw new TypeError("invalid reset-credit recovery generation"); + } +} + +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; + } +} + +function assertCanonicalTable(database: Database): void { + 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); + if (schemaRows.length === 0) { + database.exec(CREATE_TABLE); + } else if (schemaRows.length === 1 && isExactLegacySchema(database, schemaRows[0]!)) { + migrateLegacyTable(database); + } else if (schemaRows.length === 1 && isExactPriorSchema(database, schemaRows[0]!)) { + migratePriorTable(database); + } 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"); + } + assertColumnLayout(database, TABLE_NAME, EXPECTED_COLUMNS); + assertNoLedgerTriggers(database, TABLE_NAME); +} + +function ensureManualIdTable(database: Database): 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) { + 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): Readonly<{ + recordCount: number; + manualIdCount: number; +}> { + assertCanonicalTable(database); + 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); + 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 manualRows = database.query(SELECT_ALL_MANUAL_IDS).all(); + if (manualRows.length > MAX_MANUAL_RESET_CREDIT_OPERATION_IDS) { + throw new Error("invalid manual reset-credit operation identity capacity"); + } + 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: manualRows.length }); +} + +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(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); + 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; + 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 { + validateGeneration(generation); + if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); + try { + return withLedger((database, recordCount) => { + const key = accountKey(generation.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, generation); + 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"); + const values = [ + "recovery", + generation.credentialGeneration, + generation.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: generation.credentialGeneration, + exhaustionGeneration: generation.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(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"); + validateGeneration(generation); + return updateOperation({ + accountKey: accountKey(generation.accountId), + operationKind: "recovery", + credentialGeneration: generation.credentialGeneration, + exhaustionGeneration: generation.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" }); + } + validateGeneration(generation); + return updateOperation({ + accountKey: accountKey(generation.accountId), + operationKind: "recovery", + credentialGeneration: generation.credentialGeneration, + exhaustionGeneration: generation.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 validateManualIdentity(identity: ManualResetCreditOperationIdentity): { + accountKey: string; +} { + if (!isCodexResetCreditOperationId(identity.operationId)) { + throw new TypeError("invalid manual reset-credit operation id"); + } + validateManualAccountId(identity.accountId); + return { accountKey: manualPhysicalAccountKey(identity.chatgptAccountId) }; +} + +/** + * 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 = validateManualIdentity(identity); + if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); + try { + return withLedger((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, identity.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: identity.operationId, + state: "pending", + createdAt: now, + updatedAt: now, + }); + const values = [ + "manual", + null, + null, + identity.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: identity.operationId, + accountKey: owner.accountKey, + canonicalOperationId: identity.operationId, + createdAt: now, + updatedAt: now, + })); + return Object.freeze({ + kind: "execute" as const, + operationId: identity.operationId as CodexReservedOperationId, + resumed: false, + }); + }; + + const knownIdentity = readManualIdRecord(database, identity.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: identity.operationId, + accountKey: owner.accountKey, + canonicalOperationId: current.operationId, + createdAt: now, + updatedAt: now, + })); + const joined: ResetCreditOperationRecord = Object.freeze({ + ...current, + ...(current.joinedOperationId === undefined + ? { joinedOperationId: identity.operationId } + : {}), + updatedAt: Math.max(current.updatedAt, now), + }); + const result = current.joinedOperationId === undefined + ? database.query(JOIN_MANUAL_OPERATION).run( + identity.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 = validateManualIdentity(identity); + if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); + return updateOperation({ ...owner, operationKind: "manual" }, identity.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 = validateManualIdentity(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({ ...owner, operationKind: "manual" }, identity.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, + identity.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, identity.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 !== identity.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..d81171bc23 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; /** @@ -251,6 +265,8 @@ function compareGenerationOrder( return 0; } +export const compareCodexResetCreditRecoveryGenerationOrder = compareGenerationOrder; + function authorizedResetRejection(authorization: CodexResetCreditRecoveryAuthorization): boolean { const hasOwn = Object.prototype.hasOwnProperty; if (!hasOwn.call(authorization, "enabled") diff --git a/src/config.ts b/src/config.ts index dcf34313a4..41a5abc9bb 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2683,6 +2683,26 @@ function configMutationDatabasePath(): string { return path; } +/** + * 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. + */ +export class NestedConfigMutationError extends Error { + constructor() { + super("prepareConfigMutationDatabasePathForWrite must not run inside withConfigMutationLockSync"); + this.name = "NestedConfigMutationError"; + } +} + +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..348dea4977 --- /dev/null +++ b/tests/codex-reset-credit-operation-ledger.test.ts @@ -0,0 +1,878 @@ +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, +} 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 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")}`; +} + +async function waitForPath(path: string): Promise { + const deadline = Date.now() + 5_000; + while (!existsSync(path)) { + if (Date.now() >= deadline) throw new Error(`timed out waiting for ${path}`); + await Bun.sleep(10); + } +} + +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; + `); + } + 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("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(); + } + }); + + 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("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" }); + 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("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", + }); + let childExited = false; + try { + await waitForPath(readyPath); + expect(openResetCreditOperation({ ...GENERATION, accountId: "pool-b" })) + .toEqual({ kind: "unavailable" }); + child.kill(); + await child.exited; + childExited = true; + } finally { + if (!childExited) { + child.kill(); + await child.exited; + } + } + expect(openResetCreditOperation({ ...GENERATION, accountId: "pool-b" })) + .toMatchObject({ kind: "execute", resumed: false }); + }); + + 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" }); + 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" }); + }); +}); From 7654d80b988bce874158a2318fc73efb2d0c0387 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Mon, 17 Aug 2026 02:49:21 +0900 Subject: [PATCH 2/6] fix(codex): snapshot reset-credit ledger generations --- src/codex/reset-credit-operation-ledger.ts | 104 ++++++++------- src/codex/reset-credit-recovery.ts | 6 +- ...odex-reset-credit-operation-ledger.test.ts | 119 ++++++++++++++++-- 3 files changed, 172 insertions(+), 57 deletions(-) diff --git a/src/codex/reset-credit-operation-ledger.ts b/src/codex/reset-credit-operation-ledger.ts index 194f9ce957..096ca82176 100644 --- a/src/codex/reset-credit-operation-ledger.ts +++ b/src/codex/reset-credit-operation-ledger.ts @@ -6,6 +6,7 @@ import { initializeConfigGeneration } from "./generation"; import { compareCodexResetCreditRecoveryGenerationOrder, isCodexResetCreditOperationId, + snapshotCodexResetCreditRecoveryGeneration, type CodexResetCreditConsumeCode, type CodexResetCreditRecoveryGeneration, type CodexReservedOperationId, @@ -339,14 +340,6 @@ function isGenerationNumber(value: unknown): value is number { return Number.isSafeInteger(value) && Number(value) >= 0; } -function validateGeneration(generation: CodexResetCreditRecoveryGeneration): void { - if ((generation.accountId !== MAIN_CODEX_ACCOUNT_ID && !isValidCodexAccountId(generation.accountId)) - || !isGenerationNumber(generation.credentialGeneration) - || !isGenerationNumber(generation.exhaustionGeneration)) { - throw new TypeError("invalid reset-credit recovery generation"); - } -} - function parseRecord(row: ResetCreditOperationRow | null): ResetCreditOperationRecord | undefined { if (!row) return undefined; const state = row.state; @@ -910,17 +903,17 @@ export function openResetCreditOperation( generation: CodexResetCreditRecoveryGeneration, now = Date.now(), ): OpenResetCreditOperationResult { - validateGeneration(generation); + const generationSnapshot = snapshotCodexResetCreditRecoveryGeneration(generation); if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); try { return withLedger((database, recordCount) => { - const key = accountKey(generation.accountId); + 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, generation); + const comparison = compareGeneration(current, generationSnapshot); if (comparison > 0) return Object.freeze({ kind: "stale-generation" as const }); if (comparison === 0) { if (isTerminal(current)) { @@ -945,8 +938,8 @@ export function openResetCreditOperation( if (!isCodexResetCreditOperationId(operationId)) throw new Error("runtime generated invalid UUID"); const values = [ "recovery", - generation.credentialGeneration, - generation.exhaustionGeneration, + generationSnapshot.credentialGeneration, + generationSnapshot.exhaustionGeneration, operationId, null, "pending", @@ -961,8 +954,8 @@ export function openResetCreditOperation( assertStoredRecord(database, Object.freeze({ accountKey: key, operationKind: "recovery", - credentialGeneration: generation.credentialGeneration, - exhaustionGeneration: generation.exhaustionGeneration, + credentialGeneration: generationSnapshot.credentialGeneration, + exhaustionGeneration: generationSnapshot.exhaustionGeneration, operationId, state: "pending", createdAt: now, @@ -1035,12 +1028,12 @@ export function markResetCreditOperationAmbiguous( now = Date.now(), ): UpdateResetCreditOperationResult { if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); - validateGeneration(generation); + const generationSnapshot = snapshotCodexResetCreditRecoveryGeneration(generation); return updateOperation({ - accountKey: accountKey(generation.accountId), + accountKey: accountKey(generationSnapshot.accountId), operationKind: "recovery", - credentialGeneration: generation.credentialGeneration, - exhaustionGeneration: generation.exhaustionGeneration, + credentialGeneration: generationSnapshot.credentialGeneration, + exhaustionGeneration: generationSnapshot.exhaustionGeneration, }, operationId, record => { if (isTerminal(record)) return undefined; return Object.freeze({ @@ -1067,12 +1060,12 @@ export function settleResetCreditOperation( if (!Object.prototype.hasOwnProperty.call(TERMINAL_STATE_BY_CODE, code)) { return Object.freeze({ kind: "mismatch" }); } - validateGeneration(generation); + const generationSnapshot = snapshotCodexResetCreditRecoveryGeneration(generation); return updateOperation({ - accountKey: accountKey(generation.accountId), + accountKey: accountKey(generationSnapshot.accountId), operationKind: "recovery", - credentialGeneration: generation.credentialGeneration, - exhaustionGeneration: generation.exhaustionGeneration, + credentialGeneration: generationSnapshot.credentialGeneration, + exhaustionGeneration: generationSnapshot.exhaustionGeneration, }, operationId, record => { if (isTerminal(record)) return record.code === code ? record : undefined; return Object.freeze({ @@ -1084,14 +1077,35 @@ export function settleResetCreditOperation( }); } -function validateManualIdentity(identity: ManualResetCreditOperationIdentity): { +function snapshotManualIdentity(identity: ManualResetCreditOperationIdentity): { accountKey: string; + operationId: string; } { - if (!isCodexResetCreditOperationId(identity.operationId)) { + 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"); } - validateManualAccountId(identity.accountId); - return { accountKey: manualPhysicalAccountKey(identity.chatgptAccountId) }; + 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, + }); } /** @@ -1104,7 +1118,7 @@ export function openManualResetCreditOperation( identity: ManualResetCreditOperationIdentity, now = Date.now(), ): OpenManualResetCreditOperationResult { - const owner = validateManualIdentity(identity); + const owner = snapshotManualIdentity(identity); if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); try { return withLedger((database, recordCount, manualIdCount) => { @@ -1113,7 +1127,7 @@ export function openManualResetCreditOperation( if (manualIdCount >= MAX_MANUAL_RESET_CREDIT_OPERATION_IDS) { return Object.freeze({ kind: "capacity" as const }); } - const existingOwner = operationOwner(database, identity.operationId); + const existingOwner = operationOwner(database, owner.operationId); if (existingOwner !== undefined) { return Object.freeze({ kind: existingOwner === owner.accountKey ? "unavailable" as const : "identity-mismatch" as const, @@ -1129,7 +1143,7 @@ export function openManualResetCreditOperation( const record: ResetCreditOperationRecord = Object.freeze({ accountKey: owner.accountKey, operationKind: "manual", - operationId: identity.operationId, + operationId: owner.operationId, state: "pending", createdAt: now, updatedAt: now, @@ -1138,7 +1152,7 @@ export function openManualResetCreditOperation( "manual", null, null, - identity.operationId, + owner.operationId, null, "pending", null, @@ -1151,20 +1165,20 @@ export function openManualResetCreditOperation( if (result.changes !== 1) throw new Error("manual reset-credit reservation lost ownership"); assertStoredRecord(database, record); insertManualIdRecord(database, Object.freeze({ - operationId: identity.operationId, + operationId: owner.operationId, accountKey: owner.accountKey, - canonicalOperationId: identity.operationId, + canonicalOperationId: owner.operationId, createdAt: now, updatedAt: now, })); return Object.freeze({ kind: "execute" as const, - operationId: identity.operationId as CodexReservedOperationId, + operationId: owner.operationId as CodexReservedOperationId, resumed: false, }); }; - const knownIdentity = readManualIdRecord(database, identity.operationId); + const knownIdentity = readManualIdRecord(database, owner.operationId); if (knownIdentity) { if (knownIdentity.accountKey !== owner.accountKey) { return Object.freeze({ kind: "identity-mismatch" as const }); @@ -1197,7 +1211,7 @@ export function openManualResetCreditOperation( const rejected = admitNewCallerId(); if (rejected) return rejected; insertManualIdRecord(database, Object.freeze({ - operationId: identity.operationId, + operationId: owner.operationId, accountKey: owner.accountKey, canonicalOperationId: current.operationId, createdAt: now, @@ -1206,13 +1220,13 @@ export function openManualResetCreditOperation( const joined: ResetCreditOperationRecord = Object.freeze({ ...current, ...(current.joinedOperationId === undefined - ? { joinedOperationId: identity.operationId } + ? { joinedOperationId: owner.operationId } : {}), updatedAt: Math.max(current.updatedAt, now), }); const result = current.joinedOperationId === undefined ? database.query(JOIN_MANUAL_OPERATION).run( - identity.operationId, + owner.operationId, joined.updatedAt, owner.accountKey, current.operationId, @@ -1261,9 +1275,9 @@ export function markManualResetCreditOperationAmbiguous( identity: ManualResetCreditOperationIdentity, now = Date.now(), ): UpdateResetCreditOperationResult { - const owner = validateManualIdentity(identity); + const owner = snapshotManualIdentity(identity); if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); - return updateOperation({ ...owner, operationKind: "manual" }, identity.operationId, record => { + 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) }); }); @@ -1280,12 +1294,12 @@ export function settleManualResetCreditOperation( code: CodexResetCreditConsumeCode, now = Date.now(), ): UpdateResetCreditOperationResult { - const owner = validateManualIdentity(identity); + 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({ ...owner, operationKind: "manual" }, identity.operationId, record => { + return updateOperation({ accountKey: owner.accountKey, operationKind: "manual" }, owner.operationId, record => { if (isTerminal(record)) return record.code === code ? record : undefined; return Object.freeze({ ...record, @@ -1298,7 +1312,7 @@ export function settleManualResetCreditOperation( code, updated.updatedAt, owner.accountKey, - identity.operationId, + owner.operationId, code, ); if (result.changes < 1) { @@ -1306,14 +1320,14 @@ export function settleManualResetCreditOperation( } const rows = database.query( SELECT_MANUAL_IDS_BY_CANONICAL, - ).all(owner.accountKey, identity.operationId); + ).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 !== identity.operationId + || 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 d81171bc23..2111762369 100644 --- a/src/codex/reset-credit-recovery.ts +++ b/src/codex/reset-credit-recovery.ts @@ -203,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"); } @@ -523,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/tests/codex-reset-credit-operation-ledger.test.ts b/tests/codex-reset-credit-operation-ledger.test.ts index 348dea4977..bc2cc638dc 100644 --- a/tests/codex-reset-credit-operation-ledger.test.ts +++ b/tests/codex-reset-credit-operation-ledger.test.ts @@ -19,6 +19,7 @@ import { openResetCreditOperation, settleManualResetCreditOperation, settleResetCreditOperation, + type ManualResetCreditOperationIdentity, } from "../src/codex/reset-credit-operation-ledger"; import { compareCodexResetCreditRecoveryGenerationOrder, @@ -31,6 +32,9 @@ const GENERATION: CodexResetCreditRecoveryGeneration = { credentialGeneration: 4, exhaustionGeneration: 9, }; +const CHILD_READY_TIMEOUT_MS = 4_000; +const CHILD_EXIT_TIMEOUT_MS = 2_000; +const CONTENTION_TEST_TIMEOUT_MS = 15_000; const LEGACY_OPERATION_SCHEMA_SQL = `CREATE TABLE reset_credit_operations ( account_key TEXT PRIMARY KEY, @@ -107,13 +111,29 @@ function fixtureOperationId(index: number): string { } async function waitForPath(path: string): Promise { - const deadline = Date.now() + 5_000; + 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 { @@ -177,6 +197,92 @@ describe("Codex reset-credit operation ledger", () => { .toThrow(NestedConfigMutationError); }); + 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") @@ -737,23 +843,16 @@ describe("Codex reset-credit operation ledger", () => { stdout: "pipe", stderr: "pipe", }); - let childExited = false; try { await waitForPath(readyPath); expect(openResetCreditOperation({ ...GENERATION, accountId: "pool-b" })) .toEqual({ kind: "unavailable" }); - child.kill(); - await child.exited; - childExited = true; } finally { - if (!childExited) { - child.kill(); - await child.exited; - } + 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; From 1556ae3d2bd107685b2d7cc43179e5c40eb25500 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Mon, 17 Aug 2026 04:17:09 +0900 Subject: [PATCH 3/6] fix(codex): bound reset-credit ledger recovery validation --- src/codex/reset-credit-operation-ledger.ts | 51 ++++-- src/config.ts | 15 +- ...odex-reset-credit-operation-ledger.test.ts | 153 ++++++++++++++++++ 3 files changed, 204 insertions(+), 15 deletions(-) diff --git a/src/codex/reset-credit-operation-ledger.ts b/src/codex/reset-credit-operation-ledger.ts index 096ca82176..813a814dae 100644 --- a/src/codex/reset-credit-operation-ledger.ts +++ b/src/codex/reset-credit-operation-ledger.ts @@ -198,6 +198,20 @@ const SELECT_ALL_MANUAL_IDS = ` 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 @@ -619,7 +633,10 @@ function ensureManualIdTable(database: Database): boolean { return created; } -function initializeTable(database: Database): Readonly<{ +function initializeTable( + database: Database, + validationScope: ResetCreditOperationKind, +): Readonly<{ recordCount: number; manualIdCount: number; }> { @@ -660,10 +677,21 @@ function initializeTable(database: Database): Readonly<{ } } - const manualRows = database.query(SELECT_ALL_MANUAL_IDS).all(); - if (manualRows.length > MAX_MANUAL_RESET_CREDIT_OPERATION_IDS) { + 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); @@ -704,7 +732,7 @@ function initializeTable(database: Database): Readonly<{ } } } - return Object.freeze({ recordCount: rows.length, manualIdCount: manualRows.length }); + return Object.freeze({ recordCount: rows.length, manualIdCount }); } function readRecord(database: Database, key: string): ResetCreditOperationRecord | undefined { @@ -824,7 +852,7 @@ function isThenable(value: unknown): boolean { type Synchronous = T extends PromiseLike ? never : T; -function withLedger(operation: ( +function withLedger(validationScope: ResetCreditOperationKind, operation: ( database: Database, recordCount: number, manualIdCount: number, @@ -838,7 +866,7 @@ function withLedger(operation: ( database.exec("PRAGMA trusted_schema = OFF; PRAGMA busy_timeout = 0; PRAGMA synchronous = FULL; BEGIN IMMEDIATE"); transactionOpen = true; initializeConfigGeneration(database); - const counts = initializeTable(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"); @@ -869,6 +897,8 @@ function isLedgerBusyError(error: unknown): boolean { 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."); @@ -906,7 +936,7 @@ export function openResetCreditOperation( const generationSnapshot = snapshotCodexResetCreditRecoveryGeneration(generation); if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); try { - return withLedger((database, recordCount) => { + return withLedger("recovery", (database, recordCount) => { const key = accountKey(generationSnapshot.accountId); const current = readRecord(database, key); if (current) { @@ -936,6 +966,9 @@ export function openResetCreditOperation( 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, @@ -986,7 +1019,7 @@ function updateOperation( ): UpdateResetCreditOperationResult { if (!isCodexResetCreditOperationId(operationId)) return Object.freeze({ kind: "mismatch" }); try { - return withLedger(database => { + return withLedger(owner.operationKind, database => { const current = readRecord(database, owner.accountKey); if (!current || current.operationKind !== owner.operationKind @@ -1121,7 +1154,7 @@ export function openManualResetCreditOperation( const owner = snapshotManualIdentity(identity); if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp"); try { - return withLedger((database, recordCount, manualIdCount) => { + return withLedger("manual", (database, recordCount, manualIdCount) => { reportManualHistoryCapacity(manualIdCount); const admitNewCallerId = () => { if (manualIdCount >= MAX_MANUAL_RESET_CREDIT_OPERATION_IDS) { diff --git a/src/config.ts b/src/config.ts index 41a5abc9bb..a437d4e0dc 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2683,12 +2683,7 @@ function configMutationDatabasePath(): string { return path; } -/** - * 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. - */ +/** Raised when an independent config-mutation transaction is requested recursively. */ export class NestedConfigMutationError extends Error { constructor() { super("prepareConfigMutationDatabasePathForWrite must not run inside withConfigMutationLockSync"); @@ -2696,6 +2691,14 @@ export class NestedConfigMutationError extends Error { } } +/** + * 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(); diff --git a/tests/codex-reset-credit-operation-ledger.test.ts b/tests/codex-reset-credit-operation-ledger.test.ts index bc2cc638dc..5d51d7a03e 100644 --- a/tests/codex-reset-credit-operation-ledger.test.ts +++ b/tests/codex-reset-credit-operation-ledger.test.ts @@ -110,6 +110,70 @@ 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)) { @@ -168,6 +232,8 @@ beforeEach(() => { 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(); } @@ -385,6 +451,40 @@ describe("Codex reset-credit operation ledger", () => { } }); + 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", @@ -795,6 +895,55 @@ describe("Codex reset-credit operation ledger", () => { .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"); @@ -907,6 +1056,10 @@ describe("Codex reset-credit operation ledger", () => { ...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 }, []>( From 0476e7e569366a597605235963cc006066812e10 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Tue, 18 Aug 2026 02:20:19 +0900 Subject: [PATCH 4/6] test(codex): harden reset-credit ledger invariants --- ...odex-reset-credit-operation-ledger.test.ts | 58 +++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/tests/codex-reset-credit-operation-ledger.test.ts b/tests/codex-reset-credit-operation-ledger.test.ts index 5d51d7a03e..85a73908a0 100644 --- a/tests/codex-reset-credit-operation-ledger.test.ts +++ b/tests/codex-reset-credit-operation-ledger.test.ts @@ -32,9 +32,10 @@ const GENERATION: CodexResetCreditRecoveryGeneration = { credentialGeneration: 4, exhaustionGeneration: 9, }; -const CHILD_READY_TIMEOUT_MS = 4_000; -const CHILD_EXIT_TIMEOUT_MS = 2_000; -const CONTENTION_TEST_TIMEOUT_MS = 15_000; +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, @@ -263,6 +264,18 @@ describe("Codex reset-credit operation ledger", () => { .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("snapshots recovery generations once and rejects inherited fields", () => { let credentialReads = 0; const accessorGeneration = { @@ -779,6 +792,38 @@ describe("Codex reset-credit operation ledger", () => { .toMatchObject({ kind: "execute", resumed: false }); }); + 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"); @@ -994,8 +1039,11 @@ describe("Codex reset-credit operation ledger", () => { }); try { await waitForPath(readyPath); - expect(openResetCreditOperation({ ...GENERATION, accountId: "pool-b" })) - .toEqual({ kind: "unavailable" }); + 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); } From bff35fcefc6cddb2066c2c60956209f7b151fab9 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Tue, 18 Aug 2026 02:26:59 +0900 Subject: [PATCH 5/6] fix(codex): reject partial reset-credit ledger loss --- src/codex/reset-credit-operation-ledger.ts | 29 ++++++- ...odex-reset-credit-operation-ledger.test.ts | 78 +++++++++++++++++++ 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/src/codex/reset-credit-operation-ledger.ts b/src/codex/reset-credit-operation-ledger.ts index 813a814dae..806437d241 100644 --- a/src/codex/reset-credit-operation-ledger.ts +++ b/src/codex/reset-credit-operation-ledger.ts @@ -584,7 +584,9 @@ function isExactPriorSchema(database: Database, schema: SchemaObjectRow): boolea } } -function assertCanonicalTable(database: Database): void { +type PrimaryTableInitialization = "created" | "migrated" | "existing"; + +function assertCanonicalTable(database: Database): PrimaryTableInitialization { const schemaRows = database.query(` SELECT type, name, tbl_name, sql FROM main.sqlite_schema @@ -592,24 +594,31 @@ function assertCanonicalTable(database: Database): void { 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): boolean { +function ensureManualIdTable(database: Database, allowCreate: boolean): boolean { const schemaRows = database.query(` SELECT type, name, tbl_name, sql FROM main.sqlite_schema @@ -619,6 +628,9 @@ function ensureManualIdTable(database: Database): boolean { `).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 @@ -640,7 +652,16 @@ function initializeTable( recordCount: number; manualIdCount: number; }> { - assertCanonicalTable(database); + 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"); @@ -659,7 +680,7 @@ function initializeTable( for (const id of ids) operationIds.add(id); } - const manualTableCreated = ensureManualIdTable(database); + const manualTableCreated = ensureManualIdTable(database, primaryInitialization !== "existing"); if (manualTableCreated) { for (const record of records.values()) { if (record.operationKind !== "manual") continue; diff --git a/tests/codex-reset-credit-operation-ledger.test.ts b/tests/codex-reset-credit-operation-ledger.test.ts index 85a73908a0..d3598d1833 100644 --- a/tests/codex-reset-credit-operation-ledger.test.ts +++ b/tests/codex-reset-credit-operation-ledger.test.ts @@ -276,6 +276,54 @@ describe("Codex reset-credit operation ledger", () => { 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 = { @@ -792,6 +840,25 @@ describe("Codex reset-credit operation ledger", () => { .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"); @@ -829,6 +896,17 @@ describe("Codex reset-credit operation ledger", () => { 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" }); }); From f39c8c8860628112760903d99e20d525f3440f40 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Fri, 21 Aug 2026 15:44:18 +0900 Subject: [PATCH 6/6] test(codex): prove reset-credit ledger rollback boundaries --- src/codex/reset-credit-operation-ledger.ts | 22 ++- ...odex-reset-credit-operation-ledger.test.ts | 170 +++++++++++++++++- 2 files changed, 190 insertions(+), 2 deletions(-) diff --git a/src/codex/reset-credit-operation-ledger.ts b/src/codex/reset-credit-operation-ledger.ts index 806437d241..aa240b2c7b 100644 --- a/src/codex/reset-credit-operation-ledger.ts +++ b/src/codex/reset-credit-operation-ledger.ts @@ -19,6 +19,8 @@ const MANUAL_RESET_CREDIT_HISTORY_HIGH_WATER_MARK = Math.ceil( MAX_MANUAL_RESET_CREDIT_OPERATION_IDS * 0.9, ); let reportedManualHistoryLevel = 0; +type ResetCreditOperationMigrationFaultForTests = "after_first_write" | null; +let migrationFaultForTests: ResetCreditOperationMigrationFaultForTests = null; const ACCOUNT_KEY_PATTERN = /^[0-9a-f]{64}$/; const TERMINAL_STATE_BY_CODE: Readonly[] { + const database = new Database(databasePath(), { create: true }); + try { + database.exec(kind === "legacy" ? LEGACY_OPERATION_SCHEMA_SQL : PRIOR_OPERATION_SCHEMA_SQL); + const key = createHash("sha256").update(`valid-migration-${kind}`).digest("hex"); + const operationId = fixtureOperationId(kind === "legacy" ? 0x24100 : 0x24101); + if (kind === "legacy") { + database.prepare(` + INSERT INTO reset_credit_operations VALUES (?, ?, ?, ?, 'ambiguous', NULL, 100, 200) + `).run(key, GENERATION.credentialGeneration, GENERATION.exhaustionGeneration, operationId); + } else { + database.prepare(` + INSERT INTO reset_credit_operations VALUES ( + ?, 'manual', NULL, NULL, ?, 'ambiguous', NULL, 100, 200 + ) + `).run(key, operationId); + } + return database.query, []>( + "SELECT * FROM reset_credit_operations ORDER BY account_key", + ).all(); + } finally { + database.close(); + } +} + +function appendTerminalManualHistory(startIndex: number, count: number): void { + 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 offset = 0; offset < count; offset += 1) { + const operationId = fixtureOperationId(startIndex + offset); + const key = createHash("sha256") + .update(`synthetic-terminal-manual-history-${startIndex + offset}`) + .digest("hex"); + insert.run(operationId, key, operationId); + } + database.exec("COMMIT"); + } 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)) { @@ -240,6 +292,10 @@ beforeEach(() => { finally { database.close(); } }); +afterEach(() => { + setResetCreditOperationMigrationFaultForTests(null); +}); + describe("Codex reset-credit operation ledger", () => { test("exports strict operation-id, generation-order, and nested-mutation contracts", () => { const operationId = fixtureOperationId(0xabc); @@ -513,6 +569,37 @@ describe("Codex reset-credit operation ledger", () => { }); for (const fixture of MIGRATION_REJECTION_FIXTURES) { + test(`rolls back ${fixture.label} migration after the first schema write`, () => { + const before = seedValidMigration(fixture.kind); + setResetCreditOperationMigrationFaultForTests("after_first_write"); + const result = fixture.kind === "legacy" + ? openResetCreditOperation({ ...GENERATION, accountId: "pool-migration-fault" }, 300) + : openManualResetCreditOperation({ + accountId: "pool-migration-fault", + chatgptAccountId: "chatgpt-migration-fault", + operationId: fixtureOperationId(0x24102), + }, 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 }, []>(` + SELECT name FROM main.sqlite_schema + WHERE type = 'table' AND name LIKE 'reset_credit_%' + ORDER BY name + `).all()).toEqual([{ name: "reset_credit_operations" }]); + } finally { + verifier.close(); + } + }); + for (const rejection of MIGRATION_REJECTION_CASES) { test(`refuses ${fixture.label} migration with ${rejection} without rewriting state`, () => { const before = seedRejectedMigration(fixture.kind, rejection); @@ -1196,6 +1283,87 @@ describe("Codex reset-credit operation ledger", () => { } }); + test("refuses a new alias for an active manual operation when identity history is full", () => { + const canonical = { + accountId: "pool-manual-active-cap", + chatgptAccountId: "chatgpt-manual-active-cap", + operationId: fixtureOperationId(0x27000), + }; + expect(openManualResetCreditOperation(canonical, 100)).toEqual({ + kind: "execute", + operationId: canonical.operationId, + resumed: false, + }); + appendTerminalManualHistory(0x28000, MAX_MANUAL_RESET_CREDIT_OPERATION_IDS - 1); + + expect(openManualResetCreditOperation(canonical, 200)).toEqual({ + kind: "execute", + operationId: canonical.operationId, + resumed: true, + }); + const alias = { ...canonical, operationId: fixtureOperationId(0x27001) }; + expect(openManualResetCreditOperation(alias, 300)).toEqual({ kind: "capacity" }); + + 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); + expect(verifier.query<{ count: number }, [string]>(` + SELECT COUNT(*) AS count FROM reset_credit_manual_operation_ids WHERE operation_id = ? + `).get(alias.operationId)?.count).toBe(0); + expect(verifier.query<{ joined_operation_id: string | null; updated_at: number }, []>(` + SELECT joined_operation_id, updated_at FROM reset_credit_operations + `).get()).toEqual({ joined_operation_id: null, updated_at: 100 }); + } finally { + verifier.close(); + } + expect(settleManualResetCreditOperation(canonical, "reset", 400)).toEqual({ kind: "updated" }); + }); + + test("fails closed without rewriting manual identity history at max plus one", () => { + const manual = { + accountId: "pool-manual-over-cap", + chatgptAccountId: "chatgpt-manual-over-cap", + operationId: fixtureOperationId(0x29000), + }; + expect(openManualResetCreditOperation(manual, 100)).toMatchObject({ kind: "execute" }); + expect(settleManualResetCreditOperation(manual, "reset", 200)).toEqual({ kind: "updated" }); + const recoveryGeneration = { ...GENERATION, accountId: "pool-recovery-over-manual-cap" }; + expect(openResetCreditOperation(recoveryGeneration, 300)) + .toMatchObject({ kind: "execute", resumed: false }); + + const before = new Database(databasePath(), { readonly: true }); + let operationRows: Record[]; + try { + operationRows = before.query, []>( + "SELECT * FROM reset_credit_operations ORDER BY account_key", + ).all(); + } finally { + before.close(); + } + appendTerminalManualHistory(0x2a000, MAX_MANUAL_RESET_CREDIT_OPERATION_IDS); + + expect(openManualResetCreditOperation(manual, 400)).toEqual({ kind: "unavailable" }); + expect(openResetCreditOperation(recoveryGeneration, 500)).toEqual({ kind: "unavailable" }); + + 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 + 1); + expect(verifier.query, []>( + "SELECT * FROM reset_credit_operations ORDER BY account_key", + ).all()).toEqual(operationRows); + expect(schemaHash(verifier.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 { + verifier.close(); + } + }); + test("admits existing accounts but refuses a new account at capacity", () => { expect(openResetCreditOperation({ ...GENERATION, accountId: "pool-0" })) .toMatchObject({ kind: "execute", resumed: false });