@junghanacs/entwurf 0.15.1 → 0.16.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 (91) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +354 -0
  3. package/DELIVERY.md +3 -2
  4. package/README.md +68 -88
  5. package/VERIFY.md +4 -1
  6. package/demo/README.md +1 -1
  7. package/docs/acp-backend-rail.md +1 -1
  8. package/docs/external-mcp-host.md +147 -9
  9. package/docs/setup-clean-host.md +125 -6
  10. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +65 -20
  11. package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +19 -10
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +17 -6
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +1 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +172 -5
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +152 -17
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +271 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +988 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-receive-omp.js +574 -0
  20. package/mcp/entwurf-bridge/dist/scripts/omp-receive-facts.js +84 -0
  21. package/mcp/entwurf-bridge/src/index.ts +65 -19
  22. package/mcp/entwurf-bridge/tsconfig.build.json +19 -3
  23. package/mcp/tsconfig.json +10 -0
  24. package/package.json +19 -9
  25. package/pi/entwurf-capabilities.json +1 -0
  26. package/pi/meta-bridge-omp/entwurf-meta-omp/package.json +7 -0
  27. package/pi/omp-receive/entwurf-receive-omp/package.json +7 -0
  28. package/pi-extensions/entwurf-control.ts +12 -9
  29. package/pi-extensions/lib/acp/backend-adapter.ts +19 -9
  30. package/pi-extensions/lib/acp/backend.ts +125 -7
  31. package/pi-extensions/lib/acp/claude-acp-launch.js +100 -0
  32. package/pi-extensions/lib/entwurf-self-address.ts +18 -7
  33. package/pi-extensions/lib/meta-sender-identity.ts +1 -1
  34. package/pi-extensions/lib/meta-session.ts +219 -5
  35. package/pi-extensions/lib/mux-fresh-call.ts +171 -17
  36. package/pi-extensions/lib/omp-fresh-preflight.ts +275 -0
  37. package/pi-extensions/meta-bridge-omp.ts +1244 -0
  38. package/pi-extensions/meta-bridge-receive-omp.ts +666 -0
  39. package/run.sh +399 -31
  40. package/scripts/check-acp-launch-namespace.ts +127 -0
  41. package/scripts/check-acp-prompt-lifecycle.ts +145 -2
  42. package/scripts/check-copilot-birth-hook.ts +28 -1
  43. package/scripts/check-entwurf-self-address.ts +31 -0
  44. package/scripts/check-gate-qualification.ts +7 -3
  45. package/scripts/check-harness-admission-parity.ts +143 -0
  46. package/scripts/check-meta-doctor-oracle.sh +20 -0
  47. package/scripts/check-omp-birth-hook.ts +1049 -0
  48. package/scripts/check-omp-fresh-preflight.ts +208 -0
  49. package/scripts/check-omp-receive-arm.ts +516 -0
  50. package/scripts/check-setup-qualification.sh +40 -2
  51. package/scripts/copilot-bridge-oracle.sh +14 -6
  52. package/scripts/fake-copilot-vendor.sh +4 -2
  53. package/scripts/inventory-verification-surface.ts +1 -1
  54. package/scripts/meta-bridge-hook-log.sh +9 -1
  55. package/scripts/mutants/acp-launch-namespace.json +34 -0
  56. package/scripts/mutants/acp-prompt-lifecycle.json +67 -2
  57. package/scripts/mutants/copilot-birth.json +10 -10
  58. package/scripts/mutants/mux-fresh-call.json +22 -22
  59. package/scripts/mutants/omp-birth.json +173 -0
  60. package/scripts/mutants/omp-fresh.json +300 -0
  61. package/scripts/mutants/omp-receive.json +135 -0
  62. package/scripts/mutants/pack-install.json +2 -2
  63. package/scripts/mutants/self-address.json +34 -0
  64. package/scripts/mutants/setup-verdict.json +35 -0
  65. package/scripts/omp-bridge-doctor.sh +315 -0
  66. package/scripts/omp-bridge-install.sh +221 -0
  67. package/scripts/omp-bridge-oracle.sh +154 -0
  68. package/scripts/omp-bridge-uninstall.sh +57 -0
  69. package/scripts/omp-config-xdev.py +310 -0
  70. package/scripts/omp-config-xdev.sh +76 -0
  71. package/scripts/omp-mcp-bridge.sh +320 -0
  72. package/scripts/omp-mcp-config.py +392 -0
  73. package/scripts/omp-receive-doctor.sh +246 -0
  74. package/scripts/omp-receive-facts.ts +106 -0
  75. package/scripts/omp-receive-install.sh +228 -0
  76. package/scripts/omp-receive-uninstall.sh +60 -0
  77. package/scripts/omp-tool-surface.py +400 -0
  78. package/scripts/raw-acp-child-exit-measure/README.md +285 -0
  79. package/scripts/raw-acp-child-exit-measure/acp-turn-population.py +89 -0
  80. package/scripts/raw-acp-child-exit-measure/reaper-correlation.py +47 -0
  81. package/scripts/raw-omp-measure/README.md +420 -0
  82. package/scripts/raw-omp-measure/probe-extension.ts +76 -0
  83. package/scripts/raw-omp-measure/probe-receive-surface.ts +250 -0
  84. package/scripts/raw-omp-measure/source-audit.md +414 -0
  85. package/scripts/smoke-omp-bridge-state.sh +221 -0
  86. package/scripts/smoke-omp-fresh-live.ts +497 -0
  87. package/scripts/smoke-omp-mcp-state.sh +327 -0
  88. package/scripts/smoke-omp-receive-live.ts +340 -0
  89. package/scripts/smoke-omp-receive-state.sh +196 -0
  90. package/scripts/smoke-setup-verdict.sh +48 -3
  91. package/scripts/tsconfig.json +2 -0
