@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
@@ -0,0 +1,88 @@
1
+ # shellcheck shell=bash
2
+ # Release-gate STEP OUTCOME protocol — the one place PASS / SKIP / FAIL is decided.
3
+ #
4
+ # SOURCE-ONLY. It carries the executable bit because check-pack requires every
5
+ # shipped `.sh` to have one (a blanket packaging invariant worth more than an
6
+ # exception carved for this file), not because anyone should run it.
7
+ #
8
+ # Sourced by run.sh's `release_gate`; sourced directly by
9
+ # scripts/check-release-gate-outcomes.ts so the truth table is exercised as a
10
+ # unit instead of being re-derived by eye from the aggregate's output.
11
+ #
12
+ # WHY A PROTOCOL AND NOT A PER-BACKEND BRANCH (P1, 2026-07-31). The aggregate's
13
+ # prose said a cut needs `LIVE=1` and `SKIP=0`, but its exit authority read only
14
+ # the FAIL counter, so `./run.sh release-gate` returned 0 with 14 SKIPs and the
15
+ # summary still printed "all green". Worse, a step that WAS invoked could decide
16
+ # it lacked a prerequisite and exit 0 — Cortex with no
17
+ # `ENTWURF_ACP_CORTEX_CONNECTION` is the measured case — and the aggregate
18
+ # counted that as PASS. Both holes have the same shape: a skip that cannot be
19
+ # told from an acceptance. So the fix is one outcome protocol every step speaks,
20
+ # never a special case per backend or per prerequisite.
21
+ #
22
+ # EXIT CONTRACT (house style: the verdict IS the exit code)
23
+ # 0 PASS — the step ran and its assertions held.
24
+ # ENTWURF_STEP_SKIP_EXIT SKIP — the step declined a prerequisite it does
25
+ # not have. NOT acceptance, and never rounded up.
26
+ # anything else FAIL — including a crash or a signal death.
27
+ #
28
+ # WHY 97. The repo already spends 0..4 on per-tool exit contracts that mean
29
+ # different things per tool (meta-bridge-fresh-cut's 3 = "cut transition
30
+ # incomplete", meta-bridge-store-doctor's 3 = "unreadable store", dev-bin's 3 =
31
+ # "refused a foreign link"), and 126+ belongs to the shell. A LIVE smoke shells
32
+ # out to some of those tools, so reusing a low number would let a dependency's
33
+ # unrelated verdict surface as a false SKIP — the exact confusion this protocol
34
+ # exists to remove. 97 is reserved for THIS cross-tool protocol and nothing else.
35
+ ENTWURF_STEP_SKIP_EXIT=97
36
+
37
+ # Classify one step's exit code. Prints PASS | SKIP | FAIL on stdout.
38
+ entwurf_step_outcome() {
39
+ case "${1:-}" in
40
+ 0) printf 'PASS\n' ;;
41
+ "$ENTWURF_STEP_SKIP_EXIT") printf 'SKIP\n' ;;
42
+ *) printf 'FAIL\n' ;;
43
+ esac
44
+ }
45
+
46
+ # The release authority. Returns 0 when the run may be read as a cut floor.
47
+ #
48
+ # $1 MUST fail count $2 MUST skip count $3 cut mode (1 = a real cut)
49
+ #
50
+ # A FAIL always blocks. A SKIP blocks ONLY in cut mode: an ordinary diagnostic
51
+ # `./run.sh release-gate <dir>` must stay runnable unattended and still exit 0
52
+ # while honestly reporting what it did not call. `--cut` is the executable half
53
+ # of "a CUT needs LIVE=1, SKIP=0" — and it needs no separate LIVE assertion,
54
+ # because with LIVE unset every LIVE-gated step skips and the skip count blocks.
55
+ entwurf_release_releasable() {
56
+ local failc="${1:-0}" skipc="${2:-0}" cut="${3:-0}"
57
+ [ "$failc" -gt 0 ] && return 1
58
+ if [ "$cut" = "1" ] && [ "$skipc" -gt 0 ]; then
59
+ return 1
60
+ fi
61
+ return 0
62
+ }
63
+
64
+ # The same decision as ONE greppable token, because "why is this red" must be
65
+ # machine-readable and not parsed out of prose.
66
+ #
67
+ # $1 MUST fail count $2 MUST skip count $3 cut mode (1 = a real cut)
68
+ #
69
+ # A step that RAN AND BROKE and a step that NEVER RAN are different facts, and a
70
+ # release record that blurs them is worthless: the first is a defect to fix, the
71
+ # second is a prerequisite to supply. So the counters are never fudged either —
72
+ # a policy block keeps `FAIL=0 SKIP=n` and says `BLOCKED (MUST SKIP)`. Reporting
73
+ # a synthetic `FAIL=1` for the policy block would destroy exactly the
74
+ # distinction this protocol exists to create.
75
+ entwurf_release_verdict() {
76
+ local failc="${1:-0}" skipc="${2:-0}" cut="${3:-0}"
77
+ if [ "$failc" -gt 0 ]; then
78
+ printf 'cut: BLOCKED (MUST FAIL)\n'
79
+ elif [ "$cut" = "1" ] && [ "$skipc" -gt 0 ]; then
80
+ printf 'cut: BLOCKED (MUST SKIP)\n'
81
+ elif [ "$cut" = "1" ]; then
82
+ printf 'cut: OK\n'
83
+ elif [ "$skipc" -gt 0 ]; then
84
+ printf 'cut: n/a (diagnostic, %s SKIP)\n' "$skipc"
85
+ else
86
+ printf 'cut: n/a (diagnostic)\n'
87
+ fi
88
+ }
@@ -1207,14 +1207,12 @@ case "$REPO" in
1207
1207
  fi
