@junghanacs/entwurf 0.19.0 → 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 (59) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +23 -5
  3. package/CHANGELOG.md +236 -0
  4. package/DELIVERY.md +59 -6
  5. package/README.md +40 -9
  6. package/VERIFY.md +9 -1
  7. package/docs/acp-backend-rail.md +9 -1
  8. package/docs/external-mcp-host.md +6 -3
  9. package/docs/setup-clean-host.md +93 -7
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +3 -3
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +6 -2
  12. package/package.json +2 -2
  13. package/pi-extensions/acp-provider.ts +1 -1
  14. package/pi-extensions/lib/acp/models.ts +3 -3
  15. package/pi-extensions/lib/acp/overlay.ts +6 -2
  16. package/pi-extensions/lib/meta-session.ts +25 -0
  17. package/run.sh +71 -26
  18. package/scripts/agy-bridge.sh +1 -1
  19. package/scripts/agy-imprint.sh +10 -7
  20. package/scripts/check-acp-carrier-augment.ts +6 -5
  21. package/scripts/check-acp-cortex.ts +41 -0
  22. package/scripts/check-acp-provider-surface.ts +9 -6
  23. package/scripts/check-copilot-birth-hook.ts +3 -1
  24. package/scripts/check-copilot-receive-arm.ts +169 -21
  25. package/scripts/check-copilot-statusline.ts +3 -1
  26. package/scripts/check-gate-qualification.ts +10 -9
  27. package/scripts/check-hook-launch-topology.ts +6 -5
  28. package/scripts/check-install-surface.ts +2 -1
  29. package/scripts/check-meta-facts.ts +6 -4
  30. package/scripts/check-meta-hook-session-switch.ts +4 -3
  31. package/scripts/check-meta-identity-consumers.ts +8 -7
  32. package/scripts/check-meta-receiver-marker.ts +2 -1
  33. package/scripts/check-omp-birth-hook.ts +3 -2
  34. package/scripts/check-probe-bridge-command.ts +3 -1
  35. package/scripts/check-setup-qualification.sh +21 -0
  36. package/scripts/copilot-bridge-doctor.sh +15 -5
  37. package/scripts/copilot-bridge-install.sh +6 -2
  38. package/scripts/copilot-launch.sh +16 -2
  39. package/scripts/copilot-receive-bridge.sh +80 -13
  40. package/scripts/lib/reclaim-on-exit.ts +86 -0
  41. package/scripts/meta-bridge-doctor.sh +56 -15
  42. package/scripts/meta-bridge-install.sh +13 -7
  43. package/scripts/meta-bridge-uninstall.sh +6 -4
  44. package/scripts/mutants/acp-cortex.json +26 -2
  45. package/scripts/mutants/copilot-receive.json +26 -0
  46. package/scripts/mutants/omp-birth.json +23 -6
  47. package/scripts/mutants/setup-verdict.json +13 -0
  48. package/scripts/omp-bridge-doctor.sh +110 -41
  49. package/scripts/omp-bridge-install.sh +6 -2
  50. package/scripts/omp-receive-install.sh +7 -2
  51. package/scripts/raw-async-delivery/README.md +10 -1
  52. package/scripts/raw-codex-measure/README.md +689 -0
  53. package/scripts/raw-codex-measure/source-audit.md +243 -0
  54. package/scripts/raw-macos-measure/README.md +148 -0
  55. package/scripts/raw-macos-measure/probe.sh +389 -0
  56. package/scripts/smoke-meta-async-drift.sh +7 -5
  57. package/scripts/smoke-meta-install-state.sh +70 -19
  58. package/scripts/smoke-omp-bridge-state.sh +38 -0
  59. package/scripts/smoke-setup-verdict.sh +100 -0
@@ -323,6 +323,47 @@ const enrichedServers: AcpMcpServer[] = [
323
323
  /absolute realHome/,
324
324
  "a non-absolute realHome must refuse (the child must never re-derive it)",
325
325
  );
326
+ assert.throws(
327
+ () =>
328
+ ensureCortexDualHomeOverlay({
329
+ scopeKey: "pi:sess-empty",
330
+ mcpServers: [],
331
+ realHome: "",
332
+ overlaysRoot,
333
+ isPidAlive: () => true,
334
+ }),
335
+ /absolute realHome/,
336
+ "an empty realHome must refuse — the contract sentence is 'absolute', and '' is not",
337
+ );
338
+ // …and the guard states PATH-FLAVOR absoluteness, not the host it runs on:
339
+ // a drive/UNC realHome is absolute in the win32 flavor, so a POSIX host must
340
+ // NOT read it as relative. (This asserts nothing about native-Windows support
341
+ // — only that the refusal rule is decided by the path, not by process.platform.)
342
+ // realSnowflakeHome is pinned under tmp so the positive cells exercise the
343
+ // GUARD alone: the win32-flavored realHome never reaches a POSIX join for fs
344
+ // work, and every path the overlay materializes stays under overlaysRoot.
345
+ for (const winFlavored of ["C:\\Users\\x", "\\\\server\\share"]) {
346
+ let winOverlay: ReturnType<typeof ensureCortexDualHomeOverlay> | undefined;
347
+ assert.doesNotThrow(
348
+ () => {
349
+ winOverlay = ensureCortexDualHomeOverlay({
350
+ scopeKey: `pi:sess-win-${winFlavored.length}`,
351
+ mcpServers: [],
352
+ realHome: winFlavored,
353
+ realSnowflakeHome: realSnowflake,
354
+ overlaysRoot,
355
+ isPidAlive: () => true,
356
+ });
357
+ },
358
+ `a win32-flavored absolute realHome (${JSON.stringify(winFlavored)}) must PASS the guard — absoluteness is judged by PATH FLAVOR, never by the host the gate runs on [QK:CORTEX-REALHOME-PLATFORM-NEUTRAL]`,
359
+ );
360
+ assert.ok(
361
+ winOverlay !== undefined &&
362
+ winOverlay.scopeDir.startsWith(overlaysRoot) &&
363
+ existsSync(join(winOverlay.snowflakeHome, "cortex")),
364
+ "the accepted win32-flavored realHome must still materialize entirely under the overlays root (the guard is the only thing the flavor cell widens)",
365
+ );
366
+ }
326
367
  } finally {
327
368
  rmSync(root, { recursive: true, force: true });
328
369
  }
