@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
@@ -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
+ }
@@ -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
  }
@@ -0,0 +1,196 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "acp-cortex",
4
+ "mutants": [
5
+ {
6
+ "claim": "CORTEX-CURATED-FOUR-ROWS",
7
+ "title": "dropping a curated row silently shrinks the GLG-decided 4-row cortex surface",
8
+ "subject": "pi-extensions/lib/acp/models.ts",
9
+ "find": [
10
+ "export const SUPPORTED_CORTEX_MODEL_IDS = [",
11
+ "\t\"cortex-auto\",",
12
+ "\t\"cortex-claude-opus-5\",",
13
+ "\t\"cortex-claude-sonnet-5\",",
14
+ "\t\"cortex-openai-gpt-5.4\",",
15
+ "] as const;"
16
+ ],
17
+ "replace": [
18
+ "export const SUPPORTED_CORTEX_MODEL_IDS = [",
19
+ "\t\"cortex-auto\",",
20
+ "\t\"cortex-claude-sonnet-5\",",
21
+ "\t\"cortex-openai-gpt-5.4\",",
22
+ "] as const;"
23
+ ],
24
+ "gate": ["bash", "run.sh", "check-acp-cortex"],
25
+ "timeoutSeconds": 300,
26
+ "signature": "[QK:CORTEX-CURATED-FOUR-ROWS]",
27
+ "signatureSource": "scripts/check-acp-cortex.ts"
28
+ },
29
+ {
30
+ "claim": "CORTEX-AUTH-NARROW-CREDENTIAL-CACHE",
31
+ "title": "widening the auth passthrough to the whole cortex/cache leaks operator tool_outputs/tip_history into the child",
32
+ "subject": "pi-extensions/lib/acp/overlay.ts",
33
+ "find": [
34
+ "\tcortexLinkIfExists(",
35
+ "\t\tjoin(realSnowflake, \"cortex\", \"cache\", \"credential_cache\"),",
36
+ "\t\tjoin(cortexDir, \"cache\", \"credential_cache\"),",
37
+ "\t);"
38
+ ],
39
+ "replace": [
40
+ "\trmSync(join(cortexDir, \"cache\"), { recursive: true, force: true });",
41
+ "\tcortexLinkIfExists(join(realSnowflake, \"cortex\", \"cache\"), join(cortexDir, \"cache\"));"
42
+ ],
43
+ "gate": ["bash", "run.sh", "check-acp-cortex"],
44
+ "timeoutSeconds": 300,
45
+ "signature": "[QK:CORTEX-AUTH-NARROW-CREDENTIAL-CACHE]",
46
+ "signatureSource": "scripts/check-acp-cortex.ts"
47
+ },
48
+ {
49
+ "claim": "CORTEX-AUTOUPDATE-OFF",
50
+ "title": "flipping autoUpdate back on lets the cortex runtime replace itself on launch mid-rail (D4)",
51
+ "subject": "pi-extensions/lib/acp/overlay.ts",
52
+ "find": ["\treturn `${JSON.stringify({ autoUpdate: false }, null, \"\\t\")}\\n`;"],
53
+ "replace": ["\treturn `${JSON.stringify({ autoUpdate: true }, null, \"\\t\")}\\n`;"],
54
+ "gate": ["bash", "run.sh", "check-acp-cortex"],
55
+ "timeoutSeconds": 300,
56
+ "signature": "[QK:CORTEX-AUTOUPDATE-OFF]",
57
+ "signatureSource": "scripts/check-acp-cortex.ts"
58
+ },
59
+ {
60
+ "claim": "CORTEX-BRIDGE-DUAL-HOME",
61
+ "title": "dropping the bridge-only real-HOME restore leaves the garden store cut off — tools reach the model but see an empty garden (D10)",
62
+ "subject": "pi-extensions/lib/acp/overlay.ts",
63
+ "find": ["\t\tif (stdio.name === CORTEX_DUAL_HOME_BRIDGE_SERVER) env.HOME = realHome;"],
64
+ "replace": ["\t\tvoid realHome;"],
65
+ "gate": ["bash", "run.sh", "check-acp-cortex"],
66
+ "timeoutSeconds": 300,
67
+ "signature": "[QK:CORTEX-BRIDGE-DUAL-HOME]",
68
+ "signatureSource": "scripts/check-acp-cortex.ts"
69
+ },
70
+ {
71
+ "claim": "CORTEX-MCP-NON-STDIO-FAILLOUD",
72
+ "title": "silently dropping a non-stdio server advertises a config the session does not have (D9 fail-loud)",
73
+ "subject": "pi-extensions/lib/acp/overlay.ts",
74
+ "find": [
75
+ "\t\tif (\"type\" in server && (server.type === \"http\" || server.type === \"sse\")) {",
76
+ "\t\t\tthrow new Error(",
77
+ "\t\t\t\t`entwurf: cortex mcp.json projection cannot represent ${server.type} server ` +",
78
+ "\t\t\t\t\t`${JSON.stringify(server.name)} — only stdio entries are measured through ` +",
79
+ "\t\t\t\t\t`$SNOWFLAKE_HOME/cortex/mcp.json (CP0 D9). Remove it from entwurfProvider.mcpServers ` +",
80
+ "\t\t\t\t\t`for cortex models or front it with a stdio bridge.`,",
81
+ "\t\t\t);",
82
+ "\t\t}"
83
+ ],
84
+ "replace": [
85
+ "\t\tif (\"type\" in server && (server.type === \"http\" || server.type === \"sse\")) {",
86
+ "\t\t\tcontinue;",
87
+ "\t\t}"
88
+ ],
89
+ "gate": ["bash", "run.sh", "check-acp-cortex"],
90
+ "timeoutSeconds": 300,
91
+ "signature": "[QK:CORTEX-MCP-NON-STDIO-FAILLOUD]",
92
+ "signatureSource": "scripts/check-acp-cortex.ts"
93
+ },
94
+ {
95
+ "claim": "CORTEX-OVERLAY-SESSION-SCOPED",
96
+ "title": "a static shared overlay dir lets two residents overwrite one mcp.json/envelope",
97
+ "subject": "pi-extensions/lib/acp/overlay.ts",
98
+ "find": ["\tconst scopeDir = join(overlaysRoot, cortexOverlayScopeId(params.scopeKey));"],
99
+ "replace": ["\tconst scopeDir = join(overlaysRoot, \"shared\");"],
100
+ "gate": ["bash", "run.sh", "check-acp-cortex"],
101
+ "timeoutSeconds": 300,
102
+ "signature": "[QK:CORTEX-OVERLAY-SESSION-SCOPED]",
103
+ "signatureSource": "scripts/check-acp-cortex.ts"
104
+ },
105
+ {
106
+ "claim": "CORTEX-HOME-PRESENCE-REFUSED",
107
+ "title": "a truthy check instead of presence lets an empty-string CORTEX_HOME slip past the refusal (D3 E2b nuance)",
108
+ "subject": "pi-extensions/lib/acp/backend-adapter.ts",
109
+ "find": ["\t\tif (\"CORTEX_HOME\" in process.env) {"],
110
+ "replace": ["\t\tif (process.env.CORTEX_HOME) {"],
111
+ "gate": ["bash", "run.sh", "check-acp-cortex"],
112
+ "timeoutSeconds": 300,
113
+ "signature": "[QK:CORTEX-HOME-PRESENCE-REFUSED]",
114
+ "signatureSource": "scripts/check-acp-cortex.ts"
115
+ },
116
+ {
117
+ "claim": "CORTEX-LAUNCH-NO-MODEL-PIN",
118
+ "title": "re-introducing the launch -m pin creates a second model authority that drifts from per-turn set-model",
119
+ "subject": "pi-extensions/lib/acp/backend-adapter.ts",
120
+ "find": [
121
+ "\tresolveLaunch({ config }) {",
122
+ "\t\tconst settings = config.adapterSettings as CortexAdapterSettings | undefined;",
123
+ "\t\tconst connection = settings?.cortexConnection?.trim() || undefined;",
124
+ "\t\tconst selectionArgs: string[] = [];"
125
+ ],
126
+ "replace": [
127
+ "\tresolveLaunch({ config, nativeModelId }) {",
128
+ "\t\tconst settings = config.adapterSettings as CortexAdapterSettings | undefined;",
129
+ "\t\tconst connection = settings?.cortexConnection?.trim() || undefined;",
130
+ "\t\tconst selectionArgs: string[] = [\"-m\", nativeModelId];"
131
+ ],
132
+ "gate": ["bash", "run.sh", "check-acp-cortex"],
133
+ "timeoutSeconds": 300,
134
+ "signature": "[QK:CORTEX-LAUNCH-NO-MODEL-PIN]",
135
+ "signatureSource": "scripts/check-acp-cortex.ts"
136
+ },
137
+ {
138
+ "claim": "CORTEX-ENFORCE-SET-MODEL",
139
+ "title": "enforcing a constant instead of the native id silently answers with the wrong model (CP0-M)",
140
+ "subject": "pi-extensions/lib/acp/backend-adapter.ts",
141
+ "find": [
142
+ "\t\tawait setConfig.call(connection, { sessionId: acpSessionId, configId: \"model\", value: nativeModelId });",
143
+ "\t},",
144
+ "",
145
+ "\t// A connection change must invalidate a reused session (rail: Adapter contract). Flat,"
146
+ ],
147
+ "replace": [
148
+ "\t\tawait setConfig.call(connection, { sessionId: acpSessionId, configId: \"model\", value: \"auto\" });",
149
+ "\t},",
150
+ "",
151
+ "\t// A connection change must invalidate a reused session (rail: Adapter contract). Flat,"
152
+ ],
153
+ "gate": ["bash", "run.sh", "check-acp-cortex"],
154
+ "timeoutSeconds": 300,
155
+ "signature": "[QK:CORTEX-ENFORCE-SET-MODEL]",
156
+ "signatureSource": "scripts/check-acp-cortex.ts"
157
+ },
158
+ {
159
+ "claim": "CORTEX-OVERLAY-KEY-IS-SESSION-KEY",
160
+ "title": "re-deriving the overlay scope from ambient env drops opts.sessionId — two same-process/cwd sessions alias one overlay/mcp.json",
161
+ "subject": "pi-extensions/lib/acp/backend-adapter.ts",
162
+ "find": ["\t\tconst overlay = ensureCortexDualHomeOverlay({", "\t\t\tscopeKey: sessionKey,"],
163
+ "replace": ["\t\tconst overlay = ensureCortexDualHomeOverlay({", "\t\t\tscopeKey: piSessionId ?? \"static\","],
164
+ "gate": ["bash", "run.sh", "check-acp-cortex"],
165
+ "timeoutSeconds": 300,
166
+ "signature": "[QK:CORTEX-OVERLAY-KEY-IS-SESSION-KEY]",
167
+ "signatureSource": "scripts/check-acp-cortex.ts"
168
+ },
169
+ {
170
+ "claim": "ACP-OVERLAY-SESSIONKEY-WIRED",
171
+ "title": "passing a cwd-derived key instead of resolveSessionKey's value silently merges distinct sessions' overlay scopes at the production call site",
172
+ "subject": "pi-extensions/lib/acp/backend.ts",
173
+ "find": [
174
+ "\t\t\tconst overlay = adapter.ensureOverlay({ cwd, modelId: model.id, nativeModelId, config, sessionKey });"
175
+ ],
176
+ "replace": [
177
+ "\t\t\tconst overlay = adapter.ensureOverlay({ cwd, modelId: model.id, nativeModelId, config, sessionKey: `cwd:${cwd}` });"
178
+ ],
179
+ "gate": ["bash", "run.sh", "check-acp-session-reuse"],
180
+ "timeoutSeconds": 300,
181
+ "signature": "[QK:ACP-OVERLAY-SESSIONKEY-WIRED]",
182
+ "signatureSource": "scripts/check-acp-session-reuse.ts"
183
+ },
184
+ {
185
+ "claim": "CORTEX-PROVIDER-SIX-ROW-SURFACE",
186
+ "title": "filtering the cortex rows out of the REAL provider entry drops the whole backend from the operator's model list while models.ts and check-acp-cortex stay green",
187
+ "subject": "pi-extensions/acp-provider.ts",
188
+ "find": ["\t\tmodels: allCuratedModels(),"],
189
+ "replace": ["\t\tmodels: allCuratedModels().filter((m) => !m.id.startsWith(\"cortex-\")),"],
190
+ "gate": ["bash", "run.sh", "check-acp-provider-surface"],
191
+ "timeoutSeconds": 300,
192
+ "signature": "[QK:CORTEX-PROVIDER-SIX-ROW-SURFACE]",
193
+ "signatureSource": "scripts/check-acp-provider-surface.ts"
194
+ }
195
+ ]
196
+ }
@@ -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
+ }