@junghanacs/entwurf 0.45.0 → 0.50.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 (105) hide show
  1. package/AGENTS.md +3 -2
  2. package/BASELINE.md +110 -1
  3. package/CHANGELOG.md +169 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/ENTWURF-CLI.json +1229 -0
  6. package/ENTWURF.json +63 -15
  7. package/FAQ.md +4 -4
  8. package/README.md +124 -188
  9. package/VERIFY.md +2 -2
  10. package/docs/external-mcp-host.md +3 -3
  11. package/docs/setup-clean-host.md +37 -12
  12. package/mcp/entwurf-bridge/dist/ENTWURF.json +63 -15
  13. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +19 -8
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +483 -18
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -10
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +613 -33
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-resume-call.js +497 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +3 -1
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +50 -5
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +7 -1
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-location.js +347 -0
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-owner-fence.js +1132 -0
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +1 -0
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +18 -11
  25. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +30 -15
  26. package/mcp/entwurf-bridge/dist/scripts/herdr-rails.mjs +40 -7
  27. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +1 -27
  28. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-prune.js +1 -1
  29. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
  30. package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +1 -1
  31. package/mcp/entwurf-bridge/src/index.ts +28 -8
  32. package/mcp/entwurf-bridge/tsconfig.build.json +7 -1
  33. package/native/win32-x64/manifest.json +1 -1
  34. package/package.json +6 -3
  35. package/pi/pi-durable/bootstrap.mjs +179 -6
  36. package/pi/pi-durable/carrier/runtime.js +108 -9
  37. package/pi/pi-durable/carrier/session-name.js +11 -0
  38. package/pi/pi-durable/carrier/sessions.js +96 -20
  39. package/pi/pi-durable/overlay/upstream-pin.json +11 -5
  40. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +710 -38
  41. package/pi-extensions/lib/fresh-call-dispatch.ts +64 -12
  42. package/pi-extensions/lib/herdr-fresh-call.ts +919 -40
  43. package/pi-extensions/lib/herdr-resume-call.ts +810 -0
  44. package/pi-extensions/lib/meta-session.ts +13 -8
  45. package/pi-extensions/lib/mux-resume-call.ts +77 -4
  46. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +9 -2
  47. package/pi-extensions/lib/pi-durable-location.ts +446 -0
  48. package/pi-extensions/lib/pi-durable-owner-fence.ts +1461 -0
  49. package/pi-extensions/lib/pi-durable-runtime.ts +4 -0
  50. package/pi-extensions/lib/resume-launch-identity.ts +31 -12
  51. package/pi-extensions/meta-bridge-pi-durable.ts +29 -15
  52. package/run.sh +411 -657
  53. package/scripts/check-claude-launch.ts +341 -0
  54. package/scripts/check-entwurf-v2-surface.ts +2 -2
  55. package/scripts/check-fresh-call-dispatch.ts +70 -9
  56. package/scripts/check-fresh-cut-gate.sh +23 -9
  57. package/scripts/check-gate-qualification.ts +18 -13
  58. package/scripts/check-herdr-activation.ts +301 -23
  59. package/scripts/check-herdr-fresh-call.ts +161 -25
  60. package/scripts/check-herdr-plugin-build.ts +173 -1
  61. package/scripts/check-herdr-plugin.ts +28 -2
  62. package/scripts/check-herdr-runtime-bootstrap.ts +136 -0
  63. package/scripts/check-install-surface.ts +1236 -6
  64. package/scripts/check-meta-doctor-oracle.sh +1 -1
  65. package/scripts/check-meta-facts.ts +20 -1
  66. package/scripts/check-mux-resume-call.ts +28 -0
  67. package/scripts/check-peer-facts.ts +39 -2
  68. package/scripts/cli-help.mjs +222 -0
  69. package/scripts/dev-bin.sh +39 -2
  70. package/scripts/herdr-activation.mjs +28 -6
  71. package/scripts/herdr-operator-link.mjs +389 -0
  72. package/scripts/herdr-plugin-activate.mjs +5 -0
  73. package/scripts/herdr-plugin-deactivate.mjs +18 -5
  74. package/scripts/herdr-rails.mjs +43 -7
  75. package/scripts/herdr-runtime.mjs +27 -0
  76. package/scripts/meta-bridge-fresh-cut.ts +1 -27
  77. package/scripts/meta-bridge-prune.ts +1 -1
  78. package/scripts/meta-facts.ts +1 -1
  79. package/scripts/mutants/agent-guidance.json +4 -4
  80. package/scripts/mutants/claude-launch.json +142 -0
  81. package/scripts/mutants/cli-help-source.json +420 -0
  82. package/scripts/mutants/dev-gate-dispatch.json +109 -0
  83. package/scripts/mutants/fresh-call-dispatch.json +38 -2
  84. package/scripts/mutants/fresh-cut.json +15 -0
  85. package/scripts/mutants/herdr-activation.json +128 -0
  86. package/scripts/mutants/herdr-fresh-call.json +278 -5
  87. package/scripts/mutants/herdr-operator-link.json +318 -0
  88. package/scripts/mutants/herdr-plugin-build.json +55 -0
  89. package/scripts/mutants/herdr-plugin.json +17 -0
  90. package/scripts/mutants/herdr-resume-call.json +288 -0
  91. package/scripts/mutants/herdr-runtime-bootstrap.json +48 -0
  92. package/scripts/mutants/meta-facts.json +15 -0
  93. package/scripts/mutants/mux-resume-call.json +63 -2
  94. package/scripts/mutants/peer-facts.json +15 -0
  95. package/scripts/mutants/pi-durable.json +979 -1
  96. package/scripts/mutants/source-install.json +35 -0
  97. package/scripts/mutants/v2-surface.json +2 -2
  98. package/scripts/mutants/v2-visible-resume.json +255 -6
  99. package/scripts/peer-facts.ts +1 -1
  100. package/scripts/smoke-agy-install-state.sh +66 -0
  101. package/scripts/smoke-meta-prune.sh +23 -0
  102. package/scripts/smoke-omp-fresh-live.ts +12 -3
  103. package/scripts/smoke-omp-receive-live.ts +7 -1
  104. package/scripts/smoke-pi-durable-fresh-live.ts +12 -2
  105. package/team.json.example +39 -0
