@vela-science/canopus 0.6.5 → 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.
Files changed (120) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +67 -139
  3. package/SECURITY.md +3 -2
  4. package/capsules/erdos1056-k15/bin/linux-arm64/{10428801-10429000 → 10429201-10429400}/verifier +0 -0
  5. package/capsules/erdos1056-k15/bin/linux-x86_64/{10428801-10429000 → 10429201-10429400}/verifier +0 -0
  6. package/compatibility.json +25 -0
  7. package/dist/src/activity/events.d.ts +4 -1
  8. package/dist/src/activity/events.js +8 -2
  9. package/dist/src/activity/store.d.ts +2 -2
  10. package/dist/src/activity/store.js +4 -3
  11. package/dist/src/{receipt/map.d.ts → candidate/finalize.d.ts} +2 -9
  12. package/dist/src/candidate/finalize.js +71 -0
  13. package/dist/src/cli.js +88 -229
  14. package/dist/src/contracts/mission.d.ts +1 -13
  15. package/dist/src/contracts/mission.js +4 -42
  16. package/dist/src/contracts/validation.d.ts +1 -25
  17. package/dist/src/contracts/validation.js +1 -95
  18. package/dist/src/engines/codex-tools-native.js +2 -2
  19. package/dist/src/index.d.ts +5 -34
  20. package/dist/src/index.js +4 -34
  21. package/dist/src/mission/prepare.d.ts +10 -1
  22. package/dist/src/mission/prepare.js +29 -30
  23. package/dist/src/product/coverage.d.ts +17 -0
  24. package/dist/src/product/coverage.js +137 -0
  25. package/dist/src/product/doctor.d.ts +1 -2
  26. package/dist/src/product/doctor.js +29 -8
  27. package/dist/src/product/profile.js +8 -2
  28. package/dist/src/product/replay.js +11 -12
  29. package/dist/src/product/run.d.ts +5 -9
  30. package/dist/src/product/run.js +17 -88
  31. package/dist/src/product/submission.d.ts +40 -0
  32. package/dist/src/product/submission.js +217 -0
  33. package/dist/src/product/submit.d.ts +22 -0
  34. package/dist/src/product/submit.js +264 -0
  35. package/dist/src/product/version.d.ts +2 -2
  36. package/dist/src/product/version.js +27 -4
  37. package/dist/src/projection/current-run.d.ts +12 -0
  38. package/dist/src/projection/current-run.js +94 -0
  39. package/dist/src/projection/failure.d.ts +17 -7
  40. package/dist/src/projection/failure.js +43 -12
  41. package/dist/src/projection/retained-mission.d.ts +6 -0
  42. package/dist/src/projection/retained-mission.js +41 -0
  43. package/dist/src/projection/retained-run.d.ts +7 -0
  44. package/dist/src/projection/retained-run.js +50 -0
  45. package/dist/src/run.d.ts +43 -41
  46. package/dist/src/run.js +34 -412
  47. package/dist/src/util/canonical.d.ts +1 -5
  48. package/dist/src/util/canonical.js +1 -53
  49. package/dist/src/vela/cli.d.ts +7 -51
  50. package/dist/src/vela/cli.js +102 -523
  51. package/dist/src/vela/types.d.ts +3 -19
  52. package/dist/src/verifier/run.js +1 -1
  53. package/dist/src/workspace/prepare.d.ts +1 -1
  54. package/dist/src/workspace/prepare.js +15 -15
  55. package/docs/EVALUATION.md +142 -0
  56. package/docs/MISSIONS.md +42 -80
  57. package/docs/RUN_RECORD.md +77 -99
  58. package/docs/adr/0010-nonmutating-runs-and-explicit-submission.md +191 -0
  59. package/missions/erdos1056-k15-next/mission.draft.json +10 -8
  60. package/package.json +18 -22
  61. package/profiles/{erdos1056-k15-10428801-10429000.json → erdos1056-k15-10429201-10429400.json} +10 -10
  62. package/schemas/failure.v1.json +17 -0
  63. package/schemas/mission.v0.json +3 -4
  64. package/schemas/mission.v1.json +7 -28
  65. package/schemas/{run.v1.json → run.v2.json} +58 -116
  66. package/schemas/submission-bundle.v1.json +63 -0
  67. package/toolchain.lock.json +31 -0
  68. package/BUILD_WEEK.md +0 -198
  69. package/advisories/erdos1056-claim-fidelity/output.schema.json +0 -90
  70. package/advisories/erdos1056-claim-fidelity/registration.json +0 -42
  71. package/advisories/erdos1056-claim-fidelity/results/assessment.json +0 -1
  72. package/advisories/erdos1056-claim-fidelity/results/verification.json +0 -1
  73. package/capsules/formal-erdos-505-test-dim-one/verifier +0 -105
  74. package/capsules/quantum-10-1-4/verifier.py +0 -152
  75. package/capsules/sidon-a24/bin/linux-arm64/verifier +0 -0
  76. package/capsules/sidon-a24/bin/linux-x86_64/verifier +0 -0
  77. package/dist/src/capability/withdrawal.d.ts +0 -47
  78. package/dist/src/capability/withdrawal.js +0 -487
  79. package/dist/src/engines/codex-exec.d.ts +0 -17
  80. package/dist/src/engines/codex-exec.js +0 -136
  81. package/dist/src/engines/codex-tool-free.d.ts +0 -26
  82. package/dist/src/engines/codex-tool-free.js +0 -213
  83. package/dist/src/product/withdraw.d.ts +0 -8
  84. package/dist/src/product/withdraw.js +0 -255
  85. package/dist/src/projection/diagnostic.d.ts +0 -12
  86. package/dist/src/projection/diagnostic.js +0 -61
  87. package/dist/src/projection/public-run.d.ts +0 -59
  88. package/dist/src/projection/public-run.js +0 -106
  89. package/dist/src/projection/publication.d.ts +0 -36
  90. package/dist/src/projection/publication.js +0 -47
  91. package/dist/src/projection/run.d.ts +0 -95
  92. package/dist/src/projection/run.js +0 -173
  93. package/dist/src/receipt/map.js +0 -168
  94. package/dist/src/repair/plan.d.ts +0 -2
  95. package/dist/src/repair/plan.js +0 -22
  96. package/dist/src/roles.d.ts +0 -2
  97. package/dist/src/roles.js +0 -9
  98. package/docs/RELEASES.md +0 -1389
  99. package/evidence/build-week/run_eb6bcd46-cffd-4ae8-b630-2681bd84da71.public.json +0 -1
  100. package/evidence/build-week/run_f68e4cfc-e5c7-4c73-86cb-d79807c47ec4.public.json +0 -1
  101. package/evidence/erdos/run_192b3bef-9d6e-49e5-b72d-7ae903b29d5e/pending-commands.json +0 -1
  102. package/evidence/erdos/run_192b3bef-9d6e-49e5-b72d-7ae903b29d5e/public-run.json +0 -1
  103. package/evidence/erdos/run_192b3bef-9d6e-49e5-b72d-7ae903b29d5e/root-manifest.json +0 -1
  104. package/evidence/erdos/run_192b3bef-9d6e-49e5-b72d-7ae903b29d5e/web-import.json +0 -1
  105. package/evidence/erdos/run_b3b0cf07-b149-4d53-8258-76ba0e1fc0a5/pending-commands.json +0 -1
  106. package/evidence/erdos/run_b3b0cf07-b149-4d53-8258-76ba0e1fc0a5/public-run.json +0 -1
  107. package/evidence/erdos/run_b3b0cf07-b149-4d53-8258-76ba0e1fc0a5/root-manifest.json +0 -1
  108. package/evidence/erdos/run_b3b0cf07-b149-4d53-8258-76ba0e1fc0a5/web-import.json +0 -1
  109. package/missions/formal-erdos-505-test-dim-one-gpt56/mission.draft.json +0 -40
  110. package/missions/quantum-10-1-4-retry/mission.draft.json +0 -40
  111. package/missions/sidon-a24-at-least-7194-gpt56/mission.draft.json +0 -42
  112. package/missions/sidon-a24-at-least-7194-gpt56-v2/mission.draft.json +0 -42
  113. package/missions/sidon-a24-at-least-7194-gpt56-v3/mission.draft.json +0 -42
  114. package/profiles/formal-erdos-505-test-dim-one-gpt56.json +0 -35
  115. package/profiles/quantum-10-1-4-stabilizer-retry.json +0 -35
  116. package/profiles/sidon-a24-at-least-7194-gpt56-v2.json +0 -35
  117. package/profiles/sidon-a24-at-least-7194-gpt56-v3.json +0 -35
  118. package/profiles/sidon-a24-at-least-7194-gpt56.json +0 -35
  119. package/schemas/canopus.public-run.v1.json +0 -116
  120. package/scripts/run-claim-fidelity-advisory.mjs +0 -235
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 { 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";
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
- landing: "landing",
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, strictBaseline(options.mission)),
206
- options.vela.assertRoots(paths.landing, options.mission.frontier, options.mission.roots, strictBaseline(options.mission)),
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.landing);
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
- let workBriefing;
220
- let postWork;
221
- if (options.noLand === true) {
222
- // Diagnostic mode has no landing edge and therefore needs no durable
223
- // lease. Avoiding `vela work` keeps the disposable clone at the exact
224
- // registered roots and prevents an operational attempt event from
225
- // needlessly invalidating a previously exported proof packet.
226
- workBriefing = offer.value;
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 landing were not run`);
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}; candidate and landing were not advanced`);
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
- if (options.noLand === true) {
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, strictBaseline(options.mission));
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("clean-clone diagnostic verifier replay did not match");
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.diagnostic-run.v1",
233
+ schema: "canopus.run.v2",
383
234
  run_id: runId,
