@bjoernrochel/sevro 0.1.0-rc.8 → 1.0.0
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 +22 -6
- package/README.md +5 -1
- package/docs/README.md +19 -15
- package/docs/architecture.md +138 -17
- package/docs/assets/README.md +5 -4
- package/docs/builtin-graders-v1.md +9 -2
- package/docs/codex-host.md +155 -57
- package/docs/contracts.md +2 -1
- package/docs/creating-cases.md +9 -2
- package/docs/development-cli.md +187 -19
- package/docs/documentation-quality.md +12 -2
- package/docs/evaluation-workflow.md +110 -0
- package/docs/extension-protocol-v1.md +26 -3
- package/docs/getting-started.md +23 -2
- package/docs/guide-evaluation.md +33 -1
- package/docs/identity-v1.md +25 -2
- package/docs/installing.md +11 -11
- package/docs/native-controls.md +76 -5
- package/docs/native-hosts.md +196 -7
- package/docs/reading-results.md +102 -0
- package/docs/recipes.md +489 -0
- package/docs/releases.md +31 -12
- package/docs/report-v1.md +29 -1
- package/docs/results-v1.md +122 -4
- package/docs/run-settings-v1.md +147 -0
- package/docs/runtime-v1.md +255 -22
- package/docs/troubleshooting.md +12 -12
- package/docs/typescript-quality.md +89 -26
- 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 +11 -5
- package/schemas/report-v1.schema.json +9 -0
- package/schemas/run-evidence-v1.schema.json +2 -1
- package/schemas/runtime-v1.schema.json +43 -7
- package/src/advisory.ts +2 -2
- package/src/candidate-artifacts.ts +42 -11
- package/src/candidate-codex-state.ts +185 -0
- package/src/case-validation.ts +73 -50
- package/src/cli/cancellation.ts +20 -0
- package/src/cli/command.ts +87 -0
- package/src/cli/completion.ts +15 -0
- package/src/cli/demo.ts +60 -0
- package/src/cli/evaluation.ts +55 -0
- package/src/cli/extension.ts +79 -0
- package/src/cli/help.ts +198 -0
- package/src/cli/hosts.ts +55 -0
- package/src/cli/init-settings.ts +30 -0
- package/src/cli/init.ts +111 -0
- package/src/{cli-invocation.ts → cli/invocation.ts} +214 -218
- package/src/cli/loading.ts +151 -0
- package/src/cli/preparation.ts +154 -0
- package/src/cli/presentation.ts +93 -0
- package/src/cli/progress.ts +6 -0
- package/src/cli/run-manifest.ts +128 -0
- package/src/cli/run-options.ts +403 -0
- package/src/cli/run-overrides.ts +69 -0
- package/src/cli/settings.ts +154 -0
- package/src/cli/summary.ts +43 -0
- package/src/cli.ts +13 -591
- package/src/contract-types.ts +94 -0
- package/src/engine.ts +9 -5
- package/src/evaluation/advisory.ts +150 -0
- package/src/{evaluation-check-plan.ts → evaluation/check-plan.ts} +103 -67
- package/src/evaluation/collection.ts +88 -0
- package/src/evaluation/context.ts +10 -0
- package/src/{evaluation-grading-host.ts → evaluation/grading-host.ts} +5 -5
- package/src/evaluation/grading.ts +235 -0
- package/src/{evaluation-invocation-plan.ts → evaluation/invocation-plan.ts} +147 -58
- package/src/{evaluation-plan.ts → evaluation/plan.ts} +208 -141
- package/src/evaluation/progress.ts +19 -0
- package/src/evaluation/resources.ts +241 -0
- package/src/evaluation/run.ts +206 -0
- package/src/{evaluation-semantic.ts → evaluation/semantic.ts} +77 -87
- package/src/evaluation/trial.ts +800 -0
- package/src/{evaluation-types.ts → evaluation/types.ts} +79 -29
- package/src/evidence/host-results.ts +85 -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 +34 -96
- package/src/extension-session.ts +24 -48
- package/src/{advisory-fixture.ts → fixtures/advisory.ts} +12 -72
- package/src/{fixture-bin.ts → fixtures/bin.ts} +1 -1
- package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
- package/src/fixtures/create.ts +183 -0
- package/src/{generated-fixture.ts → fixtures/generated.ts} +10 -53
- package/src/{git-hooks.ts → fixtures/git-hooks.ts} +1 -1
- package/src/{preparation.ts → fixtures/preparation.ts} +6 -16
- package/src/{repository-fixture.ts → fixtures/repository.ts} +30 -64
- package/src/{fixture-setup.ts → fixtures/setup.ts} +33 -51
- package/src/{fixture-tools.ts → fixtures/tools.ts} +1 -1
- package/src/{workspace-state.ts → fixtures/workspace-state.ts} +5 -5
- package/src/generated/cli-result.d.cts +2 -1
- package/src/generated/extension.d.cts +2 -1
- package/src/generated/report.cjs +1 -1
- package/src/generated/report.d.cts +2 -1
- package/src/generated/run-evidence.cjs +1 -1
- package/src/generated/run-evidence.d.cts +2 -1
- package/src/generated/runtime.cjs +1 -1
- package/src/generated/runtime.d.cts +2 -1
- package/src/git.ts +93 -0
- package/src/graders/git-head.ts +29 -84
- package/src/graders/shell.ts +50 -68
- package/src/host-diagnostic-redaction.ts +52 -0
- package/src/host-diagnostics.ts +331 -0
- package/src/hosts/claude-continuation.ts +57 -40
- package/src/hosts/claude-credential.ts +160 -63
- package/src/hosts/claude-events.ts +34 -18
- package/src/hosts/claude-keychain.ts +78 -21
- package/src/hosts/claude-login-recovery.ts +85 -0
- package/src/hosts/claude-login-refresh.ts +178 -0
- package/src/hosts/claude-login-store.ts +162 -0
- package/src/hosts/claude-login.ts +107 -0
- package/src/hosts/claude-native-goal.ts +15 -12
- package/src/hosts/claude-process.ts +10 -50
- package/src/hosts/claude-refresh-lock.ts +81 -0
- package/src/hosts/claude-repository-invocation.ts +9 -34
- package/src/hosts/claude-settings.ts +46 -9
- package/src/hosts/claude-skill-activations.ts +169 -0
- package/src/hosts/claude-state.ts +28 -15
- package/src/hosts/claude-tool-calls.ts +13 -10
- package/src/hosts/claude.ts +121 -81
- package/src/hosts/codex-app-server.ts +78 -52
- package/src/hosts/codex-configuration.ts +5 -10
- package/src/hosts/codex-exec-controls.ts +516 -0
- package/src/hosts/codex-marketplace.ts +12 -10
- package/src/hosts/codex-native-calls.ts +15 -122
- package/src/hosts/codex-process.ts +10 -183
- package/src/hosts/codex-profile.ts +36 -21
- package/src/hosts/codex-session-files.ts +1 -9
- package/src/hosts/codex-state.ts +134 -18
- package/src/hosts/codex.ts +210 -183
- package/src/hosts/isolation-common.ts +8 -11
- package/src/hosts/isolation-roots.ts +2 -2
- package/src/hosts/linux-sandbox.ts +15 -2
- package/src/hosts/mac-sandbox.ts +87 -18
- package/src/hosts/native-controls.ts +72 -22
- package/src/hosts/native-request.ts +55 -0
- package/src/hosts/process-lifecycle.ts +216 -48
- package/src/hosts/protected-worktrees.ts +1 -1
- package/src/hosts/skill-hook-catalog.ts +131 -0
- package/src/hosts/skill-hook-handler.ts +374 -0
- package/src/hosts/skill-hooks.ts +577 -0
- package/src/instrumentation.ts +7 -8
- package/src/native-transcript-state.ts +22 -3
- package/src/native-transcript-view.ts +52 -13
- package/src/native-transcripts.ts +26 -11
- package/src/paths.ts +22 -0
- package/src/process-lifecycle.ts +150 -0
- package/src/provenance.ts +27 -7
- package/src/prune.ts +300 -0
- package/src/report.ts +63 -36
- package/src/resolved-case.ts +1 -1
- package/src/results-lock.ts +42 -0
- package/src/results.ts +14 -18
- package/src/{runtime-config.ts → runtime/config.ts} +160 -46
- package/src/{runtime-discovery-apple.ts → runtime/discovery-apple.ts} +3 -3
- package/src/{runtime-discovery-providers.ts → runtime/discovery-providers.ts} +3 -9
- package/src/{runtime-discovery.ts → runtime/discovery.ts} +4 -4
- package/src/{runtime-hook-processes.ts → runtime/hook-processes.ts} +1 -1
- package/src/{runtime-hooks.ts → runtime/hooks.ts} +32 -22
- package/src/{runtime-paths.ts → runtime/paths.ts} +5 -19
- package/src/{runtime-query.ts → runtime/query.ts} +5 -5
- package/src/{runtime-seeds.ts → runtime/seeds.ts} +3 -3
- package/src/runtime/shared-cache.ts +130 -0
- package/src/{runtime-state.ts → runtime/state.ts} +28 -11
- package/src/schema-types.ts +73 -161
- package/src/shell.ts +4 -0
- package/src/transcript-retention.ts +146 -0
- package/src/value-guards.ts +22 -0
- package/docs/assets/sevro-logo.png +0 -0
- package/docs/assets/sevro-symbol.svg +0 -1
- package/docs/assets/sevro-wordmark.svg +0 -1
- package/docs/cache-ownership-validation.md +0 -49
- package/docs/claude-continuation-validation.md +0 -47
- package/docs/concurrency-validation.md +0 -86
- package/docs/configuration-root-validation.md +0 -70
- package/docs/engine-slice.md +0 -114
- package/docs/evidence/runtime-startup-30/README.md +0 -185
- package/docs/evidence/runtime-startup-30/after.json +0 -319
- package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +0 -2
- package/docs/evidence/runtime-startup-30/before.json +0 -403
- package/docs/evidence/runtime-startup-30/consumer-smoke.json +0 -63
- package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +0 -3
- package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +0 -3
- package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +0 -20
- package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +0 -2
- package/docs/evidence.md +0 -17
- package/docs/fixture-cleanup-validation.md +0 -73
- package/docs/guide-verification.json +0 -239
- package/src/evaluation-context.ts +0 -259
- package/src/evaluation-fixture.ts +0 -302
- package/src/evaluation-run.ts +0 -200
- package/src/evaluation-trial.ts +0 -1016
- package/src/hosts/claude-nested-skills.ts +0 -272
- package/src/hosts/codex-command-output.ts +0 -510
- package/src/hosts/codex-executor-command.ts +0 -291
- package/src/hosts/codex-skill-reads.ts +0 -783
- /package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +0 -0
|
@@ -1,25 +1,38 @@
|
|
|
1
1
|
import { lstat, readFile, realpath } from "node:fs/promises";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
+
import type { ErrorObject } from "ajv";
|
|
3
4
|
import {
|
|
4
5
|
declaredRuntimeRoots,
|
|
5
6
|
optionalRuntimeRoots,
|
|
6
7
|
runtimePathDirectories,
|
|
7
8
|
runtimeInheritedPath,
|
|
8
9
|
requireRuntimeReadRoots,
|
|
9
|
-
} from "./
|
|
10
|
-
import validate from "
|
|
10
|
+
} from "./paths";
|
|
11
|
+
import validate from "../generated/runtime.cjs";
|
|
11
12
|
import {
|
|
12
13
|
requireArtifactDirectories,
|
|
13
14
|
type RuntimeArtifactDirectory,
|
|
14
|
-
} from "
|
|
15
|
-
import { runtimeSeedDigest } from "./
|
|
15
|
+
} from "../candidate-artifacts";
|
|
16
|
+
import { runtimeSeedDigest } from "./seeds";
|
|
17
|
+
import {
|
|
18
|
+
resolveSharedCaches,
|
|
19
|
+
requireSharedCachePolicy,
|
|
20
|
+
type SharedCacheDirectory,
|
|
21
|
+
} from "./shared-cache";
|
|
16
22
|
import {
|
|
17
23
|
discoverRuntimeSupport,
|
|
18
24
|
type RuntimeDiscoverySource,
|
|
19
|
-
} from "./
|
|
25
|
+
} from "./discovery";
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
/** Canonical roles; schemas:generate projects this list into the runtime schema. */
|
|
28
|
+
export const RUNTIME_ROLES = [
|
|
29
|
+
"candidate",
|
|
30
|
+
"checks",
|
|
31
|
+
"hooks",
|
|
32
|
+
"semantic",
|
|
33
|
+
"advisory",
|
|
34
|
+
] as const;
|
|
35
|
+
export type RuntimeRole = (typeof RUNTIME_ROLES)[number];
|
|
23
36
|
export interface RuntimeSeed {
|
|
24
37
|
source: string;
|
|
25
38
|
target: string;
|
|
@@ -27,11 +40,14 @@ export interface RuntimeSeed {
|
|
|
27
40
|
}
|
|
28
41
|
export interface RuntimeConfiguration {
|
|
29
42
|
format: "sevro.runtime.v1";
|
|
43
|
+
codexState?: boolean;
|
|
44
|
+
network?: { localHttp?: boolean };
|
|
30
45
|
nativeTranscripts?: boolean;
|
|
31
46
|
environment?: { inherit?: string[]; set?: Record<string, string> };
|
|
32
47
|
filesystem?: { readOnlyRoots?: string[]; optionalReadOnlyRoots?: string[] };
|
|
33
48
|
runtime?: {
|
|
34
49
|
seedDirectories?: RuntimeSeed[];
|
|
50
|
+
sharedCacheDirectories?: SharedCacheDirectory[];
|
|
35
51
|
artifactDirectories?: RuntimeArtifactDirectory[];
|
|
36
52
|
};
|
|
37
53
|
hooks?: { nativeGoal?: boolean; plugins?: string[] };
|
|
@@ -39,6 +55,8 @@ export interface RuntimeConfiguration {
|
|
|
39
55
|
|
|
40
56
|
export interface RuntimePolicy {
|
|
41
57
|
format: "sevro.runtime.v1";
|
|
58
|
+
codexState?: boolean;
|
|
59
|
+
network?: { localHttp?: boolean };
|
|
42
60
|
artifactDirectories?: RuntimeArtifactDirectory[];
|
|
43
61
|
nativeTranscripts?: boolean;
|
|
44
62
|
environment: Record<string, string>;
|
|
@@ -48,9 +66,18 @@ export interface RuntimePolicy {
|
|
|
48
66
|
/** Original source denials retained when a derived role policy omits seeds. */
|
|
49
67
|
seedReadDenyRoots?: string[];
|
|
50
68
|
seeds?: (RuntimeSeed & { sha256: string })[];
|
|
69
|
+
sharedCacheDirectories?: SharedCacheDirectory[];
|
|
51
70
|
hooks?: { nativeGoal?: boolean; plugins?: string[] };
|
|
52
71
|
}
|
|
53
72
|
|
|
73
|
+
export function runtimeLocalHttpEnabled(
|
|
74
|
+
policy: RuntimePolicy | undefined,
|
|
75
|
+
role: RuntimeRole,
|
|
76
|
+
): boolean {
|
|
77
|
+
const network = policy?.network;
|
|
78
|
+
return network?.localHttp === true && ["candidate", "checks"].includes(role);
|
|
79
|
+
}
|
|
80
|
+
|
|
54
81
|
export function runtimeNativeGoalEnabled(
|
|
55
82
|
policy: RuntimePolicy | undefined,
|
|
56
83
|
): boolean {
|
|
@@ -58,28 +85,64 @@ export function runtimeNativeGoalEnabled(
|
|
|
58
85
|
return hooks?.nativeGoal === true;
|
|
59
86
|
}
|
|
60
87
|
|
|
88
|
+
const validatedPolicies = new WeakSet<RuntimePolicy>();
|
|
89
|
+
|
|
90
|
+
export function runtimePolicySnapshot(policy: RuntimePolicy): RuntimePolicy;
|
|
91
|
+
export function runtimePolicySnapshot(
|
|
92
|
+
policy: RuntimePolicy | undefined,
|
|
93
|
+
): RuntimePolicy | undefined;
|
|
61
94
|
export function runtimePolicySnapshot(
|
|
62
95
|
policy: RuntimePolicy | undefined,
|
|
63
96
|
): RuntimePolicy | undefined {
|
|
64
97
|
if (!policy) return undefined;
|
|
65
|
-
requireRuntimeEnvironment(policy.environment);
|
|
66
98
|
const snapshot = structuredClone(policy);
|
|
99
|
+
if (!validatedPolicies.has(policy)) requireRuntimePolicy(snapshot);
|
|
100
|
+
Object.freeze(snapshot.network);
|
|
67
101
|
freezeArtifactDirectories(snapshot);
|
|
68
102
|
Object.freeze(snapshot.environment);
|
|
69
103
|
Object.freeze(snapshot.readOnlyRoots);
|
|
70
104
|
freezeDiscovery(snapshot);
|
|
71
|
-
|
|
72
|
-
Object.freeze(seed.roles);
|
|
73
|
-
Object.freeze(seed);
|
|
74
|
-
}
|
|
75
|
-
Object.freeze(snapshot.seeds);
|
|
105
|
+
freezeRuntimeCaches(snapshot);
|
|
76
106
|
Object.freeze(snapshot.seedReadDenyRoots);
|
|
77
107
|
const hooks = snapshot.hooks;
|
|
78
108
|
if (hooks) {
|
|
79
109
|
Object.freeze(hooks.plugins);
|
|
80
110
|
Object.freeze(hooks);
|
|
81
111
|
}
|
|
82
|
-
|
|
112
|
+
Object.freeze(snapshot);
|
|
113
|
+
validatedPolicies.add(snapshot);
|
|
114
|
+
return snapshot;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Loaded snapshots are already validated; mutable direct API inputs still need validation. */
|
|
118
|
+
export function requireRuntimePolicy(policy: RuntimePolicy): void {
|
|
119
|
+
if (validatedPolicies.has(policy)) return;
|
|
120
|
+
snapshotEnvironment([], policy.environment);
|
|
121
|
+
requireRuntimeCaches(policy.seeds ?? [], policy.sharedCacheDirectories ?? []);
|
|
122
|
+
requireSharedCachePolicy(policy);
|
|
123
|
+
requireArtifactDirectories(policy.artifactDirectories ?? []);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function requireRuntimeCaches(
|
|
127
|
+
seeds: RuntimeSeed[],
|
|
128
|
+
caches: SharedCacheDirectory[],
|
|
129
|
+
): void {
|
|
130
|
+
requireRuntimeSeedTargets([...seeds, ...caches]);
|
|
131
|
+
for (const seed of seeds)
|
|
132
|
+
if (seed.roles !== undefined) requireRuntimeRoles(seed.roles, "seed");
|
|
133
|
+
for (const cache of caches) requireRuntimeRoles(cache.roles, "shared cache");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function freezeRuntimeCaches(snapshot: RuntimePolicy): void {
|
|
137
|
+
for (const seed of [
|
|
138
|
+
...(snapshot.seeds ?? []),
|
|
139
|
+
...(snapshot.sharedCacheDirectories ?? []),
|
|
140
|
+
]) {
|
|
141
|
+
Object.freeze(seed.roles);
|
|
142
|
+
Object.freeze(seed);
|
|
143
|
+
}
|
|
144
|
+
Object.freeze(snapshot.seeds);
|
|
145
|
+
Object.freeze(snapshot.sharedCacheDirectories);
|
|
83
146
|
}
|
|
84
147
|
|
|
85
148
|
function freezeDiscovery(snapshot: RuntimePolicy): void {
|
|
@@ -93,7 +156,6 @@ function freezeDiscovery(snapshot: RuntimePolicy): void {
|
|
|
93
156
|
|
|
94
157
|
function freezeArtifactDirectories(snapshot: RuntimePolicy): void {
|
|
95
158
|
const directories = snapshot.artifactDirectories ?? [];
|
|
96
|
-
requireArtifactDirectories(directories);
|
|
97
159
|
for (const directory of directories) Object.freeze(directory);
|
|
98
160
|
Object.freeze(snapshot.artifactDirectories);
|
|
99
161
|
}
|
|
@@ -101,11 +163,27 @@ function freezeArtifactDirectories(snapshot: RuntimePolicy): void {
|
|
|
101
163
|
function assertRuntimeConfiguration(
|
|
102
164
|
value: unknown,
|
|
103
165
|
): asserts value is RuntimeConfiguration {
|
|
104
|
-
if (!validate(value))
|
|
166
|
+
if (!validate(value)) {
|
|
167
|
+
// Ajv always populates errors when validation fails.
|
|
168
|
+
const issue = (validate.errors as [ErrorObject, ...ErrorObject[]])[0];
|
|
169
|
+
throw new Error(
|
|
170
|
+
`runtime configuration schema ${runtimeSchemaPath(issue)}: ${issue.message}`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function runtimeSchemaPath(issue: ErrorObject): string {
|
|
176
|
+
const params = issue.params as Record<string, unknown>;
|
|
177
|
+
const property = params.additionalProperty ?? params.missingProperty;
|
|
178
|
+
const suffix =
|
|
179
|
+
typeof property === "string"
|
|
180
|
+
? "/" + property.replaceAll("~", "~0").replaceAll("/", "~1")
|
|
181
|
+
: "";
|
|
182
|
+
return safeFilesystemPath(issue.instancePath + suffix || "/");
|
|
105
183
|
}
|
|
106
184
|
|
|
107
185
|
const RESERVED_ENVIRONMENT =
|
|
108
|
-
/^(HOME|TMPDIR|TMP|TEMP|CODEX_HOME|CODEX_THREAD_ID|CLAUDE_CONFIG_DIR|CLAUDE_EFFORT|BASH_ENV|ENV|ZDOTDIR|NODE_OPTIONS|BUN_OPTIONS|UV_OFFLINE|GIT_CONFIG.*|LD_.*|DYLD_.*|CLAUDE_CODE_.*|SEVRO_NATIVE_.*|SEVRO_CANDIDATE_.*)$/i;
|
|
186
|
+
/^(HOME|TMPDIR|TMPPREFIX|TMP|TEMP|CODEX_HOME|CODEX_THREAD_ID|CLAUDE_CONFIG_DIR|CLAUDE_EFFORT|BASH_ENV|ENV|ZDOTDIR|NODE_OPTIONS|BUN_OPTIONS|UV_OFFLINE|GIT_CONFIG.*|LD_.*|DYLD_.*|CLAUDE_CODE_.*|SEVRO_CODEX_.*|SEVRO_NATIVE_.*|SEVRO_CANDIDATE_.*)$/i;
|
|
109
187
|
|
|
110
188
|
function requireEnvironmentName(name: string): void {
|
|
111
189
|
if (!/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(name))
|
|
@@ -139,16 +217,7 @@ function snapshotEnvironment(
|
|
|
139
217
|
return environment;
|
|
140
218
|
}
|
|
141
219
|
|
|
142
|
-
|
|
143
|
-
environment: Record<string, string>,
|
|
144
|
-
): void {
|
|
145
|
-
snapshotEnvironment([], environment);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function requireRuntimeSeedTargets(
|
|
149
|
-
seeds: NonNullable<RuntimePolicy["seeds"]>,
|
|
150
|
-
): void {
|
|
151
|
-
for (const seed of seeds) requireRuntimeSeedRoles(seed.roles);
|
|
220
|
+
function requireRuntimeSeedTargets(seeds: RuntimeSeed[]): void {
|
|
152
221
|
const targets = seeds.map((seed) => seed.target);
|
|
153
222
|
if (
|
|
154
223
|
targets.some(
|
|
@@ -175,13 +244,29 @@ export async function loadRuntimeConfiguration(
|
|
|
175
244
|
const source = await readFile(canonical, "utf8");
|
|
176
245
|
if (Buffer.byteLength(source) > 65536)
|
|
177
246
|
throw new Error("runtime configuration exceeds 64 KiB");
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
247
|
+
return runtimePolicySnapshot(
|
|
248
|
+
await resolveRuntimeConfiguration(
|
|
249
|
+
parseRuntimeConfiguration(source),
|
|
250
|
+
dirname(canonical),
|
|
251
|
+
protectedRoots,
|
|
252
|
+
),
|
|
182
253
|
);
|
|
183
254
|
} catch (cause) {
|
|
184
|
-
throw new Error(
|
|
255
|
+
throw new Error(
|
|
256
|
+
`${runtimeConfigurationDiagnostic(cause)} at ${safeFilesystemPath(path)}`,
|
|
257
|
+
{ cause },
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function parseRuntimeConfiguration(source: string): unknown {
|
|
263
|
+
try {
|
|
264
|
+
return JSON.parse(source);
|
|
265
|
+
} catch (cause) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
"runtime configuration $: invalid JSON; check the JSON syntax",
|
|
268
|
+
{ cause },
|
|
269
|
+
);
|
|
185
270
|
}
|
|
186
271
|
}
|
|
187
272
|
|
|
@@ -256,7 +341,7 @@ async function resolvedSeeds(
|
|
|
256
341
|
environment: Record<string, string>,
|
|
257
342
|
base: string,
|
|
258
343
|
) {
|
|
259
|
-
|
|
344
|
+
return Promise.all(
|
|
260
345
|
seeds.map(async (seed) => {
|
|
261
346
|
const roots = await declaredRuntimeRoots(
|
|
262
347
|
[seed.source],
|
|
@@ -274,8 +359,6 @@ async function resolvedSeeds(
|
|
|
274
359
|
};
|
|
275
360
|
}),
|
|
276
361
|
);
|
|
277
|
-
requireRuntimeSeedTargets(resolved);
|
|
278
|
-
return resolved;
|
|
279
362
|
}
|
|
280
363
|
|
|
281
364
|
async function resolveRuntimeConfiguration(
|
|
@@ -308,19 +391,53 @@ async function resolveRuntimeConfiguration(
|
|
|
308
391
|
requireRuntimeReadRoots(readOnlyRoots, protectedRoots);
|
|
309
392
|
return {
|
|
310
393
|
format: value.format,
|
|
394
|
+
...selectedRuntimeCapabilities(value),
|
|
311
395
|
...artifactConfiguration(value.runtime),
|
|
312
|
-
...(value.nativeTranscripts === undefined
|
|
313
|
-
? {}
|
|
314
|
-
: { nativeTranscripts: value.nativeTranscripts }),
|
|
315
396
|
environment,
|
|
316
397
|
readOnlyRoots: [...new Set(readOnlyRoots)].sort(),
|
|
317
398
|
discovery: discovery.sources,
|
|
318
399
|
skippedOptionalReadOnlyRoots: optional.skipped,
|
|
319
|
-
|
|
400
|
+
...(await resolvedRuntimeCaches(value, environment, base, [
|
|
401
|
+
...protectedRoots,
|
|
402
|
+
...readOnlyRoots,
|
|
403
|
+
])),
|
|
320
404
|
hooks: resolvedHooks(value.hooks),
|
|
321
405
|
};
|
|
322
406
|
}
|
|
323
407
|
|
|
408
|
+
async function resolvedRuntimeCaches(
|
|
409
|
+
value: RuntimeConfiguration,
|
|
410
|
+
environment: Record<string, string>,
|
|
411
|
+
base: string,
|
|
412
|
+
protectedRoots: string[],
|
|
413
|
+
) {
|
|
414
|
+
const seeds = await resolvedSeeds(
|
|
415
|
+
configurationSeeds(value),
|
|
416
|
+
environment,
|
|
417
|
+
base,
|
|
418
|
+
);
|
|
419
|
+
const caches = await resolveSharedCaches(
|
|
420
|
+
value.runtime?.sharedCacheDirectories ?? [],
|
|
421
|
+
environment,
|
|
422
|
+
base,
|
|
423
|
+
[...protectedRoots, ...seeds.map((seed) => seed.source)],
|
|
424
|
+
);
|
|
425
|
+
return {
|
|
426
|
+
seeds,
|
|
427
|
+
...(caches.length ? { sharedCacheDirectories: caches } : {}),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function selectedRuntimeCapabilities(value: RuntimeConfiguration) {
|
|
432
|
+
return {
|
|
433
|
+
...(value.network === undefined ? {} : { network: value.network }),
|
|
434
|
+
...(value.codexState === undefined ? {} : { codexState: value.codexState }),
|
|
435
|
+
...(value.nativeTranscripts === undefined
|
|
436
|
+
? {}
|
|
437
|
+
: { nativeTranscripts: value.nativeTranscripts }),
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
324
441
|
async function resolvedRuntimeEnvironment(value: RuntimeConfiguration) {
|
|
325
442
|
const { inherit, set } = configurationEnvironment(value);
|
|
326
443
|
const environment = snapshotEnvironment(inherit, set);
|
|
@@ -339,7 +456,6 @@ function configurationEnvironment(value: RuntimeConfiguration) {
|
|
|
339
456
|
function artifactConfiguration(runtime: RuntimeConfiguration["runtime"]) {
|
|
340
457
|
const directories = runtime?.artifactDirectories;
|
|
341
458
|
if (directories === undefined) return {};
|
|
342
|
-
requireArtifactDirectories(directories);
|
|
343
459
|
return { artifactDirectories: directories };
|
|
344
460
|
}
|
|
345
461
|
function configurationReadRoots(value: RuntimeConfiguration): string[] {
|
|
@@ -356,20 +472,18 @@ function resolvedHooks(hooks: RuntimeConfiguration["hooks"]) {
|
|
|
356
472
|
};
|
|
357
473
|
}
|
|
358
474
|
|
|
359
|
-
function
|
|
360
|
-
if (roles === undefined) return;
|
|
361
|
-
const allowed = ["candidate", "checks", "hooks", "semantic", "advisory"];
|
|
475
|
+
function requireRuntimeRoles(roles: RuntimeRole[], kind: string): void {
|
|
362
476
|
if (
|
|
363
477
|
!validRoleArray(roles) ||
|
|
364
478
|
new Set(roles).size !== roles.length ||
|
|
365
|
-
roles.some((role) => !
|
|
479
|
+
roles.some((role) => !RUNTIME_ROLES.includes(role))
|
|
366
480
|
)
|
|
367
|
-
throw new Error(
|
|
481
|
+
throw new Error(`runtime ${kind} roles are invalid`);
|
|
368
482
|
}
|
|
369
483
|
|
|
370
484
|
export function runtimeRoleSeeds(policy: RuntimePolicy, role: RuntimeRole) {
|
|
371
485
|
return (policy.seeds ?? []).filter(
|
|
372
|
-
(seed) => seed.roles
|
|
486
|
+
(seed) => seed.roles?.includes(role) ?? role !== "checks",
|
|
373
487
|
);
|
|
374
488
|
}
|
|
375
489
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { realpath } from "node:fs/promises";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
import { RuntimeMetadataQueryError } from "./
|
|
4
|
-
import { optionalRuntimeRoots, requireRuntimeReadRoots } from "./
|
|
3
|
+
import { RuntimeMetadataQueryError } from "./query";
|
|
4
|
+
import { optionalRuntimeRoots, requireRuntimeReadRoots } from "./paths";
|
|
5
5
|
import {
|
|
6
6
|
providerExecutable,
|
|
7
7
|
query,
|
|
8
8
|
requireProvider,
|
|
9
9
|
type RuntimeDiscoverySource,
|
|
10
|
-
} from "./
|
|
10
|
+
} from "./discovery-providers";
|
|
11
11
|
|
|
12
12
|
export async function discoverApple(
|
|
13
13
|
path: string,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { access, realpath } from "node:fs/promises";
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { delimiter, isAbsolute, join } from "node:path";
|
|
4
|
-
import { runtimeMetadataQuery } from "./
|
|
5
|
-
import { requireRuntimeReadRoots } from "./
|
|
4
|
+
import { runtimeMetadataQuery } from "./query";
|
|
5
|
+
import { requireRuntimeReadRoots } from "./paths";
|
|
6
|
+
import { containsControl } from "../value-guards";
|
|
6
7
|
|
|
7
8
|
export interface RuntimeDiscoverySource {
|
|
8
9
|
provider: "homebrew" | "apple-developer";
|
|
@@ -45,13 +46,6 @@ export async function query(
|
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
function containsControl(value: string): boolean {
|
|
49
|
-
return Array.from(value).some(
|
|
50
|
-
(character) =>
|
|
51
|
-
character.charCodeAt(0) < 0x20 || character.charCodeAt(0) === 0x7f,
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
49
|
export async function requireProvider(
|
|
56
50
|
executable: string,
|
|
57
51
|
protectedRoots: string[],
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
insideRuntimeRoot,
|
|
5
5
|
optionalRuntimeRoots,
|
|
6
6
|
requireRuntimeReadRoots,
|
|
7
|
-
} from "./
|
|
7
|
+
} from "./paths";
|
|
8
8
|
import {
|
|
9
9
|
providerExecutable,
|
|
10
10
|
query,
|
|
11
11
|
requireProvider,
|
|
12
12
|
type RuntimeDiscoverySource,
|
|
13
|
-
} from "./
|
|
14
|
-
export type { RuntimeDiscoverySource } from "./
|
|
13
|
+
} from "./discovery-providers";
|
|
14
|
+
export type { RuntimeDiscoverySource } from "./discovery-providers";
|
|
15
15
|
|
|
16
16
|
async function homebrew(
|
|
17
17
|
executable: string,
|
|
@@ -63,7 +63,7 @@ export async function discoverRuntimeSupport(
|
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
if (process.platform === "darwin") {
|
|
66
|
-
const { discoverApple } = await import("./
|
|
66
|
+
const { discoverApple } = await import("./discovery-apple");
|
|
67
67
|
await discoverApple(path, environment, sources, protectedRoots);
|
|
68
68
|
}
|
|
69
69
|
return { sources, excludedPrefixes };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
|
-
import { isRecord } from "
|
|
3
|
+
import { isRecord } from "../value-guards";
|
|
4
4
|
|
|
5
5
|
export async function hookExecutions(
|
|
6
6
|
receipts: string,
|
|
@@ -7,19 +7,21 @@ import {
|
|
|
7
7
|
writeFile,
|
|
8
8
|
} from "node:fs/promises";
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
10
|
+
import { shellQuote as quote } from "../shell";
|
|
11
|
+
import type { RuntimePolicy } from "./config";
|
|
12
|
+
import { insideRuntimeRoot, requireRuntimeReadRoots } from "./paths";
|
|
13
|
+
import { runtimeSeedDigest } from "./seeds";
|
|
14
|
+
import { prepareRuntimeState } from "./state";
|
|
15
|
+
import { sharedCacheDenyRoots, sharedCacheRoots } from "./shared-cache";
|
|
16
|
+
import { macSandboxProfile } from "../hosts/mac-sandbox";
|
|
17
|
+
import type { IsolatedCommand } from "../hosts/mac-sandbox";
|
|
18
|
+
import { prepareLinuxSandboxCommand } from "../hosts/linux-sandbox";
|
|
19
|
+
import { isRecord, isUnknownArray } from "../value-guards";
|
|
20
|
+
import { hookExecutions, releaseHookProcesses } from "./hook-processes";
|
|
19
21
|
import {
|
|
20
22
|
nativeStateParent,
|
|
21
23
|
requireNativeHookRoots,
|
|
22
|
-
} from "
|
|
24
|
+
} from "../native-transcript-state";
|
|
23
25
|
|
|
24
26
|
export interface HookMounts {
|
|
25
27
|
directories: string[];
|
|
@@ -61,10 +63,6 @@ function object(value: unknown): Record<string, unknown> {
|
|
|
61
63
|
return value;
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
function quote(value: string): string {
|
|
65
|
-
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
66
|
/** Materialize immutable mounts; only declared plugin hooks reach the host. */
|
|
69
67
|
export async function prepareHookMounts(options: {
|
|
70
68
|
workspace: string;
|
|
@@ -156,7 +154,7 @@ function lazyHookExecution(
|
|
|
156
154
|
options,
|
|
157
155
|
index,
|
|
158
156
|
readRoots,
|
|
159
|
-
runtime
|
|
157
|
+
runtime,
|
|
160
158
|
);
|
|
161
159
|
if (boundary.isolation) isolation.push(boundary.isolation);
|
|
162
160
|
return {
|
|
@@ -175,31 +173,43 @@ async function prepareHookIsolation(
|
|
|
175
173
|
options: Parameters<typeof prepareHookMounts>[0],
|
|
176
174
|
index: number,
|
|
177
175
|
roots: { ordinary: string[]; curated: string[] },
|
|
178
|
-
|
|
176
|
+
runtime: Awaited<ReturnType<typeof prepareRuntimeState>>,
|
|
179
177
|
): Promise<{ profile?: string; isolation?: IsolatedCommand }> {
|
|
178
|
+
const cacheRoots = await sharedCacheRoots(
|
|
179
|
+
options.policy,
|
|
180
|
+
"hooks",
|
|
181
|
+
options.protectedRoots,
|
|
182
|
+
);
|
|
183
|
+
const protectedRoots = [
|
|
184
|
+
...options.protectedRoots,
|
|
185
|
+
...sharedCacheDenyRoots(options.policy, "hooks"),
|
|
186
|
+
];
|
|
180
187
|
if (process.platform === "linux")
|
|
181
188
|
return {
|
|
182
189
|
isolation: await prepareLinuxSandboxCommand({
|
|
183
190
|
argv: ["/bin/true"],
|
|
184
191
|
workspace: options.workspace,
|
|
185
|
-
protectedRoots
|
|
192
|
+
protectedRoots,
|
|
186
193
|
privateStateRoot: options.privateRoot,
|
|
187
194
|
denyNetwork: true,
|
|
188
195
|
readOnlyRoots: roots.ordinary,
|
|
189
196
|
hookReadRoots: roots.curated,
|
|
190
|
-
writableRuntimeRoot:
|
|
197
|
+
writableRuntimeRoot: runtime.root,
|
|
198
|
+
writableCacheRoots: cacheRoots,
|
|
191
199
|
}),
|
|
192
200
|
};
|
|
193
201
|
const profile = join(options.privateRoot, `hooks-${index}.sb`);
|
|
194
202
|
await writeFile(
|
|
195
203
|
profile,
|
|
196
204
|
macSandboxProfile(
|
|
197
|
-
|
|
205
|
+
protectedRoots,
|
|
198
206
|
true,
|
|
199
207
|
roots.ordinary,
|
|
200
|
-
|
|
208
|
+
runtime.root,
|
|
201
209
|
roots.curated,
|
|
202
210
|
options.workspace,
|
|
211
|
+
undefined,
|
|
212
|
+
cacheRoots,
|
|
203
213
|
) + "\n(deny process-info* (require-not (target self)))\n",
|
|
204
214
|
{ mode: 0o600 },
|
|
205
215
|
);
|
|
@@ -217,7 +227,7 @@ async function hookReadRoots(
|
|
|
217
227
|
];
|
|
218
228
|
const curated = trusted ? [root] : [];
|
|
219
229
|
requireRuntimeReadRoots(ordinary, options.protectedRoots);
|
|
220
|
-
await requireNativeHookRoots(curated);
|
|
230
|
+
await requireNativeHookRoots(curated, options.privateRoot);
|
|
221
231
|
return { ordinary, curated };
|
|
222
232
|
}
|
|
223
233
|
|
|
@@ -422,7 +432,7 @@ async function rewriteMarkdown(
|
|
|
422
432
|
else delete metadata.hooks;
|
|
423
433
|
await writeFile(
|
|
424
434
|
path,
|
|
425
|
-
`---\n${Bun.YAML.stringify(metadata)}---\n${body.slice(header[0].length)}`,
|
|
435
|
+
`---\n${Bun.YAML.stringify(metadata)}\n---\n${body.slice(header[0].length)}`,
|
|
426
436
|
);
|
|
427
437
|
}
|
|
428
438
|
|
|
@@ -8,27 +8,20 @@ import {
|
|
|
8
8
|
} from "node:fs/promises";
|
|
9
9
|
import { constants, lstatSync, readlinkSync, realpathSync } from "node:fs";
|
|
10
10
|
import { homedir, userInfo } from "node:os";
|
|
11
|
-
import { nativeStateProtectedRoots } from "
|
|
12
|
-
import { workspaceStatePath } from "
|
|
11
|
+
import { nativeStateProtectedRoots } from "../native-transcript-state";
|
|
12
|
+
import { workspaceStatePath } from "../fixtures/workspace-state";
|
|
13
|
+
import { insideRoot as insideRuntimeRoot } from "../paths";
|
|
14
|
+
import { containsControl } from "../value-guards";
|
|
15
|
+
export { insideRoot as insideRuntimeRoot } from "../paths";
|
|
13
16
|
import {
|
|
14
17
|
basename,
|
|
15
18
|
delimiter,
|
|
16
19
|
dirname,
|
|
17
20
|
isAbsolute,
|
|
18
21
|
join,
|
|
19
|
-
relative,
|
|
20
22
|
resolve,
|
|
21
|
-
sep,
|
|
22
23
|
} from "node:path";
|
|
23
24
|
|
|
24
|
-
export function insideRuntimeRoot(root: string, path: string): boolean {
|
|
25
|
-
const child = relative(root, path);
|
|
26
|
-
return (
|
|
27
|
-
child === "" ||
|
|
28
|
-
(!child.startsWith(`..${sep}`) && child !== ".." && !isAbsolute(child))
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
25
|
export function isRuntimeHomeRoot(root: string): boolean {
|
|
33
26
|
return [homedir(), userInfo().homedir]
|
|
34
27
|
.map((home) => resolve(home))
|
|
@@ -195,13 +188,6 @@ export async function runtimePathDirectories(
|
|
|
195
188
|
return [...new Set(roots)].sort();
|
|
196
189
|
}
|
|
197
190
|
|
|
198
|
-
function containsControl(value: string): boolean {
|
|
199
|
-
return Array.from(value).some(
|
|
200
|
-
(character) =>
|
|
201
|
-
character.charCodeAt(0) < 0x20 || character.charCodeAt(0) === 0x7f,
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
191
|
function requirePathEntry(entry: string): void {
|
|
206
192
|
if (!isAbsolute(entry) || containsControl(entry))
|
|
207
193
|
throw new Error("runtime PATH entries must be absolute and nonempty");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn, type ChildProcess } from "node:child_process";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
+
import { terminateOwnedProcessGroup } from "../process-lifecycle";
|
|
3
4
|
|
|
4
5
|
export class RuntimeMetadataQueryError extends Error {
|
|
5
6
|
constructor(
|
|
@@ -25,11 +26,10 @@ function exitError(
|
|
|
25
26
|
|
|
26
27
|
function stopQuery(child: ChildProcess): void {
|
|
27
28
|
if (child.pid === undefined) return;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
29
|
+
terminateOwnedProcessGroup(
|
|
30
|
+
{ pid: child.pid, kill: (signal) => child.kill(signal) },
|
|
31
|
+
{ alwaysGroup: true, fallbackError: "propagate" },
|
|
32
|
+
);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/** Bound both output streams and stop every descendant of this metadata query. */
|
|
@@ -8,9 +8,9 @@ import {
|
|
|
8
8
|
writeFile,
|
|
9
9
|
} from "node:fs/promises";
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
-
import { hashJson, sha256 } from "
|
|
12
|
-
import type { RuntimePolicy } from "./
|
|
13
|
-
import { insideRuntimeRoot } from "./
|
|
11
|
+
import { hashJson, sha256 } from "../identity";
|
|
12
|
+
import type { RuntimePolicy } from "./config";
|
|
13
|
+
import { insideRuntimeRoot } from "./paths";
|
|
14
14
|
|
|
15
15
|
class SeedSnapshot {
|
|
16
16
|
private entries = 0;
|