package/run.sh CHANGED
@@ -119,7 +119,7 @@ run_vitest() {
119
119
  usage() {
120
120
  cat <<'EOF'
121
121
  Usage:
122
- ./run.sh setup [project-dir] # ONE presence-driven composition (#86): mode-first (source bootstrap only on a checkout), then per-component PASS/SKIP/FAIL for pi (presence+floor)/claude/agy/copilot (all four units: birth→MCP→receiver→footer, independently) + stable dev bins + v2 install smoke; absent harness = zero-state SKIP, detected-incomplete = named FAIL + nonzero exit. Never installs a harness or touches credentials
122
+ ./run.sh setup [project-dir] # ONE presence-driven composition (#86): mode-first (source bootstrap only on a checkout), then per-component PASS/SKIP/FAIL for pi (presence+floor)/claude/agy/copilot (all four units: birth→MCP→receiver→footer, independently)/omp (four units: birth→MCP→tools.xdev setting→receiver, independently) + stable dev bins + v2 install smoke; absent harness = zero-state SKIP, detected-incomplete = named FAIL + nonzero exit. Never installs a harness or touches credentials
123
123
  ./run.sh release-gate [project-dir] [--cut] [--allow-skip-gemini] # SINGLE release gate: full static (pnpm run check:full) + the v2-native live gates (v2 matrix-live, check-bridge, doctor-pi-provider, RGG) + the ACP plugin acceptance floor (12 LIVE smokes: socket-citizen/raw-turn/overlay/provider/session-reuse/carrier-augment/memory-containment/rgg/mcp/skill/bundled-mcp/v2-send) + the one surviving axis the aggregate used to omit silently (claude-native-resume; Cortex stays a documented on-demand direct call) + the cross-harness delivery chain (smoke-entwurf-chain-live). TWO-TIER summary: MUST (release-blocking, owns the exit code — "green" applies here) + BEHAVIOR (advisory, non-blocking: RGG positives model-in-loop turn). STEP OUTCOME protocol: every step is INVOKED and reports its own PASS / SKIP (exit 97, a prerequisite it does not have) / FAIL — a skip is never counted as a pass. Without --cut this is the unattended diagnostic (SKIPs reported, exit 0). WITH --cut it is read as release acceptance and ANY MUST SKIP is red, which is what makes "a CUT needs LIVE=1, SKIP=0" executable instead of prose. --allow-skip-gemini accepted-but-ignored (back-compat). final cut authorization is GLG's.
124
124
  ./run.sh check-bridge # entwurf-bridge direct MCP smoke + protocol/negative-path test.sh (live substrate = v2 live smokes)
125
125
  ./run.sh check-entwurf-bridge-boot # deterministic gate (5d-5-pre, G1a/G1b/G1e/G1f, IN pnpm run check:full): boot start.sh under strip-types + assert v2 fence graph loads + entwurf_v2 and entwurf_resume_call registered/schema + the tools/list surface is EXACTLY the seven shipped garden verbs; tools/list only, no auth/side-effect
@@ -131,10 +131,13 @@ Usage:
131
131
  ./run.sh check-meta-v3-record # deterministic gate: the ONE live record schema (v3) — canonical serialize/round-trip/mint, foreign-generation rejections name fresh-cut with the actual version value, strict keyset, no API
132
132
  ./run.sh check-mailbox-receipt-state # deterministic gate (0.11 Stage 0 step 3B): mailbox receipt state schema + store (stamp→persist→read-back) in a temp mailbox, strict keyset, no API
133
133
  ./run.sh check-copilot-birth-hook # #82 gate: drives the real Copilot assembler into a temp dir, fires the baked launcher with NO ARGV (the way Copilot's `exec`-string schema forces), and requires a backend:"copilot" v3 record + attach + peer row + a SENDER marker the resolver joins back to that record, and still zero mailbox/receiver marker (who-sent needs a shared parent; a receiver needs a doorbell this backend has not got). Hermetic; no Copilot, no model turn
134
+ ./run.sh check-omp-birth-hook # #87 gate: drives the real OMP assembler into a temp dir, imports the ASSEMBLED index.ts into a MOCK omp host and fires session_start/session_switch. tui mints one backend:"omp" record + a sender marker keyed to the host's OWN pid (the one-process join) + the garden id on the status line; print/rpc/json mint NOTHING (hasUI is true on the rpc rows, as in the vendor); switch attaches on the same native id and mints the replacement on a new one; the CERTIFIED receiver reader still finds no marker. Also the four-root joint binding: extension and omp-labeled bridge child resolve the same sessions/mailbox/senders/receivers bundle against test-built literals (never the production resolver as its own oracle), under a poisoned PI_CODING_AGENT_DIR and under four distinct ENTWURF_META_* overrides; the override grammar is absolute-or-~ and both halves plus the doctor refuse anything else by name; and a drifted provenance label selects no root policy at all. Hermetic; no omp, no model turn
134
135
  ./run.sh check-copilot-receive-arm # #82 RAIL 5 gate: the REAL receiver installer + the REAL extension.mjs forked with a stubbed SDK. Arms only after birth, marker owned by the WATCHER pid, self-fetch dispatch answer, doorbell carries the garden id and NOT the body, id-drift/foreign-parent refusals. Hermetic; no Copilot, no model turn
135
136
  ./run.sh check-copilot-launch # #82 RAIL 7 gate: the MANAGED launch `entwurf copilot`, driven through its public address against a FAKE VENDOR on a sandbox PATH. Receiver precondition refusals, EXTENSIONS token + operator token preservation, injected defaults before the `--` terminator, byte-identical argv, the 11 explicit permission/surface policy overrides that suppress `--yolo`, exec (not fork) pid identity, exit passthrough, recursion refusal. Hermetic; no Copilot, no model turn
136
137
  ./run.sh check-copilot-statusline # #82 Copilot custom-footer renderer. session_id → ready/?/gid + rail `cop`; exit 0. IN pnpm check. No Copilot, no model turn
137
138
  ./run.sh check-entwurf-capabilities # deterministic gate (0.11 Stage 0 step 3C): backend capability registry (pi/entwurf-capabilities.json) — coverage==META_CITIZEN_BACKENDS + agrees with live META_BACKEND_DESCRIPTORS + strict keyset, no API
139
+ ./run.sh check-omp-fresh-preflight # #87 C: the OMP fresh preflight reproduces omp_agent_dir and the tools.xdev read in TS (it runs from two emit depths and cannot call a sibling script). This drives the SHIPPED shell/python leaves over the same inputs — refusals included — and requires the TS half to agree, so the reproduction cannot silently drift from the installer's own oracle
140
+ ./run.sh check-harness-admission-parity # #87 C: the EDGE the two closed parity loops never had. Every citizen backend is fresh-openable or a declared pre-#82 legacy admission whose exception a reader finds in DELIVERY.md — so a post-contract harness that mints records but cannot be opened by entwurf_fresh_call blocks the release package instead of only carrying an `unsupported` note (docs/adding-a-harness.md step 9)
138
141
  ./run.sh check-capability-bundle-reach # deterministic gate (IN pnpm check): re-ask EVERY shipped copy of meta-session (source + bridge bundle emit) whether metaCapabilitiesFilePath() reaches the registry — the artifact-depth check the source-path gates cannot make; needs a built dist, missing dist FAILS
139
142
  ./run.sh smoke-pi-attach # deterministic gate (#50 C2 checkpoint + C3 ACP tail): a pi session attaches as a V3 meta-record citizen (backend:"pi"), the gardenId is the RECORD's not pi's session id, the control socket is keyed on it, a re-open ATTACHES to the same address (never a second mint), the BUILT DIST ENTRY driven over MCP stdio lists the citizen + delivers entwurf_v2 to that socket with an RPC ack, and the ACP identity chain lands a send AS the host record (enrichMcpServersWithEnvelope env → bridge sender = host gardenId). mkdtemp-isolated; the live store is never read
140
143
  ./run.sh check-bridge-delivery # deterministic gate (IN pnpm run check:full): demo scene 3 recovered — seed strict meta-sender + armed receiver citizens in an isolated temp world, scrub ambient pi/sender carriers, drive the BUILT DIST ENTRY over MCP stdio through a real tools/call entwurf_v2, assert the .msg landed under the seeded sender + doorbell poked. DELIVERS through the artifact, not from source. ENTWURF_DELIVERY_SUBJECT=<launcher> replays the same scene against another consumer artifact (check-pack-install passes the npm-installed bin). No model/network/cost; stale or missing dist FAILS
@@ -184,6 +187,8 @@ Usage:
184
187
  ./run.sh smoke-agy-install-state # agy MCP + exact permission ownership regression: isolated HOME+XDG, adopt/state/inverse, symlink refuse, truthful setup outcomes. Offline/deterministic
185
188
  ./run.sh smoke-setup-verdict # #86 C1+C3b aggregate setup verdict fixture: all-absent SKIP/green, pi presence+floor, detected-FAIL nonzero, copilot four-unit composition (present=independent PASS/FAIL rows, absent=zero-state SKIP), installed-mode named branch, credential store untouched. Offline/deterministic
186
189
  ./run.sh check-agy-permission-matrix # AGY permission CONTRACT SPACE as a literal table (55 cells): parser-state × operation × settings × ownership × precedence with stated exclusion rules; expectations are hand-written literals, never read from the SUT. Offline/deterministic (deps: python3)
190
+ ./run.sh smoke-omp-fresh-live # #87 bundle C acceptance, RELEASE MUST: omp opened as ONE visible fresh sibling through the PUBLIC entwurf_fresh_call surface (never raw tmux), exact nonce callback -> garden id from the SENDER ENVELOPE -> record/backend identity -> addressed receive -> lastReadAt -> the drain visible in that same native session's own transcript. Needs LIVE=1 and spends real model turns. Self-deciding: omp outside FRESH_CALL_BACKENDS or without a mailbox rail is a protocol SKIP, which --cut reads as RED
191
+ ./run.sh smoke-omp-receive-live # #87 bundle B acceptance: another harness wakes an ALREADY-OPEN omp citizen, which reads its inbox and answers in the same session. Decides its own outcome from the capability registry — omp without a drainable mailbox is a protocol SKIP (no receiver unit exists to arm), which an unattended release-gate reports and `--cut` reads as RED; a registry that claims a receive rail with no acceptance body here is a FAIL
187
192
  ./run.sh smoke-entwurf-chain-live # LIVE cross-harness delivery chain: native Claude Code -> pi GPT -> pi ACP Sonnet -> mailbox terminus, proving sender identity/replyable at every hop and a real read receipt at the end. Prerequisites (claude on PATH, pi credentials per backend) report protocol SKIP, never a pass
188
193
  ./run.sh check-release-gate-outcomes # release-gate STEP OUTCOME protocol (P1): one skip exit code shared by the shell + TS halves, classifier never rounds a skip up to a pass, `--cut` refuses a MUST SKIP while a bare diagnostic stays exit 0, no LIVE smoke keeps the old exit-0 skip shape, and both real skip surfaces are INVOKED and observed to propagate the code
189
194
  ./run.sh check-gate-qualification # kill-proof qualification (the gate-of-gates): runner self-test (classifier truth table + synthetic negatives incl. wrong-reason/hang/control-red/impurity) + committed mutant manifests (scripts/mutants/*.json) run in an isolated snapshot repo under control→mutant→restore→control; the real checkout is never written. Evidence = claim IDs + killed mutant IDs, never assertion counts
@@ -192,6 +197,7 @@ Usage:
192
197
  ./run.sh smoke-agy-statusline-state # agy ambient garden-id statusLine install/doctor/inverse regression. Offline/deterministic
193
198
  ./run.sh smoke-copilot-statusline-state # Copilot custom-footer install/doctor/inverse regression. Offline/deterministic
194
199
  ./run.sh smoke-copilot-mcp-state # Copilot MCP install/doctor/inverse regression. Offline/deterministic
200
+ ./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
195
201
  ./run.sh smoke-agy-hooks-state # agy PreInvocation birth/sender hook install/doctor/inverse + direct stdin→meta-record regression. Offline/deterministic
196
202
  ./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)
197
203
  ./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)
