@bjoernrochel/sevro 0.1.0-rc.2 → 0.1.0-rc.21

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 (243) hide show
  1. package/CONTRIBUTING.md +170 -0
  2. package/LICENSE +63 -136
  3. package/README.md +83 -66
  4. package/docs/README.md +44 -0
  5. package/docs/acknowledgements.md +14 -0
  6. package/docs/architecture.md +146 -0
  7. package/docs/assets/README.md +27 -0
  8. package/docs/assets/sevro-logo.png +0 -0
  9. package/docs/assets/sevro-symbol.svg +1 -0
  10. package/docs/assets/sevro-wordmark.svg +1 -0
  11. package/docs/builtin-graders-v1.md +41 -13
  12. package/docs/codex-host.md +184 -52
  13. package/docs/contracts.md +19 -0
  14. package/docs/creating-cases.md +139 -0
  15. package/docs/development-cli.md +190 -30
  16. package/docs/documentation-quality.md +61 -0
  17. package/docs/evaluation-workflow.md +110 -0
  18. package/docs/evidence/code-mode-controls-71.md +64 -0
  19. package/docs/evidence/code-mode-programs-85.md +64 -0
  20. package/docs/evidence/codex-state-73/README.md +204 -0
  21. package/docs/evidence/codex-state-73/darrow-migration.patch +182 -0
  22. package/docs/evidence/codex-state-73/results.json +661 -0
  23. package/docs/evidence/local-http-74.md +104 -0
  24. package/docs/evidence/runtime-startup-30/README.md +185 -0
  25. package/docs/evidence/runtime-startup-30/after.json +319 -0
  26. package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +2 -0
  27. package/docs/evidence/runtime-startup-30/before.json +403 -0
  28. package/docs/evidence/runtime-startup-30/consumer-smoke.json +63 -0
  29. package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +3 -0
  30. package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +3 -0
  31. package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +20 -0
  32. package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +2 -0
  33. package/docs/evidence/skill-reads-70/results.json +823 -0
  34. package/docs/evidence.md +21 -0
  35. package/docs/extension-protocol-v1.md +50 -16
  36. package/docs/fixture-cleanup-validation.md +3 -0
  37. package/docs/getting-started.md +102 -0
  38. package/docs/guide-evaluation.md +89 -0
  39. package/docs/guide-verification.json +239 -0
  40. package/docs/identity-v1.md +35 -1
  41. package/docs/installing.md +88 -0
  42. package/docs/licenses/BUSL-1.1-0.1.0-rc.2.txt +159 -0
  43. package/docs/licensing.md +42 -0
  44. package/docs/native-controls.md +76 -5
  45. package/docs/native-hosts.md +228 -0
  46. package/docs/reading-results.md +141 -0
  47. package/docs/recipes.md +489 -0
  48. package/docs/releases.md +81 -40
  49. package/docs/report-v1.md +29 -1
  50. package/docs/results-v1.md +180 -0
  51. package/docs/run-settings-v1.md +147 -0
  52. package/docs/running-evaluations.md +31 -0
  53. package/docs/runtime-v1.md +614 -0
  54. package/docs/specs/repository-guide.md +58 -0
  55. package/docs/troubleshooting.md +30 -0
  56. package/docs/typescript-quality.md +234 -0
  57. package/docs/usability-study.md +114 -0
  58. package/examples/basic/host.ts +2 -2
  59. package/examples/recipes/candidate.ts +26 -0
  60. package/examples/recipes/cases/coding-change.json +21 -0
  61. package/examples/recipes/cases/host-tools.json +21 -0
  62. package/examples/recipes/cases/mise.json +21 -0
  63. package/examples/recipes/cases/node.json +21 -0
  64. package/examples/recipes/cases/python-uv.json +21 -0
  65. package/examples/recipes/cases/semantic.json +17 -0
  66. package/examples/recipes/coding-change/check.ts +23 -0
  67. package/examples/recipes/extension-artifacts.ts +65 -0
  68. package/examples/recipes/extension-methods.ts +174 -0
  69. package/examples/recipes/extension.ts +37 -0
  70. package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
  71. package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
  72. package/examples/recipes/repository-skill/README.md +3 -0
  73. package/examples/recipes/run-inputs.ts +188 -0
  74. package/examples/recipes/run-options.ts +131 -0
  75. package/examples/recipes/run.ts +63 -0
  76. package/examples/recipes/runtime/empty.json +1 -0
  77. package/examples/recipes/runtime/goals.json +1 -0
  78. package/examples/recipes/runtime/mise.json +6 -0
  79. package/examples/recipes/runtime/path.json +1 -0
  80. package/examples/recipes/runtime/plugins.json +4 -0
  81. package/examples/recipes/runtime/python-uv.json +10 -0
  82. package/examples/recipes/semantic-judge.ts +29 -0
  83. package/examples/templates/coding/adapter.ts +17 -0
  84. package/examples/templates/coding/case.json +15 -0
  85. package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
  86. package/examples/templates/repository-skill/extension-data.ts +64 -0
  87. package/examples/templates/repository-skill/extension.ts +31 -0
  88. package/examples/templates/response/adapter.ts +12 -0
  89. package/examples/templates/response/case.json +13 -0
  90. package/package.json +39 -6
  91. package/schemas/report-v1.schema.json +9 -0
  92. package/schemas/run-evidence-v1.schema.json +2 -1
  93. package/schemas/runtime-v1.schema.json +193 -0
  94. package/src/advisory.ts +10 -5
  95. package/src/candidate-artifacts.ts +208 -0
  96. package/src/candidate-codex-state.ts +185 -0
  97. package/src/case-validation.ts +105 -0
  98. package/src/cli/cancellation.ts +20 -0
  99. package/src/cli/command.ts +81 -0
  100. package/src/cli/completion.ts +15 -0
  101. package/src/cli/demo.ts +59 -0
  102. package/src/cli/evaluation.ts +55 -0
  103. package/src/cli/extension.ts +79 -0
  104. package/src/cli/help.ts +222 -0
  105. package/src/cli/hosts.ts +59 -0
  106. package/src/cli/init-settings.ts +30 -0
  107. package/src/cli/init.ts +111 -0
  108. package/src/cli/invocation.ts +627 -0
  109. package/src/cli/loading.ts +151 -0
  110. package/src/cli/preparation.ts +163 -0
  111. package/src/cli/presentation.ts +91 -0
  112. package/src/cli/progress.ts +6 -0
  113. package/src/cli/run-manifest.ts +133 -0
  114. package/src/cli/run-options.ts +101 -0
  115. package/src/cli/run-overrides.ts +83 -0
  116. package/src/cli/settings.ts +160 -0
  117. package/src/cli/summary.ts +43 -0
  118. package/src/cli.ts +13 -843
  119. package/src/contract-types.ts +94 -0
  120. package/src/engine.ts +19 -2373
  121. package/src/evaluation/advisory.ts +150 -0
  122. package/src/evaluation/check-plan.ts +510 -0
  123. package/src/evaluation/collection.ts +88 -0
  124. package/src/evaluation/context.ts +10 -0
  125. package/src/evaluation/grading-host.ts +39 -0
  126. package/src/evaluation/grading.ts +258 -0
  127. package/src/evaluation/invocation-plan.ts +548 -0
  128. package/src/evaluation/plan.ts +736 -0
  129. package/src/evaluation/progress.ts +19 -0
  130. package/src/evaluation/resources.ts +241 -0
  131. package/src/evaluation/run.ts +206 -0
  132. package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +3 -1
  133. package/src/evaluation/semantic.ts +312 -0
  134. package/src/evaluation/trial.ts +866 -0
  135. package/src/evaluation/types.ts +207 -0
  136. package/src/evidence/host-results.ts +92 -0
  137. package/src/evidence/retained-artifacts.ts +18 -0
  138. package/src/evidence/trial-writer.ts +203 -0
  139. package/src/evidence/usage.ts +17 -0
  140. package/src/extension-client.ts +228 -148
  141. package/src/extension-session.ts +326 -195
  142. package/src/fixtures/advisory.ts +338 -0
  143. package/src/fixtures/bin.ts +57 -0
  144. package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
  145. package/src/fixtures/create.ts +187 -0
  146. package/src/fixtures/generated.ts +300 -0
  147. package/src/fixtures/git-hooks.ts +24 -0
  148. package/src/fixtures/preparation.ts +270 -0
  149. package/src/{repository-fixture.ts → fixtures/repository.ts} +207 -85
  150. package/src/fixtures/setup.ts +224 -0
  151. package/src/fixtures/tools.ts +88 -0
  152. package/src/fixtures/workspace-state.ts +212 -0
  153. package/src/generated/cli-result.cjs +2 -0
  154. package/src/generated/cli-result.d.cts +3 -0
  155. package/src/generated/extension.cjs +2 -0
  156. package/src/generated/extension.d.cts +3 -0
  157. package/src/generated/report.cjs +2 -0
  158. package/src/generated/report.d.cts +3 -0
  159. package/src/generated/run-evidence.cjs +2 -0
  160. package/src/generated/run-evidence.d.cts +3 -0
  161. package/src/generated/runtime.cjs +2 -0
  162. package/src/generated/runtime.d.cts +3 -0
  163. package/src/graders/artifact.ts +56 -23
  164. package/src/graders/git-head.ts +79 -75
  165. package/src/graders/output.ts +222 -140
  166. package/src/graders/semantic.ts +102 -43
  167. package/src/graders/shell.ts +379 -207
  168. package/src/host-diagnostic-redaction.ts +52 -0
  169. package/src/host-diagnostics.ts +319 -0
  170. package/src/hosts/claude-continuation.ts +63 -18
  171. package/src/hosts/claude-credential.ts +43 -62
  172. package/src/hosts/claude-events.ts +86 -51
  173. package/src/hosts/claude-keychain.ts +59 -0
  174. package/src/hosts/claude-native-goal.ts +57 -33
  175. package/src/hosts/claude-process.ts +111 -0
  176. package/src/hosts/claude-repository-invocation.ts +208 -117
  177. package/src/hosts/claude-settings.ts +240 -21
  178. package/src/hosts/claude-skill-activations.ts +166 -0
  179. package/src/hosts/claude-state.ts +403 -0
  180. package/src/hosts/claude-tool-calls.ts +171 -106
  181. package/src/hosts/claude.ts +561 -451
  182. package/src/hosts/codex-app-server.ts +326 -178
  183. package/src/hosts/codex-configuration.ts +44 -18
  184. package/src/hosts/codex-events.ts +97 -60
  185. package/src/hosts/codex-exec-controls.ts +516 -0
  186. package/src/hosts/codex-invocation.ts +70 -0
  187. package/src/hosts/codex-marketplace.ts +283 -0
  188. package/src/hosts/codex-native-calls.ts +546 -500
  189. package/src/hosts/codex-process.ts +202 -0
  190. package/src/hosts/codex-profile.ts +257 -28
  191. package/src/hosts/codex-session-files.ts +106 -0
  192. package/src/hosts/codex-state.ts +492 -0
  193. package/src/hosts/codex.ts +783 -832
  194. package/src/hosts/isolation-common.ts +94 -0
  195. package/src/hosts/isolation-roots.ts +54 -27
  196. package/src/hosts/isolation.ts +10 -0
  197. package/src/hosts/linux-sandbox.ts +159 -0
  198. package/src/hosts/mac-sandbox.ts +187 -61
  199. package/src/hosts/native-controls.ts +152 -62
  200. package/src/hosts/process-lifecycle.ts +37 -0
  201. package/src/hosts/protected-worktrees.ts +51 -19
  202. package/src/hosts/skill-hook-catalog.ts +131 -0
  203. package/src/hosts/skill-hook-handler.ts +374 -0
  204. package/src/hosts/skill-hooks.ts +580 -0
  205. package/src/hosts/workspace-fingerprint.ts +55 -35
  206. package/src/identity.ts +102 -48
  207. package/src/instrumentation.ts +109 -48
  208. package/src/native-transcript-state.ts +131 -0
  209. package/src/native-transcript-view.ts +90 -0
  210. package/src/native-transcripts.ts +286 -0
  211. package/src/process-lifecycle.ts +150 -0
  212. package/src/provenance.ts +161 -81
  213. package/src/prune.ts +215 -0
  214. package/src/report.ts +172 -140
  215. package/src/resolved-case.ts +33 -0
  216. package/src/results-lock.ts +29 -0
  217. package/src/results.ts +133 -86
  218. package/src/run-owner.ts +154 -63
  219. package/src/runtime/config.ts +456 -0
  220. package/src/runtime/discovery-apple.ts +90 -0
  221. package/src/runtime/discovery-providers.ts +63 -0
  222. package/src/runtime/discovery.ts +70 -0
  223. package/src/runtime/hook-processes.ts +97 -0
  224. package/src/runtime/hooks.ts +476 -0
  225. package/src/runtime/paths.ts +313 -0
  226. package/src/runtime/query.ts +85 -0
  227. package/src/runtime/seeds.ts +134 -0
  228. package/src/runtime/shared-cache.ts +143 -0
  229. package/src/runtime/state.ts +143 -0
  230. package/src/schema-types.ts +198 -0
  231. package/src/schema.ts +15 -19
  232. package/src/transcript-retention.ts +146 -0
  233. package/src/value-guards.ts +20 -0
  234. package/src/advisory-fixture.ts +0 -282
  235. package/src/fixture-bin.ts +0 -87
  236. package/src/fixture-setup.ts +0 -163
  237. package/src/generated-fixture.ts +0 -220
  238. package/src/git-hooks.ts +0 -63
  239. package/src/hosts/claude-nested-skills.ts +0 -218
  240. package/src/hosts/codex-command-output.ts +0 -399
  241. package/src/hosts/codex-executor-command.ts +0 -195
  242. package/src/hosts/codex-skill-reads.ts +0 -391
  243. package/src/preparation.ts +0 -211
