@bjoernrochel/sevro 0.1.0-rc.8 → 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 (228) 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 +9 -2
  7. package/docs/codex-host.md +155 -57
  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 +122 -4
  25. package/docs/run-settings-v1.md +147 -0
  26. package/docs/runtime-v1.md +255 -22
  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 +43 -7
  64. package/src/advisory.ts +2 -2
  65. package/src/candidate-artifacts.ts +42 -11
  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 +9 -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} +77 -87
  103. package/src/evaluation/trial.ts +800 -0
  104. package/src/{evaluation-types.ts → evaluation/types.ts} +79 -29
  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} +12 -72
  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/{workspace-state.ts → fixtures/workspace-state.ts} +5 -5
  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 +50 -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 +46 -9
  148. package/src/hosts/claude-skill-activations.ts +169 -0
  149. package/src/hosts/claude-state.ts +28 -15
  150. package/src/hosts/claude-tool-calls.ts +13 -10
  151. package/src/hosts/claude.ts +121 -81
  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 -183
  158. package/src/hosts/codex-profile.ts +36 -21
  159. package/src/hosts/codex-session-files.ts +1 -9
  160. package/src/hosts/codex-state.ts +134 -18
  161. package/src/hosts/codex.ts +210 -183
  162. package/src/hosts/isolation-common.ts +8 -11
  163. package/src/hosts/isolation-roots.ts +2 -2
  164. package/src/hosts/linux-sandbox.ts +15 -2
  165. package/src/hosts/mac-sandbox.ts +87 -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 +216 -48
  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 +26 -11
  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/{runtime-config.ts → runtime/config.ts} +160 -46
  186. package/src/{runtime-discovery-apple.ts → runtime/discovery-apple.ts} +3 -3
  187. package/src/{runtime-discovery-providers.ts → runtime/discovery-providers.ts} +3 -9
  188. package/src/{runtime-discovery.ts → runtime/discovery.ts} +4 -4
  189. package/src/{runtime-hook-processes.ts → runtime/hook-processes.ts} +1 -1
  190. package/src/{runtime-hooks.ts → runtime/hooks.ts} +32 -22
  191. package/src/{runtime-paths.ts → runtime/paths.ts} +5 -19
  192. package/src/{runtime-query.ts → runtime/query.ts} +5 -5
  193. package/src/{runtime-seeds.ts → runtime/seeds.ts} +3 -3
  194. package/src/runtime/shared-cache.ts +130 -0
  195. package/src/{runtime-state.ts → runtime/state.ts} +28 -11
  196. package/src/schema-types.ts +73 -161
  197. package/src/shell.ts +4 -0
  198. package/src/transcript-retention.ts +146 -0
  199. package/src/value-guards.ts +22 -0
  200. package/docs/assets/sevro-logo.png +0 -0
  201. package/docs/assets/sevro-symbol.svg +0 -1
  202. package/docs/assets/sevro-wordmark.svg +0 -1
  203. package/docs/cache-ownership-validation.md +0 -49
  204. package/docs/claude-continuation-validation.md +0 -47
  205. package/docs/concurrency-validation.md +0 -86
  206. package/docs/configuration-root-validation.md +0 -70
  207. package/docs/engine-slice.md +0 -114
  208. package/docs/evidence/runtime-startup-30/README.md +0 -185
  209. package/docs/evidence/runtime-startup-30/after.json +0 -319
  210. package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +0 -2
  211. package/docs/evidence/runtime-startup-30/before.json +0 -403
  212. package/docs/evidence/runtime-startup-30/consumer-smoke.json +0 -63
  213. package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +0 -3
  214. package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +0 -3
  215. package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +0 -20
  216. package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +0 -2
  217. package/docs/evidence.md +0 -17
  218. package/docs/fixture-cleanup-validation.md +0 -73
  219. package/docs/guide-verification.json +0 -239
  220. package/src/evaluation-context.ts +0 -259
  221. package/src/evaluation-fixture.ts +0 -302
  222. package/src/evaluation-run.ts +0 -200
  223. package/src/evaluation-trial.ts +0 -1016
  224. package/src/hosts/claude-nested-skills.ts +0 -272
  225. package/src/hosts/codex-command-output.ts +0 -510
  226. package/src/hosts/codex-executor-command.ts +0 -291
  227. package/src/hosts/codex-skill-reads.ts +0 -783
  228. /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
