@junghanacs/entwurf 0.24.0 → 0.25.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 (75) hide show
  1. package/CHANGELOG.md +564 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/DELIVERY.md +24 -1
  4. package/README.md +2 -2
  5. package/VERIFY.md +20 -2
  6. package/demo/README.md +5 -2
  7. package/docs/acp-backend-rail.md +49 -2
  8. package/docs/setup-clean-host.md +4 -4
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +5 -4
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-send-receipt.js +101 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -2
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +17 -1
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +4 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +53 -6
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +38 -3
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +13 -3
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +11 -0
  18. package/mcp/entwurf-bridge/dist/protocol.js +0 -7
  19. package/mcp/entwurf-bridge/dist/scripts/doctor-pi-provider.js +40 -3
  20. package/mcp/entwurf-bridge/dist/scripts/herdr-rails.mjs +72 -0
  21. package/mcp/entwurf-bridge/src/index.ts +5 -4
  22. package/package.json +10 -9
  23. package/pi-extensions/entwurf-control.ts +27 -154
  24. package/pi-extensions/lib/compaction-send-guard.ts +5 -3
  25. package/pi-extensions/lib/control-send-receipt.ts +108 -0
  26. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
  27. package/pi-extensions/lib/entwurf-v2-contract.ts +6 -2
  28. package/pi-extensions/lib/entwurf-v2-production.ts +19 -1
  29. package/pi-extensions/lib/entwurf-v2-runner.ts +23 -1
  30. package/pi-extensions/lib/entwurf-v2-send.ts +82 -6
  31. package/pi-extensions/lib/entwurf-v2-surface.ts +42 -3
  32. package/pi-extensions/lib/herdr-fresh-call.ts +13 -3
  33. package/pi-extensions/lib/herdr-placement.ts +11 -0
  34. package/protocol.js +0 -8
  35. package/run.sh +202 -33
  36. package/scripts/check-entwurf-v2-surface.ts +289 -10
  37. package/scripts/check-gate-qualification.ts +2354 -11
  38. package/scripts/check-herdr-fresh-call.ts +10 -2
  39. package/scripts/check-herdr-placement.ts +51 -0
  40. package/scripts/check-herdr-plugin-build.ts +11 -4
  41. package/scripts/check-herdr-plugin.ts +745 -38
  42. package/scripts/check-mux-launcher-fence.ts +37 -0
  43. package/scripts/check-pi-launch.ts +7 -4
  44. package/scripts/check-release-gate-outcomes.ts +165 -0
  45. package/scripts/doctor-pi-provider.ts +44 -4
  46. package/scripts/herdr-rails.mjs +75 -0
  47. package/scripts/lib/qualification-acceptance.ts +146 -0
  48. package/scripts/lib/qualification-artifact.ts +359 -0
  49. package/scripts/lib/qualification-collection.ts +384 -0
  50. package/scripts/lib/qualification-gh.ts +231 -0
  51. package/scripts/lib/qualification-ledger.ts +669 -0
  52. package/scripts/lib/qualification-oracle-core.ts +279 -0
  53. package/scripts/lib/qualification-receipt.ts +561 -0
  54. package/scripts/lib/qualification-release.ts +271 -0
  55. package/scripts/meta-bridge-state.py +18 -1
  56. package/scripts/mutants/bridge-command-boot.json +38 -0
  57. package/scripts/mutants/control-send-receipt.json +235 -0
  58. package/scripts/mutants/herdr-fresh-call.json +4 -4
  59. package/scripts/mutants/herdr-plugin-build.json +7 -11
  60. package/scripts/mutants/herdr-plugin.json +177 -9
  61. package/scripts/mutants/meta-retire.json +22 -0
  62. package/scripts/mutants/pack-install.json +4 -4
  63. package/scripts/mutants/pi-floor.json +17 -0
  64. package/scripts/mutants/qualification-ledger.json +489 -0
  65. package/scripts/mutants/release-gate.json +48 -0
  66. package/scripts/mutants/retired-sent-surface.json +32 -0
  67. package/scripts/mutants/v2-surface.json +7 -7
  68. package/scripts/qualification-oracle.ts +124 -0
  69. package/scripts/smoke-codex-fresh-live.ts +94 -13
  70. package/scripts/smoke-herdr-fresh-call-live.ts +15 -8
  71. package/scripts/smoke-herdr-raw-install-live.sh +66 -4
  72. package/scripts/smoke-meta-install-state.sh +107 -1
  73. package/scripts/smoke-mux-lifecycle-live.ts +9 -2
  74. package/scripts/smoke-pi-provider-state.sh +106 -0
  75. package/scripts/smoke-setup-verdict.sh +18 -2
