@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
@@ -1,95 +1 @@
1
- import path from "node:path";
2
- export class ContractError extends Error {
3
- constructor(message) {
4
- super(message);
5
- this.name = "ContractError";
6
- }
7
- }
8
- export function objectAt(value, at) {
9
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
10
- throw new ContractError(`${at} must be an object`);
11
- }
12
- return value;
13
- }
14
- export function exactKeys(value, required, optional, at) {
15
- const allowed = new Set([...required, ...optional]);
16
- for (const key of Object.keys(value)) {
17
- if (!allowed.has(key)) {
18
- throw new ContractError(`${at}.${key} is not allowed`);
19
- }
20
- }
21
- for (const key of required) {
22
- if (!(key in value)) {
23
- throw new ContractError(`${at}.${key} is required`);
24
- }
25
- }
26
- }
27
- export function stringAt(value, at, options) {
28
- if (typeof value !== "string") {
29
- throw new ContractError(`${at} must be a string`);
30
- }
31
- const min = options.min ?? 0;
32
- if (value.length < min || value.length > options.max) {
33
- throw new ContractError(`${at} length must be ${min}..${options.max}`);
34
- }
35
- if (options.pattern !== undefined && !options.pattern.test(value)) {
36
- throw new ContractError(`${at} has an invalid format`);
37
- }
38
- return value;
39
- }
40
- export function integerAt(value, at, min, max) {
41
- if (!Number.isInteger(value) || typeof value !== "number") {
42
- throw new ContractError(`${at} must be an integer`);
43
- }
44
- if (value < min || value > max) {
45
- throw new ContractError(`${at} must be ${min}..${max}`);
46
- }
47
- return value;
48
- }
49
- export function enumAt(value, at, allowed) {
50
- if (typeof value !== "string" || !allowed.includes(value)) {
51
- throw new ContractError(`${at} must be one of ${allowed.join(", ")}`);
52
- }
53
- return value;
54
- }
55
- export function arrayAt(value, at, options, parse) {
56
- if (!Array.isArray(value)) {
57
- throw new ContractError(`${at} must be an array`);
58
- }
59
- const min = options.min ?? 0;
60
- if (value.length < min || value.length > options.max) {
61
- throw new ContractError(`${at} length must be ${min}..${options.max}`);
62
- }
63
- const parsed = value.map((item, index) => parse(item, `${at}[${index}]`));
64
- if (options.unique === true) {
65
- const seen = new Set(parsed.map((item) => JSON.stringify(item)));
66
- if (seen.size !== parsed.length) {
67
- throw new ContractError(`${at} must not contain duplicates`);
68
- }
69
- }
70
- return parsed;
71
- }
72
- export const GIT_OBJECT_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
73
- export const SHA256_RE = /^sha256:[0-9a-f]{64}$/u;
74
- export const MISSION_ID_RE = /^mission_[a-z0-9][a-z0-9._-]{0,126}$/u;
75
- export const AGENT_RE = /^agent:[a-z0-9][a-z0-9._-]{0,62}$/u;
76
- export function relativePathAt(value, at) {
77
- const parsed = stringAt(value, at, { min: 1, max: 1024 });
78
- if (parsed.includes("\0") || path.isAbsolute(parsed) || parsed.includes("\\")) {
79
- throw new ContractError(`${at} must be a portable relative path`);
80
- }
81
- const normalized = path.posix.normalize(parsed);
82
- if (normalized === "." ||
83
- normalized === ".." ||
84
- normalized.startsWith("../") ||
85
- normalized !== parsed) {
86
- throw new ContractError(`${at} must be normalized and remain below its root`);
87
- }
88
- return parsed;
89
- }
90
- export function sha256At(value, at) {
91
- return stringAt(value, at, { max: 71, pattern: SHA256_RE });
92
- }
93
- export function gitObjectAt(value, at) {
94
- return stringAt(value, at, { max: 64, pattern: GIT_OBJECT_RE });
95
- }
1
+ export { AGENT_RE, ContractError, GIT_OBJECT_RE, MISSION_ID_RE, SHA256_RE, arrayAt, enumAt, exactKeys, gitObjectAt, integerAt, objectAt, relativePathAt, sha256At, stringAt, } from "@vela-science/protocol";
@@ -70,7 +70,7 @@ function prompt(mission) {
70
70
  `The exact Vela work claim and roots were validated by the harness. The bound target packet is available at ${mission.target_packet.path}; inspect only the fields needed for this bounded computation.`,
71
71
  "If the packet has a repair_context object, read that object first. It is a root-bound intervention record and producer strategy, not a verifier or authority decision.",
72
72
  "Keep tool output narrow. Do not print or ingest the whole target packet.",
73
- "Worker status reports producer completion, not verifier or scientific standing. Return status success when you produced all artifact bytes required by the output contract, even though you cannot run the separate verifier. State that verification remains pending; Canopus will freeze the bytes and run the verifier after you exit.",
73
+ "Worker status reports producer completion, not verifier or scientific standing. Return status success when you produced all artifact bytes required by the output contract, even though you cannot run the separate verifier. Keep the Claim limited to the bounded result and do not put verifier status in it. State in a caveat that verification remains pending; Canopus will freeze the bytes and run the verifier after you exit.",
74
74
  "Return null only when the bounded work produced no candidate. Return failed only when you could not produce a contract-complete candidate or observed disqualifying evidence. Never turn a bounded negative search into universal nonexistence, verifier failure into success, or Git publication into scientific acceptance.",
75
75
  "Return only the supplied engine-output JSON shape. Artifact bytes must be UTF-8 at mission.allowed_paths. Inline small artifacts. For a large declared artifact, content may be the empty string only when the complete bytes exist at that exact path inside the current workspace; Canopus will bound, scan, and freeze those bytes before workspace cleanup.",
76
76
  ...execution,
@@ -350,7 +350,7 @@ export class CodexToolsNativeEngine {
350
350
  sourceAuth: path.join(this.#options.authHome, "auth.json"),
351
351
  runtimeAuth: path.join(runtimeCodexHome, "auth.json"),
352
352
  inaccessibleInput: path.join(context.paths.input, mission.target_packet.path),
353
- unrelatedFile: path.join(context.paths.landing, ".git", "HEAD"),
353
+ unrelatedFile: path.join(context.paths.frontier, ".git", "HEAD"),
354
354
  canary,
355
355
  outsideWrite: path.join(context.paths.work, "outside-write"),
356
356
  timeoutMs: context.budget.remainingTimeMs(),
@@ -1,34 +1,5 @@
1
- export * from "./contracts/candidate.js";
2
- export * from "./contracts/mission.js";
3
- export * from "./contracts/validation.js";
4
- export * from "./budget/enforce.js";
5
- export * from "./evidence/manifests.js";
6
- export * from "./artifact/freeze.js";
7
- export * from "./activity/events.js";
8
- export * from "./activity/store.js";
9
- export * from "./candidate/validate.js";
10
- export * from "./util/canonical.js";
11
- export * from "./projection/public-run.js";
12
- export * from "./projection/publication.js";
13
- export * from "./util/command.js";
14
- export * from "./util/files.js";
15
- export * from "./roles.js";
16
- export * from "./receipt/map.js";
17
- export * from "./repair/plan.js";
18
- export * from "./projection/run.js";
19
- export * from "./projection/diagnostic.js";
20
- export * from "./product/doctor.js";
21
- export * from "./product/profile.js";
22
- export * from "./product/profile-bundle.js";
23
- export * from "./product/replay.js";
24
- export * from "./product/run.js";
25
- export * from "./run.js";
26
- export * from "./engines/engine.js";
27
- export * from "./engines/fake.js";
28
- export * from "./engines/codex-events.js";
29
- export * from "./engines/codex-tool-free.js";
30
- export * from "./engines/codex-exec.js";
31
- export * from "./vela/cli.js";
32
- export * from "./vela/types.js";
33
- export * from "./workspace/prepare.js";
34
- export * from "./verifier/run.js";
1
+ export { FAILURE_PROJECTION_SCHEMA, FAILURE_RECORD_SCHEMA, parseFailureRecord, projectFailure, } from "./projection/failure.js";
2
+ export type { FailureProjection, FailureRecord } from "./projection/failure.js";
3
+ export { parseCurrentRunRecord, projectCurrentRun, } from "./projection/current-run.js";
4
+ export { exportSubmission, verifySubmission } from "./product/submission.js";
5
+ export { submitBundle } from "./product/submit.js";
package/dist/src/index.js CHANGED
@@ -1,34 +1,4 @@
1
- export * from "./contracts/candidate.js";
2
- export * from "./contracts/mission.js";
3
- export * from "./contracts/validation.js";
4
- export * from "./budget/enforce.js";
5
- export * from "./evidence/manifests.js";
6
- export * from "./artifact/freeze.js";
7
- export * from "./activity/events.js";
8
- export * from "./activity/store.js";
9
- export * from "./candidate/validate.js";
10
- export * from "./util/canonical.js";
11
- export * from "./projection/public-run.js";
12
- export * from "./projection/publication.js";
13
- export * from "./util/command.js";
14
- export * from "./util/files.js";
15
- export * from "./roles.js";
16
- export * from "./receipt/map.js";
17
- export * from "./repair/plan.js";
18
- export * from "./projection/run.js";
19
- export * from "./projection/diagnostic.js";
20
- export * from "./product/doctor.js";
21
- export * from "./product/profile.js";
22
- export * from "./product/profile-bundle.js";
23
- export * from "./product/replay.js";
24
- export * from "./product/run.js";
25
- export * from "./run.js";
26
- export * from "./engines/engine.js";
27
- export * from "./engines/fake.js";
28
- export * from "./engines/codex-events.js";
29
- export * from "./engines/codex-tool-free.js";
30
- export * from "./engines/codex-exec.js";
31
- export * from "./vela/cli.js";
32
- export * from "./vela/types.js";
33
- export * from "./workspace/prepare.js";
34
- export * from "./verifier/run.js";
1
+ export { FAILURE_PROJECTION_SCHEMA, FAILURE_RECORD_SCHEMA, parseFailureRecord, projectFailure, } from "./projection/failure.js";
2
+ export { parseCurrentRunRecord, projectCurrentRun, } from "./projection/current-run.js";
3
+ export { exportSubmission, verifySubmission } from "./product/submission.js";
4
+ export { submitBundle } from "./product/submit.js";
@@ -28,6 +28,15 @@ export interface PreparedMission {
28
28
  missionPath: string;
29
29
  manifestPath: string;
30
30
  }
31
+ export declare function parseVelaVersionOutput(versionText: string): string;
31
32
  export declare function assertVerifierWorkingDirectory(sourceRepo: string, declaredCwd: string): Promise<void>;
32
- export declare function validateMissionBundle(mission: MissionV1, bundleRoot: string): Promise<void>;
33
+ export declare function validateMissionBundle(mission: MissionV1, bundleRoot: string, expectedMissionRoot?: string): Promise<void>;
34
+ export declare function selectedProducerTarget(offer: Record<string, unknown>, requested?: string): Record<string, unknown>;
35
+ export declare function packetFromTarget(target: Record<string, unknown>, expected?: {
36
+ target: string;
37
+ schema: string;
38
+ }): {
39
+ path: string;
40
+ sha256: string;
41
+ };
33
42
  export declare function prepareMission(options: PrepareMissionOptions): Promise<PreparedMission>;
@@ -9,6 +9,12 @@ import { canonicalJson, contentDigest, sha256Bytes } from "../util/canonical.js"
9
9
  import { isolatedEnvironment, runCommand } from "../util/command.js";
10
10
  import { MAX_EXECUTABLE_BYTES, readBoundedRegularFile, sha256RegularFile } from "../util/files.js";
11
11
  import { VelaClient } from "../vela/cli.js";
12
+ export function parseVelaVersionOutput(versionText) {
13
+ const match = /^vela (0\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?)$/u.exec(versionText);
14
+ if (match?.[1] === undefined)
15
+ throw new Error("Vela returned an invalid version");
16
+ return match[1];
17
+ }
12
18
  export async function assertVerifierWorkingDirectory(sourceRepo, declaredCwd) {
13
19
  const root = await realpath(sourceRepo);
14
20
  const relative = relativePathAt(declaredCwd, "mission.verifier.cwd");
@@ -25,7 +31,7 @@ export async function assertVerifierWorkingDirectory(sourceRepo, declaredCwd) {
25
31
  throw new Error(`mission verifier cwd is not a real directory below the sealed source checkout: ${relative}`);
26
32
  }
27
33
  }
28
- export async function validateMissionBundle(mission, bundleRoot) {
34
+ export async function validateMissionBundle(mission, bundleRoot, expectedMissionRoot = contentDigest(mission)) {
29
35
  const root = await realpath(bundleRoot);
30
36
  const capsule = await sourceFile(root, mission.verifier.capsule_path, "verifier capsule");
31
37
  await access(capsule, constants.X_OK);
@@ -59,7 +65,7 @@ export async function validateMissionBundle(mission, bundleRoot) {
59
65
  const manifest = objectAt(manifestValue, "mission bundle manifest");
60
66
  if (manifest.schema !== "canopus.mission-bundle.v1" ||
61
67
  manifest.authority !== "non_authoritative" ||
62
- manifest.mission_sha256 !== contentDigest(mission)) {
68
+ manifest.mission_sha256 !== expectedMissionRoot) {
63
69
  throw new Error("mission bundle manifest does not bind the mission");
64
70
  }
65
71
  }
@@ -111,45 +117,43 @@ function boundedObjective(value) {
111
117
  }
112
118
  return objective;
113
119
  }
114
- function selectedAttack(offer, requested) {
120
+ export function selectedProducerTarget(offer, requested) {
115
121
  if (!Array.isArray(offer.targets))
116
122
  throw new Error("vela next omitted targets");
117
- const attacks = [];
123
+ const producers = [];
118
124
  for (const [index, raw] of offer.targets.entries()) {
119
125
  const target = objectAt(raw, `vela next.targets[${index}]`);
120
- if (target.lane === "attack")
121
- attacks.push(target);
126
+ if (target.lane === "produce")
127
+ producers.push(target);
122
128
  }
123
- if (attacks.length === 0) {
124
- throw new Error("vela next returned no non-review attack target in the first 128 offers");
129
+ if (producers.length === 0) {
130
+ throw new Error("vela next returned no producer target in the first 128 offers");
125
131
  }
126
132
  if (requested === undefined)
127
- return attacks[0];
128
- const matches = attacks.filter((target) => (target.target_id ?? target.id) === requested);
133
+ return producers[0];
134
+ const matches = producers.filter((target) => (target.target_id ?? target.id) === requested);
129
135
  if (matches.length !== 1) {
130
- throw new Error(`explicit mission target ${requested} must appear exactly once among the first 128 attack offers; ` +
136
+ throw new Error(`explicit mission target ${requested} must appear exactly once among the first 128 producer offers; ` +
131
137
  `observed ${matches.length}`);
132
138
  }
133
139
  return matches[0];
134
140
  }
135
- function packetFromTarget(target, expected) {
136
- const packet = target.packet === undefined
137
- ? objectAt(objectAt(target.task, "vela next attack.task").packet_ref, "vela next attack.task.packet_ref")
138
- : objectAt(target.packet, "vela next attack.packet");
141
+ export function packetFromTarget(target, expected) {
142
+ const packet = objectAt(target.packet, "vela next producer packet");
139
143
  const targetId = target.target_id ?? target.id;
140
144
  if (expected !== undefined && targetId !== expected.target) {
141
- throw new Error(`selected attack target ${String(targetId)} does not match profile target ${expected.target}`);
145
+ throw new Error(`selected producer target ${String(targetId)} does not match profile target ${expected.target}`);
142
146
  }
143
- const schema = stringAt(packet.schema, "vela next attack.packet.schema", { min: 1, max: 128 });
147
+ const schema = stringAt(packet.schema, "vela next producer packet.schema", { min: 1, max: 128 });
144
148
  if (expected !== undefined && schema !== expected.schema) {
145
149
  throw new Error(`selected packet schema ${schema} does not match profile schema ${expected.schema}`);
146
150
  }
147
151
  if (expected === undefined && schema !== "erdos-frontier.problem-work.v1") {
148
- throw new Error("selected attack target has no registered exact problem packet schema");
152
+ throw new Error("selected producer target has no registered exact problem packet schema");
149
153
  }
150
154
  return {
151
- path: relativePathAt(packet.path, "vela next attack.packet.path"),
152
- sha256: sha256At(packet.sha256, "vela next attack.packet.sha256"),
155
+ path: relativePathAt(packet.path, "vela next producer packet.path"),
156
+ sha256: sha256At(packet.sha256, "vela next producer packet.sha256"),
153
157
  };
154
158
  }
155
159
  async function imageId(runner, dockerBinary, image, cwd, home) {
@@ -190,10 +194,7 @@ export async function prepareMission(options) {
190
194
  const velaBinary = await realpath(options.velaBinary);
191
195
  const velaSha256 = await sha256RegularFile(velaBinary, MAX_EXECUTABLE_BYTES);
192
196
  const versionText = await commandText(runner, [velaBinary, "--version"], sourceRepo, runtimeHome);
193
- const versionMatch = /^vela (0\.[0-9]+\.[0-9]+)$/u.exec(versionText);
194
- if (versionMatch?.[1] === undefined)
195
- throw new Error("Vela returned an invalid version");
196
- const velaVersion = versionMatch[1];
197
+ const velaVersion = parseVelaVersionOutput(versionText);
197
198
  const vela = new VelaClient({
198
199
  binary: velaBinary,
199
200
  expectedVersion: velaVersion,
@@ -201,14 +202,13 @@ export async function prepareMission(options) {
201
202
  home: path.join(runtimeHome, "vela"),
202
203
  runner,
203
204
  });
204
- const strictBaseline = await vela.observeStrictBaseline(sourceRepo, frontier);
205
- const inspection = await vela.inspect(sourceRepo, frontier, strictBaseline);
206
- const offer = await vela.offer(sourceRepo, frontier, inspection.roots, strictBaseline, 128);
205
+ const inspection = await vela.inspect(sourceRepo, frontier);
206
+ const offer = await vela.offer(sourceRepo, frontier, inspection.roots, 128);
207
207
  const requestedTarget = raw.target === undefined || raw.target === "auto"
208
208
  ? undefined
209
209
  : stringAt(raw.target, "mission.target", { min: 1, max: 256 });
210
- const target = selectedAttack(offer.value, requestedTarget);
211
- const targetId = stringAt(target.target_id ?? target.id, "vela next attack.target_id", { min: 1, max: 256 });
210
+ const target = selectedProducerTarget(offer.value, requestedTarget);
211
+ const targetId = stringAt(target.target_id ?? target.id, "vela next producer target_id", { min: 1, max: 256 });
212
212
  const packet = packetFromTarget(target, options.targetPacket);
213
213
  const packetSource = await sourceFile(sourceRepo, packet.path, "target packet");
214
214
  const packetBytes = await readBoundedRegularFile(packetSource, 64 * 1024 * 1024);
@@ -249,7 +249,6 @@ export async function prepareMission(options) {
249
249
  objective: boundedObjective(raw.objective),
250
250
  roots: inspection.roots,
251
251
  target_packet: packet,
252
- strict_baseline: strictBaseline,
253
252
  worker: {
254
253
  ...workerDraft,
255
254
  kind: "codex_tools_native",
@@ -0,0 +1,17 @@
1
+ export interface CoveredMission {
2
+ mission_id: string;
3
+ coverage_key?: string;
4
+ run_id: string;
5
+ run_file: string;
6
+ }
7
+ export declare function findCoveredMission(options: {
8
+ missionId: string;
9
+ coverageKey?: string | null;
10
+ frontier: string;
11
+ runsRoot?: string;
12
+ }): Promise<CoveredMission | null>;
13
+ export declare function assertMissionNotCovered(options: {
14
+ draft: unknown;
15
+ frontier: string;
16
+ runsRoot?: string;
17
+ }): Promise<void>;
@@ -0,0 +1,137 @@
1
+ import { lstat, readdir } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { objectAt, stringAt } from "../contracts/validation.js";
5
+ import { parseRetainedRunRecord } from "../projection/retained-run.js";
6
+ import { contentDigest } from "../util/canonical.js";
7
+ import { readBoundedRegularFile } from "../util/files.js";
8
+ const MAX_RUN_DIRECTORIES = 4096;
9
+ function consistentMatch(value, expression) {
10
+ const matches = [...value.matchAll(expression)];
11
+ if (matches.length === 0)
12
+ return null;
13
+ const expected = matches[0].slice(1);
14
+ return matches.every((match) => match.slice(1).length === expected.length &&
15
+ match.slice(1).every((entry, index) => entry === expected[index]))
16
+ ? matches[0]
17
+ : null;
18
+ }
19
+ function erdosCoverageKey(value, context) {
20
+ const target = typeof value.target === "string" ? value.target : "";
21
+ const objective = typeof value.objective === "string" ? value.objective : "";
22
+ const targetMatch = target.match(/^erdos:(\d+)$/u);
23
+ if (targetMatch === null)
24
+ return null;
25
+ const problemMatch = consistentMatch(objective, /Erdős\s+(\d+)/gu);
26
+ const kMatch = consistentMatch(objective, /\bk=(\d+)\b/gu);
27
+ const rangeMatch = consistentMatch(objective, /\binclusive range\s+(\d+)\.\.(\d+)\b/gu);
28
+ if (problemMatch === null || kMatch === null || rangeMatch === null) {
29
+ throw new Error(`${context} does not expose one exact Erdős problem, k, and inclusive range`);
30
+ }
31
+ if (problemMatch[1] !== targetMatch[1]) {
32
+ throw new Error(`${context} target and objective disagree on the Erdős problem`);
33
+ }
34
+ const start = BigInt(rangeMatch[1]);
35
+ const end = BigInt(rangeMatch[2]);
36
+ if (start > end)
37
+ throw new Error(`${context} inclusive range is reversed`);
38
+ return `erdos:${targetMatch[1]}:k=${kMatch[1]}:${start}..${end}`;
39
+ }
40
+ async function retainedMissionForRun(runFile, expected) {
41
+ const missionFile = path.join(path.dirname(runFile), "..", "mission", "mission.json");
42
+ const mission = objectAt(JSON.parse((await readBoundedRegularFile(missionFile, 8 * 1024 * 1024)).toString("utf8")), `retained Mission ${missionFile}`);
43
+ if (contentDigest(mission) !== expected.digest) {
44
+ throw new Error(`retained Run ${runFile} and Mission ${missionFile} roots disagree`);
45
+ }
46
+ if (stringAt(mission.id, `retained Mission ${missionFile}.id`, { min: 1, max: 134 }) !==
47
+ expected.id ||
48
+ stringAt(mission.target, `retained Mission ${missionFile}.target`, { min: 1, max: 256 }) !==
49
+ expected.target) {
50
+ throw new Error(`retained Run ${runFile} and Mission ${missionFile} identities disagree`);
51
+ }
52
+ return mission;
53
+ }
54
+ async function regularFileIfPresent(candidate) {
55
+ try {
56
+ const metadata = await lstat(candidate);
57
+ return metadata.isFile() && !metadata.isSymbolicLink() ? candidate : null;
58
+ }
59
+ catch (error) {
60
+ if (error.code === "ENOENT")
61
+ return null;
62
+ throw error;
63
+ }
64
+ }
65
+ async function currentRunFiles(root) {
66
+ let entries;
67
+ try {
68
+ entries = await readdir(root, { withFileTypes: true });
69
+ }
70
+ catch (error) {
71
+ if (error.code === "ENOENT")
72
+ return [];
73
+ throw error;
74
+ }
75
+ if (entries.length > MAX_RUN_DIRECTORIES) {
76
+ throw new Error(`retained Run coverage index exceeds ${MAX_RUN_DIRECTORIES} entries; ` +
77
+ "archive obsolete generated output before running another mission");
78
+ }
79
+ const files = [];
80
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
81
+ if (!entry.isDirectory() || entry.isSymbolicLink())
82
+ continue;
83
+ const runRoot = path.join(root, entry.name);
84
+ const candidates = [
85
+ path.join(runRoot, "run.json"),
86
+ path.join(runRoot, "run", "run.json"),
87
+ ];
88
+ for (const candidate of candidates) {
89
+ const runFile = await regularFileIfPresent(candidate);
90
+ if (runFile !== null)
91
+ files.push(runFile);
92
+ }
93
+ }
94
+ return files;
95
+ }
96
+ export async function findCoveredMission(options) {
97
+ const root = options.runsRoot ??
98
+ path.join(os.homedir(), ".canopus", "runs", path.basename(path.resolve(options.frontier)));
99
+ for (const runFile of await currentRunFiles(root)) {
100
+ const value = JSON.parse((await readBoundedRegularFile(runFile, 8 * 1024 * 1024)).toString("utf8"));
101
+ const object = objectAt(value, `retained Run ${runFile}`);
102
+ if (object.schema !== "canopus.run.v2")
103
+ continue;
104
+ const run = parseRetainedRunRecord(value).record;
105
+ const mission = await retainedMissionForRun(runFile, run.mission);
106
+ const coverageKey = erdosCoverageKey(mission, `retained Run ${runFile} Mission`);
107
+ if (run.mission.id === options.missionId ||
108
+ (options.coverageKey !== null &&
109
+ options.coverageKey !== undefined &&
110
+ coverageKey === options.coverageKey)) {
111
+ return {
112
+ mission_id: run.mission.id,
113
+ ...(coverageKey === null ? {} : { coverage_key: coverageKey }),
114
+ run_id: run.run_id,
115
+ run_file: runFile,
116
+ };
117
+ }
118
+ }
119
+ return null;
120
+ }
121
+ export async function assertMissionNotCovered(options) {
122
+ const draft = objectAt(options.draft, "profile draft");
123
+ const missionId = stringAt(draft.id, "profile draft.id", { min: 1, max: 134 });
124
+ const coverageKey = erdosCoverageKey(draft, "profile draft");
125
+ const covered = await findCoveredMission({
126
+ missionId,
127
+ coverageKey,
128
+ frontier: options.frontier,
129
+ ...(options.runsRoot === undefined ? {} : { runsRoot: options.runsRoot }),
130
+ });
131
+ if (covered !== null) {
132
+ throw new Error(`mission ${covered.mission_id} is already covered by verifier-passing Run ` +
133
+ `${covered.run_id} at ${covered.run_file}` +
134
+ `${covered.coverage_key === undefined ? "" : ` (${covered.coverage_key})`}; ` +
135
+ "freeze the first uncovered bounded range instead of repeating it");
136
+ }
137
+ }
@@ -9,8 +9,7 @@ export interface ProductDoctorResult {
9
9
  path: string;
10
10
  git_commit: string;
11
11
  git_tree: string;
12
- event_log_root: string;
13
- scientific_state_root: string;
12
+ repository_root: string;
14
13
  clean: boolean;
15
14
  strict_blockers: number;
16
15
  };
@@ -16,7 +16,7 @@ async function jsonCommand(options) {
16
16
  timeoutMs: 120_000,
17
17
  maxOutputBytes: 16 * 1024 * 1024,
18
18
  });
19
- if (result.exitCode !== 0) {
19
+ if (!(options.acceptedExitCodes ?? [0]).includes(result.exitCode)) {
20
20
  throw new Error(`${options.label} failed: stdout_sha256=${sha256Bytes(result.stdout)}; ` +
21
21
  `stderr_sha256=${sha256Bytes(result.stderr)}`);
22
22
  }
@@ -124,7 +124,16 @@ export async function doctorProduct(options) {
124
124
  throw new Error(`Canopus ${CANOPUS_VERSION} requires vela ${SUPPORTED_VELA_VERSION}, observed ${vela.version}`);
125
125
  }
126
126
  const [status, offer, gitStatus] = await Promise.all([
127
- jsonCommand({ runner, argv: [vela.binary, "status", ".", "--json"], cwd: frontier, home: runtime, label: "vela status" }),
127
+ jsonCommand({
128
+ runner,
129
+ argv: [vela.binary, "status", ".", "--json"],
130
+ cwd: frontier,
131
+ home: runtime,
132
+ label: "vela status",
133
+ // Vela deliberately returns domain failure when strict debt exists,
134
+ // while still emitting the complete read-only compact projection.
135
+ acceptedExitCodes: [0, 1],
136
+ }),
128
137
  jsonCommand({ runner, argv: [vela.binary, "next", ".", "--limit", "128", "--json"], cwd: frontier, home: runtime, label: "vela next" }),
129
138
  runner({ argv: [git.binary, "status", "--porcelain=v1", "--untracked-files=all"], cwd: frontier, env: isolatedEnvironment(runtime), timeoutMs: 30_000, maxOutputBytes: 8 * 1024 * 1024 }),
130
139
  ]);
@@ -133,14 +142,28 @@ export async function doctorProduct(options) {
133
142
  const clean = gitStatus.stdout.length === 0;
134
143
  if (!clean)
135
144
  throw new Error("frontier checkout must be clean before Canopus runs");
136
- if (status.schema !== "vela.status.v1" || offer.schema !== "vela.offer.v1") {
137
- throw new Error("frontier did not return the Vela 0.9 compact contracts");
145
+ if (status.schema !== "vela.status.v1" ||
146
+ status.ok !== true ||
147
+ status.command !== "status" ||
148
+ offer.schema !== "vela.offer.v1" ||
149
+ offer.ok !== true ||
150
+ offer.command !== "next") {
151
+ throw new Error("frontier did not return the current Vela compact contracts");
138
152
  }
139
153
  const profile = await resolveProductProfile(offer, options.profileName, options.requestedTarget);
140
154
  const selected = selectProductOffer(offer, profile, options.requestedTarget);
141
155
  const roots = objectAt(status.roots, "vela status.roots");
142
156
  const gitState = objectAt(status.git, "vela status.git");
143
157
  const integrity = objectAt(status.integrity, "vela status.integrity");
158
+ const repositoryRoot = sha256At(roots.repository, "vela status.roots.repository");
159
+ if (integrity.replay !== "verified" ||
160
+ integrity.strict !== "pass" ||
161
+ nonnegative(integrity.blocker_count, "vela status.integrity.blocker_count") !== 0) {
162
+ throw new Error("frontier must replay and pass strict verification before Canopus can run");
163
+ }
164
+ if (sha256At(offer.repository_root, "vela next.repository_root") !== repositoryRoot) {
165
+ throw new Error("vela status and next disagree on the current repository root");
166
+ }
144
167
  const staging = path.join(runtime, "capsule-build");
145
168
  await mkdir(staging, { mode: 0o700 });
146
169
  const capsule = await stageProfileCapsule({ profile, stagingRoot: staging });
@@ -155,8 +178,7 @@ export async function doctorProduct(options) {
155
178
  path: frontier,
156
179
  git_commit: stringAt(gitState.commit, "vela status.git.commit", { min: 40, max: 64 }),
157
180
  git_tree: stringAt(gitState.tree, "vela status.git.tree", { min: 40, max: 64 }),
158
- event_log_root: sha256At(roots.event_log, "vela status.roots.event_log"),
159
- scientific_state_root: sha256At(roots.scientific_state_root, "vela status.roots.scientific_state_root"),
181
+ repository_root: repositoryRoot,
160
182
  clean,
161
183
  strict_blockers: nonnegative(integrity.blocker_count, "vela status.integrity.blocker_count"),
162
184
  },
@@ -250,8 +272,7 @@ export async function doctorProduct(options) {
250
272
  path: frontier,
251
273
  git_commit: stringAt(gitState.commit, "vela status.git.commit", { min: 40, max: 64 }),
252
274
  git_tree: stringAt(gitState.tree, "vela status.git.tree", { min: 40, max: 64 }),
253
- event_log_root: sha256At(roots.event_log, "vela status.roots.event_log"),
254
- scientific_state_root: sha256At(roots.scientific_state_root, "vela status.roots.scientific_state_root"),
275
+ repository_root: repositoryRoot,
255
276
  clean,
256
277
  strict_blockers: nonnegative(integrity.blocker_count, "vela status.integrity.blocker_count"),
257
278
  },
@@ -1,4 +1,4 @@
1
- import { constants } from "node:fs";
1
+ import { constants, existsSync } from "node:fs";
2
2
  import { access, chmod, copyFile, mkdir, realpath } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
@@ -11,7 +11,13 @@ export const PROFILE_V1_SCHEMA = "canopus.profile.v1";
11
11
  export const PROFILE_SCHEMA = "canopus.profile.v2";
12
12
  export const SUPPORTED_PRODUCT_PLATFORMS = ["darwin-arm64", "linux-x86_64"];
13
13
  export function productPackageRoot() {
14
- return fileURLToPath(new URL("../../../", import.meta.url));
14
+ const root = [
15
+ fileURLToPath(new URL("../../", import.meta.url)),
16
+ fileURLToPath(new URL("../../../", import.meta.url)),
17
+ ].find((candidate) => existsSync(path.join(candidate, "package.json")));
18
+ if (root === undefined)
19
+ throw new Error("Canopus package root is missing");
20
+ return root;
15
21
  }
16
22
  export function currentProductPlatform() {
17
23
  if (process.platform === "darwin" && process.arch === "arm64")