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

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