@junghanacs/entwurf 0.50.0 → 0.52.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 (39) hide show
  1. package/AGENTS.md +2 -1
  2. package/CHANGELOG.md +140 -0
  3. package/ENTWURF.json +10 -4
  4. package/README.md +16 -8
  5. package/VERIFY.md +16 -9
  6. package/demo/README.md +1 -1
  7. package/docs/acp-backend-rail.md +1 -1
  8. package/docs/external-mcp-host.md +2 -2
  9. package/docs/setup-clean-host.md +92 -35
  10. package/mcp/entwurf-bridge/dist/ENTWURF.json +10 -4
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +4 -1
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +4 -4
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +1 -1
  14. package/package.json +11 -18
  15. package/pi/pi-durable/carrier/harness-setup.js +1 -1
  16. package/pi/pi-durable/carrier/runtime.js +221 -50
  17. package/pi/pi-durable/carrier/subagent.js +6 -3
  18. package/pi/pi-durable/carrier/tui.js +60 -22
  19. package/pi/pi-durable/overlay/upstream-pin.json +19 -18
  20. package/pi-extensions/lib/entwurf-control-rpc.ts +5 -1
  21. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +73 -69
  22. package/pi-extensions/lib/entwurf-v2-contract.ts +4 -4
  23. package/pi-extensions/lib/meta-mailbox-body.ts +1 -1
  24. package/pi-extensions/meta-bridge-pi-durable.ts +1 -1
  25. package/run.sh +148 -46
  26. package/scripts/check-entwurf-v2-contract.ts +4 -4
  27. package/scripts/check-entwurf-v2-surface.ts +7 -21
  28. package/scripts/check-gate-qualification.ts +3 -3
  29. package/scripts/check-pi-durable-send.ts +4 -1
  30. package/scripts/lib/codex-fresh-source-receipts.ts +12 -6
  31. package/scripts/mutants/agent-guidance.json +2 -2
  32. package/scripts/mutants/codex-native.json +2 -2
  33. package/scripts/mutants/control-send-receipt.json +47 -7
  34. package/scripts/mutants/pack-install.json +4 -4
  35. package/scripts/mutants/pi-durable.json +189 -3
  36. package/scripts/mutants/pi-floor.json +20 -5
  37. package/scripts/mutants/v2-surface.json +4 -6
  38. package/scripts/smoke-acp-cortex-live.ts +1 -1
  39. package/scripts/smoke-acp-v2-send-live.ts +1 -1
@@ -23,9 +23,11 @@ evidence split, and its three states must not collapse into "macOS is supported"
23
23
 
24
24
  ## Requirements
25
25
 
