@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,1426 @@
1
+ /**
2
+ * First-admission LIVE acceptance for Codex visible fresh — reshaped for the #95 lane B caller
3
+ * seat.
4
+ *
5
+ * The operator owns the Codex app-server and the tmux session it sits in (#95 D1 retired the fixed
6
+ * `codex` home). This smoke neither creates nor supervises them: an explicit
7
+ * ENTWURF_CODEX_APP_SERVER_PID names the already-running process whose /proc environment must
8
+ * corroborate its seat. The attached TUI pane is never guessed.
9
+ *
10
+ * ── THE TOPOLOGY, AND WHY IT IS DELIBERATELY SPLIT (#95 lane B) ──
11
+ *
12
+ * Four coordinates on ONE tmux server, in TWO sessions:
13
+ *
14
+ * A = wherever the operator started their app-server, and it must NOT be S. This is the
15
+ * session the app-server's inherited `TMUX` names — the value a Codex MCP child would
16
+ * fall back to. Its NAME does not matter: #95 D1 retired the fixed `codex` home, so the
17
+ * operator seats the app-server wherever they like and Entwurf never asks which room.
18
+ * S = the fixture's own session. The initial Pi caller opens here.
19
+ *
20
+ * initial Pi → S (the fixture's own seat)
21
+ * fresh Codex → S (hop 1, omitted placement: the caller's own session)
22
+ * outbound Pi → S (hop 2, omitted placement — and this is the claim)
23
+ *
24
+ * Hop 2 is the whole point. Its seat is decided by the Codex caller's own pane title
25
+ * (`codex-title-anchor`), and the proof is that it lands in S: the app-server's environment
26
+ * points at A, so an env fallback — the pre-#95 behaviour — would have put it there. S is
27
+ * reachable only through the anchor. A receipt that says `codex-title-anchor` AND a session
28
+ * that is not A are two independent halves of the same claim, and both are asserted.
29
+ *
30
+ * A ≠ S is therefore a PRECONDITION of this card, not a convenience: run it with the app-server
31
+ * in the fixture's own session and hop 2 proves nothing, because both rules would answer S.
32
+ *
33
+ * RELEASE MUST once Codex is admitted. LIVE!=1 is the only host-prerequisite SKIP. With LIVE=1,
34
+ * missing system birth, user MCP/status/terminal-title config, app-server, models, runtime,
35
+ * bridge, or tmux seat is a FAIL. The smoke drives only public MCP calls, then joins Pi's native
36
+ * toolCall/toolResult rows and Codex app-server thread events as receipt authority; screen text
37
+ * and model-reformatted reports are never oracles.
38
+ *
39
+ * A record-backed self-fetch fixture opens a real visible Pi caller through public fresh_call.
40
+ * That Pi opens Codex, correlates Codex's raw callback, and addresses Codex through v2. Codex then
41
+ * opens outbound Pi, correlates that raw callback, and sends one final exact message that the
42
+ * fixture must actually read.
43
+ */
44
+
45
+ import type { ChildProcess } from "node:child_process";
46
+ import { spawn, spawnSync } from "node:child_process";
47
+ import { createHash } from "node:crypto";
48
+ import * as fs from "node:fs";
49
+ import * as os from "node:os";
50
+ import * as path from "node:path";
51
+ import {
52
+ CODEX_CALLER_PREFLIGHT_HINT,
53
+ CODEX_LAUNCH_CWD_PREFLIGHT_HINT,
54
+ CODEX_PREFLIGHT_HINT,
55
+ codexCallerFreshPreflight,
56
+ codexFreshPreflight,
57
+ codexLaunchCwdFreshPreflight,
58
+ } from "../pi-extensions/lib/codex-fresh-preflight.ts";
59
+ import {
60
+ defaultMetaMailboxDir,
61
+ defaultMetaSessionsDir,
62
+ metaRecordFilename,
63
+ readMetaIdentityByGardenId,
64
+ readMetaInbox,
65
+ upsertMetaSession,
66
+ writeMetaReceiverMarker,
67
+ writeMetaSenderMarker,
68
+ } from "../pi-extensions/lib/meta-session.ts";
69
+ import { FRESH_CALL_BACKENDS } from "../pi-extensions/lib/mux-fresh-call.ts";
70
+ import {
71
+ readCodexThread,
72
+ realCodexProtocolOpener,
73
+ resolveCodexDefaultSocketPath,
74
+ } from "../pi-extensions/lib/native-push/codex-ws-client.ts";
75
+ import {
76
+ buildCodexInstruction,
77
+ buildInitialPiPhaseOne,
78
+ runCleanupStages,
79
+ selectReport,
80
+ } from "./lib/codex-fresh-live-protocol.ts";
81
+ import {
82
+ assertExactSourceToolCalls,
83
+ assertSourceCallScopes,
84
+ codexCallbackSenders,
85
+ codexSourceToolReceipts,
86
+ codexThreadIsTerminal,
87
+ mailboxCallbacks,
88
+ parseJsonLines,
89
+ parseMetaMailboxBody,
90
+ piCallbackSenders,
91
+ piSourceToolReceipts,
92
+ resolveSourceTranscriptPath,
93
+ selectExactSourceToolReceipt,
94
+ sourceCleanupWindows,
95
+ validateFinalMailboxBody,
96
+ } from "./lib/codex-fresh-source-receipts.ts";
97
+ import { skipLive } from "./lib/live-skip.ts";
98
+ import { parseTmuxRow, TMUX_COORDINATE_FIELDS, TMUX_COORDINATE_FORMAT } from "./lib/tmux-coordinate-row.ts";
99
+
100
+ const LABEL = "smoke-codex-fresh-live";
101
+ const REPO = path.resolve(import.meta.dirname, "..");
102
+ const GARDEN_ID = /^\d{8}T\d{6}-[0-9a-f]{6}$/;
103
+ const WINDOW_ID = /^@\d+$/;
104
+ const CALLBACK_WAIT_MS = 300_000;
105
+ // Source calls in accepted runs arrive in under a minute. Five minutes preserves cold-start room
106
+ // without repeating the old blind ten-minute model-report wait.
107
+ const SOURCE_WAIT_MS = 300_000;
108
+ const META_CONTEXT_ENV_KEYS = [
109
+ "HOME",
110
+ "CODEX_HOME",
111
+ "PI_CODING_AGENT_DIR",
112
+ "ENTWURF_META_SESSIONS_DIR",
113
+ "ENTWURF_META_MAILBOX_DIR",
114
+ "ENTWURF_META_SENDERS_DIR",
115
+ "ENTWURF_META_RECEIVERS_DIR",
116
+ ] as const;
117
+
118
+ type ToolResult = { text: string; isError: boolean };
119
+ type RpcReply = { result?: unknown; error?: unknown };
120
+ interface TmuxCoordinate {
121
+ serverPid: string;
122
+ sessionId: string;
123
+ windowId: string;
124
+ paneId: string;
125
+ }
126
+
127
+ const receipts: Record<string, string> = {};
128
+ let passed = 0;
129
+ let appServerEnv: NodeJS.ProcessEnv | null = null;
130
+ let bridge: BridgeClient | null = null;
131
+ const openedWindows: string[] = [];
132
+ const fixtureArtifacts: string[] = [];
133
+ // What a LATER receipt would have named. A timeout is exactly where a visible window
134
+ // becomes an orphan, so the two authorities that already hold the exact handles — the
135
+ // caller Pi's own transcript and the Codex thread's own tool-call results — are recorded
136
+ // as they become known.
137
+ let codexSocketPath = "";
138
+ let initialPiTranscript = "";
139
+ let initialPiGardenId = "";
140
+ // Set once run() owns its evidence file, so a path resolved DURING a poll is recorded there too.
141
+ let recordEvidence: ((patch: Record<string, unknown>) => void) | null = null;
142
+ let codexLaunchNonce = "";
143
+ let codexThreadId = "";
144
+ let codexGardenId = "";
145
+ // Reachable from the EXIT path, not just from a wait: an assertion that throws must still be
146
+ // able to ask the fixture's own mailbox which windows this run opened.
147
+ let fixtureGid = "";
148
+ let evidenceDir = "";
149
+ const seenInbox: string[] = [];
150
+ const expectedReportTokens: string[] = [];
151
+ const relatedRecordPaths = new Set<string>();
152
+ const relatedTranscripts = new Set<string>();
153
+
154
+ function ok(label: string, condition: unknown, detail = ""): asserts condition {
155
+ if (!condition) throw new Error(`${label}${detail ? `\n${detail}` : ""}`);
156
+ console.log(` ok ${label}`);
157
+ passed++;
158
+ }
159
+
160
+ function printReceipts(): void {
161
+ if (Object.keys(receipts).length === 0) return;
162
+ console.log(`\n[${LABEL}] decisive receipts`);
163
+ for (const [name, receipt] of Object.entries(receipts)) console.log(`--- ${name} ---\n${receipt}`);
164
+ }
165
+
166
+ function parseNulFile(file: string): string[] {
167
+ return fs
168
+ .readFileSync(file)
169
+ .toString("utf8")
170
+ .split("\0")
171
+ .filter((value) => value.length > 0);
172
+ }
173
+
174
+ function readProcessEnvironment(pid: number): NodeJS.ProcessEnv {
175
+ const result: NodeJS.ProcessEnv = {};
176
+ for (const entry of parseNulFile(`/proc/${pid}/environ`)) {
177
+ const equals = entry.indexOf("=");
178
+ if (equals > 0) result[entry.slice(0, equals)] = entry.slice(equals + 1);
179
+ }
180
+ return result;
181
+ }
182
+
183
+ function resolveExecutable(name: string, env: NodeJS.ProcessEnv): string | null {
184
+ for (const dir of (env.PATH ?? "").split(path.delimiter)) {
185
+ if (!dir) continue;
186
+ const candidate = path.resolve(dir, name);
187
+ try {
188
+ fs.accessSync(candidate, fs.constants.X_OK);
189
+ if (!fs.statSync(candidate).isFile()) continue;
190
+ return candidate;
191
+ } catch {
192
+ // Continue through the app-server's literal PATH; do not consult a shell alias.
193
+ }
194
+ }
195
+ return null;
196
+ }
197
+
198
+ function inspectTmuxCoordinate(label: string, env: NodeJS.ProcessEnv): TmuxCoordinate {
199
+ const tmuxValue = env.TMUX ?? "";
200
+ const paneValue = env.TMUX_PANE ?? "";
201
+ const tmuxMatch = /^(.*),(\d+),(\d+)$/.exec(tmuxValue);
202
+ ok(
203
+ `${label} carries an inspectable absolute TMUX socket tuple`,
204
+ tmuxMatch !== null && path.isAbsolute(tmuxMatch[1] ?? ""),
205
+ `TMUX=${JSON.stringify(tmuxValue)}`,
206
+ );
207
+ ok(`${label} carries a native TMUX_PANE handle`, /^%\d+$/.test(paneValue));
208
+ const inspected = spawnSync("tmux", ["display-message", "-p", "-t", paneValue, TMUX_COORDINATE_FORMAT], {
209
+ env,
210
+ encoding: "utf8",
211
+ });
212
+ ok(
213
+ `${label} tmux coordinate answers through its own environment`,
214
+ inspected.status === 0,
215
+ String(inspected.stderr ?? ""),
216
+ );
217
+ // Positionally, with empty fields preserved: a format key that answers with nothing must
218
+ // fail at ITS coordinate instead of shifting every later one left (measured on tmux 3.6a,
219
+ // where `#{server_pid}` is empty — see scripts/lib/tmux-coordinate-row.ts).
220
+ let row: string[];
221
+ try {
222
+ row = parseTmuxRow(String(inspected.stdout ?? ""), TMUX_COORDINATE_FIELDS);
223
+ } catch (error) {
224
+ throw new Error(
225
+ `${label} tmux coordinate row is malformed: ${error instanceof Error ? error.message : String(error)}`,
226
+ );
227
+ }
228
+ const [serverPid, sessionId, windowId, paneId] = row;
229
+ ok(
230
+ `${label} resolves its exact server/session/window/pane coordinate`,
231
+ serverPid === tmuxMatch?.[2] &&
232
+ /^\$\d+$/.test(sessionId ?? "") &&
233
+ WINDOW_ID.test(windowId ?? "") &&
234
+ paneId === paneValue,
235
+ `coordinate=${JSON.stringify({ serverPid, sessionId, windowId, paneId })}`,
236
+ );
237
+ return {
238
+ serverPid: serverPid as string,
239
+ sessionId: sessionId as string,
240
+ windowId: windowId as string,
241
+ paneId: paneId as string,
242
+ };
243
+ }
244
+
245
+ /**
246
+ * The session NAME behind a coordinate. Read on its own rather than added to the shared
247
+ * `TMUX_COORDINATE_FORMAT`: that row's field count is a pinned contract with its own
248
+ * deterministic oracle, and this card needs a name for exactly two reasons — to hand the fresh
249
+ * hand the fresh call a seat it can address, and to record which rooms A and S actually were.
250
+ */
251
+ function tmuxSessionName(label: string, env: NodeJS.ProcessEnv, paneId: string): string {
252
+ const inspected = spawnSync("tmux", ["display-message", "-p", "-t", paneId, "#{session_name}"], {
253
+ env,
254
+ encoding: "utf8",
255
+ });
256
+ ok(
257
+ `${label} session name answers through its own environment`,
258
+ inspected.status === 0,
259
+ String(inspected.stderr ?? ""),
260
+ );
261
+ const name = String(inspected.stdout ?? "").replace(/\r?\n$/, "");
262
+ ok(`${label} session name is non-empty`, name.length > 0, `name=${JSON.stringify(name)}`);
263
+ return name;
264
+ }
265
+
266
+ /**
267
+ * The DIRECTORY behind a pane, read from tmux itself. Its own reader for the same reason the
268
+ * session name has one: this card needs it for exactly one claim (#95 lane C — where a sibling
269
+ * actually started), and the pinned coordinate row stays a fixed field count.
270
+ *
271
+ * `[측정 2026-09-16]` this value is read from the child's `/proc` at QUERY time and tmux chdirs
272
+ * in that child AFTER forking it, so a read taken in the same millisecond as a launch can still
273
+ * answer the SERVER's directory. Every call here happens after that citizen's own callback has
274
+ * already arrived, which is long past exec — the deterministic gate that reads it straight after
275
+ * a launch is the one that has to wait (`scripts/check-mux-launch-tmux.ts`).
276
+ */
277
+ function tmuxPaneCwd(label: string, env: NodeJS.ProcessEnv, paneId: string): string {
278
+ const inspected = spawnSync("tmux", ["display-message", "-p", "-t", paneId, "#{pane_current_path}"], {
279
+ env,
280
+ encoding: "utf8",
281
+ });
282
+ ok(
283
+ `${label} pane directory answers through its own environment`,
284
+ inspected.status === 0,
285
+ String(inspected.stderr ?? ""),
286
+ );
287
+ const dir = String(inspected.stdout ?? "").replace(/\r?\n$/, "");
288
+ ok(`${label} pane directory is non-empty`, dir.length > 0, `cwd=${JSON.stringify(dir)}`);
289
+ return dir;
290
+ }
291
+
292
+ /** A live process's own working directory, read from the kernel rather than from any report. */
293
+ function processCwd(pid: number): string {
294
+ return fs.readlinkSync(`/proc/${pid}/cwd`);
295
+ }
296
+
297
+ /** The `session_meta` header of a Codex rollout — the VENDOR's own record of where a thread opened. */
298
+ function rolloutSessionMetaCwd(transcriptPath: string): string {
299
+ const first = fs.readFileSync(transcriptPath, "utf8").split("\n", 1)[0] ?? "";
300
+ const row = JSON.parse(first) as { type?: unknown; payload?: { cwd?: unknown } };
301
+ if (row.type !== "session_meta") throw new Error(`rollout row 1 is ${JSON.stringify(row.type)}, not session_meta`);
302
+ const cwd = row.payload?.cwd;
303
+ if (typeof cwd !== "string" || cwd.length === 0) throw new Error("rollout session_meta carries no cwd string");
304
+ return cwd;
305
+ }
306
+
307
+ function cleanupWindows(): string[] {
308
+ const failures: string[] = [];
309
+ if (!appServerEnv) return failures;
310
+ for (const windowId of [...openedWindows].reverse()) {
311
+ if (!WINDOW_ID.test(windowId)) {
312
+ failures.push(`refused malformed recorded window handle ${JSON.stringify(windowId)}`);
313
+ continue;
314
+ }
315
+ const killed = spawnSync("tmux", ["kill-window", "-t", windowId], {
316
+ env: appServerEnv,
317
+ encoding: "utf8",
318
+ });
319
+ if (killed.status !== 0) {
320
+ failures.push(
321
+ `${windowId}: tmux kill-window failed (${killed.status ?? "signal"}): ${(killed.stderr ?? killed.stdout ?? "").trim()}`,
322
+ );
323
+ } else {
324
+ console.log(` cleanup removed smoke-opened window ${windowId}`);
325
+ }
326
+ }
327
+ openedWindows.length = 0;
328
+ return failures;
329
+ }
330
+
331
+ async function preservePreCleanupEvidence(reason: unknown): Promise<string[]> {
332
+ if (evidenceDir === "") return [];
333
+ const failures: string[] = [];
334
+ const snapshot = path.join(evidenceDir, "pre-cleanup-snapshot");
335
+ try {
336
+ fs.mkdirSync(snapshot, { recursive: true, mode: 0o700 });
337
+ fs.writeFileSync(
338
+ path.join(snapshot, "reason.txt"),
339
+ `${reason instanceof Error ? reason.stack : String(reason)}\n`,
340
+ {
341
+ mode: 0o600,
342
+ },
343
+ );
344
+ } catch (snapshotError) {
345
+ return [`failure evidence root: ${String(snapshotError)}`];
346
+ }
347
+ // Recovery is optional enrichment. A corrupt source is exactly when the raw files matter most,
348
+ // so its failure is recorded but can never prevent the known sources below from being copied.
349
+ try {
350
+ recoverCodexThreadAuthority();
351
+ } catch (recoveryError) {
352
+ failures.push(`optional identity recovery: ${String(recoveryError)}`);
353
+ }
354
+ const sources = [
355
+ ...(fixtureGid === "" ? [] : [path.join(defaultMetaMailboxDir(), fixtureGid)]),
356
+ ...fixtureArtifacts,
357
+ ...relatedRecordPaths,
358
+ ...relatedTranscripts,
359
+ ];
360
+ for (const [index, source] of sources.entries()) {
361
+ if (!fs.existsSync(source)) {
362
+ failures.push(`missing expected evidence source ${source}`);
363
+ continue;
364
+ }
365
+ try {
366
+ fs.cpSync(source, path.join(snapshot, `${String(index).padStart(2, "0")}-${path.basename(source)}`), {
367
+ recursive: true,
368
+ });
369
+ } catch (copyError) {
370
+ failures.push(`${source}: ${String(copyError)}`);
371
+ }
372
+ }
373
+ if (codexThreadId !== "" && codexSocketPath !== "") {
374
+ try {
375
+ const thread = await readCodexThread(codexSocketPath, codexThreadId, true);
376
+ fs.writeFileSync(path.join(snapshot, "codex-thread.json"), `${JSON.stringify(thread, null, 2)}\n`, {
377
+ mode: 0o600,
378
+ });
379
+ } catch (threadError) {
380
+ failures.push(`Codex thread ${codexThreadId}: ${String(threadError)}`);
381
+ }
382
+ }
383
+ console.error(` evidence preserved pre-cleanup snapshot at ${snapshot}`);
384
+ return failures;
385
+ }
386
+
387
+ function cleanupFixture(): string[] {
388
+ const failures: string[] = [];
389
+ for (const artifact of fixtureArtifacts.reverse()) {
390
+ try {
391
+ fs.rmSync(artifact, { recursive: true, force: true });
392
+ } catch (error) {
393
+ failures.push(`${artifact}: ${String(error)}`);
394
+ }
395
+ }
396
+ fixtureArtifacts.length = 0;
397
+ return failures;
398
+ }
399
+
400
+ function readInitialPiEntries(): unknown[] {
401
+ const before = initialPiTranscript;
402
+ initialPiTranscript = resolveSourceTranscriptPath(initialPiTranscript, () =>
403
+ initialPiGardenId === "" ? "" : readMetaIdentityByGardenId(initialPiGardenId).transcriptPath,
404
+ );
405
+ if (initialPiTranscript !== before && initialPiTranscript !== "") {
406
+ relatedTranscripts.add(initialPiTranscript);
407
+ recordEvidence?.({ initialPiTranscript, initialPiTranscriptResolvedLate: true });
408
+ }
409
+ if (initialPiTranscript === "" || !fs.existsSync(initialPiTranscript)) return [];
410
+ return parseJsonLines(fs.readFileSync(initialPiTranscript, "utf8"));
411
+ }
412
+
413
+ /** Exact joined fresh-call receipts from the initial Pi's own call/result rows. */
414
+ function readInitialPiFreshReceipts(backend: "codex" | "pi"): string[] {
415
+ return piSourceToolReceipts(readInitialPiEntries())
416
+ .filter(
417
+ (receipt) =>
418
+ receipt.toolName === "entwurf_fresh_call" &&
419
+ !receipt.isError &&
420
+ new RegExp(`^\\[entwurf fresh call →\\]\\n\\s*backend:\\s+${backend}\\b`, "m").test(receipt.text),
421
+ )
422
+ .map((receipt) => receipt.text);
423
+ }
424
+
425
+ /** The Codex window handle as the initial Pi caller's OWN transcript recorded its tool result. */
426
+ function recoverCodexWindows(): string[] {
427
+ return sourceCleanupWindows(piSourceToolReceipts(readInitialPiEntries()));
428
+ }
429
+
430
+ /** Recover the exact launch nonce from the initial Pi's one raw Codex tool receipt. */
431
+ function recoverCodexLaunchNonce(): void {
432
+ if (codexLaunchNonce !== "") return;
433
+ const receipts = readInitialPiFreshReceipts("codex");
434
+ if (receipts.length !== 1) return;
435
+ codexLaunchNonce = /^\s*nonce:\s*(mux-fresh-call-[0-9a-f]+)/m.exec(receipts[0] ?? "")?.[1] ?? "";
436
+ }
437
+
438
+ /**
439
+ * Recover the launched Codex thread from its exact nonce callback in the initial Pi transcript,
440
+ * then validate that carrier through the V3 record authority. The ordinary and failure paths use
441
+ * the same source-owned callback; model report prose has no identity role.
442
+ */
443
+ function recoverCodexThreadAuthority(): void {
444
+ recoverCodexLaunchNonce();
445
+ if (codexThreadId !== "" || initialPiTranscript === "" || codexLaunchNonce === "") return;
446
+ try {
447
+ const senders = piCallbackSenders(readInitialPiEntries(), codexLaunchNonce);
448
+ if (senders.length > 1) throw new Error(`duplicate exact Codex callbacks (${senders.length})`);
449
+ const sender = senders[0];
450
+ if (sender === undefined || !GARDEN_ID.test(sender)) return;
451
+ const identity = readMetaIdentityByGardenId(sender);
452
+ if (identity.backend !== "codex") return;
453
+ codexGardenId = sender;
454
+ codexThreadId = identity.nativeSessionId;
455
+ relatedRecordPaths.add(path.join(defaultMetaSessionsDir(), metaRecordFilename(identity)));
456
+ if (identity.transcriptPath != null) relatedTranscripts.add(identity.transcriptPath);
457
+ console.error(` recovered Codex thread ${codexThreadId} from its exact nonce callback`);
458
+ return;
459
+ } catch {
460
+ // The later cleanup stages still remove every stable window handle already recorded.
461
+ }
462
+ }
463
+
464
+ /** The outbound Pi handle as the Codex thread's own mcpToolCall result recorded it. */
465
+ async function recoverOutboundPiWindows(): Promise<string[]> {
466
+ if (codexThreadId === "" || codexSocketPath === "") return [];
467
+ try {
468
+ const thread = await readCodexThread(codexSocketPath, codexThreadId, true);
469
+ return sourceCleanupWindows(codexSourceToolReceipts(thread));
470
+ } catch {
471
+ return [];
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Wait for source evidence, and when it never comes, hand cleanup the exact handles the two
477
+ * transcript authorities already hold before rethrowing. No tmux inventory scan and no guess:
478
+ * a handle no joined fresh-call result recorded stays unrecovered.
479
+ */
480
+ async function awaitOrRecover<T>(what: string, timeoutMs: number, probe: () => T | null): Promise<T> {
481
+ try {
482
+ return await until(what, timeoutMs, probe);
483
+ } catch (error) {
484
+ recoverCodexThreadAuthority();
485
+ for (const windowId of [...recoverCodexWindows(), ...(await recoverOutboundPiWindows())]) {
486
+ if (openedWindows.includes(windowId)) continue;
487
+ openedWindows.push(windowId);
488
+ console.error(` recovered ${windowId} from its own receipt authority after: ${what}`);
489
+ }
490
+ // A source that never appeared leaves no cleanup authority, and mailbox prose or a tmux
491
+ // listing must not be promoted into one. Name the state instead of leaking it silently.
492
+ if (initialPiTranscript === "") {
493
+ const unrecoverable = "initial Pi record still carries no transcriptPath; no unverified child window killed";
494
+ console.error(` cleanup ${unrecoverable}`);
495
+ recordEvidence?.({ sourcePathUnresolved: unrecoverable });
496
+ }
497
+ throw error;
498
+ }
499
+ }
500
+
501
+ /**
502
+ * Every stable window handle the initial Pi or Codex source transcript recorded in an exact
503
+ * fresh-call tool result. Mailbox prose is diagnostic evidence, never cleanup authority.
504
+ */
505
+ async function recoverEveryRecordedWindow(): Promise<void> {
506
+ recoverCodexThreadAuthority();
507
+ const found = [...recoverCodexWindows(), ...(await recoverOutboundPiWindows())];
508
+ for (const windowId of found) {
509
+ if (!WINDOW_ID.test(windowId) || openedWindows.includes(windowId)) continue;
510
+ openedWindows.push(windowId);
511
+ console.error(` recovered ${windowId} from a receipt this run already held`);
512
+ }
513
+ }
514
+
515
+ /**
516
+ * Interrupt only this smoke's exact still-running Codex turn before its visible window closes.
517
+ * `[source]` rust-v0.153.4 `app-server-protocol/src/protocol/v2/turn.rs:311-319` owns
518
+ * `{threadId,turnId}` and the empty acknowledgement; no app-server lifecycle is touched here.
519
+ */
520
+ async function interruptCodexSmokeTurn(): Promise<string[]> {
521
+ if (codexThreadId === "" || codexSocketPath === "") return [];
522
+ try {
523
+ const thread = (await readCodexThread(codexSocketPath, codexThreadId, true)) as {
524
+ turns?: Array<{ id?: unknown; status?: unknown }>;
525
+ };
526
+ const turn = [...(thread.turns ?? [])].reverse().find((candidate) => candidate.status === "inProgress");
527
+ if (typeof turn?.id !== "string" || turn.id === "") return [];
528
+ const protocol = await realCodexProtocolOpener.open(codexSocketPath);
529
+ try {
530
+ await protocol.request("initialize", {
531
+ clientInfo: { name: "entwurf-codex-live-cleanup", title: "entwurf-codex-live-cleanup", version: "0" },
532
+ capabilities: { experimentalApi: true, requestAttestation: false },
533
+ });
534
+ protocol.notify("initialized");
535
+ await protocol.request("turn/interrupt", { threadId: codexThreadId, turnId: turn.id });
536
+ } finally {
537
+ protocol.close();
538
+ }
539
+ console.error(` cleanup interrupted smoke-owned Codex turn ${turn.id}`);
540
+ return [];
541
+ } catch (error) {
542
+ return [`codex turn ${codexThreadId}: ${String(error)}`];
543
+ }
544
+ }
545
+
546
+ async function until<T>(what: string, timeoutMs: number, probe: () => T | null): Promise<T> {
547
+ const deadline = Date.now() + timeoutMs;
548
+ for (;;) {
549
+ const found = probe();
550
+ if (found !== null) return found;
551
+ if (Date.now() >= deadline) throw new Error(`timed out after ${timeoutMs}ms waiting for ${what}`);
552
+ await new Promise<void>((resolve) => setTimeout(resolve, 500));
553
+ }
554
+ }
555
+
556
+ async function untilAsync<T>(what: string, timeoutMs: number, probe: () => Promise<T | null>): Promise<T> {
557
+ const deadline = Date.now() + timeoutMs;
558
+ for (;;) {
559
+ const found = await probe();
560
+ if (found !== null) return found;
561
+ if (Date.now() >= deadline) throw new Error(`timed out after ${timeoutMs}ms waiting for ${what}`);
562
+ await new Promise<void>((resolve) => setTimeout(resolve, 500));
563
+ }
564
+ }
565
+
566
+ class BridgeClient {
567
+ private readonly child: ChildProcess;
568
+ private buffer = "";
569
+ private stderr = "";
570
+ private nextId = 1;
571
+ private readonly waiters = new Map<number, (reply: RpcReply) => void>();
572
+
573
+ constructor(command: string, env: NodeJS.ProcessEnv) {
574
+ this.child = spawn(command, [], { env, stdio: ["pipe", "pipe", "pipe"] });
575
+ this.child.stderr?.on("data", (chunk) => {
576
+ this.stderr += chunk.toString();
577
+ });
578
+ this.child.stdout?.on("data", (chunk) => {
579
+ this.buffer += chunk.toString();
580
+ const lines = this.buffer.split("\n");
581
+ this.buffer = lines.pop() ?? "";
582
+ for (const line of lines) {
583
+ if (!line.trim().startsWith("{")) continue;
584
+ try {
585
+ const reply = JSON.parse(line) as { id?: unknown; result?: unknown; error?: unknown };
586
+ if (typeof reply.id !== "number") continue;
587
+ const waiter = this.waiters.get(reply.id);
588
+ if (!waiter) continue;
589
+ this.waiters.delete(reply.id);
590
+ waiter({ result: reply.result, error: reply.error });
591
+ } catch {
592
+ // A non-protocol stdout line is diagnostic, never evidence.
593
+ }
594
+ }
595
+ });
596
+ }
597
+
598
+ private send(message: unknown): void {
599
+ this.child.stdin?.write(`${JSON.stringify(message)}\n`);
600
+ }
601
+
602
+ private rpc(method: string, params: unknown, timeoutMs = 60_000): Promise<RpcReply> {
603
+ const id = this.nextId++;
604
+ return new Promise<RpcReply>((resolve, reject) => {
605
+ const timer = setTimeout(() => {
606
+ this.waiters.delete(id);
607
+ reject(new Error(`${method} did not answer in ${timeoutMs}ms\n${this.stderrTail()}`));
608
+ }, timeoutMs);
609
+ this.waiters.set(id, (reply) => {
610
+ clearTimeout(timer);
611
+ resolve(reply);
612
+ });
613
+ this.send({ jsonrpc: "2.0", id, method, params });
614
+ });
615
+ }
616
+
617
+ async initialize(): Promise<string[]> {
618
+ const initialized = await this.rpc("initialize", {
619
+ protocolVersion: "2024-11-05",
620
+ capabilities: {},
621
+ clientInfo: { name: LABEL, version: "0" },
622
+ });
623
+ if (initialized.error !== undefined)
624
+ throw new Error(`installed bridge initialize failed: ${JSON.stringify(initialized.error)}`);
625
+ this.send({ jsonrpc: "2.0", method: "notifications/initialized" });
626
+ const listed = await this.rpc("tools/list", {});
627
+ if (listed.error !== undefined)
628
+ throw new Error(`installed bridge tools/list failed: ${JSON.stringify(listed.error)}`);
629
+ const tools = (listed.result as { tools?: Array<{ name?: unknown }> } | undefined)?.tools;
630
+ if (!Array.isArray(tools))
631
+ throw new Error(`installed bridge returned malformed tools/list: ${JSON.stringify(listed.result)}`);
632
+ return tools.map((tool) => tool.name).filter((name): name is string => typeof name === "string");
633
+ }
634
+
635
+ async call(name: string, args: Record<string, unknown>, timeoutMs = 60_000): Promise<ToolResult> {
636
+ const reply = await this.rpc("tools/call", { name, arguments: args }, timeoutMs);
637
+ if (reply.error !== undefined) throw new Error(`${name} RPC error: ${JSON.stringify(reply.error)}`);
638
+ const result = reply.result as { content?: Array<{ text?: unknown }>; isError?: unknown } | undefined;
639
+ return {
640
+ text: (result?.content ?? []).map((item) => (typeof item.text === "string" ? item.text : "")).join("\n"),
641
+ isError: result?.isError === true,
642
+ };
643
+ }
644
+
645
+ stderrTail(): string {
646
+ return this.stderr.slice(-3000);
647
+ }
648
+
649
+ close(): void {
650
+ this.child.kill("SIGTERM");
651
+ }
652
+ }
653
+
654
+ function closeBridge(): void {
655
+ const activeBridge = bridge;
656
+ if (activeBridge !== null) activeBridge.close();
657
+ }
658
+
659
+ async function run(): Promise<void> {
660
+ if (!(FRESH_CALL_BACKENDS as readonly string[]).includes("codex")) {
661
+ skipLive(
662
+ LABEL,
663
+ "codex is not admitted in FRESH_CALL_BACKENDS, so there is no visible-fresh product to accept; admission parity owns that composition state.",
664
+ );
665
+ }
666
+ if (process.env.LIVE !== "1") {
667
+ skipLive(
668
+ LABEL,
669
+ "set LIVE=1 plus ENTWURF_CODEX_APP_SERVER_PID, ENTWURF_CODEX_FRESH_MODEL, and ENTWURF_CODEX_FRESH_PI_MODEL to run; this opens real Codex and Pi windows and spends model turns.",
670
+ );
671
+ }
672
+ ok("the measured/certified host axis is Linux", process.platform === "linux", `host=${process.platform}`);
673
+
674
+ const pidText = process.env.ENTWURF_CODEX_APP_SERVER_PID?.trim() ?? "";
675
+ ok(
676
+ "ENTWURF_CODEX_APP_SERVER_PID explicitly names the existing operator-owned app-server",
677
+ /^[1-9]\d*$/.test(pidText),
678
+ );
679
+ const appServerPid = Number(pidText);
680
+ const procDir = `/proc/${appServerPid}`;
681
+ ok(
682
+ "the explicit app-server PID names a currently inspectable Linux process",
683
+ fs.existsSync(procDir),
684
+ `missing ${procDir}`,
685
+ );
686
+ const procStat = fs.statSync(procDir);
687
+ if (typeof process.getuid === "function") {
688
+ ok("the named app-server process belongs to this operator", procStat.uid === process.getuid());
689
+ }
690
+ const argv = parseNulFile(path.join(procDir, "cmdline"));
691
+ const codexArg = argv.some((arg) => /(^|\/)codex(?:\.js)?$/.test(arg) || arg.includes("@openai/codex"));
692
+ ok(
693
+ "the explicit PID argv is a Codex app-server, not a guessed Codex-related process",
694
+ codexArg && argv.includes("app-server"),
695
+ `argv=${JSON.stringify(argv)}`,
696
+ );
697
+
698
+ const procEnv = readProcessEnvironment(appServerPid);
699
+ ok(
700
+ "the app-server /proc environment carries its own absolute HOME",
701
+ typeof procEnv.HOME === "string" && path.isAbsolute(procEnv.HOME),
702
+ `HOME=${JSON.stringify(procEnv.HOME)}`,
703
+ );
704
+ ok(
705
+ "the app-server /proc environment carries its own non-empty PATH",
706
+ typeof procEnv.PATH === "string" && procEnv.PATH.length > 0,
707
+ `PATH=${JSON.stringify(procEnv.PATH)}`,
708
+ );
709
+ // Reproduce only the app-server's environment. Release-shell values absent from /proc
710
+ // cannot be borrowed into a claim about what a Codex MCP child inherits.
711
+ appServerEnv = { ...procEnv };
712
+ codexSocketPath = resolveCodexDefaultSocketPath(appServerEnv);
713
+ const expectedListen = `unix://${codexSocketPath}`;
714
+ const listensOnDefaultSocket = argv.some(
715
+ (arg, index) => (arg === "--listen" && argv[index + 1] === expectedListen) || arg === `--listen=${expectedListen}`,
716
+ );
717
+ ok(
718
+ "the explicit app-server PID owns the same default UDS the fresh Codex runtime will use",
719
+ listensOnDefaultSocket,
720
+ `expected --listen ${expectedListen}; argv=${JSON.stringify(argv)}`,
721
+ );
722
+ const appServerCoordinate = inspectTmuxCoordinate("Codex app-server/MCP inherited seat", appServerEnv);
723
+ const fixtureCoordinate = inspectTmuxCoordinate("fixture/initial-Pi caller seat", process.env);
724
+ ok(
725
+ "the fixture/initial-Pi seat S and the app-server seat A share one tmux server but are different sessions",
726
+ fixtureCoordinate.serverPid === appServerCoordinate.serverPid &&
727
+ fixtureCoordinate.sessionId !== appServerCoordinate.sessionId,
728
+ `fixture=${fixtureCoordinate.serverPid}/${fixtureCoordinate.sessionId} app-server=${appServerCoordinate.serverPid}/${appServerCoordinate.sessionId}`,
729
+ );
730
+ // A and S, by NAME — recorded so the receipt says which rooms this run actually used. The
731
+ // load-bearing fact is A ≠ S, already asserted above: "the env fallback would have said A"
732
+ // is only a proof when A is a session the anchor could not have chosen. Since #95 D1 there
733
+ // is no required NAME for either — the operator seats the app-server wherever they like.
734
+ const appServerSessionName = tmuxSessionName(
735
+ "Codex app-server/MCP inherited seat",
736
+ appServerEnv,
737
+ appServerCoordinate.paneId,
738
+ );
739
+ const fixtureSessionName = tmuxSessionName("fixture/initial-Pi caller seat", process.env, fixtureCoordinate.paneId);
740
+ ok(
741
+ "the app-server's own session is a DIFFERENT room from the fixture's, which is what makes hop 2 decisive",
742
+ appServerSessionName !== fixtureSessionName,
743
+ `app-server=${JSON.stringify(appServerSessionName)} fixture=${JSON.stringify(fixtureSessionName)}`,
744
+ );
745
+ receipts["1-codex-app-server-mcp-coordinate"] =
746
+ `pid=${appServerPid}\nargv=${argv.join(" ")}\nserver=${appServerCoordinate.serverPid}\n` +
747
+ `session=${appServerCoordinate.sessionId}\nname=${appServerSessionName}\n` +
748
+ `window=${appServerCoordinate.windowId}\npane=${appServerCoordinate.paneId}\n` +
749
+ `fixture-session=${fixtureCoordinate.sessionId}\nfixture-name=${fixtureSessionName}\n` +
750
+ `fixture-window=${fixtureCoordinate.windowId}`;
751
+
752
+ const codexModel = process.env.ENTWURF_CODEX_FRESH_MODEL?.trim() ?? "";
753
+ const piModel = process.env.ENTWURF_CODEX_FRESH_PI_MODEL?.trim() ?? "";
754
+ ok("ENTWURF_CODEX_FRESH_MODEL explicitly names the Codex runtime model", codexModel.length > 0);
755
+ ok("ENTWURF_CODEX_FRESH_PI_MODEL explicitly names the Pi callback sibling model", piModel.length > 0);
756
+ const codexRuntime = resolveExecutable("codex", appServerEnv);
757
+ ok("the Codex runtime resolves on the app-server PATH", codexRuntime !== null);
758
+ const bridgeRuntime = resolveExecutable("entwurf-bridge", appServerEnv);
759
+ ok("the installed compiled entwurf-bridge resolves on the app-server PATH", bridgeRuntime !== null);
760
+
761
+ const missing = await codexFreshPreflight(appServerEnv);
762
+ ok(
763
+ "the real installed Codex birth, MCP, visible identity, and default app-server preflight is green",
764
+ missing === null,
765
+ missing === null ? "" : `${missing}: ${CODEX_PREFLIGHT_HINT[missing]}`,
766
+ );
767
+ // The CALLER axis, and a different fact from the five above: hop 2 is made BY a Codex
768
+ // citizen, so this host's `[tui].terminal_title` must carry `thread-id` or that citizen has
769
+ // no findable pane. Asserted here, before any window opens, because the alternative is
770
+ // discovering it three model turns later as a seat refusal.
771
+ const callerMissing = codexCallerFreshPreflight(appServerEnv);
772
+ ok(
773
+ "the installed Codex CALLER seat config is green — tui.terminal_title carries thread-id",
774
+ callerMissing === null,
775
+ callerMissing === null ? "" : `${callerMissing}: ${CODEX_CALLER_PREFLIGHT_HINT[callerMissing]}`,
776
+ );
777
+
778
+ // The fixture and every bridge it talks to must resolve the same real stores the app-server
779
+ // forwards into Codex MCP children. This is not an isolated lookalike store.
780
+ for (const key of META_CONTEXT_ENV_KEYS) {
781
+ const value = appServerEnv[key];
782
+ if (typeof value === "string") process.env[key] = value;
783
+ else delete process.env[key];
784
+ }
785
+
786
+ // A STABLE directory, not a fresh `mkdtemp` per run, and that is a folder-consent fact rather
787
+ // than a tidiness preference. `[측정 2026-09-16]` codex keys its project trust to the exact
788
+ // launch directory on this rail, so a new random scratch is an UNDECIDED folder every time:
789
+ // the sibling opens on the vendor's trust screen, starts no turn, writes no rollout and never
790
+ // calls back — which is exactly how the two unattended release-gate runs failed while the two
791
+ // runs a human sat through passed. One path, answered `Trust` once, keeps the operator's
792
+ // config from growing an entry per run. The path stays OUTSIDE the repo and is asserted
793
+ // different from the app-server's own directory below, which is all lane C needs of it.
794
+ //
795
+ // It is PRINTED rather than documented as a literal, because `os.tmpdir()` is a host fact:
796
+ // this process's `TMPDIR` when it has one and `/tmp` when it does not, and the trust key is
797
+ // the exact string either way. A doc that spelled it `$TMPDIR/...` would send an operator
798
+ // whose TMPDIR is unset to `/entwurf-codex-fresh-live` — a different directory, in the root
799
+ // of the filesystem, that the vendor would then ask about separately.
800
+ const scratch = path.join(os.tmpdir(), "entwurf-codex-fresh-live");
801
+ fs.mkdirSync(scratch, { recursive: true, mode: 0o700 });
802
+ fixtureArtifacts.push(scratch);
803
+ console.log(` launch-cwd ${scratch}`);
804
+ // Named HERE, before anything is launched, because the same refusal arriving 20 assertions
805
+ // later reads as a callback timeout — the failure mode this whole comment exists to retire.
806
+ // The detail carries the REPAIR COMMAND with the resolved path already in it, so the answer
807
+ // cannot be given for a near-miss directory.
808
+ const untrustedScratch = codexLaunchCwdFreshPreflight(appServerEnv, scratch);
809
+ ok(
810
+ "the Codex launch directory carries the vendor's own trust receipt, so a sibling opened there starts its first turn",
811
+ untrustedScratch === null,
812
+ untrustedScratch === null
813
+ ? ""
814
+ : `${untrustedScratch}: ${CODEX_LAUNCH_CWD_PREFLIGHT_HINT[untrustedScratch]}\n` +
815
+ ` repair (this host, exact directory): codex -C ${scratch} → answer \`Trust\``,
816
+ );
817
+ fs.mkdirSync(path.join(REPO, ".probe-artifacts"), { recursive: true, mode: 0o700 });
818
+ evidenceDir = fs.mkdtempSync(path.join(REPO, ".probe-artifacts", "codex-fresh-live-"));
819
+ fs.chmodSync(evidenceDir, 0o700);
820
+ console.log(` evidence ${evidenceDir}`);
821
+ const nativeSessionId = `codex-fresh-live-fixture-${process.pid}-${Date.now()}`;
822
+ const caller = upsertMetaSession({
823
+ input: { backend: "claude-code", nativeSessionId, cwd: scratch },
824
+ });
825
+ const callerGid = caller.record.gardenId;
826
+ fixtureGid = callerGid;
827
+ relatedRecordPaths.add(caller.path);
828
+ const receiverMarker = writeMetaReceiverMarker({
829
+ gardenId: callerGid,
830
+ backend: "claude-code",
831
+ nativeSessionId,
832
+ ownerPid: process.pid,
833
+ armProvenance: "session-start",
834
+ });
835
+ const senderMarker = writeMetaSenderMarker({
836
+ backend: "claude-code",
837
+ gardenId: callerGid,
838
+ nativeSessionId,
839
+ cwd: scratch,
840
+ ownerPid: process.pid,
841
+ });
842
+ fixtureArtifacts.push(path.join(defaultMetaMailboxDir(), callerGid), receiverMarker, senderMarker, caller.path);
843
+ const evidenceManifest: Record<string, unknown> = {
844
+ runPid: process.pid,
845
+ scratch,
846
+ appServerPid,
847
+ appServerCoordinate,
848
+ fixtureCoordinate,
849
+ fixtureGardenId: callerGid,
850
+ fixtureRecordPath: caller.path,
851
+ resolvedRoots: Object.fromEntries(META_CONTEXT_ENV_KEYS.map((key) => [key, appServerEnv?.[key] ?? null])),
852
+ resolvedMetaSessionsDir: defaultMetaSessionsDir(),
853
+ resolvedMetaMailboxDir: defaultMetaMailboxDir(),
854
+ };
855
+ const writeEvidenceManifest = (patch: Record<string, unknown>): void => {
856
+ Object.assign(evidenceManifest, patch);
857
+ fs.writeFileSync(path.join(evidenceDir, "run-manifest.json"), `${JSON.stringify(evidenceManifest, null, 2)}\n`, {
858
+ mode: 0o600,
859
+ });
860
+ };
861
+ recordEvidence = writeEvidenceManifest;
862
+ writeEvidenceManifest({ phase: "fixture-ready" });
863
+ ok("the original caller is a record-backed fixture on the app-server's real meta roots", GARDEN_ID.test(callerGid));
864
+
865
+ const callerBridgeEnv: NodeJS.ProcessEnv = {
866
+ ...appServerEnv,
867
+ // The fixture launches the initial Pi from THIS operator session, not from Codex's
868
+ // app-server seat. Store/runtime facts come from the app-server environment above;
869
+ // only placement comes from the fixture's own exact tmux anchor.
870
+ TMUX: process.env.TMUX,
871
+ TMUX_PANE: process.env.TMUX_PANE,
872
+ ENTWURF_META_SENDER_MARKER: senderMarker,
873
+ };
874
+ delete callerBridgeEnv.ENTWURF_BRIDGE_NATIVE_HOST;
875
+ delete callerBridgeEnv.PI_SESSION_ID;
876
+ delete callerBridgeEnv.PI_AGENT_ID;
877
+ bridge = new BridgeClient(bridgeRuntime, callerBridgeEnv);
878
+ const tools = await bridge.initialize();
879
+ ok(
880
+ "the installed compiled bridge boots and exposes both public calls used by this acceptance",
881
+ tools.includes("entwurf_fresh_call") && tools.includes("entwurf_v2"),
882
+ `tools=${tools.join(",")}`,
883
+ );
884
+
885
+ const initialPiWaitToken = `INITIAL-PI-WAIT-${Math.random().toString(36).slice(2, 10).toUpperCase()}`;
886
+ const initialPiLaunch = await bridge.call("entwurf_fresh_call", {
887
+ backend: "pi",
888
+ model: piModel,
889
+ cwd: scratch,
890
+ task:
891
+ `After your required callback receipt, end the turn and wait passively for one addressed message containing ${initialPiWaitToken}. ` +
892
+ "That message is the only authority to open the Codex sibling. Do not call shell, sleep, terminal, or any tool to wait.",
893
+ });
894
+ receipts["2-initial-pi-launch"] = initialPiLaunch.text;
895
+ ok(
896
+ "the record-backed fixture opened a real initial Pi caller through public entwurf_fresh_call",
897
+ !initialPiLaunch.isError,
898
+ `${initialPiLaunch.text}\nbridge stderr:\n${bridge.stderrTail()}`,
899
+ );
900
+ const initialPiNonce = /nonce:\s*(mux-fresh-call-[0-9a-f]+)/.exec(initialPiLaunch.text)?.[1] ?? "";
901
+ const initialPiWindow = /window:\s+(@\d+)/.exec(initialPiLaunch.text)?.[1] ?? "";
902
+ const initialPiTmuxSession = /window:\s+@\d+.*\bin session (\$\d+)/.exec(initialPiLaunch.text)?.[1] ?? "";
903
+ ok("the initial Pi LAUNCH receipt carries a stable cleanup handle", WINDOW_ID.test(initialPiWindow));
904
+ openedWindows.push(initialPiWindow);
905
+ ok("the initial Pi LAUNCH receipt carries its exact callback nonce", initialPiNonce.length > 0);
906
+ ok(
907
+ "the initial Pi caller is visible in the fixture's own session S, not the app-server's A",
908
+ initialPiTmuxSession === fixtureCoordinate.sessionId && initialPiTmuxSession !== appServerCoordinate.sessionId,
909
+ `initial-pi=${initialPiTmuxSession} fixture=${fixtureCoordinate.sessionId} app-server=${appServerCoordinate.sessionId}`,
910
+ );
911
+ receipts["3-initial-caller-pi-coordinate"] =
912
+ `session=${initialPiTmuxSession}\nwindow=${initialPiWindow}\napp-server-session=${appServerCoordinate.sessionId}`;
913
+
914
+ let drainedCount = 0;
915
+ const drain = (): void => {
916
+ for (const message of readMetaInbox({ gardenId: callerGid }).messages) {
917
+ const index = String(++drainedCount).padStart(3, "0");
918
+ const bodyFile = path.join(evidenceDir, `mailbox-${index}.txt`);
919
+ fs.writeFileSync(bodyFile, message.body, { mode: 0o600 });
920
+ const parsed = parseMetaMailboxBody(message.body);
921
+ const matchingTokens = expectedReportTokens.filter((token) => message.body.includes(token));
922
+ const observedTokens = message.body.match(/\b(?:INITIAL-PI-WAIT|CODEX-WAIT|CODEX-PI-FINAL)-[A-Z0-9]+\b/g) ?? [];
923
+ const observation = {
924
+ kind: "candidate",
925
+ file: message.file,
926
+ bytes: Buffer.byteLength(message.body),
927
+ sha256: createHash("sha256").update(message.body).digest("hex"),
928
+ sender: parsed?.sender ?? null,
929
+ matchingTokens,
930
+ observedTokens,
931
+ bodyArtifact: path.basename(bodyFile),
932
+ };
933
+ fs.appendFileSync(path.join(evidenceDir, "mailbox-observations.jsonl"), `${JSON.stringify(observation)}\n`, {
934
+ mode: 0o600,
935
+ });
936
+ seenInbox.push(message.body);
937
+ }
938
+ };
939
+ const recordMailboxSelection = (phase: string, body: string, selected: boolean, reason: string): void => {
940
+ const parsed = parseMetaMailboxBody(body);
941
+ fs.appendFileSync(
942
+ path.join(evidenceDir, "mailbox-selections.jsonl"),
943
+ `${JSON.stringify({
944
+ phase,
945
+ sha256: createHash("sha256").update(body).digest("hex"),
946
+ sender: parsed?.sender ?? null,
947
+ selected,
948
+ reason,
949
+ })}\n`,
950
+ { mode: 0o600 },
951
+ );
952
+ };
953
+ const initialPiCallback = await awaitOrRecover("the initial Pi exact nonce callback", CALLBACK_WAIT_MS, () => {
954
+ drain();
955
+ const matching = mailboxCallbacks(seenInbox, initialPiNonce);
956
+ if (matching.length > 1) throw new Error(`duplicate exact initial Pi callbacks (${matching.length})`);
957
+ return matching[0] ?? null;
958
+ });
959
+ for (const body of seenInbox) {
960
+ recordMailboxSelection(
961
+ "initial-pi-callback",
962
+ body,
963
+ body === initialPiCallback.body,
964
+ body === initialPiCallback.body
965
+ ? "selected: exact outer frame, nonce, and sender"
966
+ : "excluded: not the exact callback",
967
+ );
968
+ }
969
+ receipts["4-initial-pi-callback"] = initialPiCallback.body;
970
+ const initialPiGid = initialPiCallback.sender;
971
+ ok(
972
+ "the initial Pi callback carries the exact launch nonce",
973
+ mailboxCallbacks([initialPiCallback.body], initialPiNonce).length === 1,
974
+ );
975
+ ok("the initial Pi callback sender envelope supplies its garden address", GARDEN_ID.test(initialPiGid));
976
+ const initialPiIdentity = readMetaIdentityByGardenId(initialPiGid);
977
+ ok("the initial caller callback resolves to a real Pi V3 citizen", initialPiIdentity.backend === "pi");
978
+ relatedRecordPaths.add(path.join(defaultMetaSessionsDir(), metaRecordFilename(initialPiIdentity)));
979
+ initialPiGardenId = initialPiGid;
980
+ initialPiTranscript = initialPiIdentity.transcriptPath ?? "";
981
+ if (initialPiTranscript !== "") relatedTranscripts.add(initialPiTranscript);
982
+ writeEvidenceManifest({
983
+ phase: "initial-pi-correlated",
984
+ initialPiGardenId: initialPiGid,
985
+ initialPiTranscript,
986
+ initialPiRecordPath: path.join(defaultMetaSessionsDir(), metaRecordFilename(initialPiIdentity)),
987
+ });
988
+
989
+ const codexWaitToken = `CODEX-WAIT-${Math.random().toString(36).slice(2, 10).toUpperCase()}`;
990
+ const finalToken = `CODEX-PI-FINAL-${Math.random().toString(36).slice(2, 10).toUpperCase()}`;
991
+ expectedReportTokens.push(finalToken);
992
+ const outboundPiTask = "After your automatic callback succeeds, answer ACK and stop. Do not open another sibling.";
993
+ const codexTask =
994
+ `After your required callback receipt, end the turn and wait passively for the addressed instruction containing ${codexWaitToken}. ` +
995
+ "Do not call shell, sleep, terminal, or any tool to wait.";
996
+ const codexInstruction = buildCodexInstruction({
997
+ waitToken: codexWaitToken,
998
+ finalToken,
999
+ callerGid,
1000
+ piModel,
1001
+ });
1002
+ const initialPiInstruction = buildInitialPiPhaseOne({
1003
+ initialPiWaitToken,
1004
+ codexWaitToken,
1005
+ codexModel,
1006
+ scratch,
1007
+ codexInstruction,
1008
+ });
1009
+
1010
+ // The model is asked to operate public tools, but every launch and delivery assertion below
1011
+ // joins the harness's source-owned call arguments to its native result. Model reports are not
1012
+ // coordinates and never enter the cleanup allowlist.
1013
+ const instructInitialPi = await bridge.call("entwurf_v2", {
1014
+ target: initialPiGid,
1015
+ intent: "fire-and-forget",
1016
+ message: initialPiInstruction,
1017
+ });
1018
+ receipts["5-fixture-to-initial-pi-v2"] = instructInitialPi.text;
1019
+ ok(
1020
+ "the fixture addressed the real initial Pi caller through an actual control-socket entwurf_v2 receipt",
1021
+ !instructInitialPi.isError &&
1022
+ /control-socket/i.test(instructInitialPi.text) &&
1023
+ /(?:sent|delivered)/i.test(instructInitialPi.text),
1024
+ instructInitialPi.text,
1025
+ );
1026
+
1027
+ const piFreshReceipt = await awaitOrRecover("the initial Pi source-owned Codex LAUNCH receipt", SOURCE_WAIT_MS, () =>
1028
+ selectExactSourceToolReceipt(
1029
+ piSourceToolReceipts(readInitialPiEntries()),
1030
+ "entwurf_fresh_call",
1031
+ {
1032
+ backend: "codex",
1033
+ model: codexModel,
1034
+ cwd: scratch,
1035
+ task: codexTask,
1036
+ },
1037
+ "initial Pi Codex fresh call",
1038
+ ),
1039
+ );
1040
+ const codexLaunchReceipt = piFreshReceipt.text;
1041
+ receipts["6-initial-pi-source-codex-launch"] = codexLaunchReceipt;
1042
+ const codexNonce = /^\s*nonce:\s*(mux-fresh-call-[0-9a-f]+)/m.exec(codexLaunchReceipt)?.[1] ?? "";
1043
+ codexLaunchNonce = codexNonce;
1044
+ const codexReceiptCoordinate = /^\s*window:\s+(@\d+).*?\bin session (\$\d+)/m.exec(codexLaunchReceipt);
1045
+ const codexWindow = codexReceiptCoordinate?.[1] ?? "";
1046
+ const codexLaunchSession = codexReceiptCoordinate?.[2] ?? "";
1047
+ ok(
1048
+ "the initial Pi's joined source receipt names Codex and a stable cleanup handle",
1049
+ WINDOW_ID.test(codexWindow) && /^\s*backend:\s+codex\b/m.test(codexLaunchReceipt),
1050
+ );
1051
+ if (!openedWindows.includes(codexWindow)) openedWindows.push(codexWindow);
1052
+ ok("the source-owned Codex LAUNCH receipt carries its exact callback nonce", codexNonce.length > 0);
1053
+ // HOP 1. An omitted seat, which since #95 D1 means the CALLER's own session for every backend
1054
+ // — Codex no longer selects a fixed room. It is the setup rather than the claim: it puts the
1055
+ // Codex TUI in S so hop 2's answer can be told apart from the app-server's inherited A. The
1056
+ // receipt must name NO seat at all: the caller's own session is the absence of a seat rule,
1057
+ // not a rule with a name.
1058
+ ok(
1059
+ "omitted placement opened fresh Codex in the caller's own session S, away from the app-server's A",
1060
+ codexLaunchSession === fixtureCoordinate.sessionId &&
1061
+ codexLaunchSession !== appServerCoordinate.sessionId &&
1062
+ !/^\s*seat:/m.test(codexLaunchReceipt),
1063
+ `codex=${codexLaunchSession} fixture=${fixtureCoordinate.sessionId} app-server=${appServerCoordinate.sessionId}`,
1064
+ );
1065
+ receipts["7-fresh-codex-coordinate"] =
1066
+ `session=${codexLaunchSession}\nname=${fixtureSessionName}\nwindow=${codexWindow}\n` +
1067
+ `app-server-session=${appServerCoordinate.sessionId}\napp-server-name=${appServerSessionName}`;
1068
+
1069
+ const codexCallbackSender = await awaitOrRecover(
1070
+ "the exact Codex callback in initial Pi source",
1071
+ CALLBACK_WAIT_MS,
1072
+ () => {
1073
+ const senders = piCallbackSenders(readInitialPiEntries(), codexNonce);
1074
+ if (senders.length > 1) throw new Error(`duplicate exact Codex callbacks (${senders.length})`);
1075
+ return senders[0] ?? null;
1076
+ },
1077
+ );
1078
+ ok("the Codex callback sender envelope supplies its garden address", GARDEN_ID.test(codexCallbackSender));
1079
+ const codexIdentity = readMetaIdentityByGardenId(codexCallbackSender);
1080
+ ok("the callback-derived citizen resolves to a real Codex V3 citizen", codexIdentity.backend === "codex");
1081
+ codexGardenId = codexCallbackSender;
1082
+ codexThreadId = codexIdentity.nativeSessionId;
1083
+ relatedRecordPaths.add(path.join(defaultMetaSessionsDir(), metaRecordFilename(codexIdentity)));
1084
+ if (codexIdentity.transcriptPath != null) relatedTranscripts.add(codexIdentity.transcriptPath);
1085
+ writeEvidenceManifest({
1086
+ phase: "codex-correlated",
1087
+ codexGardenId,
1088
+ codexThreadId,
1089
+ codexRecordPath: path.join(defaultMetaSessionsDir(), metaRecordFilename(codexIdentity)),
1090
+ });
1091
+ receipts["8-initial-pi-source-codex-callback"] =
1092
+ `nonce=${codexNonce}\ngarden=${codexGardenId}\nthread=${codexThreadId}`;
1093
+
1094
+ // #95 lane C (i). WHERE THE CODEX THREAD OPENED, from four authorities that cannot borrow
1095
+ // from each other: tmux's pane, the vendor's own rollout header, Entwurf's record, and the
1096
+ // directory this call requested. Before the `-C` carrier they disagreed while every receipt
1097
+ // still looked right — the pane sat in the requested scratch and the THREAD opened in the
1098
+ // app-server's repo, which the birth hook then recorded honestly. The app-server's live cwd
1099
+ // is read from the kernel and asserted DIFFERENT, because a scratch that happened to equal it
1100
+ // would make all four agree for the wrong reason.
1101
+ const codexPane = /^\s*pane:\s+(%\d+)/m.exec(codexLaunchReceipt)?.[1] ?? "";
1102
+ ok("the Codex LAUNCH receipt names its pane", /^%\d+$/.test(codexPane), codexLaunchReceipt);
1103
+ const appServerCwd = processCwd(appServerPid);
1104
+ const codexPaneCwd = tmuxPaneCwd("fresh Codex", process.env, codexPane);
1105
+ const codexRolloutCwd =
1106
+ codexIdentity.transcriptPath === null ? "" : rolloutSessionMetaCwd(codexIdentity.transcriptPath);
1107
+ ok(
1108
+ "the fresh Codex thread, its pane, its record and the requested cwd are ONE directory — and it is not the app-server's",
1109
+ codexPaneCwd === scratch &&
1110
+ codexRolloutCwd === scratch &&
1111
+ codexIdentity.cwd === scratch &&
1112
+ appServerCwd !== scratch,
1113
+ `pane=${codexPaneCwd} rollout=${codexRolloutCwd} record=${codexIdentity.cwd} requested=${scratch} app-server=${appServerCwd}`,
1114
+ );
1115
+ receipts["8b-codex-thread-cwd"] =
1116
+ `requested=${scratch}\npane=${codexPaneCwd}\nrollout-session_meta=${codexRolloutCwd}\n` +
1117
+ `record=${codexIdentity.cwd}\napp-server=${appServerCwd}`;
1118
+
1119
+ const initialPiToCodex = await awaitOrRecover("the initial Pi joined native-push receipt", SOURCE_WAIT_MS, () => {
1120
+ const piSources = piSourceToolReceipts(readInitialPiEntries());
1121
+ assertSourceCallScopes(piSources, "entwurf_v2", "target", [callerGid, codexGardenId], "initial Pi v2 roles");
1122
+ return selectExactSourceToolReceipt(
1123
+ piSources,
1124
+ "entwurf_v2",
1125
+ {
1126
+ target: codexGardenId,
1127
+ intent: "fire-and-forget",
1128
+ wants_reply: false,
1129
+ message: codexInstruction,
1130
+ },
1131
+ "initial Pi addressed Codex call",
1132
+ );
1133
+ });
1134
+ ok(
1135
+ "initial Pi -> Codex used an actual joined native-push entwurf_v2 receipt",
1136
+ initialPiToCodex.text === "entwurf_v2 native-push → delivered",
1137
+ initialPiToCodex.text,
1138
+ );
1139
+ receipts["9-initial-pi-source-native-push"] = initialPiToCodex.text;
1140
+
1141
+ let codexThread = await readCodexThread(codexSocketPath, codexThreadId, true);
1142
+ writeEvidenceManifest({
1143
+ phase: "codex-thread-read",
1144
+ codexThreadPath: (codexThread as { path?: unknown }).path ?? null,
1145
+ });
1146
+ receipts["10-codex-live-thread-title"] =
1147
+ `threadId=${codexThreadId}\nlive name=${JSON.stringify(codexThread.name)}\ngarden=${codexGardenId}`;
1148
+ ok(
1149
+ "the LIVE Codex thread's visible title IS its garden id (read back through the operator's app-server)",
1150
+ codexThread.name === codexGardenId,
1151
+ `thread/read name=${JSON.stringify(codexThread.name)} garden=${codexGardenId}`,
1152
+ );
1153
+ ok("the live thread the title was read from is the record's own thread", codexThread.id === codexThreadId);
1154
+
1155
+ const codexPiFreshReceipt = await untilAsync(
1156
+ "the Codex thread source-owned outbound Pi LAUNCH receipt",
1157
+ SOURCE_WAIT_MS,
1158
+ async () => {
1159
+ codexThread = await readCodexThread(codexSocketPath, codexThreadId, true);
1160
+ return selectExactSourceToolReceipt(
1161
+ codexSourceToolReceipts(codexThread),
1162
+ "entwurf_fresh_call",
1163
+ { backend: "pi", model: piModel, task: outboundPiTask },
1164
+ "Codex outbound Pi fresh call",
1165
+ );
1166
+ },
1167
+ );
1168
+ const outboundPiReceipt = codexPiFreshReceipt.text;
1169
+ receipts["11-codex-source-outbound-pi-launch"] = outboundPiReceipt;
1170
+ const outboundPiNonce = /^\s*nonce:\s*(mux-fresh-call-[0-9a-f]+)/m.exec(outboundPiReceipt)?.[1] ?? "";
1171
+ const outboundPiReceiptCoordinate = /^\s*window:\s+(@\d+).*?\bin session (\$\d+)/m.exec(outboundPiReceipt);
1172
+ const outboundPiWindow = outboundPiReceiptCoordinate?.[1] ?? "";
1173
+ const outboundPiSession = outboundPiReceiptCoordinate?.[2] ?? "";
1174
+ ok(
1175
+ "the Codex thread's joined source receipt names Pi and a stable cleanup handle",
1176
+ WINDOW_ID.test(outboundPiWindow) && /^\s*backend:\s+pi\b/m.test(outboundPiReceipt),
1177
+ );
1178
+ if (!openedWindows.includes(outboundPiWindow)) openedWindows.push(outboundPiWindow);
1179
+ ok("the source-owned outbound Pi LAUNCH receipt carries its exact nonce", outboundPiNonce.length > 0);
1180
+ // HOP 2 — THE CLAIM (#95 lane B). Two independent halves of one fact, and both are required.
1181
+ // The SEAT: this Pi landed in S, the session the Codex TUI is in. The app-server's inherited
1182
+ // `TMUX` names A, so the pre-#95 env fallback would have put it there; S is reachable only
1183
+ // through the caller's own pane title. The LABEL: the receipt says `codex-title-anchor`, so
1184
+ // the rule that chose it is named rather than inferred from a coincidence of sessions.
1185
+ ok(
1186
+ "omitted placement opened the outbound Pi beside the CODEX TUI in S — not in the app-server's home A",
1187
+ outboundPiSession === codexLaunchSession &&
1188
+ outboundPiSession === fixtureCoordinate.sessionId &&
1189
+ outboundPiSession !== appServerCoordinate.sessionId,
1190
+ `outbound-pi=${outboundPiSession} codex=${codexLaunchSession} fixture=${fixtureCoordinate.sessionId} app-server=${appServerCoordinate.sessionId}`,
1191
+ );
1192
+ ok(
1193
+ "the outbound Pi receipt names the title anchor as the rule that chose that seat, and no session name",
1194
+ /^\s*seat:\s+\$\d+ \(the Codex caller's own pane, found by its thread-id terminal title/m.test(outboundPiReceipt) &&
1195
+ !outboundPiReceipt.includes("requested tmux session"),
1196
+ outboundPiReceipt,
1197
+ );
1198
+ // The four coordinates, stated together so the shape is readable in one place. This claims
1199
+ // the CALLER-seat topology and nothing wider: no arbitrary attached-TUI inference, and no
1200
+ // claim about a Codex TUI this smoke did not open.
1201
+ ok(
1202
+ "supported caller-seat topology: initial Pi in S, Codex in S, Codex opens outbound Pi in S, " +
1203
+ "while the operator-owned app-server stays in its own session A",
1204
+ initialPiTmuxSession === fixtureCoordinate.sessionId &&
1205
+ codexLaunchSession === fixtureCoordinate.sessionId &&
1206
+ outboundPiSession === fixtureCoordinate.sessionId &&
1207
+ appServerCoordinate.sessionId !== fixtureCoordinate.sessionId,
1208
+ `initial-pi=${initialPiTmuxSession} codex=${codexLaunchSession} outbound-pi=${outboundPiSession} app-server=${appServerCoordinate.sessionId}`,
1209
+ );
1210
+ receipts["12-outbound-fresh-pi-coordinate"] =
1211
+ `session=${outboundPiSession}\nwindow=${outboundPiWindow}\ncodex-session=${codexLaunchSession}\n` +
1212
+ `app-server-session=${appServerCoordinate.sessionId}\nseat-source=codex-title-anchor`;
1213
+
1214
+ const outboundPiGid = await untilAsync(
1215
+ "the exact outbound Pi callback in Codex source",
1216
+ CALLBACK_WAIT_MS,
1217
+ async () => {
1218
+ codexThread = await readCodexThread(codexSocketPath, codexThreadId, true);
1219
+ const senders = codexCallbackSenders(codexThread, outboundPiNonce);
1220
+ if (senders.length > 1) throw new Error(`duplicate exact outbound Pi callbacks (${senders.length})`);
1221
+ return senders[0] ?? null;
1222
+ },
1223
+ );
1224
+ ok(
1225
+ "the outbound fresh Pi callback names a new citizen, not the initial Pi or Codex",
1226
+ GARDEN_ID.test(outboundPiGid) && outboundPiGid !== initialPiGid && outboundPiGid !== codexGardenId,
1227
+ );
1228
+ const outboundPiIdentity = readMetaIdentityByGardenId(outboundPiGid);
1229
+ ok("the outbound Pi callback address resolves to a real Pi V3 citizen", outboundPiIdentity.backend === "pi");
1230
+ relatedRecordPaths.add(path.join(defaultMetaSessionsDir(), metaRecordFilename(outboundPiIdentity)));
1231
+ if (outboundPiIdentity.transcriptPath != null) relatedTranscripts.add(outboundPiIdentity.transcriptPath);
1232
+ writeEvidenceManifest({
1233
+ phase: "outbound-pi-correlated",
1234
+ outboundPiGardenId: outboundPiGid,
1235
+ outboundPiRecordPath: path.join(defaultMetaSessionsDir(), metaRecordFilename(outboundPiIdentity)),
1236
+ outboundPiTranscript: outboundPiIdentity.transcriptPath ?? null,
1237
+ });
1238
+ receipts["13-codex-source-outbound-pi-callback"] = `nonce=${outboundPiNonce}\ngarden=${outboundPiGid}`;
1239
+
1240
+ // #95 lane C (ii) — THE CLAIM. This leg named NO cwd, so the only directory the composition
1241
+ // could use is the Codex caller's own record. Its oracle is independent twice over: the pane
1242
+ // answers tmux, and the outbound Pi's RECORD is written by that Pi's own birth from its own
1243
+ // process directory — neither reads the launch receipt. Before lane C this landed in the
1244
+ // app-server's repo, because the bridge is the app-server's MCP child and an omitted cwd
1245
+ // inherited ITS directory; the app-server's live cwd is asserted different so that failure
1246
+ // mode stays visible rather than coincidentally equal.
1247
+ const outboundPiPane = /^\s*pane:\s+(%\d+)/m.exec(outboundPiReceipt)?.[1] ?? "";
1248
+ ok("the outbound Pi LAUNCH receipt names its pane", /^%\d+$/.test(outboundPiPane), outboundPiReceipt);
1249
+ const outboundPiPaneCwd = tmuxPaneCwd("outbound Pi", process.env, outboundPiPane);
1250
+ ok(
1251
+ "with NO cwd requested, the outbound Pi opened in the CODEX CALLER's own record directory — not in the app-server's",
1252
+ outboundPiPaneCwd === codexIdentity.cwd &&
1253
+ outboundPiIdentity.cwd === codexIdentity.cwd &&
1254
+ outboundPiPaneCwd !== appServerCwd,
1255
+ `pane=${outboundPiPaneCwd} outbound-record=${outboundPiIdentity.cwd} codex-record=${codexIdentity.cwd} app-server=${appServerCwd}`,
1256
+ );
1257
+ ok(
1258
+ "the outbound Pi receipt NAMES the caller-record rule rather than calling that directory requested",
1259
+ outboundPiReceipt.includes(`cwd: ${codexIdentity.cwd} (the Codex caller's own record directory`) &&
1260
+ !outboundPiReceipt.includes("requested start directory"),
1261
+ outboundPiReceipt,
1262
+ );
1263
+ receipts["13b-outbound-pi-cwd"] =
1264
+ `requested=<none>\npane=${outboundPiPaneCwd}\nrecord=${outboundPiIdentity.cwd}\n` +
1265
+ `codex-caller-record=${codexIdentity.cwd}\napp-server=${appServerCwd}`;
1266
+
1267
+ const expectedFinalMessage =
1268
+ `${finalToken}\nPI_LAUNCH_NONCE=${outboundPiNonce}\nPI_CALLBACK_NONCE=${outboundPiNonce}\n` +
1269
+ `PI_CALLBACK_FROM=${outboundPiGid}\nPI_SESSION_ID=${outboundPiSession}\nPI_WINDOW_ID=${outboundPiWindow}`;
1270
+ const codexFinalReceipt = await untilAsync("the Codex joined final mailbox receipt", SOURCE_WAIT_MS, async () => {
1271
+ codexThread = await readCodexThread(codexSocketPath, codexThreadId, true);
1272
+ const codexSources = codexSourceToolReceipts(codexThread);
1273
+ assertSourceCallScopes(codexSources, "entwurf_v2", "target", [initialPiGid, callerGid], "Codex v2 roles");
1274
+ return selectExactSourceToolReceipt(
1275
+ codexSources,
1276
+ "entwurf_v2",
1277
+ { target: callerGid, intent: "fire-and-forget", wants_reply: false, message: expectedFinalMessage },
1278
+ "Codex final fixture call",
1279
+ );
1280
+ });
1281
+ ok(
1282
+ "Codex -> fixture used an actual joined meta-mailbox entwurf_v2 receipt",
1283
+ /^entwurf_v2 meta-mailbox → enqueued \([^\n]+\.msg\)$/.test(codexFinalReceipt.text),
1284
+ codexFinalReceipt.text,
1285
+ );
1286
+ receipts["14-codex-source-final-mailbox"] = codexFinalReceipt.text;
1287
+
1288
+ const finalBody = await awaitOrRecover(
1289
+ "the fixture read Codex's final exact source message",
1290
+ CALLBACK_WAIT_MS,
1291
+ () => {
1292
+ drain();
1293
+ return selectReport(seenInbox, finalToken, codexGardenId);
1294
+ },
1295
+ );
1296
+ for (const body of seenInbox) {
1297
+ recordMailboxSelection(
1298
+ "codex-final-candidate",
1299
+ body,
1300
+ body === finalBody,
1301
+ body === finalBody
1302
+ ? "candidate selected: exact token and sender; body validation pending"
1303
+ : "candidate excluded: token or sender differs",
1304
+ );
1305
+ }
1306
+ receipts["15-fixture-read-codex-final"] = finalBody;
1307
+ const finalValidation = validateFinalMailboxBody(finalBody, codexGardenId, expectedFinalMessage);
1308
+ recordMailboxSelection("codex-final-validation", finalBody, finalValidation.accepted, finalValidation.reason);
1309
+ ok(
1310
+ "the fixture read one exact outer-framed final message from the same Codex citizen",
1311
+ finalValidation.parsed?.sender === codexGardenId,
1312
+ );
1313
+ ok(
1314
+ "the fixture mailbox payload exactly equals the source-validated Codex call argument",
1315
+ finalValidation.accepted,
1316
+ `expected=${JSON.stringify(expectedFinalMessage)}\nactual=${JSON.stringify(finalValidation.parsed?.payload)}`,
1317
+ );
1318
+
1319
+ // Final audit closes the race left by polling a completed subset. Codex's final tool result can
1320
+ // precede turn completion, so wait for the app-server's own terminal turn state before recounting.
1321
+ codexThread = await untilAsync("the Codex final turn to become terminal", SOURCE_WAIT_MS, async () => {
1322
+ const observed = await readCodexThread(codexSocketPath, codexThreadId, true);
1323
+ return codexThreadIsTerminal(observed) ? observed : null;
1324
+ });
1325
+ assertExactSourceToolCalls(
1326
+ piSourceToolReceipts(readInitialPiEntries()),
1327
+ [
1328
+ {
1329
+ toolName: "entwurf_v2",
1330
+ arguments: {
1331
+ target: callerGid,
1332
+ intent: "fire-and-forget",
1333
+ message: initialPiNonce,
1334
+ wants_reply: false,
1335
+ },
1336
+ },
1337
+ {
1338
+ toolName: "entwurf_fresh_call",
1339
+ arguments: {
1340
+ backend: "codex",
1341
+ model: codexModel,
1342
+ cwd: scratch,
1343
+ task: codexTask,
1344
+ },
1345
+ },
1346
+ {
1347
+ toolName: "entwurf_v2",
1348
+ arguments: {
1349
+ target: codexGardenId,
1350
+ intent: "fire-and-forget",
1351
+ wants_reply: false,
1352
+ message: codexInstruction,
1353
+ },
1354
+ },
1355
+ ],
1356
+ "initial Pi final exact-once audit",
1357
+ );
1358
+ assertExactSourceToolCalls(
1359
+ codexSourceToolReceipts(codexThread),
1360
+ [
1361
+ {
1362
+ toolName: "entwurf_v2",
1363
+ arguments: {
1364
+ target: initialPiGid,
1365
+ intent: "fire-and-forget",
1366
+ wants_reply: false,
1367
+ message: codexNonce,
1368
+ },
1369
+ },
1370
+ {
1371
+ toolName: "entwurf_fresh_call",
1372
+ arguments: { backend: "pi", model: piModel, task: outboundPiTask },
1373
+ },
1374
+ {
1375
+ toolName: "entwurf_v2",
1376
+ arguments: {
1377
+ target: callerGid,
1378
+ intent: "fire-and-forget",
1379
+ wants_reply: false,
1380
+ message: expectedFinalMessage,
1381
+ },
1382
+ },
1383
+ ],
1384
+ "Codex final exact-once audit",
1385
+ );
1386
+ receipts["16-source-final-audit"] = "initial-pi=3/3 completed exact\ncodex=3/3 completed exact";
1387
+ }
1388
+
1389
+ let failure: unknown = null;
1390
+ try {
1391
+ await run();
1392
+ } catch (error) {
1393
+ failure = error;
1394
+ }
1395
+ // Cleanup authority runs on EVERY exit, not only on a wait that timed out. Only joined source
1396
+ // fresh-call receipts may add stable handles to the allowlist; mailbox prose is preserved as
1397
+ // diagnostic evidence but never authorizes a kill. Every stage is attempted even when an earlier
1398
+ // stage throws. A mandatory snapshot is copied after source recovery and before any destructive
1399
+ // turn/window/fixture cleanup, so cleanup-only failures cannot erase their own evidence.
1400
+ const cleanupFailures = await runCleanupStages([
1401
+ { label: "bridge", run: closeBridge },
1402
+ { label: "recovery", run: recoverEveryRecordedWindow },
1403
+ {
1404
+ label: "evidence",
1405
+ run: () => preservePreCleanupEvidence(failure ?? "run succeeded; mandatory pre-cleanup evidence checkpoint"),
1406
+ },
1407
+ { label: "codex-turn", run: interruptCodexSmokeTurn },
1408
+ { label: "window", run: cleanupWindows },
1409
+ { label: "fixture", run: cleanupFixture },
1410
+ ]);
1411
+ printReceipts();
1412
+ if (cleanupFailures.length > 0) {
1413
+ const cleanupError =
1414
+ `[${LABEL}] CLEANUP FAILURE — only the exact smoke-owned Codex turn, recorded stable window ids, and exact fixture artifacts were targeted:\n` +
1415
+ cleanupFailures.join("\n");
1416
+ console.error(cleanupError);
1417
+ failure = failure === null ? new Error(cleanupError) : new Error(`${String(failure)}\n${cleanupError}`);
1418
+ }
1419
+ if (failure !== null) {
1420
+ console.error(`[${LABEL}] FAIL: ${failure instanceof Error ? failure.message : String(failure)}`);
1421
+ process.exitCode = 1;
1422
+ } else {
1423
+ console.log(
1424
+ `[${LABEL}] ${passed} assertions ok — fixture -> initial visible Pi -> visible Codex/exact callback -> Pi-addressed native-push v2 -> Codex outbound v2 -> visible Pi/exact callback`,
1425
+ );
1426
+ }