@retinue/agentkit 0.2.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 +1 -1
- 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/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 +78 -1
- package/dist/agents/agent.js +118 -5
- package/dist/agents/define.js +24 -4
- package/dist/agents/engine.d.ts +20 -0
- package/dist/agents/engine.js +177 -6
- package/dist/audio/index.d.ts +151 -0
- package/dist/audio/index.js +183 -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/content-parts.d.ts +18 -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 +44 -2
- package/dist/core/events.js +31 -2
- package/dist/core/validation.js +19 -0
- package/dist/entries/connections.d.ts +10 -0
- package/dist/entries/connections.js +10 -0
- package/dist/entries/knowledge.d.ts +8 -0
- package/dist/entries/knowledge.js +8 -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 +3 -1
- package/dist/graphql/resolvers.js +14 -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 +24 -0
- package/dist/knowledge/index.js +36 -1
- package/dist/knowledge/retrieval.d.ts +26 -1
- package/dist/knowledge/retrieval.js +77 -1
- 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 +89 -0
- package/dist/models/streaming.js +179 -2
- 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 +46 -0
- 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/context.d.ts +61 -0
- package/dist/skills/context.js +107 -0
- package/dist/skills/index.d.ts +1 -0
- package/dist/skills/index.js +1 -0
- package/dist/telemetry/spans.js +5 -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/http.d.ts +27 -0
- package/dist/toolkit/http.js +48 -0
- package/dist/toolkit/index.d.ts +27 -1
- package/dist/toolkit/index.js +24 -1
- package/dist/toolkit/mime.d.ts +97 -0
- package/dist/toolkit/mime.js +233 -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/tools/credentials.d.ts +265 -6
- package/dist/tools/credentials.js +318 -3
- package/dist/tools/define.d.ts +14 -0
- package/dist/tools/define.js +3 -0
- package/dist/tools/index.d.ts +21 -0
- package/dist/tools/index.js +7 -0
- package/dist/tools/library/audio.d.ts +63 -0
- package/dist/tools/library/audio.js +148 -0
- package/dist/tools/library/index.d.ts +8 -0
- package/dist/tools/library/index.js +7 -0
- package/dist/tools/registry.d.ts +33 -0
- package/dist/tools/registry.js +72 -2
- 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 +29 -2
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `EvaluationStore` conformance (#141).
|
|
3
|
+
*
|
|
4
|
+
* The cases are mostly about *aggregates agreeing with their evidence* and about *idempotency*, because those
|
|
5
|
+
* are what a release gate depends on: a mean that disagrees with its case rows is a number nobody can defend,
|
|
6
|
+
* and a double-counted case makes the gate pass or fail for the wrong reason.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
import { asId } from "../../core/ids.js";
|
|
10
|
+
const T1 = asId("conf-eval-tenant-1");
|
|
11
|
+
const T2 = asId("conf-eval-tenant-2");
|
|
12
|
+
const STARTED = "2026-08-23T10:00:00.000Z";
|
|
13
|
+
const FINISHED = "2026-08-23T10:05:00.000Z";
|
|
14
|
+
const result = (overrides) => ({
|
|
15
|
+
caseId: overrides.id,
|
|
16
|
+
dimension: overrides.dimension ?? "task-completion",
|
|
17
|
+
expectKind: overrides.expectKind ?? "contains",
|
|
18
|
+
verdict: overrides.verdict ?? { pass: true, score: 1, reason: "ok" },
|
|
19
|
+
graderId: overrides.graderId ?? "contains",
|
|
20
|
+
graderVersion: overrides.graderVersion ?? "1",
|
|
21
|
+
...(overrides.modelId === undefined ? {} : { modelId: overrides.modelId }),
|
|
22
|
+
...(overrides.promptVersion === undefined ? {} : { promptVersion: overrides.promptVersion }),
|
|
23
|
+
costMinorUnits: overrides.costMinorUnits ?? 0,
|
|
24
|
+
});
|
|
25
|
+
export function evaluationStoreConformance(make) {
|
|
26
|
+
describe("EvaluationStore conformance", () => {
|
|
27
|
+
const started = async (id = "run-1", release = "v1") => {
|
|
28
|
+
const store = await make();
|
|
29
|
+
await store.startRun({ tenantId: T1, id, release, startedAt: STARTED });
|
|
30
|
+
return store;
|
|
31
|
+
};
|
|
32
|
+
it("opens a run with zeroed totals", async () => {
|
|
33
|
+
const store = await make();
|
|
34
|
+
const run = await store.startRun({ tenantId: T1, id: "run-1", release: "v1", startedAt: STARTED });
|
|
35
|
+
expect(run).toMatchObject({ id: "run-1", release: "v1", total: 0, passed: 0, meanScore: 0 });
|
|
36
|
+
// Unfinished, and visibly so: an interrupted run must be distinguishable from an absent one.
|
|
37
|
+
expect(run.finishedAt).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
it("refuses to open the same run twice", async () => {
|
|
40
|
+
// Silently reopening a completed run would discard the numbers a release was gated on.
|
|
41
|
+
const store = await started();
|
|
42
|
+
await expect(store.startRun({ tenantId: T1, id: "run-1", release: "v1", startedAt: STARTED })).rejects.toMatchObject({ code: "conflict" });
|
|
43
|
+
});
|
|
44
|
+
it("computes aggregates from the recorded cases", async () => {
|
|
45
|
+
const store = await started();
|
|
46
|
+
for (const r of [
|
|
47
|
+
result({ id: "a", verdict: { pass: true, score: 1, reason: "ok" } }),
|
|
48
|
+
result({ id: "b", verdict: { pass: false, score: 0, reason: "no" } }),
|
|
49
|
+
result({ id: "c", verdict: { pass: true, score: 0.5, reason: "partial" } }),
|
|
50
|
+
]) {
|
|
51
|
+
await store.recordCase({ tenantId: T1, runId: "run-1", result: r });
|
|
52
|
+
}
|
|
53
|
+
const run = await store.completeRun({
|
|
54
|
+
tenantId: T1,
|
|
55
|
+
runId: "run-1",
|
|
56
|
+
finishedAt: FINISHED,
|
|
57
|
+
graderVersions: { contains: "1" },
|
|
58
|
+
});
|
|
59
|
+
expect(run).toMatchObject({ total: 3, passed: 2, finishedAt: FINISHED });
|
|
60
|
+
expect(run.meanScore).toBeCloseTo(0.5, 5);
|
|
61
|
+
});
|
|
62
|
+
it("breaks the run down by dimension", async () => {
|
|
63
|
+
// AC-4. An aggregate without a breakdown cannot tell "authorization got worse" from "everything got
|
|
64
|
+
// slightly worse", and those need different responses.
|
|
65
|
+
const store = await started();
|
|
66
|
+
for (const r of [
|
|
67
|
+
result({ id: "a", dimension: "authorization", verdict: { pass: true, score: 1, reason: "ok" } }),
|
|
68
|
+
result({ id: "b", dimension: "authorization", verdict: { pass: false, score: 0, reason: "no" } }),
|
|
69
|
+
result({ id: "c", dimension: "groundedness", verdict: { pass: true, score: 1, reason: "ok" } }),
|
|
70
|
+
]) {
|
|
71
|
+
await store.recordCase({ tenantId: T1, runId: "run-1", result: r });
|
|
72
|
+
}
|
|
73
|
+
const run = await store.completeRun({
|
|
74
|
+
tenantId: T1,
|
|
75
|
+
runId: "run-1",
|
|
76
|
+
finishedAt: FINISHED,
|
|
77
|
+
graderVersions: {},
|
|
78
|
+
});
|
|
79
|
+
expect(run.byDimension).toEqual([
|
|
80
|
+
{ dimension: "authorization", total: 2, passed: 1, meanScore: 0.5 },
|
|
81
|
+
{ dimension: "groundedness", total: 1, passed: 1, meanScore: 1 },
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
it("totals the run's cost", async () => {
|
|
85
|
+
// AC-6: the gate's own expense. A gate whose cost is unknown is one nobody can decide to run less often.
|
|
86
|
+
const store = await started();
|
|
87
|
+
for (const r of [
|
|
88
|
+
result({ id: "a", costMinorUnits: 0 }),
|
|
89
|
+
result({ id: "b", costMinorUnits: 7, modelId: "judge-1", promptVersion: "1" }),
|
|
90
|
+
result({ id: "c", costMinorUnits: 11, modelId: "judge-1", promptVersion: "1" }),
|
|
91
|
+
]) {
|
|
92
|
+
await store.recordCase({ tenantId: T1, runId: "run-1", result: r });
|
|
93
|
+
}
|
|
94
|
+
const run = await store.completeRun({
|
|
95
|
+
tenantId: T1,
|
|
96
|
+
runId: "run-1",
|
|
97
|
+
finishedAt: FINISHED,
|
|
98
|
+
graderVersions: {},
|
|
99
|
+
});
|
|
100
|
+
expect(run.costMinorUnits).toBe(18);
|
|
101
|
+
});
|
|
102
|
+
it("does not double count a case recorded twice", async () => {
|
|
103
|
+
// A resumed run re-recording a case must replace it. Double-counting makes the gate pass or fail for the
|
|
104
|
+
// wrong reason, and the aggregate looks plausible either way.
|
|
105
|
+
const store = await started();
|
|
106
|
+
await store.recordCase({ tenantId: T1, runId: "run-1", result: result({ id: "a", costMinorUnits: 5 }) });
|
|
107
|
+
await store.recordCase({ tenantId: T1, runId: "run-1", result: result({ id: "a", costMinorUnits: 5 }) });
|
|
108
|
+
const run = await store.completeRun({
|
|
109
|
+
tenantId: T1,
|
|
110
|
+
runId: "run-1",
|
|
111
|
+
finishedAt: FINISHED,
|
|
112
|
+
graderVersions: {},
|
|
113
|
+
});
|
|
114
|
+
expect(run.total).toBe(1);
|
|
115
|
+
expect(run.costMinorUnits).toBe(5);
|
|
116
|
+
});
|
|
117
|
+
it("lets a re-record correct a verdict", async () => {
|
|
118
|
+
// The other half of idempotency: replacing, not ignoring. A re-scored case must take its new verdict, or
|
|
119
|
+
// a fixed grader could never update a run.
|
|
120
|
+
const store = await started();
|
|
121
|
+
await store.recordCase({
|
|
122
|
+
tenantId: T1,
|
|
123
|
+
runId: "run-1",
|
|
124
|
+
result: result({ id: "a", verdict: { pass: false, score: 0, reason: "first" } }),
|
|
125
|
+
});
|
|
126
|
+
await store.recordCase({
|
|
127
|
+
tenantId: T1,
|
|
128
|
+
runId: "run-1",
|
|
129
|
+
result: result({ id: "a", verdict: { pass: true, score: 1, reason: "second" } }),
|
|
130
|
+
});
|
|
131
|
+
const page = await store.listCaseResults({ tenantId: T1, runId: "run-1", limit: 10 });
|
|
132
|
+
expect(page.items).toHaveLength(1);
|
|
133
|
+
expect(page.items[0]?.verdict).toMatchObject({ pass: true, reason: "second" });
|
|
134
|
+
});
|
|
135
|
+
it("reports rather than throws when the run is gone", async () => {
|
|
136
|
+
// A harness losing that race is ordinary; a thrown error would abandon a whole scoring pass.
|
|
137
|
+
const store = await make();
|
|
138
|
+
expect(await store.recordCase({ tenantId: T1, runId: "missing", result: result({ id: "a" }) })).toEqual({ recorded: false });
|
|
139
|
+
});
|
|
140
|
+
it("stores the grader and prompt versions on a judged result", async () => {
|
|
141
|
+
// AC-2 and AC-3 both depend on it: a score that moved after a prompt edit is not a quality change, and
|
|
142
|
+
// without the version on the result the two are indistinguishable.
|
|
143
|
+
const store = await started();
|
|
144
|
+
await store.recordCase({
|
|
145
|
+
tenantId: T1,
|
|
146
|
+
runId: "run-1",
|
|
147
|
+
result: result({ id: "a", graderId: "refuses-judged", graderVersion: "1+p2", modelId: "judge-1", promptVersion: "2" }),
|
|
148
|
+
});
|
|
149
|
+
const page = await store.listCaseResults({ tenantId: T1, runId: "run-1", limit: 10 });
|
|
150
|
+
expect(page.items[0]).toMatchObject({
|
|
151
|
+
graderId: "refuses-judged",
|
|
152
|
+
graderVersion: "1+p2",
|
|
153
|
+
modelId: "judge-1",
|
|
154
|
+
promptVersion: "2",
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
it("records the grader versions on the run", async () => {
|
|
158
|
+
const store = await started();
|
|
159
|
+
await store.recordCase({ tenantId: T1, runId: "run-1", result: result({ id: "a" }) });
|
|
160
|
+
const run = await store.completeRun({
|
|
161
|
+
tenantId: T1,
|
|
162
|
+
runId: "run-1",
|
|
163
|
+
finishedAt: FINISHED,
|
|
164
|
+
graderVersions: { contains: "1", "refuses-judged": "1+p2" },
|
|
165
|
+
});
|
|
166
|
+
expect(run.graderVersions).toEqual({ contains: "1", "refuses-judged": "1+p2" });
|
|
167
|
+
});
|
|
168
|
+
it("breaks a tie by start then id, so the latest run is never arbitrary", async () => {
|
|
169
|
+
// Two runs *can* finish in the same instant — a fixed clock, or simply a fast pair. An unstable answer
|
|
170
|
+
// here is a gate comparing against an arbitrary one of two runs, and it would be intermittent.
|
|
171
|
+
const store = await make();
|
|
172
|
+
for (const [id, startedAt] of [
|
|
173
|
+
["a", "2026-08-23T09:00:00.000Z"],
|
|
174
|
+
["b", "2026-08-23T09:30:00.000Z"],
|
|
175
|
+
]) {
|
|
176
|
+
await store.startRun({ tenantId: T1, id, release: "v1", startedAt });
|
|
177
|
+
await store.completeRun({ tenantId: T1, runId: id, finishedAt: FINISHED, graderVersions: {} });
|
|
178
|
+
}
|
|
179
|
+
// Same `finishedAt`; the later start wins.
|
|
180
|
+
expect((await store.latest({ tenantId: T1 }))?.id).toBe("b");
|
|
181
|
+
});
|
|
182
|
+
it("returns the latest completed run, never an in-flight one", async () => {
|
|
183
|
+
// An unfinished run's totals are partial, and comparing against one reports every case it has not reached
|
|
184
|
+
// yet as a regression.
|
|
185
|
+
const store = await started("done", "v1");
|
|
186
|
+
await store.recordCase({ tenantId: T1, runId: "done", result: result({ id: "a" }) });
|
|
187
|
+
await store.completeRun({ tenantId: T1, runId: "done", finishedAt: FINISHED, graderVersions: {} });
|
|
188
|
+
await store.startRun({ tenantId: T1, id: "running", release: "v2", startedAt: "2026-08-23T11:00:00.000Z" });
|
|
189
|
+
expect((await store.latest({ tenantId: T1 }))?.id).toBe("done");
|
|
190
|
+
});
|
|
191
|
+
it("returns null when the only run is still in flight", async () => {
|
|
192
|
+
// The discriminating case. With a completed run also present, an unfiltered `latest` can still return the
|
|
193
|
+
// right answer by accident — an undefined `finishedAt` sorts last — so the filter is only really tested
|
|
194
|
+
// when there is nothing else to fall back to. Found by sabotage.
|
|
195
|
+
const store = await started("running", "v1");
|
|
196
|
+
expect(await store.latest({ tenantId: T1 })).toBeNull();
|
|
197
|
+
expect(await store.latest({ tenantId: T1, release: "v1" })).toBeNull();
|
|
198
|
+
});
|
|
199
|
+
it("finds the latest run for one release", async () => {
|
|
200
|
+
const store = await started("v1-run", "v1");
|
|
201
|
+
await store.completeRun({ tenantId: T1, runId: "v1-run", finishedAt: FINISHED, graderVersions: {} });
|
|
202
|
+
await store.startRun({ tenantId: T1, id: "v2-run", release: "v2", startedAt: "2026-08-23T11:00:00.000Z" });
|
|
203
|
+
await store.completeRun({
|
|
204
|
+
tenantId: T1,
|
|
205
|
+
runId: "v2-run",
|
|
206
|
+
finishedAt: "2026-08-23T11:05:00.000Z",
|
|
207
|
+
graderVersions: {},
|
|
208
|
+
});
|
|
209
|
+
expect((await store.latest({ tenantId: T1, release: "v1" }))?.id).toBe("v1-run");
|
|
210
|
+
expect((await store.latest({ tenantId: T1 }))?.id).toBe("v2-run");
|
|
211
|
+
});
|
|
212
|
+
it("returns null when a release has never been scored", async () => {
|
|
213
|
+
const store = await started();
|
|
214
|
+
expect(await store.latest({ tenantId: T1, release: "never" })).toBeNull();
|
|
215
|
+
});
|
|
216
|
+
it("does not resolve another tenant's run", async () => {
|
|
217
|
+
// One tenant's quality gate is not another's business.
|
|
218
|
+
const store = await started();
|
|
219
|
+
expect(await store.get({ tenantId: T2, runId: "run-1" })).toBeNull();
|
|
220
|
+
expect(await store.latest({ tenantId: T2 })).toBeNull();
|
|
221
|
+
expect((await store.listCaseResults({ tenantId: T2, runId: "run-1", limit: 10 })).items).toEqual([]);
|
|
222
|
+
});
|
|
223
|
+
it("does not record into another tenant's run", async () => {
|
|
224
|
+
const store = await started();
|
|
225
|
+
expect(await store.recordCase({ tenantId: T2, runId: "run-1", result: result({ id: "a" }) })).toEqual({ recorded: false });
|
|
226
|
+
expect((await store.listCaseResults({ tenantId: T1, runId: "run-1", limit: 10 })).items).toEqual([]);
|
|
227
|
+
});
|
|
228
|
+
it("lists runs newest first", async () => {
|
|
229
|
+
const store = await make();
|
|
230
|
+
for (const [id, at] of [
|
|
231
|
+
["old", "2026-08-21T10:00:00.000Z"],
|
|
232
|
+
["new", "2026-08-23T10:00:00.000Z"],
|
|
233
|
+
["mid", "2026-08-22T10:00:00.000Z"],
|
|
234
|
+
]) {
|
|
235
|
+
await store.startRun({ tenantId: T1, id, release: id, startedAt: at });
|
|
236
|
+
}
|
|
237
|
+
const page = await store.list({ tenantId: T1, limit: 10 });
|
|
238
|
+
expect(page.items.map((r) => r.id)).toEqual(["new", "mid", "old"]);
|
|
239
|
+
});
|
|
240
|
+
it("pages case results without repeating or skipping one", async () => {
|
|
241
|
+
const store = await started();
|
|
242
|
+
for (const n of [1, 2, 3, 4, 5]) {
|
|
243
|
+
await store.recordCase({ tenantId: T1, runId: "run-1", result: result({ id: `case-${n}` }) });
|
|
244
|
+
}
|
|
245
|
+
const seen = [];
|
|
246
|
+
let cursor;
|
|
247
|
+
do {
|
|
248
|
+
const page = await store.listCaseResults({
|
|
249
|
+
tenantId: T1,
|
|
250
|
+
runId: "run-1",
|
|
251
|
+
limit: 2,
|
|
252
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
253
|
+
});
|
|
254
|
+
seen.push(...page.items.map((c) => c.caseId));
|
|
255
|
+
cursor = page.nextCursor;
|
|
256
|
+
} while (cursor !== undefined);
|
|
257
|
+
expect(seen).toEqual(["case-1", "case-2", "case-3", "case-4", "case-5"]);
|
|
258
|
+
});
|
|
259
|
+
it("completes an empty run as zero rather than refusing", async () => {
|
|
260
|
+
// A run that scored nothing has a mean of zero. NaN would propagate into every comparison that touched it.
|
|
261
|
+
const store = await started();
|
|
262
|
+
const run = await store.completeRun({
|
|
263
|
+
tenantId: T1,
|
|
264
|
+
runId: "run-1",
|
|
265
|
+
finishedAt: FINISHED,
|
|
266
|
+
graderVersions: {},
|
|
267
|
+
});
|
|
268
|
+
expect(run).toMatchObject({ total: 0, passed: 0, meanScore: 0, byDimension: [] });
|
|
269
|
+
});
|
|
270
|
+
it("recomputes aggregates when completed again", async () => {
|
|
271
|
+
// A re-completion after a corrected case must reflect it. Accumulated totals would keep the old number,
|
|
272
|
+
// and the number that gates a release must be derivable from its evidence.
|
|
273
|
+
const store = await started();
|
|
274
|
+
await store.recordCase({
|
|
275
|
+
tenantId: T1,
|
|
276
|
+
runId: "run-1",
|
|
277
|
+
result: result({ id: "a", verdict: { pass: false, score: 0, reason: "first" } }),
|
|
278
|
+
});
|
|
279
|
+
await store.completeRun({ tenantId: T1, runId: "run-1", finishedAt: FINISHED, graderVersions: {} });
|
|
280
|
+
await store.recordCase({
|
|
281
|
+
tenantId: T1,
|
|
282
|
+
runId: "run-1",
|
|
283
|
+
result: result({ id: "a", verdict: { pass: true, score: 1, reason: "fixed" } }),
|
|
284
|
+
});
|
|
285
|
+
const run = await store.completeRun({
|
|
286
|
+
tenantId: T1,
|
|
287
|
+
runId: "run-1",
|
|
288
|
+
finishedAt: "2026-08-23T10:10:00.000Z",
|
|
289
|
+
graderVersions: {},
|
|
290
|
+
});
|
|
291
|
+
expect(run).toMatchObject({ total: 1, passed: 1, meanScore: 1 });
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=evaluation.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `FileMetadataStore` and `FileContentStore` conformance (#129).
|
|
3
|
+
*
|
|
4
|
+
* Both carry safety properties rather than only a data contract, and both are asserted here rather than
|
|
5
|
+
* assumed:
|
|
6
|
+
*
|
|
7
|
+
* - **A `contentKey` from one tenant must not resolve another's bytes.** The key is opaque and a caller
|
|
8
|
+
* cannot construct one — but an adapter that took the key as sufficient would leak across tenants the
|
|
9
|
+
* moment a key was guessed, logged or copied. #91 found an `AgentStore` that accepted `TenantScope` and
|
|
10
|
+
* ignored it, which is why every adapter is asked the question directly.
|
|
11
|
+
* - **`transition` is a compare-and-set.** A blind write lets a conversation delete racing an upload leave a
|
|
12
|
+
* file `stored` after its bytes were scheduled for removal — bytes that then never get swept, because
|
|
13
|
+
* nothing is looking for a `stored` file's object.
|
|
14
|
+
*/
|
|
15
|
+
import type { FileContentStore, FileMetadataStore } from "../../persistence/index.js";
|
|
16
|
+
import { type FixtureOrStore } from "./parents.js";
|
|
17
|
+
export declare function fileMetadataStoreConformance(makeFixture: () => FixtureOrStore<FileMetadataStore>): void;
|
|
18
|
+
export declare function fileContentStoreConformance(make: () => FileContentStore): void;
|
|
19
|
+
//# sourceMappingURL=files.d.ts.map
|