@junghanacs/entwurf 0.52.0 → 0.53.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 (68) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +116 -397
  3. package/CHANGELOG.md +131 -2
  4. package/CONTRIBUTING.md +4 -4
  5. package/DELIVERY.md +2 -2
  6. package/ENTWURF-CLI.json +1 -1
  7. package/README.md +7 -4
  8. package/VERIFY.md +332 -142
  9. package/demo/demo-baseline.sh +1 -1
  10. package/demo/demo.sh +1 -1
  11. package/docs/acp-backend-rail.md +248 -34
  12. package/docs/setup-clean-host.md +50 -65
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +1 -1
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +90 -14
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +4 -9
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +15 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/model-ids.js +9 -12
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +41 -56
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +33 -6
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +34 -3
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -1
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +2 -1
  23. package/package.json +4 -4
  24. package/pi/settings.reference.json +0 -1
  25. package/pi-extensions/acp-provider.ts +12 -6
  26. package/pi-extensions/lib/acp/acp-client.ts +6 -7
  27. package/pi-extensions/lib/acp/backend-adapter.ts +139 -15
  28. package/pi-extensions/lib/acp/backend.ts +261 -32
  29. package/pi-extensions/lib/acp/config.ts +4 -9
  30. package/pi-extensions/lib/acp/context.ts +17 -0
  31. package/pi-extensions/lib/acp/event-mapper.ts +236 -17
  32. package/pi-extensions/lib/acp/model-ids.js +9 -12
  33. package/pi-extensions/lib/acp/models.ts +46 -57
  34. package/pi-extensions/lib/acp/tool-surface.ts +34 -6
  35. package/pi-extensions/lib/fresh-call-composition.ts +37 -3
  36. package/pi-extensions/lib/herdr-fresh-call.ts +3 -1
  37. package/pi-extensions/lib/mux-fresh-call.ts +3 -1
  38. package/run.sh +25 -86
  39. package/scripts/check-acp-carrier-augment.ts +3 -21
  40. package/scripts/check-acp-prompt-lifecycle.ts +24 -3
  41. package/scripts/check-acp-provider-surface.ts +17 -26
  42. package/scripts/check-acp-session-reuse.ts +23 -4
  43. package/scripts/check-acp-stop-reason.ts +22 -3
  44. package/scripts/check-acp-stream-hooks.ts +24 -3
  45. package/scripts/check-acp-usage-accounting.ts +53 -24
  46. package/scripts/check-copilot-receive-arm.ts +19 -2
  47. package/scripts/check-fresh-call-dispatch.ts +9 -3
  48. package/scripts/check-gate-qualification.ts +5 -5
  49. package/scripts/check-herdr-fresh-call.ts +1 -0
  50. package/scripts/check-install-surface.ts +6 -0
  51. package/scripts/check-probe-ordering.ts +34 -24
  52. package/scripts/check-release-gate-outcomes.ts +9 -23
  53. package/scripts/lib/probe-cli-target.ts +3 -2
  54. package/scripts/mutants/acp-augment.json +0 -26
  55. package/scripts/mutants/acp-cortex.json +10 -183
  56. package/scripts/mutants/acp-foreground-only.json +11 -0
  57. package/scripts/mutants/acp-launch-namespace.json +3 -1
  58. package/scripts/mutants/acp-prompt-lifecycle.json +2 -2
  59. package/scripts/mutants/acp-set-model-readback.json +488 -0
  60. package/scripts/mutants/mux-fresh-call.json +12 -1
  61. package/scripts/mutants/pi-floor.json +2 -2
  62. package/scripts/mutants/release-gate.json +3 -3
  63. package/scripts/raw-acp-compaction-measure/probe.ts +8 -2
  64. package/scripts/smoke-acp-cortex-live.ts +4 -408
  65. package/scripts/smoke-acp-ordering-probe-live.ts +7 -1
  66. package/scripts/smoke-acp-session-reuse-live.ts +11 -2
  67. package/scripts/smoke-herdr-raw-install-live.sh +4 -4
  68. package/scripts/check-acp-cortex.ts +0 -710
@@ -27,7 +27,7 @@
27
27
  // totalTokens is CONTEXT OCCUPANCY, not a turn total, and is never overwritten
28
28
  // with the turn aggregate — pi reads that field as the context gauge.
29
29
  // a backend with no measured semantics (no sealsTurnAccounting) is sealed NOT AT
30
- // ALL: cortex's emitted usage must be byte-identical to pre-#93.
30
+ // ALL: the no-capability fixture preserves the mapper's output.
31
31
  //
32
32
  // ORACLES, each independent of the subject:
