@arnilo/prism 0.0.20 → 0.0.23

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.
@@ -0,0 +1,173 @@
1
+ # Enterprise PostgreSQL state
2
+
3
+ ## What it does
4
+
5
+ `@arnilo/prism-enterprise-postgres` is one optional PostgreSQL composition for the four existing enterprise state seams:
6
+
7
+ | State | Composition property | Durable behavior |
8
+ | --- | --- | --- |
9
+ | Policy decisions | `policy` | Append-only `PolicyDecisionStore` with owner-bound cursor pages. |
10
+ | Evaluations | `evaluations` | `EvaluationStore` append/query records with exact owner pages. |
11
+ | Work mutations | `workIdempotency` | Atomic claim/CAS lifecycle for connector effects. |
12
+ | Model routing | `modelRouter` | Shared rate, budget, and circuit state for router replicas. |
13
+
14
+ `createPostgresEnterpriseState()` opens a host-supplied or adapter-owned `pg` pool, verifies/applies the fixed checksum-protected `001_enterprise_state` migration, and returns those stores plus explicit cleanup and close operations. Importing it performs no I/O. It is separate from session/run persistence in [`@arnilo/prism-session-store-postgres`](postgres-persistence.md).
15
+
16
+ ## When to use it
17
+
18
+ Use it for a multi-process production host that needs policy/evaluation audit state, connector mutation reconciliation, or model-router limits to survive restart and coordinate across replicas.
19
+
20
+ Use memory/file stores only for tests, demos, or a deliberately single-process host. They do not provide PostgreSQL cross-replica coordination. This package is optional; it adds no database driver to `@arnilo/prism` core.
21
+
22
+ ## Inputs / request
23
+
24
+ ```ts
25
+ import { createPostgresEnterpriseState } from "@arnilo/prism-enterprise-postgres";
26
+ import { Pool } from "pg";
27
+
28
+ const pool = new Pool({
29
+ connectionString: process.env.DATABASE_URL,
30
+ max: 10,
31
+ ssl: { rejectUnauthorized: true },
32
+ });
33
+
34
+ const state = await createPostgresEnterpriseState({ pool, schema: "prism" });
35
+ ```
36
+
37
+ | Input | Meaning |
38
+ | --- | --- |
39
+ | `pool` | Existing `pg` pool. Exactly one of `pool` and `connectionString` is required; caller retains pool lifecycle. |
40
+ | `connectionString` | Creates an adapter-owned pool when `pool` is omitted. |
41
+ | `schema` | Validated identifier; defaults to `"prism"`. It is never interpolated as an unchecked SQL identifier. |
42
+ | `poolMax` / `poolConfig` | Adapter-owned pool settings; maximum defaults to 10 and is capped at 100. Put TLS in `poolConfig.ssl`. |
43
+ | `skipMigrations` | Isolated-test escape hatch only. Production opens verify/apply the fixed migration before request traffic. |
44
+ | `cleanup({ tenantId, accountId?, userId?, principalId, limit?, signal? })` | Explicit exact-owner cleanup; default 100 and hard maximum 500 rows. No worker starts automatically. |
45
+
46
+ Every durable policy/work/router action starts from an active host-verified `AgentIdentity`. Evaluation records/queries must be projected by the host from that verified ownership. PostgreSQL rejects missing tenant scope; optional account/user values are normalized and matched exactly, including absence.
47
+
48
+ ## Outputs / response / events
49
+
50
+ `PostgresEnterpriseState` has this public shape:
51
+
52
+ ```ts
53
+ interface PostgresEnterpriseState {
54
+ readonly policy: PolicyDecisionStore;
55
+ readonly evaluations: EvaluationStore;
56
+ readonly workIdempotency: IdempotencyStore;
57
+ readonly modelRouter: ModelRouterStateStore;
58
+ cleanup(input: EnterpriseStateCleanupInput): Promise<EnterpriseStateCleanupResult>;
59
+ close(): Promise<void>;
60
+ }
61
+ ```
62
+
63
+ `close()` ends only a pool created from `connectionString`; it leaves a caller-owned pool open. `cleanup()` returns `{ removed, transitioned }`. It transitions expired work claims to `unknown` and abandoned circuit probes back to cooldown before deleting expired/idle retained state.
64
+
65
+ Work mutations expose six observable states: **absent**, `in_progress`, `completed`, `failed_retryable`, `failed_terminal`, and `unknown`. `begin()` atomically returns `acquired` or the existing record; `complete`/`fail`/`markUnknown` use claim-token plus version compare-and-swap. `unknown` requires an operator/connector-specific `resolveUnknown` decision. It is not automatically replayed and it does **not** claim exactly-once external effects.
66
+
67
+ Model-router state is asynchronous and owner/principal/provider/model scoped. Supplying it to `createModelRouter({ stateStore })` requires awaited `resolve`, `recordUsage`, and `recordOutcome` calls with verified identity. The legacy synchronous `providerSource` facade throws `ERR_PRISM_MODEL_ROUTER_ASYNC_STATE` when durable state is configured.
68
+
69
+ ## Request/response example
70
+
71
+ ```json
72
+ {
73
+ "schema": "prism",
74
+ "cleanup": {
75
+ "tenantId": "tenant-1",
76
+ "userId": "user-7",
77
+ "principalId": "agent-9",
78
+ "limit": 100
79
+ },
80
+ "result": { "removed": 12, "transitioned": 1 }
81
+ }
82
+ ```
83
+
84
+ A migration creates `prism_policy_decisions`, `prism_evaluations`, `prism_work_idempotency`, three `prism_model_router_*` tables, and its separate `prism_enterprise_migrations` history. Startup serializes per-schema setup with an advisory transaction lock and rejects checksum or catalog drift rather than silently repairing it.
85
+
86
+ ## Implementation example
87
+
88
+ ```ts
89
+ import type { AgentIdentity } from "@arnilo/prism";
90
+ import { createPostgresEnterpriseState, type PostgresEnterpriseState } from "@arnilo/prism-enterprise-postgres";
91
+
92
+ const identity: AgentIdentity = {
93
+ tenantId: "tenant-1",
94
+ userId: "user-7",
95
+ principal: { kind: "agent", id: "agent-9" },
96
+ scopes: ["enterprise:write"],
97
+ verified: true,
98
+ issuedAt: "2026-08-03T00:00:00.000Z",
99
+ };
100
+
101
+ export async function recordEnterpriseState(state: PostgresEnterpriseState) {
102
+ const now = new Date().toISOString();
103
+ await state.policy.append({
104
+ id: "policy-1",
105
+ policyId: "mail",
106
+ policyVersion: "2026-08-03",
107
+ outcome: "approval",
108
+ identity,
109
+ target: { kind: "draft", id: "draft-1" },
110
+ evidenceRefs: ["rule:external-recipient"],
111
+ createdAt: now,
112
+ });
113
+ await state.evaluations.append({
114
+ id: "eval-1",
115
+ scorerId: "quality",
116
+ status: "scored",
117
+ score: 1,
118
+ sampled: true,
119
+ tenantId: identity.tenantId,
120
+ userId: identity.userId,
121
+ createdAt: now,
122
+ });
123
+
124
+ const claim = await state.workIdempotency.begin({ identity, key: "mail-send-1", op: "mail.send" });
125
+ if (claim.outcome === "acquired") {
126
+ // Run approved connector effect outside PostgreSQL transaction.
127
+ await state.workIdempotency.complete({
128
+ identity,
129
+ key: "mail-send-1",
130
+ op: "mail.send",
131
+ claimToken: claim.record.claimToken!,
132
+ expectedVersion: claim.record.version,
133
+ result: { draftId: "draft-1", resourceId: "message-1" },
134
+ });
135
+ }
136
+
137
+ await state.modelRouter.addUsage({
138
+ key: { tenantId: "tenant-1", userId: "user-7", principalId: "agent-9", provider: "openai", model: "gpt-4.1-mini" },
139
+ tokens: 100,
140
+ windowMs: 86_400_000,
141
+ now: Date.now(),
142
+ });
143
+ return state.cleanup({ tenantId: "tenant-1", userId: "user-7", principalId: "agent-9" });
144
+ }
145
+
146
+ // `state` comes from `await createPostgresEnterpriseState({ pool, schema: "prism" })`.
147
+ ```
148
+
149
+ ## Extension and configuration notes
150
+
151
+ - `createModelRouter({ resolver, stateStore: state.modelRouter })` keeps allow-list, residency, fallback, and diagnostics behavior in `@arnilo/prism-model-router`; this package only supplies durable state.
152
+ - Policy/evaluation/query public contracts stay in their owning packages. This package exports only `createPostgresEnterpriseState`, its options/result types, and `EnterprisePostgresError`; it has no SQL, DDL, codec, queryable, or migration subpath.
153
+ - The fixed schema has no generic key/value table and no background cleanup scheduler. Schedule `state.cleanup()` from an authorized host job, size its bounded batch for the deployment, and monitor unknown work rows for reconciliation. Run protected integration checks with `PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres`; the command rejects an absent URL instead of silently skipping database coverage.
154
+ - Request-path state SQL uses `SELECT`, `INSERT`, `UPDATE`, and `DELETE` on the six state tables. The open/migration lifecycle additionally needs schema/catalog/advisory-lock and DDL permissions. Use a deployment migration principal for that lifecycle and a least-privilege request role for request traffic; this release intentionally does not ship a migration CLI or worker.
155
+
156
+ ## Security and performance notes
157
+
158
+ - Configure TLS, database credentials, pool timeouts, backups, restore drills, retention schedule, and database role grants in the host. Never put connection strings, tokens, prompts, raw connector responses, unrestricted payloads, or provider credentials in records.
159
+ - Every value is a bound parameter. Schema identifiers are validated; table/index names are fixed. Cursors embed and recheck ownership, so a foreign tenant cannot reuse a page cursor.
160
+ - Policy records cap at 64 KiB; evaluations at 64 KiB; work rows at 8 KiB; router material at 512 bytes. JSON rejects prototype-pollution keys, non-finite values, excess depth/properties, and over-size material.
161
+ - PostgreSQL transaction SQLSTATE `40001`/`40P01` retries whole safe transactions up to three times. Connector effects remain outside those transactions and ambiguous errors become `unknown` rather than being retried.
162
+ - Recorded `postgres:16-alpine` evidence (Node 24.18.0/Linux x64, 10 tenants × 10 principals × 1,000 policy/evaluation rows, 10,000 router keys, 16 clients) stayed below 50 ms p95 for point operations and 100 ms for cursor/cleanup pages. The highest recorded p95 was router-circuit contention at 28.410 ms. Fourteen representative `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)` shapes used named indexes with no sequential scans. These are recorded comparison evidence, not hardware-independent guarantees.
163
+
164
+ ## Related APIs
165
+
166
+ - [Policy and audit](policy-and-audit.md): policy record and WORM export semantics.
167
+ - [Evaluations](evaluations.md): scorer/evaluation record lifecycle.
168
+ - [Work tools](work-tools.md): draft approval, unknown outcomes, and connector boundaries.
169
+ - [Model routing](model-routing.md): durable asynchronous router migration.
170
+ - [PostgreSQL persistence](postgres-persistence.md): sessions/runs/checkpoints/leases adapter.
171
+ - [Database persistence](database-persistence.md): host retention and persistence guidance.
172
+ - [Host security](host-security.md): database ownership, TLS, secrets, and role boundaries.
173
+ - [Migration guide](migration.md): 0.0.22 → 0.0.23 upgrade steps.
@@ -146,6 +146,18 @@ Release 0.0.9 ships curated network-free adversarial fixtures in package tests:
146
146
 
