@junghanacs/entwurf 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/AGENTS.md +52 -17
  2. package/BASELINE.md +60 -206
  3. package/CHANGELOG.md +64 -0
  4. package/CONTRIBUTING.md +14 -10
  5. package/DELIVERY.md +118 -261
  6. package/README.md +91 -431
  7. package/VERIFY.md +74 -95
  8. package/demo/README.md +1 -1
  9. package/demo/demo-baseline.sh +1 -3
  10. package/demo/demo.sh +2 -5
  11. package/docs/acp-backend-rail.md +236 -0
  12. package/docs/external-mcp-host.md +131 -0
  13. package/docs/fresh-cut-policy.md +99 -0
  14. package/docs/setup-clean-host.md +124 -328
  15. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +160 -67
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +12 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +11 -10
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +1 -1
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +4 -4
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +11 -9
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +7 -5
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +13 -14
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +45 -40
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +117 -95
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +23 -57
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +16 -7
  28. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -53
  29. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +21 -36
  30. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -15
  31. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +12 -11
  32. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +2 -7
  33. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +30 -67
  34. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +256 -0
  35. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +91 -5
  36. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +258 -0
  37. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +202 -0
  38. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +289 -0
  39. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +170 -0
  40. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +136 -0
  41. package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +8 -5
  42. package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +3 -3
  43. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +51 -0
  44. package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +9 -4
  45. package/mcp/entwurf-bridge/src/index.ts +176 -67
  46. package/mcp/entwurf-bridge/start.sh +2 -2
  47. package/mcp/entwurf-bridge/test.sh +23 -9
  48. package/mcp/entwurf-bridge/tsconfig.build.json +11 -2
  49. package/package.json +25 -11
  50. package/pi-extensions/entwurf-control.ts +220 -71
  51. package/pi-extensions/lib/acp/acp-client.ts +22 -0
  52. package/pi-extensions/lib/acp/augment.ts +42 -3
  53. package/pi-extensions/lib/acp/backend-adapter.ts +12 -11
  54. package/pi-extensions/lib/acp/backend.ts +366 -38
  55. package/pi-extensions/lib/acp/config.ts +1 -1
  56. package/pi-extensions/lib/acp/engraving.ts +43 -1
  57. package/pi-extensions/lib/acp/event-mapper.ts +16 -13
  58. package/pi-extensions/lib/acp/models.ts +4 -4
  59. package/pi-extensions/lib/acp/overlay.ts +11 -9
  60. package/pi-extensions/lib/entwurf-control-rpc.ts +7 -5
  61. package/pi-extensions/lib/entwurf-core.ts +15 -15
  62. package/pi-extensions/lib/entwurf-resume-args.ts +41 -52
  63. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
  64. package/pi-extensions/lib/entwurf-v2-contract.ts +120 -99
  65. package/pi-extensions/lib/entwurf-v2-decider.ts +30 -91
  66. package/pi-extensions/lib/entwurf-v2-lock.ts +16 -7
  67. package/pi-extensions/lib/entwurf-v2-production.ts +4 -78
  68. package/pi-extensions/lib/entwurf-v2-release.ts +25 -49
  69. package/pi-extensions/lib/entwurf-v2-runner.ts +6 -21
  70. package/pi-extensions/lib/entwurf-v2-send-fallback.ts +12 -11
  71. package/pi-extensions/lib/entwurf-v2-send.ts +2 -7
  72. package/pi-extensions/lib/entwurf-v2-surface.ts +36 -76
  73. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +370 -0
  74. package/pi-extensions/lib/meta-session.ts +93 -5
  75. package/pi-extensions/lib/mux-fresh-call.ts +328 -0
  76. package/pi-extensions/lib/mux-launch.ts +267 -0
  77. package/pi-extensions/lib/mux-placement.ts +387 -0
  78. package/pi-extensions/lib/mux-resume-call.ts +221 -0
  79. package/pi-extensions/lib/resume-launch-identity.ts +162 -0
  80. package/pi-extensions/lib/session-id.js +8 -5
  81. package/pi-extensions/lib/socket-discovery.ts +3 -3
  82. package/prompts/engraving.md +13 -5
  83. package/run.sh +688 -282
  84. package/scripts/agy-bridge-config.py +5 -1
  85. package/scripts/check-acp-backend-preflight.ts +1 -1
  86. package/scripts/check-acp-carrier-augment.ts +246 -10
  87. package/scripts/check-acp-cortex.ts +5 -5
  88. package/scripts/check-acp-overlay.ts +13 -3
  89. package/scripts/check-acp-prompt-lifecycle.ts +565 -0
  90. package/scripts/check-acp-stop-reason.ts +342 -0
  91. package/scripts/check-acp-stream-hooks.ts +504 -0
  92. package/scripts/check-elapsed.sh +25 -0
  93. package/scripts/check-entwurf-bridge-boot.ts +51 -4
  94. package/scripts/check-entwurf-bridge-pi-free.ts +6 -5
  95. package/scripts/check-entwurf-control-rpc.ts +4 -3
  96. package/scripts/check-entwurf-resume-args.ts +72 -70
  97. package/scripts/check-entwurf-session-identity.ts +14 -10
  98. package/scripts/check-entwurf-v2-contract.ts +34 -59
  99. package/scripts/check-entwurf-v2-decider.ts +17 -177
  100. package/scripts/check-entwurf-v2-lock.ts +5 -2
  101. package/scripts/check-entwurf-v2-matrix.ts +3 -53
  102. package/scripts/check-entwurf-v2-production.ts +2 -91
  103. package/scripts/check-entwurf-v2-release.ts +10 -105
  104. package/scripts/check-entwurf-v2-runner.ts +4 -85
  105. package/scripts/check-entwurf-v2-send-fallback.ts +5 -6
  106. package/scripts/check-entwurf-v2-send.ts +0 -28
  107. package/scripts/check-entwurf-v2-surface.ts +198 -128
  108. package/scripts/check-entwurf-v2-visible-resume.ts +445 -0
  109. package/scripts/check-fresh-cut-gate.sh +1 -1
  110. package/scripts/check-gate-qualification.ts +101 -7
  111. package/scripts/check-install-container.sh +10 -2
  112. package/scripts/check-install-surface.ts +1 -1
  113. package/scripts/check-keyset-overlap.py +1 -1
  114. package/scripts/check-meta-facts.ts +249 -0
  115. package/scripts/check-meta-identity-consumers.ts +1 -1
  116. package/scripts/check-meta-session.ts +169 -0
  117. package/scripts/check-mux-launch-tmux.ts +316 -0
  118. package/scripts/check-mux-launch.ts +288 -0
  119. package/scripts/check-mux-launcher-fence.ts +264 -0
  120. package/scripts/check-mux-parent-artifact.ts +195 -0
  121. package/scripts/check-mux-placement-tmux.ts +322 -0
  122. package/scripts/check-mux-placement.ts +323 -0
  123. package/scripts/check-mux-resume-call.ts +282 -0
  124. package/scripts/check-probe-cli-shim.ts +25 -22
  125. package/scripts/check-probe-ordering.ts +121 -81
  126. package/scripts/check-release-gate-outcomes.ts +452 -0
  127. package/scripts/check-resume-launch-identity.ts +244 -0
  128. package/scripts/check-socket-discovery.ts +1 -1
  129. package/scripts/fixtures/mux-parent-transcript.scrubbed.jsonl +3 -0
  130. package/scripts/inventory-verification-surface.ts +349 -0
  131. package/scripts/lib/claude-launcher-fence.ts +322 -0
  132. package/scripts/lib/live-skip.ts +33 -0
  133. package/scripts/lib/mutation-qualify.ts +109 -3
  134. package/scripts/lib/probe-acp-turn.ts +33 -12
  135. package/scripts/lib/step-outcome.sh +88 -0
  136. package/scripts/meta-bridge-doctor.sh +6 -8
  137. package/scripts/meta-facts.ts +60 -0
  138. package/scripts/mutants/acp-augment.json +106 -0
  139. package/scripts/mutants/acp-cortex.json +2 -2
  140. package/scripts/mutants/acp-overlay.json +17 -0
  141. package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
  142. package/scripts/mutants/acp-stop-reason.json +80 -0
  143. package/scripts/mutants/acp-stream-hooks.json +158 -0
  144. package/scripts/mutants/bridge-boot-resume.json +45 -0
  145. package/scripts/mutants/meta-facts.json +50 -0
  146. package/scripts/mutants/meta-identity.json +36 -0
  147. package/scripts/mutants/mux-boundary.json +196 -0
  148. package/scripts/mutants/mux-fresh-call.json +185 -0
  149. package/scripts/mutants/mux-launcher-fence.json +123 -0
  150. package/scripts/mutants/mux-parent-artifact.json +39 -0
  151. package/scripts/mutants/mux-resume-call.json +148 -0
  152. package/scripts/mutants/probe-ordering.json +6 -1021
  153. package/scripts/mutants/release-gate.json +140 -0
  154. package/scripts/mutants/resume-args.json +76 -0
  155. package/scripts/mutants/resume-launch-identity.json +96 -0
  156. package/scripts/mutants/v2-surface.json +74 -23
  157. package/scripts/mutants/v2-visible-resume.json +215 -0
  158. package/scripts/new-session-id.ts +9 -4
  159. package/scripts/smoke-acp-bundled-mcp-live.ts +4 -3
  160. package/scripts/smoke-acp-carrier-augment-live.ts +2 -2
  161. package/scripts/smoke-acp-cortex-live.ts +8 -14
  162. package/scripts/smoke-acp-long-turn-live.ts +185 -0
  163. package/scripts/smoke-acp-mcp-live.ts +2 -2
  164. package/scripts/smoke-acp-memory-containment-live.ts +2 -2
  165. package/scripts/smoke-acp-ordering-probe-live.ts +2 -2
  166. package/scripts/smoke-acp-overlay-live.ts +2 -2
  167. package/scripts/smoke-acp-provider-live.ts +2 -2
  168. package/scripts/smoke-acp-raw-turn-live.ts +3 -3
  169. package/scripts/smoke-acp-session-reuse-live.ts +2 -2
  170. package/scripts/smoke-acp-skill-live.ts +2 -2
  171. package/scripts/smoke-acp-socket-citizen-live.ts +4 -3
  172. package/scripts/smoke-acp-v2-send-live.ts +4 -3
  173. package/scripts/smoke-agy-native-push-live.ts +10 -20
  174. package/scripts/smoke-claude-native-resume-live.sh +13 -3
  175. package/scripts/smoke-entwurf-chain-live.ts +352 -0
  176. package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -3
  177. package/scripts/smoke-meta-honesty.sh +1 -1
  178. package/scripts/smoke-meta-install-state.sh +4 -0
  179. package/scripts/smoke-mux-fresh-call-live.ts +365 -0
  180. package/scripts/smoke-mux-lifecycle-live.ts +1136 -0
  181. package/scripts/smoke-pi-attach.ts +1 -1
  182. package/scripts/smoke-user-scope-citizen.sh +1 -1
  183. package/scripts/tsconfig.json +1 -0
  184. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +0 -160
  185. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +0 -273
  186. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +0 -216
  187. package/pi-extensions/lib/entwurf-v2-spawn-production.ts +0 -373
  188. package/pi-extensions/lib/entwurf-v2-spawn.ts +0 -323
  189. package/scripts/check-acp-sdk-surface.ts +0 -236
  190. package/scripts/check-entwurf-v2-spawn-production.ts +0 -551
  191. package/scripts/check-entwurf-v2-spawn.ts +0 -399
  192. package/scripts/smoke-entwurf-v2-spawn-live.ts +0 -188
  193. package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +0 -469
package/run.sh CHANGED
@@ -51,6 +51,22 @@ PROVIDER_ID="entwurf"
51
51
  # surfaces). Under an installed package it REFUSES rather than falling back to raw
52
52
  # .ts — a fallback would just re-raise the fence error with a worse message.
53
53
  # check-install-surface pins both halves statically.
