@junghanacs/entwurf 0.20.1 → 0.22.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 (111) hide show
  1. package/AGENTS.md +85 -190
  2. package/BASELINE.md +6 -3
  3. package/CHANGELOG.md +473 -14
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +332 -60
  6. package/README.md +100 -22
  7. package/VERIFY.md +93 -7
  8. package/docs/acp-backend-rail.md +0 -1
  9. package/docs/external-mcp-host.md +64 -33
  10. package/docs/setup-clean-host.md +151 -17
  11. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +130 -114
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-caller-seat.js +174 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +519 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +22 -2
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +3 -1
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -7
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-native-push.js +30 -17
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +7 -1
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -2
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +8 -4
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +7 -4
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +20 -11
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +227 -1
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +189 -19
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/adapter.js +10 -7
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/codex-ws-client.js +403 -0
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +4 -4
  28. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-codex.js +325 -0
  29. package/mcp/entwurf-bridge/dist/scripts/codex-socket-path.js +30 -0
  30. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +6 -1
  31. package/mcp/entwurf-bridge/src/index.ts +154 -116
  32. package/mcp/entwurf-bridge/tsconfig.build.json +10 -0
  33. package/package.json +13 -4
  34. package/pi-extensions/entwurf-control.ts +71 -19
  35. package/pi-extensions/lib/codex-caller-seat.ts +204 -0
  36. package/pi-extensions/lib/codex-fresh-preflight.ts +580 -0
  37. package/pi-extensions/lib/compaction-send-guard.ts +80 -0
  38. package/pi-extensions/lib/entwurf-fact-provider.ts +29 -3
  39. package/pi-extensions/lib/entwurf-peers-render.ts +4 -1
  40. package/pi-extensions/lib/entwurf-v2-contract.ts +6 -7
  41. package/pi-extensions/lib/entwurf-v2-native-push.ts +35 -18
  42. package/pi-extensions/lib/entwurf-v2-production.ts +10 -3
  43. package/pi-extensions/lib/entwurf-v2-runner.ts +3 -2
  44. package/pi-extensions/lib/entwurf-v2-send.ts +16 -11
  45. package/pi-extensions/lib/entwurf-v2-surface.ts +7 -4
  46. package/pi-extensions/lib/meta-mailbox-body.ts +22 -13
  47. package/pi-extensions/lib/meta-sender-identity.ts +305 -0
  48. package/pi-extensions/lib/mux-fresh-call.ts +233 -29
  49. package/pi-extensions/lib/native-push/adapter.ts +21 -24
  50. package/pi-extensions/lib/native-push/codex-ws-client.ts +506 -0
  51. package/pi-extensions/lib/native-push/register.ts +7 -9
  52. package/pi-extensions/meta-bridge-hook-codex.ts +371 -0
  53. package/run.sh +342 -28
  54. package/scripts/check-agy-sender-identity.ts +1 -1
  55. package/scripts/check-codex-app-server-launch.ts +445 -0
  56. package/scripts/check-codex-birth-hook.ts +264 -0
  57. package/scripts/check-codex-bridge-identity.ts +179 -0
  58. package/scripts/check-codex-native-push.ts +386 -0
  59. package/scripts/check-codex-sender-identity.ts +495 -0
  60. package/scripts/check-compaction-send-guard.ts +130 -0
  61. package/scripts/check-copilot-receive-arm.ts +4 -1
  62. package/scripts/check-entwurf-fact-provider.ts +38 -0
  63. package/scripts/check-entwurf-peers-surface.ts +13 -1
  64. package/scripts/check-entwurf-self-address.ts +15 -16
  65. package/scripts/check-entwurf-v2-contract.ts +4 -3
  66. package/scripts/check-entwurf-v2-decider.ts +7 -5
  67. package/scripts/check-entwurf-v2-native-push.ts +35 -7
  68. package/scripts/check-entwurf-v2-production.ts +245 -12
  69. package/scripts/check-entwurf-v2-runner.ts +1 -1
  70. package/scripts/check-entwurf-v2-send.ts +26 -7
  71. package/scripts/check-entwurf-v2-surface.ts +1 -1
  72. package/scripts/check-gate-qualification.ts +8 -3
  73. package/scripts/check-harness-admission-parity.ts +0 -1
  74. package/scripts/check-mux-launch-tmux.ts +345 -4
  75. package/scripts/check-native-push-adapter.ts +20 -16
  76. package/scripts/check-native-push-register.ts +5 -1
  77. package/scripts/check-release-gate-outcomes.ts +47 -1
  78. package/scripts/check-setup-qualification.sh +3 -1
  79. package/scripts/codex-app-server-launch.sh +275 -0
  80. package/scripts/codex-birth-doctor.sh +276 -0
  81. package/scripts/codex-birth-install.sh +414 -0
  82. package/scripts/codex-birth-uninstall.sh +170 -0
  83. package/scripts/codex-mcp-config.py +435 -0
  84. package/scripts/codex-socket-path.ts +33 -0
  85. package/scripts/codex-statusline-config.py +434 -0
  86. package/scripts/codex-terminal-title-config.py +500 -0
  87. package/scripts/codex_toml_io.py +653 -0
  88. package/scripts/lib/codex-fresh-live-protocol.ts +121 -0
  89. package/scripts/lib/codex-fresh-source-receipts.ts +426 -0
  90. package/scripts/lib/launch-receipt-windows.ts +46 -0
  91. package/scripts/lib/tmux-coordinate-row.ts +58 -0
  92. package/scripts/meta-bridge-fresh-cut.ts +6 -1
  93. package/scripts/mutants/codex-app-server-launch.json +157 -0
  94. package/scripts/mutants/codex-caller-seat.json +336 -0
  95. package/scripts/mutants/codex-native.json +838 -0
  96. package/scripts/mutants/compaction-send-guard.json +103 -0
  97. package/scripts/mutants/entwurf-peers.json +19 -0
  98. package/scripts/mutants/mux-fresh-call.json +93 -10
  99. package/scripts/mutants/omp-fresh.json +6 -4
  100. package/scripts/mutants/release-gate.json +13 -0
  101. package/scripts/mutants/v2-surface.json +75 -1
  102. package/scripts/raw-async-delivery/README.md +2 -1
  103. package/scripts/raw-codex-measure/README.md +114 -46
  104. package/scripts/smoke-agy-native-push-live.ts +3 -1
  105. package/scripts/smoke-codex-birth.sh +347 -0
  106. package/scripts/smoke-codex-config-state.sh +700 -0
  107. package/scripts/smoke-codex-fresh-live.ts +1426 -0
  108. package/scripts/smoke-codex-native-push-live.ts +75 -0
  109. package/scripts/smoke-entwurf-chain-live.ts +50 -0
  110. package/scripts/smoke-setup-verdict.sh +125 -10
  111. 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");
