@retinue/agentkit 0.1.0 → 0.3.0
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/README.md +59 -277
- package/dist/adapters/audio/openai.d.ts +36 -0
- package/dist/adapters/audio/openai.js +136 -0
- package/dist/adapters/bullmq/lock.d.ts +26 -2
- package/dist/adapters/embeddings/openai.d.ts +45 -0
- package/dist/adapters/embeddings/openai.js +109 -0
- package/dist/adapters/memory/connections.d.ts +10 -0
- package/dist/adapters/memory/connections.js +87 -0
- package/dist/adapters/memory/graph.d.ts +17 -0
- package/dist/adapters/memory/graph.js +267 -0
- package/dist/adapters/memory/index.d.ts +3 -0
- package/dist/adapters/memory/index.js +3 -0
- package/dist/adapters/memory/rate-limit.d.ts +14 -0
- package/dist/adapters/memory/rate-limit.js +28 -0
- package/dist/adapters/memory/runtime.js +0 -0
- package/dist/adapters/postgres/connections.d.ts +15 -0
- package/dist/adapters/postgres/connections.js +134 -0
- package/dist/adapters/postgres/graph.d.ts +24 -0
- package/dist/adapters/postgres/graph.js +325 -0
- package/dist/adapters/postgres/index.d.ts +2 -0
- package/dist/adapters/postgres/index.js +2 -0
- package/dist/adapters/postgres/migrations.js +198 -0
- package/dist/adapters/postgres/run-store.js +26 -2
- package/dist/adapters/postgres/schema.d.ts +61 -2
- package/dist/adapters/postgres/schema.js +95 -10
- package/dist/adapters/redis/index.d.ts +1 -0
- package/dist/adapters/redis/index.js +1 -0
- package/dist/adapters/redis/rate-limit.d.ts +46 -0
- package/dist/adapters/redis/rate-limit.js +55 -0
- package/dist/adapters/supabase/index.d.ts +15 -0
- package/dist/adapters/supabase/index.js +15 -0
- package/dist/adapters/supabase/rls.js +33 -0
- package/dist/agents/agent.d.ts +99 -1
- package/dist/agents/agent.js +215 -16
- package/dist/agents/define.js +24 -4
- package/dist/agents/engine.d.ts +48 -0
- package/dist/agents/engine.js +370 -13
- package/dist/audio/index.d.ts +151 -0
- package/dist/audio/index.js +183 -0
- package/dist/capabilities/index.d.ts +5 -1
- package/dist/capabilities/index.js +23 -0
- package/dist/capabilities/runtime.d.ts +8 -0
- package/dist/connections/cipher.d.ts +103 -0
- package/dist/connections/cipher.js +141 -0
- package/dist/connections/index.d.ts +156 -0
- package/dist/connections/index.js +72 -0
- package/dist/connections/oauth/client.d.ts +84 -0
- package/dist/connections/oauth/client.js +141 -0
- package/dist/connections/oauth/index.d.ts +166 -0
- package/dist/connections/oauth/index.js +226 -0
- package/dist/connections/oauth/service.d.ts +90 -0
- package/dist/connections/oauth/service.js +132 -0
- package/dist/connections/pause.d.ts +68 -0
- package/dist/connections/pause.js +97 -0
- package/dist/connections/resolver.d.ts +59 -0
- package/dist/connections/resolver.js +138 -0
- package/dist/connections/resume.d.ts +64 -0
- package/dist/connections/resume.js +99 -0
- package/dist/connections/vault-cipher.d.ts +95 -0
- package/dist/connections/vault-cipher.js +206 -0
- package/dist/core/budget.d.ts +55 -0
- package/dist/core/budget.js +56 -0
- package/dist/core/content-parts.d.ts +26 -2
- package/dist/core/content-parts.js +1 -0
- package/dist/core/context.d.ts +25 -0
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.js +15 -0
- package/dist/core/events.d.ts +110 -2
- package/dist/core/events.js +33 -2
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/validation.js +19 -0
- package/dist/documents/index.d.ts +14 -0
- package/dist/documents/parsers/text.d.ts +16 -0
- package/dist/documents/parsers/text.js +54 -2
- package/dist/entries/connections.d.ts +10 -0
- package/dist/entries/connections.js +10 -0
- package/dist/entries/guardrails.d.ts +14 -0
- package/dist/entries/guardrails.js +14 -0
- package/dist/entries/knowledge.d.ts +17 -0
- package/dist/entries/knowledge.js +16 -0
- package/dist/entries/mcp-server.d.ts +9 -0
- package/dist/entries/mcp-server.js +9 -0
- package/dist/entries/runtime.d.ts +10 -0
- package/dist/entries/runtime.js +8 -0
- package/dist/entries/testing.d.ts +28 -0
- package/dist/entries/testing.js +28 -0
- package/dist/graphql/resolvers.d.ts +7 -1
- package/dist/graphql/resolvers.js +20 -0
- package/dist/graphql/schema.d.ts +1 -1
- package/dist/graphql/schema.js +44 -0
- package/dist/guardrails/index.d.ts +115 -0
- package/dist/guardrails/index.js +108 -0
- package/dist/guardrails/moderation.d.ts +53 -0
- package/dist/guardrails/moderation.js +75 -0
- package/dist/guardrails/pii.d.ts +75 -0
- package/dist/guardrails/pii.js +193 -0
- package/dist/knowledge/communities.d.ts +166 -0
- package/dist/knowledge/communities.js +377 -0
- package/dist/knowledge/graph-global.d.ts +153 -0
- package/dist/knowledge/graph-global.js +246 -0
- package/dist/knowledge/graph-retrieval.d.ts +150 -0
- package/dist/knowledge/graph-retrieval.js +303 -0
- package/dist/knowledge/graph.d.ts +223 -0
- package/dist/knowledge/graph.js +369 -0
- package/dist/knowledge/index.d.ts +25 -0
- package/dist/knowledge/index.js +37 -1
- package/dist/knowledge/navigate.d.ts +89 -0
- package/dist/knowledge/navigate.js +107 -0
- package/dist/knowledge/retrieval.d.ts +98 -5
- package/dist/knowledge/retrieval.js +158 -28
- package/dist/mcp-server/index.d.ts +168 -0
- package/dist/mcp-server/index.js +175 -0
- package/dist/models/extraction.d.ts +73 -0
- package/dist/models/extraction.js +118 -0
- package/dist/models/index.d.ts +40 -1
- package/dist/models/index.js +22 -1
- package/dist/models/pricing.d.ts +2 -0
- package/dist/models/pricing.js +17 -1
- package/dist/models/provider-factory.js +9 -7
- package/dist/models/streaming.d.ts +111 -1
- package/dist/models/streaming.js +184 -3
- package/dist/persistence/index.d.ts +230 -0
- package/dist/persistence/index.js +2 -0
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +12 -0
- package/dist/runtime/worker.d.ts +36 -1
- package/dist/runtime/worker.js +37 -4
- package/dist/security/checklist.js +55 -0
- package/dist/security/findings.js +18 -9
- package/dist/server/bin.d.ts +19 -0
- package/dist/server/bin.js +226 -0
- package/dist/server/boot.d.ts +20 -2
- package/dist/server/boot.js +29 -3
- package/dist/server/cli.d.ts +13 -0
- package/dist/server/cli.js +7 -4
- package/dist/server/doctor.d.ts +74 -0
- package/dist/server/doctor.js +280 -0
- package/dist/skills/catalogue.d.ts +49 -0
- package/dist/skills/catalogue.js +61 -0
- package/dist/skills/context.d.ts +61 -0
- package/dist/skills/context.js +107 -0
- package/dist/skills/index.d.ts +2 -0
- package/dist/skills/index.js +2 -0
- package/dist/telemetry/spans.js +17 -0
- package/dist/testing/conformance/artifact-exports.d.ts +20 -0
- package/dist/testing/conformance/artifact-exports.js +172 -0
- package/dist/testing/conformance/artifacts.d.ts +11 -0
- package/dist/testing/conformance/artifacts.js +316 -0
- package/dist/testing/conformance/audio.d.ts +38 -0
- package/dist/testing/conformance/audio.js +145 -0
- package/dist/testing/conformance/capability.d.ts +23 -0
- package/dist/testing/conformance/capability.js +27 -0
- package/dist/testing/conformance/checkpoint-store.d.ts +15 -0
- package/dist/testing/conformance/checkpoint-store.js +72 -0
- package/dist/testing/conformance/connections.d.ts +14 -0
- package/dist/testing/conformance/connections.js +171 -0
- package/dist/testing/conformance/conversation-store.d.ts +10 -0
- package/dist/testing/conformance/conversation-store.js +60 -0
- package/dist/testing/conformance/evaluation.d.ts +10 -0
- package/dist/testing/conformance/evaluation.js +295 -0
- package/dist/testing/conformance/files.d.ts +19 -0
- package/dist/testing/conformance/files.js +454 -0
- package/dist/testing/conformance/flows.d.ts +16 -0
- package/dist/testing/conformance/flows.js +193 -0
- package/dist/testing/conformance/graph.d.ts +22 -0
- package/dist/testing/conformance/graph.js +500 -0
- package/dist/testing/conformance/hitl.d.ts +25 -0
- package/dist/testing/conformance/hitl.js +523 -0
- package/dist/testing/conformance/index.d.ts +163 -0
- package/dist/testing/conformance/index.js +321 -0
- package/dist/testing/conformance/invariants.d.ts +23 -0
- package/dist/testing/conformance/invariants.js +80 -0
- package/dist/testing/conformance/knowledge.d.ts +41 -0
- package/dist/testing/conformance/knowledge.js +592 -0
- package/dist/testing/conformance/parents.d.ts +50 -0
- package/dist/testing/conformance/parents.js +39 -0
- package/dist/testing/conformance/rate-limit.d.ts +25 -0
- package/dist/testing/conformance/rate-limit.js +71 -0
- package/dist/testing/conformance/records.d.ts +36 -0
- package/dist/testing/conformance/records.js +400 -0
- package/dist/testing/conformance/rollups.d.ts +30 -0
- package/dist/testing/conformance/rollups.js +425 -0
- package/dist/testing/conformance/run-coordinator.d.ts +27 -0
- package/dist/testing/conformance/run-coordinator.js +150 -0
- package/dist/testing/conformance/run-event-log.d.ts +9 -0
- package/dist/testing/conformance/run-event-log.js +138 -0
- package/dist/testing/conformance/run-store.d.ts +12 -0
- package/dist/testing/conformance/run-store.js +294 -0
- package/dist/testing/conformance/session-state.d.ts +38 -0
- package/dist/testing/conformance/session-state.js +198 -0
- package/dist/testing/conformance/usage-limits.d.ts +10 -0
- package/dist/testing/conformance/usage-limits.js +309 -0
- package/dist/testing/conformance.d.ts +8 -0
- package/dist/testing/conformance.js +8 -0
- package/dist/testing/memory-backend.d.ts +39 -0
- package/dist/testing/memory-backend.js +41 -0
- package/dist/testing/pglite.d.ts +55 -0
- package/dist/testing/pglite.js +78 -0
- package/dist/testing/stub-model.d.ts +58 -0
- package/dist/testing/stub-model.js +71 -0
- package/dist/testing/supabase-storage-double.d.ts +35 -0
- package/dist/testing/supabase-storage-double.js +101 -0
- package/dist/toolkit/files.d.ts +125 -0
- package/dist/toolkit/files.js +320 -0
- package/dist/toolkit/http.d.ts +27 -0
- package/dist/toolkit/http.js +48 -0
- package/dist/toolkit/index.d.ts +31 -1
- package/dist/toolkit/index.js +26 -1
- package/dist/toolkit/mime.d.ts +97 -0
- package/dist/toolkit/mime.js +233 -0
- package/dist/toolkit/sandbox.d.ts +119 -0
- package/dist/toolkit/sandbox.js +239 -0
- package/dist/toolkit/ssrf.d.ts +139 -0
- package/dist/toolkit/ssrf.js +349 -0
- package/dist/toolkit/vendor.d.ts +91 -0
- package/dist/toolkit/vendor.js +153 -0
- package/dist/toolkit/web.d.ts +13 -0
- package/dist/toolkit/web.js +7 -1
- package/dist/tools/budget.d.ts +28 -0
- package/dist/tools/budget.js +35 -0
- package/dist/tools/credentials.d.ts +316 -0
- package/dist/tools/credentials.js +369 -0
- package/dist/tools/define.d.ts +45 -0
- package/dist/tools/define.js +26 -0
- package/dist/tools/find.d.ts +109 -0
- package/dist/tools/find.js +210 -0
- package/dist/tools/index.d.ts +35 -2
- package/dist/tools/index.js +11 -0
- package/dist/tools/library/audio.d.ts +63 -0
- package/dist/tools/library/audio.js +148 -0
- package/dist/tools/library/fs.d.ts +24 -0
- package/dist/tools/library/fs.js +102 -0
- package/dist/tools/library/index.d.ts +37 -2
- package/dist/tools/library/index.js +47 -0
- package/dist/tools/library/shell.d.ts +45 -0
- package/dist/tools/library/shell.js +70 -0
- package/dist/tools/meta-tools.js +8 -0
- package/dist/tools/registry.d.ts +146 -0
- package/dist/tools/registry.js +250 -4
- package/dist/usage/index.d.ts +21 -0
- package/dist/usage/index.js +1 -0
- package/dist/usage/rate-limit.d.ts +122 -0
- package/dist/usage/rate-limit.js +131 -0
- package/dist/usage/recorder.js +3 -0
- package/package.json +33 -2
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `RunEventLog` conformance — `docs/04` → reconnect. This log is the catch-up half of streaming and
|
|
3
|
+
* the input worker recovery reconciles against, so ordering must be exact and gap-free on every
|
|
4
|
+
* adapter. A missing or reordered event here becomes a client that renders duplicated or lost
|
|
5
|
+
* output after a refresh.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, expect, it } from "vitest";
|
|
8
|
+
import { asId } from "../../core/ids.js";
|
|
9
|
+
const T1 = asId("conf-tenant-1");
|
|
10
|
+
const T2 = asId("conf-tenant-2");
|
|
11
|
+
const RUN = asId("conf-run-1");
|
|
12
|
+
const OTHER = asId("conf-run-2");
|
|
13
|
+
const lifecycle = (runId, sequence) => ({
|
|
14
|
+
type: "run.checkpointed",
|
|
15
|
+
runId,
|
|
16
|
+
sequence,
|
|
17
|
+
occurredAt: `2020-01-01T00:00:${String(sequence).padStart(2, "0")}.000Z`,
|
|
18
|
+
});
|
|
19
|
+
export function runEventLogConformance(makeLog) {
|
|
20
|
+
describe("RunEventLog conformance", () => {
|
|
21
|
+
it("appends and replays the whole run with after: 0", async () => {
|
|
22
|
+
const log = makeLog();
|
|
23
|
+
for (const s of [1, 2, 3])
|
|
24
|
+
await log.append({ tenantId: T1, event: lifecycle(RUN, s) });
|
|
25
|
+
const all = await log.listAfter({ tenantId: T1, runId: RUN, after: 0 });
|
|
26
|
+
expect(all.map((e) => e.sequence)).toEqual([1, 2, 3]);
|
|
27
|
+
});
|
|
28
|
+
it("returns events strictly after the cursor, ascending — no overlap, no gap", async () => {
|
|
29
|
+
const log = makeLog();
|
|
30
|
+
for (const s of [1, 2, 3, 4, 5])
|
|
31
|
+
await log.append({ tenantId: T1, event: lifecycle(RUN, s) });
|
|
32
|
+
const after2 = await log.listAfter({ tenantId: T1, runId: RUN, after: 2 });
|
|
33
|
+
expect(after2.map((e) => e.sequence)).toEqual([3, 4, 5]);
|
|
34
|
+
});
|
|
35
|
+
it("orders by sequence even when appended out of order", async () => {
|
|
36
|
+
const log = makeLog();
|
|
37
|
+
for (const s of [3, 1, 5, 2, 4])
|
|
38
|
+
await log.append({ tenantId: T1, event: lifecycle(RUN, s) });
|
|
39
|
+
const all = await log.listAfter({ tenantId: T1, runId: RUN, after: 0 });
|
|
40
|
+
expect(all.map((e) => e.sequence)).toEqual([1, 2, 3, 4, 5]);
|
|
41
|
+
});
|
|
42
|
+
it("honors limit while preserving order, so paged catch-up loses nothing", async () => {
|
|
43
|
+
const log = makeLog();
|
|
44
|
+
for (const s of [1, 2, 3, 4, 5])
|
|
45
|
+
await log.append({ tenantId: T1, event: lifecycle(RUN, s) });
|
|
46
|
+
const first = await log.listAfter({ tenantId: T1, runId: RUN, after: 0, limit: 2 });
|
|
47
|
+
expect(first.map((e) => e.sequence)).toEqual([1, 2]);
|
|
48
|
+
const last = first[first.length - 1];
|
|
49
|
+
const second = await log.listAfter({ tenantId: T1, runId: RUN, after: last.sequence, limit: 2 });
|
|
50
|
+
expect(second.map((e) => e.sequence)).toEqual([3, 4]);
|
|
51
|
+
});
|
|
52
|
+
it("latestSequence reports the head, and 0 for a run with no events", async () => {
|
|
53
|
+
const log = makeLog();
|
|
54
|
+
expect(await log.latestSequence({ tenantId: T1, runId: RUN })).toBe(0);
|
|
55
|
+
for (const s of [1, 2, 3])
|
|
56
|
+
await log.append({ tenantId: T1, event: lifecycle(RUN, s) });
|
|
57
|
+
expect(await log.latestSequence({ tenantId: T1, runId: RUN })).toBe(3);
|
|
58
|
+
});
|
|
59
|
+
it("scopes events per run", async () => {
|
|
60
|
+
const log = makeLog();
|
|
61
|
+
await log.append({ tenantId: T1, event: lifecycle(RUN, 1) });
|
|
62
|
+
await log.append({ tenantId: T1, event: lifecycle(OTHER, 1) });
|
|
63
|
+
const forRun = await log.listAfter({ tenantId: T1, runId: RUN, after: 0 });
|
|
64
|
+
expect(forRun).toHaveLength(1);
|
|
65
|
+
expect(forRun[0]?.runId).toBe(RUN);
|
|
66
|
+
});
|
|
67
|
+
it("enforces tenant isolation", async () => {
|
|
68
|
+
const log = makeLog();
|
|
69
|
+
await log.append({ tenantId: T1, event: lifecycle(RUN, 1) });
|
|
70
|
+
expect(await log.listAfter({ tenantId: T2, runId: RUN, after: 0 })).toHaveLength(0);
|
|
71
|
+
expect(await log.latestSequence({ tenantId: T2, runId: RUN })).toBe(0);
|
|
72
|
+
});
|
|
73
|
+
/**
|
|
74
|
+
* Duplicate append — added in #94 to close a gap in this harness.
|
|
75
|
+
*
|
|
76
|
+
* The reference adapter documents the behaviour ("Idempotent: never store a sequence twice — a
|
|
77
|
+
* retried append is a no-op") but nothing here asserted it, so an adapter that *threw* on a
|
|
78
|
+
* retried append would have passed this whole suite while diverging from memory. A recovered
|
|
79
|
+
* worker retries appends, so it is a live path: `emit()` writes the event before the checkpoint,
|
|
80
|
+
* and recovery re-runs that emit.
|
|
81
|
+
*/
|
|
82
|
+
it("re-appending an existing sequence is a silent no-op, not an error", async () => {
|
|
83
|
+
const log = makeLog();
|
|
84
|
+
await log.append({ tenantId: T1, event: lifecycle(RUN, 1) });
|
|
85
|
+
// Must not throw — a retried append is normal, not exceptional.
|
|
86
|
+
await log.append({ tenantId: T1, event: lifecycle(RUN, 1) });
|
|
87
|
+
const all = await log.listAfter({ tenantId: T1, runId: RUN, after: 0 });
|
|
88
|
+
expect(all.map((e) => e.sequence)).toEqual([1]);
|
|
89
|
+
});
|
|
90
|
+
it("re-appending a sequence does not overwrite the stored event", async () => {
|
|
91
|
+
const log = makeLog();
|
|
92
|
+
const original = { ...lifecycle(RUN, 1), type: "run.started" };
|
|
93
|
+
await log.append({ tenantId: T1, event: original });
|
|
94
|
+
// A retry carrying different content must not silently mutate history: the first write wins,
|
|
95
|
+
// so a replayed client sees the same stream it saw before the retry.
|
|
96
|
+
await log.append({ tenantId: T1, event: { ...lifecycle(RUN, 1), type: "run.completed" } });
|
|
97
|
+
const all = await log.listAfter({ tenantId: T1, runId: RUN, after: 0 });
|
|
98
|
+
expect(all).toHaveLength(1);
|
|
99
|
+
expect(all[0]?.type).toBe("run.started");
|
|
100
|
+
});
|
|
101
|
+
it("keeps the sequence the caller supplied, rather than assigning its own", async () => {
|
|
102
|
+
const log = makeLog();
|
|
103
|
+
// The sequence is the worker's checkpoint cursor and the client's reconnect cursor. An adapter
|
|
104
|
+
// that renumbered on insert (e.g. MAX(sequence)+1) would break both, so pin it explicitly.
|
|
105
|
+
await log.append({ tenantId: T1, event: lifecycle(RUN, 42) });
|
|
106
|
+
const all = await log.listAfter({ tenantId: T1, runId: RUN, after: 0 });
|
|
107
|
+
expect(all.map((e) => e.sequence)).toEqual([42]);
|
|
108
|
+
expect(await log.latestSequence({ tenantId: T1, runId: RUN })).toBe(42);
|
|
109
|
+
});
|
|
110
|
+
/**
|
|
111
|
+
* The out-of-order duplicate. Found by writing the case above and reasoning about the reference
|
|
112
|
+
* adapter's guard, which only tested for a duplicate when the log's *tail* sequence was >= the
|
|
113
|
+
* incoming one — so an earlier out-of-order append lowered the tail and the next duplicate slipped
|
|
114
|
+
* through, storing sequence 3 twice. A replaying client would then receive the same event twice,
|
|
115
|
+
* breaking the "no missing or duplicated parts" guarantee `openRunEventStream` rests on.
|
|
116
|
+
*
|
|
117
|
+
* Postgres cannot exhibit this (the primary key forbids it), which is exactly why it belongs in
|
|
118
|
+
* the shared harness: without it the two adapters diverge and only one of them is correct.
|
|
119
|
+
*/
|
|
120
|
+
it("rejects a duplicate sequence even when appends arrive out of order", async () => {
|
|
121
|
+
const log = makeLog();
|
|
122
|
+
for (const s of [3, 1, 3])
|
|
123
|
+
await log.append({ tenantId: T1, event: lifecycle(RUN, s) });
|
|
124
|
+
const all = await log.listAfter({ tenantId: T1, runId: RUN, after: 0 });
|
|
125
|
+
expect(all.map((e) => e.sequence)).toEqual([1, 3]);
|
|
126
|
+
});
|
|
127
|
+
it("appending after a gap leaves the gap — numbering is the emitter's contract", async () => {
|
|
128
|
+
const log = makeLog();
|
|
129
|
+
for (const s of [1, 5])
|
|
130
|
+
await log.append({ tenantId: T1, event: lifecycle(RUN, s) });
|
|
131
|
+
const all = await log.listAfter({ tenantId: T1, runId: RUN, after: 0 });
|
|
132
|
+
// The store must not invent 2..4 or renumber 5 to 2. A schema cannot enforce gaplessness; only
|
|
133
|
+
// the emitter can, and the store's job is to be faithful.
|
|
134
|
+
expect(all.map((e) => e.sequence)).toEqual([1, 5]);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=run-event-log.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `RunStore` conformance — the harness `persistence/index.ts` and `adapters/memory/runtime.ts`
|
|
3
|
+
* already claim in their docstrings ("Verified by the shared `runStoreConformance` harness so every
|
|
4
|
+
* adapter agrees on claim/lease/transition semantics"). Until #91 it did not exist.
|
|
5
|
+
*
|
|
6
|
+
* These are the primitives `createDurableWorker` relies on for crash recovery without duplicate
|
|
7
|
+
* work, so every adapter must agree on them exactly: lease-based claim, keepalive that reports a
|
|
8
|
+
* lost claim, guarded transitions, durable cancellation and stale-lease reaping.
|
|
9
|
+
*/
|
|
10
|
+
import type { RunStore } from "../../persistence/index.js";
|
|
11
|
+
export declare function runStoreConformance(makeStore: () => RunStore): void;
|
|
12
|
+
//# sourceMappingURL=run-store.d.ts.map
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `RunStore` conformance — the harness `persistence/index.ts` and `adapters/memory/runtime.ts`
|
|
3
|
+
* already claim in their docstrings ("Verified by the shared `runStoreConformance` harness so every
|
|
4
|
+
* adapter agrees on claim/lease/transition semantics"). Until #91 it did not exist.
|
|
5
|
+
*
|
|
6
|
+
* These are the primitives `createDurableWorker` relies on for crash recovery without duplicate
|
|
7
|
+
* work, so every adapter must agree on them exactly: lease-based claim, keepalive that reports a
|
|
8
|
+
* lost claim, guarded transitions, durable cancellation and stale-lease reaping.
|
|
9
|
+
*/
|
|
10
|
+
import { describe, expect, it } from "vitest";
|
|
11
|
+
import { asId } from "../../core/ids.js";
|
|
12
|
+
const T1 = asId("conf-tenant-1");
|
|
13
|
+
const T2 = asId("conf-tenant-2");
|
|
14
|
+
const CONVO = asId("conf-convo-1");
|
|
15
|
+
const AGENT = asId("conf-agent-1");
|
|
16
|
+
const run = (s) => asId(s);
|
|
17
|
+
/** Fixed instants so lease arithmetic is deterministic rather than wall-clock dependent. */
|
|
18
|
+
const T0 = "2020-01-01T00:00:00.000Z";
|
|
19
|
+
const T30 = "2020-01-01T00:00:30.000Z";
|
|
20
|
+
const T90 = "2020-01-01T00:01:30.000Z";
|
|
21
|
+
export function runStoreConformance(makeStore) {
|
|
22
|
+
const seed = async (store, id, tenantId = T1) => store.create({ tenantId, id: run(id), conversationId: CONVO, agentId: AGENT, agentVersion: 1 });
|
|
23
|
+
describe("RunStore conformance", () => {
|
|
24
|
+
it("creates a run as queued and reads it back within its tenant", async () => {
|
|
25
|
+
const store = makeStore();
|
|
26
|
+
const created = await seed(store, "r1");
|
|
27
|
+
expect(created).toMatchObject({ id: "r1", tenantId: T1, status: "queued", agentVersion: 1 });
|
|
28
|
+
expect(await store.findById({ tenantId: T1, id: run("r1") })).toMatchObject({ id: "r1" });
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* The run remembers who it is for — #164.
|
|
32
|
+
*
|
|
33
|
+
* A run carried a tenant and no principal, so a durable worker had nothing to rebuild the caller's identity
|
|
34
|
+
* from and every host invented one. An adapter that dropped these on write would restore that bug while
|
|
35
|
+
* every other test still passed.
|
|
36
|
+
*/
|
|
37
|
+
it("records the principal and roles a run was admitted for", async () => {
|
|
38
|
+
const store = makeStore();
|
|
39
|
+
await store.create({
|
|
40
|
+
tenantId: T1,
|
|
41
|
+
id: run("r-who"),
|
|
42
|
+
conversationId: CONVO,
|
|
43
|
+
agentId: AGENT,
|
|
44
|
+
agentVersion: 1,
|
|
45
|
+
principalId: asId("conf-principal-1"),
|
|
46
|
+
roleIds: ["viewer", "auditor"],
|
|
47
|
+
});
|
|
48
|
+
const found = await store.findById({ tenantId: T1, id: run("r-who") });
|
|
49
|
+
expect(found?.principalId).toBe("conf-principal-1");
|
|
50
|
+
// Order preserved and nothing added: roles are compared against a policy, and a reordered or padded list
|
|
51
|
+
// is a different authorization question.
|
|
52
|
+
expect(found?.roleIds).toEqual(["viewer", "auditor"]);
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* Absent is absent. A run created before this was recorded, or by a caller that supplied nothing, must not
|
|
56
|
+
* come back with an invented identity or an empty role list that reads like "a caller with no roles".
|
|
57
|
+
*/
|
|
58
|
+
it("leaves the principal and roles undefined when none were supplied", async () => {
|
|
59
|
+
const store = makeStore();
|
|
60
|
+
await seed(store, "r-anon");
|
|
61
|
+
const found = await store.findById({ tenantId: T1, id: run("r-anon") });
|
|
62
|
+
expect(found?.principalId).toBeUndefined();
|
|
63
|
+
expect(found?.roleIds).toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
it("creates a run that belongs to no conversation", async () => {
|
|
66
|
+
/**
|
|
67
|
+
* The whole point of #198. A triggered automation — a webhook, a schedule, a flow step — has no
|
|
68
|
+
* conversation, and until now had to invent one to exist at all.
|
|
69
|
+
*
|
|
70
|
+
* `conversationId` must come back **absent**, not as an empty string or a placeholder. That is what
|
|
71
|
+
* distinguishes "belongs to no conversation" from "belongs to a conversation whose id we lost", and the
|
|
72
|
+
* difference decides whether every conversation-scoped query silently matches it.
|
|
73
|
+
*/
|
|
74
|
+
const store = makeStore();
|
|
75
|
+
const created = await store.create({ tenantId: T1, id: run("no-convo"), agentId: AGENT, agentVersion: 1 });
|
|
76
|
+
expect(created.conversationId).toBeUndefined();
|
|
77
|
+
expect(created.status).toBe("queued");
|
|
78
|
+
const read = await store.findById({ tenantId: T1, id: run("no-convo") });
|
|
79
|
+
expect(read?.conversationId).toBeUndefined();
|
|
80
|
+
// Not the empty string, which a NOT NULL column would have forced a caller to write.
|
|
81
|
+
expect(read?.conversationId).not.toBe("");
|
|
82
|
+
});
|
|
83
|
+
it("keeps a conversation-less run separate from one in a conversation", async () => {
|
|
84
|
+
// Both exist, and neither acquires the other's conversation. A store that defaulted the absent one to
|
|
85
|
+
// some sentinel would make them collide in any query grouped by conversation.
|
|
86
|
+
const store = makeStore();
|
|
87
|
+
await store.create({ tenantId: T1, id: run("in-convo"), conversationId: CONVO, agentId: AGENT, agentVersion: 1 });
|
|
88
|
+
await store.create({ tenantId: T1, id: run("free"), agentId: AGENT, agentVersion: 1 });
|
|
89
|
+
expect((await store.findById({ tenantId: T1, id: run("in-convo") }))?.conversationId).toBe(CONVO);
|
|
90
|
+
expect((await store.findById({ tenantId: T1, id: run("free") }))?.conversationId).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
it("claims and completes a conversation-less run like any other", async () => {
|
|
93
|
+
// The lifecycle must not depend on having a conversation, or an automation could be admitted and never
|
|
94
|
+
// executed — which would be the worst possible reading of "supported".
|
|
95
|
+
const store = makeStore();
|
|
96
|
+
await store.create({ tenantId: T1, id: run("free-2"), agentId: AGENT, agentVersion: 1 });
|
|
97
|
+
const claimed = await store.claim({
|
|
98
|
+
tenantId: T1, id: run("free-2"), workerId: "w1", leaseMs: 30_000, now: T0,
|
|
99
|
+
});
|
|
100
|
+
expect(claimed?.status).toBe("running");
|
|
101
|
+
const done = await store.transition({
|
|
102
|
+
tenantId: T1, id: run("free-2"), workerId: "w1", to: "completed", now: T30,
|
|
103
|
+
});
|
|
104
|
+
expect(done.status).toBe("completed");
|
|
105
|
+
expect(done.conversationId).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
it("enforces tenant isolation", async () => {
|
|
108
|
+
const store = makeStore();
|
|
109
|
+
await seed(store, "r1");
|
|
110
|
+
expect(await store.findById({ tenantId: T2, id: run("r1") })).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
it("claims a queued run and returns it running", async () => {
|
|
113
|
+
const store = makeStore();
|
|
114
|
+
await seed(store, "r1");
|
|
115
|
+
const claimed = await store.claim({
|
|
116
|
+
tenantId: T1,
|
|
117
|
+
id: run("r1"),
|
|
118
|
+
workerId: "w1",
|
|
119
|
+
leaseMs: 60_000,
|
|
120
|
+
now: T0,
|
|
121
|
+
});
|
|
122
|
+
expect(claimed).not.toBeNull();
|
|
123
|
+
expect(claimed?.claimedBy).toBe("w1");
|
|
124
|
+
});
|
|
125
|
+
it("refuses a second claim while the lease is live, so two workers never share a run", async () => {
|
|
126
|
+
const store = makeStore();
|
|
127
|
+
await seed(store, "r1");
|
|
128
|
+
await store.claim({ tenantId: T1, id: run("r1"), workerId: "w1", leaseMs: 60_000, now: T0 });
|
|
129
|
+
const second = await store.claim({
|
|
130
|
+
tenantId: T1,
|
|
131
|
+
id: run("r1"),
|
|
132
|
+
workerId: "w2",
|
|
133
|
+
leaseMs: 60_000,
|
|
134
|
+
now: T30,
|
|
135
|
+
});
|
|
136
|
+
expect(second).toBeNull();
|
|
137
|
+
});
|
|
138
|
+
it("allows re-claim once the lease has expired — the crash-recovery path", async () => {
|
|
139
|
+
const store = makeStore();
|
|
140
|
+
await seed(store, "r1");
|
|
141
|
+
await store.claim({ tenantId: T1, id: run("r1"), workerId: "w1", leaseMs: 60_000, now: T0 });
|
|
142
|
+
const recovered = await store.claim({
|
|
143
|
+
tenantId: T1,
|
|
144
|
+
id: run("r1"),
|
|
145
|
+
workerId: "w2",
|
|
146
|
+
leaseMs: 60_000,
|
|
147
|
+
now: T90,
|
|
148
|
+
});
|
|
149
|
+
expect(recovered).not.toBeNull();
|
|
150
|
+
expect(recovered?.claimedBy).toBe("w2");
|
|
151
|
+
});
|
|
152
|
+
it("keepalive extends the holder's lease and reports false once the claim is lost", async () => {
|
|
153
|
+
const store = makeStore();
|
|
154
|
+
await seed(store, "r1");
|
|
155
|
+
await store.claim({ tenantId: T1, id: run("r1"), workerId: "w1", leaseMs: 60_000, now: T0 });
|
|
156
|
+
expect(await store.keepalive({ tenantId: T1, id: run("r1"), workerId: "w1", leaseMs: 60_000, now: T30 })).toBe(true);
|
|
157
|
+
// A worker that never held the claim must be told so, and must not steal the lease.
|
|
158
|
+
expect(await store.keepalive({ tenantId: T1, id: run("r1"), workerId: "w2", leaseMs: 60_000, now: T30 })).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
it("rejects a transition absent from RUN_TRANSITIONS", async () => {
|
|
161
|
+
const store = makeStore();
|
|
162
|
+
await seed(store, "r1");
|
|
163
|
+
await store.claim({ tenantId: T1, id: run("r1"), workerId: "w1", leaseMs: 60_000, now: T0 });
|
|
164
|
+
// running → completed is legal; completed is terminal, so completed → running is not.
|
|
165
|
+
await store.transition({ tenantId: T1, id: run("r1"), workerId: "w1", to: "completed", now: T30 });
|
|
166
|
+
await expect(store.transition({ tenantId: T1, id: run("r1"), workerId: "w1", to: "running", now: T90 })).rejects.toThrow();
|
|
167
|
+
});
|
|
168
|
+
it("records a cancellation request durably so any worker honors it", async () => {
|
|
169
|
+
const store = makeStore();
|
|
170
|
+
await seed(store, "r1");
|
|
171
|
+
const cancelled = await store.requestCancel({ tenantId: T1, id: run("r1"), now: T0 });
|
|
172
|
+
expect(cancelled?.cancelRequestedAt).toBeDefined();
|
|
173
|
+
const reread = await store.findById({ tenantId: T1, id: run("r1") });
|
|
174
|
+
expect(reread?.cancelRequestedAt).toBeDefined();
|
|
175
|
+
});
|
|
176
|
+
it("reapExpired returns only runs whose lease has expired, each carrying its own tenantId", async () => {
|
|
177
|
+
const store = makeStore();
|
|
178
|
+
await seed(store, "live");
|
|
179
|
+
await seed(store, "stale");
|
|
180
|
+
await store.claim({ tenantId: T1, id: run("live"), workerId: "w1", leaseMs: 600_000, now: T0 });
|
|
181
|
+
await store.claim({ tenantId: T1, id: run("stale"), workerId: "w2", leaseMs: 1_000, now: T0 });
|
|
182
|
+
const reaped = await store.reapExpired({ now: T90, limit: 10 });
|
|
183
|
+
const ids = reaped.map((r) => r.id);
|
|
184
|
+
expect(ids).toContain("stale");
|
|
185
|
+
expect(ids).not.toContain("live");
|
|
186
|
+
// Cross-tenant by design: a background reaper has no tenant, so each row must self-identify.
|
|
187
|
+
for (const r of reaped)
|
|
188
|
+
expect(r.tenantId).toBeDefined();
|
|
189
|
+
});
|
|
190
|
+
it("reapExpired honors its limit", async () => {
|
|
191
|
+
const store = makeStore();
|
|
192
|
+
for (const n of [1, 2, 3]) {
|
|
193
|
+
await seed(store, `s${n}`);
|
|
194
|
+
await store.claim({ tenantId: T1, id: run(`s${n}`), workerId: "w", leaseMs: 1_000, now: T0 });
|
|
195
|
+
}
|
|
196
|
+
expect((await store.reapExpired({ now: T90, limit: 2 })).length).toBeLessThanOrEqual(2);
|
|
197
|
+
});
|
|
198
|
+
/**
|
|
199
|
+
* The per-tenant concurrent-run limit — REQ-058 (#246), task #265.
|
|
200
|
+
*
|
|
201
|
+
* In the conformance harness rather than beside the memory adapter, deliberately: the guarantee is that the
|
|
202
|
+
* count and the claim are **one atomic operation**, and that is a claim about SQL as much as about
|
|
203
|
+
* JavaScript. The reference adapter gets atomicity free — no await between the count and the write — so
|
|
204
|
+
* testing only there would prove nothing about the adapter where it is hard.
|
|
205
|
+
*/
|
|
206
|
+
describe("per-tenant concurrency, on the lease", () => {
|
|
207
|
+
it("refuses a claim once the tenant holds its limit of live leases", async () => {
|
|
208
|
+
const store = makeStore();
|
|
209
|
+
for (const n of [1, 2, 3])
|
|
210
|
+
await seed(store, `c${n}`);
|
|
211
|
+
expect(await store.claim({ tenantId: T1, id: run("c1"), workerId: "w1", leaseMs: 60_000, now: T0, maxConcurrent: 2 })).not.toBeNull();
|
|
212
|
+
expect(await store.claim({ tenantId: T1, id: run("c2"), workerId: "w2", leaseMs: 60_000, now: T0, maxConcurrent: 2 })).not.toBeNull();
|
|
213
|
+
// Two live leases, limit two: the third gets nothing.
|
|
214
|
+
expect(await store.claim({ tenantId: T1, id: run("c3"), workerId: "w3", leaseMs: 60_000, now: T0, maxConcurrent: 2 })).toBeNull();
|
|
215
|
+
expect(await store.countLive({ tenantId: T1, now: T0 })).toBe(2);
|
|
216
|
+
// And it is still queued, not lost — the run waits rather than being discarded.
|
|
217
|
+
expect((await store.findById({ tenantId: T1, id: run("c3") }))?.status).toBe("queued");
|
|
218
|
+
});
|
|
219
|
+
it("two concurrent claims for one free slot admit exactly one — AC-3", async () => {
|
|
220
|
+
const store = makeStore();
|
|
221
|
+
await seed(store, "race-holder");
|
|
222
|
+
await seed(store, "race-a");
|
|
223
|
+
await seed(store, "race-b");
|
|
224
|
+
await store.claim({ tenantId: T1, id: run("race-holder"), workerId: "w0", leaseMs: 60_000, now: T0, maxConcurrent: 2 });
|
|
225
|
+
/**
|
|
226
|
+
* Issued without awaiting between them, which is what makes this a race rather than a sequence.
|
|
227
|
+
*
|
|
228
|
+
* Against the reference adapter both run in one turn of the event loop; against SQL they are two
|
|
229
|
+
* statements in flight at once. A check-then-act implementation passes the sequential test above and
|
|
230
|
+
* fails this one — which is why both exist.
|
|
231
|
+
*/
|
|
232
|
+
const [a, b] = await Promise.all([
|
|
233
|
+
store.claim({ tenantId: T1, id: run("race-a"), workerId: "wa", leaseMs: 60_000, now: T0, maxConcurrent: 2 }),
|
|
234
|
+
store.claim({ tenantId: T1, id: run("race-b"), workerId: "wb", leaseMs: 60_000, now: T0, maxConcurrent: 2 }),
|
|
235
|
+
]);
|
|
236
|
+
expect([a, b].filter((claimed) => claimed !== null)).toHaveLength(1);
|
|
237
|
+
expect(await store.countLive({ tenantId: T1, now: T0 })).toBe(2);
|
|
238
|
+
});
|
|
239
|
+
it("reclaims a crashed worker's slot with no operator action — AC-2", async () => {
|
|
240
|
+
const store = makeStore();
|
|
241
|
+
await seed(store, "crashed");
|
|
242
|
+
await seed(store, "waiting");
|
|
243
|
+
// A worker takes the only slot, then dies: no transition, no release, just a lease that stops being
|
|
244
|
+
// renewed. This is the case a decrement-on-completion counter never recovers from.
|
|
245
|
+
await store.claim({ tenantId: T1, id: run("crashed"), workerId: "dead", leaseMs: 1_000, now: T0, maxConcurrent: 1 });
|
|
246
|
+
expect(await store.claim({ tenantId: T1, id: run("waiting"), workerId: "w2", leaseMs: 60_000, now: T0, maxConcurrent: 1 })).toBeNull();
|
|
247
|
+
// Time passes. Nothing else happens — no reaper, no restart, no intervention.
|
|
248
|
+
expect(await store.countLive({ tenantId: T1, now: T90 })).toBe(0);
|
|
249
|
+
expect(await store.claim({ tenantId: T1, id: run("waiting"), workerId: "w2", leaseMs: 60_000, now: T90, maxConcurrent: 1 })).not.toBeNull();
|
|
250
|
+
});
|
|
251
|
+
it("does not count a run against its own recovery", async () => {
|
|
252
|
+
const store = makeStore();
|
|
253
|
+
await seed(store, "recovering");
|
|
254
|
+
await store.claim({ tenantId: T1, id: run("recovering"), workerId: "dead", leaseMs: 1_000, now: T0, maxConcurrent: 1 });
|
|
255
|
+
// The lease has expired and this run is the tenant's only one. A limit of 1 must not block the recovery
|
|
256
|
+
// of the very run whose lease is being replaced.
|
|
257
|
+
expect(await store.claim({ tenantId: T1, id: run("recovering"), workerId: "w2", leaseMs: 60_000, now: T90, maxConcurrent: 1 })).not.toBeNull();
|
|
258
|
+
});
|
|
259
|
+
it("counts each tenant separately — AC-5", async () => {
|
|
260
|
+
const store = makeStore();
|
|
261
|
+
await seed(store, "t1-a", T1);
|
|
262
|
+
await seed(store, "t2-a", T2);
|
|
263
|
+
await store.claim({ tenantId: T1, id: run("t1-a"), workerId: "w1", leaseMs: 60_000, now: T0, maxConcurrent: 1 });
|
|
264
|
+
// T1 is full. T2 is untouched by that, and a shared counter would be the bug this catches.
|
|
265
|
+
expect(await store.claim({ tenantId: T2, id: run("t2-a"), workerId: "w2", leaseMs: 60_000, now: T0, maxConcurrent: 1 })).not.toBeNull();
|
|
266
|
+
expect(await store.countLive({ tenantId: T1, now: T0 })).toBe(1);
|
|
267
|
+
expect(await store.countLive({ tenantId: T2, now: T0 })).toBe(1);
|
|
268
|
+
});
|
|
269
|
+
it("absent or zero means unlimited — AC-6", async () => {
|
|
270
|
+
const store = makeStore();
|
|
271
|
+
for (const n of [1, 2, 3, 4])
|
|
272
|
+
await seed(store, `u${n}`);
|
|
273
|
+
// The branch that matters on the day this ships: a deployment that has configured nothing keeps working.
|
|
274
|
+
expect(await store.claim({ tenantId: T1, id: run("u1"), workerId: "w", leaseMs: 60_000, now: T0 })).not.toBeNull();
|
|
275
|
+
expect(await store.claim({ tenantId: T1, id: run("u2"), workerId: "w", leaseMs: 60_000, now: T0 })).not.toBeNull();
|
|
276
|
+
expect(await store.claim({ tenantId: T1, id: run("u3"), workerId: "w", leaseMs: 60_000, now: T0, maxConcurrent: 0 })).not.toBeNull();
|
|
277
|
+
expect(await store.claim({ tenantId: T1, id: run("u4"), workerId: "w", leaseMs: 60_000, now: T0, maxConcurrent: 0 })).not.toBeNull();
|
|
278
|
+
expect(await store.countLive({ tenantId: T1, now: T0 })).toBe(4);
|
|
279
|
+
});
|
|
280
|
+
it("a released slot is available immediately", async () => {
|
|
281
|
+
const store = makeStore();
|
|
282
|
+
await seed(store, "first");
|
|
283
|
+
await seed(store, "second");
|
|
284
|
+
await store.claim({ tenantId: T1, id: run("first"), workerId: "w1", leaseMs: 60_000, now: T0, maxConcurrent: 1 });
|
|
285
|
+
expect(await store.claim({ tenantId: T1, id: run("second"), workerId: "w2", leaseMs: 60_000, now: T0, maxConcurrent: 1 })).toBeNull();
|
|
286
|
+
// A terminal transition releases the lease, so the slot returns without waiting for an expiry.
|
|
287
|
+
await store.transition({ tenantId: T1, id: run("first"), workerId: "w1", to: "completed", now: T0 });
|
|
288
|
+
expect(await store.countLive({ tenantId: T1, now: T0 })).toBe(0);
|
|
289
|
+
expect(await store.claim({ tenantId: T1, id: run("second"), workerId: "w2", leaseMs: 60_000, now: T0, maxConcurrent: 1 })).not.toBeNull();
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=run-store.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SessionStateStore`, `ConversationBindingStore`, `ThreadSummaryStore` and `UnitOfWork`
|
|
3
|
+
* conformance — `docs/13-sessions-and-threads.md`.
|
|
4
|
+
*
|
|
5
|
+
* Session state is the deliberate replacement for Agno's fixed 20-turn re-injection (see the
|
|
6
|
+
* extraction inventory), so its optimistic concurrency and size ceiling are load-bearing: they are
|
|
7
|
+
* what stop two runs on one conversation from clobbering each other and what keep working memory
|
|
8
|
+
* from growing into a document store.
|
|
9
|
+
*/
|
|
10
|
+
import type { ConversationId, TenantId } from "../../core/ids.js";
|
|
11
|
+
import type { ConversationBindingStore, SessionStateStore, ThreadSummaryStore, UnitOfWork } from "../../persistence/index.js";
|
|
12
|
+
import { type AdapterDeclaration } from "./capability.js";
|
|
13
|
+
import { type FixtureOrStore } from "./parents.js";
|
|
14
|
+
export declare function sessionStateStoreConformance(makeFixture: () => FixtureOrStore<SessionStateStore>, options?: {
|
|
15
|
+
readonly maxBytes?: number;
|
|
16
|
+
}): void;
|
|
17
|
+
export declare function conversationBindingStoreConformance(makeFixture: () => FixtureOrStore<ConversationBindingStore>): void;
|
|
18
|
+
export declare function threadSummaryStoreConformance(makeFixture: () => FixtureOrStore<ThreadSummaryStore>): void;
|
|
19
|
+
/**
|
|
20
|
+
* A unit of work together with the store it must roll back, from **one** backend.
|
|
21
|
+
*
|
|
22
|
+
* The previous signature took two independent factories, which was fine for the reference adapter
|
|
23
|
+
* (its compensations are in-process) and quietly wrong for Postgres: two factories produce two
|
|
24
|
+
* databases, so the transaction and the write would land in different places and the rollback
|
|
25
|
+
* assertion would pass or fail for reasons unrelated to transactions. A single fixture makes the
|
|
26
|
+
* shared backend a requirement of the type rather than something each adapter has to remember.
|
|
27
|
+
*/
|
|
28
|
+
export type UnitOfWorkFixture = {
|
|
29
|
+
readonly unitOfWork: UnitOfWork;
|
|
30
|
+
readonly sessions: SessionStateStore;
|
|
31
|
+
/** Seeds the conversation the session-state foreign key requires (#97). Omitted when there is none. */
|
|
32
|
+
readonly seedConversation?: (input: {
|
|
33
|
+
readonly tenantId: TenantId;
|
|
34
|
+
readonly conversationId: ConversationId;
|
|
35
|
+
}) => Promise<void>;
|
|
36
|
+
};
|
|
37
|
+
export declare function unitOfWorkConformance(makeFixture: () => UnitOfWorkFixture, declaration?: AdapterDeclaration): void;
|
|
38
|
+
//# sourceMappingURL=session-state.d.ts.map
|