@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.
Files changed (85) hide show
  1. package/README.md +35 -38
  2. package/dist/adapters/codex-sdk.d.ts +66 -0
  3. package/dist/adapters/codex-sdk.d.ts.map +1 -1
  4. package/dist/adapters/codex-sdk.js +321 -4
  5. package/dist/adapters/codex-sdk.js.map +1 -1
  6. package/dist/adapters/critic-result-schema.d.ts +5 -0
  7. package/dist/adapters/critic-result-schema.d.ts.map +1 -1
  8. package/dist/adapters/critic-result-schema.js +10 -0
  9. package/dist/adapters/critic-result-schema.js.map +1 -1
  10. package/dist/adapters/cursor-cli.d.ts +19 -1
  11. package/dist/adapters/cursor-cli.d.ts.map +1 -1
  12. package/dist/adapters/cursor-cli.js +96 -14
  13. package/dist/adapters/cursor-cli.js.map +1 -1
  14. package/dist/branch-protection/spec-default.yaml +2 -2
  15. package/dist/cli.js +127 -81
  16. package/dist/cli.js.map +1 -1
  17. package/dist/commands/show.d.ts +11 -0
  18. package/dist/commands/show.d.ts.map +1 -0
  19. package/dist/commands/show.js +78 -0
  20. package/dist/commands/show.js.map +1 -0
  21. package/dist/commands/status.d.ts +6 -0
  22. package/dist/commands/status.d.ts.map +1 -0
  23. package/dist/commands/status.js +85 -0
  24. package/dist/commands/status.js.map +1 -0
  25. package/dist/compact/index.d.ts +2 -0
  26. package/dist/compact/index.d.ts.map +1 -0
  27. package/dist/compact/index.js +3 -0
  28. package/dist/compact/index.js.map +1 -0
  29. package/dist/compact/lockfile.d.ts +53 -0
  30. package/dist/compact/lockfile.d.ts.map +1 -0
  31. package/dist/compact/lockfile.js +626 -0
  32. package/dist/compact/lockfile.js.map +1 -0
  33. package/dist/cycle-doc-validator/validate_cycle_doc.py +39 -9
  34. package/dist/doctor.d.ts +34 -1
  35. package/dist/doctor.d.ts.map +1 -1
  36. package/dist/doctor.js +390 -1
  37. package/dist/doctor.js.map +1 -1
  38. package/dist/handoff/handoff-verb.d.ts.map +1 -1
  39. package/dist/handoff/handoff-verb.js +6 -3
  40. package/dist/handoff/handoff-verb.js.map +1 -1
  41. package/dist/handoff/ports.d.ts +8 -0
  42. package/dist/handoff/ports.d.ts.map +1 -1
  43. package/dist/handoff/real-clients.js +1 -1
  44. package/dist/handoff/real-clients.js.map +1 -1
  45. package/dist/index.d.ts +1 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +4 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/lib/show-status-core.d.ts +63 -0
  50. package/dist/lib/show-status-core.d.ts.map +1 -0
  51. package/dist/lib/show-status-core.js +156 -0
  52. package/dist/lib/show-status-core.js.map +1 -0
  53. package/dist/mcp/prompts.d.ts.map +1 -1
  54. package/dist/mcp/prompts.js +113 -78
  55. package/dist/mcp/prompts.js.map +1 -1
  56. package/dist/mcp/resources.js +1 -1
  57. package/dist/mcp/resources.js.map +1 -1
  58. package/dist/mcp/tools/findings.d.ts +0 -22
  59. package/dist/mcp/tools/findings.d.ts.map +1 -1
  60. package/dist/mcp/tools/findings.js +5 -73
  61. package/dist/mcp/tools/findings.js.map +1 -1
  62. package/dist/mcp/tools/handoff.d.ts.map +1 -1
  63. package/dist/mcp/tools/handoff.js +10 -17
  64. package/dist/mcp/tools/handoff.js.map +1 -1
  65. package/dist/mcp/tools/review-bypass.d.ts.map +1 -1
  66. package/dist/mcp/tools/review-bypass.js +1 -1
  67. package/dist/mcp/tools/review-bypass.js.map +1 -1
  68. package/dist/policy/baseline.d.ts.map +1 -1
  69. package/dist/policy/baseline.js +11 -3
  70. package/dist/policy/baseline.js.map +1 -1
  71. package/dist/prompt.d.ts.map +1 -1
  72. package/dist/prompt.js +18 -3
  73. package/dist/prompt.js.map +1 -1
  74. package/dist/report.d.ts +24 -0
  75. package/dist/report.d.ts.map +1 -1
  76. package/dist/report.js +85 -0
  77. package/dist/report.js.map +1 -1
  78. package/dist/runner.d.ts +4 -0
  79. package/dist/runner.d.ts.map +1 -1
  80. package/dist/runner.js +127 -2
  81. package/dist/runner.js.map +1 -1
  82. package/dist/trusted-surface/rebind.d.ts.map +1 -1
  83. package/dist/trusted-surface/rebind.js +185 -1
  84. package/dist/trusted-surface/rebind.js.map +1 -1
  85. 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