54
+ # Release-gate STEP OUTCOME protocol (P1) — loaded LAZILY, never at top level.
55
+ #
56
+ # Only two surfaces need it: `release_gate`, which classifies each step's exit
57
+ # code, and the LIVE smoke wrappers, which return `$ENTWURF_STEP_SKIP_EXIT` when
58
+ # a prerequisite is missing. Both are dev-only. Sourcing it unconditionally at
59
+ # the top made EVERY subcommand depend on `scripts/lib/` being present, and
60
+ # check-fresh-cut-gate I9c caught the consequence immediately: on an installed
61
+ # tree assembled without that path, run.sh died with a bash "No such file"
62
+ # instead of printing the refusal it is supposed to print. An operator verb must
63
+ # never fail on a dev-only dependency, so the load happens where it is used.
64
+ entwurf_require_step_outcome() {
65
+ [ -n "${ENTWURF_STEP_SKIP_EXIT:-}" ] && return 0
66
+ # shellcheck source=scripts/lib/step-outcome.sh
67
+ . "$REPO_DIR/scripts/lib/step-outcome.sh"
68
+ }
69
+
54
70
  run_ts() {
55
71
  local rel="$1"; shift
56
72
  case "$REPO_DIR" in
@@ -69,13 +85,44 @@ run_ts() {
69
85
  esac
70
86
  }
71
87
 
88
+ # Vitest-backed gate lanes (issue #62). Same authority boundary as run_ts's dev-clone
89
+ # refusal: vitest is a devDependency, so an installed package has no runner and the
90
+ # gate refuses legibly instead of silently passing or crashing on a missing binary.
91
+ run_vitest() {
92
+ case "$REPO_DIR" in
93
+ */node_modules/*)
94
+ echo "entwurf: this gate is a dev-clone-only surface — vitest is a devDependency the installed package does not ship." >&2
95
+ echo " (Run it from a checkout after 'pnpm install'.)" >&2
96
+ return 1
97
+ ;;
98
+ esac
99
+ local bin="$REPO_DIR/node_modules/.bin/vitest"
100
+ if [ ! -x "$bin" ]; then
101
+ echo "entwurf: vitest is not installed — run 'pnpm install' in the checkout first." >&2
102
+ return 1
103
+ fi
104
+ if [ -n "${ENTWURF_MUTATION_VITEST_REPORT:-}" ]; then
105
+ # Qualification asked for structured attribution: a kill is attributed from the
106
+ # failed TEST TITLES in this report, never from Vitest's code frame (which quotes
107
+ # the source lines around the assertion — including an adjacent PASSING test's
108
+ # [QK:…] title). The marker declares the lane structured; the report goes to a
109
+ # FILE because the runner merges stdout+stderr and one warning line ahead of the
110
+ # JSON would break the parse. Human output still rides stdout.
111
+ echo "__ENTWURF_VITEST_JSON__"
112
+ (cd "$REPO_DIR" && "$bin" run --reporter=default --reporter=json \
113
+ --outputFile.json="$ENTWURF_MUTATION_VITEST_REPORT" "$@")
114
+ return
115
+ fi
116
+ (cd "$REPO_DIR" && "$bin" run "$@")
117
+ }
118
+
72
119
  usage() {
73
120
  cat <<'EOF'
74
121
  Usage:
75
122
  ./run.sh setup [project-dir] # ONE confident install: pnpm install + install + meta-bridge (if native harness) + v2 install smoke (LIVE substrate = release-gate)
76
- ./run.sh release-gate [project-dir] [--allow-skip-gemini] # SINGLE release gate: full static (pnpm check) + the v2-native live gates (v2 matrix/spawn-resume-live, check-bridge, 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). TWO-TIER summary: MUST (release-blocking, owns the exit code — "green" applies here) + BEHAVIOR (advisory, non-blocking: RGG positives model-in-loop turn). LIVE-gated MUST steps HONEST-SKIP when LIVE!=1 (a CUT needs LIVE=1, SKIP=0). --allow-skip-gemini accepted-but-ignored (back-compat). final cut authorization is GLG's.
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, 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.
77
124
  ./run.sh check-bridge # entwurf-bridge direct MCP smoke + protocol/negative-path test.sh (live substrate = v2 live smokes)
78
- ./run.sh check-entwurf-bridge-boot # deterministic gate (5d-5-pre, G1a/G1b, IN pnpm check): boot start.sh under strip-types + assert v2 fence graph loads + entwurf_v2 registered/schema; tools/list only, no auth/side-effect
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
79
126
  ./run.sh check-entwurf-bridge-pi-free # deterministic gate (0.12.1 A, IN pnpm check): static — bridge index eager value-import closure must carry no @earendil-works/pi-* (type-only + dynamic import excluded); proves the meta-bridge boots pi-free
80
127
  ./run.sh check-model-lock # deterministic unit test for pi-extensions/model-lock.ts (4-quadrant + edge cases, no API)
81
128
  ./run.sh check-shell-quote # POSIX-safety gate for shellQuote (remote SSH arg quoting in entwurf paths) — source parity + behavior matrix, no SSH
@@ -86,7 +133,7 @@ Usage:
86
133
  ./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
87
134
  ./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
88
135
  ./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
89
- ./run.sh check-bridge-delivery # deterministic gate (IN pnpm check): 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
136
+ ./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
90
137
  ./run.sh check-meta-mailbox-state-write # deterministic gate (0.11 Stage 0 step 3D-4 commit2): post-cut receipt is state-only — meta-record file byte-identical across enqueue/read, state carries lastEnqueuedAt/lastReadAt (field isolation), empty inbox no-op on record+state, drift surfaces; no API
91
138
  ./run.sh check-meta-receiver-marker # deterministic gate (SE-2): receiver marker round-trip/start-key/provenance, UserPromptSubmit cannot mint presence, reader does not gate on record existence — marker SEMANTICS only; launch topology moved to check-hook-launch-topology
92
139
  ./run.sh check-hook-launch-topology # #51 gate 1: shipped hooks.json is exec form through hook-launch.sh, launcher is loud on an empty argv (older Claude's silent args drop), exec preserves the pid so the hook's parent is Claude, and a space/$/backtick plugin path survives as one argv element
@@ -94,24 +141,27 @@ Usage:
94
141
  ./run.sh check-meta-capability-source # deterministic gate (0.11 Stage 0 step 3D-3): capability-source cut-over — mint/parse read wakeMode/deliveryLevel from the registry (metaCapabilityFor, registry-driven via injection), not META_BACKEND_DESCRIPTORS; behaviour-preserving (registry ≡ const); the record.delivery slot 3D-3 preserved was deleted by 3D-4, no API
95
142
  ./run.sh check-socket-probe # deterministic gate (0.11 Stage 0, F3): three-valued control-socket liveness (alive|dead|indeterminate) — GC reclaims dead only, indeterminate survives; pure classify + 2-socket integration, no API
96
143
  ./run.sh check-project-trust-handler # deterministic gate (0.11 Stage 0, Trust 2층): project_trust handler — decideProjectTrust matrix (escape=inherited-false+interactive+trust-here→{yes,remember:true}; non-interactive→undecided; never undefined) + adapter single-writer, fake prompt, no UI
97
- ./run.sh check-entwurf-v2-contract # deterministic gate (0.11 Stage 0 step 4-pre, 동결결정 10 + Fable R1-R5): FROZEN entwurf_v2 contract — R1 control-socket domain (currently pi; claude/codex/agy=unsupported, not folded), 6-cell intent×liveness table (single verdict, 2 allow/4 reject), N1 indeterminate-no-spawn, Q2 owned-live-no-autosend, R3 table↔receipt round-trip, R5 taxonomy, schema↔types drift; pure, no API
144
+ ./run.sh check-entwurf-v2-contract # deterministic gate (0.11 Stage 0 step 4-pre, 동결결정 10 + Fable R1-R5): FROZEN entwurf_v2 contract — R1 control-socket domain (currently pi; claude/codex/agy=unsupported, not folded), 3-cell intent×liveness table (single verdict, 1 allow/2 reject after the visible-first cut), N1 indeterminate-no-spawn, R3 table↔receipt round-trip, R5 taxonomy, schema↔types drift; pure, no API
98
145
  ./run.sh check-entwurf-v2-lock # deterministic gate (0.11 Stage 0 step 5a, 버킷 B F2): per-gid dispatch LOCK primitive — openSync wx atomic acquire, second-acquire=target-locked conflict (holder JSON for human cleanup), nonce-owned release (successor survives late release), stale reclaim same-host+ESRCH-only (EPERM/remote/alive/unknown fail-closed), empty/corrupt=conflict not auto-deleted, F2-P1 malformed gid throws; real temp dir, deps injected
99
146
  ./run.sh check-entwurf-v2-decider # deterministic gate (0.11 Stage 0 step 5b): PURE dispatch decider decideDispatch — frozen 7-step order over injected fakes, lock acquire+release tracked so reject⇒no-plan-no-lock proven; pre-probe rejects observedLiveness=null, send/resume execute keep lock + mailbox no-lock (?7), resume plan no mode/provider/model, invalid gid throws (F2-P1); pure, no IO
100
- ./run.sh check-entwurf-v2-matrix # deterministic gate (0.11 Stage 0 step 5d-5 a): REACHABILITY + LOCK SSOT table — drives REAL decideDispatch over fakes, fixes every (target kind → transport → lock class) cell as one table (control-socket/meta-mailbox/spawn-bg + bad-target/conflict/locked/undeliverable/owned-live/dormant/indeterminate rejects), coverage pass fails on a dropped cell; thin coverage not a decider re-impl; pure, no IO
101
- ./run.sh check-entwurf-v2-release # deterministic gate (0.11 Stage 0 step 5c-1): PURE release-policy reducer (decideReleasePolicy + reduceRelease) — Fable-3 release-after-observation as a state machine; spawn-started is NOT a release event, release on first socket-alive child-exited (any code) or failed start, socket↔exit race idempotent (single release), lock-nullness invariant enforced; pure, no IO
147
+ ./run.sh check-entwurf-v2-matrix # deterministic gate (0.11 Stage 0 step 5d-5 a): REACHABILITY + LOCK SSOT table — drives REAL decideDispatch over fakes, fixes every (target kind → transport → lock class) cell as one table (control-socket/meta-mailbox/native-push + bad-target/conflict/locked/undeliverable/dormant/indeterminate rejects), coverage pass fails on a dropped cell; thin coverage not a decider re-impl; pure, no IO
148
+ ./run.sh check-entwurf-v2-release # deterministic gate (0.11 Stage 0 step 5c-1): PURE release-policy reducer (decideReleasePolicy + reduceRelease) — the spawn-observation policy and its four release events went with the transport in the visible-first cut, so the shipped machine is meta-mailbox=never release (no lock) + control-socket=release once on send-final; decideReleasePolicy still enforces the lock-nullness invariant; pure, no IO
102
149
  ./run.sh check-entwurf-v2-send # deterministic gate (0.11 Stage 0 step 5c-2a): control-socket SEND hand (executeControlSocketSend) wiring transport IO onto the 5c-1 reducer — ack→sent, in-band reject→rejected (no fallback), dead→same-lock one-shot re-resolve (control retry / mailbox enqueue), indeterminate→failed+rethrow with NO fallback (no double-delivery); release exactly once, releaseLock throw never masks the send error; IO-via-dep
103
- ./run.sh check-entwurf-v2-send-fallback # deterministic gate (0.11 Stage 0 step 5c-2b): same-lock re-resolve RESOLVER (resolveDeadControlSendFallback) — fire-and-forget re-resolve: alive→control retry, dead→reject (NEVER spawn-bg), indeterminate→reject, unsupported+deliverable→mailbox plan, undeliverable/bad-target/conflict→reject; resolver never releases, mis-wire fails loud, inspect/probe throws propagate; no IO (fakes)
104
- ./run.sh check-entwurf-v2-runner # deterministic gate (0.11 Stage 0 step 5d-1): execute-router (executeDispatch) routing an already-decided DispatchDecision to its 5c transport hand → one outcome-rich EntwurfV2RunResult. reject→rejected (no hand) / control/spawn/mailbox→matching hand with decision.lock verbatim / spawn lock-retained rides executed (fail-closed) / N3 rejectReason carried / N1 SendDeliveredReleaseFailedError→execution-failed{finalizedOutcome,releaseFailed,retrySafe:false}; fake hands, no IO
150
+ ./run.sh check-entwurf-v2-send-fallback # deterministic gate (0.11 Stage 0 step 5c-2b): same-lock re-resolve RESOLVER (resolveDeadControlSendFallback) — fire-and-forget re-resolve: alive→control retry, dead→reject (nothing is ever launched), indeterminate→reject, unsupported+deliverable→mailbox plan, undeliverable/bad-target/conflict→reject; resolver never releases, mis-wire fails loud, inspect/probe throws propagate; no IO (fakes)
151
+ ./run.sh check-entwurf-v2-runner # deterministic gate (0.11 Stage 0 step 5d-1): execute-router (executeDispatch) routing an already-decided DispatchDecision to its 5c transport hand → one outcome-rich EntwurfV2RunResult. reject→rejected (no hand) / control/mailbox→matching hand with decision.lock verbatim / N3 rejectReason carried / N1 SendDeliveredReleaseFailedError→execution-failed{finalizedOutcome,releaseFailed,retrySafe:false}; fake hands, no IO
105
152
  ./run.sh check-entwurf-v2-mailbox # deterministic gate (0.11 Stage 0 step 5c-4, LAST 5c transport slice): ENQUEUE-ONLY meta-mailbox SEND body (executeMetaMailboxSend) + production sendViaMailbox adapter — sender→formatMetaMailboxBody with plan.wantsReply threaded (divergence from legacy hard false), sender absent→raw plan.message, enqueue opts EXACTLY {gardenId,body,sessionsDir,mailboxDir}, enqueue throw PROPAGATES (no success:false fold — mailbox has no in-band refuse); adapter NEVER touches lock (release is the hand's job); source guard: no release/routing seam
106
- ./run.sh check-entwurf-v2-spawn # deterministic gate (0.11 Stage 0 step 5c-3a): spawn-bg RESUME watcher hand (executeSpawnBgResume) wiring spawn + socket-observe IO onto the 5c-1 reducer Fable-3: TIMEOUT IS NOT A RELEASE (bare observeTimeout→killChild, release 0; bounded killGrace then real socket-alive child-exited releases ×1); spawnChild throw→spawn-start-failed; no observation obtainable (grace elapses / post-spawn watch dep throws)→lock-retained fail-closed (released:false, evidence surfaced), NO direct-release hatch; IO-via-dep, controlled promises
107
- ./run.sh check-entwurf-resume-args # deterministic gate (0.11 Stage 0 step 5c-3b): resume-argv SSOT (buildResumePiArgs) for the v2 spawn-bg RESIDENT citizen the `legacy` one-shot variant and its async worker were removed 2026-07-27, so the shipped posture is the only one the gate drives: v2-control=--entwurf-control + no --no-extensions (keep-alive is the goal, resumed session stays addressable); keeps --mode json -p + prompt-as-turn (-p NOT dropped); explicitExtensionArgs preserved exactly once (#29); plan.launchArgs (--approve) ride before the prompt; null provider→no --provider
108
- ./run.sh check-entwurf-v2-spawn-production # deterministic gate (0.11 Stage 0 step 5c-3c): production SpawnBgResumeDeps factory (makeProductionSpawnBgResumeDeps) wiring the 5c-3a watcher's 6 IO seams no real pi/socket/timer (that=opt-in smoke-entwurf-v2-spawn-live, OUT of pnpm check). socketWatchVerdict: address-conflict→forged (reject, never wait)/alive→alive/dead·indeterminate→wait; spawnChild builds v2-control argv (--entwurf-control, no --no-extensions, -p+prompt, --approve, cwd authority); awaitSocketAlive connectable→resolve / symlink→reject without connect / dead→wait→alive / abort-clears; awaitChildExit code + listener cleanup; awaitTimeout schedule + abort-clear; killChild=SIGTERM; proc-less child fails loud
109
- ./run.sh smoke-entwurf-v2-spawn-live # LIVE phase gate (0.11 Stage 0 step 5c-3c, D5) OUT of pnpm check, needs LIVE=1. Exercises the production SpawnBgResumeDeps against REAL OS objects: S1 real unix socket awaitSocketAlive resolves (real lstat+probe), symlink→forged, absent→abort settles; S2 real child spawn-event resolve + SIGTERM kill + exit-code capture; S3 watcher integration real timeout→kill→child-exited→release ×1. Does NOT spawn a real pi resume (that=5d matrix). Run before 5d: LIVE=1 ./run.sh smoke-entwurf-v2-spawn-live
110
- ./run.sh smoke-entwurf-v2-spawn-resume-live # 0.11.0 (A) ACCEPTANCE gate OUT of pnpm check, needs LIVE=1. The FULL spawn-bg resident lifecycle: mint backend=pi identity seed a REAL dormant pi session (one-shot into ~/.pi/agent/sessions) runEntwurfV2(owned-outcome) routes dormant→spawn-bg resume a REAL detached pi --entwurf-control child stands its socket up, resumes, DOES a model turn. Asserts executed/spawn-bg/socket-alive/released + lock released ×1 + no lock file + pid alive + socket connectable + resume USER & assistant OK nonces in the session JSONL. Model-in-loop IN. The gate v1 deprecation (0.12) is predicated on. Model: ENTWURF_LIVE_TARGET=<provider>/<model> (default openai-codex/gpt-5.4). LIVE=1 ./run.sh smoke-entwurf-v2-spawn-resume-live
153
+ ./run.sh check-entwurf-resume-args # deterministic gate: resume-argv SSOT (buildResumePiArgs) for the S1 VISIBLE resume. The headless shape was measured wrong for a window before the consumer was written (`-p` is pi's own non-interactive mode), so the one shipped posture is `--entwurf-control` FIRST + ext args exactly once + `--session <abs file>` + optional `--provider` + `--model <m>` and the gate pins the ABSENCES as hard as the presences: no --mode, no -p, no positional prompt (a resume runs no turn), no --no-extensions, never --session-id (which MINTS a session instead of resuming one)
154
+ ./run.sh check-mux-resume-call # deterministic gate: S1 resume PLACEMENT composition (mux-resume-call.ts). No fake tmux. cwd rules are MEASURED tmux 3.6a behaviours, each a way a resume looks successful while being wrong: a nonexistent `-c` exits 0 and lands the child in $HOME (so it is refused HERE), `-c` is FORMAT-EXPANDED so `#{…}` silently rewrites the path and `#(…)` was observed running a command (so `#` is refused), and whitespace measured SAFE (so no escaping layer is owed). Also pins `-c` reaching tmux, runtime after `--`, carrier-free argv, zero identity in this module, and the surface seam that keeps the v2 composition from importing mux
155
+ ./run.sh check-mux-parent-artifact # deterministic gate for the tracked scrubbed parent-transcript fixture a version-pinned sample of the PARENT-SIDE shape (fresh_call toolResult + the later callback custom_message) so downstream never opens a private transcript. Pins event order, the toolCallId join on the RESULT (pi writes no separate toolCall row), the launch nonce reappearing verbatim in the callback body, the <sender_info> envelope field names, and the absence of operator paths / real garden ids / real uuids. NOT placement evidence
156
+ ./run.sh check-mux-launcher-fence # deterministic gate for the shared operator-launcher fence (issue #67): scripts/lib/claude-launcher-fence.ts + its wiring into BOTH mux LIVE smokes. Replants the observed install-destruction shape (real HOME + fixture XDG_DATA_HOME → self-update retargets the real `claude` launcher into the fixture tree, teardown deletes it) wholly inside disposable mkdtemp roots — the real launcher is never inspected. Pins fail-closed preflight, retarget/content-change detection before cleanup, removal BLOCKED on fixture reference / unproven safety / surviving tracked panes, exact operator-parity XDG restore (absent = DELETED, not canonical defaults), the lifecycle cell-branch topology, and one shared helper consumed by both smokes
157
+ ./run.sh check-entwurf-v2-visible-resume # deterministic gate: S1 visible-resume COMPOSITION (entwurf-v2-visible-resume.ts) with every seam injected the whole state machine incl. the timeout branch runs with no tmux/lock/socket/clock. Pins lock BEFORE liveness, identity under the lock and before any window (no-transcript citizen fails loud, opens nothing), live/indeterminate/address-conflict refused unlaunched, observation as a BOUNDED WAIT (measured: socket answers ~2–4s after launch, so one immediate probe would call a successful resume unobserved), exactly ONE launch on every path, timeout lock released + window left open + nothing retried/killed, failed release throws, and the two receipts staying separate in type and text
158
+ ./run.sh check-resume-launch-identity # deterministic gate for resume-launch-identity.ts, the record-authoritative launch-identity leaf preserved through the visible-first cut (spawn-bg and all its callers are gone; this leaf answers "which being is this, and which conversation is theirs"). Temp meta-store fixture: gardenId→record.transcriptPath happy path with header cwd/provider/model; C3 integrity (header id ≠ record.nativeSessionId → refused, never resumed); #52 ADDRESSABLE read (a gid that no longer holds its nativeSessionId alone is refused from EITHER side — the plain targeted read would resume one transcript twice under two locks); cause fidelity per impossible resume incl. the F7 pin (recorded-but-deleted transcript → MISSING, not "no recorded model"); header↔gate SSOT. No spawn/socket/timer
111
159
  ./run.sh smoke-entwurf-v2-matrix-live # LIVE sentinel (0.11 Stage 0 step 5d-5, D4-b) — OUT of pnpm check, needs LIVE=1. Drives REAL production runEntwurfV2 deps over REAL OS objects, 4 cells: C1 control-socket (real pi --entwurf-control resident → RPC send → lock acquire→release ×1), C1b record-less socket (#50 C4: live record-less pi → EVERY intent rejected pre-probe record-less-socket, no lock, rendered hint names record authority + fresh-cut), C2 meta-mailbox deliverable (armed self-fetch citizen → real .msg enqueue, lock-free), C3 meta-mailbox guard (no armed receiver → reject, no garbage). Model-in-loop OUT (transport/lock/enqueue gate, GPT Q2); negative/timeout stay deterministic. Model: ENTWURF_LIVE_TARGET=<provider>/<model> (default openai-codex/gpt-5.4). LIVE=1 ./run.sh smoke-entwurf-v2-matrix-live
112
- ./run.sh smoke-agy-native-push-live # 봉인 8 LIVE acceptance for the native-push (agy) rail — OUT of pnpm check, needs LIVE=1 + AGY_CONVERSATION_ID (a live agy conversation). Drives the REAL antigravity adapter + register core + runEntwurfV2 (production deps): doctor-static preflight (dangling→FAIL, the ③ gate), probe route, register create/attach idempotency, fire→native-push delivered, post-send re-probe (D7 partial), owned-outcome→native-push-no-resume-authority, bogus-conv→native-push-probe-indeterminate. Meta-store isolated to a temp dir (only the agy round-trip is real; no real-store residue). LIVE=1 AGY_CONVERSATION_ID=<convId> ./run.sh smoke-agy-native-push-live
160
+ ./run.sh smoke-agy-native-push-live # 봉인 8 LIVE acceptance for the native-push (agy) rail — OUT of pnpm check, needs LIVE=1 + AGY_CONVERSATION_ID (a live agy conversation). Drives the REAL antigravity adapter + register core + runEntwurfV2 (production deps): doctor-static preflight (dangling→FAIL, the ③ gate), probe route, register create/attach idempotency, fire→native-push delivered, post-send re-probe (D7 partial), bogus-conv→native-push-probe-indeterminate. Meta-store isolated to a temp dir (only the agy round-trip is real; no real-store residue). LIVE=1 AGY_CONVERSATION_ID=<convId> ./run.sh smoke-agy-native-push-live
161
+ ./run.sh smoke-mux-lifecycle-live # RELEASE MUST integrated LIVE lifecycle acceptance for mux, through the REAL MCP surface — OUT of pnpm check, needs LIVE=1 and spends model turns (two pi siblings: native + recorded-ACP provider, each resumed once; one Claude Code sibling). tools/call fresh_call -> nonce callback sender envelope -> v2 control send landing in the sibling's own transcript -> resume_call REFUSED while live (window count unchanged) -> stable-handle close (pane gone, socket dead, record kept) -> dormant delivery refused honestly -> public entwurf_resume_call with LAUNCH and OBSERVATION receipts kept apart, same-gid socket alive, zero new citizens, zero lock residue, resumed pane_start_path == RECORD cwd (separate tmux query), transcript byte-identical across the resume -> v2 recall of the pre-close fact. claude-code resume refused target-not-pi, no window opened and no lock residue. LIVE=1 ./run.sh smoke-mux-lifecycle-live
113
162
  ./run.sh check-entwurf-facts # deterministic gate (0.11 Stage 0 step 4, fact-provider slice 1+2): PURE PeerFact core + resolveFactList union — R1 out-of-domain→unsupported, R3b socket-domain 4-value, facts-only keyset; union: PeerFact + RecordLessSocketFact by gardenId (#50 C4: record-less socket = diagnostic subject, gid+liveness only), dormant→dead, F3 indeterminate preserved, out-of-socket-domain+socket fail-loud; pure, no IO
114
163
  ./run.sh check-socket-discovery # deterministic gate (0.11 Stage 0 step 4, fact-provider slice 3): SOCKET-axis scanSocketProbes — probes (dir sockets) ∪ (in-domain citizen canonical paths) 3-valued; dormant citizen no-file → dead (resumable, not unprobed), stall → indeterminate (F3), dir hygiene/dedup/missing-dir + e2e → resolveFactList; readdir/probe injected, no IO
164
+ ./run.sh check-meta-facts # deterministic gate for the meta-facts projection (#65): drives the REAL CLI — full-record join, parse-before-uniqueness, no-winner duplicates, drift/symlink/invalid-UTF-8 defects in-band, deterministic bytes, exit contract 0/2/3, dispatch+emit reachability
115
165
  ./run.sh check-meta-listing # deterministic gate: META-STORE facts axis — kind-carrying entries; non-regular records are never read, parse/drift become diagnostics, duplicate nativeSessionId quarantines every rival but not unrelated citizens; strict throws / collect partial; pure injected IO
116
166
  ./run.sh check-entwurf-fact-provider # deterministic gate (0.11 Stage 0 step 4, fact-provider slice 4b): ASSEMBLY listEntwurfFacts — listAllMetaIdentities→scanSocketProbes→pre-quarantine out-of-socket-domain/socket conflicts→resolveFactList(clean)→{facts,diagnostics}; C-원칙: expected corruption (parse/collision)→diagnostics (listing survives), impossible invariant (dup/unprobed)→throw; collision quarantines BOTH PeerFact+socket; deps injected, no IO
117
167
  ./run.sh check-entwurf-peers-surface # deterministic gate (0.11 Stage 0 step 4, fact-provider slice 4c): MCP entwurf_peers RENDER renderEntwurfPeers (#50 C4) — payload keyset exactly {peers, diagnostics}; FORBIDDEN keys sessions/socketOnly/controlDir/socketPath/count + no .sock in text (socket is transport, never identity); record-less socket = aggregated record-less-socket diagnostic (F8, liveness-keyed message, alive names fresh-cut); NO verb-routing key (JSON deep scan) NOR word (text), diagnostics both surfaces, empty→(none), unsupported shown; WIRING guard: both surfaces call provider+render, getLiveSessions + /entwurf-sessions gone; facts fabricated, no IO
@@ -129,9 +179,11 @@ Usage:
129
179
  ./run.sh check-meta-doctor-oracle # 0.12.8 (#51): detection power of the release ORACLE — healthy fixture must reach `doctor: PASS`, then 21 planted defects (retired shell form, partial hand-patch, launcher bypass/repoint/provenance loss, malformed exec args, owner type drift, extra leaf/group, doorbell asyncRewake/path/timeout, no live bridge, stale receiver, ambiguous/missing cache, missing hook log/writer, failing CLI probes) must each turn it FAIL naming their own cause, plus a positive case pinning that a long-writing CLI is NOT a false negative. Offline/deterministic (deps: bash+node+python3)
130
180
  ./run.sh smoke-agy-install-state # agy MCP + exact permission ownership regression: isolated HOME+XDG, adopt/state/inverse, symlink refuse, setup degrade. Offline/deterministic
131
181
  ./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)
182
+ ./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
183
+ ./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
132
184
  ./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
133
- ./run.sh check-probe-ordering # §11-7 ordering-probe deterministic gate: raw-client SAMENESS pinned to backend.ts (sequence/args/timeouts/permission policy — the probe may never measure a lookalike), phase attribution incl. set-model, probe-mode fixture wire markers (delay honored, probeRunId REQUIRED, smoke-acp-mcp-live legacy compat), event-log door integrity (reserved keys refused at write; unknown marker name, broken sort axis, or a payload the classifier cannot judge on is MALFORMED, never a quiet event), and the §11-7 paired-verdict truth table (P0/I0 outside the space, phase-qualified D, B promotion ladder, C, A two-delay rule). Offline/deterministic; kill-proofed via scripts/mutants/probe-ordering.json
134
- ./run.sh check-probe-cli-shim # §11-7-c B-name-snapshot PRODUCER gate: the CLI shim driven as a REAL process against fake CLIs (no API, no cost). Proves what a defect would buy — FABRICATED evidence (a malformed init is never reported as an empty name set; the boot report carries the true target path+sha256 the classifier verifies against the roster), a DESTROYED turn (byte transparency across mid-UTF8/CRLF/oversized/unterminated framing, exit-code fidelity, signal re-raise, inbound signal forwarding, stderr passthrough, stdout backpressure), and LEAKED operator state (exact-allowlist env scrub, no argv/env/prompt body in the log). Offline/deterministic; kill-proofed via scripts/mutants/probe-ordering.json
185
+ ./run.sh check-probe-ordering # §11-7 ordering-probe deterministic gate: raw-client SAMENESS pinned to backend.ts (sequence/args/timeouts/permission policy — the probe may never measure a lookalike), phase attribution incl. set-model, probe-mode fixture wire markers (delay honored, probeRunId REQUIRED, smoke-acp-mcp-live legacy compat), event-log door integrity (reserved keys refused at write; unknown marker name, broken sort axis, or a payload the classifier cannot judge on is MALFORMED, never a quiet event), and the §11-7 paired-verdict truth table (P0/I0 outside the space, phase-qualified D, B promotion ladder, C, A two-delay rule). Offline/deterministic; one product claim (no production prompt cutoff) is replant-qualified via scripts/mutants/probe-ordering.json, while the other assertions are direct [CHECK:*] contracts
186
+ ./run.sh check-probe-cli-shim # §11-7-c B-name-snapshot PRODUCER gate: the CLI shim driven as a REAL process against fake CLIs (no API, no cost). Proves what a defect would buy — FABRICATED evidence (a malformed init is never reported as an empty name set; the boot report carries the true target path+sha256 the classifier verifies against the roster), a DESTROYED turn (byte transparency across mid-UTF8/CRLF/oversized/unterminated framing, exit-code fidelity, signal re-raise, inbound signal forwarding, stderr passthrough, stdout backpressure), and LEAKED operator state (exact-allowlist env scrub, no argv/env/prompt body in the log). Offline/deterministic; 20 direct [CHECK:*] contracts, deliberately no longer replant-qualified after #70 subtraction
135
187
  ./run.sh smoke-agy-statusline-state # agy ambient garden-id statusLine install/doctor/inverse regression. Offline/deterministic
136
188
  ./run.sh smoke-agy-hooks-state # agy PreInvocation birth/sender hook install/doctor/inverse + direct stdin→meta-record regression. Offline/deterministic
137
189
  ./run.sh smoke-user-scope-citizen # 0.12.6 install-boundary: pi packages[] registration SSOT (register-pi-package.py) — idempotent + preserves unrelated + normalizes stale + remove symmetry + fails loud. Offline/hermetic (deps: bash+python3)
@@ -154,12 +206,13 @@ Usage:
154
206
  ./run.sh doctor-agy-hooks # fail-loud doctor for agy hooks.json imprint wiring
155
207
  ./run.sh meta-bridge-prune # 1.0.0 meta-bridge Phase 4: LISTING-ONLY store hygiene — classify orphan/stale/ambiguous/keep, print manual rm commands, delete NOTHING ([dir] [--ttl-days N])
156
208
  ./run.sh meta-bridge-fresh-cut # the ONE generation verb (the verb every v3-only rejection names): quiesce-check live sockets/markers/native-push conversations (refusing any surface it cannot inspect), archive meta-sessions/ + meta-mailbox/ to `<dir>.archive-<ts>`, clear dead transport residue, open an empty v3 generation. No migration, no restore — the archive is forensic only. EXIT CONTRACT (#54, `--help` prints it): 0 complete / 1 NOTHING MOVED (re-run, do not setup) / 2 usage / 3 cut transition incomplete (inspect) / 4 cut complete but residue cleanup failed (`setup` may run; re-run only before new citizen birth, otherwise remove residue manually)
209
+ ./run.sh meta-facts # #65 owner-normalized READ-ONLY store projection: deterministic JSON {schemaVersion:1, storeDir, citizens: full v3 records sorted by gardenId, defects: {filename,message}} — THE listing contract emitted by the owner so consumers stop copying the certification. No liveness/sockets/transcript contents. EXIT: 0 readable (defects in-band; missing store = empty), 2 usage, 3 unreadable ([dir])
157
210
  ./run.sh meta-bridge-managed-keys # 0.10.0 meta-bridge: print the SSOT of settings keys entwurf OWNS (consumers read this to stay disjoint — keyset-owner invariant)
158
211
  ./run.sh check-keyset-overlap <fragment.json...> # 0.10.0 meta-bridge: PREVENTIVE keyset guard — fail if a consumer fragment collides with any pi-owned key (cross-repo; not in pnpm check)
159
212
  ./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)
160
213
  ./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
161
214
  ./run.sh check-pack # publish gate (dry-run): npm pack --dry-run + tarball invariants (runtime-critical present, dev residue absent)
162
- ./run.sh check-fresh-cut-gate # SOURCE cell of the generation-boundary proof (IN pnpm check): 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
215
+ ./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
163
216
  ./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke with the pinned pi peers (0.82.x) + the npm-installed bridge BOOTS (tools/list) and DELIVERS (tools/call entwurf_v2 → .msg lands) + 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)
164
217
  ./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)
165
218
  ./run.sh sync-auth # copy ~/.pi/agent/auth.json anthropic OAuth credentials to entwurf alias
@@ -362,7 +415,7 @@ preflight_dep_integrity() {
362
415
  # pi-managed install — pi omits peers (--legacy-peer-deps) and its own loader
363
416
  # supplies the runtime, so the trio is legitimately absent.
364
417
  # neutral npm/pnpm install — nothing supplies it. The optional peer is simply
365
- # unresolved (entwurf-preflight.ts:51), and the owned-outcome lane that needs it
418
+ # unresolved (entwurf-preflight.ts:51), and the project-trust lane that needs it
366
419
  # is dead on that host. Excluding the trio from this probe is still right (a hard
367
420
  # require would reject every consumer install), but it is a KNOWN GAP, not proof
368
421
  # that the runtime is present by another route.
@@ -517,7 +570,7 @@ remove_user_scope_citizen() {
517
570
 
518
571
  # The ~/.pi/agent/entwurf-targets.json symlink machinery (ensure_agent_dir_symlinks
519
572
  # + the `setup:links` command) is GONE (#50 C3): the target registry it linked has
520
- # no reader anymore — v2 resumes record-backed citizens and never resolves a spawn
573
+ # no reader anymore — v2 addresses record-backed citizens and never resolves a spawn
521
574
  # model from a file. An operator's existing link/copy is inert; nothing reads it.
522
575
 
523
576
  remove_local_package() {
@@ -649,7 +702,7 @@ smoke_pi_attach() {
649
702
  # ARTIFACT routes that garden id to its control socket. Two halves, deliberately
650
703
  # different in kind: the record+address half drives `birthPiCitizen` (the exact seam
651
704
  # entwurf-control's session_start calls) so the gate is deterministic and lives in
652
- # `pnpm check`; the delivery half spawns the dist entry as its own process and speaks
705
+ # `pnpm run check:full`; the delivery half spawns the dist entry as its own process and speaks
653
706
  # MCP stdio (check-bridge-delivery's driver, socket-rail fixture). P4 is the one with
654
707
  # teeth — re-opening the same pi session must ATTACH to the same gardenId, never mint
655
708
  # a second address under peers that already hold it. P8 (#50 C3 tail) gates goal 3:
@@ -749,17 +802,17 @@ check_entwurf_v2_contract() {
749
802
  # decision table is a constant; the "table cell ↔ dispatch receipt" round-trip
750
803
  # is asserted exhaustively — THE executable proof F6 demands ("산문 금지").
751
804
  # R1 control-socket capability domain (currently pi; claude-code/codex/antigravity =
752
- # unsupported, never folded into dead/indeterminate). 6-cell table, single
753
- # verdict per cell (Q2), 2 allow / 4 reject. N1 indeterminate never spawns;
754
- # Q2 owned-outcome+live never auto-sends. R5 taxonomy covers table reasons +
755
- # pre-claims bad-target/untrusted-fail-fast/target-locked (bucket B F2). Plus
805
+ # unsupported, never folded into dead/indeterminate). 3-cell table since the
806
+ # visible-first cut, single verdict per cell, 1 allow / 2 reject. N1
807
+ # indeterminate never dispatches, and nothing on any cell starts a process.
808
+ # R5 taxonomy covers table reasons + pre-claims bad-target/target-locked. Plus
756
809
  # a schema↔types drift guard on the TypeBox input/receipt. Pure, no API.
757
810
  run_ts scripts/check-entwurf-v2-contract.ts
758
811
  }
759
812
 
760
813
  check_entwurf_v2_lock() {
761
814
  # Deterministic gate for 0.11 Stage 0 step 5a (버킷 B F2): the per-gid dispatch
762
- # LOCK primitive — the only guard against a double-spawn of the same dormant
815
+ # LOCK primitive — what serializes concurrent in-domain dispatch at one
763
816
  # target (pi self-guards CREATE but not RESUME, 검증원장 F2). acquire =
764
817
  # openSync(lockPath,"wx") atomic; a second acquire without release =
765
818
  # target-locked conflict carrying the holder JSON (F2-P2 human cleanup). release
@@ -778,10 +831,8 @@ check_entwurf_v2_decider() {
778
831
  # lookup / lock / socket inspect+probe / preflight / capability), tracking lock
779
832
  # acquire+release so "reject ⇒ no plan AND no lock retained" is PROVEN. Covers:
780
833
  # bad-target/target-locked/target-address-conflict carry observedLiveness=null
781
- # (pre-probe), every other reject + untrusted-fail-fast carry a measured value;
782
- # control-socket send + spawn-bg resume execute KEEP the lock, meta-mailbox send
783
- # takes NO lock (?7); resume plan has no mode/wantsReply/provider/model but has
784
- # expectedSocketPath/observeTimeoutMs/releaseWhen; an invalid gid throws before
834
+ # (pre-probe), every other reject carries a measured value; a control-socket send
835
+ # executes KEEPING the lock, meta-mailbox send takes NO lock (?7); an invalid gid throws before
785
836
  # any lookup (F2-P1). Pure, no IO, no API.
786
837
  run_ts scripts/check-entwurf-v2-decider.ts
787
838
  }
@@ -791,9 +842,9 @@ check_entwurf_v2_matrix() {
791
842
  # TABLE. Drives the REAL decideDispatch over minimal injected fakes and fixes, as
792
843
  # one readable table, every (target kind → transport → lock class) cell the 5d-5
793
844
  # claim covers: bad-target/address-conflict/target-locked rejects, unsupported
794
- # meta-mailbox (deliverable) vs mailbox-undeliverable (inactive) vs owned reject,
795
- # in-domain control-socket (live) / spawn-bg (dormant owned) / released rejects
796
- # (owned-live, ff-dormant, indeterminate, under-lock conflict). A coverage pass
845
+ # meta-mailbox (deliverable) vs mailbox-undeliverable (inactive) reject,
846
+ # in-domain control-socket (live) / released rejects
847
+ # (ff-dormant, indeterminate, under-lock conflict). A coverage pass
797
848
  # FAILS if any transport / lock class / pre-probe reject is missing — a dropped
798
849
  # decider cell cannot pass silently. Thin coverage, NOT a decider re-impl; surface
799
850
  # parity stays in check-entwurf-v2-surface. Pure, no IO, no API.
@@ -804,11 +855,10 @@ check_entwurf_v2_release() {
804
855
  # Deterministic gate for 0.11 Stage 0 step 5c-1: the PURE release-policy reducer
805
856
  # (decideReleasePolicy + reduceRelease) for the 5c transport hand. Proves the
806
857
  # Fable-3 "release-after-observation" timing as a pure state machine BEFORE any
807
- # spawn/send IO: meta-mailbox=never release (no lock), control-socket=release once
808
- # on send-final, spawn-bg=spawn-started is NOT a release event (load-bearing)
809
- # release on the FIRST observed transition (socket-alive child-exited any code)
810
- # or a failed start; socket↔exit race idempotent (single release either order);
811
- # decideReleasePolicy enforces the lock-nullness invariant (?7). Pure, no IO.
858
+ # send IO: meta-mailbox=never release (no lock), control-socket=release once
859
+ # on send-final. The spawn-observation policy and its four release events went with
860
+ # the transport in the visible-first cut; decideReleasePolicy still enforces the
861
+ # lock-nullness invariant (?7). Pure, no IO.
812
862
  run_ts scripts/check-entwurf-v2-release.ts
813
863
  }
814
864
 
@@ -829,11 +879,11 @@ check_entwurf_v2_send_fallback() {
829
879
  # (resolveDeadControlSendFallback) the 5c-2a hand calls on a dead connect. Proves the
830
880
  # fire-and-forget re-resolve routing over injected fakes (no filesystem): alive->
831
881
  # control-socket retry (inspected socketPath) / dead(absent)->reject (dormant-fire-
832
- # forget-unsupported, NEVER spawn-bg) / indeterminate->reject / unsupported+deliverable
882
+ # forget-unsupported, and nothing is launched) / indeterminate->reject / unsupported+deliverable
833
883
  # ->meta-mailbox plan (mini-table, no inspect/probe) / unsupported+undeliverable->reject
834
884
  # / bad-target + address-conflict->reject pre-probe. Mis-wire (plan/lock gid) fails loud
835
885
  # before IO; inspect/probe throws PROPAGATE (the hand owns failed+release); the resolver
836
- # has NO release seam; every execute plan keeps the held gid and is never spawn-bg.
886
+ # has NO release seam; every execute plan keeps the held gid.
837
887
  run_ts scripts/check-entwurf-v2-send-fallback.ts
838
888
  }
839
889
 
@@ -871,8 +921,7 @@ check_entwurf_v2_runner() {
871
921
  # routes an already-decided DispatchDecision to its 5c transport hand and maps the outcome
872
922
  # to one outcome-rich EntwurfV2RunResult. Proves over injected fake hands (no socket/spawn/
873
923
  # timer): reject -> rejected (receipt+diagnostic carried, NO hand called) / control-socket ->
874
- # sendControl(plan, lock) / spawn-bg -> resumeSpawnBg (socket-alive AND lock-retained both
875
- # ride `executed`, fail-closed is not a failure) / meta-mailbox -> sendMailbox(plan, NULL
924
+ # sendControl(plan, lock) / meta-mailbox -> sendMailbox(plan, NULL
876
925
  # lock, ?7). Carry-overs: N3 control `rejected` carries rejectReason verbatim; N1
877
926
  # SendDeliveredReleaseFailedError -> execution-failed{finalizedOutcome, releaseFailed,
878
927
  # retrySafe:false}; a plain hand throw -> execution-failed{retrySafe:false} with no
@@ -884,8 +933,9 @@ check_entwurf_v2_surface() {
884
933
  # Deterministic gate for 0.11 Stage 0 step 5d-3a: the ctx-free surface adapter
885
934
  # (entwurf-v2-surface.ts) + the entwurf-control.ts wiring contract. Proves the pure parts:
886
935
  # toDispatchInput (wants_reply→wantsReply, absent mode/wants_reply undefined) / renderEntwurfV2Result
887
- # per result kind ({text,isError} surfacing reject diagnostic, control N3 rejectReason, spawn
888
- # lock-retained, N1 delivered-but-dirty) / surface ctx-free source guard / entwurf-control
936
+ # per result kind ({text,isError} surfacing reject diagnostic, control N3 rejectReason,
937
+ # N1 delivered-but-dirty) + the dormant/indeterminate reject HINTS an operator meets after the
938
+ # visible-first cut / surface ctx-free source guard / entwurf-control
889
939
  # registers entwurf_v2 + reaches the fence via a NON-LITERAL dynamic import (no static fence
890
940
  # import → TS5097 stays closed) + decorates sender origin:pi-session/replyable:true.
891
941
  run_ts scripts/check-entwurf-v2-surface.ts
@@ -897,7 +947,7 @@ check_entwurf_bridge_boot() {
897
947
  # source-shape gate check-entwurf-v2-surface cannot — that the whole v2 fence graph LOADS at
898
948
  # boot under strip-types (G1a: a parseable tools/list proves it) and that entwurf_v2 is
899
949
  # registered on the runtime surface with its schema (G1b). tools/list only → no tools/call,
900
- # no lock/fs side effect, no auth → safe in pnpm check. Broad protocol/negative suite stays
950
+ # no lock/fs side effect, no auth → safe in pnpm run check:full. Broad protocol/negative suite stays
901
951
  # in check-bridge/test.sh (D1=A안).
902
952
  run_ts scripts/check-entwurf-bridge-boot.ts
903
953
  }
@@ -916,10 +966,9 @@ check_entwurf_bridge_pi_free() {
916
966
  check_entwurf_v2_production() {
917
967
  # Deterministic gate for 0.11 Stage 0 step 5d-2b: makeProductionEntwurfV2Deps — the ctx-free
918
968
  # PRODUCTION assembly of runEntwurfV2's deps. Proves the wiring over fake leaf-IO spies (no
919
- # real socket/lock/spawn/meta-record): decide wraps decideDispatch and acquires under the
969
+ # real socket/lock/meta-record): decide wraps decideDispatch and acquires under the
920
970
  # wired lockDir / control sendOverSocket builds the RpcSendCommand + maps + releases under
921
- # lockDir / QB3 the spawn watcher releases via the SHARED lockDir release (not the spawn
922
- # factory default) / the mailbox hand enqueues onto the wired dirs / a dead control send
971
+ # lockDir / the mailbox hand enqueues onto the wired dirs / a dead control send
923
972
  # re-resolves to the SAME sendViaMailbox instance on the SAME dirs (Q3+Q5 no drift).
924
973
  run_ts scripts/check-entwurf-v2-production.ts
925
974
  }
@@ -935,62 +984,166 @@ check_entwurf_control_rpc() {
935
984
  run_ts scripts/check-entwurf-control-rpc.ts
936
985
  }
937
986
 
938
- check_entwurf_v2_spawn() {
939
- # Deterministic gate for 0.11 Stage 0 step 5c-3a: the spawn-bg RESUME watcher hand
940
- # (executeSpawnBgResume) wiring spawn + socket-observe IO onto the 5c-1 reducer. Proves
941
- # Fable-3 over injected deferred promises (no real child/socket/timer): TIMEOUT IS NOT A
942
- # RELEASE — a bare observeTimeout escalates to killChild (release 0), then a BOUNDED
943
- # killGrace waits for a real socket-alive / child-exited to release. socket-alive ∨
944
- # child-exited(any code incl. null) -> release exactly once; the loser settling later is a
945
- # no-op. spawnChild throw -> spawn-start-failed (release, nothing to watch). No observation
946
- # obtainable (grace elapses, or a post-spawn watch dep throws and the exit can't be
947
- # observed) -> lock-retained fail-closed (released:false, pid/socket/lockPath surfaced) —
948
- # there is NO direct-release hatch; deps.releaseLock is reached ONLY via reduceRelease.
949
- run_ts scripts/check-entwurf-v2-spawn.ts
950
- }
951
-
952
987
  check_entwurf_resume_args() {
953
- # Deterministic gate for 0.11 Stage 0 step 5c-3b: the resume-argv SSOT
954
- # (buildResumePiArgs). v2-control is the only shipped shape (the legacy one-shot variant and
955
- # its launcher were removed 2026-07-27). Pins the resident posture: `--entwurf-control`
956
- # present + `--no-extensions` never (the keep-alive is the goal a resumed session stands
957
- # its socket up and stays addressable), the headless `--mode json -p` prefix and the
958
- # prompt-as-turn positional (`-p` NOT dropped), explicitExtensionArgs preserved exactly once
959
- # (provider-resolution footgun #29), plan.launchArgs (--approve) before the prompt, and a
960
- # null provider emitting no --provider.
988
+ # Deterministic gate for the resume-argv SSOT (buildResumePiArgs). S1 replaced the headless
989
+ # shape with the VISIBLE one, measured against the runtime before the consumer was written:
990
+ # `-p` is pi's own non-interactive mode, so the old `--mode json -p … <prompt>` prefix would
991
+ # put a JSON stream in the operator's window instead of a session they can use. Pins the one
992
+ # shipped posture `--entwurf-control` FIRST (the resumed session stands its socket up), no
993
+ # --mode/-p/positional prompt (a resume runs no turn), explicitExtensionArgs exactly once
994
+ # between the control flag and --session (#29 provider-resolution footgun), --session <abs
995
+ # file> never --session-id (which MINTS), and a null provider emitting no --provider.
961
996
  run_ts scripts/check-entwurf-resume-args.ts
962
997
  }
963
998
 
964
- check_entwurf_v2_spawn_production() {
965
- # Deterministic gate for 0.11 Stage 0 step 5c-3c: the production SpawnBgResumeDeps factory
966
- # (makeProductionSpawnBgResumeDeps) wiring the 5c-3a watcher's six IO seams onto the real
967
- # world proven WITHOUT a real pi spawn/socket/timer (that is the opt-in
968
- # smoke-entwurf-v2-spawn-live, kept OUT of pnpm check). socketWatchVerdict (R2 policy):
969
- # address-conflict→forged (reject, never wait), alive→alive, dead/indeterminate→wait.
970
- # spawnChild builds the v2-control argv (--entwurf-control, no --no-extensions, -p+prompt,
971
- # --approve, ext/provider/model, header cwd). awaitSocketAlive: connectable resolves, forged
972
- # (symlink) rejects without connecting, dead→wait→alive, abort clears the sleep. awaitChildExit
973
- # resolves the code + removes the listener on abort. awaitTimeout schedules + abort-clears.
974
- # killChild=SIGTERM; releaseLock delegates; a proc-less child fails loud (mis-wire).
975
- run_ts scripts/check-entwurf-v2-spawn-production.ts
976
- }
977
-
978
-
979
- smoke_entwurf_v2_spawn_live() {
980
- # LIVE phase gate for 0.11 Stage 0 step 5c-3c (D5) — kept OUT of `pnpm check`. Exercises the
981
- # production SpawnBgResumeDeps against REAL OS objects (a real unix socket, real child
982
- # processes, real timers, real abort teardown) to catch what the deterministic gate's fakes
983
- # cannot: actual spawn/exit/error event semantics, real lstat+connect liveness, and the
984
- # 5c-3a watcher's timeout→kill→child-exited→release integration on a live process. It does
985
- # NOT spawn a real `pi --entwurf-control` resume (that is the 5d surface matrix). Run once
986
- # before 5d and record the result: LIVE=1 ./run.sh smoke-entwurf-v2-spawn-live
987
- if [ "${LIVE:-}" != "1" ]; then
988
- echo "[smoke-entwurf-v2-spawn-live] skipped set LIVE=1 to run (spawns real children + opens a real unix socket)."
989
- return 0
990
- fi
991
- run_ts scripts/smoke-entwurf-v2-spawn-live.ts
999
+ check_mux_launcher_fence() {
1000
+ # Deterministic gate for the shared operator-launcher fence (issue #67):
1001
+ # scripts/lib/claude-launcher-fence.ts plus its wiring into BOTH mux LIVE smokes. Replants the
1002
+ # observed install-destruction shape (real HOME + fixture XDG_DATA_HOME self-update retargets
1003
+ # the real `claude` launcher into the fixture tree, teardown deletes it) entirely inside
1004
+ # disposable mkdtemp roots — the operator's real launcher is never inspected or touched. Pins:
1005
+ # fail-closed preflight (launcher and resolved target present, regular, executable, OUTSIDE the
1006
+ # fixture cleanup root), retarget/content-change detection before cleanup, removal BLOCKED when
1007
+ # the launcher references the fixture OR safety cannot be proven OR tracked panes are not gone
1008
+ # (known reference and unproven state each named as itself), exact operator-parity XDG restore
1009
+ # (absent means DELETED, never canonical defaults; RUNTIME_DIR stays fixture by design), the
1010
+ # lifecycle pi/claude cell-branch topology, and both smokes consuming the one shared helper.
1011
+ run_ts scripts/check-mux-launcher-fence.ts
1012
+ }
1013
+
1014
+ check_mux_parent_artifact() {
1015
+ # Deterministic gate for the tracked scrubbed parent-transcript fixture
1016
+ # (scripts/fixtures/mux-parent-transcript.scrubbed.jsonl). The fixture is a version-pinned
1017
+ # SAMPLE OF THE PARENT-SIDE SHAPE what an entwurf_fresh_call and the sibling's nonce callback
1018
+ # look like in the transcript of the citizen that made the call — so a downstream parser never
1019
+ # has to open a private transcript to learn it. Structure came from a fixture-only real Pi
1020
+ # parent retake and was then scrubbed. Pins: event order (fresh_call toolResult, then the later
1021
+ # callback custom_message), the toolCallId join on the RESULT (measured: pi writes no separate
1022
+ # toolCall row), the launch nonce reappearing verbatim in the callback body, the <sender_info>
1023
+ # envelope field names, and the absence of any operator path / real garden id / real uuid.
1024
+ # It is NOT placement evidence and the gate says so.
1025
+ run_ts scripts/check-mux-parent-artifact.ts
1026
+ }
1027
+
1028
+ check_mux_resume_call() {
1029
+ # Deterministic gate for the S1 resume placement composition (mux-resume-call.ts). Same scope
1030
+ # as check-mux-launch/check-mux-fresh-call: no fake tmux, only what is decidable without one.
1031
+ # The cwd rules are MEASURED tmux 3.6a behaviours, each a way a resume looks successful while
1032
+ # being wrong — a nonexistent `-c` exits 0 and lands the child in $HOME, and `-c` is
1033
+ # FORMAT-EXPANDED so `#{…}` rewrites the path and `#(…)` was observed running a command.
1034
+ # Whitespace measured SAFE, so the refusal set stays at two rules with no escaping layer.
1035
+ # Also pins: `-c` reaches tmux, runtime after `--`, carrier-free argv, no identity in this
1036
+ # module, and the surface seam that keeps the v2 composition from importing mux.
1037
+ run_ts scripts/check-mux-resume-call.ts
1038
+ }
1039
+
1040
+ check_entwurf_v2_visible_resume() {
1041
+ # Deterministic gate for the S1 visible-resume composition (entwurf-v2-visible-resume.ts).
1042
+ # Every seam is injected, so the whole state machine — including the timeout branch — runs
1043
+ # without tmux, a lock file, a socket or a clock. Pins: lock BEFORE liveness, identity under
1044
+ # the lock and before any window (a citizen with no transcript fails loud and opens nothing),
1045
+ # live/indeterminate/address-conflict refused unlaunched, the observation as a BOUNDED WAIT
1046
+ # (measured: the socket answers ~2–4s after launch, so one immediate probe would report a
1047
+ # successful resume as unobserved), exactly one launch on every path, timeout releasing the
1048
+ # lock and leaving the window open, a failed release throwing, and the two receipts staying
1049
+ # separate in both the type and the rendered text.
1050
+ run_ts scripts/check-entwurf-v2-visible-resume.ts
1051
+ }
1052
+
1053
+ check_resume_launch_identity() {
1054
+ # Deterministic gate for resume-launch-identity.ts, the record-authoritative launch-identity
1055
+ # leaf preserved when the visible-first cut deleted spawn-bg and every caller it had. Identity
1056
+ # authority is fail-closed risk, so the preserved leaf does not ship on its header comment:
1057
+ # a temp meta-store fixture drives gardenId → record.transcriptPath → header-id integrity
1058
+ # (a foreign/stale transcript is refused, never resumed), the #52 ADDRESSABLE read (a garden id
1059
+ # that no longer holds its nativeSessionId alone is refused from either side), and cause
1060
+ # fidelity for every impossible resume — including the F7 pin that a recorded-but-deleted
1061
+ # transcript is reported as MISSING rather than as "no recorded model". No spawn, socket, or
1062
+ # timer: the launch this identity would feed is deliberately out of scope.
1063
+ run_ts scripts/check-resume-launch-identity.ts
1064
+ }
1065
+
1066
+ check_mux_placement() {
1067
+ # Deterministic gate for the T0-b placement primitives (mux-placement.ts): the argv shapes,
1068
+ # the anchor precondition, the parse, and the close classification — everything decidable
1069
+ # without tmux. It does NOT simulate tmux; no fake tmux exists in this repo on purpose.
1070
+ # Topology is judged by check-mux-placement-tmux against a real private server.
1071
+ run_ts scripts/check-mux-placement.ts
1072
+ }
1073
+
1074
+ check_mux_placement_tmux() {
1075
+ # REAL tmux acceptance: drives the production placement functions against a private fixture
1076
+ # server (unique -S socket, never the operator's), with TMUX/TMUX_PANE actually INHERITED by
1077
+ # a fixture pane. Proves one session throughout, 1,2 -> 1,2,3 -> 1,2,3,4 -> 1,2, surviving
1078
+ # pane pids and focus, stable @window/%pane handles, the display-message rc=0 trap and its
1079
+ # refusal, and both close paths (closed / already-gone). Skips (97) without tmux or /proc.
1080
+ # Operator-run: kept OUT of pnpm check, and it is not a release-gate MUST at T0.
1081
+ run_ts scripts/check-mux-placement-tmux.ts
992
1082
  }
993
1083
 
1084
+ check_mux_launch() {
1085
+ # Deterministic gate for the T1-a visible launch (mux-launch.ts): the named preconditions
1086
+ # that run BEFORE any window opens, the PATH resolution rule, the `--` argv shape, and the
1087
+ # docs §11 import boundary. It does NOT simulate tmux. The measured trap it exists for — a
1088
+ # failed exec exits 0, prints a handle, and its window is still listed on an immediate
1089
+ # re-read — is judged against a real server by check-mux-launch-tmux.
1090
+ run_ts scripts/check-mux-launch.ts
1091
+ }
1092
+
1093
+ check_mux_fresh_call() {
1094
+ # Transition shim (issue #62): this lane is the vitest pilot, so the gate NAME is preserved
1095
+ # while the contracts live in test/*.test.ts. Same scope as before — the per-backend argv
1096
+ # dialects (both measured to fail the other way round), first-turn framing order, named
1097
+ # refusals, per-surface caller identity — PLUS the runtime axes the retired source-text gate
1098
+ # could not see: real bridge boot → tools/list schema/description, Rust-regex-family pattern
1099
+ # validity on every emitted pattern (the #62 escape), and the schema riding the actual
1100
+ # anthropic-messages request body. No fake tmux: the real-window axis is smoke-mux-fresh-call-live.
1101
+ run_vitest test/mux-fresh-call.test.ts test/fresh-call-surfaces.contract.test.ts test/fresh-call-provider.contract.test.ts
1102
+ }
1103
+
1104
+ smoke_mux_fresh_call_live() {
1105
+ # LIVE acceptance for entwurf_fresh_call — OUT of pnpm check, needs LIVE=1, spends two model
1106
+ # turns. The one axis no deterministic gate reaches: a real window, a real runtime, a real first
1107
+ # turn, and a real nonce callback whose SENDER ENVELOPE carries the new sibling's garden id.
1108
+ # Entwurf-owned write axes (XDG, all four meta roots, v2 locks, cwd) are fixture-bound; Pi also
1109
+ # gets fixture HOME so its control socket is isolated. Runtime-owned auth/config stays real and
1110
+ # native transcripts remain as evidence. Private tmux servers plus entry-set/GID residue checks
1111
+ # prove no fixture citizen lands in the operator's real record or control-socket directories.
1112
+ run_ts scripts/smoke-mux-fresh-call-live.ts
1113
+ }
1114
+
1115
+ smoke_mux_lifecycle_live() {
1116
+ # RELEASE MUST integrated LIVE acceptance for the whole mux lifecycle, through the REAL MCP
1117
+ # surface. Needs LIVE=1; spends model turns on the operator's configured runtimes (two pi
1118
+ # siblings — native provider and recorded-ACP provider — each resumed once, plus one Claude
1119
+ # Code sibling). Enters via tools/call on the shipped bridge launcher and follows one citizen
1120
+ # around: fresh_call launch receipt -> nonce callback SENDER ENVELOPE -> entwurf_v2 control
1121
+ # send landing in the sibling's own transcript -> resume_call REFUSED while live (window count
1122
+ # unchanged) -> stable-handle close proving pane/socket gone with the record preserved ->
1123
+ # dormant delivery refused honestly -> public entwurf_resume_call with its LAUNCH and
1124
+ # OBSERVATION receipts kept apart, same-gid socket alive, no new citizen, no lock residue,
1125
+ # resumed pane_start_path == the RECORD's cwd (separate tmux query, never pane text), and the
1126
+ # transcript byte-identical across the resume itself -> entwurf_v2 recalling the fact from
1127
+ # BEFORE the close, which is what proves the same CONVERSATION returned. Claude Code runs the
1128
+ # callback+mailbox half and its resume is refused as target-not-pi with no window opened and no lock residue.
1129
+ # Fixture-fenced writes (XDG, four meta roots, v2 lock dir, control socket via fixture HOME);
1130
+ # the runtime auth roots stay REAL and sibling transcripts remain in the real pi agent dir as
1131
+ # evidence. Private tmux server per cell, bounded teardown, operator real-root entry sets
1132
+ # proven unchanged.
1133
+ run_ts scripts/smoke-mux-lifecycle-live.ts
1134
+ }
1135
+
1136
+ check_mux_launch_tmux() {
1137
+ # REAL tmux acceptance for T1-a: drives production launchPi against a private fixture server
1138
+ # (unique -S socket, never the operator's) with TMUX/TMUX_PANE actually INHERITED. Proves the
1139
+ # window lands at {end} of the caller's own session, the pane process IS the runtime (no
1140
+ # shell wrapper), focus and the original panes survive, a precondition refusal opens NO
1141
+ # window, and the failed-exec trap is real. The runtime is a fixture stand-in, never the
1142
+ # operator's pi. Skips (97) without tmux or /proc. Operator-run: kept OUT of pnpm check.
1143
+ run_ts scripts/check-mux-launch-tmux.ts
1144
+ }
1145
+
1146
+
994
1147
  smoke_acp_socket_citizen_live() {
995
1148
  # S1 acceptance smoke (ACP plugin on v2) — OUT of pnpm check, needs LIVE=1.
996
1149
  # Spawns a REAL `pi --entwurf-control` resident on an ACP model
@@ -1051,6 +1204,18 @@ smoke_acp_memory_containment_live() {
1051
1204
  run_ts scripts/smoke-acp-memory-containment-live.ts
1052
1205
  }
1053
1206
 
1207
+ smoke_acp_long_turn_live() {
1208
+ # 0.13.1 LIVE acceptance for the prompt-lifecycle contract — OUT of pnpm check,
1209
+ # needs LIVE=1. Drives ONE real pi provider turn whose tool work (default
1210
+ # 3 x 240s foreground wait) deliberately outlasts the RETIRED 600s prompt cutoff, and
1211
+ # requires it to finish as ONE turn: the nonce arrives, elapsed > 600000ms, the
1212
+ # persisted transcript shows exactly ONE cold ACP bootstrap (no replay), and no
1213
+ # retry/timeout is reported anywhere. Takes >12 minutes by construction.
1214
+ # Overrides: ENTWURF_ACP_LONG_TURN_{MODEL,SLEEP_SECONDS,ROUNDS,HORIZON_MS}.
1215
+ # LIVE=1 ./run.sh smoke-acp-long-turn-live
1216
+ run_ts scripts/smoke-acp-long-turn-live.ts
1217
+ }
1218
+
1054
1219
  smoke_acp_provider_live() {
1055
1220
  # S2c acceptance smoke (ACP plugin on v2) — OUT of pnpm check, needs LIVE=1.
1056
1221
  # Drives the REAL pi PROVIDER path end to end: a real `pi` loads this
@@ -1181,16 +1346,16 @@ smoke_acp_rgg_live() {
1181
1346
 
1182
1347
  smoke_acp_cortex_live() {
1183
1348
  # On-demand LIVE smoke for the Cortex (Snowflake Cortex Code) ACP backend — the
1184
- # first NON-claude adapter on the rail (docs/acp-backend-rail.md §6). OUT of
1349
+ # first NON-claude adapter on the rail (docs/acp-backend-rail.md, Cortex audit). OUT of
1185
1350
  # `pnpm check` AND OUTSIDE the claude-only aggregate LIVE floor (capability
1186
1351
  # dignity, invariant #7): a host with no cortex install / no Snowflake auth must
1187
1352
  # not redden a release for a backend it does not run, so this is deliberately
1188
1353
  # absent from release-gate's 11-smoke ACP acceptance floor. That is a WIRING
1189
1354
  # decision, not a lower evidence bar — a cut that SHIPS cortex still owes a
1190
1355
  # deliberate run of this smoke, and the aggregate gate's silence is not a pass
1191
- # (rail §11-8 tail). Drives one real
1356
+ # (ACP rail Cortex verification boundary). Drives one real
1192
1357
  # cortex ACP turn through the entwurf provider path (outbound entwurf_v2 +
1193
- # dual-HOME overlay facts + process-group reclaim — CP2, rail §11-8).
1358
+ # dual-HOME overlay facts + process-group reclaim — CP2).
1194
1359
  # HONEST-SKIP (exit 0) when LIVE!=1 OR `cortex` is not on PATH OR no
1195
1360
  # connection is pinned — the live turn needs `cortex` installed with the
1196
1361
  # operator's own web-login auth already present (there is no `cortex auth`
@@ -1202,13 +1367,14 @@ smoke_acp_cortex_live() {
1202
1367
  # operator settings.json where a default would live. PR #40 called this
1203
1368
  # `smoke-cortex`; the canonical family name is smoke-acp-cortex-live.
1204
1369
  # LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live
1370
+ entwurf_require_step_outcome
1205
1371
  if [ "${LIVE:-}" != "1" ]; then
1206
- echo "[smoke-acp-cortex-live] skipped — set LIVE=1 (+ ENTWURF_ACP_CORTEX_CONNECTION) to run."
1207
- return 0
1372
+ echo "[entwurf:skip] smoke-acp-cortex-live — set LIVE=1 (+ ENTWURF_ACP_CORTEX_CONNECTION) to run."
1373
+ return "$ENTWURF_STEP_SKIP_EXIT"
1208
1374
  fi
1209
1375
  if ! command -v cortex >/dev/null 2>&1; then
1210
- echo "[smoke-acp-cortex-live] skipped — cortex not on PATH (install Snowflake Cortex Code and complete its own login flow)."
1211
- return 0
1376
+ echo "[entwurf:skip] smoke-acp-cortex-live — cortex not on PATH (install Snowflake Cortex Code and complete its own login flow)."
1377
+ return "$ENTWURF_STEP_SKIP_EXIT"
1212
1378
  fi
1213
1379
  run_ts scripts/smoke-acp-cortex-live.ts
1214
1380
  }
@@ -1225,35 +1391,14 @@ smoke_entwurf_v2_matrix_live() {
1225
1391
  # stay deterministic. Honest skip when LIVE!=1 so the release-gate is runnable unattended.
1226
1392
  # Model: ENTWURF_LIVE_TARGET=<provider>/<model> (default openai-codex/gpt-5.4).
1227
1393
  # LIVE=1 ./run.sh smoke-entwurf-v2-matrix-live
1394
+ entwurf_require_step_outcome
1228
1395
  if [ "${LIVE:-}" != "1" ]; then
1229
- echo "[smoke-entwurf-v2-matrix-live] skipped — set LIVE=1 to run (spawns a real pi --entwurf-control + opens a real socket)."
1230
- return 0
1396
+ echo "[entwurf:skip] smoke-entwurf-v2-matrix-live — set LIVE=1 to run (spawns a real pi --entwurf-control + opens a real socket)."
1397
+ return "$ENTWURF_STEP_SKIP_EXIT"
1231
1398
  fi
1232
1399
  run_ts scripts/smoke-entwurf-v2-matrix-live.ts
1233
1400
  }
1234
1401
 
1235
- smoke_entwurf_v2_spawn_resume_live() {
1236
- # The 0.11.0 (A) acceptance gate — kept OUT of `pnpm check`. Unlike matrix-live (a
1237
- # transport/lock sentinel, model-in-loop OUT) and spawn-live (OS-substrate watcher, no real
1238
- # pi), this drives the FULL production loop: mint a backend=pi meta identity → seed a REAL
1239
- # dormant pi session (one-shot `pi --mode json -p --no-extensions` into the REAL
1240
- # ~/.pi/agent/sessions) → runEntwurfV2(intent=owned-outcome) routes the dormant in-domain pi
1241
- # citizen to spawn-bg resume → a REAL detached `pi --entwurf-control` child stands its socket
1242
- # up, resumes, and DOES a model turn. Asserts: executed/spawn-bg/socket-alive/released, lock
1243
- # released exactly once + no lock file, resident pid alive + socket connectable, and the
1244
- # resume USER + assistant OK nonces appended to the session JSONL (real work, not just
1245
- # "process up"). This is the evidence v1 deprecation (0.12) is predicated on. Model-in-loop is
1246
- # IN. Honest skip when LIVE!=1 (skip = CI safety, NOT an acceptance PASS).
1247
- # Model: ENTWURF_LIVE_TARGET=<provider>/<model> (default openai-codex/gpt-5.4);
1248
- # ENTWURF_SPAWN_RESUME_ASSISTANT_TIMEOUT_MS (default 180000).
1249
- # LIVE=1 ./run.sh smoke-entwurf-v2-spawn-resume-live
1250
- if [ "${LIVE:-}" != "1" ]; then
1251
- echo "[smoke-entwurf-v2-spawn-resume-live] skipped — set LIVE=1 to run (spawns a real pi resume child + opens a real socket)."
1252
- return 0
1253
- fi
1254
- run_ts scripts/smoke-entwurf-v2-spawn-resume-live.ts
1255
- }
1256
-
1257
1402
  check_entwurf_facts() {
1258
1403
  # Deterministic gate for 0.11 Stage 0 step 4 (fact-provider slice 1): the PURE
1259
1404
  # fact core. Locks the PeerFact shape + R1/R3b liveness invariant before any IO
@@ -1299,6 +1444,17 @@ check_meta_listing() {
1299
1444
  run_ts scripts/check-meta-listing.ts
1300
1445
  }
1301
1446
 
1447
+ check_meta_facts() {
1448
+ # Deterministic gate for the #65 owner-normalized store projection. Drives the
1449
+ # REAL scripts/meta-facts.ts as a subprocess against sandboxed fixture stores:
1450
+ # full verbatim v3 records in the join, parse-before-uniqueness (a schema-invalid
1451
+ # rival never quarantines a healthy citizen), no-winner duplicates, drift /
1452
+ # symlink / invalid-UTF-8 defects in-band with exit 0, byte-deterministic JSON,
1453
+ # missing store = readable empty store, unreadable store = exit 3 with NO JSON,
1454
+ # usage = exit 2, plus dispatch + compiled-twin emit reachability.
1455
+ run_ts scripts/check-meta-facts.ts
1456
+ }
1457
+
1302
1458
  check_entwurf_fact_provider() {
1303
1459
  # Deterministic gate for 0.11 Stage 0 step 4 (fact-provider slice 4b): the
1304
1460
  # ASSEMBLY layer listEntwurfFacts. listAllMetaIdentities → scanSocketProbes →
@@ -1416,7 +1572,7 @@ check_package_source_routing() {
1416
1572
  # substrate entwurf used to stand on. C2 removed every entwurf use of that
1417
1573
  # substrate (the record mints the address; pi owns id and name), so the smoke's
1418
1574
  # subject no longer exists. The resident identity axis is covered by
1419
- # smoke-pi-attach (deterministic, in pnpm check) + smoke-resident-garden-guard (LIVE).
1575
+ # smoke-pi-attach (deterministic, in pnpm run check:full) + smoke-resident-garden-guard (LIVE).
1420
1576
 
1421
1577
 
1422
1578
 
@@ -1425,7 +1581,7 @@ check_dep_versions() {
1425
1581
  # docs. Concretely the kind of skew that produced commit 21de0f9's "0.11.1
1426
1582
  # leftover" review comment: package.json bumped to 0.12.0 while README
1427
1583
  # and run.sh's setup gate still claimed 0.11.1. Static check, no
1428
- # subprocess — fast enough to run inside `pnpm check` and pre-commit.
1584
+ # subprocess — fast enough to run inside `pnpm check`.
1429
1585
  # The doc half of that promise was prose only until 0.12.8 — see the
1430
1586
  # BASELINE DOCS block below, which finally makes this comment true.
1431
1587
  (cd "$REPO_DIR" && node --input-type=module <<'EOF'
@@ -1472,12 +1628,13 @@ assert.equal(peerTui, piAi,
1472
1628
  // floor tracks the devDep pin so a consumer can't install against a pi lacking
1473
1629
  // the public trust exports the bridge imports at the pinned minor, AND an upper
1474
1630
  // bound at the next minor stops a fresh install from silently pulling a future
1475
- // pi (past the declared ceiling — 0.83+ at the current 0.82.1 pin) whose
1631
+ // pi (past the declared ceiling — 0.85+ at the current 0.84.1 pin) whose
1476
1632
  // internal export surface has drifted from the one we typecheck against.
1477
1633
  // pi moves its public surface every minor (the 0.79→0.80 getModels→provider-
1478
1634
  // factory churn is exactly this), so an open `>=` floor is exactly how the next
1479
- // installer re-acquires the drift. Expected
1480
- // shape: `>=<devDep> <0.<minor+1>` (e.g. `>=0.82.1 <0.83`).
1635
+ // installer re-acquires the drift. The floor is also the HARD MINIMUM a consumer
1636
+ // install resolves: at `>=0.84.1` an existing 0.83.x host is upgraded, not kept.
1637
+ // Expected shape: `>=<devDep> <0.<minor+1>` (e.g. `>=0.84.1 <0.85`).
1481
1638
  const [piMaj, piMin] = piAi.split('.').map(Number);
1482
1639
  assert.equal(piMaj, 0,
1483
1640
  `pi pin major must stay 0 for the next-minor ceiling rule (got ${piAi}); revisit check-dep-versions when pi reaches 1.x`);
@@ -1923,9 +2080,13 @@ check_pi_import_surface() {
1923
2080
  # pattern catches static `from`, dynamic `import()`, `require()`,
1924
2081
  # `export … from`, side-effect `import "…"`, and whitespace variants alike.
1925
2082
  # Root import `@earendil-works/pi-coding-agent` (no trailing slash) is allowed.
1926
- # Scans EVERY tracked .ts/.js/.mjs/.cjs source (git ls-files), not a hardcoded
1927
- # file list a new root file (acp-bridge.ts, event-mapper.ts, engraving.ts,
1928
- # pi-context-augment.ts, protocol.js, …) can never silently escape the gate.
2083
+ # Scans the whole git WORK SURFACE (tracked + untracked-non-ignored), not a
2084
+ # hardcoded or index-only file list. The index-only corpus was the #62 measured
2085
+ # escape: a brand-new test file carrying a forbidden subpath was invisible to the
2086
+ # floor until it happened to be staged, so the gate went green on a candidate CI
2087
+ # then rejected. The denominator is re-proved every run against an EXTERNAL
2088
+ # throwaway git repo (never this worktree — the frozen candidate must not be
2089
+ # written to) holding one tracked-clean and one untracked-forbidden fixture.
1929
2090
  #
1930
2091
  # pi 0.80 EXCEPTION — exactly ONE allowlisted subpath:
1931
2092
  # @earendil-works/pi-ai/compat
@@ -1938,24 +2099,57 @@ check_pi_import_surface() {
1938
2099
  # the remainder, producing the unresolvable `…/dist/compat.js/providers/
1939
2100
  # anthropic` (verified live: extension load crash — invisible to static
1940
2101
  # typecheck, which resolves against node_modules `exports`). So `/compat` is the
1941
- # sanctioned extension entrypoint this repo uses for the old global model-catalog API
1942
- # (lib/acp/models.ts: `getModels`), and the ONLY allowlisted exception. The
2102
+ # sanctioned extension entrypoint, and the ONLY allowlisted exception. It has TWO
2103
+ # consumers: lib/acp/models.ts (`getModels`, the old global model-catalog API) and
2104
+ # test/fresh-call-provider.contract.test.ts (`stream`, the loopback provider-conversion
2105
+ # contract — pi-ai 0.84's package ROOT exports no stream/streamAnthropic at all, and
2106
+ # root `lazyStream(model, setup)` only defers the same private import into `setup`,
2107
+ # so /compat is the only surface that can observe the real anthropic request body). The
1943
2108
  # allow-pattern is closing-quote-anchored (`@earendil-works/pi-ai/compat["'\`]`)
1944
2109
  # so it permits ONLY that exact specifier: `/compat-foo`, `/oauth`, every
1945
2110
  # `/providers/*`, and any deeper path stay FORBIDDEN (we use only `/compat`).
1946
2111
  # Do NOT widen this to a `providers/*` subpath — it typechecks but CANNOT
1947
2112
  # resolve under the extension loader.
1948
- local hits
1949
- hits=$(cd "$REPO_DIR" && git ls-files '*.ts' '*.js' '*.mjs' '*.cjs' \
1950
- | grep -vE '^(node_modules|dist)/' \
1951
- | xargs -r grep -HnE "[\"'\`]@earendil-works/pi-(ai|coding-agent|tui)/" 2>/dev/null \
1952
- | grep -vE "[\"'\`]@earendil-works/pi-ai/compat[\"'\`]" 2>/dev/null || true)
2113
+ # ONE corpus, TWO callers. The real scan and the denominator fixture must run the
2114
+ # SAME two lines, or the fixture would prove a command the gate does not use.
2115
+ pi_import_work_surface() {
2116
+ git ls-files -z --cached --others --exclude-standard -- '*.ts' '*.js' '*.mjs' '*.cjs'
2117
+ }
2118
+ pi_import_scan() {
2119
+ pi_import_work_surface \
2120
+ | xargs -0r grep -HnE "[\"'\`]@earendil-works/pi-(ai|coding-agent|tui)/" 2>/dev/null \
2121
+ | grep -vE "[\"'\`]@earendil-works/pi-ai/compat[\"'\`]" 2>/dev/null || true
2122
+ }
2123
+
2124
+ local probe_dir probe_hits hits
2125
+ probe_dir=$(mktemp -d "${TMPDIR:-/tmp}/entwurf-pi-import-denominator.XXXXXX")
2126
+ (
2127
+ cd "$probe_dir" || exit 1
2128
+ git -c init.defaultBranch=main init -q .
2129
+ printf '%s\n' 'import { getModels } from "@earendil-works/pi-ai/compat";' > tracked-allowed.ts
2130
+ git add tracked-allowed.ts
2131
+ printf '%s\n' 'import "@earendil-works/pi-ai/private-probe";' > untracked-forbidden.ts
2132
+ ) || { fail "[check-pi-import-surface] could not build the denominator fixture in $probe_dir"; rm -rf "$probe_dir"; return 1; }
2133
+ probe_hits=$(cd "$probe_dir" && pi_import_scan)
2134
+ rm -rf "$probe_dir"
2135
+ # ONE assertion, ONE claim token: the manifest contract requires the signature to
2136
+ # occur exactly once in this file, so both fixture conditions are folded into a
2137
+ # single failure whose message still names which half broke.
2138
+ local saw_untracked_forbidden="no" saw_tracked_allowed="no"
2139
+ grep -qF 'untracked-forbidden.ts' <<<"$probe_hits" && saw_untracked_forbidden="yes"
2140
+ grep -qF 'tracked-allowed.ts' <<<"$probe_hits" && saw_tracked_allowed="yes"
2141
+ if [ "$saw_untracked_forbidden" != "yes" ] || [ "$saw_tracked_allowed" != "no" ]; then
2142
+ fail "[QK:PIIMPORT-WORK-SURFACE] the denominator fixture disagrees with the corpus — untracked-forbidden reached=$saw_untracked_forbidden (want yes: a brand-new forbidden import must not escape until it is staged), tracked-allowed flagged=$saw_tracked_allowed (want no: the /compat exception must survive)"
2143
+ return 1
2144
+ fi
2145
+
2146
+ hits=$(cd "$REPO_DIR" && pi_import_scan)
1953
2147
  if [ -n "$hits" ]; then
1954
2148
  echo "[check-pi-import-surface] FAIL: pi private subpath reference(s) — import @earendil-works/pi-* by the package ROOT only:"
1955
2149
  echo "$hits"
1956
2150
  exit 1
1957
2151
  fi
1958
- ok "[check-pi-import-surface] pi references are root-only (no private subpath; all tracked ts/js scanned)"
2152
+ ok "[check-pi-import-surface] pi references are root/compat-only across tracked + untracked-non-ignored ts/js; the untracked denominator is proved on an external fixture repo"
1959
2153
  }
1960
2154
 
1961
2155
  check_env_namespace() {
@@ -2261,22 +2455,21 @@ check_acp_provider_surface() {
2261
2455
  }
2262
2456
 
2263
2457
  check_acp_sdk_surface() {
2264
- # Deterministic gate for the S2a ACP SDK dependency surface. Pins the three
2265
- # ACP runtime deps to the current oracle versions (@agentclientprotocol/sdk
2266
- # 1.3.0 + claude-agent-acp 0.62.0 + @anthropic-ai/sdk 0.100.1), locks the
2267
- # peer-resolution that keeps claude-agent-sdk satisfiable (0.100.1, not the
2268
- # stale 0.91.1), asserts the wire SDK still value-exports the symbols the raw
2269
- # turn needs (silent-rename gate), and forbids any source-level anthropic SDK
2270
- # import / API-client use (the anthropic dep is a peer-pin ONLY).
2458
+ # Transition shim (issue #62 Phase 3): the gate NAME is preserved while the S2a
2459
+ # ACP SDK dependency-surface contracts live in the vitest lane. Same layers as
2460
+ # the retired scripts gate exact dep pins, pnpm-lock peer-resolution lock,
2461
+ # runtime resolver probes (anthropic peer / shared wire SDK / MCP peer edge),
2462
+ # wire-SDK value-export surface, and the forbidden anthropic-import sweep. The
2463
+ # per-layer mapping table is in the test file's header.
2271
2464
  section "ACP SDK surface (S2a dep pin + peer-resolution + no-client-use)"
2272
- run_ts scripts/check-acp-sdk-surface.ts
2465
+ run_vitest test/acp-sdk-surface.contract.test.ts
2273
2466
  }
2274
2467
 
2275
2468
  check_acp_overlay() {
2276
2469
  # Deterministic gate for the S2b Claude config overlay materializer. Drives
2277
2470
  # ensureClaudeConfigOverlay against injected temp realDir/overlayDir (no
2278
2471
  # operator ~/.claude touched) and asserts: settings.json hooks:{} +
2279
- # defaultMode default + autoMemory off; whitelisted entries symlinked;
2472
+ # defaultMode bypassPermissions (unattended turns never prompt) + autoMemory off; whitelisted entries symlinked;
2280
2473
  # projects/sessions overlay-private real dirs (NOT symlinks); operator
2281
2474
  # personal config (CLAUDE.md/settings.local.json/plugins/agents) never leaks;
2282
2475
  # stale symlinks cleaned; binary-owned files preserved; CLAUDE_CONFIG_DIR
@@ -2308,6 +2501,53 @@ check_acp_event_mapper() {
2308
2501
  run_ts scripts/check-acp-event-mapper.ts
2309
2502
  }
2310
2503
 
2504
+ check_acp_stop_reason() {
2505
+ # Deterministic gate for the ACP stop-reason contract. Drives every member of
2506
+ # the closed ACP terminal set (end_turn / max_tokens / max_turn_requests /
2507
+ # refusal / cancelled), plus an unrecognized reason and an ABSENT one, end to
2508
+ # end through streamAcpTurn against a fake connection — asserting the event
2509
+ # kind, the event reason, and the final message's stopReason / rawStopReason /
2510
+ # errorMessage together. Forbids the collapse-to-"stop" default that reported
2511
+ # refusals and exhausted turn budgets as finished answers. Also pins the
2512
+ # "pending" seed on the freshly created stream state.
2513
+ section "ACP stop reason (terminal set → pi verdict)"
2514
+ run_ts scripts/check-acp-stop-reason.ts
2515
+ }
2516
+
2517
+ check_acp_prompt_lifecycle() {
2518
+ # Deterministic gate for the ACP prompt LIFECYCLE contract. A prompt has no
2519
+ # wall-clock cutoff: it ends when it resolves, when the operator aborts, or
2520
+ # when the child dies / its stdio ends. Drives streamAcpTurn against a fake
2521
+ # ACP child + connection for eight cells — a quiet in-flight turn is not
2522
+ # sealed and later completes on its ORIGINAL prompt; an abort sends ACP
2523
+ # session/cancel first (cancelled→aborted, no signal to a cooperating child);
2524
+ # a wedged agent is torn down after the bounded grace and still returns; a
2525
+ # mid-prompt child death reports exit status + stderr tail on BOTH the new and
2526
+ # the reuse path; a death BETWEEN turns is announced once by the next turn
2527
+ # while a teardown WE performed stays silent — plus pi's own
2528
+ # isRetryableAssistantError as
2529
+ # the oracle that our prompt-phase failure text is not a transient
2530
+ # (cold-replay) error.
2531
+ section "ACP prompt lifecycle (no wall clock; abort/child-death endings)"
2532
+ run_ts scripts/check-acp-prompt-lifecycle.ts
2533
+ }
2534
+
2535
+ check_acp_stream_hooks() {
2536
+ # Deterministic gate for the pi 0.84 streamSimple hook contract on the ACP rail
2537
+ # (#63; upstream pi-mono #7372 → doc-only #7576). before_provider_request
2538
+ # (onPayload) receives the EXACT ACP session/prompt wire params on new AND
2539
+ # reuse turns and its replacement becomes the wire — fail-closed: a non-null/
2540
+ # non-array-object violation, a changed bootstrapped sessionId, or an emptied
2541
+ # prompt array refuses the turn loudly with zero sends, a hook rejection fails
2542
+ # loud, and an abort raised while the hook is awaited wins before the wire
2543
+ # write on BOTH the new and the reuse path. after_provider_response (onResponse)
2544
+ # is an explicit local non-HTTP exemption: ACP has no truthful {status,
2545
+ # headers} and its terminal result arrives after the body was consumed, so the
2546
+ # gate pins ZERO invocations across success/error/abort turns.
2547
+ section "ACP streamSimple hooks (truthful onPayload; onResponse exemption)"
2548
+ run_ts scripts/check-acp-stream-hooks.ts
2549
+ }
2550
+
2311
2551
  check_acp_prompt_builder() {
2312
2552
  # Deterministic gate for the S2d bootstrapPath-scoped ACP prompt builder (핀4).
2313
2553
  # Proves prompt SCOPE follows bootstrapPath: new=full transcript (history
@@ -2383,11 +2623,11 @@ check_acp_carrier_augment() {
2383
2623
  check_acp_cortex() {
2384
2624
  # Deterministic gate for the Cortex (Snowflake Cortex Code) ACP backend — the
2385
2625
  # first non-claude adapter on the rail, landed 0.13.0 on the CP0-measured
2386
- # contract (docs/acp-backend-rail.md §4/§6/§11-8). The cortex source is one
2626
+ # contract (docs/acp-backend-rail.md, Cortex audit D1–D10). The cortex source is one
2387
2627
  # `cortexAdapter` object (backend-adapter.ts) + its curated surface (models.ts)
2388
2628
  # + the dual-HOME overlay (overlay.ts); the 결합 규칙 requires the gate to land
2389
- # WITH it, and §6 said EXTEND the check-acp-* family, so cortex's whole
2390
- # deterministic axis lives here. Locks (each §11-8 D-pinned): the GLG-decided
2629
+ # WITH it, extending the check-acp-* family, so cortex's whole
2630
+ # deterministic axis lives here. Locks (each Cortex-audit D-pinned): the GLG-decided
2391
2631
  # 4-row curation rides real registry bases and the `cortex-` prefix routes to
2392
2632
  # cortexAdapter; launch is `cortex acp serve` with NO -m — the model is
2393
2633
  # enforced per turn via session/set_config_option with the native id (E);
@@ -2505,6 +2745,9 @@ check_pack() {
2505
2745
  # previous-generation store; without this twin the prescription every
2506
2746
  # v3-only rejection names would be dead exactly where it matters.
2507
2747
  "mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js"
2748
+ # #65 — the owner-normalized store projection consumers call INSTEAD of
2749
+ # parsing the store; installed hosts are exactly where those consumers live.
2750
+ "mcp/entwurf-bridge/dist/scripts/meta-facts.js"
2508
2751
  # 0.12.5 — the node_modules-safe plugin hook + its lib. install-meta-bridge copies
2509
2752
  # these compiled JS into the assembled plugin when installed (raw .ts can't
2510
2753
  # strip-types under node_modules). meta-session.js is shared with the store-doctor
@@ -2551,7 +2794,7 @@ check_pack() {
2551
2794
  '\.agent-(reports|shell)/'
2552
2795
  'pi/meta-bridge/\.assembled/'
2553
2796
  # Python bytecode residue — `scripts/` ships whole via the files allowlist,
2554
- # which BYPASSES .gitignore/.npmignore for its contents, so a `pnpm check`
2797
+ # which BYPASSES .gitignore/.npmignore for its contents, so a `pnpm run check:full`
2555
2798
  # run's generated scripts/__pycache__/*.pyc rode into the 0.12.6 tarball. The
2556
2799
  # files-array `!**/__pycache__` / `!**/*.pyc` negations exclude it; this is the
2557
2800
  # tripwire that fails loud if that negation is ever dropped (결합 규칙).
@@ -2639,7 +2882,7 @@ _check_pack_install_impl() {
2639
2882
 
2640
2883
  echo "[check-pack-install] npm pack -> ${tgz_name}"
2641
2884
  # with-dist-lock: same whole-pack serialization as check-pack — this heavy gate
2642
- # and a background check-pack (via `pnpm check`) both pack, and unserialized they
2885
+ # and a background check-pack (via `pnpm run check:full`) both pack, and unserialized they
2643
2886
  # race the shared dist dir. The stale-dist sentinel planted just above still
2644
2887
  # proves build-bridge's own `rm -rf dist` clean step under the lock.
2645
2888
  (cd "$REPO_DIR" && bash scripts/with-dist-lock.sh npm pack --dry-run=false --pack-destination "$pack_tmp" 2>&1 | tail -1) || {
@@ -2671,8 +2914,8 @@ _check_pack_install_impl() {
2671
2914
  # Required tarball contents. The old 0.11.0 ACP root files (index.ts,
2672
2915
  # acp-bridge.ts, event-mapper.ts, engraving.ts, pi-context-augment.ts,
2673
2916
  # pi-extensions/entwurf.ts) were removed on v2-only and are GONE — keeping them
2674
- # here made this heavy gate silently RED (publish-blocking) while `pnpm check`
2675
- # (which runs only check-pack, not check-pack-install) stayed green. The ACP
2917
+ # here made this heavy gate silently RED (publish-blocking) while the deterministic floor
2918
+ # (check:full runs only check-pack, not check-pack-install) stayed green. The ACP
2676
2919
  # plugin re-enters on v2 as the provider entry + lib/acp/* modules below.
2677
2920
  local tar_required=(
2678
2921
  "package.json" "README.md" "LICENSE" "CHANGELOG.md"
@@ -2700,6 +2943,8 @@ _check_pack_install_impl() {
2700
2943
  # The generation verb (see check-pack). The installed-command regression below
2701
2944
  # opens a fresh generation on a 0-record sandbox through the real bin.
2702
2945
  "mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js"
2946
+ # #65 — the owner-normalized store projection (see check-pack).
2947
+ "mcp/entwurf-bridge/dist/scripts/meta-facts.js"
2703
2948
  # 0.12.5 — node_modules-safe plugin hook + lib (see check-pack). The installed
2704
2949
  # hook regression below runs exactly this compiled JS from under node_modules.
2705
2950
  "mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js"
@@ -2776,21 +3021,31 @@ _check_pack_install_impl() {
2776
3021
  printf '%s\n' '{ "name": "entwurf-install-smoke", "version": "0.0.0", "private": true }' > "$tmp/package.json"
2777
3022
 
2778
3023
  # pi-agent-core is pinned even though we never import it: pi-coding-agent depends
2779
- # on it by CARET (`^0.82.1`), so with no lockfile in this fresh temp project it
3024
+ # on it by CARET (`^0.84.1`), so with no lockfile in this fresh temp project it
2780
3025
  # floats to whatever pi published last — and that newer core then drags a NESTED
2781
3026
  # pi-ai of its own. Measured 2026-07-21: pinning only the three we import left
2782
3027
  # pi-agent-core@0.80.10 + pi-ai@0.80.10 in the tree while the gate still announced
2783
3028
  # "pinned pi 0.80.7". The gate would then be verifying an UNVERIFIED runtime — the
2784
3029
  # exact class this cut exists to close. Pin every @earendil-works package that
2785
3030
  # constitutes the pi runtime, not just the ones whose types we touch.
2786
- echo "[check-pack-install] pnpm add into $tmp (with 0.82.x peers + typebox)"
3031
+ #
3032
+ # The constellation is re-measured at each pi bump, never carried forward. At
3033
+ # 0.84.0 it GREW: pi-coding-agent's @earendil-works caret set went from
3034
+ # {pi-agent-core, pi-ai, pi-tui} at 0.83.0 to {pi-agent-core, pi-ai, pi-client,
3035
+ # pi-protocol, pi-tui}, so pi-client and pi-protocol are pinned here for the
3036
+ # first time. Unpinned they would float exactly like pi-agent-core did in the
3037
+ # 2026-07-21 incident above. (pi-telemetry arrives transitively, not as a
3038
+ # direct caret; the leak assertion below covers it and every other pi package.)
3039
+ echo "[check-pack-install] pnpm add into $tmp (with 0.84.x peers + typebox)"
2787
3040
  local install_log
2788
3041
  install_log=$(cd "$tmp" && pnpm add \
2789
3042
  "$tgz_path" \
2790
- "@earendil-works/pi-ai@0.82.1" \
2791
- "@earendil-works/pi-coding-agent@0.82.1" \
2792
- "@earendil-works/pi-tui@0.82.1" \
2793
- "@earendil-works/pi-agent-core@0.82.1" \
3043
+ "@earendil-works/pi-ai@0.84.1" \
3044
+ "@earendil-works/pi-coding-agent@0.84.1" \
3045
+ "@earendil-works/pi-tui@0.84.1" \
3046
+ "@earendil-works/pi-agent-core@0.84.1" \
3047
+ "@earendil-works/pi-client@0.84.1" \
3048
+ "@earendil-works/pi-protocol@0.84.1" \
2794
3049
  "typebox@latest" \
2795
3050
  --ignore-workspace --ignore-scripts 2>&1) || {
2796
3051
  fail "[check-pack-install] pnpm add failed:"
@@ -2800,17 +3055,17 @@ _check_pack_install_impl() {
2800
3055
 
2801
3056
  # A pin is a wish until the resolved tree is read back. Assert it: EVERY
2802
3057
  # @earendil-works pi package present — direct or transitive, top level or nested —
2803
- # must be the pinned 0.82.1. Anything else means an unpinned caret floated and the
3058
+ # must be the pinned 0.84.1. Anything else means an unpinned caret floated and the
2804
3059
  # rest of this gate would be exercising a runtime nobody verified, while still
2805
- # printing "pinned pi 0.82.1". Fail loud instead of proving the wrong floor.
3060
+ # printing "pinned pi 0.84.1". Fail loud instead of proving the wrong floor.
2806
3061
  local leaked_pi
2807
- leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | grep '^@earendil-works+pi-' | grep -v '@0\.82\.1' || true)
3062
+ leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | grep '^@earendil-works+pi-' | grep -v '@0\.84\.1' || true)
2808
3063
  if [ -n "$leaked_pi" ]; then
2809
- fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.82.1):"
3064
+ fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.84.1):"
2810
3065
  printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
2811
3066
  return 1
2812
3067
  fi
2813
- echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works pi package is 0.82.1"
3068
+ echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works pi package is 0.84.1"
2814
3069
 
2815
3070
  # Resolve the installed package.json and confirm pi.extensions
2816
3071
  # arrived intact. If pi.extensions is empty or missing, the
@@ -3200,8 +3455,16 @@ function finish(trimmed) {
3200
3455
  try { msg = JSON.parse(trimmed); }
3201
3456
  catch { console.error('unparseable tools/list:', trimmed.slice(0, 300)); if (stderr.trim()) console.error(stderr.trim()); process.exit(1); }
3202
3457
  const names = (msg?.result?.tools ?? []).map((t) => t?.name).sort();
3203
- for (const need of ['entwurf_v2', 'entwurf_peers', 'entwurf_self', 'entwurf_inbox_read', 'entwurf_register_native']) {
3204
- if (!names.includes(need)) { console.error('missing MCP tool from installed boot:', need, '— got', names.join(',')); process.exit(1); }
3458
+ // EXACT set, not a floor. The subset form of this assertion named only the five
3459
+ // pre-0.14 verbs, so an ARTIFACT whose bridge boots and answers but carries an older
3460
+ // emitted surface — no entwurf_fresh_call, no entwurf_resume_call — passed the very
3461
+ // gate that exists to catch "green clone, dead consumer". Equality also catches the
3462
+ // other direction the narrow-surface rule cares about: an extra or duplicated verb
3463
+ // reaching hosts without a decision. Written sorted; `names` is sorted above.
3464
+ const EXPECT_TOOLS = ['entwurf_fresh_call', 'entwurf_inbox_read', 'entwurf_peers', 'entwurf_register_native', 'entwurf_resume_call', 'entwurf_self', 'entwurf_v2'];
3465
+ if (names.length !== EXPECT_TOOLS.length || EXPECT_TOOLS.some((n, i) => names[i] !== n)) {
3466
+ console.error('installed MCP tool set MISMATCH — want exactly [' + EXPECT_TOOLS.join(',') + '] got [' + names.join(',') + ']');
3467
+ process.exit(1);
3205
3468
  }
3206
3469
  console.log(names.join(','));
3207
3470
  child.kill('SIGTERM');
@@ -3483,6 +3746,21 @@ JSON
3483
3746
  echo "$devgate_out" | tail -6 | sed 's/^/ /' >&2
3484
3747
  return 1
3485
3748
  fi
3749
+ # The migrated fresh-call gate takes a third path: run_vitest. Drive that exact
3750
+ # installed branch and require both the nonzero refusal and its intended cause;
3751
+ # an incidental missing file or raw strip-types crash is not consumer evidence.
3752
+ local vitestgate_out vitestgate_rc=0
3753
+ vitestgate_out=$(HOME="$npmhome" XDG_DATA_HOME="$op_xdg_data" XDG_STATE_HOME="$op_xdg_state" XDG_CACHE_HOME="$op_xdg_cache" PI_CODING_AGENT_DIR="$op_agent" "$installed_entwurf" check-mux-fresh-call 2>&1) || vitestgate_rc=$?
3754
+ if [ "$vitestgate_rc" -eq 0 ]; then
3755
+ fail "[check-pack-install] the Vitest-backed check-mux-fresh-call exited 0 from an installed package — the devDependency runner is absent"
3756
+ return 1
3757
+ fi
3758
+ if ! printf '%s' "$vitestgate_out" | grep -q 'dev-clone-only surface' || ! printf '%s' "$vitestgate_out" | grep -q 'vitest is a devDependency'; then
3759
+ fail "[check-pack-install] installed check-mux-fresh-call did not refuse for run_vitest's intended devDependency/dev-clone-only reason:"
3760
+ echo "$vitestgate_out" | tail -6 | sed 's/^/ /' >&2
3761
+ return 1
3762
+ fi
3763
+
3486
3764
  # Same rule, different mechanism. check-fresh-cut-gate is a SHELL gate, so run_ts
3487
3765
  # never sees it — `scripts/` ships whole and the dispatch would happily run it
3488
3766
  # from under node_modules, where the dev sandbox it builds has no business
@@ -3499,7 +3777,7 @@ JSON
3499
3777
  echo "$shgate_out" | tail -6 | sed 's/^/ /' >&2
3500
3778
  return 1
3501
3779
  fi
3502
- echo "[check-pack-install] dev-only gate refusal pass (installed package refuses check-* legibly via run_ts, and the shell-side check-fresh-cut-gate refuses on its own guard; no raw-.ts fallback, no silent exit 0)"
3780
+ echo "[check-pack-install] dev-only gate refusal pass (installed package refuses run_ts and run_vitest checks for their exact causes; shell-side check-fresh-cut-gate refuses on its own guard; no silent exit 0)"
3503
3781
 
3504
3782
  # 0.12.4 — installed STORE-DOCTOR regression. meta-bridge-doctor.sh's full store
3505
3783
  # scan runs the prebuilt dist JS when it lives under node_modules (strip-types
@@ -3599,7 +3877,7 @@ check_pack_install() {
3599
3877
  # NOT run the legacy ACP/v1 Axis 1 interview gates (session-messaging/sentinel):
3600
3878
  # those call removed surfaces and survive only as fail-loud reference subcommands
3601
3879
  # until rewritten onto entwurf_v2. The release-gate owns the heavier live v2
3602
- # substrate proof (matrix-live + spawn-resume-live).
3880
+ # substrate proof (matrix-live).
3603
3881
 
3604
3882
  validate_entwurf_bridge() {
3605
3883
  local bridge_dir="$REPO_DIR/mcp/entwurf-bridge"
@@ -3610,7 +3888,11 @@ validate_entwurf_bridge() {
3610
3888
  return 1
3611
3889
  fi
3612
3890
 
3613
- log "entwurf-bridge: direct MCP smoke (strip-types launcher, no build step)"
3891
+ # The launcher picks its own mode by LOCATION (dev clone → strip-types source; under
3892
+ # node_modules → prebuilt dist), and this subcommand ships, so an operator running
3893
+ # `entwurf check-bridge` from an npm install reads this line too. Naming only the dev
3894
+ # branch here told that operator the installed bytes were never the subject.
3895
+ log "entwurf-bridge: direct MCP smoke (start.sh as it ships — source under a clone, prebuilt dist under node_modules)"
3614
3896
 
3615
3897
  if ! raw=$(cd "$bridge_dir" && node --input-type=module <<'JS'
3616
3898
  import { spawn } from 'node:child_process';
@@ -3632,12 +3914,16 @@ function finishOk(trimmed) {
3632
3914
  process.exit(1);
3633
3915
  }
3634
3916
  const names = tools.map((t) => t?.name).sort();
3635
- const expected = ['entwurf_peers', 'entwurf_self', 'entwurf_inbox_read', 'entwurf_register_native', 'entwurf_v2'];
3636
- for (const name of expected) {
3637
- if (!names.includes(name)) {
3638
- console.error(`missing MCP tool: ${name}`);
3639
- process.exit(1);
3640
- }
3917
+ // EXACT set, not a floor. This is the assertion behind the operator-facing claim that
3918
+ // `entwurf check-bridge` proves the installed bytes list the seven garden tools — and
3919
+ // under an installed package this launcher IS the dist branch of start.sh, so a subset
3920
+ // check here let an artifact missing entwurf_fresh_call / entwurf_resume_call read as a
3921
+ // green bridge. Equality also refuses an undecided extra verb. Written sorted; `names`
3922
+ // is sorted above.
3923
+ const expected = ['entwurf_fresh_call', 'entwurf_inbox_read', 'entwurf_peers', 'entwurf_register_native', 'entwurf_resume_call', 'entwurf_self', 'entwurf_v2'];
3924
+ if (names.length !== expected.length || expected.some((n, i) => names[i] !== n)) {
3925
+ console.error(`MCP tool set MISMATCH — want exactly [${expected.join(',')}] got [${names.join(',')}]`);
3926
+ process.exit(1);
3641
3927
  }
3642
3928
  console.log(names.join(','));
3643
3929
  child.kill('SIGTERM');
@@ -3757,7 +4043,7 @@ setup_all() {
3757
4043
  # native-harness meta-bridge, so a relocate/clone needs ONE command — not the
3758
4044
  # install + install-meta-bridge two-pronged split that froze the Claude
3759
4045
  # statusLine on the old path (the 2026-06-23 relocation gap). Detection-gated:
3760
- # a pi-only host with no native harness skips it cleanly (§10 "있으면 설정,
4046
+ # a pi-only host with no native harness skips it cleanly (rail “Shipped adapters” — "있으면 설정,
3761
4047
  # 없으면 담아준다"); meta-bridge-install.sh is itself idempotent.
3762
4048
  if command -v claude >/dev/null 2>&1; then
3763
4049
  section "meta-bridge install (native harness detected: Claude Code)"
@@ -3788,8 +4074,8 @@ setup_all() {
3788
4074
  # survives while the meta-record is created/attached by conversationId.
3789
4075
  wire_agy_hooks
3790
4076
 
3791
- # Deterministic preflight lives in `pnpm check`; live substrate acceptance lives
3792
- # in `LIVE=1 ./run.sh release-gate <scratch>`. Setup is the install path, so it
4077
+ # Deterministic preflight lives in `pnpm run check:full`; live substrate acceptance lives
4078
+ # in `LIVE=1 ./run.sh release-gate <scratch> --cut`. Setup is the install path, so it
3793
4079
  # verifies the installed MCP bridge boundary only and does NOT run the legacy
3794
4080
  # ACP/v1 session-messaging/sentinel gates.
3795
4081
  section "v2 install smoke: entwurf-bridge (direct MCP protocol)"
@@ -3805,7 +4091,7 @@ setup_all() {
3805
4091
  echo " ./run.sh doctor-agy-statusline"
3806
4092
  echo " ./run.sh doctor-agy-hooks"
3807
4093
  fi
3808
- echo "Run 'LIVE=1 ./run.sh release-gate <scratch>' for live substrate acceptance."
4094
+ echo "Run 'LIVE=1 ./run.sh release-gate <scratch> --cut' for live substrate acceptance (--cut refuses any MUST SKIP)."
3809
4095
  }
3810
4096
 
3811
4097
  # wire_agy_bridge — detection-gated, NON-FATAL agy (Antigravity) MCP bridge wiring, folded into
@@ -3967,13 +4253,13 @@ expose_dev_bin() {
3967
4253
  }
3968
4254
 
3969
4255
  # release-gate — the single command that, when GREEN, is sufficient to cut
3970
- # release cuts. Runs the full static floor (`pnpm check`) followed by the
4256
+ # release cuts. Runs the full static floor (`pnpm run check:full`) followed by the
3971
4257
  # v2-native live gates, then emits one PASS/FAIL/SKIP summary. Everything is
3972
4258
  # invoked through run.sh subcommands — never a script in scripts/ directly.
3973
4259
  #
3974
4260
  # Design invariants (NEXT Step 1e + GPT-5.5 reviews):
3975
4261
  # - v2-native live floor: the MUST tier is the v2 dispatch substrate
3976
- # (smoke-entwurf-v2-matrix-live + smoke-entwurf-v2-spawn-resume-live, opt-in
4262
+ # (smoke-entwurf-v2-matrix-live, opt-in
3977
4263
  # LIVE), the MCP bridge (check-bridge), and the resident citizen guard
3978
4264
  # (smoke-resident-garden-guard).
3979
4265
  # - ACP plugin acceptance floor (S0~S2g): the 11 ACP LIVE smokes
@@ -3989,10 +4275,18 @@ expose_dev_bin() {
3989
4275
  # - Final release authorization is GLG's, not this script's: a green
3990
4276
  # run is necessary, and the operator closes the decision.
3991
4277
  release_gate() {
4278
+ entwurf_require_step_outcome
3992
4279
  local -a positional=()
3993
4280
  local a
4281
+ # --cut is the EXECUTABLE half of "a CUT needs LIVE=1, SKIP=0" (P1). Without it
4282
+ # this stays the unattended diagnostic it has always been: SKIPs are reported
4283
+ # and the run still exits 0. With it, any MUST SKIP is red — including the
4284
+ # LIVE!=1 case, which needs no separate assertion because every LIVE-gated step
4285
+ # then skips on its own and the skip counter blocks.
4286
+ local cut_mode=0
3994
4287
  for a in "$@"; do
3995
4288
  case "$a" in
4289
+ --cut) cut_mode=1 ;;
3996
4290
  --allow-skip-gemini) ;; # accepted-but-ignored: gemini removed from the claude-only floor (back-compat for existing scripts)
3997
4291
  *) positional+=("$a") ;;
3998
4292
  esac
@@ -4043,27 +4337,36 @@ release_gate() {
4043
4337
  }
4044
4338
 
4045
4339
  # LIVE-gated MUST step: a release-blocking gate that needs a real backend turn
4046
- # (auth/model/credit). When LIVE!=1 it is an HONEST SKIP counted as SKIP, NOT
4047
- # PASSso an unattended `./run.sh release-gate` stays runnable without faking
4048
- # coverage. A release CUT therefore requires `LIVE=1 ./run.sh release-gate
4049
- # <scratch>` to land MUST PASS with SKIP=0; a green run that still shows SKIP is
4050
- # CI safety, never live acceptance. (Same rule the v2 substrate sentinels below
4051
- # spell out inline; this helper applies it to the ACP plugin acceptance floor.)
4340
+ # (auth/model/credit). It is ALWAYS INVOKED and the step itself reports whether
4341
+ # it could run that is the whole point of P1. The old shape asked `LIVE=1?`
4342
+ # here and skipped without calling, which meant the summary asserted a skip it
4343
+ # had never confirmed, while a step that WAS called could still exit 0 on a
4344
+ # missing prerequisite (Cortex without a connection) and be counted PASS.
4345
+ # Now one protocol decides: exit 0 = PASS, $ENTWURF_STEP_SKIP_EXIT = SKIP,
4346
+ # anything else = FAIL. A SKIP is never rounded up, and `--cut` refuses any.
4347
+ #
4348
+ # `LIVE` is NOT forced here — it rides the operator's environment into the
4349
+ # child, so `LIVE=1 ./run.sh release-gate` runs the step for real and a bare
4350
+ # invocation gets the step's own honest skip.
4052
4351
  run_live_step() {
4053
4352
  local name="$1"; shift
4054
4353
  section "release-gate step: $name"
4055
- if [ "${LIVE:-}" = "1" ]; then
4056
- if "$@"; then
4354
+ local rc=0
4355
+ "$@" || rc=$?
4356
+ case "$(entwurf_step_outcome "$rc")" in
4357
+ PASS)
4057
4358
  ok "$name: PASS"
4058
4359
  results+=("PASS $name"); pass=$((pass + 1))
4059
- else
4060
- fail "$name: FAIL"
4360
+ ;;
4361
+ SKIP)
4362
+ warn "$name: SKIP — the step declined a prerequisite it does not have (see its [entwurf:skip] line above). NOT a live acceptance."
4363
+ results+=("SKIP $name"); skip=$((skip + 1))
4364
+ ;;
4365
+ *)
4366
+ fail "$name: FAIL (exit $rc)"
4061
4367
  results+=("FAIL $name"); failc=$((failc + 1))
4062
- fi
4063
- else
4064
- warn "$name: LIVE!=1 — skipped (opt-in: needs auth/model — NOT a live acceptance run)"
4065
- results+=("SKIP $name (LIVE!=1)"); skip=$((skip + 1))
4066
- fi
4368
+ ;;
4369
+ esac
4067
4370
  }
4068
4371
 
4069
4372
  # BEHAVIOR lane (0.11.0, GLG+GPT+Opus): a SEPARATE advisory counter for
@@ -4087,29 +4390,56 @@ release_gate() {
4087
4390
  # deterministic/programmatic must-pass gates above (check-entwurf-v2-*,
4088
4391
  # check-bridge) — this lane is autonomous-tool-selection *behavior*, not
4089
4392
  # *function*. Residual bypass → 0.11.x usability lane.
4090
- local behavior_pass=0 behavior_failc=0
4393
+ # The lane speaks the same STEP OUTCOME protocol (P1) so a missing prerequisite
4394
+ # here is a BEHAVIOR-SKIP, not a fabricated BEHAVIOR-PASS or a misread
4395
+ # BEHAVIOR-FAIL. Advisory semantics are untouched: none of these counters ever
4396
+ # reaches `failc`, and `--cut` reads the MUST skip counter only.
4397
+ local behavior_pass=0 behavior_failc=0 behavior_skip=0
4091
4398
  local -a behavior_results=()
4092
4399
 
4093
4400
  run_behavior_step() {
4094
4401
  local name="$1"; shift
4095
4402
  section "release-gate BEHAVIOR step (advisory, non-blocking): $name"
4096
- if "$@"; then
4097
- ok "$name: BEHAVIOR-PASS"
4098
- behavior_results+=("BEHAVIOR-PASS $name"); behavior_pass=$((behavior_pass + 1))
4099
- else
4100
- warn "$name: BEHAVIOR-FAIL (advisory — model-in-loop signal; S7=Bash-bypass stays hard-fail here; NOT a cut blocker)"
4101
- behavior_results+=("BEHAVIOR-FAIL $name"); behavior_failc=$((behavior_failc + 1))
4102
- fi
4403
+ local rc=0
4404
+ "$@" || rc=$?
4405
+ case "$(entwurf_step_outcome "$rc")" in
4406
+ PASS)
4407
+ ok "$name: BEHAVIOR-PASS"
4408
+ behavior_results+=("BEHAVIOR-PASS $name"); behavior_pass=$((behavior_pass + 1))
4409
+ ;;
4410
+ SKIP)
4411
+ warn "$name: BEHAVIOR-SKIP — prerequisite absent (advisory lane; not a signal either way)"
4412
+ behavior_results+=("BEHAVIOR-SKIP $name"); behavior_skip=$((behavior_skip + 1))
4413
+ ;;
4414
+ *)
4415
+ warn "$name: BEHAVIOR-FAIL (advisory — model-in-loop signal; S7=Bash-bypass stays hard-fail here; NOT a cut blocker)"
4416
+ behavior_results+=("BEHAVIOR-FAIL $name"); behavior_failc=$((behavior_failc + 1))
4417
+ ;;
4418
+ esac
4103
4419
  }
4104
4420
 
4105
- # 1. Static floor (deterministic; includes the two folded gates).
4106
- section "release-gate step: static (pnpm check)"
4107
- if (cd "$REPO_DIR" && pnpm check); then
4108
- ok "static (pnpm check): PASS"
4109
- results+=("PASS static (pnpm check)"); pass=$((pass + 1))
4421
+ # 1. Static floor (deterministic; the FULL tier — core plus hermetic/package —
4422
+ # never the ≤60s everyday core alone).
4423
+ section "release-gate step: static (pnpm run check:full)"
4424
+ if (cd "$REPO_DIR" && pnpm run check:full); then
4425
+ ok "static (pnpm run check:full): PASS"
4426
+ results+=("PASS static (pnpm run check:full)"); pass=$((pass + 1))
4110
4427
  else
4111
- fail "static (pnpm check): FAIL"
4112
- results+=("FAIL static (pnpm check)"); failc=$((failc + 1))
4428
+ fail "static (pnpm run check:full): FAIL"
4429
+ results+=("FAIL static (pnpm run check:full)"); failc=$((failc + 1))
4430
+ fi
4431
+
4432
+ # 1b. Discriminating power of that floor. check-gate-qualification left the
4433
+ # default check chains (operator inner-loop cost), so release acceptance
4434
+ # carries it explicitly as its own MUST step: a cut must re-prove the gates
4435
+ # still kill what they claim to kill.
4436
+ section "release-gate step: check-gate-qualification"
4437
+ if (cd "$REPO_DIR" && bash "$self" check-gate-qualification); then
4438
+ ok "check-gate-qualification: PASS"
4439
+ results+=("PASS check-gate-qualification"); pass=$((pass + 1))
4440
+ else
4441
+ fail "check-gate-qualification: FAIL"
4442
+ results+=("FAIL check-gate-qualification"); failc=$((failc + 1))
4113
4443
  fi
4114
4444
 
4115
4445
  # 2. (gemini-availability step removed — claude-only floor; gemini CLI is
@@ -4125,7 +4455,7 @@ release_gate() {
4125
4455
  # the identity foundation is broken, every Entwurf live gate below is
4126
4456
  # meaningless, so fail fast here. (The old smoke-session-id-name substrate
4127
4457
  # proof is gone with the substrate itself — #50 C3; smoke-pi-attach carries
4128
- # the deterministic half inside pnpm check.)
4458
+ # the deterministic half inside pnpm run check:full.)
4129
4459
  # RGG split: the 0-token half (BIRTH: record + record-keyed socket / ATTACH:
4130
4460
  # re-open keeps the address / REPLACEMENT: in-process /new is pi's again) is
4131
4461
  # release-blocking and stays here as a must-pass with SMOKE_RGG_POSITIVE=0. The
@@ -4137,41 +4467,16 @@ release_gate() {
4137
4467
  # production runEntwurfV2 deps + real pi control-socket RPC + real mailbox enqueue + v2 lock, not
4138
4468
  # per-backend model behavior). Placed right after check-bridge: the MCP/protocol substrate must be
4139
4469
  # green first so a matrix-live failure reads as "v2 transport/lock/enqueue", not bridge basics.
4140
- # Opt-in LIVE: it spawns a real `pi --entwurf-control` (needs auth/model), so LIVE!=1 is an HONEST
4141
- # SKIP (not a PASS) — an unattended release-gate stays runnable without faking coverage. Independent
4142
- # of --allow-skip-gemini (now a no-op back-compat flag on the claude-only floor; this gates substrate auth).
4143
- section "release-gate step: smoke-entwurf-v2-matrix-live"
4144
- if [ "${LIVE:-}" = "1" ]; then
4145
- if gate env LIVE=1 bash "$self" smoke-entwurf-v2-matrix-live; then
4146
- ok "smoke-entwurf-v2-matrix-live: PASS"
4147
- results+=("PASS smoke-entwurf-v2-matrix-live"); pass=$((pass + 1))
4148
- else
4149
- fail "smoke-entwurf-v2-matrix-live: FAIL"
4150
- results+=("FAIL smoke-entwurf-v2-matrix-live"); failc=$((failc + 1))
4151
- fi
4152
- else
4153
- warn "smoke-entwurf-v2-matrix-live: LIVE!=1 — skipped (v2 substrate sentinel, opt-in: needs auth/model)"
4154
- results+=("SKIP smoke-entwurf-v2-matrix-live (LIVE!=1)"); skip=$((skip + 1))
4155
- fi
4156
- # 0.11.0 (A) acceptance: the FULL spawn-bg resident lifecycle — a real `pi` resume child stands
4157
- # its control socket up, does a model turn, lock released ×1. Placed right after matrix-live (the
4158
- # transport sentinel): a spawn-resume failure then reads as "resume/resident lifecycle", not
4159
- # transport basics. Same opt-in LIVE rule — LIVE!=1 is an HONEST SKIP (model-in-loop, needs
4160
- # auth/model). NOTE: a 0.11.0 tag REQUIRES `LIVE=1 ./run.sh release-gate` (or this step direct)
4161
- # to PASS — the SKIP is CI safety, never acceptance.
4162
- section "release-gate step: smoke-entwurf-v2-spawn-resume-live"
4163
- if [ "${LIVE:-}" = "1" ]; then
4164
- if gate env LIVE=1 bash "$self" smoke-entwurf-v2-spawn-resume-live; then
4165
- ok "smoke-entwurf-v2-spawn-resume-live: PASS"
4166
- results+=("PASS smoke-entwurf-v2-spawn-resume-live"); pass=$((pass + 1))
4167
- else
4168
- fail "smoke-entwurf-v2-spawn-resume-live: FAIL"
4169
- results+=("FAIL smoke-entwurf-v2-spawn-resume-live"); failc=$((failc + 1))
4170
- fi
4171
- else
4172
- warn "smoke-entwurf-v2-spawn-resume-live: LIVE!=1 — skipped (0.11.0 A acceptance, opt-in: needs auth/model)"
4173
- results+=("SKIP smoke-entwurf-v2-spawn-resume-live (LIVE!=1)"); skip=$((skip + 1))
4174
- fi
4470
+ # Opt-in LIVE: it spawns a real `pi --entwurf-control` (needs auth/model), so a missing
4471
+ # prerequisite is an HONEST SKIP (not a PASS) — an unattended release-gate stays runnable without
4472
+ # faking coverage. Independent of --allow-skip-gemini (now a no-op back-compat flag on the
4473
+ # claude-only floor; this gates substrate auth). It used to carry its own inline copy of the
4474
+ # PASS/FAIL/SKIP branch; P1 folded it onto `run_live_step` so there is exactly ONE classifier —
4475
+ # a second copy is how a lane silently keeps counting a skip as a pass.
4476
+ run_live_step "smoke-entwurf-v2-matrix-live (D4-c: v2 dispatch substrate sentinel)" gate bash "$self" smoke-entwurf-v2-matrix-live
4477
+ # The spawn-bg resident-lifecycle acceptance that used to sit here left with the transport in the
4478
+ # visible-first cut. A same-id resume lane will need its own acceptance step; it does not inherit
4479
+ # this one, because what that step proved was a HIDDEN child standing its socket up.
4175
4480
 
4176
4481
  # 3b. ACP plugin acceptance floor (S0~S2f live). These prove the ACP plugin's
4177
4482
  # programmatic transport/provider/backend invariants on the v2 core — NOT
@@ -4186,18 +4491,35 @@ release_gate() {
4186
4491
  # → first-user augment delivery + empty-carrier billing-clean (핀1) →
4187
4492
  # ACP-target garden guard (deterministic half). Opt-in LIVE: LIVE!=1 is an
4188
4493
  # HONEST SKIP via run_live_step (see its note) — a CUT needs LIVE=1, SKIP=0.
4189
- run_live_step "smoke-acp-socket-citizen-live (S1: turn-free socket citizenship)" gate env LIVE=1 bash "$self" smoke-acp-socket-citizen-live
4190
- run_live_step "smoke-acp-raw-turn-live (S2a: pinned ACP pipe + local auth)" gate env LIVE=1 bash "$self" smoke-acp-raw-turn-live
4191
- run_live_step "smoke-acp-overlay-live (S2b: config overlay + hooks:{} + tool meta)" gate env LIVE=1 bash "$self" smoke-acp-overlay-live
4192
- run_live_step "smoke-acp-provider-live (S2c/S2f: real pi provider path + progress/L3)" gate env LIVE=1 bash "$self" smoke-acp-provider-live
4193
- run_live_step "smoke-acp-session-reuse-live (S2d: process-scoped reuse + recall)" gate env LIVE=1 bash "$self" smoke-acp-session-reuse-live
4194
- run_live_step "smoke-acp-carrier-augment-live (S2e-1: augment delivery + 핀1 billing)" gate env LIVE=1 bash "$self" smoke-acp-carrier-augment-live
4195
- run_live_step "smoke-acp-memory-containment-live (Gate D: no overlay memory leak)" gate env LIVE=1 bash "$self" smoke-acp-memory-containment-live
4196
- run_live_step "smoke-acp-rgg-live (S2e-2: ACP-target garden guard, deterministic half)" gate env LIVE=1 bash "$self" smoke-acp-rgg-live
4197
- run_live_step "smoke-acp-mcp-live (S2g: operator mcpServers reach the live ACP session)" gate env LIVE=1 bash "$self" smoke-acp-mcp-live
4198
- run_live_step "smoke-acp-skill-live (S2g: operator skillPlugins reach the live ACP session)" gate env LIVE=1 bash "$self" smoke-acp-skill-live
4199
- run_live_step "smoke-acp-bundled-mcp-live (S2g axis 3: bundled entwurf-bridge via 0.11.0 resident/RPC circuit)" gate env LIVE=1 bash "$self" smoke-acp-bundled-mcp-live
4200
- run_live_step "smoke-acp-v2-send-live (S2g axis 4: an ACP model SENDS via entwurf_v2, landing as itself)" gate env LIVE=1 bash "$self" smoke-acp-v2-send-live
4494
+ run_live_step "smoke-acp-socket-citizen-live (S1: turn-free socket citizenship)" gate bash "$self" smoke-acp-socket-citizen-live
4495
+ run_live_step "smoke-acp-raw-turn-live (S2a: pinned ACP pipe + local auth)" gate bash "$self" smoke-acp-raw-turn-live
4496
+ run_live_step "smoke-acp-overlay-live (S2b: config overlay + hooks:{} + tool meta)" gate bash "$self" smoke-acp-overlay-live
4497
+ run_live_step "smoke-acp-provider-live (S2c/S2f: real pi provider path + progress/L3)" gate bash "$self" smoke-acp-provider-live
4498
+ run_live_step "smoke-acp-session-reuse-live (S2d: process-scoped reuse + recall)" gate bash "$self" smoke-acp-session-reuse-live
4499
+ run_live_step "smoke-acp-carrier-augment-live (S2e-1: augment delivery + 핀1 billing)" gate bash "$self" smoke-acp-carrier-augment-live
4500
+ run_live_step "smoke-acp-memory-containment-live (Gate D: no overlay memory leak)" gate bash "$self" smoke-acp-memory-containment-live
4501
+ run_live_step "smoke-acp-rgg-live (S2e-2: ACP-target garden guard, deterministic half)" gate bash "$self" smoke-acp-rgg-live
4502
+ run_live_step "smoke-acp-mcp-live (S2g: operator mcpServers reach the live ACP session)" gate bash "$self" smoke-acp-mcp-live
4503
+ run_live_step "smoke-acp-skill-live (S2g: operator skillPlugins reach the live ACP session)" gate bash "$self" smoke-acp-skill-live
4504
+ run_live_step "smoke-acp-bundled-mcp-live (S2g axis 3: bundled entwurf-bridge via 0.11.0 resident/RPC circuit)" gate bash "$self" smoke-acp-bundled-mcp-live
4505
+ run_live_step "smoke-acp-v2-send-live (S2g axis 4: an ACP model SENDS via entwurf_v2, landing as itself)" gate bash "$self" smoke-acp-v2-send-live
4506
+
4507
+ # 3c. The axes the aggregate used to leave out with no stated reason (P2,
4508
+ # 2026-07-31). Three LIVE smokes existed and were simply never wired, so a
4509
+ # green cut said nothing about them. Under the P1 outcome protocol wiring
4510
+ # them costs nothing on a host that cannot run them — they SKIP honestly and
4511
+ # `--cut` names the missing prerequisite — while a host that CAN run them now
4512
+ # must. Cortex is deliberately NOT here: its rail needs an external Snowflake
4513
+ # connection the HOST owns, so wiring it would block every cut taken without
4514
+ # that account; it stays a required DIRECT call for a Cortex-rail cut instead
4515
+ # (VERIFY 「Cortex is an on-demand axis」). Same for `smoke-acp-long-turn-live`,
4516
+ # `smoke-agy-native-push-live` and `smoke-acp-ordering-probe-live`: >12min by
4517
+ # construction, no aggregate-owned agy conversation id, and an opt-in instrument
4518
+ # rather than acceptance. check-release-gate-outcomes pins that every LIVE smoke
4519
+ # is either wired here or excluded by a reason the docs still carry.
4520
+ run_live_step "smoke-claude-native-resume-live (native Claude Code resume + meta-bridge neutrality)" gate bash "$self" smoke-claude-native-resume-live
4521
+ 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
4522
+ 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
4201
4523
 
4202
4524
  # 4. BEHAVIOR lane (advisory, non-blocking). Model-in-loop gates that probe
4203
4525
  # whether the model AUTONOMOUSLY drives the MCP entwurf surface. These never
@@ -4227,21 +4549,44 @@ release_gate() {
4227
4549
  if [ "${#behavior_results[@]}" -gt 0 ]; then
4228
4550
  printf ' %s\n' "${behavior_results[@]}"
4229
4551
  fi
4230
- echo " BEHAVIOR: PASS=$behavior_pass FAIL=$behavior_failc"
4552
+ echo " BEHAVIOR: PASS=$behavior_pass FAIL=$behavior_failc SKIP=$behavior_skip"
4231
4553
  echo " (per-step artifact paths are printed in each step's output above)"
4232
4554
  if [ "$behavior_failc" -gt 0 ]; then
4233
4555
  echo ""
4234
4556
  warn "BEHAVIOR FAIL present ($behavior_failc) — advisory model-in-loop signal (e.g. S7 Bash-bypass / entwurf_self not autonomously called). Tracked, NOT a cut blocker; see the 0.11.x usability lane."
4235
4557
  fi
4236
- # Exit authority = MUST `failc` ONLY. A BEHAVIOR fail never blocks the cut.
4237
- if [ "$failc" -gt 0 ]; then
4558
+ echo ""
4559
+ if [ "$cut_mode" = "1" ]; then
4560
+ echo " mode: --cut (a MUST SKIP is a BLOCKER — this run is being read as release acceptance)"
4561
+ else
4562
+ echo " mode: diagnostic (MUST SKIPs are reported, not blocking — re-run with --cut to read this as acceptance)"
4563
+ fi
4564
+ # The verdict as one greppable token, so release evidence can distinguish a
4565
+ # step that RAN AND BROKE from one that NEVER RAN without reading prose. The
4566
+ # counters above stay literal — a policy block is `FAIL=0 SKIP=n` plus
4567
+ # `BLOCKED (MUST SKIP)`, never a synthesized failure.
4568
+ echo " $(entwurf_release_verdict "$failc" "$skip" "$cut_mode")"
4569
+
4570
+ # Exit authority — MUST tier only, BEHAVIOR never blocks. The releasable
4571
+ # decision lives in scripts/lib/step-outcome.sh so it is one testable function
4572
+ # rather than a branch re-derived here (and re-derived wrong: the pre-P1 branch
4573
+ # read `failc` alone, so a run with 14 SKIPs still printed "all green").
4574
+ if ! entwurf_release_releasable "$failc" "$skip" "$cut_mode"; then
4238
4575
  echo ""
4239
- fail "release-gate MUST NOT green — $failc release-blocking step(s) failed. Current release is NOT releasable."
4576
+ if [ "$failc" -gt 0 ]; then
4577
+ fail "release-gate MUST NOT green — $failc release-blocking step(s) failed. Current release is NOT releasable."
4578
+ else
4579
+ fail "release-gate --cut REFUSED — $skip release-blocking step(s) SKIPPED, so this run does not prove they were called."
4580
+ echo " A skipped step is not acceptance. Supply each missing prerequisite (usually LIVE=1 plus the"
4581
+ echo " per-step env the [entwurf:skip] lines above name) and re-run, or drop --cut to keep this a diagnostic."
4582
+ fi
4240
4583
  echo " A green MUST gate is necessary but not sufficient; GLG closes the call."
4241
4584
  return 1
4242
4585
  fi
4243
- if [ "$behavior_failc" -gt 0 ]; then
4244
- ok "release-gate MUST PASS (all release-blocking steps green); BEHAVIOR FAIL present (advisory). Necessary condition met GLG authorizes the cut."
4586
+ if [ "$skip" -gt 0 ]; then
4587
+ warn "release-gate MUST has no failures, but $skip step(s) SKIPPED this is a DIAGNOSTIC run, not live acceptance. A cut needs \`LIVE=1 ./run.sh release-gate <scratch> --cut\` with SKIP=0."
4588
+ elif [ "$behavior_failc" -gt 0 ]; then
4589
+ ok "release-gate MUST PASS (all release-blocking steps ran and are green); BEHAVIOR FAIL present (advisory). Necessary condition met — GLG authorizes the cut."
4245
4590
  else
4246
4591
  ok "release-gate MUST PASS + BEHAVIOR PASS — all green. Necessary condition met — GLG authorizes the cut."
4247
4592
  fi
@@ -4363,20 +4708,44 @@ case "$cmd" in
4363
4708
  check-entwurf-bridge-pi-free)
4364
4709
  check_entwurf_bridge_pi_free
4365
4710
  ;;
4366
- check-entwurf-v2-spawn)
4367
- check_entwurf_v2_spawn
4368
- ;;
4369
4711
  check-entwurf-resume-args)
4370
4712
  check_entwurf_resume_args
4371
4713
  ;;
4372
- check-entwurf-v2-spawn-production)
4373
- check_entwurf_v2_spawn_production
4714
+ check-resume-launch-identity)
4715
+ check_resume_launch_identity
4716
+ ;;
4717
+ check-mux-placement)
4718
+ check_mux_placement
4719
+ ;;
4720
+ check-mux-placement-tmux)
4721
+ check_mux_placement_tmux
4722
+ ;;
4723
+ check-mux-launch)
4724
+ check_mux_launch
4374
4725
  ;;
4375
- smoke-entwurf-v2-spawn-live)
4376
- smoke_entwurf_v2_spawn_live
4726
+ check-mux-fresh-call)
4727
+ check_mux_fresh_call
4377
4728
  ;;
4378
- smoke-entwurf-v2-spawn-resume-live)
4379
- smoke_entwurf_v2_spawn_resume_live
4729
+ check-mux-resume-call)
4730
+ check_mux_resume_call
4731
+ ;;
4732
+ check-mux-parent-artifact)
4733
+ check_mux_parent_artifact
4734
+ ;;
4735
+ check-mux-launcher-fence)
4736
+ check_mux_launcher_fence
4737
+ ;;
4738
+ check-entwurf-v2-visible-resume)
4739
+ check_entwurf_v2_visible_resume
4740
+ ;;
4741
+ smoke-mux-fresh-call-live)
4742
+ smoke_mux_fresh_call_live
4743
+ ;;
4744
+ smoke-mux-lifecycle-live)
4745
+ smoke_mux_lifecycle_live
4746
+ ;;
4747
+ check-mux-launch-tmux)
4748
+ check_mux_launch_tmux
4380
4749
  ;;
4381
4750
  smoke-entwurf-v2-matrix-live)
4382
4751
  smoke_entwurf_v2_matrix_live
@@ -4393,6 +4762,9 @@ case "$cmd" in
4393
4762
  smoke-acp-provider-live)
4394
4763
  smoke_acp_provider_live
4395
4764
  ;;
4765
+ smoke-acp-long-turn-live)
4766
+ smoke_acp_long_turn_live
4767
+ ;;
4396
4768
  smoke-acp-session-reuse-live)
4397
4769
  smoke_acp_session_reuse_live
4398
4770
  ;;
@@ -4405,6 +4777,15 @@ case "$cmd" in
4405
4777
  smoke-acp-bundled-mcp-live)
4406
4778
  smoke_acp_bundled_mcp_live
4407
4779
  ;;
4780
+ smoke-entwurf-chain-live)
4781
+ # P3 — the cross-harness delivery CHAIN on real authenticated rails:
4782
+ # native Claude Code -> pi GPT -> pi ACP Claude Sonnet -> mailbox terminus,
4783
+ # with sender identity at every hop and a read receipt at the end. Needs
4784
+ # `claude` on PATH plus pi credentials for both pi backends; each missing
4785
+ # prerequisite is an honest protocol SKIP, never a pass.
4786
+ # LIVE=1 ./run.sh smoke-entwurf-chain-live
4787
+ run_ts scripts/smoke-entwurf-chain-live.ts
4788
+ ;;
4408
4789
  smoke-acp-v2-send-live)
4409
4790
  smoke_acp_v2_send_live
4410
4791
  ;;
@@ -4432,6 +4813,9 @@ case "$cmd" in
4432
4813
  check-meta-listing)
4433
4814
  check_meta_listing
4434
4815
  ;;
4816
+ check-meta-facts)
4817
+ check_meta_facts
4818
+ ;;
4435
4819
  check-entwurf-fact-provider)
4436
4820
  check_entwurf_fact_provider
4437
4821
  ;;
@@ -4608,7 +4992,7 @@ case "$cmd" in
4608
4992
  # 봉인 8 LIVE acceptance gate for the native-push (agy) delivery rail. Drives the REAL
4609
4993
  # antigravity adapter + register core + runEntwurfV2 (production deps) against a live agy
4610
4994
  # conversation (AGY_CONVERSATION_ID): probe route, register create/attach idempotency,
4611
- # fire→native-push delivered, owned-outcome reject, bogus-conv probe-indeterminate reject.
4995
+ # fire→native-push delivered, bogus-conv probe-indeterminate reject.
4612
4996
  # Meta-store is isolated to a temp dir (only the agy round-trip is real); honest SKIP when
4613
4997
  # LIVE!=1. doctor-static preflight FAILs before the agy bridge is wired (③).
4614
4998
  run_ts scripts/smoke-agy-native-push-live.ts
@@ -4775,6 +5159,16 @@ case "$cmd" in
4775
5159
  shift || true
4776
5160
  run_ts scripts/meta-bridge-prune.ts "$@"
4777
5161
  ;;
5162
+ meta-facts)
5163
+ # #65 owner-normalized READ-ONLY projection of the meta-record store: the
5164
+ # (gardenId, nativeSessionId, transcriptPath) join as deterministic JSON,
5165
+ # emitted by THE listing contract (listAllMetaIdentities) so consumers stop
5166
+ # carrying a decaying copy of the certification. Defects ride in-band with
5167
+ # exit 0; an unreadable store is exit 3 and never looks empty. No liveness,
5168
+ # no socket paths, no transcript contents, no state.
5169
+ shift || true
5170
+ run_ts scripts/meta-facts.ts "$@"
5171
+ ;;
4778
5172
  meta-bridge-managed-keys)
4779
5173
  # 0.10.0 meta-bridge: emit the SSOT of settings.json/~/.claude.json keys that
4780
5174
  # entwurf's install OWNS. Consumers (agent-config fragment, future
@@ -4834,6 +5228,15 @@ case "$cmd" in
4834
5228
  check-acp-event-mapper)
4835
5229
  check_acp_event_mapper
4836
5230
  ;;
5231
+ check-acp-stop-reason)
5232
+ check_acp_stop_reason
5233
+ ;;
5234
+ check-acp-prompt-lifecycle)
5235
+ check_acp_prompt_lifecycle
5236
+ ;;
5237
+ check-acp-stream-hooks)
5238
+ check_acp_stream_hooks
5239
+ ;;
4837
5240
  check-acp-prompt-builder)
4838
5241
  check_acp_prompt_builder
4839
5242
  ;;
@@ -4849,6 +5252,9 @@ case "$cmd" in
4849
5252
  check-acp-session-reuse)
4850
5253
  check_acp_session_reuse
4851
5254
  ;;
5255
+ check-release-gate-outcomes)
5256
+ run_ts scripts/check-release-gate-outcomes.ts
5257
+ ;;
4852
5258
  check-acp-carrier-augment)
4853
5259
  check_acp_carrier_augment
4854
5260
  ;;