@bitkyc08/opencodex 2.36.0 → 2.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/bin/ocx.mjs +69 -10
  2. package/gui/dist/assets/index-C14iCj_Q.js +112 -0
  3. package/gui/dist/assets/index-D7PIz7_g.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/gui/dist/provider-icons/aside.svg +3 -0
  6. package/gui/dist/provider-icons/deepseek-harness.svg +3 -0
  7. package/gui/dist/provider-icons/oh-my-pi.svg +11 -0
  8. package/gui/dist/provider-icons/openclaw.svg +54 -0
  9. package/gui/dist/provider-icons/prime-agent.svg +21 -0
  10. package/gui/dist/provider-icons/zcode.svg +219 -0
  11. package/package.json +1 -1
  12. package/src/adapters/cursor/protobuf-request.ts +4 -1
  13. package/src/adapters/cursor/tool-definitions.ts +36 -4
  14. package/src/adapters/kiro-constants.ts +36 -2
  15. package/src/adapters/kiro.ts +13 -2
  16. package/src/cli/capabilities.ts +14 -0
  17. package/src/cli/claude.ts +12 -0
  18. package/src/cli/codex-cli-update.ts +96 -0
  19. package/src/cli/codex-shim-autorestore.ts +3 -0
  20. package/src/cli/export-command.ts +18 -17
  21. package/src/cli/help.ts +2 -2
  22. package/src/cli/index.ts +3 -2
  23. package/src/cli/launcher-context.ts +53 -2
  24. package/src/cli/opencode.ts +126 -33
  25. package/src/cli/registry.ts +16 -10
  26. package/src/cli/system-command.ts +6 -1
  27. package/src/clients/config-export.ts +293 -28
  28. package/src/codex/account-store.ts +10 -4
  29. package/src/codex/autostart-health.ts +3 -3
  30. package/src/codex/catalog/provider-fetch.ts +20 -1
  31. package/src/codex/catalog/sync.ts +4 -3
  32. package/src/codex/cli-install-provenance.ts +795 -0
  33. package/src/codex/convergence.ts +4 -3
  34. package/src/codex/credential-mutation-epoch.ts +11 -0
  35. package/src/codex/main-account.ts +2 -0
  36. package/src/codex/model-entitlements.ts +489 -30
  37. package/src/codex/native-profile-manager.ts +4 -0
  38. package/src/codex/reset-credit-operation-ledger.ts +1411 -0
  39. package/src/codex/reset-credit-recovery.ts +20 -2
  40. package/src/codex/shim.ts +204 -18
  41. package/src/codex/user-identity.ts +2 -1
  42. package/src/config/paths.ts +18 -3
  43. package/src/config.ts +23 -0
  44. package/src/generated/compatibility-version.json +84 -48
  45. package/src/integrations/registry.ts +112 -0
  46. package/src/integrations/state.ts +67 -5
  47. package/src/integrations/writer.ts +25 -9
  48. package/src/lib/bounded-subprocess.ts +36 -0
  49. package/src/lib/strict-semver.ts +47 -0
  50. package/src/lib/windows-elevation.ts +32 -1
  51. package/src/lib/windows-secret-acl.ts +47 -25
  52. package/src/lib/windows-service-mutation-lock.ts +133 -0
  53. package/src/lib/windows-user-principal.ts +15 -17
  54. package/src/responses/spill-store.ts +334 -29
  55. package/src/responses/state.ts +488 -7
  56. package/src/server/index.ts +4 -3
  57. package/src/server/lifecycle.ts +5 -1
  58. package/src/server/management/model-rows.ts +11 -2
  59. package/src/server/management/provider-routes.ts +4 -0
  60. package/src/server/management/system-restart.ts +5 -5
  61. package/src/server/management-api.ts +7 -2
  62. package/src/server/startup-action-control.ts +3 -2
  63. package/src/service.ts +594 -33
  64. package/src/sidecar/candidates.ts +1 -1
  65. package/src/update/codex-cli-update-launch-policy.d.mts +18 -0
  66. package/src/update/codex-cli-update-launch-policy.mjs +30 -0
  67. package/src/update/index.ts +3 -2
  68. package/src/update/job.ts +10 -11
  69. package/gui/dist/assets/index-Cy7Z_pl0.css +0 -1
  70. package/gui/dist/assets/index-DO8liQVL.js +0 -112
