@junghanacs/entwurf 0.31.0 → 0.32.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 (56) hide show
  1. package/AGENTS.md +3 -2
  2. package/CHANGELOG.md +82 -0
  3. package/README.md +46 -36
  4. package/VERIFY.md +35 -16
  5. package/demo/README.md +1 -1
  6. package/docs/acp-backend-rail.md +1 -1
  7. package/docs/setup-clean-host.md +112 -95
  8. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +1 -1
  9. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +1 -1
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +8 -10
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +3 -3
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +153 -205
  13. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +6 -4
  14. package/mcp/entwurf-bridge/src/index.ts +1 -1
  15. package/package.json +14 -17
  16. package/pi/pi-durable/bootstrap.mjs +142 -19
  17. package/pi/pi-durable/carrier/LICENSE +21 -0
  18. package/pi/pi-durable/carrier/harness-setup.js +123 -0
  19. package/pi/pi-durable/carrier/prompt.js +74 -0
  20. package/pi/pi-durable/carrier/runtime.js +402 -0
  21. package/pi/pi-durable/carrier/sessions.js +56 -0
  22. package/pi/pi-durable/carrier/subagent.js +55 -0
  23. package/pi/pi-durable/carrier/tui.js +655 -0
  24. package/pi/pi-durable/carrier-relocation.mjs +160 -0
  25. package/pi/pi-durable/carrier-resolver.mjs +38 -0
  26. package/pi/pi-durable/overlay/upstream-pin.json +103 -11
  27. package/pi-extensions/lib/fresh-call-composition.ts +1 -1
  28. package/pi-extensions/lib/mux-fresh-call.ts +8 -10
  29. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +3 -3
  30. package/pi-extensions/lib/pi-durable-runtime.ts +197 -263
  31. package/pi-extensions/meta-bridge-pi-durable.ts +11 -7
  32. package/run.sh +508 -235
  33. package/scripts/check-entwurf-bridge-pi-free.ts +7 -5
  34. package/scripts/check-fresh-call-dispatch.ts +1 -1
  35. package/scripts/check-gate-qualification.ts +86 -34
  36. package/scripts/check-herdr-activation.ts +5 -4
  37. package/scripts/check-pi-durable-contact.ts +26 -93
  38. package/scripts/check-pi-durable-receive.ts +22 -59
  39. package/scripts/check-pi-durable-send.ts +235 -75
  40. package/scripts/emit-pi-durable-carrier.ts +355 -0
  41. package/scripts/lib/mutation-qualify.ts +21 -45
  42. package/scripts/lib/pi-durable-operator-guard.ts +89 -0
  43. package/scripts/lib/qualification-receipt.ts +7 -11
  44. package/scripts/mutants/control-send-receipt.json +1 -1
  45. package/scripts/mutants/gate-qualification.json +13 -0
  46. package/scripts/mutants/mux-fresh-call.json +13 -13
  47. package/scripts/mutants/pack-install.json +3 -3
  48. package/scripts/mutants/pi-durable.json +442 -109
  49. package/scripts/mutants/pi-floor.json +24 -2
  50. package/scripts/pi-durable-contact-host.mjs +3 -3
  51. package/scripts/pi-durable-receive-host.mjs +3 -3
  52. package/scripts/pi-durable-send-host.mjs +76 -5
  53. package/scripts/smoke-herdr-raw-install-live.sh +1 -1
  54. package/scripts/smoke-pi-durable-fresh-live.ts +3 -3
  55. package/scripts/smoke-setup-verdict.sh +15 -15
  56. package/scripts/smoke-user-scope-citizen.sh +1 -1
@@ -1,10 +1,12 @@
1
1
  // check-entwurf-bridge-pi-free (0.12.1 A-gate, static half)
2
2
  //
3
- // INVARIANT: the entwurf-bridge MCP server must boot WITHOUT any pi package
4
- // (`@earendil-works/pi-*`). entwurf is a harness-neutral npm package; pi is one
5
- // optional adapter lane, not a boot dependency. A plain `npm install
6
- // @junghanacs/entwurf` (no pi peers) must still stand `entwurf-bridge` up and
7
- // answer MCP `tools/list`.
3
+ // INVARIANT: the entwurf-bridge MCP server must boot WITHOUT importing any pi
4
+ // package (`@earendil-works/pi-*`). entwurf is a harness-neutral npm package; pi is
5
+ // one adapter lane, not a boot dependency. A plain `npm install @junghanacs/entwurf`
6
+ // must still stand `entwurf-bridge` up and answer MCP `tools/list` without touching
7
+ // pi. Since #130 that install carries the pi SDK as a production dependency (the
8
+ // pi-durable carrier's), so the runtime half proves this under a deny hook in
9
+ // check-pack-install rather than by the SDK's absence.
8
10
  //
9
11
  // This gate is the STATIC half of that contract: it walks the EAGER static
10
12
  // value-import closure of the bridge entry (`mcp/entwurf-bridge/src/index.ts`)