+ }
@@ -1,56 +1,52 @@
1
- import { clearFixtureContents } from "./fixture-cleanup";
2
- import type { CandidateArtifactRoots } from "./candidate-artifacts";
1
+ import { clearFixtureContents } from "../fixtures/cleanup";
2
+ import type { CandidateArtifactRoots } from "../candidate-artifacts";
3
+ import { createFixture } from "../fixtures/create";
4
+ import { hostArtifacts } from "../evidence/host-results";
5
+ import { usage } from "../evidence/usage";
6
+ import { gradingRuntimePolicy, scopedHostArtifacts } from "./grading-host";
3
7
  import {
4
- CodexProcessFailure,
5
- codexFailureMessage,
6
- } from "./hosts/codex-process";
7
- import { join } from "node:path";
8
- import { pathToFileURL } from "node:url";
9
- import { createFixture, hostArtifacts, usage } from "./evaluation-fixture";
10
- import {
11
- gradingRuntimePolicy,
12
- scopedHostArtifacts,
13
- } from "./evaluation-grading-host";
14
- import type { HostAdapter, HostResult, ResolvedCase } from "./evaluation-types";
15
- import { readSemanticArtifact } from "./graders/artifact";
8
+ HostExecutionFailure,
9
+ type HostAdapter,
10
+ type HostResult,
11
+ } from "./types";
12
+ import { readSemanticArtifact } from "../graders/artifact";
16
13
  import {
17
14
  parseSemanticVerdicts,
18
15
  semanticCheckGroups,
19
16
  semanticPrompt,
20
17
  type PreparedSemanticCheck,
21
18
  type SemanticVerdict,
22
- } from "./graders/semantic";
23
- import { sha256 } from "./identity";
24
- import type { CheckOutcome } from "./results";
25
- import type { RuntimePolicy } from "./runtime-config";
19
+ } from "../graders/semantic";
20
+ import { sha256 } from "../identity";
21
+ import type { CheckOutcome } from "../results";
22
+ import type {
23
+ TrialEvidenceWriter,
24
+ TrialObservation,
25
+ } from "../evidence/trial-writer";
26
+ import type { GradingFailure, GradingResult } from "./grading";
27
+ import type { RuntimePolicy } from "../runtime/config";
28
+ import { HostDiagnosticPersistenceError } from "../host-diagnostics";
26
29
 
27
- interface SemanticObservation {
28
- id: string;
29
- source: string;
30
- completeness: "complete" | "partial";
31
- data: Record<string, unknown>;
32
- }
33
30
  interface SemanticGradingInput {
34
- checks: PreparedSemanticCheck[];
35
- declaredChecks: ResolvedCase["checks"];
36
- host: HostAdapter | undefined;
37
- message: string | null;
38
- candidateWorkspace: string;
39
- candidateTranscriptRoot?: string;
40
- candidateArtifactRoots?: CandidateArtifactRoots;
41
- projectRoot: string;
42
- reservedWorkspace?: string;
43
- runDir: string;
44
- trial: number;
45
- runtimePolicy?: RuntimePolicy;
46
- signal?: AbortSignal;
31
+ readonly checks: readonly PreparedSemanticCheck[];
32
+ readonly declaredChecks: readonly { id: string }[];
33
+ readonly host: HostAdapter | undefined;
34
+ readonly message: string | null;
35
+ readonly candidateWorkspace: string;
36
+ readonly candidateTranscriptRoot?: string;
37
+ readonly candidateArtifactRoots?: CandidateArtifactRoots;
38
+ readonly projectRoot: string;
39
+ readonly reservedWorkspace?: string;
40
+ readonly runtimePolicy?: RuntimePolicy;
41
+ readonly signal?: AbortSignal;
47
42
  }
