@junghanacs/entwurf 0.41.1 → 0.41.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +7 -5
  3. package/CHANGELOG.md +185 -0
  4. package/DELIVERY.md +37 -20
  5. package/README.md +70 -65
  6. package/VERIFY.md +9 -9
  7. package/docs/external-mcp-host.md +1 -1
  8. package/docs/setup-clean-host.md +59 -33
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +14 -11
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +10 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +23 -2
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +7 -1
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +21 -10
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +4 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +5 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +6 -4
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +13 -2
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +2 -2
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +1 -1
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +25 -17
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +94 -28
  23. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +57 -7
  24. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +2 -2
  25. package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +12 -12
  26. package/mcp/entwurf-bridge/src/index.ts +14 -11
  27. package/native/win32-x64/entwurf_win.node +0 -0
  28. package/native/win32-x64/manifest.json +2 -2
  29. package/package.json +4 -4
  30. package/pi-extensions/lib/entwurf-self-address.ts +13 -0
  31. package/pi-extensions/lib/entwurf-v2-contract.ts +25 -2
  32. package/pi-extensions/lib/entwurf-v2-decider.ts +7 -0
  33. package/pi-extensions/lib/entwurf-v2-surface.ts +3 -0
  34. package/pi-extensions/lib/fresh-call-composition.ts +21 -10
  35. package/pi-extensions/lib/herdr-fresh-call.ts +4 -2
  36. package/pi-extensions/lib/meta-sender-identity.ts +4 -0
  37. package/pi-extensions/lib/mux-fresh-call.ts +6 -4
  38. package/pi-extensions/lib/native-push/register.ts +17 -2
  39. package/pi-extensions/lib/omp-fresh-preflight.ts +2 -2
  40. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +1 -1
  41. package/pi-extensions/lib/remote-pair-receive.ts +27 -19
  42. package/pi-extensions/lib/remote-pair-send.ts +117 -30
  43. package/pi-extensions/meta-bridge-omp.ts +69 -7
  44. package/pi-extensions/meta-bridge-pi-durable.ts +2 -2
  45. package/run.sh +173 -22
  46. package/scripts/check-agy-sender-identity.ts +6 -4
  47. package/scripts/check-entwurf-self-address.ts +34 -3
  48. package/scripts/check-entwurf-v2-contract.ts +24 -2
  49. package/scripts/check-entwurf-v2-production.ts +45 -31
  50. package/scripts/check-gate-qualification.ts +10 -8
  51. package/scripts/check-native-push-adapter.ts +4 -0
  52. package/scripts/check-native-push-register.ts +32 -82
  53. package/scripts/check-remote-pair-installed.ts +2 -2
  54. package/scripts/dev-bin.sh +112 -1
  55. package/scripts/lib/chain-terminus-publication.ts +58 -0
  56. package/scripts/lib/codex-fresh-live-protocol.ts +30 -0
  57. package/scripts/lib/codex-fresh-source-receipts.ts +112 -3
  58. package/scripts/lib/pi-durable-fresh-live-receipts.ts +29 -1
  59. package/scripts/mutants/chain-terminus-publication.json +17 -0
  60. package/scripts/mutants/codex-native.json +98 -0
  61. package/scripts/mutants/control-send-receipt.json +2 -2
  62. package/scripts/mutants/mux-fresh-call.json +16 -1
  63. package/scripts/mutants/native-push-paused.json +17 -0
  64. package/scripts/mutants/omp-fresh.json +13 -2
  65. package/scripts/mutants/pack-install.json +46 -0
  66. package/scripts/mutants/remote-pair-display.json +3 -3
  67. package/scripts/mutants/remote-pair-receive.json +41 -11
  68. package/scripts/mutants/remote-pair-roundtrip.json +67 -7
  69. package/scripts/mutants/remote-pair-send.json +156 -17
  70. package/scripts/mutants/remote-pair-transport.json +4 -4
  71. package/scripts/mutants/source-install.json +66 -0
  72. package/scripts/remote-pair-receive.ts +12 -12
  73. package/scripts/smoke-agy-install-state.sh +121 -0
  74. package/scripts/smoke-agy-native-push-live.ts +13 -0
  75. package/scripts/smoke-codex-fresh-live.ts +50 -27
  76. package/scripts/smoke-entwurf-chain-live.ts +7 -9
  77. package/scripts/smoke-herdr-fresh-call-live.ts +4 -4
  78. package/scripts/smoke-herdr-plugin-build-live.ts +179 -50
  79. package/scripts/smoke-mux-lifecycle-live.ts +140 -8
  80. package/scripts/smoke-omp-fresh-live.ts +5 -4
  81. package/scripts/smoke-pi-durable-fresh-live.ts +29 -7
  82. package/scripts/smoke-setup-verdict.sh +27 -2
package/AGENTS.md CHANGED
@@ -35,7 +35,7 @@ Ask: does this follow capability rather than a surface name; record backend asym
35
35
  7. **One meta-store contract.** Record body and filename agree; active records are readable regular non-symlink V3 files; `nativeSessionId` is unique. Writers and doctor certify the active store before writing. Address-bearing reads use the addressable reader; `readStoreRecordFile`/`O_NOFOLLOW` and lstat policy both remain load-bearing. Quiesce and fresh-cut an unreadable generation; roll forward a stale reader that rejects a newly known backend or V3 key. Reader-version skew is not a generation change: preserve identities and records, and verify every participating reader before claiming rollout complete. Store protocol and gates: `meta-session.ts`, `check-meta-*`, `check-fresh-cut-gate`.
36
36
  8. **GC reclaims processes, not memory.** Preserve or archive records and transcripts; do not casually delete them.
37
37
  9. **Not a second harness.** No prompt reconstruction, transcript hydration, tool-result ledger, credential mediation, or harness emulation. Backends own auth and transcript.
