@junghanacs/entwurf 0.20.1 → 0.21.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 (96) hide show
  1. package/AGENTS.md +85 -190
  2. package/BASELINE.md +4 -3
  3. package/CHANGELOG.md +195 -14
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +202 -60
  6. package/README.md +49 -22
  7. package/VERIFY.md +37 -7
  8. package/docs/acp-backend-rail.md +0 -1
  9. package/docs/external-mcp-host.md +54 -33
  10. package/docs/setup-clean-host.md +110 -17
  11. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +101 -109
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +326 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +22 -2
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +3 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -7
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-native-push.js +30 -17
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +7 -1
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +7 -4
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +20 -11
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +227 -1
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +50 -15
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/adapter.js +10 -7
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/codex-ws-client.js +403 -0
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +4 -4
  25. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-codex.js +325 -0
  26. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +6 -1
  27. package/mcp/entwurf-bridge/src/index.ts +115 -111
  28. package/mcp/entwurf-bridge/tsconfig.build.json +1 -0
  29. package/package.json +13 -4
  30. package/pi-extensions/entwurf-control.ts +71 -19
  31. package/pi-extensions/lib/codex-fresh-preflight.ts +363 -0
  32. package/pi-extensions/lib/compaction-send-guard.ts +80 -0
  33. package/pi-extensions/lib/entwurf-fact-provider.ts +29 -3
  34. package/pi-extensions/lib/entwurf-peers-render.ts +4 -1
  35. package/pi-extensions/lib/entwurf-v2-contract.ts +6 -7
  36. package/pi-extensions/lib/entwurf-v2-native-push.ts +35 -18
  37. package/pi-extensions/lib/entwurf-v2-production.ts +10 -3
  38. package/pi-extensions/lib/entwurf-v2-surface.ts +7 -4
  39. package/pi-extensions/lib/meta-mailbox-body.ts +22 -13
  40. package/pi-extensions/lib/meta-sender-identity.ts +305 -0
  41. package/pi-extensions/lib/mux-fresh-call.ts +64 -19
  42. package/pi-extensions/lib/native-push/adapter.ts +21 -24
  43. package/pi-extensions/lib/native-push/codex-ws-client.ts +506 -0
  44. package/pi-extensions/lib/native-push/register.ts +7 -9
  45. package/pi-extensions/meta-bridge-hook-codex.ts +371 -0
  46. package/run.sh +218 -28
  47. package/scripts/check-agy-sender-identity.ts +1 -1
  48. package/scripts/check-codex-birth-hook.ts +264 -0
  49. package/scripts/check-codex-bridge-identity.ts +179 -0
  50. package/scripts/check-codex-native-push.ts +386 -0
  51. package/scripts/check-codex-sender-identity.ts +495 -0
  52. package/scripts/check-compaction-send-guard.ts +130 -0
  53. package/scripts/check-copilot-receive-arm.ts +4 -1
  54. package/scripts/check-entwurf-fact-provider.ts +38 -0
  55. package/scripts/check-entwurf-peers-surface.ts +13 -1
  56. package/scripts/check-entwurf-self-address.ts +15 -16
  57. package/scripts/check-entwurf-v2-contract.ts +4 -3
  58. package/scripts/check-entwurf-v2-decider.ts +7 -5
  59. package/scripts/check-entwurf-v2-native-push.ts +35 -7
  60. package/scripts/check-entwurf-v2-production.ts +203 -11
  61. package/scripts/check-entwurf-v2-runner.ts +1 -1
  62. package/scripts/check-entwurf-v2-surface.ts +1 -1
  63. package/scripts/check-gate-qualification.ts +6 -3
  64. package/scripts/check-harness-admission-parity.ts +0 -1
  65. package/scripts/check-mux-launch-tmux.ts +47 -2
  66. package/scripts/check-native-push-adapter.ts +20 -16
  67. package/scripts/check-native-push-register.ts +5 -1
  68. package/scripts/check-release-gate-outcomes.ts +47 -1
  69. package/scripts/check-setup-qualification.sh +3 -1
  70. package/scripts/codex-birth-doctor.sh +276 -0
  71. package/scripts/codex-birth-install.sh +414 -0
  72. package/scripts/codex-birth-uninstall.sh +170 -0
  73. package/scripts/codex-mcp-config.py +435 -0
  74. package/scripts/codex-statusline-config.py +434 -0
  75. package/scripts/codex_toml_io.py +532 -0
  76. package/scripts/lib/codex-fresh-live-protocol.ts +113 -0
  77. package/scripts/lib/codex-fresh-source-receipts.ts +399 -0
  78. package/scripts/lib/launch-receipt-windows.ts +46 -0
  79. package/scripts/lib/tmux-coordinate-row.ts +58 -0
  80. package/scripts/meta-bridge-fresh-cut.ts +6 -1
  81. package/scripts/mutants/codex-native.json +838 -0
  82. package/scripts/mutants/compaction-send-guard.json +103 -0
  83. package/scripts/mutants/entwurf-peers.json +19 -0
  84. package/scripts/mutants/mux-fresh-call.json +19 -8
  85. package/scripts/mutants/omp-fresh.json +6 -4
  86. package/scripts/mutants/release-gate.json +13 -0
  87. package/scripts/mutants/v2-surface.json +53 -1
  88. package/scripts/raw-async-delivery/README.md +2 -1
  89. package/scripts/raw-codex-measure/README.md +114 -46
  90. package/scripts/smoke-agy-native-push-live.ts +3 -1
  91. package/scripts/smoke-codex-birth.sh +347 -0
  92. package/scripts/smoke-codex-config-state.sh +511 -0
  93. package/scripts/smoke-codex-fresh-live.ts +1186 -0
  94. package/scripts/smoke-codex-native-push-live.ts +75 -0
  95. package/scripts/smoke-setup-verdict.sh +123 -10
  96. package/scripts/tsconfig.json +1 -0