384
235
  status: "completed",
385
- mode: "no_land",
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
- landing: null,
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.diagnostic-projection.v1",
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
- landed: false,
274
+ submitted: false,
424
275
  clean_clone_reproduced: true,
425
276
  };
426
277
  await rm(paths.velaHome, { recursive: true, force: true });
@@ -428,254 +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
- mode: "no_land",
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
- if (options.retainWithdrawalCapability !== undefined) {
546
- phase = "withdrawal_capability";
547
- await options.retainWithdrawalCapability({
548
- velaHome: paths.velaHome,
549
- landingRepo: paths.landing,
550
- mission: options.mission,
551
- landing,
552
- finalRoots: final.roots,
553
- });
554
- await activity.append("withdrawal_capability.retained", {
555
- proposal_id: landing.proposalId,
556
- authority: "producer_withdrawal_only",
557
- });
558
- }
559
- await rm(paths.velaHome, { recursive: true, force: true });
560
- const record = {
561
- schema: RUN_RECORD_SCHEMA,
562
- run_id: runId,
563
- status: "completed",
564
- authority: "non_authoritative",
565
- external_gate_credit: false,
566
- mission: {
567
- id: options.mission.id,
568
- target: options.mission.target,
569
- digest: contentDigest(options.mission),
570
- starting_roots: options.mission.roots,
571
- },
572
- candidate: {
573
- digest: candidateDigest,
574
- status: candidate.status,
575
- claim: candidate.claim,
576
- artifacts: candidate.artifacts,
577
- caveats: candidate.caveats,
578
- },
579
- observations: {
580
- worker_observations: candidate.observations,
581
- verifier_observations: [
582
- `Frozen verifier passed in ${verifier.sandbox}.`,
583
- `Verifier stdout ${verifier.record.stdout_digest}; stderr ${verifier.record.stderr_digest}.`,
584
- ],
585
- standing_caveats: candidate.caveats,
586
- },
587
- verifier: {
588
- status: verifier.status,
589
- sandbox: verifier.sandbox,
590
- record: verifier.record,
591
- },
592
- landing: {
593
- operation_id: landing.operationId,
594
- receipt_root: landing.receiptRoot,
595
- proposal_id: landing.proposalId,
596
- route: landing.route,
597
- original_route: landing.originalRoute,
598
- accepted_event_delta: landing.acceptedEventDelta,
599
- publication_state: publicationState(landing),
600
- },
601
- final_roots: final.roots,
602
- reproduction: {
603
- matched: reproduced,
604
- roots: reproductionInspection.roots,
605
- verifier_status: reproductionVerifier.status,
606
- stdout_digest: reproductionVerifier.record.stdout_digest,
607
- stderr_digest: reproductionVerifier.record.stderr_digest,
608
- },
609
- budget: budget.snapshot(),
610
- };
611
- const projection = projectRun(record);
612
- phase = "record_finalization";
613
- await writeExclusive(path.join(paths.root, "candidate.json"), candidate);
614
- await writeExclusive(path.join(paths.root, "projection.json"), projection);
615
- await writeExclusive(path.join(paths.root, "run.json"), record);
616
- await activity.append("projection.written", {
617
- candidate_digest: candidateDigest,
618
- run_digest: contentDigest(record),
619
- projection_digest: contentDigest(projection),
620
- });
621
- await activity.append("run.completed", {
622
- receipt_root: landing.receiptRoot,
623
- candidate_digest: candidateDigest,
624
- });
625
- return { record, projection, paths };
626
287
  }
