@arnilo/prism 0.5.6 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/CHANGELOG.md +81 -5
  2. package/README.md +10 -10
  3. package/dist/agent-approval.js +7 -6
  4. package/dist/agent-definitions.js +1 -0
  5. package/dist/agent-loops.js +51 -12
  6. package/dist/agent-run-lifecycle.js +11 -0
  7. package/dist/agent-run-state.d.ts +6 -0
  8. package/dist/agent-run-state.js +29 -9
  9. package/dist/agent-session/session/assemble.js +33 -2
  10. package/dist/agent-session/session/persist.js +6 -2
  11. package/dist/agent-session/session/tool-round.js +1 -0
  12. package/dist/agent-session/session/types.d.ts +10 -0
  13. package/dist/agent-session/session.d.ts +15 -0
  14. package/dist/agent-session/session.js +59 -4
  15. package/dist/agent-tool-dispatch.js +5 -4
  16. package/dist/artifacts.d.ts +39 -1
  17. package/dist/artifacts.js +73 -0
  18. package/dist/attention-compiler.d.ts +121 -0
  19. package/dist/attention-compiler.js +479 -0
  20. package/dist/cli-init.js +20 -6
  21. package/dist/content.d.ts +3 -16
  22. package/dist/content.js +9 -99
  23. package/dist/context-budget.d.ts +32 -2
  24. package/dist/context-budget.js +51 -19
  25. package/dist/contracts-core/agent.d.ts +18 -0
  26. package/dist/contracts-core/agent.js +4 -1
  27. package/dist/contracts-core/attention.d.ts +66 -0
  28. package/dist/contracts-core/attention.js +2 -0
  29. package/dist/contracts-core/compaction.d.ts +59 -0
  30. package/dist/contracts-core/compaction.js +77 -1
  31. package/dist/contracts-core/provider.d.ts +4 -0
  32. package/dist/contracts-core.d.ts +1 -0
  33. package/dist/contracts-core.js +1 -0
  34. package/dist/contracts-protocol.d.ts +29 -0
  35. package/dist/contracts-run-state.d.ts +6 -0
  36. package/dist/host-composition.d.ts +78 -0
  37. package/dist/host-composition.js +248 -0
  38. package/dist/index.d.ts +11 -8
  39. package/dist/index.js +6 -5
  40. package/dist/input.d.ts +19 -1
  41. package/dist/input.js +52 -2
  42. package/dist/media-types.d.ts +34 -0
  43. package/dist/media-types.js +158 -0
  44. package/dist/pinned-fetch.d.ts +2 -2
  45. package/dist/pinned-fetch.js +11 -12
  46. package/dist/redaction.js +74 -1
  47. package/dist/secure-agent.d.ts +2 -0
  48. package/dist/secure-agent.js +6 -1
  49. package/dist/session-stores.d.ts +11 -0
  50. package/dist/session-stores.js +23 -8
  51. package/dist/tool-result-fold.d.ts +12 -0
  52. package/dist/tool-result-fold.js +13 -6
  53. package/dist/tools.d.ts +10 -0
  54. package/dist/tools.js +41 -0
  55. package/docs/acp-agent.md +42 -11
  56. package/docs/acp.md +3 -2
  57. package/docs/ag-ui.md +9 -5
  58. package/docs/agent-definitions.md +9 -1
  59. package/docs/agent-events.md +6 -1
  60. package/docs/agent-loops.md +1 -1
  61. package/docs/agent-session-runtime.md +9 -7
  62. package/docs/attention-compiler.md +272 -0
  63. package/docs/browser-automation.md +5 -2
  64. package/docs/cli-rpc.md +4 -2
  65. package/docs/coding-agent-tools.md +1 -1
  66. package/docs/coding-security.md +5 -3
  67. package/docs/coding-tools.md +1 -1
  68. package/docs/coding-workspaces.md +22 -0
  69. package/docs/compaction-and-retry.md +36 -4
  70. package/docs/compaction-observational-memory.md +62 -9
  71. package/docs/context-and-skills.md +4 -2
  72. package/docs/contributing.md +37 -0
  73. package/docs/conversations.md +1 -1
  74. package/docs/core.md +2 -0
  75. package/docs/dev-inspector.md +4 -0
  76. package/docs/device-adapters.md +1 -0
  77. package/docs/document-reader.md +12 -2
  78. package/docs/documents.md +11 -3
  79. package/docs/enterprise-postgres-state.md +2 -2
  80. package/docs/evaluations.md +168 -4
  81. package/docs/execution-timeline.md +180 -0
  82. package/docs/graft.md +3 -1
  83. package/docs/history/0.7.0-primitive-review.md +254 -0
  84. package/docs/history/migration-0.0.md +2 -2
  85. package/docs/history/release-handoffs.md +70 -1
  86. package/docs/host-compositions.md +147 -0
  87. package/docs/host-security.md +2 -2
  88. package/docs/hosted-sandboxes.md +94 -0
  89. package/docs/index.md +73 -41
  90. package/docs/input-and-prompt-assembly.md +5 -4
  91. package/docs/knowledge-sync.md +84 -0
  92. package/docs/language-intelligence.md +2 -2
  93. package/docs/live-testing.md +4 -1
  94. package/docs/mcp-tools.md +2 -1
  95. package/docs/memory-fabric.md +416 -0
  96. package/docs/migrate-to-0.5.md +8 -3
  97. package/docs/migrate-to-0.6.md +90 -0
  98. package/docs/migrate-to-0.7.md +345 -0
  99. package/docs/migration.md +43 -1
  100. package/docs/model-registry.md +1 -1
  101. package/docs/model-routing.md +79 -4
  102. package/docs/multi-agent-patterns.md +20 -6
  103. package/docs/multimodal-content.md +1 -1
  104. package/docs/obscura.md +3 -1
  105. package/docs/observability.md +52 -1
  106. package/docs/operations.md +13 -1
  107. package/docs/options-index.md +298 -0
  108. package/docs/peer-dependencies.md +96 -0
  109. package/docs/performance.md +34 -2
  110. package/docs/ponytail.md +2 -0
  111. package/docs/postgres-persistence.md +3 -1
  112. package/docs/process-sessions.md +3 -1
  113. package/docs/prompt-registry.md +1 -1
  114. package/docs/provider-caching.md +4 -2
  115. package/docs/provider-conformance.md +2 -2
  116. package/docs/provider-packages.md +23 -23
  117. package/docs/provider-primitives.md +2 -1
  118. package/docs/providers/ai-sdk.md +5 -2
  119. package/docs/providers/bedrock.md +71 -7
  120. package/docs/providers/openai.md +1 -1
  121. package/docs/public-contracts.md +2 -2
  122. package/docs/rag.md +24 -8
  123. package/docs/realtime-voice.md +87 -0
  124. package/docs/release-and-install.md +78 -56
  125. package/docs/runs-and-usage.md +3 -2
  126. package/docs/server.md +6 -4
  127. package/docs/session-stores.md +3 -1
  128. package/docs/speech.md +2 -0
  129. package/docs/sqlite-persistence.md +2 -0
  130. package/docs/supervisors.md +33 -5
  131. package/docs/testing.md +38 -0
  132. package/docs/thinking-and-reasoning.md +3 -1
  133. package/docs/tools.md +7 -6
  134. package/docs/web-tools.md +2 -1
  135. package/docs/wiki.md +1 -1
  136. package/docs/work-artifacts-and-review.md +14 -4
  137. package/docs/work-connectors.md +3 -1
  138. package/docs/work-tools.md +14 -4
  139. package/docs/workflows.md +69 -1
  140. package/docs/working-and-semantic-memory.md +25 -14
  141. package/package.json +5 -5
  142. package/templates/README.md +2 -0
  143. package/templates/business-worker/README.md.tmpl +19 -0
  144. package/templates/business-worker/env.example.tmpl +1 -0
  145. package/templates/business-worker/gitignore.tmpl +11 -0
  146. package/templates/business-worker/manifest.json +11 -0
  147. package/templates/business-worker/package.json.tmpl +23 -0
  148. package/templates/business-worker/src/agent.ts.tmpl +92 -0
  149. package/templates/business-worker/src/index.ts.tmpl +13 -0
  150. package/templates/business-worker/src/tests/agent.test.ts.tmpl +77 -0
  151. package/templates/business-worker/tsconfig.json.tmpl +15 -0
  152. package/templates/personal-assistant/README.md.tmpl +18 -0
  153. package/templates/personal-assistant/env.example.tmpl +1 -0
  154. package/templates/personal-assistant/gitignore.tmpl +11 -0
  155. package/templates/personal-assistant/manifest.json +11 -0
  156. package/templates/personal-assistant/package.json.tmpl +23 -0
  157. package/templates/personal-assistant/src/agent.ts.tmpl +65 -0
  158. package/templates/personal-assistant/src/index.ts.tmpl +13 -0
  159. package/templates/personal-assistant/src/tests/agent.test.ts.tmpl +28 -0
  160. package/templates/personal-assistant/tsconfig.json.tmpl +15 -0
