@momentiq/dark-factory-cli 1.0.0 → 1.1.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.
- package/README.md +35 -38
- package/dist/adapters/codex-sdk.d.ts +66 -0
- package/dist/adapters/codex-sdk.d.ts.map +1 -1
- package/dist/adapters/codex-sdk.js +321 -4
- package/dist/adapters/codex-sdk.js.map +1 -1
- package/dist/adapters/critic-result-schema.d.ts +5 -0
- package/dist/adapters/critic-result-schema.d.ts.map +1 -1
- package/dist/adapters/critic-result-schema.js +10 -0
- package/dist/adapters/critic-result-schema.js.map +1 -1
- package/dist/adapters/cursor-cli.d.ts +19 -1
- package/dist/adapters/cursor-cli.d.ts.map +1 -1
- package/dist/adapters/cursor-cli.js +96 -14
- package/dist/adapters/cursor-cli.js.map +1 -1
- package/dist/branch-protection/spec-default.yaml +2 -2
- package/dist/cli.js +127 -81
- package/dist/cli.js.map +1 -1
- package/dist/commands/show.d.ts +11 -0
- package/dist/commands/show.d.ts.map +1 -0
- package/dist/commands/show.js +78 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +85 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/compact/index.d.ts +2 -0
- package/dist/compact/index.d.ts.map +1 -0
- package/dist/compact/index.js +3 -0
- package/dist/compact/index.js.map +1 -0
- package/dist/compact/lockfile.d.ts +53 -0
- package/dist/compact/lockfile.d.ts.map +1 -0
- package/dist/compact/lockfile.js +626 -0
- package/dist/compact/lockfile.js.map +1 -0
- package/dist/cycle-doc-validator/validate_cycle_doc.py +39 -9
- package/dist/doctor.d.ts +34 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +390 -1
- package/dist/doctor.js.map +1 -1
- package/dist/handoff/handoff-verb.d.ts.map +1 -1
- package/dist/handoff/handoff-verb.js +6 -3
- package/dist/handoff/handoff-verb.js.map +1 -1
- package/dist/handoff/ports.d.ts +8 -0
- package/dist/handoff/ports.d.ts.map +1 -1
- package/dist/handoff/real-clients.js +1 -1
- package/dist/handoff/real-clients.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/show-status-core.d.ts +63 -0
- package/dist/lib/show-status-core.d.ts.map +1 -0
- package/dist/lib/show-status-core.js +156 -0
- package/dist/lib/show-status-core.js.map +1 -0
- package/dist/mcp/prompts.d.ts.map +1 -1
- package/dist/mcp/prompts.js +113 -78
- package/dist/mcp/prompts.js.map +1 -1
- package/dist/mcp/resources.js +1 -1
- package/dist/mcp/resources.js.map +1 -1
- package/dist/mcp/tools/findings.d.ts +0 -22
- package/dist/mcp/tools/findings.d.ts.map +1 -1
- package/dist/mcp/tools/findings.js +5 -73
- package/dist/mcp/tools/findings.js.map +1 -1
- package/dist/mcp/tools/handoff.d.ts.map +1 -1
- package/dist/mcp/tools/handoff.js +10 -17
- package/dist/mcp/tools/handoff.js.map +1 -1
- package/dist/mcp/tools/review-bypass.d.ts.map +1 -1
- package/dist/mcp/tools/review-bypass.js +1 -1
- package/dist/mcp/tools/review-bypass.js.map +1 -1
- package/dist/policy/baseline.d.ts.map +1 -1
- package/dist/policy/baseline.js +11 -3
- package/dist/policy/baseline.js.map +1 -1
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +18 -3
- package/dist/prompt.js.map +1 -1
- package/dist/report.d.ts +24 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +85 -0
- package/dist/report.js.map +1 -1
- package/dist/runner.d.ts +4 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +127 -2
- package/dist/runner.js.map +1 -1
- package/dist/trusted-surface/rebind.d.ts.map +1 -1
- package/dist/trusted-surface/rebind.js +185 -1
- package/dist/trusted-surface/rebind.js.map +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -4,9 +4,8 @@ Dark Factory OSS CLI — multi-vendor adversarial critic orchestration.
|
|
|
4
4
|
|
|
5
5
|
## What this package gives you
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
subcommands:
|
|
7
|
+
Nine Dark Factory services, consumable as a TypeScript library and (where
|
|
8
|
+
relevant) as `df` subcommands:
|
|
10
9
|
|
|
11
10
|
1. **Critic Orchestrator** (`./adapters/*`) — vendor-neutral adapter contract
|
|
12
11
|
(`CriticAdapter`) with concrete adapters for Cursor SDK, OpenAI Codex SDK,
|
|
@@ -23,33 +22,31 @@ subcommands:
|
|
|
23
22
|
5. **Cycle-Doc Trailer Validator** (`./cycle-doc-validator/*` + `df validate-cycle-doc`)
|
|
24
23
|
— enforces per-PR `Cycle:` / `Issue:` / `ProjectItem:` trailer rules.
|
|
25
24
|
6. **Merge Queue Admission Policy** (`./policy/merge-queue.ts` + `df admit-pr`) —
|
|
26
|
-
plan-vs-code PR classifier
|
|
27
|
-
|
|
28
|
-
`
|
|
29
|
-
|
|
25
|
+
plan-vs-code PR classifier + the typed ruleset shape
|
|
26
|
+
(`defaultMainRulesetShape`, `defaultCeReviewRulesetShape`,
|
|
27
|
+
`defaultMergeQueueRule`) that consumers declare so the branch-protection
|
|
28
|
+
auditor can detect drift against it.
|
|
30
29
|
7. **Branch-Protection Drift Detector** (`./branch-protection/*` + `df audit-branch-protection`)
|
|
31
30
|
— compares a declarative `spec.yaml` against the live GitHub ruleset.
|
|
32
31
|
8. **Audit / Compliance Trail** (`./evidence/audit-trail.ts` + `df audit stats`) —
|
|
33
32
|
the `_runs.ndjson` NDJSON sink + read/summarize/agreement-rate/quorum-stats
|
|
34
|
-
helpers behind
|
|
35
|
-
|
|
33
|
+
helpers behind `make agent-review-stats`. Every critic run, every gate
|
|
34
|
+
verdict, every bypass invocation appends here.
|
|
36
35
|
9. **Cycle Tracker Sync + PR Attribution** (`./cycle-tracker-sync/*` + `df sync-trackers` + `df attribute-pr`)
|
|
37
36
|
— reconciles GitHub tracker issues with cycle docs + writes the
|
|
38
37
|
`Cycle Ref` custom field on PR project items.
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
critic / aggregator logic lands in Phase F.
|
|
39
|
+
The package also ships five reusable GitHub Actions workflows
|
|
40
|
+
(`.github/workflows/*.yml`) that consumers wire up via `uses:`. See the
|
|
41
|
+
[root README](../../README.md#reusable-workflows) for the consumer wiring
|
|
42
|
+
pattern.
|
|
45
43
|
|
|
46
44
|
## Status
|
|
47
45
|
|
|
48
|
-
`
|
|
49
|
-
`
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`df critic` subcommand is the CI cold-path API-key version.
|
|
46
|
+
`1.0.0` — shipped on npm. Library API + the hook-facing binary surface
|
|
47
|
+
(`review`, `gate-push`, `doctor`, `gates`, `stats`) are stable. The
|
|
48
|
+
`df critic` subcommand is the CI cold-path (API-key) counterpart to the
|
|
49
|
+
subscription-auth local hooks.
|
|
53
50
|
|
|
54
51
|
## Install
|
|
55
52
|
|
|
@@ -87,19 +84,20 @@ await runValidateCycleDoc({
|
|
|
87
84
|
df --help
|
|
88
85
|
df --version
|
|
89
86
|
|
|
90
|
-
#
|
|
91
|
-
# script verbatim, so `df <sub> --help` returns the Python argparse
|
|
87
|
+
# Python-backed subcommands — each forwards remaining argv to the bundled
|
|
88
|
+
# Python script verbatim, so `df <sub> --help` returns the Python argparse
|
|
89
|
+
# banner.
|
|
92
90
|
df validate-cycle-doc --help
|
|
93
91
|
df audit-branch-protection --use-bundled-default-spec --repo owner/repo
|
|
94
92
|
df sync-trackers --dry-run
|
|
95
93
|
df attribute-pr # env-driven; needs PR_NUMBER, PR_NODE_ID, PR_BODY_FILE, PROJECT_TOKEN
|
|
96
94
|
|
|
97
|
-
#
|
|
95
|
+
# Pure-TS subcommands.
|
|
98
96
|
df audit stats --path .git/agent-reviews/_runs.ndjson
|
|
99
97
|
df admit-pr --files-stdin # newline-separated file paths on stdin
|
|
100
|
-
df admit-pr --files docs/roadmap/cycles/
|
|
98
|
+
df admit-pr --files docs/roadmap/cycles/cycle1.md,packages/cli/src/cli.ts
|
|
101
99
|
|
|
102
|
-
#
|
|
100
|
+
# Hook-facing subcommands (subscription cost model).
|
|
103
101
|
df review --commit HEAD --profile local --foreground
|
|
104
102
|
df gate-push # local pre-push, reads stdin
|
|
105
103
|
df gate-push --commit HEAD --ci # CI replay
|
|
@@ -107,24 +105,23 @@ df doctor --profile local # env + per-adapter auth check
|
|
|
107
105
|
df gates # static gates, no LLM
|
|
108
106
|
df stats # alias for `df audit stats`
|
|
109
107
|
|
|
110
|
-
#
|
|
111
|
-
#
|
|
108
|
+
# Stdio Model Context Protocol server — exposes the CLI surface to any
|
|
109
|
+
# MCP-speaking agent.
|
|
112
110
|
df mcp # start the stdio MCP server
|
|
113
111
|
df mcp --help # config snippets for Claude Code, Cursor, Codex
|
|
114
112
|
```
|
|
115
113
|
|
|
116
114
|
> **Note on `--use-bundled-default-spec`**: the bundled `spec-default.yaml`
|
|
117
|
-
>
|
|
118
|
-
> `agent-critic
|
|
119
|
-
>
|
|
120
|
-
> repo's own dogfood gate. Consumers SHOULD author their own `spec.yaml`
|
|
115
|
+
> asserts the standard Dark Factory required-status-check contexts (e.g.
|
|
116
|
+
> `agent-critic`, `cycle-doc-validation`). It exists as a working starting
|
|
117
|
+
> point for first-run audits. Consumers SHOULD author their own `spec.yaml`
|
|
121
118
|
> matching their repo's actual posture — running the bundled default
|
|
122
119
|
> against an arbitrary repo will surface drift against contexts that
|
|
123
120
|
> don't exist there.
|
|
124
121
|
|
|
125
122
|
## For consumer repos — hook wiring + subscription cost model
|
|
126
123
|
|
|
127
|
-
The
|
|
124
|
+
The hook-facing subcommands (`review`, `gate-push`, `doctor`, `gates`,
|
|
128
125
|
`stats`) are designed to power consumer repos' `.husky/post-commit` and
|
|
129
126
|
`.husky/pre-push` hooks. The **cost model** is critical: per-commit critic
|
|
130
127
|
invocations from API tokens cost $1000s/week on a busy repo, while
|
|
@@ -251,12 +248,12 @@ API — see `packages/cli/src/doppler-bootstrap.ts` for the
|
|
|
251
248
|
## System requirements
|
|
252
249
|
|
|
253
250
|
- **Node.js >=20**
|
|
254
|
-
- **Python 3.11+** — required for services #5, #7, #9. The
|
|
255
|
-
|
|
251
|
+
- **Python 3.11+** — required for services #5, #7, #9. The package bundles
|
|
252
|
+
the source Python scripts (`validate_cycle_doc.py`,
|
|
256
253
|
`audit_branch_protection.py`, `sync_cycle_trackers.py`,
|
|
257
254
|
`attribute_pr_cycle_ref.py`) and wraps each in a TypeScript subprocess
|
|
258
|
-
spawn.
|
|
259
|
-
|
|
255
|
+
spawn. A pure-TS rewrite is on the roadmap and will eliminate this
|
|
256
|
+
dependency in a future release.
|
|
260
257
|
- **`gh` CLI** (authenticated) — all four Python scripts shell out to `gh api`
|
|
261
258
|
for GitHub queries. CI invocations provide `GH_TOKEN` / `PROJECT_TOKEN`
|
|
262
259
|
via environment.
|
|
@@ -278,6 +275,6 @@ option is supplied — pass it when invoking outside a git worktree.
|
|
|
278
275
|
|
|
279
276
|
## License
|
|
280
277
|
|
|
281
|
-
Apache-2.0. The OSS critic surface is a public artifact.
|
|
282
|
-
|
|
283
|
-
|
|
278
|
+
Apache-2.0. The OSS critic surface is a public artifact. The hosted Dark
|
|
279
|
+
Factory runtime layers proprietary calibrated prompts and a calibrated
|
|
280
|
+
bypass-classifier on top of this CLI; those are out-of-scope here.
|
|
@@ -61,6 +61,9 @@ export type CodexExecHook = (binaryPath: string, args: readonly string[], option
|
|
|
61
61
|
*/
|
|
62
62
|
export declare function probeCodexLoginStatus(cliPath: string, exec?: CodexExecHook, timeoutMs?: number): Promise<CodexAuthProbeOutcome>;
|
|
63
63
|
export declare const DEFAULT_REASONING_EFFORT: "high";
|
|
64
|
+
export type CodexSandboxMode = "read-only" | "workspace-write" | "danger-full-access";
|
|
65
|
+
export declare const CODEX_SANDBOX_MODES: readonly CodexSandboxMode[];
|
|
66
|
+
export declare const DEFAULT_SANDBOX_MODE: CodexSandboxMode;
|
|
64
67
|
/**
|
|
65
68
|
* Test-shape compatible with the `@openai/codex-sdk` Codex / Thread
|
|
66
69
|
* classes. The unit tests pass a mock conforming to this shape; production
|
|
@@ -219,6 +222,21 @@ export interface CodexSdkAdapterOptions {
|
|
|
219
222
|
* enum). Exported for direct unit testing.
|
|
220
223
|
*/
|
|
221
224
|
export declare function resolveCodexReasoningEffort(critic: CriticConfig): string;
|
|
225
|
+
/**
|
|
226
|
+
* Issue #68 — resolve the Codex `sandboxMode` from `critic.model.params`.
|
|
227
|
+
*
|
|
228
|
+
* Falls back to {@link DEFAULT_SANDBOX_MODE} ("read-only") when unset OR
|
|
229
|
+
* when the value is malformed (typo, wrong type). The typo-tolerance is
|
|
230
|
+
* intentional: a relaxed sandbox setting silently corrupting an SDK call
|
|
231
|
+
* would be worse than the operator's misconfiguration loudly defaulting
|
|
232
|
+
* to the safe value at review time.
|
|
233
|
+
*
|
|
234
|
+
* Operators opt into `workspace-write` or `danger-full-access` when the
|
|
235
|
+
* container is the security boundary (hosted W3 worker on GKE Autopilot
|
|
236
|
+
* lacks SYS_ADMIN, so bwrap's `read-only` sandbox fails at startup with
|
|
237
|
+
* `bwrap: No permissions to create a new namespace`).
|
|
238
|
+
*/
|
|
239
|
+
export declare function resolveCodexSandboxMode(critic: CriticConfig): CodexSandboxMode;
|
|
222
240
|
/**
|
|
223
241
|
* Probe a thrown error for a Codex SDK structured error code. The
|
|
224
242
|
* Codex SDK surfaces errors as plain `Error` instances; some SDK
|
|
@@ -229,6 +247,54 @@ export declare function resolveCodexReasoningEffort(critic: CriticConfig): strin
|
|
|
229
247
|
* Exported for unit testing.
|
|
230
248
|
*/
|
|
231
249
|
export declare function extractCodexErrorCode(err: unknown): string | null;
|
|
250
|
+
export declare const SANDBOX_INIT_FAILURE_CODE: "sandbox_init_failure";
|
|
251
|
+
/**
|
|
252
|
+
* Issue #109 — scan a string for known environmental sandbox-init
|
|
253
|
+
* failure signatures. Returns the FIRST matching substring (trimmed to
|
|
254
|
+
* the matched line) so the error envelope's detail message carries the
|
|
255
|
+
* actual citation, or `null` if no pattern matches.
|
|
256
|
+
*
|
|
257
|
+
* Pure function — exported for direct unit testing. The conservative
|
|
258
|
+
* pattern list lives in {@link SANDBOX_INIT_FAILURE_PATTERNS} (see the
|
|
259
|
+
* comment there for the "what counts as environmental" contract).
|
|
260
|
+
*/
|
|
261
|
+
export declare function detectSandboxInitFailure(text: string): string | null;
|
|
262
|
+
/**
|
|
263
|
+
* Issue #109 — scan a Codex turn's `items[]` for command_execution
|
|
264
|
+
* outputs whose `aggregated_output` cites a known sandbox-init failure.
|
|
265
|
+
* The codex CLI's bwrap wrapper writes its initialization error to the
|
|
266
|
+
* spawned command's stderr, which the SDK surfaces in the item's
|
|
267
|
+
* `aggregated_output` field. The model then frequently fabricates a
|
|
268
|
+
* CHANGES_REQUESTED finding citing the failure as a "blocker"; this
|
|
269
|
+
* scan catches the failure before the fabricated finding is admitted.
|
|
270
|
+
*
|
|
271
|
+
* Returns the FIRST matching line found across `status: "failed"`
|
|
272
|
+
* command_execution items, or `null` if no failed item's output cites a
|
|
273
|
+
* sandbox-init failure.
|
|
274
|
+
*
|
|
275
|
+
* PR #112 false-positive guard (rounds 1-3): detection is gated SOLELY
|
|
276
|
+
* on `status === "failed"` items AND the signature regexes are
|
|
277
|
+
* line-anchored at column 0 (multiline `^`). Both gates are
|
|
278
|
+
* load-bearing because the Codex CLI's `handle_exec_command_end` maps
|
|
279
|
+
* any non-zero `exit_code` to `CommandExecutionStatus = "failed"`
|
|
280
|
+
* (`completed` only on `exit_code == 0`). So a legitimate `git diff
|
|
281
|
+
* --exit-code` that finds a diff arrives as `status: "failed",
|
|
282
|
+
* exit_code: 1` with diff content in `aggregated_output` — and when
|
|
283
|
+
* the PR under review touches this repo's pattern list, that diff
|
|
284
|
+
* content contains the bwrap citation prefixed by `+` / `-` / context
|
|
285
|
+
* whitespace. The line anchor ensures only stderr-shaped occurrences
|
|
286
|
+
* (citation at column 0, as real bwrap/landlock stderr always writes
|
|
287
|
+
* them) classify, not diff-prefixed source content. Without the
|
|
288
|
+
* anchor, real APPROVED / CHANGES_REQUESTED verdicts get silently
|
|
289
|
+
* erased from quorum on every PR that touches the pattern list. The
|
|
290
|
+
* SDK-thrown-Error path in `runOnce`'s catch block remains the
|
|
291
|
+
* secondary detection point for startup failures that prevent any
|
|
292
|
+
* command_execution stream from emitting.
|
|
293
|
+
*
|
|
294
|
+
* Items without an `aggregated_output` string are skipped. Pure
|
|
295
|
+
* function — exported for direct unit testing.
|
|
296
|
+
*/
|
|
297
|
+
export declare function detectSandboxInitFailureInItems(items: readonly unknown[]): string | null;
|
|
232
298
|
/**
|
|
233
299
|
* Issue #2103 — strict auth resolver. Validates `critic.auth` against
|
|
234
300
|
* the codex adapter vocabulary ({@link CODEX_AUTH_MODES}) and surfaces
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-sdk.d.ts","sourceRoot":"","sources":["../../src/adapters/codex-sdk.ts"],"names":[],"mappings":"AAuEA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,YAAY,EAClB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAQtE,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAQrB,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAChD,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AACjD,eAAO,MAAM,cAAc,eAAe,CAAC;AAwB3C,eAAO,MAAM,kBAAkB,EAAG,SAAkB,CAAC;AACrD,eAAO,MAAM,cAAc,EAAG,KAAc,CAAC;AAC7C,MAAM,MAAM,aAAa,GAAG,OAAO,kBAAkB,GAAG,OAAO,cAAc,CAAC;AAC9E,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EAGpD,CAAC;AA8BF;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,IAAI,GAAE,MAAqB,GAC1B,MAAM,GAAG,IAAI,CAkBf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,GAAG,IAAI,CAsB1D;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,KAC3B,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjC;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,aACsD,EAC5D,SAAS,SAAQ,GAChB,OAAO,CAAC,qBAAqB,CAAC,CAUhC;AAiBD,eAAO,MAAM,wBAAwB,EAAG,MAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"codex-sdk.d.ts","sourceRoot":"","sources":["../../src/adapters/codex-sdk.ts"],"names":[],"mappings":"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"}
|
|
@@ -237,6 +237,21 @@ const ALLOWED_REASONING_EFFORTS = new Set([
|
|
|
237
237
|
"xhigh",
|
|
238
238
|
]);
|
|
239
239
|
export const DEFAULT_REASONING_EFFORT = "high";
|
|
240
|
+
// Issue #68 — `sandbox_mode` param id used in `critic.model.params`;
|
|
241
|
+
// threaded into Codex's `startThread({ sandboxMode })`. Operators
|
|
242
|
+
// opt into a relaxed host-level sandbox when the container itself is
|
|
243
|
+
// the security boundary (e.g., hosted W3 worker on GKE Autopilot,
|
|
244
|
+
// where bwrap's `clone(CLONE_NEWUSER)` is rejected by the security
|
|
245
|
+
// profile and `read-only` fails at startup with `bwrap: No
|
|
246
|
+
// permissions to create a new namespace`).
|
|
247
|
+
const SANDBOX_MODE_PARAM_ID = "sandbox_mode";
|
|
248
|
+
export const CODEX_SANDBOX_MODES = [
|
|
249
|
+
"read-only",
|
|
250
|
+
"workspace-write",
|
|
251
|
+
"danger-full-access",
|
|
252
|
+
];
|
|
253
|
+
const ALLOWED_SANDBOX_MODES = new Set(CODEX_SANDBOX_MODES);
|
|
254
|
+
export const DEFAULT_SANDBOX_MODE = "read-only";
|
|
240
255
|
/**
|
|
241
256
|
* Resolve the Codex reasoning-effort from the critic config's
|
|
242
257
|
* `model.params`. Falls back to {@link DEFAULT_REASONING_EFFORT} when
|
|
@@ -257,6 +272,31 @@ export function resolveCodexReasoningEffort(critic) {
|
|
|
257
272
|
return norm;
|
|
258
273
|
return DEFAULT_REASONING_EFFORT;
|
|
259
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Issue #68 — resolve the Codex `sandboxMode` from `critic.model.params`.
|
|
277
|
+
*
|
|
278
|
+
* Falls back to {@link DEFAULT_SANDBOX_MODE} ("read-only") when unset OR
|
|
279
|
+
* when the value is malformed (typo, wrong type). The typo-tolerance is
|
|
280
|
+
* intentional: a relaxed sandbox setting silently corrupting an SDK call
|
|
281
|
+
* would be worse than the operator's misconfiguration loudly defaulting
|
|
282
|
+
* to the safe value at review time.
|
|
283
|
+
*
|
|
284
|
+
* Operators opt into `workspace-write` or `danger-full-access` when the
|
|
285
|
+
* container is the security boundary (hosted W3 worker on GKE Autopilot
|
|
286
|
+
* lacks SYS_ADMIN, so bwrap's `read-only` sandbox fails at startup with
|
|
287
|
+
* `bwrap: No permissions to create a new namespace`).
|
|
288
|
+
*/
|
|
289
|
+
export function resolveCodexSandboxMode(critic) {
|
|
290
|
+
const param = critic.model.params.find((p) => p.id === SANDBOX_MODE_PARAM_ID);
|
|
291
|
+
if (!param)
|
|
292
|
+
return DEFAULT_SANDBOX_MODE;
|
|
293
|
+
const v = param.value;
|
|
294
|
+
if (typeof v !== "string")
|
|
295
|
+
return DEFAULT_SANDBOX_MODE;
|
|
296
|
+
if (ALLOWED_SANDBOX_MODES.has(v))
|
|
297
|
+
return v;
|
|
298
|
+
return DEFAULT_SANDBOX_MODE;
|
|
299
|
+
}
|
|
260
300
|
/**
|
|
261
301
|
* Probe a thrown error for a Codex SDK structured error code. The
|
|
262
302
|
* Codex SDK surfaces errors as plain `Error` instances; some SDK
|
|
@@ -281,6 +321,150 @@ export function extractCodexErrorCode(err) {
|
|
|
281
321
|
}
|
|
282
322
|
return null;
|
|
283
323
|
}
|
|
324
|
+
// Issue #109 — environmental sandbox-init failure code surfaced on the
|
|
325
|
+
// CriticError envelope when the underlying codex CLI's sandbox primitive
|
|
326
|
+
// (bwrap, landlock) cannot initialize. Distinct from upstream / auth /
|
|
327
|
+
// quota codes; min-complete-quorum treats `status: error` (regardless of
|
|
328
|
+
// code) as "this critic produced no evidence — degrade per policy".
|
|
329
|
+
// Naming the code lets operators grep `_runs.ndjson` for
|
|
330
|
+
// `errorCode=sandbox_init_failure` separate from generic transport
|
|
331
|
+
// errors so the hosted W3 worker's container-OOM / GKE-Autopilot
|
|
332
|
+
// posture failures stay distinguishable from upstream OpenAI outages.
|
|
333
|
+
export const SANDBOX_INIT_FAILURE_CODE = "sandbox_init_failure";
|
|
334
|
+
// Issue #109 — known environmental sandbox-init failure signatures.
|
|
335
|
+
// CONSERVATIVE LIST: every entry here MUST be a citation-backed
|
|
336
|
+
// environmental error that the codex CLI emits when its sandbox layer
|
|
337
|
+
// cannot initialize, NOT a generic runtime failure mode.
|
|
338
|
+
//
|
|
339
|
+
// Adding a new signature requires:
|
|
340
|
+
// 1. A real captured failure citation (paste the literal stderr line
|
|
341
|
+
// into the comment that introduces the regex).
|
|
342
|
+
// 2. The regex must NOT match arbitrary diff content the model might
|
|
343
|
+
// copy into a finding (e.g., a diff that happens to contain the
|
|
344
|
+
// word "namespace" must not trip detection).
|
|
345
|
+
//
|
|
346
|
+
// The conservatism matters: classifying arbitrary stderr as
|
|
347
|
+
// environmental would silently swallow real findings. Operators rely on
|
|
348
|
+
// `status: complete` + a CHANGES_REQUESTED verdict as the gate's
|
|
349
|
+
// fail-closed signal; misclassifying a real finding as a sandbox-init
|
|
350
|
+
// failure would let real bugs through.
|
|
351
|
+
//
|
|
352
|
+
// Citations:
|
|
353
|
+
// - bwrap user-namespace allocation refusal (issue #109, PR #102):
|
|
354
|
+
// "bwrap: No permissions to create a new namespace"
|
|
355
|
+
// Observed on GKE Autopilot pods that lack SYS_ADMIN (the security
|
|
356
|
+
// profile rejects `clone(CLONE_NEWUSER)`).
|
|
357
|
+
// - bwrap seccomp setup refusal (same kernel-side cause, different
|
|
358
|
+
// bwrap codepath):
|
|
359
|
+
// "bwrap: Setting up seccomp failed"
|
|
360
|
+
// - bwrap mount-namespace refusal (different syscall but same
|
|
361
|
+
// unprivileged-user-namespace root cause):
|
|
362
|
+
// "bwrap: setting up uid map: Permission denied"
|
|
363
|
+
// - landlock ruleset creation refusal (codex's secondary sandbox
|
|
364
|
+
// primitive when bwrap is unavailable):
|
|
365
|
+
// "landlock_create_ruleset: Operation not permitted"
|
|
366
|
+
//
|
|
367
|
+
// Each regex anchors on the tool name + the specific failure verb AND
|
|
368
|
+
// requires the citation to start at column 0 of a line (multiline `^`).
|
|
369
|
+
// The column-0 anchor is load-bearing: the Codex CLI's
|
|
370
|
+
// `handle_exec_command_end` maps any non-zero `exit_code` to
|
|
371
|
+
// `CommandExecutionStatus = "failed"`, so a legitimate
|
|
372
|
+
// `git diff --exit-code` that finds a diff arrives with
|
|
373
|
+
// `status: "failed"` and diff content in `aggregated_output`. When the
|
|
374
|
+
// PR under review touches this repo's own pattern list, that diff content
|
|
375
|
+
// contains the bwrap citation prefixed by `+` / `-` / context whitespace.
|
|
376
|
+
// Without the line anchor those diff-prefixed occurrences classify as
|
|
377
|
+
// sandbox_init_failure and silently erase real APPROVED /
|
|
378
|
+
// CHANGES_REQUESTED verdicts from quorum (PR #112 round-3 cursor blocker).
|
|
379
|
+
// Real bwrap/landlock stderr always writes the citation at column 0,
|
|
380
|
+
// matching the anchor.
|
|
381
|
+
const SANDBOX_INIT_FAILURE_PATTERNS = Object.freeze([
|
|
382
|
+
/^bwrap:\s+No permissions to create a new namespace/im,
|
|
383
|
+
/^bwrap:\s+Setting up seccomp failed/im,
|
|
384
|
+
/^bwrap:\s+setting up uid map:\s+Permission denied/im,
|
|
385
|
+
/^landlock_create_ruleset:\s+Operation not permitted/im,
|
|
386
|
+
]);
|
|
387
|
+
/**
|
|
388
|
+
* Issue #109 — scan a string for known environmental sandbox-init
|
|
389
|
+
* failure signatures. Returns the FIRST matching substring (trimmed to
|
|
390
|
+
* the matched line) so the error envelope's detail message carries the
|
|
391
|
+
* actual citation, or `null` if no pattern matches.
|
|
392
|
+
*
|
|
393
|
+
* Pure function — exported for direct unit testing. The conservative
|
|
394
|
+
* pattern list lives in {@link SANDBOX_INIT_FAILURE_PATTERNS} (see the
|
|
395
|
+
* comment there for the "what counts as environmental" contract).
|
|
396
|
+
*/
|
|
397
|
+
export function detectSandboxInitFailure(text) {
|
|
398
|
+
if (!text)
|
|
399
|
+
return null;
|
|
400
|
+
for (const pattern of SANDBOX_INIT_FAILURE_PATTERNS) {
|
|
401
|
+
const match = pattern.exec(text);
|
|
402
|
+
if (!match)
|
|
403
|
+
continue;
|
|
404
|
+
// Anchor on the matched substring's line so the detail carries the
|
|
405
|
+
// citation literally without the surrounding 50 KB of fabricated
|
|
406
|
+
// finding text the model wrapped around it.
|
|
407
|
+
const idx = match.index;
|
|
408
|
+
const lineStart = text.lastIndexOf("\n", idx) + 1;
|
|
409
|
+
const lineEndRaw = text.indexOf("\n", idx);
|
|
410
|
+
const lineEnd = lineEndRaw === -1 ? text.length : lineEndRaw;
|
|
411
|
+
return text.slice(lineStart, lineEnd).trim();
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Issue #109 — scan a Codex turn's `items[]` for command_execution
|
|
417
|
+
* outputs whose `aggregated_output` cites a known sandbox-init failure.
|
|
418
|
+
* The codex CLI's bwrap wrapper writes its initialization error to the
|
|
419
|
+
* spawned command's stderr, which the SDK surfaces in the item's
|
|
420
|
+
* `aggregated_output` field. The model then frequently fabricates a
|
|
421
|
+
* CHANGES_REQUESTED finding citing the failure as a "blocker"; this
|
|
422
|
+
* scan catches the failure before the fabricated finding is admitted.
|
|
423
|
+
*
|
|
424
|
+
* Returns the FIRST matching line found across `status: "failed"`
|
|
425
|
+
* command_execution items, or `null` if no failed item's output cites a
|
|
426
|
+
* sandbox-init failure.
|
|
427
|
+
*
|
|
428
|
+
* PR #112 false-positive guard (rounds 1-3): detection is gated SOLELY
|
|
429
|
+
* on `status === "failed"` items AND the signature regexes are
|
|
430
|
+
* line-anchored at column 0 (multiline `^`). Both gates are
|
|
431
|
+
* load-bearing because the Codex CLI's `handle_exec_command_end` maps
|
|
432
|
+
* any non-zero `exit_code` to `CommandExecutionStatus = "failed"`
|
|
433
|
+
* (`completed` only on `exit_code == 0`). So a legitimate `git diff
|
|
434
|
+
* --exit-code` that finds a diff arrives as `status: "failed",
|
|
435
|
+
* exit_code: 1` with diff content in `aggregated_output` — and when
|
|
436
|
+
* the PR under review touches this repo's pattern list, that diff
|
|
437
|
+
* content contains the bwrap citation prefixed by `+` / `-` / context
|
|
438
|
+
* whitespace. The line anchor ensures only stderr-shaped occurrences
|
|
439
|
+
* (citation at column 0, as real bwrap/landlock stderr always writes
|
|
440
|
+
* them) classify, not diff-prefixed source content. Without the
|
|
441
|
+
* anchor, real APPROVED / CHANGES_REQUESTED verdicts get silently
|
|
442
|
+
* erased from quorum on every PR that touches the pattern list. The
|
|
443
|
+
* SDK-thrown-Error path in `runOnce`'s catch block remains the
|
|
444
|
+
* secondary detection point for startup failures that prevent any
|
|
445
|
+
* command_execution stream from emitting.
|
|
446
|
+
*
|
|
447
|
+
* Items without an `aggregated_output` string are skipped. Pure
|
|
448
|
+
* function — exported for direct unit testing.
|
|
449
|
+
*/
|
|
450
|
+
export function detectSandboxInitFailureInItems(items) {
|
|
451
|
+
for (const item of items) {
|
|
452
|
+
if (!item || typeof item !== "object")
|
|
453
|
+
continue;
|
|
454
|
+
const obj = item;
|
|
455
|
+
if (obj["type"] !== "command_execution")
|
|
456
|
+
continue;
|
|
457
|
+
if (obj["status"] !== "failed")
|
|
458
|
+
continue;
|
|
459
|
+
const output = obj["aggregated_output"];
|
|
460
|
+
if (typeof output !== "string")
|
|
461
|
+
continue;
|
|
462
|
+
const match = detectSandboxInitFailure(output);
|
|
463
|
+
if (match !== null)
|
|
464
|
+
return match;
|
|
465
|
+
}
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
284
468
|
/**
|
|
285
469
|
* Issue #2103 — strict auth resolver. Validates `critic.auth` against
|
|
286
470
|
* the codex adapter vocabulary ({@link CODEX_AUTH_MODES}) and surfaces
|
|
@@ -420,6 +604,7 @@ export class CodexSdkAdapter {
|
|
|
420
604
|
}
|
|
421
605
|
async attemptReview(packet, critic, options, attemptIdx) {
|
|
422
606
|
const reasoningEffort = resolveCodexReasoningEffort(critic);
|
|
607
|
+
const sandboxMode = resolveCodexSandboxMode(critic);
|
|
423
608
|
// Issue #2103 — strict-no-fallback auth resolution. The runner sets
|
|
424
609
|
// `critic.auth` via `applyProfileAuth()` from
|
|
425
610
|
// `profile.auth[critic.id]`; this adapter honors it without
|
|
@@ -460,6 +645,23 @@ export class CodexSdkAdapter {
|
|
|
460
645
|
adapter: this.id,
|
|
461
646
|
model: critic.model.id,
|
|
462
647
|
});
|
|
648
|
+
// Issue #68 — surface the host-level sandbox relaxation in the
|
|
649
|
+
// audit trail so operators can grep `_runs.ndjson` for runs that
|
|
650
|
+
// opted out of bwrap defense-in-depth. Fires exactly once per
|
|
651
|
+
// critic run (first attempt only, mirrors critic_run_started).
|
|
652
|
+
// Suppressed on the default path so the back-compat usage emits
|
|
653
|
+
// zero new events.
|
|
654
|
+
if (sandboxMode !== DEFAULT_SANDBOX_MODE) {
|
|
655
|
+
options.emit?.({
|
|
656
|
+
ts: new Date().toISOString(),
|
|
657
|
+
event: "sandbox_mode_overridden",
|
|
658
|
+
commit: packet.commit.sha,
|
|
659
|
+
criticId: critic.id,
|
|
660
|
+
adapter: this.id,
|
|
661
|
+
model: critic.model.id,
|
|
662
|
+
sandboxMode,
|
|
663
|
+
});
|
|
664
|
+
}
|
|
463
665
|
}
|
|
464
666
|
let codex;
|
|
465
667
|
try {
|
|
@@ -507,10 +709,18 @@ export class CodexSdkAdapter {
|
|
|
507
709
|
thread = codex.startThread({
|
|
508
710
|
workingDirectory: packet.repoRoot,
|
|
509
711
|
// Defense in depth — critic must never write files even if a
|
|
510
|
-
// malicious diff convinces the model to try.
|
|
511
|
-
// blocks WRITES, not READS, so the agent may still run
|
|
512
|
-
// commands to explore the repo (see
|
|
513
|
-
|
|
712
|
+
// malicious diff convinces the model to try. Default `read-only`
|
|
713
|
+
// sandbox blocks WRITES, not READS, so the agent may still run
|
|
714
|
+
// shell commands to explore the repo (see
|
|
715
|
+
// fixtures/spike-codex-2026-05.json). Issue #68 — operators may
|
|
716
|
+
// opt into `workspace-write` or `danger-full-access` via
|
|
717
|
+
// `critic.model.params[].sandbox_mode` when the container itself
|
|
718
|
+
// is the security boundary (e.g., hosted W3 worker on GKE
|
|
719
|
+
// Autopilot, where bwrap's `clone(CLONE_NEWUSER)` is rejected
|
|
720
|
+
// by the security profile so `read-only` fails at startup).
|
|
721
|
+
// The override emits a `sandbox_mode_overridden` telemetry
|
|
722
|
+
// event so the audit log captures the relaxation.
|
|
723
|
+
sandboxMode,
|
|
514
724
|
// No interactive prompts in non-interactive runs.
|
|
515
725
|
approvalPolicy: "never",
|
|
516
726
|
// Critic must not exfiltrate diff content to external services.
|
|
@@ -552,6 +762,43 @@ export class CodexSdkAdapter {
|
|
|
552
762
|
catch (err) {
|
|
553
763
|
const e = err;
|
|
554
764
|
const codeStr = extractCodexErrorCode(err);
|
|
765
|
+
// Issue #109 — when the codex CLI's sandbox primitive (bwrap,
|
|
766
|
+
// landlock) cannot initialize, the thrown Error's message
|
|
767
|
+
// typically carries the literal failure citation from the
|
|
768
|
+
// wrapped subprocess. Detect FIRST so we classify these as
|
|
769
|
+
// permanent + non-retryable + emit a distinct error code
|
|
770
|
+
// (`sandbox_init_failure`) instead of letting the generic
|
|
771
|
+
// transport_error path retry into the same failure 3x.
|
|
772
|
+
const sandboxCitation = detectSandboxInitFailure(e.message);
|
|
773
|
+
if (sandboxCitation !== null) {
|
|
774
|
+
options.emit?.({
|
|
775
|
+
ts: new Date().toISOString(),
|
|
776
|
+
event: "critic_run_error",
|
|
777
|
+
commit: packet.commit.sha,
|
|
778
|
+
criticId: critic.id,
|
|
779
|
+
adapter: this.id,
|
|
780
|
+
model: critic.model.id,
|
|
781
|
+
durationMs: Date.now() - startMs,
|
|
782
|
+
error: e.message,
|
|
783
|
+
status: "run_failure_permanent",
|
|
784
|
+
retryCount: attemptIdx,
|
|
785
|
+
errorCode: SANDBOX_INIT_FAILURE_CODE,
|
|
786
|
+
...(thread.id !== null ? { runId: thread.id } : {}),
|
|
787
|
+
});
|
|
788
|
+
return {
|
|
789
|
+
kind: "permanent_failure",
|
|
790
|
+
errorCode: SANDBOX_INIT_FAILURE_CODE,
|
|
791
|
+
statusMessage: null,
|
|
792
|
+
result: buildErrorResult({
|
|
793
|
+
critic,
|
|
794
|
+
message: `codex SDK sandbox-init failure (${SANDBOX_INIT_FAILURE_CODE}): ${sandboxCitation}`,
|
|
795
|
+
retryable: false,
|
|
796
|
+
code: SANDBOX_INIT_FAILURE_CODE,
|
|
797
|
+
retryCount: attemptIdx,
|
|
798
|
+
...(thread.id !== null ? { runId: thread.id } : {}),
|
|
799
|
+
}),
|
|
800
|
+
};
|
|
801
|
+
}
|
|
555
802
|
// Permanent codes are the same classification used by the Cursor
|
|
556
803
|
// adapter: auth/quota/policy failures where retrying wastes
|
|
557
804
|
// budget AND can mask the real fault.
|
|
@@ -595,6 +842,76 @@ export class CodexSdkAdapter {
|
|
|
595
842
|
agentId: null,
|
|
596
843
|
};
|
|
597
844
|
}
|
|
845
|
+
// Issue #109 — scan the executed-command items for known environmental
|
|
846
|
+
// sandbox-init failure signatures (bwrap user namespace, landlock
|
|
847
|
+
// ruleset, etc.) BEFORE the parse path admits the model's fabricated
|
|
848
|
+
// CHANGES_REQUESTED verdict.
|
|
849
|
+
//
|
|
850
|
+
// Failure mode: when the codex CLI's bwrap sandbox cannot allocate a
|
|
851
|
+
// Linux user namespace (e.g., GKE Autopilot without SYS_ADMIN), every
|
|
852
|
+
// `command_execution` item the model issues to read the diff arrives
|
|
853
|
+
// with `status: "failed"` and the bwrap error citation in its
|
|
854
|
+
// `aggregated_output` (the spawned shell never exec'd because bwrap
|
|
855
|
+
// init died). The model, unable to actually read the diff, fabricates
|
|
856
|
+
// a `[blocker] contracts` CHANGES_REQUESTED finding citing the bwrap
|
|
857
|
+
// error as evidence. Other critics in the same quorum APPROVED, but
|
|
858
|
+
// veto-quorum semantics fail-closed on the fabricated verdict.
|
|
859
|
+
//
|
|
860
|
+
// PR #112 false-positive guard (codex + cursor blockers, rounds 1-3):
|
|
861
|
+
// detection is gated on `status === "failed"` items only AND the
|
|
862
|
+
// signature regexes are line-anchored (column 0). Both gates are
|
|
863
|
+
// load-bearing because the Codex CLI's `handle_exec_command_end`
|
|
864
|
+
// maps non-zero `exit_code` to `status: "failed"` — so a legitimate
|
|
865
|
+
// `git diff --exit-code` that finds a diff arrives as `failed` with
|
|
866
|
+
// diff content (including possible `+`/`-` lines carrying the bwrap
|
|
867
|
+
// literal verbatim) in `aggregated_output`. The line anchor ensures
|
|
868
|
+
// only stderr-shaped lines (citation at column 0) classify, not
|
|
869
|
+
// diff-prefixed source content. finalResponse is also NOT scanned —
|
|
870
|
+
// a real CHANGES_REQUESTED whose evidence quotes the canonical
|
|
871
|
+
// citation must pass through. Startup failures that prevent any
|
|
872
|
+
// command_execution from emitting are caught by the SDK-thrown-error
|
|
873
|
+
// path above (see the catch block's `detectSandboxInitFailure(e.message)`
|
|
874
|
+
// scan).
|
|
875
|
+
//
|
|
876
|
+
// Under `min-complete-quorum` with `required: false` on this critic,
|
|
877
|
+
// `status: error` is non-blocking; a `status: complete` +
|
|
878
|
+
// CHANGES_REQUESTED that doesn't reflect a real code issue blocks the
|
|
879
|
+
// merge queue. Degrade to error so the quorum aggregator can route
|
|
880
|
+
// around the failure (per docs/CONSUMER-ADOPTION.md's missing-key
|
|
881
|
+
// degrade-and-pass posture, extended to environmental failures).
|
|
882
|
+
const sandboxCitation = detectSandboxInitFailureInItems(turn.items);
|
|
883
|
+
if (sandboxCitation !== null) {
|
|
884
|
+
options.emit?.({
|
|
885
|
+
ts: new Date().toISOString(),
|
|
886
|
+
event: "critic_run_error",
|
|
887
|
+
commit: packet.commit.sha,
|
|
888
|
+
criticId: critic.id,
|
|
889
|
+
adapter: this.id,
|
|
890
|
+
model: critic.model.id,
|
|
891
|
+
durationMs: Date.now() - startMs,
|
|
892
|
+
error: `sandbox-init failure cited in command_execution: ${sandboxCitation}`,
|
|
893
|
+
status: "run_failure_permanent",
|
|
894
|
+
retryCount: attemptIdx,
|
|
895
|
+
errorCode: SANDBOX_INIT_FAILURE_CODE,
|
|
896
|
+
...(thread.id !== null ? { runId: thread.id } : {}),
|
|
897
|
+
});
|
|
898
|
+
return {
|
|
899
|
+
kind: "permanent_failure",
|
|
900
|
+
errorCode: SANDBOX_INIT_FAILURE_CODE,
|
|
901
|
+
statusMessage: null,
|
|
902
|
+
result: buildErrorResult({
|
|
903
|
+
critic,
|
|
904
|
+
message: `codex sandbox-init failure (${SANDBOX_INIT_FAILURE_CODE}) cited in command_execution: ` +
|
|
905
|
+
`${sandboxCitation}. The CLI's underlying sandbox primitive could not initialize, ` +
|
|
906
|
+
`so the model could not read the diff. Any verdict in this run is fabricated from ` +
|
|
907
|
+
`the unread diff and is discarded; routing as status:error so quorum can degrade.`,
|
|
908
|
+
retryable: false,
|
|
909
|
+
code: SANDBOX_INIT_FAILURE_CODE,
|
|
910
|
+
retryCount: attemptIdx,
|
|
911
|
+
...(thread.id !== null ? { runId: thread.id } : {}),
|
|
912
|
+
}),
|
|
913
|
+
};
|
|
914
|
+
}
|
|
598
915
|
// Parse path. With `outputSchema` set, Codex enforces schema-validated
|
|
599
916
|
// JSON at the model level; the `parseAssistantJson` fallback chain is
|
|
600
917
|
// defense in depth against occasional format drift in older models or
|