@junghanacs/entwurf 0.12.10 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/AGENTS.md +6 -3
  2. package/BASELINE.md +56 -163
  3. package/CHANGELOG.md +35 -0
  4. package/CONTRIBUTING.md +4 -2
  5. package/DELIVERY.md +117 -261
  6. package/README.md +74 -399
  7. package/VERIFY.md +59 -85
  8. package/demo/README.md +1 -1
  9. package/docs/acp-backend-rail.md +231 -0
  10. package/docs/external-mcp-host.md +132 -0
  11. package/docs/fresh-cut-policy.md +99 -0
  12. package/docs/setup-clean-host.md +123 -328
  13. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +21 -48
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +12 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +155 -11
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +16 -4
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +66 -7
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +190 -5
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +12 -4
  21. package/mcp/entwurf-bridge/src/index.ts +21 -48
  22. package/package.json +14 -9
  23. package/pi/settings.reference.json +1 -1
  24. package/pi-extensions/acp-provider.ts +20 -10
  25. package/pi-extensions/entwurf-control.ts +20 -49
  26. package/pi-extensions/lib/acp/acp-client.ts +22 -0
  27. package/pi-extensions/lib/acp/augment.ts +99 -2
  28. package/pi-extensions/lib/acp/backend-adapter.ts +190 -14
  29. package/pi-extensions/lib/acp/backend.ts +310 -37
  30. package/pi-extensions/lib/acp/config.ts +19 -5
  31. package/pi-extensions/lib/acp/engraving.ts +46 -2
  32. package/pi-extensions/lib/acp/event-mapper.ts +26 -16
  33. package/pi-extensions/lib/acp/models.ts +69 -7
  34. package/pi-extensions/lib/acp/overlay.ts +234 -5
  35. package/pi-extensions/lib/acp/tool-surface.ts +12 -4
  36. package/prompts/engraving.md +13 -5
  37. package/run.sh +386 -119
  38. package/scripts/check-acp-carrier-augment.ts +246 -10
  39. package/scripts/check-acp-cortex.ts +668 -0
  40. package/scripts/check-acp-prompt-lifecycle.ts +565 -0
  41. package/scripts/check-acp-provider-surface.ts +50 -6
  42. package/scripts/check-acp-sdk-surface.ts +60 -21
  43. package/scripts/check-acp-session-reuse.ts +64 -1
  44. package/scripts/check-acp-stop-reason.ts +342 -0
  45. package/scripts/check-entwurf-v2-surface.ts +41 -0
  46. package/scripts/check-gate-qualification.ts +7 -2
  47. package/scripts/check-probe-cli-shim.ts +879 -0
  48. package/scripts/check-probe-ordering.ts +2482 -0
  49. package/scripts/check-release-gate-outcomes.ts +332 -0
  50. package/scripts/check-shell-quote.ts +4 -4
  51. package/scripts/fixtures/probe-cli-shim +20 -0
  52. package/scripts/fixtures/probe-mcp-server.ts +168 -12
  53. package/scripts/lib/live-skip.ts +33 -0
  54. package/scripts/lib/probe-acp-turn.ts +228 -0
  55. package/scripts/lib/probe-cli-shim.ts +464 -0
  56. package/scripts/lib/probe-cli-target.ts +165 -0
  57. package/scripts/lib/probe-event-log.ts +383 -0
  58. package/scripts/lib/probe-verdict.ts +1213 -0
  59. package/scripts/lib/step-outcome.sh +88 -0
  60. package/scripts/mutants/acp-augment.json +106 -0
  61. package/scripts/mutants/acp-cortex.json +196 -0
  62. package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
  63. package/scripts/mutants/acp-stop-reason.json +80 -0
  64. package/scripts/mutants/probe-ordering.json +1054 -0
  65. package/scripts/mutants/release-gate.json +105 -0
  66. package/scripts/mutants/v2-surface.json +26 -15
  67. package/scripts/smoke-acp-bundled-mcp-live.ts +4 -3
  68. package/scripts/smoke-acp-carrier-augment-live.ts +2 -2
  69. package/scripts/smoke-acp-cortex-live.ts +386 -0
  70. package/scripts/smoke-acp-long-turn-live.ts +185 -0
  71. package/scripts/smoke-acp-mcp-live.ts +2 -2
  72. package/scripts/smoke-acp-memory-containment-live.ts +2 -2
  73. package/scripts/smoke-acp-ordering-probe-live.ts +848 -0
  74. package/scripts/smoke-acp-overlay-live.ts +2 -2
  75. package/scripts/smoke-acp-provider-live.ts +2 -2
  76. package/scripts/smoke-acp-raw-turn-live.ts +3 -3
  77. package/scripts/smoke-acp-session-reuse-live.ts +2 -2
  78. package/scripts/smoke-acp-skill-live.ts +2 -2
  79. package/scripts/smoke-acp-socket-citizen-live.ts +4 -3
  80. package/scripts/smoke-acp-v2-send-live.ts +4 -3
  81. package/scripts/smoke-agy-native-push-live.ts +4 -3
  82. package/scripts/smoke-claude-native-resume-live.sh +13 -3
  83. package/scripts/smoke-entwurf-chain-live.ts +352 -0
  84. package/scripts/smoke-entwurf-v2-matrix-live.ts +2 -2
  85. package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +2 -4
  86. package/scripts/smoke-meta-install-state.sh +4 -0
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
@@ -73,7 +89,7 @@ usage() {
73
89
  cat <<'EOF'
74
90
  Usage:
75
91
  ./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.
92
+ ./run.sh release-gate [project-dir] [--cut] [--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) + the two axes the aggregate used to omit silently (spawn-live/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
93
  ./run.sh check-bridge # entwurf-bridge direct MCP smoke + protocol/negative-path test.sh (live substrate = v2 live smokes)
78
94
  ./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
79
95
  ./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
@@ -129,7 +145,11 @@ Usage:
129
145
  ./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
146
  ./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
147
  ./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)
148
+ ./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
149
+ ./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
150
  ./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
151
+ ./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
152
+ ./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
133
153
  ./run.sh smoke-agy-statusline-state # agy ambient garden-id statusLine install/doctor/inverse regression. Offline/deterministic
134
154
  ./run.sh smoke-agy-hooks-state # agy PreInvocation birth/sender hook install/doctor/inverse + direct stdin→meta-record regression. Offline/deterministic
135
155
  ./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)
@@ -982,9 +1002,10 @@ smoke_entwurf_v2_spawn_live() {
982
1002
  # 5c-3a watcher's timeout→kill→child-exited→release integration on a live process. It does
983
1003
  # NOT spawn a real `pi --entwurf-control` resume (that is the 5d surface matrix). Run once
984
1004
  # before 5d and record the result: LIVE=1 ./run.sh smoke-entwurf-v2-spawn-live
1005
+ entwurf_require_step_outcome
985
1006
  if [ "${LIVE:-}" != "1" ]; then
986
- echo "[smoke-entwurf-v2-spawn-live] skipped — set LIVE=1 to run (spawns real children + opens a real unix socket)."
987
- return 0
1007
+ echo "[entwurf:skip] smoke-entwurf-v2-spawn-live — set LIVE=1 to run (spawns real children + opens a real unix socket)."
1008
+ return "$ENTWURF_STEP_SKIP_EXIT"
988
1009
  fi
989
1010
  run_ts scripts/smoke-entwurf-v2-spawn-live.ts
990
1011
  }
@@ -1049,6 +1070,18 @@ smoke_acp_memory_containment_live() {
1049
1070
  run_ts scripts/smoke-acp-memory-containment-live.ts
1050
1071
  }
1051
1072
 
