@junghanacs/entwurf 0.17.2 → 0.18.1

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 (65) hide show
  1. package/AGENTS.md +6 -5
  2. package/CHANGELOG.md +299 -0
  3. package/CONTRIBUTING.md +1 -1
  4. package/README.md +9 -5
  5. package/VERIFY.md +3 -3
  6. package/demo/README.md +1 -1
  7. package/docs/acp-backend-rail.md +46 -12
  8. package/docs/setup-clean-host.md +35 -3
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +14 -9
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +76 -0
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +9 -6
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +11 -3
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +66 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +10 -1
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +8 -2
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +25 -13
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +6 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +38 -0
  20. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +64 -4
  21. package/mcp/entwurf-bridge/src/index.ts +16 -8
  22. package/package.json +10 -9
  23. package/pi-extensions/entwurf-control.ts +12 -2
  24. package/pi-extensions/lib/acp/acp-client.ts +3 -3
  25. package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
  26. package/pi-extensions/lib/acp/backend.ts +3 -3
  27. package/pi-extensions/lib/acp/event-mapper.ts +5 -5
  28. package/pi-extensions/lib/entwurf-deliverability.ts +104 -0
  29. package/pi-extensions/lib/entwurf-fact-provider.ts +20 -6
  30. package/pi-extensions/lib/entwurf-facts.ts +52 -3
  31. package/pi-extensions/lib/entwurf-peer-observe.ts +77 -0
  32. package/pi-extensions/lib/entwurf-peers-render.ts +12 -1
  33. package/pi-extensions/lib/entwurf-v2-decider.ts +26 -8
  34. package/pi-extensions/lib/entwurf-v2-production.ts +32 -12
  35. package/pi-extensions/lib/entwurf-v2-surface.ts +6 -0
  36. package/pi-extensions/lib/meta-session.ts +44 -0
  37. package/pi-extensions/meta-bridge-hook.ts +72 -3
  38. package/run.sh +146 -56
  39. package/scripts/check-acp-usage-accounting.ts +9 -9
  40. package/scripts/check-bridge-delivery.ts +42 -1
  41. package/scripts/check-entwurf-fact-provider.ts +22 -1
  42. package/scripts/check-entwurf-facts.ts +17 -2
  43. package/scripts/check-entwurf-peers-surface.ts +20 -2
  44. package/scripts/check-entwurf-self-address.ts +20 -2
  45. package/scripts/check-entwurf-v2-decider.ts +6 -3
  46. package/scripts/check-entwurf-v2-matrix.ts +10 -5
  47. package/scripts/check-entwurf-v2-production.ts +72 -1
  48. package/scripts/check-gate-qualification.ts +63 -12
  49. package/scripts/check-meta-hook-session-switch.ts +560 -0
  50. package/scripts/check-release-gate-outcomes.ts +160 -3
  51. package/scripts/meta-bridge-doctor.sh +29 -4
  52. package/scripts/mutants/bridge-command-boot.json +3 -3
  53. package/scripts/mutants/gate-qualification.json +28 -0
  54. package/scripts/mutants/meta-hook-session-switch.json +223 -0
  55. package/scripts/mutants/pack-install.json +14 -3
  56. package/scripts/mutants/release-gate.json +22 -0
  57. package/scripts/raw-acp-compaction-measure/README.md +77 -0
  58. package/scripts/raw-acp-compaction-measure/probe.ts +196 -0
  59. package/scripts/raw-claude-session-switch/README.md +170 -0
  60. package/scripts/smoke-acp-cortex-live.ts +22 -2
  61. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  62. package/scripts/smoke-acp-v2-send-live.ts +23 -2
  63. package/scripts/smoke-entwurf-chain-live.ts +85 -13
  64. package/scripts/smoke-entwurf-v2-matrix-live.ts +23 -1
  65. package/scripts/smoke-mux-fresh-call-live.ts +13 -3
@@ -25,7 +25,15 @@
25
25
  // "internal prerequisite" case, Cortex being the one that was measured).
26
26
  // 7. every LIVE smoke is either wired into the aggregate or excluded for a
27
27
  // reason the docs actually state — the protocol cannot vouch for a step the
28
- // gate never lists.
28
+ // gate never lists;
29
+ // 8. the moved check-gate-qualification stays reachable on its owners, and the
30
+ // CI step qualifies the FULL floor;
31
+ // 9. every gate a committed mutant NAMES also runs inside `check:full` (or says
32
+ // in its own prose why it deliberately does not) — a gate reachable only
33
+ // through qualification's control-pre puts a whole defect class behind the
34
+ // 28-minute body;
35
+ // 10. the CI push trigger is filtered to BRANCH refs, so pushing a release tag
36
+ // does not rebuild a SHA its branch run already built.
29
37
  //
30
38
  // Cells 5-6 SPAWN the real subcommands rather than reasoning about them: the
31
39
  // whole defect was an assumption about what a step would do, so an assumption is
