mtech-ai-local-runner 0.1.8 → 0.1.11

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 (81) hide show
  1. package/README.md +56 -4
  2. package/dist/src/adapters/claude-code/adapter.js +162 -120
  3. package/dist/src/adapters/claude-code/completion-report.js +2 -64
  4. package/dist/src/adapters/claude-code/prompt-compiler.js +1 -60
  5. package/dist/src/adapters/claude-code/runtime-options.js +72 -0
  6. package/dist/src/adapters/codex/account-sources.js +113 -0
  7. package/dist/src/adapters/codex/adapter.js +217 -0
  8. package/dist/src/adapters/codex/app-server-events.js +84 -0
  9. package/dist/src/adapters/codex/compatibility.js +45 -0
  10. package/dist/src/adapters/codex/config-builder.js +43 -0
  11. package/dist/src/adapters/codex/exec-invocation.js +62 -0
  12. package/dist/src/adapters/codex/exec-launch-host.js +209 -0
  13. package/dist/src/adapters/codex/exec-observer.js +83 -0
  14. package/dist/src/adapters/codex/exec-preflight.js +41 -0
  15. package/dist/src/adapters/codex/exec-release-policy.js +44 -0
  16. package/dist/src/adapters/codex/exec-runtime-options.js +113 -0
  17. package/dist/src/adapters/codex/exec-tool-gate.js +81 -0
  18. package/dist/src/adapters/codex/global-instructions.js +32 -0
  19. package/dist/src/adapters/codex/host-admission.js +3 -0
  20. package/dist/src/adapters/codex/inspection-rpc.js +48 -0
  21. package/dist/src/adapters/codex/job-rpc.js +189 -0
  22. package/dist/src/adapters/codex/manual-policy-scope.js +67 -0
  23. package/dist/src/adapters/codex/native-policy.js +128 -0
  24. package/dist/src/adapters/codex/native-proof.js +14 -0
  25. package/dist/src/adapters/codex/native-session.js +356 -0
  26. package/dist/src/adapters/codex/plugin.js +26 -0
  27. package/dist/src/adapters/codex/pre-model-gate.js +112 -0
  28. package/dist/src/adapters/codex/prompt-compiler.js +13 -0
  29. package/dist/src/adapters/codex/runtime-options.js +163 -0
  30. package/dist/src/adapters/codex/scratch.js +28 -0
  31. package/dist/src/adapters/codex/stdio-rpc.js +113 -0
  32. package/dist/src/adapters/codex/stream-parser.js +85 -0
  33. package/dist/src/adapters/codex/supported-admission.js +97 -0
  34. package/dist/src/adapters/codex/tool-dispatcher.js +20 -0
  35. package/dist/src/adapters/codex/unmanaged-scope.js +85 -0
  36. package/dist/src/adapters/completion-report.js +64 -0
  37. package/dist/src/adapters/discovery.js +76 -0
  38. package/dist/src/adapters/prompt-compiler.js +60 -0
  39. package/dist/src/adapters/registry.js +10 -5
  40. package/dist/src/auth/token-provider.js +2 -0
  41. package/dist/src/cli.js +98 -58
  42. package/dist/src/commands/doctor.js +7 -1
  43. package/dist/src/commands/setup.js +3 -0
  44. package/dist/src/config.js +9 -2
  45. package/dist/src/daemon/execution-coordinator.js +19 -2
  46. package/dist/src/daemon/manual-codex.js +82 -0
  47. package/dist/src/daemon/runtime.js +43 -43
  48. package/dist/src/gateway-client.js +3 -2
  49. package/dist/src/process/node-process-group-host.js +74 -0
  50. package/dist/src/safety/cli-host-admission.js +74 -0
  51. package/dist/src/safety/cli-security-modes.js +4 -0
  52. package/dist/src/safety/codex-exec-production-host.js +249 -0
  53. package/dist/src/safety/codex-exec-releases.js +3 -0
  54. package/dist/src/safety/codex-manual-controller.js +72 -0
  55. package/dist/src/safety/codex-manual-profile-query.js +30 -0
  56. package/dist/src/safety/codex-manual-registered.js +30 -0
  57. package/dist/src/safety/codex-native-observation.js +68 -0
  58. package/dist/src/safety/codex-native-process.js +59 -0
  59. package/dist/src/safety/codex-production-host.js +267 -0
  60. package/dist/src/safety/codex-reviewed-manual-release.js +37 -0
  61. package/dist/src/safety/codex-runtime-host.js +36 -0
  62. package/dist/src/safety/codex-supported-releases.js +1 -0
  63. package/dist/src/safety/hook-cli.js +39 -9
  64. package/dist/src/safety/hook-command.js +156 -0
  65. package/dist/src/safety/hook-policy-builder.js +6 -11
  66. package/dist/src/safety/job-security-policy.js +62 -0
  67. package/dist/src/safety/job-security-receipt.js +20 -0
  68. package/dist/src/safety/job-security-session.js +133 -0
  69. package/dist/src/safety/job-tool-dispatcher.js +52 -0
  70. package/dist/src/safety/job-tool-policy.js +61 -0
  71. package/dist/src/safety/job-tool-session.js +82 -0
  72. package/dist/src/safety/local-tool-policy.js +101 -0
  73. package/dist/src/safety/local-tool-server.js +211 -0
  74. package/dist/src/safety/local-tool-worker.js +57 -0
  75. package/dist/src/safety/pre-tool-use-hook.js +3 -39
  76. package/dist/src/safety/registered-command-integrity.js +52 -0
  77. package/dist/src/safety/security-requirements.js +49 -0
  78. package/dist/src/safety/stable-file-read.js +33 -0
  79. package/dist/src/safety/subprocess-sandbox.js +62 -0
  80. package/native/bin/codex-observe-darwin-arm64 +0 -0
  81. package/package.json +5 -3