48
43
  interface SemanticGradingEffects {
49
- artifactRefs: { id: string; path: string; sha256: string }[];
50
- canGrade(): boolean;
51
- failGrading(message: string, cancelable?: boolean): void;
52
- retainArtifacts(artifacts: ReturnType<typeof hostArtifacts>): Promise<void>;
53
- writeTrialFile(path: string, bytes: string | Uint8Array): Promise<void>;
44
+ readonly evidence: Pick<
45
+ TrialEvidenceWriter,
46
+ "artifactRefs" | "retainArtifacts" | "retainSemanticVerdicts"
47
+ >;
48
+ /** Close admission at detection, before retaining or cleaning auxiliary state. */
49
+ onFailure(failure: GradingFailure): void;
54
50
  retainWorkspace(workspace: string): Promise<void>;
55
51
  }
56
52
  type SemanticGroup = ReturnType<typeof semanticCheckGroups>[number];
@@ -101,16 +97,25 @@ function semanticHostPrompt(
101
97
  );
102
98
  }
103
99
  class SemanticGrading {
104
- private observations: SemanticObservation[] = [];
100
+ private observations: TrialObservation[] = [];
105
101
  private checks: CheckOutcome[] = [];
102
+ private failure: GradingFailure | null = null;
106
103
  constructor(
107
104
  private readonly input: SemanticGradingInput,
108
105
  private readonly effects: SemanticGradingEffects,
109
106
  ) {}
110
- async run() {
111
- for (const group of semanticCheckGroups(this.input.checks))
107
+ async run(): Promise<GradingResult> {
108
+ for (const group of semanticCheckGroups([...this.input.checks]))
112
109
  await this.gradeSemanticGroup(group);
113
- return { checks: this.checks, observations: this.observations };
110
+ return {
111
+ checks: this.checks,
112
+ observations: this.observations,
113
+ failure: this.failure,
114
+ };
115
+ }
116
+ private failGrading(message: string, cancelable = true): void {
117
+ this.failure = { message, cancelable };
118
+ this.effects.onFailure(this.failure);
114
119
  }
115
120
  private async semanticInput(
116
121
  pattern: string | undefined,
@@ -122,16 +127,12 @@ class SemanticGrading {
122
127
 
123
128
  private async semanticWorkspace(): Promise<string> {
124
129
  return createFixture(
125
- { files: {} },
126
- [],
127
- undefined,
128
- null,
129
- null,
130
- null,
131
- null,
132
- this.input.projectRoot,
133
- this.input.signal,
134
- this.input.reservedWorkspace,
130
+ { kind: "inline", files: {} },
131
+ {
132
+ projectRoot: this.input.projectRoot,
133
+ signal: this.input.signal,
134
+ reservedWorkspace: this.input.reservedWorkspace,
135
+ },
135
136
  );
136
137
  }
137
138
 
@@ -150,6 +151,13 @@ class SemanticGrading {
150
151
  if (!host) throw new Error("semantic host is unavailable");
151
152
  const response = await host
152
153
  .run({
154
+ retainHostDiagnostics: (bytes) =>
155
+ this.effects.evidence.retainArtifacts([
156
+ {
157
+ id: `sevro.semantic${suffix}.sevro.host.diagnostics`,
158
+ bytes: Buffer.from(bytes),
159
+ },
160
+ ]),
153
161
  candidateTranscriptRoot: this.input.candidateTranscriptRoot,
154
162
  candidateArtifactRoots: this.input.candidateArtifactRoots,
155
163
  prompt: semanticHostPrompt(message, group, artifact),
@@ -163,45 +171,24 @@ class SemanticGrading {
163
171
  signal: this.input.signal,
164
172
  })
165
173
  .catch((cause: unknown) => {
166
- if (cause instanceof CodexProcessFailure) return cause.result;
174
+ if (cause instanceof HostExecutionFailure) return cause.result;
167
175
  throw cause;
168
176
  });
169
177
  const artifacts = scopedHostArtifacts(
170
178
  response,
171
179
  `sevro.semantic${suffix}.`,
172
- this.effects.artifactRefs,
180
+ this.effects.evidence.artifactRefs,
173
181
  this.input.declaredChecks,
174
182
  `sevro.semantic.verdicts${suffix}`,
175
183
  );
176
184
  return { result: response, artifacts, source, hostId: host.id };
177
- } catch {
178
- this.effects.failGrading("semantic grading did not complete");
185
+ } catch (cause) {
186
+ if (cause instanceof HostDiagnosticPersistenceError) throw cause;
187
+ this.failGrading("semantic grading did not complete");
179
188
  return { result: null, artifacts: [], source, hostId: null };
180
189
  }
181
190
  }
182
191
 
183
- private async retainSemanticVerdicts(
184
- result: HostResult,
185
- suffix: string,
186
- ): Promise<void> {
187
- if (
188
- result.finalMessage !== null &&
189
- Buffer.byteLength(result.finalMessage, "utf8") <= 1024 * 1024
190
- ) {
191
- const bytes = Buffer.from(result.finalMessage, "utf8");
192
- const path = join(
193
- this.input.runDir,
194
- `trial-${this.input.trial}-semantic-verdicts${suffix}.json`,
195
- );
196
- await this.effects.writeTrialFile(path, bytes);
197
- this.effects.artifactRefs.push({
198
- id: `sevro.semantic.verdicts${suffix}`,
199
- path: pathToFileURL(path).href,
200
- sha256: sha256(bytes),
201
- });
202
- }
203
- }
204
-
205
192
  private recordSemanticVerdict(
206
193
  verdict: SemanticVerdict,
207
194
  pattern: string | undefined,
@@ -242,8 +229,8 @@ class SemanticGrading {
242
229
  this.recordSemanticVerdict(verdict, pattern, source, hostId);
243
230
  return true;
244
231
  } catch {
245
- this.effects.failGrading(
246
- codexFailureMessage(result) ?? "semantic grading did not complete",
232
+ this.failGrading(
233
+ result.failureMessage ?? "semantic grading did not complete",
247
234
  result.processFailure?.reason === "cancelled",
248
235
  );
249
236
  return false;
@@ -259,7 +246,7 @@ class SemanticGrading {
259
246
  private async gradeSemanticGroup(group: SemanticGroup): Promise<void> {
260
247
  const pattern = group[0].artifactPath;
261
248
  const suffix = semanticGroupSuffix(pattern);
262
- if (!this.effects.canGrade()) return;
249
+ if (this.failure) return;
263
250
  const message = this.input.message;
264
251
  if (message === null) {
265
252
  this.checks.push(
@@ -283,14 +270,17 @@ class SemanticGrading {
283
270
  message,
284
271
  );
285
272
  if (outcome.result === null) return;
286
- await this.effects.retainArtifacts(outcome.artifacts);
273
+ await this.effects.evidence.retainArtifacts(outcome.artifacts);
287
274
  this.observations.push({
288
275
  id: `sevro.observation.semantic.usage${suffix}`,
289
276
  source: outcome.hostId,
290
277
  completeness: outcome.result.usageComplete ? "complete" : "partial",
291
278
  data: usage(outcome.result),
292
279
  });
293
- await this.retainSemanticVerdicts(outcome.result, suffix);
280
+ await this.effects.evidence.retainSemanticVerdicts(
281
+ outcome.result.finalMessage,
282
+ suffix,
283
+ );
294
284
  successful = this.parseSemanticResults(
295
285
  outcome.result,
296
286
  group,