@vela-science/canopus 0.4.0-rc.1
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 +249 -0
- package/LICENSE-APACHE +176 -0
- package/LICENSE-MIT +21 -0
- package/README.md +124 -0
- package/SECURITY.md +35 -0
- package/capsules/erdos1056-k15/README.md +35 -0
- package/capsules/erdos1056-k15/bin/linux-arm64/10428401-10428600/verifier +0 -0
- package/capsules/erdos1056-k15/bin/linux-x86_64/10428401-10428600/verifier +0 -0
- package/capsules/formal-erdos-505-test-dim-one/README.md +20 -0
- package/capsules/formal-erdos-505-test-dim-one/verifier +105 -0
- package/dist/src/activity/events.d.ts +15 -0
- package/dist/src/activity/events.js +24 -0
- package/dist/src/activity/store.d.ts +10 -0
- package/dist/src/activity/store.js +165 -0
- package/dist/src/artifact/freeze.d.ts +21 -0
- package/dist/src/artifact/freeze.js +177 -0
- package/dist/src/artifact/materialize.d.ts +6 -0
- package/dist/src/artifact/materialize.js +29 -0
- package/dist/src/budget/enforce.d.ts +30 -0
- package/dist/src/budget/enforce.js +98 -0
- package/dist/src/candidate/validate.d.ts +3 -0
- package/dist/src/candidate/validate.js +32 -0
- package/dist/src/capability/withdrawal.d.ts +47 -0
- package/dist/src/capability/withdrawal.js +487 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +503 -0
- package/dist/src/contracts/candidate.d.ts +50 -0
- package/dist/src/contracts/candidate.js +93 -0
- package/dist/src/contracts/mission.d.ts +139 -0
- package/dist/src/contracts/mission.js +401 -0
- package/dist/src/contracts/validation.d.ts +25 -0
- package/dist/src/contracts/validation.js +95 -0
- package/dist/src/engines/codex-events.d.ts +25 -0
- package/dist/src/engines/codex-events.js +219 -0
- package/dist/src/engines/codex-exec.d.ts +17 -0
- package/dist/src/engines/codex-exec.js +136 -0
- package/dist/src/engines/codex-home.d.ts +7 -0
- package/dist/src/engines/codex-home.js +50 -0
- package/dist/src/engines/codex-tool-free.d.ts +26 -0
- package/dist/src/engines/codex-tool-free.js +213 -0
- package/dist/src/engines/codex-tools-native.d.ts +30 -0
- package/dist/src/engines/codex-tools-native.js +405 -0
- package/dist/src/engines/engine.d.ts +55 -0
- package/dist/src/engines/engine.js +4 -0
- package/dist/src/engines/fake.d.ts +7 -0
- package/dist/src/engines/fake.js +37 -0
- package/dist/src/evidence/manifests.d.ts +9 -0
- package/dist/src/evidence/manifests.js +56 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.js +32 -0
- package/dist/src/mission/prepare.d.ts +33 -0
- package/dist/src/mission/prepare.js +375 -0
- package/dist/src/product/custody.d.ts +27 -0
- package/dist/src/product/custody.js +131 -0
- package/dist/src/product/doctor.d.ts +61 -0
- package/dist/src/product/doctor.js +278 -0
- package/dist/src/product/profile-bundle.d.ts +21 -0
- package/dist/src/product/profile-bundle.js +78 -0
- package/dist/src/product/profile.d.ts +67 -0
- package/dist/src/product/profile.js +251 -0
- package/dist/src/product/replay.d.ts +10 -0
- package/dist/src/product/replay.js +71 -0
- package/dist/src/product/run.d.ts +31 -0
- package/dist/src/product/run.js +264 -0
- package/dist/src/product/runtime.d.ts +18 -0
- package/dist/src/product/runtime.js +64 -0
- package/dist/src/product/version.d.ts +1 -0
- package/dist/src/product/version.js +6 -0
- package/dist/src/product/withdraw.d.ts +8 -0
- package/dist/src/product/withdraw.js +255 -0
- package/dist/src/projection/diagnostic.d.ts +12 -0
- package/dist/src/projection/diagnostic.js +61 -0
- package/dist/src/projection/failure.d.ts +26 -0
- package/dist/src/projection/failure.js +63 -0
- package/dist/src/projection/run.d.ts +89 -0
- package/dist/src/projection/run.js +159 -0
- package/dist/src/receipt/map.d.ts +25 -0
- package/dist/src/receipt/map.js +162 -0
- package/dist/src/repair/plan.d.ts +2 -0
- package/dist/src/repair/plan.js +22 -0
- package/dist/src/roles.d.ts +2 -0
- package/dist/src/roles.js +9 -0
- package/dist/src/run.d.ts +94 -0
- package/dist/src/run.js +654 -0
- package/dist/src/util/canonical.d.ts +5 -0
- package/dist/src/util/canonical.js +53 -0
- package/dist/src/util/command.d.ts +26 -0
- package/dist/src/util/command.js +161 -0
- package/dist/src/util/files.d.ts +3 -0
- package/dist/src/util/files.js +65 -0
- package/dist/src/vela/cli.d.ts +71 -0
- package/dist/src/vela/cli.js +718 -0
- package/dist/src/vela/types.d.ts +27 -0
- package/dist/src/vela/types.js +1 -0
- package/dist/src/verifier/run.d.ts +24 -0
- package/dist/src/verifier/run.js +315 -0
- package/dist/src/workspace/prepare.d.ts +30 -0
- package/dist/src/workspace/prepare.js +183 -0
- package/docs/MISSIONS.md +78 -0
- package/docs/RELEASES.md +1036 -0
- package/docs/RUN_RECORD.md +70 -0
- package/missions/erdos1056-k15-next/mission.draft.json +40 -0
- package/missions/formal-erdos-505-test-dim-one/mission.draft.json +40 -0
- package/package.json +89 -0
- package/profiles/erdos1056-k15-10428401-10428600.json +33 -0
- package/profiles/formal-erdos-505-test-dim-one.json +35 -0
- package/runtime/native-worker/config-linux.toml +20 -0
- package/runtime/native-worker/config.toml +17 -0
- package/schemas/candidate.v0.json +125 -0
- package/schemas/engine-manifest.v0.json +45 -0
- package/schemas/engine-output.v0.json +41 -0
- package/schemas/mission.v0.json +175 -0
- package/schemas/mission.v1.json +224 -0
- package/schemas/verifier-manifest.v0.json +48 -0
- package/schemas/verifier-manifest.v1.json +59 -0
- package/scripts/run-hostile-native-custody-fixture.mjs +382 -0
- package/scripts/run-hostile-verifier-fixture.mjs +63 -0
- package/tests/fixtures/hostile-verifier/probe.py +35 -0
package/dist/src/run.js
ADDED
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { ActivityStore } from "./activity/store.js";
|
|
5
|
+
import { freezeArtifact, sealArtifactStore, } from "./artifact/freeze.js";
|
|
6
|
+
import { materializeDraftArtifacts } from "./artifact/materialize.js";
|
|
7
|
+
import { BudgetTracker } from "./budget/enforce.js";
|
|
8
|
+
import { engineManifest, verifierManifest } from "./evidence/manifests.js";
|
|
9
|
+
import { projectRun, RUN_RECORD_SCHEMA } from "./projection/run.js";
|
|
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";
|
|
14
|
+
import { runVerifier } from "./verifier/run.js";
|
|
15
|
+
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
|
+
function exactText(value, expected, at) {
|
|
33
|
+
if (value !== expected) {
|
|
34
|
+
throw new Error(`${at} mismatch: expected ${expected}, observed ${String(value)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
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
|
+
export function validateTargetOffer(target, response) {
|
|
76
|
+
exactText(response.value.command, "next", "vela next.command");
|
|
77
|
+
const targets = response.value.targets;
|
|
78
|
+
if (!Array.isArray(targets))
|
|
79
|
+
throw new Error("vela next.targets is not an array");
|
|
80
|
+
const matches = [];
|
|
81
|
+
for (const [index, entry] of targets.entries()) {
|
|
82
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
83
|
+
throw new Error(`vela next.targets[${index}] is not an object`);
|
|
84
|
+
}
|
|
85
|
+
const object = entry;
|
|
86
|
+
const id = object.target_id ?? object.id;
|
|
87
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
88
|
+
throw new Error(`vela next.targets[${index}].id is not a nonempty string`);
|
|
89
|
+
}
|
|
90
|
+
if (id === target)
|
|
91
|
+
matches.push({ index, id });
|
|
92
|
+
}
|
|
93
|
+
if (matches.length !== 1) {
|
|
94
|
+
throw new Error(`registered mission target must appear exactly once in vela next; observed ${matches.length}`);
|
|
95
|
+
}
|
|
96
|
+
return matches[0];
|
|
97
|
+
}
|
|
98
|
+
function publicationState(land) {
|
|
99
|
+
const value = land.publication.state;
|
|
100
|
+
return typeof value === "string" ? value : "unknown";
|
|
101
|
+
}
|
|
102
|
+
async function writeExclusive(file, value) {
|
|
103
|
+
await writeFile(file, canonicalJson(value), { flag: "wx", mode: 0o600 });
|
|
104
|
+
}
|
|
105
|
+
async function publishArtifactSources(options) {
|
|
106
|
+
const frontierRoot = path.resolve(options.repoRoot, options.frontier);
|
|
107
|
+
const paths = [...new Set(options.artifacts.map((artifact) => {
|
|
108
|
+
const absolute = path.resolve(frontierRoot, artifact.path);
|
|
109
|
+
const relative = path.relative(options.repoRoot, absolute);
|
|
110
|
+
if (relative === "" ||
|
|
111
|
+
relative === ".." ||
|
|
112
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
113
|
+
path.isAbsolute(relative)) {
|
|
114
|
+
throw new Error(`artifact publication path escapes the landing repository: ${artifact.path}`);
|
|
115
|
+
}
|
|
116
|
+
return relative;
|
|
117
|
+
}))].sort();
|
|
118
|
+
const environment = isolatedEnvironment(options.home);
|
|
119
|
+
const git = async (argv) => {
|
|
120
|
+
const result = await runCommand({
|
|
121
|
+
argv: ["git", ...argv],
|
|
122
|
+
cwd: options.repoRoot,
|
|
123
|
+
env: environment,
|
|
124
|
+
timeoutMs: 30_000,
|
|
125
|
+
maxOutputBytes: 1_048_576,
|
|
126
|
+
});
|
|
127
|
+
if (result.exitCode !== 0 || result.stderr.length !== 0) {
|
|
128
|
+
throw new Error(`artifact publication git command failed: exit=${result.exitCode}; ` +
|
|
129
|
+
`stdout=${sha256Bytes(result.stdout)}; stderr=${sha256Bytes(result.stderr)}`);
|
|
130
|
+
}
|
|
131
|
+
return result.stdout;
|
|
132
|
+
};
|
|
133
|
+
await git(["add", "--force", "--", ...paths]);
|
|
134
|
+
const staged = (await git(["diff", "--cached", "--name-only", "-z"]))
|
|
135
|
+
.toString("utf8").split("\0").filter((entry) => entry.length > 0).sort();
|
|
136
|
+
if (canonicalJson(staged) !== canonicalJson(paths)) {
|
|
137
|
+
throw new Error("artifact publication staged a path outside the exact frozen artifact set");
|
|
138
|
+
}
|
|
139
|
+
const status = (await git(["status", "--porcelain=v1", "-z", "--untracked-files=all"]))
|
|
140
|
+
.toString("utf8").split("\0").filter((entry) => entry.length > 0).sort();
|
|
141
|
+
const expectedStatus = paths.map((entry) => `A ${entry}`).sort();
|
|
142
|
+
if (canonicalJson(status) !== canonicalJson(expectedStatus)) {
|
|
143
|
+
throw new Error("artifact publication observed unrelated or unstaged repository changes");
|
|
144
|
+
}
|
|
145
|
+
await git([
|
|
146
|
+
"-c", "user.name=Canopus Agent",
|
|
147
|
+
"-c", "user.email=canopus-agent@invalid.example",
|
|
148
|
+
"commit", "--no-gpg-sign", "-m", "canopus: publish verified mission artifacts",
|
|
149
|
+
]);
|
|
150
|
+
const commit = (await git(["rev-parse", "--verify", "HEAD^{commit}"])).toString("utf8").trim();
|
|
151
|
+
const tree = (await git(["rev-parse", "--verify", "HEAD^{tree}"])).toString("utf8").trim();
|
|
152
|
+
if (!/^[0-9a-f]{40}$/u.test(commit) || !/^[0-9a-f]{40}$/u.test(tree)) {
|
|
153
|
+
throw new Error("artifact publication returned malformed Git object IDs");
|
|
154
|
+
}
|
|
155
|
+
return { commit, tree, paths };
|
|
156
|
+
}
|
|
157
|
+
export async function runCanopus(options) {
|
|
158
|
+
const runId = `run_${randomUUID()}`;
|
|
159
|
+
const paths = await prepareWorkspace({
|
|
160
|
+
sourceRepo: options.sourceRepo,
|
|
161
|
+
runRoot: options.runRoot,
|
|
162
|
+
gitCommit: options.mission.roots.git_commit,
|
|
163
|
+
gitTree: options.mission.roots.git_tree,
|
|
164
|
+
});
|
|
165
|
+
const activity = await ActivityStore.open(path.join(paths.root, "activity.jsonl"), runId);
|
|
166
|
+
const budget = new BudgetTracker(options.mission.budgets);
|
|
167
|
+
let phase = "initializing";
|
|
168
|
+
let landingCommand;
|
|
169
|
+
let rawLanding;
|
|
170
|
+
let parsedLanding;
|
|
171
|
+
try {
|
|
172
|
+
await activity.append("run.started", {
|
|
173
|
+
mission_id: options.mission.id,
|
|
174
|
+
mission_digest: contentDigest(options.mission),
|
|
175
|
+
});
|
|
176
|
+
await activity.append("workspace.prepared", {
|
|
177
|
+
input: "input",
|
|
178
|
+
landing: "landing",
|
|
179
|
+
output: "output",
|
|
180
|
+
artifacts: "artifacts",
|
|
181
|
+
});
|
|
182
|
+
await Promise.all([
|
|
183
|
+
options.vela.assertRoots(paths.input, options.mission.frontier, options.mission.roots, strictBaseline(options.mission)),
|
|
184
|
+
options.vela.assertRoots(paths.landing, options.mission.frontier, options.mission.roots, strictBaseline(options.mission)),
|
|
185
|
+
]);
|
|
186
|
+
await activity.append("roots.verified", { roots: options.mission.roots });
|
|
187
|
+
// Vela's task offer performs recovery-barrier bookkeeping even though it
|
|
188
|
+
// does not change scientific state. Run it in the exact-root control clone;
|
|
189
|
+
// the separate worker input clone stays sealed and read-only.
|
|
190
|
+
const offer = await options.vela.next(options.mission, paths.landing);
|
|
191
|
+
const selected = validateTargetOffer(options.mission.target, offer);
|
|
192
|
+
await activity.append("target.offered", {
|
|
193
|
+
target: selected.id,
|
|
194
|
+
rank: selected.index,
|
|
195
|
+
offer_digest: contentDigest(offer.value),
|
|
196
|
+
});
|
|
197
|
+
let workBriefing;
|
|
198
|
+
let postWork;
|
|
199
|
+
if (options.noLand === true) {
|
|
200
|
+
// Diagnostic mode has no landing edge and therefore needs no durable
|
|
201
|
+
// lease. Avoiding `vela work` keeps the disposable clone at the exact
|
|
202
|
+
// registered roots and prevents an operational attempt event from
|
|
203
|
+
// needlessly invalidating a previously exported proof packet.
|
|
204
|
+
workBriefing = offer.value;
|
|
205
|
+
await activity.append("work.skipped", {
|
|
206
|
+
target: options.mission.target,
|
|
207
|
+
mode: "no_land",
|
|
208
|
+
reason: "diagnostic run has no landing edge",
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
const work = await options.vela.work(options.mission, paths.landing, options.mission.target, options.mission.roots);
|
|
213
|
+
postWork = await options.vela.inspect(paths.landing, options.mission.frontier, strictBaseline(options.mission));
|
|
214
|
+
assertWorkBinding(options.mission, work, postWork);
|
|
215
|
+
workBriefing = work.value;
|
|
216
|
+
await activity.append("work.claimed", {
|
|
217
|
+
target: options.mission.target,
|
|
218
|
+
roots: postWork.roots,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
await activity.append("engine.started", {
|
|
222
|
+
engine: options.engine.name,
|
|
223
|
+
role: options.mission.role,
|
|
224
|
+
});
|
|
225
|
+
const engine = await options.engine.run({
|
|
226
|
+
mission: options.mission,
|
|
227
|
+
briefing: workBriefing,
|
|
228
|
+
paths,
|
|
229
|
+
budget,
|
|
230
|
+
});
|
|
231
|
+
await activity.append("engine.completed", {
|
|
232
|
+
status: engine.draft.status,
|
|
233
|
+
claim: engine.draft.claim,
|
|
234
|
+
observations: engine.draft.observations,
|
|
235
|
+
caveats: engine.draft.caveats,
|
|
236
|
+
declared_artifacts: engine.draft.artifacts.map((artifact) => ({
|
|
237
|
+
path: artifact.path,
|
|
238
|
+
kind: artifact.kind,
|
|
239
|
+
bytes: Buffer.byteLength(artifact.content),
|
|
240
|
+
})),
|
|
241
|
+
engine: engine.engine,
|
|
242
|
+
usage: engine.usage,
|
|
243
|
+
events_digest: engine.eventsDigest,
|
|
244
|
+
action_types: engine.actionTypes,
|
|
245
|
+
});
|
|
246
|
+
await writeExclusive(path.join(paths.root, "engine-result.json"), {
|
|
247
|
+
schema: "canopus.engine-result.v0",
|
|
248
|
+
authority: "non_authoritative",
|
|
249
|
+
draft: engine.draft,
|
|
250
|
+
engine: engine.engine,
|
|
251
|
+
usage: engine.usage,
|
|
252
|
+
wall_time_ms: engine.wallTimeMs,
|
|
253
|
+
event_types: engine.eventTypes,
|
|
254
|
+
action_types: engine.actionTypes,
|
|
255
|
+
events_digest: engine.eventsDigest,
|
|
256
|
+
stderr_digest: engine.stderrDigest,
|
|
257
|
+
});
|
|
258
|
+
if (engine.draft.status !== "success") {
|
|
259
|
+
phase = "engine_non_success";
|
|
260
|
+
throw new Error(`worker returned ${engine.draft.status}; verifier and landing were not run`);
|
|
261
|
+
}
|
|
262
|
+
await materializeDraftArtifacts({
|
|
263
|
+
draft: engine.draft,
|
|
264
|
+
outputRoot: paths.output,
|
|
265
|
+
maxTotalBytes: options.mission.budgets.max_artifact_bytes,
|
|
266
|
+
});
|
|
267
|
+
const frozen = [];
|
|
268
|
+
for (const artifact of engine.draft.artifacts) {
|
|
269
|
+
const entry = await freezeArtifact({
|
|
270
|
+
sourceRoot: paths.output,
|
|
271
|
+
artifactRoot: paths.artifacts,
|
|
272
|
+
path: artifact.path,
|
|
273
|
+
kind: artifact.kind,
|
|
274
|
+
maxBytes: budget.remainingArtifactBytes(),
|
|
275
|
+
});
|
|
276
|
+
budget.addArtifact(entry.artifact.bytes);
|
|
277
|
+
frozen.push(entry);
|
|
278
|
+
await activity.append("artifact.frozen", { artifact: entry.artifact });
|
|
279
|
+
}
|
|
280
|
+
const supporting = [engineManifest(engine), verifierManifest(options.mission)];
|
|
281
|
+
for (const manifest of supporting) {
|
|
282
|
+
const source = path.join(paths.output, manifest.path);
|
|
283
|
+
await mkdir(path.dirname(source), { recursive: true, mode: 0o700 });
|
|
284
|
+
await writeExclusive(source, manifest.value);
|
|
285
|
+
const entry = await freezeArtifact({
|
|
286
|
+
sourceRoot: paths.output,
|
|
287
|
+
artifactRoot: paths.artifacts,
|
|
288
|
+
path: manifest.path,
|
|
289
|
+
kind: manifest.kind,
|
|
290
|
+
maxBytes: budget.remainingArtifactBytes(),
|
|
291
|
+
});
|
|
292
|
+
budget.addArtifact(entry.artifact.bytes);
|
|
293
|
+
frozen.push(entry);
|
|
294
|
+
await activity.append("artifact.frozen", { artifact: entry.artifact });
|
|
295
|
+
}
|
|
296
|
+
await sealArtifactStore(paths.artifacts);
|
|
297
|
+
const verifier = await runVerifier({
|
|
298
|
+
mission: options.mission,
|
|
299
|
+
paths,
|
|
300
|
+
artifacts: frozen,
|
|
301
|
+
budget,
|
|
302
|
+
...(options.bundleRoot === undefined ? {} : { bundleRoot: options.bundleRoot }),
|
|
303
|
+
...(options.dockerBinary === undefined ? {} : { dockerBinary: options.dockerBinary }),
|
|
304
|
+
...(options.verifierRunner === undefined ? {} : { runner: options.verifierRunner }),
|
|
305
|
+
});
|
|
306
|
+
await activity.append("verifier.completed", {
|
|
307
|
+
status: verifier.status,
|
|
308
|
+
record: verifier.record,
|
|
309
|
+
sandbox: verifier.sandbox,
|
|
310
|
+
...(verifier.error === undefined ? {} : { error: verifier.error }),
|
|
311
|
+
});
|
|
312
|
+
if (verifier.status !== "passed") {
|
|
313
|
+
phase = "verifier_non_success";
|
|
314
|
+
throw new Error(`verifier returned ${verifier.status}; candidate and landing were not advanced`);
|
|
315
|
+
}
|
|
316
|
+
const candidate = finalizeCandidate({
|
|
317
|
+
mission: options.mission,
|
|
318
|
+
engine,
|
|
319
|
+
frozen,
|
|
320
|
+
verifier,
|
|
321
|
+
budget: budget.snapshot(),
|
|
322
|
+
supportingArtifacts: supporting,
|
|
323
|
+
});
|
|
324
|
+
const candidateDigest = contentDigest(candidate);
|
|
325
|
+
await activity.append("candidate.finalized", {
|
|
326
|
+
candidate_digest: candidateDigest,
|
|
327
|
+
status: candidate.status,
|
|
328
|
+
});
|
|
329
|
+
if (options.noLand === true) {
|
|
330
|
+
phase = "clean_clone_reproduction";
|
|
331
|
+
const reproductionRoot = `${paths.root}-reproduce`;
|
|
332
|
+
const reproductionPaths = await prepareWorkspace({
|
|
333
|
+
sourceRepo: options.sourceRepo,
|
|
334
|
+
runRoot: reproductionRoot,
|
|
335
|
+
gitCommit: options.mission.roots.git_commit,
|
|
336
|
+
gitTree: options.mission.roots.git_tree,
|
|
337
|
+
});
|
|
338
|
+
let reproductionVerifier;
|
|
339
|
+
try {
|
|
340
|
+
await options.vela.assertRoots(reproductionPaths.input, options.mission.frontier, options.mission.roots, strictBaseline(options.mission));
|
|
341
|
+
reproductionVerifier = await runVerifier({
|
|
342
|
+
mission: options.mission,
|
|
343
|
+
paths: reproductionPaths,
|
|
344
|
+
artifacts: frozen,
|
|
345
|
+
budget,
|
|
346
|
+
...(options.bundleRoot === undefined ? {} : { bundleRoot: options.bundleRoot }),
|
|
347
|
+
...(options.dockerBinary === undefined ? {} : { dockerBinary: options.dockerBinary }),
|
|
348
|
+
...(options.verifierRunner === undefined ? {} : { runner: options.verifierRunner }),
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
await cleanupWorkspace(reproductionPaths);
|
|
353
|
+
}
|
|
354
|
+
const reproduced = reproductionVerifier.status === verifier.status &&
|
|
355
|
+
reproductionVerifier.record.stdout_digest === verifier.record.stdout_digest &&
|
|
356
|
+
reproductionVerifier.record.stderr_digest === verifier.record.stderr_digest;
|
|
357
|
+
if (!reproduced)
|
|
358
|
+
throw new Error("clean-clone diagnostic verifier replay did not match");
|
|
359
|
+
const record = {
|
|
360
|
+
schema: "canopus.diagnostic-run.v1",
|
|
361
|
+
run_id: runId,
|
|
362
|
+
status: "completed",
|
|
363
|
+
mode: "no_land",
|
|
364
|
+
authority: "non_authoritative",
|
|
365
|
+
external_gate_credit: false,
|
|
366
|
+
mission: {
|
|
367
|
+
id: options.mission.id,
|
|
368
|
+
target: options.mission.target,
|
|
369
|
+
digest: contentDigest(options.mission),
|
|
370
|
+
starting_roots: options.mission.roots,
|
|
371
|
+
},
|
|
372
|
+
candidate: {
|
|
373
|
+
digest: candidateDigest,
|
|
374
|
+
status: candidate.status,
|
|
375
|
+
claim: candidate.claim,
|
|
376
|
+
artifacts: candidate.artifacts,
|
|
377
|
+
caveats: candidate.caveats,
|
|
378
|
+
},
|
|
379
|
+
verifier: {
|
|
380
|
+
status: verifier.status,
|
|
381
|
+
sandbox: verifier.sandbox,
|
|
382
|
+
record: verifier.record,
|
|
383
|
+
},
|
|
384
|
+
landing: null,
|
|
385
|
+
reproduction: {
|
|
386
|
+
matched: true,
|
|
387
|
+
roots: options.mission.roots,
|
|
388
|
+
verifier_status: reproductionVerifier.status,
|
|
389
|
+
stdout_digest: reproductionVerifier.record.stdout_digest,
|
|
390
|
+
stderr_digest: reproductionVerifier.record.stderr_digest,
|
|
391
|
+
},
|
|
392
|
+
budget: budget.snapshot(),
|
|
393
|
+
};
|
|
394
|
+
const projection = {
|
|
395
|
+
schema: "canopus.diagnostic-projection.v1",
|
|
396
|
+
authority: "read_only_projection",
|
|
397
|
+
run_id: runId,
|
|
398
|
+
target: options.mission.target,
|
|
399
|
+
candidate_digest: candidateDigest,
|
|
400
|
+
verifier_status: verifier.status,
|
|
401
|
+
landed: false,
|
|
402
|
+
clean_clone_reproduced: true,
|
|
403
|
+
};
|
|
404
|
+
await rm(paths.velaHome, { recursive: true, force: true });
|
|
405
|
+
await writeExclusive(path.join(paths.root, "candidate.json"), candidate);
|
|
406
|
+
await writeExclusive(path.join(paths.root, "projection.json"), projection);
|
|
407
|
+
await writeExclusive(path.join(paths.root, "run.json"), record);
|
|
408
|
+
await activity.append("run.completed", {
|
|
409
|
+
mode: "no_land",
|
|
410
|
+
candidate_digest: candidateDigest,
|
|
411
|
+
});
|
|
412
|
+
return { record, projection, paths };
|
|
413
|
+
}
|
|
414
|
+
if (postWork === undefined) {
|
|
415
|
+
throw new Error("landing run omitted the required Vela work lease");
|
|
416
|
+
}
|
|
417
|
+
const preLand = await options.vela.assertRoots(paths.landing, options.mission.frontier, postWork.roots, strictBaseline(options.mission));
|
|
418
|
+
// Verify the complete Vela state before placing candidate artifacts. The
|
|
419
|
+
// artifact files intentionally make vela.lock stale until `vela land`
|
|
420
|
+
// admits them transactionally, so a strict check between installation and
|
|
421
|
+
// the porcelain command would reject the ordinary authored-receipt flow.
|
|
422
|
+
await installFrozenArtifacts({
|
|
423
|
+
landingRepo: paths.landing,
|
|
424
|
+
frontier: options.mission.frontier,
|
|
425
|
+
frozen,
|
|
426
|
+
maxBytes: options.mission.budgets.max_artifact_bytes,
|
|
427
|
+
});
|
|
428
|
+
const artifactPublication = await publishArtifactSources({
|
|
429
|
+
repoRoot: paths.landing,
|
|
430
|
+
frontier: options.mission.frontier,
|
|
431
|
+
artifacts: candidate.artifacts,
|
|
432
|
+
home: paths.velaHome,
|
|
433
|
+
});
|
|
434
|
+
await activity.append("artifacts.published", {
|
|
435
|
+
authority: "non_authoritative_git_publication",
|
|
436
|
+
...artifactPublication,
|
|
437
|
+
});
|
|
438
|
+
const receipt = mapCandidateToReceipt(options.mission, candidate, verifier, options.mission.target);
|
|
439
|
+
await activity.append("receipt.mapped", {
|
|
440
|
+
candidate_digest: candidateDigest,
|
|
441
|
+
evidence: receipt.evidence,
|
|
442
|
+
counterevidence: receipt.counterevidence,
|
|
443
|
+
});
|
|
444
|
+
phase = "landing_command";
|
|
445
|
+
landingCommand = await options.vela.landAuthoredCommand(options.mission, paths.landing, receipt, preLand.roots);
|
|
446
|
+
await writeExclusive(path.join(paths.root, "landing-command.json"), {
|
|
447
|
+
schema: "canopus.landing-command.v0",
|
|
448
|
+
authority: "observed_process_effect",
|
|
449
|
+
...landingCommand,
|
|
450
|
+
});
|
|
451
|
+
rawLanding = options.vela.parseLandCommand(landingCommand);
|
|
452
|
+
const rawLandingDigest = contentDigest(rawLanding);
|
|
453
|
+
await writeExclusive(path.join(paths.root, "landing-observation.json"), {
|
|
454
|
+
schema: "canopus.landing-observation.v0",
|
|
455
|
+
authority: "observed_vela_effect",
|
|
456
|
+
raw_digest: rawLandingDigest,
|
|
457
|
+
raw: rawLanding,
|
|
458
|
+
});
|
|
459
|
+
await activity.append("landing.observed", {
|
|
460
|
+
raw_digest: rawLandingDigest,
|
|
461
|
+
observation: "landing-observation.json",
|
|
462
|
+
});
|
|
463
|
+
phase = "landing_validation";
|
|
464
|
+
const landing = options.vela.validateLandResult(options.mission, rawLanding);
|
|
465
|
+
parsedLanding = landing;
|
|
466
|
+
phase = "receipt_binding";
|
|
467
|
+
await options.vela.verifyReceiptBinding(options.mission, paths.landing, landing, receipt, candidate.artifacts);
|
|
468
|
+
await activity.append("landing.bound", {
|
|
469
|
+
receipt_root: landing.receiptRoot,
|
|
470
|
+
raw_digest: rawLandingDigest,
|
|
471
|
+
});
|
|
472
|
+
await activity.append("landing.completed", {
|
|
473
|
+
receipt_root: landing.receiptRoot,
|
|
474
|
+
proposal_id: landing.proposalId,
|
|
475
|
+
route: landing.route,
|
|
476
|
+
original_route: landing.originalRoute,
|
|
477
|
+
accepted_event_delta: landing.acceptedEventDelta,
|
|
478
|
+
publication_state: publicationState(landing),
|
|
479
|
+
});
|
|
480
|
+
const final = await options.vela.inspect(paths.landing, options.mission.frontier, strictBaseline(options.mission));
|
|
481
|
+
phase = "clean_clone_reproduction";
|
|
482
|
+
const reproductionRoot = `${paths.root}-reproduce`;
|
|
483
|
+
const reproductionPaths = await prepareWorkspace({
|
|
484
|
+
sourceRepo: paths.landing,
|
|
485
|
+
runRoot: reproductionRoot,
|
|
486
|
+
gitCommit: final.roots.git_commit,
|
|
487
|
+
gitTree: final.roots.git_tree,
|
|
488
|
+
});
|
|
489
|
+
let reproductionVerifier;
|
|
490
|
+
let reproductionInspection;
|
|
491
|
+
try {
|
|
492
|
+
reproductionInspection = await options.vela.assertRoots(reproductionPaths.input, options.mission.frontier, final.roots, strictBaseline(options.mission));
|
|
493
|
+
await options.vela.verifyReceiptBinding(options.mission, reproductionPaths.input, landing, receipt, candidate.artifacts);
|
|
494
|
+
const reproducedArtifacts = candidate.artifacts.map((artifact) => ({
|
|
495
|
+
artifact,
|
|
496
|
+
frozenPath: retainedArtifactPath(reproductionPaths.input, options.mission.frontier, artifact.digest),
|
|
497
|
+
}));
|
|
498
|
+
reproductionVerifier = await runVerifier({
|
|
499
|
+
mission: options.mission,
|
|
500
|
+
paths: reproductionPaths,
|
|
501
|
+
artifacts: reproducedArtifacts,
|
|
502
|
+
budget,
|
|
503
|
+
...(options.bundleRoot === undefined ? {} : { bundleRoot: options.bundleRoot }),
|
|
504
|
+
...(options.dockerBinary === undefined ? {} : { dockerBinary: options.dockerBinary }),
|
|
505
|
+
...(options.verifierRunner === undefined ? {} : { runner: options.verifierRunner }),
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
finally {
|
|
509
|
+
await cleanupWorkspace(reproductionPaths);
|
|
510
|
+
}
|
|
511
|
+
const reproduced = sameRoots(reproductionInspection.roots, final.roots) &&
|
|
512
|
+
reproductionVerifier.status === verifier.status &&
|
|
513
|
+
reproductionVerifier.record.stdout_digest === verifier.record.stdout_digest &&
|
|
514
|
+
reproductionVerifier.record.stderr_digest === verifier.record.stderr_digest;
|
|
515
|
+
if (!reproduced) {
|
|
516
|
+
throw new Error("clean-clone reproduction did not match the landed run");
|
|
517
|
+
}
|
|
518
|
+
await activity.append("reproduction.completed", {
|
|
519
|
+
matched: true,
|
|
520
|
+
roots: reproductionInspection.roots,
|
|
521
|
+
verifier_status: reproductionVerifier.status,
|
|
522
|
+
});
|
|
523
|
+
if (options.retainWithdrawalCapability !== undefined) {
|
|
524
|
+
phase = "withdrawal_capability";
|
|
525
|
+
await options.retainWithdrawalCapability({
|
|
526
|
+
velaHome: paths.velaHome,
|
|
527
|
+
landingRepo: paths.landing,
|
|
528
|
+
mission: options.mission,
|
|
529
|
+
landing,
|
|
530
|
+
finalRoots: final.roots,
|
|
531
|
+
});
|
|
532
|
+
await activity.append("withdrawal_capability.retained", {
|
|
533
|
+
proposal_id: landing.proposalId,
|
|
534
|
+
authority: "producer_withdrawal_only",
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
await rm(paths.velaHome, { recursive: true, force: true });
|
|
538
|
+
const record = {
|
|
539
|
+
schema: RUN_RECORD_SCHEMA,
|
|
540
|
+
run_id: runId,
|
|
541
|
+
status: "completed",
|
|
542
|
+
authority: "non_authoritative",
|
|
543
|
+
external_gate_credit: false,
|
|
544
|
+
mission: {
|
|
545
|
+
id: options.mission.id,
|
|
546
|
+
target: options.mission.target,
|
|
547
|
+
digest: contentDigest(options.mission),
|
|
548
|
+
starting_roots: options.mission.roots,
|
|
549
|
+
},
|
|
550
|
+
candidate: {
|
|
551
|
+
digest: candidateDigest,
|
|
552
|
+
status: candidate.status,
|
|
553
|
+
claim: candidate.claim,
|
|
554
|
+
artifacts: candidate.artifacts,
|
|
555
|
+
caveats: candidate.caveats,
|
|
556
|
+
},
|
|
557
|
+
verifier: {
|
|
558
|
+
status: verifier.status,
|
|
559
|
+
sandbox: verifier.sandbox,
|
|
560
|
+
record: verifier.record,
|
|
561
|
+
},
|
|
562
|
+
landing: {
|
|
563
|
+
operation_id: landing.operationId,
|
|
564
|
+
receipt_root: landing.receiptRoot,
|
|
565
|
+
proposal_id: landing.proposalId,
|
|
566
|
+
route: landing.route,
|
|
567
|
+
original_route: landing.originalRoute,
|
|
568
|
+
accepted_event_delta: landing.acceptedEventDelta,
|
|
569
|
+
publication_state: publicationState(landing),
|
|
570
|
+
},
|
|
571
|
+
final_roots: final.roots,
|
|
572
|
+
reproduction: {
|
|
573
|
+
matched: reproduced,
|
|
574
|
+
roots: reproductionInspection.roots,
|
|
575
|
+
verifier_status: reproductionVerifier.status,
|
|
576
|
+
stdout_digest: reproductionVerifier.record.stdout_digest,
|
|
577
|
+
stderr_digest: reproductionVerifier.record.stderr_digest,
|
|
578
|
+
},
|
|
579
|
+
budget: budget.snapshot(),
|
|
580
|
+
};
|
|
581
|
+
const projection = projectRun(record);
|
|
582
|
+
phase = "record_finalization";
|
|
583
|
+
await writeExclusive(path.join(paths.root, "candidate.json"), candidate);
|
|
584
|
+
await writeExclusive(path.join(paths.root, "projection.json"), projection);
|
|
585
|
+
await writeExclusive(path.join(paths.root, "run.json"), record);
|
|
586
|
+
await activity.append("projection.written", {
|
|
587
|
+
candidate_digest: candidateDigest,
|
|
588
|
+
run_digest: contentDigest(record),
|
|
589
|
+
projection_digest: contentDigest(projection),
|
|
590
|
+
});
|
|
591
|
+
await activity.append("run.completed", {
|
|
592
|
+
receipt_root: landing.receiptRoot,
|
|
593
|
+
candidate_digest: candidateDigest,
|
|
594
|
+
});
|
|
595
|
+
return { record, projection, paths };
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
599
|
+
let recovery;
|
|
600
|
+
if (phase.startsWith("landing_") ||
|
|
601
|
+
phase === "receipt_binding" ||
|
|
602
|
+
phase === "clean_clone_reproduction" ||
|
|
603
|
+
phase === "withdrawal_capability" ||
|
|
604
|
+
phase === "record_finalization") {
|
|
605
|
+
let observedRoots = null;
|
|
606
|
+
let inspectionError = null;
|
|
607
|
+
try {
|
|
608
|
+
observedRoots = (await options.vela.inspect(paths.landing, options.mission.frontier, strictBaseline(options.mission))).roots;
|
|
609
|
+
}
|
|
610
|
+
catch (inspectionFailure) {
|
|
611
|
+
inspectionError =
|
|
612
|
+
inspectionFailure instanceof Error ? inspectionFailure.message : String(inspectionFailure);
|
|
613
|
+
}
|
|
614
|
+
recovery = {
|
|
615
|
+
schema: "canopus.landing-recovery.v0",
|
|
616
|
+
authority: "observed_vela_effect",
|
|
617
|
+
phase,
|
|
618
|
+
command: landingCommand ?? null,
|
|
619
|
+
raw_digest: rawLanding === undefined ? null : contentDigest(rawLanding),
|
|
620
|
+
raw: rawLanding ?? null,
|
|
621
|
+
parsed: parsedLanding === undefined
|
|
622
|
+
? null
|
|
623
|
+
: {
|
|
624
|
+
operation_id: parsedLanding.operationId,
|
|
625
|
+
receipt_root: parsedLanding.receiptRoot,
|
|
626
|
+
proposal_id: parsedLanding.proposalId,
|
|
627
|
+
route: parsedLanding.route,
|
|
628
|
+
original_route: parsedLanding.originalRoute,
|
|
629
|
+
accepted_event_delta: parsedLanding.acceptedEventDelta,
|
|
630
|
+
publication: parsedLanding.publication,
|
|
631
|
+
},
|
|
632
|
+
observed_roots: observedRoots,
|
|
633
|
+
inspection_error: inspectionError,
|
|
634
|
+
};
|
|
635
|
+
await writeExclusive(path.join(paths.root, "landing-recovery.json"), recovery).catch(() => undefined);
|
|
636
|
+
}
|
|
637
|
+
await activity.append("run.failed", {
|
|
638
|
+
error: message,
|
|
639
|
+
phase,
|
|
640
|
+
landing_observed: landingCommand !== undefined,
|
|
641
|
+
}).catch(() => undefined);
|
|
642
|
+
await writeExclusive(path.join(paths.root, "failure.json"), {
|
|
643
|
+
schema: "canopus.failure.v0",
|
|
644
|
+
run_id: runId,
|
|
645
|
+
error: message,
|
|
646
|
+
phase,
|
|
647
|
+
landing_observed: landingCommand !== undefined,
|
|
648
|
+
landing_recovery: recovery === undefined ? null : "landing-recovery.json",
|
|
649
|
+
activity_tip: activity.tip,
|
|
650
|
+
authority: "non_authoritative",
|
|
651
|
+
}).catch(() => undefined);
|
|
652
|
+
throw error;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function canonicalJson(value: unknown): string;
|
|
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;
|