@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,213 +0,0 @@
1
- import { access, realpath } from "node:fs/promises";
2
- import path from "node:path";
3
- export const CODEX_TOOL_FEATURES = [
4
- "apply_patch_freeform",
5
- "apply_patch_streaming_events",
6
- "apps",
7
- "artifact",
8
- "browser_use",
9
- "browser_use_external",
10
- "computer_use",
11
- "enable_mcp_apps",
12
- "goals",
13
- "hooks",
14
- "image_generation",
15
- "in_app_browser",
16
- "memories",
17
- "multi_agent",
18
- "plugin_sharing",
19
- "remote_plugin",
20
- "shell_snapshot",
21
- "shell_tool",
22
- "tool_suggest",
23
- "unified_exec",
24
- "workspace_dependencies",
25
- ];
26
- function sbpl(value) {
27
- return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
28
- }
29
- function metadataLiterals(paths) {
30
- const values = new Set(["/"]);
31
- for (const value of paths) {
32
- for (let current = value; current !== "/"; current = path.dirname(current)) {
33
- values.add(current);
34
- }
35
- }
36
- return [...values]
37
- .sort()
38
- .map((value) => `(literal "${sbpl(value)}")`)
39
- .join(" ");
40
- }
41
- async function optionalRealpath(candidate) {
42
- try {
43
- await access(candidate);
44
- return await realpath(candidate);
45
- }
46
- catch {
47
- return undefined;
48
- }
49
- }
50
- /**
51
- * Wrap Codex itself in a host read boundary. Codex 0.139 always registers
52
- * view_image even when every feature-backed tool is disabled, so this outer
53
- * sandbox is the control that makes an arbitrary host image unreadable.
54
- */
55
- export async function sandboxedToolFreeCodexExecArgv(options) {
56
- if (process.platform !== "darwin") {
57
- throw new Error("Canopus v0 requires the macOS outer Codex sandbox");
58
- }
59
- const lexical = {
60
- binary: path.resolve(options.binary),
61
- cwd: path.resolve(options.cwd),
62
- outputSchema: path.resolve(options.outputSchema),
63
- finalParent: path.resolve(path.dirname(options.finalPath)),
64
- authHome: path.resolve(options.authHome),
65
- authFile: path.resolve(options.authHome, "auth.json"),
66
- modelCatalog: path.resolve(options.authHome, "models_cache.json"),
67
- };
68
- const managedRequirements = [
69
- "/etc/codex/requirements.toml",
70
- "/private/etc/codex/requirements.toml",
71
- ];
72
- // These are public operating-system inputs used by the macOS resolver and
73
- // network stack. Keep the outer boundary file-specific: Codex does not need
74
- // the user's preferences, keychain, or arbitrary /etc contents.
75
- const networkRuntimeFiles = [
76
- "/Library/Preferences/com.apple.networkd.plist",
77
- "/etc/hosts",
78
- "/etc/protocols",
79
- "/etc/resolv.conf",
80
- "/etc/services",
81
- "/private/etc/hosts",
82
- "/private/etc/protocols",
83
- "/private/etc/resolv.conf",
84
- "/private/etc/services",
85
- "/private/var/run/resolv.conf",
86
- "/var/run/resolv.conf",
87
- ];
88
- const [binary, cwd, outputSchema, finalParent, authHome, authFile, modelCatalog] = await Promise.all([
89
- realpath(lexical.binary),
90
- realpath(lexical.cwd),
91
- realpath(lexical.outputSchema),
92
- realpath(lexical.finalParent),
93
- realpath(lexical.authHome),
94
- optionalRealpath(lexical.authFile),
95
- optionalRealpath(lexical.modelCatalog),
96
- ]);
97
- if (authFile === undefined) {
98
- throw new Error("Codex auth home has no readable auth.json");
99
- }
100
- const finalPath = path.join(finalParent, path.basename(options.finalPath));
101
- const inner = toolFreeCodexExecArgv({
102
- ...options,
103
- binary,
104
- cwd,
105
- outputSchema,
106
- finalPath,
107
- });
108
- const readableFiles = [
109
- binary,
110
- lexical.binary,
111
- outputSchema,
112
- lexical.outputSchema,
113
- authFile,
114
- authHome,
115
- lexical.authFile,
116
- lexical.authHome,
117
- modelCatalog,
118
- ...networkRuntimeFiles,
119
- ...(modelCatalog === undefined ? [] : [lexical.modelCatalog]),
120
- ]
121
- .filter((item) => item !== undefined)
122
- .filter((item, index, values) => values.indexOf(item) === index)
123
- .map((item) => `(literal "${sbpl(item)}")`)
124
- .join(" ");
125
- const metadata = metadataLiterals([
126
- binary,
127
- cwd,
128
- outputSchema,
129
- finalPath,
130
- authFile,
131
- lexical.binary,
132
- lexical.cwd,
133
- lexical.outputSchema,
134
- lexical.finalParent,
135
- lexical.authFile,
136
- ...managedRequirements,
137
- ...networkRuntimeFiles,
138
- ...(modelCatalog === undefined ? [] : [modelCatalog]),
139
- ]);
140
- const profile = [
141
- "(version 1)",
142
- "(deny default)",
143
- '(import "dyld-support.sb")',
144
- "(deny file-link file-clone)",
145
- // Native Codex creates runtime threads after entering Seatbelt. macOS
146
- // classifies that operation under process-fork; process-exec remains
147
- // restricted to the checksum-pinned Codex binary below.
148
- "(allow process-fork)",
149
- "(allow process-info* (target same-sandbox))",
150
- "(allow signal (target same-sandbox))",
151
- `(allow file-read-metadata ${metadata})`,
152
- `(allow process-exec (literal "${sbpl(binary)}"))`,
153
- `(allow file-map-executable (literal "${sbpl(binary)}"))`,
154
- '(allow sysctl-read (sysctl-name "hw.activecpu") (sysctl-name "hw.logicalcpu") (sysctl-name "hw.machine") (sysctl-name "hw.model") (sysctl-name "hw.ncpu") (sysctl-name "hw.pagesize") (sysctl-name "hw.pagesize_compat") (sysctl-name "kern.argmax") (sysctl-name "kern.hostname") (sysctl-name "kern.osproductversion") (sysctl-name "kern.osrelease") (sysctl-name "kern.ostype") (sysctl-name "kern.osversion") (sysctl-name "kern.usrstack64") (sysctl-name "kern.version") (sysctl-name-regex #"^net.routetable"))',
155
- // This is the finite service set required by native Codex startup plus the
156
- // DNS/TLS services in Codex's own macOS network policy. No wildcard Mach
157
- // lookup or user application service is admitted.
158
- '(allow mach-lookup (global-name "com.apple.SecurityServer") (global-name "com.apple.SystemConfiguration.DNSConfiguration") (global-name "com.apple.SystemConfiguration.configd") (global-name "com.apple.bsd.dirhelper") (global-name "com.apple.cfprefsd.agent") (local-name "com.apple.cfprefsd.agent") (global-name "com.apple.logd") (global-name "com.apple.networkd") (global-name "com.apple.ocspd") (global-name "com.apple.system.notification_center") (global-name "com.apple.system.opendirectoryd.libinfo") (global-name "com.apple.system.opendirectoryd.membership") (global-name "com.apple.trustd") (global-name "com.apple.trustd.agent"))',
159
- '(allow ipc-posix-shm-read* (ipc-posix-name "apple.shm.notification_center") (ipc-posix-name-prefix "apple.cfprefs."))',
160
- '(allow system-socket (require-all (socket-domain AF_SYSTEM) (socket-protocol 2)))',
161
- '(allow system-socket (socket-domain AF_UNIX))',
162
- '(allow file-read* (subpath "/Library/Apple") (subpath "/System") (subpath "/usr/lib") (subpath "/usr/share") (subpath "/private/etc/ssl") (subpath "/private/var/db/timezone") (literal "/dev/null") (literal "/dev/urandom"))',
163
- `(allow file-read* file-test-existence (subpath "${sbpl(cwd)}") (subpath "${sbpl(lexical.cwd)}") (subpath "${sbpl(authHome)}") (subpath "${sbpl(lexical.authHome)}") ${readableFiles})`,
164
- `(allow file-write* (subpath "${sbpl(authHome)}") (subpath "${sbpl(lexical.authHome)}") (literal "${sbpl(finalPath)}") (literal "/dev/null"))`,
165
- // Chromium's macOS network profile isolates the resolver socket from
166
- // ordinary IP traffic. This keeps AF_UNIX open only for mDNSResponder,
167
- // while permitting the model request over TCP/UDP.
168
- '(allow network-outbound (control-name "com.apple.netsrc") (literal "/private/var/run/mDNSResponder") (remote tcp) (remote udp))',
169
- ].join(" ");
170
- return ["/usr/bin/sandbox-exec", "-p", profile, "--", ...inner];
171
- }
172
- /**
173
- * Build the complete, pinned Codex lane used for untrusted synthesis.
174
- *
175
- * Read-only sandboxing alone still permits host reads. This lane registers no
176
- * shell, patch, browser, app, MCP, memory, or computer-use surface at all.
177
- * The only accepted product is the final response constrained by outputSchema.
178
- */
179
- export function toolFreeCodexExecArgv(options) {
180
- return [
181
- options.binary,
182
- "exec",
183
- "--ephemeral",
184
- "--skip-git-repo-check",
185
- "--ignore-user-config",
186
- "--ignore-rules",
187
- "--strict-config",
188
- "--sandbox",
189
- "read-only",
190
- "--model",
191
- options.model,
192
- "--output-schema",
193
- options.outputSchema,
194
- "--output-last-message",
195
- options.finalPath,
196
- "--json",
197
- "--color",
198
- "never",
199
- "--cd",
200
- options.cwd,
201
- "--config",
202
- "shell_environment_policy.inherit=none",
203
- "--config",
204
- "approval_policy=\"never\"",
205
- "--config",
206
- "web_search=\"disabled\"",
207
- ...(options.reasoningEffort === undefined
208
- ? []
209
- : ["--config", `model_reasoning_effort=\"${options.reasoningEffort}\"`]),
210
- ...CODEX_TOOL_FEATURES.flatMap((feature) => ["--disable", feature]),
211
- "-",
212
- ];
213
- }
@@ -1,8 +0,0 @@
1
- import { type CommandRunner } from "../util/command.js";
2
- export declare function withdrawProduct(options: {
3
- frontier: string;
4
- runFile: string;
5
- reason: string;
6
- storeRoot?: string;
7
- runner?: CommandRunner;
8
- }): Promise<Record<string, unknown>>;
@@ -1,255 +0,0 @@
1
- import os from "node:os";
2
- import path from "node:path";
3
- import { mkdir, mkdtemp, readdir, realpath, rm } from "node:fs/promises";
4
- import { consumeWithdrawalCapability, installWithdrawalCapabilitySecret, loadWithdrawalCapability, } from "../capability/withdrawal.js";
5
- import { parseRunRecord } from "../projection/run.js";
6
- import { canonicalJcs, contentDigest, protocolDigest, sha256Bytes, } from "../util/canonical.js";
7
- import { isolatedEnvironment, runCommand } from "../util/command.js";
8
- import { readBoundedRegularFile } from "../util/files.js";
9
- import { VelaClient } from "../vela/cli.js";
10
- async function gitText(runner, cwd, home, argv) {
11
- const result = await runner({
12
- argv: ["git", ...argv],
13
- cwd,
14
- env: isolatedEnvironment(home),
15
- timeoutMs: 120_000,
16
- maxOutputBytes: 8 * 1024 * 1024,
17
- });
18
- if (result.exitCode !== 0 || result.stderr.length !== 0) {
19
- throw new Error(`git ${argv.join(" ")} failed: stdout=${sha256Bytes(result.stdout)} stderr=${sha256Bytes(result.stderr)}`);
20
- }
21
- return result.stdout.toString("utf8").trim();
22
- }
23
- function safeActor(actor) {
24
- return [...actor].map((character) => /[A-Za-z0-9_-]/u.test(character) ? character : "-").join("");
25
- }
26
- async function scientificProjectionRoot(frontierRoot) {
27
- const project = JSON.parse((await readBoundedRegularFile(path.join(frontierRoot, "frontier.json"), 128 * 1024 * 1024))
28
- .toString("utf8"));
29
- const fields = [
30
- "actors",
31
- "anchor_links",
32
- "artifacts",
33
- "condition_records",
34
- "evidence_atoms",
35
- "findings",
36
- "frontier",
37
- "frontier_id",
38
- "released_diff_packs",
39
- "sources",
40
- "statement_attestations",
41
- "verifier_attachments",
42
- ];
43
- return contentDigest(Object.fromEntries(fields.map((field) => [field, project[field] ?? null])));
44
- }
45
- function jsonObject(value, at) {
46
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
47
- throw new Error(`${at} must be an object`);
48
- }
49
- return value;
50
- }
51
- function proposalStanding(value) {
52
- if (value !== "pending_review" &&
53
- value !== "accepted" &&
54
- value !== "applied" &&
55
- value !== "rejected" &&
56
- value !== "revision_requested" &&
57
- value !== "needs_revision" &&
58
- value !== "withdrawn") {
59
- throw new Error(`proposal has unrecognized standing ${JSON.stringify(value)}`);
60
- }
61
- return value;
62
- }
63
- async function frontierWithin(repo, frontier) {
64
- const resolved = await realpath(path.resolve(repo, frontier));
65
- if (resolved !== repo && !resolved.startsWith(`${repo}${path.sep}`)) {
66
- throw new Error("capability frontier resolves outside the selected source repository");
67
- }
68
- return resolved;
69
- }
70
- async function terminalProposalEvent(frontierRoot, proposalId) {
71
- const directory = path.join(frontierRoot, ".vela", "events");
72
- const entries = await readdir(directory, { withFileTypes: true });
73
- if (entries.length > 1_000_000)
74
- throw new Error("frontier event directory exceeds the supported audit bound");
75
- const matches = [];
76
- const decisionByKind = new Map([
77
- ["review.accepted", "accepted"],
78
- ["review.rejected", "rejected"],
79
- ["review.revision_requested", "revision_requested"],
80
- ["proposal.withdrawn", "withdrawn"],
81
- ]);
82
- for (const entry of entries) {
83
- if (!entry.isFile() || !/^vev_[0-9a-f]{16}\.json$/u.test(entry.name))
84
- continue;
85
- const event = jsonObject(JSON.parse((await readBoundedRegularFile(path.join(directory, entry.name), 8 * 1024 * 1024)).toString("utf8")), `event ${entry.name}`);
86
- const decision = typeof event.kind === "string" ? decisionByKind.get(event.kind) : undefined;
87
- if (decision === undefined)
88
- continue;
89
- const target = jsonObject(event.target, `event ${entry.name}.target`);
90
- if (target.type !== "proposal" || target.id !== proposalId)
91
- continue;
92
- const eventId = entry.name.slice(0, -".json".length);
93
- if (event.id !== eventId || typeof event.signature !== "string" || event.signature.length === 0) {
94
- throw new Error(`terminal proposal event ${entry.name} is not a signed canonical event file`);
95
- }
96
- const payload = jsonObject(event.payload, `event ${entry.name}.payload`);
97
- if (payload.proposal_id !== proposalId) {
98
- throw new Error(`terminal proposal event ${entry.name} payload targets another proposal`);
99
- }
100
- if (decision === "withdrawn") {
101
- if (payload.schema !== "vela.proposal-withdrawal.v1") {
102
- throw new Error(`withdrawal event ${entry.name} has the wrong payload schema`);
103
- }
104
- }
105
- else if (payload.verdict !== decision) {
106
- throw new Error(`review event ${entry.name} verdict disagrees with its kind`);
107
- }
108
- matches.push({ event_id: eventId, decision });
109
- }
110
- if (matches.length > 1)
111
- throw new Error("proposal has conflicting terminal events");
112
- return matches[0] ?? null;
113
- }
114
- function terminalDecisionMatchesStanding(decision, standing) {
115
- return decision === "accepted"
116
- ? standing === "accepted" || standing === "applied"
117
- : decision === "revision_requested"
118
- ? standing === "revision_requested" || standing === "needs_revision"
119
- : decision === standing;
120
- }
121
- export async function withdrawProduct(options) {
122
- if (options.reason.trim().length === 0)
123
- throw new Error("withdrawal reason must not be empty");
124
- const runner = options.runner ?? runCommand;
125
- const source = await realpath(options.frontier);
126
- const runFile = path.resolve(options.runFile);
127
- const record = parseRunRecord(JSON.parse((await readBoundedRegularFile(runFile, 8 * 1024 * 1024)).toString("utf8")));
128
- const capability = await loadWithdrawalCapability(record.landing.proposal_id, options.storeRoot);
129
- if (capability.manifest.state !== "available" || !capability.secret_available) {
130
- throw new Error("withdrawal capability is not available");
131
- }
132
- if (capability.manifest.receipt_root !== record.landing.receipt_root ||
133
- canonicalJcs(capability.manifest.final_roots) !== canonicalJcs(record.final_roots)) {
134
- throw new Error("withdrawal capability does not match the selected run");
135
- }
136
- const controlRoot = await mkdtemp(path.join(os.tmpdir(), "canopus-withdraw-"));
137
- const controlHome = path.join(controlRoot, "home");
138
- const clone = path.join(controlRoot, "candidate");
139
- const replay = path.join(controlRoot, "replay");
140
- await mkdir(path.join(controlHome, ".vela", "agents", safeActor(capability.manifest.actor)), {
141
- recursive: true,
142
- mode: 0o700,
143
- });
144
- try {
145
- const sourceHead = await gitText(runner, source, controlHome, ["rev-parse", "--verify", "HEAD^{commit}"]);
146
- const sourceStatus = await gitText(runner, source, controlHome, ["status", "--porcelain=v1", "--untracked-files=all"]);
147
- if (sourceStatus !== "")
148
- throw new Error("source frontier must be clean before withdrawal");
149
- const client = new VelaClient({
150
- binary: capability.manifest.vela.binary,
151
- expectedVersion: capability.manifest.vela.version,
152
- expectedSha256: capability.manifest.vela.sha256,
153
- home: controlHome,
154
- runner,
155
- });
156
- const strictBaseline = capability.manifest.strict_baseline;
157
- const sourceInspection = await client.inspect(source, capability.manifest.frontier, strictBaseline);
158
- const frontierRoot = await frontierWithin(source, capability.manifest.frontier);
159
- const proposalPath = path.join(frontierRoot, ".vela", "proposals", `${record.landing.proposal_id}.json`);
160
- const proposal = jsonObject(JSON.parse((await readBoundedRegularFile(proposalPath, 8 * 1024 * 1024)).toString("utf8")), "proposal");
161
- if (proposal.id !== record.landing.proposal_id)
162
- throw new Error("proposal file id disagrees with the run");
163
- const standing = proposalStanding(proposal.status);
164
- const terminalEvent = await terminalProposalEvent(frontierRoot, record.landing.proposal_id);
165
- const stableHead = await gitText(runner, source, controlHome, ["rev-parse", "--verify", "HEAD^{commit}"]);
166
- const stableStatus = await gitText(runner, source, controlHome, ["status", "--porcelain=v1", "--untracked-files=all"]);
167
- if (stableHead !== sourceHead || stableStatus !== "") {
168
- throw new Error("source frontier changed during proposal verification");
169
- }
170
- if (standing !== "pending_review") {
171
- if (terminalEvent === null || !terminalDecisionMatchesStanding(terminalEvent.decision, standing)) {
172
- throw new Error("terminal proposal standing is not backed by exactly one matching signed event");
173
- }
174
- await consumeWithdrawalCapability(record.landing.proposal_id, terminalEvent.decision === "withdrawn" ? "withdrawn" : "human_decision_observed", options.storeRoot);
175
- return {
176
- ok: true,
177
- command: "withdraw",
178
- proposal_id: record.landing.proposal_id,
179
- terminal_event_id: terminalEvent.event_id,
180
- state: terminalEvent.decision === "withdrawn" ? "already_withdrawn" : "human_decision_observed",
181
- frontier_mutated: false,
182
- capability_consumed: true,
183
- };
184
- }
185
- if (terminalEvent !== null)
186
- throw new Error("pending proposal already has a terminal event");
187
- if (protocolDigest(proposal) !== capability.manifest.proposal_root) {
188
- throw new Error("pending proposal bytes drifted from the retained withdrawal capability");
189
- }
190
- await gitText(runner, controlRoot, controlHome, ["clone", "--quiet", "--no-local", source, clone]);
191
- const before = await client.inspect(clone, capability.manifest.frontier, strictBaseline);
192
- if (canonicalJcs(before.roots) !== canonicalJcs(sourceInspection.roots)) {
193
- throw new Error("disposable withdrawal clone does not match the selected source head");
194
- }
195
- await installWithdrawalCapabilitySecret(record.landing.proposal_id, path.join(controlHome, ".vela", "agents", safeActor(capability.manifest.actor), "private.key"), options.storeRoot);
196
- const scientificRootBefore = await scientificProjectionRoot(path.resolve(clone, capability.manifest.frontier));
197
- const withdrawal = await client.withdraw(clone, capability.manifest.frontier, record.landing.proposal_id, capability.manifest.actor, options.reason);
198
- const after = await client.inspect(clone, capability.manifest.frontier, strictBaseline);
199
- const scientificRootAfter = await scientificProjectionRoot(path.resolve(clone, capability.manifest.frontier));
200
- if (after.roots.git_commit === before.roots.git_commit || after.roots.vela_event_log === before.roots.vela_event_log) {
201
- throw new Error("withdrawal did not append and publish one event");
202
- }
203
- if (withdrawal.publication_commit !== after.roots.git_commit ||
204
- withdrawal.state_root_before !== before.roots.vela_event_log ||
205
- withdrawal.state_root_after !== after.roots.vela_event_log) {
206
- throw new Error("withdrawal response does not bind the verified Git and event-log roots");
207
- }
208
- if (scientificRootAfter !== scientificRootBefore) {
209
- throw new Error("withdrawal changed the accepted scientific projection");
210
- }
211
- const withdrawnFrontier = await frontierWithin(await realpath(clone), capability.manifest.frontier);
212
- const withdrawnProposal = jsonObject(JSON.parse((await readBoundedRegularFile(path.join(withdrawnFrontier, ".vela", "proposals", `${record.landing.proposal_id}.json`), 8 * 1024 * 1024)).toString("utf8")), "withdrawn proposal");
213
- const withdrawnEvent = await terminalProposalEvent(withdrawnFrontier, record.landing.proposal_id);
214
- if (withdrawnProposal.status !== "withdrawn" ||
215
- withdrawnEvent?.decision !== "withdrawn" ||
216
- withdrawnEvent.event_id !== withdrawal.withdrawal_event_id) {
217
- throw new Error("withdrawal output is not backed by the exact materialized terminal event");
218
- }
219
- await gitText(runner, controlRoot, controlHome, ["clone", "--quiet", "--no-local", clone, replay]);
220
- const reproduced = await client.inspect(replay, capability.manifest.frontier, strictBaseline);
221
- if (canonicalJcs(reproduced.roots) !== canonicalJcs(after.roots)) {
222
- throw new Error("clean-clone withdrawal replay drifted");
223
- }
224
- const unchangedHead = await gitText(runner, source, controlHome, ["rev-parse", "--verify", "HEAD^{commit}"]);
225
- const unchangedStatus = await gitText(runner, source, controlHome, ["status", "--porcelain=v1", "--untracked-files=all"]);
226
- if (unchangedHead !== sourceHead || unchangedStatus !== "") {
227
- throw new Error("source frontier changed during withdrawal");
228
- }
229
- await gitText(runner, source, controlHome, ["fetch", "--quiet", "--no-tags", clone, "HEAD"]);
230
- await gitText(runner, source, controlHome, ["merge", "--ff-only", "--no-edit", "FETCH_HEAD"]);
231
- const installed = await client.inspect(source, capability.manifest.frontier, strictBaseline);
232
- if (canonicalJcs(installed.roots) !== canonicalJcs(after.roots)) {
233
- throw new Error("fast-forwarded source does not match the verified withdrawal clone");
234
- }
235
- const installedStatus = await gitText(runner, source, controlHome, ["status", "--porcelain=v1", "--untracked-files=all"]);
236
- if (installedStatus !== "")
237
- throw new Error("fast-forwarded source is not clean");
238
- await consumeWithdrawalCapability(record.landing.proposal_id, "withdrawn", options.storeRoot);
239
- return {
240
- ok: true,
241
- command: "withdraw",
242
- proposal_id: record.landing.proposal_id,
243
- withdrawal_event_id: withdrawal.withdrawal_event_id,
244
- roots: after.roots,
245
- scientific_state_root_before: scientificRootBefore,
246
- scientific_state_root_after: scientificRootAfter,
247
- accepted_state_changed: false,
248
- clean_clone_reproduced: true,
249
- capability_consumed: true,
250
- };
251
- }
252
- finally {
253
- await rm(controlRoot, { recursive: true, force: true });
254
- }
255
- }
@@ -1,12 +0,0 @@
1
- import type { DiagnosticRunRecord } from "../run.js";
2
- export declare function parseDiagnosticRunRecord(value: unknown): DiagnosticRunRecord;
3
- export declare function projectDiagnosticRun(record: DiagnosticRunRecord): {
4
- schema: "canopus.diagnostic-projection.v1";
5
- authority: "read_only_projection";
6
- run_id: string;
7
- target: string;
8
- candidate_digest: string;
9
- verifier_status: "passed" | "failed" | "error";
10
- landed: false;
11
- clean_clone_reproduced: true;
12
- };
@@ -1,61 +0,0 @@
1
- import { arrayAt, enumAt, exactKeys, objectAt, relativePathAt, sha256At, stringAt, } from "../contracts/validation.js";
2
- function literal(value, expected, at) {
3
- if (value !== expected)
4
- throw new Error(`${at} must be ${String(expected)}`);
5
- return expected;
6
- }
7
- export function parseDiagnosticRunRecord(value) {
8
- const record = objectAt(value, "diagnostic run");
9
- exactKeys(record, [
10
- "schema", "run_id", "status", "mode", "authority", "external_gate_credit",
11
- "mission", "candidate", "verifier", "landing", "reproduction", "budget",
12
- ], [], "diagnostic run");
13
- const mission = objectAt(record.mission, "diagnostic run.mission");
14
- exactKeys(mission, ["id", "target", "digest", "starting_roots"], [], "diagnostic run.mission");
15
- const candidate = objectAt(record.candidate, "diagnostic run.candidate");
16
- exactKeys(candidate, ["digest", "status", "claim", "artifacts", "caveats"], [], "diagnostic run.candidate");
17
- const verifier = objectAt(record.verifier, "diagnostic run.verifier");
18
- exactKeys(verifier, ["status", "sandbox", "record"], [], "diagnostic run.verifier");
19
- const reproduction = objectAt(record.reproduction, "diagnostic run.reproduction");
20
- exactKeys(reproduction, ["matched", "roots", "verifier_status", "stdout_digest", "stderr_digest"], [], "diagnostic run.reproduction");
21
- objectAt(mission.starting_roots, "diagnostic run.mission.starting_roots");
22
- objectAt(reproduction.roots, "diagnostic run.reproduction.roots");
23
- objectAt(record.budget, "diagnostic run.budget");
24
- literal(record.schema, "canopus.diagnostic-run.v1", "diagnostic run.schema");
25
- literal(record.status, "completed", "diagnostic run.status");
26
- literal(record.mode, "no_land", "diagnostic run.mode");
27
- literal(record.authority, "non_authoritative", "diagnostic run.authority");
28
- literal(record.external_gate_credit, false, "diagnostic run.external_gate_credit");
29
- literal(record.landing, null, "diagnostic run.landing");
30
- stringAt(record.run_id, "diagnostic run.run_id", { min: 5, max: 128 });
31
- stringAt(mission.id, "diagnostic run.mission.id", { min: 1, max: 134 });
32
- stringAt(mission.target, "diagnostic run.mission.target", { min: 1, max: 256 });
33
- sha256At(mission.digest, "diagnostic run.mission.digest");
34
- sha256At(candidate.digest, "diagnostic run.candidate.digest");
35
- enumAt(candidate.status, "diagnostic run.candidate.status", ["success", "null", "failed"]);
36
- stringAt(candidate.claim, "diagnostic run.candidate.claim", { min: 1, max: 8192 });
37
- arrayAt(candidate.artifacts, "diagnostic run.candidate.artifacts", { max: 10 }, (item, at) => {
38
- const artifact = objectAt(item, at);
39
- exactKeys(artifact, ["path", "kind", "digest", "bytes"], [], at);
40
- relativePathAt(artifact.path, `${at}.path`);
41
- stringAt(artifact.kind, `${at}.kind`, { min: 1, max: 128 });
42
- sha256At(artifact.digest, `${at}.digest`);
43
- return true;
44
- });
45
- arrayAt(candidate.caveats, "diagnostic run.candidate.caveats", { max: 10 }, (item, at) => stringAt(item, at, { min: 1, max: 4096 }));
46
- enumAt(verifier.status, "diagnostic run.verifier.status", ["passed", "failed", "error"]);
47
- literal(reproduction.matched, true, "diagnostic run.reproduction.matched");
48
- return value;
49
- }
50
- export function projectDiagnosticRun(record) {
51
- return {
52
- schema: "canopus.diagnostic-projection.v1",
53
- authority: "read_only_projection",
54
- run_id: record.run_id,
55
- target: record.mission.target,
56
- candidate_digest: record.candidate.digest,
57
- verifier_status: record.verifier.status,
58
- landed: false,
59
- clean_clone_reproduced: true,
60
- };
61
- }
@@ -1,59 +0,0 @@
1
- import type { Mission } from "../contracts/mission.js";
2
- import type { RunRecord } from "./run.js";
3
- export declare const PUBLIC_RUN_SCHEMA: "canopus.public-run.v1";
4
- export interface PublicRunProjection {
5
- schema: typeof PUBLIC_RUN_SCHEMA;
6
- authority: "read_only_projection";
7
- run_id: string;
8
- mission: {
9
- id: string;
10
- target: string;
11
- digest: string;
12
- registration_kind: "profile" | "mission_bundle";
13
- registration_name: string;
14
- registration_root: string;
15
- target_packet_root: string;
16
- model: string;
17
- };
18
- activity: {
19
- worker: "success";
20
- verifier: "pass";
21
- clean_clone_replay: "matched";
22
- };
23
- claim: string;
24
- caveats: string[];
25
- artifact_roots: string[];
26
- verifier_root: string;
27
- receipt_root: string;
28
- policy: {
29
- route: "defer";
30
- proposal_id: string;
31
- accepted_state_delta: 0;
32
- };
33
- usage: {
34
- research_elapsed_ms: number;
35
- research_processes: number;
36
- observed_tokens: number;
37
- attempts: number;
38
- };
39
- source: {
40
- repository: string;
41
- commit: string;
42
- event_log_root: string;
43
- snapshot_root: string;
44
- };
45
- final: {
46
- commit: string;
47
- event_log_root: string;
48
- snapshot_root: string;
49
- };
50
- reproduction: {
51
- commands: string[];
52
- };
53
- nonclaims: string[];
54
- }
55
- export declare function projectPublicRun(options: {
56
- record: RunRecord;
57
- mission: Mission;
58
- repository: string;
59
- }): PublicRunProjection;