@@ -329,7 +337,7 @@ function runSubcommand(sub: string, env: Record<string, string | undefined>): {
329
337
  // check-gate-qualification left the default check chains (operator
330
338
  // inner-loop cost, 2026-08 subtraction). That move is a gate/release
331
339
  // contract: the step must stay REACHABLE on the axes that now own it — the
332
- // CI check job on every push and release_gate as its own MUST step — and
340
+ // CI check job on every branch push and release_gate as its own MUST step — and
333
341
  // must not silently return to the default chain. Without this cell,
334
342
  // deleting the release_gate qualification block or the CI line leaves every
335
343
  // focused gate green while a cut quietly loses its discriminating-power
@@ -374,7 +382,7 @@ function runSubcommand(sub: string, env: Record<string, string | undefined>): {
374
382
  !qualGateBody.includes('results+=("FAIL check-gate-qualification")')
375
383
  )
376
384
  holes.push("the release_gate qualification step does not wire PASS/FAIL into the MUST counters");
377
- if (!verifyDoc.includes("in the CI `check` job on every push, and as a release-gate MUST step"))
385
+ if (!verifyDoc.includes("in the CI `check` job on every branch push, and as a release-gate MUST step"))
378
386
  holes.push("VERIFY.md no longer names the owners of the moved qualification step");
379
387
  assert.ok(
380
388
  holes.length === 0,
@@ -406,6 +414,153 @@ function runSubcommand(sub: string, env: Record<string, string | undefined>): {
406
414
  );
407
415
  }
408
416
 
417
+ // ===========================================================================
418
+ // 9. Every gate a mutant manifest NAMES runs inside `pnpm run check:full`
419
+ //
420
+ // The measurement behind this (#99 B-3/B-4): across 549 CI runs the mutant-
421
+ // EXECUTION half of qualification never once produced a SURVIVED / WRONG-REASON /
422
+ // MUTANT-STALE / HANG. What it did catch twice was a gate that was already red on a
423
+ // clean tree — CONTROL-PRE — and that class is caught 5.4 minutes earlier, and for
424
+ // free, by any gate the deterministic floor already runs. So a mutant-named gate
425
+ // that sits OUTSIDE `check:full` is the one place where a baseline break can only
426
+ // be found by paying the 28-minute body. `check-omp-birth-hook` was exactly that
427
+ // gate, reachable in the whole repo only through qualification's control-pre.
428
+ //
429
+ // The exclusion arm is not a loophole: `scripts/check-setup-qualification.sh` is a
430
+ // mutation-attribution oracle the manifests invoke DIRECTLY and its own header says
431
+ // it is deliberately outside every tier. Like cell 7, the excuse is checked against
432
+ // the text an operator can read, never against a list only this gate believes in.
433
+ //
434
+ // ONE PREMISE, stated because it is invisible from here: the script-path join below
435
+ // ignores MODE FLAGS in a gate argv. The `--attribution-self-test` gate therefore
436
+ // counts as covered by the `check-gate-manifests` arm only because
437
+ // `checkVitestAttribution()` runs UNCONDITIONALLY, before that flag is read
438
+ // (`scripts/check-gate-qualification.ts`). Move that call behind a flag and this
439
+ // cell stays green while saying something false.
440
+ // ===========================================================================
441
+ {
442
+ const pkg = JSON.parse(readFileSync(join(REPO_DIR, "package.json"), "utf8")) as { scripts: Record<string, string> };
443
+ // The floor is composed (#70): `check:full` names group scripts, and a group script
444
+ // may name further ones. Expand transitively rather than hard-coding the tier list.
445
+ const expand = (name: string, seen: Set<string>): string => {
446
+ if (seen.has(name)) return "";
447
+ seen.add(name);
448
+ const body = pkg.scripts[name] ?? "";
449
+ let out = body;
450
+ for (const ref of body.matchAll(/pnpm run ([\w:.-]+)/g)) out += ` ${expand(ref[1], seen)}`;
451
+ const elapsed = /check-elapsed\.sh\s+[\w:.-]+\s+(.+)$/.exec(body);
452
+ if (elapsed) for (const g of elapsed[1].trim().split(/\s+/)) out += ` ${expand(g, seen)}`;
453
+ return out;
454
+ };
455
+ const floorText = expand("check:full", new Set());
456
+ const floorSubcommands = new Set([...floorText.matchAll(/\.\/run\.sh ([\w:.-]+)/g)].map((m) => m[1]));
457
+
458
+ // run.sh case arms, so a gate invoked by SCRIPT PATH can be joined to the floor
459
+ // subcommand that runs that same script.
460
+ const arms = new Map<string, string>();
461
+ {
462
+ let names: string[] = [];
463
+ let body: string[] = [];
464
+ for (const line of readFileSync(join(REPO_DIR, "run.sh"), "utf8").split("\n")) {
465
+ const head = /^ {2}([\w|:.-]+)\)$/.exec(line);
466
+ if (head) {
467
+ names = head[1].split("|");
468
+ body = [];
469
+ continue;
470
+ }
471
+ if (line === " ;;") {
472
+ for (const n of names) arms.set(n, body.join("\n"));
473
+ names = [];
474
+ continue;
475
+ }
476
+ if (names.length > 0) body.push(line);
477
+ }
478
+ }
479
+
480
+ const gates = new Map<string, string[]>();
481
+ for (const rel of globSync("scripts/mutants/*.json", { cwd: REPO_DIR }).sort()) {
482
+ const doc = JSON.parse(readFileSync(join(REPO_DIR, rel), "utf8")) as { mutants: Array<{ gate: string[] }> };
483
+ for (const m of doc.mutants) gates.set(m.gate.join(" "), m.gate);
484
+ }
485
+ assert.ok(gates.size >= 40, `expected the committed mutant gate set, found ${gates.size} distinct gate argvs`);
486
+
487
+ // argv → the file that states, in prose an operator reads, why it is outside the floor.
488
+ const DOCUMENTED_OUTSIDE: Record<string, [file: string, sentence: string]> = {
489
+ "bash scripts/check-setup-qualification.sh": [
490
+ "scripts/check-setup-qualification.sh",
491
+ "run.sh subcommand and NOT in any check tier",
492
+ ],
493
+ };
494
+
495
+ const unreached: string[] = [];
496
+ for (const [key, argv] of gates) {
497
+ if (argv[0] === "bash" && argv[1] === "run.sh") {
498
+ if (!floorSubcommands.has(argv[2])) unreached.push(`${key} — run.sh ${argv[2]} is in no check:full group`);
499
+ continue;
500
+ }
501
+ const scriptPath = argv.find((t) => /^scripts\/.+\.(sh|ts|py)$/.test(t));
502
+ if (scriptPath !== undefined && [...floorSubcommands].some((sub) => (arms.get(sub) ?? "").includes(scriptPath))) {
503
+ continue;
504
+ }
505
+ const excused = DOCUMENTED_OUTSIDE[key];
506
+ if (excused === undefined) {
507
+ unreached.push(`${key} — no check:full subcommand invokes ${scriptPath ?? "(no script path in its argv)"}`);
508
+ continue;
509
+ }
510
+ const [file, sentence] = excused;
511
+ if (!readFileSync(join(REPO_DIR, file), "utf8").includes(sentence)) {
512
+ unreached.push(`${key} claims a documented exclusion, but ${file} no longer says "${sentence}"`);
513
+ }
514
+ }
515
+ assert.ok(
516
+ unreached.length === 0,
517
+ "[QK:MUTANT-GATES-INSIDE-FULL-FLOOR] every gate a committed mutant names must also run inside `pnpm run " +
518
+ "check:full`, or state its exclusion where an operator reads it. A gate reachable ONLY through " +
519
+ "qualification's control-pre makes the 28-minute mutant body the only thing that can notice it going red on " +
520
+ "a clean tree — the one CI class that body has actually caught, and the one the 5.4-minute floor catches for " +
521
+ `free everywhere else. Unreached: ${unreached.join("; ")}`,
522
+ );
523
+ }
524
+
525
+ // ===========================================================================
526
+ // 10. A TAG push rebuilds nothing
527
+ //
528
+ // Measured over this repo's whole history (#99 B-1): 66 of 66 semver tag-push runs
529
+ // rebuilt a SHA some other run had already built, and no tag run ever reported a
530
+ // fact its branch run did not — the single non-green one failed at the same step as
531
+ // its main run, two seconds later. That is 28% of a release window's runner minutes
532
+ // buying a badge on a ref. The exact-SHA evidence a release quotes is the BRANCH
533
+ // run, which is also the run `entwurf-release` land mode reports.
534
+ //
535
+ // So `on.push` must carry a REF FILTER that selects branches. `branches:` alone is
536
+ // what makes a tag push create no run at all; a `tags:` key would put them back.
537
+ // ===========================================================================
538
+ {
539
+ const ciYml = readFileSync(join(REPO_DIR, ".github/workflows/ci.yml"), "utf8");
540
+ const onAt = ciYml.indexOf("\non:\n");
541
+ const envAt = ciYml.indexOf("\nenv:\n");
542
+ assert.ok(onAt !== -1 && envAt > onAt, "located the workflow `on:` block");
543
+ const onLines = ciYml.slice(onAt, envAt).split("\n");
544
+ const pushAt = onLines.findIndex((l) => /^ {2}push:\s*$/.test(l));
545
+ const nextKeyAt = onLines.findIndex((l, i) => i > pushAt && /^ {2}\S/.test(l));
546
+ const pushBody = onLines
547
+ .slice(pushAt + 1, nextKeyAt === -1 ? undefined : nextKeyAt)
548
+ .filter((l) => l.trim() !== "" && !/^\s*#/.test(l));
549
+ const gaps: string[] = [];
550
+ if (pushAt === -1) gaps.push("the `on:` block has no `push:` trigger");
551
+ if (!pushBody.some((l) => /^ {4}branches(-ignore)?:/.test(l)))
552
+ gaps.push(`the push trigger carries no branch ref filter (body: ${JSON.stringify(pushBody)})`);
553
+ if (pushBody.some((l) => /^ {4}tags(-ignore)?:/.test(l)))
554
+ gaps.push("the push trigger names tags — a tag push would create a second run for a SHA already built");
555
+ assert.ok(
556
+ gaps.length === 0,
557
+ "[QK:CI-TAG-PUSH-NOT-REBUILT] the CI `push` trigger must be filtered to BRANCH refs, so pushing a release tag " +
558
+ "creates no run: 66/66 semver tag runs in this repo's history rebuilt an already-built SHA and none ever " +
559
+ "reported a fact its branch run had not, while costing 28% of a release window's runner minutes. Dropping " +
560
+ `the filter does not add evidence, it duplicates it. Broken: ${gaps.join("; ")}`,
561
+ );
562
+ }
563
+
409
564
  // ===========================================================================
410
565
  // The integrated mux lifecycle is a MUST, and the focused fresh-call LIVE is not
411
566
  //
@@ -501,5 +656,7 @@ console.log(
501
656
  "LIVE smoke is either wired into release_gate or excluded by a sentence the docs still carry; and the moved " +
502
657
  "check-gate-qualification stays reachable on its owners (absent from the default chain, exactly once in CI, " +
503
658
  "exactly once as a release-gate MUST step) and the CI step qualifies the FULL floor, which runs before it; and " +
659
+ "every gate a committed mutant names is itself inside check:full or states its exclusion in prose an operator " +
660
+ "reads; and the CI push trigger is filtered to branch refs, so a release tag creates no duplicate run; and " +
504
661
  "the operator's CONFIGURED bridge invocation is booted exactly once through run_step, before the ACP LIVE tier",
505
662
  );
@@ -736,19 +736,43 @@ writeMetaReceiverMarker({
736
736
  receiversDir,
737
737
  });
738
738
 
739
- // RECEIVER — a DIFFERENT citizen, armed by a live owner pid (this process). An unarmed
740
- // target is correctly fail-closed as mailbox-undeliverable, so an unarmed probe could
741
- // never separate "the artifact cannot read its registry" from "nobody is listening".
739
+ // RECEIVER — a DIFFERENT citizen, armed by a DIFFERENT live owner pid. An unarmed target
740
+ // is correctly fail-closed as mailbox-undeliverable, so an unarmed probe could never
741
+ // separate "the artifact cannot read its registry" from "nobody is listening".
742
+ //
743
+ // The owner is a spawned idle process, not this one (#101): a claude-code watch owner
744
+ // serves exactly one garden at a time, and deliverability asks whether that owner's sender
745
+ // marker still names the garden being addressed. This probe's own pid already carries the
746
+ // SENDER citizen's marker, so arming the receiver under it would model a session switch —
747
+ // a retired watch — and the probe would report a delivery failure that is really the
748
+ // fixture. It cannot be the probe's parent either: the bridge resolves its own sender
749
+ // identity through its ancestry, and an ancestor holding two gardens is the "ambiguous
750
+ // sender identity" refusal. A live pid outside that ancestry is what a second native CLI
751
+ // actually looks like.
752
+ const receiverOwner = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 300000)'], { stdio: 'ignore' });
753
+ const receiverOwnerPid = receiverOwner.pid;
754
+ if (typeof receiverOwnerPid !== 'number') {
755
+ console.error('could not spawn the stand-in receiver owner process');
756
+ process.exit(1);
757
+ }
742
758
  const receiver = upsertMetaSession({
743
759
  input: { backend: 'claude-code', nativeSessionId: `doctor-delivery-receiver-${process.pid}`, cwd: tmp },
744
760
  dir: sessionsDir,
745
761
  });
