@velum-labs/routekit-eval-setup 1.0.26 → 1.1.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.
Files changed (38) hide show
  1. package/dist/adapters/git-basis-snapshot.d.ts +27 -0
  2. package/dist/adapters/git-basis-snapshot.js +178 -0
  3. package/dist/basis-candidates.d.ts +72 -0
  4. package/dist/basis-candidates.js +157 -0
  5. package/dist/basis-projection.d.ts +9 -0
  6. package/dist/basis-projection.js +30 -0
  7. package/dist/basis-validation.d.ts +38 -0
  8. package/dist/basis-validation.js +187 -0
  9. package/dist/effect-api.d.ts +8 -0
  10. package/dist/effect-api.js +5 -0
  11. package/dist/errors.d.ts +11 -1
  12. package/dist/errors.js +5 -0
  13. package/dist/index.d.ts +15 -3
  14. package/dist/index.js +10 -2
  15. package/dist/project-artifacts.d.ts +26 -1
  16. package/dist/project-artifacts.js +52 -1
  17. package/dist/project-contracts.d.ts +3594 -0
  18. package/dist/project-contracts.js +344 -1
  19. package/dist/project-store.d.ts +3 -1
  20. package/dist/project-store.js +39 -33
  21. package/dist/project-workflow.d.ts +2 -2
  22. package/dist/services/basis-authoring/service.d.ts +253 -0
  23. package/dist/services/basis-authoring/service.js +547 -0
  24. package/dist/services/basis-onboarding/service.d.ts +48 -0
  25. package/dist/services/basis-onboarding/service.js +491 -0
  26. package/dist/services/basis-repair/service.d.ts +25 -0
  27. package/dist/services/basis-repair/service.js +249 -0
  28. package/dist/test/basis-authoring.test.d.ts +1 -0
  29. package/dist/test/basis-authoring.test.js +194 -0
  30. package/dist/test/basis-onboarding.test.d.ts +1 -0
  31. package/dist/test/basis-onboarding.test.js +385 -0
  32. package/dist/test/basis-repair.test.d.ts +1 -0
  33. package/dist/test/basis-repair.test.js +338 -0
  34. package/dist/test/basis-v3.test.d.ts +1 -0
  35. package/dist/test/basis-v3.test.js +262 -0
  36. package/dist/test/project-v2.test.d.ts +1 -0
  37. package/dist/test/project-v2.test.js +99 -0
  38. package/package.json +4 -3