1073
+ smoke_acp_long_turn_live() {
1074
+ # 0.13.1 LIVE acceptance for the prompt-lifecycle contract — OUT of pnpm check,
1075
+ # needs LIVE=1. Drives ONE real pi provider turn whose tool work (default
1076
+ # 3 x 240s foreground wait) deliberately outlasts the RETIRED 600s prompt cutoff, and
1077
+ # requires it to finish as ONE turn: the nonce arrives, elapsed > 600000ms, the
1078
+ # persisted transcript shows exactly ONE cold ACP bootstrap (no replay), and no
1079
+ # retry/timeout is reported anywhere. Takes >12 minutes by construction.
1080
+ # Overrides: ENTWURF_ACP_LONG_TURN_{MODEL,SLEEP_SECONDS,ROUNDS,HORIZON_MS}.
1081
+ # LIVE=1 ./run.sh smoke-acp-long-turn-live
1082
+ run_ts scripts/smoke-acp-long-turn-live.ts
1083
+ }
1084
+
1052
1085
  smoke_acp_provider_live() {
1053
1086
  # S2c acceptance smoke (ACP plugin on v2) — OUT of pnpm check, needs LIVE=1.
1054
1087
  # Drives the REAL pi PROVIDER path end to end: a real `pi` loads this
@@ -1177,6 +1210,41 @@ smoke_acp_rgg_live() {
1177
1210
  (cd "$REPO_DIR" && ENTWURF_LIVE_TARGET="$target" SMOKE_RGG_POSITIVE=0 bash scripts/smoke-resident-garden-guard.sh)
1178
1211
  }
1179
1212
 
1213
+ smoke_acp_cortex_live() {
1214
+ # On-demand LIVE smoke for the Cortex (Snowflake Cortex Code) ACP backend — the
1215
+ # first NON-claude adapter on the rail (docs/acp-backend-rail.md, Cortex audit). OUT of
1216
+ # `pnpm check` AND OUTSIDE the claude-only aggregate LIVE floor (capability
1217
+ # dignity, invariant #7): a host with no cortex install / no Snowflake auth must
1218
+ # not redden a release for a backend it does not run, so this is deliberately
1219
+ # absent from release-gate's 11-smoke ACP acceptance floor. That is a WIRING
1220
+ # decision, not a lower evidence bar — a cut that SHIPS cortex still owes a
1221
+ # deliberate run of this smoke, and the aggregate gate's silence is not a pass
1222
+ # (ACP rail Cortex verification boundary). Drives one real
1223
+ # cortex ACP turn through the entwurf provider path (outbound entwurf_v2 +
1224
+ # dual-HOME overlay facts + process-group reclaim — CP2).
1225
+ # HONEST-SKIP (exit 0) when LIVE!=1 OR `cortex` is not on PATH OR no
1226
+ # connection is pinned — the live turn needs `cortex` installed with the
1227
+ # operator's own web-login auth already present (there is no `cortex auth`
1228
+ # subcommand — CP0 D6), reached through the overlay's narrow credential
1229
+ # symlinks — narrow in WHICH paths are reachable (D5), not read-only: a symlink
1230
+ # carries no write protection (AGENTS §ACP Plugin Boundary: entwurf never
1231
+ # provides/proxies the Snowflake credential). The connection must ride the adapter seam
1232
+ # (ENTWURF_ACP_CORTEX_CONNECTION) because the dual-HOME overlay denies the
1233
+ # operator settings.json where a default would live. PR #40 called this
1234
+ # `smoke-cortex`; the canonical family name is smoke-acp-cortex-live.
1235
+ # LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live
1236
+ entwurf_require_step_outcome
1237
+ if [ "${LIVE:-}" != "1" ]; then
1238
+ echo "[entwurf:skip] smoke-acp-cortex-live — set LIVE=1 (+ ENTWURF_ACP_CORTEX_CONNECTION) to run."
1239
+ return "$ENTWURF_STEP_SKIP_EXIT"
1240
+ fi
1241
+ if ! command -v cortex >/dev/null 2>&1; then
1242
+ echo "[entwurf:skip] smoke-acp-cortex-live — cortex not on PATH (install Snowflake Cortex Code and complete its own login flow)."
1243
+ return "$ENTWURF_STEP_SKIP_EXIT"
1244
+ fi
1245
+ run_ts scripts/smoke-acp-cortex-live.ts
1246
+ }
1247
+
1180
1248
  smoke_entwurf_v2_matrix_live() {
1181
1249
  # LIVE sentinel for 0.11 Stage 0 step 5d-5 (D4-b) — kept OUT of `pnpm check`. The deterministic
1182
1250
  # sibling (check-entwurf-v2-matrix) fixes every (target kind → transport → lock) cell over fakes
@@ -1189,9 +1257,10 @@ smoke_entwurf_v2_matrix_live() {
1189
1257
  # stay deterministic. Honest skip when LIVE!=1 so the release-gate is runnable unattended.
1190
1258
  # Model: ENTWURF_LIVE_TARGET=<provider>/<model> (default openai-codex/gpt-5.4).
1191
1259
  # LIVE=1 ./run.sh smoke-entwurf-v2-matrix-live
1260
+ entwurf_require_step_outcome
1192
1261
  if [ "${LIVE:-}" != "1" ]; then
1193
- echo "[smoke-entwurf-v2-matrix-live] skipped — set LIVE=1 to run (spawns a real pi --entwurf-control + opens a real socket)."
1194
- return 0
1262
+ echo "[entwurf:skip] smoke-entwurf-v2-matrix-live — set LIVE=1 to run (spawns a real pi --entwurf-control + opens a real socket)."
1263
+ return "$ENTWURF_STEP_SKIP_EXIT"
1195
1264
  fi
1196
1265
  run_ts scripts/smoke-entwurf-v2-matrix-live.ts
1197
1266
  }
@@ -1211,9 +1280,10 @@ smoke_entwurf_v2_spawn_resume_live() {
1211
1280
  # Model: ENTWURF_LIVE_TARGET=<provider>/<model> (default openai-codex/gpt-5.4);
1212
1281
  # ENTWURF_SPAWN_RESUME_ASSISTANT_TIMEOUT_MS (default 180000).
1213
1282
  # LIVE=1 ./run.sh smoke-entwurf-v2-spawn-resume-live
1283
+ entwurf_require_step_outcome
1214
1284
  if [ "${LIVE:-}" != "1" ]; then
1215
- echo "[smoke-entwurf-v2-spawn-resume-live] skipped — set LIVE=1 to run (spawns a real pi resume child + opens a real socket)."
1216
- return 0
1285
+ echo "[entwurf:skip] smoke-entwurf-v2-spawn-resume-live — set LIVE=1 to run (spawns a real pi resume child + opens a real socket)."
1286
+ return "$ENTWURF_STEP_SKIP_EXIT"
1217
1287
  fi
1218
1288
  run_ts scripts/smoke-entwurf-v2-spawn-resume-live.ts
1219
1289
  }
@@ -1436,12 +1506,13 @@ assert.equal(peerTui, piAi,
1436
1506
  // floor tracks the devDep pin so a consumer can't install against a pi lacking
1437
1507
  // the public trust exports the bridge imports at the pinned minor, AND an upper
1438
1508
  // bound at the next minor stops a fresh install from silently pulling a future
1439
- // pi (past the declared ceiling — 0.83+ at the current 0.82.1 pin) whose
1509
+ // pi (past the declared ceiling — 0.84+ at the current 0.83.0 pin) whose
1440
1510
  // internal export surface has drifted from the one we typecheck against.
1441
1511
  // pi moves its public surface every minor (the 0.79→0.80 getModels→provider-
1442
1512
  // factory churn is exactly this), so an open `>=` floor is exactly how the next
1443
- // installer re-acquires the drift. Expected
1444
- // shape: `>=<devDep> <0.<minor+1>` (e.g. `>=0.82.1 <0.83`).
1513
+ // installer re-acquires the drift. The floor is also the HARD MINIMUM a consumer
1514
+ // install resolves: at `>=0.83.0` an existing 0.82.x host is upgraded, not kept.
1515
+ // Expected shape: `>=<devDep> <0.<minor+1>` (e.g. `>=0.83.0 <0.84`).
1445
1516
  const [piMaj, piMin] = piAi.split('.').map(Number);
1446
1517
  assert.equal(piMaj, 0,
1447
1518
  `pi pin major must stay 0 for the next-minor ceiling rule (got ${piAi}); revisit check-dep-versions when pi reaches 1.x`);
@@ -2211,19 +2282,23 @@ EOF
2211
2282
  }
2212
2283
 
2213
2284
  check_acp_provider_surface() {
2214
- # Deterministic gate for the S0 ACP provider loader/fence slice. Loads the REAL
2215
- # provider lib modules and asserts the registration surface: one surface name,
2216
- # curated Claude anchor present with full ProviderModelConfig rows, no-auth
2217
- # sentinel shape, and a FAIL-LOUD streamSimple (calling it throws — no native
2218
- # fallback, no empty-but-successful stream). Pure, no pi runtime, no API.
2219
- section "ACP provider surface (S0 loader/fence)"
2285
+ # Deterministic gate for the ACP provider registration surface. Loads the REAL
2286
+ # provider lib modules AND compiles + drives the REAL entry against a fake pi,
2287
+ # asserting: one surface name, the no-auth sentinel shape, full
2288
+ # ProviderModelConfig rows on the curated claude anchor, the EXACT curated model
2289
+ # set both adapters contribute (claude 2 + cortex 4 —
2290
+ # [QK:CORTEX-PROVIDER-SIX-ROW-SURFACE], the claim that catches an entry which
2291
+ # silently drops a whole backend), and that streamSimple is the real
2292
+ # streamShellAcp backend (checked BY NAME, never invoked — invoking spawns an
2293
+ # ACP child). Pure, no pi runtime, no API.
2294
+ section "ACP provider surface (registration + both-adapter model set)"
2220
2295
  run_ts scripts/check-acp-provider-surface.ts
2221
2296
  }
2222
2297
 
2223
2298
  check_acp_sdk_surface() {
2224
2299
  # Deterministic gate for the S2a ACP SDK dependency surface. Pins the three
2225
2300
  # ACP runtime deps to the current oracle versions (@agentclientprotocol/sdk
2226
- # 1.3.0 + claude-agent-acp 0.62.0 + @anthropic-ai/sdk 0.100.1), locks the
2301
+ # 1.3.0 + claude-agent-acp 0.64.0 + @anthropic-ai/sdk 0.100.1), locks the
2227
2302
  # peer-resolution that keeps claude-agent-sdk satisfiable (0.100.1, not the
2228
2303
  # stale 0.91.1), asserts the wire SDK still value-exports the symbols the raw
2229
2304
  # turn needs (silent-rename gate), and forbids any source-level anthropic SDK
@@ -2268,6 +2343,37 @@ check_acp_event_mapper() {
2268
2343
  run_ts scripts/check-acp-event-mapper.ts
2269
2344
  }
2270
2345
 
2346
+ check_acp_stop_reason() {
2347
+ # Deterministic gate for the ACP stop-reason contract. Drives every member of
2348
+ # the closed ACP terminal set (end_turn / max_tokens / max_turn_requests /
2349
+ # refusal / cancelled), plus an unrecognized reason and an ABSENT one, end to
2350
+ # end through streamAcpTurn against a fake connection — asserting the event
2351
+ # kind, the event reason, and the final message's stopReason / rawStopReason /
2352
+ # errorMessage together. Forbids the collapse-to-"stop" default that reported
2353
+ # refusals and exhausted turn budgets as finished answers. Also pins the
2354
+ # "pending" seed on the freshly created stream state.
2355
+ section "ACP stop reason (terminal set → pi verdict)"
2356
+ run_ts scripts/check-acp-stop-reason.ts
2357
+ }
2358
+
2359
+ check_acp_prompt_lifecycle() {
2360
+ # Deterministic gate for the ACP prompt LIFECYCLE contract. A prompt has no
2361
+ # wall-clock cutoff: it ends when it resolves, when the operator aborts, or
2362
+ # when the child dies / its stdio ends. Drives streamAcpTurn against a fake
2363
+ # ACP child + connection for eight cells — a quiet in-flight turn is not
2364
+ # sealed and later completes on its ORIGINAL prompt; an abort sends ACP
2365
+ # session/cancel first (cancelled→aborted, no signal to a cooperating child);
2366
+ # a wedged agent is torn down after the bounded grace and still returns; a
2367
+ # mid-prompt child death reports exit status + stderr tail on BOTH the new and
2368
+ # the reuse path; a death BETWEEN turns is announced once by the next turn
2369
+ # while a teardown WE performed stays silent — plus pi's own
2370
+ # isRetryableAssistantError as
2371
+ # the oracle that our prompt-phase failure text is not a transient
2372
+ # (cold-replay) error.
2373
+ section "ACP prompt lifecycle (no wall clock; abort/child-death endings)"
2374
+ run_ts scripts/check-acp-prompt-lifecycle.ts
2375
+ }
2376
+
2271
2377
  check_acp_prompt_builder() {
2272
2378
  # Deterministic gate for the S2d bootstrapPath-scoped ACP prompt builder (핀4).
2273
2379
  # Proves prompt SCOPE follows bootstrapPath: new=full transcript (history
@@ -2340,6 +2446,33 @@ check_acp_carrier_augment() {
2340
2446
  run_ts scripts/check-acp-carrier-augment.ts
2341
2447
  }
2342
2448
 
2449
+ check_acp_cortex() {
2450
+ # Deterministic gate for the Cortex (Snowflake Cortex Code) ACP backend — the
2451
+ # first non-claude adapter on the rail, landed 0.13.0 on the CP0-measured
2452
+ # contract (docs/acp-backend-rail.md, Cortex audit D1–D10). The cortex source is one
2453
+ # `cortexAdapter` object (backend-adapter.ts) + its curated surface (models.ts)
2454
+ # + the dual-HOME overlay (overlay.ts); the 결합 규칙 requires the gate to land
2455
+ # WITH it, extending the check-acp-* family, so cortex's whole
2456
+ # deterministic axis lives here. Locks (each Cortex-audit D-pinned): the GLG-decided
2457
+ # 4-row curation rides real registry bases and the `cortex-` prefix routes to
2458
+ # cortexAdapter; launch is `cortex acp serve` with NO -m — the model is
2459
+ # enforced per turn via session/set_config_option with the native id (E);
2460
+ # CORTEX_HOME presence (empty included) refuses the spawn (D3); the dual-HOME
2461
+ # overlay isolates HOME+SNOWFLAKE_HOME per session key (P0-1: scope =
2462
+ # backend-passed resolveSessionKey, never ambient), passes through only
2463
+ # connections.toml / optional config.toml / credential_cache (D5/F — never the
2464
+ # whole cache, never skills; symlink-through, no copies — AGENTS §ACP Plugin
2465
+ # Boundary), authors autoUpdate:false (D4), and projects envelope-enriched
2466
+ # explicit servers into cortex/mcp.json with the real HOME restored on
2467
+ # entwurf-bridge alone (D9/D10), non-stdio failing loud; the
2468
+ # CORTEX_ACP_COMMAND override single-quotes shell-metachar connection tokens.
2469
+ # The gate self-manages the tsc-emit layer for the .js-suffixed backend-adapter
2470
+ # imports; no auth, no spawn, no live cortex needed. Kill-qualified via the
2471
+ # `acp-cortex` mutant lane under check-gate-qualification.
2472
+ section "ACP cortex backend (Snowflake Cortex Code — 1st non-claude adapter)"
2473
+ run_ts scripts/check-acp-cortex.ts
2474
+ }
2475
+
2343
2476
  check_pack() {
2344
2477
  # Dry-run tarball invariant gate for the public npm surface.
2345
2478
  #
@@ -2709,21 +2842,21 @@ _check_pack_install_impl() {
2709
2842
  printf '%s\n' '{ "name": "entwurf-install-smoke", "version": "0.0.0", "private": true }' > "$tmp/package.json"
2710
2843
 
2711
2844
  # pi-agent-core is pinned even though we never import it: pi-coding-agent depends
2712
- # on it by CARET (`^0.82.1`), so with no lockfile in this fresh temp project it
2845
+ # on it by CARET (`^0.83.0`), so with no lockfile in this fresh temp project it
2713
2846
  # floats to whatever pi published last — and that newer core then drags a NESTED
2714
2847
  # pi-ai of its own. Measured 2026-07-21: pinning only the three we import left
2715
2848
  # pi-agent-core@0.80.10 + pi-ai@0.80.10 in the tree while the gate still announced
2716
2849
  # "pinned pi 0.80.7". The gate would then be verifying an UNVERIFIED runtime — the
2717
2850
  # exact class this cut exists to close. Pin every @earendil-works package that
2718
2851
  # constitutes the pi runtime, not just the ones whose types we touch.
2719
- echo "[check-pack-install] pnpm add into $tmp (with 0.82.x peers + typebox)"
2852
+ echo "[check-pack-install] pnpm add into $tmp (with 0.83.x peers + typebox)"
2720
2853
  local install_log
2721
2854
  install_log=$(cd "$tmp" && pnpm add \
2722
2855
  "$tgz_path" \
2723
- "@earendil-works/pi-ai@0.82.1" \
2724
- "@earendil-works/pi-coding-agent@0.82.1" \
2725
- "@earendil-works/pi-tui@0.82.1" \
2726
- "@earendil-works/pi-agent-core@0.82.1" \
2856
+ "@earendil-works/pi-ai@0.83.0" \
2857
+ "@earendil-works/pi-coding-agent@0.83.0" \
2858
+ "@earendil-works/pi-tui@0.83.0" \
2859
+ "@earendil-works/pi-agent-core@0.83.0" \
2727
2860
  "typebox@latest" \
2728
2861
  --ignore-workspace --ignore-scripts 2>&1) || {
2729
2862
  fail "[check-pack-install] pnpm add failed:"
@@ -2733,17 +2866,17 @@ _check_pack_install_impl() {
2733
2866
 
2734
2867
  # A pin is a wish until the resolved tree is read back. Assert it: EVERY
2735
2868
  # @earendil-works pi package present — direct or transitive, top level or nested —
2736
- # must be the pinned 0.82.1. Anything else means an unpinned caret floated and the
2869
+ # must be the pinned 0.83.0. Anything else means an unpinned caret floated and the
2737
2870
  # rest of this gate would be exercising a runtime nobody verified, while still
2738
- # printing "pinned pi 0.82.1". Fail loud instead of proving the wrong floor.
2871
+ # printing "pinned pi 0.83.0". Fail loud instead of proving the wrong floor.
2739
2872
  local leaked_pi
2740
- leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | grep '^@earendil-works+pi-' | grep -v '@0\.82\.1' || true)
2873
+ leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | grep '^@earendil-works+pi-' | grep -v '@0\.83\.0' || true)
2741
2874
  if [ -n "$leaked_pi" ]; then
2742
- fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.82.1):"
2875
+ fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.83.0):"
2743
2876
  printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
2744
2877
  return 1
2745
2878
  fi
2746
- echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works pi package is 0.82.1"
2879
+ echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works pi package is 0.83.0"
2747
2880
 
2748
2881
  # Resolve the installed package.json and confirm pi.extensions
2749
2882
  # arrived intact. If pi.extensions is empty or missing, the
@@ -2824,18 +2957,45 @@ _check_pack_install_impl() {
2824
2957
  echo "$loader_out" | tail -10 | sed 's/^/ /' >&2
2825
2958
  return 1
2826
2959
  fi
2827
- # Verify the full curated Claude surface is visible: the Sonnet row AND the
2828
- # opus anchor (CURATED_ANCHOR_MODEL_ID in lib/acp/models.ts — the model whose
2829
- # absence is a hard registry regression). Checking only one would let half the
2830
- # surface drop silently.
2831
- for anchor in "claude-sonnet-5" "claude-opus-5"; do
2832
- if ! grep -q "$anchor" <<<"$loader_out"; then
2833
- fail "[check-pack-install] pi loader output missing curated Claude model $anchor:"
2834
- echo "$loader_out" | tail -10 | sed 's/^/ /' >&2
2835
- return 1
2836
- fi
2837
- done
2838
- echo "[check-pack-install] pi loader smoke pass (entwurf registered, claude-sonnet-5 + claude-opus-5 anchor)"
2960
+ # Verify the EXACT curated surface the installed package registers — both
2961
+ # backends (claude 2 rows + cortex 4 rows = 6), as a SET, not as anchors.
2962
+ #
2963
+ # Why exact-set and not `grep -q <id>`: the cortex ids CONTAIN the claude ids
2964
+ # as substrings (`cortex-claude-sonnet-5` matches a bare `claude-sonnet-5`
2965
+ # grep), so a substring probe could go green on a surface that dropped every
2966
+ # unprefixed Claude row. And an anchor-only probe cannot see a MISSING cortex
2967
+ # row at all — that is exactly the installed-consumer gap this assertion
2968
+ # closes: the source-side sets are pinned by check-acp-cortex /
2969
+ # check-acp-provider-surface, but only THIS gate proves the ids survive the
2970
+ # pack → install → pi-loader path into a real consumer's model list.
2971
+ #
2972
+ # Parse rows whose FIRST field is the provider `entwurf` (pi prints a
2973
+ # `provider model context …` table; the header row's first field is
2974
+ # `provider`, so it drops out) and take the second field as the model id.
2975
+ local loader_ids loader_expected
2976
+ loader_ids=$(awk '$1 == "entwurf" { print $2 }' <<<"$loader_out" | LC_ALL=C sort)
2977
+ loader_expected=$(printf '%s\n' \
2978
+ claude-opus-5 \
2979
+ claude-sonnet-5 \
2980
+ cortex-auto \
2981
+ cortex-claude-opus-5 \
2982
+ cortex-claude-sonnet-5 \
2983
+ cortex-openai-gpt-5.4 | LC_ALL=C sort)
2984
+ if [ "$loader_ids" != "$loader_expected" ]; then
2985
+ fail "[check-pack-install] installed provider model set drifted — expected exactly 6 curated ids (claude 2 + cortex 4):"
2986
+ echo " expected:" >&2
2987
+ echo "$loader_expected" | sed 's/^/ /' >&2
2988
+ echo " actual (entwurf rows):" >&2
2989
+ echo "${loader_ids:-<none>}" | sed 's/^/ /' >&2
2990
+ echo " raw loader output:" >&2
2991
+ echo "$loader_out" | tail -15 | sed 's/^/ /' >&2
2992
+ return 1
2993
+ fi
2994
+ # Name the set on SUCCESS too, not only in the failure branch — same principle as
2995
+ # the pinned-pi version assert above: a gate that cannot name what it proved has
2996
+ # proved nothing, and "6 rows" without the ids cannot be audited from a CI log.
2997
+ echo "[check-pack-install] pi loader smoke pass (entwurf registered; exact 6-row curated set: claude 2 + cortex 4):"
2998
+ echo "$loader_ids" | sed 's/^/ /'
2839
2999
 
2840
3000
  # npm-managed neutral install regression — the README's PRIMARY install path is
2841
3001
  # now `npm install @junghanacs/entwurf` (NOT `pi install npm:...`). This layout
@@ -3663,7 +3823,7 @@ setup_all() {
3663
3823
  # native-harness meta-bridge, so a relocate/clone needs ONE command — not the
3664
3824
  # install + install-meta-bridge two-pronged split that froze the Claude
3665
3825
  # statusLine on the old path (the 2026-06-23 relocation gap). Detection-gated:
3666
- # a pi-only host with no native harness skips it cleanly (§10 "있으면 설정,
3826
+ # a pi-only host with no native harness skips it cleanly (rail “Shipped adapters” — "있으면 설정,
3667
3827
  # 없으면 담아준다"); meta-bridge-install.sh is itself idempotent.
3668
3828
  if command -v claude >/dev/null 2>&1; then
3669
3829
  section "meta-bridge install (native harness detected: Claude Code)"
@@ -3695,7 +3855,7 @@ setup_all() {
3695
3855
  wire_agy_hooks
3696
3856
 
3697
3857
  # Deterministic preflight lives in `pnpm check`; live substrate acceptance lives
3698
- # in `LIVE=1 ./run.sh release-gate <scratch>`. Setup is the install path, so it
3858
+ # in `LIVE=1 ./run.sh release-gate <scratch> --cut`. Setup is the install path, so it
3699
3859
  # verifies the installed MCP bridge boundary only and does NOT run the legacy
3700
3860
  # ACP/v1 session-messaging/sentinel gates.
3701
3861
  section "v2 install smoke: entwurf-bridge (direct MCP protocol)"
@@ -3711,7 +3871,7 @@ setup_all() {
3711
3871
  echo " ./run.sh doctor-agy-statusline"
3712
3872
  echo " ./run.sh doctor-agy-hooks"
3713
3873
  fi
3714
- echo "Run 'LIVE=1 ./run.sh release-gate <scratch>' for live substrate acceptance."
3874
+ echo "Run 'LIVE=1 ./run.sh release-gate <scratch> --cut' for live substrate acceptance (--cut refuses any MUST SKIP)."
3715
3875
  }
3716
3876
 
3717
3877
  # wire_agy_bridge — detection-gated, NON-FATAL agy (Antigravity) MCP bridge wiring, folded into
@@ -3727,7 +3887,7 @@ wire_agy_bridge() {
3727
3887
  # whatever agy the CI/dev host happens to have — production leaves it unset (= `command -v
3728
3888
  # agy`, no regression). Same override spirit as agy-bridge.sh's AGY_MCP_CONFIG/AGY_BRIDGE_COMMAND.
3729
3889
  if ! command -v "${AGY_BIN:-agy}" >/dev/null 2>&1; then
3730
- echo "[setup] no agy on PATH — skipping agy bridge wiring (no state; pi/Claude-only host)"
3890
+ echo "[setup] no agy on PATH — skipping agy bridge wiring (no state; this host runs no Antigravity)"
3731
3891
  return 0
3732
3892
  fi
3733
3893
  section "agy bridge install (native harness detected: Antigravity)"
@@ -3780,7 +3940,7 @@ wire_agy_bridge() {
3780
3940
  # stable bin entwurf-agy-statusline, exposed by expose_dev_bin above.
3781
3941
  wire_agy_statusline() {
3782
3942
  if ! command -v "${AGY_BIN:-agy}" >/dev/null 2>&1; then
3783
- echo "[setup] no agy on PATH — skipping agy statusLine wiring (no state; pi/Claude-only host)"
3943
+ echo "[setup] no agy on PATH — skipping agy statusLine wiring (no state; this host runs no Antigravity)"
3784
3944
  return 0
3785
3945
  fi
3786
3946
  section "agy statusLine install (native harness detected: Antigravity)"
@@ -3818,7 +3978,7 @@ wire_agy_statusline() {
3818
3978
  # garden id after the first invocation.
3819
3979
  wire_agy_hooks() {
3820
3980
  if ! command -v "${AGY_BIN:-agy}" >/dev/null 2>&1; then
3821
- echo "[setup] no agy on PATH — skipping agy hooks wiring (no state; pi/Claude-only host)"
3981
+ echo "[setup] no agy on PATH — skipping agy hooks wiring (no state; this host runs no Antigravity)"
3822
3982
  return 0
3823
3983
  fi
3824
3984
  section "agy hooks install (native harness detected: Antigravity)"
@@ -3895,10 +4055,18 @@ expose_dev_bin() {
3895
4055
  # - Final release authorization is GLG's, not this script's: a green
3896
4056
  # run is necessary, and the operator closes the decision.
3897
4057
  release_gate() {
4058
+ entwurf_require_step_outcome
3898
4059
  local -a positional=()
3899
4060
  local a
4061
+ # --cut is the EXECUTABLE half of "a CUT needs LIVE=1, SKIP=0" (P1). Without it
4062
+ # this stays the unattended diagnostic it has always been: SKIPs are reported
4063
+ # and the run still exits 0. With it, any MUST SKIP is red — including the
4064
+ # LIVE!=1 case, which needs no separate assertion because every LIVE-gated step
4065
+ # then skips on its own and the skip counter blocks.
4066
+ local cut_mode=0
3900
4067
  for a in "$@"; do
3901
4068
  case "$a" in
4069
+ --cut) cut_mode=1 ;;
3902
4070
  --allow-skip-gemini) ;; # accepted-but-ignored: gemini removed from the claude-only floor (back-compat for existing scripts)
3903
4071
  *) positional+=("$a") ;;
3904
4072
  esac
@@ -3949,27 +4117,36 @@ release_gate() {
3949
4117
  }
3950
4118
 
3951
4119
  # LIVE-gated MUST step: a release-blocking gate that needs a real backend turn
3952
- # (auth/model/credit). When LIVE!=1 it is an HONEST SKIP — counted as SKIP, NOT
3953
- # PASS — so an unattended `./run.sh release-gate` stays runnable without faking
3954
- # coverage. A release CUT therefore requires `LIVE=1 ./run.sh release-gate
3955
- # <scratch>` to land MUST PASS with SKIP=0; a green run that still shows SKIP is
3956
- # CI safety, never live acceptance. (Same rule the v2 substrate sentinels below
3957
- # spell out inline; this helper applies it to the ACP plugin acceptance floor.)
4120
+ # (auth/model/credit). It is ALWAYS INVOKED and the step itself reports whether
4121
+ # it could run — that is the whole point of P1. The old shape asked `LIVE=1?`
4122
+ # here and skipped without calling, which meant the summary asserted a skip it
4123
+ # had never confirmed, while a step that WAS called could still exit 0 on a
4124
+ # missing prerequisite (Cortex without a connection) and be counted PASS.
4125
+ # Now one protocol decides: exit 0 = PASS, $ENTWURF_STEP_SKIP_EXIT = SKIP,
4126
+ # anything else = FAIL. A SKIP is never rounded up, and `--cut` refuses any.
4127
+ #
4128
+ # `LIVE` is NOT forced here — it rides the operator's environment into the
4129
+ # child, so `LIVE=1 ./run.sh release-gate` runs the step for real and a bare
4130
+ # invocation gets the step's own honest skip.
3958
4131
  run_live_step() {
3959
4132
  local name="$1"; shift
3960
4133
  section "release-gate step: $name"
3961
- if [ "${LIVE:-}" = "1" ]; then
3962
- if "$@"; then
4134
+ local rc=0
4135
+ "$@" || rc=$?
4136
+ case "$(entwurf_step_outcome "$rc")" in
4137
+ PASS)
3963
4138
  ok "$name: PASS"
3964
4139
  results+=("PASS $name"); pass=$((pass + 1))
3965
- else
3966
- fail "$name: FAIL"
4140
+ ;;
4141
+ SKIP)
4142
+ warn "$name: SKIP — the step declined a prerequisite it does not have (see its [entwurf:skip] line above). NOT a live acceptance."
4143
+ results+=("SKIP $name"); skip=$((skip + 1))
4144
+ ;;
4145
+ *)
4146
+ fail "$name: FAIL (exit $rc)"
3967
4147
  results+=("FAIL $name"); failc=$((failc + 1))
3968
- fi
3969
- else
3970
- warn "$name: LIVE!=1 — skipped (opt-in: needs auth/model — NOT a live acceptance run)"
3971
- results+=("SKIP $name (LIVE!=1)"); skip=$((skip + 1))
3972
- fi
4148
+ ;;
4149
+ esac
3973
4150
  }
3974
4151
 
3975
4152
  # BEHAVIOR lane (0.11.0, GLG+GPT+Opus): a SEPARATE advisory counter for
@@ -3993,19 +4170,32 @@ release_gate() {
3993
4170
  # deterministic/programmatic must-pass gates above (check-entwurf-v2-*,
3994
4171
  # check-bridge) — this lane is autonomous-tool-selection *behavior*, not
3995
4172
  # *function*. Residual bypass → 0.11.x usability lane.
3996
- local behavior_pass=0 behavior_failc=0
4173
+ # The lane speaks the same STEP OUTCOME protocol (P1) so a missing prerequisite
4174
+ # here is a BEHAVIOR-SKIP, not a fabricated BEHAVIOR-PASS or a misread
4175
+ # BEHAVIOR-FAIL. Advisory semantics are untouched: none of these counters ever
4176
+ # reaches `failc`, and `--cut` reads the MUST skip counter only.
4177
+ local behavior_pass=0 behavior_failc=0 behavior_skip=0
3997
4178
  local -a behavior_results=()
3998
4179
 
3999
4180
  run_behavior_step() {
4000
4181
  local name="$1"; shift
4001
4182
  section "release-gate BEHAVIOR step (advisory, non-blocking): $name"
4002
- if "$@"; then
4003
- ok "$name: BEHAVIOR-PASS"
4004
- behavior_results+=("BEHAVIOR-PASS $name"); behavior_pass=$((behavior_pass + 1))
4005
- else
4006
- warn "$name: BEHAVIOR-FAIL (advisory — model-in-loop signal; S7=Bash-bypass stays hard-fail here; NOT a cut blocker)"
4007
- behavior_results+=("BEHAVIOR-FAIL $name"); behavior_failc=$((behavior_failc + 1))
4008
- fi
4183
+ local rc=0
4184
+ "$@" || rc=$?
4185
+ case "$(entwurf_step_outcome "$rc")" in
4186
+ PASS)
4187
+ ok "$name: BEHAVIOR-PASS"
4188
+ behavior_results+=("BEHAVIOR-PASS $name"); behavior_pass=$((behavior_pass + 1))
4189
+ ;;
4190
+ SKIP)
4191
+ warn "$name: BEHAVIOR-SKIP — prerequisite absent (advisory lane; not a signal either way)"
4192
+ behavior_results+=("BEHAVIOR-SKIP $name"); behavior_skip=$((behavior_skip + 1))
4193
+ ;;
4194
+ *)
4195
+ warn "$name: BEHAVIOR-FAIL (advisory — model-in-loop signal; S7=Bash-bypass stays hard-fail here; NOT a cut blocker)"
4196
+ behavior_results+=("BEHAVIOR-FAIL $name"); behavior_failc=$((behavior_failc + 1))
4197
+ ;;
4198
+ esac
4009
4199
  }
4010
4200
 
4011
4201
  # 1. Static floor (deterministic; includes the two folded gates).
@@ -4043,41 +4233,20 @@ release_gate() {
4043
4233
  # production runEntwurfV2 deps + real pi control-socket RPC + real mailbox enqueue + v2 lock, not
4044
4234
  # per-backend model behavior). Placed right after check-bridge: the MCP/protocol substrate must be
4045
4235
  # green first so a matrix-live failure reads as "v2 transport/lock/enqueue", not bridge basics.
4046
- # Opt-in LIVE: it spawns a real `pi --entwurf-control` (needs auth/model), so LIVE!=1 is an HONEST
4047
- # SKIP (not a PASS) — an unattended release-gate stays runnable without faking coverage. Independent
4048
- # of --allow-skip-gemini (now a no-op back-compat flag on the claude-only floor; this gates substrate auth).
4049
- section "release-gate step: smoke-entwurf-v2-matrix-live"
4050
- if [ "${LIVE:-}" = "1" ]; then
4051
- if gate env LIVE=1 bash "$self" smoke-entwurf-v2-matrix-live; then
4052
- ok "smoke-entwurf-v2-matrix-live: PASS"
4053
- results+=("PASS smoke-entwurf-v2-matrix-live"); pass=$((pass + 1))
4054
- else
4055
- fail "smoke-entwurf-v2-matrix-live: FAIL"
4056
- results+=("FAIL smoke-entwurf-v2-matrix-live"); failc=$((failc + 1))
4057
- fi
4058
- else
4059
- warn "smoke-entwurf-v2-matrix-live: LIVE!=1 — skipped (v2 substrate sentinel, opt-in: needs auth/model)"
4060
- results+=("SKIP smoke-entwurf-v2-matrix-live (LIVE!=1)"); skip=$((skip + 1))
4061
- fi
4236
+ # Opt-in LIVE: it spawns a real `pi --entwurf-control` (needs auth/model), so a missing
4237
+ # prerequisite is an HONEST SKIP (not a PASS) — an unattended release-gate stays runnable without
4238
+ # faking coverage. Independent of --allow-skip-gemini (now a no-op back-compat flag on the
4239
+ # claude-only floor; this gates substrate auth). It used to carry its own inline copy of the
4240
+ # PASS/FAIL/SKIP branch; P1 folded it onto `run_live_step` so there is exactly ONE classifier —
4241
+ # a second copy is how a lane silently keeps counting a skip as a pass.
4242
+ run_live_step "smoke-entwurf-v2-matrix-live (D4-c: v2 dispatch substrate sentinel)" gate bash "$self" smoke-entwurf-v2-matrix-live
4062
4243
  # 0.11.0 (A) acceptance: the FULL spawn-bg resident lifecycle — a real `pi` resume child stands
4063
4244
  # its control socket up, does a model turn, lock released ×1. Placed right after matrix-live (the
4064
4245
  # transport sentinel): a spawn-resume failure then reads as "resume/resident lifecycle", not
4065
- # transport basics. Same opt-in LIVE rule — LIVE!=1 is an HONEST SKIP (model-in-loop, needs
4066
- # auth/model). NOTE: a 0.11.0 tag REQUIRES `LIVE=1 ./run.sh release-gate` (or this step direct)
4067
- # to PASS — the SKIP is CI safety, never acceptance.
4068
- section "release-gate step: smoke-entwurf-v2-spawn-resume-live"
4069
- if [ "${LIVE:-}" = "1" ]; then
4070
- if gate env LIVE=1 bash "$self" smoke-entwurf-v2-spawn-resume-live; then
4071
- ok "smoke-entwurf-v2-spawn-resume-live: PASS"
4072
- results+=("PASS smoke-entwurf-v2-spawn-resume-live"); pass=$((pass + 1))
4073
- else
4074
- fail "smoke-entwurf-v2-spawn-resume-live: FAIL"
4075
- results+=("FAIL smoke-entwurf-v2-spawn-resume-live"); failc=$((failc + 1))
4076
- fi
4077
- else
4078
- warn "smoke-entwurf-v2-spawn-resume-live: LIVE!=1 — skipped (0.11.0 A acceptance, opt-in: needs auth/model)"
4079
- results+=("SKIP smoke-entwurf-v2-spawn-resume-live (LIVE!=1)"); skip=$((skip + 1))
4080
- fi
4246
+ # transport basics. Same opt-in LIVE rule. NOTE: a tag REQUIRES `LIVE=1 ./run.sh release-gate
4247
+ # --cut` (or this step direct) to PASS — a SKIP is CI safety, never acceptance, and `--cut` is
4248
+ # what now enforces that instead of prose.
4249
+ run_live_step "smoke-entwurf-v2-spawn-resume-live (0.11.0 A: full spawn-bg resident lifecycle)" gate bash "$self" smoke-entwurf-v2-spawn-resume-live
4081
4250
 
4082
4251
  # 3b. ACP plugin acceptance floor (S0~S2f live). These prove the ACP plugin's
4083
4252
  # programmatic transport/provider/backend invariants on the v2 core — NOT
@@ -4092,18 +4261,35 @@ release_gate() {
4092
4261
  # → first-user augment delivery + empty-carrier billing-clean (핀1) →
4093
4262
  # ACP-target garden guard (deterministic half). Opt-in LIVE: LIVE!=1 is an
4094
4263
  # HONEST SKIP via run_live_step (see its note) — a CUT needs LIVE=1, SKIP=0.
4095
- run_live_step "smoke-acp-socket-citizen-live (S1: turn-free socket citizenship)" gate env LIVE=1 bash "$self" smoke-acp-socket-citizen-live
4096
- 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
4097
- run_live_step "smoke-acp-overlay-live (S2b: config overlay + hooks:{} + tool meta)" gate env LIVE=1 bash "$self" smoke-acp-overlay-live
4098
- 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
4099
- run_live_step "smoke-acp-session-reuse-live (S2d: process-scoped reuse + recall)" gate env LIVE=1 bash "$self" smoke-acp-session-reuse-live
4100
- 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
4101
- 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
4102
- 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
4103
- 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
4104
- 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
4105
- 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
4106
- 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
4264
+ run_live_step "smoke-acp-socket-citizen-live (S1: turn-free socket citizenship)" gate bash "$self" smoke-acp-socket-citizen-live
4265
+ run_live_step "smoke-acp-raw-turn-live (S2a: pinned ACP pipe + local auth)" gate bash "$self" smoke-acp-raw-turn-live
4266
+ run_live_step "smoke-acp-overlay-live (S2b: config overlay + hooks:{} + tool meta)" gate bash "$self" smoke-acp-overlay-live
4267
+ run_live_step "smoke-acp-provider-live (S2c/S2f: real pi provider path + progress/L3)" gate bash "$self" smoke-acp-provider-live
4268
+ run_live_step "smoke-acp-session-reuse-live (S2d: process-scoped reuse + recall)" gate bash "$self" smoke-acp-session-reuse-live
4269
+ run_live_step "smoke-acp-carrier-augment-live (S2e-1: augment delivery + 핀1 billing)" gate bash "$self" smoke-acp-carrier-augment-live
4270
+ run_live_step "smoke-acp-memory-containment-live (Gate D: no overlay memory leak)" gate bash "$self" smoke-acp-memory-containment-live
4271
+ run_live_step "smoke-acp-rgg-live (S2e-2: ACP-target garden guard, deterministic half)" gate bash "$self" smoke-acp-rgg-live
4272
+ run_live_step "smoke-acp-mcp-live (S2g: operator mcpServers reach the live ACP session)" gate bash "$self" smoke-acp-mcp-live
4273
+ run_live_step "smoke-acp-skill-live (S2g: operator skillPlugins reach the live ACP session)" gate bash "$self" smoke-acp-skill-live
4274
+ 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
4275
+ 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
4276
+
4277
+ # 3c. The axes the aggregate used to leave out with no stated reason (P2,
4278
+ # 2026-07-31). Three LIVE smokes existed and were simply never wired, so a
4279
+ # green cut said nothing about them. Under the P1 outcome protocol wiring
4280
+ # them costs nothing on a host that cannot run them — they SKIP honestly and
4281
+ # `--cut` names the missing prerequisite — while a host that CAN run them now
4282
+ # must. Cortex is deliberately NOT here: its rail needs an external Snowflake
4283
+ # connection the HOST owns, so wiring it would block every cut taken without
4284
+ # that account; it stays a required DIRECT call for a Cortex-rail cut instead
4285
+ # (VERIFY 「Cortex is an on-demand axis」). Same for `smoke-acp-long-turn-live`,
4286
+ # `smoke-agy-native-push-live` and `smoke-acp-ordering-probe-live`: >12min by
4287
+ # construction, no aggregate-owned agy conversation id, and an opt-in instrument
4288
+ # rather than acceptance. check-release-gate-outcomes pins that every LIVE smoke
4289
+ # is either wired here or excluded by a reason the docs still carry.
4290
+ run_live_step "smoke-entwurf-v2-spawn-live (5c-3c: spawn-bg OS substrate on real children/sockets)" gate bash "$self" smoke-entwurf-v2-spawn-live
4291
+ run_live_step "smoke-claude-native-resume-live (native Claude Code resume + meta-bridge neutrality)" gate bash "$self" smoke-claude-native-resume-live
4292
+ 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
4107
4293
 
4108
4294
  # 4. BEHAVIOR lane (advisory, non-blocking). Model-in-loop gates that probe
4109
4295
  # whether the model AUTONOMOUSLY drives the MCP entwurf surface. These never
@@ -4133,21 +4319,44 @@ release_gate() {
4133
4319
  if [ "${#behavior_results[@]}" -gt 0 ]; then
4134
4320
  printf ' %s\n' "${behavior_results[@]}"
4135
4321
  fi
4136
- echo " BEHAVIOR: PASS=$behavior_pass FAIL=$behavior_failc"
4322
+ echo " BEHAVIOR: PASS=$behavior_pass FAIL=$behavior_failc SKIP=$behavior_skip"
4137
4323
  echo " (per-step artifact paths are printed in each step's output above)"
4138
4324
  if [ "$behavior_failc" -gt 0 ]; then
4139
4325
  echo ""
4140
4326
  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."
4141
4327
  fi
4142
- # Exit authority = MUST `failc` ONLY. A BEHAVIOR fail never blocks the cut.
4143
- if [ "$failc" -gt 0 ]; then
4328
+ echo ""
4329
+ if [ "$cut_mode" = "1" ]; then
4330
+ echo " mode: --cut (a MUST SKIP is a BLOCKER — this run is being read as release acceptance)"
4331
+ else
4332
+ echo " mode: diagnostic (MUST SKIPs are reported, not blocking — re-run with --cut to read this as acceptance)"
4333
+ fi
4334
+ # The verdict as one greppable token, so release evidence can distinguish a
4335
+ # step that RAN AND BROKE from one that NEVER RAN without reading prose. The
4336
+ # counters above stay literal — a policy block is `FAIL=0 SKIP=n` plus
4337
+ # `BLOCKED (MUST SKIP)`, never a synthesized failure.
4338
+ echo " $(entwurf_release_verdict "$failc" "$skip" "$cut_mode")"
4339
+
4340
+ # Exit authority — MUST tier only, BEHAVIOR never blocks. The releasable
4341
+ # decision lives in scripts/lib/step-outcome.sh so it is one testable function
4342
+ # rather than a branch re-derived here (and re-derived wrong: the pre-P1 branch
4343
+ # read `failc` alone, so a run with 14 SKIPs still printed "all green").
4344
+ if ! entwurf_release_releasable "$failc" "$skip" "$cut_mode"; then
4144
4345
  echo ""
4145
- fail "release-gate MUST NOT green — $failc release-blocking step(s) failed. Current release is NOT releasable."
4346
+ if [ "$failc" -gt 0 ]; then
4347
+ fail "release-gate MUST NOT green — $failc release-blocking step(s) failed. Current release is NOT releasable."
4348
+ else
4349
+ fail "release-gate --cut REFUSED — $skip release-blocking step(s) SKIPPED, so this run does not prove they were called."
4350
+ echo " A skipped step is not acceptance. Supply each missing prerequisite (usually LIVE=1 plus the"
4351
+ echo " per-step env the [entwurf:skip] lines above name) and re-run, or drop --cut to keep this a diagnostic."
4352
+ fi
4146
4353
  echo " A green MUST gate is necessary but not sufficient; GLG closes the call."
4147
4354
  return 1
4148
4355
  fi
4149
- if [ "$behavior_failc" -gt 0 ]; then
4150
- ok "release-gate MUST PASS (all release-blocking steps green); BEHAVIOR FAIL present (advisory). Necessary condition met — GLG authorizes the cut."
4356
+ if [ "$skip" -gt 0 ]; then
4357
+ 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."
4358
+ elif [ "$behavior_failc" -gt 0 ]; then
4359
+ ok "release-gate MUST PASS (all release-blocking steps ran and are green); BEHAVIOR FAIL present (advisory). Necessary condition met — GLG authorizes the cut."
4151
4360
  else
4152
4361
  ok "release-gate MUST PASS + BEHAVIOR PASS — all green. Necessary condition met — GLG authorizes the cut."
4153
4362
  fi
@@ -4299,6 +4508,9 @@ case "$cmd" in
4299
4508
  smoke-acp-provider-live)
4300
4509
  smoke_acp_provider_live
4301
4510
  ;;
4511
+ smoke-acp-long-turn-live)
4512
+ smoke_acp_long_turn_live
4513
+ ;;
4302
4514
  smoke-acp-session-reuse-live)
4303
4515
  smoke_acp_session_reuse_live
4304
4516
  ;;
@@ -4311,6 +4523,15 @@ case "$cmd" in
4311
4523
  smoke-acp-bundled-mcp-live)
4312
4524
  smoke_acp_bundled_mcp_live
4313
4525
  ;;
4526
+ smoke-entwurf-chain-live)
4527
+ # P3 — the cross-harness delivery CHAIN on real authenticated rails:
4528
+ # native Claude Code -> pi GPT -> pi ACP Claude Sonnet -> mailbox terminus,
4529
+ # with sender identity at every hop and a read receipt at the end. Needs
4530
+ # `claude` on PATH plus pi credentials for both pi backends; each missing
4531
+ # prerequisite is an honest protocol SKIP, never a pass.
4532
+ # LIVE=1 ./run.sh smoke-entwurf-chain-live
4533
+ run_ts scripts/smoke-entwurf-chain-live.ts
4534
+ ;;
4314
4535
  smoke-acp-v2-send-live)
4315
4536
  smoke_acp_v2_send_live
4316
4537
  ;;
@@ -4320,6 +4541,9 @@ case "$cmd" in
4320
4541
  smoke-acp-rgg-live)
4321
4542
  smoke_acp_rgg_live
4322
4543
  ;;
4544
+ smoke-acp-cortex-live)
4545
+ smoke_acp_cortex_live
4546
+ ;;
4323
4547
  smoke-acp-socket-citizen-live)
