@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.
Files changed (118) hide show
  1. package/CHANGELOG.md +249 -0
  2. package/LICENSE-APACHE +176 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +124 -0
  5. package/SECURITY.md +35 -0
  6. package/capsules/erdos1056-k15/README.md +35 -0
  7. package/capsules/erdos1056-k15/bin/linux-arm64/10428401-10428600/verifier +0 -0
  8. package/capsules/erdos1056-k15/bin/linux-x86_64/10428401-10428600/verifier +0 -0
  9. package/capsules/formal-erdos-505-test-dim-one/README.md +20 -0
  10. package/capsules/formal-erdos-505-test-dim-one/verifier +105 -0
  11. package/dist/src/activity/events.d.ts +15 -0
  12. package/dist/src/activity/events.js +24 -0
  13. package/dist/src/activity/store.d.ts +10 -0
  14. package/dist/src/activity/store.js +165 -0
  15. package/dist/src/artifact/freeze.d.ts +21 -0
  16. package/dist/src/artifact/freeze.js +177 -0
  17. package/dist/src/artifact/materialize.d.ts +6 -0
  18. package/dist/src/artifact/materialize.js +29 -0
  19. package/dist/src/budget/enforce.d.ts +30 -0
  20. package/dist/src/budget/enforce.js +98 -0
  21. package/dist/src/candidate/validate.d.ts +3 -0
  22. package/dist/src/candidate/validate.js +32 -0
  23. package/dist/src/capability/withdrawal.d.ts +47 -0
  24. package/dist/src/capability/withdrawal.js +487 -0
  25. package/dist/src/cli.d.ts +2 -0
  26. package/dist/src/cli.js +503 -0
  27. package/dist/src/contracts/candidate.d.ts +50 -0
  28. package/dist/src/contracts/candidate.js +93 -0
  29. package/dist/src/contracts/mission.d.ts +139 -0
  30. package/dist/src/contracts/mission.js +401 -0
  31. package/dist/src/contracts/validation.d.ts +25 -0
  32. package/dist/src/contracts/validation.js +95 -0
  33. package/dist/src/engines/codex-events.d.ts +25 -0
  34. package/dist/src/engines/codex-events.js +219 -0
  35. package/dist/src/engines/codex-exec.d.ts +17 -0
  36. package/dist/src/engines/codex-exec.js +136 -0
  37. package/dist/src/engines/codex-home.d.ts +7 -0
  38. package/dist/src/engines/codex-home.js +50 -0
  39. package/dist/src/engines/codex-tool-free.d.ts +26 -0
  40. package/dist/src/engines/codex-tool-free.js +213 -0
  41. package/dist/src/engines/codex-tools-native.d.ts +30 -0
  42. package/dist/src/engines/codex-tools-native.js +405 -0
  43. package/dist/src/engines/engine.d.ts +55 -0
  44. package/dist/src/engines/engine.js +4 -0
  45. package/dist/src/engines/fake.d.ts +7 -0
  46. package/dist/src/engines/fake.js +37 -0
  47. package/dist/src/evidence/manifests.d.ts +9 -0
  48. package/dist/src/evidence/manifests.js +56 -0
  49. package/dist/src/index.d.ts +32 -0
  50. package/dist/src/index.js +32 -0
  51. package/dist/src/mission/prepare.d.ts +33 -0
  52. package/dist/src/mission/prepare.js +375 -0
  53. package/dist/src/product/custody.d.ts +27 -0
  54. package/dist/src/product/custody.js +131 -0
  55. package/dist/src/product/doctor.d.ts +61 -0
  56. package/dist/src/product/doctor.js +278 -0
  57. package/dist/src/product/profile-bundle.d.ts +21 -0
  58. package/dist/src/product/profile-bundle.js +78 -0
  59. package/dist/src/product/profile.d.ts +67 -0
  60. package/dist/src/product/profile.js +251 -0
  61. package/dist/src/product/replay.d.ts +10 -0
  62. package/dist/src/product/replay.js +71 -0
  63. package/dist/src/product/run.d.ts +31 -0
  64. package/dist/src/product/run.js +264 -0
  65. package/dist/src/product/runtime.d.ts +18 -0
  66. package/dist/src/product/runtime.js +64 -0
  67. package/dist/src/product/version.d.ts +1 -0
  68. package/dist/src/product/version.js +6 -0
  69. package/dist/src/product/withdraw.d.ts +8 -0
  70. package/dist/src/product/withdraw.js +255 -0
  71. package/dist/src/projection/diagnostic.d.ts +12 -0
  72. package/dist/src/projection/diagnostic.js +61 -0
  73. package/dist/src/projection/failure.d.ts +26 -0
  74. package/dist/src/projection/failure.js +63 -0
  75. package/dist/src/projection/run.d.ts +89 -0
  76. package/dist/src/projection/run.js +159 -0
  77. package/dist/src/receipt/map.d.ts +25 -0
  78. package/dist/src/receipt/map.js +162 -0
  79. package/dist/src/repair/plan.d.ts +2 -0
  80. package/dist/src/repair/plan.js +22 -0
  81. package/dist/src/roles.d.ts +2 -0
  82. package/dist/src/roles.js +9 -0
  83. package/dist/src/run.d.ts +94 -0
  84. package/dist/src/run.js +654 -0
  85. package/dist/src/util/canonical.d.ts +5 -0
  86. package/dist/src/util/canonical.js +53 -0
  87. package/dist/src/util/command.d.ts +26 -0
  88. package/dist/src/util/command.js +161 -0
  89. package/dist/src/util/files.d.ts +3 -0
  90. package/dist/src/util/files.js +65 -0
  91. package/dist/src/vela/cli.d.ts +71 -0
  92. package/dist/src/vela/cli.js +718 -0
  93. package/dist/src/vela/types.d.ts +27 -0
  94. package/dist/src/vela/types.js +1 -0
  95. package/dist/src/verifier/run.d.ts +24 -0
  96. package/dist/src/verifier/run.js +315 -0
  97. package/dist/src/workspace/prepare.d.ts +30 -0
  98. package/dist/src/workspace/prepare.js +183 -0
  99. package/docs/MISSIONS.md +78 -0
  100. package/docs/RELEASES.md +1036 -0
  101. package/docs/RUN_RECORD.md +70 -0
  102. package/missions/erdos1056-k15-next/mission.draft.json +40 -0
  103. package/missions/formal-erdos-505-test-dim-one/mission.draft.json +40 -0
  104. package/package.json +89 -0
  105. package/profiles/erdos1056-k15-10428401-10428600.json +33 -0
  106. package/profiles/formal-erdos-505-test-dim-one.json +35 -0
  107. package/runtime/native-worker/config-linux.toml +20 -0
  108. package/runtime/native-worker/config.toml +17 -0
  109. package/schemas/candidate.v0.json +125 -0
  110. package/schemas/engine-manifest.v0.json +45 -0
  111. package/schemas/engine-output.v0.json +41 -0
  112. package/schemas/mission.v0.json +175 -0
  113. package/schemas/mission.v1.json +224 -0
  114. package/schemas/verifier-manifest.v0.json +48 -0
  115. package/schemas/verifier-manifest.v1.json +59 -0
  116. package/scripts/run-hostile-native-custody-fixture.mjs +382 -0
  117. package/scripts/run-hostile-verifier-fixture.mjs +63 -0
  118. package/tests/fixtures/hostile-verifier/probe.py +35 -0