@@ -138,19 +138,22 @@ try {
138
138
  // Set comparison (sorted, exact) — not `includes`: a substring/anchor probe
139
139
  // cannot see a row that VANISHED, and the cortex ids contain the claude ids as
140
140
  // substrings. Expected is derived from the two source id constants so there is
141
- // one SSOT; the literal 6 below is the independent floor that catches a
142
- // curation list mutated to empty (which would move expected and captured
143
- // together).
141
+ // one SSOT; the literal 7 below is the independent floor that catches a
142
+ // curation list that SHRANK expected derives from the same constants, so a
143
+ // dropped row moves expected and captured together and the set comparison
144
+ // stays green. That is why the count is asserted separately and carries its
145
+ // own claim: the cortex list has `CORTEX-CURATED-FOUR-ROWS` for exactly this
146
+ // failure, and the claude list needs its own oracle rather than borrowing it.
144
147
  const expectedIds = [...SUPPORTED_ANTHROPIC_MODEL_IDS, ...SUPPORTED_CORTEX_MODEL_IDS].slice().sort();
145
148
  assert.equal(
146
149
  expectedIds.length,
147
- 6,
148
- `curated id constants must total 6 rows (claude 2 + cortex 4) — got ${expectedIds.length}: ${expectedIds.join(", ")}`,
150
+ 7,
151
+ `[QK:CLAUDE-CURATED-THREE-ROWS] curated id constants must total 7 rows (claude 3 + cortex 4) — got ${expectedIds.length}: ${expectedIds.join(", ")}`,
149
152
  );
150
153
  assert.deepEqual(
151
154
  capIds.slice().sort(),
152
155
  expectedIds,
153
- `[QK:CORTEX-PROVIDER-SIX-ROW-SURFACE] compiled entry must register the EXACT curated set of both adapters (claude 2 + cortex 4) — got: ${capIds.join(", ") || "none"}`,
156
+ `[QK:CORTEX-PROVIDER-EXACT-ROW-SURFACE] compiled entry must register the EXACT curated set of both adapters (claude 3 + cortex 4) — got: ${capIds.join(", ") || "none"}`,
154
157
  );
155
158
  assert.equal(typeof cap.cfg.streamSimple, "function", "entry streamSimple must be a function");
156
159
  // Regression guard: the entry must wire the REAL backend (streamShellAcp),
@@ -52,6 +52,7 @@ import { resolveFactList } from "../pi-extensions/lib/entwurf-facts.ts";
52
52
  import { nativePushSupported } from "../pi-extensions/lib/entwurf-v2-contract.ts";
53
53
  import { META_SENDER_BACKENDS, resolveTrustedMetaSenderIdentity } from "../pi-extensions/lib/meta-sender-identity.ts";
54
54
  import { listAllMetaIdentitiesDir, processStartKey } from "../pi-extensions/lib/meta-session.ts";
55
+ import { reclaimOnExit } from "./lib/reclaim-on-exit.ts";
55
56
 
56
57
  const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
57
58
  const PLUGIN = "entwurf-meta-receive-copilot";
@@ -63,7 +64,8 @@ function ok(label: string, cond: boolean): void {
63
64
  passed++;
64
65
  }
65
66
 
66
- const root = mkdtempSync(path.join(tmpdir(), "entwurf-copilot-birth."));
67
+ const root = reclaimOnExit(mkdtempSync(path.join(tmpdir(), "entwurf-copilot-birth.")));
68
+
67
69
  const asm = path.join(root, "asm");
68
70
 
69
71
  // ── 1. the real assembler ────────────────────────────────────────────────────
@@ -60,6 +60,7 @@ import {
60
60
  upsertMetaSession,
61
61
  writeMetaSenderMarker,
62
62
  } from "../pi-extensions/lib/meta-session.ts";
63
+ import { killOnExit, reclaimNow, reclaimOnExit } from "./lib/reclaim-on-exit.ts";
63
64
 
64
65
  const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
65
66
  const UNIT = "entwurf-receive";
@@ -90,7 +91,7 @@ async function waitUntil(fn: () => boolean, ms = 15000): Promise<boolean> {
90
91
  }
91
92
  }
92
93
 
93
- const root = mkdtempSync(path.join(tmpdir(), "entwurf-copilot-receive."));
94
+ const root = reclaimOnExit(mkdtempSync(path.join(tmpdir(), "entwurf-copilot-receive.")));
94
95
  const home = path.join(root, "home");