@@ -140,7 +140,7 @@ async function main(): Promise<void> {
140
140
 
141
141
  // #129: pi-durable joined the tmux fresh set, NOT the herdr pilot set (GLG, 2026-10-06: no herdr
142
142
  // pi-durable support in 0.31). Same axis as the codex cell: refused by the herdr reason, nothing
143
- // spawned, no tmux fallback, and its operator-runtime preflight is never asked on this branch.
143
+ // spawned, no tmux fallback, and its installed-carrier preflight is never asked on this branch.
144
144
  const durable = recordingSpawn();
145
145
  const durableRefused = await dispatchFreshCall(
146
146
  { ...REQUEST, backend: "pi-durable", model: "loopback/scripted" },
@@ -66,9 +66,9 @@ import {
66
66
  runGateBounded,
67
67
  signatureAttributedToFailure,
68
68
  signatureOnFailureLine,
69
- sweepStaleSnapshots,
70
69
  validateManifest,
71
70
  validateManifestSet,
71
+ withOwnSnapshot,
72
72
  } from "./lib/mutation-qualify.ts";
73
73
  import { ACCEPTANCE_SCHEMA, checkAcceptanceRecord, RECORD_CONSISTENT } from "./lib/qualification-acceptance.ts";
74
74
  import {
@@ -637,6 +637,32 @@ function fixtureSpec(patch: Partial<MutantSpec>): MutantSpec {
637
637
  };
638
638
  }
639
639
 
640
+ /**
641
+ * A PRIOR run's snapshot root inside a self-test's private `tmpRoot`, in the exact shape the
642
+ * retired ambient sweep deleted (snapshot prefix, a `runner.json` naming a dead pid). It is
643
+ * that run's evidence: no qualification run may list it, let alone remove it. `state()`
644
+ * names every entry with its bytes, so a removal or a rewrite both read as a change.
645
+ */
646
+ const PRIOR_RUN_ROOT = "entwurf-qualify-prior1";
647
+ function seedPriorRunRoot(tmpRoot: string): { before: string; state: () => string } {
648
+ const dir = path.join(tmpRoot, PRIOR_RUN_ROOT);
649
+ fs.mkdirSync(dir);
650
+ fs.writeFileSync(path.join(dir, "runner.json"), JSON.stringify({ pid: 999_999_999, startedAt: 0 }));
651
+ fs.writeFileSync(path.join(dir, "evidence.log"), "a prior run's red evidence\n");
652
+ const state = (): string =>
653
+ fs.existsSync(dir)
654
+ ? fs
655
+ .readdirSync(dir)
656
+ .sort()
657
+ .map((name) => `${name}\0${fs.readFileSync(path.join(dir, name), "utf8")}`)
658
+ .join("\n")
659
+ : "absent";
660
+ return { before: state(), state };
661
+ }
662
+
663
+ /** Each prior-root observation, asserted ONCE under its QK by Phase 1c-s (the token is exactly-once). */
664
+ const priorRunKept: Record<string, boolean> = {};
665
+
640
666
  const quiet = (): void => {};
641
667
 
642
668
  {
@@ -897,21 +923,6 @@ const quiet = (): void => {};
897
923
  ok(`pgroup: the GRANDCHILD (pid ${grandchild}) is dead after the group SIGKILL`, dead);
898
924
  fs.rmSync(invocationDir, { recursive: true, force: true });
899
925
  }
900
-
901
- // Stale-snapshot sweep: dead-pid residue is reclaimed, a live runner's dir survives.
902
- {
903
- const tmpRoot = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-qualify-sweep-")));
904
- const deadDir = path.join(tmpRoot, "entwurf-qualify-dead1");
905
- fs.mkdirSync(deadDir);
906
- fs.writeFileSync(path.join(deadDir, "runner.json"), JSON.stringify({ pid: 999_999_999, startedAt: 0 }));
907
- const liveDir = path.join(tmpRoot, "entwurf-qualify-live1");
908
- fs.mkdirSync(liveDir);
909
- fs.writeFileSync(path.join(liveDir, "runner.json"), JSON.stringify({ pid: process.pid, startedAt: Date.now() }));
910
- const swept = sweepStaleSnapshots(tmpRoot);
911
- ok("sweep: a dead-pid snapshot dir is reclaimed", swept.includes(deadDir) && !fs.existsSync(deadDir));
912
- ok("sweep: a live runner's snapshot dir survives", fs.existsSync(liveDir));
913
- fs.rmSync(tmpRoot, { recursive: true, force: true });
914
- }
915
926
  } finally {
916
927
  fs.rmSync(fixtureOrigin, { recursive: true, force: true });
917
928
  fs.rmSync(path.dirname(externalTarget), { recursive: true, force: true });
@@ -1262,8 +1273,11 @@ function buildDeclaredOutputFixture(): string {
1262
1273
  logError: quiet,
1263
1274
  tmpRoot,
1264
1275
  });
1265
- const snapshotsLeft = (): string[] => fs.readdirSync(tmpRoot).filter((f) => f.startsWith("entwurf-qualify-"));
1276
+ // This run's own snapshots only: the seeded prior root is not this run's to reclaim.
1277
+ const snapshotsLeft = (): string[] =>
1278
+ fs.readdirSync(tmpRoot).filter((f) => f.startsWith("entwurf-qualify-") && f !== PRIOR_RUN_ROOT);
1266
1279
  const readReceipt = (name: string) => JSON.parse(fs.readFileSync(out(name), "utf8"));
1280
+ const prior = seedPriorRunRoot(tmpRoot);
1267
1281
 
1268
1282
  const green = await runFx(["bash", "gates/kill.sh"], "green.json");
1269
1283
  const gr = readReceipt("green.json");
@@ -1279,6 +1293,7 @@ function buildDeclaredOutputFixture(): string {
1279
1293
  gr.receiptSha256 === green.receiptSha256,
1280
1294
  );
1281
1295
  ok("group run: the snapshot is reclaimed", snapshotsLeft().length === 0);
1296
+ priorRunKept["group green"] = prior.state() === prior.before;
1282
1297
 
1283
1298
  const red = await runFx(["bash", "gates/survive.sh"], "red.json");
1284
1299
  const rr = readReceipt("red.json");
@@ -1286,6 +1301,7 @@ function buildDeclaredOutputFixture(): string {
1286
1301
  "group run: a SURVIVED mutant is still written — red receipt, passed false",
1287
1302
  red.passed === false && rr.result.passed === false && rr.result.mutants[0].verdict === "SURVIVED",
1288
1303
  );
1304
+ priorRunKept["group red"] = prior.state() === prior.before && snapshotsLeft().length === 0;
1289
1305
 
1290
1306
  let refused = "";
1291
1307
  try {
@@ -1405,6 +1421,7 @@ const refuseArgs = (argv: string[], naming: string): void =>
1405
1421
  const receiptOf = (dir: string, file: string | null) =>
1406
1422
  JSON.parse(fs.readFileSync(path.join(tmpRoot, dir, file ?? ""), "utf8"));
1407
1423
 
1424
+ const prior = seedPriorRunRoot(tmpRoot);
1408
1425
  const abc = await collect("abc", [sA, sB, sC]);
1409
1426
  ok(
1410
1427
  "[QK:COLLECT-RED-DOES-NOT-STOP] collect: green / red / green — the red middle does not stop the tail; collection COMPLETE and intact",
@@ -1430,8 +1447,9 @@ const refuseArgs = (argv: string[], naming: string): void =>
1430
1447
  ) &&
1431
1448
  rB.result.control === "ok" &&
1432
1449
  rB.result.mutants[0].verdict === "SURVIVED" &&
1433
- fs.readdirSync(tmpRoot).every((f) => !f.startsWith("entwurf-qualify-")),
1450
+ fs.readdirSync(tmpRoot).every((f) => f === PRIOR_RUN_ROOT || !f.startsWith("entwurf-qualify-")),
1434
1451
  );
1452
+ priorRunKept["collect green/red/green"] = prior.state() === prior.before;
1435
1453
  ok(
1436
1454
  "collect: runId/runAttempt recorded exactly as the environment gave them; requested order kept",
1437
1455
  abc.runId === "123" &&
@@ -1586,6 +1604,47 @@ const refuseArgs = (argv: string[], naming: string): void =>
1586
1604
  }
1587
1605
  }