33
33
  // - the cost sum: THIS FILE constructs the cumulative series and asserts the
@@ -62,8 +62,21 @@ import { isContextOverflow, normalizeContext } from "@earendil-works/pi-ai";
62
62
  import { calculateContextTokens } from "@earendil-works/pi-coding-agent";
63
63
  import { assertOverlayInSandbox, sandboxAcpGateHome } from "./lib/acp-gate-home.ts";
64
64
 
65
- const claude = { id: "claude-sonnet-5-5" } as unknown as Model<Api>;
66
- const cortex = { id: "cortex-auto" } as unknown as Model<Api>;
65
+ // #142: a Claude turn carries pi's thinking level, which the backend sets as effort and reads back
66
+ // before the prompt. This fixture runs every turn at one fixed level on rows carrying the curated
67
+ // level map, and its fake driver reads back whatever config option it was asked to set. The effort
68
+ // contract itself is certified in pi-extensions/lib/acp/backend.thinking.test.ts.
69
+ const FIXTURE_THINKING_LEVEL = "medium";
70
+ const FIXTURE_THINKING_LEVEL_MAP = {
71
+ off: null,
72
+ minimal: null,
73
+ low: "low",
74
+ medium: "medium",
75
+ high: "high",
76
+ xhigh: "xhigh",
77
+ max: "max",
78
+ };
79
+ const claude = { id: "claude-sonnet-5-5", thinkingLevelMap: FIXTURE_THINKING_LEVEL_MAP } as unknown as Model<Api>;
67
80
 
68
81
  type Stream = AsyncIterable<AssistantMessageEvent>;
69
82
 
@@ -161,7 +174,7 @@ function makeHarness(recordDir: string, scripts: TurnScript[]) {
161
174
  newSessionCalls++;
162
175
  return { sessionId: "ACP-1" };
163
176
  },
