@junghanacs/entwurf 0.23.1 → 0.25.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.
- package/BASELINE.md +1 -0
- package/CHANGELOG.md +628 -0
- package/DELIVERY.md +24 -1
- package/FAQ.md +47 -0
- package/README.md +5 -3
- package/VERIFY.md +20 -2
- package/demo/README.md +5 -2
- package/docs/acp-backend-rail.md +49 -2
- package/docs/setup-clean-host.md +8 -7
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +38 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +19 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/callback-env.js +68 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-send-receipt.js +101 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +17 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +4 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +53 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +38 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +54 -22
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +20 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +24 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +19 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +35 -42
- package/mcp/entwurf-bridge/dist/protocol.js +0 -7
- package/mcp/entwurf-bridge/dist/scripts/doctor-pi-provider.js +40 -3
- package/mcp/entwurf-bridge/dist/scripts/herdr-rails.mjs +72 -0
- package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
- package/mcp/entwurf-bridge/src/index.ts +46 -5
- package/mcp/entwurf-bridge/test.sh +2 -2
- package/package.json +11 -9
- package/pi-extensions/entwurf-control.ts +83 -154
- package/pi-extensions/lib/acp/backend.ts +16 -6
- package/pi-extensions/lib/acp/context.ts +30 -10
- package/pi-extensions/lib/acp/session-store.ts +2 -2
- package/pi-extensions/lib/callback-env.ts +78 -0
- package/pi-extensions/lib/compaction-send-guard.ts +6 -1
- package/pi-extensions/lib/control-send-receipt.ts +108 -0
- package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
- package/pi-extensions/lib/entwurf-v2-contract.ts +6 -2
- package/pi-extensions/lib/entwurf-v2-production.ts +19 -1
- package/pi-extensions/lib/entwurf-v2-runner.ts +23 -1
- package/pi-extensions/lib/entwurf-v2-send.ts +82 -6
- package/pi-extensions/lib/entwurf-v2-surface.ts +42 -3
- package/pi-extensions/lib/fresh-call-composition.ts +56 -22
- package/pi-extensions/lib/herdr-fresh-call.ts +25 -7
- package/pi-extensions/lib/herdr-placement.ts +24 -4
- package/pi-extensions/lib/mux-fresh-call.ts +23 -8
- package/pi-extensions/meta-bridge-omp.ts +35 -48
- package/protocol.js +0 -8
- package/run.sh +306 -53
- package/scripts/check-acp-backend-preflight.ts +117 -9
- package/scripts/check-acp-carrier-augment.ts +6 -4
- package/scripts/check-acp-event-mapper.ts +17 -6
- package/scripts/check-acp-prompt-builder.ts +17 -9
- package/scripts/check-acp-prompt-lifecycle.ts +23 -6
- package/scripts/check-acp-session-reuse.ts +64 -51
- package/scripts/check-acp-session-store.ts +31 -8
- package/scripts/check-acp-stop-reason.ts +20 -2
- package/scripts/check-acp-stream-hooks.ts +22 -5
- package/scripts/check-acp-usage-accounting.ts +33 -9
- package/scripts/check-entwurf-bridge-boot.ts +16 -1
- package/scripts/check-entwurf-v2-surface.ts +289 -10
- package/scripts/check-fresh-call-dispatch.ts +4 -5
- package/scripts/check-gate-qualification.ts +51 -9
- package/scripts/check-herdr-activation.ts +73 -0
- package/scripts/check-herdr-fresh-call.ts +30 -6
- package/scripts/check-herdr-placement.ts +51 -0
- package/scripts/check-herdr-plugin-build.ts +11 -4
- package/scripts/check-herdr-plugin.ts +745 -38
- package/scripts/check-herdr-sandbox.ts +14 -2
- package/scripts/check-install-container.sh +1 -1
- package/scripts/check-mux-launcher-fence.ts +37 -0
- package/scripts/check-pi-launch.ts +8 -2
- package/scripts/check-release-gate-outcomes.ts +165 -0
- package/scripts/doctor-pi-provider.ts +44 -4
- package/scripts/herdr-rails.mjs +75 -0
- package/scripts/lib/mutation-qualify.ts +1 -1
- package/scripts/meta-bridge-state.py +18 -1
- package/scripts/mutants/acp-backend-preflight.json +20 -0
- package/scripts/mutants/bridge-boot-resume.json +26 -0
- package/scripts/mutants/bridge-command-boot.json +38 -0
- package/scripts/mutants/control-send-receipt.json +235 -0
- package/scripts/mutants/herdr-activation.json +16 -0
- package/scripts/mutants/herdr-fresh-call.json +15 -4
- package/scripts/mutants/herdr-plugin-build.json +7 -11
- package/scripts/mutants/herdr-plugin.json +177 -9
- package/scripts/mutants/meta-retire.json +22 -0
- package/scripts/mutants/mux-fresh-call.json +21 -8
- package/scripts/mutants/omp-fresh.json +5 -9
- package/scripts/mutants/pack-install.json +4 -4
- package/scripts/mutants/pi-floor.json +17 -0
- package/scripts/mutants/release-gate.json +48 -0
- package/scripts/mutants/retired-sent-surface.json +32 -0
- package/scripts/mutants/v2-surface.json +7 -7
- package/scripts/probe-bridge-command.ts +1 -0
- package/scripts/smoke-acp-session-reuse-live.ts +23 -5
- package/scripts/smoke-agy-install-state.sh +2 -2
- package/scripts/smoke-codex-fresh-live.ts +94 -13
- package/scripts/smoke-copilot-mcp-state.sh +1 -1
- package/scripts/smoke-herdr-fresh-call-live.ts +71 -36
- package/scripts/smoke-herdr-raw-install-live.sh +90 -5
- package/scripts/smoke-meta-install-state.sh +107 -1
- package/scripts/smoke-mux-lifecycle-live.ts +9 -2
- package/scripts/smoke-omp-fresh-live.ts +4 -2
- package/scripts/smoke-omp-mcp-state.sh +1 -1
- package/scripts/smoke-pi-provider-state.sh +108 -2
- package/scripts/smoke-setup-verdict.sh +18 -2
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"title": "claude-code goes back to the variadic space form, which swallows the prompt as an option value",
|
|
19
19
|
"subject": "pi-extensions/lib/fresh-call-composition.ts",
|
|
20
20
|
"find": [
|
|
21
|
-
"\t\t\treturn [
|
|
21
|
+
"\t\t\treturn [\n\t\t\t\tcomposition.prompt,\n\t\t\t\t`--allowedTools=${FRESH_CALL_CALLBACK_TOOL[\"claude-code\"]},${FRESH_CALL_DELIVERY_TOOL[\"claude-code\"]}`,\n\t\t\t\t`--model=${model}`,\n\t\t\t];"
|
|
22
22
|
],
|
|
23
23
|
"replace": [
|
|
24
24
|
"\t\t\treturn [composition.prompt, \"--allowedTools\", FRESH_CALL_CALLBACK_TOOL[\"claude-code\"], `--model=${model}`];"
|
|
@@ -55,9 +55,11 @@
|
|
|
55
55
|
"title": "Claude Code drops the explicit model token and silently resolves its ambient Opus default again",
|
|
56
56
|
"subject": "pi-extensions/lib/fresh-call-composition.ts",
|
|
57
57
|
"find": [
|
|
58
|
-
"\t\t\treturn [
|
|
58
|
+
"\t\t\treturn [\n\t\t\t\tcomposition.prompt,\n\t\t\t\t`--allowedTools=${FRESH_CALL_CALLBACK_TOOL[\"claude-code\"]},${FRESH_CALL_DELIVERY_TOOL[\"claude-code\"]}`,\n\t\t\t\t`--model=${model}`,\n\t\t\t];"
|
|
59
|
+
],
|
|
60
|
+
"replace": [
|
|
61
|
+
"\t\t\treturn [\n\t\t\t\tcomposition.prompt,\n\t\t\t\t`--allowedTools=${FRESH_CALL_CALLBACK_TOOL[\"claude-code\"]},${FRESH_CALL_DELIVERY_TOOL[\"claude-code\"]}`,\n\t\t\t];"
|
|
59
62
|
],
|
|
60
|
-
"replace": ["\t\t\treturn [composition.prompt, `--allowedTools=${FRESH_CALL_CALLBACK_TOOL[\"claude-code\"]}`];"],
|
|
61
63
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
62
64
|
"timeoutSeconds": 120,
|
|
63
65
|
"signature": "[QK:FRESHCALL-CLAUDE-MODEL-ARGV]",
|
|
@@ -67,8 +69,8 @@
|
|
|
67
69
|
"claim": "FRESHCALL-CALLBACK-PRECEDES-TASK",
|
|
68
70
|
"title": "the task is framed before the callback instruction, so a sibling that stalls in the task never names itself",
|
|
69
71
|
"subject": "pi-extensions/lib/fresh-call-composition.ts",
|
|
70
|
-
"find": ["\t\t`FIRST ACTION, before reading files or anything else: call ${
|
|
71
|
-
"replace": ["\t\t`FINAL ACTION, after completing the task: call ${
|
|
72
|
+
"find": ["\t\t\t\t\t`FIRST ACTION, before reading files or anything else: call ${birthTool} with no arguments.`"],
|
|
73
|
+
"replace": ["\t\t\t\t\t`FINAL ACTION, after completing the task: call ${birthTool} with no arguments.`"],
|
|
72
74
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
73
75
|
"timeoutSeconds": 120,
|
|
74
76
|
"signature": "[QK:FRESHCALL-CALLBACK-PRECEDES-TASK]",
|
|
@@ -275,7 +277,7 @@
|
|
|
275
277
|
"claim": "FRESHCALL-COPILOT-CALLBACK-TOOL",
|
|
276
278
|
"title": "the Copilot callback names Claude Code's `mcp__server__tool` spelling — a dialect copied instead of measured, and the whole first turn is spent looking for a tool that is not there",
|
|
277
279
|
"subject": "pi-extensions/lib/fresh-call-composition.ts",
|
|
278
|
-
"find": ["\tcopilot: \"entwurf-bridge-
|
|
280
|
+
"find": ["\tcopilot: \"entwurf-bridge-entwurf_callback\","],
|
|
279
281
|
"replace": ["\tcopilot: \"mcp__entwurf-bridge__entwurf_v2\","],
|
|
280
282
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
281
283
|
"timeoutSeconds": 180,
|
|
@@ -669,7 +671,7 @@
|
|
|
669
671
|
"title": "the first turn stops naming where the result goes, so a sibling finishes its task, prints the answer in its own window, and the caller never learns it is done",
|
|
670
672
|
"subject": "pi-extensions/lib/fresh-call-composition.ts",
|
|
671
673
|
"find": [
|
|
672
|
-
"\t\t`When the task reaches its requested final result, send that result to the same target with ${
|
|
674
|
+
"\t\t`When the task reaches its requested final result, send that result to the same target with ${deliveryTool}.`,"
|
|
673
675
|
],
|
|
674
676
|
"replace": [""],
|
|
675
677
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
@@ -682,13 +684,24 @@
|
|
|
682
684
|
"title": "the Claude child loses the one sentence that tells it how to load a deferred callback tool — the framing goes back to naming a tool the child cannot call on its first turn, which is the measured silence: 0/5 directly callable, 5 of 14 production launches dead",
|
|
683
685
|
"subject": "pi-extensions/lib/fresh-call-composition.ts",
|
|
684
686
|
"find": [
|
|
685
|
-
"\t\"claude-code\": [\n\t\t\"If ${callbackTool} is not callable yet, the entwurf-bridge server is still connecting or its\",\n\t\t'tools are deferred: load them first with ToolSearch(\"select:${callbackTool},${peersTool}\") —',\n\t\t\"it waits for the server — then call ${callbackTool}.\",\n\t],"
|
|
687
|
+
"\t\"claude-code\": [\n\t\t\"If ${callbackTool} is not callable yet, the entwurf-bridge server is still connecting or its\",\n\t\t'tools are deferred: load them first with ToolSearch(\"select:${callbackTool},${deliveryTool},${peersTool}\") —',\n\t\t\"it waits for the server — then call ${callbackTool}.\",\n\t],"
|
|
686
688
|
],
|
|
687
689
|
"replace": ["\t\"claude-code\": [],"],
|
|
688
690
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
689
691
|
"timeoutSeconds": 300,
|
|
690
692
|
"signature": "[QK:FRESHCOMP-TOOL-LOAD-HINT-CLAUDE-ONLY]",
|
|
691
693
|
"signatureSource": "test/fresh-call-composition.contract.test.ts"
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
"claim": "FRESHCALL-CALLBACK-ENV",
|
|
697
|
+
"title": "the launcher-computed callback pair is dropped from tmux -e, so the sibling's first action has to retype a garden id out of prose — Hard Rule 2",
|
|
698
|
+
"subject": "pi-extensions/lib/mux-fresh-call.ts",
|
|
699
|
+
"find": ["\t\t...callbackEnvAssignments(callback).flatMap((assignment) => [\"-e\", assignment]),"],
|
|
700
|
+
"replace": [""],
|
|
701
|
+
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
702
|
+
"timeoutSeconds": 120,
|
|
703
|
+
"signature": "[QK:FRESHCALL-CALLBACK-ENV]",
|
|
704
|
+
"signatureSource": "test/mux-fresh-call.test.ts"
|
|
692
705
|
}
|
|
693
706
|
]
|
|
694
707
|
}
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"claim": "FRESHCALL-OMP-CALLBACK-TOOL-DIALECT",
|
|
46
46
|
"title": "the callback tool name spelled the way a reader expects instead of the way omp mints it — `entwurf_v2` keeps its digit, which omp's sanitizer charset [a-z_] eats. The sibling is told to call a tool that does not exist in its session, so the whole first turn is spent and no address ever comes back. A launch receipt still prints",
|
|
47
47
|
"subject": "pi-extensions/lib/fresh-call-composition.ts",
|
|
48
|
-
"find": ["\tomp: \"
|
|
48
|
+
"find": ["\tomp: \"mcp__entwurf_bridge_entwurf_callback\","],
|
|
49
49
|
"replace": ["\tomp: \"mcp__entwurf_bridge_entwurf_v2\","],
|
|
50
50
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
51
51
|
"timeoutSeconds": 300,
|
|
@@ -167,10 +167,8 @@
|
|
|
167
167
|
"claim": "OMP-BOOTSTRAP-CALLBACK-ONLY",
|
|
168
168
|
"title": "the task folded back into the stage-one prompt, so the sibling is asked to call back AND to work in one message. This is the prompt the failed LIVE actually ran, minus the missing tool: a competing goal beside the callback is what the two-stage split exists to remove, and nothing about argv or readiness would notice it coming back",
|
|
169
169
|
"subject": "pi-extensions/meta-bridge-omp.ts",
|
|
170
|
-
"find": ["\t\t\tsend(buildOmpCallbackOnlyPrompt(
|
|
171
|
-
"replace": [
|
|
172
|
-
"\t\t\tsend(`${buildOmpCallbackOnlyPrompt({ target: payload.target, nonce: payload.nonce })}\\n\\n${payload.task}`);"
|
|
173
|
-
],
|
|
170
|
+
"find": ["\t\t\tsend(buildOmpCallbackOnlyPrompt());"],
|
|
171
|
+
"replace": ["\t\t\tsend(`${buildOmpCallbackOnlyPrompt()}\\n\\n${payload.task}`);"],
|
|
174
172
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
175
173
|
"timeoutSeconds": 300,
|
|
176
174
|
"signature": "[QK:OMP-BOOTSTRAP-CALLBACK-ONLY]",
|
|
@@ -204,11 +202,9 @@
|
|
|
204
202
|
"claim": "OMP-BOOTSTRAP-RESULT-ARMS-ONLY",
|
|
205
203
|
"title": "the task sent from inside the tool_result handler again, instead of at the turn_end boundary. `[LIVE 2026-08-30]` this is the exact shape that failed: the hook log showed armed/ready/callback-observed/released, the release predicate was provably correct, and the task still never appeared in the session — because that handler runs while the callback turn is streaming. Every static contract about correlation and readiness stays green while the product delivers nothing",
|
|
206
204
|
"subject": "pi-extensions/meta-bridge-omp.ts",
|
|
207
|
-
"find": [
|
|
208
|
-
"\t\t\tlog(\"INFO\", `bootstrap-released nonce=${payload.nonce} toolCallId=${callId}: task armed for the next turn_end`);"
|
|
209
|
-
],
|
|
205
|
+
"find": ["\t\t\tlog(\"INFO\", `bootstrap-released toolCallId=${callId}: task armed for the next turn_end`);"],
|
|
210
206
|
"replace": [
|
|
211
|
-
"\t\t\tlog(\"INFO\", `bootstrap-released
|
|
207
|
+
"\t\t\tlog(\"INFO\", `bootstrap-released toolCallId=${callId}: task armed for the next turn_end`);\n\t\t\tphase = \"task-sent\";\n\t\t\tsend(payload.task);"
|
|
212
208
|
],
|
|
213
209
|
"gate": ["bash", "run.sh", "check-mux-fresh-call"],
|
|
214
210
|
"timeoutSeconds": 300,
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"claim": "PACK-INSTALL-PIN-MATCHER-BOUNDED",
|
|
7
7
|
"title": "the pin-leak matcher loses its version boundary and blesses substring lookalikes — @0.85.10 reads as the pinned 0.85.1 and the gate announces a verified tree it never verified (independent review, 2026-08-25)",
|
|
8
8
|
"subject": "run.sh",
|
|
9
|
-
"find": [" grep '^@earendil-works+' | grep -Ev '@0\\.
|
|
10
|
-
"replace": [" grep '^@earendil-works+' | grep -v '@0\\.
|
|
9
|
+
"find": [" grep '^@earendil-works+' | grep -Ev '@0\\.87\\.1(_|$)' || true"],
|
|
10
|
+
"replace": [" grep '^@earendil-works+' | grep -v '@0\\.87\\.0' || true"],
|
|
11
11
|
"gate": ["bash", "run.sh", "check-pack-pin-matcher"],
|
|
12
12
|
"timeoutSeconds": 120,
|
|
13
13
|
"signature": "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED]",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"claim": "PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE",
|
|
18
18
|
"title": "the pin-leak matcher narrows back to the pi-* families and goes blind to a non-pi member of the same runtime closure — @earendil-works/chord (new in pi 0.85.0, a runtime dependency of pi-coding-agent/pi-agent-core/pi-client/pi-protocol) floats unpinned while the gate prints a verified pin (measured 2026-09-06 on a real 0.85.1 install tree)",
|
|
19
19
|
"subject": "run.sh",
|
|
20
|
-
"find": [" grep '^@earendil-works+' | grep -Ev '@0\\.
|
|
21
|
-
"replace": [" grep '^@earendil-works+pi-' | grep -Ev '@0\\.
|
|
20
|
+
"find": [" grep '^@earendil-works+' | grep -Ev '@0\\.87\\.1(_|$)' || true"],
|
|
21
|
+
"replace": [" grep '^@earendil-works+pi-' | grep -Ev '@0\\.87\\.1(_|$)' || true"],
|
|
22
22
|
"gate": ["bash", "run.sh", "check-pack-pin-matcher"],
|
|
23
23
|
"timeoutSeconds": 120,
|
|
24
24
|
"signature": "[QK:PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE]",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"lane": "pi-floor",
|
|
4
|
+
"mutants": [
|
|
5
|
+
{
|
|
6
|
+
"claim": "PI-FLOOR-SINGLE-SOURCE",
|
|
7
|
+
"title": "one row of the pnpm workspace closure is left on the previous floor while every other surface moved — the shape a version bump actually fails in. Nothing else reads that file, so the stranded row surfaces only at install time, in a different command, far from the change that stranded it",
|
|
8
|
+
"subject": "pnpm-workspace.yaml",
|
|
9
|
+
"find": [" - '@earendil-works/pi-telemetry@0.85.0 || 0.85.1 || 0.86.0 || 0.87.0 || 0.87.1'"],
|
|
10
|
+
"replace": [" - '@earendil-works/pi-telemetry@0.85.0 || 0.85.1 || 0.86.0'"],
|
|
11
|
+
"gate": ["bash", "run.sh", "check-dep-versions"],
|
|
12
|
+
"timeoutSeconds": 300,
|
|
13
|
+
"signature": "[QK:PI-FLOOR-SINGLE-SOURCE]",
|
|
14
|
+
"signatureSource": "run.sh"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -227,6 +227,54 @@
|
|
|
227
227
|
"timeoutSeconds": 180,
|
|
228
228
|
"signature": "[QK:CODEX-FIRST-ADMISSION-IS-RELEASE-MUST]",
|
|
229
229
|
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"claim": "CODEX-OPERATOR-PREREQ-ABSENT-SKIPS",
|
|
233
|
+
"title": "a constant-false guard leaves the decline arm as dead code, so every absent operator input falls through to the assertions and exits FAIL again — the shape that made this MUST step red inside every cut and would require a per-cut exception",
|
|
234
|
+
"subject": "scripts/smoke-codex-fresh-live.ts",
|
|
235
|
+
"find": [
|
|
236
|
+
"\tconst absentInputs = OPERATOR_INPUTS.filter(([name]) => (process.env[name]?.trim() ?? \"\") === \"\");",
|
|
237
|
+
"\tif (absentInputs.length > 0) {"
|
|
238
|
+
],
|
|
239
|
+
"replace": [
|
|
240
|
+
"\tconst absentInputs = OPERATOR_INPUTS.filter(([name]) => (process.env[name]?.trim() ?? \"\") === \"\");",
|
|
241
|
+
"\tif (absentInputs.length > 0 && process.env.ENTWURF_NEVER_SET === \"1\") {"
|
|
242
|
+
],
|
|
243
|
+
"gate": ["bash", "run.sh", "check-release-gate-outcomes"],
|
|
244
|
+
"timeoutSeconds": 300,
|
|
245
|
+
"signature": "[QK:CODEX-OPERATOR-PREREQ-ABSENT-SKIPS]",
|
|
246
|
+
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"claim": "CODEX-OPERATOR-PREREQ-WRONG-FAILS",
|
|
250
|
+
"title": "turning the pid grammar into a decline means a malformed, dead or non-Codex app-server pid the operator DID supply reports as a missing prerequisite, so a broken answer buys silence where a cut reads silence as an unmet precondition rather than a defect",
|
|
251
|
+
"subject": "scripts/smoke-codex-fresh-live.ts",
|
|
252
|
+
"find": [
|
|
253
|
+
"\tok(",
|
|
254
|
+
"\t\t\"ENTWURF_CODEX_APP_SERVER_PID explicitly names the existing operator-owned app-server\",",
|
|
255
|
+
"\t\t/^[1-9]\\d*$/.test(pidText),"
|
|
256
|
+
],
|
|
257
|
+
"replace": [
|
|
258
|
+
"\tif (!/^[1-9]\\d*$/.test(pidText)) skipLive(LABEL, \"no usable app-server pid was supplied\");",
|
|
259
|
+
"\tok(",
|
|
260
|
+
"\t\t\"an app-server pid was supplied\",",
|
|
261
|
+
"\t\ttrue,"
|
|
262
|
+
],
|
|
263
|
+
"gate": ["bash", "run.sh", "check-release-gate-outcomes"],
|
|
264
|
+
"timeoutSeconds": 300,
|
|
265
|
+
"signature": "[QK:CODEX-OPERATOR-PREREQ-WRONG-FAILS]",
|
|
266
|
+
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"claim": "CODEX-OPERATOR-PREREQ-STATED-NOT-SEARCHED",
|
|
270
|
+
"title": "deriving the app-server pid by process search in the release instructions teaches the exact inference the MUST step refuses, and on a host running several app-servers it hands the gate an arbitrary process to verify as the operator's answer",
|
|
271
|
+
"subject": ".claude/skills/entwurf-release/SKILL.md",
|
|
272
|
+
"find": [": \"${CODEX_APP_SERVER_PID:?state the app-server pid you started; never search for it}\""],
|
|
273
|
+
"replace": ["CODEX_APP_SERVER_PID=$(pgrep -f 'codex.*app-server' | head -1)"],
|
|
274
|
+
"gate": ["bash", "run.sh", "check-release-gate-outcomes"],
|
|
275
|
+
"timeoutSeconds": 300,
|
|
276
|
+
"signature": "[QK:CODEX-OPERATOR-PREREQ-STATED-NOT-SEARCHED]",
|
|
277
|
+
"signatureSource": "scripts/check-release-gate-outcomes.ts"
|
|
230
278
|
}
|
|
231
279
|
]
|
|
232
280
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"lane": "retired-sent-surface",
|
|
4
|
+
"mutants": [
|
|
5
|
+
{
|
|
6
|
+
"claim": "NO-RENDERER-WITHOUT-PRODUCER",
|
|
7
|
+
"title": "a renderer is registered for a customType nothing in this extension emits — a producer-less orphan box no operator can ever reach, the same shape that shipped for two releases behind comments naming producers that did not exist",
|
|
8
|
+
"subject": "pi-extensions/entwurf-control.ts",
|
|
9
|
+
"find": ["\tpi.registerMessageRenderer(SESSION_MESSAGE_TYPE, renderSessionMessage);"],
|
|
10
|
+
"replace": [
|
|
11
|
+
"\tpi.registerMessageRenderer(SESSION_MESSAGE_TYPE, renderSessionMessage);\n\tpi.registerMessageRenderer(\"entwurf-orphan\", renderSessionMessage);"
|
|
12
|
+
],
|
|
13
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
14
|
+
"timeoutSeconds": 300,
|
|
15
|
+
"signature": "[QK:NO-RENDERER-WITHOUT-PRODUCER]",
|
|
16
|
+
"signatureSource": "scripts/check-entwurf-v2-surface.ts"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"claim": "RETIRED-SENT-SURFACE-ABSENT",
|
|
20
|
+
"title": "the retired sender-side echo constant comes back as a public export of a SHIPPED file — a name an npm consumer can import and nothing in the repo can ever produce, which is the promise #120 P3 removed rather than renamed",
|
|
21
|
+
"subject": "protocol.js",
|
|
22
|
+
"find": ["export const ENTWURF_PROJECT_CONTEXT_OPEN_TAG = \"<project-context\";"],
|
|
23
|
+
"replace": [
|
|
24
|
+
"export const ENTWURF_PROJECT_CONTEXT_OPEN_TAG = \"<project-context\";\nexport const ENTWURF_SENT_MESSAGE_TYPE = \"entwurf-sent\";"
|
|
25
|
+
],
|
|
26
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
27
|
+
"timeoutSeconds": 300,
|
|
28
|
+
"signature": "[QK:RETIRED-SENT-SURFACE-ABSENT]",
|
|
29
|
+
"signatureSource": "scripts/check-entwurf-v2-surface.ts"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"claim": "V2PROD-INBAND-ERROR-WIRED",
|
|
32
|
-
"title": "the production sendOverSocket closure stops copying response.error onto the RpcSendResult, so a receiver's named in-band refusal is stranded one hop before driveSend and the caller sees a reasonless rejected",
|
|
32
|
+
"title": "the production sendOverSocket closure stops copying response.error onto the RpcSendResult, so a receiver's named in-band refusal is stranded one hop before driveSend and the caller sees a reasonless rejected (#120 P2: the wire seam now also classifies the acceptance boundary, so the same line carries one more field — the mutation still drops the receiver's own error)",
|
|
33
33
|
"subject": "pi-extensions/lib/entwurf-v2-production.ts",
|
|
34
|
-
"find": ["\t\t\treturn { success: response.success, error: response.error };"],
|
|
35
|
-
"replace": ["\t\t\treturn { success: response.success };"],
|
|
34
|
+
"find": ["\t\t\treturn { success: response.success, error: response.error, boundary };"],
|
|
35
|
+
"replace": ["\t\t\treturn { success: response.success, boundary };"],
|
|
36
36
|
"gate": ["bash", "run.sh", "check-entwurf-v2-production"],
|
|
37
37
|
"timeoutSeconds": 60,
|
|
38
38
|
"signature": "[QK:V2PROD-INBAND-ERROR-WIRED]",
|
|
@@ -40,15 +40,15 @@
|
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
"claim": "V2SURF-PI-MODE-SCOPE",
|
|
43
|
-
"title": "pi-native mode param reverts to \"Delivery mode for a live send\" (false for native-push)",
|
|
43
|
+
"title": "pi-native mode param reverts to \"Delivery mode for a live send\" (false for native-push) (#120 P2: the description was rewritten to stop claiming an interrupt, so the mutation now strips the rail scope from the corrected sentence)",
|
|
44
44
|
"subject": "pi-extensions/entwurf-control.ts",
|
|
45
45
|
"find": [
|
|
46
|
-
"\t\t\t\t\t\"Injection style for a CONTROL-SOCKET send only: steer (
|
|
46
|
+
"\t\t\t\t\t\"Injection style for a CONTROL-SOCKET send only: steer (ask the receiver's steering queue) or follow_up (ask its follow-up queue). NEITHER is an interrupt: a busy receiver drains steering after each turn and follow-ups only when its inner loop ends, so a later steer overtakes every earlier follow_up and there is no order between the two. Both queues are volatile process memory — an abort drops what is in them. The receipt names which queue accepted the message (`queued-steer` / `queued-follow-up`); an idle receiver answers `sent`, meaning the turn was TRIGGERED, not that the model has seen the text. \" +",
|
|
47
47
|
"\t\t\t\t\t\"The mailbox and native-push plans carry no mode, so it has no effect on those rails.\","
|
|
48
48
|
],
|
|
49
49
|
"replace": [
|
|
50
|
-
"\t\t\t\t\t\"Delivery mode for a live send: steer (
|
|
51
|
-
"\t\t\t\t\t\"
|
|
50
|
+
"\t\t\t\t\t\"Delivery mode for a live send: steer (ask the steering queue) or follow_up (ask the follow-up queue). \" +",
|
|
51
|
+
"\t\t\t\t\t\"Both queues are volatile process memory.\","
|
|
52
52
|
],
|
|
53
53
|
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
54
54
|
"timeoutSeconds": 60,
|
|
@@ -67,6 +67,7 @@ export const EXPECTED_TOOLS = [
|
|
|
67
67
|
"entwurf_register_native",
|
|
68
68
|
"entwurf_fresh_call",
|
|
69
69
|
"entwurf_resume_call",
|
|
70
|
+
"entwurf_callback",
|
|
70
71
|
] as const;
|
|
71
72
|
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
72
73
|
/** How long a probed child gets to die politely before it is killed outright. */
|
|
@@ -24,11 +24,26 @@ import { copyFileSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
|
24
24
|
import { tmpdir } from "node:os";
|
|
25
25
|
import { dirname, resolve } from "node:path";
|
|
26
26
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
27
|
-
import type { Api, AssistantMessageEvent,
|
|
27
|
+
import type { Api, AssistantMessageEvent, Model, TranscriptContext } from "@earendil-works/pi-ai";
|
|
28
|
+
import { normalizeContext } from "@earendil-works/pi-ai";
|
|
28
29
|
import { skipLive } from "./lib/live-skip.ts";
|
|
29
30
|
|
|
30
31
|
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
31
32
|
const MODEL = process.env.ENTWURF_ACP_PROVIDER_MODEL?.trim() || "claude-sonnet-5";
|
|
33
|
+
// The pi built-ins the Claude child also exposes natively. pi declares its tool surface on
|
|
34
|
+
// every turn, and since 0.86 that declaration reaches a provider as the leading system
|
|
35
|
+
// message's `toolsAdded` (`normalizeContext`), which backend.ts replays for the exclude-tools
|
|
36
|
+
// truthfulness preflight. A fixture that declares NO tools is therefore not "a turn with the
|
|
37
|
+
// defaults" — it is a turn where the operator excluded everything, and the preflight rejects it
|
|
38
|
+
// before the prompt is ever sent. So every provider-path context here is built through
|
|
39
|
+
// `normalizeContext`, never typed as a raw `Context`: that annotation is exactly what hid this
|
|
40
|
+
// shape from typecheck when the 0.86 pin landed.
|
|
41
|
+
const PI_DECLARED_TOOLS = ["read", "bash", "edit", "write"].map((name) => ({
|
|
42
|
+
name,
|
|
43
|
+
description: "",
|
|
44
|
+
parameters: {} as never,
|
|
45
|
+
}));
|
|
46
|
+
|
|
32
47
|
const TURN_TIMEOUT_MS = Number(process.env.ENTWURF_ACP_PROVIDER_TIMEOUT_MS) || 240_000;
|
|
33
48
|
|
|
34
49
|
function fail(msg: string): never {
|
|
@@ -101,7 +116,8 @@ async function main(): Promise<void> {
|
|
|
101
116
|
console.error(`[smoke-acp-session-reuse-live] codeword: ${codeword}`);
|
|
102
117
|
|
|
103
118
|
// --- turn 1 (new): introduce the codeword, full transcript ---
|
|
104
|
-
const turn1:
|
|
119
|
+
const turn1: TranscriptContext = normalizeContext({
|
|
120
|
+
tools: PI_DECLARED_TOOLS,
|
|
105
121
|
messages: [
|
|
106
122
|
{
|
|
107
123
|
role: "user",
|
|
@@ -109,7 +125,7 @@ async function main(): Promise<void> {
|
|
|
109
125
|
timestamp: 0,
|
|
110
126
|
},
|
|
111
127
|
],
|
|
112
|
-
};
|
|
128
|
+
});
|
|
113
129
|
const r1 = await withTimeout(
|
|
114
130
|
"turn 1",
|
|
115
131
|
consume(backend.streamShellAcp(model, turn1, options) as Stream),
|
|
@@ -121,7 +137,9 @@ async function main(): Promise<void> {
|
|
|
121
137
|
console.error("[smoke-acp-session-reuse-live] starting turn 2 reuse prompt");
|
|
122
138
|
|
|
123
139
|
// --- turn 2 (reuse): ask for the codeword, DELTA ONLY ---
|
|
124
|
-
|
|
140
|
+
// turn1.messages already carries the folded system message, so spreading it keeps the
|
|
141
|
+
// declared surface on the reuse turn too; normalizeContext does not double it.
|
|
142
|
+
const turn2: TranscriptContext = normalizeContext({
|
|
125
143
|
messages: [
|
|
126
144
|
...turn1.messages,
|
|
127
145
|
{
|
|
@@ -147,7 +165,7 @@ async function main(): Promise<void> {
|
|
|
147
165
|
timestamp: 0,
|
|
148
166
|
},
|
|
149
167
|
],
|
|
150
|
-
};
|
|
168
|
+
});
|
|
151
169
|
const r2 = await withTimeout(
|
|
152
170
|
"turn 2",
|
|
153
171
|
consume(backend.streamShellAcp(model, turn2, options) as Stream),
|
|
@@ -67,7 +67,7 @@ write_mcp_fake() { # $1 = path
|
|
|
67
67
|
while IFS= read -r line; do
|
|
68
68
|
case "$line" in
|
|
69
69
|
*'"id":1'*) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{},"serverInfo":{"name":"fake-entwurf-bridge","version":"0"}}}' ;;
|
|
70
|
-
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"}]}}' ;;
|
|
70
|
+
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_callback"}]}}' ;;
|
|
71
71
|
esac
|
|
72
72
|
done
|
|
73
73
|
FAKE
|
|
@@ -89,7 +89,7 @@ if [ "${REVIEW_ENV:-}" = "fail" ]; then echo configured-env-failure >&2; exit 24
|
|
|
89
89
|
while IFS= read -r line; do
|
|
90
90
|
case "$line" in
|
|
91
91
|
*'"id":1'*) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{},"serverInfo":{"name":"fake-entwurf-bridge","version":"0"}}}' ;;
|
|
92
|
-
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"}]}}' ;;
|
|
92
|
+
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_callback"}]}}' ;;
|
|
93
93
|
esac
|
|
94
94
|
done
|
|
95
95
|
FAKE
|
|
@@ -669,12 +669,75 @@ async function run(): Promise<void> {
|
|
|
669
669
|
"set LIVE=1 plus ENTWURF_CODEX_APP_SERVER_PID, ENTWURF_CODEX_FRESH_MODEL, and ENTWURF_CODEX_FRESH_PI_MODEL to run; this opens real Codex and Pi windows and spends model turns.",
|
|
670
670
|
);
|
|
671
671
|
}
|
|
672
|
+
|
|
673
|
+
// ── THE OPERATOR-SUPPLIED PREREQUISITE TRIPLE, AND WHY ITS ABSENCE IS A SKIP ──
|
|
674
|
+
//
|
|
675
|
+
// Entwurf does not own the Codex app-server (VERIFY: "The A cell takes no inferred
|
|
676
|
+
// server or models"), so these three values can only come from the operator. That
|
|
677
|
+
// stays true. What was wrong is the CLASSIFICATION of their absence.
|
|
678
|
+
//
|
|
679
|
+
// `[측정 2026-09-23]` the skip line directly above promised four prerequisites in one
|
|
680
|
+
// breath — LIVE=1 *plus* the three env names — but only the first could ever produce a
|
|
681
|
+
// SKIP. The other three were `ok()` assertions, and `ok()` throws, so a missing
|
|
682
|
+
// operator input exited as FAIL. `release_gate()` supplies none of them, so this MUST
|
|
683
|
+
// step declined as a FAILURE inside every aggregate on every host: 0.25.0's own
|
|
684
|
+
// candidate receipt reads `MUST PASS=23 FAIL=1`, and the release record had to carry a
|
|
685
|
+
// prose classification explaining that the one red was "the shape of that step", and the
|
|
686
|
+
// cut stayed BLOCKED. A contract that manufactures a red every cut and then needs a human
|
|
687
|
+
// to excuse it is not a floor.
|
|
688
|
+
//
|
|
689
|
+
// `scripts/lib/step-outcome.sh` already wrote the rule this violated: "a step that RAN
|
|
690
|
+
// AND BROKE and a step that NEVER RAN are different facts, and a release record that
|
|
691
|
+
// blurs them is worthless: the first is a defect to fix, the second is a prerequisite to
|
|
692
|
+
// supply." An absent operator input is the second.
|
|
693
|
+
//
|
|
694
|
+
// So the axis is split, and the split is the whole repair:
|
|
695
|
+
// ABSENT (unset/empty) -> skipLive: nothing ran, the missing names are printed, and
|
|
696
|
+
// `--cut` still refuses it as BLOCKED (MUST SKIP). Nothing is
|
|
697
|
+
// excused — a cut without this receipt remains impossible.
|
|
698
|
+
// PRESENT BUT WRONG -> FAIL, unchanged: a malformed or dead PID, a process whose
|
|
699
|
+
// argv is not a Codex app-server, a foreign uid, A == S. The
|
|
700
|
+
// operator answered, and the answer is a defect.
|
|
701
|
+
// Nothing is inferred either way, and no default is invented for a value the operator
|
|
702
|
+
// did not give. The reproducible green is the invocation VERIFY and the release skill's
|
|
703
|
+
// P5 both now spell out, and `release_gate` needs no plumbing for it: measured on this
|
|
704
|
+
// source, that function exports no scrub and unsets nothing, so an exported triple
|
|
705
|
+
// already reaches this step.
|
|
706
|
+
//
|
|
707
|
+
// DELIBERATELY NOT MOVED: the installed-config preflights below (`codexFreshPreflight`,
|
|
708
|
+
// `codexCallerFreshPreflight`, `codexLaunchCwdFreshPreflight`). Those are host state
|
|
709
|
+
// `setup` establishes and the Codex doctors cover, and the file header's contract
|
|
710
|
+
// already decided them — "missing system birth, user MCP/status/terminal-title config,
|
|
711
|
+
// app-server, models, runtime, bridge, or tmux seat is a FAIL". A one-time answered
|
|
712
|
+
// launch directory is host setup, not a per-invocation input, and reclassifying it would
|
|
713
|
+
// be a second contract change riding this one.
|
|
714
|
+
const OPERATOR_INPUTS = [
|
|
715
|
+
["ENTWURF_CODEX_APP_SERVER_PID", "the pid of the app-server YOU started (never inferred)"],
|
|
716
|
+
["ENTWURF_CODEX_FRESH_MODEL", "the Codex-side model this cell may spend a turn on"],
|
|
717
|
+
["ENTWURF_CODEX_FRESH_PI_MODEL", "the pi-side model of the callback sibling"],
|
|
718
|
+
] as const;
|
|
719
|
+
const absentInputs = OPERATOR_INPUTS.filter(([name]) => (process.env[name]?.trim() ?? "") === "");
|
|
720
|
+
if (absentInputs.length > 0) {
|
|
721
|
+
skipLive(
|
|
722
|
+
LABEL,
|
|
723
|
+
`this cell takes no inferred server or models, and ${absentInputs.length} operator-supplied ` +
|
|
724
|
+
`prerequisite(s) are absent: ${absentInputs.map(([name, why]) => `${name} (${why})`).join("; ")}. ` +
|
|
725
|
+
"Supply them on the invocation — LIVE=1 ENTWURF_CODEX_APP_SERVER_PID=<pid> " +
|
|
726
|
+
"ENTWURF_CODEX_FRESH_MODEL=<codex-model> ENTWURF_CODEX_FRESH_PI_MODEL=<pi-model> — either for the " +
|
|
727
|
+
"standalone run or for `./run.sh release-gate <scratch> --cut`, which inherits them. " +
|
|
728
|
+
"A cut is still blocked without this receipt: --cut reads a MUST SKIP as red.",
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
672
732
|
ok("the measured/certified host axis is Linux", process.platform === "linux", `host=${process.platform}`);
|
|
673
733
|
|
|
734
|
+
// Present, so a malformed value is the operator's answer being wrong — a FAIL, not a
|
|
735
|
+
// declined prerequisite. The absence arm above already left.
|
|
674
736
|
const pidText = process.env.ENTWURF_CODEX_APP_SERVER_PID?.trim() ?? "";
|
|
675
737
|
ok(
|
|
676
738
|
"ENTWURF_CODEX_APP_SERVER_PID explicitly names the existing operator-owned app-server",
|
|
677
739
|
/^[1-9]\d*$/.test(pidText),
|
|
740
|
+
`ENTWURF_CODEX_APP_SERVER_PID=${JSON.stringify(pidText)} is not a positive decimal pid`,
|
|
678
741
|
);
|
|
679
742
|
const appServerPid = Number(pidText);
|
|
680
743
|
const procDir = `/proc/${appServerPid}`;
|
|
@@ -749,10 +812,13 @@ async function run(): Promise<void> {
|
|
|
749
812
|
`fixture-session=${fixtureCoordinate.sessionId}\nfixture-name=${fixtureSessionName}\n` +
|
|
750
813
|
`fixture-window=${fixtureCoordinate.windowId}`;
|
|
751
814
|
|
|
815
|
+
// Both are non-empty: the prerequisite arm at the top of `run` left otherwise. Asserting
|
|
816
|
+
// `.length > 0` again here would be a tautology dressed as a check, so the pair is
|
|
817
|
+
// RECORDED instead — the tier a live receipt is read on is a fact the evidence needs, and
|
|
818
|
+
// 0.25.0 already had to explain in prose that its pi leg ran on terra rather than luna.
|
|
752
819
|
const codexModel = process.env.ENTWURF_CODEX_FRESH_MODEL?.trim() ?? "";
|
|
753
820
|
const piModel = process.env.ENTWURF_CODEX_FRESH_PI_MODEL?.trim() ?? "";
|
|
754
|
-
|
|
755
|
-
ok("ENTWURF_CODEX_FRESH_PI_MODEL explicitly names the Pi callback sibling model", piModel.length > 0);
|
|
821
|
+
receipts["1b-operator-supplied-models"] = `codex=${codexModel}\npi=${piModel}`;
|
|
756
822
|
const codexRuntime = resolveExecutable("codex", appServerEnv);
|
|
757
823
|
ok("the Codex runtime resolves on the app-server PATH", codexRuntime !== null);
|
|
758
824
|
const bridgeRuntime = resolveExecutable("entwurf-bridge", appServerEnv);
|
|
@@ -1016,11 +1082,19 @@ async function run(): Promise<void> {
|
|
|
1016
1082
|
message: initialPiInstruction,
|
|
1017
1083
|
});
|
|
1018
1084
|
receipts["5-fixture-to-initial-pi-v2"] = instructInitialPi.text;
|
|
1085
|
+
// #120 P2: the control-socket rail answers with the ACCEPTANCE BOUNDARY the receiver observed,
|
|
1086
|
+
// and every member of that closed set is a success. The initial Pi is mid-turn while the fixture
|
|
1087
|
+
// addresses it, so `queued-follow-up` is an ORDINARY answer here — measured 2026-09-23, this
|
|
1088
|
+
// exact step. The old `sent|delivered` pin passed only when the caller happened to be idle, and
|
|
1089
|
+
// before P2 it passed on the busy path too because the rail said `sent` either way; `delivered`
|
|
1090
|
+
// was never a member of the set at all. The sibling LIVE smokes took this widening in 6d90d73
|
|
1091
|
+
// (`smoke-herdr-fresh-call-live.ts`, `smoke-mux-lifecycle-live.ts`); this step was missed.
|
|
1019
1092
|
ok(
|
|
1020
1093
|
"the fixture addressed the real initial Pi caller through an actual control-socket entwurf_v2 receipt",
|
|
1021
1094
|
!instructInitialPi.isError &&
|
|
1022
|
-
/control-socket
|
|
1023
|
-
|
|
1095
|
+
/entwurf_v2 control-socket → (?:sent|queued-steer|queued-follow-up|accepted-unknown-boundary)/.test(
|
|
1096
|
+
instructInitialPi.text,
|
|
1097
|
+
),
|
|
1024
1098
|
instructInitialPi.text,
|
|
1025
1099
|
);
|
|
1026
1100
|
|
|
@@ -1325,15 +1399,22 @@ async function run(): Promise<void> {
|
|
|
1325
1399
|
assertExactSourceToolCalls(
|
|
1326
1400
|
piSourceToolReceipts(readInitialPiEntries()),
|
|
1327
1401
|
[
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1402
|
+
// 0.24.0 moved a pi sibling's first action to the ZERO-ARGUMENT eighth verb, and this
|
|
1403
|
+
// smoke's own launch receipt says so in the same run: "its first action is the
|
|
1404
|
+
// zero-argument callback verb (codex is the one arm that still sends the nonce as
|
|
1405
|
+
// arguments)". The Codex audit below therefore keeps its `entwurf_v2` shape, and only
|
|
1406
|
+
// this one moves. `{}` is matched by deep-strict equality, so it pins the verb's actual
|
|
1407
|
+
// contract — a target or message typed here would be a second address axis and the verb
|
|
1408
|
+
// refuses it by name.
|
|
1409
|
+
//
|
|
1410
|
+
// Nothing is weakened by dropping the nonce from THIS expectation: the correlation is
|
|
1411
|
+
// proven earlier and from the delivered body, which is where a nonce can actually be
|
|
1412
|
+
// corrupted — `the initial Pi callback carries the exact launch nonce` (:970-973) reads
|
|
1413
|
+
// the fixture's own mailbox, requires exactly one exact match and refuses duplicates,
|
|
1414
|
+
// and the two cells beside it bind that callback's sender envelope to a real pi V3
|
|
1415
|
+
// citizen. This audit's job is the different one its name says: exactly these calls,
|
|
1416
|
+
// once each, and nothing else.
|
|
1417
|
+
{ toolName: "entwurf_callback", arguments: {} },
|
|
1337
1418
|
{
|
|
1338
1419
|
toolName: "entwurf_fresh_call",
|
|
1339
1420
|
arguments: {
|
|
@@ -26,7 +26,7 @@ write_mcp_fake() {
|
|
|
26
26
|
while IFS= read -r line; do
|
|
27
27
|
case "$line" in
|
|
28
28
|
*'"id":1'*) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{},"serverInfo":{"name":"fake-entwurf-bridge","version":"0"}}}' ;;
|
|
29
|
-
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"}]}}' ;;
|
|
29
|
+
*'"id":2'*) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"entwurf_v2"},{"name":"entwurf_self"},{"name":"entwurf_peers"},{"name":"entwurf_inbox_read"},{"name":"entwurf_register_native"},{"name":"entwurf_fresh_call"},{"name":"entwurf_resume_call"},{"name":"entwurf_callback"}]}}' ;;
|
|
30
30
|
esac
|
|
31
31
|
done
|
|
32
32
|
FAKE
|