746
762
  const gid = receiver.record.gardenId;
763
+ writeMetaSenderMarker({
764
+ backend: 'claude-code',
765
+ gardenId: gid,
766
+ nativeSessionId: receiver.record.nativeSessionId,
767
+ cwd: tmp,
768
+ ownerPid: receiverOwnerPid,
769
+ sendersDir,
770
+ });
747
771
  writeMetaReceiverMarker({
748
772
  gardenId: gid,
749
773
  backend: 'claude-code',
750
774
  nativeSessionId: receiver.record.nativeSessionId,
751
- ownerPid: process.pid,
775
+ ownerPid: receiverOwnerPid,
752
776
  armProvenance: 'session-start',
753
777
  receiversDir,
754
778
  });
@@ -845,6 +869,7 @@ try {
845
869
  bail(String(e instanceof Error ? e.message : e));
846
870
  } finally {
847
871
  try { child?.kill('SIGTERM'); } catch {}
872
+ try { receiverOwner.kill('SIGTERM'); } catch {}
848
873
  await fsp.rm(tmp, { recursive: true, force: true });
849
874
  }
850
875
  if (verdict === 0) console.log('delivered: one .msg landed + doorbell poked; seeded meta-sender identity joined');
@@ -19,7 +19,7 @@
19
19
  "subject": "scripts/agy-bridge.sh",
