@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/VERIFY.md CHANGED
@@ -6,8 +6,8 @@ and the manual judgements a gate cannot make.
6
6
 
7
7
  > **Current surface.** `entwurf-bridge` exposes `entwurf_v2`, `entwurf_peers`,
8
8
  > `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_retire_call` (0.41.0; operator-requested target only), `entwurf_callback`, `entwurf_self`, `entwurf_inbox_read`, and `entwurf_register_native` — nine; a pi citizen offers seven with two hidden, the pi-durable contact eight. The ACP
9
- > backends are Claude and Snowflake Cortex Code. Antigravity is a separate shipped
10
- > native-push citizen lane. Codex is a native-push citizen supported in 0.21.0, whose birth hook is
9
+ > backends are Claude and Snowflake Cortex Code. Antigravity is a separate legacy citizen
10
+ > lane whose native push is paused (#152). Codex is a native-push citizen supported in 0.21.0, whose birth hook is
11
11
  > operator-owned and gated on one vendor trust receipt, with strict request-scoped identity and
12
12
  > visible fresh; it remains outside ACP and has no resume.
13
13
  > This banner names the surface, not an acceptance. The 48-assertion / MUST 24/0/0 / 475/475 numbers
@@ -115,7 +115,7 @@ that moved out of `scripts/` and into a file beside its subject would otherwise
115
115
  >
116
116
  > A cut that touches the prompt-lifecycle contract (no wall clock on a running turn) owes one long-turn acceptance the aggregate floor is too short to carry: `LIVE=1 ./run.sh smoke-acp-long-turn-live` drives a real turn whose tool work outlasts the retired 600s cutoff and requires exactly one cold ACP bootstrap in the transcript. It takes >12 minutes by construction and is on-demand, not part of `release-gate`.
117
117
  >
118
- > **The integrated mux lifecycle IS a release-gate MUST.** `LIVE=1 ./run.sh smoke-mux-lifecycle-live` enters through a real MCP `tools/call` on the shipped bridge launcher and follows one citizen the whole way: fresh-call launch receipt, the sibling's own nonce callback whose SENDER ENVELOPE is the address, an `entwurf_v2` control-socket send that lands in the sibling's own transcript, `entwurf_resume_call` REFUSED while that citizen is live, a stable-handle close proving pane and socket gone with the record preserved, delivery to the dormant citizen refused honestly, then the public `entwurf_resume_call` — whose LAUNCH and OBSERVATION receipts stay apart — and finally an `entwurf_v2` send the resumed session answers with a fact it was told BEFORE the window closed. That last step is the point: a socket answering under the same garden id proves a process stood up at that address, and only the recall proves the same conversation came back. Both pi provider shapes are driven to it (native `openai-codex/gpt-6.1-sol:low` and recorded-ACP `entwurf/claude-sonnet-5-5`, the only shape that re-injects a bridge extension), and the Claude Code cell, whose sibling is still live at that point, has its resume refused as `target-live` with no window opened and no lock residue (the per-gid coordination lock is released). Claude Code's dormant same-id resume (#147 L2) is certified deterministically; its LIVE dormant→resume leg is not in this MUST yet. One test-owned retire→resume round trip outside this gate is recorded as dated n=1 evidence in [BASELINE](./BASELINE.md#147-native-round-trip-2026-10-22-n1); it is not this MUST.
118
+ > **The integrated mux lifecycle IS a release-gate MUST.** `LIVE=1 ./run.sh smoke-mux-lifecycle-live` enters through a real MCP `tools/call` on the shipped bridge launcher and follows one citizen the whole way. The native pi launch names `placement.tmuxSession` (#108): the harness asks tmux itself that the pane sits in a caller-distinct seat session on the same private server, that the caller's windows are unchanged, and that the close later removes only that window and leaves the seat's anchor standing. The steps are: fresh-call launch receipt, the sibling's own nonce callback whose SENDER ENVELOPE is the address, an `entwurf_v2` control-socket send that lands in the sibling's own transcript, `entwurf_resume_call` REFUSED while that citizen is live, a stable-handle close proving pane and socket gone with the record preserved, delivery to the dormant citizen refused honestly, then the public `entwurf_resume_call` — whose LAUNCH and OBSERVATION receipts stay apart — and finally an `entwurf_v2` send the resumed session answers with a fact it was told BEFORE the window closed. That last step is the point: a socket answering under the same garden id proves a process stood up at that address, and only the recall proves the same conversation came back. Both pi provider shapes are driven to it (native `openai-codex/gpt-6.1-sol:low` and recorded-ACP `entwurf/claude-sonnet-5-5`, the only shape that re-injects a bridge extension), and the Claude Code cell, whose sibling is still live at that point, has its resume refused as `target-live` with no window opened and no lock residue (the per-gid coordination lock is released). Claude Code's dormant same-id resume (#147 L2) is certified deterministically; its LIVE dormant→resume leg is not in this MUST yet. One test-owned retire→resume round trip outside this gate is recorded as dated n=1 evidence in [BASELINE](./BASELINE.md#147-native-round-trip-2026-10-22-n1); it is not this MUST.
119
119
  >
120
120
  > One cell cannot be fully fenced, and that is recorded rather than glossed: the recorded-ACP pi cell runs under the operator's REAL home, because its provider spawns `claude` as a child that authenticates from there — measured, a fenced HOME kills that turn with `Authentication required` before the model is ever reached, and `CLAUDE_CONFIG_DIR` alone does not fix it. That is the same trade `smoke-acp-bundled-mcp-live` already makes. While that cell runs, its citizen's control socket and per-gid lock exist under the operator's own `~/.pi/entwurf-control` and `~/.pi/entwurf-v2-locks`; its meta store stays fixture, so no garden record is minted outside the fixture, and the run ends by proving all six real roots hold the entry sets they started with, with no fixture garden id and no leftover lock.
121
121
  >
@@ -125,7 +125,7 @@ that moved out of `scripts/` and into a file beside its subject would otherwise
125
125
 
126
126
  > **A visible-fresh receipt outside the MUST tier is no longer the general rule — it is Copilot's preserved exception.** #87 measured what that wording permitted: because clause 7 sat outside every aggregate, a harness could be admitted as a full D6 citizen that `entwurf_fresh_call` cannot open, carry an honest "not a supported harness" sentence in the delivery matrix, and still pass the whole floor toward a cut. From OMP onward, the FIRST release of a harness admitted under the #82 contract owes its clause 7 receipt as a release-gate MUST step (`smoke-omp-fresh-live`), and the deterministic half of the same rule (`check-harness-admission-parity`, inside `check:full`) makes a citizen backend missing from `FRESH_CALL_BACKENDS` a red floor rather than a documented one. Forward-only by decision: Copilot's row is not reopened. The contract lives in `docs/adding-a-harness.md` step 9, "The release stop"; this note exists so the cost discipline above is not read as permission.
127
127
  >
128
- > The aggregate release gate does not own a live agy conversation id, so agy's real native-push round trip is a separate acceptance axis: three fail-loud doctors plus `LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live`, followed by a fresh-conversation sender/reply check after package install. Its deterministic install/sender gates are already inside `pnpm run check:full`; do not misreport the aggregate gate as live agy evidence. **Cost fence:** the agy conversation this smoke drives runs on a free account, so open it on `gemini-3.6-flash` — never a Pro tier. The model is the operator's choice at conversation-open time; entwurf never selects it, and no assertion reads it (see the shipped-lane note: model display is not part of the agy contract).
128
+ > agy native push is paused (#152). 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. So `entwurf_v2` refuses an agy target before any probe as `native-push-paused` (`observedLiveness: null`), an agy sender's `replyable` is false by name, and `entwurf_register_native` refuses before probing or minting a garden id. Existing agy records stay readable. The pause itself is proven by the deterministic `[QK:AGY-NATIVE-PUSH-PAUSED]` cell, which needs no agy conversation. Separately, an agy host's retained legacy identity surfaces can be checked by three fail-loud doctors plus a fresh-conversation identity check (birth, statusline, `entwurf_self`) after package install. While the pause holds, that check is an optional diagnostic run on request, not a cut requirement; a green one is neither send evidence nor a certification that the installed agy host works or can receive. `LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live` exits 2 with `[entwurf:paused]` before it reads the conversation id, and that is not a PASS. The deterministic paused refusal (`[QK:AGY-NATIVE-PUSH-PAUSED]`) and the install/sender gates are inside `pnpm run check:full`; do not misreport the aggregate gate as live agy evidence. **Cost fence:** the pause proof (criterion 7) needs no agy conversation, model or conversation id, and none is opened for `smoke-agy-native-push-live`. The optional retained-identity diagnostic does need one fresh agy conversation after package install; when it is asked for, open it on `gemini-3.6-flash` (free account) — never a Pro tier; entwurf never selects the model, and no assertion reads it (model display is not part of the agy contract). Lifting the pause would be a separate future change with its own positive acceptance; nothing here certifies one.
129
129
  >
130
130
  > Aggregate release-gate does not own a loaded Codex thread. Codex native-push remains an
131
131
  > on-demand host axis. The first-admission acceptance passed on 2026-09-12 under the fixed `codex`
@@ -211,7 +211,7 @@ Do not collapse source, package, fixture, and native-host evidence into one “g
211
211
 
212
212
  **Remote pair (#107).** Each layer of evidence lives in its own place:
213
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.
214
+ - **Deterministic.** The cells sit beside the behavior in `pi-extensions/lib/remote-pair-*.test.ts` and are found by `check-tests-beside-behavior`. Their mutants run through the narrow execution coordinate `check-remote-pair-beside` (rule 3, #149). 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. It covers two hosts, and since #149 also three hosts whose one file carries several routes. Both are offline.
215
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
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
217
  - the SSH argv or quoting;
@@ -289,7 +289,7 @@ caller/target/cross-harness LIVE are owed by `smoke-pi-durable-fresh-live`, a re
289
289
  | Native-Windows carrier (carrier-bearing release, from 0.39.0) | one `windows-native-preview.yml` dispatch at the exact prepared SHA after its four `ci.yml` jobs are green: accepted build receipt (two byte-identical MSVC builds, exact import set), `pack` printing `present`, eight Windows addon/citizen cells on the installed tarball; that run's `win32-preview-tgz` is the release candidate, retrieved (never packed locally) with its `SHA256SUMS`, the Windows-printed install hash and the downloaded hash equal, then accepted by the Linux `check-install-container` unchanged (release skill M3w) | Experimental, shape-only, non-voting for `ci.yml`: no vendor turn, x64 runner only, named gaps in BASELINE. The four `ci.yml` jobs (incl. macOS) saw an addon-less pack, so the carrier-bearing tarball's macOS install is unmeasured. Artifacts expire after 14 days; the preserved copy is the durable one. |
290
290
  | LIVE runtime | `LIVE=1 ./run.sh release-gate <scratch> --cut` plus any shipped on-demand backend axis | `--cut` enforces `SKIP=0`; a red wired gate blocks the cut. |
291
291
  | Native Claude host | installed strict doctor against a new real session | Missing live join is `NOT CERTIFIED`, not a fixture PASS. |
292
- | Native agy host | three doctors plus conversation-id-gated native-push round trip | Aggregate release-gate does not own an agy conversation id. |
292
+ | Native agy host | the deterministic pause cell plus the agy record, install and sender gates inside `check:full`; native push is paused (#152), so no round trip is claimed. The three doctors and a fresh-conversation identity check are an optional diagnostic, run on request | Aggregate release-gate does not own an agy conversation id. The paused LIVE smoke exits 2 `[entwurf:paused]`, not a PASS. |
293
293
  | Native Codex host | caller-seat topology accepted 2026-09-16 (56 assertions, exit 0); caller-DIRECTORY axis accepted the same day (65 assertions, exit 0) | Operator-owned birth/MCP/statusline/terminal-title atoms and the on-demand four-coordinate chain are measured on Linux at Codex 0.153.4: the app-server alone in session A (`$30/@41/%45`, pid 737636), and initial Pi / omitted-placement fresh Codex / Codex-opened outbound Pi all in a different session S (`$2`, `@46`/`@47`/`@48`; `@58`/`@59`/`@61` in the lane C run); exact callbacks and addressed delivery both ways. A ≠ S is the load-bearing half — the app-server's inherited `TMUX` names A, so the outbound Pi landing in S can only have come from the caller's own pane title, and its receipt records `seat-source=codex-title-anchor`. **#95 lane C added the second axis to the same card: WHERE the sibling starts.** Hop 1 joins four authorities that cannot borrow from each other — the pane's `#{pane_current_path}`, the vendor rollout's `session_meta.cwd`, the Entwurf record and the requested scratch — and hop 1's leg requests a cwd while hop 2 requests NONE, so hop 2's directory can only come from the Codex caller's own record; both are asserted different from the app-server's live `/proc/<pid>/cwd`. 65 and 56 are different contracts, not a regression. The 2026-09-12 fixed-home chain is PAST CONTRACT (#95 D1); `DELIVERY.md` owns every receipt and digest. Placement beside a TUI whose thread nobody named is unsupported and unclaimed. |
294
294
 
295
295
  The repo-local `entwurf-release` skill owns the `land → prepare → make → publish`
@@ -305,7 +305,7 @@ consumer image outside this standing protocol.
305
305
 
306
306
  - **Garden-id delivery:** discover a target with `entwurf_peers`, then `entwurf_v2 fire-and-forget` — the intent axis is single-valued since the visible-first cut. It reaches a live pi target over its control socket, a deliverable self-fetch citizen through the mailbox, and a probe-alive native-push conversation by direct injection. A DORMANT in-domain citizen rejects as `dormant-fire-forget-unsupported` — delivery never wakes it; reopen it with the separate lifecycle verb `entwurf_resume_call {target}`, then dispatch again. A wrong target state is rejected, never auto-fixed and never launched into.
307
307
  - **ACP continuity:** a direct `pi --provider entwurf --model claude-sonnet-5-5` turn, or the `smoke-acp-session-reuse-live` gate (process-scoped reuse + recall). Multi-turn reuse is proven by that gate, not by any v1 resume tool.
308
- - **agy citizenship:** in a fresh agy conversation, the first `PreInvocation` must yield a garden id, `entwurf_self` must report `agentId=meta-session/antigravity` and `replyable:true` only while the native probe is alive, and a reply to that same garden id must direct-inject into the same conversation. No mailbox/receiver-marker evidence counts on this rail.
308
+ - **agy citizenship (optional diagnostic while #152 holds; run on request, not a cut requirement):** in a fresh agy conversation, the first `PreInvocation` must yield a garden id and `entwurf_self` must report `agentId=meta-session/antigravity`. While native push is paused (#152), `replyable` is `false` with a reason naming the pause, and a dispatch to that garden id returns `native-push-paused` with nothing probed or sent. A same-gid reply is not claimed. No mailbox/receiver-marker evidence counts on this rail.
309
309
 
310
310
  ### What NOT to do — bypassing the operational path
311
311
 
@@ -437,7 +437,7 @@ addressable sends require `--entwurf-control` (measured 2026-07-24: the same
437
437
  one-shot with that flag returns its own gid and delivers `entwurf_v2` to a peer
438
438
  mailbox with `origin=pi-session`, `replyable=true`).
439
439
 
440
- `setup` requires Node 24 and Python 3 (pnpm only on a source checkout — the dependency bootstrap is source-only, and installed mode needs no pnpm); harnesses including pi are optional-by-presence (absent → explicit zero-state SKIP, detected incomplete/below-floor → named FAIL + nonzero result). On a source checkout it runs the frozen `pnpm install` + presence-gated pi project/user wiring + detected Claude wiring + source stable-bin exposure + detected agy wiring + detected Copilot four-unit composition + the v2 install smoke. A green setup certifies that the source-owned `entwurf` symlink targets this checkout and wins PATH resolution; helper units are attempted independently and a foreign helper is a named bins FAIL (nonzero), with harness-specific doctors keeping the per-leaf verdict. A detected `copilot` composes all four native units (birth → MCP → receiver → visible footer) with independent per-unit verdicts (#86 C3b); setup does **not** replace any native-harness doctor. The full aggregate live floor is still `LIVE=1 ./run.sh release-gate <scratch> --cut` — without `--cut` it is a diagnostic pass, not acceptance — with agy's conversation-id-gated round trip verified separately.
440
+ `setup` requires Node 24 and Python 3 (pnpm only on a source checkout — the dependency bootstrap is source-only, and installed mode needs no pnpm); harnesses including pi are optional-by-presence (absent → explicit zero-state SKIP, detected incomplete/below-floor → named FAIL + nonzero result). On a source checkout it runs the frozen `pnpm install` + presence-gated pi project/user wiring + detected Claude wiring + source stable-bin exposure + detected agy wiring + detected Copilot four-unit composition + the v2 install smoke. A green setup certifies that the source-owned `entwurf` symlink targets this checkout and wins PATH resolution; helper units are attempted independently and a foreign helper is a named bins FAIL (nonzero), with harness-specific doctors keeping the per-leaf verdict. A detected `copilot` composes all four native units (birth → MCP → receiver → visible footer) with independent per-unit verdicts (#86 C3b); setup does **not** replace any native-harness doctor. The full aggregate live floor is still `LIVE=1 ./run.sh release-gate <scratch> --cut` — without `--cut` it is a diagnostic pass, not acceptance — with agy accepted separately under criterion 7: its paused native push by the deterministic `[QK:AGY-NATIVE-PUSH-PAUSED]` cell and its record, install and sender gates, all inside `check:full`; the retained legacy identity check is an optional diagnostic while #152 holds, and no agy round trip is claimed.
441
441
 
442
442
  ### 1.4 Cross-install / cross-backend parity (optional, high-value)
443
443
 
@@ -545,7 +545,7 @@ The minimum passing bar:
545
545
  4. **Native-host doctor green where the Claude meta-bridge is claimed:** a new post-install Claude session exists, live evidence is present, and the installed `doctor-meta-bridge` exits 0. `NOT CERTIFIED` is a release failure for that host, not a skip.
546
546
  5. **Honest self-recognition:** the bridged model identifies its actual harness/backend, lists `entwurf-bridge` as the single MCP server with the tools its loaded bridge actually lists (the current source lists nine; a pi citizen offers seven with two hidden, the pi-durable contact eight), and presents a backend-native (not normalized) tool surface.
547
547
  6. **Carrier separation honored:** engraving vs pi-context-augment kept distinct (§1A.0); no bridge-identity narrative attributed to the engraving carrier.
548
- 7. **agy shipped lane accepted:** all three agy doctors are green; automatic birth/statusline/sender identity and same-gid native-push reply are confirmed in a fresh conversation. `agentId=meta-session/antigravity` is correct; model display is not part of that contract. Same-pid concurrent conversation invocation is not claimed.
548
+ 7. **agy native push paused (#152); acceptance is deterministic while the pause holds.** Required for a cut: a dispatch to an agy target returns `native-push-paused` with zero probe, send, adapter or record activity, an agy sender's `replyable` is false by name, and `entwurf_register_native` refuses before probing — proven by the deterministic `[QK:AGY-NATIVE-PUSH-PAUSED]` cell, which needs no agy conversation and is not a live round trip — and the agy record schema and reader stay in place, with the agy install and sender gates green inside `check:full`. A same-gid reply is not claimed while the pause holds. Not required for a cut: the retained legacy identity check (all three agy doctors, plus automatic birth/statusline/sender identity in a fresh conversation, where `agentId=meta-session/antigravity` is correct and model display is not part of it). It is an optional diagnostic run when explicitly asked, it is not send evidence, and a release does not claim that the installed agy host is certified or can receive. An optional diagnostic is not a cut requirement, and neither is a native-push PASS. Lifting the pause needs a separate future change with its own positive acceptance. Same-pid concurrent conversation invocation is not claimed.
549
549
  8. **Codex caller-seat topology accepted:** its birth/MCP/status-line/terminal-title doctors are green and the on-demand real visible `Pi → Codex → Pi` cell records four coordinates: the operator-owned app-server in its own session A; initial Pi, omitted-placement fresh Codex, and Codex-opened outbound Pi all in a DIFFERENT session S; exact callbacks and addressed delivery succeed both ways. A ≠ S is a precondition, not a preference — the app-server's inherited environment names A, so an outbound Pi in S can only have come from the caller's own pane title, and the receipt must name `codex-title-anchor` as the rule that chose it. 0 or 2+ matching panes, and a caller whose `[tui].terminal_title` lacks `thread-id`, reject without mutation. Entwurf neither creates nor supervises the app-server, infers attached-TUI panes, nor claims placement beside a TUI whose thread nobody named. (The 2026-09-12 fixed-`codex`-home acceptance this condition replaced stays recorded in `DELIVERY.md` as history; #95 D1 retired that room.) Codex remains outside ACP and has no resume claim.
550
550
  9. **Boundary preservation across backends/machines:** for every shipped or explicitly probed backend, regardless of install path or host, no cross-backend tool-surface contamination and no confabulation about pi internals.
551
551
  10. **Hygiene:** no orphan ACP children; no unexpected persisted session garbage (a turn-scoped `cwd:` fallback is never a persisted reuse).
@@ -83,7 +83,7 @@ Emergency/manual workaround when the MCP server environment is wrong but an exis
83
83
  External/meta-session semantics:
84
84
 
85
85
  - `entwurf_v2` from a plain external host is **refused by default** (no authoritative sender — #50 C4). With the explicit `ENTWURF_BRIDGE_ALLOW_ANONYMOUS_SENDER=1` hatch it delivers with `origin: "external-mcp"` / `replyable: false`; there is still no reply address.
86
- - `entwurf_v2` from a trusted meta-session delivers with `origin: "meta-session"`, and `replyable` is **derived from that sender's own rail — not granted by being trusted**: a self-fetch sender (Claude Code, Copilot, OMP) is replyable only while its receiver is live and armed; Antigravity requires a live native probe; Codex requires a matching request-scoped record and loaded app-server thread. Identity survives either way; only `replyable` drops to `false`.
86
+ - `entwurf_v2` from a trusted meta-session delivers with `origin: "meta-session"`, and `replyable` is **derived from that sender's own rail — not granted by being trusted**: a self-fetch sender (Claude Code, Copilot, OMP) is replyable only while its receiver is live and armed; Antigravity requires a live native probe, and while its native push is paused (#152) it is `false` by name without a probe; Codex requires a matching request-scoped record and loaded app-server thread. Identity survives either way; only `replyable` drops to `false`.
87
87
  - `entwurf_v2` never launches a process, so no delivery path needs `pi` on PATH. A dormant pi target is refused as `dormant-fire-forget-unsupported`: the hidden background resume that used to answer there was withdrawn under the visible-first rule, and re-opening the session is the separate `entwurf_resume_call` verb — which DOES need `pi` on PATH, because it starts one.
88
88
  - `entwurf_self` returns the same authoritative identity for pi sessions **and** trusted meta-sessions. A plain external host with no pi env, native marker, or Codex request claim still fails because there is no reply address to report.
89
89
 
@@ -23,9 +23,9 @@ evidence split, and its three states must not collapse into "macOS is supported"
23
23
 
24
24
  ## Requirements
25
25
 
26
- This table is this source candidate's, which moves the Pi SDK set to 1.4.0 and is **not published
27
- to npm yet**. The newest published package, `@junghanacs/entwurf@0.41.0`, ships the Pi 1.3.0 SDK
28
- set and requires Pi 1.3.x; the setup guide inside that tarball is its contract.
26
+ This table is the published `@junghanacs/entwurf@0.41.1`'s, which ships the Pi 1.4.0 SDK set.
27
+ The previous package, `0.41.0`, ships the Pi 1.3.0 SDK set and requires Pi 1.3.x; the setup guide
28
+ inside that tarball is its contract.
29
29
 
30
30
  | Component | Requirement | Needed for |
31
31
  |---|---|---|
@@ -38,7 +38,7 @@ set and requires Pi 1.3.x; the setup guide inside that tarball is its contract.
38
38
  | Claude Code | optional, **`>=2.1.306`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
39
39
  | GitHub Copilot CLI | optional-by-presence, operator-installed and authenticated — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/receiver/footer) | self-fetch citizen and visible fresh |
40
40
  | OMP (`omp`) | optional-by-presence, operator-installed — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/`tools.xdev` setting/receiver) | self-fetch citizen and visible fresh (accepted on one host — see §4b) |
41
- | Antigravity `agy` | optional, operator-installed and authenticated | native-push citizen |
41
+ | Antigravity `agy` | optional, operator-installed and authenticated | legacy citizen; native push paused (#152) |
42
42
  | OpenAI Codex CLI | optional-by-presence, operator-installed and authenticated; detected composes all four operator-owned units (birth/MCP/status-line/terminal-title) and stays non-green until the operator trusts the birth declaration once in a visible Codex; visible fresh also requires the operator-owned app-server, started with `entwurf codex-app-server` in a tmux session of the operator's choosing (#95 D1 retired the fixed `codex` home) | native-push and visible fresh, supported in 0.21.0 |
43
43
  | Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
44
44
 
@@ -83,15 +83,14 @@ node --version
83
83
  npm --version
84
84
  ```
85
85
 
86
- The two npm forms below install the **published** package, `0.41.0` (Pi 1.3.x, the Pi 1.3.0 SDK
87
- set), not this candidate. This candidate (Pi 1.4) installs only from a source checkout of its
88
- commit, further down this section; it has no npm install command until it is released.
86
+ The two npm forms below install the **published** package, `0.41.1` (Pi 1.4.x, the Pi 1.4.0 SDK
87
+ set).
89
88
 
90
89
  Global install is simplest when native harnesses should find stable bins from every
91
90
  working directory:
92
91
 
93
92
  ```bash
94
- npm install -g @junghanacs/entwurf@0.41.0
93
+ npm install -g @junghanacs/entwurf@0.41.1
95
94
  entwurf --help
96
95
  entwurf setup /path/to/your-project
97
96
  entwurf check-bridge
@@ -102,7 +101,7 @@ A project-local installation is also supported:
102
101
  ```bash
103
102
  mkdir -p ~/entwurf-smoke && cd ~/entwurf-smoke
104
103
  npm init -y
105
- npm install --save-dev @junghanacs/entwurf@0.41.0
104
+ npm install --save-dev @junghanacs/entwurf@0.41.1
106
105
  npx entwurf setup .
107
106
  npx entwurf check-bridge
108
107
  ```
@@ -112,7 +111,7 @@ project-local install, or `./run.sh <command>` from a source checkout. Keep that
112
111
  same scope when upgrading and checking; native process restarts are separate (§7).
113
112
 
114
113
  `check-bridge` is auth-free. It proves the installed prebuilt MCP server boots and
115
- lists the nine garden tools (0.41.0); it does not prove a backend model turn, native hook or retirement permission.
114
+ lists the nine garden tools (0.41.1); it does not prove a backend model turn, native hook or retirement permission.
116
115
 
117
116
  Neither npm form installs a harness runtime. `pi`, Claude Code, Copilot CLI, Codex CLI, agy,
118
117
  Cortex, and their authentication remain operator-owned optional prerequisites for the
@@ -137,7 +136,9 @@ release acceptance. A checkout that already holds the intended commit skips the
137
136
  `git checkout`.
138
137
 
139
138
  This owns `~/.local/bin/entwurf` as a symlink to that checkout's `run.sh` and fails if the
140
- link is foreign, outside PATH, or shadowed by another command. It detects and wires
139
+ link is foreign, outside PATH, or shadowed by another command, or if another root still answers to
140
+ a package bin name in PATH or in the global bin dir `pnpm bin -g` / `npm prefix -g` report (it
141
+ removes nothing). It detects and wires
141
142
  pi/Claude/agy/Copilot/OMP/Codex by presence and prints a computed per-component
142
143
  PASS/SKIP/FAIL summary — a detected harness that cannot be completed makes setup exit nonzero.
143
144
  A detected `copilot` composes all four native units, and a detected `omp` composes its own four.
@@ -221,10 +222,9 @@ physical-Darwin receipt. `entwurf install` may write `.pi/settings.json`
221
222
  on Darwin; that is wiring, not a rail receipt. If `pi` is on PATH,
222
223
  `setup` names the pi row FAIL with the §1 Darwin wording.
223
224
 
224
- Install this candidate's exact Pi floor, then wire the project from the candidate's source
225
- checkout (§1; `entwurf` is the link `./run.sh setup` made to it). The published 0.41.0 instead
226
- takes the Pi 1.3.x its own setup guide names and the global package root,
227
- `$(npm root -g)/@junghanacs/entwurf`, in place of the checkout:
225
+ Install the exact release floor, then wire the project. The previous 0.41.0 instead takes the
226
+ Pi 1.3.x its own setup guide names. A source checkout passes its checkout path to `-e` in place of
227
+ the global package root:
228
228
 
229
229
  ```bash
230
230
  npm install -g @earendil-works/pi-coding-agent@1.4.0
@@ -232,7 +232,7 @@ pi --version
232
232
 
233
233
  cd ~/entwurf-smoke
234
234
  entwurf install .
235
- pi -e ~/repos/gh/entwurf --list-models entwurf
235
+ pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
236
236
  ```
237
237
 
238
238
  The supported range is `>=1.4.0 <1.5`. It is a hard minimum: installing this
@@ -257,14 +257,14 @@ A plain `pi` can load the package without becoming a citizen.
257
257
  In that session ask for `entwurf_self`, then `entwurf_peers`. Pick an existing
258
258
  live garden id and ask for `entwurf_v2` with `intent: "fire-and-forget"` and your
259
259
  message. These are harness tools, not shell subcommands; pi exposes them through
260
- Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The 0.41.0 bridge has nine tools;
260
+ Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The 0.41.1 bridge has nine tools;
261
261
  pi hides `entwurf_inbox_read` and `entwurf_register_native`, leaving seven active.
262
262
  The added `entwurf_retire_call` is only for one target/action explicitly requested by the
263
263
  human operator, never autonomous cleanup. Where a host provides or is configured with an approval
264
264
  step, that step applies as well; Pi and pi-durable 1.3.0 have no built-in approval prompt by
265
265
  default. The configured Claude ACP child is also [set to run without per-call permission prompts](./acp-backend-rail.md#configured-claude-acp-permissions); ACP retirement remains unmeasured.
266
266
  Tool availability, annotations and caller identity are not a grant.
267
- These candidate counts do not claim that an older installed package or running host has rolled
267
+ These counts do not claim that an older installed package or running host has rolled
268
268
  forward; see [applying new source to a running host](#applying-new-source-to-a-running-host).
269
269
  Tool-call ingress is MCP; delivery to a pi citizen is still its record-addressed
270
270
  control address (a Unix socket; a named pipe on the experimental native-Windows Pi
@@ -450,8 +450,8 @@ proof ([#130](https://github.com/junghan0611/entwurf/issues/130)).
450
450
 
451
451
  ### Moving durable sessions from the Pi 1.3 SDK to 1.4
452
452
 
453
- This candidate is unpublished, and moving a live host onto it is a separate operator decision. When
454
- you do:
453
+ 0.41.1 is published; moving a live host onto it remains a separate operator decision for each
454
+ host. When you do:
455
455
 
456
456
  1. **Confirm the old owner is gone.** Close the 0.41 (SDK 1.3) durable session normally and check
457
457
  that its process has exited. A stale session lock alone is not proof.
@@ -498,31 +498,45 @@ re-emitted with `--write`, and its generated digest is reviewed like any other c
498
498
 
499
499
  ## 2c. Optional remote pair between two hosts (#107)
500
500
 
501
- 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.
501
+ This lets declared pairs of existing citizens on two or more hosts reach each other over SSH that you already run. Each pair is one route; one host's file may carry several (#149). [DELIVERY](../DELIVERY.md#remote-pairs-107-149-declared-pairs-across-hosts) owns the contract. The machine boundary is set by a person, and nothing here is automatic.
502
502
 
503
- 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.
503
+ 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. Everything that can log in to an account is inside that account's trust: if two other hosts' keys reach the same account, either can claim any garden id that account's file declares.
504
504
  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.
505
505
  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:
506
506
 
507
507
  ```json
508
508
  {
509
- "remoteGardenId": "<the other host's garden id>",
510
- "ssh": "<your existing alias for the other host>",
511
- "localGardenId": "<this host's garden id that talks to it>",
512
- "remotePairFile": "<absolute path of the other host's own file>"
509
+ "version": 2,
510
+ "routes": [
511
+ {
512
+ "localGardenId": "<this host's garden id that talks to it>",
513
+ "remoteGardenId": "<the other host's garden id>",
514
+ "ssh": "<your existing alias for the other host>",
515
+ "remotePairFile": "<absolute path of the other host's own file>"
516
+ }
517
+ ]
513
518
  }
514
519
  ```
515
520
 
516
- 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.
521
+ Add one route per pair. One local citizen may appear in several routes, and so may one remote citizen or one alias. The file is refused if it repeats a pair, uses one garden id as both local and remote, reaches one remote garden id through two aliases or files, or gives one alias two different files. For each route, the other host's file needs the mirror route: there, this route's `remoteGardenId` is the `localGardenId`. That host's `remotePairFile` must spell this path as it is normalized, not through a symlinked alias.
517
522
 
518
523
  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.
519
524
 
520
525
  ```bash
521
- node --experimental-strip-types -e 'import("./pi-extensions/lib/remote-pair-send.ts").then(m=>console.log(m.readRemotePairOptIn(m.remotePairOptInPath())))'
526
+ node --experimental-strip-types -e 'import("./pi-extensions/lib/remote-pair-send.ts").then(m=>console.log(JSON.stringify(m.readRemotePairOptIn(m.remotePairOptInPath()),null,2)))'
522
527
  ```
523
528
 
524
529
  Do not validate the configuration by sending a test message.
525
- 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.
530
+ 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 every route off; there is nothing to stop or reconnect.
531
+
532
+ **Converting a file from 0.41 or earlier.** The earlier versionless file (the four keys alone) is refused by name. It is not read as one route, and it is not read as off. Convert each host by hand, one host at a time:
533
+
534
+ 1. Inspect the existing file.
535
+ 2. Install the new bytes on that host.
536
+ 3. Restart the processes whose bridge you use. A bridge that is already running keeps the code it loaded.
537
+ 4. Rewrite the file as `{"version":2,"routes":[<the old object>]}`, mode `0600`.
538
+
539
+ While the bytes and the file disagree, **every** `entwurf_v2` call of that host's bridges is refused, local targets included, because the file is read before the target is classified. That applies either way: new code with the old file, or old code with the new file. Keep that window short, and do not hide it with a fallback. The other host can stay on its own release and file until it converts. The claim, SSH argv, exit codes and receipt did not change, so the two hosts should still interoperate, but that mixed state has not been measured, and neither has any exchange over the new file.
526
540
 
527
541
  ## 3. Optional Claude Code native citizen
528
542
 
@@ -939,9 +953,14 @@ mechanism, because both the anchor and the env fallback would answer the same ro
939
953
 
940
954
  ## 5. Optional Antigravity native citizen
941
955
 
942
- This rail is CERTIFIED on Linux; on macOS it is NOT CERTIFIED — pending
943
- physical host. The installers may write on Darwin; a green install is
944
- not a native-push receipt.
956
+ Native-push send on this rail is paused (#152, below). Its Linux certification is
957
+ history: the last live bidirectional round trip was on agy 1.1.0 (2026-07-14), and it
958
+ is not a current certification. What remains is the legacy identity surface — install,
959
+ birth, statusline and sender identity. While the pause holds, a release accepts agy
960
+ through the deterministic pause cell and the install/sender gates only; checking these
961
+ surfaces in a fresh conversation is an optional diagnostic, not a release certification of
962
+ the installed host. On macOS it is NOT CERTIFIED — pending physical host. The installers may write on Darwin; a green install is not a native-push
963
+ receipt.
945
964
 
946
965
  Install the three independently owned surfaces:
947
966
 
@@ -960,9 +979,16 @@ owns its subtree; the hook owns one `PreInvocation` entry. Unrelated settings ar
960
979
  preserved. A fresh conversation initially may show `🪛 ? agy`; the first invocation
961
980
  births the record by native `conversationId`, after which the garden id appears.
962
981
 
963
- Real native-push acceptance needs an already-running conversation:
982
+ Native push to agy is paused (#152): on the external path entwurf measured, the agy
983
+ language server refused `agentapi` calls for a missing CSRF token (a first-party token
984
+ path is unmeasured), so `entwurf_v2` refuses an agy target as
985
+ `native-push-paused`, `entwurf_self` reports `replyable: false`, and
986
+ `entwurf_register_native` refuses. The citizen keeps its garden id and statusline. While
987
+ the pause holds, the LIVE smoke exits 2 with `[entwurf:paused]` before it reads the
988
+ conversation id, which is not a PASS, and no conversation needs to be opened for it:
964
989
 
965
990
  ```bash
991
+ # exits 2 [entwurf:paused] while #152 holds — not a PASS, not a SKIP
966
992
  LIVE=1 AGY_CONVERSATION_ID=<id> entwurf smoke-agy-native-push-live
967
993
  ```
968
994
 
@@ -77,7 +77,7 @@ import { resolveMailboxReceiverFacts } from "../../../pi-extensions/lib/entwurf-
77
77
  import { listEntwurfFacts } from "../../../pi-extensions/lib/entwurf-fact-provider.js";
78
78
  import { ENTWURF_PEERS_RENDER_LIMIT, renderEntwurfPeers } from "../../../pi-extensions/lib/entwurf-peers-render.js";
79
79
  import { computeSelfAddressability } from "../../../pi-extensions/lib/entwurf-self-address.js";
80
- import { nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.js";
80
+ import { nativePushPaused, nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.js";
81
81
  import { resolveMailboxWakeModeCapability } from "../../../pi-extensions/lib/entwurf-v2-decider.js";
82
82
  import { retireCitizen } from "../../../pi-extensions/lib/entwurf-v2-retire.js";
83
83
  import { makeProductionRetireDeps } from "../../../pi-extensions/lib/entwurf-v2-retire-production.js";
@@ -235,6 +235,7 @@ async function buildMetaSenderEnvelope(identity, cwd, trustedMarker) {
235
235
  metaDeliveryDomain,
236
236
  recordBacked: true,
237
237
  probeAlive: await probeNativeSenderAlive(identity),
238
+ nativePushPaused: nativePushPaused(identity.backend),
238
239
  }
239
240
  : metaDeliveryDomain === "self-fetch"
240
241
  ? (() => {
@@ -402,7 +403,7 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
402
403
  "mailbox only while deliverable, native-push direct injection only while its adapter probe is alive, " +
403
404
  "or a THIRD RESULT: rejection when neither holds. An inactive self-fetch citizen is mailbox-undeliverable; " +
404
405
  "native-push has NO mailbox: dead → native-push-target-dead; indeterminate → " +
405
- "native-push-probe-indeterminate. " +
406
+ "native-push-probe-indeterminate; agy (paused, #152) → native-push-paused, no probe. " +
406
407
  "DORMANT IS UNREACHABLE: a socket-domain citizen that is not running gets dormant-fire-forget-unsupported " +
407
408
  "— same receiver rule as the mailbox, no active drainer means no delivery. " +
408
409
  "The intent that used to answer there, owned-outcome, resumed it by launching a hidden background child " +
@@ -417,7 +418,8 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
417
418
  .describe("fire-and-forget = send/reply/hand-off to a LIVE socket target or to any citizen with no " +
418
419
  "socket liveness — the decider picks its rail, and a rail can also REJECT (self-fetch → " +
419
420
  "mailbox when deliverable, else mailbox-undeliverable; native-push → alive: direct injection, " +
420
- "dead: native-push-target-dead, indeterminate: native-push-probe-indeterminate); set " +
421
+ "dead: native-push-target-dead, indeterminate: native-push-probe-indeterminate; a paused " +
422
+ "backend — agy, #152 — rejects as native-push-paused before any probe); set " +
421
423
  "wants_reply for an answer. This is the ONLY intent: owned-outcome, which resumed a dormant " +
422
424
  "citizen by launching a hidden background child, was withdrawn under the visible-first rule. " +
423
425
  "It is not selectable, not deprecated-but-tolerated; a dormant citizen is currently " +
@@ -438,11 +440,11 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
438
440
  // Resolved ONCE so the dispatch-moment timestamp is fixed and the control RPC sender
439
441
  // + the mailbox body sender share one envelope. No replyability gate (see above).
440
442
  const sender = await buildSendSenderEnvelope({ requestMeta: extra._meta });
441
- // #107: the operator's remote-pair opt-in is read per send. Absent = off and the local
442
- // surface below runs exactly as before; a declared target goes over that one route.
443
+ // #107/#149: the operator's remote-pair opt-in is read per send. Absent = off and the local
444
+ // surface below runs exactly as before; a declared target goes over its one exact route.
443
445
  const rendered = await runEntwurfV2WithRemoteRoute({ target, intent, message, mode, wants_reply }, {
444
446
  senderProvider: () => sender,
445
- route: readRemotePairOptIn(remotePairOptInPath()),
447
+ optIn: readRemotePairOptIn(remotePairOptInPath()),
446
448
  // The decider's canonical socket dir AND this bridge's ENTWURF_DIR: a trace in either is local.
447
449
  localTrace: makeLocalTraceFact({
448
450
  sessionsDir: defaultMetaSessionsDir(),
@@ -546,7 +548,7 @@ server.tool("entwurf_peers", "List the entwurf fact surface: garden citizens (fr
546
548
  "or a REJECT when neither holds: mailbox-undeliverable for a self-fetch citizen whose inbox " +
547
549
  "nobody drains, and on the native-push probe dead → native-push-target-dead vs " +
548
550
  "indeterminate → native-push-probe-indeterminate, kept apart because an unestablished probe " +
549
- "is not a departed host. So " +
551
+ "is not a departed host; an agy target rejects as native-push-paused (#152) without being probed. So " +
550
552
  "`unsupported` does not promise reachability either: a record whose backend has no adapter on " +
551
553
  "this lane resolves to that reject. " +
552
554
  "Note: this is the *active* world and it is facts-only — it creates nothing. Pass an " +
@@ -616,8 +618,9 @@ server.tool("entwurf_register_native", "Register an ALREADY-RUNNING native conve
616
618
  "must be LIVE: it is probed first, and a dead/indeterminate probe is refused (no garden id is " +
617
619
  "minted for a pointer that does not resolve to a real host). Re-registering the same " +
618
620
  "conversation attaches to the SAME garden id and refreshes the cwd. Only 'antigravity' is " +
619
- "registerable on this lane. No mailbox receiver marker is written — native-push has no " +
620
- "idle-wake watch; the returned garden id is the reply handle.", {
621
+ "registerable on this lane, and antigravity native-push is PAUSED (#152): this verb refuses it by " +
622
+ "name before any probe and mints no garden id. No mailbox receiver marker is written — native-push " +
623
+ "has no idle-wake watch; the returned garden id is the reply handle.", {
621
624
  backend: z
622
625
  .enum(["antigravity"])
623
626
  .describe("The native backend hosting the conversation. Only 'antigravity' — codex is a separate lane."),
@@ -659,8 +662,8 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling beside you and
659
662
  "herdr workspace, pi and claude-code " +
660
663
  "only; everywhere else it opens in the operator's tmux with all six backends (pi, " +
661
664
  "claude-code, copilot, omp, codex, pi-durable). No fallback: an incomplete herdr context is refused by name, " +
662
- "never by a tmux window you cannot see from herdr. The sibling's FIRST action " +
663
- "is a callback to you carrying a nonce, whose sender envelope is its garden id — that is how you learn the " +
665
+ "never by a tmux window you cannot see from herdr. If proceeding, the sibling calls back before its task with a nonce; " +
666
+ "the sender envelope names its garden id — that is how you learn the " +
664
667
  "address of a thing that did not exist a moment ago. This returns a LAUNCH receipt (the owner's " +
665
668
  "coordinates plus that nonce): it does NOT mean the runtime started, the first turn ran, or " +
666
669
  "the task was delivered. Those coordinates are a VIEW, never an address — a herdr tab/pane id can change under " +
@@ -75,6 +75,16 @@ export function computeSelfAddressability(facts) {
75
75
  // default fail-closed row).
76
76
  switch (facts.metaDeliveryDomain) {
77
77
  case "native-push": {
78
+ // #152 policy, decided BEFORE the pure record+probe predicate and kept out of it:
79
+ // only a record-backed citizen can be "paused" (a record-less one keeps its own
80
+ // reason), and no probe was run for this answer, so none is reported.
81
+ if (facts.recordBacked === true && facts.nativePushPaused === true) {
82
+ return {
83
+ replyable: false,
84
+ socketState: "none",
85
+ reason: "native-push unreachable — native-push for this backend is paused (#152); no probe ran for this answer",
86
+ };
87
+ }
78
88
  const push = nativePushDeliverable({ recordBacked: facts.recordBacked, probeAlive: facts.probeAlive });
79
89
  return {
80
90
  replyable: push.deliverable,
@@ -119,6 +119,24 @@ export const NATIVE_PUSH_BACKENDS = ["antigravity", "codex"];
119
119
  export function nativePushSupported(backend) {
120
120
  return NATIVE_PUSH_BACKENDS.includes(backend);
121
121
  }
122
+ // ── Paused native-push backends (#152) ─────────────────────────────────────
123
+ // A native-push backend whose SEND is paused by product policy. On the external send path
124
+ // entwurf measured, Antigravity's language server refused the `agentapi` call with
125
+ // `Unauthenticated: missing CSRF token` (agy 1.2.16 in #128; agy 1.4.6 again for #152).
126
+ // Whether a first-party own-tool shell holds a token entwurf could legitimately present is
127
+ // UNMEASURED, not ruled out. So instead of probing a port that refused on that path and
128
+ // reporting it as an inconclusive liveness, the three public entries that would reach the agy LS —
129
+ // v2 dispatch, a sender's own `replyable`, and `entwurf_register_native` — refuse by name
130
+ // BEFORE any probe. This is a fixed constant, not a flag: no env, config or runtime toggle
131
+ // can lift it. The backend stays in NATIVE_PUSH_BACKENDS, so existing agy records, the V3
132
+ // reader and the peers listing are unchanged; only the native-push traffic stops.
133
+ // The one probe that is NOT behind this guard is the fresh-cut quiescence check
134
+ // (scripts/meta-bridge-fresh-cut.ts), which protects an operator-named archive and must keep
135
+ // failing closed rather than read the pause as a departed host.
136
+ export const NATIVE_PUSH_PAUSED_BACKENDS = ["antigravity"];
137
+ export function nativePushPaused(backend) {
138
+ return NATIVE_PUSH_PAUSED_BACKENDS.includes(backend);
139
+ }
122
140
  /**
123
141
  * Compose the 4-value FACT liveness from a backend and its socket probe.
124
142
  * Out-of-domain backend → `unsupported` (NOT dead/indeterminate, R1). An
@@ -148,18 +166,20 @@ export const ENTWURF_V2_REJECT_REASONS = [
148
166
  "mailbox-undeliverable", // F-mailbox: fire-and-forget to an unsupported citizen whose mailbox is not deliverable (fail-closed; future pi-backend non-drainable mailbox)
149
167
  "native-push-target-dead", // 봉인 1: fire-and-forget to a native-push (agy) target whose adapter probe found NO live conversation. Post-probe; observedLiveness = dead.
150
168
  "native-push-probe-indeterminate", // 봉인 1: fire-and-forget to a native-push target whose adapter probe was inconclusive (agy alive but no port served the conv, or a probe error). Post-probe; observedLiveness = indeterminate. Never coerced to dead.
169
+ "native-push-paused", // #152: fire-and-forget to a native-push backend whose send is paused by product policy (NATIVE_PUSH_PAUSED_BACKENDS — antigravity). Pre-probe, observedLiveness = null: nothing was probed, so no liveness is claimed — never coerced to dead or indeterminate.
151
170
  "bad-target", // R2: absent/typo garden-id (no existing citizen); fresh creation out of v2 scope
152
171
  "record-less-socket", // #50 C4: a gid-shaped non-symlink control socket exists but NO meta-record claims it. The record is the sole address authority (목표 ②), so a bare socket is not an addressable citizen — it is a diagnostic state, refused for EVERY intent before any lock/probe (pre-probe, observedLiveness=null). NOT `bad-target`: something real answers to this gid, and mislabeling it absent would hide the very state the reject exists to surface.
153
172
  "target-locked", // R5 pre-claim for bucket B F2 per-gid lockfile conflict
154
173
  "target-address-conflict", // F3: a quarantined citizen (garden-id-socket-conflict / symlinked socket) — the gid resolves to two different receivers (record vs socket), so dispatch refuses to pick. The ONLY in-band honest channel for a dispatch-level identity-split (the listing diagnostic channel is not visible to a v2 caller, who only gets a receipt). Pre-resolver, like bad-target/target-locked — NOT a RESOLVER_REJECT_REASONS member.
155
174
  ];
156
175
  // ── Pre-probe reject reasons (?6 — observedLiveness = null) ────────────────
157
- // These four rejects are decided BEFORE any liveness probe runs, so there is no
176
+ // These five rejects are decided BEFORE any liveness probe runs, so there is no
158
177
  // honest 4-value FactLiveness to stamp: `bad-target` (no citizen/backend),
159
178
  // `target-locked` (5a lock conflict, before lstat/connect), `target-address-conflict`
160
179
  // (address-subject conflict → probing is forbidden), `record-less-socket` (#50 C4:
161
180
  // no record ⇒ not a citizen ⇒ no in-domain probe ever runs — the presence hint is
162
- // a single lstat, not a liveness measurement). `indeterminate` means an
181
+ // a single lstat, not a liveness measurement), `native-push-paused` (#152: the backend's
182
+ // native-push send is paused by policy, so its adapter is never probed). `indeterminate` means an
163
183
  // in-domain probe was inconclusive (≠ "not looked yet"); `unsupported` means the
164
184
  // backend has no predicate (≠ "pre-probe"). So a pre-probe reject's
165
185
  // observedLiveness is `null`, NOT one of the four values. Every OTHER reject —
@@ -176,6 +196,7 @@ export const PRE_PROBE_REJECT_REASONS = [
176
196
  "target-locked",
177
197
  "target-address-conflict",
178
198
  "record-less-socket",
199
+ "native-push-paused",
179
200
  ];
180
201
  export function isPreProbeReject(reason) {
181
202
  return PRE_PROBE_REJECT_REASONS.includes(reason);
@@ -44,7 +44,7 @@
44
44
  * be bypassed.
45
45
  */
46
46
  import { isOutOfSocketDomainGardenIdConflict } from "./entwurf-facts.js";
47
- import { isLivenessSupported, makeRejectReceipt, nativePushSupported, resolveDispatch, resolveNativePushDispatch, } from "./entwurf-v2-contract.js";
47
+ import { isLivenessSupported, makeRejectReceipt, nativePushPaused, nativePushSupported, resolveDispatch, resolveNativePushDispatch, } from "./entwurf-v2-contract.js";
48
48
  import { defaultMetaMailboxDir, defaultMetaSessionsDir, metaCapabilityFor, } from "./meta-session.js";
49
49
  import { isValidSessionId } from "./session-id.js";
50
50
  import { controlSocketPath, mapInspectionToLiveness } from "./socket-discovery.js";
@@ -121,6 +121,12 @@ export async function decideDispatch(input, deps) {
121
121
  // branch is LOCK-FREE (봉인 4): the pi in-domain lock closes a socket TOCTOU, but a
122
122
  // volatile probe route has no lock meaning (a duplicate-send idempotency is a D8 future).
123
123
  if (nativePushSupported(identity.backend)) {
124
+ // #152: a paused native-push backend is refused by name BEFORE its adapter is probed —
125
+ // on the measured external path that language server refused the call (missing CSRF token),
126
+ // and a refusal must not be reported as a measured liveness. Pre-probe, observedLiveness null.
127
+ if (nativePushPaused(identity.backend)) {
128
+ return reject(makeRejectReceipt("native-push-paused", null));
129
+ }
124
130
  const probe = await deps.nativePushProbe(identity);
125
131
  const receipt = resolveNativePushDispatch(input.intent, probe.status);
126
132
  if (!receipt.ok)
@@ -74,6 +74,9 @@ export function actionableRejectHint(reason) {
74
74
  // Native host up, but nothing served this conversation/thread — inconclusive, not a
75
75
  // hard dead. Backend-neutral for the same reason as the dead cell above.
76
76
  return "the native host (harness process or app-server) is up, but nothing served this conversation/thread, so the probe is INCONCLUSIVE — not a measured death. Retry once it is loaded, or verify the conversation/thread id.";
77
+ case "native-push-paused":
78
+ // #152: a policy refusal, not a liveness verdict — this dispatch probed and sent nothing.
79
+ return "native-push for this backend is paused (#152): its language server refused entwurf's measured external calls for a missing CSRF token, so this dispatch probed and sent nothing. The citizen and its record are kept; reach it from inside its own harness instead.";
77
80
  case "record-less-socket":
78
81
  // #50 C4: name the true cause AND the fix — a bare socket is a diagnostic
79
82
  // state, not an addressable citizen (the record is the address).