@junghanacs/entwurf 0.18.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +248 -0
  3. package/CONTRIBUTING.md +1 -1
  4. package/README.md +8 -6
  5. package/VERIFY.md +3 -3
  6. package/docs/external-mcp-host.md +2 -2
  7. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +18 -7
  8. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +76 -9
  9. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +54 -6
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resolve-tmux-session.js +114 -0
  11. package/mcp/entwurf-bridge/src/index.ts +22 -7
  12. package/package.json +1 -1
  13. package/pi-extensions/entwurf-control.ts +27 -3
  14. package/pi-extensions/lib/mux-fresh-call.ts +98 -11
  15. package/pi-extensions/lib/mux-placement.ts +65 -10
  16. package/pi-extensions/lib/resolve-tmux-session.ts +137 -0
  17. package/scripts/check-fresh-cut-gate.sh +54 -2
  18. package/scripts/check-gate-qualification.ts +5 -5
  19. package/scripts/check-mux-launch-tmux.ts +77 -2
  20. package/scripts/check-mux-launch.ts +17 -0
  21. package/scripts/check-mux-placement-tmux.ts +61 -1
  22. package/scripts/check-mux-placement.ts +33 -0
  23. package/scripts/check-release-gate-outcomes.ts +224 -9
  24. package/scripts/ci-qualify-decide.sh +159 -0
  25. package/scripts/fixtures/qualify-replay.json +149 -0
  26. package/scripts/mutants/fresh-cut.json +26 -0
  27. package/scripts/mutants/mux-boundary.json +24 -0
  28. package/scripts/mutants/mux-fresh-call.json +81 -0
  29. package/scripts/mutants/omp-birth.json +16 -3
  30. package/scripts/mutants/release-gate.json +45 -1
  31. package/scripts/omp-bridge-doctor.sh +11 -1
  32. package/scripts/smoke-mux-fresh-call-live.ts +175 -3
  33. package/scripts/smoke-omp-bridge-state.sh +5 -2
package/AGENTS.md CHANGED
@@ -30,7 +30,7 @@ For agents that own this repo: invariant principles and reproducible verificatio
30
30
  - **pi adapter** attaches a pi session to a record at `session_start`, hosts the record-keyed control socket, and exposes the native pi tool surface.
31
31
  - **Native bridges** register already-running native sessions without taking over their transcript or auth: Claude Code and Copilot CLI are mailbox/self-fetch (a Claude hook arms the watch; a forked Copilot extension holds it); Antigravity is probe-backed native-push. Codex has archived probe evidence, but its managed native lane was declined because pi already supplies the official GPT provider path; do not duplicate it as a native citizen or ACP backend.
32
32
  - **ACP plugin** registers provider `entwurf` inside a pi host session and drives a backend under an isolated overlay. The host pi session is already a record-backed socket citizen; the plugin does not mint another citizen/socket/peer layer.
