mtech-ai-local-runner 0.1.28 → 0.1.30
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.
- package/README.md +49 -1
- package/dist/src/adapters/claude-code/adapter.js +13 -0
- package/dist/src/adapters/codex/adapter.js +1 -0
- package/dist/src/adapters/codex/exec-launch-host.js +16 -2
- package/dist/src/adapters/codex/exec-release-policy.js +4 -3
- package/dist/src/adapters/codex/manual-policy-scope.js +11 -6
- package/dist/src/adapters/codex/prelaunch-policy.js +9 -0
- package/dist/src/adapters/codex/unmanaged-scope.js +11 -2
- package/dist/src/daemon/execution-coordinator.js +22 -4
- package/dist/src/daemon/runtime.js +31 -25
- package/dist/src/daemon/terminal-recovery.js +59 -0
- package/dist/src/package-version.js +23 -0
- package/dist/src/safety/codex-daemon-host.js +101 -0
- package/dist/src/safety/codex-daemon-releases.js +42 -0
- package/dist/src/safety/codex-exec-production-host.js +5 -3
- package/dist/src/safety/codex-manual-controller.js +14 -10
- package/dist/src/safety/codex-manual-registered.js +4 -2
- package/dist/src/safety/local-read-observations.js +17 -0
- package/dist/src/safety/local-tool-server.js +36 -1
- package/dist/src/safety/policy-inventory-client.js +77 -0
- package/dist/src/state-store.js +15 -2
- package/native/bin/policy-observer-darwin-arm64 +0 -0
- package/native/policy-observer-installed-evidence.json +22 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
## 0.1.30: governed local alert investigation evidence
|
|
2
|
+
|
|
3
|
+
The Gateway candidate offers explicit Local Codex Runner or Local Claude Runner
|
|
4
|
+
selection for read-only Sentry investigation. It binds the chosen adapter/model
|
|
5
|
+
and employee device to the exact approved launch; it never falls back to another
|
|
6
|
+
adapter. Each adapter retains authenticated host-owned bounded read observations
|
|
7
|
+
through completion and cleanup. These counts are employee-runner evidence, not
|
|
8
|
+
independent root-cause verification.
|
|
9
|
+
|
|
10
|
+
This release does not remove Claude's current unsupported host-admission gate.
|
|
11
|
+
An installed or authenticated CLI alone is not execution permission. Gateway
|
|
12
|
+
deployment, feature activation and actual execution approvals remain separate.
|
|
13
|
+
|
|
14
|
+
## 0.1.29: reviewed daemon Codex admission
|
|
15
|
+
|
|
16
|
+
This candidate connects the installed, hash-pinned root policy observer to the
|
|
17
|
+
existing employee-privileged Codex CLI adapter. The observer accepts only its
|
|
18
|
+
fixed employee UID and runs only a fixed read-only machine-policy command; it
|
|
19
|
+
never runs a model or reads credentials. A fresh per-job inventory and local
|
|
20
|
+
forced-policy checks are required. Managed, partial, stale and unknown evidence
|
|
21
|
+
block execution. This snapshot is not continuous machine-policy monitoring.
|
|
22
|
+
|
|
23
|
+
The package alone does not install or start the privileged service. Deployment
|
|
24
|
+
and employee-daemon upgrade remain separately approved operations. The daemon
|
|
25
|
+
advertises Codex only when the installed observer and supported binary/account
|
|
26
|
+
checks pass; the current account settings and Claude path are retained. Existing
|
|
27
|
+
manual releases remain separately registered. Already approved foreground Codex
|
|
28
|
+
acceptance is not daemon acceptance.
|
|
29
|
+
|
|
30
|
+
Terminal completion requests are persisted before delivery and replayed with
|
|
31
|
+
the original generation, lease and result after an unknown acknowledgement;
|
|
32
|
+
this never reruns a model. Admitted task duration/count remain unbounded. The
|
|
33
|
+
preparation, lease, cancellation, path and authenticated-MCP checks remain.
|
|
34
|
+
|
|
35
|
+
Starting the upgraded employee daemon can refresh Gateway OAuth credentials.
|
|
36
|
+
Future actual Codex execution may refresh its original shared account tokens.
|
|
37
|
+
Neither effect is part of no-model local verification. See the repository
|
|
38
|
+
`docs/ai-ops-gateway/codex-daemon-admission-validation.md` for exact evidence and
|
|
39
|
+
remaining acceptance gates.
|
|
40
|
+
|
|
1
41
|
# MTEC AI Local Runner
|
|
2
42
|
|
|
3
43
|
Install and configure the employee-bound macOS daemon:
|
|
@@ -8,7 +48,15 @@ npx mtech-ai-local-runner setup
|
|
|
8
48
|
|
|
9
49
|
The package stores safe runner configuration locally and keeps OAuth refresh credentials in macOS Keychain when available.
|
|
10
50
|
|
|
11
|
-
Public npm installation requires no npm account or publishing OTP. Setup requires
|
|
51
|
+
Public npm installation requires no npm account or publishing OTP. Setup requires
|
|
52
|
+
the employee's own Gateway OAuth login and a supported, authenticated selected
|
|
53
|
+
CLI. Codex uses the employee's ChatGPT account. Claude requires its own login
|
|
54
|
+
and a separately verified host admission; it is currently not admitted.
|
|
55
|
+
|
|
56
|
+
For the current unattended Codex pilot, an administrator must separately install
|
|
57
|
+
the reviewed, employee-UID-bound policy observer on that Mac. The npm setup
|
|
58
|
+
command does not install that privileged service. A fresh employee computer is
|
|
59
|
+
not ready merely because npm installation succeeds.
|
|
12
60
|
|
|
13
61
|
## 0.1.28: completion receipt wire compatibility
|
|
14
62
|
|
|
@@ -8,6 +8,7 @@ import { completionEvent } from "./completion-report.js";
|
|
|
8
8
|
import { requireJobSecurity } from "../../safety/job-security-policy.js";
|
|
9
9
|
import { HOOK_POLICY_ENV_VAR, buildHookPolicy, encodeHookPolicy } from "../../safety/hook-policy-builder.js";
|
|
10
10
|
import { formatHookCommand } from "../../safety/hook-command.js";
|
|
11
|
+
import { localEndpointReadObservations } from '../../safety/local-tool-server.js';
|
|
11
12
|
const ALLOWED_ENV_KEYS = ["PATH", "HOME", "USER", "LOGNAME", "LANG", "LC_ALL", "TMPDIR", "NODE_ENV", "CLAUDE_CONFIG_DIR"];
|
|
12
13
|
const ALLOWED_SUPERVISOR_COMMANDS = new Set(["continue", "answer_question", "request_revision", "request_checkpoint", "submit_for_review"]);
|
|
13
14
|
const LOCAL_NAMES = ["local_read", "local_search", "local_edit", "local_test"].map(name => `mcp__ai_local__${name}`);
|
|
@@ -44,7 +45,16 @@ export class ClaudeCodeAdapter {
|
|
|
44
45
|
cancelled = false;
|
|
45
46
|
started = false;
|
|
46
47
|
receipt;
|
|
48
|
+
toolEndpoint;
|
|
49
|
+
readSnapshot;
|
|
47
50
|
securityReceipt() { return this.receipt ? structuredClone(this.receipt) : undefined; }
|
|
51
|
+
localReadObservations() {
|
|
52
|
+
const endpoint = this.toolEndpoint;
|
|
53
|
+
const observed = endpoint ? localEndpointReadObservations(endpoint.url, endpoint.token) : undefined;
|
|
54
|
+
if (observed)
|
|
55
|
+
this.readSnapshot = observed;
|
|
56
|
+
return this.readSnapshot ? structuredClone(this.readSnapshot) : undefined;
|
|
57
|
+
}
|
|
48
58
|
constructor(options) {
|
|
49
59
|
this.options = options;
|
|
50
60
|
}
|
|
@@ -97,6 +107,7 @@ export class ClaudeCodeAdapter {
|
|
|
97
107
|
if (this.cancelled)
|
|
98
108
|
throw new Error("adapter startup cancelled");
|
|
99
109
|
const endpoint = await this.tools.start();
|
|
110
|
+
this.toolEndpoint = endpoint;
|
|
100
111
|
if (this.cancelled)
|
|
101
112
|
throw new Error("adapter startup cancelled");
|
|
102
113
|
const mcpConfigPath = this.options.writeLocalMcpConfig(endpoint);
|
|
@@ -222,6 +233,8 @@ export class ClaudeCodeAdapter {
|
|
|
222
233
|
await this.cleanup();
|
|
223
234
|
}
|
|
224
235
|
async cleanup() {
|
|
236
|
+
this.localReadObservations();
|
|
237
|
+
this.toolEndpoint = undefined;
|
|
225
238
|
const tools = this.tools;
|
|
226
239
|
const handle = this.handle;
|
|
227
240
|
this.tools = undefined;
|
|
@@ -19,6 +19,7 @@ export class CodexAdapter {
|
|
|
19
19
|
started = false;
|
|
20
20
|
receipt;
|
|
21
21
|
terminalAudit;
|
|
22
|
+
localReadObservations() { return this.usingOfficialExec ? this.ownedExec?.localReadObservations() : undefined; }
|
|
22
23
|
securityReceipt() { return this.terminalAudit ? structuredClone(this.terminalAudit) : this.usingOfficialExec ? this.ownedExec?.receipt() : this.receipt ? structuredClone(this.receipt) : undefined; }
|
|
23
24
|
cancelled = false;
|
|
24
25
|
usingOfficialExec = false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { performance } from 'node:perf_hooks';
|
|
2
2
|
import { PreparationClock, CODEX_PREPARATION_BUDGET_MS } from '../../safety/preparation-timing.js';
|
|
3
|
-
import { localEndpointObservations } from '../../safety/local-tool-server.js';
|
|
3
|
+
import { localEndpointObservations, localEndpointReadObservations } from '../../safety/local-tool-server.js';
|
|
4
4
|
import { processStartupObservations, freezeProcessLaunchObservation } from '../../process/node-process-group-host.js';
|
|
5
5
|
import { executionError } from '../../safety/execution-failure.js';
|
|
6
6
|
import { UNMANAGED_EXEC_MODE } from './unmanaged-scope.js';
|
|
@@ -24,6 +24,7 @@ export class CodexExecLaunchHost {
|
|
|
24
24
|
handle;
|
|
25
25
|
inspection;
|
|
26
26
|
exitAudit;
|
|
27
|
+
readAudit;
|
|
27
28
|
inspectionFingerprint;
|
|
28
29
|
handshake;
|
|
29
30
|
receiptValue;
|
|
@@ -105,7 +106,7 @@ export class CodexExecLaunchHost {
|
|
|
105
106
|
this.inspectionFingerprint = hash(this.inspection.snapshot);
|
|
106
107
|
this.current();
|
|
107
108
|
const invocation = requireOfficialExecPlan(this.plan);
|
|
108
|
-
const manual = ctx.approvedTaskPackage?.security_requirements?.mdm_verification
|
|
109
|
+
const manual = ctx.approvedTaskPackage?.security_requirements?.mdm_verification !== undefined;
|
|
109
110
|
if (manual && typeof this.inspection.beforeSpawn !== 'function')
|
|
110
111
|
throw fail();
|
|
111
112
|
assertPreparationBudget();
|
|
@@ -233,9 +234,22 @@ export class CodexExecLaunchHost {
|
|
|
233
234
|
catch {
|
|
234
235
|
return undefined;
|
|
235
236
|
} }
|
|
237
|
+
localReadObservations() {
|
|
238
|
+
if (!this.stopped) {
|
|
239
|
+
try {
|
|
240
|
+
const token = requireOfficialExecPlan(this.plan).env.AI_GATEWAY_JOB_MCP_TOKEN;
|
|
241
|
+
const current = token ? localEndpointReadObservations(this.plan.endpoint, token) : undefined;
|
|
242
|
+
if (current)
|
|
243
|
+
this.readAudit = current;
|
|
244
|
+
}
|
|
245
|
+
catch { /* Absent observation is never interpreted as successful reads. */ }
|
|
246
|
+
}
|
|
247
|
+
return this.readAudit ? structuredClone(this.readAudit) : undefined;
|
|
248
|
+
}
|
|
236
249
|
cancel() {
|
|
237
250
|
if (this.closePromise)
|
|
238
251
|
return this.closePromise;
|
|
252
|
+
this.localReadObservations();
|
|
239
253
|
this.stopped = true;
|
|
240
254
|
this.receiptValue = undefined;
|
|
241
255
|
if (!this.ownsAttempt) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PRIVILEGED_MDM_MODE, PRIVILEGED_MDM_RISK, validPolicyObserverPin } from './prelaunch-policy.js';
|
|
1
2
|
import { MANUAL_MDM_RISK } from './manual-policy-scope.js';
|
|
2
3
|
import { UNMANAGED_EXEC_MODE, UNMANAGED_SCOPE, requirePolicyScope } from './unmanaged-scope.js';
|
|
3
4
|
import { isDeepStrictEqual } from 'node:util';
|
|
@@ -6,13 +7,13 @@ export const EXEC_RELEASE_GATES = Object.freeze(['binary_release', 'official_per
|
|
|
6
7
|
export const EXEC_INPUT_GATES = Object.freeze(['exact_inputs', 'launch_binding', 'repository_scope', 'private_paths_environment', 'owned_lifecycle']);
|
|
7
8
|
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
9
|
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
|
+
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] : mdmVerification === PRIVILEGED_MDM_MODE ? [PRIVILEGED_MDM_RISK] : [])] : EXEC_RELEASE_RISKS;
|
|
10
11
|
const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
11
12
|
const exact = (v, keys) => isDeepStrictEqual(Object.keys(v).sort(), keys.sort());
|
|
12
13
|
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
14
|
export function requireReleaseDescriptor(r) {
|
|
14
15
|
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
|
+
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.mdm_verification === PRIVILEGED_MDM_MODE ? ['policy_observer'] : [])] : [])]) || 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.mdm_verification !== PRIVILEGED_MDM_MODE) || r.mdm_verification === PRIVILEGED_MDM_MODE && !validPolicyObserverPin(r.policy_observer) || 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
17
|
throw Error('EXEC_RELEASE_DESCRIPTOR_REJECTED');
|
|
17
18
|
}
|
|
18
19
|
/** Diagnostic validator only; live currentness and trusted dependency origin
|
|
@@ -28,7 +29,7 @@ export function assessReleaseExec(value, plan) {
|
|
|
28
29
|
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
30
|
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
31
|
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(isDeepStrictEqual(value.acceptedResidualRisks, releaseRisks(plan.mode, record(value.policyScope) && record(value.policyScope.manual_verification) ? value.policyScope.manual_verification.mode : record(value.policyScope) && record(value.policyScope.privileged_verification) ? value.policyScope.privileged_verification.mode : undefined)), 'EXEC_RELEASE_RISK_MISMATCH');
|
|
32
33
|
need(Array.isArray(value.warnings) && value.warnings.length === 0, 'EXEC_RELEASE_WARNING');
|
|
33
34
|
const r = record(value.release) ? value.release : {};
|
|
34
35
|
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');
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
import { PRIVILEGED_MDM_MODE, validPolicyObserverPin } from './prelaunch-policy.js';
|
|
1
2
|
import { isDeepStrictEqual } from 'node:util';
|
|
2
3
|
import { assessMdmInventory } from '../../safety/codex-native-observation.js';
|
|
3
4
|
import { securityRequirementsFingerprint as hash } from '../../safety/security-requirements.js';
|
|
4
5
|
import { classifyUnmanagedEnvironment } from './unmanaged-scope.js';
|
|
5
6
|
const denied = () => Error('CODEX_MANUAL_POLICY_REJECTED');
|
|
6
7
|
export const MANUAL_MDM_RISK = 'manual_prelaunch_mdm_snapshot_not_continuously_reobserved';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
export function createManualPolicyScopeVerifier(input) { return createPrelaunchVerifier(input); }
|
|
9
|
+
/** Trusted caller must obtain metadata from the pinned peer-fenced native client. */
|
|
10
|
+
export function createPrivilegedPolicyScopeVerifier(input, observer) {
|
|
11
|
+
if (!validPolicyObserverPin(observer))
|
|
12
|
+
throw denied();
|
|
13
|
+
return createPrelaunchVerifier(input, structuredClone(observer));
|
|
14
|
+
}
|
|
15
|
+
function createPrelaunchVerifier(input, observer) {
|
|
11
16
|
const binding = structuredClone(input.binding), inventory = structuredClone(input.inventory), lease = input.leaseDigest, read = input.readLocal, now = input.now ?? Date.now;
|
|
12
17
|
const identityKeys = ['jobId', 'generation', 'actorId', 'runnerId', 'sourceSha', 'binarySha256', 'releaseManifestFingerprint', 'requirementsFingerprint', 'contractFingerprint', 'taskPackageFingerprint', 'launchFingerprint', 'commandsFingerprint', 'toolSchemaFingerprint', 'barrierFingerprint'];
|
|
13
18
|
if (!isDeepStrictEqual(Object.keys(binding).sort(), identityKeys.sort()) || !Number.isSafeInteger(binding.generation) || binding.generation < 1 || !['jobId', 'actorId', 'runnerId'].every(k => typeof binding[k] === 'string' && binding[k].length > 0 && binding[k].length <= 256 && !/[\r\n\0]/.test(binding[k])) || !/^[a-f0-9]{40}$/.test(binding.sourceSha) || !['binarySha256', 'releaseManifestFingerprint', 'requirementsFingerprint', 'contractFingerprint', 'taskPackageFingerprint', 'launchFingerprint', 'commandsFingerprint', 'toolSchemaFingerprint', 'barrierFingerprint'].every(k => /^[a-f0-9]{64}$/.test(binding[k])) || !/^[a-f0-9]{64}$/.test(lease))
|
|
@@ -47,8 +52,8 @@ export function createManualPolicyScopeVerifier(input) {
|
|
|
47
52
|
catch {
|
|
48
53
|
dead = true;
|
|
49
54
|
} return !dead; };
|
|
50
|
-
const verification = Object.freeze({ mode: 'manual_prelaunch', observedAt: inventory.observedAt, inventoryFingerprint: inventory.inventoryFingerprint, continuousMdmMonitoring: false, bindingFingerprint: hash({ ...binding, leaseDigest: lease }) });
|
|
51
|
-
return Object.freeze({ sourcesFingerprint: first.sourcesFingerprint, accountFingerprint: first.accountFingerprint, accountRootFingerprint: first.accountRootFingerprint, observedAt: inventory.observedAt, manualVerification: verification, isCurrent: current,
|
|
55
|
+
const verification = Object.freeze({ mode: observer ? PRIVILEGED_MDM_MODE : 'manual_prelaunch', ...(observer ? { observer } : {}), observedAt: inventory.observedAt, inventoryFingerprint: inventory.inventoryFingerprint, continuousMdmMonitoring: false, bindingFingerprint: hash({ ...binding, leaseDigest: lease }) });
|
|
56
|
+
return Object.freeze({ sourcesFingerprint: first.sourcesFingerprint, accountFingerprint: first.accountFingerprint, accountRootFingerprint: first.accountRootFingerprint, observedAt: inventory.observedAt, ...(observer ? { privilegedVerification: verification } : { manualVerification: verification }), isCurrent: current,
|
|
52
57
|
beforeSpawn(identity, leaseDigest) {
|
|
53
58
|
if (started || spent || !isDeepStrictEqual(identity, binding) || leaseDigest !== lease || !current()) {
|
|
54
59
|
dead = true;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Compiled observer identity, never a task-selected executable or command. */
|
|
2
|
+
export const PRIVILEGED_MDM_MODE = 'privileged_prelaunch';
|
|
3
|
+
export const PRIVILEGED_MDM_RISK = 'privileged_prelaunch_mdm_snapshot_not_continuously_reobserved';
|
|
4
|
+
export function validPolicyObserverPin(v) {
|
|
5
|
+
if (!v || typeof v !== 'object' || Array.isArray(v))
|
|
6
|
+
return false;
|
|
7
|
+
const p = v;
|
|
8
|
+
return Object.keys(p).sort().join(',') === 'protocol,sha256' && p.protocol === 'policy-inventory-v1' && typeof p.sha256 === 'string' && /^[a-f0-9]{64}$/.test(p.sha256);
|
|
9
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PRIVILEGED_MDM_MODE, validPolicyObserverPin } from './prelaunch-policy.js';
|
|
1
2
|
import { isDeepStrictEqual } from 'node:util';
|
|
2
3
|
import { securityRequirementsFingerprint as hash } from '../../safety/security-requirements.js';
|
|
3
4
|
export const UNMANAGED_EXEC_MODE = 'supported_cli_exec_release_policy_v3';
|
|
@@ -68,18 +69,26 @@ export function policyScopeReceipt(plan, leaseDigest, observation) {
|
|
|
68
69
|
if (accountRootFingerprint !== observation.accountRootFingerprint)
|
|
69
70
|
throw denied();
|
|
70
71
|
const manual = observation.manualVerification;
|
|
71
|
-
const
|
|
72
|
+
const privileged = observation.privilegedVerification;
|
|
73
|
+
const value = { ...(manual ? { manual_verification: manual } : {}), ...(privileged ? { privileged_verification: privileged } : {}), version: 'codex-policy-scope-v1', scope: UNMANAGED_SCOPE, classification: 'unmanaged_personal', managed_enforcement: 'not_run', managed_hosts: 'unsupported', sources_fingerprint: observation.sourcesFingerprint, account_fingerprint: observation.accountFingerprint, account_root_fingerprint: accountRootFingerprint, observed_at: observation.observedAt, lease_digest: leaseDigest, binding_fingerprint: hash(scopeBinding(plan, leaseDigest, accountRootFingerprint, observation.sourcesFingerprint, observation.accountFingerprint)) };
|
|
72
74
|
return { ...value, fingerprint: hash(value) };
|
|
73
75
|
}
|
|
74
76
|
/** Audit shape validation is not OS attestation; live trusted currentness must
|
|
75
77
|
* additionally be supplied by the owning host before spawn and every tool. */
|
|
76
78
|
export function requirePolicyScope(value, plan) {
|
|
79
|
+
if (value && typeof value === 'object' && 'manual_verification' in value && 'privileged_verification' in value)
|
|
80
|
+
throw denied();
|
|
77
81
|
if (value && typeof value === 'object' && 'manual_verification' in value) {
|
|
78
82
|
const m = value.manual_verification;
|
|
79
83
|
if (!exact(m, ['mode', 'observedAt', 'inventoryFingerprint', 'continuousMdmMonitoring', 'bindingFingerprint']) || m.mode !== 'manual_prelaunch' || m.continuousMdmMonitoring !== false || !Number.isSafeInteger(m.observedAt) || m.observedAt < 0 || m.observedAt !== value.observed_at || !digest(m.inventoryFingerprint) || m.bindingFingerprint !== hash({ ...plan.identity, leaseDigest: value.lease_digest }))
|
|
80
84
|
throw denied();
|
|
81
85
|
}
|
|
82
|
-
if (
|
|
86
|
+
if (value && typeof value === 'object' && 'privileged_verification' in value) {
|
|
87
|
+
const m = value.privileged_verification;
|
|
88
|
+
if (!exact(m, ['mode', 'observedAt', 'inventoryFingerprint', 'continuousMdmMonitoring', 'bindingFingerprint', 'observer']) || m.mode !== PRIVILEGED_MDM_MODE || !validPolicyObserverPin(m.observer) || m.continuousMdmMonitoring !== false || !Number.isSafeInteger(m.observedAt) || m.observedAt < 0 || m.observedAt !== value.observed_at || !digest(m.inventoryFingerprint) || m.bindingFingerprint !== hash({ ...plan.identity, leaseDigest: value.lease_digest }))
|
|
89
|
+
throw denied();
|
|
90
|
+
}
|
|
91
|
+
if (!exact(value, ['version', 'scope', 'classification', 'managed_enforcement', 'managed_hosts', 'sources_fingerprint', 'account_fingerprint', 'account_root_fingerprint', 'observed_at', 'lease_digest', 'binding_fingerprint', 'fingerprint', ...(value && typeof value === 'object' && 'manual_verification' in value ? ['manual_verification'] : []), ...(value && typeof value === 'object' && 'privileged_verification' in value ? ['privileged_verification'] : [])]) || value.version !== 'codex-policy-scope-v1' || value.scope !== UNMANAGED_SCOPE || value.classification !== 'unmanaged_personal' || value.managed_enforcement !== 'not_run' || value.managed_hosts !== 'unsupported' || !Number.isSafeInteger(value.observed_at) || value.observed_at < 0
|
|
83
92
|
|| !['sources_fingerprint', 'account_fingerprint', 'account_root_fingerprint', 'lease_digest', 'binding_fingerprint', 'fingerprint'].every(k => digest(value[k])) || hash(value) !== value.fingerprint || hash(scopeBinding(plan, value.lease_digest, value.account_root_fingerprint, value.sources_fingerprint, value.account_fingerprint)) !== value.binding_fingerprint || hash({ accountRoot: plan.accountRoot }) !== value.account_root_fingerprint)
|
|
84
93
|
throw denied();
|
|
85
94
|
}
|
|
@@ -116,10 +116,11 @@ export class ExecutionCoordinator {
|
|
|
116
116
|
throw new Error("Binding outcome requires reconciliation");
|
|
117
117
|
}
|
|
118
118
|
// Retain this exact safe terminal payload until Gateway acknowledges it.
|
|
119
|
+
const completionRequest = { jobId: job.id, generation: job.generation, leaseToken: job.leaseToken,
|
|
120
|
+
status: "blocked", result: { preparation_failure: { ...diagnostic } } };
|
|
119
121
|
await this.deps.state.save({ jobId: job.id, generation: job.generation, leaseToken: job.leaseToken,
|
|
120
|
-
phase: "
|
|
121
|
-
await this.deps.gateway.complete(
|
|
122
|
-
status: "blocked", result: { preparation_failure: { ...diagnostic } } });
|
|
122
|
+
phase: "terminal_pending", preparationFailure: diagnostic, completionRequest: structuredClone(completionRequest) });
|
|
123
|
+
await this.deps.gateway.complete(completionRequest);
|
|
123
124
|
await this.deps.state.clear();
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
@@ -183,6 +184,16 @@ export class ExecutionCoordinator {
|
|
|
183
184
|
const evidence = this.deps.terminalEvidenceCollector.collect({ checkoutPath: workspace.path, baseSha: workspace.baseCommitSha });
|
|
184
185
|
payload = { ...payload, diff_hash: evidence.diffHash, changed_file_manifest_hash: evidence.changedFileManifestHash };
|
|
185
186
|
}
|
|
187
|
+
// A vendor report cannot supply host observations, even when its shape looks valid.
|
|
188
|
+
if (payload) {
|
|
189
|
+
payload = { ...payload };
|
|
190
|
+
delete payload.local_read_observations;
|
|
191
|
+
}
|
|
192
|
+
if (isTerminalEventType(event.eventType) || event.eventType === 'blocked' || event.eventType === 'scope_conflict') {
|
|
193
|
+
const reads = safeLocalReadObservations(adapter.localReadObservations?.());
|
|
194
|
+
if (reads)
|
|
195
|
+
payload = { ...payload, local_read_observations: reads };
|
|
196
|
+
}
|
|
186
197
|
const normalized = {
|
|
187
198
|
protocolVersion: PROTOCOL_VERSION, bindingId: binding.bindingId,
|
|
188
199
|
sequence: sequence + 1, eventType: event.eventType,
|
|
@@ -272,7 +283,14 @@ export class ExecutionCoordinator {
|
|
|
272
283
|
const diagnostic = status === "failed" || terminal.eventType === "blocked" ? completionReportDiagnostic(terminal.payload?.completion_report) : undefined;
|
|
273
284
|
const reportDiagnostic = diagnostic && (terminal.eventType === "blocked"
|
|
274
285
|
? diagnostic.code === "CODEX_COMPLETION_REPORT_BLOCKED" : diagnostic.code !== "CODEX_COMPLETION_REPORT_BLOCKED") ? diagnostic : undefined;
|
|
275
|
-
|
|
286
|
+
const securityReceipt = adapter.securityReceipt?.();
|
|
287
|
+
const reads = safeLocalReadObservations(adapter.localReadObservations?.());
|
|
288
|
+
const completionRequest = structuredClone({ jobId: job.id, generation: job.generation, leaseToken: job.leaseToken, status, result: { binding_id: binding.bindingId, event_type: terminal.eventType, summary: terminal.summary, ...(reads ? { local_read_observations: reads } : {}), ...(failureDiagnostic ? { execution_failure: { ...failureDiagnostic } } : {}), ...(reportDiagnostic ? { completion_report: reportDiagnostic } : {}), ...(securityReceipt ? { security_receipt: securityReceipt } : {}) } });
|
|
289
|
+
await this.deps.state.save({ jobId: job.id, generation: job.generation, leaseToken: job.leaseToken,
|
|
290
|
+
phase: "terminal_pending", bindingId: binding.bindingId, terminalEventType: terminal.eventType,
|
|
291
|
+
completionRequest: structuredClone(completionRequest) });
|
|
292
|
+
await this.deps.gateway.complete(completionRequest);
|
|
276
293
|
await this.deps.state.clear();
|
|
277
294
|
}
|
|
278
295
|
}
|
|
296
|
+
import { safeLocalReadObservations } from '../safety/local-read-observations.js';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createCodexDaemonHost } from '../safety/codex-daemon-host.js';
|
|
2
|
+
import { reconcileTerminalState } from './terminal-recovery.js';
|
|
3
|
+
import { packageVersion } from '../package-version.js';
|
|
2
4
|
import { hostname } from "node:os";
|
|
3
5
|
import { createHash } from "node:crypto";
|
|
4
6
|
import { join } from "node:path";
|
|
@@ -21,7 +23,7 @@ import { packagedHookCommandParts } from "../safety/hook-command.js";
|
|
|
21
23
|
export async function buildDaemon(input) {
|
|
22
24
|
if (input.codexSupportedProbe && !input.codexSecurityHost)
|
|
23
25
|
throw new Error('CODEX_SUPPORTED_HOST_REQUIRED');
|
|
24
|
-
const production = !input.codexSecurityHost && !input.codexSupportedProbe ?
|
|
26
|
+
const production = !input.codexSecurityHost && !input.codexSupportedProbe ? createCodexDaemonHost({ runnerId: input.runnerId, binary: input.env.AI_GATEWAY_LOCAL_RUNNER_CODEX_BINARY ?? 'codex', home: input.env.HOME ?? '', codexHome: input.env.CODEX_HOME, searchPath: input.env.PATH }) : undefined;
|
|
25
27
|
let pendingCodexClaim;
|
|
26
28
|
const gateway = new McpGatewayClient({ mcpUrl: input.gatewayMcpUrl, tokenProvider: input.tokenProvider, runnerId: input.runnerId, fetchFn: input.fetchFn });
|
|
27
29
|
// The same parts the CLI self-test ran before starting this daemon (verifyPackagedHook).
|
|
@@ -62,7 +64,8 @@ export async function buildDaemon(input) {
|
|
|
62
64
|
if (renewed?.job) {
|
|
63
65
|
if (renewed.job.id !== job.id || renewed.job.generation !== job.generation)
|
|
64
66
|
throw new Error('CODEX_JOB_LEASE_FENCE_MISMATCH');
|
|
65
|
-
|
|
67
|
+
if (job.taskPackage.runtime_adapter === 'codex_cli')
|
|
68
|
+
production?.heartbeat(job.id, job.generation, renewed.job.lease_expires_at);
|
|
66
69
|
}
|
|
67
70
|
},
|
|
68
71
|
complete: async (result) => { await gateway.completeLocalJob(result); await production?.releaseJob(result.jobId); pendingCodexClaim = undefined; },
|
|
@@ -80,12 +83,29 @@ export async function buildDaemon(input) {
|
|
|
80
83
|
});
|
|
81
84
|
const poller = new JobPoller({
|
|
82
85
|
reportError: (diagnostic) => { process.stderr.write(`${JSON.stringify({ event: "poller_failed", ...diagnostic })}\n`); },
|
|
83
|
-
heartbeat: () =>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
heartbeat: async () => {
|
|
87
|
+
let current;
|
|
88
|
+
try {
|
|
89
|
+
current = await codex.probe();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
current = { available: false, authenticated: false, capabilities: [] };
|
|
93
|
+
}
|
|
94
|
+
const ready = current.available && current.authenticated && !!current.executableVersion && codex.capabilities.every(c => current.capabilities.includes(c));
|
|
95
|
+
const index = probes.findIndex(p => p.id === 'codex_cli');
|
|
96
|
+
if (index >= 0)
|
|
97
|
+
probes[index] = { id: 'codex_cli', version: '1', ...current, authenticated: ready };
|
|
98
|
+
if (ready)
|
|
99
|
+
versions.codex_cli = '1';
|
|
100
|
+
else
|
|
101
|
+
delete versions.codex_cli;
|
|
102
|
+
await gateway.heartbeatLocalRunner({
|
|
103
|
+
deviceFingerprint: createHash("sha256").update(`${process.getuid?.() ?? 0}|${runnerDisplayName()}|${input.gatewayMcpUrl}`).digest("hex"),
|
|
104
|
+
displayName: runnerDisplayName(), packageVersion: packageVersion(),
|
|
105
|
+
capabilities: probes.map((probe) => ({ adapter: probe.id, version: probe.version,
|
|
106
|
+
capabilities: probe.capabilities, authenticated: probe.available && probe.authenticated })),
|
|
107
|
+
});
|
|
108
|
+
},
|
|
89
109
|
wait: async () => {
|
|
90
110
|
const claim = await gateway.waitForLocalJob(versions, 25);
|
|
91
111
|
if (claim.status !== "claimed" || !claim.job || !claim.task_package || !claim.lease_token || !claim.launch_grant)
|
|
@@ -110,26 +130,12 @@ export async function buildDaemon(input) {
|
|
|
110
130
|
lock: new ProcessLock(join(input.stateRoot, "daemon.lock")), state,
|
|
111
131
|
reconcile: async (stored) => {
|
|
112
132
|
const previous = stored;
|
|
133
|
+
if (await reconcileTerminalState(previous, { gateway: { complete: request => gateway.completeLocalJob(request) }, state }))
|
|
134
|
+
return;
|
|
113
135
|
if (await reconcilePreparationState(previous, {
|
|
114
136
|
complete: (result) => gateway.completeLocalJob(result), clear: () => state.clear(),
|
|
115
137
|
}))
|
|
116
138
|
return;
|
|
117
|
-
if (previous.bindingId && previous.jobId && previous.generation && previous.leaseToken) {
|
|
118
|
-
try {
|
|
119
|
-
const receipt = await gateway.readBindingEvents(previous.bindingId, previous.eventCursor ?? 0);
|
|
120
|
-
const status = receipt.terminal
|
|
121
|
-
? (receipt.terminalEventType ?? previous.terminalEventType) === "completed" ? "completed"
|
|
122
|
-
: (receipt.terminalEventType ?? previous.terminalEventType) === "cancelled" ? "cancelled" : "failed"
|
|
123
|
-
: "failed";
|
|
124
|
-
await gateway.completeLocalJob({
|
|
125
|
-
jobId: previous.jobId, generation: previous.generation, leaseToken: previous.leaseToken, status,
|
|
126
|
-
result: { binding_id: previous.bindingId, recovery: receipt.terminal ? "terminal_event_reconciled" : "daemon_restarted_before_terminal_event" },
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
// An expired generation is safely fenced by Gateway and will be reclaimed.
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
139
|
await state.clear();
|
|
134
140
|
},
|
|
135
141
|
poller,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { isTerminalEventType } from '../protocol.js';
|
|
2
|
+
function record(value) {
|
|
3
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function nonempty(value) { return typeof value === 'string' && value.trim().length > 0; }
|
|
6
|
+
function invalid() { throw new Error('Terminal outcome requires reconciliation: invalid pending completion receipt'); }
|
|
7
|
+
/** Replay only the stored completion request; never restart an adapter or reconstruct evidence. */
|
|
8
|
+
export async function reconcileTerminalState(previous, deps) {
|
|
9
|
+
if (previous === null || previous === undefined)
|
|
10
|
+
return false;
|
|
11
|
+
if (!record(previous))
|
|
12
|
+
invalid();
|
|
13
|
+
if (previous.phase !== 'terminal_pending') {
|
|
14
|
+
if (!['claimed', 'shadow', 'binding', 'binding_uncertain', 'preparation_failed'].includes(String(previous.phase))
|
|
15
|
+
|| !nonempty(previous.jobId) || !Number.isSafeInteger(previous.generation) || previous.generation < 1
|
|
16
|
+
|| !nonempty(previous.leaseToken))
|
|
17
|
+
invalid();
|
|
18
|
+
// Only preparation states without execution evidence may reach the legacy
|
|
19
|
+
// preparation handler (whose caller may clear an unhandled receipt).
|
|
20
|
+
const allowed = ['phase', 'jobId', 'generation', 'leaseToken', 'workspaceManifest'];
|
|
21
|
+
if (previous.phase === 'preparation_failed' || previous.phase === 'binding_uncertain')
|
|
22
|
+
allowed.push('preparationFailure');
|
|
23
|
+
if (Object.keys(previous).some(key => !allowed.includes(key)))
|
|
24
|
+
invalid();
|
|
25
|
+
if (previous.phase === 'preparation_failed' || previous.preparationFailure !== undefined) {
|
|
26
|
+
const diagnostic = previous.preparationFailure;
|
|
27
|
+
if (!record(diagnostic) || !nonempty(diagnostic.stage) || !nonempty(diagnostic.code)
|
|
28
|
+
|| Object.keys(diagnostic).some(key => !['stage', 'code', 'tool'].includes(key))
|
|
29
|
+
|| (diagnostic.tool !== undefined && !nonempty(diagnostic.tool)))
|
|
30
|
+
invalid();
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const request = previous.completionRequest;
|
|
35
|
+
if (!record(request) || Object.keys(request).sort().join(',') !== 'generation,jobId,leaseToken,result,status'
|
|
36
|
+
|| !nonempty(previous.jobId) || !Number.isSafeInteger(previous.generation) || previous.generation < 1
|
|
37
|
+
|| !nonempty(previous.leaseToken) || request.jobId !== previous.jobId
|
|
38
|
+
|| request.generation !== previous.generation || request.leaseToken !== previous.leaseToken || !record(request.result))
|
|
39
|
+
invalid();
|
|
40
|
+
const result = request.result;
|
|
41
|
+
if (previous.bindingId !== undefined) {
|
|
42
|
+
const eventType = result.event_type;
|
|
43
|
+
if (!nonempty(previous.bindingId) || result.binding_id !== previous.bindingId || typeof eventType !== 'string'
|
|
44
|
+
|| (!isTerminalEventType(eventType) && eventType !== 'blocked' && eventType !== 'scope_conflict') || previous.terminalEventType !== eventType || typeof result.summary !== 'string')
|
|
45
|
+
invalid();
|
|
46
|
+
const status = eventType === 'completed' ? 'completed' : eventType === 'cancelled' ? 'cancelled'
|
|
47
|
+
: eventType === 'blocked' || eventType === 'scope_conflict' ? 'blocked' : 'failed';
|
|
48
|
+
if (request.status !== status)
|
|
49
|
+
invalid();
|
|
50
|
+
}
|
|
51
|
+
else if (request.status !== 'blocked' || !record(result.preparation_failure)
|
|
52
|
+
|| result.binding_id !== undefined || result.event_type !== undefined || previous.terminalEventType !== undefined
|
|
53
|
+
|| !nonempty(result.preparation_failure.stage) || !nonempty(result.preparation_failure.code))
|
|
54
|
+
invalid();
|
|
55
|
+
// Snapshot the validated receipt so a transport cannot mutate retained recovery state.
|
|
56
|
+
await deps.gateway.complete(structuredClone(request));
|
|
57
|
+
await deps.state.clear();
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
// Both the source tree (src/) and packaged build (dist/src/) resolve their own metadata.
|
|
3
|
+
export function packageVersion(moduleUrl = import.meta.url) {
|
|
4
|
+
const semver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(?:(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
5
|
+
for (const relative of ['../package.json', '../../package.json']) {
|
|
6
|
+
let metadata;
|
|
7
|
+
try {
|
|
8
|
+
metadata = JSON.parse(readFileSync(new URL(relative, moduleUrl), 'utf8'));
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error.code === 'ENOENT')
|
|
12
|
+
continue;
|
|
13
|
+
throw new Error('Runner package version metadata is invalid');
|
|
14
|
+
}
|
|
15
|
+
if (metadata && typeof metadata === 'object' && metadata.name === 'mtech-ai-local-runner') {
|
|
16
|
+
const version = metadata.version;
|
|
17
|
+
if (typeof version === 'string' && semver.test(version))
|
|
18
|
+
return version;
|
|
19
|
+
throw new Error('Runner package version metadata is invalid');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
throw new Error('Runner package version metadata is missing');
|
|
23
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { CODEX_DAEMON_EXEC_RELEASES } from './codex-daemon-releases.js';
|
|
2
|
+
import { registeredPrelaunchDependencies, CODEX_MANUAL_BINARY } from './codex-manual-registered.js';
|
|
3
|
+
import { createPrivilegedDaemonController } from './codex-manual-controller.js';
|
|
4
|
+
import { createPolicyInventoryClient, verifyPolicyObserver } from './policy-inventory-client.js';
|
|
5
|
+
import { PRIVILEGED_MDM_MODE } from '../adapters/codex/prelaunch-policy.js';
|
|
6
|
+
import { buildCodexExecJobOptions } from '../adapters/codex/exec-runtime-options.js';
|
|
7
|
+
import { reviewedCodexExecRelease } from './codex-exec-production-host.js';
|
|
8
|
+
/** Compiled/trusted host composition. No environment, task or saved JSON creates
|
|
9
|
+
* an admission capability. A missing reviewed release stays unavailable. */
|
|
10
|
+
export function createCodexDaemonHost(input, deps) {
|
|
11
|
+
const claims = new Map(), controllers = new Map(), hosts = new Map(), leases = new Map();
|
|
12
|
+
const releases = structuredClone(deps?.native.releases ?? CODEX_DAEMON_EXEC_RELEASES);
|
|
13
|
+
const native = deps?.native ?? (releases.length ? { ...registeredPrelaunchDependencies(input.home, input.codexHome, PRIVILEGED_MDM_MODE, releases), releases } : undefined);
|
|
14
|
+
let selected;
|
|
15
|
+
const available = async () => {
|
|
16
|
+
const matches = releases.filter(r => reviewedCodexExecRelease(r) && r.mdmVerification === PRIVILEGED_MDM_MODE && r.credentialBackend === 'auto');
|
|
17
|
+
if (!native || matches.length !== 1)
|
|
18
|
+
return { available: false, authenticated: false, capabilities: [], reason: 'CODEX_DAEMON_REVIEWED_OBSERVER_UNAVAILABLE' };
|
|
19
|
+
try {
|
|
20
|
+
const pin = matches[0].policyObserver;
|
|
21
|
+
(native.verifyObserver ?? verifyPolicyObserver)(pin);
|
|
22
|
+
await (native.checkObserverReady ?? (p => createPolicyInventoryClient(p).checkReady()))(pin);
|
|
23
|
+
return await native.probe();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { available: false, authenticated: false, capabilities: [], reason: 'CODEX_DAEMON_REVIEWED_OBSERVER_UNAVAILABLE' };
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
probe: available,
|
|
31
|
+
registerClaim(value) {
|
|
32
|
+
if (selected || claims.size)
|
|
33
|
+
throw Error('CODEX_ACTIVE_CLAIM_CONFLICT');
|
|
34
|
+
if (value.task_package?.security_requirements?.mdm_verification !== PRIVILEGED_MDM_MODE)
|
|
35
|
+
throw Error('CODEX_DAEMON_PROVENANCE_REQUIRED');
|
|
36
|
+
if (!native || releases.length !== 1)
|
|
37
|
+
throw Error('CODEX_DAEMON_REVIEWED_OBSERVER_UNAVAILABLE');
|
|
38
|
+
const c = structuredClone(value);
|
|
39
|
+
if (!c.job?.id || !Number.isSafeInteger(c.job.lease_expires_at))
|
|
40
|
+
throw Error('CODEX_DAEMON_CLAIM_REJECTED');
|
|
41
|
+
selected = c.job.id;
|
|
42
|
+
claims.set(selected, c);
|
|
43
|
+
leases.set(selected, { generation: c.job.generation, expires: c.job.lease_expires_at });
|
|
44
|
+
},
|
|
45
|
+
options(jobId, worktreePath, stateRoot, env) {
|
|
46
|
+
const claim = claims.get(jobId);
|
|
47
|
+
if (selected !== jobId || !claim || !native)
|
|
48
|
+
throw Error('CODEX_TRUSTED_CLAIM_REQUIRED');
|
|
49
|
+
let inner, cleanup, closed = false;
|
|
50
|
+
const lazy = { async prepare(ctx) {
|
|
51
|
+
if (closed || controllers.has(jobId))
|
|
52
|
+
throw Error('CODEX_DAEMON_PREPARATION_REUSED');
|
|
53
|
+
const pin = releases[0].policyObserver;
|
|
54
|
+
const client = createPolicyInventoryClient(pin);
|
|
55
|
+
const controller = createPrivilegedDaemonController({ runnerId: input.runnerId, binary: native.binary ?? CODEX_MANUAL_BINARY, home: input.home, codexHome: input.codexHome, claim }, { releases, observer: pin, readLocal: native.readLocal, readNativeAuth: native.readNativeAuth, collectInventory: native.collectInventory ?? client.collectOnce });
|
|
56
|
+
controllers.set(jobId, controller);
|
|
57
|
+
const host = await controller.prepare();
|
|
58
|
+
if (closed) {
|
|
59
|
+
await host.close();
|
|
60
|
+
throw Error('CODEX_DAEMON_CANCELLED');
|
|
61
|
+
}
|
|
62
|
+
hosts.set(jobId, host);
|
|
63
|
+
const lease = leases.get(jobId);
|
|
64
|
+
host.heartbeat(jobId, lease.generation, lease.expires);
|
|
65
|
+
inner = host.job(jobId, worktreePath);
|
|
66
|
+
if (cleanup)
|
|
67
|
+
inner.attachCleanup(cleanup);
|
|
68
|
+
return inner.prepare(ctx);
|
|
69
|
+
}, async inspect(plan) { if (!inner || closed)
|
|
70
|
+
throw Error('CODEX_DAEMON_NOT_PREPARED'); return inner.inspect(plan); }, revoke() { closed = true; controllers.get(jobId)?.cancel(); inner?.revoke(); }, attachCleanup(action) { if (cleanup || closed)
|
|
71
|
+
throw Error('CODEX_DAEMON_CLEANUP_REUSED'); cleanup = action; }, async close() { closed = true; controllers.get(jobId)?.cancel(); await inner?.close(); } };
|
|
72
|
+
return buildCodexExecJobOptions({ jobId, worktreePath, stateRoot, env, productionJob: () => lazy, processHost: deps?.processHost });
|
|
73
|
+
},
|
|
74
|
+
heartbeat(id, generation, expires) { const l = leases.get(id); if (!l || l.generation !== generation || !Number.isSafeInteger(expires) || expires * 1000 <= Date.now())
|
|
75
|
+
throw Error('CODEX_JOB_LEASE_FENCE_MISMATCH'); l.expires = expires; hosts.get(id)?.heartbeat(id, generation, expires); },
|
|
76
|
+
async releaseJob(id) { try {
|
|
77
|
+
await controllers.get(id)?.close();
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
controllers.delete(id);
|
|
81
|
+
hosts.delete(id);
|
|
82
|
+
claims.delete(id);
|
|
83
|
+
leases.delete(id);
|
|
84
|
+
if (selected === id)
|
|
85
|
+
selected = undefined;
|
|
86
|
+
} },
|
|
87
|
+
async close() { for (const c of controllers.values())
|
|
88
|
+
c.cancel(); try {
|
|
89
|
+
const results = await Promise.allSettled([...controllers.values()].map(c => c.close()));
|
|
90
|
+
if (results.some(r => r.status === 'rejected'))
|
|
91
|
+
throw Error('CODEX_DAEMON_CLEANUP_FAILED');
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
claims.clear();
|
|
95
|
+
controllers.clear();
|
|
96
|
+
hosts.clear();
|
|
97
|
+
leases.clear();
|
|
98
|
+
selected = undefined;
|
|
99
|
+
} },
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Installed no-model observer evidence; actual daemon model acceptance remains pending.
|
|
2
|
+
* Prior exact CLI gate fixtures are inherited, not rerun or relabelled as daemon acceptance. */
|
|
3
|
+
export const CODEX_DAEMON_EXEC_RELEASES = Object.freeze([{
|
|
4
|
+
"version": "codex-exec-supported-release-v3",
|
|
5
|
+
"adapterVersion": "1",
|
|
6
|
+
"transport": "official_exec_v1",
|
|
7
|
+
"securityMode": "supported_cli_exec_release_policy_v3",
|
|
8
|
+
"policyScope": "unmanaged_personal_only",
|
|
9
|
+
"managedEnforcement": "not_run",
|
|
10
|
+
"executableVersion": "codex-cli 0.163.0-alpha.10.2",
|
|
11
|
+
"binarySha256": "1afb28c01d7d49e892bd75f11076b10f1451e34e0ee308a95e5ab7f69f9c84fe",
|
|
12
|
+
"modelIds": [
|
|
13
|
+
"gpt-5.6-sol"
|
|
14
|
+
],
|
|
15
|
+
"decisionId": "d31b9b90-2aeb-4735-88eb-61b5de777d85",
|
|
16
|
+
"acceptedResidualRisks": [
|
|
17
|
+
"vendor_native_policy_enforcement",
|
|
18
|
+
"managed_policy_not_directly_observed_per_job",
|
|
19
|
+
"first_inference_order_not_observed_per_job",
|
|
20
|
+
"job_mcp_token_possession_not_os_identity",
|
|
21
|
+
"account_startup_may_refresh_shared_credentials",
|
|
22
|
+
"concurrent_policy_change_not_atomic",
|
|
23
|
+
"local_account_metadata_not_remote_entitlement",
|
|
24
|
+
"privileged_prelaunch_mdm_snapshot_not_continuously_reobserved"
|
|
25
|
+
],
|
|
26
|
+
"credentialBackend": "auto",
|
|
27
|
+
"mdmVerification": "privileged_prelaunch",
|
|
28
|
+
"fixtureEvidence": {
|
|
29
|
+
"binary_release": "fa1cd6ce84178d0696c1ae397c05cf7cea35dcac5616e91dda85725e50976371",
|
|
30
|
+
"official_permissions": "cabb85e115996d50fe375f234aad4551dbfa5c7fe7a86cbda0f3d30c1d6fd471",
|
|
31
|
+
"managed_scope_exclusion": "a559199e229e153759f7a7cbd682aae423c0e3d33ef2a692fcccb5eed7c57ed3",
|
|
32
|
+
"required_mcp_startup": "e7305dcdfc4c86fd30d02d311ae7a061e7a21800ef86c3f7d1035330d49ae474",
|
|
33
|
+
"source_exclusion": "93697ceee0d5e864dc7267dab99fabd93db35c7d841dce50e39d6d6ad705bd08",
|
|
34
|
+
"auth_backend_compatibility": "7c42b882a52698fef3e14b4b956ede91ed1e3b29511fbff3176387063b8d74f0"
|
|
35
|
+
},
|
|
36
|
+
"fixturesFingerprint": "f1ccefe90b39aae420b47b902be5a2d61d957e2b600dd26081ae91e5e94dc0af",
|
|
37
|
+
"commands": [],
|
|
38
|
+
"policyObserver": {
|
|
39
|
+
"protocol": "policy-inventory-v1",
|
|
40
|
+
"sha256": "2a07812cd47782826a359b749096b681a6ee49e91f990d48abdc60f2d46e4c9c"
|
|
41
|
+
}
|
|
42
|
+
}]);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { PRIVILEGED_MDM_MODE, validPolicyObserverPin } from '../adapters/codex/prelaunch-policy.js';
|
|
1
3
|
import { createHash } from 'node:crypto';
|
|
2
4
|
import { UNMANAGED_EXEC_MODE, UNMANAGED_SCOPE, createUnmanagedScopeVerifier, policyScopeReceipt } from '../adapters/codex/unmanaged-scope.js';
|
|
3
5
|
import { CODEX_GLOBAL_INSTRUCTION_WRAPPER, accountAncestorPaths, requireGlobalInstructionInvocation } from '../adapters/codex/global-instructions.js';
|
|
@@ -22,7 +24,7 @@ const exact = (v, keys) => JSON.stringify(Object.keys(v).sort()) === JSON.string
|
|
|
22
24
|
const inside = (r, p) => p === r || p.startsWith(r + path.sep);
|
|
23
25
|
export const execReleaseFingerprint = (value) => hash(value);
|
|
24
26
|
export function reviewedCodexExecRelease(r) { try {
|
|
25
|
-
return exact(r, ['version', 'adapterVersion', 'transport', 'securityMode', 'executableVersion', 'binarySha256', 'modelIds', 'decisionId', 'acceptedResidualRisks', 'credentialBackend', 'fixtureEvidence', 'fixturesFingerprint', 'commands', ...(r.securityMode === UNMANAGED_EXEC_MODE ? ['policyScope', 'managedEnforcement', ...(r.mdmVerification === undefined ? [] : ['mdmVerification'])] : [])]) && r.version === (r.securityMode === UNMANAGED_EXEC_MODE ? 'codex-exec-supported-release-v3' : 'codex-exec-supported-release-v2') && (r.securityMode !== UNMANAGED_EXEC_MODE || r.policyScope === UNMANAGED_SCOPE && r.managedEnforcement === 'not_run' && (r.mdmVerification === undefined || r.mdmVerification === 'manual_prelaunch')) && r.adapterVersion === '1' && r.transport === 'official_exec_v1' && isReleaseExecMode(r.securityMode) && (r.credentialBackend === 'file' || r.securityMode === UNMANAGED_EXEC_MODE && r.credentialBackend === 'auto') && typeof r.executableVersion === 'string' && !!r.executableVersion && !/[\r\n\0]/.test(r.executableVersion) && hex(r.binarySha256) && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(r.decisionId) && Array.isArray(r.modelIds) && r.modelIds.length > 0 && new Set(r.modelIds).size === r.modelIds.length && r.modelIds.every(m => typeof m === 'string' && m.length > 0 && m.length < 256 && !/[\r\n\0]/.test(m)) && JSON.stringify(r.acceptedResidualRisks) === JSON.stringify(releaseRisks(r.securityMode, r.mdmVerification)) && r.fixtureEvidence && exact(r.fixtureEvidence, [...releaseGates(r.securityMode)]) && Object.values(r.fixtureEvidence).every(hex) && r.fixturesFingerprint === fingerprint(r.fixtureEvidence) && Array.isArray(r.commands) && new Set(r.commands.map(c => c.id)).size === r.commands.length && r.commands.every(registeredCommandValid);
|
|
27
|
+
return exact(r, ['version', 'adapterVersion', 'transport', 'securityMode', 'executableVersion', 'binarySha256', 'modelIds', 'decisionId', 'acceptedResidualRisks', 'credentialBackend', 'fixtureEvidence', 'fixturesFingerprint', 'commands', ...(r.securityMode === UNMANAGED_EXEC_MODE ? ['policyScope', 'managedEnforcement', ...(r.mdmVerification === undefined ? [] : ['mdmVerification']), ...(r.mdmVerification === PRIVILEGED_MDM_MODE ? ['policyObserver'] : [])] : [])]) && r.version === (r.securityMode === UNMANAGED_EXEC_MODE ? 'codex-exec-supported-release-v3' : 'codex-exec-supported-release-v2') && (r.securityMode !== UNMANAGED_EXEC_MODE || r.policyScope === UNMANAGED_SCOPE && r.managedEnforcement === 'not_run' && (r.mdmVerification === undefined || r.mdmVerification === 'manual_prelaunch' || r.mdmVerification === PRIVILEGED_MDM_MODE && validPolicyObserverPin(r.policyObserver))) && r.adapterVersion === '1' && r.transport === 'official_exec_v1' && isReleaseExecMode(r.securityMode) && (r.credentialBackend === 'file' || r.securityMode === UNMANAGED_EXEC_MODE && r.credentialBackend === 'auto') && typeof r.executableVersion === 'string' && !!r.executableVersion && !/[\r\n\0]/.test(r.executableVersion) && hex(r.binarySha256) && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(r.decisionId) && Array.isArray(r.modelIds) && r.modelIds.length > 0 && new Set(r.modelIds).size === r.modelIds.length && r.modelIds.every(m => typeof m === 'string' && m.length > 0 && m.length < 256 && !/[\r\n\0]/.test(m)) && JSON.stringify(r.acceptedResidualRisks) === JSON.stringify(releaseRisks(r.securityMode, r.mdmVerification)) && r.fixtureEvidence && exact(r.fixtureEvidence, [...releaseGates(r.securityMode)]) && Object.values(r.fixtureEvidence).every(hex) && r.fixturesFingerprint === fingerprint(r.fixtureEvidence) && Array.isArray(r.commands) && new Set(r.commands.map(c => c.id)).size === r.commands.length && r.commands.every(registeredCommandValid);
|
|
26
28
|
}
|
|
27
29
|
catch {
|
|
28
30
|
return false;
|
|
@@ -64,7 +66,7 @@ export function createCodexExecProductionHost(original) {
|
|
|
64
66
|
if (!credential || credential.backend !== backend || !credential.available || !Number.isFinite(credential.expiresAt) || credential.expiresAt <= Date.now())
|
|
65
67
|
throw reject();
|
|
66
68
|
const policyObservation = matches[0].securityMode === UNMANAGED_EXEC_MODE ? policyVerifier.observe() : undefined;
|
|
67
|
-
if (policyObservation && (policyObservation.accountRootFingerprint !== hash({ accountRoot: account }) ||
|
|
69
|
+
if (policyObservation && (policyObservation.accountRootFingerprint !== hash({ accountRoot: account }) || (policyObservation.manualVerification?.mode ?? policyObservation.privilegedVerification?.mode) !== matches[0].mdmVerification || matches[0].mdmVerification === PRIVILEGED_MDM_MODE && !isDeepStrictEqual(policyObservation.privilegedVerification?.observer, matches[0].policyObserver)))
|
|
68
70
|
throw reject();
|
|
69
71
|
return { policyObservation, binary, release: matches[0], config, binaryStamp, configStamp: configData.stamp };
|
|
70
72
|
}
|
|
@@ -119,7 +121,7 @@ export function createCodexExecProductionHost(original) {
|
|
|
119
121
|
try {
|
|
120
122
|
selected = await inspect();
|
|
121
123
|
const r = requireSecurityRequirements(p.security_requirements, { adapterId: 'codex_cli', adapterVersion: '1' }), release = selected.release;
|
|
122
|
-
if (r.mdm_verification !== release.mdmVerification || r.security_mode !== release.securityMode || value.generation !== j.generation || value.resolvedModelId !== p.resolved_model_id || !release.modelIds.includes(value.resolvedModelId) || fingerprint(value.approvedTaskPackage) !== c.task_package_fingerprint || value.securityBinding?.contractFingerprint !== c.contract_fingerprint || value.securityBinding?.taskPackageFingerprint !== c.task_package_fingerprint || value.securityBinding?.launchFingerprint !== j.launch_fingerprint || r.release_manifest_sha256 !== execReleaseFingerprint(release) || r.binary_sha256 !== release.binarySha256 || r.fixtures_sha256 !== release.fixturesFingerprint || r.commands_sha256 !== commandRegistryFingerprint(release.commands) || r.tools_sha256 !== fingerprint({ tools: localToolInventory() }) || JSON.stringify(r.model_ids) !== JSON.stringify(release.modelIds))
|
|
124
|
+
if (r.mdm_verification !== release.mdmVerification || !isDeepStrictEqual(r.policy_observer, release.policyObserver) || r.security_mode !== release.securityMode || value.generation !== j.generation || value.resolvedModelId !== p.resolved_model_id || !release.modelIds.includes(value.resolvedModelId) || fingerprint(value.approvedTaskPackage) !== c.task_package_fingerprint || value.securityBinding?.contractFingerprint !== c.contract_fingerprint || value.securityBinding?.taskPackageFingerprint !== c.task_package_fingerprint || value.securityBinding?.launchFingerprint !== j.launch_fingerprint || r.release_manifest_sha256 !== execReleaseFingerprint(release) || r.binary_sha256 !== release.binarySha256 || r.fixtures_sha256 !== release.fixturesFingerprint || r.commands_sha256 !== commandRegistryFingerprint(release.commands) || r.tools_sha256 !== fingerprint({ tools: localToolInventory() }) || JSON.stringify(r.model_ids) !== JSON.stringify(release.modelIds))
|
|
123
125
|
throw reject();
|
|
124
126
|
const source = await realpath(sourceRoot), home = await realpath(account);
|
|
125
127
|
if (inside(source, home) || inside(home, source))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PRIVILEGED_MDM_MODE, validPolicyObserverPin } from '../adapters/codex/prelaunch-policy.js';
|
|
1
2
|
import { executionError } from './execution-failure.js';
|
|
2
3
|
import { eligiblePersonalAccount } from '../adapters/codex/unmanaged-scope.js';
|
|
3
4
|
import { securityRequirementsFingerprint as hash } from './security-requirements.js';
|
|
@@ -15,13 +16,15 @@ import { CODEX_EXEC_RELEASES } from './codex-exec-releases.js';
|
|
|
15
16
|
import { measureCommandFile } from './registered-command-integrity.js';
|
|
16
17
|
import { readStableText } from './stable-file-read.js';
|
|
17
18
|
import { createManualProfileQuery } from './codex-manual-profile-query.js';
|
|
18
|
-
import { createManualPolicyScopeVerifier } from '../adapters/codex/manual-policy-scope.js';
|
|
19
|
+
import { createManualPolicyScopeVerifier, createPrivilegedPolicyScopeVerifier } from '../adapters/codex/manual-policy-scope.js';
|
|
19
20
|
const rejected = () => Error('CODEX_MANUAL_PILOT_REJECTED');
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
export function createManualPilotController(input, deps) { return createPrelaunchController(input, deps, 'manual_prelaunch'); }
|
|
22
|
+
export function createPrivilegedDaemonController(input, deps) {
|
|
23
|
+
if (!validPolicyObserverPin(deps.observer) || typeof deps.collectInventory !== 'function')
|
|
24
|
+
throw rejected();
|
|
25
|
+
return createPrelaunchController(input, deps, PRIVILEGED_MDM_MODE);
|
|
26
|
+
}
|
|
27
|
+
function createPrelaunchController(input, deps, mode) {
|
|
25
28
|
const claim = structuredClone(input.claim), releases = structuredClone(deps?.releases ?? CODEX_EXEC_RELEASES), readLocal = deps?.readLocal, now = deps?.now ?? Date.now;
|
|
26
29
|
const account = path.resolve(input.codexHome ?? path.join(input.home, '.codex'));
|
|
27
30
|
let used = false, closed = false, manual, host;
|
|
@@ -37,13 +40,13 @@ export function createManualPilotController(input, deps) {
|
|
|
37
40
|
if (!readLocal || !path.isAbsolute(input.binary) || !path.isAbsolute(input.home) || await realpath(input.home) !== input.home || await realpath(account) !== account)
|
|
38
41
|
throw rejected();
|
|
39
42
|
stage = 'codex_manual_release';
|
|
40
|
-
const sha = await measureCommandFile(input.binary), matches = releases.filter(r => reviewedCodexExecRelease(r) && r.mdmVerification ===
|
|
43
|
+
const sha = await measureCommandFile(input.binary), matches = releases.filter(r => reviewedCodexExecRelease(r) && r.mdmVerification === mode && r.binarySha256 === sha);
|
|
41
44
|
if (matches.length !== 1)
|
|
42
45
|
throw rejected();
|
|
43
46
|
const release = matches[0], requirements = claim.task_package?.security_requirements;
|
|
44
47
|
stage = 'codex_manual_config';
|
|
45
48
|
const config = parse((await readStableText(path.join(account, 'config.toml'))).text);
|
|
46
|
-
if ((config.cli_auth_credentials_store ?? 'auto') !== release.credentialBackend || requirements?.release_manifest_sha256 !== execReleaseFingerprint(release) || requirements?.mdm_verification !==
|
|
49
|
+
if ((config.cli_auth_credentials_store ?? 'auto') !== release.credentialBackend || requirements?.release_manifest_sha256 !== execReleaseFingerprint(release) || requirements?.mdm_verification !== mode || mode === PRIVILEGED_MDM_MODE && !isDeepStrictEqual(release.policyObserver, deps?.observer))
|
|
47
50
|
throw rejected();
|
|
48
51
|
host = createCodexExecProductionHost({ runnerId: input.runnerId, binary: input.binary, home: input.home, codexHome: account, releases: [release], readNativeAuth: deps?.readNativeAuth, policyScopeVerifier: { observe() { if (closed || !manual)
|
|
49
52
|
throw rejected(); return manual.observe(); } } });
|
|
@@ -71,14 +74,15 @@ export function createManualPilotController(input, deps) {
|
|
|
71
74
|
if (closed)
|
|
72
75
|
throw rejected();
|
|
73
76
|
stage = 'codex_manual_policy_scope';
|
|
74
|
-
|
|
77
|
+
const scopeInput = { binding: { jobId: job.id, generation: job.generation, actorId: claim.security_context.actor_id, runnerId: input.runnerId, sourceSha: p.repository.base_commit_sha, binarySha256: release.binarySha256, releaseManifestFingerprint: String(r.release_manifest_sha256), requirementsFingerprint: String(r.fingerprint), contractFingerprint: claim.contract_fingerprint, taskPackageFingerprint: claim.task_package_fingerprint, launchFingerprint: job.launch_fingerprint, commandsFingerprint: String(r.commands_sha256), toolSchemaFingerprint: String(r.tools_sha256), barrierFingerprint: String(r.fixtures_sha256) }, leaseDigest: createHash('sha256').update(claim.lease_token).digest('hex'), inventory, readLocal, now };
|
|
78
|
+
manual = mode === PRIVILEGED_MDM_MODE ? createPrivilegedPolicyScopeVerifier(scopeInput, deps.observer) : createManualPolicyScopeVerifier(scopeInput);
|
|
75
79
|
stage = 'codex_manual_host_probe';
|
|
76
80
|
if (!(await host.probe()).available || closed)
|
|
77
81
|
throw rejected();
|
|
78
82
|
return host;
|
|
79
83
|
}
|
|
80
84
|
catch (error) {
|
|
81
|
-
const safe = executionError(error, stage, 'CODEX_MANUAL_PILOT_REJECTED');
|
|
85
|
+
const safe = executionError(error, mode === PRIVILEGED_MDM_MODE ? 'codex_policy_inventory' : stage, mode === PRIVILEGED_MDM_MODE ? 'CODEX_POLICY_INVENTORY_REJECTED' : 'CODEX_MANUAL_PILOT_REJECTED');
|
|
82
86
|
await close().catch(() => { });
|
|
83
87
|
throw safe;
|
|
84
88
|
}
|
|
@@ -12,13 +12,15 @@ import { CLAUDE_CAPABILITIES } from '../adapters/discovery.js';
|
|
|
12
12
|
/** Compiled reviewed identity; never selected by task, environment or receipts. */
|
|
13
13
|
export const CODEX_MANUAL_BINARY = '/Applications/ChatGPT.app/Contents/Resources/codex-cli/CodexCLI.app/Contents/MacOS/codex';
|
|
14
14
|
export const CODEX_MANUAL_NATIVE_READER = Object.freeze({ "binary": "/Library/PrivilegedHelperTools/com.mtec.ai-gateway.codex-observe.681fb12e0b83ff8fbb612dc71f9a5f5c9be5334bcfead29314ca5b979b1dd986", "sha256": "681fb12e0b83ff8fbb612dc71f9a5f5c9be5334bcfead29314ca5b979b1dd986" });
|
|
15
|
-
export function registeredManualDependencies(home, codexHome) {
|
|
15
|
+
export function registeredManualDependencies(home, codexHome) { return registeredPrelaunchDependencies(home, codexHome, 'manual_prelaunch', CODEX_EXEC_RELEASES); }
|
|
16
|
+
/** Trusted compiled registration shared by manual and daemon host composition. */
|
|
17
|
+
export function registeredPrelaunchDependencies(home, codexHome, mode, releases) {
|
|
16
18
|
const account = path.resolve(codexHome ?? path.join(home, '.codex')), pin = CODEX_MANUAL_NATIVE_READER;
|
|
17
19
|
if (!path.isAbsolute(home) || !pin)
|
|
18
20
|
throw Error('CODEX_MANUAL_REGISTRATION_UNAVAILABLE');
|
|
19
21
|
const reader = createNativeObservationReader({ accountRoot: account, backend: 'auto', keyringKind: 'direct', process: nativeMetadataProcess(pin.binary, pin.sha256), systemSource: systemPolicySource, mdmInventory: () => { throw Error('MANUAL_INVENTORY_ONLY'); } });
|
|
20
22
|
return Object.freeze({ binary: CODEX_MANUAL_BINARY, readLocal: reader.readLocal, readNativeAuth: () => reader.readAuth(), async probe() {
|
|
21
|
-
const digest = await measureCommandFile(CODEX_MANUAL_BINARY), matches =
|
|
23
|
+
const digest = await measureCommandFile(CODEX_MANUAL_BINARY), matches = releases.filter(r => reviewedCodexExecRelease(r) && r.binarySha256 === digest && r.mdmVerification === mode && r.credentialBackend === 'auto');
|
|
22
24
|
const config = parse((await readStableText(path.join(account, 'config.toml'))).text);
|
|
23
25
|
if (matches.length !== 1 || (config.cli_auth_credentials_store ?? 'auto') !== 'auto' || config.features?.secret_auth_storage === true)
|
|
24
26
|
throw Error('CODEX_MANUAL_REGISTRATION_UNAVAILABLE');
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function safeLocalReadObservations(value) {
|
|
2
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
3
|
+
return;
|
|
4
|
+
const v = value, c = v.classification_counts;
|
|
5
|
+
if (Object.keys(v).sort().join() !== ['version', 'successful_read_calls', 'unique_successful_paths', 'failed_read_calls', 'classification_counts', 'truncated'].sort().join()
|
|
6
|
+
|| v.version !== 'local-read-observations-v1' || typeof v.truncated !== 'boolean' || !c || typeof c !== 'object' || Array.isArray(c))
|
|
7
|
+
return;
|
|
8
|
+
const counts = c;
|
|
9
|
+
if (Object.keys(counts).sort().join() !== 'documentation,source')
|
|
10
|
+
return;
|
|
11
|
+
const numbers = [v.successful_read_calls, v.unique_successful_paths, v.failed_read_calls, counts.documentation, counts.source];
|
|
12
|
+
if (numbers.some(n => !Number.isSafeInteger(n) || Number(n) < 0 || Number(n) > 1_000_000)
|
|
13
|
+
|| Number(v.unique_successful_paths) > 1024 || Number(v.unique_successful_paths) > Number(v.successful_read_calls)
|
|
14
|
+
|| Number(counts.documentation) + Number(counts.source) !== v.successful_read_calls)
|
|
15
|
+
return;
|
|
16
|
+
return structuredClone(value);
|
|
17
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
2
|
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
import { LOCAL_TOOL_NAMES } from './local-tool-policy.js';
|
|
4
5
|
const discoveryScopes = new WeakMap();
|
|
5
6
|
export function localDiscoveryScopeMatches(scope, endpoint, token) {
|
|
@@ -10,6 +11,10 @@ export function localDiscoveryScopeMatches(scope, endpoint, token) {
|
|
|
10
11
|
}
|
|
11
12
|
// Bounded diagnostic metadata; possession grants neither discovery nor tool authority.
|
|
12
13
|
const endpointObservations = new Map();
|
|
14
|
+
export function localEndpointReadObservations(url, token) {
|
|
15
|
+
const record = endpointObservations.get(url);
|
|
16
|
+
return record?.credentialHash === createHash('sha256').update(token).digest('hex') ? record.reads() : undefined;
|
|
17
|
+
}
|
|
13
18
|
export function localEndpointObservations(url, token) {
|
|
14
19
|
const record = endpointObservations.get(url);
|
|
15
20
|
return record?.credentialHash === createHash('sha256').update(token).digest('hex') ? [...record.values] : [];
|
|
@@ -28,6 +33,32 @@ export function localToolInventory() {
|
|
|
28
33
|
/** Job-only JSON MCP endpoint; intentionally has no Gateway transport or discovery. */
|
|
29
34
|
export class LocalToolServer {
|
|
30
35
|
options;
|
|
36
|
+
readCounts = { version: 'local-read-observations-v1', successful_read_calls: 0, unique_successful_paths: 0, failed_read_calls: 0, classification_counts: { documentation: 0, source: 0 }, truncated: false };
|
|
37
|
+
readPathDigests = new Set();
|
|
38
|
+
/** Counts are server-authored observations, not model evidence or a policy attestation. */
|
|
39
|
+
readObservations() { return structuredClone(this.readCounts); }
|
|
40
|
+
recordRead(success, args) {
|
|
41
|
+
const count = success ? 'successful_read_calls' : 'failed_read_calls';
|
|
42
|
+
if (this.readCounts[count] >= 1_000_000) {
|
|
43
|
+
this.readCounts.truncated = true;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this.readCounts[count]++;
|
|
47
|
+
if (!success)
|
|
48
|
+
return;
|
|
49
|
+
const value = (args && typeof args === 'object' && !Array.isArray(args) ? args.path : undefined);
|
|
50
|
+
const file = path.posix.normalize(typeof value === 'string' ? value : '');
|
|
51
|
+
const category = file.startsWith('docs/') ? 'documentation' : 'source';
|
|
52
|
+
this.readCounts.classification_counts[category]++;
|
|
53
|
+
const digest = createHash('sha256').update(file).digest('hex');
|
|
54
|
+
if (!this.readPathDigests.has(digest)) {
|
|
55
|
+
if (this.readPathDigests.size < 1024)
|
|
56
|
+
this.readPathDigests.add(digest);
|
|
57
|
+
else
|
|
58
|
+
this.readCounts.truncated = true;
|
|
59
|
+
}
|
|
60
|
+
this.readCounts.unique_successful_paths = this.readPathDigests.size;
|
|
61
|
+
}
|
|
31
62
|
observations = new Set();
|
|
32
63
|
server = null;
|
|
33
64
|
revoked = false;
|
|
@@ -174,6 +205,8 @@ export class LocalToolServer {
|
|
|
174
205
|
return;
|
|
175
206
|
}
|
|
176
207
|
send({ content: [{ type: 'text', text: JSON.stringify(result) }] });
|
|
208
|
+
if (params.name === 'local_read')
|
|
209
|
+
this.recordRead(true, params.arguments);
|
|
177
210
|
}
|
|
178
211
|
catch (error) {
|
|
179
212
|
if (this.token !== token || !this.allowed()) {
|
|
@@ -182,6 +215,8 @@ export class LocalToolServer {
|
|
|
182
215
|
}
|
|
183
216
|
// Only stable policy error identifiers cross this boundary, never raw paths/stderr.
|
|
184
217
|
const code = error instanceof Error ? error.message.match(/^(DENIED_[A-Z_]+|LOCAL_SANDBOX_UNAVAILABLE)/)?.[0] : undefined;
|
|
218
|
+
if (params.name === 'local_read')
|
|
219
|
+
this.recordRead(false, params.arguments);
|
|
185
220
|
send({ isError: true, content: [{ type: 'text', text: code ?? 'LOCAL_TOOL_FAILED' }] });
|
|
186
221
|
}
|
|
187
222
|
}
|
|
@@ -203,7 +238,7 @@ export class LocalToolServer {
|
|
|
203
238
|
if (!address || typeof address === 'string')
|
|
204
239
|
throw new Error('Failed to bind local tool server');
|
|
205
240
|
this.url = `http://127.0.0.1:${address.port}/mcp`;
|
|
206
|
-
endpointObservations.set(this.url, { credentialHash: createHash('sha256').update(this.token).digest('hex'), values: this.observations });
|
|
241
|
+
endpointObservations.set(this.url, { credentialHash: createHash('sha256').update(this.token).digest('hex'), values: this.observations, reads: () => this.readObservations() });
|
|
207
242
|
discoveryScopes.set(this.discoveryScope, { endpoint: this.url, credentialHash: createHash('sha256').update(this.token).digest('hex') });
|
|
208
243
|
return { url: this.url, token: this.token };
|
|
209
244
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
3
|
+
import { lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { regularFileStamp } from './stable-file-read.js';
|
|
6
|
+
import { validPolicyObserverPin } from '../adapters/codex/prelaunch-policy.js';
|
|
7
|
+
const denied = () => Error('CODEX_POLICY_INVENTORY_REJECTED');
|
|
8
|
+
export const policyObserverPath = (pin) => `/Library/PrivilegedHelperTools/com.mtec.ai-gateway.policy-observer.${pin.sha256}`;
|
|
9
|
+
/** Only compiled reviewed registrations select this executable, never task/env input. */
|
|
10
|
+
export function verifyPolicyObserver(pin) {
|
|
11
|
+
if (!validPolicyObserverPin(pin))
|
|
12
|
+
throw denied();
|
|
13
|
+
const binary = policyObserverPath(pin);
|
|
14
|
+
if (realpathSync(binary) !== binary)
|
|
15
|
+
throw denied();
|
|
16
|
+
const file = lstatSync(binary);
|
|
17
|
+
if (!file.isFile() || file.isSymbolicLink() || file.uid !== 0 || (file.mode & 0o022) !== 0 || (file.mode & 0o111) === 0)
|
|
18
|
+
throw denied();
|
|
19
|
+
for (let dir = path.dirname(binary);; dir = path.dirname(dir)) {
|
|
20
|
+
const s = lstatSync(dir);
|
|
21
|
+
if (!s.isDirectory() || s.isSymbolicLink() || s.uid !== 0 || (s.mode & 0o022) !== 0)
|
|
22
|
+
throw denied();
|
|
23
|
+
if (dir === path.dirname(dir))
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
const before = regularFileStamp(binary);
|
|
27
|
+
if (createHash('sha256').update(readFileSync(binary)).digest('hex') !== pin.sha256 || regularFileStamp(binary) !== before)
|
|
28
|
+
throw denied();
|
|
29
|
+
}
|
|
30
|
+
const query = (binary, args, request, signal) => new Promise((resolve, reject) => {
|
|
31
|
+
const child = execFile(binary, [...args], { encoding: 'utf8', timeout: args[0] === '--ready' ? 5000 : 70_000, maxBuffer: 8192, signal, env: { PATH: '/usr/bin:/bin', LANG: 'C', LC_ALL: 'C' } }, (error, stdout, stderr) => { if (error || stderr || !stdout)
|
|
32
|
+
return reject(denied()); resolve(stdout); });
|
|
33
|
+
child.stdin?.on('error', () => reject(denied()));
|
|
34
|
+
child.stdin?.end(request);
|
|
35
|
+
});
|
|
36
|
+
/** Single-use asynchronous IPC; the native client verifies root peer credentials.
|
|
37
|
+
* Injection seams are trusted in-process test code, not configuration loaders. */
|
|
38
|
+
export function createPolicyInventoryClient(original, deps) {
|
|
39
|
+
if (!validPolicyObserverPin(original))
|
|
40
|
+
throw denied();
|
|
41
|
+
const pin = Object.freeze({ ...original });
|
|
42
|
+
let used = false;
|
|
43
|
+
const perform = async (operation, signal) => {
|
|
44
|
+
if (used)
|
|
45
|
+
throw denied();
|
|
46
|
+
used = true;
|
|
47
|
+
try {
|
|
48
|
+
if (signal?.aborted)
|
|
49
|
+
throw denied();
|
|
50
|
+
const verify = deps?.verify ?? verifyPolicyObserver, now = deps?.now ?? Date.now;
|
|
51
|
+
verify(pin);
|
|
52
|
+
const startedAt = now(), nonce = (deps?.nonce ?? (() => randomBytes(32).toString('hex')))();
|
|
53
|
+
if (!Number.isSafeInteger(startedAt) || startedAt < 0 || !/^[a-f0-9]{64}$/.test(nonce))
|
|
54
|
+
throw denied();
|
|
55
|
+
const raw = await (deps?.run ?? query)(policyObserverPath(pin), [operation === 'ready' ? '--ready' : '--query'], JSON.stringify({ version: 1, operation, nonce }), signal);
|
|
56
|
+
if (signal?.aborted || typeof raw !== 'string' || Buffer.byteLength(raw) > 8192)
|
|
57
|
+
throw denied();
|
|
58
|
+
verify(pin);
|
|
59
|
+
const at = now(), r = JSON.parse(raw), keys = operation === 'ready' ? 'nonce,observerSha256,operation,status,version' : 'complete,empty,inventoryFingerprint,nonce,observedAt,observerSha256,operation,status,version';
|
|
60
|
+
if (!r || typeof r !== 'object' || Array.isArray(r) || Object.keys(r).sort().join(',') !== keys || r.version !== 1 || r.operation !== operation || r.nonce !== nonce || r.observerSha256 !== pin.sha256 || !Number.isSafeInteger(at) || at < startedAt)
|
|
61
|
+
throw denied();
|
|
62
|
+
if (operation === 'ready') {
|
|
63
|
+
if (r.status !== 'ready')
|
|
64
|
+
throw denied();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (r.status !== 'completed' || r.complete !== true || r.empty !== true || !Number.isSafeInteger(r.observedAt) || r.observedAt < startedAt || r.observedAt > at || at - r.observedAt > 60_000 || typeof r.inventoryFingerprint !== 'string' || !/^[a-f0-9]{64}$/.test(r.inventoryFingerprint))
|
|
68
|
+
throw denied();
|
|
69
|
+
return Object.freeze({ version: 1, exit: 0, euid: 0, scope: 'machine_and_current_user', complete: true, empty: true, observedAt: r.observedAt, inventoryFingerprint: r.inventoryFingerprint });
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw denied();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
return Object.freeze({ async checkReady(signal) { await perform('ready', signal); }, async collectOnce(signal) { const r = await perform('inventory', signal); if (!r)
|
|
76
|
+
throw denied(); return r; } });
|
|
77
|
+
}
|
package/dist/src/state-store.js
CHANGED
|
@@ -20,9 +20,22 @@ export class FileDaemonStateStore {
|
|
|
20
20
|
async save(state) {
|
|
21
21
|
mkdirSync(this.root, { recursive: true, mode: 0o700 });
|
|
22
22
|
const temporary = `${this.filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
23
|
-
await fs.
|
|
23
|
+
const file = await fs.open(temporary, 'wx', 0o600);
|
|
24
|
+
try {
|
|
25
|
+
await file.writeFile(JSON.stringify(state, null, 2));
|
|
26
|
+
await file.sync();
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
await file.close();
|
|
30
|
+
}
|
|
24
31
|
await fs.rename(temporary, this.filePath);
|
|
25
|
-
await fs.
|
|
32
|
+
const directory = await fs.open(this.root, 'r');
|
|
33
|
+
try {
|
|
34
|
+
await directory.sync();
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await directory.close();
|
|
38
|
+
}
|
|
26
39
|
}
|
|
27
40
|
async clear() { await fs.rm(this.filePath, { force: true }); }
|
|
28
41
|
}
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"fixture_class": "installed_observer_no_model_fixture",
|
|
3
|
+
"work_item_id": "bcb9164a-d4f8-4caa-8f61-5eeb7b8a1cc0",
|
|
4
|
+
"worklog_id": "dbf85621-b9ed-4843-8eeb-cc648ab758e1",
|
|
5
|
+
"checkpoint_id": "2748b184-39c2-412f-b542-d86fee2c13d8",
|
|
6
|
+
"observer_sha256": "2a07812cd47782826a359b749096b681a6ee49e91f990d48abdc60f2d46e4c9c",
|
|
7
|
+
"observer_protocol": "policy-inventory-v1",
|
|
8
|
+
"plist_sha256": "1fc069a257349b49a8dabd8c18defc12e9a94b14902ffa129c3b4438441c455d",
|
|
9
|
+
"employee_uid": 501,
|
|
10
|
+
"ready_requests": 1,
|
|
11
|
+
"machine_inventory_queries": 1,
|
|
12
|
+
"machine_complete": true,
|
|
13
|
+
"machine_empty": true,
|
|
14
|
+
"current_user_forced_config": false,
|
|
15
|
+
"current_user_forced_requirements": false,
|
|
16
|
+
"owned_query_children_remaining": 0,
|
|
17
|
+
"existing_employee_daemon_unloaded": true,
|
|
18
|
+
"actual_codex_daemon_model_acceptance": false,
|
|
19
|
+
"different_uid_and_cancellation_coverage": "synthetic_only",
|
|
20
|
+
"continuous_mdm_monitoring": false,
|
|
21
|
+
"inherited_manual_fixture_gate": "3bb8b2ad50a44cdea5a2a3f388f8009b51d8c60d46230fb3d0b7fed0b9f7452d"
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mtech-ai-local-runner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"dist/src",
|
|
11
11
|
"README.md",
|
|
12
12
|
"LICENSE",
|
|
13
|
-
"native/bin/codex-observe-darwin-arm64"
|
|
13
|
+
"native/bin/codex-observe-darwin-arm64",
|
|
14
|
+
"native/bin/policy-observer-darwin-arm64",
|
|
15
|
+
"native/policy-observer-installed-evidence.json"
|
|
14
16
|
],
|
|
15
17
|
"type": "module",
|
|
16
18
|
"engines": {
|