@@ -0,0 +1,75 @@
1
+ import assert from "node:assert/strict";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { makeProductionEntwurfV2Deps } from "../pi-extensions/lib/entwurf-v2-production.ts";
6
+ import { runEntwurfV2 } from "../pi-extensions/lib/entwurf-v2-runner.ts";
7
+ import { upsertMetaSession } from "../pi-extensions/lib/meta-session.ts";
8
+ import { probeCodexThread } from "../pi-extensions/lib/native-push/codex-ws-client.ts";
9
+ import { skipLive } from "./lib/live-skip.ts";
10
+
11
+ if (process.env.LIVE !== "1") {
12
+ skipLive(
13
+ "smoke-codex-native-push-live",
14
+ "set LIVE=1 + CODEX_LIVE_THREAD_ID to run (sends a real turn to an already-loaded Codex thread).",
15
+ );
16
+ }
17
+
18
+ const threadId = process.env.CODEX_LIVE_THREAD_ID?.trim();
19
+ if (!threadId) {
20
+ skipLive(
21
+ "smoke-codex-native-push-live",
22
+ "set CODEX_LIVE_THREAD_ID to a thread loaded by a TUI attached to the default Codex app-server.",
23
+ );
24
+ }
25
+
26
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-codex-live-"));
27
+ process.on("exit", () => fs.rmSync(root, { recursive: true, force: true }));
28
+ const sessionsDir = path.join(root, "meta-sessions");
29
+ const born = upsertMetaSession({
30
+ dir: sessionsDir,
31
+ input: {
32
+ backend: "codex",
33
+ nativeSessionId: threadId,
34
+ cwd: process.cwd(),
35
+ model: process.env.CODEX_LIVE_MODEL?.trim() || null,
36
+ transcriptPath: path.join(root, "codex-live-transcript.jsonl"),
37
+ },
38
+ });
39
+
40
+ const before = await probeCodexThread(threadId);
41
+ assert.equal(before.status, "alive", `[QK:CODEX-LIVE-PROBE] ${before.status === "alive" ? "" : before.reason}`);
42
+
43
+ const token = `CODEX-NATIVE-LIVE-${Date.now()}`;
44
+ const result = await runEntwurfV2(
45
+ {
46
+ target: born.record.gardenId,
47
+ intent: "fire-and-forget",
48
+ wantsReply: true,
49
+ message: `Entwurf native-push live acceptance. Reply with exactly ${token}`,
50
+ },
51
+ makeProductionEntwurfV2Deps({
52
+ senderProvider: () => ({
53
+ sessionId: born.record.gardenId,
54
+ agentId: "meta-session/codex",
55
+ cwd: process.cwd(),
56
+ timestamp: new Date().toISOString(),
57
+ origin: "meta-session",
58
+ replyable: true,
59
+ }),
60
+ sessionsDir,
61
+ mailboxDir: path.join(root, "mailbox"),
62
+ controlSocketDir: path.join(root, "control"),
63
+ lockDir: path.join(root, "locks"),
64
+ }),
65
+ );
66
+ assert.equal(result.kind, "executed", `[QK:CODEX-LIVE-PUBLIC-DISPATCH] ${JSON.stringify(result)}`);
67
+ assert.equal(result.kind === "executed" ? result.transport : null, "native-push");
68
+ const after = await probeCodexThread(threadId);
69
+ assert.equal(after.status, "alive", "Codex thread remains loaded after the one-shot queue acceptance");
70
+
71
+ console.log(`smoke-codex-native-push-live: delivered ${token}`);
72
+ console.log(` gardenId: ${born.record.gardenId}`);
73
+ console.log(` threadId: ${threadId}`);
74
+ console.log(` socket: ${before.status === "alive" ? before.route.socketPath : "unreachable"}`);
75
+ console.log(" verify the exact token appears in that visible Codex TUI; the delivery receipt is not the model result");
@@ -16,6 +16,9 @@
16
16
  # core and later components still attempted
