@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,143 @@
|
|
|
1
|
+
import { mkdir, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
requireRuntimeEnvironment,
|
|
5
|
+
runtimeRoleSeeds,
|
|
6
|
+
type RuntimeRole,
|
|
7
|
+
requireRuntimeSeedTargets,
|
|
8
|
+
type RuntimePolicy,
|
|
9
|
+
} from "./config";
|
|
10
|
+
import { materializeRuntimeSeed } from "./seeds";
|
|
11
|
+
import { requireRuntimeReadRoots } from "./paths";
|
|
12
|
+
import { hashJson } from "../identity";
|
|
13
|
+
import {
|
|
14
|
+
linkSharedCaches,
|
|
15
|
+
sharedCacheRoots,
|
|
16
|
+
selectedSharedCaches,
|
|
17
|
+
} from "./shared-cache";
|
|
18
|
+
|
|
19
|
+
export type { RuntimeRole } from "./config";
|
|
20
|
+
|
|
21
|
+
export function runtimeObservations(
|
|
22
|
+
policy: RuntimePolicy | undefined,
|
|
23
|
+
role: RuntimeRole = "candidate",
|
|
24
|
+
) {
|
|
25
|
+
if (!policy) return [];
|
|
26
|
+
return [
|
|
27
|
+
{
|
|
28
|
+
id: "sevro.host.runtime",
|
|
29
|
+
completeness: "complete" as const,
|
|
30
|
+
data: {
|
|
31
|
+
method: "runner_configuration",
|
|
32
|
+
role,
|
|
33
|
+
policyDigest: runtimeDigest(policy),
|
|
34
|
+
environmentNames: Object.keys(policy.environment).sort(),
|
|
35
|
+
readOnlyRoots: policy.readOnlyRoots,
|
|
36
|
+
...discoveryObservations(policy),
|
|
37
|
+
...(policy.sharedCacheDirectories?.length
|
|
38
|
+
? { sharedCacheDirectories: selectedSharedCaches(policy, role) }
|
|
39
|
+
: {}),
|
|
40
|
+
seeds: runtimeRoleSeeds(policy, role).map((seed) => ({
|
|
41
|
+
target: seed.target,
|
|
42
|
+
sha256: seed.sha256,
|
|
43
|
+
})),
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function discoveryObservations(policy: RuntimePolicy) {
|
|
50
|
+
return {
|
|
51
|
+
discovery: policy.discovery ?? [],
|
|
52
|
+
skippedOptionalReadOnlyRoots: policy.skippedOptionalReadOnlyRoots ?? [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function runtimeDigest(policy: RuntimePolicy): string {
|
|
57
|
+
const hooks = policy.hooks ?? {};
|
|
58
|
+
return hashJson({
|
|
59
|
+
...policy,
|
|
60
|
+
seeds: policy.seeds ?? [],
|
|
61
|
+
hooks: {
|
|
62
|
+
nativeGoal: hooks.nativeGoal ?? false,
|
|
63
|
+
plugins: hooks.plugins ?? [],
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Private role state contains copied seeds and aliases to explicitly shared caches. */
|
|
69
|
+
export async function prepareRuntimeState(
|
|
70
|
+
workspace: string,
|
|
71
|
+
policy: RuntimePolicy,
|
|
72
|
+
role: RuntimeRole,
|
|
73
|
+
privateRoot?: string,
|
|
74
|
+
) {
|
|
75
|
+
requireRuntimeEnvironment(policy.environment);
|
|
76
|
+
requireRuntimeSeedTargets([
|
|
77
|
+
...(policy.seeds ?? []),
|
|
78
|
+
...(policy.sharedCacheDirectories ?? []),
|
|
79
|
+
]);
|
|
80
|
+
const root = await runtimeRoot(workspace, role, privateRoot);
|
|
81
|
+
const writableCacheRoots = await sharedCacheRoots(policy, role, [
|
|
82
|
+
workspace,
|
|
83
|
+
root,
|
|
84
|
+
]);
|
|
85
|
+
const home = join(root, "home"),
|
|
86
|
+
temp = join(root, "tmp");
|
|
87
|
+
await Promise.all(
|
|
88
|
+
[home, temp].map((path) => mkdir(path, { recursive: true, mode: 0o700 })),
|
|
89
|
+
);
|
|
90
|
+
for (const seed of runtimeRoleSeeds(policy, role))
|
|
91
|
+
await copyRuntimeSeed(seed, join(root, seed.target));
|
|
92
|
+
await linkSharedCaches(policy, role, root);
|
|
93
|
+
const environment = Object.fromEntries(
|
|
94
|
+
Object.entries(policy.environment).map(([name, value]) => [
|
|
95
|
+
name,
|
|
96
|
+
value.replaceAll("{{sevro.runtime}}", root),
|
|
97
|
+
]),
|
|
98
|
+
);
|
|
99
|
+
const isolatedEnvironment: Record<string, string> = {
|
|
100
|
+
...environment,
|
|
101
|
+
HOME: home,
|
|
102
|
+
TMPDIR: temp,
|
|
103
|
+
UV_OFFLINE: "1",
|
|
104
|
+
PYTHONDONTWRITEBYTECODE: "1",
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
root,
|
|
108
|
+
home,
|
|
109
|
+
temp,
|
|
110
|
+
environment: isolatedEnvironment,
|
|
111
|
+
writableCacheRoots,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function runtimeRoot(
|
|
116
|
+
workspace: string,
|
|
117
|
+
role: RuntimeRole,
|
|
118
|
+
privateRoot: string | undefined,
|
|
119
|
+
): Promise<string> {
|
|
120
|
+
const root = privateRoot ?? join(workspace, ".git", "sevro-runtime", role);
|
|
121
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
122
|
+
return realpath(root);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function copyRuntimeSeed(
|
|
126
|
+
seed: NonNullable<RuntimePolicy["seeds"]>[number],
|
|
127
|
+
target: string,
|
|
128
|
+
): Promise<void> {
|
|
129
|
+
const canonical = await realpath(seed.source);
|
|
130
|
+
requireRuntimeReadRoots([seed.source, canonical], []);
|
|
131
|
+
if (await seedTargetExists(target)) return;
|
|
132
|
+
await materializeRuntimeSeed(seed, target);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function seedTargetExists(target: string): Promise<boolean> {
|
|
136
|
+
try {
|
|
137
|
+
if ((await stat(target)).isDirectory()) return true;
|
|
138
|
+
throw new Error("runtime seed target is not a directory");
|
|
139
|
+
} catch (cause) {
|
|
140
|
+
if ((cause as NodeJS.ErrnoException).code !== "ENOENT") throw cause;
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type { AdvisoryAssessment } from "./advisory";
|
|
2
|
+
import type {
|
|
3
|
+
ArtifactReference,
|
|
4
|
+
Assessment,
|
|
5
|
+
CheckOutcome,
|
|
6
|
+
Diagnostic,
|
|
7
|
+
DomainOutcome,
|
|
8
|
+
EvidenceRoute,
|
|
9
|
+
ExecutionStatus,
|
|
10
|
+
ExitCode,
|
|
11
|
+
GradingStatus,
|
|
12
|
+
MetricEvidence,
|
|
13
|
+
ObservationCompleteness,
|
|
14
|
+
ObservationEvidence,
|
|
15
|
+
OutcomeStatus,
|
|
16
|
+
RawResultReference,
|
|
17
|
+
TaskVerdict,
|
|
18
|
+
UsageEvidence,
|
|
19
|
+
} from "./contract-types";
|
|
20
|
+
import type { IdentityDimensions } from "./identity";
|
|
21
|
+
import type { InstrumentationRequest } from "./instrumentation";
|
|
22
|
+
|
|
23
|
+
/** Wire assessments additionally allow optional serialized error codes. */
|
|
24
|
+
export type WireAssessment = {
|
|
25
|
+
execution: Assessment["execution"] & { errorCode?: string };
|
|
26
|
+
grading: Assessment["grading"] & { errorCode?: string };
|
|
27
|
+
task: Assessment["task"];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Validated v1 wire data also represents failures before run allocation. */
|
|
31
|
+
export type CliResultData = WireAssessment & {
|
|
32
|
+
format: "sevro.cli-result.v1";
|
|
33
|
+
runId: string | null;
|
|
34
|
+
exitCode: ExitCode;
|
|
35
|
+
evidencePath: string | null;
|
|
36
|
+
cases: Array<CliResultDataCase>;
|
|
37
|
+
diagnostic?: Diagnostic;
|
|
38
|
+
};
|
|
39
|
+
export type CliResultDataTrial = WireAssessment & {
|
|
40
|
+
trial: number;
|
|
41
|
+
checks: Array<CheckOutcome>;
|
|
42
|
+
domainOutcomes?: Array<DomainOutcome>;
|
|
43
|
+
artifactPath: string | null;
|
|
44
|
+
};
|
|
45
|
+
export type CliResultDataCase = WireAssessment & {
|
|
46
|
+
caseId: string;
|
|
47
|
+
trials: Array<CliResultDataTrial>;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Structural contracts for data validated by the versioned JSON schemas. */
|
|
51
|
+
export type RunEvidenceData = {
|
|
52
|
+
format: "sevro.run-evidence.v1";
|
|
53
|
+
runId: string;
|
|
54
|
+
evaluationIdentity: {
|
|
55
|
+
algorithm: "sevro.identity.v1";
|
|
56
|
+
digest: RunEvidenceDataDigest;
|
|
57
|
+
dimensions: IdentityDimensions;
|
|
58
|
+
};
|
|
59
|
+
configuration: {
|
|
60
|
+
digest: RunEvidenceDataDigest;
|
|
61
|
+
redacted: Record<string, unknown>;
|
|
62
|
+
resolved?: Record<string, unknown>;
|
|
63
|
+
};
|
|
64
|
+
runner: RunEvidenceDataRunner;
|
|
65
|
+
project: {
|
|
66
|
+
root: RunEvidenceDataFileUrl;
|
|
67
|
+
revision: string | null;
|
|
68
|
+
dirtyPatchDigest: RunEvidenceDataDigest | null;
|
|
69
|
+
};
|
|
70
|
+
extension: null | RunEvidenceDataExtension;
|
|
71
|
+
condition: {
|
|
72
|
+
requested: "passive" | "enforced";
|
|
73
|
+
actual: "passive" | "enforced" | "unknown";
|
|
74
|
+
requestedInstrumentation: InstrumentationRequest[];
|
|
75
|
+
appliedInstrumentation: InstrumentationRequest[];
|
|
76
|
+
};
|
|
77
|
+
graders: {
|
|
78
|
+
active: Array<RunEvidenceDataGrader>;
|
|
79
|
+
replacedDefaults: Array<string>;
|
|
80
|
+
};
|
|
81
|
+
routes: Array<EvidenceRoute>;
|
|
82
|
+
result: CliResultData;
|
|
83
|
+
trials: Array<RunEvidenceDataTrialEvidence>;
|
|
84
|
+
diagnostic?: Diagnostic;
|
|
85
|
+
};
|
|
86
|
+
type RunEvidenceDataDigest = string;
|
|
87
|
+
type RunEvidenceDataFileUrl = string;
|
|
88
|
+
type RunEvidenceDataRunner =
|
|
89
|
+
| {
|
|
90
|
+
source: "package";
|
|
91
|
+
packageName: string;
|
|
92
|
+
version: string;
|
|
93
|
+
buildDigest: RunEvidenceDataDigest;
|
|
94
|
+
}
|
|
95
|
+
| {
|
|
96
|
+
source: "checkout";
|
|
97
|
+
root: RunEvidenceDataFileUrl;
|
|
98
|
+
revision: string;
|
|
99
|
+
dirtyPatchDigest: RunEvidenceDataDigest | null;
|
|
100
|
+
buildDigest: RunEvidenceDataDigest;
|
|
101
|
+
};
|
|
102
|
+
type RunEvidenceDataExtension = {
|
|
103
|
+
id: string;
|
|
104
|
+
version: string;
|
|
105
|
+
sourceDigest: RunEvidenceDataDigest;
|
|
106
|
+
configurationDigest: RunEvidenceDataDigest;
|
|
107
|
+
protocol: "sevro.extension.v1";
|
|
108
|
+
capabilities: Array<string>;
|
|
109
|
+
replacements: { graders: Array<string>; taskVerdictPolicy: string | null };
|
|
110
|
+
};
|
|
111
|
+
type RunEvidenceDataGrader = {
|
|
112
|
+
id: string;
|
|
113
|
+
source: "builtin" | "extension";
|
|
114
|
+
version: string;
|
|
115
|
+
};
|
|
116
|
+
export type AdvisoryReviewData = {
|
|
117
|
+
status: "completed" | "failed" | "not_run";
|
|
118
|
+
assessment: AdvisoryAssessment | null;
|
|
119
|
+
usage: UsageEvidence;
|
|
120
|
+
rawResult: RawResultReference;
|
|
121
|
+
};
|
|
122
|
+
export type RunEvidenceDataTrialEvidence = {
|
|
123
|
+
caseId: string;
|
|
124
|
+
trial: number;
|
|
125
|
+
executionMode: "executed" | "dry" | "unknown";
|
|
126
|
+
candidateDurationMs?: number | null;
|
|
127
|
+
condition: {
|
|
128
|
+
requested: "passive" | "enforced";
|
|
129
|
+
actual: "passive" | "enforced" | "unknown";
|
|
130
|
+
appliedInstrumentation: InstrumentationRequest[];
|
|
131
|
+
};
|
|
132
|
+
observationCompleteness: ObservationCompleteness;
|
|
133
|
+
observations: Array<ObservationEvidence>;
|
|
134
|
+
metrics: Array<MetricEvidence>;
|
|
135
|
+
domainOutcomes?: Array<DomainOutcome>;
|
|
136
|
+
taskVerdictPolicy: null | {
|
|
137
|
+
id: string;
|
|
138
|
+
recommendation: TaskVerdict | null;
|
|
139
|
+
};
|
|
140
|
+
advisoryReview?: AdvisoryReviewData | null;
|
|
141
|
+
routes: Array<EvidenceRoute>;
|
|
142
|
+
usage: UsageEvidence;
|
|
143
|
+
rawResult: RawResultReference;
|
|
144
|
+
artifactRefs: Array<ArtifactReference>;
|
|
145
|
+
};
|
|
146
|
+
export type ReportData = {
|
|
147
|
+
format: "sevro.report.v1";
|
|
148
|
+
inputs: Array<ReportDataInput>;
|
|
149
|
+
rows: Array<ReportDataRow>;
|
|
150
|
+
summary: {
|
|
151
|
+
cases: number;
|
|
152
|
+
passed: number;
|
|
153
|
+
failed: number;
|
|
154
|
+
notAssessed: number;
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
type ReportDataPath = string;
|
|
158
|
+
type ReportDataDigest = string;
|
|
159
|
+
/** The report wire schema deliberately allows either state family in rows. */
|
|
160
|
+
type ReportDataState = ExecutionStatus | GradingStatus;
|
|
161
|
+
type ReportDataInput = {
|
|
162
|
+
resultFile: ReportDataPath;
|
|
163
|
+
execution: ExecutionStatus;
|
|
164
|
+
grading: GradingStatus;
|
|
165
|
+
task: TaskVerdict;
|
|
166
|
+
exitCode: ExitCode;
|
|
167
|
+
caseCount: number;
|
|
168
|
+
};
|
|
169
|
+
type ReportDataRoute = Omit<EvidenceRoute, "role">;
|
|
170
|
+
type ReportDataOutcome = {
|
|
171
|
+
trial: number;
|
|
172
|
+
id: string;
|
|
173
|
+
status: OutcomeStatus;
|
|
174
|
+
};
|
|
175
|
+
type ReportDataRow = {
|
|
176
|
+
nativeTranscripts?: { retained: number; pruned: number };
|
|
177
|
+
resultFile: ReportDataPath;
|
|
178
|
+
evidencePath: string | null;
|
|
179
|
+
runId: string | null;
|
|
180
|
+
caseId: string;
|
|
181
|
+
execution: ReportDataState;
|
|
182
|
+
grading: ReportDataState;
|
|
183
|
+
task: TaskVerdict;
|
|
184
|
+
exitCode: ExitCode;
|
|
185
|
+
trialCount: number;
|
|
186
|
+
taskPassRate: number | null;
|
|
187
|
+
candidateDurationMs: number | null;
|
|
188
|
+
inputTokens: number | null;
|
|
189
|
+
outputTokens: number | null;
|
|
190
|
+
costUsd: number | null;
|
|
191
|
+
runnerBuildDigest: ReportDataDigest | null;
|
|
192
|
+
evaluationDigest: ReportDataDigest | null;
|
|
193
|
+
requestedCondition: "passive" | "enforced" | null;
|
|
194
|
+
actualCondition: "passive" | "enforced" | "unknown" | null;
|
|
195
|
+
candidateRoute: ReportDataRoute | null;
|
|
196
|
+
routes: Array<EvidenceRoute>;
|
|
197
|
+
domainOutcomes: Array<ReportDataOutcome>;
|
|
198
|
+
};
|
package/src/schema.ts
CHANGED
|
@@ -1,28 +1,24 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type {
|
|
2
|
+
CliResultData,
|
|
3
|
+
RunEvidenceData,
|
|
4
|
+
ReportData,
|
|
5
|
+
} from "./schema-types";
|
|
6
|
+
import cliValidator from "./generated/cli-result.cjs";
|
|
7
|
+
import runValidator from "./generated/run-evidence.cjs";
|
|
8
|
+
import reportValidator from "./generated/report.cjs";
|
|
5
9
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
strictTypes: false,
|
|
10
|
-
});
|
|
11
|
-
ajv.addSchema(cliSchema);
|
|
12
|
-
ajv.addSchema(runSchema);
|
|
13
|
-
ajv.addSchema(reportSchema);
|
|
14
|
-
const cliValidator = ajv.getSchema("urn:sevro:schema:cli-result:v1")!;
|
|
15
|
-
const runValidator = ajv.getSchema("urn:sevro:schema:run-evidence:v1")!;
|
|
16
|
-
const reportValidator = ajv.getSchema("urn:sevro:schema:report:v1")!;
|
|
17
|
-
|
|
18
|
-
export function assertCliResult(value: unknown): void {
|
|
10
|
+
export function assertCliResult(
|
|
11
|
+
value: unknown,
|
|
12
|
+
): asserts value is CliResultData {
|
|
19
13
|
if (!cliValidator(value)) throw new Error("invalid Sevro CLI result");
|
|
20
14
|
}
|
|
21
15
|
|
|
22
|
-
export function assertRunEvidence(
|
|
16
|
+
export function assertRunEvidence(
|
|
17
|
+
value: unknown,
|
|
18
|
+
): asserts value is RunEvidenceData {
|
|
23
19
|
if (!runValidator(value)) throw new Error("invalid Sevro retained evidence");
|
|
24
20
|
}
|
|
25
21
|
|
|
26
|
-
export function assertReport(value: unknown):
|
|
22
|
+
export function assertReport(value: unknown): asserts value is ReportData {
|
|
27
23
|
if (!reportValidator(value)) throw new Error("invalid Sevro report");
|
|
28
24
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import type { RunEvidenceData } from "./schema-types";
|
|
5
|
+
import { canonicalJson, sha256 } from "./identity";
|
|
6
|
+
import type { ArtifactReference } from "./contract-types";
|
|
7
|
+
import { retainedFile } from "./results-lock";
|
|
8
|
+
import { atomicWriteJson } from "./storage";
|
|
9
|
+
|
|
10
|
+
function isNativeTranscript(artifact: ArtifactReference) {
|
|
11
|
+
return /^(?:sevro\.(?:semantic(?:\.[a-f0-9]{64})?|advisory)\.)?sevro\.native-transcripts\.bundle$/.test(
|
|
12
|
+
artifact.id,
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function transcriptArtifacts(evidence: RunEvidenceData) {
|
|
17
|
+
return evidence.trials.flatMap((trial) =>
|
|
18
|
+
trial.artifactRefs.filter(isNativeTranscript).map((artifact) => ({
|
|
19
|
+
caseId: trial.caseId,
|
|
20
|
+
trial: trial.trial,
|
|
21
|
+
...artifact,
|
|
22
|
+
})),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function pruningRecord(evidence: RunEvidenceData) {
|
|
27
|
+
return {
|
|
28
|
+
format: "sevro.pruned-native-transcripts.v1",
|
|
29
|
+
runId: evidence.runId,
|
|
30
|
+
artifacts: transcriptArtifacts(evidence),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function pruningPath(evidence: RunEvidenceData) {
|
|
35
|
+
if (evidence.result.evidencePath === null)
|
|
36
|
+
throw new Error(
|
|
37
|
+
"native transcript retention requires a retained evidence path",
|
|
38
|
+
);
|
|
39
|
+
return join(
|
|
40
|
+
dirname(evidence.result.evidencePath),
|
|
41
|
+
"native-transcripts-pruned.json",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function transcriptPath(
|
|
46
|
+
evidence: RunEvidenceData,
|
|
47
|
+
artifact: ArtifactReference,
|
|
48
|
+
) {
|
|
49
|
+
const path = fileURLToPath(artifact.path);
|
|
50
|
+
if (dirname(path) !== dirname(pruningPath(evidence)))
|
|
51
|
+
throw new Error(
|
|
52
|
+
"native transcript artifact is outside its retained result directory",
|
|
53
|
+
);
|
|
54
|
+
if (metadataPaths(evidence).includes(path))
|
|
55
|
+
throw new Error(
|
|
56
|
+
"native transcript artifact aliases retained result metadata",
|
|
57
|
+
);
|
|
58
|
+
if (otherArtifactPaths(evidence).includes(path))
|
|
59
|
+
throw new Error(
|
|
60
|
+
"native transcript artifact aliases other retained evidence",
|
|
61
|
+
);
|
|
62
|
+
return path;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function otherArtifactPaths(evidence: RunEvidenceData) {
|
|
66
|
+
return evidence.trials
|
|
67
|
+
.flatMap((trial) => [
|
|
68
|
+
trial.rawResult.path,
|
|
69
|
+
trial.advisoryReview?.rawResult.path,
|
|
70
|
+
...trial.artifactRefs
|
|
71
|
+
.filter((ref) => !isNativeTranscript(ref))
|
|
72
|
+
.map((ref) => ref.path),
|
|
73
|
+
])
|
|
74
|
+
.filter((path): path is string => path != null)
|
|
75
|
+
.map((path) => fileURLToPath(path));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function metadataPaths(evidence: RunEvidenceData) {
|
|
79
|
+
const receipt = pruningPath(evidence);
|
|
80
|
+
return [
|
|
81
|
+
receipt,
|
|
82
|
+
evidence.result.evidencePath,
|
|
83
|
+
join(dirname(receipt), "result.json"),
|
|
84
|
+
join(dirname(receipt), "latest.json"),
|
|
85
|
+
...evidence.result.cases.flatMap((selected) =>
|
|
86
|
+
selected.trials.map((trial) => trial.artifactPath),
|
|
87
|
+
),
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function transcriptPresent(
|
|
92
|
+
evidence: RunEvidenceData,
|
|
93
|
+
artifact: ArtifactReference,
|
|
94
|
+
) {
|
|
95
|
+
const path = transcriptPath(evidence, artifact);
|
|
96
|
+
if (!(await retainedFile(path))) return false;
|
|
97
|
+
if (sha256(await readFile(path)) !== artifact.sha256)
|
|
98
|
+
throw new Error("retained native transcript evidence hash differs");
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Keep capture evidence immutable; a separate receipt records intentional removal. */
|
|
103
|
+
export async function pruneNativeTranscripts(evidence: RunEvidenceData) {
|
|
104
|
+
const artifacts = transcriptArtifacts(evidence);
|
|
105
|
+
if (!artifacts.length) return 0;
|
|
106
|
+
await atomicWriteJson(pruningPath(evidence), pruningRecord(evidence));
|
|
107
|
+
let removed = 0;
|
|
108
|
+
for (const artifact of artifacts) {
|
|
109
|
+
const path = transcriptPath(evidence, artifact);
|
|
110
|
+
if (!(await retainedFile(path))) continue;
|
|
111
|
+
await rm(path);
|
|
112
|
+
removed++;
|
|
113
|
+
}
|
|
114
|
+
return removed;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Missing native bytes require an exact receipt bound to their original references. */
|
|
118
|
+
async function hasPruningRecord(evidence: RunEvidenceData) {
|
|
119
|
+
const path = pruningPath(evidence);
|
|
120
|
+
const marked = await retainedFile(path);
|
|
121
|
+
if (marked) {
|
|
122
|
+
const receipt: unknown = JSON.parse(await readFile(path, "utf8"));
|
|
123
|
+
if (canonicalJson(receipt) !== canonicalJson(pruningRecord(evidence)))
|
|
124
|
+
throw new Error(
|
|
125
|
+
"native transcript pruning record differs from retained evidence",
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return marked !== null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function nativeTranscriptRetention(
|
|
132
|
+
evidence: RunEvidenceData | null,
|
|
133
|
+
) {
|
|
134
|
+
if (!evidence) return [];
|
|
135
|
+
const marked = await hasPruningRecord(evidence);
|
|
136
|
+
const states = [];
|
|
137
|
+
for (const artifact of transcriptArtifacts(evidence)) {
|
|
138
|
+
const present = await transcriptPresent(evidence, artifact);
|
|
139
|
+
if (!present && !marked)
|
|
140
|
+
throw new Error(
|
|
141
|
+
"retained native transcript evidence is missing without a pruning record",
|
|
142
|
+
);
|
|
143
|
+
states.push({ caseId: artifact.caseId, pruned: !present });
|
|
144
|
+
}
|
|
145
|
+
return states;
|
|
146
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Narrow JSON containers before accessing untrusted members. */
|
|
2
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
3
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function isUnknownArray(value: unknown): value is unknown[] {
|
|
7
|
+
return Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isStringArray(value: unknown): value is string[] {
|
|
11
|
+
return (
|
|
12
|
+
isUnknownArray(value) && value.every((item) => typeof item === "string")
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isOptionalNonblankString(value: unknown): boolean {
|
|
17
|
+
return (
|
|
18
|
+
value === undefined || (typeof value === "string" && Boolean(value.trim()))
|
|
19
|
+
);
|
|
20
|
+
}
|