@@ -212,6 +218,19 @@ Usage:
212
218
  ./run.sh install-copilot-mcp # #82 RAIL 5: register ONE entwurf-bridge server in ~/.copilot/mcp-config.json (adopt / create / REFUSE symlink), type:local, install-state under $XDG_DATA_HOME/entwurf/copilot-mcp/
213
219
  ./run.sh uninstall-copilot-mcp # honest inverse of install-copilot-mcp from install-state
214
220
  ./run.sh doctor-copilot-mcp # static ownership/config/boot doctor; RED only when install-state exists
221
+ ./run.sh install-omp-config # #87 follow-on: write the ONE operator setting omp's tool hand requires — `tools.xdev: false` in <omp agent dir>/config.yml. Owns exactly the line(s) it adds (recorded in install-state), refuses a symlinked config, refuses a config it cannot parse, and refuses an EXPLICIT operator `xdev: true` by name rather than overwriting a decision. Without it the vendor default wraps every MCP tool behind `xd://` and the doorbell announces a tool the model cannot call
222
+ ./run.sh uninstall-omp-config # honest inverse from install-state: takes back exactly the recorded line(s), removes the file only when entwurf created it, REFUSES when the config changed since install
223
+ ./run.sh install-omp-bridge # #87: install the OMP BIRTH extension into <omp agent dir>/extensions/entwurf-meta-omp (index.ts|js + lib + capability registry). No launcher and no bake — an omp hook is an in-process extension. Refuses when an inherited PI_CODING_AGENT_DIR/PI_CONFIG_DIR/PI_PROFILE makes the target agent dir ambiguous (ledger M6), and refuses ANY pre-existing artifact at the unit path that entwurf holds no ownership state for — a shape is not a proof of ownership
224
+ ./run.sh uninstall-omp-bridge # honest inverse from install-state (exact unit dir + recorded entry; no-state host REFUSES; state deleted LAST). Records already minted are preserved
225
+ ./run.sh doctor-omp-bridge # #87: runtime axis (importable unit, writer/registry parity, mint vs sender-marker errors on SEPARATE axes, scope-fence receipts, a root-grammar preflight that goes RED on a relative ENTWURF_META_* override instead of reporting on some other directory, CERTIFIED omp record count via meta-facts under the omp root policy — never a text grep, live omp processes carrying inherited PI_SESSION_ID/PI_AGENT_ID) + ownership axis. PI_CODING_AGENT_DIR is the vendor's own agent dir here and is reported as ignored, never as contamination. Zero records = NOT-YET, never red
226
+ ./run.sh install-omp-receive # #87 bundle B: install the OMP RECEIVER extension into <omp agent dir>/extensions/entwurf-receive-omp. Own unit, own install-state, own doctor, own inverse. It joins the citizen BIRTH minted in the same process (sender marker for this pid + V3 record + the vendor's current session id, all three or refuse), holds an fs.watch on that citizen's mailbox signal, and rings an ANNOUNCE-ONLY doorbell via pi.sendUserMessage. It never assumes it loads after birth: extension handlers run in directory-name order and a before-sorting unit was MEASURED to see no marker, so the arm retries on a bounded ctx.setInterval cancelled with ctx.clearTimer (the vendor exposes no clearInterval)
227
+ ./run.sh uninstall-omp-receive # honest inverse from install-state; records and sender identity untouched. A session that ALREADY armed keeps its marker until its process exits
228
+ ./run.sh doctor-omp-receive # runtime axis (importable unit, writer parity, arm failures and doorbell failures on SEPARATE axes, who is armed RIGHT NOW through the production marker reader) + ownership axis. Zero armed receivers = NOT-YET, never red. Reports a marker for exactly what a marker proves — a live owner reached the arm emit — and never upgrades that into "a wake will happen"
229
+ ./run.sh omp-receive-facts # read-only JSON projection of the omp receive rail: every receiver marker through readMetaReceiverMarker in BOTH readings (live / as-written) plus mailbox counts. The shared oracle so no consumer answers "is a doorbell held?" from a filename
230
+ ./run.sh install-omp-mcp # #87 step 5: write ONE omp-native entwurf-bridge server into <omp agent dir>/mcp.json with env ENTWURF_BRIDGE_EXTERNAL_AGENT_ID=external-mcp/omp. The server key is a PINNED LITERAL, byte-identical to the Claude import's — same-key first-wins at native=100 > claude=80 is what SHADOWS the import (never disabledServers, which kills both). Adopt / create / REFUSE symlink, preimage recorded
231
+ ./run.sh uninstall-omp-mcp # honest inverse from install-state (restores the preimage, or removes a file we created); the Claude import becomes effective again
232
+ ./run.sh doctor-omp-mcp # ownership + config + boot doctor, plus the EFFECTIVE-source read (native-wins / native-invalid / import-wins / both-suppressed) — a configuration read, never a runtime receipt. Runtime validity and ownership are SEPARATE axes: foreign provenance, a disabledServers denylist, and a malformed entry under our key are each RED even with no install-state. tools.xdev is a RUNTIME cell: absent file/key applies the vendor default (true, no inline allowlist) and is RED while the native hand is the effective source; xdev false is ok; a covering xdevInlineDevices glob is ok-with-note
233
+ ./run.sh smoke-omp-mcp-state # OMP MCP install/doctor/inverse regression incl. shadowing, the denylist refusal, target confinement + retarget refusal, and a malformed entry going RED with zero install-state. Fully sandboxed HOME/PI/XDG. Offline/deterministic
215
234
  ./run.sh install-copilot-receive # #82 RAIL 5: install the RECEIVER extension (user scope ~/.copilot/extensions/entwurf-receive). Owns the artifact and CHECKS the launch flag; it never sets one (a launch does — see `./run.sh copilot`). Arms per session after birth
216
235
  ./run.sh uninstall-copilot-receive # honest inverse from install-state; never removes a unit it did not install
217
236
  ./run.sh doctor-copilot-receive # artifact + digest, COPILOT_CLI_ENABLED_FEATURE_FLAGS on the LIVE copilot processes (the silent failure), live receiver markers via the production reader, receiver log. RED only when install-state exists
@@ -233,9 +252,9 @@ Usage:
233
252
  ./run.sh check-dep-versions # local deterministic check that the pi pin agrees across package.json (devDeps + peer range), run.sh (peer-install pins), and the baseline docs (AGENTS/README/ROADMAP/setup-clean-host/demo)
234
253
  ./run.sh check-node-floor-coherence # binds the Node floor (24+, single axis) across engines.node, run.sh setup preflight, meta-bridge install/doctor judgment logic, clean-host docs, the bridge launcher header, and the CI runner node-version — engines.node is the SSOT, everything else is derived; sweeps tracked contract text for an unregistered declaration
235
254
  ./run.sh check-pack # publish gate (dry-run): npm pack --dry-run + tarball invariants (runtime-critical present, dev residue absent)
236
- ./run.sh check-pack-pin-matcher # pure self-test of check-pack-install's pin-leak matcher against synthetic .pnpm lookalikes (version boundary: @0.84.30 must leak, @0.84.3 bare/peer-hash must pass); snapshot-safe qualification oracle, also run first inside check-pack-install
255
+ ./run.sh check-pack-pin-matcher # pure self-test of check-pack-install's pin-leak matcher against synthetic .pnpm lookalikes (version boundary: @0.84.40 must leak, @0.84.4 bare/peer-hash must pass); snapshot-safe qualification oracle, also run first inside check-pack-install
237
256
  ./run.sh check-fresh-cut-gate # SOURCE cell of the generation-boundary proof (IN pnpm run check:full): drives real install/setup/fresh-cut in a sandbox; certification refusal is pre-write, quiescence is fail-closed, archives preserve bytes, and the #54 exit matrix distinguishes complete / no-move / usage / incomplete transition / complete-with-cleanup-residue. No model/network/cost