17
17
  # S-8 omp present → the four omp units compose (birth/MCP/tools.xdev
18
18
  # setting/receiver), states exist, second run idempotent
19
+ # S-8c codex present → birth publishes but stays a named FAIL until the vendor trust
20
+ # receipt exists; all three atoms compose twice, preserve foreign
21
+ # config, stay idempotent, and go green on the trusted rerun
19
22
  # S-5 installed mode → the installed-vs-source branch is a NAMED verdict
20
23
  # printed before anything else, and never reaches
21
24
  # the source pnpm bootstrap
@@ -27,12 +30,15 @@
27
30
  # below-floor and false-green shapes as living end-to-end evidence.
28
31
  #
29
32
  # Deterministic: no model, no network, no cost. Presence probes are pinned via
30
- # PI_BIN / CLAUDE_BIN / AGY_BIN / COPILOT_BIN / OMP_BIN (the same hermetic seam
33
+ # PI_BIN / CLAUDE_BIN / AGY_BIN / COPILOT_BIN / OMP_BIN / CODEX_BIN (the same hermetic seam
31
34
  # smoke-agy-install-state
32
35
  # uses), and every write root is sandboxed: HOME, XDG roots, the pi agent dir,
33
- # and the dev-bin dir. The source bootstrap (`pnpm install --frozen-lockfile`)
34
- # runs against the ALREADY-INSTALLED checkout, which is a verified no-op under a
35
- # sandbox HOME (measured 2026-08-26).
36
+ # and the dev-bin dir. Source-mode cells put a strict fixture `pnpm` first on PATH:
37
+ # it accepts the no-op `install --frozen-lockfile` and the source-owned `run build-bridge`
38
+ # emit only. Package-manager behavior belongs to the package gates. This is load-bearing
39
+ # under mutation qualification, whose snapshot shares node_modules read-only — a real pnpm
40
+ # follows that symlink and attempts to rewrite the origin checkout. Any dist this gate had
41
+ # to build is removed on exit, restoring the snapshot's pre-gate surface.
36
42
  set -euo pipefail
37
43
 
38
44
  HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -51,7 +57,13 @@ esac
51
57
  export PYTHONDONTWRITEBYTECODE=1
52
58
 
53
59
  SB="$(mktemp -d -t entwurf-setup-verdict.XXXXXX)"
54
- cleanup() { rm -rf "$SB"; }
60
+ DIST_ROOT="$REPO_DIR/mcp/entwurf-bridge/dist"
61
+ DIST_PREEXISTED=0
62
+ [ -d "$DIST_ROOT" ] && DIST_PREEXISTED=1
63
+ cleanup() {
64
+ [ "$DIST_PREEXISTED" -eq 1 ] || rm -rf "$DIST_ROOT"
65
+ rm -rf "$SB"
66
+ }
55
67
  trap cleanup EXIT
56
68
 
57
69
  export HOME="$SB/home"
@@ -62,12 +74,27 @@ export XDG_CONFIG_HOME="$SB/home/.config"
62
74
  export PI_CODING_AGENT_DIR="$SB/home/.pi/agent"
63
75
  export ENTWURF_DEV_BIN_DIR="$SB/bin"