147
147
  Fixtures reuse `@arnilo/prism-evals` (`defineDataset` / `defineScorer` / `scoreRun` / `assertEvaluationThreshold` / `serializeEvaluationReport`). Optional SWE-bench-compatible or live-browser harnesses remain host adapters — they are not default dependencies or quality claims. Protected real Docker/Playwright gates stay env-gated (`PRISM_TEST_DOCKER_SANDBOX`, `PRISM_LIVE_PLAYWRIGHT`) and never enter `sdk:ready`.
148
148
 
149
+ ## PostgreSQL enterprise state (0.0.23)
150
+
151
+ `createPostgresEnterpriseState({ pool, schema }).evaluations` implements this package's existing `EvaluationStore`. The host creates an `EvaluationRecord` from verified ownership before append; every PostgreSQL query requires tenant scope, uses exact normalized account/user matching, and returns owner-bound opaque cursor pages. It is durable across reopen and supports the existing id/scorer/session/run/trace/dataset/item/experiment/status filters.
152
+
153
+ ```ts
154
+ const state = await createPostgresEnterpriseState({ pool });
155
+ await state.evaluations.append(record); // record is already host-owned and redacted
156
+ const page = await state.evaluations.query({ tenantId: "t1", userId: "u1", status: "scored", limit: 100 });
157
+ ```
158
+
159
+ Memory evaluation storage remains suitable for development and deterministic tests; it is not cross-replica production storage. PostgreSQL bounds each evaluation row to 64 KiB (reason/error 8 KiB each and metadata 32 KiB).
160
+
149
161
  ## Related APIs
