@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
@@ -23,11 +23,11 @@ evidence split, and its three states must not collapse into "macOS is supported"
23
23
 
24
24
  ## Requirements
25
25
 
26
- The requirements table and pinned npm examples describe published Entwurf 0.51.0, whose exact SDK set is 1.5.0. The unpublished 0.52.0 source candidate (#158) instead pins SDK 1.5.2 and declares host Pi `>=1.5.2 <1.6`. Source validation, publication and a running host's loaded bytes are separate evidence; an addon-less validation tarball has been installed on one operating host, with same-identity return observed for the operating coordinator; that bounded observation is not full release or all-rail acceptance (see the [#158 candidate boundary](#sdk-152-candidate-boundary-158)).
26
+ The current published Entwurf 0.52.0 uses the exact Pi 1.5.2 SDK set and host Pi `>=1.5.2 <1.6`. The marked 0.51.0 / SDK 1.5.0 examples below are historical and remain bound to their own published manifest. Source validation, publication and a running host's loaded bytes are separate evidence; the earlier addon-less validation tarball installed on one operating host is not the published artifact (see the [#158 boundary](#sdk-152-candidate-boundary-158)).
27
27
 
28
28
  The validation TGZ installed in #155's operating window before the D1 display repair is an earlier, separate artifact, not the published one; see the [SDK 1.5 boundary](#sdk-15-development-boundary-155) below.
29
29
 
30
- The previous package, `0.50.0`, ships the Pi 1.4.0 SDK set and requires a Pi 1.4.x host; the setup guide inside that tarball is its contract.
30
+ The older package, `0.50.0`, ships the Pi 1.4.0 SDK set and requires a Pi 1.4.x host; the setup guide inside that tarball is its contract.
31
31
 
32
32
  | Component | Requirement | Needed for |
33
33
  |---|---|---|
@@ -35,14 +35,13 @@ The previous package, `0.50.0`, ships the Pi 1.4.0 SDK set and requires a Pi 1.4
35
35
  | npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
36
36
  | Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot/Codex wiring writers |
37
37
  | entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
38
- | pi | optional-by-presence — published 0.51.0: <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->`>=1.5.0 <1.6`<!-- /entwurf:published -->; #158 source candidate: `>=1.5.2 <1.6`. Absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
39
- | Native durable (`pi-durable`) | supplied by the Entwurf package (#130): Entwurf's owned app carrier plus its exact pi SDK set (1.5.0 in published 0.51.0; 1.5.2 in the #158 source candidate) as production dependencies — setup PASS when they verify, a named FAIL otherwise (never SKIP) | the `entwurf pi-durable` contact; native birth/LIVE on this supply surface is a separate release proof (§2b) |
38
+ | pi | optional-by-presence — current 0.52.0: `>=1.5.2 <1.6`; historical 0.51.0: <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->`>=1.5.0 <1.6`<!-- /entwurf:published -->. Absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
39
+ | Native durable (`pi-durable`) | supplied by the Entwurf package (#130): Entwurf's owned app carrier plus its exact pi SDK set (1.5.2 in published 0.52.0; 1.5.0 in historical 0.51.0) as production dependencies — setup PASS when they verify, a named FAIL otherwise (never SKIP) | the `entwurf pi-durable` contact; native birth/LIVE on this supply surface is a separate release proof (§2b) |
40
40
  | Claude Code | optional, **`>=2.1.306`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
41
41
  | GitHub Copilot CLI | optional-by-presence, operator-installed and authenticated — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/receiver/footer) | self-fetch citizen and visible fresh |
42
42
  | OMP (`omp`) | optional-by-presence, operator-installed — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/`tools.xdev` setting/receiver) | self-fetch citizen and visible fresh (accepted on one host — see §4b) |
43
43
  | Antigravity `agy` | optional, operator-installed and authenticated | legacy citizen; native push paused (#152) |
44
44
  | OpenAI Codex CLI | optional-by-presence, operator-installed and authenticated; detected composes all four operator-owned units (birth/MCP/status-line/terminal-title) and stays non-green until the operator trusts the birth declaration once in a visible Codex; visible fresh also requires the operator-owned app-server, started with `entwurf codex-app-server` in a tmux session of the operator's choosing (#95 D1 retired the fixed `codex` home) | native-push and visible fresh, supported in 0.21.0 |
45
- | Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
46
45
 
47
46
  Claude Code >=2.1.306 is required for the managed exec-hook lifecycle and same-id resume. The package
48
47
  never supplies or proxies backend credentials.
@@ -85,8 +84,8 @@ node --version
85
84
  npm --version
86
85
  ```
87
86
 
88
- The two npm forms below install the **published** package, `0.51.0` (Pi 1.5.x, the Pi 1.5.0 SDK
89
- set); they do not install the #158 source candidate (SDK 1.5.2), which is not published — a developer runs that from a source checkout (below). The [product entry](../README.md#install)'s unpinned `npm install -g @junghanacs/entwurf`
87
+ The two npm forms below install the **published** package, `0.52.0` (Pi 1.5.x, the Pi 1.5.2 SDK
88
+ set). The [product entry](../README.md#install)'s unpinned `npm install -g @junghanacs/entwurf`
90
89
  takes whichever version npm's `latest` dist-tag names; pin `@<version>` as below when you mean the
91
90
  exact artifact the [Requirements](#requirements) table describes.
92
91
 
@@ -94,7 +93,7 @@ Global install is simplest when native harnesses should find stable bins from ev
94
93
  working directory:
95
94
 
96
95
  ```bash
97
- npm install -g @junghanacs/entwurf@0.51.0
96
+ npm install -g @junghanacs/entwurf@0.52.0
98
97
  entwurf --help
99
98
  entwurf setup /path/to/your-project
100
99
  entwurf check-bridge
@@ -105,7 +104,7 @@ A project-local installation is also supported:
105
104
  ```bash
106
105
  mkdir -p ~/entwurf-smoke && cd ~/entwurf-smoke
107
106
  npm init -y
108
- npm install --save-dev @junghanacs/entwurf@0.51.0
107
+ npm install --save-dev @junghanacs/entwurf@0.52.0
109
108
  npx entwurf setup .
110
109
  npx entwurf check-bridge
111
110
  ```
@@ -115,10 +114,10 @@ project-local install, or `./run.sh <command>` from a source checkout. Keep that
115
114
  same scope when upgrading and checking; native process restarts are separate (§7).
116
115
 
117
116
  `check-bridge` is auth-free. It proves the installed prebuilt MCP server boots and
118
- lists the nine garden tools (0.51.0); it does not prove a backend model turn, native hook or retirement permission.
117
+ lists the nine garden tools (0.52.0); it does not prove a backend model turn, native hook or retirement permission.
119
118
 
120
119
  Neither npm form installs a harness runtime. `pi`, Claude Code, Copilot CLI, Codex CLI, agy,
121
- Cortex, and their authentication remain operator-owned optional prerequisites for the
120
+ and their authentication remain operator-owned optional prerequisites for the
122
121
  integrations that use them; all may be absent on an Entwurf-only host. A source checkout's
123
122
  pinned Pi development packages are for building and testing this repo, not a transitive product installation promise.
124
123
 
@@ -226,12 +225,11 @@ physical-Darwin receipt. `entwurf install` may write `.pi/settings.json`
226
225
  on Darwin; that is wiring, not a rail receipt. If `pi` is on PATH,
227
226
  `setup` names the pi row FAIL with the §1 Darwin wording.
228
227
 
229
- <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->
230
- For published Entwurf 0.51.0, install its exact host Pi floor and wire the project as shown below.
231
- The previous 0.50.0 uses the Pi 1.4.x host named in its own setup guide:
228
+ For published Entwurf 0.52.0, install its exact host Pi floor and wire the project as shown below.
229
+ The previous 0.51.0 uses the Pi 1.5.0 floor named in its own setup guide:
232
230
 
233
231
  ```bash
234
- npm install -g @earendil-works/pi-coding-agent@1.5.0
232
+ npm install -g @earendil-works/pi-coding-agent@1.5.2
235
233
  pi --version
236
234
 
237
235
  cd ~/entwurf-smoke
@@ -239,13 +237,12 @@ entwurf install .
239
237
  pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
240
238
  ```
241
239
 
242
- The supported host Pi range for published Entwurf 0.51.0 is `>=1.5.0 <1.6`.
240
+ The supported host Pi range for published Entwurf 0.52.0 is `>=1.5.2 <1.6`.
243
241
  A detected host outside that range is refused for Pi wiring; Entwurf does not upgrade it.
244
242
  Align the host explicitly, for example with the exact npm command above, before wiring Pi. A host using only the external MCP bridge can skip pi until it needs a
245
243
  control socket; no delivery rail launches a pi process.
246
- <!-- /entwurf:published -->
247
244
 
248
- The #158 source candidate instead requires host Pi `>=1.5.2 <1.6`; checkout `setup` reports a detected older host as FAIL. Prepare host Pi 1.5.2 before wiring this checkout, and use the checkout path rather than the global npm package for a direct `pi -e` invocation. Host Pi 1.5.2 also falls within published 0.51.0's declared range, but #158 has not measured that pairing; range eligibility does not replace installation, validation or operating approval. Ordinary Pi 1.5.2 defaults its global `cacheWarming` setting to `idle`, which may send additional provider requests; pi-durable does not map that setting. See the [#158 durable boundary](./durable-native-support.md#sdk-152-candidate-boundary-158).
245
+ Published 0.52.0 requires host Pi `>=1.5.2 <1.6`; `setup` reports a detected older host as FAIL. Prepare host Pi 1.5.2 before wiring, and use the checkout path rather than the global npm package when running a checkout directly. Range eligibility does not replace installation, validation or operating approval. Ordinary Pi 1.5.2 defaults its global `cacheWarming` setting to `idle`, which may send additional provider requests; pi-durable does not map that setting. See the [#158 durable boundary](./durable-native-support.md#sdk-152-candidate-boundary-158).
249
246
 
250
247
  For daily garden-native pi sessions:
251
248
 
@@ -264,7 +261,7 @@ A plain `pi` can load the package without becoming a citizen.
264
261
  In that session ask for `entwurf_self`, then `entwurf_peers`. Pick an existing
265
262
  live garden id and ask for `entwurf_v2` with `intent: "fire-and-forget"` and your
266
263
  message. These are harness tools, not shell subcommands; pi exposes them through
267
- Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The 0.51.0 bridge has nine tools;
264
+ Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The bridge has nine tools;
268
265
  pi hides `entwurf_inbox_read` and `entwurf_register_native`, leaving seven active.
269
266
  The added `entwurf_retire_call` is only for one target/action explicitly requested by the
270
267
  human operator, never autonomous cleanup. Where a host provides or is configured with an approval
@@ -349,12 +346,12 @@ by `entwurf pi` or by registering ordinary Pi's adapter.
349
346
 
350
347
  What the Entwurf package supplies:
351
348
 
352
- 1. **The pi SDK set** — published 0.51.0 supplies `@earendil-works/{pi-coding-agent, pi-ai, pi-tui, pi-agent-core,
353
- pi-durable, chord, pi-mcp, pi-codemode, pi-telemetry, pi-schema}` at exactly `1.5.0`, plus
354
- `proper-lockfile`, as Entwurf's own production dependencies; its model data rides <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->`@earendil-works/pi-ai@1.5.0`<!-- /entwurf:published -->, not a catalog copy.
355
- The #158 source candidate supplies the same ten-member set at exactly `1.5.2` (its model data rides the 1.5.2 pi-ai). The
356
- [SDK pin](../pi/pi-durable/overlay/upstream-pin.json) and [package manifest](../package.json) are the authority
357
- for the checkout you hold: they describe the source candidate, not the immutable published 0.51.0 bytes.
349
+ 1. **The pi SDK set** — published 0.52.0 supplies `@earendil-works/{pi-coding-agent, pi-ai, pi-tui, pi-agent-core,
350
+ pi-durable, chord, pi-mcp, pi-codemode, pi-telemetry, pi-schema}` at exactly `1.5.2`, plus
351
+ `proper-lockfile`, as production dependencies. Model data rides `pi-ai@1.5.2`, not a catalog copy.
352
+ Historical 0.51.0 used <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->`@earendil-works/pi-ai@1.5.0`<!-- /entwurf:published -->.
353
+ The [SDK pin](../pi/pi-durable/overlay/upstream-pin.json) and [package manifest](../package.json)
354
+ describe the checkout you hold, not immutable published bytes.
358
355
  2. **The native app/TUI carrier** — upstream does not publish the experimental durable
359
356
  app (`pi-coding-agent` excludes `dist/experimental`; `pi-durable` on npm is the
360
357
  library). Entwurf owns the six app files as canonical source (`pi/pi-durable/src`,
@@ -364,7 +361,7 @@ What the Entwurf package supplies:
364
361
  (`pi/pi-durable/carrier-resolver.mjs`), which maps them to the published package's
365
362
  own `dist/` as installed — never through a source checkout.
366
363
 
367
- The upstream library guide for the published 0.51.0 pairing is [v1.5.0](https://github.com/earendil-works/pi/blob/v1.5.0/packages/durable/README.md); for the #158 candidate's SDK APIs use [v1.5.2](https://github.com/earendil-works/pi/blob/v1.5.2/packages/durable/README.md).
364
+ The upstream library guide for the published 0.51.0 pairing is [v1.5.0](https://github.com/earendil-works/pi/blob/v1.5.0/packages/durable/README.md); for the published 0.52.0 SDK APIs use [v1.5.2](https://github.com/earendil-works/pi/blob/v1.5.2/packages/durable/README.md).
368
365
  The [experimental app guide](https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/src/experimental/durable/README.md)
369
366
  (last published at v1.1.0) records the app's origin: Entwurf's maintained app source, not a newer upstream app, is the current composition authority. Both guides describe what the carrier is. Running the bare upstream app does not attach Entwurf's
370
367
  garden contact. Entwurf does not rebuild the controller/model/auth/storage system or
@@ -379,7 +376,7 @@ carrier file, the license, the resolver and its relocation grammar are present
379
376
  (`pi-durable-carrier-drift`); every declared SDK package resolves from the carrier
380
377
  (`pi-durable-sdk-absent`); and every reachable physical edge — from the carrier to each package,
381
378
  and from every package copy it reaches, at any depth, to its own `@earendil-works` dependencies —
382
- lands on a package of the declared name at exactly the pinned version (`pi-durable-sdk-mismatch`). The members' declarations are exact in the published 1.5.0 set and, measured on the registry tarballs (#158), in the 1.5.2 candidate set; even so, the physical package each import binds must be checked. Sharing
379
+ lands on a package of the declared name at exactly the pinned version (`pi-durable-sdk-mismatch`). The members' declarations are exact in the published 1.5.0 set and, measured on the registry tarballs (#158), in the 1.5.2 set; even so, the physical package each import binds must be checked. Sharing
383
380
  one copy with an aligned host install of the same version is an ordinary layout. A mixed
384
381
  tree (for example an override that binds pi-durable's `pi-ai` to an older published version) still
385
382
  *imports* — which is why the edge check exists — and is refused by name.
@@ -461,7 +458,7 @@ tree (for example an override that binds pi-durable's `pi-ai` to an older publis
461
458
  manifest field; it is a known constraint of supplying both from one package, accepted for 0.32,
462
459
  not a claim that every load path is harmless.
463
460
 
464
- The ordinary Pi CLI on PATH is a separate subject (§2; published 0.51.0 range <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->`>=1.5.0 <1.6`<!-- /entwurf:published -->, #158 source candidate range `>=1.5.2 <1.6`): a host Pi is
461
+ The ordinary Pi CLI on PATH is a separate subject (§2; historical 0.51.0 range <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->`>=1.5.0 <1.6`<!-- /entwurf:published -->, current 0.52.0 range `>=1.5.2 <1.6`): a host Pi is
465
462
  not needed to run pi-durable, and a host inside the range need not be the same copy as the
466
463
  carrier's. The native harnesses own credentials, model settings and their respective session stores — ordinary Pi JSONL and pi-durable SQLite; keep them
467
464
  in place rather than copying credentials or migrating ordinary Pi transcripts. Native
@@ -474,7 +471,7 @@ proof ([#130](https://github.com/junghan0611/entwurf/issues/130)).
474
471
 
475
472
  ### SDK 1.5 development boundary (#155)
476
473
 
477
- The published-0.51.0 instructions above describe the SDK 1.5.0 set that #155 shipped; the #155 SDK
474
+ The historical 0.51.0 instructions describe the SDK 1.5.0 set that #155 shipped; the #155 SDK
478
475
  1.5 development candidates that preceded it were distinct subjects. Synthetic migration and
479
476
  downgrade-refusal fixtures on `0408a66` did not authorize an operating first write. A separately
480
477
  approved 1.5 operating window and Native LIVE were later measured as described below; neither made
@@ -483,7 +480,7 @@ the validation TGZ the published artifact. Installing an older package is not a
483
480
 
484
481
  ### SDK 1.5.2 candidate boundary (#158)
485
482
 
486
- The 0.52.0 source candidate (#158) pins the Pi SDK at exactly 1.5.2 (ten members) and declares host Pi `>=1.5.2 <1.6`. Assigning that version does not publish or install it: the published instructions above still describe 0.51.0 / SDK 1.5.0, and an operating move requires its own installation and same-identity return receipts. The #155 receipts above prove their own subject only; none is inherited by 1.5.2.
483
+ The published 0.52.0 package (#158) pins the Pi SDK at exactly 1.5.2 (ten members) and declares host Pi `>=1.5.2 <1.6`. Publication does not install it on an operating host; an operating move requires its own installation and same-identity return receipts. The #155 receipts above prove their own subject only; none is inherited by 1.5.2.
487
484
 
488
485
  What #158 measured so far and what it did not is recorded in [the durable-native boundary](./durable-native-support.md#sdk-152-candidate-boundary-158) and in the #158 thread: registry tarball integrity and member edges, an isolated six-member scheduler comparison and a native SQLite cross-version read/append check. The pre-version checkpoint's frozen FULL belongs to its own recorded worktree. The earlier validation candidate has its own floor and installation receipts; later source edits and the final carrier-bearing artifact require their own acceptance. Operating-scale and interrupted sessions are not certified by the bounded fixtures. An operating move follows the default radius below, each step with its own receipt.
489
486
 
@@ -998,7 +995,7 @@ real initial Pi outside `codex`, then omitted-placement Codex and outbound Pi in
998
995
  callbacks and addressed delivery both ways. `DELIVERY.md` owns that 57-assertion receipt, its four
999
996
  coordinates, and its digest as history. The caller-seat re-run is **accepted** — 2026-09-16, 56
1000
997
  assertions for the seat topology and 65 for the caller-DIRECTORY axis, both exit 0 — and it, not the
1001
- fixed home, is the current contract (`DELIVERY.md`, `BASELINE.md:37-38`, `VERIFY.md` release
998
+ fixed home, is the current contract (`DELIVERY.md`, BASELINE.md's release-host table (Codex caller-seat rows), `VERIFY.md` release
1002
999
  acceptance).
1003
1000
 
1004
1001
  `entwurf_fresh_call` accepts backend `codex` — supported in 0.21.0 — and requires an explicit model. Its
@@ -1078,7 +1075,7 @@ LIVE=1 AGY_CONVERSATION_ID=<id> entwurf smoke-agy-native-push-live
1078
1075
 
1079
1076
  ACP turns are CERTIFIED on Linux; on macOS they are NOT CERTIFIED —
1080
1077
  pending physical host. Mux (visible fresh / resume) is the same state.
1081
- A CI runner has no Claude or Cortex login.
1078
+ A CI runner has no Claude login.
1082
1079
 
1083
1080
  Claude uses the operator's existing local Claude authentication:
1084
1081
 
@@ -1086,16 +1083,10 @@ Claude uses the operator's existing local Claude authentication:
1086
1083
  LIVE=1 entwurf smoke-acp-provider-live
1087
1084
  ```
1088
1085
 
1089
- Cortex requires an authenticated `cortex` CLI and an explicit connection. Keep
1090
- `CORTEX_HOME` unset; the adapter refuses its presence because it bypasses containment.
1091
-
1092
- ```bash
1093
- LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> \
1094
- entwurf smoke-acp-cortex-live
1095
- ```
1096
-
1097
- The aggregate release gate is Claude-backed and does not run Cortex automatically.
1098
- Its silence is not a Cortex PASS.
1086
+ Cortex ACP is disabled. Existing Cortex configuration and credentials are not modified.
1087
+ Entwurf fresh/resume exact admission refuses retired Cortex ids; the ACP adapter also
1088
+ refuses a Cortex id that reaches it, without a Claude fallback. Pi's own direct `--model`
1089
+ fuzzy selection precedes that adapter; its result for a typed retired id is unmeasured here.
1099
1090
 
1100
1091
  ### Backend prerequisites and resolver
1101
1092
 
@@ -1107,12 +1098,10 @@ operator-installed Claude runtime. Resolution order:
1107
1098
  2. `require.resolve(...)` against the bundled `@agentclientprotocol/claude-agent-acp`.
1108
1099
  3. `PATH:claude-agent-acp` fallback if package resolution fails.
1109
1100
 
1110
- The curated Claude ids are `claude-opus-5-5`, `claude-sonnet-5-5`, and
1111
- `claude-fable-5-1`. Cortex exposes `cortex-auto`, `cortex-claude-opus-5`,
1112
- `cortex-claude-sonnet-5`, and `cortex-openai-gpt-5.4`.
1113
- `entwurfProvider.cortexConnection` or `ENTWURF_ACP_CORTEX_CONNECTION` selects
1114
- an authenticated connection; the env override wins. Codex and Antigravity are
1115
- native-push citizens (§4c/§5), not ACP backends.
1101
+ The curated Claude ids are `claude-opus-5-5`, `claude-sonnet-5-5`,
1102
+ `claude-fable-5-1`, and `claude-fable-5-2`. Cortex ACP is disabled; a legacy
1103
+ `entwurfProvider.cortexConnection` does not select a backend or block Claude.
1104
+ Codex and Antigravity are native-push citizens (§4c/§5), not ACP backends.
1116
1105
 
1117
1106
  ### Settings reference and backend-specific keys
1118
1107
 
@@ -1147,15 +1136,11 @@ retired bundled `session-bridge` entry. It is not a general MCP catalog.
1147
1136
  user augment. Large Claude `_meta.systemPrompt` carriers can route OAuth sessions
1148
1137
  to metered “extra usage” billing.
1149
1138
 
1150
- `entwurfProvider` is one block, not a promise that every key shapes both backends.
1151
1139
  `tools`, `permissionAllow`, `disallowedTools`, `settingSources`, `skillPlugins`
1152
- and `appendSystemPrompt` are Claude's declaration surface, not Cortex's native
1153
- tool surface. They still enter the config signature (and `tools` the common
1154
- exclude-tools preflight), so editing them forces a fresh Cortex session.
1155
- `cortexConnection` is Cortex-only; `mcpServers` reaches both through their own
1156
- transports. `compaction` is a **top-level pi key**, not a backend knob. Unknown
1157
- and retired keys are ignored; a wrong-backend key can fail silently, so check
1158
- which backend consumes it before diagnosing bridge failure.
1140
+ and `appendSystemPrompt` are Claude's declaration surface. `mcpServers` carries
1141
+ explicit servers. `compaction` is a **top-level pi key**, not a backend knob.
1142
+ The retired `cortexConnection` key is not consumed by Claude and does not enable
1143
+ Cortex ACP; unknown and retired keys are ignored.
1159
1144
 
1160
1145
  ### Backend operation and containment
1161
1146
 
@@ -1170,12 +1155,8 @@ Read/Bash/Edit/Write; `Skill` is added for non-empty `skillPlugins`.
1170
1155
  The launch env sets `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1`: vendor-managed
1171
1156
  background work is off, not arbitrary processes a command detaches itself.
1172
1157
 
1173
- Cortex uses its native tools, session-private HOME / `SNOWFLAKE_HOME`, and private
1174
- `cortex/mcp.json` projection because it ignores wire `mcpServers`. Only the bridge
1175
- receives the real operator HOME needed for the garden store; `CORTEX_HOME` is
1176
- refused, including an empty value, because it bypasses containment.
1177
1158
  [ACP backend rail — Shipped adapters](./acp-backend-rail.md#shipped-adapters)
1178
- owns the exact overlay/carrier/backend mechanics and audit limits.
1159
+ owns the exact Claude overlay/carrier mechanics and audit limits.
1179
1160
 
1180
1161
  Entwurf owns no memory layer or memory DB, and no backend is another's memory
1181
1162
  authority. Operator semantic-memory / Denote tools remain host-native skills,
@@ -1210,7 +1191,7 @@ Non-empty `skillPlugins` auto-adds `Skill` to `tools` and `Skill(*)` to
1210
1191
  path or missing `.claude-plugin/plugin.json` before starting the session; the
1211
1192
  SDK, not the bridge, validates plugin.json contents and SKILL.md bodies.
1212
1193
  Start a fresh Claude session and ask it to list callable skills. The operator
1213
- judgement is `Q-SKILL-CALLABLE` in [VERIFY](../VERIFY.md).
1194
+ judgement is asking the session which skills it can call; it is not a named row in [VERIFY](../VERIFY.md).
1214
1195
  This is Claude-only; Codex uses native `~/.codex/skills/` passthrough.
1215
1196
  [agent-config](https://github.com/junghan0611/agent-config) is a real multi-harness
1216
1197
  consumer, not a requirement for this setup.
@@ -1236,8 +1217,7 @@ Bridge identity, pi context, `~/AGENTS.md`, cwd AGENTS and date/cwd ride a one-s
1236
1217
  first-user prepend (`pi-context-augment.ts`), not the system carrier. It removes
1237
1218
  duplicate cwd AGENTS already in `<project-context ...>`. Its descriptions do not
1238
1219
  grant tools: the actual callable schema is truth (`read` / `Read` / `exec_command`,
1239
- MCP only when schema-visible). Cortex engraving rides this augment because it
1240
- has no system-prompt carrier; Claude uses `_meta.systemPrompt`.
1220
+ MCP only when schema-visible). Claude uses `_meta.systemPrompt` for its engraving.
1241
1221
 
1242
1222
  ### Session reuse and compaction
1243
1223
 
@@ -1253,7 +1233,12 @@ owns the reuse policy and distinguishes live mapping from semantic recall.
1253
1233
  **Entwurf does not implement compaction.** A backend's native compaction preserves
1254
1234
  the pi session/mapping; configure its policy through that backend's own interface,
1255
1235
  not a pi-side JSONL summary. The bridge exposes no backend-specific compaction
1256
- knobs. The ACP footer uses `usage_update.used / size` (backend prompt/tools/cache/
1236
+ knobs. One fixed exception on the Claude ACP rail: its adapter's idle `/compact` (an autonomous
1237
+ turn on an idle session) is turned off inline (`settings.idleCompaction: false`), because pi would
1238
+ not see that turn; in-prompt auto-compaction stays the backend's, and a managed settings tier would
1239
+ still win over the inline value ([rail §Capability posture](./acp-backend-rail.md#capability-posture)).
1240
+ Pi's own summaries on a Claude ACP model (`/compact`, the threshold, overflow recovery, `/tree`) are not that backend compaction: each runs as a one-shot tool-less turn on a new child, and the session's retained child is closed after a compaction ([rail §11-10](./acp-backend-rail.md)).
1241
+ The ACP footer uses `usage_update.used / size` (backend prompt/tools/cache/
1257
1242
  session included) with `[entwurf:usage]` diagnostics. Near limit choose a visible
1258
1243
  action: clear, open a new session with a different model, or let the backend
1259
1244
  compact natively.
@@ -16,7 +16,7 @@
16
16
  // This module owns the `AcpConnectionLike` seam so the backend and the live
17
17
  // smokes both drive ONE adapter — the SDK method-name mapping lives here only,
18
18
  // and the backend's orchestration + the gate fakes stay untouched.
19
- import { AGENT_METHODS, CLIENT_METHODS, client, } from "@agentclientprotocol/sdk";
19
+ import { AGENT_METHODS, CLIENT_METHODS, client } from "@agentclientprotocol/sdk";
20
20
  /**
21
21
  * Production factory — wrap the SDK 1.1 fluent `client()` into the
22
22
  * `AcpConnectionLike` seam the backend (and the live smokes) drive.
@@ -23,7 +23,7 @@ import { fileURLToPath } from "node:url";
23
23
  import { RequestError } from "@agentclientprotocol/sdk";
24
24
  import { enrichMcpServersWithEnvelope } from "./config.js";
25
25
  import { loadEngraving } from "./engraving.js";
26
- import { CORTEX_MODEL_PREFIX, curatedClaudeModels, curatedCortexModels, SUPPORTED_ANTHROPIC_MODEL_IDS, SUPPORTED_CORTEX_MODEL_IDS, } from "./models.js";
26
+ import { CLAUDE_ACP_EFFORT_VALUES, CORTEX_MODEL_PREFIX, curatedClaudeModels, curatedCortexModels, LEGACY_CORTEX_MODEL_IDS, SUPPORTED_ANTHROPIC_MODEL_IDS, } from "./models.js";
27
27
  import { claudeLaunchEnvDefaults, ensureClaudeConfigOverlay, ensureCortexDualHomeOverlay } from "./overlay.js";
28
28
  import { buildClaudeSessionMeta } from "./tool-surface.js";
29
29
  // POSIX-safe single-quote wrapper for shell arg interpolation. Byte-for-byte
@@ -114,7 +114,7 @@ export const CLAUDE_FOREGROUND_ONLY_ENV = { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS
114
114
  * The set-model answer is read back before the prompt (#128). claude-agent-acp resolves the
115
115
  * requested id onto one of the rows it offers and answers with that row's `value` as the model
116
116
  * option's `currentValue` (0.85.1 `dist/acp-agent.js:5459-5499`). `[측정 2026-10-12]` 0.85.1, no
117
- * prompt: `claude-fable-5-1` answers its own row, while `claude-sonnet-5-5` and `claude-opus-5-5`
117
+ * prompt: `claude-fable-5-1` answers its own row (and, at 0.106.0 #142, so does `claude-fable-5-2`), while `claude-sonnet-5-5` and `claude-opus-5-5`
118
118
  * answer the alias rows `sonnet` and `opus`, named `Sonnet 5.5` and `Opus 5.5`. The id an alias row
119
119
  * resolves to (`ModelInfo.resolvedModel`) is not on the ACP wire, so an alias answer is accepted
120
120
  * only as the exact measured row, value AND name, declared here for the requested id. No other
@@ -148,6 +148,46 @@ export function confirmClaudeSetModelAnswer(response, nativeModelId) {
148
148
  `name the requested model. Refusing before the prompt so a different model cannot answer under this id; ` +
149
149
  `the installed driver and the curated id disagree, and repeating the turn cannot change that.`);
150
150
  }
151
+ const CLAUDE_EFFORT_VALUE_SET = new Set(CLAUDE_ACP_EFFORT_VALUES);
152
+ /** The named refusal for a thinking level ACP Claude cannot carry (#142). The text is fixed and
153
+ * must read as NON-transient to pi's retry and overflow classifiers: replaying the turn cannot
154
+ * change the level that was asked for. */
155
+ export const ACP_THINKING_LEVEL_UNSUPPORTED = "acp-thinking-level-unsupported";
156
+ /**
157
+ * Map pi's requested level to the claude-agent-acp effort value, or refuse by name (#142).
158
+ * The value is the registered row's `thinkingLevelMap` entry for that level — the documented pi
159
+ * meaning of the map — and must be one of the measured effort values. `undefined` (pi off, or no
160
+ * level) is refused rather than left at the vendor `default`: that would let the turn run at a
161
+ * depth nobody chose while the operator believes a level was applied.
162
+ */
163
+ export function resolveClaudeEffort(modelId, reasoning, thinkingLevelMap) {
164
+ const value = reasoning === undefined ? undefined : thinkingLevelMap?.[reasoning];
165
+ if (typeof value === "string" && CLAUDE_EFFORT_VALUE_SET.has(value))
166
+ return value;
167
+ const asked = reasoning === undefined
168
+ ? "no thinking level (pi sends none for `off`, and none when no level is set; the two are indistinguishable here)"
169
+ : `thinking level ${JSON.stringify(reasoning)}`;
170
+ throw new Error(`entwurf: ${ACP_THINKING_LEVEL_UNSUPPORTED} — ${modelId} over Claude ACP carries effort ` +
171
+ `${CLAUDE_ACP_EFFORT_VALUES.join("/")} only, and was asked for ${asked}. Choose one of those levels; ` +
172
+ `the turn was not sent, and repeating it at the same level cannot change that.`);
173
+ }
174
+ function claudeEffortRefusal(modelId, value, what, cause) {
175
+ const error = new Error(`entwurf: claude-agent-acp ${what} for effort ${JSON.stringify(value)} on ${modelId}. Refusing before the prompt ` +
176
+ `so the turn cannot run at an effort nobody chose; repeating the turn cannot change the driver's answer.`);
177
+ if (cause !== undefined)
178
+ error.cause = cause;
179
+ return error;
180
+ }
181
+ /** Refuse unless the set-effort answer reads back the requested value. */
182
+ export function confirmClaudeEffortAnswer(response, modelId, value) {
183
+ const configOptions = response?.configOptions;
184
+ const effort = Array.isArray(configOptions)
185
+ ? configOptions.find((option) => option?.id === "effort")
186
+ : undefined;
187
+ if (effort?.currentValue === value)
188
+ return;
189
+ throw claudeEffortRefusal(modelId, value, effort == null ? "answered with no effort option" : `answered effort ${jsonText(effort.currentValue)}`);
190
+ }
151
191
  export const claudeAdapter = {
152
192
  backend: "claude",
153
193
  // Claude owns its UNPREFIXED curated ids only (rail “Adapter contract”). The native id
@@ -206,6 +246,23 @@ export const claudeAdapter = {
206
246
  });
207
247
  confirmClaudeSetModelAnswer(answer, nativeModelId);
208
248
  },
249
+ resolveThinking({ modelId, reasoning, thinkingLevelMap }) {
250
+ return resolveClaudeEffort(modelId, reasoning, thinkingLevelMap);
251
+ },
252
+ async enforceThinking({ connection, acpSessionId, modelId, value }) {
253
+ const setConfig = connection.setSessionConfigOption;
254
+ if (typeof setConfig !== "function") {
255
+ throw new Error(`setSessionConfigOption unsupported — cannot set effort for ${modelId}`);
256
+ }
257
+ let answer;
258
+ try {
259
+ answer = await setConfig.call(connection, { sessionId: acpSessionId, configId: "effort", value });
260
+ }
261
+ catch (err) {
262
+ throw claudeEffortRefusal(modelId, value, "refused the set", err);
263
+ }
264
+ confirmClaudeEffortAnswer(answer, modelId, value);
265
+ },
209
266
  configSignatureFields(_adapterSettings) {
210
267
  // Claude folds no extra backend-specific fields beyond backend + nativeModelId
211
268
  // (which backend.ts adds). A future backend reads its own stable id off
@@ -226,21 +283,15 @@ export const claudeAdapter = {
226
283
  // ---------------------------------------------------------------------------
227
284
  // Registry — modelId → adapter
228
285
  // ---------------------------------------------------------------------------
229
- /** Registered adapters. Order carries NO routing authority — routeModel decides.
230
- * claude (unprefixed ids) + cortex (the `cortex-` prefix). A further backend
231
- * appends here with its own reserved prefix; the fail-fast below proves no two
232
- * adapters claim one id. */
233
- // ---------------------------------------------------------------------------
234
- // cortex adapter — Snowflake Cortex Code, the first non-claude backend on the
235
- // rail (docs/acp-backend-rail.md, “Shipped adapters”). It adds ZERO to the common layer:
236
- // everything cortex-specific lives here + models.ts + overlay.ts + the gates.
237
- // ---------------------------------------------------------------------------
238
- const SUPPORTED_CORTEX_IDS = new Set(SUPPORTED_CORTEX_MODEL_IDS);
286
+ // The former Cortex implementation is retained privately for historical source
287
+ // continuity only. The exported adapter below denies every side-effecting entry,
288
+ // and the production registry does not contain this implementation.
289
+ const SUPPORTED_CORTEX_IDS = new Set(LEGACY_CORTEX_MODEL_IDS);
239
290
  /** The env var an operator sets to pin a Snowflake connection per-shell without
240
291
  * editing settings.json. Wins over `entwurfProvider.cortexConnection`. The
241
292
  * ENTWURF_ACP_* convention (PR #40's legacy PI_SHELL_ACP* cortex-connection var renamed). */
242
293
  export const CORTEX_CONNECTION_ENV = "ENTWURF_ACP_CORTEX_CONNECTION";
243
- export const cortexAdapter = {
294
+ const retiredCortexAdapter = {
244
295
  backend: "cortex",
245
296
  // Cortex owns the reserved `cortex-` prefix (rail “Adapter contract”). routeModel strips it to the
246
297
  // native id: `cortex-auto` → "auto", `cortex-claude-sonnet-5` → "claude-sonnet-5".
@@ -373,7 +424,29 @@ export const cortexAdapter = {
373
424
  // server rejects a prompt with, so every cortex failure keeps the verbatim-first
374
425
  // errorMessage. Borrowing claude's `incomplete_tool_call` reader would be a guess.
375
426
  };
376
- const ADAPTERS = [claudeAdapter, cortexAdapter];
427
+ const cortexDisabled = () => {
428
+ throw new Error("entwurf: cortex-acp-disabled: Cortex ACP is not supported");
429
+ };
430
+ /** Retained exported shape refuses the retired adapter before a launch or overlay can be made. */
431
+ export const cortexAdapter = {
432
+ ...retiredCortexAdapter,
433
+ routeModel: () => undefined,
434
+ curatedModels: () => [],
435
+ resolveAdapterSettings: cortexDisabled,
436
+ resolveLaunch: cortexDisabled,
437
+ launchEnvDefaults: cortexDisabled,
438
+ ensureOverlay: cortexDisabled,
439
+ loadCarrier: cortexDisabled,
440
+ buildSessionMeta: cortexDisabled,
441
+ enforceModel: cortexDisabled,
442
+ configSignatureFields: cortexDisabled,
443
+ };
444
+ const ADAPTERS = [claudeAdapter];
445
+ /** A declared backend is only a guard, never a replacement routing authority. */
446
+ export function assertSupportedDeclaredBackend(backend) {
447
+ if (backend?.toLowerCase() === "cortex")
448
+ cortexDisabled();
449
+ }
377
450
  /**
378
451
  * Resolve the backend adapter that owns `modelId`.
379
452
  *
@@ -385,6 +458,9 @@ const ADAPTERS = [claudeAdapter, cortexAdapter];
385
458
  * so callers get `{ adapter, nativeModelId }` and never re-parse the id string.
386
459
  */
387
460
  export function resolveAcpBackendAdapter(modelId) {
461
+ if (modelId.toLowerCase().startsWith(CORTEX_MODEL_PREFIX)) {
462
+ throw new Error(`entwurf: cortex-acp-disabled: ${JSON.stringify(modelId)} is not supported`);
463
+ }
388
464
  const matches = [];
389
465
  for (const adapter of ADAPTERS) {
390
466
  const routed = adapter.routeModel(modelId);
@@ -16,15 +16,10 @@
16
16
  //
17
17
  // The COMMON keys parsed here are Claude-SHAPED by origin (tools / permissionAllow /
18
18
  // disallowedTools / settingSources / skillPlugins are claude-agent-acp's surface).
19
- // What varies per backend is whether they are PROJECTED ONTO THE BACKEND SESSION —
20
- // not whether they are read at all. Cortex runs native tools and reaches MCP through
21
- // its own overlay mcp.json projection, so none of those keys shape a cortex session;
22
- // they nonetheless stay live on the common path, where backend.ts reads `tools` for
23
- // the backend-invariant exclude-tools preflight and folds every one of them into
24
- // `bridgeConfigSignature` (so changing one still invalidates a reused cortex
25
- // session). Backend-OWNED settings never land here: they ride the opaque
26
- // `adapterSettings` seam (rail “Adapter contract”), which is what keeps a backend-named key like
27
- // `cortexConnection` off this common shape.
19
+ // The common path reads `tools` for the backend-invariant exclude-tools preflight
20
+ // and folds these keys into `bridgeConfigSignature`. Backend-owned settings ride
21
+ // the opaque `adapterSettings` seam. A dormant `cortexConnection` does not select
22
+ // or enable Cortex ACP, which is disabled.
28
23
  //
29
24
  // PURITY / SIGNATURE contract (NEXT oracle C / 핀1 / GPT `…2f9325` boost):
30
25
  // - `normalizeMcpServers` is pure: a SORTED, validated server list + a sha256
@@ -57,6 +57,17 @@ function textFromAssistantContent(content) {
57
57
  .map((c) => c.text)
58
58
  .join(""));
59
59
  }
60
+ /**
61
+ * The harness fact pi already holds about an assistant turn that was cut
62
+ * (`stopReason: "aborted"` — an operator abort, or a vendor `cancelled` mapped to it; the line
63
+ * names no cause), stated so a NEW session's transcript does not read a
64
+ * cancelled turn as a clean refusal (#142: a fresh session concluded a command had
65
+ * not run when its first effect was on disk). It adds no claim about what did or
66
+ * did not happen — only that the turn was cut and that a cut is not a rollback.
67
+ * Only the full transcript carries it: a reused session already holds its own
68
+ * record of the interruption and receives just the latest user delta.
69
+ */
70
+ export const ABORTED_TURN_STATUS = "[turn status: aborted — actions this turn had started may already have taken effect; cancellation is not rollback]";
60
71
  /** Render one pi message as a transcript line, or undefined to skip it. */
61
72
  function renderMessage(message) {
62
73
  switch (message.role) {
@@ -66,6 +77,10 @@ function renderMessage(message) {
66
77
  }
67
78
  case "assistant": {
68
79
  const text = textFromAssistantContent(message.content).trim();
80
+ if (message.stopReason === "aborted") {
81
+ // Kept even when the cut turn left no text: the cut itself is the fact.
82
+ return text ? `Assistant: ${text}\n${ABORTED_TURN_STATUS}` : `Assistant: ${ABORTED_TURN_STATUS}`;
83
+ }
69
84
  return text ? `Assistant: ${text}` : undefined;
70
85
  }
71
86
  case "toolResult": {
@@ -12,12 +12,9 @@
12
12
  // would need a `.ts` specifier the root config cannot allow.
13
13
  //
14
14
  // WHY THE LAUNCH SEAMS ASK AT ALL. Pi's `--model` resolver falls back to a
15
- // case-insensitive substring match among the provider's rows and prefers the
16
- // lexically later id. Under one `entwurf` provider a retired `claude-sonnet-5`
17
- // is a substring of `cortex-claude-sonnet-5`, so a launch Entwurf builds would
18
- // silently open on the Snowflake Cortex rail instead of the Claude subscription.
19
- // An exact match against this list is the only admission; nothing is aliased,
20
- // substituted or rewritten.
15
+ // case-insensitive substring match among provider rows. The old multi-backend
16
+ // surface could silently match a retired Claude id to a Cortex row. Cortex is
17
+ // now disabled; exact membership remains the admission, with no substitution.
21
18
  /** The single ACP provider id under which every curated row is registered. */
22
19
  export const PROVIDER_ID = "entwurf";
23
20
  // The curated Claude ids. Adding one here is a commitment to verify it across
@@ -26,20 +23,21 @@ export const SUPPORTED_ANTHROPIC_MODEL_IDS = /** @type {const} */ ([
26
23
  "claude-sonnet-5-5",
27
24
  "claude-opus-5-5",
28
25
  "claude-fable-5-1",
26
+ "claude-fable-5-2",
29
27
  ]);
30
28
  /** The anchor model whose absence is a hard registry regression, not a soft skip. */
31
29
  export const CURATED_ANCHOR_MODEL_ID = "claude-opus-5-5";
32
- // Cortex ids carry the reserved `cortex-` prefix; the rest of each id is the
33
- // Snowflake-native model name, which is why Cortex still says `claude-opus-5`.
30
+ // These former IDs are retained only as historical refusal inputs. The prefix
31
+ // is reserved so a disabled Cortex request never falls through to Claude.
34
32
  export const CORTEX_MODEL_PREFIX = "cortex-";
35
- export const SUPPORTED_CORTEX_MODEL_IDS = /** @type {const} */ ([
33
+ export const LEGACY_CORTEX_MODEL_IDS = /** @type {const} */ ([
36
34
  "cortex-auto",
37
35
  "cortex-claude-opus-5",
38
36
  "cortex-claude-sonnet-5",
39
37
  "cortex-openai-gpt-5.4",
40
38
  ]);
41
39
  /** @type {ReadonlySet<string>} */
42
- const CURATED_ACP_MODEL_IDS = new Set([...SUPPORTED_ANTHROPIC_MODEL_IDS, ...SUPPORTED_CORTEX_MODEL_IDS]);
40
+ const CURATED_ACP_MODEL_IDS = new Set(SUPPORTED_ANTHROPIC_MODEL_IDS);
43
41
  /**
44
42
  * Exact membership only — no trim, no case folding, no prefix.
45
43
  * @param {string} model
@@ -51,8 +49,7 @@ export function isCuratedAcpModelId(model) {
51
49
  /** The named refusal both launch seams answer with. */
52
50
  export const ACP_MODEL_NOT_CURATED = /** @type {const} */ ("acp-model-not-curated");
53
51
  export const ACP_MODEL_NOT_CURATED_HINT = `an ${PROVIDER_ID} model must be one exact curated ACP id (${[...CURATED_ACP_MODEL_IDS].join(", ")}); ` +
54
- "Pi would otherwise pick another row by substring — a retired claude-sonnet-5 lands on cortex-claude-sonnet-5 — " +
55
- "so nothing was opened, and nothing is aliased or rewritten";
52
+ "Cortex ACP is disabled; Pi may otherwise pick another row by substring, so nothing was opened and nothing is aliased or rewritten";
56
53
  /** Carries `reason` so callers match a FIELD, never the wording. */
57
54
  export class AcpModelNotCuratedError extends Error {
58
55
  /** @param {string} model */