@bjoernrochel/sevro 0.1.0-rc.7 → 1.0.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 (230) hide show
  1. package/CONTRIBUTING.md +22 -6
  2. package/README.md +5 -1
  3. package/docs/README.md +19 -15
  4. package/docs/architecture.md +138 -17
  5. package/docs/assets/README.md +5 -4
  6. package/docs/builtin-graders-v1.md +17 -3
  7. package/docs/codex-host.md +155 -49
  8. package/docs/contracts.md +2 -1
  9. package/docs/creating-cases.md +9 -2
  10. package/docs/development-cli.md +187 -19
  11. package/docs/documentation-quality.md +12 -2
  12. package/docs/evaluation-workflow.md +110 -0
  13. package/docs/extension-protocol-v1.md +26 -3
  14. package/docs/getting-started.md +23 -2
  15. package/docs/guide-evaluation.md +33 -1
  16. package/docs/identity-v1.md +25 -2
  17. package/docs/installing.md +11 -11
  18. package/docs/native-controls.md +76 -5
  19. package/docs/native-hosts.md +196 -7
  20. package/docs/reading-results.md +102 -0
  21. package/docs/recipes.md +489 -0
  22. package/docs/releases.md +31 -12
  23. package/docs/report-v1.md +29 -1
  24. package/docs/results-v1.md +141 -4
  25. package/docs/run-settings-v1.md +147 -0
  26. package/docs/runtime-v1.md +307 -18
  27. package/docs/troubleshooting.md +12 -12
  28. package/docs/typescript-quality.md +89 -26
  29. package/examples/recipes/candidate.ts +26 -0
  30. package/examples/recipes/cases/coding-change.json +21 -0
  31. package/examples/recipes/cases/host-tools.json +21 -0
  32. package/examples/recipes/cases/mise.json +21 -0
  33. package/examples/recipes/cases/node.json +21 -0
  34. package/examples/recipes/cases/python-uv.json +21 -0
  35. package/examples/recipes/cases/semantic.json +17 -0
  36. package/examples/recipes/coding-change/check.ts +23 -0
  37. package/examples/recipes/extension-artifacts.ts +65 -0
  38. package/examples/recipes/extension-methods.ts +174 -0
  39. package/examples/recipes/extension.ts +37 -0
  40. package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
  41. package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
  42. package/examples/recipes/repository-skill/README.md +3 -0
  43. package/examples/recipes/run-inputs.ts +188 -0
  44. package/examples/recipes/run-options.ts +131 -0
  45. package/examples/recipes/run.ts +63 -0
  46. package/examples/recipes/runtime/empty.json +1 -0
  47. package/examples/recipes/runtime/goals.json +1 -0
  48. package/examples/recipes/runtime/mise.json +6 -0
  49. package/examples/recipes/runtime/path.json +1 -0
  50. package/examples/recipes/runtime/plugins.json +4 -0
  51. package/examples/recipes/runtime/python-uv.json +10 -0
  52. package/examples/recipes/semantic-judge.ts +29 -0
  53. package/examples/templates/coding/adapter.ts +17 -0
  54. package/examples/templates/coding/case.json +15 -0
  55. package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
  56. package/examples/templates/repository-skill/extension-data.ts +64 -0
  57. package/examples/templates/repository-skill/extension.ts +31 -0
  58. package/examples/templates/response/adapter.ts +12 -0
  59. package/examples/templates/response/case.json +13 -0
  60. package/package.json +11 -5
  61. package/schemas/report-v1.schema.json +9 -0
  62. package/schemas/run-evidence-v1.schema.json +2 -1
  63. package/schemas/runtime-v1.schema.json +63 -7
  64. package/src/advisory.ts +2 -2
  65. package/src/candidate-artifacts.ts +206 -0
  66. package/src/candidate-codex-state.ts +185 -0
  67. package/src/case-validation.ts +73 -50
  68. package/src/cli/cancellation.ts +20 -0
  69. package/src/cli/command.ts +87 -0
  70. package/src/cli/completion.ts +15 -0
  71. package/src/cli/demo.ts +60 -0
  72. package/src/cli/evaluation.ts +55 -0
  73. package/src/cli/extension.ts +79 -0
  74. package/src/cli/help.ts +198 -0
  75. package/src/cli/hosts.ts +55 -0
  76. package/src/cli/init-settings.ts +30 -0
  77. package/src/cli/init.ts +111 -0
  78. package/src/{cli-invocation.ts → cli/invocation.ts} +214 -218
  79. package/src/cli/loading.ts +151 -0
  80. package/src/cli/preparation.ts +154 -0
  81. package/src/cli/presentation.ts +93 -0
  82. package/src/cli/progress.ts +6 -0
  83. package/src/cli/run-manifest.ts +128 -0
  84. package/src/cli/run-options.ts +403 -0
  85. package/src/cli/run-overrides.ts +69 -0
  86. package/src/cli/settings.ts +154 -0
  87. package/src/cli/summary.ts +43 -0
  88. package/src/cli.ts +13 -591
  89. package/src/contract-types.ts +94 -0
  90. package/src/engine.ts +13 -5
  91. package/src/evaluation/advisory.ts +150 -0
  92. package/src/{evaluation-check-plan.ts → evaluation/check-plan.ts} +103 -67
  93. package/src/evaluation/collection.ts +88 -0
  94. package/src/evaluation/context.ts +10 -0
  95. package/src/{evaluation-grading-host.ts → evaluation/grading-host.ts} +5 -5
  96. package/src/evaluation/grading.ts +235 -0
  97. package/src/{evaluation-invocation-plan.ts → evaluation/invocation-plan.ts} +147 -58
  98. package/src/{evaluation-plan.ts → evaluation/plan.ts} +208 -141
  99. package/src/evaluation/progress.ts +19 -0
  100. package/src/evaluation/resources.ts +241 -0
  101. package/src/evaluation/run.ts +206 -0
  102. package/src/{evaluation-semantic.ts → evaluation/semantic.ts} +133 -115
  103. package/src/evaluation/trial.ts +800 -0
  104. package/src/evaluation/types.ts +207 -0
  105. package/src/evidence/host-results.ts +85 -0
  106. package/src/evidence/retained-artifacts.ts +18 -0
  107. package/src/evidence/trial-writer.ts +203 -0
  108. package/src/evidence/usage.ts +17 -0
  109. package/src/extension-client.ts +34 -96
  110. package/src/extension-session.ts +24 -48
  111. package/src/{advisory-fixture.ts → fixtures/advisory.ts} +15 -76
  112. package/src/{fixture-bin.ts → fixtures/bin.ts} +1 -1
  113. package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
  114. package/src/fixtures/create.ts +183 -0
  115. package/src/{generated-fixture.ts → fixtures/generated.ts} +10 -53
  116. package/src/{git-hooks.ts → fixtures/git-hooks.ts} +1 -1
  117. package/src/{preparation.ts → fixtures/preparation.ts} +6 -16
  118. package/src/{repository-fixture.ts → fixtures/repository.ts} +30 -64
  119. package/src/{fixture-setup.ts → fixtures/setup.ts} +33 -51
  120. package/src/{fixture-tools.ts → fixtures/tools.ts} +1 -1
  121. package/src/fixtures/workspace-state.ts +212 -0
  122. package/src/generated/cli-result.d.cts +2 -1
  123. package/src/generated/extension.d.cts +2 -1
  124. package/src/generated/report.cjs +1 -1
  125. package/src/generated/report.d.cts +2 -1
  126. package/src/generated/run-evidence.cjs +1 -1
  127. package/src/generated/run-evidence.d.cts +2 -1
  128. package/src/generated/runtime.cjs +1 -1
  129. package/src/generated/runtime.d.cts +2 -1
  130. package/src/git.ts +93 -0
  131. package/src/graders/git-head.ts +29 -84
  132. package/src/graders/shell.ts +57 -68
  133. package/src/host-diagnostic-redaction.ts +52 -0
  134. package/src/host-diagnostics.ts +331 -0
  135. package/src/hosts/claude-continuation.ts +57 -40
  136. package/src/hosts/claude-credential.ts +160 -63
  137. package/src/hosts/claude-events.ts +34 -18
  138. package/src/hosts/claude-keychain.ts +78 -21
  139. package/src/hosts/claude-login-recovery.ts +85 -0
  140. package/src/hosts/claude-login-refresh.ts +178 -0
  141. package/src/hosts/claude-login-store.ts +162 -0
  142. package/src/hosts/claude-login.ts +107 -0
  143. package/src/hosts/claude-native-goal.ts +15 -12
  144. package/src/hosts/claude-process.ts +10 -50
  145. package/src/hosts/claude-refresh-lock.ts +81 -0
  146. package/src/hosts/claude-repository-invocation.ts +9 -34
  147. package/src/hosts/claude-settings.ts +91 -14
  148. package/src/hosts/claude-skill-activations.ts +169 -0
  149. package/src/hosts/claude-state.ts +46 -16
  150. package/src/hosts/claude-tool-calls.ts +13 -10
  151. package/src/hosts/claude.ts +122 -73
  152. package/src/hosts/codex-app-server.ts +78 -52
  153. package/src/hosts/codex-configuration.ts +5 -10
  154. package/src/hosts/codex-exec-controls.ts +516 -0
  155. package/src/hosts/codex-marketplace.ts +12 -10
  156. package/src/hosts/codex-native-calls.ts +15 -122
  157. package/src/hosts/codex-process.ts +10 -54
  158. package/src/hosts/codex-profile.ts +50 -21
  159. package/src/hosts/codex-session-files.ts +1 -9
  160. package/src/hosts/codex-state.ts +141 -19
  161. package/src/hosts/codex.ts +221 -179
  162. package/src/hosts/isolation-common.ts +15 -11
  163. package/src/hosts/isolation-roots.ts +13 -20
  164. package/src/hosts/linux-sandbox.ts +27 -3
  165. package/src/hosts/mac-sandbox.ts +111 -18
  166. package/src/hosts/native-controls.ts +72 -22
  167. package/src/hosts/native-request.ts +55 -0
  168. package/src/hosts/process-lifecycle.ts +221 -45
  169. package/src/hosts/protected-worktrees.ts +1 -1
  170. package/src/hosts/skill-hook-catalog.ts +131 -0
  171. package/src/hosts/skill-hook-handler.ts +374 -0
  172. package/src/hosts/skill-hooks.ts +577 -0
  173. package/src/instrumentation.ts +7 -8
  174. package/src/native-transcript-state.ts +22 -3
  175. package/src/native-transcript-view.ts +52 -13
  176. package/src/native-transcripts.ts +34 -15
  177. package/src/paths.ts +22 -0
  178. package/src/process-lifecycle.ts +150 -0
  179. package/src/provenance.ts +27 -7
  180. package/src/prune.ts +300 -0
  181. package/src/report.ts +63 -36
  182. package/src/resolved-case.ts +1 -1
  183. package/src/results-lock.ts +42 -0
  184. package/src/results.ts +14 -18
  185. package/src/run-owner.ts +4 -4
  186. package/src/{runtime-config.ts → runtime/config.ts} +182 -44
  187. package/src/{runtime-discovery-apple.ts → runtime/discovery-apple.ts} +3 -3
  188. package/src/{runtime-discovery-providers.ts → runtime/discovery-providers.ts} +3 -9
  189. package/src/{runtime-discovery.ts → runtime/discovery.ts} +4 -4
  190. package/src/{runtime-hook-processes.ts → runtime/hook-processes.ts} +1 -1
  191. package/src/{runtime-hooks.ts → runtime/hooks.ts} +33 -22
  192. package/src/{runtime-paths.ts → runtime/paths.ts} +6 -18
  193. package/src/{runtime-query.ts → runtime/query.ts} +5 -5
  194. package/src/{runtime-seeds.ts → runtime/seeds.ts} +3 -3
  195. package/src/runtime/shared-cache.ts +130 -0
  196. package/src/{runtime-state.ts → runtime/state.ts} +28 -11
  197. package/src/schema-types.ts +73 -161
  198. package/src/shell.ts +4 -0
  199. package/src/transcript-retention.ts +146 -0
  200. package/src/value-guards.ts +22 -0
  201. package/docs/assets/sevro-logo.png +0 -0
  202. package/docs/assets/sevro-symbol.svg +0 -1
  203. package/docs/assets/sevro-wordmark.svg +0 -1
  204. package/docs/cache-ownership-validation.md +0 -49
  205. package/docs/claude-continuation-validation.md +0 -47
  206. package/docs/concurrency-validation.md +0 -86
  207. package/docs/configuration-root-validation.md +0 -70
  208. package/docs/engine-slice.md +0 -114
  209. package/docs/evidence/runtime-startup-30/README.md +0 -185
  210. package/docs/evidence/runtime-startup-30/after.json +0 -319
  211. package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +0 -2
  212. package/docs/evidence/runtime-startup-30/before.json +0 -403
  213. package/docs/evidence/runtime-startup-30/consumer-smoke.json +0 -63
  214. package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +0 -3
  215. package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +0 -3
  216. package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +0 -20
  217. package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +0 -2
  218. package/docs/evidence.md +0 -17
  219. package/docs/fixture-cleanup-validation.md +0 -70
  220. package/docs/guide-verification.json +0 -239
  221. package/src/evaluation-context.ts +0 -257
  222. package/src/evaluation-fixture.ts +0 -313
  223. package/src/evaluation-run.ts +0 -198
  224. package/src/evaluation-trial.ts +0 -946
  225. package/src/evaluation-types.ts +0 -149
  226. package/src/hosts/claude-nested-skills.ts +0 -272
  227. package/src/hosts/codex-command-output.ts +0 -510
  228. package/src/hosts/codex-executor-command.ts +0 -291
  229. package/src/hosts/codex-skill-reads.ts +0 -609
  230. /package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +0 -0