1208
1208
  ;;
1209
1209
  esac
1210
- # prefixRoots operator policy (5d-4b): the shared env SSOT both v2 surfaces read. Display
1211
- # ONLY the parser SSOT lives in entwurf-v2-surface.ts (proven by check-entwurf-v2-surface);
1212
- # the doctor does NOT re-implement parsing. Unset no prefix promotion (the safe default).
1213
- if [ -z "${ENTWURF_PREFIX_ROOTS:-}" ]; then
1214
- ok "ENTWURF_PREFIX_ROOTS unset no prefix auto-approve (preflight default trust)"
1215
- else
1216
- ok "ENTWURF_PREFIX_ROOTS set → operator prefix-approve roots: $ENTWURF_PREFIX_ROOTS"
1217
- fi
1210
+ # NOT REPORTED: ENTWURF_PREFIX_ROOTS. It was the operator-policy SSOT that both v2 surfaces
1211
+ # read to feed the dispatch-path trust preflight, and that preflight guarded the resume verdict
1212
+ # so it left with `owned-outcome` in the visible-first cut. Nothing on the dispatch path reads
1213
+ # the variable now, and a doctor line saying "set → operator prefix-approve roots" would report a
1214
+ # knob that does nothing. Reporting an inert setting as live policy is the failure this doctor
1215
+ # exists to prevent, so the row is gone rather than softened.
1218
1216
 
1219
1217
  echo
