@junghanacs/entwurf 0.34.0 → 0.34.2

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.
@@ -27,6 +27,7 @@ import os from "node:os";
27
27
  import path from "node:path";
28
28
  import { pathToFileURL } from "node:url";
29
29
  import { reclaimOnExit } from "./lib/reclaim-on-exit.ts";
30
+ import { treeDigest } from "./lib/tree-digest.ts";
30
31
 
31
32
  let passed = 0;
32
33
  function ok(label: string, cond: boolean): void {
@@ -182,6 +183,7 @@ interface Mod {
182
183
  resolveEntwurfDataRoot: (env: NodeJS.ProcessEnv) => string;
183
184
  LEDGER_PHASES: string[];
184
185
  COMPONENT_STATES: string[];
186
+ resolveDoctorPluginRuntime: (env: NodeJS.ProcessEnv, opts: { repo: string; override?: string }) => string | null;
185
187
  }
186
188
 
187
189
  const mod = (await import(pathToFileURL(ACTIVATION).href)) as Mod;
@@ -203,6 +205,7 @@ const {
203
205
  resolveEntwurfDataRoot,
204
206
  LEDGER_PHASES,
205
207
  COMPONENT_STATES,
208
+ resolveDoctorPluginRuntime,
206
209
  } = mod;
207
210
 
208
211
  /** The pi pin package.json declares (an exact production dependency since #130) — the SSOT
@@ -737,21 +740,12 @@ function ledgerWith(
737
740
  const dataRoot = resolveEntwurfDataRoot(bare);
738
741
  const states = piStatePaths(bare);
739
742
  const piExpected = path.join(home, ".local", "share", "entwurf");
740
- // The python writers derive the SAME stable root from the SAME environment.
741
- const pyRoot = spawnSync(
742
- "python3",
743
- [
744
- "-c",
745
- "import sys; sys.path.insert(0, 'scripts'); from importlib import import_module; m = import_module('register-pi-provider'.replace('-', '_')) if False else None",
746
- ],
747
- { encoding: "utf8", env: bare },
748
- );
749
743
  ok(
750
744
  "[QK:HAC-XDG-RESOLUTION-SHARED] every root comes from one resolution — `XDG_DATA_HOME`, else " +
751
745
  "`$HOME/.local/share` — so a host with XDG unset is the same host to the teardown as it is to the writers. " +
752
746
  'The first cut built its Pi state paths from `env.XDG_DATA_HOME || ""`, which on such a host pointed at ' +
753
747
  "`/entwurf/...`: a preflight that looked at a file nobody writes and passed for the wrong reason " +
754
- `(dataRoot=${dataRoot === piExpected} packageState=${path.relative(home, states.packageState)} py=${pyRoot.status === 0})`,
748
+ `(dataRoot=${dataRoot === piExpected} packageState=${path.relative(home, states.packageState)})`,
755
749
  dataRoot === piExpected &&
756
750
  states.packageState === path.join(piExpected, "pi-package", "install-state.json") &&
757
751
  states.providerState === path.join(piExpected, "pi-provider", "install-state.json"),
@@ -1364,4 +1358,210 @@ function ledgerWith(
1364
1358
  );
1365
1359
  }
1366
1360
 
1361
+ // ── 26. both Pi owner records are asked where their writers keep them (#135 D1) ─
1362
+ {
1363
+ const env = world("pi-state-paths");
1364
+ const defaultData = path.join(env.HOME as string, ".local", "share");
1365
+ // An EMPTY XDG_DATA_HOME: run.sh and the runtime owner both fall back to $HOME/.local/share.
1366
+ const runEnv = { ...env, XDG_DATA_HOME: "" };
1367
+ seedRuntime({ ...env, XDG_DATA_HOME: defaultData });
1368
+ const agentSettings = path.join(env.PI_CODING_AGENT_DIR as string, "settings.json");
1369
+ fs.writeFileSync(agentSettings, '{"theme":"dark"}\n');
1370
+ const foreign = path.join(env.HOME as string, "dev", "entwurf");
1371
+ fs.mkdirSync(foreign, { recursive: true });
1372
+ const seeded = py(env, "register-pi-provider.py", [
1373
+ "install",
1374
+ agentSettings,
1375
+ foreign,
1376
+ "--scope",
1377
+ "user",
1378
+ "--state",
1379
+ path.join(defaultData, "entwurf", "pi-provider", "install-state.json"),
1380
+ ]);
1381
+ assert.equal(seeded.status, 0, `fixture: seeding the provider owner failed: ${seeded.stderr}`);
1382
+ const layout = resolveActivationLayout(runEnv);
1383
+ const run = spawnSync("node", [ACTIVATE, "pi"], { encoding: "utf8", env: runEnv, cwd: env.HOME as string });
1384
+ ok(
1385
+ "[QK:HAC-PI-STATE-PATHS-SHARED] both Pi ownership preflights read the record where its writer keeps it: with " +
1386
+ "XDG_DATA_HOME set but EMPTY, run.sh writes to $HOME/.local/share, so the provider preflight must look there " +
1387
+ "too — the first cut built a RELATIVE path for the provider half alone, saw no owner, and let the ledger land " +
1388
+ `on a host the provider writer then refused (exit=${run.status} ledger=${fs.existsSync(layout.ledgerPath)} refusal=${JSON.stringify((run.stderr || "").trim().split("\n").pop())})`,
1389
+ run.status !== 0 &&
1390
+ (run.stderr || "").includes("activation-preflight-refused") &&
1391
+ (run.stderr || "").includes("pi-provider") &&
1392
+ !fs.existsSync(layout.ledgerPath),
1393
+ );
1394
+ }
1395
+
1396
+ // ── 27. no ledger is not "nothing here" (#135) ─────────────────────────────────
1397
+ {
1398
+ const plugin = (env: NodeJS.ProcessEnv) => path.join(env.XDG_DATA_HOME as string, "entwurf", "herdr-plugin");
1399
+ const rows: [string, (env: NodeJS.ProcessEnv) => void][] = [
1400
+ [
1401
+ "installing-journal",
1402
+ (env) => {
1403
+ fs.mkdirSync(path.join(plugin(env), "runtime"), { recursive: true });
1404
+ fs.writeFileSync(
1405
+ path.join(plugin(env), "journal.json"),
1406
+ `${JSON.stringify({
1407
+ schemaVersion: 2,
1408
+ phase: "installing",
1409
+ runtimeRoot: runtimeRootOf(env),
1410
+ artifactIdentity: { kind: "npm", name: PACKAGE, version: "0.21.0", expectedIntegrity: "sha512-fixture" },
1411
+ previousRuntime: null,
1412
+ })}\n`,
1413
+ );
1414
+ },
1415
+ ],
1416
+ ["ready-runtime", (env) => void seedRuntime(env)],
1417
+ [
1418
+ "cache-only",
1419
+ (env) =>
1420
+ fs.mkdirSync(path.join(env.XDG_CACHE_HOME as string, "entwurf", "herdr-plugin", "npm"), { recursive: true }),
1421
+ ],
1422
+ ["journal-less-runtime", (env) => fs.mkdirSync(path.join(plugin(env), "runtime", "active"), { recursive: true })],
1423
+ ];
1424
+ const results = rows.map(([tag, make]) => {
1425
+ const env = world(`no-ledger-${tag}`);
1426
+ make(env);
1427
+ const before = treeDigest(env.HOME as string);
1428
+ const run = spawnSync("node", [DEACTIVATE], { encoding: "utf8", env });
1429
+ return {
1430
+ tag,
1431
+ status: run.status,
1432
+ named: (run.stderr || "").includes("deactivate-unattributed-leftovers"),
1433
+ unchanged: before === treeDigest(env.HOME as string),
1434
+ };
1435
+ });
1436
+ const clean = world("no-ledger-clean");
1437
+ const cleanRun = spawnSync("node", [DEACTIVATE], { encoding: "utf8", env: clean });
1438
+ ok(
1439
+ "[QK:HAC-NO-LEDGER-LEFTOVERS-REPORTED] without a ledger the teardown no longer says 'nothing to undo' over what " +
1440
+ "an aborted install left: an installing journal, a ready runtime, our npm cache, or a runtime with no journal " +
1441
+ "are each NAMED and the verb exits non-zero with every byte untouched — nothing proves what they belong to, so " +
1442
+ "nothing is removed. A truly clean host still exits 0 with the old line " +
1443
+ `(rows=${JSON.stringify(results)} clean=${cleanRun.status}/${JSON.stringify((cleanRun.stdout || "").trim())})`,
1444
+ results.every((r) => r.status !== 0 && r.named && r.unchanged) &&
1445
+ cleanRun.status === 0 &&
1446
+ (cleanRun.stdout || "").includes("no certified activation ledger — nothing to undo"),
1447
+ );
1448
+ }
1449
+
1450
+ // ── 28. only a regular file — or nothing — may stand at the ledger address (#135) ─
1451
+ {
1452
+ const env = world("ledger-kinds");
1453
+ seedRuntime(env);
1454
+ const layout = resolveActivationLayout(env);
1455
+ fs.mkdirSync(layout.stateRoot, { recursive: true });
1456
+ // a DANGLING symlink: readFileSync used to answer ENOENT, i.e. "no ledger".
1457
+ fs.symlinkSync(path.join(env.HOME as string, "nowhere.json"), layout.ledgerPath);
1458
+ const dangling = refusal(() => readCertifiedLedger(layout));
1459
+ const danglingRun = spawnSync("node", [DEACTIVATE], { encoding: "utf8", env });
1460
+ fs.unlinkSync(layout.ledgerPath);
1461
+ // a symlink to a perfectly valid ledger is still not OUR ledger file.
1462
+ const elsewhere = path.join(env.HOME as string, "ledger-elsewhere.json");
1463
+ fs.writeFileSync(elsewhere, `${JSON.stringify({ schemaVersion: 2, ...ledgerFor(env, ["pi"]) }, null, 2)}\n`);
1464
+ fs.symlinkSync(elsewhere, layout.ledgerPath);
1465
+ const linked = refusal(() => readCertifiedLedger(layout));
1466
+ fs.unlinkSync(layout.ledgerPath);
1467
+ fs.mkdirSync(layout.ledgerPath);
1468
+ const directory = refusal(() => readCertifiedLedger(layout));
1469
+ // a state root that is a FILE: lstat answers ENOTDIR, which must stay a named refusal, not a raw errno.
1470
+ const fileRoot = path.join(env.HOME as string, "state-root-is-a-file");
1471
+ fs.writeFileSync(fileRoot, "not a directory\n");
1472
+ const notDir = refusal(() =>
1473
+ readCertifiedLedger({ stateRoot: fileRoot, ledgerPath: path.join(fileRoot, "activation.json") }),
1474
+ );
1475
+ ok(
1476
+ "[QK:HAC-LEDGER-PATH-KIND] the ledger address is read by lstat first: a dangling symlink, a symlink to a valid " +
1477
+ "ledger elsewhere, a directory, and a state root that is a file are each refused by name — none of them is " +
1478
+ "'no ledger', which is what a dangling link used to read as, letting every verb act as if nothing had been " +
1479
+ `activated (dangling=${dangling}/teardown-exit=${danglingRun.status} linked=${linked} directory=${directory} not-dir=${notDir})`,
1480
+ dangling === "activation-ledger-uncertified" &&
1481
+ danglingRun.status !== 0 &&
1482
+ (danglingRun.stderr || "").includes("activation-ledger-uncertified") &&
1483
+ linked === "activation-ledger-uncertified" &&
1484
+ directory === "activation-ledger-uncertified" &&
1485
+ notDir === "activation-ledger-uncertified",
1486
+ );
1487
+ }
1488
+
1489
+ // ── 29. the doctor's expectation mode comes from the activation ledger (#136) ────
1490
+ // `doctor-meta-bridge` must compute the plugin activation's ABSOLUTE expectations exactly when the
1491
+ // ledger authorised that wiring, and never adopt it from a checkout. The oracle for the returned
1492
+ // root is `runtimeRootOf`, computed here independently of the resolver.
1493
+ {
1494
+ const env = world("doctor-mode");
1495
+ seedRuntime(env);
1496
+ const active = runtimeRootOf(env);
1497
+ const runtimeRepo = path.join(active, "node_modules", "@junghanacs", "entwurf");
1498
+ const layout = resolveActivationLayout(env);
1499
+ const mode = (opts: { repo: string; override?: string }): string | null => {
1500
+ try {
1501
+ return resolveDoctorPluginRuntime(env, opts);
1502
+ } catch (err) {
1503
+ return `refused:${(err as { code?: string }).code ?? String(err)}`;
1504
+ }
1505
+ };
1506
+ // D2 — no ledger: a doctor running from the runtime package is simply not in plugin mode.
1507
+ const absent = mode({ repo: runtimeRepo });
1508
+ writeLedger(layout, ledgerFor(env, ["pi", "claude-code"]));
1509
+ // D1 — runtime package + an active ledger that activated claude-code at THIS stable root.
1510
+ const derived = mode({ repo: runtimeRepo });
1511
+ ok(
1512
+ "[QK:DOCTOR-MODE-DERIVED] a doctor running from the plugin runtime's package derives plugin-runtime mode from " +
1513
+ `an active ledger that activated claude-code at this host's stable root, and from nothing else (no-ledger=${absent} derived=${derived})`,
1514
+ absent === null && derived === active,
1515
+ );
1516
+ // D3 — the checkout never derives, and never even opens the ledger: an uncertified body is
1517
+ // planted first, so reading it would throw instead of returning null.
1518
+ fs.writeFileSync(layout.ledgerPath, "not json\n");
1519
+ const checkout = mode({ repo: REPO });
1520
+ ok(
1521
+ "[QK:DOCTOR-MODE-CHECKOUT-NEVER-DERIVES] a doctor whose repo is not the runtime package returns no plugin mode " +
1522
+ `without opening the ledger, so a checkout never adopts plugin wiring (checkout=${checkout})`,
1523
+ checkout === null,
1524
+ );
1525
+ // D5 — on a reading path an uncertified ledger dies with the reader's own refusal, never null.
1526
+ fs.unlinkSync(layout.ledgerPath);
1527
+ fs.symlinkSync(path.join(env.HOME as string, "nowhere.json"), layout.ledgerPath);
1528
+ const viaRuntime = mode({ repo: runtimeRepo });
1529
+ const viaOverride = mode({ repo: REPO, override: active });
1530
+ fs.unlinkSync(layout.ledgerPath);
1531
+ ok(
1532
+ "[QK:DOCTOR-MODE-UNCERTIFIED-DIES] an uncertified ledger on either reading path (runtime-repo derivation, explicit " +
1533
+ `override) refuses with the reader's own code, never 'not plugin mode' (runtime=${viaRuntime} override=${viaOverride})`,
1534
+ viaRuntime === "refused:activation-ledger-uncertified" && viaOverride === "refused:activation-ledger-uncertified",
1535
+ );
1536
+ // D4 — the admission clauses on a reading path, each on a CERTIFIED ledger so only the clause can
1537
+ // refuse: a phase other than active, a ledger that never activated claude-code, and a runtimeRoot
1538
+ // 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" }));
1540
+ const notActive = mode({ repo: runtimeRepo });
1541
+ writeLedger(layout, ledgerFor(env, ["pi"]));
1542
+ const piOnly = mode({ repo: runtimeRepo });
1543
+ writeLedger(layout, {
1544
+ ...ledgerFor(env, ["pi", "claude-code"]),
1545
+ runtimeRoot: path.join(env.HOME as string, "another-runtime", "active"),
1546
+ });
1547
+ const foreignRoot = mode({ repo: runtimeRepo });
1548
+ ok(
1549
+ "[QK:DOCTOR-MODE-ADMISSION-CLAUSES] on the runtime repo a certified ledger still admits plugin mode only when " +
1550
+ "phase is active, claude-code was activated and its runtimeRoot is this host's stable root — each other case " +
1551
+ `is refused by name (activating=${notActive} pi-only=${piOnly} foreign-root=${foreignRoot})`,
1552
+ notActive === "refused:doctor-plugin-runtime-unadmitted" &&
1553
+ piOnly === "refused:doctor-plugin-runtime-unadmitted" &&
1554
+ foreignRoot === "refused:doctor-plugin-runtime-unadmitted",
1555
+ );
1556
+ // 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"]));
1558
+ const elsewhere = mode({ repo: REPO, override: path.join(env.HOME as string, "elsewhere") });
1559
+ const agreeing = mode({ repo: REPO, override: active });
1560
+ ok(
1561
+ "[QK:DOCTOR-MODE-OVERRIDE-AGREES] an explicit --plugin-runtime is admitted only when it names the ledger's own " +
1562
+ `stable root (elsewhere=${elsewhere} agreeing=${agreeing})`,
1563
+ elsewhere === "refused:doctor-plugin-runtime-unadmitted" && agreeing === active,
1564
+ );
1565
+ }
1566
+
1367
1567
  console.log(`\ncheck-herdr-activation: ${passed} assertions passed`);
@@ -187,12 +187,18 @@ function tree(root: string): string[] {
187
187
  * installed package's activation verb is — absent, incapable (exits 0 on an empty request), or a
188
188
  * faithful recorder that refuses an empty request by name and logs the argv it was given.
189
189
  */
190
- function fixtureAcquire(opts: { activate: "absent" | "incapable" | "recorder"; log?: string; fail?: boolean }) {
190
+ function fixtureAcquire(opts: {
191
+ activate: "absent" | "incapable" | "recorder";
192
+ log?: string;
193
+ fail?: boolean;
194
+ name?: string;
195
+ }) {
196
+ const name = opts.name ?? PACKAGE;
191
197
  return ({ identity, prefix }: AcquireArgs) => {
192
198
  const version = identity.version ?? FIXTURE_VERSION;
193
- const root = path.join(prefix, "node_modules", PACKAGE);
199
+ const root = path.join(prefix, "node_modules", name);
194
200
  fs.mkdirSync(path.join(root, path.dirname(COMPILED_ENTRY)), { recursive: true });
195
- fs.writeFileSync(path.join(root, "package.json"), `${JSON.stringify({ name: PACKAGE, version })}\n`);
201
+ fs.writeFileSync(path.join(root, "package.json"), `${JSON.stringify({ name, version })}\n`);
196
202
  fs.writeFileSync(path.join(root, COMPILED_ENTRY), "// compiled entry fixture\n");
197
203
  const bin = path.join(prefix, "node_modules", ".bin");
198
204
  fs.mkdirSync(bin, { recursive: true });
@@ -222,7 +228,7 @@ function fixtureAcquire(opts: { activate: "absent" | "incapable" | "recorder"; l
222
228
  observedDigest: `sha256-${createHash("sha256")
223
229
  .update(identity.commit ?? "x")
224
230
  .digest("hex")}`,
225
- packageName: PACKAGE,
231
+ packageName: name,
226
232
  packageVersion: version,
227
233
  };
228
234
  };
@@ -238,7 +244,7 @@ function drive(
238
244
  env: NodeJS.ProcessEnv,
239
245
  text: string | { error: string } | { status: number; stderr: string },
240
246
  deps: Record<string, unknown> = {},
241
- ): { code: number | null; refusal: string | null; out: string; progress: string[] } {
247
+ ): { code: number | null; refusal: string | null; detail: string; out: string; progress: string[] } {
242
248
  let out = "";
243
249
  // The operator's terminal is a SEAM here, never the real `/dev/tty`: a gate that narrated into
244
250
  // whatever terminal happened to be running it would be writing outside its own sandbox.
@@ -269,11 +275,12 @@ function drive(
269
275
  readRuntimeLock: () => FIXTURE_NPM_LOCK,
270
276
  ...deps,
271
277
  });
272
- return { code, refusal: null, out, progress: progressLines };
278
+ return { code, refusal: null, detail: "", out, progress: progressLines };
273
279
  } catch (err) {
274
280
  return {
275
281
  code: null,
276
282
  refusal: (err as { code?: string }).code ?? `not-a-HerdrBuildError:${String(err)}`,
283
+ detail: (err as { detail?: string }).detail ?? String(err),
277
284
  out,
278
285
  progress: progressLines,
279
286
  };
@@ -727,6 +734,228 @@ function drive(
727
734
  );
728
735
  }
729
736
 
737
+ // ── 6c. the selected components' owners are asked before the first byte (#135) ─
738
+ /** Run one of the shipped Pi ownership writers against a sandbox, exactly as run.sh does. */
739
+ function piOwner(env: NodeJS.ProcessEnv, script: string, args: string[]): SpawnResult {
740
+ return spawnSync("python3", [path.join(REPO, "scripts", script), ...args], {
741
+ encoding: "utf8",
742
+ env,
743
+ }) as unknown as SpawnResult;
744
+ }
745
+ function piSettingsOf(env: NodeJS.ProcessEnv): string {
746
+ return path.join(env.HOME as string, ".pi", "agent", "settings.json");
747
+ }
748
+ function piStateOf(env: NodeJS.ProcessEnv, half: "package" | "provider"): string {
749
+ return path.join(env.XDG_DATA_HOME as string, "entwurf", `pi-${half}`, "install-state.json");
750
+ }
751
+ /** A LIVE foreign owner: a real directory standing for a developer checkout, registered for real. */
752
+ function foreignPiOwner(env: NodeJS.ProcessEnv, halves: ("package" | "provider")[]): string {
753
+ const checkout = path.join(env.HOME as string, "dev", "entwurf");
754
+ fs.mkdirSync(checkout, { recursive: true });
755
+ fs.mkdirSync(path.dirname(piSettingsOf(env)), { recursive: true });
756
+ fs.writeFileSync(piSettingsOf(env), '{"theme":"dark"}\n');
757
+ if (halves.includes("package")) {
758
+ const r = piOwner(env, "register-pi-package.py", [
759
+ piSettingsOf(env),
760
+ checkout,
761
+ "--scope",
762
+ "user",
763
+ "--state",
764
+ piStateOf(env, "package"),
765
+ ]);
766
+ assert.equal(r.status, 0, `fixture: seeding the foreign package owner failed: ${r.stderr}`);
767
+ }
768
+ if (halves.includes("provider")) {
769
+ const r = piOwner(env, "register-pi-provider.py", [
770
+ "install",
771
+ piSettingsOf(env),
772
+ checkout,
773
+ "--scope",
774
+ "user",
775
+ "--state",
776
+ piStateOf(env, "provider"),
777
+ ]);
778
+ assert.equal(r.status, 0, `fixture: seeding the foreign provider owner failed: ${r.stderr}`);
779
+ }
780
+ return checkout;
781
+ }
782
+ /** An acquisition that must never be reached: it counts, and it throws a sentinel instead of fetching. */
783
+ function countingSentinel(): { acquire: () => never; calls: () => number } {
784
+ let calls = 0;
785
+ return {
786
+ acquire: () => {
787
+ calls++;
788
+ throw new Error("HPB_OWNERSHIP_SENTINEL_NO_NETWORK");
789
+ },
790
+ calls: () => calls,
791
+ };
792
+ }
793
+ {
794
+ const env = world("owner-package");
795
+ foreignPiOwner(env, ["package"]);
796
+ const before = treeDigest(env.HOME as string);
797
+ const sentinel = countingSentinel();
798
+ const r = drive(env, listing({ pi: "current (v8) (/home/u/.pi)" }), {
799
+ acquire: sentinel.acquire,
800
+ resolveCommit: () => COMMIT_A,
801
+ });
802
+ const after = treeDigest(env.HOME as string);
803
+ ok(
804
+ "[QK:HPB-OWNERSHIP-BEFORE-ACQUISITION] a Pi user-scope registration owned by another LIVE root refuses the " +
805
+ "plugin install BEFORE anything is fetched or written: the SAME shipped package-owner preflight the installed " +
806
+ "activation runs is asked against the prospective plugin root, so the refusal names the component and the " +
807
+ "acquisition is never called — no installing journal, runtime, cache or ledger appears and every byte under " +
808
+ "the sandbox HOME is unchanged. The first cut asked only after bootstrap, so a known refusal still fetched and " +
809
+ `installed a runtime first (refusal=${r.refusal} acquired=${sentinel.calls()} home-unchanged=${before === after} ` +
810
+ `journal=${fs.existsSync(journalPathOf(env))} detail=${JSON.stringify(r.detail.slice(0, 160))})`,
811
+ r.refusal === "herdr-build-component-ownership-refused" &&
812
+ r.detail.includes("pi-package") &&
813
+ sentinel.calls() === 0 &&
814
+ before === after &&
815
+ !fs.existsSync(journalPathOf(env)),
816
+ );
817
+ }
818
+ {
819
+ // The provider key carries its OWN ownership record; a host whose package entry is free but whose
820
+ // provider key another root owns must refuse just as early.
821
+ const env = world("owner-provider");
822
+ foreignPiOwner(env, ["provider"]);
823
+ const before = treeDigest(env.HOME as string);
824
+ const sentinel = countingSentinel();
825
+ const r = drive(env, listing({ pi: "current (v8) (/home/u/.pi)" }), {
826
+ acquire: sentinel.acquire,
827
+ resolveCommit: () => COMMIT_A,
828
+ });
829
+ ok(
830
+ "[QK:HPB-OWNERSHIP-PROVIDER-HALF] the provider half is asked on its own: a free package entry with a provider " +
831
+ "key another LIVE root owns still refuses before acquisition, names `pi-provider`, and leaves the sandbox " +
832
+ `HOME byte-identical (refusal=${r.refusal} acquired=${sentinel.calls()} home-unchanged=${before === treeDigest(env.HOME as string)} detail=${JSON.stringify(r.detail.slice(0, 120))})`,
833
+ r.refusal === "herdr-build-component-ownership-refused" &&
834
+ r.detail.includes("pi-provider") &&
835
+ !r.detail.includes("pi-package:") &&
836
+ sentinel.calls() === 0 &&
837
+ before === treeDigest(env.HOME as string),
838
+ );
839
+ }
840
+ {
841
+ // Claude's preflight is its PLAN check (documents and managed-key shapes), run against the
842
+ // prospective root in plugin mode. It is not a claim about which root owns Claude.
843
+ const env = world("owner-claude-plan");
844
+ fs.mkdirSync(path.join(env.HOME as string, ".claude"), { recursive: true });
845
+ fs.writeFileSync(path.join(env.HOME as string, ".claude", "settings.json"), "[]\n");
846
+ const before = treeDigest(env.HOME as string);
847
+ const sentinel = countingSentinel();
848
+ const r = drive(env, listing({ claude: "current (v2) (/home/u/.claude)" }), {
849
+ acquire: sentinel.acquire,
850
+ resolveCommit: () => COMMIT_A,
851
+ });
852
+ ok(
853
+ "[QK:HPB-OWNERSHIP-CLAUDE-PLAN] a selected Claude whose install plan its own state owner refuses (a settings " +
854
+ "file that is not a JSON object) refuses before acquisition, names `claude-code`, and writes nothing " +
855
+ `(refusal=${r.refusal} acquired=${sentinel.calls()} home-unchanged=${before === treeDigest(env.HOME as string)} detail=${JSON.stringify(r.detail.slice(0, 120))})`,
856
+ r.refusal === "herdr-build-component-ownership-refused" &&
857
+ r.detail.includes("claude-code") &&
858
+ sentinel.calls() === 0 &&
859
+ before === treeDigest(env.HOME as string),
860
+ );
861
+ }
862
+ {
863
+ // Only the SELECTED components are asked: a backend Herdr has not integrated cannot veto another's install.
864
+ const piOnly = world("owner-unselected-claude");
865
+ fs.mkdirSync(path.join(piOnly.HOME as string, ".claude"), { recursive: true });
866
+ fs.writeFileSync(path.join(piOnly.HOME as string, ".claude", "settings.json"), "[]\n");
867
+ const piLog = path.join(piOnly.HOME as string, "argv.log");
868
+ const piRun = drive(piOnly, listing({ pi: "current (v8) (/home/u/.pi)" }), {
869
+ acquire: fixtureAcquire({ activate: "recorder", log: piLog }),
870
+ resolveCommit: () => COMMIT_A,
871
+ });
872
+ const claudeOnly = world("owner-unselected-pi");
873
+ foreignPiOwner(claudeOnly, ["package", "provider"]);
874
+ const claudeLog = path.join(claudeOnly.HOME as string, "argv.log");
875
+ const claudeRun = drive(claudeOnly, listing({ claude: "current (v2) (/home/u/.claude)" }), {
876
+ acquire: fixtureAcquire({ activate: "recorder", log: claudeLog }),
877
+ resolveCommit: () => COMMIT_A,
878
+ });
879
+ const logged = (f: string) => (fs.existsSync(f) ? fs.readFileSync(f, "utf8").trim() : "");
880
+ ok(
881
+ "[QK:HPB-OWNERSHIP-SELECTED-ONLY] only the backends in `A` are asked: Pi alone installs over a Claude settings " +
882
+ "file its owner would refuse, and Claude alone installs over a Pi registration another root owns — an " +
883
+ "unselected backend's owner never vetoes the selected one " +
884
+ `(pi-only=${piRun.code}/${piRun.refusal}/${JSON.stringify(logged(piLog))} claude-only=${claudeRun.code}/${claudeRun.refusal}/${JSON.stringify(logged(claudeLog))})`,
885
+ piRun.code === 0 && logged(piLog) === "pi" && claudeRun.code === 0 && logged(claudeLog) === "claude-code",
886
+ );
887
+ }
888
+ {
889
+ // A SAME-owner reinstall under a symlinked XDG_DATA_HOME. The installed verb sees its own root in
890
+ // PHYSICAL form (Node realpaths the ESM entry) and the provider owner records that root without
891
+ // resolving it, so the early ask must use the physical form too, while the stable command stays the
892
+ // lexical runtime address.
893
+ const home = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-hpb-owner-symlinked-")));
894
+ const realData = path.join(home, "real-data");
895
+ fs.mkdirSync(realData, { recursive: true });
896
+ fs.symlinkSync(realData, path.join(home, "data"));
897
+ const env: NodeJS.ProcessEnv = {
898
+ HOME: home,
899
+ XDG_DATA_HOME: path.join(home, "data"),
900
+ XDG_CACHE_HOME: path.join(home, "cache"),
901
+ XDG_STATE_HOME: path.join(home, "state"),
902
+ PATH: process.env.PATH,
903
+ };
904
+ const physicalRoot = path.join(realData, "entwurf", "herdr-plugin", "runtime", "active", "node_modules", PACKAGE);
905
+ fs.mkdirSync(path.dirname(piSettingsOf(env)), { recursive: true });
906
+ fs.writeFileSync(piSettingsOf(env), '{"theme":"dark"}\n');
907
+ const pkg = piOwner(env, "register-pi-package.py", [
908
+ piSettingsOf(env),
909
+ physicalRoot,
910
+ "--scope",
911
+ "user",
912
+ "--state",
913
+ piStateOf(env, "package"),
914
+ ]);
915
+ const prov = piOwner(env, "register-pi-provider.py", [
916
+ "install",
917
+ piSettingsOf(env),
918
+ physicalRoot,
919
+ "--scope",
920
+ "user",
921
+ "--state",
922
+ piStateOf(env, "provider"),
923
+ "--plugin-runtime",
924
+ activeDirOf(env),
925
+ ]);
926
+ assert.equal(pkg.status, 0, `fixture: same-owner package seed failed: ${pkg.stderr}`);
927
+ assert.equal(prov.status, 0, `fixture: same-owner provider seed failed: ${prov.stderr}`);
928
+ const log = path.join(home, "argv.log");
929
+ const r = drive(env, listing({ pi: "current (v8) (/home/u/.pi)" }), {
930
+ acquire: fixtureAcquire({ activate: "recorder", log }),
931
+ resolveCommit: () => COMMIT_A,
932
+ });
933
+ ok(
934
+ "[QK:HPB-OWNERSHIP-PHYSICAL-ROOT] a same-owner reinstall under a symlinked XDG_DATA_HOME is admitted, not " +
935
+ "mistaken for a foreign root: the early owner preflight asks with the PHYSICAL prospective package root " +
936
+ "(the form the installed verb records as its own) and passes the LEXICAL stable runtime as the plugin-mode " +
937
+ "runtime flag (the only form the provider owner accepts), so the build reaches activation " +
938
+ `(code=${r.code} refusal=${r.refusal} detail=${JSON.stringify(r.detail.slice(0, 160))})`,
939
+ r.code === 0 && fs.existsSync(log) && fs.readFileSync(log, "utf8").trim() === "pi",
940
+ );
941
+ }
942
+ {
943
+ // The owners were asked about one root; the installed verb must run from that same root.
944
+ const env = world("owner-root-tie");
945
+ const log = path.join(env.HOME as string, "argv.log");
946
+ const r = drive(env, listing({ pi: "current (v8) (/home/u/.pi)" }), {
947
+ acquire: fixtureAcquire({ activate: "recorder", log, name: "@elsewhere/entwurf" }),
948
+ readRuntimeLock: () => FIXTURE_CHECKOUT_LOCK,
949
+ resolveCommit: () => COMMIT_A,
950
+ });
951
+ ok(
952
+ "[QK:HPB-PLANNED-ROOT-TIE] the root the component owners were asked about is the root the installed verb runs " +
953
+ "from: an artifact that lands under another package name is refused by name before activation, so the early " +
954
+ `answer can never have been about a different root (refusal=${r.refusal} activated=${fs.existsSync(log)} detail=${JSON.stringify(r.detail.slice(0, 160))})`,
955
+ r.refusal === "herdr-build-artifact-drifted" && !fs.existsSync(log),
956
+ );
957
+ }
958
+
730
959
  // ── 7. the runner claims nothing about Herdr's own commit ──────────────────────
731
960
  {
732
961
  const env = world("post-build-gap");
@@ -227,6 +227,8 @@ ok "fake live entwurf MCP bridge running as a child of the stand-in Claude pid (
227
227
  # --- doctor driver -----------------------------------------------------------
228
228
  DOC_OUT=""
229
229
  DOC_RC=0
230
+ # #136: arguments for the plugin-runtime block at the end; empty for every clone cell.
231
+ DOC_ARGS=()
230
232
  run_doctor() {
231
233
  set +e
232
234
  # Deliberate cross-harness poison. Before the doctor scrubbed these carriers, the
@@ -237,7 +239,7 @@ run_doctor() {
237
239
  DOC_OUT="$(PI_SESSION_ID=ambient-pi-session-must-not-win \
238
240
  PI_AGENT_ID=ambient-pi-agent/must-not-win \
239
241
  ENTWURF_META_SENDER_MARKER="$TMP/ambient-sender-marker-must-not-win.json" \
240
- bash "$REPO/scripts/meta-bridge-doctor.sh" 2>&1)"
242
+ bash "$REPO/scripts/meta-bridge-doctor.sh" ${DOC_ARGS[@]+"${DOC_ARGS[@]}"} 2>&1)"
241
243
  DOC_RC=$?
242
244
  set -e
243
245
  }
@@ -713,5 +715,88 @@ else
713
715
  bad "doctor stayed red after reverting every mutation (a restore is incomplete):"$'\n'"$(printf '%s\n' "$DOC_OUT" | grep -E '^ FAIL' | sed 's/^/ /')"
714
716
  fi
715
717
 
718
+ # --- 6. #136: a Herdr plugin activation's ABSOLUTE wiring ---------------------
719
+ # The plugin activation runs the same installer with ENTWURF_PLUGIN_RUNTIME, so Claude's MCP and
720
+ # statusLine name absolute bins under the stable runtime. The doctor must learn that mode from the
721
+ # activation ledger and certify the wiring, not call it drift. FIXTURE HONESTY: the two runtime
722
+ # bins are wrappers onto this checkout's own launcher and statusline, and the ledger is written by
723
+ # the shipped writer with a fixture artifact identity; no npm runtime is installed here.
724
+ echo "[plugin-runtime] the installer's absolute wiring, certified through the activation ledger"
725
+ ACTIVE="$XDG_DATA_HOME/entwurf/herdr-plugin/runtime/active"
726
+ mkdir -p "$ACTIVE/node_modules/.bin" "$ACTIVE/node_modules/@junghanacs/entwurf"
727
+ printf '#!/usr/bin/env bash\nexec bash "%s/mcp/entwurf-bridge/start.sh" "$@"\n' "$REPO" > "$ACTIVE/node_modules/.bin/entwurf-bridge"
728
+ printf '#!/usr/bin/env bash\nexec "%s/scripts/meta-bridge-statusline.sh" "$@"\n' "$REPO" > "$ACTIVE/node_modules/.bin/entwurf-statusline"
729
+ chmod +x "$ACTIVE/node_modules/.bin/entwurf-bridge" "$ACTIVE/node_modules/.bin/entwurf-statusline"
730
+ if ENTWURF_PLUGIN_RUNTIME="$ACTIVE" bash "$REPO/scripts/meta-bridge-install.sh" >"$TMP/install-plugin.log" 2>&1; then
731
+ ok "real meta-bridge-install.sh wires the plugin-runtime mode (absolute bins under the sandbox stable root)"
732
+ else
733
+ bad "plugin-runtime install-meta-bridge failed in the sandbox:"$'\n'"$(sed 's/^/ install| /' "$TMP/install-plugin.log")"
734
+ fi
735
+ rm -rf "$PLANTED"
736
+ mkdir -p "$PLANTED"
737
+ cp -r "$ASM/$PLUGIN/." "$PLANTED/"
738
+ write_ledger() {
739
+ node --input-type=module - "$REPO/scripts/herdr-activation.mjs" "$ACTIVE" "$1" <<'JS'
740
+ import { pathToFileURL } from "node:url";
741
+ const [modPath, active, backends] = process.argv.slice(2);
742
+ const m = await import(pathToFileURL(modPath).href);
743
+ const states = Object.fromEntries(backends.split(",").map((b) => [b, "active"]));
744
+ m.writeLedger(m.resolveActivationLayout(process.env), m.ledgerBody({
745
+ phase: "active",
746
+ runtimeRoot: active,
747
+ artifactIdentity: { kind: "npm", name: "@junghanacs/entwurf", version: "0.34.1", expectedIntegrity: "sha512-fixture", observedDigest: `sha256-${"c".repeat(64)}` },
748
+ roots: m.resolveComponentRoots(process.env),
749
+ states,
750
+ }));
751
+ JS
752
+ }
753
+ write_ledger "pi,claude-code"
754
+ DOC_ARGS=(--plugin-runtime "$ACTIVE")
755
+
756
+ # P1 — the plugin wiring the installer wrote is certified: PASS, and the doctor names its mode.
757
+ # The [QK:] token lives on the `bad` line only (see M9b).
758
+ run_doctor
759
+ if [ "$DOC_RC" -eq 0 ] && printf '%s\n' "$DOC_OUT" | grep -q 'meta-bridge doctor: PASS' \
760
+ && printf '%s\n' "$DOC_OUT" | grep -qF "mode: plugin-runtime $ACTIVE (activation ledger)" \
761
+ && printf '%s\n' "$DOC_OUT" | grep -qF 'live mcpServers.entwurf-bridge equals desired_mcp()'; then
762
+ ok "plugin-runtime wiring reaches doctor PASS through the activation ledger, and the doctor names that mode"
763
+ else
764
+ bad "[QK:DOCTOR-PLUGIN-MODE-GREEN] the plugin activation's absolute wiring did NOT reach PASS (exit $DOC_RC):"$'\n'"$(printf '%s\n' "$DOC_OUT" | grep -E '^ (FAIL|WARN)|mode:' | sed 's/^/ /')"
765
+ fi
766
+
767
+ # P2 — genuine drift stays red in plugin mode: an absolute statusLine under the same root, but not ours.
768
+ cp "$CLAUDE_CONFIG_DIR/settings.json" "$TMP/settings-p2.bak"
769
+ python3 - "$CLAUDE_CONFIG_DIR/settings.json" "$ACTIVE/node_modules/.bin/evil" <<'PY'
770
+ import json, sys
771
+ p = sys.argv[1]
772
+ d = json.load(open(p, encoding="utf-8"))
773
+ d["statusLine"]["command"] = sys.argv[2]
774
+ json.dump(d, open(p, "w", encoding="utf-8"), indent=2)
775
+ PY
776
+ expect_red "plugin-mode statusLine repointed under the same runtime root" "statusLine.command drifted"
777
+ cp "$TMP/settings-p2.bak" "$CLAUDE_CONFIG_DIR/settings.json"
778
+
779
+ # P3 — a ledger that did not activate claude-code admits no plugin mode. The refusal is named,
780
+ # and it is NOT an early exit: the store and live-owner sections still run (Hard Rule 13).
781
+ write_ledger "pi"
782
+ expect_red "override against a ledger that never activated claude-code" "doctor-plugin-runtime-unadmitted"
783
+ # Each of the three expectation sites says UNKNOWN for that reason (never a bare-bin fallback),
784
+ # and the runtime axes still run: the live command still DELIVERS, the store and owner join report.
785
+ for claim in \
786
+ 'managed settings/MCP keyset is UNKNOWN: the expectation mode was not established' \
787
+ 'statusLine expectation is UNKNOWN: the expectation mode was not established' \
788
+ 'live-vs-desired MCP ownership is UNKNOWN: the expectation mode was not established' \
789
+ 'live bridge command DELIVERS' \
790
+ '[meta-record store]' \
791
+ '[live Claude MCP owner join]'
792
+ do
793
+ if printf '%s\n' "$DOC_OUT" | sed -n '/expectation mode refused: doctor-plugin-runtime-unadmitted/,$p' | grep -qF "$claim"; then
794
+ ok "after the mode refusal the doctor still reports: $claim"
795
+ else
796
+ bad "after the mode refusal the doctor never reported: $claim"
797
+ fi
798
+ done
799
+ write_ledger "pi,claude-code"
800
+
716
801
  echo
717
802
  if [ "$fail" -eq 0 ]; then echo "check-meta-doctor-oracle: PASS"; else echo "check-meta-doctor-oracle: FAIL (see above)"; exit 1; fi