@@ -0,0 +1,241 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, realpath, rm, writeFile } from "node:fs/promises";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import {
6
+ allocateWorkspace,
7
+ retainWorkspace,
8
+ removeWorkspace,
9
+ } from "../fixtures/workspace-state";
10
+ import { createFixture } from "../fixtures/create";
11
+ import { fixtureBinDirectory } from "../fixtures/bin";
12
+ import { fixtureParts } from "../fixtures/preparation";
13
+ import { startRunOwner, type RunOwner } from "../run-owner";
14
+ import { EvaluationConfigurationError, type EvaluationOptions } from "./types";
15
+ import type { PreparedFixture, PreparedInvocation } from "./plan";
16
+ import {
17
+ INVOCATION_PLACEHOLDER,
18
+ LEGACY_CODEX_PLACEHOLDER,
19
+ } from "./invocation-plan";
20
+ import type { ArtifactReference as PreparationArtifactReference } from "../contract-types";
21
+ export type { ArtifactReference as PreparationArtifactReference } from "../contract-types";
22
+
23
+ /** Reservations outlive completed contents until all admitted trials drain. */
24
+ export class RunWorkspaceReservations {
25
+ private readonly candidates: string[] = [];
26
+ private readonly semantics: string[] = [];
27
+ private readonly retained = new Set<string>();
28
+
29
+ async reserve(input: {
30
+ count: number;
31
+ semantic: boolean;
32
+ signal?: AbortSignal;
33
+ }): Promise<void> {
34
+ for (
35
+ let trial = 1;
36
+ trial <= input.count && !input.signal?.aborted;
37
+ trial++
38
+ ) {
39
+ this.candidates.push(await allocateWorkspace());
40
+ if (input.semantic) this.semantics.push(await allocateWorkspace());
41
+ }
42
+ }
43
+
44
+ candidate(trial: number): string {
45
+ const reservation = this.candidates[trial - 1];
46
+ if (reservation === undefined)
47
+ throw new Error(`trial ${trial} workspace was not reserved`);
48
+ return reservation;
49
+ }
50
+
51
+ semantic(trial: number): string | undefined {
52
+ return this.semantics[trial - 1];
53
+ }
54
+
55
+ retain(workspace: string): void {
56
+ this.retained.add(workspace);
57
+ }
58
+
59
+ completed(reservation: string): void {
60
+ this.retained.delete(reservation);
61
+ }
62
+
63
+ async cleanup(): Promise<void> {
64
+ for (const workspace of this.retained) await retainWorkspace(workspace);
65
+ await Promise.all(
66
+ [...this.candidates, ...this.semantics]
67
+ .filter((workspace) => !this.retained.has(workspace))
68
+ .map(async (workspace) => {
69
+ try {
70
+ await removeWorkspace(workspace);
71
+ } catch {
72
+ console.warn(`fixture cleanup failed; retained at ${workspace}`);
73
+ }
74
+ }),
75
+ );
76
+ }
77
+ }
78
+
79
+ export interface RunResources {
80
+ readonly runId: string;
81
+ readonly runDir: string;
82
+ readonly runStateRoot: string;
83
+ readonly stateDir: string;
84
+ readonly evidencePath: string;
85
+ readonly owner: RunOwner;
86
+ readonly workspaces: RunWorkspaceReservations;
87
+ }
88
+
89
+ export async function allocateRunResources(
90
+ options: Pick<EvaluationOptions, "resultsRoot" | "runStateRoot">,
91
+ evaluationDigest: string,
92
+ ): Promise<RunResources> {
93
+ const runId = randomUUID();
94
+ await mkdir(options.resultsRoot, { recursive: true, mode: 0o700 });
95
+ const runDir = resolve(await realpath(options.resultsRoot), runId);
96
+ await mkdir(runDir, { mode: 0o700 });
97
+ const runStateRoot = options.runStateRoot ?? options.resultsRoot;
98
+ await mkdir(runStateRoot, { recursive: true, mode: 0o700 });
99
+ const stateRoot = await realpath(runStateRoot);
100
+ const stateDir = join(stateRoot, runId);
101
+ if (stateDir !== runDir) await mkdir(stateDir, { mode: 0o700 });
102
+ const activePath = join(stateRoot, "active", `${runId}.json`);
103
+ const checkpointPath = join(stateDir, "checkpoint.json");
104
+ const evidencePath = join(runDir, "run.json");
105
+ let owner: RunOwner;
106
+ try {
107
+ owner = startRunOwner({
108
+ stateRoot,
109
+ evaluationDigest,
110
+ attemptId: runId,
111
+ activeRunPath: activePath,
112
+ checkpointPath,
113
+ evidenceDirectory: stateDir,
114
+ artifactPath: evidencePath,
115
+ });
116
+ } catch (error) {
117
+ await rm(runDir, { recursive: true, force: true });
118
+ if (stateDir !== runDir)
119
+ await rm(stateDir, { recursive: true, force: true });
120
+ throw error;
121
+ }
122
+ return {
123
+ runId,
124
+ runDir,
125
+ runStateRoot,
126
+ stateDir,
127
+ evidencePath,
128
+ owner,
129
+ workspaces: new RunWorkspaceReservations(),
130
+ };
131
+ }
132
+
133
+ export async function retainPreparationArtifacts(
134
+ artifacts: PreparedFixture["inlineArtifacts"],
135
+ runDir: string,
136
+ ): Promise<PreparationArtifactReference[]> {
137
+ const artifactRefs: PreparationArtifactReference[] = [];
138
+ for (const artifact of artifacts) {
139
+ const retainedPath = join(
140
+ runDir,
141
+ "prepared",
142
+ ...fixtureParts(artifact.relativePath),
143
+ );
144
+ await mkdir(dirname(retainedPath), { recursive: true, mode: 0o700 });
145
+ await writeFile(retainedPath, artifact.bytes, {
146
+ flag: "wx",
147
+ mode: artifact.executable ? 0o700 : 0o600,
148
+ });
149
+ artifactRefs.push({
150
+ id: artifact.id,
151
+ path: pathToFileURL(retainedPath).href,
152
+ sha256: artifact.sha256,
153
+ ...(artifact.gitExclude ? { gitExclude: true } : {}),
154
+ ...(artifact.executable ? { executable: true } : {}),
155
+ });
156
+ }
157
+ return artifactRefs;
158
+ }
159
+
160
+ function renderTrialPrompt(
161
+ prompt: string,
162
+ workspace: string,
163
+ invocation: PreparedInvocation,
164
+ ): string {
165
+ return prompt
166
+ .replaceAll("{{sevro.workspace}}", workspace)
167
+ .replaceAll(
168
+ INVOCATION_PLACEHOLDER,
169
+ invocation.invocationToken ?? INVOCATION_PLACEHOLDER,
170
+ )
171
+ .replaceAll(
172
+ LEGACY_CODEX_PLACEHOLDER,
173
+ invocation.invocationToken ?? LEGACY_CODEX_PLACEHOLDER,
174
+ );
175
+ }
176
+
177
+ type TrialPreparationOptions = Pick<
178
+ EvaluationOptions,
179
+ "case" | "preparationSources" | "signal"
180
+ >;
181
+
182
+ export async function prepareTrialWorkspace(
183
+ options: TrialPreparationOptions,
184
+ fixture: PreparedFixture,
185
+ invocation: PreparedInvocation,
186
+ workspaces: RunWorkspaceReservations,
187
+ trial: number,
188
+ candidateRuntimeRoot?: string,
189
+ ) {
190
+ const reservation = workspaces.candidate(trial);
191
+ workspaces.retain(reservation);
192
+ const workspace = await createFixture(fixture.source, {
193
+ artifacts: fixture.inlineArtifacts,
194
+ sources: options.preparationSources,
195
+ setup: fixture.fixtureSetup,
196
+ projectRoot: fixture.projectRoot,
197
+ signal: options.signal,
198
+ reservedWorkspace: reservation,
199
+ candidateRuntimeRoot,
200
+ }).catch((error: unknown) => {
201
+ if (
202
+ !options.signal?.aborted ||
203
+ error instanceof EvaluationConfigurationError
204
+ )
205
+ throw error;
206
+ return null;
207
+ });
208
+ const details = await trialWorkspaceDetails(
209
+ options.case,
210
+ invocation,
211
+ reservation,
212
+ workspace,
213
+ );
214
+ return { ...details, candidateRuntimeRoot };
215
+ }
216
+
217
+ async function trialWorkspaceDetails(
218
+ caseData: EvaluationOptions["case"],
219
+ invocation: PreparedInvocation,
220
+ reservation: string,
221
+ preparedWorkspace: string | null,
222
+ ) {
223
+ const workspace = preparedWorkspace ?? reservation;
224
+ const fixtureBinDir =
225
+ preparedWorkspace === null
226
+ ? undefined
227
+ : ((await fixtureBinDirectory(workspace)) ?? undefined);
228
+ const trialPrompt = renderTrialPrompt(caseData.prompt, workspace, invocation);
229
+ const trialFollowUpPrompt =
230
+ caseData.followUpPrompt === undefined
231
+ ? undefined
232
+ : renderTrialPrompt(caseData.followUpPrompt, workspace, invocation);
233
+ return {
234
+ reservation,
235
+ workspace,
236
+ preparationCancelled: preparedWorkspace === null,
237
+ fixtureBinDir,
238
+ trialPrompt,
239
+ trialFollowUpPrompt,
240
+ };
241
+ }
@@ -0,0 +1,206 @@
1
+ import { prepareEvaluation, type PreparedEvaluation } from "./plan";
2
+ import { type CliResult, type EvaluationOptions } from "./types";
3
+ import { canonicalJson, hashJson } from "../identity";
4
+ import { exitCodeFor, summarizeCases } from "../results";
5
+ import { runtimePolicySnapshot } from "../runtime/config";
6
+ import { cleanupExpiredWorkspaces } from "../fixtures/workspace-state";
7
+ import { assertCliResult, assertRunEvidence } from "../schema";
8
+ import { atomicWriteJson } from "../storage";
9
+ import {
10
+ allocateRunResources,
11
+ retainPreparationArtifacts,
12
+ type RunResources,
13
+ } from "./resources";
14
+ import type { EvaluationContext } from "./context";
15
+ import { ResultCollector, type CollectedResults } from "./collection";
16
+ import { executeEvaluationTrials } from "./trial";
17
+ import { evaluationProgress } from "./progress";
18
+
19
+ function evaluationSignal(
20
+ options: EvaluationOptions,
21
+ ): "SIGINT" | "SIGTERM" | undefined {
22
+ const reason: unknown = options.signal?.aborted
23
+ ? options.signal.reason
24
+ : null;
25
+ return reason === "SIGINT" || reason === "SIGTERM" ? reason : undefined;
26
+ }
27
+
28
+ function finalCliResult(
29
+ options: EvaluationOptions,
30
+ resources: RunResources,
31
+ collected: CollectedResults,
32
+ ): CliResult {
33
+ const caseResult = collected.caseSummary;
34
+ const assessment = summarizeCases([caseResult]);
35
+ const diagnostic = collected.diagnostic;
36
+ return {
37
+ format: "sevro.cli-result.v1",
38
+ runId: resources.runId,
39
+ ...assessment,
40
+ exitCode: exitCodeFor(assessment, {
41
+ dryRun: options.dry,
42
+ signal: evaluationSignal(options),
43
+ }),
44
+ evidencePath: resources.evidencePath,
45
+ cases: [caseResult],
46
+ ...(diagnostic ? { diagnostic } : {}),
47
+ };
48
+ }
49
+
50
+ function finalConditionEvidence(
51
+ options: Pick<EvaluationOptions, "condition" | "trialCount">,
52
+ requestedInstrumentation: PreparedEvaluation["requestedInstrumentation"],
53
+ trials: CollectedResults["trials"],
54
+ ) {
55
+ const actualConditions = [
56
+ ...new Set(trials.map((entry) => entry.condition.actual)),
57
+ ];
58
+ const commonAppliedInstrumentation =
59
+ trials.length === options.trialCount &&
60
+ trials.every(
61
+ (entry) =>
62
+ canonicalJson(entry.condition.appliedInstrumentation) ===
63
+ canonicalJson(requestedInstrumentation),
64
+ )
65
+ ? requestedInstrumentation
66
+ : [];
67
+ return {
68
+ requested: options.condition,
69
+ actual: actualConditions.length === 1 ? actualConditions[0] : "unknown",
70
+ requestedInstrumentation,
71
+ appliedInstrumentation: commonAppliedInstrumentation,
72
+ };
73
+ }
74
+
75
+ function finalExtensionEvidence(
76
+ options: EvaluationOptions,
77
+ replacedBuiltinGraders: string[],
78
+ ) {
79
+ const extension = options.extension;
80
+ if (!extension) return null;
81
+ const identity = extension.session.identity;
82
+ return {
83
+ id: identity.id,
84
+ version: identity.version,
85
+ sourceDigest: identity.sourceDigest,
86
+ configurationDigest: identity.configurationDigest,
87
+ protocol: identity.protocol,
88
+ capabilities: identity.capabilities,
89
+ replacements: {
90
+ graders: replacedBuiltinGraders,
91
+ taskVerdictPolicy: identity.selectedTaskVerdictPolicy,
92
+ },
93
+ };
94
+ }
95
+
96
+ function finalRunEvidence(
97
+ options: EvaluationOptions,
98
+ prepared: PreparedEvaluation,
99
+ resources: RunResources,
100
+ collected: CollectedResults,
101
+ result: CliResult,
102
+ ) {
103
+ const condition = finalConditionEvidence(
104
+ options,
105
+ prepared.requestedInstrumentation,
106
+ collected.trials,
107
+ );
108
+ const diagnostic = collected.diagnostic;
109
+ return {
110
+ format: "sevro.run-evidence.v1",
111
+ runId: resources.runId,
112
+ evaluationIdentity: prepared.evaluationIdentity,
113
+ configuration: {
114
+ digest: hashJson(prepared.redactedConfig),
115
+ redacted: prepared.redactedConfig,
116
+ ...(options.resolvedRunSettings
117
+ ? { resolved: options.resolvedRunSettings }
118
+ : {}),
119
+ },
120
+ runner: prepared.runner,
121
+ project: prepared.project,
122
+ extension: finalExtensionEvidence(
123
+ options,
124
+ prepared.checks.replacedBuiltinGraders,
125
+ ),
126
+ condition,
127
+ graders: {
128
+ active: prepared.activeGraders,
129
+ replacedDefaults: prepared.checks.replacedBuiltinGraders,
130
+ },
131
+ routes: prepared.routes,
132
+ result,
133
+ trials: collected.trials,
134
+ ...(diagnostic ? { diagnostic } : {}),
135
+ };
136
+ }
137
+
138
+ async function finishEvaluationRun(
139
+ options: EvaluationOptions,
140
+ context: EvaluationContext,
141
+ ) {
142
+ const collected = context.results.snapshot({
143
+ caseId: options.case.id,
144
+ passThreshold: options.passThreshold,
145
+ cancelled: Boolean(options.signal?.aborted),
146
+ });
147
+ const result = finalCliResult(options, context.resources, collected);
148
+ const evidence = finalRunEvidence(
149
+ options,
150
+ context.prepared,
151
+ context.resources,
152
+ collected,
153
+ result,
154
+ );
155
+ assertCliResult(result);
156
+ assertRunEvidence(evidence);
157
+ evaluationProgress(options, "evidence", "started");
158
+ await atomicWriteJson(context.resources.evidencePath, evidence);
159
+ context.results.checkpoint(
160
+ options.signal?.aborted ? "interrupted" : "complete",
161
+ );
162
+ evaluationProgress(options, "evidence", "completed");
163
+ return { result };
164
+ }
165
+
166
+ /** Execute resolved evaluator data through an injected host and retain every trial. */
167
+ export async function runEvaluation(
168
+ options: EvaluationOptions,
169
+ ): Promise<{ result: CliResult }> {
170
+ options = {
171
+ ...options,
172
+ runtimePolicy: runtimePolicySnapshot(options.runtimePolicy),
173
+ };
174
+ evaluationProgress(options, "preparation", "started");
175
+ await cleanupExpiredWorkspaces();
176
+ const prepared = await prepareEvaluation(options);
177
+ const resources = await allocateRunResources(
178
+ options,
179
+ prepared.evaluationIdentity.digest,
180
+ );
181
+ const results = new ResultCollector(resources.owner);
182
+ try {
183
+ const preparationArtifacts = await retainPreparationArtifacts(
184
+ prepared.fixture.inlineArtifacts,
185
+ resources.runDir,
186
+ );
187
+ await resources.workspaces.reserve({
188
+ count: options.trialCount,
189
+ semantic: prepared.checks.preparedSemantic.length > 0,
190
+ signal: options.signal,
191
+ });
192
+ const context: EvaluationContext = {
193
+ prepared,
194
+ resources,
195
+ results,
196
+ preparationArtifacts,
197
+ };
198
+ await executeEvaluationTrials(options, context);
199
+ return await finishEvaluationRun(options, context);
200
+ } catch (error) {
201
+ results.diagnoseFailure();
202
+ throw error;
203
+ } finally {
204
+ await resources.workspaces.cleanup();
205
+ }
206
+ }