@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,242 @@
|
|
|
1
|
+
# Coding agent tools (first-party package)
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`@arnilo/prism-coding-agent` is an optional first-party package that provides host shell/filesystem tools as Prism `ToolDefinition` objects. It ships four tools — `shell`, `read`, `write`, `edit` — plus aggregator factories. The tools are **inert** until a host imports them and registers them into a `ToolRegistry`. Behavior is a behavioral port of the pi coding agent's `bash`/`read`/`write`/`edit` tools, adapted to Prism's `ToolDefinition` / `ToolResult` contracts (no `@earendil-works/*` or `typebox` dependencies; only `diff` plus the Node standard library).
|
|
6
|
+
|
|
7
|
+
| Export | Purpose |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `createShellTool(cwd, options?)` | `shell` tool: run a shell command and return combined output + exit code. |
|
|
10
|
+
| `createReadTool(cwd, options?)` | `read` tool: read a text or image file into `TextContent` / `ImageContent`. |
|
|
11
|
+
| `createWriteTool(cwd, options?)` | `write` tool: create or overwrite a file, creating parent directories. |
|
|
12
|
+
| `createEditTool(cwd, options?)` | `edit` tool: precise exact-then-fuzzy text replacement in an existing file. |
|
|
13
|
+
| `createCodingTools(cwd, options?)` | All four tools (`shell`, `read`, `write`, `edit`). |
|
|
14
|
+
| `createReadOnlyTools(cwd, options?)` | Read-only subset: `read` only. |
|
|
15
|
+
| `createAllTools(cwd, options?)` | Every tool the package provides (currently identical to `createCodingTools`). |
|
|
16
|
+
| `detectSupportedImageMimeType(buf)` / `detectSupportedImageMimeTypeFromFile(path)` | Magic-byte image MIME detection (PNG/JPEG/GIF/WebP/BMP) used by `read`. |
|
|
17
|
+
| `DEFAULT_MAX_IMAGE_BYTES` | Default `read` image size ceiling (10 MB). |
|
|
18
|
+
| `TransformImage` / `TransformImageInput` | Types for the optional `read` `transformImage` callback. |
|
|
19
|
+
| `withFileMutationQueue(path, fn)` | Per-path serialization primitive re-exported for hosts. |
|
|
20
|
+
|
|
21
|
+
Each factory returns a plain `ToolDefinition` (no auto-registration). Register what you need:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { createToolRegistry } from "@arnilo/prism";
|
|
25
|
+
import { createCodingTools } from "@arnilo/prism-coding-agent";
|
|
26
|
+
|
|
27
|
+
const tools = createToolRegistry(createCodingTools(process.cwd()));
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## When to use it
|
|
31
|
+
|
|
32
|
+
Use this package when a host wants ready-made coding tools for an agent, session, or run, registered explicitly into a `ToolRegistry` and dispatched through the normal Prism tool harness. The tools perform **real** shell and filesystem operations on the host — they are not mocked or sandboxed. Use the individual factories when you need per-tool options or custom operation backends; use the aggregators when you want the default set.
|
|
33
|
+
|
|
34
|
+
Do not use this package as a sandbox, permission policy, secret store, or provider loop. Prism gates tool dispatch with `PermissionPolicy` / `ToolValidator` / trust policies; pass an optional `ExecutionPolicy` (for example from `@arnilo/prism-coding-security`) for path/command approval before side effects. Do not register these tools for an untrusted provider.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createCodingTools } from "@arnilo/prism-coding-agent";
|
|
38
|
+
import { createCodingApprovalPolicy } from "@arnilo/prism-coding-security";
|
|
39
|
+
|
|
40
|
+
const tools = createCodingTools(workspaceRoot, {
|
|
41
|
+
executionPolicy: createCodingApprovalPolicy({
|
|
42
|
+
roots: [workspaceRoot],
|
|
43
|
+
approve: async ({ action }) => host.confirm(action),
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### pi name mapping
|
|
49
|
+
|
|
50
|
+
| Prism (`@arnilo/prism-coding-agent`) | pi coding agent |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `shell` | `bash` |
|
|
53
|
+
| `read` | `read` |
|
|
54
|
+
| `write` | `write` |
|
|
55
|
+
| `edit` | `edit` |
|
|
56
|
+
|
|
57
|
+
## Inputs / request
|
|
58
|
+
|
|
59
|
+
### `shell`
|
|
60
|
+
|
|
61
|
+
Run a shell command and return combined stdout+stderr.
|
|
62
|
+
|
|
63
|
+
**Inputs:**
|
|
64
|
+
|
|
65
|
+
| Field | Type | Purpose |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `command` | `string` | Shell command to execute (required). |
|
|
68
|
+
| `timeout` | `number` | Timeout in **seconds** (optional; no default). |
|
|
69
|
+
|
|
70
|
+
**Outputs:** a `ToolResult` whose `content[0]` is a `TextContent` with the combined output. Non-zero exit is **not** a tool error: it is returned as a normal result with `[Command exited with code N]` appended to the content and `exitCode` in metadata. Timeout and abort are error results that still carry the partial output captured so far.
|
|
71
|
+
|
|
72
|
+
`shell` result `metadata`:
|
|
73
|
+
|
|
74
|
+
| Field | Present when | Purpose |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| `exitCode` | always | Process exit code, or `null` when the process was killed by timeout/abort. |
|
|
77
|
+
| `truncation` | always | `TruncationResult` from the bounded output accumulator. |
|
|
78
|
+
| `fullOutputPath?` | truncated only | Path to the spilled temp file holding the full output. |
|
|
79
|
+
|
|
80
|
+
Shell resolution honors `options.shellPath` → `SHELL` env → `/bin/bash` → `sh`, and the process group is killed on timeout/abort (`process.kill(-pid)` on Unix, `taskkill /F /T` on Windows).
|
|
81
|
+
|
|
82
|
+
### `read`
|
|
83
|
+
|
|
84
|
+
Read a text or image file.
|
|
85
|
+
|
|
86
|
+
**Inputs:**
|
|
87
|
+
|
|
88
|
+
| Field | Type | Purpose |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| `path` | `string` | Path to the file (relative or absolute; `~` and `file://` expanded). Required. |
|
|
91
|
+
| `offset` | `number` | Line to start reading from (1-indexed). |
|
|
92
|
+
| `limit` | `number` | Maximum number of lines to read. |
|
|
93
|
+
|
|
94
|
+
**Outputs:** text files become a single `TextContent`, truncated to `maxLines`/`maxBytes` (defaults 2000 lines / 50 KB) with a `Use offset=N to continue` footer when more remains. Image files (PNG/JPEG/GIF/WebP/BMP by **magic bytes**, not extension) become `[TextContent note, ImageContent]` with base64 `data` and `mimeType`. Oversize images are rejected by `stat` (when available) or `buffer.length` against `maxImageBytes` (default 10 MB) before base64 encoding. An optional `transformImage` callback lets hosts resize or re-encode images without adding image-processing dependencies to the base package. Read failures (missing file, offset beyond end, oversize image, abort) are error results.
|
|
95
|
+
|
|
96
|
+
`read` tool options (via `createReadTool(cwd, options)` or `ToolsOptions.read`):
|
|
97
|
+
|
|
98
|
+
| Option | Default | Purpose |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| `maxImageBytes` | `DEFAULT_MAX_IMAGE_BYTES` (10 MB) | Reject image reads larger than this many bytes. |
|
|
101
|
+
| `transformImage` | — | Host callback `( { buffer, mimeType } ) => Promise<Buffer>` run after read, before base64. |
|
|
102
|
+
| `autoResizeImages` | — | **Deprecated.** Ignored unless `transformImage` is also set (use `transformImage` instead). |
|
|
103
|
+
| `maxLines` / `maxBytes` | 2000 / 50 KB | Text head truncation limits. |
|
|
104
|
+
| `operations` | local fs | Pluggable `ReadOperations` backend. |
|
|
105
|
+
| `executionPolicy` | — | Structured pre-execution policy (see [Coding security](coding-security.md)). |
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { createReadTool, DEFAULT_MAX_IMAGE_BYTES } from "@arnilo/prism-coding-agent";
|
|
109
|
+
|
|
110
|
+
const read = createReadTool(cwd, {
|
|
111
|
+
maxImageBytes: DEFAULT_MAX_IMAGE_BYTES,
|
|
112
|
+
transformImage: async ({ buffer, mimeType }) => host.resizeImage(buffer, mimeType),
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`read` result `metadata`:
|
|
117
|
+
|
|
118
|
+
| Field | Present when | Purpose |
|
|
119
|
+
| --- | --- | --- |
|
|
120
|
+
| `truncation` | text reads | `TruncationResult`. |
|
|
121
|
+
| `image` | image reads | `{ mimeType, resized, bytes }`. `resized` is `true` when `transformImage` ran. |
|
|
122
|
+
|
|
123
|
+
> `autoResizeImages` is deprecated. It has no effect without `transformImage`; use `transformImage` for host-owned resizing.
|
|
124
|
+
|
|
125
|
+
### `write`
|
|
126
|
+
|
|
127
|
+
Create or overwrite a file, creating parent directories as needed.
|
|
128
|
+
|
|
129
|
+
**Inputs:**
|
|
130
|
+
|
|
131
|
+
| Field | Type | Purpose |
|
|
132
|
+
| --- | --- | --- |
|
|
133
|
+
| `path` | `string` | Path to the file to write (relative or absolute). Required. |
|
|
134
|
+
| `content` | `string` | Content to write (empty string creates an empty file). Required. |
|
|
135
|
+
|
|
136
|
+
**Outputs:** a `TextContent` confirmation naming the **absolute path** with UTF-8 byte and line counts (e.g. `Successfully wrote 42 bytes (3 lines) to /abs/path.txt`). Write failures and abort are error results. Empty `content` is valid.
|
|
137
|
+
|
|
138
|
+
`write` result `metadata`: `{ bytes, lines, path }` (absolute path). Concurrent writes to the same path serialize through `withFileMutationQueue`; writes to different paths run in parallel.
|
|
139
|
+
|
|
140
|
+
### `edit`
|
|
141
|
+
|
|
142
|
+
Precise text replacement in an existing file via exact-then-fuzzy matching.
|
|
143
|
+
|
|
144
|
+
**Inputs:**
|
|
145
|
+
|
|
146
|
+
| Field | Type | Purpose |
|
|
147
|
+
| --- | --- | --- |
|
|
148
|
+
| `path` | `string` | Path to the file to edit. Required. |
|
|
149
|
+
| `edits` | `Array<{ oldText: string, newText: string }>` | Targeted replacements, each matched against the **original** file (not incrementally). No overlapping/nested edits. Required, non-empty. |
|
|
150
|
+
|
|
151
|
+
Each `edits[].oldText` must match a unique, non-overlapping region of the original file. Matching is exact first, then fuzzy (unicode normalization / whitespace collapse). A BOM is stripped before matching and re-prepended on write; original line endings are restored.
|
|
152
|
+
|
|
153
|
+
**Outputs:** a `TextContent` confirmation (`Successfully replaced N block(s) in {path}.`) plus `metadata`. Any failure — missing/unreadable file, no match, duplicate (non-unique) match, overlap, empty `oldText`, no-op edit, or abort — is an error result, and the file is left **unchanged** (the match runs before the write).
|
|
154
|
+
|
|
155
|
+
`edit` result `metadata`: `{ diff, patch, firstChangedLine }` — a display-oriented diff, a standard unified patch, and the first changed line in the new file. These are host-readable; the model only sees the short confirmation (keeps model context small).
|
|
156
|
+
|
|
157
|
+
## Outputs / response / events
|
|
158
|
+
|
|
159
|
+
Every tool returns a `ToolResult` with `toolCallId`, `name`, `content` (`readonly ContentBlock[]`), optional `error`, and optional `metadata`. Mutating tools (`shell` with same cwd, `write`, `edit`) serialize per realpath through `withFileMutationQueue` so concurrent calls targeting one file do not interleave. The package emits no events of its own; hosts observe tool execution through the normal Prism `AgentEvent` stream via `dispatchToolCall`.
|
|
160
|
+
|
|
161
|
+
## Request/response example
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
// edit request
|
|
165
|
+
{ "path": "src/app.ts", "edits": [{ "oldText": "const x = 1;", "newText": "const x = 2;" }] }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
// edit success result
|
|
170
|
+
{
|
|
171
|
+
"toolCallId": "call_1",
|
|
172
|
+
"name": "edit",
|
|
173
|
+
"content": [{ "type": "text", "text": "Successfully replaced 1 block(s) in src/app.ts." }],
|
|
174
|
+
"metadata": { "diff": "...", "patch": "--- src/app.ts\n+++ src/app.ts\n...", "firstChangedLine": 3 }
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
// edit no-match result (file unchanged)
|
|
180
|
+
{
|
|
181
|
+
"toolCallId": "call_2",
|
|
182
|
+
"name": "edit",
|
|
183
|
+
"error": { "message": "Could not find edits[0] in src/app.ts. The oldText must match exactly including all whitespace and newlines." }
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Implementation example
|
|
188
|
+
|
|
189
|
+
Minimal drop-in for any Prism app:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { createToolRegistry } from "@arnilo/prism";
|
|
193
|
+
import { createCodingTools, createReadOnlyTools } from "@arnilo/prism-coding-agent";
|
|
194
|
+
|
|
195
|
+
// Full coding set (shell + read + write + edit) against the project root:
|
|
196
|
+
const tools = createToolRegistry(createCodingTools(process.cwd()));
|
|
197
|
+
|
|
198
|
+
// Or a read-only set for inspection-only agents:
|
|
199
|
+
const ro = createToolRegistry(createReadOnlyTools(process.cwd()));
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Customizing a single tool (force bash, cap output, delegate writes to a remote backend):
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import { createShellTool, createWriteTool } from "@arnilo/prism-coding-agent";
|
|
206
|
+
|
|
207
|
+
const shell = createShellTool("/repo", {
|
|
208
|
+
shellPath: "/bin/bash",
|
|
209
|
+
commandPrefix: "set -euo pipefail",
|
|
210
|
+
maxLines: 500,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const remoteWrite = createWriteTool("/repo", {
|
|
214
|
+
operations: {
|
|
215
|
+
writeFile: async (abs, content) => { /* ship to remote */ },
|
|
216
|
+
mkdir: async (dir) => { /* mkdir -p remotely */ },
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Extension and configuration notes
|
|
222
|
+
|
|
223
|
+
- **Pluggable operation backends.** Every tool accepts an `operations` seam so a host can delegate to a remote system (e.g. SSH) while keeping the tool's matching/serialization behavior: `BashOperations` (`shell`), `ReadOperations` (`read`), `WriteOperations` (`write`), `EditOperations` (`edit`).
|
|
224
|
+
- **Per-tool options.** `ShellToolOptions` (`shellPath`, `commandPrefix`, `maxLines`, `maxBytes`, `tempFilePrefix`, `operations`, `spawnHook`, `executionPolicy`); `ReadToolOptions` (`operations`, `maxImageBytes`, `transformImage`, `maxLines`, `maxBytes`, `executionPolicy`; `autoResizeImages` deprecated); `WriteToolOptions` (`operations`, `executionPolicy`); `EditToolOptions` (`operations`, `executionPolicy`).
|
|
225
|
+
- **Aggregator options.** `ToolsOptions` (`{ shell?, read?, write?, edit? }`) threads each sub-object to the matching tool.
|
|
226
|
+
- **`ToolsOptions`** and the per-tool option types are exported from the package barrel for host configuration.
|
|
227
|
+
- No auto-discovery or manifest registration: import and register explicitly. This package registers no extensions and owns no globals (the mutation queue is a process-wide per-path map — see `ponytail:` note in the source).
|
|
228
|
+
|
|
229
|
+
## Security and performance notes
|
|
230
|
+
|
|
231
|
+
- **Host shell/filesystem access.** These tools run real commands and read/write real files. They provide **no sandbox**. Gate them with Prism `PermissionPolicy` / `ToolValidator` / trust policies before registering them for any provider turn. See [Host security guide](host-security.md) and [Security/auth/trust](settings-auth-trust-security.md).
|
|
232
|
+
- **Non-zero exit is not an error.** A failing command is a normal `shell` result (exit code in metadata); only timeout/abort/spawn failures are error results. Do not assume `error == undefined` means the command succeeded.
|
|
233
|
+
- **Bounded output.** `shell`/`read` accumulate output into a rolling tail bounded by `maxLines`/`maxBytes`; oversized output spills to a temp file (`fullOutputPath`), so memory use is bounded regardless of command output size.
|
|
234
|
+
- **Per-path serialization.** Concurrent mutations to the same file serialize; concurrent mutations to different files do not block each other. The queue is a process-wide map — across sessions in one process, same-path writes still serialize (upgrade path: scope per registry if throughput matters).
|
|
235
|
+
- **Bounded image reads.** `read` rejects images over `maxImageBytes` (default 10 MB) by `stat` before read when possible; MIME is detected from magic bytes only. Optional `transformImage` is host-owned — the base package has no image-processing dependency.
|
|
236
|
+
|
|
237
|
+
## Related APIs
|
|
238
|
+
|
|
239
|
+
- [Tools](tools.md): the host-owned tool harness — `createToolRegistry`, `dispatchToolCall`, filtering, and the `ToolDefinition` contract these factories satisfy.
|
|
240
|
+
- [Public contracts](public-contracts.md): `ToolDefinition`, `ToolResult`, `ToolExecutionContext`, `ContentBlock`, and `JsonObject` shapes.
|
|
241
|
+
- [Host security guide](host-security.md): fail-closed checklist for permission policies, tool validation, and trust boundaries that must gate these tools.
|
|
242
|
+
- [Tool conformance](tool-conformance.md): assertions for the tool-dispatch blocked-reason matrix these tools participate in.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Coding execution approval and sandboxing
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`@arnilo/prism-coding-security` is an optional package that supplies structured execution policy for `@arnilo/prism-coding-agent` tools. It complements name-based `PermissionPolicy` at dispatch time with path/command context checked **inside** each tool before side effects.
|
|
6
|
+
|
|
7
|
+
| Export | Purpose |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `createCodingApprovalPolicy(options)` | Returns an `ExecutionPolicy` with trusted roots, read-only mode, command allow/deny rules, approval caching, and timeout/abort-aware approval waits. |
|
|
10
|
+
| `createSandboxBashOperations(adapter)` | Maps a host-owned `SandboxAdapter` to coding-agent `BashOperations` for delegated shell execution. |
|
|
11
|
+
| `assertPathInsideRoots`, `isPathInsideReal` | Symlink-aware path containment helpers. |
|
|
12
|
+
| `evaluateCommandRules`, `hasShellMetacharacters` | Command classification helpers. |
|
|
13
|
+
|
|
14
|
+
Core contracts live in `@arnilo/prism`:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import type { ExecutionAction, ExecutionPolicy, ExecutionDecision } from "@arnilo/prism";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## When to use it
|
|
21
|
+
|
|
22
|
+
Use this package when coding tools need path scoping, human approval, command rules, or a pluggable sandbox backend. Wire the returned policy through `createCodingTools(cwd, { executionPolicy })` or per-tool `executionPolicy` options.
|
|
23
|
+
|
|
24
|
+
Prism does **not** claim OS-level isolation unless the host provides a sandbox adapter. Default policy denies shell/write/edit without an `approve` callback and rejects paths outside configured roots. Coding shell definitions are marked `exclusive: true`, matching the approval policy's shell decision, so a single-shot turn containing shell work runs sequentially even when `toolConcurrency > 1`. Non-shell turns retain configured parallelism.
|
|
25
|
+
|
|
26
|
+
## Inputs / request
|
|
27
|
+
|
|
28
|
+
| Option | Default | Purpose |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `roots` | required | Realpath-contained filesystem roots. |
|
|
31
|
+
| `readOnly` | `false` | Deny shell/write/edit actions. |
|
|
32
|
+
| `commandRules` | `[]` | Ordered allow/deny/approval command classification. |
|
|
33
|
+
| `approve` | none | Host callback for actions not statically allowed; omission fails closed. |
|
|
34
|
+
| `approvalCacheScope` | `"none"` | Optional `run` or `session` decision cache scope. |
|
|
35
|
+
| `approvalTimeoutMs` | `30000` | Bound approval wait; caller abort also cancels it. |
|
|
36
|
+
|
|
37
|
+
## Outputs / response / events
|
|
38
|
+
|
|
39
|
+
`createCodingApprovalPolicy()` returns an `ExecutionPolicy`. Allowed checks return `ExecutionDecision { allowed: true }`; denied checks include a stable reason; shell decisions set `exclusive: true`. Sandbox adapters return coding-agent-compatible `BashOperations` and never grant policy approval themselves.
|
|
40
|
+
|
|
41
|
+
## Request/response example
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"action": { "kind": "shell", "operation": "execute", "command": "npm test", "paths": [] },
|
|
46
|
+
"decision": { "allowed": true, "exclusive": true }
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Implementation example
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { createCodingTools } from "@arnilo/prism-coding-agent";
|
|
54
|
+
import { createCodingApprovalPolicy, createSandboxBashOperations } from "@arnilo/prism-coding-security";
|
|
55
|
+
|
|
56
|
+
const policy = createCodingApprovalPolicy({
|
|
57
|
+
roots: [workspaceRoot],
|
|
58
|
+
approve: async ({ action, signal }) => ui.confirm(action, { signal }),
|
|
59
|
+
approvalCacheScope: "run",
|
|
60
|
+
approvalTimeoutMs: 60_000,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const tools = createCodingTools(workspaceRoot, {
|
|
64
|
+
executionPolicy: policy,
|
|
65
|
+
shell: {
|
|
66
|
+
operations: createSandboxBashOperations(mySandboxAdapter),
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Extension and configuration notes
|
|
72
|
+
|
|
73
|
+
Policies are ordinary host values: attach one globally through `createCodingTools()` or per tool. `SandboxAdapter` is replaceable and host-owned; approval policy and sandboxing are separate layers. Use run-scoped approval caching unless a wider host identity/lifecycle is explicit.
|
|
74
|
+
|
|
75
|
+
## Security and performance notes
|
|
76
|
+
|
|
77
|
+
Containment resolves symlinks and rejects paths outside roots. Command rules are not a shell parser; shell metacharacters require approval. Approval waits and subprocess execution honor abort/timeouts. Path checks and cache lookup are local; sandbox latency belongs to the supplied adapter.
|
|
78
|
+
|
|
79
|
+
## Related APIs
|
|
80
|
+
|
|
81
|
+
- [Coding agent tools](coding-agent-tools.md)
|
|
82
|
+
- [Host security guide](host-security.md)
|
|
83
|
+
- [Tool execution primitives](tool-execution-primitives.md)
|
|
84
|
+
- [Security/auth/trust](settings-auth-trust-security.md)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Credential storage
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
The optional `@arnilo/prism-credentials-node` package ships host-owned credential persistence for Node.js CLI and desktop apps:
|
|
6
|
+
|
|
7
|
+
- **Encrypted file store** — AES-256-GCM envelope with scrypt KDF, atomic rename writes, versioned on-disk format
|
|
8
|
+
- **System keychain store** — cross-platform secret service via `@napi-rs/keyring@^1.3.0`
|
|
9
|
+
- **Stored credential resolver** — `createStoredCredentialResolver(store)` for explicit resolver chains
|
|
10
|
+
- **OAuth adapter** — extends the core `OAuthCredentialStore` seam with `get`/`delete` for refresh flows
|
|
11
|
+
|
|
12
|
+
Factories:
|
|
13
|
+
|
|
14
|
+
- `openEncryptedCredentialStore(options)` / `createEncryptedCredentialStore(options)`
|
|
15
|
+
- `createKeychainCredentialStore(options)`
|
|
16
|
+
- `createStoredCredentialResolver(store)`
|
|
17
|
+
- `createOAuthCredentialStoreAdapter(store)`
|
|
18
|
+
- `rotateEncryptedCredentialStorePassphrase(options)`
|
|
19
|
+
|
|
20
|
+
Core `@arnilo/prism` remains storage-free. Hosts choose a backend explicitly at startup; there is no global credential singleton and no silent fallback from keychain to plaintext file storage.
|
|
21
|
+
|
|
22
|
+
## When to use it
|
|
23
|
+
|
|
24
|
+
Use this package when a host needs durable credentials beyond `createMemoryCredentialStore()`:
|
|
25
|
+
|
|
26
|
+
- local CLI tools storing API keys or OAuth tokens between runs
|
|
27
|
+
- desktop hosts integrating with macOS Keychain, Windows Credential Manager, or Linux Secret Service
|
|
28
|
+
- integration tests that need encrypted reopen semantics without a live keychain
|
|
29
|
+
|
|
30
|
+
Do **not** use it when credentials should live in a remote vault, HSM, or cloud secret manager — implement `CredentialResolver` against that service instead.
|
|
31
|
+
|
|
32
|
+
## Inputs / request
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import {
|
|
36
|
+
openEncryptedCredentialStore,
|
|
37
|
+
createKeychainCredentialStore,
|
|
38
|
+
} from "@arnilo/prism-credentials-node";
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Encrypted file
|
|
42
|
+
|
|
43
|
+
| Field | Type | Purpose |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| `path` | `string` | Vault file path. Parent directories are created as needed. |
|
|
46
|
+
| `getPassphrase` | `() => string \| Promise<string>` | Host-owned passphrase retrieval. Never logged by the adapter. |
|
|
47
|
+
| `scrypt` | `{ N?, r?, p?, keyLength? }` | Optional KDF tuning. Defaults: `N=32768`, `r=8`, `p=1`, `keyLength=32`. Minimum `N=16384`. |
|
|
48
|
+
| `fileMode` | `number` | Unix mode for newly written files. Defaults to `0o600`. |
|
|
49
|
+
|
|
50
|
+
### System keychain
|
|
51
|
+
|
|
52
|
+
| Field | Type | Purpose |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| `service` | `string` | Keychain service name (application identifier). |
|
|
55
|
+
| `namespace` | `string` | Optional prefix separating environments or tenants within one service. |
|
|
56
|
+
| `timeoutMs` | `number` | Operation timeout. Defaults to `5000`. |
|
|
57
|
+
|
|
58
|
+
## Outputs / response / events
|
|
59
|
+
|
|
60
|
+
Both backends implement `StoredCredentialStore`:
|
|
61
|
+
|
|
62
|
+
| Method | Behavior |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `set(record)` / `get(request)` / `delete(request)` | Namespaced by `(provider, name)` for API keys and bearer tokens. |
|
|
65
|
+
| `setOAuth(provider, credentials, accountId?)` | Stores OAuth tokens per provider/account. |
|
|
66
|
+
| `getOAuth(provider, accountId?)` / `deleteOAuth(...)` | Reads or removes OAuth rows. |
|
|
67
|
+
| `resolve(request)` | `CredentialResolver` compatibility via `createStoredCredentialResolver`. |
|
|
68
|
+
|
|
69
|
+
Encrypted file stores also expose:
|
|
70
|
+
|
|
71
|
+
- `reload()` — re-read and decrypt from disk
|
|
72
|
+
- `flush()` — force rewrite of the encrypted envelope
|
|
73
|
+
|
|
74
|
+
Errors are explicit and fail closed:
|
|
75
|
+
|
|
76
|
+
| Error | Code | When |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| `CredentialDecryptError` | `credential_decrypt_failed` | Wrong passphrase or tampered ciphertext |
|
|
79
|
+
| `CredentialStoreLockedError` | `credential_store_locked` | Keychain denied or locked |
|
|
80
|
+
| `CredentialStoreUnavailableError` | `credential_store_unavailable` | No OS secret service |
|
|
81
|
+
| `CredentialStoreTimeoutError` | `credential_store_timeout` | Keychain call exceeded `timeoutMs` |
|
|
82
|
+
| `WeakKdfParametersError` | `weak_kdf_parameters` | scrypt work factor below minimum |
|
|
83
|
+
|
|
84
|
+
## Request/response example
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"path": "./credentials.vault",
|
|
89
|
+
"fileMode": 384,
|
|
90
|
+
"keychain": {
|
|
91
|
+
"service": "my-app",
|
|
92
|
+
"namespace": "production",
|
|
93
|
+
"timeoutMs": 5000
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
On-disk envelope (illustrative — ciphertext is base64, secrets are not plaintext):
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"version": 1,
|
|
103
|
+
"kdf": { "algorithm": "scrypt", "N": 32768, "r": 8, "p": 1, "salt": "...", "keyLength": 32 },
|
|
104
|
+
"cipher": { "algorithm": "aes-256-gcm", "iv": "..." },
|
|
105
|
+
"ciphertext": "..."
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Implementation example
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import {
|
|
113
|
+
createExplicitCredentialResolver,
|
|
114
|
+
refreshOAuthCredential,
|
|
115
|
+
resolveCredentialValue,
|
|
116
|
+
} from "@arnilo/prism";
|
|
117
|
+
import {
|
|
118
|
+
createOAuthCredentialStoreAdapter,
|
|
119
|
+
createStoredCredentialResolver,
|
|
120
|
+
openEncryptedCredentialStore,
|
|
121
|
+
} from "@arnilo/prism-credentials-node";
|
|
122
|
+
|
|
123
|
+
const store = await openEncryptedCredentialStore({
|
|
124
|
+
path: "./credentials.vault",
|
|
125
|
+
getPassphrase: () => process.env.MY_APP_CREDENTIAL_PASSPHRASE!,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const resolver = createExplicitCredentialResolver([
|
|
129
|
+
{ name: "stored", resolver: createStoredCredentialResolver(store) },
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
const apiKey = await resolveCredentialValue(resolver, { name: "apiKey", provider: "demo" });
|
|
133
|
+
|
|
134
|
+
const oauthStore = createOAuthCredentialStoreAdapter(store);
|
|
135
|
+
await refreshOAuthCredential({
|
|
136
|
+
provider: myOAuthProvider,
|
|
137
|
+
credentials: existing,
|
|
138
|
+
store: oauthStore,
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Passphrase rotation:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { rotateEncryptedCredentialStorePassphrase } from "@arnilo/prism-credentials-node";
|
|
146
|
+
|
|
147
|
+
await rotateEncryptedCredentialStorePassphrase({
|
|
148
|
+
path: "./credentials.vault",
|
|
149
|
+
getCurrentPassphrase: () => oldPassphrase,
|
|
150
|
+
getNewPassphrase: () => newPassphrase,
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Extension and configuration notes
|
|
155
|
+
|
|
156
|
+
- Passphrase retrieval, TLS, and OS permission prompts remain host-owned.
|
|
157
|
+
- Use distinct `namespace` or vault paths per tenant/environment.
|
|
158
|
+
- Keychain `list()` / `listOAuth()` are intentionally unsupported — enumerate credentials through host configuration instead of scanning the OS store.
|
|
159
|
+
- Combine with `createExplicitCredentialResolver()` so runtime overrides still win over stored values.
|
|
160
|
+
- Wire `createOAuthCredentialStoreAdapter(store)` into `refreshOAuthCredential()` so refreshed tokens persist durably.
|
|
161
|
+
|
|
162
|
+
## Security and performance notes
|
|
163
|
+
|
|
164
|
+
- Authenticated encryption uses Node built-in `aes-256-gcm` and `scrypt`; no extra crypto dependencies for the file backend.
|
|
165
|
+
- Atomic writes use temp file + rename; partial writes cannot replace a valid vault.
|
|
166
|
+
- Derived keys are zeroed after encrypt/decrypt operations where practical.
|
|
167
|
+
- Default scrypt `N=32768` targets interactive CLI unlock; raise `N` for higher security at the cost of unlock latency.
|
|
168
|
+
- Keychain operations honor `timeoutMs` and surface `CredentialStoreTimeoutError` instead of blocking indefinitely.
|
|
169
|
+
- Never log passphrases, derived keys, or decrypted credential payloads. Error messages do not echo secret values.
|
|
170
|
+
- Live keychain tests are opt-in (`PRISM_TEST_KEYCHAIN=1`); default `npm test` stays offline.
|
|
171
|
+
|
|
172
|
+
## Related APIs
|
|
173
|
+
|
|
174
|
+
- [Credentials and redaction](credentials-and-redaction.md): core resolver helpers and `refreshOAuthCredential()`
|
|
175
|
+
- [Security/auth/trust](settings-auth-trust-security.md): host-owned settings/credentials boundaries
|
|
176
|
+
- [Persistence, credentials, and multimodality primitives](persistence-credentials-multimodality-primitives.md): Plan 056 threat model and conformance matrix rows 7–10
|
|
177
|
+
- `@arnilo/prism`: `CredentialResolver`, `OAuthCredentialStore`, `createMemoryCredentialStore()`
|
|
@@ -113,6 +113,7 @@ console.log(error.message);
|
|
|
113
113
|
- `AgentConfig.credentials` is not eagerly resolved, serialized into provider requests/events/stores, or passed to loops/compaction by the core runtime.
|
|
114
114
|
- `resolveCredentialValue()` and `createExplicitCredentialResolver()` do not cache values. Add host-side caching only if a real credential source needs it.
|
|
115
115
|
- `refreshOAuthCredential()` only calls the supplied OAuth provider and optional store; it has no built-in persistence or retry loop.
|
|
116
|
+
- OpenAI Codex device-code OAuth polls inside `createOpenAICodexOAuthProvider().login()` with bounded delays and abort support via `OAuthLoginCallbacks.signal`. Token-endpoint failures redact authorization codes, PKCE verifiers, device/user codes, and access/refresh tokens when those values are known.
|
|
116
117
|
|
|
117
118
|
## Related APIs
|
|
118
119
|
|
|
@@ -121,4 +122,4 @@ console.log(error.message);
|
|
|
121
122
|
- [LLM compaction package](compaction-llm.md): resolves optional summary-provider credentials per compaction call and redacts exact known values.
|
|
122
123
|
- [OpenAI-compatible provider](providers/openai-compatible.md): resolves API keys per request and redacts known values from adapter errors.
|
|
123
124
|
|
|
124
|
-
Phase 10 added `createMemoryCredentialStore()`, `createChainedCredentialResolver()`, and `createSecretRedactor()` for opt-in in-memory auth and runtime redaction. Phase 11 adds OAuth/API-key contracts plus explicit resolver order helpers. Core still has no persistent secret store and does not read environment variables or files for credentials. See [Security/auth/trust](settings-auth-trust-security.md).
|
|
125
|
+
Phase 10 added `createMemoryCredentialStore()`, `createChainedCredentialResolver()`, and `createSecretRedactor()` for opt-in in-memory auth and runtime redaction. Phase 11 adds OAuth/API-key contracts plus explicit resolver order helpers. Core still has no persistent secret store and does not read environment variables or files for credentials. For durable storage, use [`@arnilo/prism-credentials-node`](credential-storage.md) encrypted-file or keychain backends. See [Security/auth/trust](settings-auth-trust-security.md).
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
The production persistence contracts describe database-neutral types for durable, multi-tenant storage of Prism sessions, branch handles, session entries, runs, agent-event ledger rows, tool-call rows, usage rows, agent-definition versions, retention policies, and migration records. They also define cursor-paginated query shapes so hosts can implement SQL, NoSQL, or object-store adapters without changing Prism runtime internals.
|
|
6
6
|
|
|
7
|
-
Prism itself does not ship a production database adapter. The built-in `SessionStore` contract (`append` / `list` / optional `get`) remains the runtime seam; `ProductionPersistenceStore` is the optional adapter-facing contract for hosts that need paginated reads, tenant isolation, audit tables, and
|
|
7
|
+
Prism itself does not ship a production database adapter. The built-in `SessionStore` contract (`append` / `list` / optional `get`) remains the runtime seam; `ProductionPersistenceStore` is the optional adapter-facing contract for hosts that need paginated reads, tenant isolation, audit tables, retention, and optional generic `CheckpointStore` / `LeaseStore` capabilities.
|
|
8
|
+
|
|
9
|
+
Plan 056 Task 1 adds dialect-neutral shared primitives under `@arnilo/prism/testing/persistence-schema`, `@arnilo/prism/testing/session-store-conformance`, and `@arnilo/prism/testing/run-ledger-conformance`. Task 2 ships `@arnilo/prism-session-store-sqlite` (see [SQLite persistence](sqlite-persistence.md)); Task 3 ships `@arnilo/prism-session-store-postgres` (see [PostgreSQL persistence](postgres-persistence.md)). Both implement dialect-local SQL against the shared model; Prism core still ships no ORM, driver, or migration runner.
|
|
8
10
|
|
|
9
11
|
## When to use it
|
|
10
12
|
|
|
@@ -184,6 +186,42 @@ The `usage` JSONB stores the `Usage` shape: input/output/total/cache tokens, cos
|
|
|
184
186
|
|
|
185
187
|
Prism does not run migrations; hosts own migration tooling and use this table to record applied changes.
|
|
186
188
|
|
|
189
|
+
## Shared schema model and migration contract
|
|
190
|
+
|
|
191
|
+
Adapter packages import the shared model instead of copying table names piecemeal:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import {
|
|
195
|
+
createPersistenceSchemaModel,
|
|
196
|
+
createPersistenceMigrationContract,
|
|
197
|
+
assertPersistenceSchemaModel,
|
|
198
|
+
assertAdapterSchemaMatchesModel,
|
|
199
|
+
assertPersistenceQueryPaginationConforms,
|
|
200
|
+
assertTenantScopedQueryIsolation,
|
|
201
|
+
getPersistencePaginationCursors,
|
|
202
|
+
PARAMETERIZED_QUERY_GUIDANCE,
|
|
203
|
+
} from "@arnilo/prism/testing/persistence-schema";
|
|
204
|
+
import { assertSessionStoreConforms, runSessionStoreConformance } from "@arnilo/prism/testing/session-store-conformance";
|
|
205
|
+
import { assertRunLedgerConforms, runRunLedgerConformance } from "@arnilo/prism/testing/run-ledger-conformance";
|
|
206
|
+
|
|
207
|
+
const model = createPersistenceSchemaModel();
|
|
208
|
+
assertPersistenceSchemaModel(model);
|
|
209
|
+
|
|
210
|
+
await runSessionStoreConformance(() => createStore(testDatabase), { exerciseReopen: true });
|
|
211
|
+
await runRunLedgerConformance(() => createLedger(testDatabase), { exerciseReopen: true });
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
| Primitive | Purpose |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| `PersistenceSchemaModel` | Versioned table/column/index model covering sessions, entries, parent chain, idempotency side table, runs, events, tool calls, usage, tenant columns, and `prism_migrations` |
|
|
217
|
+
| `createPersistenceMigrationContract()` | Strictly increasing migration steps, `prism_migrations` recording, advisory-lock guidance, and least-privilege migration/runtime role guidance |
|
|
218
|
+
| `getPersistencePaginationCursors()` | Indexed `(session_id, timestamp, id)`, `(run_id, sequence)`, `(run_id, recorded_at, id)` cursor shapes that avoid offset scans |
|
|
219
|
+
| `assertPersistenceQueryPaginationConforms()` | Generic cursor pagination fixture for `queryEntries` |
|
|
220
|
+
| `assertTenantScopedQueryIsolation()` | Tenant-filtered reads must not leak rows or primary-id collisions across tenants |
|
|
221
|
+
| `PARAMETERIZED_QUERY_GUIDANCE` | Values are always bound parameters; only validated identifiers may be quoted |
|
|
222
|
+
|
|
223
|
+
Dialect-local SQL remains in optional adapter packages. The shared model is the contract both adapters must satisfy before release.
|
|
224
|
+
|
|
187
225
|
## Adapter readiness checklist
|
|
188
226
|
|
|
189
227
|
Before using a host database adapter in production:
|
|
@@ -382,9 +420,12 @@ const dbStore: ProductionPersistenceStore = {
|
|
|
382
420
|
## Extension and configuration notes
|
|
383
421
|
|
|
384
422
|
- `ProductionPersistenceStore` is an optional extension point. The runtime does not require it.
|
|
385
|
-
-
|
|
423
|
+
- `ProductionPersistenceStore.checkpoints?: CheckpointStore` exposes generic versioned save/load/bounded-list/delete with compare-and-swap and fencing tokens, without workflow vocabulary.
|
|
424
|
+
- `ProductionPersistenceStore.leases?: LeaseStore` exposes atomic acquire/renew/release/get with opaque claim tokens, expiries, ownership scope, and monotonic fencing tokens.
|
|
425
|
+
- Hosts choose the database, schema, transaction, and indexing strategy. The contracts specify query and checkpoint capability shapes.
|
|
386
426
|
- `SessionStore` (`append`/`list`/`get`/optional `readBranchPath`) can be implemented on top of `ProductionPersistenceStore` or kept separate.
|
|
387
427
|
- Cursor values and idempotency keys are host-defined and opaque to Prism.
|
|
428
|
+
- First-party SQLite/PostgreSQL adapters expose `persistence.checkpoints` and `persistence.leases`, backed by package-owned `prism_checkpoints` / `prism_leases` tables. `@arnilo/prism-workflows` consumes them for durable resume and multi-process coordination; workflow code owns no SQL table.
|
|
388
429
|
|
|
389
430
|
## Security and performance notes
|
|
390
431
|
|
|
@@ -405,3 +446,4 @@ const dbStore: ProductionPersistenceStore = {
|
|
|
405
446
|
- [Agent events](agent-events.md): `AgentEvent` variants and redaction.
|
|
406
447
|
- [Tools](tools.md): `ToolResult`, `ToolCallContent`, and tool execution events.
|
|
407
448
|
- [Public contracts](public-contracts.md): full public contract inventory.
|
|
449
|
+
- [Workflows](workflows.md): package-local durable checkpoint adapters on shared SQLite/Postgres handles.
|
package/docs/host-security.md
CHANGED
|
@@ -25,6 +25,7 @@ Start from explicit host inputs. Do not let runtime code discover security state
|
|
|
25
25
|
| Permission decisions | allow/deny rules or approval UI result | `createStaticPermissionPolicy`, `assertPermission()` |
|
|
26
26
|
| Tool allow-list | active tools for this agent/session/run | `createToolRegistry`, `filterTools()`, `dispatchToolCall()` |
|
|
27
27
|
| Tool argument rules | host validator | `AgentConfig.validator`, `RunOptions.validate`, `ToolValidator` |
|
|
28
|
+
| Coding execution policy | path/command approval adapter | `ExecutionPolicy`, `@arnilo/prism-coding-security` |
|
|
28
29
|
| Durable history | host database adapter | `SessionStore`, `assertSessionStoreConforms()` |
|
|
29
30
|
| Durable audit | host ledger adapter | `RunLedger`, `redactRunLedgerRecord()` |
|
|
30
31
|
| Extensions | explicit package imports only | `createExtensionKernel`, `ExtensionAPI` |
|
|
@@ -120,12 +121,25 @@ Wire those values where they matter: provider adapters receive the resolved cred
|
|
|
120
121
|
- Prism does not sandbox host tools, extensions, provider adapters, credential resolvers, or custom middleware. Use OS/container/process isolation when code is untrusted.
|
|
121
122
|
- Redaction is exact known-secret replacement only. It is not arbitrary secret detection, entropy scanning, or DLP.
|
|
122
123
|
- Known secrets must be passed into redactors before data is emitted or persisted. Redact again in host adapters if they transform records after Prism redaction.
|
|
123
|
-
- Tool `parameters` metadata is not
|
|
124
|
+
- Tool `parameters` metadata is not validated by default. Add a `ToolValidator`, use `createToolParameterValidator()` with a schema adapter, or install `@arnilo/prism-tool-validator-json-schema` before side effects.
|
|
125
|
+
- MCP tools from `@arnilo/prism-mcp` are untrusted remote servers. Configure stdio commands and HTTP URLs explicitly; bound output with `maxResultBytes`; register prefixed tools only after trust review. See [MCP client bridge](mcp-tools.md).
|
|
126
|
+
- Coding tools from `@arnilo/prism-coding-agent` accept an optional `ExecutionPolicy` checked inside each tool before side effects. Use `@arnilo/prism-coding-security` for path roots, command rules, and approval caching. Prism does not provide OS sandboxing unless the host supplies a sandbox adapter.
|
|
124
127
|
- Permission checks happen before tool validation and before `tool.execute()`. Middleware cannot grant permission by renaming a tool.
|
|
125
128
|
- Session stores and ledgers receive redacted values when a redactor is active, but durable storage remains host-owned. Enforce tenant/account/user ownership and retention in the database layer.
|
|
126
129
|
- Provider-owned auth/content/session/cache/security headers win over caller headers in adapters that merge headers.
|
|
127
130
|
- Security checks are bounded explicit calls on the active path. Prism adds no hidden global middleware, background workers, watchers, network calls, or filesystem scans.
|
|
128
131
|
|
|
132
|
+
### 0.0.4 release security audit (2026-07-14)
|
|
133
|
+
|
|
134
|
+
- `npm audit --audit-level=high`: 0 vulnerabilities at every severity.
|
|
135
|
+
- Lockfile: 162 registry dependency records, all with `resolved` provenance URL and integrity hash; `npm ls --all` reports a clean graph.
|
|
136
|
+
- License inventory: 160 locked third-party packages; all declare permissive MIT, ISC, BSD, Apache-2.0, or compatible dual licenses. No GPL, AGPL, SSPL, or missing lockfile license metadata.
|
|
137
|
+
- Install scripts: only `better-sqlite3@12.11.1` runs an install script (`prebuild-install || node-gyp rebuild --release`), required by the explicitly installed SQLite adapter. Core and other optional packages add no install hook.
|
|
138
|
+
- Secret scan: source, tests, docs, workflow files, package metadata, built tests, packed-install canary, and tarball deny-list checks found no private-key block or common live-token prefix. Runtime redaction fixtures cover requests, events, ledgers, stores, checkpoints, provider/OAuth errors, and credential ciphertext.
|
|
139
|
+
- Threat suites pass for parameterized SQL/tenant isolation, HTTP URL/SSRF rejection, realpath/symlink containment, shell-metacharacter approval, schema prototype-pollution/remote-reference bounds, OAuth polling/abort/redaction, credential tamper/wrong-key/KDF floors, MCP result bounds/timeouts, and coding approval/path policy.
|
|
140
|
+
|
|
141
|
+
PostgreSQL TLS/network policy, MCP endpoint allow-listing, provider base URLs, OS keychain availability, process sandboxing, workflow tenant identity, and ANSI/control-sequence sanitization in any host terminal renderer remain host boundaries. Prism 0.0.4 ships JSON-line RPC, not an interactive TUI; hosts must render untrusted model/tool text safely. Credential-gated PostgreSQL/provider/keychain tests are separate operator/CI gates, not silently replaced by mocks.
|
|
142
|
+
|
|
129
143
|
## Related APIs
|
|
130
144
|
|
|
131
145
|
- [Settings, auth, trust, and security controls](settings-auth-trust-security.md): low-level helpers and boundary hardening table.
|