@@ -165,6 +165,49 @@ async function main(): Promise<void> {
165
165
  const receiversDir = path.join(world, "receivers");
166
166
  const sendersDir = path.join(world, "senders");
167
167
  for (const d of [sessionsDir, mailboxDir, receiversDir, sendersDir]) await fsp.mkdir(d, { recursive: true });
168
+ // THE WORLD'S STANDING INSTRUCTION — the authority channel for hop 1, and the reason the
169
+ // payload itself says nothing about who authorised it.
170
+ //
171
+ // `[측정 2026-09-17]` a native Claude Code hop declined to forward, twice, and both refusals
172
+ // were CORRECT readings of the operator's own global rules ("entwurf는 GLG가 명시적으로
173
+ // 요청할 때만 사용", "다른 sibling에게 일방적으로 작업을 전달하지 않는다"). The second refusal
174
+ // went further and named a provenance sentence inside the prompt as the tell it is: content
175
+ // that vouches for itself is what an injection looks like, so no amount of asserting inside
176
+ // the payload can settle the question — the more insistent the sentence, the worse it reads.
177
+ //
178
+ // What actually separates this gate from an injection is not the message but the DIRECTORY
179
+ // the turn is started in: a disposable world this gate just minted, whose every
180
+ // `ENTWURF_META_*_DIR` points inside it, created because the operator ran `release-gate` on
181
+ // this host. So the fact is written where a standing operator instruction belongs, and the
182
+ // operator's own global policy is what makes it load-bearing — `agent-config/home/AGENTS.md`
183
+ // says "For repo-specific behavior, obey the nearest AGENTS.md". Both filenames are written
184
+ // because the harnesses that can host a hop do not agree on which one they read.
185
+ //
186
+ // This is not a "do not check your policy" instruction, which would buy the same green by
187
+ // asking the model to stop reading and would keep passing after the chain broke. The hop is
188
+ // free to refuse; what it gains is the one fact it had no other way to have.
189
+ const worldStanding = [
190
+ "# entwurf release-gate scratch world",
191
+ "",
192
+ "This directory was created moments ago by entwurf's release gate `smoke-entwurf-chain-live`,",
193
+ "which the operator (GLG) started on this host by running `release-gate`. It holds fixture",
194
+ "citizens and their mailboxes and nothing else, and it is deleted when the gate ends.",
195
+ "",
196
+ "The gate measures one capability: that a payload handed to a hop reaches a mailbox terminus",
197
+ "through three sibling hops, carrying the right sender identity at each one. A turn started in",
198
+ "this directory IS one of those hops.",
199
+ "",
200
+ "- Inside this directory, forwarding the payload a hop is given to the target it names, with",
201
+ " `entwurf_v2`, is the operator's own request. It is the subject under measurement — not a",
202
+ " delegation the model chose, and not a peer lane opened on the model's initiative.",
203
+ "- Every target is a fixture citizen this gate created in this world; the `ENTWURF_META_*_DIR`",
204
+ " carriers all resolve inside this directory, so nothing outside it is addressed.",
205
+ "- The standing rule that entwurf is used only when GLG asks is satisfied by GLG having",
206
+ " started the gate. This file is the nearest `AGENTS.md`, which the operator's global",
207
+ " `AGENTS.md` defers to for behaviour scoped to a directory.",
208
+ "",
209
+ ].join("\n");
210
+ for (const name of ["AGENTS.md", "CLAUDE.md"]) await fsp.writeFile(path.join(world, name), worldStanding);
168
211
  const worldEnv = {
169
212
  ENTWURF_META_SESSIONS_DIR: sessionsDir,
170
213
  ENTWURF_META_MAILBOX_DIR: mailboxDir,
@@ -295,6 +338,13 @@ async function main(): Promise<void> {
295
338
  hop3,
296
339
  "INNER-END",
297
340
  ].join("\n");
341
+ // The payload carries NO claim about its own authority, and that is deliberate.
342
+ // `[측정 2026-09-17]` a first attempt put the provenance in these very lines — "this prompt is
343
+ // issued by entwurf's release gate, which the operator started" — and the hop named those
344
+ // sentences as the tell: *"컨텐츠가 '이것은 위임이 아니라 승인된 테스트 대상 자체다'라고
345
+ // 스스로를 정당화하는 것도 전형적인 수법입니다."* It was right. A message that vouches for
346
+ // itself is exactly the shape of an injection, so authority cannot ride the payload; it rides
347
+ // the WORLD this turn is started in (see the standing instruction written next to `world`).
298
348
  const hop1Prompt = [
299
349
  "You are the first hop of a delivery chain. Do exactly this and nothing else.",
300
350
  `Call the tool mcp__entwurf-bridge__entwurf_v2 exactly once with target=${gidB}, intent=fire-and-forget,`,
@@ -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,92 @@ 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: all three 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' && printf '%s' \"\$OUT\" | grep -q 'codex-terminal-title: 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
+ CODEX_TERMINAL_TITLE_STATE="$XDG_DATA_HOME/entwurf/codex-terminal-title/install-state.json"
368
+ want "S-8c: all three user atoms wrote their package-owned install states inside the sandbox" \
369
+ "[ -f '$CODEX_MCP_STATE' ] && [ -f '$CODEX_STATUSLINE_STATE' ] && [ -f '$CODEX_TERMINAL_TITLE_STATE' ] && grep -q '\"atom\": \"codex-mcp\"' '$CODEX_MCP_STATE' && grep -q '\"atom\": \"codex-statusline\"' '$CODEX_STATUSLINE_STATE' && grep -q '\"atom\": \"codex-terminal-title\"' '$CODEX_TERMINAL_TITLE_STATE'"
370
+ want "S-8c: MCP, visible-identity and caller-seat atoms landed in the sandbox Codex config" \
371
+ "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' && grep -q 'terminal_title = \\[\"activity\", \"project-name\", \"thread-id\"\\]' '$CODEX_HOME_SANDBOX/config.toml'"
372
+ want "S-8c: unrelated operator config survives both atom writers" \
373
+ "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'"
374
+ CODEX_CONFIG_AFTER="$(sha256sum "$CODEX_HOME_SANDBOX/config.toml" | cut -d' ' -f1)"
375
+ CODEX_MCP_STATE_AFTER="$(sha256sum "$CODEX_MCP_STATE" | cut -d' ' -f1)"
376
+ CODEX_STATUSLINE_STATE_AFTER="$(sha256sum "$CODEX_STATUSLINE_STATE" | cut -d' ' -f1)"
377
+ CODEX_TERMINAL_TITLE_STATE_AFTER="$(sha256sum "$CODEX_TERMINAL_TITLE_STATE" | cut -d' ' -f1)"
378
+ set +e
379
+ 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)"
380
+ RC2=$?
381
+ set -e
382
+ want "S-8c: second setup still reports the untrusted birth FAIL and all three user atoms PASS" \
383
+ "[ '$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' && printf '%s' \"\$OUT2\" | grep -q 'codex-terminal-title: PASS'"
384
+ want "S-8c: second setup is byte-idempotent for config and all three ownership receipts" \
385
+ "[ \"\$(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' ] && [ \"\$(sha256sum '$CODEX_TERMINAL_TITLE_STATE' | cut -d' ' -f1)\" = '$CODEX_TERMINAL_TITLE_STATE_AFTER' ]"
386
+ want "S-8c: second setup rechecks rather than faking the missing receipt" \
387
+ "! printf '%s' \"\$OUT2\" | grep -q 'codex-birth: PASS' && ! grep -q 'trusted_hash' '$CODEX_HOME_SANDBOX/config.toml'"
388
+
389
+ # The vendor answers. Written by the FIXTURE, exactly as the vendor writes it after one
390
+ # 'Trust all': the receipt keyed to this declaration, carrying its own hash.
391
+ printf '\n[hooks.state."%s/hooks.json:session_start:0:0"]\ntrusted_hash = "sha256:%s"\n' \
392
+ "$CODEX_HOME_SANDBOX" "$(printf '4%.0s' $(seq 64))" >> "$CODEX_HOME_SANDBOX/config.toml"
393
+ CODEX_CONFIG_TRUSTED="$(sha256sum "$CODEX_HOME_SANDBOX/config.toml" | cut -d' ' -f1)"
394
+ set +e
395
+ 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)"
396
+ RC3=$?
397
+ set -e
398
+ want "[QK:CODEX-SETUP-TRUSTED-RERUN-GREEN] S-8c: once the vendor receipt exists the same setup is GREEN" \
399
+ "[ '$RC3' -eq 0 ] && printf '%s' \"\$OUT3\" | grep -q 'codex-birth: PASS' && printf '%s' \"\$OUT3\" | grep -q 'result: green'"
400
+ want "S-8c: the trusted rerun is byte-idempotent — it neither rewrote the config nor touched the receipt" \
401
+ "[ \"\$(sha256sum '$CODEX_HOME_SANDBOX/config.toml' | cut -d' ' -f1)\" = '$CODEX_CONFIG_TRUSTED' ]"
402
+ want_auth_untouched "S-8c"
403
+ rm -rf "$HOME/.codex" "$XDG_DATA_HOME/entwurf/codex-mcp" "$XDG_DATA_HOME/entwurf/codex-statusline" "$XDG_DATA_HOME/entwurf/codex-terminal-title" "$CODEX_BIRTH_UNIT_ROOT"
404
+
290
405
  # ── S-9: rail certification is a SEPARATE axis from install success (#78 D1) ──
291
406
  # The 0.20.0 macOS lane opened the installers to Darwin (meta-bridge-install.sh's
292
407
  # platform gate became its own python3/node dependency instead of the platform
@@ -379,8 +494,8 @@ PROJ9C="$SB/proj9c"; mkdir -p "$PROJ9C"
379
494
  seed_auth
380
495
  set +e; OUT="$(PATH="$FAKE_UNAME:$PATH" bash "$REPO_DIR/run.sh" setup "$PROJ9C" 2>&1)"; RC=$?; set -e
381
496
  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'"
497
+ want "S-9c: all six harness probes are still zero-state SKIPs" \
498
+ "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
499
  want "S-9c: core still PASSes and the computed summary is still green" \
385
500
  "printf '%s' \"\$OUT\" | grep -q 'core: PASS' && printf '%s' \"\$OUT\" | grep -q 'result: green (computed from the component outcomes above)'"
386
501
  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"