@arnilo/prism 0.0.2 → 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 +37 -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 +242 -0
- 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 -11
- 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 -23
- 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 +40 -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 +34 -5
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Observability
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
Prism exposes provider and tool timing through stable, metadata-only `AgentEvent` variants. Hosts subscribe via `session.subscribe()` or persist events through `RunLedger`. Core helpers build `ProviderTurnMetadata` and classify HTTP failures without echoing prompts, tool arguments, or credentials.
|
|
6
|
+
|
|
7
|
+
Optional package `@arnilo/prism-observability-opentelemetry` maps those events to OpenTelemetry spans and low-cardinality metrics. OpenTelemetry is **not** a dependency of `@arnilo/prism`.
|
|
8
|
+
|
|
9
|
+
APIs:
|
|
10
|
+
|
|
11
|
+
- `ProviderTurnMetadata`, `ToolExecutionMetadata` on `AgentEvent`
|
|
12
|
+
- `createProviderTurnMetadata()`, `readProviderHttpStatus()` in `@arnilo/prism`
|
|
13
|
+
- `createOpenTelemetryInstrumentation()`, `wrapOpenTelemetryApi()`, `createInMemoryTelemetry()` in `@arnilo/prism-observability-opentelemetry`
|
|
14
|
+
|
|
15
|
+
## When to use it
|
|
16
|
+
|
|
17
|
+
Use agent events when you need run-scoped latency, retry attempt numbers, token/cache usage, tool duration, or error classification in-process or through your own exporter.
|
|
18
|
+
|
|
19
|
+
Use the OpenTelemetry adapter when you already run the OpenTelemetry SDK and want spans/metrics without forking the runtime.
|
|
20
|
+
|
|
21
|
+
Do not parse raw provider SSE for timing — provider packages normalize stream events; the session emits `provider_turn_*` once per `generate()` attempt.
|
|
22
|
+
|
|
23
|
+
## Inputs / request
|
|
24
|
+
|
|
25
|
+
Core metadata helpers:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createProviderTurnMetadata, readProviderHttpStatus } from "@arnilo/prism";
|
|
29
|
+
|
|
30
|
+
const metadata = createProviderTurnMetadata(request, providerId, { attempt: 2, latencyMs: 120 });
|
|
31
|
+
const httpStatus = readProviderHttpStatus(errorInfo);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
New agent event variants (metadata only):
|
|
35
|
+
|
|
36
|
+
| Variant | When | Key fields |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `provider_turn_started` | Before each provider `generate()` attempt | `turn`, `metadata: ProviderTurnMetadata` |
|
|
39
|
+
| `provider_turn_finished` | After success or failure of that attempt | `metadata` (includes `latencyMs`, optional `httpStatus`), `usage?`, `error?` |
|
|
40
|
+
|
|
41
|
+
`ToolExecutionMetadata` on terminal tool events:
|
|
42
|
+
|
|
43
|
+
| Field | Meaning |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `durationMs` | Wall time from dispatch start to finish/block/error |
|
|
46
|
+
| `status` | `finished` \| `error` \| `blocked` |
|
|
47
|
+
|
|
48
|
+
OpenTelemetry adapter:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { trace, metrics } from "@opentelemetry/api";
|
|
52
|
+
import { createOpenTelemetryInstrumentation, wrapOpenTelemetryApi } from "@arnilo/prism-observability-opentelemetry";
|
|
53
|
+
|
|
54
|
+
const { tracer, meter } = wrapOpenTelemetryApi(trace.getTracer("app"), metrics.getMeter("app"));
|
|
55
|
+
const telemetry = createOpenTelemetryInstrumentation({ tracer, meter, onExporterError: console.error });
|
|
56
|
+
|
|
57
|
+
const detach = telemetry.attachSession(session);
|
|
58
|
+
// or: for await (const event of session.subscribe()) telemetry.handleAgentEvent(event);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Set `enabled: false` or omit `tracer`/`meter` for a no-op adapter.
|
|
62
|
+
|
|
63
|
+
## Outputs / response / events
|
|
64
|
+
|
|
65
|
+
Provider turn metadata fields:
|
|
66
|
+
|
|
67
|
+
| Field | Source |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `providerId` | Active provider id |
|
|
70
|
+
| `model` | `ProviderRequest.model` |
|
|
71
|
+
| `requestId` | `request.metadata.requestId` or `request.options.sessionId` |
|
|
72
|
+
| `attempt` | Retry attempt (1-based) |
|
|
73
|
+
| `latencyMs` | Set on `provider_turn_finished` |
|
|
74
|
+
| `httpStatus` | Numeric `ErrorInfo.code` when present |
|
|
75
|
+
| `rateLimitRemaining` / `rateLimitResetMs` | Reserved for provider adapters (optional) |
|
|
76
|
+
|
|
77
|
+
OpenTelemetry mapping (when enabled):
|
|
78
|
+
|
|
79
|
+
| Agent event | Span | Metric labels |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| `agent_started` / `agent_finished` | `prism.agent.run` | — |
|
|
82
|
+
| `provider_turn_*` | `prism.provider.turn` | `provider_id`, `outcome` on duration histogram |
|
|
83
|
+
| `tool_execution_*` (terminal) | `prism.tool.execute` when started | `status` on duration histogram |
|
|
84
|
+
| `provider_turn_finished` / `agent_finished` usage | span attributes | `prism.provider.tokens` counter (`kind`: input/output/cache_*) |
|
|
85
|
+
|
|
86
|
+
High-cardinality identifiers (`sessionId`, `runId`, `requestId`, `toolCallId`) are **span attributes only**, never metric labels.
|
|
87
|
+
|
|
88
|
+
## Request/response example
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"type": "provider_turn_finished",
|
|
93
|
+
"sessionId": "sess_01J...",
|
|
94
|
+
"runId": "run_01J...",
|
|
95
|
+
"turn": 1,
|
|
96
|
+
"metadata": {
|
|
97
|
+
"providerId": "openai",
|
|
98
|
+
"model": { "provider": "openai", "model": "gpt-4.1" },
|
|
99
|
+
"requestId": "sess_01J...",
|
|
100
|
+
"attempt": 2,
|
|
101
|
+
"latencyMs": 842,
|
|
102
|
+
"httpStatus": 503
|
|
103
|
+
},
|
|
104
|
+
"error": { "message": "upstream unavailable", "code": 503 }
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"type": "tool_execution_finished",
|
|
111
|
+
"sessionId": "sess_01J...",
|
|
112
|
+
"runId": "run_01J...",
|
|
113
|
+
"result": { "toolCallId": "call_1", "name": "echo" },
|
|
114
|
+
"metadata": { "durationMs": 12, "status": "finished" }
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Implementation example
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { createAgent, createMockProvider, providerDone, providerTextDelta } from "@arnilo/prism";
|
|
122
|
+
import { createInMemoryTelemetry, createOpenTelemetryInstrumentation } from "@arnilo/prism-observability-opentelemetry";
|
|
123
|
+
|
|
124
|
+
const memory = createInMemoryTelemetry();
|
|
125
|
+
const telemetry = createOpenTelemetryInstrumentation({ tracer: memory.tracer, meter: memory.meter });
|
|
126
|
+
|
|
127
|
+
const session = createAgent({
|
|
128
|
+
model: { provider: "mock", model: "demo" },
|
|
129
|
+
provider: createMockProvider([providerTextDelta("hi"), providerDone()]),
|
|
130
|
+
}).createSession();
|
|
131
|
+
|
|
132
|
+
const detach = telemetry.attachSession(session);
|
|
133
|
+
await session.run("hello");
|
|
134
|
+
detach();
|
|
135
|
+
|
|
136
|
+
console.log(memory.spans.map((span) => span.name));
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Extension and configuration notes
|
|
140
|
+
|
|
141
|
+
- Events flow through `redactAgentEvent` before subscribers and ledger writes — configure `createSecretRedactor` on the agent/run.
|
|
142
|
+
- `retry_scheduled` still signals backoff; each retry attempt emits its own `provider_turn_*` pair with `metadata.attempt`.
|
|
143
|
+
- NeuralWatt `neuralwatt:telemetry` provider events remain package-local; hosts may forward numeric cost/energy into custom metrics.
|
|
144
|
+
- `@arnilo/prism-observability-opentelemetry` is optional and included through `@arnilo/prism-sdk` and `@arnilo/prism-all`; instrumentation remains disabled until a host configures it.
|
|
145
|
+
- Exporter failures are isolated: instrumentation catches tracer/meter errors and invokes `onExporterError` without affecting the run.
|
|
146
|
+
- Disabled instrumentation performs no per-delta span work (`enabled: false` or missing tracer/meter).
|
|
147
|
+
|
|
148
|
+
## Security and performance notes
|
|
149
|
+
|
|
150
|
+
- Default events are metadata-only — no prompts, streamed deltas, tool arguments, or credentials.
|
|
151
|
+
- Opt-in content in other event types (`message_delta`, tool `result`) is still subject to `redactAgentEvent`.
|
|
152
|
+
- Metric labels stay low-cardinality (`provider_id`, `outcome`, `status`, token `kind`); never use `sessionId`/`runId` as labels.
|
|
153
|
+
- Target overhead when enabled is under 5% excluding exporter I/O; disabled hooks allocate no spans.
|
|
154
|
+
- Provider transport limits and redaction order are documented in [Provider primitives](provider-primitives.md).
|
|
155
|
+
|
|
156
|
+
## Related APIs
|
|
157
|
+
|
|
158
|
+
- [Agent events](agent-events.md): full `AgentEvent` union and subscriber semantics.
|
|
159
|
+
- [Runs and usage ledger](runs-and-usage.md): durable `AgentEventRecord` persistence.
|
|
160
|
+
- [Middleware hooks](middleware-hooks.md): transform boundaries alongside event subscribers.
|
|
161
|
+
- [Provider primitives](provider-primitives.md): frozen observability contract for Plan 054.
|
|
162
|
+
- [Credentials and redaction](credentials-and-redaction.md): secret redaction before events and ledger rows.
|
|
163
|
+
- [Workflows](workflows.md): package-local `WorkflowEvent` stream that can wrap redacted `AgentEvent`s from agent nodes.
|
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
|