@bjoernrochel/sevro 0.1.0-rc.15 → 0.1.0-rc.16
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/docs/extension-protocol-v1.md +10 -2
- package/docs/installing.md +2 -2
- package/docs/runtime-v1.md +28 -4
- package/package.json +1 -1
- package/src/candidate-artifacts.ts +11 -1
- package/src/evaluation/resources.ts +4 -1
- package/src/evaluation/trial.ts +35 -19
- package/src/fixtures/create.ts +10 -1
- package/src/fixtures/setup.ts +12 -2
|
@@ -106,8 +106,16 @@ trial in the Git fixture after source history and working-tree files are built,
|
|
|
106
106
|
before extension artifacts are mounted or the host starts. The command is never
|
|
107
107
|
interpreted through a shell by Sevro; an extension may explicitly select a
|
|
108
108
|
shell executable. `{{sevro.project}}` and `{{sevro.workspace}}` in environment
|
|
109
|
-
values resolve to the declared roots at execution time.
|
|
110
|
-
|
|
109
|
+
values resolve to the declared roots at execution time. Setup's `HOME` is the
|
|
110
|
+
candidate commands' private trial home, outside the Git fixture. Ordinary
|
|
111
|
+
`$HOME` paths can initialize directories and seed prior state before the candidate
|
|
112
|
+
starts, including without a runtime configuration file. This home is separate
|
|
113
|
+
from host authentication and native configuration, other trials, and grader homes.
|
|
114
|
+
Setup's `TMPDIR` remains disposable and is removed when setup finishes; the shared
|
|
115
|
+
home survives through candidate execution and grading, then is removed even on
|
|
116
|
+
setup failure, cancellation, or dry runs. Trusted injected hosts must use the
|
|
117
|
+
supplied `candidateRuntimeRoot` and runtime policy to preserve this boundary.
|
|
118
|
+
The symbolic values and command enter fixture and comparison identity, while retained configuration
|
|
111
119
|
records only their digest. Setup has a two-minute limit, inherits no credential
|
|
112
120
|
environment variables, and its output is discarded. Failure or interruption
|
|
113
121
|
stops the run before host execution; it cannot become a passing assessment.
|
package/docs/installing.md
CHANGED
|
@@ -23,10 +23,10 @@ Local installation records the selected version in the project lockfile.
|
|
|
23
23
|
|
|
24
24
|
## Current release
|
|
25
25
|
|
|
26
|
-
The current release is `0.1.0-rc.
|
|
26
|
+
The current release is `0.1.0-rc.16`. Install this exact version:
|
|
27
27
|
|
|
28
28
|
```sh
|
|
29
|
-
bun add --exact @bjoernrochel/sevro@0.1.0-rc.
|
|
29
|
+
bun add --exact @bjoernrochel/sevro@0.1.0-rc.16
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
<!-- sevro-current-release:end -->
|
package/docs/runtime-v1.md
CHANGED
|
@@ -260,7 +260,10 @@ so zsh here-documents use their existing private temporary storage while shared
|
|
|
260
260
|
host temporary directories remain denied. `TMPPREFIX` is runner-owned and cannot
|
|
261
261
|
be inherited or set through runtime configuration.
|
|
262
262
|
Sevro supplies private `HOME` and temporary storage and sets `UV_OFFLINE=1`
|
|
263
|
-
and `PYTHONDONTWRITEBYTECODE=1`.
|
|
263
|
+
and `PYTHONDONTWRITEBYTECODE=1`. Fixture setup uses the candidate commands'
|
|
264
|
+
private `HOME`, so `$HOME` paths initialize the same state the candidate will
|
|
265
|
+
use. Its temporary directory stays disposable.
|
|
266
|
+
Candidate, hook, checker, semantic, and advisory
|
|
264
267
|
state are separate. With a runtime policy, native candidate, semantic, and
|
|
265
268
|
advisory roots live in a unique runner-owned host directory outside the Git
|
|
266
269
|
fixture. The root contains `home`, `tmp`, and declared seed slots; plugins can
|
|
@@ -270,7 +273,8 @@ configuration, and live transcript sources. Checker and hook caches also live
|
|
|
270
273
|
outside candidate-controlled workspace state. Private roots survive native
|
|
271
274
|
continuation turns and children, then are removed with their owning trial or
|
|
272
275
|
host state after owned hooks terminate. Opted-in candidate artifact runtimes
|
|
273
|
-
remain alive through grading as described below. Runs
|
|
276
|
+
remain alive through grading as described below. Runs with neither fixture setup
|
|
277
|
+
nor a selected runtime policy
|
|
274
278
|
retain their previous writable-state locations.
|
|
275
279
|
|
|
276
280
|
Claude refuses managed workspace or runtime namespaces overlapping its
|
|
@@ -297,7 +301,8 @@ candidate's private home after execution.
|
|
|
297
301
|
}
|
|
298
302
|
```
|
|
299
303
|
|
|
300
|
-
The example permits only the
|
|
304
|
+
The example permits only the `~/.darrow/reviews` subtree, which may be initialized
|
|
305
|
+
by trusted fixture setup or created by the candidate.
|
|
301
306
|
Its original absolute path reaches isolated shell checks and native
|
|
302
307
|
semantic/advisory commands as `SEVRO_CANDIDATE_ARTIFACT_REVIEWS`. Graders keep
|
|
303
308
|
their own private `HOME`; use the supplied variable to read candidate evidence.
|
|
@@ -313,12 +318,31 @@ to 16,384 entries, including its root. Missing directories, symlinked ancestors,
|
|
|
313
318
|
descendant symlinks, hardlinks and special files make grading an error; missing
|
|
314
319
|
evidence cannot produce a pass.
|
|
315
320
|
|
|
321
|
+
Initialize an empty directory or seed prior Review state in the extension's
|
|
322
|
+
trusted `fixtureSetup` command before the candidate starts. For example:
|
|
323
|
+
|
|
324
|
+
```sh
|
|
325
|
+
mkdir -p "$HOME/.darrow/reviews"
|
|
326
|
+
printf '%s\n' '{"verdict":"passed"}' > "$HOME/.darrow/reviews/prior.json"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Setup and candidate commands share this private trial home, including when no
|
|
330
|
+
artifact grants or runtime file are selected. Graders keep their own homes and
|
|
331
|
+
receive only declared read-only artifact grants. To test missing reports or
|
|
332
|
+
absence of new artifacts, initialize the directory without writing a report;
|
|
333
|
+
an empty safe directory reaches ordinary task checks. Missing required reports
|
|
334
|
+
can then fail those checks, while negative cases can assert the directory is
|
|
335
|
+
empty. Sevro does not automatically create declared artifact directories: if
|
|
336
|
+
setup and candidate both leave one absent, or replace it with an unsafe path,
|
|
337
|
+
grading still errors. Setup state is temporary and is removed with its trial,
|
|
338
|
+
including setup failure, cancellation, and dry runs.
|
|
339
|
+
|
|
316
340
|
The grant permits reads and denies writes, creation, deletion and renaming.
|
|
317
341
|
Credentials, other home contents and peer role/trial state stay protected.
|
|
318
342
|
Ordinary `filesystem.readOnlyRoots` cannot select this private-state exception.
|
|
319
343
|
Artifact grants and native transcript access are independent opt-ins.
|
|
320
344
|
|
|
321
|
-
The trial allocates the retained runtime before
|
|
345
|
+
The trial allocates the retained runtime before fixture setup and
|
|
322
346
|
removes it after grading, advisory review and evidence persistence, including
|
|
323
347
|
failure and cancellation paths. Review state remains outside the Git fixture.
|
|
324
348
|
These artifacts are temporary; declaring access does not copy their bytes into
|
package/package.json
CHANGED
|
@@ -65,10 +65,12 @@ function validArtifactPath(path: string): boolean {
|
|
|
65
65
|
/** Allocate before host execution so exceptions cannot lose cleanup ownership. */
|
|
66
66
|
export async function allocateCandidateRuntime(
|
|
67
67
|
policy: RuntimePolicy | undefined,
|
|
68
|
+
retainHome = false,
|
|
68
69
|
): Promise<string | undefined> {
|
|
69
70
|
const directories = policy?.artifactDirectories ?? [];
|
|
70
71
|
const codexState = codexStateEnabled(policy);
|
|
71
|
-
if (!directories
|
|
72
|
+
if (!candidateRuntimeRequired(directories, codexState, retainHome))
|
|
73
|
+
return undefined;
|
|
72
74
|
requireArtifactDirectories(directories);
|
|
73
75
|
const root = await allocateNativeState("candidate-runtime-");
|
|
74
76
|
leases.set(root, directories);
|
|
@@ -76,6 +78,14 @@ export async function allocateCandidateRuntime(
|
|
|
76
78
|
return root;
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
function candidateRuntimeRequired(
|
|
82
|
+
directories: RuntimeArtifactDirectory[],
|
|
83
|
+
codexState: boolean,
|
|
84
|
+
retainHome: boolean,
|
|
85
|
+
): boolean {
|
|
86
|
+
return directories.length > 0 || codexState || retainHome;
|
|
87
|
+
}
|
|
88
|
+
|
|
79
89
|
export async function requireCandidateRuntime(
|
|
80
90
|
root: string | undefined,
|
|
81
91
|
role?: RuntimeRole,
|
|
@@ -181,6 +181,7 @@ export async function prepareTrialWorkspace(
|
|
|
181
181
|
invocation: PreparedInvocation,
|
|
182
182
|
workspaces: RunWorkspaceReservations,
|
|
183
183
|
trial: number,
|
|
184
|
+
candidateRuntimeRoot?: string,
|
|
184
185
|
) {
|
|
185
186
|
const reservation = workspaces.candidate(trial);
|
|
186
187
|
workspaces.retain(reservation);
|
|
@@ -195,6 +196,7 @@ export async function prepareTrialWorkspace(
|
|
|
195
196
|
fixture.projectRoot,
|
|
196
197
|
options.signal,
|
|
197
198
|
reservation,
|
|
199
|
+
candidateRuntimeRoot,
|
|
198
200
|
).catch((error: unknown) => {
|
|
199
201
|
if (
|
|
200
202
|
!options.signal?.aborted ||
|
|
@@ -203,12 +205,13 @@ export async function prepareTrialWorkspace(
|
|
|
203
205
|
throw error;
|
|
204
206
|
return null;
|
|
205
207
|
});
|
|
206
|
-
|
|
208
|
+
const details = await trialWorkspaceDetails(
|
|
207
209
|
options.case,
|
|
208
210
|
invocation,
|
|
209
211
|
reservation,
|
|
210
212
|
workspace,
|
|
211
213
|
);
|
|
214
|
+
return { ...details, candidateRuntimeRoot };
|
|
212
215
|
}
|
|
213
216
|
|
|
214
217
|
async function trialWorkspaceDetails(
|
package/src/evaluation/trial.ts
CHANGED
|
@@ -231,6 +231,7 @@ class EvaluationTrial {
|
|
|
231
231
|
private readonly fixture: Awaited<ReturnType<typeof prepareTrialWorkspace>>,
|
|
232
232
|
) {
|
|
233
233
|
this.state = initialTrialState(options.dry);
|
|
234
|
+
this.state.candidateRuntimeRoot = fixture.candidateRuntimeRoot;
|
|
234
235
|
this.evidence = new TrialEvidenceWriter({
|
|
235
236
|
runDir: context.resources.runDir,
|
|
236
237
|
trial,
|
|
@@ -296,7 +297,7 @@ class EvaluationTrial {
|
|
|
296
297
|
workspace: this.fixture.workspace,
|
|
297
298
|
condition: this.options.condition,
|
|
298
299
|
fixtureBinDir: this.fixture.fixtureBinDir,
|
|
299
|
-
runtimePolicy: this.
|
|
300
|
+
runtimePolicy: this.candidateRuntimePolicy(),
|
|
300
301
|
instrumentation: snapshotInstrumentation(
|
|
301
302
|
this.context.prepared.requestedInstrumentation,
|
|
302
303
|
),
|
|
@@ -313,6 +314,16 @@ class EvaluationTrial {
|
|
|
313
314
|
};
|
|
314
315
|
}
|
|
315
316
|
|
|
317
|
+
private candidateRuntimePolicy() {
|
|
318
|
+
if (this.options.runtimePolicy) return this.options.runtimePolicy;
|
|
319
|
+
if (!this.context.prepared.fixture.fixtureSetup) return undefined;
|
|
320
|
+
return {
|
|
321
|
+
format: "sevro.runtime.v1" as const,
|
|
322
|
+
environment: {},
|
|
323
|
+
readOnlyRoots: [],
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
316
327
|
private candidateReservedIds(): Set<string> {
|
|
317
328
|
return new Set([
|
|
318
329
|
"sevro.observation.final-message",
|
|
@@ -387,9 +398,6 @@ class EvaluationTrial {
|
|
|
387
398
|
const started = performance.now();
|
|
388
399
|
try {
|
|
389
400
|
ensureNotCancelled(this.options.signal);
|
|
390
|
-
this.state.candidateRuntimeRoot = await allocateCandidateRuntime(
|
|
391
|
-
this.options.runtimePolicy,
|
|
392
|
-
);
|
|
393
401
|
this.state.hostResult = await this.options.host.run(
|
|
394
402
|
this.candidateRequest(),
|
|
395
403
|
);
|
|
@@ -781,7 +789,6 @@ class EvaluationTrial {
|
|
|
781
789
|
evaluationProgress(this.options, stage, status, this.trial);
|
|
782
790
|
}
|
|
783
791
|
private async cleanup(): Promise<void> {
|
|
784
|
-
await releaseCandidateRuntime(this.state.candidateRuntimeRoot);
|
|
785
792
|
if (this.state.candidateTranscriptRoot)
|
|
786
793
|
await rm(this.state.candidateTranscriptRoot, {
|
|
787
794
|
recursive: true,
|
|
@@ -819,21 +826,30 @@ export async function executeEvaluationTrials(
|
|
|
819
826
|
signal: options.signal,
|
|
820
827
|
},
|
|
821
828
|
async (trial, stopAdmission) => {
|
|
822
|
-
const
|
|
823
|
-
options,
|
|
824
|
-
context.prepared.fixture,
|
|
825
|
-
context.prepared.invocation,
|
|
826
|
-
context.resources.workspaces,
|
|
827
|
-
trial,
|
|
829
|
+
const candidateRuntimeRoot = await allocateCandidateRuntime(
|
|
830
|
+
options.runtimePolicy,
|
|
831
|
+
Boolean(context.prepared.fixture.fixtureSetup),
|
|
828
832
|
);
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
833
|
+
try {
|
|
834
|
+
const fixture = await prepareTrialWorkspace(
|
|
835
|
+
options,
|
|
836
|
+
context.prepared.fixture,
|
|
837
|
+
context.prepared.invocation,
|
|
838
|
+
context.resources.workspaces,
|
|
839
|
+
trial,
|
|
840
|
+
candidateRuntimeRoot,
|
|
841
|
+
);
|
|
842
|
+
evaluationProgress(options, "preparation", "completed", trial);
|
|
843
|
+
return await new EvaluationTrial(
|
|
844
|
+
options,
|
|
845
|
+
context,
|
|
846
|
+
trial,
|
|
847
|
+
stopAdmission,
|
|
848
|
+
fixture,
|
|
849
|
+
).run();
|
|
850
|
+
} finally {
|
|
851
|
+
await releaseCandidateRuntime(candidateRuntimeRoot);
|
|
852
|
+
}
|
|
837
853
|
},
|
|
838
854
|
);
|
|
839
855
|
}
|
package/src/fixtures/create.ts
CHANGED
|
@@ -36,6 +36,7 @@ export async function createFixture(
|
|
|
36
36
|
projectRoot: string,
|
|
37
37
|
signal?: AbortSignal,
|
|
38
38
|
reservedWorkspace?: string,
|
|
39
|
+
candidateRuntimeRoot?: string,
|
|
39
40
|
): Promise<string> {
|
|
40
41
|
const paths = inlineFixturePaths(fixture, generated, repository);
|
|
41
42
|
const workspace = reservedWorkspace ?? (await allocateWorkspace());
|
|
@@ -53,7 +54,13 @@ export async function createFixture(
|
|
|
53
54
|
workspace,
|
|
54
55
|
);
|
|
55
56
|
await writeInlineFixtureFiles(paths, workspace);
|
|
56
|
-
await setupFixture(
|
|
57
|
+
await setupFixture(
|
|
58
|
+
setup,
|
|
59
|
+
workspace,
|
|
60
|
+
projectRoot,
|
|
61
|
+
signal,
|
|
62
|
+
candidateRuntimeRoot,
|
|
63
|
+
);
|
|
57
64
|
await mountPreparationArtifacts(artifacts, workspace);
|
|
58
65
|
await excludePreparationArtifacts(artifacts, workspace);
|
|
59
66
|
await fixtureBinDirectory(workspace);
|
|
@@ -131,6 +138,7 @@ async function setupFixture(
|
|
|
131
138
|
workspace: string,
|
|
132
139
|
projectRoot: string,
|
|
133
140
|
signal?: AbortSignal,
|
|
141
|
+
candidateRuntimeRoot?: string,
|
|
134
142
|
): Promise<void> {
|
|
135
143
|
if (!setup) return;
|
|
136
144
|
await runFixtureSetup(setup, {
|
|
@@ -138,6 +146,7 @@ async function setupFixture(
|
|
|
138
146
|
projectRoot,
|
|
139
147
|
fixtureBinDir: (await fixtureBinDirectory(workspace)) ?? undefined,
|
|
140
148
|
signal,
|
|
149
|
+
candidateRuntimeRoot,
|
|
141
150
|
});
|
|
142
151
|
}
|
|
143
152
|
|
package/src/fixtures/setup.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
1
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { delimiter, isAbsolute, join } from "node:path";
|
|
4
4
|
import { isRecord } from "../value-guards";
|
|
5
|
+
import { requireCandidateRuntime } from "../candidate-artifacts";
|
|
5
6
|
import {
|
|
6
7
|
ProcessDeadline,
|
|
7
8
|
terminateOwnedProcessGroup,
|
|
@@ -112,6 +113,7 @@ interface FixtureSetupOptions {
|
|
|
112
113
|
projectRoot: string;
|
|
113
114
|
fixtureBinDir?: string;
|
|
114
115
|
signal?: AbortSignal;
|
|
116
|
+
candidateRuntimeRoot?: string;
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
function setupPath(directory: string | undefined): string {
|
|
@@ -134,7 +136,9 @@ function executionEnvironment(
|
|
|
134
136
|
);
|
|
135
137
|
return {
|
|
136
138
|
PATH: setupPath(options.fixtureBinDir),
|
|
137
|
-
HOME:
|
|
139
|
+
HOME: options.candidateRuntimeRoot
|
|
140
|
+
? join(options.candidateRuntimeRoot, "home")
|
|
141
|
+
: home,
|
|
138
142
|
TMPDIR: home,
|
|
139
143
|
LANG: process.env.LANG ?? "C",
|
|
140
144
|
GIT_CONFIG_GLOBAL: process.platform === "win32" ? "NUL" : "/dev/null",
|
|
@@ -201,6 +205,12 @@ export async function runFixtureSetup(
|
|
|
201
205
|
options: FixtureSetupOptions,
|
|
202
206
|
): Promise<void> {
|
|
203
207
|
if (options.signal?.aborted) throw new Error("fixture setup cancelled");
|
|
208
|
+
await requireCandidateRuntime(options.candidateRuntimeRoot);
|
|
209
|
+
if (options.candidateRuntimeRoot)
|
|
210
|
+
await mkdir(join(options.candidateRuntimeRoot, "home"), {
|
|
211
|
+
recursive: true,
|
|
212
|
+
mode: 0o700,
|
|
213
|
+
});
|
|
204
214
|
const home = await mkdtemp(join(tmpdir(), "sevro-setup-"));
|
|
205
215
|
const execution = new FixtureSetupExecution(setup, options, home);
|
|
206
216
|
try {
|