1220
1218
  if [ "$fail" -eq 0 ]; then echo "meta-bridge doctor: PASS"; else echo "meta-bridge doctor: FAIL (see above)"; exit 1; fi
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * meta-facts — owner-normalized, read-only projection of the meta-record store (#65).
4
+ *
5
+ * The one join a consumer cannot make anywhere else — `(gardenId, nativeSessionId,
6
+ * transcriptPath)` — lives only in the meta-record store, and reading that store
7
+ * correctly means carrying the whole certification contract (strict v3 keyset,
8
+ * parse-before-uniqueness, no-winner duplicates, O_NOFOLLOW regular-file reads).
9
+ * External consumers that copied it decayed the day the contract moved. This surface
10
+ * makes THE owner emit the join: it is `listAllMetaIdentities` over the real store —
11
+ * the same listing `entwurf_peers` trusts — serialized verbatim. No parallel parser,
12
+ * no liveness, no socket paths, no transcript contents, no state, no watching.
13
+ *
14
+ * stdout (deterministic, 2-space indent, trailing newline):
15
+ * {
16
+ * "schemaVersion": 1, // of THIS projection, not of the records
17
+ * "storeDir": "/abs/path",
18
+ * "citizens": [ <full verbatim strict 9-field v3 records, sorted by gardenId> ],
19
+ * "defects": [ { "filename", "message" }, … sorted by filename ]
20
+ * }
21
+ *
22
+ * Defects ride IN-BAND: a readable store with uncertifiable entries is a fact to
23
+ * report, not a failure of this command — the healthy citizens are still true, and
24
+ * the defect list is exactly what `listAllMetaIdentities` refused and why (a rival
25
+ * pair is refused on BOTH sides; a schema-invalid record can never quarantine a
26
+ * healthy neighbour, because parse runs before uniqueness).
27
+ *
28
+ * EXIT CONTRACT — the verdict is the exit code (store-doctor precedent):
29
+ * 0 — store readable; the JSON above is on stdout (defects, if any, in-band).
30
+ * A store that does not exist is a readable EMPTY store (ENOENT only).
31
+ * 2 — usage error (bad argv).
32
+ * 3 — the store could not be READ (EACCES/ENOTDIR/…). No JSON is emitted:
33
+ * an unreadable host must never look like an empty one.
34
+ */
35
+
36
+ import { defaultMetaSessionsDir, listAllMetaIdentitiesDir } from "../pi-extensions/lib/meta-session.ts";
37
+
38
+ const arg = process.argv[2];
39
+ // A dash argv is a flag this command does not have, not a store directory — treating
40
+ // it as a path would answer `--help` with "empty store, exit 0", a silent wrong fact.
41
+ if (process.argv.length > 3 || (arg !== undefined && arg.startsWith("-"))) {
42
+ console.error("usage: entwurf meta-facts [meta-sessions-dir]");
43
+ process.exit(2);
44
+ }
45
+
46
+ let facts: ReturnType<typeof listAllMetaIdentitiesDir>;
47
+ try {
48
+ facts = listAllMetaIdentitiesDir(arg ?? defaultMetaSessionsDir());
49
+ } catch (err) {
50
+ console.error(`FAIL: ${err instanceof Error ? err.message : String(err)}`);
51
+ process.exit(3); // see the EXIT CONTRACT above: 3 = unreadable store, never an empty one
52
+ }
53
+
54
+ process.stdout.write(
55
+ `${JSON.stringify(
56
+ { schemaVersion: 1, storeDir: facts.dir, citizens: facts.identities, defects: facts.errors },
57
+ null,
58
+ 2,
59
+ )}\n`,
60
+ );
@@ -25,6 +25,112 @@
25
25
  "timeoutSeconds": 60,
26
26
  "signature": "[QK:AUGMENT-TRUNC-MARKER]",
27
27
  "signatureSource": "scripts/check-acp-carrier-augment.ts"
28
+ },
29
+ {
30
+ "claim": "CARRIER-PROVENANCE-STATED",
31
+ "title": "the augment stops saying what it is — the model is back to reading a first-user prepend as its system prompt (the measured 2026-07-30 misattribution)",
32
+ "subject": "pi-extensions/lib/acp/augment.ts",
33
+ "find": [
34
+ "\t\"This block is prepended to the FIRST USER MESSAGE of this session. It is not your system prompt.\";"
35
+ ],
36
+ "replace": ["\t\"\";"],
37
+ "gate": ["bash", "run.sh", "check-acp-carrier-augment"],
38
+ "timeoutSeconds": 60,
39
+ "signature": "[QK:CARRIER-PROVENANCE-STATED]",
40
+ "signatureSource": "scripts/check-acp-carrier-augment.ts"
41
+ },
42
+ {
43
+ "claim": "CARRIER-FRAME-NAMES-CLAUDE-CARRIER",
44
+ "title": "the claude frame stops naming its real engraving-only carrier — the model is told what its system prompt is NOT, but never what it IS",
45
+ "subject": "pi-extensions/lib/acp/augment.ts",
46
+ "find": [
47
+ "\t\t\t\t\t`The ${params.backend} rail does have a system-prompt carrier (\\`_meta.systemPrompt\\`), and entwurf keeps it deliberately tiny: it carries the operator engraving only — never this narrative, never AGENTS.md.`,"
48
+ ],
49
+ "replace": ["\t\t\t\t\t`The ${params.backend} rail carries no system-prompt carrier at all.`,"],
50
+ "gate": ["bash", "run.sh", "check-acp-carrier-augment"],
51
+ "timeoutSeconds": 60,
52
+ "signature": "[QK:CARRIER-FRAME-NAMES-CLAUDE-CARRIER]",
53
+ "signatureSource": "scripts/check-acp-carrier-augment.ts"
54
+ },
55
+ {
56
+ "claim": "CARRIER-FRAME-DENIES-CORTEX-CARRIER",
57
+ "title": "the carrier-less rail is told it has a tiny system-prompt carrier — a flat lie, since cortex's buildSessionMeta sends no _meta at all",
58
+ "subject": "pi-extensions/lib/acp/augment.ts",
59
+ "find": [
60
+ "\t\t\t\t\t`The ${params.backend} rail carries no system-prompt carrier at all — entwurf sends no \\`_meta.systemPrompt\\`, so everything entwurf tells you, including any operator engraving above, arrives here as user-message text.`,"
61
+ ],
62
+ "replace": [
63
+ "\t\t\t\t\t`The ${params.backend} rail does have a system-prompt carrier (\\`_meta.systemPrompt\\`), and entwurf keeps it deliberately tiny: it carries the operator engraving only.`,"
64
+ ],
65
+ "gate": ["bash", "run.sh", "check-acp-carrier-augment"],
66
+ "timeoutSeconds": 60,
67
+ "signature": "[QK:CARRIER-FRAME-DENIES-CORTEX-CARRIER]",
68
+ "signatureSource": "scripts/check-acp-carrier-augment.ts"
69
+ },
70
+ {
71
+ "claim": "CARRIER-RAIL-DIFF-IS-SOURCE-PINNED",
72
+ "title": "cortex grows a session carrier while the augment still tells it there is none — the frame becomes a lie and no prose check would notice",
73
+ "subject": "pi-extensions/lib/acp/backend-adapter.ts",
74
+ "find": ["\tbuildSessionMeta() {\n\t\treturn undefined;\n\t},"],
75
+ "replace": ["\tbuildSessionMeta() {\n\t\treturn { systemPrompt: \"x\" };\n\t},"],
76
+ "gate": ["bash", "run.sh", "check-acp-carrier-augment"],
77
+ "timeoutSeconds": 60,
78
+ "signature": "[QK:CARRIER-RAIL-DIFF-IS-SOURCE-PINNED]",
79
+ "signatureSource": "scripts/check-acp-carrier-augment.ts"
80
+ },
81
+ {
82
+ "claim": "CARRIER-LEADS-ITS-OWN-BLOCK",
83
+ "title": "the carrier loses its leading boundary — the Claude SDK's fixed sentence swallows the operator engraving again (the measured 2026-07-31 A-join)",
84
+ "subject": "pi-extensions/lib/acp/engraving.ts",
85
+ "find": ["export const CARRIER_LEAD_SEPARATOR = \"\\n\\n\";"],
86
+ "replace": ["export const CARRIER_LEAD_SEPARATOR = \"\";"],
87
+ "gate": ["bash", "run.sh", "check-acp-carrier-augment"],
88
+ "timeoutSeconds": 60,
89
+ "signature": "[QK:CARRIER-LEADS-ITS-OWN-BLOCK]",
90
+ "signatureSource": "scripts/check-acp-carrier-augment.ts"
91
+ },
92
+ {
93
+ "claim": "CARRIER-OPT-OUT-SURVIVES-SEPARATOR",
94
+ "title": "the boundary is attached before the emptiness test — an emptied engraving file stops being the opt-out and becomes a whitespace carrier that still replaces the claude_code preset",
95
+ "subject": "pi-extensions/lib/acp/engraving.ts",
96
+ "find": [
97
+ "\tconst body = interpolate(source, params).trim();",
98
+ "\tif (body.length === 0) return \"\";",
99
+ "\treturn `${CARRIER_LEAD_SEPARATOR}${body}`;"
100
+ ],
101
+ "replace": [
102
+ "\tconst body = `${CARRIER_LEAD_SEPARATOR}${interpolate(source, params).trim()}`;",
103
+ "\tif (body.length === 0) return \"\";",
104
+ "\treturn body;"
105
+ ],
106
+ "gate": ["bash", "run.sh", "check-acp-carrier-augment"],
107
+ "timeoutSeconds": 60,
108
+ "signature": "[QK:CARRIER-OPT-OUT-SURVIVES-SEPARATOR]",
109
+ "signatureSource": "scripts/check-acp-carrier-augment.ts"
110
+ },
111
+ {
112
+ "claim": "CARRIER-STAYS-TINY",
113
+ "title": "the shipped carrier grows into a real prompt — size, not shape, is what routes an OAuth subscription call to metered extra usage",
114
+ "subject": "pi-extensions/lib/acp/prompts/engraving.md",
115
+ "find": ["# Engraving Here"],
116
+ "replace": [
117
+ "# Engraving Here\n\n## Operator standing orders\n\n- Address the operator by name and keep the reply language matched to the prompt.\n- Prefer surgical edits over rewrites; verify every claim you make about behavior.\n- Never bypass a commit hook, and never push without an explicit request.\n- Indentation is tabs unless the project's linter says otherwise.\n- When docs and behavior disagree, report it and fix the source of truth.\n- Long-running commands belong in tmux, not in a foreground shell.\n- Record the next concrete move before you stop working.\n\n## Project context\n\nThis checkout is the bridge itself, so treat the adapter contract in the rail doc\nas authoritative and re-read it before touching a gate. The qualification lane is\nthe oracle for every contract claim; a green assertion count proves nothing on\nits own."
118
+ ],
119
+ "gate": ["bash", "run.sh", "check-acp-carrier-augment"],
120
+ "timeoutSeconds": 60,
121
+ "signature": "[QK:CARRIER-STAYS-TINY]",
122
+ "signatureSource": "scripts/check-acp-carrier-augment.ts"
123
+ },
124
+ {
125
+ "claim": "CARRIER-META-SENDS-CARRIER-VERBATIM",
126
+ "title": "the meta hop normalizes the carrier — the boundary is stripped back off on the wire while bridgeConfigSignature still folds the bounded value, so the A-join returns and reuse keys on a string the backend never received",
127
+ "subject": "pi-extensions/lib/acp/tool-surface.ts",
128
+ "find": ["\t\tmeta.systemPrompt = normalizedSystemPrompt;"],
129
+ "replace": ["\t\tmeta.systemPrompt = normalizedSystemPrompt.trim();"],
130
+ "gate": ["bash", "run.sh", "check-acp-carrier-augment"],
131
+ "timeoutSeconds": 60,
132
+ "signature": "[QK:CARRIER-META-SENDS-CARRIER-VERBATIM]",
133
+ "signatureSource": "scripts/check-acp-carrier-augment.ts"
28
134
  }