package/run.sh CHANGED
@@ -170,7 +170,7 @@ Usage:
170
170
  ./run.sh smoke-codex-fresh-live # #95 RELEASE MUST: record-backed receipt fixture -> PUBLIC initial Pi fresh/callback in a session S that is NOT the app-server's -> Pi PUBLIC omitted-placement Codex fresh/exact callback, also in S -> Pi-to-Codex addressed native-push v2 -> Codex PUBLIC omitted-placement AND omitted-cwd fresh Pi/exact callback IN S beside its own TUI (lane B claim: the app-server env names another session, so S can only come from the caller's pane title; lane C claim: no cwd was asked for, so the directory can only come from the Codex caller's own record — and the Codex thread's own rollout session_meta.cwd, its pane and its record must all be the requested scratch, not the app-server's repo) -> Codex final v2 evidence. Needs LIVE=1 + ENTWURF_CODEX_APP_SERVER_PID + ENTWURF_CODEX_FRESH_MODEL + ENTWURF_CODEX_FRESH_PI_MODEL, and its ONE stable launch directory answered 'Trust' once in a plain codex -C there: codex records its direct consent per exact directory, so a directory with no answer at all opens a consent screen instead of a first turn. Only THIS gate treats that as a precondition and fails up front as codex-launch-cwd-undecided rather than timing out on a callback — an ordinary entwurf_fresh_call only prints the note and opens the window, because a human can answer the screen. The smoke PRINTS that directory as 'launch-cwd <dir>' and repeats it in the failure as a ready-to-run repair command — do not retype it from memory, and do not write it as \$TMPDIR/... (os.tmpdir() falls back to /tmp when TMPDIR is unset, so that spelling names a different directory in the filesystem root). The PID names the operator-owned app-server, which must sit in a DIFFERENT session from S; the smoke checks/prints all four coordinates, never starts/stops/guesses the app-server/session, never reads screen text, interrupts only its exact still-running Codex turn, and cleans only receipt-named window ids
171
171
  ./run.sh smoke-mux-lifecycle-live # RELEASE MUST integrated LIVE lifecycle acceptance for mux, through the REAL MCP surface — OUT of pnpm check, needs LIVE=1 and spends model turns (two pi siblings: native + recorded-ACP provider, each resumed once; one Claude Code sibling). tools/call fresh_call -> nonce callback sender envelope -> v2 control send landing in the sibling's own transcript -> resume_call REFUSED while live (window count unchanged) -> stable-handle close (pane gone, socket dead, record kept) -> dormant delivery refused honestly -> public entwurf_resume_call with LAUNCH and OBSERVATION receipts kept apart, same-gid socket alive, zero new citizens, zero lock residue, resumed pane_start_path == RECORD cwd (separate tmux query), transcript byte-identical across the resume -> v2 recall of the pre-close fact. claude-code resume refused target-not-pi, no window opened and no lock residue. LIVE=1 ./run.sh smoke-mux-lifecycle-live
172
172
  ./run.sh check-meta-facts # deterministic gate for the meta-facts projection (#65): drives the REAL CLI — full-record join, parse-before-uniqueness, no-winner duplicates, drift/symlink/invalid-UTF-8 defects in-band, deterministic bytes, exit contract 0/2/3, dispatch+emit reachability
173
- ./run.sh check-herdr-plugin # deterministic gate for the #116 M2-b herdr plugin (`plugins/herdr/`): STATIC manifest shape for herdr 0.9.0 + the forbidden sections ([[startup]]/[[events]]/[[actions]]/[[link_handlers]]) asserted absent ([[build]] is check-herdr-plugin-build's subject since #116 M3-b3); BEHAVIOURAL drive of the REAL pane entry against a stub `entwurf` and stub HERDR_BIN_PATH that LOG every call — exactly one peer-facts + one agent list per open (counted, not claimed), skip-by-name when Entwurf is absent, four distinct named refusals instead of an empty table, ambiguous never first-wins, diagnostics shown, activity in its own column, and zero writes to the plugin state/config dirs. No herdr binary, no Entwurf install
173
+ ./run.sh check-herdr-plugin # deterministic gate for the #116 M2-b herdr plugin (`plugins/herdr/`): STATIC manifest shape for herdr 0.9.0 + the forbidden sections ([[startup]]/[[events]]/[[actions]]/[[link_handlers]]) asserted absent ([[build]] is check-herdr-plugin-build's subject since #116 M3-b3); BEHAVIOURAL drive of the REAL pane entry against a stub `entwurf` and stub HERDR_BIN_PATH that LOG every call — exactly one `herdr integration status` + one `entwurf peer-facts` + one `herdr agent list` per open, in that order (counted, not claimed), through ONE spawn call site; the four blocks each naming their own owner/observation time with no aggregate verdict; the D3=C fallback reading the activation ledger from the path its WRITER owns (XDG state, never the runtime's data root) and only when the phase is `active`, with ENTWURF_BIN and PATH still winning outright; the citizen axis alone skipping by name when no binary is reachable; every broken-evidence state a named red rather than a crash or an empty table, and a selected herdr atom that is `outdated`/`needs repair` as block [1]'s OWN red (`not installed` stays a reported skip); ambiguous never first-wins, diagnostics shown, activity in its own column, and zero writes to the plugin state/config dirs. No herdr binary, no Entwurf install
174
174
  ./run.sh check-herdr-runtime-bootstrap # deterministic gate for the #116 M3-b1 runtime leaf (`plugins/herdr/lib/runtime-bootstrap.mjs`): the Entwurf-owned stable active root as a REAL directory (nothing is put on PATH — the scoped wiring above names ABSOLUTE commands under it), ownership decided BEFORE the first mkdir, disk facts from lstat (a dangling symlink is not 'absent' and a link into another tree is not a directory), exact name@version + compiled entry + all three required bins present AND executable + a real `check-bridge`, the plugin-owned artifact lock coherent with the checkout, the owned npm cache and --ignore-scripts, a CERTIFIED journal (non-object/array/scalar/blank-identity/phase-contradicting-digest all refused) as the only ownership proof, all EIGHT active/staging/previous combinations named, the last good runtime never lost (torn swap AND corrupt-active beside a good backup), prior provenance carried across an install that may not finish, a failed candidate leaving the running runtime byte-identical, idempotent same-spec reinstall, a journal believed only while the disk backs it, a preflight-then-mutate inverse taking runtime last, and the refusal to record Herdr's commit as ours. NETWORK ZERO, npm ZERO — it drives a FIXTURE package, so the ACTUAL package proof lives in check-pack-install