@@ -0,0 +1,209 @@
1
+ import { UNMANAGED_EXEC_MODE } from './unmanaged-scope.js';
2
+ import { fingerprint as taskFingerprint } from '../../daemon/execution-coordinator.js';
3
+ import { securityRequirementsFingerprint as hash } from '../../safety/security-requirements.js';
4
+ import { ExecToolGate } from './exec-tool-gate.js';
5
+ import { assessReleaseExec, requireReleaseDescriptor, releaseGates, EXEC_INPUT_GATES } from './exec-release-policy.js';
6
+ import { officialExecObservationLimits } from './exec-observer.js';
7
+ import { EXEC_GATES, assessExecPreflight, requireExecHandshake } from './exec-preflight.js';
8
+ import { EXEC_MODE, isReleaseExecMode, EXEC_TRANSPORT, assertOfficialExecPlanAvailable, claimOfficialExecPlan, requireOfficialExecPlan } from './exec-invocation.js';
9
+ const fail = () => new Error('CODEX_EXEC_PREFLIGHT_REJECTED');
10
+ /** No default native inspector, handshake observer or OS process host is created.
11
+ * Missing per-execution evidence blocks before spawn. Synthetic callbacks grant
12
+ * only the fake host used by tests, never a compiled production release entry. */
13
+ export class CodexExecLaunchHost {
14
+ plan;
15
+ deps;
16
+ used = false;
17
+ ownsAttempt = false;
18
+ stopped = false;
19
+ handle;
20
+ inspection;
21
+ exitAudit;
22
+ inspectionFingerprint;
23
+ handshake;
24
+ receiptValue;
25
+ abort = new AbortController();
26
+ closePromise;
27
+ constructor(plan, deps) {
28
+ this.plan = plan;
29
+ this.deps = deps;
30
+ requireOfficialExecPlan(plan);
31
+ this.deps = Object.freeze({ ...deps, processHost: Object.freeze({ spawn: deps.processHost.spawn.bind(deps.processHost) }) });
32
+ }
33
+ current() { if (this.stopped || this.abort.signal.aborted || this.inspection?.isCurrent() !== true || !(isReleaseExecMode(this.plan.mode) ? assessReleaseExec(this.inspection.snapshot, this.plan) : assessExecPreflight(this.inspection.snapshot, this.plan)).ready || hash(this.inspection.snapshot) !== this.inspectionFingerprint) {
34
+ if (this.plan.mode === UNMANAGED_EXEC_MODE)
35
+ void this.cancel().catch(() => { });
36
+ throw fail();
37
+ } }
38
+ assertContext(ctx, jobId) {
39
+ const a = ctx.approvedTaskPackage, r = a?.security_requirements, b = ctx.taskPackage, execution = b?.execution;
40
+ if (isReleaseExecMode(this.plan.mode)) {
41
+ if (!r)
42
+ throw fail();
43
+ requireReleaseDescriptor(r);
44
+ if (r.binary_sha256 !== this.plan.identity.binarySha256 || r.fixtures_sha256 !== this.plan.identity.barrierFingerprint || r.commands_sha256 !== this.plan.identity.commandsFingerprint || r.tools_sha256 !== this.plan.identity.toolSchemaFingerprint || !r.model_ids.includes(this.plan.modelId))
45
+ throw fail();
46
+ }
47
+ if (!a || !r || jobId !== this.plan.identity.jobId || ctx.generation !== this.plan.identity.generation || ctx.resumeSessionId || ctx.resolvedModelId !== this.plan.modelId || a.runtime_adapter !== 'codex_cli' || a.runtime_adapter_version !== '1' || a.runtime_transport !== EXEC_TRANSPORT || a.resolved_provider !== 'openai' || a.resolved_model_id !== this.plan.modelId || a.repository?.base_commit_sha !== this.plan.identity.sourceSha || a.contract_fingerprint !== this.plan.identity.contractFingerprint
48
+ || taskFingerprint(a) !== this.plan.identity.taskPackageFingerprint || ctx.securityBinding?.taskPackageFingerprint !== this.plan.identity.taskPackageFingerprint || ctx.securityBinding.contractFingerprint !== this.plan.identity.contractFingerprint || ctx.securityBinding.launchFingerprint !== this.plan.identity.launchFingerprint
49
+ || b?.repositories?.length !== 1 || b.repositories[0]?.base_commit_sha !== this.plan.identity.sourceSha
50
+ || execution?.runtime_transport !== EXEC_TRANSPORT || execution?.mode !== 'employee_ai_work_session' || execution.runtime_adapter !== 'codex_cli' || execution.limits_mode !== 'unbounded'
51
+ || r.version !== (this.plan.mode === UNMANAGED_EXEC_MODE ? 'cli-exec-security-requirements-v3' : isReleaseExecMode(this.plan.mode) ? 'cli-exec-security-requirements-v2' : 'cli-exec-security-requirements-v1') || r.security_mode !== this.plan.mode || r.transport !== EXEC_TRANSPORT || r.status !== 'verified' || r.adapter_id !== 'codex_cli' || r.adapter_version !== '1' || JSON.stringify(r.mandatory_gates) !== JSON.stringify(isReleaseExecMode(this.plan.mode) ? [...releaseGates(this.plan.mode), ...EXEC_INPUT_GATES, 'authenticated_mcp_discovery'] : EXEC_GATES) || r.release_manifest_sha256 !== this.plan.identity.releaseManifestFingerprint || r.fingerprint !== this.plan.identity.requirementsFingerprint || hash(r) !== r.fingerprint)
52
+ throw fail();
53
+ }
54
+ assertAvailableContext(ctx, jobId) { if (this.used || this.stopped)
55
+ throw fail(); this.assertContext(ctx, jobId); assertOfficialExecPlanAvailable(this.plan); if (isReleaseExecMode(this.plan.mode) && (!(this.deps.execTools instanceof ExecToolGate) || !this.deps.execTools.matches(this.plan)))
56
+ throw fail(); }
57
+ async start(ctx, jobId, prompt, signal) {
58
+ if (this.used || this.stopped)
59
+ throw fail();
60
+ this.assertAvailableContext(ctx, jobId);
61
+ claimOfficialExecPlan(this.plan);
62
+ this.ownsAttempt = true;
63
+ this.used = true;
64
+ let timer, external;
65
+ try {
66
+ this.assertContext(ctx, jobId);
67
+ requireOfficialExecPlan(this.plan);
68
+ if (typeof prompt !== 'string' || !prompt || prompt.length > 1_000_000 || signal?.aborted)
69
+ throw fail();
70
+ const stopped = new Promise((_, reject) => { external = () => { void this.cancel().catch(() => { }); reject(fail()); }; this.abort.signal.addEventListener('abort', () => reject(fail()), { once: true }); signal?.addEventListener('abort', external, { once: true }); timer = setTimeout(external, 30_000); });
71
+ const preparation = (async () => {
72
+ const supported = isReleaseExecMode(this.plan.mode);
73
+ this.inspection = await (supported ? this.deps.inspectRelease?.(this.plan, this.abort.signal) : this.deps.inspect?.(this.plan, this.abort.signal));
74
+ const assessment = supported ? assessReleaseExec(this.inspection?.snapshot, this.plan) : assessExecPreflight(this.inspection?.snapshot, this.plan);
75
+ if (!supported && !this.deps.inspect)
76
+ assessment.reasonCodes.push(...officialExecObservationLimits());
77
+ this.deps.onAssessment?.(structuredClone(assessment));
78
+ if (!assessment.ready || !supported && typeof this.deps.handshake !== 'function')
79
+ throw fail();
80
+ this.inspectionFingerprint = hash(this.inspection.snapshot);
81
+ this.current();
82
+ const invocation = requireOfficialExecPlan(this.plan);
83
+ const manual = ctx.approvedTaskPackage?.security_requirements?.mdm_verification === 'manual_prelaunch';
84
+ if (manual && typeof this.inspection.beforeSpawn !== 'function')
85
+ throw fail();
86
+ this.inspection.beforeSpawn?.();
87
+ const h = this.deps.processHost.spawn(invocation.binary, invocation.args, { cwd: this.plan.cwd, env: invocation.env });
88
+ this.handle = h;
89
+ if (this.stopped) {
90
+ h.kill('SIGKILL');
91
+ throw fail();
92
+ }
93
+ h.once('exit', (code, signal) => {
94
+ // Preserve audit only for a clean owned exit that was still authorized.
95
+ // This immutable record never restores tool or spawn authority.
96
+ if (code === 0 && signal === null && !this.stopped && this.receiptValue) {
97
+ try {
98
+ this.current();
99
+ this.exitAudit = structuredClone(this.receiptValue);
100
+ }
101
+ catch { }
102
+ }
103
+ void this.cancel().catch(() => { });
104
+ });
105
+ this.current();
106
+ if (supported) {
107
+ const gate = this.deps.execTools;
108
+ gate.bind(h, () => { try {
109
+ this.current();
110
+ return true;
111
+ }
112
+ catch {
113
+ return false;
114
+ } });
115
+ // Vendor exec consumes stdin before it can initialize its required MCP.
116
+ // Prompt submission may cause provider/account effects; do not call this no-model.
117
+ h.stdin.write(prompt);
118
+ this.current();
119
+ h.stdin.end();
120
+ this.current();
121
+ gate.promptSubmitted(h);
122
+ const observed = await gate.wait(h, this.abort.signal);
123
+ this.current();
124
+ requireExecHandshake(observed, h, this.plan);
125
+ gate.authorize(h);
126
+ this.current();
127
+ requireExecHandshake(observed, h, this.plan);
128
+ this.receiptValue = Object.freeze({ receipt_version: this.plan.mode === UNMANAGED_EXEC_MODE ? 'cli-exec-job-security-receipt-v3' : 'cli-exec-job-security-receipt-v2', evidence_origin: 'employee_runner', transport: EXEC_TRANSPORT, security_mode: this.plan.mode, ...(this.plan.mode === UNMANAGED_EXEC_MODE ? { policy_scope: this.inspection.snapshot.policyScope } : {}), phase: 'post_prompt_mcp_authorized', policy_evidence: 'reviewed_release_and_current_inputs', mcp_evidence: 'authenticated_server_discovery', native_policy_observed: false, first_inference_observed: false, execution_nonce: this.plan.executionNonce, invocation_fingerprint: this.plan.invocationFingerprint, ...this.plan.identity });
129
+ return h;
130
+ }
131
+ const handshake = await this.deps.handshake?.(h, this.plan, this.abort.signal);
132
+ this.current();
133
+ requireExecHandshake(handshake, h, this.plan);
134
+ this.handshake = handshake;
135
+ // An input hash is not a native effective-config certificate. Preserve the
136
+ // evidence origin in the audit receipt and never reuse AppServer authority.
137
+ const receipt = Object.freeze({ receipt_version: 'cli-exec-job-security-receipt-v1', evidence_origin: 'employee_runner', transport: EXEC_TRANSPORT, security_mode: EXEC_MODE, phase: 'exec_pre_prompt', execution_nonce: this.plan.executionNonce, invocation_fingerprint: this.plan.invocationFingerprint, ...this.plan.identity });
138
+ this.current();
139
+ requireExecHandshake(handshake, h, this.plan);
140
+ h.stdin.write(prompt);
141
+ this.current();
142
+ h.stdin.end();
143
+ this.current();
144
+ requireExecHandshake(handshake, h, this.plan);
145
+ this.receiptValue = receipt;
146
+ return h;
147
+ })();
148
+ return await Promise.race([preparation, stopped]);
149
+ }
150
+ catch {
151
+ await this.cancel().catch(() => { });
152
+ throw fail();
153
+ }
154
+ finally {
155
+ if (timer)
156
+ clearTimeout(timer);
157
+ if (external)
158
+ signal?.removeEventListener('abort', external);
159
+ }
160
+ }
161
+ receipt() { if (this.exitAudit)
162
+ return structuredClone(this.exitAudit); if (!this.receiptValue)
163
+ return undefined; try {
164
+ if (this.plan.mode === UNMANAGED_EXEC_MODE)
165
+ this.current();
166
+ return this.receiptValue ? structuredClone(this.receiptValue) : undefined;
167
+ }
168
+ catch {
169
+ return undefined;
170
+ } }
171
+ cancel() {
172
+ if (this.closePromise)
173
+ return this.closePromise;
174
+ this.stopped = true;
175
+ this.receiptValue = undefined;
176
+ if (!this.ownsAttempt) {
177
+ this.closePromise = Promise.resolve();
178
+ return this.closePromise;
179
+ }
180
+ let finish, reject;
181
+ this.closePromise = new Promise((resolve, rejectPromise) => { finish = resolve; reject = rejectPromise; });
182
+ let cleanupError = false;
183
+ // Publish closePromise first: synchronous exit/cancel cannot recurse or skip cleanup.
184
+ try {
185
+ this.deps.execTools?.revoke();
186
+ }
187
+ catch {
188
+ cleanupError = true;
189
+ }
190
+ try {
191
+ this.deps.revokeTools();
192
+ }
193
+ catch {
194
+ cleanupError = true;
195
+ }
196
+ this.abort.abort();
197
+ try {
198
+ this.handle?.kill('SIGKILL');
199
+ }
200
+ catch {
201
+ cleanupError = true;
202
+ }
203
+ Promise.resolve().then(() => this.deps.closeTools()).then(() => { if (cleanupError)
204
+ reject(new Error('CODEX_EXEC_CLEANUP_UNCONFIRMED'));
205
+ else
206
+ finish(); }, () => reject(new Error('CODEX_EXEC_CLEANUP_UNCONFIRMED')));
207
+ return this.closePromise;
208
+ }
209
+ }
@@ -0,0 +1,83 @@
1
+ /** Official exec JSONL exposes no native same-execution effective/managed policy
2
+ * inspection. stdin is consumed before startup; waiting for child discovery
3
+ * before submitting stdin cannot be used as an inference authorization barrier.
4
+ * Fixed limits, not user/task-provided release claims. */
5
+ export function officialExecObservationLimits() {
6
+ return [
7
+ 'EXEC_EFFECTIVE_POLICY_OBSERVATION_UNAVAILABLE',
8
+ 'EXEC_MANAGED_POLICY_OBSERVATION_UNAVAILABLE',
9
+ 'EXEC_REQUIRED_MCP_INFERENCE_BARRIER_UNVERIFIED',
10
+ 'EXEC_STDIN_REQUIRED_BEFORE_STARTUP',
11
+ ];
12
+ }
13
+ /** Raw wire diagnostics. Attach discovery only to this launch's authenticated
14
+ * server; do not call from task JSON/stdout. Possession of the endpoint token is
15
+ * not OS process identity or proof of when native inference begins.
16
+ * No permit, LiveExecInspection or ExecHandshake is produced by this observer. */
17
+ export class ExecWireObserver {
18
+ stage = 'unobserved';
19
+ thread = false;
20
+ reasons = new Set();
21
+ discovery = (event) => {
22
+ if (this.stage === 'revoked')
23
+ return;
24
+ if (event === 'revoked') {
25
+ this.stage = 'revoked';
26
+ return;
27
+ }
28
+ if (event === 'initialize_response' && this.stage === 'unobserved') {
29
+ this.stage = 'initialized';
30
+ return;
31
+ }
32
+ if (event === 'initialized_notification' && this.stage === 'initialized') {
33
+ this.stage = 'notified';
34
+ return;
35
+ }
36
+ if (event === 'inventory_response' && (this.stage === 'notified' || this.stage === 'observed')) {
37
+ this.stage = 'observed';
38
+ return;
39
+ }
40
+ this.stage = 'invalid';
41
+ this.reasons.add('EXEC_MCP_DISCOVERY_SEQUENCE_INVALID');
42
+ };
43
+ line(line) {
44
+ // Deliberately keep no raw payload, thread ID, path, stderr or provider usage.
45
+ try {
46
+ if (typeof line !== 'string' || Buffer.byteLength(line) > 256_000)
47
+ throw Error();
48
+ const e = JSON.parse(line);
49
+ if (!e || typeof e !== 'object' || Array.isArray(e) || typeof e.type !== 'string')
50
+ throw Error();
51
+ const event = e;
52
+ if (event.type === 'thread.started') {
53
+ if (Object.keys(event).length !== 2 || typeof event.thread_id !== 'string' || !event.thread_id || event.thread_id.length > 256)
54
+ throw Error();
55
+ this.thread = true;
56
+ return;
57
+ }
58
+ if (event.type === 'error' || event.type === 'turn.failed') {
59
+ this.reasons.add('EXEC_CHILD_FAILURE');
60
+ return;
61
+ }
62
+ if (['turn.started', 'turn.completed', 'item.started', 'item.updated', 'item.completed'].includes(event.type)) {
63
+ // These events occur after prompt submission; never elevate them to proof.
64
+ this.reasons.add('EXEC_POST_PROMPT_EVENT_OBSERVED');
65
+ return;
66
+ }
67
+ this.reasons.add('EXEC_UNSUPPORTED_WIRE_EVENT');
68
+ }
69
+ catch {
70
+ this.reasons.add('EXEC_INVALID_WIRE_EVENT');
71
+ }
72
+ }
73
+ report() {
74
+ return {
75
+ executionReady: false,
76
+ effectivePolicy: 'unverified', managedPolicy: 'unverified',
77
+ requiredMcpInferenceBarrier: 'unverified',
78
+ mcpDiscovery: this.stage === 'observed' ? 'observed' : this.stage === 'revoked' ? 'revoked' : 'unobserved',
79
+ threadObserved: this.thread,
80
+ reasonCodes: [...officialExecObservationLimits(), ...this.reasons],
81
+ };
82
+ }
83
+ }
@@ -0,0 +1,41 @@
1
+ import { isDeepStrictEqual } from 'node:util';
2
+ import { SUPPORTED_CLI_GATES } from '../../safety/cli-security-modes.js';
3
+ import { localToolInventory } from '../../safety/local-tool-server.js';
4
+ import { EXEC_TRANSPORT, requireOfficialExecPlan } from './exec-invocation.js';
5
+ export const EXEC_GATES = Object.freeze([...SUPPORTED_CLI_GATES, 'effective_policy_observation', 'managed_policy_observation', 'required_mcp_inference_barrier']);
6
+ const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
7
+ const exact = (v, keys) => isDeepStrictEqual(Reflect.ownKeys(v).sort(), keys.sort());
8
+ const sha = (v) => typeof v === 'string' && /^[a-f0-9]{64}$/.test(v);
9
+ /** Diagnostics only. Even a ready assessment is not a policy or spawn permit. */
10
+ export function assessExecPreflight(value, plan) {
11
+ const reasonCodes = [];
12
+ const need = (ok, code) => { if (!ok)
13
+ reasonCodes.push(code); };
14
+ try {
15
+ requireOfficialExecPlan(plan);
16
+ if (!object(value))
17
+ return { ready: false, reasonCodes: ['EXEC_OBSERVATION_UNAVAILABLE', ...EXEC_GATES.map(g => 'EXEC_' + g.toUpperCase() + '_UNVERIFIED'), 'EXEC_EFFECTIVE_POLICY_UNVERIFIED', 'EXEC_MANAGED_POLICY_UNVERIFIED', 'EXEC_MCP_BARRIER_UNVERIFIED'] };
18
+ need(exact(value, ['version', 'transport', 'executionNonce', 'identity', 'invocationFingerprint', 'checks', 'effectivePolicy', 'managedPolicy', 'mcpPolicy', 'warnings']), 'EXEC_EVIDENCE_SHAPE');
19
+ need(value.version === 'cli-exec-security-evidence-v1' && value.transport === EXEC_TRANSPORT, 'EXEC_EVIDENCE_VERSION');
20
+ need(value.executionNonce === plan.executionNonce && value.invocationFingerprint === plan.invocationFingerprint && isDeepStrictEqual(value.identity, plan.identity), 'EXEC_IDENTITY_MISMATCH');
21
+ need(Array.isArray(value.warnings) && value.warnings.length === 0, 'EXEC_WARNING');
22
+ const checks = Array.isArray(value.checks) ? value.checks : [];
23
+ need(checks.length === EXEC_GATES.length && new Set(checks.map(c => object(c) ? c.gate : null)).size === EXEC_GATES.length && checks.every(c => object(c) && exact(c, ['gate', 'status', 'basis']) && EXEC_GATES.includes(c.gate)), 'EXEC_CHECK_SET');
24
+ for (const gate of EXEC_GATES)
25
+ need(checks.filter(c => object(c) && c.gate === gate && c.status === 'verified' && c.basis === 'current_execution').length === 1, 'EXEC_' + gate.toUpperCase() + '_UNVERIFIED');
26
+ const p = object(value.effectivePolicy) ? value.effectivePolicy : {};
27
+ need(exact(p, ['source', 'executionNonce', 'modelId', 'provider', 'cwd', 'approvalPolicy', 'permissionProfile', 'nativeNetwork', 'fingerprint']) && p.source === 'owned_exec_pre_model_observation' && p.executionNonce === plan.executionNonce && p.modelId === plan.modelId && p.provider === 'openai' && p.cwd === plan.cwd && p.approvalPolicy === 'never' && p.permissionProfile === 'local_job' && p.nativeNetwork === false && sha(p.fingerprint), 'EXEC_EFFECTIVE_POLICY_UNVERIFIED');
28
+ const m = object(value.managedPolicy) ? value.managedPolicy : {};
29
+ need(exact(m, ['source', 'executionNonce', 'decision', 'fingerprint', 'sourcesFingerprint']) && m.source === 'owned_exec_pre_model_observation' && m.executionNonce === plan.executionNonce && m.decision === 'compatible' && sha(m.fingerprint) && sha(m.sourcesFingerprint), 'EXEC_MANAGED_POLICY_UNVERIFIED');
30
+ const b = object(value.mcpPolicy) ? value.mcpPolicy : {};
31
+ need(exact(b, ['source', 'executionNonce', 'endpoint', 'toolSchemaFingerprint', 'required', 'startupFailurePreventsInference', 'barrierFingerprint']) && b.source === 'owned_exec_native_barrier' && b.executionNonce === plan.executionNonce && b.endpoint === plan.endpoint && b.toolSchemaFingerprint === plan.identity.toolSchemaFingerprint && b.required === true && b.startupFailurePreventsInference === true && b.barrierFingerprint === plan.identity.barrierFingerprint, 'EXEC_MCP_BARRIER_UNVERIFIED');
32
+ }
33
+ catch {
34
+ reasonCodes.push('EXEC_OBSERVATION_UNAVAILABLE');
35
+ }
36
+ return { ready: reasonCodes.length === 0, reasonCodes };
37
+ }
38
+ export function requireExecHandshake(h, process, plan) {
39
+ if (!object(h) || h.process !== process || h.executionNonce !== plan.executionNonce || h.endpoint !== plan.endpoint || h.authenticated !== true || h.ready !== true || h.toolSchemaFingerprint !== plan.identity.toolSchemaFingerprint || !isDeepStrictEqual(h.tools, localToolInventory().map(t => t.name)) || typeof h.isCurrent !== 'function' || h.isCurrent() !== true)
40
+ throw Error('CODEX_EXEC_MCP_REJECTED');
41
+ }
@@ -0,0 +1,44 @@
1
+ import { MANUAL_MDM_RISK } from './manual-policy-scope.js';
2
+ import { UNMANAGED_EXEC_MODE, UNMANAGED_SCOPE, requirePolicyScope } from './unmanaged-scope.js';
3
+ import { isDeepStrictEqual } from 'node:util';
4
+ import { isReleaseExecMode, EXEC_TRANSPORT, requireOfficialExecPlan } from './exec-invocation.js';
5
+ export const EXEC_RELEASE_GATES = Object.freeze(['binary_release', 'official_permissions', 'managed_policy_enforcement', 'required_mcp_startup', 'source_exclusion', 'auth_backend_compatibility']);
6
+ export const EXEC_INPUT_GATES = Object.freeze(['exact_inputs', 'launch_binding', 'repository_scope', 'private_paths_environment', 'owned_lifecycle']);
7
+ export const EXEC_RELEASE_RISKS = Object.freeze(['vendor_native_policy_enforcement', 'managed_policy_not_directly_observed_per_job', 'first_inference_order_not_observed_per_job', 'job_mcp_token_possession_not_os_identity', 'account_startup_may_refresh_shared_credentials']);
8
+ export const releaseGates = (mode) => mode === UNMANAGED_EXEC_MODE ? EXEC_RELEASE_GATES.map(g => g === 'managed_policy_enforcement' ? 'managed_scope_exclusion' : g) : EXEC_RELEASE_GATES;
9
+ export const releaseRisks = (mode, mdmVerification) => mode === UNMANAGED_EXEC_MODE ? [...EXEC_RELEASE_RISKS, 'concurrent_policy_change_not_atomic', 'local_account_metadata_not_remote_entitlement', ...(mdmVerification === 'manual_prelaunch' ? [MANUAL_MDM_RISK] : [])] : EXEC_RELEASE_RISKS;
10
+ const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
11
+ const exact = (v, keys) => isDeepStrictEqual(Object.keys(v).sort(), keys.sort());
12
+ const checks = (v, gates, basis) => Array.isArray(v) && v.length === gates.length && gates.every(g => v.filter(c => record(c) && exact(c, ['gate', 'status', 'basis']) && c.gate === g && c.status === 'verified' && c.basis === basis).length === 1);
13
+ export function requireReleaseDescriptor(r) {
14
+ const v3 = r.security_mode === UNMANAGED_EXEC_MODE;
15
+ if (!['release_manifest_sha256', 'binary_sha256', 'fixtures_sha256', 'commands_sha256', 'tools_sha256', 'fingerprint'].every(k => typeof r[k] === 'string' && /^[a-f0-9]{64}$/.test(r[k])) || !Array.isArray(r.model_ids) || !r.model_ids.length || new Set(r.model_ids).size !== r.model_ids.length || r.model_ids.some(m => typeof m !== 'string' || !m || m.length > 255 || /[\r\n\0]/.test(m)) || !exact(r, ['version', 'security_mode', 'transport', 'status', 'adapter_id', 'adapter_version', 'mandatory_gates', 'accepted_residual_risks', 'evidence_class', 'release_manifest_sha256', 'binary_sha256', 'fixtures_sha256', 'commands_sha256', 'tools_sha256', 'model_ids', 'fingerprint', ...(v3 ? ['policy_scope', 'managed_enforcement', ...(r.mdm_verification === undefined ? [] : ['mdm_verification'])] : [])]) || r.version !== (v3 ? 'cli-exec-security-requirements-v3' : 'cli-exec-security-requirements-v2') || !isReleaseExecMode(r.security_mode) || v3 && (r.policy_scope !== UNMANAGED_SCOPE || r.managed_enforcement !== 'not_run' || r.mdm_verification !== undefined && r.mdm_verification !== 'manual_prelaunch') || r.evidence_class !== 'reviewed_release_and_current_inputs' || !isDeepStrictEqual(r.mandatory_gates, [...releaseGates(r.security_mode), ...EXEC_INPUT_GATES, 'authenticated_mcp_discovery']) || !isDeepStrictEqual(r.accepted_residual_risks, releaseRisks(r.security_mode, r.mdm_verification)))
16
+ throw Error('EXEC_RELEASE_DESCRIPTOR_REJECTED');
17
+ }
18
+ /** Diagnostic validator only; live currentness and trusted dependency origin
19
+ * are checked by the host. Release checks never become direct native proof. */
20
+ export function assessReleaseExec(value, plan) {
21
+ const reasonCodes = [];
22
+ const need = (ok, code) => { if (!ok)
23
+ reasonCodes.push(code); };
24
+ try {
25
+ requireOfficialExecPlan(plan);
26
+ const v3 = plan.mode === UNMANAGED_EXEC_MODE;
27
+ if (!record(value))
28
+ return { ready: false, reasonCodes: ['EXEC_RELEASE_EVIDENCE_UNAVAILABLE', ...EXEC_RELEASE_GATES.map(g => 'EXEC_RELEASE_' + g.toUpperCase() + '_UNVERIFIED'), ...EXEC_INPUT_GATES.map(g => 'EXEC_INPUT_' + g.toUpperCase() + '_UNVERIFIED')] };
29
+ need(isReleaseExecMode(plan.mode) && exact(value, ['version', 'securityMode', 'transport', 'executionNonce', 'identity', 'invocationFingerprint', 'acceptedResidualRisks', 'release', 'inputs', 'warnings', ...(v3 ? ['policyScope'] : [])]) && value.version === (v3 ? 'cli-exec-release-evidence-v3' : 'cli-exec-release-evidence-v2') && value.securityMode === plan.mode && value.transport === EXEC_TRANSPORT, 'EXEC_RELEASE_EVIDENCE_SHAPE');
30
+ need(value.executionNonce === plan.executionNonce && value.invocationFingerprint === plan.invocationFingerprint && isDeepStrictEqual(value.identity, plan.identity), 'EXEC_RELEASE_IDENTITY_MISMATCH');
31
+ need(isDeepStrictEqual(value.acceptedResidualRisks, releaseRisks(plan.mode, record(value.policyScope) && record(value.policyScope.manual_verification) ? value.policyScope.manual_verification.mode : undefined)), 'EXEC_RELEASE_RISK_MISMATCH');
32
+ need(Array.isArray(value.warnings) && value.warnings.length === 0, 'EXEC_RELEASE_WARNING');
33
+ const r = record(value.release) ? value.release : {};
34
+ need(exact(r, ['source', 'binarySha256', 'manifestFingerprint', 'fixturesFingerprint', 'checks']) && r.source === 'reviewed_compiled_release' && r.binarySha256 === plan.identity.binarySha256 && r.manifestFingerprint === plan.identity.releaseManifestFingerprint && r.fixturesFingerprint === plan.identity.barrierFingerprint, 'EXEC_RELEASE_PIN_MISMATCH');
35
+ need(checks(r.checks, releaseGates(plan.mode), 'pinned_release_fixture'), 'EXEC_RELEASE_CHECKS_UNVERIFIED');
36
+ if (v3)
37
+ requirePolicyScope(value.policyScope, plan);
38
+ need(checks(value.inputs, EXEC_INPUT_GATES, 'current_execution'), 'EXEC_INPUT_CHECKS_UNVERIFIED');
39
+ }
40
+ catch {
41
+ reasonCodes.push('EXEC_RELEASE_EVIDENCE_UNAVAILABLE');
42
+ }
43
+ return { ready: reasonCodes.length === 0, reasonCodes };
44
+ }
@@ -0,0 +1,113 @@
1
+ import path from 'node:path';
2
+ import { mkdtemp, mkdir, realpath, writeFile, rm, lstat } from 'node:fs/promises';
3
+ import { NodeProcessGroupHost } from '../../process/node-process-group-host.js';
4
+ import { LocalToolPolicy } from '../../safety/local-tool-policy.js';
5
+ import { jobToolPolicyOptions } from '../../safety/job-tool-policy.js';
6
+ import { LocalToolServer, localToolInventory } from '../../safety/local-tool-server.js';
7
+ import { commandRegistryFingerprint } from '../../safety/job-security-session.js';
8
+ import { fingerprint } from '../../daemon/execution-coordinator.js';
9
+ import { buildOfficialExecPlan } from './exec-invocation.js';
10
+ import { ExecToolGate } from './exec-tool-gate.js';
11
+ import { CodexExecLaunchHost } from './exec-launch-host.js';
12
+ import { CODEX_REPORT_SCHEMA } from './prompt-compiler.js';
13
+ const inside = (a, b) => a === b || b.startsWith(a + path.sep);
14
+ const reject = () => Error('CODEX_EXEC_COMPOSITION_REJECTED');
15
+ /** The caller supplies a trusted claimed job, not a task-selected host factory. */
16
+ export function buildCodexExecJobOptions(input) {
17
+ let job, server, gate, policy, control, closed = false, used = false, closing;
18
+ let launchHost;
19
+ const dispose = () => closing ??= Promise.resolve().then(async () => { closed = true; gate?.revoke(); job?.revoke(); try {
20
+ server?.revoke();
21
+ }
22
+ finally {
23
+ policy?.revoke();
24
+ try {
25
+ await server?.close();
26
+ }
27
+ finally {
28
+ if (control)
29
+ await rm(control, { recursive: true, force: true });
30
+ }
31
+ } });
32
+ const close = async () => { try {
33
+ await launchHost?.cancel();
34
+ }
35
+ finally {
36
+ await dispose();
37
+ } };
38
+ const processHost = input.processHost ?? new NodeProcessGroupHost();
39
+ const env = Object.fromEntries(['HOME', 'CODEX_HOME', 'PATH', 'USER', 'LOGNAME', 'LANG', 'LC_ALL', 'TMPDIR'].filter(k => input.env[k] !== undefined).map(k => [k, input.env[k]]));
40
+ return { worktreeRoot: input.worktreePath, jobId: input.jobId, env: input.env, processHost,
41
+ closeOfficialExec: close, revokeSecurity: () => { closed = true; gate?.revoke(); job?.revoke(); server?.revoke(); policy?.revoke(); },
42
+ createTools: () => { throw reject(); }, writeSchema: async () => { throw reject(); },
43
+ createOfficialExec: async (ctx, signal) => {
44
+ if (used || closed || signal.aborted)
45
+ throw reject();
46
+ used = true;
47
+ const abort = () => { void close().catch(() => { }); };
48
+ signal.addEventListener('abort', abort, { once: true });
49
+ try {
50
+ job = input.productionJob();
51
+ job.attachCleanup(close);
52
+ const admitted = await job.prepare(ctx);
53
+ if (closed || signal.aborted)
54
+ throw reject();
55
+ if (!input.env.HOME || !path.isAbsolute(input.stateRoot))
56
+ throw reject();
57
+ const account = path.resolve(input.env.CODEX_HOME ?? path.join(input.env.HOME, '.codex'));
58
+ await mkdir(input.stateRoot, { recursive: true, mode: 0o700 });
59
+ const state = await realpath(input.stateRoot), source = await realpath(input.worktreePath), home = await realpath(account);
60
+ if (inside(source, state) || inside(state, source) || inside(home, state) || inside(state, home))
61
+ throw reject();
62
+ control = await mkdtemp(path.join(state, 'codex-exec-'));
63
+ const cwd = path.join(control, 'cwd'), scratch = { tmpDir: path.join(control, 'tmp'), logDir: path.join(control, 'log'), sqliteHome: path.join(control, 'sqlite') };
64
+ await Promise.all([cwd, ...Object.values(scratch)].map(p => mkdir(p, { mode: 0o700 })));
65
+ if (closed || signal.aborted)
66
+ throw reject();
67
+ for (let dir = cwd;; dir = path.dirname(dir)) {
68
+ for (const base of ['.codex/config.toml']) {
69
+ const f = path.join(dir, base);
70
+ if (f !== path.join(account, 'config.toml')) {
71
+ try {
72
+ await lstat(f);
73
+ throw reject();
74
+ }
75
+ catch (e) {
76
+ if (e.code !== 'ENOENT')
77
+ throw e;
78
+ }
79
+ }
80
+ }
81
+ if (dir === path.dirname(dir))
82
+ break;
83
+ }
84
+ const schema = path.join(control, 'report-schema.json');
85
+ await writeFile(schema, JSON.stringify(CODEX_REPORT_SCHEMA), { mode: 0o600, flag: 'wx' });
86
+ if (closed || signal.aborted)
87
+ throw reject();
88
+ const approved = ctx.approvedTaskPackage, requirements = approved.security_requirements;
89
+ policy = new LocalToolPolicy(jobToolPolicyOptions(source, ctx.taskPackage, approved, admitted.release.commands, { runtimeAdapter: 'codex_cli', runtimeAdapterVersion: '1' }));
90
+ server = new LocalToolServer({ policy, authorizeCall: () => gate?.allowsCall() === true, onDiscovery: (event, scope) => gate?.discovery(event, scope) });
91
+ const endpoint = await server.start();
92
+ if (closed || signal.aborted)
93
+ throw reject();
94
+ const plan = buildOfficialExecPlan({ policyMode: admitted.release.securityMode, binary: admitted.binary, credentialBackend: admitted.release.credentialBackend, identity: { jobId: input.jobId, generation: ctx.generation, actorId: admitted.actorId, runnerId: admitted.runnerId, sourceSha: String(approved.repository.base_commit_sha), binarySha256: admitted.release.binarySha256, releaseManifestFingerprint: String(requirements.release_manifest_sha256), requirementsFingerprint: String(requirements.fingerprint), contractFingerprint: ctx.securityBinding.contractFingerprint, taskPackageFingerprint: ctx.securityBinding.taskPackageFingerprint, launchFingerprint: ctx.securityBinding.launchFingerprint, commandsFingerprint: commandRegistryFingerprint(admitted.release.commands), toolSchemaFingerprint: fingerprint({ tools: localToolInventory() }), barrierFingerprint: admitted.release.fixturesFingerprint }, invocation: { modelId: ctx.resolvedModelId, worktreeRoot: cwd, sourceRoot: source, schemaPath: schema, endpoint, env, scratch } });
95
+ gate = new ExecToolGate(plan);
96
+ const host = new CodexExecLaunchHost(plan, { processHost, inspectRelease: p => job.inspect(p), execTools: gate, revokeTools: () => { gate?.revoke(); server?.revoke(); policy?.revoke(); job?.revoke(); }, closeTools: dispose });
97
+ launchHost = host;
98
+ if (closed || signal.aborted)
99
+ throw reject();
100
+ return host;
101
+ }
102
+ catch {
103
+ await close().catch(() => { });
104
+ if (control)
105
+ await rm(control, { recursive: true, force: true }).catch(() => { });
106
+ throw reject();
107
+ }
108
+ finally {
109
+ signal.removeEventListener('abort', abort);
110
+ }
111
+ },
112
+ };
113
+ }
@@ -0,0 +1,81 @@
1
+ import { isReleaseExecMode, requireOfficialExecPlan } from './exec-invocation.js';
2
+ import { localDiscoveryScopeMatches, localToolInventory } from '../../safety/local-tool-server.js';
3
+ import { securityRequirementsFingerprint as hash } from '../../safety/security-requirements.js';
4
+ const denied = () => Error('EXEC_TOOL_GATE_REJECTED');
5
+ /** Host-owned capability, never populated from stdout/task data. Authenticated
6
+ * discovery is server-side evidence; it does not attest OS client identity. */
7
+ export class ExecToolGate {
8
+ plan;
9
+ process;
10
+ current;
11
+ submitted = false;
12
+ revoked = false;
13
+ authorized = false;
14
+ stage = 0;
15
+ settle;
16
+ reject;
17
+ constructor(plan) {
18
+ this.plan = plan;
19
+ requireOfficialExecPlan(plan);
20
+ if (!isReleaseExecMode(plan.mode) || plan.identity.toolSchemaFingerprint !== hash({ tools: localToolInventory() }))
21
+ throw denied();
22
+ }
23
+ matches(plan) { return this.plan === plan && !this.process && !this.revoked; }
24
+ bind(process, current) { if (this.process || this.revoked)
25
+ throw denied(); this.process = process; this.current = current; }
26
+ active() { try {
27
+ return !this.revoked && !!this.process && this.current?.() === true;
28
+ }
29
+ catch {
30
+ return false;
31
+ } }
32
+ promptSubmitted(process) { if (this.process !== process || this.submitted || !this.active())
33
+ throw denied(); this.submitted = true; }
34
+ discovery = (event, scope) => {
35
+ const token = requireOfficialExecPlan(this.plan).env.AI_GATEWAY_JOB_MCP_TOKEN;
36
+ if (!localDiscoveryScopeMatches(scope, this.plan.endpoint, token)) {
37
+ this.revoke();
38
+ return;
39
+ }
40
+ if (event === 'revoked') {
41
+ this.revoke();
42
+ return;
43
+ }
44
+ if (this.revoked)
45
+ return;
46
+ if (!this.active() || !this.submitted) {
47
+ this.revoke();
48
+ return;
49
+ }
50
+ const expected = ['initialize_response', 'initialized_notification', 'inventory_response'];
51
+ if (this.stage === 3 && event === 'inventory_response')
52
+ return;
53
+ if (event !== expected[this.stage]) {
54
+ this.revoke();
55
+ return;
56
+ }
57
+ this.stage++;
58
+ if (this.stage === 3)
59
+ this.settle?.();
60
+ };
61
+ async wait(process, signal) {
62
+ if (this.process !== process || !this.submitted || !this.active() || signal.aborted)
63
+ throw denied();
64
+ if (this.stage !== 3)
65
+ await new Promise((resolve, reject) => {
66
+ const abort = () => { clear(); reject(denied()); };
67
+ const clear = () => { signal.removeEventListener('abort', abort); this.settle = undefined; this.reject = undefined; };
68
+ this.reject = abort;
69
+ this.settle = () => { clear(); resolve(); };
70
+ signal.addEventListener('abort', abort, { once: true });
71
+ });
72
+ if (!this.active() || signal.aborted || this.stage !== 3)
73
+ throw denied();
74
+ return { process, executionNonce: this.plan.executionNonce, endpoint: this.plan.endpoint, authenticated: true, ready: true, toolSchemaFingerprint: this.plan.identity.toolSchemaFingerprint, tools: localToolInventory().map(t => t.name), isCurrent: () => this.active() && this.stage === 3 };
75
+ }
76
+ authorize(process) { if (process !== this.process || !this.active() || this.stage !== 3)
77
+ throw denied(); this.authorized = true; }
78
+ allowsCall() { return this.authorized && this.submitted && this.stage === 3 && this.active(); }
79
+ revoke() { if (this.revoked)
80
+ return; this.revoked = true; this.authorized = false; this.reject?.(); }
81
+ }
@@ -0,0 +1,32 @@
1
+ import path from 'node:path';
2
+ export const CODEX_GLOBAL_INSTRUCTION_WRAPPER = '/usr/bin/sandbox-exec';
3
+ const reject = () => new Error('CODEX_GLOBAL_INSTRUCTION_BOUNDARY_REJECTED');
4
+ export function accountAncestorPaths(account) {
5
+ if (!path.isAbsolute(account) || path.resolve(account) !== account || account === '/' || account.length > 4096 || /[\r\n\0]/.test(account))
6
+ throw reject();
7
+ const parents = [];
8
+ for (let dir = account;; dir = path.dirname(dir)) {
9
+ parents.push(dir);
10
+ if (parents.length > 64)
11
+ throw reject();
12
+ if (dir === path.dirname(dir))
13
+ break;
14
+ }
15
+ return Object.freeze(parents);
16
+ }
17
+ /** Additional per-process denial, not a grant of authority or global policy edit.
18
+ * file-read* is intentional: data-only denial permits a final symlink alias.
19
+ * Ancestor symlink denials prevent a later account-directory redirect. */
20
+ export function globalInstructionProfile(account) {
21
+ const ancestors = accountAncestorPaths(account), literal = (p) => `(literal ${JSON.stringify(p)})`;
22
+ const profile = '(version 1)(allow default)'
23
+ + '(deny file-read* ' + ['AGENTS.md', 'AGENTS.override.md'].map(name => literal(path.join(account, name))).join(' ') + ')'
24
+ + '(deny file-read* (require-all (vnode-type SYMLINK) (require-any ' + ancestors.map(literal).join(' ') + ')))';
25
+ if (profile.length > 64000)
26
+ throw reject();
27
+ return profile;
28
+ }
29
+ export function requireGlobalInstructionInvocation(nativeBinary, account, invocation) {
30
+ if (invocation.binary !== CODEX_GLOBAL_INSTRUCTION_WRAPPER || invocation.args[0] !== '-p' || invocation.args[1] !== globalInstructionProfile(account) || invocation.args[2] !== nativeBinary)
31
+ throw reject();
32
+ }
@@ -0,0 +1,3 @@
1
+ import { diagnoseCliHostAdmission } from '../../safety/cli-host-admission.js';
2
+ /** Compatibility wrapper; all CLIs share the same closed release admission. */
3
+ export const diagnoseCodexHostAdmission = (binary, env) => diagnoseCliHostAdmission('codex_cli', binary, env);