1588
1606
 
1607
+ // ═══ Phase 1c-s — every real run owns exactly its own snapshot (#130 r3) ═════
1608
+ //
1609
+ // The group (1c-g) and collection (1c-m) fixtures above seeded a prior run's root and
1610
+ // recorded whether it survived their green and red runs. That holds for real runs only while
1611
+ // every real run goes THROUGH the seam, so the production callers are read here too: the full
1612
+ // body and the group receipt each make their snapshot only via withOwnSnapshot, the collection
1613
+ // only via the group receipt, and the library exports no sweep and lists no tmp root. The
1614
+ // retired ambient sweep removed a protected prior snapshot during a release-gate run; the
1615
+ // fixtures cannot see a caller that bypasses the seam, this can. All four observations are
1616
+ // asserted once, under the claim's single token.
1617
+
1618
+ {
1619
+ const read = (rel: string): string => fs.readFileSync(path.join(REPO_DIR, rel), "utf8");
1620
+ const qualifier = read("scripts/check-gate-qualification.ts");
1621
+ // The LAST occurrence is the real header: this block's own needle sits above it.
1622
+ const bodyAt = qualifier.lastIndexOf("// ═══ Phase 2m — several exact-argv groups");
1623
+ const body = qualifier.slice(bodyAt);
1624
+ const receipt = read("scripts/lib/qualification-receipt.ts");
1625
+ const collection = read("scripts/lib/qualification-collection.ts");
1626
+ const lib = read("scripts/lib/mutation-qualify.ts");
1627
+ const libExports = Object.keys(await import("./lib/mutation-qualify.ts"));
1628
+ priorRunKept.wiring =
1629
+ bodyAt > qualifier.indexOf("// ═══ Phase 1c-s") &&
1630
+ countOccurrences(body, "withOwnSnapshot(REPO_DIR, undefined,") === 1 &&
1631
+ countOccurrences(body, "createRepoSnapshot(") === 0 &&
1632
+ countOccurrences(receipt, "withOwnSnapshot(repoDir, opts.tmpRoot,") === 1 &&
1633
+ countOccurrences(receipt, "createRepoSnapshot(") === 0 &&
1634
+ countOccurrences(collection, "runGroupReceipt(") >= 1 &&
1635
+ countOccurrences(collection, "createRepoSnapshot(") === 0 &&
1636
+ countOccurrences(collection, "withOwnSnapshot(") === 0 &&
1637
+ countOccurrences(lib, "createRepoSnapshot(") === 2 &&
1638
+ countOccurrences(lib, "const snap = createRepoSnapshot(originDir, tmpRoot);") === 1 &&
1639
+ countOccurrences(lib, "readdirSync(tmpRoot") === 0 &&
1640
+ !libExports.some((name) => /sweep/i.test(name));
1641
+ const failed = Object.keys(priorRunKept).filter((k) => !priorRunKept[k]);
1642
+ ok(
1643
+ `[QK:QUALIFY-PRIOR-RUN-EVIDENCE-KEPT] a prior run's dead-pid snapshot root keeps every name and byte through a green and a red group run and a green/red/green collection, each run's own snapshot is reclaimed, and the full body and group receipt snapshot only through withOwnSnapshot — failed: ${failed.join(", ") || "none"}`,
1644
+ Object.keys(priorRunKept).length === 4 && failed.length === 0,
1645
+ );
1646
+ }
1647
+
1589
1648
  // ═══ Phase 1c-l — the composite ledger over REAL fixture receipts (#124 P2) ═
1590
1649
  //
1591
1650
  // A late failure, end to end: receipts are written by runGroupReceipt on a fixture
@@ -3342,16 +3401,16 @@ let manifestCount: number;
3342
3401
  "copilot-birth": 19,
3343
3402
  "copilot-launch": 14,
3344
3403
  "pi-launch": 9,