175
175
  ./run.sh check-herdr-activation # deterministic gate for the #116 M3-b2 scoped activation: the pi user-scope-ONLY forward seam (no project write, OpenCode byte-untouched), the absolute command DERIVED from the runtime root for BOTH harnesses (relative/unnormalised refused), default bare/clone bytes unchanged when no mode is asked, the pi inverse refusing a drifted command instead of deleting an override, a certified activation ledger that can never name an atom outside {pi, claude-code}, add-only reinstall (a shrinking H retains, never removes), a roots-drift refusal before the first byte, the teardown order components->runtime->ledger LAST, one refused preflight leaving every byte untouched, and no deferred module load on the path that deletes its own runtime. Drives the REAL pi writers; the vendor `claude` CLI is NOT invoked and its acceptance of an absolute executable stays a named LIVE boundary
176
176
  ./run.sh check-herdr-plugin-profile # deterministic gate for the #116 M3-a PURE activation leaf (`plugins/herdr/lib/integration-profile.mjs`): the closed {pi→pi, claude→claude-code} table, exactly-one-row-or-named-refusal, the FRONT-ANCHORED state grammar (a `/srv/current (v9)/` directory name may not decide a verdict), no path in the plan, outdated/needs-repair as named FAILs, not-installed as a zero-write SKIP, `current` as herdr's admission with no floor of our own, OpenCode observed but never planned, and the leaf's purity. Listing strings only — no herdr binary, no install, no environment, no writes
@@ -199,7 +199,7 @@ Usage:
199
199
  ./run.sh smoke-omp-receive-live # #87 bundle B acceptance: another harness wakes an ALREADY-OPEN omp citizen, which reads its inbox and answers in the same session. Decides its own outcome from the capability registry — omp without a drainable mailbox is a protocol SKIP (no receiver unit exists to arm), which an unattended release-gate reports and `--cut` reads as RED; a registry that claims a receive rail with no acceptance body here is a FAIL
200
200
  ./run.sh smoke-entwurf-chain-live # LIVE cross-harness delivery chain: native Claude Code -> pi GPT -> pi ACP Sonnet -> mailbox terminus, proving sender identity/replyable at every hop and a real read receipt at the end. Prerequisites (claude on PATH, pi credentials per backend) report protocol SKIP, never a pass
201
201
  ./run.sh check-release-gate-outcomes # release-gate STEP OUTCOME protocol (P1): one skip exit code shared by the shell + TS halves, classifier never rounds a skip up to a pass, `--cut` refuses a MUST SKIP while a bare diagnostic stays exit 0, no LIVE smoke keeps the old exit-0 skip shape, and both real skip surfaces are INVOKED and observed to propagate the code
202
- ./run.sh check-gate-manifests # the HEAD of that qualification, on its own: runner self-test + the committed manifests validated against the origin index + the declared lane inventory. Executes ZERO mutants and never snapshots this repo (~8s), which is why it lives in check:hermetic while the body stays on CI/release-gate — three of the five reds qualification ever produced in CI died right here, before a mutant gate ran (#99 B-3)
202
+ ./run.sh check-gate-manifests # the HEAD of that qualification, on its own: runner self-test + the committed manifests validated against the origin index + the declared lane inventory. Executes ZERO committed mutants (only the self-test's synthetic fixture mutants, in tmp fixture repos) and never snapshots this repo (~20s), which is why it lives in check:hermetic while the body stays on CI/release-gate — three of the five reds qualification ever produced in CI died right here, before a mutant gate ran (#99 B-3)
203
203
  ./run.sh check-gate-qualification # kill-proof qualification (the gate-of-gates): runner self-test (classifier truth table + synthetic negatives incl. wrong-reason/hang/control-red/impurity) + committed mutant manifests (scripts/mutants/*.json) run in an isolated snapshot repo under control→mutant→restore→control; the real checkout is never written. Evidence = claim IDs + killed mutant IDs, never assertion counts
204
204
  ./run.sh check-probe-ordering # §11-7 ordering-probe deterministic gate: raw-client SAMENESS pinned to backend.ts (sequence/args/timeouts/permission policy — the probe may never measure a lookalike), phase attribution incl. set-model, probe-mode fixture wire markers (delay honored, probeRunId REQUIRED, smoke-acp-mcp-live legacy compat), event-log door integrity (reserved keys refused at write; unknown marker name, broken sort axis, or a payload the classifier cannot judge on is MALFORMED, never a quiet event), and the §11-7 paired-verdict truth table (P0/I0 outside the space, phase-qualified D, B promotion ladder, C, A two-delay rule). Offline/deterministic; one product claim (no production prompt cutoff) is replant-qualified via scripts/mutants/probe-ordering.json, while the other assertions are direct [CHECK:*] contracts
205
205
  ./run.sh check-probe-cli-shim # §11-7-c B-name-snapshot PRODUCER gate: the CLI shim driven as a REAL process against fake CLIs (no API, no cost). Proves what a defect would buy — FABRICATED evidence (a malformed init is never reported as an empty name set; the boot report carries the true target path+sha256 the classifier verifies against the roster), a DESTROYED turn (byte transparency across mid-UTF8/CRLF/oversized/unterminated framing, exit-code fidelity, signal re-raise, inbound signal forwarding, stderr passthrough, stdout backpressure), and LEAKED operator state (exact-allowlist env scrub, no argv/env/prompt body in the log). Offline/deterministic; 20 direct [CHECK:*] contracts, deliberately no longer replant-qualified after #70 subtraction
