From 3ae981423311972f5d46a75f26e8b82b7d96594b Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Fri, 14 Aug 2026 02:14:10 +0900 Subject: [PATCH] feat(codex): add quota recovery policy contract --- .../content/docs/reference/configuration.md | 24 ++ src/codex/reset-credit-policy.ts | 61 +++++ src/config.ts | 169 ++++++++++++- src/types.ts | 13 + tests/codex-reset-credit-policy.test.ts | 101 ++++++++ tests/config-user-edits.test.ts | 239 ++++++++++++++++++ 6 files changed, 597 insertions(+), 10 deletions(-) create mode 100644 src/codex/reset-credit-policy.ts create mode 100644 tests/codex-reset-credit-policy.test.ts diff --git a/docs-site/src/content/docs/reference/configuration.md b/docs-site/src/content/docs/reference/configuration.md index 10844a5da5..14d32cc3d2 100644 --- a/docs-site/src/content/docs/reference/configuration.md +++ b/docs-site/src/content/docs/reference/configuration.md @@ -48,6 +48,30 @@ Routing has its own ordered resolution rules; see [Routing](/reference/configura - [Server and runtime](/reference/configuration/server/) — listener and remote access, admission keys, timeouts, storage, sidecars, startup behavior, and shadow calls. +## Pending Codex quota-recovery policy + +`codexQuotaRecovery` is a dormant policy contract for the remaining work in issue #657. Current +releases validate and persist it, but do **not** consume reset credits or replay failed requests from +this setting. Manual reset-credit inspection and consumption remain separate account actions. + +The policy is absent and disabled by default. Future automatic redemption may be considered only +when both opt-ins are exactly `true`; `priority` must be either `"alternate-first"` or +`"reset-first"`: + +```json +{ + "codexQuotaRecovery": { + "enabled": true, + "autoRedeemResetCredit": true, + "priority": "alternate-first" + } +} +``` + +A malformed hand edit disables only this policy and preserves the rest of `config.json`. Live config +writes reject malformed values. Setting this block today still spends no credit and triggers no +replay; runtime integration will remain a separate, explicitly reviewed change. + ## Keep secrets out of the file Prefer `${ENV_VAR}` references for API keys. Literal `apiKey`, `apiKeyPool[].key`, and `apiKeys[].key` diff --git a/src/codex/reset-credit-policy.ts b/src/codex/reset-credit-policy.ts new file mode 100644 index 0000000000..e1bd48bf8e --- /dev/null +++ b/src/codex/reset-credit-policy.ts @@ -0,0 +1,61 @@ +import type { OcxCodexQuotaRecoveryConfig } from "../types"; + +export type CodexQuotaRecoveryPriority = OcxCodexQuotaRecoveryConfig["priority"]; + +export type EffectiveCodexQuotaRecoveryPolicy = Readonly<{ + enabled: boolean; + autoRedeemResetCredit: boolean; + priority: CodexQuotaRecoveryPriority; + automaticRedemptionAllowed: boolean; +}>; + +const DEFAULT_PRIORITY: CodexQuotaRecoveryPriority = "alternate-first"; + +function ownDataValue(record: Record | undefined, key: string): unknown { + if (!record) return undefined; + const descriptor = Object.getOwnPropertyDescriptor(record, key); + return descriptor && "value" in descriptor ? descriptor.value : undefined; +} + +/** + * Normalize the persisted policy at the runtime boundary. + * + * Automatic redemption is authorized only by the exact double opt-in. Unknown + * input is deliberately treated as disabled even if a caller bypasses config + * validation and invokes this helper directly. + */ +export function effectiveCodexQuotaRecoveryPolicy( + raw: unknown, +): EffectiveCodexQuotaRecoveryPolicy { + let enabledValue: unknown; + let autoRedeemValue: unknown; + let priorityValue: unknown; + try { + const record = raw !== null && typeof raw === "object" && !Array.isArray(raw) + ? raw as Record + : undefined; + enabledValue = ownDataValue(record, "enabled"); + autoRedeemValue = ownDataValue(record, "autoRedeemResetCredit"); + priorityValue = ownDataValue(record, "priority"); + } catch { + return Object.freeze({ + enabled: false, + autoRedeemResetCredit: false, + priority: DEFAULT_PRIORITY, + automaticRedemptionAllowed: false, + }); + } + const enabled = enabledValue === true; + const autoRedeemResetCredit = autoRedeemValue === true; + const validPriority = priorityValue === "alternate-first" || priorityValue === "reset-first"; + const priority = priorityValue === "reset-first" + ? "reset-first" + : DEFAULT_PRIORITY; + + return Object.freeze({ + enabled, + autoRedeemResetCredit, + priority, + automaticRedemptionAllowed: enabled && autoRedeemResetCredit && validPriority, + }); +} diff --git a/src/config.ts b/src/config.ts index 703a444d87..a264daa5d3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1213,6 +1213,64 @@ const agentTaskRecoverySchema = z.object({ cacheEntries: z.number().int().min(1).max(512).optional(), }).strict(); +const codexQuotaRecoverySchema = z.object({ + enabled: z.boolean(), + autoRedeemResetCredit: z.boolean(), + priority: z.enum(["alternate-first", "reset-first"]), +}).strict(); + +const CODEX_QUOTA_RECOVERY_FIELDS = ["enabled", "autoRedeemResetCredit", "priority"] as const; + +function ownDataProperty(record: object, key: PropertyKey): PropertyDescriptor | undefined { + const descriptor = Object.getOwnPropertyDescriptor(record, key); + return descriptor && "value" in descriptor ? descriptor : undefined; +} + +type CheckedCodexQuotaRecovery = + | { ok: true; value: Record<(typeof CODEX_QUOTA_RECOVERY_FIELDS)[number], unknown> } + | { ok: false; error: string }; + +function checkedCodexQuotaRecovery(value: unknown): CheckedCodexQuotaRecovery { + try { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return { ok: false, error: "codexQuotaRecovery must be a plain object" }; + } + const descriptors = {} as Record<(typeof CODEX_QUOTA_RECOVERY_FIELDS)[number], PropertyDescriptor>; + for (const field of CODEX_QUOTA_RECOVERY_FIELDS) { + const descriptor = ownDataProperty(value, field); + if (!descriptor) { + return { ok: false, error: `codexQuotaRecovery.${field} must be an own data property` }; + } + descriptors[field] = descriptor; + } + const allowed = new Set(CODEX_QUOTA_RECOVERY_FIELDS); + if (Reflect.ownKeys(value).some(key => !allowed.has(key))) { + return { ok: false, error: "codexQuotaRecovery contains unknown fields" }; + } + return { + ok: true, + value: { + enabled: descriptors.enabled.value, + autoRedeemResetCredit: descriptors.autoRedeemResetCredit.value, + priority: descriptors.priority.value, + }, + }; + } catch { + return { ok: false, error: "codexQuotaRecovery could not be inspected safely" }; + } +} + +function rawCodexQuotaRecovery(value: unknown): { present: false } | { present: true; value: unknown } { + if (!value || typeof value !== "object" || Array.isArray(value)) return { present: false }; + const descriptor = ownDataProperty(value, "codexQuotaRecovery"); + if (!descriptor) { + return Object.hasOwn(value, "codexQuotaRecovery") || "codexQuotaRecovery" in value + ? { present: true, value: null } + : { present: false }; + } + return { present: true, value: descriptor.value }; +} + const configSchema = z.object({ port: z.number().int().min(0).max(65535).default(10100), managementUsageMaxReadBytes: z.number().int().positive().default(64 * 1024 * 1024), @@ -1253,6 +1311,9 @@ const configSchema = z.object({ multiAgentGuidanceEnabled: z.boolean().optional(), // Invalid optional recovery config must not discard unrelated provider/account state. agentTaskRecovery: agentTaskRecoverySchema.optional().catch(undefined), + // Malformed hand edits disable only this irreversible-operation opt-in. + // Live writes remain strict at validateConfigCandidate(). + codexQuotaRecovery: codexQuotaRecoverySchema.optional().catch(undefined), // These selections pre-date schema validation and used to pass through as // unknown fields. Invalid hand edits must disable only the optional // delegation/native-default feature, not reject the whole config and hide @@ -1997,11 +2058,27 @@ function malformedAgentTaskRecoveryWarning(rawParsed: unknown): string | null { return `agentTaskRecovery${field ? `.${field}` : ""} ignored: invalid experimental recovery configuration`; } +function malformedCodexQuotaRecoveryWarning(rawParsed: unknown): string | null { + const raw = rawCodexQuotaRecovery(rawParsed); + if (!raw.present) return null; + const checked = checkedCodexQuotaRecovery(raw.value); + if (!checked.ok) return `${checked.error} — policy disabled`; + const result = codexQuotaRecoverySchema.safeParse(checked.value); + if (result.success) return null; + const field = result.error.issues[0]?.path.join("."); + return `codexQuotaRecovery${field ? `.${field}` : ""} ignored: invalid reset-credit recovery policy`; +} + function warnDegradedAgentTaskRecovery(rawParsed: unknown): void { const warning = malformedAgentTaskRecoveryWarning(rawParsed); if (warning) console.warn(`⚠️ config.json ${warning}. Other settings were preserved.`); } +function warnDegradedCodexQuotaRecovery(rawParsed: unknown): void { + const warning = malformedCodexQuotaRecoveryWarning(rawParsed); + if (warning) console.warn(`⚠️ config.json ${warning}. Other settings were preserved.`); +} + type NativeSubagentPersistedField = "injectionModel" | "injectionEffort" | "syncCodexSubagentDefaults"; function rawConfigRecord(rawParsed: unknown): Record | null { @@ -2105,6 +2182,7 @@ export function loadConfig(): OcxConfig { warnDegradedCodexAccountPicker(parsed); warnDegradedUpstreamHostCircuitThreshold(parsed); warnDegradedAgentTaskRecovery(parsed); + warnDegradedCodexQuotaRecovery(parsed); return withRefreshedCostOverlays(normalizeClaudeSubagentEffort(normalizeNativeSubagentSync(config, parsed), parsed)); } // Schema validation failed — merge defaults into the raw object instead of @@ -2128,6 +2206,7 @@ export function loadConfig(): OcxConfig { warnDegradedCodexAccountPicker(parsed); warnDegradedUpstreamHostCircuitThreshold(parsed); warnDegradedAgentTaskRecovery(parsed); + warnDegradedCodexQuotaRecovery(parsed); return withRefreshedCostOverlays(normalizeClaudeSubagentEffort(normalizeNativeSubagentSync(config, parsed), parsed)); } // Merge couldn't fix it — truly broken config @@ -2189,6 +2268,8 @@ function validFileConfigDiagnostics(config: OcxConfig, rawParsed: unknown): Conf if (hostCircuitWarning) warnings.push(hostCircuitWarning); const recoveryWarning = malformedAgentTaskRecoveryWarning(rawParsed); if (recoveryWarning) warnings.push(recoveryWarning); + const quotaRecoveryWarning = malformedCodexQuotaRecoveryWarning(rawParsed); + if (quotaRecoveryWarning) warnings.push(quotaRecoveryWarning); if (syncDisabledReason) { warnings.push(`syncCodexSubagentDefaults ignored: ${syncDisabledReason}`); } @@ -2280,6 +2361,71 @@ function agentTaskRecoveryError(value: unknown): string | null { return `schema_invalid: agentTaskRecovery${field ? `.${field}` : ""}: ${issue?.message ?? "invalid configuration"}`; } +type PreparedConfigCandidate = + | { ok: true; value: unknown } + | { ok: false; error: string }; + +/** + * Snapshot an in-memory candidate without evaluating accessors, and replace the + * reset-credit policy with the already-validated plain snapshot before Zod can + * read it. This makes one descriptor view authoritative even for Proxy callers. + */ +function prepareConfigCandidate(value: unknown): PreparedConfigCandidate { + try { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return { ok: true, value }; + } + const descriptors = Object.getOwnPropertyDescriptors(value); + const policyDescriptor = descriptors.codexQuotaRecovery; + if (policyDescriptor) { + if (!("value" in policyDescriptor)) { + return { ok: false, error: "schema_invalid: codexQuotaRecovery must be an own data property" }; + } + if (policyDescriptor.value !== undefined) { + const checked = checkedCodexQuotaRecovery(policyDescriptor.value); + if (!checked.ok) return { ok: false, error: `schema_invalid: ${checked.error}` }; + const parsed = codexQuotaRecoverySchema.safeParse(checked.value); + if (!parsed.success) { + const issue = parsed.error.issues[0]; + const field = issue?.path.join("."); + return { + ok: false, + error: `schema_invalid: codexQuotaRecovery${field ? `.${field}` : ""}: ${issue?.message ?? "invalid configuration"}`, + }; + } + const policySnapshot = Object.freeze({ ...parsed.data }); + descriptors.codexQuotaRecovery = { + value: policySnapshot, + enumerable: policyDescriptor.enumerable ?? true, + writable: false, + configurable: false, + }; + } else { + descriptors.codexQuotaRecovery = { + value: undefined, + enumerable: policyDescriptor.enumerable ?? true, + writable: false, + configurable: false, + }; + } + } else { + descriptors.codexQuotaRecovery = { + value: undefined, + enumerable: false, + writable: false, + configurable: false, + }; + } + // Preserve inherited-property observability for the existing live-write + // guards (for example codexAccountPickerEnabled), while keeping the policy + // itself pinned as the immutable own slot installed above. + const prototype = Object.getPrototypeOf(value); + return { ok: true, value: Object.defineProperties(Object.create(prototype), descriptors) }; + } catch { + return { ok: false, error: "schema_invalid: configuration candidate could not be inspected safely" }; + } +} + /** * Same reasoning as {@link blankHostnameError}, and more urgent: the read path degrades a * malformed selection-order map to undefined, which on a write would drop every entry the @@ -2371,17 +2517,20 @@ function loopbackListenerPortError(value: unknown): string | null { } export function validateConfigCandidate(value: unknown): { ok: true; config: OcxConfig } | { ok: false; error: string } { - const boundaryError = blankHostnameError(value) - ?? claudeSubagentEffortError(value) - ?? appOwnedMemoryBudgetError(value) - ?? upstreamHostCircuitThresholdError(value) - ?? agentTaskRecoveryError(value) - ?? googleAntigravityStaticCatalogVersionError(value) - ?? codexAccountPrioritiesError(value) - ?? codexAccountPickerEnabledError(value) - ?? loopbackListenerPortError(value); + const prepared = prepareConfigCandidate(value); + if (!prepared.ok) return prepared; + const candidate = prepared.value; + const boundaryError = blankHostnameError(candidate) + ?? claudeSubagentEffortError(candidate) + ?? appOwnedMemoryBudgetError(candidate) + ?? upstreamHostCircuitThresholdError(candidate) + ?? agentTaskRecoveryError(candidate) + ?? googleAntigravityStaticCatalogVersionError(candidate) + ?? codexAccountPrioritiesError(candidate) + ?? codexAccountPickerEnabledError(candidate) + ?? loopbackListenerPortError(candidate); if (boundaryError) return { ok: false, error: boundaryError }; - const result = configSchema.safeParse(value); + const result = configSchema.safeParse(candidate); if (result.success) return { ok: true, config: normalizeApiKeyIds(result.data as OcxConfig) }; return { ok: false, error: schemaDiagnosticsError(result.error) }; } diff --git a/src/types.ts b/src/types.ts index d24811f430..c74e52552a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -613,6 +613,17 @@ export interface OcxClientIntegrationsConfig { "claude-desktop"?: boolean; } +/** + * Explicit, default-off policy for a future verified reset-credit recovery path. + * Both booleans must be exactly true before an irreversible credit spend may be + * considered; this type does not itself enable runtime recovery. + */ +export interface OcxCodexQuotaRecoveryConfig { + enabled: boolean; + autoRedeemResetCredit: boolean; + priority: "alternate-first" | "reset-first"; +} + export interface OcxConfig { port: number; /** Maximum usage-log bytes read for one management snapshot. */ @@ -787,6 +798,8 @@ export interface OcxConfig { /** Maximum in-memory ciphertext-to-assignment entries. Default: 200. */ cacheEntries?: number; }; + /** Default-off policy contract for verified Codex quota recovery. */ + codexQuotaRecovery?: OcxCodexQuotaRecoveryConfig; /** Provider-level Codex-visible context caps. Values only lower known model context windows. */ providerContextCaps?: Record; /** Global Codex-visible context cap value (tokens). Falls back to DEFAULT_PROVIDER_CONTEXT_CAP. */ diff --git a/tests/codex-reset-credit-policy.test.ts b/tests/codex-reset-credit-policy.test.ts new file mode 100644 index 0000000000..aa62b7481f --- /dev/null +++ b/tests/codex-reset-credit-policy.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, test } from "bun:test"; +import { effectiveCodexQuotaRecoveryPolicy } from "../src/codex/reset-credit-policy"; + +describe("effectiveCodexQuotaRecoveryPolicy", () => { + test("defaults to a frozen disabled alternate-first policy", () => { + for (const raw of [undefined, null, false, [], {}, { enabled: "true" }]) { + const policy = effectiveCodexQuotaRecoveryPolicy(raw); + expect(policy).toEqual({ + enabled: false, + autoRedeemResetCredit: false, + priority: "alternate-first", + automaticRedemptionAllowed: false, + }); + expect(Object.isFrozen(policy)).toBe(true); + } + }); + + test("requires both exact opt-ins before authorizing automatic redemption", () => { + expect(effectiveCodexQuotaRecoveryPolicy({ enabled: true }).automaticRedemptionAllowed).toBe(false); + expect(effectiveCodexQuotaRecoveryPolicy({ autoRedeemResetCredit: true }).automaticRedemptionAllowed).toBe(false); + expect(effectiveCodexQuotaRecoveryPolicy({ + enabled: false, + autoRedeemResetCredit: true, + priority: "reset-first", + }).automaticRedemptionAllowed).toBe(false); + expect(effectiveCodexQuotaRecoveryPolicy({ + enabled: true, + autoRedeemResetCredit: true, + priority: "alternate-first", + })).toMatchObject({ priority: "alternate-first", automaticRedemptionAllowed: true }); + expect(effectiveCodexQuotaRecoveryPolicy({ + enabled: true, + autoRedeemResetCredit: true, + priority: "reset-first", + })).toMatchObject({ priority: "reset-first", automaticRedemptionAllowed: true }); + }); + + test("does not broaden authorization for malformed priority or inherited values", () => { + const inherited = Object.create({ enabled: true, autoRedeemResetCredit: true }); + expect(effectiveCodexQuotaRecoveryPolicy(inherited).automaticRedemptionAllowed).toBe(false); + const accessor = Object.defineProperties({}, { + enabled: { get: () => { throw new Error("must not run"); } }, + autoRedeemResetCredit: { value: true }, + priority: { value: "alternate-first" }, + }); + expect(() => effectiveCodexQuotaRecoveryPolicy(accessor)).not.toThrow(); + expect(effectiveCodexQuotaRecoveryPolicy(accessor).automaticRedemptionAllowed).toBe(false); + expect(effectiveCodexQuotaRecoveryPolicy({ + enabled: true, + autoRedeemResetCredit: true, + priority: "unexpected", + })).toEqual({ + enabled: true, + autoRedeemResetCredit: true, + priority: "alternate-first", + automaticRedemptionAllowed: false, + }); + }); + + test("treats a throwing descriptor Proxy as a disabled policy", () => { + let descriptorCalls = 0; + const hostile = new Proxy({ + enabled: true, + autoRedeemResetCredit: true, + priority: "reset-first", + }, { + getOwnPropertyDescriptor() { + descriptorCalls++; + throw new Error("must not escape"); + }, + }); + + const policy = effectiveCodexQuotaRecoveryPolicy(hostile); + expect(policy).toEqual({ + enabled: false, + autoRedeemResetCredit: false, + priority: "alternate-first", + automaticRedemptionAllowed: false, + }); + expect(Object.isFrozen(policy)).toBe(true); + expect(descriptorCalls).toBe(1); + }); + + test("treats a revoked Proxy as a disabled policy", () => { + const { proxy, revoke } = Proxy.revocable({ + enabled: true, + autoRedeemResetCredit: true, + priority: "reset-first", + }, {}); + revoke(); + + const policy = effectiveCodexQuotaRecoveryPolicy(proxy); + expect(policy).toEqual({ + enabled: false, + autoRedeemResetCredit: false, + priority: "alternate-first", + automaticRedemptionAllowed: false, + }); + expect(Object.isFrozen(policy)).toBe(true); + }); +}); diff --git a/tests/config-user-edits.test.ts b/tests/config-user-edits.test.ts index d64b566572..96419e5cb1 100644 --- a/tests/config-user-edits.test.ts +++ b/tests/config-user-edits.test.ts @@ -17,6 +17,7 @@ import { } from "../src/config"; import { legacyCustomModelCatalogSlugs } from "../src/codex/custom-model-catalog-migration"; import { rateLimitRetryPolicyFor } from "../src/providers/key-failover"; +import { effectiveCodexQuotaRecoveryPolicy } from "../src/codex/reset-credit-policy"; import { activeUserCostOverlays, refreshUserCostOverlays, @@ -768,3 +769,241 @@ test("a malformed upstreamHostCircuitThreshold hand edit disables only the circu ); expect(diagnostics.config.providers.test).toBeDefined(); }); + +test("codex quota recovery policy round-trips only explicit valid opt-ins", () => { + const policy = { + enabled: true, + autoRedeemResetCredit: true, + priority: "alternate-first" as const, + }; + const candidate = validateConfigCandidate({ ...getDefaultConfig(), codexQuotaRecovery: policy }); + expect(candidate).toMatchObject({ ok: true, config: { codexQuotaRecovery: policy } }); + if (!candidate.ok) throw new Error(candidate.error); + saveConfig(candidate.config); + expect(loadConfig().codexQuotaRecovery).toEqual(policy); + expect(effectiveCodexQuotaRecoveryPolicy(loadConfig().codexQuotaRecovery).automaticRedemptionAllowed).toBe(true); +}); + +test("a malformed quota recovery hand edit disables only that policy and warns", () => { + writeDiskConfig({ + codexQuotaRecovery: { + enabled: true, + autoRedeemResetCredit: "yes", + priority: "alternate-first", + }, + }); + const diagnostics = readConfigDiagnostics(); + expect(diagnostics.source).toBe("file"); + expect(diagnostics.config.codexQuotaRecovery).toBeUndefined(); + expect(diagnostics.config.providers.test).toBeDefined(); + expect(diagnostics.warnings).toContain( + "codexQuotaRecovery.autoRedeemResetCredit ignored: invalid reset-credit recovery policy", + ); + expect(effectiveCodexQuotaRecoveryPolicy(diagnostics.config.codexQuotaRecovery).automaticRedemptionAllowed).toBe(false); +}); + +test("live writes reject malformed quota recovery policies and defaults stay off", () => { + for (const policy of [ + { enabled: true, autoRedeemResetCredit: true }, + { enabled: true, autoRedeemResetCredit: true, priority: "first" }, + { enabled: true, autoRedeemResetCredit: "yes", priority: "alternate-first" }, + { enabled: true, autoRedeemResetCredit: true, priority: "alternate-first", extra: true }, + ]) { + const result = validateConfigCandidate({ ...getDefaultConfig(), codexQuotaRecovery: policy }); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.error).toContain("codexQuotaRecovery"); + } + expect(getDefaultConfig().codexQuotaRecovery).toBeUndefined(); + expect(effectiveCodexQuotaRecoveryPolicy(getDefaultConfig().codexQuotaRecovery).automaticRedemptionAllowed).toBe(false); +}); + +test("live quota recovery validation never materializes inherited values or getters", () => { + const base = getDefaultConfig(); + const inherited = Object.create({ + enabled: true, + autoRedeemResetCredit: true, + priority: "alternate-first", + }); + expect(validateConfigCandidate({ ...base, codexQuotaRecovery: inherited })).toMatchObject({ + ok: false, + error: expect.stringContaining("own data property"), + }); + + let nestedGetterCalls = 0; + const nestedAccessor = Object.defineProperties({}, { + enabled: { get: () => { nestedGetterCalls++; return true; } }, + autoRedeemResetCredit: { value: true }, + priority: { value: "alternate-first" }, + }); + expect(validateConfigCandidate({ ...base, codexQuotaRecovery: nestedAccessor })).toMatchObject({ + ok: false, + error: expect.stringContaining("own data property"), + }); + expect(nestedGetterCalls).toBe(0); + + let unknownGetterCalls = 0; + const unknownAccessor = Object.defineProperties({}, { + enabled: { value: true }, + autoRedeemResetCredit: { value: true }, + priority: { value: "alternate-first" }, + extra: { get: () => { unknownGetterCalls++; return true; } }, + }); + expect(validateConfigCandidate({ ...base, codexQuotaRecovery: unknownAccessor })).toMatchObject({ + ok: false, + error: expect.stringContaining("unknown fields"), + }); + expect(unknownGetterCalls).toBe(0); + + let topLevelGetterCalls = 0; + const topLevelAccessor = { ...base } as Record; + Object.defineProperty(topLevelAccessor, "codexQuotaRecovery", { + enumerable: true, + get: () => { + topLevelGetterCalls++; + return { enabled: true, autoRedeemResetCredit: true, priority: "alternate-first" }; + }, + }); + expect(validateConfigCandidate(topLevelAccessor)).toMatchObject({ + ok: false, + error: expect.stringContaining("own data property"), + }); + expect(topLevelGetterCalls).toBe(0); +}); + +test("live quota recovery validation uses one descriptor snapshot for Proxy candidates", () => { + const base = getDefaultConfig(); + let nestedGetCalls = 0; + const nestedPolicy = new Proxy({ + enabled: false, + autoRedeemResetCredit: false, + priority: "alternate-first", + }, { + get(target, key, receiver) { + nestedGetCalls++; + if (key === "enabled" || key === "autoRedeemResetCredit") return true; + return Reflect.get(target, key, receiver); + }, + }); + const nestedResult = validateConfigCandidate({ ...base, codexQuotaRecovery: nestedPolicy }); + expect(nestedResult).toMatchObject({ + ok: true, + config: { + codexQuotaRecovery: { + enabled: false, + autoRedeemResetCredit: false, + priority: "alternate-first", + }, + }, + }); + expect(nestedGetCalls).toBe(0); + + let topLevelPolicyGets = 0; + const topLevelTarget = { ...base } as Record; + const topLevelProxy = new Proxy(topLevelTarget, { + get(target, key, receiver) { + if (key === "codexQuotaRecovery") { + topLevelPolicyGets++; + return { enabled: true, autoRedeemResetCredit: true, priority: "alternate-first" }; + } + return Reflect.get(target, key, receiver); + }, + }); + const topLevelResult = validateConfigCandidate(topLevelProxy); + expect(topLevelResult).toMatchObject({ ok: true }); + if (!topLevelResult.ok) throw new Error(topLevelResult.error); + expect(topLevelResult.config.codexQuotaRecovery).toBeUndefined(); + expect(topLevelPolicyGets).toBe(0); +}); + +test("live quota recovery validation converts Proxy inspection failures to schema errors", () => { + const base = getDefaultConfig(); + const topLevelTrap = new Proxy({ ...base }, { + ownKeys() { + throw new Error("must not escape"); + }, + }); + expect(() => validateConfigCandidate(topLevelTrap)).not.toThrow(); + expect(validateConfigCandidate(topLevelTrap)).toMatchObject({ + ok: false, + error: expect.stringContaining("could not be inspected safely"), + }); + + const nestedTrap = new Proxy({ + enabled: true, + autoRedeemResetCredit: true, + priority: "alternate-first", + }, { + ownKeys() { + throw new Error("must not escape"); + }, + }); + expect(() => validateConfigCandidate({ ...base, codexQuotaRecovery: nestedTrap })).not.toThrow(); + expect(validateConfigCandidate({ ...base, codexQuotaRecovery: nestedTrap })).toMatchObject({ + ok: false, + error: expect.stringContaining("could not be inspected safely"), + }); +}); + +test("unrelated accessors cannot mutate or replace the checked quota policy", () => { + const candidate = { ...getDefaultConfig() } as Record; + candidate.codexQuotaRecovery = { + enabled: false, + autoRedeemResetCredit: false, + priority: "alternate-first", + }; + let accessorCalls = 0; + Object.defineProperty(candidate, "hostname", { + enumerable: true, + get(this: Record) { + accessorCalls++; + const policy = this.codexQuotaRecovery as Record; + try { policy.enabled = true; } catch { /* immutable snapshot */ } + try { policy.autoRedeemResetCredit = true; } catch { /* immutable snapshot */ } + try { + this.codexQuotaRecovery = { + enabled: true, + autoRedeemResetCredit: true, + priority: "alternate-first", + }; + } catch { /* non-replaceable top-level snapshot */ } + return "127.0.0.1"; + }, + }); + + const result = validateConfigCandidate(candidate); + expect(result).toMatchObject({ + ok: true, + config: { + codexQuotaRecovery: { + enabled: false, + autoRedeemResetCredit: false, + priority: "alternate-first", + }, + }, + }); + expect(accessorCalls).toBeGreaterThan(0); + if (!result.ok) throw new Error(result.error); + expect(effectiveCodexQuotaRecoveryPolicy(result.config.codexQuotaRecovery).automaticRedemptionAllowed).toBe(false); + + for (const initial of ["absent", "undefined"] as const) { + const withoutPolicy = { ...getDefaultConfig() } as Record; + if (initial === "undefined") withoutPolicy.codexQuotaRecovery = undefined; + Object.defineProperty(withoutPolicy, "hostname", { + enumerable: true, + get(this: Record) { + try { + this.codexQuotaRecovery = { + enabled: true, + autoRedeemResetCredit: true, + priority: "alternate-first", + }; + } catch { /* fixed absent policy slot */ } + return "127.0.0.1"; + }, + }); + const omitted = validateConfigCandidate(withoutPolicy); + expect(omitted).toMatchObject({ ok: true }); + if (!omitted.ok) throw new Error(omitted.error); + expect(omitted.config.codexQuotaRecovery).toBeUndefined(); + } +});