@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.
Files changed (102) hide show
  1. package/AGENTS.md +3 -1
  2. package/BASELINE.md +2 -0
  3. package/CHANGELOG.md +307 -0
  4. package/FAQ.md +47 -0
  5. package/README.md +5 -3
  6. package/VERIFY.md +43 -2
  7. package/demo/README.md +1 -1
  8. package/docs/acp-backend-rail.md +97 -18
  9. package/docs/setup-clean-host.md +8 -7
  10. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +33 -1
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +19 -5
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +2 -1
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/callback-env.js +68 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +2 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +1 -1
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +54 -22
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +7 -3
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +13 -4
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +19 -10
  21. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +35 -42
  22. package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
  23. package/mcp/entwurf-bridge/src/index.ts +41 -1
  24. package/mcp/entwurf-bridge/test.sh +2 -2
  25. package/package.json +15 -13
  26. package/pi-extensions/entwurf-control.ts +56 -0
  27. package/pi-extensions/lib/acp/acp-client.ts +3 -3
  28. package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
  29. package/pi-extensions/lib/acp/backend.ts +20 -10
  30. package/pi-extensions/lib/acp/context.ts +30 -10
  31. package/pi-extensions/lib/acp/event-mapper.ts +5 -5
  32. package/pi-extensions/lib/acp/session-store.ts +2 -2
  33. package/pi-extensions/lib/acp/tool-surface.ts +2 -1
  34. package/pi-extensions/lib/callback-env.ts +78 -0
  35. package/pi-extensions/lib/compaction-send-guard.ts +4 -1
  36. package/pi-extensions/lib/entwurf-v2-contract.ts +2 -2
  37. package/pi-extensions/lib/entwurf-v2-lock.ts +1 -1
  38. package/pi-extensions/lib/entwurf-v2-runner.ts +1 -1
  39. package/pi-extensions/lib/fresh-call-composition.ts +56 -22
  40. package/pi-extensions/lib/herdr-fresh-call.ts +12 -4
  41. package/pi-extensions/lib/herdr-placement.ts +13 -4
  42. package/pi-extensions/lib/mux-fresh-call.ts +23 -8
  43. package/pi-extensions/meta-bridge-omp.ts +35 -48
  44. package/run.sh +327 -246
  45. package/scripts/check-acp-backend-preflight.ts +117 -9
  46. package/scripts/check-acp-carrier-augment.ts +6 -4
  47. package/scripts/check-acp-event-mapper.ts +17 -6
  48. package/scripts/check-acp-prompt-builder.ts +17 -9
  49. package/scripts/check-acp-prompt-lifecycle.ts +23 -6
  50. package/scripts/check-acp-session-reuse.ts +64 -51
  51. package/scripts/check-acp-session-store.ts +31 -8
  52. package/scripts/check-acp-stop-reason.ts +20 -2
  53. package/scripts/check-acp-stream-hooks.ts +22 -5
  54. package/scripts/check-acp-usage-accounting.ts +42 -18
  55. package/scripts/check-entwurf-bridge-boot.ts +16 -1
  56. package/scripts/check-fresh-call-dispatch.ts +4 -5
  57. package/scripts/check-gate-qualification.ts +45 -5
  58. package/scripts/check-herdr-activation.ts +73 -0
  59. package/scripts/check-herdr-fresh-call.ts +22 -6
  60. package/scripts/check-herdr-sandbox.ts +14 -2
  61. package/scripts/check-install-container.sh +1 -1
  62. package/scripts/check-pi-launch.ts +361 -0
  63. package/scripts/check-release-gate-outcomes.ts +7 -0
  64. package/scripts/inventory-verification-surface.ts +86 -10
  65. package/scripts/lib/mutation-qualify.ts +1 -1
  66. package/scripts/mutants/acp-backend-preflight.json +20 -0
  67. package/scripts/mutants/bridge-boot-resume.json +26 -0
  68. package/scripts/mutants/herdr-activation.json +16 -0
  69. package/scripts/mutants/herdr-fresh-call.json +11 -0
  70. package/scripts/mutants/herdr-plugin-build.json +1 -1
  71. package/scripts/mutants/mux-fresh-call.json +21 -8
  72. package/scripts/mutants/omp-fresh.json +5 -9
  73. package/scripts/mutants/pack-install.json +3 -3
  74. package/scripts/mutants/pi-launch.json +113 -0
  75. package/scripts/mutants/resume-args.json +6 -6
  76. package/scripts/mutants/v2-surface.json +2 -2
  77. package/scripts/mutants/v2-visible-resume.json +17 -17
  78. package/scripts/probe-bridge-command.ts +1 -0
  79. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  80. package/scripts/smoke-acp-session-reuse-live.ts +23 -5
  81. package/scripts/smoke-agy-install-state.sh +2 -2
  82. package/scripts/smoke-copilot-mcp-state.sh +1 -1
  83. package/scripts/smoke-entwurf-v2-matrix-live.ts +1 -1
  84. package/scripts/smoke-herdr-fresh-call-live.ts +56 -28
  85. package/scripts/smoke-herdr-raw-install-live.sh +877 -0
  86. package/scripts/smoke-omp-fresh-live.ts +4 -2
  87. package/scripts/smoke-omp-mcp-state.sh +1 -1
  88. package/scripts/smoke-pi-provider-state.sh +2 -2
  89. package/scripts/tsconfig.json +5 -0
  90. package/scripts/check-entwurf-facts.ts +0 -391
  91. package/scripts/check-entwurf-resume-args.ts +0 -122
  92. package/scripts/check-entwurf-v2-decider.ts +0 -657
  93. package/scripts/check-entwurf-v2-lock.ts +0 -371
  94. package/scripts/check-entwurf-v2-mailbox.ts +0 -261
  95. package/scripts/check-entwurf-v2-matrix.ts +0 -401
  96. package/scripts/check-entwurf-v2-native-push.ts +0 -221
  97. package/scripts/check-entwurf-v2-release.ts +0 -151
  98. package/scripts/check-entwurf-v2-runner.ts +0 -448
  99. package/scripts/check-entwurf-v2-send-fallback.ts +0 -350
  100. package/scripts/check-entwurf-v2-send.ts +0 -458
  101. package/scripts/check-entwurf-v2-visible-resume.ts +0 -445
  102. package/scripts/check-socket-discovery.ts +0 -401
package/AGENTS.md CHANGED
@@ -82,6 +82,8 @@ Keep at most ten open issues, including at most five implementation issues. Clas
82
82
 
83
83
  Two independent axes are required: deterministic/package gates and opt-in LIVE evidence. Exact commands, evidence levels, and release acceptance are owned by [VERIFY.md](./VERIFY.md); recorded host evidence by [BASELINE.md](./BASELINE.md). Run LIVE gates from scratch with ambient identity carriers stripped so artifacts and callbacks cannot bleed from the operator session.
84
84
 
85
+ A new contract's test goes beside the behavior it certifies — `pi-extensions/**/*.test.ts` or `plugins/herdr/**/*.test.mjs` — and `check-tests-beside-behavior` discovers it by glob, so landing one edits no filename list. `scripts/check-*` stays the home of LIVE, install, spawning and mixed gates; a new hand-built gate there needs a reason the vitest lane could not carry it.
86
+
85
87
  ### Scheduling
86
88
 