26
- This table is the published `@junghanacs/entwurf@0.45.0`'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.
26
+ The requirements table and pinned npm examples describe published Entwurf 0.51.0, whose exact SDK set is 1.5.0. The unpublished 0.52.0 source candidate (#158) instead pins SDK 1.5.2 and declares host Pi `>=1.5.2 <1.6`. Source validation, publication and a running host's loaded bytes are separate evidence; an addon-less validation tarball has been installed on one operating host, with same-identity return observed for the operating coordinator; that bounded observation is not full release or all-rail acceptance (see the [#158 candidate boundary](#sdk-152-candidate-boundary-158)).
27
+
28
+ The validation TGZ installed in #155's operating window before the D1 display repair is an earlier, separate artifact, not the published one; see the [SDK 1.5 boundary](#sdk-15-development-boundary-155) below.
29
+
30
+ The previous package, `0.50.0`, ships the Pi 1.4.0 SDK set and requires a Pi 1.4.x host; the setup guide inside that tarball is its contract.
29
31
 
30
32
  | Component | Requirement | Needed for |
31
33
  |---|---|---|
@@ -33,8 +35,8 @@ inside that tarball is its contract.
33
35
  | npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
34
36
  | Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot/Codex wiring writers |
35
37
  | entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
36
- | pi | optional-by-presence, `>=1.4.0 <1.5` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
37
- | Native durable (`pi-durable`) | supplied by the Entwurf package (#130): Entwurf's owned app carrier plus the exact 1.4.0 pi SDK set as production dependencies — setup PASS when they verify, a named FAIL otherwise (never SKIP) | the `entwurf pi-durable` contact; native birth/LIVE on this supply surface is a separate release proof (§2b) |
38
+ | pi | optional-by-presence — published 0.51.0: <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->`>=1.5.0 <1.6`<!-- /entwurf:published -->; #158 source candidate: `>=1.5.2 <1.6`. Absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
39
+ | Native durable (`pi-durable`) | supplied by the Entwurf package (#130): Entwurf's owned app carrier plus its exact pi SDK set (1.5.0 in published 0.51.0; 1.5.2 in the #158 source candidate) as production dependencies — setup PASS when they verify, a named FAIL otherwise (never SKIP) | the `entwurf pi-durable` contact; native birth/LIVE on this supply surface is a separate release proof (§2b) |
38
40
  | Claude Code | optional, **`>=2.1.306`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
39
41
  | 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
42
  | 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) |
@@ -83,8 +85,8 @@ node --version
83
85
  npm --version
84
86
  ```
85
87
 
86
- The two npm forms below install the **published** package, `0.45.0` (Pi 1.4.x, the Pi 1.4.0 SDK
87
- set). The [product entry](../README.md#install)'s unpinned `npm install -g @junghanacs/entwurf`
88
+ The two npm forms below install the **published** package, `0.51.0` (Pi 1.5.x, the Pi 1.5.0 SDK
89
+ set); they do not install the #158 source candidate (SDK 1.5.2), which is not published — a developer runs that from a source checkout (below). The [product entry](../README.md#install)'s unpinned `npm install -g @junghanacs/entwurf`
88
90
  takes whichever version npm's `latest` dist-tag names; pin `@<version>` as below when you mean the
89
91
  exact artifact the [Requirements](#requirements) table describes.
90
92
 
@@ -92,7 +94,7 @@ Global install is simplest when native harnesses should find stable bins from ev
92
94
  working directory:
93
95
 
94
96
  ```bash
95
- npm install -g @junghanacs/entwurf@0.45.0
97
+ npm install -g @junghanacs/entwurf@0.51.0
96
98
  entwurf --help
97
99
  entwurf setup /path/to/your-project
98
100
  entwurf check-bridge
@@ -103,7 +105,7 @@ A project-local installation is also supported:
103
105
  ```bash
104
106
  mkdir -p ~/entwurf-smoke && cd ~/entwurf-smoke
105
107
  npm init -y
106
- npm install --save-dev @junghanacs/entwurf@0.45.0
108
+ npm install --save-dev @junghanacs/entwurf@0.51.0
107
109
  npx entwurf setup .
108
110
  npx entwurf check-bridge
109
111
  ```
@@ -113,7 +115,7 @@ project-local install, or `./run.sh <command>` from a source checkout. Keep that
113
115
  same scope when upgrading and checking; native process restarts are separate (§7).
114
116
 
115
117
  `check-bridge` is auth-free. It proves the installed prebuilt MCP server boots and
116
- lists the nine garden tools (0.45.0); it does not prove a backend model turn, native hook or retirement permission.
118
+ lists the nine garden tools (0.51.0); it does not prove a backend model turn, native hook or retirement permission.
117
119
 
118
120
  Neither npm form installs a harness runtime. `pi`, Claude Code, Copilot CLI, Codex CLI, agy,
119
121
  Cortex, and their authentication remain operator-owned optional prerequisites for the
@@ -224,12 +226,12 @@ physical-Darwin receipt. `entwurf install` may write `.pi/settings.json`
224
226
  on Darwin; that is wiring, not a rail receipt. If `pi` is on PATH,
225
227
  `setup` names the pi row FAIL with the §1 Darwin wording.
226
228
 
227
- Install the exact release floor, then wire the project. The previous 0.41.0 instead takes the
228
- Pi 1.3.x its own setup guide names. A source checkout passes its checkout path to `-e` in place of
229
- the global package root:
229
+ <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->
230
+ For published Entwurf 0.51.0, install its exact host Pi floor and wire the project as shown below.
231
+ The previous 0.50.0 uses the Pi 1.4.x host named in its own setup guide:
230
232
 
231
233
  ```bash
232
- npm install -g @earendil-works/pi-coding-agent@1.4.0
234
+ npm install -g @earendil-works/pi-coding-agent@1.5.0
233
235
  pi --version
234
236
 
235
237
  cd ~/entwurf-smoke
@@ -237,10 +239,13 @@ entwurf install .
237
239
  pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
238
240
  ```
239
241
 
240
- The supported range is `>=1.4.0 <1.5`. It is a hard minimum: installing this
241
- release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
242
- minor. A host using only the external MCP bridge can skip pi until it needs a
242
+ The supported host Pi range for published Entwurf 0.51.0 is `>=1.5.0 <1.6`.
243
+ A detected host outside that range is refused for Pi wiring; Entwurf does not upgrade it.
244
+ Align the host explicitly, for example with the exact npm command above, before wiring Pi. A host using only the external MCP bridge can skip pi until it needs a
243
245
  control socket; no delivery rail launches a pi process.
246
+ <!-- /entwurf:published -->
247
+
248
+ The #158 source candidate instead requires host Pi `>=1.5.2 <1.6`; checkout `setup` reports a detected older host as FAIL. Prepare host Pi 1.5.2 before wiring this checkout, and use the checkout path rather than the global npm package for a direct `pi -e` invocation. Host Pi 1.5.2 also falls within published 0.51.0's declared range, but #158 has not measured that pairing; range eligibility does not replace installation, validation or operating approval. Ordinary Pi 1.5.2 defaults its global `cacheWarming` setting to `idle`, which may send additional provider requests; pi-durable does not map that setting. See the [#158 durable boundary](./durable-native-support.md#sdk-152-candidate-boundary-158).
244
249
 
245
250
  For daily garden-native pi sessions:
246
251
 
@@ -259,7 +264,7 @@ A plain `pi` can load the package without becoming a citizen.
259
264
  In that session ask for `entwurf_self`, then `entwurf_peers`. Pick an existing
260
265
  live garden id and ask for `entwurf_v2` with `intent: "fire-and-forget"` and your
261
266
  message. These are harness tools, not shell subcommands; pi exposes them through
262
- Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The 0.45.0 bridge has nine tools;
267
+ Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The 0.51.0 bridge has nine tools;
263
268
  pi hides `entwurf_inbox_read` and `entwurf_register_native`, leaving seven active.
264
269
  The added `entwurf_retire_call` is only for one target/action explicitly requested by the
265
270
  human operator, never autonomous cleanup. Where a host provides or is configured with an approval
@@ -344,11 +349,12 @@ by `entwurf pi` or by registering ordinary Pi's adapter.
344
349
 
345
350
  What the Entwurf package supplies:
346
351
 
347
- 1. **The pi SDK set** — `@earendil-works/{pi-coding-agent, pi-ai, pi-tui, pi-agent-core,
348
- pi-durable, chord, pi-mcp, pi-codemode, pi-telemetry}` at exactly `1.4.0`, plus
349
- `proper-lockfile`, as Entwurf's own production dependencies (declared once in
350
- `pi/pi-durable/overlay/upstream-pin.json` `sdk`). Model data rides the published
351
- `pi-ai`; there is no catalog copy.
352
+ 1. **The pi SDK set** — published 0.51.0 supplies `@earendil-works/{pi-coding-agent, pi-ai, pi-tui, pi-agent-core,
353
+ pi-durable, chord, pi-mcp, pi-codemode, pi-telemetry, pi-schema}` at exactly `1.5.0`, plus
354
+ `proper-lockfile`, as Entwurf's own production dependencies; its model data rides <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->`@earendil-works/pi-ai@1.5.0`<!-- /entwurf:published -->, not a catalog copy.
355
+ The #158 source candidate supplies the same ten-member set at exactly `1.5.2` (its model data rides the 1.5.2 pi-ai). The
356
+ [SDK pin](../pi/pi-durable/overlay/upstream-pin.json) and [package manifest](../package.json) are the authority
357
+ for the checkout you hold: they describe the source candidate, not the immutable published 0.51.0 bytes.
352
358
  2. **The native app/TUI carrier** — upstream does not publish the experimental durable
353
359
  app (`pi-coding-agent` excludes `dist/experimental`; `pi-durable` on npm is the
354
360
  library). Entwurf owns the six app files as canonical source (`pi/pi-durable/src`,
@@ -358,9 +364,9 @@ What the Entwurf package supplies:
358
364
  (`pi/pi-durable/carrier-resolver.mjs`), which maps them to the published package's
359
365
  own `dist/` as installed — never through a source checkout.
360
366
 
361
- The upstream [library guide](https://github.com/earendil-works/pi/blob/v1.4.0/packages/durable/README.md)
362
- and the [experimental app guide](https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/src/experimental/durable/README.md)
363
- (last published at v1.1.0) describe what the carrier is. Running the bare upstream app does not attach Entwurf's
367
+ The upstream library guide for the published 0.51.0 pairing is [v1.5.0](https://github.com/earendil-works/pi/blob/v1.5.0/packages/durable/README.md); for the #158 candidate's SDK APIs use [v1.5.2](https://github.com/earendil-works/pi/blob/v1.5.2/packages/durable/README.md).
368
+ The [experimental app guide](https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/src/experimental/durable/README.md)
369
+ (last published at v1.1.0) records the app's origin: Entwurf's maintained app source, not a newer upstream app, is the current composition authority. Both guides describe what the carrier is. Running the bare upstream app does not attach Entwurf's
364
370
  garden contact. Entwurf does not rebuild the controller/model/auth/storage system or
365
371
  proxy a subscription; native execution, models, auth, queues, SQLite and recovery stay
366
372
  upstream-owned.
@@ -373,8 +379,7 @@ carrier file, the license, the resolver and its relocation grammar are present
373
379
  (`pi-durable-carrier-drift`); every declared SDK package resolves from the carrier
374
380
  (`pi-durable-sdk-absent`); and every reachable physical edge — from the carrier to each package,
375
381
  and from every package copy it reaches, at any depth, to its own `@earendil-works` dependencies —
376
- lands on a package of the declared name at exactly the pinned version (`pi-durable-sdk-mismatch`). The members declare one another with caret
377
- ranges, so semver satisfaction is not the contract; the bytes an import binds are. Sharing
382
+ lands on a package of the declared name at exactly the pinned version (`pi-durable-sdk-mismatch`). The members' declarations are exact in the published 1.5.0 set and, measured on the registry tarballs (#158), in the 1.5.2 candidate set; even so, the physical package each import binds must be checked. Sharing
378
383
  one copy with an aligned host install of the same version is an ordinary layout. A mixed
379
384
  tree (for example an override that binds pi-durable's `pi-ai` to an older published version) still
380
385
  *imports* — which is why the edge check exists — and is refused by name.
@@ -399,7 +404,12 @@ tree (for example an override that binds pi-durable's `pi-ai` to an older publis
399
404
  `--session` takes the native session id (`<13-digit ms>-<uuid>`, as `--resume` lists it), not a
400
405
  garden id, with no interactive session picker or picker-specific TTY check (the normal TUI still
401
406
  runs); a value of any other shape is refused as `session-id-malformed` before your module is
402
- evaluated, the TUI loads or the app opens.
407
+ evaluated, the TUI loads or the app opens. To retain an operator module on a CLI reopen, pass
408
+ its original explicit `--native-module <absolute file>` or `--native-module-dir <absolute directory>`
409
+ again; garden-id `entwurf_resume_call` is bridge-only and does not restore either ingress or
410
+ the prior seat. It opens on the caller's launch rail; check for an existing owner before opening
411
+ another instance. Close the old owner normally and prove its exit before reopening; for an SDK transition, back up
412
+ the complete durable SQLite file set before the first write ([upgrade boundary](#default-radius-for-a-requested-pi-sdk-upgrade)). Ordinary Pi transcripts are separate JSONL files.
403
413
 
404
414
  The bootstrap imports only the compiled closure shipped beside it
405
415
  (`mcp/entwurf-bridge/dist/…`); a source checkout needs a built bridge
@@ -451,9 +461,9 @@ tree (for example an override that binds pi-durable's `pi-ai` to an older publis
451
461
  manifest field; it is a known constraint of supplying both from one package, accepted for 0.32,
452
462
  not a claim that every load path is harmless.
453
463
 
454
- The ordinary Pi CLI on PATH is a separate subject (§2, range `>=1.4.0 <1.5`): a host Pi is
464
+ The ordinary Pi CLI on PATH is a separate subject (§2; published 0.51.0 range <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->`>=1.5.0 <1.6`<!-- /entwurf:published -->, #158 source candidate range `>=1.5.2 <1.6`): a host Pi is
455
465
  not needed to run pi-durable, and a host inside the range need not be the same copy as the
456
- carrier's. Native Pi owns credentials, model settings and durable SQLite sessions; keep them
466
+ carrier's. The native harnesses own credentials, model settings and their respective session stores — ordinary Pi JSONL and pi-durable SQLite; keep them
457
467
  in place rather than copying credentials or migrating ordinary Pi transcripts. Native
458
468
  recovery does not prove exactly-once Entwurf dispatch or inbox read recovery. The contact
459
469
  provides garden attachment, visible identity and announce-only root-directed self-fetch.
@@ -462,9 +472,56 @@ to the app's own model resolution; the checkout contact/send/receive gates run t
462
472
  with scripted S plus native H; Pi ↔ pi-durable LIVE on this supply surface is a separate release
463
473
  proof ([#130](https://github.com/junghan0611/entwurf/issues/130)).
464
474
 
475
+ ### SDK 1.5 development boundary (#155)
476
+
477
+ The published-0.51.0 instructions above describe the SDK 1.5.0 set that #155 shipped; the #155 SDK
478
+ 1.5 development candidates that preceded it were distinct subjects. Synthetic migration and
479
+ downgrade-refusal fixtures on `0408a66` did not authorize an operating first write. A separately
480
+ approved 1.5 operating window and Native LIVE were later measured as described below; neither made
481
+ the validation TGZ the published artifact. Installing an older package is not a data rollback. Follow the current evidence in
482
+ [#155](https://github.com/junghan0611/entwurf/issues/155), not the 1.3→1.4 receipt below. The #158 source candidate is a separate boundary, next.
483
+
484
+ ### SDK 1.5.2 candidate boundary (#158)
485
+
486
+ The 0.52.0 source candidate (#158) pins the Pi SDK at exactly 1.5.2 (ten members) and declares host Pi `>=1.5.2 <1.6`. Assigning that version does not publish or install it: the published instructions above still describe 0.51.0 / SDK 1.5.0, and an operating move requires its own installation and same-identity return receipts. The #155 receipts above prove their own subject only; none is inherited by 1.5.2.
487
+
488
+ What #158 measured so far and what it did not is recorded in [the durable-native boundary](./durable-native-support.md#sdk-152-candidate-boundary-158) and in the #158 thread: registry tarball integrity and member edges, an isolated six-member scheduler comparison and a native SQLite cross-version read/append check. The pre-version checkpoint's frozen FULL belongs to its own recorded worktree. The earlier validation candidate has its own floor and installation receipts; later source edits and the final carrier-bearing artifact require their own acceptance. Operating-scale and interrupted sessions are not certified by the bounded fixtures. An operating move follows the default radius below, each step with its own receipt.
489
+
490
+ ### Default radius for a requested Pi SDK upgrade
491
+
492
+ **[Decision, 2026-11-03 (#155)](https://github.com/junghan0611/entwurf/issues/155#issuecomment-6595580400): reuse existing native authentication; do not make a fresh login or credential copy the default upgrade prerequisite.** An upgrade request selects this method without another authentication-strategy questionnaire. It does not authorize an immediate operating cutover, autonomous retirement, or publication.
493
+
494
+ The measured precedent is [#148's pre-cutover hold](https://github.com/junghan0611/entwurf/issues/148#issuecomment-6342034678) and [its actual Pi 1.4 operating reattachment](https://github.com/junghan0611/entwurf/issues/148#issuecomment-6348113761): isolated package/SDK checks first; no authentication-copy or mixed host/extension workaround; then normal owner exit, a consistent complete durable-session file-set backup, coherent candidate installation/takeover, same-identity reattachment and actual turns/round trips. Native aggregate acceptance followed the coherent installation. These are historical receipts, not evidence for the next SDK.
495
+
496
+ - **Proceed within the requested upgrade:** inspect upstream changes; isolate the development checkout from stable operating runtimes; pin the coherent SDK set; run private, authentication-free fixtures, focused checks, review and the required floors. Prepare the exact candidate, shutdown/reopen commands and backup manifest before the operating window.
497
+ - **Reuse at the coherent operating boundary:** the native harness uses its existing account/subscription authentication in its ordinary location. Its normal same-account token refresh is not a reason to demand another login. Entwurf does not extract, print, copy, transplant or proxy credentials, and does not introduce an auth-path shim. Keep the selected subscription/provider rail; no account, endpoint, scope or billing-rail substitution is implied.
498
+ - **Still coordinate the human-owned lifecycle window:** identify the exact operating owners and obtain the requested normal shutdown; prove old-owner exit and preserve the complete session directory consistently, including WAL/SHM, before the newer SDK opens operating data. Verify candidate, host, extension, bridge and SDK coherence, then reopen the same identities and measure actual model/tool/round-trip behavior. Authentication reuse is the default method, not permission to skip these gates or to force-close a working citizen.
499
+ - **Do not invent a pre-cutover exception:** if a real-model test cannot use the approved isolated surface without touching operating auth/data, defer it to that operating window. A separate private login or operating-agent-directory experiment is optional and needs a specific request; neither is a recurring prerequisite. Record the missing model/TUI evidence rather than claiming scripted S/H proves it.
500
+ - **Stop and report a real exception:** expired/revoked authentication needing login, changed account/permissions/endpoint or an unmeasured new external transfer, ownership conflict, inconsistent backup, unexpected write, or unknown result. No blind retry or automatic data rollback; downgrading the package does not undo a newer document write.
501
+
502
+ Push, release/tag/npm publication and retirement of any other citizen retain their separate authorization boundaries. This decision fixes the repeatable method; it does not pre-authorize those actions.
503
+
504
+ **Installation handoff: prepared is not installed.** State prominently whether the installer has actually run or is still waiting for the operator's approval. A prepared terminal, a pulled checkout or a printed resume command is not success. Keep the normal old-owner exit and consistent complete backup gates; before presenting the reopen step as ready, require the recorded installation exit 0 and coherent carrier/SDK verification. Then reopen the intended same session with its original explicit native module. Do not automatically send Enter, install or switch ownership to bridge an approval wait. The [0.51.0 ThinkPad incident](../CHANGELOG.md#install-handoff-incident-and-recovery--thinkpad-2026-11-04) records a new-source/old-dependency launch refusal and the separately authorized recovery; it is not evidence of data loss or an SDK regression.
505
+
506
+ **Recurring SDK rail.** At task start, agree the exact SDK and the scope of development, review and verification once; do not repeatedly ask for the existing-auth strategy. Inspect the published SDK set and upstream changes, isolate the development checkout from operating runtimes, and implement against the coherent pinned set in private HOME, PI and writable XDG roots. Use the gate's own temporary-directory constraints: the #155 FULL's deeper scratch produced 117/109-byte socket paths and EINVAL; run3 used `TMPDIR=/tmp` with each gate creating its own private scratch and passed. Preserve both `pnpm_config_verify_deps_before_run=error` and `npm_config_verify_deps_before_run=error`, managed builds and candidate seals; do not run offline gates under ambient CI.
507
+
508
+ The owned canonical app/TUI source is production responsibility; the SDK owns the execution engine. In independent upgrade review, compare old and new app-visible payload semantics against known fixtures and a separate official SDK oracle: more than 50 active entries on reopen and same-process conversation switch, head/reset and background-compaction retention, and the distinction from model memory. API names, typechecks or a QK that only fixes the new SDK's window can all be green while older display compatibility regresses. When official emission changes a generated digest, check its pin-linked mutant find literal and the existing manifest inventory together; this is not a new gate.
509
+
510
+ Freeze for sequential independent review, then affected PARTIAL groups, frozen FULL and applicable scripted/native-H carrier cells. Accept the **same reviewed validation TGZ** in the Linux container and rehearse ownership refusal/takeover in isolation. Measure SDK publication age early to plan the window, then recheck it fresh before installation; never automatically exempt the global policy. Any operator-approved exception must name the exact packages and versions and be recorded with that upgrade's decision; it is not a standing patch-release exemption and not permission for an operating cutover. For #158 the operator explicitly approved the exact SDK 1.5.2 release-age exception on 2026-11-11: only those ten member/version terms are added to `minimumReleaseAgeExclude`, and the global policy and every other dependency remain subject to their own checks. That differs from #155's dated no-exception installation and authorises no operating write or publication.
511
+
512
+ In the named operating window, identify owners, obtain their normal exit and prove it, then take a consistent complete session backup including WAL/SHM before the first new-SDK write. Install a coherent host and validation TGZ, verify installed global root (GR), runtime and ownership independently, and perform only the approved takeover. Reopen the same identities with their original explicitly named native modules and existing auth; observe real turns and round trips before the Native LIVE floor. Public pi-durable `entwurf_resume_call` is bridge-only: it does not save or restore operator native modules. If one is needed, use the original explicit CLI entry; do not infer a profile or kill a citizen automatically. One approval can cover the named scope, sibling roles and fixture work, but not bypass normal exit, the first-write decision, or separate push/npm authority.
513
+
514
+ For each named Native fixture, record the runtime root and SDK actually selected separately from the operator installation: a private PATH can select the genuine source CLI while an ordinary Pi extension still comes from an older installed GR. Do not substitute a wrapper or rewire the operating host to make the evidence agree. A planned source-CLI Native run is not a completed receipt.
515
+
516
+ **#155 measured example (2026-11-03; before the D1 display repair):** at 20:47 KST host Pi 1.5.0 and the unpublished, addon-less 0.51.0 validation TGZ (SHA-256 `199e5f41d20e5b2665125868b7e3e27740e5985f7a25ac61902126b7ed638bf5`) were installed, not the final publication artifact. Four owners exited normally; the 171-file durable backup's source-before/backup/source-after manifests matched (`5c08bed1968fd1e26baa58112f0ce5c36874e147ccc5e9b4c69ec1a0d9070a46`); two Pi transcripts were also backed up. All ten SDK members were freshly measured at 24.70–24.84 hours old, without a policy exception. The P5 Native LIVE cut recorded MUST 24/0/0 and BEHAVIOR 1/0/0 (pass/fail/skip) on its own sealed candidate, not the prepared SHA. Native-host criterion 4 separately measured a fresh Claude Code fixture's actual installed 0.51 MCP child and strict doctor; existing pre-cutover Claude sessions retained their loaded 0.50 bridge. Neither the 20:47 installation nor P5 proves that the later D1 display repair is operating: the D1-bearing artifact must first be installed, and the human must then restart the process; restarting against the earlier artifact still loads the old app. See [0.51.0 release evidence](../CHANGELOG.md#0510--2026-11-04) for scopes and receipts.
517
+
518
+ The Windows-bearing release TGZ, its Linux acceptance, exact prepared-SHA CI, installed-host acceptance and publication/registry proof are separate later evidence. Re-measure owners, loaded bytes, version/integrity, backup and candidate evidence on every upgrade; this ordering does not guarantee success for a future SDK.
519
+
520
+ For the separate land, prepare, make and publish authorities and the one accepted artifact, follow [entwurf-release](../.claude/skills/entwurf-release/SKILL.md). Push, tag, human-owned npm publication and the subsequent Herdr re-pin remain independently coordinated decisions. The current SDK-specific decisions and receipts belong to the active issue and handoff, not this reusable ordering.
521
+
465
522
  ### Moving durable sessions from the Pi 1.3 SDK to 1.4
466
523
 
467
- 0.41.1, 0.41.2 and 0.45.0 are published on the 1.4.0 SDK set; moving a live host onto any of them
524
+ 0.41.1, 0.41.2, 0.45.0 and 0.50.0 are published on the 1.4.0 SDK set; moving a live host onto any of them
468
525
  remains a separate operator decision for each host. When you do:
469
526
 
470
527
  1. **Confirm the old owner is gone.** Close the 0.41 (SDK 1.3) durable session normally and check
@@ -726,7 +783,7 @@ as a note rather than a fault.
726
783
 
727
784
  **entwurf sets no OMP version floor in code, and will not grow one on schedule.** Detection is
728
785
  presence-only (`command -v "${OMP_BIN:-omp}"`); there is no `entwurf.ompFloor`, no coherence
729
- gate, and no exact pin — unlike Node (`engines.node`), pi (`>=1.4.0 <1.5`) and Claude Code
786
+ gate, and no exact pin — unlike Node (`engines.node`), pi (the #158 checkout's predicate, `>=1.5.2 <1.6`) and Claude Code
730
787
  (`entwurf.claudeCodeFloor`), each of which has an enforcement point. That asymmetry is a
731
788
  decision, not an omission. A floor is the answer to a vendor that fails SILENTLY — Claude Code
732
789
  earned one because an older binary validates the exec manifest, drops `args` at runtime, and
@@ -1219,7 +1276,7 @@ harness unit FAIL with the §1 Darwin wording — upgrade does not mint a
1219
1276
  rail receipt. Doctors refuse on Darwin until a physical-host receipt
1220
1277
  exists.
1221
1278
 
1222
- If install or doctor reports an unreadable/old active citizen generation, do not edit
1279
+ If install or doctor reports an active citizen generation it cannot read, first distinguish reader-version skew from a genuinely unreadable generation. Roll a stale reader forward and preserve identities and records; only a genuinely unreadable generation, as the [fresh-cut policy](./fresh-cut-policy.md) prescribes, takes the procedure below. Do not fresh-cut merely because an SDK or checkout changed. For a genuinely unreadable generation, do not edit
1223
1280
  records by hand. Close pi, Claude, Copilot, OMP, agy, and Codex sessions first, run
1224
1281
  `entwurf meta-bridge-fresh-cut`, and read its exit status before any install. Then choose the
1225
1282
  installation mode you actually own:
@@ -1269,7 +1326,7 @@ An already-running process keeps the modules it loaded, whatever is on disk. So,
1269
1326
 
1270
1327
  1. Build once from the checkout (`pnpm run build-bridge`, or its body `bash scripts/with-dist-lock.sh bash scripts/build-bridge.sh` when no package lifecycle should run).
1271
1328
  2. Compare the whole compiled output with the accepted candidate's, as a file-set and hash manifest. A hash of the selected entry alone is not enough: the entry can stay identical while a helper it loads differs. Check the pinned SDK set separately, and that the new process started after the build.
1272
- 3. Close the visible session and reopen it with its own native same-id resume. Closing a working sibling is an operator decision for that session, not something an agent does because new bytes landed.
1329
+ 3. Close the visible session and reopen it with its own native same-id resume. Closing a working sibling is an operator decision for that session, not something an agent does because new bytes landed. For a pi-durable session launched with an operator module, reopen the intended native session with its original explicit CLI and module after verifying the installation; garden-id resume does not restore modules.
1273
1330
  4. Confirm `entwurf_self` names the same garden id, and that the receiver/owner join points at the new process, whose bridge child started after the build.
1274
1331
 
1275
1332
  A per-component setup PASS or a `git pull` does not show which bytes a running bridge loaded. On 2026-10-20 one host of the #107 pair still selected a pre-feature dist after a pull and a green setup. The cause was not traced. A hash check caught it before the first message, and a manual build and visible resume fixed it. Entwurf adds no auto-build, reload or watcher for this.
@@ -6,8 +6,14 @@
6
6
  "editing": "Preserve decoded strings exactly unless an explicit semantic amendment is intended. Review related consumers and independent oracles atomically. Accepted payload changes advance revision; history-only edits, routine releases and unrelated code changes do not. schemaVersion tracks reader/structural compatibility. A slotted hint holds only the text around its slot: the slot value (for record-less-socket, the fresh-cut command in both invocation forms) is supplied by its code owner and never copied here. Metadata is not delivered to models."
7
7
  },
8
8
  "schemaVersion": 6,
9
- "revision": 19,
9
+ "revision": 20,
10
10
  "history": [
11
+ {
12
+ "revision": 20,
13
+ "source": "b9fa0d2aa43ebefc1c3bd5bc864c84965b52289a",
14
+ "issue": "https://github.com/junghan0611/entwurf/issues/159",
15
+ "change": "Align delivery descriptions with completion and reply routing while preserving receipt, rail, and lifecycle boundaries."
16
+ },
11
17
  {
12
18
  "revision": 19,
13
19
  "source": "2734f524bfeca6eb1a0ae092494d45576af9ebef",
@@ -126,11 +132,11 @@
126
132
  "tools": {
127
133
  "entwurf_v2": {
128
134
  "descriptions": {
129
- "mcp": "CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id alone does not say which rail that citizen answers on. Give target + intent; the decider picks transport from liveness (live socket citizen → control-socket send; deliverable self-fetch citizen → meta-bridge mailbox; probe-alive native-push citizen → direct injection into its conversation) and reports ONE outcome, named per rail: a control receiver's acceptance boundary (`sent` / `queued-steer` / `queued-follow-up` / `accepted-unknown-boundary` — those four, or `mailbox-enqueued` on a re-resolved mailbox leg), a mailbox ENQUEUE receipt, a native-push INJECTION, a rejection, or lock-dirty. This receipt is not the colleague's reply or evidence of reading. EXISTING targets only; discover with entwurf_peers. A live socket can receive fire-and-forget. unsupported means only \"no control-socket probe,\" not reachability. The decider resolves the actual rail at dispatch time: a self-fetch mailbox only while deliverable, native-push direct injection only while its adapter probe is alive, or a THIRD RESULT: rejection when neither holds. An inactive self-fetch citizen is mailbox-undeliverable; native-push has NO mailbox: dead → native-push-target-dead; indeterminate → native-push-probe-indeterminate; agy (paused, #152) → native-push-paused, no probe. A dormant socket rejects this delivery verb as dormant-fire-forget-unsupported. owned-outcome was withdrawn. Only on an explicit operator request, entwurf_resume_call may visibly reopen a proven-dormant Pi, pi-durable or Claude Code citizen under the same id, submitting no prompt; that lifecycle is separate from delivery. LOCK: taken for a control-socket-DOMAIN dispatch. The mailbox and native-push rails are lock-free — deliverability and the adapter probe guard them. mode applies to a CONTROL-SOCKET send only; other plans carry no mode. wants_reply rides every rail. message caps at 16000 chars; send an artifact path + digest for more.",
130
- "durable": "Send a message to an existing garden citizen by garden id through the entwurf-bridge (intent fire-and-forget). Returns the bridge's delivery receipt, or fails with its named rejection. This receipt is not the colleague's reply."
135
+ "mcp": "Send, reply or hand off to an existing garden id; the decider selects the live rail (live socket citizen → control-socket send; deliverable self-fetch citizen → meta-bridge mailbox; probe-alive native-push citizen → direct injection into its conversation) and reports ONE outcome, named per rail: a control receiver's acceptance boundary (`sent` / `queued-steer` / `queued-follow-up` / `accepted-unknown-boundary` — those four, or `mailbox-enqueued` on a re-resolved mailbox leg), a mailbox ENQUEUE receipt, a native-push INJECTION, a rejection, or lock-dirty. A receipt is not a reply or proof of reading. A local final is not delivery: when asked to reply, call entwurf_v2 for the requested recipient (normally the replyable sender); honor routing and no-send instructions. Discover existing targets with entwurf_peers. A live socket can receive fire-and-forget; unsupported means only \"no control-socket probe,\" not reachability. For a target without a live socket, the THIRD RESULT is rejection if neither mailbox nor native-push is available. An inactive self-fetch citizen is mailbox-undeliverable; native-push has NO mailbox: dead → native-push-target-dead; indeterminate → native-push-probe-indeterminate; agy (paused) → native-push-paused, no probe. A dormant socket rejects this delivery verb as dormant-fire-forget-unsupported. This delivery verb never launches or resumes a process. Only on an explicit operator request, entwurf_resume_call may visibly reopen a proven-dormant Pi, pi-durable or Claude Code citizen under the same id, submitting no prompt; that lifecycle is separate from delivery. A control-socket-DOMAIN dispatch takes a target lock; mailbox and native-push rails are lock-free. mode applies only to a CONTROL-SOCKET send. wants_reply requests a response without authorizing an action or requiring an automatic acknowledgment; an absent or false marker does not cancel a request in the message. message caps at 16000 chars; send an artifact path + digest for more.",
136
+ "durable": "Send, reply or hand off to an existing garden id through entwurf-bridge (intent fire-and-forget), returning a delivery receipt or named rejection. The receipt is not a reply or proof of reading. For a requested reply, send to the authorized recipient, normally the replyable sender; a local final is not delivery. Honor routing and no-send instructions; an absent or false wants_reply does not cancel a request, and an address alone needs no ACK."
131
137
  },
132
138
  "parameters": {
133
- "intent": "fire-and-forget = send/reply/hand-off to an existing target after a current rail check. unsupported is not a reachability verdict; the decider picks a rail or REJECTS (self-fetch → mailbox when deliverable, else mailbox-undeliverable; native-push → alive: direct injection, dead: native-push-target-dead, indeterminate: native-push-probe-indeterminate; a paused backend — agy, #152 — rejects as native-push-paused before any probe); set wants_reply to request an answer, not to wait for one. This is the ONLY intent: owned-outcome, which resumed a dormant citizen by launching a hidden background child, was withdrawn under the visible-first rule. It is not selectable, not deprecated-but-tolerated; a dormant socket citizen is currently unreachable by this verb and rejects as dormant-fire-forget-unsupported.",
139
+ "intent": "fire-and-forget sends, replies or hands off to an existing target after a current rail check. unsupported is not a reachability verdict: self-fetch uses a deliverable mailbox or rejects as mailbox-undeliverable; native-push injects when alive, rejects dead as native-push-target-dead and indeterminate as native-push-probe-indeterminate; paused agy rejects as native-push-paused before probing. wants_reply requests a response, not a wait. This delivery verb never launches or resumes a process; a dormant socket rejects as dormant-fire-forget-unsupported.",
134
140
  "message": "Message / prompt to the colleague. In familiar language and natural sentences, convey what matters now and the context they need. Keep identifiers and evidence exact; trim repetition, not meaning. Hard cap 16000 chars; for larger payloads send a file/artifact path plus digest.",
135
141
  "mode": "Injection style for a CONTROL-SOCKET send only: steer (ask the receiver's steering queue) or follow_up (ask its follow-up queue). NEITHER is an interrupt: a busy receiver drains steering after each turn and follow-ups only when its inner loop ends, so a later steer overtakes every earlier follow_up and there is no order between the two. Both queues are volatile process memory — an abort drops what is in them. The receipt names which queue accepted the message (`queued-steer` / `queued-follow-up`); an idle receiver answers `sent`, meaning the turn was TRIGGERED, not that the model has seen the text. The mailbox and native-push plans carry no mode, so it has no effect on those rails.",
136
142
  "target": "Target garden id (use entwurf_peers to discover)",
@@ -29,7 +29,10 @@ import { win32Native } from "./win32-native.js";
29
29
  * renders nothing (etiquette marker, not transport contract).
30
30
  */
31
31
  export function formatSenderInfoBlock(sender, wantsReply = false) {
32
- return `\n\n<sender_info>${JSON.stringify({
32
+ const replyGuidance = sender.replyable === true
33
+ ? "\n\n[entwurf: When a reply is requested, send the result via entwurf_v2 to the requested recipient (default: sender_info.sessionId); a local final is not delivery. Honor routing and no-send instructions; an absent or false wants_reply does not cancel a request. A replyable address alone needs no acknowledgment.]"
34
+ : "";
35
+ return `${replyGuidance}\n\n<sender_info>${JSON.stringify({
33
36
  sessionId: sender.sessionId,
34
37
  agentId: sender.agentId,
35
38
  cwd: sender.cwd,
@@ -3,7 +3,7 @@
3
3
  * verb (0.11 Stage 0 step 4-pre / 동결결정 10). PURE pi-FREE core: the
4
4
  * intent×liveness decision table + the reject taxonomy + a pure resolver.
5
5
  * NO runtime dispatch, NO spawn/send, NO I/O — step 5 wires this to transports.
6
- * The pi-ai TypeBox REPRESENTATION of this contract lives in the separate
6
+ * The pi-schema REPRESENTATION of this contract lives in the separate
7
7
  * `entwurf-v2-contract-schema.ts` (0.12.1 B-1) so this module — which the
8
8
  * harness-neutral MCP bridge reaches at boot — carries no pi dependency.
9
9
  *
@@ -431,9 +431,9 @@ export function resolveNativePushDispatch(intent, liveness) {
431
431
  observedLiveness: liveness,
432
432
  };
433
433
  }
434
- // ── TypeBox schemas ────────────────────────────────────────────────────────
435
- // MOVED to `entwurf-v2-contract-schema.ts` (0.12.1 B-1): the pi-ai TypeBox
436
- // builders (StringEnum/Type) are a pi-lane dependency, so they cannot live in
434
+ // ── JSON schemas ───────────────────────────────────────────────────────────
435
+ // MOVED to `entwurf-v2-contract-schema.ts` (0.12.1 B-1): the pi schema
436
+ // builders (pi-ai StringEnum, pi-schema since #155) are a pi-lane dependency, so they cannot live in
437
437
  // this pi-free core — the MCP bridge reaches this module at boot and must stay
438
438
  // harness-neutral (check-entwurf-bridge-pi-free). The schemas import the
439
439
  // constants/types above; pi-side consumers import the schemas from there.
@@ -64,7 +64,7 @@ export function formatMetaMailboxBody(sender, message, wantsReply) {
64
64
  // SENDING host, so it is neither abbreviated against this host's HOME nor shown as a local path.
65
65
  const remote = !replyable ? sender.remote : undefined;
66
66
  const sessionLine = replyable
67
- ? `${sender.sessionId} (${kind}replyable — reply via entwurf_v2 to this sessionId, intent=fire-and-forget)`
67
+ ? `${sender.sessionId} (${kind}replyable — when a reply is requested, send the result via entwurf_v2 (intent=fire-and-forget) to the requested recipient, defaulting to this sessionId; a local final is not delivery. Honor routing and no-send instructions; an absent or false wants_reply does not cancel a request. A replyable address alone needs no acknowledgment)`
68
68
  : remote
69
69
  ? `${sender.sessionId} (remote via ${remote.via}; ${remote.replyRoute === "declared" ? "reply route declared, not probed" : "no reply route declared"})`
70
70
  : isMeta
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junghanacs/entwurf",
3
- "version": "0.50.0",
3
+ "version": "0.52.0",
4
4
  "description": "Visible sibling sessions across independent agent harnesses: launch in Herdr or tmux, retire on request, visibly reopen supported sessions under the same id, and deliver messages by garden id; native bridges and Claude/Cortex ACP adapters.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -105,23 +105,21 @@
105
105
  "@agentclientprotocol/claude-agent-acp": "0.90.0",
106
106
  "@agentclientprotocol/sdk": "1.8.0",
107
107
  "@anthropic-ai/sdk": "0.100.1",
108
- "@earendil-works/chord": "1.4.0",
109
- "@earendil-works/pi-agent-core": "1.4.0",
110
- "@earendil-works/pi-ai": "1.4.0",
111
- "@earendil-works/pi-codemode": "1.4.0",
112
- "@earendil-works/pi-coding-agent": "1.4.0",
113
- "@earendil-works/pi-durable": "1.4.0",
114
- "@earendil-works/pi-mcp": "1.4.0",
115
- "@earendil-works/pi-telemetry": "1.4.0",
116
- "@earendil-works/pi-tui": "1.4.0",
108
+ "@earendil-works/chord": "1.5.2",
109
+ "@earendil-works/pi-agent-core": "1.5.2",
110
+ "@earendil-works/pi-ai": "1.5.2",
111
+ "@earendil-works/pi-codemode": "1.5.2",
112
+ "@earendil-works/pi-coding-agent": "1.5.2",
113
+ "@earendil-works/pi-durable": "1.5.2",
114
+ "@earendil-works/pi-mcp": "1.5.2",
115
+ "@earendil-works/pi-schema": "1.5.2",
116
+ "@earendil-works/pi-telemetry": "1.5.2",
117
+ "@earendil-works/pi-tui": "1.5.2",
117
118
  "@modelcontextprotocol/sdk": "^1.12.1",
118
119
  "proper-lockfile": "4.1.2",
119
120
  "smol-toml": "^1.8.0",
120
121
  "zod": "^3.25.0 || ^4.0.0"
121
122
  },
122
- "peerDependencies": {
123
- "typebox": "*"
124
- },
125
123
  "devDependencies": {
126
124
  "@biomejs/biome": "^2.4.13",
127
125
  "@types/node": "^24.3.0",
@@ -220,10 +218,5 @@
220
218
  "entwurf-agy-statusline": "./scripts/agy-statusline.sh",
221
219
  "entwurf-agy-imprint": "./scripts/agy-imprint.sh",
222
220
  "entwurf-copilot-statusline": "./scripts/copilot-statusline.sh"
223
- },
224
- "peerDependenciesMeta": {
225
- "typebox": {
226
- "optional": true
227
- }
228
221
  }
229
222
  }
@@ -1,4 +1,4 @@
1
-
1
+
2
2
 
3
3
  import {
4
4
  createRegistry,