627
288
  catch (error) {
628
289
  const message = error instanceof Error ? error.message : String(error);
629
- let recovery;
630
- if (phase.startsWith("landing_") ||
631
- phase === "receipt_binding" ||
632
- phase === "clean_clone_reproduction" ||
633
- phase === "withdrawal_capability" ||
634
- phase === "record_finalization") {
635
- let observedRoots = null;
636
- let inspectionError = null;
637
- try {
638
- observedRoots = (await options.vela.inspect(paths.landing, options.mission.frontier, strictBaseline(options.mission))).roots;
639
- }
640
- catch (inspectionFailure) {
641
- inspectionError =
642
- inspectionFailure instanceof Error ? inspectionFailure.message : String(inspectionFailure);
643
- }
644
- recovery = {
645
- schema: "canopus.landing-recovery.v0",
646
- authority: "observed_vela_effect",
647
- phase,
648
- command: landingCommand ?? null,
649
- raw_digest: rawLanding === undefined ? null : contentDigest(rawLanding),
650
- raw: rawLanding ?? null,
651
- parsed: parsedLanding === undefined
652
- ? null
653
- : {
654
- operation_id: parsedLanding.operationId,
655
- receipt_root: parsedLanding.receiptRoot,
656
- proposal_id: parsedLanding.proposalId,
657
- route: parsedLanding.route,
658
- original_route: parsedLanding.originalRoute,
659
- accepted_event_delta: parsedLanding.acceptedEventDelta,
660
- publication: parsedLanding.publication,
661
- },
662
- observed_roots: observedRoots,
663
- inspection_error: inspectionError,
664
- };
665
- await writeExclusive(path.join(paths.root, "landing-recovery.json"), recovery).catch(() => undefined);
666
- }
667
290
  await activity.append("run.failed", {
668
291
  error: message,
669
292
  phase,
670
- landing_observed: landingCommand !== undefined,
293
+ effect: "none",
671
294
  }).catch(() => undefined);
672
295
  await writeExclusive(path.join(paths.root, "failure.json"), {
673
- schema: "canopus.failure.v0",
296
+ schema: "canopus.failure.v1",
674
297
  run_id: runId,
675
298
  error: message,
676
299
  phase,
677
- landing_observed: landingCommand !== undefined,
678
- landing_recovery: recovery === undefined ? null : "landing-recovery.json",
300
+ effect: "none",
679
301
  activity_tip: activity.tip,
680
302
  authority: "non_authoritative",
681
303
  }).catch(() => undefined);
@@ -1,5 +1 @@
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;
1
+ export { canonicalJcs, canonicalJson, contentDigest, protocolDigest, sha256Bytes, } from "@vela-science/protocol";
@@ -1,53 +1 @@
1
- import { createHash } from "node:crypto";
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";