20
20
  "find": [" if command_boots \"$invocation\"; then"],
21
21
  "replace": [" if true; then"],
22
- "gate": ["bash", "scripts/smoke-agy-install-state.sh"],
22
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
23
23
  "timeoutSeconds": 300,
24
24
  "signature": "[QK:AGY-DOCTOR-BOOT-NEGATIVE]",
25
25
  "signatureSource": "scripts/smoke-agy-install-state.sh"
@@ -43,7 +43,7 @@
43
43
  "subject": "scripts/agy-bridge-config.py",
44
44
  "find": [" args = server.get(\"args\", [])"],
45
45
  "replace": [" args = []"],
46
- "gate": ["bash", "scripts/smoke-agy-install-state.sh"],
46
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
47
47
  "timeoutSeconds": 300,
48
48
  "signature": "[QK:AGY-DOCTOR-PROBES-ARGS]",
49
49
  "signatureSource": "scripts/smoke-agy-install-state.sh"
@@ -54,7 +54,7 @@
54
54
  "subject": "scripts/agy-bridge-config.py",
55
55
  "find": [" env = server.get(\"env\", {})"],
56
56
  "replace": [" env = {}"],
57
- "gate": ["bash", "scripts/smoke-agy-install-state.sh"],
57
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
58
58
  "timeoutSeconds": 300,
