@bjoernrochel/sevro 0.1.0-rc.7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/CONTRIBUTING.md +22 -6
  2. package/README.md +5 -1
  3. package/docs/README.md +19 -15
  4. package/docs/architecture.md +138 -17
  5. package/docs/assets/README.md +5 -4
  6. package/docs/builtin-graders-v1.md +17 -3
  7. package/docs/codex-host.md +155 -49
  8. package/docs/contracts.md +2 -1
  9. package/docs/creating-cases.md +9 -2
  10. package/docs/development-cli.md +187 -19
  11. package/docs/documentation-quality.md +12 -2
  12. package/docs/evaluation-workflow.md +110 -0
  13. package/docs/extension-protocol-v1.md +26 -3
  14. package/docs/getting-started.md +23 -2
  15. package/docs/guide-evaluation.md +33 -1
  16. package/docs/identity-v1.md +25 -2
  17. package/docs/installing.md +11 -11
  18. package/docs/native-controls.md +76 -5
  19. package/docs/native-hosts.md +196 -7
  20. package/docs/reading-results.md +102 -0
  21. package/docs/recipes.md +489 -0
  22. package/docs/releases.md +31 -12
  23. package/docs/report-v1.md +29 -1
  24. package/docs/results-v1.md +141 -4
  25. package/docs/run-settings-v1.md +147 -0
  26. package/docs/runtime-v1.md +307 -18
  27. package/docs/troubleshooting.md +12 -12
  28. package/docs/typescript-quality.md +89 -26
  29. package/examples/recipes/candidate.ts +26 -0
  30. package/examples/recipes/cases/coding-change.json +21 -0
  31. package/examples/recipes/cases/host-tools.json +21 -0
  32. package/examples/recipes/cases/mise.json +21 -0
  33. package/examples/recipes/cases/node.json +21 -0
  34. package/examples/recipes/cases/python-uv.json +21 -0
  35. package/examples/recipes/cases/semantic.json +17 -0
  36. package/examples/recipes/coding-change/check.ts +23 -0
  37. package/examples/recipes/extension-artifacts.ts +65 -0
  38. package/examples/recipes/extension-methods.ts +174 -0
  39. package/examples/recipes/extension.ts +37 -0
  40. package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
  41. package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
  42. package/examples/recipes/repository-skill/README.md +3 -0
  43. package/examples/recipes/run-inputs.ts +188 -0
  44. package/examples/recipes/run-options.ts +131 -0
  45. package/examples/recipes/run.ts +63 -0
  46. package/examples/recipes/runtime/empty.json +1 -0
  47. package/examples/recipes/runtime/goals.json +1 -0
  48. package/examples/recipes/runtime/mise.json +6 -0
  49. package/examples/recipes/runtime/path.json +1 -0
  50. package/examples/recipes/runtime/plugins.json +4 -0
  51. package/examples/recipes/runtime/python-uv.json +10 -0
  52. package/examples/recipes/semantic-judge.ts +29 -0
  53. package/examples/templates/coding/adapter.ts +17 -0
  54. package/examples/templates/coding/case.json +15 -0
  55. package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
  56. package/examples/templates/repository-skill/extension-data.ts +64 -0
  57. package/examples/templates/repository-skill/extension.ts +31 -0
  58. package/examples/templates/response/adapter.ts +12 -0
  59. package/examples/templates/response/case.json +13 -0
  60. package/package.json +11 -5
  61. package/schemas/report-v1.schema.json +9 -0
  62. package/schemas/run-evidence-v1.schema.json +2 -1
  63. package/schemas/runtime-v1.schema.json +63 -7
  64. package/src/advisory.ts +2 -2
  65. package/src/candidate-artifacts.ts +206 -0
  66. package/src/candidate-codex-state.ts +185 -0
  67. package/src/case-validation.ts +73 -50
  68. package/src/cli/cancellation.ts +20 -0
  69. package/src/cli/command.ts +87 -0
  70. package/src/cli/completion.ts +15 -0
  71. package/src/cli/demo.ts +60 -0
  72. package/src/cli/evaluation.ts +55 -0
  73. package/src/cli/extension.ts +79 -0
  74. package/src/cli/help.ts +198 -0
  75. package/src/cli/hosts.ts +55 -0
  76. package/src/cli/init-settings.ts +30 -0
  77. package/src/cli/init.ts +111 -0
  78. package/src/{cli-invocation.ts → cli/invocation.ts} +214 -218
  79. package/src/cli/loading.ts +151 -0
  80. package/src/cli/preparation.ts +154 -0
  81. package/src/cli/presentation.ts +93 -0
  82. package/src/cli/progress.ts +6 -0
  83. package/src/cli/run-manifest.ts +128 -0
  84. package/src/cli/run-options.ts +403 -0
  85. package/src/cli/run-overrides.ts +69 -0
  86. package/src/cli/settings.ts +154 -0
  87. package/src/cli/summary.ts +43 -0
  88. package/src/cli.ts +13 -591
  89. package/src/contract-types.ts +94 -0
  90. package/src/engine.ts +13 -5
  91. package/src/evaluation/advisory.ts +150 -0
  92. package/src/{evaluation-check-plan.ts → evaluation/check-plan.ts} +103 -67
  93. package/src/evaluation/collection.ts +88 -0
  94. package/src/evaluation/context.ts +10 -0
  95. package/src/{evaluation-grading-host.ts → evaluation/grading-host.ts} +5 -5
  96. package/src/evaluation/grading.ts +235 -0
  97. package/src/{evaluation-invocation-plan.ts → evaluation/invocation-plan.ts} +147 -58
  98. package/src/{evaluation-plan.ts → evaluation/plan.ts} +208 -141
  99. package/src/evaluation/progress.ts +19 -0
  100. package/src/evaluation/resources.ts +241 -0
  101. package/src/evaluation/run.ts +206 -0
  102. package/src/{evaluation-semantic.ts → evaluation/semantic.ts} +133 -115
  103. package/src/evaluation/trial.ts +800 -0
  104. package/src/evaluation/types.ts +207 -0
  105. package/src/evidence/host-results.ts +85 -0
  106. package/src/evidence/retained-artifacts.ts +18 -0
  107. package/src/evidence/trial-writer.ts +203 -0
  108. package/src/evidence/usage.ts +17 -0
  109. package/src/extension-client.ts +34 -96
  110. package/src/extension-session.ts +24 -48
  111. package/src/{advisory-fixture.ts → fixtures/advisory.ts} +15 -76
  112. package/src/{fixture-bin.ts → fixtures/bin.ts} +1 -1
  113. package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
  114. package/src/fixtures/create.ts +183 -0
  115. package/src/{generated-fixture.ts → fixtures/generated.ts} +10 -53
  116. package/src/{git-hooks.ts → fixtures/git-hooks.ts} +1 -1
  117. package/src/{preparation.ts → fixtures/preparation.ts} +6 -16
  118. package/src/{repository-fixture.ts → fixtures/repository.ts} +30 -64
  119. package/src/{fixture-setup.ts → fixtures/setup.ts} +33 -51
  120. package/src/{fixture-tools.ts → fixtures/tools.ts} +1 -1
  121. package/src/fixtures/workspace-state.ts +212 -0
  122. package/src/generated/cli-result.d.cts +2 -1
  123. package/src/generated/extension.d.cts +2 -1
  124. package/src/generated/report.cjs +1 -1
  125. package/src/generated/report.d.cts +2 -1
  126. package/src/generated/run-evidence.cjs +1 -1
  127. package/src/generated/run-evidence.d.cts +2 -1
  128. package/src/generated/runtime.cjs +1 -1
  129. package/src/generated/runtime.d.cts +2 -1
  130. package/src/git.ts +93 -0
  131. package/src/graders/git-head.ts +29 -84
  132. package/src/graders/shell.ts +57 -68
  133. package/src/host-diagnostic-redaction.ts +52 -0
  134. package/src/host-diagnostics.ts +331 -0
  135. package/src/hosts/claude-continuation.ts +57 -40
  136. package/src/hosts/claude-credential.ts +160 -63
  137. package/src/hosts/claude-events.ts +34 -18
  138. package/src/hosts/claude-keychain.ts +78 -21
  139. package/src/hosts/claude-login-recovery.ts +85 -0
  140. package/src/hosts/claude-login-refresh.ts +178 -0
  141. package/src/hosts/claude-login-store.ts +162 -0
  142. package/src/hosts/claude-login.ts +107 -0
  143. package/src/hosts/claude-native-goal.ts +15 -12
  144. package/src/hosts/claude-process.ts +10 -50
  145. package/src/hosts/claude-refresh-lock.ts +81 -0
  146. package/src/hosts/claude-repository-invocation.ts +9 -34
  147. package/src/hosts/claude-settings.ts +91 -14
  148. package/src/hosts/claude-skill-activations.ts +169 -0
  149. package/src/hosts/claude-state.ts +46 -16
  150. package/src/hosts/claude-tool-calls.ts +13 -10
  151. package/src/hosts/claude.ts +122 -73
  152. package/src/hosts/codex-app-server.ts +78 -52
  153. package/src/hosts/codex-configuration.ts +5 -10
  154. package/src/hosts/codex-exec-controls.ts +516 -0
  155. package/src/hosts/codex-marketplace.ts +12 -10
  156. package/src/hosts/codex-native-calls.ts +15 -122
  157. package/src/hosts/codex-process.ts +10 -54
  158. package/src/hosts/codex-profile.ts +50 -21
  159. package/src/hosts/codex-session-files.ts +1 -9
  160. package/src/hosts/codex-state.ts +141 -19
  161. package/src/hosts/codex.ts +221 -179
  162. package/src/hosts/isolation-common.ts +15 -11
  163. package/src/hosts/isolation-roots.ts +13 -20
  164. package/src/hosts/linux-sandbox.ts +27 -3
  165. package/src/hosts/mac-sandbox.ts +111 -18
  166. package/src/hosts/native-controls.ts +72 -22
  167. package/src/hosts/native-request.ts +55 -0
  168. package/src/hosts/process-lifecycle.ts +221 -45
  169. package/src/hosts/protected-worktrees.ts +1 -1
  170. package/src/hosts/skill-hook-catalog.ts +131 -0
  171. package/src/hosts/skill-hook-handler.ts +374 -0
  172. package/src/hosts/skill-hooks.ts +577 -0
  173. package/src/instrumentation.ts +7 -8
  174. package/src/native-transcript-state.ts +22 -3
  175. package/src/native-transcript-view.ts +52 -13
  176. package/src/native-transcripts.ts +34 -15
  177. package/src/paths.ts +22 -0
  178. package/src/process-lifecycle.ts +150 -0
  179. package/src/provenance.ts +27 -7
  180. package/src/prune.ts +300 -0
  181. package/src/report.ts +63 -36
  182. package/src/resolved-case.ts +1 -1
  183. package/src/results-lock.ts +42 -0
  184. package/src/results.ts +14 -18
  185. package/src/run-owner.ts +4 -4
  186. package/src/{runtime-config.ts → runtime/config.ts} +182 -44
  187. package/src/{runtime-discovery-apple.ts → runtime/discovery-apple.ts} +3 -3
  188. package/src/{runtime-discovery-providers.ts → runtime/discovery-providers.ts} +3 -9
  189. package/src/{runtime-discovery.ts → runtime/discovery.ts} +4 -4
  190. package/src/{runtime-hook-processes.ts → runtime/hook-processes.ts} +1 -1
  191. package/src/{runtime-hooks.ts → runtime/hooks.ts} +33 -22
  192. package/src/{runtime-paths.ts → runtime/paths.ts} +6 -18
  193. package/src/{runtime-query.ts → runtime/query.ts} +5 -5
  194. package/src/{runtime-seeds.ts → runtime/seeds.ts} +3 -3
  195. package/src/runtime/shared-cache.ts +130 -0
  196. package/src/{runtime-state.ts → runtime/state.ts} +28 -11
  197. package/src/schema-types.ts +73 -161
  198. package/src/shell.ts +4 -0
  199. package/src/transcript-retention.ts +146 -0
  200. package/src/value-guards.ts +22 -0
  201. package/docs/assets/sevro-logo.png +0 -0
  202. package/docs/assets/sevro-symbol.svg +0 -1
  203. package/docs/assets/sevro-wordmark.svg +0 -1
  204. package/docs/cache-ownership-validation.md +0 -49
  205. package/docs/claude-continuation-validation.md +0 -47
  206. package/docs/concurrency-validation.md +0 -86
  207. package/docs/configuration-root-validation.md +0 -70
  208. package/docs/engine-slice.md +0 -114
  209. package/docs/evidence/runtime-startup-30/README.md +0 -185
  210. package/docs/evidence/runtime-startup-30/after.json +0 -319
  211. package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +0 -2
  212. package/docs/evidence/runtime-startup-30/before.json +0 -403
  213. package/docs/evidence/runtime-startup-30/consumer-smoke.json +0 -63
  214. package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +0 -3
  215. package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +0 -3
  216. package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +0 -20
  217. package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +0 -2
  218. package/docs/evidence.md +0 -17
  219. package/docs/fixture-cleanup-validation.md +0 -70
  220. package/docs/guide-verification.json +0 -239
  221. package/src/evaluation-context.ts +0 -257
  222. package/src/evaluation-fixture.ts +0 -313
  223. package/src/evaluation-run.ts +0 -198
  224. package/src/evaluation-trial.ts +0 -946
  225. package/src/evaluation-types.ts +0 -149
  226. package/src/hosts/claude-nested-skills.ts +0 -272
  227. package/src/hosts/codex-command-output.ts +0 -510
  228. package/src/hosts/codex-executor-command.ts +0 -291
  229. package/src/hosts/codex-skill-reads.ts +0 -609
  230. /package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +0 -0
@@ -0,0 +1,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
+ }