150
162
 
151
163
  - [Agent/session runtime](agent-session-runtime.md): `AgentRunResult` and `session.run()`
@@ -153,4 +165,5 @@ Fixtures reuse `@arnilo/prism-evals` (`defineDataset` / `defineScorer` / `scoreR
153
165
  - [Observability](observability.md): use `onTraceReference` or bounded `traceId(runId)` to supply `ScoreRunOptions.traceId`; evaluation telemetry emits no reason/explanation content
154
166
  - [Coding agent tools](coding-agent-tools.md) / [Browser automation](browser-automation.md) / [Workflows](workflows.md): network-free coding-task composition at `examples/durable-coding-workflow.ts`; adversarial coding/browser eval example at `examples/coding-browser-evaluation.ts`
155
167
  - [Performance limits](performance.md): `scripts/benchmark-0.0.11.mjs` search/budget evidence, `scripts/benchmark-0.0.10.mjs` workspace-mode evidence, and `scripts/benchmark-0.0.9.mjs` coding/browser evidence fields
168
+ - [Enterprise PostgreSQL state](enterprise-postgres-state.md): durable owner-scoped evaluation storage.
156
169
  - [Release and install](release-and-install.md): optional package install and protected sandbox-browser workflow
@@ -140,6 +140,8 @@ await kernel.middleware.run("provider_request", { metadata: {} });
140
140
  - [Compaction and retry policies](compaction-and-retry.md): compaction strategy/retry policy contributions and `compaction`/`retry` middleware runtime behavior.
141
141
  - [LLM compaction package](compaction-llm.md): optional extension helper that registers a provider-backed compaction strategy.
142
142
  - [Observational memory compaction package](compaction-observational-memory.md): optional extension helper that registers an inert fast memory compaction strategy.
143
+ - [Caveman behavior integration](caveman.md): optional `@arnilo/prism-caveman` upstream Caveman skills, commands, level injector, and session `caveman-level` persistence.
144
+ - [Ponytail behavior integration](ponytail.md): optional `@arnilo/prism-ponytail` upstream Ponytail skills, commands, mode injector, and session `ponytail-mode` persistence.
143
145
  - [Public contracts](public-contracts.md): `Extension`, `ExtensionAPI`, and contribution contract types.
144
146
  - [Credentials and redaction](credentials-and-redaction.md): secret-redaction behavior used for extension errors.
145
147
 
@@ -36,6 +36,7 @@ Start from explicit host inputs. Do not let runtime code discover security state
36
36
  | Remote agent/workflow API | host authentication + ownership mapping | `@arnilo/prism-server`, `createPrismHandler()` |
37
37
  | Authenticated identity | host `IdentityVerifier` → verified `AgentIdentity` | [Agent identity](agent-identity.md), `assertIdentityActive`, `narrowIdentity` |
38
38
  | Policy decision audit | optional redacted ledger + host WORM sink | [Policy and audit](policy-and-audit.md), `@arnilo/prism-policy` |
39
+ | Durable enterprise state | host PostgreSQL pool, TLS, exact owner/principal projection, migration/runtime database roles, backup and explicit cleanup schedule | [Enterprise PostgreSQL state](enterprise-postgres-state.md), `@arnilo/prism-enterprise-postgres` |
39
40
  | MCP server exposure | host MCP auth + selected capability list | `createPrismMcpServer()`, `createPrismMcpWebHandler()` |
40
41
 
41
42
  ## Outputs / response / events
@@ -152,6 +153,7 @@ Wire those values where they matter: provider adapters receive the resolved cred
152
153
  - Default remote-media loading resolves every DNS answer, rejects the hostname if any address is non-public, and pins one validated address through the request. Explicit `allowedHostnames` can trust private destinations. A host-supplied `fetch` owns DNS/rebinding/proxy/redirect safety; a custom `requestUrl` must connect to its supplied validated address.
153
154
  - Permission checks happen before tool validation and before `tool.execute()`. Middleware cannot grant permission by renaming a tool.
154
155
  - Session stores and ledgers receive redacted values when a redactor is active, but durable storage remains host-owned. Enforce tenant/account/user ownership, retention, legal hold, and quotas via `ProductionPersistenceStore.lifecycle` (or host-equivalent DB controls). Hold always blocks delete.
156
+ - `@arnilo/prism-enterprise-postgres` request paths require exact tenant scope plus principal for work/router state, use bound SQL values, and retain no prompts, connector request bodies, raw provider results, tokens, or credentials. Configure TLS/credential rotation/connection limits with the host `pg` pool. Run checksum/catalog migration setup with a controlled migration principal; keep request-path SQL least-privilege (`USAGE`, `SELECT`, `INSERT`, `UPDATE`, `DELETE` on six state tables) and do not grant request workers `CREATE`, `ALTER`, `DROP`, `TRUNCATE`, `GRANT`, or `COPY PROGRAM`. Back up and restore-test the schema; run bounded owner-scoped `state.cleanup()` from an authorized host job. `unknown` connector outcomes require reconciliation and must never auto-replay.
155
157
  - Prefer `createExtensionKernel({ loadPolicy })` allow-list/signature checks before loading third-party extension packages.
156
158
  - Provider-owned auth/content/session/cache/security headers win over caller headers in adapters that merge headers.
157
159
  - Security checks are bounded explicit calls on the active path. Prism adds no hidden global middleware, background workers, watchers, network calls, or filesystem scans.
@@ -213,4 +215,5 @@ PostgreSQL TLS/network policy, MCP endpoint trust/credentials and egress policy
213
215
  - [Session stores](session-stores.md): durable session store contract and secret/persistence boundaries.
214
216
  - [Runs and usage ledger](runs-and-usage.md): redacted run/event/tool/usage ledger records.
215
217
  - [Database persistence](database-persistence.md): production schema, ownership, indexes, retention, and adapter readiness checklist.
218
+ - [Enterprise PostgreSQL state](enterprise-postgres-state.md): durable policy/evaluation/work/router state and database-role boundary.
216
219
  - [Provider caching](provider-caching.md): cache keys and provider-owned header safety rules.
package/docs/index.md CHANGED
@@ -7,8 +7,8 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
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.
10
- - [Policy and audit](policy-and-audit.md): optional `@arnilo/prism-policy` decision ledger (allow/deny/modify/approval), evidence refs only, and cursor-paginated WORM export.
11
- - [Model routing](model-routing.md): optional `@arnilo/prism-model-router` allow-list/residency/budget/rate/circuit/fallback governance over `ProviderResolver` with redacted diagnostics.
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.
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.
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()`, subscribe to normalized events, and expose opted-in durable lifecycle capabilities.
@@ -17,7 +17,7 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
17
17
  - [Guardrails](guardrails.md): typed fail-closed input/output/tool checks with buffered provider output and redacted decision records.
18
18
  - [Agent events](agent-events.md): redacted lifecycle stream used by UIs, ledgers, and metadata-only parented telemetry; message/progress deltas never create spans.
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
- - [Evaluations](evaluations.md): deterministic and bounded trace/model-judge/pairwise scoring, CI thresholds, OTel trace-reference linkage, coding/browser adversarial fixtures, and ID-only linkage to immutable owned run feedback.
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.
22
22
  - [Performance limits](performance.md): 0.0.15 network-free provider/RAG/memory benchmark evidence and frozen caps, bounded evaluation traces/judges/reports, 0.0.12 frontend interoperability, 0.0.11 search/budget, 0.0.10 workspace-mode, 0.0.9 coding/browser, security scan/live-canary backstops, live subscriber queues, branch-read pagination expectations, JSONL/dev-store limits, and production sizing assumptions.
23
23
  - [Structured output](structured-output.md): the `Artifact*` seam plus provider-native `StructuredOutputOptions` / `structuredOutputMode` for capable models.
@@ -34,7 +34,8 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
34
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.
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
- - [Migration guide](migration.md): **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.
37
+ - [Enterprise PostgreSQL state](enterprise-postgres-state.md): optional `@arnilo/prism-enterprise-postgres` composition for durable policy/evaluation/work-idempotency/model-router state, exact ownership, checksummed migration, and explicit cleanup.
38
+ - [Migration guide](migration.md): **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.
38
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`.
39
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.
40
41
 
@@ -67,11 +68,11 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
67
68
  - [Tool validator JSON Schema package](../packages/tool-validator-json-schema/README.md): optional `@arnilo/prism-tool-validator-json-schema` adapter for `tool.parameters`.
68
69
  - [MCP client bridge and server exposure](mcp-tools.md): SDK-1.29.0 bounded tools/resources/prompts, host-owned roots/sampling/elicitation, exact-origin DNS-pinned client transport, and principal-bound opt-in Streamable HTTP sessions.
69
70
  - [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.
70
- - [Work tools](work-tools.md): optional `@arnilo/prism-work-tools` identity-scoped M365 + GWS connectors (hard-coded CLI argv, draft-then-approve, idempotency, shared result shapes); 0.0.14 adds a late-bound per-identity `tokenProvider` (env-only, fail-closed).
71
+ - [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).
71
72
  - [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.
72
73
  - [Browser automation](browser-automation.md): optional `@arnilo/prism-browser` with host-supplied Playwright contexts, AI-mode snapshots/refs, ordered `browser_open`/`browser_snapshot`/`browser_act`/`browser_close`, egress/side-effect/upload/download/screenshot policy, finite page/action/snapshot/network/artifact caps, and 0.0.14 verified-state checkpoints with reload/verify-before-side-effect.
73
74
  - [Device adapters](device-adapters.md): deny-by-default realtime voice / desktop-control contract + conformance (0.0.14); no vendor package — admission fails closed without explicit consent+sandbox+approval, stream bounds, shared `RunLimits`, redacted telemetry.
74
- - [Coding agent tools](coding-agent-tools.md): optional `shell`, `read`, `write`, `edit`, `repo_list`, and `repo_search` definitions plus opt-in `createGitTools()` / `coding_check`, opt-in `createAskUserDecisionTool` (single/multi/free-text + durable suspend glue), and `runCodingGoalVerify`; durable plan/todo Markdown helpers with workflow `state.coding` checkpoint metadata; streamed text pages, bounded repository list/search, finite Git/check/plan/ask caps, bounded image/edit reads and write/edit payloads, finite shell wall/total-output limits, secure host-owned spill cleanup, pluggable bounded operation contracts, per-path mutation serialization, and optional `ExecutionPolicy`. Limits do not sandbox host access—gate with permission/trust policy and `@arnilo/prism-coding-security`.
75
+ - [Coding agent tools](coding-agent-tools.md): optional `shell`, `read`, `write`, `edit`, `repo_list`, `repo_search`, `glob`, `delete`, and `move` definitions plus opt-in `createGitTools()` / `coding_check`, opt-in `createAskUserDecisionTool` (single/multi/free-text + durable suspend glue), and `runCodingGoalVerify`; durable plan/todo Markdown helpers with workflow `state.coding` checkpoint metadata; streamed text pages, `repo_search` `outputMode`, bounded glob, optional read-before-write, finite Git/check/plan/ask caps, bounded image/edit reads and write/edit payloads, finite shell wall/total-output limits, secure host-owned spill cleanup, pluggable bounded operation contracts, per-path mutation serialization, and optional `ExecutionPolicy`. No PDF/trash/PTY/LSP in 0.0.21. Limits do not sandbox host access—gate with permission/trust policy and `@arnilo/prism-coding-security`.
75
76
  - [Coding execution approval and sandboxing](coding-security.md): path/command approval, identity-scoped caching, shell-turn exclusivity, required `workspaceMode` (`host`/`sandbox`) with fail-closed mixed wiring, `createSandboxCodingComposition()` containment metadata, and the disposable Docker/OCI sandbox reference with bounded workspace import/export.
76
77
 
77
78
  ## Extensions/plugins
@@ -101,7 +102,7 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
101
102
  - [Workflow/TUI scope](workflow-tui-primitives.md): records why 0.0.5 ships workflow APIs/RPC control but no interactive terminal UI.
102
103
 
103
104
  ## Security and credentials
104
- - [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, persistence, extensions, and tool validation.
105
+ - [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.
105
106
  - [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.
106
107
  - [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.
107
108
  - [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).
@@ -114,11 +115,15 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
114
115
  - [Compaction conformance](compaction-conformance.md): assert any `CompactionStrategy` returns a non-empty redacted summary and observes abort from `@arnilo/prism/testing/compaction-conformance`.
115
116
  - [Tool conformance](tool-conformance.md): assert the tool-dispatch blocked-reason matrix (unknown/denied/invalid/permission/validator) and success path from `@arnilo/prism/testing/tool-conformance`.
116
117
  - [Extension conformance](extension-conformance.md): assert an `Extension` setup runs, contributions stay inert, and setup errors are redacted or rethrown from `@arnilo/prism/testing/extension-conformance`.
117
- - `examples/`: compile-checked typed examples and runnable mock demos (SDK basics, provider registration, auth, tools, [`examples/ag-ui-server.ts`](../examples/ag-ui-server.ts), [`examples/enterprise-identity.ts`](../examples/enterprise-identity.ts), [`examples/enterprise-policy-audit.ts`](../examples/enterprise-policy-audit.ts), [`examples/enterprise-work-connectors.ts`](../examples/enterprise-work-connectors.ts), [`examples/conversation-durable-replay.ts`](../examples/conversation-durable-replay.ts), [`examples/artifact-review-delivery.ts`](../examples/artifact-review-delivery.ts), [`examples/server-deployment-seams.ts`](../examples/server-deployment-seams.ts), cache-aware prompt assembly, NeuralWatt agent run ([`examples/neuralwatt-agent-run.ts`](../examples/neuralwatt-agent-run.ts)), [`examples/coding-compaction.ts`](../examples/coding-compaction.ts), stores/branching, structured-output/artifact-loop, CLI, RPC, workflow orchestration).
118
+ - `examples/`: compile-checked typed examples and runnable mock demos (SDK basics, provider registration, auth, tools, [`examples/ag-ui-server.ts`](../examples/ag-ui-server.ts), [`examples/enterprise-identity.ts`](../examples/enterprise-identity.ts), [`examples/enterprise-policy-audit.ts`](../examples/enterprise-policy-audit.ts), [`examples/enterprise-work-connectors.ts`](../examples/enterprise-work-connectors.ts), [`examples/enterprise-postgres-state.ts`](../examples/enterprise-postgres-state.ts), [`examples/conversation-durable-replay.ts`](../examples/conversation-durable-replay.ts), [`examples/artifact-review-delivery.ts`](../examples/artifact-review-delivery.ts), [`examples/server-deployment-seams.ts`](../examples/server-deployment-seams.ts), cache-aware prompt assembly, NeuralWatt agent run ([`examples/neuralwatt-agent-run.ts`](../examples/neuralwatt-agent-run.ts)), [`examples/coding-compaction.ts`](../examples/coding-compaction.ts), [`examples/caveman-ponytail.ts`](../examples/caveman-ponytail.ts), stores/branching, structured-output/artifact-loop, CLI, RPC, workflow orchestration).
119
+
120
+ ## Third-party integrations
121
+ - [Caveman behavior integration](caveman.md): optional `@arnilo/prism-caveman` — upstream Caveman skills/commands, `caveman-mode` injector, session `caveman-level` persistence, progressive catalog + `load_skill`; requires host `upstreamPath` and session attach callbacks; inert until `kernel.load`.
122
+ - [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).
118
123
 
119
124
  ## Release and install
120
- - [Release and install](release-and-install.md): current **0.0.19** 44-package graph (Phase 2 observational memory lifecycle; plan 002), exact-peer/install/tarball rules, deterministic resumable publication and publish dry-run, 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.
121
- - [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.19** published target), signed-publication/live-canary prerequisites for 1.0, and Phase 12 demand-evidence entry criteria.
125
+ - [Release and install](release-and-install.md): current **0.0.23** 47-package graph (Phase 6 enterprise PostgreSQL state adapters; plan 006), exact-peer/install/tarball rules, deterministic resumable publication and publish dry-run, 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.
126
+ - [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.
122
127
  - [Review coverage (2026-07-26 Phase 11)](review-coverage-2026-07-26-phase-11.md): Plan 079 evidence freeze — baseline size/startup/benchmark budgets, hotspot domain extraction table, confirmed duplication survivors (redactor/cleanJson/row-codecs/checkpoints/exec-runner/approval/ownership), profile adoption recommendations, and tarball artifact-diet findings for 0.0.16.
123
128
  - [Review coverage (2026-07-26 Phase 10)](review-coverage-2026-07-26-phase-10.md): Plan 078 evidence freeze — OpenAI hosted tools/continuation/realtime, AI SDK version matrix, remaining provider metadata parity, RAG replaceSource/loaders/parsers/reranker/provenance/ingestion-status, memory export/rebuild/conformance, and 0.0.15 (43 → 43 manifests; no new package) release gates.
124
129
  - [Review coverage (2026-07-25 Phase 9)](review-coverage-2026-07-25-phase-9.md): Plan 077 evidence freeze — conversation service, memory consent/lifecycle, artifact co-work review, AG-UI co-work events, scoped M365/GWS OAuth, browser checkpoint composition, and deny-by-default device contracts for 0.0.14 (41 → 43 manifests; only the two provider packages are new).
package/docs/migration.md CHANGED
@@ -1,5 +1,73 @@
1
1
  # Migration guide
2
2
 
3
+ ## 0.0.22 → 0.0.23 production enterprise state adapters (intentional pre-1.0 contract changes)
4
+
5
+ Release **0.0.23** adds `@arnilo/prism-enterprise-postgres` and makes work-mutation idempotency plus durable model-router state explicit. Core agent/session behavior stays unchanged; install/configure this package only when a host needs PostgreSQL coordination.
6
+
7
+ 1. **Install and open deliberately.** Add `@arnilo/prism-enterprise-postgres` with `@arnilo/prism`, the four domain packages, and `pg`. Call `await createPostgresEnterpriseState({ pool, schema })`; import is inert. Open applies/verifies the checksum-protected enterprise migration under a per-schema advisory lock. Keep its migration history separate from session-store PostgreSQL history; backup/restore-test both. Configure TLS, credentials, pool limits, roles, and a deployment migration principal in the host.
8
+ 2. **Use one composition, not ad-hoc SQL.** Wire `state.policy`, `state.evaluations`, and `state.workIdempotency` into existing package APIs. Policy/work/router operations require active verified identity; hosts must project evaluation records and queries from verified ownership. Every query needs tenant scope; owner-bound cursors cannot cross tenants. Memory/JSONL stores remain test/single-process adapters, not production substitutes.
9
+ 3. **Replace work `get`/`put` with claim transitions.** `IdempotencyStore` now uses async `begin`, `complete`, `fail`, `markUnknown`, and `resolveUnknown` (plus reconciliation `get`). Call `begin` before an approved external connector effect and use returned claim token/version for CAS transitions. Treat **absent**, `in_progress`, `completed`, `failed_retryable`, `failed_terminal`, and `unknown` differently. Only completed summaries replay; ambiguous `unknown` requires connector/operator reconciliation and is never auto-replayed. This is not exactly-once delivery.
10
+ 4. **Make router paths asynchronous when state is durable.** Pass `stateStore: state.modelRouter` to `createModelRouter`. Await `resolve`, `recordUsage`, and `recordOutcome`, pass a verified identity to each, and retain any `circuitProbeToken` from `resolve` for outcome recording. `providerSource` is memory-only; with a durable state store it throws `ERR_PRISM_MODEL_ROUTER_ASYNC_STATE` rather than bypassing rate/budget/circuit state.
11
+ 5. **Own cleanup.** No background worker starts. Schedule bounded `await state.cleanup({ tenantId, accountId?, userId?, principalId, limit })` from an authorized host job; expired work claims become `unknown` and expired circuit probes reopen safely. Do not make a global sweep or auto-resolve unknown outcomes.
12
+ 6. **Keep request roles narrow.** Request state SQL is limited to `SELECT`/`INSERT`/`UPDATE`/`DELETE` on six state tables plus schema `USAGE`; DDL/catalog/advisory-lock work belongs to controlled migration setup. Never persist prompt/body/tool-argument material, raw connector/provider results, JWTs, or credentials. See [Enterprise PostgreSQL state](enterprise-postgres-state.md) for bounds, cleanup, SQL inventory, and recorded performance evidence.
13
+
14
+ ```ts
15
+ const state = await createPostgresEnterpriseState({ pool, schema: "prism" });
16
+ const router = createModelRouter({ resolver, stateStore: state.modelRouter });
17
+ const selected = await router.resolve({ model, identity });
18
+ await router.recordOutcome({ identity, provider: selected.provider.id, model: selected.model.model, success: true, circuitProbeToken: selected.circuitProbeToken });
19
+ await state.close(); // caller-owned pool stays open
20
+ ```
21
+
22
+ ## 0.0.21 → 0.0.22 third-party behavior integrations (additive)
23
+
24
+ Release **0.0.22** adds two optional behavior packages; core `@arnilo/prism` runtime behavior is unchanged.
25
+
26
+ 1. **New packages (opt-in).** `@arnilo/prism-caveman` and `@arnilo/prism-ponytail` wire upstream Caveman and Ponytail into Prism extension contracts. They are **not** included in `@arnilo/prism-code`, `@arnilo/prism-sdk`, or `@arnilo/prism-all` by default — install explicitly when needed.
27
+ 2. **Inert until loaded.** Import registers nothing. Host calls `createExtensionKernel().load([createCavemanExtension(...)])` / `createPonytailExtension(...)`.
28
+ 3. **Session attach required.** Both factories require host `appendEntry` and `getEntries` callbacks (same pattern as observational memory `attach`) for mode/level persistence (`caveman-level`, `ponytail-mode` custom entries).
29
+ 4. **Progressive disclosure.** Keep `skillsDisclosure: "progressive"` and register `createLoadSkillTool`; mode/level slices come from `caveman-mode` / `ponytail-mode` instruction injectors, not eager full `SKILL.md` bodies.
30
+ 5. **Upstream resolution.** Caveman requires `upstreamPath` to a [juliusbrussee/caveman](https://github.com/juliusbrussee/caveman) checkout (`skills/` marker). Ponytail resolves optional peer `@dietrichgebert/ponytail@^4.8.4` or `upstreamPath`. Missing upstream → `setup` throws; zero contributions registered.
31
+ 6. **Publish graph.** Publishable manifest count is **46** (was 44).
32
+
33
+ Example: `node examples/caveman-ponytail.ts` (network-free fixture upstream trees).
34
+
35
+ ```ts
36
+ import { createCavemanExtension } from "@arnilo/prism-caveman";
37
+ import { createPonytailExtension } from "@arnilo/prism-ponytail";
38
+
39
+ await kernel.load([
40
+ createCavemanExtension({ upstreamPath: "/path/to/caveman", appendEntry, getEntries }),
41
+ createPonytailExtension({ defaultMode: "full", appendEntry, getEntries }),
42
+ ]);
43
+ ```
44
+
45
+ No breaking changes for hosts that do not install the new packages.
46
+
47
+ ## 0.0.20 → 0.0.21 coding-tool capability gaps (small intentional breaks)
48
+
49
+ Release **0.0.21** completes Phase 4 coding-tool capability gaps in `@arnilo/prism-coding-agent` / `@arnilo/prism-coding-security`:
50
+
51
+ 1. **`repo_search` gains `outputMode`.** Optional `outputMode?: "content" | "files_with_matches" | "count"` (default `"content"`). Files-only and count modes omit match body text from model content; invalid values fail closed.
52
+ 2. **Bounded `glob` tool.** `createGlobTool` / aggregator membership; `*` / `?` / `**` only (no brace expansion); reuses repository walk limits; files only.
53
+ 3. **Optional read-before-write.** Host sets `requireReadBeforeWrite: true` with a shared `ReadPathSet` on read/write/edit; unread paths fail unless `force: true`. In-memory / session-scoped only — not checkpoint-persisted.
54
+ 4. **Bounded `delete` and `move`.** File or empty directory delete (no recursive); move with `overwrite` default `false`; high-risk `ExecutionPolicy` kinds; host undo is not automatic.
55
+ 5. **Aggregator membership.** `createCodingTools` → **9** tools (adds `glob`, `delete`, `move`); `createReadOnlyTools` → **4** (adds `glob`). Hosts asserting exact `.length` must update.
56
+ 6. **Approval / sandbox.** `isMutatingKind` includes `delete` and `move` (not `glob`). Full sandbox custom ops must supply delete/move backends; `RepositoryOperations` requires `glob`.
57
+
58
+ Example: `node examples/coding-tools-capability-gaps.ts` (network-free).
59
+
60
+ ```ts
61
+ const tools = createCodingTools(cwd); // length 9
62
+ const search = createRepoSearchTool(cwd);
63
+ await search.execute({ query: "TODO", outputMode: "files_with_matches" }, ctx);
64
+
65
+ const readPathSet = createReadPathSet();
66
+ const write = createWriteTool(cwd, { requireReadBeforeWrite: true, readPathSet });
67
+ ```
68
+
69
+ Fuzzy edit may still succeed silently on a normalized whitespace/unicode match — docs state that tradeoff; ambiguous multi-match already fails closed. No PDF/trash/PTY/LSP in 0.0.21.
70
+
3
71
  ## 0.0.19 → 0.0.20 skills and context progressive disclosure (small intentional breaks)
4
72
 
5
73
  Release **0.0.20** completes Phase 3 progressive skill disclosure in core `@arnilo/prism`:
@@ -14,7 +14,7 @@ Do not put secrets, prompts, or raw OpenRouter keys into diagnostics. Do not hon
14
14
 
15
15
  | API / field | Meaning |
16
16
  | --- | --- |
17
- | `createModelRouter({ resolver, ... })` | Wraps host `ProviderResolver` |
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
20
  | `budgets` / per-call `maxTokens` / `maxCostUsd` | Finite non-negative ceilings; `recordUsage` charges |
@@ -24,7 +24,7 @@ Do not put secrets, prompts, or raw OpenRouter keys into diagnostics. Do not hon
24
24
  | `allowOpenRouterRouting` | Default `false`; when false, routing metadata is stripped |
25
25
  | `onDiagnostics` | Optional redacted hook (e.g. policy ledger evidence ref) |
26
26
  | `router.resolve({ model, identity?, residency?, ... })` | Rich async selection |
27
- | `router.providerSource` | Sync `ProviderResolver` facade for `AgentConfig` |
27
+ | `router.providerSource` | Sync facade only for memory state; with `stateStore` it throws `ERR_PRISM_MODEL_ROUTER_ASYNC_STATE` rather than bypass durable checks. |
28
28
 
29
29
  Frozen caps (default / hard): attempts `3 / 8`, circuit keys `1,024 / 16,384`, diagnostics `8 KiB / 64 KiB`.
30
30
 
@@ -32,7 +32,7 @@ Frozen caps (default / hard): attempts `3 / 8`, circuit keys `1,024 / 16,384`, d
32
32
 
33
33
  - `ModelRouterResolveResult` — selected `provider` + possibly stripped `model`, `diagnostics`, and `providerRequestPolicy`.
34
34
  - Deny throws `ModelRouterError` with code + redacted `diagnostics` (allow-list/residency/budget fail closed without calling resolver).
35
- - `recordOutcome({ success })` opens/closes circuits; `recordUsage` advances budgets.
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.
36
36
 
37
37
  ## Request/response example
38
38
 
@@ -56,9 +56,12 @@ Frozen caps (default / hard): attempts `3 / 8`, circuit keys `1,024 / 16,384`, d
56
56
  ```ts
57
57
  import { createAgent, createProviderResolver } from "@arnilo/prism";
58
58
  import { createModelRouter } from "@arnilo/prism-model-router";
59
+ import { createPostgresEnterpriseState } from "@arnilo/prism-enterprise-postgres";
59
60
 
61
+ const enterprise = await createPostgresEnterpriseState({ pool, schema: "prism" });
60
62
  const router = createModelRouter({
61
63
  resolver: createProviderResolver(providers),
64
+ stateStore: enterprise.modelRouter,
62
65
  allowList: { providers: ["openai", "openrouter"] },
63
66
  allowedResidencies: ["eu"],
64
67
  fallbacks: [{ provider: "openrouter", model: "auto" }],
@@ -77,7 +80,11 @@ const agent = createAgent({
77
80
  provider,
78
81
  providerRequestPolicies: [providerRequestPolicy],
79
82
  });
80
- // or: providerSource: router.providerSource
83
+ await router.recordUsage({ identity, provider: provider.id, model: model.model, tokens: 500 });
84
+ await router.recordOutcome({ identity, provider: provider.id, model: model.model, success: true });
85
+ await enterprise.close();
86
+
87
+ // `router.providerSource` is unavailable with durable state; resolve before provider I/O.
81
88
  ```
82
89
 
83
90
  ## Extension and configuration notes
@@ -87,10 +94,11 @@ Router is optional. Chain returned `providerRequestPolicy` with other `ProviderR
87
94
  ## Security and performance notes
88
95
 
89
96
  - Allow-list and residency denies never call the underlying resolver.
90
- - Budget/rate/circuit state is memory-capped; oldest keys evict.
91
- - Diagnostics carry identity refs and attempt outcomes only — no prompts/secrets.
92
- - Selection is O(attempts × map ops); no network I/O inside the router.
93
- - Raising hard caps requires Phase 8 freeze + tests + docs updates.
97
+ - Without `stateStore`, budget/rate/circuit state is process-local, memory-capped, and oldest keys evict. It is not a cross-replica production path.
98
+ - With `stateStore: createPostgresEnterpriseState(...).modelRouter`, rate/budget updates 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.
99
+ - Diagnostics carry identity refs and attempt outcomes only — no prompts/secrets. Durable state stores at most bounded numeric/timestamp/token material, never prompts or credentials.
100
+ - Selection is O(attempts × state operations); no provider network I/O happens inside state updates. Recorded 0.0.23 PostgreSQL p95 point operations stayed under 50 ms and cursor/cleanup pages under 100 ms on the documented fixture.
101
+ - Raising hard caps requires a reviewed release update with tests and docs.
94
102
 
95
103
  ## Related APIs
96
104
 
@@ -99,4 +107,5 @@ Router is optional. Chain returned `providerRequestPolicy` with other `ProviderR
99
107
  - [OpenRouter](providers/openrouter.md)
100
108
  - [Policy and audit](policy-and-audit.md)
101
109
  - [Agent identity](agent-identity.md)
110
+ - [Enterprise PostgreSQL state](enterprise-postgres-state.md): durable router state, migration, cleanup, and ownership requirements.
102
111
  - Package README: [`@arnilo/prism-model-router`](../packages/model-router/README.md)
@@ -6,6 +6,20 @@ Evaluation defaults are finite: 100 trace rows × 20 pages and 4 MiB aggregate t
6
6
 
7
7
  This page states Prism runtime limits that keep slow consumers and long sessions from becoming unbounded memory or latency problems.
8
8
 
9
+ ## Release 0.0.23 enterprise PostgreSQL evidence
10
+
11
+ `node scripts/benchmark-0.0.23.mjs` is an explicit protected PostgreSQL benchmark, not part of `npm test` or `sdk:ready`. It requires `PRISM_TEST_POSTGRES_URL`, creates/drops an isolated schema, and checks frozen p95 ceilings from `scripts/budgets.json`. The checked `scripts/benchmark-0.0.23.json` evidence was recorded on Node v24.18.0/Linux x64 with `postgres:16-alpine`: 10 tenants × 10 principals × 1,000 policy/evaluation rows, 10,000 router keys, 16 pool clients, 100 warmups, 1,000 measured operations, and 100-row cleanup batches.
12
+
13
+ | Scenario | Recorded p95 ms | Ceiling |
14
+ | --- | ---: | ---: |
15
+ | Policy append / query | 0.747 / 1.479 | 50 / 100 |
16
+ | Evaluation append / query | 0.698 / 0.963 | 50 / 100 |
17
+ | Work claim+complete / contention | 1.892 / 4.162 | 50 / 50 |
18
+ | Router rate / budget / circuit contention | 12.011 / 6.715 / 28.410 | 50 / 50 / 50 |
19
+ | Explicit cleanup batch | 2.981 | 100 |
20
+
21
+ The same run accepted 1,000 rate claims, accumulated 16,000 budget tokens, granted 1,000 circuit probes, and verified 14 named-index `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)` plans with no sequential scans. Before cleanup it recorded 101,100 policy rows (68,517,888 bytes), 101,100 evaluation rows (97,296,384 bytes), and 121,100 router-rate rows; cleanup removed exactly 100,000 expired rate rows. PostgreSQL relation allocation does not necessarily shrink after `DELETE` under MVCC, so row removal—not immediate file shrink—is the cleanup assertion. Values are dated environment evidence, not universal production SLOs; size pools, partitions, retention, and cleanup frequency from host measurements.
22
+
9
23
  ## Release 0.0.16 performance budgets and artifact diet
10
24
 
11
25
  Release 0.0.16 is a simplification/readiness release: it added no performance-affecting code, so the six network-free scenario medians are held at the 0.0.15 baseline and the win is a smaller published artifact. Budgets live in `scripts/budgets.json` (measured baselines + tolerance) and are enforced two ways:
@@ -117,6 +117,17 @@ Policy is optional. Hosts wire `record*` helpers or `evaluateAndAppend` at permi
117
117
  - Evaluate/append are O(fields) and network-free in-package; remote WORM I/O stays in the host sink/adapter.
118
118
  - Export never full-scans: page size is capped; raise hard caps only with Phase 8 freeze + tests + docs updates.
119
119
 
120
+ ## PostgreSQL enterprise state (0.0.23)
121
+
122
+ For durable multi-replica policy decisions, construct [`createPostgresEnterpriseState`](enterprise-postgres-state.md) and pass its `policy` store to the existing helpers. PostgreSQL keeps the same append/query contract, requires tenant scope and verified identity at append, binds owner data into opaque cursors, validates record bounds on read, and rejects duplicate ids. Memory and JSONL remain development/reference adapters, not production WORM or cross-replica stores.
123
+
124
+ ```ts
125
+ const state = await createPostgresEnterpriseState({ pool, schema: "prism" });
126
+ await evaluateAndAppend(request, { store: state.policy, evaluator, id: crypto.randomUUID() });
127
+ ```
128
+
129
+ `state.close()` leaves a caller-owned pool open. Run `state.cleanup(...)` from an authorized host schedule only when expiration cleanup is needed; it does not run in the background.
130
+
120
131
  ## Related APIs
121
132
 
122
133
  - [Model routing](model-routing.md)
@@ -125,4 +136,5 @@ Policy is optional. Hosts wire `record*` helpers or `evaluateAndAppend` at permi
125
136
  - [Runs and usage ledger](runs-and-usage.md)
126
137
  - [Workflows](workflows.md): proactive schedule capability enable/revoke events bridge here via `onCapability`.
127
138
  - [Host security](host-security.md)
139
+ - [Enterprise PostgreSQL state](enterprise-postgres-state.md): durable policy/evaluation/work/router composition.
128
140
  - Package README: [`@arnilo/prism-policy`](../packages/policy/README.md)