@@ -0,0 +1,345 @@
1
+ # Migrate Prism 0.6 to 0.7
2
+
3
+ > **Status: 0.7.0** (Host Completeness, Evidence, and Capability Boundaries).
4
+
5
+ This document details migration steps, security tightenings, and compatibility notes for upgrading from Prism 0.6.0 to 0.7.0.
6
+
7
+ ---
8
+
9
+ ## Security Tightenings and Breaking Behavioral Changes
10
+
11
+ ### 1. ACP MCP Destination Matching (Trap A)
12
+
13
+ In Prism 0.6.0 and earlier, `@arnilo/prism-acp-agent` matched candidate MCP server URLs against `mcp.allow` using string prefix matching (`server.url.startsWith(entry)`). This permitted:
14
+ - **Origin lookalikes**: An allow entry for `https://mcp.example.com` inadvertently matched `https://mcp.example.com.attacker.invalid/mcp`.
15
+ - **Path prefix bleeding**: An allow entry for `https://mcp.example.com/mcp` matched sibling paths like `https://mcp.example.com/mcp-other`.
16
+
17
+ In Prism 0.7.0, destination matching strictly adheres to WHATWG URL origin and path-segment subtree standards:
18
+
19
+ - **Origin matching**: Normalizes scheme (`http:` / `https:`), hostname (case-insensitive, punycode IDN, IPv6 brackets), and effective port (default ports 80/443 normalized). Lookalike hosts are rejected.
20
+ - **Path-segment subtree matching**:
21
+ - Origin-level entries (e.g. `https://mcp.example.com` or `https://mcp.example.com/`) match any path under that exact origin.
22
+ - Path-scoped entries (e.g. `https://mcp.example.com/mcp` or `https://mcp.example.com/mcp/`) match `/mcp`, `/mcp/`, and `/mcp/sub`, but reject sibling prefixes such as `/mcp-other` or `/mcpextra`.
23
+ - **Configuration validation**:
24
+ - Every `mcp.allow` entry in `prism-acp-agent.json` must be `"stdio"` or a valid absolute `http:` or `https:` URL.
25
+ - Entries containing userinfo (`user:pass@`), query parameters (`?query`), fragment identifiers (`#hash`), or ambiguous encoded path characters (`%2e%2e`, `%2f`, `%5c`, `..`, backslashes) throw `ConfigError` during config parsing.
26
+ - **Candidate URL validation**: Candidate server URLs with embedded credentials or ambiguous encoded path forms fail closed at selection time.
27
+ - **Transports**: Unstable `acp` transport remains rejected. `stdio` servers require the explicit `"stdio"` marker in `mcp.allow`.
28
+
29
+ #### Migration Actions
30
+ - Review `mcp.allow` in `prism-acp-agent.json`: remove any query strings, hashes, or credentials from allow entries.
31
+ - If previously relying on prefix matching across sibling paths (e.g. relying on `/mcp` to match `/mcp-internal`), explicitly list both entries or use an origin-level entry.
32
+
33
+ ### 2. Model Router Facade Fail-Closed Governance (Trap B)
34
+
35
+ In Prism 0.6.0 and earlier, the synchronous `router.providerSource(model)` facade only checked allow-lists, residencies, and durable state presence. When configured with in-memory budgets (such as `maxCostUsd: 0` or positive limits), rate limits, circuit breakers, fallbacks, or selection policies, `router.providerSource(model)` returned an unwrapped provider and bypassed these governance controls without enforcement.
36
+
37
+ In Prism 0.7.0, `router.providerSource(model)` enforces fail-closed boundary safety:
38
+ - **Refuses unenforced governance**: If the router is configured with `budgets`, `rateLimit`, `circuit`, `fallbacks`, or `selection`, calling `providerSource(model)` throws `ModelRouterError` with stable code `ERR_PRISM_MODEL_ROUTER_ASYNC_REQUIRED`.
39
+ - **Refuses durable state**: If configured with `stateStore`, calling `providerSource(model)` throws `ERR_PRISM_MODEL_ROUTER_ASYNC_STATE`.
40
+ - **Runtime validation**: Untyped JavaScript callers passing malformed model arguments fail closed before resolver invocation with `ERR_PRISM_MODEL_ROUTER_VALIDATION`.
41
+ - **Resolver isolation**: The underlying `resolver` function is never called when `providerSource` refuses or denies a request.
42
+ - **Synchronous allow-list / residency support**: Simple allow-list-only and residency-only routers without async state or governance caps remain fully supported via `router.providerSource(model)`.
43
+
44
+ #### Migration Actions
45
+
46
+ Do not use `router.providerSource(model)` when governance (budgets, rate limits, circuit breaker, fallbacks, selection policies, or durable state) is configured. Replace calls with the asynchronous admission method `await router.resolve(...)`:
47
+
48
+ ```ts
49
+ // ❌ Bypasses governance in 0.6.0 / Refused in 0.7.0 (throws ERR_PRISM_MODEL_ROUTER_ASYNC_REQUIRED)
50
+ const provider = router.providerSource(model);
51
+
52
+ // ✅ Governed admission in 0.7.0
53
+ const { provider, model: selectedModel, providerRequestPolicy, budgetReservation } = await router.resolve({
54
+ model,
55
+ identity,
56
+ maxCostUsd: 0.25,
57
+ });
58
+
59
+ // Pass to agent
60
+ const agent = createAgent({
61
+ model: selectedModel,
62
+ provider,
63
+ providerRequestPolicies: [providerRequestPolicy],
64
+ });
65
+
66
+ // Commit actual usage after run
67
+ await router.recordUsage({
68
+ identity,
69
+ provider: provider.id,
70
+ model: selectedModel.model,
71
+ tokens: runTokens,
72
+ budgetReservation,
73
+ });
74
+ ```
75
+
76
+ To test whether a router configuration supports `providerSource` before calling it, use `isProviderSourceEligible(options)` or `assertProviderSourceEligible(options)` exported from `@arnilo/prism-core/governance/model-router`.
77
+
78
+ ### 3. ACP Real-Provider Launcher and Explicit Mock Mode (Trap C / R05)
79
+
80
+ In Prism 0.6.0 and earlier, `@arnilo/prism-acp-agent` silently defaulted to `createMockProvider()` when no provider was passed and no model was configured. Hosts deploying the ACP binary without explicit provider wiring ran mock sessions without warning or token generation (Trap C).
81
+
82
+ In Prism 0.7.0, `createSpawnableAgent` enforces fail-closed startup validation:
83
+
84
+ - **Explicit model required**: Config must specify a valid `model` (`{ "provider": "<name>", "model": "<id>" }`) unless a provider instance is passed via API options.
85
+ - **Credential reference required**: Real providers require `credentialRef` in the configuration. The secret value is resolved from `process.env[ref]` or an optional host `credentialResolver`. If missing or unresolvable, startup fails immediately with `ConfigError` (code `PRISM_ACP_AGENT_CONFIG`).
86
+ - **Provider matching & allow-list**: Only allow-listed first-party providers are supported (`openai`, `anthropic`, `google`, `deepseek`, `openrouter`, `ollama`, `xai`, `zai`, `alibaba`, `kimi`, `clinepass`, `commandcode`, `neuralwatt`, `opencode-go`, `hyper`, and `mock`). Unknown providers or mismatches between configured models and injected providers fail before startup.
87
+ - **Explicit mock mode**: Mock mode requires explicit configuration: `{ "model": { "provider": "mock", "model": "mock" } }` (or injected `provider.id === "mock"`). It stays 100% offline and requires no credentials.
88
+ - **Lazy provider loading**: First-party provider adapters are dynamically imported on demand on the first generation call; `@arnilo/prism-providers` is never loaded at root import or during mock runs.
89
+ - **Credential safety**: Secret values never enter config persistence, argv flags, stdout protocol streams, events, or model context. Identity records only the non-secret `credentialRef`.
90
+ - **Durable session reconstruction**: With SQLite persistence, per-session agents (including editor-backed filesystem sessions) are reconstructed across process restarts with their selected model and provider intact.
91
+
92
+ #### Migration Actions
93
+
94
+ - **For real-provider deployments**: Add `model` and `credentialRef` to `prism-acp-agent.json`:
95
+ ```json
96
+ {
97
+ "userId": "local",
98
+ "cwd": ".",
99
+ "model": { "provider": "openai", "model": "gpt-4o" },
100
+ "credentialRef": "OPENAI_API_KEY",
101
+ "sessionStore": { "type": "sqlite", "path": ".prism/sessions.db" }
102
+ }
103
+ ```
104
+ - **For offline testing / mock journeys**: Explicitly declare mock mode:
105
+ ```json
106
+ {
107
+ "userId": "local",
108
+ "cwd": ".",
109
+ "model": { "provider": "mock", "model": "mock" }
110
+ }
111
+ ```
112
+
113
+ ### 4. Aggregate Task/Tenant Accounting Across All Paid Work (R01)
114
+
115
+ In Prism 0.6.0, model-router budgets were strictly per-model and per-provider (`(provider, model)`). When complex tasks involved retries, model switches, delegated child tasks, embeddings, compactions, or paid tools, each model bucket maintained separate counters, allowing budget resets through model hopping or child delegation.
116
+
117
+ In Prism 0.7.0, router state introduces unified task-level aggregate accounting:
118
+
119
+ - **Task-scoped reservations & accounting**: Supplying `taskId` and `kind` (`"generation" | "embedding" | "compaction" | "tool"`) groups all paid work for a given task under a single shared atomic budget ceiling.
120
+ - **Attribution breakdown**: Calling `router.readBudget({ identity, taskId })` decomposes total utilization into typed attributions (`byModel`, `byKind`, `attributions`).
121
+ - **Strict hard-budget mode (`budgets.strict = true`)**: In strict hard-budget mode, requests lacking enforceable bounds or candidates lacking cost pricing are denied fail-closed with `ERR_PRISM_MODEL_ROUTER_BUDGET`.
122
+ - **Hold renewal (`renewBudget`)**: Long-running holds can be renewed via `router.renewBudget()` or `governedProvider.renewBudget()` before expiry; the hold's fencing token advances and previous fencing tokens are invalidated. Expired holds fail closed with `ERR_PRISM_MODEL_ROUTER_STATE`.
123
+ - **PostgreSQL schema migration `006_aggregate_budgets`**: Adds `task_id` and `attributions` columns to `prism_model_router_budgets` with partial index `prism_model_router_budgets_task_idx`.
124
+
125
+ #### Migration Actions
126
+
127
+ - **Durable PostgreSQL deployments**: Apply migration `006_aggregate_budgets` on startup via `createPostgresEnterpriseState()`. Existing rows automatically default `task_id` to empty string and `attributions` to `{}`.
128
+ - **Task/Job workflows**: Pass `taskId` to `router.resolve()`, `recordUsage()`, and `createGovernedProvider()` to prevent budget oversubscription across retries and child agents.
129
+ - **Budget enforcement**: Set `budgets.strict: true` when hard cost/token ceilings must be guaranteed against unmetered or unpriced providers.
130
+
131
+ ### 5. Durable Business-Action Draft Persistence and Resumption (R02)
132
+
133
+ In Prism 0.6.0, Microsoft 365 and Google Workspace connector mutation drafts were kept ephemerally in adapter memory. If an agent process restarted or if an asynchronous human approval took minutes or hours, drafts were lost, forcing re-creation of drafts from scratch without revision guarantees. Furthermore, modifying a draft did not systematically invalidate existing approvals.
134
+
135
+ In Prism 0.7.0, business-action drafts feature durable lifecycle tracking:
136
+
137
+ - **Checkpoint-backed draft store**: Adapters accept `checkpoints: CheckpointStore` (e.g. `createPostgresEnterpriseState({ pool }).checkpoints` or `createMemoryCheckpointStore()`) or a dedicated `draftStore: WorkDraftStore`. Drafts persist under namespace `prism.work.draft` and survive process restart.
138
+ - **Exact revision binding & payload digest**: Drafts start at `revision: 1` with a canonical SHA-256 `payloadDigest`. Approvals bind strictly to `{ draftId, revision, payloadDigest, identityKey, approvedAt, expiresAt, policyRevision }`.
139
+ - **Edits invalidate approval**: Updating draft payload or recipients increments `revision`, clears `approval`, and resets `status` to `pending_approval`.
140
+ - **Resuming approved revisions**: Tools (`m365_mail_draft_send`, `gws_mail_draft_send`, `m365_calendar_draft_add`, `gws_calendar_draft_add`) accept `{ draftId, revision }` without repeating the payload; the tool resumes the stored payload and executes after approval validation.
141
+ - **Fail-closed ambiguous failure handling**: Connector failures after dispatch mark both the idempotency record and the draft `unknown`. Automatic replays are rejected with `ERR_PRISM_WORK_IDEMPOTENCY_UNKNOWN` until operator reconciliation.
142
+
143
+ #### Migration Actions
144
+
145
+ - **For persistent workloads**: Pass `checkpoints` (and optionally `bodies: ArtifactBodyStore`) to `createMicrosoft365CliAdapter` and `createGoogleWorkspaceCliAdapter` so drafts survive process restart.
146
+ - **Approval systems**: Ensure approval decisions provide or verify `{ draftId, revision, payloadDigest }`.
147
+
148
+ ### 6. RAG document authorization (R04)
149
+
150
+ `retrieveContext` without `authorization` is unchanged. Passing `authorization` injects host-verified principal/group constraints into both vector and lexical legs **before** ranking, then rechecks before rerank and injection.
151
+
152
+ - `filter` is metadata equality only. It is not document ACL.
153
+ - Stores that do not declare `authorization: "acl"` throw rather than claim protection. In-memory and PostgreSQL adapters declare it (`setSourceAccess` / `checkSourceAccess`).
154
+ - Missing grants and unresolved `accessVersion` deny. Revoking a grant (empty principal+group lists, bumped version) hides the source on the next query without re-embedding.
155
+ - `authorization.tenantId` must match every retrieve scope.
156
+
157
+ #### Migration Actions
158
+
159
+ - Hosts that need per-document ACL pass `authorization` and call `setSourceAccess` for each source. Hosts that do not pass `authorization` keep today's retrieve behavior.
160
+ - Custom `VectorStore` implementations must implement query-time ACL and `checkSourceAccess` before advertising `authorization: "acl"`.
161
+
162
+ ### 7. Drive knowledge synchronization (R04)
163
+
164
+ Opt-in. `syncKnowledge` + `createGoogleDriveConnector` page Drive `files.list` / `changes.list` into existing `replaceSource` / `setSourceAccess`. Cursors live on the host `CheckpointStore` and advance only after a page commits. `changes.watch` payloads are not authorization.
165
+
166
+ #### Migration Actions
167
+
168
+ - Hosts that want incremental Drive RAG import wire a token provider (`drive.readonly`), `resolveAccess` for permissions, and a durable checkpoint key. Hosts that do not call `syncKnowledge` are unchanged.
169
+
170
+ ### 8. Hosted E2B sandbox (R03)
171
+
172
+ Opt-in. `createE2BSandbox` / `connectE2BSandbox` implement `DisposableSandbox` plus `pause`/`resume`. Docker and native adapters are unchanged. E2B `Sandbox.connect` auto-resumes paused VMs; Prism reconnects paused sandboxes **without** calling it unless the host calls `resume()` or passes `resume: true`. `lifecycle.autoResume` is always false. `pause({ keepMemory: false })` is filesystem-only: processes do not survive resume. A 503 pause refusal leaves the sandbox running. `close({ export })` is unsupported (pause is the snapshot).
173
+
174
+ Default capabilities do **not** claim Docker `network: none` / egress parity.
175
+
176
+ #### Migration Actions
177
+
178
+ - Hosts that want E2B install optional peer `e2b@2.49.1` (or inject `client`) and pass `apiKey` at the edge. Hosts that keep Docker/native are unchanged.
179
+
180
+ ### 9. Fair worker admission and operator routes (R06)
181
+
182
+ Opt-in. Existing `createWorkflowCoordinator` and `createPrismDrainController` stay the composition entry points. Optional `admission` adds cursor wrap, per-tenant/per-class claim caps, queue deadlines, and drain-aware claiming. `createPrismOperatorHandler` is a separate authorized handler for queue/suspended/failed/unknown inspect plus cancel/reconcile. Reconcile cannot mark unknown effects retryable. No new job broker.
183
+
184
+ #### Migration Actions
185
+
186
+ - Hosts that want fair multi-tenant workers set `admission` and mount `/ops`. Hosts that keep a single-tenant coordinator with default FIFO first-page polling are unchanged.
187
+
188
+ ### 10. Behavioral eval inspector wiring (R07)
189
+
190
+ Opt-in. Trajectory scorers, scenario runner, manifests, and `summarizeTimeline` stay in `@arnilo/prism-core/governance/evals` and `@arnilo/prism-core/governance/observability`. The dev inspector adds `GET /runs/:id/summary` and `POST /compare` that render those artifacts. Host-journey fixtures live in `examples/behavior-evaluation.ts`.
191
+
192
+ #### Migration Actions
193
+
194
+ - Hosts that want inspector quality/cost/latency compare wire `eventSource` + `resolveRun` and call `compareInspectorRuns` / `POST /compare`. Hosts that keep the previous inspector timeline-only UI are unchanged.
195
+
196
+ ### 11. Cross-layer memory lineage (R08)
197
+
198
+ Opt-in. `remember({ lineage: { sourceIds } })` stamps `metadata._lineage` (schema v1). `forget` / `setConsent(visible: false)` / `correct` write invalidation rows **before** body delete. Query/lexical legs exclude forgotten/revoked/held ids and any record listing them in `_lineage.sourceIds`. `correct` keeps the source (`reason: corrected`). `forget({ hold: true })` skips body delete. Missing `_lineage` is self-only. Parent-child `shareWith` is an explicit allow-list; siblings and expired grants fail closed. Observational projection/recall take `invalidatedIds`. `revokedIdsAbsent` is the 072 invariant body.
199
+
200
+ Custom `VectorStore` implementations that omit `lineage: "invalidation"` keep pre-0.7 forget-as-delete (dependents may leak). In-memory and PostgreSQL adapters declare it.
201
+
202
+ #### Migration Actions
203
+
204
+ - Hosts that want derived-context exclusion stamp `lineage` on writes and pass `authorization`/invalidation through recall. Hosts that do not stamp lineage keep self-only delete. Do not edit frozen `docs/migrate-to-0.6.md`.
205
+ - `setConsent(entryId, { visible: false })` marks `revoked` (not `legal_hold`), so recall excludes the record but a later `forget` still purges it; re-granting clears only a revoked mark and never resurrects a `corrected`/`forgotten` one. Use `forget({ ids, hold: true })` when you actually need retention.
206
+ - Observational recall keeps a reflection whose supporting observation was dropped by compaction and annotates it `(dropped)`; invalidation still wins, so a revoked observation id (or a revoked source of one) withholds the reflection with `reason: "revoked"`.
207
+
208
+ ### 12. Evidence-backed citations and document diffs (R10)
209
+
210
+ Additive. `ArtifactCitation` may include `sourceId` / `revision` / `contentHash` / `retrievedAt` / `excerpt` / `span` / `tenantId` / `support`. Approvals stamp `evidenceDigest`. `checkCitationIntegrity` fails closed on missing source, hash/span mismatch, revoked ACL, or tenant mismatch; `support` is ignored. `diffDocument` walks Document Model nodes with op/node caps (`truncated` instead of unbounded diff). `createCitationIntegrityScorer` is a 072 invariant: score 0 cannot be averaged away by a semantic judge.
211
+
212
+ #### Migration Actions
213
+
214
+ - Legacy uri/title/kind citations still attach. Evidence fields are optional until the host starts integrity checks.
215
+ - Structural Office review uses `diffDocument`; artifact `compare` remains hash+metadata.
216
+
217
+ ### 13. Import fidelity and optional OCR (R10)
218
+
219
+ Additive. `importDocument` returns `{ model, fidelity }` — ZIP-name report of dropped OOXML structures (macros, comments, media, …). `parseDocument` still returns the model only.
220
+
221
+ `createMistralOcrParser({ apiKey })` is a host-selected `DocumentParser`. Default `createDocumentReader()` still uses `pdf-parse` / `mammoth` only and does not call OCR. Inline data URLs; no Files API. `documentUrl` is SSRF-checked. `recordUsage` admits pages/bytes into Task 7 accounting.
222
+
223
+ #### Migration Actions
224
+
225
+ - Existing `parseDocument` callers are unchanged. Switch to `importDocument` when review UI must show lost structures.
226
+ - OCR is opt-in. Do not add the OCR parser to default `parsers`.
227
+
228
+ ### 14. Per-run tool narrowing (R11)
229
+
230
+ Additive. `RunOptions.toolNames` allow-lists registered tool names for one run. Omitted = full registry (0.6 behavior). Empty = no tools. Unknown names throw. Resume persists the grant and will not widen it. MCP `execute` fails closed when a refresh changes schema/effect or revokes the tool.
231
+
232
+ Still no `RunOptions.tools` or `RunOptions.toolFilter`.
233
+
234
+ #### Migration Actions
235
+
236
+ - Existing `session.run(input)` callers are unchanged.
237
+ - Pass `{ toolNames: ["web_search"] }` when a continuing session should see a subset.
238
+
239
+ ### 15. Governed realtime voice (R14)
240
+
241
+ Additive. `createRealtimeVoiceBridge` (`@arnilo/prism-core/runtime/realtime`) admits a voice device, dispatches host `function_call` items through the ordinary execute path, cancels queued calls on barge-in, and skips `seenCallIds` on reconnect. Transcripts are off by default; audio is never retained. `RealtimeEvent` may include `usage`. `RealtimeSession.completeTool` is optional. OpenAI Realtime advertises host tools via `session.update` and completes them with `function_call_output`.
242
+
243
+ #### Migration Actions
244
+
245
+ - Existing `createOpenAIRealtimeSession` callers that only send audio are unchanged.
246
+ - Wire `execute` through `dispatchToolCall` / durable approval. Session microphone consent is not tool approval.
247
+
248
+ ### 16. Eval primitives match their contracts (R07)
249
+
250
+ Additive. `runExperiment({ trials: N, seed })` re-runs each item N times (cap 16) and reports `sampleCount` / `standard_error`. `wrapAgentWithFailureInjection` honors `failStore` (fails on `run`), `denyTools`, and `unknownEffect`. Timeline collection no longer uses a 10ms drain. `validateEvalManifest` stays two-field; `validateReleaseEvalManifest` adds prompt/tools/model/policy binding.
251
+
252
+ #### Migration Actions
253
+
254
+ - Existing `validateEvalManifest({ runtimeRevision, datasetVersion })` callers are unchanged.
255
+ - Call `validateReleaseEvalManifest` when packing release evidence. Do not pass `expectedBehavior`, `failStoreAfterTurns`, `denyToolCalls`, or `uncertaintyMethod: "bootstrap"` — those were docs drift, not APIs.
256
+
257
+
258
+
259
+
260
+ ### 17. Native Bedrock Converse route (R12)
261
+
262
+ Additive. `@arnilo/prism-providers/bedrock` now ships a native route next to the OpenAI-compatible one: `createBedrockConverseProvider({ region, credential, stream })` runs `ConverseStream` (default) or one `Converse` call, and `createBedrockProviderPackage({ region, credential, api: "converse" })` registers it under the same `id`/model bindings. Prism messages map to `messages`/`system`/`inferenceConfig`/`toolConfig`, Prism media blocks map to `image`/`document` blocks, cache breakpoints map to `cachePoint` blocks, `options.structuredOutput` maps to `outputConfig.textFormat`, and Anthropic/OpenAI-family reasoning maps to `additionalModelRequestFields.thinking` / `.reasoning_effort`. Responses are decoded from the `application/vnd.amazon.eventstream` framing with bounded frame sizes and CRC validation, still with no AWS SDK dependency.
263
+
264
+ #### Migration Actions
265
+
266
+ - Nothing changes for existing `createBedrockProvider` / `createBedrockProviderPackage` hosts: the default route stays `compatible`.
267
+ - To move a provider id to Converse, set `api: "converse"` (and keep `stream: false` only if the deployment must avoid `InvokeModelWithResponseStream`). One provider id serves one route; register a second `id` if you need both.
268
+ - Model bindings that relied on OpenAI image parts must declare `capabilities.input` with `image`/`document` and use Prism media blocks; unsupported media types now refuse before the request instead of being dropped.
269
+ - Do not pass `cachePoint`, `toolConfig`, `outputConfig`, or `additionalModelRequestFields` through `compat`; use Prism cache breakpoints, `tools`, `structuredOutput`, and `compat.thinking`/`compat.reasoning_effort` (or `options.extra.additionalModelRequestFields` for model-specific fields).
270
+
271
+ ---
272
+
273
+ ## Opt-in additions (no behavior change until enabled)
274
+
275
+ ### 18. Attention compiler (R17, opt-in)
276
+
277
+ `createAttentionCompiler(options?, context?)` is a per-turn gate that measures the assembled input against a host ratio of the model input cap and rewrites nothing until that ratio is reached; over the ratio it mutates a **history clone** monotonically (oldest `thinking` blocks first, then oldest fold-eligible tool results), so prompt-cache prefixes survive and the session store, observational-memory ledger, and input history array are untouched. Still over after every eligible row → `AttentionBudgetError` instead of silent eviction.
278
+
279
+ #### Migration Actions
280
+
281
+ - Nothing changes by default: with `attentionCompiler` omitted, request bytes and the store are byte-identical to 0.6.0.
282
+ - Turn it on per agent (`AgentConfig.attentionCompiler`) or per run (`RunOptions.attentionCompiler`, which narrows, never widens: `false` disables for that run).
283
+ - Hosts that compact on overflow can drive compaction from the gate instead of guessing a token threshold with the host-programmable compaction trigger.
284
+ - Do not use it as a replacement for compaction or `applyContextBudget` eviction: compaction is the boundary operation that writes a summary.
285
+
286
+ `AgentDefinition.attentionCompiler` and the direct `assembleProviderInput` seam carry the same option shape; a definition sets it for every resolved config.
287
+
288
+ ### 19. Memory Fabric subpath (opt-in)
289
+
290
+ `@arnilo/prism-memory/fabric` adds typed notes over the stores a host already configured: `createMemoryFabric({ memory, observational?, linker?, ... })` writes `fact`, `procedure`, `file`, `working`, and `episode` notes through the existing working/vector stores, with links, validity windows, and time/tool recall.
291
+
292
+ #### Migration Actions
293
+
294
+ - Add the subpath import; no new package, provider, database, or mandatory dependency is introduced.
295
+ - The fabric is inert until `fabric.attach(session)`: importing the subpath starts no worker, no timer, and no process, and an unattached fabric enriches nothing.
296
+ - Episode notes (`kind: "episode"`) and `searchConversation` require an attached observational-memory session; without one they fail closed.
297
+ - Reserved keys: `metadata.fabric` on vector records and `_fabric.blocks` inside the working value. A host schema that validates working values must allow them; the host schema remains the last word on block content.
298
+ - Existing `createMemory` hosts keep working unchanged — the fabric writes through the same store, consent, redaction, and lineage paths.
299
+
300
+ ### 20. Work-scope memory index (R16, opt-in)
301
+
302
+ `createWorkScopeController({ session, appendEntry, secrets? })` appends `om.scope.*` entries to one observational-memory ledger and folds them with `foldWorkScopeMap()`; `projectWorkMemory(ledger, map, query)` filters observations/reflections to a host-selected working set and `withWorkScope(controller, spec, fn)` opens/enters/leaves around a callback.
303
+
304
+ #### Migration Actions
305
+
306
+ - Nothing changes without `om.scope.*` entries: the map has only its implicit `session` root, context renders the existing active pool, and the dropper keeps its 0.6.0 behavior.
307
+ - While any host scope exists, the runtime skips the observation dropper — the working set is now the projection, and the folded-payload byte cap stays a storage safety cap, not garbage collection.
308
+ - Bindings are exact ids (`om:<12-hex>` / `reflection:<12-hex>`); a reflection bound on a **closed** scope can graduate into durable semantic memory through the fabric's `remember({ kind: "fact" | "procedure", reflectionId })`.
309
+ - Caps fail closed: 256 scopes, depth/stack 8, 4,096 binds per scope, 512-character labels/kinds, ids `[A-Za-z0-9._:/-]{1,128}` without `..`.
310
+ - `recallObservationalMemory()` still reads the complete current branch by exact id — the projection never hides memory from exact-id recall.
311
+
312
+ ### 21. Host-owned subagent spawn (opt-in)
313
+
314
+ `createSupervisor({ ownership, children })` gains model-facing tools: `createSpawnAgentTool({ supervisor, name?, mode? })` returns a non-exclusive `spawn_agent` whose closed schema exposes only the host's allow-listed child IDs, `input`, an optional `threadId`, and `mode: "sync" | "async"`; `createWaitAgentTool` / `createCancelAgentTool` return `wait_agent` / `cancel_agent` for host-owned async handles. `delegateAsync()` returns `{ delegationId, status: "running" }` without waiting.
315
+
316
+ #### Migration Actions
317
+
318
+ - Nothing changes for existing `delegate()` callers; the tools are additive and opt-in.
319
+ - Advertised children come from the supervisor's own list — a model cannot name a child the host did not construct, cannot supply child tools, identity, scopes, or higher limits, and sees redacted results and error text only.
320
+ - Child identity narrows from the parent (`narrowIdentity` + `assertIdentityPropagation`): a delegated scope set must be non-empty, must not widen the parent's scopes, and must not extend the parent expiry.
321
+ - Async handles are **in-process**: ownership-scoped, bounded, cached terminal records retained only up to `limits.maxQueuedEvents`, and they do not survive a host restart. Parent-run abort propagates to running children.
322
+ - Install `createWorktreeChildFactory` (`@arnilo/prism-coding-tools/agent`) and pass its `after` as the supervisor terminal hook when parallel children would otherwise collide in one working tree; the child context then carries `cwd` pointing at its own linked worktree, and cleanup runs on every terminal outcome — including a child that suspended for approval and later resumes.
323
+ - `observeSupervisorLifecycle` (coding tools) bridges supervisor `delegation_*` events to redacted coding `subagent_started` / `subagent_stopped` lifecycle events (and AG-UI/ACP projections).
324
+
325
+ ## Upgrade steps
326
+
327
+ 1. Bump every `@arnilo/*` dependency and peer to `^0.7.0` (all ten manifests cut together; a range that only *satisfies* 0.7.0 is refused by the release gate). The published predecessor is 0.6.0.
328
+ 2. Read §1–§3 if you run the ACP agent (`mcp.allow` entries may need tightening) or call `router.providerSource()` with governance configured — those are the only hard refusals for an existing host.
329
+ 3. Re-check each §4–§17 item your host touches: they are behavioral tightenings inside existing surfaces (accounting, drafts, authorization, narrowing, fidelity), not new opt-ins.
330
+ 4. Adopt the opt-in additions only where they matter: §18 attention compiler, §19 memory fabric, §20 work scopes, §21 spawn tools.
331
+ 5. Build and run your suite. No persisted-data migration exists or is needed: the 0.7.0 additions write through existing stores (working/vector records, checkpoints, observational-memory entries) under the same schema, and stores still fail closed on unknown or newer schema versions rather than rewriting data.
332
+ 6. Optional: re-run `npm run release:gate` locally to reproduce the release evidence matrix.
333
+
334
+ ## Rollback
335
+
336
+ Pin the previous published line: `@arnilo/prism@0.6.0` (exact pins per package, plus each package's `0.6.0`). Nothing persisted under 0.7 is rewritten in place: the new subpaths and options are additive, and the fabric/scopes/spawn features write only new records through existing stores — a rolled-back 0.6.0 host keeps reading its data and simply stops seeing entries it never wrote. The refusal changes in §1–§3 are the only deltas a 0.6.0 host regains by rolling back (and it regains the unsafe prefix matching and facade bypass with them, so re-check anything relying on those).
337
+
338
+ Back up the session/checkpoint store before a rollback if the 0.7 host wrote fabric notes or scope entries you intend to keep: 0.6.0 code paths ignore them rather than deleting them, but only the 0.7 docs describe them.
339
+
340
+ ## Related APIs
341
+
342
+ - [Migration guide](migration.md): the era index of migration cuts with replacement tables and rollback notes.
343
+ - [Migrate Prism 0.5 to 0.6](migrate-to-0.6.md): Node 22 floor, folded 0.5.7 delta, third-party floors.
344
+ - [Release and install](release-and-install.md): packed surfaces, install rules, support matrix, and the offline test budget.
345
+ - [Memory fabric](memory-fabric.md), [Observational memory compaction](compaction-observational-memory.md), [Attention compiler](attention-compiler.md), [Supervisors](supervisors.md): owning pages for the opt-in additions above.
package/docs/migration.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Migration guide
2
2
 
3
+ ## 0.6.0 → 0.7.0 (host completeness, evidence, and capability boundaries)
4
+
5
+ **Prism 0.7.0 is a lockstep minor for all ten publishable packages.** Node `>=22` stays the floor; no import path was removed and no store schema changed, so a host that does not touch the ACP agent or the model-router facade upgrades by moving every `@arnilo/*` dependency and peer to `^0.7.0`. The full guide — per-item migration actions, opt-in activation, and rollback — is [migrate-to-0.7.md](migrate-to-0.7.md).
6
+
7
+ What a 0.6.0 host must check before upgrading:
8
+
9
+ - **Two hard refusals inside existing surfaces (the only host-breaking changes).** `@arnilo/prism-acp-agent` now matches `mcp.allow` destinations by WHATWG origin plus path-segment subtree (prefix lookalikes such as `https://mcp.example.com.attacker.invalid` no longer match, and allow entries with userinfo/query/fragment/ambiguous encodings fail `ConfigError` at parse time); `router.providerSource(model)` throws `ERR_PRISM_MODEL_ROUTER_ASYNC_REQUIRED` / `ERR_PRISM_MODEL_ROUTER_ASYNC_STATE` instead of silently bypassing budgets, rate limits, circuits, fallbacks, selection policies, or durable state — move those call sites to `await router.resolve(...)`. An ACP deployment without an explicit provider no longer silently runs `createMockProvider()`; mock mode is explicit.
10
+ - **Behavioral tightenings inside existing surfaces** (agent tool narrowing is monotonic per run, RAG queries are authorized in both legs, memory corrections/revocations propagate, import-fidelity reports replace silent drops, model-router/worker accounting is aggregated): these need a host read-through, not a code change.
11
+ - **Additive declarations only.** No export was removed in 0.7.0; the compat baselines were regenerated because existing declaration groups gained members (new fields on options/results and new subpath exports for the attention compiler, memory fabric, work scopes, and supervisor spawn tools).
12
+ - **Opt-in additions are inert by default**: attention compiler (`AgentConfig`/`RunOptions`), `@arnilo/prism-memory/fabric`, the observational-memory work-scope index, and the supervisor `spawn_agent`/`wait_agent`/`cancel_agent` tools. Omitted, request bytes, stores, and tool lists are unchanged.
13
+ - **Channel adapters (Telegram/Signal) are not in 0.7.0.**
14
+
15
+ ## 0.5.6 → 0.6.0 (Node 22 floor; folds the never-published 0.5.7)
16
+
17
+ **Prism 0.6.0 requires Node `>=22`.** Every publishable package declares `"engines": { "node": ">=22" }`; a Node 20 host gets an `EBADENGINE` warning from npm (a hard failure under `engine-strict`) and an unsupported runtime. Node 20 reached upstream end-of-life on 2026-04-30, so the 0.6.0 line moves to Node 22 (maintenance LTS to 2027-04-30) while Node 24 stays the CI default (active LTS to 2028-04-30). The full 0.5.6 → 0.6.0 guide — third-party floors, the removed office peer, the additive host knobs, and upgrade/rollback steps — lives in [migrate-to-0.6.md](migrate-to-0.6.md).
18
+
19
+ What a 0.5.x host must check before upgrading:
20
+
21
+ - **Runtime.** Move the host process and any container image to Node 22.6+ (the docs/test harness strips TypeScript natively from 22.6; Node 22 LTS or later is the supported answer). `engines.node` is now `>=22`, so `npm install` fails closed on older runtimes with `engine-strict` enabled.
22
+ - **CI legs.** The release workflow's compatibility leg is renamed `node20-compat` → `node22-compat` and runs on `node-version: "22"`; branch-protection required-check lists that name the old job id must be updated.
23
+ - **Development types.** `@types/node` (dev) moves `^20.19.0` → `^22.20.0` in the root and `@arnilo/prism-coding-tools`, tracking the declared floor. Hosts building Prism from source should not pin their own `@types/node` below 22 while the floor is `>=22`.
24
+ - **No migration step for Prism itself.** No import path, store schema, event shape, or public signature changed for the floor; it is one of the two host-visible deltas in this cut (`scripts/phase12-freeze-manifest.json` deviation `dev-006`), the other being the third-party peer floors and the removed `@arnilo/prism-office` `playwright-core` peer listed in [migrate-to-0.6.md](migrate-to-0.6.md#3-arniloprim-office-is-peer-free).
25
+
26
+ ### Folded 0.5.7 content (no import, store, or event-shape break)
27
+
28
+ The 0.5.7 cut was never published, so its content ships in 0.6.0. Third-party ranges moved; hosts that pin these themselves must move with them:
29
+
30
+ - `pg` **`^8.22.0` → `^8.23.0`** — driver dependency of `@arnilo/prism-core/sessions/postgres` and `@arnilo/prism-memory`, and its optional peer in core. Hosts on 8.22 see a peer warning until they upgrade.
31
+ - `playwright-core` optional exact peer **`1.61.0` → `1.63.0`** in `@arnilo/prism-web-tools` (the exact pin is deliberate — browser control is version-sensitive, and the host still owns the browser binary/image). `@arnilo/prism-office` **drops** its optional `playwright-core` peer: no office subpath ever imported it at runtime (the diagrams embed takes a host-supplied iframe), so it becomes a devDependency behind the gated live draw.io test. Hosts that installed it for office can remove it.
32
+ - `@ai-sdk/provider` exact peer **`4.0.10` → `4.0.13`** in `@arnilo/prism-providers/ai-sdk`. The supported-version matrix gained a `4.0.13` row; `4.0.3`, `4.0.4`, and `4.0.10` stay listed. Unlisted versions still fail closed with `AiSdkProviderError { code: "unsupported_version" }`.
33
+ - `@nanonets/graft` optional peer **`^0.16.0` → `^0.16.0 || ^0.18.0`** in `@arnilo/prism-memory/graft` (upstream published no 0.17; both listed floors pass the offline peer-contract smoke).
34
+ - `@agentclientprotocol/sdk` exact pin **`1.3.0` → `1.4.0`** in `@arnilo/prism-ag-ui/acp` and `@arnilo/prism-acp-agent`. Wire protocol stays v1 (`PROTOCOL_VERSION === 1`); 1.4.0 stabilizes elicitation (the SDK's `unstable_createElicitation`/`unstable_completeElicitation` helpers become `createElicitation`/`completeElicitation`, wire method names unchanged — Prism never called the unstable helpers) and adds `compaction` session-update kinds, which Prism does not advertise or map.
35
+ - `@office-open/*` **`0.13.1` → `0.14.5`** in `@arnilo/prism-office`. Upstream made `parseDocument`/`parsePresentation`/`parseWorkbook` async; Prism's synchronous document adapters now call the new `parse*Sync` variants, so no Prism signature changed — but the office package requires the 0.14.5 line.
36
+ - `zod` **`^4.4.3` → `^4.6.2`** in `@arnilo/prism-mcp` (AG-UI's `^3.25.0 || ^4.0.0` peer range is unchanged and still admits it).
37
+ - `@biomejs/biome` dev **`2.5.11` → `2.5.13`** (lint/format only; 0 findings on the repo).
38
+
39
+ Dev-tooling and release-gate changes in the same cut (no host action required):
40
+
41
+ - **`@types/node` dev `^26.1.1` → `^20.19.0` at the 0.5.7 cut, then `^22.20.0` here.** Development types track the declared runtime floor, so a Node-22+-only API fails the build instead of compiling clean against a newer type surface. `docs/release-and-install.md` records the policy: the types package tracks the floor, and raising the floor is a support-matrix change (freeze manifest + CI legs), not a dependency bump. The `^20.19.0` pin immediately caught four runnable examples using `import.meta.main` (Node ≥22.18/≥24.2) on a Node-20 floor — they now use the house `import.meta.url === \`file://${process.argv[1]}\`` guard, so they no longer silently no-op below Node 22.18.
42
+ - **Node 20 floor removed in 0.6.0.** The never-published 0.5.7 deliberately kept `engines.node >=20` because dropping a supported line is a host-breaking support-matrix change that does not belong in a patch release; the 0.6.0 minor is the right vehicle (Node 22 is maintenance LTS to 2027-04-30, Node 24 active LTS to 2028-04-30).
43
+ - **Internal first-party ranges are gated at the cut version exactly.** `release.mjs validateRelease` (lockstep mode, which `release.mjs gate --lockstep --version` and the publish path both use) requires every `@arnilo/*` range to be the cut version (exact `0.6.0` or caret `^0.6.0`). A range that merely *satisfies* it — `^0.5.5` alongside `^0.5.6`, which is what the pre-cut tree carried — now fails the gate closed, because it lets two installs of the same release line resolve different first-party minors.
44
+
3
45
  ## 0.5.3 → 0.5.4 (export-shape break in `@arnilo/prism`)
4
46
 
5
47
 
@@ -18,7 +60,7 @@ Stream tokens coalesce on persist (adjacent `text`/`thinking` deltas merge). Rep
18
60
  ## 0.5.0 → 0.5.1 (additive)
19
61
 
20
62
 
21
- Kernel constructs valid provider requests: session correlation, default cache breakpoints, and `thinkingLevel` on `AgentConfig` / `RunOptions`. Clay may drop host-only `createSessionCachePolicy`. OpenCode Go raw `generate` without `sessionId` throws `ProviderRequirementError` (`ERR_PRISM_PROVIDER_REQUIREMENT`) before fetch instead of an upstream 400. Observational memory uses derived `om:{session.id}`; LLM compaction uses the agent session id. See [migrate-to-0.5.md](migrate-to-0.5.md#8-provider-request-construction--additive-plan-066--051).
63
+ Kernel constructs valid provider requests: session correlation, default cache breakpoints, and `thinkingLevel` on `AgentConfig` / `RunOptions`. Hosts may drop a host-side `createSessionCachePolicy` overlay. OpenCode Go raw `generate` without `sessionId` throws `ProviderRequirementError` (`ERR_PRISM_PROVIDER_REQUIREMENT`) before fetch instead of an upstream 400. Observational memory uses derived `om:{session.id}`; LLM compaction uses the agent session id. See [migrate-to-0.5.md](migrate-to-0.5.md#8-provider-request-construction--additive-plan-066--051).
22
64
 
23
65
  ## What it does
24
66
 
@@ -37,7 +37,7 @@ import { createModelRegistry, type ModelConfig } from "@arnilo/prism";
37
37
  | `provider` / `model` | Required registry key. |
38
38
  | `displayName` | Human-readable label. |
39
39
  | `capabilities` | Input/output modes (`text`, `image`, `audio`, `file`, `document`) plus reasoning/tools/streaming booleans and optional `structuredOutput` (`true` or `"json_schema"`) for native JSON-schema requests. |
40
- | `limits` | Context and output-token limits. |
40
+ | `limits` | Context and output-token limits (`ModelLimits`). |
41
41
  | `cost` | Input/output/cache read/cache write pricing. |
42
42
  | `cache` | Generic `ModelCacheCapabilities`. |
43
43
  | `compat` | Provider-owned inert JSON escape hatch. |
@@ -19,13 +19,19 @@ Do not put secrets, prompts, or raw OpenRouter keys into diagnostics. Do not hon
19
19
  | `allowedResidencies` | Request residency must match when configured |
20
20
  | `budgets` / per-call `maxTokens` / `maxCostUsd` | Finite non-negative ceilings; requests with a per-call cap reserve it atomically at admission; `recordUsage` commits actuals against the reservation |
21
21
  | `budgets.reservationTtlMs` | How long an admission reservation pins capacity (default 60s); a run that outlives it reconciles as unknown usage |
22
+ | `budgets.strict` | When `true`, enables strict hard-budget mode: calls lacking enforceable bounds or candidates lacking cost pricing are denied fail-closed with `ERR_PRISM_MODEL_ROUTER_BUDGET` |
22
23
  | `rateLimit` | Per identity+model key window |
23
24
  | `circuit` | Failure threshold + cooldown; keys capped |
24
25
  | `fallbacks` | Ordered candidates after primary; total attempts capped |
25
26
  | `allowOpenRouterRouting` | Default `false`; when false, routing metadata is stripped |
26
27
  | `onDiagnostics` | Optional redacted hook (e.g. policy ledger evidence ref) |
27
- | `router.resolve({ model, identity?, residency?, maxTokens?, ... })` | Rich async selection; returns `budgetReservation` when a per-request cap was reserved |
28
- | `router.providerSource` | Sync facade only for memory state; with `stateStore` it throws `ERR_PRISM_MODEL_ROUTER_ASYNC_STATE` rather than bypass durable checks. |
28
+ | `router.resolve({ model, identity?, residency?, maxTokens?, taskId?, kind?, attemptId?, ... })` | Rich async selection; accepts optional `taskId` and `kind` (`"generation" \| "embedding" \| "compaction" \| "tool"`); returns `budgetReservation` when a per-request cap was reserved |
29
+ | `router.releaseBudget({ identity, provider, model, budgetReservation, taskId?, kind? })` | Explicitly releases a held budget reservation back to the pool |
30
+ | `router.renewBudget({ identity, provider, model, budgetReservation, extendTtlMs?, taskId?, kind? })` | Renews an active reservation hold before expiry, returning an updated reservation with an advanced fencing token |
31
+ | `router.readBudget({ identity, provider?, model?, taskId? })` | Reads budget utilization; when `taskId` is provided, returns granular `byModel`, `byKind`, and `attributions` breakdowns |
32
+ | `createGovernedProvider(options)` / `router.createGovernedProvider(options)` | Wraps an `AIProvider` in an opt-in adapter that automates the full admission, atomic reservation, request-policy application, streaming, and explicit settlement lifecycle (supports `taskId`, `kind`, and `renewBudget()`) |
33
+ | `isGovernedProvider(provider)` | Type guard and runtime predicate indicating that a provider has opt-in governance lifecycle wrapping |
34
+ | `router.providerSource` | Sync facade for allow-list and residency-only routers without async state, budgets, rate limits, circuits, fallbacks, or selection policies. Throws `ERR_PRISM_MODEL_ROUTER_ASYNC_STATE` or `ERR_PRISM_MODEL_ROUTER_ASYNC_REQUIRED` when unenforced governance options are configured; use `router.resolve()` instead. |
29
35
 
30
36
  Frozen caps (default / hard): attempts `3 / 8`, circuit keys `1,024 / 16,384`, diagnostics `8 KiB / 64 KiB`, rate keys `4,096 / 65,536`, budget keys `4,096 / 65,536`.
31
37
 
@@ -33,7 +39,9 @@ Frozen caps (default / hard): attempts `3 / 8`, circuit keys `1,024 / 16,384`, d
33
39
 
34
40
  - `ModelRouterResolveResult` — selected `provider` + possibly stripped `model`, `diagnostics`, and `providerRequestPolicy`; `budgetReservation` carries the admission reservation handle when the request had a per-call budget cap.
35
41
  - Deny throws `ModelRouterError` with code + redacted `diagnostics` (allow-list/residency/budget fail closed without calling resolver); budget denies carry `details.retryAfterMs`.
36
- - `await recordOutcome({ identity, success, circuitProbeToken? })` opens/closes circuits; `await recordUsage({ identity, budgetReservation?, ... })` commits the reservation against actual usage (pass the handle returned by `resolve`) or advances budgets directly. Pass the probe token returned by `resolve` for a half-open outcome.
42
+ - `await recordOutcome({ identity, success, circuitProbeToken? })` opens/closes circuits; `await recordUsage({ identity, budgetReservation?, taskId?, kind?, attemptId?, ... })` commits the reservation against actual usage (pass the handle returned by `resolve`) or advances budgets directly. Pass the probe token returned by `resolve` for a half-open outcome.
43
+ - `await renewBudget({ identity, provider, model, budgetReservation, extendTtlMs?, taskId?, kind? })` extends hold TTL and advances fencing tokens for long-running workflows. Expired holds fail closed with `ERR_PRISM_MODEL_ROUTER_STATE`.
44
+ - `await readBudget({ identity, taskId? })` returns `{ tokens, costUsd, byModel?, byKind?, attributions? }`. When queried with `taskId`, attributions decompose usage across models and paid work categories (`generation`, `embedding`, `compaction`, `tool`).
37
45
  - A reservation whose TTL elapses before `recordUsage` charges the **reserved** amount and emits one redacted `unknown_usage` diagnostic (deterministic reconciliation, never a silent drop).
38
46
 
39
47
  ## Request/response example
@@ -86,7 +94,7 @@ await router.recordUsage({ identity, provider: provider.id, model: model.model,
86
94
  await router.recordOutcome({ identity, provider: provider.id, model: model.model, success: true });
87
95
  await enterprise.close();
88
96
 
89
- // `router.providerSource` is unavailable with durable state; resolve before provider I/O.
97
+ // `router.providerSource` is unavailable with durable state or unenforced governance (budgets, rate limits, circuit breaker, fallbacks, selection); resolve before provider I/O.
90
98
  ```
91
99
 
92
100
  ## Extension and configuration notes
@@ -138,14 +146,81 @@ await router.recordOutcome({ identity, provider, model, success: true, latencyMs
138
146
  process-local; durable latency statistics would require a
139
147
  `ModelRouterStateStore` contract change and are demand-gated.
140
148
 
149
+ ### Governed provider adapter (`createGovernedProvider`)
150
+
151
+ Hosts that want complete, automated governance enforcement across every provider call can wrap their `ModelRouter` in an opt-in `GovernedProvider` adapter instead of manually coordinating `resolve`, `recordUsage`, and `recordOutcome`:
152
+
153
+ ```ts
154
+ import { createModelRouter, isGovernedProvider } from "@arnilo/prism-core/governance/model-router";
155
+
156
+ const router = createModelRouter({
157
+ resolver,
158
+ allowList: { providers: ["anthropic", "openai"] },
159
+ budgets: { maxTokens: 10_000 },
160
+ fallbacks: [{ provider: "openai", model: "gpt-4o" }],
161
+ });
162
+
163
+ const governed = router.createGovernedProvider({
164
+ identity,
165
+ model: { provider: "anthropic", model: "claude-3-5-sonnet" },
166
+ maxTokens: 500,
167
+ onSettlement: (settlement) => {
168
+ console.log(settlement.outcome, settlement.durationMs, settlement.usage);
169
+ },
170
+ });
171
+
172
+ assert.ok(isGovernedProvider(governed));
173
+ for await (const event of governed.generate({ model, messages })) {
174
+ // stream events incrementally; bounded chunks, no full-stream buffering
175
+ }
176
+ ```
177
+
178
+ The adapter executes an explicit six-stage lifecycle for every call:
179
+
180
+ 1. **Admission & Selection**: Runs `router.resolve(...)` across configured candidates. If the primary model fails admission (e.g. not allow-listed, residency mismatch, budget exhausted, or circuit open) and safe fallbacks are configured, the router or adapter evaluates the next candidate.
181
+ 2. **Atomic Budget Reservation**: When `maxTokens` or `maxCostUsd` is specified, capacity is reserved atomically prior to invoking the provider.
182
+ 3. **Request Policy Application**: Applies model and provider request policies (e.g., stripping unauthorized OpenRouter metadata) before calling the provider.
183
+ 4. **Bounded Streaming**: Consumes provider events incrementally without full-response buffering.
184
+ 5. **Safe Fallback vs Forbidden Replay**:
185
+ - **Safe Fallback**: If an error occurs *before* any output or tool call is emitted, the adapter catches the failure, records outcome feedback, releases any reservation, and seamlessly falls back to the next configured candidate.
186
+ - **Forbidden Fallback**: If a failure happens *after* text deltas or tool calls have already been delivered to the consumer, failover is strictly forbidden to prevent replay of side effects or inconsistent state.
187
+ 6. **Explicit Settlement**: Idempotently settles the call exactly once across all outcomes:
188
+ - `success`: Commits actual tokens and cost; releases unspent reservations; records positive outcome.
189
+ - `abort` / `error` / `early_close` before output: Releases the reservation cleanly; records outcome.
190
+ - `abort` / `early_close` after output: Commits the held reservation as `unknownUsage: true` (missing actual usage is charged as reserved liability, never assumed to be zero).
191
+ - Upstream EOF without usage: Commits the reservation as `unknownUsage: true`.
192
+ - Usage recording errors: Propagate out on success so persistence failures are not concealed.
193
+ - Invokes `onSettlement` callback with `GovernedInvocationSettlement` telemetry.
194
+
195
+ ### Synchronous facade (`providerSource`) governance matrix
196
+
197
+ The synchronous `router.providerSource(model)` facade is strictly intended for simple synchronous resolution where allow-lists and residency checks are sufficient. Any configuration requiring asynchronous state or multi-candidate evaluations fails closed at call time:
198
+
199
+ | Feature / Configuration | Supported in `resolve()` | Supported in `providerSource(model)` | Refusal Code |
200
+ | --- | --- | --- | --- |
201
+ | Allow-list (`allowList`) | Yes | Yes | `ERR_PRISM_MODEL_ROUTER_ALLOW_LIST` |
202
+ | Allowed residencies (`allowedResidencies`) | Yes | Yes (from `model.compat.residency`) | `ERR_PRISM_MODEL_ROUTER_RESIDENCY` |
203
+ | Token / cost budgets (`budgets`) | Yes | Refused | `ERR_PRISM_MODEL_ROUTER_ASYNC_REQUIRED` |
204
+ | Rate limits (`rateLimit`) | Yes | Refused | `ERR_PRISM_MODEL_ROUTER_ASYNC_REQUIRED` |
205
+ | Circuit breaker (`circuit`) | Yes | Refused | `ERR_PRISM_MODEL_ROUTER_ASYNC_REQUIRED` |
206
+ | Fallbacks (`fallbacks`) | Yes | Refused | `ERR_PRISM_MODEL_ROUTER_ASYNC_REQUIRED` |
207
+ | Selection policy (`selection`) | Yes | Refused | `ERR_PRISM_MODEL_ROUTER_ASYNC_REQUIRED` |
208
+ | Durable state (`stateStore`) | Yes | Refused | `ERR_PRISM_MODEL_ROUTER_ASYNC_STATE` |
209
+
210
+ All refusals and denials occur before any resolver invocation. In untyped JavaScript contexts, malformed model arguments fail closed with `ERR_PRISM_MODEL_ROUTER_VALIDATION`.
211
+
141
212
  ## Security and performance notes
142
213
 
143
214
  - Allow-list and residency denies never call the underlying resolver.
215
+ - Zero-budget facade probes or unmetered executions are strictly prevented: `providerSource` never executes wrapped resolver logic when any budget, rate limit, circuit, fallback, selection, or durable state is configured.
144
216
  - Budget admission is **reservation-based** when the request carries a per-request cap: `resolve` atomically reserves the full cap against remaining capacity (`max − used − reserved`) and returns a `budgetReservation` handle; parallel admissions can never collectively exceed the reserved budget. Commit the handle in `recordUsage` with the actual tokens/cost (a negative remainder is released back); release happens automatically on internal denial (rate limit, circuit open, provider miss), on TTL expiry, or on an explicit late commit (which charges the reserved amount as unknown usage). Requests without a per-request cap keep read-then-compare admission (`used >= cap` denies) and are outside the reservation guarantee.
145
217
  - Without `stateStore`, budget/rate/circuit state is process-local, memory-capped (rate/budget/circuit keys), and LRU-evicts on insert; a held reservation's budget row is never evicted. It is not a cross-replica production path.
146
218
  - With `stateStore: createPostgresEnterpriseState(...).modelRouter`, rate/budget updates, reservations, and circuit probes are atomic across replicas, use database time, and are owner/principal/provider/model scoped. Router calls become asynchronous and require verified identity.
147
219
  - Diagnostics carry identity refs and attempt outcomes only — no prompts/secrets, tokens, or reservation material. Durable state stores at most bounded numeric/timestamp/token material, never prompts or credentials.
148
220
  - Selection is O(attempts × state operations); no provider network I/O happens inside state updates. Reservation is one atomic UPSERT (denial adds one retry-after query); commit/release are O(1) row updates. Recorded 0.0.23 PostgreSQL p95 point operations stayed under 50 ms and cursor/cleanup pages under 100 ms on the documented fixture.
221
+ - **Aggregate task/tenant accounting**: When `taskId` is supplied, admission reservations and usage records are aggregated at the task level rather than per provider/model alone. Parallel requests (tested up to 32 concurrent attempts across models and child workers) share the atomic budget ceiling without oversubscription. Model switches, tool calls, and background jobs attribute separately to `byModel` and `byKind` while sharing a single liability pool.
222
+ - **Strict hard-budget mode**: Setting `budgets.strict = true` prevents unmetered or unbounded model execution: requests lacking output/token bounds or models lacking cost pricing are rejected fail-closed with `ERR_PRISM_MODEL_ROUTER_BUDGET`.
223
+ - **Hold renewal fencing**: `router.renewBudget()` and `governedProvider.renewBudget()` advance the reservation's fencing token upon extending TTL; previous fencing tokens are immediately invalidated to prevent stale or split-brain commits. Expired holds cannot be renewed and fail closed with `ERR_PRISM_MODEL_ROUTER_STATE`.
149
224
  - Raising hard caps requires a reviewed release update with tests and docs.
150
225
 
151
226
  ## Related APIs