@@ -0,0 +1,139 @@
1
+ export declare const MISSION_SCHEMA: "canopus.mission.v0";
2
+ export declare const MISSION_V1_SCHEMA: "canopus.mission.v1";
3
+ export declare const EXECUTION_BINDING_SCHEMA: "vela.execution-binding.v1";
4
+ export declare const RESULT_CONTRACT_SCHEMA: "canopus.result-contract.v1";
5
+ export declare const ROLES: readonly ["producer", "adversary", "verifier", "fidelity"];
6
+ export declare const LAND_ROUTES: readonly ["permit", "defer"];
7
+ export declare const CLAIM_TYPES: readonly ["computational", "theoretical", "empirical", "negative", "contradiction"];
8
+ export declare const REPLAYABILITY: readonly ["exact", "bounded", "approximate", "unavailable", "unknown"];
9
+ export type MissionRole = (typeof ROLES)[number];
10
+ export type LandRoute = (typeof LAND_ROUTES)[number];
11
+ export type ClaimType = (typeof CLAIM_TYPES)[number];
12
+ export type Replayability = (typeof REPLAYABILITY)[number];
13
+ export interface MissionRoots {
14
+ git_commit: string;
15
+ git_tree: string;
16
+ vela_event_log: string;
17
+ vela_snapshot: string;
18
+ }
19
+ export interface MissionBudgets {
20
+ max_research_wall_time_ms: number;
21
+ max_research_processes: number;
22
+ max_research_output_bytes: number;
23
+ max_prompt_bytes: number;
24
+ max_artifact_bytes: number;
25
+ max_attempts: number;
26
+ max_observed_tokens: number;
27
+ }
28
+ export interface VerifierSpec {
29
+ argv: string[];
30
+ executable_sha256: string;
31
+ cwd: string;
32
+ timeout_ms: number;
33
+ max_output_bytes: number;
34
+ network: "deny";
35
+ writes: "deny";
36
+ }
37
+ export interface ContainerVerifierSpec extends VerifierSpec {
38
+ capsule_path: string;
39
+ capsule_sha256: string;
40
+ image: string;
41
+ platform?: "linux/amd64" | "linux/arm64";
42
+ }
43
+ export interface WorkerSpec {
44
+ kind: "codex_tools_native";
45
+ platform: "darwin" | "linux";
46
+ codex_version: string;
47
+ codex_sha256: string;
48
+ permission_profile_path: string;
49
+ permission_profile_sha256: string;
50
+ workspace: "target_packet_only";
51
+ output_schema_sha256: string;
52
+ model: string;
53
+ network: "provider_only";
54
+ tools: ["shell", "apply_patch"];
55
+ }
56
+ export interface TargetPacketSpec {
57
+ path: string;
58
+ sha256: string;
59
+ }
60
+ export interface ProducerProfileSpec {
61
+ name: string;
62
+ root: string;
63
+ }
64
+ export interface StrictRuleCount {
65
+ rule: string;
66
+ count: number;
67
+ }
68
+ export interface StrictBaseline {
69
+ status: "pass" | "fail";
70
+ blocker_count: number;
71
+ blockers_root: string;
72
+ rule_counts: StrictRuleCount[];
73
+ }
74
+ export interface LandingSpec {
75
+ expected_routes: LandRoute[];
76
+ max_accepted_delta: number;
77
+ }
78
+ export interface ExecutionBindingV1 {
79
+ schema: typeof EXECUTION_BINDING_SCHEMA;
80
+ packet_root: string;
81
+ profile_root: string;
82
+ verifier_capsule_root: string;
83
+ result_contract_root: string;
84
+ }
85
+ export interface PositiveResultContractV1 {
86
+ schema: typeof RESULT_CONTRACT_SCHEMA;
87
+ target: string;
88
+ claim_exact: string;
89
+ claim_type: "computational";
90
+ replayability: "exact";
91
+ candidate_status: "success";
92
+ verifier_status: "passed";
93
+ required_artifact_kinds: string[];
94
+ }
95
+ export type ScientificChainSpec = {
96
+ predicted_observable: string;
97
+ performed_test: string;
98
+ } | {
99
+ not_applicable: true;
100
+ performed_test: string;
101
+ };
102
+ interface MissionBase {
103
+ id: string;
104
+ target: string;
105
+ vela_version: string;
106
+ vela_sha256: string;
107
+ frontier: string;
108
+ actor: string;
109
+ role: MissionRole;
110
+ claim_type: ClaimType;
111
+ replayability: Replayability;
112
+ objective: string;
113
+ completion_condition: string;
114
+ roots: MissionRoots;
115
+ allowed_paths: string[];
116
+ budgets: MissionBudgets;
117
+ scientific_chain: ScientificChainSpec;
118
+ landing: LandingSpec;
119
+ parent_candidate?: string;
120
+ repair_reason?: string;
121
+ }
122
+ export interface MissionV0 extends MissionBase {
123
+ schema: typeof MISSION_SCHEMA;
124
+ verifier: VerifierSpec;
125
+ }
126
+ export interface MissionV1 extends MissionBase {
127
+ schema: typeof MISSION_V1_SCHEMA;
128
+ target_packet: TargetPacketSpec;
129
+ profile?: ProducerProfileSpec;
130
+ strict_baseline: StrictBaseline;
131
+ worker: WorkerSpec;
132
+ verifier: ContainerVerifierSpec;
133
+ execution_binding?: ExecutionBindingV1;
134
+ result_contract?: PositiveResultContractV1;
135
+ }
136
+ export type Mission = MissionV0 | MissionV1;
137
+ export declare function parsePositiveResultContract(value: unknown): PositiveResultContractV1;
138
+ export declare function parseMission(value: unknown): Mission;
139
+ export {};
@@ -0,0 +1,401 @@
1
+ import { AGENT_RE, ContractError, MISSION_ID_RE, arrayAt, enumAt, exactKeys, gitObjectAt, integerAt, objectAt, relativePathAt, sha256At, stringAt, } from "./validation.js";
2
+ import { contentDigest } from "../util/canonical.js";
3
+ export const MISSION_SCHEMA = "canopus.mission.v0";
4
+ export const MISSION_V1_SCHEMA = "canopus.mission.v1";
5
+ export const EXECUTION_BINDING_SCHEMA = "vela.execution-binding.v1";
6
+ export const RESULT_CONTRACT_SCHEMA = "canopus.result-contract.v1";
7
+ export const ROLES = ["producer", "adversary", "verifier", "fidelity"];
8
+ // Vela Deny is deliberately error-shaped and therefore cannot be a successful
9
+ // Canopus landing outcome. Missions register only success-shaped routes.
10
+ export const LAND_ROUTES = ["permit", "defer"];
11
+ export const CLAIM_TYPES = [
12
+ "computational",
13
+ "theoretical",
14
+ "empirical",
15
+ "negative",
16
+ "contradiction",
17
+ ];
18
+ export const REPLAYABILITY = [
19
+ "exact",
20
+ "bounded",
21
+ "approximate",
22
+ "unavailable",
23
+ "unknown",
24
+ ];
25
+ function parseRoots(value) {
26
+ const object = objectAt(value, "mission.roots");
27
+ exactKeys(object, ["git_commit", "git_tree", "vela_event_log", "vela_snapshot"], [], "mission.roots");
28
+ return {
29
+ git_commit: gitObjectAt(object.git_commit, "mission.roots.git_commit"),
30
+ git_tree: gitObjectAt(object.git_tree, "mission.roots.git_tree"),
31
+ vela_event_log: sha256At(object.vela_event_log, "mission.roots.vela_event_log"),
32
+ vela_snapshot: sha256At(object.vela_snapshot, "mission.roots.vela_snapshot"),
33
+ };
34
+ }
35
+ function frontierPathAt(value) {
36
+ return value === "." ? "." : relativePathAt(value, "mission.frontier");
37
+ }
38
+ function parseBudgets(value) {
39
+ const object = objectAt(value, "mission.budgets");
40
+ exactKeys(object, [
41
+ "max_research_wall_time_ms",
42
+ "max_research_processes",
43
+ "max_research_output_bytes",
44
+ "max_prompt_bytes",
45
+ "max_artifact_bytes",
46
+ "max_attempts",
47
+ "max_observed_tokens",
48
+ ], [], "mission.budgets");
49
+ return {
50
+ max_research_wall_time_ms: integerAt(object.max_research_wall_time_ms, "mission.budgets.max_research_wall_time_ms", 100, 3_600_000),
51
+ max_research_processes: integerAt(object.max_research_processes, "mission.budgets.max_research_processes", 1, 64),
52
+ max_research_output_bytes: integerAt(object.max_research_output_bytes, "mission.budgets.max_research_output_bytes", 1024, 67_108_864),
53
+ max_prompt_bytes: integerAt(object.max_prompt_bytes, "mission.budgets.max_prompt_bytes", 1024, 8_388_608),
54
+ max_artifact_bytes: integerAt(object.max_artifact_bytes, "mission.budgets.max_artifact_bytes", 1, 1_073_741_824),
55
+ max_attempts: integerAt(object.max_attempts, "mission.budgets.max_attempts", 1, 8),
56
+ max_observed_tokens: integerAt(object.max_observed_tokens, "mission.budgets.max_observed_tokens", 1, 1_000_000),
57
+ };
58
+ }
59
+ function parseVerifier(value, container) {
60
+ const object = objectAt(value, "mission.verifier");
61
+ exactKeys(object, ["argv", "executable_sha256", "cwd", "timeout_ms", "max_output_bytes", "network", "writes"], container ? ["capsule_path", "capsule_sha256", "image", "platform"] : [], "mission.verifier");
62
+ const base = {
63
+ argv: arrayAt(object.argv, "mission.verifier.argv", { min: 1, max: 64 }, (item, at) => stringAt(item, at, { max: 4096 })),
64
+ executable_sha256: sha256At(object.executable_sha256, "mission.verifier.executable_sha256"),
65
+ cwd: relativePathAt(object.cwd, "mission.verifier.cwd"),
66
+ timeout_ms: integerAt(object.timeout_ms, "mission.verifier.timeout_ms", 100, 3_600_000),
67
+ max_output_bytes: integerAt(object.max_output_bytes, "mission.verifier.max_output_bytes", 1024, 67_108_864),
68
+ network: enumAt(object.network, "mission.verifier.network", ["deny"]),
69
+ writes: enumAt(object.writes, "mission.verifier.writes", ["deny"]),
70
+ };
71
+ if (!container)
72
+ return base;
73
+ return {
74
+ ...base,
75
+ capsule_path: relativePathAt(object.capsule_path, "mission.verifier.capsule_path"),
76
+ capsule_sha256: sha256At(object.capsule_sha256, "mission.verifier.capsule_sha256"),
77
+ image: sha256At(object.image, "mission.verifier.image"),
78
+ ...(object.platform === undefined
79
+ ? {}
80
+ : {
81
+ platform: enumAt(object.platform, "mission.verifier.platform", ["linux/amd64", "linux/arm64"]),
82
+ }),
83
+ };
84
+ }
85
+ function parseWorker(value) {
86
+ const object = objectAt(value, "mission.worker");
87
+ exactKeys(object, [
88
+ "kind", "platform", "codex_version", "codex_sha256", "permission_profile_path",
89
+ "permission_profile_sha256", "workspace", "output_schema_sha256", "model", "network", "tools",
90
+ ], [], "mission.worker");
91
+ const tools = arrayAt(object.tools, "mission.worker.tools", { min: 2, max: 2, unique: true }, (item, at) => enumAt(item, at, ["shell", "apply_patch"]));
92
+ if (tools[0] !== "shell" || tools[1] !== "apply_patch") {
93
+ throw new ContractError("mission.worker.tools must be [shell, apply_patch]");
94
+ }
95
+ return {
96
+ kind: enumAt(object.kind, "mission.worker.kind", ["codex_tools_native"]),
97
+ platform: enumAt(object.platform, "mission.worker.platform", ["darwin", "linux"]),
98
+ codex_version: stringAt(object.codex_version, "mission.worker.codex_version", {
99
+ min: 10,
100
+ max: 64,
101
+ pattern: /^codex-cli [0-9]+\.[0-9]+\.[0-9]+(?:[-+][A-Za-z0-9.-]+)?$/u,
102
+ }),
103
+ codex_sha256: sha256At(object.codex_sha256, "mission.worker.codex_sha256"),
104
+ permission_profile_path: relativePathAt(object.permission_profile_path, "mission.worker.permission_profile_path"),
105
+ permission_profile_sha256: sha256At(object.permission_profile_sha256, "mission.worker.permission_profile_sha256"),
106
+ workspace: enumAt(object.workspace, "mission.worker.workspace", ["target_packet_only"]),
107
+ output_schema_sha256: sha256At(object.output_schema_sha256, "mission.worker.output_schema_sha256"),
108
+ model: stringAt(object.model, "mission.worker.model", { min: 1, max: 128 }),
109
+ network: enumAt(object.network, "mission.worker.network", ["provider_only"]),
110
+ tools: ["shell", "apply_patch"],
111
+ };
112
+ }
113
+ function parseTargetPacket(value) {
114
+ const object = objectAt(value, "mission.target_packet");
115
+ exactKeys(object, ["path", "sha256"], [], "mission.target_packet");
116
+ return {
117
+ path: relativePathAt(object.path, "mission.target_packet.path"),
118
+ sha256: sha256At(object.sha256, "mission.target_packet.sha256"),
119
+ };
120
+ }
121
+ function parseStrictBaseline(value) {
122
+ const object = objectAt(value, "mission.strict_baseline");
123
+ exactKeys(object, ["status", "blocker_count", "blockers_root", "rule_counts"], [], "mission.strict_baseline");
124
+ const rule_counts = arrayAt(object.rule_counts, "mission.strict_baseline.rule_counts", { min: 0, max: 64 }, (item, at) => {
125
+ const entry = objectAt(item, at);
126
+ exactKeys(entry, ["rule", "count"], [], at);
127
+ return {
128
+ rule: stringAt(entry.rule, `${at}.rule`, {
129
+ min: 1,
130
+ max: 128,
131
+ pattern: /^[a-z][a-z0-9_]*$/u,
132
+ }),
133
+ count: integerAt(entry.count, `${at}.count`, 1, 1_000_000),
134
+ };
135
+ });
136
+ for (let index = 1; index < rule_counts.length; index += 1) {
137
+ if ((rule_counts[index - 1]?.rule ?? "") >= (rule_counts[index]?.rule ?? "")) {
138
+ throw new ContractError("mission.strict_baseline.rule_counts must be sorted and unique");
139
+ }
140
+ }
141
+ const blocker_count = integerAt(object.blocker_count, "mission.strict_baseline.blocker_count", 0, 1_000_000);
142
+ if (rule_counts.reduce((sum, entry) => sum + entry.count, 0) !== blocker_count) {
143
+ throw new ContractError("mission.strict_baseline.rule_counts must sum to blocker_count");
144
+ }
145
+ const status = enumAt(object.status, "mission.strict_baseline.status", ["pass", "fail"]);
146
+ if ((status === "pass") !== (blocker_count === 0)) {
147
+ throw new ContractError("mission.strict_baseline status must agree with blocker_count");
148
+ }
149
+ return {
150
+ status,
151
+ blocker_count,
152
+ blockers_root: sha256At(object.blockers_root, "mission.strict_baseline.blockers_root"),
153
+ rule_counts,
154
+ };
155
+ }
156
+ function parseLanding(value) {
157
+ const object = objectAt(value, "mission.landing");
158
+ exactKeys(object, ["expected_routes", "max_accepted_delta"], [], "mission.landing");
159
+ return {
160
+ expected_routes: arrayAt(object.expected_routes, "mission.landing.expected_routes", { min: 1, max: 3, unique: true }, (item, at) => enumAt(item, at, LAND_ROUTES)),
161
+ max_accepted_delta: integerAt(object.max_accepted_delta, "mission.landing.max_accepted_delta", 0, 1),
162
+ };
163
+ }
164
+ function parseProducerProfile(value) {
165
+ const object = objectAt(value, "mission.profile");
166
+ exactKeys(object, ["name", "root"], [], "mission.profile");
167
+ return {
168
+ name: stringAt(object.name, "mission.profile.name", {
169
+ min: 1,
170
+ max: 128,
171
+ pattern: /^[a-z0-9][a-z0-9._-]*$/u,
172
+ }),
173
+ root: sha256At(object.root, "mission.profile.root"),
174
+ };
175
+ }
176
+ function parseExecutionBinding(value) {
177
+ const object = objectAt(value, "mission.execution_binding");
178
+ exactKeys(object, ["schema", "packet_root", "profile_root", "verifier_capsule_root", "result_contract_root"], [], "mission.execution_binding");
179
+ if (object.schema !== EXECUTION_BINDING_SCHEMA) {
180
+ throw new ContractError(`mission.execution_binding.schema must be ${EXECUTION_BINDING_SCHEMA}`);
181
+ }
182
+ return {
183
+ schema: EXECUTION_BINDING_SCHEMA,
184
+ packet_root: sha256At(object.packet_root, "mission.execution_binding.packet_root"),
185
+ profile_root: sha256At(object.profile_root, "mission.execution_binding.profile_root"),
186
+ verifier_capsule_root: sha256At(object.verifier_capsule_root, "mission.execution_binding.verifier_capsule_root"),
187
+ result_contract_root: sha256At(object.result_contract_root, "mission.execution_binding.result_contract_root"),
188
+ };
189
+ }
190
+ export function parsePositiveResultContract(value) {
191
+ const object = objectAt(value, "mission.result_contract");
192
+ exactKeys(object, [
193
+ "schema",
194
+ "target",
195
+ "claim_exact",
196
+ "claim_type",
197
+ "replayability",
198
+ "candidate_status",
199
+ "verifier_status",
200
+ "required_artifact_kinds",
201
+ ], [], "mission.result_contract");
202
+ if (object.schema !== RESULT_CONTRACT_SCHEMA) {
203
+ throw new ContractError(`mission.result_contract.schema must be ${RESULT_CONTRACT_SCHEMA}`);
204
+ }
205
+ if (object.claim_type !== "computational" || object.replayability !== "exact") {
206
+ throw new ContractError("mission.result_contract is limited to exact computational results");
207
+ }
208
+ if (object.candidate_status !== "success" || object.verifier_status !== "passed") {
209
+ throw new ContractError("mission.result_contract must require a successful verified result");
210
+ }
211
+ return {
212
+ schema: RESULT_CONTRACT_SCHEMA,
213
+ target: stringAt(object.target, "mission.result_contract.target", { min: 1, max: 256 }),
214
+ claim_exact: stringAt(object.claim_exact, "mission.result_contract.claim_exact", {
215
+ min: 1,
216
+ max: 8192,
217
+ }),
218
+ claim_type: "computational",
219
+ replayability: "exact",
220
+ candidate_status: "success",
221
+ verifier_status: "passed",
222
+ required_artifact_kinds: arrayAt(object.required_artifact_kinds, "mission.result_contract.required_artifact_kinds", { min: 1, max: 16, unique: true }, (item, at) => stringAt(item, at, {
223
+ min: 1,
224
+ max: 64,
225
+ pattern: /^[a-z][a-z0-9._-]*$/u,
226
+ })).sort(),
227
+ };
228
+ }
229
+ function parseScientificChain(value) {
230
+ const object = objectAt(value, "mission.scientific_chain");
231
+ exactKeys(object, ["performed_test"], ["predicted_observable", "not_applicable"], "mission.scientific_chain");
232
+ const performed_test = stringAt(object.performed_test, "mission.scientific_chain.performed_test", { min: 1, max: 16_384 });
233
+ const hasPrediction = object.predicted_observable !== undefined;
234
+ const hasNotApplicable = object.not_applicable !== undefined;
235
+ if (hasPrediction === hasNotApplicable) {
236
+ throw new ContractError("mission.scientific_chain requires exactly one of predicted_observable or not_applicable");
237
+ }
238
+ if (hasPrediction) {
239
+ return {
240
+ predicted_observable: stringAt(object.predicted_observable, "mission.scientific_chain.predicted_observable", { min: 1, max: 16_384 }),
241
+ performed_test,
242
+ };
243
+ }
244
+ if (object.not_applicable !== true) {
245
+ throw new ContractError("mission.scientific_chain.not_applicable must be true");
246
+ }
247
+ return { not_applicable: true, performed_test };
248
+ }
249
+ export function parseMission(value) {
250
+ const object = objectAt(value, "mission");
251
+ const schema = enumAt(object.schema, "mission.schema", [MISSION_SCHEMA, MISSION_V1_SCHEMA]);
252
+ const isV1 = schema === MISSION_V1_SCHEMA;
253
+ exactKeys(object, [
254
+ "schema",
255
+ "id",
256
+ "target",
257
+ "vela_version",
258
+ "vela_sha256",
259
+ "frontier",
260
+ "actor",
261
+ "role",
262
+ "claim_type",
263
+ "replayability",
264
+ "objective",
265
+ "completion_condition",
266
+ "roots",
267
+ "allowed_paths",
268
+ "budgets",
269
+ "verifier",
270
+ "scientific_chain",
271
+ "landing",
272
+ ], isV1
273
+ ? [
274
+ "parent_candidate",
275
+ "repair_reason",
276
+ "target_packet",
277
+ "profile",
278
+ "strict_baseline",
279
+ "worker",
280
+ "execution_binding",
281
+ "result_contract",
282
+ ]
283
+ : ["parent_candidate", "repair_reason"], "mission");
284
+ if (isV1) {
285
+ for (const required of ["target_packet", "strict_baseline", "worker"]) {
286
+ if (object[required] === undefined) {
287
+ throw new ContractError(`mission.${required} is required for mission v1`);
288
+ }
289
+ }
290
+ }
291
+ const replayability = enumAt(object.replayability, "mission.replayability", REPLAYABILITY);
292
+ if (replayability !== "exact") {
293
+ throw new ContractError("Canopus v0 requires replayability exact");
294
+ }
295
+ const verifier = parseVerifier(object.verifier, isV1);
296
+ const verifierExecutable = relativePathAt(verifier.argv[0], "mission.verifier.argv[0]");
297
+ if (!/^(?:[A-Za-z0-9_+.-]+\/)*[A-Za-z0-9_+.-]+$/u.test(verifierExecutable)) {
298
+ throw new ContractError("mission.verifier.argv[0] must name one relative verifier capsule without shell syntax");
299
+ }
300
+ verifier.argv[0] = verifierExecutable;
301
+ const landing = parseLanding(object.landing);
302
+ const requiredDelta = landing.expected_routes.includes("permit") ? 1 : 0;
303
+ if (landing.max_accepted_delta !== requiredDelta) {
304
+ throw new ContractError(`mission.landing.max_accepted_delta must be ${requiredDelta} for the registered routes`);
305
+ }
306
+ if (landing.expected_routes.length !== 1) {
307
+ throw new ContractError("mission landing must register exactly one expected route");
308
+ }
309
+ const base = {
310
+ schema,
311
+ id: stringAt(object.id, "mission.id", { max: 134, pattern: MISSION_ID_RE }),
312
+ target: stringAt(object.target, "mission.target", { min: 1, max: 256 }),
313
+ vela_version: stringAt(object.vela_version, "mission.vela_version", {
314
+ min: 5,
315
+ max: 32,
316
+ pattern: /^0\.[0-9]+\.[0-9]+$/u,
317
+ }),
318
+ vela_sha256: sha256At(object.vela_sha256, "mission.vela_sha256"),
319
+ frontier: frontierPathAt(object.frontier),
320
+ actor: stringAt(object.actor, "mission.actor", { max: 69, pattern: AGENT_RE }),
321
+ role: enumAt(object.role, "mission.role", ROLES),
322
+ claim_type: enumAt(object.claim_type, "mission.claim_type", CLAIM_TYPES),
323
+ replayability,
324
+ objective: stringAt(object.objective, "mission.objective", { min: 1, max: 8192 }),
325
+ completion_condition: stringAt(object.completion_condition, "mission.completion_condition", {
326
+ min: 1,
327
+ max: 4096,
328
+ }),
329
+ roots: parseRoots(object.roots),
330
+ allowed_paths: arrayAt(object.allowed_paths, "mission.allowed_paths", { min: 1, max: 64, unique: true }, (item, at) => {
331
+ const allowed = relativePathAt(item, at);
332
+ if (allowed === "canopus" || allowed.startsWith("canopus/")) {
333
+ throw new ContractError(`${at} uses the reserved Canopus evidence namespace`);
334
+ }
335
+ return allowed;
336
+ }),
337
+ budgets: parseBudgets(object.budgets),
338
+ verifier,
339
+ scientific_chain: parseScientificChain(object.scientific_chain),
340
+ landing,
341
+ };
342
+ const versioned = isV1
343
+ ? (() => {
344
+ const targetPacket = parseTargetPacket(object.target_packet);
345
+ const profile = object.profile === undefined
346
+ ? undefined
347
+ : parseProducerProfile(object.profile);
348
+ const executionBinding = object.execution_binding === undefined
349
+ ? undefined
350
+ : parseExecutionBinding(object.execution_binding);
351
+ const resultContract = object.result_contract === undefined
352
+ ? undefined
353
+ : parsePositiveResultContract(object.result_contract);
354
+ if ((executionBinding === undefined) !== (resultContract === undefined)) {
355
+ throw new ContractError("mission v1 exact Permit requires both execution_binding and result_contract");
356
+ }
357
+ if (landing.expected_routes[0] === "permit" && executionBinding === undefined) {
358
+ throw new ContractError("mission v1 Permit requires an exact execution binding");
359
+ }
360
+ if (executionBinding !== undefined && resultContract !== undefined) {
361
+ if (base.claim_type !== "computational" || base.replayability !== "exact") {
362
+ throw new ContractError("exact Permit bindings require an exact computational mission");
363
+ }
364
+ if (executionBinding.packet_root !== targetPacket.sha256 ||
365
+ executionBinding.verifier_capsule_root !== verifier.capsule_sha256 ||
366
+ executionBinding.result_contract_root !== contentDigest(resultContract) ||
367
+ resultContract.target !== base.target) {
368
+ throw new ContractError("mission execution binding does not match its retained packet, capsule, result contract, or target");
369
+ }
370
+ if (profile !== undefined && executionBinding.profile_root !== profile.root) {
371
+ throw new ContractError("mission execution binding does not match its retained producer profile");
372
+ }
373
+ }
374
+ return {
375
+ ...base,
376
+ schema: MISSION_V1_SCHEMA,
377
+ target_packet: targetPacket,
378
+ ...(profile === undefined ? {} : { profile }),
379
+ strict_baseline: parseStrictBaseline(object.strict_baseline),
380
+ worker: parseWorker(object.worker),
381
+ verifier: verifier,
382
+ ...(executionBinding === undefined ? {} : { execution_binding: executionBinding }),
383
+ ...(resultContract === undefined ? {} : { result_contract: resultContract }),
384
+ };
385
+ })()
386
+ : { ...base, schema: MISSION_SCHEMA, verifier: verifier };
387
+ if ((object.parent_candidate === undefined) !== (object.repair_reason === undefined)) {
388
+ throw new ContractError("mission repair requires both parent_candidate and repair_reason");
389
+ }
390
+ if (object.parent_candidate === undefined || object.repair_reason === undefined) {
391
+ return versioned;
392
+ }
393
+ return {
394
+ ...versioned,
395
+ parent_candidate: sha256At(object.parent_candidate, "mission.parent_candidate"),
396
+ repair_reason: stringAt(object.repair_reason, "mission.repair_reason", {
397
+ min: 1,
398
+ max: 4096,
399
+ }),
400
+ };
401
+ }
@@ -0,0 +1,25 @@
1
+ export declare class ContractError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export type JsonObject = Record<string, unknown>;
5
+ export declare function objectAt(value: unknown, at: string): JsonObject;
6
+ export declare function exactKeys(value: JsonObject, required: readonly string[], optional: readonly string[], at: string): void;
7
+ export declare function stringAt(value: unknown, at: string, options: {
8
+ min?: number;
9
+ max: number;
10
+ pattern?: RegExp;
11
+ }): string;
12
+ export declare function integerAt(value: unknown, at: string, min: number, max: number): number;
13
+ export declare function enumAt<const T extends string>(value: unknown, at: string, allowed: readonly T[]): T;
14
+ export declare function arrayAt<T>(value: unknown, at: string, options: {
15
+ min?: number;
16
+ max: number;
17
+ unique?: boolean;
18
+ }, parse: (item: unknown, at: string) => T): T[];
19
+ export declare const GIT_OBJECT_RE: RegExp;
20
+ export declare const SHA256_RE: RegExp;
21
+ export declare const MISSION_ID_RE: RegExp;
22
+ export declare const AGENT_RE: RegExp;
23
+ export declare function relativePathAt(value: unknown, at: string): string;
24
+ export declare function sha256At(value: unknown, at: string): string;
25
+ export declare function gitObjectAt(value: unknown, at: string): string;
@@ -0,0 +1,95 @@
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
+ }