@junghanacs/entwurf 0.39.0 → 0.40.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 (31) hide show
  1. package/BASELINE.md +5 -3
  2. package/CHANGELOG.md +236 -0
  3. package/DELIVERY.md +34 -0
  4. package/README.md +11 -9
  5. package/VERIFY.md +11 -1
  6. package/docs/setup-clean-host.md +42 -3
  7. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +19 -5
  8. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +10 -4
  9. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +123 -0
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +259 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-transport.js +80 -0
  12. package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +65 -0
  13. package/mcp/entwurf-bridge/src/index.ts +25 -5
  14. package/mcp/entwurf-bridge/tsconfig.build.json +7 -0
  15. package/package.json +2 -2
  16. package/pi-extensions/lib/meta-mailbox-body.ts +13 -4
  17. package/pi-extensions/lib/remote-pair-receive.ts +184 -0
  18. package/pi-extensions/lib/remote-pair-send.ts +325 -0
  19. package/pi-extensions/lib/remote-pair-transport.ts +81 -0
  20. package/run.sh +44 -0
  21. package/scripts/check-gate-qualification.ts +6 -0
  22. package/scripts/check-remote-pair-installed.ts +242 -0
  23. package/scripts/lib/remote-pair-installed-audit.ts +25 -0
  24. package/scripts/mutants/control-send-receipt.json +2 -2
  25. package/scripts/mutants/remote-pair-display.json +53 -0
  26. package/scripts/mutants/remote-pair-installed-audit.json +28 -0
  27. package/scripts/mutants/remote-pair-receive.json +126 -0
  28. package/scripts/mutants/remote-pair-roundtrip.json +83 -0
  29. package/scripts/mutants/remote-pair-send.json +194 -0
  30. package/scripts/mutants/remote-pair-transport.json +66 -0
  31. package/scripts/remote-pair-receive.ts +73 -0
package/BASELINE.md CHANGED
@@ -46,8 +46,10 @@ outside the experimental preview (rows below).
46
46
  | macOS physical-host probe | shipped, unrun on a Mac | `scripts/raw-macos-measure/` — `/bin/sh`, cells M1–M9, 3.5 s; needs neither entwurf install nor login. Still unmeasured: Darwin foreign-process environ (`ps -Eww`) and whether macOS `python3` is a CommandLineTools stub. |