@@ -0,0 +1,385 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFileSync } from "node:fs";
3
+ import { mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { after, test } from "node:test";
7
+ import { execFile } from "node:child_process";
8
+ import { promisify } from "node:util";
9
+ import { layer as NodeServicesLayer } from "@effect/platform-node/NodeServices";
10
+ import { routingBasisDigestV3, routingContractDigest } from "@velum-labs/routekit-eval-contracts";
11
+ import { Effect, Layer } from "effect";
12
+ import { captureFrozenRepositorySnapshotV3 } from "../adapters/git-basis-snapshot.js";
13
+ import { evaluateBasisCandidateValidationV3, makeBasisCandidateDatasetV3 } from "../basis-validation.js";
14
+ import { EvalProjectArtifacts, EvalProjectArtifactsLive } from "../project-artifacts.js";
15
+ import { EvalProjectStoreLive } from "../project-store.js";
16
+ import { BasisOnboarding, BasisOnboardingLive } from "../services/basis-onboarding/service.js";
17
+ const execFilePromise = promisify(execFile);
18
+ const roots = [];
19
+ after(async () => Promise.all(roots.map((root) => rm(root, { recursive: true, force: true }))));
20
+ const configuration = {
21
+ workloadDescription: "RouteKit repository changes",
22
+ candidateModels: ["openai/a", "openai/b"],
23
+ classifierModel: "openai/classifier",
24
+ authorModel: "openai/author",
25
+ judgeModel: "openai/judge",
26
+ objective: { kind: "highest-quality" },
27
+ maximumUnknownWeight: 0.3
28
+ };
29
+ const basisFixture = () => JSON.parse(readFileSync(new URL("../../../../test/fixtures/routing-v3/examples/routing-basis-v3.example.json", import.meta.url), "utf8"));
30
+ const task = (index) => ({
31
+ caseId: `case-${String(index).padStart(3, "0")}`,
32
+ occurredAt: new Date(Date.UTC(2026, 0, 1) + index * 86_400_000).toISOString(),
33
+ taskDigest: routingContractDigest("routekit.test-task.v1", "task", index),
34
+ summary: `Task ${String(index)}`,
35
+ changedPathDigest: routingContractDigest("routekit.test-paths.v1", "paths", [`src/${String(index)}.ts`]),
36
+ changedPaths: [`src/${String(index)}.ts`]
37
+ });
38
+ const candidateFor = (recipeId, repositoryDigest, deterministic) => {
39
+ const source = basisFixture();
40
+ const semanticBasis = {
41
+ ...source,
42
+ basisId: recipeId,
43
+ basisDigest: "0".repeat(64),
44
+ repositoryDigest,
45
+ facetSnapshot: {
46
+ ...source.facetSnapshot,
47
+ repositoryDigest
48
+ },
49
+ provenance: {
50
+ ...source.provenance,
51
+ recipeId,
52
+ ...(deterministic
53
+ ? {
54
+ origin: {
55
+ kind: "incumbent-enriched",
56
+ sourceBasisId: source.basisId,
57
+ sourceBasisVersion: source.version
58
+ }
59
+ }
60
+ : {
61
+ authorModel: configuration.authorModel,
62
+ origin: { kind: "generated" }
63
+ })
64
+ }
65
+ };
66
+ const basis = {
67
+ ...semanticBasis,
68
+ basisDigest: routingBasisDigestV3(semanticBasis)
69
+ };
70
+ return {
71
+ candidateId: basis.basisId,
72
+ recipeId,
73
+ basis,
74
+ basisDigest: basis.basisDigest,
75
+ ...(deterministic ? {} : { authorModel: configuration.authorModel }),
76
+ generationConfigurationDigest: routingContractDigest("routekit.test-generation.v1", "configuration", recipeId),
77
+ evidenceDigest: routingContractDigest("routekit.test-evidence.v1", "evidence", { recipeId, repositoryDigest }),
78
+ createdAt: "2026-08-23T00:00:00.000Z"
79
+ };
80
+ };
81
+ async function repository() {
82
+ const root = await mkdtemp(path.join(os.tmpdir(), "routekit-basis-onboarding-"));
83
+ roots.push(root);
84
+ await execFilePromise("git", ["init", "-q", root]);
85
+ await execFilePromise("git", [
86
+ "-C",
87
+ root,
88
+ "config",
89
+ "user.name",
90
+ "RouteKit Test"
91
+ ]);
92
+ await execFilePromise("git", [
93
+ "-C",
94
+ root,
95
+ "config",
96
+ "user.email",
97
+ "routekit@example.test"
98
+ ]);
99
+ await writeFile(path.join(root, "README.md"), "# Repository\n");
100
+ await execFilePromise("git", ["-C", root, "add", "README.md"]);
101
+ await execFilePromise("git", [
102
+ "-C",
103
+ root,
104
+ "commit",
105
+ "-q",
106
+ "-m",
107
+ "initial"
108
+ ]);
109
+ return root;
110
+ }
111
+ const OnboardingTestLive = BasisOnboardingLive.pipe(Layer.provide(EvalProjectArtifactsLive), Layer.provide(EvalProjectStoreLive), Layer.provide(NodeServicesLayer));
112
+ test("Git snapshot capture binds commit, tree, inventory, and clean status", async () => {
113
+ const root = await repository();
114
+ const first = await Effect.runPromise(captureFrozenRepositorySnapshotV3({
115
+ repositoryRoot: root,
116
+ requestedRef: "HEAD",
117
+ allowDirty: false,
118
+ capturedAt: "2026-08-23T00:00:00.000Z"
119
+ }));
120
+ const second = await Effect.runPromise(captureFrozenRepositorySnapshotV3({
121
+ repositoryRoot: root,
122
+ requestedRef: "HEAD",
123
+ allowDirty: false,
124
+ capturedAt: "2026-08-23T00:01:00.000Z"
125
+ }));
126
+ assert.equal(first.snapshot.dirty, false);
127
+ assert.equal(first.snapshot.repositoryDigest, second.snapshot.repositoryDigest);
128
+ assert.deepEqual(first.sourceInventory, ["README.md"]);
129
+ assert.match(first.snapshot.commit, /^[0-9a-f]{40,64}$/u);
130
+ assert.match(first.snapshot.tree, /^[0-9a-f]{40,64}$/u);
131
+ });
132
+ test("Git snapshot refuses dirty worktrees unless lab-only dirty evidence is explicit", async () => {
133
+ const root = await repository();
134
+ await writeFile(path.join(root, "README.md"), "# Changed\n");
135
+ await writeFile(path.join(root, "untracked.txt"), "private test content\n");
136
+ await assert.rejects(Effect.runPromise(captureFrozenRepositorySnapshotV3({
137
+ repositoryRoot: root,
138
+ requestedRef: "HEAD",
139
+ allowDirty: false,
140
+ capturedAt: "2026-08-23T00:00:00.000Z"
141
+ })), /repository is dirty/);
142
+ const captured = await Effect.runPromise(captureFrozenRepositorySnapshotV3({
143
+ repositoryRoot: root,
144
+ requestedRef: "HEAD",
145
+ allowDirty: true,
146
+ capturedAt: "2026-08-23T00:00:00.000Z"
147
+ }));
148
+ assert.equal(captured.snapshot.dirty, true);
149
+ assert.match(captured.snapshot.dirtyPatchDigest ?? "", /^[0-9a-f]{64}$/u);
150
+ assert.deepEqual(captured.sourceInventory, ["README.md"]);
151
+ });
152
+ test("basis onboarding initializes once, reserves seven author jobs, and resumes", async () => {
153
+ const root = await repository();
154
+ const first = await Effect.runPromise(Effect.gen(function* () {
155
+ const onboarding = yield* BasisOnboarding;
156
+ return yield* onboarding.initialize({
157
+ repositoryRoot: root,
158
+ requestedRef: "HEAD",
159
+ allowDirty: false,
160
+ configuration,
161
+ tasks: []
162
+ });
163
+ }).pipe(Effect.provide(OnboardingTestLive)));
164
+ assert.equal(first.stage, "basis-generation-ready");
165
+ assert.equal(first.generationPlan.recipes.length, 8);
166
+ assert.equal(first.generationPlan.plannedPrimaryCalls, 7);
167
+ assert.equal(first.generationPlan.maximumAttempts, 14);
168
+ assert.equal(first.split.provisional, true);
169
+ const resumed = await Effect.runPromise(Effect.gen(function* () {
170
+ const onboarding = yield* BasisOnboarding;
171
+ return yield* onboarding.initialize({
172
+ repositoryRoot: root,
173
+ requestedRef: "HEAD",
174
+ allowDirty: false,
175
+ configuration: { ...configuration, authorModel: "openai/changed" },
176
+ tasks: []
177
+ });
178
+ }).pipe(Effect.provide(OnboardingTestLive)));
179
+ assert.equal(resumed.projectId, first.projectId);
180
+ assert.equal(resumed.generationPlan.planDigest, first.generationPlan.planDigest);
181
+ assert.equal(JSON.parse(await readFile(path.join(root, ".routekit", "evals", "project.json"), "utf8")).version, 2);
182
+ });
183
+ test("basis onboarding persists exact immutable artifacts through qualification", async () => {
184
+ const root = await repository();
185
+ const tasks = Array.from({ length: 50 }, (_, index) => task(index));
186
+ const initialized = await Effect.runPromise(Effect.gen(function* () {
187
+ const onboarding = yield* BasisOnboarding;
188
+ return yield* onboarding.initialize({
189
+ repositoryRoot: root,
190
+ requestedRef: "HEAD",
191
+ allowDirty: false,
192
+ configuration,
193
+ tasks
194
+ });
195
+ }).pipe(Effect.provide(OnboardingTestLive)));
196
+ assert.equal(initialized.stage, "basis-generation-ready");
197
+ const candidates = initialized.generationPlan.recipes.map((recipe) => candidateFor(recipe.id, initialized.snapshot.repositoryDigest, recipe.deterministic));
198
+ const jobs = initialized.generationLedger.jobs.map((job) => ({
199
+ ...job,
200
+ status: "completed",
201
+ candidateId: candidates.find((candidate) => candidate.recipeId === job.recipeId).candidateId,
202
+ attemptCount: 1,
203
+ inputTokens: 100,
204
+ outputTokens: 50,
205
+ callIds: [`call-${job.recipeId}`]
206
+ }));
207
+ const ledgerSemantic = {
208
+ version: 1,
209
+ planDigest: initialized.generationPlan.planDigest,
210
+ jobs
211
+ };
212
+ const generationLedger = {
213
+ ...ledgerSemantic,
214
+ ledgerDigest: routingContractDigest("routekit.basis-generation-ledger.v1", "ledger", ledgerSemantic)
215
+ };
216
+ const reviewed = await Effect.runPromise(Effect.gen(function* () {
217
+ const onboarding = yield* BasisOnboarding;
218
+ return yield* onboarding.recordCandidates({
219
+ repositoryRoot: root,
220
+ generationLedger,
221
+ candidates
222
+ });
223
+ }).pipe(Effect.provide(OnboardingTestLive)));
224
+ assert.equal(reviewed.stage, "basis-candidates-review");
225
+ assert.equal(reviewed.revision, 1);
226
+ assert.equal(reviewed.structureReports.length, 8);
227
+ assert.ok(reviewed.structureReports.every((report) => report.eligible));
228
+ const labelProtocol = {
229
+ version: 1,
230
+ protocolDigest: "c".repeat(64),
231
+ kind: "reviewed-human",
232
+ author: "Author",
233
+ reviewer: "Reviewer",
234
+ blindness: "candidate-output-hidden",
235
+ activeDimensionThreshold: 0.25,
236
+ unknownThreshold: 0.3
237
+ };
238
+ const datasets = reviewed.candidates.map((candidate) => makeBasisCandidateDatasetV3({
239
+ splitDigest: reviewed.split.splitDigest,
240
+ candidate,
241
+ tasks: reviewed.split.validation,
242
+ labelProtocol,
243
+ labels: reviewed.split.validation.map((entry) => ({
244
+ caseId: entry.caseId,
245
+ basisDigest: candidate.basisDigest,
246
+ scores: candidate.basis.dimensions.map((dimension, index) => ({
247
+ dimensionId: dimension.id,
248
+ score: index === 0 ? 1 : 0
249
+ })),
250
+ unknownProbability: 0
251
+ }))
252
+ }));
253
+ const validationPlanSemantic = {
254
+ version: 1,
255
+ repositoryDigest: reviewed.snapshot.repositoryDigest,
256
+ splitDigest: reviewed.split.splitDigest,
257
+ classifierBindings: reviewed.candidates.map((candidate) => ({
258
+ candidateId: candidate.candidateId,
259
+ basisDigest: candidate.basisDigest,
260
+ classifierConfigurationDigest: routingContractDigest("routekit.test-classifier-configuration.v1", "configuration", candidate.basisDigest)
261
+ })),
262
+ candidateIds: reviewed.structureReports
263
+ .filter((report) => report.eligible)
264
+ .map((report) => report.candidateId),
265
+ datasetDigests: datasets.map((dataset) => dataset.datasetDigest),
266
+ repetitions: 1,
267
+ plannedPrimaryCalls: datasets.length * reviewed.split.validation.length,
268
+ maximumAttempts: datasets.length * reviewed.split.validation.length * 2,
269
+ requiredTotalInputTokens: 1_000_000,
270
+ requiredTotalOutputTokens: 100_000,
271
+ acknowledgeUnpricedExposure: true
272
+ };
273
+ const validationPlan = {
274
+ ...validationPlanSemantic,
275
+ planDigest: routingContractDigest("routekit.basis-validation-plan.v1", "plan", validationPlanSemantic)
276
+ };
277
+ const validationReady = await Effect.runPromise(Effect.gen(function* () {
278
+ const onboarding = yield* BasisOnboarding;
279
+ return yield* onboarding.prepareValidation({
280
+ repositoryRoot: root,
281
+ datasets,
282
+ validationPlan
283
+ });
284
+ }).pipe(Effect.provide(OnboardingTestLive)));
285
+ assert.equal(validationReady.stage, "basis-validation-ready");
286
+ assert.equal(validationReady.revision, 2);
287
+ const validationResults = validationReady.candidates.map((candidate, index) => {
288
+ const dataset = validationReady.datasets[index];
289
+ return evaluateBasisCandidateValidationV3({
290
+ candidate,
291
+ dataset,
292
+ validationPlan: validationReady.validationPlan,
293
+ predictions: dataset.labels.map((label) => ({
294
+ ...label,
295
+ classifierConfigurationDigest: validationReady.validationPlan.classifierBindings[index]
296
+ .classifierConfigurationDigest
297
+ })),
298
+ provisional: validationReady.split.provisional
299
+ });
300
+ });
301
+ const validationReview = await Effect.runPromise(Effect.gen(function* () {
302
+ const onboarding = yield* BasisOnboarding;
303
+ return yield* onboarding.recordValidation({
304
+ repositoryRoot: root,
305
+ validationResults
306
+ });
307
+ }).pipe(Effect.provide(OnboardingTestLive)));
308
+ assert.equal(validationReview.stage, "basis-validation-review");
309
+ assert.equal(validationReview.revision, 3);
310
+ assert.equal(await stat(path.join(root, ".routekit", "evals", "basis", "validation-results.v3.json")).then((info) => info.mode & 0o777), 0o600);
311
+ const qualified = await Effect.runPromise(Effect.gen(function* () {
312
+ const onboarding = yield* BasisOnboarding;
313
+ return yield* onboarding.qualify({
314
+ repositoryRoot: root,
315
+ candidateId: validationReview.selection.selectedCandidateId,
316
+ basisDigest: validationReview.candidates.find((candidate) => candidate.candidateId ===
317
+ validationReview.selection.selectedCandidateId).basisDigest,
318
+ approvedBy: "Reviewer"
319
+ });
320
+ }).pipe(Effect.provide(OnboardingTestLive)));
321
+ assert.equal(qualified.stage, "basis-qualified");
322
+ assert.equal(qualified.revision, 4);
323
+ assert.equal(qualified.approval.approvedBy, "Reviewer");
324
+ assert.equal(qualified.validationResults.length, 8);
325
+ assert.deepEqual(qualified.validationResults, validationResults);
326
+ assert.equal(await stat(path.join(root, ".routekit", "evals", "basis", "evaluation-projection.v3.json")).then((info) => info.mode & 0o777), 0o600);
327
+ const resumed = await Effect.runPromise(Effect.gen(function* () {
328
+ const onboarding = yield* BasisOnboarding;
329
+ return yield* onboarding.qualify({
330
+ repositoryRoot: root,
331
+ candidateId: qualified.approval.candidateId,
332
+ basisDigest: qualified.approval.basisDigest,
333
+ approvedBy: "Reviewer"
334
+ });
335
+ }).pipe(Effect.provide(OnboardingTestLive)));
336
+ assert.equal(resumed.revision, qualified.revision);
337
+ for (const artifact of [
338
+ "generation.v3.json",
339
+ "candidates.v3.json",
340
+ "validation.v3.json",
341
+ "qualification.v3.json"
342
+ ]) {
343
+ const artifactPath = path.join(root, ".routekit", "evals", "basis", artifact);
344
+ assert.equal((await stat(artifactPath)).mode & 0o777, 0o600);
345
+ }
346
+ });
347
+ test("basis validation checkpoints round-trip by immutable job identity", async () => {
348
+ const root = await repository();
349
+ const checkpoint = {
350
+ version: 1,
351
+ validationPlanDigest: "a".repeat(64),
352
+ candidateId: "candidate",
353
+ basisDigest: "b".repeat(64),
354
+ validationDatasetDigest: "c".repeat(64),
355
+ caseId: "case-001",
356
+ repetition: 0,
357
+ classifierConfigurationDigest: "d".repeat(64),
358
+ status: "completed",
359
+ prediction: {
360
+ caseId: "case-001",
361
+ basisDigest: "b".repeat(64),
362
+ scores: [{ dimensionId: "dimension", score: 1 }],
363
+ unknownProbability: 0,
364
+ classifierConfigurationDigest: "d".repeat(64)
365
+ },
366
+ attemptCount: 1,
367
+ callIds: ["call-1"],
368
+ knownInputTokens: 10,
369
+ knownOutputTokens: 5,
370
+ knownCostUsd: 0.01,
371
+ unpricedCalls: 0,
372
+ unknownUsageCalls: 0
373
+ };
374
+ const loaded = await Effect.runPromise(Effect.gen(function* () {
375
+ const artifacts = yield* EvalProjectArtifacts;
376
+ yield* artifacts.saveBasisValidationJobCheckpointV3(root, checkpoint);
377
+ return yield* artifacts.loadBasisValidationJobCheckpointV3(root, {
378
+ validationPlanDigest: checkpoint.validationPlanDigest,
379
+ candidateId: checkpoint.candidateId,
380
+ caseId: checkpoint.caseId,
381
+ repetition: checkpoint.repetition
382
+ });
383
+ }).pipe(Effect.provide(EvalProjectArtifactsLive.pipe(Layer.provide(NodeServicesLayer)))));
384
+ assert.deepEqual(loaded, checkpoint);
385
+ });
@@ -0,0 +1 @@
1
+ export {};