@junghanacs/entwurf 0.18.2 → 0.20.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 (77) hide show
  1. package/AGENTS.md +2 -2
  2. package/BASELINE.md +23 -5
  3. package/CHANGELOG.md +415 -0
  4. package/DELIVERY.md +59 -6
  5. package/README.md +43 -12
  6. package/VERIFY.md +10 -2
  7. package/docs/acp-backend-rail.md +9 -1
  8. package/docs/external-mcp-host.md +8 -5
  9. package/docs/setup-clean-host.md +93 -7
  10. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +18 -7
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +3 -3
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +6 -2
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +76 -9
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +54 -6
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resolve-tmux-session.js +114 -0
  16. package/mcp/entwurf-bridge/src/index.ts +22 -7
  17. package/package.json +2 -2
  18. package/pi-extensions/acp-provider.ts +1 -1
  19. package/pi-extensions/entwurf-control.ts +27 -3
  20. package/pi-extensions/lib/acp/models.ts +3 -3
  21. package/pi-extensions/lib/acp/overlay.ts +6 -2
  22. package/pi-extensions/lib/meta-session.ts +25 -0
  23. package/pi-extensions/lib/mux-fresh-call.ts +98 -11
  24. package/pi-extensions/lib/mux-placement.ts +65 -10
  25. package/pi-extensions/lib/resolve-tmux-session.ts +137 -0
  26. package/run.sh +71 -26
  27. package/scripts/agy-bridge.sh +1 -1
  28. package/scripts/agy-imprint.sh +10 -7
  29. package/scripts/check-acp-carrier-augment.ts +6 -5
  30. package/scripts/check-acp-cortex.ts +41 -0
  31. package/scripts/check-acp-provider-surface.ts +9 -6
  32. package/scripts/check-copilot-birth-hook.ts +3 -1
  33. package/scripts/check-copilot-receive-arm.ts +169 -21
  34. package/scripts/check-copilot-statusline.ts +3 -1
  35. package/scripts/check-fresh-cut-gate.sh +54 -2
  36. package/scripts/check-gate-qualification.ts +13 -12
  37. package/scripts/check-hook-launch-topology.ts +6 -5
  38. package/scripts/check-install-surface.ts +2 -1
  39. package/scripts/check-meta-facts.ts +6 -4
  40. package/scripts/check-meta-hook-session-switch.ts +4 -3
  41. package/scripts/check-meta-identity-consumers.ts +8 -7
  42. package/scripts/check-meta-receiver-marker.ts +2 -1
  43. package/scripts/check-mux-launch-tmux.ts +77 -2
  44. package/scripts/check-mux-launch.ts +17 -0
  45. package/scripts/check-mux-placement-tmux.ts +61 -1
  46. package/scripts/check-mux-placement.ts +33 -0
  47. package/scripts/check-omp-birth-hook.ts +3 -2
  48. package/scripts/check-probe-bridge-command.ts +3 -1
  49. package/scripts/check-setup-qualification.sh +21 -0
  50. package/scripts/copilot-bridge-doctor.sh +15 -5
  51. package/scripts/copilot-bridge-install.sh +6 -2
  52. package/scripts/copilot-launch.sh +16 -2
  53. package/scripts/copilot-receive-bridge.sh +80 -13
  54. package/scripts/lib/reclaim-on-exit.ts +86 -0
  55. package/scripts/meta-bridge-doctor.sh +56 -15
  56. package/scripts/meta-bridge-install.sh +13 -7
  57. package/scripts/meta-bridge-uninstall.sh +6 -4
  58. package/scripts/mutants/acp-cortex.json +26 -2
  59. package/scripts/mutants/copilot-receive.json +26 -0
  60. package/scripts/mutants/fresh-cut.json +26 -0
  61. package/scripts/mutants/mux-boundary.json +24 -0
  62. package/scripts/mutants/mux-fresh-call.json +81 -0
  63. package/scripts/mutants/omp-birth.json +33 -3
  64. package/scripts/mutants/setup-verdict.json +13 -0
  65. package/scripts/omp-bridge-doctor.sh +110 -31
  66. package/scripts/omp-bridge-install.sh +6 -2
  67. package/scripts/omp-receive-install.sh +7 -2
  68. package/scripts/raw-async-delivery/README.md +10 -1
  69. package/scripts/raw-codex-measure/README.md +689 -0
  70. package/scripts/raw-codex-measure/source-audit.md +243 -0
  71. package/scripts/raw-macos-measure/README.md +148 -0
  72. package/scripts/raw-macos-measure/probe.sh +389 -0
  73. package/scripts/smoke-meta-async-drift.sh +7 -5
  74. package/scripts/smoke-meta-install-state.sh +70 -19
  75. package/scripts/smoke-mux-fresh-call-live.ts +175 -3
  76. package/scripts/smoke-omp-bridge-state.sh +43 -2
  77. package/scripts/smoke-setup-verdict.sh +100 -0