59
59
  "signature": "[QK:AGY-DOCTOR-PROBES-ENV]",
60
60
  "signatureSource": "scripts/smoke-agy-install-state.sh"
@@ -0,0 +1,28 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "gate-qualification",
4
+ "mutants": [
5
+ {
6
+ "claim": "MANIFEST-SET-INTEGRITY-REFUSED",
7
+ "title": "a committed manifest points a claim at a gate source that does not carry its token — the cross-manifest contract that makes a KILLED verdict mean anything is broken, and the run must refuse rather than certify kills whose signature nothing can attribute (the 08-20 CI catch, run 32421631735, which died in validateManifestSet in 5s)",
8
+ "subject": "scripts/mutants/probe-ordering.json",
9
+ "find": ["\"signatureSource\": \"scripts/check-probe-ordering.ts\""],
10
+ "replace": ["\"signatureSource\": \"run.sh\""],
11
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
12
+ "timeoutSeconds": 120,
13
+ "signature": "[QK:MANIFEST-SET-INTEGRITY-REFUSED]",
14
+ "signatureSource": "scripts/check-gate-qualification.ts"
15
+ },
16
+ {
17
+ "claim": "LANE-INVENTORY-DECLARED",
18
+ "title": "a lane is renamed in its manifest and the declared inventory is not moved with it — the vacuous-green shape where the run reports n/n killed over whatever set happened to be on disk (the 08-21 and 08-28 CI catches, runs 32450955048 and 33154263432, both dead at the inventory assertion in under 5s)",
19
+ "subject": "scripts/mutants/capability-cache.json",
20
+ "find": ["\"lane\": \"capability-cache\","],
21
+ "replace": ["\"lane\": \"capability-cache-renamed\","],
22
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
23
+ "timeoutSeconds": 120,
24
+ "signature": "[QK:LANE-INVENTORY-DECLARED]",
25
+ "signatureSource": "scripts/check-gate-qualification.ts"
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,223 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "meta-hook-session-switch",
4
+ "mutants": [
5
+ {
6
+ "claim": "MHSS-RETIRE-PREV-GARDEN",
7
+ "title": "a hook that never retires the garden its pid stopped serving leaves the abandoned startup session advertising a doorbell nobody holds — the abandoned-session registration measured on oracle, where a real message sat unread in its mailbox for an hour",
8
+ "subject": "pi-extensions/meta-bridge-hook.ts",
9
+ "find": ["\t\tif (previous && previous.gardenId !== gardenId) {"],
10
+ "replace": ["\t\tif (false && previous && previous.gardenId !== gardenId) {"],
11
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
12
+ "timeoutSeconds": 300,
13
+ "signature": "[QK:MHSS-RETIRE-PREV-GARDEN]",
14
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
15
+ },
16
+ {
17
+ "claim": "MHSS-RETIRE-KEEPS-CURRENT",
18
+ "title": "retiring on ANY previous sender marker instead of a CHANGED one disarms the live session's own inbox on its next keystroke — the repair eating the citizen it exists to protect, which is worse than the defect",
19
+ "subject": "pi-extensions/meta-bridge-hook.ts",
20
+ "find": ["\t\tif (previous && previous.gardenId !== gardenId) {"],
21
+ "replace": ["\t\tif (previous) {"],
22
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
23
+ "timeoutSeconds": 300,
24
+ "signature": "[QK:MHSS-RETIRE-KEEPS-CURRENT]",
25
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
26
+ },
27
+ {
28
+ "claim": "MHSS-RETIRE-ONLY-OWN-OWNER",
29
+ "title": "dropping the owner-pid guard lets one session's SessionStart delete a marker another LIVE process holds — a silent cross-session disarm, not a stale-marker cleanup",
30
+ "subject": "pi-extensions/lib/meta-session.ts",
31
+ "find": ["\t\tif (!marker || marker.ownerPid !== opts.ownerPid) return false;"],
32
+ "replace": ["\t\tif (!marker) return false;"],
33
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
34
+ "timeoutSeconds": 300,
35
+ "signature": "[QK:MHSS-RETIRE-ONLY-OWN-OWNER]",
36
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
37
+ },
38
+ {
39
+ "claim": "MHSS-SOURCE-LOGGED",
40
+ "title": "dropping the vendor's `source` from the log leaves this host with no receipt for WHY a SessionStart fired — the exact evidence the #101 diagnosis had to inherit from vendor docs because nothing here recorded it",
41
+ "subject": "pi-extensions/meta-bridge-hook.ts",
42
+ "find": [
43
+ "\tconst source = typeof env.source === \"string\" && env.source.length > 0 ? env.source : \"(unset)\";"
44
+ ],
45
+ "replace": ["\tconst source = \"(unset)\";"],
46
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
47
+ "timeoutSeconds": 300,
48
+ "signature": "[QK:MHSS-SOURCE-LOGGED]",
49
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
50
+ },
51
+ {
52
+ "claim": "MHSS-SENDER-JOIN-MEASURED",
53
+ "title": "a join that always answers yes turns the receiver marker back into proof of something it never proved — that the process holding it is still serving THIS garden",
54
+ "subject": "pi-extensions/lib/entwurf-deliverability.ts",
55
+ "find": [
56
+ "\treturn !!senderMarker && senderMarker.gardenId === marker.gardenId && senderMarker.backend === marker.backend;"
57
+ ],
58
+ "replace": ["\treturn true;"],
59
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
60
+ "timeoutSeconds": 300,
61
+ "signature": "[QK:MHSS-SENDER-JOIN-MEASURED]",
62
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
63
+ },
64
+ {
65
+ "claim": "MHSS-SEAM-USES-JOIN",
66
+ "title": "restoring `watchArmed` as a copy of the identity match at the production seam re-ships the false deliverable even with the join intact — the shipped line that let a message be enqueued into a void",
67
+ "subject": "pi-extensions/lib/entwurf-v2-production.ts",
68
+ "find": [
69
+ "\t\tconst { ownerAlive, watchArmed } = resolveMailboxReceiverFacts(identity, {\n\t\t\treadReceiverMarker: io.readReceiverMarker,\n\t\t\treadSenderMarker: io.readSenderMarker,\n\t\t});"
70
+ ],
71
+ "replace": [
72
+ "\t\tconst { ownerAlive } = resolveMailboxReceiverFacts(identity, {\n\t\t\treadReceiverMarker: io.readReceiverMarker,\n\t\t\treadSenderMarker: io.readSenderMarker,\n\t\t});\n\t\tconst watchArmed = ownerAlive;"
73
+ ],
74
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
75
+ "timeoutSeconds": 300,
76
+ "signature": "[QK:MHSS-SEAM-USES-JOIN]",
77
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
78
+ },
79
+ {
80
+ "claim": "MHSS-REJECT-CARRIES-REASON",
81
+ "title": "dropping the deliverability reason at the decider boundary hands the caller a verdict with no cause — the bare `mailbox-undeliverable` that sent a sibling looking for a dead session when the session was alive and had moved",
82
+ "subject": "pi-extensions/lib/entwurf-v2-decider.ts",
83
+ "find": [
84
+ "\t\t\treturn deliverability.deliverable\n\t\t\t\t? reject(receipt)\n\t\t\t\t: reject(receipt, { kind: \"mailbox-undeliverable\", reason: deliverability.reason });"
85
+ ],
86
+ "replace": ["\t\t\treturn reject(receipt);"],
87
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
88
+ "timeoutSeconds": 300,
89
+ "signature": "[QK:MHSS-REJECT-CARRIES-REASON]",
90
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
91
+ },
92
+ {
93
+ "claim": "MHSS-REASON-RENDERED",
94
+ "title": "a surface that carries the diagnostic and prints nothing is the same silence one layer up — the operator still reads only `mailbox-undeliverable`",
95
+ "subject": "pi-extensions/lib/entwurf-v2-surface.ts",
96
+ "find": ["\t\t\t\ttext += `\\n mailbox-undeliverable: ${result.diagnostic.reason}`;"],
97
+ "replace": ["\t\t\t\ttext += \"\";"],
98
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
99
+ "timeoutSeconds": 300,
100
+ "signature": "[QK:MHSS-REASON-RENDERED]",
101
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
102
+ },
103
+ {
104
+ "claim": "MHSS-PEERS-RECEIVER-OBSERVED",
105
+ "title": "collapsing `inactive` into `none` erases the distinction a reader needs — a watch that was armed and is no longer valid looks exactly like one that was never armed, which is the listing telling a caller nothing again",
106
+ "subject": "pi-extensions/lib/entwurf-peer-observe.ts",
107
+ "find": ["\treturn fs.existsSync(metaReceiverMarkerPath(identity.gardenId)) ? \"inactive\" : \"none\";"],
108
+ "replace": ["\treturn \"none\";"],
109
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
110
+ "timeoutSeconds": 300,
111
+ "signature": "[QK:MHSS-PEERS-RECEIVER-OBSERVED]",
112
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
113
+ },
114
+ {
115
+ "claim": "MHSS-PEERS-TRANSCRIPT-OBSERVED",
116
+ "title": "reporting every transcript as present hides exactly the phantom this column exists to show: a registration minted at SessionStart with no conversation ever written behind it",
117
+ "subject": "pi-extensions/lib/entwurf-peer-observe.ts",
118
+ "find": [
119
+ "\t\treturn identity.transcriptPath && fs.existsSync(identity.transcriptPath) ? \"exists\" : \"absent\";"
120
+ ],
121
+ "replace": ["\t\treturn \"exists\";"],
122
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
123
+ "timeoutSeconds": 300,
124
+ "signature": "[QK:MHSS-PEERS-TRANSCRIPT-OBSERVED]",
125
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
126
+ },
127
+ {
128
+ "claim": "MHSS-PEERS-COLUMNS-RENDERED",
129
+ "title": "measuring both axes and printing neither returns the listing to two identical rows for a live citizen and a phantom — the surface gap that made a sibling dispatch into a void",
130
+ "subject": "pi-extensions/lib/entwurf-peers-render.ts",
131
+ "find": [
132
+ "\t\t`- ${p.gardenId} backend=${p.backend} liveness=${p.liveness} receiver=${p.receiver} ` +\n\t\t`transcript=${p.transcript} cwd=${p.cwd} model=${model}`"
133
+ ],
134
+ "replace": [
135
+ "\t\t`- ${p.gardenId} backend=${p.backend} liveness=${p.liveness} ` + `cwd=${p.cwd} model=${model}`"
136
+ ],
137
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
138
+ "timeoutSeconds": 300,
139
+ "signature": "[QK:MHSS-PEERS-COLUMNS-RENDERED]",
140
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
141
+ },
142
+ {
143
+ "claim": "MHSS-RETIRE-ARM-CAPABLE-ONLY",
144
+ "title": "retiring from an event that cannot arm subtracts a doorbell with nothing in the same run able to restore one — UserPromptSubmit cannot emit watchPaths, so a retirement reached from there is a one-way disarm",
145
+ "subject": "pi-extensions/meta-bridge-hook.ts",
146
+ "find": [
147
+ "\t\tconst previous =\n\t\t\tarmProvenanceFor(eventName) !== null\n\t\t\t\t? readMetaSenderMarker({ backend: \"claude-code\", ownerPid, verifyOwner: false })\n\t\t\t\t: null;"
148
+ ],
149
+ "replace": [
150
+ "\t\tconst previous = readMetaSenderMarker({ backend: \"claude-code\", ownerPid, verifyOwner: false });"
151
+ ],
152
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
153
+ "timeoutSeconds": 300,
154
+ "signature": "[QK:MHSS-RETIRE-ARM-CAPABLE-ONLY]",
155
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
156
+ },
157
+ {
158
+ "claim": "MHSS-RECORDS-SURVIVE",
159
+ "title": "a retirement that takes the RECORD with the marker deletes identity to clean up presence — the store-certification and fresh-cut rules exist because a record is the only address a citizen has",
160
+ "subject": "pi-extensions/meta-bridge-hook.ts",
161
+ "find": ["\t\t\tconst retired = removeMetaReceiverMarker({ gardenId: previous.gardenId, ownerPid });"],
162
+ "replace": [
163
+ "\t\t\tconst retired = removeMetaReceiverMarker({ gardenId: previous.gardenId, ownerPid });\n\t\t\tfs.rmSync(path.join(defaultMetaSessionsDir(), `${previous.gardenId}.meta.json`), { force: true });"
164
+ ],
165
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
166
+ "timeoutSeconds": 300,
167
+ "signature": "[QK:MHSS-RECORDS-SURVIVE]",
168
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
169
+ },
170
+ {
171
+ "claim": "MHSS-EXACTLY-ONE-DELIVERABLE",
172
+ "title": "forcing the watch fact closed refuses the citizen the operator is actually in — fail-closed is not free when it closes on every live session too",
173
+ "subject": "pi-extensions/lib/entwurf-v2-production.ts",
174
+ "find": ["\t\t\townerAlive,\n\t\t\twatchArmed,\n\t\t});"],
175
+ "replace": ["\t\t\townerAlive,\n\t\t\twatchArmed: false,\n\t\t});"],
176
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
177
+ "timeoutSeconds": 300,
178
+ "signature": "[QK:MHSS-EXACTLY-ONE-DELIVERABLE]",
179
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
180
+ },
181
+ {
182
+ "claim": "MHSS-JOIN-SCOPE-OWNERKIND",
183
+ "title": "admitting copilot-extension into the sender-marker join makes every Copilot citizen permanently mailbox-undeliverable: its watch owner is a forked child whose pid never carries a sender marker, so the join can only ever answer no",
184
+ "subject": "pi-extensions/lib/entwurf-deliverability.ts",
185
+ "find": ["export const SENDER_JOINED_RECEIVER_OWNER_KINDS: readonly string[] = [\"claude-code-cli\"];"],
186
+ "replace": [
187
+ "export const SENDER_JOINED_RECEIVER_OWNER_KINDS: readonly string[] = [\"claude-code-cli\", \"copilot-extension\"];"
188
+ ],
189
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
190
+ "timeoutSeconds": 300,
191
+ "signature": "[QK:MHSS-JOIN-SCOPE-OWNERKIND]",
192
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
193
+ },
194
+ {
195
+ "claim": "MHSS-SENDER-START-KEY-VERIFIED",
196
+ "title": "reading the sender marker without the pid-reuse guard lets a dead session's leftover file answer \"which garden does this pid serve now?\" — the join then vouches for a watch nobody holds",
197
+ "subject": "pi-extensions/lib/entwurf-v2-production.ts",
198
+ "find": [
199
+ "\t\t\t((backend: string, ownerPid: number) => readMetaSenderMarker({ backend: requireBackend(backend), ownerPid })),"
200
+ ],
201
+ "replace": [
202
+ "\t\t\t((backend: string, ownerPid: number) =>\n\t\t\t\treadMetaSenderMarker({ backend: requireBackend(backend), ownerPid, verifyOwner: false })),"
203
+ ],
204
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
205
+ "timeoutSeconds": 300,
206
+ "signature": "[QK:MHSS-SENDER-START-KEY-VERIFIED]",
207
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
208
+ },
209
+ {
210
+ "claim": "MHSS-JOIN-BACKEND-EQUALITY",
211
+ "title": "comparing only the garden id lets a marker written by another backend under the same pid vouch for this receiver — two harnesses can key markers under one process",
212
+ "subject": "pi-extensions/lib/entwurf-deliverability.ts",
213
+ "find": [
214
+ "\treturn !!senderMarker && senderMarker.gardenId === marker.gardenId && senderMarker.backend === marker.backend;"
215
+ ],
216
+ "replace": ["\treturn !!senderMarker && senderMarker.gardenId === marker.gardenId;"],
217
+ "gate": ["bash", "run.sh", "check-meta-hook-session-switch"],
218
+ "timeoutSeconds": 300,
219
+ "signature": "[QK:MHSS-JOIN-BACKEND-EQUALITY]",
220
+ "signatureSource": "scripts/check-meta-hook-session-switch.ts"
221
+ }
222
+ ]
223
+ }
@@ -4,14 +4,25 @@
4
4
  "mutants": [
5
5
  {
6
6
  "claim": "PACK-INSTALL-PIN-MATCHER-BOUNDED",
7
- "title": "the pin-leak matcher loses its version boundary and blesses substring lookalikes — @0.84.30 reads as the pinned 0.84.3 and the gate announces a verified tree it never verified (independent review, 2026-08-25)",
7
+ "title": "the pin-leak matcher loses its version boundary and blesses substring lookalikes — @0.85.10 reads as the pinned 0.85.1 and the gate announces a verified tree it never verified (independent review, 2026-08-25)",
8
8
  "subject": "run.sh",
9
- "find": [" grep '^@earendil-works+pi-' | grep -Ev '@0\\.84\\.4(_|$)' || true"],
10
- "replace": [" grep '^@earendil-works+pi-' | grep -v '@0\\.84\\.4' || true"],
9
+ "find": [" grep '^@earendil-works+' | grep -Ev '@0\\.85\\.1(_|$)' || true"],
10
+ "replace": [" grep '^@earendil-works+' | grep -v '@0\\.85\\.1' || true"],
11
11
  "gate": ["bash", "run.sh", "check-pack-pin-matcher"],
12
12
  "timeoutSeconds": 120,
13
13
  "signature": "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED]",
14
14
  "signatureSource": "run.sh"
15
+ },
16
+ {
17
+ "claim": "PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE",
18
+ "title": "the pin-leak matcher narrows back to the pi-* families and goes blind to a non-pi member of the same runtime closure — @earendil-works/chord (new in pi 0.85.0, a runtime dependency of pi-coding-agent/pi-agent-core/pi-client/pi-protocol) floats unpinned while the gate prints a verified pin (measured 2026-09-06 on a real 0.85.1 install tree)",
19
+ "subject": "run.sh",
20
+ "find": [" grep '^@earendil-works+' | grep -Ev '@0\\.85\\.1(_|$)' || true"],
21
+ "replace": [" grep '^@earendil-works+pi-' | grep -Ev '@0\\.85\\.1(_|$)' || true"],
22
+ "gate": ["bash", "run.sh", "check-pack-pin-matcher"],
23
+ "timeoutSeconds": 120,
24
+ "signature": "[QK:PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE]",
25
+ "signatureSource": "run.sh"
15
26
  }
16
27
  ]
17
28
  }