64
76
  export PATH="$SB/bin:$PATH"
65
- mkdir -p "$HOME" "$PI_CODING_AGENT_DIR" "$SB/bin" "$SB/harness"
77
+ mkdir -p "$HOME" "$PI_CODING_AGENT_DIR" "$SB/bin" "$SB/harness" "$SB/bootstrap-bin"
78
+ cat > "$SB/bootstrap-bin/pnpm" <<'SH'
79
+ #!/bin/sh
80
+ if [ "$#" -eq 2 ] && [ "$1" = install ] && [ "$2" = --frozen-lockfile ]; then
81
+ exit 0
82
+ fi
83
+ if [ "$#" -eq 2 ] && [ "$1" = run ] && [ "$2" = build-bridge ]; then
84
+ # The qualification snapshot is process-exclusive. Invoke the source-owned emit
85
+ # directly so the production lock wrapper cannot leave its ignored parent dir.
86
+ exec bash scripts/build-bridge.sh
87
+ fi
88
+ echo "fixture pnpm accepts only: install --frozen-lockfile | run build-bridge" >&2
89
+ exit 64
90
+ SH
91
+ chmod +x "$SB/bootstrap-bin/pnpm"
92
+ export PATH="$SB/bootstrap-bin:$PATH"
66
93
 
67
94
  # Presence pins: default every harness to a definitely-absent path; each cell
68
95
  # re-pins what it needs. Production leaves these unset.
69
96
  ABSENT="$SB/harness/definitely-absent"
70
- export PI_BIN="$ABSENT" CLAUDE_BIN="$ABSENT" AGY_BIN="$ABSENT" COPILOT_BIN="$ABSENT" OMP_BIN="$ABSENT"
97
+ export PI_BIN="$ABSENT" CLAUDE_BIN="$ABSENT" AGY_BIN="$ABSENT" COPILOT_BIN="$ABSENT" OMP_BIN="$ABSENT" CODEX_BIN="$ABSENT"
71
98
 
72
99
  PASS=0
73
100
  ok() { PASS=$((PASS + 1)); printf ' ok %s\n' "$*"; }
@@ -94,7 +121,8 @@ echo "[smoke-setup-verdict] S-1 all-harness-absent setup"
94
121
  PROJ1="$SB/proj1"; mkdir -p "$PROJ1"
95
122
  seed_auth
96
123
  set +e; OUT="$(bash "$REPO_DIR/run.sh" setup "$PROJ1" 2>&1)"; RC=$?; set -e
97
- want "S-1: all-absent setup exits 0 (core green is still green)" "[ '$RC' -eq 0 ]"
124
+ want "S-1: all-absent setup exits 0 with a Codex SKIP" \
125
+ "[ '$RC' -eq 0 ] && printf '%s' \"\$OUT\" | grep -q 'codex: SKIP'"
98
126
  want "S-1: mode is a named source-checkout branch, printed first" \
99
127
  "printf '%s' \"\$OUT\" | head -n 1 | grep -q 'mode: source checkout'"
100
128
  want "S-1: pi absent is an explicit zero-state SKIP" "printf '%s' \"\$OUT\" | grep -q 'pi: SKIP'"
@@ -112,6 +140,7 @@ want "S-1: no user-scope pi settings were created" "[ ! -e '$PI_CODING_AGENT_DIR
112
140
  want "S-1: no agy config was created" "[ ! -e '$HOME/.gemini' ]"
113
141
  want "S-1: no Copilot config/units were created" "[ ! -e '$HOME/.copilot' ]"
114
142
  want "S-1: no OMP config/units were created" "[ ! -e '$HOME/.omp' ]"
143
+ want "S-1: no Codex config/units were created" "[ ! -e '$HOME/.codex' ]"
115
144
  want_auth_untouched "S-1"
116
145
 
117
146
  # ── S-2: pi resolvable but BELOW floor → detected FAIL, never SKIP, no writes ──
@@ -287,6 +316,90 @@ rm -rf "$HOME/.omp" "$XDG_DATA_HOME/entwurf/omp-bridge" "$XDG_DATA_HOME/entwurf/
287
316
  "$XDG_DATA_HOME/entwurf/omp-config" "$XDG_DATA_HOME/entwurf/omp-receive" \