87
89
  ```text
@@ -97,7 +99,7 @@ implement → affected focused gates → independent review → one amendment bu
97
99
 
98
100
  ## Type and Working Boundaries
99
101
 
100
- - Each `.ts` file belongs to one declared typecheck fence; never hide a file with `exclude`. Root pi extensions use TypeBox through `@earendil-works/pi-ai`; MCP/scripts use explicit `.ts` imports required by Node strip-types. Installed surfaces route to compiled JS.
102
+ - Each `.ts` file belongs to one declared typecheck fence; `exclude` may move a file between fences but never out of all of them, so an exclude states the receiving `include` in the same change. Root pi extensions use TypeBox through `@earendil-works/pi-ai`; MCP/scripts use explicit `.ts` imports required by Node strip-types. Installed surfaces route to compiled JS. Behavior-adjacent `.test.ts` takes the fence `test/` takes — `scripts/tsconfig.json`, never the emit-capable root program — and is kept out of the tarball by a files-array negation whose tripwire is `check-pack`.
101
103
  - Make surgical, one-contract changes. Removal repairs source and its proof together; a green gate never proves retired behavior.
102
104
  - Before commit, sweep repository-wide for retired authority vocabulary and landed-plan future tense. Historical tombstones may remain; live docs, source, gates, and usage must agree.
103
105
  - Prefer capability-domain language over identity rank. Use tabs unless the project formatter requires otherwise. GLG decides commit, push, and release gates.
package/BASELINE.md CHANGED
@@ -34,6 +34,8 @@ not **UNSUPPORTED** — the deliberate never, which today is native Windows only
34
34
  | Maintainer NixOS installed package | **certified** for `0.12.8-repair.1` | 2026-07-25 registry install → doctor exit 0 (HISTORY) |
35
35
  | Secondary Ubuntu installed package | **certified** for `0.12.8-repair.1` | 2026-07-25 same artifact, isolated agent dir → doctor exit 0 (HISTORY) |
36
36
  | macOS Claude meta-bridge | NOT CERTIFIED — pending physical host | No physical-Mac doctor yet; a CI runner has no Claude login. Physical-host rental remains unscheduled; no cut has carried it. |
37
+ | 0.23.1 release acceptance (observer-amended) | accepted by GLG | `pnpm run check:full` P4 exit 0 (577 s); second aggregate `release-gate --cut` at `/tmp/entwurf-release-gate-0.23.1-retry.UIFctR` reached MUST 23/1/0 and BEHAVIOR 1/0, with only `smoke-codex-fresh-live` red because luna dropped `$`/`@` sigils in its relay (`release-gate.log:6156-6157`). The observer-amended standalone cell reran that exact leg with Codex `gpt-5.6-terra`, Pi `openai-codex/gpt-5.6-luna`: 66 assertions, exit 0; A app-server PID `3969303` `$227` ≠ fixture S `$231`; artifact `.probe-artifacts/codex-fresh-live-eZmf6p/`; pipe-pane stdout sha256 `56ee73e046e16fa95ef093fccf037670bf28b53a054bb5eec2a3b3b3ff08d93a`. Luna sigil handling is a post-cut issue, not a production-byte change. |
38
+ | 0.24.0 release acceptance (observer-amended) | accepted by GLG | `pnpm run build-bridge` then `pnpm run check:full` P4 exit 0 (594 s) on the prepared tree; aggregate `release-gate --cut` at `/tmp/entwurf-release-gate-0.24.0.anVBvk` (1h34m) reached MUST 22/2/0 and BEHAVIOR 1/0 with `check-gate-qualification` 739/739 killed at the candidate (`release-gate.log:5507`). The two MUST reds: `smoke-codex-fresh-live` (the Pi→Codex `<placeholder>` template relay audited for byte equality, ROADMAP 0.24.0 follow-up #3, same class as the 0.23.1 amendment) and `smoke-entwurf-chain-live` — delivery A→B (`entwurf_v2 control-socket → sent`) and B→C (B's sender envelope in C's stream) both landed, then hop 3 (ACP `claude-sonnet-5`) declined the relay on principle, naming the scratch `AGENTS.md` authority as self-vouching content (`release-gate.log:5841`); the same class hop 1 refused on the 0.22.0 gate (`f5d6e10`), not rerun (a second run is a coin flip on a model's judgement), ROADMAP follow-up #5. Standalone LIVE before the aggregate, each once with ambient identity stripped: mux-fresh-call 39/39, mux-lifecycle 81/81, omp-receive 11/11, herdr-fresh-call 31/32 after `149bef7` (the red is a claude child that called back and never attempted delivery, follow-up #1), omp-fresh 21/21 after `install-omp-bridge` (stale v1 birth unit on the host, doctor named it). P9 after the aggregate: 548 → 546 dirs, 2 roots / 756K reclaimed, operator store and control-socket dir unchanged. GLG: "릴리즈 하는데 문제 없는것 같은데? 기록하고 진행하자." |
37
39
  | Native Codex Linux caller-DIRECTORY axis (current; #95 lane C) | **certified** | 2026-09-16, Codex 0.153.4: 65-assertion LIVE, exit 0, at `b3e07dc` with a clean worktree. Same host and topology as the lane B row below (app-server `737636` in A `$30/@41/%45`; initial Pi `20260916T154902-556eb5` `$2/@58`, Codex `20260916T155037-10ddaa` `$2/@59`, Codex-opened Pi `20260916T155102-9add8a` `$2/@61`, `seat-source=codex-title-anchor`). **hop 1 (cwd requested):** pane `#{pane_current_path}` = rollout `session_meta.cwd` = record cwd = requested scratch = `/tmp/entwurf-codex-fresh-live-db65N2`, against app-server cwd `/home/junghan/repos/gh/entwurf` read from `/proc/737636/cwd`. **hop 2 (cwd NOT requested):** the outbound Pi's pane, its own birth-written record and the Codex caller's record all read that same scratch, and its receipt names the caller-record rule. Artifact `.probe-artifacts/codex-fresh-live-w4yJBw/`; stdout `.probe-artifacts/lane-c-live-20260916T154900.log` sha256 `712050e7a8cf03ece98e7f34029ae98a92e9b2a7fd96aff87d0c1e6ba2ee9af2`. Cleanup reclaimed `@58`/`@59`/`@61` with no failure; records and transcripts preserved; app-server and `$30` untouched. 65 and 56 are different contracts — lane C added the cwd axis on top of lane B. Long floors (qualification body, frozen `check:full`) NOT run at this SHA; GLG decides host vs CI. |