238
- ./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke with the pinned pi peers (pins derived from the package.json devDep; check-dep-versions binds them) + the npm-installed bridge BOOTS (tools/list) and DELIVERS (tools/call entwurf_v2 → .msg lands) + the installed all-absent and copilot-present (four-unit fake-vendor) `entwurf setup` rows + the INSTALLED generation lifecycle on a seeded previous-generation host (REFUSE before activation writes / zero Claude invocations → installed fresh-cut archives + opens empty → install-meta-bridge PASSES)
257
+ ./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke with the pinned pi peers (pins derived from the package.json devDep; check-dep-versions binds them) + the npm-installed bridge BOOTS (tools/list) and DELIVERS (tools/call entwurf_v2 → .msg lands) + the installed all-absent and copilot-present (four-unit fake-vendor) `entwurf setup` rows + the INSTALLED generation lifecycle on a seeded previous-generation host (REFUSE before activation writes / zero Claude invocations → installed fresh-cut archives + opens empty → install-meta-bridge PASSES) + the INSTALLED-PACKAGE branch of the Copilot and OMP birth installers actually RUN (compiled entry selected, no raw .ts, and a real birth edge mints a citizen — the half a required-artifact list can never stand in for)
239
258
  ./run.sh check-install-container # 0.12.8 (#51 C): Linux artifact-CONSUMER gate — one candidate .tgz handed read-only to a checkout-invisible node:<engines-major>-bookworm cell. Default packs once to temp; ENTWURF_CANDIDATE_TGZ=/absolute/preserved.tgz consumes those exact bytes with no re-pack and prints canonical path+sha256 for release. Non-root global PATH install, frozen package, MCP tools/list, fake-Claude install-meta-bridge, path+sha256 fence, strict doctor, and the GENERATION host-state matrix (clean / v3-only store bytes unchanged / previous-generation REFUSE→fresh-cut→retry PASS) seeded inline. Docker missing = honest SKIP; ENTWURF_REQUIRE_DOCKER=1 makes that RED (required CI)
240
259
  ./run.sh install [project-dir] # INTERNAL part of `setup` (project .pi/settings.json wiring) + npm-consumer entry — prefer `setup`, don't call directly for dev
241
260
  ./run.sh remove [project-dir] # remove entwurf entries from project .pi/settings.json (project scope only; global user-scope citizen left intact)
@@ -774,6 +793,42 @@ check_entwurf_capabilities() {
774
793
  run_ts scripts/check-entwurf-capabilities.ts
775
794
  }
776
795
 