4324
4548
  smoke_acp_socket_citizen_live
4325
4549
  ;;
@@ -4452,6 +4676,37 @@ case "$cmd" in
4452
4676
  # control-red/impurity all must classify red before any real manifest counts.
4453
4677
  run_ts scripts/check-gate-qualification.ts
4454
4678
  ;;
4679
+ check-probe-ordering)
4680
+ # §11-7 ordering-probe gate (docs/acp-backend-rail.md §11-7): the raw-client seam is
4681
+ # only admissible "bound by a gate asserting it issues the same calls, arguments, and
4682
+ # order as the backend's real sequence" — this is that gate. Sameness over a recording
4683
+ # fake + backend.ts SOURCE pins (order/clientInfo/timeouts/permission policy), phase
4684
+ # attribution (set-model included), the probe-mode fixture's wire instrumentation
4685
+ # (real child, raw JSON-RPC, no API), the shared log's contract at its door — envelope
4686
+ # (the evidence line's own runId/marker/sort axis cannot be rewritten by a payload) AND
4687
+ # payload (every field the classifier judges on is typed there) — and the PURE
4688
+ # paired-verdict truth table.
4689
+ run_ts scripts/check-probe-ordering.ts
4690
+ ;;
4691
+ check-probe-cli-shim)
4692
+ # §11-7-c producer gate (docs/acp-backend-rail.md §11-7-c condition 5: "Byte-transparency,
4693
+ # backpressure, and exit/signal propagation are proved by a fake-CLI deterministic gate").
4694
+ # The shim sits on the production spawn path of a paid LIVE turn, so this drives it as a
4695
+ # REAL process against fake CLIs and asks what each defect would buy: fabricated absence
4696
+ # evidence (a malformed init reported as an empty name set, a boot report that does not
4697
+ # name what was actually exec'd), a destroyed turn (mangled bytes, a swallowed signal, a
4698
+ # crash reported as exit 0), or leaked operator state (a prefix env scrub, argv/env/prompt
4699
+ # body in the shared log). No API, no network, no cost.
4700
+ run_ts scripts/check-probe-cli-shim.ts
4701
+ ;;
4702
+ smoke-acp-ordering-probe-live)
4703
+ # §11-7 ordering probe — LIVE paired-run instrument, OUT of pnpm check. Control
4704
+ # (delay=0, must be visible AND callable) + interventions D1/D2 against the SAME
4705
+ # pins/config/fixture; classifies A/A-withheld/B/C/D-<phase> or P0/I0, preserves
4706
+ # the shared NDJSON log + classification under .probe-artifacts/ (gitignored).
4707
+ # LIVE=1 ./run.sh smoke-acp-ordering-probe-live
4708
+ run_ts scripts/smoke-acp-ordering-probe-live.ts
4709
+ ;;
4455
4710
  smoke-agy-statusline-state)
