@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,207 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
InstrumentationCapability,
|
|
3
|
+
InstrumentationRequest,
|
|
4
|
+
} from "../instrumentation";
|
|
5
|
+
import type { GeneratedFixture } from "../fixtures/generated";
|
|
6
|
+
import type { PreparationSources } from "../fixtures/preparation";
|
|
7
|
+
import type { RepositoryFixture } from "../fixtures/repository";
|
|
8
|
+
import type { ExtensionCase } from "../extension-session";
|
|
9
|
+
import type { openExtensionSession } from "../extension-session";
|
|
10
|
+
import type {
|
|
11
|
+
Assessment,
|
|
12
|
+
CheckDeclaration,
|
|
13
|
+
DomainOutcome,
|
|
14
|
+
ObservationEvidence,
|
|
15
|
+
} from "../contract-types";
|
|
16
|
+
import type {
|
|
17
|
+
CliResultData,
|
|
18
|
+
CliResultDataCase,
|
|
19
|
+
CliResultDataTrial,
|
|
20
|
+
} from "../schema-types";
|
|
21
|
+
import type { RuntimePolicy } from "../runtime/config";
|
|
22
|
+
import type { RuntimeRole } from "../runtime/state";
|
|
23
|
+
import type { CandidateArtifactRoots } from "../candidate-artifacts";
|
|
24
|
+
|
|
25
|
+
export class EvaluationConfigurationError extends Error {
|
|
26
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
27
|
+
super(message, options);
|
|
28
|
+
this.name = "EvaluationConfigurationError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface HostResult {
|
|
33
|
+
finalMessage: string | null;
|
|
34
|
+
complete: boolean;
|
|
35
|
+
executionFailed?: boolean;
|
|
36
|
+
/** Adapter-owned, bounded failure summary suitable for CLI diagnostics. */
|
|
37
|
+
failureMessage?: string;
|
|
38
|
+
processFailure?: {
|
|
39
|
+
phase: "isolation-preflight" | "executable-preflight" | "execution";
|
|
40
|
+
reason: "exit" | "timeout" | "cancelled";
|
|
41
|
+
timeoutMs: number;
|
|
42
|
+
};
|
|
43
|
+
artifacts?: { id: string; bytes: Uint8Array }[];
|
|
44
|
+
observations?: Omit<ObservationEvidence, "source">[];
|
|
45
|
+
actualCondition?: "passive" | "enforced" | "unknown";
|
|
46
|
+
appliedInstrumentation?: InstrumentationRequest[];
|
|
47
|
+
inputTokens?: number | null;
|
|
48
|
+
outputTokens?: number | null;
|
|
49
|
+
costUsd?: number | null;
|
|
50
|
+
usageComplete?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A failed host call can still provide bounded evidence for retention. */
|
|
54
|
+
export class HostExecutionFailure extends Error {
|
|
55
|
+
readonly result: HostResult;
|
|
56
|
+
constructor(
|
|
57
|
+
message: string | undefined,
|
|
58
|
+
result: HostResult,
|
|
59
|
+
options?: ErrorOptions,
|
|
60
|
+
) {
|
|
61
|
+
super(message, options);
|
|
62
|
+
this.result = { ...result, executionFailed: true, failureMessage: message };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class TrialPersistenceError extends Error {
|
|
67
|
+
constructor(workspace: string, options?: ErrorOptions) {
|
|
68
|
+
super(
|
|
69
|
+
`trial persistence failed; fixture retained at ${workspace}`,
|
|
70
|
+
options,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface HostAdapter {
|
|
76
|
+
id: string;
|
|
77
|
+
model: string;
|
|
78
|
+
effort: string;
|
|
79
|
+
instrumentation?: InstrumentationCapability[];
|
|
80
|
+
hostCapabilities?: string[];
|
|
81
|
+
configuration?: Record<string, unknown>;
|
|
82
|
+
run(request: {
|
|
83
|
+
/** Persist private diagnostic bytes before the native host removes its state. */
|
|
84
|
+
retainHostDiagnostics?: (bytes: Uint8Array) => Promise<void>;
|
|
85
|
+
runtimePolicy?: RuntimePolicy;
|
|
86
|
+
runtimeRole?: RuntimeRole;
|
|
87
|
+
candidateTranscriptRoot?: string;
|
|
88
|
+
candidateRuntimeRoot?: string;
|
|
89
|
+
candidateArtifactRoots?: CandidateArtifactRoots;
|
|
90
|
+
prompt: string;
|
|
91
|
+
followUpPrompt?: string;
|
|
92
|
+
workspace: string;
|
|
93
|
+
condition: "passive" | "enforced";
|
|
94
|
+
fixtureBinDir?: string;
|
|
95
|
+
instrumentation?: InstrumentationRequest[];
|
|
96
|
+
codexMarketplace?: {
|
|
97
|
+
artifactRoot: string;
|
|
98
|
+
marketplaceName: string;
|
|
99
|
+
pluginNames: string[];
|
|
100
|
+
artifactPaths: string[];
|
|
101
|
+
};
|
|
102
|
+
claudePluginDirs?: {
|
|
103
|
+
artifactRoots: string[];
|
|
104
|
+
artifactPaths: string[];
|
|
105
|
+
};
|
|
106
|
+
explicitSkillInvocation?:
|
|
107
|
+
| {
|
|
108
|
+
scope?: "plugin";
|
|
109
|
+
pluginName: string;
|
|
110
|
+
skillName: string;
|
|
111
|
+
token: string;
|
|
112
|
+
}
|
|
113
|
+
| {
|
|
114
|
+
scope: "repository";
|
|
115
|
+
pluginName?: never;
|
|
116
|
+
skillName: string;
|
|
117
|
+
token: string;
|
|
118
|
+
};
|
|
119
|
+
signal?: AbortSignal;
|
|
120
|
+
}): Promise<HostResult>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface ResolvedCase {
|
|
124
|
+
id: string;
|
|
125
|
+
prompt: string;
|
|
126
|
+
followUpPrompt?: string;
|
|
127
|
+
fixture:
|
|
128
|
+
| { files: Record<string, string>; sourceRef?: never }
|
|
129
|
+
| Omit<RepositoryFixture, "kind">
|
|
130
|
+
| (GeneratedFixture & { sourceRef?: never });
|
|
131
|
+
checks: CheckDeclaration[];
|
|
132
|
+
requiredEvidence: string[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface EvaluationProgress {
|
|
136
|
+
stage: "preparation" | "candidate" | "grading" | "evidence";
|
|
137
|
+
status: "started" | "completed" | "skipped";
|
|
138
|
+
trial?: number;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface EvaluationOptions {
|
|
142
|
+
/** Optional best-effort observation; exceptions never change the evaluation. */
|
|
143
|
+
onProgress?: (event: EvaluationProgress) => void;
|
|
144
|
+
/** Redacted operator preview, retained for reproduction outside semantic identity. */
|
|
145
|
+
resolvedRunSettings?: Record<string, unknown>;
|
|
146
|
+
/** Additional non-secret assessment settings included in configuration identity. */
|
|
147
|
+
assessmentSettings?: Record<string, unknown>;
|
|
148
|
+
runtimePolicy?: RuntimePolicy;
|
|
149
|
+
projectRoot: string;
|
|
150
|
+
resultsRoot: string;
|
|
151
|
+
runStateRoot?: string;
|
|
152
|
+
case: ResolvedCase;
|
|
153
|
+
host: HostAdapter;
|
|
154
|
+
semanticHost?: HostAdapter;
|
|
155
|
+
advisoryHost?: HostAdapter;
|
|
156
|
+
advisoryExcludedPaths?: string[];
|
|
157
|
+
runnerBuildDigest: string;
|
|
158
|
+
runnerCheckoutRoot?: string;
|
|
159
|
+
projectDigest: string;
|
|
160
|
+
condition: "passive" | "enforced";
|
|
161
|
+
trialCount: number;
|
|
162
|
+
jobs?: number;
|
|
163
|
+
passThreshold: number;
|
|
164
|
+
dry?: boolean;
|
|
165
|
+
signal?: AbortSignal;
|
|
166
|
+
extension?: {
|
|
167
|
+
session: Awaited<ReturnType<typeof openExtensionSession>>;
|
|
168
|
+
resolvedCase: ExtensionCase;
|
|
169
|
+
};
|
|
170
|
+
preparationSources?: PreparationSources;
|
|
171
|
+
shellIsolation?: {
|
|
172
|
+
protectedRoots: string[];
|
|
173
|
+
toolchainBinDir?: string;
|
|
174
|
+
uvRuntimeCache?: boolean;
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Persisted evaluations always retain a trial path and a known outcome list. */
|
|
179
|
+
export interface TrialSummary
|
|
180
|
+
extends
|
|
181
|
+
Assessment,
|
|
182
|
+
Omit<
|
|
183
|
+
CliResultDataTrial,
|
|
184
|
+
keyof Assessment | "domainOutcomes" | "artifactPath"
|
|
185
|
+
> {
|
|
186
|
+
domainOutcomes: DomainOutcome[];
|
|
187
|
+
artifactPath: string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface CaseSummary
|
|
191
|
+
extends Assessment, Omit<CliResultDataCase, keyof Assessment | "trials"> {
|
|
192
|
+
trials: TrialSummary[];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Unlike pre-run wire failures, a returned evaluation has run/evidence paths. */
|
|
196
|
+
export interface CliResult
|
|
197
|
+
extends
|
|
198
|
+
Assessment,
|
|
199
|
+
Omit<
|
|
200
|
+
CliResultData,
|
|
201
|
+
keyof Assessment | "runId" | "exitCode" | "evidencePath" | "cases"
|
|
202
|
+
> {
|
|
203
|
+
runId: string;
|
|
204
|
+
exitCode: number;
|
|
205
|
+
evidencePath: string;
|
|
206
|
+
cases: CaseSummary[];
|
|
207
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { isRecord } from "../value-guards";
|
|
2
|
+
import { canonicalJson } from "../identity";
|
|
3
|
+
import type { HostResult } from "../evaluation/types";
|
|
4
|
+
import type {
|
|
5
|
+
ObservationCompleteness,
|
|
6
|
+
ObservationEvidence,
|
|
7
|
+
} from "../contract-types";
|
|
8
|
+
|
|
9
|
+
export function hostObservations(
|
|
10
|
+
result: HostResult,
|
|
11
|
+
hostId: string,
|
|
12
|
+
existingIds: Set<string>,
|
|
13
|
+
): ObservationEvidence[] {
|
|
14
|
+
const items = result.observations ?? [];
|
|
15
|
+
if (items.length > 128) throw new Error("too many host observations");
|
|
16
|
+
const ids = new Set([...existingIds, "sevro.observation.final-message"]);
|
|
17
|
+
let totalBytes = 0;
|
|
18
|
+
return items.map((value: unknown) => {
|
|
19
|
+
const item = validatedHostObservation(value, ids);
|
|
20
|
+
ids.add(item.id);
|
|
21
|
+
const data = canonicalJson(item.data);
|
|
22
|
+
totalBytes += Buffer.byteLength(data, "utf8");
|
|
23
|
+
if (totalBytes > 8 * 1024 * 1024)
|
|
24
|
+
throw new Error("host observations exceed 8 MiB");
|
|
25
|
+
return {
|
|
26
|
+
id: item.id,
|
|
27
|
+
source: hostId,
|
|
28
|
+
completeness: item.completeness,
|
|
29
|
+
data: JSON.parse(data) as Record<string, unknown>,
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function hostArtifacts(result: HostResult, existingIds: Set<string>) {
|
|
35
|
+
const items = result.artifacts ?? [];
|
|
36
|
+
if (!Array.isArray(items) || items.length > 32)
|
|
37
|
+
throw new Error("too many host artifacts");
|
|
38
|
+
const ids = new Set(existingIds);
|
|
39
|
+
let totalBytes = 0;
|
|
40
|
+
return items.map((value: unknown) => {
|
|
41
|
+
const item = validatedHostArtifact(value, ids);
|
|
42
|
+
ids.add(item.id);
|
|
43
|
+
totalBytes += item.bytes.byteLength;
|
|
44
|
+
if (
|
|
45
|
+
item.bytes.byteLength > 8 * 1024 * 1024 ||
|
|
46
|
+
totalBytes > 32 * 1024 * 1024
|
|
47
|
+
)
|
|
48
|
+
throw new Error("host artifacts exceed the size limit");
|
|
49
|
+
return { id: item.id, bytes: Buffer.from(item.bytes) };
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function validEvidenceId(value: unknown): value is string {
|
|
54
|
+
return (
|
|
55
|
+
typeof value === "string" &&
|
|
56
|
+
/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/.test(value)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validObservationId(value: unknown, ids: Set<string>): value is string {
|
|
61
|
+
return (
|
|
62
|
+
validEvidenceId(value) &&
|
|
63
|
+
!value.startsWith("sevro.observation.") &&
|
|
64
|
+
!ids.has(value)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function validCompleteness(value: unknown): value is ObservationCompleteness {
|
|
69
|
+
return value === "complete" || value === "partial" || value === "unavailable";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function validatedHostObservation(value: unknown, ids: Set<string>) {
|
|
73
|
+
if (!isRecord(value)) throw new Error("invalid host observation");
|
|
74
|
+
if (
|
|
75
|
+
!validObservationId(value.id, ids) ||
|
|
76
|
+
!validCompleteness(value.completeness) ||
|
|
77
|
+
!isRecord(value.data)
|
|
78
|
+
)
|
|
79
|
+
throw new Error("invalid host observation");
|
|
80
|
+
return { id: value.id, completeness: value.completeness, data: value.data };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function validArtifactId(value: unknown, ids: Set<string>): value is string {
|
|
84
|
+
return validEvidenceId(value) && !ids.has(value);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function validatedHostArtifact(value: unknown, ids: Set<string>) {
|
|
88
|
+
if (!isRecord(value)) throw new Error("invalid host artifact");
|
|
89
|
+
if (!validArtifactId(value.id, ids) || !(value.bytes instanceof Uint8Array))
|
|
90
|
+
throw new Error("invalid host artifact");
|
|
91
|
+
return { id: value.id, bytes: value.bytes };
|
|
92
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { sha256 } from "../identity";
|
|
4
|
+
|
|
5
|
+
export async function verifyRetainedArtifacts(
|
|
6
|
+
artifacts: { path: string; sha256: string }[],
|
|
7
|
+
): Promise<void> {
|
|
8
|
+
for (const artifact of artifacts) {
|
|
9
|
+
let actual: string;
|
|
10
|
+
try {
|
|
11
|
+
actual = sha256(await readFile(fileURLToPath(artifact.path)));
|
|
12
|
+
} catch {
|
|
13
|
+
throw new Error("retained preparation artifact is unreadable");
|
|
14
|
+
}
|
|
15
|
+
if (actual !== artifact.sha256)
|
|
16
|
+
throw new Error("retained preparation artifact changed");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { TrialPersistenceError, type TrialSummary } from "../evaluation/types";
|
|
5
|
+
import { sha256 } from "../identity";
|
|
6
|
+
import type { Assessment, CheckOutcome } from "../results";
|
|
7
|
+
import type {
|
|
8
|
+
ArtifactReference,
|
|
9
|
+
Diagnostic,
|
|
10
|
+
ObservationEvidence,
|
|
11
|
+
RawResultReference,
|
|
12
|
+
} from "../contract-types";
|
|
13
|
+
import type { RunEvidenceDataTrialEvidence } from "../schema-types";
|
|
14
|
+
import { atomicWriteJson } from "../storage";
|
|
15
|
+
import type { hostArtifacts } from "./host-results";
|
|
16
|
+
import { verifyRetainedArtifacts } from "./retained-artifacts";
|
|
17
|
+
|
|
18
|
+
type TrialEvidence = RunEvidenceDataTrialEvidence;
|
|
19
|
+
export type TrialObservation = ObservationEvidence;
|
|
20
|
+
|
|
21
|
+
interface TrialWriterInput {
|
|
22
|
+
readonly runDir: string;
|
|
23
|
+
readonly trial: number;
|
|
24
|
+
readonly workspace: string;
|
|
25
|
+
readonly preparationArtifacts: readonly ArtifactReference[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Owns private trial files and their references, independently of run collection. */
|
|
29
|
+
export class TrialEvidenceWriter {
|
|
30
|
+
private readonly references: ArtifactReference[];
|
|
31
|
+
private rawPath: string | null = null;
|
|
32
|
+
private rawDigest: string | null = null;
|
|
33
|
+
|
|
34
|
+
constructor(private readonly input: TrialWriterInput) {
|
|
35
|
+
this.references = [...input.preparationArtifacts];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get artifactRefs(): ArtifactReference[] {
|
|
39
|
+
return [...this.references];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get hasCandidateMessage(): boolean {
|
|
43
|
+
return this.rawDigest !== null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private async writePrivateFile(
|
|
47
|
+
path: string,
|
|
48
|
+
bytes: string | Uint8Array,
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
try {
|
|
51
|
+
await writeFile(path, bytes, { flag: "wx", mode: 0o600 });
|
|
52
|
+
} catch (cause) {
|
|
53
|
+
throw new TrialPersistenceError(this.input.workspace, { cause });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private async retainReference(
|
|
58
|
+
id: string,
|
|
59
|
+
filename: string,
|
|
60
|
+
bytes: string | Uint8Array,
|
|
61
|
+
): Promise<ArtifactReference> {
|
|
62
|
+
const path = join(
|
|
63
|
+
this.input.runDir,
|
|
64
|
+
`trial-${this.input.trial}-${filename}`,
|
|
65
|
+
);
|
|
66
|
+
await this.writePrivateFile(path, bytes);
|
|
67
|
+
const reference = {
|
|
68
|
+
id,
|
|
69
|
+
path: pathToFileURL(path).href,
|
|
70
|
+
sha256: sha256(bytes),
|
|
71
|
+
};
|
|
72
|
+
this.references.push(reference);
|
|
73
|
+
return reference;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async retainArtifacts(
|
|
77
|
+
artifacts: readonly ReturnType<typeof hostArtifacts>[number][],
|
|
78
|
+
prefix = "",
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
for (const artifact of artifacts)
|
|
81
|
+
await this.retainReference(
|
|
82
|
+
artifact.id,
|
|
83
|
+
`${prefix}${artifact.id}.bin`,
|
|
84
|
+
artifact.bytes,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
verifyArtifacts(): Promise<void> {
|
|
89
|
+
return verifyRetainedArtifacts(this.references);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
observeCandidate(
|
|
93
|
+
source: string,
|
|
94
|
+
result: { finalMessage: string | null; complete: boolean } | null,
|
|
95
|
+
): TrialObservation {
|
|
96
|
+
if (result === null || result.finalMessage == null) {
|
|
97
|
+
this.rawDigest = null;
|
|
98
|
+
return {
|
|
99
|
+
id: "sevro.observation.final-message",
|
|
100
|
+
source,
|
|
101
|
+
completeness: "unavailable",
|
|
102
|
+
data: {},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
this.rawDigest = sha256(result.finalMessage);
|
|
106
|
+
return {
|
|
107
|
+
id: "sevro.observation.final-message",
|
|
108
|
+
source,
|
|
109
|
+
completeness: result.complete ? "complete" : "partial",
|
|
110
|
+
data: {
|
|
111
|
+
sha256: this.rawDigest,
|
|
112
|
+
byteLength: Buffer.byteLength(result.finalMessage, "utf8"),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async retainSemanticVerdicts(
|
|
118
|
+
message: string | null,
|
|
119
|
+
suffix: string,
|
|
120
|
+
): Promise<void> {
|
|
121
|
+
if (message === null || Buffer.byteLength(message, "utf8") > 1024 * 1024)
|
|
122
|
+
return;
|
|
123
|
+
await this.retainReference(
|
|
124
|
+
`sevro.semantic.verdicts${suffix}`,
|
|
125
|
+
`semantic-verdicts${suffix}.json`,
|
|
126
|
+
Buffer.from(message, "utf8"),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async retainAdvisoryResponse(
|
|
131
|
+
source: string,
|
|
132
|
+
message: string | null,
|
|
133
|
+
): Promise<RawResultReference> {
|
|
134
|
+
if (message === null || Buffer.byteLength(message, "utf8") > 64 * 1024)
|
|
135
|
+
return { source, path: null, sha256: null };
|
|
136
|
+
const reference = await this.retainReference(
|
|
137
|
+
"sevro.advisory.response",
|
|
138
|
+
"advisory.json",
|
|
139
|
+
message,
|
|
140
|
+
);
|
|
141
|
+
return { source, path: reference.path, sha256: reference.sha256 };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async retainRawCandidate(message: string | null | undefined): Promise<void> {
|
|
145
|
+
await this.verifyArtifacts();
|
|
146
|
+
if (message == null) {
|
|
147
|
+
this.rawPath = null;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
this.rawPath = join(this.input.runDir, `trial-${this.input.trial}-raw.txt`);
|
|
151
|
+
await this.writePrivateFile(this.rawPath, message);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
candidateRawResult(source: string): RawResultReference {
|
|
155
|
+
return {
|
|
156
|
+
source,
|
|
157
|
+
path: this.rawPath ? pathToFileURL(this.rawPath).href : null,
|
|
158
|
+
sha256: this.rawDigest,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
diagnosticLocation(diagnostic: Diagnostic | undefined) {
|
|
163
|
+
if (!diagnostic) return diagnostic;
|
|
164
|
+
const artifact = this.references.find((ref) =>
|
|
165
|
+
ref.id.endsWith("sevro.host.diagnostics"),
|
|
166
|
+
);
|
|
167
|
+
if (!artifact) return diagnostic;
|
|
168
|
+
const message = `${diagnostic.message}; diagnostics: ${artifact.path}`;
|
|
169
|
+
return message.length <= 4096 ? { ...diagnostic, message } : diagnostic;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async persistTrial(
|
|
173
|
+
caseId: string,
|
|
174
|
+
assessment: Assessment,
|
|
175
|
+
checks: CheckOutcome[],
|
|
176
|
+
domainOutcomes: TrialSummary["domainOutcomes"],
|
|
177
|
+
evidence: TrialEvidence,
|
|
178
|
+
): Promise<TrialSummary> {
|
|
179
|
+
const artifactPath = join(
|
|
180
|
+
this.input.runDir,
|
|
181
|
+
`trial-${this.input.trial}.json`,
|
|
182
|
+
);
|
|
183
|
+
const summary: TrialSummary = {
|
|
184
|
+
trial: this.input.trial,
|
|
185
|
+
...assessment,
|
|
186
|
+
checks,
|
|
187
|
+
domainOutcomes,
|
|
188
|
+
artifactPath,
|
|
189
|
+
};
|
|
190
|
+
try {
|
|
191
|
+
await atomicWriteJson(artifactPath, {
|
|
192
|
+
format: "sevro.trial-evidence.v1",
|
|
193
|
+
caseId,
|
|
194
|
+
trial: this.input.trial,
|
|
195
|
+
result: summary,
|
|
196
|
+
evidence,
|
|
197
|
+
});
|
|
198
|
+
} catch (cause) {
|
|
199
|
+
throw new TrialPersistenceError(this.input.workspace, { cause });
|
|
200
|
+
}
|
|
201
|
+
return summary;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HostResult } from "../evaluation/types";
|
|
2
|
+
import type { UsageEvidence } from "../contract-types";
|
|
3
|
+
|
|
4
|
+
export function usage(result: HostResult | null): UsageEvidence {
|
|
5
|
+
return result === null
|
|
6
|
+
? { inputTokens: null, outputTokens: null, costUsd: null, complete: false }
|
|
7
|
+
: availableUsage(result);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function availableUsage(result: HostResult): UsageEvidence {
|
|
11
|
+
return {
|
|
12
|
+
inputTokens: result.inputTokens ?? null,
|
|
13
|
+
outputTokens: result.outputTokens ?? null,
|
|
14
|
+
costUsd: result.costUsd ?? null,
|
|
15
|
+
complete: result.usageComplete ?? false,
|
|
16
|
+
};
|
|
17
|
+
}
|