@junghanacs/entwurf 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +3 -1
- package/BASELINE.md +2 -0
- package/CHANGELOG.md +307 -0
- package/FAQ.md +47 -0
- package/README.md +5 -3
- package/VERIFY.md +43 -2
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +97 -18
- package/docs/setup-clean-host.md +8 -7
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +33 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +19 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +2 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/callback-env.js +68 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +54 -22
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +7 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +13 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +19 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +35 -42
- package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
- package/mcp/entwurf-bridge/src/index.ts +41 -1
- package/mcp/entwurf-bridge/test.sh +2 -2
- package/package.json +15 -13
- package/pi-extensions/entwurf-control.ts +56 -0
- package/pi-extensions/lib/acp/acp-client.ts +3 -3
- package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
- package/pi-extensions/lib/acp/backend.ts +20 -10
- package/pi-extensions/lib/acp/context.ts +30 -10
- package/pi-extensions/lib/acp/event-mapper.ts +5 -5
- package/pi-extensions/lib/acp/session-store.ts +2 -2
- package/pi-extensions/lib/acp/tool-surface.ts +2 -1
- package/pi-extensions/lib/callback-env.ts +78 -0
- package/pi-extensions/lib/compaction-send-guard.ts +4 -1
- package/pi-extensions/lib/entwurf-v2-contract.ts +2 -2
- package/pi-extensions/lib/entwurf-v2-lock.ts +1 -1
- package/pi-extensions/lib/entwurf-v2-runner.ts +1 -1
- package/pi-extensions/lib/fresh-call-composition.ts +56 -22
- package/pi-extensions/lib/herdr-fresh-call.ts +12 -4
- package/pi-extensions/lib/herdr-placement.ts +13 -4
- package/pi-extensions/lib/mux-fresh-call.ts +23 -8
- package/pi-extensions/meta-bridge-omp.ts +35 -48
- package/run.sh +327 -246
- package/scripts/check-acp-backend-preflight.ts +117 -9
- package/scripts/check-acp-carrier-augment.ts +6 -4
- package/scripts/check-acp-event-mapper.ts +17 -6
- package/scripts/check-acp-prompt-builder.ts +17 -9
- package/scripts/check-acp-prompt-lifecycle.ts +23 -6
- package/scripts/check-acp-session-reuse.ts +64 -51
- package/scripts/check-acp-session-store.ts +31 -8
- package/scripts/check-acp-stop-reason.ts +20 -2
- package/scripts/check-acp-stream-hooks.ts +22 -5
- package/scripts/check-acp-usage-accounting.ts +42 -18
- package/scripts/check-entwurf-bridge-boot.ts +16 -1
- package/scripts/check-fresh-call-dispatch.ts +4 -5
- package/scripts/check-gate-qualification.ts +45 -5
- package/scripts/check-herdr-activation.ts +73 -0
- package/scripts/check-herdr-fresh-call.ts +22 -6
- package/scripts/check-herdr-sandbox.ts +14 -2
- package/scripts/check-install-container.sh +1 -1
- package/scripts/check-pi-launch.ts +361 -0
- package/scripts/check-release-gate-outcomes.ts +7 -0
- package/scripts/inventory-verification-surface.ts +86 -10
- package/scripts/lib/mutation-qualify.ts +1 -1
- package/scripts/mutants/acp-backend-preflight.json +20 -0
- package/scripts/mutants/bridge-boot-resume.json +26 -0
- package/scripts/mutants/herdr-activation.json +16 -0
- package/scripts/mutants/herdr-fresh-call.json +11 -0
- package/scripts/mutants/herdr-plugin-build.json +1 -1
- package/scripts/mutants/mux-fresh-call.json +21 -8
- package/scripts/mutants/omp-fresh.json +5 -9
- package/scripts/mutants/pack-install.json +3 -3
- package/scripts/mutants/pi-launch.json +113 -0
- package/scripts/mutants/resume-args.json +6 -6
- package/scripts/mutants/v2-surface.json +2 -2
- package/scripts/mutants/v2-visible-resume.json +17 -17
- package/scripts/probe-bridge-command.ts +1 -0
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-acp-session-reuse-live.ts +23 -5
- package/scripts/smoke-agy-install-state.sh +2 -2
- package/scripts/smoke-copilot-mcp-state.sh +1 -1
- package/scripts/smoke-entwurf-v2-matrix-live.ts +1 -1
- package/scripts/smoke-herdr-fresh-call-live.ts +56 -28
- package/scripts/smoke-herdr-raw-install-live.sh +877 -0
- package/scripts/smoke-omp-fresh-live.ts +4 -2
- package/scripts/smoke-omp-mcp-state.sh +1 -1
- package/scripts/smoke-pi-provider-state.sh +2 -2
- package/scripts/tsconfig.json +5 -0
- package/scripts/check-entwurf-facts.ts +0 -391
- package/scripts/check-entwurf-resume-args.ts +0 -122
- package/scripts/check-entwurf-v2-decider.ts +0 -657
- package/scripts/check-entwurf-v2-lock.ts +0 -371
- package/scripts/check-entwurf-v2-mailbox.ts +0 -261
- package/scripts/check-entwurf-v2-matrix.ts +0 -401
- package/scripts/check-entwurf-v2-native-push.ts +0 -221
- package/scripts/check-entwurf-v2-release.ts +0 -151
- package/scripts/check-entwurf-v2-runner.ts +0 -448
- package/scripts/check-entwurf-v2-send-fallback.ts +0 -350
- package/scripts/check-entwurf-v2-send.ts +0 -458
- package/scripts/check-entwurf-v2-visible-resume.ts +0 -445
- package/scripts/check-socket-discovery.ts +0 -401
package/run.sh
CHANGED
|
@@ -122,7 +122,7 @@ Usage:
|
|
|
122
122
|
./run.sh setup [project-dir] # ONE presence-driven composition (#86): per-component PASS/SKIP/FAIL for pi/claude/agy/copilot/omp/codex + stable dev bins + v2 install smoke. Codex birth/MCP/status-line atoms are composed at user scope; setup stays non-green until the operator trusts the birth declaration once in visible Codex. Absent harness = zero-state SKIP, detected-incomplete = named FAIL + nonzero exit. Never installs a harness, starts a daemon, touches credentials, or writes the vendor trust receipt
|
|
123
123
|
./run.sh release-gate [project-dir] [--cut] [--allow-skip-gemini] # SINGLE release gate: full static (pnpm run check:full) + the v2-native live gates (v2 matrix-live, check-bridge, doctor-pi-provider, RGG) + the ACP plugin acceptance floor (12 LIVE smokes: socket-citizen/raw-turn/overlay/provider/session-reuse/carrier-augment/memory-containment/rgg/mcp/skill/bundled-mcp/v2-send) + the one surviving axis the aggregate used to omit silently (claude-native-resume; Cortex stays a documented on-demand direct call) + the cross-harness delivery chain (smoke-entwurf-chain-live) + the Codex Pi -> visible Codex -> visible Pi receipt chain (smoke-codex-fresh-live; initial Pi and the Codex it opens in one session S, explicit operator-owned app-server PID in a DIFFERENT session — that separation is the lane B claim). TWO-TIER summary: MUST (release-blocking, owns the exit code — "green" applies here) + BEHAVIOR (advisory, non-blocking: RGG positives model-in-loop turn). STEP OUTCOME protocol: every step is INVOKED and reports its own PASS / SKIP (exit 97, a prerequisite it does not have) / FAIL — a skip is never counted as a pass. Without --cut this is the unattended diagnostic (SKIPs reported, exit 0). WITH --cut it is read as release acceptance and ANY MUST SKIP is red, which is what makes "a CUT needs LIVE=1, SKIP=0" executable instead of prose. --allow-skip-gemini accepted-but-ignored (back-compat). final cut authorization is GLG's.
|
|
124
124
|
./run.sh check-bridge # entwurf-bridge direct MCP smoke + protocol/negative-path test.sh (live substrate = v2 live smokes)
|
|
125
|
-
./run.sh check-entwurf-bridge-boot # deterministic gate (5d-5-pre, G1a/G1b/G1e/G1f, IN pnpm run check:full): boot start.sh under strip-types + assert v2 fence graph loads + entwurf_v2 and
|
|
125
|
+
./run.sh check-entwurf-bridge-boot # deterministic gate (5d-5-pre, G1a/G1b/G1e/G1f/G1h, IN pnpm run check:full): boot start.sh under strip-types + assert v2 fence graph loads + entwurf_v2, entwurf_resume_call and entwurf_callback registered/schema + the tools/list surface is EXACTLY the eight shipped garden verbs; tools/list only, no auth/side-effect
|
|
126
126
|
./run.sh check-entwurf-bridge-pi-free # deterministic gate (0.12.1 A, IN pnpm check): static — bridge index eager value-import closure must carry no @earendil-works/pi-* (type-only + dynamic import excluded); proves the meta-bridge boots pi-free
|
|
127
127
|
./run.sh check-model-lock # deterministic unit test for pi-extensions/model-lock.ts (4-quadrant + edge cases, no API)
|
|
128
128
|
./run.sh check-shell-quote # POSIX-safety gate for shellQuote (remote SSH arg quoting in entwurf paths) — source parity + behavior matrix, no SSH
|
|
@@ -158,29 +158,17 @@ Usage:
|
|
|
158
158
|
./run.sh check-socket-probe # deterministic gate (0.11 Stage 0, F3): three-valued control-socket liveness (alive|dead|indeterminate) — GC reclaims dead only, indeterminate survives; pure classify + 2-socket integration, no API
|
|
159
159
|
./run.sh check-project-trust-handler # deterministic gate (0.11 Stage 0, Trust 2층): project_trust handler — decideProjectTrust matrix (escape=inherited-false+interactive+trust-here→{yes,remember:true}; non-interactive→undecided; never undefined) + adapter single-writer, fake prompt, no UI
|
|
160
160
|
./run.sh check-entwurf-v2-contract # FROZEN entwurf_v2 contract — control-socket liveness domain is currently pi; self-fetch/native-push citizens are out of that domain, not globally unsupported; pure, no IO
|
|
161
|
-
./run.sh check-entwurf-v2-lock # deterministic gate (0.11 Stage 0 step 5a, 버킷 B F2): per-gid dispatch LOCK primitive — openSync wx atomic acquire, second-acquire=target-locked conflict (holder JSON for human cleanup), nonce-owned release (successor survives late release), stale reclaim same-host+ESRCH-only (EPERM/remote/alive/unknown fail-closed), empty/corrupt=conflict not auto-deleted, F2-P1 malformed gid throws; real temp dir, deps injected
|
|
162
|
-
./run.sh check-entwurf-v2-decider # deterministic gate (0.11 Stage 0 step 5b): PURE dispatch decider decideDispatch — frozen 7-step order over injected fakes, lock acquire+release tracked so reject⇒no-plan-no-lock proven; pre-probe rejects observedLiveness=null, send/resume execute keep lock + mailbox no-lock (?7), resume plan no mode/provider/model, invalid gid throws (F2-P1); pure, no IO
|
|
163
|
-
./run.sh check-entwurf-v2-matrix # deterministic gate (0.11 Stage 0 step 5d-5 a): REACHABILITY + LOCK SSOT table — drives REAL decideDispatch over fakes, fixes every (target kind → transport → lock class) cell as one table (control-socket/meta-mailbox/native-push + bad-target/conflict/locked/undeliverable/dormant/indeterminate rejects), coverage pass fails on a dropped cell; thin coverage not a decider re-impl; pure, no IO
|
|
164
|
-
./run.sh check-entwurf-v2-release # deterministic gate (0.11 Stage 0 step 5c-1): PURE release-policy reducer (decideReleasePolicy + reduceRelease) — the spawn-observation policy and its four release events went with the transport in the visible-first cut, so the shipped machine is meta-mailbox=never release (no lock) + control-socket=release once on send-final; decideReleasePolicy still enforces the lock-nullness invariant; pure, no IO
|
|
165
|
-
./run.sh check-entwurf-v2-send # deterministic gate (0.11 Stage 0 step 5c-2a): control-socket SEND hand (executeControlSocketSend) wiring transport IO onto the 5c-1 reducer — ack→sent, in-band reject→rejected (no fallback), dead→same-lock one-shot re-resolve (control retry / mailbox enqueue), indeterminate→failed+rethrow with NO fallback (no double-delivery); release exactly once, releaseLock throw never masks the send error; IO-via-dep
|
|
166
161
|
./run.sh check-compaction-send-guard # deterministic gate (#111): control-socket send during Pi compaction — event-armed refuse compacting, quiet unknown non-idle refuse busy (ctx.signal is not isStreaming); no pi.sendMessage, no delivered:true; idle/live-run steer/followUp preserved; pure, no IO
|
|
167
|
-
./run.sh check-entwurf-v2-send-fallback # deterministic gate (0.11 Stage 0 step 5c-2b): same-lock re-resolve RESOLVER (resolveDeadControlSendFallback) — fire-and-forget re-resolve: alive→control retry, dead→reject (nothing is ever launched), indeterminate→reject, unsupported+deliverable→mailbox plan, undeliverable/bad-target/conflict→reject; resolver never releases, mis-wire fails loud, inspect/probe throws propagate; no IO (fakes)
|
|
168
|
-
./run.sh check-entwurf-v2-runner # deterministic gate (0.11 Stage 0 step 5d-1): execute-router (executeDispatch) routing an already-decided DispatchDecision to its 5c transport hand → one outcome-rich EntwurfV2RunResult. reject→rejected (no hand) / control/mailbox→matching hand with decision.lock verbatim / N3 rejectReason carried / N1 SendDeliveredReleaseFailedError→execution-failed{finalizedOutcome,releaseFailed,retrySafe:false}; fake hands, no IO
|
|
169
|
-
./run.sh check-entwurf-v2-mailbox # deterministic gate (0.11 Stage 0 step 5c-4, LAST 5c transport slice): ENQUEUE-ONLY meta-mailbox SEND body (executeMetaMailboxSend) + production sendViaMailbox adapter — sender→formatMetaMailboxBody with plan.wantsReply threaded (divergence from legacy hard false), sender absent→raw plan.message, enqueue opts EXACTLY {gardenId,body,sessionsDir,mailboxDir}, enqueue throw PROPAGATES (no success:false fold — mailbox has no in-band refuse); adapter NEVER touches lock (release is the hand's job); source guard: no release/routing seam
|
|
170
|
-
./run.sh check-entwurf-resume-args # deterministic gate: resume-argv SSOT (buildResumePiArgs) for the S1 VISIBLE resume. The headless shape was measured wrong for a window before the consumer was written (`-p` is pi's own non-interactive mode), so the one shipped posture is `--entwurf-control` FIRST + ext args exactly once + `--session <abs file>` + optional `--provider` + `--model <m>` — and the gate pins the ABSENCES as hard as the presences: no --mode, no -p, no positional prompt (a resume runs no turn), no --no-extensions, never --session-id (which MINTS a session instead of resuming one)
|
|
171
162
|
./run.sh check-mux-resume-call # deterministic gate: S1 resume PLACEMENT composition (mux-resume-call.ts). No fake tmux. cwd rules are MEASURED tmux 3.6a behaviours, each a way a resume looks successful while being wrong: a nonexistent `-c` exits 0 and lands the child in $HOME (so it is refused HERE), `-c` is FORMAT-EXPANDED so `#{…}` silently rewrites the path and `#(…)` was observed running a command (so `#` is refused), and whitespace measured SAFE (so no escaping layer is owed). Also pins `-c` reaching tmux, runtime after `--`, carrier-free argv, zero identity in this module, and the surface seam that keeps the v2 composition from importing mux
|
|
172
163
|
./run.sh check-mux-parent-artifact # deterministic gate for the tracked scrubbed parent-transcript fixture — a version-pinned sample of the PARENT-SIDE shape (fresh_call toolResult + the later callback custom_message) so downstream never opens a private transcript. Pins event order, the toolCallId join on the RESULT (pi writes no separate toolCall row), the launch nonce reappearing verbatim in the callback body, the <sender_info> envelope field names, and the absence of operator paths / real garden ids / real uuids. NOT placement evidence
|
|
173
164
|
./run.sh check-mux-launcher-fence # deterministic gate for the shared operator-launcher fence (issue #67): scripts/lib/claude-launcher-fence.ts + its wiring into BOTH mux LIVE smokes. Replants the observed install-destruction shape (real HOME + fixture XDG_DATA_HOME → self-update retargets the real `claude` launcher into the fixture tree, teardown deletes it) wholly inside disposable mkdtemp roots — the real launcher is never inspected. Pins fail-closed preflight, retarget/content-change detection before cleanup, removal BLOCKED on fixture reference / unproven safety / surviving tracked panes, exact operator-parity XDG restore (absent = DELETED, not canonical defaults), the lifecycle cell-branch topology, and one shared helper consumed by both smokes
|
|
174
165
|
./run.sh check-typing-call-fence # deterministic gate for the boundary nobody has broken: this product composes ARGUMENTS and LETTERS, never keystrokes. Scans every tracked production source under pi-extensions/ and mcp/ — comments blanked, string literals kept — for tmux send-keys/paste-buffer/load-buffer and herdr's PTY verbs (agent.prompt, agent.send_keys, pane.send_text, pane.input.set). Prose is exempt on purpose: the sentence stating the rule names the very calls it forbids, and a scanner that read prose could be made green by deleting the law. Carries its own oracle — the detector is proven on planted code, proven blind to comments, and the scanned inventory is measured
|
|
175
|
-
./run.sh check-entwurf-v2-visible-resume # deterministic gate: S1 visible-resume COMPOSITION (entwurf-v2-visible-resume.ts) with every seam injected — the whole state machine incl. the timeout branch runs with no tmux/lock/socket/clock. Pins lock BEFORE liveness, identity under the lock and before any window (no-transcript citizen fails loud, opens nothing), live/indeterminate/address-conflict refused unlaunched, observation as a BOUNDED WAIT (measured: socket answers ~2–4s after launch, so one immediate probe would call a successful resume unobserved), exactly ONE launch on every path, timeout → lock released + window left open + nothing retried/killed, failed release throws, and the two receipts staying separate in type and text
|
|
176
166
|
./run.sh check-resume-launch-identity # deterministic gate for resume-launch-identity.ts, the record-authoritative launch-identity leaf preserved through the visible-first cut (spawn-bg and all its callers are gone; this leaf answers "which being is this, and which conversation is theirs"). Temp meta-store fixture: gardenId→record.transcriptPath happy path with header cwd/provider/model; C3 integrity (header id ≠ record.nativeSessionId → refused, never resumed); #52 ADDRESSABLE read (a gid that no longer holds its nativeSessionId alone is refused from EITHER side — the plain targeted read would resume one transcript twice under two locks); cause fidelity per impossible resume incl. the F7 pin (recorded-but-deleted transcript → MISSING, not "no recorded model"); header↔gate SSOT. No spawn/socket/timer
|
|
177
167
|
./run.sh smoke-entwurf-v2-matrix-live # LIVE sentinel (0.11 Stage 0 step 5d-5, D4-b) — OUT of pnpm check, needs LIVE=1. Drives REAL production runEntwurfV2 deps over REAL OS objects, 4 cells: C1 control-socket (real pi --entwurf-control resident → RPC send → lock acquire→release ×1), C1b record-less socket (#50 C4: live record-less pi → EVERY intent rejected pre-probe record-less-socket, no lock, rendered hint names record authority + fresh-cut), C2 meta-mailbox deliverable (armed self-fetch citizen → real .msg enqueue, lock-free), C3 meta-mailbox guard (no armed receiver → reject, no garbage). Model-in-loop OUT (transport/lock/enqueue gate, GPT Q2); negative/timeout stay deterministic. Model: ENTWURF_LIVE_TARGET=<provider>/<model> (default openai-codex/gpt-5.6-luna). LIVE=1 ./run.sh smoke-entwurf-v2-matrix-live
|
|
178
168
|
./run.sh smoke-agy-native-push-live # 봉인 8 LIVE acceptance for the native-push (agy) rail — OUT of pnpm check, needs LIVE=1 + AGY_CONVERSATION_ID (a live agy conversation). Drives the REAL antigravity adapter + register core + runEntwurfV2 (production deps): doctor-static preflight (dangling→FAIL, the ③ gate), probe route, register create/attach idempotency, fire→native-push delivered, post-send re-probe (D7 partial), bogus-conv→native-push-probe-indeterminate. Meta-store isolated to a temp dir (only the agy round-trip is real; no real-store residue). COST FENCE: open that agy conversation on gemini-3.6-flash (free account) — never a Pro tier; entwurf never selects the agy model and no assertion reads it. LIVE=1 AGY_CONVERSATION_ID=<convId> ./run.sh smoke-agy-native-push-live
|
|
179
169
|
./run.sh smoke-codex-native-push-live # on-demand LIVE acceptance for Codex native-push — needs LIVE=1 + CODEX_LIVE_THREAD_ID loaded by a visible TUI attached to the default app-server; probes the real UDS, routes through production runEntwurfV2, queues one exact-token turn without retry, re-probes, and leaves the visible TUI to show the model result
|
|
180
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
|
|
181
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
|
|
182
|
-
./run.sh check-entwurf-facts # deterministic gate (0.11 Stage 0 step 4, fact-provider slice 1+2): PURE PeerFact core + resolveFactList union — R1 out-of-domain→unsupported, R3b socket-domain 4-value, facts-only keyset; union: PeerFact + RecordLessSocketFact by gardenId (#50 C4: record-less socket = diagnostic subject, gid+liveness only), dormant→dead, F3 indeterminate preserved, out-of-socket-domain+socket fail-loud; pure, no IO
|
|
183
|
-
./run.sh check-socket-discovery # deterministic gate (0.11 Stage 0 step 4, fact-provider slice 3): SOCKET-axis scanSocketProbes — probes (dir sockets) ∪ (in-domain citizen canonical paths) 3-valued; dormant citizen no-file → dead (resumable, not unprobed), stall → indeterminate (F3), dir hygiene/dedup/missing-dir + e2e → resolveFactList; readdir/probe injected, no IO
|
|
184
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
|
|
185
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
|
|
186
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
|
|
@@ -288,7 +276,7 @@ Usage:
|
|
|
288
276
|
./run.sh check-dep-versions # local deterministic check that the pi pin agrees across package.json (devDeps + peer range), run.sh (peer-install pins), and the baseline docs (AGENTS/README/ROADMAP/setup-clean-host/demo)
|
|
289
277
|
./run.sh check-node-floor-coherence # binds the Node floor (24+, single axis) across engines.node, run.sh setup preflight, meta-bridge install/doctor judgment logic, clean-host docs, the bridge launcher header, and the CI runner node-version — engines.node is the SSOT, everything else is derived; sweeps tracked contract text for an unregistered declaration
|
|
290
278
|
./run.sh check-pack # publish gate (dry-run): npm pack --dry-run + tarball invariants (runtime-critical present, dev residue absent)
|
|
291
|
-
./run.sh check-pack-pin-matcher # pure self-test of check-pack-install's pin-leak matcher against synthetic .pnpm lookalikes, one cell per property: version boundary (@0.85.
|
|
279
|
+
./run.sh check-pack-pin-matcher # pure self-test of check-pack-install's pin-leak matcher against synthetic .pnpm lookalikes, one cell per property: version boundary (a prefix-extended @0.86.0-beta.1 and an off-pin @0.85.1 must leak, @0.86.0 bare or with any measured peer-hash must pass) and closure prefix (an off-pin @earendil-works/chord must leak — it carries no `pi-` prefix); snapshot-safe qualification oracle, also run first inside check-pack-install
|
|
292
280
|
./run.sh check-fresh-cut-gate # SOURCE cell of the generation-boundary proof (IN pnpm run check:full): drives real install/setup/fresh-cut in a sandbox; certification refusal is pre-write, quiescence is fail-closed, archives preserve bytes, and the #54 exit matrix distinguishes complete / no-move / usage / incomplete transition / complete-with-cleanup-residue. No model/network/cost
|
|
293
281
|
./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke + the #116 M3-b1 herdr-plugin RUNTIME VERIFIER run against that actual installed tarball (the focused check-herdr-runtime-bootstrap drives a fixture; THIS is where the real package is proven) with the pinned pi peers (pins derived from the package.json devDep; check-dep-versions binds them) + the npm-installed bridge BOOTS (tools/list) and DELIVERS (tools/call entwurf_v2 → .msg lands) + the installed all-absent and copilot-present (four-unit fake-vendor) `entwurf setup` rows + the INSTALLED generation lifecycle on a seeded previous-generation host (REFUSE before activation writes / zero Claude invocations → installed fresh-cut archives + opens empty → install-meta-bridge PASSES) + the INSTALLED-PACKAGE branch of the Copilot and OMP birth installers actually RUN (compiled entry selected, no raw .ts, and a real birth edge mints a citizen — the half a required-artifact list can never stand in for)
|
|
294
282
|
./run.sh check-install-container # 0.12.8 (#51 C): Linux artifact-CONSUMER gate — one candidate .tgz handed read-only to a checkout-invisible node:<engines-major>-bookworm cell. Default packs once to temp; ENTWURF_CANDIDATE_TGZ=/absolute/preserved.tgz consumes those exact bytes with no re-pack and prints canonical path+sha256 for release. Non-root global PATH install, frozen package, MCP tools/list, fake-Claude install-meta-bridge, path+sha256 fence, strict doctor, and the GENERATION host-state matrix (clean / v3-only store bytes unchanged / previous-generation REFUSE→fresh-cut→retry PASS) seeded inline. Docker missing = honest SKIP; ENTWURF_REQUIRE_DOCKER=1 makes that RED (required CI)
|
|
@@ -721,6 +709,31 @@ install_user_scope() {
|
|
|
721
709
|
esac
|
|
722
710
|
shift
|
|
723
711
|
done
|
|
712
|
+
# pi FLOOR — the same closed range `setup` enforces (#119 door symmetry). setup refuses an
|
|
713
|
+
# out-of-range pi by name before it writes any Pi wiring; this verb is the OTHER door onto the
|
|
714
|
+
# same registration (herdr plugin activation reaches it through plugins/herdr/lib/build.mjs →
|
|
715
|
+
# scripts/herdr-plugin-activate.mjs → `run.sh install-user-scope --plugin-runtime …`) and had no
|
|
716
|
+
# version check at all, so a host pi setup would name as FAIL got its citizen written here in
|
|
717
|
+
# silence. Both doors now tell the same truth, off the SAME derived range — `pi_supported_range`
|
|
718
|
+
# reads the package.json devDep pin, so there is one range in the repo and no second parser.
|
|
719
|
+
#
|
|
720
|
+
# ABSENT pi is deliberately NOT changed here. This verb is called by an activation that has
|
|
721
|
+
# already decided pi is one of its backends; inventing a presence verdict at the writer would put
|
|
722
|
+
# a second detection authority next to setup's. Out-of-range is the asymmetry that was real, and
|
|
723
|
+
# it is the only one closed. PI_BIN is the same probe seam setup uses (hermetic gates pin it;
|
|
724
|
+
# production leaves it unset).
|
|
725
|
+
local pi_bin="${PI_BIN:-pi}" pi_range pi_ver
|
|
726
|
+
if command -v "$pi_bin" >/dev/null 2>&1; then
|
|
727
|
+
pi_range=$(pi_supported_range)
|
|
728
|
+
# Bounded capture, same reason as setup's: a resolvable pi whose --version crashes or prints
|
|
729
|
+
# garbage must reach the refusal below, never abort through set -e with nothing said.
|
|
730
|
+
pi_ver="$("$pi_bin" --version 2>/dev/null | head -n 1 | tr -d '[:space:]')" || pi_ver=""
|
|
731
|
+
if ! pi_version_in_range "${pi_ver:-0}" "$pi_range"; then
|
|
732
|
+
echo "[install-user-scope] pi: FAIL — detected pi ${pi_ver:-<unreadable version>} is outside the supported range $pi_range; nothing was written. Align pi, then re-run." >&2
|
|
733
|
+
exit 1
|
|
734
|
+
fi
|
|
735
|
+
fi
|
|
736
|
+
|
|
724
737
|
preflight_v3_store install-user-scope
|
|
725
738
|
section "install-user-scope: the GLOBAL pi citizen only (no project wiring)"
|
|
726
739
|
register_user_scope_citizen "$plugin_runtime"
|
|
@@ -1095,6 +1108,14 @@ check_copilot_launch() {
|
|
|
1095
1108
|
run_ts scripts/check-copilot-launch.ts
|
|
1096
1109
|
}
|
|
1097
1110
|
|
|
1111
|
+
check_pi_launch() {
|
|
1112
|
+
# #118 홉 2. Same oracle as check-copilot-launch and for the same reason: the subject is
|
|
1113
|
+
# a process replacement, so a fake vendor named `pi` on a sandbox PATH reports the argv,
|
|
1114
|
+
# env and pid it was handed — never a reading of the launcher's source. Also carries the
|
|
1115
|
+
# --entwurf-control literal cross-check, because the shell cannot import the TS constant.
|
|
1116
|
+
run_ts scripts/check-pi-launch.ts
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1098
1119
|
check_codex_app_server_launch() {
|
|
1099
1120
|
# #95: the managed app-server SPELLING, proved against a fake vendor. There is no second
|
|
1100
1121
|
# spelling of the address to compare any more — the launcher asks `codex-socket-path` — so
|
|
@@ -1185,123 +1206,28 @@ check_entwurf_v2_contract() {
|
|
|
1185
1206
|
run_ts scripts/check-entwurf-v2-contract.ts
|
|
1186
1207
|
}
|
|
1187
1208
|
|
|
1188
|
-
check_entwurf_v2_lock() {
|
|
1189
|
-
# Deterministic gate for 0.11 Stage 0 step 5a (버킷 B F2): the per-gid dispatch
|
|
1190
|
-
# LOCK primitive — what serializes concurrent in-domain dispatch at one
|
|
1191
|
-
# target (pi self-guards CREATE but not RESUME, 검증원장 F2). acquire =
|
|
1192
|
-
# openSync(lockPath,"wx") atomic; a second acquire without release =
|
|
1193
|
-
# target-locked conflict carrying the holder JSON (F2-P2 human cleanup). release
|
|
1194
|
-
# = unlink ONLY when the on-disk nonce is still ours (a successor's re-acquire
|
|
1195
|
-
# survives a late release). Stale reclaim ONLY for same host + ESRCH; EPERM
|
|
1196
|
-
# (other user's live pid) / different host / alive pid / unknown error all
|
|
1197
|
-
# fail-closed to conflict. Empty/corrupt lockfile = conflict, never
|
|
1198
|
-
# auto-deleted. F2-P1: a malformed gid throws before any path is built. Real
|
|
1199
|
-
# temp dir (wx atomicity under test); clock/nonce/pid/host/kill injected.
|
|
1200
|
-
run_ts scripts/check-entwurf-v2-lock.ts
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
check_entwurf_v2_decider() {
|
|
1204
|
-
# Deterministic gate for 0.11 Stage 0 step 5b: the PURE dispatch decider
|
|
1205
|
-
# decideDispatch. Drives the frozen 7-step order over INJECTED fakes (target
|
|
1206
|
-
# lookup / lock / socket inspect+probe / preflight / capability), tracking lock
|
|
1207
|
-
# acquire+release so "reject ⇒ no plan AND no lock retained" is PROVEN. Covers:
|
|
1208
|
-
# bad-target/target-locked/target-address-conflict carry observedLiveness=null
|
|
1209
|
-
# (pre-probe), every other reject carries a measured value; a control-socket send
|
|
1210
|
-
# executes KEEPING the lock, meta-mailbox send takes NO lock (?7); an invalid gid throws before
|
|
1211
|
-
# any lookup (F2-P1). Pure, no IO, no API.
|
|
1212
|
-
run_ts scripts/check-entwurf-v2-decider.ts
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
check_entwurf_v2_matrix() {
|
|
1216
|
-
# Deterministic gate for 0.11 Stage 0 step 5d-5 (a): the REACHABILITY + LOCK SSOT
|
|
1217
|
-
# TABLE. Drives the REAL decideDispatch over minimal injected fakes and fixes, as
|
|
1218
|
-
# one readable table, every (target kind → transport → lock class) cell the 5d-5
|
|
1219
|
-
# claim covers: bad-target/address-conflict/target-locked rejects, unsupported
|
|
1220
|
-
# meta-mailbox (deliverable) vs mailbox-undeliverable (inactive) reject,
|
|
1221
|
-
# in-domain control-socket (live) / released rejects
|
|
1222
|
-
# (ff-dormant, indeterminate, under-lock conflict). A coverage pass
|
|
1223
|
-
# FAILS if any transport / lock class / pre-probe reject is missing — a dropped
|
|
1224
|
-
# decider cell cannot pass silently. Thin coverage, NOT a decider re-impl; surface
|
|
1225
|
-
# parity stays in check-entwurf-v2-surface. Pure, no IO, no API.
|
|
1226
|
-
run_ts scripts/check-entwurf-v2-matrix.ts
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
check_entwurf_v2_release() {
|
|
1230
|
-
# Deterministic gate for 0.11 Stage 0 step 5c-1: the PURE release-policy reducer
|
|
1231
|
-
# (decideReleasePolicy + reduceRelease) for the 5c transport hand. Proves the
|
|
1232
|
-
# Fable-3 "release-after-observation" timing as a pure state machine BEFORE any
|
|
1233
|
-
# send IO: meta-mailbox=never release (no lock), control-socket=release once
|
|
1234
|
-
# on send-final. The spawn-observation policy and its four release events went with
|
|
1235
|
-
# the transport in the visible-first cut; decideReleasePolicy still enforces the
|
|
1236
|
-
# lock-nullness invariant (?7). Pure, no IO.
|
|
1237
|
-
run_ts scripts/check-entwurf-v2-release.ts
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
1209
|
check_entwurf_v2_send() {
|
|
1241
|
-
#
|
|
1242
|
-
#
|
|
1243
|
-
#
|
|
1244
|
-
#
|
|
1245
|
-
# re-
|
|
1246
|
-
#
|
|
1247
|
-
#
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
check_entwurf_v2_send_fallback() {
|
|
1253
|
-
# Deterministic gate for 0.11 Stage 0 step 5c-2b: the same-lock re-resolve RESOLVER
|
|
1254
|
-
# (resolveDeadControlSendFallback) the 5c-2a hand calls on a dead connect. Proves the
|
|
1255
|
-
# fire-and-forget re-resolve routing over injected fakes (no filesystem): alive->
|
|
1256
|
-
# control-socket retry (inspected socketPath) / dead(absent)->reject (dormant-fire-
|
|
1257
|
-
# forget-unsupported, and nothing is launched) / indeterminate->reject / unsupported+deliverable
|
|
1258
|
-
# ->meta-mailbox plan (mini-table, no inspect/probe) / unsupported+undeliverable->reject
|
|
1259
|
-
# / bad-target + address-conflict->reject pre-probe. Mis-wire (plan/lock gid) fails loud
|
|
1260
|
-
# before IO; inspect/probe throws PROPAGATE (the hand owns failed+release); the resolver
|
|
1261
|
-
# has NO release seam; every execute plan keeps the held gid.
|
|
1262
|
-
run_ts scripts/check-entwurf-v2-send-fallback.ts
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
check_entwurf_v2_mailbox() {
|
|
1266
|
-
# Deterministic gate for 0.11 Stage 0 step 5c-4 (the LAST 5c transport slice): the
|
|
1267
|
-
# ENQUEUE-ONLY meta-mailbox SEND body (executeMetaMailboxSend) + its production
|
|
1268
|
-
# sendViaMailbox adapter (makeProductionSendViaMailbox). Proves the wiring over an
|
|
1269
|
-
# injected fake enqueue (no filesystem): sender present -> formatMetaMailboxBody with
|
|
1270
|
-
# plan.wantsReply threaded (yes/no in body, the deliberate divergence from legacy's
|
|
1271
|
-
# hard-coded false) / sender absent -> raw plan.message / enqueue opts EXACTLY
|
|
1272
|
-
# {gardenId: plan.targetGardenId, body, sessionsDir, mailboxDir} (no re-derivation) /
|
|
1273
|
-
# enqueue throw PROPAGATES (never folded into success:false — a mailbox has no in-band
|
|
1274
|
-
# refuse) / success -> {success:true}. Production adapter resolves {success:true},
|
|
1275
|
-
# consults senderProvider once, and NEVER touches the lock (a poison LockClaim whose
|
|
1276
|
-
# every access throws still resolves). Source guard: the lib code has NO release seam
|
|
1277
|
-
# and NO routing seam (no releaseLock / inspect / probe / resolve) — a lock leak or
|
|
1278
|
-
# re-route is structurally impossible.
|
|
1279
|
-
run_ts scripts/check-entwurf-v2-mailbox.ts
|
|
1210
|
+
# MUTANT EXECUTION COORDINATE, not a discovery path — the test is found by
|
|
1211
|
+
# check-tests-beside-behavior like every other beside-behaviour lane. This case exists
|
|
1212
|
+
# because the V2SEND-INBAND-REJECT-REASON mutant names it as gate argv, and attribution is
|
|
1213
|
+
# read from the JSON test titles `run_vitest` emits (run.sh:104-113). Narrow file filter: a
|
|
1214
|
+
# mutant pointed at the glob shim would re-run every beside-behaviour test once per mutant.
|
|
1215
|
+
# It stays inside check:contracts even though the discovery door also runs this file — see
|
|
1216
|
+
# check-release-gate-outcomes: a gate a mutant names must be reachable outside qualification.
|
|
1217
|
+
section "control-socket send hand (mutant execution coordinate)"
|
|
1218
|
+
run_vitest pi-extensions/lib/entwurf-v2-send.test.ts
|
|
1280
1219
|
}
|
|
1281
1220
|
|
|
1282
1221
|
check_entwurf_v2_native_push() {
|
|
1283
|
-
#
|
|
1284
|
-
#
|
|
1285
|
-
#
|
|
1286
|
-
#
|
|
1287
|
-
#
|
|
1288
|
-
#
|
|
1289
|
-
#
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
check_entwurf_v2_runner() {
|
|
1295
|
-
# Deterministic gate for 0.11 Stage 0 step 5d-1: the execute-router (executeDispatch) that
|
|
1296
|
-
# routes an already-decided DispatchDecision to its 5c transport hand and maps the outcome
|
|
1297
|
-
# to one outcome-rich EntwurfV2RunResult. Proves over injected fake hands (no socket/spawn/
|
|
1298
|
-
# timer): reject -> rejected (receipt+diagnostic carried, NO hand called) / control-socket ->
|
|
1299
|
-
# sendControl(plan, lock) / meta-mailbox -> sendMailbox(plan, NULL
|
|
1300
|
-
# lock, ?7). Carry-overs: N3 control `rejected` carries rejectReason verbatim; N1
|
|
1301
|
-
# SendDeliveredReleaseFailedError -> execution-failed{finalizedOutcome, releaseFailed,
|
|
1302
|
-
# retrySafe:false}; a plain hand throw -> execution-failed{retrySafe:false} with no
|
|
1303
|
-
# finalizedOutcome. Exactly one hand runs per execute.
|
|
1304
|
-
run_ts scripts/check-entwurf-v2-runner.ts
|
|
1222
|
+
# MUTANT EXECUTION COORDINATE, not a discovery path — the test is found by
|
|
1223
|
+
# check-tests-beside-behavior like every other beside-behaviour lane. This case exists
|
|
1224
|
+
# because the NATIVE-PUSH-RETRY-BYTE-IDENTITY mutant names it as gate argv, and attribution
|
|
1225
|
+
# is read from the JSON test titles `run_vitest` emits (run.sh:104-113). Narrow file filter:
|
|
1226
|
+
# a mutant pointed at the glob shim would re-run every beside-behaviour test once per mutant.
|
|
1227
|
+
# It stays inside check:contracts even though the discovery door also runs this file — see
|
|
1228
|
+
# check-release-gate-outcomes: a gate a mutant names must be reachable outside qualification.
|
|
1229
|
+
section "native-push send hand (mutant execution coordinate)"
|
|
1230
|
+
run_vitest pi-extensions/lib/entwurf-v2-native-push.test.ts
|
|
1305
1231
|
}
|
|
1306
1232
|
|
|
1307
1233
|
check_entwurf_v2_surface() {
|
|
@@ -1368,15 +1294,22 @@ check_compaction_send_guard() {
|
|
|
1368
1294
|
}
|
|
1369
1295
|
|
|
1370
1296
|
check_entwurf_resume_args() {
|
|
1371
|
-
#
|
|
1372
|
-
#
|
|
1373
|
-
#
|
|
1374
|
-
#
|
|
1375
|
-
#
|
|
1376
|
-
#
|
|
1377
|
-
#
|
|
1378
|
-
# file
|
|
1379
|
-
|
|
1297
|
+
# NOT a discovery path — the test is found by check-tests-beside-behavior like every other
|
|
1298
|
+
# beside-behaviour lane. This case exists as the MUTANT EXECUTION COORDINATE: the six
|
|
1299
|
+
# resume-args mutants name it as their gate argv, the qualification runner re-invokes that
|
|
1300
|
+
# argv once per mutant, and attribution is read from the JSON test titles `run_vitest` emits
|
|
1301
|
+
# under ENTWURF_MUTATION_VITEST_REPORT (run.sh:104-113). A gate argv that called vitest
|
|
1302
|
+
# directly would skip that reporter and every kill would come back unattributed.
|
|
1303
|
+
#
|
|
1304
|
+
# The file filter is NARROW on purpose. Pointing a mutant at the glob shim would make the
|
|
1305
|
+
# runner re-run every beside-behaviour test once per mutant.
|
|
1306
|
+
#
|
|
1307
|
+
# It stays inside check:contracts even though the discovery door also runs this file, and the
|
|
1308
|
+
# duplicate is deliberate: check-release-gate-outcomes requires every gate a mutant names to
|
|
1309
|
+
# run inside check:full, because a gate reachable ONLY through qualification makes the
|
|
1310
|
+
# 28-minute mutant body the only thing that can notice it going red on a clean tree.
|
|
1311
|
+
section "resume-argv SSOT (mutant execution coordinate)"
|
|
1312
|
+
run_vitest pi-extensions/lib/entwurf-resume-args.test.ts
|
|
1380
1313
|
}
|
|
1381
1314
|
|
|
1382
1315
|
check_mux_launcher_fence() {
|
|
@@ -1432,16 +1365,16 @@ check_mux_resume_call() {
|
|
|
1432
1365
|
}
|
|
1433
1366
|
|
|
1434
1367
|
check_entwurf_v2_visible_resume() {
|
|
1435
|
-
#
|
|
1436
|
-
#
|
|
1437
|
-
#
|
|
1438
|
-
#
|
|
1439
|
-
#
|
|
1440
|
-
#
|
|
1441
|
-
#
|
|
1442
|
-
#
|
|
1443
|
-
|
|
1444
|
-
|
|
1368
|
+
# MUTANT EXECUTION COORDINATE, not a discovery path — the test is found by
|
|
1369
|
+
# check-tests-beside-behavior like every other beside-behaviour lane. This case exists
|
|
1370
|
+
# because SEVENTEEN mutants name it as gate argv, and attribution is read from the JSON test
|
|
1371
|
+
# titles `run_vitest` emits (run.sh:104-113), which is why each of those claims is its own
|
|
1372
|
+
# test title. Narrow file filter: pointing them at the glob shim would re-run every
|
|
1373
|
+
# beside-behaviour test seventeen times. It stays inside check:contracts even though the
|
|
1374
|
+
# discovery door also runs this file — see check-release-gate-outcomes: a gate a mutant names
|
|
1375
|
+
# must be reachable outside qualification.
|
|
1376
|
+
section "visible same-id resume (mutant execution coordinate)"
|
|
1377
|
+
run_vitest pi-extensions/lib/entwurf-v2-visible-resume.test.ts
|
|
1445
1378
|
}
|
|
1446
1379
|
|
|
1447
1380
|
check_resume_launch_identity() {
|
|
@@ -1788,7 +1721,7 @@ smoke_acp_cortex_live() {
|
|
|
1788
1721
|
|
|
1789
1722
|
smoke_entwurf_v2_matrix_live() {
|
|
1790
1723
|
# LIVE sentinel for 0.11 Stage 0 step 5d-5 (D4-b) — kept OUT of `pnpm check`. The deterministic
|
|
1791
|
-
# sibling (
|
|
1724
|
+
# sibling (pi-extensions/lib/entwurf-v2-decider.matrix.test.ts) fixes every (target kind → transport → lock) cell over fakes
|
|
1792
1725
|
# with ZERO IO; this drives the REAL production runEntwurfV2 deps against REAL OS objects on the
|
|
1793
1726
|
# substrate happy path across 3 cells: C1 control-socket (a real `pi --entwurf-control` resident
|
|
1794
1727
|
# → control-socket RPC send → lock acquire→release ×1), C2 meta-mailbox deliverable (armed
|
|
@@ -1806,18 +1739,6 @@ smoke_entwurf_v2_matrix_live() {
|
|
|
1806
1739
|
run_ts scripts/smoke-entwurf-v2-matrix-live.ts
|
|
1807
1740
|
}
|
|
1808
1741
|
|
|
1809
|
-
check_entwurf_facts() {
|
|
1810
|
-
# Deterministic gate for 0.11 Stage 0 step 4 (fact-provider slice 1): the PURE
|
|
1811
|
-
# fact core. Locks the PeerFact shape + R1/R3b liveness invariant before any IO
|
|
1812
|
-
# wiring (gate-first). R1: out-of-domain backend (claude-code/codex/antigravity)
|
|
1813
|
-
# → unsupported for EVERY socket input (never coerced to the socket value or
|
|
1814
|
-
# dead). R3b: in-domain pi → alive/dead/indeterminate, null → indeterminate
|
|
1815
|
-
# (no proof ≠ dead). facts-only keyset: identity facts + liveness, NO
|
|
1816
|
-
# verb-routing (resumable/sendable/transport/dispatch/action) and NO
|
|
1817
|
-
# transcriptPath (동결결정 10). Pure, no IO, no API.
|
|
1818
|
-
run_ts scripts/check-entwurf-facts.ts
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
1742
|
check_control_socket_path() {
|
|
1822
1743
|
# Deterministic gate: the control-socket path grammar `<dir>/<gid>.sock` has ONE
|
|
1823
1744
|
# definition (pi-extensions/lib/control-socket-path.js) instead of one per
|
|
@@ -1828,18 +1749,6 @@ check_control_socket_path() {
|
|
|
1828
1749
|
run_ts scripts/check-control-socket-path.ts
|
|
1829
1750
|
}
|
|
1830
1751
|
|
|
1831
|
-
check_socket_discovery() {
|
|
1832
|
-
# Deterministic gate for 0.11 Stage 0 step 4 (fact-provider slice 3): the
|
|
1833
|
-
# SOCKET-axis wiring scanSocketProbes. Probes the union of (dir sockets) ∪
|
|
1834
|
-
# (every in-domain pi citizen's canonical path) so a dormant citizen with no
|
|
1835
|
-
# socket file reads dead (ENOENT) → resumable, never an unprobed gap (slice 2
|
|
1836
|
-
# throws on that). Three-valued throughout — a stalled socket stays
|
|
1837
|
-
# indeterminate (F3), never folded to dead by an alive-only listing. Dir
|
|
1838
|
-
# hygiene (non-.sock / malformed names ignored), dedup, missing-dir, sort, and
|
|
1839
|
-
# an end-to-end scanSocketProbes→resolveFactList. readdir/probe injected, no IO.
|
|
1840
|
-
run_ts scripts/check-socket-discovery.ts
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
1752
|
check_meta_listing() {
|
|
1844
1753
|
# Deterministic gate for 0.11 Stage 0 step 4 (fact-provider slice 4a): the
|
|
1845
1754
|
# meta-store axis listAllMetaIdentities. Explicit-partial: a parse failure or
|
|
@@ -2236,13 +2145,16 @@ assert.equal(peerTui, piAi,
|
|
|
2236
2145
|
// floor tracks the devDep pin so a consumer can't install against a pi lacking
|
|
2237
2146
|
// the public trust exports the bridge imports at the pinned minor, AND an upper
|
|
2238
2147
|
// bound at the next minor stops a fresh install from silently pulling a future
|
|
2239
|
-
// pi (past the declared ceiling — 0.
|
|
2148
|
+
// pi (past the declared ceiling — 0.87+ at the current 0.86.0 pin) whose
|
|
2240
2149
|
// internal export surface has drifted from the one we typecheck against.
|
|
2241
2150
|
// pi moves its public surface every minor (the 0.79→0.80 getModels→provider-
|
|
2242
|
-
// factory churn is exactly this
|
|
2243
|
-
//
|
|
2244
|
-
//
|
|
2245
|
-
//
|
|
2151
|
+
// factory churn is exactly this, and 0.86 is the same shape again: a custom
|
|
2152
|
+
// provider's streamSimple now receives a branded TranscriptContext instead of a
|
|
2153
|
+
// raw Context, with systemPrompt/tools folded into a leading system message), so
|
|
2154
|
+
// an open `>=` floor is exactly how the next installer re-acquires the drift.
|
|
2155
|
+
// The floor is also the HARD MINIMUM a consumer install resolves: at `>=0.86.0`
|
|
2156
|
+
// an existing 0.85.1 host is upgraded, not kept.
|
|
2157
|
+
// Expected shape: `>=<devDep> <0.<minor+1>` (e.g. `>=0.86.0 <0.87`).
|
|
2246
2158
|
const [piMaj, piMin] = piAi.split('.').map(Number);
|
|
2247
2159
|
assert.equal(piMaj, 0,
|
|
2248
2160
|
`pi pin major must stay 0 for the next-minor ceiling rule (got ${piAi}); revisit check-dep-versions when pi reaches 1.x`);
|
|
@@ -2273,10 +2185,48 @@ assert.equal(peerDepTui, expectedPeer,
|
|
|
2273
2185
|
// read, and sat two bumps stale (`>=0.84.3 <0.85` at a 0.85.1 pin) precisely
|
|
2274
2186
|
// because it was outside this list. Its declaration is a plain range, so the
|
|
2275
2187
|
// range scan below binds it — no new PROSE_DECL is needed for it.
|
|
2276
|
-
|
|
2188
|
+
// plugins/herdr/README.md joined on 2026-09-20, the same repair one bump later:
|
|
2189
|
+
// its step-1 `npm install -g @earendil-works/pi-coding-agent` was UNPINNED, so
|
|
2190
|
+
// from the hour 0.86.0 published it installed a pi outside the range the plugin
|
|
2191
|
+
// ships against — and nothing read that line. It now carries the range and is
|
|
2192
|
+
// scanned here, which is the only reason writing the number down is allowed.
|
|
2193
|
+
// docs/acp-backend-rail.md joined on 2026-09-20 for the same reason the two before it did:
|
|
2194
|
+
// its support table declared `peer >=0.85.1 <0.86` in prose no gate read, and the 0.86.0 bump
|
|
2195
|
+
// would have carried that into a release. Its declaration is a plain closed range and it is the
|
|
2196
|
+
// ONLY one in that file (the other rows declare exact versions of non-pi packages, which neither
|
|
2197
|
+
// pattern below matches), so the range scan binds it with no new PROSE_DECL. The bare `0.86.0`
|
|
2198
|
+
// devDep mentions in the same cell are prose, not an install pin, and stay unscanned — the range
|
|
2199
|
+
// half is what holds the row honest.
|
|
2200
|
+
const BASELINE_DOCS = ['README.md', 'ROADMAP.md', 'docs/setup-clean-host.md', 'demo/README.md', 'VERIFY.md', 'plugins/herdr/README.md', 'docs/acp-backend-rail.md'];
|
|
2201
|
+
|
|
2202
|
+
// ROADMAP.md is a baseline doc AND the home of the dated bump ledger, and those
|
|
2203
|
+
// are opposite kinds of sentence. A ledger entry is a RECEIPT: `2026-09-06 bump
|
|
2204
|
+
// — pi 0.84.4 → 0.85.1` records the range that landed THAT day, and rewriting it
|
|
2205
|
+
// at the next bump would destroy the evidence trail the entry exists to keep.
|
|
2206
|
+
// The comment above always said history keeps its old versions; until 0.86.0
|
|
2207
|
+
// that was only true because the newest entry happened to name the live pin, so
|
|
2208
|
+
// the FIRST bump after an entry was written is what exposed it. A ledger entry
|
|
2209
|
+
// opens at its dated bullet and runs until the enclosing list resumes at column
|
|
2210
|
+
// 0 (measured: every ` - ` bullet inside the Dep-bump block is a bump entry).
|
|
2211
|
+
function stripBumpLedger(file, text) {
|
|
2212
|
+
if (file !== 'ROADMAP.md') return text;
|
|
2213
|
+
let inLedger = false, dropped = 0;
|
|
2214
|
+
const kept = text.split('\n').filter((line) => {
|
|
2215
|
+
if (/^ - \*\*\d{4}-\d{2}-\d{2} bump — /.test(line)) { inLedger = true; }
|
|
2216
|
+
else if (inLedger && /^(- |#|\S)/.test(line)) { inLedger = false; }
|
|
2217
|
+
if (inLedger) dropped++;
|
|
2218
|
+
return !inLedger;
|
|
2219
|
+
});
|
|
2220
|
+
// Guard the exclusion itself: it must find a real ledger and must not eat the
|
|
2221
|
+
// whole file. Either way the range/exact scans below would pass vacuously.
|
|
2222
|
+
assert.ok(dropped > 0, 'ROADMAP.md: no dated bump-ledger entry found — the ledger moved or its bullet shape changed; re-point stripBumpLedger instead of leaving history inside the pin scan');
|
|
2223
|
+
assert.ok(kept.length > 100, `ROADMAP.md: the bump-ledger exclusion swallowed the document (${kept.length} lines left) — the live pin declarations would no longer be scanned`);
|
|
2224
|
+
return kept.join('\n');
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2277
2227
|
let rangeDecls = 0, exactDecls = 0;
|
|
2278
2228
|
for (const file of BASELINE_DOCS) {
|
|
2279
|
-
const text = readFileSync(file, 'utf8');
|
|
2229
|
+
const text = stripBumpLedger(file, readFileSync(file, 'utf8'));
|
|
2280
2230
|
// Closed-range declarations: `>=<floor> <0.<ceiling>` (spaces optional).
|
|
2281
2231
|
for (const [decl, floor, ceilMinor] of text.matchAll(/>=\s?(\d+\.\d+\.\d+)\s?<\s?0\.(\d+)/g)) {
|
|
2282
2232
|
rangeDecls++;
|
|
@@ -3075,6 +3025,70 @@ check_acp_sdk_surface() {
|
|
|
3075
3025
|
run_vitest test/acp-sdk-surface.contract.test.ts
|
|
3076
3026
|
}
|
|
3077
3027
|
|
|
3028
|
+
check_tests_beside_behavior() {
|
|
3029
|
+
# The DISCOVERY door (issue #119 V2). A `.test.ts` beside a pi extension or a
|
|
3030
|
+
# `.test.mjs` beside the herdr plugin lib is run by public `pnpm check` without
|
|
3031
|
+
# anyone adding its filename anywhere. That is the whole contract: the other two
|
|
3032
|
+
# vitest shims list their files, and a door that also listed files would shut
|
|
3033
|
+
# again the first time someone forgot.
|
|
3034
|
+
#
|
|
3035
|
+
# The globs are EXPANDED here, not handed to vitest as positional filters. The first
|
|
3036
|
+
# cut passed `pi-extensions plugins/herdr` as positionals, and vitest reads a positional
|
|
3037
|
+
# as a path SUBSTRING — so `test/pi-extensions-probe.test.ts` was selected too, which is
|
|
3038
|
+
# the opposite of the two-locations contract this gate exists to hold (measured: that file
|
|
3039
|
+
# ran as "Test Files 1 passed"). Expanding first keeps the selected set exactly the two
|
|
3040
|
+
# globs while the shim still contains no filename: it computes them every run.
|
|
3041
|
+
#
|
|
3042
|
+
# Keeping these globs out of the other two shims stays deliberate — those are mutant gate
|
|
3043
|
+
# argv (mux-fresh-call carries 56), and widening their include would rerun unrelated tests
|
|
3044
|
+
# once per mutant.
|
|
3045
|
+
#
|
|
3046
|
+
# An empty expansion RETURNS rather than calling vitest with no arguments, because a
|
|
3047
|
+
# vitest run with no filter runs the whole include — that is, all of test/**. The honest
|
|
3048
|
+
# steady state is "the door is open and unused", and it must not be spelled as a run.
|
|
3049
|
+
section "tests beside behavior (discovery, not a filename list)"
|
|
3050
|
+
local cfg="$REPO_DIR/vitest.config.ts" glob
|
|
3051
|
+
for glob in 'pi-extensions/**/*.test.ts' 'plugins/herdr/**/*.test.mjs'; do
|
|
3052
|
+
if ! grep -qF "\"$glob\"" "$cfg"; then
|
|
3053
|
+
fail "[check-tests-beside-behavior] vitest.config.ts no longer includes $glob — the discovery door is shut, and an expansion that found nothing would still pass"
|
|
3054
|
+
return 1
|
|
3055
|
+
fi
|
|
3056
|
+
done
|
|
3057
|
+
|
|
3058
|
+
local -a selected=()
|
|
3059
|
+
local f
|
|
3060
|
+
while IFS= read -r f; do
|
|
3061
|
+
[ -n "$f" ] && selected+=("$f")
|
|
3062
|
+
done < <(cd "$REPO_DIR" && {
|
|
3063
|
+
find pi-extensions -name node_modules -prune -o -type f -name '*.test.ts' -print
|
|
3064
|
+
find plugins/herdr -name node_modules -prune -o -type f -name '*.test.mjs' -print
|
|
3065
|
+
} 2>/dev/null | LC_ALL=C sort)
|
|
3066
|
+
|
|
3067
|
+
# Independent of HOW the expansion was produced: every selected path must be under one of
|
|
3068
|
+
# the two declared locations, and none may come from test/. The second clause is the one
|
|
3069
|
+
# that names the defect above, so it is asserted rather than left implied by the first.
|
|
3070
|
+
for f in "${selected[@]}"; do
|
|
3071
|
+
case "$f" in
|
|
3072
|
+
test/*)
|
|
3073
|
+
fail "[check-tests-beside-behavior] selected $f — this gate covers the two behavior-adjacent locations, never test/**"
|
|
3074
|
+
return 1
|
|
3075
|
+
;;
|
|
3076
|
+
pi-extensions/*.test.ts | plugins/herdr/*.test.mjs) ;;
|
|
3077
|
+
*)
|
|
3078
|
+
fail "[check-tests-beside-behavior] selected $f, which matches neither declared glob"
|
|
3079
|
+
return 1
|
|
3080
|
+
;;
|
|
3081
|
+
esac
|
|
3082
|
+
done
|
|
3083
|
+
|
|
3084
|
+
if [ "${#selected[@]}" -eq 0 ]; then
|
|
3085
|
+
echo " the door is open and unused — 0 tests beside behavior at this HEAD"
|
|
3086
|
+
return 0
|
|
3087
|
+
fi
|
|
3088
|
+
echo " selected ${#selected[@]}: ${selected[*]}"
|
|
3089
|
+
run_vitest "${selected[@]}"
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3078
3092
|
check_acp_overlay() {
|
|
3079
3093
|
# Deterministic gate for the S2b Claude config overlay materializer. Drives
|
|
3080
3094
|
# ensureClaudeConfigOverlay against injected temp realDir/overlayDir (no
|
|
@@ -3478,6 +3492,13 @@ check_pack() {
|
|
|
3478
3492
|
# tripwire that fails loud if that negation is ever dropped (결합 규칙).
|
|
3479
3493
|
'__pycache__'
|
|
3480
3494
|
'\.pyc$'
|
|
3495
|
+
# Tests written beside behavior (#119 V2) are a dev surface, and `pi-extensions/`
|
|
3496
|
+
# ships WHOLE through the files allowlist, so the `!pi-extensions/**/*.test.ts`
|
|
3497
|
+
# negation is the only thing keeping them out — same 결합 규칙 as __pycache__ above,
|
|
3498
|
+
# this pattern is its tripwire. Written loose rather than anchored under
|
|
3499
|
+
# pi-extensions/ so it also covers any future allowlist entry; `plugins/` needs no
|
|
3500
|
+
# companion negation because `^plugins/` above already refuses that whole tree.
|
|
3501
|
+
'\.test\.[cm]?[jt]s$'
|
|
3481
3502
|
)
|
|
3482
3503
|
|
|
3483
3504
|
local pass=1 f pat hit
|
|
@@ -3515,8 +3536,10 @@ check_pack() {
|
|
|
3515
3536
|
#
|
|
3516
3537
|
# (1) The version BOUNDARY. A pnpm .pnpm entry is `<name>@<version>` followed by either
|
|
3517
3538
|
# `_<peer-hash>` or end-of-name (measured pnpm 11.20.0 on this tree), so an unbounded
|
|
3518
|
-
# substring match would bless a lookalike
|
|
3519
|
-
#
|
|
3539
|
+
# substring match would bless a lookalike whose version merely STARTS with the pin — a
|
|
3540
|
+
# prerelease such as `@0.86.0-beta.1`, or the `@0.85.10` shape that first exposed this —
|
|
3541
|
+
# while announcing the pinned floor: a false-green oracle (found by independent review,
|
|
3542
|
+
# 2026-08-25).
|
|
3520
3543
|
# (2) The name PREFIX covers the whole @earendil-works closure, not the `pi-` families alone.
|
|
3521
3544
|
# 0.85.0 added `@earendil-works/chord`, declared as a runtime `dependencies` entry by
|
|
3522
3545
|
# pi-coding-agent, pi-agent-core, pi-client and pi-protocol — inside the closure, with a
|
|
@@ -3524,15 +3547,17 @@ check_pack() {
|
|
|
3524
3547
|
# `@earendil-works+chord@0.85.1` next to the seven pi entries, and the old
|
|
3525
3548
|
# `^@earendil-works+pi-` filter did not see it: chord would have floated silently while
|
|
3526
3549
|
# this gate printed a verified pin. The prefix is the closure's org, not one family in it.
|
|
3527
|
-
# (The measured peer-hash suffix
|
|
3528
|
-
# `
|
|
3529
|
-
#
|
|
3550
|
+
# (The measured peer-hash suffix keeps moving — `_@modelcontextprotocol+sdk@…` at 0.84.4,
|
|
3551
|
+
# `_ws@8.21.3` at 0.85.1, and `[측정 2026-09-20]` the combined
|
|
3552
|
+
# `_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6` on a 0.86.0 tree —
|
|
3553
|
+
# which is exactly what property (1)'s `(_|$)` absorbs; all three shapes are fixtures
|
|
3554
|
+
# below so a future suffix change cannot pass vacuously.)
|
|
3530
3555
|
pack_install_leaked_pi() {
|
|
3531
|
-
grep '^@earendil-works+' | grep -Ev '@0\.
|
|
3556
|
+
grep '^@earendil-works+' | grep -Ev '@0\.86\.0(_|$)' || true
|
|
3532
3557
|
}
|
|
3533
3558
|
|
|
3534
3559
|
# Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise either
|
|
3535
|
-
# false-green shape (it contains no
|
|
3560
|
+
# false-green shape (it contains no prefix-extended version and no off-pin chord), so the oracle is proven
|
|
3536
3561
|
# against fixture listings. One cell per property of pack_install_leaked_pi, so a regression
|
|
3537
3562
|
# in either is named by its own signature rather than folded into a single verdict. Exposed
|
|
3538
3563
|
# as its own snapshot-safe subcommand because the heavy check-pack-install cannot run inside
|
|
@@ -3542,30 +3567,33 @@ pack_install_leaked_pi() {
|
|
|
3542
3567
|
check_pack_pin_matcher() {
|
|
3543
3568
|
local matcher_probe
|
|
3544
3569
|
|
|
3545
|
-
# Cell 1 — the version boundary.
|
|
3546
|
-
# 0.84.4-era `_@modelcontextprotocol+sdk
|
|
3547
|
-
# 0.85.1 tree
|
|
3570
|
+
# Cell 1 — the version boundary. Every measured peer-hash shape is a fixture: the
|
|
3571
|
+
# 0.84.4-era `_@modelcontextprotocol+sdk@…`, the `_ws@8.21.3` observed on a real
|
|
3572
|
+
# 0.85.1 tree, and `[측정 2026-09-20]` the combined suffix a 0.86.0 install carries.
|
|
3573
|
+
# None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`0.86.0-beta.1`,
|
|
3574
|
+
# the prerelease shape that an unanchored match would bless) and an off-pin version.
|
|
3548
3575
|
matcher_probe=$(printf '%s\n' \
|
|
3549
|
-
'@earendil-works+pi-ai@0.
|
|
3550
|
-
'@earendil-works+pi-ai@0.
|
|
3551
|
-
'@earendil-works+pi-ai@0.
|
|
3552
|
-
'@earendil-works+pi-ai@0.
|
|
3553
|
-
'@earendil-works+pi-
|
|
3554
|
-
|
|
3555
|
-
@earendil-works+pi-
|
|
3556
|
-
|
|
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>}"
|
|
3557
3585
|
return 1
|
|
3558
3586
|
fi
|
|
3559
3587
|
|
|
3560
3588
|
# Cell 2 — the closure prefix. chord is a non-`pi-` member of the same runtime closure
|
|
3561
|
-
# (0.85.0 onward). An off-pin chord MUST leak; the pinned one must not. A matcher narrowed
|
|
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
|
|
3562
3590
|
# back to `^@earendil-works+pi-` sees nothing here and dies at this signature.
|
|
3563
3591
|
matcher_probe=$(printf '%s\n' \
|
|
3592
|
+
'@earendil-works+chord@0.86.0' \
|
|
3564
3593
|
'@earendil-works+chord@0.85.1' \
|
|
3565
|
-
'@earendil-works+
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
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/pi-protocol at 0.85.x) — got: ${matcher_probe:-<nothing leaked>}"
|
|
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>}"
|
|
3569
3597
|
return 1
|
|
3570
3598
|
fi
|
|
3571
3599
|
|
|
@@ -3798,7 +3826,7 @@ _check_pack_install_impl() {
|
|
|
3798
3826
|
printf '%s\n' '{ "name": "entwurf-install-smoke", "version": "0.0.0", "private": true }' > "$tmp/package.json"
|
|
3799
3827
|
|
|
3800
3828
|
# pi-agent-core is pinned even though we never import it: pi-coding-agent depends
|
|
3801
|
-
# on it by CARET (`^0.
|
|
3829
|
+
# on it by CARET (`^0.86.x`), so with no lockfile in this fresh temp project it
|
|
3802
3830
|
# floats to whatever pi published last — and that newer core then drags a NESTED
|
|
3803
3831
|
# pi-ai of its own. Measured 2026-07-21: pinning only the three we import left
|
|
3804
3832
|
# pi-agent-core@0.80.10 + pi-ai@0.80.10 in the tree while the gate still announced
|
|
@@ -3827,19 +3855,31 @@ _check_pack_install_impl() {
|
|
|
3827
3855
|
# It is pinned here for the first time. The leak assertion below covers every
|
|
3828
3856
|
# other @earendil-works package too — but only because the matcher's prefix was
|
|
3829
3857
|
# widened from `pi-` to the org in the same change; before that it was blind to
|
|
3830
|
-
# precisely this class of new member.
|
|
3831
|
-
|
|
3858
|
+
# precisely this class of new member.
|
|
3859
|
+
#
|
|
3860
|
+
# `[측정 2026-09-20]` at 0.86.0 the constellation SHRANK for the first time:
|
|
3861
|
+
# `npm view @earendil-works/pi-coding-agent@0.86.0 dependencies` lists only
|
|
3862
|
+
# {chord, pi-ai, pi-tui, pi-agent-core} — pi-client and pi-protocol are no
|
|
3863
|
+
# longer reachable from the three packages we import (pi-client still carries
|
|
3864
|
+
# {chord, pi-protocol}, pi-protocol still carries {chord}, both published at
|
|
3865
|
+
# 0.86.0). Their explicit pins stay: a pin that is momentarily redundant costs
|
|
3866
|
+
# two frozen rows in a temp tree, while retiring one is how a caret re-enters
|
|
3867
|
+
# the closure unwatched on the next minor — the same shape as the 2026-07-21
|
|
3868
|
+
# pi-agent-core incident. The list moves WITH the floor and is re-measured at
|
|
3869
|
+
# each bump, never pruned on a single quiet minor.
|
|
3870
|
+
# 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)"
|
|
3832
3872
|
local install_log
|
|
3833
3873
|
install_log=$(cd "$tmp" && pnpm add \
|
|
3834
3874
|
"$tgz_path" \
|
|
3835
|
-
"@earendil-works/pi-ai@0.
|
|
3836
|
-
"@earendil-works/pi-coding-agent@0.
|
|
3837
|
-
"@earendil-works/pi-tui@0.
|
|
3838
|
-
"@earendil-works/pi-agent-core@0.
|
|
3839
|
-
"@earendil-works/pi-client@0.
|
|
3840
|
-
"@earendil-works/pi-protocol@0.
|
|
3841
|
-
"@earendil-works/pi-telemetry@0.
|
|
3842
|
-
"@earendil-works/chord@0.
|
|
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" \
|
|
3843
3883
|
"typebox@latest" \
|
|
3844
3884
|
--ignore-workspace --ignore-scripts 2>&1) || {
|
|
3845
3885
|
fail "[check-pack-install] pnpm add failed:"
|
|
@@ -3849,17 +3889,17 @@ _check_pack_install_impl() {
|
|
|
3849
3889
|
|
|
3850
3890
|
# A pin is a wish until the resolved tree is read back. Assert it: EVERY
|
|
3851
3891
|
# @earendil-works package present — direct or transitive, top level or nested,
|
|
3852
|
-
# chord included — must be the pinned 0.
|
|
3892
|
+
# chord included — must be the pinned 0.86.0. Anything else means an unpinned caret
|
|
3853
3893
|
# floated and the rest of this gate would be exercising a runtime nobody verified,
|
|
3854
|
-
# while still printing "pinned pi 0.
|
|
3894
|
+
# while still printing "pinned pi 0.86.0". Fail loud instead of proving the wrong floor.
|
|
3855
3895
|
local leaked_pi
|
|
3856
3896
|
leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
|
|
3857
3897
|
if [ -n "$leaked_pi" ]; then
|
|
3858
|
-
fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.
|
|
3898
|
+
fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.86.0):"
|
|
3859
3899
|
printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
|
|
3860
3900
|
return 1
|
|
3861
3901
|
fi
|
|
3862
|
-
echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 0.
|
|
3902
|
+
echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 0.86.0 (chord included)"
|
|
3863
3903
|
|
|
3864
3904
|
# Resolve the installed package.json and confirm pi.extensions
|
|
3865
3905
|
# arrived intact. If pi.extensions is empty or missing, the
|
|
@@ -4665,7 +4705,7 @@ function finish(trimmed) {
|
|
|
4665
4705
|
// gate that exists to catch "green clone, dead consumer". Equality also catches the
|
|
4666
4706
|
// other direction the narrow-surface rule cares about: an extra or duplicated verb
|
|
4667
4707
|
// reaching hosts without a decision. Written sorted; `names` is sorted above.
|
|
4668
|
-
const EXPECT_TOOLS = ['entwurf_fresh_call', 'entwurf_inbox_read', 'entwurf_peers', 'entwurf_register_native', 'entwurf_resume_call', 'entwurf_self', 'entwurf_v2'];
|
|
4708
|
+
const EXPECT_TOOLS = ['entwurf_callback', 'entwurf_fresh_call', 'entwurf_inbox_read', 'entwurf_peers', 'entwurf_register_native', 'entwurf_resume_call', 'entwurf_self', 'entwurf_v2'];
|
|
4669
4709
|
if (names.length !== EXPECT_TOOLS.length || EXPECT_TOOLS.some((n, i) => names[i] !== n)) {
|
|
4670
4710
|
console.error('installed MCP tool set MISMATCH — want exactly [' + EXPECT_TOOLS.join(',') + '] got [' + names.join(',') + ']');
|
|
4671
4711
|
process.exit(1);
|
|
@@ -5224,12 +5264,12 @@ function finishOk(trimmed) {
|
|
|
5224
5264
|
}
|
|
5225
5265
|
const names = tools.map((t) => t?.name).sort();
|
|
5226
5266
|
// EXACT set, not a floor. This is the assertion behind the operator-facing claim that
|
|
5227
|
-
// `entwurf check-bridge` proves the installed bytes list the
|
|
5267
|
+
// `entwurf check-bridge` proves the installed bytes list the eight garden tools — and
|
|
5228
5268
|
// under an installed package this launcher IS the dist branch of start.sh, so a subset
|
|
5229
5269
|
// check here let an artifact missing entwurf_fresh_call / entwurf_resume_call read as a
|
|
5230
5270
|
// green bridge. Equality also refuses an undecided extra verb. Written sorted; `names`
|
|
5231
5271
|
// is sorted above.
|
|
5232
|
-
const expected = ['entwurf_fresh_call', 'entwurf_inbox_read', 'entwurf_peers', 'entwurf_register_native', 'entwurf_resume_call', 'entwurf_self', 'entwurf_v2'];
|
|
5272
|
+
const expected = ['entwurf_callback', 'entwurf_fresh_call', 'entwurf_inbox_read', 'entwurf_peers', 'entwurf_register_native', 'entwurf_resume_call', 'entwurf_self', 'entwurf_v2'];
|
|
5233
5273
|
if (names.length !== expected.length || expected.some((n, i) => names[i] !== n)) {
|
|
5234
5274
|
console.error(`MCP tool set MISMATCH — want exactly [${expected.join(',')}] got [${names.join(',')}]`);
|
|
5235
5275
|
process.exit(1);
|
|
@@ -6405,6 +6445,9 @@ case "$cmd" in
|
|
|
6405
6445
|
check-copilot-launch)
|
|
6406
6446
|
check_copilot_launch
|
|
6407
6447
|
;;
|
|
6448
|
+
check-pi-launch)
|
|
6449
|
+
check_pi_launch
|
|
6450
|
+
;;
|
|
6408
6451
|
check-copilot-receive-arm)
|
|
6409
6452
|
check_copilot_receive_arm
|
|
6410
6453
|
;;
|
|
@@ -6420,33 +6463,12 @@ case "$cmd" in
|
|
|
6420
6463
|
check-entwurf-v2-contract)
|
|
6421
6464
|
check_entwurf_v2_contract
|
|
6422
6465
|
;;
|
|
6423
|
-
check-entwurf-v2-lock)
|
|
6424
|
-
check_entwurf_v2_lock
|
|
6425
|
-
;;
|
|
6426
|
-
check-entwurf-v2-decider)
|
|
6427
|
-
check_entwurf_v2_decider
|
|
6428
|
-
;;
|
|
6429
|
-
check-entwurf-v2-matrix)
|
|
6430
|
-
check_entwurf_v2_matrix
|
|
6431
|
-
;;
|
|
6432
|
-
check-entwurf-v2-release)
|
|
6433
|
-
check_entwurf_v2_release
|
|
6434
|
-
;;
|
|
6435
6466
|
check-entwurf-v2-send)
|
|
6436
6467
|
check_entwurf_v2_send
|
|
6437
6468
|
;;
|
|
6438
|
-
check-entwurf-v2-send-fallback)
|
|
6439
|
-
check_entwurf_v2_send_fallback
|
|
6440
|
-
;;
|
|
6441
|
-
check-entwurf-v2-mailbox)
|
|
6442
|
-
check_entwurf_v2_mailbox
|
|
6443
|
-
;;
|
|
6444
6469
|
check-entwurf-v2-native-push)
|
|
6445
6470
|
check_entwurf_v2_native_push
|
|
6446
6471
|
;;
|
|
6447
|
-
check-entwurf-v2-runner)
|
|
6448
|
-
check_entwurf_v2_runner
|
|
6449
|
-
;;
|
|
6450
6472
|
check-entwurf-control-rpc)
|
|
6451
6473
|
check_entwurf_control_rpc
|
|
6452
6474
|
;;
|
|
@@ -6567,15 +6589,9 @@ case "$cmd" in
|
|
|
6567
6589
|
smoke-acp-socket-citizen-live)
|
|
6568
6590
|
smoke_acp_socket_citizen_live
|
|
6569
6591
|
;;
|
|
6570
|
-
check-entwurf-facts)
|
|
6571
|
-
check_entwurf_facts
|
|
6572
|
-
;;
|
|
6573
6592
|
check-control-socket-path)
|
|
6574
6593
|
check_control_socket_path
|
|
6575
6594
|
;;
|
|
6576
|
-
check-socket-discovery)
|
|
6577
|
-
check_socket_discovery
|
|
6578
|
-
;;
|
|
6579
6595
|
check-meta-listing)
|
|
6580
6596
|
check_meta_listing
|
|
6581
6597
|
;;
|
|
@@ -6597,6 +6613,20 @@ case "$cmd" in
|
|
|
6597
6613
|
smoke-herdr-plugin-build-live)
|
|
6598
6614
|
smoke_herdr_plugin_build_live
|
|
6599
6615
|
;;
|
|
6616
|
+
smoke-herdr-raw-install-live)
|
|
6617
|
+
# #118 홉 1: the FIRST USER PATH. Every other herdr gate proves the plugin against a
|
|
6618
|
+
# source we control — a stub, or the product remote redirected by git insteadOf to a
|
|
6619
|
+
# local bare clone asserting identity.kind === "herdr-checkout". None has watched the
|
|
6620
|
+
# plugin acquire its runtime from npm on a machine that has never seen this repo, which
|
|
6621
|
+
# is the one axis VERIFY.md:61 leaves open while the production lock says npm. Clean
|
|
6622
|
+
# container, public remote, no mounts, no insteadOf. Its first question is whether
|
|
6623
|
+
# `plugin install --yes` completes with no herdr server; a "no" is #118 investigation
|
|
6624
|
+
# output, not a completion. on-demand LIVE, never an aggregate MUST.
|
|
6625
|
+
# shift is load-bearing: this dispatcher keeps the verb in "$@" and each branch that
|
|
6626
|
+
# forwards arguments drops it for itself. Without it `--ref` arrives behind the gate name.
|
|
6627
|
+
shift
|
|
6628
|
+
(cd "$REPO_DIR" && bash scripts/smoke-herdr-raw-install-live.sh "$@")
|
|
6629
|
+
;;
|
|
6600
6630
|
check-peer-facts)
|
|
6601
6631
|
check_peer_facts
|
|
6602
6632
|
;;
|
|
@@ -7112,6 +7142,54 @@ case "$cmd" in
|
|
|
7112
7142
|
shift || true
|
|
7113
7143
|
exec bash "$REPO_DIR/scripts/copilot-launch.sh" "$@"
|
|
7114
7144
|
;;
|
|
7145
|
+
pi)
|
|
7146
|
+
# #118 홉 2: the managed pi launch. Same shape and same reasons as `copilot` above —
|
|
7147
|
+
# `exec`, no subshell and no cd, because this operates on the operator's session; and
|
|
7148
|
+
# `shift`, because this branch forwards straight into the VENDOR argv where a stray
|
|
7149
|
+
# "pi" would arrive as a prompt argument.
|
|
7150
|
+
#
|
|
7151
|
+
# NO separate launcher script, and no precondition check, because the precondition is
|
|
7152
|
+
# ZERO and that was measured rather than assumed: on a host where entwurf's pi extension
|
|
7153
|
+
# is not registered, `pi --entwurf-control` already fails as
|
|
7154
|
+
# `Error: Unknown option: --entwurf-control` with exit 1. A pre-check here would only
|
|
7155
|
+
# duplicate a refusal pi owns and would go stale the day pi renames it.
|
|
7156
|
+
#
|
|
7157
|
+
# ONE recursion fence, not copilot's two, and the difference is measured. The sentinel
|
|
7158
|
+
# closes the only real loop: a PATH executable named `pi` that shells back to
|
|
7159
|
+
# `entwurf pi` re-enters this branch, and finds the flag already exported. Copilot's
|
|
7160
|
+
# second fence — resolving the binary and refusing our own entrypoints — closes a case
|
|
7161
|
+
# that is not a loop here: a PATH `pi` symlinked to run.sh would be exec'd as
|
|
7162
|
+
# `run.sh --entwurf-control …`, which is an unknown verb this dispatcher already
|
|
7163
|
+
# refuses. `type -P` is what keeps the operator's own shell wrappers out of it
|
|
7164
|
+
# entirely: it returns a PATH file only, never a function or alias, so a
|
|
7165
|
+
# `pi() { entwurf pi "$@"; }` in a profile can never be re-entered through it.
|
|
7166
|
+
#
|
|
7167
|
+
# The flag is added, never deduplicated: `pit`/`pius`-style wrappers already pass
|
|
7168
|
+
# `--entwurf-control`, and passing it twice was measured to be byte-identical to
|
|
7169
|
+
# passing it once. An argv scan would be code earning nothing.
|
|
7170
|
+
#
|
|
7171
|
+
# Nothing else is injected. `--emacs-agent-socket` and friends are operator taste, and
|
|
7172
|
+
# a launcher that decides them stops being a spelling (Hard Rule 9).
|
|
7173
|
+
shift || true
|
|
7174
|
+
if [ -n "${ENTWURF_PI_LAUNCH_ACTIVE:-}" ]; then
|
|
7175
|
+
echo "entwurf: recursive managed launch detected (ENTWURF_PI_LAUNCH_ACTIVE is already set)." >&2
|
|
7176
|
+
echo " Something on PATH named 'pi' resolves back to this launcher. Fix PATH so 'pi'" >&2
|
|
7177
|
+
echo " is the pi coding agent, or run the vendor binary by its full path." >&2
|
|
7178
|
+
exit 1
|
|
7179
|
+
fi
|
|
7180
|
+
entwurf_pi_bin="$(type -P pi 2>/dev/null || true)"
|
|
7181
|
+
if [ -z "$entwurf_pi_bin" ]; then
|
|
7182
|
+
echo "entwurf: no 'pi' executable found on PATH." >&2
|
|
7183
|
+
echo " 'entwurf pi' launches the pi coding agent; it does not install one." >&2
|
|
7184
|
+
exit 1
|
|
7185
|
+
fi
|
|
7186
|
+
if [ ! -f "$entwurf_pi_bin" ] || [ ! -x "$entwurf_pi_bin" ]; then
|
|
7187
|
+
echo "entwurf: '$entwurf_pi_bin' is not an executable regular file — refusing to exec it." >&2
|
|
7188
|
+
exit 1
|
|
7189
|
+
fi
|
|
7190
|
+
export ENTWURF_PI_LAUNCH_ACTIVE=1
|
|
7191
|
+
exec "$entwurf_pi_bin" --entwurf-control "$@"
|
|
7192
|
+
;;
|
|
7115
7193
|
install-copilot-receive)
|
|
7116
7194
|
# #82 RAIL 5: install the RECEIVER extension into the Copilot USER extensions dir.
|
|
7117
7195
|
# Artifact ownership only — it sets no launch flag (`run.sh copilot` owns one
|
|
@@ -7349,6 +7427,9 @@ case "$cmd" in
|
|
|
7349
7427
|
check-acp-sdk-surface)
|
|
7350
7428
|
check_acp_sdk_surface
|
|
7351
7429
|
;;
|
|
7430
|
+
check-tests-beside-behavior)
|
|
7431
|
+
check_tests_beside_behavior
|
|
7432
|
+
;;
|
|
7352
7433
|
check-acp-overlay)
|
|
7353
7434
|
check_acp_overlay
|
|
7354
7435
|
;;
|