4456
4711
  # #46 Task 1 regression gate for the agy statusLine install adapter: install→doctor→uninstall
4457
4712
  # in an ISOLATED HOME+XDG with a fake stable bin (entwurf-agy-statusline) + fake pgrep —
@@ -4706,6 +4961,12 @@ case "$cmd" in
4706
4961
  check-acp-event-mapper)
4707
4962
  check_acp_event_mapper
4708
4963
  ;;
4964
+ check-acp-stop-reason)
4965
+ check_acp_stop_reason
4966
+ ;;
4967
+ check-acp-prompt-lifecycle)
4968
+ check_acp_prompt_lifecycle
4969
+ ;;
4709
4970
  check-acp-prompt-builder)
4710
4971
  check_acp_prompt_builder
4711
4972
  ;;
@@ -4721,9 +4982,15 @@ case "$cmd" in
4721
4982
  check-acp-session-reuse)
4722
4983
  check_acp_session_reuse
4723
4984
  ;;
4985
+ check-release-gate-outcomes)
4986
+ run_ts scripts/check-release-gate-outcomes.ts
4987
+ ;;
4724
4988
  check-acp-carrier-augment)
4725
4989
  check_acp_carrier_augment
4726
4990
  ;;
4991
+ check-acp-cortex)
4992
+ check_acp_cortex
4993
+ ;;
4727
4994
  check-fresh-cut-gate)
4728
4995
  # SOURCE cell of the generation-boundary proof: a host whose store the live
4729
4996
  # schema cannot read must be refused BEFORE any write, told the fresh-cut