95
96
  const store = path.join(root, "agent");
96
97
  const extRoot = path.join(root, "extensions");
@@ -260,6 +261,30 @@ writeFileSync(
260
261
  path.join(stubDir, "register.mjs"),
261
262
  `import { register } from "node:module";
262
263
  register("./loader.mjs", import.meta.url);
264
+ // The vendor half this fixture MUST model. Production children are started by the CLI's
265
+ // own bootstrap (\`preloads/extension_bootstrap.mjs\`), which exits the extension when its
266
+ // parent disappears — checked at startup and then once a second — and the receiver's
267
+ // owner-pid contract is built on that. A stub that omitted it made the fixture MORE
268
+ // permissive than production: the stub's referenced timer kept every child alive after
269
+ // the gate died, so each red run reparented its children to pid 1 forever (measured
270
+ // 2026-09-07/08 on \`oracle\`: 360 such node processes, 720 temp roots). The check is
271
+ // \`unref\`ed so it never becomes the reason a child stays up.
272
+ //
273
+ // KNOWN-OPEN, deliberately: under a subreaper (\`PR_SET_CHILD_SUBREAPER\`, systemd-run)
274
+ // a parent that dies before this module's FIRST tick is already reparented to a LIVE
275
+ // subreaper, so \`parentAtStart\` records that pid and never changes again. The obvious
276
+ // patch — probing the recorded pid with signal 0 — closes nothing: Linux reparents in
277
+ // \`forget_original_parent()\` BEFORE the old pid becomes unreachable, and a zombie
278
+ // parent still answers signal 0, so the probe is false at startup in exactly the case
279
+ // it was written for. The honest close is to watch the DECLARED parent
280
+ // (\`COPILOT_EXTENSION_PARENT_PID\`), which this gate deliberately sets to a pid that is
281
+ // not the child's real parent, so it is left documented rather than half-fixed. The
282
+ // window is microseconds and no measured orphan on this host came through it.
283
+ const parentAtStart = process.ppid;
284
+ if (parentAtStart === 1) process.exit(0);
285
+ setInterval(() => {
286
+ if (process.ppid !== parentAtStart) process.exit(0);
287
+ }, 1000).unref();
263
288
  `,
264
289
  );
265
290
 