- All nine Dark Factory services extracted from sage3c, today consumable as a
8
- TypeScript library and (for the Python-backed and Phase D services) as `df`
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 (the same heuristic sage3c's plan-PR review gate
27
- uses) + the typed ruleset shape (`defaultMainRulesetShape`,
28
- `defaultCeReviewRulesetShape`, `defaultMergeQueueRule`) that consumers
29
- declare so the branch-protection auditor can detect drift against it.
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 the legacy `make agent-review-stats`. Every critic run,
35
- every gate verdict, every bypass invocation appends here.
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
- After Phase D all nine services are present in the package. Phase E adds the
41
- reusable GitHub workflows that consumers wire up via `uses:`, plus stub
42
- subcommands (`status-check`, `critic`) so those workflows can satisfy the
43
- five required-status-check contexts in dark-factory's own ruleset while real
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
- `0.1.0-alpha.6` — extracted from `momentiq-ai/sage3c:tools/agent-review/` +
49
- `scripts/ci/` per cycle 331.1 Phases B–F-LOCAL. Library API is stable;
50
- binary subcommands cover the full hook-facing surface (review, gate-push,
51
- doctor, gates, stats) under the subscription cost model. The Phase F
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
- # Phase C subcommands — each forwards remaining argv to the bundled Python
91
- # script verbatim, so `df <sub> --help` returns the Python argparse banner.
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
- # Phase D subcommands — pure-TS, parse flags directly.
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/cycle331.md,packages/cli/src/cli.ts
98
+ df admit-pr --files docs/roadmap/cycles/cycle1.md,packages/cli/src/cli.ts
101
99
 
102
- # Phase F-LOCAL hook-facing subcommands (subscription cost model).
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
- # Phase G MCP server (cycle 5). Stdio Model Context Protocol server
111
- # exposing the CLI surface to any MCP-speaking agent.
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
- > mirrors the sage3c-shaped branch-protection posture (e.g., asserts the
118
- > `agent-critic` and `cycle-doc-validation` required contexts). It exists
119
- > as a working starting point for first-run audits and the standalone
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 Phase F-LOCAL subcommands (`review`, `gate-push`, `doctor`, `gates`,
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 Phase C extraction
255
- bundles the original Python scripts (`validate_cycle_doc.py`,
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. The pure-TS rewrite is tracked as Phase C-PORT follow-up and will
259
- eliminate this dependency in a future release.
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. Calibrated prompts
282
- and the App's calibrated bypass-classifier are out-of-scope here and live in
283
- private repos (see [parent cycle 331](https://github.com/momentiq-ai/sage3c/blob/main/docs/roadmap/cycles/cycle331-dark-factory-platformization.md)).
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;AAKxD;;;;;;;;;GASG;AACH,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,WAAW,CAAC;CAC3D;AAED,MAAM,WAAW,WAAW;IAC1B,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,GAAG,EAAE,CACH,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,KACtD,OAAO,CAAC,eAAe,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,WAAW,GAAG,iBAAiB,GAAG,oBAAoB,CAAC;IACrE,cAAc,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,YAAY,GAAG,WAAW,CAAC;IACrE,oBAAoB,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IACvE,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GACxB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE;QACtB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClC,KAAK,WAAW,CAAC;IAClB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC3C;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,CACV,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAC3B,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjC;;;;;;;;;;;;;;OAcG;IACH,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACvD;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAQxE;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAWjE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5B,UAAU,EAAE,MAAM,GACjB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAAG,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,mBAAmB,CAAA;CAAE,CAAC,CA0D1H;AAED,qBAAa,eAAgB,YAAW,aAAa;IAgBvC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAfpC,QAAQ,CAAC,EAAE,eAAwB;IAQnC,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAM;IAEjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAGV;gBAEW,OAAO,GAAE,sBAA2B;IAY3D,MAAM,CACV,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC;YA+BV,aAAa;IAuUrB,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAoN3D"}
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. Read-only sandbox
511
- // blocks WRITES, not READS, so the agent may still run shell
512
- // commands to explore the repo (see fixtures/spike-codex-2026-05.json).
513
- sandboxMode: "read-only",
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