164
- setSessionConfigOption: async (p: any) => ({ configOptions: [{ id: "model", currentValue: p.value }] }),
177
+ setSessionConfigOption: async (p: any) => ({ configOptions: [{ id: p.configId, currentValue: p.value }] }),
165
178
  prompt: async ({ sessionId }: any) => {
166
179
  const script = scripts[turnIndex++] ?? {};
167
180
  const update: Record<string, unknown> = { sessionUpdate: "usage_update" };
@@ -305,7 +318,14 @@ try {
305
318
  const promptsOut = resolve(TMP_EMIT, "pi-extensions/lib/acp/prompts");
306
319
  mkdirSync(promptsOut, { recursive: true });
307
320
  copyFileSync("pi-extensions/lib/acp/prompts/engraving.md", resolve(promptsOut, "engraving.md"));
308
- const backend = (await import(pathToFileURL(resolve(TMP_EMIT, "pi-extensions/lib/acp/backend.js")).href)) as any;
321
+ const backendModule = (await import(
322
+ pathToFileURL(resolve(TMP_EMIT, "pi-extensions/lib/acp/backend.js")).href
323
+ )) as any;
324
+ const backend = {
325
+ ...backendModule,
326
+ streamAcpTurn: (model: unknown, context: unknown, options: Record<string, unknown> | undefined, deps: unknown) =>
327
+ backendModule.streamAcpTurn(model, context, { reasoning: FIXTURE_THINKING_LEVEL, ...options }, deps),
328
+ };
309
329
  await assertOverlayInSandbox(TMP_EMIT, gateHome);
310
330
 
311
331
  // ----------------------------------------------------------------------
@@ -915,13 +935,10 @@ try {
915
935
  // ----------------------------------------------------------------------
916
936
  // CELL 5 — a backend with no MEASURED semantics is sealed NOT AT ALL.
917
937
  //
918
- // backend.ts / event-mapper.ts are COMMON layer: cortex drives the same turn
919
- // loop and the same mapper. Nobody has measured what cortex's ACP `usage`
920
- // means (turn delta or session total? against which price table?), and ACP's
921
- // own type is self-contradictory about it — the outer comment says "for this
922
- // turn", the field comments say "across all turns/session". So cortex must
923
- // keep exactly its pre-#93 output: the coarse mapper assignment, and no token
924
- // projection, no baseline, no diff.
938
+ // The common loop must not invent accounting semantics for an adapter that
939
+ // declares none. Cortex is disabled, so this isolated fixture temporarily
940
+ // removes the capability from the compiled Claude adapter; it changes no
941
+ // production routing and restores the capability before the next cell.
925
942
  // ----------------------------------------------------------------------
926
943
  // TWO turns, and the second one is what carries the claim: on turn ONE a
927
944
  // diff-against-an-empty-baseline and the raw cumulative are the SAME number, so
@@ -946,18 +963,30 @@ try {
946
963
  occupancyTokens: 140_000,
947
964
  },
948
965
  ]);
949
- await collect(backend.streamAcpTurn(cortex, userCtx("cortex NONCE-1"), { sessionId: "usage-E" }, h.deps) as Stream);
950
- const msg = sealedMessage(
951
- await collect(
952
- backend.streamAcpTurn(
953
- cortex,
954
- reuseCtx("cortex NONCE-1", "cortex NONCE-2"),
955
- { sessionId: "usage-E" },
956
- h.deps,
957
- ) as Stream,
958
- ),
966
+ const { claudeAdapter } = await import(
967
+ pathToFileURL(resolve(TMP_EMIT, "pi-extensions/lib/acp/backend-adapter.js")).href
959
968
  );
960
- assert.equal(h.children.length, 1, "both cortex turns ran on ONE reused child");
969
+ const oldCapability = claudeAdapter.sealsTurnAccounting;
970
+ claudeAdapter.sealsTurnAccounting = false;
971
+ let msg: AssistantMessage;
972
+ try {
973
+ await collect(
974
+ backend.streamAcpTurn(claude, userCtx("fixture NONCE-1"), { sessionId: "usage-E" }, h.deps) as Stream,
975
+ );
976
+ msg = sealedMessage(
977
+ await collect(
978
+ backend.streamAcpTurn(
979
+ claude,
980
+ reuseCtx("fixture NONCE-1", "fixture NONCE-2"),
981
+ { sessionId: "usage-E" },
982
+ h.deps,
983
+ ) as Stream,
984
+ ),
985
+ );
986
+ } finally {
987
+ claudeAdapter.sealsTurnAccounting = oldCapability;
988
+ }
989
+ assert.equal(h.children.length, 1, "both synthetic no-accounting turns ran on ONE reused child");
961
990
  assert.deepEqual(
962
991
  {
963
992
  input: msg.usage.input,
@@ -1094,7 +1123,7 @@ console.log(
1094
1123
  "a reused session; a turn with no cost notification attributes $0 while HOLDING the baseline so the session sum " +
1095
1124
  "reproduces the backend's own cumulative ESTIMATE exactly (agreement with that carrier, never with a bill); a DECREASING total rebaselines, attributes $0 and says so to the operator with both numbers; " +
1096
1125
  "totalTokens stays CONTEXT OCCUPANCY (verified through pi's own calculateContextTokens) and is carried forward " +
1097
- "when a turn reports none; a backend that declares no measured semantics — cortex — is sealed not at all; and a next turn " +
1126
+ "when a turn reports none; an injected no-accounting capability fixture is sealed not at all; and a next turn " +
1098
1127
  "started from INSIDE the previous turn's terminal event still reuses the one session, so the cost baseline " +
1099
1128
  "survives the turn boundary",
1100
1129
  );
@@ -705,12 +705,29 @@ ok(
705
705
  "python3",
706
706
  [nonDumpable, path.join(root, "node_modules", "@github", "copilot", "npm-loader.js")],
707
707
  {
708
- stdio: "ignore",
708
+ stdio: ["ignore", "ignore", "pipe"],
709
709
  },
710
710
  );
711
711
  running.push(opaque);
712
712
  try {
713
- await sleep(400);
713
+ // Wait for the child's own "ready" (written after prctl succeeded), not a fixed delay: on a
714
+ // loaded host 400 ms was not enough for python to start and drop dumpability, so the
715
+ // environ was still readable and this cell failed for a timing reason (0.53.0 release gate).
716
+ await new Promise<void>((resolve, reject) => {
717
+ let seen = "";
718
+ const timer = setTimeout(() => reject(new Error("non-dumpable fixture never reported ready")), 15_000);
719
+ opaque.stderr?.on("data", (b: Buffer) => {
720
+ seen += b.toString();
721
+ if (seen.includes("ready")) {
722
+ clearTimeout(timer);
723
+ resolve();
724
+ }
725
+ });
726
+ opaque.once("exit", (code) => {
727
+ clearTimeout(timer);
728
+ reject(new Error(`non-dumpable fixture exited early (${code})`));
729
+ });
730
+ });
714
731
  let environReadable = true;
715
732
  try {
716
733
  readFileSync(`/proc/${opaque.pid}/environ`);
@@ -150,7 +150,10 @@ async function main(): Promise<void> {
150
150
  const tmuxRetired = await dispatchFreshCall(pi(" entwurf/claude-opus-5 "), {}, recordingSpawn().spawn);
151
151
  const current = recordingSpawn();
152
152
  const herdrCurrent = await dispatchFreshCall(pi("entwurf/claude-opus-5-5"), HERDR_ENV, current.spawn);
153
- const cortex = await dispatchFreshCall(pi("entwurf/cortex-claude-sonnet-5"), {}, recordingSpawn().spawn);
153
+ const cortexSpawn = recordingSpawn();
154
+ const cortex = await dispatchFreshCall(pi("entwurf/cortex-claude-sonnet-5"), {}, cortexSpawn.spawn);
155
+ const cortexHerdrSpawn = recordingSpawn();
156
+ const cortexHerdr = await dispatchFreshCall(pi("entwurf/cortex-auto"), HERDR_ENV, cortexHerdrSpawn.spawn);
154
157
  const otherBackend = await dispatchFreshCall(
155
158
  { ...REQUEST, backend: "claude-code", model: "entwurf/claude-sonnet-5" },
156
159
  {},
@@ -158,7 +161,7 @@ async function main(): Promise<void> {
158
161
  );
159
162
  const reasonOf = (d: Awaited<ReturnType<typeof dispatchFreshCall>>) => (d.result.ok ? null : d.result.reason);
160
163
  ok(
161
- "[QK:FCD-ACP-MODEL-EXACT] a pi sibling under the entwurf provider must name an EXACT curated ACP id: a retired Claude 5 id refuses as acp-model-not-curated on BOTH rails (trimmed like the rails read it) before herdr is asked anything, while the 5.5 id and a Cortex id pass through and another backend is not judged — Pi would otherwise open the retired id on a Cortex row by substring",
164
+ "[QK:FCD-ACP-MODEL-EXACT] a pi sibling under the entwurf provider names an exact supported Claude id: retired Claude and Cortex ids refuse before launch, while a current Claude id passes and another backend is not judged",
162
165
  herdrRetired.rail === "herdr" &&
163
166
  reasonOf(herdrRetired) === "acp-model-not-curated" &&
164
167
  herdrSpawn.calls.length === 0 &&
@@ -166,7 +169,10 @@ async function main(): Promise<void> {
166
169
  reasonOf(tmuxRetired) === "acp-model-not-curated" &&
167
170
  reasonOf(herdrCurrent) !== "acp-model-not-curated" &&
168
171
  current.calls.length > 0 &&
169
- reasonOf(cortex) !== "acp-model-not-curated" &&
172
+ reasonOf(cortex) === "acp-model-not-curated" &&
173
+ cortexSpawn.calls.length === 0 &&
174
+ reasonOf(cortexHerdr) === "acp-model-not-curated" &&
175
+ cortexHerdrSpawn.calls.length === 0 &&
170
176
  reasonOf(otherBackend) !== "acp-model-not-curated" &&
171
177
  renderDispatchedFreshCall(herdrRetired).text.includes("exact curated ACP id") &&
172
178
  renderDispatchedFreshCall(tmuxRetired).text.includes("exact curated ACP id"),
@@ -3435,13 +3435,13 @@ let manifestCount: number;
3435
3435
  // Extend this inventory and the manifests TOGETHER, never silently.
3436
3436
  const EXPECTED_LANE_MUTANTS: Record<string, number> = {
3437
3437
  "acp-backend-preflight": 1,
3438
- "acp-augment": 10,
3439
- "acp-cortex": 14,
3440
- "acp-foreground-only": 1,
3438
+ "acp-augment": 8,
3439
+ "acp-cortex": 3,
3440
+ "acp-foreground-only": 2,
3441
3441
  "acp-launch-namespace": 2,
3442
3442
  "acp-overlay": 1,
3443
3443
  "acp-prompt-lifecycle": 15,
3444
- "acp-set-model-readback": 2,
3444
+ "acp-set-model-readback": 44,
3445
3445
  "acp-stop-reason": 7,
3446
3446
  "acp-stream-hooks": 10,
3447
3447
  "acp-usage-accounting": 12,
@@ -3500,7 +3500,7 @@ let manifestCount: number;
3500
3500
  "meta-identity": 9,
3501
3501
  "meta-retire": 4,
3502
3502
  "mux-boundary": 16,
3503
- "mux-fresh-call": 67,
3503
+ "mux-fresh-call": 68,
3504
3504
  "mux-launcher-fence": 8,
3505
3505
  "mux-parent-artifact": 3,
3506
3506
  "pack-install": 9,
@@ -413,6 +413,7 @@ async function main(): Promise<void> {
413
413
  // bytes somebody wrote — while the framing gets its own claim about surviving the fold verbatim.
414
414
  const FRAMING = composeFreshCallFraming({
415
415
  backend: "claude-code",
416
+ model: "claude-sonnet-5-5",
416
417
  callerGardenId: "20260101T010101-aaaaaa",
417
418
  nonce: "mux-fresh-call-deadbeefdeadbeefdeadbeef",
418
419
  openingLine: HERDR_FRESH_CALL_OPENING_LINE,
@@ -516,6 +516,12 @@ const operatorCmds = [...targets].filter(([cmd, ts]) => !isDevGate(cmd) && ts.le
516
516
  l.replace(/^ {2}\.\/run\.sh (\S.*?)( +)# /, (_m, syn, gap) => ` entwurf ${syn}${gap} # `);
517
517
  const toolCountWord = ({ 9: "nine" } as Record<number, string>)[EXPECTED_TOOLS.length];
518
518
  const literalAmendments: { old: string; next: string; receipt: string }[] = [
519
+ {
520
+ old: "claude-native-resume; Cortex stays a documented on-demand direct call",
521
+ next: "claude-native-resume; Cortex ACP is disabled",
522
+ receipt:
523
+ "run.sh smoke_acp_cortex_live refuses with cortex-acp-disabled before a probe; the other MUST axes are unchanged",
524
+ },
519
525
  {
520
526
  old: "EXACTLY the eight shipped garden verbs",
521
527
  next: `EXACTLY the ${toolCountWord} shipped garden verbs`,
@@ -1892,20 +1892,24 @@ function intervention(
1892
1892
  // a mutant subject (§11-7-c), so kill-power here is the synthetic
1893
1893
  // negatives, not a manifest entry.
1894
1894
  {
1895
- const inspect = (acpSrc: string, sdkSrc: string): string[] => {
1895
+ // claude-agent-acp 0.103.0+ split the resolver out of the agent: `claudeCliPath` lives in
1896
+ // `dist/claude-cli.js` (`cliSrc`) and the query-options literal in `dist/v1/acp-agent.js`
1897
+ // (`acpSrc`), which now passes `await claudeCliPath()` alone — the env check moved inside
1898
+ // the resolver, so the verbatim-override premise is judged there.
1899
+ const inspect = (cliSrc: string, acpSrc: string, sdkSrc: string): string[] => {
1896
1900
  const violations: string[] = [];
1897
- if (!acpSrc.includes("export async function claudeCliPath()")) {
1898
- violations.push("claudeCliPath export missing from acp-agent.js");
1901
+ if (!cliSrc.includes("export async function claudeCliPath()")) {
1902
+ violations.push("claudeCliPath export missing from claude-cli.js");
1899
1903
  }
1900
- if (!/if \(process\.env\.CLAUDE_CODE_EXECUTABLE\) \{\s*return process\.env\.CLAUDE_CODE_EXECUTABLE;/.test(acpSrc)) {
1904
+ if (!/if \(process\.env\.CLAUDE_CODE_EXECUTABLE\) \{\s*return process\.env\.CLAUDE_CODE_EXECUTABLE;/.test(cliSrc)) {
1901
1905
  violations.push(
1902
1906
  "claudeCliPath no longer returns the ambient override VERBATIM — the refusal precondition's premise moved",
1903
1907
  );
1904
1908
  }
1905
- const overrideLine = "pathToClaudeCodeExecutable: process.env.CLAUDE_CODE_EXECUTABLE ?? (await claudeCliPath())";
1909
+ const overrideLine = "pathToClaudeCodeExecutable: await claudeCliPath(),";
1906
1910
  const iOverride = acpSrc.indexOf(overrideLine);
1907
1911
  if (iOverride === -1) {
1908
- violations.push("the env-??-claudeCliPath resolution at the query options literal is gone");
1912
+ violations.push("the claudeCliPath resolution at the query options literal is gone");
1909
1913
  } else {
1910
1914
  const iSpread = acpSrc.indexOf("...userProvidedOptions,");
1911
1915
  if (iSpread === -1 || iSpread > iOverride) {
@@ -1944,49 +1948,55 @@ function intervention(
1944
1948
 
1945
1949
  // Synthetic fixtures FIRST — an inspector that cannot see the defect it
1946
1950
  // exists for is no inspector.
1947
- const goodAcp =
1951
+ const goodCli =
1948
1952
  "export async function claudeCliPath() {\n" +
1949
- " if (process.env.CLAUDE_CODE_EXECUTABLE) {\n return process.env.CLAUDE_CODE_EXECUTABLE;\n }\n}\n" +
1953
+ " if (process.env.CLAUDE_CODE_EXECUTABLE) {\n return process.env.CLAUDE_CODE_EXECUTABLE;\n }\n}\n";
1954
+ const goodAcp =
1950
1955
  "const options = {\n ...userProvidedOptions,\n" +
1951
- " pathToClaudeCodeExecutable: process.env.CLAUDE_CODE_EXECUTABLE ?? (await claudeCliPath()),\n};\n";
1956
+ " pathToClaudeCodeExecutable: await claudeCliPath(),\n};\n";
1952
1957
  const goodSdk = `x=![".js",".mjs",".tsx",".ts",".jsx"].some((r)=>e.endsWith(r));y=Cs()?"bun":"node";spawnLocalProcess(e){let{command:t,args:r,cwd:n,env:o,signal:i}=e,s=cxe(t,r,{cwd:n,stdio:["pipe","pipe","pipe"],signal:i,env:o,windowsHide:!0})}`;
1953
- assert.deepEqual(inspect(goodAcp, goodSdk), [], "inspector passes the correct synthetic fixture");
1958
+ assert.deepEqual(inspect(goodCli, goodAcp, goodSdk), [], "inspector passes the correct synthetic fixture");
1954
1959
  const invertedAcp = goodAcp.replace(
1955
- " ...userProvidedOptions,\n pathToClaudeCodeExecutable: process.env.CLAUDE_CODE_EXECUTABLE ?? (await claudeCliPath()),\n",
1956
- " pathToClaudeCodeExecutable: process.env.CLAUDE_CODE_EXECUTABLE ?? (await claudeCliPath()),\n ...userProvidedOptions,\n",
1960
+ " ...userProvidedOptions,\n pathToClaudeCodeExecutable: await claudeCliPath(),\n",
1961
+ " pathToClaudeCodeExecutable: await claudeCliPath(),\n ...userProvidedOptions,\n",
1957
1962
  );
1958
1963
  assert.ok(
1959
- inspect(invertedAcp, goodSdk).some((v) => v.includes("key-order")),
1964
+ inspect(goodCli, invertedAcp, goodSdk).some((v) => v.includes("key-order")),
1960
1965
  "inspector catches userProvidedOptions inverted to win over the explicit override key",
1961
1966
  );
1962
1967
  assert.ok(
1963
- inspect(goodAcp.replace("export async function claudeCliPath()", "async function claudeCliPath()"), goodSdk).some(
1964
- (v) => v.includes("export missing"),
1965
- ),
1968
+ inspect(
1969
+ goodCli.replace("export async function claudeCliPath()", "async function claudeCliPath()"),
1970
+ goodAcp,
1971
+ goodSdk,
1972
+ ).some((v) => v.includes("export missing")),
1966
1973
  "inspector catches the claudeCliPath export disappearing",
1967
1974
  );
1968
1975
  assert.ok(
1969
- inspect(goodAcp, goodSdk.replace('".mjs",', "")).some((v) => v.includes("discriminator")),
1976
+ inspect(goodCli, goodAcp, goodSdk.replace('".mjs",', "")).some((v) => v.includes("discriminator")),
1970
1977
  "inspector catches the SDK suffix list drifting from the pinned SDK_SCRIPT_SUFFIXES",
1971
1978
  );
1972
1979
  // The no-shell negatives must be caught IN THE LEAF WINDOW, in both the
1973
1980
  // minified and the plain spelling — the global one-spelling absence check
1974
1981
  // this replaced passed them all.
1975
1982
  assert.ok(
1976
- inspect(goodAcp, goodSdk.replace("windowsHide:!0", "windowsHide:!0,shell:!0")).some((v) =>
1983
+ inspect(goodCli, goodAcp, goodSdk.replace("windowsHide:!0", "windowsHide:!0,shell:!0")).some((v) =>
1977
1984
  v.includes("no-shell"),
1978
1985
  ) &&
1979
- inspect(goodAcp, goodSdk.replace("windowsHide:!0", "windowsHide:!0,shell:true")).some((v) =>
1986
+ inspect(goodCli, goodAcp, goodSdk.replace("windowsHide:!0", "windowsHide:!0,shell:true")).some((v) =>
1980
1987
  v.includes("no-shell"),
1981
1988
  ) &&
1982
- inspect(goodAcp, goodSdk.replace("spawnLocalProcess(", "spawnElsewhere(")).some((v) => v.includes("anchor")),
1989
+ inspect(goodCli, goodAcp, goodSdk.replace("spawnLocalProcess(", "spawnElsewhere(")).some((v) =>
1990
+ v.includes("anchor"),
1991
+ ),
1983
1992
  "inspector catches shell:!0 AND shell:true inside the spawn leaf, and the leaf disappearing entirely",
1984
1993
  );
1985
1994
 
1986
1995
  // Installed dists — the actual §11-7-c load-bearing assumptions.
1987
1996
  const rootRequire = createRequire(resolve(REPO_ROOT, "package.json"));
1988
1997
  const acpPkgJson = rootRequire.resolve("@agentclientprotocol/claude-agent-acp/package.json");
1989
- const acpDist = readFileSync(join(dirname(acpPkgJson), "dist", "acp-agent.js"), "utf8");
1998
+ const cliDist = readFileSync(join(dirname(acpPkgJson), "dist", "claude-cli.js"), "utf8");
1999
+ const acpDist = readFileSync(join(dirname(acpPkgJson), "dist", "v1", "acp-agent.js"), "utf8");
1990
2000
  const acpRequire = createRequire(acpPkgJson);
1991
2001
  const sdkEntry = acpRequire.resolve("@anthropic-ai/claude-agent-sdk");
1992
2002
  let sdkDist = readFileSync(sdkEntry, "utf8");
@@ -1995,9 +2005,9 @@ function intervention(
1995
2005
  if (existsSync(sibling)) sdkDist = readFileSync(sibling, "utf8");
1996
2006
  }
1997
2007
  assert.deepEqual(
1998
- inspect(acpDist, sdkDist),
2008
+ inspect(cliDist, acpDist, sdkDist),
1999
2009
  [],
2000
- "installed acp-agent.js + sdk.mjs still carry every §11-7-c launch-semantics assumption (claudeCliPath verbatim env, override key order, suffix discriminator, node|bun choice, piped no-shell spawn)",
2010
+ "installed claude-cli.js + v1/acp-agent.js + sdk.mjs still carry every §11-7-c launch-semantics assumption (claudeCliPath verbatim env, override key order, suffix discriminator, node|bun choice, piped no-shell spawn)",
2001
2011
  );
2002
2012
  }
2003
2013
 
@@ -2050,7 +2060,7 @@ function intervention(
2050
2060
  );
2051
2061
  assert.ok(
2052
2062
  RUNNER_SRC.includes("CLI_TARGET = await resolveProbeCliTarget({") &&
2053
- RUNNER_SRC.includes('"@agentclientprotocol/claude-agent-acp/dist/acp-agent.js"') &&
2063
+ RUNNER_SRC.includes('join(dirname(pkgJson), "dist", "claude-cli.js")') &&
2054
2064
  RUNNER_SRC.includes("`precondition-${err.reason}`"),
2055
2065
  "the runner resolves the target through upstream claudeCliPath BEFORE any run, and a precondition refusal writes a NAMED classification on the artifact (not stderr alone)",
2056
2066
  );
@@ -260,30 +260,18 @@ function runSubcommand(sub: string, env: Record<string, string | undefined>): {
260
260
  // is the second skip surface: some smokes never reach their .ts file at all.
261
261
  // ===========================================================================
262
262
  {
263
- const viaWrapper = runSubcommand("smoke-acp-cortex-live", { LIVE: undefined });
263
+ const viaWrapper = runSubcommand("smoke-entwurf-v2-matrix-live", { LIVE: undefined });
264
264
  assert.equal(
265
265
  viaWrapper.code,
266
266
  LIVE_SKIP_EXIT,
267
267
  `[QK:WRAPPER-SKIP-IS-PROTOCOL-EXIT] a run.sh smoke WRAPPER that declines a prerequisite must return the protocol ` +
268
- `SKIP code, not 0 — got exit ${viaWrapper.code}. The wrapper is a skip surface of its own (cortex and matrix ` +
269
- "both decline before their .ts is ever reached), so fixing only the TypeScript " +
268
+ `SKIP code, not 0 — got exit ${viaWrapper.code}. The matrix wrapper declines before its .ts is reached, so fixing only the TypeScript ` +
270
269
  `half would leave the aggregate counting those as PASS. Output: ${JSON.stringify(viaWrapper.output.slice(-300))}`,
271
270
  );
272
271
 
273
- // The measured Cortex cell: LIVE IS set, but the connection the adapter needs
274
- // is not. Which branch declines (the wrapper's `cortex` PATH check or the
275
- // smoke's own connection check) depends on the host; the OUTCOME must not.
276
- const missingPrereq = runSubcommand("smoke-acp-cortex-live", {
277
- LIVE: "1",
278
- ENTWURF_ACP_CORTEX_CONNECTION: undefined,
279
- });
280
- assert.equal(
281
- missingPrereq.code,
282
- LIVE_SKIP_EXIT,
283
- "LIVE=1 with no ENTWURF_ACP_CORTEX_CONNECTION must be a SKIP, not a PASS — this is the exact cell that made a " +
284
- "cortex-less host look like cortex acceptance. Output: " +
285
- JSON.stringify(missingPrereq.output.slice(-300)),
286
- );
272
+ const disabled = runSubcommand("smoke-acp-cortex-live", { LIVE: undefined });
273
+ assert.equal(disabled.code, 2, "disabled Cortex smoke must refuse before a vendor probe");
274
+ assert.match(disabled.output, /cortex-acp-disabled/);
287
275
  }
288
276
 
289
277
  // ===========================================================================
@@ -312,11 +300,9 @@ function runSubcommand(sub: string, env: Record<string, string | undefined>): {
312
300
  "smoke-agy-native-push-live": ["VERIFY.md", "Aggregate release-gate does not own an agy conversation id"],
313
301
  "smoke-codex-native-push-live": ["VERIFY.md", "Aggregate release-gate does not own a loaded Codex thread"],
314
302
  "smoke-acp-ordering-probe-live": ["docs/acp-backend-rail.md", "opt-in paired observation"],
315
- // Cortex needs an external Snowflake connection the HOST owns, so an aggregate
316
- // that required it would block every cut taken without that account. Excluded —
317
- // NOT waived: its direct call stays required for a Cortex-rail cut, and running
318
- // it without the connection still reports protocol SKIP rather than a pass.
319
- "smoke-acp-cortex-live": ["VERIFY.md", "The release aggregate does not re-certify Cortex"],
303
+ // Cortex ACP is disabled. Its former direct LIVE requirement is historical;
304
+ // the retained command refuses by name without starting a vendor probe.
305
+ "smoke-acp-cortex-live": ["VERIFY.md", "Cortex ACP is disabled"],
320
306
  // #116 C4: first evidence precedes promotion. The herdr rail's LIVE acceptance exists and
321
307
  // runs on demand, but a rail whose first real acceptance run is days old has not earned a
322
308
  // place in the aggregate that blocks every cut — and herdr is an OPTIONAL rail, so a cut
@@ -1066,7 +1052,7 @@ console.log(
1066
1052
  "everything else→FAIL (never rounding a skip up), `--cut` refuses a MUST SKIP while a bare diagnostic run stays " +
1067
1053
  "exit 0, no LIVE smoke still carries the pre-P1 exit-0 skip shape, and both real skip surfaces were INVOKED and " +
1068
1054
  "observed to propagate the code: a smoke with LIVE unset (through run_ts, with its operator-readable marker) and " +
1069
- "a run.sh wrapper declining its own prerequisite (including the measured LIVE=1 no-cortex-connection cell); and every " +
1055
+ "a run.sh wrapper declining its own prerequisite; the retired Cortex smoke refuses by name; and every " +
1070
1056
  "LIVE smoke is either wired into release_gate or excluded by a sentence the docs still carry; and the moved " +
1071
1057
  "check-gate-qualification stays reachable only as the schedule/dispatch drift diagnostic (absent from the " +
1072
1058
  "default chain, from release_gate, from every push and from the release skill; exactly once in CI) over the " +
@@ -92,8 +92,9 @@ export function hashFileSha256(path: string): string {
92
92
 
93
93
  /** Refuse if the given env carries the upstream override — KEY PRESENCE is the
94
94
  * predicate, an empty string included. Upstream consumers disagree about
95
- * empty (`??` at acp-agent.js:4083 treats "" as set and passes it on; a
96
- * truthy check treats it as unset), and the probe refuses the ambiguity
95
+ * empty (0.85.1's `??` at acp-agent.js:4083 treated "" as set and passed it
96
+ * on; 0.106.0's truthy check at dist/claude-cli.js:20 treats it as unset), and
97
+ * the probe refuses the ambiguity
97
98
  * instead of picking a side. Used twice: on the runner's own process.env
98
99
  * before resolving, and on the COMPOSED spawn env of every ACP child (adapter
99
100
  * launch defaults / overlay overrides could inject what process.env did not
@@ -52,32 +52,6 @@
52
52
  "signature": "[QK:CARRIER-FRAME-NAMES-CLAUDE-CARRIER]",
53
53
  "signatureSource": "scripts/check-acp-carrier-augment.ts"
54
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
55
  {
82
56
  "claim": "CARRIER-LEADS-ITS-OWN-BLOCK",
83
57
  "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)",