@@ -274,23 +299,25 @@ interface Harness {
274
299
  function launch(label: string, sessionId: string, extra: NodeJS.ProcessEnv = {}): Harness {
275
300
  const box = path.join(root, `box-${label}`);
276
301
  mkdirSync(box, { recursive: true });
277
- const child = spawn(process.execPath, ["--import", path.join(stubDir, "register.mjs"), entry], {
278
- env: {
279
- ...process.env,
280
- HOME: home,
281
- PI_CODING_AGENT_DIR: store,
282
- STUB_BOX: box,
283
- STUB_SDK: path.join(stubDir, "extension.js"),
284
- STUB_SESSION_ID: sessionId,
285
- SESSION_ID: sessionId,
286
- // The vendor's carrier. This gate process IS the fork parent, which is also
287
- // what makes the sender-marker join real rather than mocked: the marker below
288
- // is written under the very pid the child will look itself up by.
289
- COPILOT_EXTENSION_PARENT_PID: String(process.pid),
290
- ...extra,
291
- },
292
- stdio: ["ignore", "pipe", "pipe"],
293
- });
302
+ const child = killOnExit(
303
+ spawn(process.execPath, ["--import", path.join(stubDir, "register.mjs"), entry], {
304
+ env: {
305
+ ...process.env,
306
+ HOME: home,
307
+ PI_CODING_AGENT_DIR: store,
308
+ STUB_BOX: box,
309
+ STUB_SDK: path.join(stubDir, "extension.js"),
310
+ STUB_SESSION_ID: sessionId,
311
+ SESSION_ID: sessionId,
312
+ // The vendor's carrier. This gate process IS the fork parent, which is also
313
+ // what makes the sender-marker join real rather than mocked: the marker below
314
+ // is written under the very pid the child will look itself up by.
315
+ COPILOT_EXTENSION_PARENT_PID: String(process.pid),
316
+ ...extra,
317
+ },
318
+ stdio: ["ignore", "pipe", "pipe"],
319
+ }),
320
+ );
294
321
  child.stdout?.resume();
295
322
  child.stderr?.resume();
296
323
  return {
@@ -699,6 +726,68 @@ ok(
699
726
  opaque.kill("SIGKILL");
700
727
  }
701
728
  }
729
+ {
730
+ // AN UNREADABLE argv IS "UNKNOWN", NOT "ABSENT" (#78 B2). Identity is the FIRST
731
+ // predicate of the scan, so a candidate whose `cmdline` will not open cannot be
732
+ // classified at all — and a blanket `except OSError: return None` classified it
733
+ // anyway, as "not a Copilot CLI". Every candidate taking that branch left all
734
+ // counters at zero, which is the benign "no live GitHub Copilot CLI process" note:
735
+ // a doctor claiming ABSENCE while its identity input was unavailable. That is the
736
+ // same false-success class as the platform branch above, so it gets the same verdict.
737
+ //
738
+ // THE DENIAL IS INJECTED, AND THAT IS SAID OUT LOUD BECAUSE IT MATTERS. The environ
739
+ // cell above makes its file really unreadable with prctl(PR_SET_DUMPABLE, 0), and
740
+ // measured on this kernel that trick does NOT reach argv: `/proc/<pid>/cmdline` stays
741
+ // mode 0444 and readable even for a non-dumpable child (a full /proc census read
742
+ // 340/340 cmdlines). A real EACCES there needs a `hidepid` /proc mount, which an
743
+ // unprivileged gate cannot create. So the refusal is raised exactly where the kernel
744
+ // would raise it — at the scanner's own read, with EACCES — through a `sitecustomize`
745
+ // on PYTHONPATH, and what this cell pins is the CLASSIFICATION of a refused read,
746
+ // which is the entire contract. The fixture process is real, and the control run
747
+ // asserts it WOULD be identified as a native CLI when its argv can be read, so
748
+ // neither half of the pin is vacuous.
749
+ const denyDir = path.join(root, "deny-cmdline");
750
+ mkdirSync(denyDir, { recursive: true });
751
+ writeFileSync(
752
+ path.join(denyDir, "sitecustomize.py"),
753
+ [
754
+ "import errno, os, pathlib",
755
+ "_pid = os.environ.get('ENTWURF_TEST_DENY_CMDLINE_PID')",
756
+ "if _pid:",
757
+ " _target = '/proc/' + _pid + '/cmdline'",
758
+ " _real = pathlib.Path.read_bytes",
759
+ " def _denied(self):",
760
+ " if str(self) == _target:",
761
+ " raise PermissionError(errno.EACCES, 'Permission denied', str(self))",
762
+ " return _real(self)",
763
+ " pathlib.Path.read_bytes = _denied",
764
+ ].join("\n") + "\n",
765
+ );
766
+ const opaqueArgv = spawn(
767
+ "python3",
768
+ ["-c", "import time; time.sleep(60)", path.join(root, "node_modules", "@github", "copilot", "npm-loader.js")],
769
+ { stdio: "ignore" },
770
+ );
771
+ running.push(opaqueArgv);
772
+ try {
773
+ await sleep(400);
774
+ const identified = doctor({ ENTWURF_COPILOT_RECEIVE_PIDS: String(opaqueArgv.pid) });
775
+ const blind = doctor({
776
+ ENTWURF_COPILOT_RECEIVE_PIDS: String(opaqueArgv.pid),
777
+ PYTHONPATH: denyDir,
778
+ ENTWURF_TEST_DENY_CMDLINE_PID: String(opaqueArgv.pid),
779
+ });
780
+ ok(
781
+ "[QK:COPILOT-RECEIVE-DOCTOR-UNREADABLE-ARGV-IS-UNVERIFIABLE] a live process whose argv is REFUSED is UNKNOWN, not absent — the doctor declines to claim that no CLI is running and ends RED, where the same process with a readable argv is judged",
782
+ identified.includes("lack COPILOT_CLI_ENABLED_FEATURE_FLAGS") &&
783
+ blind.includes("UNVERIFIABLE — the argv of 1 live process(es) could not be read") &&
784
+ !blind.includes("no live GitHub Copilot CLI process") &&
785
+ blind.trimEnd().endsWith("[copilot-receive-doctor] FAIL"),
786
+ );
787
+ } finally {
788
+ opaqueArgv.kill("SIGKILL");
789
+ }
790
+ }
702
791
  {
703
792
  // THE SCANNER'S OWN FAILURE IS THIS DOCTOR'S VERDICT, NOT ITS DEATH. Under
704
793
  // `set -euo pipefail`, `x="$(python3 …)"` hands the interpreter's status to the
@@ -725,10 +814,69 @@ ok(
725
814
  );
726
815
  }
727
816
 
728
- // ── 10. the installer's honest inverse ───────────────────────────────────────
817
+ // ── 10. the fixture dies with its parent, like the vendor's child does ───────
818
+ // This gate spawns real Node children that hold a referenced timer, so "the gate
819
+ // stopped" and "the gate's children stopped" are two different facts. Production gets
820
+ // the second one from the CLI's bootstrap; the stub loader has to supply it here, or a
821
+ // red run (which ends by throwing, long before any cleanup statement) leaves a process
822
+ // per launch on the host forever. Orphan one deliberately and require it to leave on
823
+ // its own. The intermediate `bash` holds the child for two seconds first: a child whose
824
+ // parent is ALREADY gone when its loader runs leaves at the watchdog's startup check,
825
+ // which proves nothing about a session that was live when the gate died.
826
+ {
827
+ const orphanBox = path.join(root, "box-orphan");
828
+ mkdirSync(orphanBox, { recursive: true });
829
+ const pidFile = path.join(root, "orphan.pid");
830
+ const ORPHAN_SESSION = "cop-recv-orphan";
831
+ spawnSync(
832
+ "bash",
833
+ [
834
+ "-c",
835
+ `${JSON.stringify(process.execPath)} --import ${JSON.stringify(path.join(stubDir, "register.mjs"))} ` +
836
+ `${JSON.stringify(entry)} </dev/null >/dev/null 2>&1 & echo $! > ${JSON.stringify(pidFile)}; sleep 2`,
837
+ ],
838
+ {
839
+ env: {
840
+ ...process.env,
841
+ HOME: home,
842
+ PI_CODING_AGENT_DIR: store,
843
+ STUB_BOX: orphanBox,
844
+ STUB_SDK: path.join(stubDir, "extension.js"),
845
+ STUB_SESSION_ID: ORPHAN_SESSION,
846
+ SESSION_ID: ORPHAN_SESSION,
847
+ COPILOT_EXTENSION_PARENT_PID: String(process.pid),
848
+ },
849
+ stdio: "ignore",
850
+ },
851
+ );
852
+ const orphanPid = Number(readFileSync(pidFile, "utf8").trim());
853
+ // Non-vacuity: it is only evidence if the process it names actually reached the
854
+ // same live state every other child in this gate reaches. Its log line is written
855
+ // during boot and outlives it, so reading it after the exit is not a race.
856
+ const booted = await waitUntil(() => {
857
+ const f = path.join(store, "meta-bridge-receive-copilot.log");
858
+ return existsSync(f) && readFileSync(f, "utf8").includes(`joined session=${ORPHAN_SESSION}`);
859
+ });
860
+ ok("the orphaned child booted the real unit (so its exit below is not a startup crash)", booted);
861
+ const gone = await waitUntil(() => {
862
+ try {
863
+ process.kill(orphanPid, 0);
864
+ return false;
865
+ } catch {
866
+ return true; // ESRCH — the pid is gone
867
+ }
868
+ });
869
+ ok(
870
+ "[QK:COPILOT-RECEIVE-STUB-CHILD-DIES-WITH-PARENT] a fixture child whose parent disappeared exits by " +
871
+ "itself — no pid-1 receiver survives a red or interrupted run",
872
+ gone,
873
+ );
874
+ if (!gone) process.kill(orphanPid, "SIGKILL");
875
+ }
876
+
877
+ // ── 11. the installer's honest inverse ───────────────────────────────────────
729
878
  execFileSync("bash", [path.join(REPO, "run.sh"), "uninstall-copilot-receive"], { env: installEnv, stdio: "pipe" });
730
879
  ok("uninstall removes the unit it installed and clears its state", !existsSync(unitDir) && !existsSync(stateFile));
731
880
 
732
- for (const c of running) c.kill("SIGKILL");
733
- rmSync(root, { recursive: true, force: true });
881
+ reclaimNow();
734
882
  console.log(`\n[check-copilot-receive-arm] PASS (${passed} assertions)`);
@@ -13,6 +13,7 @@ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
13
13
  import { tmpdir } from "node:os";
14
14
  import * as path from "node:path";
15
15
  import { fileURLToPath } from "node:url";
16
+ import { reclaimOnExit } from "./lib/reclaim-on-exit.ts";
16
17
 
17
18
  const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
18
19
  const SCRIPT = path.join(REPO, "scripts", "copilot-statusline.sh");
@@ -27,7 +28,8 @@ function ok(label: string, cond: boolean): void {
27
28
  passed++;
28
29
  }
29
30
 
30
- const root = mkdtempSync(path.join(tmpdir(), "entwurf-copilot-statusline."));
31
+ const root = reclaimOnExit(mkdtempSync(path.join(tmpdir(), "entwurf-copilot-statusline.")));
32
+
31
33
  const home = path.join(root, "home");
32
34
  const store = path.join(root, "meta-sessions");
33
35
  mkdirSync(home);
@@ -66,6 +66,7 @@ import {
66
66
  validateManifest,
67
67
  validateManifestSet,
68
68
  } from "./lib/mutation-qualify.ts";
69
+ import { reclaimOnExit } from "./lib/reclaim-on-exit.ts";
69
70
 
70
71
  const REPO_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
71
72
  const MUTANTS_DIR = path.join(REPO_DIR, "scripts", "mutants");
@@ -135,7 +136,7 @@ function checkVitestAttribution(): void {
135
136
  ],
136
137
  });
