@junghanacs/entwurf 0.12.6 → 0.12.8-repair.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 (109) hide show
  1. package/AGENTS.md +35 -18
  2. package/BASELINE.md +82 -8
  3. package/CHANGELOG.md +46 -0
  4. package/DELIVERY.md +90 -15
  5. package/README.md +111 -49
  6. package/VERIFY.md +87 -15
  7. package/demo/README.md +1 -1
  8. package/docs/setup-clean-host.md +145 -31
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +90 -66
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +54 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +153 -0
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +436 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +157 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +105 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +90 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +194 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +153 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +42 -9
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +49 -13
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +104 -11
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +30 -1
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-native-push.js +57 -0
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +10 -0
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +9 -0
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +21 -0
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +5 -0
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +17 -0
  28. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +131 -0
  29. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +7 -4
  30. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/adapter.js +158 -0
  31. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +61 -0
  32. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +86 -22
  33. package/mcp/entwurf-bridge/dist/scripts/agy-imprint.js +166 -0
  34. package/mcp/entwurf-bridge/dist/scripts/doctor-pi-provider.js +130 -0
  35. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-prune.js +178 -0
  36. package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +24 -0
  37. package/mcp/entwurf-bridge/src/index.ts +101 -67
  38. package/mcp/entwurf-bridge/start.sh +2 -1
  39. package/mcp/entwurf-bridge/test.sh +1 -1
  40. package/mcp/entwurf-bridge/tsconfig.build.json +23 -3
  41. package/package.json +25 -12
  42. package/pi/meta-bridge/entwurf-meta-receive/hooks/hooks.json +8 -4
  43. package/pi/meta-bridge/entwurf-meta-receive/scripts/hook-launch.sh +77 -0
  44. package/pi-extensions/lib/entwurf-deliverability.ts +62 -9
  45. package/pi-extensions/lib/entwurf-self-address.ts +58 -15
  46. package/pi-extensions/lib/entwurf-v2-contract.ts +120 -12
  47. package/pi-extensions/lib/entwurf-v2-decider.ts +60 -0
  48. package/pi-extensions/lib/entwurf-v2-native-push.ts +86 -0
  49. package/pi-extensions/lib/entwurf-v2-production.ts +20 -0
  50. package/pi-extensions/lib/entwurf-v2-release.ts +9 -0
  51. package/pi-extensions/lib/entwurf-v2-runner.ts +29 -1
  52. package/pi-extensions/lib/entwurf-v2-send.ts +7 -0
  53. package/pi-extensions/lib/entwurf-v2-surface.ts +17 -0
  54. package/pi-extensions/lib/meta-sender-identity.ts +160 -0
  55. package/pi-extensions/lib/meta-session.ts +8 -5
  56. package/pi-extensions/lib/native-push/adapter.ts +255 -0
  57. package/pi-extensions/lib/native-push/register.ts +99 -0
  58. package/pi-extensions/meta-bridge-hook.ts +87 -22
  59. package/run.sh +1363 -249
  60. package/scripts/agy-bridge-config.py +446 -0
  61. package/scripts/agy-bridge.sh +359 -0
  62. package/scripts/agy-hooks-bridge.sh +193 -0
  63. package/scripts/agy-hooks-config.py +257 -0
  64. package/scripts/agy-imprint.sh +28 -0
  65. package/scripts/agy-imprint.ts +193 -0
  66. package/scripts/agy-statusline-bridge.sh +176 -0
  67. package/scripts/agy-statusline-config.py +213 -0
  68. package/scripts/agy-statusline.sh +256 -0
  69. package/scripts/build-bridge.sh +20 -0
  70. package/scripts/check-agy-sender-identity.ts +364 -0
  71. package/scripts/check-entwurf-bridge-boot.ts +8 -2
  72. package/scripts/check-entwurf-deliverability.ts +34 -0
  73. package/scripts/check-entwurf-self-address.ts +78 -11
  74. package/scripts/check-entwurf-v2-contract.ts +136 -1
  75. package/scripts/check-entwurf-v2-decider.ts +95 -1
  76. package/scripts/check-entwurf-v2-matrix.ts +14 -3
  77. package/scripts/check-entwurf-v2-native-push.ts +193 -0
  78. package/scripts/check-entwurf-v2-production.ts +68 -1
  79. package/scripts/check-entwurf-v2-runner.ts +58 -0
  80. package/scripts/check-entwurf-v2-surface.ts +35 -0
  81. package/scripts/check-hook-launch-topology.ts +312 -0
  82. package/scripts/check-install-container.sh +458 -0
  83. package/scripts/check-install-surface.ts +563 -0
  84. package/scripts/check-meta-doctor-oracle.sh +479 -0
  85. package/scripts/check-meta-manifest-schema.py +45 -3
  86. package/scripts/check-meta-receiver-marker.ts +30 -5
  87. package/scripts/check-native-push-adapter.ts +319 -0
  88. package/scripts/check-native-push-register.ts +130 -0
  89. package/scripts/dev-bin.sh +195 -0
  90. package/scripts/doctor-pi-provider.ts +140 -0
  91. package/scripts/meta-bridge-claude-floor.sh +84 -0
  92. package/scripts/meta-bridge-doctor.sh +592 -39
  93. package/scripts/meta-bridge-install.sh +54 -14
  94. package/scripts/meta-bridge-uninstall.sh +4 -1
  95. package/scripts/register-pi-package.py +37 -3
  96. package/scripts/register-pi-provider.py +287 -0
  97. package/scripts/smoke-agy-hooks-state.sh +172 -0
  98. package/scripts/smoke-agy-install-state.sh +660 -0
  99. package/scripts/smoke-agy-native-push-live.ts +243 -0
  100. package/scripts/smoke-agy-statusline-state.sh +300 -0
  101. package/scripts/smoke-meta-async-drift.sh +9 -2
  102. package/scripts/smoke-meta-honesty.sh +13 -1
  103. package/scripts/smoke-meta-install-state.sh +120 -1
  104. package/scripts/smoke-pi-provider-state.sh +182 -0
  105. package/scripts/smoke-user-scope-citizen.sh +62 -0
  106. package/scripts/with-dist-lock.sh +81 -0
  107. package/scripts/__pycache__/meta-bridge-state.cpython-312.pyc +0 -0
  108. package/scripts/__pycache__/meta-bridge-state.cpython-313.pyc +0 -0
  109. package/scripts/__pycache__/register-pi-package.cpython-313.pyc +0 -0
package/AGENTS.md CHANGED
@@ -27,7 +27,7 @@ For agents that own this repo: invariant principles + reproducible verification,
27
27
  ### 먼저 붙들 정체성
28
28
 
29
29
  - **entwurf가 주어이고 pi는 한 adapter다.** pi는 지금 이 repo가 가장 깊게 붙어 있는 하네스지만 4번째 하네스일 뿐이다. 이 repo는 pi의 세션 모델, transcript, UI, tool semantics와 경쟁하지 않는다.
30
- - **다른 하네스의 세션은 형제다.** Claude Code, Codex, Antigravity는 학교가 달라도 모두 frontier 친구들이다. meta-bridge는 그들을 garden id로 호명 가능한 citizen으로 등록할 뿐, 누구를 다른 누구로 위장시키지 않는다.
30
+ - **다른 하네스의 세션은 형제다.** Claude Code, Codex, Antigravity는 학교가 달라도 모두 frontier 친구들이다. native bridge는 증명된 lifecycle/transport가 있는 세션만 garden id로 호명 가능한 citizen으로 등록할 뿐, 누구를 다른 누구로 위장시키지 않는다.
31
31
  - **표면은 달라도 능력의 존엄은 낮추지 않는다.** 어떤 backend에서 `mcp__...`가 직접 보이지 않는다고 해서, 곧바로 그 backend를 "못하는 존재"로 취급하지 마라. 먼저 capability를 보고, 그 capability가 어떤 surface로 열리는지 확인하라.
32
32
  - **substrate는 결정적 dispatch만 맡는다.** target liveness를 fact로 읽고, intent와 곱해 transport를 고른다. 그 이상 마술을 부리면 안 된다.
33
33
  - **명시는 주변기류보다 강하다.** 숨겨진 transcript hydration, ambient MCP scanning, invisible tool claims, giant magical system prompt, 근거 없는 서사를 만들지 마라.
@@ -51,7 +51,7 @@ For agents that own this repo: invariant principles + reproducible verification,
51
51
  - 문서에 적힌 asymmetry를 면책조항처럼 사용하는 것
52
52
  - `entwurf`를 하네스 런타임이나 범용 AI 작업실로 설명하는 것 — pi가 하네스 중 하나이고, 이 repo는 garden-citizen dispatch capability다
53
53
  - MCP를 자동 맥락 검색이나 ambient tool scanning처럼 설명하는 것 — explicit injection만 허용된다
54
- - `entwurf_v2`를 "새 분신을 만드는 도구"로 설명하는 것 — v2의 3 transport는 전부 **기존** garden citizen 대상이다. fresh sibling 생성은 0.12.x로 연기된 별개 능력이다
54
+ - `entwurf_v2`를 "새 분신을 만드는 도구"로 설명하는 것 — v2의 4 transport(control-socket / spawn-bg resume / meta-mailbox / native-push)는 전부 **기존** garden citizen 대상이다. fresh sibling 생성은 별개 능력이다
55
55
  - 사용자가 이미 철학과 방향을 준 문제를 다시 사용자에게 되묻는 것
56
56
 
57
57
  릴리즈 이야기와 개별 기능은 주변을 돈다.
@@ -61,8 +61,8 @@ For agents that own this repo: invariant principles + reproducible verification,
61
61
 
62
62
  An **entwurf garden-citizen dispatch substrate** + a **meta-bridge** + an **ACP plugin** + a **pi adapter**. Pi stays a harness/runtime, not the project center; every addressed session keeps its own identity.
63
63
 