@@ -160,7 +160,7 @@ interface Mod {
160
160
  claudeUserConfig: Root;
161
161
  };
162
162
  readCertifiedLedger: (layout: Layout) => Ledger | null;
163
- writeLedger: (layout: Layout, entry: Record<string, unknown>) => void;
163
+ writeLedger: (layout: Layout, version: number, entry: Record<string, unknown>) => void;
164
164
  certifyRootsAgainstLedger: (ledger: Ledger | null, roots: unknown, runtimeRoot: string) => void;
165
165
  planActivation: (args: { ledger: Ledger | null; requested: string[] }) => {
166
166
  reconcile: string[];
@@ -590,7 +590,7 @@ function ledgerWith(
590
590
  {
591
591
  const env = world("ledger");
592
592
  const layout = resolveActivationLayout(env);
593
- writeLedger(layout, ledgerFor(env, ["pi"]));
593
+ writeLedger(layout, 2, ledgerFor(env, ["pi"]));
594
594
  const good = readCertifiedLedger(layout) as Ledger;
595
595
  const write = (body: unknown): string | null => {
596
596
  fs.writeFileSync(layout.ledgerPath, `${JSON.stringify(body)}\n`);
@@ -628,7 +628,7 @@ function ledgerWith(
628
628
  {
629
629
  const env = world("state-machine");
630
630
  const layout = resolveActivationLayout(env);
631
- writeLedger(layout, ledgerWith(env, "deactivating", { pi: "active", "claude-code": "removed" }));
631
+ writeLedger(layout, 2, ledgerWith(env, "deactivating", { pi: "active", "claude-code": "removed" }));
632
632
  const mid = readCertifiedLedger(layout) as Ledger;
633
633
  const write = (body: unknown): string | null => {
634
634
  fs.writeFileSync(layout.ledgerPath, `${JSON.stringify(body)}\n`);
@@ -678,7 +678,7 @@ function ledgerWith(
678
678
  {
679
679
  const env = world("add-only");
680
680
  const layout = resolveActivationLayout(env);
681
- writeLedger(layout, ledgerFor(env, ["pi"]));
681
+ writeLedger(layout, 2, ledgerFor(env, ["pi"]));
682
682
  const ledger = readCertifiedLedger(layout);
683
683
  const grow = planActivation({ ledger, requested: ["pi", "claude-code"] });
684
684
  const shrink = planActivation({ ledger: readCertifiedLedger(layout), requested: [] });
@@ -702,7 +702,7 @@ function ledgerWith(
702
702
  {
703
703
  const env = world("roots");
704
704
  const layout = resolveActivationLayout(env);
705
- writeLedger(layout, ledgerFor(env, ["pi"]));
705
+ writeLedger(layout, 2, ledgerFor(env, ["pi"]));
706
706
  const ledger = readCertifiedLedger(layout);
707
707
  const moved = { ...env, PI_CODING_AGENT_DIR: path.join(env.HOME as string, "somewhere-else") };
708
708
  const drifted = refusal(() => certifyRootsAgainstLedger(ledger, resolveComponentRoots(moved), runtimeRootOf(env)));
@@ -720,7 +720,7 @@ function ledgerWith(
720
720
  {
721
721
  const env = world("order");
722
722
  const layout = resolveActivationLayout(env);
723
- writeLedger(layout, ledgerFor(env, ["pi", "claude-code"]));
723
+ writeLedger(layout, 2, ledgerFor(env, ["pi", "claude-code"]));
724
724
  const steps = planDeactivation(readCertifiedLedger(layout));
725
725
  const absent = refusal(() => planDeactivation(null));
726
726
  ok(
@@ -894,7 +894,7 @@ function ledgerWith(
894
894
  const agentSettings = path.join(env.PI_CODING_AGENT_DIR as string, "settings.json");
895
895
  fs.writeFileSync(agentSettings, '{"theme":"dark"}\n');
896
896
  sh(env, ["install-user-scope", "--plugin-runtime", runtimeRootOf(env)]);
897
- writeLedger(layout, ledgerFor(env, ["pi", "claude-code"]));
897
+ writeLedger(layout, 2, ledgerFor(env, ["pi", "claude-code"]));
898
898
  // claude-code is in the ledger but was never installed, so its preflight must refuse.
899
899
  const settingsBefore = fs.readFileSync(agentSettings, "utf8");
900
900
  const stateBefore = fs
@@ -960,7 +960,7 @@ function ledgerWith(
960
960
  // Pi's has not. A retry must NOT re-run Claude — its state file is gone, so its preflight would
961
961
  // refuse forever — and must pick up at Pi.
962
962
  spawnSync("bash", [path.join(REPO, "run.sh"), "uninstall-meta-bridge"], { encoding: "utf8", env: runEnv });
963
- writeLedger(layout, ledgerWith(env, "deactivating", { pi: "active", "claude-code": "removed" }));
963
+ writeLedger(layout, 2, ledgerWith(env, "deactivating", { pi: "active", "claude-code": "removed" }));
964
964
  const resumed = spawnSync("node", [DEACTIVATE], { encoding: "utf8", env: runEnv });
965
965
  const afterResume = JSON.parse(fs.readFileSync(agentSettings, "utf8")) as Record<string, unknown>;
966
966
 
@@ -968,7 +968,7 @@ function ledgerWith(
968
968
  const env2 = world("retry-ledger");
969
969
  const layout2 = resolveActivationLayout(env2);
970
970
  fs.mkdirSync(path.join(env2.XDG_DATA_HOME as string, "entwurf", "herdr-plugin"), { recursive: true });
971
- writeLedger(layout2, ledgerWith(env2, "deactivating", { pi: "removed", "claude-code": "removed" }));
971
+ writeLedger(layout2, 2, ledgerWith(env2, "deactivating", { pi: "removed", "claude-code": "removed" }));
972
972
  const finished = spawnSync("node", [DEACTIVATE], { encoding: "utf8", env: env2 });
973
973
 
974
974
  ok(
@@ -1009,7 +1009,7 @@ function ledgerWith(
1009
1009
  previousRuntime: null,
1010
1010
  })}\n`,
1011
1011
  );
1012
- writeLedger(layout, ledgerFor(env, ["pi"]));
1012
+ writeLedger(layout, 2, ledgerFor(env, ["pi"]));
1013
1013
 
1014
1014
  const run = spawnSync("node", [DEACTIVATE], { encoding: "utf8", env });
1015
1015
  const after = readJson(agentSettings) as Record<string, unknown>;
@@ -1042,7 +1042,12 @@ function ledgerWith(
1042
1042
 
1043
1043
  // ── 17. nothing is imported after the runtime is deleted ───────────────────────
1044
1044
  {
1045
- const sources = [DEACTIVATE, ACTIVATION, path.join(REPO, "scripts", "herdr-runtime.mjs")];
1045
+ const sources = [
1046
+ DEACTIVATE,
1047
+ ACTIVATION,
1048
+ path.join(REPO, "scripts", "herdr-runtime.mjs"),
1049
+ path.join(REPO, "scripts", "herdr-operator-link.mjs"),
1050
+ ];
1046
1051
  // Comments are stripped first: these files DOCUMENT the hazard, and a scan that cannot tell a
1047
1052
  // warning about `import()` from a call to it would be satisfied by deleting the explanation.
1048
1053
  const strip = (body: string): string => body.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
@@ -1075,13 +1080,13 @@ function ledgerWith(
1075
1080
  fs.writeFileSync(path.join(env.PI_CODING_AGENT_DIR as string, "settings.json"), '{"theme":"dark"}\n');
1076
1081
 
1077
1082
  // forward over a teardown-in-progress: refused, and not one byte written.
1078
- writeLedger(layout, ledgerWith(env, "deactivating", { pi: "active" }));
1083
+ writeLedger(layout, 2, ledgerWith(env, "deactivating", { pi: "active" }));
1079
1084
  const midTeardown = fs.readFileSync(layout.ledgerPath, "utf8");
1080
1085
  const piBefore = fs.readFileSync(path.join(env.PI_CODING_AGENT_DIR as string, "settings.json"), "utf8");
1081
1086
  const overTeardown = spawnSync("node", [ACTIVATE, "pi"], { encoding: "utf8", env: runEnv });
1082
1087
 
1083
1088
  // inverse over an activation-in-progress: refused, told to repair the activation first.
1084
- writeLedger(layout, ledgerWith(env, "activating", { pi: "active", "claude-code": "pending" }));
1089
+ writeLedger(layout, 2, ledgerWith(env, "activating", { pi: "active", "claude-code": "pending" }));
1085
1090
  const overActivating = spawnSync("node", [DEACTIVATE], { encoding: "utf8", env: runEnv });
1086
1091
 
1087
1092
  // forward retry of the SAME set over `activating`: allowed, that is what the phase is for.
@@ -1111,7 +1116,7 @@ function ledgerWith(
1111
1116
  const env = world("claude-roots");
1112
1117
  const roots = resolveComponentRoots(env);
1113
1118
  const layout = resolveActivationLayout(env);
1114
- writeLedger(layout, ledgerFor(env, ["claude-code"]));
1119
+ writeLedger(layout, 2, ledgerFor(env, ["claude-code"]));
1115
1120
  const ledger = readCertifiedLedger(layout) as Ledger;
1116
1121
  // Same XDG, same CLAUDE_CONFIG_DIR, HOME moved: the settings half still resolves under the
1117
1122
  // override, but `$HOME/.claude.json` — where the user-scope MCP entry lives — has moved with it.
@@ -1218,7 +1223,7 @@ function ledgerWith(
1218
1223
  {
1219
1224
  const env = world("ledger-identity");
1220
1225
  const layout = resolveActivationLayout(env);
1221
- writeLedger(layout, ledgerFor(env, ["pi"]));
1226
+ writeLedger(layout, 2, ledgerFor(env, ["pi"]));
1222
1227
  const good = readCertifiedLedger(layout) as Ledger;
1223
1228
  const write = (body: unknown): string | null => {
1224
1229
  fs.writeFileSync(layout.ledgerPath, `${JSON.stringify(body)}\n`);
@@ -1253,7 +1258,7 @@ function ledgerWith(
1253
1258
  const env = world("rebind-legality");
1254
1259
  const layout = resolveActivationLayout(env);
1255
1260
  const ledgerA = () => {
1256
- writeLedger(layout, ledgerFor(env, ["pi", "claude-code"]));
1261
+ writeLedger(layout, 2, ledgerFor(env, ["pi", "claude-code"]));
1257
1262
  return readCertifiedLedger(layout) as Ledger;
1258
1263
  };
1259
1264
  const sourceDrift = refusal(() => certifyArtifactForForward(ledgerA(), IDENTITY_NPM, ["pi", "claude-code"]));
@@ -1261,11 +1266,11 @@ function ledgerWith(
1261
1266
  const legal = certifyArtifactForForward(ledgerA(), IDENTITY_B, ["pi", "claude-code", "pi"]);
1262
1267
  const fresh = certifyArtifactForForward(null, IDENTITY_B, ["pi"]);
1263
1268
  const dropped = refusal(() => certifyArtifactForForward(ledgerA(), IDENTITY_B, ["pi"]));
1264
- writeLedger(layout, ledgerWith(env, "activating", { pi: "pending" }));
1269
+ writeLedger(layout, 2, ledgerWith(env, "activating", { pi: "pending" }));
1265
1270
  const midActivation = refusal(() =>
1266
1271
  certifyArtifactForForward(readCertifiedLedger(layout) as Ledger, IDENTITY_B, ["pi"]),
1267
1272
  );
1268
- writeLedger(layout, ledgerWith(env, "deactivating", { pi: "removed" }));
1273
+ writeLedger(layout, 2, ledgerWith(env, "deactivating", { pi: "removed" }));
1269
1274
  const midTeardown = refusal(() =>
1270
1275
  certifyArtifactForForward(readCertifiedLedger(layout) as Ledger, IDENTITY_B, ["pi"]),
1271
1276
  );
@@ -1505,7 +1510,7 @@ function ledgerWith(
1505
1510
  };
1506
1511
  // D2 — no ledger: a doctor running from the runtime package is simply not in plugin mode.
1507
1512
  const absent = mode({ repo: runtimeRepo });
1508
- writeLedger(layout, ledgerFor(env, ["pi", "claude-code"]));
1513
+ writeLedger(layout, 2, ledgerFor(env, ["pi", "claude-code"]));
1509
1514
  // D1 — runtime package + an active ledger that activated claude-code at THIS stable root.
1510
1515
  const derived = mode({ repo: runtimeRepo });
1511
1516
  ok(
@@ -1536,11 +1541,11 @@ function ledgerWith(
1536
1541
  // D4 — the admission clauses on a reading path, each on a CERTIFIED ledger so only the clause can
1537
1542
  // refuse: a phase other than active, a ledger that never activated claude-code, and a runtimeRoot
1538
1543
  // that is not this host's stable root. Each is refused by name, never read as 'not plugin mode'.
1539
- writeLedger(layout, ledgerWith(env, "activating", { pi: "active", "claude-code": "pending" }));
1544
+ writeLedger(layout, 2, ledgerWith(env, "activating", { pi: "active", "claude-code": "pending" }));
1540
1545
  const notActive = mode({ repo: runtimeRepo });
1541
- writeLedger(layout, ledgerFor(env, ["pi"]));
1546
+ writeLedger(layout, 2, ledgerFor(env, ["pi"]));
1542
1547
  const piOnly = mode({ repo: runtimeRepo });
1543
- writeLedger(layout, {
1548
+ writeLedger(layout, 2, {
1544
1549
  ...ledgerFor(env, ["pi", "claude-code"]),
1545
1550
  runtimeRoot: path.join(env.HOME as string, "another-runtime", "active"),
1546
1551
  });
@@ -1554,7 +1559,7 @@ function ledgerWith(
1554
1559
  foreignRoot === "refused:doctor-plugin-runtime-unadmitted",
1555
1560
  );
1556
1561
  // D6 — the override must agree with the ledger's root; agreeing, it is admitted from a checkout.
1557
- writeLedger(layout, ledgerFor(env, ["pi", "claude-code"]));
1562
+ writeLedger(layout, 2, ledgerFor(env, ["pi", "claude-code"]));
1558
1563
  const elsewhere = mode({ repo: REPO, override: path.join(env.HOME as string, "elsewhere") });
1559
1564
  const agreeing = mode({ repo: REPO, override: active });
1560
1565
  ok(
@@ -1564,4 +1569,277 @@ function ledgerWith(
1564
1569
  );
1565
1570
  }
1566
1571
 
1572
+ // ── 30. the ledger floor: 2 and 3 are read, and a write keeps the version it found (#153) ─
1573
+ {
1574
+ const versionOf = (layout: Layout): string => {
1575
+ try {
1576
+ const ledger = readCertifiedLedger(layout) as Ledger | null;
1577
+ return ledger === null ? "absent" : String(ledger.schemaVersion);
1578
+ } catch (err) {
1579
+ return `refused:${(err as { code?: string }).code ?? String(err)}`;
1580
+ }
1581
+ };
1582
+ const env = world("ledger-version");
1583
+ const layout = resolveActivationLayout(env);
1584
+ const body = ledgerFor(env, ["pi"]);
1585
+ const raw = (version: unknown): string => {
1586
+ fs.mkdirSync(layout.stateRoot, { recursive: true });
1587
+ fs.writeFileSync(layout.ledgerPath, `${JSON.stringify({ schemaVersion: version, ...body }, null, 2)}\n`);
1588
+ return versionOf(layout);
1589
+ };
1590
+ const cells: Record<string, string> = {
1591
+ v2: raw(2),
1592
+ v3: raw(3),
1593
+ v1: raw(1),
1594
+ v4: raw(4),
1595
+ "string-3": raw("3"),
1596
+ null: raw(null),
1597
+ };
1598
+ ok(
1599
+ "[QK:HAC-LEDGER-VERSION-ADMITTED] the reader admits schema 2 AND schema 3 under the same exact key set and " +
1600
+ "hands back the version it read, and nothing else: 3 is the floor an older reader refuses before its first " +
1601
+ "write, so a reader that admits only 2 is exactly the artifact the floor exists to stop " +
1602
+ `(${Object.entries(cells)
1603
+ .map(([k, v]) => `${k}=${v}`)
1604
+ .join(" ")})`,
1605
+ cells.v2 === "2" &&
1606
+ cells.v3 === "3" &&
1607
+ cells.v1 === "refused:activation-ledger-uncertified" &&
1608
+ cells.v4 === "refused:activation-ledger-uncertified" &&
1609
+ cells["string-3"] === "refused:activation-ledger-uncertified" &&
1610
+ cells.null === "refused:activation-ledger-uncertified",
1611
+ );
1612
+
1613
+ const fresh = world("ledger-writer");
1614
+ const freshLayout = resolveActivationLayout(fresh);
1615
+ const freshBody = ledgerFor(fresh, ["pi"]);
1616
+ const invalid = {
1617
+ v4: refusal(() => writeLedger(freshLayout, 4, freshBody)),
1618
+ v1: refusal(() => writeLedger(freshLayout, 1, freshBody)),
1619
+ undefined: refusal(() => writeLedger(freshLayout, undefined as unknown as number, freshBody)),
1620
+ };
1621
+ const absentAfterInvalid = !fs.existsSync(freshLayout.ledgerPath);
1622
+ ok(
1623
+ "[QK:HAC-LEDGER-WRITER-VERSION-CHECKED] the writer takes the version as an explicit argument and refuses one " +
1624
+ "outside the admitted set before the first byte — a ledger the reader would refuse must never be written " +
1625
+ `(${Object.entries(invalid)
1626
+ .map(([k, v]) => `${k}=${v}`)
1627
+ .join(" ")} absent=${absentAfterInvalid})`,
1628
+ Object.values(invalid).every((v) => v === "activation-ledger-version-invalid") && absentAfterInvalid,
1629
+ );
1630
+
1631
+ writeLedger(freshLayout, 3, freshBody);
1632
+ const bytes3 = fs.readFileSync(freshLayout.ledgerPath, "utf8");
1633
+ const embedded = {
1634
+ lower: refusal(() => writeLedger(freshLayout, 3, { ...freshBody, schemaVersion: 2 })),
1635
+ raise: refusal(() => writeLedger(freshLayout, 2, { ...freshBody, schemaVersion: 3 })),
1636
+ };
1637
+ const unchanged = fs.readFileSync(freshLayout.ledgerPath, "utf8") === bytes3;
1638
+ ok(
1639
+ "[QK:HAC-LEDGER-WRITER-NO-EMBEDDED-VERSION] an entry carrying its own `schemaVersion` is refused rather than " +
1640
+ "merged, so no spread order lets a body silently lower or raise the floor the caller named, and the ledger " +
1641
+ `already there is byte-identical (lower=${embedded.lower} raise=${embedded.raise} unchanged=${unchanged})`,
1642
+ embedded.lower === "activation-ledger-version-invalid" &&
1643
+ embedded.raise === "activation-ledger-version-invalid" &&
1644
+ unchanged &&
1645
+ versionOf(freshLayout) === "3",
1646
+ );
1647
+
1648
+ // activate: a host with no ledger is born at 2; a host already at 3 stays at 3 through a forward run.
1649
+ const act = world("ledger-version-activate");
1650
+ const actLayout = resolveActivationLayout(act);
1651
+ const actEnv = {
1652
+ ...act,
1653
+ PATH: `${fakeClaude(act)}${path.delimiter}${act.PATH}`,
1654
+ FAKE_CLAUDE_LOG: path.join(act.HOME as string, "claude.log"),
1655
+ };
1656
+ fs.writeFileSync(path.join(act.PI_CODING_AGENT_DIR as string, "settings.json"), '{"theme":"dark"}\n');
1657
+ seedRuntime(act);
1658
+ const born = spawnSync("node", [ACTIVATE, "pi"], { encoding: "utf8", env: actEnv });
1659
+ const bornVersion = versionOf(actLayout);
1660
+ writeLedger(actLayout, 3, ledgerFor(act, ["pi"]));
1661
+ const kept = spawnSync("node", [ACTIVATE, "pi", "claude-code"], { encoding: "utf8", env: actEnv });
1662
+ const keptVersion = versionOf(actLayout);
1663
+ const keptLedger = keptVersion === "3" ? (readCertifiedLedger(actLayout) as Ledger) : null;
1664
+ ok(
1665
+ "[QK:HAC-LEDGER-VERSION-KEPT-BY-ACTIVATE] activation neither raises nor lowers the floor: a fresh host's " +
1666
+ "ledger is born at 2, and a forward run over a 3-ledger checkpoints and settles at 3 — a blanket rewrite to " +
1667
+ "the fresh version would hand the host back to every older reader " +
1668
+ `(born=${born.status}/${bornVersion} kept=${kept.status}/${keptVersion}/${keptLedger?.phase}/${JSON.stringify(keptLedger?.activatedBackends)})`,
1669
+ born.status === 0 &&
1670
+ bornVersion === "2" &&
1671
+ kept.status === 0 &&
1672
+ keptVersion === "3" &&
1673
+ keptLedger?.phase === "active" &&
1674
+ keptLedger.activatedBackends.join(",") === "pi,claude-code",
1675
+ );
1676
+
1677
+ // deactivate: its checkpoint is written before the first step, so a step refused after it leaves that
1678
+ // checkpoint on disk. Residue under the runtime root with no journal behind it is refused by the
1679
+ // runtime inverse, which runs right after the checkpoint here because both components are already
1680
+ // recorded removed. The seeded ledger is compact JSON, so a rewrite is visible in its bytes.
1681
+ const de = world("ledger-version-deactivate");
1682
+ const deLayout = resolveActivationLayout(de);
1683
+ fs.mkdirSync(deLayout.stateRoot, { recursive: true });
1684
+ const compact = `${JSON.stringify({ schemaVersion: 3, ...ledgerWith(de, "deactivating", { pi: "removed", "claude-code": "removed" }) })}\n`;
1685
+ fs.writeFileSync(deLayout.ledgerPath, compact);
1686
+ fs.mkdirSync(runtimeRootOf(de), { recursive: true });
1687
+ fs.writeFileSync(path.join(runtimeRootOf(de), "residue"), "not ours\n");
1688
+ const stopped = spawnSync("node", [DEACTIVATE], { encoding: "utf8", env: de });
1689
+ const rewritten = fs.existsSync(deLayout.ledgerPath) && fs.readFileSync(deLayout.ledgerPath, "utf8") !== compact;
1690
+ const deVersion = versionOf(deLayout);
1691
+ ok(
1692
+ "[QK:HAC-LEDGER-VERSION-KEPT-BY-DEACTIVATE] the teardown checkpoint carries the version it read: a 3-ledger " +
1693
+ "rewritten mid-teardown is still 3, so a teardown stopped halfway is resumed only by a reader that knows the " +
1694
+ `floor (exit=${stopped.status} refusal=${(stopped.stderr || "").includes("runtime-inverse-foreign-refused")} rewritten=${rewritten} version=${deVersion})`,
1695
+ stopped.status !== 0 &&
1696
+ (stopped.stderr || "").includes("runtime-inverse-foreign-refused") &&
1697
+ rewritten &&
1698
+ deVersion === "3",
1699
+ );
1700
+ }
1701
+
1702
+ // ── 31. deactivate takes the operator link back FIRST, and judges it before the first write (#153) ─
1703
+ {
1704
+ /** A receipt-owned operator link in a physical bin dir, written raw — independent of the writer. */
1705
+ const linked = (env: NodeJS.ProcessEnv, link: "ours" | "foreign") => {
1706
+ const B = path.join(env.HOME as string, "bin-B");
1707
+ fs.mkdirSync(B, { recursive: true });
1708
+ const target = path.join(runtimeRootOf(env), "node_modules", ".bin", "entwurf");
1709
+ const linkPath = path.join(B, "entwurf");
1710
+ fs.symlinkSync(link === "ours" ? target : path.join(env.HOME as string, "someone-else"), linkPath);
1711
+ const layout = resolveActivationLayout(env);
1712
+ fs.mkdirSync(layout.stateRoot, { recursive: true });
1713
+ const receiptPath = path.join(layout.stateRoot, "operator-link.json");
1714
+ fs.writeFileSync(
1715
+ receiptPath,
1716
+ `${JSON.stringify({ schemaVersion: 1, phase: "linked", binDir: B, linkPath, target, runtimeRoot: runtimeRootOf(env) }, null, 2)}\n`,
1717
+ );
1718
+ return { linkPath, receiptPath };
1719
+ };
1720
+ const linkState = (p: string) => {
1721
+ const kind = fs.lstatSync(p, { throwIfNoEntry: false });
1722
+ return kind === undefined ? "<absent>" : kind.isSymbolicLink() ? `-> ${fs.readlinkSync(p)}` : "<other>";
1723
+ };
1724
+ /** A 3-ledger mid-teardown with both components already removed, written compact. */
1725
+ const floorLedger = (env: NodeJS.ProcessEnv): string => {
1726
+ const layout = resolveActivationLayout(env);
1727
+ fs.mkdirSync(layout.stateRoot, { recursive: true });
1728
+ const text = `${JSON.stringify({ schemaVersion: 3, ...ledgerWith(env, "deactivating", { pi: "removed", "claude-code": "removed" }) })}\n`;
1729
+ fs.writeFileSync(layout.ledgerPath, text);
1730
+ return text;
1731
+ };
1732
+
1733
+ // (a) ORDER: the runtime step is refused (residue with no journal), so the run stops there — by
1734
+ // then the link and its receipt must already be gone, and the ledger kept as the retry authority.
1735
+ const first = world("operator-link-first");
1736
+ floorLedger(first);
1737
+ fs.mkdirSync(runtimeRootOf(first), { recursive: true });
1738
+ fs.writeFileSync(path.join(runtimeRootOf(first), "residue"), "not ours\n");
1739
+ const firstLink = linked(first, "ours");
1740
+ const stopped = spawnSync("node", [DEACTIVATE], { encoding: "utf8", env: first });
1741
+ const kept = refusal(() => readCertifiedLedger(resolveActivationLayout(first))) ?? "certified";
1742
+
1743
+ // (a') ...and BEFORE the components: a real pi + Claude activation whose preflights are all green, raised
1744
+ // to 3, whose FIRST component inverse (Claude's) is stopped at its own platform fence by a sandbox
1745
+ // `uname` shim. No preflight reads uname, so the stop lands after the checkpoint and before any
1746
+ // component is removed; by then the link and its receipt must be gone, the ledger deactivating at 3.
1747
+ const comp = world("operator-link-before-components");
1748
+ const compEnv = {
1749
+ ...comp,
1750
+ PATH: `${fakeClaude(comp)}${path.delimiter}${comp.PATH}`,
1751
+ FAKE_CLAUDE_LOG: path.join(comp.HOME as string, "claude.log"),
1752
+ };
1753
+ seedRuntime(comp);
1754
+ fs.writeFileSync(path.join(comp.PI_CODING_AGENT_DIR as string, "settings.json"), '{"theme":"dark"}\n');
1755
+ const compActivated = spawnSync("node", [ACTIVATE, "pi", "claude-code"], { encoding: "utf8", env: compEnv });
1756
+ const compLayout = resolveActivationLayout(comp);
1757
+ const born = readCertifiedLedger(compLayout);
1758
+ if (born !== null) {
1759
+ writeLedger(compLayout, 3, Object.fromEntries(Object.entries(born).filter(([k]) => k !== "schemaVersion")));
1760
+ }
1761
+ const compLink = linked(comp, "ours");
1762
+ const unameShim = path.join(comp.HOME as string, "uname-shim");
1763
+ fs.mkdirSync(unameShim, { recursive: true });
1764
+ fs.writeFileSync(path.join(unameShim, "uname"), "#!/bin/sh\necho Plan9\n", { mode: 0o755 });
1765
+ const compRun = spawnSync("node", [DEACTIVATE], {
1766
+ encoding: "utf8",
1767
+ env: { ...compEnv, PATH: `${unameShim}${path.delimiter}${compEnv.PATH}` },
1768
+ });
1769
+ let compLedger: Ledger | null = null;
1770
+ try {
1771
+ compLedger = readCertifiedLedger(compLayout);
1772
+ } catch {
1773
+ // Bounded probe: an uncertifiable ledger is reported below as null.
1774
+ compLedger = null;
1775
+ }
1776
+ const compStates = (compLedger?.components ?? []).map((c) => `${c.backend}:${c.state}`).join(",");
1777
+ ok(
1778
+ "[QK:HAC-DEACTIVATE-LINK-FIRST] the operator link is removed BEFORE every component and the runtime — it names " +
1779
+ "the runtime, so a teardown that stops later must not leave it pointing at what it is about to delete " +
1780
+ `(exit=${stopped.status} runtime-refused=${(stopped.stderr || "").includes("runtime-inverse-foreign-refused")} link=${linkState(firstLink.linkPath)} receipt=${fs.existsSync(firstLink.receiptPath)} ledger=${kept}; ` +
1781
+ `first-component: activated=${compActivated.status} exit=${compRun.status} claude-refused=${(compRun.stderr || "").includes("claude-code inverse failed")} link=${linkState(compLink.linkPath)} receipt=${fs.existsSync(compLink.receiptPath)} ledger=${compLedger?.schemaVersion}/${compLedger?.phase}/${compStates})`,
1782
+ stopped.status !== 0 &&
1783
+ (stopped.stderr || "").includes("runtime-inverse-foreign-refused") &&
1784
+ linkState(firstLink.linkPath) === "<absent>" &&
1785
+ !fs.existsSync(firstLink.receiptPath) &&
1786
+ kept === "certified" &&
1787
+ compActivated.status === 0 &&
1788
+ compRun.status !== 0 &&
1789
+ (compRun.stderr || "").includes("deactivate-component-failed") &&
1790
+ (compRun.stderr || "").includes(
1791
+ "claude-code inverse failed — runtime and ledger preserved as retry authority (0 component(s) already removed and recorded)",
1792
+ ) &&
1793
+ linkState(compLink.linkPath) === "<absent>" &&
1794
+ !fs.existsSync(compLink.receiptPath) &&
1795
+ compLedger?.schemaVersion === 3 &&
1796
+ compLedger.phase === "deactivating" &&
1797
+ compStates === "pi:active,claude-code:active",
1798
+ );
1799
+
1800
+ // (b) a link that is no longer the receipt's refuses the WHOLE teardown before the checkpoint: the
1801
+ // compact ledger would be rewritten by it, so byte-identity proves the refusal came first.
1802
+ const foreign = world("operator-link-foreign");
1803
+ const foreignLedger = floorLedger(foreign);
1804
+ const foreignLink = linked(foreign, "foreign");
1805
+ const receiptBefore = fs.readFileSync(foreignLink.receiptPath, "utf8");
1806
+ const linkBefore = linkState(foreignLink.linkPath);
1807
+ const refusedRun = spawnSync("node", [DEACTIVATE], { encoding: "utf8", env: foreign });
1808
+ ok(
1809
+ "[QK:HAC-DEACTIVATE-FOREIGN-LINK-REFUSES] a link that is not the receipt's stops the whole teardown before its " +
1810
+ "first write — the ledger, the receipt and that link are byte-identical — instead of being found later by " +
1811
+ `the removal after the ledger already moved (exit=${refusedRun.status} named=${(refusedRun.stderr || "").includes("operator-link-foreign")} ledger-unchanged=${fs.readFileSync(resolveActivationLayout(foreign).ledgerPath, "utf8") === foreignLedger})`,
1812
+ refusedRun.status !== 0 &&
1813
+ (refusedRun.stderr || "").includes("operator-link-foreign") &&
1814
+ fs.readFileSync(resolveActivationLayout(foreign).ledgerPath, "utf8") === foreignLedger &&
1815
+ fs.readFileSync(foreignLink.receiptPath, "utf8") === receiptBefore &&
1816
+ linkState(foreignLink.linkPath) === linkBefore,
1817
+ );
1818
+
1819
+ // (c) no ledger: the receipt address is a named leftover; its link is named only from a certified
1820
+ // receipt, and a damaged receipt names nothing beyond its own address. Nothing is removed.
1821
+ const orphan = world("operator-link-orphan");
1822
+ const orphanLink = linked(orphan, "ours");
1823
+ const orphanRun = spawnSync("node", [DEACTIVATE], { encoding: "utf8", env: orphan });
1824
+ const damaged = world("operator-link-damaged");
1825
+ const damagedLink = linked(damaged, "ours");
1826
+ fs.writeFileSync(damagedLink.receiptPath, "{broken");
1827
+ const damagedRun = spawnSync("node", [DEACTIVATE], { encoding: "utf8", env: damaged });
1828
+ ok(
1829
+ "[QK:HAC-NO-LEDGER-OPERATOR-LEFTOVERS] with no ledger an operator-link receipt is a named leftover with a " +
1830
+ "non-zero exit and nothing removed; its link is named only when the receipt is certified, never searched " +
1831
+ `for (orphan=${orphanRun.status}/receipt-named=${(orphanRun.stderr || "").includes(orphanLink.receiptPath)}/link-named=${(orphanRun.stderr || "").includes(`operator-link (${orphanLink.linkPath})`)} damaged=${damagedRun.status}/link-named=${(damagedRun.stderr || "").includes(`operator-link (${damagedLink.linkPath})`)})`,
1832
+ orphanRun.status !== 0 &&
1833
+ (orphanRun.stderr || "").includes("deactivate-unattributed-leftovers") &&
1834
+ (orphanRun.stderr || "").includes(orphanLink.receiptPath) &&
1835
+ (orphanRun.stderr || "").includes(`operator-link (${orphanLink.linkPath})`) &&
1836
+ linkState(orphanLink.linkPath) !== "<absent>" &&
1837
+ fs.existsSync(orphanLink.receiptPath) &&
1838
+ damagedRun.status !== 0 &&
1839
+ (damagedRun.stderr || "").includes(damagedLink.receiptPath) &&
1840
+ !(damagedRun.stderr || "").includes(`operator-link (${damagedLink.linkPath})`) &&
1841
+ fs.readFileSync(damagedLink.receiptPath, "utf8") === "{broken",
1842
+ );
1843
+ }
1844
+
1567
1845
  console.log(`\ncheck-herdr-activation: ${passed} assertions passed`);