@@ -2,7 +2,9 @@
2
2
  * smoke-mux-fresh-call-live — the ONE axis a deterministic gate cannot reach: a real window, a
3
3
  * real runtime, a real first turn, and a real callback arriving on a real inbound surface.
4
4
  *
5
- * OUT of `pnpm check`. Needs `LIVE=1`. Costs two model turns (one pi, one Claude Code).
5
+ * OUT of `pnpm check`. Needs `LIVE=1`. Costs FOUR model turns (three pi, one Claude Code): two
6
+ * open in the caller's own session, and #105 added two more in a second SEAT session on the same
7
+ * private server so the seat x cwd matrix is read on real panes rather than argued about.
6
8
  *
7
9
  * ── Isolate the WRITES, keep the runtimes real ──
8
10
  *
@@ -59,6 +61,17 @@ const REAL_CONTROL_DIR = path.join(REAL_HOME, ".pi", "entwurf-control");
59
61
  // so the real-HOME Claude cell can be given exact operator-env parity (issue #67).
60
62
  const ORIGINAL_XDG = snapshotOriginalXdg(process.env);
61
63
 
64
+ // The #105 seat fixture. A second session on the SAME private server, its own session_path, and
65
+ // two distinct requested cwds — three directories that must never be confusable with each other.
66
+ const SEAT_SESSION = "seat";
67
+ const SEAT_HOME = (root: string): string => path.join(root, "seat-home");
68
+ const CROSS_REPO_A = (root: string): string => path.join(root, "cross-repo-a");
69
+ const CROSS_REPO_B = (root: string): string => path.join(root, "cross-repo-b");
70
+ /** tmux fills `#{pane_current_path}` with the SERVER's cwd at creation time and only becomes
71
+ * true 0.2–0.6s later (measured ×2, #105 R1) — so acceptance reads it after a delay, never from
72
+ * the launch receipt. */
73
+ const PANE_CWD_SETTLE_SECONDS = "3";
74
+
62
75
  let passed = 0;