29
135
  ]
30
136
  }
@@ -142,13 +142,13 @@
142
142
  "\t\tawait setConfig.call(connection, { sessionId: acpSessionId, configId: \"model\", value: nativeModelId });",
143
143
  "\t},",
144
144
  "",
145
- "\t// A connection change must invalidate a reused session (§4/§7). Flat,"
145
+ "\t// A connection change must invalidate a reused session (rail: Adapter contract). Flat,"
146
146
  ],
147
147
  "replace": [
148
148
  "\t\tawait setConfig.call(connection, { sessionId: acpSessionId, configId: \"model\", value: \"auto\" });",
149
149
  "\t},",
150
150
  "",
151
- "\t// A connection change must invalidate a reused session (§4/§7). Flat,"
151
+ "\t// A connection change must invalidate a reused session (rail: Adapter contract). Flat,"
152
152
  ],
153
153
  "gate": ["bash", "run.sh", "check-acp-cortex"],
154
154
  "timeoutSeconds": 300,
@@ -0,0 +1,17 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "acp-overlay",
4
+ "mutants": [
5
+ {
6
+ "claim": "ACP-OVERLAY-UNATTENDED-BYPASS",
7
+ "title": "the isolated Claude overlay goes back to authoring permissions.defaultMode \"default\" — the overlay is the only settings authority the ACP child reads, so an unattended entwurf turn suspends on an interactive permission prompt nobody is there to answer, and the session stalls rather than fails",
8
+ "subject": "pi-extensions/lib/acp/overlay.ts",
9
+ "find": ["\t\t\tpermissions: { defaultMode: \"bypassPermissions\" },"],
10
+ "replace": ["\t\t\tpermissions: { defaultMode: \"default\" },"],
11
+ "gate": ["bash", "run.sh", "check-acp-overlay"],
12
+ "timeoutSeconds": 180,
13
+ "signature": "[QK:ACP-OVERLAY-UNATTENDED-BYPASS]",
14
+ "signatureSource": "scripts/check-acp-overlay.ts"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,100 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "acp-prompt-lifecycle",
4
+ "mutants": [
5
+ {
6
+ "claim": "PROMPT-STALL-NOT-KILLED",
7
+ "title": "an absolute wall clock re-planted on the in-flight prompt — a turn that is still working is cut for being long",
8
+ "subject": "pi-extensions/lib/acp/backend.ts",
9
+ "find": ["\t\treturn await Promise.race([session.connection.prompt(promptArgs), lifecycle]);"],
10
+ "replace": [
11
+ "\t\treturn await Promise.race([\n\t\t\tsession.connection.prompt(promptArgs),\n\t\t\tlifecycle,\n\t\t\tnew Promise<never>((_, reject) => setTimeout(() => reject(new Error(\"prompt timed out after 100ms\")), 100)),\n\t\t]);"
12
+ ],
13
+ "gate": ["bash", "run.sh", "check-acp-prompt-lifecycle"],
14
+ "timeoutSeconds": 180,
15
+ "signature": "[QK:PROMPT-STALL-NOT-KILLED]",
16
+ "signatureSource": "scripts/check-acp-prompt-lifecycle.ts"
17
+ },
18
+ {
19
+ "claim": "ABORT-SENDS-PROTOCOL-CANCEL",
20
+ "title": "abort stops asking the agent to cancel its own turn — back to killing a child mid-tool instead of the protocol ending",
21
+ "subject": "pi-extensions/lib/acp/backend.ts",
22
+ "find": ["\t\t\tsession.connection.cancel?.({ sessionId: promptArgs.sessionId });"],
23
+ "replace": ["\t\t\tvoid promptArgs;"],
24
+ "gate": ["bash", "run.sh", "check-acp-prompt-lifecycle"],
25
+ "timeoutSeconds": 180,
26
+ "signature": "[QK:ABORT-SENDS-PROTOCOL-CANCEL]",
27
+ "signatureSource": "scripts/check-acp-prompt-lifecycle.ts"
28
+ },
29
+ {
30
+ "claim": "ABORT-ESCALATION-BOUNDED",
31
+ "title": "the post-abort grace ignores the bound it was given — an abort against a wedged agent waits on a clock the caller does not control",
32
+ "subject": "pi-extensions/lib/acp/backend.ts",
33
+ "find": ["\t\tgraceTimer = setTimeout(escalateAbort, opts.graceMs);"],
34
+ "replace": ["\t\tgraceTimer = setTimeout(escalateAbort, 5_000);"],
35
+ "gate": ["bash", "run.sh", "check-acp-prompt-lifecycle"],
36
+ "timeoutSeconds": 180,
37
+ "signature": "[QK:ABORT-ESCALATION-BOUNDED]",
38
+ "signatureSource": "scripts/check-acp-prompt-lifecycle.ts"
39
+ },
40
+ {
41
+ "claim": "CHILD-EXIT-DIAGNOSED",
42
+ "title": "a mid-prompt child death stops reporting HOW it ended — the operator is back to an undiagnosable connection error",
43
+ "subject": "pi-extensions/lib/acp/backend.ts",
44
+ "find": [
45
+ "\tconst parts = [\n\t\texit.code !== null ? `exit code ${exit.code}` : undefined,\n\t\texit.signal ? `signal ${exit.signal}` : undefined,\n\t].filter(Boolean);\n\treturn parts.length > 0 ? parts.join(\", \") : \"no exit status\";"
46
+ ],
47
+ "replace": ["\treturn \"no exit status\";"],
48
+ "gate": ["bash", "run.sh", "check-acp-prompt-lifecycle"],
49
+ "timeoutSeconds": 180,
50
+ "signature": "[QK:CHILD-EXIT-DIAGNOSED]",
51
+ "signatureSource": "scripts/check-acp-prompt-lifecycle.ts"
52
+ },
53
+ {
54
+ "claim": "REUSE-CARRIES-CHILD-DIAGNOSTICS",
55
+ "title": "the reuse path drops the child's stderr tail again — exactly the live sonnet failure that showed only \"ACP connection closed\"",
56
+ "subject": "pi-extensions/lib/acp/backend.ts",
57
+ "find": ["\t\t\tfinishError(err, aborted, session.stderrTail);"],
58
+ "replace": ["\t\t\tfinishError(err, aborted);"],
59
+ "gate": ["bash", "run.sh", "check-acp-prompt-lifecycle"],
60
+ "timeoutSeconds": 180,
61
+ "signature": "[QK:REUSE-CARRIES-CHILD-DIAGNOSTICS]",
62
+ "signatureSource": "scripts/check-acp-prompt-lifecycle.ts"
63
+ },
64
+ {
65
+ "claim": "PROMPT-ERROR-NOT-TRANSIENT",
66
+ "title": "a prompt-phase lifecycle failure worded as a timeout — pi reclassifies it as transient and cold-replays the whole prompt",
67
+ "subject": "pi-extensions/lib/acp/backend.ts",
68
+ "find": ["\t\t\t\"this turn has no answer\","],
69
+ "replace": ["\t\t\t\"the prompt timed out\","],
70
+ "gate": ["bash", "run.sh", "check-acp-prompt-lifecycle"],
71
+ "timeoutSeconds": 180,
72
+ "signature": "[QK:PROMPT-ERROR-NOT-TRANSIENT]",
73
+ "signatureSource": "scripts/check-acp-prompt-lifecycle.ts"
74
+ },
75
+ {
76
+ "claim": "IDLE-DEATH-ANNOUNCED",
77
+ "title": "a backend session that died between turns is cleared but never announced — the next turn respawns silently and the operator never learns the session is gone",
78
+ "subject": "pi-extensions/lib/acp/backend.ts",
79
+ "find": [
80
+ "\t\t\tconst priorEnd = takeUnreportedChildEnd(sessionKey);\n\t\t\tif (priorEnd) {\n\t\t\t\tpushAcpLifecycleNotice(\n\t\t\t\t\tstate,\n\t\t\t\t\t`previous ${adapter.backend} session ended between turns (${describeChildEnd(priorEnd)}) — opening a new one`,\n\t\t\t\t);\n\t\t\t}"
81
+ ],
82
+ "replace": ["\t\t\ttakeUnreportedChildEnd(sessionKey);"],
83
+ "gate": ["bash", "run.sh", "check-acp-prompt-lifecycle"],
84
+ "timeoutSeconds": 180,
85
+ "signature": "[QK:IDLE-DEATH-ANNOUNCED]",
86
+ "signatureSource": "scripts/check-acp-prompt-lifecycle.ts"
87
+ },
88
+ {
89
+ "claim": "RETIRED-TEARDOWN-NOT-ANNOUNCED",
90
+ "title": "a teardown we performed ourselves is reported as a death — every ordinary turn-scoped turn cries wolf about its own cleanup",
91
+ "subject": "pi-extensions/lib/acp/backend.ts",
92
+ "find": ["\t} else if (!session.retiring) {"],
93
+ "replace": ["\t} else {"],
94
+ "gate": ["bash", "run.sh", "check-acp-prompt-lifecycle"],
95
+ "timeoutSeconds": 180,
96
+ "signature": "[QK:RETIRED-TEARDOWN-NOT-ANNOUNCED]",
97
+ "signatureSource": "scripts/check-acp-prompt-lifecycle.ts"
98
+ }
99
+ ]
100
+ }
@@ -0,0 +1,80 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "acp-stop-reason",
4
+ "mutants": [
5
+ {
6
+ "claim": "STOP-REFUSAL-NOT-SUCCESS",
7
+ "title": "a model refusal reported as a finished answer — the turn has no content, yet pi seals it done/stop",
8
+ "subject": "pi-extensions/lib/acp/backend.ts",
9
+ "find": [
10
+ "\t\tcase \"refusal\":\n\t\t\treturn {\n\t\t\t\tstopReason: \"error\",\n\t\t\t\trawStopReason: stopReason,\n\t\t\t\terrorMessage: \"ACP backend stopped with: refusal (the model declined to answer; the turn is incomplete)\",\n\t\t\t};"
11
+ ],
12
+ "replace": ["\t\tcase \"refusal\":\n\t\t\treturn { stopReason: \"stop\", rawStopReason: stopReason };"],
13
+ "gate": ["bash", "run.sh", "check-acp-stop-reason"],
14
+ "timeoutSeconds": 120,
15
+ "signature": "[QK:STOP-REFUSAL-NOT-SUCCESS]",
16
+ "signatureSource": "scripts/check-acp-stop-reason.ts"
17
+ },
18
+ {
19
+ "claim": "STOP-TURNBUDGET-NOT-SUCCESS",
20
+ "title": "an exhausted per-turn request budget reported as success — the answer is cut short but pi seals it done",
21
+ "subject": "pi-extensions/lib/acp/backend.ts",
22
+ "find": [
23
+ "\t\tcase \"max_turn_requests\":\n\t\t\treturn {\n\t\t\t\tstopReason: \"error\",\n\t\t\t\trawStopReason: stopReason,\n\t\t\t\terrorMessage:\n\t\t\t\t\t\"ACP backend stopped with: max_turn_requests (the backend's per-turn request budget was exhausted; the turn is incomplete)\",\n\t\t\t};"
24
+ ],
25
+ "replace": ["\t\tcase \"max_turn_requests\":\n\t\t\treturn { stopReason: \"stop\", rawStopReason: stopReason };"],
26
+ "gate": ["bash", "run.sh", "check-acp-stop-reason"],
27
+ "timeoutSeconds": 120,
28
+ "signature": "[QK:STOP-TURNBUDGET-NOT-SUCCESS]",
29
+ "signatureSource": "scripts/check-acp-stop-reason.ts"
30
+ },
31
+ {
32
+ "claim": "STOP-UNKNOWN-NOT-SUCCESS",
33
+ "title": "the collapse-to-stop default returns: any reason this build has never seen becomes a clean success",
34
+ "subject": "pi-extensions/lib/acp/backend.ts",
35
+ "find": [
36
+ "\t\tdefault:\n\t\t\treturn {\n\t\t\t\tstopReason: \"error\",\n\t\t\t\trawStopReason: stopReason,\n\t\t\t\terrorMessage: `ACP backend stopped with an unrecognized reason: ${stopReason}`,\n\t\t\t};"
37
+ ],
38
+ "replace": ["\t\tdefault:\n\t\t\treturn { stopReason: \"stop\", rawStopReason: stopReason };"],
39
+ "gate": ["bash", "run.sh", "check-acp-stop-reason"],
40
+ "timeoutSeconds": 120,
41
+ "signature": "[QK:STOP-UNKNOWN-NOT-SUCCESS]",
42
+ "signatureSource": "scripts/check-acp-stop-reason.ts"
43
+ },
44
+ {
45
+ "claim": "STOP-ABSENT-NOT-SUCCESS",
46
+ "title": "a prompt result carrying NO stop reason treated as a finished turn instead of an error",
47
+ "subject": "pi-extensions/lib/acp/backend.ts",
48
+ "find": [
49
+ "\t\tcase undefined:\n\t\t\treturn {\n\t\t\t\tstopReason: \"error\",\n\t\t\t\terrorMessage: \"ACP backend ended the turn without a stop reason\",\n\t\t\t};"
50
+ ],
51
+ "replace": ["\t\tcase undefined:\n\t\t\treturn { stopReason: \"stop\" };"],
52
+ "gate": ["bash", "run.sh", "check-acp-stop-reason"],
53
+ "timeoutSeconds": 120,
54
+ "signature": "[QK:STOP-ABSENT-NOT-SUCCESS]",
55
+ "signatureSource": "scripts/check-acp-stop-reason.ts"
56
+ },
57
+ {
58
+ "claim": "STOP-RAW-PRESERVED",
59
+ "title": "the raw ACP reason is dropped on the way out, so no consumer can see what the backend actually said",
60
+ "subject": "pi-extensions/lib/acp/backend.ts",
61
+ "find": ["\t\tif (verdict.rawStopReason !== undefined) state.output.rawStopReason = verdict.rawStopReason;\n"],
62
+ "replace": [""],
63
+ "gate": ["bash", "run.sh", "check-acp-stop-reason"],
64
+ "timeoutSeconds": 120,
65
+ "signature": "[QK:STOP-RAW-PRESERVED]",
66
+ "signatureSource": "scripts/check-acp-stop-reason.ts"
67
+ },
68
+ {
69
+ "claim": "STOP-PENDING-SEED",
70
+ "title": "the stream state seeds success: an in-flight turn already reads as a finished stop before any terminal arrives",
71
+ "subject": "pi-extensions/lib/acp/event-mapper.ts",
72
+ "find": ["\t\tstopReason: \"pending\","],
73
+ "replace": ["\t\tstopReason: \"stop\","],
74
+ "gate": ["bash", "run.sh", "check-acp-stop-reason"],
75
+ "timeoutSeconds": 120,
76
+ "signature": "[QK:STOP-PENDING-SEED]",
77
+ "signatureSource": "scripts/check-acp-stop-reason.ts"
78
+ }
79
+ ]
80
+ }