288
317
  "$XDG_DATA_HOME/entwurf/meta-bridge-omp" "$XDG_DATA_HOME/entwurf/omp-receive"
289
318
 
319
+ # ── S-8c: codex PRESENT → setup publishes birth, and stays NON-GREEN until the vendor agrees ──
320
+ # The birth unit's paths are the operator's own, so setup installs it here for real, inside the
321
+ # fixture's HOME/CODEX_HOME/XDG. What setup CANNOT do is answer the vendor's one-time trust
322
+ # prompt: that is the operator's, in their own visible Codex. So the first run writes every
323
+ # byte and is honestly non-green, the two config atoms compose anyway, and the run after a
324
+ # vendor receipt appears is green and byte-idempotent. Nothing here ever writes [hooks.state] —
325
+ # the receipt is forged by the FIXTURE to stand in for the vendor, never by setup.
326
+ echo "[smoke-setup-verdict] S-8c codex present — incomplete three-unit composition"
327
+ PROJ8C="$SB/proj8c"; mkdir -p "$PROJ8C"
328
+ FAKE_CODEX="$SB/harness/codex-fake"; mkdir -p "$FAKE_CODEX"
329
+ printf '#!/usr/bin/env bash\necho "codex-cli 0.153.4"\n' > "$FAKE_CODEX/codex"
330
+ chmod +x "$FAKE_CODEX/codex"
331
+ CODEX_HOME_SANDBOX="$HOME/.codex"
332
+ mkdir -p "$CODEX_HOME_SANDBOX"
333
+ cat > "$CODEX_HOME_SANDBOX/config.toml" <<'TOML'
334
+ model = "operator-model"
335
+
336
+ [projects."/operator/keep"]
337
+ trust_level = "trusted"
338
+
339
+ [mcp_servers.operator-owned]
340
+ command = "/operator/bin/server"
341
+ args = ["--keep"]
342
+
343
+ [tui]
344
+ status_line = ["model-with-reasoning"]
345
+ TOML
346
+ REAL_BASH="$(command -v bash)"
347
+ CODEX_BIRTH_UNIT_ROOT="$XDG_DATA_HOME/entwurf/codex-birth"
348
+ seed_auth
349
+ set +e
350
+ OUT="$(CODEX_BIN="$FAKE_CODEX/codex" CODEX_HOME="$CODEX_HOME_SANDBOX" PATH="$FAKE_CODEX:$PATH" "$REAL_BASH" "$REPO_DIR/run.sh" setup "$PROJ8C" 2>&1)"
351
+ RC=$?
352
+ set -e
353
+ want "[QK:CODEX-SETUP-NEVER-PRESEEDS-TRUST] S-8c: setup wrote NO vendor trust receipt of its own" \
354
+ "! grep -q 'hooks.state' '$CODEX_HOME_SANDBOX/config.toml' && ! grep -q 'trusted_hash' '$CODEX_HOME_SANDBOX/config.toml'"
355
+ want "S-8c: detected-incomplete Codex is a named nonzero result, never an absent SKIP [QK:CODEX-SETUP-BIRTH-COSMETIC-PASS]" \
356
+ "[ '$RC' -ne 0 ] && printf '%s' \"\$OUT\" | grep -q 'codex-birth: FAIL' && ! printf '%s' \"\$OUT\" | grep -q 'codex: SKIP'"
357
+ want "S-8c: both user-owned Codex atoms still compose independently as PASS [QK:CODEX-SETUP-BIRTH-INDEPENDENT]" \
358
+ "printf '%s' \"\$OUT\" | grep -q 'codex-mcp: PASS' && printf '%s' \"\$OUT\" | grep -q 'codex-statusline: PASS'"
359
+ want "S-8c: summary is NON-GREEN and attributes it to the untrusted birth unit" \
360
+ "printf '%s' \"\$OUT\" | grep -q 'NON-GREEN (FAIL: codex-birth)' && ! printf '%s' \"\$OUT\" | grep -q 'result: green'"
361
+ want "S-8c: the reason names the operator's one-time answer, not a repair setup could have made" \
362
+ "printf '%s' \"\$OUT\" | grep -q 'no trust receipt' && printf '%s' \"\$OUT\" | grep -q 'Trust all and continue'"
363
+ want "S-8c: setup PUBLISHED the birth unit — declaration, launcher closure and ownership state" \
364
+ "[ -f '$CODEX_HOME_SANDBOX/hooks.json' ] && [ -x '$CODEX_BIRTH_UNIT_ROOT/helper/codex-birth-launch.sh' ] && [ -f '$CODEX_BIRTH_UNIT_ROOT/install-state.json' ]"
365
+ CODEX_MCP_STATE="$XDG_DATA_HOME/entwurf/codex-mcp/install-state.json"
366
+ CODEX_STATUSLINE_STATE="$XDG_DATA_HOME/entwurf/codex-statusline/install-state.json"
367
+ want "S-8c: both user atoms wrote their package-owned install states inside the sandbox" \
368
+ "[ -f '$CODEX_MCP_STATE' ] && [ -f '$CODEX_STATUSLINE_STATE' ] && grep -q '\"atom\": \"codex-mcp\"' '$CODEX_MCP_STATE' && grep -q '\"atom\": \"codex-statusline\"' '$CODEX_STATUSLINE_STATE'"
369
+ want "S-8c: MCP and visible-identity atoms landed in the sandbox Codex config" \
370
+ "grep -q '\\[mcp_servers\\.entwurf-bridge\\]' '$CODEX_HOME_SANDBOX/config.toml' && grep -q 'status_line = \\[\"thread-title\", \"model-with-reasoning\"\\]' '$CODEX_HOME_SANDBOX/config.toml'"
371
+ want "S-8c: unrelated operator config survives both atom writers" \
372
+ "grep -q 'model = \"operator-model\"' '$CODEX_HOME_SANDBOX/config.toml' && grep -q '\\[mcp_servers\\.operator-owned\\]' '$CODEX_HOME_SANDBOX/config.toml' && grep -q 'command = \"/operator/bin/server\"' '$CODEX_HOME_SANDBOX/config.toml'"
373
+ CODEX_CONFIG_AFTER="$(sha256sum "$CODEX_HOME_SANDBOX/config.toml" | cut -d' ' -f1)"
374
+ CODEX_MCP_STATE_AFTER="$(sha256sum "$CODEX_MCP_STATE" | cut -d' ' -f1)"
375
+ CODEX_STATUSLINE_STATE_AFTER="$(sha256sum "$CODEX_STATUSLINE_STATE" | cut -d' ' -f1)"
376
+ set +e
377
+ OUT2="$(CODEX_BIN="$FAKE_CODEX/codex" CODEX_HOME="$CODEX_HOME_SANDBOX" PATH="$FAKE_CODEX:$PATH" "$REAL_BASH" "$REPO_DIR/run.sh" setup "$PROJ8C" 2>&1)"
378
+ RC2=$?
379
+ set -e
380
+ want "S-8c: second setup still reports the untrusted birth FAIL and both user atoms PASS" \
381
+ "[ '$RC2' -ne 0 ] && printf '%s' \"\$OUT2\" | grep -q 'codex-birth: FAIL' && printf '%s' \"\$OUT2\" | grep -q 'codex-mcp: PASS' && printf '%s' \"\$OUT2\" | grep -q 'codex-statusline: PASS'"
382
+ want "S-8c: second setup is byte-idempotent for config and both ownership receipts" \
383
+ "[ \"\$(sha256sum '$CODEX_HOME_SANDBOX/config.toml' | cut -d' ' -f1)\" = '$CODEX_CONFIG_AFTER' ] && [ \"\$(sha256sum '$CODEX_MCP_STATE' | cut -d' ' -f1)\" = '$CODEX_MCP_STATE_AFTER' ] && [ \"\$(sha256sum '$CODEX_STATUSLINE_STATE' | cut -d' ' -f1)\" = '$CODEX_STATUSLINE_STATE_AFTER' ]"
384
+ want "S-8c: second setup rechecks rather than faking the missing receipt" \
385
+ "! printf '%s' \"\$OUT2\" | grep -q 'codex-birth: PASS' && ! grep -q 'trusted_hash' '$CODEX_HOME_SANDBOX/config.toml'"
386
+
387
+ # The vendor answers. Written by the FIXTURE, exactly as the vendor writes it after one
388
+ # 'Trust all': the receipt keyed to this declaration, carrying its own hash.
389
+ printf '\n[hooks.state."%s/hooks.json:session_start:0:0"]\ntrusted_hash = "sha256:%s"\n' \
390
+ "$CODEX_HOME_SANDBOX" "$(printf '4%.0s' $(seq 64))" >> "$CODEX_HOME_SANDBOX/config.toml"
391
+ CODEX_CONFIG_TRUSTED="$(sha256sum "$CODEX_HOME_SANDBOX/config.toml" | cut -d' ' -f1)"
392
+ set +e
393
+ OUT3="$(CODEX_BIN="$FAKE_CODEX/codex" CODEX_HOME="$CODEX_HOME_SANDBOX" PATH="$FAKE_CODEX:$PATH" "$REAL_BASH" "$REPO_DIR/run.sh" setup "$PROJ8C" 2>&1)"
394
+ RC3=$?
395
+ set -e
396
+ want "[QK:CODEX-SETUP-TRUSTED-RERUN-GREEN] S-8c: once the vendor receipt exists the same setup is GREEN" \
397
+ "[ '$RC3' -eq 0 ] && printf '%s' \"\$OUT3\" | grep -q 'codex-birth: PASS' && printf '%s' \"\$OUT3\" | grep -q 'result: green'"
398
+ want "S-8c: the trusted rerun is byte-idempotent — it neither rewrote the config nor touched the receipt" \
399
+ "[ \"\$(sha256sum '$CODEX_HOME_SANDBOX/config.toml' | cut -d' ' -f1)\" = '$CODEX_CONFIG_TRUSTED' ]"
400
+ want_auth_untouched "S-8c"
401
+ rm -rf "$HOME/.codex" "$XDG_DATA_HOME/entwurf/codex-mcp" "$XDG_DATA_HOME/entwurf/codex-statusline" "$CODEX_BIRTH_UNIT_ROOT"
402
+
290
403
  # ── S-9: rail certification is a SEPARATE axis from install success (#78 D1) ──