3345
- "pi-floor": 1,
3404
+ "pi-floor": 3,
3346
3405
  "pi-bridge-sender": 4,
3347
3406
  "pi-mcp-bridge": 2,
3348
3407
  "pi-mcp-register": 3,
3349
- "pi-durable": 24,
3408
+ "pi-durable": 47,
3350
3409
  "copilot-receive": 20,
3351
3410
  "entwurf-peers": 1,
3352
3411
  "fresh-call-dispatch": 11,
3353
3412
  "fresh-cut": 3,
3354
- "gate-qualification": 7,
3413
+ "gate-qualification": 8,
3355
3414
  "herdr-placement": 13,
3356
3415
  "herdr-plugin": 25,
3357
3416
  "herdr-plugin-profile": 14,
@@ -3503,7 +3562,6 @@ if (ARGS.mode === "compose") {
3503
3562
  if (ARGS.mode === "groups") {
3504
3563
  const requested = ARGS.groups === "all" ? allGroupArgv(selected) : ARGS.groups;
3505
3564
  checkRequestedGroups(requested, selected);
3506
- sweepStaleSnapshots(os.tmpdir());
3507
3565
  const c = await collectGroups({
3508
3566
  repoDir: REPO_DIR,
3509
3567
  selected,
@@ -3532,7 +3590,6 @@ if (ARGS.mode === "groups") {
3532
3590
  // prints `[gate-qualification] ok` and never claims the full set.
3533
3591
 
3534
3592
  if (ARGS.mode === "group") {
3535
- sweepStaleSnapshots(os.tmpdir());
3536
3593
  const { passed: groupPassed } = await runGroupReceipt({
3537
3594
  repoDir: REPO_DIR,
3538
3595
  selected,
@@ -3550,17 +3607,12 @@ if (ARGS.mode === "group") {
3550
3607
  const headBefore = originHead(REPO_DIR);
3551
3608
  const workSurfaceBefore = originWorkSurfaceSha(REPO_DIR);
3552
3609
 
3553
- sweepStaleSnapshots(os.tmpdir());
3554
- const snap = createRepoSnapshot(REPO_DIR);
3555
- console.log(
3556
- `[gate-qualification] snapshot: ${snap.repoDir} (${snap.fileCount} files, origin HEAD ${headBefore.slice(0, 12)})`,
3557
- );
3558
- let report: Awaited<ReturnType<typeof qualifyMutants>>;
3559
- try {
3560
- report = await qualifyMutants(snap, selected, (line) => console.log(line));
3561
- } finally {
3562
- fs.rmSync(snap.baseDir, { recursive: true, force: true });
3563
- }
3610
+ const report = await withOwnSnapshot(REPO_DIR, undefined, (snap) => {
3611
+ console.log(
3612
+ `[gate-qualification] snapshot: ${snap.repoDir} (${snap.fileCount} files, origin HEAD ${headBefore.slice(0, 12)})`,
3613
+ );
3614
+ return qualifyMutants(snap, selected, (line) => console.log(line));
3615
+ });
3564
3616
 
3565
3617
  // Tripwire, not recovery: the runner never writes the origin, so any drift here is
3566
3618
  // an outside writer racing the qualification — evidence cannot be attributed.
@@ -205,12 +205,13 @@ const {
205
205
  COMPONENT_STATES,
206
206
  } = mod;
207
207
 
208
- /** The pi pin package.json declares — the SSOT `run.sh pi_supported_range` derives its range from. */
208
+ /** The pi pin package.json declares (an exact production dependency since #130) — the SSOT
209
+ * `run.sh pi_supported_range` derives its range from. */
209
210
  const PI_PIN = (
210
211
  JSON.parse(fs.readFileSync(path.join(REPO, "package.json"), "utf8")) as {
211
- devDependencies: Record<string, string>;
212
+ dependencies: Record<string, string>;
212
213
  }
213
- ).devDependencies["@earendil-works/pi-coding-agent"];
214
+ ).dependencies["@earendil-works/pi-coding-agent"];
214
215
 
215
216
  /** A `pi` stand-in that reports `version` and nothing else — enough for a `--version` floor probe. */
216
217
  function fakePi(dir: string, name: string, version: string): string {
@@ -364,7 +365,7 @@ function ledgerWith(
364
365
  {
365
366
  const env = world("pi-floor");
366
367
  const bin = path.join(env.HOME as string, "bin");
367
- // The supported range is DERIVED from the package.json devDep pin (run.sh
368
+ // The supported range is DERIVED from the package.json production dependency pin (run.sh
368
369
  // `pi_supported_range`), so the fixtures are derived from it too — a hardcoded "0.85.1" here
369
370
  // would silently stop being out-of-range the day the pin moves past it.
370
371
  const [maj, min] = PI_PIN.split(".").map(Number);
@@ -3,7 +3,8 @@
3
3
  * CHECKOUT-ONLY dev gate: an installed package refuses it in `run_ts` before it reads an input.
4
4
  *
5
5
  * LABEL: adapter-direct, harness-bypassed. The durable app is opened through the production
6
- * bootstrap composition against a provisioned overlay checkout, the contact is born, and its
6
+ * bootstrap composition from THIS checkout's carrier (pi/pi-durable/carrier/, under its resolver,
7
+ * against the checkout's pinned pi SDK set — verified first), the contact is born, and its
7
8
  * registered tools are executed DIRECTLY in the host (scripts/pi-durable-contact-host.mjs). No
8
9
  * model turn, no ToolTask: this gate does NOT prove that the harness selects, validates or
9
10
  * replays these tools, that a model accepts their schema, or anything about send / delivery /
@@ -11,10 +12,9 @@
11
12
  * the contact's own `receiver()` state and the bridge's replyability verdict — never a doorbell, an
12
13
  * admission or a drain; those are check-pi-durable-receive's.
13
14
  *
14
- * Inputs, both required, neither provisioned here (missing → SKIP, never a pass):
15
- * ENTWURF_PI_DURABLE_RUNTIME an upstream checkout at pi/pi-durable/overlay's pin,
16
- * with exactly the overlay patch applied, its deps installed
17
- * and the pin's git-ignored model data in place
15
+ * Input, required, not provisioned here (missing → SKIP, never a pass). The durable app itself is
16
+ * no longer an input: it is this checkout's carrier, refused by name before anything spawns when it
17
+ * or the SDK set it resolves is not the pin (#130).
18
18
  * ENTWURF_PI_DURABLE_BRIDGE_ENTRY <bundle>/mcp/entwurf-bridge/src/index.js emitted from THIS
19
19
  * checkout into a private bundle (a shared/installed bridge
20
20
  * carries the old backend list and is refused as stale) — the
@@ -44,7 +44,7 @@
44
44
  * `malformed-report` makes the gate's host driver send a non-JSON report line, which arrives while
45
45
  * the gate is already waiting for it (the pending-line path of `Host.next`).
46
46
  */
47
- import { type ChildProcess, spawn, spawnSync } from "node:child_process";
47
+ import { type ChildProcess, spawn } from "node:child_process";
48
48
  import { createHash } from "node:crypto";
49
49
  import * as fs from "node:fs";
50
50
  import * as os from "node:os";
@@ -52,16 +52,16 @@ import * as path from "node:path";
52
52
  import { createInterface } from "node:readline";
53
53
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
54
54
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
55
- import {
56
- inspectPiDurableRuntime,
57
- PI_DURABLE_RESOLVER_RELATIVE,
58
- readPiDurablePin,
59
- } from "../pi-extensions/lib/pi-durable-runtime.ts";
55
+ import { checkPiDurableRuntime, piDurableLayoutAt } from "../pi-extensions/lib/pi-durable-runtime.ts";
60
56
  import { skipLive } from "./lib/live-skip.ts";
57
+ import {
58
+ describeOperatorSnapshot,
59
+ operatorPiDurableSnapshot,
60
+ operatorSnapshotUnchanged,
61
+ } from "./lib/pi-durable-operator-guard.ts";
61
62
 
62
63
  const LABEL = "check-pi-durable-contact";
63
64
  const REPO = path.resolve(import.meta.dirname, "..");
64
- const OVERLAY = path.join(REPO, "pi", "pi-durable", "overlay");
65
65
  const HOST_DRIVER = path.join(REPO, "scripts", "pi-durable-contact-host.mjs");
66
66
  const REPORT_TIMEOUT_MS = 90_000;
67
67
  const CLOSE_TIMEOUT_MS = 30_000;
@@ -74,17 +74,14 @@ if (FAULT !== undefined && FAULT !== "after-birth" && FAULT !== "malformed-repor
74
74
  process.exit(2);
75
75
  }
76
76
 
77
- const runtimeInput = process.env.ENTWURF_PI_DURABLE_RUNTIME?.trim();
78
77
  const bridgeInput = process.env.ENTWURF_PI_DURABLE_BRIDGE_ENTRY?.trim();
79
- if (!runtimeInput || !bridgeInput) {
78
+ if (!bridgeInput) {
80
79
  skipLive(
81
80
  LABEL,
82
- "set ENTWURF_PI_DURABLE_RUNTIME=<overlay checkout at the pin, patch applied, deps and model data installed> and " +
83
- "ENTWURF_PI_DURABLE_BRIDGE_ENTRY=<bridge index.js emitted from this checkout into a private bundle>; " +
84
- "this gate provisions neither",
81
+ "set ENTWURF_PI_DURABLE_BRIDGE_ENTRY=<bridge index.js emitted from this checkout into a private bundle>; " +
82
+ "this gate does not provision it",
85
83
  );
86
84
  }
87
- const runtime = path.resolve(runtimeInput);
88
85
  const bridgeEntry = path.resolve(bridgeInput);
89
86
 
90
87
  let passed = 0;
@@ -104,36 +101,18 @@ function fatal(message: string): never {
104
101
  process.exit(1);
105
102
  }
106
103
 
107
- const sha256 = (data: string | Buffer): string => createHash("sha256").update(data).digest("hex");
108
-
109
104
  // ---------------------------------------------------------------------------
110
105
  // 0. Inputs. A provisioned-but-wrong input is broken, not absent: these FAIL, they do not skip.
111
106
  // ---------------------------------------------------------------------------
112
- // The pin and the runtime facts come from the ONE verifier the managed launch, setup and the fresh
113
- // preflight use (pi-extensions/lib/pi-durable-runtime.ts); this gate keeps its finer-grained QKs over
114
- // those facts and adds the reverse-apply check only it makes.
115
- const { pin, patchBytes } = readPiDurablePin(OVERLAY);
116
- const patchFile = path.join(OVERLAY, pin.patches[0] as string);
117
- const facts = inspectPiDurableRuntime(runtime, OVERLAY);
118
- const reverse = spawnSync("git", ["-C", runtime, "apply", "-R", "--check", patchFile]);
119
- console.log("0. inputs: tracked overlay, ignored model data, bridge bundle registry");
120
- ok(
121
- `[QK:PDC-PIN] runtime HEAD is the overlay pin ${pin.commit} (got ${facts.head || "no answer"})`,
122
- facts.gitAnswered && facts.head === pin.commit,
123
- );
124
- ok(
125
- `[QK:PDC-TRACKED-OVERLAY-EXACT] the TRACKED tree is exactly pin + overlay patch and no non-ignored file is untracked (index ${facts.indexMatchesHead ? "==" : "!="} HEAD, diff ${facts.diffEqualsPatch ? "==" : "!="} patch ${sha256(patchBytes).slice(0, 12)}, untracked ${facts.untracked || "none"}) — git-ignored runtime inputs are outside this assertion`,
126
- facts.gitAnswered && facts.indexMatchesHead && facts.diffEqualsPatch && facts.untracked === "",
127
- );
128
- ok("[QK:PDC-OVERLAY-REVERSE] the patch reverse-applies cleanly", reverse.status === 0);
107
+ // The carrier and its SDK set are verified by the ONE verifier the managed launch, setup and the
108
+ // fresh preflight use (pi-extensions/lib/pi-durable-runtime.ts), from this checkout's layout.
109
+ const carrier = checkPiDurableRuntime(piDurableLayoutAt(REPO));
110
+ console.log("0. inputs: the checkout carrier and its pi SDK set, bridge bundle registry");
129
111
  ok(
130
- `[QK:PDC-MODEL-DATA-DIGEST] the git-ignored model data is the pin's recorded bytes (${facts.modelDataFiles} files, aggregate ${facts.modelDataAggregate.slice(0, 12)}, manifest ${facts.manifestSha256.slice(0, 12)})`,
131
- facts.modelDataFiles === pin.modelData.files &&
132
- facts.modelDataAggregate === pin.modelData.aggregateSha256 &&
133
- facts.manifestSha256 === pin.modelData.manifestSha256,
112
+ `[QK:PDC-CARRIER-VERIFIED] this checkout's pi-durable carrier and the pi SDK set it resolves are the pin (${carrier.ok ? "verified" : `${carrier.reason}: ${carrier.detail}`})`,
113
+ carrier.ok,
134
114
  );
135
- const resolver = path.join(runtime, PI_DURABLE_RESOLVER_RELATIVE);
136
- ok("[QK:PDC-RESOLVER] the checkout carries the experimental source resolver", facts.resolverPresent);
115
+ const resolver = carrier.ok ? carrier.resolver : "";
137
116
  const bundleRoot = path.resolve(bridgeEntry, "..", "..", "..", "..");
138
117
  const bundleRegistry = path.join(bundleRoot, "pi-extensions", "entwurf-capabilities.json");
139
118
  const repoRegistry = path.join(REPO, "pi", "entwurf-capabilities.json");
@@ -154,52 +133,14 @@ ok(
154
133
  `[QK:PDC-BUNDLE-OWNER-JOIN] the bridge bundle's compiled owner join admits pi-durable-host (${joinLine || "not found"})`,
155
134
  joinLine.includes('"pi-durable-host"'),
156
135
  );
157
- if (failed > 0)
158
- fatal("inputs are not the pinned overlay, its model data, this checkout's registry and owner join — refusing to run");
136
+ if (failed > 0) fatal("inputs are not the verified carrier, this checkout's registry and owner join — refusing to run");
159
137
 
160
138
  // ---------------------------------------------------------------------------
161
139
  // Operator guard — COARSE and pi-durable-specific: the operator's own roots gain no pi-durable
162
140
  // record, sender dir or durable session tree. Not a proof about any other operator byte.
163
141
  // ---------------------------------------------------------------------------
164
142
  const operatorAgent = path.join(os.homedir(), ".pi", "agent");
165
- function operatorPiDurableFacts(): string {
166
- const sessions = path.join(operatorAgent, "meta-sessions");
167
- const durableRecords = fs.existsSync(sessions)
168
- ? fs
169
- .readdirSync(sessions)
170
- .filter((n) => n.endsWith(".meta.json"))
171
- .filter((n) => {
172
- try {
173
- return (
174
- (JSON.parse(fs.readFileSync(path.join(sessions, n), "utf8")) as { backend?: unknown }).backend ===
175
- "pi-durable"
176
- );
177
- } catch {
178
- return false;
179
- }
180
- })
181
- : [];
182
- const senders = fs.existsSync(path.join(operatorAgent, "meta-senders", "pi-durable"));
183
- const durableSessions = fs.existsSync(path.join(operatorAgent, "experimental", "durable-sessions"));
184
- const receiversDir = path.join(operatorAgent, "meta-receivers");
185
- const durableReceivers = fs.existsSync(receiversDir)
186
- ? fs
187
- .readdirSync(receiversDir)
188
- .filter((n) => n.endsWith(".json"))
189
- .filter((n) => {
190
- try {
191
- return (
192
- (JSON.parse(fs.readFileSync(path.join(receiversDir, n), "utf8")) as { backend?: unknown }).backend ===
193
- "pi-durable"
194
- );
195
- } catch {
196
- return false;
197
- }
198
- })
199
- : [];
200
- return JSON.stringify({ durableRecords, senders, durableSessions, durableReceivers });
201
- }
202
- const operatorBefore = operatorPiDurableFacts();
143
+ const operatorBefore = operatorPiDurableSnapshot(operatorAgent);
203
144
 
204
145
  // ---------------------------------------------------------------------------
205
146
  // Sandbox: private root, env built from nothing, children at umask 022.
@@ -810,17 +751,9 @@ ok(
810
751
  `[QK:PDC-OWNED-REAPED] every host this gate spawned and every bridge observed under it is gone (${ownedSummary || "none spawned"}; alive ${stillAlive.join(",") || "none"})`,
811
752
  stillAlive.length === 0 && cleanupProblems.length === 0,
812
753
  );
813
- const before = JSON.parse(operatorBefore) as {
814
- durableRecords: string[];
815
- senders: boolean;
816
- durableReceivers: string[];
817
- };
818
754
  ok(
819
- `[QK:PDC-OPERATOR-UNTOUCHED] coarse pi-durable guard: the operator's own roots are unchanged by this run and hold no pi-durable record, sender dir or receiver marker (${operatorBefore})`,
820
- operatorPiDurableFacts() === operatorBefore &&
821
- before.durableRecords.length === 0 &&
822
- !before.senders &&
823
- before.durableReceivers.length === 0,
755
+ `[QK:PDC-OPERATOR-UNTOUCHED] coarse pi-durable registration guard: this run left the operator's pi-durable registrations exactly as it found them (${describeOperatorSnapshot(operatorBefore)} — same names, same bytes; existing state is not a red; no auth or session content read)`,
756
+ operatorSnapshotUnchanged(operatorBefore, operatorPiDurableSnapshot(operatorAgent)),
824
757
  );
825
758
  if (stillAlive.length > 0) console.log(`sandbox kept because owned processes are still alive: ${root}`);
826
759
  else if (process.env.ENTWURF_PI_DURABLE_KEEP === "1") console.log(`sandbox kept: ${root}`);
@@ -40,7 +40,8 @@
40
40
  * Intended single idle cell: controller input 0, doorbell admission 1, S requests 2, V 0.
41
41
  *
42
42
  * Inputs as check-pi-durable-contact (missing → SKIP 97, never a pass):
43
- * ENTWURF_PI_DURABLE_RUNTIME, ENTWURF_PI_DURABLE_BRIDGE_ENTRY (a bundle carrying the owner join)
43
+ * ENTWURF_PI_DURABLE_BRIDGE_ENTRY (a bundle carrying the owner join; the durable app is this
44
+ * checkout's verified carrier, #130)
44
45
  * Optional:
45
46
  * ENTWURF_PI_DURABLE_RECEIPTS a NEW absolute directory (refused if it exists) for the cell's raw bytes.
46
47
  *
@@ -58,12 +59,13 @@ import { createInterface } from "node:readline";
58
59
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
59
60
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
60
61
  import { metaReceiverMarkerPath, upsertMetaSession, writeMetaSenderMarker } from "../pi-extensions/lib/meta-session.ts";
61
- import {
62
- inspectPiDurableRuntime,
63
- PI_DURABLE_RESOLVER_RELATIVE,
64
- readPiDurablePin,
65
- } from "../pi-extensions/lib/pi-durable-runtime.ts";
62
+ import { checkPiDurableRuntime, piDurableLayoutAt } from "../pi-extensions/lib/pi-durable-runtime.ts";
66
63
  import { skipLive } from "./lib/live-skip.ts";
64
+ import {
65
+ describeOperatorSnapshot,
66
+ operatorPiDurableSnapshot,
67
+ operatorSnapshotUnchanged,
68
+ } from "./lib/pi-durable-operator-guard.ts";
67
69
  import { ownedReapVerdict } from "./lib/pi-durable-reap.ts";
68
70
  import {
69
71
  encodedRequestIdColumn,
@@ -75,7 +77,6 @@ import {
75
77
  const LABEL = "check-pi-durable-receive";
76
78
  const QK = "PRX";
77
79
  const REPO = path.resolve(import.meta.dirname, "..");
78
- const OVERLAY = path.join(REPO, "pi", "pi-durable", "overlay");
79
80
  const HOST_DRIVER = path.join(REPO, "scripts", "pi-durable-receive-host.mjs");
80
81
  const REPORT_TIMEOUT_MS = 120_000;
81
82
  /** Validity cutoff for request #1 after the ENQUEUE — a gap bound, never a success inference. */
@@ -85,17 +86,14 @@ const REAP_TIMEOUT_MS = 5_000;
85
86
  const CALL_ID = "call_prx_1";
86
87
  const FINAL_TEXT = "read.";
87
88
 
88
- const runtimeInput = process.env.ENTWURF_PI_DURABLE_RUNTIME?.trim();
89
89
  const bridgeInput = process.env.ENTWURF_PI_DURABLE_BRIDGE_ENTRY?.trim();
90
- if (!runtimeInput || !bridgeInput) {
90
+ if (!bridgeInput) {
91
91
  skipLive(
92
92
  LABEL,
93
- "set ENTWURF_PI_DURABLE_RUNTIME=<overlay checkout at the pin, patch applied, deps and model data installed> and " +
94
- "ENTWURF_PI_DURABLE_BRIDGE_ENTRY=<bridge index.js emitted from this checkout into a private bundle>; " +
95
- "this gate provisions neither",
93
+ "set ENTWURF_PI_DURABLE_BRIDGE_ENTRY=<bridge index.js emitted from this checkout into a private bundle>; " +
94
+ "this gate does not provision it",
96
95
  );
97
96
  }
98
- const runtime = path.resolve(runtimeInput);
99
97
  const bridgeEntry = path.resolve(bridgeInput);
100
98
  const receiptsInput = process.env.ENTWURF_PI_DURABLE_RECEIPTS?.trim() || undefined;
101
99
  if (receiptsInput !== undefined) {
@@ -158,28 +156,19 @@ function messageText(content: unknown): string {
158
156
  }
159
157
 
160
158
  // ---------------------------------------------------------------------------
161
- // 0. Inputs — the pinned overlay, its model data, the bundle registry, and the bundle's owner join.
159
+ // 0. Inputs — the verified checkout carrier, the bundle registry, and the bundle's owner join.
162
160
  // ---------------------------------------------------------------------------
163
- // The pin and the runtime facts come from the ONE verifier the managed launch, setup and the fresh
164
- // preflight use (pi-extensions/lib/pi-durable-runtime.ts).
165
- const { pin } = readPiDurablePin(OVERLAY);
166
- const facts = inspectPiDurableRuntime(runtime, OVERLAY);
167
- const resolver = path.join(runtime, PI_DURABLE_RESOLVER_RELATIVE);
161
+ // The carrier and its SDK set are verified by the ONE verifier the managed launch, setup and the fresh
162
+ // preflight use (pi-extensions/lib/pi-durable-runtime.ts), from this checkout's layout.
163
+ const carrier = checkPiDurableRuntime(piDurableLayoutAt(REPO));
164
+ const resolver = carrier.ok ? carrier.resolver : "";
168
165
  const bundleRoot = path.resolve(bridgeEntry, "..", "..", "..", "..");
169
166
  const bundleRegistry = path.join(bundleRoot, "pi-extensions", "entwurf-capabilities.json");
170
167
  const bundleDeliverability = path.join(bundleRoot, "pi-extensions", "lib", "entwurf-deliverability.js");
171
168
  console.log("0. inputs");
172
169
  ok(
173
- `[QK:${QK}-INPUTS] runtime is the pin with exactly the tracked overlay (index == HEAD, nothing staged), the pin's ignored model data, the resolver, and a bridge bundle whose registry matches this checkout`,
174
- facts.gitAnswered &&
175
- facts.head === pin.commit &&
176
- facts.indexMatchesHead &&
177
- facts.diffEqualsPatch &&
178
- facts.untracked === "" &&
179
- facts.modelDataFiles === pin.modelData.files &&
180
- facts.modelDataAggregate === pin.modelData.aggregateSha256 &&
181
- facts.manifestSha256 === pin.modelData.manifestSha256 &&
182
- facts.resolverPresent &&
170
+ `[QK:${QK}-INPUTS] this checkout's pi-durable carrier and the pi SDK set it resolves are the pin (${carrier.ok ? "verified" : `${carrier.reason}: ${carrier.detail}`}), and the bridge bundle's registry matches this checkout`,
171
+ carrier.ok &&
183
172
  fs.existsSync(bridgeEntry) &&
184
173
  fs.existsSync(bundleRegistry) &&
185
174
  fs.readFileSync(bundleRegistry).equals(fs.readFileSync(path.join(REPO, "pi", "entwurf-capabilities.json"))),
@@ -192,33 +181,11 @@ ok(
192
181
  `[QK:${QK}-BUNDLE-OWNER-JOIN] the bridge bundle's compiled owner join admits pi-durable-host (${joinLine || "not found"})`,
193
182
  joinLine.includes('"pi-durable-host"'),
194
183
  );
195
- if (failed > 0) fatal("inputs are not the pinned overlay, its model data, this checkout's registry and owner join");
184
+ if (failed > 0) fatal("inputs are not the verified carrier, this checkout's registry and owner join");
196
185
 
197
186
  // Coarse pi-durable-specific operator guard (as G-contact): no operator-byte claim beyond it.
198
187
  const operatorAgent = path.join(os.homedir(), ".pi", "agent");
199
- function operatorPiDurableFacts(): string {
200
- const backendOf = (file: string): unknown => {
201
- try {
202
- return (JSON.parse(fs.readFileSync(file, "utf8")) as { backend?: unknown }).backend;
203
- } catch {
204
- return undefined;
205
- }
206
- };
207
- const list = (dir: string, suffix: string) =>
208
- fs.existsSync(dir)
209
- ? fs
210
- .readdirSync(dir)
211
- .filter((n) => n.endsWith(suffix))
212
- .filter((n) => backendOf(path.join(dir, n)) === "pi-durable")
213
- : [];
214
- return JSON.stringify({
215
- durableRecords: list(path.join(operatorAgent, "meta-sessions"), ".meta.json"),
216
- senders: fs.existsSync(path.join(operatorAgent, "meta-senders", "pi-durable")),
217
- durableReceivers: list(path.join(operatorAgent, "meta-receivers"), ".json"),
218
- durableSessions: fs.existsSync(path.join(operatorAgent, "experimental", "durable-sessions")),
219
- });
220
- }
221
- const operatorBefore = operatorPiDurableFacts();
188
+ const operatorBefore = operatorPiDurableSnapshot(operatorAgent);
222
189
 
223
190
  // ---------------------------------------------------------------------------
224
191
  // Sandbox: one private world — HOME/XDG/agent, the four garden roots and ENTWURF_DIR.
@@ -1096,13 +1063,9 @@ if (receiptsInput !== undefined) {
1096
1063
  ok(`[QK:${QK}-RECEIPTS-EXPORTED] receipt export failed: ${(error as Error).message}`, false);
1097
1064
  }
1098
1065
  }
1099
- const before = JSON.parse(operatorBefore) as { durableRecords: string[]; senders: boolean; durableReceivers: string[] };
1100
1066
  ok(
1101
- `[QK:${QK}-OPERATOR-UNTOUCHED] coarse pi-durable guard: the operator's own roots are unchanged by this run and hold no pi-durable record, sender dir or receiver marker (${operatorBefore})`,
1102
- operatorPiDurableFacts() === operatorBefore &&
1103
- before.durableRecords.length === 0 &&
1104
- !before.senders &&
1105
- before.durableReceivers.length === 0,
1067
+ `[QK:${QK}-OPERATOR-UNTOUCHED] coarse pi-durable registration guard: this run left the operator's pi-durable registrations exactly as it found them (${describeOperatorSnapshot(operatorBefore)} — same names, same bytes; existing state is not a red; no auth or session content read)`,
1068
+ operatorSnapshotUnchanged(operatorBefore, operatorPiDurableSnapshot(operatorAgent)),
1106
1069
  );
1107
1070
  if (alive.length > 0) console.log(`sandbox kept because owned processes are still alive: ${root}`);
1108
1071
  else if (process.env.ENTWURF_PI_DURABLE_KEEP === "1") console.log(`sandbox kept: ${root}`);