137
138
 
138
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-attribution-selftest-"));
139
+ const dir = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-attribution-selftest-")));
139
140
  const reportPath = path.join(dir, "vitest-report.json");
140
141
  try {
141
142
  fs.writeFileSync(reportPath, report);
@@ -422,10 +423,10 @@ if (process.argv.includes("--attribution-self-test")) {
422
423
  // ═══ Phase 1c — the pipeline over a synthetic fixture repo ══════════════════
423
424
 
424
425
  function buildFixtureOrigin(): { dir: string; externalTarget: string } {
425
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-qualify-fixture-"));
426
+ const dir = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-qualify-fixture-")));
426
427
  // A file OUTSIDE the fixture repo, reachable only through a tracked symlink — the
427
428
  // P0-1 escape shape: mutating `linked.txt` would write THIS file.
428
- const externalDir = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-qualify-external-"));
429
+ const externalDir = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-qualify-external-")));
429
430
  const externalTarget = path.join(externalDir, "external-target.txt");
430
431
  fs.writeFileSync(externalTarget, "original-line outside the sandbox\n");
431
432
  const write = (rel: string, body: string, mode = 0o644): void => {
@@ -745,7 +746,7 @@ const quiet = (): void => {};
745
746
 
746
747
  // pgroup kill: the grandchild really dies on this host (hardening #6, Linux axis).
747
748
  {
748
- const invocationDir = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-qualify-pgk-"));
749
+ const invocationDir = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-qualify-pgk-")));
749
750
  const pidFile = path.join(invocationDir, "grandchild.pid");
750
751
  const script = path.join(invocationDir, "spawner.sh");
751
752
  fs.writeFileSync(script, `#!/usr/bin/env bash\nsleep 300 & echo $! > ${JSON.stringify(pidFile)}\nwait\n`);
@@ -773,7 +774,7 @@ const quiet = (): void => {};
773
774
 
774
775
  // Stale-snapshot sweep: dead-pid residue is reclaimed, a live runner's dir survives.
775
776
  {
776
- const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-qualify-sweep-"));
777
+ const tmpRoot = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-qualify-sweep-")));
777
778
  const deadDir = path.join(tmpRoot, "entwurf-qualify-dead1");
778
779
  fs.mkdirSync(deadDir);
779
780
  fs.writeFileSync(path.join(deadDir, "runner.json"), JSON.stringify({ pid: 999_999_999, startedAt: 0 }));
@@ -820,7 +821,7 @@ let manifestCount: number;
820
821
  // Extend this inventory and the manifests TOGETHER, never silently.
821
822
  const EXPECTED_LANE_MUTANTS: Record<string, number> = {
822
823
  "acp-augment": 10,
823
- "acp-cortex": 12,
824
+ "acp-cortex": 14,
824
825
  "acp-launch-namespace": 2,
825
826
  "acp-overlay": 1,
826
827
  "acp-prompt-lifecycle": 15,
@@ -833,7 +834,7 @@ let manifestCount: number;
833
834
  "capability-cache": 3,
834
835
  "copilot-birth": 19,
835
836
  "copilot-launch": 14,
836
- "copilot-receive": 18,
837
+ "copilot-receive": 20,
837
838
  "fresh-cut": 3,
838
839
  "gate-qualification": 2,
839
840
  "meta-facts": 4,
@@ -847,7 +848,7 @@ let manifestCount: number;
847
848
  "pack-install": 2,
848
849
  "pi-package-ownership": 6,
849
850
  "mux-resume-call": 12,
850
- "omp-birth": 12,
851
+ "omp-birth": 13,
851
852
  "omp-fresh": 24,
852
853
  "omp-receive": 11,
853
854
  "probe-ordering": 1,
@@ -855,7 +856,7 @@ let manifestCount: number;
855
856
  "resume-args": 6,
856
857
  "resume-launch-identity": 6,
857
858
  "self-address": 5,
858
- "setup-verdict": 13,
859
+ "setup-verdict": 14,
859
860
  "source-install": 2,
860
861
  "v2-surface": 7,
861
862
  "v2-visible-resume": 17,
@@ -45,6 +45,7 @@ import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSyn
45
45
  import { tmpdir } from "node:os";
46
46
  import * as path from "node:path";
47
47
  import { fileURLToPath } from "node:url";
48
+ import { reclaimOnExit } from "./lib/reclaim-on-exit.ts";
48
49
 
49
50
  let passed = 0;
50
51
  function ok(label: string, cond: boolean): void {
@@ -272,7 +273,7 @@ function hookEnv(agentRoot: string, pluginRoot: string): NodeJS.ProcessEnv {
272
273
  }
273
274
 
274
275
  function driveHook(label: string, pluginRoot: string, nativeSessionId: string): void {
275
- const agentRoot = mkdtempSync(path.join(tmpdir(), "psa-hook-topology-"));
276
+ const agentRoot = reclaimOnExit(mkdtempSync(path.join(tmpdir(), "psa-hook-topology-")));
276
277
  const leaf = leafOf("SessionStart");
277
278
  const exe = resolveEl(leaf.command ?? "", pluginRoot);
278
279
  const argv = (leaf.args ?? []).map((a) => resolveEl(a, pluginRoot));
@@ -317,7 +318,7 @@ function driveHook(label: string, pluginRoot: string, nativeSessionId: string):
317
318
  }
318
319
 
319
320
  {
320
- const plain = mkdtempSync(path.join(tmpdir(), "psa-hook-bundle-"));
321
+ const plain = reclaimOnExit(mkdtempSync(path.join(tmpdir(), "psa-hook-bundle-")));
321
322
  makeBundle(plain);
322
323
  driveHook("exec direct topology", plain, "native-exec-direct");
323
324
  }
@@ -329,7 +330,7 @@ function driveHook(label: string, pluginRoot: string, nativeSessionId: string):
329
330
  // path was pasted into a command string and would have been word-split or
330
331
  // command-substituted; under exec form each element is one opaque argv slot.
331
332
  {
332
- const holder = mkdtempSync(path.join(tmpdir(), "psa-hook-meta-"));
333
+ const holder = reclaimOnExit(mkdtempSync(path.join(tmpdir(), "psa-hook-meta-")));
333
334
  const nasty = path.join(holder, "plug in $HOME `id` ;& dir");
334
335
  makeBundle(nasty);
335
336
  ok("metachar fixture root really contains space/$/backtick/;&", /[ $`;&]/.test(path.basename(nasty)));
@@ -345,9 +346,9 @@ function driveHook(label: string, pluginRoot: string, nativeSessionId: string):
345
346
  // it. The launcher's provenance token is what makes this fail CLOSED — the record still
346
347
  // lands (best-effort, as always), but no presence is claimed that cannot be backed.
347
348
  {
348
- const pluginRoot = mkdtempSync(path.join(tmpdir(), "psa-hook-oldcmd-"));
349
+ const pluginRoot = reclaimOnExit(mkdtempSync(path.join(tmpdir(), "psa-hook-oldcmd-")));
349
350
  makeBundle(pluginRoot);
350
- const agentRoot = mkdtempSync(path.join(tmpdir(), "psa-hook-oldcmd-agent-"));
351
+ const agentRoot = reclaimOnExit(mkdtempSync(path.join(tmpdir(), "psa-hook-oldcmd-agent-")));
351
352
  const result = spawnSync(process.execPath, [path.join(pluginRoot, "meta-bridge-hook.ts")], {
352
353
  encoding: "utf8",
353
354
  input: JSON.stringify({
@@ -510,12 +510,13 @@ const operatorCmds = [...targets].filter(([cmd, ts]) => !isDevGate(cmd) && ts.le
510
510
  `${skillRel}: missing name, four authority modes, prerelease contract, or English/ASCII-only surface`,
511
511
  );
512
512
  ok(
513
- "S7g: the executable ASCII CI oracle binds headSha and all three required jobs",
513
+ "S7g: the executable ASCII CI oracle binds headSha and all four required jobs",
514
514
  ciOracle === null ||
515
515
  (ciOracle.includes("headSha") &&
516
516
  ciOracle.includes('"check"') &&
517
517
  ciOracle.includes('"install-surface"') &&
518
518
  ciOracle.includes('"artifact-consumer"') &&
519
+ ciOracle.includes('"macos-install-surface"') &&
519
520
  ciOracleMode === "100755" &&
520
521
  oracleIsAscii),
521
522
  `${ciOracleRel}: missing executable mode, exact-SHA classification, required jobs, or ASCII-only content`,
@@ -33,6 +33,7 @@ import {
33
33
  type MetaIdentity,
34
34
  serializeMetaIdentity,
35
35
  } from "../pi-extensions/lib/meta-session.ts";
36
+ import { reclaimOnExit } from "./lib/reclaim-on-exit.ts";
36
37
 
37
38
  let passed = 0;
38
39
  function ok(label: string, cond: boolean): void {
@@ -62,7 +63,7 @@ function record(gardenId: string, over: Partial<MetaIdentity> = {}): string {
62
63
  }
63
64
 
64
65
  function store(files: Record<string, string | Buffer>): string {
65
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-meta-facts-"));
66
+ const dir = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-meta-facts-")));
66
67
  for (const [name, body] of Object.entries(files)) fs.writeFileSync(path.join(dir, name), body);
67
68
  return dir;
68
69
  }
@@ -148,7 +149,7 @@ function parseProjection(run: CliRun, label: string): Projection {
148
149
 
149
150
  // ── drift / symlink / invalid UTF-8 are in-band defects, exit 0 ─────────────
150
151
  {
151
- const outside = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-meta-facts-outside-"));
152
+ const outside = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-meta-facts-outside-")));
152
153
  fs.writeFileSync(path.join(outside, "foreign.meta.json"), record(GID_C));
153
154
  const dir = store({
154
155
  [`${GID_A}.meta.json`]: record(GID_A),
@@ -206,7 +207,8 @@ function parseProjection(run: CliRun, label: string): Projection {
206
207
 
207
208
  // ── missing store = readable EMPTY store ────────────────────────────────────
208
209
  {
209
- const gone = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-meta-facts-")), "never-created");
210
+ const goneRoot = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-meta-facts-")));
211
+ const gone = path.join(goneRoot, "never-created");
210
212
  const facts = parseProjection(cli([gone]), "missing store");
211
213
  ok(
212
214
  "a store that does not exist is a readable empty store (ENOENT only)",
@@ -216,7 +218,7 @@ function parseProjection(run: CliRun, label: string): Projection {
216
218
 
217
219
  // ── unreadable store = exit 3, NO JSON ──────────────────────────────────────
218
220
  {
219
- const base = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-meta-facts-"));
221
+ const base = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-meta-facts-")));
220
222
  const file = path.join(base, "not-a-dir");
221
223
  fs.writeFileSync(file, "plain file");
222
224
  const run = cli([path.join(file, "store")]); // ENOTDIR: the path runs THROUGH a regular file
@@ -70,6 +70,7 @@ import {
70
70
  upsertMetaSession,
71
71
  writeMetaReceiverMarker,
72
72
  } from "../pi-extensions/lib/meta-session.ts";
73
+ import { reclaimOnExit } from "./lib/reclaim-on-exit.ts";
73
74
 
74
75
  let passed = 0;
75
76
  function ok(label: string, cond: boolean): void {
@@ -112,9 +113,9 @@ function resolveEl(value: string, pluginRoot: string): string {
112
113
  }
113
114
 
114
115
  // ── one sandbox for the whole switch story (the pid is what ties it together) ──
115
- const AGENT_ROOT = mkdtempSync(path.join(tmpdir(), "psa-hook-switch-agent-"));
116
- const PLUGIN_ROOT = mkdtempSync(path.join(tmpdir(), "psa-hook-switch-plugin-"));
117
- const CWD = mkdtempSync(path.join(tmpdir(), "psa-hook-switch-cwd-"));
116
+ const AGENT_ROOT = reclaimOnExit(mkdtempSync(path.join(tmpdir(), "psa-hook-switch-agent-")));
117
+ const PLUGIN_ROOT = reclaimOnExit(mkdtempSync(path.join(tmpdir(), "psa-hook-switch-plugin-")));
118
+ const CWD = reclaimOnExit(mkdtempSync(path.join(tmpdir(), "psa-hook-switch-cwd-")));
118
119
  makeBundle(PLUGIN_ROOT);
119
120
 
120
121
  const ROOTS = {
@@ -52,6 +52,7 @@ import {
52
52
  serializeMetaIdentity,
53
53
  } from "../pi-extensions/lib/meta-session.ts";
54
54
  import { birthPiCitizen } from "../pi-extensions/lib/pi-citizen-birth.ts";
55
+ import { reclaimOnExit } from "./lib/reclaim-on-exit.ts";
55
56
 
56
57
  let passed = 0;
57
58
  function ok(label: string, cond: boolean): void {
@@ -103,7 +104,7 @@ const v2Body = `${JSON.stringify(
103
104
 
104
105
  /** Run `body` against a store dir seeded by `seed`, then remove it. */
105
106
  function withStore(seed: (dir: string) => void, body: (dir: string) => void): void {
106
- const dir = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-case-"));
107
+ const dir = reclaimOnExit(fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-case-")));
107
108
  try {
108
109
  seed(dir);
109
110
  body(dir);
@@ -129,7 +130,7 @@ const record = (dir: string, gardenId: string, nativeSessionId: string): void =>
129
130
  write(dir, `${gardenId}.meta.json`, serializeMetaIdentity(identity(gardenId, nativeSessionId)));
130
131
  const recordCount = (dir: string): number => fs.readdirSync(dir).filter((f) => f.endsWith(".meta.json")).length;
131
132
 
132
- const dir = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-"));
133
+ const dir = reclaimOnExit(fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-")));
133
134
  try {
134
135
  record(dir, GID_A, "native-a");
135
136
  record(dir, GID_B, "native-b");
@@ -355,7 +356,7 @@ withStore(
355
356
  // entries with the right kind. A pure-only proof would have passed while the fs path
356
357
  // still did a bare-name readdir and followed symlinks — which is exactly what shipped.
357
358
  {
358
- const tmp = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-addr-"));
359
+ const tmp = reclaimOnExit(fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-addr-")));
359
360
  try {
360
361
  // A symlink whose target lives OUTSIDE the store and holds the same native id.
361
362
  const dir = path.join(tmp, "symlink-store");
@@ -444,7 +445,7 @@ withStore(
444
445
  // into foreign bytes while every "is it a symlink" test on a settled store stayed green.
445
446
  // A plain already-a-symlink read cannot fail that way, so it cannot prove this.
446
447
  {
447
- const tmp = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-toctou-"));
448
+ const tmp = reclaimOnExit(fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-toctou-")));
448
449
  try {
449
450
  const dir = path.join(tmp, "store");
450
451
  const outside = path.join(tmp, "outside");
@@ -700,7 +701,7 @@ withStore(
700
701
  // where they live and a probe of this directory cannot certify them.
701
702
  withStore(
702
703
  (dir) => {
703
- const outside = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-outside-"));
704
+ const outside = reclaimOnExit(fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-outside-")));
704
705
  fs.writeFileSync(path.join(outside, "real.json"), serializeMetaIdentity(identity(GID_C, "native-symlinked")));
705
706
  fs.symlinkSync(path.join(outside, "real.json"), path.join(dir, `${GID_C}.meta.json`));
706
707
  },
@@ -889,7 +890,7 @@ withStore(
889
890
  // clean "no such citizen" — the fail-open shape (2026-07-25 fresh-eyes review). ENOENT
890
891
  // alone is absence; a broken store SHAPE (ENOTDIR here) is a failure to inspect.
891
892
  {
892
- const tmp = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-shape-"));
893
+ const tmp = reclaimOnExit(fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-shape-")));
893
894
  try {
894
895
  const brokenStore = path.join(tmp, "store-is-actually-a-file");
895
896
  fs.writeFileSync(brokenStore, "not a store\n");
@@ -927,7 +928,7 @@ withStore(
927
928
  // — the doctor and the install preflight would call an unreadable host clean. ENOENT
928
929
  // alone is an absent store (a host that never had a generation is not broken).
929
930
  {
930
- const tmp = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-unreadable-"));
931
+ const tmp = reclaimOnExit(fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "psa-idcons-unreadable-")));
931
932
  try {
932
933
  ok(
933
934
  "an absent store still certifies as empty (ENOENT alone)",
@@ -41,6 +41,7 @@ import {
41
41
  startKeyScheme,
42
42
  writeMetaReceiverMarker,
43
43
  } from "../pi-extensions/lib/meta-session.ts";
44
+ import { reclaimOnExit } from "./lib/reclaim-on-exit.ts";
44
45
 
45
46
  let passed = 0;
46
47
  function ok(label: string, cond: boolean): void {
@@ -50,7 +51,7 @@ function ok(label: string, cond: boolean): void {
50
51
  }
51
52
 
52
53
  const REPO_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
53
- const DIR = mkdtempSync(path.join(tmpdir(), "psa-meta-receivers-"));
54
+ const DIR = reclaimOnExit(mkdtempSync(path.join(tmpdir(), "psa-meta-receivers-")));
54
55
  const GARDEN = "20260614T120000-aaaaaa";
55
56
 
56
57
  // ── write → read round-trip, garden-id keyed ────────────────────────────────