@arnilo/prism 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/cache-telemetry.js +4 -1
- package/dist/content.d.ts +2 -2
- package/dist/content.js +30 -50
- package/dist/contracts-core.d.ts +32 -2
- package/dist/contracts-core.js +20 -0
- package/dist/conversations.d.ts +2 -0
- package/dist/conversations.js +1 -0
- package/dist/event-multiplexer.d.ts +13 -0
- package/dist/event-multiplexer.js +39 -16
- package/dist/index.d.ts +5 -3
- package/dist/index.js +5 -3
- package/dist/oauth-device-code.d.ts +58 -0
- package/dist/oauth-device-code.js +119 -0
- package/dist/pinned-fetch.d.ts +25 -0
- package/dist/pinned-fetch.js +246 -0
- package/dist/providers/openai-compatible.d.ts +2 -2
- package/dist/providers/openai-compatible.js +2 -2
- package/dist/providers/transport.d.ts +14 -1
- package/dist/providers/transport.js +43 -0
- package/dist/testing/persistence-schema.d.ts +1 -1
- package/dist/testing/persistence-schema.js +32 -28
- package/dist/testing/state-concurrency-conformance.d.ts +145 -0
- package/dist/testing/state-concurrency-conformance.js +348 -0
- package/docs/agent-events.md +1 -1
- package/docs/conversations.md +5 -2
- package/docs/credential-storage.md +1 -0
- package/docs/credentials-and-redaction.md +1 -0
- package/docs/database-persistence.md +4 -0
- package/docs/enterprise-postgres-state.md +4 -3
- package/docs/index.md +17 -17
- package/docs/mcp-tools.md +1 -1
- package/docs/migration.md +83 -0
- package/docs/model-routing.md +13 -10
- package/docs/multimodal-content.md +1 -0
- package/docs/policy-and-audit.md +1 -0
- package/docs/provider-caching.md +4 -1
- package/docs/provider-primitives.md +17 -0
- package/docs/providers/azure.md +1 -1
- package/docs/providers/bedrock.md +1 -0
- package/docs/providers/openai-compatible.md +4 -3
- package/docs/providers/vertex.md +1 -1
- package/docs/public-contracts.md +3 -3
- package/docs/release-and-install.md +39 -0
- package/docs/workflows.md +2 -1
- package/package.json +8 -4
package/docs/index.md
CHANGED
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
Prism is a TypeScript/Node.js agent harness. Host apps and extension packages own providers, tools, resources, credentials, storage, UI, and business behavior. Prism supplies contracts, registries, streaming events, and replaceable runtime primitives.
|
|
4
4
|
|
|
5
5
|
## Public contracts
|
|
6
|
-
- [Public contracts](public-contracts.md): type shapes for messages, agents, tools, stores, generic `CheckpointStore`, atomic `LeaseStore`, bounded `EventMultiplexer`, resources, credentials, and events.
|
|
6
|
+
- [Public contracts](public-contracts.md): type shapes for messages, agents, tools, stores, generic `CheckpointStore`, atomic `LeaseStore`, bounded single-consumer `EventMultiplexer`, resources, credentials, and events.
|
|
7
7
|
|
|
8
8
|
## Identity and governance
|
|
9
9
|
- [Agent identity](agent-identity.md): host-verified `Principal` / `AgentIdentity`, delegation narrowing, ownership projection, and redacted telemetry refs for enterprise runs/tools/MCP/A2A/workflows; optional OIDC/JWKS verifier adapter (`@arnilo/prism-credentials-node/oidc` — pinned issuer/audience/JWKS, bounded claims, fail closed).
|
|
10
|
-
- [Policy and audit](policy-and-audit.md): optional `@arnilo/prism-policy` decision ledger (allow/deny/modify/approval), evidence refs only, cursor-paginated WORM export, and durable PostgreSQL composition; 0.0.28 adds the OPA REST evaluator (`@arnilo/prism-policy/opa` — pinned SSRF-checked endpoint, redacted input, fail-closed deny, optional bundle-revision pin).
|
|
11
|
-
- [Model routing](model-routing.md): optional `@arnilo/prism-model-router` allow-list/residency/budget/rate/circuit/fallback governance with redacted diagnostics; durable state requires awaited identity-scoped calls; host-configurable selection policies (reference cost/latency policy ranks by `ModelCost` then in-memory latency EMA fed from `recordOutcome`).
|
|
10
|
+
- [Policy and audit](policy-and-audit.md): optional `@arnilo/prism-policy` decision ledger (allow/deny/modify/approval), evidence refs only, cursor-paginated WORM export, and durable PostgreSQL composition; 0.0.28 adds the OPA REST evaluator (`@arnilo/prism-policy/opa` — pinned SSRF-checked endpoint, redacted input, fail-closed deny, optional bundle-revision pin); 0.2.1 makes the OPA decision fetch DNS-pinned (core `pinnedFetch`).
|
|
11
|
+
- [Model routing](model-routing.md): optional `@arnilo/prism-model-router` allow-list/residency/budget/rate/circuit/fallback governance with redacted diagnostics; budget admission reserves per-request caps atomically (commit/release/TTL reconciliation); durable state requires awaited identity-scoped calls; host-configurable selection policies (reference cost/latency policy ranks by `ModelCost` then in-memory latency EMA fed from `recordOutcome`).
|
|
12
12
|
|
|
13
13
|
## Agent/session runtime
|
|
14
14
|
- [Agent/session runtime](agent-session-runtime.md): create explicit or opt-in secure agents/sessions, get direct `AgentRunResult` values from `run`/`prompt`, mid-run `steer` (turn-boundary or softInterrupt), use integrated `stream()`/`resumeAgentRunStream()`, shared batch pending-decisions / sticky run-scope approvals, subscribe to normalized events, and expose opted-in durable lifecycle capabilities (0.1.6 plan 018 closeout `checkpoint-bodies`: optional `includeSkillBodies` persists the exact loaded-skill instructions with the names-only `persistSessionState`, so resume re-renders bodies registry-independently; ≤64 bodies, `maxStateBytes` refuses oversize; 0.2.0 plan 020 Task 2: durable resume input is validated in core before any side effect — fail-closed durable resume rejects unknown legacy decisions and malformed batches with zero checkpoint writes, tool calls, or resumed events).
|
|
15
15
|
- [Agent definitions](agent-definitions.md): resolve declarative `AgentDefinition` values via `resolveAgentDefinition`, and turn app-config `<configRoot>/agents/<name>/AGENT.md` bundles into runnable agents via `discoverAgentBundles` / `resolveAgentBundle` (explicit tool/skill activation by name, fail-closed omitted capabilities, migration-only `activateAllCapabilities`, strict duplicate scope checks, configurable prompt layers, no auto-discovery).
|
|
16
16
|
- [Agent loops](agent-loops.md): replaceable per-run control loops — `singleShotLoop` default, opt-in bounded artifact-loop tool rounds, and durable custom-loop `revision`/`snapshot`/`restore` hooks with fail-closed resume.
|
|
17
17
|
- [Guardrails](guardrails.md): typed fail-closed input/output/tool checks with buffered provider output and redacted decision records.
|
|
18
|
-
- [Agent events](agent-events.md): live `session.subscribe` plus durable `AgentEventSource` page/subscribe/resume for cross-replica reconnect; message/progress deltas never create spans. Durable sources: PostgreSQL `LISTEN`/`NOTIFY` (reference, `@arnilo/prism-session-store-postgres` root export) and NATS JetStream (`@arnilo/prism-session-store-nats`, FR-5).
|
|
18
|
+
- [Agent events](agent-events.md): live `session.subscribe` plus durable `AgentEventSource` page/subscribe/resume for cross-replica reconnect; message/progress deltas never create spans. Durable sources: PostgreSQL `LISTEN`/`NOTIFY` (reference, `@arnilo/prism-session-store-postgres` root export) and NATS JetStream (`@arnilo/prism-session-store-nats`, FR-5) with restart-stable durable consumer identity (`prism_<hmac16>`) for cursor resume across crash/restart.
|
|
19
19
|
- [Observability](observability.md): OTel GenAI agent/provider/tool hierarchy, host context parenting, bounded trace linkage, safe evaluation events, controlled metrics, and exporter isolation.
|
|
20
20
|
- [Evaluations](evaluations.md): deterministic and bounded trace/model-judge/pairwise scoring, CI thresholds, OTel trace-reference linkage, coding/browser adversarial fixtures, ID-only linkage to immutable owned run feedback, and optional durable PostgreSQL records.
|
|
21
21
|
- [Runs and usage ledger](runs-and-usage.md): durable run/event/tool/usage persistence, optional bounded FIFO durability policies, session snapshot caching, and immutable run/trace feedback.
|
|
@@ -28,35 +28,35 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
|
|
|
28
28
|
- [Observational memory compaction package](compaction-observational-memory.md): optional source-backed memory with explicit `attach()` lifecycle — **Recent exact messages**, **Observation log**, **Reflections**, **Raw-source retrieval** (exact-id recall + cursor paging); dual coverage, **nested-only settings** (pre-0.0.19 flat keys and top-level `workerProvider`/`workerModel` aliases removed in 0.1.5; removed keys fail closed naming the nested replacement), branch-isolated `appendEntry`, secrets redaction, and inert import/extension.
|
|
29
29
|
- [Working and semantic memory](working-and-semantic-memory.md): optional `@arnilo/prism-memory` working-memory store, semantic recall, finite Embedder/VectorStore contracts, PostgreSQL/pgvector path, consent lifecycle, identity-bound redacted export, and resumable bounded rebuild.
|
|
30
30
|
- [Session stores](session-stores.md): `SessionStore` contract, `SessionAppendOptions`, `SessionAppendConflictError`, branch handles, `readBranchPath`, optional bounded `searchSessions` / `SessionIndex` (memory linear|unsupported), and dev-vs-production branch reads — start here for session persistence.
|
|
31
|
-
- [Conversations](conversations.md): durable user-scoped conversation threads (create/list/continue/branch/archive/export/delete) on session + event-ledger seams, thread-bound reconnectable replay, frozen caps, and legal-hold-aware deletion.
|
|
31
|
+
- [Conversations](conversations.md): durable user-scoped conversation threads (create/list/continue/branch/archive/export/delete) on session + event-ledger seams, thread-bound reconnectable replay, frozen caps, atomic metadata via version/CAS (`metadata_conflict` on stale writes), and legal-hold-aware deletion.
|
|
32
32
|
- [Work artifacts and review](work-artifacts-and-review.md): durable artifact co-work review — authorized attach (MIME/hash/version, producer run, citations, preview metadata), revision compare, approve/reject with last-validated recovery, and authorized expiring delivery links; records persist as versioned checkpoints, never file bodies. 0.0.28 adds the core `ArtifactBodyStore` contract (put/get/delete/presign by opaque ownership-scoped ref, hash/size/MIME verification, legal-hold-aware idempotent delete) and the reference `@arnilo/prism-server/artifact-bodies` S3-compatible adapter (hand-rolled SigV4, native fetch + WebCrypto, optional host KMS callback); delivery links resolve through `bodies.presign` when wired.
|
|
33
33
|
- [Session stores and branching](session-stores-and-branching.md): detailed branch semantics and helper reference (kept for compatibility; links back to the canonical atomic append / branch-handle sections).
|
|
34
|
-
- [Database persistence](database-persistence.md): production persistence contracts, shared checksummed migration/full-shape catalog primitives (`@arnilo/prism/testing/persistence-schema`), conditional append, indexes, `readBranchPath`, reference relational schema, retention/legal-hold/quota lifecycle (`lifecycle`), and NoSQL mapping.
|
|
34
|
+
- [Database persistence](database-persistence.md): production persistence contracts, shared checksummed migration/full-shape catalog primitives (`@arnilo/prism/testing/persistence-schema`), conditional append, indexes, `readBranchPath`, reference relational schema, retention/legal-hold/quota lifecycle (`lifecycle`), and NoSQL mapping. `appendSession` gains version/CAS (migration `008_session_version`); durable adapters must pass `assertStateConcurrencyConforms` (`@arnilo/prism/testing/state-concurrency-conformance`: approval/checkpoint-CAS/cursor/idempotency/reservation/conversation-metadata/unknown-outcome probes; memory leg in `npm test`, durable legs in `test:postgres`/`test:nats`, `scripts/phase22-conformance.test.mjs` gate accounting).
|
|
35
35
|
- [SQLite persistence](sqlite-persistence.md): optional `better-sqlite3` adapter with session/run storage, checkpoints/leases, feedback, FTS `searchSessions` (migration-v4), and transactionally verified/backfilled migration metadata.
|
|
36
36
|
- [PostgreSQL persistence](postgres-persistence.md): optional pooled `pg` adapter with session/run/checkpoint/lease/feedback storage, FTS `searchSessions` (migration-v4), advisory-locked checksummed/full-shape migrations, and opt-in live conformance.
|
|
37
|
-
- [Enterprise PostgreSQL state](enterprise-postgres-state.md): optional `@arnilo/prism-enterprise-postgres` composition for durable policy/evaluation/work-idempotency/model-router/`toolEffects` state, exact ownership, checksummed migrations, and explicit cleanup.
|
|
37
|
+
- [Enterprise PostgreSQL state](enterprise-postgres-state.md): optional `@arnilo/prism-enterprise-postgres` composition for durable policy/evaluation/work-idempotency/model-router/`toolEffects` state, exact ownership, checksummed migrations (001-003; 003 adds router budget reservation slots), and explicit cleanup.
|
|
38
38
|
- [Migration guide](migration.md): **0.1.4 → 0.1.5** documented breaking cut — deprecated-option removal (the inert provider request knobs, `maxToolRounds` alias, observational-memory flat keys/worker aliases, `autoResizeImages`, `INIT_PROVIDERS`) with exact replacement table, before/after examples, and fail-closed refusal behavior; **0.0.28 → 0.1.0** release-candidate hardening (no migration); **0.0.17 → 0.1.0 upgrade matrix** (store compatibility per release line: compatible / tested migration / tested refusal, plus breaking-default callouts); **0.0.27** ACP coding-host interop (capability advertise-when, session modes/config, MCP select, lifecycle events, elicitation); **0.0.26** coding intelligence, managed processes, forge, and safe egress; **0.0.25** durable custom loops and shared human-in-the-loop decisions; **0.0.24** distributed events and recoverable tool effects; **0.0.23** enterprise PostgreSQL state adapters (async router and work reconciliation); **0.0.22** third-party behavior integrations (Caveman, Ponytail); **0.0.21** coding-tool capability gaps (`outputMode`, glob, read-before-write, delete/move, aggregator 9/4); **0.0.20** progressive skill disclosure, empty registry default, `load_skill`, priority budget demotion, optional tool-result fold; **0.0.19** observational memory lifecycle; **0.0.15** OpenAI hosted tools/continuation/Realtime, exact AI SDK v4 matrix, RAG lifecycle/reranking/trust/status, and memory export/rebuild; **0.0.14** conversations, memory consent/lifecycle, artifact co-work review, AG-UI co-work events, scoped M365/GWS OAuth connectors, browser checkpoints, device contracts, and Alibaba/Ollama providers; plus prior release migrations.
|
|
39
39
|
- [Node JSONL session store](node-jsonl-session-store.md): development-only JSONL file adapter for single-process Node hosts; no cross-process safety; `searchSessions` throws `SessionSearchUnsupportedError`.
|
|
40
40
|
- [Persistence, credentials, and multimodality primitives](persistence-credentials-multimodality-primitives.md): Plan 056 inventory — session/run-ledger/persistence contracts, credential/OAuth seams, content/resource/model capabilities, package dependency matrix, conformance matrix, and threat model for production adapters.
|
|
41
41
|
|
|
42
42
|
## Provider and model connection
|
|
43
|
-
- [Provider primitives](provider-primitives.md): shared bounded transport and OpenAI serialization helpers — migrated across first-party providers; native structured-output and observability contracts.
|
|
43
|
+
- [Provider primitives](provider-primitives.md): shared bounded transport and OpenAI serialization helpers — migrated across first-party providers; bounded success-body reader for all non-stream JSON endpoints; native structured-output and observability contracts.
|
|
44
44
|
- [Provider layer](provider-layer.md): register and resolve host-owned providers/models, choose replace-or-error duplicate policy, create provider events, stream/reconstruct tool-call deltas, use generic provider request options, and test with the mock provider; deprecated provider-level timeout/retry hints point to runtime abort/retry.
|
|
45
45
|
- [Model registry](model-registry.md): register and resolve `ModelConfig` records with capabilities, limits, cost, cache support metadata, compat data, and duplicate policy.
|
|
46
|
-
- [Provider caching](provider-caching.md): use `PromptCacheHints`, `PromptCacheBreakpoint`, `ModelCacheCapabilities`, cache-aware stable-prefix guidance, and shared cache diagnostics helpers; includes the complete per-provider explicit/implicit cache matrix plus no-Prism-cache entries (including Anthropic, Google, Alibaba, Ollama, cloud adapters, and host-owned AI SDK); cache hints are best-effort and cache keys are never secrets; `createCacheTelemetry()` aggregates per-provider/model hit rate and cache-token totals from the `usage` event stream for tuning the `cache_aware` layout.
|
|
46
|
+
- [Provider caching](provider-caching.md): use `PromptCacheHints`, `PromptCacheBreakpoint`, `ModelCacheCapabilities`, cache-aware stable-prefix guidance, and shared cache diagnostics helpers; includes the complete per-provider explicit/implicit cache matrix plus no-Prism-cache entries (including Anthropic, Google, Alibaba, Ollama, cloud adapters, and host-owned AI SDK); cache hints are best-effort and cache keys are never secrets; `createCacheTelemetry()` aggregates per-provider/model hit rate and cache-token totals from the `usage` event stream for tuning the `cache_aware` layout — the `__overflow__` bucket reports requests/token totals only, never mixed-model cost.
|
|
47
47
|
- [Thinking and reasoning](thinking-and-reasoning.md): portable `ThinkingLevel` helpers (`applyThinkingLevel` / `thinkingCompatFor`) map per-turn effort into provider `compat` fields; model defaults stay on `ModelConfig.compat`; no second options tree.
|
|
48
48
|
- [Use-case model selection](use-case-model-selection.md): bind `{ model?, provider?, thinkingLevel? }` for observational memory, LLM compaction, and other non-session LLM jobs with explicit session-model fallback via `resolveUseCaseModel`.
|
|
49
49
|
- [Provider request policies](provider-request-policies.md): chain `ProviderRequestPolicy` hooks, use `createSessionCachePolicy`, and merge legacy/structured cache options safely.
|
|
50
50
|
- [Provider packages](provider-packages.md): define explicit provider packages, model metadata, auth descriptors, request/cache policies, provider-owned header precedence, the provider-authorized OAuth matrix, and the Phase 10 first-party compatibility matrix without package discovery or provider-specific core behavior; includes a cache behavior summary and **caller-gated on-demand model discovery** (`list*Models`, setup zero-fetch).
|
|
51
51
|
- Phase 12 package workspaces: [`@arnilo/prism-provider-openai`](providers/openai.md) (Responses hosted-tool attribution, bounded continuation, Realtime session seam), [`@arnilo/prism-provider-anthropic`](providers/anthropic.md) (native Messages, `cache_control`, thinking, caller-gated `listAnthropicModels`), [`@arnilo/prism-provider-google`](providers/google.md) (native Gemini `generateContent` SSE, caller-gated `listGoogleModels`), [`@arnilo/prism-provider-opencode-go`](providers/opencode-go.md) (official Go open models, dual-route Anthropic/OpenAI, caller-gated `listOpenCodeGoModels`, `reasoning_content`/thinking preserve), [`@arnilo/prism-provider-openrouter`](providers/openrouter.md) (app-controlled catalog, caller-gated `listOpenRouterModels`, `reasoning` merge/preserve, `cache_control` + sticky `session_id`), [`@arnilo/prism-provider-zai`](providers/zai.md) (official `thinking`/`reasoning_effort`/`tool_stream`, implicit cache, caller-gated `listZaiModels`), [`@arnilo/prism-provider-kimi`](providers/kimi.md), [`@arnilo/prism-provider-alibaba`](providers/alibaba.md) (Alibaba Cloud Model Studio / DashScope + Coding Plan, OpenAI-compatible, caller-gated `listAlibabaModels`, implicit + explicit `cache_control` caching, Qwen `enable_thinking`), [`@arnilo/prism-provider-ollama`](providers/ollama.md) (Ollama Cloud + local, OpenAI-compatible, caller-gated `listOllamaModels`, implicit-only caching, `reasoning_effort`), and [`@arnilo/prism-provider-neuralwatt`](providers/neuralwatt.md) with implicit vLLM prefix caching, reasoning controls (`reasoning_effort`/`thinking_token_budget`/`enable_thinking`/`preserve_thinking`/`clear_thinking`), reasoning preservation, OpenAI-style tool-call loop, quota, telemetry, and retry classification helpers.
|
|
52
|
-
- Phase 8 enterprise cloud (workload identity; separate from consumer Anthropic/Google): [`@arnilo/prism-provider-azure`](providers/azure.md) (Entra / Foundry), [`@arnilo/prism-provider-bedrock`](providers/bedrock.md) (IAM/IRSA + region/PrivateLink), [`@arnilo/prism-provider-vertex`](providers/vertex.md) (ADC / Vertex OpenAPI).
|
|
52
|
+
- Phase 8 enterprise cloud (workload identity; separate from consumer Anthropic/Google): [`@arnilo/prism-provider-azure`](providers/azure.md) (Entra / Foundry, credential once per request), [`@arnilo/prism-provider-bedrock`](providers/bedrock.md) (IAM/IRSA + region/PrivateLink, duplicate-case-safe SigV4 signing), [`@arnilo/prism-provider-vertex`](providers/vertex.md) (ADC / Vertex OpenAPI, credential once per request).
|
|
53
53
|
- Optional AI SDK adapter: [`@arnilo/prism-provider-ai-sdk`](providers/ai-sdk.md) maps host-owned pinned `LanguageModelV4` models onto Prism `AIProvider` streams (offline-tested `@ai-sdk/provider` version matrix; no Prism catalog; maps metadata/tool authority/`finish.usage` cache tokens; reasoning is host-model-owned).
|
|
54
|
-
- [OpenAI-compatible provider](providers/openai-compatible.md): optional provider subpath using native or injected `fetch` for Chat Completions streaming (`chatCompletionsUrl` / `authStyle` overrides for enterprise adapters; `buildBodyExtra` / `mapMessages` / `mapUsage` / `extraHeaders` hooks for vendor variants).
|
|
54
|
+
- [OpenAI-compatible provider](providers/openai-compatible.md): optional provider subpath using native or injected `fetch` for Chat Completions streaming (`chatCompletionsUrl` / `authStyle` overrides for enterprise adapters; `buildBodyExtra` / `mapMessages` / `mapUsage` / `extraHeaders` hooks for vendor variants; **strict completion is the shared default** — streams ending without `[DONE]` + `finish_reason` fail closed instead of emitting a successful `done`, with explicit `strictCompletion: false` as the documented opt-out).
|
|
55
55
|
|
|
56
56
|
## Input, prompt, and context assembly
|
|
57
57
|
- [SDK customization guide](customization.md): map provider resolution, middleware, context, builders, injectors, loops, compaction, retry, stores, and skills to explicit host-wired APIs.
|
|
58
58
|
- [Input and prompt assembly](input-and-prompt-assembly.md): render tiny prompt templates and turn common host input, history, attachments, explicit resources, summaries, and tool results into messages with replaceable builders, provider-input assembly, cache-aware default order, opt-in legacy ordering, and optional `contextBudget` eviction + omission reports. Audio/file/document `ContentBlock` types and capability checks are documented there.
|
|
59
|
-
- [Multimodal content](multimodal-content.md): complete-request media resolution and aggregate bounds, DNS-classified/address-pinned URLs, SSRF/MIME policy, `ModelCapabilities.input` tags, and first-party content-type mapping.
|
|
59
|
+
- [Multimodal content](multimodal-content.md): complete-request media resolution and aggregate bounds, DNS-classified/address-pinned URLs, SSRF/MIME policy, `ModelCapabilities.input` tags, and first-party content-type mapping; 0.2.1 routes media URL fetches through the core DNS-pinned fetch primitive.
|
|
60
60
|
- [System prompts](system-prompts.md): compose explicit user/package/app/run system prompt layers, auto-load the standard `AGENTS.md` (workspace) / `SYSTEM.md` prompt files via the Node `loadSystemPromptFiles` loader (trust-gated for `AGENTS.md`), and append `SYSTEM.md` → per-agent `AGENT.md` body → repo `AGENTS.md` layers from a discovered agent bundle via `resolveAgentBundle`.
|
|
61
61
|
- [Instruction injection](instruction-injection.md): register package injectors that layer redacted instructions/context blocks without granting tools, permissions, or resource escapes.
|
|
62
62
|
- [Context and skills](context-and-skills.md): resolve ordered context providers; progressive skill catalog (`skillsDisclosure`, default catalog-only), `load_skill` on-demand bodies, fail-closed registry activation (`activateAllSkills` migration opt-in), `toolNames` fail closed before provider turns, priority-aware budget demotion, and optional `toolResultFold`.
|
|
@@ -68,7 +68,7 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
|
|
|
68
68
|
- [OpenAPI tools adapter](openapi-tools.md): optional `@arnilo/prism-openapi-tools` `createOpenApiTools` — compile host-selected OpenAPI 3.1 operationIds into bounded `ToolDefinition`s (allow-list only, pinned origin, resolved/bounded schemas, approval + effect-store idempotency on mutations, bounded body/response/retries/pagination, host credential resolver, untrusted output).
|
|
69
69
|
- [Tool execution primitives](tool-execution-primitives.md): finite JSON Schema LRU validation, exclusive-aware bounded parallel dispatch, MCP bridge mapping, coding execution policy, and image-read bounds.
|
|
70
70
|
- [Tool validator JSON Schema package](../packages/tool-validator-json-schema/README.md): optional `@arnilo/prism-tool-validator-json-schema` adapter for `tool.parameters`.
|
|
71
|
-
- [MCP client bridge and server exposure](mcp-tools.md): SDK-1.30.0 bounded tools/resources/prompts, host-owned roots/sampling/elicitation, exact-origin DNS-pinned client transport, and principal-bound opt-in Streamable HTTP sessions. 0.0.28 adds MCP OAuth: `createMcpOAuthTransport`/`createMcpOAuthFetch`/`createMcpClientAuth` (RFC 9728/8414 discovery, PKCE, RFC 8707 audience binding, RFC 7009 revocation, host-owned bounded state) and server `protectedResource` metadata + `WWW-Authenticate` challenges.
|
|
71
|
+
- [MCP client bridge and server exposure](mcp-tools.md): SDK-1.30.0 bounded tools/resources/prompts, host-owned roots/sampling/elicitation, exact-origin DNS-pinned client transport, and principal-bound opt-in Streamable HTTP sessions. 0.0.28 adds MCP OAuth: `createMcpOAuthTransport`/`createMcpOAuthFetch`/`createMcpClientAuth` (RFC 9728/8414 discovery, PKCE, RFC 8707 audience binding, RFC 7009 revocation, host-owned bounded state) and server `protectedResource` metadata + `WWW-Authenticate` challenges; 0.2.1 re-routes the client transport through the shared core DNS-pinned fetch primitive.
|
|
72
72
|
- [Web search, fetch, and extraction](web-tools.md): optional host-selected Brave/Exa discovery and Firecrawl Markdown/schema tools with native fetch, stable citations, late credentials, finite limits, and explicit untrusted-content boundaries.
|
|
73
73
|
- [Work tools](work-tools.md): optional `@arnilo/prism-work-tools` identity-scoped M365 + GWS connectors (hard-coded CLI argv, draft-then-approve, state-machine idempotency, shared result shapes); 0.0.14 adds a late-bound per-identity `tokenProvider` (env-only, fail-closed); 0.2.0 plan 020 Task 3 provides an isolated subprocess environment (fixed allow-listed base + explicit env + late-bound token env, forced `HOME`/telemetry controls, 64-name/64-KiB caps) and requires host-pinned **absolute** binary/configDir paths.
|
|
74
74
|
- [Work connectors](work-connectors.md): connector principles, capability gates, scoped OAuth establishment (0.0.14), and out-of-scope boundaries (Slack/Teams channels not shipped) for Microsoft 365 / Google Workspace.
|
|
@@ -104,15 +104,15 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
|
|
|
104
104
|
|
|
105
105
|
## CLI/RPC
|
|
106
106
|
- [CLI/RPC](cli-rpc.md): Run print/json modes and LF-delimited RPC over the public AgentSession runtime, including mid-run `steer`, branch-handle results, fixed `forkSession`, and `checkout`. `prism init` scaffolds a tiny TypeScript project with one selected provider and an offline mock test; `prism providers add <name>` scaffolds an OpenAI-compatible provider package (manifest, provider, models, cache helpers, conformance test, docs stub).
|
|
107
|
-
- [Workflows](workflows.md): optional `@arnilo/prism-workflows` typed bounded DAG orchestration — explicit recursive definition revisions, exact-owner cancellation/active identity, finite hard limits, durable human suspend/resume, schedules/background execution, revocable proactive schedule capability tokens, nested workflows, replay, coordination, events, and optional RPC/Web bindings. Compose coding plans/checkpoints via workspace Markdown + `state.coding` without a second runtime. Interactive TUI (C-012) deferred.
|
|
107
|
+
- [Workflows](workflows.md): optional `@arnilo/prism-workflows` typed bounded DAG orchestration — explicit recursive definition revisions, exact-owner cancellation/active identity, finite hard limits, durable human suspend/resume, schedules/background execution, revocable proactive schedule capability tokens, nested workflows, replay, coordination, events, and optional RPC/Web bindings. Compose coding plans/checkpoints via workspace Markdown + `state.coding` without a second runtime. Active-run registry is non-durable, in-process only, with bounded sweep/cap cleanup. Interactive TUI (C-012) deferred.
|
|
108
108
|
- [Workflow orchestration primitives](workflow-orchestration-primitives.md): architecture inventory — workflow adapters consume core `CheckpointStore`, `LeaseStore`, and bounded `EventMultiplexer`; run control and optional RPC commands stay package-local.
|
|
109
109
|
- [Workflow/TUI scope](workflow-tui-primitives.md): records why 0.0.5 ships workflow APIs/RPC control but no interactive terminal UI.
|
|
110
110
|
|
|
111
111
|
## Security and credentials
|
|
112
112
|
- [Host security guide](host-security.md): fail-closed checklist for supply-chain/attestation/canary isolation, bounded credentials, AG-UI/ACP/A2A/web remote boundaries, untrusted external content, settings, redaction, trust roots, workflow ownership, coding I/O, permissions, PostgreSQL TLS/roles/cleanup, persistence, extensions, and tool validation; **0.1.0 security evidence** (plan 012 Task 6): moderate audit policy, named threat-suites leg (`npm run security:threat-suites`), supply-chain negative fixtures, blocked-gate canary semantics.
|
|
113
113
|
- [Security/auth/trust](settings-auth-trust-security.md): settings providers, credential helpers, trust/permission policies, redaction controls, host-owned settings/credentials wiring outside `AgentConfig`, and security-boundary hardening summary.
|
|
114
|
-
- [Credentials and redaction](credentials-and-redaction.md): compose explicit credential resolver order, use caller-supplied env objects/OAuth refresh + revoke helpers, resolve credentials only at the provider edge, redact known secret values, and follow the provider-authorized subscription OAuth matrix.
|
|
115
|
-
- [Credential storage](credential-storage.md): optional `@arnilo/prism-credentials-node` adapter with strict bounded AES-GCM envelopes, async finite scrypt, restrictive Unix files, abort-aware bounded system-keychain calls, optional host-KMS wrap (`encryptWithHostKms`), and 0.0.14 Microsoft 365 / Google Workspace OAuth providers (PKCE/device-code, least-privilege scope bundles, per-identity work-token bridge); `./oidc` subpath adds the OIDC/JWKS identity verifier.
|
|
114
|
+
- [Credentials and redaction](credentials-and-redaction.md): compose explicit credential resolver order, use caller-supplied env objects/OAuth refresh + revoke helpers, resolve credentials only at the provider edge, redact known secret values, and follow the provider-authorized subscription OAuth matrix; 0.2.1 adds the shared bounded device/token flow (core `pollDeviceCodeToken`, fail-closed token shape) behind both the OpenAI Codex and OAuth 2.0 providers.
|
|
115
|
+
- [Credential storage](credential-storage.md): optional `@arnilo/prism-credentials-node` adapter with strict bounded AES-GCM envelopes, async finite scrypt, restrictive Unix files, abort-aware bounded system-keychain calls, optional host-KMS wrap (`encryptWithHostKms`), and 0.0.14 Microsoft 365 / Google Workspace OAuth providers (PKCE/device-code, least-privilege scope bundles, per-identity work-token bridge); `./oidc` subpath adds the OIDC/JWKS identity verifier; 0.2.1 makes the JWKS fetch DNS-pinned (core `pinnedFetch`, redirect-free, byte-bounded).
|
|
116
116
|
|
|
117
117
|
## Testing and examples
|
|
118
118
|
- Provider test doubles: `createMockProvider()` and provider event helpers are documented on the canonical Provider layer page above.
|
|
@@ -129,7 +129,7 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
|
|
|
129
129
|
- [Ponytail behavior integration](ponytail.md): optional `@arnilo/prism-ponytail` — upstream Ponytail skills/commands, `ponytail-mode` injector, session `ponytail-mode` persistence; resolves peer `@dietrichgebert/ponytail` or `upstreamPath`; opt-in (not in code/sdk profiles).
|
|
130
130
|
|
|
131
131
|
## Release and install
|
|
132
|
-
- [Release and install](release-and-install.md): current **0.2.
|
|
132
|
+
- [Release and install](release-and-install.md): current **0.2.2** 50-package graph (root + 49 workspace packages) — plan 022 the concurrent-state-and-durability-integrity cut: atomic model-budget reservation (`ModelRouterStateStore.reserveBudget`/`commitBudget`/`releaseBudget` with fencing tokens, `reservationTtlMs` expiry and unknown-usage reconciliation, rate/budget key-map caps with LRU eviction that never drops a held reservation), atomic conversation metadata (`SessionRecord.version` + `appendSession` `expectedVersion` CAS across Postgres/SQLite — create-only `0`, exact-version `N>0`, legacy last-write-wins when omitted; `SessionMetadataConflictError` `metadata_conflict` with versions only, HTTP 409; concurrent create/branch/archive single-statement with branch caps inside the CAS, archive wins, deleted rows never resurrect), single-consumer `EventMultiplexer` (`EventMultiplexerError` `ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER` instead of silent queue sharing), restart-stable NATS durable consumer identity (`prism_<hmac16>` with no random suffix — crash-resumed subscribe continues from the last ack, orphaned 0.2.1 consumers reclaimed on clean stop), and bounded non-durable active-run registries (sweep + fail-closed 512 cap `ERR_PRISM_WORKFLOW_RUN_REGISTRY_OVERFLOW`); new regression surface `scripts/phase22-security.test.mjs` (4 blockers + gate accounting over built public entrypoints) + packed plain-JS `security22.mjs` consumer + the `@arnilo/prism/testing/state-concurrency-conformance` harness (7 probes across memory/Postgres/SQLite/NATS legs, no timing-only sleeps) + the `scripts/phase22-conformance.test.mjs` gate; additive-only compat (new exports only, no removals); forward-only migrations 008 (`prism_sessions.version`) and 003 (`prism_model_router_budgets.reservations`); migration `0.2.1 → 0.2.2`; then plan 021 the provider-completion-and-outbound-trust-boundaries cut: strict stream completion is the shared OpenAI-compatible default (truncated streams fail `incomplete_delta`, explicit `strictCompletion: false` opt-out), bounded success bodies via `readBoundedResponseJson` on all discovery/quota/embeddings/upload/OAuth JSON endpoints (65,536-byte ceiling, depth/property/shape caps), DNS-pinned OIDC JWKS/OPA/content fetches through the core `pinnedFetch` primitive with 3xx redirects rejected outright (private/metadata answers fail closed `ssrf_denied`), shared bounded OAuth device/token polling (`pollDeviceCodeToken`) across provider-openai and credentials-node, and the four edge fixes (Azure/Vertex credential-once, Bedrock duplicate-case/repeated-query SigV4 canonicalization, OpenAI upload failed-DELETE retention, cache `__overflow__` tokens-only); public-entrypoint threat-suite `scripts/phase21-security.test.mjs` + packed plain-JS consumer; additive-only compat (MCP transport helpers re-exported from core, no removals); migration `0.2.0 → 0.2.1`; then plan 020 the fail-closed runtime-and-sandbox-security cut on the 0.2.x review-remediation line: durable-resume decision validation in core (`assertValidAgentRunResume` — unknown decisions/malformed batches fail closed with `ERR_PRISM_DECISION_*` before any state claim, checkpoint write, or tool execution; server parser remains defense in depth), isolated work-tool subprocess environments (`@arnilo/prism-work-tools` — fixed base allow-list + explicit env + forced HOME/telemetry + late-bound per-identity tokens, 64-name/64-KiB caps, absolute binary/configDir, linear output capture), and explicit sandbox capabilities (`@arnilo/prism-coding-security` — `SandboxAdapter.capabilities` with omission-is-false fail-closed resolution, `SandboxCodingComposition.capabilities` from verified wiring, `containmentClaim` deprecated as the conservative projection; Docker reports only verified controls, native reports filesystem/process/privilege `false`); public-entrypoint security conformance (`scripts/phase20-security.test.mjs`, wired into `security:threat-suites`), packed plain-JS consumer regressions, and the sandbox-browser workflow's fail-loud Docker/native capability evidence gate — 0.2.0 never ships while a blocker is skipped; migration and rollback notes in `docs/migration.md` `0.1.7 → 0.2.0`, store-compatible with 0.1.7 in both directions; 0.1.7 was the performance-and-DX patch — dependency-free `createCacheTelemetry()` per-provider/model cache hit/miss aggregator (bounded cardinality with `__overflow__`, token counters/rates only, host-activated), host-configurable `ModelRouterSelectionPolicy` on `createModelRouter` with the reference `createCostLatencySelection` (ModelCost rank then in-memory latency EMA, default ordered behavior byte-identical), `prism providers add <name>` OpenAI-compatible provider scaffold (manifest/provider/models/cache/conformance test/docs stub, npm-name + traversal + symlink-escape validation, placeholders only), and the async `AgUiProjection` verification closeout (plan 009 Task 15 evidence recorded, no new code); plan 017 the documented breaking cut — deprecated-option removal with `docs/migration.md` `0.1.4 → 0.1.5` section and reviewed compat-baseline regeneration via `--allow-break` then `--update-baseline`: the inert provider request knobs, `RunOptions.maxToolRounds`, observational-memory flat settings keys + top-level worker aliases, `ReadToolOptions.autoResizeImages`, `INIT_PROVIDERS`; all removals fail closed naming their replacement; plan 016 internal god-module split — `agents.ts`/`contracts.ts` reorganized behind barrel re-exports with a byte-identical public entry surface, measured tree-shaking improvement in `scripts/phase16-baseline.json`, and additive `@arnilo/prism-browser` Chrome DevTools Protocol capabilities — `browser_evaluate`/`browser_observe` and `block_urls`/`unblock_urls`/`throttle`/`emulate` act actions; plan 015 dead-code and deprecation hygiene on the frozen 0.1.x line — parameterized benchmark runner `scripts/benchmark.mjs` absorbing the per-version runners, archived review-coverage evidence in `docs/_evidence/`, non-blocking unused-code sweep `npm run sweep:unused`, opt-in checkpoint persistence for loaded-skill names and read-path sets; plan 014 Alibaba provider enrichment — embeddings, video input, verified compatible-mode surface decision table; plan 013 post-release hardening — build single-flight, MCP SSE relay test, combined coverage summary, canonical manifest-count narrative, ACP modes/config persistence guidance; Phase 12 release-candidate hardening; plan 012 — freeze manifest, compatibility matrix, upgrade matrix, packed-install e2e journeys, restart-recovery evidence, capacity envelopes, security policy), exact-peer/install/tarball rules, deterministic resumable publication and publish dry-run, frozen 0.1.x compatibility and support matrix (Node/PostgreSQL/platform/provider/protocol pins and unsupported combinations, machine-checked against `scripts/phase12-freeze-manifest.json`), protected PostgreSQL gate, pinned supply-chain gates, offline tests, the 0.0.15 provider/AI-SDK/RAG/memory protected live-canary matrix, and sandbox-browser Docker/Playwright gates.
|
|
133
133
|
- [0.1.0 / 1.0 readiness gates](0.1.0-readiness.md): command-per-gate 1.0 readiness table — frozen API surface + compat gate, migration/docs tripwires, budget table, live-suite matrix, security matrix, current-line status (**0.0.23** published target), signed-publication/live-canary prerequisites for 1.0, and Phase 12 demand-evidence entry criteria.
|
|
134
134
|
- [Review coverage archive](_evidence/): per-phase evidence freezes (plans 067–079, releases 0.0.4–0.0.16) — traceability matrices, provider validation, capability/primitive/limit matrices, benchmark budgets, and artifact-diet findings; tarball-excluded, kept in-repo for audit.
|
|
135
135
|
|
package/docs/mcp-tools.md
CHANGED
|
@@ -208,7 +208,7 @@ Remote MCP tools default to `external_mutation`/`unsupported` unless the host `e
|
|
|
208
208
|
| Risk | Mitigation |
|
|
209
209
|
| --- | --- |
|
|
210
210
|
| Untrusted subprocess (stdio) | Explicit `command` / `args` / `env` / `cwd`; review before deploy |
|
|
211
|
-
| SSRF / DNS rebinding / redirects (HTTP) | Exact HTTPS origins; credentials/fragments/redirects denied; every DNS answer public; one address pinned per request; explicit loopback-only HTTP escape hatch |
|
|
211
|
+
| SSRF / DNS rebinding / redirects (HTTP) | Exact HTTPS origins; credentials/fragments/redirects denied; every DNS answer public; one address pinned per request; explicit loopback-only HTTP escape hatch. Since 0.2.1 the client transport re-routes through the shared core `pinnedFetch` primitive (DNS-pinned fetch) with byte-identical `McpBridgeError`/`McpOAuthError` wrapping |
|
|
212
212
|
| Hostile discovery / schema compilation | Raw SDK `tools/list` requests avoid SDK Ajv output-schema compilation; finite pages/tools/cursors/metadata/schema totals; failed refresh leaves previous tools unchanged |
|
|
213
213
|
| Tool-name shadowing | Prefixed names + `createToolRegistry({ duplicate: "error" })` |
|
|
214
214
|
| MCP Apps metadata/HTML | Explicit extension acknowledgement; bounded nested metadata; app-only tools absent from model list; only linked `ui://` HTML/MIME resource body reaches the host renderer |
|
package/docs/migration.md
CHANGED
|
@@ -1,5 +1,88 @@
|
|
|
1
1
|
# Migration guide
|
|
2
2
|
|
|
3
|
+
## 0.2.1 → 0.2.2 concurrent state and durability integrity (plan 022)
|
|
4
|
+
|
|
5
|
+
Release **0.2.2** (plan 022) makes four concurrency/durability boundaries atomic or fail-loud. The API surface is **additive-only** (plain reviewed compat gate at 0.2.2: expected deltas are the version literal, `ModelRouterStateStore.reserveBudget`/`commitBudget`/`releaseBudget` plus `ModelRouterReservation`/`ModelRouterBudgets.reservationTtlMs`/`ModelRouterLimits.maxRateKeys`/`maxBudgetKeys` (memory + Postgres), `SessionRecord.version` with `appendSession` `expectedVersion`, `EventMultiplexerError` with code `ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER`, and the `@arnilo/prism/testing/state-concurrency-conformance` subpath; no removal, no `--allow-break`). Three of the four changes tighten behavior where 0.2.1 silently accepted a race — concurrent hosts may now see an explicit conflict where 0.2.1 lost an update or oversubscribed a budget:
|
|
6
|
+
|
|
7
|
+
1. **Atomic model-budget reservation (`model-router`, `enterprise-postgres`).** Admission is now reserve/commit/release: `reserveBudget` runs at admission and fails the request when `used + reserved + requested` would exceed the window max, returning `{ reservationId, fencingToken, admitted, retryAfterMs? }`; `commitBudget` applies the actual usage delta at the outcome (an expired reservation still charges the reserved amount with `unknownUsage: true` so a late commit can never disappear from accounting); `releaseBudget` frees an uncommitted reservation. `readBudget`-based admission stays for requests with no per-request cap, and the 0.2.1 post-hoc `addUsage` remains as retrospective accounting only — it is no longer admission authority.
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
// 0.2.1: readBudget then consumeRate then addUsage — concurrent admissions could collectively oversubscribe
|
|
11
|
+
// 0.2.2: admission reserves the full per-request cap, outcome commits/releases actuals
|
|
12
|
+
const reservation = await store.reserveBudget({
|
|
13
|
+
key: { tenantId, principalId, provider, model },
|
|
14
|
+
tokens: request.maxTokens, costUsd: request.maxCostUsd, // per-request caps, when set
|
|
15
|
+
windowMs: 24 * 60 * 60 * 1000, reservationTtlMs: 60_000,
|
|
16
|
+
});
|
|
17
|
+
if (!reservation.admitted) { /* denied; retry after reservation.retryAfterMs */ }
|
|
18
|
+
// ... run the request ...
|
|
19
|
+
await store.commitBudget({
|
|
20
|
+
key, reservationId: reservation.reservationId,
|
|
21
|
+
fencingToken: reservation.fencingToken, tokens: actualTokens, windowMs: 24 * 60 * 60 * 1000,
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Reservations expire after `reservationTtlMs` (default 60,000 ms, bounded to 31 days) even if a host never commits, so a crashed request cannot hold capacity forever. Rate/budget/circuit key maps are now capped (`maxRateKeys`/`maxBudgetKeys`, default 4,096, hard cap 65,536; circuits stay 1,024/16,384) with LRU eviction on insert; a budget row holding an active reservation is never evicted (the eviction candidates exclude held rows, and if nothing is evictable the insert fails with `ERR_PRISM_MODEL_ROUTER_STATE` `capacity-exhausted`). The durable Postgres store keeps reservations in a new `reservations` JSONB column on `prism_model_router_budgets` (migration 003, forward-only, applied automatically by `applyEnterpriseMigrations`; existing rows are untouched and read as no reservations).
|
|
26
|
+
|
|
27
|
+
2. **Atomic conversation metadata (`session-store-postgres`, `session-store-sqlite`, core `SessionRecord`).** `SessionRecord` gains `version` (fresh rows start at 1; migration 008 backfills legacy 0-version rows to 1) and `appendSession` accepts `expectedVersion`: `0` = create-only, `N > 0` = exact-version CAS update-only, omitted = the 0.2.1 last-write-wins behavior for untyped/legacy callers. A stale write throws `SessionMetadataConflictError` (`metadata_conflict`) carrying only `{ id, expectedVersion, currentVersion }` — never metadata content — and the HTTP server maps it to 409. Concurrent create/branch/archive are now single-statement: the branch `maxActiveBranches` cap is enforced inside the CAS write (a concurrent branch at cap-1 fails its version guard instead of silently dropping the oldest ref), archive wins over a stale concurrent write, and a retention-deleted session is never resurrected (the update arm requires the row to still exist).
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
// 0.2.1: create could race to the last metadata write; concurrent branch calls could lose a ref
|
|
31
|
+
// 0.2.2: exactly one concurrent writer wins per version; losers get metadata_conflict
|
|
32
|
+
const { version } = await persistence.appendSession({
|
|
33
|
+
id: sessionId, ...ownership, createdAt, updatedAt, metadata: { state: "active" },
|
|
34
|
+
expectedVersion: 0, // create-only: conflict if the session already exists
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
await persistence.appendSession({ ...record, metadata: { state: "archived" }, expectedVersion: version });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error.code === "metadata_conflict") { /* re-read the winning version and retry */ }
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
3. **Single-consumer `EventMultiplexer` (core).** `createEventMultiplexer().subscribe()` now rejects a second concurrent consumer with `EventMultiplexerError` `ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER` instead of parking both consumers on one queue and silently losing events. The slot frees when the active consumer's iterator completes, is `return()`ed at a yield, or the multiplexer closes. Hosts that previously relied on multiple `subscribe()` calls sharing one multiplexer must either serialize consumption or use the event source's own broadcast `subscribe` (agent-events), which still supports multiple subscribers. `createWorkflowEventBus` and the supervisor (the only in-repo consumers) are unaffected — each already uses a single subscriber.
|
|
44
|
+
|
|
45
|
+
4. **Restart-stable NATS durable consumer identity (`session-store-nats`).** The durable consumer name is now exactly `prism_<hmac16 of tenantId|sessionId|runId>` — the 0.2.1 random suffix is gone, so a crashed durable subscribe is reused at its last-acked position by a restarting process (cursor resume, at-least-once). Clean stops still delete the durable consumer (resume then relies on the HMAC-signed cursor); only a crash leaves the consumer in place. Pre-0.2.2 consumers minted with the random suffix (`prism_<digest>_<random>`) are orphaned and reclaimed by the existing `deleteConsumer`/consumer-enumeration cleanup path on the next clean stop of a same-subject subscribe.
|
|
46
|
+
|
|
47
|
+
5. **Bounded, non-durable active-run registries (`workflows`).** The in-process workflow active-run registry is documented as non-durable (no timer, no background service): `registerActiveWorkflowRun` sweeps aborted/leaked entries before every insert and fails closed with `WorkflowRuntimeError` `ERR_PRISM_WORKFLOW_RUN_REGISTRY_OVERFLOW` at the 512 cap instead of evicting a live entry (a live eviction could silently allow a duplicate run). A run whose promise never settles is reclaimed only when it is aborted or the cap forces a sweep — there is no durable recovery of active runs in 0.2.2 (see Further Actions: 0.2.6).
|
|
48
|
+
|
|
49
|
+
**Store compatibility:** 0.2.2 is **not** rollback-compatible with 0.2.1 in the Postgres/SQLite persisted shape: `prism_sessions` gains a `version` column (migration 008) and `prism_model_router_budgets` gains a `reservations` column (enterprise migration 003). Both migrations are forward-only and additive — 0.2.2 code reads 0.2.1 databases correctly after migration (backfill included); a 0.2.1 binary pointed at a 0.2.2 database still works because the new columns are nullable/defaulted, but it will not maintain versions or reservations. The NATS durable-name change touches no persisted data (consumers are runtime state; orphaned 0.2.1 consumers are reclaimed on the next clean stop).
|
|
50
|
+
|
|
51
|
+
**Rollout:** upgrade core and the session stores together (migration 008 runs automatically via the existing checksummed `prism_migrations`; the version column must exist before any host writes CAS updates). Then `enterprise-postgres` (migration 003) and `model-router` (reservation admission can be enabled per-host; hosts that never call `recordUsage` rely on TTL expiry). Then `workflows`/`server` (conversation CAS is transparent to clients except new 409 responses), then `session-store-nats`. Branch/archive callers that intentionally lost races in 0.2.1 must now handle `metadata_conflict` (re-read + retry) where they previously accepted last-write-wins.
|
|
52
|
+
|
|
53
|
+
**Rollback risk:** restoring 0.2.1 against a 0.2.2 database is safe for reads and last-write-wins writes (the new columns are ignored) but silently reopens all four race windows: oversubscription, conversation lost updates, silent multi-subscriber event loss, and non-restart-stable NATS resume. Rollback is therefore only a stopgap, not a mitigation — prefer fixing the failing host on 0.2.2.
|
|
54
|
+
|
|
55
|
+
## 0.2.0 → 0.2.1 provider completion and outbound trust boundaries (plan 021)
|
|
56
|
+
|
|
57
|
+
Release **0.2.1** (plan 021) tightens the streaming-completion, outbound-fetch, and credential/signing/upload boundaries. The API surface is **additive-only** (plain reviewed compat gate at 0.2.1: the only deltas are the version literal and `@arnilo/prism-mcp` transport helpers `boundResponse`/`defaultResolver`/`isLoopbackAddress`/`isLoopbackHostname`/`normalizeHostname`/`raceAbort`/`requestPinned`/`resolvePinnedAddress` becoming re-exports of the lifted core primitives — same names, same signatures, no removal; no `--allow-break`), with five documented security-motivated behavior tightenings. Untyped/legacy callers may now fail where 0.2.0 silently proceeded:
|
|
58
|
+
|
|
59
|
+
1. **Strict stream completion is the shared default (all OpenAI-compatible adapters).** `createOpenAICompatibleProvider` now defaults `strictCompletion: true` — a stream that ends without a `[DONE]` marker AND a choice-level `finish_reason` (EOF, network cut, provider truncation) emits a `ProviderTransportError` (`incomplete_delta`) instead of a successful `providerDone`, and a successful done never fabricates usage. This applies to every inheriting adapter: Azure, Bedrock, Vertex, OpenRouter, ZAI, NeuralWatt (Alibaba/Kimi/Ollama/OpenCode-go had already opted in).
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
// 0.2.1: truncated stream fails closed
|
|
63
|
+
for await (const event of provider.generate(request)) {
|
|
64
|
+
if (event.type === "error") {
|
|
65
|
+
event.error.code; // "incomplete_delta"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// explicit opt-out stays available where hosts own truncation detection:
|
|
69
|
+
createOpenAICompatibleProvider({ ..., strictCompletion: false });
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
2. **Bounded success bodies on non-stream JSON endpoints.** `readBoundedResponseJson` (exported from `@arnilo/prism/providers/transport`) replaces unbounded `response.json()` on all model-discovery `/models` calls, NeuralWatt quota, Alibaba embeddings, OpenAI uploads, and the OAuth success paths. Defaults: 65,536-byte UTF-8 ceiling, max JSON depth 32, max properties 4096, caller-supplied shape gate, abort support, secret-redacted errors. Oversized or malformed bodies abort with `ProviderTransportError` `response_body_overflow`/`response_body_shape` instead of buffering unbounded input.
|
|
73
|
+
|
|
74
|
+
3. **DNS-pinned OIDC JWKS, OPA, and content fetches; redirects rejected.** The default fetch paths of `credentials-node` JWKS (`@arnilo/prism-credentials-node/oidc`), `policy` OPA decisions, and core content/media fetches now resolve the hostname once (1–32 addresses), validate every candidate against the SSRF policy, and connect only to a pinned address via a lookup-hook socket (no re-resolution). **3xx redirects are rejected outright** (`MediaContentError` code `redirect`) — a redirected fetch is never re-validated or followed. Private/metadata/loopback addresses fail closed (`MediaContentError` `ssrf_denied`). The MCP transport helpers were lifted to the shared core primitive (`pinnedFetch`, `resolvePinnedAddress`, `requestPinned` from `@arnilo/prism`) with byte-identical behavior and are re-exported from `@arnilo/prism-mcp`.
|
|
75
|
+
|
|
76
|
+
4. **Shared bounded OAuth device/token polling.** Core OpenAI OAuth (`@arnilo/prism-provider-openai`) and `@arnilo/prism-credentials-node` now share `pollDeviceCodeToken` (RFC 8628 poll loop with `authorization_pending` continue, `slow_down` +5 s backoff, expiry deadline, cancellation, bounded success/error reads, fail-closed token-shape gates, `[REDACTED]` secret redaction). No public change — the device/token flows keep their messages and cadence; provider-specific fields stay adapter options.
|
|
77
|
+
|
|
78
|
+
5. **Credential, signing, upload, and cache edge fixes.** (a) Azure and Vertex resolve a rotating/single-use credential **exactly once per request** — the inner provider signs with the same token the wrapper validated (a `CredentialValueSource` is never consumed twice). (b) Bedrock SigV4 canonicalization lowercases and merges duplicate-case request headers last-wins and sorts query parameters by encoded key then value — duplicate-case or reordered input can no longer produce a malformed signature. (c) OpenAI upload cleanup retains a file id until its `DELETE` succeeds — a failed/skipped cleanup leaves the id registered for a retried cleanup instead of leaking the remote file. (d) The cache-telemetry `__overflow__` bucket never carries cost — it reports requests and token totals only, so one model's cost metadata cannot mix into mixed-model overflow tokens.
|
|
79
|
+
|
|
80
|
+
**Store compatibility:** 0.2.1 is store-compatible with 0.2.0 in both directions — no persisted-shape change, no migration step. Checkpoint, session-store, approval, and registry payloads are byte-identical; only fetch/stream/credential behavior changed.
|
|
81
|
+
|
|
82
|
+
**Rollout:** upgrade core first (strict completion and bounded readers apply to all hosts immediately; truncated-stream callers must add `strictCompletion: false` only if they intentionally accept incomplete streams), then `@arnilo/prism-credentials-node` + `@arnilo/prism-policy` (DNS-pinned fetches; ensure JWKS/OPA hosts resolve to public addresses and never redirect), then the provider adapters (Azure/Vertex credential handling, Bedrock signing), then `@arnilo/prism-mcp` (re-export-only change).
|
|
83
|
+
|
|
84
|
+
**Rollback risk:** restoring 0.2.0 restores all five boundary gaps — rollback is **not** a mitigation. Hosts that must roll back should disable truncated-stream acceptance, unbounded-body endpoints, redirect-following fetches, rotating-credential reuse, and upload cleanup at their own boundary until they can return to 0.2.1.
|
|
85
|
+
|
|
3
86
|
## 0.1.7 → 0.2.0 fail-closed runtime and sandbox security (plan 020)
|
|
4
87
|
|
|
5
88
|
Release **0.2.0** (plan 020) is the first cut of the 0.2.x review-remediation line: it closes the three security blockers found in the 2026-08-12 comprehensive review. The API surface is **additive-only** (plain compat gate at 0.2.0 shows zero removed/changed declarations; no `--allow-break`), but three behaviors are deliberately tightened for security, so untyped/legacy callers may now fail where 0.1.7 silently proceeded:
|
package/docs/model-routing.md
CHANGED
|
@@ -17,22 +17,24 @@ Do not put secrets, prompts, or raw OpenRouter keys into diagnostics. Do not hon
|
|
|
17
17
|
| `createModelRouter({ resolver, stateStore?, ... })` | Wraps host `ProviderResolver`; omit `stateStore` for in-process memory state or pass durable async state. |
|
|
18
18
|
| `allowList.providers` / `allowList.models` | Exact provider id / model id or `provider/model` |
|
|
19
19
|
| `allowedResidencies` | Request residency must match when configured |
|
|
20
|
-
| `budgets` / per-call `maxTokens` / `maxCostUsd` | Finite non-negative ceilings; `recordUsage`
|
|
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
|
+
| `budgets.reservationTtlMs` | How long an admission reservation pins capacity (default 60s); a run that outlives it reconciles as unknown usage |
|
|
21
22
|
| `rateLimit` | Per identity+model key window |
|
|
22
23
|
| `circuit` | Failure threshold + cooldown; keys capped |
|
|
23
24
|
| `fallbacks` | Ordered candidates after primary; total attempts capped |
|
|
24
25
|
| `allowOpenRouterRouting` | Default `false`; when false, routing metadata is stripped |
|
|
25
26
|
| `onDiagnostics` | Optional redacted hook (e.g. policy ledger evidence ref) |
|
|
26
|
-
| `router.resolve({ model, identity?, residency?, ... })` | Rich async selection |
|
|
27
|
+
| `router.resolve({ model, identity?, residency?, maxTokens?, ... })` | Rich async selection; returns `budgetReservation` when a per-request cap was reserved |
|
|
27
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
29
|
|
|
29
|
-
Frozen caps (default / hard): attempts `3 / 8`, circuit keys `1,024 / 16,384`, diagnostics `8 KiB / 64 KiB`.
|
|
30
|
+
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`.
|
|
30
31
|
|
|
31
32
|
## Outputs / response / events
|
|
32
33
|
|
|
33
|
-
- `ModelRouterResolveResult` — selected `provider` + possibly stripped `model`, `diagnostics`, and `providerRequestPolicy
|
|
34
|
-
- Deny throws `ModelRouterError` with code + redacted `diagnostics` (allow-list/residency/budget fail closed without calling resolver).
|
|
35
|
-
- `await recordOutcome({ identity, success, circuitProbeToken? })` opens/closes circuits; `await recordUsage({ identity, ... })` advances budgets. Pass the probe token returned by `resolve` for a half-open outcome.
|
|
34
|
+
- `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
|
+
- 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.
|
|
37
|
+
- A reservation whose TTL elapses before `recordUsage` charges the **reserved** amount and emits one redacted `unknown_usage` diagnostic (deterministic reconciliation, never a silent drop).
|
|
36
38
|
|
|
37
39
|
## Request/response example
|
|
38
40
|
|
|
@@ -139,10 +141,11 @@ await router.recordOutcome({ identity, provider, model, success: true, latencyMs
|
|
|
139
141
|
## Security and performance notes
|
|
140
142
|
|
|
141
143
|
- Allow-list and residency denies never call the underlying resolver.
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
144
|
+
- 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
|
+
- 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
|
+
- 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
|
+
- 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
|
+
- 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.
|
|
146
149
|
- Raising hard caps requires a reviewed release update with tests and docs.
|
|
147
150
|
|
|
148
151
|
## Related APIs
|
|
@@ -145,6 +145,7 @@ try {
|
|
|
145
145
|
- SSRF deny-by-default blocks IPv4/IPv6 loopback, private/unique-local, link-local, unspecified, multicast, IPv4-mapped private, and cloud metadata targets. DNS answers are all classified before one public address is pinned; mixed public/private answers fail closed.
|
|
146
146
|
- `allowedHostnames` is an explicit trust override and may permit a private destination. `denyPrivateHosts: false` is broader and should be reserved for hosts that intentionally own private-network access.
|
|
147
147
|
- DNS lookup, connection, and body streaming share `fetchTimeoutMs` and caller abort; more than 32 resolved addresses, redirects, and oversized response bodies are rejected.
|
|
148
|
+
- Media URL fetches (0.2.1) route through the core `pinnedFetch` primitive — DNS-pinned resolution with per-answer SSRF checks (rebinding defense) and outright 3xx rejection — while keeping the `fetch`/`resolveHostname`/`requestUrl` host seams and the existing byte budgets.
|
|
148
149
|
- MIME validation rejects common magic-byte spoofing; extensions alone are never trusted.
|
|
149
150
|
- Byte budgets use base64 size estimates before decode and re-check decoded bytes after every read/fetch. Complete-request resolution keeps at most the configured request budget plus one bounded item in memory and performs no provider upload/request until validation succeeds.
|
|
150
151
|
- Media errors omit raw bytes/base64 payloads from messages.
|
package/docs/policy-and-audit.md
CHANGED
|
@@ -115,6 +115,7 @@ Policy is optional. Hosts wire `record*` helpers or `evaluateAndAppend` at permi
|
|
|
115
115
|
- Policy version pin fails closed on mismatch.
|
|
116
116
|
- Unrestricted payload field names (`prompt`, `body`, `toolArguments`, …) are rejected before append.
|
|
117
117
|
- Evaluate/append are O(fields) and network-free in-package; remote WORM I/O stays in the host sink/adapter.
|
|
118
|
+
- The OPA decision fetch (0.2.1) is DNS-pinned through the core `pinnedFetch` primitive: one resolve per request, every resolved address SSRF-checked before the connect (rebinding defense), redirects rejected outright, timeouts/retries unchanged, and private-answer denials surface `MediaContentError` (`ssrf_denied`) rather than a transport error.
|
|
118
119
|
- Export never full-scans: page size is capped; raise hard caps only with Phase 8 freeze + tests + docs updates.
|
|
119
120
|
|
|
120
121
|
## OPA external policy adapter (`@arnilo/prism-policy/opa`, 0.0.28)
|
package/docs/provider-caching.md
CHANGED
|
@@ -279,7 +279,10 @@ for (const sample of report.samples) {
|
|
|
279
279
|
- Cardinality is bounded: beyond `maxKeys` distinct provider/model keys, excess
|
|
280
280
|
keys accumulate in a single `__overflow__` bucket; memory cannot grow with
|
|
281
281
|
hostile model names (`ponytail:` ceiling — upgrade to host-configurable caps
|
|
282
|
-
or LRU eviction only if a real deployment exceeds it).
|
|
282
|
+
or LRU eviction only if a real deployment exceeds it). The `__overflow__`
|
|
283
|
+
bucket aggregates mixed provider/model tokens, so it never carries cost
|
|
284
|
+
metadata: `estimatedSavings`/`currency` are unset there and it reports
|
|
285
|
+
requests and token totals only.
|
|
283
286
|
- `record()` is O(1) per usage event; `report()` is O(keys). No secrets or
|
|
284
287
|
cache keys are accepted or stored.
|
|
285
288
|
|
|
@@ -137,10 +137,27 @@ export function tryParseJsonObjectArguments(
|
|
|
137
137
|
text: string,
|
|
138
138
|
options?: { toolName?: string; maxBytes?: number },
|
|
139
139
|
): { ok: true; value: JsonObject } | { ok: false; error: ProviderTransportError };
|
|
140
|
+
|
|
141
|
+
/** Read a success body with the same byte ceiling as `readBoundedResponseText`, then parse it as JSON
|
|
142
|
+
* with depth/property caps and an optional caller-supplied shape gate. Malformed JSON, over-limit
|
|
143
|
+
* nesting/width, or shape failure throw `ProviderTransportError` (code `response_body_shape`). */
|
|
144
|
+
export async function readBoundedResponseJson<T>(
|
|
145
|
+
response: Response,
|
|
146
|
+
options?: {
|
|
147
|
+
secrets?: readonly (string | undefined)[];
|
|
148
|
+
maxResponseBodyBytes?: number;
|
|
149
|
+
maxDepth?: number; // default 32
|
|
150
|
+
maxProperties?: number; // default 4_096 per object/array
|
|
151
|
+
shape?: (value: unknown) => boolean; // caller-supplied shape gate, fails closed
|
|
152
|
+
signal?: AbortSignal;
|
|
153
|
+
},
|
|
154
|
+
): Promise<T>;
|
|
140
155
|
```
|
|
141
156
|
|
|
142
157
|
**Performance:** Single pass over chunks; retained memory is `O(min(buffer, maxBufferBytes))`, not `O(stream)`. No full-stream accumulation.
|
|
143
158
|
|
|
159
|
+
**Security:** the bounded success-body reader (added in 0.2.1) caps every non-stream JSON response: a hard UTF-8 byte ceiling (`maxResponseBodyBytes`, default 65_536) that cancels the body before full buffering, a nesting depth cap (default 32), a per-container property/element cap (default 4_096), and a caller-supplied shape gate. Malformed JSON, over-limit nesting or width, and shape mismatches all fail closed with `ProviderTransportError` code `response_body_shape`; errors are static text and never embed body content or secrets. It replaces the former unbounded `response.json()` in all ten model-discovery implementations, NeuralWatt quota, Alibaba embeddings, OpenAI uploads (0.2.1 task 6), and the shared OAuth device/token flows (0.2.1 task 5); per-adapter post-parse validation stays as defense in depth.
|
|
160
|
+
|
|
144
161
|
### `@arnilo/prism/providers/openai` — **shipped**
|
|
145
162
|
|
|
146
163
|
Import:
|
package/docs/providers/azure.md
CHANGED
|
@@ -60,7 +60,7 @@ Register via `createExtensionKernel().load([createAzureOpenAIProviderPackage(...
|
|
|
60
60
|
|
|
61
61
|
## Security and performance notes
|
|
62
62
|
|
|
63
|
-
- No credential prefetch at import;
|
|
63
|
+
- No credential prefetch at import; the credential is resolved exactly once per request (a rotating `CredentialValueSource` is never consumed twice — the same resolved token drives the wrapper check and the inner auth header).
|
|
64
64
|
- Endpoint host is never rewritten to public DNS.
|
|
65
65
|
- Errors redact credential values via shared transport helpers.
|
|
66
66
|
- No Azure SDK dependency.
|
|
@@ -60,6 +60,7 @@ Uses Bedrock’s OpenAI-compatible runtime route (not Converse eventstream). Hos
|
|
|
60
60
|
## Security and performance notes
|
|
61
61
|
|
|
62
62
|
- No AWS SDK; package-local SigV4 only for `bedrock` service.
|
|
63
|
+
- Input headers are normalized once before signing: names are lowercased and duplicate-case keys merge last-wins, so the canonical request always matches the signed header list (no duplicate-case mismatch); query parameters are canonicalized sorted by encoded key then value.
|
|
63
64
|
- Private endpoint hosts are not rewritten to public DNS.
|
|
64
65
|
- Credential secrets are redacted from provider errors.
|
|
65
66
|
- No credential prefetch at import.
|
|
@@ -41,12 +41,12 @@ Options:
|
|
|
41
41
|
| `onComment` | `(text) => ProviderEvent \| undefined` | Handle SSE comment lines (text after `:`), e.g. NeuralWatt `: energy` / `: cost` telemetry. Returned events are yielded in stream order. |
|
|
42
42
|
| `extraHeaders` | `(request) => Record<string, string>` | Optional extra request headers; provider auth and `content-type` still win. |
|
|
43
43
|
| `transformBody` | `(body, request) => JsonObject` | Optional final body transform, applied last (token limits, compat stripping); wins over everything. |
|
|
44
|
-
| `strictCompletion` | `boolean` | Require `[DONE]` and a `finish_reason`; truncated streams yield an `error` and `done` carries the final usage. |
|
|
44
|
+
| `strictCompletion` | `boolean` | Require `[DONE]` **and** a `finish_reason` before emitting `done`; truncated streams yield an `error` and `done` carries the final usage. **Default `true`** (fail-closed); set `false` explicitly to accept streams that end without completion evidence — the documented downgrade whose risk the opting host owns. |
|
|
45
45
|
| `requestFailedPrefix` | `string` | Prefix for HTTP error messages. Default `OpenAI-compatible request failed`. |
|
|
46
46
|
|
|
47
47
|
The subpath also exports the building blocks for provider packages that keep public body/stream helpers:
|
|
48
48
|
|
|
49
|
-
- `openAIChatEvents(body, { signal, strictCompletion, doneUsage, mapUsage, onComment })`: the shared SSE stream loop as an `AsyncIterable<ProviderEvent>`.
|
|
49
|
+
- `openAIChatEvents(body, { signal, strictCompletion, doneUsage, mapUsage, onComment })`: the shared SSE stream loop as an `AsyncIterable<ProviderEvent>`. `strictCompletion` defaults to `true` (see the Security notes).
|
|
50
50
|
- `buildOpenAIChatBody(request, { mapMessages, serializeMessage, buildBodyExtra, transformBody })`: the base Chat Completions request body builder.
|
|
51
51
|
|
|
52
52
|
Provider requests use the standard `ProviderRequest` shape: `model`, `messages`, optional `tools`, `metadata`, and `signal`.
|
|
@@ -62,7 +62,7 @@ The returned provider emits normalized `ProviderEvent` values:
|
|
|
62
62
|
| streamed `tool_calls` fragments | `tool_call_delta` events. |
|
|
63
63
|
| complete accumulated tool call | final `tool_call` event. |
|
|
64
64
|
| `usage` | `usage` event. |
|
|
65
|
-
| `[DONE]`
|
|
65
|
+
| `[DONE]` + `finish_reason` | `done` event. A stream ending without either terminal variant yields an `error` instead (strict default). |
|
|
66
66
|
| HTTP/stream/parsing error | `error` event with redacted `ErrorInfo`. |
|
|
67
67
|
|
|
68
68
|
The adapter passes `request.signal` to `fetch` for abort propagation; an already-aborted signal throws before fetch.
|
|
@@ -146,6 +146,7 @@ const provider = createOpenAICompatibleProvider({
|
|
|
146
146
|
- Redaction only removes known values supplied to the helper. Avoid logging raw provider requests/responses.
|
|
147
147
|
- `fetch` receives the request `AbortSignal`.
|
|
148
148
|
- SSE and HTTP error bodies are read through bounded `@arnilo/prism/providers/transport` helpers (`readSseData`, `readBoundedResponseText`) with configurable byte ceilings.
|
|
149
|
+
- **Strict completion is the shared default (since 0.2.1):** a chat stream is complete only when both `[DONE]` and a `finish_reason` were observed. EOF without either is treated as truncation and emits an `error` ("Chat stream ended without completion evidence"), never a successful `done` — a partial answer can no longer be mistaken for a completed one. Providers that legitimately omit one of the terminal markers must pass `strictCompletion: false` explicitly, accepting the truncation-detection downgrade. `done` carries the final stream usage when the stream was strict (or `doneUsage` is set).
|
|
149
150
|
- Tests should use injected `fetch` and never make real network calls.
|
|
150
151
|
- Tool-call arguments are accumulated as streamed text, parsed with `parseJsonObjectArguments` when the final tool call is emitted; empty argument text yields `{}`, malformed JSON yields an `error` event.
|
|
151
152
|
|
package/docs/providers/vertex.md
CHANGED
|
@@ -59,7 +59,7 @@ const provider = createVertexProvider({
|
|
|
59
59
|
|
|
60
60
|
- No Google Cloud SDK dependency in the package.
|
|
61
61
|
- Custom/private endpoint hosts are preserved.
|
|
62
|
-
- Tokens redacted from errors; no import-time credential prefetch.
|
|
62
|
+
- Tokens redacted from errors; no import-time credential prefetch — the credential is resolved exactly once per request (a rotating `CredentialValueSource` is never consumed twice; the same resolved token drives the wrapper check and the inner auth header).
|
|
63
63
|
- Pair with model-router residency allow-lists on `location`.
|
|
64
64
|
|
|
65
65
|
## Related APIs
|
package/docs/public-contracts.md
CHANGED
|
@@ -15,7 +15,7 @@ Current contract groups:
|
|
|
15
15
|
- Extensions/middleware: `ExtensionLifecycleEventName`, `ExtensionEvent`, `Extension`, `ExtensionAPI`, `MiddlewareHookName`, `Middleware`, `MiddlewareNext`, `MiddlewareRegistry`
|
|
16
16
|
- Configuration/manifests: `ConfigProvider`, `ConfigLayer`, `ConfigLoadContext`, `PrismManifest`, `ManifestContributionDeclaration`, `ManifestResourceDeclaration`, `ManifestContributionKind`
|
|
17
17
|
- Stores/resources/settings/credentials/compaction/retry/cache helpers: `SessionEntry`, `SessionStore`, `StoreFactory`, `Resource`, `ResourceLoader`, `ResourceLoadContext`, `SettingsProvider`, `CredentialRequest`, `Credential`, `CredentialResolver`, `CompactionStrategy`, `CompactionContext`, `CompactionResult`, `CompactionOptions`, `CompactionMiddlewarePayload`, `CompactionEntryData`, `DefaultCompactionStrategyOptions`, `RetryPolicy`, `RetryContext`, `RetryDecision`, `RetryOptions`, `RetryMiddlewarePayload`, `DefaultRetryPolicyOptions`, `CacheUsageReport`, `sanitizeCacheKey`, `mapCacheRetention`, `applyCacheControl`, `cacheHitRate`, `cacheSavings`, `cacheUsageReport`
|
|
18
|
-
- Production persistence (adapter-facing): `ProductionPersistenceStore` (optional `lifecycle`), `CheckpointStore`, `CheckpointKey`, `CheckpointSaveInput`, `CheckpointRecord`, `CheckpointQuery`, `LeaseStore`, `LeaseKey`, `LeaseAcquireInput`, `LeaseClaimInput`, `LeaseRecord`, `PersistencePage`, `PersistenceQuery`, `OwnershipScope`, `SessionRecord`, `SessionQuery`, `BranchRecord`, `BranchQuery`, `SessionEntryQuery`, `RunRecord`, `RunQuery`, `RunFeedbackRecord`, `RunFeedbackStore`, `RunFeedbackQuery`, `AgentEventRecord`, `AgentEventQuery`, `ToolCallRecord`, `ToolCallQuery`, `UsageRecord`, `UsageQuery`, `AgentDefinitionRecord`, `AgentDefinitionQuery`, `RetentionPolicy`, `RetentionPolicyQuery`, `MigrationRecord`, `MigrationQuery`, `PersistenceLifecycleStore`, `LegalHoldRecord`, `TenantQuota`
|
|
18
|
+
- Production persistence (adapter-facing): `ProductionPersistenceStore` (optional `lifecycle`), `CheckpointStore`, `CheckpointKey`, `CheckpointSaveInput`, `CheckpointRecord`, `CheckpointQuery`, `LeaseStore`, `LeaseKey`, `LeaseAcquireInput`, `LeaseClaimInput`, `LeaseRecord`, `PersistencePage`, `PersistenceQuery`, `OwnershipScope`, `SessionRecord`, `SessionQuery`, `SessionMetadataConflict`, `SessionMetadataConflictError`, `SESSION_METADATA_CONFLICT_CODE`, `isSessionMetadataConflict`, `BranchRecord`, `BranchQuery`, `SessionEntryQuery`, `RunRecord`, `RunQuery`, `RunFeedbackRecord`, `RunFeedbackStore`, `RunFeedbackQuery`, `AgentEventRecord`, `AgentEventQuery`, `ToolCallRecord`, `ToolCallQuery`, `UsageRecord`, `UsageQuery`, `AgentDefinitionRecord`, `AgentDefinitionQuery`, `RetentionPolicy`, `RetentionPolicyQuery`, `MigrationRecord`, `MigrationQuery`, `PersistenceLifecycleStore`, `LegalHoldRecord`, `TenantQuota`
|
|
19
19
|
- Identity: `Principal`, `AgentIdentity`, `IdentityVerifier`, `assertIdentityActive`, `narrowIdentity`, `ownershipFromIdentity`, `assertIdentityMatchesOwnership`, `assertIdentityPropagation`, `identityTelemetryAttributes`, `resolveRunIdentity`, `IdentityError`, identity limit constants
|
|
20
20
|
|
|
21
21
|
## When to use it
|
|
@@ -148,11 +148,11 @@ Important request shapes:
|
|
|
148
148
|
| `CheckpointStore` | Generic versioned checkpoint capability: save/load/bounded-list/delete by namespace and key, with ownership, exact-version CAS, and lease fencing. `createMemoryCheckpointStore()` is the reference implementation; it is bounded — `maxRecords` (default 10,000, evicts least-recently-saved) and `maxValueBytes` (default 1 MiB per JSON value). |
|
|
149
149
|
| `LeaseStore` | Atomic acquire/renew/release/get by namespace and key, with opaque claim tokens, expiry, ownership scope, and monotonically increasing takeover fences. `createMemoryLeaseStore()` is the reference implementation. |
|
|
150
150
|
| `RunFeedbackStore` | Immutable append, bounded owned query, and owned deletion for ratings/comments/tags linked to existing run/trace/evaluation IDs. `createMemoryRunFeedbackStore()` is the reference implementation. |
|
|
151
|
-
| `EventMultiplexer<T>` | Generic bounded fan-in from async sources. `createEventMultiplexer()` owns queue limits, overflow policy, abort, source teardown, and close behavior. |
|
|
151
|
+
| `EventMultiplexer<T>` | Generic bounded fan-in from async sources. `createEventMultiplexer()` owns queue limits, overflow policy, abort, source teardown, and close behavior. Single-consumer contract: a second concurrent `subscribe()` throws `EventMultiplexerError` (`ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER`); the slot frees when the active consumer completes/is `return()`ed at a yield or the multiplexer closes. `observe` fan-in is unchanged (broadcast happens at the source). |
|
|
152
152
|
| `PersistencePage<T>` | Cursor-paginated result page: `items`, optional `nextCursor`, optional `total`. |
|
|
153
153
|
| `PersistenceQuery` | Common pagination controls: `cursor?`, `limit?`, `order?: "asc" \| "desc"`. |
|
|
154
154
|
| `OwnershipScope` | Multi-tenant scope: `tenantId?`, `accountId?`, `userId?`. Included in records and queries. |
|
|
155
|
-
| `SessionRecord` / `SessionQuery` | Stored session and query filters (parent, agent definition, retention policy, timestamps, ownership). |
|
|
155
|
+
| `SessionRecord` / `SessionQuery` | Stored session and query filters (parent, agent definition, retention policy, timestamps, ownership). `SessionRecord.version` (with `appendSession` `expectedVersion`) is the optimistic metadata CAS: 0 = create-only, N = exact-version update; mismatch throws `SessionMetadataConflictError` (`metadata_conflict`). |
|
|
156
156
|
| `SessionIndex` / `SessionSearchQuery` / `SessionSearchHit` | Bounded optional session search seam (`search` / `SessionStore.searchSessions?`). Filters: workspace (`metadata.workspaceRoot`), time, provider/model, label/summary, optional FTS `query`, ownership. Hits return `sessionId` + optional `leafId` for resume; never credentials. Caps via `resolveSessionSearchQuery` / `DEFAULT_*` / `HARD_MAX_*` session-search constants. |
|
|
157
157
|
| `contextBudget` / `getContextBudgetReport` / `ContextBudgetError` | Opt-in assembler budget on `AssembleProviderInputOptions`; deterministic eviction; omission report in `ProviderRequest.metadata` (kinds/ids/sizes only). |
|
|
158
158
|
| `AgentSession.steer` / `SteerOptions` / pending-steer caps | Mid-run enqueue into active run; optional `softInterrupt`; default 8 msgs / 64 KiB UTF-8. |
|
|
@@ -68,6 +68,7 @@ Run `npm run clean` explicitly after deleting source files or switching branches
|
|
|
68
68
|
| `@arnilo/prism/providers/media` | `dist/providers/media.{js,d.ts}` |
|
|
69
69
|
| `@arnilo/prism/testing/provider-conformance` | `dist/testing/provider-conformance.{js,d.ts}` |
|
|
70
70
|
| `@arnilo/prism/testing/agent-event-source-conformance` | `dist/testing/agent-event-source-conformance.{js,d.ts}` |
|
|
71
|
+
| `@arnilo/prism/testing/state-concurrency-conformance` | `dist/testing/state-concurrency-conformance.{js,d.ts}` |
|
|
71
72
|
| `@arnilo/prism/testing/session-store-conformance` | `dist/testing/session-store-conformance.{js,d.ts}` |
|
|
72
73
|
| `@arnilo/prism/testing/compaction-conformance` | `dist/testing/compaction-conformance.{js,d.ts}` |
|
|
73
74
|
| `@arnilo/prism/testing/tool-conformance` | `dist/testing/tool-conformance.{js,d.ts}` |
|
|
@@ -274,6 +275,44 @@ git push origin v0.1.2 # tag push triggers release.yml publish job (prove
|
|
|
274
275
|
|
|
275
276
|
**Rollback notes.** `release:publish --version 0.1.2 --resume --report release-artifacts/publish-report.json` resumes an interrupted publication and skips only registry versions whose internal dependency fingerprint matches the local manifest. A failed package aborts the run with its status written to the report; re-run after fixing the cause. npm cannot unpublish the `0.1.2` line after 72 hours — a post-publication defect ships as a `0.1.x` patch (additive-only compat promise, `release:gate` enforced), or as a documented break in the next line with a `docs/migration.md` entry. `0.1.2` is store-compatible with `0.1.1` in **both directions** (no migration ran — same checksum-protected contract), so an operator may defer or roll back the patch without a database rollback.
|
|
276
277
|
|
|
278
|
+
### 0.2.2 publish handoff (plan 022 Task 6)
|
|
279
|
+
|
|
280
|
+
**Decision: GO when the operator prerequisites below are recorded.** Release **0.2.2** (plan 022) is the concurrent-state-and-durability-integrity cut on the 0.2.x review-remediation line. API surface **additive-only** vs 0.2.1 (plain reviewed compat gate at 0.2.2: deltas are the version literal plus `ModelRouterStateStore.reserveBudget`/`commitBudget`/`releaseBudget`, `ModelRouterReservation`, `ModelRouterBudgets.reservationTtlMs`, `ModelRouterLimits.maxRateKeys`/`maxBudgetKeys`, `SessionRecord.version` with `appendSession` `expectedVersion`, `EventMultiplexerError`, and the `@arnilo/prism/testing/state-concurrency-conformance` subpath — no removal; baselines regenerated with `--update-baseline`, no `--allow-break`; freeze manifest `scripts/phase22-freeze-manifest.json` records per-task evidence tokens). Four behavior tightenings documented in `docs/migration.md` `0.2.1 → 0.2.2`: (1) **atomic model-budget reservation** — `reserveBudget` at admission (used + reserved + requested <= window max, `{reservationId, fencingToken, admitted, retryAfterMs?}`), `commitBudget`/`releaseBudget` at outcome, TTL expiry (default 60 s) with late commits reconciled as `unknownUsage: true`; rate/budget key maps capped (4,096 default / 65,536 hard) with LRU eviction that never drops a held-reservation row; durable reservations live in a new `reservations` JSONB column (enterprise migration 003). (2) **atomic conversation metadata** — `SessionRecord.version` + `appendSession` `expectedVersion` (`0` create-only, `N>0` exact-CAS update-only, omitted = legacy last-write-wins); stale writes throw `SessionMetadataConflictError` `metadata_conflict` (versions only, HTTP 409); concurrent create/branch/archive single-statement with branch caps inside the CAS, archive wins, deleted rows never resurrect (migration 008). (3) **single-consumer EventMultiplexer** — second concurrent `subscribe()` throws `EventMultiplexerError` `ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER`. (4) **restart-stable NATS durable identity + bounded non-durable active-run registries** — durable name exactly `prism_<hmac16>`, crash-resume continues from the last ack, orphaned 0.2.1 random-suffixed consumers reclaimed on clean stop; workflow active-run registry sweeps aborted entries and fails closed at the 512 cap. New regression surface: `scripts/phase22-security.test.mjs` (4 blockers + gate accounting over built public entrypoints, wired into `security:threat-suites`), packed plain-JS `security22.mjs` consumer in install-smoke, the `@arnilo/prism/testing/state-concurrency-conformance` harness (7 probes; memory leg in npm test, durable legs in `test:postgres` and the NATS seam; zero timing-only sleeps), and the `scripts/phase22-conformance.test.mjs` gate in the `test:postgres` chain. Store compatibility with 0.2.1: **forward-only migrations** (008 + 003), see `docs/migration.md` for rollback risk. Exit gate green: npm test core + workspace + script gates (incl. phase21-freeze done-phase + phase22 conformance), `sdk:ready` exit 0, audit 0 moderate, secret scans 0 findings, pack dry-run 50/50 twice byte-identical, plain reviewed compat gate at 0.2.2, protected OIDC/OPA evidence + durable state-concurrency evidence; evidence in `scripts/phase22-baseline.json` `exitGate`. Rollback = restore the 0.2.1 manifests/tag — but that reopens all four race windows, so prefer fixing the failing host on 0.2.2.
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
# Operator prerequisites recorded: clean tree at the v0.2.2 tag candidate, GPG key, npm OIDC publisher.
|
|
284
|
+
npm test # core + workspace suites + all script gates (incl. phase22 conformance)
|
|
285
|
+
npm run security:threat-suites # phase8-11 + phase20 + phase21 + phase22 public-entry conformance
|
|
286
|
+
npm run sdk:ready # typecheck, lint, format, test, coverage, pack, release:gate
|
|
287
|
+
node scripts/release.mjs gate --version 0.2.2 # plain reviewed additive gate, 0 breaking deltas
|
|
288
|
+
npm run pack:dry-run # twice; diff reports — deterministic
|
|
289
|
+
npm audit --audit-level=moderate
|
|
290
|
+
npm run release:check -- --version 0.2.2 --report /tmp/prism-0.2.2-preflight.json
|
|
291
|
+
npm run release:publish -- --version 0.2.2 --dry-run --allow-dirty --allow-untagged --report /tmp/prism-0.2.2-dry-run.json
|
|
292
|
+
# run the dry-run twice and diff the reports: deterministic, byte-identical
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Protected evidence (never a passing skip): live OIDC JWKS through the default pinned path (`createOidcIdentityVerifier` against a real public IdP — real DNS/TLS/JWKS document, e.g. `https://login.microsoftonline.com/common/discovery/v2.0/keys`, success proven by a key-lookup miss after a 200 fetch), live OPA (dockerized `openpolicyagent/opa`, default pinned path fails closed `ssrf_denied`), and the durable state-concurrency legs (Postgres `prism_phase22_*` schemas for sessions/checkpoints/events + enterprise router reservations/idempotency, NATS restart-durable resume against the seam). Missing protected evidence records 0.2.2 as **blocked**, never a passing skip.
|
|
296
|
+
|
|
297
|
+
### 0.2.1 publish handoff (plan 021 Task 8)
|
|
298
|
+
|
|
299
|
+
**Decision: GO when the operator prerequisites below are recorded.** Release **0.2.1** (plan 021) is the provider-completion and outbound-trust-boundaries cut on the 0.2.x review-remediation line. API surface **additive-only** vs 0.2.0 (plain reviewed compat gate at 0.2.1: the only deltas are the version literal and `@arnilo/prism-mcp` transport helpers `boundResponse`/`defaultResolver`/`isLoopbackAddress`/`isLoopbackHostname`/`normalizeHostname`/`raceAbort`/`requestPinned`/`resolvePinnedAddress` becoming re-exports of the lifted core primitives — same names/signatures, no removal; baselines regenerated with `--update-baseline`, no `--allow-break`; freeze manifest `scripts/phase21-freeze-manifest.json` machine-checks each task's diff and the preserved surface). Five documented security-motivated behavior tightenings in `docs/migration.md` `0.2.0 → 0.2.1`: (1) **strict stream completion is the shared default** (`strictCompletion: true` in `createOpenAICompatibleProvider`; explicit `false` stays the documented opt-out; truncated streams fail `ProviderTransportError` `incomplete_delta` instead of a successful `providerDone`; applies to Azure/Bedrock/Vertex/OpenRouter/ZAI/NeuralWatt); (2) **bounded success bodies** — additive `readBoundedResponseJson` (65,536-byte ceiling, depth 32, properties 4096, shape gate, abort, redacted errors, `response_body_shape` code) replaces unbounded `response.json()` on all ten model-discovery sites plus NeuralWatt quota, Alibaba embeddings, OpenAI uploads, and both OAuth success paths; (3) **DNS-pinned OIDC JWKS/OPA/content fetch, redirects rejected** — core `pinnedFetch` (one resolve, 1–32 bound, per-candidate SSRF validation, pinned-lookup socket) serves the default JWKS, OPA decision, and content/media paths; 3xx fails `MediaContentError` `redirect`; private/metadata/loopback fails `ssrf_denied`; MCP re-exports the lifted helpers byte-identically; (4) **shared bounded OAuth device/token polling** — core `pollDeviceCodeToken` serves provider-openai and credentials-node with equivalent cadence/backoff/redaction; (5) **edge fixes** — Azure/Vertex credential-once, Bedrock duplicate-case/repeated-query SigV4 canonicalization, OpenAI upload failed-DELETE retention, cache `__overflow__` tokens-only. New regression surface: `scripts/phase21-security.test.mjs` (10 conformance tests over built public entrypoints, wired into `security:threat-suites`) and a packed plain-JS `security21.mjs` consumer in install-smoke. Store compatibility with 0.2.0: **compatible, no migration**. Exit gate green: npm test core + script gates (incl. phase21-freeze done-phase), `sdk:ready` exit 0, audit 0 moderate, pack dry-run 50/50 twice byte-identical, plain reviewed compat gate at 0.2.1, live OIDC JWKS + live OPA protected evidence; evidence in `scripts/phase21-baseline.json` `exitGate`. Rollback = restore the 0.2.0 manifests/tag — but rollback restores the five boundary gaps, so hosts should disable truncated-stream acceptance, unbounded-body endpoints, redirect-following fetches, rotating-credential reuse, and upload cleanup at their own boundary if rollback is unavoidable.
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
# Operator prerequisites recorded: clean tree at the v0.2.1 tag candidate, GPG key, npm OIDC publisher.
|
|
303
|
+
npm test # core + workspace suites + all script gates
|
|
304
|
+
npm run security:threat-suites # phase8-11 + phase20 + phase21 public-entry conformance
|
|
305
|
+
npm run sdk:ready # typecheck, lint, format, test, coverage, pack, release:gate
|
|
306
|
+
node scripts/release.mjs gate --version 0.2.1 # plain reviewed additive gate, 0 breaking deltas
|
|
307
|
+
npm run pack:dry-run # twice; diff reports — deterministic
|
|
308
|
+
npm audit --audit-level=moderate
|
|
309
|
+
npm run release:check -- --version 0.2.1 --report /tmp/prism-0.2.1-preflight.json
|
|
310
|
+
npm run release:publish -- --version 0.2.1 --dry-run --allow-dirty --allow-untagged --report /tmp/prism-0.2.1-dry-run.json
|
|
311
|
+
# run the dry-run twice and diff the reports: deterministic, byte-identical
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Protected evidence (never a passing skip): live OIDC JWKS through the default pinned path (`createOidcIdentityVerifier` against a real public IdP — real DNS/TLS/JWKS document, e.g. `https://login.microsoftonline.com/common/discovery/v2.0/keys`, success proven by a key-lookup miss after a 200 fetch) and live OPA (`docker run -p 127.0.0.1:8181:8181 openpolicyagent/opa run --server`, push a policy, then prove the default pinned path fails closed `ssrf_denied` against the real server; decision-success behavior is covered by the built public conformance suite since the pinned path refuses private addresses by design). Missing protected evidence records 0.2.1 as **blocked**, never a passing skip.
|
|
315
|
+
|
|
277
316
|
### 0.2.0 publish handoff (plan 020 Task 6)
|
|
278
317
|
|
|
279
318
|
**Decision: GO when the operator prerequisites below are recorded.** Release **0.2.0** (plan 020) is the first cut of the 0.2.x review-remediation line — fail-closed runtime and sandbox security. API surface **additive-only** vs 0.1.7 (plain compat gate at 0.2.0: 0 breaking declaration deltas — the three blockers are behavior tightenings, not removals; `containmentClaim` retained deprecated; baseline text regenerated with `--update-baseline`, no `--allow-break` anywhere; freeze manifest `scripts/phase20-freeze-manifest.json` machine-checks each task's diff stayed inside its allowed files). Shipped: (1) **durable-resume input validation** — `assertValidAgentRunResume` at the top of `prepareAgentRunResume` covers all four public resume entrypoints; unknown legacy decisions (`"sideways"`), malformed batches, oversized reasons/elicitation, duplicate approval ids fail closed `ERR_PRISM_DECISION_*` with zero checkpoint writes/tool calls (server parser stays defense in depth); (2) **work-tool environment isolation** — `createCliRunner` children get a fixed base allow-list + explicit env + forced HOME/telemetry controls + late-bound per-identity tokens, 64-name/64-KiB caps `ERR_PRISM_WORK_ENV`, absolute binary/configDir, linear output capture; (3) **explicit sandbox capabilities** — `SandboxAdapter.capabilities` (six immutable booleans, omission/malformed ⇒ all false), composition capabilities from verified wiring, `containmentClaim` deprecated as the conservative projection; Docker reports only verified controls, native reports filesystem/process/privilege false; docs/coding-security.md capability table, docs/host-security.md authorization guidance. New regression surface: `scripts/phase20-security.test.mjs` (public built entrypoints, wired into `security:threat-suites`), packed plain-JS consumer regressions in install-smoke, and the sandbox-browser workflow's fail-loud 0.2.0 blocker gate recording Docker/native capability evidence — **0.2.0 does not ship while any blocker is skipped**. Store compatibility with 0.1.7: **compatible, no migration** (no persisted-shape change; `docs/migration.md` `0.1.7 → 0.2.0` section). Exit gate green: npm test core + script gates (incl. phase20-freeze done-phase), `sdk:ready` exit 0, audit 0 moderate, pack dry-run 50/50 twice byte-identical, plain reviewed compat gate at 0.2.0, Docker daemon + native netns protected evidence; evidence in `scripts/phase20-baseline.json` `exitGate`. Rollback = restore the 0.1.7 manifests/tag — but rollback restores the three defects, so hosts should disable resume side effects and work-tool execution at their own boundary if rollback is unavoidable.
|