@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,249 @@
1
+ import { assertRoutingBasisV3, routingContractDigest } from "@velum-labs/routekit-eval-contracts";
2
+ import { Clock, Context, Effect, Layer, Schema } from "effect";
3
+ import { readFrozenRepositorySourcesV3 } from "../../adapters/git-basis-snapshot.js";
4
+ import { BASIS_AUTHOR_INPUT_TOKENS_PER_ATTEMPT, BASIS_AUTHOR_OUTPUT_TOKENS_PER_ATTEMPT } from "../../basis-candidates.js";
5
+ import { EvalProjectAuthoringError, EvalProjectTransitionError } from "../../errors.js";
6
+ import { EvalProjectArtifacts } from "../../project-artifacts.js";
7
+ import { EvalAuthoringTransport } from "../../project-authoring.js";
8
+ import { EVAL_PROJECT_V2_VERSION } from "../../project-contracts.js";
9
+ import { EvalProjectStore } from "../../project-store.js";
10
+ import { AUTHORED_BASIS_JSON_SCHEMA, AuthoredBasis, withBasisDigest } from "../basis-authoring/service.js";
11
+ const REPAIR_INSTRUCTIONS = [
12
+ "Repair one immutable RouteKit Classification V3 Routing Basis.",
13
+ "Return the same flat number of peer workload dimensions as the source basis.",
14
+ "Use only the supplied source basis, structural warnings, reviewed error categories, frozen repository sources, and construction tasks.",
15
+ "Do not infer or mention validation labels, validation predictions, metric values, final tasks, final changed paths, routing models, model winners, provider traces, credentials, or hidden reasoning.",
16
+ "Preserve the repository identity and improve semantic separability without introducing repository-facet, file-type, test-layer, documentation-layer, or implementation-layer dimensions.",
17
+ "Return only strict structured JSON."
18
+ ].join("\n");
19
+ const failure = (detail, cause) => new EvalProjectAuthoringError({
20
+ operation: "authoring-basis",
21
+ detail,
22
+ ...(cause === undefined ? {} : { cause })
23
+ });
24
+ const transition = (state, detail) => new EvalProjectTransitionError({ state, detail });
25
+ function expectedSummaryDigest(summary) {
26
+ return routingContractDigest("routekit.basis-repair-summary.v1", "summary", {
27
+ version: summary.version,
28
+ sourceCandidateId: summary.sourceCandidateId,
29
+ sourceBasisDigest: summary.sourceBasisDigest,
30
+ errorCategories: summary.errorCategories
31
+ });
32
+ }
33
+ export class BasisRepair extends Context.Service()("@velum-labs/routekit-eval-setup/BasisRepair") {
34
+ }
35
+ export const makeBasisRepair = Effect.gen(function* () {
36
+ const store = yield* EvalProjectStore;
37
+ const artifacts = yield* EvalProjectArtifacts;
38
+ const requireReview = (repositoryRoot) => store.loadDocument(repositoryRoot).pipe(Effect.flatMap((document) => document === undefined ||
39
+ document.version !== EVAL_PROJECT_V2_VERSION ||
40
+ (document.stage !== "basis-validation-review" &&
41
+ document.stage !== "basis-qualified")
42
+ ? Effect.fail(transition(document?.stage ?? "missing", "basis repair requires completed validation review"))
43
+ : Effect.succeed(document)));
44
+ return BasisRepair.of({
45
+ plan: (input) => Effect.gen(function* () {
46
+ const state = yield* requireReview(input.repositoryRoot);
47
+ if (input.sourceCandidateId !== state.selection.selectedCandidateId) {
48
+ return yield* transition(state.stage, "repair must start from the persisted selected original candidate");
49
+ }
50
+ const source = state.candidates.find((candidate) => candidate.candidateId === input.sourceCandidateId);
51
+ if (source === undefined ||
52
+ input.summary.sourceCandidateId !== source.candidateId ||
53
+ input.summary.sourceBasisDigest !== source.basisDigest ||
54
+ input.summary.summaryDigest !==
55
+ expectedSummaryDigest(input.summary)) {
56
+ return yield* transition(state.stage, "repair summary does not bind the exact selected candidate");
57
+ }
58
+ const semantic = {
59
+ version: 1,
60
+ repositoryDigest: state.snapshot.repositoryDigest,
61
+ sourceCandidateId: source.candidateId,
62
+ sourceBasisDigest: source.basisDigest,
63
+ summaryDigest: input.summary.summaryDigest,
64
+ authorModel: input.authorModel,
65
+ plannedPrimaryCalls: 1,
66
+ maximumAttempts: 2,
67
+ requiredTotalInputTokens: BASIS_AUTHOR_INPUT_TOKENS_PER_ATTEMPT * 2,
68
+ requiredTotalOutputTokens: BASIS_AUTHOR_OUTPUT_TOKENS_PER_ATTEMPT * 2
69
+ };
70
+ return {
71
+ ...semantic,
72
+ planDigest: routingContractDigest("routekit.basis-repair-plan.v1", "plan", semantic)
73
+ };
74
+ }),
75
+ execute: (input) => Effect.gen(function* () {
76
+ const transport = yield* EvalAuthoringTransport;
77
+ const state = yield* requireReview(input.repositoryRoot);
78
+ const source = state.candidates.find((candidate) => candidate.candidateId === input.plan.sourceCandidateId);
79
+ if (source === undefined ||
80
+ input.plan.sourceCandidateId !==
81
+ state.selection.selectedCandidateId ||
82
+ input.summary.sourceCandidateId !== source.candidateId ||
83
+ input.summary.sourceBasisDigest !== source.basisDigest ||
84
+ input.summary.summaryDigest !==
85
+ expectedSummaryDigest(input.summary)) {
86
+ return yield* transition(state.stage, "repair inputs do not bind the selected original candidate");
87
+ }
88
+ const expectedSemantic = {
89
+ version: 1,
90
+ repositoryDigest: state.snapshot.repositoryDigest,
91
+ sourceCandidateId: source.candidateId,
92
+ sourceBasisDigest: source.basisDigest,
93
+ summaryDigest: input.summary.summaryDigest,
94
+ authorModel: input.plan.authorModel,
95
+ plannedPrimaryCalls: 1,
96
+ maximumAttempts: 2,
97
+ requiredTotalInputTokens: BASIS_AUTHOR_INPUT_TOKENS_PER_ATTEMPT * 2,
98
+ requiredTotalOutputTokens: BASIS_AUTHOR_OUTPUT_TOKENS_PER_ATTEMPT * 2
99
+ };
100
+ const expectedPlan = {
101
+ ...expectedSemantic,
102
+ planDigest: routingContractDigest("routekit.basis-repair-plan.v1", "plan", expectedSemantic)
103
+ };
104
+ if (JSON.stringify(expectedPlan) !== JSON.stringify(input.plan)) {
105
+ return yield* transition(state.stage, "repair plan differs from the frozen reviewed inputs");
106
+ }
107
+ const existing = yield* artifacts.loadBasisRepairV3(input.repositoryRoot);
108
+ if (existing !== undefined) {
109
+ if (existing.plan.planDigest !== input.plan.planDigest ||
110
+ existing.summary.summaryDigest !== input.summary.summaryDigest) {
111
+ return yield* transition(state.stage, "a different immutable repair artifact already exists");
112
+ }
113
+ return existing;
114
+ }
115
+ const structure = state.structureReports.find((report) => report.candidateId === source.candidateId);
116
+ const { validation: _sourceValidation, ...sourceSemanticBasis } = source.basis;
117
+ const sources = yield* readFrozenRepositorySourcesV3({
118
+ repositoryRoot: input.repositoryRoot,
119
+ tree: state.snapshot.tree,
120
+ sourceInventory: state.sourceInventory
121
+ });
122
+ const sourceDigest = routingContractDigest("routekit.basis-repair-sources.v1", "sources", sources);
123
+ let candidate;
124
+ let lastCause;
125
+ for (let attempt = 1; attempt <= 2 && candidate === undefined; attempt += 1) {
126
+ const outcome = yield* transport.complete({
127
+ operationId: `basis-repair-${state.projectId}-${input.plan.planDigest.slice(0, 16)}-${String(attempt)}`,
128
+ model: input.plan.authorModel,
129
+ instructions: attempt === 1
130
+ ? REPAIR_INSTRUCTIONS
131
+ : `${REPAIR_INSTRUCTIONS}\nThe previous output failed schema or semantic validation. Correct only the contract.`,
132
+ input: JSON.stringify({
133
+ sourceBasis: sourceSemanticBasis,
134
+ structuralWarnings: structure?.findings.map(({ severity, code, dimensionIds, detail }) => ({
135
+ severity,
136
+ code,
137
+ dimensionIds,
138
+ detail
139
+ })) ?? [],
140
+ reviewedErrorCategories: input.summary.errorCategories,
141
+ frozenRepository: {
142
+ repositoryDigest: state.snapshot.repositoryDigest,
143
+ commit: state.snapshot.commit,
144
+ tree: state.snapshot.tree,
145
+ sources
146
+ },
147
+ constructionTasks: state.split.construction
148
+ }),
149
+ schemaName: "routekit_repaired_routing_basis_v3",
150
+ jsonSchema: AUTHORED_BASIS_JSON_SCHEMA,
151
+ maximumOutputTokens: BASIS_AUTHOR_OUTPUT_TOKENS_PER_ATTEMPT
152
+ }).pipe(Effect.flatMap((text) => Effect.gen(function* () {
153
+ const parsed = yield* Effect.try({
154
+ try: () => JSON.parse(text),
155
+ catch: (cause) => failure("repair model returned invalid JSON", cause)
156
+ });
157
+ return yield* Schema.decodeUnknownEffect(AuthoredBasis)(parsed).pipe(Effect.mapError((cause) => failure("repair model returned an invalid basis shape", cause)));
158
+ })), Effect.match({
159
+ onFailure: (cause) => ({ ok: false, cause }),
160
+ onSuccess: (authored) => ({ ok: true, authored })
161
+ }));
162
+ if (!outcome.ok) {
163
+ lastCause = outcome.cause;
164
+ continue;
165
+ }
166
+ const candidateId = `repair-${input.plan.planDigest.slice(0, 16)}`;
167
+ const facetSnapshot = {
168
+ ...outcome.authored.facetSnapshot,
169
+ repositoryDigest: state.snapshot.repositoryDigest
170
+ };
171
+ const basis = yield* Effect.try({
172
+ try: () => {
173
+ const value = withBasisDigest({
174
+ version: 3,
175
+ basisId: candidateId,
176
+ repositoryDigest: state.snapshot.repositoryDigest,
177
+ facetSnapshot,
178
+ dimensions: outcome.authored
179
+ .dimensions,
180
+ provenance: {
181
+ generatorVersion: "basis-repair-v1",
182
+ recipeId: candidateId,
183
+ authorModel: input.plan.authorModel,
184
+ sourceDigests: [
185
+ state.snapshot.repositoryDigest,
186
+ state.snapshot.sourceInventoryDigest,
187
+ source.basisDigest,
188
+ input.summary.summaryDigest,
189
+ sourceDigest
190
+ ],
191
+ origin: {
192
+ kind: "repair",
193
+ sourceBasisId: source.basis.basisId,
194
+ sourceBasisVersion: source.basis.version
195
+ }
196
+ }
197
+ });
198
+ if (value.dimensions.length !==
199
+ source.basis.dimensions.length) {
200
+ throw new Error("repair must preserve the source dimension count");
201
+ }
202
+ assertRoutingBasisV3(value);
203
+ return value;
204
+ },
205
+ catch: (cause) => failure("repaired basis failed semantic validation", cause)
206
+ }).pipe(Effect.match({
207
+ onFailure: (cause) => ({ ok: false, cause }),
208
+ onSuccess: (value) => ({ ok: true, value })
209
+ }));
210
+ if (!basis.ok) {
211
+ lastCause = basis.cause;
212
+ continue;
213
+ }
214
+ candidate = {
215
+ candidateId,
216
+ recipeId: candidateId,
217
+ basis: basis.value,
218
+ basisDigest: basis.value.basisDigest,
219
+ authorModel: input.plan.authorModel,
220
+ generationConfigurationDigest: routingContractDigest("routekit.basis-repair-configuration.v1", "configuration", {
221
+ instructions: REPAIR_INSTRUCTIONS,
222
+ schema: AUTHORED_BASIS_JSON_SCHEMA,
223
+ maximumOutputTokens: BASIS_AUTHOR_OUTPUT_TOKENS_PER_ATTEMPT,
224
+ planDigest: input.plan.planDigest
225
+ }),
226
+ evidenceDigest: routingContractDigest("routekit.basis-repair-evidence.v1", "evidence", {
227
+ repositoryDigest: state.snapshot.repositoryDigest,
228
+ sourceBasisDigest: source.basisDigest,
229
+ summaryDigest: input.summary.summaryDigest,
230
+ sourceDigest
231
+ }),
232
+ createdAt: new Date(yield* Clock.currentTimeMillis).toISOString()
233
+ };
234
+ }
235
+ if (candidate === undefined) {
236
+ return yield* failure("basis repair exhausted its two approved attempts", lastCause);
237
+ }
238
+ const artifactsValue = {
239
+ version: 1,
240
+ plan: input.plan,
241
+ summary: input.summary,
242
+ candidate
243
+ };
244
+ yield* artifacts.saveBasisRepairV3(input.repositoryRoot, artifactsValue);
245
+ return artifactsValue;
246
+ })
247
+ });
248
+ });
249
+ export const BasisRepairLive = Layer.effect(BasisRepair, makeBasisRepair);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,194 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFile } from "node:child_process";
3
+ import { readFileSync } from "node:fs";
4
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { after, test } from "node:test";
8
+ import { promisify } from "node:util";
9
+ import { layer as NodeServicesLayer } from "@effect/platform-node/NodeServices";
10
+ import { Effect, Layer } from "effect";
11
+ import { BASIS_AUTHOR_INPUT_TOKENS_PER_ATTEMPT, BASIS_AUTHOR_OUTPUT_TOKENS_PER_ATTEMPT } from "../basis-candidates.js";
12
+ import { EvalProjectArtifactsLive } from "../project-artifacts.js";
13
+ import { EvalAuthoringTransport } from "../project-authoring.js";
14
+ import { EvalProjectStoreLive } from "../project-store.js";
15
+ import { BasisAuthoring, BasisAuthoringLive } from "../services/basis-authoring/service.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
+ async function repository() {
31
+ const root = await mkdtemp(path.join(os.tmpdir(), "routekit-basis-author-"));
32
+ roots.push(root);
33
+ await execFilePromise("git", ["init", "-q", root]);
34
+ await execFilePromise("git", [
35
+ "-C",
36
+ root,
37
+ "config",
38
+ "user.name",
39
+ "RouteKit Test"
40
+ ]);
41
+ await execFilePromise("git", [
42
+ "-C",
43
+ root,
44
+ "config",
45
+ "user.email",
46
+ "routekit@example.test"
47
+ ]);
48
+ await writeFile(path.join(root, "README.md"), "# Frozen source\n");
49
+ await execFilePromise("git", ["-C", root, "add", "README.md"]);
50
+ await execFilePromise("git", [
51
+ "-C",
52
+ root,
53
+ "commit",
54
+ "-q",
55
+ "-m",
56
+ "initial"
57
+ ]);
58
+ return root;
59
+ }
60
+ function dimensionsFor(count) {
61
+ const fixture = basisFixture();
62
+ const source = fixture.dimensions.map((dimension) => ({
63
+ ...dimension,
64
+ confusableDimensionIds: []
65
+ }));
66
+ while (source.length < count) {
67
+ const index = source.length + 1;
68
+ source.push({
69
+ ...source[0],
70
+ id: `synthetic-${String(index)}`,
71
+ name: `Synthetic responsibility ${String(index)}`,
72
+ description: `A distinct material responsibility ${String(index)} for authoring tests.`,
73
+ inclusions: [`Work that materially owns synthetic responsibility ${String(index)}.`],
74
+ exclusions: ["Every other material responsibility in this test basis."],
75
+ confusableDimensionIds: [],
76
+ boundaryRules: [
77
+ `Use synthetic-${String(index)} only for its distinct test responsibility.`
78
+ ],
79
+ positiveExamples: [`Implement synthetic responsibility ${String(index)}.`],
80
+ negativeExamples: ["Change an unrelated repository responsibility."],
81
+ codeSummaries: [`Synthetic responsibility ${String(index)}.`]
82
+ });
83
+ }
84
+ return source.slice(0, count);
85
+ }
86
+ test("basis authoring makes seven bounded calls and resumes from durable checkpoints", async () => {
87
+ const root = await repository();
88
+ const requests = [];
89
+ const transport = Layer.succeed(EvalAuthoringTransport, EvalAuthoringTransport.of({
90
+ complete: (input) => Effect.sync(() => {
91
+ const decoded = JSON.parse(input.input);
92
+ requests.push({
93
+ recipeId: decoded.recipe.id,
94
+ source: decoded.frozenRepository.sources[0]?.content ?? ""
95
+ });
96
+ const fixture = basisFixture();
97
+ const { repositoryDigest: _repositoryDigest, ...facetSnapshot } = fixture.facetSnapshot;
98
+ return JSON.stringify({
99
+ facetSnapshot,
100
+ dimensions: dimensionsFor(decoded.recipe.dimensionCount)
101
+ });
102
+ })
103
+ }));
104
+ const infrastructure = Layer.mergeAll(EvalProjectStoreLive, EvalProjectArtifactsLive, transport).pipe(Layer.provide(NodeServicesLayer));
105
+ const onboarding = BasisOnboardingLive.pipe(Layer.provide(infrastructure));
106
+ const live = BasisAuthoringLive.pipe(Layer.provide(onboarding), Layer.provide(infrastructure));
107
+ await Effect.runPromise(Effect.gen(function* () {
108
+ const service = yield* BasisOnboarding;
109
+ yield* service.initialize({
110
+ repositoryRoot: root,
111
+ requestedRef: "HEAD",
112
+ allowDirty: false,
113
+ configuration,
114
+ tasks: []
115
+ });
116
+ }).pipe(Effect.provide(onboarding)));
117
+ await writeFile(path.join(root, "README.md"), "# Mutable worktree content\n");
118
+ const first = await Effect.runPromise(Effect.gen(function* () {
119
+ const service = yield* BasisAuthoring;
120
+ return yield* service.generate({
121
+ repositoryRoot: root,
122
+ incumbent: basisFixture()
123
+ });
124
+ }).pipe(Effect.provide(live)));
125
+ assert.equal(first.stage, "basis-candidates-review");
126
+ assert.equal(first.candidates.length, 8);
127
+ assert.equal(requests.length, 7);
128
+ assert.deepEqual(requests.map((request) => request.recipeId), first.generationPlan.recipes.slice(1).map((recipe) => recipe.id));
129
+ assert.ok(requests.every((request) => request.source.includes("Frozen source")));
130
+ assert.ok(requests.every((request) => !request.source.includes("Mutable worktree content")));
131
+ assert.equal(first.candidates[0]?.authorModel, undefined);
132
+ assert.ok(first.candidates
133
+ .slice(1)
134
+ .every((candidate) => candidate.authorModel === configuration.authorModel));
135
+ assert.ok(first.generationLedger.jobs.every((job) => job.status === "completed" && job.attemptCount === 1));
136
+ const resumed = await Effect.runPromise(Effect.gen(function* () {
137
+ const service = yield* BasisAuthoring;
138
+ return yield* service.generate({
139
+ repositoryRoot: root,
140
+ incumbent: basisFixture()
141
+ });
142
+ }).pipe(Effect.provide(live)));
143
+ assert.equal(resumed.projectId, first.projectId);
144
+ assert.equal(requests.length, 7);
145
+ });
146
+ test("basis authoring accumulates both reservations when a malformed candidate is retried", async () => {
147
+ const root = await repository();
148
+ let calls = 0;
149
+ const transport = Layer.succeed(EvalAuthoringTransport, EvalAuthoringTransport.of({
150
+ complete: (input) => Effect.sync(() => {
151
+ calls += 1;
152
+ if (calls === 1)
153
+ return "{}";
154
+ const decoded = JSON.parse(input.input);
155
+ const fixture = basisFixture();
156
+ const { repositoryDigest: _repositoryDigest, ...facetSnapshot } = fixture.facetSnapshot;
157
+ return JSON.stringify({
158
+ facetSnapshot,
159
+ dimensions: dimensionsFor(decoded.recipe.dimensionCount)
160
+ });
161
+ })
162
+ }));
163
+ const infrastructure = Layer.mergeAll(EvalProjectStoreLive, EvalProjectArtifactsLive, transport).pipe(Layer.provide(NodeServicesLayer));
164
+ const onboarding = BasisOnboardingLive.pipe(Layer.provide(infrastructure));
165
+ const live = BasisAuthoringLive.pipe(Layer.provide(onboarding), Layer.provide(infrastructure));
166
+ await Effect.runPromise(Effect.gen(function* () {
167
+ const service = yield* BasisOnboarding;
168
+ yield* service.initialize({
169
+ repositoryRoot: root,
170
+ requestedRef: "HEAD",
171
+ allowDirty: false,
172
+ configuration,
173
+ tasks: []
174
+ });
175
+ }).pipe(Effect.provide(onboarding)));
176
+ const result = await Effect.runPromise(Effect.gen(function* () {
177
+ const service = yield* BasisAuthoring;
178
+ return yield* service.generate({
179
+ repositoryRoot: root,
180
+ incumbent: basisFixture()
181
+ });
182
+ }).pipe(Effect.provide(live)));
183
+ assert.equal(result.stage, "basis-candidates-review");
184
+ assert.equal(calls, 8);
185
+ assert.deepEqual(result.generationLedger.jobs[0], {
186
+ recipeId: result.generationPlan.recipes[1].id,
187
+ status: "completed",
188
+ candidateId: result.candidates[1].candidateId,
189
+ attemptCount: 2,
190
+ inputTokens: BASIS_AUTHOR_INPUT_TOKENS_PER_ATTEMPT * 2,
191
+ outputTokens: BASIS_AUTHOR_OUTPUT_TOKENS_PER_ATTEMPT * 2,
192
+ callIds: []
193
+ });
194
+ });
@@ -0,0 +1 @@
1
+ export {};