@@ -1218,6 +1218,30 @@ check_entwurf_v2_send() {
1218
1218
  run_vitest pi-extensions/lib/entwurf-v2-send.test.ts
1219
1219
  }
1220
1220
 
1221
+ check_entwurf_v2_runner() {
1222
+ # MUTANT EXECUTION COORDINATE, not a discovery path — the file is found by
1223
+ # check-tests-beside-behavior like every other beside-behaviour lane. This case exists because
1224
+ # the #120 P2 SEND-DIRTY-LOCK-RUNNER-MOVES-ACCEPTANCE mutant names it as gate argv, and
1225
+ # attribution is read from the JSON test titles `run_vitest` emits. Narrow file filter, same
1226
+ # reason as check-entwurf-v2-send: a mutant pointed at the discovery door would re-run every
1227
+ # beside-behaviour test once per mutant.
1228
+ # It stays inside check:contracts even though the discovery door also runs this file — see
1229
+ # check-release-gate-outcomes: a gate a mutant names must be reachable outside qualification.
1230
+ section "v2 dispatch runner (mutant execution coordinate)"
1231
+ run_vitest pi-extensions/lib/entwurf-v2-runner.test.ts
1232
+ }
1233
+
1234
+ check_control_send_receipt() {
1235
+ # MUTANT EXECUTION COORDINATE, not a discovery path — the test is found by
1236
+ # check-tests-beside-behavior like every other beside-behaviour lane. This case exists because
1237
+ # the #120 P2 SEND-RECEIPT-* mutants name it as gate argv, and attribution is read from the JSON
1238
+ # test titles `run_vitest` emits. Narrow file filter, same reason as check-entwurf-v2-send.
1239
+ # It stays inside check:contracts even though the discovery door also runs this file — see
1240
+ # check-release-gate-outcomes: a gate a mutant names must be reachable outside qualification.
1241
+ section "control-socket acceptance boundary (mutant execution coordinate)"
1242
+ run_vitest pi-extensions/lib/control-send-receipt.test.ts
1243
+ }
1244
+
1221
1245
  check_entwurf_v2_native_push() {
1222
1246
  # MUTANT EXECUTION COORDINATE, not a discovery path — the test is found by
1223
1247
  # check-tests-beside-behavior like every other beside-behaviour lane. This case exists
@@ -2123,7 +2147,14 @@ const runSh = readFileSync('run.sh', 'utf8');
2123
2147
  const piAi = pkg.devDependencies?.['@earendil-works/pi-ai'];
2124
2148
  const piCoding = pkg.devDependencies?.['@earendil-works/pi-coding-agent'];
2125
2149
  const piTui = pkg.devDependencies?.['@earendil-works/pi-tui'];
2150
+ // #120 P2: pi-agent-core is now DIRECTLY imported by test/pi-queue.oracle.test.ts, which proves
2151
+ // what the control-socket receipt says about a receiver's queues. An oracle pinned to a DIFFERENT
2152
+ // pi than the one this repo targets would answer honestly about a pi nobody runs, so its exact
2153
+ // dev pin moves with the floor or the floor is not a floor.
2154
+ const piCore = pkg.devDependencies?.['@earendil-works/pi-agent-core'];
2126
2155
  assert.ok(piAi, 'package.json devDependencies must pin @earendil-works/pi-ai');
2156
+ assert.equal(piCore, piAi,
2157
+ `[QK:PI-CORE-PIN-TRACKS-FLOOR] @earendil-works/pi-agent-core (${piCore}) must match @earendil-works/pi-ai (${piAi}) — the queue oracle imports it directly to prove what the control-socket receipt claims about a receiver's queues, so a pin that drifts off the floor would answer honestly about a pi nobody here runs`);
2127
2158
  assert.equal(piCoding, piAi,
2128
2159
  `@earendil-works/pi-coding-agent (${piCoding}) must match @earendil-works/pi-ai (${piAi})`);
2129
2160
  assert.equal(piTui, piAi,
@@ -2254,6 +2285,79 @@ for (const [file, re, shape] of PROSE_DECLS) {
2254
2285
  assert.ok(m, `${file}: the baseline sentence "${shape}" is gone — restore it or update check-dep-versions; a doc reword must not silently drop the pin from the gate`);
2255
2286
  assert.equal(m[1], piAi, `${file}: "${shape}" declares ${m[1]}, but the devDep pin is ${piAi}`);
2256
2287
  }
2288
+ // ── #120 P4: ONE FLOOR, FOUR MECHANICAL SURFACES ──────────────────────────
2289
+ // The bump is mostly a number substitution, and that is exactly how a bump goes
2290
+ // wrong: three surfaces move and the fourth is noticed months later by an install
2291
+ // that fails somewhere else. `[측정 2026-09-22]` the workspace closure was already
2292
+ // outside this gate — nothing here read `pnpm-workspace.yaml` at all — so a stale
2293
+ // row there could only surface at `pnpm install` time, far from the change.
2294
+ //
2295
+ // ONE assertion, because the claim token may appear exactly once in a gate source.
2296
+ // Every way the floor can fragment is collected first and named individually in the
2297
+ // failure, so "which surface" is never lost to the single-token rule.
2298
+ const nextFloorRange = `>=${piAi} <0.${piMin + 1}`;
2299
+ const floorGaps = [];
2300
+ const PI_CONSTELLATION = [
2301
+ '@earendil-works/pi-ai', '@earendil-works/pi-coding-agent', '@earendil-works/pi-tui',
2302
+ '@earendil-works/pi-agent-core', '@earendil-works/pi-client', '@earendil-works/pi-protocol',
2303
+ '@earendil-works/pi-telemetry', '@earendil-works/chord',
2304
+ ];
2305
+ const sameSet = (got, want, where) => {
2306
+ const missing = want.filter((n) => !got.includes(n));
2307
+ const extra = got.filter((n) => !want.includes(n));
2308
+ if (missing.length > 0) floorGaps.push(`${where}: missing ${missing.join(', ')}`);
2309
+ if (extra.length > 0) floorGaps.push(`${where}: unexpected ${extra.join(', ')}`);
2310
+ };
2311
+ // run.sh check-pack-install: the packages a consumer's fresh tree must resolve.
2312
+ const installed = [];
2313
+ for (const [, name, ver] of runSh.matchAll(/"(@earendil-works\/[a-z-]+)@(\d+\.\d+\.\d+)"/g)) {
2314
+ installed.push(name);
2315
+ if (ver !== piAi) floorGaps.push(`run.sh install constellation: ${name}@${ver} is not the floor ${piAi}`);
2316
+ }
2317
+ sameSet(installed, PI_CONSTELLATION, 'run.sh install constellation');
2318
+ // pnpm-workspace.yaml minimumReleaseAgeExclude: the same eight, each ADMITTING the
2319
+ // current floor. History stays (`0.85.0 || 0.85.1 || …`) — only the newest term is
2320
+ // the contract, because that is what an install resolves against.
2321
+ const closure = [...readFileSync('pnpm-workspace.yaml', 'utf8').matchAll(/^\s*-\s*'(@earendil-works\/[a-z-]+)@([^']+)'/gm)];
2322
+ sameSet(closure.map(([, name]) => name), PI_CONSTELLATION, 'pnpm-workspace.yaml closure');
2323
+ for (const [, name, spec] of closure) {
2324
+ if (!spec.split('||').map((t) => t.trim()).includes(piAi)) {
2325
+ floorGaps.push(`pnpm-workspace.yaml closure: ${name}@${spec} does not admit the floor ${piAi} — a stranded row fails only at install time`);
2326
+ }
2327
+ }
2328
+ // The PIN-LEAK MATCHER's own version bound. `[측정 2026-09-22]` this one was missed by the
2329
+ // first cut of this very claim and only surfaced when check-pack-install actually ran: the
2330
+ // install resolved 0.87.0 correctly and the gate refused it, because the matcher still said
2331
+ // `@0\.86\.0`. It is a regex literal, not a quoted `@pkg@version` spec, so the scan above
2332
+ // walked straight past it — which is exactly the "surface nothing else reads" this claim is
2333
+ // about, found the hard way. It is bound by name now.
2334
+ const matcherPin = runSh.match(/grep -Ev '@(\d+\\?\.\d+\\?\.\d+)\(_\|\$\)'/);
2335
+ if (!matcherPin) {
2336
+ floorGaps.push("run.sh pack_install_leaked_pi: the version-bounded pin regex is gone — a matcher that stopped bounding the version would pass every leaked runtime");
2337
+ } else if (matcherPin[1].replace(/\\/g, '') !== piAi) {
2338
+ floorGaps.push(`run.sh pack_install_leaked_pi: the pin-leak matcher bounds @${matcherPin[1].replace(/\\/g, '')}, but the floor is ${piAi} — the consumer install proof would refuse the very runtime this repo pins`);
2339
+ }
2340
+
2341
+ // The three CURRENT-contract doc sentences the range/exact scans above cannot see,
2342
+ // each anchored on its own wording. Historical samples (the 0.84.x placement
2343
+ // measurement in docs/mux-launch-rail.md, for one) are deliberately NOT scanned:
2344
+ // they record what was true then, and rewriting them would falsify evidence.
2345
+ const CURRENT_CONTRACT_DECLS = [
2346
+ ['plugins/herdr/README.md', /npm install -g "@earendil-works\/pi-coding-agent@(>=[\d.]+ <0\.\d+)"/, 'the herdr install line'],
2347
+ ['docs/mux-launch-rail.md', /현 supported range 는 `(>=[\d.]+ <0\.\d+)`/, "mux-launch-rail's current supported range"],
2348
+ ['docs/acp-backend-rail.md', /devDep exact `(\d+\.\d+\.\d+)`, peer `(>=[\d.]+ <0\.\d+)`/, "the ACP support row's exact+range pair"],
2349
+ ];
2350
+ for (const [file, re, shape] of CURRENT_CONTRACT_DECLS) {
2351
+ const m = readFileSync(file, 'utf8').match(re);
2352
+ if (!m) { floorGaps.push(`${file}: ${shape} is gone — a reworded current-contract sentence must fail loud, not drop out silently`); continue; }
2353
+ for (const captured of m.slice(1)) {
2354
+ const expected = captured.startsWith('>=') ? nextFloorRange : piAi;
2355
+ if (captured !== expected) floorGaps.push(`${file}: ${shape} declares "${captured}", but the floor is ${piAi} (${nextFloorRange})`);
2356
+ }
2357
+ }
2358
+ assert.deepEqual(floorGaps, [],
2359
+ `[QK:PI-FLOOR-SINGLE-SOURCE] the pi floor is declared on four mechanical surfaces — the package's exact dev pins and peer ceiling, run.sh's install constellation, the pnpm workspace closure, and the current-contract sentences in the docs — and every one of them must name the SAME floor. A bump that moves three of the four is the ordinary way this breaks, and the surface left behind is the one nothing else reads`);
2360
+
2257
2361
  // Guard the guard: if the patterns ever stop matching, the loops above pass
2258
2362
  // vacuously and the docs fall back OUT of the gate without a word.
2259
2363
  assert.ok(rangeDecls >= 5, `expected at least 5 pi range declarations across the baseline docs, found ${rangeDecls} — the doc scan matched (almost) nothing and would pass vacuously`);
@@ -3025,6 +3129,21 @@ check_acp_sdk_surface() {
3025
3129
  run_vitest test/acp-sdk-surface.contract.test.ts
3026
3130
  }
3027
3131
 
3132
+ check_pi_queue_oracle() {
3133
+ # #120 P2. The PUBLIC execution coordinate for `test/pi-queue.oracle.test.ts`, and the reason it
3134
+ # needs one: the discovery door below selects `pi-extensions/**/*.test.ts` and
3135
+ # `plugins/herdr/**/*.test.mjs` and deliberately NOT `test/**`, so a file under `test/` that
3136
+ # nothing names is green only when somebody runs `pnpm test` by hand. This oracle backs every
3137
+ # sentence the receipt and DELIVERY.md make about `queued-steer` / `queued-follow-up`, which is
3138
+ # exactly the kind of claim that must not rest on a manual run.
3139
+ #
3140
+ # It is a Vitest file, not a new hand-built gate: the subject is the INSTALLED
3141
+ # @earendil-works/pi-agent-core (exact dev pin), driven with a fake stream function. Zero model,
3142
+ # zero network, zero child process.
3143
+ section "pi receiver queue oracle (installed pi-agent-core, no model)"
3144
+ run_vitest test/pi-queue.oracle.test.ts
3145
+ }
3146
+
3028
3147
  check_tests_beside_behavior() {
3029
3148
  # The DISCOVERY door (issue #119 V2). A `.test.ts` beside a pi extension or a
3030
3149
  # `.test.mjs` beside the herdr plugin lib is run by public `pnpm check` without
@@ -3055,6 +3174,44 @@ check_tests_beside_behavior() {
3055
3174
  fi
3056
3175
  done
3057
3176
 
3177
+ # #120 P2 — THE OTHER HALF OF THE SAME CONTRACT: what this door deliberately does NOT open.
3178
+ # `test/**` is excluded on purpose (see the positional-substring measurement above), so a vitest
3179
+ # file under `test/` is reached by public `pnpm check` ONLY if some run.sh verb names it AND that
3180
+ # verb is in the `check:vitest` chain. Measured: a queue-oracle file under test/ landed green under
3181
+ # a manual `pnpm test` while the floor never ran it at all. A claim proven only when somebody
3182
+ # remembers to run it by hand is not on the floor, so the reachability is asserted rather than
3183
+ # assumed — every file, every time, with no filename list living here either.
3184
+ #
3185
+ # AND NO PATH IS SPELLED IN THIS FUNCTION. Measured: naming the oracle's path in the comment above
3186
+ # made the scan match its OWN comment — this verb is in the chain, so every file appeared reachable
3187
+ # and the regression mutant survived. A checker that can satisfy itself is not a checker.
3188
+ local chain tf verb
3189
+ chain="$(cd "$REPO_DIR" && node -e 'process.stdout.write(require("./package.json").scripts["check:vitest"] ?? "")')"
3190
+ while IFS= read -r tf; do
3191
+ [ -n "$tf" ] || continue
3192
+ # The enclosing `check_xxx()` is the coordinate; `check:vitest` must call its `check-xxx` form.
3193
+ # ONE failure message, because the claim token may appear exactly once in a gate source — and
3194
+ # the two ways this can break ("nothing names it" / "what names it is never called") are the
3195
+ # same defect seen from two sides: the file is not on the floor.
3196
+ # THE COORDINATE IS AN EXECUTABLE LINE, NOT A MENTION. Measured: when this scan accepted any
3197
+ # line inside an enclosing function that contained the path, deleting the `run_vitest` call
3198
+ # itself left the function's own COMMENT matching — the file stopped being run and the checker
3199
+ # stayed green. So only `run_vitest <path>` counts, and the two seams are separated: does
3200
+ # something execute it, and is that something on the floor.
3201
+ verb="$(awk -v needle="$tf" -v chain="$chain" '
3202
+ /^check_[a-z0-9_]+\(\) \{/ { fn = $1; sub(/\(\).*/, "", fn); gsub(/_/, "-", fn) }
3203
+ $1 == "run_vitest" && fn != "" { for (i = 2; i <= NF; i++) if ($i == needle) { print fn; found = 1; exit } }
3204
+ END { if (!found) print "" }' "$REPO_DIR/run.sh")"
3205
+ if [ -z "$verb" ]; then
3206
+ fail "[QK:VITEST-TEST-DIR-HAS-COORDINATE] $tf is under test/, which the discovery door deliberately excludes, and no run.sh verb EXECUTES it — a mention in a comment is not a coordinate, so nothing runs this file at all"
3207
+ return 1
3208
+ fi
3209
+ if ! printf '%s' "$chain" | grep -qF "$verb"; then
3210
+ fail "[QK:VITEST-TEST-DIR-ON-THE-FLOOR] $tf is executed by run.sh verb '$verb', which check:vitest does not call — the file has a coordinate nobody walks, so it is green only when somebody remembers to run pnpm test by hand"
3211
+ return 1
3212
+ fi
3213
+ done < <(cd "$REPO_DIR" && find test -type f -name '*.test.ts' -print | LC_ALL=C sort)
3214
+
3058
3215
  local -a selected=()
3059
3216
  local f
3060
3217
  while IFS= read -r f; do
@@ -3553,7 +3710,7 @@ check_pack() {
3553
3710
  # which is exactly what property (1)'s `(_|$)` absorbs; all three shapes are fixtures
3554
3711
  # below so a future suffix change cannot pass vacuously.)
3555
3712
  pack_install_leaked_pi() {
3556
- grep '^@earendil-works+' | grep -Ev '@0\.86\.0(_|$)' || true
3713
+ grep '^@earendil-works+' | grep -Ev '@0\.87\.1(_|$)' || true
3557
3714
  }
3558
3715
 
3559
3716
  # Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise either
@@ -3573,27 +3730,27 @@ check_pack_pin_matcher() {
3573
3730
  # None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`0.86.0-beta.1`,
3574
3731
  # the prerelease shape that an unanchored match would bless) and an off-pin version.
3575
3732
  matcher_probe=$(printf '%s\n' \
3576
- '@earendil-works+pi-ai@0.86.0' \
3577
- '@earendil-works+pi-ai@0.86.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
3578
- '@earendil-works+pi-ai@0.86.0_ws@8.21.3' \
3579
- '@earendil-works+pi-ai@0.86.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
3580
- '@earendil-works+pi-ai@0.86.0-beta.1' \
3581
- '@earendil-works+pi-agent-core@0.85.1' | pack_install_leaked_pi)
3582
- if [ "$matcher_probe" != '@earendil-works+pi-ai@0.86.0-beta.1
3583
- @earendil-works+pi-agent-core@0.85.1' ]; then
3584
- fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 0.86.0-beta.1 and the off-pin 0.85.1 lookalikes, and pass 0.86.0 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
3733
+ '@earendil-works+pi-ai@0.87.1' \
3734
+ '@earendil-works+pi-ai@0.87.1_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
3735
+ '@earendil-works+pi-ai@0.87.1_ws@8.21.3' \
3736
+ '@earendil-works+pi-ai@0.87.1_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
3737
+ '@earendil-works+pi-ai@0.87.1-beta.1' \
3738
+ '@earendil-works+pi-agent-core@0.87.0' | pack_install_leaked_pi)
3739
+ if [ "$matcher_probe" != '@earendil-works+pi-ai@0.87.1-beta.1
3740
+ @earendil-works+pi-agent-core@0.87.0' ]; then
3741
+ fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 0.87.1-beta.1 and the off-pin 0.87.0 lookalikes, and pass 0.87.1 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
3585
3742
  return 1
3586
3743
  fi
3587
3744
 
3588
3745
  # Cell 2 — the closure prefix. chord is a non-`pi-` member of the same runtime closure
3589
- # (0.85.0 onward, still true at 0.86.0). An off-pin chord MUST leak; the pinned one must not. A matcher narrowed
3746
+ # (0.85.0 onward, still true at 0.87.1). An off-pin chord MUST leak; the pinned one must not. A matcher narrowed
3590
3747
  # back to `^@earendil-works+pi-` sees nothing here and dies at this signature.
3591
3748
  matcher_probe=$(printf '%s\n' \
3592
- '@earendil-works+chord@0.86.0' \
3593
- '@earendil-works+chord@0.85.1' \
3594
- '@earendil-works+pi-ai@0.86.0' | pack_install_leaked_pi)
3595
- if [ "$matcher_probe" != '@earendil-works+chord@0.85.1' ]; then
3596
- fail "[QK:PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE] the pin-leak matcher must cover every @earendil-works closure member, not just the pi-* families — an off-pin @earendil-works/chord has to leak (it is a runtime dependency of pi-coding-agent, pi-agent-core, pi-client and pi-protocol at 0.86.0 — `[측정 2026-09-20]` `npm view <pkg>@0.86.0 dependencies`) — got: ${matcher_probe:-<nothing leaked>}"
3749
+ '@earendil-works+chord@0.87.1' \
3750
+ '@earendil-works+chord@0.87.0' \
3751
+ '@earendil-works+pi-ai@0.87.1' | pack_install_leaked_pi)
3752
+ if [ "$matcher_probe" != '@earendil-works+chord@0.87.0' ]; then
3753
+ fail "[QK:PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE] the pin-leak matcher must cover every @earendil-works closure member, not just the pi-* families — an off-pin @earendil-works/chord has to leak (it is a runtime dependency of pi-coding-agent, pi-agent-core, pi-client and pi-protocol — `[측정 2026-09-20]` at 0.86.0, re-measured `[측정 2026-09-22]` at 0.87.0 and `[측정 2026-09-23]` at 0.87.1: the same four @earendil-works direct deps) — got: ${matcher_probe:-<nothing leaked>}"
3597
3754
  return 1
3598
3755
  fi
3599
3756
 
@@ -3825,7 +3982,9 @@ _check_pack_install_impl() {
3825
3982
 
3826
3983
  printf '%s\n' '{ "name": "entwurf-install-smoke", "version": "0.0.0", "private": true }' > "$tmp/package.json"
3827
3984
 
3828
- # pi-agent-core is pinned even though we never import it: pi-coding-agent depends
3985
+ # pi-agent-core is pinned for TWO reasons now. Directly: `test/pi-queue.oracle.test.ts` imports
3986
+ # it to drive a real `Agent` (#120 P2), so its version is part of what the receipt's claims are
3987
+ # true OF. And transitively, which is why it was pinned here first: pi-coding-agent depends
3829
3988
  # on it by CARET (`^0.86.x`), so with no lockfile in this fresh temp project it
3830
3989
  # floats to whatever pi published last — and that newer core then drags a NESTED
3831
3990
  # pi-ai of its own. Measured 2026-07-21: pinning only the three we import left
@@ -3868,18 +4027,18 @@ _check_pack_install_impl() {
3868
4027
  # pi-agent-core incident. The list moves WITH the floor and is re-measured at
3869
4028
  # each bump, never pruned on a single quiet minor.
3870
4029
  # The verified floor is 0.86.0 as of 2026-09-20.
3871
- echo "[check-pack-install] pnpm add into $tmp (with 0.86.x peers + chord + typebox)"
4030
+ echo "[check-pack-install] pnpm add into $tmp (with 0.87.x peers + chord + typebox)"
3872
4031
  local install_log
3873
4032
  install_log=$(cd "$tmp" && pnpm add \
3874
4033
  "$tgz_path" \
3875
- "@earendil-works/pi-ai@0.86.0" \
3876
- "@earendil-works/pi-coding-agent@0.86.0" \
3877
- "@earendil-works/pi-tui@0.86.0" \
3878
- "@earendil-works/pi-agent-core@0.86.0" \
3879
- "@earendil-works/pi-client@0.86.0" \
3880
- "@earendil-works/pi-protocol@0.86.0" \
3881
- "@earendil-works/pi-telemetry@0.86.0" \
3882
- "@earendil-works/chord@0.86.0" \
4034
+ "@earendil-works/pi-ai@0.87.1" \
4035
+ "@earendil-works/pi-coding-agent@0.87.1" \
4036
+ "@earendil-works/pi-tui@0.87.1" \
4037
+ "@earendil-works/pi-agent-core@0.87.1" \
4038
+ "@earendil-works/pi-client@0.87.1" \
4039
+ "@earendil-works/pi-protocol@0.87.1" \
4040
+ "@earendil-works/pi-telemetry@0.87.1" \
4041
+ "@earendil-works/chord@0.87.1" \
3883
4042
  "typebox@latest" \
3884
4043
  --ignore-workspace --ignore-scripts 2>&1) || {
3885
4044
  fail "[check-pack-install] pnpm add failed:"
@@ -3895,11 +4054,11 @@ _check_pack_install_impl() {
3895
4054
  local leaked_pi
3896
4055
  leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
3897
4056
  if [ -n "$leaked_pi" ]; then
3898
- fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.86.0):"
4057
+ fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.87.1):"
3899
4058
  printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
3900
4059
  return 1
3901
4060
  fi
3902
- echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 0.86.0 (chord included)"
4061
+ echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 0.87.1 (chord included)"
3903
4062
 
3904
4063
  # Resolve the installed package.json and confirm pi.extensions
3905
4064
  # arrived intact. If pi.extensions is empty or missing, the
@@ -6466,6 +6625,12 @@ case "$cmd" in
6466
6625
  check-entwurf-v2-send)
6467
6626
  check_entwurf_v2_send
6468
6627
  ;;
6628
+ check-entwurf-v2-runner)
6629
+ check_entwurf_v2_runner
6630
+ ;;
6631
+ check-control-send-receipt)
6632
+ check_control_send_receipt
6633
+ ;;
6469
6634
  check-entwurf-v2-native-push)
6470
6635
  check_entwurf_v2_native_push
6471
6636
  ;;
@@ -6773,9 +6938,10 @@ case "$cmd" in
6773
6938
  # floor can pay for it: the runner self-test (classifier truth table + synthetic
6774
6939
  # negatives) plus the REAL manifests — schema, global claim uniqueness, subjects
6775
6940
  # tracked and lstat-regular in the origin index, claim tokens exactly once in their
6776
- # gate sources — plus the declared lane inventory. It executes ZERO mutants and
6777
- # makes NO snapshot of this repo; the body (check-gate-qualification) owns that and
6778
- # is unchanged. Cheap enough for check:hermetic (~8s).
6941
+ # gate sources — plus the declared lane inventory. It executes ZERO committed mutants
6942
+ # (the self-test runs only synthetic fixture mutants in tmp fixture repos) and makes NO
6943
+ # snapshot of this repo; the body (check-gate-qualification) owns the committed set and
6944
+ # is unchanged. Cheap enough for check:hermetic (~20s measured 2026-09-29 on oracle).
6779
6945
  run_ts scripts/check-gate-qualification.ts --manifests-only
6780
6946
  ;;
6781
6947
  check-gate-qualification)
@@ -7427,6 +7593,9 @@ case "$cmd" in
7427
7593
  check-acp-sdk-surface)
7428
7594
  check_acp_sdk_surface
7429
7595
  ;;
7596
+ check-pi-queue-oracle)
7597
+ check_pi_queue_oracle
7598
+ ;;
7430
7599
  check-tests-beside-behavior)
7431
7600
  check_tests_beside_behavior
7432
7601
  ;;