291
404
  # The 0.20.0 macOS lane opened the installers to Darwin (meta-bridge-install.sh's
292
405
  # platform gate became its own python3/node dependency instead of the platform
@@ -379,8 +492,8 @@ PROJ9C="$SB/proj9c"; mkdir -p "$PROJ9C"
379
492
  seed_auth
380
493
  set +e; OUT="$(PATH="$FAKE_UNAME:$PATH" bash "$REPO_DIR/run.sh" setup "$PROJ9C" 2>&1)"; RC=$?; set -e
381
494
  want "S-9c: a harness-free uncertified host still exits 0" "[ '$RC' -eq 0 ]"
382
- want "S-9c: all five harness probes are still zero-state SKIPs" \
383
- "printf '%s' \"\$OUT\" | grep -q 'pi: SKIP' && printf '%s' \"\$OUT\" | grep -q 'claude: SKIP' && printf '%s' \"\$OUT\" | grep -q 'agy: SKIP' && printf '%s' \"\$OUT\" | grep -q 'copilot: SKIP' && printf '%s' \"\$OUT\" | grep -q 'omp: SKIP'"
495
+ want "S-9c: all six harness probes are still zero-state SKIPs" \
496
+ "printf '%s' \"\$OUT\" | grep -q 'pi: SKIP' && printf '%s' \"\$OUT\" | grep -q 'claude: SKIP' && printf '%s' \"\$OUT\" | grep -q 'agy: SKIP' && printf '%s' \"\$OUT\" | grep -q 'copilot: SKIP' && printf '%s' \"\$OUT\" | grep -q 'omp: SKIP' && printf '%s' \"\$OUT\" | grep -q 'codex: SKIP'"
384
497
  want "S-9c: core still PASSes and the computed summary is still green" \
385
498
  "printf '%s' \"\$OUT\" | grep -q 'core: PASS' && printf '%s' \"\$OUT\" | grep -q 'result: green (computed from the component outcomes above)'"
386
499
  want "S-9c: an absent harness is never given a rail verdict it did not earn" \
@@ -31,6 +31,7 @@
31
31
  "../pi-extensions/model-lock.ts",
32
32
  "../pi-extensions/meta-bridge-hook.ts",
33
33
  "../pi-extensions/meta-bridge-hook-copilot.ts",
34
+ "../pi-extensions/meta-bridge-hook-codex.ts",
34
35
  "../pi-extensions/meta-bridge-omp.ts",
35
36
  "../pi-extensions/meta-bridge-receive-omp.ts",
36
37
  "../mcp/entwurf-bridge/src/resume-mode.ts"