@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,213 @@
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
+ }
@@ -0,0 +1,30 @@
1
+ import { type CommandRunner } from "../util/command.js";
2
+ import type { Engine, EngineContext, EngineResult } from "./engine.js";
3
+ export declare const NATIVE_WORKER_DISABLED_FEATURES: readonly ["apps", "artifact", "auth_elicitation", "browser_use", "browser_use_external", "computer_use", "enable_fanout", "enable_mcp_apps", "goals", "hooks", "image_generation", "in_app_browser", "memories", "multi_agent", "multi_agent_v2", "plugin_sharing", "plugins", "remote_plugin", "standalone_web_search", "tool_call_mcp_elicitation", "tool_suggest", "workspace_dependencies"];
4
+ export interface CodexToolsNativeOptions {
5
+ binary: string;
6
+ authHome: string;
7
+ outputSchema: string;
8
+ permissionProfile: string;
9
+ runner?: CommandRunner;
10
+ }
11
+ export declare function assertNativeRuntimeProfile(options: {
12
+ binary: string;
13
+ runner: CommandRunner;
14
+ environment: NodeJS.ProcessEnv;
15
+ cwd: string;
16
+ sourceAuth: string;
17
+ runtimeAuth: string;
18
+ inaccessibleInput: string;
19
+ unrelatedFile: string;
20
+ canary: string;
21
+ outsideWrite: string;
22
+ timeoutMs: number;
23
+ includeSafeDiagnostics?: boolean;
24
+ }): Promise<void>;
25
+ export declare class CodexToolsNativeEngine implements Engine {
26
+ #private;
27
+ readonly name = "codex-tools-native";
28
+ constructor(options: CodexToolsNativeOptions);
29
+ run(context: EngineContext): Promise<EngineResult>;
30
+ }
@@ -0,0 +1,405 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import { chmod, copyFile, lstat, mkdir, realpath, rm, writeFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import process from "node:process";
6
+ import { assertDraftArtifactsAllowed, parseCandidateDraft } from "../candidate/validate.js";
7
+ import { canonicalJson, contentDigest, sha256Bytes } from "../util/canonical.js";
8
+ import { isolatedEnvironment, runCommand } from "../util/command.js";
9
+ import { MAX_EXECUTABLE_BYTES, readBoundedRegularFile, sha256RegularFile } from "../util/files.js";
10
+ import { parseCodexEvents, summarizeCodexFailure } from "./codex-events.js";
11
+ import { prepareIsolatedCodexHome, removeIsolatedCodexHome } from "./codex-home.js";
12
+ export const NATIVE_WORKER_DISABLED_FEATURES = [
13
+ "apps",
14
+ "artifact",
15
+ "auth_elicitation",
16
+ "browser_use",
17
+ "browser_use_external",
18
+ "computer_use",
19
+ "enable_fanout",
20
+ "enable_mcp_apps",
21
+ "goals",
22
+ "hooks",
23
+ "image_generation",
24
+ "in_app_browser",
25
+ "memories",
26
+ "multi_agent",
27
+ "multi_agent_v2",
28
+ "plugin_sharing",
29
+ "plugins",
30
+ "remote_plugin",
31
+ "standalone_web_search",
32
+ "tool_call_mcp_elicitation",
33
+ "tool_suggest",
34
+ "workspace_dependencies",
35
+ ];
36
+ function prompt(mission) {
37
+ const execution = mission.target === "erdos:1056"
38
+ ? [
39
+ "This registered Erdős mission already supplies the complete algorithm, range, artifact grammar, and completion test. The packet identity has already been verified by the harness.",
40
+ "Do not enumerate the workspace, rediscover files, re-hash the packet, inspect unrelated packet fields, or search for the verifier. Start by writing the bounded search program, then compile and run it, inspect only the small result artifact, and return.",
41
+ mission.worker.platform === "darwin"
42
+ ? "The profile exposes the Xcode compiler and SDK directly. Compile inside the workspace with `mkdir -p artifacts tmp && TMPDIR=$PWD/tmp clang++ ...`; do not call `/usr/bin/clang++`, `xcrun`, or `xcodebuild`."
43
+ : "The profile exposes the system C++ compiler directly. Compile inside the workspace with `mkdir -p artifacts tmp && TMPDIR=$PWD/tmp c++ ...`; do not install packages or invoke a container from the worker.",
44
+ "Implement the registered flat table literally: table size 1<<25; slot=(uint64_t(residue)*11400714819323198485ull)>>(64-25); linear probing with mask; uint32 key and generation arrays plus uint8 counts; increment generation once per prime; factorial starts at 1 and multiplies by cut only when cut>0 before counting that cut.",
45
+ "For each prime choose the greatest count and then smallest residue. Across primes replace the best only on a strictly greater count, preserving the earliest prime on ties. After choosing the best prime/residue, recompute its increasing cut list from scratch.",
46
+ "Use at most six shell or patch tool calls. A token-efficient correct computation is part of this mission's product contract.",
47
+ ]
48
+ : mission.target === "formal:erdos-505-test-dim-one"
49
+ ? [
50
+ "This registered formal mission exposes exactly one packet file. Do not enumerate the workspace or use rg, find, Python, package managers, or external tools.",
51
+ `Use one narrow jq command to read only repair_context, source.statement, and output_contract from ${mission.target_packet.path}.`,
52
+ "Then write the single raw Lean term with apply_patch. An optional wc -c check is the only useful shell follow-up; do not print the finished artifact.",
53
+ "Return the artifact path and kind exactly as output_contract specifies. Status success means only that those complete candidate bytes exist; the separate frozen Lean capsule owns elaboration and the axiom audit.",
54
+ "Use at most four shell or patch tool calls. A token-efficient handoff is part of this mission's product contract.",
55
+ ]
56
+ : [];
57
+ return [
58
+ "Execute one bounded Canopus research mission inside a fresh writable workspace containing only the exact hash-verified target packet.",
59
+ "Use shell and apply_patch only when useful. Browser, web search, MCP, apps, memories, computer use, delegation, signing, and human keys are forbidden.",
60
+ "Do not inspect Codex configuration, credentials, process state, unrelated repositories, or paths outside the current workspace.",
61
+ "Command network is denied. Do not invoke Vela or the separately frozen verifier as an authority oracle.",
62
+ `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.`,
63
+ "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.",
64
+ "Keep tool output narrow. Do not print or ingest the whole target packet.",
65
+ "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.",
66
+ "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.",
67
+ "Return only the supplied engine-output JSON shape. Artifact bytes must be inline UTF-8 content at mission.allowed_paths.",
68
+ ...execution,
69
+ "Mission:",
70
+ canonicalJson(mission),
71
+ ].join("\n");
72
+ }
73
+ async function prepareTargetPacketWorkspace(sourceRoot, destination, mission) {
74
+ const root = await realpath(sourceRoot);
75
+ const source = await realpath(path.join(root, mission.target_packet.path));
76
+ const relative = path.relative(root, source);
77
+ if (relative === "" || relative === ".." || relative.startsWith(`..${path.sep}`)) {
78
+ throw new Error("native worker target packet escapes the sealed source checkout");
79
+ }
80
+ const stat = await lstat(source);
81
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) {
82
+ throw new Error("native worker target packet must be one regular, singly linked file");
83
+ }
84
+ const bytes = await readBoundedRegularFile(source, 64 * 1024 * 1024);
85
+ if (sha256Bytes(bytes) !== mission.target_packet.sha256) {
86
+ throw new Error("native worker target packet drifted from the prepared mission");
87
+ }
88
+ const target = path.join(destination, mission.target_packet.path);
89
+ await mkdir(path.dirname(target), { recursive: true, mode: 0o700 });
90
+ await writeFile(target, bytes, { flag: "wx", mode: 0o600 });
91
+ }
92
+ async function stageLinuxSandboxBinary(workspace, sourceBinary, expectedDigest) {
93
+ if (process.platform !== "linux")
94
+ return sourceBinary;
95
+ const runtimeDirectory = path.join(workspace, ".canopus-runtime");
96
+ await mkdir(runtimeDirectory, { recursive: false, mode: 0o700 });
97
+ const runtimeBinary = path.join(runtimeDirectory, "codex");
98
+ await copyFile(sourceBinary, runtimeBinary, constants.COPYFILE_EXCL);
99
+ await chmod(runtimeBinary, 0o500);
100
+ if (await sha256RegularFile(runtimeBinary, MAX_EXECUTABLE_BYTES) !== expectedDigest) {
101
+ throw new Error("native Codex binary changed while it was staged for the Linux sandbox");
102
+ }
103
+ return runtimeBinary;
104
+ }
105
+ function authSecrets(bytes) {
106
+ let value;
107
+ try {
108
+ value = JSON.parse(bytes.toString("utf8"));
109
+ }
110
+ catch (error) {
111
+ throw new Error(`Codex auth file is invalid JSON: ${String(error)}`);
112
+ }
113
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
114
+ throw new Error("Codex auth file must be an object");
115
+ }
116
+ const object = value;
117
+ const tokens = typeof object.tokens === "object" && object.tokens !== null && !Array.isArray(object.tokens)
118
+ ? object.tokens
119
+ : {};
120
+ return [
121
+ object.OPENAI_API_KEY,
122
+ tokens.access_token,
123
+ tokens.id_token,
124
+ tokens.refresh_token,
125
+ ]
126
+ .filter((item) => typeof item === "string" && item.length >= 16)
127
+ .map((item) => Buffer.from(item));
128
+ }
129
+ function assertNoSecrets(buffers, secrets) {
130
+ for (const buffer of buffers) {
131
+ for (const secret of secrets) {
132
+ if (buffer.includes(secret))
133
+ throw new Error("native Codex worker exposed authentication material");
134
+ }
135
+ }
136
+ }
137
+ function workerArgv(options) {
138
+ return [
139
+ options.binary,
140
+ "exec",
141
+ "--ephemeral",
142
+ "--strict-config",
143
+ "--ignore-rules",
144
+ "--skip-git-repo-check",
145
+ "--model",
146
+ options.mission.worker.model,
147
+ "--output-schema",
148
+ options.outputSchema,
149
+ "--output-last-message",
150
+ options.finalPath,
151
+ "--json",
152
+ "--color",
153
+ "never",
154
+ "--cd",
155
+ options.cwd,
156
+ "--config",
157
+ 'web_search="disabled"',
158
+ "--config",
159
+ 'model_reasoning_effort="low"',
160
+ ...NATIVE_WORKER_DISABLED_FEATURES.flatMap((feature) => ["--disable", feature]),
161
+ "-",
162
+ ];
163
+ }
164
+ function requireV1(context) {
165
+ if (context.mission.schema !== "canopus.mission.v1") {
166
+ throw new Error("the native tool worker accepts only canopus.mission.v1");
167
+ }
168
+ return context.mission;
169
+ }
170
+ export async function assertNativeRuntimeProfile(options) {
171
+ const script = [
172
+ "curl=false; source=false; runtime=false; input=false; unrelated=false; canary=false; writable=false; network=false; environ=false; proc=false;",
173
+ 'if /usr/bin/curl --version >/dev/null 2>&1; then curl=true; fi;',
174
+ 'if /bin/dd if="$1" of=/dev/null bs=1 count=1 2>/dev/null; then source=true; fi;',
175
+ 'if /bin/dd if="$2" of=/dev/null bs=1 count=1 2>/dev/null; then runtime=true; fi;',
176
+ 'if /bin/dd if="$3" of=/dev/null bs=1 count=1 2>/dev/null; then input=true; fi;',
177
+ 'if /bin/dd if="$4" of=/dev/null bs=1 count=1 2>/dev/null; then unrelated=true; fi;',
178
+ 'if /bin/dd if="$5" of=/dev/null bs=1 count=1 2>/dev/null; then canary=true; fi;',
179
+ 'if { printf "probe\\n" > "$6"; } 2>/dev/null; then writable=true; fi;',
180
+ 'if /usr/bin/curl --fail --silent --show-error --max-time 3 --output /dev/null https://example.com/ 2>/dev/null; then network=true; fi;',
181
+ "if /usr/bin/env | /usr/bin/grep -Eq '^(OPENAI_API_KEY|CODEX_API_KEY|CANOPUS_AUTH)='; then environ=true; fi;",
182
+ "if [ -r /proc/1/environ ] && /usr/bin/grep -aEq '(OPENAI_API_KEY|CODEX_API_KEY|CANOPUS_AUTH)=' /proc/1/environ; then proc=true; fi;",
183
+ 'printf "%s %s %s %s %s %s %s %s %s %s\\n" "$curl" "$source" "$runtime" "$input" "$unrelated" "$canary" "$writable" "$network" "$environ" "$proc";',
184
+ ].join(" ");
185
+ const result = await options.runner({
186
+ argv: [
187
+ options.binary,
188
+ "sandbox",
189
+ "-P",
190
+ "canopus-worker",
191
+ "-C",
192
+ options.cwd,
193
+ "--sandbox-state-readable-root",
194
+ options.binary,
195
+ "--",
196
+ "/bin/sh",
197
+ "-c",
198
+ script,
199
+ "sh",
200
+ options.sourceAuth,
201
+ options.runtimeAuth,
202
+ options.inaccessibleInput,
203
+ options.unrelatedFile,
204
+ options.canary,
205
+ options.outsideWrite,
206
+ ],
207
+ cwd: options.cwd,
208
+ env: options.environment,
209
+ timeoutMs: Math.min(30_000, options.timeoutMs),
210
+ maxOutputBytes: 4096,
211
+ });
212
+ if (result.exitCode !== 0 ||
213
+ result.stderr.length !== 0 ||
214
+ result.stdout.toString("utf8").trim() !==
215
+ "true false false false false false false false false false") {
216
+ const stderr = result.stderr.toString("utf8");
217
+ if (stderr.includes("bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted")) {
218
+ throw new Error("native Codex sandbox is blocked by Ubuntu AppArmor user-namespace policy; " +
219
+ "install and load the targeted bwrap-userns-restrict profile documented at " +
220
+ "https://developers.openai.com/codex/concepts/sandboxing#prerequisites, then retry");
221
+ }
222
+ const stdout = result.stdout.toString("utf8").trim();
223
+ const boundedStdout = /^(?:true|false)(?: (?:true|false)){9}$/u.test(stdout)
224
+ ? stdout
225
+ : `sha256=${sha256Bytes(result.stdout)}`;
226
+ const boundedStderr = options.includeSafeDiagnostics === true &&
227
+ result.stderr.length <= 4096 &&
228
+ /^[\t\n\r\x20-\x7e]*$/u.test(stderr)
229
+ ? JSON.stringify(stderr.trim())
230
+ : `sha256=${sha256Bytes(result.stderr)}`;
231
+ throw new Error(`native Codex permission profile failed its deterministic custody preflight: ` +
232
+ `exit=${result.exitCode}; stdout=${boundedStdout}; stderr=${boundedStderr}`);
233
+ }
234
+ }
235
+ export class CodexToolsNativeEngine {
236
+ name = "codex-tools-native";
237
+ #options;
238
+ #runner;
239
+ constructor(options) {
240
+ this.#options = options;
241
+ this.#runner = options.runner ?? runCommand;
242
+ }
243
+ async run(context) {
244
+ const mission = requireV1(context);
245
+ if (process.platform !== mission.worker.platform) {
246
+ throw new Error(`native worker requires ${mission.worker.platform}, observed ${process.platform}`);
247
+ }
248
+ context.budget.beginAttempt();
249
+ const binary = await realpath(this.#options.binary);
250
+ const [binaryDigest, profileBytes, schemaBytes, sourceAuthBytes] = await Promise.all([
251
+ sha256RegularFile(binary, MAX_EXECUTABLE_BYTES),
252
+ readBoundedRegularFile(this.#options.permissionProfile, 8 * 1024 * 1024),
253
+ readBoundedRegularFile(this.#options.outputSchema, 8 * 1024 * 1024),
254
+ readBoundedRegularFile(path.join(this.#options.authHome, "auth.json"), 2 * 1024 * 1024),
255
+ ]);
256
+ const profileDigest = sha256Bytes(profileBytes);
257
+ const schemaDigest = sha256Bytes(schemaBytes);
258
+ if (binaryDigest !== mission.worker.codex_sha256)
259
+ throw new Error("native Codex binary drifted");
260
+ if (profileDigest !== mission.worker.permission_profile_sha256) {
261
+ throw new Error("native Codex permission profile drifted");
262
+ }
263
+ if (schemaDigest !== mission.worker.output_schema_sha256) {
264
+ throw new Error("native Codex output schema drifted");
265
+ }
266
+ const workspace = path.join(context.paths.work, "native-worker");
267
+ await prepareTargetPacketWorkspace(context.paths.input, workspace, mission);
268
+ const runtimeBinary = await stageLinuxSandboxBinary(workspace, binary, binaryDigest);
269
+ const canaryDirectory = path.join(context.paths.work, "custody-canary");
270
+ await mkdir(canaryDirectory, { mode: 0o700 });
271
+ const canaryBytes = Buffer.from(`canopus-host-secret-${randomBytes(32).toString("hex")}\n`);
272
+ const canary = path.join(canaryDirectory, "host-secret");
273
+ await writeFile(canary, canaryBytes, { flag: "wx", mode: 0o400 });
274
+ const runtimeCodexHome = await prepareIsolatedCodexHome(this.#options.authHome, context.paths.home, profileBytes);
275
+ const finalPath = path.join(workspace, ".canopus-final.json");
276
+ const environment = {
277
+ ...isolatedEnvironment(context.paths.home),
278
+ CODEX_HOME: runtimeCodexHome,
279
+ NO_COLOR: "1",
280
+ };
281
+ const secrets = [...authSecrets(sourceAuthBytes), canaryBytes.subarray(0, canaryBytes.length - 1)];
282
+ try {
283
+ context.budget.beginProcess();
284
+ const versionResult = await this.#runner({
285
+ argv: [runtimeBinary, "--version"],
286
+ cwd: workspace,
287
+ env: environment,
288
+ timeoutMs: Math.min(30_000, context.budget.remainingTimeMs()),
289
+ maxOutputBytes: 4096,
290
+ });
291
+ const version = versionResult.stdout.toString("utf8").trim();
292
+ if (versionResult.exitCode !== 0 ||
293
+ versionResult.stderr.length !== 0 ||
294
+ version !== mission.worker.codex_version) {
295
+ throw new Error(`expected ${mission.worker.codex_version}, observed ${JSON.stringify(version)}`);
296
+ }
297
+ context.budget.beginProcess();
298
+ await assertNativeRuntimeProfile({
299
+ binary: runtimeBinary,
300
+ runner: this.#runner,
301
+ environment: {
302
+ ...environment,
303
+ CANOPUS_AUTH: "canopus-preflight-environment-canary",
304
+ },
305
+ cwd: workspace,
306
+ sourceAuth: path.join(this.#options.authHome, "auth.json"),
307
+ runtimeAuth: path.join(runtimeCodexHome, "auth.json"),
308
+ inaccessibleInput: path.join(context.paths.input, mission.target_packet.path),
309
+ unrelatedFile: path.join(context.paths.landing, ".git", "HEAD"),
310
+ canary,
311
+ outsideWrite: path.join(context.paths.work, "outside-write"),
312
+ timeoutMs: context.budget.remainingTimeMs(),
313
+ });
314
+ const workerPrompt = prompt(mission);
315
+ context.budget.addPrompt(Buffer.byteLength(workerPrompt));
316
+ context.budget.beginProcess();
317
+ const started = performance.now();
318
+ const result = await this.#runner({
319
+ argv: workerArgv({
320
+ binary: runtimeBinary,
321
+ mission,
322
+ outputSchema: this.#options.outputSchema,
323
+ finalPath,
324
+ cwd: workspace,
325
+ }),
326
+ cwd: workspace,
327
+ env: environment,
328
+ timeoutMs: context.budget.remainingTimeMs(),
329
+ maxOutputBytes: context.budget.remainingOutputBytes(),
330
+ stdin: workerPrompt,
331
+ });
332
+ context.budget.addOutput(result.stdout.length + result.stderr.length);
333
+ assertNoSecrets([result.stdout, result.stderr], secrets);
334
+ if (result.exitCode !== 0) {
335
+ throw new Error(`native codex exec exited ${result.exitCode}: ` +
336
+ `${summarizeCodexFailure(result.stdout.toString("utf8"))}; ` +
337
+ `stdout_sha256=${sha256Bytes(result.stdout)}; stderr_sha256=${sha256Bytes(result.stderr)}`);
338
+ }
339
+ await Promise.all([
340
+ writeFile(path.join(context.paths.root, "worker-events.jsonl"), result.stdout, {
341
+ flag: "wx",
342
+ mode: 0o600,
343
+ }),
344
+ writeFile(path.join(context.paths.root, "worker-stderr.bin"), result.stderr, {
345
+ flag: "wx",
346
+ mode: 0o600,
347
+ }),
348
+ ]);
349
+ const events = parseCodexEvents(result.stdout.toString("utf8"));
350
+ try {
351
+ context.budget.addTokens(events.usage.input_tokens + events.usage.output_tokens);
352
+ }
353
+ catch (error) {
354
+ const message = error instanceof Error ? error.message : String(error);
355
+ throw new Error(`${message}; event_stream_sha256=${sha256Bytes(result.stdout)}; ` +
356
+ `stderr_sha256=${sha256Bytes(result.stderr)}`);
357
+ }
358
+ const finalBytes = await readBoundedRegularFile(finalPath, 1_048_576);
359
+ context.budget.addOutput(finalBytes.length);
360
+ assertNoSecrets([finalBytes], secrets);
361
+ await writeFile(path.join(context.paths.root, "worker-final.json"), finalBytes, {
362
+ flag: "wx",
363
+ mode: 0o600,
364
+ });
365
+ let raw;
366
+ try {
367
+ raw = JSON.parse(finalBytes.toString("utf8"));
368
+ }
369
+ catch (error) {
370
+ throw new Error(`native Codex final response is not JSON: ${String(error)}`);
371
+ }
372
+ const draft = parseCandidateDraft(raw);
373
+ assertDraftArtifactsAllowed(draft, mission.allowed_paths);
374
+ return {
375
+ draft,
376
+ engine: {
377
+ name: this.name,
378
+ version,
379
+ binary_sha256: binaryDigest,
380
+ model: mission.worker.model,
381
+ configuration_sha256: contentDigest({
382
+ worker: mission.worker,
383
+ permission_profile_sha256: profileDigest,
384
+ output_schema_sha256: schemaDigest,
385
+ disabled_features: NATIVE_WORKER_DISABLED_FEATURES,
386
+ source_projection: "exact_target_packet_only",
387
+ command_network: "denied",
388
+ credentials: "outside_command_read_boundary",
389
+ }),
390
+ },
391
+ usage: events.usage,
392
+ wallTimeMs: Math.max(0, Math.round(performance.now() - started)),
393
+ eventTypes: events.eventTypes,
394
+ actionTypes: events.actionTypes,
395
+ eventsDigest: sha256Bytes(result.stdout),
396
+ stderrDigest: sha256Bytes(result.stderr),
397
+ };
398
+ }
399
+ finally {
400
+ await removeIsolatedCodexHome(runtimeCodexHome);
401
+ await rm(canaryDirectory, { recursive: true, force: true });
402
+ await rm(workspace, { recursive: true, force: true });
403
+ }
404
+ }
405
+ }