@junghanacs/entwurf 0.19.0 → 0.20.1

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 (74) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +23 -5
  3. package/CHANGELOG.md +261 -0
  4. package/DELIVERY.md +59 -6
  5. package/README.md +41 -9
  6. package/VERIFY.md +9 -1
  7. package/docs/acp-backend-rail.md +41 -16
  8. package/docs/external-mcp-host.md +10 -5
  9. package/docs/fresh-cut-policy.md +3 -2
  10. package/docs/setup-clean-host.md +93 -7
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +3 -3
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +6 -2
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +6 -0
  15. package/package.json +3 -3
  16. package/pi-extensions/acp-provider.ts +1 -1
  17. package/pi-extensions/lib/acp/acp-client.ts +3 -3
  18. package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
  19. package/pi-extensions/lib/acp/backend.ts +3 -3
  20. package/pi-extensions/lib/acp/event-mapper.ts +4 -4
  21. package/pi-extensions/lib/acp/models.ts +3 -3
  22. package/pi-extensions/lib/acp/overlay.ts +6 -2
  23. package/pi-extensions/lib/acp/tool-surface.ts +6 -0
  24. package/pi-extensions/lib/meta-session.ts +25 -0
  25. package/run.sh +104 -33
  26. package/scripts/agy-bridge.sh +1 -1
  27. package/scripts/agy-imprint.sh +10 -7
  28. package/scripts/check-acp-carrier-augment.ts +6 -5
  29. package/scripts/check-acp-cortex.ts +41 -0
  30. package/scripts/check-acp-provider-surface.ts +9 -6
  31. package/scripts/check-acp-usage-accounting.ts +9 -9
  32. package/scripts/check-copilot-birth-hook.ts +3 -1
  33. package/scripts/check-copilot-receive-arm.ts +169 -21
  34. package/scripts/check-copilot-statusline.ts +3 -1
  35. package/scripts/check-gate-qualification.ts +11 -10
  36. package/scripts/check-hook-launch-topology.ts +6 -5
  37. package/scripts/check-install-surface.ts +25 -8
  38. package/scripts/check-meta-facts.ts +6 -4
  39. package/scripts/check-meta-hook-session-switch.ts +4 -3
  40. package/scripts/check-meta-identity-consumers.ts +8 -7
  41. package/scripts/check-meta-receiver-marker.ts +2 -1
  42. package/scripts/check-omp-birth-hook.ts +3 -2
  43. package/scripts/check-probe-bridge-command.ts +3 -1
  44. package/scripts/check-setup-qualification.sh +21 -0
  45. package/scripts/copilot-bridge-doctor.sh +15 -5
  46. package/scripts/copilot-bridge-install.sh +6 -2
  47. package/scripts/copilot-launch.sh +16 -2
  48. package/scripts/copilot-receive-bridge.sh +80 -13
  49. package/scripts/lib/reclaim-on-exit.ts +86 -0
  50. package/scripts/meta-bridge-doctor.sh +56 -15
  51. package/scripts/meta-bridge-install.sh +13 -7
  52. package/scripts/meta-bridge-uninstall.sh +6 -4
  53. package/scripts/mutants/acp-cortex.json +26 -2
  54. package/scripts/mutants/copilot-receive.json +26 -0
  55. package/scripts/mutants/omp-birth.json +23 -6
  56. package/scripts/mutants/pi-package-ownership.json +26 -0
  57. package/scripts/mutants/setup-verdict.json +13 -0
  58. package/scripts/omp-bridge-doctor.sh +110 -41
  59. package/scripts/omp-bridge-install.sh +6 -2
  60. package/scripts/omp-receive-install.sh +7 -2
  61. package/scripts/pi_settings_io.py +3 -1
  62. package/scripts/raw-async-delivery/README.md +10 -1
  63. package/scripts/raw-codex-measure/README.md +689 -0
  64. package/scripts/raw-codex-measure/source-audit.md +243 -0
  65. package/scripts/raw-macos-measure/README.md +148 -0
  66. package/scripts/raw-macos-measure/probe.sh +389 -0
  67. package/scripts/register-pi-package.py +38 -30
  68. package/scripts/register-pi-provider.py +3 -2
  69. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  70. package/scripts/smoke-meta-async-drift.sh +7 -5
  71. package/scripts/smoke-meta-install-state.sh +70 -19
  72. package/scripts/smoke-omp-bridge-state.sh +38 -0
  73. package/scripts/smoke-setup-verdict.sh +100 -0
  74. package/scripts/smoke-user-scope-citizen.sh +199 -71
package/run.sh CHANGED
@@ -201,7 +201,7 @@ Usage:
201
201
  ./run.sh smoke-copilot-mcp-state # Copilot MCP install/doctor/inverse regression. Offline/deterministic
202
202
  ./run.sh smoke-omp-bridge-state # OMP birth-extension install/doctor/inverse regression: placement, stale-writer detection, honest inverse, no-state refusal (structurally VALID as well as foreign), symlink refusal, ambiguous-agent-dir refusal (ledger M6), and a poisoned PI_CODING_AGENT_DIR that attracts no artifact. Fully sandboxed HOME/PI/XDG. Offline/deterministic
203
203
  ./run.sh smoke-agy-hooks-state # agy PreInvocation birth/sender hook install/doctor/inverse + direct stdin→meta-record regression. Offline/deterministic
204
- ./run.sh smoke-user-scope-citizen # 0.12.6 install-boundary: pi packages[] registration SSOT (register-pi-package.py) — idempotent + preserves unrelated + remove symmetry + fails loud, and the #86 C2 explicit ownership cells (project scope still normalizes ITS OWN stale entries; user scope refuses other owners and only takeover-user-scope moves the shared entry). Offline/hermetic (deps: bash+python3)
204
+ ./run.sh smoke-user-scope-citizen # 0.12.6 install-boundary: pi packages[] registration SSOT (register-pi-package.py) — idempotent + preserves unrelated + remove symmetry + fails loud, and the #86 C2 explicit ownership cells (project scope still normalizes ITS OWN stale entries; user scope refuses other owners and only takeover-user-scope moves the shared entry), plus the #110 self-checkout cells: the real `run.sh install <checkout>` drive leaves the tracked .pi/settings.json byte-identical, writes NO project-scope packages[] self-registration, and REPORTS the skip; cell 15 boots the pinned devDep pi against two stand-in checkouts and requires the --entwurf-control collision to APPEAR before asserting its absence. Offline/hermetic (deps: bash+python3+pinned pi)
205
205
  ./run.sh smoke-meta-prune # 1.0.0 meta-bridge Phase 4: listing-only store janitor regression gate — classify keep/orphan/stale/ambiguous, delete nothing. Offline/deterministic (deps: bash+node)
