@velum-labs/routekit-eval-setup 1.0.22 → 1.0.23

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.
@@ -14,7 +14,7 @@ export type EvalProjectArtifactsShape = {
14
14
  readonly loadEvaluationsApproval: (repositoryRoot: string) => Effect.Effect<EvalArtifactApproval | undefined, EvalProjectArtifactError, never>;
15
15
  readonly saveEvaluationsApproval: (repositoryRoot: string, approval: EvalArtifactApproval) => Effect.Effect<void, EvalProjectArtifactError, never>;
16
16
  readonly savePlan: (repositoryRoot: string, plan: EvalExecutionPlan) => Effect.Effect<void, EvalProjectArtifactError, never>;
17
- readonly materializePlanSuites: (repositoryRoot: string, plan: EvalExecutionPlan, proposal: EvalEvaluationProposal) => Effect.Effect<void, EvalProjectArtifactError, never>;
17
+ readonly materializePlanSuites: (repositoryRoot: string, plan: EvalExecutionPlan, proposal: EvalEvaluationProposal, mode?: "create" | "rematerialize") => Effect.Effect<void, EvalProjectArtifactError, never>;
18
18
  readonly planSuitePath: (repositoryRoot: string, planId: string, dimensionId: string) => Effect.Effect<string, EvalProjectArtifactError, never>;
19
19
  readonly compositionSuitePath: (repositoryRoot: string, planId: string) => Effect.Effect<string, EvalProjectArtifactError, never>;
20
20
  readonly loadPlan: (repositoryRoot: string, planId: string) => Effect.Effect<EvalExecutionPlan | undefined, EvalProjectArtifactError, never>;
@@ -234,7 +234,7 @@ export const makeFileEvalProjectArtifacts = Effect.gen(function* () {
234
234
  loadEvaluationsApproval: (repositoryRoot) => read(repositoryRoot, EVALUATIONS_APPROVAL, Schema.decodeUnknownEffect(EvalArtifactApprovalSchema)),
235
235
  saveEvaluationsApproval: (repositoryRoot, approval) => write(repositoryRoot, EVALUATIONS_APPROVAL, approval),
236
236
  savePlan: (repositoryRoot, plan) => write(repositoryRoot, paths.join("plans", `${plan.planId}.json`), plan, false),
237
- materializePlanSuites: (repositoryRoot, plan, proposal) => Effect.gen(function* () {
237
+ materializePlanSuites: (repositoryRoot, plan, proposal, mode = "create") => Effect.gen(function* () {
238
238
  yield* requireArtifactId("plan", plan.planId);
239
239
  const suites = new Map(proposal.suites.map((suite) => [suite.dimensionId, suite]));
240
240
  for (const selection of plan.selectedCaseIds) {
@@ -254,8 +254,8 @@ export const makeFileEvalProjectArtifacts = Effect.gen(function* () {
254
254
  return testCase;
255
255
  });
256
256
  const suiteRoot = paths.join("plans", plan.planId, "dimensions", selection.dimensionId);
257
- yield* writeText(repositoryRoot, paths.join(suiteRoot, `${selection.dimensionId}.eval.ts`), renderDimensionSuite(), false);
258
- yield* write(repositoryRoot, paths.join(suiteRoot, "data", "cases.json"), cases, false);
257
+ yield* writeText(repositoryRoot, paths.join(suiteRoot, `${selection.dimensionId}.eval.ts`), renderDimensionSuite(), mode === "rematerialize");
258
+ yield* write(repositoryRoot, paths.join(suiteRoot, "data", "cases.json"), cases, mode === "rematerialize");
259
259
  yield* write(repositoryRoot, paths.join(suiteRoot, "routekit.eval-manifest.json"), {
260
260
  version: EVAL_PROJECT_VERSION,
261
261
  profileId: selection.dimensionId,
@@ -265,7 +265,7 @@ export const makeFileEvalProjectArtifacts = Effect.gen(function* () {
265
265
  caseIds: cases.map((testCase) => testCase.id),
266
266
  maxOutputTokens: suite.maximumOutputTokens,
267
267
  expectedCallCount: cases.length * plan.candidateModels.length * 2
268
- }, false);
268
+ }, mode === "rematerialize");
269
269
  }
270
270
  const compositionById = new Map(proposal.compositionSuite.cases.map((testCase) => [testCase.id, testCase]));
271
271
  const compositionCases = plan.selectedCompositionCaseIds.map((caseId) => {
@@ -276,8 +276,8 @@ export const makeFileEvalProjectArtifacts = Effect.gen(function* () {
276
276
  return testCase;
277
277
  });
278
278
  const compositionRoot = paths.join("plans", plan.planId, "composition");
279
- yield* writeText(repositoryRoot, paths.join(compositionRoot, "composition.eval.ts"), renderDimensionSuite(), false);
280
- yield* write(repositoryRoot, paths.join(compositionRoot, "data", "cases.json"), compositionCases, false);
279
+ yield* writeText(repositoryRoot, paths.join(compositionRoot, "composition.eval.ts"), renderDimensionSuite(), mode === "rematerialize");
280
+ yield* write(repositoryRoot, paths.join(compositionRoot, "data", "cases.json"), compositionCases, mode === "rematerialize");
281
281
  yield* write(repositoryRoot, paths.join(compositionRoot, "routekit.eval-manifest.json"), {
282
282
  version: EVAL_PROJECT_VERSION,
283
283
  profileId: "composition",
@@ -287,7 +287,7 @@ export const makeFileEvalProjectArtifacts = Effect.gen(function* () {
287
287
  caseIds: compositionCases.map((testCase) => testCase.id),
288
288
  maxOutputTokens: proposal.compositionSuite.maximumOutputTokens,
289
289
  expectedCallCount: compositionCases.length * plan.candidateModels.length * 2
290
- }, false);
290
+ }, mode === "rematerialize");
291
291
  }).pipe(Effect.mapError((cause) => cause instanceof EvalProjectArtifactError
292
292
  ? cause
293
293
  : artifactFailure("writing", artifactPath(repositoryRoot, paths.join("plans", plan.planId)), cause))),
@@ -782,6 +782,13 @@ export const makeEvalProjectWorkflow = Effect.gen(function* () {
782
782
  plan.judgeModel !== state.configuration.judgeModel) {
783
783
  return yield* transitionError(state.stage, "execution plan is stale or does not match the approved project");
784
784
  }
785
+ const proposal = yield* artifacts.loadEvaluationProposal(root);
786
+ if (proposal === undefined ||
787
+ proposal.evaluationDigest !== plan.evaluationDigest ||
788
+ proposal.basisDigest !== plan.basisDigest) {
789
+ return yield* transitionError(state.stage, "the approved evaluation proposal for the execution plan is missing or stale");
790
+ }
791
+ yield* artifacts.materializePlanSuites(root, plan, proposal, "rematerialize");
785
792
  const now = yield* isoNow;
786
793
  const runId = crypto.randomUUID();
787
794
  const next = {
@@ -621,6 +621,55 @@ test("failed runs preserve plan revisions for retry while mismatched plans remai
621
621
  assert.equal(rerun.ready.state.evaluationDigest, plan.evaluationDigest);
622
622
  assert.equal(rerun.second.plan.planId, plan.planId);
623
623
  });
624
+ test("startRun rematerializes stale dimension and composition suite templates", async () => {
625
+ const root = await makeRepository();
626
+ const plan = await createPlan(root, "pilot");
627
+ const selection = plan.selectedCaseIds[0];
628
+ const planRoot = path.join(root, ".routekit", "evals", "plans", plan.planId);
629
+ const dimensionRoot = path.join(planRoot, "dimensions", selection.dimensionId);
630
+ const dimensionSuitePath = path.join(dimensionRoot, `${selection.dimensionId}.eval.ts`);
631
+ const compositionRoot = path.join(planRoot, "composition");
632
+ const compositionSuitePath = path.join(compositionRoot, "composition.eval.ts");
633
+ const dimensionManifestPath = path.join(dimensionRoot, "routekit.eval-manifest.json");
634
+ const compositionManifestPath = path.join(compositionRoot, "routekit.eval-manifest.json");
635
+ const staleTemplate = `// generated by RouteKit 1.0.21
636
+ run.toComplete();
637
+ await judge.autoEvals({ criteria: testCase.rubric, prompt, run });
638
+ `;
639
+ await Promise.all([
640
+ writeFile(dimensionSuitePath, staleTemplate),
641
+ writeFile(compositionSuitePath, staleTemplate)
642
+ ]);
643
+ const [dimensionManifestBefore, compositionManifestBefore] = await Promise.all([
644
+ readFile(dimensionManifestPath, "utf8"),
645
+ readFile(compositionManifestPath, "utf8")
646
+ ]);
647
+ await Effect.runPromise(Effect.gen(function* () {
648
+ const workflow = yield* EvalProjectWorkflow;
649
+ yield* workflow.startRun(root, plan.planId);
650
+ }).pipe(Effect.provide(ProjectWorkflowTestLive)));
651
+ const [dimensionSuite, compositionSuite, dimensionManifestAfter, compositionManifestAfter] = await Promise.all([
652
+ readFile(dimensionSuitePath, "utf8"),
653
+ readFile(compositionSuitePath, "utf8"),
654
+ readFile(dimensionManifestPath, "utf8"),
655
+ readFile(compositionManifestPath, "utf8")
656
+ ]);
657
+ for (const suite of [dimensionSuite, compositionSuite]) {
658
+ const completionAssertion = suite.indexOf("run.toComplete();");
659
+ const judgeCall = suite.indexOf("await judge.autoEvals");
660
+ const completionRethrow = suite.indexOf("throw candidateCompletionError;");
661
+ assert.doesNotMatch(suite, /generated by RouteKit 1\.0\.21/u);
662
+ assert.ok(completionAssertion >= 0);
663
+ assert.ok(judgeCall > completionAssertion);
664
+ assert.ok(completionRethrow > judgeCall);
665
+ }
666
+ assert.equal(dimensionManifestAfter, dimensionManifestBefore);
667
+ assert.equal(compositionManifestAfter, compositionManifestBefore);
668
+ const dimensionManifest = JSON.parse(dimensionManifestAfter);
669
+ const compositionManifest = JSON.parse(compositionManifestAfter);
670
+ assert.equal(dimensionManifest.expectedCallCount, selection.caseIds.length * plan.candidateModels.length * 2);
671
+ assert.equal(compositionManifest.expectedCallCount, plan.selectedCompositionCaseIds.length * plan.candidateModels.length * 2);
672
+ });
624
673
  test("failed run reports persist the nested qualification error chain", async () => {
625
674
  const root = await makeRepository();
626
675
  const plan = await createPlan(root, "full");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@velum-labs/routekit-eval-setup",
3
3
  "private": false,
4
- "version": "1.0.22",
4
+ "version": "1.0.23",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/velum-labs/routekit.git",
@@ -32,8 +32,8 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "effect": "4.0.0-rc.108",
35
- "@velum-labs/routekit-eval-contracts": "1.0.22",
36
- "@velum-labs/routekit-runtime": "1.0.22"
35
+ "@velum-labs/routekit-eval-contracts": "1.0.23",
36
+ "@velum-labs/routekit-runtime": "1.0.23"
37
37
  },
38
38
  "keywords": [
39
39
  "routekit",