@@ -0,0 +1,1411 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { chmodSync } from "node:fs";
3
+ import { Database } from "bun:sqlite";
4
+ import { NestedConfigMutationError, prepareConfigMutationDatabasePathForWrite } from "../config";
5
+ import { initializeConfigGeneration } from "./generation";
6
+ import {
7
+ compareCodexResetCreditRecoveryGenerationOrder,
8
+ isCodexResetCreditOperationId,
9
+ snapshotCodexResetCreditRecoveryGeneration,
10
+ type CodexResetCreditConsumeCode,
11
+ type CodexResetCreditRecoveryGeneration,
12
+ type CodexReservedOperationId,
13
+ } from "./reset-credit-recovery";
14
+ import { isValidCodexAccountId, MAIN_CODEX_ACCOUNT_ID } from "./account-id";
15
+
16
+ export const MAX_RESET_CREDIT_OPERATION_ACCOUNTS = 128;
17
+ export const MAX_MANUAL_RESET_CREDIT_OPERATION_IDS = 4_096;
18
+ const MANUAL_RESET_CREDIT_HISTORY_HIGH_WATER_MARK = Math.ceil(
19
+ MAX_MANUAL_RESET_CREDIT_OPERATION_IDS * 0.9,
20
+ );
21
+ let reportedManualHistoryLevel = 0;
22
+ type ResetCreditOperationMigrationFaultForTests = "after_first_write" | null;
23
+ let migrationFaultForTests: ResetCreditOperationMigrationFaultForTests = null;
24
+ const ACCOUNT_KEY_PATTERN = /^[0-9a-f]{64}$/;
25
+ const TERMINAL_STATE_BY_CODE: Readonly<Record<
26
+ CodexResetCreditConsumeCode,
27
+ "confirmed" | "stopped"
28
+ >> = Object.freeze({
29
+ reset: "confirmed",
30
+ already_redeemed: "confirmed",
31
+ nothing_to_reset: "stopped",
32
+ no_credit: "stopped",
33
+ });
34
+ const STATES: ReadonlySet<string> = new Set(["pending", "ambiguous", "confirmed", "stopped"]);
35
+
36
+ type ResetCreditOperationState = "pending" | "ambiguous" | "confirmed" | "stopped";
37
+ type ResetCreditOperationKind = "recovery" | "manual";
38
+
39
+ type ResetCreditOperationRecord = Readonly<{
40
+ accountKey: string;
41
+ operationKind: ResetCreditOperationKind;
42
+ credentialGeneration?: number;
43
+ exhaustionGeneration?: number;
44
+ operationId: string;
45
+ joinedOperationId?: string;
46
+ state: ResetCreditOperationState;
47
+ code?: CodexResetCreditConsumeCode;
48
+ createdAt: number;
49
+ updatedAt: number;
50
+ }>;
51
+
52
+ type ResetCreditOperationRow = {
53
+ account_key: unknown;
54
+ operation_kind: unknown;
55
+ credential_generation: unknown;
56
+ exhaustion_generation: unknown;
57
+ operation_id: unknown;
58
+ joined_operation_id: unknown;
59
+ state: unknown;
60
+ code: unknown;
61
+ created_at: unknown;
62
+ updated_at: unknown;
63
+ };
64
+
65
+ type ManualResetCreditOperationIdRecord = Readonly<{
66
+ operationId: string;
67
+ accountKey: string;
68
+ canonicalOperationId: string;
69
+ terminalCode?: CodexResetCreditConsumeCode;
70
+ createdAt: number;
71
+ updatedAt: number;
72
+ }>;
73
+
74
+ type ManualResetCreditOperationIdRow = {
75
+ operation_id: unknown;
76
+ account_key: unknown;
77
+ canonical_operation_id: unknown;
78
+ terminal_code: unknown;
79
+ created_at: unknown;
80
+ updated_at: unknown;
81
+ };
82
+
83
+ export type OpenResetCreditOperationResult =
84
+ | Readonly<{ kind: "execute"; operationId: CodexReservedOperationId; resumed: boolean }>
85
+ | Readonly<{ kind: "terminal"; operationId: CodexReservedOperationId; code: CodexResetCreditConsumeCode }>
86
+ | Readonly<{ kind: "stale-generation" | "unresolved-prior-generation" | "capacity" | "unavailable" }>;
87
+
88
+ export type UpdateResetCreditOperationResult =
89
+ | Readonly<{ kind: "updated" }>
90
+ | Readonly<{ kind: "mismatch" | "unavailable" }>;
91
+
92
+ export type ManualResetCreditOperationIdentity = Readonly<{
93
+ accountId: string;
94
+ chatgptAccountId: string;
95
+ operationId: string;
96
+ }>;
97
+
98
+ export type OpenManualResetCreditOperationResult =
99
+ | Readonly<{ kind: "execute"; operationId: CodexReservedOperationId; resumed: boolean }>
100
+ | Readonly<{ kind: "terminal"; operationId: CodexReservedOperationId; code: CodexResetCreditConsumeCode }>
101
+ | Readonly<{ kind: "capacity" | "identity-mismatch" | "unavailable" }>;
102
+
103
+ const TABLE_NAME = "reset_credit_operations";
104
+ const CREATE_TABLE = `CREATE TABLE main.reset_credit_operations (
105
+ account_key TEXT PRIMARY KEY,
106
+ operation_kind TEXT NOT NULL CHECK (operation_kind IN ('recovery', 'manual')),
107
+ credential_generation INTEGER,
108
+ exhaustion_generation INTEGER,
109
+ operation_id TEXT NOT NULL,
110
+ joined_operation_id TEXT,
111
+ state TEXT NOT NULL,
112
+ code TEXT,
113
+ created_at INTEGER NOT NULL CHECK (created_at >= 0),
114
+ updated_at INTEGER NOT NULL CHECK (updated_at >= created_at),
115
+ CHECK (
116
+ (operation_kind = 'recovery'
117
+ AND credential_generation IS NOT NULL AND credential_generation >= 0
118
+ AND exhaustion_generation IS NOT NULL AND exhaustion_generation >= 0
119
+ AND joined_operation_id IS NULL)
120
+ OR
121
+ (operation_kind = 'manual'
122
+ AND credential_generation IS NULL AND exhaustion_generation IS NULL)
123
+ ),
124
+ CHECK (joined_operation_id IS NULL OR joined_operation_id <> operation_id)
125
+ ) STRICT, WITHOUT ROWID`;
126
+ const EXPECTED_SCHEMA_SQL = CREATE_TABLE.replace("main.", "");
127
+ const MANUAL_ID_TABLE_NAME = "reset_credit_manual_operation_ids";
128
+ const CREATE_MANUAL_ID_TABLE = `CREATE TABLE main.reset_credit_manual_operation_ids (
129
+ operation_id TEXT PRIMARY KEY,
130
+ account_key TEXT NOT NULL,
131
+ canonical_operation_id TEXT NOT NULL,
132
+ terminal_code TEXT CHECK (
133
+ terminal_code IS NULL OR terminal_code IN (
134
+ 'reset', 'already_redeemed', 'nothing_to_reset', 'no_credit'
135
+ )
136
+ ),
137
+ created_at INTEGER NOT NULL CHECK (created_at >= 0),
138
+ updated_at INTEGER NOT NULL CHECK (updated_at >= created_at)
139
+ ) STRICT, WITHOUT ROWID`;
140
+ const EXPECTED_MANUAL_ID_SCHEMA_SQL = CREATE_MANUAL_ID_TABLE.replace("main.", "");
141
+ const PRIOR_TABLE_NAME = "reset_credit_operations_legacy_v2";
142
+ const PRIOR_CREATE_TABLE = `CREATE TABLE reset_credit_operations (
143
+ account_key TEXT PRIMARY KEY,
144
+ operation_kind TEXT NOT NULL CHECK (operation_kind IN ('recovery', 'manual')),
145
+ credential_generation INTEGER,
146
+ exhaustion_generation INTEGER,
147
+ operation_id TEXT NOT NULL,
148
+ state TEXT NOT NULL,
149
+ code TEXT,
150
+ created_at INTEGER NOT NULL CHECK (created_at >= 0),
151
+ updated_at INTEGER NOT NULL CHECK (updated_at >= created_at),
152
+ CHECK (
153
+ (operation_kind = 'recovery'
154
+ AND credential_generation IS NOT NULL AND credential_generation >= 0
155
+ AND exhaustion_generation IS NOT NULL AND exhaustion_generation >= 0)
156
+ OR
157
+ (operation_kind = 'manual'
158
+ AND credential_generation IS NULL AND exhaustion_generation IS NULL)
159
+ )
160
+ ) STRICT, WITHOUT ROWID`;
161
+ const LEGACY_TABLE_NAME = "reset_credit_operations_legacy_v1";
162
+ const LEGACY_CREATE_TABLE = `CREATE TABLE reset_credit_operations (
163
+ account_key TEXT PRIMARY KEY,
164
+ credential_generation INTEGER NOT NULL CHECK (credential_generation >= 0),
165
+ exhaustion_generation INTEGER NOT NULL CHECK (exhaustion_generation >= 0),
166
+ operation_id TEXT NOT NULL,
167
+ state TEXT NOT NULL,
168
+ code TEXT,
169
+ created_at INTEGER NOT NULL CHECK (created_at >= 0),
170
+ updated_at INTEGER NOT NULL CHECK (updated_at >= created_at)
171
+ ) STRICT, WITHOUT ROWID`;
172
+ const SELECT_ALL = `
173
+ SELECT account_key, operation_kind, credential_generation,
174
+ exhaustion_generation, operation_id, joined_operation_id,
175
+ state, code, created_at, updated_at
176
+ FROM main.reset_credit_operations
177
+ ORDER BY account_key
178
+ LIMIT ${MAX_RESET_CREDIT_OPERATION_ACCOUNTS + 1}`;
179
+ const SELECT_BY_KEY = `
180
+ SELECT account_key, operation_kind, credential_generation,
181
+ exhaustion_generation, operation_id, joined_operation_id,
182
+ state, code, created_at, updated_at
183
+ FROM main.reset_credit_operations
184
+ WHERE account_key = ?
185
+ LIMIT 2`;
186
+ const SELECT_KEY_BY_OPERATION_ID = `
187
+ SELECT account_key FROM (
188
+ SELECT account_key
189
+ FROM main.reset_credit_operations
190
+ WHERE operation_id = ? OR joined_operation_id = ?
191
+ UNION
192
+ SELECT account_key
193
+ FROM main.reset_credit_manual_operation_ids
194
+ WHERE operation_id = ?
195
+ )
196
+ LIMIT 2`;
197
+ const SELECT_ALL_MANUAL_IDS = `
198
+ SELECT operation_id, account_key, canonical_operation_id,
199
+ terminal_code, created_at, updated_at
200
+ FROM main.reset_credit_manual_operation_ids
201
+ ORDER BY operation_id
202
+ LIMIT ${MAX_MANUAL_RESET_CREDIT_OPERATION_IDS + 1}`;
203
+ const SELECT_BOUNDED_MANUAL_ID_COUNT = `
204
+ SELECT COUNT(*) AS count
205
+ FROM (
206
+ SELECT 1
207
+ FROM main.reset_credit_manual_operation_ids
208
+ LIMIT ${MAX_MANUAL_RESET_CREDIT_OPERATION_IDS + 1}
209
+ )`;
210
+ const SELECT_DUPLICATE_RECOVERY_MANUAL_ID = `
211
+ SELECT operations.operation_id
212
+ FROM main.reset_credit_operations AS operations
213
+ JOIN main.reset_credit_manual_operation_ids AS manual_ids
214
+ ON manual_ids.operation_id = operations.operation_id
215
+ WHERE operations.operation_kind = 'recovery'
216
+ LIMIT 1`;
217
+ const SELECT_MANUAL_ID = `
218
+ SELECT operation_id, account_key, canonical_operation_id,
219
+ terminal_code, created_at, updated_at
220
+ FROM main.reset_credit_manual_operation_ids
221
+ WHERE operation_id = ?
222
+ LIMIT 2`;
223
+ const SELECT_MANUAL_IDS_BY_CANONICAL = `
224
+ SELECT operation_id, account_key, canonical_operation_id,
225
+ terminal_code, created_at, updated_at
226
+ FROM main.reset_credit_manual_operation_ids
227
+ WHERE account_key = ? AND canonical_operation_id = ?
228
+ ORDER BY operation_id
229
+ LIMIT ${MAX_MANUAL_RESET_CREDIT_OPERATION_IDS + 1}`;
230
+ const INSERT_MANUAL_ID = `
231
+ INSERT INTO main.reset_credit_manual_operation_ids (
232
+ operation_id, account_key, canonical_operation_id,
233
+ terminal_code, created_at, updated_at
234
+ ) VALUES (?, ?, ?, ?, ?, ?)`;
235
+ const SETTLE_MANUAL_IDS = `
236
+ UPDATE main.reset_credit_manual_operation_ids
237
+ SET terminal_code = ?, updated_at = ?
238
+ WHERE account_key = ? AND canonical_operation_id = ?
239
+ AND (terminal_code IS NULL OR terminal_code = ?)`;
240
+ const INSERT_RECORD = `
241
+ INSERT INTO main.reset_credit_operations (
242
+ account_key, operation_kind, credential_generation, exhaustion_generation,
243
+ operation_id, joined_operation_id, state, code, created_at, updated_at
244
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
245
+ const REPLACE_RECORD = `
246
+ UPDATE main.reset_credit_operations
247
+ SET operation_kind = ?, credential_generation = ?, exhaustion_generation = ?,
248
+ operation_id = ?, joined_operation_id = ?, state = ?, code = ?,
249
+ created_at = ?, updated_at = ?
250
+ WHERE account_key = ?`;
251
+ const UPDATE_RECORD = `
252
+ UPDATE main.reset_credit_operations
253
+ SET state = ?, code = ?, updated_at = ?
254
+ WHERE account_key = ? AND operation_kind = ? AND operation_id = ?
255
+ AND credential_generation IS ? AND exhaustion_generation IS ?`;
256
+ const JOIN_MANUAL_OPERATION = `
257
+ UPDATE main.reset_credit_operations
258
+ SET joined_operation_id = ?, updated_at = ?
259
+ WHERE account_key = ? AND operation_kind = 'manual' AND operation_id = ?
260
+ AND joined_operation_id IS NULL AND state IN ('pending', 'ambiguous')`;
261
+ const TOUCH_MANUAL_OPERATION = `
262
+ UPDATE main.reset_credit_operations
263
+ SET updated_at = ?
264
+ WHERE account_key = ? AND operation_kind = 'manual' AND operation_id = ?
265
+ AND joined_operation_id IS NOT NULL AND state IN ('pending', 'ambiguous')`;
266
+
267
+ type SchemaObjectRow = {
268
+ type: unknown;
269
+ name: unknown;
270
+ tbl_name: unknown;
271
+ sql: unknown;
272
+ };
273
+
274
+ type TableListRow = {
275
+ schema: unknown;
276
+ name: unknown;
277
+ type: unknown;
278
+ ncol: unknown;
279
+ wr: unknown;
280
+ strict: unknown;
281
+ };
282
+
283
+ type TableColumnRow = {
284
+ cid: unknown;
285
+ name: unknown;
286
+ type: unknown;
287
+ notnull: unknown;
288
+ dflt_value: unknown;
289
+ pk: unknown;
290
+ hidden: unknown;
291
+ };
292
+
293
+ const EXPECTED_COLUMNS = Object.freeze([
294
+ Object.freeze({ name: "account_key", type: "TEXT", notnull: 1, pk: 1 }),
295
+ Object.freeze({ name: "operation_kind", type: "TEXT", notnull: 1, pk: 0 }),
296
+ Object.freeze({ name: "credential_generation", type: "INTEGER", notnull: 0, pk: 0 }),
297
+ Object.freeze({ name: "exhaustion_generation", type: "INTEGER", notnull: 0, pk: 0 }),
298
+ Object.freeze({ name: "operation_id", type: "TEXT", notnull: 1, pk: 0 }),
299
+ Object.freeze({ name: "joined_operation_id", type: "TEXT", notnull: 0, pk: 0 }),
300
+ Object.freeze({ name: "state", type: "TEXT", notnull: 1, pk: 0 }),
301
+ Object.freeze({ name: "code", type: "TEXT", notnull: 0, pk: 0 }),
302
+ Object.freeze({ name: "created_at", type: "INTEGER", notnull: 1, pk: 0 }),
303
+ Object.freeze({ name: "updated_at", type: "INTEGER", notnull: 1, pk: 0 }),
304
+ ]);
305
+
306
+ const MANUAL_ID_COLUMNS = Object.freeze([
307
+ Object.freeze({ name: "operation_id", type: "TEXT", notnull: 1, pk: 1 }),
308
+ Object.freeze({ name: "account_key", type: "TEXT", notnull: 1, pk: 0 }),
309
+ Object.freeze({ name: "canonical_operation_id", type: "TEXT", notnull: 1, pk: 0 }),
310
+ Object.freeze({ name: "terminal_code", type: "TEXT", notnull: 0, pk: 0 }),
311
+ Object.freeze({ name: "created_at", type: "INTEGER", notnull: 1, pk: 0 }),
312
+ Object.freeze({ name: "updated_at", type: "INTEGER", notnull: 1, pk: 0 }),
313
+ ]);
314
+
315
+ const LEGACY_COLUMNS = Object.freeze([
316
+ Object.freeze({ name: "account_key", type: "TEXT", notnull: 1, pk: 1 }),
317
+ Object.freeze({ name: "credential_generation", type: "INTEGER", notnull: 1, pk: 0 }),
318
+ Object.freeze({ name: "exhaustion_generation", type: "INTEGER", notnull: 1, pk: 0 }),
319
+ Object.freeze({ name: "operation_id", type: "TEXT", notnull: 1, pk: 0 }),
320
+ Object.freeze({ name: "state", type: "TEXT", notnull: 1, pk: 0 }),
321
+ Object.freeze({ name: "code", type: "TEXT", notnull: 0, pk: 0 }),
322
+ Object.freeze({ name: "created_at", type: "INTEGER", notnull: 1, pk: 0 }),
323
+ Object.freeze({ name: "updated_at", type: "INTEGER", notnull: 1, pk: 0 }),
324
+ ]);
325
+ const PRIOR_COLUMNS = Object.freeze([
326
+ Object.freeze({ name: "account_key", type: "TEXT", notnull: 1, pk: 1 }),
327
+ Object.freeze({ name: "operation_kind", type: "TEXT", notnull: 1, pk: 0 }),
328
+ Object.freeze({ name: "credential_generation", type: "INTEGER", notnull: 0, pk: 0 }),
329
+ Object.freeze({ name: "exhaustion_generation", type: "INTEGER", notnull: 0, pk: 0 }),
330
+ Object.freeze({ name: "operation_id", type: "TEXT", notnull: 1, pk: 0 }),
331
+ Object.freeze({ name: "state", type: "TEXT", notnull: 1, pk: 0 }),
332
+ Object.freeze({ name: "code", type: "TEXT", notnull: 0, pk: 0 }),
333
+ Object.freeze({ name: "created_at", type: "INTEGER", notnull: 1, pk: 0 }),
334
+ Object.freeze({ name: "updated_at", type: "INTEGER", notnull: 1, pk: 0 }),
335
+ ]);
336
+
337
+ function accountKey(accountId: string): string {
338
+ return createHash("sha256").update(`codex-reset-credit-operation\0${accountId}`).digest("hex");
339
+ }
340
+
341
+ function validateManualAccountId(accountId: string): void {
342
+ if (accountId !== MAIN_CODEX_ACCOUNT_ID && !isValidCodexAccountId(accountId)) {
343
+ throw new TypeError("invalid manual reset-credit account");
344
+ }
345
+ }
346
+
347
+ function manualPhysicalAccountKey(chatgptAccountId: string): string {
348
+ const normalized = chatgptAccountId.trim();
349
+ if (!normalized) throw new TypeError("invalid manual reset-credit credential identity");
350
+ return createHash("sha256")
351
+ .update(`codex-reset-credit-manual-physical\0${normalized}`)
352
+ .digest("hex");
353
+ }
354
+
355
+ function isGenerationNumber(value: unknown): value is number {
356
+ return Number.isSafeInteger(value) && Number(value) >= 0;
357
+ }
358
+
359
+ function parseRecord(row: ResetCreditOperationRow | null): ResetCreditOperationRecord | undefined {
360
+ if (!row) return undefined;
361
+ const state = row.state;
362
+ const code = row.code;
363
+ const joinedOperationId = row.joined_operation_id;
364
+ if (typeof row.account_key !== "string" || !ACCOUNT_KEY_PATTERN.test(row.account_key)
365
+ || (row.operation_kind !== "recovery" && row.operation_kind !== "manual")
366
+ || !isCodexResetCreditOperationId(row.operation_id)
367
+ || (joinedOperationId !== null
368
+ && (!isCodexResetCreditOperationId(joinedOperationId) || joinedOperationId === row.operation_id))
369
+ || typeof state !== "string" || !STATES.has(state)
370
+ || !isGenerationNumber(row.created_at)
371
+ || !isGenerationNumber(row.updated_at)
372
+ || row.updated_at < row.created_at) {
373
+ return undefined;
374
+ }
375
+ const recovery = row.operation_kind === "recovery";
376
+ const manual = row.operation_kind === "manual";
377
+ if (recovery !== (isGenerationNumber(row.credential_generation)
378
+ && isGenerationNumber(row.exhaustion_generation))
379
+ || manual !== (row.credential_generation === null
380
+ && row.exhaustion_generation === null)
381
+ || (recovery && joinedOperationId !== null)) {
382
+ return undefined;
383
+ }
384
+ const terminal = state === "confirmed" || state === "stopped";
385
+ if (!terminal && code !== null) return undefined;
386
+ const terminalState = typeof code === "string"
387
+ && Object.prototype.hasOwnProperty.call(TERMINAL_STATE_BY_CODE, code)
388
+ ? TERMINAL_STATE_BY_CODE[code as CodexResetCreditConsumeCode]
389
+ : undefined;
390
+ if (terminal !== (terminalState !== undefined)) return undefined;
391
+ if (terminal && state !== terminalState) return undefined;
392
+ return Object.freeze({
393
+ accountKey: row.account_key,
394
+ operationKind: row.operation_kind,
395
+ ...(recovery
396
+ ? {
397
+ credentialGeneration: row.credential_generation as number,
398
+ exhaustionGeneration: row.exhaustion_generation as number,
399
+ }
400
+ : {}),
401
+ operationId: row.operation_id,
402
+ ...(joinedOperationId === null ? {} : { joinedOperationId }),
403
+ state: state as ResetCreditOperationState,
404
+ ...(terminal ? { code: code as CodexResetCreditConsumeCode } : {}),
405
+ createdAt: row.created_at,
406
+ updatedAt: row.updated_at,
407
+ });
408
+ }
409
+
410
+ function parseManualIdRecord(
411
+ row: ManualResetCreditOperationIdRow | null,
412
+ ): ManualResetCreditOperationIdRecord | undefined {
413
+ if (!row) return undefined;
414
+ const terminalCode = row.terminal_code;
415
+ if (!isCodexResetCreditOperationId(row.operation_id)
416
+ || typeof row.account_key !== "string" || !ACCOUNT_KEY_PATTERN.test(row.account_key)
417
+ || !isCodexResetCreditOperationId(row.canonical_operation_id)
418
+ || (terminalCode !== null
419
+ && (typeof terminalCode !== "string"
420
+ || !Object.prototype.hasOwnProperty.call(TERMINAL_STATE_BY_CODE, terminalCode)))
421
+ || !isGenerationNumber(row.created_at)
422
+ || !isGenerationNumber(row.updated_at)
423
+ || row.updated_at < row.created_at) {
424
+ return undefined;
425
+ }
426
+ return Object.freeze({
427
+ operationId: row.operation_id,
428
+ accountKey: row.account_key,
429
+ canonicalOperationId: row.canonical_operation_id,
430
+ ...(terminalCode === null ? {} : { terminalCode: terminalCode as CodexResetCreditConsumeCode }),
431
+ createdAt: row.created_at,
432
+ updatedAt: row.updated_at,
433
+ });
434
+ }
435
+
436
+ function assertColumnLayout(
437
+ database: Database,
438
+ tableName: string,
439
+ expectedColumns: readonly Readonly<{ name: string; type: string; notnull: number; pk: number }>[],
440
+ ): void {
441
+ const tableRows = database.query<TableListRow, []>("PRAGMA main.table_list").all()
442
+ .filter(row => row.name === tableName);
443
+ if (tableRows.length !== 1) throw new Error("invalid reset-credit operation ledger table");
444
+ const table = tableRows[0]!;
445
+ if (table.schema !== "main" || table.type !== "table" || table.ncol !== expectedColumns.length
446
+ || table.wr !== 1 || table.strict !== 1) {
447
+ throw new Error("invalid reset-credit operation ledger table");
448
+ }
449
+ const columns = database.query<TableColumnRow, []>(
450
+ `PRAGMA main.table_xinfo(${tableName})`,
451
+ ).all();
452
+ if (columns.length !== expectedColumns.length) {
453
+ throw new Error("invalid reset-credit operation ledger columns");
454
+ }
455
+ for (let index = 0; index < expectedColumns.length; index += 1) {
456
+ const actual = columns[index]!;
457
+ const expected = expectedColumns[index]!;
458
+ if (actual.cid !== index || actual.name !== expected.name || actual.type !== expected.type
459
+ || actual.notnull !== expected.notnull || actual.dflt_value !== null
460
+ || actual.pk !== expected.pk || actual.hidden !== 0) {
461
+ throw new Error("invalid reset-credit operation ledger columns");
462
+ }
463
+ }
464
+ }
465
+
466
+ function assertNoLedgerTriggers(database: Database, tableName: string): void {
467
+ const mainTrigger = database.query<{ name: unknown }, [string]>(`
468
+ SELECT name FROM main.sqlite_schema
469
+ WHERE type = 'trigger' AND tbl_name = ? COLLATE NOCASE LIMIT 1
470
+ `).get(tableName);
471
+ const tempTrigger = database.query<{ name: unknown }, [string]>(`
472
+ SELECT name FROM temp.sqlite_schema
473
+ WHERE type = 'trigger' AND tbl_name = ? COLLATE NOCASE LIMIT 1
474
+ `).get(tableName);
475
+ if (mainTrigger || tempTrigger) throw new Error("reset-credit operation ledger triggers are forbidden");
476
+ }
477
+
478
+ function failMigrationAfterFirstWriteForTests(): void {
479
+ if (migrationFaultForTests === "after_first_write") {
480
+ throw new Error("synthetic reset-credit operation migration failure");
481
+ }
482
+ }
483
+
484
+ /** @internal Test-only fault injection after the first transactional migration write. */
485
+ export function setResetCreditOperationMigrationFaultForTests(
486
+ fault: ResetCreditOperationMigrationFaultForTests,
487
+ ): void {
488
+ if (process.env.OCX_TEST_HOME_GUARD !== "1") {
489
+ throw new Error("reset-credit operation migration faults require the repository test preload");
490
+ }
491
+ migrationFaultForTests = fault;
492
+ }
493
+
494
+ function migrateLegacyTable(database: Database): void {
495
+ assertColumnLayout(database, TABLE_NAME, LEGACY_COLUMNS);
496
+ assertNoLedgerTriggers(database, TABLE_NAME);
497
+ const legacyRows = database.query<{
498
+ account_key: unknown;
499
+ credential_generation: unknown;
500
+ exhaustion_generation: unknown;
501
+ operation_id: unknown;
502
+ state: unknown;
503
+ code: unknown;
504
+ created_at: unknown;
505
+ updated_at: unknown;
506
+ }, []>(`
507
+ SELECT account_key, credential_generation, exhaustion_generation, operation_id,
508
+ state, code, created_at, updated_at
509
+ FROM main.reset_credit_operations
510
+ ORDER BY account_key
511
+ LIMIT ${MAX_RESET_CREDIT_OPERATION_ACCOUNTS + 1}
512
+ `).all();
513
+ if (legacyRows.length > MAX_RESET_CREDIT_OPERATION_ACCOUNTS) {
514
+ throw new Error("invalid reset-credit operation ledger capacity");
515
+ }
516
+ const keys = new Set<string>();
517
+ const operations = new Set<string>();
518
+ for (const row of legacyRows) {
519
+ const record = parseRecord({
520
+ ...row,
521
+ operation_kind: "recovery",
522
+ joined_operation_id: null,
523
+ });
524
+ if (!record || keys.has(record.accountKey) || operations.has(record.operationId)) {
525
+ throw new Error("invalid reset-credit operation ledger state");
526
+ }
527
+ keys.add(record.accountKey);
528
+ operations.add(record.operationId);
529
+ }
530
+ database.exec(`ALTER TABLE main.${TABLE_NAME} RENAME TO ${LEGACY_TABLE_NAME}`);
531
+ failMigrationAfterFirstWriteForTests();
532
+ database.exec(CREATE_TABLE);
533
+ database.exec(`
534
+ INSERT INTO main.${TABLE_NAME} (
535
+ account_key, operation_kind, credential_generation, exhaustion_generation,
536
+ operation_id, joined_operation_id, state, code, created_at, updated_at
537
+ )
538
+ SELECT account_key, 'recovery', credential_generation, exhaustion_generation,
539
+ operation_id, NULL, state, code, created_at, updated_at
540
+ FROM main.${LEGACY_TABLE_NAME}
541
+ `);
542
+ database.exec(`DROP TABLE main.${LEGACY_TABLE_NAME}`);
543
+ }
544
+
545
+ function migratePriorTable(database: Database): void {
546
+ assertColumnLayout(database, TABLE_NAME, PRIOR_COLUMNS);
547
+ assertNoLedgerTriggers(database, TABLE_NAME);
548
+ const priorRows = database.query<Omit<ResetCreditOperationRow, "joined_operation_id">, []>(`
549
+ SELECT account_key, operation_kind, credential_generation, exhaustion_generation,
550
+ operation_id, state, code, created_at, updated_at
551
+ FROM main.reset_credit_operations
552
+ ORDER BY account_key
553
+ LIMIT ${MAX_RESET_CREDIT_OPERATION_ACCOUNTS + 1}
554
+ `).all();
555
+ if (priorRows.length > MAX_RESET_CREDIT_OPERATION_ACCOUNTS) {
556
+ throw new Error("invalid reset-credit operation ledger capacity");
557
+ }
558
+ const keys = new Set<string>();
559
+ const operations = new Set<string>();
560
+ for (const row of priorRows) {
561
+ const record = parseRecord({ ...row, joined_operation_id: null });
562
+ if (!record || keys.has(record.accountKey) || operations.has(record.operationId)) {
563
+ throw new Error("invalid reset-credit operation ledger state");
564
+ }
565
+ keys.add(record.accountKey);
566
+ operations.add(record.operationId);
567
+ }
568
+ database.exec(`ALTER TABLE main.${TABLE_NAME} RENAME TO ${PRIOR_TABLE_NAME}`);
569
+ failMigrationAfterFirstWriteForTests();
570
+ database.exec(CREATE_TABLE);
571
+ database.exec(`
572
+ INSERT INTO main.${TABLE_NAME} (
573
+ account_key, operation_kind, credential_generation, exhaustion_generation,
574
+ operation_id, joined_operation_id, state, code, created_at, updated_at
575
+ )
576
+ SELECT account_key, operation_kind, credential_generation, exhaustion_generation,
577
+ operation_id, NULL, state, code, created_at, updated_at
578
+ FROM main.${PRIOR_TABLE_NAME}
579
+ `);
580
+ database.exec(`DROP TABLE main.${PRIOR_TABLE_NAME}`);
581
+ }
582
+
583
+ function isExactLegacySchema(database: Database, schema: SchemaObjectRow): boolean {
584
+ if (schema.type !== "table" || schema.name !== TABLE_NAME || schema.tbl_name !== TABLE_NAME
585
+ || schema.sql !== LEGACY_CREATE_TABLE) return false;
586
+ try {
587
+ assertColumnLayout(database, TABLE_NAME, LEGACY_COLUMNS);
588
+ assertNoLedgerTriggers(database, TABLE_NAME);
589
+ return true;
590
+ } catch {
591
+ return false;
592
+ }
593
+ }
594
+
595
+ function isExactPriorSchema(database: Database, schema: SchemaObjectRow): boolean {
596
+ if (schema.type !== "table" || schema.name !== TABLE_NAME || schema.tbl_name !== TABLE_NAME
597
+ || schema.sql !== PRIOR_CREATE_TABLE) return false;
598
+ try {
599
+ assertColumnLayout(database, TABLE_NAME, PRIOR_COLUMNS);
600
+ assertNoLedgerTriggers(database, TABLE_NAME);
601
+ return true;
602
+ } catch {
603
+ return false;
604
+ }
605
+ }
606
+
607
+ type PrimaryTableInitialization = "created" | "migrated" | "existing";
608
+
609
+ function assertCanonicalTable(database: Database): PrimaryTableInitialization {
610
+ const schemaRows = database.query<SchemaObjectRow, [string, string]>(`
611
+ SELECT type, name, tbl_name, sql
612
+ FROM main.sqlite_schema
613
+ WHERE name = ? COLLATE NOCASE OR tbl_name = ? COLLATE NOCASE
614
+ ORDER BY type, name
615
+ LIMIT 4
616
+ `).all(TABLE_NAME, TABLE_NAME);
617
+ let initialization: PrimaryTableInitialization;
618
+ if (schemaRows.length === 0) {
619
+ database.exec(CREATE_TABLE);
620
+ initialization = "created";
621
+ } else if (schemaRows.length === 1 && isExactLegacySchema(database, schemaRows[0]!)) {
622
+ migrateLegacyTable(database);
623
+ initialization = "migrated";
624
+ } else if (schemaRows.length === 1 && isExactPriorSchema(database, schemaRows[0]!)) {
625
+ migratePriorTable(database);
626
+ initialization = "migrated";
627
+ } else if (schemaRows.length !== 1
628
+ || schemaRows[0]?.type !== "table"
629
+ || schemaRows[0]?.name !== TABLE_NAME
630
+ || schemaRows[0]?.tbl_name !== TABLE_NAME
631
+ || schemaRows[0]?.sql !== EXPECTED_SCHEMA_SQL) {
632
+ throw new Error("invalid reset-credit operation ledger schema");
633
+ } else {
634
+ initialization = "existing";
635
+ }
636
+ assertColumnLayout(database, TABLE_NAME, EXPECTED_COLUMNS);
637
+ assertNoLedgerTriggers(database, TABLE_NAME);
638
+ return initialization;
639
+ }
640
+
641
+ function ensureManualIdTable(database: Database, allowCreate: boolean): boolean {
642
+ const schemaRows = database.query<SchemaObjectRow, [string, string]>(`
643
+ SELECT type, name, tbl_name, sql
644
+ FROM main.sqlite_schema
645
+ WHERE name = ? COLLATE NOCASE OR tbl_name = ? COLLATE NOCASE
646
+ ORDER BY type, name
647
+ LIMIT 4
648
+ `).all(MANUAL_ID_TABLE_NAME, MANUAL_ID_TABLE_NAME);
649
+ let created = false;
650
+ if (schemaRows.length === 0) {
651
+ if (!allowCreate) {
652
+ throw new Error("missing manual reset-credit operation identity schema");
653
+ }
654
+ database.exec(CREATE_MANUAL_ID_TABLE);
655
+ created = true;
656
+ } else if (schemaRows.length !== 1
657
+ || schemaRows[0]?.type !== "table"
658
+ || schemaRows[0]?.name !== MANUAL_ID_TABLE_NAME
659
+ || schemaRows[0]?.tbl_name !== MANUAL_ID_TABLE_NAME
660
+ || schemaRows[0]?.sql !== EXPECTED_MANUAL_ID_SCHEMA_SQL) {
661
+ throw new Error("invalid manual reset-credit operation identity schema");
662
+ }
663
+ assertColumnLayout(database, MANUAL_ID_TABLE_NAME, MANUAL_ID_COLUMNS);
664
+ assertNoLedgerTriggers(database, MANUAL_ID_TABLE_NAME);
665
+ return created;
666
+ }
667
+
668
+ function initializeTable(
669
+ database: Database,
670
+ validationScope: ResetCreditOperationKind,
671
+ ): Readonly<{
672
+ recordCount: number;
673
+ manualIdCount: number;
674
+ }> {
675
+ const manualSchemaPresentBefore = database.query<{ present: number }, [string, string]>(`
676
+ SELECT 1 AS present
677
+ FROM main.sqlite_schema
678
+ WHERE name = ? COLLATE NOCASE OR tbl_name = ? COLLATE NOCASE
679
+ LIMIT 1
680
+ `).get(MANUAL_ID_TABLE_NAME, MANUAL_ID_TABLE_NAME) !== null;
681
+ const primaryInitialization = assertCanonicalTable(database);
682
+ if (primaryInitialization !== "existing" && manualSchemaPresentBefore) {
683
+ throw new Error("invalid partial reset-credit operation ledger schema");
684
+ }
685
+ const rows = database.query<ResetCreditOperationRow, []>(SELECT_ALL).all();
686
+ if (rows.length > MAX_RESET_CREDIT_OPERATION_ACCOUNTS) {
687
+ throw new Error("invalid reset-credit operation ledger capacity");
688
+ }
689
+ const accountKeys = new Set<string>();
690
+ const operationIds = new Set<string>();
691
+ const records = new Map<string, ResetCreditOperationRecord>();
692
+ for (const row of rows) {
693
+ const record = parseRecord(row);
694
+ const ids = record ? [record.operationId, ...(record.joinedOperationId ? [record.joinedOperationId] : [])] : [];
695
+ if (!record || accountKeys.has(record.accountKey) || ids.some(id => operationIds.has(id))) {
696
+ throw new Error("invalid reset-credit operation ledger state");
697
+ }
698
+ accountKeys.add(record.accountKey);
699
+ records.set(record.accountKey, record);
700
+ for (const id of ids) operationIds.add(id);
701
+ }
702
+
703
+ const manualTableCreated = ensureManualIdTable(database, primaryInitialization !== "existing");
704
+ if (manualTableCreated) {
705
+ for (const record of records.values()) {
706
+ if (record.operationKind !== "manual") continue;
707
+ const ids = [record.operationId, ...(record.joinedOperationId ? [record.joinedOperationId] : [])];
708
+ for (const operationId of ids) {
709
+ insertManualIdRecord(database, Object.freeze({
710
+ operationId,
711
+ accountKey: record.accountKey,
712
+ canonicalOperationId: record.operationId,
713
+ ...(record.code === undefined ? {} : { terminalCode: record.code }),
714
+ createdAt: record.createdAt,
715
+ updatedAt: record.updatedAt,
716
+ }));
717
+ }
718
+ }
719
+ }
720
+
721
+ const manualIdCount = database.query<{ count: unknown }, []>(SELECT_BOUNDED_MANUAL_ID_COUNT)
722
+ .get()?.count;
723
+ if (typeof manualIdCount !== "number" || !Number.isSafeInteger(manualIdCount) || manualIdCount < 0
724
+ || manualIdCount > MAX_MANUAL_RESET_CREDIT_OPERATION_IDS) {
725
+ throw new Error("invalid manual reset-credit operation identity capacity");
726
+ }
727
+ if (validationScope === "recovery") {
728
+ if (database.query<{ operation_id: unknown }, []>(SELECT_DUPLICATE_RECOVERY_MANUAL_ID).get()) {
729
+ throw new Error("duplicate reset-credit operation ids");
730
+ }
731
+ return Object.freeze({ recordCount: rows.length, manualIdCount });
732
+ }
733
+
734
+ const manualRows = database.query<ManualResetCreditOperationIdRow, []>(SELECT_ALL_MANUAL_IDS).all();
735
+ if (manualRows.length !== manualIdCount) throw new Error("invalid manual reset-credit operation identity state");
736
+ const manualIds = new Map<string, ManualResetCreditOperationIdRecord>();
737
+ for (const row of manualRows) {
738
+ const record = parseManualIdRecord(row);
739
+ if (!record || manualIds.has(record.operationId)) {
740
+ throw new Error("invalid manual reset-credit operation identity state");
741
+ }
742
+ manualIds.set(record.operationId, record);
743
+ }
744
+ for (const record of manualIds.values()) {
745
+ const canonical = manualIds.get(record.canonicalOperationId);
746
+ if (!canonical || canonical.operationId !== canonical.canonicalOperationId
747
+ || canonical.accountKey !== record.accountKey
748
+ || canonical.terminalCode !== record.terminalCode) {
749
+ throw new Error("invalid manual reset-credit operation identity state");
750
+ }
751
+ if (record.terminalCode === undefined) {
752
+ const current = records.get(record.accountKey);
753
+ if (!current || current.operationKind !== "manual" || isTerminal(current)
754
+ || current.operationId !== record.canonicalOperationId) {
755
+ throw new Error("invalid manual reset-credit operation identity state");
756
+ }
757
+ }
758
+ }
759
+ for (const record of records.values()) {
760
+ if (record.operationKind === "recovery") {
761
+ if (manualIds.has(record.operationId)) {
762
+ throw new Error("duplicate reset-credit operation ids");
763
+ }
764
+ continue;
765
+ }
766
+ const expectedIds = [record.operationId, ...(record.joinedOperationId ? [record.joinedOperationId] : [])];
767
+ for (const operationId of expectedIds) {
768
+ const identity = manualIds.get(operationId);
769
+ if (!identity || identity.accountKey !== record.accountKey
770
+ || identity.canonicalOperationId !== record.operationId
771
+ || identity.terminalCode !== record.code) {
772
+ throw new Error("invalid manual reset-credit operation identity state");
773
+ }
774
+ }
775
+ }
776
+ return Object.freeze({ recordCount: rows.length, manualIdCount });
777
+ }
778
+
779
+ function readRecord(database: Database, key: string): ResetCreditOperationRecord | undefined {
780
+ const rows = database.query<ResetCreditOperationRow, [string]>(SELECT_BY_KEY).all(key);
781
+ if (rows.length > 1) throw new Error("duplicate reset-credit operation records");
782
+ const row = rows[0];
783
+ const record = parseRecord(row ?? null);
784
+ if (row && !record) throw new Error("invalid reset-credit operation record");
785
+ return record;
786
+ }
787
+
788
+ function readManualIdRecord(
789
+ database: Database,
790
+ operationId: string,
791
+ ): ManualResetCreditOperationIdRecord | undefined {
792
+ const rows = database.query<ManualResetCreditOperationIdRow, [string]>(SELECT_MANUAL_ID)
793
+ .all(operationId);
794
+ if (rows.length > 1) throw new Error("duplicate manual reset-credit operation ids");
795
+ const row = rows[0];
796
+ const record = parseManualIdRecord(row ?? null);
797
+ if (row && !record) throw new Error("invalid manual reset-credit operation identity");
798
+ return record;
799
+ }
800
+
801
+ function sameManualIdRecord(
802
+ left: ManualResetCreditOperationIdRecord,
803
+ right: ManualResetCreditOperationIdRecord,
804
+ ): boolean {
805
+ return left.operationId === right.operationId
806
+ && left.accountKey === right.accountKey
807
+ && left.canonicalOperationId === right.canonicalOperationId
808
+ && left.terminalCode === right.terminalCode
809
+ && left.createdAt === right.createdAt
810
+ && left.updatedAt === right.updatedAt;
811
+ }
812
+
813
+ function assertStoredManualIdRecord(
814
+ database: Database,
815
+ expected: ManualResetCreditOperationIdRecord,
816
+ ): void {
817
+ const stored = readManualIdRecord(database, expected.operationId);
818
+ if (!stored || !sameManualIdRecord(stored, expected)) {
819
+ throw new Error("manual reset-credit operation identity write did not persist");
820
+ }
821
+ }
822
+
823
+ function insertManualIdRecord(
824
+ database: Database,
825
+ record: ManualResetCreditOperationIdRecord,
826
+ ): void {
827
+ const result = database.query(INSERT_MANUAL_ID).run(
828
+ record.operationId,
829
+ record.accountKey,
830
+ record.canonicalOperationId,
831
+ record.terminalCode ?? null,
832
+ record.createdAt,
833
+ record.updatedAt,
834
+ );
835
+ if (result.changes !== 1) throw new Error("manual reset-credit operation identity insert failed");
836
+ assertStoredManualIdRecord(database, record);
837
+ }
838
+
839
+ function operationOwner(database: Database, operationId: string): string | undefined {
840
+ const rows = database.query<{ account_key: unknown }, [string, string, string]>(SELECT_KEY_BY_OPERATION_ID)
841
+ .all(operationId, operationId, operationId);
842
+ if (rows.length > 1) throw new Error("duplicate reset-credit operation ids");
843
+ const owner = rows[0]?.account_key;
844
+ if (owner !== undefined && (typeof owner !== "string" || !ACCOUNT_KEY_PATTERN.test(owner))) {
845
+ throw new Error("invalid reset-credit operation owner");
846
+ }
847
+ return owner;
848
+ }
849
+
850
+ function sameRecord(left: ResetCreditOperationRecord, right: ResetCreditOperationRecord): boolean {
851
+ return left.accountKey === right.accountKey
852
+ && left.operationKind === right.operationKind
853
+ && left.credentialGeneration === right.credentialGeneration
854
+ && left.exhaustionGeneration === right.exhaustionGeneration
855
+ && left.operationId === right.operationId
856
+ && left.joinedOperationId === right.joinedOperationId
857
+ && left.state === right.state
858
+ && left.code === right.code
859
+ && left.createdAt === right.createdAt
860
+ && left.updatedAt === right.updatedAt;
861
+ }
862
+
863
+ function assertStoredRecord(
864
+ database: Database,
865
+ expected: ResetCreditOperationRecord,
866
+ ): void {
867
+ const stored = readRecord(database, expected.accountKey);
868
+ if (!stored || !sameRecord(stored, expected)) {
869
+ throw new Error("reset-credit operation write did not persist the expected record");
870
+ }
871
+ }
872
+
873
+ function compareGeneration(
874
+ record: ResetCreditOperationRecord,
875
+ generation: CodexResetCreditRecoveryGeneration,
876
+ ): -1 | 0 | 1 {
877
+ return compareCodexResetCreditRecoveryGenerationOrder({
878
+ accountId: generation.accountId,
879
+ credentialGeneration: record.credentialGeneration!,
880
+ exhaustionGeneration: record.exhaustionGeneration!,
881
+ }, generation);
882
+ }
883
+
884
+ function isTerminal(record: ResetCreditOperationRecord): boolean {
885
+ return record.state === "confirmed" || record.state === "stopped";
886
+ }
887
+
888
+ function isThenable(value: unknown): boolean {
889
+ return (typeof value === "object" && value !== null) || typeof value === "function"
890
+ ? typeof (value as { then?: unknown }).then === "function"
891
+ : false;
892
+ }
893
+
894
+ type Synchronous<T> = T extends PromiseLike<unknown> ? never : T;
895
+
896
+ function withLedger<T>(validationScope: ResetCreditOperationKind, operation: (
897
+ database: Database,
898
+ recordCount: number,
899
+ manualIdCount: number,
900
+ ) => Synchronous<T>): T {
901
+ const path = prepareConfigMutationDatabasePathForWrite();
902
+ let database: Database | undefined;
903
+ let transactionOpen = false;
904
+ try {
905
+ database = new Database(path, { create: true });
906
+ try { chmodSync(path, 0o600); } catch { /* platform may ignore chmod */ }
907
+ database.exec("PRAGMA trusted_schema = OFF; PRAGMA busy_timeout = 0; PRAGMA synchronous = FULL; BEGIN IMMEDIATE");
908
+ transactionOpen = true;
909
+ initializeConfigGeneration(database);
910
+ const counts = initializeTable(database, validationScope);
911
+ const value = operation(database, counts.recordCount, counts.manualIdCount);
912
+ if (isThenable(value) || !database.inTransaction) {
913
+ throw new Error("reset-credit operation ledger work escaped its synchronous transaction");
914
+ }
915
+ database.exec("COMMIT");
916
+ transactionOpen = false;
917
+ return value;
918
+ } catch (error) {
919
+ if (transactionOpen) {
920
+ try { database?.exec("ROLLBACK"); } catch { /* close still releases the write lock */ }
921
+ transactionOpen = false;
922
+ }
923
+ throw error;
924
+ } finally {
925
+ try { database?.close(); } catch { /* operation already completed */ }
926
+ }
927
+ }
928
+
929
+ function isLedgerBusyError(error: unknown): boolean {
930
+ const code = error && typeof error === "object" && "code" in error
931
+ ? String((error as { code?: unknown }).code)
932
+ : "";
933
+ const message = error instanceof Error ? error.message : "";
934
+ return code === "SQLITE_BUSY" || code === "SQLITE_LOCKED"
935
+ || /database (?:is|table is) locked/i.test(message);
936
+ }
937
+
938
+ function warnLedgerUnavailable(error: unknown): void {
939
+ if (isLedgerBusyError(error)) return;
940
+ const nested = error instanceof NestedConfigMutationError;
941
+ // Native SQLite and filesystem errors may contain absolute, account-bearing
942
+ // paths. Keep this warning categorical rather than forwarding error.message.
943
+ console.warn(nested
944
+ ? "[opencodex] Reset-credit operation ledger refused a nested config mutation."
945
+ : "[opencodex] Reset-credit operation ledger is unavailable.");
946
+ }
947
+
948
+ function reportManualHistoryCapacity(count: number): void {
949
+ const level = count >= MAX_MANUAL_RESET_CREDIT_OPERATION_IDS
950
+ ? MAX_MANUAL_RESET_CREDIT_OPERATION_IDS
951
+ : count >= MANUAL_RESET_CREDIT_HISTORY_HIGH_WATER_MARK
952
+ ? MANUAL_RESET_CREDIT_HISTORY_HIGH_WATER_MARK
953
+ : 0;
954
+ if (level === 0 || level <= reportedManualHistoryLevel) return;
955
+ reportedManualHistoryLevel = level;
956
+ try {
957
+ console.warn(
958
+ `[opencodex] Reset-credit manual operation history is at ${count}/${MAX_MANUAL_RESET_CREDIT_OPERATION_IDS} entries${
959
+ level === MAX_MANUAL_RESET_CREDIT_OPERATION_IDS
960
+ ? "; new manual operation IDs, including aliases, are disabled until a maintainer expands capacity or applies an approved retirement policy."
961
+ : "."
962
+ }`,
963
+ );
964
+ } catch {
965
+ // Count-only operational reporting must never weaken the fail-closed result.
966
+ }
967
+ }
968
+
969
+ /**
970
+ * Throws `TypeError` for a malformed generation or timestamp. Runtime storage
971
+ * and contention failures are represented by a result kind.
972
+ */
973
+ export function openResetCreditOperation(
974
+ generation: CodexResetCreditRecoveryGeneration,
975
+ now = Date.now(),
976
+ ): OpenResetCreditOperationResult {
977
+ const generationSnapshot = snapshotCodexResetCreditRecoveryGeneration(generation);
978
+ if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp");
979
+ try {
980
+ return withLedger("recovery", (database, recordCount) => {
981
+ const key = accountKey(generationSnapshot.accountId);
982
+ const current = readRecord(database, key);
983
+ if (current) {
984
+ if (current.operationKind !== "recovery") {
985
+ return Object.freeze({ kind: "unresolved-prior-generation" as const });
986
+ }
987
+ const comparison = compareGeneration(current, generationSnapshot);
988
+ if (comparison > 0) return Object.freeze({ kind: "stale-generation" as const });
989
+ if (comparison === 0) {
990
+ if (isTerminal(current)) {
991
+ return Object.freeze({
992
+ kind: "terminal" as const,
993
+ operationId: current.operationId as CodexReservedOperationId,
994
+ code: current.code!,
995
+ });
996
+ }
997
+ return Object.freeze({
998
+ kind: "execute" as const,
999
+ operationId: current.operationId as CodexReservedOperationId,
1000
+ resumed: true,
1001
+ });
1002
+ }
1003
+ if (!isTerminal(current)) return Object.freeze({ kind: "unresolved-prior-generation" as const });
1004
+ } else if (recordCount >= MAX_RESET_CREDIT_OPERATION_ACCOUNTS) {
1005
+ return Object.freeze({ kind: "capacity" as const });
1006
+ }
1007
+
1008
+ const operationId = randomUUID();
1009
+ if (!isCodexResetCreditOperationId(operationId)) throw new Error("runtime generated invalid UUID");
1010
+ if (operationOwner(database, operationId) !== undefined) {
1011
+ throw new Error("duplicate reset-credit operation ids");
1012
+ }
1013
+ const values = [
1014
+ "recovery",
1015
+ generationSnapshot.credentialGeneration,
1016
+ generationSnapshot.exhaustionGeneration,
1017
+ operationId,
1018
+ null,
1019
+ "pending",
1020
+ null,
1021
+ now,
1022
+ now,
1023
+ ] as const;
1024
+ const result = current
1025
+ ? database.query(REPLACE_RECORD).run(...values, key)
1026
+ : database.query(INSERT_RECORD).run(key, ...values);
1027
+ if (result.changes !== 1) throw new Error("reset-credit operation reservation lost ownership");
1028
+ assertStoredRecord(database, Object.freeze({
1029
+ accountKey: key,
1030
+ operationKind: "recovery",
1031
+ credentialGeneration: generationSnapshot.credentialGeneration,
1032
+ exhaustionGeneration: generationSnapshot.exhaustionGeneration,
1033
+ operationId,
1034
+ state: "pending",
1035
+ createdAt: now,
1036
+ updatedAt: now,
1037
+ }));
1038
+ return Object.freeze({
1039
+ kind: "execute" as const,
1040
+ operationId: operationId as CodexReservedOperationId,
1041
+ resumed: false,
1042
+ });
1043
+ });
1044
+ } catch (error) {
1045
+ warnLedgerUnavailable(error);
1046
+ return Object.freeze({ kind: "unavailable" });
1047
+ }
1048
+ }
1049
+
1050
+ function updateOperation(
1051
+ owner: Readonly<{
1052
+ accountKey: string;
1053
+ operationKind: ResetCreditOperationKind;
1054
+ credentialGeneration?: number;
1055
+ exhaustionGeneration?: number;
1056
+ }>,
1057
+ operationId: string,
1058
+ update: (record: ResetCreditOperationRecord) => ResetCreditOperationRecord | undefined,
1059
+ afterWrite?: (database: Database, updated: ResetCreditOperationRecord) => void,
1060
+ ): UpdateResetCreditOperationResult {
1061
+ if (!isCodexResetCreditOperationId(operationId)) return Object.freeze({ kind: "mismatch" });
1062
+ try {
1063
+ return withLedger(owner.operationKind, database => {
1064
+ const current = readRecord(database, owner.accountKey);
1065
+ if (!current
1066
+ || current.operationKind !== owner.operationKind
1067
+ || current.credentialGeneration !== owner.credentialGeneration
1068
+ || current.exhaustionGeneration !== owner.exhaustionGeneration
1069
+ || current.operationId !== operationId) {
1070
+ return Object.freeze({ kind: "mismatch" as const });
1071
+ }
1072
+ const updated = update(current);
1073
+ if (!updated) return Object.freeze({ kind: "mismatch" as const });
1074
+ const result = database.query(UPDATE_RECORD).run(
1075
+ updated.state,
1076
+ updated.code ?? null,
1077
+ updated.updatedAt,
1078
+ owner.accountKey,
1079
+ owner.operationKind,
1080
+ operationId,
1081
+ owner.credentialGeneration ?? null,
1082
+ owner.exhaustionGeneration ?? null,
1083
+ );
1084
+ if (result.changes !== 1) throw new Error("reset-credit operation update lost ownership");
1085
+ assertStoredRecord(database, updated);
1086
+ afterWrite?.(database, updated);
1087
+ return Object.freeze({ kind: "updated" as const });
1088
+ });
1089
+ } catch (error) {
1090
+ warnLedgerUnavailable(error);
1091
+ return Object.freeze({ kind: "unavailable" });
1092
+ }
1093
+ }
1094
+
1095
+ /**
1096
+ * Throws `TypeError` for a malformed generation or timestamp. An invalid
1097
+ * operation id returns `mismatch`; runtime storage failures return `unavailable`.
1098
+ */
1099
+ export function markResetCreditOperationAmbiguous(
1100
+ generation: CodexResetCreditRecoveryGeneration,
1101
+ operationId: string,
1102
+ now = Date.now(),
1103
+ ): UpdateResetCreditOperationResult {
1104
+ if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp");
1105
+ const generationSnapshot = snapshotCodexResetCreditRecoveryGeneration(generation);
1106
+ return updateOperation({
1107
+ accountKey: accountKey(generationSnapshot.accountId),
1108
+ operationKind: "recovery",
1109
+ credentialGeneration: generationSnapshot.credentialGeneration,
1110
+ exhaustionGeneration: generationSnapshot.exhaustionGeneration,
1111
+ }, operationId, record => {
1112
+ if (isTerminal(record)) return undefined;
1113
+ return Object.freeze({
1114
+ ...record,
1115
+ state: "ambiguous",
1116
+ code: undefined,
1117
+ updatedAt: Math.max(record.updatedAt, now),
1118
+ });
1119
+ });
1120
+ }
1121
+
1122
+ /**
1123
+ * Throws `TypeError` for a malformed generation or timestamp. An invalid
1124
+ * operation id or non-terminal code returns `mismatch`; runtime storage
1125
+ * failures return `unavailable`.
1126
+ */
1127
+ export function settleResetCreditOperation(
1128
+ generation: CodexResetCreditRecoveryGeneration,
1129
+ operationId: string,
1130
+ code: CodexResetCreditConsumeCode,
1131
+ now = Date.now(),
1132
+ ): UpdateResetCreditOperationResult {
1133
+ if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp");
1134
+ if (!Object.prototype.hasOwnProperty.call(TERMINAL_STATE_BY_CODE, code)) {
1135
+ return Object.freeze({ kind: "mismatch" });
1136
+ }
1137
+ const generationSnapshot = snapshotCodexResetCreditRecoveryGeneration(generation);
1138
+ return updateOperation({
1139
+ accountKey: accountKey(generationSnapshot.accountId),
1140
+ operationKind: "recovery",
1141
+ credentialGeneration: generationSnapshot.credentialGeneration,
1142
+ exhaustionGeneration: generationSnapshot.exhaustionGeneration,
1143
+ }, operationId, record => {
1144
+ if (isTerminal(record)) return record.code === code ? record : undefined;
1145
+ return Object.freeze({
1146
+ ...record,
1147
+ state: TERMINAL_STATE_BY_CODE[code],
1148
+ code,
1149
+ updatedAt: Math.max(record.updatedAt, now),
1150
+ });
1151
+ });
1152
+ }
1153
+
1154
+ function snapshotManualIdentity(identity: ManualResetCreditOperationIdentity): {
1155
+ accountKey: string;
1156
+ operationId: string;
1157
+ } {
1158
+ if (!identity || typeof identity !== "object" || Array.isArray(identity)) {
1159
+ throw new TypeError("manual reset-credit identity must be an object");
1160
+ }
1161
+ const value = identity as unknown as Record<string, unknown>;
1162
+ const hasOwn = Object.prototype.hasOwnProperty;
1163
+ if (!hasOwn.call(value, "accountId")
1164
+ || !hasOwn.call(value, "chatgptAccountId")
1165
+ || !hasOwn.call(value, "operationId")) {
1166
+ throw new TypeError("manual reset-credit identity fields must be own properties");
1167
+ }
1168
+ const accountId = value.accountId;
1169
+ const chatgptAccountId = value.chatgptAccountId;
1170
+ const operationId = value.operationId;
1171
+ if (!isCodexResetCreditOperationId(operationId)) {
1172
+ throw new TypeError("invalid manual reset-credit operation id");
1173
+ }
1174
+ if (typeof accountId !== "string") throw new TypeError("invalid manual reset-credit account");
1175
+ validateManualAccountId(accountId);
1176
+ if (typeof chatgptAccountId !== "string") {
1177
+ throw new TypeError("invalid manual reset-credit credential identity");
1178
+ }
1179
+ return Object.freeze({
1180
+ accountKey: manualPhysicalAccountKey(chatgptAccountId),
1181
+ operationId,
1182
+ });
1183
+ }
1184
+
1185
+ /**
1186
+ * Reserve or restore one explicit manual redemption intent.
1187
+ *
1188
+ * Throws `TypeError` for malformed identity fields or `now`; these are caller
1189
+ * contract violations. Durable-state and runtime failures return a result kind.
1190
+ */
1191
+ export function openManualResetCreditOperation(
1192
+ identity: ManualResetCreditOperationIdentity,
1193
+ now = Date.now(),
1194
+ ): OpenManualResetCreditOperationResult {
1195
+ const owner = snapshotManualIdentity(identity);
1196
+ if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp");
1197
+ try {
1198
+ return withLedger("manual", (database, recordCount, manualIdCount) => {
1199
+ reportManualHistoryCapacity(manualIdCount);
1200
+ const admitNewCallerId = () => {
1201
+ if (manualIdCount >= MAX_MANUAL_RESET_CREDIT_OPERATION_IDS) {
1202
+ return Object.freeze({ kind: "capacity" as const });
1203
+ }
1204
+ const existingOwner = operationOwner(database, owner.operationId);
1205
+ if (existingOwner !== undefined) {
1206
+ return Object.freeze({
1207
+ kind: existingOwner === owner.accountKey ? "unavailable" as const : "identity-mismatch" as const,
1208
+ });
1209
+ }
1210
+ return undefined;
1211
+ };
1212
+
1213
+ const reserve = (replaceCurrent: boolean): OpenManualResetCreditOperationResult => {
1214
+ const rejected = admitNewCallerId();
1215
+ if (rejected) return rejected;
1216
+
1217
+ const record: ResetCreditOperationRecord = Object.freeze({
1218
+ accountKey: owner.accountKey,
1219
+ operationKind: "manual",
1220
+ operationId: owner.operationId,
1221
+ state: "pending",
1222
+ createdAt: now,
1223
+ updatedAt: now,
1224
+ });
1225
+ const values = [
1226
+ "manual",
1227
+ null,
1228
+ null,
1229
+ owner.operationId,
1230
+ null,
1231
+ "pending",
1232
+ null,
1233
+ now,
1234
+ now,
1235
+ ] as const;
1236
+ const result = replaceCurrent
1237
+ ? database.query(REPLACE_RECORD).run(...values, owner.accountKey)
1238
+ : database.query(INSERT_RECORD).run(owner.accountKey, ...values);
1239
+ if (result.changes !== 1) throw new Error("manual reset-credit reservation lost ownership");
1240
+ assertStoredRecord(database, record);
1241
+ insertManualIdRecord(database, Object.freeze({
1242
+ operationId: owner.operationId,
1243
+ accountKey: owner.accountKey,
1244
+ canonicalOperationId: owner.operationId,
1245
+ createdAt: now,
1246
+ updatedAt: now,
1247
+ }));
1248
+ return Object.freeze({
1249
+ kind: "execute" as const,
1250
+ operationId: owner.operationId as CodexReservedOperationId,
1251
+ resumed: false,
1252
+ });
1253
+ };
1254
+
1255
+ const knownIdentity = readManualIdRecord(database, owner.operationId);
1256
+ if (knownIdentity) {
1257
+ if (knownIdentity.accountKey !== owner.accountKey) {
1258
+ return Object.freeze({ kind: "identity-mismatch" as const });
1259
+ }
1260
+ if (knownIdentity.terminalCode !== undefined) {
1261
+ return Object.freeze({
1262
+ kind: "terminal" as const,
1263
+ operationId: knownIdentity.canonicalOperationId as CodexReservedOperationId,
1264
+ code: knownIdentity.terminalCode,
1265
+ });
1266
+ }
1267
+ const current = readRecord(database, owner.accountKey);
1268
+ if (!current || current.operationKind !== "manual" || isTerminal(current)
1269
+ || current.operationId !== knownIdentity.canonicalOperationId) {
1270
+ throw new Error("manual reset-credit operation identity lost its active owner");
1271
+ }
1272
+ return Object.freeze({
1273
+ kind: "execute" as const,
1274
+ operationId: current.operationId as CodexReservedOperationId,
1275
+ resumed: true,
1276
+ });
1277
+ }
1278
+
1279
+ const current = readRecord(database, owner.accountKey);
1280
+ if (current) {
1281
+ if (current.operationKind !== "manual") {
1282
+ return Object.freeze({ kind: "unavailable" as const });
1283
+ }
1284
+ if (!isTerminal(current)) {
1285
+ const rejected = admitNewCallerId();
1286
+ if (rejected) return rejected;
1287
+ insertManualIdRecord(database, Object.freeze({
1288
+ operationId: owner.operationId,
1289
+ accountKey: owner.accountKey,
1290
+ canonicalOperationId: current.operationId,
1291
+ createdAt: now,
1292
+ updatedAt: now,
1293
+ }));
1294
+ const joined: ResetCreditOperationRecord = Object.freeze({
1295
+ ...current,
1296
+ ...(current.joinedOperationId === undefined
1297
+ ? { joinedOperationId: owner.operationId }
1298
+ : {}),
1299
+ updatedAt: Math.max(current.updatedAt, now),
1300
+ });
1301
+ const result = current.joinedOperationId === undefined
1302
+ ? database.query(JOIN_MANUAL_OPERATION).run(
1303
+ owner.operationId,
1304
+ joined.updatedAt,
1305
+ owner.accountKey,
1306
+ current.operationId,
1307
+ )
1308
+ : database.query(TOUCH_MANUAL_OPERATION).run(
1309
+ joined.updatedAt,
1310
+ owner.accountKey,
1311
+ current.operationId,
1312
+ );
1313
+ if (result.changes !== 1) {
1314
+ throw new Error("manual reset-credit join lost ownership");
1315
+ }
1316
+ assertStoredRecord(database, joined);
1317
+ // The upstream request keeps the original durable id. Every caller id
1318
+ // is retained in the identity history; the first alias is also kept on
1319
+ // the current row for compatibility with the previous schema.
1320
+ return Object.freeze({
1321
+ kind: "execute" as const,
1322
+ operationId: current.operationId as CodexReservedOperationId,
1323
+ resumed: true,
1324
+ });
1325
+ }
1326
+ // Deliberate: a distinct caller id after a settled intent represents a
1327
+ // new explicit redemption. Prior ids remain immutable in the history,
1328
+ // so a delayed retry can never be reclassified as this new intent.
1329
+ return reserve(true);
1330
+ }
1331
+ if (recordCount >= MAX_RESET_CREDIT_OPERATION_ACCOUNTS) {
1332
+ return Object.freeze({ kind: "capacity" as const });
1333
+ }
1334
+ return reserve(false);
1335
+ });
1336
+ } catch (error) {
1337
+ warnLedgerUnavailable(error);
1338
+ return Object.freeze({ kind: "unavailable" });
1339
+ }
1340
+ }
1341
+
1342
+ /**
1343
+ * Mark a reserved manual redemption as ambiguous.
1344
+ *
1345
+ * Throws `TypeError` for malformed identity fields or `now`. A missing or
1346
+ * incompatible durable record returns the existing result kind.
1347
+ */
1348
+ export function markManualResetCreditOperationAmbiguous(
1349
+ identity: ManualResetCreditOperationIdentity,
1350
+ now = Date.now(),
1351
+ ): UpdateResetCreditOperationResult {
1352
+ const owner = snapshotManualIdentity(identity);
1353
+ if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp");
1354
+ return updateOperation({ accountKey: owner.accountKey, operationKind: "manual" }, owner.operationId, record => {
1355
+ if (isTerminal(record)) return undefined;
1356
+ return Object.freeze({ ...record, state: "ambiguous", code: undefined, updatedAt: Math.max(record.updatedAt, now) });
1357
+ });
1358
+ }
1359
+
1360
+ /**
1361
+ * Settle a reserved manual redemption with one terminal consume code.
1362
+ *
1363
+ * Throws `TypeError` for malformed identity fields or `now`; an unsupported
1364
+ * code or incompatible durable record returns `mismatch`.
1365
+ */
1366
+ export function settleManualResetCreditOperation(
1367
+ identity: ManualResetCreditOperationIdentity,
1368
+ code: CodexResetCreditConsumeCode,
1369
+ now = Date.now(),
1370
+ ): UpdateResetCreditOperationResult {
1371
+ const owner = snapshotManualIdentity(identity);
1372
+ if (!Number.isSafeInteger(now) || now < 0) throw new TypeError("invalid reset-credit operation timestamp");
1373
+ if (!Object.prototype.hasOwnProperty.call(TERMINAL_STATE_BY_CODE, code)) {
1374
+ return Object.freeze({ kind: "mismatch" });
1375
+ }
1376
+ return updateOperation({ accountKey: owner.accountKey, operationKind: "manual" }, owner.operationId, record => {
1377
+ if (isTerminal(record)) return record.code === code ? record : undefined;
1378
+ return Object.freeze({
1379
+ ...record,
1380
+ state: TERMINAL_STATE_BY_CODE[code],
1381
+ code,
1382
+ updatedAt: Math.max(record.updatedAt, now),
1383
+ });
1384
+ }, (database, updated) => {
1385
+ const result = database.query(SETTLE_MANUAL_IDS).run(
1386
+ code,
1387
+ updated.updatedAt,
1388
+ owner.accountKey,
1389
+ owner.operationId,
1390
+ code,
1391
+ );
1392
+ if (result.changes < 1) {
1393
+ throw new Error("manual reset-credit terminal identity update lost ownership");
1394
+ }
1395
+ const rows = database.query<ManualResetCreditOperationIdRow, [string, string]>(
1396
+ SELECT_MANUAL_IDS_BY_CANONICAL,
1397
+ ).all(owner.accountKey, owner.operationId);
1398
+ if (rows.length < 1 || rows.length > MAX_MANUAL_RESET_CREDIT_OPERATION_IDS) {
1399
+ throw new Error("invalid manual reset-credit terminal identity set");
1400
+ }
1401
+ for (const row of rows) {
1402
+ const stored = parseManualIdRecord(row);
1403
+ if (!stored || stored.accountKey !== owner.accountKey
1404
+ || stored.canonicalOperationId !== owner.operationId
1405
+ || stored.terminalCode !== code
1406
+ || stored.updatedAt !== updated.updatedAt) {
1407
+ throw new Error("manual reset-credit terminal identity write did not persist");
1408
+ }
1409
+ }
1410
+ });
1411
+ }