38
40
  | Native Codex Linux caller-seat topology (current; #95 lane B) | **certified** | 2026-09-16, Codex 0.153.4: 56-assertion LIVE, exit 0. App-server `737636` alone in session A `$30/@41/%45`; initial Pi `20260916T144205-e876ff` `$2/@46`, omitted-placement Codex `20260916T144230-d050d7` `$2/@47`, Codex-opened Pi `$2/@48`. A ≠ S is the decisive half: the app-server env names A, so S can only have come from the caller's pane title — receipt `seat-source=codex-title-anchor`. Artifact `.probe-artifacts/codex-fresh-live-nYcGC1/run-stdout.log` sha256 `ed60c2bd…`; the 43-assertion hop-1 run that measured the D1 retirement is kept at `codex-fresh-live-4aFCDD/run-stdout.log` sha256 `8392a603…`. Cleanup reclaimed `@46`/`@47`/`@48` with no failure; app-server and `$30` untouched. |
39
41
  | Native Codex Linux home topology (PAST CONTRACT — #95 D1 retired the fixed home 2026-09-16) | **certified** for 0.21.0 as shipped | 2026-09-12, Codex 0.153.4: 57-assertion explicit-home LIVE. Initial Pi `$150/@397`; operator app-server `$158/@390/%390`; omitted-placement Codex `$158/@398`; Codex-opened Pi `$158/@399`; exact callbacks and addressed delivery both ways. Receipt and digest: `DELIVERY.md`. Deterministic closure of that axis: qualification 460/460 across 43 lanes with origin/snapshot purity green, then frozen full floor exit 0 in 506s — both pre-amendment. **Observer-amended acceptance, 2026-09-12:** standalone LIVE 48 assertions exit 0, source audit `initial-pi=3/3`/`codex=3/3` completed exact, initial Pi `$150/@431`, Codex `$158/@432`, Codex-opened Pi `$158/@433`, all three reclaimed by their own source receipts; artifact `.probe-artifacts/codex-fresh-live-fZccoK/`. The 48 and 57 counts are different contracts, not a regression. **Release acceptance at that same fingerprint:** `check:full` exit 0 502s, LIVE release gate `--cut` MUST 24/0/0 · BEHAVIOR 1/0/0 · `cut: OK`, `check-gate-qualification` 475/475 across 43 lanes with origin purity green, and the aggregate's Codex leg repeating the 48-assertion acceptance (artifact `codex-fresh-live-2oId4C`). Unrestricted attached-TUI placement remains unsupported and unclaimed. |
package/CHANGELOG.md CHANGED
@@ -4,6 +4,313 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.24.0 - 2026-09-20
8
+
9
+ ### Added
10
+
11
+ - **`entwurf_callback` — the eighth garden verb, and it takes no arguments.** The fresh-call
12
+ callback was the one place in the garden where the transcript was the address axis: the caller
13
+ garden id and the launch nonce reached a new sibling only as prose in its first prompt, and the
14
+ sibling had to retype both into `entwurf_v2`. Three releases of symptoms were one cause — 0.23.1
15
+ dropped `$`/`@` sigils, this lane's first release gate filled in `<placeholders>` early, its second lost the
16
+ trailing character of a garden id (`…-26428c` → `…-26428`, refused correctly by the decider) —
17
+ and the protocol had no error-detecting property: a mangled id is indistinguishable from a
18
+ different one, so the decider could only refuse, never repair. GLG's ruling: "luna만 못하는게
19
+ 아니라 구조적 문제". Now both launch rails inject `ENTWURF_CALLBACK_TARGET` and
20
+ `ENTWURF_CALLBACK_NONCE` beside the identity scrub (tmux `-e` in `mux-fresh-call`,
21
+ `tab create --env` in `herdr-fresh-call`), computed by the rail and never caller-chosen;
22
+ `callback-env.ts` is the single leaf that formats and parses the pair, so a drifted regex cannot
23
+ inject what the verb rejects; and `entwurf_callback {}` reads the pair from its own process,
24
+ validates garden-id and nonce grammar (exactly what `mintNonce` emits), and dispatches through
25
+ the existing v2 runner so the target is still re-resolved by record and decider (Hard Rule 2).
26
+ Absent or malformed env is a named refusal with no fallback to a model-supplied target (Hard
27
+ Rule 5). Framing for pi, claude-code, copilot and omp now says only "call the callback with no
28
+ arguments"; a separate `FRESH_CALL_DELIVERY_TOOL` names the v2 dialect for the task result, and
29
+ Claude's `--allowedTools` and ToolSearch hint carry callback, delivery and peers together (the
30
+ review found the first cut loaded only two, which would have left a claude-code sibling unable
31
+ to send its result). OMP's bootstrap payload moves to v2 with the closed shape `{v, task}`; a
32
+ leftover `target`/`nonce` key is refused. Measured before the seam was written: the
33
+ entwurf-bridge MCP child inherits the pane env for claude-code, copilot and omp (canary in
34
+ `/proc/<child>/environ`, private tmux, fixture roots); pi is in-process. **Codex is the named
35
+ exception:** the bridge serving a Codex tool call is the operator app-server's child (ppid
36
+ measured) and its identity is request-scoped `_meta.threadId`, so a window env never reaches
37
+ it; `entwurf_callback` refuses there as `codex-callback-env-unsupported`, the Codex framing
38
+ keeps the argument form and says why, and a per-thread carrier is a follow-up after a
39
+ measurement that does not exist yet. The separate Pi→Codex template relay in
40
+ `codex-fresh-live-protocol.ts` (a semantic `<placeholder>` template pushed through a model and
41
+ audited for byte equality) is recorded, not fixed. Surface gates moved with the verb
42
+ (`check-entwurf-bridge-boot` exact eight-verb set + `QK:BRIDGEBOOT-CALLBACK-ZERO-ARG`,
43
+ `check-probe-bridge-command`, `check-bridge`, install/state smokes, README/VERIFY); manifests
44
+ 735 → 739 mutants, 59 lanes. Commits `cb86d44`, `ff09522`, `ca04c1b`; designs by
45
+ `claude-opus-5` and `openai-codex/gpt-5.6-terra`, Codex verdict and implementation by
46
+ `xai/grok-4.6`, closing review by a fresh `claude-opus-5`.
47
+ - **`plugins/herdr` 0.4.0.** The plugin's install-time build now reaches a pi range check
48
+ (`install-user-scope`, above), its README installs pi at the published peer range, and its
49
+ runtime lock rides the 0.24.0 candidate window. A minor rather than a patch because a host that
50
+ activated 0.3.1 against an out-of-range pi was silently accepted and is now refused by name.
51
+
52
+ ### Changed
53
+
54
+ - **pi floor moves to 0.86.0 (`>=0.86.0 <0.87`), and the ACP provider reads the 0.86 shape
55
+ honestly.** pi 0.86 hands a custom provider a branded `TranscriptContext`: `systemPrompt`
56
+ and `tools` are folded into a leading `role:"system"` message (`normalizeContext`,
57
+ pi-mono `model-runtime.ts:639-642`, `v0.86.0` = `ecac0a9c`). The ACP preflight read
58
+ `context.tools`, which is always `undefined` there, and fell back to the full builtin set —
59
+ so `assertExcludeToolsHonored` could never fire, while `tsc` (the old parameter type is
60
+ assignable) and `check-acp-backend-preflight` (a 0.85-shaped literal fed straight to
61
+ `streamAcpTurn`) both stayed green. Measured on real `pi-ai@0.86.0`: raw Context with `bash`
62
+ excluded → THROW; the same input through `normalizeContext` → PASS with no throw. The adapter
63
+ now replays the transcript (`getCurrentTools(context.messages)`, no fallback — an empty
64
+ replayed list is "the operator excluded everything" and is rejected as such),
65
+ `renderMessage` drops system messages by an explicit `case` so the never-forward-systemPrompt
66
+ invariant is a decision rather than a `default`, and every provider-path gate builds its
67
+ fixture through `normalizeContext()`. New lane `acp-backend-preflight`
68
+ (`[QK:ACP-PREFLIGHT-REPLAYS-TRANSCRIPT-TOOLS]`, exact-once mutant = the old fallback).
69
+ Session-reuse signatures now hash the folded leading system message, so a record persisted
70
+ under 0.85.1 is not a prefix of a 0.86 turn and falls back to `new` (the designed safe path).
71
+ - **Pin move, with every mirror.** devDeps 0.86.0; peers `>=0.86.0 <0.87`; the eight explicit
72
+ `check-pack-install` pins and its leak regex (`@0\.86\.0(_|$)`, boundary lookalike now the
73
+ prefix-extended `0.86.0-beta.1`); lockfile (typebox resolves to a single 1.3.27); baseline
74
+ docs; ROADMAP bump ledger. `check-dep-versions` now excludes dated ledger entries from its
75
+ baseline scan (`stripBumpLedger`, guarded both ways), because a ledger line is a receipt of
76
+ the range that landed THAT day and the first bump after an entry is written is what exposed
77
+ it. Vendor floors were re-measured rather than carried: a real 0.86.0 session file
78
+ (`herdr-placement.ts`), the installed `types.d.ts` (`compaction-send-guard.ts`), the
79
+ unregistered `--entwurf-control` refusal (`check-pi-launch.ts:18`); the registered-host
80
+ duplicate-flag receipt at `check-pi-launch.ts:29` stays dated 0.85.1 with the reason.
81
+ Constellation shrank for the first time (`pi-client`/`pi-protocol` left pi-coding-agent's
82
+ direct deps at 0.86.0); all eight pins are kept on purpose — a retired pin is how a caret
83
+ re-enters unwatched.
84
+ - **The two doors onto one pi registration tell the same truth (#119).** `entwurf setup`
85
+ refused an out-of-range pi by name, but `run.sh install-user-scope` — the verb herdr plugin
86
+ activation actually reaches (`build.mjs` → `herdr-plugin-activate.mjs`) — had no range check
87
+ and wrote the citizen in silence. It now enforces the same closed range through the same
88
+ helpers (`pi_supported_range`/`pi_version_in_range`, one parser), refuses with
89
+ `[install-user-scope] pi: FAIL … outside the supported range` and zero bytes written; absent
90
+ pi is deliberately unchanged (this verb invents no presence verdict). `check-herdr-activation`
91
+ gains `[QK:HAC-PI-FLOOR-BOTH-DOORS]` (24 → 25, the out-of-range/in-range PAIR, fixtures
92
+ derived from the package.json pin) and pins `PI_BIN` in its sandbox `world()` — without that
93
+ the gate's verdict depended on the operator's installed pi. `plugins/herdr/README.md` now
94
+ installs pi at the published peer range and joined `check-dep-versions` BASELINE_DOCS, so
95
+ the number cannot drift. Since 0.86.0 published (2026-09-19T23:15Z) the old unpinned line
96
+ installed a pi the shipped range rejects; the plugin had no known installs, so the repair is
97
+ a correction, not a migration.
98
+ - **Provider-path fixtures are built through `normalizeContext`, and a cast past the brand is
99
+ now a red gate.** The full floor on the bump commit went red in four ACP gates
100
+ (`check-acp-stop-reason`, `-prompt-lifecycle`, `-stream-hooks`, `-usage-accounting`) as
101
+ `'error' !== 'done'` — the new preflight correctly rejecting seven fixture helpers that cast a
102
+ tool-less literal `as Context`, which is exactly the operation that silences the
103
+ `TranscriptContext` brand at typecheck time. Every helper now declares the four pi builtins
104
+ through `normalizeContext`; no production source or assertion text changed.
105
+ `check-acp-backend-preflight` gains a static sweep (`[QK:ACP-FIXTURE-NO-CONTEXT-CAST]`) over
106
+ `scripts/check-acp-*.ts` and the lib tests so that class cannot come back silently; no mutant
107
+ by decision (the claim is the absence of a token and has no production subject — its
108
+ kill-proof is an injected offender named by `file:line`). Full floor on `5d3140c`:
109
+ `check:full` exit 0 in 598s. Manifests: 733 → 735 mutants, 58 → 59 lanes.
110
+ - **The first release gate of this lane (then numbered 0.23.2) was red on three MUST steps, and
111
+ two of them were this lane's own holes** (`/tmp/entwurf-release-gate-0.23.2.3s5A0o`, `MUST: PASS=21 FAIL=3 SKIP=0`).
112
+ `check-gate-qualification` 733/735: both `pack-install` mutants still quoted the old run.sh
113
+ `@0\.85\.1(_|$)` line after the pin mirror moved it (MUTANT-STALE), and nothing in
114
+ `check:full` looks at whether a `find` still matches — `check-gate-manifests` proved the
115
+ subject was tracked but never opened it. The finds now mirror run.sh byte-exact and the head
116
+ gains `[QK:MUTANT-FIND-MATCHES-SUBJECT]` (every `find` matches its subject exactly once, using
117
+ the body's own `countOccurrences`), which would have caught this at commit time instead of
118
+ fifty minutes into the gate. `smoke-acp-session-reuse-live`: `turn1`/`turn2` were annotated
119
+ `: Context` with no tools and the preflight rejected them — the third fixture of this class,
120
+ and the sweep written to stop it had globbed only `check-acp-*` and only the cast spelling.
121
+ Both turns build through `normalizeContext`; the sweep now covers `scripts/smoke-*.ts` and the
122
+ annotation form (indexed-access types such as `Context["messages"]` excluded), and retyped
123
+ `startTurn` in `check-acp-prompt-lifecycle` on the way (`5aeb8d4`). The third red,
124
+ `smoke-codex-fresh-live`, was a host prerequisite: the gate shell did not export
125
+ `ENTWURF_CODEX_APP_SERVER_PID` / `ENTWURF_CODEX_FRESH_MODEL` / `ENTWURF_CODEX_FRESH_PI_MODEL`
126
+ (the app-server was alive, PID 3969303); the rerun carries them. An independent audit
127
+ (`openai-codex/gpt-5.6-terra`, read-only) re-measured every mutant `find` against its subject
128
+ (0 stale), every direct provider-path fixture (0 tool-less), and named four stale declarations
129
+ fixed before the rerun: `docs/acp-backend-rail.md` support table, `pnpm-workspace.yaml`
130
+ release-age exclusions for pi-client/pi-protocol, `check-pack-install` diagnostic wording, and
131
+ the filename-floor sentence in `docs/mux-launch-rail.md`.
132
+ The qualification body on `ca04c1b` then reported 737/739 with two WRONG-REASON verdicts —
133
+ mutants dying at a neighbouring claim: `check-herdr-fresh-call` asserted the umbrella
134
+ whole-argv cell before the callback-pair cell, and the `pack-install` COVERS-CLOSURE `replace`
135
+ still carried the 0.85.1 pin. Both fixed in `3a9ae38`; a lane-scoped replay through the real
136
+ `qualifyMutants` runner (herdr-fresh-call + pack-install, 40/40 killed, 504s) is the fix
137
+ receipt, and the exact-SHA full body is read from `release-gate --cut`. Bridge source changes
138
+ need `pnpm run build-bridge` before any gate: `check-bridge-delivery` compares the gitignored
139
+ `dist` against source and reports staleness as red, which it did once in this lane.
140
+ The standalone LIVE pass that followed (each smoke once, ambient identity stripped) found the
141
+ one surface the verb move had not reached: `smoke-herdr-fresh-call-live` still joined both of
142
+ its cells on `entwurf_v2` as the callback — the pi cell on an argument predicate the
143
+ zero-argument verb can never satisfy, the claude cell on `indexOf("entwurf_v2")`, which landed
144
+ on the result delivery and reported the real callback as forbidden work before the callback.
145
+ The same red run is the receipt that production was right: the claude child's MCP activity
146
+ log reads `entwurf_peers → entwurf_callback (completed) → entwurf_v2 (completed)`, the pi
147
+ child's own tool result says the callback was delivered, and every delivery/fence cell passed
148
+ (2 FAILED / 30 ok, preserved fixture `/tmp/entwurf-opus2/herdr-live-fixture-red`). The oracle
149
+ now joins on the bare `entwurf_callback` the observing surfaces spell and on the ABSENCE of an
150
+ address in the pi call (`149bef7`); no deterministic gate covers this on-demand axis, which is
151
+ why it surfaced only here.
152
+ The same pass found the host behind the tree: `smoke-omp-fresh-live` timed out waiting for the
153
+ callback because the INSTALLED omp birth unit still spoke bootstrap v1 (no
154
+ `entwurf_bridge_entwurf_callback`, `{v:1}`), so the v2 payload the checkout launcher sent was
155
+ refused as `version-unsupported` — the exact condition `doctor-omp-bridge` exists to name, and
156
+ it did (runtime FAIL / ownership PASS). `install-omp-bridge` with no omp session open, doctor
157
+ green on both axes, re-run 21/21. The herdr re-run on `149bef7` is 31/32: the two repaired
158
+ cells pass; the remaining red is a claude-code child that called back and then never attempted
159
+ the delivery verb, recorded in ROADMAP as a follow-up rather than patched on n=2.
160
+ - **The plugin runtime lock names `herdr-checkout` again, for the length of the 0.24.0
161
+ candidate window** — the same two steps 0.23.0 (`dd84ac0`) and 0.23.1 (`3c3a67d`/`194e800`)
162
+ took: an `npm` lock must name a version that is already published and coherent with this
163
+ checkout's `package.json` (`certifyLockCoherence`, `check-herdr-runtime-bootstrap` 8a), and the
164
+ version bump lands before the publish. Lock and `check-herdr-plugin-build` moved together
165
+ (`47f6d36`, the measured inverse of `194e800`); the npm pin returns with its published sha512
166
+ once 0.24.0 is on the registry.
167
+
168
+ - **`plugins/herdr` 0.3.1 leads with the workbench a visitor is looking at.** Its manifest and
169
+ README now say that Entwurf brings sibling AI sessions into Herdr, then name its actual narrow
170
+ integration scope: the install-time build activates only Herdr-integrated pi and Claude Code,
171
+ while the pane is read-only. This is an affordance correction, not a new runtime claim.
172
+ - **The plugin runtime lock is pinned to npm `@junghanacs/entwurf@0.23.1`** and its published
173
+ sha512, closing the candidate window 0.23.1 shipped in. The pair moves together, as that entry
174
+ says it must: the lock and `check-herdr-plugin-build`'s committed-lock expectations, with the
175
+ `IDENTITY` fixture's sha512 travelling with its version rather than being left on the previous
176
+ release's bytes. The integrity was read from the registry in this session
177
+ (`npm view @junghanacs/entwurf@0.23.1 dist.integrity`) rather than copied from another
178
+ session's report, which is the whole point of a pin.
179
+
180
+ ## 0.23.1 - 2026-09-19
181
+
182
+ ### Changed
183
+
184
+ - **The plugin runtime lock names `herdr-checkout` again, for the length of the 0.23.1 candidate
185
+ window.** This is a state, not a direction: an `npm` lock must name a version that is ALREADY
186
+ published and coherent with this checkout's `package.json`
187
+ (`certifyLockCoherence`, asserted on the committed lock by `check-herdr-runtime-bootstrap` 8a,
188
+ which sits inside `check:hermetic`), and the version bump necessarily lands before the publish.
189
+ So a cut cannot ride an npm lock naming the version it is about to create. `dd84ac0` is the
190
+ precedent and the exit: 0.23.0 was tagged on the checkout carrier and pinned to npm afterwards,
191
+ and 0.23.1 follows the same two steps. While this window is open, `smoke-herdr-raw-install-live`
192
+ reports the npm acquisition axis as a NAMED SKIP rather than a failure, and still asserts that
193
+ the runtime came from the source this ref locks.
194
+
195
+ **A pin and its inverse move the lock AND `check-herdr-plugin-build` together**, which is a fact
196
+ worth writing down because it has now been rediscovered twice. That gate reads the COMMITTED lock
197
+ in three places — the two-stage journey expects one acquisition and the locked `kind@version`, the
198
+ activation-authority cell seeds its ledger from the committed kind, and the progress cell pins the
199
+ long-step wording — so `dd84ac0` changed 90 lines of it alongside the eight lines of lock, and
200
+ this commit is the measured inverse of both halves (14/14 assertions, exit 0). No mutant is
201
+ disturbed: every claim in `scripts/mutants/herdr-plugin-build.json` names a production file
202
+ (`herdr-plugin.toml`, `lib/build.mjs`, `lib/build-progress.mjs`), none names the gate, and the
203
+ `HPB-PROGRESS-NAMED-SEQUENCE` needle re-aimed by `37b81e7` still occurs exactly once in
204
+ `lib/build.mjs`, which this commit does not touch. Teaching the gate to read either lock kind,
205
+ so neither direction needs it flipped again, is deferred to its own issue after the cut.
206
+
207
+ ### Added
208
+
209
+ - **A test written beside the behaviour it certifies is run by public `pnpm check`, and nobody
210
+ names the file** (#119 V2). `vitest.config.ts` include grows two globs —
211
+ `pi-extensions/**/*.test.ts` and `plugins/herdr/**/*.test.mjs` — and `check-tests-beside-behavior`
212
+ expands them every run and hands the resulting paths to `run_vitest`. Both halves were measured
213
+ the hard way. The first cut passed `pi-extensions plugins/herdr` as vitest POSITIONALS, which are
214
+ path substrings, so `test/pi-extensions-probe.test.ts` was selected too (measured: it ran);
215
+ expanding first keeps the selected set exactly the two globs while the shim still contains no
216
+ filename. And an empty expansion RETURNS rather than calling vitest with no arguments, because a
217
+ vitest run with no filter runs the whole include. The literal cross-check against
218
+ `vitest.config.ts` stays, because the shell cannot read vitest's resolved config and a deleted
219
+ glob would otherwise pass. Proven by three still photos in one public `pnpm check`: two failing
220
+ beside-behaviour tests turn it red (`selected 2`), and a third file planted under `test/` is not
221
+ selected.
222
+ - **`entwurf pi`** (#118 hop 2) — `exec pi --entwurf-control "$@"`, a dispatcher case with no new
223
+ bin. Two things it deliberately does NOT do, both measured on pi 0.85.1: there is no
224
+ precondition check, because an unregistered host already gets `Error: Unknown option:
225
+ --entwurf-control` and exit 1 from pi itself; and the flag is added, never deduplicated, because
226
+ passing it twice was measured byte-identical to passing it once. One recursion fence, not
227
+ copilot's two — the sentinel closes the real loop, and the case copilot's second fence covers is
228
+ measured here to be a fail-closed unknown verb rather than a loop. `check-pi-launch` reuses
229
+ `check-copilot-launch`'s fake-vendor posture and carries the `--entwurf-control` literal
230
+ cross-check, since the shell cannot import `ENTWURF_CONTROL_FLAG`. Nine mutants, each killed by
231
+ exactly its own claim; exit-status passthrough is asserted without one, because `set -euo
232
+ pipefail` couples it to `exec` and a mutant that kills two claims attributes to neither.
233
+ - **`smoke-herdr-raw-install-live`** (#118 hop 1) — the plugin's first user path, on a clean
234
+ `node:24` container with no host socket/config/cache mounted, no git `insteadOf`, and the public
235
+ remote spelled as a user would type it. It closes the one npm re-proof axis VERIFY.md demanded
236
+ and the production lock had left empty since `dd84ac0`. Receipts: **(a)** `--ref main` →
237
+ `37b81e725cde4d0a548f1c0faab4fcb5c62942b2`, **(b)** `--ref set/119-verify-herdr` →
238
+ `11ec0c38af36d5cd4a59614c3c8e07c5b5b0f9ae`, both `kind=npm @junghanacs/entwurf@0.23.0` with the
239
+ ledger widening `["pi"]` → `["pi","claude-code"]` and the pi wiring byte-identical across the
240
+ reinstall. Three facts it measured rather than assumed: herdr's offline-persist path carries
241
+ 0.9.1, so `plugin install --yes` completes with no server; `herdr integration install` refuses a
242
+ harness that has never run, because it creates `~/.pi/agent/extensions` only when the parent
243
+ exists and refuses Claude without `~/.claude`; and `--ref` takes a REMOTE REF, not an arbitrary
244
+ commit (`--ref <sha>` fails as `couldn't find remote ref`).
245
+
246
+ ### Changed
247
+
248
+ - **`plugins/herdr` 0.3.0 — the Install path, rewritten as the nine steps a raw box needs.** The
249
+ eighth and ninth are the ones nobody guesses and they come from the container measurement above:
250
+ each harness must be started once (`pi --help`, `claude mcp list`) before herdr will integrate
251
+ it. The release says what it does NOT give you, because the honest combination is this plugin
252
+ plus the runtime its lock still names: `entwurf pi` exists in the repository and is not in
253
+ `@junghanacs/entwurf@0.23.0`, so the user command stays `pi --entwurf-control` until the next
254
+ cut carries the launcher. **`runtime-lock.json` is untouched on purpose** — a pin can only move
255
+ after the version it names is published, so the 0.23.1 pin is a follow-up commit after that
256
+ release, the same two-step `dd84ac0` took for 0.23.0.
257
+ - **Thirteen v2-spine gates moved out of `scripts/` and beside the modules they certify** (#119
258
+ V3), assertion for assertion, with the inventory's denominator fixed FIRST so the move reads as
259
+ a move. `inventory-verification-surface.ts` now counts both vitest locations; without that, the
260
+ migration would have subtracted 2,799 lines from the one number that exists to make migration
261
+ honest. What actually left is 133 lines of plumbing — per-file `ok()` scaffolding, `main()`
262
+ wrappers, summary trailers — and the combined file count is unchanged, seven out and seven in
263
+ per slice.
264
+ - The six mutant-carrying lanes needed four rules the mutant-free seven did not, each learned by
265
+ getting it wrong: a QK claim must BE an `it` title (attribution reads the failed test title);
266
+ the token must appear exactly once per file (title keeps the token, assertion keeps the
267
+ sentence); the `run.sh` case survives as a mutant EXECUTION COORDINATE and keeps its
268
+ `check:contracts` entry (branch CI caught the removal — a gate a mutant names must run inside
269
+ `check:full`); and attribution is containment, not exclusivity (vitest runs every test, so one
270
+ mutation surfaces 1–15 failed titles). 25/25 mutants verified killed and attributed by hand;
271
+ 733 mutants across 58 lanes unchanged, `signatureSource` moved, gate argv did not.
272
+ - `entwurf-v2-visible-resume.test.ts` grew 445 → 908 lines and the growth is the design:
273
+ seventeen claims that shared four drives of the subject now each replay their own, because
274
+ nearly every one is an "X happened BEFORE Y" reading a call-order trace, and a trace polluted
275
+ by a neighbouring test answers wrongly while staying green.
276
+ - Three gates stay under `scripts/` with a reason: `check-entwurf-v2-contract` and
277
+ `check-entwurf-v2-production` are cross-lane (`mux-boundary.json` and `codex-native.json` call
278
+ them as gate argv), and `check-entwurf-v2-surface` generates its seven QK labels inside shared
279
+ helpers called twice from `await` blocks, so the established method would either lose
280
+ attribution or require an async `describe`.
281
+
282
+
283
+ - **The bundled Claude ACP adapter is `@agentclientprotocol/claude-agent-acp` 0.79.0** (from
284
+ 0.76.0, crossing 0.77.0/0.78.0/0.79.0; `@anthropic-ai/claude-agent-sdk` 0.3.257 → 0.3.274,
285
+ ACP wire SDK 1.4.0 and `@anthropic-ai/sdk` 0.100.1 unchanged). Three upstream releases, three
286
+ changes with a reachable shape, and **no entwurf code change** — each "inert" is a fresh
287
+ measurement, not the previous bump's argument reused:
288
+ - 0.77.0's BREAKING removal of `claudeCode.options.agent` misses us twice: our
289
+ `buildClaudeSessionMeta` never set that key, and we spawn the adapter's binary rather than
290
+ importing the removed agent-picker exports (repo-wide grep: 0 hits).
291
+ - 0.77.0's `allowDangerouslySkipPermissions` host opt-out is a NEW lever we do not pull. We set
292
+ the option nowhere, `ALLOW_BYPASS` is byte-identical across both versions, and the overlay's
293
+ `permissions.defaultMode: "bypassPermissions"` resolves the same under each, so a sibling's
294
+ effective permission mode is unchanged.
295
+ - 0.78.0's `compaction_update` / `compaction_summary_chunk` are the first new `sessionUpdate`
296
+ kinds since the pin moved, and they are gated off by our `clientCapabilities: {}` — with the
297
+ mapper's forward-compatible `default` arm as an independent second reason. The `stopReason`
298
+ axis, where "unknown is an error" actually applies, is unchanged.
299
+ - 0.79.0's shell-command permission prompts reorder the option array reject-first under the new
300
+ `defaultToNo` hint. Our approve-all policy selects by option KIND, not position, and every
301
+ upstream option builder emits an allow option, so the order-sensitive fallback is unreachable.
302
+ Re-measured and unchanged: the #96 `mcpServerStatus` evidence (2 call sites, now at
303
+ `v0.79.0 src/acp-agent.ts:1773`/`:1866`, surrounding 200-line window byte-identical) and all four
304
+ model-forcing/accounting wire calls (`setSessionConfigOption`, `sessionUsage`, `turnQuotaMeta`,
305
+ `resolveModelPreference` — byte-identical). Full per-change classification with evidence state:
306
+ [ROADMAP.md](./ROADMAP.md) 2026-09-18 bump entry and
307
+ [docs/acp-backend-rail.md](./docs/acp-backend-rail.md) capability posture.
308
+
309
+ - **The Herdr plugin is 0.2.0 and pins its runtime to npm `@junghanacs/entwurf@0.23.0`.** Its
310
+ committed lock carries the published sha512
311
+ `sha512-ZR2VCui7JjK3w56rQSDs3AuAJMMuiXCNWH7HB52SQ3E/7p0oPhcxD+fb6Gdzi0VcBnheqxPzvJHMPQQcdYtNiw==`;
312
+ the 0.1.0 `herdr-checkout` source remains the candidate-verification path.
313
+
7
314
  ## 0.23.0 - 2026-09-18
8
315
 
9
316
  ### Added
package/FAQ.md ADDED
@@ -0,0 +1,47 @@
1
+ # Entwurf FAQ
2
+
3
+ Short answers for people meeting Entwurf from a different agent-harness tradition.
4
+
5
+ ## Is Entwurf an agent factory or a subagent framework?
6
+
7
+ No. Entwurf is closer to a workshop than a factory: it connects independently owned, visible sibling sessions without turning them into workers of a new central runtime. A harness may run its own subagents or teams, but that remains the harness's responsibility.
8
+
9
+ ## What does Entwurf do?
10
+
11
+ It gives an existing session a stable **garden id** and routes a message, reply, or visible sibling launch through the rail that session actually supports. It does not reconstruct prompts, hydrate transcripts, manage credentials, or emulate another harness.
12
+
13
+ ## What is a garden id?
14
+
15
+ A garden id is the stable address of one visible, record-backed session. Think of it as an invitation to a shared workshop table, not a worker name, a pane id, a transcript id, or a worktree lease.
16
+
17
+ ## How are session state and project state separated?
18
+
19
+ Entwurf does not merge or isolate either one. Each harness keeps its own authentication, tools, transcript, and workspace policy. Git branches, worktrees, file locks, task allocation, and any coordination around a shared checkout belong to the operator and the harnesses involved; a garden id is never a file-isolation claim.
20
+
21
+ ## What happens to a harness's internal subagents?
22
+
23
+ The visible top-level session is the garden principal. Internal children remain inside that harness and do not receive a second garden id, separate delivery authority, or an Entwurf-managed access-control layer.
24
+
25
+ ## Does sending a message start a process?
26
+
27
+ No. [`entwurf_v2`](./README.md#entwurf_v2--canonical-dispatch-verb) only addresses an existing garden citizen and selects its supported delivery rail. `entwurf_fresh_call` opens a new visible sibling; `entwurf_resume_call` reopens a dormant pi citizen under the same id.
28
+
29
+ ## Where do visible siblings open?
30
+
31
+ Inside Herdr, Entwurf opens a new unfocused tab in the caller's workspace and currently admits pi and Claude Code. Outside Herdr, it opens one of the fixed pi, Claude Code, Copilot, OMP, or Codex runtimes in a visible tmux window. The runtime keeps its own model, tools, authentication, and transcript.
32
+
33
+ ## Does Entwurf replace Herdr or tmux?
34
+
35
+ No. Herdr and tmux provide visible seats. Entwurf provides identity, delivery, and receipts across independent harness sessions. A pane or tmux session is a location for a sibling, never its address or liveness proof.
36
+
37
+ ## How do I install it?
38
+
39
+ Use the [direct installation route](./README.md#install) for the broader harness surface. In an existing Herdr workbench, use the [Herdr integration guide](./plugins/herdr/README.md); it activates only Herdr-integrated pi and Claude Code and leaves the rest to the direct route.
40
+
41
+ ## How do I start pi as a citizen?
42
+
43
+ After the direct install, run `entwurf pi`; it starts pi with Entwurf's control surface. The Herdr integration does not put `entwurf` on `PATH`, so its documented command remains `pi --entwurf-control`.
44
+
45
+ ## Where is the precise contract?
46
+
47
+ [AGENTS.md](./AGENTS.md) owns the project invariants. [DELIVERY.md](./DELIVERY.md), [VERIFY.md](./VERIFY.md), and [docs/adding-a-harness.md](./docs/adding-a-harness.md) own capability, evidence, and admission details.
package/README.md CHANGED
@@ -8,6 +8,8 @@
8
8
 
9
9
  npm package: <https://www.npmjs.com/package/@junghanacs/entwurf>
10
10
 
11
+ **Short answers for common questions:** [FAQ.md](./FAQ.md).
12
+
11
13
  Legacy package: [`@junghanacs/pi-shell-acp`](https://www.npmjs.com/package/@junghanacs/pi-shell-acp). `entwurf` is its 0.12+ successor line: the same work renamed around the garden-citizen dispatch substrate rather than the pi adapter.
12
14
 
13
15
  > **Repository shape.** This repo is **entwurf-core (v2 dispatch) + native-harness bridges + a pi adapter + an ACP plugin**. Pi is one adapter, not the project subject. Claude Code, GitHub Copilot CLI, and OMP (`omp`) are shipped self-fetch citizens; Antigravity (`agy`) is shipped native-push. Codex CLI is a **native-push citizen, supported in 0.21.0 on Linux** over the operator-owned app-server: vendor-trusted user-scope birth, strict request-scoped identity, loaded-thread probe, one-shot queue delivery, and visible fresh beside the CALLER's own pane. Unrestricted attached-TUI seat inference is not supported or claimed; #95 D1 retired the fixed `codex` tmux home on 2026-09-16, so the operator seats the app-server wherever they like and a Codex caller opens its sibling beside its own TUI pane. Codex remains native so it keeps its vendor tools, delegation, and work context; this is not another way to buy or expose GPT through ACP.
@@ -252,7 +254,7 @@ cd ~/repos/gh/entwurf
252
254
 
253
255
  The full source setup requires Node 24, pnpm, and Python 3 on PATH. Harnesses are
254
256
  optional-by-presence: `setup` runs the frozen dependency install, then composes what the operator
255
- already installed — a compatible `pi` (`>=0.85.1 <0.86`), Claude Code, agy, and the Copilot CLI
257
+ already installed — a compatible `pi` (`>=0.86.0 <0.87`), Claude Code, agy, and the Copilot CLI
256
258
  each get their wiring completed when detected, an absent harness is an explicit zero-state SKIP,
257
259
  and a detected harness that cannot be completed (including a below-floor `pi`) is a named FAIL
258
260
  that makes setup exit nonzero. `setup` never installs a harness binary or touches a credential
@@ -286,7 +288,7 @@ This rail is CERTIFIED on Linux; on macOS it is NOT CERTIFIED — pending
286
288
  physical host (control sockets, mux, ACP plugin hosting).
287
289
 
288
290
  To use the `entwurf` provider inside pi, install a compatible pi binary
289
- separately (`@earendil-works/pi-coding-agent >=0.85.1 <0.86`). Then point pi at
291
+ separately (`@earendil-works/pi-coding-agent >=0.86.0 <0.87`). Then point pi at
290
292
  the npm-installed package or development clone:
291
293
 
292
294
  ```bash
@@ -636,7 +638,7 @@ A two-pane recording covers the pre-0.12 v1 surface end-to-end — sibling resum
636
638
 
637
639
  Live peer messaging carries a sender envelope `{ sessionId, agentId, cwd, timestamp, origin?, replyable? }`; `entwurf_self` returns that authoritative envelope for the current pi session or trusted meta-session. Plain external MCP hosts are non-replyable. A garden-native meta-session carries a trusted `meta-session` envelope, but **`replyable` is a fact its own rail decides, not a consequence of being trusted** — a self-fetch citizen needs a live armed receiver, a native-push citizen needs an alive adapter probe, and a pi session needs its control socket. `entwurf_self` also reports which rail a meta-session reply would ride, because a native-push citizen has no mailbox to name. `wants_reply` is an etiquette marker rendered as a `(wants reply)` badge — not a transport contract, no wait, no polling. **v2 never gates on it:** a `wants_reply` from an external/non-replyable caller is passed through and surfaced honestly beside that sender's `replyable: false`, not rejected — the decider routes on target + intent, never on sender replyability. (The retired v1 `entwurf_send` did reject it; that behaviour went with the verb.)
638
640
 
639
- In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes seven tools: `entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_self`, `entwurf_inbox_read`, and the explicit/manual `entwurf_register_native` fallback. Native pi exposes the shared capability directly through the extension surface (`entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call` tools; the socket-scan `/entwurf-sessions` command is gone — #50 C4). **For garden-id delivery/reply use `entwurf_v2`** — the canonical surface that classifies the target and routes to live-pi / self-fetch meta-mailbox (Claude Code, Copilot, OMP) / native-push (Antigravity, Codex), and refuses a dormant target instead of waking it. **To open a sibling that does not exist yet use `entwurf_fresh_call`** — WHERE it opens is a fact about the calling process, not a parameter: inside herdr (`HERDR_ENV=1`) it opens a NEW TAB in the caller's own workspace, without taking focus, and admits only `pi` and `claude-code`, refusing `placement` by name; everywhere else it launches one of five fixed backends (`pi`, `claude-code`, `copilot`, `omp`, `codex`) as a visible window on the operator's own tmux server. There is no fallback between the two — an incomplete herdr context is refused by its herdr reason rather than quietly opening a tmux window nobody inside herdr can see. Everything that follows in this paragraph describes the tmux rail; the herdr rail's coordinates, refusals and reclaim are owned by [docs/herdr-launch-rail.md](./docs/herdr-launch-rail.md). On tmux it requires an explicit model, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd) and one optional `placement.tmuxSession` naming an EXISTING session on that server to open the sibling in instead of the caller's own (the operator's project seat — nothing is ever created: an absent session is `tmux-session-missing` and a name outside the addressed grammar `[A-Za-z0-9][A-Za-z0-9_-]*` is `tmux-session-name-invalid`, with no window and no session left behind. The two inputs never infer each other, and the positive form of that matters more than the prohibition: **with `cwd` omitted, a seated sibling starts in THIS agent's own working directory**, measured — it does not inherit the target session's path or its active pane, so naming the `org` seat does not put the sibling in the `org` project. Pass `cwd` when you want a different directory. entwurf never resolves that path: it hands the literal string to tmux, so with a SYMLINKED directory the receipt echoes what you asked for while the record, the transcript path, the callback envelope and `pane_current_path` all show the path the OS resolved it to — two spellings of one place, and the resolved one is what `entwurf_resume_call` later reopens), passes the model and selected directory through the runtime's visible launch path, and hands it a first task; a `copilot` launch goes through entwurf's own managed invocation and is refused before any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units, and an `omp` launch carries its task in the `--entwurf-bootstrap` payload instead of an argv prompt because that vendor connects its MCP tools after the session has already started. Omitted placement follows the CALLER rather than the backend being opened: a Codex CALLER opens its sibling beside its own TUI pane, located by the `thread-id` that pane's terminal title carries (0 or 2+ matching panes reject with no fallback); every other caller opens in its own seat. (0.21.0 shipped a backend-selected default instead — an omitted-placement `codex` TARGET resolved a fixed operator-owned session named `codex` — and #95 D1 retired it on 2026-09-16.) Any of the five can still be sent to an operator-owned existing seat with an explicit `placement.tmuxSession`, and named `claude`/`pi` seats remain available that way, but they are not omitted-placement defaults. Read tmux here as the VISIBLE LAUNCH SEAT, never as an address: the garden id is the address, and it arrives in the callback envelope. Sibling symmetry means each backend keeps its own transcript, auth, native tools and visibility — not that the transports are made identical. The sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model/cwd/seat plus tmux coordinates — including the RESOLVED target session id, which is where the window actually is — and never claims that the runtime accepted them or completed delivery. (The v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.) Garden-native operator commands require `--entwurf-control`. There is no spawn target allowlist — the target registry is gone (#50 C3): `entwurf_v2` and `entwurf_resume_call` address an existing record-backed citizen, while `entwurf_fresh_call` takes its explicit backend/model/task and optional cwd directly rather than resolving a model tuple from a registry file. **To reopen a DORMANT pi citizen use `entwurf_resume_call {target}`** — the record supplies transcript, model, provider and cwd, so it takes no prompt, no task and no model override; it runs no turn, returns a LAUNCH receipt and a separate OBSERVATION receipt (only the second says the citizen is back), refuses a non-pi target as `target-not-pi`, and on an unobserved socket leaves the visible window open and releases its lock rather than retrying.
641
+ In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes eight tools: `entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_callback`, `entwurf_self`, `entwurf_inbox_read`, and the explicit/manual `entwurf_register_native` fallback. Native pi exposes the shared capability directly through the extension surface (`entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_callback` tools; the socket-scan `/entwurf-sessions` command is gone — #50 C4). **For garden-id delivery/reply use `entwurf_v2`** — the canonical surface that classifies the target and routes to live-pi / self-fetch meta-mailbox (Claude Code, Copilot, OMP) / native-push (Antigravity, Codex), and refuses a dormant target instead of waking it. **To open a sibling that does not exist yet use `entwurf_fresh_call`** — WHERE it opens is a fact about the calling process, not a parameter: inside herdr (`HERDR_ENV=1`) it opens a NEW TAB in the caller's own workspace, without taking focus, and admits only `pi` and `claude-code`, refusing `placement` by name; everywhere else it launches one of five fixed backends (`pi`, `claude-code`, `copilot`, `omp`, `codex`) as a visible window on the operator's own tmux server. There is no fallback between the two — an incomplete herdr context is refused by its herdr reason rather than quietly opening a tmux window nobody inside herdr can see. Everything that follows in this paragraph describes the tmux rail; the herdr rail's coordinates, refusals and reclaim are owned by [docs/herdr-launch-rail.md](./docs/herdr-launch-rail.md). On tmux it requires an explicit model, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd) and one optional `placement.tmuxSession` naming an EXISTING session on that server to open the sibling in instead of the caller's own (the operator's project seat — nothing is ever created: an absent session is `tmux-session-missing` and a name outside the addressed grammar `[A-Za-z0-9][A-Za-z0-9_-]*` is `tmux-session-name-invalid`, with no window and no session left behind. The two inputs never infer each other, and the positive form of that matters more than the prohibition: **with `cwd` omitted, a seated sibling starts in THIS agent's own working directory**, measured — it does not inherit the target session's path or its active pane, so naming the `org` seat does not put the sibling in the `org` project. Pass `cwd` when you want a different directory. entwurf never resolves that path: it hands the literal string to tmux, so with a SYMLINKED directory the receipt echoes what you asked for while the record, the transcript path, the callback envelope and `pane_current_path` all show the path the OS resolved it to — two spellings of one place, and the resolved one is what `entwurf_resume_call` later reopens), passes the model and selected directory through the runtime's visible launch path, and hands it a first task; a `copilot` launch goes through entwurf's own managed invocation and is refused before any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units, and an `omp` launch carries its task in the `--entwurf-bootstrap` payload instead of an argv prompt because that vendor connects its MCP tools after the session has already started. Omitted placement follows the CALLER rather than the backend being opened: a Codex CALLER opens its sibling beside its own TUI pane, located by the `thread-id` that pane's terminal title carries (0 or 2+ matching panes reject with no fallback); every other caller opens in its own seat. (0.21.0 shipped a backend-selected default instead — an omitted-placement `codex` TARGET resolved a fixed operator-owned session named `codex` — and #95 D1 retired it on 2026-09-16.) Any of the five can still be sent to an operator-owned existing seat with an explicit `placement.tmuxSession`, and named `claude`/`pi` seats remain available that way, but they are not omitted-placement defaults. Read tmux here as the VISIBLE LAUNCH SEAT, never as an address: the garden id is the address, and it arrives in the callback envelope. Sibling symmetry means each backend keeps its own transcript, auth, native tools and visibility — not that the transports are made identical. The sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model/cwd/seat plus tmux coordinates — including the RESOLVED target session id, which is where the window actually is — and never claims that the runtime accepted them or completed delivery. (The v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.) Garden-native operator commands require `--entwurf-control`. There is no spawn target allowlist — the target registry is gone (#50 C3): `entwurf_v2` and `entwurf_resume_call` address an existing record-backed citizen, while `entwurf_fresh_call` takes its explicit backend/model/task and optional cwd directly rather than resolving a model tuple from a registry file. **To reopen a DORMANT pi citizen use `entwurf_resume_call {target}`** — the record supplies transcript, model, provider and cwd, so it takes no prompt, no task and no model override; it runs no turn, returns a LAUNCH receipt and a separate OBSERVATION receipt (only the second says the citizen is back), refuses a non-pi target as `target-not-pi`, and on an unobserved socket leaves the visible window open and releases its lock rather than retrying.
640
642
 
641
643
  ### `entwurf_v2` — canonical dispatch verb
642
644
 
package/VERIFY.md CHANGED
@@ -5,7 +5,7 @@ invariants live in gates; this file defines evidence strength, release acceptanc
5
5
  and the manual judgements a gate cannot make.
6
6
 
7
7
  > **Current surface.** `entwurf-bridge` exposes `entwurf_v2`, `entwurf_peers`,
8
- > `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_self`, `entwurf_inbox_read`, and `entwurf_register_native`. The ACP
8
+ > `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_callback`, `entwurf_self`, `entwurf_inbox_read`, and `entwurf_register_native`. The ACP
9
9
  > backends are Claude and Snowflake Cortex Code. Antigravity is a separate shipped
10
10
  > native-push citizen lane. Codex is a native-push citizen supported in 0.21.0, whose birth hook is
11
11
  > operator-owned and gated on one vendor trust receipt, with strict request-scoped identity and
@@ -44,6 +44,45 @@ When you write a new entry, mark its rung. "L1 only" is honest; "L2 reached" is
44
44
 
45
45
  Verification here is not a benchmark. In production we exchange short turns and stop immediately to isolate a cause before resuming when something looks off. This document records **verification intent (what we look at) and pass criteria (how to judge)**; the execution shape is the agent's choice as long as the criteria are met.
46
46
 
47
+ ### Where a verification lives — two locations, and what decides
48
+
49
+ A new contract's test goes BESIDE the behaviour it certifies: `pi-extensions/**/*.test.ts` or
50
+ `plugins/herdr/**/*.test.mjs`. Nothing names those files. `check-tests-beside-behavior` expands
51
+ the two globs every run and hands the resulting paths to `run_vitest`, so landing a test edits no
52
+ list — and a failure is attributed from the JSON test TITLE (`run.sh:104-113`), never from the
53
+ code frame, which quotes an adjacent passing test's own title.
54
+
55
+ `scripts/check-*` is not a legacy pile; it is the home of what a vitest lane cannot hold. What
56
+ stays there, and why: LIVE lanes that need real accounts, models, tmux or a Docker daemon;
57
+ install and pack gates that consume a tarball; gates that spawn a subprocess or stand a server up;
58
+ gates that read product source as text; and the mutant EXECUTION COORDINATES described below. A
59
+ new hand-built gate needs a reason one of those five applies.
60
+
61
+ **Four rules bind a migrated gate that carries mutants**, and each of them was measured rather
62
+ than assumed (#119 V3 slice 2):
63
+
64
+ 1. **Every QK claim is its own `it` title.** Attribution reads the failed test title, so a QK
65
+ that lives only in an assertion message leaves its mutant unattributable.
66
+ 2. **The token appears exactly once in the file.** Putting it in the title AND the assertion
67
+ label is refused by the manifest set-integrity contract — the title keeps the token, the
68
+ assertion keeps the sentence.
69
+ 3. **The `run.sh` case survives as a mutant execution coordinate, and keeps its `check:contracts`
70
+ entry.** The case is not a discovery path: the door already finds the test. It exists because
71
+ the qualification runner re-invokes a gate's argv once per mutant, and a gate argv calling
72
+ vitest directly would skip the reporter attribution depends on. Its file filter is narrow,
73
+ because a mutant pointed at the glob shim would re-run every beside-behaviour test once per
74
+ mutant. It stays in `check:contracts` because `check-release-gate-outcomes` requires a gate a
75
+ mutant names to run inside `check:full` — reachable only through qualification, its going red
76
+ on a clean tree would be noticed by nothing but the mutant body.
77
+ 4. **Attribution is containment, not exclusivity.** `assert.ok` aborted at the first broken claim,
78
+ so exactly one label was ever reported; vitest runs every test, so one mutation surfaces
79
+ several failed titles (measured: 1–15). A kill is attributed when the claim's token is AMONG
80
+ them.
81
+
82
+ Migration moves lines BETWEEN the two axes `inventory-verification-surface.ts` counts, and only a
83
+ drop in the COMBINED number is subtraction. That is why the inventory reads both locations: a gate
84
+ that moved out of `scripts/` and into a file beside its subject would otherwise read as deleted.
85
+
47
86
  ### The canonical floor — two entry points
48
87
 
49
88
  - **Deterministic floor — two tiers (#70):** `pnpm check` is the everyday CORE — toolchain (lint + typecheck), the vitest lanes, and the pure-unit / behavioral-contract / source-topology gates plus the cheap static coherence checks. It prints its own total wall time; acceptance is ≤60s on the reference host `oracle` (an operator measurement, never a hard wall-clock gate on arbitrary hosts). `pnpm run check:full` is the FULL deterministic floor — the core plus the hermetic-integration and package/install tiers — and is what the frozen-candidate protocol, push CI, release-gate, and `prepublishOnly` run. Exact membership is the named `check:*` group scripts in `package.json` (the executable SSOT — this document records meaning and principles, not the command list); a gate changes tier by semantic-class decision, never by getting faster or slower. The FULL tier carries the qualification HEAD (`check-gate-manifests`, through `check:hermetic`); the everyday core does not, and neither tier carries the separately scheduled mutant-executing body (`check-gate-qualification`).
@@ -56,6 +95,8 @@ Verification here is not a benchmark. In production we exchange short turns and
56
95
 
57
96
  > **The herdr rail is an on-demand axis until it has run.** `smoke-herdr-fresh-call-live` opens a real caller inside a real herdr pane, has it invoke the PUBLIC `entwurf_fresh_call`, and reads the child's nonce callback — the one axis no deterministic gate reaches, since the rail is selected from the caller's own process environment. **The release aggregate does not yet require the herdr rail**: herdr is optional (a cut on a host without it must not block), and first evidence precedes promotion — a rail whose first acceptance run is days old has not earned a place in the gate that blocks every cut. Run it directly with `LIVE=1 ./run.sh smoke-herdr-fresh-call-live` whenever a cut changes herdr rail code, and promote it to a MUST only after the axis has a run history to point at. Without `LIVE=1`, or without herdr/pi/claude on PATH, it reports protocol SKIP — never a pass.
58
97
 
98
+ > **The plugin's FIRST USER PATH is its own on-demand axis, and until it runs the npm acquisition has no receipt (#118 홉 1).** Every other herdr gate proves the plugin against a source this repository controls: `check-herdr-plugin-build` drives a stub, and `smoke-herdr-plugin-build-live` redirects the product remote to a local bare clone with git `insteadOf` and asserts `identity.kind === "herdr-checkout"`. The production lock has said `npm` since `dd84ac0`, and the re-proof this same document demands above (§ source switch) still has an empty acquisition row. `LIVE=1 ./run.sh smoke-herdr-raw-install-live [--ref <REF>]` fills that row in a clean `node:24` container with no socket, config, cache or repo mounted, no `insteadOf`, the public remote spelled as a user would type it, and pi / herdr / Claude Code installed in the IMAGE BUILD as scaffolding (Hard Rule 17 — the product face begins at `herdr integration install pi`). herdr itself comes from `scripts/install-herdr-ci.sh` against `scripts/fixtures/herdr-supply.json`, so no version or digest lives here. `--ref` defaults to `main` rather than a pinned SHA because receipt (a) is defined as what a user gets today; the resolved commit is printed, so the receipt stays exact. It takes a REMOTE REF and not an arbitrary commit — measured on herdr 0.9.1, `--ref <sha>` fails as `couldn't find remote ref`, because the checkout fetches by name — so a candidate is addressed by its branch and pinned by the resolved commit the gate prints. **Its first question is a measurement, not an assumption:** herdr registers a plugin through a running server and falls back to `persist_plugin_offline` only on a connection error (`src/cli/plugin.rs:914-981,1016-1021` @ `c77af189`, 0.9.0 source), and a container has no server — whether 0.9.1 carries that fallback is unmeasured, and a "no" is #118 investigation output rather than a completion. `--yes` is always passed so a non-interactive exit 2 (`:189`) can never be misread as a missing server. **At a ref whose lock names `herdr-checkout` the acquisition axis reports a NAMED SKIP rather than a red, and the rest of the journey still runs.** That lock is the candidate carrier a cut legitimately sits on — 0.23.0 was tagged that way and `dd84ac0` pinned npm afterwards — so a red there would report a broken install when what is true is that the ref does not lock npm. What is asserted at BOTH lock kinds is that the runtime came from the source the ref locks, and the package name is read from the checkout manifest rather than the lock, because a `herdr-checkout` lock carries no `name` (`herdr-runtime.mjs:317-328`) and joining `undefined` throws instead of failing an assertion. **Cells [6]–[9] carry it past the install into the USE path (#118 H1-4/H1-5).** An install is not a use, so the same container then runs what those bytes became: the compiled bridge entry, the three `REQUIRED_BINS` as executables, and a real `entwurf check-bridge` printing its exact seven-verb set — the installed-runtime row below, measured rather than inferred from the bootstrap's refusal to install without them. Then the pi that this plugin WIRED is started with no `-e` and no `--no-extensions`, because the subject is the wiring and not this checkout: one `get_state` in `--mode rpc` costs zero tokens and needs no provider argument (a bogus `--provider` exits 1 before session_start; omitting the pair lets pi resolve the default the extension itself registers), and while that session is alive the cell reads its V3 record, its gardenId-keyed control socket, and the installed `entwurf peer-facts` reporting it `alive`. The plugin's own status fan is then driven with NEITHER side stubbed, in both states a user can be in: with no herdr server it must name `herdr-agent-list-failed` and go red rather than draw an empty table, and under a headless `herdr server` — scaffolding for that second read only, stopped again before the teardown — it exits 0 and COUNTS the citizen as `unobserved` instead of dropping it. Last, the shipped `entwurf herdr-plugin-deactivate` runs from the installed bin, deletes the runtime it is executing from, and the plugin reinstalls onto the host it fully left — the deactivation row, on the npm source. **What these cells still do not close:** swap / torn-swap recovery on the npm source, the package-consumer proof, and the fan drawing a citizen as a ROW — that needs `placement.kind === "herdr-pane"`, so the pi session must live in a herdr pane a session reference joins, and a headless container has no panes. **This smoke carries no test beside behavior and no mutant**, and that is a property of its class rather than an omission: it is a real-live container journey whose oracle is the container's own end state, so there is no pure seam to assert against and nothing a source mutation could kill deterministically. Like the other two herdr LIVE lanes it is **on-demand and not an aggregate MUST** — herdr is optional, a cut on a host without it must not block, and first evidence precedes promotion. Without `LIVE=1`, or without a usable Docker daemon, it reports a named SKIP; `ENTWURF_REQUIRE_DOCKER=1` turns the Docker absence red. Neither skip is a pass.
99
+
59
100
  > **The Herdr plugin's install-time build has two axes, and one of them needs the network (#116 M3-b3).** `check-herdr-plugin-build` is deterministic and rides `check:hermetic`: the listing reaches the runner through its own spawn seam (there is no fake `herdr` binary in this repo), and the one cell that drives the REAL binary reports a named SKIP on a host without it — `ENTWURF_REQUIRE_HERDR=1` makes that absence red. What it cannot have is the real acquisition: `npm pack git+https://github.com/junghan0611/entwurf.git#<full sha>` builds the bridge through `prepare`, which installs devDependencies from the registry. So `LIVE=1 ./run.sh smoke-herdr-plugin-build-live` owns that axis, and its cells are **two kinds of evidence, labelled as such**: three are the real Herdr journey — a real `herdr plugin install` driving the real `[[build]]`, with the UNCHANGED product remote redirected to a local bare clone by git `insteadOf` (the one substitution, which is what makes an unpushed candidate testable) — covering remote-commit available, the `{pi}` → `{pi, claude-code}` reinstall with OpenCode as a zero-write negative control and exactly ONE counted vendor MCP owner entry, and the post-build Herdr-commit gap (where the ledger has already rebound to the gap commit, so the honest assertion is that transition and not unchanged bytes). The fourth cell — remote-commit **unavailable** — calls the shipped runtime leaf directly with the fixed product argv and is therefore **acquisition-leaf evidence, not a journey**; routing it through Herdr would add a second explanation for one red. Its oracle is the honest one too: active runtime, ledger and harness bytes preserved, while the journal is left as the certified `installing` retry authority the transaction deliberately writes before acquiring, and a normal install afterwards is green. Its Claude cell is where the vendor question the deterministic gates refuse to claim gets answered — whether Claude Code accepts an ABSOLUTE executable as an MCP command — and it reports a named skip on a host without `claude`, never a pass. Like the herdr fresh-call rail, this smoke is **on-demand and not an aggregate MUST**. The release aggregate does not require the plugin build smoke: herdr is optional, a cut on a host without it must not block, and first evidence precedes promotion.
60
101
 
61
102
  > **Switching the plugin's production acquisition source is a re-proof, not a configuration change (#116 M3-b3).** `runtime-lock.json` carries a closed `source` discriminant: `npm` (exact `name@version` plus the published sha512, compared against the tarball's own bytes) is the production authority, and `herdr-checkout` (the exact commit Herdr checked out, from the fixed remote) is a VERIFICATION-ONLY carrier that exists so a candidate needs no npm release. Evidence does not transfer between them. Before a release that moves the production source — to npm, or to a GitHub Release tarball plus its sha512 — the exact acquisition and integrity comparison, the installed runtime (name@version, compiled entry, three executable bins, a real `check-bridge`), the swap and torn-swap recovery, activation and deactivation, and the package-consumer proof must all be re-run against THAT source. A candidate proof green on the checkout carrier says nothing about a registry artifact, and the reverse is equally true. **Records from before this contract are refused, not migrated:** a v1 runtime journal or activation ledger fails certification by name, and the way forward on such a host is an explicit `entwurf herdr-plugin-deactivate` (or a hand-cleared runtime/ledger state) before the next install — there is no reader that accepts both schemas, because the older shape cannot say WHICH artifact it was.
@@ -228,7 +269,7 @@ The goal is not merely "invoke Claude Code." We want:
228
269
  **One install command to remember: `./run.sh setup <project>`.** It is idempotent — re-run the exact same command whenever anything looks wrong. There is no second install surface to juggle: from a clone `setup` runs the whole floor in order.
229
270
 
230
271
  1. `pnpm install` — installs the pinned development dependencies and builds the bridge (source-checkout bootstrap only; an installed package never runs npm/pnpm inside `node_modules`)
231
- 2. pi wiring → `<project>/.pi/settings.json` + user-scope registration — only when a `pi` inside the supported range (`>=0.85.1 <0.86`) is on PATH; absent pi is an explicit zero-state SKIP, a below-floor pi is a detected FAIL. The user-scope entry is owner-recorded (#86 C2): another root's live-or-missing ownership makes this step a zero-write refusal (setup: pi FAIL) that names `takeover-user-scope`; the install-states bind the exact managed settings path (a drifted/symlinked/corrupt target is a zero-write refusal) and the inverse removes only the recorded owner's exact entry; `doctor-pi-package` reports the verdict
272
+ 2. pi wiring → `<project>/.pi/settings.json` + user-scope registration — only when a `pi` inside the supported range (`>=0.86.0 <0.87`) is on PATH; absent pi is an explicit zero-state SKIP, a below-floor pi is a detected FAIL. The user-scope entry is owner-recorded (#86 C2): another root's live-or-missing ownership makes this step a zero-write refusal (setup: pi FAIL) that names `takeover-user-scope`; the install-states bind the exact managed settings path (a drifted/symlinked/corrupt target is a zero-write refusal) and the inverse removes only the recorded owner's exact entry; `doctor-pi-package` reports the verdict
232
273
  3. Claude meta-bridge global plugin — only when `claude` is on PATH; otherwise skipped cleanly
233
274
  4. source stable-bin exposure — including certified `entwurf` → this checkout's `run.sh`, the managed runtime Copilot fresh resolves; helper units are attempted independently and a foreign helper is a named FAIL
234
275
  5. agy bridge + exact permission + statusline + `PreInvocation` hook — only when `agy` is on PATH; each adapter is idempotent and independently doctorable
package/demo/README.md CHANGED
@@ -139,7 +139,7 @@ SCENE_DELAY=30 FINAL_PAUSE=10 bash demo.sh
139
139
 
140
140
  ## Prerequisites
141
141
 
142
- - `pi` on PATH (current floor 0.85.1)
142
+ - `pi` on PATH (current floor 0.86.0)
143
143
  - `entwurf` provider configured + auth ready for the selected sender/peer models
144
144
  - `asciinema` installed
145
145
  - `agg` installed (optional — only for GIF conversion)