@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.
- package/AGENTS.md +1 -1
- package/BASELINE.md +7 -5
- package/CHANGELOG.md +185 -0
- package/DELIVERY.md +37 -20
- package/README.md +70 -65
- package/VERIFY.md +9 -9
- package/docs/external-mcp-host.md +1 -1
- package/docs/setup-clean-host.md +59 -33
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +14 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +10 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +23 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +21 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +4 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +5 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +6 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +13 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +25 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +94 -28
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +57 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +2 -2
- package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +12 -12
- package/mcp/entwurf-bridge/src/index.ts +14 -11
- package/native/win32-x64/entwurf_win.node +0 -0
- package/native/win32-x64/manifest.json +2 -2
- package/package.json +4 -4
- package/pi-extensions/lib/entwurf-self-address.ts +13 -0
- package/pi-extensions/lib/entwurf-v2-contract.ts +25 -2
- package/pi-extensions/lib/entwurf-v2-decider.ts +7 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +3 -0
- package/pi-extensions/lib/fresh-call-composition.ts +21 -10
- package/pi-extensions/lib/herdr-fresh-call.ts +4 -2
- package/pi-extensions/lib/meta-sender-identity.ts +4 -0
- package/pi-extensions/lib/mux-fresh-call.ts +6 -4
- package/pi-extensions/lib/native-push/register.ts +17 -2
- package/pi-extensions/lib/omp-fresh-preflight.ts +2 -2
- package/pi-extensions/lib/pi-durable-fresh-preflight.ts +1 -1
- package/pi-extensions/lib/remote-pair-receive.ts +27 -19
- package/pi-extensions/lib/remote-pair-send.ts +117 -30
- package/pi-extensions/meta-bridge-omp.ts +69 -7
- package/pi-extensions/meta-bridge-pi-durable.ts +2 -2
- package/run.sh +173 -22
- package/scripts/check-agy-sender-identity.ts +6 -4
- package/scripts/check-entwurf-self-address.ts +34 -3
- package/scripts/check-entwurf-v2-contract.ts +24 -2
- package/scripts/check-entwurf-v2-production.ts +45 -31
- package/scripts/check-gate-qualification.ts +10 -8
- package/scripts/check-native-push-adapter.ts +4 -0
- package/scripts/check-native-push-register.ts +32 -82
- package/scripts/check-remote-pair-installed.ts +2 -2
- package/scripts/dev-bin.sh +112 -1
- package/scripts/lib/chain-terminus-publication.ts +58 -0
- package/scripts/lib/codex-fresh-live-protocol.ts +30 -0
- package/scripts/lib/codex-fresh-source-receipts.ts +112 -3
- package/scripts/lib/pi-durable-fresh-live-receipts.ts +29 -1
- package/scripts/mutants/chain-terminus-publication.json +17 -0
- package/scripts/mutants/codex-native.json +98 -0
- package/scripts/mutants/control-send-receipt.json +2 -2
- package/scripts/mutants/mux-fresh-call.json +16 -1
- package/scripts/mutants/native-push-paused.json +17 -0
- package/scripts/mutants/omp-fresh.json +13 -2
- package/scripts/mutants/pack-install.json +46 -0
- package/scripts/mutants/remote-pair-display.json +3 -3
- package/scripts/mutants/remote-pair-receive.json +41 -11
- package/scripts/mutants/remote-pair-roundtrip.json +67 -7
- package/scripts/mutants/remote-pair-send.json +156 -17
- package/scripts/mutants/remote-pair-transport.json +4 -4
- package/scripts/mutants/source-install.json +66 -0
- package/scripts/remote-pair-receive.ts +12 -12
- package/scripts/smoke-agy-install-state.sh +121 -0
- package/scripts/smoke-agy-native-push-live.ts +13 -0
- package/scripts/smoke-codex-fresh-live.ts +50 -27
- package/scripts/smoke-entwurf-chain-live.ts +7 -9
- package/scripts/smoke-herdr-fresh-call-live.ts +4 -4
- package/scripts/smoke-herdr-plugin-build-live.ts +179 -50
- package/scripts/smoke-mux-lifecycle-live.ts +140 -8
- package/scripts/smoke-omp-fresh-live.ts +5 -4
- package/scripts/smoke-pi-durable-fresh-live.ts +29 -7
- 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
|
|
10
|
-
> native
|
|
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
|
-
>
|
|
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 |
|
|
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
|
|
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
|
|
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
|
|
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
|
|
package/docs/setup-clean-host.md
CHANGED
|
@@ -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
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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.
|
|
87
|
-
set)
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
"
|
|
510
|
-
"
|
|
511
|
-
|
|
512
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
943
|
-
|
|
944
|
-
not a
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
620
|
-
"
|
|
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.
|
|
663
|
-
"
|
|
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
|
|
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)
|
|
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).
|