64
- - **Meta-bridge**: a global `SessionStart` hook registers a native-harness session (Claude Code / Codex / Antigravity) as a **garden-native meta-session** — a garden id, a mailbox, a trusted sender marker — without importing that harness's transcript or pretending pi owns it. Installed/inspected via `./run.sh install-meta-bridge` / `doctor-meta-bridge`.
65
- - **v2 dispatch (`entwurf_v2`)**: one verb that delivers to / wakes an *already-identified* garden citizen. A pure decider reads target liveness as a fact and picks transport from a frozen table keyed on **target state × intent**: live pi + fire-and-forget → **control-socket** send; dormant pi + owned-outcome → **spawn-bg resume**; active self-fetch meta-session + fire-and-forget → **meta-mailbox** enqueue; every other state×intent pair is an honest reject. It does **not** mint new siblings.
64
+ - **Native-harness bridges**: Claude Code's global `SessionStart` hook creates a mailbox-backed garden meta-session; Antigravity's `PreInvocation` imprint creates/attaches a native-push garden citizen and writes its sender marker. Both preserve native transcript/auth/runtime ownership, but they are different rails and install surfaces. Codex has probe evidence only, not a shipped managed native-citizen lane.
65
+ - **v2 dispatch (`entwurf_v2`)**: one verb that delivers to / wakes an *already-identified* garden citizen. A pure decider reads transport-specific liveness facts and picks from a frozen table keyed on **target state × intent**: live pi + fire-and-forget → **control-socket**; dormant pi + owned-outcome → **spawn-bg resume**; active self-fetch + fire-and-forget → **meta-mailbox**; probe-alive native-push + fire-and-forget → **native-push**. Every complementary pair is an honest reject. It does **not** mint new siblings.
66
66
  - **ACP plugin** (one pi-adapter ingress): registers the package provider `entwurf` as a pi session provider/model and drives the chosen ACP backend (Claude first; vendor/governed CLIs like Cortex next) under an isolated config overlay. It owns the backend process, the overlay, and the per-backend ACP dialect — **not** socket-citizenship. The host `--entwurf-control` pi session that selected the ACP model is *already* a v2 socket-citizen; the plugin does **not** mint a socket / peers / citizen layer. It is not the substrate and not a second harness. v1 entwurf verbs (`entwurf` / `entwurf_resume` / `entwurf_send`) are gone for good; the ACP plugin is a fresh build on the v2 core (0.11.0's `acp-bridge.ts` is a behavior oracle, not architecture to re-center). See §ACP Plugin Boundary.
67
67
 
68
68
  ## Code Principle — Crash, Don't Warn
@@ -80,13 +80,18 @@ Warnings make agents blame themselves and flail. Broken tool state must surface
80
80
  ## Hard Rules
81
81
 
82
82
  1. **One surface name, hard-cut cutover**: provider/model/routing strings are `entwurf`. No permanent runtime aliases, legacy provider-id accept, or dual-read of old state. If existing operator state must be helped across, do it as an explicit one-shot cutover or a documented break, never as hidden dual routing. The `provider:` routing strings (`getRegistryRouting`, `model-lock.ts`) are **load-bearing** — they are identity, not residue.
83
- 2. **Dispatch is a function of liveness, not session type.** `entwurf_v2` never asks "is this a resume or a send" up front — it probes liveness and routes: live→control-socket, dormant→spawn-bg resume, active self-fetch→meta-mailbox. State is computed, never stored (a stored liveness bit is a lie).
83
+ 2. **Dispatch is a function of liveness, not session type.** `entwurf_v2` never asks "is this a resume or a send" up front — it probes the target on its own rail and routes: live pi→control-socket, dormant pi→spawn-bg resume, active self-fetch→meta-mailbox, live native conversation→native-push. State is computed, never stored (a stored liveness bit is a lie).
84
84
  3. **A reject is honest, never cosmetic.** When a target cannot receive (dead, drifted identity, wrong state×intent), the decider returns a reject — no `✓ delivered`, no `.msg` written, no signal poke. Silent degraded "delivery" is forbidden.
85
85
  4. **MCP injection**: only via explicit `mcpServers` wiring. No ambient `~/.mcp.json` scanning, no automatic retrieval.
86
86
  5. **Meta-record authority is the record body, never the filename.** `scanByNativeId` scans `.meta.json` bodies, throws on duplicate `nativeSessionId` (authority ambiguity is fail-fast), and never derives identity from a filename. A meta-record is nullable-at-birth (`model`/`transcriptPath` null until known); a backend↔wakeMode contradiction is corrupt-and-crash.
87
87
  6. **GC reclaims process resources only — never data.** meta-records and transcripts (the denote-id memory layer) are preserved; dormant/stale entries are archived/TTL'd, not deleted.
88
- 7. **This is not a second harness**: no prompt reconstruction, no transcript hydration, no tool result ledger, no harness emulation. The meta-bridge fronts a mailbox + a garden id; it does not scrape transcripts or run a control daemon for the native session.
88
+ 7. **This is not a second harness**: no prompt reconstruction, no transcript hydration, no tool result ledger, no harness emulation. Native bridges front only a garden id plus their narrow delivery rail (Claude mailbox or agy native-push); they do not scrape transcripts or run a replacement control daemon.
89
89
  8. **Auth boundary is deployment-surface-agnostic**. This repo does not provide, copy, proxy, decrypt, or mediate any backend's credentials. Native-harness sessions read whatever auth state is visible in their own process filesystem; nothing here moves that.
90
+ 9. **Native-push is not a mailbox or pi socket in disguise.** Antigravity replyability is `recordBacked ∧ probeAlive`; it gets no receiver marker, no `watchArmed`, and no spawn/resume authority. Its `agentId` remains `meta-session/antigravity`. The pid+start-key sender join assumes serialized model invocation per agy process: two conversations concurrently invoking under one pid are unsupported and must never be claimed safe.
91
+ 10. **A green dev clone is not a working package — and a green package on the maintainer's host is not a working consumer.** Node refuses `--experimental-strip-types` below `node_modules`, so any surface an operator can invoke must reach compiled JS when installed. This class has shipped four times (start.sh 0.12.1, store-doctor 0.12.4, plugin hook 0.12.5, agy imprint + three operator commands 0.12.7) because the fence was crossed by hand, per surface, and the source-tree floor cannot see it. There is now exactly one crossing — `run_ts` in `run.sh` — and two gates that hold it: `check-install-surface` (structural) and `check-pack-install` (drives the real tarball, in CI). A new `.ts` entrypoint routes through `run_ts` or it does not ship. Dev-only gates have no compiled twin by design and must be REFUSED under an installed package, never silently skipped — a `.sh` dev gate refuses in its own body, since `scripts/` ships whole and run.sh's dispatch is not the only way in. **`check-pack-install` is still a maintainer-shaped proof**: the checkout is present, every tree is operator-owned, and the install is project-local, so a surface that writes beside the installed package or depends on the repo being nearby is green there and broken for a real consumer. `check-install-container` (#51 C, own required CI job) closes that: one candidate tarball, read-only, into a container that has never seen this repo — non-root `npm install -g`, resolution through the PATH shim, a frozen package root, and a regular-file path+sha256 manifest fence across `install-meta-bridge`; the evidence line records the canonical tarball path + sha256 and the Node image id/repository digest. Default CI packs once into a temp dir; release acceptance passes a caller-preserved tarball through `ENTWURF_CANDIDATE_TGZ` and the gate consumes that exact file without re-packing, so `npm publish <same.tgz> --tag repair` can publish the accepted bytes. The two are not redundant detectors of one defect: the **freeze is a permission-level consumer fact** (the cell actually refuses the write, EACCES, the way a real consumer's host would), while the **manifest fence is the detector** — and it is exactly a regular-file path+sha256 comparison, not a whole-tree guarantee: it reads no permissions, ownership or symlink targets. A freeze at the package root alone is demonstrably insufficient (a write one directory down sails past it and only the fence sees it). Model the consumer's world, never a stricter one: a blanket `chmod -R a-w` freeze produced false reds because `cp -r` propagates modes into the installer's own assembly target, which no `sudo npm i -g` consumer can reach.
92
+ 11. **Verification must not rewire the operator's own install.** An offline smoke that writes a live `~/.claude` / `~/.gemini` / `~/.pi` path uninstalls the operator as a side effect of "testing". Swap `HOME` **and every already-exported writable `XDG_*` root** (`XDG_DATA_HOME`: install-state · `XDG_STATE_HOME`: the imprint log · `XDG_CACHE_HOME`: the statusline gid cache): moving HOME alone still writes below the inherited roots. This class struck three times in two days — hard-verify 2026-07-13 (DATA, scratch scripts), `check-pack-install`'s own drives 2026-07-14 (DATA + STATE, inside run.sh), and `smoke-user-scope-citizen` 2026-07-14 (fake `PI_CODING_AGENT_DIR` paired with the real XDG ownership state, so its inverse followed the real `managedSettingsPath` and removed the live MCP key). `check-install-surface` S5 is a static **tripwire** over `scripts/*.sh` source only: it catches a literal live path, one hop of aliasing, (S5b) HOME-without-XDG swaps, and (S5c) a mutating `run.sh` drive left unsandboxed at any root that command writes — the agent dir, `XDG_DATA_HOME`, and, for `install`/`setup`, `HOME` itself, because `ensure_agent_dir_symlinks` hard-codes `$HOME/.pi/agent` and never reads the agent-dir override (so sandboxing `PI_CODING_AGENT_DIR` is not isolation for those commands) — but it cannot see a path assembled across variables, an embedded heredoc, or run.sh itself. **A tripwire keyed to one syntactic form is not a tripwire**: S5c first shipped matching only the inline-env drive, and a review mutation walked the identical leak straight past it by hoisting the same override into an `export` one line up. Match the drive, then demand the isolation — never the other way round. The dynamic complement is `check-pack-install`'s **outer self-fence**, which runs after every success or early-failure path: the operator's real `$XDG_DATA_HOME/entwurf` tree must be byte-identical, and the gate-specific fake agy marker count in the real `$XDG_STATE_HOME/entwurf/agy-imprint.log` must not increase (mutation-checked). Read a green S5 as "no obvious destructive line", never as "verification is sandboxed" — the real guarantee is running the offline floor under a swapped HOME+XDG, which is still open. LIVE gates are the only surfaces that may drive the real host, and they say so in their name.
93
+ 12. **A doctor reports runtime truth and ownership truth separately.** Read the target's own semantics before calling a host broken. agy matches `mcp(*)` and `mcp(<server>)` against our tool wherever those rules appear, so an operator's broad `allow` already grants `entwurf_v2` — reporting that host as "NOT granted, agy prompts on every call" was a false red about a working surface. Installers still take the narrowest rule they need; doctors distinguish **we own this** from **someone else's rule is carrying it** from **it is genuinely broken**. Install-state is evidence only when it parses, names its required managed-path field as an absolute path, and that normalized path equals the live target this host reads; corrupt or foreign-target state is a failure even when the live command itself resolves. Ownership beats coverage: an element the state records as ours that has since vanished stays a failure even while an operator's broader rule keeps the surface working (a whole-file settings relink produces exactly this shape). Conversely, broken ownership state does not justify saying a visibly configured runtime command is absent — report both axes honestly and keep the final verdict red.
94
+ 13. **A native-hook owner is structural, not a topology guess — and the structure is the exec form.** Shell-form command hooks do not expose one portable process tree: under the same Claude Code version we observed both a direct hook→Claude join and a retained `/bin/bash -c` wrapper, and ordinary tail-exec tests never reproduced the trigger. That form is retired, not patched. The meta-bridge declares the **exec form** — `command` is the shipped `hook-launch.sh`, `args` is the real argv — so no shell exists on the launch path, the launcher `exec`s the payload and preserves the pid, and the hook's parent IS Claude on every host (#51 B2, measured at Claude Code 2.1.217). The hook therefore reads `process.ppid` directly; the `$PPID` carrier, the ancestry walk, and the missing-carrier contract are **gone**, and re-introducing any of them means the manifest stopped feeding the owner. **But `process.ppid` is only the owner when the launcher was actually on the path, so `hook-launch.sh` stamps a non-identity `ENTWURF_META_HOOK_LAUNCH` provenance token and the hook writes NO sender/receiver marker without it.** This is not the retired carrier wearing a new name: the carrier smuggled a *pid* that had to be ancestry-checked, while this token carries no identity at all and answers only "was the authorized launch path taken". It is what keeps the upgrade mismatch fail-closed — an already-open Claude session still holding the OLD cached command reaches the new hook with a shell wrapper as its parent, and without the token that wrapper would be minted as an owner. Deleting it is never a cleanup. **entwurf requires Claude Code `>=2.1.217` and enforces that floor itself, because upstream gives no fail-loud:** an older Claude passes `plugin validate` on the exec manifest (unknown-key passthrough), then at runtime drops `args`, runs `command` alone, and reports the hook as `exit_code: 0, outcome: success` — measured at 2.1.138. `hook-launch.sh` refusing an empty argv is that silence made loud; installer and doctor refuse the version outright; there is no shell-form fallback for older versions. `check-hook-launch-topology` drives the shipped argv for real — including a plugin path containing a space, `$`, a backtick, and `;&` — and `check-claude-floor-coherence` keeps the floor one number derived from `package.json` `entwurf.claudeCodeFloor`. Evidence stays tiered: B/B2 are direct-native observations from actual 2.1.138/2.1.217 sessions on one NixOS host; the Linux artifact-consumer's fake Claude, planted cache, stand-in owner and `/proc` bridge are fixtures that prove package/oracle behavior, never a second native-host acceptance. **The doctor is the release oracle, so its exit 0 must mean every required layer was measured, never that a layer was skipped.** It resolves the ONE artifact Claude loads (`claude plugin list --json`.installPath; an ambiguous multi-version cache is refused, never guessed), classifies the installed *launch form* by name across all three owner hooks — a shell-form or launcher-less exec manifest is refused by name, not reported as unreadable drift — and then requires the live MCP↔marker join. Missing live evidence is `NOT CERTIFIED`, a failure worded distinctly from a broken install. The #51 repair cut has **Linux as its only currently certified axis**: install refuses Darwin because `/proc`-based live bridge discovery cannot certify it yet, doctor stays `NOT CERTIFIED`/nonzero there, and uninstall alone retains Darwin support so legacy state is not stranded. This is an evidence boundary, not a permanent macOS impossibility; future native validation may reopen the lane. `check-meta-doctor-oracle` holds this: a healthy fixture must reach PASS and twenty-one planted defects must each turn it red *naming their own cause*. An oracle with an optional central evidence layer is not an oracle.
90
95
 
91
96
  ## ACP Plugin Boundary
92
97
 
@@ -94,11 +99,11 @@ Warnings make agents blame themselves and flail. Broken tool state must surface
94
99
 
95
100
  | Layer | Owns |
96
101
  |---|---|
97
- | **entwurf-core (v2)** | garden id · peer identity · liveness fact interface · dispatch decision · delivery evidence · rail choice (socket / mailbox / spawn) |
102
+ | **entwurf-core (v2)** | garden id · peer identity · liveness fact interface · dispatch decision · delivery evidence · rail choice (socket / mailbox / spawn / native-push) |
98
103
  | **ACP plugin** | ACP backend process lifecycle · config overlay (isolation + tool-narrowing + identity-carrier materialization) · per-backend ACP dialect quirks · backend health / turn evidence — **NOT** socket-citizen registration or liveness/addressability facts (those are the host `--entwurf-control` session's, supplied via socket-discovery) |
99
104
  | **ACP plugin MUST NOT become** | a memory DB · a task planner · an orchestrator · a second harness · a mailbox-citizen impersonation |
100
105
 
101
- - **Sibling equality is a citizen-level property, not a rail-level one.** Every sibling is addressable (peers-visible, garden-id-addressed, `entwurf_v2`-reachable, replyable). The *rail* differs by lifecycle: a live ACP backend is a **socket-citizen** (no mailbox — it is always live, so durable async delivery is unneeded, not withheld); a come-and-go native-harness session is a **mailbox-citizen**. Missing a mailbox is right-sizing, not discrimination.
106
+ - **Sibling equality is a citizen-level property, not a rail-level one.** Every sibling is addressable (peers-visible, garden-id-addressed, `entwurf_v2`-reachable, replyable when its rail proves a return path). The *rail* differs by lifecycle: an ACP-backed pi resident is a **socket-citizen**; Claude Code is a **mailbox-citizen**; agy is a **native-push citizen**. Missing a mailbox on socket/native-push rails is right-sizing, not discrimination.
102
107
  - **Durable memory is the authored common record** (`~/org`, botlog, agenda, Denote, andenken). entwurf lets peers move across that record layer; it never replaces it.
103
108
  - **ACP enters as a model/provider, not a socket layer.** The ACP plugin registers as a pi session's provider/model and spawns the backend under an overlay; **socket-citizenship is supplied by the host `--entwurf-control` pi session**, not minted by the plugin. The plugin never builds a new socket registry, peers layer, or citizen protocol — over-designing one is the failure mode to avoid (`socket-discovery` is model-agnostic, so an ACP-model session is already a citizen).
104
109
 
@@ -123,7 +128,15 @@ pnpm check # full static floor: lint + typechec
123
128
  ./run.sh check-entwurf-v2-matrix # the decider's state×intent table, read as an SSOT (REAL decideDispatch)
124
129
  ./run.sh check-entwurf-v2-decider # + -contract / -lock / -release / -send / -send-fallback / -mailbox / -runner / -production / -surface / -spawn / -spawn-production
125
130
  ./run.sh check-meta-session # + -record-v2 / -dual-read / -migration / -mailbox-state-write / -receiver-marker / -capability-source / -dual-consumers / -listing
126
- ./run.sh check-entwurf-bridge-boot # the MCP entwurf-bridge stands up + exposes the v2 tool set
131
+ ./run.sh check-meta-doctor-oracle # detection power of the release oracle: healthy fixture reaches `doctor: PASS`, 21 planted defects each turn it FAIL naming their own cause
132
+ ./run.sh check-native-push-adapter # agy probe/route leaf; separate from pi socket and mailbox liveness
133
+ ./run.sh check-agy-sender-identity # record-backed pid/start-key sender resolution + ambiguity refusal
134
+ ./run.sh smoke-agy-install-state # MCP + exact permission ownership + honest inverse (140)
135
+ ./run.sh smoke-agy-statusline-state # ambient garden identity install surface (69)
136
+ ./run.sh smoke-agy-hooks-state # PreInvocation birth/sender hook install surface (44)
137
+ ./run.sh check-entwurf-bridge-boot # the MCP entwurf-bridge stands up + exposes the v2/native-register tool set
138
+ ./run.sh check-install-surface # structural strip-types fence: run_ts is the only crossing, every operator command has a compiled twin, offline smokes never write the real $HOME
139
+ ./run.sh check-install-container # Linux artifact CONSUMER (#51 C, own CI job): one candidate .tgz, read-only, into a checkout-invisible node:<engines-major> cell — non-root `npm install -g`, PATH shim, frozen package root, MCP tools/list, install-meta-bridge under a path+sha256 byte-fence, strict doctor. Default pack-once temp; ENTWURF_CANDIDATE_TGZ consumes an exact preserved file without re-pack. SKIP without Docker; ENTWURF_REQUIRE_DOCKER=1 makes that RED
127
140
  ./run.sh check-bridge /path/to/project # entwurf-bridge direct MCP smoke (tools/list + protocol/negative-path)
128
141
  ./run.sh check-auth-boundary # ACP plugin no-auth sentinel present + no legacy-ENV apiKey literal (trust invariant, code-level)
129
142
  ./run.sh check-acp-provider-surface # provider registers curated Claude anchor + streamSimple wired to the real streamShellAcp backend
@@ -138,6 +151,7 @@ pnpm check # full static floor: lint + typechec
138
151
  ```bash
139
152
  LIVE=1 ./run.sh release-gate /path/to/scratch # two-tier: MUST (release-blocking, owns exit code) + BEHAVIOR (advisory)
140
153
  LIVE=1 ./run.sh smoke-acp-socket-citizen-live # S1: a real ACP-model --entwurf-control resident is a first-class socket-citizen (peers + get_info), turn-free (no backend, no stub fire)
154
+ LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live # real agy probe/register/direct-inject evidence; conversation-id gated, outside aggregate release-gate
141
155
  ```
142
156
 
143
157
  The MUST tier is the necessary condition ("green" = MUST PASS, FAIL=0); BEHAVIOR is advisory — the `smoke-resident-garden-guard` positives (a model-in-loop garden identity turn). Run every live gate with `PWD=scratch` so sessions never land in the repo's own session dir.
@@ -150,8 +164,8 @@ If a gate fails or a claim drops below its needed evidence level, do not commit.
150
164
 
151
165
  Uses `entwurf` instead of `delegate` to avoid ecosystem collisions. spawn-bg resume creates a sibling, not a worker.
152
166
 
153
- - **Surface** — MCP `entwurf-bridge`: `entwurf_v2`, `entwurf_self`, `entwurf_peers`, `entwurf_inbox_read`. pi-native (`pi-extensions/entwurf-control.ts`): `entwurf_v2`, `entwurf_peers` tools + `/entwurf-sessions`, `/gnew` (`/garden-new`) commands. The v1 `entwurf` / `entwurf_resume` / `entwurf_send` tools and the `/entwurf` / `/entwurf-send` / `/entwurf-status` commands are **removed** on this branch.
154
- - **`entwurf_v2` is the one delivery verb.** Given a garden id, it classifies the target (live pi vs. dormant pi vs. meta-session — a bare garden id does not reveal this) and routes correctly. It does **not** mint a fresh sibling: the `dormant pi → spawn-bg resume` row resumes an *already-identified* citizen. Fresh creation was the v1 `entwurf` verb and is deferred to 0.12.x.
167
+ - **Surface** — MCP `entwurf-bridge`: `entwurf_v2`, `entwurf_self`, `entwurf_peers`, `entwurf_inbox_read`, `entwurf_register_native` (explicit/manual fallback for an already-running native conversation). pi-native (`pi-extensions/entwurf-control.ts`): `entwurf_v2`, `entwurf_peers` tools + `/entwurf-sessions`, `/gnew` (`/garden-new`) commands. The v1 `entwurf` / `entwurf_resume` / `entwurf_send` tools and the `/entwurf` / `/entwurf-send` / `/entwurf-status` commands are **removed**.
168
+ - **`entwurf_v2` is the one delivery verb.** Given a garden id, it classifies the target (live pi vs. dormant pi vs. mailbox meta-session vs. native-push citizen — a bare garden id does not reveal this) and routes correctly. It does **not** mint a fresh sibling: spawn-bg resumes an *already-identified* citizen, while native-register binds an *already-running* conversation. Fresh creation was the v1 `entwurf` verb and remains deferred.
155
169
  - **`entwurf_peers`** is a read-only fact surface (liveness / capability / identity / cwd-history). Do not bake verb-routing (`resumable`/`sendable`) into the fact layer; routing is the decider's job.
156
170
  - **`entwurf_self`** returns the authoritative identity envelope (pi-session env, or a trusted meta-session sender marker) and is identity-required.
157
171
  - Target registry: `pi/entwurf-targets.json` (spawn-bg resume allowlist). Identity Preservation Rule: no model override on resume.
@@ -175,8 +189,8 @@ Garden identity covers the operator's OWN `--entwurf-control` session, not just
175
189
 
176
190
  Messages are thrown, not awaited.
177
191
 
178
- - v2 delivery is fire-and-forget. There is no `wait_until` / `subscribe` / `turn_end` channel and no caller-side baseline correlation. For a control-socket send the RPC ack is the entire delivery contract; for a meta-mailbox enqueue the receipt is the write. If you need a reply, say so in the message.
179
- - The sender envelope rides every send by default: `{ sessionId, agentId, cwd, timestamp, origin?, replyable? }`. `origin` distinguishes pi-session senders (`replyable: true`) and trusted meta-session senders (`replyable: true` by garden id). `entwurf_self` is authoritative-identity-required.
192
+ - v2 delivery is fire-and-forget. There is no `wait_until` / `subscribe` / `turn_end` channel and no caller-side baseline correlation. For a control-socket send the RPC ack is the contract; for meta-mailbox it is the enqueue receipt; for native-push it is adapter acceptance plus the bounded post-send probe evidence. If you need a reply, say so in the message.
193
+ - The sender envelope rides every send by default: `{ sessionId, agentId, cwd, timestamp, origin?, replyable? }`. `origin` distinguishes pi-session senders (`replyable: true`) and trusted meta-session senders. Claude meta replyability is mailbox-backed; native-push replyability is record-backed + probe-alive. `entwurf_self` is authoritative-identity-required.
180
194
  - **Human-greeted 담당자** is a first-class pattern: GLG may open a session in repo B, greet it directly, then hand its garden id to repo A. Spawned siblings and human-opened peers share the same messaging semantics; only the creation sequence differs.
181
195
 
182
196
  ## File Structure
@@ -187,9 +201,12 @@ Messages are thrown, not awaited.
187
201
  | `pi-extensions/lib/acp/*.ts` | ACP plugin internals: curated Claude surface + no-auth sentinel (`models.ts`), Claude config overlay (`overlay.ts`), tool surface + exclude-tools preflight (`tool-surface.ts`), ACP→pi event mapper (`event-mapper.ts`), pi Context→ACP prompt (`context.ts`), spawn-per-turn `streamSimple` backend (`backend.ts`) |
188
202
  | `pi-extensions/entwurf-control.ts` | control plane: `--entwurf-control` socket, RPC, `entwurf_v2` / `entwurf_peers` tools, `/entwurf-sessions` / `/gnew` |
189
203
  | `pi-extensions/model-lock.ts` | package-provider model lock (pi.extension) |
190
- | `pi-extensions/meta-bridge-hook.ts` | global `SessionStart` hook: register native-harness session as a garden meta-session |
191
- | `pi-extensions/lib/entwurf-v2-*.ts` | v2 substrate: contract / lock / decider / matrix / release / send / mailbox / runner / production / surface / spawn(+production) + resume-marker |
192
- | `pi-extensions/lib/meta-*.ts` | meta-record authority, mailbox state, dual-read/migration, receiver marker |
204
+ | `pi-extensions/meta-bridge-hook.ts` | Claude Code `SessionStart` hook: register a mailbox-backed garden meta-session |
205
+ | `pi-extensions/lib/entwurf-v2-*.ts` | v2 substrate: contract / lock / decider / matrix / release / send / mailbox / native-push / runner / production / surface / spawn(+production) + resume-marker |
206
+ | `pi-extensions/lib/native-push/` | Antigravity adapter probe/route, direct-inject hand, explicit native registration core |
207
+ | `pi-extensions/lib/meta-*.ts` | meta-record authority, mailbox state, dual-read/migration, receiver/sender identity |
208
+ | `scripts/agy-{bridge,statusline-bridge,hooks-bridge}.*` | three state-backed agy install/doctor/inverse surfaces |
209
+ | `scripts/agy-imprint.ts` | agy `PreInvocation` automatic birth + record-backed sender marker |
193
210
  | `pi-extensions/lib/entwurf-core.ts` | shared core (session-file lookup, identity read, explicit-extension args); some v1 exports now dead pending routing cleanup |
194
211
  | `protocol.js` | dependency-free shared wire constants (`<project-context` marker); single source for tsc emit + strip-types MCP paths |
195
212
  | `run.sh` | install (incl. `install-meta-bridge`), check-*/smoke-* gates, release-gate |
@@ -217,8 +234,8 @@ Code-level invariants pinned at the same time:
217
234
 
218
235
  ## Runtime Dependencies
219
236
 
220
- - `@modelcontextprotocol/sdk` and `zod` are the substrate runtime deps. With the Claude-first ACP plugin shipped, the Claude/ACP backend deps are pinned alongside them: `@agentclientprotocol/claude-agent-acp` (`0.54.1`), `@agentclientprotocol/sdk` (`1.1.0`), `@anthropic-ai/sdk` (`0.100.1`). The Codex/Gemini ACP packages stay out of scope (native already reaches Codex; Gemini/major tools use native).
221
- - `pi` (`@earendil-works/pi-ai`) on PATH at the pinned range (`>= 0.80.3 < 0.81` — devDep exact `0.80.3` + next-minor ceiling). Mismatches are caught by `check-dep-versions` / `check-pi-runtime-version`. 0.80 moved the standalone root `getModels()` to the deprecated `@earendil-works/pi-ai/compat` entrypoint; the curated Claude surface (`pi-extensions/lib/acp/models.ts`) imports `getModels` from `/compat` — the single subpath allowlisted in `check-pi-import-surface`. NOT the 0.80 provider-factory `providers/anthropic` subpath: although it typechecks, pi's extension loader (jiti alias map in pi-coding-agent `core/extensions/loader.ts`) resolves only the bare root, `/compat`, and `/oauth` for extensions — a `providers/*` import resolves to the unresolvable `dist/compat.js/providers/…` and crashes extension load (caught live by `smoke-resident-garden-guard`, not by static typecheck). This `/compat` use is an **extension-loader compatibility shim** chosen by loader constraint, not a preference for a deprecated API — the `<0.81` ceiling guards it; when 0.81 changes `compat` or the loader alias map, re-evaluate against whatever root/loader surface 0.81 then exposes.
237
+ - `@modelcontextprotocol/sdk` and `zod` are the substrate runtime deps. With the Claude-first ACP plugin shipped, the Claude/ACP backend deps are pinned alongside them: `@agentclientprotocol/claude-agent-acp` (`0.54.1`), `@agentclientprotocol/sdk` (`1.1.0`), `@anthropic-ai/sdk` (`0.100.1`). Codex/Gemini ACP packages stay out of scope; Codex is native/probe, agy is the shipped native-push Google lane, and Gemini ACP remains compatibility history rather than a current target.
238
+ - `pi` (`@earendil-works/pi-ai`) on PATH at the pinned range (`>= 0.80.7 < 0.81` — devDep exact `0.80.7` + next-minor ceiling). Mismatches are caught by `check-dep-versions` / `check-pi-runtime-version`. 0.80 moved the standalone root `getModels()` to the deprecated `@earendil-works/pi-ai/compat` entrypoint; the curated Claude surface (`pi-extensions/lib/acp/models.ts`) imports `getModels` from `/compat` — the single subpath allowlisted in `check-pi-import-surface`. NOT the 0.80 provider-factory `providers/anthropic` subpath: although it typechecks, pi's extension loader (jiti alias map in pi-coding-agent `core/extensions/loader.ts`) resolves only the bare root, `/compat`, and `/oauth` for extensions — a `providers/*` import resolves to the unresolvable `dist/compat.js/providers/…` and crashes extension load (caught live by `smoke-resident-garden-guard`, not by static typecheck). This `/compat` use is an **extension-loader compatibility shim** chosen by loader constraint, not a preference for a deprecated API — the `<0.81` ceiling guards it; when 0.81 changes `compat` or the loader alias map, re-evaluate against whatever root/loader surface 0.81 then exposes.
222
239
 
223
240
  ## Working Style
224
241
 
package/BASELINE.md CHANGED
@@ -6,10 +6,34 @@ silently drifted into a different identity / context surface. Questions
6
6
  are deliberately open-ended — they probe what the agent actually sees,
7
7
  not what it was told to claim.
8
8
 
9
- The 0.12 shipped ACP backend is **Claude**; the question bank below is the
10
- Claude baseline. Codex (pi-native, or ACP via `ENTWURF_ACP_FOR_CODEX=1`)
11
- and Gemini (non-goal/probe) are carried as the probe appendix, not the
12
- release baseline.
9
+ The 0.12 shipped ACP backend is **Claude**; the main question bank below is the
10
+ Claude ACP baseline. Antigravity (`agy`) is also shipped, but as a native-push
11
+ garden citizen rather than an ACP backend, so it has a separate citizen/round-trip
12
+ baseline below instead of being forced into Claude's overlay questions. Codex
13
+ (pi-native / delivery probe) and Gemini (historical non-goal ACP probe) remain
14
+ reference axes, not the shipped ACP baseline.
15
+
16
+ ## Release-host baseline — #51 repair cut
17
+
18
+ This table is the operator-facing support/certification view. It complements the
19
+ model interview below; a persuasive answer from the model cannot turn an unmeasured
20
+ host into a certified one.
21
+
22
+ | Surface | Automated artifact evidence | Direct/native evidence | Current verdict |
23
+ |---|---|---|---|
24
+ | Node 24 Linux package consumer | Required `artifact-consumer` CI: read-only candidate `.tgz`, checkout-invisible, non-root global install, PATH shims, frozen package root, path+sha256 regular-file fence, strict doctor fixture | None required for the package layout itself | Package-consumer shape verified. The planted Claude cache/owner/bridge are synthetic and prove no real Claude lifecycle. |
25
+ | Claude Code 2.1.217 exec form | `check-hook-launch-topology` + `check-claude-floor-coherence`; doctor oracle healthy fixture + 21 defect mutations | B2 actual Claude session on NixOS: args per element, literal `${HOME}`, direct parent, FileChanged exit 2 → idle wake | Runtime behavior verified at 2.1.217 on one host; this is the supported floor. |
26
+ | Claude Code 2.1.138 negative | Launcher empty-argv refusal + installer/doctor floor checks | B actual Claude session on NixOS: args discarded while runtime reported success | Unsupported; no shell-form fallback. |
27
+ | Maintainer NixOS installed package | Gates and B/B2 are green, but the installed artifact is intentionally stale before release | Post-release clean reinstall → new Claude session → installed doctor exit 0 **pending** | Not yet host-certified for the repair artifact. |
28
+ | hejdev6 Ubuntu installed package | Linux artifact-consumer gate models the package shape, not this machine | Post-release clean reinstall → new Claude session → installed doctor exit 0 **pending** | Recovery remains open; hand-patched hooks and validate output are not acceptance. |
29
+ | macOS Claude meta-bridge | No artifact-consumer job and no `/proc` live join | None | **Not yet verified/certified for this repair cut.** Installer refuses Darwin and doctor remains nonzero; uninstall permits Darwin to remove older managed state. This is not permanent—future native validation may reopen it, and package-level `os` stays unrestricted. |
30
+ | WSL2 / Windows | None | None | Unverified / outside this repair cut. |
31
+
32
+ **Operator acceptance rule:** on a claimed Claude host, reinstall from the released
33
+ artifact, restart every old Claude process, open a new session, and run the doctor
34
+ from that installed package. PASS means exit 0 with the live MCP↔sender↔receiver
35
+ join. `plugin validate`, a hand-inspected marker, or a synthetic fixture cannot
36
+ supersede doctor RED.
13
37
 
14
38
  ## How to use
15
39
 
@@ -48,10 +72,12 @@ expected isolation-closed response, **FAIL** = listed failure mode,
48
72
  > `check-acp-carrier-augment`) and the live `smoke-acp-memory-containment-live`;
49
73
  > this document records the model-side observation.
50
74
 
51
- ## Per-backend specifics
75
+ ## Per-ACP-backend specifics
52
76
 
53
- Pick the active backend's column before pasting a question block. Claude
54
- is the 0.12 shipped baseline; Codex/Gemini are probe reference.
77
+ Pick the active **ACP backend's** column before pasting a question block. Claude
78
+ is the 0.12 shipped ACP baseline; Codex/Gemini are historical probe reference.
79
+ Do not replace the Gemini column with agy: agy does not use this overlay/carrier
80
+ contract at all, and its shipped baseline is the native-citizen section below.
55
81
 
56
82
  | Slot | Claude *(shipped)* | Codex *(probe)* | Gemini *(probe)* |
57
83
  |---|---|---|---|
@@ -186,7 +212,37 @@ Per-question PASS / FAIL / NOTE for grading the model's response.
186
212
  ### Q-MCP — MCP enumerate
187
213
  - **PASS** — Exactly one: `entwurf-bridge`.
188
214
  - **FAIL** — Any second server appears, or `entwurf-bridge` missing.
189
- - **NOTE** — Codex naturally writes the name with underscores (`entwurf_bridge`); that is the agent-visible backend marker, not a mutation.
215
+ - **NOTE** — Codex naturally writes the name with underscores (`entwurf_bridge`); that is the agent-visible backend marker, not a mutation. The current server exposes five tools, including the manual `entwurf_register_native` fallback; MCP enumeration asks for the server name, not a stale four-tool count.
216
+
217
+ ---
218
+
219
+ ## Native-citizen baseline — Antigravity / agy (shipped)
220
+
221
+ This is not an ACP overlay interview. Run it in a **fresh agy conversation** after
222
+ `install-agy-bridge`, `install-agy-statusline`, and `install-agy-hooks`, with all
223
+ three doctors green. `PreInvocation` is the earliest lifecycle event, so a brief
224
+ `🪛 ? agy` before the first model invocation is honest; after that first invocation
225
+ the same conversation must have a garden id.
226
+
227
+ | ID | Check | PASS | FAIL |
228
+ |---|---|---|---|
229
+ | Q-AGY-BIRTH | Automatic birth | First invocation creates/attaches one record by native `conversationId`; statusline becomes `🪛 <garden-id> agy`. | Manual cwd matching or `entwurf_register_native` is required for normal birth; a new id appears on every turn. |
230
+ | Q-AGY-SELF | Sender identity | `entwurf_self` reports the same garden id, `origin=meta-session`, `agentId=meta-session/antigravity`, and `replyable:true` while the native probe is alive. | Anonymous `external-mcp`, unbacked marker accepted, model name substituted into `agentId`, or mailbox evidence used to infer replyability. |
231
+ | Q-AGY-SEND | Outbound attribution | `entwurf_v2` from agy reaches a sibling carrying that same sender garden id and `replyable:true`. | Receiver sees unknown host/wrong garden id, or sender ambiguity is silently guessed. |
232
+ | Q-AGY-REPLY | Same-conversation reply | Sibling replies with `entwurf_v2(target=<agy-gid>, intent=fire-and-forget)` and the message direct-injects into the same live agy conversation. | New conversation/spawn, mailbox file/doorbell, or a cosmetic delivered result with no live native route. |
233
+ | Q-AGY-OWNERSHIP | Install scope | MCP owns one server plus `mcp(entwurf-bridge/entwurf_v2)` only; statusline owns its subtree; hooks own one named hook. | Installer broadens YOLO policy (`command(*)`, `unsandboxed(*)`) or overwrites unrelated settings/hooks. |
234
+ | Q-AGY-CONCURRENCY | Evidence boundary | Separate agy processes have separate pid/start-key markers; same-pid concurrent model invocation is explicitly reported unsupported. | Claims that one pid can safely identify two simultaneously invoking conversations. |
235
+
236
+ The replyability formula is **record-backed identity AND live native-push probe**.
237
+ There is intentionally no receiver marker, `watchArmed`, mailbox, or owned-outcome
238
+ resume authority. The model field may exist in the meta-record/status display,
239
+ but `agentId=meta-session/antigravity` is the stable sender contract.
240
+
241
+ Recorded operator evidence (2026-07-13): automatic birth → gid/statusline → MCP
242
+ send with record-backed sender identity → same-gid native-push reply passed on a
243
+ live conversation; three simultaneous agy processes produced three distinct pid
244
+ and sender markers. This is live evidence for that host, not proof of unsupported
245
+ same-process concurrency.
190
246
 
191
247
  ---
192
248
 
@@ -218,6 +274,24 @@ prompt, and if so quote the visible text exactly:
218
274
 
219
275
  # HISTORY (pointer)
220
276
 
277
+ 2026-07-22 repair evidence: Linux artifact-consumer C is committed locally as
278
+ `328c66e` (not yet pushed at the time of this baseline update); B/B2 direct-native
279
+ observations and the exec-only production cut are documented in issue #51 and
280
+ VERIFY's host matrix. **Post-provenance C was re-proven rather than inheriting the
281
+ earlier green:** the first rerun correctly went RED because its stand-in Claude was
282
+ container PID 1, which the product rejects as an impossible/reparented owner. The
283
+ fixture now keeps an outer PID-1 shell and runs the consumer as pid 8; both default
284
+ pack-once and caller-preserved exact-tgz modes reached doctor PASS with marker
285
+ `ownerPid=8 (>1)` and identical artifact sha256. The preserved file's
286
+ inode/size/mtime/sha tuple was unchanged across acceptance. Evidence logs:
287
+ `/tmp/pi-tmux-entwurf-exact-final.log` and
288
+ `/tmp/pi-tmux-entwurf-default-final.log`; the digest belongs in the external cut log,
289
+ not inside this shipped file (embedding it would mutate the tarball it names).
290
+ This was the current `0.12.7-1` gate candidate, **not** the approved release artifact;
291
+ repeat exact mode after the separate `0.12.8-repair.0` version commit. Maintainer/
292
+ hejdev6 installed doctor GREEN remains deliberately pending until after release.
293
+
294
+
221
295
  Per-release baselines — the 0.9.0 garden-native identity cut (17 PASS / 0 FAIL /
222
296
  0 SKIP `/gnew`-inclusive gate, #28), and the older 0.8.x / 0.5.0 context-pressure
223
297
  baselines — live in **CHANGELOG.md and git history**, including the gate names of
package/CHANGELOG.md CHANGED
@@ -4,6 +4,52 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.12.8-repair.0 — 2026-07-22
8
+
9
+ ### Fixed
10
+
11
+ - **ACTION REQUIRED — reinstall the Claude meta-bridge and restart every already-open Claude Code session after upgrading to this release.** The old hook keyed sender and receiver liveness to `process.ppid`; on an observed installed host Claude retained a `/bin/bash -c` wrapper, so all markers were written under short-lived shell pids while the MCP child looked under the still-live Claude pid. The same Claude Code version also produced a direct join elsewhere and ordinary tail-exec tests never reproduced the trigger — so rather than keep fighting for a portable topology inside the shell form, **this release leaves the shell form entirely**. Every meta-bridge hook is now declared in Claude's **exec form**: `command` is a shipped `hook-launch.sh` and `args` is the real argv, so no shell is on the launch path at all. The launcher `exec`s its argv, which preserves the pid, and the hook's parent is therefore the Claude process itself on every host — structurally, not conditionally. Measured at Claude Code 2.1.217 (#51 B2): `args` elements arrive verbatim, a literal `${HOME}` is never expanded, and a `FileChanged` `asyncRewake` hook exiting 2 really does wake an idle session. The shell `$PPID` carrier, its ancestry walk and its missing-carrier contract are **removed**; the hook reads `process.ppid` directly. **entwurf now requires Claude Code `>=2.1.217` and enforces that floor itself, because upstream gives no fail-loud:** an older Claude passes `claude plugin validate` on the exec manifest and then, at runtime, DROPS the `args` array, runs `command` alone, and reports the hook as `exit_code: 0, outcome: success` — measured at 2.1.138. There is no shell-form fallback lane. `install-meta-bridge` and `doctor-meta-bridge` refuse an older version outright, and `hook-launch.sh` refuses an empty argv so that silence becomes a visible error on the host where it happens. **Upgrade mismatch stays deliberately fail-closed.** An already-open session still holding the OLD cached command reaches the new hook without the launcher, so the launcher stamps an explicit exec-launch provenance token and the hook mints NO sender/receiver marker without it: the meta-record still lands, but a session with no trusted send identity or deliverability evidence never claims one, and any older marker still on disk is not proof that the upgraded session joined correctly. Run `entwurf install-meta-bridge`, restart all existing Claude sessions so they load the new manifest, then run `entwurf doctor-meta-bridge`. The doctor refuses a shell-form or launcher-less manifest **by name**, requires the installed manifest to equal the shipped template modulo the two baked values, execs the installed argv directly (no shell) for a synthetic owner join, and on Linux requires live MCP↔sender↔receiver agreement — **missing live evidence is `NOT CERTIFIED` and a nonzero exit, not a WARN**. Linux is the only certified axis for this repair cut: `install-meta-bridge` refuses macOS because its live bridge cannot yet be discovered without `/proc`, doctor reports `NOT CERTIFIED`/nonzero there, and `uninstall-meta-bridge` alone keeps Darwin support so an older install can still be removed honestly. This is not a permanent impossibility claim; future native validation may reopen the macOS lane. New gates: `check-hook-launch-topology` drives the shipped argv for real, including a plugin path containing a space, `$`, a backtick and `;&`, and asserts the upgrade-mismatch fail-closed; `check-claude-floor-coherence` keeps the floor a single number derived from `package.json` `entwurf.claudeCodeFloor`.
12
+ - **The install gate pinned three of the four packages that make up the pi runtime, so it verified a runtime nobody verified.** `check-pack-install` builds a fresh temp project with no lockfile and pinned `pi-ai` / `pi-coding-agent` / `pi-tui` at the declared `0.80.7` — but `pi-coding-agent` depends on `@earendil-works/pi-agent-core` by CARET, so that fourth package floated to whatever pi published last and then dragged a NESTED `pi-ai` of its own. Measured 2026-07-21: the tree held `pi-agent-core@0.80.10` + `pi-ai@0.80.10` while the gate still printed `pinned pi 0.80.7`. Only the operator's stale pnpm metadata cache had been hiding it — refreshing the cache does not unblock the gate so much as let the unverified runtime in, which is why the earlier "npm registry partial publish" reading was wrong on both counts (`@aws-sdk/token-providers@3.1088.0` and `@earendil-works/pi-*@0.80.10` are both published; the local cache simply had not seen them). The gate now pins every `@earendil-works` package that constitutes the runtime **and reads the resolved tree back**, failing loud if any of them is not `0.80.7` — a pin is a wish until something asserts it.
13
+ - **A gate that cannot name which pi it proved has proved nothing.** `check-pack-install`'s loader and foreign-cwd smokes resolved `pi` through the **host PATH**, so the release commit's `install-surface` job went RED on a CI runner that carries no global pi — and the mirror image was worse: the gate had been green locally only because the dev box happened to carry a *newer* global pi (0.80.6) than the repo pinned (0.80.3). It was never driving the runtime it declares. Both smokes now drive `$tmp/node_modules/.bin/pi` — the pinned peer this gate already installs next to the tarball — and **assert `--version` against the `package.json` devDep** before proving anything with it. Review caught the fix reintroducing the very coupling it removes: the `--version` probe itself ran unsandboxed, and **pi reads settings before it prints its version** (`bootstrapSettingsManager` precedes the `--version` branch in pi's `main`), so the probe opened the operator's real `~/.pi/agent/settings.json` — 1 access before, 0 after (strace-verified). Every pi invocation in the gate now runs under one throwaway `HOME`/XDG/agent-dir env array. The rule this leaves behind: **a gate may not READ the operator's global install any more than it may WRITE it.** `AGENTS.md` rule 11 forbade only the write half, and the read half is the more insidious one — locally it is always green, so without CI it never surfaces.
14
+ - **The runtime-floor gate carried a pin that no gate enforced, and verified only half the range it declared.** `check-pi-runtime-version` held `const FLOOR = '0.80.3'` as a hand-kept literal that `check-dep-versions` had never seen: its assertions bind the devDeps to the peer range and to the `check-pack-install` peer-install pins, but nothing bound this constant, so a bump that forgot it would leave the runtime gate still blessing the OLD floor while every other pin moved. The floor is now **derived from the `package.json` devDep** (an exact `x.y.z` pin is required, or the gate fails loud): one pin to move, and the diagnostic names the real floor instead of a frozen string. The check is also **closed at the top** now, matching the range we actually declare (`>=<devDep> <0.<minor+1>`) — a floor-only comparison blessed any *future* pi, and "the runtime moved past the range while every gate stayed green" is this cut's entire subject. Mutation-checked in both directions: devDep `0.80.9` against the installed 0.80.6 fails the floor; devDep `0.79.9` (ceiling `0.80.0`) fails the ceiling.
15
+ - **`check-dep-versions` outlived its own doc coverage and kept advertising it.** The gate was born reading a doc: 362becd added it after the 21de0f9 pin drift (package bumped to 0.12.0, README left at 0.11.1) and asserted README's codex-acp install pin against `package.json`. bf4a533 then dropped the openclaw/ACP lane and removed that assertion with it — while leaving the coverage claim standing in the usage line and the function comment. So the doc half of the promise has been prose ever since, and the pi baseline docs were never bound to the gate at all. The pi pin lives in five of them, this bump touched all five, and what kept `demo/README.md` from being left behind was a hand-grep, not a gate. The docs are now **in** the gate: every closed-range declaration (`>=<floor> <0.<ceiling>`), every exact install pin (`@earendil-works/pi-<pkg>@<version>`), and four prose declarations that carry the pin in sentences those patterns cannot see (`current floor …`, `pi … fence`, `floor = **…**`, ``devDep exact `…` ``) must equal the devDep pin. A missing prose anchor fails loud rather than passing vacuously — a reworded sentence may not quietly drop the pin out of the gate — and a floor on the match counts guards against the whole doc scan silently matching nothing. History (`CHANGELOG` / `NEXT`) keeps its old versions; only sentences that *declare* the current pin are in scope. Mutation-checked: reverting `demo/README.md` alone to the old floor exits 1.
16
+ - **An unidentifiable Claude version could kill the installer/doctor before either printed its intended diagnosis.** The shared detector used `claude --version | head | grep | head` under the callers' inherited `set -euo pipefail`, and both callers captured it in a bare assignment. A nonzero CLI or output with no dotted triple therefore exited at the assignment instead of reaching the explicit install refusal / `NOT CERTIFIED` branch; a verbose writer also retained the known early-reader SIGPIPE shape. The detector now consumes the complete output, returns success-with-empty on failed/unparseable probes, and lets each caller own the diagnosis. `check-claude-floor-coherence` drives normal multi-line, unparseable, nonzero, and 128-KiB long-writer fixtures under `set -euo pipefail`, then invokes the real installer and doctor to prove they reach their own install-refusal / `NOT CERTIFIED` branches (and the doctor's final FAIL summary).
17
+
18
+ ### Added
19
+
20
+ - **A required Linux artifact-consumer CI lane now tests the package in a world that has never seen the checkout.** `check-install-container` makes one candidate tarball on the host, mounts only that file read-only into a Node-24 Linux container, installs globally as a non-root user through an isolated prefix, resolves all five bins through PATH, freezes the package root, boots MCP `tools/list`, and runs `install-meta-bridge` plus the strict doctor under a regular-file path+sha256 fence. The output records canonical artifact path + sha256 and container image id/repository digest. Default CI keeps its pack-once temp mode; release acceptance may set `ENTWURF_CANDIDATE_TGZ` to a caller-preserved `npm pack` output, in which case the gate verifies name/version and consumes that exact file without chmod/copy/re-pack so the accepted bytes can be passed directly to `npm publish <same.tgz> --tag repair`. Its fake Claude CLI, planted plugin cache, stand-in owner, and `/proc` bridge are labelled fixtures: this is L3 package/oracle evidence, not proof that a real Claude process installed the plugin or woke. Direct runtime evidence remains #51 B/B2 (actual 2.1.138/2.1.217 sessions on one NixOS host), and a production host is accepted only by the installed doctor against a new live session. The post-provenance rerun caught one fixture lie before final acceptance: the container runner itself was PID 1, so the product correctly refused it as an impossible owner. C now keeps an outer PID-1 shell and runs the stand-in Claude below it; both default and exact-artifact modes assert marker owner `>1`, reached doctor PASS, and consumed byte-identical candidates (recorded in BASELINE). The earlier pre-provenance green is not reused.
21
+
22
+ ### Changed
23
+
24
+ - **Release-preparation evidence:** the pre-version landing HEAD `8f566e01dc9c4510f7485d08bcdf370eb364a614` passed the exact-SHA GitHub Actions run [29899948565](https://github.com/junghan0611/entwurf/actions/runs/29899948565) with `check`, `install-surface`, and `artifact-consumer` all green. After the `0.12.8-repair.0` version change, `pnpm check` passed and `LIVE=1 ./run.sh release-gate /tmp/entwurf-release-gate-0.12.8-repair.0.LMTFbr` completed with `MUST: PASS=17 FAIL=0 SKIP=0` and advisory `BEHAVIOR: PASS=1 FAIL=0`; full log: `/tmp/entwurf-release-gate-0.12.8-repair.0.LMTFbr/release-gate.log`. The final preserved tarball, second exact-SHA CI, and artifact acceptance remain deliberately deferred to `make`.
25
+ - **Release operation is now one repo-local Agent Skill shared by Claude Code and pi, instead of two pi-only prompt files.** `.claude/skills/entwurf-release/SKILL.md` owns four explicit authority modes: `land` pushes a reviewed pre-version HEAD and requires its exact-SHA CI; `prepare` edits, gates, and commits without pushing; `make` pushes the prepared HEAD, requires the second exact-SHA CI, accepts one preserved candidate, and only then tags and creates the GitHub release; `publish` alone may send those accepted bytes to an explicitly named npm dist-tag. The sibling `verify-exact-ci.sh` oracle binds both CI checkpoints to the requested `headSha` and all three required jobs (`check`, `install-surface`, `artifact-consumer`) instead of trusting a branch badge. `.pi/settings.json` points pi at the same project skill directory, and the former `.pi/prompts/prepare-release.md` / `make-release.md` copies are removed, so the two harnesses no longer see different release hands. The shared version contract accepts ordinary SemVer prereleases such as `0.12.8-repair.0` (still no leading `v`).
26
+ - **The pi runtime pin moves 0.80.6 → 0.80.7 — a fix+minor release that carries none of 0.80.6's anchor risk.** Where 0.80.6 shrank the Anthropic catalog 24→14 and put `curatedClaudeModels()`'s `claude-opus-4-8` anchor one dropped row from a crash, 0.80.7 leaves `anthropic.models.ts` **byte-identical** — the anchor and `claude-sonnet-5` both survive untouched (source-diffed against `pi-mono v0.80.6..v0.80.7`). The `getModels` slice this repo consumes via `/compat` is unchanged; `compat.ts` also adds an `AMBIENT_AUTH_MARKER` ambient-auth path in `withEnvApiKey()`, but that behavior change never reaches the catalog/getModels surface entwurf imports, so it is irrelevant to this path. The extension loader's jiti alias map (`root` / `/compat` / `/oauth`) is byte-identical, so the `/compat` exception holds. The one breaking change — `OpenAIResponsesCompat.sendSessionIdHeader` replaced by `sessionAffinityFormat` — is referenced zero times in this repo (grep-verified), as are `supportsToolReferences` and the deferred-tool `addedToolNames`. `package-manager`'s new `--legacy-peer-deps` is on the npm uninstall path only (`packageManagerName !== "pnpm"`), so entwurf's pnpm install/uninstall is byte-identical; the dynamic-tool `wrapper.ts` change is a no-op for a static tool set (zero `getActiveTools` deltas mid-execution); `agent-session.ts` is a private `_getCompactionRequestAuth` → `_getSummarizationRequestAuth` rename (an ambient-auth branch-summary fix). `system-prompt.ts` drops the `Current date:` line for a prompt-cache win — safe here because entwurf's code and contract consume no default prompt date. devDeps / peer range (`>=0.80.7 <0.81`) / the `check-pack-install` peer pins / the five baseline docs move together under `check-dep-versions`, and the lockfile resolved with no transitive change beyond the four pi packages. `pnpm-workspace.yaml` gains four `minimumReleaseAgeExclude` entries that pnpm 11.9 records automatically for the <24h-old 0.80.7 pins.
27
+ - **The pi runtime pin moves 0.80.3 → 0.80.6, so the runtime we declare is the runtime that exists.** The global pi had already moved to 0.80.6 while the repo still pinned 0.80.3; that gap is what made the `check-pack-install` fix above matter, and closing it is the other half of the same repair. devDeps (`pi-ai` / `pi-coding-agent` / `pi-tui`), the peer range (`>=0.80.6 <0.81`), the `check-pack-install` peer-install pins, and the baseline statements in `AGENTS.md` / `README.md` / `ROADMAP.md` / `docs/setup-clean-host.md` / `demo/README.md` all move together, and `check-dep-versions` now fails if any one of them lags. The peer floor rises with the devDep on purpose: keeping `>=0.80.3` would declare support for a version no gate verifies. The lockfile resolved with no transitive change beyond the four pi packages (`pi-agent-core` is coding-agent's own dependency, not a new pin of ours).
28
+ - **What only the gates could answer, they answered.** 0.80.4 reworked `package-manager` (an `autoload:false` package delta plus a dedupe rewrite), `settings-manager`, and `resource-loader` — the exact three files our install surface stands on, and typecheck can say nothing about any of it. Under a **deliberately failing fake `pi` planted first on PATH** (stronger than merely unsetting the global one: an unqualified `pi` call exits 97 instead of silently working), `check-pack-install` drove the pinned 0.80.6 out of the install-smoke tree and kept both regressions green — the npm-managed install writing settings through a hoisted dep, and the user-scope citizen loading from a foreign cwd.
29
+ - **The Anthropic catalog SHRANK in 0.80.6, and that is a risk no type could have shown.** The registry drops ten legacy rows (`claude-3-*`, `claude-opus-4-0`, `claude-sonnet-4-0`, …), 24 model ids down to 14. `curatedClaudeModels()` calls `requireRegistryModel` on `claude-opus-4-8` and **crashes rather than fabricating a row** if the anchor is gone, so a bump that dropped it would have taken the provider surface down at extension load. Both curated rows survive with byte-identical `cost` / `contextWindow` / `maxTokens`. The only metadata added is `thinkingLevelMap` — Opus 4.8 goes `{xhigh}` → `{xhigh, max}` and Sonnet 5 gains the map outright (it had none) — and the curated rows copy neither, so the registered surface is unchanged. `ThinkingLevel` likewise gains `"max"` (a union widening this repo never consumes: it holds no exhaustive map over the type), and `cost` becomes the `ModelCost` superset with optional `tiers[]`, which passes through untouched because the curated surface copies `cost` wholesale and the provider gate asserts field presence, not an exact key set. Wiring thinking/effort remains a separate lane (#49 D), not a consequence of the bump.
30
+
31
+ ## 0.12.7 — 2026-07-14
32
+
33
+ ### Fixed
34
+
35
+ - **Three operator commands were dead in every installed package, and the class is now fenced in one place.** `entwurf doctor-pi-provider`, `entwurf new-session-id`, and `entwurf meta-bridge-prune` dispatch through `run.sh`, whose `REPO_DIR` sits under `node_modules` once installed — so each one executed a raw `.ts` and died on `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`. `new-session-id` is the alias `docs/setup-clean-host.md` tells operators to run, and `doctor-pi-provider` is the pi-ownership verdict, so both shipped broken while every dev clone stayed green. This is the same fence start.sh (0.12.1), the store-doctor (0.12.4), the plugin hook (0.12.5), and the agy imprint hook already crossed by hand — the fourth recurrence, and the reason it is no longer hand-written. All 75 `.ts` entrypoints in `run.sh` now route through a single `run_ts` helper that dispatches to the prepack-emitted JS when installed and keeps transparent source execution in a dev clone; a dev-only gate, which has no compiled twin by design, is REFUSED with a legible message rather than falling back to raw `.ts` or exiting 0 as a silent no-op.
36
+ - **The install surface is now verified, not assumed.** `check-pack-install` packed a real tarball but drove only bins — never a subcommand — which is precisely why the three commands passed every gate while being dead on arrival. It now executes them from the installed bin under `node_modules` and asserts MEANING, not the absence of a crash: the session id must match `SESSION_ID_RE`, the doctor must reach its own verdict body, and prune must walk the 0-record store it was handed. A new static `check-install-surface` closes the other half: `run_ts` is the only fence crossing (S1), every operator subcommand has a compiled twin whether it calls `run_ts` directly or through a helper — the house style (S2), no npm bin points at a raw `.ts` and every `.sh` bin that execs one branches on `node_modules` (S3), and dev-only gates stay out of the tarball (S4). Each S is mutation-checked against the bug it names; review found three bypasses in the first cut (a raw-`.ts` bin, an operator command hidden behind a helper, and a smoke that aliased the live path into a variable), and all three now fail the gate.
37
+ - **The agy bridge doctor no longer reports a working host as broken.** It demanded the literal string `mcp(entwurf-bridge/entwurf_v2)` in `permissions.allow`, so a host whose operator had granted a broad `mcp(*)` was told the bridge was "registered and unusable — agy prompts on EVERY entwurf_v2 call". That was false: agy matches `mcp(*)` and `mcp(<server>)` against our tool, which the doctor already knew — it read exactly that coverage in the `deny`/`ask` direction to detect shadowing, and then refused to read it in the `allow` direction. Coverage is now read both ways. The installer still writes only the narrowest rule it needs; the doctor distinguishes a grant we own (`allow → …`) from one the operator's broader rule is carrying (a NOTE that names the covering rule and warns that narrowing it takes the grant away) from a genuinely missing one (DRIFT). Deny/ask precedence is unchanged and still fails loud, including when the same broad rule sits in both lists. Ownership beats coverage in the other direction too (review follow-up): when the permission-state records that WE added the exact rule and it has since vanished, an operator wildcard keeping calls alive does not make the doctor green — it reports both axes (our grant gone, their rule covering) and stays red; a whole-file settings relink (agent-config `ensure_link`) produces exactly this shape, and only the statusline doctor caught it before.
38
+ - **agy doctors now bind install-state to the live file this host actually reads.** A hard-verification sweep moved `HOME` to `/tmp` but inherited the operator's real `XDG_DATA_HOME`, writing seven sandbox-target state records into `~/.local/share/entwurf`; all three doctors inspected those foreign files and reported green. Bridge, permission, statusline, and hook state now fail `FOREIGN TARGET` when their normalized managed path differs from the live target, and fail `CORRUPT` when the state body is unreadable or lacks its required path. Permission state is checked independently even if bridge state is absent. Runtime and ownership evidence remain separate: a resolvable live command is still reported as present while foreign/corrupt provenance keeps the final verdict red. A relative managed path is CORRUPT too (review follow-up): install only ever records absolute paths, and normalizing a relative one against the doctor's own cwd could bless whatever directory it happens to run from. Regressions cover foreign targets, corrupt/relative state, the independent permission-state rail, and wildcard-masked owned drift (agy install/statusline/hooks: 140/69/44).
39
+
40
+ ### Fixed (post-review, 2026-07-14 PM)
41
+
42
+ - **The offline floor no longer removes the operator's live pi-provider wiring while reporting green.** `smoke-user-scope-citizen` redirected `PI_CODING_AGENT_DIR` to fake settings but left `XDG_DATA_HOME` real. Its `run.sh remove-user-scope` drive therefore consumed the operator's real ownership state, followed that state's recorded `managedSettingsPath`, removed `entwurfProvider.mcpServers.entwurf-bridge` from the real `~/.pi/agent/settings.json`, and deleted the real state. This made the final bundled-MCP LIVE gate fail with `Connected MCP servers: (none registered)` after `pnpm check` had passed. Both inverse calls now pair the fake agent dir with fake XDG state, and the gate sandboxes HOME and the whole XDG trio up front, so the next root run.sh reaches for is already fenced. A before/after byte comparison proves the smoke leaves the live settings, the real `$XDG_DATA_HOME/entwurf` tree, and the real imprint log unchanged. Review then found the first S5c too narrow to hold the class it was written for: it fired only on the **inline-env** form, so hoisting the same override into an `export` one line up walked the identical leak past a green gate (mutation-proven), and its command list blessed `install`/`setup` drives that sandbox `PI_CODING_AGENT_DIR` — which those commands ignore, since `ensure_agent_dir_symlinks` hard-codes `$HOME/.pi/agent` and would still relink the operator's real agent dir. S5c now matches the **drive** (in any env form, and never in prose or an assertion string) and then demands the isolation that command actually needs at each root it writes. Cross-review closed one more ordering hole in that rewrite: a sandbox `export XDG_DATA_HOME` counts as isolation only for drives that come **after** it, so a trailing export can no longer retroactively bless a mutation that already ran against the live state (mutation-checked).
43
+ - **`check-pack-install` no longer leaks into the operator's real XDG roots — and proves it on every return path.** The gate swapped `HOME` per drive but inherited the operator's exported XDG roots, so its `run.sh install` drive wrote a **foreign pi-provider install-state into the real `~/.local/share/entwurf`** and the agy-imprint drive appended fake birth lines to the real `~/.local/state/entwurf/agy-imprint.log` — the same class as the 2026-07-13 hard-verify pollution, one layer deeper: inside `run.sh` itself, where S5/S5b (which scan only `scripts/*.sh`) cannot see. Every sandbox drive now exports `XDG_DATA_HOME`/`XDG_STATE_HOME`/`XDG_CACHE_HOME` alongside `HOME`. Review found the first self-fence was itself too narrow: it ran only on the success tail and fenced DATA while the known leak also touched STATE. The final **outer self-fence** runs after every success or early-failure path, requires the operator's real install-state tree to stay byte-identical, and requires the gate-specific fake agy marker count in the real imprint log not to increase. Dropping a drive's XDG swap is mutation-checked. A separate live audit also disproved the initial “all wiring intact” claim: Claude's XDG marketplace artifact was absent, producing `cache-miss`, no SessionStart records after 08:46, and an honest `🪛 ? cc`; this was a real meta-bridge disconnect, not agy's documented pre-first-turn `?`. Reinstalling the live meta-bridge restored `source=assembled=installed` parity and a fresh Claude probe automatically birthed garden id `20260714T121134-5effc4` (record count 116→117).
44
+
45
+ ### Changed
46
+
47
+ - **Backend drift pins moved, each with an explicit verdict.** agy `1.0 → 1.1` after live re-verification on the new minor (2026-07-14, agy 1.1.0: `entwurf_self` without a permission prompt, bidirectional native-push reply on the same gid, `LIVE=1 smoke-agy-native-push-live` 13/13 — evidence in `DELIVERY.md` §Antigravity). codex `0.136 → 0.144` as an **observed bump, not a re-verification**: codex is not a shipped native-citizen lane in 0.12.x, so the probe evidence stays dated at 0.136.0 and `DELIVERY.md` §Codex now carries the explicit non-reverification verdict; re-run the raw probes before building any codex adapter.
48
+ - **CI now runs the install surface, not just the source tree.** `pnpm check` is a dev-clone floor by construction: every fence bug this repo has shipped was green on it. `check-pack-install` was release-gate-only, so the installed axis had never been in CI at all. It is now its own job (~1 minute), which is what turns "we fixed it" into "it cannot come back".
49
+ - **Verification may not rewire the operator's own installation.** `check-install-surface` S5 flags an offline smoke that writes a live `~/.claude` / `~/.gemini` / `~/.pi` path before swapping the process HOME to a sandbox — or without swapping at all — including one hop of variable aliasing. The current tree is clean (the install smokes all `export HOME` to a sandbox first, and `smoke-resident-garden-guard`'s `rm -rf` targets a `mktemp -d`), so this pins the existing contract rather than fixing a live break. **It is a static tripwire, not a sandbox proof:** it reads shell source, so a path assembled across several variables or built inside an embedded heredoc would slip past it. The real guarantee — running the whole offline floor under a swapped HOME — is recorded in `NEXT.md` as open, not claimed here. S5b (review follow-up) pins the axis the 2026-07-14 pollution actually used: any offline smoke that swaps HOME into a sandbox must swap `XDG_DATA_HOME` with it, because HOME alone still writes real install-state below the inherited XDG root.
50
+
51
+ - **Install/package hygiene guards now seal three post-0.12.6 edges.** Package tarballs exclude Python bytecode residue even though `scripts/` ships as a whole, pack gates serialize the full `npm pack` dist-read window and use per-run tarball destinations, and the user-scope pi package inverse is exposed as explicit `remove-user-scope` with a read-only `--remove --dry-run` preview.
52
+
7
53
  ## 0.12.6 — 2026-07-03
8
54
 
9
55
  ### Fixed
package/DELIVERY.md CHANGED
@@ -18,10 +18,11 @@ Companion surfaces:
18
18
 
19
19
  ## Scope and non-goals
20
20
 
21
- This document is about **native live-session delivery** for the 0.12.0
22
- meta-bridge direction: a garden meta-session points at a backend-owned native
23
- session, and async messages reach that session through the backend's own
24
- supported surfaces.
21
+ This document is about **native live-session delivery** on the current 0.12.x
22
+ surface: a garden citizen points at a backend-owned native session, and async
23
+ messages reach that session through the backend's own supported surface —
24
+ mailbox wake for Claude Code, native-push for agy, or a launch-mode-specific
25
+ probe rail for Codex.
25
26
 
26
27
  Non-goals:
27
28
 
@@ -94,23 +95,23 @@ When a level is **not applicable** or **conditional**, say so explicitly. For
94
95
  example, Codex app-server delivery is conditional on a loaded thread and control
95
96
  socket; direct Codex TUI is a different surface.
96
97
 
97
- ## Current capability matrix (2026-06-24)
98
+ ## Current capability matrix (2026-07-22)
98
99
 
99
100
  This matrix is a snapshot of what the raw probes have established. It should be
100
101
  updated when a backend version changes the delivery surface.
101
102
 
102
- The **Status** column is the 0.12.0 release framing, kept separate from the
103
- `D0–D8` capability level:
103
+ The **Status** column is the current 0.12.x release framing, kept separate from
104
+ the `D0–D8` capability level:
104
105
 
105
- - **shipped** — a supported entwurf 0.12.0 lane: wired, gated, and addressable through the bridge today.
106
- - **verified-probe** — async delivery proven by a raw probe, but not yet a shipped/supported lane in 0.12.0 (documented, ships after this cut).
107
- - **deferred** — not addressable as-is, or needs an extra managed install / cloud surface that is out of 0.12.0 scope.
106
+ - **shipped** — a supported lane: wired, gated, and addressable through the bridge today.
107
+ - **verified-probe** — async delivery proven by a raw probe, but not yet a managed supported citizen lane.
108
+ - **deferred** — not addressable as-is, or needs an extra managed install / cloud surface outside the current release.
108
109
 
109
110
  | Harness / surface | Status | Highest current level | Transport | Notes |
110
111
  |---|---|---:|---|---|
111
- | **pi native Entwurf** | shipped | D7+ | Unix control socket + pi followUp/custom messages | Replyable pi session. This is the resident baseline, not an external meta-session. 0.12.0 `entwurf_v2` treats a record-less but live pi control socket as a socket-only `fire-and-forget` target (addressed by its socket, not a meta-record); record-less *dormant* resume is intentionally not claimed. |
112
- | **Claude Code interactive 2.1.163** | shipped | D6, D7 partial, D8 partial | Plugin/global `SessionStart` arms `watchPaths`; external write triggers `FileChanged`; `asyncRewake` wakes idle session | Active idle wake proven without pty. `Stop` alone is piggyback-only. `asyncRewake` is a doorbell; body is self-fetched from mailbox. D8 partial: duplicate/read idempotence, honest unread counts, and level-triggered body drain are gated; empirical wake-edge bounds and unread-heartbeat backstop remain open (#34). |
113
- | **Antigravity / agy** | verified-probe | D6+ | Native LS gRPC `agentapi send-message` | Active push into live conversation. Same judgement levels; transport differs from Claude. Delivery proven; a shipped adapter lane lands after the 0.12.0 doc cut. |
112
+ | **pi native Entwurf** | shipped | D7+ | Unix control socket + pi followUp/custom messages | Replyable pi session. This is the resident baseline, not an external meta-session. `entwurf_v2` treats a record-less but live pi control socket as a socket-only `fire-and-forget` target; record-less *dormant* resume is intentionally not claimed. |
113
+ | **Claude Code interactive >=2.1.217** | shipped *(Linux is the only certified axis in this repair cut)* | D6, D7 partial, D8 partial | Exec-form global plugin: `SessionStart` arms `watchPaths`; external write triggers exec-form `FileChanged`; `asyncRewake` wakes idle session | B2 direct-native at 2.1.217 on one NixOS host proved per-element argv, no shell expansion, parent join, and exit-2 idle wake. B at 2.1.138 proved the negative: `args` discarded while Claude reported success, so installer/doctor enforce 2.1.217 and there is no shell fallback. The launcher provenance token keeps an old cached command fail-closed. Active idle wake is D6; D7/D8 remain partial as before. The Linux container's planted cache/owner/bridge are fixtures, not a second native-host proof. |
114
+ | **Antigravity / agy** | shipped | D6, D7 partial | Native LS gRPC `agentapi send-message` (native-push) | `PreInvocation` automatically births/attaches by native `conversationId` and writes the record-backed pid/start-key sender marker; `entwurf_v2` fire-and-forget probes and direct-injects through the antigravity adapter with a one-shot re-probe retry. Three managed adapters own MCP+one exact permission, statusline, and hook separately. `entwurf_register_native` remains an explicit/manual fallback, not the normal birth path. Live sender→sibling→same-gid reply passed on 2026-07-13, re-verified at **agy 1.1.0** on 2026-07-14 (13/13 LIVE checks); D7 stays partial because there is no canonical transcript/content receipt owned by the smoke. |
114
115
  | **Codex app-server-backed TUI 0.136.0** | verified-probe | D6, D7 (status) | WebSocket-over-UDS `turn/start` into the live `threadId` | **Demonstrated, no managed standalone, no cloud.** `codex app-server --listen unix://<owned 0700 dir>` + plain `codex` auto-attach (or `--remote unix://`). Full message injection (agy-like, not a doorbell); `thread/status/changed` gives completion observation. D8 robustness (dedupe / crash recovery / ordering policy) is not tested. `turn/steer` is active-turn steering, not idle wake. |
115
116
  | **Codex embedded TUI 0.136.0** | deferred | D0 partial | Native state DB / rollout transcript only | Standalone Embedded TUI binds no socket; no `FileChanged`/`asyncRewake` in Codex hooks; not retrofittable. Identify-only via state DB / rollout. |
116
117
  | **Codex managed-daemon / remote-control 0.136.0** | deferred | D4–D6 conditional | `app-server proxy` newline JSON-RPC over the daemon control socket | Needs the managed standalone install; `remote-control` also enables the **cloud** bridge. Use the bare `--listen` path above for a purely-local setup. |
@@ -120,6 +121,24 @@ The **Status** column is the 0.12.0 release framing, kept separate from the
120
121
 
121
122
  ### Claude Code — filesystem event wake, not socket push
122
123
 
124
+ The current launch contract is **exec-only at Claude Code >=2.1.217**. All four
125
+ hook leaves run the shipped `hook-launch.sh` as `command` with the real argv in
126
+ `args`; the launcher stamps non-identity launch provenance and `exec`s the payload.
127
+ A hook reached through an old cached shell command still mints its record but writes
128
+ no sender/receiver marker, so an upgrade mismatch is fail-closed. Reinstall the
129
+ meta-bridge and restart all old Claude sessions before judging delivery.
130
+
131
+ Evidence boundary: B/B2 were real Claude sessions and therefore direct-native
132
+ runtime evidence, but both ran on one NixOS host. `check-hook-launch-topology` is a
133
+ deterministic execution proof of the shipped argv; `check-install-container` uses a
134
+ fake Claude, planted plugin cache, stand-in owner, and fake live bridge. Those fixtures
135
+ prove package/oracle behavior, not actual native session wake. A claimed Linux host
136
+ is accepted only when its **installed** strict doctor sees the live owner join and
137
+ exits 0; missing evidence is `NOT CERTIFIED`, not a partial delivery PASS. macOS is
138
+ not yet verified/certified for this repair cut: install refuses Darwin, doctor stays
139
+ nonzero, and only the uninstaller keeps Darwin support so an older managed install
140
+ can be removed. Future native validation may reopen that lane.
141
+
123
142
  A missing local listening socket does **not** imply idle wake is impossible.
124
143
  Claude Code interactive can be woken by a supported filesystem-event path:
125
144
 
@@ -158,8 +177,64 @@ Antigravity reaches the same delivery levels through a different transport:
158
177
  to make the garden layer backend-specific; it is exactly why the adapter contract
159
178
  must describe capability (`D0–D8`) separately from transport.
160
179
 
180
+ The raw probe (`scripts/raw-async-delivery/raw-agy-send.sh` — the Live-SSOT method
181
+ `pgrep -x agy` + an LS socket that answers `get-conversation-metadata`) is now
182
+ productionized as the **native-push rail**: `pi-extensions/lib/native-push/adapter.ts`
183
+ (full pid/LS scan, volatile route, 1-shot re-probe retry in the executor hand),
184
+ `registerNativeConversation` (bind an already-running conversation as a garden
185
+ citizen; no spawn), the `entwurf_v2` `native-push` transport (post-probe reject
186
+ taxonomy: `native-push-target-dead` / `-probe-indeterminate` / `-no-resume-authority`),
187
+ and the `install-agy-bridge` install adapter. agy is a `native-push` domain, distinct
188
+ from the pi control-socket liveness domain and from the Claude mailbox self-fetch domain.
189
+
190
+ #### agy ambient-status axis (install surface, orthogonal to D0–D8)
191
+
192
+ Beyond delivery, agy carries two more entwurf-owned install surfaces: **ambient
193
+ garden identity in the native statusline** (`entwurf-agy-statusline`) and the
194
+ **`PreInvocation` birth/sender imprint** (`entwurf-agy-imprint`). These are not
195
+ delivery levels — they are install-surface ownership axes with the same discipline
196
+ the delivery rail uses: bare stable bins only (never repo/checkout paths),
197
+ state-backed install/uninstall, element-level adopt-and-preserve with honest
198
+ inverse, symlink refusal, fail-loud doctors, and an honest `?` before identity
199
+ exists.
200
+
201
+ Identity authority is the native `conversationId` looked up against meta-record
202
+ **bodies**. No cwd back-match, filename-derived identity, or gid invention. agy
203
+ has no `SessionStart`; the earliest hook is `PreInvocation`, so a new conversation
204
+ may briefly render `🪛 ? agy`. On the first invocation the installed hook reads
205
+ `conversationId` + `workspacePaths`, calls `upsertMetaSession` idempotently, and
206
+ writes a sender marker only after the record exists. It always returns the neutral
207
+ `{"injectSteps":[]}` response so identity bookkeeping cannot block the agy loop.
208
+
209
+ The marker is keyed by the shared host pid + process start-key and is revalidated
210
+ against the record body. Replyability is `recordBacked ∧ probeAlive`, never
211
+ mailbox `watchArmed`. This supports separate agy processes (measured: three pids,
212
+ three markers) but **not** simultaneous model invocation by two conversations
213
+ under one agy pid: one marker file would be last-writer-wins, so that concurrency
214
+ is explicitly unsupported.
215
+
216
+ Current deterministic floor: `smoke-agy-install-state` 140 checks,
217
+ `smoke-agy-statusline-state` 69, `smoke-agy-hooks-state` 44,
218
+ `check-agy-sender-identity` 28, plus the shared self-address/native-push gates.
219
+ The bridge installer owns only `mcp(entwurf-bridge/entwurf_v2)` in
220
+ `permissions.allow`; broad YOLO policy stays operator-owned. Live 2026-07-13
221
+ (agy 1.0.x): automatic birth → gid/statusline → record-backed sender → sibling
222
+ delivery → same-gid native-push reply passed. Live 2026-07-14 (**agy 1.1.0**):
223
+ re-verified on the new minor — `entwurf_self` answered without a permission
224
+ prompt under the operator's broad allow (gid `20260714T101829-e7fccd`, native
225
+ conversation `21266946-64a6-4a35-a7e5-fc84f0a7f250`), bidirectional native-push
226
+ reply arrived on the same gid, and `LIVE=1 smoke-agy-native-push-live` passed
227
+ 13/13; the drift-sentinel agy pin moved to the 1.1 line on this evidence.
228
+
161
229
  ### Codex — split by launch mode, not by "Codex"
162
230
 
231
+ > **Version verdict (2026-07-14, 0.12.7 cut):** the installed codex is **0.144.1**;
232
+ > every claim in this section was measured at **0.136.0** and has **NOT been
233
+ > re-verified** since. Codex is not a shipped native-citizen lane in 0.12.x, so the
234
+ > drift-sentinel pin moved to the 0.144 line with this explicit non-reverification
235
+ > verdict instead of a fresh probe run. Re-run the raw probes (and re-date the matrix
236
+ > rows) before building any codex adapter on the new line.
237
+
163
238
  Do not describe "Codex" as one delivery shape. The split is the TUI's launch mode:
164
239
 
165
240
  - **standalone Embedded TUI**: binds no socket, no `FileChanged`/`asyncRewake` in
@@ -181,7 +256,7 @@ not addressability.
181
256
 
182
257
  A Codex adapter must declare which launch mode + which socket it targets.
183
258
 
184
- ## How to use this in 0.12.0 design
259
+ ## How to use this in the current 0.12.x design
185
260
 
186
261
  For meta-sessions, peer records should expose capability rather than hiding
187
262
  backend differences:
@@ -192,7 +267,7 @@ type WakeMode = "socket" | "file-watch" | "native-push" | "app-server" | "piggyb
192
267
  type DeliveryPeer = {
193
268
  sessionId: string; // garden id
194
269
  kind: "pi-session" | "meta-session";
195
- backend: "pi" | "claude-code" | "agy" | "codex" | string;
270
+ backend: "pi" | "claude-code" | "antigravity" | "codex" | string;
196
271
  replyable: boolean;
197
272
  wakeMode: WakeMode;
198
273
  deliveryLevel: "D0" | "D1" | "D2" | "D3" | "D4" | "D5" | "D6" | "D7" | "D8";