206
206
  ./run.sh smoke-meta-keyset-guard # 0.10.0 meta-bridge: keyset-owner guard regression — check-keyset-overlap + managed-keys SSOT (disjoint passes, collisions fail). Offline/hermetic (deps: bash+python3)
207
207
  ./run.sh check-meta-manifest-schema # 0.12.2 meta-bridge: CLI-version-INDEPENDENT static guard — plugin manifests pinned to the minimal keyset that validates on the lowest supported Claude (closed-schema regression that broke 0.12.1 install on floor) + desired_mcp installed-vs-clone dual-mode. Offline (deps: python3)
@@ -209,7 +209,7 @@ Usage:
209
209
 
210
210
  ./run.sh install-meta-bridge # INTERNAL part of `setup` (native-harness plugin) + doctor recovery path — prefer `setup`; stateful GLOBAL install (plugin + USER MCP + settings keyset, honest uninstall state)
211
211
  ./run.sh uninstall-meta-bridge # 1.0.0 meta-bridge Phase 2: stateful GLOBAL uninstall (restore only keys/items captured in install-state)
212
- ./run.sh doctor-meta-bridge # THE RELEASE ORACLE (#51, Linux-certified repair axis). exit 0 = every required layer was MEASURED on this Linux host: toolchain + state + plugin/MCP + resolved-artifact launch-form classification (all 3 owner hooks + doorbell static contract) + synthetic owner join + store scan + hook errors + SessionStart evidence + REQUIRED live MCP↔marker join + writer-version parity. Missing live evidence is NOT CERTIFIED (open a Claude session and re-run), never a pass; Darwin is not yet verified/certified and stays nonzero for this cut (future validation may reopen it). Detection power is held by check-meta-doctor-oracle
212
+ ./run.sh doctor-meta-bridge # THE RELEASE ORACLE (#51, Linux-certified repair axis). exit 0 = every required layer was MEASURED on this Linux host: toolchain + state + plugin/MCP + resolved-artifact launch-form classification (all 3 owner hooks + doorbell static contract) + synthetic owner join + store scan + hook errors + SessionStart evidence + REQUIRED live MCP↔marker join + writer-version parity. Missing live evidence is NOT CERTIFIED (open a Claude session and re-run), never a pass; Darwin is NOT CERTIFIED and stays nonzero, on ONE narrowed reason — start-key and ppid are portable there (`ps` fallback, same string the TS core mints), so what holds the lane closed is per-process environment DISCOVERY alone, pending physical-host evidence. Detection power is held by check-meta-doctor-oracle
213
213
  ./run.sh copilot [args...] # #82 RAIL 7: the MANAGED Copilot launch. exec()s the vendor CLI in THIS terminal (cwd/pid/exit preserved) with COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS set for that one invocation — entwurf owns no part of your shell and writes nothing to it, but it owns the process it is about to become. Without that flag Copilot skips the extension scan SILENTLY. Refuses to launch unless the receiver unit is actually installed. Injects `--model auto` when no --model was given and `--yolo` when no explicit permission/surface policy flag was given, both BEFORE any `--`; every argument you pass is forwarded byte-identical. RUNNING THIS IS YOUR CONSENT to that profile — use plain `copilot` for stock vendor behaviour. Not tmux, not fresh-call, mints no citizen (birth is still the first prompt)
214
214
  ./run.sh install-copilot-bridge # #82: GLOBAL install of the Copilot BIRTH plugin (own marketplace root; node+entry baked into the no-argv exec string). MCP wiring and the RECEIVER extension are separate install surfaces (install-copilot-mcp, install-copilot-receive). Also retires the stale Claude unit (--keep-stale-claude-unit opts out)
215
215
  ./run.sh uninstall-copilot-bridge # #86 C3a: honest inverse of install-copilot-bridge from the package-owned install-state (exact qualified id + exact marketplace name/path + recorded assembly only; state deleted LAST; a failing vendor list is UNKNOWN and refuses; never --force, never the stale Claude unit)