47
47
  | WSL2 Entwurf-only install + zero-token Pi citizen (GitHub Actions `windows-2025` runner, not a physical machine) | shape-only | Experimental, non-voting CI (not read by the release oracle): 2026-10-18, `.github/workflows/wsl2-install-surface.yml` run 39098773010 @ `b03d646` ([#78 receipt](https://github.com/junghan0611/entwurf/issues/78#issuecomment-6245947301)). Guest kernel `6.18.40.1-microsoft-standard-WSL2`, host `wsl --list --verbose` VERSION 2, guest fstype quoted raw as `ext2/ext3`. The run's packed tgz digest is equal across producer, host and guest; guest Node 24.18.1; harness-absent `setup` green with protected auth/harness state unchanged. Pi 1.3.0 (sha512 pinned to the registry integrity) wired by `setup`: one V3 record whose filename is its gid, a gid-keyed control socket with a native connect, `peer-facts` alive; only `get_state` was sent and no `agent_start` was observed. Pi creating an empty `auth.json` is not a login. It certifies no harness rail. Linux-guest runtime evidence only: not native-Windows evidence, and not a substitute for the required Linux CI jobs. |
48
48
  | WSL2 delivery, non-pi harnesses, vendor turn, physical user machine | unverified | No receipt. Authentication and model turns were deliberately excluded from the CI cells' scope (not a CI impossibility); non-pi harnesses, delivery, fresh/resume and physical machines are unmeasured. |
49
- | native Windows experimental preview: Pi citizen + `entwurf_v2` delivery (GitHub Actions `windows-2025` x64 runner, Node 24.18.1, Pi 1.3.0; 0.39.0 candidate, not yet released; release and npm publication pending; the evidence below is the feature-branch CI artifact at `3b65bdc`) | shape-only | Experimental, non-voting CI (not read by the release oracle): 2026-10-18, `.github/workflows/windows-native-preview.yml` run 39123555360 @ `3b65bdc`, with the required `ci.yml` four jobs green at the same SHA (run 39123555224). Build: MSVC (VS 2026, toolset v145, SDK 10.0.26100.0), two builds byte-identical (`entwurf_win.node` sha256 `9837893521ec…`), imports advapi32 + kernel32 with `node.exe` the only delay-load; node-gyp 13.1.0 and the Node headers/`node.lib` pinned. The pack carries exactly the addon and its manifest; npm installed it into a sandboxed consumer prefix; Pi came from a tarball checked against its pinned SRI. Six addon cells PASS (final-component reparse and non-regular refusals on the read handle, no connect through a symlink to a pipe, one-deadline timeout with reclaim, pipe-name stability and loader refusals). B1: two `pi --entwurf-control` citizens, one V3 record each, record-derived pipes alive through the product probe, `peer-facts` alive, graceful exit with both addresses dead afterwards. D1: A's model called `entwurf_v2` toward B's garden id with a nonce; A's receipt `control-socket → sent`, B's model context and B's transcript `entwurf-message` entry from A carry it. The model is an offline pi-ai `fauxProvider`: no vendor turn and no comprehension claim. The transcript fact is the harness's assertion; the raw transcript is not archived. The run used the elevated Administrators owner branch, so user-SID-only privacy is not measured. Pi's `peerDependencies` warning was observed, not waived. Not evidence for any other harness on Windows. |
49
+ | native Windows experimental preview: Pi citizen + `entwurf_v2` delivery (GitHub Actions `windows-2025` x64 runner, Node 24.18.1, Pi 1.3.0; ships in core 0.39.0; the evidence below is the feature-branch CI artifact at `3b65bdc`, and the released tarball's own Windows cells are preview run 39146098592 @ `551402c`) | shape-only | Released tarball: preview run 39146098592 @ `551402c` installed the core 0.39.0 tarball (sha256 `1d5693241b8c…`) and its eight cells (L0, L0c, R1, R2, T1, N1, B1, D1) PASS, with a faux model and no vendor turn. The text that follows is the earlier feature-branch receipt (same addon sha256 `9837893521ec…`, not the release pack). Experimental, non-voting CI (not read by the release oracle): 2026-10-18, `.github/workflows/windows-native-preview.yml` run 39123555360 @ `3b65bdc`, with the required `ci.yml` four jobs green at the same SHA (run 39123555224). Build: MSVC (VS 2026, toolset v145, SDK 10.0.26100.0), two builds byte-identical (`entwurf_win.node` sha256 `9837893521ec…`), imports advapi32 + kernel32 with `node.exe` the only delay-load; node-gyp 13.1.0 and the Node headers/`node.lib` pinned. The pack carries exactly the addon and its manifest; npm installed it into a sandboxed consumer prefix; Pi came from a tarball checked against its pinned SRI. Six addon cells PASS (final-component reparse and non-regular refusals on the read handle, no connect through a symlink to a pipe, one-deadline timeout with reclaim, pipe-name stability and loader refusals). B1: two `pi --entwurf-control` citizens, one V3 record each, record-derived pipes alive through the product probe, `peer-facts` alive, graceful exit with both addresses dead afterwards. D1: A's model called `entwurf_v2` toward B's garden id with a nonce; A's receipt `control-socket → sent`, B's model context and B's transcript `entwurf-message` entry from A carry it. The model is an offline pi-ai `fauxProvider`: no vendor turn and no comprehension claim. The transcript fact is the harness's assertion; the raw transcript is not archived. The run used the elevated Administrators owner branch, so user-SID-only privacy is not measured. Pi's `peerDependencies` warning was observed, not waived. Not evidence for any other harness on Windows. |
50
50
  | native Windows: own pre-existing pipe-name birth refusal, record link/rename syscalls, descendant cleanup after a forced kill, cross-user, mixed elevation, remote callers, user-SID-only privacy, physical user machine | unverified | No receipt; the preview cells name these as unmeasured. |
51
+ | Remote pair (#107): one declared pi-durable pair, Linux server ↔ Linux notebook over operator SSH (feature branch, not a release) | one LIVE exchange recorded (L4, one pair, one run) | 2026-10-20, both hosts at branch commit `e632294`; that commit's exact-SHA CI run 39202248407 was green on all four jobs. B's native `entwurf_v2` → one SSH → A `remote-pair-receive` → A mailbox enqueue `2026-10-19T22-19-47-662Z-13c897.msg`; A's one reply → B inbox read at `2026-10-19T22:20:24.837Z`, with the matching nonce `107-live-20261020T071937-B-A-6e42d1`. Before the send, the selected compiled bridge hash, the post-build bridge start and the record/owner/sender joins were checked on both hosts; the user reopened both visible sessions. Receipt: [#107](https://github.com/junghan0611/entwurf/issues/107#issuecomment-6278665895). |
52
+ | Remote pair: OFF target, negative paths over real SSH, other rails, more than one pair, non-Linux hosts, GUI rendering of the received envelope | unverified | No receipt. The deterministic and installed cells (VERIFY) do not stand for these. |
51
53
  | native Windows: other harnesses, `setup` and the `.sh` bins, fresh/resume, mux/Herdr/ACP, ARM64, records over 16 MiB, reparse-backed store folders | UNSUPPORTED | Outside the preview's scope by decision; the preview loader refuses an install without the Windows addon by name. |
52
54
 
53
55
  Notes the table cannot carry without becoming prose again:
@@ -69,8 +71,8 @@ Notes the table cannot carry without becoming prose again:
69
71
  - **macOS evidence is split, not a support claim.** The Entwurf-only install surface is
70
72
  CERTIFIED (CI) (row above); the harness rails are NOT CERTIFIED — pending
71
73
  physical host (rental remains unscheduled). The package-level `os` field stays
72
- unrestricted, and native Windows is an experimental preview (0.39.0 candidate, not
73
- yet released; release and npm publication pending) with its own named gaps (rows above), not a supported platform.
74
+ unrestricted, and native Windows is an experimental preview (core 0.39.0) with its own
75
+ named gaps (rows above), not a supported platform.
74
76
 
75
77
  **Operator acceptance rule:** on a claimed Claude host, reinstall from the released
76
78
  artifact, restart every old Claude process, open a new session, and run the doctor
package/CHANGELOG.md CHANGED
@@ -4,6 +4,242 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.40.0 - 2026-10-20
8
+
9
+ The bridge is crossed and the questions are carried forward. A person laid a bridge
10
+ between two machines, and a message sent across it to an existing sibling came back. That
11
+ crossing happened once, at pre-release branch commit `e632294`. This cut carries that
12
+ capability and its open questions, not a new two-host LIVE measurement at its own SHA. It
13
+ does not solve remote trust or remote visibility; the questions it leaves open are listed
14
+ below as questions, not as features.
15
+
16
+ ### Remote pair (#107)
17
+
18
+ - **One operator-declared pair across two hosts.** An opt-in `entwurf-remote-pair.json`
19
+ beside each host's meta-record store has exactly four keys: `remoteGardenId`, `ssh` (an
20
+ existing alias), `localGardenId` and `remotePairFile` (the other host's own file). One
21
+ file serves both directions. An absent file is off. A symlink, non-regular, unreadable
22
+ or invalid file is refused. The file is read at every send and receive and never cached.
23
+ The operator owns the connection between the hosts (a private network such as Tailscale
24
+ is recommended, not required) and SSH authentication. Entwurf installs, configures,
25
+ discovers and retries none of it.
26
+ - **Send.** `entwurf_v2` to the declared `remoteGardenId` makes exactly one `ssh -o
27
+ BatchMode=yes -o ConnectTimeout=10 <alias> entwurf remote-pair-receive --pair <file>`
28
+ call, with no shell and the claim on stdin. A declared target that also has a local
29
+ trace is refused as `remote-route-local-conflict`. A sender other than `localGardenId`
30
+ is refused as `remote-sender-not-declared`. Both refusals make zero SSH calls. An
31
+ undeclared target takes the unchanged local path. The send ends in one of three ways.
32
+ The first is the other host's receipt, quoted verbatim: an acceptance, or an error it
33
+ reported while processing (`remote-reported-error`). The second is its named refusal
34
+ before any mutation. The third is `remote-indeterminate`. A result is decided only when
35
+ the exit code and JSON kind agree. There is no retry.
36
+ - **Receive.** `entwurf remote-pair-receive` accepts only its own canonical file path
37
+ (normalized, not realpath). It refuses a `from` or `target` outside the declared pair
38
+ before any mailbox mutation and hands a match to that host's existing local `entwurf_v2`
39
+ dispatch. The compiled twin ships in the tarball
40
+ (`dist/scripts/remote-pair-receive.js`). The received envelope names the remote origin,
41
+ marks the sender's cwd as a sender-host path and carries `replyable=false`, with "reply
42
+ route declared, not probed". This display is proven for the self-fetch mailbox body
43
+ only. `entwurf_callback` stays local.
44
+ - **Trust and visibility, as built.** The trust model is same-operator SSH account trust.
45
+ The receiving host checks the declared pair. It does not cryptographically authenticate
46
+ a citizen, and it says nothing about whether the content deserves trust. The route has
47
+ no launch, fresh or resume path. For the measured self-fetch pair an OFF citizen is
48
+ undeliverable, and every other target keeps its own rail's admission. The code does not
49
+ check that the target's screen is open; owner pids, TTYs and garden-id labels are not
50
+ visibility. Delivery to an active, armed pi-durable can start a turn at once.
51
+ - **A reported error is not read as non-delivery.** When the other host's dispatch reports
52
+ an error (exit 4), the sender's first line says that an error was reported, that
53
+ delivery is not inferred from that line and that nothing was retried, and it quotes the
54
+ other host's text verbatim. That text includes its own `retry-safe: false` after a
55
+ possible write. An external source review on #107 raised this before the cut.
56
+ - **Docs.** DELIVERY ("Remote pair (#107)") owns the contract. `docs/setup-clean-host.md`
57
+ gains the operator steps (§2c) and "Applying new source to a running host": a pi-durable
58
+ host runs the compiled bridge even from a checkout, and a running bridge keeps the
59
+ modules it loaded, so neither a pull nor a green setup line shows which bytes are live.
60
+ `docs/remote-sibling-principles.md` is new; it holds the boundaries kept across hosts
61
+ and the questions below.
62
+
63
+ ### Questions carried forward
64
+
65
+ - **Is a connection the same as trust?** Today's boundary is an operator-owned SSH account
66
+ and one exactly declared pair. How should the reach of an account be told apart from
67
+ trusting the sibling that sent a message?
68
+ - **Does visibility survive a machine boundary?** Locally, the operator's visible-first
69
+ policy keeps conversation turns to siblings the user has open; that is a policy, not a
70
+ technical screen check. Across hosts, the first exchange relied on the user's own
71
+ confirmation that both sessions were open. What would observe and keep visible-first
72
+ remotely, without a hidden turn or a watcher?
73
+ - **How much can a sender know?** Acceptance is not a read, and a reply does not tell its
74
+ sender that the reply was read. How should that uncertainty be shown honestly without
75
+ retries, a third ACK, read tracking or a conversation manager?
76
+
77
+ None of these is a hidden backlog item;
78
+ [remote-sibling-principles](./docs/remote-sibling-principles.md) keeps them as open
79
+ questions. Automatic discovery, reconnection, OFF wake, hidden model turns, remote
80
+ callbacks and support for every backend or platform are not claimed.
81
+
82
+ ### Evidence boundary
83
+
84
+ - **Deterministic and installed.** T1 executor-in and T2 surface-in cells sit beside the
85
+ behavior: the real bridge over MCP stdio, with a stand-in `ssh` running the other host's
86
+ real entry under a separate HOME. `./run.sh check-remote-pair-installed --tgz <abs>`
87
+ (checkout-only) runs T2 against installed bytes and audits module realpaths, with a
88
+ checkout negative control. Six new mutant lanes (remote-pair display 3, receive 8,
89
+ roundtrip 5, send 12, transport 4, installed-audit 2): the committed set is now 1034
90
+ mutants across 79 lanes. `check:vitest` runs `check-remote-pair-installed-audit`.
91
+ - **One real exchange, at `e632294`, not at this release's SHA.** On 2026-10-20 B's native
92
+ `entwurf_v2` went over one SSH call to A's mailbox
93
+ (`2026-10-19T22-19-47-662Z-13c897.msg`). A's one reply carried nonce
94
+ `107-live-20261020T071937-B-A-6e42d1`, and B read it at `2026-10-19T22:20:24.837Z`. The
95
+ pair was two already-open pi-durable siblings on two Linux hosts, which the user
96
+ reopened visibly beforehand. Before the send, the selected compiled bridge hash, the
97
+ bridge start time after the build and the record/owner/sender joins were checked on both
98
+ hosts. That check first caught one host still on a pre-feature build, so the trial was
99
+ held until a manual build and visible resume. This is L4 (one pair, one run). It is not
100
+ a release-gate MUST and was not re-measured at this release's SHA. Unmeasured: an OFF
101
+ target, negative paths over real SSH, other rails, more than one pair, non-Linux hosts,
102
+ GUI rendering, and SSH's own known-hosts side effects. Receipt:
103
+ [#107](https://github.com/junghan0611/entwurf/issues/107#issuecomment-6278665895).
104
+ - Kill-power re-measured at this shipped SHA: none (all 1034 committed claims not re-proved,
105
+ #144).
106
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head 6ac477d944c6 surface
107
+ 974e264f5879 -- `["bash","run.sh","check-tests-beside-behavior"]` 38/38 receipt
108
+ c8d59ba79b6a
109
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head 3afbfb660562 surface
110
+ bc925701184b -- `["bash","run.sh","check-tests-beside-behavior"]` 37/37 receipt
111
+ 4e324810643d
112
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head 3afbfb660562 surface
113
+ bc925701184b -- `["bash","run.sh","check-remote-pair-installed-audit"]` 2/2 receipt
114
+ bfa5031abc54
115
+ - **An earlier RED is kept as history.** The `check-tests-beside-behavior` group at the R2
116
+ candidate (head a7aaac7390d8 surface 07625e040df9, receipt b5d681a291f5) killed 34 of
117
+ 37: three mutants failed type-checking rather than the claim. Their replacements were
118
+ repaired and the group re-ran on the next candidate. No kill-power was carried over from
119
+ that run.
120
+ - **Static/LIVE split.** Local `check:full` is not run on this operator host and is not
121
+ counted as passed. The whole static floor is owed to this prepared SHA's exact push CI.
122
+ The earlier branch commit `e632294` passed all four jobs in run 39202248407; that is not
123
+ this SHA. The remote-pair installed gate is owed against this release's own candidate
124
+ tarball. The installed 11/11 on an earlier manually produced tarball is not this
125
+ release's evidence.
126
+ - **Local LIVE r1, this release's non-static LIVE: MUST (non-static 23) PASS=23 FAIL=0
127
+ SKIP=0 UNRUN=0; BEHAVIOR PASS=1 FAIL=0.** It ran once, 09:40:03–09:44:49 KST on
128
+ 2026-10-20, on head `de0bf03` plus this preparation's four metadata files (diff
129
+ `22d0a8b856ae`), with the candidate fingerprint (`22053b62bc0e`) equal at freeze, before
130
+ and after. The one-off driver (`15d7fb9dd49f`) ran the release gate's non-static steps
131
+ in its own order, command, environment and working directory; its 24 step lines equal
132
+ `release_gate()`'s by kind, name and argv, and that body is byte-identical to 0.39.0's.
133
+ GLG approved this shape for this cut: test-owned scratch sessions only, including
134
+ headless model turns and the gates' own close/resume, with no existing user or OFF
135
+ session as a target
136
+ ([#107](https://github.com/junghan0611/entwurf/issues/107#issuecomment-6280460022)). The
137
+ Codex step ran against a test-owned app-server in its own tmux session A (named pane pid
138
+ with its recorded start ticks, stopped by one TERM afterwards) from gate session S, with
139
+ the requested tiers `gpt-6-luna` and `openai-codex/gpt-6-luna`; that is the request, as
140
+ the step's own assertions check it, not a billing or model-identity proof. The compiled
141
+ bridge those steps spawned was rebuilt once beforehand, with GLG's approval, and matched
142
+ a private emit of this source 87/87. Receipts on the operator host:
143
+ `.tmp-verify/release-0.40.0/live-r1/` (`live.log` `4ced6b51ec03`, `steps.SHA256SUMS`
144
+ `f1b7e7b79d97`, 24 logs). It is not a local `release-gate --cut` PASS: the static step
145
+ did not run locally.
146
+ - **Still owed before the tag.** The static floor is this prepared SHA's exact push CI
147
+ (four required jobs). This release is carrier-bearing, so its candidate is the
148
+ native-Windows preview workflow's pack at the prepared SHA (release skill M3w), accepted
149
+ by the eight Windows cells and the Linux container, never a local `npm pack`; the
150
+ remote-pair installed gate runs against that same candidate. VERIFY §3.4 (the strict
151
+ `doctor-meta-bridge` on an installed 0.40.0 Claude host in a new post-install session)
152
+ follows the accepted candidate. Tag, GitHub release and npm publication each need their
153
+ own GO.
154
+ - **agy excluded from this cut.** VERIFY §3.7 (three agy doctors and a same-gid
155
+ native-push reply in a new operator conversation) is excluded by GLG's explicit decision
156
+ of 2026-10-20: unmeasured, not a PASS. It is a new decision for this cut only, not the
157
+ 0.39.0 exclusion carried over and not a standing waiver
158
+ ([#107](https://github.com/junghan0611/entwurf/issues/107#issuecomment-6280460022)).
159
+ - **Host residue after LIVE (P9).** The census counted 1934 directories owned by this
160
+ operator under `/tmp` after one stale, unused root of this repo's own prefix (16K) was
161
+ reclaimed, from 1935. The largest groups (`acp-cache-test`, `render-take-test`,
162
+ `caption-take-test`) do not match the prefixes this repo's gates mint, so their
163
+ ownership is not certified by that extractor; they were not deleted. One reparented
164
+ holder of a `/tmp` path is an unrelated editor daemon, untouched. Observation: the
165
+ memory-containment and chain gates left fresh roots of their own prefixes
166
+ (`entwurf-memguard-overlay-*`, `entwurf-chain-*`); that is a finding about those gates,
167
+ not fixed in this cut.
168
+ - **Herdr follows core.** `plugins/herdr/runtime-lock.json` returns from the published npm
169
+ 0.39.0 pin to the closed `herdr-checkout` source for this unpublished candidate, as
170
+ 0.38.1 and 0.39.0 did; the comment is re-dated only. The runtime-bootstrap gate requires
171
+ an npm-source lock to equal the checkout's package version, so an npm 0.39.0 pin cannot
172
+ sit beside package 0.40.0. There are no plugin manifest, registry pin or snapshot
173
+ changes; re-pinning the measured 0.40.0 registry bytes is a separate Herdr snapshot
174
+ after publication.
175
+
176
+ ## v2026.10.19-herdr.0.39.0 — Herdr Entwurf plugin 0.39.0
177
+
178
+ A separate Herdr plugin snapshot, not another core release: core package 0.39.0 and its
179
+ `v0.39.0` tag remain unchanged. The operator has published the core to npm.
180
+
181
+ **Reading guide (2026-10-19).** The `## 0.39.0` section below and the appendix of the
182
+ [v0.39.0 GitHub release](https://github.com/junghan0611/entwurf/releases/tag/v0.39.0) are
183
+ prepare- and cut-time text. The appendix records the tag, VERIFY §3.4 met, and §3.7 (agy)
184
+ excluded for that cut only by the operator's decision, not a PASS. Both still say npm was not
185
+ published. The publish is the later registry fact recorded in this section (`latest` 0.39.0,
186
+ published 2026-10-18T21:24:39Z, registry bytes equal to the accepted candidate). Neither the
187
+ published tarball's CHANGELOG nor the release page is amended by this snapshot.
188
+
189
+ ### Herdr plugin
190
+
191
+ - **Re-pin the published core 0.39.0 and close the checkout window.**
192
+ `plugins/herdr/runtime-lock.json` returns from the `herdr-checkout` carrier that core 0.39.0
193
+ rode to production npm source `@junghanacs/entwurf@0.39.0` with the registry's measured
194
+ sha512 (`sha512-xTRMQkSi…JY+1A==`). The plugin manifest moves 0.38.0 → 0.39.0; admission
195
+ remains Herdr >= 0.9.0 and the one-build/one-pane/Linux shape is unchanged. Core 0.38.1 got
196
+ no Herdr snapshot, so the previous npm pin is 0.38.0.
197
+ - **Pi range unchanged.** The acquired runtime still accepts Pi `>=1.3.0 <1.4`.
198
+ - **Upgrade by acquisition source, not just version.** A settled npm-source 0.38.0 installation
199
+ can rebind under the plugin README's ownership conditions. A host that installed an
200
+ intervening `herdr-checkout` candidate must explicitly deactivate by its own absolute runtime
201
+ path before reinstalling the npm-source snapshot. No operator installation or active session
202
+ is rewired by this release work.
203
+
204
+ ### Source-specific verification
205
+
206
+ - Registry metadata reports `latest` 0.39.0 and `repair` 0.12.8-repair.1, 605 files, published
207
+ 2026-10-18T21:24:39Z. The downloaded registry tarball is byte-equal to the preserved accepted
208
+ core candidate (SHA256 `1d5693241b8cd5fa827869cf15ecbf0048215c7f0b4ebb0422650b6ae2773578`,
209
+ 11254616 bytes); its independently computed sha512 and sha1 equal `dist.integrity` and
210
+ `dist.shasum`, so core make's Docker consumer of those bytes (`node:24-bookworm`, repoDigest
211
+ `node@sha256:6dac556d…`) is reused here, not repeated. A sandboxed `pi install` of the
212
+ registry package resolved its bridge inside the sandbox, recorded the registry URL with that
213
+ integrity, and listed the curated Claude anchors with `--list-models entwurf`; a sandboxed
214
+ global npm install of the same package passed its installed `check-bridge`.
215
+ - **The npm runtime swap against the real registry**, by the registry-shipped
216
+ `scripts/herdr-runtime.mjs` (byte-equal to this checkout's) in sandboxed user roots, 22/22
217
+ checks. The 0.38.0 runtime was seeded from its own committed lock (`beb5925`); the swap to
218
+ 0.39.0 took one acquisition, the cached tarball's sha512 equals the lock and its sha256 equals
219
+ both the ready journal's observed digest and the accepted candidate. The `installing` journal
220
+ carried the 0.38.0 identity as `previousRuntime` and the `runtime-ready` journal carries
221
+ `null`; when acquisition began the 0.38.0 tree was still at the active address. A manually
222
+ seeded torn swap (the active tree moved to the backup address with a half candidate in
223
+ staging; not a killed process) was restored before acquisition and reported `recovered:
224
+ torn-swap-with-candidate`. An exact re-request acquired nothing and left journal and tree
225
+ byte-identical, and the owned removal reclaimed cache, runtime and journal. The helper is a
226
+ leaf that probes no Pi, so these cells certify neither a Pi upgrade nor Herdr's
227
+ install/update relocation, harness wiring, the installed CLI's deactivate path, a model turn,
228
+ or recovery from a real kill.
229
+ - **Focused gates on the branch**, with HEAD, index and work surface unchanged across them:
230
+ `check-herdr-supply` (11 assertions), `check-herdr-plugin` (49),
231
+ `check-herdr-plugin-profile` (14), `check-herdr-runtime-bootstrap` (31),
232
+ `check-herdr-activation` (33), `check-herdr-placement` (25), `check-herdr-fresh-call` (45)
233
+ and `check-gate-manifests` (1000 committed mutants across 73 lanes validated, none executed).
234
+ `check-herdr-plugin-build` and `check-herdr-sandbox` were not run locally because they run
235
+ the operator's own herdr; they ride the pushed SHA's FULL floor in CI.
236
+ - Pending, and not claimed here: the pushed SHA's four-job CI and the public-ref
237
+ install/use/deactivate journey of this npm pin, on Herdr 0.9.5 (the binary
238
+ `scripts/fixtures/herdr-supply.json` names) with Pi 1.3.0, from the public `main` ref rather
239
+ than the tag. Earlier npm or checkout-carrier receipts cannot certify these source-specific
240
+ axes. Those outcomes are recorded in the snapshot's post-push acceptance ledger, not in this
241
+ frozen text.
242
+
7
243
  ## 0.39.0 - 2026-10-18
8
244
 
9
245
  ### Native Windows (experimental preview, #78)
package/DELIVERY.md CHANGED
@@ -571,6 +571,40 @@ same class of capability through the CLI-owned stdio lifecycle. Absence of Claud
571
571
  `FileChanged` / `asyncRewake` / `watchPaths` therefore means only that Claude's hook
572
572
  mechanism cannot be copied — it never proved Copilot had no vendor wake surface.
573
573
 
574
+ ### Remote pair (#107): one declared pair across two hosts
575
+
576
+ 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.
577
+
578
+ - **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:
579
+ - `remoteGardenId`.
580
+ - `ssh`: an existing SSH alias whose characters need no quoting.
581
+ - `localGardenId`.
582
+ - `remotePairFile`: the other host's canonical absolute path to its own file.
583
+
584
+ 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.
585
+ - **Send.** The sending host's `entwurf_v2` reads the file and then decides:
586
+ - A target other than `remoteGardenId` takes the unchanged local path.
587
+ - 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.
588
+ - A sender other than `localGardenId` is refused as `remote-sender-not-declared`, with zero SSH.
589
+ - 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.
590
+ - **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.
591
+ - **Outcomes.** The send ends in one of three outcomes:
592
+ - 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;
593
+ - the other host's named refusal, made before any mutation: a pair mismatch (exit 3) or invalid input (exit 2);
594
+ - `remote-indeterminate`.
595
+
596
+ 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.
597
+ - **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.
598
+ - **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.
599
+ - **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.
600
+ - **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.
601
+
602
+ Evidence:
603
+
604
+ - 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.
605
+ - 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.
606
+ - Unmeasured: an OFF target, negative paths over real SSH, other rails, more than one pair, and non-Linux hosts.
607
+
574
608
  ## Recording a new claim
575
609
 
576
610
  For every matrix change, record:
package/README.md CHANGED
@@ -81,11 +81,10 @@ Detected harness wiring on Darwin can therefore be written while setup stays
81
81
  non-green. WSL2: a CI runner (experimental) measured the Entwurf-only install and a
82
82
  zero-token Pi citizen; general, physical-machine or harness-parity WSL2 support is **not
83
83
  declared**, and delivery, other harnesses and physical machines are unmeasured. Native
84
- Windows has an **experimental first preview**, a CI-built x64 addon on `main` for the
85
- 0.39.0 candidate (not yet released; release and npm publication pending); the registry's
86
- 0.38.1 has no Windows addon. On a GitHub Actions `windows-2025` x64 runner with Node
87
- 24.18.1 and Pi 1.3.0, a CI-built artifact was installed, wired with `pi install`, birthed
88
- two Pi citizens and
84
+ Windows has an **experimental first preview**, a CI-built x64 addon that ships in core
85
+ 0.39.0 (published to npm); 0.38.1 and earlier carry no Windows addon. On a GitHub Actions
86
+ `windows-2025` x64 runner with Node 24.18.1 and Pi 1.3.0, a CI-built artifact was installed,
87
+ wired with `pi install`, birthed two Pi citizens and
89
88
  delivered one garden-id `entwurf_v2` message between them, with an offline scripted model
90
89
  and no vendor turn. Every other native-Windows surface (other harnesses, `setup` and the
91
90
  `.sh` bins, fresh/resume, mux/Herdr/ACP, physical machines) is **not declared**; the
@@ -198,9 +197,9 @@ pi / pi-durable / Claude Code / Copilot / OMP / Antigravity / Codex
198
197
  | 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 |
199
198
  | Reopen a dormant pi citizen under its own id | `entwurf_resume_call {target}` | Separate LAUNCH and socket OBSERVATION receipts; no turn is run |
200
199
 
201
- **Delivery starts no process.** A live pi receives over its record-addressed Unix
202
- control socket; an armed self-fetch citizen (Claude Code, Copilot, OMP, pi-durable)
203
- through its mailbox; a probe-alive native-push citizen (Antigravity, Codex) through
200
+ **Delivery starts no process.** A live pi receives over its record-addressed control
201
+ address (a Unix socket; on the experimental native-Windows Pi preview, a named pipe);
202
+ an armed self-fetch citizen (Claude Code, Copilot, OMP, pi-durable) through its mailbox; a probe-alive native-push citizen (Antigravity, Codex) through
204
203
  its native conversation/thread. Dormant pi, undeliverable mailboxes, dead/indeterminate native
205
204
  push and record-less sockets reject honestly. Only control-socket dispatch takes
206
205
  the per-target lock; the other rails use their own deliverability/probe evidence.
@@ -213,6 +212,8 @@ receipt. `wants_reply` is etiquette, not waiting, polling or ownership; replyabi
213
212
  is a live rail fact, not merely a consequence of trusted identity.
214
213
  [DELIVERY](./DELIVERY.md#control-socket-acceptance-boundaries-120) owns these distinctions.
215
214
 
215
+ **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.
216
+
216
217
  **Mux launches; it never delivers.** Inside Herdr, fresh opens a new unfocused tab
217
218
  in the caller's workspace, pi/Claude Code only, with no tmux fallback. Outside,
218
219
  it opens pi/pi-durable/Claude Code/Copilot/OMP/Codex visibly on the caller's own tmux
@@ -238,7 +239,8 @@ and [Herdr launch](https://github.com/junghan0611/entwurf/blob/main/docs/herdr-l
238
239
 
239
240
  The pi adapter registers the compiled bridge with Pi's built-in MCP after record
240
241
  birth; Pi supervises the MCP child. **MCP is ingress; pi receive remains the
241
- record-addressed Unix control socket.** Garden identity and dispatch stay Entwurf's.
242
+ record-addressed control address (a Unix socket, or the native-Windows preview's named
243
+ pipe).** Garden identity and dispatch stay Entwurf's.
242
244
  Codemode is harness-internal, not another garden citizen.
243
245
 
244
246
  ## Operation guides
package/VERIFY.md CHANGED
@@ -209,6 +209,16 @@ that moved out of `scripts/` and into a file beside its subject would otherwise
209
209
 
210
210
  Do not collapse source, package, fixture, and native-host evidence into one “green.”
211
211
 
212
+ **Remote pair (#107).** Each layer of evidence lives in its own place:
213
+
214
+ - **Deterministic.** The cells sit beside the behavior in `pi-extensions/lib/remote-pair-*.test.ts` and are found by `check-tests-beside-behavior`. T1 is executor-in. T2 is surface-in: the real bridge over MCP stdio, with a stand-in `ssh` on PATH that runs the other host's real `run.sh` under a separate HOME. Both are offline.
215
+ - **Installed bytes.** `./run.sh check-remote-pair-installed --tgz <abs>` is checkout-only and takes a caller-supplied candidate tarball. It installs a sandboxed copy, checks the compiled surface, and runs the T2 cells against the installed subject. It also audits that every module those processes load resolves inside the consumer prefix, with a checkout negative control. Its audit primitive is mutation-qualified as `check-remote-pair-installed-audit` inside `check:vitest`.
216
+ - **Real two-host exchange (T3).** This is opt-in operator LIVE evidence, recorded in [BASELINE](./BASELINE.md) and #107. It is not a release-gate MUST and not a fixture that needs a second machine online. A T1, T2 or installed green does not stand for it, and its record belongs to the commit it ran on; a later SHA does not inherit it. Re-run T3 when any of these change:
217
+ - the SSH argv or quoting;
218
+ - the receive entry's layout, stdout or exit codes;
219
+ - the declaration schema or root resolution;
220
+ - either host's Node or Pi.
221
+
212
222
  **Native durable contact (#129, #130 carrier).** Three deterministic beside-test/mutant
213
223
  coordinates in `check:contracts` split the durable contact by the file that carries each claim's QK
214
224
  (#144): a carrier or native-module mutant runs its one file, the rest run four. `check-pi-durable-beside` holds the adapter, the installed
@@ -428,7 +438,7 @@ mailbox with `origin=pi-session`, `replyable=true`).
428
438
 
429
439
  ### 1.4 Cross-install / cross-backend parity (optional, high-value)
430
440
 
431
- Compare a fresh self-awareness report across axes: (1) same backend, different install path — answer must be path-invariant; (2) same backend, different machine — identical native tool list + MCP server/tool set; (3) different backend, same bridge — same garden capability but **different** native tool surface (a Claude session reporting another backend's native tools is a fail); (4) native pi routing vs ACP-bridged, same model — the native pi citizen reaches the compiled `entwurf-bridge` through Pi's built-in MCP (`mcp__entwurf_bridge__<verb>`, six active tools and two hidden), while the ACP backend reaches that bridge through the explicit `entwurfProvider.mcpServers` projection in its own callable dialect (see §2.1). Compare the actual ingress provenance and backend-native tools, not an assumed absence of MCP on pi; receive to the pi citizen remains its record-addressed Unix control socket. Honest "I cannot tell" hedging is preferable to inventing provenance. This is the current #125 comparison criterion, not a newly measured parity receipt. agy is graded by its native-citizen checklist, not by pretending it has an ACP overlay.
441
+ Compare a fresh self-awareness report across axes: (1) same backend, different install path — answer must be path-invariant; (2) same backend, different machine — identical native tool list + MCP server/tool set; (3) different backend, same bridge — same garden capability but **different** native tool surface (a Claude session reporting another backend's native tools is a fail); (4) native pi routing vs ACP-bridged, same model — the native pi citizen reaches the compiled `entwurf-bridge` through Pi's built-in MCP (`mcp__entwurf_bridge__<verb>`, six active tools and two hidden), while the ACP backend reaches that bridge through the explicit `entwurfProvider.mcpServers` projection in its own callable dialect (see §2.1). Compare the actual ingress provenance and backend-native tools, not an assumed absence of MCP on pi; receive to the pi citizen remains its record-addressed control address (a Unix socket; a named pipe on the experimental native-Windows Pi preview). Honest "I cannot tell" hedging is preferable to inventing provenance. This is the current #125 comparison criterion, not a newly measured parity receipt. agy is graded by its native-citizen checklist, not by pretending it has an ACP overlay.
432
442
 
433
443
  **Cortex on axis 3.** A cortex session must expose the same garden capability
434
444
  while keeping cortex's own native tool surface. Its callable MCP identifier shape is
@@ -19,8 +19,7 @@ evidence split, and its three states must not collapse into "macOS is supported"
19
19
  unmeasured Darwin doctor reason is per-process environment DISCOVERY
20
20
  (start-key is portable).
21
21
  - **UNSUPPORTED** — native Windows `setup` and its harness rails. macOS is never this.
22
- An experimental native-Windows Pi preview (0.39.0 candidate, not yet released; release
23
- and npm publication pending) exists outside `setup`; see [BASELINE](../BASELINE.md#release-host-baseline).
22
+ An experimental native-Windows Pi preview (core 0.39.0) exists outside `setup`; see [BASELINE](../BASELINE.md#release-host-baseline).
24
23
 
25
24
  ## Requirements
26
25
 
@@ -244,7 +243,8 @@ message. These are harness tools, not shell subcommands; pi exposes them through
244
243
  Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The bridge has eight tools;
245
244
  pi hides `entwurf_inbox_read` and `entwurf_register_native`, leaving six active.
246
245
  Tool-call ingress is MCP; delivery to a pi citizen is still its record-addressed
247
- Unix control socket. A boot/listing from `check-bridge` is not a model-turn or
246
+ control address (a Unix socket; a named pipe on the experimental native-Windows Pi
247
+ preview). A boot/listing from `check-bridge` is not a model-turn or
248
248
  native-rail acceptance.
249
249
 
250
250
  To inspect the ACP provider from a project-local package instead of a global one:
@@ -449,6 +449,34 @@ The owned source is formatted at upstream width (`biome.json` override, indentWi
449
449
  per-file exception. A formatting change to an owned file changes its emitted bytes. So it is
450
450
  re-emitted with `--write`, and its generated digest is reviewed like any other carrier change.
451
451
 
452
+ ## 2c. Optional remote pair between two hosts (#107)
453
+
454
+ This lets one declared pair of existing citizens on two hosts reach each other over SSH that you already run. [DELIVERY](../DELIVERY.md#remote-pair-107-one-declared-pair-across-two-hosts) owns the contract. The machine boundary is set by a person, and nothing here is automatic.
455
+
456
+ 1. **Connect the hosts yourself.** Use a private network (Tailscale or similar is recommended) and SSH keys and authorization you already trust. From each host, `ssh -o BatchMode=yes <alias> true` must succeed non-interactively, and the other host's non-interactive `command -v entwurf` must resolve to the installation you intend. Entwurf edits no SSH config, key, known-hosts file or network setting. Whatever SSH itself writes under your own policy, such as host-key updates, stays yours.
457
+ 2. **Run the accepted bytes on both hosts, in the processes that matter.** See [applying new source to a running host](#applying-new-source-to-a-running-host). A pulled checkout or a green setup line does not, by itself, show what a running bridge loaded.
458
+ 3. **Write one file on each host,** at `remotePairOptInPath()`. That is the pi agent dir unless `ENTWURF_META_SESSIONS_DIR` moves the meta-record store. Check that the sending bridge's environment and the SSH receive environment resolve the same path. Use mode `0600` and never overwrite a file you did not inspect:
459
+
460
+ ```json
461
+ {
462
+ "remoteGardenId": "<the other host's garden id>",
463
+ "ssh": "<your existing alias for the other host>",
464
+ "localGardenId": "<this host's garden id that talks to it>",
465
+ "remotePairFile": "<absolute path of the other host's own file>"
466
+ }
467
+ ```
468
+
469
+ The two files mirror each other: one host's `remoteGardenId` is the other's `localGardenId`. The other host's `remotePairFile` must spell this path as it is normalized, not through a symlinked alias.
470
+
471
+ To check a file, read it with the repository's internal parser from a source checkout. Use the same agent/store-root settings the bridge runs with. This is a read-only dev-clone check: there is no installed operator validation verb, and it performs no send and no model input.
472
+
473
+ ```bash
474
+ node --experimental-strip-types -e 'import("./pi-extensions/lib/remote-pair-send.ts").then(m=>console.log(m.readRemotePairOptIn(m.remotePairOptInPath())))'
475
+ ```
476
+
477
+ Do not validate the configuration by sending a test message.
478
+ 4. **Use it only between siblings you have open.** The measured self-fetch pair must be active and armed, and the target rail's own admission still applies. Delivery never starts, resumes or wakes an OFF citizen. To an active, armed one it rings the normal doorbell, which may start a turn at once in the receiving TUI. Remove the file to switch the pair off; there is nothing to stop or reconnect.
479
+
452
480
  ## 3. Optional Claude Code native citizen
453
481
 
454
482
  This rail is CERTIFIED on Linux; on macOS it is NOT CERTIFIED — pending physical
@@ -1128,6 +1156,17 @@ run `pnpm install` when the lockfile changed and `pnpm run build-bridge` before
1128
1156
  artifact-consuming checks such as `./run.sh check-bridge-delivery`; ignored dist
1129
1157
  may be missing or stale. Installed mode never bootstraps pnpm in `node_modules`.
1130
1158
 
1159
+ ### Applying new source to a running host
1160
+
1161
+ New source on disk is not a fresh runtime. A pi-durable host spawns the compiled bridge `mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js` even from a checkout (the bootstrap's `PACKAGED_BRIDGE_ENTRY`). Ordinary `start.sh` in a checkout runs the TypeScript source instead. An already-running bridge keeps the modules it loaded. So, for a host that has to run new bridge code:
1162
+
1163
+ 1. Build once from the checkout (`pnpm run build-bridge`, or its body `bash scripts/with-dist-lock.sh bash scripts/build-bridge.sh` when no package lifecycle should run).
1164
+ 2. Compare the whole compiled output with the accepted candidate's, as a file-set and hash manifest. A hash of the selected entry alone is not enough: the entry can stay identical while a helper it loads differs. Check the pinned SDK set separately, and that the new process started after the build.
1165
+ 3. Close the visible session and reopen it with its own native same-id resume.
1166
+ 4. Confirm `entwurf_self` names the same garden id, and that the receiver/owner join points at the new process, whose bridge child started after the build.
1167
+
1168
+ A per-component setup PASS or a `git pull` does not show which bytes a running bridge loaded. On 2026-10-20 one host of the #107 pair still selected a pre-feature dist after a pull and a green setup. The cause was not traced. A hash check caught it before the first message, and a manual build and visible resume fixed it. Entwurf adds no auto-build, reload or watcher for this.
1169
+
1131
1170
  ## 8. Release acceptance versus host acceptance
1132
1171
 
1133
1172
  - `entwurf check-bridge`: installed MCP bytes boot; no backend auth.
@@ -84,6 +84,9 @@ import { probeNativeSenderAlive, reconcileSenderIdentityClaims, resolveCodexRequ
84
84
  import { applyOmpBridgeChildRootPolicy, defaultMetaMailboxDir, defaultMetaSessionsDir, makeStoreRecordReader, readActiveStoreEntries, readMetaIdentityByGardenId, readMetaInbox, readMetaReceiverMarker, readMetaSenderMarker, requireBackend, } from "../../../pi-extensions/lib/meta-session.js";
85
85
  import { RESUME_CALL_REJECT_HINT, resumeCall } from "../../../pi-extensions/lib/mux-resume-call.js";
86
86
  import { registerNativeConversation } from "../../../pi-extensions/lib/native-push/register.js";
87
+ import { makeLocalTraceFact, readRemotePairOptIn, remotePairOptInPath, runEntwurfV2WithRemoteRoute, } from "../../../pi-extensions/lib/remote-pair-send.js";
88
+ import { processTransport } from "../../../pi-extensions/lib/remote-pair-transport.js";
89
+ import { CONTROL_SOCKET_DIR } from "../../../pi-extensions/lib/socket-discovery.js";
87
90
  import { probeSocketLiveness } from "../../../pi-extensions/lib/socket-probe.js";
88
91
  const HOME = os.homedir();
89
92
  // ============================================================================
@@ -431,11 +434,22 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
431
434
  // Resolved ONCE so the dispatch-moment timestamp is fixed and the control RPC sender
432
435
  // + the mailbox body sender share one envelope. No replyability gate (see above).
433
436
  const sender = await buildSendSenderEnvelope({ requestMeta: extra._meta });
434
- const rendered = await runAndRenderEntwurfV2FromSurface({ target, intent, message, mode, wants_reply },
435
- // No trust-preflight inputs are passed, and none exist to pass: the preflight on this
436
- // path guarded the resume verdict and left with `owned-outcome`. `senderProvider` is
437
- // the whole options surface now.
438
- { senderProvider: () => sender });
437
+ // #107: the operator's remote-pair opt-in is read per send. Absent = off and the local
438
+ // surface below runs exactly as before; a declared target goes over that one route.
439
+ const rendered = await runEntwurfV2WithRemoteRoute({ target, intent, message, mode, wants_reply }, {
440
+ senderProvider: () => sender,
441
+ route: readRemotePairOptIn(remotePairOptInPath()),
442
+ // The decider's canonical socket dir AND this bridge's ENTWURF_DIR: a trace in either is local.
443
+ localTrace: makeLocalTraceFact({
444
+ sessionsDir: defaultMetaSessionsDir(),
445
+ controlSocketDirs: [CONTROL_SOCKET_DIR, ENTWURF_DIR],
446
+ }),
447
+ transport: processTransport,
448
+ // No trust-preflight inputs are passed, and none exist to pass: the preflight on this
449
+ // path guarded the resume verdict and left with `owned-outcome`. `senderProvider` is
450
+ // the whole options surface now.
451
+ runLocal: (params, opts) => runAndRenderEntwurfV2FromSurface(params, opts),
452
+ });
439
453
  return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
440
454
  }
441
455
  catch (err) {
@@ -60,13 +60,19 @@ export function formatMetaMailboxBody(sender, message, wantsReply) {
60
60
  const replyable = sender.replyable === true;
61
61
  const isMeta = sender.origin === "meta-session";
62
62
  const kind = isMeta ? "meta-session, " : "";
63
+ // A remote sender is never replyable here: no reply route was probed. Its cwd is a path on the
64
+ // SENDING host, so it is neither abbreviated against this host's HOME nor shown as a local path.
65
+ const remote = !replyable ? sender.remote : undefined;
63
66
  const sessionLine = replyable
64
67
  ? `${sender.sessionId} (${kind}replyable — reply via entwurf_v2 to this sessionId, intent=fire-and-forget)`
65
- : isMeta
66
- ? `${sender.sessionId} (meta-session, non-replyable)`
67
- : `${sender.sessionId} (external, non-replyable)`;
68
+ : remote
69
+ ? `${sender.sessionId} (remote via ${remote.via}; ${remote.replyRoute === "declared" ? "reply route declared, not probed" : "no reply route declared"})`
70
+ : isMeta
71
+ ? `${sender.sessionId} (meta-session, non-replyable)`
72
+ : `${sender.sessionId} (external, non-replyable)`;
73
+ const where = remote ? `${sender.cwd} (sender-host path)` : abbreviateHome(sender.cwd);
68
74
  return (`[entwurf received ⟵]\n` +
69
- ` from: ${sender.agentId} @ ${abbreviateHome(sender.cwd)}\n` +
75
+ ` from: ${sender.agentId} @ ${where}\n` +
70
76
  ` session: ${sessionLine}\n` +
71
77
  ` at: ${formatKstTimestamp(sender.timestamp)}\n` +
72
78
  ` wants reply: ${wantsReply ? "yes" : "no"}\n` +