@@ -136,6 +136,28 @@
136
136
  "signature": "[QK:MUX-LIFECYCLE-IS-RELEASE-MUST]",
137
137
  "signatureSource": "scripts/check-release-gate-outcomes.ts"
138
138
  },
139
+ {
140
+ "claim": "MUTANT-GATES-INSIDE-FULL-FLOOR",
141
+ "title": "a gate a committed mutant names drops out of check:full — the #99 B-4 shape, where check-omp-birth-hook was reachable in the whole repo only through qualification's control-pre, so the one CI class that half has ever caught (a gate already red on a clean tree) could be found only by paying the 28-minute mutant body",
142
+ "subject": "package.json",
143
+ "find": [" && ./run.sh check-omp-birth-hook"],
144
+ "replace": [""],
145
+ "gate": ["bash", "run.sh", "check-release-gate-outcomes"],
146
+ "timeoutSeconds": 180,
147
+ "signature": "[QK:MUTANT-GATES-INSIDE-FULL-FLOOR]",
148
+ "signatureSource": "scripts/check-release-gate-outcomes.ts"
149
+ },
150
+ {
151
+ "claim": "CI-TAG-PUSH-NOT-REBUILT",
152
+ "title": "the CI push trigger loses its branch ref filter, so every release tag rebuilds a SHA its branch run already built — 66/66 historically, 28% of a release window's runner minutes, and not once a fact the branch run had not already reported",
153
+ "subject": ".github/workflows/ci.yml",
154
+ "find": [" branches: ['**']"],
155
+ "replace": [" # the branch ref filter was dropped, so a tag push rebuilds the SHA again"],
156
+ "gate": ["bash", "run.sh", "check-release-gate-outcomes"],
157
+ "timeoutSeconds": 180,
158
+ "signature": "[QK:CI-TAG-PUSH-NOT-REBUILT]",
159
+ "signatureSource": "scripts/check-release-gate-outcomes.ts"
160
+ },
139
161
  {
140
162
  "claim": "PI-DOCTOR-IS-RELEASE-MUST",
141
163
  "title": "the pi-provider boot doctor is quietly dropped from the gate — the operator's CONFIGURED bridge invocation is never booted, so a 127 launcher (the #81 relocated cmd-shim class) stays invisible until smoke-acp-bundled-mcp-live sixteen LIVE steps and real model spend later",