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