Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs-site/src/content/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
61 changes: 61 additions & 0 deletions src/codex/reset-credit-policy.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown> | 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<string, unknown>
: 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,
});
}
169 changes: 159 additions & 10 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PropertyKey>(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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<string, unknown> | null {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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}`);
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) };
}
Expand Down
13 changes: 13 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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<string, number>;
/** Global Codex-visible context cap value (tokens). Falls back to DEFAULT_PROVIDER_CONTEXT_CAP. */
Expand Down
Loading
Loading