@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.
- package/CONTRIBUTING.md +170 -0
- package/LICENSE +63 -136
- package/README.md +83 -66
- package/docs/README.md +44 -0
- package/docs/acknowledgements.md +14 -0
- package/docs/architecture.md +146 -0
- package/docs/assets/README.md +27 -0
- package/docs/assets/sevro-logo.png +0 -0
- package/docs/assets/sevro-symbol.svg +1 -0
- package/docs/assets/sevro-wordmark.svg +1 -0
- package/docs/builtin-graders-v1.md +41 -13
- package/docs/codex-host.md +184 -52
- package/docs/contracts.md +19 -0
- package/docs/creating-cases.md +139 -0
- package/docs/development-cli.md +190 -30
- package/docs/documentation-quality.md +61 -0
- package/docs/evaluation-workflow.md +110 -0
- package/docs/evidence/code-mode-controls-71.md +64 -0
- package/docs/evidence/code-mode-programs-85.md +64 -0
- package/docs/evidence/codex-state-73/README.md +204 -0
- package/docs/evidence/codex-state-73/darrow-migration.patch +182 -0
- package/docs/evidence/codex-state-73/results.json +661 -0
- package/docs/evidence/local-http-74.md +104 -0
- package/docs/evidence/runtime-startup-30/README.md +185 -0
- package/docs/evidence/runtime-startup-30/after.json +319 -0
- package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +2 -0
- package/docs/evidence/runtime-startup-30/before.json +403 -0
- package/docs/evidence/runtime-startup-30/consumer-smoke.json +63 -0
- package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +3 -0
- package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +3 -0
- package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +20 -0
- package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +2 -0
- package/docs/evidence/skill-reads-70/results.json +823 -0
- package/docs/evidence.md +21 -0
- package/docs/extension-protocol-v1.md +50 -16
- package/docs/fixture-cleanup-validation.md +3 -0
- package/docs/getting-started.md +102 -0
- package/docs/guide-evaluation.md +89 -0
- package/docs/guide-verification.json +239 -0
- package/docs/identity-v1.md +35 -1
- package/docs/installing.md +88 -0
- package/docs/licenses/BUSL-1.1-0.1.0-rc.2.txt +159 -0
- package/docs/licensing.md +42 -0
- package/docs/native-controls.md +76 -5
- package/docs/native-hosts.md +228 -0
- package/docs/reading-results.md +141 -0
- package/docs/recipes.md +489 -0
- package/docs/releases.md +81 -40
- package/docs/report-v1.md +29 -1
- package/docs/results-v1.md +180 -0
- package/docs/run-settings-v1.md +147 -0
- package/docs/running-evaluations.md +31 -0
- package/docs/runtime-v1.md +614 -0
- package/docs/specs/repository-guide.md +58 -0
- package/docs/troubleshooting.md +30 -0
- package/docs/typescript-quality.md +234 -0
- package/docs/usability-study.md +114 -0
- package/examples/basic/host.ts +2 -2
- package/examples/recipes/candidate.ts +26 -0
- package/examples/recipes/cases/coding-change.json +21 -0
- package/examples/recipes/cases/host-tools.json +21 -0
- package/examples/recipes/cases/mise.json +21 -0
- package/examples/recipes/cases/node.json +21 -0
- package/examples/recipes/cases/python-uv.json +21 -0
- package/examples/recipes/cases/semantic.json +17 -0
- package/examples/recipes/coding-change/check.ts +23 -0
- package/examples/recipes/extension-artifacts.ts +65 -0
- package/examples/recipes/extension-methods.ts +174 -0
- package/examples/recipes/extension.ts +37 -0
- package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
- package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
- package/examples/recipes/repository-skill/README.md +3 -0
- package/examples/recipes/run-inputs.ts +188 -0
- package/examples/recipes/run-options.ts +131 -0
- package/examples/recipes/run.ts +63 -0
- package/examples/recipes/runtime/empty.json +1 -0
- package/examples/recipes/runtime/goals.json +1 -0
- package/examples/recipes/runtime/mise.json +6 -0
- package/examples/recipes/runtime/path.json +1 -0
- package/examples/recipes/runtime/plugins.json +4 -0
- package/examples/recipes/runtime/python-uv.json +10 -0
- package/examples/recipes/semantic-judge.ts +29 -0
- package/examples/templates/coding/adapter.ts +17 -0
- package/examples/templates/coding/case.json +15 -0
- package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
- package/examples/templates/repository-skill/extension-data.ts +64 -0
- package/examples/templates/repository-skill/extension.ts +31 -0
- package/examples/templates/response/adapter.ts +12 -0
- package/examples/templates/response/case.json +13 -0
- package/package.json +39 -6
- package/schemas/report-v1.schema.json +9 -0
- package/schemas/run-evidence-v1.schema.json +2 -1
- package/schemas/runtime-v1.schema.json +193 -0
- package/src/advisory.ts +10 -5
- package/src/candidate-artifacts.ts +208 -0
- package/src/candidate-codex-state.ts +185 -0
- package/src/case-validation.ts +105 -0
- package/src/cli/cancellation.ts +20 -0
- package/src/cli/command.ts +81 -0
- package/src/cli/completion.ts +15 -0
- package/src/cli/demo.ts +59 -0
- package/src/cli/evaluation.ts +55 -0
- package/src/cli/extension.ts +79 -0
- package/src/cli/help.ts +222 -0
- package/src/cli/hosts.ts +59 -0
- package/src/cli/init-settings.ts +30 -0
- package/src/cli/init.ts +111 -0
- package/src/cli/invocation.ts +627 -0
- package/src/cli/loading.ts +151 -0
- package/src/cli/preparation.ts +163 -0
- package/src/cli/presentation.ts +91 -0
- package/src/cli/progress.ts +6 -0
- package/src/cli/run-manifest.ts +133 -0
- package/src/cli/run-options.ts +101 -0
- package/src/cli/run-overrides.ts +83 -0
- package/src/cli/settings.ts +160 -0
- package/src/cli/summary.ts +43 -0
- package/src/cli.ts +13 -843
- package/src/contract-types.ts +94 -0
- package/src/engine.ts +19 -2373
- package/src/evaluation/advisory.ts +150 -0
- package/src/evaluation/check-plan.ts +510 -0
- package/src/evaluation/collection.ts +88 -0
- package/src/evaluation/context.ts +10 -0
- package/src/evaluation/grading-host.ts +39 -0
- package/src/evaluation/grading.ts +258 -0
- package/src/evaluation/invocation-plan.ts +548 -0
- package/src/evaluation/plan.ts +736 -0
- package/src/evaluation/progress.ts +19 -0
- package/src/evaluation/resources.ts +241 -0
- package/src/evaluation/run.ts +206 -0
- package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +3 -1
- package/src/evaluation/semantic.ts +312 -0
- package/src/evaluation/trial.ts +866 -0
- package/src/evaluation/types.ts +207 -0
- package/src/evidence/host-results.ts +92 -0
- package/src/evidence/retained-artifacts.ts +18 -0
- package/src/evidence/trial-writer.ts +203 -0
- package/src/evidence/usage.ts +17 -0
- package/src/extension-client.ts +228 -148
- package/src/extension-session.ts +326 -195
- package/src/fixtures/advisory.ts +338 -0
- package/src/fixtures/bin.ts +57 -0
- package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
- package/src/fixtures/create.ts +187 -0
- package/src/fixtures/generated.ts +300 -0
- package/src/fixtures/git-hooks.ts +24 -0
- package/src/fixtures/preparation.ts +270 -0
- package/src/{repository-fixture.ts → fixtures/repository.ts} +207 -85
- package/src/fixtures/setup.ts +224 -0
- package/src/fixtures/tools.ts +88 -0
- package/src/fixtures/workspace-state.ts +212 -0
- package/src/generated/cli-result.cjs +2 -0
- package/src/generated/cli-result.d.cts +3 -0
- package/src/generated/extension.cjs +2 -0
- package/src/generated/extension.d.cts +3 -0
- package/src/generated/report.cjs +2 -0
- package/src/generated/report.d.cts +3 -0
- package/src/generated/run-evidence.cjs +2 -0
- package/src/generated/run-evidence.d.cts +3 -0
- package/src/generated/runtime.cjs +2 -0
- package/src/generated/runtime.d.cts +3 -0
- package/src/graders/artifact.ts +56 -23
- package/src/graders/git-head.ts +79 -75
- package/src/graders/output.ts +222 -140
- package/src/graders/semantic.ts +102 -43
- package/src/graders/shell.ts +379 -207
- package/src/host-diagnostic-redaction.ts +52 -0
- package/src/host-diagnostics.ts +319 -0
- package/src/hosts/claude-continuation.ts +63 -18
- package/src/hosts/claude-credential.ts +43 -62
- package/src/hosts/claude-events.ts +86 -51
- package/src/hosts/claude-keychain.ts +59 -0
- package/src/hosts/claude-native-goal.ts +57 -33
- package/src/hosts/claude-process.ts +111 -0
- package/src/hosts/claude-repository-invocation.ts +208 -117
- package/src/hosts/claude-settings.ts +240 -21
- package/src/hosts/claude-skill-activations.ts +166 -0
- package/src/hosts/claude-state.ts +403 -0
- package/src/hosts/claude-tool-calls.ts +171 -106
- package/src/hosts/claude.ts +561 -451
- package/src/hosts/codex-app-server.ts +326 -178
- package/src/hosts/codex-configuration.ts +44 -18
- package/src/hosts/codex-events.ts +97 -60
- package/src/hosts/codex-exec-controls.ts +516 -0
- package/src/hosts/codex-invocation.ts +70 -0
- package/src/hosts/codex-marketplace.ts +283 -0
- package/src/hosts/codex-native-calls.ts +546 -500
- package/src/hosts/codex-process.ts +202 -0
- package/src/hosts/codex-profile.ts +257 -28
- package/src/hosts/codex-session-files.ts +106 -0
- package/src/hosts/codex-state.ts +492 -0
- package/src/hosts/codex.ts +783 -832
- package/src/hosts/isolation-common.ts +94 -0
- package/src/hosts/isolation-roots.ts +54 -27
- package/src/hosts/isolation.ts +10 -0
- package/src/hosts/linux-sandbox.ts +159 -0
- package/src/hosts/mac-sandbox.ts +187 -61
- package/src/hosts/native-controls.ts +152 -62
- package/src/hosts/process-lifecycle.ts +37 -0
- package/src/hosts/protected-worktrees.ts +51 -19
- package/src/hosts/skill-hook-catalog.ts +131 -0
- package/src/hosts/skill-hook-handler.ts +374 -0
- package/src/hosts/skill-hooks.ts +580 -0
- package/src/hosts/workspace-fingerprint.ts +55 -35
- package/src/identity.ts +102 -48
- package/src/instrumentation.ts +109 -48
- package/src/native-transcript-state.ts +131 -0
- package/src/native-transcript-view.ts +90 -0
- package/src/native-transcripts.ts +286 -0
- package/src/process-lifecycle.ts +150 -0
- package/src/provenance.ts +161 -81
- package/src/prune.ts +215 -0
- package/src/report.ts +172 -140
- package/src/resolved-case.ts +33 -0
- package/src/results-lock.ts +29 -0
- package/src/results.ts +133 -86
- package/src/run-owner.ts +154 -63
- package/src/runtime/config.ts +456 -0
- package/src/runtime/discovery-apple.ts +90 -0
- package/src/runtime/discovery-providers.ts +63 -0
- package/src/runtime/discovery.ts +70 -0
- package/src/runtime/hook-processes.ts +97 -0
- package/src/runtime/hooks.ts +476 -0
- package/src/runtime/paths.ts +313 -0
- package/src/runtime/query.ts +85 -0
- package/src/runtime/seeds.ts +134 -0
- package/src/runtime/shared-cache.ts +143 -0
- package/src/runtime/state.ts +143 -0
- package/src/schema-types.ts +198 -0
- package/src/schema.ts +15 -19
- package/src/transcript-retention.ts +146 -0
- package/src/value-guards.ts +20 -0
- package/src/advisory-fixture.ts +0 -282
- package/src/fixture-bin.ts +0 -87
- package/src/fixture-setup.ts +0 -163
- package/src/generated-fixture.ts +0 -220
- package/src/git-hooks.ts +0 -63
- package/src/hosts/claude-nested-skills.ts +0 -218
- package/src/hosts/codex-command-output.ts +0 -399
- package/src/hosts/codex-executor-command.ts +0 -195
- package/src/hosts/codex-skill-reads.ts +0 -391
- package/src/preparation.ts +0 -211
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {
|
|
2
|
+
advisoryPrompt,
|
|
3
|
+
parseAdvisoryAssessment,
|
|
4
|
+
type AdvisoryAssessment,
|
|
5
|
+
} from "../advisory";
|
|
6
|
+
import type { CandidateArtifactRoots } from "../candidate-artifacts";
|
|
7
|
+
import type { TrialEvidenceWriter } from "../evidence/trial-writer";
|
|
8
|
+
import { usage } from "../evidence/usage";
|
|
9
|
+
import { buildBlindAdvisoryFixture } from "../fixtures/advisory";
|
|
10
|
+
import { removeWorkspace } from "../fixtures/workspace-state";
|
|
11
|
+
import { HostDiagnosticPersistenceError } from "../host-diagnostics";
|
|
12
|
+
import type { CheckOutcome } from "../results";
|
|
13
|
+
import type { AdvisoryReviewData } from "../schema-types";
|
|
14
|
+
import type { RuntimePolicy } from "../runtime/config";
|
|
15
|
+
import { gradingRuntimePolicy, scopedHostArtifacts } from "./grading-host";
|
|
16
|
+
import {
|
|
17
|
+
TrialPersistenceError,
|
|
18
|
+
type HostAdapter,
|
|
19
|
+
type HostResult,
|
|
20
|
+
} from "./types";
|
|
21
|
+
|
|
22
|
+
export type AdvisoryReview = AdvisoryReviewData;
|
|
23
|
+
|
|
24
|
+
interface AdvisoryInput {
|
|
25
|
+
readonly host: HostAdapter;
|
|
26
|
+
readonly candidateCompleted: boolean;
|
|
27
|
+
readonly candidateWorkspace: string;
|
|
28
|
+
readonly candidateTranscriptRoot?: string;
|
|
29
|
+
readonly candidateArtifactRoots?: CandidateArtifactRoots;
|
|
30
|
+
readonly baseRevision: string | null;
|
|
31
|
+
readonly excludedPaths?: string[];
|
|
32
|
+
readonly task: string;
|
|
33
|
+
readonly checks: readonly CheckOutcome[];
|
|
34
|
+
readonly declaredChecks: { id: string }[];
|
|
35
|
+
readonly runtimePolicy?: RuntimePolicy;
|
|
36
|
+
readonly signal?: AbortSignal;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface AdvisoryEffects {
|
|
40
|
+
readonly evidence: Pick<
|
|
41
|
+
TrialEvidenceWriter,
|
|
42
|
+
"artifactRefs" | "retainArtifacts" | "retainAdvisoryResponse"
|
|
43
|
+
>;
|
|
44
|
+
retainWorkspace(workspace: string): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function defaultAdvisoryReview(host: HostAdapter): AdvisoryReview {
|
|
48
|
+
return {
|
|
49
|
+
status: "not_run",
|
|
50
|
+
assessment: null,
|
|
51
|
+
usage: usage(null),
|
|
52
|
+
rawResult: { source: host.id, path: null, sha256: null },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function completeAdvisoryAssessment(response: HostResult): AdvisoryAssessment {
|
|
57
|
+
if (
|
|
58
|
+
response.executionFailed ||
|
|
59
|
+
!response.complete ||
|
|
60
|
+
response.finalMessage === null
|
|
61
|
+
)
|
|
62
|
+
throw new Error("advisory response is incomplete");
|
|
63
|
+
return parseAdvisoryAssessment(response.finalMessage);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function cleanupAdvisoryWorkspace(
|
|
67
|
+
workspace: string | null,
|
|
68
|
+
successful: boolean,
|
|
69
|
+
effects: AdvisoryEffects,
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
if (!workspace) return;
|
|
72
|
+
if (!successful) return effects.retainWorkspace(workspace);
|
|
73
|
+
try {
|
|
74
|
+
await removeWorkspace(workspace);
|
|
75
|
+
} catch {
|
|
76
|
+
console.warn(`advisory fixture cleanup failed; retained at ${workspace}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function executeAdvisoryReview(
|
|
81
|
+
input: AdvisoryInput,
|
|
82
|
+
effects: AdvisoryEffects,
|
|
83
|
+
review: AdvisoryReview,
|
|
84
|
+
): Promise<void> {
|
|
85
|
+
let workspace: string | null = null;
|
|
86
|
+
try {
|
|
87
|
+
if (input.baseRevision === null)
|
|
88
|
+
throw new Error("advisory base revision is unavailable");
|
|
89
|
+
workspace = await buildBlindAdvisoryFixture(input.candidateWorkspace, {
|
|
90
|
+
baseRevision: input.baseRevision,
|
|
91
|
+
excludedPaths: input.excludedPaths,
|
|
92
|
+
});
|
|
93
|
+
const response = await input.host.run({
|
|
94
|
+
retainHostDiagnostics: (bytes) =>
|
|
95
|
+
effects.evidence.retainArtifacts([
|
|
96
|
+
{
|
|
97
|
+
id: "sevro.advisory.sevro.host.diagnostics",
|
|
98
|
+
bytes: Buffer.from(bytes),
|
|
99
|
+
},
|
|
100
|
+
]),
|
|
101
|
+
candidateTranscriptRoot: input.candidateTranscriptRoot,
|
|
102
|
+
candidateArtifactRoots: input.candidateArtifactRoots,
|
|
103
|
+
prompt: advisoryPrompt(input.task, [...input.checks]),
|
|
104
|
+
runtimePolicy: gradingRuntimePolicy(input.runtimePolicy),
|
|
105
|
+
runtimeRole: "advisory",
|
|
106
|
+
workspace,
|
|
107
|
+
condition: "passive",
|
|
108
|
+
signal: input.signal,
|
|
109
|
+
});
|
|
110
|
+
review.usage = usage(response);
|
|
111
|
+
const artifacts = scopedHostArtifacts(
|
|
112
|
+
response,
|
|
113
|
+
"sevro.advisory.",
|
|
114
|
+
effects.evidence.artifactRefs,
|
|
115
|
+
input.declaredChecks,
|
|
116
|
+
"sevro.advisory.response",
|
|
117
|
+
);
|
|
118
|
+
await effects.evidence.retainArtifacts(artifacts);
|
|
119
|
+
review.rawResult = await effects.evidence.retainAdvisoryResponse(
|
|
120
|
+
input.host.id,
|
|
121
|
+
response.finalMessage,
|
|
122
|
+
);
|
|
123
|
+
review.assessment = completeAdvisoryAssessment(response);
|
|
124
|
+
review.status = "completed";
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (
|
|
127
|
+
error instanceof HostDiagnosticPersistenceError ||
|
|
128
|
+
error instanceof TrialPersistenceError
|
|
129
|
+
)
|
|
130
|
+
throw error;
|
|
131
|
+
review.status = "failed";
|
|
132
|
+
} finally {
|
|
133
|
+
await cleanupAdvisoryWorkspace(
|
|
134
|
+
workspace,
|
|
135
|
+
review.status === "completed",
|
|
136
|
+
effects,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Advisory execution depends on candidate completion, independently of task grading. */
|
|
142
|
+
export async function runAdvisoryReview(
|
|
143
|
+
input: AdvisoryInput,
|
|
144
|
+
effects: AdvisoryEffects,
|
|
145
|
+
): Promise<AdvisoryReview> {
|
|
146
|
+
const review = defaultAdvisoryReview(input.host);
|
|
147
|
+
if (!input.candidateCompleted || input.signal?.aborted) return review;
|
|
148
|
+
await executeAdvisoryReview(input, effects, review);
|
|
149
|
+
return review;
|
|
150
|
+
}
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
import { validInlineFixture } from "../case-validation";
|
|
3
|
+
import {
|
|
4
|
+
EvaluationConfigurationError,
|
|
5
|
+
type EvaluationOptions,
|
|
6
|
+
type HostAdapter,
|
|
7
|
+
type ResolvedCase,
|
|
8
|
+
} from "./types";
|
|
9
|
+
import {
|
|
10
|
+
prepareGeneratedFixture,
|
|
11
|
+
type GeneratedFixture,
|
|
12
|
+
} from "../fixtures/generated";
|
|
13
|
+
import {
|
|
14
|
+
prepareGitHeadChecks,
|
|
15
|
+
type GitHeadCheckDeclaration,
|
|
16
|
+
} from "../graders/git-head";
|
|
17
|
+
import {
|
|
18
|
+
isOutputGrader,
|
|
19
|
+
prepareOutputChecks,
|
|
20
|
+
type OutputCheckDeclaration,
|
|
21
|
+
} from "../graders/output";
|
|
22
|
+
import {
|
|
23
|
+
prepareSemanticChecks,
|
|
24
|
+
type SemanticCheckDeclaration,
|
|
25
|
+
} from "../graders/semantic";
|
|
26
|
+
import {
|
|
27
|
+
prepareShellChecks,
|
|
28
|
+
type ShellCheckDeclaration,
|
|
29
|
+
} from "../graders/shell";
|
|
30
|
+
import { hashJson } from "../identity";
|
|
31
|
+
import { fixtureParts } from "../fixtures/preparation";
|
|
32
|
+
import {
|
|
33
|
+
prepareRepositoryFixture,
|
|
34
|
+
type RepositoryFixture,
|
|
35
|
+
} from "../fixtures/repository";
|
|
36
|
+
import { resolvedFixture } from "../resolved-case";
|
|
37
|
+
import { isOptionalNonblankString, isRecord } from "../value-guards";
|
|
38
|
+
|
|
39
|
+
function validateRootOptions(options: EvaluationOptions): void {
|
|
40
|
+
if (
|
|
41
|
+
!isAbsolute(options.projectRoot) ||
|
|
42
|
+
!isAbsolute(options.resultsRoot) ||
|
|
43
|
+
(options.runStateRoot !== undefined && !isAbsolute(options.runStateRoot))
|
|
44
|
+
)
|
|
45
|
+
throw new EvaluationConfigurationError(
|
|
46
|
+
"project, results, and run-state roots must be absolute",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function hostIdentityComplete(host: HostAdapter): boolean {
|
|
51
|
+
return Boolean(host.id && host.model && host.effort);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function validateEvaluationIdentity(options: EvaluationOptions): void {
|
|
55
|
+
if (
|
|
56
|
+
!options.case.id ||
|
|
57
|
+
!options.case.prompt ||
|
|
58
|
+
!hostIdentityComplete(options.host)
|
|
59
|
+
)
|
|
60
|
+
throw new EvaluationConfigurationError(
|
|
61
|
+
"case and host identities must be nonempty",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function validateFollowUpPrompt(value: unknown): void {
|
|
66
|
+
if (!isOptionalNonblankString(value))
|
|
67
|
+
throw new EvaluationConfigurationError("follow-up prompt must be nonempty");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function validateContinuationHost(options: EvaluationOptions): void {
|
|
71
|
+
if (
|
|
72
|
+
options.case.followUpPrompt !== undefined &&
|
|
73
|
+
!options.host.hostCapabilities?.includes("sevro.host.continuation")
|
|
74
|
+
)
|
|
75
|
+
throw new EvaluationConfigurationError(
|
|
76
|
+
"selected host does not support continuation",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function validateContinuationNegotiation(options: EvaluationOptions): void {
|
|
81
|
+
if (
|
|
82
|
+
options.case.followUpPrompt !== undefined &&
|
|
83
|
+
options.extension &&
|
|
84
|
+
!options.extension.session.identity.capabilities.includes(
|
|
85
|
+
"sevro.host.continuation",
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
throw new EvaluationConfigurationError(
|
|
89
|
+
"continuation capability was not negotiated",
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function validateEvaluationInputs(options: EvaluationOptions): void {
|
|
94
|
+
validateRootOptions(options);
|
|
95
|
+
validateEvaluationIdentity(options);
|
|
96
|
+
validateFollowUpPrompt(options.case.followUpPrompt);
|
|
97
|
+
validateContinuationHost(options);
|
|
98
|
+
validateContinuationNegotiation(options);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function evaluationFixtureObject(value: unknown): Record<string, unknown> {
|
|
102
|
+
if (!isRecord(value))
|
|
103
|
+
throw new EvaluationConfigurationError("invalid fixture declaration");
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function generatedEvaluationFixture(
|
|
108
|
+
fixture: Record<string, unknown>,
|
|
109
|
+
): GeneratedFixture {
|
|
110
|
+
try {
|
|
111
|
+
return prepareGeneratedFixture(fixture);
|
|
112
|
+
} catch (cause) {
|
|
113
|
+
throw new EvaluationConfigurationError(
|
|
114
|
+
cause instanceof Error ? cause.message : "invalid generated fixture",
|
|
115
|
+
{ cause },
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function repositoryEvaluationFixture(
|
|
121
|
+
fixture: Record<string, unknown>,
|
|
122
|
+
): RepositoryFixture {
|
|
123
|
+
try {
|
|
124
|
+
return prepareRepositoryFixture({ kind: "repository", ...fixture });
|
|
125
|
+
} catch (cause) {
|
|
126
|
+
throw new EvaluationConfigurationError(
|
|
127
|
+
cause instanceof Error ? cause.message : "invalid repository fixture",
|
|
128
|
+
{ cause },
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function validateInlineEvaluationFixture(
|
|
134
|
+
fixture: Record<string, unknown>,
|
|
135
|
+
): void {
|
|
136
|
+
if (!validInlineFixture(fixture))
|
|
137
|
+
throw new EvaluationConfigurationError(
|
|
138
|
+
"fixture must declare inline files or a repository source",
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function prepareEvaluationFixture(options: EvaluationOptions): {
|
|
143
|
+
generated: GeneratedFixture | null;
|
|
144
|
+
repositoryFixture: RepositoryFixture | null;
|
|
145
|
+
} {
|
|
146
|
+
const fixture = evaluationFixtureObject(options.case.fixture);
|
|
147
|
+
if ("kind" in fixture)
|
|
148
|
+
return {
|
|
149
|
+
generated: generatedEvaluationFixture(fixture),
|
|
150
|
+
repositoryFixture: null,
|
|
151
|
+
};
|
|
152
|
+
if (Object.hasOwn(fixture, "sourceRef"))
|
|
153
|
+
return {
|
|
154
|
+
generated: null,
|
|
155
|
+
repositoryFixture: repositoryEvaluationFixture(fixture),
|
|
156
|
+
};
|
|
157
|
+
validateInlineEvaluationFixture(fixture);
|
|
158
|
+
return { generated: null, repositoryFixture: null };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function validateTrialInteger(value: number, message: string): void {
|
|
162
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
163
|
+
throw new EvaluationConfigurationError(message);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function validatePassThreshold(value: number): void {
|
|
167
|
+
if (!Number.isFinite(value) || value <= 0 || value > 1)
|
|
168
|
+
throw new EvaluationConfigurationError(
|
|
169
|
+
"pass threshold must be greater than zero and at most one",
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function validateRequiredEvidence(ids: string[]): void {
|
|
174
|
+
if (
|
|
175
|
+
new Set(ids).size !== ids.length ||
|
|
176
|
+
ids.some((id) => !/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/.test(id))
|
|
177
|
+
)
|
|
178
|
+
throw new EvaluationConfigurationError("invalid required evidence IDs");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function prepareTrialParameters(
|
|
182
|
+
options: EvaluationOptions,
|
|
183
|
+
state: ReturnType<typeof prepareEvaluationFixture>,
|
|
184
|
+
) {
|
|
185
|
+
validateTrialInteger(
|
|
186
|
+
options.trialCount,
|
|
187
|
+
"trial count must be a positive integer",
|
|
188
|
+
);
|
|
189
|
+
const jobs = options.jobs ?? 3;
|
|
190
|
+
validateTrialInteger(jobs, "jobs must be a positive integer");
|
|
191
|
+
validatePassThreshold(options.passThreshold);
|
|
192
|
+
validateRequiredEvidence(options.case.requiredEvidence);
|
|
193
|
+
return { ...state, jobs };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function declaredCaseChecks(options: EvaluationOptions) {
|
|
197
|
+
const allBuiltinDeclarations = options.case.checks.filter((check) =>
|
|
198
|
+
isOutputGrader(check.grader),
|
|
199
|
+
) as OutputCheckDeclaration[];
|
|
200
|
+
const allShellDeclarations = options.case.checks.filter(
|
|
201
|
+
(check) => check.grader === "sevro.shell",
|
|
202
|
+
) as ShellCheckDeclaration[];
|
|
203
|
+
const allGitHeadDeclarations = options.case.checks.filter(
|
|
204
|
+
(check) => check.grader === "sevro.git-head",
|
|
205
|
+
) as GitHeadCheckDeclaration[];
|
|
206
|
+
const allSemanticDeclarations = options.case.checks.filter(
|
|
207
|
+
(check) => check.grader === "sevro.semantic",
|
|
208
|
+
) as SemanticCheckDeclaration[];
|
|
209
|
+
const extensionDeclarations = options.case.checks.filter(
|
|
210
|
+
(check) =>
|
|
211
|
+
!isOutputGrader(check.grader) &&
|
|
212
|
+
check.grader !== "sevro.shell" &&
|
|
213
|
+
check.grader !== "sevro.git-head" &&
|
|
214
|
+
check.grader !== "sevro.semantic",
|
|
215
|
+
);
|
|
216
|
+
const replacedBuiltinGraders =
|
|
217
|
+
options.extension?.session.identity.replacedBuiltinGraders ?? [];
|
|
218
|
+
const replaced = new Set(replacedBuiltinGraders);
|
|
219
|
+
return {
|
|
220
|
+
allBuiltinDeclarations,
|
|
221
|
+
allShellDeclarations,
|
|
222
|
+
allGitHeadDeclarations,
|
|
223
|
+
allSemanticDeclarations,
|
|
224
|
+
extensionDeclarations,
|
|
225
|
+
replacedBuiltinGraders,
|
|
226
|
+
replaced,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
type CaseCheckDeclarations = ReturnType<typeof declaredCaseChecks>;
|
|
231
|
+
|
|
232
|
+
function validateBuiltInReplacements(context: CaseCheckDeclarations): void {
|
|
233
|
+
const {
|
|
234
|
+
allBuiltinDeclarations,
|
|
235
|
+
allShellDeclarations,
|
|
236
|
+
allGitHeadDeclarations,
|
|
237
|
+
allSemanticDeclarations,
|
|
238
|
+
extensionDeclarations,
|
|
239
|
+
replacedBuiltinGraders,
|
|
240
|
+
replaced,
|
|
241
|
+
} = context;
|
|
242
|
+
const declaredGraders = new Set<string>(
|
|
243
|
+
[
|
|
244
|
+
...allBuiltinDeclarations,
|
|
245
|
+
...allShellDeclarations,
|
|
246
|
+
...allGitHeadDeclarations,
|
|
247
|
+
...allSemanticDeclarations,
|
|
248
|
+
].map((check) => check.grader),
|
|
249
|
+
);
|
|
250
|
+
if (
|
|
251
|
+
replaced.size !== replacedBuiltinGraders.length ||
|
|
252
|
+
replacedBuiltinGraders.some((id) => !declaredGraders.has(id))
|
|
253
|
+
)
|
|
254
|
+
throw new EvaluationConfigurationError(
|
|
255
|
+
"unknown or duplicate built-in grader replacement",
|
|
256
|
+
);
|
|
257
|
+
if (replaced.size && !extensionDeclarations.length)
|
|
258
|
+
throw new EvaluationConfigurationError(
|
|
259
|
+
"built-in grader replacement requires an extension check",
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function activeCheckDeclarations(context: CaseCheckDeclarations) {
|
|
264
|
+
const {
|
|
265
|
+
allBuiltinDeclarations,
|
|
266
|
+
allShellDeclarations,
|
|
267
|
+
allGitHeadDeclarations,
|
|
268
|
+
allSemanticDeclarations,
|
|
269
|
+
replaced,
|
|
270
|
+
} = context;
|
|
271
|
+
const builtinDeclarations = allBuiltinDeclarations.filter(
|
|
272
|
+
(check) => !replaced.has(check.grader),
|
|
273
|
+
);
|
|
274
|
+
const shellDeclarations = allShellDeclarations.filter(
|
|
275
|
+
(check) => !replaced.has(check.grader),
|
|
276
|
+
);
|
|
277
|
+
const gitHeadDeclarations = allGitHeadDeclarations.filter(
|
|
278
|
+
(check) => !replaced.has(check.grader),
|
|
279
|
+
);
|
|
280
|
+
const semanticDeclarations = allSemanticDeclarations.filter(
|
|
281
|
+
(check) => !replaced.has(check.grader),
|
|
282
|
+
);
|
|
283
|
+
return {
|
|
284
|
+
builtinDeclarations,
|
|
285
|
+
shellDeclarations,
|
|
286
|
+
gitHeadDeclarations,
|
|
287
|
+
semanticDeclarations,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
type CaseCheckPlan = ReturnType<typeof prepareTrialParameters> &
|
|
292
|
+
CaseCheckDeclarations &
|
|
293
|
+
ReturnType<typeof activeCheckDeclarations>;
|
|
294
|
+
|
|
295
|
+
function validateSemanticRoute(
|
|
296
|
+
options: EvaluationOptions,
|
|
297
|
+
checks: SemanticCheckDeclaration[],
|
|
298
|
+
): void {
|
|
299
|
+
if (checks.length && !options.semanticHost)
|
|
300
|
+
throw new EvaluationConfigurationError(
|
|
301
|
+
"semantic checks require an explicit semantic host",
|
|
302
|
+
);
|
|
303
|
+
if (options.semanticHost && !hostIdentityComplete(options.semanticHost))
|
|
304
|
+
throw new EvaluationConfigurationError(
|
|
305
|
+
"semantic host identity is incomplete",
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function validateAdvisoryGitFixture(
|
|
310
|
+
options: EvaluationOptions,
|
|
311
|
+
generated: GeneratedFixture | null,
|
|
312
|
+
): void {
|
|
313
|
+
if (!generated && !options.case.fixture.sourceRef)
|
|
314
|
+
throw new EvaluationConfigurationError(
|
|
315
|
+
"advisory review requires a Git fixture",
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function validateAdvisoryExclusions(paths: string[]): void {
|
|
320
|
+
try {
|
|
321
|
+
for (const path of paths) {
|
|
322
|
+
fixtureParts(path);
|
|
323
|
+
if (
|
|
324
|
+
path.toLowerCase() === ".git" ||
|
|
325
|
+
path.toLowerCase().startsWith(".git/")
|
|
326
|
+
)
|
|
327
|
+
throw new Error("repository metadata cannot be an advisory exclusion");
|
|
328
|
+
}
|
|
329
|
+
} catch (cause) {
|
|
330
|
+
throw new EvaluationConfigurationError("invalid advisory exclusion path", {
|
|
331
|
+
cause,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function validateAdvisoryRoute(
|
|
337
|
+
options: EvaluationOptions,
|
|
338
|
+
generated: GeneratedFixture | null,
|
|
339
|
+
): void {
|
|
340
|
+
const host = options.advisoryHost;
|
|
341
|
+
if (!host) {
|
|
342
|
+
if (options.advisoryExcludedPaths?.length)
|
|
343
|
+
throw new EvaluationConfigurationError(
|
|
344
|
+
"advisory exclusions require an advisory host",
|
|
345
|
+
);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
validateAdvisoryGitFixture(options, generated);
|
|
349
|
+
validateAdvisoryIdentity(host);
|
|
350
|
+
validateAdvisoryExclusions(options.advisoryExcludedPaths ?? []);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function validateAdvisoryIdentity(host: HostAdapter): void {
|
|
354
|
+
if (!hostIdentityComplete(host))
|
|
355
|
+
throw new EvaluationConfigurationError(
|
|
356
|
+
"advisory host identity is incomplete",
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function validateShellRoute(
|
|
361
|
+
options: EvaluationOptions,
|
|
362
|
+
checks: ShellCheckDeclaration[],
|
|
363
|
+
): void {
|
|
364
|
+
if (checks.length && !options.shellIsolation)
|
|
365
|
+
throw new EvaluationConfigurationError(
|
|
366
|
+
"shell checks require explicit protected source roots",
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function validateGitHeadRoute(context: CaseCheckPlan): void {
|
|
371
|
+
if (
|
|
372
|
+
context.gitHeadDeclarations.length &&
|
|
373
|
+
!context.generated &&
|
|
374
|
+
!context.repositoryFixture
|
|
375
|
+
)
|
|
376
|
+
throw new EvaluationConfigurationError(
|
|
377
|
+
"Git HEAD checks require a Git fixture",
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function validateCaseCheckIds(checks: ResolvedCase["checks"]): void {
|
|
382
|
+
if (new Set(checks.map((check) => check.id)).size !== checks.length)
|
|
383
|
+
throw new EvaluationConfigurationError("case check IDs must be unique");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function validateExtensionGraders(
|
|
387
|
+
options: EvaluationOptions,
|
|
388
|
+
declarations: ResolvedCase["checks"],
|
|
389
|
+
): void {
|
|
390
|
+
if (!declarations.length) return;
|
|
391
|
+
const extension = options.extension;
|
|
392
|
+
if (!extension)
|
|
393
|
+
throw new EvaluationConfigurationError(
|
|
394
|
+
"case declares an unavailable extension grader",
|
|
395
|
+
);
|
|
396
|
+
if (
|
|
397
|
+
declarations.some(
|
|
398
|
+
(check) => !extension.session.identity.graders.includes(check.grader),
|
|
399
|
+
)
|
|
400
|
+
)
|
|
401
|
+
throw new EvaluationConfigurationError(
|
|
402
|
+
"case declares an unavailable extension grader",
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function comparisonCase(caseData: ResolvedCase) {
|
|
407
|
+
return {
|
|
408
|
+
id: caseData.id,
|
|
409
|
+
prompt: caseData.prompt,
|
|
410
|
+
...(caseData.followUpPrompt !== undefined
|
|
411
|
+
? { followUpPrompt: caseData.followUpPrompt }
|
|
412
|
+
: {}),
|
|
413
|
+
fixture: caseData.fixture,
|
|
414
|
+
checks: caseData.checks,
|
|
415
|
+
requiredEvidence: caseData.requiredEvidence,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function validateResolvedExtensionCase(options: EvaluationOptions): void {
|
|
420
|
+
if (!options.extension) return;
|
|
421
|
+
const resolved = options.extension.resolvedCase;
|
|
422
|
+
const comparable = comparisonCase({
|
|
423
|
+
...resolved,
|
|
424
|
+
fixture: resolvedFixture(resolved.fixture),
|
|
425
|
+
});
|
|
426
|
+
if (hashJson(comparable) !== hashJson(comparisonCase(options.case)))
|
|
427
|
+
throw new EvaluationConfigurationError(
|
|
428
|
+
"resolved extension case does not match the selected case",
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function activePreparedChecks<T>(
|
|
433
|
+
checks: T[],
|
|
434
|
+
replaced: Set<string>,
|
|
435
|
+
grader: string,
|
|
436
|
+
): T[] {
|
|
437
|
+
return replaced.has(grader) ? [] : checks;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function validateInlinePaths(files: ResolvedCase["fixture"]["files"]): void {
|
|
441
|
+
for (const path of Object.keys(files ?? {})) fixtureParts(path);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function prepareBuiltInChecks(
|
|
445
|
+
options: EvaluationOptions,
|
|
446
|
+
context: CaseCheckPlan,
|
|
447
|
+
) {
|
|
448
|
+
const {
|
|
449
|
+
allBuiltinDeclarations,
|
|
450
|
+
allShellDeclarations,
|
|
451
|
+
allGitHeadDeclarations,
|
|
452
|
+
allSemanticDeclarations,
|
|
453
|
+
replaced,
|
|
454
|
+
} = context;
|
|
455
|
+
try {
|
|
456
|
+
const prepared = prepareOutputChecks(allBuiltinDeclarations).filter(
|
|
457
|
+
(check) => !replaced.has(check.grader),
|
|
458
|
+
);
|
|
459
|
+
const preparedShell = activePreparedChecks(
|
|
460
|
+
prepareShellChecks(allShellDeclarations),
|
|
461
|
+
replaced,
|
|
462
|
+
"sevro.shell",
|
|
463
|
+
);
|
|
464
|
+
const preparedGitHead = activePreparedChecks(
|
|
465
|
+
prepareGitHeadChecks(allGitHeadDeclarations),
|
|
466
|
+
replaced,
|
|
467
|
+
"sevro.git-head",
|
|
468
|
+
);
|
|
469
|
+
const preparedSemantic = activePreparedChecks(
|
|
470
|
+
prepareSemanticChecks(allSemanticDeclarations),
|
|
471
|
+
replaced,
|
|
472
|
+
"sevro.semantic",
|
|
473
|
+
);
|
|
474
|
+
validateInlinePaths(options.case.fixture.files);
|
|
475
|
+
return { prepared, preparedShell, preparedGitHead, preparedSemantic };
|
|
476
|
+
} catch (cause) {
|
|
477
|
+
throw new EvaluationConfigurationError(
|
|
478
|
+
cause instanceof Error ? cause.message : "invalid case configuration",
|
|
479
|
+
{ cause },
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function prepareCheckPlan(
|
|
485
|
+
options: EvaluationOptions,
|
|
486
|
+
state: ReturnType<typeof prepareTrialParameters>,
|
|
487
|
+
) {
|
|
488
|
+
const declarations = declaredCaseChecks(options);
|
|
489
|
+
validateBuiltInReplacements(declarations);
|
|
490
|
+
const context = {
|
|
491
|
+
...state,
|
|
492
|
+
...declarations,
|
|
493
|
+
...activeCheckDeclarations(declarations),
|
|
494
|
+
};
|
|
495
|
+
validateSemanticRoute(options, context.semanticDeclarations);
|
|
496
|
+
validateAdvisoryRoute(options, context.generated);
|
|
497
|
+
validateShellRoute(options, context.shellDeclarations);
|
|
498
|
+
validateGitHeadRoute(context);
|
|
499
|
+
validateCaseCheckIds(options.case.checks);
|
|
500
|
+
validateExtensionGraders(options, context.extensionDeclarations);
|
|
501
|
+
validateResolvedExtensionCase(options);
|
|
502
|
+
return { ...context, ...prepareBuiltInChecks(options, context) };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export function prepareEvaluationChecks(options: EvaluationOptions) {
|
|
506
|
+
validateEvaluationInputs(options);
|
|
507
|
+
const fixture = prepareEvaluationFixture(options);
|
|
508
|
+
const trials = prepareTrialParameters(options, fixture);
|
|
509
|
+
return prepareCheckPlan(options, trials);
|
|
510
|
+
}
|