@vela-science/canopus 0.7.0 → 0.8.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/CHANGELOG.md +80 -0
- package/README.md +67 -145
- package/capsules/erdos1056-k15/bin/linux-arm64/{10428801-10429000 → 10429201-10429400}/verifier +0 -0
- package/capsules/erdos1056-k15/bin/linux-x86_64/{10428801-10429000 → 10429201-10429400}/verifier +0 -0
- package/compatibility.json +25 -0
- package/dist/src/{receipt/map.d.ts → candidate/finalize.d.ts} +2 -9
- package/dist/src/candidate/finalize.js +71 -0
- package/dist/src/cli.js +89 -195
- package/dist/src/contracts/mission.d.ts +1 -13
- package/dist/src/contracts/mission.js +4 -42
- package/dist/src/contracts/validation.d.ts +1 -25
- package/dist/src/contracts/validation.js +1 -95
- package/dist/src/engines/codex-tools-native.js +2 -2
- package/dist/src/index.d.ts +5 -34
- package/dist/src/index.js +4 -34
- package/dist/src/mission/prepare.d.ts +10 -1
- package/dist/src/mission/prepare.js +29 -30
- package/dist/src/product/coverage.d.ts +17 -0
- package/dist/src/product/coverage.js +137 -0
- package/dist/src/product/doctor.d.ts +1 -2
- package/dist/src/product/doctor.js +29 -8
- package/dist/src/product/profile.js +8 -2
- package/dist/src/product/replay.js +11 -12
- package/dist/src/product/run.d.ts +5 -9
- package/dist/src/product/run.js +17 -81
- package/dist/src/product/submission.d.ts +40 -0
- package/dist/src/product/submission.js +217 -0
- package/dist/src/product/submit.d.ts +22 -0
- package/dist/src/product/submit.js +264 -0
- package/dist/src/product/version.d.ts +2 -2
- package/dist/src/product/version.js +27 -4
- package/dist/src/projection/current-run.d.ts +12 -0
- package/dist/src/projection/current-run.js +94 -0
- package/dist/src/projection/failure.d.ts +17 -7
- package/dist/src/projection/failure.js +43 -12
- package/dist/src/projection/retained-mission.d.ts +6 -0
- package/dist/src/projection/retained-mission.js +41 -0
- package/dist/src/projection/retained-run.d.ts +7 -0
- package/dist/src/projection/retained-run.js +50 -0
- package/dist/src/run.d.ts +43 -34
- package/dist/src/run.js +34 -397
- package/dist/src/util/canonical.d.ts +1 -5
- package/dist/src/util/canonical.js +1 -53
- package/dist/src/vela/cli.d.ts +7 -42
- package/dist/src/vela/cli.js +102 -475
- package/dist/src/vela/types.d.ts +3 -19
- package/dist/src/verifier/run.js +1 -1
- package/dist/src/workspace/prepare.d.ts +1 -1
- package/dist/src/workspace/prepare.js +15 -15
- package/docs/EVALUATION.md +142 -0
- package/docs/MISSIONS.md +42 -80
- package/docs/RUN_RECORD.md +77 -99
- package/docs/adr/0010-nonmutating-runs-and-explicit-submission.md +191 -0
- package/missions/erdos1056-k15-next/mission.draft.json +10 -8
- package/package.json +18 -15
- package/profiles/{erdos1056-k15-10428801-10429000.json → erdos1056-k15-10429201-10429400.json} +10 -10
- package/schemas/failure.v1.json +17 -0
- package/schemas/mission.v0.json +3 -4
- package/schemas/mission.v1.json +7 -28
- package/schemas/{run.v1.json → run.v2.json} +58 -116
- package/schemas/submission-bundle.v1.json +63 -0
- package/toolchain.lock.json +31 -0
- package/capsules/formal-erdos-505-test-dim-one/verifier +0 -105
- package/capsules/quantum-10-1-4/verifier.py +0 -152
- package/capsules/sidon-a24/bin/linux-arm64/verifier +0 -0
- package/capsules/sidon-a24/bin/linux-x86_64/verifier +0 -0
- package/dist/src/engines/codex-exec.d.ts +0 -17
- package/dist/src/engines/codex-exec.js +0 -136
- package/dist/src/engines/codex-tool-free.d.ts +0 -26
- package/dist/src/engines/codex-tool-free.js +0 -213
- package/dist/src/projection/diagnostic.d.ts +0 -12
- package/dist/src/projection/diagnostic.js +0 -61
- package/dist/src/projection/public-run.d.ts +0 -59
- package/dist/src/projection/public-run.js +0 -106
- package/dist/src/projection/publication.d.ts +0 -36
- package/dist/src/projection/publication.js +0 -47
- package/dist/src/projection/run.d.ts +0 -95
- package/dist/src/projection/run.js +0 -173
- package/dist/src/receipt/map.js +0 -168
- package/dist/src/repair/plan.d.ts +0 -2
- package/dist/src/repair/plan.js +0 -22
- package/dist/src/roles.d.ts +0 -2
- package/dist/src/roles.js +0 -9
- package/missions/formal-erdos-505-test-dim-one-gpt56/mission.draft.json +0 -40
- package/missions/quantum-10-1-4-retry/mission.draft.json +0 -40
- package/missions/sidon-a24-at-least-7194-gpt56/mission.draft.json +0 -42
- package/missions/sidon-a24-at-least-7194-gpt56-v2/mission.draft.json +0 -42
- package/missions/sidon-a24-at-least-7194-gpt56-v3/mission.draft.json +0 -42
- package/profiles/formal-erdos-505-test-dim-one-gpt56.json +0 -35
- package/profiles/quantum-10-1-4-stabilizer-retry.json +0 -35
- package/profiles/sidon-a24-at-least-7194-gpt56-v2.json +0 -35
- package/profiles/sidon-a24-at-least-7194-gpt56-v3.json +0 -35
- package/profiles/sidon-a24-at-least-7194-gpt56.json +0 -35
- package/schemas/canopus.public-run.v1.json +0 -116
package/dist/src/run.js
CHANGED
|
@@ -5,73 +5,16 @@ import { ActivityStore } from "./activity/store.js";
|
|
|
5
5
|
import { freezeArtifact, sealArtifactStore, } from "./artifact/freeze.js";
|
|
6
6
|
import { materializeDraftArtifacts } from "./artifact/materialize.js";
|
|
7
7
|
import { BudgetTracker } from "./budget/enforce.js";
|
|
8
|
+
import { finalizeCandidate } from "./candidate/finalize.js";
|
|
8
9
|
import { engineManifest, verifierManifest } from "./evidence/manifests.js";
|
|
9
|
-
import {
|
|
10
|
-
import { finalizeCandidate, installFrozenArtifacts, mapCandidateToReceipt, } from "./receipt/map.js";
|
|
11
|
-
import { canonicalJson, contentDigest, sha256Bytes } from "./util/canonical.js";
|
|
12
|
-
import { isolatedEnvironment, runCommand } from "./util/command.js";
|
|
13
|
-
import { retainedArtifactPath } from "./vela/cli.js";
|
|
10
|
+
import { canonicalJson, contentDigest } from "./util/canonical.js";
|
|
14
11
|
import { runVerifier } from "./verifier/run.js";
|
|
15
12
|
import { cleanupWorkspace, prepareWorkspace } from "./workspace/prepare.js";
|
|
16
|
-
function sameRoots(left, right) {
|
|
17
|
-
return (left.git_commit === right.git_commit &&
|
|
18
|
-
left.git_tree === right.git_tree &&
|
|
19
|
-
left.vela_event_log === right.vela_event_log &&
|
|
20
|
-
left.vela_snapshot === right.vela_snapshot);
|
|
21
|
-
}
|
|
22
|
-
function strictBaseline(mission) {
|
|
23
|
-
return mission.schema === "canopus.mission.v1" ? mission.strict_baseline : undefined;
|
|
24
|
-
}
|
|
25
|
-
function recordField(value, key, at) {
|
|
26
|
-
const field = value[key];
|
|
27
|
-
if (typeof field !== "object" || field === null || Array.isArray(field)) {
|
|
28
|
-
throw new Error(`${at}.${key} is not an object`);
|
|
29
|
-
}
|
|
30
|
-
return field;
|
|
31
|
-
}
|
|
32
13
|
function exactText(value, expected, at) {
|
|
33
14
|
if (value !== expected) {
|
|
34
15
|
throw new Error(`${at} mismatch: expected ${expected}, observed ${String(value)}`);
|
|
35
16
|
}
|
|
36
17
|
}
|
|
37
|
-
function assertWorkBinding(mission, response, postWork) {
|
|
38
|
-
if (response.value.schema === "vela.work.v1") {
|
|
39
|
-
exactText(response.value.command, "work", "vela work.command");
|
|
40
|
-
exactText(response.value.target_id, mission.target, "vela work.target_id");
|
|
41
|
-
const roots = recordField(response.value, "starting_roots", "vela work");
|
|
42
|
-
exactText(roots.event_log, mission.roots.vela_event_log, "vela work.starting_roots.event_log");
|
|
43
|
-
exactText(roots.git_commit, mission.roots.git_commit, "vela work.starting_roots.git_commit");
|
|
44
|
-
const session = recordField(response.value, "session", "vela work");
|
|
45
|
-
if (typeof session.id !== "string" || session.id.length === 0) {
|
|
46
|
-
throw new Error("vela work.session.id must be nonempty");
|
|
47
|
-
}
|
|
48
|
-
if (postWork.roots.git_commit === mission.roots.git_commit ||
|
|
49
|
-
postWork.roots.git_tree === mission.roots.git_tree ||
|
|
50
|
-
postWork.roots.vela_event_log === mission.roots.vela_event_log) {
|
|
51
|
-
throw new Error("vela work did not publish an exact lease delta");
|
|
52
|
-
}
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
const claim = recordField(response.value, "claim", "vela work");
|
|
56
|
-
const session = recordField(response.value, "session", "vela work");
|
|
57
|
-
const publication = recordField(claim, "publication", "vela work.claim");
|
|
58
|
-
exactText(response.value.command, "work", "vela work.command");
|
|
59
|
-
exactText(claim.state_root_before, mission.roots.vela_event_log, "vela work.claim.state_root_before");
|
|
60
|
-
exactText(session.base_event_log_root, mission.roots.vela_event_log, "vela work.session.base_event_log_root");
|
|
61
|
-
exactText(session.source_git_commit_oid, mission.roots.git_commit, "vela work.session.source_git_commit_oid");
|
|
62
|
-
exactText(claim.state_root_after, postWork.roots.vela_event_log, "vela work.claim.state_root_after");
|
|
63
|
-
const publicationState = publication.state;
|
|
64
|
-
if (publicationState !== "committed_local" &&
|
|
65
|
-
publicationState !== "pushed" &&
|
|
66
|
-
publicationState !== "unchanged") {
|
|
67
|
-
throw new Error(`vela work lease was not published: ${String(publicationState)}`);
|
|
68
|
-
}
|
|
69
|
-
exactText(publication.commit, postWork.roots.git_commit, "vela work publication commit");
|
|
70
|
-
if (postWork.roots.git_commit === mission.roots.git_commit ||
|
|
71
|
-
postWork.roots.git_tree === mission.roots.git_tree) {
|
|
72
|
-
throw new Error("vela work did not publish an exact lease delta");
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
18
|
export function validateTargetOffer(target, response) {
|
|
76
19
|
exactText(response.value.command, "next", "vela next.command");
|
|
77
20
|
const targets = response.value.targets;
|
|
@@ -95,87 +38,9 @@ export function validateTargetOffer(target, response) {
|
|
|
95
38
|
}
|
|
96
39
|
return matches[0];
|
|
97
40
|
}
|
|
98
|
-
function publicationState(land) {
|
|
99
|
-
const value = land.publication.state;
|
|
100
|
-
return typeof value === "string" ? value : "unknown";
|
|
101
|
-
}
|
|
102
|
-
export function isPrivateWorkSessionStatus(entry) {
|
|
103
|
-
if (!entry.startsWith("?? "))
|
|
104
|
-
return false;
|
|
105
|
-
const relative = entry.slice(3);
|
|
106
|
-
const parts = relative.split("/");
|
|
107
|
-
return (parts.length === 4 &&
|
|
108
|
-
parts[0] === ".vela" &&
|
|
109
|
-
parts[1] === "work" &&
|
|
110
|
-
parts[2] !== undefined &&
|
|
111
|
-
/^[a-zA-Z0-9._-]+$/u.test(parts[2]) &&
|
|
112
|
-
parts[3] === "session.json");
|
|
113
|
-
}
|
|
114
41
|
async function writeExclusive(file, value) {
|
|
115
42
|
await writeFile(file, canonicalJson(value), { flag: "wx", mode: 0o600 });
|
|
116
43
|
}
|
|
117
|
-
async function publishArtifactSources(options) {
|
|
118
|
-
const frontierRoot = path.resolve(options.repoRoot, options.frontier);
|
|
119
|
-
const paths = [...new Set(options.artifacts.map((artifact) => {
|
|
120
|
-
const absolute = path.resolve(frontierRoot, artifact.path);
|
|
121
|
-
const relative = path.relative(options.repoRoot, absolute);
|
|
122
|
-
if (relative === "" ||
|
|
123
|
-
relative === ".." ||
|
|
124
|
-
relative.startsWith(`..${path.sep}`) ||
|
|
125
|
-
path.isAbsolute(relative)) {
|
|
126
|
-
throw new Error(`artifact publication path escapes the landing repository: ${artifact.path}`);
|
|
127
|
-
}
|
|
128
|
-
return relative;
|
|
129
|
-
}))].sort();
|
|
130
|
-
const environment = isolatedEnvironment(options.home);
|
|
131
|
-
const git = async (argv) => {
|
|
132
|
-
const result = await runCommand({
|
|
133
|
-
argv: ["git", ...argv],
|
|
134
|
-
cwd: options.repoRoot,
|
|
135
|
-
env: environment,
|
|
136
|
-
timeoutMs: 30_000,
|
|
137
|
-
maxOutputBytes: 1_048_576,
|
|
138
|
-
});
|
|
139
|
-
if (result.exitCode !== 0 || result.stderr.length !== 0) {
|
|
140
|
-
throw new Error(`artifact publication git command failed: exit=${result.exitCode}; ` +
|
|
141
|
-
`stdout=${sha256Bytes(result.stdout)}; stderr=${sha256Bytes(result.stderr)}`);
|
|
142
|
-
}
|
|
143
|
-
return result.stdout;
|
|
144
|
-
};
|
|
145
|
-
await git(["add", "--force", "--", ...paths]);
|
|
146
|
-
const staged = (await git(["diff", "--cached", "--name-only", "-z"]))
|
|
147
|
-
.toString("utf8").split("\0").filter((entry) => entry.length > 0).sort();
|
|
148
|
-
if (canonicalJson(staged) !== canonicalJson(paths)) {
|
|
149
|
-
throw new Error("artifact publication staged a path outside the exact frozen artifact set");
|
|
150
|
-
}
|
|
151
|
-
const status = (await git(["status", "--porcelain=v1", "-z", "--untracked-files=all"]))
|
|
152
|
-
.toString("utf8").split("\0").filter((entry) => entry.length > 0).sort();
|
|
153
|
-
// `vela work` deliberately keeps its producer capability in one private,
|
|
154
|
-
// untracked session file until `vela land` consumes it. The worker cannot
|
|
155
|
-
// access the landing clone, and every other unstaged path remains fatal.
|
|
156
|
-
const privateWorkSessions = status.filter(isPrivateWorkSessionStatus);
|
|
157
|
-
if (privateWorkSessions.length > 1) {
|
|
158
|
-
throw new Error("artifact publication observed multiple private Vela work sessions");
|
|
159
|
-
}
|
|
160
|
-
const expectedStatus = [
|
|
161
|
-
...paths.map((entry) => `A ${entry}`),
|
|
162
|
-
...privateWorkSessions,
|
|
163
|
-
].sort();
|
|
164
|
-
if (canonicalJson(status) !== canonicalJson(expectedStatus)) {
|
|
165
|
-
throw new Error("artifact publication observed unrelated or unstaged repository changes");
|
|
166
|
-
}
|
|
167
|
-
await git([
|
|
168
|
-
"-c", "user.name=Canopus Agent",
|
|
169
|
-
"-c", "user.email=canopus-agent@invalid.example",
|
|
170
|
-
"commit", "--no-gpg-sign", "-m", "canopus: publish verified mission artifacts",
|
|
171
|
-
]);
|
|
172
|
-
const commit = (await git(["rev-parse", "--verify", "HEAD^{commit}"])).toString("utf8").trim();
|
|
173
|
-
const tree = (await git(["rev-parse", "--verify", "HEAD^{tree}"])).toString("utf8").trim();
|
|
174
|
-
if (!/^[0-9a-f]{40}$/u.test(commit) || !/^[0-9a-f]{40}$/u.test(tree)) {
|
|
175
|
-
throw new Error("artifact publication returned malformed Git object IDs");
|
|
176
|
-
}
|
|
177
|
-
return { commit, tree, paths };
|
|
178
|
-
}
|
|
179
44
|
export async function runCanopus(options) {
|
|
180
45
|
const runId = `run_${randomUUID()}`;
|
|
181
46
|
const paths = await prepareWorkspace({
|
|
@@ -187,9 +52,6 @@ export async function runCanopus(options) {
|
|
|
187
52
|
const activity = await ActivityStore.open(path.join(paths.root, "activity.jsonl"), runId);
|
|
188
53
|
const budget = new BudgetTracker(options.mission.budgets);
|
|
189
54
|
let phase = "initializing";
|
|
190
|
-
let landingCommand;
|
|
191
|
-
let rawLanding;
|
|
192
|
-
let parsedLanding;
|
|
193
55
|
try {
|
|
194
56
|
await activity.append("run.started", {
|
|
195
57
|
mission_id: options.mission.id,
|
|
@@ -197,49 +59,33 @@ export async function runCanopus(options) {
|
|
|
197
59
|
});
|
|
198
60
|
await activity.append("workspace.prepared", {
|
|
199
61
|
input: "input",
|
|
200
|
-
|
|
62
|
+
frontier: "frontier",
|
|
201
63
|
output: "output",
|
|
202
64
|
artifacts: "artifacts",
|
|
203
65
|
});
|
|
204
66
|
await Promise.all([
|
|
205
|
-
options.vela.assertRoots(paths.input, options.mission.frontier, options.mission.roots
|
|
206
|
-
options.vela.assertRoots(paths.
|
|
67
|
+
options.vela.assertRoots(paths.input, options.mission.frontier, options.mission.roots),
|
|
68
|
+
options.vela.assertRoots(paths.frontier, options.mission.frontier, options.mission.roots),
|
|
207
69
|
]);
|
|
208
70
|
await activity.append("roots.verified", { roots: options.mission.roots });
|
|
209
71
|
// Vela's task offer performs recovery-barrier bookkeeping even though it
|
|
210
72
|
// does not change scientific state. Run it in the exact-root control clone;
|
|
211
73
|
// the separate worker input clone stays sealed and read-only.
|
|
212
|
-
const offer = await options.vela.next(options.mission, paths.
|
|
74
|
+
const offer = await options.vela.next(options.mission, paths.frontier);
|
|
213
75
|
const selected = validateTargetOffer(options.mission.target, offer);
|
|
214
76
|
await activity.append("target.offered", {
|
|
215
77
|
target: selected.id,
|
|
216
78
|
rank: selected.index,
|
|
217
79
|
offer_digest: contentDigest(offer.value),
|
|
218
80
|
});
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
await activity.append("work.skipped", {
|
|
228
|
-
target: options.mission.target,
|
|
229
|
-
mode: "no_land",
|
|
230
|
-
reason: "diagnostic run has no landing edge",
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
const work = await options.vela.work(options.mission, paths.landing, options.mission.target, options.mission.roots);
|
|
235
|
-
postWork = await options.vela.inspect(paths.landing, options.mission.frontier, strictBaseline(options.mission));
|
|
236
|
-
assertWorkBinding(options.mission, work, postWork);
|
|
237
|
-
workBriefing = work.value;
|
|
238
|
-
await activity.append("work.claimed", {
|
|
239
|
-
target: options.mission.target,
|
|
240
|
-
roots: postWork.roots,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
81
|
+
// A Run is nonmutating. The rooted offer is the complete bounded briefing;
|
|
82
|
+
// a durable Vela Attempt begins only through an explicit Vela workflow.
|
|
83
|
+
const workBriefing = offer.value;
|
|
84
|
+
await activity.append("work.skipped", {
|
|
85
|
+
target: options.mission.target,
|
|
86
|
+
mode: "no_submit",
|
|
87
|
+
reason: "Canopus run is nonmutating by contract",
|
|
88
|
+
});
|
|
243
89
|
await activity.append("engine.started", {
|
|
244
90
|
engine: options.engine.name,
|
|
245
91
|
role: options.mission.role,
|
|
@@ -279,7 +125,7 @@ export async function runCanopus(options) {
|
|
|
279
125
|
});
|
|
280
126
|
if (engine.draft.status !== "success") {
|
|
281
127
|
phase = "engine_non_success";
|
|
282
|
-
throw new Error(`worker returned ${engine.draft.status}; verifier and
|
|
128
|
+
throw new Error(`worker returned ${engine.draft.status}; verifier and export were not run`);
|
|
283
129
|
}
|
|
284
130
|
await materializeDraftArtifacts({
|
|
285
131
|
draft: engine.draft,
|
|
@@ -333,7 +179,7 @@ export async function runCanopus(options) {
|
|
|
333
179
|
});
|
|
334
180
|
if (verifier.status !== "passed") {
|
|
335
181
|
phase = "verifier_non_success";
|
|
336
|
-
throw new Error(`verifier returned ${verifier.status};
|
|
182
|
+
throw new Error(`verifier returned ${verifier.status}; no Submission can be exported`);
|
|
337
183
|
}
|
|
338
184
|
const candidate = finalizeCandidate({
|
|
339
185
|
mission: options.mission,
|
|
@@ -348,7 +194,7 @@ export async function runCanopus(options) {
|
|
|
348
194
|
candidate_digest: candidateDigest,
|
|
349
195
|
status: candidate.status,
|
|
350
196
|
});
|
|
351
|
-
|
|
197
|
+
{
|
|
352
198
|
phase = "clean_clone_reproduction";
|
|
353
199
|
const reproductionRoot = `${paths.root}-reproduce`;
|
|
354
200
|
const reproductionPaths = await prepareWorkspace({
|
|
@@ -359,7 +205,7 @@ export async function runCanopus(options) {
|
|
|
359
205
|
});
|
|
360
206
|
let reproductionVerifier;
|
|
361
207
|
try {
|
|
362
|
-
await options.vela.assertRoots(reproductionPaths.input, options.mission.frontier, options.mission.roots
|
|
208
|
+
await options.vela.assertRoots(reproductionPaths.input, options.mission.frontier, options.mission.roots);
|
|
363
209
|
reproductionVerifier = await runVerifier({
|
|
364
210
|
mission: options.mission,
|
|
365
211
|
paths: reproductionPaths,
|
|
@@ -376,13 +222,18 @@ export async function runCanopus(options) {
|
|
|
376
222
|
const reproduced = reproductionVerifier.status === verifier.status &&
|
|
377
223
|
reproductionVerifier.record.stdout_digest === verifier.record.stdout_digest &&
|
|
378
224
|
reproductionVerifier.record.stderr_digest === verifier.record.stderr_digest;
|
|
379
|
-
if (!reproduced)
|
|
380
|
-
throw new Error(
|
|
225
|
+
if (!reproduced) {
|
|
226
|
+
throw new Error(`clean-clone verifier replay did not match: initial=${verifier.status}/` +
|
|
227
|
+
`${verifier.record.exit_code}/${verifier.record.stdout_digest}/${verifier.record.stderr_digest}, reproduced=` +
|
|
228
|
+
`${reproductionVerifier.status}/${reproductionVerifier.record.exit_code}/` +
|
|
229
|
+
`${reproductionVerifier.record.stdout_digest}/` +
|
|
230
|
+
`${reproductionVerifier.record.stderr_digest}`);
|
|
231
|
+
}
|
|
381
232
|
const record = {
|
|
382
|
-
schema: "canopus.
|
|
233
|
+
schema: "canopus.run.v2",
|
|
383
234
|
run_id: runId,
|
|
384
235
|
status: "completed",
|
|
385
|
-
|
|
236
|
+
effect: "none",
|
|
386
237
|
authority: "non_authoritative",
|
|
387
238
|
external_gate_credit: false,
|
|
388
239
|
mission: {
|
|
@@ -403,7 +254,7 @@ export async function runCanopus(options) {
|
|
|
403
254
|
sandbox: verifier.sandbox,
|
|
404
255
|
record: verifier.record,
|
|
405
256
|
},
|
|
406
|
-
|
|
257
|
+
submission: null,
|
|
407
258
|
reproduction: {
|
|
408
259
|
matched: true,
|
|
409
260
|
roots: options.mission.roots,
|
|
@@ -414,13 +265,13 @@ export async function runCanopus(options) {
|
|
|
414
265
|
budget: budget.snapshot(),
|
|
415
266
|
};
|
|
416
267
|
const projection = {
|
|
417
|
-
schema: "canopus.
|
|
268
|
+
schema: "canopus.run-projection.v2",
|
|
418
269
|
authority: "read_only_projection",
|
|
419
270
|
run_id: runId,
|
|
420
271
|
target: options.mission.target,
|
|
421
272
|
candidate_digest: candidateDigest,
|
|
422
273
|
verifier_status: verifier.status,
|
|
423
|
-
|
|
274
|
+
submitted: false,
|
|
424
275
|
clean_clone_reproduced: true,
|
|
425
276
|
};
|
|
426
277
|
await rm(paths.velaHome, { recursive: true, force: true });
|
|
@@ -428,239 +279,25 @@ export async function runCanopus(options) {
|
|
|
428
279
|
await writeExclusive(path.join(paths.root, "projection.json"), projection);
|
|
429
280
|
await writeExclusive(path.join(paths.root, "run.json"), record);
|
|
430
281
|
await activity.append("run.completed", {
|
|
431
|
-
|
|
282
|
+
effect: "none",
|
|
432
283
|
candidate_digest: candidateDigest,
|
|
433
284
|
});
|
|
434
285
|
return { record, projection, paths };
|
|
435
286
|
}
|
|
436
|
-
if (postWork === undefined) {
|
|
437
|
-
throw new Error("landing run omitted the required Vela work lease");
|
|
438
|
-
}
|
|
439
|
-
const preLand = await options.vela.assertRoots(paths.landing, options.mission.frontier, postWork.roots, strictBaseline(options.mission));
|
|
440
|
-
// Verify the complete Vela state before placing candidate artifacts. The
|
|
441
|
-
// artifact files intentionally make vela.lock stale until `vela land`
|
|
442
|
-
// admits them transactionally, so a strict check between installation and
|
|
443
|
-
// the porcelain command would reject the ordinary authored-receipt flow.
|
|
444
|
-
await installFrozenArtifacts({
|
|
445
|
-
landingRepo: paths.landing,
|
|
446
|
-
frontier: options.mission.frontier,
|
|
447
|
-
frozen,
|
|
448
|
-
maxBytes: options.mission.budgets.max_artifact_bytes,
|
|
449
|
-
});
|
|
450
|
-
const artifactPublication = await publishArtifactSources({
|
|
451
|
-
repoRoot: paths.landing,
|
|
452
|
-
frontier: options.mission.frontier,
|
|
453
|
-
artifacts: candidate.artifacts,
|
|
454
|
-
home: paths.velaHome,
|
|
455
|
-
});
|
|
456
|
-
await activity.append("artifacts.published", {
|
|
457
|
-
authority: "non_authoritative_git_publication",
|
|
458
|
-
...artifactPublication,
|
|
459
|
-
});
|
|
460
|
-
const receipt = mapCandidateToReceipt(options.mission, candidate, verifier, options.mission.target);
|
|
461
|
-
await activity.append("receipt.mapped", {
|
|
462
|
-
candidate_digest: candidateDigest,
|
|
463
|
-
evidence: receipt.evidence,
|
|
464
|
-
counterevidence: receipt.counterevidence,
|
|
465
|
-
});
|
|
466
|
-
phase = "landing_command";
|
|
467
|
-
landingCommand = await options.vela.landAuthoredCommand(options.mission, paths.landing, receipt, preLand.roots);
|
|
468
|
-
await writeExclusive(path.join(paths.root, "landing-command.json"), {
|
|
469
|
-
schema: "canopus.landing-command.v0",
|
|
470
|
-
authority: "observed_process_effect",
|
|
471
|
-
...landingCommand,
|
|
472
|
-
});
|
|
473
|
-
rawLanding = options.vela.parseLandCommand(landingCommand);
|
|
474
|
-
const rawLandingDigest = contentDigest(rawLanding);
|
|
475
|
-
await writeExclusive(path.join(paths.root, "landing-observation.json"), {
|
|
476
|
-
schema: "canopus.landing-observation.v0",
|
|
477
|
-
authority: "observed_vela_effect",
|
|
478
|
-
raw_digest: rawLandingDigest,
|
|
479
|
-
raw: rawLanding,
|
|
480
|
-
});
|
|
481
|
-
await activity.append("landing.observed", {
|
|
482
|
-
raw_digest: rawLandingDigest,
|
|
483
|
-
observation: "landing-observation.json",
|
|
484
|
-
});
|
|
485
|
-
phase = "landing_validation";
|
|
486
|
-
const landing = options.vela.validateLandResult(options.mission, rawLanding);
|
|
487
|
-
parsedLanding = landing;
|
|
488
|
-
phase = "receipt_binding";
|
|
489
|
-
await options.vela.verifyReceiptBinding(options.mission, paths.landing, landing, receipt, candidate.artifacts);
|
|
490
|
-
await activity.append("landing.bound", {
|
|
491
|
-
receipt_root: landing.receiptRoot,
|
|
492
|
-
raw_digest: rawLandingDigest,
|
|
493
|
-
});
|
|
494
|
-
await activity.append("landing.completed", {
|
|
495
|
-
receipt_root: landing.receiptRoot,
|
|
496
|
-
proposal_id: landing.proposalId,
|
|
497
|
-
route: landing.route,
|
|
498
|
-
original_route: landing.originalRoute,
|
|
499
|
-
accepted_event_delta: landing.acceptedEventDelta,
|
|
500
|
-
publication_state: publicationState(landing),
|
|
501
|
-
});
|
|
502
|
-
const final = await options.vela.inspect(paths.landing, options.mission.frontier, strictBaseline(options.mission));
|
|
503
|
-
phase = "clean_clone_reproduction";
|
|
504
|
-
const reproductionRoot = `${paths.root}-reproduce`;
|
|
505
|
-
const reproductionPaths = await prepareWorkspace({
|
|
506
|
-
sourceRepo: paths.landing,
|
|
507
|
-
runRoot: reproductionRoot,
|
|
508
|
-
gitCommit: final.roots.git_commit,
|
|
509
|
-
gitTree: final.roots.git_tree,
|
|
510
|
-
});
|
|
511
|
-
let reproductionVerifier;
|
|
512
|
-
let reproductionInspection;
|
|
513
|
-
try {
|
|
514
|
-
reproductionInspection = await options.vela.assertRoots(reproductionPaths.input, options.mission.frontier, final.roots, strictBaseline(options.mission));
|
|
515
|
-
await options.vela.verifyReceiptBinding(options.mission, reproductionPaths.input, landing, receipt, candidate.artifacts);
|
|
516
|
-
const reproducedArtifacts = candidate.artifacts.map((artifact) => ({
|
|
517
|
-
artifact,
|
|
518
|
-
frozenPath: retainedArtifactPath(reproductionPaths.input, options.mission.frontier, artifact.digest),
|
|
519
|
-
}));
|
|
520
|
-
reproductionVerifier = await runVerifier({
|
|
521
|
-
mission: options.mission,
|
|
522
|
-
paths: reproductionPaths,
|
|
523
|
-
artifacts: reproducedArtifacts,
|
|
524
|
-
budget,
|
|
525
|
-
...(options.bundleRoot === undefined ? {} : { bundleRoot: options.bundleRoot }),
|
|
526
|
-
...(options.dockerBinary === undefined ? {} : { dockerBinary: options.dockerBinary }),
|
|
527
|
-
...(options.verifierRunner === undefined ? {} : { runner: options.verifierRunner }),
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
finally {
|
|
531
|
-
await cleanupWorkspace(reproductionPaths);
|
|
532
|
-
}
|
|
533
|
-
const reproduced = sameRoots(reproductionInspection.roots, final.roots) &&
|
|
534
|
-
reproductionVerifier.status === verifier.status &&
|
|
535
|
-
reproductionVerifier.record.stdout_digest === verifier.record.stdout_digest &&
|
|
536
|
-
reproductionVerifier.record.stderr_digest === verifier.record.stderr_digest;
|
|
537
|
-
if (!reproduced) {
|
|
538
|
-
throw new Error("clean-clone reproduction did not match the landed run");
|
|
539
|
-
}
|
|
540
|
-
await activity.append("reproduction.completed", {
|
|
541
|
-
matched: true,
|
|
542
|
-
roots: reproductionInspection.roots,
|
|
543
|
-
verifier_status: reproductionVerifier.status,
|
|
544
|
-
});
|
|
545
|
-
await rm(paths.velaHome, { recursive: true, force: true });
|
|
546
|
-
const record = {
|
|
547
|
-
schema: RUN_RECORD_SCHEMA,
|
|
548
|
-
run_id: runId,
|
|
549
|
-
status: "completed",
|
|
550
|
-
authority: "non_authoritative",
|
|
551
|
-
external_gate_credit: false,
|
|
552
|
-
mission: {
|
|
553
|
-
id: options.mission.id,
|
|
554
|
-
target: options.mission.target,
|
|
555
|
-
digest: contentDigest(options.mission),
|
|
556
|
-
starting_roots: options.mission.roots,
|
|
557
|
-
},
|
|
558
|
-
candidate: {
|
|
559
|
-
digest: candidateDigest,
|
|
560
|
-
status: candidate.status,
|
|
561
|
-
claim: candidate.claim,
|
|
562
|
-
artifacts: candidate.artifacts,
|
|
563
|
-
caveats: candidate.caveats,
|
|
564
|
-
},
|
|
565
|
-
observations: {
|
|
566
|
-
worker_observations: candidate.observations,
|
|
567
|
-
verifier_observations: [
|
|
568
|
-
`Frozen verifier passed in ${verifier.sandbox}.`,
|
|
569
|
-
`Verifier stdout ${verifier.record.stdout_digest}; stderr ${verifier.record.stderr_digest}.`,
|
|
570
|
-
],
|
|
571
|
-
standing_caveats: candidate.caveats,
|
|
572
|
-
},
|
|
573
|
-
verifier: {
|
|
574
|
-
status: verifier.status,
|
|
575
|
-
sandbox: verifier.sandbox,
|
|
576
|
-
record: verifier.record,
|
|
577
|
-
},
|
|
578
|
-
landing: {
|
|
579
|
-
operation_id: landing.operationId,
|
|
580
|
-
receipt_root: landing.receiptRoot,
|
|
581
|
-
proposal_id: landing.proposalId,
|
|
582
|
-
route: landing.route,
|
|
583
|
-
original_route: landing.originalRoute,
|
|
584
|
-
accepted_event_delta: landing.acceptedEventDelta,
|
|
585
|
-
publication_state: publicationState(landing),
|
|
586
|
-
},
|
|
587
|
-
final_roots: final.roots,
|
|
588
|
-
reproduction: {
|
|
589
|
-
matched: reproduced,
|
|
590
|
-
roots: reproductionInspection.roots,
|
|
591
|
-
verifier_status: reproductionVerifier.status,
|
|
592
|
-
stdout_digest: reproductionVerifier.record.stdout_digest,
|
|
593
|
-
stderr_digest: reproductionVerifier.record.stderr_digest,
|
|
594
|
-
},
|
|
595
|
-
budget: budget.snapshot(),
|
|
596
|
-
};
|
|
597
|
-
const projection = projectRun(record);
|
|
598
|
-
phase = "record_finalization";
|
|
599
|
-
await writeExclusive(path.join(paths.root, "candidate.json"), candidate);
|
|
600
|
-
await writeExclusive(path.join(paths.root, "projection.json"), projection);
|
|
601
|
-
await writeExclusive(path.join(paths.root, "run.json"), record);
|
|
602
|
-
await activity.append("projection.written", {
|
|
603
|
-
candidate_digest: candidateDigest,
|
|
604
|
-
run_digest: contentDigest(record),
|
|
605
|
-
projection_digest: contentDigest(projection),
|
|
606
|
-
});
|
|
607
|
-
await activity.append("run.completed", {
|
|
608
|
-
receipt_root: landing.receiptRoot,
|
|
609
|
-
candidate_digest: candidateDigest,
|
|
610
|
-
});
|
|
611
|
-
return { record, projection, paths };
|
|
612
287
|
}
|
|
613
288
|
catch (error) {
|
|
614
289
|
const message = error instanceof Error ? error.message : String(error);
|
|
615
|
-
let recovery;
|
|
616
|
-
if (phase.startsWith("landing_") ||
|
|
617
|
-
phase === "receipt_binding" ||
|
|
618
|
-
phase === "clean_clone_reproduction" ||
|
|
619
|
-
phase === "record_finalization") {
|
|
620
|
-
let observedRoots = null;
|
|
621
|
-
let inspectionError = null;
|
|
622
|
-
try {
|
|
623
|
-
observedRoots = (await options.vela.inspect(paths.landing, options.mission.frontier, strictBaseline(options.mission))).roots;
|
|
624
|
-
}
|
|
625
|
-
catch (inspectionFailure) {
|
|
626
|
-
inspectionError =
|
|
627
|
-
inspectionFailure instanceof Error ? inspectionFailure.message : String(inspectionFailure);
|
|
628
|
-
}
|
|
629
|
-
recovery = {
|
|
630
|
-
schema: "canopus.landing-recovery.v0",
|
|
631
|
-
authority: "observed_vela_effect",
|
|
632
|
-
phase,
|
|
633
|
-
command: landingCommand ?? null,
|
|
634
|
-
raw_digest: rawLanding === undefined ? null : contentDigest(rawLanding),
|
|
635
|
-
raw: rawLanding ?? null,
|
|
636
|
-
parsed: parsedLanding === undefined
|
|
637
|
-
? null
|
|
638
|
-
: {
|
|
639
|
-
operation_id: parsedLanding.operationId,
|
|
640
|
-
receipt_root: parsedLanding.receiptRoot,
|
|
641
|
-
proposal_id: parsedLanding.proposalId,
|
|
642
|
-
route: parsedLanding.route,
|
|
643
|
-
original_route: parsedLanding.originalRoute,
|
|
644
|
-
accepted_event_delta: parsedLanding.acceptedEventDelta,
|
|
645
|
-
publication: parsedLanding.publication,
|
|
646
|
-
},
|
|
647
|
-
observed_roots: observedRoots,
|
|
648
|
-
inspection_error: inspectionError,
|
|
649
|
-
};
|
|
650
|
-
await writeExclusive(path.join(paths.root, "landing-recovery.json"), recovery).catch(() => undefined);
|
|
651
|
-
}
|
|
652
290
|
await activity.append("run.failed", {
|
|
653
291
|
error: message,
|
|
654
292
|
phase,
|
|
655
|
-
|
|
293
|
+
effect: "none",
|
|
656
294
|
}).catch(() => undefined);
|
|
657
295
|
await writeExclusive(path.join(paths.root, "failure.json"), {
|
|
658
|
-
schema: "canopus.failure.
|
|
296
|
+
schema: "canopus.failure.v1",
|
|
659
297
|
run_id: runId,
|
|
660
298
|
error: message,
|
|
661
299
|
phase,
|
|
662
|
-
|
|
663
|
-
landing_recovery: recovery === undefined ? null : "landing-recovery.json",
|
|
300
|
+
effect: "none",
|
|
664
301
|
activity_tip: activity.tip,
|
|
665
302
|
authority: "non_authoritative",
|
|
666
303
|
}).catch(() => undefined);
|
|
@@ -1,5 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export declare function canonicalJcs(value: unknown): string;
|
|
3
|
-
export declare function sha256Bytes(value: Uint8Array | string): string;
|
|
4
|
-
export declare function contentDigest(value: unknown): string;
|
|
5
|
-
export declare function protocolDigest(value: unknown): string;
|
|
1
|
+
export { canonicalJcs, canonicalJson, contentDigest, protocolDigest, sha256Bytes, } from "@vela-science/protocol";
|
|
@@ -1,53 +1 @@
|
|
|
1
|
-
|
|
2
|
-
function normalize(value, at) {
|
|
3
|
-
if (value === null ||
|
|
4
|
-
typeof value === "string" ||
|
|
5
|
-
typeof value === "boolean") {
|
|
6
|
-
return value;
|
|
7
|
-
}
|
|
8
|
-
if (typeof value === "number") {
|
|
9
|
-
if (!Number.isFinite(value)) {
|
|
10
|
-
throw new TypeError(`${at} contains a non-finite number`);
|
|
11
|
-
}
|
|
12
|
-
return value;
|
|
13
|
-
}
|
|
14
|
-
if (Array.isArray(value)) {
|
|
15
|
-
return value.map((item, index) => normalize(item, `${at}[${index}]`));
|
|
16
|
-
}
|
|
17
|
-
if (typeof value === "object") {
|
|
18
|
-
const prototype = Object.getPrototypeOf(value);
|
|
19
|
-
if (prototype !== Object.prototype && prototype !== null) {
|
|
20
|
-
throw new TypeError(`${at} must contain plain JSON objects`);
|
|
21
|
-
}
|
|
22
|
-
const input = value;
|
|
23
|
-
const output = {};
|
|
24
|
-
for (const key of Object.keys(input).sort()) {
|
|
25
|
-
const item = input[key];
|
|
26
|
-
if (item === undefined) {
|
|
27
|
-
throw new TypeError(`${at}.${key} is undefined`);
|
|
28
|
-
}
|
|
29
|
-
output[key] = normalize(item, `${at}.${key}`);
|
|
30
|
-
}
|
|
31
|
-
return output;
|
|
32
|
-
}
|
|
33
|
-
throw new TypeError(`${at} contains a non-JSON value`);
|
|
34
|
-
}
|
|
35
|
-
export function canonicalJson(value) {
|
|
36
|
-
return `${JSON.stringify(normalize(value, "$"))}\n`;
|
|
37
|
-
}
|
|
38
|
-
// RFC 8785 uses ECMAScript JSON serialization over recursively sorted object
|
|
39
|
-
// keys and does not append a record newline. Vela Receipt v1 uses these bytes.
|
|
40
|
-
export function canonicalJcs(value) {
|
|
41
|
-
return JSON.stringify(normalize(value, "$"));
|
|
42
|
-
}
|
|
43
|
-
export function sha256Bytes(value) {
|
|
44
|
-
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
45
|
-
}
|
|
46
|
-
export function contentDigest(value) {
|
|
47
|
-
return sha256Bytes(canonicalJson(value));
|
|
48
|
-
}
|
|
49
|
-
// Vela protocol objects use compact canonical JSON bytes, without the record
|
|
50
|
-
// newline used by Canopus-owned manifests and activity records.
|
|
51
|
-
export function protocolDigest(value) {
|
|
52
|
-
return sha256Bytes(canonicalJcs(value));
|
|
53
|
-
}
|
|
1
|
+
export { canonicalJcs, canonicalJson, contentDigest, protocolDigest, sha256Bytes, } from "@vela-science/protocol";
|