@arnilo/prism 0.0.3 → 0.0.4
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 +22 -0
- package/README.md +32 -20
- package/dist/agent-loops.d.ts +8 -1
- package/dist/agent-loops.js +57 -11
- package/dist/agents.js +70 -17
- package/dist/checkpoints.d.ts +11 -0
- package/dist/checkpoints.js +144 -0
- package/dist/compaction.js +9 -1
- package/dist/content.d.ts +102 -0
- package/dist/content.js +410 -0
- package/dist/contracts.d.ts +142 -2
- package/dist/event-multiplexer.d.ts +23 -0
- package/dist/event-multiplexer.js +136 -0
- package/dist/execution-policy.d.ts +28 -0
- package/dist/execution-policy.js +24 -0
- package/dist/index.d.ts +17 -5
- package/dist/index.js +11 -4
- package/dist/input.js +11 -1
- package/dist/leases.d.ts +8 -0
- package/dist/leases.js +111 -0
- package/dist/node/agent-definitions.js +3 -5
- package/dist/node/config.d.ts +1 -0
- package/dist/node/config.js +5 -3
- package/dist/node/contribution-discovery.js +5 -8
- package/dist/node/session-store-jsonl.js +8 -5
- package/dist/node/settings.js +2 -2
- package/dist/node/trust.js +2 -4
- package/dist/observability.d.ts +3 -0
- package/dist/observability.js +18 -0
- package/dist/providers/media.d.ts +42 -0
- package/dist/providers/media.js +116 -0
- package/dist/providers/openai-compatible.js +18 -119
- package/dist/providers/openai-primitives.d.ts +9 -0
- package/dist/providers/openai-primitives.js +129 -0
- package/dist/providers/transport.d.ts +40 -0
- package/dist/providers/transport.js +221 -0
- package/dist/redaction.js +40 -13
- package/dist/resources.d.ts +5 -0
- package/dist/resources.js +4 -0
- package/dist/structured-output.d.ts +11 -0
- package/dist/structured-output.js +59 -0
- package/dist/testing/persistence-schema.d.ts +102 -0
- package/dist/testing/persistence-schema.js +457 -0
- package/dist/testing/provider-conformance.js +10 -1
- package/dist/testing/run-ledger-conformance.d.ts +33 -0
- package/dist/testing/run-ledger-conformance.js +172 -0
- package/dist/testing/session-store-conformance.d.ts +16 -0
- package/dist/testing/session-store-conformance.js +73 -0
- package/dist/tools.d.ts +17 -0
- package/dist/tools.js +29 -2
- package/docs/agent-events.md +13 -4
- package/docs/agent-loops.md +10 -4
- package/docs/agent-session-runtime.md +1 -0
- package/docs/cli-rpc.md +3 -0
- package/docs/coding-agent-tools.md +41 -7
- package/docs/coding-security.md +84 -0
- package/docs/credential-storage.md +177 -0
- package/docs/credentials-and-redaction.md +2 -1
- package/docs/database-persistence.md +44 -2
- package/docs/host-security.md +15 -1
- package/docs/index.md +28 -12
- package/docs/input-and-prompt-assembly.md +6 -5
- package/docs/mcp-tools.md +139 -0
- package/docs/middleware-hooks.md +2 -0
- package/docs/migration.md +21 -28
- package/docs/model-registry.md +5 -3
- package/docs/multimodal-content.md +148 -0
- package/docs/observability.md +163 -0
- package/docs/performance.md +40 -1
- package/docs/persistence-credentials-multimodality-primitives.md +303 -0
- package/docs/postgres-persistence.md +141 -0
- package/docs/provider-conformance.md +17 -0
- package/docs/provider-layer.md +1 -1
- package/docs/provider-primitives.md +281 -0
- package/docs/providers/kimi.md +1 -0
- package/docs/providers/neuralwatt.md +1 -0
- package/docs/providers/openai-compatible.md +2 -1
- package/docs/providers/openai.md +8 -1
- package/docs/providers/opencode-go.md +1 -0
- package/docs/providers/openrouter.md +1 -0
- package/docs/providers/zai.md +1 -0
- package/docs/public-contracts.md +9 -2
- package/docs/release-and-install.md +209 -25
- package/docs/resource-loading.md +14 -4
- package/docs/review-coverage-2026-07-14.md +260 -0
- package/docs/run-ledger-conformance.md +96 -0
- package/docs/runs-and-usage.md +2 -0
- package/docs/session-store-conformance.md +16 -0
- package/docs/session-stores-and-branching.md +1 -0
- package/docs/settings-auth-trust-security.md +2 -1
- package/docs/sqlite-persistence.md +122 -0
- package/docs/structured-output.md +9 -0
- package/docs/tool-conformance.md +1 -0
- package/docs/tool-execution-primitives.md +374 -0
- package/docs/tools.md +39 -1
- package/docs/workflow-orchestration-primitives.md +565 -0
- package/docs/workflow-tui-primitives.md +5 -0
- package/docs/workflows.md +219 -0
- package/package.json +33 -5
package/docs/performance.md
CHANGED
|
@@ -7,6 +7,7 @@ This page states Prism runtime limits that keep slow consumers and long sessions
|
|
|
7
7
|
Current surfaces:
|
|
8
8
|
|
|
9
9
|
- `SubscribeOptions` for bounded live `AgentEvent` subscriber queues.
|
|
10
|
+
- Bounded provider transport primitives (`readSseEvents`, `readBoundedResponseText`) used by every first-party provider package — see [Provider primitives](provider-primitives.md).
|
|
10
11
|
- `SessionStore.readBranchPath(query)` for branch reads that avoid full-session scans.
|
|
11
12
|
- `ProductionPersistenceStore` cursor queries for entries, events, runs, tool calls, and usage.
|
|
12
13
|
- JSONL and memory stores documented as development/local adapters, not production multi-writer stores.
|
|
@@ -112,10 +113,48 @@ const store = {
|
|
|
112
113
|
|
|
113
114
|
- Overflow events contain only counts and policy, never message text, tool arguments, prompts, provider payloads, or credentials.
|
|
114
115
|
- Runtime event payloads can be large (`Message`, content deltas, tool results, summaries, artifact metadata). Size queues by events and keep payload size in mind.
|
|
116
|
+
- `toolConcurrency` on the single-shot loop bounds in-flight tool dispatches per provider turn to `min(toolConcurrency, calls.length)`. Independent slow tools can overlap; transcript appends remain ordered. Default `1` preserves sequential behavior.
|
|
117
|
+
- `read` image bounds: default `maxImageBytes` is 10 MB (`DEFAULT_MAX_IMAGE_BYTES`). Oversize images are rejected by `stat` before read when possible; hosts may supply `transformImage` for resize/re-encode without adding image-processing deps to the base package.
|
|
115
118
|
- Live subscriber queues are bounded by default. Durable replay belongs to host storage.
|
|
116
119
|
- `SessionStore.list(sessionId)` is a full-session read. It is fine for memory/JSONL development stores, but production adapters should use `readBranchPath` for provider context and branch views.
|
|
117
120
|
- The JSONL store rereads/parses the file for validation/list/get and serializes appends only within one process. It has no cross-process lock, pagination, migrations, tenant isolation, or retention.
|
|
118
121
|
- Recommended database indexes: session id, run id, parent id, branch leaf id, timestamps, tenant/account/user, event type, entry kind, `(run_id, sequence)` for event timelines, and `(run_id, recorded_at, id)` for usage. Allocate event `sequence` per run for stable timeline pagination.
|
|
122
|
+
- Provider SSE parsing defaults: 256 KiB per completed event, 512 KiB incomplete buffer, 64 KiB error response bodies. Override per call via `BoundedStreamLimits` on `@arnilo/prism/providers/transport`.
|
|
123
|
+
|
|
124
|
+
### Provider-phase benchmark snapshot (2026-07-14)
|
|
125
|
+
|
|
126
|
+
Node v24.18.0, Linux x86_64, AMD Ryzen 9 PRO 7940HS; local synthetic streams, no network or exporter I/O:
|
|
127
|
+
|
|
128
|
+
| Case | Result |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| `readSseEvents`, 16 MiB total as 4 KiB events | 380 MiB/s, +1.7 MiB end-of-run heap delta |
|
|
131
|
+
| 100 provider deltas with 1 ms transport delay, no telemetry | 107.97 ms median |
|
|
132
|
+
| Same run, disabled adapter attached | 107.25 ms median (-0.67%, noise) |
|
|
133
|
+
| Same run, enabled no-op exporter | 107.22 ms median (-0.70%, noise) |
|
|
134
|
+
|
|
135
|
+
Nine measured runs per telemetry mode after warm-up; table reports median. A zero-I/O burst of 5,000 deltas measured 1.06 ms without telemetry and 2.09 ms with the adapter: about 1 ms absolute adapter cost, but a large percentage against an unrealistically tiny baseline. No span is created for message deltas. Add subscriber-side event filtering only if measured high-frequency in-memory streams make that ceiling material.
|
|
136
|
+
|
|
137
|
+
Configured overflow behavior is enforced by `src/__tests__/provider-transport.test.ts` for event, incomplete-buffer, response-body, argument, and abort limits.
|
|
138
|
+
|
|
139
|
+
### 0.0.4 release audit snapshot (2026-07-14)
|
|
140
|
+
|
|
141
|
+
Node v24.18.0 on the same Linux x86_64 / Ryzen 9 PRO 7940HS host. Results are medians of 7-9 warm runs unless the row describes file/database appends. Synthetic operations use local memory/files only. These numbers are release ceilings and comparison points, not cross-machine guarantees.
|
|
142
|
+
|
|
143
|
+
| Surface | Workload | Result | 0.0.4 release threshold |
|
|
144
|
+
| --- | --- | --- | --- |
|
|
145
|
+
| Run ledger | One mock run, 500 text deltas, 510 total records | 1.19 ms with in-memory ledger vs 0.17 ms without; +1.02 ms absolute; event append max concurrency 1 | < 10 ms with zero-I/O adapter; event appends remain serialized |
|
|
146
|
+
| JSONL store | 500 sequential label appends, including fail-closed reread/validation | 141.10 ms; 3,544 appends/s | < 500 ms; development/single-process only |
|
|
147
|
+
| JSON Schema compile cache | 5,000 validations through one warm adapter | 4.97 ms; 0.99 µs/validation | < 25 µs/validation |
|
|
148
|
+
| JSON Schema cold compile | 100 new adapters + first validation | 249.89 ms; 2.50 ms/compile | Warm cache must remain at least 20x faster than cold compile |
|
|
149
|
+
| Parallel tools | Six independent 20 ms calls | concurrency 1: 121.12 ms; concurrency 2: 60.92 ms; 1.99x speedup | concurrency 2 < 75% of sequential; configured worker cap remains enforced |
|
|
150
|
+
| SQLite session store | 1,000 sequential transactional label appends | 31.84 ms; 31,405 appends/s | < 250 ms on local SSD/tmp storage |
|
|
151
|
+
| Secret redaction | 10,000 shallow objects containing one known secret | 4.79 ms; 2.09 million objects/s | < 25 ms |
|
|
152
|
+
| Credential KDF | Default scrypt + AES-256-GCM encryption | 48.09 ms median | 20-250 ms; security floor stays `N >= 16,384` |
|
|
153
|
+
| Workflow runner | Existing bounded 1,000-node DAG fixture | 27.68 ms in aggregate release gate | < 1 s; no rescan failure |
|
|
154
|
+
|
|
155
|
+
Provider SSE remained at the frozen 380 MiB/s / +1.7 MiB heap snapshot. Media and MCP retain 10 MB defaults and finite timeout/total-byte guards; their malicious-input and oversize fixtures pass. PostgreSQL latency remains environment-dependent and is gated by transactional conformance in CI rather than a hardware-specific wall-clock assertion.
|
|
156
|
+
|
|
157
|
+
The ledger percentage overhead is intentionally not a threshold: its no-ledger baseline is below 1 ms, making the percentage unstable while absolute added latency remains about 1 ms. JSONL's append path is intentionally O(n²) across repeated appends because it rereads for corruption/conflict checks; move production or high-volume workloads to SQLite/PostgreSQL rather than weakening validation.
|
|
119
158
|
|
|
120
159
|
## Related APIs
|
|
121
160
|
|
|
@@ -124,4 +163,4 @@ const store = {
|
|
|
124
163
|
- [Session stores](session-stores.md): `SessionStore.readBranchPath` and dev-vs-production branch reads.
|
|
125
164
|
- [Database persistence](database-persistence.md): cursor queries, reference schema, indexes, and event sequence guidance.
|
|
126
165
|
- [Runs and usage ledger](runs-and-usage.md): durable event, tool-call, and usage persistence.
|
|
127
|
-
- [
|
|
166
|
+
- [Provider primitives](provider-primitives.md): bounded SSE/error-body limits for first-party providers.
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Persistence, credentials, and multimodality primitives
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
This page freezes the Plan 056 Task 0 inventory for production persistence, credential storage, and multimodal content. It maps existing `@arnilo/prism` contracts and conformance helpers, documents JSONL/security boundaries, records capability gaps **C-005**, **C-010**, and **C-011**, and pins package dependency choices for Tasks 1–7.
|
|
6
|
+
|
|
7
|
+
Implementation is **shipped and phase-verified** (Tasks 0–7). Optional packages cover SQLite/PostgreSQL persistence and Node credential storage; core remains dependency-free.
|
|
8
|
+
|
|
9
|
+
## When to use it
|
|
10
|
+
|
|
11
|
+
- **Adapter authors** implementing SQLite/PostgreSQL `SessionStore` + `RunLedger` should start here, then follow [Database persistence](database-persistence.md) and [Session store conformance](session-store-conformance.md).
|
|
12
|
+
- **Host apps** wiring CLI/desktop credential persistence should use the credential seams and package matrix here before choosing `@arnilo/prism-credentials-node` backends.
|
|
13
|
+
- **Provider and core authors** extending multimodal input should use the content/resource/capability designs here instead of embedding provider upload IDs in core contracts.
|
|
14
|
+
- **Security reviewers** use the threat model and conformance matrix on this page as the acceptance baseline for Plan 056 Tasks 1–7.
|
|
15
|
+
|
|
16
|
+
## Inventory (2026-07-14 baseline)
|
|
17
|
+
|
|
18
|
+
Static review of `src/contracts.ts`, `src/session-stores.ts`, `src/credentials.ts`, `src/input.ts`, `src/agents.ts`, `src/redaction.ts`, `src/node/session-store-jsonl.ts`, `src/testing/session-store-conformance.ts`, `examples/external-app-db-backed.ts`, and docs under `docs/session-stores*.md`, `docs/database-persistence.md`, `docs/runs-and-usage.md`, `docs/credentials-and-redaction.md`, `docs/settings-auth-trust-security.md`, `docs/input-and-prompt-assembly.md`, `docs/resource-loading.md`, `docs/model-registry.md`, `docs/provider-conformance.md`.
|
|
19
|
+
|
|
20
|
+
### Session store and branch invariants (shipped)
|
|
21
|
+
|
|
22
|
+
| Surface | Location | Behavior today |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| `SessionStore` | `src/contracts.ts` | `append`, `list`, optional `get`, optional `readBranchPath` |
|
|
25
|
+
| `SessionAppendOptions` | `src/contracts.ts` | `expectedParentId` existence validation; opaque `idempotencyKey` for same-position retry dedup |
|
|
26
|
+
| `SessionAppendConflictError` | `src/contracts.ts` | Stable `code: "session_append_conflict"`; `isSessionAppendConflict()` |
|
|
27
|
+
| Branch helpers | `src/session-stores.ts` | `getSessionBranchEntries`, `listSessionBranches`, `rebuildSessionContext`; reader overload follows `nextCursor` (max 64 pages) |
|
|
28
|
+
| In-memory store | `src/session-stores.ts` | `createMemorySessionStore()` — O(1) dup/idempotency/parent maps; not durable |
|
|
29
|
+
| JSONL store | `src/node/session-store-jsonl.ts` | Single-process dev adapter; per-line quarantine; in-memory idempotency only |
|
|
30
|
+
| Conformance | `src/testing/session-store-conformance.ts` | `assertSessionStoreConforms(store, { exerciseReadBranchPath? })` |
|
|
31
|
+
| Reference adapter | `examples/external-app-db-backed.ts` | In-memory tables implementing `SessionStore` + `RunLedger` + `ProductionPersistenceStore` |
|
|
32
|
+
|
|
33
|
+
**Frozen branch semantics:** `expectedParentId` is parent **existence** validation, not tip-CAS. Two children of the same parent are allowed (fork/branch). Production adapters may add stricter tip compare-and-swap and populate `currentLeafId` in conflicts.
|
|
34
|
+
|
|
35
|
+
**Frozen idempotency semantics:** Dedup key is `(session_id, expected_parent_id, idempotency_key)`. A run-level key may appear on multiple linear appends as the leaf advances; only exact same-position retries collapse.
|
|
36
|
+
|
|
37
|
+
### Run ledger (shipped)
|
|
38
|
+
|
|
39
|
+
| Surface | Location | Behavior today |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| `RunLedger` | `src/contracts.ts` | `appendRun`, `appendEvent`, `appendToolCall`, `appendUsage` |
|
|
42
|
+
| Runtime wiring | `src/agents.ts` | Active ledger from `RunOptions.runLedger ?? AgentConfig.runLedger`; ownership/idempotency from run options |
|
|
43
|
+
| Serialization | `src/agents.ts` | `ledgerChain` serializes event ledger appends (R-004 backpressure fix) |
|
|
44
|
+
| Redaction | `src/redaction.ts` | `redactRunLedgerRecord()` before every ledger write when `SecretRedactor` active |
|
|
45
|
+
| Conformance helper | `src/testing/session-store-conformance.ts` | `assertSessionStoreConforms`, `runSessionStoreConformance` |
|
|
46
|
+
| Run ledger conformance | `src/testing/run-ledger-conformance.ts` | `assertRunLedgerConforms`, `runRunLedgerConformance` |
|
|
47
|
+
| Shared schema model | `src/testing/persistence-schema.ts` | `createPersistenceSchemaModel`, `createPersistenceMigrationContract`, pagination/tenant fixtures |
|
|
48
|
+
|
|
49
|
+
### Production persistence contracts (shipped)
|
|
50
|
+
|
|
51
|
+
| Surface | Location | Behavior today |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| `ProductionPersistenceStore` | `src/contracts.ts` | Cursor `query*` methods for sessions, branches, entries, runs, events, tool calls, usage, agent definitions, retention, migrations; optional `readBranchPath` |
|
|
54
|
+
| `OwnershipScope` | `src/contracts.ts` | `tenantId`, `accountId`, `userId` on records and queries — enforcement host-owned |
|
|
55
|
+
| Reference schema | `docs/database-persistence.md` | Relational table/index reference, conditional append transaction pattern, NoSQL mapping notes |
|
|
56
|
+
| Type compile test | `src/__tests__/persistence-contracts.types.test.ts` | Host adapter shapes compile without DB deps |
|
|
57
|
+
|
|
58
|
+
**Core boundary:** No ORM, SQL driver, connection pool, migration runner, or filesystem persistence in `@arnilo/prism`.
|
|
59
|
+
|
|
60
|
+
### Credential and OAuth seams (shipped — storage gap)
|
|
61
|
+
|
|
62
|
+
| Surface | Location | Behavior today |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| `CredentialResolver` / `CredentialRequest` | `src/contracts.ts` | Host-implemented resolve at request edge |
|
|
65
|
+
| `resolveCredentialValue` | `src/credentials.ts` | String, callback, or resolver |
|
|
66
|
+
| `createExplicitCredentialResolver` | `src/credentials.ts` | Named source order (runtime → stored → env → fallback) |
|
|
67
|
+
| `createEnvCredentialResolver` | `src/credentials.ts` | Caller-supplied env object only; never reads `process.env` |
|
|
68
|
+
| `createMemoryCredentialStore` | `src/credentials.ts` | In-memory resolver + `set`/`delete`; not durable |
|
|
69
|
+
| `createChainedCredentialResolver` | `src/credentials.ts` | First match wins |
|
|
70
|
+
| `refreshOAuthCredential` | `src/credentials.ts` | Calls `OAuthProvider.refresh`; optional `OAuthCredentialStore.set` |
|
|
71
|
+
| `OAuthCredentialStore` | `src/contracts.ts` | `set(provider, credentials)` only — no `get`/`delete` in core contract |
|
|
72
|
+
| `OAuthProvider` / `OAuthCredentials` | `src/contracts.ts` | `login`, optional `refresh`, optional `getCredential` |
|
|
73
|
+
| Device-code OAuth | `packages/provider-openai` | Bounded polling; abort via `OAuthLoginCallbacks.signal` |
|
|
74
|
+
| Redaction | `src/redaction.ts` | Exact known-secret replacement; not secret detection |
|
|
75
|
+
|
|
76
|
+
**Gaps (C-011):** No encrypted file store, no system keychain adapter, no versioned credential envelope, no `OAuthCredentialStore` `get`/`delete`/`list` in core (Task 4 package may extend store interface locally while integrating `refreshOAuthCredential`).
|
|
77
|
+
|
|
78
|
+
### Content, resources, and model capabilities (shipped — modality gap)
|
|
79
|
+
|
|
80
|
+
| Surface | Location | Behavior today |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| `ContentBlock` union | `src/contracts.ts` | `text`, `image`, `audio`, `file`, `document`, `thinking`, `tool_call_delta`, `tool_call`, `tool_result` |
|
|
83
|
+
| `AudioContent` / `FileContent` / `DocumentContent` | `src/content.ts` | MIME, name, `data`/`url`/`resourceUri`, optional transcript/metadata |
|
|
84
|
+
| `ImageContent` | `src/contracts.ts` | `mimeType?`, `data?`, `url?`, `resourceUri?`, `name?` |
|
|
85
|
+
| `InputAttachment` | `src/input.ts` | `text`, inline `content` blocks, or `uri` + `ResourceLoader` → text user message |
|
|
86
|
+
| `ResourceLoader` / `Resource` | `src/contracts.ts` | Host-owned `load(uri)`; optional `list`; `data`/`text`/`mediaType` |
|
|
87
|
+
| Resource helpers | `src/resources.ts` | `loadTextResource`, `loadJsonResource`, `loadManifestResource`, `loadBinaryResource` — decode/bounds only |
|
|
88
|
+
| Media helpers | `src/content.ts` | `resolveMediaContentBlock`, `assertSsrfAllowedUrl`, `assertMediaBlocksWithinBounds`, `UnsupportedModalityError` |
|
|
89
|
+
| `ModelCapabilities.input` | `src/contracts.ts` | Known tags: `text`, `image`, `audio`, `file`, `document`; `MODEL_INPUT_CAPABILITIES` export |
|
|
90
|
+
| Provider image mapping | first-party packages | OpenAI Responses, OpenRouter, OpenCode Go (Anthropic route), Kimi, NeuralWatt map `image` when capability allows |
|
|
91
|
+
| Provider audio/file/document mapping | first-party packages | OpenAI Responses maps `audio`/`file`/`document`; Anthropic routes map PDF `document`/`file`; others reject undeclared media |
|
|
92
|
+
| Provider conformance | `src/testing/provider-conformance.ts` | `assertSerializedRequestCoversContent` with per-provider `unsupported` list |
|
|
93
|
+
|
|
94
|
+
### JSONL and dev-store security boundaries (shipped)
|
|
95
|
+
|
|
96
|
+
| Boundary | Rule |
|
|
97
|
+
| --- | --- |
|
|
98
|
+
| Cross-process writes | JSONL has no lock; two processes on one file can race |
|
|
99
|
+
| Durable idempotency | JSONL idempotency is in-process only |
|
|
100
|
+
| Corrupt lines | Quarantined per line; do not poison whole file (R-005) |
|
|
101
|
+
| Schema/kind | Unknown kinds and future `schemaVersion` fail closed |
|
|
102
|
+
| Secrets in file | Host responsibility; runtime redacts before `append` when redactor configured |
|
|
103
|
+
| Production use | JSONL documented as dev-only; database adapters required for multi-writer |
|
|
104
|
+
|
|
105
|
+
## Package dependency and support matrix (pinned Task 0)
|
|
106
|
+
|
|
107
|
+
Prism `engines.node` is `>=20`. Optional packages stay package-local; core adds no new runtime dependencies.
|
|
108
|
+
|
|
109
|
+
| Package (planned) | Driver / backend | Pinned version | Node support | Rationale |
|
|
110
|
+
| --- | --- | --- | --- | --- |
|
|
111
|
+
| `@arnilo/prism-session-store-sqlite` | `better-sqlite3` | `^12.11.1` | 20.x–26.x per upstream engines | Synchronous API, WAL/busy_timeout, mature Node 20 baseline; `node:sqlite` rejected — requires Node ≥22.5 and is still experimental vs declared `>=20` baseline |
|
|
112
|
+
| `@arnilo/prism-session-store-postgres` | `pg` | `^8.22.0` | ≥16 (satisfies 20+) | Standard pool + parameterized queries; TLS/credentials host-owned |
|
|
113
|
+
| `@arnilo/prism-credentials-node` encrypted file | Node `crypto` (AES-256-GCM + scrypt) | built-in | 20+ | No extra deps for AEAD/KDF; atomic rename writes |
|
|
114
|
+
| `@arnilo/prism-credentials-node` keychain | `@napi-rs/keyring` | `^1.3.0` | ≥10 (satisfies 20+) | Cross-platform, actively maintained (2026); `keytar@7.9.0` rejected — last release 2022, heavier native rebuild friction |
|
|
115
|
+
|
|
116
|
+
**Rejected options:**
|
|
117
|
+
|
|
118
|
+
| Option | Why rejected |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| Database/ORM in core | Violates dependency-light core; hosts choose SQL dialect |
|
|
121
|
+
| `node:sqlite` at current baseline | Incompatible with Node 20 floor unless release plan raises engine |
|
|
122
|
+
| Generic SQL layer shared by SQLite + Postgres | Dialect/driver differences outweigh shared SQL abstraction |
|
|
123
|
+
| Core global credential store | Violates host ownership |
|
|
124
|
+
| Keychain silent fallback to plaintext file | Fail closed; explicit backend selection only |
|
|
125
|
+
| Provider upload IDs in `ContentBlock` | Provider-local mapping over generic references (Task 5–6) |
|
|
126
|
+
|
|
127
|
+
## ADR decision table (frozen for Tasks 1–7)
|
|
128
|
+
|
|
129
|
+
| Concern | Option A | Option B | **Chosen** | Rationale |
|
|
130
|
+
| --- | --- | --- | --- | --- |
|
|
131
|
+
| Persistence location | Core built-in DB | Optional packages over contracts | **Optional packages** | Matches Plan 053 JSONL boundary and `ProductionPersistenceStore` extension point |
|
|
132
|
+
| Schema/migrations | Core DDL generator | Shared fixture model + dialect-local SQL | **Shared fixtures + local SQL** | Two adapters without ORM |
|
|
133
|
+
| Run ledger conformance | Per-package tests only | Shared conformance module (Task 1) | **Shared module** | Parity with session-store conformance |
|
|
134
|
+
| Credential persistence | Core global store | `@arnilo/prism-credentials-node` | **Optional package** | Host selects file vs keychain |
|
|
135
|
+
| KDF | PBKDF2 default | scrypt with documented minimums | **scrypt** (configurable N/r/p) | Node built-in; calibrate in Task 4 tests |
|
|
136
|
+
| Multimodal content | Provider-specific options only | Generic `ContentBlock` + capability tags | **Generic blocks + capabilities** | Portable input; provider maps/uploads locally |
|
|
137
|
+
| URL/file sources | Loader reads anything | Bounded loader policy + trust integration | **Bounded + trust** | Reuse `createPathTrustPolicy` patterns; SSRF deny-by-default for URLs |
|
|
138
|
+
| Unsupported modality | Silent drop | Capability check before network | **Reject before provider call** | `UnsupportedModalityError` or equivalent host-visible error |
|
|
139
|
+
|
|
140
|
+
## Planned generic APIs (Tasks 1–6)
|
|
141
|
+
|
|
142
|
+
### Task 1 — Shared schema/conformance primitives
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
// Extend @arnilo/prism/testing exports (names illustrative)
|
|
146
|
+
export async function assertSessionStoreConforms(store, options?): Promise<void>;
|
|
147
|
+
export async function assertRunLedgerConforms(factory, options?): Promise<void>;
|
|
148
|
+
export interface PersistenceSchemaModel { /* versioned tables, tenant columns, idempotency side table */ }
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Task 2–3 — Database adapters
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const store: SessionStore = createPostgresSessionStore({ pool, schema: "prism" });
|
|
155
|
+
const persistence = createSqlitePersistence({ filename: "./prism.db" });
|
|
156
|
+
await persistence.close();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Transaction boundary: **one transaction per `SessionStore.append`** covering idempotency insert, parent check, entry insert, optional branch leaf update.
|
|
160
|
+
|
|
161
|
+
### Task 4 — Credential package
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
const store = createEncryptedCredentialStore({ path, getPassphrase });
|
|
165
|
+
const keychain = createKeychainCredentialStore({ service: "prism" });
|
|
166
|
+
const resolver = createStoredCredentialResolver(store);
|
|
167
|
+
await refreshOAuthCredential({ provider, credentials, store });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Versioned envelope: `{ version, kdf, cipher, salt, nonce, ciphertext }` with authenticated encryption; file mode `0600` on Unix; wrong passphrase fails closed.
|
|
171
|
+
|
|
172
|
+
### Task 5 — Content contracts
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
{ type: "file", mediaType: "application/pdf", name: "report.pdf", data: bytes }
|
|
176
|
+
{ type: "audio", mediaType: "audio/wav", data: bytes, durationMs?: number }
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`ResourceLoader` resolves `bytes`/`url`/`resource:` references under configurable global and per-item limits. `ModelCapabilities.input` gains `audio`, `file`, `document` tags when models support them.
|
|
180
|
+
|
|
181
|
+
### Task 6 — Provider mapping
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
if (!model.capabilities?.input?.includes(block.type)) {
|
|
185
|
+
throw new UnsupportedModalityError(block.type);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Upload-backed providers manage create/use/delete lifecycle in package-local code; conformance asserts capabilities match behavior.
|
|
190
|
+
|
|
191
|
+
## Conformance and threat-model matrix
|
|
192
|
+
|
|
193
|
+
Tasks 1–7 must pass this matrix (unit/integration tests + existing `assertSessionStoreConforms` / provider conformance extensions):
|
|
194
|
+
|
|
195
|
+
| # | Scenario | Expected behavior |
|
|
196
|
+
| ---: | --- | --- |
|
|
197
|
+
| 1 | Process restart after successful append | Entry durable; idempotency row survives |
|
|
198
|
+
| 2 | Concurrent append same parent | One succeeds; other conflicts or serializes per adapter policy |
|
|
199
|
+
| 3 | Exact idempotency retry | `SessionAppendConflictError` with `idempotencyDuplicate: true`; no duplicate row |
|
|
200
|
+
| 4 | Distinct linear appends same run key | Both entries persist |
|
|
201
|
+
| 5 | Tenant A query with tenant B id | Empty or denied; never cross-tenant rows |
|
|
202
|
+
| 6 | SQL injection in session id / idempotency key | Parameterized queries only; no string interpolation of values |
|
|
203
|
+
| 7 | Wrong encryption passphrase | Decrypt fails closed; no partial plaintext |
|
|
204
|
+
| 8 | Tampered ciphertext / truncated file | AEAD verification fails |
|
|
205
|
+
| 9 | Key rotation / rewrap | Old credentials readable during migration window; new writes use new key |
|
|
206
|
+
| 10 | Keychain unavailable / timeout | Explicit locked/unavailable error; no silent plaintext fallback |
|
|
207
|
+
| 11 | URL fetch to RFC1918 / metadata IP | Denied by SSRF policy before fetch |
|
|
208
|
+
| 12 | Local path outside trust root | `createPathTrustPolicy` denies before read |
|
|
209
|
+
| 13 | MIME spoof (extension vs magic) | Reject or re-label per policy; no trust extension alone |
|
|
210
|
+
| 14 | Media bomb (zip/gzip/pdf) | Byte/time ceilings; streaming handles closed |
|
|
211
|
+
| 15 | Unsupported provider modality | Error before provider HTTP; never silent drop |
|
|
212
|
+
| 16 | Provider upload lifecycle | Temp remote IDs cleaned up on run end/abort within retention bound |
|
|
213
|
+
| 17 | Ledger + store redaction | No raw secrets in durable rows after `redactRunLedgerRecord` / `redactSessionEntry` |
|
|
214
|
+
| 18 | PostgreSQL migration race | Advisory lock or equivalent; one winner applies migrations |
|
|
215
|
+
|
|
216
|
+
### Threat model summary
|
|
217
|
+
|
|
218
|
+
| Threat | Owner | Mitigation |
|
|
219
|
+
| --- | --- | --- |
|
|
220
|
+
| SQL injection | Adapter package | Parameterized statements; validated/quoted identifiers for schema names |
|
|
221
|
+
| Tenant isolation bypass | Host + adapter | `tenantId` in unique/FK boundaries; integration tests per tenant |
|
|
222
|
+
| World-readable credential file | Credential package | `0600` perms, umask guidance, no plaintext temp files |
|
|
223
|
+
| Weak KDF parameters | Credential package | Documented minimum scrypt work factor; reject weak config |
|
|
224
|
+
| Keychain trust / spoof UI | Host OS | Document service/name namespacing; no auto-trust |
|
|
225
|
+
| SSRF via content URL | Host loader policy | Deny private/link-local/metadata ranges; allow-list option |
|
|
226
|
+
| Path traversal via file block | Host trust policy | Realpath containment before read |
|
|
227
|
+
| Media decompression bomb | Core limits + loader | Byte caps, stat-first reject, timeouts |
|
|
228
|
+
| MIME spoofing | Loader validation | Magic-byte sniff + declared type cross-check |
|
|
229
|
+
| Secret retention in DB/JSONL | Host + runtime | Redact before append; audit scans in Task 7 |
|
|
230
|
+
| JSONL multi-writer corruption | Host deployment | Document dev-only boundary; use DB adapter in production |
|
|
231
|
+
| Unsupported modality data loss | Provider layer | Capability-gated reject before network |
|
|
232
|
+
|
|
233
|
+
## Performance notes
|
|
234
|
+
|
|
235
|
+
| Concern | Target / guidance |
|
|
236
|
+
| --- | --- |
|
|
237
|
+
| Branch read | `readBranchPath` single ancestor query; avoid `list(sessionId)` for production context |
|
|
238
|
+
| Pagination | Cursor on `(timestamp, id)` or `(run_id, sequence)`; no offset scans on long sessions |
|
|
239
|
+
| `SessionStore.append` | One entry per transaction; O(1) indexed parent/idempotency checks |
|
|
240
|
+
| Connection pool (Postgres) | Host-sized; default max ~10 per process documented in package README |
|
|
241
|
+
| SQLite WAL | Enable WAL; busy timeout ≥ 5s documented; single-writer still recommended |
|
|
242
|
+
| Ledger writes | Serialized per session run; batching inside adapter allowed if order preserved |
|
|
243
|
+
| scrypt KDF | Default N=2^15, r=8, p=1 (adjust in docs after Task 4 calibration); cap passphrase attempts |
|
|
244
|
+
| Keychain calls | Timeout/abort where backend supports; do not block run loop unbounded |
|
|
245
|
+
| Global media budget | Default 32 MiB total per request assembly (Task 5); per-item default 10 MiB (align with `DEFAULT_MAX_IMAGE_BYTES`) |
|
|
246
|
+
| Audio duration ceiling | Default 5 minutes decoded (Task 5) |
|
|
247
|
+
| Fetch timeout | Default 30s per URL resource (Task 5) |
|
|
248
|
+
| Upload cache | Provider-local; bounded entries per run; cleanup on `done`/`error`/`abort` |
|
|
249
|
+
|
|
250
|
+
## Implementation example
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
import {
|
|
254
|
+
createExplicitCredentialResolver,
|
|
255
|
+
createMemoryCredentialStore,
|
|
256
|
+
createMemorySessionStore,
|
|
257
|
+
} from "@arnilo/prism";
|
|
258
|
+
import { assertSessionStoreConforms } from "@arnilo/prism/testing/session-store-conformance";
|
|
259
|
+
|
|
260
|
+
// Today: memory + JSONL dev stores only.
|
|
261
|
+
const store = createMemorySessionStore();
|
|
262
|
+
|
|
263
|
+
// Task 2+:
|
|
264
|
+
// const store = createSqliteSessionStore({ filename: "./prism.db" });
|
|
265
|
+
|
|
266
|
+
await assertSessionStoreConforms(store, { exerciseReadBranchPath: true });
|
|
267
|
+
|
|
268
|
+
const resolver = createExplicitCredentialResolver([
|
|
269
|
+
{ name: "memory", resolver: createMemoryCredentialStore() },
|
|
270
|
+
]);
|
|
271
|
+
void resolver;
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Extension and configuration notes
|
|
275
|
+
|
|
276
|
+
- Hosts implement `SessionStore` and optional `RunLedger` / `ProductionPersistenceStore`; Prism runtime does not open databases or keychains.
|
|
277
|
+
- Credential backends are selected explicitly at app startup; no core singleton.
|
|
278
|
+
- Multimodal limits and SSRF/path policy are host-configurable; defaults are finite.
|
|
279
|
+
- Provider packages declare truthful `ModelCapabilities.input` and map or reject each block type.
|
|
280
|
+
- Live PostgreSQL tests remain opt-in for local runs (`PRISM_TEST_POSTGRES_URL` / `npm run test:postgres`) and are enforced in CI by the `postgres-integration` job; default `npm test` / `sdk:ready` stay network-free.
|
|
281
|
+
|
|
282
|
+
## Security and performance notes
|
|
283
|
+
|
|
284
|
+
See **Threat model summary** and **Performance notes** above. Cross-cutting rules:
|
|
285
|
+
|
|
286
|
+
- Never store `CredentialResolver`, provider instances, API keys, or unredacted secrets in session entries, ledger rows, or idempotency tables.
|
|
287
|
+
- Redact with `createSecretRedactor` / `redactRunLedgerRecord` / `redactSessionEntry` before durable writes.
|
|
288
|
+
- JSONL remains development-only; production multi-writer requires database adapters from Tasks 2–3.
|
|
289
|
+
|
|
290
|
+
## Related APIs
|
|
291
|
+
|
|
292
|
+
- [Database persistence](database-persistence.md): reference schema, indexes, conditional append pattern
|
|
293
|
+
- [Session stores](session-stores.md): runtime `SessionStore` contract and branch handles
|
|
294
|
+
- [Session store conformance](session-store-conformance.md): `assertSessionStoreConforms`
|
|
295
|
+
- [Runs and usage ledger](runs-and-usage.md): `RunLedger` write seam
|
|
296
|
+
- [Node JSONL session store](node-jsonl-session-store.md): dev-only file adapter boundaries
|
|
297
|
+
- [Credentials and redaction](credentials-and-redaction.md): core resolver/OAuth helpers
|
|
298
|
+
- [Security/auth/trust](settings-auth-trust-security.md): trust, permissions, memory credentials
|
|
299
|
+
- [Input and prompt assembly](input-and-prompt-assembly.md): attachments and input layout
|
|
300
|
+
- [Resource loading](resource-loading.md): `ResourceLoader` decode helpers
|
|
301
|
+
- [Model registry](model-registry.md): `ModelCapabilities` metadata
|
|
302
|
+
- [Provider conformance](provider-conformance.md): content preservation and secret leak checks
|
|
303
|
+
- [Review coverage (2026-07-14)](review-coverage-2026-07-14.md): traceability for C-005, C-010, C-011
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# PostgreSQL persistence
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
The optional `@arnilo/prism-session-store-postgres` package ships a production-oriented PostgreSQL adapter that implements:
|
|
6
|
+
|
|
7
|
+
- `SessionStore` — atomic `append` / `list` / `get` / `readBranchPath`
|
|
8
|
+
- `RunLedger` — durable run, event, tool-call, and usage rows
|
|
9
|
+
- `ProductionPersistenceStore` — cursor-paginated `query*` reads plus generic `checkpoints` and atomic `leases` capabilities
|
|
10
|
+
|
|
11
|
+
Factory:
|
|
12
|
+
|
|
13
|
+
- `createPostgresPersistence(options)` (async)
|
|
14
|
+
- `PostgresPersistenceOptions`
|
|
15
|
+
- `PostgresPersistence.close()` (async; ends adapter-owned pools only)
|
|
16
|
+
|
|
17
|
+
The adapter uses `pg@^8.22.0`, applies versioned migrations from the shared Plan 056 schema model inside a transaction guarded by `pg_advisory_xact_lock`, validates/quotes schema identifiers, and passes the full session-store and run-ledger conformance suites when `PRISM_TEST_POSTGRES_URL` is set.
|
|
18
|
+
|
|
19
|
+
## When to use it
|
|
20
|
+
|
|
21
|
+
Use this package when you need server-backed persistence with pooled connections and multi-writer concurrency:
|
|
22
|
+
|
|
23
|
+
- hosted SaaS agents with PostgreSQL as the system of record
|
|
24
|
+
- managed cloud databases (RDS, Cloud SQL, Neon, Supabase, etc.)
|
|
25
|
+
- CI integration tests against a real PostgreSQL service
|
|
26
|
+
|
|
27
|
+
Prefer [`@arnilo/prism-session-store-sqlite`](sqlite-persistence.md) for local CLI tools, single-writer workloads, and network-free default tests.
|
|
28
|
+
|
|
29
|
+
## Inputs / request
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { Pool } from "pg";
|
|
33
|
+
import { createPostgresPersistence } from "@arnilo/prism-session-store-postgres";
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Field | Type | Purpose |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `pool` | `Pool` | Existing `pg` pool (caller owns lifecycle). |
|
|
39
|
+
| `connectionString` | `string` | Create an adapter-owned bounded pool when `pool` is omitted. |
|
|
40
|
+
| `schema` | `string` | PostgreSQL schema for Prism tables. Defaults to `"prism"`. Validated and double-quoted. |
|
|
41
|
+
| `poolMax` | `number` | Maximum pool size for adapter-owned pools. Defaults to `10`. |
|
|
42
|
+
| `poolConfig` | `PoolConfig` | Additional `pg` options (TLS, idle timeout, application name, etc.). |
|
|
43
|
+
|
|
44
|
+
Hosts own TLS (`ssl` in `poolConfig`), credentials, connection limits, and backup/retention enforcement.
|
|
45
|
+
|
|
46
|
+
## Outputs / response / events
|
|
47
|
+
|
|
48
|
+
`createPostgresPersistence()` returns one object implementing the three persistence contracts plus generic checkpoints and leases:
|
|
49
|
+
|
|
50
|
+
| Method group | Behavior |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `SessionStore.append` | One transaction per append: parent existence check, idempotency dedup, duplicate-id rejection, entry insert. |
|
|
53
|
+
| `SessionStore.list` / `get` | Indexed reads by `session_id` and primary key. |
|
|
54
|
+
| `SessionStore.readBranchPath` | Recursive ancestor query from `leafId` (or latest leaf) in root→leaf order. |
|
|
55
|
+
| `RunLedger.append*` | Inserts run/event/tool/usage rows; events receive monotonic per-run `sequence` values. |
|
|
56
|
+
| `ProductionPersistenceStore.query*` | Parameterized cursor pagination on indexed columns with tenant/account/user filters. |
|
|
57
|
+
| `checkpoints` | Generic versioned `CheckpointStore` backed by `prism_checkpoints`; ownership, CAS/fencing checks, and bounded pagination. |
|
|
58
|
+
| `leases` | Atomic `LeaseStore` backed by `prism_leases`; database-clock expiry, opaque renew/release token, monotonic takeover fence. |
|
|
59
|
+
| `close()` | Ends the pool when the adapter created it from `connectionString`. |
|
|
60
|
+
|
|
61
|
+
Migrations run automatically on open and are idempotent across reopen. Concurrent setup uses per-schema advisory transaction locks.
|
|
62
|
+
|
|
63
|
+
## Request/response example
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"connectionString": "postgres://app:***@db.example.com:5432/prism",
|
|
68
|
+
"schema": "prism",
|
|
69
|
+
"poolMax": 10,
|
|
70
|
+
"poolConfig": {
|
|
71
|
+
"ssl": { "rejectUnauthorized": true }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Implementation example
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { Pool } from "pg";
|
|
80
|
+
import { createAgentSession } from "@arnilo/prism";
|
|
81
|
+
import { createPostgresPersistence } from "@arnilo/prism-session-store-postgres";
|
|
82
|
+
import { runSessionStoreConformance } from "@arnilo/prism/testing/session-store-conformance";
|
|
83
|
+
|
|
84
|
+
const pool = new Pool({
|
|
85
|
+
connectionString: process.env.DATABASE_URL,
|
|
86
|
+
max: 10,
|
|
87
|
+
ssl: { rejectUnauthorized: true },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const persistence = await createPostgresPersistence({ pool, schema: "prism" });
|
|
91
|
+
|
|
92
|
+
await runSessionStoreConformance(
|
|
93
|
+
async () => createPostgresPersistence({ pool, schema: "prism" }),
|
|
94
|
+
{ exerciseReadBranchPath: true, exerciseReopen: true },
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const session = createAgentSession({
|
|
98
|
+
sessionStore: persistence,
|
|
99
|
+
runLedger: persistence,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await session.run("hello");
|
|
103
|
+
await persistence.close();
|
|
104
|
+
await pool.end();
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Live conformance and integration tests:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres --workspace @arnilo/prism-session-store-postgres
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Extension and configuration notes
|
|
114
|
+
|
|
115
|
+
- The package is optional and workspace-local; `@arnilo/prism` core has no PostgreSQL dependency.
|
|
116
|
+
- Schema names must match `^[a-zA-Z_][a-zA-Z0-9_]*$`; the adapter quotes them and never interpolates user values into identifier positions.
|
|
117
|
+
- `SessionAppendOptions` idempotency rows are durable in `prism_session_append_idempotency` and survive reopen.
|
|
118
|
+
- Schema version **1** (`001_init`) matches `@arnilo/prism/testing/persistence-schema` — SQLite adapters share the same model with dialect-local DDL.
|
|
119
|
+
- Pass an existing `pg` `Pool` when your host already manages pooling, TLS, and credential rotation.
|
|
120
|
+
|
|
121
|
+
## Security and performance notes
|
|
122
|
+
|
|
123
|
+
- **Parameterized SQL only.** Session ids, idempotency keys, tenant ids, and JSON payloads are bound parameters (`$1`, `$2`, …).
|
|
124
|
+
- **Identifier validation.** Configurable `schema` is validated and double-quoted; table names are fixed constants in adapter SQL.
|
|
125
|
+
- **TLS and credentials.** Configure via `pg` `Pool` / `PoolConfig`; the adapter does not read environment variables unless the host passes them into `connectionString` or `poolConfig`.
|
|
126
|
+
- **Redaction upstream.** Event and tool-call payloads may contain secrets; redact before ledger writes. The adapter does not scan or rewrite row contents.
|
|
127
|
+
- **Bounded pool.** Adapter-owned pools default to `max: 10`. Hosts with heavy concurrency should supply their own pool sizing.
|
|
128
|
+
- **Indexed operations.** Append, parent validation, idempotency dedup, branch reads, and pagination use the indexes documented in [Database persistence](database-persistence.md); normal paths avoid sequential scans.
|
|
129
|
+
- **Migration locking.** `pg_advisory_xact_lock` prevents concurrent `001_init` races when multiple processes open the adapter at once.
|
|
130
|
+
- **Tenant isolation.** `tenant_id` / `account_id` / `user_id` columns participate in query filters; hosts must still scope writes correctly.
|
|
131
|
+
- **Benchmark target.** Indexed append + paginated branch read on a warm pool should stay under **50 ms p95** for local/CI-sized datasets (≤100k entries per session); measure with your pool size and hardware before production sizing.
|
|
132
|
+
|
|
133
|
+
## Related APIs
|
|
134
|
+
|
|
135
|
+
- [Database persistence](database-persistence.md): shared schema model, conditional append pattern, indexes.
|
|
136
|
+
- [SQLite persistence](sqlite-persistence.md): file-backed alternative for local/single-writer hosts.
|
|
137
|
+
- [Session store conformance](session-store-conformance.md): `assertSessionStoreConforms` / `runSessionStoreConformance`.
|
|
138
|
+
- [Run ledger conformance](run-ledger-conformance.md): `assertRunLedgerConforms` / `runRunLedgerConformance`.
|
|
139
|
+
- [Persistence, credentials, and multimodality primitives](persistence-credentials-multimodality-primitives.md): package matrix and threat model.
|
|
140
|
+
- [Workflows](workflows.md): adapt `persistence.checkpoints` and pass `persistence.leases` to `createWorkflowCoordinator()` for durable multi-process workflow execution.
|
|
141
|
+
- [Migration guide](migration.md): moving from JSONL/in-memory to database-backed persistence.
|
|
@@ -93,6 +93,22 @@ const body = JSON.parse(String(fetchInit.body));
|
|
|
93
93
|
assertSerializedRequestCoversContent(request, body, { unsupported: ["image"] });
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
Multimodal coverage example:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const request = {
|
|
100
|
+
model: { provider: "openai", model: "gpt-5.1", capabilities: { input: ["text", "file", "document", "audio"] } },
|
|
101
|
+
messages: [{ role: "user", content: [
|
|
102
|
+
{ type: "file", mediaType: "application/pdf", name: "report.pdf", data: "..." },
|
|
103
|
+
{ type: "audio", mediaType: "audio/wav", data: "..." },
|
|
104
|
+
] }],
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
assertSerializedRequestCoversContent(request, body);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Pass `unsupported` only for modalities the provider deliberately omits from the wire format while still accepting the turn via another block type.
|
|
111
|
+
|
|
96
112
|
Protected header-ownership example:
|
|
97
113
|
|
|
98
114
|
```ts
|
|
@@ -127,6 +143,7 @@ The helpers are a testing subpath only. Provider packages can use them with thei
|
|
|
127
143
|
- Use fake credentials only in fixtures.
|
|
128
144
|
- The helpers collect one stream into memory; keep conformance fixtures small.
|
|
129
145
|
- Redaction remains the provider/runtime boundary's job. Use `assertNoSecretLeak()` with known fake secrets to catch regressions, not as a general secret scanner.
|
|
146
|
+
- First-party providers read SSE streams and HTTP error bodies through bounded helpers from `@arnilo/prism/providers/transport` (`readSseEvents` / `readSseData`, `readBoundedResponseText`). Oversized remote input terminates with `ProviderTransportError` instead of unbounded buffering.
|
|
130
147
|
|
|
131
148
|
## Related APIs
|
|
132
149
|
|
package/docs/provider-layer.md
CHANGED
|
@@ -129,7 +129,7 @@ const agent = createAgent({ model: { provider: own.id, model: "demo" }, provider
|
|
|
129
129
|
- Provider event helpers return plain `ProviderEvent` objects.
|
|
130
130
|
- `providerError()` converts unknown errors to redacted `ErrorInfo` through `errorToErrorInfo()` and preserves safe string/number `code` fields for retry classification.
|
|
131
131
|
- `createMockProvider()` returns an `AIProvider` whose `generate()` yields the scripted events in order and checks `request.signal?.aborted` before each event.
|
|
132
|
-
- The agent/session runtime passes its per-run abort signal as `ProviderRequest.signal`. `ProviderRequestOptions.timeoutMs`, `maxRetries`, and `maxRetryDelayMs` are deprecated inert hints in first-party providers; use `RunOptions.signal`/host abort controllers for timeouts and `AgentConfig.retry`/`RunOptions.retry` for retry.
|
|
132
|
+
- The agent/session runtime passes its per-run abort signal as `ProviderRequest.signal`. `ProviderRequestOptions.structuredOutput` requests provider-native JSON-schema output when the model declares `capabilities.structuredOutput`; unsupported models fail before fetch. `ProviderRequestOptions.timeoutMs`, `maxRetries`, and `maxRetryDelayMs` are deprecated inert hints in first-party providers; use `RunOptions.signal`/host abort controllers for timeouts and `AgentConfig.retry`/`RunOptions.retry` for retry.
|
|
133
133
|
|
|
134
134
|
## Request/response example
|
|
135
135
|
|