@@ -0,0 +1,866 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { hashJson } from "../identity";
3
+ import {
4
+ allocateCandidateRuntime,
5
+ candidateArtifactRoots,
6
+ releaseCandidateRuntime,
7
+ type CandidateArtifactRoots,
8
+ } from "../candidate-artifacts";
9
+ import { join } from "node:path";
10
+ import { advisoryBaseRevision } from "../fixtures/advisory";
11
+ import type { EvaluationContext } from "./context";
12
+ import { prepareTrialWorkspace } from "./resources";
13
+ import type { PreparedFixture, PreparedInvocation } from "./plan";
14
+ import { hostArtifacts, hostObservations } from "../evidence/host-results";
15
+ import {
16
+ TrialEvidenceWriter,
17
+ type TrialObservation,
18
+ } from "../evidence/trial-writer";
19
+ import { usage } from "../evidence/usage";
20
+ import { runAdvisoryReview, type AdvisoryReview } from "./advisory";
21
+ import {
22
+ gradeCandidateOutput,
23
+ gradeShellChecks,
24
+ gradeGitHeadChecks,
25
+ gradeExtensionChecks,
26
+ type GradingFailure,
27
+ } from "./grading";
28
+ import { gradeSemanticChecks } from "./semantic";
29
+ import {
30
+ HostExecutionFailure,
31
+ type EvaluationOptions,
32
+ type HostAdapter,
33
+ type HostResult,
34
+ } from "./types";
35
+ import type { EvaluationResult } from "../extension-session";
36
+ import type {
37
+ Diagnostic,
38
+ ExecutionStatus,
39
+ ObservationCompleteness,
40
+ TaskVerdict,
41
+ } from "../contract-types";
42
+ import {
43
+ clearFixtureContents,
44
+ removeDisposableDirectory,
45
+ } from "../fixtures/cleanup";
46
+ import { retainWorkspace } from "../fixtures/workspace-state";
47
+ import { gitHeadRevision } from "../graders/git-head";
48
+ import {
49
+ InstrumentationEvidenceError,
50
+ snapshotInstrumentation,
51
+ verifyAppliedInstrumentation,
52
+ type InstrumentationRequest,
53
+ } from "../instrumentation";
54
+ import { materializeNativeTranscriptView } from "../native-transcript-view";
55
+ import {
56
+ applyTaskVerdictPolicy,
57
+ assessTrial,
58
+ type Assessment,
59
+ type CheckOutcome,
60
+ } from "../results";
61
+ import type { RunEvidenceDataTrialEvidence } from "../schema-types";
62
+ import { scheduleTrials } from "./scheduler";
63
+ import { rethrowDiagnosticPersistenceError } from "../host-diagnostics";
64
+ import { evaluationProgress } from "./progress";
65
+
66
+ const MAX_FINAL_MESSAGE_BYTES = 8 * 1024 * 1024;
67
+
68
+ function candidateMarketplaceRequest(
69
+ invocation: PreparedInvocation,
70
+ artifacts: PreparedFixture["inlineArtifacts"],
71
+ ) {
72
+ const marketplace = invocation.codexMarketplace;
73
+ if (!marketplace) return {};
74
+ return {
75
+ codexMarketplace: {
76
+ ...marketplace,
77
+ artifactPaths: artifacts
78
+ .filter((artifact) =>
79
+ artifact.relativePath.startsWith(`${marketplace.artifactRoot}/`),
80
+ )
81
+ .map((artifact) => artifact.relativePath),
82
+ },
83
+ };
84
+ }
85
+
86
+ function candidatePluginDirectoriesRequest(
87
+ invocation: PreparedInvocation,
88
+ artifacts: PreparedFixture["inlineArtifacts"],
89
+ ) {
90
+ const directories = invocation.claudePluginDirs;
91
+ if (!directories) return {};
92
+ return {
93
+ claudePluginDirs: {
94
+ ...directories,
95
+ artifactPaths: artifacts
96
+ .filter((artifact) =>
97
+ directories.artifactRoots.some((root) =>
98
+ artifact.relativePath.startsWith(`${root}/`),
99
+ ),
100
+ )
101
+ .map((artifact) => artifact.relativePath),
102
+ },
103
+ };
104
+ }
105
+
106
+ function candidateInvocationRequest(invocation: PreparedInvocation) {
107
+ if (
108
+ invocation.invocation === undefined ||
109
+ invocation.invocationToken === null
110
+ )
111
+ return {};
112
+ return {
113
+ explicitSkillInvocation: {
114
+ ...invocation.invocation,
115
+ token: invocation.invocationToken,
116
+ },
117
+ };
118
+ }
119
+
120
+ function ensureNotCancelled(signal: AbortSignal | undefined): void {
121
+ if (signal?.aborted) throw new Error("cancelled");
122
+ }
123
+
124
+ function hostFailureDiagnostic(execution: string, error: unknown) {
125
+ if (execution === "cancelled")
126
+ return { code: "sevro.run.cancelled", message: "run cancelled" };
127
+ if (error instanceof InstrumentationEvidenceError)
128
+ return { code: "sevro.instrumentation.mismatch", message: error.message };
129
+ if (error instanceof HostExecutionFailure)
130
+ return { code: "sevro.host.failed", message: error.message };
131
+ return {
132
+ code: "sevro.host.failed",
133
+ message: "host execution did not complete",
134
+ };
135
+ }
136
+
137
+ function extensionFinalMessageData(result: HostResult | null) {
138
+ return result?.finalMessage == null ? {} : { text: result.finalMessage };
139
+ }
140
+
141
+ function compareDeclaredChecks(
142
+ left: CheckOutcome,
143
+ right: CheckOutcome,
144
+ order: Map<string, number>,
145
+ ): number {
146
+ const leftIndex = order.get(left.id);
147
+ const rightIndex = order.get(right.id);
148
+ // Match the stable NaN comparator fallback; assessment reports undeclared IDs.
149
+ if (leftIndex === undefined || rightIndex === undefined) return 0;
150
+ return leftIndex - rightIndex;
151
+ }
152
+
153
+ function selectedTrialTaskPolicy(
154
+ options: EvaluationOptions,
155
+ recommendation: TaskVerdict | null,
156
+ ) {
157
+ const id = options.extension?.session.identity.selectedTaskVerdictPolicy;
158
+ return id ? { id, recommendation } : null;
159
+ }
160
+
161
+ interface TrialRuntimeState {
162
+ candidateTranscriptRoot?: string;
163
+ candidateRuntimeRoot?: string;
164
+ candidateArtifacts?: CandidateArtifactRoots;
165
+ diagnostic: Diagnostic | undefined;
166
+ persisted: boolean;
167
+ gitHeadBase: string | null;
168
+ advisoryRevision: string | null;
169
+ hostResult: HostResult | null;
170
+ appliedInstrumentation: InstrumentationRequest[];
171
+ additionalObservations: ReturnType<typeof hostObservations>;
172
+ producedArtifacts: ReturnType<typeof hostArtifacts>;
173
+ execution: ExecutionStatus;
174
+ candidateDurationMs: number | null;
175
+ completeness: ObservationCompleteness;
176
+ observation: TrialObservation;
177
+ shellObservations: TrialObservation[];
178
+ gitHeadObservations: TrialObservation[];
179
+ semanticObservations: TrialObservation[];
180
+ checks: CheckOutcome[];
181
+ extensionMetrics: EvaluationResult["metrics"];
182
+ domainOutcomes: NonNullable<EvaluationResult["domainOutcomes"]>;
183
+ taskPolicyRecommendation: TaskVerdict | null;
184
+ graderError: boolean;
185
+ assessment: Assessment;
186
+ advisoryReview: AdvisoryReview | null;
187
+ retainedAuxiliaryWorkspaces: string[];
188
+ }
189
+
190
+ function initialTrialState(dry: boolean | undefined): TrialRuntimeState {
191
+ return {
192
+ diagnostic: undefined,
193
+ persisted: false,
194
+ gitHeadBase: null,
195
+ advisoryRevision: null,
196
+ hostResult: null,
197
+ appliedInstrumentation: [],
198
+ additionalObservations: [],
199
+ producedArtifacts: [],
200
+ execution: dry ? "not_run" : "completed",
201
+ candidateDurationMs: null,
202
+ completeness: "unavailable",
203
+ observation: {
204
+ id: "sevro.observation.final-message",
205
+ source: "",
206
+ completeness: "unavailable",
207
+ data: {},
208
+ },
209
+ shellObservations: [],
210
+ gitHeadObservations: [],
211
+ semanticObservations: [],
212
+ checks: [],
213
+ extensionMetrics: [],
214
+ domainOutcomes: [],
215
+ taskPolicyRecommendation: null,
216
+ graderError: false,
217
+ assessment: {
218
+ execution: { status: "not_run" },
219
+ grading: { status: "not_requested" },
220
+ task: { verdict: "not_assessed" },
221
+ },
222
+ advisoryReview: null,
223
+ retainedAuxiliaryWorkspaces: [],
224
+ };
225
+ }
226
+
227
+ class EvaluationTrial {
228
+ private readonly state: TrialRuntimeState;
229
+ private readonly evidence: TrialEvidenceWriter;
230
+ constructor(
231
+ private readonly options: EvaluationOptions,
232
+ private readonly context: EvaluationContext,
233
+ private readonly trial: number,
234
+ private readonly stopAdmission: () => void,
235
+ private readonly fixture: Awaited<ReturnType<typeof prepareTrialWorkspace>>,
236
+ ) {
237
+ this.state = initialTrialState(options.dry);
238
+ this.state.candidateRuntimeRoot = fixture.candidateRuntimeRoot;
239
+ this.evidence = new TrialEvidenceWriter({
240
+ runDir: context.resources.runDir,
241
+ trial,
242
+ workspace: fixture.workspace,
243
+ preparationArtifacts: context.preparationArtifacts,
244
+ });
245
+ }
246
+ async run(): Promise<boolean> {
247
+ try {
248
+ if (this.fixture.preparationCancelled)
249
+ return await this.cancelPreparation();
250
+ await this.initializeGradingBases();
251
+ await this.runCandidate();
252
+ await this.retainCandidateArtifacts();
253
+ this.observeCandidate();
254
+ await this.prepareCandidateArtifacts();
255
+ this.progress(
256
+ "grading",
257
+ this.state.execution === "completed" ? "started" : "skipped",
258
+ );
259
+ this.gradeOutput();
260
+ await this.gradeShell();
261
+ await this.gradeGitHead();
262
+ await this.gradeSemantic();
263
+ await this.gradeExtension();
264
+ this.assess();
265
+ if (this.state.execution === "completed")
266
+ this.progress("grading", "completed");
267
+ await this.runAdvisory();
268
+ await this.retainRawCandidate();
269
+ return await this.persistTrial();
270
+ } finally {
271
+ await this.cleanup();
272
+ }
273
+ }
274
+ private async cancelPreparation(): Promise<boolean> {
275
+ this.failCandidate(undefined);
276
+ await this.retainCandidateArtifacts();
277
+ this.observeCandidate();
278
+ this.assess();
279
+ return this.persistTrial();
280
+ }
281
+ private async initializeGradingBases(): Promise<void> {
282
+ this.state.gitHeadBase = this.context.prepared.checks.preparedGitHead.length
283
+ ? await gitHeadRevision(this.fixture.workspace, this.options.signal)
284
+ : null;
285
+ this.state.advisoryRevision = this.options.advisoryHost
286
+ ? await advisoryBaseRevision(this.fixture.workspace)
287
+ : null;
288
+ }
289
+ private candidateRequest(): Parameters<HostAdapter["run"]>[0] {
290
+ return {
291
+ retainHostDiagnostics: (bytes) =>
292
+ this.evidence.retainArtifacts(
293
+ [{ id: "sevro.host.diagnostics", bytes: Buffer.from(bytes) }],
294
+ "host-",
295
+ ),
296
+ candidateRuntimeRoot: this.state.candidateRuntimeRoot,
297
+ prompt: this.fixture.trialPrompt,
298
+ ...(this.fixture.trialFollowUpPrompt !== undefined
299
+ ? { followUpPrompt: this.fixture.trialFollowUpPrompt }
300
+ : {}),
301
+ workspace: this.fixture.workspace,
302
+ condition: this.options.condition,
303
+ fixtureBinDir: this.fixture.fixtureBinDir,
304
+ runtimePolicy: this.candidateRuntimePolicy(),
305
+ instrumentation: snapshotInstrumentation(
306
+ this.context.prepared.requestedInstrumentation,
307
+ ),
308
+ ...candidateMarketplaceRequest(
309
+ this.context.prepared.invocation,
310
+ this.context.prepared.fixture.inlineArtifacts,
311
+ ),
312
+ ...candidatePluginDirectoriesRequest(
313
+ this.context.prepared.invocation,
314
+ this.context.prepared.fixture.inlineArtifacts,
315
+ ),
316
+ ...candidateInvocationRequest(this.context.prepared.invocation),
317
+ signal: this.options.signal,
318
+ };
319
+ }
320
+
321
+ private candidateRuntimePolicy() {
322
+ if (this.options.runtimePolicy) return this.options.runtimePolicy;
323
+ if (!this.context.prepared.fixture.fixtureSetup) return undefined;
324
+ return {
325
+ format: "sevro.runtime.v1" as const,
326
+ environment: {},
327
+ readOnlyRoots: [],
328
+ };
329
+ }
330
+
331
+ private candidateReservedIds(): Set<string> {
332
+ return new Set([
333
+ "sevro.observation.final-message",
334
+ "sevro.observation.workspaces",
335
+ ...this.context.preparationArtifacts.map((item) => item.id),
336
+ ...this.state.additionalObservations.map((item) => item.id),
337
+ ...this.options.case.checks.map((item) => item.id),
338
+ ...this.context.prepared.checks.preparedShell.map(
339
+ (item) => `sevro.observation.shell.${item.id}`,
340
+ ),
341
+ ...(this.context.prepared.checks.preparedGitHead.length
342
+ ? ["sevro.observation.git-head"]
343
+ : []),
344
+ ...(this.context.prepared.checks.preparedSemantic.length
345
+ ? ["sevro.semantic.verdicts"]
346
+ : []),
347
+ ]);
348
+ }
349
+
350
+ private acceptCandidateResult(response: HostResult): void {
351
+ this.state.appliedInstrumentation = verifyAppliedInstrumentation(
352
+ this.context.prepared.requestedInstrumentation,
353
+ response.appliedInstrumentation,
354
+ this.options.condition,
355
+ response.actualCondition,
356
+ );
357
+ if (
358
+ response.finalMessage !== null &&
359
+ Buffer.byteLength(response.finalMessage, "utf8") > MAX_FINAL_MESSAGE_BYTES
360
+ )
361
+ throw new Error("oversized host result");
362
+ this.state.additionalObservations = hostObservations(
363
+ response,
364
+ this.options.host.id,
365
+ new Set([
366
+ ...this.context.preparationArtifacts.map((item) => item.id),
367
+ ...this.options.case.checks.map((item) => item.id),
368
+ ]),
369
+ );
370
+ this.state.producedArtifacts = hostArtifacts(
371
+ response,
372
+ this.candidateReservedIds(),
373
+ );
374
+ if (response.executionFailed) {
375
+ this.state.execution = "failed";
376
+ this.stopAdmission();
377
+ this.state.diagnostic = {
378
+ code: "sevro.host.failed",
379
+ message: response.failureMessage ?? "host execution did not complete",
380
+ };
381
+ }
382
+ }
383
+
384
+ private failCandidate(error: unknown): void {
385
+ this.stopAdmission();
386
+ this.state.hostResult =
387
+ error instanceof HostExecutionFailure ? error.result : null;
388
+ if (this.state.hostResult)
389
+ this.acceptCandidateResult(this.state.hostResult);
390
+ this.state.execution = this.options.signal?.aborted
391
+ ? "cancelled"
392
+ : "failed";
393
+ this.state.diagnostic = hostFailureDiagnostic(this.state.execution, error);
394
+ }
395
+
396
+ private async runCandidate(): Promise<void> {
397
+ if (this.options.dry) {
398
+ this.progress("candidate", "skipped");
399
+ return;
400
+ }
401
+ this.progress("candidate", "started");
402
+ const started = performance.now();
403
+ try {
404
+ ensureNotCancelled(this.options.signal);
405
+ this.state.hostResult = await this.options.host.run(
406
+ this.candidateRequest(),
407
+ );
408
+ this.acceptCandidateResult(this.state.hostResult);
409
+ if (this.options.signal?.aborted) {
410
+ this.state.execution = "cancelled";
411
+ this.stopAdmission();
412
+ this.state.diagnostic = hostFailureDiagnostic("cancelled", undefined);
413
+ }
414
+ } catch (error) {
415
+ rethrowDiagnosticPersistenceError(error);
416
+ this.failCandidate(error);
417
+ } finally {
418
+ this.state.candidateDurationMs = Math.max(0, performance.now() - started);
419
+ this.progress("candidate", "completed");
420
+ }
421
+ }
422
+
423
+ private async retainCandidateArtifacts(): Promise<void> {
424
+ await this.evidence.retainArtifacts(this.state.producedArtifacts, "host-");
425
+ await this.evidence.verifyArtifacts();
426
+ const bundle = this.state.producedArtifacts.find(
427
+ (item) => item.id === "sevro.native-transcripts.bundle",
428
+ );
429
+ if (this.options.runtimePolicy?.nativeTranscripts && bundle)
430
+ this.state.candidateTranscriptRoot =
431
+ await materializeNativeTranscriptView(bundle.bytes);
432
+ }
433
+
434
+ private observeCandidate(): void {
435
+ this.state.observation = this.evidence.observeCandidate(
436
+ this.options.host.id,
437
+ this.state.hostResult,
438
+ );
439
+ this.state.completeness = this.state.observation.completeness;
440
+ }
441
+
442
+ private failGrading(message: string, cancelable = true): void {
443
+ if (cancelable && this.options.signal?.aborted) {
444
+ this.state.execution = "cancelled";
445
+ this.state.diagnostic = {
446
+ code: "sevro.run.cancelled",
447
+ message: "run cancelled",
448
+ };
449
+ return;
450
+ }
451
+ this.state.graderError = true;
452
+ this.state.diagnostic = { code: "sevro.grader.error", message };
453
+ this.stopAdmission();
454
+ }
455
+
456
+ private canGrade(): boolean {
457
+ return this.state.execution === "completed" && !this.state.graderError;
458
+ }
459
+
460
+ private async prepareCandidateArtifacts(): Promise<void> {
461
+ if (this.state.execution !== "completed") return;
462
+ try {
463
+ this.state.candidateArtifacts = await candidateArtifactRoots(
464
+ this.state.candidateRuntimeRoot,
465
+ );
466
+ } catch {
467
+ this.failGrading("candidate artifact directories are missing or unsafe");
468
+ }
469
+ }
470
+
471
+ private acceptGradingFailure(failure: GradingFailure | null): void {
472
+ if (failure) this.failGrading(failure.message, failure.cancelable);
473
+ }
474
+
475
+ private gradingFailureEffects() {
476
+ return {
477
+ onFailure: (failure: GradingFailure) => {
478
+ this.acceptGradingFailure(failure);
479
+ },
480
+ };
481
+ }
482
+
483
+ private gradeOutput(): void {
484
+ if (this.state.execution !== "completed") return;
485
+ const graded = gradeCandidateOutput({
486
+ result: this.state.hostResult,
487
+ checks: this.context.prepared.checks.prepared,
488
+ evidenceRefs: this.evidence.hasCandidateMessage
489
+ ? [this.state.observation.id]
490
+ : [],
491
+ });
492
+ this.state.checks = graded.checks;
493
+ this.acceptGradingFailure(graded.failure);
494
+ }
495
+
496
+ private shellRootRequest() {
497
+ const isolation = this.options.shellIsolation;
498
+ if (!isolation) return null;
499
+ return {
500
+ workspace: this.fixture.workspace,
501
+ projectRoot: this.context.prepared.fixture.projectRoot,
502
+ resultsRoot: this.options.resultsRoot,
503
+ additionalRoots: [
504
+ ...isolation.protectedRoots,
505
+ ...(this.options.preparationSources
506
+ ? [this.options.preparationSources.root]
507
+ : []),
508
+ ...(this.context.prepared.fixture.repository
509
+ ? [this.context.prepared.fixture.repository.path]
510
+ : []),
511
+ this.context.resources.runStateRoot,
512
+ ],
513
+ };
514
+ }
515
+
516
+ private async gradeShell(): Promise<void> {
517
+ if (
518
+ !this.canGrade() ||
519
+ this.context.prepared.checks.preparedShell.length === 0
520
+ )
521
+ return;
522
+ const graded = await gradeShellChecks(
523
+ {
524
+ checks: this.context.prepared.checks.preparedShell,
525
+ protectedRoots: this.shellRootRequest(),
526
+ execution: {
527
+ candidateTranscriptRoot: this.state.candidateTranscriptRoot,
528
+ candidateArtifactRoots: this.state.candidateArtifacts,
529
+ workspace: this.fixture.workspace,
530
+ fixtureBinDir: this.fixture.fixtureBinDir,
531
+ toolchainBinDir: this.options.shellIsolation?.toolchainBinDir,
532
+ runtimePolicy: this.options.runtimePolicy,
533
+ uvRuntimeCache: this.options.shellIsolation?.uvRuntimeCache,
534
+ privateStateRoot: join(
535
+ this.context.resources.stateDir,
536
+ "shell-sandbox",
537
+ ),
538
+ signal: this.options.signal,
539
+ },
540
+ },
541
+ this.gradingFailureEffects(),
542
+ );
543
+ this.state.shellObservations.push(...graded.observations);
544
+ this.state.checks.push(...graded.checks);
545
+ }
546
+
547
+ private async gradeGitHead(): Promise<void> {
548
+ if (
549
+ !this.canGrade() ||
550
+ this.context.prepared.checks.preparedGitHead.length === 0
551
+ )
552
+ return;
553
+ const graded = await gradeGitHeadChecks(
554
+ {
555
+ checks: this.context.prepared.checks.preparedGitHead,
556
+ base: this.state.gitHeadBase,
557
+ workspace: this.fixture.workspace,
558
+ signal: this.options.signal,
559
+ },
560
+ this.gradingFailureEffects(),
561
+ );
562
+ this.state.gitHeadObservations.push(...graded.observations);
563
+ this.state.checks.push(...graded.checks);
564
+ }
565
+
566
+ private completeCandidateMessage(): string | null {
567
+ const result = this.state.hostResult;
568
+ if (result?.finalMessage == null || !result.complete) return null;
569
+ return result.finalMessage;
570
+ }
571
+
572
+ private async gradeSemantic(): Promise<void> {
573
+ if (!this.canGrade()) return;
574
+ const graded = await gradeSemanticChecks(
575
+ {
576
+ checks: this.context.prepared.checks.preparedSemantic,
577
+ declaredChecks: this.options.case.checks,
578
+ host: this.options.semanticHost,
579
+ message: this.completeCandidateMessage(),
580
+ candidateWorkspace: this.fixture.workspace,
581
+ candidateTranscriptRoot: this.state.candidateTranscriptRoot,
582
+ candidateArtifactRoots: this.state.candidateArtifacts,
583
+ projectRoot: this.context.prepared.fixture.projectRoot,
584
+ reservedWorkspace: this.context.resources.workspaces.semantic(
585
+ this.trial,
586
+ ),
587
+ runtimePolicy: this.options.runtimePolicy,
588
+ signal: this.options.signal,
589
+ },
590
+ {
591
+ evidence: this.evidence,
592
+ ...this.gradingFailureEffects(),
593
+ retainWorkspace: (workspace) =>
594
+ this.retainAuxiliaryWorkspace(workspace),
595
+ },
596
+ );
597
+ this.state.semanticObservations.push(...graded.observations);
598
+ this.state.checks.push(...graded.checks);
599
+ }
600
+
601
+ private trialObservations(): TrialObservation[] {
602
+ return [
603
+ this.state.observation,
604
+ ...this.state.shellObservations,
605
+ ...this.state.gitHeadObservations,
606
+ ...this.state.semanticObservations,
607
+ ...this.state.additionalObservations,
608
+ {
609
+ id: "sevro.observation.workspaces",
610
+ source: "sevro.runner",
611
+ completeness: "complete",
612
+ data: {
613
+ candidate: this.fixture.workspace,
614
+ retainedAuxiliary: this.state.retainedAuxiliaryWorkspaces,
615
+ },
616
+ },
617
+ ];
618
+ }
619
+
620
+ private extensionEvaluationRequest(): Parameters<
621
+ NonNullable<EvaluationOptions["extension"]>["session"]["evaluate"]
622
+ >[0] {
623
+ return {
624
+ caseId: this.options.case.id,
625
+ execution: { status: this.state.execution },
626
+ observations: this.trialObservations().map((observation) =>
627
+ observation === this.state.observation
628
+ ? {
629
+ ...observation,
630
+ completeness: this.state.completeness,
631
+ data: {
632
+ ...observation.data,
633
+ ...extensionFinalMessageData(this.state.hostResult),
634
+ },
635
+ }
636
+ : observation,
637
+ ),
638
+ builtinChecks: this.state.checks.map((check) => ({
639
+ id: check.id,
640
+ status: check.status,
641
+ ...(check.detail ? { detail: check.detail } : {}),
642
+ evidenceRefs: check.evidenceRefs ?? [],
643
+ })),
644
+ artifacts: this.evidence.artifactRefs,
645
+ extensionData: this.context.prepared.extensionData,
646
+ };
647
+ }
648
+
649
+ private async gradeExtension(): Promise<void> {
650
+ const extension = this.options.extension;
651
+ if (!extension || !this.canGrade()) return;
652
+ const graded = await gradeExtensionChecks(
653
+ {
654
+ evaluate: (request) => extension.session.evaluate(request),
655
+ request: this.extensionEvaluationRequest(),
656
+ declarations: this.context.prepared.checks.extensionDeclarations,
657
+ },
658
+ this.gradingFailureEffects(),
659
+ );
660
+ this.state.checks.push(...graded.checks);
661
+ this.state.extensionMetrics = graded.metrics;
662
+ this.state.domainOutcomes = graded.domainOutcomes;
663
+ this.state.taskPolicyRecommendation = graded.recommendation;
664
+ }
665
+
666
+ private assess(): void {
667
+ const declaredOrder = new Map(
668
+ this.options.case.checks.map((check, index) => [check.id, index]),
669
+ );
670
+ this.state.checks.sort((left, right) =>
671
+ compareDeclaredChecks(left, right, declaredOrder),
672
+ );
673
+ const defaultAssessment = assessTrial({
674
+ execution: this.state.execution,
675
+ declaredChecks: this.options.case.checks
676
+ .filter(
677
+ (check) => !this.context.prepared.checks.replaced.has(check.grader),
678
+ )
679
+ .map((check) => check.id),
680
+ checks: this.state.checks,
681
+ graderError: this.state.graderError,
682
+ requiredEvidenceUnavailable: this.options.case.requiredEvidence.some(
683
+ (id) =>
684
+ !this.state.producedArtifacts.some((item) => item.id === id) &&
685
+ !this.trialObservations().some(
686
+ (item) => item.id === id && item.completeness === "complete",
687
+ ),
688
+ ),
689
+ });
690
+ this.state.assessment = applyTaskVerdictPolicy(
691
+ defaultAssessment,
692
+ this.state.taskPolicyRecommendation,
693
+ Boolean(
694
+ this.options.extension?.session.identity.selectedTaskVerdictPolicy,
695
+ ),
696
+ );
697
+ }
698
+ private retainAuxiliaryWorkspace(workspace: string): Promise<void> {
699
+ this.state.retainedAuxiliaryWorkspaces.push(workspace);
700
+ this.context.resources.workspaces.retain(workspace);
701
+ return retainWorkspace(workspace);
702
+ }
703
+
704
+ private async runAdvisory(): Promise<void> {
705
+ const host = this.options.advisoryHost;
706
+ if (!host) return;
707
+ this.state.advisoryReview = await runAdvisoryReview(
708
+ {
709
+ host,
710
+ candidateCompleted: this.state.execution === "completed",
711
+ candidateWorkspace: this.fixture.workspace,
712
+ candidateTranscriptRoot: this.state.candidateTranscriptRoot,
713
+ candidateArtifactRoots: this.state.candidateArtifacts,
714
+ baseRevision: this.state.advisoryRevision,
715
+ excludedPaths: this.options.advisoryExcludedPaths,
716
+ task: this.fixture.trialPrompt,
717
+ checks: this.state.checks,
718
+ declaredChecks: this.options.case.checks,
719
+ runtimePolicy: this.options.runtimePolicy,
720
+ signal: this.options.signal,
721
+ },
722
+ {
723
+ evidence: this.evidence,
724
+ retainWorkspace: (workspace) =>
725
+ this.retainAuxiliaryWorkspace(workspace),
726
+ },
727
+ );
728
+ }
729
+
730
+ private retainRawCandidate(): Promise<void> {
731
+ return this.evidence.retainRawCandidate(
732
+ this.state.hostResult?.finalMessage,
733
+ );
734
+ }
735
+
736
+ private trialEvidenceRecord(): RunEvidenceDataTrialEvidence {
737
+ return {
738
+ caseId: this.options.case.id,
739
+ trial: this.trial,
740
+ executionMode: this.options.dry ? "dry" : "executed",
741
+ candidateDurationMs: this.state.candidateDurationMs,
742
+ condition: {
743
+ requested: this.options.condition,
744
+ actual: this.state.hostResult?.actualCondition ?? "unknown",
745
+ appliedInstrumentation: this.state.appliedInstrumentation,
746
+ },
747
+ observationCompleteness: this.state.completeness,
748
+ observations: this.trialObservations(),
749
+ metrics: this.state.extensionMetrics,
750
+ domainOutcomes: this.state.domainOutcomes,
751
+ taskVerdictPolicy: selectedTrialTaskPolicy(
752
+ this.options,
753
+ this.state.taskPolicyRecommendation,
754
+ ),
755
+ advisoryReview: this.state.advisoryReview,
756
+ routes: this.context.prepared.routes,
757
+ usage: usage(this.state.hostResult),
758
+ rawResult: this.evidence.candidateRawResult(this.options.host.id),
759
+ artifactRefs: this.evidence.artifactRefs,
760
+ };
761
+ }
762
+
763
+ private async persistTrial(): Promise<boolean> {
764
+ const evidence = this.trialEvidenceRecord();
765
+ this.progress("evidence", "started");
766
+ const trialSummary = await this.evidence.persistTrial(
767
+ this.options.case.id,
768
+ this.state.assessment,
769
+ this.state.checks,
770
+ this.state.domainOutcomes,
771
+ evidence,
772
+ );
773
+ this.state.persisted = true;
774
+ this.state.diagnostic = this.evidence.diagnosticLocation(
775
+ this.state.diagnostic,
776
+ );
777
+ this.context.results.complete({
778
+ summary: trialSummary,
779
+ evidence,
780
+ diagnostic: this.state.diagnostic,
781
+ });
782
+ this.progress("evidence", "completed");
783
+ return !(
784
+ (this.state.execution !== "completed" &&
785
+ this.state.execution !== "not_run") ||
786
+ this.state.graderError
787
+ );
788
+ }
789
+ private progress(
790
+ stage: "candidate" | "grading" | "evidence",
791
+ status: "started" | "completed" | "skipped",
792
+ ): void {
793
+ evaluationProgress(this.options, stage, status, this.trial);
794
+ }
795
+ private async cleanup(): Promise<void> {
796
+ await removeDisposableDirectory(
797
+ join(
798
+ this.context.resources.stateDir,
799
+ "shell-sandbox/runtime",
800
+ hashJson(this.fixture.workspace),
801
+ ),
802
+ );
803
+ if (this.state.candidateTranscriptRoot)
804
+ await rm(this.state.candidateTranscriptRoot, {
805
+ recursive: true,
806
+ force: true,
807
+ });
808
+ if (this.state.persisted && !this.retainFailedWorkspace()) {
809
+ try {
810
+ await clearFixtureContents(this.fixture.workspace);
811
+ this.context.resources.workspaces.completed(this.fixture.reservation);
812
+ } catch {
813
+ console.warn(
814
+ `fixture cleanup failed; retained at ${this.fixture.workspace}`,
815
+ );
816
+ }
817
+ }
818
+ }
819
+ private retainFailedWorkspace(): boolean {
820
+ return (
821
+ this.state.execution === "failed" ||
822
+ this.state.execution === "cancelled" ||
823
+ this.state.graderError ||
824
+ this.state.assessment.grading.status === "unavailable" ||
825
+ this.state.assessment.task.verdict === "failed"
826
+ );
827
+ }
828
+ }
829
+ export async function executeEvaluationTrials(
830
+ options: EvaluationOptions,
831
+ context: EvaluationContext,
832
+ ): Promise<void> {
833
+ await scheduleTrials(
834
+ {
835
+ count: options.trialCount,
836
+ jobs: context.prepared.jobs,
837
+ signal: options.signal,
838
+ },
839
+ async (trial, stopAdmission) => {
840
+ const candidateRuntimeRoot = await allocateCandidateRuntime(
841
+ options.runtimePolicy,
842
+ Boolean(context.prepared.fixture.fixtureSetup),
843
+ );
844
+ try {
845
+ const fixture = await prepareTrialWorkspace(
846
+ options,
847
+ context.prepared.fixture,
848
+ context.prepared.invocation,
849
+ context.resources.workspaces,
850
+ trial,
851
+ candidateRuntimeRoot,
852
+ );
853
+ evaluationProgress(options, "preparation", "completed", trial);
854
+ return await new EvaluationTrial(
855
+ options,
856
+ context,
857
+ trial,
858
+ stopAdmission,
859
+ fixture,
860
+ ).run();
861
+ } finally {
862
+ await releaseCandidateRuntime(candidateRuntimeRoot);
863
+ }
864
+ },
865
+ );
866
+ }