33
- - **mux is launch-only and deliberately small.** Shipped: the tmux placement leaf (`mux-placement.ts` — inspect/append/close inside the caller's own session), the visible runtime launch composition (`mux-launch.ts`), the fresh-call composition (`mux-fresh-call.ts`), and the cwd-aware resume placement composition (`mux-resume-call.ts`). Fresh call is exposed as `entwurf_fresh_call`; visible same-id resume is `entwurf_resume_call`, composed at the two public surfaces by injecting `mux-resume-call` into the v2-side `entwurf-v2-visible-resume.ts`. The exact import graph is enumerated in [docs/mux-launch-rail.md](./docs/mux-launch-rail.md) §11. `entwurf_v2` behavior is unchanged and delivery still never imports launch. Fresh call learns a new sibling's garden id from the CALLBACK it makes, never from a lookup — so the pre-injected-token → identity-lookup design (rail §6) is **closed, not paused**. Do not reopen it without new evidence and explicit GLG re-approval. Fresh-call requires one explicit model and passes it in each runtime's measured CLI dialect (Pi `--model <provider/model>`, Claude Code `--model=<id-or-alias>`, Copilot `--model <name>` beside `--interactive <prompt>` and the explicit `--yolo` policy — a GLG width decision, 2026-08-25, after a callback-only `--allow-tool` grant stopped the sibling at every task tool); the Copilot backend opens through entwurf's OWN managed invocation (`entwurf copilot`), never the bare vendor, and its four required capabilities — birth, MCP hand, receiver, visible footer — are decided by `copilot-fresh-preflight.ts` BEFORE the tmux mutation, so a missing unit is a named refusal instead of a dead window; it also takes ONE optional literal absolute `cwd` (#73 — omitted/`""` means the caller's own directory; no trim, no realpath, no project-name resolver; classified by the shared `classify-tmux-cwd.ts` leaf; the receipt echoes the REQUEST only), so a cross-repo fresh sibling is a fresh-call fact and never a reason to resume a dormant record. Resume takes only an existing target id and gets transcript/model/provider/cwd from its record. Do not grow either narrow carrier into a generic driver, harness profile, arbitrary command/env selection, or a second creation API. Ownership and import prohibitions: [docs/mux-launch-rail.md](./docs/mux-launch-rail.md) §11.
33
+ - **mux is launch-only and deliberately small.** Shipped: the tmux placement leaf (`mux-placement.ts` — inspect/append in the caller's own session, close on its whole SERVER), the visible runtime launch composition (`mux-launch.ts`), the fresh-call composition (`mux-fresh-call.ts`), the cwd-aware resume placement composition (`mux-resume-call.ts`), and the import-free leaves they inject (`classify-tmux-cwd.ts`, `resolve-tmux-session.ts`). Fresh call is exposed as `entwurf_fresh_call`; visible same-id resume is `entwurf_resume_call`, composed at the two public surfaces by injecting `mux-resume-call` into the v2-side `entwurf-v2-visible-resume.ts`. The exact import graph is enumerated in [docs/mux-launch-rail.md](./docs/mux-launch-rail.md) §11. `entwurf_v2` behavior is unchanged and delivery still never imports launch. Fresh call learns a new sibling's garden id from the CALLBACK it makes, never from a lookup — so the pre-injected-token → identity-lookup design (rail §6) is **closed, not paused**. Do not reopen it without new evidence and explicit GLG re-approval. Fresh-call requires one explicit model and passes it in each runtime's measured CLI dialect (Pi `--model <provider/model>`, Claude Code `--model=<id-or-alias>`, Copilot `--model <name>` beside `--interactive <prompt>` and the explicit `--yolo` policy — a GLG width decision, 2026-08-25, after a callback-only `--allow-tool` grant stopped the sibling at every task tool); the Copilot backend opens through entwurf's OWN managed invocation (`entwurf copilot`), never the bare vendor, and its four required capabilities — birth, MCP hand, receiver, visible footer — are decided by `copilot-fresh-preflight.ts` BEFORE the tmux mutation, so a missing unit is a named refusal instead of a dead window; it also takes ONE optional literal absolute `cwd` (#73 — omitted/`""` means the caller's own directory; no trim, no realpath, no project-name resolver; classified by the shared `classify-tmux-cwd.ts` leaf; the receipt echoes the REQUEST only), so a cross-repo fresh sibling is a fresh-call fact and never a reason to resume a dormant record. It also takes ONE optional project seat, `placement.tmuxSession` (#105 — the exact name of an EXISTING session on the caller's OWN server, turned into a `$id` by `resolve-tmux-session.ts` before anything reaches `-t`). **It creates nothing** — `tmux-session-missing`/`tmux-session-name-invalid` are named refusals, no `ifMissing`/`new-session` axis exists (GLG, 2026-09-07), seat and cwd never infer each other, and close binds to the SERVER half since a placed window may sit outside the caller's session. Resume takes only an existing target id and gets transcript/model/provider/cwd from its record. Do not grow either narrow carrier into a generic driver, harness profile, arbitrary command/env selection, or a second creation API. Ownership and import prohibitions: [docs/mux-launch-rail.md](./docs/mux-launch-rail.md) §11.
34
34
  - **One delivery verb:** `entwurf_v2` addresses an existing garden id. Current routes are live control-socket send, active self-fetch mailbox enqueue, and probe-alive native-push. NO route starts a process: the hidden background resume for a dormant citizen was withdrawn under the visible-first rule, so a dormant in-domain citizen rejects as `dormant-fire-forget-unsupported` and the intent axis is single-valued. Every other complementary state pair rejects honestly too. Fresh creation is a separate verb — `entwurf_fresh_call` — and it returns a launch receipt only; the new sibling's address arrives asynchronously as the sender envelope of its nonce callback.
35
35
 
36
36
  ## Hard Rules
@@ -157,7 +157,7 @@ implement → affected focused gates → independent review → one amendment bu
157
157
 
158
158
  - **Inner loop:** run only the gates whose subject changed. Do not open the full floor to learn what a focused gate already answers.
159
159
  - **Review before floor:** independent review and its corrections close as one bundle before the full floor runs.
160
- - **Qualification is scheduled, not ambient — but its HEAD is not.** The mutant-EXECUTING body, `check-gate-qualification`, is not in the default check chains (core or full), so the operator inner loop never re-pays the full mutant inventory. It runs standalone once when a lane changed a gate, mutant, or matrix; machine time re-proves it everywhere else — the CI `check` job runs it on every branch push, and release-gate carries it as a MUST step. Its head — runner self-test, manifest-set validation, declared lane inventory — is `check-gate-manifests`, which executes no mutant and never snapshots the repo, and it IS in `check:hermetic`: three of the five reds qualification has ever produced in CI died there in under five seconds. A tag push runs no CI at all; the same SHA's branch run already carries every job, and that branch run is the exact-SHA evidence a release quotes.
160
+ - **Qualification is scheduled, not ambient — but its HEAD is not.** The mutant-EXECUTING body, `check-gate-qualification`, is not in the default check chains (core or full), so the operator inner loop never re-pays the full mutant inventory. It runs standalone once when a lane changed a gate, mutant, or matrix; machine time re-proves it everywhere else — the CI `check` job runs it on a branch push whose two-dot range touched the qualification surface (`scripts/ci-qualify-decide.sh` derives that path set from the manifests themselves), unconditionally on `workflow_dispatch -f qualify=true` and a weekly schedule, and release-gate carries it as a MUST step. The exact-SHA release oracle refuses a SHA whose body step did not conclude success, so a filtered-out release commit is a named failure with a documented dispatch recovery, never a quiet pass. Its head — runner self-test, manifest-set validation, declared lane inventory — is `check-gate-manifests`, which executes no mutant and never snapshots the repo, and it IS in `check:hermetic`: three of the five reds qualification has ever produced in CI died there in under five seconds. A tag push runs no CI at all, and the exact-SHA evidence a release quotes is whichever run at that commit carries the body concluding success — the branch run when the push moved the qualification surface, else the dispatch run the release skill's recovery creates.
161
161
  - **Full floor once** (`pnpm run check:full`), on the frozen commit candidate. While it runs, nothing edits the worktree or index — including the NEXT boot sectors; a moved candidate voids the run's evidence.
162
162
  - **pre-commit is not the floor.** `.husky/pre-commit` carries only fast static checks (whitespace, lint, typecheck); the full floor is owned by this protocol, not by the hook. Do not grow the hook back, and do not build receipt/cache machinery to prove the protocol was followed.
163
163
  - **Release/LIVE acceptance is untouched.** VERIFY.md floors keep full strength; a shorter inner loop never lowers release evidence.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,254 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.19.0 - 2026-09-07
8
+
9
+ One lane: #105 slice ①, the project seat. `entwurf_fresh_call` gains an optional
10
+ `placement.tmuxSession` so a fresh sibling can open in the operator's project session instead of the
11
+ caller's own, and `closeWindow` binds to the server half so a window placed elsewhere can still be
12
+ closed by its handle. Minor rather than patch: an optional input appears on all three public
13
+ surfaces at once and a shipped contract moves. Slices ② (no-turn retirement) and ③ (remote routing)
14
+ did not ship and were carried out of the issue intact before it closed, so nothing about them is
15
+ inferred from what landed here.
16
+
17
+ It also carries two repairs that were not planned: verifying this release ran the deterministic floor
18
+ often enough to expose a defect class the repo had already written down and not finished applying —
19
+ an early-exiting `grep -q` whose producer dies of SIGPIPE, which `pipefail` then promotes into the
20
+ CLEAN branch of an `if`. One instance was a shipped operator doctor; the other was a credential
21
+ tripwire that had been dead since it was written.
22
+
23
+ ### Added
24
+
25
+ - **One project seat: `placement: { tmuxSession }` (#105).** The exact name of an EXISTING session
26
+ on the caller's own tmux server. **It creates nothing** — an absent session is
27
+ `tmux-session-missing`, a name outside `[A-Za-z0-9][A-Za-z0-9_-]*` is `tmux-session-name-invalid`,
28
+ and both leave no window and no session behind. There is no `ifMissing`/`create` axis and no
29
+ `new-session` verb anywhere in the product (GLG, 2026-09-07: "없으면 reject … 다 자동화하면
30
+ 테스트·검증 비용이 커진다"). The operator makes the seat and calls again. Omitting `placement`
31
+ keeps today's behaviour exactly.
32
+ - **`pi-extensions/lib/resolve-tmux-session.ts`** — an import-free leaf owning the name grammar and
33
+ the name → native `$id` lookup, with its tmux runner injected so it cannot run tmux itself. The
34
+ engine is `list-windows -t '=NAME' -F '#{session_id}'` with absence read from the exit code, and
35
+ every rule in its header is a measured tmux 3.6a behaviour: the `-f` filter engine matched **every**
36
+ session when a name contained `}` (6/6), `display-message -p -t '=NAME'` answers rc=0 with empty
37
+ output for a name that does not exist, `#` is format-expanded when the name is stored, `.` and `:`
38
+ are tmux's own pane/window separators inside a target, and a session NAMED `$0` loses to the id
39
+ `$0`. Only the resolved id ever reaches `-t`.
40
+ - **Nine kill-qualified claims and their exact-once replants, inventory 373 → 382:**
41
+ `TMUXSESSION-LOOKUP-ENGINE`, `TMUXSESSION-NAME-GRAMMAR`, `RESOLVE-TMUX-SESSION-IMPORT-FREE`,
42
+ `FRESHCALL-PLACEMENT-MISSING-REJECTS`, `FRESHCALL-PLACEMENT-TARGET-ARGV`,
43
+ `FRESHCALL-PLACEMENT-RECEIPT-TARGET`, `FRESHCALL-PLACEMENT-SURFACE-PARITY`,
44
+ `FRESHCALL-PLACEMENT-BACKEND-PARITY` and `MUX-CLOSE-SERVER-BOUND`.
45
+
46
+ ### Changed
47
+
48
+ - **`closeWindow` binds to the SERVER half (`requireSameServer`), not the session.** A placed window
49
+ may legitimately live outside the caller's session, so the old handle↔now context check would have
50
+ refused a normal close. The session half was measured to add no protection and real harm: window
51
+ ids are never reused within one server's lifetime (`@1` killed → next is `@2`; a whole session
52
+ killed → the counter still moves), so a vanished session's handle already reaches the existing
53
+ `already-gone` outcome, and a session clause would have turned that into a throw. `isSameContext` /
54
+ `requireSameContext` are untouched and still guard `appendWindow` / `launchPi`.
55
+ - **The seat is stated the same way on every surface an operator reads.** A documentation review
56
+ (pi `grok-4.6`) read README, the skill and the tool schema as three independent readers and found
57
+ five places where each was correct alone but a caller reading only one got the seat wrong — the
58
+ README's v2 section did not mention the seat at all, `docs/external-mcp-host.md` advertised the
59
+ verb as `{backend, model, task, cwd?}`, "an absent one is refused" read equally as an absent
60
+ session and an omitted placement, and the grammar lived only inside a schema `describe`. The most
61
+ expensive misreading had no positive statement anywhere: **naming the `org` seat does not put the
62
+ sibling in the `org` directory.** That is now said as a fact, not as a prohibition.
63
+ - **A symlinked `cwd` shows two spellings of one place, and the docs now say so.** entwurf never
64
+ resolves the path — it hands the literal string to tmux — so the launch receipt echoes what was
65
+ requested while the record, the transcript path, the callback envelope and `pane_current_path` all
66
+ carry what the OS resolved it to. The resolved one is what `entwurf_resume_call` later reopens,
67
+ which is why recording it that way is load-bearing rather than incidental.
68
+
69
+ ### Fixed
70
+
71
+ - **`entwurf doctor-omp-bridge` could report a contaminated omp process as clean.** Its carrier scan
72
+ read `/proc/<pid>/environ` through `tr '\0' '\n' | grep -qE …` under `set -o pipefail`. `grep -q`
73
+ exits at the FIRST match and closes the pipe; `tr` — still writing the rest of a ~12 KB environ —
74
+ dies of SIGPIPE (141); `pipefail` makes that the pipeline's status, and the `if` takes the clean
75
+ branch. So a live omp session whose MCP children would speak under a PARENT pi citizen's garden id
76
+ was reported as no contamination, and the doctor printed PASS. It is a race on how far `tr` got, so
77
+ it read as flakiness rather than a defect: measured on one live fixture at 5–15% of reads depending
78
+ on load (6/40 and 1/40 in two independent measurements; 0/40 with `pipefail` off, and the shipped
79
+ doctor answered PASS on 3 of 60 runs against a genuinely contaminated fixture). The scan now counts
80
+ with `grep -c`, which consumes all of stdin, so there is no early close and nothing to race. The
81
+ predicate itself is unchanged byte-for-byte — `grep -qz` would have removed the pipeline entirely
82
+ but also quietly changes what counts as a carrier in both directions (a `PI_SESSION_ID=` inside
83
+ another variable's value; a value containing a newline), so transport was fixed and contract left
84
+ alone. `smoke-omp-bridge-state`'s own `wait_carrier` carried the same shape, where it cost only a
85
+ retry, and was fixed with it.
86
+ - **The `sync_auth` credential tripwire had never been able to fire (`check-fresh-cut-gate` D4b).**
87
+ Same cause, deterministic rather than racy: the producer is `awk` over a 433 KB `run.sh` emitting
88
+ ~229 KB, so an early-exiting `grep -q` guarantees SIGPIPE. Measured with one `sync_auth` line
89
+ injected as code into a copy of `run.sh`: the pipeline exited 141 on **100 of 100** runs and the
90
+ gate printed `ok — no sync_auth credential surface remains`, while a clean `run.sh` exits 1 and is
91
+ correctly green. The tripwire was blind exactly when it had something to catch, which is why a
92
+ green floor never revealed it. After the repair the injected copy fires 50/50 and the clean copy
93
+ stays silent. D7 — the cell that forbids an install from cutting a generation by itself — carried
94
+ the same shape and is under one pipe buffer today, so it had no SIGPIPE window; that is a size
95
+ coincidence rather than a property, and it was counted too.
96
+
97
+ ### Verification
98
+
99
+ All on oracle (Linux, node 24.18.1, pi 0.85.1, tmux 3.6a).
100
+
101
+ - **`LIVE=1 ./run.sh release-gate /tmp/entwurf-release-gate-0.19.0.0HsnIq --cut` → `cut: OK`.**
102
+ **MUST: PASS=23 FAIL=0 SKIP=0**, BEHAVIOR: PASS=1 FAIL=0 SKIP=0, exit 0, 06:06:31→07:05:45
103
+ (59m14s). `check-gate-qualification` as its MUST step: **385/385 KILLED**. `pnpm run check:full`
104
+ inside it: exit 0, 507s.
105
+ - **`check-gate-qualification` 381/381 KILLED** on the frozen `a39b637` candidate;
106
+ `pnpm run check:full` exit 0 (554s).
107
+ - **`LIVE=1 ./run.sh smoke-mux-fresh-call-live` 39/39** — four launches covering the
108
+ {cwd omitted, cwd requested} × {caller session, seat} matrix, reading `pane_current_path` by stable
109
+ pane id after a settle delay, with the seat session's own `session_path` a fourth directory no cell
110
+ may land in. On-demand, not a release MUST (VERIFY.md).
111
+ - **`check-mux-launch-tmux` 36** including a real-tmux seat cell that calls production
112
+ `freshCall`/`closeWindow` against a second session on a private server; `check-mux-fresh-call`
113
+ 186/186; `check-gate-manifests` 383 mutants across 40 lanes.
114
+ - **For the two repairs above:** `smoke-omp-bridge-state` 20 consecutive green runs (it was 1 red in
115
+ 5 before) and `check-fresh-cut-gate` 169/169 with its two new self-test cells. Three replants,
116
+ inventory 383 → 385, each KILLED 3/3 deterministically at its own signature:
117
+ `OMP-DOCTOR-CARRIER-NONBLANK-IS-RED`, `FRESHCUT-SYNCAUTH-TRIPWIRE-FIRES` and
118
+ `FRESHCUT-PREFLIGHT-CUT-DETECTED`. The middle one replants the ACTUAL defect — `grep -c` back to
119
+ `grep -q` — and that is only a valid replant because the self-test plants its surface early in a
120
+ file far larger than the pipe buffer, which makes the SIGPIPE deterministic. The omp one cannot be
121
+ written that way: there the same restoration fails only ~15% of runs and would SURVIVE
122
+ qualification, so it makes the identical false-clean deterministic by another route. Same defect,
123
+ two replant strategies, and the difference is recorded in the manifests rather than smoothed over.
124
+
125
+ ### Notes
126
+
127
+ - **The seat's first pass over the real MCP wire was the operator's, not a gate's.** Both mux LIVE
128
+ smokes were blind to that seam by construction: `smoke-mux-fresh-call-live` imports `freshCall` and
129
+ calls it directly, and `smoke-mux-lifecycle-live` does enter through `tools/call` but passes no
130
+ `placement`. Between them the bridge handler's pass-through was proven by a tools/list schema and a
131
+ source regex — by construction, never by execution. GLG's hand-driven review on 2026-09-07 closed
132
+ that: four `tools/call entwurf_fresh_call` invocations (seat, seat + cwd, absent session, off-grammar
133
+ name), receipts read as receipts, with the whole-server `list-windows -a` byte-identical across both
134
+ refusals and exactly one added line across the successes.
135
+ - **Every seat assertion this repo owned was written against `pi`.** The unit block, the real-tmux
136
+ seat cell and both LIVE seat cells all passed `backend: "pi"`, so a later branch making the seat a
137
+ pi-only input would have gone unnoticed — while the cwd axis (#73) had carried four-backend parity
138
+ since it landed. The production path was already uniform (the seat becomes `targetSessionId`, the
139
+ backend becomes `backendArgs`, and `buildFreshCallArgs` takes them as separate parameters); what was
140
+ missing was an oracle saying so. `FRESHCALL-PLACEMENT-BACKEND-PARITY` is that oracle, and
141
+ `check-gate-manifests` caught the omission that came with it — a manifest set that drifted from its
142
+ declared inventory is red before any mutant runs.
143
+ - **Two ceilings are close enough to name.** The composed ACP first-user augment sits **311 bytes**
144
+ under its 50 KB cap (measured: 50,889 / 51,200 with the shipped repo AGENTS.md plus a 12 KB global
145
+ baseline), and the MCP `entwurf_fresh_call` description sits **28 chars** under the 2048-char host
146
+ cap (measured: 2020). The first already bit once during this lane: the initial qualification run
147
+ went CONTROL-RED on `check-acp-carrier-augment` because the feature's +1207 B to AGENTS.md breached
148
+ a cap that had 932 B of headroom, and the text was compressed to +616 B rather than the cap moved.
149
+ Neither number is a gate threshold to tune; both are room that the next lane has to spend
150
+ deliberately.
151
+ - **Independent review is where the second defect came from.** A `zai/glm-5.3` sibling was asked to
152
+ review the doctor repair and to try to disprove it, reproducing everything in an isolated snapshot
153
+ rather than trusting the implementer's summary. It confirmed the mechanism, corrected two claims —
154
+ the measured miss rate is load-dependent rather than a fixed 15%, and Copilot's immunity comes from
155
+ a TypeScript `readFileSync` reader rather than the Python one that was asserted — rejected the
156
+ `grep -qz` alternative on evidence the implementer did not have, and then found the dead credential
157
+ tripwire, which is the more severe of the two and was in a different lane entirely.
158
+ - **A forbidding cell that is green on a clean tree cannot tell blindness from compliance.** That is
159
+ why D4b was dead for as long as it was: it printed `ok` on every run this repo ever took, and it
160
+ would have printed `ok` on the run that mattered. Both cells now carry a self-test that plants the
161
+ thing they forbid and requires the SAME predicate — factored into a function precisely so a
162
+ re-typed copy cannot keep passing while the real one goes blind — to see it. The `sync_auth` plant
163
+ goes near the TOP of the real 433 KB `run.sh` rather than into a small synthetic fixture, because
164
+ the defect only exists when the consumer can exit while the producer still has bulk to write; a
165
+ tidy fixture would pass under the very mutant that must kill it.
166
+ - **A MUST gate can fail with the wiring intact.** `smoke-mux-lifecycle-live` is release-blocking on
167
+ the stated ground that a gate which TELLS the model which tool to call owns its own failures. This
168
+ cut met the case that rule does not describe: the wiring delivered perfectly — launch receipt, a
169
+ born citizen with its own garden id, the mandated first-action prompt verbatim on the visible pane —
170
+ and the model spent its one turn advising the operator to go to bed instead of calling back. Twice,
171
+ at 00:00 and 01:04, with the same cause and different wording, then green on the first daytime run.
172
+ Nothing was changed to make it pass: adjusting a shipped prompt to a current model's habit would
173
+ outlive the habit. Recorded because "MUST because failure is ours" now has a measured exception.
174
+ - **The long-command discipline earned its receipt.** The harness low-memory watchdog killed a
175
+ background tool call mid-cut while `free` reported 7 GB available and the kernel logged no OOM —
176
+ the lead recorded in 0.18.0's notes, met directly on a release lane for the first time. The gate
177
+ itself, running under tmux, was untouched every time. "Long commands go in tmux" stops being a
178
+ preference at that point.
179
+ - **An agent asking tmux where it is will answer wrong from a seat.** A seated sibling is opened
180
+ detached, so `tmux display-message -p` without `-t "$TMUX_PANE"` resolves to the target session's
181
+ ACTIVE window, not the sibling's own — measured, while the carrier itself was correct
182
+ (`TMUX_PANE=%181` in the process env). entwurf's own facts are unaffected: the launch receipt and
183
+ `list-windows` both name the right window. This is an agent-instruction gap and is tracked outside
184
+ this repo (junghan0611/sorge#7).
185
+
186
+ ## 0.18.2 - 2026-09-07
187
+
188
+ One lane: #103, the second stage of the CI evidence budget (#99). The mutant body — 373 replants,
189
+ ~30 minutes — stops running on every branch push, and the release oracle stops accepting a run that
190
+ did not carry it. Nothing in the runtime surface moves.
191
+
192
+ ### Changed
193
+
194
+ - **The exact-SHA release oracle requires the qualification BODY, not three green job names (#103
195
+ piece 1).** `verify-exact-ci.sh` reads a fourth axis over one guard: the `check` job's
196
+ `Run ./run.sh check-gate-qualification` step must conclude `success`, with absent, skipped and
197
+ failed each named separately in the refusal. It also stops filtering to `--event push`, so a
198
+ `workflow_dispatch` run at the same SHA is admissible evidence — which is exactly when the
199
+ always-present `headSha` check starts carrying load, because `gh workflow run --ref` takes a
200
+ branch and never a SHA. A skipped body is not evidence: the oracle refuses that SHA and prints the
201
+ dispatch command that fixes it. This landed BEFORE the skip could exist.
202
+ - **The body runs only on a branch push that moved the qualification surface (#103 piece 2).**
203
+ `scripts/ci-qualify-decide.sh` diffs the two-dot push range GitHub itself compares and intersects
204
+ it with a path set **derived from `scripts/mutants/*.json` at runtime** — no copy in YAML, so a new
205
+ mutant subject cannot land outside the filter. Five numbered fail-opens, each naming itself in the
206
+ log: an all-zero base, a forced push (whose base GitHub does not document), a dispatch without
207
+ `qualify=true` or a schedule, an unreadable range, and `pull_request`. `workflow_dispatch -f
208
+ qualify=true` and a weekly schedule run the body unconditionally; `fetch-depth: 0` on the check job
209
+ is what keeps the base readable, and it measured free (2s checkout, the same as depth 1).
210
+ - **Measured on real runs, not argued.** A code push ran the body and took 36m24s
211
+ ([34047559085](https://github.com/junghan0611/entwurf/actions/runs/34047559085)); a docs-only push
212
+ skipped it and took 6m13s
213
+ ([34065841309](https://github.com/junghan0611/entwurf/actions/runs/34065841309)); the four-axis
214
+ oracle **refused** that second SHA while all three of its jobs were green, and the documented
215
+ dispatch recovery turned the refusal back into a pass
216
+ ([34066181211](https://github.com/junghan0611/entwurf/actions/runs/34066181211)). The historical
217
+ justification is replayed rather than remembered: all five qualification reds this repo's CI has
218
+ ever produced still run the body under the filter.
219
+
220
+ ### Added
221
+
222
+ - Three kill-qualified claims and their exact-once replants, inventory 370 → 373:
223
+ `RELEASE-SHA-QUALIFIED-IN-CI` (the fourth axis), `QUALIFY-FILTER-COVERS-SUBJECTS` (every manifest
224
+ subject and signatureSource decides `run_body=true`, asserted behaviourally against the shipped
225
+ script), and `QUALIFY-FILTER-READS-PUSH-RANGE` (a hermetic throwaway repo with a docs-only tip over
226
+ a commit that touched a subject — the two-dot reading, proven without repo history).
227
+ `QUALIFY-FILTER-REPLAYS-PAST-CATCHES` replays the five reds from recorded file lists.
228
+ - `scripts/fixtures/qualify-replay.json` — the five historical reds as the push ranges GitHub
229
+ compared, measured once here because history is not readable everywhere the gate runs.
230
+
231
+ ### Verification
232
+
233
+ All on oracle (Linux, node 24.18.1, pi 0.85.1, omp 18.1.12 — updated from 18.0.0 during this lane).
234
+
235
+ - **`LIVE=1 ./run.sh release-gate /tmp/entwurf-release-gate-0.18.2.JlEU88 --cut` → `cut: OK`.**
236
+ **MUST: PASS=23 FAIL=0 SKIP=0**, BEHAVIOR: PASS=1 FAIL=0 SKIP=0, exit 0, 3305s (55m05s).
237
+ `check-gate-qualification` as its MUST step: **373/373 KILLED**.
238
+ - `pnpm run check:full` on the prepared tree: exit 0, 504s.
239
+ - Pre-version landing run for `0f6667d`: CI run
240
+ [34070192960](https://github.com/junghan0611/entwurf/actions/runs/34070192960), all three jobs
241
+ success — and this is the first release landing judged by the fourth axis the same release adds:
242
+ the oracle read that run's qualification step, not just its job names.
243
+
244
+ ### Notes
245
+
246
+ - **A gate that reads repo history cannot live in the qualification snapshot.** The replay cell first
247
+ read live git objects and went CONTROL-RED inside the snapshot's fresh baseline, costing the whole
248
+ release-gate lane its 17 kills. That is why the fixture carries file lists and the range semantics
249
+ are proven hermetically instead.
250
+ - **Key order in `ci.yml` is a contract.** The body step keeps `run:` as its leading key: cell 8a
251
+ counts that sequence-item form exactly once, and the oracle matches GitHub's rendered
252
+ `Run <command>` step name, so an `if:` placed first — or any `name:` — would break both. Confirmed
253
+ on a real run after the filter landed.
254
+
7
255
  ## 0.18.1 - 2026-09-06
8
256
 
9
257
  Two lanes, both landing on `main` after `v0.18.0`: the CI evidence-budget stage 1 (#102, from
package/CONTRIBUTING.md CHANGED
@@ -30,7 +30,7 @@ pnpm check # everyday core (prints wall time; <=60s on the referenc
30
30
  pnpm run check:full # full deterministic floor — the required PR gate
31
31
  ```
32
32
 
33
- The deterministic floor is tiered (#70). `pnpm check` is the everyday core (biome, tsc, the vitest lanes, and the fast contract gates); `pnpm run check:full` adds the hermetic-integration and package/install tiers — including `check-gate-manifests`, the qualification HEAD (runner self-test, manifest-set validation, declared lane inventory, zero mutants executed, ~8s), but not the separately scheduled mutant-EXECUTING body `check-gate-qualification`, which CI runs on every branch push and a gate-changing PR must run once itself. Exact membership is the named `check:*` scripts in `package.json`. Run `check:full` once on your frozen commit candidate — the pre-commit hook (`.husky/pre-commit`) carries only fast static checks (whitespace, lint, typecheck), not the full floor, so a green `pnpm run check:full` before commit is the evidence that your change holds (scheduling contract: AGENTS.md "Verification scheduling").
33
+ The deterministic floor is tiered (#70). `pnpm check` is the everyday core (biome, tsc, the vitest lanes, and the fast contract gates); `pnpm run check:full` adds the hermetic-integration and package/install tiers — including `check-gate-manifests`, the qualification HEAD (runner self-test, manifest-set validation, declared lane inventory, zero mutants executed, ~8s), but not the separately scheduled mutant-EXECUTING body `check-gate-qualification`, which CI runs on a branch push whose diff touched the qualification surface (and unconditionally on dispatch or the weekly schedule) and a gate-changing PR must run once itself. Exact membership is the named `check:*` scripts in `package.json`. Run `check:full` once on your frozen commit candidate — the pre-commit hook (`.husky/pre-commit`) carries only fast static checks (whitespace, lint, typecheck), not the full floor, so a green `pnpm run check:full` before commit is the evidence that your change holds (scheduling contract: AGENTS.md "Verification scheduling").
34
34
 
35
35
  For changes that touch backend launch, session lifecycle, or `_meta` shape, also run
36
36
  the live ACP smokes that cover the touched rail — at minimum:
package/README.md CHANGED
@@ -19,7 +19,7 @@ Claude Code / Copilot / Codex / agy / omp / pi
19
19
  → control-socket | meta-mailbox | native-push
20
20
  ```
21
21
 
22
- [`entwurf_v2`](#entwurf_v2--canonical-dispatch-verb) is the canonical dispatch surface over *existing* garden citizens — live control-socket send, meta-mailbox enqueue, and native-push into a live Antigravity conversation. It starts no process on any rail: the hidden background resume that used to answer a dormant target was withdrawn under the visible-first rule, so a dormant citizen rejects honestly here and is reopened by a separate lifecycle verb, `entwurf_resume_call`. The meta-record is the sole address authority (#50 C4): a record-less control socket is refused as a `record-less-socket` diagnostic, never dispatched. The v1 entwurf verbs are gone. Fresh siblings and resumes are separate verbs — `entwurf_fresh_call` opens a NEW sibling in the operator's own tmux session and learns its garden id from the callback it makes, while `entwurf_resume_call` reopens a DORMANT pi citizen under its own garden id in a visible window without running a turn; the non-Claude ACP lane landed earlier — Snowflake Cortex Code became the second backend in 0.13.0.
22
+ [`entwurf_v2`](#entwurf_v2--canonical-dispatch-verb) is the canonical dispatch surface over *existing* garden citizens — live control-socket send, meta-mailbox enqueue, and native-push into a live Antigravity conversation. It starts no process on any rail: the hidden background resume that used to answer a dormant target was withdrawn under the visible-first rule, so a dormant citizen rejects honestly here and is reopened by a separate lifecycle verb, `entwurf_resume_call`. The meta-record is the sole address authority (#50 C4): a record-less control socket is refused as a `record-less-socket` diagnostic, never dispatched. The v1 entwurf verbs are gone. Fresh siblings and resumes are separate verbs — `entwurf_fresh_call` opens a NEW sibling in the operator's tmux — the caller's own session, or one existing named session on the same server — and learns its garden id from the callback it makes, while `entwurf_resume_call` reopens a DORMANT pi citizen under its own garden id in a visible window without running a turn; the non-Claude ACP lane landed earlier — Snowflake Cortex Code became the second backend in 0.13.0.
23
23
 
24
24
  **Garden id is deliberate vocabulary.** It is not a decorative synonym for session id, worker, delegate, or subagent. The unfamiliar word is a guard: each harness keeps its own identity and transcript, while `entwurf` supplies a narrow addressable surface between siblings.
25
25
 
@@ -454,9 +454,11 @@ LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live # Co
454
454
  `pnpm run check:full` includes the AGY permission contract matrix and the qualification
455
455
  HEAD (`check-gate-manifests` — runner self-test, manifest-set validation, declared lane
456
456
  inventory, zero mutants executed); the committed-mutant EXECUTION is scheduled separately
457
- (`./run.sh check-gate-qualification` — the CI `check` job runs it on every branch push,
458
- and release-gate carries it as a MUST step; a tag push runs no CI, since the same SHA's
459
- branch run already carries every job). A gate a
457
+ (`./run.sh check-gate-qualification` — the CI `check` job runs it on a branch push that touched the qualification surface,
458
+ unconditionally on `workflow_dispatch -f qualify=true` and a weekly schedule, and release-gate
459
+ carries it as a MUST step; a tag push runs no CI at all). A branch run whose body was filtered out
460
+ is not release evidence: the exact-SHA oracle requires the run it reads to carry that step
461
+ concluding success. A gate a
460
462
  release touches must kill its known defect for the claimed `[QK:<claim>]` reason —
461
463
  the descriptions above name what each smoke covers, and no check count is quality
462
464
  evidence on its own. Gate qualification needs the git work surface, while the matrix
@@ -511,7 +513,7 @@ A two-pane recording covers the pre-0.12 v1 surface end-to-end — sibling resum
511
513
 
512
514
  Live peer messaging carries a sender envelope `{ sessionId, agentId, cwd, timestamp, origin?, replyable? }`; `entwurf_self` returns that authoritative envelope for the current pi session or trusted meta-session. Plain external MCP hosts are non-replyable. A garden-native meta-session carries a trusted `meta-session` envelope, but **`replyable` is a fact its own rail decides, not a consequence of being trusted** — a self-fetch citizen needs a live armed receiver, a native-push citizen needs an alive adapter probe, and a pi session needs its control socket. `entwurf_self` also reports which rail a meta-session reply would ride, because a native-push citizen has no mailbox to name. `wants_reply` is an etiquette marker rendered as a `(wants reply)` badge — not a transport contract, no wait, no polling. **v2 never gates on it:** a `wants_reply` from an external/non-replyable caller is passed through and surfaced honestly beside that sender's `replyable: false`, not rejected — the decider routes on target + intent, never on sender replyability. (The retired v1 `entwurf_send` did reject it; that behaviour went with the verb.)
513
515
 
514
- In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes seven tools: `entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_self`, `entwurf_inbox_read`, and the explicit/manual `entwurf_register_native` fallback. Native pi exposes the shared capability directly through the extension surface (`entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call` tools; the socket-scan `/entwurf-sessions` command is gone — #50 C4). **For garden-id delivery/reply use `entwurf_v2`** — the canonical surface that classifies the target and routes to live-pi / self-fetch meta-mailbox (Claude Code, Copilot, OMP) / Antigravity-native-push, and refuses a dormant target instead of waking it. **To open a sibling that does not exist yet use `entwurf_fresh_call`** — it launches one fixed backend (`pi`, `claude-code`, `copilot`, or `omp`) as a visible window in the operator's own tmux session, requires an explicit model, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd), passes the model and selected directory through the runtime's visible launch path, and hands it a first task; a `copilot` launch goes through entwurf's own managed invocation and is refused before any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units, and an `omp` launch carries its task in the `--entwurf-bootstrap` payload instead of an argv prompt because that vendor connects its MCP tools after the session has already started. The sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model/cwd plus tmux coordinates and never claims that the runtime accepted them or completed delivery. (The v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.) Garden-native operator commands require `--entwurf-control`. There is no spawn target allowlist — the target registry is gone (#50 C3): `entwurf_v2` and `entwurf_resume_call` address an existing record-backed citizen, while `entwurf_fresh_call` takes its explicit backend/model/task and optional cwd directly rather than resolving a model tuple from a registry file. **To reopen a DORMANT pi citizen use `entwurf_resume_call {target}`** — the record supplies transcript, model, provider and cwd, so it takes no prompt, no task and no model override; it runs no turn, returns a LAUNCH receipt and a separate OBSERVATION receipt (only the second says the citizen is back), refuses a non-pi target as `target-not-pi`, and on an unobserved socket leaves the visible window open and releases its lock rather than retrying.
516
+ In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes seven tools: `entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_self`, `entwurf_inbox_read`, and the explicit/manual `entwurf_register_native` fallback. Native pi exposes the shared capability directly through the extension surface (`entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call` tools; the socket-scan `/entwurf-sessions` command is gone — #50 C4). **For garden-id delivery/reply use `entwurf_v2`** — the canonical surface that classifies the target and routes to live-pi / self-fetch meta-mailbox (Claude Code, Copilot, OMP) / Antigravity-native-push, and refuses a dormant target instead of waking it. **To open a sibling that does not exist yet use `entwurf_fresh_call`** — it launches one fixed backend (`pi`, `claude-code`, `copilot`, or `omp`) as a visible window on the operator's own tmux server, requires an explicit model, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd) and one optional `placement.tmuxSession` naming an EXISTING session on that server to open the sibling in instead of the caller's own (the operator's project seat — nothing is ever created: an absent session is `tmux-session-missing` and a name outside the addressed grammar `[A-Za-z0-9][A-Za-z0-9_-]*` is `tmux-session-name-invalid`, with no window and no session left behind. The two inputs never infer each other, and the positive form of that matters more than the prohibition: **with `cwd` omitted, a seated sibling starts in THIS agent's own working directory**, measured — it does not inherit the target session's path or its active pane, so naming the `org` seat does not put the sibling in the `org` project. Pass `cwd` when you want a different directory. entwurf never resolves that path: it hands the literal string to tmux, so with a SYMLINKED directory the receipt echoes what you asked for while the record, the transcript path, the callback envelope and `pane_current_path` all show the path the OS resolved it to — two spellings of one place, and the resolved one is what `entwurf_resume_call` later reopens), passes the model and selected directory through the runtime's visible launch path, and hands it a first task; a `copilot` launch goes through entwurf's own managed invocation and is refused before any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units, and an `omp` launch carries its task in the `--entwurf-bootstrap` payload instead of an argv prompt because that vendor connects its MCP tools after the session has already started. The sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model/cwd/seat plus tmux coordinates — including the RESOLVED target session id, which is where the window actually is — and never claims that the runtime accepted them or completed delivery. (The v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.) Garden-native operator commands require `--entwurf-control`. There is no spawn target allowlist — the target registry is gone (#50 C3): `entwurf_v2` and `entwurf_resume_call` address an existing record-backed citizen, while `entwurf_fresh_call` takes its explicit backend/model/task and optional cwd directly rather than resolving a model tuple from a registry file. **To reopen a DORMANT pi citizen use `entwurf_resume_call {target}`** — the record supplies transcript, model, provider and cwd, so it takes no prompt, no task and no model override; it runs no turn, returns a LAUNCH receipt and a separate OBSERVATION receipt (only the second says the citizen is back), refuses a non-pi target as `target-not-pi`, and on an unobserved socket leaves the visible window open and releases its lock rather than retrying.
515
517
 
516
518
  ### `entwurf_v2` — canonical dispatch verb
517
519
 
@@ -530,7 +532,7 @@ In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes sev
530
532
 
531
533
  **`entwurf_v2` is the canonical surface for garden-id delivery.** When you have a garden id and want to reach whoever it names — message, reply, or hand-off — `entwurf_v2` is the one surface that reads whether the target is live pi, dormant pi, mailbox-backed Claude Code, Copilot or OMP, or native-push Antigravity and routes correctly; *when unsure which transport, use `entwurf_v2`*. This prevents callers from guessing a rail from the shape of an id.
532
534
 
533
- What v2 provides is a **deterministic dispatch substrate** that moves the "which transport?" decision out of the fallible caller/model and into the decider, with transport-appropriate locking and an honest reject (no `✓ delivered`, no `.msg` garbage) when a target cannot receive. It still does **not** mint siblings, and it does not relaunch one either: every row above either reaches a citizen that is already running or refuses. Reopening a dormant pi citizen is `entwurf_resume_call`, a separate lifecycle verb that never routes through this decider. Fresh creation is the separate `entwurf_fresh_call` verb. It opens one fixed Pi, Claude Code, Copilot or OMP runtime visibly in the caller's tmux session with a required explicit model passed in that runtime's measured CLI dialect and one optional literal absolute `cwd`; omitted or `""` means the caller's cwd. Copilot opens through `entwurf copilot`, never the bare vendor; OMP is the opposite — the bare `omp` runtime with no positional prompt at all, because that vendor connects its MCP tools after the session has started, so the task rides a two-stage `--entwurf-bootstrap` payload the installed birth extension releases once the callback tool has actually answered. It returns only a synchronous launch receipt and lets the sibling report its new address asynchronously through the sender envelope of a nonce callback. Use this cwd input for a new cross-repository sibling; do not resume a dormant citizen as a cwd substitute. The meta-mailbox row requires an **active** self-fetch receiver; native-push requires a record-backed, probe-alive native conversation and never borrows mailbox state. The [mux launch lane](./docs/mux-launch-rail.md) owns placement, fixed-runtime launch, and the two narrow compositions above it (fresh-call and resume-call placement); delivery does not import launch, and mux is not a delivery transport.
535
+ What v2 provides is a **deterministic dispatch substrate** that moves the "which transport?" decision out of the fallible caller/model and into the decider, with transport-appropriate locking and an honest reject (no `✓ delivered`, no `.msg` garbage) when a target cannot receive. It still does **not** mint siblings, and it does not relaunch one either: every row above either reaches a citizen that is already running or refuses. Reopening a dormant pi citizen is `entwurf_resume_call`, a separate lifecycle verb that never routes through this decider. Fresh creation is the separate `entwurf_fresh_call` verb. It opens one fixed Pi, Claude Code, Copilot or OMP runtime visibly on the caller's tmux server — its own session by default, or one EXISTING named session via the optional `placement.tmuxSession` — with a required explicit model passed in that runtime's measured CLI dialect and one optional literal absolute `cwd`; omitted or `""` means the caller's cwd. Copilot opens through `entwurf copilot`, never the bare vendor; OMP is the opposite — the bare `omp` runtime with no positional prompt at all, because that vendor connects its MCP tools after the session has started, so the task rides a two-stage `--entwurf-bootstrap` payload the installed birth extension releases once the callback tool has actually answered. It returns only a synchronous launch receipt and lets the sibling report its new address asynchronously through the sender envelope of a nonce callback. Use this cwd input for a new cross-repository sibling; do not resume a dormant citizen as a cwd substitute. The meta-mailbox row requires an **active** self-fetch receiver; native-push requires a record-backed, probe-alive native conversation and never borrows mailbox state. The [mux launch lane](./docs/mux-launch-rail.md) owns placement, fixed-runtime launch, and the two narrow compositions above it (fresh-call and resume-call placement); delivery does not import launch, and mux is not a delivery transport.
534
536
 
535
537
  A live pi target is *reached* over its control socket, but the socket is dispatch-internal transport, never identity (#50 C4). A control socket that no meta-record claims — a pre-record-era resident, an unreadable store, or a stale/planted file — is refused for **every** intent as `record-less-socket`, and the reject names the fix (restart the resident so `session_start` births its record, or quiesce and run the fresh-cut). `entwurf_peers` reports the same state as an aggregated `record-less-socket` diagnostic rather than a peer row.
536
538
 
package/VERIFY.md CHANGED
@@ -41,7 +41,7 @@ Verification here is not a benchmark. In production we exchange short turns and
41
41
  ### The canonical floor — two entry points
42
42
 
43
43
  - **Deterministic floor — two tiers (#70):** `pnpm check` is the everyday CORE — toolchain (lint + typecheck), the vitest lanes, and the pure-unit / behavioral-contract / source-topology gates plus the cheap static coherence checks. It prints its own total wall time; acceptance is ≤60s on the reference host `oracle` (an operator measurement, never a hard wall-clock gate on arbitrary hosts). `pnpm run check:full` is the FULL deterministic floor — the core plus the hermetic-integration and package/install tiers — and is what the frozen-candidate protocol, push CI, release-gate, and `prepublishOnly` run. Exact membership is the named `check:*` group scripts in `package.json` (the executable SSOT — this document records meaning and principles, not the command list); a gate changes tier by semantic-class decision, never by getting faster or slower. The FULL tier carries the qualification HEAD (`check-gate-manifests`, through `check:hermetic`); the everyday core does not, and neither tier carries the separately scheduled mutant-executing body (`check-gate-qualification`).
44
- - **Discriminating power of that floor:** `./run.sh check-gate-qualification` (scheduled, not ambient: standalone when a lane changes a gate/mutant/matrix, in the CI `check` job on every branch push, and as a release-gate MUST step — no longer inside the default check chains; a semver TAG push triggers no CI at all, because the same SHA's branch run already carries every job and is the exact-SHA evidence a release quotes) re-plants committed defect mutants (`scripts/mutants/*.json`, one per closed defect class) in an isolated snapshot repo and requires each to turn its gate red **bounded and at its claimed `[QK:<claim>]` signature** — a wrong-reason red fails, a baseline-red control voids the whole group, and the runner is negative-controlled on every run (zero-match/multi-match/survived/wrong-reason/hang/control-red/impurity). This measures whether the deterministic gates still *block* what they claim to block; it is **not a new evidence level** (L0–L5 are untouched) and never substitutes for LIVE evidence. Per-cut records cite claim IDs + killed mutant IDs — "N checks passed" alone is not evidence. `check-agy-permission-matrix` complements it with the enumerated permission contract space (literal cells + stated exclusion rules, oracle independent of the SUT). The gate's HEAD is separable and IS in the floor: `check-gate-manifests` runs the runner self-test, validates the committed manifest set against the origin index, and asserts the declared lane inventory, while executing zero mutants and never snapshotting the repo — three of the five reds qualification has ever produced in CI died in that head, in under five seconds.
44
+ - **Discriminating power of that floor:** `./run.sh check-gate-qualification` (scheduled, not ambient: standalone when a lane changes a gate/mutant/matrix, in the CI `check` job on a branch push that touched the qualification surface, and as a release-gate MUST step — no longer inside the default check chains; a semver TAG push triggers no CI at all, and the exact-SHA evidence a release quotes is whichever run at that commit CARRIES the body — the branch run when the push touched the qualification surface, else a dispatch run; a push that touched none of the qualification surface skips the body, and `workflow_dispatch -f qualify=true` plus a weekly `schedule` run it unconditionally — the exact-SHA release oracle refuses a SHA whose body step did not conclude success) re-plants committed defect mutants (`scripts/mutants/*.json`, one per closed defect class) in an isolated snapshot repo and requires each to turn its gate red **bounded and at its claimed `[QK:<claim>]` signature** — a wrong-reason red fails, a baseline-red control voids the whole group, and the runner is negative-controlled on every run (zero-match/multi-match/survived/wrong-reason/hang/control-red/impurity). This measures whether the deterministic gates still *block* what they claim to block; it is **not a new evidence level** (L0–L5 are untouched) and never substitutes for LIVE evidence. Per-cut records cite claim IDs + killed mutant IDs — "N checks passed" alone is not evidence. `check-agy-permission-matrix` complements it with the enumerated permission contract space (literal cells + stated exclusion rules, oracle independent of the SUT). The gate's HEAD is separable and IS in the floor: `check-gate-manifests` runs the runner self-test, validates the committed manifest set against the origin index, and asserts the declared lane inventory, while executing zero mutants and never snapshotting the repo — three of the five reds qualification has ever produced in CI died in that head, in under five seconds.
45
45
  - **Live floor:** `LIVE=1 ./run.sh release-gate <scratch-project-dir> --cut` — `pnpm run check:full` + the v2-native live gates + the ACP plugin acceptance floor. It reports a **two-tier summary**:
46
46
  - **MUST tier** (release-blocking — owns the exit code; "green" applies only here): `pnpm run check:full`, `check-gate-qualification`, `smoke-entwurf-v2-matrix-live`, `check-bridge`, `doctor-pi-provider` (#81 — `check-bridge` proves the launcher this checkout SHIPS; this proves the invocation the operator's pi provider actually EXECS, by booting it and requiring the entwurf verb set back, because `command -v` answers yes for a command that exits 127; wired as a step on 2026-08-19 after a relocated pnpm cmd-shim cost a cut sixteen LIVE steps before the same fact surfaced at `smoke-acp-bundled-mcp-live`), the resident-garden-guard zero-token half (record birth / record-keyed socket / attach-on-reopen), the `smoke-acp-*-live` ACP plugin smokes (socket-citizen / raw-turn / overlay / provider / session-reuse / carrier-augment / memory-containment / rgg / mcp / skill / bundled-mcp / v2-send), the axis wired in on 2026-07-31 that the aggregate had simply never listed (`smoke-claude-native-resume-live`), `smoke-entwurf-chain-live` — the cross-harness delivery chain (native Claude Code → pi GPT → pi ACP Sonnet → mailbox terminus) proving sender identity and replyability at every hop plus a real read receipt at the end — the integrated `smoke-mux-lifecycle-live` (its own note below), and the two OMP steps 0.16.0 wired: `smoke-omp-receive-live` (the addressed roundtrip into a live omp citizen; it reads the capability registry and decides its own outcome rather than passing by default) and `smoke-omp-fresh-live` (the clause 7 receipt the release stop now requires). (`smoke-session-id-name` is gone — #50 C3: its `--session-id`/`--name` substrate has no entwurf consumer anymore.)
47
47
  - **BEHAVIOR tier** (advisory, non-blocking): the resident-garden-guard positive (a model-in-loop `entwurf_self` turn). A BEHAVIOR FAIL is surfaced with its artifact path but **never blocks the cut**. The lane holds what the model *chooses*, never what our wiring fails to deliver — a gate that TELLS the model which tool to call stays MUST, because its failure is ours — measured 2026-07-24, when the tool turned out to be absent from the session schema in both observed failures (the bundled-MCP readiness gap recorded in `scripts/smoke-acp-v2-send-live.ts`).
@@ -58,7 +58,7 @@ Verification here is not a benchmark. In production we exchange short turns and
58
58
  >
59
59
  > Be honest about its cost, because it is a MUST: it spends real model turns on the operator's configured runtimes (two pi siblings, each resumed once, plus one Claude Code sibling), it READS the operator's real runtime config, and the siblings' native session transcripts stay in the real pi agent dir — they are not cleaned up, and they are part of the evidence a resume was real. The fence is per-axis rather than absolute: the four meta roots (records, mailbox, receivers, senders) are fixture for every cell, so no garden record is ever minted outside it; the native pi cell's control socket and v2 lock are fixture too; the ACP pi cell's are REAL and transient (see the paragraph above); and the Claude cell runs its runtime under the real HOME while its mailbox and meta stay fixture. The run ends by proving the operator's SIX real roots — the four meta roots plus `~/.pi/entwurf-control` and `~/.pi/entwurf-v2-locks` — hold the entry sets they started with, with no fixture garden id anywhere, and that proof now runs on the failure path as well as the green one. Missing prerequisites decline with the protocol SKIP (97), which `release-gate --cut` refuses rather than rounding up.
60
60
  >
61
- > **Fresh-call LIVE is on-demand, not part of `release-gate`.** `LIVE=1 ./run.sh smoke-mux-fresh-call-live` opens configured Pi and Claude Code siblings, spends two model turns, and preserves their native transcripts; run it directly whenever a cut changes mux fresh-call/launch behavior. Its private tmux servers and fixture-bound entwurf write axes make it deterministic about garden-record/socket residue, but those real native turns should not appear unexpectedly in every aggregate cut. **Both this smoke and `smoke-mux-lifecycle-live` enumerate `pi` and `claude-code` only — that exclusion is intentional.** A Copilot visible-fresh (step 9 clause 7) was accepted on 2026-08-25 as an operator-metered LIVE (receipts in `DELIVERY.md`'s Copilot matrix row and the issue #82 thread). It still spends Copilot premium quota, still needs its own grant to re-run, and is never folded into those loops or into the release-gate MUST tier.
61
+ > **Fresh-call LIVE is on-demand, not part of `release-gate`.** `LIVE=1 ./run.sh smoke-mux-fresh-call-live` opens configured Pi and Claude Code siblings, spends four model turns (two in the caller's own session, two in a second SEAT session on the same private server — the #105 seat x cwd matrix), and preserves their native transcripts; run it directly whenever a cut changes mux fresh-call/launch behavior. Its private tmux servers and fixture-bound entwurf write axes make it deterministic about garden-record/socket residue, but those real native turns should not appear unexpectedly in every aggregate cut. **Both this smoke and `smoke-mux-lifecycle-live` enumerate `pi` and `claude-code` only — that exclusion is intentional.** A Copilot visible-fresh (step 9 clause 7) was accepted on 2026-08-25 as an operator-metered LIVE (receipts in `DELIVERY.md`'s Copilot matrix row and the issue #82 thread). It still spends Copilot premium quota, still needs its own grant to re-run, and is never folded into those loops or into the release-gate MUST tier.
62
62
 
63
63
  > **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.
64
64
  >
@@ -76,7 +76,7 @@ Do not collapse source, package, fixture, and native-host evidence into one “g
76
76
  | Source | `pnpm run check:full` + `./run.sh check-gate-qualification` | Does not prove an installed consumer. |
77
77
  | Packed install | `check-pack-install` | Real tarball, but checkout-visible. |
78
78
  | Linux artifact consumer | required `check-install-container` CI job against one preserved candidate | Fixtures prove package/oracle shape, not a real Claude lifecycle. |
79
- | Exact release commit | all required CI jobs green at the exact SHA | A different green SHA is not transferable evidence. |
79
+ | Exact release commit | all required CI jobs green, and the `check` job's qualification body step concluded success, at the exact SHA | A different green SHA is not transferable evidence. |
80
80
  | 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. |
81
81
  | Native Claude host | installed strict doctor against a new real session | Missing live join is `NOT CERTIFIED`, not a fixture PASS. |
82
82
  | Native agy host | three doctors plus conversation-id-gated native-push round trip | Aggregate release-gate does not own an agy conversation id. |
@@ -11,7 +11,7 @@ the one-line registration; everything an operator needs to actually wire a host
11
11
 
12
12
  **Being garden-native is not the same as being replyable, and Copilot is the case that separates them.** Its hook writes a sender marker, so an `entwurf_v2` send carries its own garden id and the receiver learns who wrote — measured 2026-08-21 on Copilot CLI 1.0.80, where a live send arrived under its own garden id with `origin: "meta-session"` and `replyable: false`. Replyability arrived later and through a different process: a first-party extension (`run.sh install-copilot-receive`) that the CLI forks, which binds to the same V3 record and writes a receiver marker owned by its own pid (#82 RAIL 5). So a Copilot citizen is `replyable: true` exactly while that extension is armed for it, and `replyable: false` — honestly, with a real garden identity — when it is not installed, not launched with `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` (which is what `entwurf copilot` sets for one invocation), not yet born, or gone. Read the two facts off different rails: the sender marker answers *who sent this*; the receiver marker answers *can a reply land*. The onboarding obligations are in [`adding-a-harness.md`](./adding-a-harness.md) step 7, and the evidence boundary — the managed LIVE acceptance that moved receive to D6, and what D7-partial / D3-pending still exclude — is in [`DELIVERY.md`](../DELIVERY.md).
13
13
 
14
- **Which verb an external agent should reach for:** to deliver to / reply to a garden id, use **`entwurf_v2`** — it is the canonical delivery surface and the only one that reads whether the target is live pi, dormant pi, a mailbox-backed self-fetch citizen (Claude Code, Copilot, OMP), or native-push Antigravity and routes correctly. Discover targets with `entwurf_peers`, confirm your own identity with `entwurf_self`, drain a mailbox with `entwurf_inbox_read`, and use `entwurf_register_native` only as the explicit/manual fallback for binding an already-running agy conversation (normal agy birth is automatic through the installed hook). Open a NEW sibling with **`entwurf_fresh_call {backend, model, task, cwd?}`**, and reopen a DORMANT pi citizen under its own garden id with **`entwurf_resume_call {target}`**. Fresh call accepts one optional literal absolute `cwd`: omit it or pass `""` for the caller's cwd; otherwise it must name an existing directory and may not contain `#`. Use that input for a new cross-repository sibling — resume preserves a dormant Pi citizen's recorded continuity and is not a cwd substitute. Fresh call needs its selected runtime on the server's PATH (`pi`, `claude`, `entwurf` or `omp` — Copilot opens through the managed `entwurf copilot` invocation, never the bare vendor, while OMP is opened as the bare `omp` runtime); resume call always needs `pi`. Both start a runtime, while delivery does not. (The old v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.)
14
+ **Which verb an external agent should reach for:** to deliver to / reply to a garden id, use **`entwurf_v2`** — it is the canonical delivery surface and the only one that reads whether the target is live pi, dormant pi, a mailbox-backed self-fetch citizen (Claude Code, Copilot, OMP), or native-push Antigravity and routes correctly. Discover targets with `entwurf_peers`, confirm your own identity with `entwurf_self`, drain a mailbox with `entwurf_inbox_read`, and use `entwurf_register_native` only as the explicit/manual fallback for binding an already-running agy conversation (normal agy birth is automatic through the installed hook). Open a NEW sibling with **`entwurf_fresh_call {backend, model, task, cwd?, placement?}`**, and reopen a DORMANT pi citizen under its own garden id with **`entwurf_resume_call {target}`**. Fresh call accepts one optional literal absolute `cwd`: omit it or pass `""` for the caller's cwd; otherwise it must name an existing directory and may not contain `#`. Use that input for a new cross-repository sibling — resume preserves a dormant Pi citizen's recorded continuity and is not a cwd substitute. Fresh call also accepts one optional `placement.tmuxSession`: the exact name of an EXISTING session on the caller's own tmux server, so the sibling opens in the operator's project seat rather than beside the caller. It creates nothing — an absent session is refused as `tmux-session-missing`, a name outside `[A-Za-z0-9][A-Za-z0-9_-]*` as `tmux-session-name-invalid`, and no window and no session are left behind either way. The seat and the `cwd` never infer each other: with `cwd` omitted a seated sibling starts in the CALLING agent's own directory, not the target session's path. Fresh call needs its selected runtime on the server's PATH (`pi`, `claude`, `entwurf` or `omp` — Copilot opens through the managed `entwurf copilot` invocation, never the bare vendor, while OMP is opened as the bare `omp` runtime); resume call always needs `pi`. Both start a runtime, while delivery does not. (The old v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.)
15
15
 
16
16
  Observed: Claude Code, Codex CLI, Antigravity CLI, GitHub Copilot CLI and OMP all reach the read surfaces through this MCP bridge from a plain external host — `entwurf_peers` is a pure fact projection, while `entwurf_inbox_read` is a **mutating drain** (it archives the messages and stamps the read-receipt), so "read" here does not mean side-effect-free; **sending** needs an identity lane. Claude, Copilot and OMP become symmetric/replyable through a mailbox-backed meta-session — Claude's watch armed by its own hook, Copilot's by the forked extension it installs, OMP's by an extension running inside the operator's own TUI process; agy becomes symmetric/replyable through its record-backed sender marker plus live native-push probe. Codex has no managed citizen lifecycle yet, so a Codex host cannot send without the explicit anonymous hatch below.
17
17
 
@@ -151,7 +151,7 @@ entwurf doctor-agy-hooks
151
151
 
152
152
  The three adapters deliberately own different atoms:
153
153
 
154
- - bridge: one `entwurf-bridge` server in `~/.gemini/config/mcp_config.json`, plus one narrow permission string per tool the normal agy workflow calls — `mcp(entwurf-bridge/entwurf_v2)`, `mcp(entwurf-bridge/entwurf_peers)`, `mcp(entwurf-bridge/entwurf_self)` — in `~/.gemini/antigravity-cli/settings.json`. agy defaults every `mcp` action to Ask, so a tool that ships without its own rule stops for a y/n on every call; `entwurf_inbox_read` is deliberately not granted (native-push has no inbox), neither is the manual `entwurf_register_native` fallback, and neither are `entwurf_fresh_call` / `entwurf_resume_call` (both launch into the caller's own tmux session, which an agy conversation is not);
154
+ - bridge: one `entwurf-bridge` server in `~/.gemini/config/mcp_config.json`, plus one narrow permission string per tool the normal agy workflow calls — `mcp(entwurf-bridge/entwurf_v2)`, `mcp(entwurf-bridge/entwurf_peers)`, `mcp(entwurf-bridge/entwurf_self)` — in `~/.gemini/antigravity-cli/settings.json`. agy defaults every `mcp` action to Ask, so a tool that ships without its own rule stops for a y/n on every call; `entwurf_inbox_read` is deliberately not granted (native-push has no inbox), neither is the manual `entwurf_register_native` fallback, and neither are `entwurf_fresh_call` / `entwurf_resume_call` (both launch into the caller's own tmux server, which an agy conversation has none of);
155
155
  - statusline: the complete `statusLine` subtree pointing at the bare stable bin `entwurf-agy-statusline`;
156
156
  - hooks: one named `PreInvocation` hook pointing at the bare stable bin `entwurf-agy-imprint`.
157
157
 
@@ -26,10 +26,11 @@
26
26
  * this read is the receipt.
27
27
  * - entwurf_register_native — explicit/manual fallback binding an ALREADY-RUNNING native
28
28
  * conversation (antigravity) to a garden id. Never a spawn.
29
- * - entwurf_fresh_call — open ONE fresh visible sibling in the operator's own tmux session,
30
- * optionally at ONE literal requested cwd (cross-repo fresh, #73);
31
- * returns a LAUNCH receipt only, and the new address arrives later as the
32
- * sender envelope of the sibling's nonce callback.
29
+ * - entwurf_fresh_call — open ONE fresh visible sibling in the operator's tmux — the caller's own
30
+ * session, or ONE EXISTING named session on the same server (the project
31
+ * seat, #105; nothing is ever created), optionally at ONE literal requested
32
+ * cwd (cross-repo fresh, #73); returns a LAUNCH receipt only, and the new
33
+ * address arrives later as the sender envelope of the sibling's nonce callback.
33
34
  * - entwurf_resume_call — reopen ONE DORMANT pi citizen under its OWN garden id in a visible
34
35
  * window; target-only, runs no turn, LAUNCH and OBSERVATION receipts stay
35
36
  * apart.
@@ -609,7 +610,7 @@ server.tool("entwurf_register_native", "Register an ALREADY-RUNNING native conve
609
610
  // its own garden id, a fresh cell answered with its uuidv7 `PI_SESSION_ID` value read out of the
610
611
  // environment by an MCP server it had spawned itself — confidently, and wrong. A sibling launched
611
612
  // against that answer would call home to a garden id nobody holds.
612
- server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operator's own tmux session and hand it a first task. Four fixed " +
613
+ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operator's tmux and hand it a first task. Four fixed " +
613
614
  "backends only: pi, claude-code, copilot, omp. The sibling's FIRST action is a callback to you carrying a nonce, and the " +
614
615
  "sender envelope of that callback is its garden id — that is how you learn the address of something that " +
615
616
  "did not exist a moment ago. This returns a LAUNCH receipt (tmux window/pane plus that nonce) and nothing " +
@@ -624,6 +625,8 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operato
624
625
  "prompt, so the sibling could not call you back at all). An optional " +
625
626
  "cwd starts the sibling in ONE literal absolute existing directory (cross-repo fresh) — never pick resume " +
626
627
  "for a dormant record's cwd; resume is continuity-only. Omitted/empty cwd means the caller's own directory. " +
628
+ "An optional placement.tmuxSession opens it in ONE EXISTING session of this agent's own tmux server; an " +
629
+ "absent SESSION is tmux-session-missing and NOTHING is created. Omit placement for the caller's own session. " +
627
630
  "There are no arbitrary command/env knobs. Do not put secrets in the task — model and task argv are visible to " +
628
631
  "same-user processes on this host. Requires that this agent itself runs " +
629
632
  "inside tmux: without a pane anchor there is no session to open a sibling beside.", {
@@ -653,7 +656,15 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operato
653
656
  .string()
654
657
  .optional()
655
658
  .describe("Optional literal ABSOLUTE path of an existing directory to start the sibling in (cross-repo fresh). Omit or pass \"\" to start in this agent's own cwd. Taken exactly as given — no trim, no realpath, no project-name resolution; '#' is refused (tmux format expansion). The receipt echoes what was REQUESTED, never an observation."),
656
- }, async ({ backend, model, task, cwd }) => {
659
+ placement: z
660
+ .object({
661
+ tmuxSession: z
662
+ .string()
663
+ .describe("EXACT name of an EXISTING session on this agent's own tmux server. Nothing is created: an absent session is refused as tmux-session-missing, and a name outside [A-Za-z0-9][A-Za-z0-9_-]* as tmux-session-name-invalid."),
664
+ })
665
+ .optional()
666
+ .describe("Optional project seat: open the sibling in ONE EXISTING tmux session of this agent's own server instead of the caller's session. Nothing is ever created — an absent session is a refusal, not a new session. Independent of cwd; neither is inferred from the other. The receipt echoes the REQUESTED name and reports the resolved target session id."),
667
+ }, async ({ backend, model, task, cwd, placement }) => {
657
668
  let callerGardenId = null;
658
669
  try {
659
670
  const self = await buildAuthoritativeSelfEnvelope();
@@ -672,7 +683,7 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operato
672
683
  callerGardenId = null;
673
684
  }
674
685
  try {
675
- const rendered = renderFreshCall(freshCall({ backend, model, task, cwd, callerGardenId }));
686
+ const rendered = renderFreshCall(freshCall({ backend, model, task, cwd, placement, callerGardenId }));
676
687
  return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
677
688
  }
678
689
  catch (err) {