@@ -537,7 +537,28 @@ install_local_package() {
537
537
  mkdir -p "$project_dir/.pi"
538
538
  # packages[] registration via the shared SSOT — same is_entwurf_source
539
539
  # predicate + idempotency as user-scope and remove (not a substring match).
540
- python3 "$REPO_DIR/scripts/register-pi-package.py" "$project_dir/.pi/settings.json" "$REPO_DIR"
540
+ #
541
+ # #110: entwurf's OWN checkout is the one project that never gets this entry.
542
+ # pi's package identity is `local:<resolved path>` and dedupePackages collapses
543
+ # only EQUAL identities (package-manager.js getPackageIdentity/dedupePackages),
544
+ # so a project entry naming this checkout and the user-scope entry naming the
545
+ # installed root are the same package only while they are the same directory.
546
+ # In a SECOND checkout (worktree, release scratch) they differ, pi keeps both,
547
+ # and both copies of pi-extensions/entwurf-control.ts declare
548
+ # `--entwurf-control`: detectExtensionConflicts gives the flag to whoever
549
+ # registered first — project scope — and fails the INSTALLED extension while
550
+ # printing the installed path as the loser, which reads exactly backwards.
551
+ # user-scope already owns self-registration from any cwd, so the project entry
552
+ # was only ever a duplicate of it; dropping it removes the second identity in
553
+ # written bytes, the way the tracked `.pi/settings.json` no longer carries one.
554
+ # A FOREIGN project still gets its entry — that is what `entwurf install
555
+ # <project>` is for, and an npm consumer's REPO_DIR is inside node_modules, so
556
+ # it can never take this branch.
557
+ if [ "$(cd -P -- "$project_dir" 2>/dev/null && pwd)" = "$REPO_DIR" ]; then
558
+ echo "install: project-scope packages[] skipped — entwurf's own checkout self-registers through user scope (#110)"
559
+ else
560
+ python3 "$REPO_DIR/scripts/register-pi-package.py" "$project_dir/.pi/settings.json" "$REPO_DIR"
561
+ fi
541
562
  # entwurfProvider.mcpServers.entwurf-bridge (project scope — checkout-local, NO state; #46
542
563
  # Task 2) via the shared register-pi-provider SSOT: normalize the command to the bare stable
543
564
  # bin `entwurf-bridge` (ownership-classified: absent/managed-current/managed-legacy adopt, a
@@ -707,8 +728,11 @@ remove_local_package() {
707
728
  # install, so remove never over-deletes a look-alike repo (entwurf-notes, …)
708
729
  # that install would never have registered. Same rule, one shape further (#53 B):
709
730
  # register only ever WRITES the absolute path, so a settings-RELATIVE entry naming
710
- # this repo (the committed `".."` in <repo>/.pi/settings.json) is source, not
711
- # install state — remove leaves it and says so instead of editing tracked bytes.
731
+ # this repo is source an operator hand-wrote, not install state — remove leaves it
732
+ # and says so instead of editing bytes it never authored. entwurf's own checkout
733
+ # no longer ships such an entry (#110: user scope owns self-registration), so for
734
+ # THIS repo the call is a reported no-op; the predicate stays because the shape is
735
+ # still writable by hand and still must not be over-deleted.
712
736
  python3 "$REPO_DIR/scripts/register-pi-package.py" "$project_dir/.pi/settings.json" "$REPO_DIR" --remove
713
737
  # entwurfProvider.mcpServers.entwurf-bridge cleanup (project scope) via the shared SSOT: strip
714
738
  # our-managed shapes (the bare stable bin AND the legacy repo start.sh path — a true user
@@ -2667,8 +2691,8 @@ check_acp_provider_surface() {
2667
2691
  # provider lib modules AND compiles + drives the REAL entry against a fake pi,
2668
2692
  # asserting: one surface name, the no-auth sentinel shape, full
2669
2693
  # ProviderModelConfig rows on the curated claude anchor, the EXACT curated model
2670
- # set both adapters contribute (claude 2 + cortex 4 —
2671
- # [QK:CORTEX-PROVIDER-SIX-ROW-SURFACE], the claim that catches an entry which
2694
+ # set both adapters contribute (claude 3 + cortex 4 —
2695
+ # [QK:CORTEX-PROVIDER-EXACT-ROW-SURFACE], the claim that catches an entry which
2672
2696
  # silently drops a whole backend), and that streamSimple is the real
2673
2697
  # streamShellAcp backend (checked BY NAME, never invoked — invoking spawns an
2674
2698
  # ACP child). Pure, no pi runtime, no API.
@@ -3564,6 +3588,7 @@ _check_pack_install_impl() {
3564
3588
  local loader_ids loader_expected
3565
3589
  loader_ids=$(awk '$1 == "entwurf" { print $2 }' <<<"$loader_out" | LC_ALL=C sort)
3566
3590
  loader_expected=$(printf '%s\n' \
3591
+ claude-fable-5-1 \
3567
3592
  claude-opus-5 \
3568
3593
  claude-sonnet-5 \
3569
3594
  cortex-auto \
@@ -3571,7 +3596,7 @@ _check_pack_install_impl() {
3571
3596
  cortex-claude-sonnet-5 \
3572
3597
  cortex-openai-gpt-5.4 | LC_ALL=C sort)
3573
3598
  if [ "$loader_ids" != "$loader_expected" ]; then
3574
- fail "[check-pack-install] installed provider model set drifted — expected exactly 6 curated ids (claude 2 + cortex 4):"
3599
+ fail "[check-pack-install] installed provider model set drifted — expected exactly 7 curated ids (claude 3 + cortex 4):"
3575
3600
  echo " expected:" >&2
3576
3601
  echo "$loader_expected" | sed 's/^/ /' >&2
3577
3602
  echo " actual (entwurf rows):" >&2
@@ -3582,8 +3607,8 @@ _check_pack_install_impl() {
3582
3607
  fi
3583
3608
  # Name the set on SUCCESS too, not only in the failure branch — same principle as
3584
3609
  # the pinned-pi version assert above: a gate that cannot name what it proved has
3585
- # proved nothing, and "6 rows" without the ids cannot be audited from a CI log.
3586
- echo "[check-pack-install] pi loader smoke pass (entwurf registered; exact 6-row curated set: claude 2 + cortex 4):"
3610
+ # proved nothing, and a row COUNT without the ids cannot be audited from a CI log.
3611
+ echo "[check-pack-install] pi loader smoke pass (entwurf registered; exact curated set: claude 3 + cortex 4):"
3587
3612
  echo "$loader_ids" | sed 's/^/ /'
3588
3613
 
3589
3614
  # npm-managed neutral install regression — the README's PRIMARY install path is
@@ -4815,6 +4840,44 @@ setup_result() { # $1=component $2=PASS|SKIP|FAIL $3=detail
4815
4840
  esac
4816
4841
  }
4817
4842
 
4843
+ # ── platform rail certification (#78 D1) ─────────────────────────────────────
4844
+ # ONE place answers "are entwurf's HARNESS RAILS certified on this platform?".
4845
+ # Every detected-harness branch in setup_all asks it through setup_harness_result
4846
+ # below instead of re-deciding it per backend — a verdict that is honest for one
4847
+ # harness and cosmetic for the next is its own defect, not half a fix.
4848
+ #
4849
+ # Today the answer is Linux and nothing else, and that is an EVIDENCE fact, not a
4850
+ # taste: every shipped rail proves live ownership by reading the per-process
4851
+ # ENVIRONMENT of live harness children, no portable Darwin equivalent has been
4852
+ # MEASURED, and BASELINE.md/README record each rail as `NOT CERTIFIED — pending
4853
+ # physical host`. The harness doctors own that axis and refuse on Darwin.
4854
+ #
4855
+ # What moves this line: a physical-host receipt — a real harness doctor exiting 0
4856
+ # on that platform, recorded in BASELINE.md. A green INSTALLER never moves it;
4857
+ # installation portability and rail/runtime support are separate evidence axes
4858
+ # (Hard Rule 17). Do not widen this to make a CI job green.
4859
+ harness_rail_certified_platform() {
4860
+ [ "$(uname -s)" = "Linux" ]
4861
+ }
4862
+
4863
+ # setup_harness_result — the verdict for a detected harness unit whose INSTALL
4864
+ # COMPLETED. It exists because "the wiring was written" and "the rail works here"
4865
+ # are two different facts and setup measures only the first: setup runs no doctor
4866
+ # (that is the doctor commands' own job). On a platform whose rails carry no
4867
+ # receipt a completed install is therefore a detected-but-INCOMPLETE integration,
4868
+ # which Hard Rule 17 names non-green rather than cosmetic success. setup_result
4869
+ # has exactly three verdicts, so FAIL IS that named non-green — and the detail
4870
+ # must say so in the operator's own words: nothing failed to install, the rail is
4871
+ # simply uncertified here. The wording stays deliberately distinct from the
4872
+ # install-failure FAIL each branch keeps for a real refusal.
4873
+ setup_harness_result() { # $1=component $2=what got wired $3=doctor owning the rail axis
4874
+ if harness_rail_certified_platform; then
4875
+ setup_result "$1" PASS "$2 — verify: $3"
4876
+ return 0
4877
+ fi
4878
+ setup_result "$1" FAIL "$2 — the wiring WAS written and nothing failed to install, but this harness rail is NOT CERTIFIED — pending physical host on $(uname -s). setup runs no doctor; the rail axis is owned by '$3', which refuses on this platform. Tracking: #78."
4879
+ }
4880
+
4818
4881
  # Installed-vs-source is decided by PACKAGE LOCATION — the same `*/node_modules/*`
4819
4882
  # seam run_ts and the bridge launcher already branch on — never by which of
4820
4883
  # pnpm/pi happens to be missing (an accidental failure is not a mode gate).
@@ -4955,7 +5018,7 @@ setup_all() {
4955
5018
  if ! pi_version_in_range "${pi_ver:-0}" "$pi_range"; then
4956
5019
  setup_result pi FAIL "detected pi ${pi_ver:-<unreadable version>} is outside the supported range $pi_range — Pi wiring not written; align pi, then re-run setup"
4957
5020
  elif (install_local_package "$project_dir"); then
4958
- setup_result pi PASS "project + user-scope wiring complete (pi $pi_ver in $pi_range) — verify: ./run.sh doctor-pi-provider"
5021
+ setup_harness_result pi "project + user-scope wiring complete (pi $pi_ver in $pi_range)" "./run.sh doctor-pi-provider"
4959
5022
  else
4960
5023
  setup_result pi FAIL "pi wiring did not complete (see above) — repair, then re-run setup"
4961
5024
  fi
@@ -4965,16 +5028,20 @@ setup_all() {
4965
5028
  # setup ALSO wires the native-harness meta-bridge so a relocate/clone needs ONE
4966
5029
  # command. Detection-gated ("있으면 설정, 없으면 담아준다");
4967
5030
  # meta-bridge-install.sh is idempotent and enforces the Claude floor + its own
4968
- # named prerequisites itself, so a below-floor or refused host lands here as a
4969
- # detected FAIL, never a cosmetic green. CLAUDE_BIN pins the probe for the
4970
- # hermetic smoke; production leaves it unset.
5031
+ # named prerequisites itself, so a below-floor host lands here as a detected
5032
+ # FAIL. It no longer REFUSES ON PLATFORM NAME (0.20.0 opened Darwin: the
5033
+ # installer's own mechanism is portable), so the other half of that old
5034
+ # guarantee — "never a cosmetic green" — is now carried by setup_harness_result:
5035
+ # an install that completes where the RAIL has no receipt is the named
5036
+ # non-green, never a PASS pointing at a doctor that would refuse. CLAUDE_BIN
5037
+ # pins the probe for the hermetic smoke; production leaves it unset.
4971
5038
  local claude_bin="${CLAUDE_BIN:-claude}"
4972
5039
  if ! command -v "$claude_bin" >/dev/null 2>&1; then
4973
5040
  setup_result claude SKIP "claude not on PATH — zero meta-bridge wiring written"
4974
5041
  else
4975
5042
  section "meta-bridge install (native harness detected: Claude Code)"
4976
5043
  if (cd "$REPO_DIR" && bash scripts/meta-bridge-install.sh); then
4977
- setup_result claude PASS "meta-bridge wired — verify: ./run.sh doctor-meta-bridge"
5044
+ setup_harness_result claude "meta-bridge wired" "./run.sh doctor-meta-bridge"
4978
5045
  else
4979
5046
  setup_result claude FAIL "detected Claude Code, but the meta-bridge integration did not complete (see above) — repair, then re-run setup"
4980
5047
  fi
@@ -5017,19 +5084,19 @@ setup_all() {
5017
5084
  else
5018
5085
  agy_rc=0; wire_agy_bridge || agy_rc=$?
5019
5086
  if [ "$agy_rc" -eq 0 ]; then
5020
- setup_result agy-bridge PASS "MCP bridge wired — verify: ./run.sh doctor-agy-bridge"
5087
+ setup_harness_result agy-bridge "MCP bridge wired" "./run.sh doctor-agy-bridge"
5021
5088
  else
5022
5089
  setup_result agy-bridge FAIL "detected agy, but the MCP bridge integration did not complete (reason above) — repair, then re-run setup"
5023
5090
  fi
5024
5091
  agy_rc=0; wire_agy_statusline || agy_rc=$?
5025
5092
  if [ "$agy_rc" -eq 0 ]; then
5026
- setup_result agy-statusline PASS "statusLine wired — verify: ./run.sh doctor-agy-statusline"
5093
+ setup_harness_result agy-statusline "statusLine wired" "./run.sh doctor-agy-statusline"
5027
5094
  else
5028
5095
  setup_result agy-statusline FAIL "detected agy, but the statusLine integration did not complete (reason above) — repair, then re-run setup"
5029
5096
  fi
5030
5097
  agy_rc=0; wire_agy_hooks || agy_rc=$?
5031
5098
  if [ "$agy_rc" -eq 0 ]; then
5032
- setup_result agy-hooks PASS "birth imprint wired — verify: ./run.sh doctor-agy-hooks"
5099
+ setup_harness_result agy-hooks "birth imprint wired" "./run.sh doctor-agy-hooks"
5033
5100
  else
5034
5101
  setup_result agy-hooks FAIL "detected agy, but the birth-imprint integration did not complete (reason above) — repair, then re-run setup"
5035
5102
  fi
@@ -5053,13 +5120,13 @@ setup_all() {
5053
5120
  section "copilot units (native harness detected: GitHub Copilot CLI)"
5054
5121
  copilot_rc=0; (cd "$REPO_DIR" && bash scripts/copilot-bridge-install.sh) || copilot_rc=$?
5055
5122
  if [ "$copilot_rc" -eq 0 ]; then
5056
- setup_result copilot-birth PASS "birth plugin installed — verify: ./run.sh doctor-copilot-bridge"
5123
+ setup_harness_result copilot-birth "birth plugin installed" "./run.sh doctor-copilot-bridge"
5057
5124
  else
5058
5125
  setup_result copilot-birth FAIL "detected copilot, but the birth plugin install did not complete (see above) — repair, then re-run setup"
5059
5126
  fi
5060
5127
  copilot_rc=0; (cd "$REPO_DIR" && bash scripts/copilot-mcp-bridge.sh install) || copilot_rc=$?
5061
5128
  if [ "$copilot_rc" -eq 0 ]; then
5062
- setup_result copilot-mcp PASS "MCP server registered — verify: ./run.sh doctor-copilot-mcp"
5129
+ setup_harness_result copilot-mcp "MCP server registered" "./run.sh doctor-copilot-mcp"
5063
5130
  else
5064
5131
  setup_result copilot-mcp FAIL "detected copilot, but the MCP registration did not complete (see above) — repair, then re-run setup"
5065
5132
  fi
@@ -5077,14 +5144,14 @@ setup_all() {
5077
5144
  else
5078
5145
  copilot_rc=0; (cd "$REPO_DIR" && bash scripts/copilot-receive-bridge.sh install) || copilot_rc=$?
5079
5146
  if [ "$copilot_rc" -eq 0 ]; then
5080
- setup_result copilot-receive PASS "receiver extension installed — verify: ./run.sh doctor-copilot-receive"
5147
+ setup_harness_result copilot-receive "receiver extension installed" "./run.sh doctor-copilot-receive"
5081
5148
  else
5082
5149
  setup_result copilot-receive FAIL "detected copilot, but the receiver extension install did not complete (see above) — repair, then re-run setup"
5083
5150
  fi
5084
5151
  fi
5085
5152
  copilot_rc=0; (cd "$REPO_DIR" && bash scripts/copilot-statusline-bridge.sh install) || copilot_rc=$?
5086
5153
  if [ "$copilot_rc" -eq 0 ]; then
5087
- setup_result copilot-statusline PASS "visible footer wired — verify: ./run.sh doctor-copilot-statusline"
5154
+ setup_harness_result copilot-statusline "visible footer wired" "./run.sh doctor-copilot-statusline"
5088
5155
  else
5089
5156
  setup_result copilot-statusline FAIL "detected copilot, but the visible-footer integration did not complete (see above) — repair, then re-run setup"
5090
5157
  fi
@@ -5107,13 +5174,13 @@ setup_all() {
5107
5174
  section "omp units (native harness detected: oh-my-pi)"
5108
5175
  omp_rc=0; (cd "$REPO_DIR" && bash scripts/omp-bridge-install.sh) || omp_rc=$?
5109
5176
  if [ "$omp_rc" -eq 0 ]; then
5110
- setup_result omp-birth PASS "birth extension installed — verify: ./run.sh doctor-omp-bridge"
5177
+ setup_harness_result omp-birth "birth extension installed" "./run.sh doctor-omp-bridge"
5111
5178
  else
5112
5179
  setup_result omp-birth FAIL "detected omp, but the birth extension install did not complete (see above) — repair, then re-run setup"
5113
5180
  fi
5114
5181
  omp_rc=0; (cd "$REPO_DIR" && bash scripts/omp-mcp-bridge.sh install) || omp_rc=$?
5115
5182
  if [ "$omp_rc" -eq 0 ]; then
5116
- setup_result omp-mcp PASS "MCP server registered — verify: ./run.sh doctor-omp-mcp"
5183
+ setup_harness_result omp-mcp "MCP server registered" "./run.sh doctor-omp-mcp"
5117
5184
  else
5118
5185
  setup_result omp-mcp FAIL "detected omp, but the MCP registration did not complete (see above) — repair, then re-run setup"
5119
5186
  fi
@@ -5124,13 +5191,13 @@ setup_all() {
5124
5191
  # shipping a citizen whose tools nobody can call.
5125
5192
  omp_rc=0; (cd "$REPO_DIR" && bash scripts/omp-config-xdev.sh install) || omp_rc=$?
5126
5193
  if [ "$omp_rc" -eq 0 ]; then
5127
- setup_result omp-config PASS "tools.xdev: false written — verify: ./run.sh doctor-omp-mcp"
5194
+ setup_harness_result omp-config "tools.xdev: false written" "./run.sh doctor-omp-mcp"
5128
5195
  else
5129
5196
  setup_result omp-config FAIL "detected omp, but the tools.xdev operator setting did not land (see above) — resolve it, then re-run setup"
5130
5197
  fi
5131
5198
  omp_rc=0; (cd "$REPO_DIR" && bash scripts/omp-receive-install.sh) || omp_rc=$?
5132
5199
  if [ "$omp_rc" -eq 0 ]; then
5133
- setup_result omp-receive PASS "receiver extension installed — verify: ./run.sh doctor-omp-receive"
5200
+ setup_harness_result omp-receive "receiver extension installed" "./run.sh doctor-omp-receive"
5134
5201
  else
5135
5202
  setup_result omp-receive FAIL "detected omp, but the receiver extension install did not complete (see above) — repair, then re-run setup"
5136
5203
  fi
@@ -5732,9 +5799,11 @@ case "$cmd" in
5732
5799
  check_shell_quote
5733
5800
  ;;
5734
5801
  check-install-surface)
5735
- # 0.12.7 — structural half of the node_modules strip-types fence: run_ts is the only
5736
- # crossing, every operator subcommand has a compiled twin, bin wrappers branch, dev
5737
- # gates stay out of the tarball, and offline smokes never touch the real $HOME.
5802
+ # structural half of the node_modules strip-types fence: run_ts is the only crossing,
5803
+ # every operator subcommand has a compiled twin, bin wrappers branch, dev gates stay out
5804
+ # of the tarball, offline smokes carry no OBVIOUS write to the real $HOME (a static
5805
+ # tripwire, never a sandbox proof), and S7 pins the candidate .pi/settings.json — the
5806
+ # shared release skill, the stable bridge bin, and NO packages key (#110).
5738
5807
  # check-pack-install owns the dynamic half (it drives the installed commands).
5739
5808
  run_ts scripts/check-install-surface.ts
5740
5809
  ;;
@@ -6202,10 +6271,12 @@ case "$cmd" in
6202
6271
  # XDG data dir ($XDG_DATA_HOME/entwurf/meta-bridge/.assembled — dev clone and
6203
6272
  # installed package alike, never the checkout) and runs marketplace add +
6204
6273
  # install --scope user, so every native Claude Code session auto-loads it.
6205
- # Idempotent; Linux is the only currently certified axis for the #51 repair cut.
6206
- # Darwin fails loud as not-yet-verified because the strict doctor cannot currently
6207
- # certify its live owner join; future validation may reopen it. Uninstall permits Darwin
6208
- # so an older macOS install is not stranded without an honest inverse.
6274
+ # Idempotent. Install accepts Linux | Darwin the fence that remains inside the
6275
+ # installer is the toolchain check (python3/node/claude presence), not the platform
6276
+ # name. Rail certification stays with doctor-meta-bridge: Darwin is NOT CERTIFIED
6277
+ # pending physical host, reason = per-process environment DISCOVERY. Uninstall
6278
+ # permits Darwin so an older macOS install is not stranded without an honest
6279
+ # inverse.
6209
6280
  (cd "$REPO_DIR" && bash scripts/meta-bridge-install.sh "$@")
6210
6281
  ;;
6211
6282
  uninstall-meta-bridge)
@@ -233,7 +233,7 @@ doctor_static_one() {
233
233
  # entwurf does NOT repair a failed launcher here: it may be owned by a foreign file.
234
234
  log " $label: configured → '$cmd' does NOT serve MCP with its configured args/env$link_note"
235
235
  log " $BOOT_DETAIL"
236
- log " Identify the launcher: command -v '$cmd'; readlink -f \"\$(command -v '$cmd')\""
236
+ log " Identify the launcher: command -v '$cmd'; ls -l \"\$(command -v '$cmd')\""
237
237
  log " If it is entwurf's managed dev link, restore it with ./run.sh expose-dev-bin (it REFUSES a foreign link)."
238
238
  log " If a foreign launcher owns the name, repair/remove it yourself or put a working one earlier on PATH."
239
239
  return 1 ;;
@@ -1,13 +1,16 @@
1
1
  #!/usr/bin/env bash
2
2
  # Stable bin wrapper for Antigravity PreInvocation birth imprint.
3
3
  set -euo pipefail
4
- SELF="${BASH_SOURCE[0]}"
5
- if command -v readlink >/dev/null 2>&1; then
6
- RESOLVED="$(readlink -f "$SELF" 2>/dev/null || printf '%s' "$SELF")"
7
- else
8
- RESOLVED="$SELF"
9
- fi
10
- HERE="$(cd "$(dirname "$RESOLVED")" && pwd)"
4
+ SOURCE="${BASH_SOURCE[0]}"
5
+ while [ -L "$SOURCE" ]; do
6
+ DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
7
+ TARGET="$(readlink "$SOURCE")"
8
+ case "$TARGET" in
9
+ /*) SOURCE="$TARGET" ;;
10
+ *) SOURCE="$DIR/$TARGET" ;;
11
+ esac
12
+ done
13
+ HERE="$(cd -P "$(dirname "$SOURCE")" && pwd)"
11
14
  ROOT="$(cd "$HERE/.." && pwd)"
12
15
 
13
16
  # Node refuses --experimental-strip-types for raw .ts below node_modules. Dev
@@ -31,8 +31,9 @@ import { loadEngraving } from "../pi-extensions/lib/acp/engraving.ts";
31
31
  import { bridgeConfigSignature, contextMessageSignatures } from "../pi-extensions/lib/acp/session-store.ts";
32
32
  import { buildClaudeSessionMeta } from "../pi-extensions/lib/acp/tool-surface.ts";
33
33
  import { ENTWURF_PROJECT_CONTEXT_OPEN_TAG } from "../protocol.js";
34
+ import { reclaimOnExit } from "./lib/reclaim-on-exit.ts";
34
35
 
35
- const tmp = mkdtempSync(join(tmpdir(), "acp-carrier-augment-"));
36
+ const tmp = reclaimOnExit(mkdtempSync(join(tmpdir(), "acp-carrier-augment-")));
36
37
  const REPO_DIR = fileURLToPath(new URL("..", import.meta.url));
37
38
  const BRIDGE_MARK = "operating through entwurf";
38
39
 
@@ -241,8 +242,8 @@ function ctxWith(firstUser: string): Context {
241
242
  // absent → keep it. Home AGENTS.md always survives.
242
243
  // ===========================================================================
243
244
  {
244
- const home = mkdtempSync(join(tmpdir(), "acp-home-"));
245
- const proj = mkdtempSync(join(tmpdir(), "acp-proj-"));
245
+ const home = reclaimOnExit(mkdtempSync(join(tmpdir(), "acp-home-")));
246
+ const proj = reclaimOnExit(mkdtempSync(join(tmpdir(), "acp-proj-")));
246
247
  writeFileSync(join(home, "AGENTS.md"), "HOME-AGENTS-CONTENT");
247
248
  writeFileSync(join(proj, "AGENTS.md"), "CWD-AGENTS-CONTENT");
248
249
 
@@ -295,7 +296,7 @@ function ctxWith(firstUser: string): Context {
295
296
  assert.match(dateLine as string, /^Current date: \d{4}-\d{2}-\d{2}$/, "date is day-granularity only (no clock time)");
296
297
 
297
298
  // > 50KB cwd AGENTS.md → augment truncated with the marker.
298
- const bigHome = mkdtempSync(join(tmpdir(), "acp-big-"));
299
+ const bigHome = reclaimOnExit(mkdtempSync(join(tmpdir(), "acp-big-")));
299
300
  writeFileSync(join(bigHome, "AGENTS.md"), "X".repeat(80 * 1024));
300
301
  const big = buildPiContextAugment({ backend: "claude", cwd: tmp, mcpServerNames: [], homeDir: bigHome });
301
302
  assert.ok(Buffer.byteLength(big, "utf8") <= 50 * 1024, "augment is truncated to the 50KB cap");
@@ -313,7 +314,7 @@ function ctxWith(firstUser: string): Context {
313
314
  // remains an honest fallback; the maintained package prompt must not hit it.
314
315
  // ===========================================================================
315
316
  {
316
- const budgetHome = mkdtempSync(join(tmpdir(), "acp-budget-home-"));
317
+ const budgetHome = reclaimOnExit(mkdtempSync(join(tmpdir(), "acp-budget-home-")));
317
318
  writeFileSync(join(budgetHome, "AGENTS.md"), "H".repeat(12 * 1024));
318
319
  const actual = buildPiContextAugment({
319
320
  backend: "claude",
@@ -323,6 +323,47 @@ const enrichedServers: AcpMcpServer[] = [
323
323
  /absolute realHome/,
324
324
  "a non-absolute realHome must refuse (the child must never re-derive it)",
325
325
  );
326
+ assert.throws(
327
+ () =>
328
+ ensureCortexDualHomeOverlay({
329
+ scopeKey: "pi:sess-empty",
330
+ mcpServers: [],
331
+ realHome: "",
332
+ overlaysRoot,
333
+ isPidAlive: () => true,
334
+ }),
335
+ /absolute realHome/,
336
+ "an empty realHome must refuse — the contract sentence is 'absolute', and '' is not",
337
+ );
338
+ // …and the guard states PATH-FLAVOR absoluteness, not the host it runs on:
339
+ // a drive/UNC realHome is absolute in the win32 flavor, so a POSIX host must
340
+ // NOT read it as relative. (This asserts nothing about native-Windows support
341
+ // — only that the refusal rule is decided by the path, not by process.platform.)
342
+ // realSnowflakeHome is pinned under tmp so the positive cells exercise the
343
+ // GUARD alone: the win32-flavored realHome never reaches a POSIX join for fs
344
+ // work, and every path the overlay materializes stays under overlaysRoot.
345
+ for (const winFlavored of ["C:\\Users\\x", "\\\\server\\share"]) {
346
+ let winOverlay: ReturnType<typeof ensureCortexDualHomeOverlay> | undefined;
347
+ assert.doesNotThrow(
348
+ () => {
349
+ winOverlay = ensureCortexDualHomeOverlay({
350
+ scopeKey: `pi:sess-win-${winFlavored.length}`,
351
+ mcpServers: [],
352
+ realHome: winFlavored,
353
+ realSnowflakeHome: realSnowflake,
354
+ overlaysRoot,
355
+ isPidAlive: () => true,
356
+ });
357
+ },
358
+ `a win32-flavored absolute realHome (${JSON.stringify(winFlavored)}) must PASS the guard — absoluteness is judged by PATH FLAVOR, never by the host the gate runs on [QK:CORTEX-REALHOME-PLATFORM-NEUTRAL]`,
359
+ );
360
+ assert.ok(
361
+ winOverlay !== undefined &&
362
+ winOverlay.scopeDir.startsWith(overlaysRoot) &&
363
+ existsSync(join(winOverlay.snowflakeHome, "cortex")),
364
+ "the accepted win32-flavored realHome must still materialize entirely under the overlays root (the guard is the only thing the flavor cell widens)",
365
+ );
366
+ }
326
367
  } finally {
327
368
  rmSync(root, { recursive: true, force: true });
328
369
  }
@@ -138,19 +138,22 @@ try {
138
138
  // Set comparison (sorted, exact) — not `includes`: a substring/anchor probe
139
139
  // cannot see a row that VANISHED, and the cortex ids contain the claude ids as
140
140
  // substrings. Expected is derived from the two source id constants so there is
141
- // one SSOT; the literal 6 below is the independent floor that catches a
142
- // curation list mutated to empty (which would move expected and captured
143
- // together).
141
+ // one SSOT; the literal 7 below is the independent floor that catches a
142
+ // curation list that SHRANK expected derives from the same constants, so a
143
+ // dropped row moves expected and captured together and the set comparison
144
+ // stays green. That is why the count is asserted separately and carries its
145
+ // own claim: the cortex list has `CORTEX-CURATED-FOUR-ROWS` for exactly this
146
+ // failure, and the claude list needs its own oracle rather than borrowing it.
144
147
  const expectedIds = [...SUPPORTED_ANTHROPIC_MODEL_IDS, ...SUPPORTED_CORTEX_MODEL_IDS].slice().sort();
145
148
  assert.equal(
146
149
  expectedIds.length,
147
- 6,
148
- `curated id constants must total 6 rows (claude 2 + cortex 4) — got ${expectedIds.length}: ${expectedIds.join(", ")}`,
150
+ 7,
151
+ `[QK:CLAUDE-CURATED-THREE-ROWS] curated id constants must total 7 rows (claude 3 + cortex 4) — got ${expectedIds.length}: ${expectedIds.join(", ")}`,
149
152
  );
150
153
  assert.deepEqual(
151
154
  capIds.slice().sort(),
152
155
  expectedIds,
153
- `[QK:CORTEX-PROVIDER-SIX-ROW-SURFACE] compiled entry must register the EXACT curated set of both adapters (claude 2 + cortex 4) — got: ${capIds.join(", ") || "none"}`,
156
+ `[QK:CORTEX-PROVIDER-EXACT-ROW-SURFACE] compiled entry must register the EXACT curated set of both adapters (claude 3 + cortex 4) — got: ${capIds.join(", ") || "none"}`,
154
157
  );
155
158
  assert.equal(typeof cap.cfg.streamSimple, "function", "entry streamSimple must be a function");
156
159
  // Regression guard: the entry must wire the REAL backend (streamShellAcp),
@@ -72,7 +72,7 @@ interface WireUsage {
72
72
  * One `_meta.quota.token_count` row. The field NAMES deliberately differ from
73
73
  * `PromptResponse.usage`: cache reads are `cachedInputTokens` here because the
74
74
  * shape is shared with codex-acp, and `cachedWriteTokens` is Claude's extra
75
- * sibling (read at claude-agent-acp 0.75.1 `dist/acp-agent.js:6493-6502`).
75
+ * sibling (read at claude-agent-acp 0.76.0 `dist/acp-agent.js:6566-6575`).
76
76
  * Reading a quota row with the `usage` field names silently yields zeros, so the
77
77
  * fixture below spells the vendor's names out rather than reusing `WireUsage`.
78
78
  */
@@ -137,7 +137,7 @@ function makeFakeChild() {
137
137
  *
138
138
  * Each `prompt` call consumes the next TurnScript: it first pushes that turn's
139
139
  * `usage_update` notification (the wire the running cost total actually arrives
140
- * on — read at claude-agent-acp 0.75.1 `dist/acp-agent.js:3467-3482`), then answers the
140
+ * on — read at claude-agent-acp 0.76.0 `dist/acp-agent.js:3471-3486`), then answers the
141
141
  * prompt with that turn's `PromptResponse.usage` (the wire the turn aggregate
142
142
  * arrives on). Both orderings are the real one: the notification precedes the
143
143
  * response, because the SDK emits it from the `result` message that ENDS the turn.
@@ -393,8 +393,8 @@ try {
393
393
  // AGENT LOOP only. `_meta.quota.model_usage` comes from `result.modelUsage` and
394
394
  // also counts Task subagents, sidechains and INTERNAL CALLS SUCH AS COMPACTION;
395
395
  // the vendor states its rows "can total more than `token_count`" and are "the
396
- // fuller picture, not a decomposition of it" (read at claude-agent-acp 0.75.1
397
- // `dist/acp-agent.js:6468-6474`).
396
+ // fuller picture, not a decomposition of it" (read at claude-agent-acp 0.76.0
397
+ // `dist/acp-agent.js:6541-6547`).
398
398
  //
399
399
  // The wide one is required, not merely nicer, because the DENOMINATOR already
400
400
  // has that scope: turn cost is the adjacent diff of the backend's running total,
@@ -608,8 +608,8 @@ try {
608
608
  // identity that produces the bound is a property of the MAIN AGENT LOOP's
609
609
  // cache breakpoints. `_meta.quota.model_usage` is a WIDER scope: the vendor
610
610
  // states those rows also count Task subagents, sidechains, and INTERNAL
611
- // CALLS SUCH AS COMPACTION (read at claude-agent-acp 0.75.1
612
- // `dist/acp-agent.js:6465-6485`). Mixing the two scopes inflates the bound
611
+ // CALLS SUCH AS COMPACTION (read at claude-agent-acp 0.76.0
612
+ // `dist/acp-agent.js:6538-6558`). Mixing the two scopes inflates the bound
613
613
  // through both remaining terms that mention cacheWrite:
614
614
  // max(0, occupancy − cacheWrite) shrinks as wide cacheWrite grows, so
615
615
  // less is subtracted; min(rawBound, cacheWrite) rises with it.
@@ -749,9 +749,9 @@ try {
749
749
  // CELL 3 — a turn with NO cost notification holds the baseline.
750
750
  //
751
751
  // Measured upstream: the result-path `usage_update` carries cost (read at
752
- // claude-agent-acp 0.75.1 `dist/acp-agent.js:3467-3482`), while other
752
+ // claude-agent-acp 0.76.0 `dist/acp-agent.js:3471-3486`), while other
753
753
  // `usage_update` paths can carry `used` without cost (for example the
754
- // rate-limit path at `:4269-4277`). A live thinkpad ledger shows such turns
754
+ // rate-limit path at `:4273-4281`). A live thinkpad ledger shows such turns
755
755
  // really occur. The honest handling is to HOLD the baseline so the amount lands
756
756
  // in the NEXT diff: misattributed by turn, exact by session. Rebaselining to 0
757
757
  // there would double-count the whole prefix.
@@ -831,7 +831,7 @@ try {
831
831
  // CELL 4 — a DECREASING session total is never silently absorbed.
832
832
  //
833
833
  // `conversation_reset` switches the session to a fresh transcript (read at
834
- // claude-agent-acp 0.75.1 `dist/acp-agent.js:4282-4289`), but whether that
834
+ // claude-agent-acp 0.76.0 `dist/acp-agent.js:4286-4293`), but whether that
835
835
  // changes `total_cost_usd` is an SDK-internal value we cannot observe here.
836
836
  // A diff can therefore go negative in a session we are still holding.
837
837
  // Absorbing it quietly would both misreport the turn and destroy the only
@@ -52,6 +52,7 @@ import { resolveFactList } from "../pi-extensions/lib/entwurf-facts.ts";
52
52
  import { nativePushSupported } from "../pi-extensions/lib/entwurf-v2-contract.ts";
53
53
  import { META_SENDER_BACKENDS, resolveTrustedMetaSenderIdentity } from "../pi-extensions/lib/meta-sender-identity.ts";
54
54
  import { listAllMetaIdentitiesDir, processStartKey } from "../pi-extensions/lib/meta-session.ts";
55
+ import { reclaimOnExit } from "./lib/reclaim-on-exit.ts";
55
56
 
56
57
  const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
57
58
  const PLUGIN = "entwurf-meta-receive-copilot";
@@ -63,7 +64,8 @@ function ok(label: string, cond: boolean): void {
63
64
  passed++;
64
65
  }
65
66
 
66
- const root = mkdtempSync(path.join(tmpdir(), "entwurf-copilot-birth."));
67
+ const root = reclaimOnExit(mkdtempSync(path.join(tmpdir(), "entwurf-copilot-birth.")));
68
+
67
69
  const asm = path.join(root, "asm");
68
70
 
69
71
  // ── 1. the real assembler ────────────────────────────────────────────────────