@arnilo/prism 0.0.96 → 0.1.1
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 +290 -2
- package/README.md +17 -3
- package/dist/agent-definitions.js +2 -3
- package/dist/agent-event-source.d.ts +11 -0
- package/dist/agent-event-source.js +512 -0
- package/dist/agent-loops.d.ts +5 -0
- package/dist/agent-loops.js +99 -14
- package/dist/agent-run-lifecycle.d.ts +5 -2
- package/dist/agent-run-lifecycle.js +18 -2
- package/dist/agent-run-state.d.ts +27 -1
- package/dist/agent-run-state.js +113 -7
- package/dist/agents.d.ts +3 -1
- package/dist/agents.js +1255 -129
- package/dist/artifacts.d.ts +132 -0
- package/dist/artifacts.js +44 -0
- package/dist/cache-helpers.js +18 -9
- package/dist/checkpoints.d.ts +4 -0
- package/dist/checkpoints.js +17 -9
- package/dist/cli-init.js +3 -7
- package/dist/cli-runner.d.ts +2 -6
- package/dist/cli-runner.js +71 -33
- package/dist/compaction.js +5 -4
- package/dist/config.js +7 -4
- package/dist/content.js +26 -24
- package/dist/context-budget.d.ts +67 -0
- package/dist/context-budget.js +288 -0
- package/dist/contracts.d.ts +590 -8
- package/dist/contracts.js +142 -1
- package/dist/contribution-parsing.js +6 -2
- package/dist/contributions.d.ts +2 -0
- package/dist/contributions.js +3 -0
- package/dist/conversations.d.ts +50 -0
- package/dist/conversations.js +98 -0
- package/dist/credentials.d.ts +22 -2
- package/dist/credentials.js +18 -3
- package/dist/devices.d.ts +94 -0
- package/dist/devices.js +138 -0
- package/dist/event-multiplexer.js +18 -4
- package/dist/extensions.d.ts +18 -1
- package/dist/extensions.js +79 -6
- package/dist/feedback.js +12 -10
- package/dist/guardrails.d.ts +1 -1
- package/dist/guardrails.js +26 -17
- package/dist/identity.d.ts +92 -0
- package/dist/identity.js +265 -0
- package/dist/index.d.ts +94 -72
- package/dist/index.js +48 -36
- package/dist/input.d.ts +10 -1
- package/dist/input.js +152 -52
- package/dist/instruction-injection.d.ts +1 -1
- package/dist/middleware.js +9 -1
- package/dist/models.d.ts +2 -0
- package/dist/models.js +3 -0
- package/dist/node/agent-definitions.js +16 -8
- package/dist/node/contribution-discovery.d.ts +1 -2
- package/dist/node/contribution-discovery.js +3 -3
- package/dist/node/session-store-jsonl.js +13 -7
- package/dist/node/settings.d.ts +1 -1
- package/dist/node/settings.js +1 -1
- package/dist/node/system-project-prompts.js +2 -4
- package/dist/node/trust.js +1 -1
- package/dist/persistence-lifecycle.d.ts +103 -0
- package/dist/persistence-lifecycle.js +202 -0
- package/dist/provider-events.d.ts +1 -0
- package/dist/provider-events.js +6 -1
- package/dist/provider-request-policy.js +3 -4
- package/dist/providers/media.d.ts +1 -1
- package/dist/providers/openai-compatible.d.ts +46 -1
- package/dist/providers/openai-compatible.js +123 -53
- package/dist/providers/openai-primitives.js +10 -7
- package/dist/providers/transport.d.ts +6 -0
- package/dist/providers/transport.js +21 -0
- package/dist/providers.d.ts +2 -0
- package/dist/providers.js +3 -0
- package/dist/redaction.d.ts +1 -0
- package/dist/redaction.js +26 -9
- package/dist/resources.d.ts +2 -2
- package/dist/resources.js +2 -2
- package/dist/retry.d.ts +5 -0
- package/dist/retry.js +8 -1
- package/dist/rpc.js +55 -11
- package/dist/run-ledger.d.ts +6 -0
- package/dist/run-ledger.js +16 -13
- package/dist/run-limits.js +49 -10
- package/dist/secure-agent.js +8 -2
- package/dist/security.js +7 -2
- package/dist/session-stores.d.ts +7 -2
- package/dist/session-stores.js +195 -21
- package/dist/skill-disclosure.d.ts +35 -0
- package/dist/skill-disclosure.js +101 -0
- package/dist/skill-load.d.ts +25 -0
- package/dist/skill-load.js +112 -0
- package/dist/structured-output.d.ts +5 -1
- package/dist/structured-output.js +20 -2
- package/dist/system-prompts.js +7 -2
- package/dist/testing/agent-event-source-conformance.d.ts +4 -0
- package/dist/testing/agent-event-source-conformance.js +54 -0
- package/dist/testing/compaction-conformance.js +5 -1
- package/dist/testing/extension-conformance.js +15 -3
- package/dist/testing/feedback.d.ts +1 -3
- package/dist/testing/feedback.js +1 -1
- package/dist/testing/persistence-schema.d.ts +2 -2
- package/dist/testing/persistence-schema.js +280 -35
- package/dist/testing/provider-conformance.js +3 -3
- package/dist/testing/run-ledger-conformance.js +1 -1
- package/dist/testing/session-store-conformance.d.ts +6 -0
- package/dist/testing/session-store-conformance.js +37 -2
- package/dist/testing/tool-conformance.js +30 -5
- package/dist/testing/tool-effect-store-conformance.d.ts +9 -0
- package/dist/testing/tool-effect-store-conformance.js +85 -0
- package/dist/thinking.js +4 -1
- package/dist/tool-effects.d.ts +15 -0
- package/dist/tool-effects.js +352 -0
- package/dist/tool-result-fold.d.ts +40 -0
- package/dist/tool-result-fold.js +176 -0
- package/dist/tools.d.ts +8 -3
- package/dist/tools.js +248 -13
- package/docs/0.1.0-readiness.md +215 -0
- package/docs/a2a.md +33 -2
- package/docs/acp.md +152 -0
- package/docs/ag-ui-adoption.md +77 -0
- package/docs/ag-ui.md +225 -0
- package/docs/agent-events.md +34 -3
- package/docs/agent-identity.md +144 -0
- package/docs/agent-loops.md +17 -2
- package/docs/agent-session-runtime.md +21 -4
- package/docs/browser-automation.md +5 -0
- package/docs/caveman.md +129 -0
- package/docs/cli-rpc.md +3 -6
- package/docs/coding-agent-tools.md +229 -25
- package/docs/coding-security.md +77 -11
- package/docs/compaction-and-retry.md +5 -2
- package/docs/compaction-llm.md +20 -1
- package/docs/compaction-observational-memory.md +52 -8
- package/docs/context-and-skills.md +94 -7
- package/docs/contribution-registries.md +1 -0
- package/docs/conversations.md +135 -0
- package/docs/credential-storage.md +34 -1
- package/docs/credentials-and-redaction.md +11 -1
- package/docs/database-persistence.md +27 -7
- package/docs/device-adapters.md +97 -0
- package/docs/enterprise-postgres-state.md +178 -0
- package/docs/evaluations.md +14 -1
- package/docs/extensions.md +4 -1
- package/docs/forge-integration.md +113 -0
- package/docs/guardrails.md +16 -2
- package/docs/host-security.md +35 -4
- package/docs/index.md +69 -37
- package/docs/input-and-prompt-assembly.md +8 -7
- package/docs/language-intelligence.md +162 -0
- package/docs/mcp-tools.md +62 -5
- package/docs/middleware-hooks.md +2 -2
- package/docs/migration.md +427 -2
- package/docs/model-routing.md +111 -0
- package/docs/multimodal-content.md +8 -5
- package/docs/node-jsonl-session-store.md +1 -1
- package/docs/observability.md +2 -0
- package/docs/openapi-tools.md +56 -0
- package/docs/performance.md +282 -0
- package/docs/policy-and-audit.md +171 -0
- package/docs/ponytail.md +127 -0
- package/docs/postgres-persistence.md +8 -4
- package/docs/process-sessions.md +147 -0
- package/docs/provider-caching.md +13 -1
- package/docs/provider-conformance.md +29 -5
- package/docs/provider-packages.md +43 -2
- package/docs/provider-request-policies.md +2 -0
- package/docs/providers/ai-sdk.md +24 -7
- package/docs/providers/alibaba.md +179 -0
- package/docs/providers/anthropic.md +93 -0
- package/docs/providers/azure.md +74 -0
- package/docs/providers/bedrock.md +72 -0
- package/docs/providers/google.md +89 -0
- package/docs/providers/ollama.md +166 -0
- package/docs/providers/openai-compatible.md +31 -2
- package/docs/providers/openai.md +24 -5
- package/docs/providers/openrouter.md +2 -0
- package/docs/providers/vertex.md +71 -0
- package/docs/public-contracts.md +68 -4
- package/docs/rag.md +41 -12
- package/docs/release-and-install.md +362 -208
- package/docs/resource-loading.md +3 -0
- package/docs/runs-and-usage.md +3 -0
- package/docs/server.md +44 -6
- package/docs/session-store-conformance.md +2 -0
- package/docs/session-stores.md +41 -2
- package/docs/sqlite-persistence.md +11 -3
- package/docs/structured-output.md +7 -1
- package/docs/supervisors.md +8 -0
- package/docs/tool-effects.md +95 -0
- package/docs/tools.md +5 -0
- package/docs/work-artifacts-and-review.md +102 -0
- package/docs/work-connectors.md +32 -0
- package/docs/work-tools.md +137 -0
- package/docs/workflows.md +6 -0
- package/docs/working-and-semantic-memory.md +40 -7
- package/package.json +30 -7
- package/templates/init/providers.json +22 -0
- package/docs/review-coverage-2026-07-14.md +0 -260
- package/docs/review-coverage-2026-07-15.md +0 -193
- package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
- package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
- package/docs/review-coverage-2026-07-20-phase-4.md +0 -175
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// ponytail: runner-free durable-event contract probe for database adapters.
|
|
2
|
+
/** Assert durable append/page/replay ownership and cursor behavior without a database dependency. */
|
|
3
|
+
export async function assertAgentEventSourceConforms(factory) {
|
|
4
|
+
const source = await factory();
|
|
5
|
+
const ownership = { tenantId: "tenant-a", accountId: "account-a", userId: "user-a" };
|
|
6
|
+
const input = { ownership, sessionId: "session-a", runId: "run-a" };
|
|
7
|
+
const first = event("event-a", "agent_started", input);
|
|
8
|
+
const second = event("event-b", "turn_started", input, "2026-01-01T00:00:01.000Z");
|
|
9
|
+
const storedFirst = await source.append(first);
|
|
10
|
+
const storedSecond = await source.append(second);
|
|
11
|
+
equal(storedFirst.sequence, 1, "first durable event must receive sequence 1");
|
|
12
|
+
equal(storedSecond.sequence, 2, "durable sequence must increase per run");
|
|
13
|
+
equal((await source.append(first)).sequence, 1, "identical duplicate append must be idempotent");
|
|
14
|
+
await rejects(() => source.append({ ...first, timestamp: "2026-01-01T00:00:02.000Z" }), "changed duplicate append must fail");
|
|
15
|
+
const page = await source.page({ ...input, limit: 1 });
|
|
16
|
+
equal(page.items.length, 1, "page limit must be honored");
|
|
17
|
+
equal(page.items[0].record.id, first.id, "page order must follow sequence");
|
|
18
|
+
if (!page.nextCursor)
|
|
19
|
+
throw new Error("truncated page must include a cursor");
|
|
20
|
+
const secondPage = await source.page({ ...input, after: page.nextCursor, limit: 1 });
|
|
21
|
+
equal(secondPage.items[0]?.record.id, second.id, "cursor must be exclusive");
|
|
22
|
+
const iterator = source.subscribe({ ...input, after: secondPage.items[0].cursor })[Symbol.asyncIterator]();
|
|
23
|
+
const pending = iterator.next();
|
|
24
|
+
const third = await source.append(event("event-c", "turn_started", input, "2026-01-01T00:00:02.000Z"));
|
|
25
|
+
equal((await pending).value?.record.id, third.id, "replay/live handoff dropped an event");
|
|
26
|
+
await iterator.return?.();
|
|
27
|
+
const terminal = await source.append(event("event-d", "agent_finished", input, "2026-01-01T00:00:03.000Z"));
|
|
28
|
+
const final = await source.page({ ...input, after: secondPage.items[0].cursor, limit: 10 });
|
|
29
|
+
equal(final.items.at(-1)?.record.id, terminal.id, "terminal page must include its terminal event");
|
|
30
|
+
equal(final.terminal, true, "terminal event must close only after prior events are delivered");
|
|
31
|
+
await rejects(() => source.page({ ...input, ownership: { ...ownership, tenantId: "tenant-b" }, after: page.nextCursor }), "foreign cursor must fail closed");
|
|
32
|
+
await rejects(() => source.append({ ...event("event-unredacted", "turn_started", input), redacted: false }), "unredacted append must fail");
|
|
33
|
+
await rejects(() => source.page({ ...input, limit: 0 }), "invalid page limit must fail");
|
|
34
|
+
}
|
|
35
|
+
function event(id, type, input, timestamp = "2026-01-01T00:00:00.000Z") {
|
|
36
|
+
const event = type === "turn_started"
|
|
37
|
+
? { type, sessionId: input.sessionId, runId: input.runId, turn: 1 }
|
|
38
|
+
: { type, sessionId: input.sessionId, runId: input.runId };
|
|
39
|
+
return { id, ...input.ownership, sessionId: input.sessionId, runId: input.runId, type, timestamp, event, redacted: true };
|
|
40
|
+
}
|
|
41
|
+
function equal(actual, expected, message) {
|
|
42
|
+
if (actual !== expected)
|
|
43
|
+
throw new Error(`${message}; expected ${String(expected)}, received ${String(actual)}`);
|
|
44
|
+
}
|
|
45
|
+
async function rejects(action, message) {
|
|
46
|
+
try {
|
|
47
|
+
await action();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
throw new Error(message);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=agent-event-source-conformance.js.map
|
|
@@ -16,7 +16,11 @@ import { createSessionEntry } from "../session-stores.js";
|
|
|
16
16
|
export async function assertCompactionStrategyConforms(strategy, options = {}) {
|
|
17
17
|
const secrets = options.secrets ?? [];
|
|
18
18
|
const entries = [
|
|
19
|
-
createSessionEntry({
|
|
19
|
+
createSessionEntry({
|
|
20
|
+
sessionId: "s",
|
|
21
|
+
kind: "message",
|
|
22
|
+
message: { role: "user", content: [{ type: "text", text: `old ${secrets[0] ?? "history"}` }] },
|
|
23
|
+
}),
|
|
20
24
|
createSessionEntry({ sessionId: "s", kind: "message", message: { role: "assistant", content: [{ type: "text", text: "recent" }] } }),
|
|
21
25
|
];
|
|
22
26
|
const context = {
|
|
@@ -23,7 +23,12 @@ export async function assertExtensionConforms(extension, options = {}) {
|
|
|
23
23
|
// provider/tool/skill capabilities until host code resolves and calls them).
|
|
24
24
|
if (options.expectThrow) {
|
|
25
25
|
// A second extension that throws must rethrow under errorPolicy: "throw".
|
|
26
|
-
const failing = {
|
|
26
|
+
const failing = {
|
|
27
|
+
name: "conformance-failing",
|
|
28
|
+
setup: () => {
|
|
29
|
+
throw new Error("conformance setup failed");
|
|
30
|
+
},
|
|
31
|
+
};
|
|
27
32
|
let threw = false;
|
|
28
33
|
try {
|
|
29
34
|
await kernel.load([failing]);
|
|
@@ -37,9 +42,16 @@ export async function assertExtensionConforms(extension, options = {}) {
|
|
|
37
42
|
}
|
|
38
43
|
if (options.secrets && options.secrets.length > 0) {
|
|
39
44
|
const secret = options.secrets[0];
|
|
40
|
-
const failing = {
|
|
45
|
+
const failing = {
|
|
46
|
+
name: "conformance-failing",
|
|
47
|
+
setup: () => {
|
|
48
|
+
throw new Error(`boom ${secret}`);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
41
51
|
const errors = [];
|
|
42
|
-
kernel.events.on("extension_error", (event) => {
|
|
52
|
+
kernel.events.on("extension_error", (event) => {
|
|
53
|
+
errors.push(event.error ?? {});
|
|
54
|
+
});
|
|
43
55
|
await kernel.load([failing]);
|
|
44
56
|
if (errors.length === 0)
|
|
45
57
|
throw new Error("Failing extension setup did not emit an extension_error event");
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type { RunFeedbackStore } from "../contracts.js";
|
|
2
|
-
export
|
|
3
|
-
(): RunFeedbackStore | Promise<RunFeedbackStore>;
|
|
4
|
-
}
|
|
2
|
+
export type RunFeedbackConformanceFactory = () => RunFeedbackStore | Promise<RunFeedbackStore>;
|
|
5
3
|
/** Shared minimum behavior for memory and production feedback stores. */
|
|
6
4
|
export declare function runFeedbackConformance(factory: RunFeedbackConformanceFactory): Promise<void>;
|
package/dist/testing/feedback.js
CHANGED
|
@@ -31,7 +31,7 @@ export async function runFeedbackConformance(factory) {
|
|
|
31
31
|
if (await store.delete({ id: "feedback-1", tenantId: "feedback-tenant", userId: "other" })) {
|
|
32
32
|
throw new Error("cross-owner feedback deletion succeeded");
|
|
33
33
|
}
|
|
34
|
-
if (!await store.delete({ id: "feedback-1", ...owner }))
|
|
34
|
+
if (!(await store.delete({ id: "feedback-1", ...owner })))
|
|
35
35
|
throw new Error("owned feedback deletion failed");
|
|
36
36
|
}
|
|
37
37
|
//# sourceMappingURL=feedback.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PersistencePage, SessionEntry, SessionEntryQuery } from "../contracts.js";
|
|
2
2
|
/** Current shared persistence schema version for production database adapters. */
|
|
3
|
-
export declare const PERSISTENCE_SCHEMA_VERSION =
|
|
4
|
-
export type PersistenceTableName = "prism_tenants" | "prism_accounts" | "prism_users" | "prism_agent_definitions" | "prism_sessions" | "prism_branches" | "prism_session_entries" | "prism_session_append_idempotency" | "prism_runs" | "prism_agent_events" | "prism_tool_calls" | "prism_usage" | "prism_run_feedback" | "prism_retention_policies" | "prism_migrations";
|
|
3
|
+
export declare const PERSISTENCE_SCHEMA_VERSION = 7;
|
|
4
|
+
export type PersistenceTableName = "prism_tenants" | "prism_accounts" | "prism_users" | "prism_agent_definitions" | "prism_sessions" | "prism_branches" | "prism_session_entries" | "prism_session_append_idempotency" | "prism_runs" | "prism_agent_events" | "prism_agent_event_streams" | "prism_tool_calls" | "prism_usage" | "prism_run_feedback" | "prism_retention_policies" | "prism_legal_holds" | "prism_tenant_quotas" | "prism_migrations";
|
|
5
5
|
export type PersistenceColumnType = "text" | "integer" | "number" | "boolean" | "json" | "timestamp";
|
|
6
6
|
export interface PersistenceColumnDefinition {
|
|
7
7
|
readonly name: string;
|
|
@@ -4,7 +4,7 @@ import { createHash } from "node:crypto";
|
|
|
4
4
|
// this module defines the shared table/index/pagination/migration expectations
|
|
5
5
|
// adapter authors implement and test against before shipping dialect-specific DDL.
|
|
6
6
|
/** Current shared persistence schema version for production database adapters. */
|
|
7
|
-
export const PERSISTENCE_SCHEMA_VERSION =
|
|
7
|
+
export const PERSISTENCE_SCHEMA_VERSION = 7;
|
|
8
8
|
/** Guidance adapters must follow: values are bound parameters, never interpolated. */
|
|
9
9
|
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.";
|
|
10
10
|
const TENANT_COLUMNS = [
|
|
@@ -177,6 +177,17 @@ export function createPersistenceSchemaModel() {
|
|
|
177
177
|
],
|
|
178
178
|
foreignKeys: [{ columns: ["session_id"], referencesTable: "prism_sessions", referencesColumns: ["id"] }],
|
|
179
179
|
},
|
|
180
|
+
{
|
|
181
|
+
name: "prism_agent_event_streams",
|
|
182
|
+
primaryKey: ["session_id", "run_id"],
|
|
183
|
+
columns: [
|
|
184
|
+
{ name: "session_id", type: "text" },
|
|
185
|
+
{ name: "run_id", type: "text" },
|
|
186
|
+
{ name: "next_sequence", type: "integer" },
|
|
187
|
+
{ name: "updated_at", type: "timestamp" },
|
|
188
|
+
],
|
|
189
|
+
foreignKeys: [{ columns: ["session_id"], referencesTable: "prism_sessions", referencesColumns: ["id"] }],
|
|
190
|
+
},
|
|
180
191
|
{
|
|
181
192
|
name: "prism_tool_calls",
|
|
182
193
|
primaryKey: ["id"],
|
|
@@ -258,6 +269,33 @@ export function createPersistenceSchemaModel() {
|
|
|
258
269
|
{ name: "metadata", type: "json", nullable: true },
|
|
259
270
|
],
|
|
260
271
|
},
|
|
272
|
+
{
|
|
273
|
+
name: "prism_legal_holds",
|
|
274
|
+
primaryKey: ["id"],
|
|
275
|
+
columns: [
|
|
276
|
+
{ name: "id", type: "text" },
|
|
277
|
+
...TENANT_COLUMNS,
|
|
278
|
+
{ name: "resource_kind", type: "text" },
|
|
279
|
+
{ name: "resource_id", type: "text" },
|
|
280
|
+
{ name: "reason", type: "text" },
|
|
281
|
+
{ name: "created_at", type: "timestamp" },
|
|
282
|
+
{ name: "created_by", type: "text", nullable: true },
|
|
283
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: "prism_tenant_quotas",
|
|
288
|
+
primaryKey: ["id"],
|
|
289
|
+
columns: [
|
|
290
|
+
{ name: "id", type: "text" },
|
|
291
|
+
...TENANT_COLUMNS,
|
|
292
|
+
{ name: "resource_kind", type: "text" },
|
|
293
|
+
{ name: "limit_count", type: "integer" },
|
|
294
|
+
{ name: "used_count", type: "integer" },
|
|
295
|
+
{ name: "updated_at", type: "timestamp" },
|
|
296
|
+
],
|
|
297
|
+
uniqueKeys: [["tenant_id", "account_id", "user_id", "resource_kind"]],
|
|
298
|
+
},
|
|
261
299
|
{
|
|
262
300
|
name: "prism_migrations",
|
|
263
301
|
primaryKey: ["id"],
|
|
@@ -274,31 +312,167 @@ export function createPersistenceSchemaModel() {
|
|
|
274
312
|
},
|
|
275
313
|
],
|
|
276
314
|
indexes: [
|
|
277
|
-
{
|
|
315
|
+
{
|
|
316
|
+
name: "prism_sessions_tenant_created_idx",
|
|
317
|
+
table: "prism_sessions",
|
|
318
|
+
columns: ["tenant_id", "account_id", "user_id", "created_at"],
|
|
319
|
+
purpose: "tenant-scoped session listing",
|
|
320
|
+
},
|
|
278
321
|
{ name: "prism_sessions_expires_idx", table: "prism_sessions", columns: ["expires_at"], purpose: "retention expiry scans" },
|
|
279
322
|
{ name: "prism_branches_session_name_idx", table: "prism_branches", columns: ["session_id", "name"], purpose: "named branch lookup" },
|
|
280
323
|
{ name: "prism_branches_leaf_idx", table: "prism_branches", columns: ["leaf_entry_id"], purpose: "leaf-to-branch resolution" },
|
|
281
|
-
{
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
{
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
{
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
{
|
|
300
|
-
|
|
301
|
-
|
|
324
|
+
{
|
|
325
|
+
name: "prism_session_entries_session_parent_idx",
|
|
326
|
+
table: "prism_session_entries",
|
|
327
|
+
columns: ["session_id", "parent_id"],
|
|
328
|
+
purpose: "parent existence checks and child lookups",
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: "prism_session_entries_session_kind_ts_idx",
|
|
332
|
+
table: "prism_session_entries",
|
|
333
|
+
columns: ["session_id", "kind", "timestamp"],
|
|
334
|
+
purpose: "kind-filtered entry listing",
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: "prism_session_entries_session_run_ts_idx",
|
|
338
|
+
table: "prism_session_entries",
|
|
339
|
+
columns: ["session_id", "run_id", "timestamp"],
|
|
340
|
+
purpose: "run-scoped entry listing",
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: "prism_session_entries_session_ts_id_idx",
|
|
344
|
+
table: "prism_session_entries",
|
|
345
|
+
columns: ["session_id", "timestamp", "id"],
|
|
346
|
+
purpose: "cursor pagination without full scans",
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: "prism_session_entries_session_id_idx",
|
|
350
|
+
table: "prism_session_entries",
|
|
351
|
+
columns: ["session_id", "id"],
|
|
352
|
+
purpose: "append parent validation and recursive branch reads",
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: "prism_session_append_idempotency_unique",
|
|
356
|
+
table: "prism_session_append_idempotency",
|
|
357
|
+
columns: ["session_id", "expected_parent_id", "idempotency_key"],
|
|
358
|
+
purpose: "append retry deduplication (primary key enforces uniqueness)",
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: "prism_runs_session_started_idx",
|
|
362
|
+
table: "prism_runs",
|
|
363
|
+
columns: ["session_id", "started_at", "id"],
|
|
364
|
+
purpose: "run history pagination",
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: "prism_runs_branch_started_idx",
|
|
368
|
+
table: "prism_runs",
|
|
369
|
+
columns: ["branch_id", "started_at", "id"],
|
|
370
|
+
purpose: "branch-scoped runs",
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: "prism_runs_tenant_idempotency_unique",
|
|
374
|
+
table: "prism_runs",
|
|
375
|
+
columns: ["tenant_id", "idempotency_key"],
|
|
376
|
+
unique: true,
|
|
377
|
+
purpose: "run-level idempotency deduplication per tenant",
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: "prism_agent_events_run_sequence_idx",
|
|
381
|
+
table: "prism_agent_events",
|
|
382
|
+
columns: ["run_id", "sequence"],
|
|
383
|
+
unique: true,
|
|
384
|
+
purpose: "stable per-run event timeline pagination",
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: "prism_agent_events_session_ts_id_idx",
|
|
388
|
+
table: "prism_agent_events",
|
|
389
|
+
columns: ["session_id", "timestamp", "id"],
|
|
390
|
+
purpose: "event stream pagination",
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: "prism_agent_events_owner_timestamp_sequence_idx",
|
|
394
|
+
table: "prism_agent_events",
|
|
395
|
+
columns: ["tenant_id", "account_id", "user_id", "timestamp", "sequence", "id"],
|
|
396
|
+
purpose: "owned durable event retention cleanup",
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: "prism_tool_calls_session_name_started_idx",
|
|
400
|
+
table: "prism_tool_calls",
|
|
401
|
+
columns: ["session_id", "name", "started_at"],
|
|
402
|
+
purpose: "tool usage by name",
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: "prism_tool_calls_run_started_idx",
|
|
406
|
+
table: "prism_tool_calls",
|
|
407
|
+
columns: ["run_id", "started_at"],
|
|
408
|
+
purpose: "run tool-call listing",
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: "prism_usage_run_recorded_idx",
|
|
412
|
+
table: "prism_usage",
|
|
413
|
+
columns: ["run_id", "recorded_at", "id"],
|
|
414
|
+
purpose: "run usage pagination",
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
name: "prism_usage_session_recorded_idx",
|
|
418
|
+
table: "prism_usage",
|
|
419
|
+
columns: ["session_id", "recorded_at"],
|
|
420
|
+
purpose: "usage aggregation",
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: "prism_usage_session_scope_recorded_idx",
|
|
424
|
+
table: "prism_usage",
|
|
425
|
+
columns: ["session_id", "scope", "recorded_at"],
|
|
426
|
+
purpose: "scope-safe usage aggregation",
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: "prism_run_feedback_owner_created_idx",
|
|
430
|
+
table: "prism_run_feedback",
|
|
431
|
+
columns: ["tenant_id", "account_id", "user_id", "created_at", "id"],
|
|
432
|
+
purpose: "ownership-scoped feedback pagination",
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: "prism_run_feedback_run_created_idx",
|
|
436
|
+
table: "prism_run_feedback",
|
|
437
|
+
columns: ["run_id", "created_at", "id"],
|
|
438
|
+
purpose: "run feedback lookup",
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
name: "prism_run_feedback_trace_created_idx",
|
|
442
|
+
table: "prism_run_feedback",
|
|
443
|
+
columns: ["trace_id", "created_at", "id"],
|
|
444
|
+
purpose: "trace feedback lookup",
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: "prism_agent_definitions_name_version_idx",
|
|
448
|
+
table: "prism_agent_definitions",
|
|
449
|
+
columns: ["name", "version"],
|
|
450
|
+
purpose: "definition lookup",
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
name: "prism_legal_holds_owner_resource_idx",
|
|
454
|
+
table: "prism_legal_holds",
|
|
455
|
+
columns: ["tenant_id", "account_id", "user_id", "resource_kind", "resource_id"],
|
|
456
|
+
purpose: "hold lookup by owned resource",
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
name: "prism_legal_holds_created_id_idx",
|
|
460
|
+
table: "prism_legal_holds",
|
|
461
|
+
columns: ["created_at", "id"],
|
|
462
|
+
purpose: "hold export pagination",
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
name: "prism_tenant_quotas_owner_kind_idx",
|
|
466
|
+
table: "prism_tenant_quotas",
|
|
467
|
+
columns: ["tenant_id", "account_id", "user_id", "resource_kind"],
|
|
468
|
+
purpose: "tenant quota lookup",
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
name: "prism_migrations_name_version_idx",
|
|
472
|
+
table: "prism_migrations",
|
|
473
|
+
columns: ["name", "version"],
|
|
474
|
+
purpose: "applied-migration lookup (table constraint enforces uniqueness)",
|
|
475
|
+
},
|
|
302
476
|
],
|
|
303
477
|
};
|
|
304
478
|
}
|
|
@@ -307,15 +481,51 @@ function migrationStep(version, name, description) {
|
|
|
307
481
|
const content = version === 1
|
|
308
482
|
? {
|
|
309
483
|
tables: model.tables
|
|
310
|
-
.filter((table) => table.name !== "prism_run_feedback")
|
|
484
|
+
.filter((table) => table.name !== "prism_run_feedback" && table.name !== "prism_agent_event_streams")
|
|
311
485
|
.map((table) => table.name === "prism_usage"
|
|
312
486
|
? { ...table, columns: table.columns.filter((column) => !["scope", "turn", "attempt"].includes(column.name)) }
|
|
313
487
|
: table),
|
|
314
|
-
indexes: model.indexes
|
|
488
|
+
indexes: model.indexes
|
|
489
|
+
.filter((index) => !index.name.startsWith("prism_usage_session_scope_") &&
|
|
490
|
+
!index.name.startsWith("prism_run_feedback_") &&
|
|
491
|
+
!index.name.startsWith("prism_agent_event_streams_") &&
|
|
492
|
+
!index.name.startsWith("prism_agent_events_owner_timestamp_"))
|
|
493
|
+
.map((index) => index.name === "prism_agent_events_run_sequence_idx"
|
|
494
|
+
? (() => {
|
|
495
|
+
const { unique: _unique, ...legacy } = index;
|
|
496
|
+
return legacy;
|
|
497
|
+
})()
|
|
498
|
+
: index),
|
|
315
499
|
}
|
|
316
|
-
: version ===
|
|
317
|
-
? {
|
|
318
|
-
:
|
|
500
|
+
: version === 7
|
|
501
|
+
? { indexes: ["prism_agent_events_owner_timestamp_sequence_idx"] }
|
|
502
|
+
: version === 6
|
|
503
|
+
? {
|
|
504
|
+
tables: ["prism_agent_event_streams"],
|
|
505
|
+
indexes: ["prism_agent_events_run_sequence_idx"],
|
|
506
|
+
}
|
|
507
|
+
: version === 2
|
|
508
|
+
? { table: "prism_usage", columns: ["scope", "turn", "attempt"], indexes: ["prism_usage_session_scope_recorded_idx"] }
|
|
509
|
+
: version === 3
|
|
510
|
+
? {
|
|
511
|
+
tables: ["prism_run_feedback"],
|
|
512
|
+
indexes: model.indexes.filter((index) => index.name.startsWith("prism_run_feedback_")).map((index) => index.name),
|
|
513
|
+
}
|
|
514
|
+
: version === 4
|
|
515
|
+
? // Adapter-local FTS objects (SQLite FTS5 / Postgres tsvector) map to this canonical name.
|
|
516
|
+
{ search: ["prism_session_search"], indexes: ["prism_sessions_updated_id_idx"] }
|
|
517
|
+
: version === 5
|
|
518
|
+
? {
|
|
519
|
+
tables: ["prism_legal_holds", "prism_tenant_quotas"],
|
|
520
|
+
indexes: [
|
|
521
|
+
"prism_legal_holds_owner_resource_idx",
|
|
522
|
+
"prism_legal_holds_created_id_idx",
|
|
523
|
+
"prism_tenant_quotas_owner_kind_idx",
|
|
524
|
+
],
|
|
525
|
+
}
|
|
526
|
+
: (() => {
|
|
527
|
+
throw new Error(`Unknown migration version ${version}`);
|
|
528
|
+
})();
|
|
319
529
|
return {
|
|
320
530
|
version,
|
|
321
531
|
name,
|
|
@@ -332,6 +542,10 @@ export function createPersistenceMigrationContract() {
|
|
|
332
542
|
migrationStep(1, "001_init", "Create core session, branch, entry, idempotency, run, ledger, and migration tables."),
|
|
333
543
|
migrationStep(2, "002_usage_scope", "Distinguish provider-turn usage from aggregate run totals."),
|
|
334
544
|
migrationStep(3, "003_run_feedback", "Add immutable ownership-scoped run/trace feedback and evaluation links."),
|
|
545
|
+
migrationStep(4, "004_session_search", "Add bounded session search indexes and adapter-local FTS objects."),
|
|
546
|
+
migrationStep(5, "005_lifecycle_hold_quota", "Add legal-hold and tenant-quota tables for retention lifecycle."),
|
|
547
|
+
migrationStep(6, "006_agent_event_source", "Add transactional per-run event counters and unique durable event sequencing."),
|
|
548
|
+
migrationStep(7, "007_agent_event_retention_index", "Add an exact-owner durable-event retention cleanup index."),
|
|
335
549
|
],
|
|
336
550
|
lockGuidance: "Acquire a dialect-specific migration lock before applying steps (PostgreSQL advisory lock; SQLite exclusive transaction). Only one process should migrate at a time.",
|
|
337
551
|
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.",
|
|
@@ -340,13 +554,33 @@ export function createPersistenceMigrationContract() {
|
|
|
340
554
|
/** Indexed cursor columns adapters must support for paginated reads. */
|
|
341
555
|
export function getPersistencePaginationCursors() {
|
|
342
556
|
return [
|
|
343
|
-
{
|
|
557
|
+
{
|
|
558
|
+
table: "prism_session_entries",
|
|
559
|
+
columns: ["session_id", "timestamp", "id"],
|
|
560
|
+
supportsOrder: ["asc", "desc"],
|
|
561
|
+
purpose: "entry listing and branch paging",
|
|
562
|
+
},
|
|
344
563
|
{ table: "prism_runs", columns: ["session_id", "started_at", "id"], supportsOrder: ["asc", "desc"], purpose: "run history" },
|
|
345
|
-
{
|
|
346
|
-
|
|
564
|
+
{
|
|
565
|
+
table: "prism_agent_events",
|
|
566
|
+
columns: ["run_id", "sequence"],
|
|
567
|
+
supportsOrder: ["asc", "desc"],
|
|
568
|
+
purpose: "stable per-run event timeline",
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
table: "prism_agent_events",
|
|
572
|
+
columns: ["session_id", "timestamp", "id"],
|
|
573
|
+
supportsOrder: ["asc", "desc"],
|
|
574
|
+
purpose: "session event stream",
|
|
575
|
+
},
|
|
347
576
|
{ table: "prism_usage", columns: ["run_id", "recorded_at", "id"], supportsOrder: ["asc", "desc"], purpose: "run usage totals" },
|
|
348
577
|
{ table: "prism_tool_calls", columns: ["run_id", "started_at"], supportsOrder: ["asc", "desc"], purpose: "run tool-call listing" },
|
|
349
|
-
{
|
|
578
|
+
{
|
|
579
|
+
table: "prism_run_feedback",
|
|
580
|
+
columns: ["tenant_id", "account_id", "user_id", "created_at", "id"],
|
|
581
|
+
supportsOrder: ["asc", "desc"],
|
|
582
|
+
purpose: "owned feedback listing",
|
|
583
|
+
},
|
|
350
584
|
];
|
|
351
585
|
}
|
|
352
586
|
/** Build a tenant-scoped unique key column list for adapter DDL. */
|
|
@@ -379,7 +613,13 @@ export function assertPersistenceSchemaModel(model) {
|
|
|
379
613
|
throw new Error("Runs table must include tenant-scoped tenant_id");
|
|
380
614
|
}
|
|
381
615
|
const indexTables = new Set(model.indexes.map((index) => index.table));
|
|
382
|
-
for (const requiredIndex of [
|
|
616
|
+
for (const requiredIndex of [
|
|
617
|
+
"prism_session_append_idempotency",
|
|
618
|
+
"prism_session_entries",
|
|
619
|
+
"prism_agent_events",
|
|
620
|
+
"prism_runs",
|
|
621
|
+
"prism_run_feedback",
|
|
622
|
+
]) {
|
|
383
623
|
if (!indexTables.has(requiredIndex)) {
|
|
384
624
|
throw new Error(`Persistence schema missing indexes for ${requiredIndex}`);
|
|
385
625
|
}
|
|
@@ -423,7 +663,10 @@ function schemaKey(columns) {
|
|
|
423
663
|
return columns.join("\u0000");
|
|
424
664
|
}
|
|
425
665
|
function normalizedDefault(value) {
|
|
426
|
-
return value
|
|
666
|
+
return value
|
|
667
|
+
?.trim()
|
|
668
|
+
.toLowerCase()
|
|
669
|
+
.replace(/::[a-z_ ]+$/, "");
|
|
427
670
|
}
|
|
428
671
|
function compatibleColumnType(dialect, actual, expected) {
|
|
429
672
|
const type = actual.trim().toUpperCase();
|
|
@@ -488,7 +731,9 @@ export function assertPersistenceSchemaShape(shape, dialect, model = createPersi
|
|
|
488
731
|
const actual = actualIndexes.get(expected.name);
|
|
489
732
|
if (!actual)
|
|
490
733
|
throw new Error(`Persistence schema missing required index ${expected.name}`);
|
|
491
|
-
if (actual.table !== expected.table ||
|
|
734
|
+
if (actual.table !== expected.table ||
|
|
735
|
+
schemaKey(actual.columns) !== schemaKey(expected.columns) ||
|
|
736
|
+
actual.unique !== (expected.unique === true)) {
|
|
492
737
|
throw new Error(`Persistence schema index ${expected.name} has incompatible definition`);
|
|
493
738
|
}
|
|
494
739
|
}
|
|
@@ -73,7 +73,7 @@ export function assertProviderOwnedHeadersWin(captured, options) {
|
|
|
73
73
|
throw new Error(`Caller header overrode provider-owned "${name}": expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
|
74
74
|
}
|
|
75
75
|
for (const [name, callerValue] of Object.entries(options.caller)) {
|
|
76
|
-
if (Object.
|
|
76
|
+
if (Object.hasOwn(ownedLower, name.toLowerCase()))
|
|
77
77
|
continue;
|
|
78
78
|
const actual = captured.get(name);
|
|
79
79
|
if (actual !== callerValue)
|
|
@@ -90,7 +90,7 @@ export function assertNoSecretLeak(events, secrets) {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
export function assertUsageAccounting(events, expected) {
|
|
93
|
-
const usage = [...events].reverse().find((event) => event.type === "done" && event.usage || event.type === "usage");
|
|
93
|
+
const usage = [...events].reverse().find((event) => (event.type === "done" && event.usage) || event.type === "usage");
|
|
94
94
|
const actual = usage?.type === "usage" ? usage.usage : usage?.usage;
|
|
95
95
|
if (!actual)
|
|
96
96
|
throw new Error("Provider stream did not include usage");
|
|
@@ -155,6 +155,6 @@ function jsonPrimitives(value) {
|
|
|
155
155
|
return primitives;
|
|
156
156
|
}
|
|
157
157
|
function textFrom(events) {
|
|
158
|
-
return events.map((event) => event.type === "content_delta" && event.content.type === "text" ? event.content.text : "").join("");
|
|
158
|
+
return events.map((event) => (event.type === "content_delta" && event.content.type === "text" ? event.content.text : "")).join("");
|
|
159
159
|
}
|
|
160
160
|
//# sourceMappingURL=provider-conformance.js.map
|
|
@@ -138,7 +138,7 @@ export async function assertRunLedgerConforms(fixture, options = {}) {
|
|
|
138
138
|
await fixture.ledger.appendRun(otherRun);
|
|
139
139
|
const runs = await fixture.readRuns();
|
|
140
140
|
const stored = runs.find((row) => row.id === "run-tenant-b");
|
|
141
|
-
if (
|
|
141
|
+
if (stored?.tenantId !== "tenant-b") {
|
|
142
142
|
throw new Error("RunLedger must persist tenant_id on records for tenant-scoped isolation");
|
|
143
143
|
}
|
|
144
144
|
const scoped = runs.filter((row) => row.tenantId === tenantId);
|
|
@@ -10,6 +10,12 @@ export interface SessionStoreConformanceOptions {
|
|
|
10
10
|
* when the store does not implement `readBranchPath`.
|
|
11
11
|
*/
|
|
12
12
|
readonly exerciseReadBranchPath?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* When true, exercises optional `searchSessions` (empty page, limit cap,
|
|
15
|
+
* invalid limit/query rejection via `resolveSessionSearchQuery` semantics).
|
|
16
|
+
* Skipped when the store does not implement `searchSessions`.
|
|
17
|
+
*/
|
|
18
|
+
readonly exerciseSearchSessions?: boolean;
|
|
13
19
|
/** When true, appends concurrent children of the same parent (fork allowed). */
|
|
14
20
|
readonly exerciseConcurrentParentAppend?: boolean;
|
|
15
21
|
/**
|