@arnilo/prism 0.0.3 → 0.0.5
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 +40 -0
- package/README.md +62 -26
- package/dist/agent-loops.d.ts +8 -1
- package/dist/agent-loops.js +57 -11
- package/dist/agents.js +212 -32
- package/dist/checkpoints.d.ts +11 -0
- package/dist/checkpoints.js +144 -0
- package/dist/cli-init.d.ts +41 -0
- package/dist/cli-init.js +390 -0
- package/dist/cli-runner.d.ts +7 -1
- package/dist/cli-runner.js +13 -1
- package/dist/compaction.js +9 -1
- package/dist/content.d.ts +121 -0
- package/dist/content.js +538 -0
- package/dist/contracts.d.ts +236 -11
- package/dist/contracts.js +8 -0
- 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/feedback.d.ts +48 -0
- package/dist/feedback.js +230 -0
- package/dist/index.d.ts +20 -6
- package/dist/index.js +13 -5
- 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 +44 -0
- package/dist/providers/media.js +126 -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/feedback.d.ts +6 -0
- package/dist/testing/feedback.js +37 -0
- package/dist/testing/persistence-schema.d.ts +102 -0
- package/dist/testing/persistence-schema.js +487 -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 +178 -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/a2a.md +73 -0
- package/docs/agent-events.md +17 -10
- package/docs/agent-loops.md +11 -5
- package/docs/agent-session-runtime.md +15 -16
- package/docs/cli-rpc.md +36 -5
- package/docs/coding-agent-tools.md +43 -9
- package/docs/coding-security.md +88 -0
- package/docs/compaction-observational-memory.md +2 -0
- package/docs/context-and-skills.md +1 -0
- package/docs/credential-storage.md +177 -0
- package/docs/credentials-and-redaction.md +4 -3
- package/docs/database-persistence.md +52 -7
- package/docs/evaluations.md +122 -0
- package/docs/extensions.md +2 -2
- package/docs/host-security.md +33 -2
- package/docs/index.md +46 -18
- package/docs/input-and-prompt-assembly.md +6 -5
- package/docs/mcp-tools.md +184 -0
- package/docs/middleware-hooks.md +2 -0
- package/docs/migration.md +51 -28
- package/docs/model-registry.md +5 -3
- package/docs/multimodal-content.md +156 -0
- package/docs/observability.md +171 -0
- package/docs/performance.md +249 -1
- package/docs/persistence-credentials-multimodality-primitives.md +303 -0
- package/docs/postgres-persistence.md +143 -0
- package/docs/provider-conformance.md +18 -0
- package/docs/provider-layer.md +1 -1
- package/docs/provider-packages.md +2 -0
- package/docs/provider-primitives.md +281 -0
- package/docs/providers/ai-sdk.md +113 -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 +13 -5
- package/docs/rag.md +113 -0
- package/docs/release-and-install.md +237 -30
- package/docs/resource-loading.md +14 -4
- package/docs/review-coverage-2026-07-14.md +260 -0
- package/docs/review-coverage-2026-07-15.md +193 -0
- package/docs/run-ledger-conformance.md +96 -0
- package/docs/runs-and-usage.md +43 -4
- package/docs/server.md +139 -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 +6 -5
- package/docs/sqlite-persistence.md +123 -0
- package/docs/structured-output.md +9 -0
- package/docs/supervisors.md +71 -0
- package/docs/tool-conformance.md +1 -0
- package/docs/tool-execution-primitives.md +374 -0
- package/docs/tools.md +39 -1
- package/docs/workflow-orchestration-primitives.md +581 -0
- package/docs/workflow-tui-primitives.md +5 -0
- package/docs/workflows.md +293 -0
- package/docs/working-and-semantic-memory.md +169 -0
- package/package.json +43 -5
- package/templates/init/README.md.tmpl +28 -0
- package/templates/init/env.example.tmpl +1 -0
- package/templates/init/gitignore.tmpl +11 -0
- package/templates/init/optional/evals-example.ts.tmpl +17 -0
- package/templates/init/optional/workflows-example.ts.tmpl +27 -0
- package/templates/init/package.json.tmpl +22 -0
- package/templates/init/providers.json +76 -0
- package/templates/init/src/agent.ts.tmpl +10 -0
- package/templates/init/src/index.ts.tmpl +12 -0
- package/templates/init/src/tests/agent.test.ts.tmpl +24 -0
- package/templates/init/tsconfig.json.tmpl +15 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
// ponytail: dialect-neutral persistence schema model and migration contracts for
|
|
2
|
+
// SQLite/PostgreSQL adapter packages (Plan 056 Task 1). SQL stays package-local;
|
|
3
|
+
// this module defines the shared table/index/pagination/migration expectations
|
|
4
|
+
// adapter authors implement and test against before shipping dialect-specific DDL.
|
|
5
|
+
/** Current shared persistence schema version for production database adapters. */
|
|
6
|
+
export const PERSISTENCE_SCHEMA_VERSION = 3;
|
|
7
|
+
/** Guidance adapters must follow: values are bound parameters, never interpolated. */
|
|
8
|
+
export const PARAMETERIZED_QUERY_GUIDANCE = "Bind every user-supplied value (session ids, idempotency keys, tenant ids, timestamps, JSON payloads) as a query parameter. Quote/validate schema and table identifiers only; never interpolate untrusted strings into SQL text.";
|
|
9
|
+
const TENANT_COLUMNS = [
|
|
10
|
+
{ name: "tenant_id", type: "text", nullable: true, tenantScoped: true },
|
|
11
|
+
{ name: "account_id", type: "text", nullable: true, tenantScoped: true },
|
|
12
|
+
{ name: "user_id", type: "text", nullable: true, tenantScoped: true },
|
|
13
|
+
];
|
|
14
|
+
/** Canonical shared schema model for Tasks 2–3 adapter packages. */
|
|
15
|
+
export function createPersistenceSchemaModel() {
|
|
16
|
+
return {
|
|
17
|
+
version: PERSISTENCE_SCHEMA_VERSION,
|
|
18
|
+
tables: [
|
|
19
|
+
{
|
|
20
|
+
name: "prism_tenants",
|
|
21
|
+
primaryKey: ["id"],
|
|
22
|
+
columns: [
|
|
23
|
+
{ name: "id", type: "text" },
|
|
24
|
+
{ name: "name", type: "text" },
|
|
25
|
+
{ name: "created_at", type: "timestamp" },
|
|
26
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "prism_accounts",
|
|
31
|
+
primaryKey: ["id"],
|
|
32
|
+
columns: [
|
|
33
|
+
{ name: "id", type: "text" },
|
|
34
|
+
{ name: "tenant_id", type: "text", tenantScoped: true },
|
|
35
|
+
{ name: "name", type: "text" },
|
|
36
|
+
{ name: "created_at", type: "timestamp" },
|
|
37
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
38
|
+
],
|
|
39
|
+
foreignKeys: [{ columns: ["tenant_id"], referencesTable: "prism_tenants", referencesColumns: ["id"], tenantBound: true }],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "prism_users",
|
|
43
|
+
primaryKey: ["id"],
|
|
44
|
+
columns: [
|
|
45
|
+
{ name: "id", type: "text" },
|
|
46
|
+
{ name: "tenant_id", type: "text", tenantScoped: true },
|
|
47
|
+
{ name: "account_id", type: "text", nullable: true, tenantScoped: true },
|
|
48
|
+
{ name: "name", type: "text" },
|
|
49
|
+
{ name: "created_at", type: "timestamp" },
|
|
50
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "prism_agent_definitions",
|
|
55
|
+
primaryKey: ["id"],
|
|
56
|
+
columns: [
|
|
57
|
+
{ name: "id", type: "text" },
|
|
58
|
+
{ name: "name", type: "text" },
|
|
59
|
+
{ name: "version", type: "text" },
|
|
60
|
+
{ name: "source", type: "text", nullable: true },
|
|
61
|
+
{ name: "agent_definition", type: "json" },
|
|
62
|
+
...TENANT_COLUMNS,
|
|
63
|
+
{ name: "created_at", type: "timestamp" },
|
|
64
|
+
{ name: "created_by", type: "text", nullable: true },
|
|
65
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
66
|
+
],
|
|
67
|
+
uniqueKeys: [["name", "version"]],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "prism_sessions",
|
|
71
|
+
primaryKey: ["id"],
|
|
72
|
+
columns: [
|
|
73
|
+
{ name: "id", type: "text" },
|
|
74
|
+
...TENANT_COLUMNS,
|
|
75
|
+
{ name: "parent_session_id", type: "text", nullable: true },
|
|
76
|
+
{ name: "agent_definition_id", type: "text", nullable: true },
|
|
77
|
+
{ name: "agent_definition_version", type: "text", nullable: true },
|
|
78
|
+
{ name: "created_at", type: "timestamp" },
|
|
79
|
+
{ name: "updated_at", type: "timestamp" },
|
|
80
|
+
{ name: "expires_at", type: "timestamp", nullable: true },
|
|
81
|
+
{ name: "retention_policy_id", type: "text", nullable: true },
|
|
82
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "prism_branches",
|
|
87
|
+
primaryKey: ["id"],
|
|
88
|
+
columns: [
|
|
89
|
+
{ name: "id", type: "text" },
|
|
90
|
+
{ name: "session_id", type: "text" },
|
|
91
|
+
{ name: "name", type: "text", nullable: true },
|
|
92
|
+
{ name: "root_entry_id", type: "text", nullable: true },
|
|
93
|
+
{ name: "parent_branch_id", type: "text", nullable: true },
|
|
94
|
+
{ name: "leaf_entry_id", type: "text", nullable: true },
|
|
95
|
+
{ name: "created_at", type: "timestamp" },
|
|
96
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
97
|
+
],
|
|
98
|
+
foreignKeys: [{ columns: ["session_id"], referencesTable: "prism_sessions", referencesColumns: ["id"] }],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "prism_session_entries",
|
|
102
|
+
primaryKey: ["id"],
|
|
103
|
+
columns: [
|
|
104
|
+
{ name: "id", type: "text" },
|
|
105
|
+
{ name: "session_id", type: "text" },
|
|
106
|
+
{ name: "parent_id", type: "text", nullable: true },
|
|
107
|
+
{ name: "run_id", type: "text", nullable: true },
|
|
108
|
+
{ name: "timestamp", type: "timestamp" },
|
|
109
|
+
{ name: "kind", type: "text" },
|
|
110
|
+
{ name: "schema_version", type: "integer" },
|
|
111
|
+
{ name: "message", type: "json", nullable: true },
|
|
112
|
+
{ name: "event", type: "json", nullable: true },
|
|
113
|
+
{ name: "model", type: "json", nullable: true },
|
|
114
|
+
{ name: "previous_model", type: "json", nullable: true },
|
|
115
|
+
{ name: "label", type: "text", nullable: true },
|
|
116
|
+
{ name: "summary", type: "text", nullable: true },
|
|
117
|
+
{ name: "data", type: "json", nullable: true },
|
|
118
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
119
|
+
],
|
|
120
|
+
foreignKeys: [
|
|
121
|
+
{ columns: ["session_id"], referencesTable: "prism_sessions", referencesColumns: ["id"] },
|
|
122
|
+
{ columns: ["parent_id"], referencesTable: "prism_session_entries", referencesColumns: ["id"] },
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "prism_session_append_idempotency",
|
|
127
|
+
primaryKey: ["session_id", "expected_parent_id", "idempotency_key"],
|
|
128
|
+
columns: [
|
|
129
|
+
{ name: "session_id", type: "text" },
|
|
130
|
+
{ name: "expected_parent_id", type: "text" },
|
|
131
|
+
{ name: "idempotency_key", type: "text" },
|
|
132
|
+
{ name: "entry_id", type: "text" },
|
|
133
|
+
{ name: "created_at", type: "timestamp" },
|
|
134
|
+
...TENANT_COLUMNS,
|
|
135
|
+
],
|
|
136
|
+
uniqueKeys: [["session_id", "expected_parent_id", "idempotency_key"]],
|
|
137
|
+
foreignKeys: [{ columns: ["session_id"], referencesTable: "prism_sessions", referencesColumns: ["id"] }],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "prism_runs",
|
|
141
|
+
primaryKey: ["id"],
|
|
142
|
+
columns: [
|
|
143
|
+
{ name: "id", type: "text" },
|
|
144
|
+
{ name: "session_id", type: "text" },
|
|
145
|
+
{ name: "branch_id", type: "text", nullable: true },
|
|
146
|
+
{ name: "agent_definition_id", type: "text", nullable: true },
|
|
147
|
+
{ name: "agent_definition_version", type: "text", nullable: true },
|
|
148
|
+
{ name: "status", type: "text", nullable: true },
|
|
149
|
+
{ name: "started_at", type: "timestamp" },
|
|
150
|
+
{ name: "finished_at", type: "timestamp", nullable: true },
|
|
151
|
+
{ name: "model", type: "json", nullable: true },
|
|
152
|
+
{ name: "provider", type: "text", nullable: true },
|
|
153
|
+
{ name: "idempotency_key", type: "text", nullable: true },
|
|
154
|
+
{ name: "abort_reason", type: "text", nullable: true },
|
|
155
|
+
{ name: "error", type: "json", nullable: true },
|
|
156
|
+
...TENANT_COLUMNS,
|
|
157
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
158
|
+
],
|
|
159
|
+
uniqueKeys: [["tenant_id", "idempotency_key"]],
|
|
160
|
+
foreignKeys: [{ columns: ["session_id"], referencesTable: "prism_sessions", referencesColumns: ["id"] }],
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "prism_agent_events",
|
|
164
|
+
primaryKey: ["id"],
|
|
165
|
+
columns: [
|
|
166
|
+
{ name: "id", type: "text" },
|
|
167
|
+
{ name: "session_id", type: "text" },
|
|
168
|
+
{ name: "run_id", type: "text", nullable: true },
|
|
169
|
+
{ name: "entry_id", type: "text", nullable: true },
|
|
170
|
+
{ name: "sequence", type: "integer" },
|
|
171
|
+
{ name: "type", type: "text" },
|
|
172
|
+
{ name: "timestamp", type: "timestamp" },
|
|
173
|
+
{ name: "event", type: "json" },
|
|
174
|
+
{ name: "redacted", type: "boolean" },
|
|
175
|
+
...TENANT_COLUMNS,
|
|
176
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
177
|
+
],
|
|
178
|
+
foreignKeys: [{ columns: ["session_id"], referencesTable: "prism_sessions", referencesColumns: ["id"] }],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "prism_tool_calls",
|
|
182
|
+
primaryKey: ["id"],
|
|
183
|
+
columns: [
|
|
184
|
+
{ name: "id", type: "text" },
|
|
185
|
+
{ name: "session_id", type: "text" },
|
|
186
|
+
{ name: "run_id", type: "text", nullable: true },
|
|
187
|
+
{ name: "entry_id", type: "text", nullable: true },
|
|
188
|
+
{ name: "tool_call_id", type: "text" },
|
|
189
|
+
{ name: "name", type: "text" },
|
|
190
|
+
{ name: "arguments", type: "json" },
|
|
191
|
+
{ name: "result", type: "json", nullable: true },
|
|
192
|
+
{ name: "status", type: "text", nullable: true },
|
|
193
|
+
{ name: "reason", type: "text", nullable: true },
|
|
194
|
+
{ name: "progress", type: "json", nullable: true },
|
|
195
|
+
{ name: "progress_metadata", type: "json", nullable: true },
|
|
196
|
+
{ name: "progress_at", type: "timestamp", nullable: true },
|
|
197
|
+
{ name: "started_at", type: "timestamp" },
|
|
198
|
+
{ name: "finished_at", type: "timestamp", nullable: true },
|
|
199
|
+
{ name: "redacted", type: "boolean" },
|
|
200
|
+
...TENANT_COLUMNS,
|
|
201
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
202
|
+
],
|
|
203
|
+
foreignKeys: [{ columns: ["session_id"], referencesTable: "prism_sessions", referencesColumns: ["id"] }],
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "prism_usage",
|
|
207
|
+
primaryKey: ["id"],
|
|
208
|
+
columns: [
|
|
209
|
+
{ name: "id", type: "text" },
|
|
210
|
+
{ name: "session_id", type: "text" },
|
|
211
|
+
{ name: "run_id", type: "text", nullable: true },
|
|
212
|
+
{ name: "entry_id", type: "text", nullable: true },
|
|
213
|
+
{ name: "scope", type: "text" },
|
|
214
|
+
{ name: "turn", type: "integer", nullable: true },
|
|
215
|
+
{ name: "attempt", type: "integer", nullable: true },
|
|
216
|
+
{ name: "usage", type: "json" },
|
|
217
|
+
{ name: "recorded_at", type: "timestamp" },
|
|
218
|
+
...TENANT_COLUMNS,
|
|
219
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
220
|
+
],
|
|
221
|
+
foreignKeys: [{ columns: ["session_id"], referencesTable: "prism_sessions", referencesColumns: ["id"] }],
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: "prism_run_feedback",
|
|
225
|
+
primaryKey: ["id"],
|
|
226
|
+
columns: [
|
|
227
|
+
{ name: "id", type: "text" },
|
|
228
|
+
{ name: "run_id", type: "text" },
|
|
229
|
+
{ name: "session_id", type: "text" },
|
|
230
|
+
{ name: "trace_id", type: "text", nullable: true },
|
|
231
|
+
{ name: "rating", type: "number", nullable: true },
|
|
232
|
+
{ name: "comment", type: "text", nullable: true },
|
|
233
|
+
{ name: "tags", type: "json" },
|
|
234
|
+
{ name: "scorer_ids", type: "json" },
|
|
235
|
+
{ name: "evaluation_ids", type: "json" },
|
|
236
|
+
{ name: "created_at", type: "timestamp" },
|
|
237
|
+
{ name: "created_by", type: "text", nullable: true },
|
|
238
|
+
...TENANT_COLUMNS,
|
|
239
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
240
|
+
],
|
|
241
|
+
foreignKeys: [{ columns: ["run_id"], referencesTable: "prism_runs", referencesColumns: ["id"] }],
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "prism_retention_policies",
|
|
245
|
+
primaryKey: ["id"],
|
|
246
|
+
columns: [
|
|
247
|
+
{ name: "id", type: "text" },
|
|
248
|
+
...TENANT_COLUMNS,
|
|
249
|
+
{ name: "name", type: "text", nullable: true },
|
|
250
|
+
{ name: "max_age_days", type: "integer", nullable: true },
|
|
251
|
+
{ name: "max_entries_per_session", type: "integer", nullable: true },
|
|
252
|
+
{ name: "max_total_bytes", type: "integer", nullable: true },
|
|
253
|
+
{ name: "archive_store", type: "text", nullable: true },
|
|
254
|
+
{ name: "applied_kinds", type: "json", nullable: true },
|
|
255
|
+
{ name: "created_at", type: "timestamp" },
|
|
256
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
257
|
+
],
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "prism_migrations",
|
|
261
|
+
primaryKey: ["id"],
|
|
262
|
+
columns: [
|
|
263
|
+
{ name: "id", type: "text" },
|
|
264
|
+
{ name: "name", type: "text" },
|
|
265
|
+
{ name: "version", type: "text" },
|
|
266
|
+
{ name: "applied_at", type: "timestamp" },
|
|
267
|
+
{ name: "applied_by", type: "text", nullable: true },
|
|
268
|
+
{ name: "checksum", type: "text", nullable: true },
|
|
269
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
270
|
+
],
|
|
271
|
+
uniqueKeys: [["name", "version"]],
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
indexes: [
|
|
275
|
+
{ name: "prism_sessions_tenant_created_idx", table: "prism_sessions", columns: ["tenant_id", "account_id", "user_id", "created_at"], purpose: "tenant-scoped session listing" },
|
|
276
|
+
{ name: "prism_sessions_expires_idx", table: "prism_sessions", columns: ["expires_at"], purpose: "retention expiry scans" },
|
|
277
|
+
{ name: "prism_branches_session_name_idx", table: "prism_branches", columns: ["session_id", "name"], purpose: "named branch lookup" },
|
|
278
|
+
{ name: "prism_branches_leaf_idx", table: "prism_branches", columns: ["leaf_entry_id"], purpose: "leaf-to-branch resolution" },
|
|
279
|
+
{ name: "prism_session_entries_session_parent_idx", table: "prism_session_entries", columns: ["session_id", "parent_id"], purpose: "parent existence checks and child lookups" },
|
|
280
|
+
{ name: "prism_session_entries_session_kind_ts_idx", table: "prism_session_entries", columns: ["session_id", "kind", "timestamp"], purpose: "kind-filtered entry listing" },
|
|
281
|
+
{ name: "prism_session_entries_session_run_ts_idx", table: "prism_session_entries", columns: ["session_id", "run_id", "timestamp"], purpose: "run-scoped entry listing" },
|
|
282
|
+
{ name: "prism_session_entries_session_ts_id_idx", table: "prism_session_entries", columns: ["session_id", "timestamp", "id"], purpose: "cursor pagination without full scans" },
|
|
283
|
+
{ name: "prism_session_entries_session_id_idx", table: "prism_session_entries", columns: ["session_id", "id"], purpose: "append parent validation and recursive branch reads" },
|
|
284
|
+
{ name: "prism_session_append_idempotency_unique", table: "prism_session_append_idempotency", columns: ["session_id", "expected_parent_id", "idempotency_key"], unique: true, purpose: "append retry deduplication" },
|
|
285
|
+
{ name: "prism_runs_session_started_idx", table: "prism_runs", columns: ["session_id", "started_at", "id"], purpose: "run history pagination" },
|
|
286
|
+
{ name: "prism_runs_branch_started_idx", table: "prism_runs", columns: ["branch_id", "started_at", "id"], purpose: "branch-scoped runs" },
|
|
287
|
+
{ name: "prism_runs_tenant_idempotency_unique", table: "prism_runs", columns: ["tenant_id", "idempotency_key"], unique: true, purpose: "run-level idempotency deduplication per tenant" },
|
|
288
|
+
{ name: "prism_agent_events_run_sequence_idx", table: "prism_agent_events", columns: ["run_id", "sequence"], purpose: "stable per-run event timeline pagination" },
|
|
289
|
+
{ name: "prism_agent_events_session_ts_id_idx", table: "prism_agent_events", columns: ["session_id", "timestamp", "id"], purpose: "event stream pagination" },
|
|
290
|
+
{ name: "prism_tool_calls_session_name_started_idx", table: "prism_tool_calls", columns: ["session_id", "name", "started_at"], purpose: "tool usage by name" },
|
|
291
|
+
{ name: "prism_tool_calls_run_started_idx", table: "prism_tool_calls", columns: ["run_id", "started_at"], purpose: "run tool-call listing" },
|
|
292
|
+
{ name: "prism_usage_run_recorded_idx", table: "prism_usage", columns: ["run_id", "recorded_at", "id"], purpose: "run usage pagination" },
|
|
293
|
+
{ name: "prism_usage_session_recorded_idx", table: "prism_usage", columns: ["session_id", "recorded_at"], purpose: "usage aggregation" },
|
|
294
|
+
{ name: "prism_usage_session_scope_recorded_idx", table: "prism_usage", columns: ["session_id", "scope", "recorded_at"], purpose: "scope-safe usage aggregation" },
|
|
295
|
+
{ name: "prism_run_feedback_owner_created_idx", table: "prism_run_feedback", columns: ["tenant_id", "account_id", "user_id", "created_at", "id"], purpose: "ownership-scoped feedback pagination" },
|
|
296
|
+
{ name: "prism_run_feedback_run_created_idx", table: "prism_run_feedback", columns: ["run_id", "created_at", "id"], purpose: "run feedback lookup" },
|
|
297
|
+
{ name: "prism_run_feedback_trace_created_idx", table: "prism_run_feedback", columns: ["trace_id", "created_at", "id"], purpose: "trace feedback lookup" },
|
|
298
|
+
{ name: "prism_agent_definitions_name_version_idx", table: "prism_agent_definitions", columns: ["name", "version"], purpose: "definition lookup" },
|
|
299
|
+
{ name: "prism_migrations_name_version_idx", table: "prism_migrations", columns: ["name", "version"], unique: true, purpose: "applied-migration uniqueness" },
|
|
300
|
+
],
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/** Canonical migration contract for production adapters. */
|
|
304
|
+
export function createPersistenceMigrationContract() {
|
|
305
|
+
return {
|
|
306
|
+
targetSchemaVersion: PERSISTENCE_SCHEMA_VERSION,
|
|
307
|
+
appliedMigrationsTable: "prism_migrations",
|
|
308
|
+
steps: [
|
|
309
|
+
{ version: 1, name: "001_init", description: "Create core session, branch, entry, idempotency, run, ledger, and migration tables." },
|
|
310
|
+
{ version: 2, name: "002_usage_scope", description: "Distinguish provider-turn usage from aggregate run totals." },
|
|
311
|
+
{ version: 3, name: "003_run_feedback", description: "Add immutable ownership-scoped run/trace feedback and evaluation links." },
|
|
312
|
+
],
|
|
313
|
+
lockGuidance: "Acquire a dialect-specific migration lock before applying steps (PostgreSQL advisory lock; SQLite exclusive transaction). Only one process should migrate at a time.",
|
|
314
|
+
leastPrivilegeGuidance: "Run migrations with a DDL-capable role; use a separate least-privilege runtime role limited to INSERT/SELECT/UPDATE on adapter tables. Never grant migration credentials to the agent runtime.",
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/** Indexed cursor columns adapters must support for paginated reads. */
|
|
318
|
+
export function getPersistencePaginationCursors() {
|
|
319
|
+
return [
|
|
320
|
+
{ table: "prism_session_entries", columns: ["session_id", "timestamp", "id"], supportsOrder: ["asc", "desc"], purpose: "entry listing and branch paging" },
|
|
321
|
+
{ table: "prism_runs", columns: ["session_id", "started_at", "id"], supportsOrder: ["asc", "desc"], purpose: "run history" },
|
|
322
|
+
{ table: "prism_agent_events", columns: ["run_id", "sequence"], supportsOrder: ["asc", "desc"], purpose: "stable per-run event timeline" },
|
|
323
|
+
{ table: "prism_agent_events", columns: ["session_id", "timestamp", "id"], supportsOrder: ["asc", "desc"], purpose: "session event stream" },
|
|
324
|
+
{ table: "prism_usage", columns: ["run_id", "recorded_at", "id"], supportsOrder: ["asc", "desc"], purpose: "run usage totals" },
|
|
325
|
+
{ table: "prism_tool_calls", columns: ["run_id", "started_at"], supportsOrder: ["asc", "desc"], purpose: "run tool-call listing" },
|
|
326
|
+
{ table: "prism_run_feedback", columns: ["tenant_id", "account_id", "user_id", "created_at", "id"], supportsOrder: ["asc", "desc"], purpose: "owned feedback listing" },
|
|
327
|
+
];
|
|
328
|
+
}
|
|
329
|
+
/** Build a tenant-scoped unique key column list for adapter DDL. */
|
|
330
|
+
export function tenantScopedUniqueKey(baseColumns, tenantColumn = "tenant_id") {
|
|
331
|
+
return [tenantColumn, ...baseColumns];
|
|
332
|
+
}
|
|
333
|
+
/** Assert a schema model includes required tables, tenant boundaries, and indexes. */
|
|
334
|
+
export function assertPersistenceSchemaModel(model) {
|
|
335
|
+
const canonical = createPersistenceSchemaModel();
|
|
336
|
+
if (model.version !== canonical.version) {
|
|
337
|
+
throw new Error(`Persistence schema version mismatch: expected ${canonical.version}, got ${model.version}`);
|
|
338
|
+
}
|
|
339
|
+
const tableNames = new Set(model.tables.map((table) => table.name));
|
|
340
|
+
for (const required of canonical.tables.map((table) => table.name)) {
|
|
341
|
+
if (!tableNames.has(required))
|
|
342
|
+
throw new Error(`Persistence schema missing required table: ${required}`);
|
|
343
|
+
}
|
|
344
|
+
const idempotency = model.tables.find((table) => table.name === "prism_session_append_idempotency");
|
|
345
|
+
if (!idempotency)
|
|
346
|
+
throw new Error("Persistence schema missing idempotency side table");
|
|
347
|
+
if (!idempotency.columns.some((column) => column.name === "tenant_id" && column.tenantScoped)) {
|
|
348
|
+
throw new Error("Idempotency table must include tenant-scoped tenant_id for isolation boundaries");
|
|
349
|
+
}
|
|
350
|
+
const hasIdempotencyUnique = (idempotency.uniqueKeys ?? []).some((key) => key.includes("session_id") && key.includes("expected_parent_id") && key.includes("idempotency_key"));
|
|
351
|
+
if (!hasIdempotencyUnique) {
|
|
352
|
+
throw new Error("Idempotency table must declare unique (session_id, expected_parent_id, idempotency_key)");
|
|
353
|
+
}
|
|
354
|
+
const runs = model.tables.find((table) => table.name === "prism_runs");
|
|
355
|
+
if (!runs?.columns.some((column) => column.name === "tenant_id" && column.tenantScoped)) {
|
|
356
|
+
throw new Error("Runs table must include tenant-scoped tenant_id");
|
|
357
|
+
}
|
|
358
|
+
const indexTables = new Set(model.indexes.map((index) => index.table));
|
|
359
|
+
for (const requiredIndex of ["prism_session_append_idempotency", "prism_session_entries", "prism_agent_events", "prism_runs", "prism_run_feedback"]) {
|
|
360
|
+
if (!indexTables.has(requiredIndex)) {
|
|
361
|
+
throw new Error(`Persistence schema missing indexes for ${requiredIndex}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const paginationTables = new Set(getPersistencePaginationCursors().map((cursor) => cursor.table));
|
|
365
|
+
for (const table of paginationTables) {
|
|
366
|
+
if (!tableNames.has(table))
|
|
367
|
+
throw new Error(`Pagination cursor references missing table: ${table}`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/** Assert migration steps are strictly increasing and end at the target schema version. */
|
|
371
|
+
export function assertPersistenceMigrationContract(contract) {
|
|
372
|
+
if (contract.targetSchemaVersion !== PERSISTENCE_SCHEMA_VERSION) {
|
|
373
|
+
throw new Error(`Migration contract target version must be ${PERSISTENCE_SCHEMA_VERSION}`);
|
|
374
|
+
}
|
|
375
|
+
if (contract.appliedMigrationsTable !== "prism_migrations") {
|
|
376
|
+
throw new Error("Migration contract must record applied steps in prism_migrations");
|
|
377
|
+
}
|
|
378
|
+
if (!contract.lockGuidance.trim() || !contract.leastPrivilegeGuidance.trim()) {
|
|
379
|
+
throw new Error("Migration contract must document lock and least-privilege guidance");
|
|
380
|
+
}
|
|
381
|
+
if (contract.steps.length === 0)
|
|
382
|
+
throw new Error("Migration contract must include at least one step");
|
|
383
|
+
let previous = 0;
|
|
384
|
+
const names = new Set();
|
|
385
|
+
for (const step of contract.steps) {
|
|
386
|
+
if (step.version <= previous)
|
|
387
|
+
throw new Error(`Migration steps must be strictly increasing; ${step.name} is out of order`);
|
|
388
|
+
if (names.has(step.name))
|
|
389
|
+
throw new Error(`Duplicate migration step name: ${step.name}`);
|
|
390
|
+
names.add(step.name);
|
|
391
|
+
previous = step.version;
|
|
392
|
+
}
|
|
393
|
+
if (previous !== contract.targetSchemaVersion) {
|
|
394
|
+
throw new Error(`Last migration step version ${previous} must equal targetSchemaVersion ${contract.targetSchemaVersion}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Assert a dialect-local adapter exposes the canonical table and index names.
|
|
399
|
+
* Adapters pass the table/index names their migration runner created.
|
|
400
|
+
*/
|
|
401
|
+
export function assertAdapterSchemaMatchesModel(adapterTables, adapterIndexes, model = createPersistenceSchemaModel()) {
|
|
402
|
+
assertPersistenceSchemaModel(model);
|
|
403
|
+
const tables = new Set(adapterTables);
|
|
404
|
+
for (const table of model.tables.map((item) => item.name)) {
|
|
405
|
+
if (!tables.has(table))
|
|
406
|
+
throw new Error(`Adapter schema missing table ${table}`);
|
|
407
|
+
}
|
|
408
|
+
for (const index of model.indexes) {
|
|
409
|
+
if (!adapterIndexes.includes(index.name)) {
|
|
410
|
+
throw new Error(`Adapter schema missing required index ${index.name}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/** Guard adapter SQL tests: reject obvious value interpolation into statement text. */
|
|
415
|
+
export function assertParameterizedQuery(sql, boundValues) {
|
|
416
|
+
for (const value of boundValues) {
|
|
417
|
+
if (typeof value !== "string" || value.length === 0)
|
|
418
|
+
continue;
|
|
419
|
+
if (sql.includes(value)) {
|
|
420
|
+
throw new Error("SQL statement appears to interpolate a bound string value; use parameters instead");
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/** Simulate migration up + reopen: applied steps must match the contract in order. */
|
|
425
|
+
export function assertMigrationUpAndReopen(contract, appliedAfterUp, appliedAfterReopen) {
|
|
426
|
+
assertPersistenceMigrationContract(contract);
|
|
427
|
+
if (appliedAfterUp.length !== contract.steps.length) {
|
|
428
|
+
throw new Error("Migration up did not apply every contract step");
|
|
429
|
+
}
|
|
430
|
+
for (let i = 0; i < contract.steps.length; i++) {
|
|
431
|
+
const step = contract.steps[i];
|
|
432
|
+
const row = appliedAfterUp[i];
|
|
433
|
+
if (row.name !== step.name || row.version !== String(step.version)) {
|
|
434
|
+
throw new Error(`Applied migration row ${i} does not match contract step ${step.name}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (appliedAfterReopen.length !== appliedAfterUp.length) {
|
|
438
|
+
throw new Error("Reopened adapter must not re-apply migrations; applied row count changed");
|
|
439
|
+
}
|
|
440
|
+
for (let i = 0; i < appliedAfterUp.length; i++) {
|
|
441
|
+
if (appliedAfterReopen[i].name !== appliedAfterUp[i].name) {
|
|
442
|
+
throw new Error("Reopened adapter migration history diverged");
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
/** Assert cursor pagination returns stable pages without repeating rows. */
|
|
447
|
+
export async function assertPersistenceQueryPaginationConforms(fixture, sessionId = "pagination-conformance") {
|
|
448
|
+
const entries = [0, 1, 2, 3, 4].map((index) => ({
|
|
449
|
+
id: `page-${index}`,
|
|
450
|
+
sessionId,
|
|
451
|
+
timestamp: `2026-01-01T00:00:0${index}.000Z`,
|
|
452
|
+
kind: "label",
|
|
453
|
+
label: `e${index}`,
|
|
454
|
+
}));
|
|
455
|
+
await fixture.seedEntries(entries);
|
|
456
|
+
const first = await fixture.queryEntries({ sessionId, limit: 2, order: "asc" });
|
|
457
|
+
if (first.items.length !== 2)
|
|
458
|
+
throw new Error("Pagination first page must honor limit");
|
|
459
|
+
if (!first.nextCursor)
|
|
460
|
+
throw new Error("Pagination must return nextCursor when more rows exist");
|
|
461
|
+
const second = await fixture.queryEntries({ sessionId, limit: 2, order: "asc", cursor: first.nextCursor });
|
|
462
|
+
const seen = new Set(first.items.map((row) => row.id));
|
|
463
|
+
for (const row of second.items) {
|
|
464
|
+
if (seen.has(row.id))
|
|
465
|
+
throw new Error("Pagination cursor returned overlapping rows");
|
|
466
|
+
seen.add(row.id);
|
|
467
|
+
}
|
|
468
|
+
if (seen.size < 3)
|
|
469
|
+
throw new Error("Pagination did not advance past the first page");
|
|
470
|
+
}
|
|
471
|
+
/** Assert tenant-filtered queries do not return rows from another tenant. */
|
|
472
|
+
export async function assertTenantScopedQueryIsolation(queryByTenant) {
|
|
473
|
+
const tenantA = await queryByTenant("tenant-a");
|
|
474
|
+
const tenantB = await queryByTenant("tenant-b");
|
|
475
|
+
if (tenantA.some((row) => row.tenantId === "tenant-b")) {
|
|
476
|
+
throw new Error("Tenant-a query leaked tenant-b rows");
|
|
477
|
+
}
|
|
478
|
+
if (tenantB.some((row) => row.tenantId === "tenant-a")) {
|
|
479
|
+
throw new Error("Tenant-b query leaked tenant-a rows");
|
|
480
|
+
}
|
|
481
|
+
const aIds = new Set(tenantA.map((row) => row.id));
|
|
482
|
+
for (const row of tenantB) {
|
|
483
|
+
if (aIds.has(row.id))
|
|
484
|
+
throw new Error("Tenant collision: the same primary id appeared in two tenant queries");
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
//# sourceMappingURL=persistence-schema.js.map
|
|
@@ -107,7 +107,16 @@ function contentBlockCanaries(block) {
|
|
|
107
107
|
case "thinking":
|
|
108
108
|
return block.text ? [block.text] : [];
|
|
109
109
|
case "image":
|
|
110
|
-
return [block.url, block.data, block.mimeType].filter((value) => typeof value === "string" && value.length > 0);
|
|
110
|
+
return [block.url, block.resourceUri, block.data, block.mimeType, block.name].filter((value) => typeof value === "string" && value.length > 0);
|
|
111
|
+
case "audio":
|
|
112
|
+
// Providers map mediaType to wire format tokens (e.g. audio/wav → "wav") and
|
|
113
|
+
// typically omit display names from input_audio payloads; identity canaries are
|
|
114
|
+
// source bytes/refs plus optional transcript text.
|
|
115
|
+
return [block.url, block.resourceUri, block.data, block.transcript].filter((value) => typeof value === "string" && value.length > 0);
|
|
116
|
+
case "file":
|
|
117
|
+
return [block.url, block.resourceUri, block.data, block.mediaType, block.name].filter((value) => typeof value === "string" && value.length > 0);
|
|
118
|
+
case "document":
|
|
119
|
+
return [block.url, block.resourceUri, block.data, block.mediaType, block.name, block.transcript].filter((value) => typeof value === "string" && value.length > 0);
|
|
111
120
|
case "tool_call":
|
|
112
121
|
return [block.id, block.name, ...jsonPrimitives(block.arguments)];
|
|
113
122
|
case "tool_result": {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AgentEventRecord, RunLedger, RunRecord, ToolCallRecord, UsageRecord } from "../contracts.js";
|
|
2
|
+
export interface RunLedgerConformanceFixture {
|
|
3
|
+
readonly ledger: RunLedger;
|
|
4
|
+
readonly readRuns?: () => Promise<readonly RunRecord[]> | readonly RunRecord[];
|
|
5
|
+
readonly readEvents?: () => Promise<readonly AgentEventRecord[]> | readonly AgentEventRecord[];
|
|
6
|
+
readonly readToolCalls?: () => Promise<readonly ToolCallRecord[]> | readonly ToolCallRecord[];
|
|
7
|
+
readonly readUsage?: () => Promise<readonly UsageRecord[]> | readonly UsageRecord[];
|
|
8
|
+
}
|
|
9
|
+
export interface RunLedgerConformanceOptions {
|
|
10
|
+
readonly sessionId?: string;
|
|
11
|
+
readonly runId?: string;
|
|
12
|
+
readonly tenantId?: string;
|
|
13
|
+
readonly accountId?: string;
|
|
14
|
+
readonly userId?: string;
|
|
15
|
+
/** When true, requires read* callbacks and asserts tenant-scoped rows do not leak. */
|
|
16
|
+
readonly exerciseTenantIsolation?: boolean;
|
|
17
|
+
/** When true, factory is invoked twice to assert durable writes survive reopen. */
|
|
18
|
+
readonly exerciseReopen?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export type RunLedgerConformanceFactory = () => RunLedgerConformanceFixture | Promise<RunLedgerConformanceFixture>;
|
|
21
|
+
/**
|
|
22
|
+
* Assert that a `RunLedger` implementation satisfies the write contract:
|
|
23
|
+
* all record kinds round-trip via optional read callbacks, per-run event order
|
|
24
|
+
* is preserved, and tenant-scoped rows store `tenant_id` when
|
|
25
|
+
* `exerciseTenantIsolation` is enabled.
|
|
26
|
+
*/
|
|
27
|
+
export declare function assertRunLedgerConforms(fixture: RunLedgerConformanceFixture, options?: RunLedgerConformanceOptions): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Factory-based conformance entry point for durable adapters. Invokes
|
|
30
|
+
* `assertRunLedgerConforms` against a fresh fixture and optionally reopens via
|
|
31
|
+
* the same factory to assert writes survive process/database reopen.
|
|
32
|
+
*/
|
|
33
|
+
export declare function runRunLedgerConformance(factory: RunLedgerConformanceFactory, options?: RunLedgerConformanceOptions): Promise<void>;
|