38
- 10. **Native-push is distinct.** It is neither mailbox nor pi socket. Native-push citizens are record-backed plus probe-alive, with no receiver/watch state or resume authority. Antigravity retries only after bounded re-probe; Codex never replays queue acceptance. Codex sender identity is request-scoped metadata; app-server environment carries root/placement, never citizen identity.
38
+ 10. **Native-push is distinct.** It is neither mailbox nor pi socket. Native-push citizens are record-backed plus probe-alive, with no receiver/watch state or resume authority. Antigravity retries only after bounded re-probe, and its native push is paused (#152: one contract constant refuses dispatch and registration, and makes the sender non-replyable, before any probe); Codex never replays queue acceptance. Codex sender identity is request-scoped metadata; app-server environment carries root/placement, never citizen identity.
39
39
  11. **Package proof models a consumer.** Installed operator entrypoints reach compiled JS; `run_ts` is the sole TS crossing. Keep checkout, tarball, and consumer proofs distinct.
40
40
  12. **Offline verification never rewires the operator.** Sandbox HOME, `PI_CODING_AGENT_DIR`, and writable XDG roots. Only named LIVE gates may touch the host, and must say so.
41
41
  13. **Doctors report runtime and ownership independently.** Working runtime does not prove ownership; broken ownership does not erase runtime observation. Required failure on either axis is red.
package/BASELINE.md CHANGED
@@ -312,7 +312,9 @@ Per-question PASS / FAIL / NOTE for grading the model's response.
312
312
 
313
313
  ## Native-citizen baseline — Antigravity / agy (shipped)
314
314
 
315
- This is not an ACP overlay interview. Run it in a **fresh agy conversation** after
315
+ This is not an ACP overlay interview, and while agy native push is paused (#152) it is an
316
+ optional diagnostic run on request, not a release-cut requirement (VERIFY criterion 7).
317
+ Run it in a **fresh agy conversation** after
316
318
  `install-agy-bridge`, `install-agy-statusline`, and `install-agy-hooks`, with all
317
319
  three doctors green. `PreInvocation` is the earliest lifecycle event, so a brief
318
320
  `🪛 ? agy` before the first model invocation is honest; after that first invocation
@@ -321,13 +323,13 @@ the same conversation must have a garden id.
321
323
  | ID | Check | PASS | FAIL |
322
324
  |---|---|---|---|
323
325
  | Q-AGY-BIRTH | Automatic birth | First invocation creates/attaches one record by native `conversationId`; statusline becomes `🪛 <garden-id> agy`. | Manual cwd matching or `entwurf_register_native` is required for normal birth; a new id appears on every turn. |
324
- | Q-AGY-SELF | Sender identity | `entwurf_self` reports the same garden id, `origin=meta-session`, `agentId=meta-session/antigravity`, and `replyable:true` while the native probe is alive. | Anonymous `external-mcp`, unbacked marker accepted, model name substituted into `agentId`, or mailbox evidence used to infer replyability. |
325
- | Q-AGY-SEND | Outbound attribution | `entwurf_v2` from agy reaches a sibling carrying that same sender garden id and `replyable:true`. | Receiver sees unknown host/wrong garden id, or sender ambiguity is silently guessed. |
326
- | Q-AGY-REPLY | Same-conversation reply | Sibling replies with `entwurf_v2(target=<agy-gid>, intent=fire-and-forget)` and the message direct-injects into the same live agy conversation. | New conversation/spawn, mailbox file/doorbell, or a cosmetic delivered result with no live native route. |
326
+ | Q-AGY-SELF | Sender identity | `entwurf_self` reports the same garden id, `origin=meta-session`, `agentId=meta-session/antigravity`, and, while native push is paused (#152), `replyable:false` with a reason naming the pause and no probe run. | Anonymous `external-mcp`, unbacked marker accepted, model name substituted into `agentId`, or mailbox evidence used to infer replyability. |
327
+ | Q-AGY-SEND | Outbound attribution | `entwurf_v2` from agy reaches a sibling carrying that same sender garden id; while native push is paused (#152) the sender is `replyable:false`. | Receiver sees unknown host/wrong garden id, or sender ambiguity is silently guessed. |
328
+ | Q-AGY-REPLY | Same-conversation reply (paused, #152) | While the pause holds, a sibling's `entwurf_v2(target=<agy-gid>, intent=fire-and-forget)` returns `native-push-paused` with nothing probed or sent, and the agy record is unchanged. When the pause lifts: the message direct-injects into the same live agy conversation. | New conversation/spawn, mailbox file/doorbell, a probe or send while paused, or a cosmetic delivered result with no live native route. |
327
329
  | Q-AGY-OWNERSHIP | Install scope | MCP owns one server plus one narrow `mcp(entwurf-bridge/<tool>)` rule per normal-path tool (`entwurf_v2`, `entwurf_peers`, `entwurf_self`) and nothing else; statusline owns its subtree; hooks own one named hook. | Installer broadens YOLO policy (`command(*)`, `unsandboxed(*)`), grants tools it does not need (`entwurf_inbox_read`, `entwurf_register_native`), or overwrites unrelated settings/hooks. |
328
330
  | Q-AGY-CONCURRENCY | Evidence boundary | Separate agy processes have separate pid/start-key markers; same-pid concurrent model invocation is explicitly reported unsupported. | Claims that one pid can safely identify two simultaneously invoking conversations. |
329
331
 
330
- The replyability formula is **record-backed identity AND live native-push probe**.
332
+ The replyability formula is **record-backed identity AND live native-push probe**; while agy native push is paused (#152) the probe does not run and agy replyability is false by name.
331
333
  There is intentionally no receiver marker, `watchArmed`, mailbox, or resume
332
334
  authority (no rail has one since the visible-first cut). The model field may exist in the meta-record/status display,
333
335
  but `agentId=meta-session/antigravity` is the stable sender contract.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,191 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.41.2 — 2026-10-24
8
+
9
+ A bug-fix release for the setup, pack and mux verification surfaces. It also carries #149's remote-pair declaration change, a hard cut: every host that has an `entwurf-remote-pair.json` must convert it by hand (Remote pairs below, setup §2c). It also pauses agy native push (#152, Antigravity below), a scope added while this release was being made. The release evidence below separates what was measured while this release was prepared from what the MAKE phase measures on the exact artifact and installed hosts. Herdr is not re-pinned to npm here. An npm lock must name this checkout's exact version, and 0.41.2 had no published bytes when this release was prepared, so `plugins/herdr/runtime-lock.json` returned to the closed `herdr-checkout` verification carrier, as earlier release candidates did. Since the prepare commit `1e66466` reached main, a main-tracking Herdr install builds its runtime from the checked-out commit instead of npm, and a host already on the npm 0.41.1 runtime must run `herdr-plugin-deactivate` before it reinstalls. The published 0.41.1 pairing stays pinned by the Herdr tag `v2026.10.23-herdr.0.41.1` (its section below). The Herdr manifest version is unchanged; a separate snapshot re-pins npm after 0.41.2 is published.
10
+
11
+ ### Release evidence
12
+
13
+ - Kill-power re-measured at this shipped SHA: none (all 1215 committed claims not re-proved, #144).
14
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head 439b399c64ef surface de3c1bcf6db9 -- ["bash","run.sh","check-remote-pair-beside"] 47/47 receipt 55bd9563fe43
15
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head 439b399c64ef surface de3c1bcf6db9 -- ["bash","run.sh","check-tests-beside-behavior"] 6/6 receipt aef18acbe804
16
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head 156bb6231cee surface c883d0df88b5 -- ["bash","run.sh","check-pack-argv-matcher"] 4/4 receipt 11470c62389c
17
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head e7ed0576ea65 surface 9bff5948bcb3 -- ["bash","run.sh","smoke-agy-install-state"] 18/18 receipt 13b86f54457e
18
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head e7ed0576ea65 surface 9bff5948bcb3 -- ["bash","run.sh","smoke-setup-verdict"] 4/4 receipt ba3113f9661e
19
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head 3e2673ba9a1b surface 2b1c5c7c1d52 -- ["bash","run.sh","check-mux-launcher-fence"] 8/8 receipt d27f3ded70ab
20
+ - Not measured for this release: the real two-host exchange (T3) over the v2 remote-pair file. It is opt-in operator evidence, not a release-gate MUST (VERIFY, "Real two-host exchange"), and it was not re-run for this candidate.
21
+ - `LIVE=1 ./run.sh smoke-herdr-plugin-build-live` on Herdr 0.9.5, measured on an earlier working tree (diff `40f12860ffbf`, smoke `15744e13f9bf`), not on this SHA: 635 s, 5 passed, 0 skipped. The Claude cell ran the real vendor CLI. The gap install exited 1 with the registry unchanged and the ledger on the gap commit, and the reconcile then re-bound to HEAD with 0. Log `0a9f293a155c`. A fixture pack is not a release artifact.
22
+ - Prepare-time floor and release gate, measured on the working tree `80c40705809c` (fixture source `15744e13f9bf`, before this record's final wording), not on an exact SHA: managed build 6 s with dist byte-identical, `check:full` 905 s exit 0. `release-gate --cut`: MUST 24/0/0, BEHAVIOR 1/0/0, cut OK, log `8c18f08880d3`. The census afterwards was read-only.
23
+ - Prior candidate, kept and not inherited: the prepare commit `1e66466`, its push CI, its M3w artifact `df407c70c0b2` and that artifact's container and Claude installed acceptance were measured before the #152 pause. They are evidence for that candidate only; the paused product bytes do not inherit them.
24
+ - #152 PARTIAL (operator-attested, not this SHA): head 1e664667a663 surface 81cbb2888465 -- ["bash","run.sh","check-native-push-paused"] 1/1 receipt 2f262170bf98
25
+ - #152 PARTIAL (operator-attested, not this SHA): head 1e664667a663 surface 81cbb2888465 -- ["bash","run.sh","check-entwurf-v2-contract"] 1/1 receipt 0c624bad9b67
26
+ - #152 PARTIAL (operator-attested, not this SHA): head 1e664667a663 surface 81cbb2888465 -- ["bash","run.sh","check-entwurf-v2-production"] 5/5 receipt f7dd4d94c275
27
+ - #152 PARTIAL (operator-attested, not this SHA): head 1e664667a663 surface 81cbb2888465 -- ["bash","run.sh","check-entwurf-self-address"] 8/8 receipt 3c223a9034d8
28
+ - Those four groups were measured on an uncommitted working tree with green controls; the other 1189 of 1204 committed mutants were not run for them. They are not shipped-SHA kill-power and do not sum to release coverage.
29
+ - #152 candidate floor, before this record's final doc amendment: `check:full` 900 s exit 0 on the uncommitted working tree with tracked patch `f883ba29c22d` (log `ce9ca5df53eb`), not this SHA.
30
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head 100f618e2b42 surface 61f6ab3d25c1 -- ["bash","run.sh","check-chain-terminus-publication"] 1/1 receipt 6a8af23eb259
31
+ - That group was measured on an uncommitted working tree over `100f618` with green controls; the other 1204 of 1205 committed mutants were not run for it, and it is not shipped-SHA kill-power.
32
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head ed6490fdc949 surface e966ed7447e8 -- ["bash","run.sh","check-tests-beside-behavior"] 6/6 receipt cecf3a83a675
33
+ - `LIVE=1 ./run.sh smoke-entwurf-chain-live` on the chain-terminus candidate (uncommitted, tracked patch `4df5f793fb04`), not this SHA: 24 assertions, 24 s, exit 0, log `e5115bbeb06d`; every identity and read-receipt check after the doorbell was reached.
34
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head bad44f4beb6c surface cd1627ae7b7c -- ["bash","run.sh","check-mux-fresh-call"] 160/160 receipt 33b6f81cff24
35
+ - That group was measured on an uncommitted working tree over `bad44f4` (the bootstrap repair) with green controls; the other 1055 of 1215 committed mutants were not run for it, and it is not shipped-SHA kill-power.
36
+ - Guided caller check, `LIVE=1 ./run.sh smoke-codex-fresh-live` on an earlier uncommitted candidate (tracked patch `a5b3833b9ebd`), not this SHA: 66 assertions, exit 0, the initial Pi's peers prefix 1, log `753dd4b24b6c`. It is one run of the path the task asked for, not evidence of a sibling's own judgement, and the earlier outbound-callback timeout (P5 r4) is still unexplained.
37
+ - Exact prepared-SHA CI and the M3w artifact with its container acceptance belong to the final prepared SHA and produce their own receipts. §3.7 is VERIFY criterion 7's deterministic paused proof inside `check:full`; it needs no install.
38
+ - Release gate and native Claude acceptance after install (§3.4), measured on `4f14a4d` (the bootstrap checkpoint, before the docs-only agy-scope change), not this SHA: `release-gate --cut` log `fcb5c64dd70a`, its `check:full` 923 s exit 0, MUST 24/0/0, BEHAVIOR 1/0/0, cut OK. Installed `doctor-meta-bridge` exited 0 (log `0065d7a78d8e`). A new Claude Code session ran the acceptance prefix's MCP entry `8ca6d4cb4b90`, the same bytes as the tarball member, and sent its own self-report. The checkout re-registration and its doctor passed afterwards.
39
+ - Also on `4f14a4d`, not this SHA: `smoke-omp-fresh-live` 21 assertions after the managed OMP install (log `e2c91d7683cc`), and `smoke-herdr-fresh-call-live` 32 assertions (log `bbfe0386b2c6`).
40
+ - Those `4f14a4d` receipts stand for this SHA only if the final artifact differs from the `4f14a4d` artifact `37b4670837d2` in exactly BASELINE.md, CHANGELOG.md, VERIFY.md and docs/setup-clean-host.md. That comparison is an operator-handoff receipt, not part of this record.
41
+
42
+ ### Docs
43
+
44
+ - **remote-sibling-principles now carries #149.** Several relationships coexist, each one exact sender–target edge, never a union of memberships or a local fallback. Record, process, screen and conversational continuity stay separate. Disjoint routes do not narrow SSH account trust. Implementation, switch-over and a real exchange are kept as separate evidence; no version-2 nonce round trip is claimed. The page keeps the why and links the mechanism to DELIVERY and setup.
45
+ - **README's orchestration section is an entrance, not a second manual.** It drops the lifecycle table it repeated from the overview and the acceptance-boundary detail DELIVERY owns, and adds two short entries: a minimal v2 remote-pair file with its mirror rule, linked to the principles page, DELIVERY and setup §2c; and `team.json`, a person's bookmark copied from `team.json.example`, which Entwurf does not load as runtime configuration.
46
+
47
+ ### Remote pairs (#149)
48
+
49
+ - **One host's declaration now carries several exact pairs.** `entwurf-remote-pair.json` keeps its name and location. Its content is now exactly `{"version": 2, "routes": [...]}`, and each route has the four keys of the #107 file. One local citizen may pair with several remotes, one remote with several locals, and one alias may carry several routes. The file is refused if a pair repeats, a garden id is both local and remote, one remote garden id has two aliases or files, or one alias has two files. A route is selected by the exact (sender, target) pair. A target declared remote with no route for the calling sender is refused with zero SSH and no local fallback. The receiving entry matches both ends of one route at once, so a sender declared in one route cannot reach another route's local. It still refuses with the two existing reasons (`remote-pair-from-mismatch`, `remote-pair-target-mismatch`). The claim, SSH argv, exit codes and receipt are unchanged.
50
+ - **Hard cut, converted by hand.** The versionless single-pair file is refused by name with its manual rewrite; there is no legacy reader. Setup §2c gives the per-host order. While a host's bytes and its file disagree, every `entwurf_v2` call of its bridges is refused, local targets included; a running bridge keeps the code it loaded. Trust stays same-account SSH: every key that account accepts can claim any garden id its file declares.
51
+ - **Evidence.**
52
+ - T1 cells cover the v2 shape, the legacy refusal, the set rules, exact-pair selection with no local fallback, the cross-pair refusal and the matched-route label.
53
+ - The T2 surface-in scene adds a third host and a second citizen on one root, giving three routes in one file: fan-out, a shared remote, the reverse, an undeclared sender with zero SSH, and a cross claim refused on the receiving host.
54
+ - The remote-pair mutants run through the new narrow coordinate `check-remote-pair-beside`, which is in `check:contracts`.
55
+ - Not measured: a real exchange over the v2 file (T3 must be re-run for the schema change) and a sender and a receiver on different releases.
56
+
57
+ ### Antigravity native push paused (#152)
58
+
59
+ - **agy native push is paused.** On the external send path entwurf measured, the agy language server refused the `agentapi` call with `Unauthenticated: missing CSRF token` (#128 on agy 1.2.16, again on agy 1.4.6). Whether a first-party own-tool shell holds a token entwurf could legitimately present is unmeasured; this pause does not claim it is impossible. One fixed constant in the v2 contract drives three refusals before any probe: `entwurf_v2` rejects an agy target as the new pre-probe reason `native-push-paused` with `observedLiveness: null`, an agy sender's `replyable` is false with a reason naming the pause, and `entwurf_register_native` refuses without probing or minting a garden id. Existing agy records, the backend enum, the record reader and the schema are unchanged, so agy citizens stay readable and listed with their birth hook and statusline. Codex native push is unaffected. The fresh-cut quiescence probe stays outside the pause and keeps failing closed.
60
+ - **Acceptance changes with it.** VERIFY criterion 7 proves the pause with the deterministic `[QK:AGY-NATIVE-PUSH-PAUSED]` cell, which needs no agy conversation; the doctors and the fresh-conversation identity check of the retained legacy identity surfaces are an optional diagnostic run on request, not a cut requirement, and are not send evidence. A release does not claim the installed agy host is certified or can receive. A same-gid reply is not claimed while the pause holds. `smoke-agy-native-push-live` exits 2 with `[entwurf:paused]` before it reads a conversation id, and that is not a PASS. The mutant for that cell runs through the narrow coordinate `./run.sh check-native-push-paused`, which is also part of `check:contracts`.
61
+
62
+ ### Fixed
63
+
64
+ - **A fresh sibling may check its caller before calling back, and may hold back.** The framing no longer calls the callback the literal first action. A read-only `entwurf_peers` check may come first, and a capped listing that does not show the caller proves no absence. A sibling whose check does not hold up may hold the callback and the task; the caller then has no address for it. The callback correlates an address; it grants no trust and no authority, and Entwurf decides nothing on the sibling's behalf. omp's stage one reads the caller id from the launch env for display only, and a requested bootstrap without a well-formed one is refused with its task unsent. The LIVE audits admit only successful exact checks before one exact callback; that is those fixtures' condition, not a list of what a sibling may do. Pi and pi-durable prove the result order by row ordinals, Codex only the call order.
65
+ - **`smoke-entwurf-chain-live` could report a false RED while the terminus message was still being published.** `enqueueMetaMessage` writes the `.msg` body first and `inbox.signal` last. The smoke stopped waiting at the first body and checked the signal at once, and in the 0.41.2 release gate on 2026-10-24 the signal landed about 5 ms later. The wait now ends only when the body and the signal are both there, inside the same deadline. A missing body, a missing signal and a duplicate still fail, and the identity and read-receipt checks after it are unchanged. Product mailbox behavior is unchanged. The wait lives in `scripts/lib/chain-terminus-publication.ts` and is proven offline by `test/chain-terminus-publication.test.ts` through the narrow coordinate `check-chain-terminus-publication` (in `check:vitest`), with one mutant that restores the early stop.
66
+ - **The release MUST now drives `entwurf_fresh_call`'s explicit seat over the real MCP wire (#108).** Before this, `placement.tmuxSession` was checked on the bridge only through its schema and a source regex, and the seat was proven only by calling the composition directly. In `smoke-mux-lifecycle-live`, the native pi cell's existing `tools/call` now names a seat session made on the same private tmux server, with no extra model turn. The harness asks tmux itself (not the receipt, whose session id is the target the product chose) that the pane sits in that seat, that the caller's windows are unchanged, and that the stable-handle close removes only that window while the seat's original anchor session, window and pane stand. The ACP and Claude Code cells keep the omitted-placement path. Product code is unchanged.
67
+ - **`check-pack-install`'s two dispatcher-verb leak refusals could pass a leak on a SIGPIPE.** The copilot and codex-app-server cells searched the vendor stand-in's argv with `printf '%s' "$op_out" | grep -qF …` under `pipefail` and refused only when that pipeline exited 0. bash's `printf` writes a multi-line value one line at a time, so a `-q` that closes the pipe at the first match can kill the producer with 141 and pass the cell. The defect was latent: no false green was observed in CI, and the product carries no leak. Both cells now return through one verdict, `pack_install_refuse_argv_leak`. It searches a here-string, which has no producer to kill. It passes on a clean search, and refuses with the cell's existing diagnostic on a leak, or by name when the search itself fails.
68
+ - `check-pack-argv-matcher` is a snapshot-safe gate in `check:package`. Like `check-pack-pin-matcher`, it also runs first inside `check-pack-install`. It drives that same verdict: it must refuse a leak at the front or end of about 260 KB, pass clean output silently, and refuse a failing `grep`. A text check holds both cells to the verdict.
69
+ - A teeth control first requires the retired pipe shape to miss the front-loaded leak on the host. The mutant that restores that shape dies deterministically only because of this large fixture; on the real small output it would survive.
70
+ - The other pipe-to-`-q` lines reviewed alongside are unchanged: the pi-provider doctor and dev-only gate fence checks, which the next confirm-type check backstops; the meta-sessions `ls | grep -q .` cell; the `takeover-user-scope` and uninstall notes; and the confirm-type greps.
71
+ - **A source checkout's setup could report a developer install while an old global entwurf was still selectable.** The bins step certified only the PATH winner: `~/.local/bin/entwurf` into this checkout. An old pnpm global shim later in the same PATH stayed in place, and a non-interactive SSH shell, whose PATH puts that directory first, ran it with the old reader. Source setup now also refuses, by name, any other root under the six package bin names in every PATH entry and in the global bin directory that `pnpm bin -g` and `npm prefix -g` report.
72
+ - The same resolved target is the same install, so duplicate PATH entries and second links pass. An unreadable package manifest, or a package manager that fails or hangs, is refused rather than read as clean. pnpm 11 refuses `bin -g` while its global bin dir is off PATH, so that case is refused too.
73
+ - Nothing is removed and no profile is edited. The refusal prints the path, the conditional package-manager removal command and the next `./run.sh setup`. Installed mode is unchanged.
74
+ - A PASS covers only the directories checked: another shell's PATH, another package manager, a running bridge and the other host are outside it.
75
+ - The two setup smokes now drop host PATH entries that carry a package bin name and answer the global-bin queries with fixtures, so the host's own installation cannot reach their verdict.
76
+ - **`smoke-herdr-plugin-build-live` could not go green on the current Herdr and manifest.** Three fixture defects are fixed in the smoke alone; product code is unchanged.
77
+ - The snapshot and gap-probe clones now install their own frozen, offline, ignore-scripts dependencies before their ordinary commit, which the strict hook needs.
78
+ - Cell 1 locates the managed checkout from Herdr's own registration: exactly one `plugins.json` entry, whose `plugin_root` must resolve inside the sandbox. It no longer scans a `plugins/github/` layout that Herdr no longer writes.
79
+ - Cell 4's probe had replaced a `version = "0.1.0"` literal that the manifest stopped carrying at dd84ac0, so it changed nothing. It now derives the change from the version line it reads, and records the runner's code and the written change in a JSON receipt. The smoke accepts the post-build gap only when that receipt shows code 0 and a real change.
80
+
81
+ ## v2026.10.23-herdr.0.41.1 — Herdr Entwurf plugin 0.41.1
82
+
83
+ A separate Herdr plugin snapshot, not another core release: core package 0.41.1 and its
84
+ `v0.41.1` tag remain unchanged. Core 0.41.1 is now published to npm; the publication is
85
+ verified from the registry, not from observing who ran it.
86
+
87
+ **Reading guide (2026-10-23).** The `## 0.41.1` section below is prepare-time text. Its opening
88
+ "Unpublished candidate; release acceptance is not complete", its "Main still carries the
89
+ published 0.41.0 Herdr lock" and its closing "No tag, GitHub release, npm publication, Herdr
90
+ re-pin or laptop rollout has occurred" predate later facts: `v0.41.1` and its GitHub release
91
+ point at `6a88436` and that SHA's main push CI ran its four required jobs green ([#148 make
92
+ record](https://github.com/junghan0611/entwurf/issues/148#issuecomment-6353660427)), and npm
93
+ `latest` is 0.41.1 (published 2026-10-22T23:49:43.983Z), byte-equal to the accepted
94
+ Windows-bearing candidate ([#148 publication
95
+ record](https://github.com/junghan0611/entwurf/issues/148#issuecomment-6354276182)). When main
96
+ moved to `6a88436`, its Herdr lock was the `herdr-checkout` carrier, so until this snapshot
97
+ lands a main-tracking `herdr plugin install` acquires the checkout commit rather than an npm
98
+ artifact. Neither the published tarball's CHANGELOG nor the core release page is amended by
99
+ this snapshot.
100
+
101
+ ### Herdr plugin
102
+
103
+ - **Re-pin the published core 0.41.1 and close the checkout window.**
104
+ `plugins/herdr/runtime-lock.json` returns from the `herdr-checkout` carrier that the Pi 1.4.0
105
+ SDK candidate rode to production npm source `@junghanacs/entwurf@0.41.1` with the registry's
106
+ measured sha512 (`sha512-GEKfcpFC…xWqw==`). The plugin manifest moves 0.41.0 → 0.41.1;
107
+ admission remains Herdr >= 0.9.0 and the one-build/one-pane/Linux shape is unchanged. From
108
+ the `v2026.10.22-herdr.0.41.0` snapshot to `v0.41.1`, the changes under `plugins/herdr/`,
109
+ `scripts/herdr-runtime.mjs` and `scripts/fixtures/herdr-supply.json` were that lock's return
110
+ to `herdr-checkout` and the plugin README's candidate wording; the runtime helper and the
111
+ Herdr supply pin did not change.
112
+ - **Pi 1.4 is now required.** The acquired runtime carries the Pi 1.4.0 SDK set and accepts Pi
113
+ `>=1.4.0 <1.5`; the previous snapshot's runtime accepted Pi `>=1.3.0 <1.4`. Upgrade Pi to 1.4
114
+ first, then install or reinstall the plugin: a host still on Pi 1.3 is refused at activation
115
+ by name (`outside the supported range`), the check `entwurf setup` applies, and no Pi
116
+ settings are written.
117
+ - **Upgrade by acquisition source, not just version.** A settled npm-source 0.41.0
118
+ installation can rebind under the plugin README's ownership conditions. A host that
119
+ installed a `herdr-checkout` candidate — from main at `6a88436` or from a validation branch —
120
+ must explicitly deactivate by its own absolute runtime path before reinstalling the
121
+ npm-source snapshot. No operator installation or active session is rewired by this release
122
+ work.
123
+ - **What the acquired runtime carries, and what this path does not.** The runtime ships core
124
+ 0.41.1's bytes, including its nine-tool bridge and the pi-durable carrier. Herdr wires and
125
+ opens only `{pi, claude-code}`, so this plugin path launches no pi-durable session and makes
126
+ no claim about migrating durable-session storage. Inside Herdr, `entwurf_resume_call` still
127
+ refuses as `herdr-resume-unsupported`.
128
+ - **README.** The quick start and nine steps drop the candidate's `--ref "$CANDIDATE_REF"`
129
+ instructions and install against the ref Herdr resolves; the Pi step keeps `>=1.4.0 <1.5`,
130
+ and the runtime paragraph names npm 0.41.1 and points at the previous snapshot for Pi 1.3.x.
131
+ - **Current install docs.** The root README and the clean-host setup guide replace their "not
132
+ published to npm yet / newest published 0.41.0" install text with the published 0.41.1 and its
133
+ Pi 1.4.x requirement, and point at the previous 0.41.0 for Pi 1.3.x. Moving a live host onto
134
+ 0.41.1 remains a separate operator decision for each host, with the durable-session backup,
135
+ owner-exit and no-rollback boundaries unchanged.
136
+
137
+ ### Source-specific verification
138
+
139
+ - Registry metadata reports `latest` 0.41.1 and `repair` 0.12.8-repair.1, 642 files, published
140
+ 2026-10-22T23:49:43.983Z. The downloaded registry tarball is byte-equal (SHA256 and `cmp`) to
141
+ the preserved accepted core candidate (SHA256
142
+ `0172b049edde352d404b1119e330852b92ce235e3f2809e06d1ef0e71c95b95f`, 11406938 bytes); its
143
+ independently computed sha512 and sha1 equal `dist.integrity` and `dist.shasum`. Core make
144
+ already ran its container consumer against those same 0.41.1 bytes (repoDigest
145
+ `node@sha256:6dac556d…`, image ID `sha256:55ab623b…`, both recorded in make's artifact
146
+ receipt), so that result is reused here, not repeated. The new registry acquisition and
147
+ dependency resolution are closed separately: a sandboxed global npm install of the registry
148
+ package passed its installed `check-bridge` with nine tools, and a sandboxed `pi install`
149
+ resolved its bridge inside the sandbox, recorded the registry URL with that integrity, and
150
+ printed seven `entwurf` model rows (plus the header) including the curated Claude anchors.
151
+ The only checkout input of that `pi install` check, the bridge resolver script, is unchanged
152
+ on this branch. No model turn. The accepted run stripped every `PI_`, `ENTWURF_`, `CLAUDE`,
153
+ `CODEX_HOME`, `TMUX` and `HERDR_` carrier. These rows do not cover the runtime Herdr acquires
154
+ in use — its bins, its `check-bridge`, or the installed CLI's deactivate path — which belong
155
+ to the public-ref journey below. npm 11.16 listed the package's `postinstall` chmod as not
156
+ yet covered by `allowScripts`; whether that script ran was not checked. The installed
157
+ `entwurf` of the global install ran and its `check-bridge` exited 0.
158
+ - **The npm runtime swap against the real registry**, by the registry-shipped
159
+ `scripts/herdr-runtime.mjs` (byte-equal to this checkout's) in sandboxed user roots, 22/22
160
+ checks. The 0.41.0 runtime was seeded from its own committed lock (`addcb8c`); the swap to
161
+ 0.41.1 took one acquisition, the cached tarball's sha512 equals the lock and its sha256 equals
162
+ both the ready journal's observed digest and the accepted candidate. The `installing` journal
163
+ carried the 0.41.0 identity as `previousRuntime` and the `runtime-ready` journal carries
164
+ `null`; when acquisition began the 0.41.0 tree was still at the active address. A manually
165
+ seeded torn swap (the active tree moved to the backup address with a half candidate in
166
+ staging; not a killed process) was restored before acquisition and reported `recovered:
167
+ torn-swap-with-candidate`. An exact re-request acquired nothing and left journal and tree
168
+ byte-identical, and the owned removal reclaimed cache, runtime and journal. The helper is a
169
+ leaf that probes no Pi, so these cells certify neither a Pi upgrade nor Herdr's
170
+ install/update relocation, harness wiring, the installed CLI's deactivate path, a model turn,
171
+ or recovery from a real kill.
172
+ - **Focused gates, by surface.** On the pin surface (lock, manifest, plugin README and this
173
+ section before its later amendments), with HEAD, index and work surface unchanged across them:
174
+ `check-herdr-supply` (11 assertions), `check-herdr-plugin` (49),
175
+ `check-herdr-plugin-profile` (14), `check-herdr-runtime-bootstrap` (31),
176
+ `check-herdr-activation` (33), `check-herdr-placement` (25), `check-herdr-fresh-call` (45),
177
+ `check-herdr-plugin-build` (21, its real-binary cell on herdr 0.9.5), `check-herdr-sandbox`
178
+ (12, a private herdr 0.9.5 server), `check-dep-versions` (Pi 1.4.0 coherent),
179
+ `check-install-surface`, `check-claude-floor-coherence` and `check-gate-manifests` (1178
180
+ committed mutants across 85 lanes validated, none executed). After the root README, setup
181
+ guide and NEXT changes, the seven gates those documents affect ran again on that later
182
+ surface, unchanged across them: `check-dep-versions`, `check-node-floor-coherence`,
183
+ `check-claude-floor-coherence`, `check-pi-runtime-version`, `check-herdr-plugin` (49),
184
+ `check-install-surface` and `check-entwurf-v2-surface` (99). Neither set certifies the final
185
+ amended surface; that is closed by the frozen FULL.
186
+ - Pending, and not claimed here: the pushed SHA's four-job CI and the public-ref
187
+ install/use/deactivate journey of this npm pin, on Herdr 0.9.5 (the binary
188
+ `scripts/fixtures/herdr-supply.json` names) with Pi 1.4.0. Earlier npm or checkout-carrier
189
+ receipts cannot certify these source-specific axes. Those outcomes are recorded in the
190
+ snapshot's post-push acceptance ledger, not in this frozen text.
191
+
7
192
  ## 0.41.1 — 2026-10-23
8
193
 
9
194
  **Unpublished candidate; release acceptance is not complete.** The actual operator
package/DELIVERY.md CHANGED
@@ -126,7 +126,7 @@ physical host. Do not read a D-level cell as a Darwin receipt.
126
126
  |---|---|---|---|
127
127
  | **pi Entwurf** | shipped | D7; D8 partial | Record-addressed Unix control socket. A record-less socket is diagnostic only and never dispatched. A pi session calls the verbs through Pi's built-in MCP running the compiled `entwurf-bridge` (#125); the receive rail is unchanged. |
128
128
  | **Claude Code interactive `>=2.1.306`** | shipped; Linux certified | D6; D7/D8 partial | Per-session mailbox + exec-form `FileChanged`/`asyncRewake`. B2 proved idle wake and same-session continuity on one NixOS host. `>=2.1.306` is the current declared floor; that historical B2 receipt does not certify #147 resume/retire lifecycle on this floor. |
129
- | **Antigravity / agy** | shipped | D6; D7 partial | Record-backed native-push through LS gRPC `agentapi send-message`; no mailbox or receiver marker. Admitted before the #82 step 9 contract and not re-evaluated under it, so it is legacy citizen evidence, not a step-9 supported harness: `entwurf_fresh_call` cannot open an agy sibling, and nothing here should be read as claiming visible lifecycle parity. |
129
+ | **Antigravity / agy** | shipped, native push paused (#152) | D6; D7 partial | Record-backed native-push through LS gRPC `agentapi send-message` is paused: on the measured external path the agy LS refused `agentapi` calls for a missing CSRF token (#128; a first-party token path is unmeasured), so dispatch refuses an agy target pre-probe as `native-push-paused`. Birth, statusline and sender identity stay; no mailbox or receiver marker. Admitted before the #82 step 9 contract and not re-evaluated under it, so it is legacy citizen evidence, not a step-9 supported harness: `entwurf_fresh_call` cannot open an agy sibling, and nothing here should be read as claiming visible lifecycle parity. |
130
130
  | **Codex CLI app-server citizen** | supported in 0.21.0; caller-seat topology ACCEPTED on Linux at 0.153.4, 2026-09-16 (56 assertions, exit 0), and the caller-DIRECTORY axis ACCEPTED the same day on the same host (#95 lane C, 65 assertions, exit 0; the 2026-09-12 explicit-home acceptance is history — #95 D1 retired that room) | D6 accepted; D7 partial; D8 unproven | Vendor-trusted user-scope `SessionStart` birth plus strict per-request metadata join; no shared-pid sender marker. `thread/loaded/list` probes the operator-owned app-server UDS and one-shot `codex queue` delivers with no retry. The operator owns the app-server and seats it wherever they like; Entwurf creates, moves and supervises none of it. Since #95 lane B a Codex CALLER with no explicit placement opens its sibling beside its OWN pane, located by the `thread-id` its `[tui].terminal_title` writes into the pane title; 0 or 2+ matching panes refuse with no fallback, and the title is a placement input only — never an address, liveness or delivery fact. #95 D1 (2026-09-16) retired the fixed `codex` home that omitted-placement Codex TARGETS used to select, so an omitted seat is the caller's own session for every backend. Exact 0.153.4 source still bounds placing a sibling beside a TUI whose thread nobody named as unsupported; the anchor resolves only the pane showing the caller's own thread. |
131
131
  | **Codex embedded TUI** | deferred | D0 partial | At the 2026-09-08 Codex 0.153.4 measurement, standalone mode had no `watchPaths`/`FileChanged`/`asyncRewake` analogue or supported idle receive route. This is dated vendor evidence, not a claim that the current candidate lacks records or fresh. |
132
132
  | **Copilot CLI first-party extension** | raw transport probe; superseded by the owned product unit | D7 path observed; D3 control receipt incomplete; D8 unproven | CLI-spawned extension over stdio JSON-RPC; `joinSession()` + documented `fs.watch` → `session.send({mode:"enqueue"})`. Idle wake, exact-marker reply, and completion passed on 2026-08-23 (CLI 1.0.80, L4, one Linux host). Two-process isolation was observed but its decisive B log was not preserved. Kept as the transport receipt the owned receive unit was built on; the shipped unit differs deliberately — it announces the inbox instead of injecting the body. |
@@ -156,13 +156,24 @@ coalescing bounds, re-arm gaps, and crash/re-poke behavior are measured. A synth
156
156
  container doctor proves package/oracle shape, not a real Claude wake; a claimed host
157
157
  needs the installed strict doctor against a new native session.
158
158
 
159
- ### Antigravity: native push
159
+ ### Antigravity: native push (paused, #152)
160
160
 
161
161
  `PreInvocation` births or reattaches a citizen by native `conversationId` and writes a
162
- record-backed sender marker. `entwurf_v2` probes the live conversation and injects
163
- directly through the native adapter, with one bounded re-probe retry. Replyability is
164
- `record-backed identity ∧ probe-alive`; mailbox state does not exist on this rail, and no
165
- rail has resume authority since the visible-first cut.
162
+ record-backed sender marker. Native push is paused: on the external send path entwurf
163
+ measured, the agy language server refused the `agentapi` call with `Unauthenticated: missing
164
+ CSRF token` (#128 on agy 1.2.16, again on agy 1.4.6). Whether a first-party own-tool shell
165
+ holds a token entwurf could legitimately present is unmeasured, not ruled out. One fixed constant,
166
+ `NATIVE_PUSH_PAUSED_BACKENDS` in the v2 contract, drives three refusals before any probe:
167
+ `entwurf_v2` rejects an agy target as `native-push-paused` with `observedLiveness: null`, a
168
+ sender's `replyable` is false with a reason naming the pause, and `entwurf_register_native`
169
+ refuses without minting a garden id. Existing records, the backend enum and the record
170
+ reader are unchanged, so agy citizens stay readable and listed. The fresh-cut quiescence
171
+ probe is deliberately not behind the pause: it protects an operator-named archive and keeps
172
+ failing closed. When the pause lifts, the adapter path below applies again: `entwurf_v2`
173
+ probes the live conversation and injects directly through the native adapter, with one
174
+ bounded re-probe retry, and replyability is `record-backed identity ∧ probe-alive`.
175
+ Mailbox state does not exist on this rail, and no rail has resume authority since the
176
+ visible-first cut.
166
177
 
167
178
  The owned bridge, statusline, and hook installers own separate configuration atoms.
168
179
  Same-pid concurrent model invocation by multiple conversations is not claimed because
@@ -587,39 +598,45 @@ same class of capability through the CLI-owned stdio lifecycle. Absence of Claud
587
598
  `FileChanged` / `asyncRewake` / `watchPaths` therefore means only that Claude's hook
588
599
  mechanism cannot be copied — it never proved Copilot had no vendor wake surface.
589
600
 
590
- ### Remote pair (#107): one declared pair across two hosts
601
+ ### Remote pairs (#107, #149): declared pairs across hosts
591
602
 
592
- This is an opt-in route between two existing citizens on two hosts. Why it keeps these boundaries, and which questions it leaves open, is [remote-sibling-principles](./docs/remote-sibling-principles.md); this section owns the mechanism. It is off unless the operator places a file on each host. The operator owns the connection between the hosts (a private network such as Tailscale is recommended, not required) and owns SSH authentication and authorization. Entwurf installs, configures, discovers and retries none of it. It adds no relay, listener, queue or registry. One SSH process runs per send and then exits.
603
+ This is an opt-in route between existing citizens on two hosts. Why it keeps these boundaries, and which questions it leaves open, is [remote-sibling-principles](./docs/remote-sibling-principles.md); this section owns the mechanism. It is off unless the operator places a file on each host. The operator owns the connection between the hosts (a private network such as Tailscale is recommended, not required) and owns SSH authentication and authorization. Entwurf installs, configures, discovers and retries none of it. It adds no relay, listener, queue or registry. One SSH process runs per send and then exits.
593
604
 
594
- - **Declaration.** Each host has at most one `entwurf-remote-pair.json`, beside its meta-record store (`remotePairOptInPath()`: the pi agent dir, unless `ENTWURF_META_SESSIONS_DIR` moves the store and the file with it). It has exactly four keys:
595
- - `remoteGardenId`.
605
+ - **Declaration (#149).** Each host has at most one `entwurf-remote-pair.json`, beside its meta-record store (`remotePairOptInPath()`: the pi agent dir, unless `ENTWURF_META_SESSIONS_DIR` moves the store and the file with it). It is exactly `{"version": 2, "routes": [...]}` with a non-empty `routes` array. Each route is one exact pair with exactly four keys:
606
+ - `localGardenId`: the citizen on this host.
607
+ - `remoteGardenId`: the citizen on the other host.
596
608
  - `ssh`: an existing SSH alias whose characters need no quoting.
597
- - `localGardenId`.
598
609
  - `remotePairFile`: the other host's canonical absolute path to its own file.
599
610
 
600
- One file serves both directions. An absent file means off. A symlink, non-regular, unreadable or invalid file is refused. The file is read at every send and receive and never cached. The sending bridge and the SSH-spawned receive entry must resolve the same root; an override set in only one of those environments moves the file.
611
+ One route serves both directions of its pair. An absent file means off. A symlink, non-regular, unreadable or invalid file is refused, and so is an empty `routes`. The versionless single-pair object of 0.41 and earlier is refused by name with the manual rewrite (`{"version":2,"routes":[<that object>]}`); there is no legacy reader. The file is read at every send and receive and never cached. The sending bridge and the SSH-spawned receive entry must resolve the same root; an override set in only one of those environments moves the file.
612
+
613
+ The set of routes is checked as a whole, never by position:
614
+ - Allowed: one local citizen paired with several remotes, one remote paired with several locals, and several routes over one alias.
615
+ - Refused: the same exact pair twice; a garden id that is local in one route and remote in another (a route to itself included); one remote garden id with two different aliases or files; one alias with two different `remotePairFile` values, because the receive entry behind that alias accepts only its one canonical file.
616
+
617
+ A broken file is refused at every `entwurf_v2` call of that bridge, local targets included, because the bridge reads the file before it can tell a remote target from a local one. That is not a silent fallback, and it is a cost to keep in mind when editing the file.
601
618
  - **Send.** The sending host's `entwurf_v2` reads the file and then decides:
602
- - A target other than `remoteGardenId` takes the unchanged local path.
619
+ - A target that no route names as remote takes the unchanged local path.
603
620
  - A declared target that also has a local trace on this host (a record in any state, or a control socket) is refused as `remote-route-local-conflict`, with zero SSH.
604
- - A sender other than `localGardenId` is refused as `remote-sender-not-declared`, with zero SSH.
605
- - Otherwise it spawns one `ssh -o BatchMode=yes -o ConnectTimeout=10 <alias> entwurf remote-pair-receive --pair <remotePairFile>`, with no shell and the claim on stdin.
606
- - **Receive.** The entry accepts only when `--pair` equals its own canonical file path. Canonical means the normalized expected path, not realpath equivalence: a spelling through a symlinked home alias is refused. That file is the entry's whole scope. It refuses a `from` or `target` outside the declared pair before any mailbox mutation. A match goes to that host's existing local `entwurf_v2` dispatch, so every deliverability rule of the target's rail applies there; the measured pair used the self-fetch mailbox.
621
+ - Otherwise the route is the one whose two ends are this sender and this target. If there is none, the call is refused as `remote-sender-not-declared`, with zero SSH and no local fallback. That also covers an anonymous sender.
622
+ - That route spawns one `ssh -o BatchMode=yes -o ConnectTimeout=10 <alias> entwurf remote-pair-receive --pair <remotePairFile>`, with no shell and the claim on stdin.
623
+ - **Receive.** The entry accepts only when `--pair` equals its own canonical file path. Canonical means the normalized expected path, not realpath equivalence: a spelling through a symlinked home alias is refused. That file is the entry's whole scope. It looks for the one route whose remote end is the claim's `from` and whose local end is the claim's `target`, comparing both ends in that same route. A `from` that no route declares is refused as `remote-pair-from-mismatch`. A declared `from` addressing anything but its own declared local, including another route's local, is refused as `remote-pair-target-mismatch`. Both refusals come before any mailbox mutation. A match goes to that host's existing local `entwurf_v2` dispatch, so every deliverability rule of the target's rail applies there; the measured pairs used the self-fetch mailbox.
607
624
  - **Outcomes.** The send ends in one of three outcomes:
608
625
  - the other host's receipt, quoted verbatim (its paths are on the other host). This is either an acceptance or an error the other host reported while processing (`remote-reported-error`, exit 4). That error can follow a write, for example an enqueue whose later step threw and is reported `retry-safe: false`. The sender's header therefore claims neither non-delivery nor safety to resend; the quoted text is the fact;
609
626
  - the other host's named refusal, made before any mutation: a pair mismatch (exit 3) or invalid input (exit 2);
610
627
  - `remote-indeterminate`.
611
628
 
612
629
  A result is decided only when the exit code and the JSON kind agree. There is no retry. An unanswered message stays unconfirmed. Acceptance is not a read or a reply. Neither side learns whether its last message was read unless the other side answers, and there is no third ACK.
613
- - **Display.** The received envelope names the remote origin and route, marks the sender's cwd as a sender-host path, and carries `replyable=false`, with "reply route declared, not probed" stated separately. Display is proven only for the self-fetch mailbox body; control-socket and native-push rendering are not proven. `entwurf_callback` stays local.
614
- - **Trust.** The model is same-operator SSH account trust. The receiving host checks the declared pair. It does not cryptographically authenticate a citizen, and the same account could claim either declared id.
630
+ - **Display.** The received envelope names the remote origin and the matched route's alias, marks the sender's cwd as a sender-host path, and carries `replyable=false`, with "reply route declared, not probed" stated separately. Display is proven only for the self-fetch mailbox body; control-socket and native-push rendering are not proven. `entwurf_callback` stays local.
631
+ - **Trust.** The model is same-operator SSH account trust. The receiving host checks the declared pairs. It does not cryptographically authenticate a citizen, and the same account could claim any declared id. With several routes, everything allowed to log in to that account is inside this boundary. That includes the keys of every other host. Separating them is the operator's SSH policy; Entwurf adds no per-route or per-key binding.
615
632
  - **Visibility.** For the measured self-fetch pair, delivery requires backing records, live owners and armed watches, so an OFF citizen of that rail is undeliverable. Any other target keeps its own rail-specific local admission. The remote route has no launch, fresh or resume path. The code does not check that the target's screen is open: an owner pid, a TTY or a garden-id label is not visibility. Live use addresses only siblings the user has open, and delivery to an active pi-durable can start a turn at once.
616
633
  - **SSH side effects.** The transport adds exactly two options, `BatchMode=yes` and `ConnectTimeout=10`. Host-key checking, known-hosts writes and every other SSH policy remain the operator's, as configured. Their side effects on a real exchange were not measured.
617
634
 
618
635
  Evidence:
619
636
 
620
- - T1 (executor-in) and T2 (surface-in) are deterministic. T2 drives the real bridge over MCP stdio, and a stand-in `ssh` on PATH runs the other host's real entry under a separate HOME. An installed-tarball gate runs the T2 cells against installed bytes.
637
+ - T1 (executor-in) and T2 (surface-in) are deterministic. T2 drives the real bridge over MCP stdio, and a stand-in `ssh` on PATH runs the other host's real entry under a separate HOME. Since #149 it also runs three hosts, where one host's one file carries three routes: fan-out, a shared remote, both directions, an undeclared sender with zero SSH, and a cross claim refused on the receiving host. An installed-tarball gate runs the T2 cells against installed bytes.
621
638
  - One real exchange is recorded in [#107](https://github.com/junghan0611/entwurf/issues/107#issuecomment-6278665895), at branch commit `e632294` on 2026-10-20. A nonce went B→A→B between two already-open pi-durable siblings on two Linux hosts. That is L4: one pair, one run.
622
- - Unmeasured: an OFF target, negative paths over real SSH, other rails, more than one pair, and non-Linux hosts.
639
+ - Unmeasured: an OFF target, negative paths over real SSH, other rails, more than one pair over real SSH (the #149 schema has no T3 yet, so the #107 exchange does not carry over to it), a sender and a receiver on different releases (the claim, argv, exit codes and receipt are unchanged, so that interoperation is inferred, not measured), and non-Linux hosts.
623
640
 
624
641
  ## Recording a new claim
625
642
 
package/README.md CHANGED
@@ -28,10 +28,11 @@ Git branches, worktrees, file isolation and internal delegation remain their con
28
28
  | **Open** a new sibling | `entwurf_fresh_call` | Herdr: a new unfocused tab in the caller's workspace. tmux: a new window in the caller's session, or an existing session named by `placement.tmuxSession`; without an explicit tmux placement, a Codex caller opens beside its own TUI pane | Herdr: pi, Claude Code. tmux: pi, pi-durable, Claude Code, Copilot, OMP, Codex |
29
29
  | **Reopen** a dormant sibling | `entwurf_resume_call` | A visible tmux window under the same garden id, with its record selecting the native session (Pi: identity-checked recorded transcript supplies launch metadata; Claude Code: `claude --resume <session id>` at the record cwd); the verb submits no prompt | pi, Claude Code |
30
30
  | **Retire** one sibling the human operator explicitly requested | `entwurf_retire_call` | One guarded attempt on that target: a Pi shutdown request or Claude Code fd-pinned process termination. Refused/unknown outcomes remain possible; the record stays for guarded same-id return. Not a grant, a roster cleanup or an automatic retry | pi, Claude Code |
31
- | **Message** an existing sibling | `entwurf_v2` | Its own rail: control socket, self-fetch mailbox or native push; delivery starts no process | pi, pi-durable, Claude Code, Copilot, OMP, Antigravity, Codex |
31
+ | **Message** an existing sibling | `entwurf_v2` | Its own rail: control socket, self-fetch mailbox or native push; delivery starts no process | pi, pi-durable, Claude Code, Copilot, OMP, Codex (Antigravity native push is paused, #152) |
32
32
 
33
- Antigravity is message-only: an existing conversation registers itself, and Entwurf never
34
- launches one. A launch receipt is neither the new sibling's address nor proof that it
33
+ Antigravity is a legacy citizen: an existing conversation registers itself, and Entwurf never
34
+ launches one. Its native push is paused (#152): on the measured external path the agy language server
35
+ refused Entwurf's calls for a missing CSRF token, so a message to it is refused as `native-push-paused`. A launch receipt is neither the new sibling's address nor proof that it
35
36
  processed its task; the sibling's own callback envelope supplies its garden id.
36
37
 
37
38
  The package holds the v2 core, native-harness bridges, a pi adapter and an ACP
@@ -57,23 +58,16 @@ to use, already installed and authenticated. Pi is optional-by-presence, support
57
58
  range `>=1.4.0 <1.5`; Claude Code supported floor `>=2.1.306` is required for its
58
59
  managed exec-hook lifecycle. pnpm is needed only for source-checkout setup.
59
60
 
60
- **Release state.** The Pi range above belongs to this source candidate, which moves the Pi SDK
61
- set to 1.4.0 and is **not published to npm yet**. The newest published package, `0.41.0`,
62
- ships the Pi 1.3.0 SDK set and requires Pi 1.3.x; the README inside that tarball and the Herdr
63
- snapshot `v2026.10.22-herdr.0.41.0` are its contract. Pick the path that matches the Pi you run.
64
-
65
- The published package (Pi 1.3.x):
61
+ **Release state.** The Pi range above belongs to the published 0.41.1, which ships the Pi
62
+ 1.4.0 SDK set. The previous `0.41.0` ships the Pi 1.3.0 set and requires Pi 1.3.x; the README
63
+ inside that tarball and the Herdr snapshot `v2026.10.22-herdr.0.41.0` are its contract.
66
64
 
67
65
  ```bash
68
- npm install -g @junghanacs/entwurf@0.41.0
66
+ npm install -g @junghanacs/entwurf@0.41.1
69
67
  entwurf setup /path/to/your-project
70
68
  entwurf check-bridge
71
69
  ```
72
70
 
73
- This candidate (Pi 1.4): a source checkout of this commit, set up with `./run.sh setup` as
74
- [Clean-host setup](./docs/setup-clean-host.md) describes. It has no npm install command until it
75
- is released.
76
-
77
71
  `setup` composes each harness it finds and reports PASS / SKIP / FAIL. An absent
78
72
  harness is an explicit SKIP; a detected incomplete/below-floor integration is a
79
73
  named FAIL with nonzero exit, not a false green. `check-bridge` proves the installed
@@ -97,8 +91,8 @@ an npm package without an `os` restriction is not an all-platform certification.
97
91
 
98
92
  ### Native durable (pi-durable)
99
93
 
100
- **This candidate's package supplies Entwurf's owned app/TUI carrier and nine exact Pi 1.4.0 SDK
101
- members** (unpublished; the published 0.41.0 supplies the 1.3.0 set). No separate runtime checkout, patch, build or ordinary Pi CLI is needed.
94
+ **The package supplies Entwurf's owned app/TUI carrier and nine exact Pi 1.4.0 SDK
95
+ members** (the previous 0.41.0 supplies the 1.3.0 set). No separate runtime checkout, patch, build or ordinary Pi CLI is needed.
102
96
  The launch verifies carrier integrity and every reachable SDK edge's name/version;
103
97
  incomplete, drifted or mixed supply refuses by name.
104
98
 
@@ -128,17 +122,17 @@ owns flags, refusals and the extension contract.
128
122
  ### Herdr workbench
129
123
 
130
124
  ```bash
131
- herdr plugin install junghan0611/entwurf/plugins/herdr --ref "$CANDIDATE_REF" --yes
125
+ herdr plugin install junghan0611/entwurf/plugins/herdr --yes
132
126
  ```
133
127
 
134
- Set `CANDIDATE_REF` explicitly to a published branch or tag that carries the runtime you mean;
135
- these examples do not select a candidate from Herdr's default ref. A validation branch is not a
136
- release acceptance. The [Herdr guide](./plugins/herdr/README.md#from-a-raw-linux-box-in-nine-steps)
137
- says which runtime each ref installs.
128
+ The [Herdr guide](./plugins/herdr/README.md#from-a-raw-linux-box-in-nine-steps) says which
129
+ runtime each ref installs.
138
130
 
139
131
  This separate path activates Herdr-integrated **pi and Claude Code** with an
140
132
  independently locked runtime and an observation pane; it puts no Entwurf bins on PATH.
141
- An Entwurf release does not automatically re-pin that runtime.
133
+ An Entwurf release does not automatically re-pin that runtime: while 0.41.2 is an unpublished
134
+ candidate, main's lock rides the `herdr-checkout` verification carrier, and the published 0.41.1
135
+ runtime stays pinned by the Herdr tag `v2026.10.23-herdr.0.41.1`.
142
136
  [Herdr guide](./plugins/herdr/README.md) owns install, use, evidence and removal;
143
137
  the plugin does not own delivery or the Herdr launch rail.
144
138
 
@@ -199,50 +193,61 @@ pi / pi-durable / Claude Code / Copilot / OMP / Antigravity / Codex
199
193
  → control-socket | self-fetch mailbox | native-push
200
194
  ```
201
195
 
202
- | What you want | Tool | What its receipt means |
203
- |---|---|---|
204
- | Message, reply or hand off to an existing garden id | `entwurf_v2 {target, intent:"fire-and-forget", message}` | Receiver acceptance boundary, mailbox enqueue or native injection; not completion |
205
- | Open a new visible sibling | `entwurf_fresh_call {backend, model, task, cwd?}` | Launch receipt only; the sibling's nonce callback envelope supplies its new garden id |
206
- | Reopen a dormant pi or Claude Code citizen under its own id | `entwurf_resume_call {target}` | Separate LAUNCH and OBSERVATION receipts (pi: same-id socket; Claude Code: a new live receiver marker in the opened pane, bound to the record); the verb submits no prompt |
207
- | Attempt retirement of one explicitly requested Pi or Claude Code target | `entwurf_retire_call {target}` | Separate action, append-only record and lock-release results. Pi ACK is request-only; CC exit is that fd-pinned process only |
208
-
209
- **Retirement is never autonomous.** It requires the human operator's explicit target/action;
210
- a role, caller identity or tool annotation grants no permission. Host approval applies where
211
- provided or configured. No roster expansion, timer or retry of an unknown/cancelled/lost result;
212
- an absent event is unknown, not proof that no action occurred.
213
- [DELIVERY](./DELIVERY.md#candidate-lifecycle-commands-are-not-delivery-boundaries-147)
214
- owns the lifecycle boundaries; [BASELINE](./BASELINE.md#147-native-round-trip-2026-10-22-n1)
215
- keeps dated native evidence separate from release and rollout acceptance.
216
-
217
196
  **Delivery starts no process.** A live pi receives over its record-addressed control
218
- address (a Unix socket; on the experimental native-Windows Pi preview, a named pipe);
219
- an armed self-fetch citizen (Claude Code, Copilot, OMP, pi-durable) through its mailbox; a probe-alive native-push citizen (Antigravity, Codex) through
220
- its native conversation/thread. Dormant pi, undeliverable mailboxes, dead/indeterminate native
221
- push and record-less sockets reject honestly. Only control-socket dispatch takes
222
- the per-target lock; the other rails use their own deliverability/probe evidence.
223
-
224
- A control-socket `sent` means a turn was triggered, not that the model read the
225
- text. `queued-steer` and `queued-follow-up` are volatile process queues, with no
226
- FIFO between them; abort drops them. `accepted-unknown-boundary` is acceptance
227
- we cannot classify, never a reason to retry. A mailbox enqueue is a durable file
228
- receipt. `wants_reply` is etiquette, not waiting, polling or ownership; replyability
229
- is a live rail fact, not merely a consequence of trusted identity.
230
- [DELIVERY](./DELIVERY.md#control-socket-acceptance-boundaries-120) owns these distinctions.
231
-
232
- **One declared remote pair (opt-in, #107).** The operator places an `entwurf-remote-pair.json` on each of two hosts. Then `entwurf_v2` to the declared remote garden id runs the other host's `entwurf remote-pair-receive` once over the operator's own SSH, and that host checks the pair and dispatches locally. The trust is the same operator's SSH account. It makes one attempt with no retry, and it uses no relay or listener. [DELIVERY](./DELIVERY.md#remote-pair-107-one-declared-pair-across-two-hosts) owns the contract; [setup](./docs/setup-clean-host.md#2c-optional-remote-pair-between-two-hosts-107) covers the operator steps.
233
-
234
- **Mux launches; it never delivers.** Fresh uses the caller's Herdr workspace or tmux server,
235
- within the harness limits in the table above. The model is required; optional `cwd` is a literal
236
- absolute directory (omitted or empty means caller cwd). `placement.tmuxSession` names an
237
- existing tmux session, never a new session or a cwd lookup, and is refused inside Herdr.
238
- Without that override, a Codex caller opens beside its thread-id-matched TUI pane;
239
- other callers use their own session. Missing or ambiguous context refuses without fallback.
240
- Resume is tmux-only for pi and Claude Code, selected by the record, with no submitted prompt.
241
- Claude Code dormancy covers only attaches its hook committed, not every possible session holder;
242
- [V3 arm binding](./docs/adding-a-harness.md#arm-binding-within-v3) is not occupancy or permission.
243
- An unobserved window stays visible without retry. Address liveness alone is not conversation recall.
244
- Exact argv, identity checks and limits belong to [tmux launch](./docs/mux-launch-rail.md)
245
- and [Herdr launch](./docs/herdr-launch-rail.md).
197
+ address, an armed self-fetch citizen (Claude Code, Copilot, OMP, pi-durable) through its
198
+ mailbox, a probe-alive native-push citizen (Codex; Antigravity is paused, #152) through its own
199
+ conversation or thread. Anything else is refused honestly, never launched into. The receipt
200
+ is a receiver acceptance boundary, a durable mailbox file or an injection, never completion;
201
+ `wants_reply` is etiquette, not waiting. [DELIVERY](./DELIVERY.md#control-socket-acceptance-boundaries-120)
202
+ owns what each boundary means and each rail's admission.
203
+
204
+ The lifecycle verbs in the [table above](#entwurf) return their own receipts, never a delivery
205
+ result: fresh a launch receipt (the sibling's callback envelope supplies its garden id), resume
206
+ separate LAUNCH and OBSERVATION receipts, retire separate action, record and lock results.
207
+ **Retirement is never autonomous:** it needs the human operator's explicit target and action, and
208
+ no role, caller identity or tool annotation grants it ([DELIVERY](./DELIVERY.md#candidate-lifecycle-commands-are-not-delivery-boundaries-147)).
209
+
210
+ **Mux launches; it never delivers.** Fresh opens in the caller's Herdr workspace or tmux session;
211
+ `placement.tmuxSession` names another existing tmux session and is refused inside Herdr. The model
212
+ is required and `cwd` is a literal absolute directory. Missing or ambiguous context refuses with no
213
+ fallback. Exact argv, identity checks and limits: [tmux launch](./docs/mux-launch-rail.md),
214
+ [Herdr launch](./docs/herdr-launch-rail.md).
215
+
216
+ ### Remote siblings — declared pairs across hosts
217
+
218
+ Opt-in. The v2 file below is in main and the 0.41.2 candidate; the published 0.41.1 reads the
219
+ earlier single-pair file. On each host, write `~/.pi/agent/entwurf-remote-pair.json` (mode `0600`; a
220
+ moved agent dir or meta store moves it, see [setup §2c](./docs/setup-clean-host.md#2c-optional-remote-pair-between-two-hosts-107)). The ids
221
+ below are fictional; use your own records' garden ids:
222
+
223
+ ```json
224
+ {
225
+ "version": 2,
226
+ "routes": [
227
+ {
228
+ "localGardenId": "20260101T090000-a1b2c3",
229
+ "remoteGardenId": "20260101T091500-d4e5f6",
230
+ "ssh": "host-b",
231
+ "remotePairFile": "/home/me/.pi/agent/entwurf-remote-pair.json"
232
+ }
233
+ ]
234
+ }
235
+ ```
236
+
237
+ The other host carries the mirror route: the two garden ids swapped, its own SSH alias for this
238
+ host, and this file's canonical absolute path. Then send with the ordinary `entwurf_v2` to the
239
+ remote garden id: one SSH attempt over your own account, checked and dispatched by the host that
240
+ holds the record. There is no peer discovery, wake or retry. Why: [remote sibling principles](./docs/remote-sibling-principles.md);
241
+ contract: [DELIVERY](./DELIVERY.md#remote-pairs-107-149-declared-pairs-across-hosts); steps and the
242
+ hand conversion from the earlier file: [setup §2c](./docs/setup-clean-host.md#2c-optional-remote-pair-between-two-hosts-107).
243
+
244
+ ### Team bookmarks — `team.json`
245
+
246
+ A person's bookmark of the siblings one project works with. Copy [`team.json.example`](./team.json.example)
247
+ to an ignored `team.json` at the project root and fill names, backend, garden id, native session id
248
+ and cwd from Entwurf's record facts; read liveness from `entwurf_peers` when you dispatch. Entwurf
249
+ does not load it as runtime configuration, and it is not configuration for identity, routes, roles, permissions or lifecycle. The
250
+ example's own notes say the rest.
246
251
 
247
252
  ## Concepts
248
253