63
76
  function ok(label: string, cond: boolean): void {
64
77
  if (!cond) throw new Error(`${LABEL}: FAILED — ${label}`);
@@ -89,6 +102,13 @@ function tmux(socket: string, args: string[], env: NodeJS.ProcessEnv): { status:
89
102
  return { status: r.status, stdout: (r.stdout ?? "").trim() };
90
103
  }
91
104
 
105
+ /** The session the caller's own anchor pane sits in — tmux's answer, never a name we assembled. */
106
+ function inspectedSessionId(socket: string, env: NodeJS.ProcessEnv): string {
107
+ return tmux(socket, ["display-message", "-p", "-t", String(env.TMUX_PANE), "#{session_id}"], env).stdout.split(
108
+ "\n",
109
+ )[0];
110
+ }
111
+
92
112
  function pidIsAlive(pid: number): boolean {
93
113
  try {
94
114
  process.kill(pid, 0);
@@ -150,6 +170,10 @@ async function main(): Promise<void> {
150
170
  };
151
171
  for (const dir of Object.values(fenced)) fs.mkdirSync(dir, { recursive: true });
152
172
  fs.mkdirSync(scratch, { recursive: true });
173
+ // The #105 seat cells need three directories that are all DIFFERENT from each other and from
174
+ // the scratch cwd, so "the pane landed here" can only be true for one reason. `seatHome` is
175
+ // the second session's own `session_path` — the candidate the cwd must NOT come from.
176
+ for (const dir of [SEAT_HOME(root), CROSS_REPO_A(root), CROSS_REPO_B(root)]) fs.mkdirSync(dir, { recursive: true });
153
177
  // A runtime dir is a private surface by convention and tmux checks it.
154
178
  fs.chmodSync(fenced.XDG_RUNTIME_DIR, 0o700);
155
179
  for (const [k, v] of Object.entries(fenced)) process.env[k] = v;
@@ -160,7 +184,7 @@ async function main(): Promise<void> {
160
184
  // Import the meta layer only AFTER the redirects — a module that resolved its roots at import
161
185
  // time would have captured the operator's.
162
186
  const meta = await import("../pi-extensions/lib/meta-session.ts");
163
- const { freshCall } = await import("../pi-extensions/lib/mux-fresh-call.ts");
187
+ const { freshCall, renderFreshCall } = await import("../pi-extensions/lib/mux-fresh-call.ts");
164
188
 
165
189
  const siblingGids = new Set<string>();
166
190
  const siblingPids = new Set<number>();
@@ -223,6 +247,11 @@ async function main(): Promise<void> {
223
247
  // onboarding instead of exercising the configured runtime. Separate servers preserve
224
248
  // those backend-native environments without adding an env carrier to the product.
225
249
  const nonces = new Map<string, string>();
250
+ /** Every launched cell's pane, with the directory the pane is REQUIRED to have settled in
251
+ * — read after a delay, which is the only honest way to observe it (see the constant). */
252
+ const cwdCells: Array<{ label: string; socket: string; env: NodeJS.ProcessEnv; paneId: string; expect: string }> =
253
+ [];
254
+ const servers = new Map<string, { socket: string; inherited: NodeJS.ProcessEnv }>();
226
255
  // Intentional: pi + claude-code only. Copilot clause-7 LIVE is operator-metered
227
256
  // and is not a release MUST — do not add `copilot` to this loop (VERIFY.md).
228
257
  for (const backend of ["pi", "claude-code"] as const) {
@@ -248,12 +277,17 @@ async function main(): Promise<void> {
248
277
  const anchorPane = tmux(socket, ["display-message", "-p", "-t", "fixture:anchor", "#{pane_id}"], env).stdout;
249
278
  const inherited: NodeJS.ProcessEnv = { ...env, TMUX: `${socket},0,0`, TMUX_PANE: anchorPane };
250
279
  ok(`${backend}: tmux server is a private socket, never the operator's`, String(inherited.TMUX).startsWith(root));
280
+ servers.set(backend, { socket, inherited });
251
281
 
282
+ // The caller-session half of the #105 cwd matrix: pi omits the cwd, claude-code names
283
+ // one. Both open in the CALLER's own session, which is the pre-#105 behaviour.
284
+ const requestedCwd = backend === "claude-code" ? CROSS_REPO_A(root) : undefined;
252
285
  const result = freshCall(
253
286
  {
254
287
  backend,
255
288
  model: LIVE_MODEL[backend],
256
289
  task: "Reply with the single word ACK and then stop. Do not read files.",
290
+ cwd: requestedCwd,
257
291
  callerGardenId: callerGid,
258
292
  },
259
293
  inherited,
@@ -262,6 +296,13 @@ async function main(): Promise<void> {
262
296
  if (!result.ok) return;
263
297
  nonces.set(backend, result.receipt.nonce);
264
298
  siblingPids.add(Number(result.receipt.panePid));
299
+ cwdCells.push({
300
+ label: `${backend} @caller-session cwd=${requestedCwd === undefined ? "(omitted)" : "requested"}`,
301
+ socket,
302
+ env: inherited,
303
+ paneId: result.receipt.paneId,
304
+ expect: requestedCwd ?? scratch,
305
+ });
265
306
  ok(
266
307
  `${backend}: the receipt carries tmux coordinates, requested model and nonce, and nothing about delivery`,
267
308
  Boolean(
@@ -271,6 +312,113 @@ async function main(): Promise<void> {
271
312
  result.receipt.nonce,
272
313
  ) && !("gardenId" in result.receipt),
273
314
  );
315
+ ok(
316
+ `${backend}: with no seat requested the window is in the CALLER's own session and the receipt names no seat`,
317
+ result.receipt.sessionId === inspectedSessionId(socket, inherited) && result.receipt.tmuxSession === undefined,
318
+ );
319
+ }
320
+
321
+ // ── #105 acceptance: the optional project seat ───────────────────────────
322
+ // One extra session on the SAME private pi server (R6: a second session is one line and
323
+ // costs no extra server). Its own `-c` gives it a session_path that is neither the
324
+ // caller's cwd nor either requested cwd, so the cwd matrix below can only pass for one
325
+ // reason.
326
+ const pi = servers.get("pi");
327
+ if (!pi) throw new Error(`${LABEL}: the pi fixture server was not captured`);
328
+ const callerSessionId = inspectedSessionId(pi.socket, pi.inherited);
329
+ if (
330
+ tmux(pi.socket, ["new-session", "-d", "-s", SEAT_SESSION, "-c", SEAT_HOME(root), "-n", "anchor"], pi.inherited)
331
+ .status !== 0
332
+ ) {
333
+ throw new Error(`${LABEL}: could not create the second fixture session ${SEAT_SESSION}`);
334
+ }
335
+ const seatSessionId = tmux(
336
+ pi.socket,
337
+ ["list-windows", "-t", `=${SEAT_SESSION}`, "-F", "#{session_id}"],
338
+ pi.inherited,
339
+ ).stdout.split("\n")[0];
340
+ ok(
341
+ "seat: the fixture now holds two sessions on ONE server, and the seat is not the caller's",
342
+ /^\$[0-9]+$/.test(seatSessionId) && seatSessionId !== callerSessionId,
343
+ );
344
+
345
+ // Acceptance 2 FIRST, because it is the one that must leave nothing behind. Both refusals
346
+ // are read against a byte-identical session/window inventory.
347
+ const inventory = (): string =>
348
+ tmux(pi.socket, ["list-windows", "-a", "-F", "#{session_id}|#{window_id}"], pi.inherited).stdout;
349
+ const beforeRefusals = inventory();
350
+ for (const [label, seat, reason] of [
351
+ ["absent", "nosuchseat", "tmux-session-missing"],
352
+ ["unresolvable", "no.such.seat", "tmux-session-name-invalid"],
353
+ ] as const) {
354
+ const refused = freshCall(
355
+ {
356
+ backend: "pi",
357
+ model: LIVE_MODEL.pi,
358
+ task: "This task must never run.",
359
+ placement: { tmuxSession: seat },
360
+ callerGardenId: callerGid,
361
+ },
362
+ pi.inherited,
363
+ );
364
+ ok(
365
+ `seat: an ${label} seat is refused as ${reason}, and NOTHING was created`,
366
+ !refused.ok && refused.reason === reason && inventory() === beforeRefusals,
367
+ );
368
+ const rendered = renderFreshCall(refused);
369
+ ok(
370
+ `seat: the ${label} refusal says so in the operator's words`,
371
+ rendered.isError && rendered.text.includes("No window was opened"),
372
+ );
373
+ }
374
+
375
+ // Acceptance 1 + the other half of the cwd matrix: two seated pi siblings, one with the
376
+ // cwd omitted and one with a requested cwd.
377
+ for (const [label, requestedCwd] of [
378
+ ["seat cwd=(omitted)", undefined],
379
+ ["seat cwd=requested", CROSS_REPO_B(root)],
380
+ ] as const) {
381
+ const callerWindowsBefore = tmux(
382
+ pi.socket,
383
+ ["list-windows", "-t", callerSessionId, "-F", "#{window_id}"],
384
+ pi.inherited,
385
+ ).stdout;
386
+ const seated = freshCall(
387
+ {
388
+ backend: "pi",
389
+ model: LIVE_MODEL.pi,
390
+ task: "Reply with the single word ACK and then stop. Do not read files.",
391
+ cwd: requestedCwd,
392
+ placement: { tmuxSession: SEAT_SESSION },
393
+ callerGardenId: callerGid,
394
+ },
395
+ pi.inherited,
396
+ );
397
+ ok(`${label}: launch receipt is ok`, seated.ok);
398
+ if (!seated.ok) return;
399
+ nonces.set(label, seated.receipt.nonce);
400
+ siblingPids.add(Number(seated.receipt.panePid));
401
+ cwdCells.push({
402
+ label: `pi @${label}`,
403
+ socket: pi.socket,
404
+ env: pi.inherited,
405
+ paneId: seated.receipt.paneId,
406
+ expect: requestedCwd ?? scratch,
407
+ });
408
+ ok(
409
+ `${label}: the receipt echoes the REQUESTED seat name and reports the RESOLVED target session, not the caller's`,
410
+ seated.receipt.tmuxSession === SEAT_SESSION &&
411
+ seated.receipt.sessionId === seatSessionId &&
412
+ seated.receipt.sessionId !== callerSessionId,
413
+ );
414
+ ok(
415
+ `${label}: the window really landed in the seat, and the caller's session is byte-identical`,
416
+ tmux(pi.socket, ["list-windows", "-t", seatSessionId, "-F", "#{window_id}"], pi.inherited).stdout.includes(
417
+ seated.receipt.windowId,
418
+ ) &&
419
+ tmux(pi.socket, ["list-windows", "-t", callerSessionId, "-F", "#{window_id}"], pi.inherited).stdout ===
420
+ callerWindowsBefore,
421
+ );
274
422
  }
275
423
 
276
424
  // The sender envelope rides INSIDE the mailbox body (` session: <gid> (…)`) — that line
@@ -295,9 +443,33 @@ async function main(): Promise<void> {
295
443
  if (sender) siblingGids.add(sender);
296
444
  }
297
445
  ok(
298
- "correlation: the two siblings reported DIFFERENT garden ids — the envelope identifies each one, not the launcher",
446
+ "correlation: every sibling reported a DIFFERENT garden id — the envelope identifies each one, not the launcher",
299
447
  siblingGids.size === arrived.size && !siblingGids.has(callerGid),
300
448
  );
449
+
450
+ // ── #105 acceptance 3: the seat and the cwd are ORTHOGONAL ───────────────
451
+ // Read AFTER a delay and from the stable %pane, never from the launch receipt: at
452
+ // creation time `#{pane_current_path}` reports the tmux SERVER's cwd in every cell
453
+ // (measured ×2), which is why the receipt reports the REQUESTED cwd and this is the
454
+ // acceptance that reports the observed one. The 2×2 is {cwd omitted, cwd requested} ×
455
+ // {caller session, seat}, and the seat's own `session_path` is a fourth directory that
456
+ // no cell may ever land in.
457
+ spawnSync("sleep", [PANE_CWD_SETTLE_SECONDS]);
458
+ for (const cell of cwdCells) {
459
+ const observed = tmux(
460
+ cell.socket,
461
+ ["display-message", "-p", "-t", cell.paneId, "#{pane_current_path}"],
462
+ cell.env,
463
+ ).stdout;
464
+ ok(
465
+ `cwd x seat: ${cell.label} settled in ${cell.expect} — never the target session's path, never the other cell's`,
466
+ observed === cell.expect && observed !== SEAT_HOME(root),
467
+ );
468
+ }
469
+ ok(
470
+ "cwd x seat: all four cells ran — {cwd omitted, cwd requested} x {caller session, seat}",
471
+ cwdCells.length === 4 && new Set(cwdCells.map((c) => c.expect)).size === 3,
472
+ );
301
473
  ok(
302
474
  "fence: those sibling records live in the FIXTURE store",
303
475
  [...siblingGids].every(
@@ -22,7 +22,10 @@ wait_carrier() {
22
22
  local pid="$1" key="$2" i=0
23
23
  while [ "$i" -lt 50 ]; do
24
24
  if kill -0 "$pid" 2>/dev/null && [ -r "/proc/$pid/environ" ]; then
25
- if tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep -q "^${key}"; then
25
+ # `-c` not `-q`, for the reason in omp-bridge-doctor.sh's carrier scan: under
26
+ # pipefail an early-exiting grep SIGPIPEs `tr` and the pipeline reports failure even
27
+ # on a match. Here that only costs a retry, but the hazard is the same one.
28
+ if [ "$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep -cE "^${key}" || true)" -gt 0 ]; then
26
29
  return 0
27
30
  fi
28
31
  fi
@@ -103,7 +106,7 @@ CARRIER_PID=$!
103
106
  wait_carrier "$CARRIER_PID" "PI_SESSION_ID=foreign-garden"
104
107
  if OUT="$(ENTWURF_OMP_CARRIER_PIDS="$CARRIER_PID" "$RUN" doctor-omp-bridge 2>&1)"; then
105
108
  printf '%s\n' "$OUT" >&2
106
- die "doctor accepted the live omp fixture with nonblank inherited PI carriers"
109
+ die "[QK:OMP-DOCTOR-CARRIER-NONBLANK-IS-RED] doctor accepted the live omp fixture with nonblank inherited PI carriers"
107
110
  fi
108
111
  printf '%s\n' "$OUT" | grep -q "live omp process(es).* $CARRIER_PID" || die "doctor red without naming the nonblank-carrier fixture"
109
112
  ok "a nonblank inherited PI carrier on a live omp process remains red"
@@ -111,6 +114,44 @@ kill "$CARRIER_PID"
111
114
  wait "$CARRIER_PID" 2>/dev/null || true
112
115
  CARRIER_PID=""
113
116
 
117
+ # THE CANDIDATE SET ITSELF CAN BE UNKNOWN, AND UNKNOWN IS NOT EMPTY (#78 B1). `pgrep`
118
+ # answers three ways and only two of them are facts about omp: 0 = it enumerated and
119
+ # matched, 1 = it enumerated and NOTHING matched (a real, positive absence), anything
120
+ # else = it never enumerated. The doctor used to `|| true` that third answer into the
121
+ # second, so a broken enumeration produced an empty candidate set, the clean carrier
122
+ # line and a PASS — with a contaminated session possibly live on the host.
123
+ #
124
+ # BOTH DIRECTIONS ARE PINNED, because a repair that also reddened the honest "nothing
125
+ # matched" would be a different defect. `ENTWURF_OMP_CARRIER_PIDS` must be UNSET for
126
+ # these two: that seam short-circuits the very enumeration under test, which is why it
127
+ # is a narrowing seam and never production proof.
128
+ PGREP_SHIM="$SB/pgrep-shim"
129
+ mkdir -p "$PGREP_SHIM"
130
+ printf '#!/usr/bin/env bash\nexit 1\n' > "$PGREP_SHIM/pgrep"
131
+ chmod +x "$PGREP_SHIM/pgrep"
132
+ if OUT="$(env -u ENTWURF_OMP_CARRIER_PIDS PATH="$PGREP_SHIM:$PATH" "$RUN" doctor-omp-bridge 2>&1)"; then
133
+ printf '%s\n' "$OUT" | grep -q "no live omp process carries" || die "an enumerated-and-empty candidate set did not print the clean carrier verdict"
134
+ ok "a \`pgrep\` that enumerated and matched NOTHING is a real absence and stays green"
135
+ else
136
+ printf '%s\n' "$OUT" >&2
137
+ die "a \`pgrep\` exit 1 (nothing matched) turned the carrier axis red"
138
+ fi
139
+ printf '#!/usr/bin/env bash\nexit 2\n' > "$PGREP_SHIM/pgrep"
140
+ if OUT="$(env -u ENTWURF_OMP_CARRIER_PIDS PATH="$PGREP_SHIM:$PATH" "$RUN" doctor-omp-bridge 2>&1)"; then
141
+ printf '%s\n' "$OUT" >&2
142
+ die "[QK:OMP-DOCTOR-UNENUMERATED-IS-UNVERIFIABLE] a FAILED process enumeration left the doctor green — it printed a carrier verdict with no candidate set, so a live omp session carrying a foreign pi garden id would read as clean"
143
+ fi
144
+ if ! printf '%s\n' "$OUT" | grep -q "UNVERIFIABLE — 'pgrep -x omp' FAILED (exit 2)"; then
145
+ printf '%s\n' "$OUT" >&2
146
+ die "the doctor went red without naming the failed enumeration as UNVERIFIABLE"
147
+ fi
148
+ if printf '%s\n' "$OUT" | grep -q "no live omp process carries"; then
149
+ printf '%s\n' "$OUT" >&2
150
+ die "the doctor claimed a clean carrier axis while the candidate set was unknown"
151
+ fi
152
+ ok "a FAILED enumeration is UNVERIFIABLE and red, and it claims neither contamination nor absence"
153
+ rm -rf "$PGREP_SHIM"
154
+
114
155
  # ── 2. reinstall is idempotent ───────────────────────────────────────────────
115
156
  "$RUN" install-omp-bridge >/dev/null || die "reinstall over our own state failed"
116
157
  "$RUN" doctor-omp-bridge >/dev/null || die "doctor red after a reinstall"
@@ -287,6 +287,106 @@ rm -rf "$HOME/.omp" "$XDG_DATA_HOME/entwurf/omp-bridge" "$XDG_DATA_HOME/entwurf/
287
287
  "$XDG_DATA_HOME/entwurf/omp-config" "$XDG_DATA_HOME/entwurf/omp-receive" \
288
288
  "$XDG_DATA_HOME/entwurf/meta-bridge-omp" "$XDG_DATA_HOME/entwurf/omp-receive"
289
289
 
290
+ # ── S-9: rail certification is a SEPARATE axis from install success (#78 D1) ──
291
+ # The 0.20.0 macOS lane opened the installers to Darwin (meta-bridge-install.sh's
292
+ # platform gate became its own python3/node dependency instead of the platform
293
+ # name), and that removed the only thing which used to keep a detected Mac host
294
+ # non-green: the installer's refusal. A detected harness on Darwin now INSTALLS,
295
+ # every harness doctor still refuses there (NOT CERTIFIED — pending physical
296
+ # host), and `setup` runs no doctor. Hard Rule 17 names that shape non-green, so
297
+ # setup_harness_result does — and these cells are its ONLY executing consumer,
298
+ # because nothing else in this repo ever takes the Darwin branch.
299
+ #
300
+ # `uname` is faked on PATH exactly the way smoke-meta-install-state.sh already
301
+ # fakes it for the installer's own platform gate. Both directions are pinned so
302
+ # the fixture cannot pass for the wrong reason: the Linux twin below is the SAME
303
+ # sandbox, the SAME stub harnesses and the SAME fixture with one byte different,
304
+ # and S-3/S-8 above are the unfaked real-host Linux evidence.
305
+ FAKE_UNAME="$SB/fake-uname-darwin"; mkdir -p "$FAKE_UNAME"
306
+ # Absolute bash in the shebang, same reason as the sibling fixture: a cell may
307
+ # hand this script a PATH that does not carry `env`/`bash` itself.
308
+ cat > "$FAKE_UNAME/uname" <<SH
309
+ #!$(command -v bash)
310
+ printf '%s\n' Darwin
311
+ SH
312
+ chmod +x "$FAKE_UNAME/uname"
313
+ FAKE_UNAME_LINUX="$SB/fake-uname-linux"; mkdir -p "$FAKE_UNAME_LINUX"
314
+ cat > "$FAKE_UNAME_LINUX/uname" <<SH
315
+ #!$(command -v bash)
316
+ printf '%s\n' Linux
317
+ SH
318
+ chmod +x "$FAKE_UNAME_LINUX/uname"
319
+
320
+ omp_reset() {
321
+ rm -rf "$HOME/.omp" "$XDG_DATA_HOME/entwurf/omp-bridge" "$XDG_DATA_HOME/entwurf/omp-mcp" \
322
+ "$XDG_DATA_HOME/entwurf/omp-config" "$XDG_DATA_HOME/entwurf/omp-receive" \
323
+ "$XDG_DATA_HOME/entwurf/meta-bridge-omp"
324
+ }
325
+
326
+ # ── S-9a: fake Darwin + DETECTED harnesses → named non-green, install intact ──
327
+ # Two harness FAMILIES on purpose (the pi adapter rail and the four omp units):
328
+ # a rule applied to one backend and not the next is a new defect, not half a fix.
329
+ echo "[smoke-setup-verdict] S-9a detected harnesses on an uncertified platform"
330
+ PROJ9="$SB/proj9"; mkdir -p "$PROJ9"
331
+ seed_auth
332
+ set +e; OUT="$(PI_BIN="$SB/harness/pi-ok" OMP_BIN="$FAKE_OMP/omp" ENTWURF_OMP_AGENT_DIR="$HOME/.omp/agent" PATH="$FAKE_UNAME:$FAKE_OMP:$PATH" bash "$REPO_DIR/run.sh" setup "$PROJ9" 2>&1)"; RC=$?; set -e
333
+ want "S-9a: a detected harness on an uncertified platform owns a nonzero setup exit" "[ '$RC' -ne 0 ]"
334
+ want "S-9a: the pi adapter rail is named non-green, never a cosmetic PASS" \
335
+ "printf '%s' \"\$OUT\" | grep -q 'pi: FAIL' && ! printf '%s' \"\$OUT\" | grep -q 'pi: PASS'"
336
+ want "S-9a: all four omp units are named non-green, none cosmetically PASS" \
337
+ "printf '%s' \"\$OUT\" | grep -q 'omp-birth: FAIL' && printf '%s' \"\$OUT\" | grep -q 'omp-mcp: FAIL' && printf '%s' \"\$OUT\" | grep -q 'omp-config: FAIL' && printf '%s' \"\$OUT\" | grep -q 'omp-receive: FAIL' && ! printf '%s' \"\$OUT\" | grep -q 'omp-[a-z]*: PASS'"
338
+ want "S-9a: the reason says the wiring WAS written (this is not an install failure)" \
339
+ "printf '%s' \"\$OUT\" | grep -q 'the wiring WAS written and nothing failed to install'"
340
+ want "S-9a: the reason names the RAIL evidence state and the platform" \
341
+ "printf '%s' \"\$OUT\" | grep -q 'NOT CERTIFIED — pending physical host on Darwin'"
342
+ want "S-9a: the reason names the doctor that owns the rail axis, per component" \
343
+ "printf '%s' \"\$OUT\" | grep -qF \"owned by './run.sh doctor-omp-bridge'\" && printf '%s' \"\$OUT\" | grep -qF \"owned by './run.sh doctor-pi-provider'\""
344
+ want "S-9a: the reason points at the tracking issue" "printf '%s' \"\$OUT\" | grep -q 'Tracking: #78'"
345
+ want "S-9a: it never reads as an install refusal (no unsupported-platform / did-not-complete vocabulary)" \
346
+ "! printf '%s' \"\$OUT\" | grep -qi 'unsupported platform' && ! printf '%s' \"\$OUT\" | grep -q 'did not complete'"
347
+ want "S-9a: the summary is NON-GREEN and names the uncertified components" \
348
+ "printf '%s' \"\$OUT\" | grep -q 'NON-GREEN (FAIL:' && ! printf '%s' \"\$OUT\" | grep -q 'result: green'"
349
+ # The load-bearing half: the installs genuinely SUCCEEDED. Without this the cell
350
+ # would also pass if the platform had merely broken the installers.
351
+ want "S-9a: the wiring really landed — pi project settings plus both omp extensions and the MCP hand" \
352
+ "[ -f '$PROJ9/.pi/settings.json' ] && [ -d '$HOME/.omp/agent/extensions/entwurf-meta-omp' ] && [ -d '$HOME/.omp/agent/extensions/entwurf-receive-omp' ] && [ -f '$HOME/.omp/agent/mcp.json' ]"
353
+ want_auth_untouched "S-9a"
354
+ omp_reset
355
+
356
+ # ── S-9b: the Linux twin — same fixture, one byte different → still PASS/green ──
357
+ # This is the regression axis that matters most: the certified platform's behavior
358
+ # must not have moved at all.
359
+ echo "[smoke-setup-verdict] S-9b the same detected harnesses on the certified platform"
360
+ PROJ9B="$SB/proj9b"; mkdir -p "$PROJ9B"
361
+ seed_auth
362
+ set +e; OUT="$(PI_BIN="$SB/harness/pi-ok" OMP_BIN="$FAKE_OMP/omp" ENTWURF_OMP_AGENT_DIR="$HOME/.omp/agent" PATH="$FAKE_UNAME_LINUX:$FAKE_OMP:$PATH" bash "$REPO_DIR/run.sh" setup "$PROJ9B" 2>&1)"; RC=$?; set -e
363
+ want "S-9b: the certified platform still exits 0" "[ '$RC' -eq 0 ]"
364
+ want "S-9b: pi and all four omp units still compose as PASS" \
365
+ "printf '%s' \"\$OUT\" | grep -q 'pi: PASS' && printf '%s' \"\$OUT\" | grep -q 'omp-birth: PASS' && printf '%s' \"\$OUT\" | grep -q 'omp-mcp: PASS' && printf '%s' \"\$OUT\" | grep -q 'omp-config: PASS' && printf '%s' \"\$OUT\" | grep -q 'omp-receive: PASS'"
366
+ want "S-9b: a PASS row is byte-identical to the pre-#78 wording (the operator surface did not move)" \
367
+ "printf '%s' \"\$OUT\" | grep -q 'omp-birth: PASS — birth extension installed — verify: ./run.sh doctor-omp-bridge'"
368
+ want "S-9b: the computed summary is green and carries no rail-certification wording" \
369
+ "printf '%s' \"\$OUT\" | grep -q 'result: green (computed from the component outcomes above)' && ! printf '%s' \"\$OUT\" | grep -q 'NOT CERTIFIED'"
370
+ want_auth_untouched "S-9b"
371
+ omp_reset
372
+
373
+ # ── S-9c: fake Darwin + NO harness → still green (the macOS CI job's contract) ──
374
+ # The macos-install-surface job asserts exactly this shape on a real Darwin runner:
375
+ # five zero-state SKIPs, core PASS, computed green. The rule above must not reach
376
+ # an absent harness — SKIP is not a completed integration.
377
+ echo "[smoke-setup-verdict] S-9c harness-free host on an uncertified platform"
378
+ PROJ9C="$SB/proj9c"; mkdir -p "$PROJ9C"
379
+ seed_auth
380
+ set +e; OUT="$(PATH="$FAKE_UNAME:$PATH" bash "$REPO_DIR/run.sh" setup "$PROJ9C" 2>&1)"; RC=$?; set -e
381
+ 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'"
384
+ want "S-9c: core still PASSes and the computed summary is still green" \
385
+ "printf '%s' \"\$OUT\" | grep -q 'core: PASS' && printf '%s' \"\$OUT\" | grep -q 'result: green (computed from the component outcomes above)'"
386
+ want "S-9c: an absent harness is never given a rail verdict it did not earn" \
387
+ "! printf '%s' \"\$OUT\" | grep -q 'NOT CERTIFIED'"
388
+ want_auth_untouched "S-9c"
389
+
290
390
  # ── S-5: installed mode is a NAMED first verdict, never the source bootstrap ──
291
391
  # A bare copy of run.sh under a fake node_modules root pins the mode seam
292
392
  # cheaply: the copy is NOT a runnable installed package (no dist), so this cell