796
+ smoke_omp_fresh_live() {
797
+ # #87 Bundle C acceptance, RELEASE MUST. Opens omp as ONE visible fresh sibling through
798
+ # the PUBLIC entwurf_fresh_call surface in a private tmux server, correlates the exact
799
+ # nonce callback to a garden id from the SENDER ENVELOPE, then delivers an addressed
800
+ # message the sibling drains in that same native session. Decides its own outcome: omp
801
+ # absent from FRESH_CALL_BACKENDS or without a mailbox rail is a protocol SKIP, LIVE!=1
802
+ # is a protocol SKIP, and once the composition offers the backend every missing
803
+ # prerequisite is a FAIL. Writes into the REAL meta roots — the extensions run inside the
804
+ # launched omp process and no env carrier on the fresh argv could fence them.
805
+ run_ts scripts/smoke-omp-fresh-live.ts
806
+ }
807
+
808
+ check_omp_fresh_preflight() {
809
+ # #87 Bundle C. The OMP fresh preflight REPRODUCES two resolvers that already exist
810
+ # in this repo in another language — omp_agent_dir (scripts/omp-bridge-oracle.sh) and
811
+ # the tools.xdev half of scripts/omp-tool-surface.py — because the preflight runs from
812
+ # two different emit depths and cannot resolve a sibling script by relative path. A
813
+ # reproduction nothing compares is a divergence with a date on it, so this drives the
814
+ # SHIPPED shell and python leaves and requires the TS half to agree, refusals included.
815
+ # Never asks the resolver under test to confirm itself. Fixtures only; never reads ~/.omp.
816
+ run_ts scripts/check-omp-fresh-preflight.ts
817
+ }
818
+
819
+ check_harness_admission_parity() {
820
+ # #87 Bundle C. The EDGE between two parity loops that were each closed and had
821
+ # nothing between them: check-entwurf-capabilities held registry ==
822
+ # META_CITIZEN_BACKENDS, the fresh-call surfaces contract held surfaces ==
823
+ # FRESH_CALL_BACKENDS, and no file read both constants. So a harness could be a
824
+ # full D6 citizen that entwurf_fresh_call cannot open, with every gate green and
825
+ # only prose saying it was not supported — which is how an `unsupported` label
826
+ # started reading as a partial-release permit. Asserts every citizen backend is
827
+ # either fresh-openable or a DECLARED pre-#82 legacy admission whose exception a
828
+ # reader can find in DELIVERY.md, both directions, no third state. Pure parse.
829
+ run_ts scripts/check-harness-admission-parity.ts
830
+ }
831
+
777
832
  check_capability_bundle_reach() {
778
833
  # The gate check-entwurf-capabilities structurally cannot be. That gate imports
779
834
  # metaCapabilitiesFilePath() from ../pi-extensions/lib/ — the one location where
@@ -1238,7 +1293,11 @@ check_mux_fresh_call() {
1238
1293
  # copilot-fresh-preflight rides here rather than in its own gate: it exists only as
1239
1294
  # freshCall's pre-mutation branch (#82 RAIL 9), and splitting it would let the two halves
1240
1295
  # of one refusal be certified in different runs.
1241
- run_vitest test/mux-fresh-call.test.ts test/copilot-fresh-preflight.test.ts test/fresh-call-surfaces.contract.test.ts test/fresh-call-provider.contract.test.ts
1296
+ # omp-fresh-bootstrap.contract rides here for the same reason and one more: it is the ONLY
1297
+ # place the launcher's payload and the installed birth extension's decoder are read together.
1298
+ # They ship in different directories and cannot import each other (#87 Bundle C), so this
1299
+ # lane is what keeps a deliberate duplication from becoming drift.
1300
+ run_vitest test/mux-fresh-call.test.ts test/copilot-fresh-preflight.test.ts test/fresh-call-surfaces.contract.test.ts test/fresh-call-provider.contract.test.ts test/omp-fresh-bootstrap.contract.test.ts
1242
1301
  }
1243
1302
 
1244
1303
  smoke_mux_fresh_call_live() {
@@ -1768,13 +1827,13 @@ assert.equal(peerTui, piAi,
1768
1827
  // floor tracks the devDep pin so a consumer can't install against a pi lacking
1769
1828
  // the public trust exports the bridge imports at the pinned minor, AND an upper
1770
1829
  // bound at the next minor stops a fresh install from silently pulling a future
1771
- // pi (past the declared ceiling — 0.85+ at the current 0.84.3 pin) whose
1830
+ // pi (past the declared ceiling — 0.85+ at the current 0.84.4 pin) whose
1772
1831
  // internal export surface has drifted from the one we typecheck against.
1773
1832
  // pi moves its public surface every minor (the 0.79→0.80 getModels→provider-
1774
1833
  // factory churn is exactly this), so an open `>=` floor is exactly how the next
1775
1834
  // installer re-acquires the drift. The floor is also the HARD MINIMUM a consumer
1776
- // install resolves: at `>=0.84.3` an existing 0.83.x host is upgraded, not kept.
1777
- // Expected shape: `>=<devDep> <0.<minor+1>` (e.g. `>=0.84.3 <0.85`).
1835
+ // install resolves: at `>=0.84.4` an existing 0.84.3 host is upgraded, not kept.
1836
+ // Expected shape: `>=<devDep> <0.<minor+1>` (e.g. `>=0.84.4 <0.85`).
1778
1837
  const [piMaj, piMin] = piAi.split('.').map(Number);
1779
1838
  assert.equal(piMaj, 0,
1780
1839
  `pi pin major must stay 0 for the next-minor ceiling rule (got ${piAi}); revisit check-dep-versions when pi reaches 1.x`);
@@ -2672,6 +2731,17 @@ check_acp_prompt_lifecycle() {
2672
2731
  run_ts scripts/check-acp-prompt-lifecycle.ts
2673
2732
  }
2674
2733
 
2734
+ check_acp_launch_namespace() {
2735
+ # #72: the Claude ACP child must launch under a name entwurf OWNS. A janitor
2736
+ # installed on the host for ANOTHER harness selects the vendor process name
2737
+ # `claude-agent-acp` by argv substring and SIGTERMs it by age; entwurf retains
2738
+ # its child across turns, so its age is the session's. This gate holds the
2739
+ # name split, holds the launcher transparent (the vendor still answers
2740
+ # --version through it), and keeps an explicit operator override verbatim.
2741
+ section "ACP launch namespace (#72 — no vendor process name in our argv)"
2742
+ run_ts scripts/check-acp-launch-namespace.ts
2743
+ }
2744
+
2675
2745
  check_acp_stream_hooks() {
2676
2746
  # Deterministic gate for the pi 0.84 streamSimple hook contract on the ACP rail
2677
2747
  # (#63; upstream pi-mono #7372 → doc-only #7576). before_provider_request
@@ -2891,6 +2961,9 @@ check_pack() {
2891
2961
  # #65 — the owner-normalized store projection consumers call INSTEAD of
2892
2962
  # parsing the store; installed hosts are exactly where those consumers live.
2893
2963
  "mcp/entwurf-bridge/dist/scripts/meta-facts.js"
2964
+ # #87 Bundle B — omp-receive-doctor drives this operator projection through
2965
+ # run.sh, so installed node_modules must have the compiled twin too.
2966
+ "mcp/entwurf-bridge/dist/scripts/omp-receive-facts.js"
2894
2967
  # 0.12.5 — the node_modules-safe plugin hook + its lib. install-meta-bridge copies
2895
2968
  # these compiled JS into the assembled plugin when installed (raw .ts can't
2896
2969
  # strip-types under node_modules). meta-session.js is shared with the store-doctor
@@ -2899,6 +2972,13 @@ check_pack() {
2899
2972
  # #82 — the Copilot birth entry's compiled closure, for the same node_modules
2900
2973
  # strip-types refusal that forces the Claude one.
2901
2974
  "mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-copilot.js"
2975
+ # #87 — the OMP birth entry's compiled closure. install-omp-bridge SELECTS this path
2976
+ # under node_modules, and docs/setup-clean-host.md tells operators to run that installer,
2977
+ # so its absence is not a degraded path: the installed installer dies at its own artifact
2978
+ # check before assembling anything. The unit skeleton's package.json below is the other
2979
+ # half — the installer copies it, and `pi/` ships per-FILE.
2980
+ "mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js"
2981
+ "pi/meta-bridge-omp/entwurf-meta-omp/package.json"
2902
2982
  "mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
2903
2983
  "scripts/postinstall-chmod.cjs"
2904
2984
  "pi/entwurf-capabilities.json"
@@ -3000,15 +3080,15 @@ check_pack() {
3000
3080
  # The pin-leak filter for the install tree's .pnpm listing, shared by the real scan and its
3001
3081
  # self-test below. The version BOUNDARY is load-bearing: a pnpm .pnpm entry is
3002
3082
  # `<name>@<version>` followed by either `_<peer-hash>` or end-of-name (measured pnpm 11.20.0
3003
- # on this tree), so an unbounded substring match would bless a lookalike such as `@0.84.30`
3083
+ # on this tree), so an unbounded substring match would bless a lookalike such as `@0.84.40`
3004
3084
  # while announcing the pinned floor — a false-green oracle (found by independent review,
3005
3085
  # 2026-08-25).
3006
3086
  pack_install_leaked_pi() {
3007
- grep '^@earendil-works+pi-' | grep -Ev '@0\.84\.3(_|$)' || true
3087
+ grep '^@earendil-works+pi-' | grep -Ev '@0\.84\.4(_|$)' || true
3008
3088
  }
3009
3089
 
3010
3090
  # Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise the
3011
- # false-green shape (it contains no 0.84.30), so the oracle is proven against a fixture
3091
+ # false-green shape (it contains no 0.84.40), so the oracle is proven against a fixture
3012
3092
  # listing. Expected: the two lookalikes leak, the pinned version passes bare and with a
3013
3093
  # peer-hash suffix. Exposed as its own snapshot-safe subcommand because the heavy
3014
3094
  # check-pack-install cannot run inside the qualification snapshot (no install environment),
@@ -3017,13 +3097,13 @@ pack_install_leaked_pi() {
3017
3097
  check_pack_pin_matcher() {
3018
3098
  local matcher_probe
3019
3099
  matcher_probe=$(printf '%s\n' \
3020
- '@earendil-works+pi-ai@0.84.3' \
3021
- '@earendil-works+pi-ai@0.84.3_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
3022
- '@earendil-works+pi-ai@0.84.30' \
3023
- '@earendil-works+pi-agent-core@0.84.2' | pack_install_leaked_pi)
3024
- if [ "$matcher_probe" != '@earendil-works+pi-ai@0.84.30
3025
- @earendil-works+pi-agent-core@0.84.2' ]; then
3026
- fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the 0.84.30/0.84.2 lookalikes and pass 0.84.3 bare or with a peer-hash — got: ${matcher_probe:-<nothing leaked>}"
3100
+ '@earendil-works+pi-ai@0.84.4' \
3101
+ '@earendil-works+pi-ai@0.84.4_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
3102
+ '@earendil-works+pi-ai@0.84.40' \
3103
+ '@earendil-works+pi-agent-core@0.84.3' | pack_install_leaked_pi)
3104
+ if [ "$matcher_probe" != '@earendil-works+pi-ai@0.84.40
3105
+ @earendil-works+pi-agent-core@0.84.3' ]; then
3106
+ fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the 0.84.40/0.84.3 lookalikes and pass 0.84.4 bare or with a peer-hash — got: ${matcher_probe:-<nothing leaked>}"
3027
3107
  return 1
3028
3108
  fi
3029
3109
  echo "[check-pack-pin-matcher] ok — the pin-leak matcher is version-bounded (lookalikes leak, pinned version passes bare and with a peer-hash)"
@@ -3146,12 +3226,21 @@ _check_pack_install_impl() {
3146
3226
  "mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js"
3147
3227
  # #65 — the owner-normalized store projection (see check-pack).
3148
3228
  "mcp/entwurf-bridge/dist/scripts/meta-facts.js"
3229
+ # #87 Bundle B — omp-receive-doctor reaches this through the installed dispatcher.
3230
+ "mcp/entwurf-bridge/dist/scripts/omp-receive-facts.js"
3149
3231
  # 0.12.5 — node_modules-safe plugin hook + lib (see check-pack). The installed
3150
3232
  # hook regression below runs exactly this compiled JS from under node_modules.
3151
3233
  "mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js"
3152
3234
  # #82 — the Copilot birth entry's compiled closure, for the same node_modules
3153
3235
  # strip-types refusal that forces the Claude one.
3154
3236
  "mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-copilot.js"
3237
+ # #87 — the OMP birth entry's compiled closure. install-omp-bridge SELECTS this path
3238
+ # under node_modules, and docs/setup-clean-host.md tells operators to run that installer,
3239
+ # so its absence is not a degraded path: the installed installer dies at its own artifact
3240
+ # check before assembling anything. The unit skeleton's package.json below is the other
3241
+ # half — the installer copies it, and `pi/` ships per-FILE.
3242
+ "mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js"
3243
+ "pi/meta-bridge-omp/entwurf-meta-omp/package.json"
3155
3244
  "mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
3156
3245
  "scripts/postinstall-chmod.cjs"
3157
3246
  "pi/entwurf-capabilities.json"
@@ -3244,7 +3333,7 @@ _check_pack_install_impl() {
3244
3333
  printf '%s\n' '{ "name": "entwurf-install-smoke", "version": "0.0.0", "private": true }' > "$tmp/package.json"
3245
3334
 
3246
3335
  # pi-agent-core is pinned even though we never import it: pi-coding-agent depends
3247
- # on it by CARET (`^0.84.3`), so with no lockfile in this fresh temp project it
3336
+ # on it by CARET (`^0.84.x`), so with no lockfile in this fresh temp project it
3248
3337
  # floats to whatever pi published last — and that newer core then drags a NESTED
3249
3338
  # pi-ai of its own. Measured 2026-07-21: pinning only the three we import left
3250
3339
  # pi-agent-core@0.80.10 + pi-ai@0.80.10 in the tree while the gate still announced
@@ -3257,18 +3346,26 @@ _check_pack_install_impl() {
3257
3346
  # {pi-agent-core, pi-ai, pi-tui} at 0.83.0 to {pi-agent-core, pi-ai, pi-client,
3258
3347
  # pi-protocol, pi-tui}, so pi-client and pi-protocol are pinned here for the
3259
3348
  # first time. Unpinned they would float exactly like pi-agent-core did in the
3260
- # 2026-07-21 incident above. (pi-telemetry arrives transitively, not as a
3261
- # direct caret; the leak assertion below covers it and every other pi package.)
3349
+ # 2026-07-21 incident above. pi-telemetry joined the explicit pin list on
3350
+ # 2026-08-31: it arrives transitively (pi-agent-core and pi-ai both carry
3351
+ # `^0.84.x` carets on it), and upstream's 0.84.4 patch publish (2026-08-28
3352
+ # 22:04Z) floated that caret in this lockfile-less temp install, turning CI
3353
+ # red through the leak assertion below. That explicit pin is what held the
3354
+ # line until the bump lane ran; the verified floor is 0.84.4 as of
3355
+ # 2026-09-01, and the pin moved WITH it rather than being retired. The leak
3356
+ # assertion below still covers every other pi package, including any package
3357
+ # a future pi bump adds to the closure.
3262
3358
  echo "[check-pack-install] pnpm add into $tmp (with 0.84.x peers + typebox)"
3263
3359
  local install_log
3264
3360
  install_log=$(cd "$tmp" && pnpm add \
3265
3361
  "$tgz_path" \
3266
- "@earendil-works/pi-ai@0.84.3" \
3267
- "@earendil-works/pi-coding-agent@0.84.3" \
3268
- "@earendil-works/pi-tui@0.84.3" \
3269
- "@earendil-works/pi-agent-core@0.84.3" \
3270
- "@earendil-works/pi-client@0.84.3" \
3271
- "@earendil-works/pi-protocol@0.84.3" \
3362
+ "@earendil-works/pi-ai@0.84.4" \
3363
+ "@earendil-works/pi-coding-agent@0.84.4" \
3364
+ "@earendil-works/pi-tui@0.84.4" \
3365
+ "@earendil-works/pi-agent-core@0.84.4" \
3366
+ "@earendil-works/pi-client@0.84.4" \
3367
+ "@earendil-works/pi-protocol@0.84.4" \
3368
+ "@earendil-works/pi-telemetry@0.84.4" \
3272
3369
  "typebox@latest" \
3273
3370
  --ignore-workspace --ignore-scripts 2>&1) || {
3274
3371
  fail "[check-pack-install] pnpm add failed:"
@@ -3278,17 +3375,17 @@ _check_pack_install_impl() {
3278
3375
 
3279
3376
  # A pin is a wish until the resolved tree is read back. Assert it: EVERY
3280
3377
  # @earendil-works pi package present — direct or transitive, top level or nested —
3281
- # must be the pinned 0.84.3. Anything else means an unpinned caret floated and the
3378
+ # must be the pinned 0.84.4. Anything else means an unpinned caret floated and the
3282
3379
  # rest of this gate would be exercising a runtime nobody verified, while still
3283
- # printing "pinned pi 0.84.3". Fail loud instead of proving the wrong floor.
3380
+ # printing "pinned pi 0.84.4". Fail loud instead of proving the wrong floor.
3284
3381
  local leaked_pi
3285
3382
  leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
3286
3383
  if [ -n "$leaked_pi" ]; then
3287
- fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.84.3):"
3384
+ fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.84.4):"
3288
3385
  printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
3289
3386
  return 1
3290
3387
  fi
3291
- echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works pi package is 0.84.3"
3388
+ echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works pi package is 0.84.4"
3292
3389
 
3293
3390
  # Resolve the installed package.json and confirm pi.extensions
3294
3391
  # arrived intact. If pi.extensions is empty or missing, the
@@ -3882,6 +3979,76 @@ SH
3882
3979
  fi
3883
3980
  rm -rf "$cop_store"
3884
3981
  echo "[check-pack-install] installed Copilot birth unit assembles its compiled branch and mints a citizen from a no-argv launch"
3982
+
3983
+ # --- #87: the OMP birth unit's INSTALLED-PACKAGE branch ---------------------
3984
+ # Same shape and same reason as the Copilot cell above, and it is here because the gap it
3985
+ # closes was REAL, not theoretical: the compiled entry this installer selects under
3986
+ # node_modules was in no emit include and no artifact list, so `entwurf install-omp-bridge`
3987
+ # — which docs/setup-clean-host.md tells operators to run — died at its own artifact check
3988
+ # on every installed host while every gate stayed green (Terra amendment review, #87 A1).
3989
+ # The list entries above make that artifact's ABSENCE loud; this RUNS it, which is the half
3990
+ # Hard Rule 11 says a list can never stand in for.
3991
+ #
3992
+ # No omp binary is faked into doing anything: the installer only asks whether one is on
3993
+ # PATH (entwurf never installs a harness), and the unit is a module the vendor imports, not
3994
+ # a process we launch — so the exercise imports the placed artifact the way omp does.
3995
+ local omp_agent="$npm_tmp/omp-agent" omp_home="$npm_tmp/omp-home" omp_bin="$npm_tmp/omp-bin" omp_log
3996
+ mkdir -p "$omp_agent" "$omp_home" "$omp_bin"
3997
+ printf '#!/usr/bin/env bash\nexit 0\n' > "$omp_bin/omp"
3998
+ chmod +x "$omp_bin/omp"
3999
+ if ! omp_log=$(HOME="$omp_home" XDG_DATA_HOME="$omp_home/.local/share" ENTWURF_OMP_AGENT_DIR="$omp_agent" PATH="$omp_bin:$npmroot/node_modules/.bin:$PATH" "$npm_pkg/run.sh" install-omp-bridge 2>&1); then
4000
+ fail "[check-pack-install] installed install-omp-bridge failed:"
4001
+ echo "$omp_log" | tail -20 | sed 's/^/ /' >&2
4002
+ return 1
4003
+ fi
4004
+ local omp_unit="$omp_agent/extensions/entwurf-meta-omp"
4005
+ # Branch SELECTION, pinned exactly as the Copilot cell pins its own: an installed package
4006
+ # must place the compiled entry and NO raw .ts, or the fire below could pass on a layout
4007
+ # that silently chose the dev-clone branch.
4008
+ if [ ! -f "$omp_unit/index.js" ] || [ -f "$omp_unit/index.ts" ]; then
4009
+ fail "[check-pack-install] installed install-omp-bridge did not select the compiled branch (want index.js, no raw .ts) in $omp_unit"
4010
+ ls -l "$omp_unit" 2>/dev/null | sed 's/^/ /' >&2 || true
4011
+ return 1
4012
+ fi
4013
+ if [ ! -f "$omp_unit/lib/meta-session.js" ] || [ ! -f "$omp_unit/entwurf-capabilities.json" ] || [ ! -f "$omp_unit/package.json" ]; then
4014
+ fail "[check-pack-install] installed omp unit is incomplete (want lib/meta-session.js + entwurf-capabilities.json + package.json) in $omp_unit"
4015
+ return 1
4016
+ fi
4017
+ # Fired the way omp fires it: import the module, call the exported factory with a mock
4018
+ # ExtensionAPI, and hand the tui host context to the birth edge.
4019
+ local omp_fire
4020
+ if ! omp_fire=$(OMP_UNIT="$omp_unit" HOME="$omp_home" node --input-type=module <<'JS' 2>&1
4021
+ const mod = await import(process.env.OMP_UNIT + "/index.js");
4022
+ const handlers = [];
4023
+ (mod.default ?? mod)({ on: (event, handler) => { if (event === "session_start") handlers.push(handler); } });
4024
+ const ctx = {
4025
+ mode: "tui",
4026
+ cwd: "/tmp",
4027
+ ui: { setStatus: () => {} },
4028
+ sessionManager: { getSessionId: () => "pack-omp-probe", getCwd: () => "/tmp", getSessionFile: () => null },
4029
+ };
4030
+ for (const handler of handlers) await handler({ type: "session_start" }, ctx);
4031
+ process.stdout.write("fired " + handlers.length + "\n");
4032
+ JS
4033
+ ); then
4034
+ fail "[check-pack-install] the installed omp unit failed to import/run from its placed location: $(printf '%s' "$omp_fire" | tr '\n' ' ' | cut -c1-300)"
4035
+ return 1
4036
+ fi
4037
+ # Counted through the OWNER's certified projection, not a grep — and through the INSTALLED
4038
+ # bin, so this also proves dist/scripts/meta-facts.js is reachable from a consumer. `env -u
4039
+ # PI_CODING_AGENT_DIR` is the omp root policy: for backend omp that variable is the vendor's
4040
+ # own agent dir and never a garden root (#87 B1).
4041
+ local omp_citizens
4042
+ omp_citizens=$(env -u PI_CODING_AGENT_DIR HOME="$omp_home" "$npm_pkg/run.sh" meta-facts 2>&1 | python3 -c '
4043
+ import json, sys
4044
+ facts = json.load(sys.stdin)
4045
+ print(sum(1 for c in (facts.get("citizens") or []) if c.get("backend") == "omp"))
4046
+ ' 2>/dev/null) || omp_citizens=""
4047
+ if [ "$omp_citizens" != "1" ]; then
4048
+ fail "[check-pack-install] the installed omp unit did not mint exactly one CERTIFIED omp citizen (found ${omp_citizens:-<unreadable>}); log: $(tail -1 "$omp_home/.pi/agent/meta-bridge-hook.log" 2>/dev/null)"
4049
+ return 1
4050
+ fi
4051
+ echo "[check-pack-install] installed OMP birth unit places its compiled branch and mints a certified citizen from a tui birth edge"
3885
4052
  python3 - "$mb_cfg/settings.json" "$mb_home/.claude.json" "$stable_asm" <<'PY'
3886
4053
  import json, sys
3887
4054
  settings = json.load(open(sys.argv[1]))
@@ -4829,6 +4996,52 @@ setup_all() {
4829
4996
  fi
4830
4997
  fi
4831
4998
 
4999
+ # ── omp (oh-my-pi) ── presence-driven composition, same shape as copilot. OMP
5000
+ # was admitted as a garden citizen in v0.16.0 with installers, doctors and
5001
+ # inverses for every unit — but it was never composed HERE, so the one-command
5002
+ # surface left the fifth backend to a hand-run verb list and the operator setting
5003
+ # to a documentation step. That gap is what `docs/adding-a-harness.md` step 10
5004
+ # now closes for every future harness: an onboarding is not finished until setup
5005
+ # composes it. Four units, each independent: birth (who the citizen is) → MCP
5006
+ # hand (what it can call) → the tools.xdev operator setting (whether those calls
5007
+ # are REACHABLE) → receiver (whether a reply can land). OMP_BIN pins the PROBE
5008
+ # for hermetic gates; the unit scripts address `omp` on PATH.
5009
+ local omp_rc
5010
+ if ! command -v "${OMP_BIN:-omp}" >/dev/null 2>&1; then
5011
+ setup_result omp SKIP "omp not on PATH — zero OMP wiring written"
5012
+ else
5013
+ section "omp units (native harness detected: oh-my-pi)"
5014
+ omp_rc=0; (cd "$REPO_DIR" && bash scripts/omp-bridge-install.sh) || omp_rc=$?
5015
+ if [ "$omp_rc" -eq 0 ]; then
5016
+ setup_result omp-birth PASS "birth extension installed — verify: ./run.sh doctor-omp-bridge"
5017
+ else
5018
+ setup_result omp-birth FAIL "detected omp, but the birth extension install did not complete (see above) — repair, then re-run setup"
5019
+ fi
5020
+ omp_rc=0; (cd "$REPO_DIR" && bash scripts/omp-mcp-bridge.sh install) || omp_rc=$?
5021
+ if [ "$omp_rc" -eq 0 ]; then
5022
+ setup_result omp-mcp PASS "MCP server registered — verify: ./run.sh doctor-omp-mcp"
5023
+ else
5024
+ setup_result omp-mcp FAIL "detected omp, but the MCP registration did not complete (see above) — repair, then re-run setup"
5025
+ fi
5026
+ # The operator setting is a component of its own because its FAIL is a real
5027
+ # disagreement, not a broken install: an explicit `tools.xdev: true` is the
5028
+ # operator's decision and the writer refuses it by name. Naming that as a
5029
+ # component FAIL puts the choice in front of the operator instead of silently
5030
+ # shipping a citizen whose tools nobody can call.
5031
+ omp_rc=0; (cd "$REPO_DIR" && bash scripts/omp-config-xdev.sh install) || omp_rc=$?
5032
+ if [ "$omp_rc" -eq 0 ]; then
5033
+ setup_result omp-config PASS "tools.xdev: false written — verify: ./run.sh doctor-omp-mcp"
5034
+ else
5035
+ setup_result omp-config FAIL "detected omp, but the tools.xdev operator setting did not land (see above) — resolve it, then re-run setup"
5036
+ fi
5037
+ omp_rc=0; (cd "$REPO_DIR" && bash scripts/omp-receive-install.sh) || omp_rc=$?
5038
+ if [ "$omp_rc" -eq 0 ]; then
5039
+ setup_result omp-receive PASS "receiver extension installed — verify: ./run.sh doctor-omp-receive"
5040
+ else
5041
+ setup_result omp-receive FAIL "detected omp, but the receiver extension install did not complete (see above) — repair, then re-run setup"
5042
+ fi
5043
+ fi
5044
+
4832
5045
  # ── core bridge boundary ── deterministic preflight lives in `pnpm run
4833
5046
  # check:full`; live substrate acceptance lives in `LIVE=1 ./run.sh
4834
5047
  # release-gate <scratch> --cut`. Setup is the install path, so it verifies the
@@ -4849,7 +5062,7 @@ setup_all() {
4849
5062
  local entry rest s_name s_verdict s_fails=""
4850
5063
  for entry in "${SETUP_RESULTS[@]}"; do
4851
5064
  s_name="${entry%%|*}"; rest="${entry#*|}"; s_verdict="${rest%%|*}"
4852
- printf ' %-14s %-4s %s\n' "$s_name" "$s_verdict" "${rest#*|}"
5065
+ printf ' %-18s %-4s %s\n' "$s_name" "$s_verdict" "${rest#*|}"
4853
5066
  if [ "$s_verdict" = "FAIL" ]; then s_fails="$s_fails $s_name"; fi
4854
5067
  done
4855
5068
  echo ""
@@ -5317,6 +5530,21 @@ release_gate() {
5317
5530
  run_live_step "smoke-claude-native-resume-live (native Claude Code resume + meta-bridge neutrality)" gate bash "$self" smoke-claude-native-resume-live
5318
5531
  run_live_step "smoke-entwurf-chain-live (P3: Claude Code -> pi GPT -> pi ACP Sonnet -> mailbox, identity + receipt)" gate bash "$self" smoke-entwurf-chain-live
5319
5532
  run_live_step "smoke-mux-lifecycle-live (mux public-harness lifecycle: fresh -> send -> dormant -> same-id visible resume -> recall)" gate bash "$self" smoke-mux-lifecycle-live
5533
+ # #87 bundle B: MUST, and since bundle B moved omp's wakeMode to self-fetch this is a
5534
+ # REAL demand under LIVE=1, not a standing SKIP — it reads the registry, so an honest
5535
+ # SKIP now only ever names a missing prerequisite, and a registry claiming a receive
5536
+ # rail with no acceptance body here is a FAIL.
5537
+ run_live_step "smoke-omp-receive-live (#87 bundle B: other harness -> open omp citizen, addressed receive + roundtrip)" gate bash "$self" smoke-omp-receive-live
5538
+ # #87 bundle C: the step 9 clause 7 receipt, wired as a MUST rather than left to an
5539
+ # operator's memory. A schema is not a product — omp's bootstrap-payload submission
5540
+ # PARSES correctly, and parsing is exactly what pi got right while submitting no message
5541
+ # at all, so a release whose fresh_call(omp) opens a window that never runs its turn
5542
+ # would pass every deterministic gate here. That is not hypothetical for this backend:
5543
+ # the retired positional candidate parsed perfectly and still answered `ACK` with zero
5544
+ # tool calls, because the turn began before the callback tool existed. New contract,
5545
+ # applied from omp onward; it does not retroactively redesign Copilot's operator-metered
5546
+ # exclusion.
5547
+ run_live_step "smoke-omp-fresh-live (#87 bundle C: entwurf_fresh_call opens omp, exact nonce callback, addressed receive)" gate bash "$self" smoke-omp-fresh-live
5320
5548
 
5321
5549
  # 4. BEHAVIOR lane (advisory, non-blocking). Model-in-loop gates that probe
5322
5550
  # whether the model AUTONOMOUSLY drives the MCP entwurf surface. These never
@@ -5430,6 +5658,15 @@ case "$cmd" in
5430
5658
  check-entwurf-capabilities)
5431
5659
  check_entwurf_capabilities
5432
5660
  ;;
5661
+ check-harness-admission-parity)
5662
+ check_harness_admission_parity
5663
+ ;;
5664
+ check-omp-fresh-preflight)
5665
+ check_omp_fresh_preflight
5666
+ ;;
5667
+ smoke-omp-fresh-live)
5668
+ smoke_omp_fresh_live
5669
+ ;;
5433
5670
  check-capability-bundle-reach)
5434
5671
  check_capability_bundle_reach
5435
5672
  ;;
@@ -5454,6 +5691,18 @@ case "$cmd" in
5454
5691
  check-copilot-birth-hook)
5455
5692
  check_copilot_birth_hook
5456
5693
  ;;
5694
+ check-omp-birth-hook)
5695
+ # #87 gate: the OMP BIRTH path without omp. Drives the real assembler into a temp dir,
5696
+ # then imports the ASSEMBLED index.ts into a mock omp host (mock ExtensionAPI + mock
5697
+ # ExtensionContext) and fires the two birth edges. Requires: a backend:"omp" v3 record
5698
+ # only under mode "tui"; NOTHING under print/rpc/json (a task subagent is not a citizen,
5699
+ # and hasUI is deliberately true on the rpc rows because it is true in the vendor too);
5700
+ # session_switch attaching on the same native id and minting the replacement on a new
5701
+ # one; a sender marker keyed to the HOST process's OWN pid (never its parent — the
5702
+ # one-process join) that the production resolver joins back to the record; the garden id
5703
+ # on the status line; and still zero mailbox/receiver marker. Hermetic; no omp, no model turn
5704
+ run_ts scripts/check-omp-birth-hook.ts
5705
+ ;;
5457
5706
  check-copilot-statusline)
5458
5707
  check_copilot_statusline
5459
5708
  ;;
@@ -5586,6 +5835,12 @@ case "$cmd" in
5586
5835
  smoke-acp-bundled-mcp-live)
5587
5836
  smoke_acp_bundled_mcp_live
5588
5837
  ;;
5838
+ smoke-omp-receive-live)
5839
+ # #87 bundle B — the acceptance that makes omp's one-way garden executable.
5840
+ # Reads the capability registry and reports its own PASS/SKIP/FAIL; see the
5841
+ # header of the script for the three branches.
5842
+ run_ts scripts/smoke-omp-receive-live.ts
5843
+ ;;
5589
5844
  smoke-entwurf-chain-live)
5590
5845
  # P3 — the cross-harness delivery CHAIN on real authenticated rails:
5591
5846
  # native Claude Code -> pi GPT -> pi ACP Claude Sonnet -> mailbox terminus,
@@ -5910,6 +6165,116 @@ case "$cmd" in
5910
6165
  smoke-copilot-mcp-state)
5911
6166
  (cd "$REPO_DIR" && bash scripts/smoke-copilot-mcp-state.sh)
5912
6167
  ;;
6168
+ smoke-omp-bridge-state)
6169
+ (cd "$REPO_DIR" && bash scripts/smoke-omp-bridge-state.sh)
6170
+ ;;
6171
+ install-omp-mcp)
6172
+ (cd "$REPO_DIR" && bash scripts/omp-mcp-bridge.sh install "$@")
6173
+ ;;
6174
+ uninstall-omp-mcp)
6175
+ (cd "$REPO_DIR" && bash scripts/omp-mcp-bridge.sh uninstall "$@")
6176
+ ;;
6177
+ doctor-omp-mcp)
6178
+ (cd "$REPO_DIR" && bash scripts/omp-mcp-bridge.sh doctor "$@")
6179
+ ;;
6180
+ smoke-omp-mcp-state)
6181
+ (cd "$REPO_DIR" && bash scripts/smoke-omp-mcp-state.sh)
6182
+ ;;
6183
+ install-omp-config)
6184
+ # #87 follow-on: the ONE operator setting omp's tool hand requires (`tools.xdev: false`).
6185
+ # A separate unit from install-omp-mcp because it answers a different question — the MCP
6186
+ # hand registers the server, this decides whether the registered tools are REACHABLE. It
6187
+ # owns exactly the line it adds and refuses an explicit operator `xdev: true` by name.
6188
+ shift || true
6189
+ (cd "$REPO_DIR" && bash scripts/omp-config-xdev.sh install "$@")
6190
+ ;;
6191
+ uninstall-omp-config)
6192
+ # honest inverse from install-state: takes back exactly the recorded line(s), removes the
6193
+ # file only when entwurf created it, and REFUSES when the config changed since install.
6194
+ shift || true
6195
+ (cd "$REPO_DIR" && bash scripts/omp-config-xdev.sh uninstall "$@")
6196
+ ;;
6197
+ install-omp-bridge)
6198
+ # #87: the OMP BIRTH install. Not a mode of the Claude or Copilot installer, and for a
6199
+ # structural reason rather than a stylistic one: an omp "hook" IS an in-process
6200
+ # extension, so there is no launcher to bake a node path into and no hook manifest to
6201
+ # declare events in — the vendor imports the module itself. What this owns is one
6202
+ # directory under the omp agent dir; the MCP hand is install-omp-mcp.
6203
+ shift || true
6204
+ (cd "$REPO_DIR" && bash scripts/omp-bridge-install.sh "$@")
6205
+ ;;
6206
+ uninstall-omp-bridge)
6207
+ # The package-owned inverse: the ownership state is the sole removal authority (exact
6208
+ # unit dir, recorded entry name, recorded assembly), a no-state host REFUSES instead of
6209
+ # cleaning up what it cannot prove is ours, and the state is deleted LAST.
6210
+ shift || true
6211
+ (cd "$REPO_DIR" && bash scripts/omp-bridge-uninstall.sh "$@")
6212
+ ;;
6213
+ doctor-omp-bridge)
6214
+ # #87: the fail-loud surface for the OMP unit. Red is a unit omp cannot import, a STALE
6215
+ # deployed writer, an unrecovered mint ERROR, a failed sender-marker write, or a live omp
6216
+ # process carrying inherited PI_SESSION_ID/PI_AGENT_ID. "Installed with zero records" is
6217
+ # NOT-YET, not red.
6218
+ shift || true
6219
+ (cd "$REPO_DIR" && bash scripts/omp-bridge-doctor.sh "$@")
6220
+ ;;
6221
+ install-omp-receive)
6222
+ # #87 bundle B: install the OMP RECEIVER extension into
6223
+ # <omp agent dir>/extensions/entwurf-receive-omp. Its own unit, install-state, doctor and
6224
+ # inverse — birth says who sends, this says a reply can land, and neither grants the other.
6225
+ # Same ownership discipline as the birth installer: no adoption of an artifact entwurf holds
6226
+ # no state for, no writing through a symlink, refusal when the agent dir is ambiguous.
6227
+ shift || true
6228
+ (cd "$REPO_DIR" && bash scripts/omp-receive-install.sh "$@")
6229
+ ;;
6230
+ uninstall-omp-receive)
6231
+ # honest inverse from install-state (exact unit dir + recorded entry; no-state host REFUSES;
6232
+ # state deleted LAST). Records, sender identity and any ALREADY-armed live session are
6233
+ # untouched — an omp TUI that armed before this ran stays addressable until it exits.
6234
+ shift || true
6235
+ (cd "$REPO_DIR" && bash scripts/omp-receive-uninstall.sh "$@")
6236
+ ;;
6237
+ doctor-omp-receive)
6238
+ # #87 bundle B: runtime axis (importable unit, writer parity, arm vs doorbell failures on
6239
+ # SEPARATE axes, and WHO IS ARMED right now read through the PRODUCTION marker reader via
6240
+ # omp-receive-facts — never a filename) + ownership axis. Zero armed receivers is NOT-YET.
6241
+ # It reports a marker for exactly what a marker proves: a live owner reached the arm emit.
6242
+ # It never claims the vendor's watch is still registered — see the header.
6243
+ shift || true
6244
+ (cd "$REPO_DIR" && bash scripts/omp-receive-doctor.sh "$@")
6245
+ ;;
6246
+ smoke-omp-receive-state)
6247
+ # #87 bundle B: OMP RECEIVER install/doctor/inverse regression — placement, stale-writer
6248
+ # detection, honest inverse, no-state refusal (structurally VALID as well as foreign),
6249
+ # symlink refusal, ambiguous-agent-dir refusal, a poisoned PI_CODING_AGENT_DIR that
6250
+ # attracts no artifact, plus the two this surface owns: installing a doorbell ARMS
6251
+ # NOTHING, and a host with no birth unit is a green doctor with a NAMED dependency note
6252
+ # rather than a red one. Fully sandboxed HOME/PI/XDG. Offline/deterministic
6253
+ shift || true
6254
+ (cd "$REPO_DIR" && bash scripts/smoke-omp-receive-state.sh "$@")
6255
+ ;;
6256
+ check-omp-receive-arm)
6257
+ # #87 bundle B gate: drives the REAL receive assembler into a temp dir, imports the
6258
+ # ASSEMBLED index.ts into a MOCK omp host and proves everything on entwurf's side of the
6259
+ # vendor boundary — the tui-only scope fence, deferral before birth and the BOUNDED retry
6260
+ # that arms once the sender marker appears, the refusal to start a timer this build cannot
6261
+ # cancel, the refusal to arm when the wake surface is missing, id-drift refusal, the /new
6262
+ # unarm that the start-key guard structurally cannot catch, no-watch-no-marker ordering,
6263
+ # an announce-only doorbell that never carries the body, a vanished signal giving the
6264
+ # marker back, and an identity-guarded teardown. Hermetic; no omp, no model turn. What it
6265
+ # deliberately cannot prove — that the vendor really wakes an idle host — is
6266
+ # smoke-omp-receive-live's job, and neither receipt substitutes for the other
6267
+ shift || true
6268
+ run_ts scripts/check-omp-receive-arm.ts "$@"
6269
+ ;;
6270
+ omp-receive-facts)
6271
+ # #87 bundle B: read-only JSON projection of the omp receive rail — every receiver marker
6272
+ # read through readMetaReceiverMarker in BOTH readings (live / as-written), plus the mailbox
6273
+ # counts behind each. The shared oracle for doctor-omp-receive, the state smoke and the LIVE
6274
+ # acceptance, so none of them answers "is a doorbell held?" from a filename.
6275
+ shift || true
6276
+ run_ts scripts/omp-receive-facts.ts "$@"
6277
+ ;;
5913
6278
  copilot)
5914
6279
  # #82 RAIL 7: the managed launch. `exec` and NO subshell/cd on purpose — the vendor
5915
6280
  # must inherit this terminal exactly: the caller's cwd, this pid, this tty, and its own
@@ -6161,6 +6526,9 @@ case "$cmd" in
6161
6526
  check-acp-prompt-lifecycle)
6162
6527
  check_acp_prompt_lifecycle
6163
6528
  ;;
6529
+ check-acp-launch-namespace)
6530
+ check_acp_launch_namespace
6531
+ ;;
6164
6532
  check-acp-stream-hooks)
6165
6533
  check_acp_stream_hooks
6166
6534
  ;;