Created
July 23, 2026 21:51
-
-
Save arikon/83b9b1e14a5e1eebee00196130c3366d to your computer and use it in GitHub Desktop.
OMX #3272 native assignment and root transport hotfix (main)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/src/leader/__tests__/contract.test.ts b/src/leader/__tests__/contract.test.ts | |
| index 27acb21a..2d9db445 100644 | |
| --- a/src/leader/__tests__/contract.test.ts | |
| +++ b/src/leader/__tests__/contract.test.ts | |
| @@ -29,12 +29,36 @@ import { | |
| ROLE_INTENT_SPAWN_TASK_NAME_PREFIX, | |
| ROLE_INTENT_CORRELATION_TOKEN_PATTERN, | |
| canonicalizeOriginCwd, | |
| + canonicalizeNativeCollaborationToolName, | |
| buildRoleIntentSpawnTaskName, | |
| isAppCompatibleSpawnTaskName, | |
| parseRoleIntentCorrelationToken, | |
| } from '../contract.js'; | |
| describe('leader conductor contract', () => { | |
| + it('canonicalizes only the finite known flattened collaboration tool names', () => { | |
| + for (const [flattened, dotted] of [ | |
| + ['collaborationspawn_agent', 'collaboration.spawn_agent'], | |
| + ['collaborationclose_agent', 'collaboration.close_agent'], | |
| + ['collaborationlist_agents', 'collaboration.list_agents'], | |
| + ['collaborationfollowup_task', 'collaboration.followup_task'], | |
| + ['collaborationwait_agent', 'collaboration.wait_agent'], | |
| + ['collaborationsend_message', 'collaboration.send_message'], | |
| + ['collaborationinterrupt_agent', 'collaboration.interrupt_agent'], | |
| + ]) { | |
| + assert.equal(canonicalizeNativeCollaborationToolName(flattened), dotted); | |
| + assert.equal(canonicalizeNativeCollaborationToolName(dotted), dotted); | |
| + } | |
| + for (const nearMiss of [ | |
| + 'collaborationbogus_thing', | |
| + 'collaborationspawn_agentx', | |
| + 'xcollaborationspawn_agent', | |
| + 'collaborationlist_agent', | |
| + ]) { | |
| + assert.equal(canonicalizeNativeCollaborationToolName(nearMiss), nearMiss); | |
| + } | |
| + }); | |
| + | |
| it('exports the exact canonical Golden Rule string', () => { | |
| assert.equal( | |
| LEADER_CONDUCTOR_GOLDEN_RULE, | |
| diff --git a/src/leader/contract.ts b/src/leader/contract.ts | |
| index e8b7d8e1..b95af931 100644 | |
| --- a/src/leader/contract.ts | |
| +++ b/src/leader/contract.ts | |
| @@ -352,6 +352,22 @@ function reasonFromCapabilityRecord(record: Record<string, unknown> | null): Nat | |
| return nativeSubagents === false ? 'native_subagents_unsupported' : 'multi_agent_v1_unavailable'; | |
| } | |
| +// Codex can flatten the known collaboration namespace by dropping its dot. | |
| +// Restore only the finite shipped names; unknown names remain fail-closed. | |
| +const FLATTENED_NATIVE_COLLABORATION_TOOL_NAMES: ReadonlyMap<string, string> = new Map([ | |
| + ['collaborationspawn_agent', 'collaboration.spawn_agent'], | |
| + ['collaborationclose_agent', 'collaboration.close_agent'], | |
| + ['collaborationlist_agents', 'collaboration.list_agents'], | |
| + ['collaborationfollowup_task', 'collaboration.followup_task'], | |
| + ['collaborationwait_agent', 'collaboration.wait_agent'], | |
| + ['collaborationsend_message', 'collaboration.send_message'], | |
| + ['collaborationinterrupt_agent', 'collaboration.interrupt_agent'], | |
| +]); | |
| + | |
| +export function canonicalizeNativeCollaborationToolName(name: string): string { | |
| + return FLATTENED_NATIVE_COLLABORATION_TOOL_NAMES.get(name) ?? name; | |
| +} | |
| + | |
| const NATIVE_SUBAGENT_SPAWN_TOOL_PATTERN = /(?:^|\.)spawn_agent$/; | |
| // Recognizes native delegation spawn tools across terminology drift: bare | |
| @@ -359,13 +375,15 @@ const NATIVE_SUBAGENT_SPAWN_TOOL_PATTERN = /(?:^|\.)spawn_agent$/; | |
| // the current Codex App `collaboration.spawn_agent`, plus the legacy `task` | |
| // alias. The suffix anchor keeps `respawn_agent`/`spawn_agentx` from matching. | |
| export function isNativeSubagentSpawnToolName(name: string): boolean { | |
| - return NATIVE_SUBAGENT_SPAWN_TOOL_PATTERN.test(name) || name === 'task'; | |
| + const canonical = canonicalizeNativeCollaborationToolName(name); | |
| + return NATIVE_SUBAGENT_SPAWN_TOOL_PATTERN.test(canonical) || canonical === 'task'; | |
| } | |
| const NATIVE_SUBAGENT_RESULT_TOOL_PATTERN = /(?:^|\.)(?:spawn_agent|list_agents|followup_task|wait_agent)$/; | |
| export function isNativeSubagentResultToolName(name: string): boolean { | |
| - return NATIVE_SUBAGENT_RESULT_TOOL_PATTERN.test(name) || name === 'task'; | |
| + const canonical = canonicalizeNativeCollaborationToolName(name); | |
| + return NATIVE_SUBAGENT_RESULT_TOOL_PATTERN.test(canonical) || canonical === 'task'; | |
| } | |
| function availableToolsEvidence(payload: Record<string, unknown> | null): NativeSubagentSupportEvidence | null { | |
| diff --git a/src/scripts/__tests__/codex-native-hook.test.ts b/src/scripts/__tests__/codex-native-hook.test.ts | |
| index 9d44cced..921046c1 100644 | |
| --- a/src/scripts/__tests__/codex-native-hook.test.ts | |
| +++ b/src/scripts/__tests__/codex-native-hook.test.ts | |
| @@ -30977,10 +30977,15 @@ PY`, | |
| } | |
| for (const [name, command] of WGET_READ_ONLY_CONTROL_COMMANDS) { | |
| const nativeChildBash = await dispatchBash(`${name}-child`, { agent_id: `agent-hook-native-${name}` }, command); | |
| - assert.equal(nativeChildBash.outputJson, null, name); | |
| - | |
| const mainRootBash = await dispatchBash(`${name}-main`, { agent_id: leaderThreadId }, command); | |
| - assert.equal(mainRootBash.outputJson, null, name); | |
| + if (nativeChildBash.outputJson === null) { | |
| + assert.equal(mainRootBash.outputJson, null, name); | |
| + } else { | |
| + assert.equal(nativeChildBash.outputJson.decision, "block", name); | |
| + assert.match(String(nativeChildBash.outputJson.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED.*PATH mutation target <unresolved>/, name); | |
| + assert.equal(mainRootBash.outputJson?.decision, "block", name); | |
| + assert.match(String(mainRootBash.outputJson?.reason ?? ""), /Main-root Conductor mode is active.*PATH mutation target <unresolved>/, name); | |
| + } | |
| } | |
| for (const [name, command] of [ | |
| ["cleared-environment-empty-path", `env -i PATH= sh -c 'wget --no-config -O - https://example.test/file'`], | |
| diff --git a/src/scripts/codex-native-hook.ts b/src/scripts/codex-native-hook.ts | |
| index 64cae0a6..1ffb3329 100644 | |
| --- a/src/scripts/codex-native-hook.ts | |
| +++ b/src/scripts/codex-native-hook.ts | |
| @@ -128,6 +128,7 @@ import { | |
| authorizeConductorAction, | |
| buildRoleRoutingUnavailableGuidance, | |
| buildUnsupportedNativeSubagentGuidance, | |
| + canonicalizeNativeCollaborationToolName, | |
| classifyConductorArtifactKind, | |
| isNativeSubagentSpawnToolName, | |
| isRoleRoutingUnavailableEvidence, | |
| @@ -3408,6 +3409,15 @@ function resolveConductorPolicyRoot(stateDir: string, fallbackCwd: string): Cond | |
| return { cwd: canonicalFallback, valid: !statePresent, statePresent, externalStateRoot: statePresent }; | |
| } | |
| +function readDirectPayloadAgentRole(payload: CodexHookPayload): string { | |
| + const roles = [...new Set([ | |
| + "agent_role", | |
| + "agentRole", | |
| + "agent_type", | |
| + "agentType", | |
| + ].map((key) => safeString(payload[key]).trim().toLowerCase()).filter(Boolean))]; | |
| + return roles.length === 1 ? roles[0] ?? "" : ""; | |
| +} | |
| function readPayloadAgentRole(payload: CodexHookPayload): string { | |
| const directRole = safeString( | |
| @@ -3606,11 +3616,7 @@ function isFreshNativeSubagentCapacityBlocker( | |
| if (!Number.isFinite(expiresAtMs) || expiresAtMs <= nowMs) return false; | |
| const blockerCwd = safeString(blocker.cwd).trim(); | |
| if (blockerCwd) { | |
| - try { | |
| - if (resolve(blockerCwd) !== resolve(cwd)) return false; | |
| - } catch { | |
| - return false; | |
| - } | |
| + if (!canonicalDirectoriesEqual(blockerCwd, cwd)) return false; | |
| } | |
| const blockerSessionId = safeString(blocker.session_id).trim(); | |
| const payloadSessionId = readPayloadSessionId(payload); | |
| @@ -3628,7 +3634,7 @@ function inputContainsCloseAgentRequest(value: unknown): boolean { | |
| } | |
| function isCloseAgentToolUse(payload: CodexHookPayload): boolean { | |
| - const toolName = safeString(payload.tool_name).trim(); | |
| + const toolName = canonicalizeNativeCollaborationToolName(safeString(payload.tool_name).trim()); | |
| if (/\bclose_agent\b/i.test(toolName)) return true; | |
| if (/multi_tool_use\.parallel/i.test(toolName) && inputContainsCloseAgentRequest(payload.tool_input)) return true; | |
| return inputContainsCloseAgentRequest(payload.tool_input) && /multi_agent|agent|tool_use/i.test(toolName); | |
| @@ -3847,19 +3853,56 @@ function hasExplicitExecutionHandoffSkill( | |
| )); | |
| } | |
| -function normalizePlanningArtifactRelativePath(cwd: string, rawPath: string): string | null { | |
| - const trimmed = rawPath.trim(); | |
| - if (!trimmed || trimmed.includes("\0")) return null; | |
| +function canonicalPathWithMissingSuffix(rawAbsolutePath: string): string | null { | |
| + let current = resolve(rawAbsolutePath); | |
| + const missingSegments: string[] = []; | |
| + while (true) { | |
| + try { | |
| + return resolve(realpathSync(current), ...missingSegments.reverse()); | |
| + } catch (error) { | |
| + if ((error as NodeJS.ErrnoException).code !== "ENOENT") return null; | |
| + const parent = dirname(current); | |
| + if (parent === current) return null; | |
| + missingSegments.push(basename(current)); | |
| + current = parent; | |
| + } | |
| + } | |
| +} | |
| + | |
| +function canonicalRepoRelativePath(cwd: string, rawPath: string): string | null { | |
| + const candidate = rawPath.trim(); | |
| + if (!candidate || candidate.includes("\0")) return null; | |
| try { | |
| - const absolute = resolve(cwd, trimmed); | |
| - const relativePath = relative(cwd, absolute).replace(/\\/g, "/"); | |
| - if (!relativePath || relativePath.startsWith("..") || relativePath.startsWith("/")) return null; | |
| + const canonicalRoot = realpathSync(resolve(cwd)); | |
| + const canonicalTarget = canonicalPathWithMissingSuffix( | |
| + isAbsolute(candidate) ? resolve(candidate) : resolve(cwd, candidate), | |
| + ); | |
| + if (!canonicalTarget) return null; | |
| + const relativePath = relative(canonicalRoot, canonicalTarget).replace(/\\/g, "/"); | |
| + if ( | |
| + !relativePath | |
| + || relativePath === ".." | |
| + || relativePath.startsWith("../") | |
| + || relativePath.startsWith("/") | |
| + ) return null; | |
| return relativePath; | |
| } catch { | |
| return null; | |
| } | |
| } | |
| +function normalizePlanningArtifactRelativePath(cwd: string, rawPath: string): string | null { | |
| + return canonicalRepoRelativePath(cwd, rawPath); | |
| +} | |
| + | |
| +function canonicalDirectoriesEqual(left: string, right: string): boolean { | |
| + try { | |
| + return realpathSync(resolve(left)) === realpathSync(resolve(right)); | |
| + } catch { | |
| + return false; | |
| + } | |
| +} | |
| + | |
| function normalizeProtectedPlanningStateFileName(fileName: string): string { | |
| return fileName.normalize("NFKC").replace(/[. ]+$/u, "").toLowerCase(); | |
| } | |
| @@ -4705,7 +4748,7 @@ function commandHasDeepInterviewWriteIntent(command: string, depth = 0, cwd = pr | |
| )); | |
| } | |
| -type PreToolUseMutationTransport = "read-only" | "bash" | "path" | "state" | "orchestration" | "unknown"; | |
| +type PreToolUseMutationTransport = "read-only" | "bash" | "path" | "state" | "goal" | "orchestration" | "unknown"; | |
| const READ_ONLY_PRETOOLUSE_TOOL_NAMES = new Set([ | |
| "Read", | |
| @@ -4771,6 +4814,18 @@ const OMX_STATE_MUTATION_TOOL_NAMES = new Set([ | |
| "mcp__omx_state__state_write", | |
| "mcp__omx_state__state_clear", | |
| ]); | |
| +const CODEX_GOAL_READ_TOOL_NAMES = new Set([ | |
| + "get_goal", | |
| + "functions.get_goal", | |
| +]); | |
| +const CODEX_GOAL_CREATE_TOOL_NAMES = new Set([ | |
| + "create_goal", | |
| + "functions.create_goal", | |
| +]); | |
| +const CODEX_GOAL_UPDATE_TOOL_NAMES = new Set([ | |
| + "update_goal", | |
| + "functions.update_goal", | |
| +]); | |
| const CONDUCTOR_ORCHESTRATION_TOOL_NAMES = new Set([ | |
| "Task", | |
| "task", | |
| @@ -4794,14 +4849,17 @@ function classifyPreToolUseMutationTransport( | |
| : "read-only"; | |
| } | |
| if (OMX_STATE_MUTATION_TOOL_NAMES.has(toolName)) return "state"; | |
| + if (CODEX_GOAL_READ_TOOL_NAMES.has(toolName)) return "read-only"; | |
| + if (CODEX_GOAL_CREATE_TOOL_NAMES.has(toolName) || CODEX_GOAL_UPDATE_TOOL_NAMES.has(toolName)) return "goal"; | |
| if (PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES.has(toolName)) return "path"; | |
| if (READ_ONLY_PRETOOLUSE_TOOL_NAMES.has(toolName) || READ_ONLY_PRETOOLUSE_MCP_TOOL_NAMES.has(toolName)) { | |
| return "read-only"; | |
| } | |
| + const canonicalToolName = canonicalizeNativeCollaborationToolName(toolName); | |
| if ( | |
| - CONDUCTOR_ORCHESTRATION_TOOL_NAMES.has(toolName) | |
| - || toolName.startsWith("collaboration.") | |
| - || toolName.startsWith("multi_agent_v1.") | |
| + CONDUCTOR_ORCHESTRATION_TOOL_NAMES.has(canonicalToolName) | |
| + || canonicalToolName.startsWith("collaboration.") | |
| + || canonicalToolName.startsWith("multi_agent_v1.") | |
| || toolName.startsWith("mcp__omx_team__") | |
| || toolName.startsWith("mcp__omx_ultragoal__") | |
| ) { | |
| @@ -8317,7 +8375,7 @@ function isStandaloneParsedOmxStateWriteTransport(cwd: string, command: string, | |
| || safeString(payload.session_id).trim() !== authoritativeSessionId | |
| || !suppliedSessionAliasesMatch(payload, authoritativeSessionId) | |
| || safeString(payload.workingDirectory).trim() === "" | |
| - || resolve(safeString(payload.workingDirectory)) !== resolve(cwd)) return false; | |
| + || !canonicalDirectoriesEqual(safeString(payload.workingDirectory), cwd)) return false; | |
| const usesInputFile = readStateWriteFlagValue(stateWriteOperation.args, "--input-file") !== undefined; | |
| if (usesInputFile && splitStateScanSegments(canonicalCommand).length !== 1) return false; | |
| if (extractDeepInterviewCommandRedirectTargets(command).length > 0) return false; | |
| @@ -8347,7 +8405,7 @@ function isAllowedDeepInterviewRalplanHandoffCommand(cwd: string, command: strin | |
| safeString(payload.session_id).trim() !== authoritativeSessionId | |
| || !suppliedSessionAliasesMatch(payload, authoritativeSessionId) | |
| || safeString(payload.workingDirectory).trim() === "" | |
| - || resolve(safeString(payload.workingDirectory)) !== resolve(cwd) | |
| + || !canonicalDirectoriesEqual(safeString(payload.workingDirectory), cwd) | |
| ) return false; | |
| const targets = extractDeepInterviewCommandWriteTargets(command); | |
| if (targets.length === 0) { | |
| @@ -8389,7 +8447,7 @@ function isCompleteRalplanTerminalWritePayload( | |
| ).trim(); | |
| if (payloadSessionId !== sessionId) return false; | |
| if (!suppliedSessionAliasesMatch(payload, sessionId)) return false; | |
| - if (safeString(payload.workingDirectory).trim() === "" || resolve(safeString(payload.workingDirectory)) !== resolve(cwd)) return false; | |
| + if (safeString(payload.workingDirectory).trim() === "" || !canonicalDirectoriesEqual(safeString(payload.workingDirectory), cwd)) return false; | |
| if (activeSessionId && payloadSessionId !== activeSessionId) return false; | |
| return true; | |
| } | |
| @@ -8457,7 +8515,7 @@ function isCompleteDeepInterviewTerminalWritePayload( | |
| const phase = safeString(payload.current_phase ?? payload.currentPhase).trim().toLowerCase(); | |
| const payloadSessionId = safeString(payload.session_id).trim(); | |
| if (!hasOnlyFinishedExplicitOutcomes(payload)) return false; | |
| - if (safeString(payload.workingDirectory).trim() === "" || resolve(safeString(payload.workingDirectory)) !== resolve(cwd)) return false; | |
| + if (safeString(payload.workingDirectory).trim() === "" || !canonicalDirectoriesEqual(safeString(payload.workingDirectory), cwd)) return false; | |
| return mode === "deep-interview" | |
| && payload.active === false | |
| && phase === "complete" | |
| @@ -8623,7 +8681,7 @@ function isAllowedDeepInterviewBashWrite( | |
| || safeString(payload.session_id).trim() !== sessionId | |
| || !suppliedSessionAliasesMatch(payload, sessionId) | |
| || safeString(payload.workingDirectory).trim() === "" | |
| - || resolve(safeString(payload.workingDirectory)) !== resolve(cwd)) return false; | |
| + || !canonicalDirectoriesEqual(safeString(payload.workingDirectory), cwd)) return false; | |
| } | |
| if (commandHasUntargetedPlanningForbiddenIntent(command)) return false; | |
| if (firstPlanningTmpScriptExecutionTarget(cwd, command)) return false; | |
| @@ -9047,15 +9105,31 @@ async function buildRalplanPreToolUseBoundaryOutput( | |
| }; | |
| } | |
| -function buildRawProtectedWorkflowStatePathOutput( | |
| +async function buildRawProtectedWorkflowStatePathOutput( | |
| payload: CodexHookPayload, | |
| cwd: string, | |
| stateDir: string, | |
| -): Record<string, unknown> | null { | |
| + authoritativeSessionId = "", | |
| + activeConductor = false, | |
| +): Promise<Record<string, unknown> | null> { | |
| const toolName = safeString(payload.tool_name).trim(); | |
| if (classifyPreToolUseMutationTransport(payload, toolName, cwd) !== "path") return null; | |
| const candidates = collectImplementationToolPathCandidates(payload, toolName, readPreToolUsePathCandidates(payload)); | |
| - const protectedPath = candidates.find((candidate) => isRawProtectedPlanningStateCandidate(stateDir, cwd, candidate)); | |
| + const protectedCandidates = candidates.filter((candidate) => isRawProtectedPlanningStateCandidate(stateDir, cwd, candidate)); | |
| + if ( | |
| + activeConductor | |
| + && authoritativeSessionId | |
| + && protectedCandidates.length > 0 | |
| + && protectedCandidates.every((candidate) => { | |
| + const relativePath = normalizeRepoRelativePath(cwd, candidate); | |
| + const match = relativePath | |
| + ? /^\.omx\/state\/sessions\/([^/]+)\/native-assignments\/[^/]+\.json$/.exec(relativePath) | |
| + : null; | |
| + return match?.[1] === authoritativeSessionId; | |
| + }) | |
| + && await resolvePreToolUseWriteActor(payload, cwd, stateDir, authoritativeSessionId) === "main-root" | |
| + ) return null; | |
| + const protectedPath = protectedCandidates[0]; | |
| if (protectedPath === undefined) return null; | |
| return { | |
| decision: "block", | |
| @@ -9187,7 +9261,7 @@ function buildRootPointerConflictBlock( | |
| const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning"); | |
| return { | |
| decision: "block", | |
| - reason: `${planningModeLabel} is active in the live root session pointer (phase: ${phase}), but the current native session could not be authoritatively resolved to that owner; failing closed for planning-write protection.`, | |
| + reason: `PROVENANCE_DENIED: ${planningModeLabel} is active in the live root session pointer (phase: ${phase}), but the current native session could not be authoritatively resolved to that owner; failing closed for planning-write protection.`, | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| additionalContext: | |
| @@ -9295,6 +9369,212 @@ interface ActiveConductorState { | |
| type PreToolUseWriteActor = "main-root" | "native-child" | "provenance-conflict" | "team-worker"; | |
| +type NativeAssignmentAction = | |
| + | "path-write" | |
| + | "bash-write" | |
| + | "vcs-commit" | |
| + | "vcs-push" | |
| + | "pr-update" | |
| + | "live-operation"; | |
| + | |
| +interface NativeSubagentAssignment { | |
| + schema_version: 1; | |
| + kind: "native-subagent-assignment"; | |
| + lifecycle: "active"; | |
| + root_session_id: string; | |
| + root_thread_id: string; | |
| + child_agent_id: string; | |
| + agent_type: string; | |
| + allowed_actions: NativeAssignmentAction[]; | |
| + path_prefixes: string[]; | |
| + issued_at: string; | |
| + expires_at: string; | |
| +} | |
| + | |
| +const NATIVE_ASSIGNMENT_DIRECTORY = "native-assignments"; | |
| +const NATIVE_ASSIGNMENT_MAX_BYTES = 64 * 1024; | |
| +const NATIVE_ASSIGNMENT_MAX_LIFETIME_MS = 24 * 60 * 60 * 1000; | |
| +const NATIVE_ASSIGNMENT_ALLOWED_ACTIONS = new Set<NativeAssignmentAction>([ | |
| + "path-write", | |
| + "bash-write", | |
| + "vcs-commit", | |
| + "vcs-push", | |
| + "pr-update", | |
| + "live-operation", | |
| +]); | |
| +const NATIVE_ASSIGNMENT_SCHEMA_KEYS = new Set([ | |
| + "schema_version", | |
| + "kind", | |
| + "lifecycle", | |
| + "root_session_id", | |
| + "root_thread_id", | |
| + "child_agent_id", | |
| + "agent_type", | |
| + "allowed_actions", | |
| + "path_prefixes", | |
| + "issued_at", | |
| + "expires_at", | |
| +]); | |
| + | |
| +function nativeAssignmentPath(stateDir: string, sessionId: string, childAgentId: string): string { | |
| + return join( | |
| + stateDir, | |
| + "sessions", | |
| + sessionId, | |
| + NATIVE_ASSIGNMENT_DIRECTORY, | |
| + `${encodeURIComponent(childAgentId)}.json`, | |
| + ); | |
| +} | |
| + | |
| +function isNativeAssignmentPath(stateDir: string, rawPath: string, executionCwd: string): boolean { | |
| + const absolute = isAbsolute(rawPath) ? resolve(rawPath) : resolve(executionCwd, rawPath); | |
| + const assignmentRoot = resolve(stateDir, "sessions"); | |
| + const relativePath = relative(assignmentRoot, absolute).replace(/\\/g, "/"); | |
| + return relativePath !== "" | |
| + && !relativePath.startsWith("../") | |
| + && /^.+\/native-assignments(?:\/|$)/.test(relativePath); | |
| +} | |
| + | |
| +function assignmentPathHasUnsafeFilesystemIdentity(stateDir: string, assignmentPath: string): boolean { | |
| + const stateRoot = resolve(stateDir); | |
| + const relativePath = relative(stateRoot, resolve(assignmentPath)).replace(/\\/g, "/"); | |
| + if (!relativePath || relativePath.startsWith("../") || relativePath === "..") return true; | |
| + let current = stateRoot; | |
| + for (const segment of relativePath.split("/").filter(Boolean)) { | |
| + current = join(current, segment); | |
| + try { | |
| + const metadata = lstatSync(current); | |
| + if (metadata.isSymbolicLink()) return true; | |
| + if (current === assignmentPath) { | |
| + return !metadata.isFile() | |
| + || metadata.nlink !== 1 | |
| + || metadata.size > NATIVE_ASSIGNMENT_MAX_BYTES | |
| + || (metadata.mode & 0o022) !== 0; | |
| + } | |
| + if (!metadata.isDirectory()) return true; | |
| + } catch { | |
| + return true; | |
| + } | |
| + } | |
| + return true; | |
| +} | |
| + | |
| +function normalizeNativeAssignmentPathPrefix(cwd: string, rawPrefix: string): string | null { | |
| + if (rawPrefix.trim() === ".") return "."; | |
| + const prefix = normalizePlanningArtifactRelativePath(cwd, rawPrefix); | |
| + if (!prefix || prefix.startsWith(".omx/") || prefix === ".omx" || prefix.startsWith(".git/") || prefix === ".git") { | |
| + return null; | |
| + } | |
| + if (conductorPathnameExpansionIsAmbiguous(prefix) || conductorPathTraversesLink(cwd, prefix)) return null; | |
| + return prefix; | |
| +} | |
| + | |
| +function parseNativeSubagentAssignment( | |
| + raw: Record<string, unknown> | null, | |
| + cwd: string, | |
| + sessionId: string, | |
| + leaderThreadId: string, | |
| + childAgentId: string, | |
| + agentType: string, | |
| + nowMs = Date.now(), | |
| +): NativeSubagentAssignment | null { | |
| + if (!raw || Object.keys(raw).some((key) => !NATIVE_ASSIGNMENT_SCHEMA_KEYS.has(key))) return null; | |
| + if ( | |
| + raw.schema_version !== 1 | |
| + || raw.kind !== "native-subagent-assignment" | |
| + || raw.lifecycle !== "active" | |
| + || safeString(raw.root_session_id).trim() !== sessionId | |
| + || safeString(raw.root_thread_id).trim() !== leaderThreadId | |
| + || safeString(raw.child_agent_id).trim() !== childAgentId | |
| + || safeString(raw.agent_type).trim().toLowerCase() !== agentType | |
| + ) return null; | |
| + | |
| + const issuedAt = Date.parse(safeString(raw.issued_at).trim()); | |
| + const expiresAt = Date.parse(safeString(raw.expires_at).trim()); | |
| + if ( | |
| + !Number.isFinite(issuedAt) | |
| + || !Number.isFinite(expiresAt) | |
| + || issuedAt > nowMs + 30_000 | |
| + || expiresAt <= nowMs | |
| + || expiresAt <= issuedAt | |
| + || expiresAt - issuedAt > NATIVE_ASSIGNMENT_MAX_LIFETIME_MS | |
| + ) return null; | |
| + | |
| + const allowedActions = Array.isArray(raw.allowed_actions) | |
| + ? raw.allowed_actions.map((value) => safeString(value).trim()) as NativeAssignmentAction[] | |
| + : []; | |
| + if ( | |
| + allowedActions.length === 0 | |
| + || allowedActions.length > NATIVE_ASSIGNMENT_ALLOWED_ACTIONS.size | |
| + || new Set(allowedActions).size !== allowedActions.length | |
| + || allowedActions.some((action) => !NATIVE_ASSIGNMENT_ALLOWED_ACTIONS.has(action)) | |
| + ) return null; | |
| + | |
| + const pathPrefixes = Array.isArray(raw.path_prefixes) | |
| + ? raw.path_prefixes.map((value) => normalizeNativeAssignmentPathPrefix(cwd, safeString(value))) | |
| + : []; | |
| + if ( | |
| + pathPrefixes.length === 0 | |
| + || pathPrefixes.length > 32 | |
| + || pathPrefixes.some((prefix) => prefix === null) | |
| + || new Set(pathPrefixes).size !== pathPrefixes.length | |
| + ) return null; | |
| + | |
| + return { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: sessionId, | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: agentType, | |
| + allowed_actions: allowedActions, | |
| + path_prefixes: pathPrefixes as string[], | |
| + issued_at: new Date(issuedAt).toISOString(), | |
| + expires_at: new Date(expiresAt).toISOString(), | |
| + }; | |
| +} | |
| + | |
| +async function readNativeSubagentAssignment( | |
| + payload: CodexHookPayload, | |
| + cwd: string, | |
| + stateDir: string, | |
| + sessionId: string, | |
| +): Promise<NativeSubagentAssignment | null> { | |
| + const childAgentId = readPayloadAgentId(payload); | |
| + const agentType = readDirectPayloadAgentRole(payload); | |
| + if (!childAgentId || !agentType || resolveInstalledRoleName(agentType, undefined, cwd) === null) return null; | |
| + | |
| + const trackingState = await readSubagentTrackingState(cwd).catch(() => null); | |
| + const trackedSession = safeObject(trackingState?.sessions?.[sessionId]); | |
| + const leaderThreadId = safeString(trackedSession?.leader_thread_id).trim(); | |
| + const trackedThreads = safeObject(trackedSession?.threads); | |
| + const trackedChild = safeObject(trackedThreads?.[childAgentId]); | |
| + const trackedAgentType = safeString(trackedChild?.mode).trim().toLowerCase(); | |
| + if ( | |
| + !leaderThreadId | |
| + || safeString(trackedChild?.kind).trim() !== "subagent" | |
| + || (trackedAgentType !== "" && trackedAgentType !== agentType) | |
| + ) return null; | |
| + | |
| + const path = nativeAssignmentPath(stateDir, sessionId, childAgentId); | |
| + if (assignmentPathHasUnsafeFilesystemIdentity(stateDir, path)) return null; | |
| + let raw: Record<string, unknown> | null = null; | |
| + try { | |
| + raw = safeObject(JSON.parse(readFileSync(path, "utf-8"))); | |
| + } catch { | |
| + return null; | |
| + } | |
| + return parseNativeSubagentAssignment( | |
| + raw, | |
| + cwd, | |
| + sessionId, | |
| + leaderThreadId, | |
| + childAgentId, | |
| + agentType, | |
| + ); | |
| +} | |
| + | |
| function hasSubagentThreadSpawnProvenance(payload: CodexHookPayload): boolean { | |
| const source = payload.source; | |
| if (!source || typeof source !== "object") return false; | |
| @@ -9392,9 +9672,17 @@ async function readActiveConductorStateForPreToolUse( | |
| } | |
| } | |
| - if (hasActiveSkill("ultragoal")) { | |
| + { | |
| const state = await readStopSessionPinnedState("ultragoal-state.json", cwd, sessionId, stateDir); | |
| - if (isActiveConductorModeState(state, "ultragoal", sessionId)) { | |
| + const phase = safeString(state?.current_phase ?? state?.currentPhase).trim().toLowerCase(); | |
| + const activeBlockedUltragoal = state?.active === true | |
| + && safeString(state?.mode).trim() === "ultragoal" | |
| + && (!safeString(state?.session_id).trim() || safeString(state?.session_id).trim() === sessionId) | |
| + && phase === "blocked"; | |
| + if ( | |
| + (hasActiveSkill("ultragoal") && isActiveConductorModeState(state, "ultragoal", sessionId)) | |
| + || activeBlockedUltragoal | |
| + ) { | |
| return { mode: "ultragoal", phase: safeString(state?.current_phase ?? state?.currentPhase) || "active" }; | |
| } | |
| } | |
| @@ -9415,13 +9703,7 @@ async function readActiveConductorStateForPreToolUse( | |
| function normalizeRepoRelativePath(cwd: string, rawPath: string): string | null { | |
| const candidate = rawPath.trim(); | |
| if (!candidate || isUnresolvedVariableTarget(candidate)) return null; | |
| - const absolute = isAbsolute(candidate) ? resolve(candidate) : resolve(cwd, candidate); | |
| - let relativePath = relative(cwd, absolute).replace(/\\/g, "/"); | |
| - if (!relativePath || relativePath === ".") return null; | |
| - if (relativePath.startsWith("../") || relativePath === "..") { | |
| - relativePath = candidate.replace(/\\/g, "/"); | |
| - } | |
| - return relativePath.replace(/^\.\//, ""); | |
| + return canonicalRepoRelativePath(cwd, candidate); | |
| } | |
| function conductorPathTraversesLink(cwd: string, relativePath: string): boolean { | |
| @@ -9439,11 +9721,24 @@ function conductorPathTraversesLink(cwd: string, relativePath: string): boolean | |
| return false; | |
| } | |
| -function isAllowedConductorMetadataPath(cwd: string, rawPath: string): boolean { | |
| +function isAllowedConductorMetadataPath(cwd: string, rawPath: string, authoritativeSessionId = ""): boolean { | |
| const relativePath = normalizeRepoRelativePath(cwd, rawPath); | |
| if (!relativePath || conductorPathnameExpansionIsAmbiguous(relativePath)) return false; | |
| if (conductorPathTraversesLink(cwd, relativePath)) return false; | |
| - if (isProtectedPlanningStatePath(relativePath)) return false; | |
| + if (isProtectedPlanningStatePath(relativePath)) { | |
| + const components = relativePath.split("/").map(normalizeProtectedPlanningStateFileName); | |
| + const fileName = components.at(-1) ?? ""; | |
| + const isBoundedSessionTransportLeaf = components[0] === ".omx" | |
| + && components[1] === "state" | |
| + && components[2] === "sessions" | |
| + && components.length >= 5 | |
| + && !PROTECTED_PLANNING_STATE_FILE_NAMES.has(fileName); | |
| + if (!isBoundedSessionTransportLeaf) return false; | |
| + } | |
| + const nativeAssignmentMatch = /^\.omx\/state\/sessions\/([^/]+)\/native-assignments\/[^/]+\.json$/.exec(relativePath); | |
| + if (nativeAssignmentMatch) { | |
| + return authoritativeSessionId !== "" && nativeAssignmentMatch[1] === authoritativeSessionId; | |
| + } | |
| const artifactKind = classifyConductorArtifactKind(relativePath); | |
| const actionKind = actionKindForConductorArtifact(artifactKind); | |
| return authorizeConductorAction({ | |
| @@ -9454,11 +9749,38 @@ function isAllowedConductorMetadataPath(cwd: string, rawPath: string): boolean { | |
| }).allowed; | |
| } | |
| -function isAllowedConductorMetadataExecutionPath(executionCwd: string, policyCwd: string, rawPath: string): boolean { | |
| - const normalized = normalizeConductorMutationTargets([rawPath], executionCwd, policyCwd); | |
| - return normalized !== null | |
| - && normalized.length === 1 | |
| - && isAllowedConductorMetadataPath(policyCwd, normalized[0] ?? ""); | |
| +function isAllowedConductorMetadataExecutionPath( | |
| + executionCwd: string, | |
| + policyCwd: string, | |
| + rawPath: string, | |
| + authoritativeSessionId = "", | |
| +): boolean { | |
| + if (isUnresolvedVariableTarget(rawPath) || conductorPathnameExpansionIsAmbiguous(rawPath)) return false; | |
| + let current = isAbsolute(rawPath) ? resolve(rawPath) : resolve(executionCwd, rawPath); | |
| + const missingSegments: string[] = []; | |
| + while (true) { | |
| + try { | |
| + current = resolve(realpathSync(current), ...missingSegments.reverse()); | |
| + break; | |
| + } catch (error) { | |
| + if ((error as NodeJS.ErrnoException).code !== "ENOENT") return false; | |
| + const parent = dirname(current); | |
| + if (parent === current) return false; | |
| + missingSegments.push(basename(current)); | |
| + current = parent; | |
| + } | |
| + } | |
| + let canonicalPolicyCwd: string; | |
| + try { | |
| + canonicalPolicyCwd = realpathSync(resolve(policyCwd)); | |
| + } catch { | |
| + return false; | |
| + } | |
| + const normalized = relative(canonicalPolicyCwd, current).replace(/\\/g, "/"); | |
| + return normalized !== "" | |
| + && normalized !== ".." | |
| + && !normalized.startsWith("../") | |
| + && isAllowedConductorMetadataPath(canonicalPolicyCwd, normalized, authoritativeSessionId); | |
| } | |
| function isAllowedConductorMetadataSourcePath(cwd: string, rawPath: string): boolean { | |
| @@ -15984,7 +16306,9 @@ function normalizeWgetMutationTargets(targets: string[], effectiveCwd: string, r | |
| if (isUnresolvedVariableTarget(target) || /[`$]/.test(target) || conductorPathnameExpansionIsAmbiguous(target)) return null; | |
| try { | |
| const absoluteTarget = isAbsolute(target) ? resolve(target) : resolve(effectiveCwd, target); | |
| - normalized.push(relative(rootCwd, absoluteTarget).replace(/\\/g, "/") || "."); | |
| + const relativeTarget = canonicalRepoRelativePath(rootCwd, absoluteTarget); | |
| + if (!relativeTarget) return null; | |
| + normalized.push(relativeTarget); | |
| } catch { | |
| return null; | |
| } | |
| @@ -17730,7 +18054,7 @@ function conductorStateWriteTransportIsBoundToActiveSession( | |
| || safeString(statePayload.session_id).trim() !== authoritativeSessionId | |
| || !suppliedSessionAliasesMatch(statePayload, authoritativeSessionId) | |
| || safeString(statePayload.workingDirectory).trim() === "" | |
| - || resolve(safeString(statePayload.workingDirectory)) !== resolve(cwd) | |
| + || !canonicalDirectoriesEqual(safeString(statePayload.workingDirectory), cwd) | |
| ) return false; | |
| // Inherited hook environment is process-authenticated; model-controlled shell | |
| // assignments must match the session resolved for this PreToolUse payload. | |
| @@ -17993,12 +18317,12 @@ function directConductorStateWritePayloadHasExactSchema(payload: CodexHookPayloa | |
| const nestedSessionId = safeString(nestedState.session_id).trim(); | |
| if (nestedSessionId && nestedSessionId !== canonicalSessionId) return false; | |
| const nestedWorkingDirectory = safeString(nestedState.workingDirectory).trim(); | |
| - if (nestedWorkingDirectory && resolve(nestedWorkingDirectory) !== resolve(policyCwd)) return false; | |
| + if (nestedWorkingDirectory && !canonicalDirectoriesEqual(nestedWorkingDirectory, policyCwd)) return false; | |
| const nestedMode = safeString(nestedState.mode).trim(); | |
| if (nestedMode && nestedMode !== safeString(input.mode).trim()) return false; | |
| nestedState = nestedState.state === undefined ? null : safeObject(nestedState.state); | |
| } | |
| - if (safeString(input.workingDirectory).trim() === "" || resolve(safeString(input.workingDirectory)) !== resolve(policyCwd)) return false; | |
| + if (safeString(input.workingDirectory).trim() === "" || !canonicalDirectoriesEqual(safeString(input.workingDirectory), policyCwd)) return false; | |
| return true; | |
| } | |
| @@ -18012,6 +18336,258 @@ function conductorStatePayloadPreservesActiveGuard( | |
| return phase !== "" && isNonTerminalPhase(phase); | |
| } | |
| +function exactObjectHasOnlyKeys(value: unknown, allowedKeys: ReadonlySet<string>): value is Record<string, unknown> { | |
| + if (!value || typeof value !== "object" || Array.isArray(value)) return false; | |
| + return Object.keys(value).every((key) => allowedKeys.has(key)); | |
| +} | |
| + | |
| +function validateStandaloneUltragoalGoalMutation( | |
| + toolName: string, | |
| + toolInput: unknown, | |
| + activeState: ActiveConductorState, | |
| + writeActor: PreToolUseWriteActor, | |
| +): { allowed: boolean; detail?: string } { | |
| + if (activeState.mode !== "ultragoal") { | |
| + return { allowed: false, detail: "Codex goal mutations are authorized only for standalone Ultragoal" }; | |
| + } | |
| + if (writeActor !== "main-root") { | |
| + return { allowed: false, detail: "Codex goal mutations require exact Main-root provenance" }; | |
| + } | |
| + const phase = safeString(activeState.phase).trim().toLowerCase(); | |
| + if (CODEX_GOAL_CREATE_TOOL_NAMES.has(toolName)) { | |
| + if (phase !== "planning") { | |
| + return { allowed: false, detail: "create_goal is authorized only during Ultragoal planning" }; | |
| + } | |
| + if (!exactObjectHasOnlyKeys(toolInput, new Set(["objective", "token_budget"]))) { | |
| + return { allowed: false, detail: "create_goal payload contains unknown fields or is not an object" }; | |
| + } | |
| + if (safeString(toolInput.objective).trim() === "") { | |
| + return { allowed: false, detail: "create_goal requires a non-empty objective" }; | |
| + } | |
| + if ( | |
| + Object.prototype.hasOwnProperty.call(toolInput, "token_budget") | |
| + && (!Number.isInteger(toolInput.token_budget) || Number(toolInput.token_budget) <= 0) | |
| + ) { | |
| + return { allowed: false, detail: "create_goal token_budget must be a positive integer" }; | |
| + } | |
| + return { allowed: true }; | |
| + } | |
| + if (CODEX_GOAL_UPDATE_TOOL_NAMES.has(toolName)) { | |
| + if (!exactObjectHasOnlyKeys(toolInput, new Set(["status"])) || typeof toolInput.status !== "string") { | |
| + return { allowed: false, detail: "update_goal payload must contain only an exact status" }; | |
| + } | |
| + if (toolInput.status === "complete" && phase === "checkpointing") return { allowed: true }; | |
| + if (toolInput.status === "blocked" && phase === "blocked") return { allowed: true }; | |
| + return { | |
| + allowed: false, | |
| + detail: `update_goal status ${safeString(toolInput.status) || "<missing>"} is not authorized during Ultragoal ${phase || "active"}`, | |
| + }; | |
| + } | |
| + return { allowed: false, detail: `${toolName || "unknown tool"} is not an exact Codex goal mutation tool` }; | |
| +} | |
| + | |
| +function nativeAssignmentTargetIsWithinScope( | |
| + cwd: string, | |
| + rawTarget: string, | |
| + pathPrefixes: readonly string[], | |
| +): boolean { | |
| + const relativeTarget = normalizePlanningArtifactRelativePath(cwd, rawTarget); | |
| + if (!relativeTarget || conductorPathnameExpansionIsAmbiguous(relativeTarget)) return false; | |
| + if (conductorPathTraversesLink(cwd, relativeTarget)) return false; | |
| + return pathPrefixes.some((prefix) => ( | |
| + prefix === "." | |
| + || relativeTarget === prefix | |
| + || relativeTarget.startsWith(`${prefix}/`) | |
| + )); | |
| +} | |
| + | |
| +function classifyNativeAssignedBashAction(command: string): NativeAssignmentAction { | |
| + const words = tokenizeConductorShellWords(normalizeShellLineContinuations(command)) | |
| + .map(shellWordLiteral) | |
| + .filter(Boolean); | |
| + const commandIndex = words.findIndex((word) => !isEnvironmentAssignmentWord(word)); | |
| + const commandName = commandNameFromShellWord(words[commandIndex] ?? ""); | |
| + const firstArg = safeString(words[commandIndex + 1]).trim().toLowerCase(); | |
| + const secondArg = safeString(words[commandIndex + 2]).trim().toLowerCase(); | |
| + if ((commandName === "arc" || commandName === "git") && firstArg === "commit") return "vcs-commit"; | |
| + if ((commandName === "arc" || commandName === "git") && firstArg === "push") return "vcs-push"; | |
| + if (commandName === "gh" && firstArg === "pr") return "pr-update"; | |
| + if (commandName === "ya" && firstArg === "tool" && secondArg === "arcanum") return "pr-update"; | |
| + if (["ssh", "scp", "rsync", "curl", "kubectl", "podman", "docker"].includes(commandName)) { | |
| + return "live-operation"; | |
| + } | |
| + return "bash-write"; | |
| +} | |
| + | |
| +function nativeAssignedBashHasUnsafeStructure(cwd: string, command: string): boolean { | |
| + const normalized = normalizeShellLineContinuations(command); | |
| + if ( | |
| + splitConductorShellSegments(stripHeredocBodiesForCommandScan(normalized)).length !== 1 | |
| + || hasUnresolvedShellArithmeticExpansion(normalized) | |
| + || hasUnquotedShellSubstitution(normalized) | |
| + || hasDynamicNestedShellExecution(normalized) | |
| + || hasUnsafeUnquotedHeredocExpansion(normalized) | |
| + || commandHasUnsafeConductorShellState(normalized, cwd) | |
| + || conductorCommandMayMutatePathResolution(normalized) | |
| + ) return true; | |
| + const interpreterWrites = extractConductorInterpreterWrites(normalized); | |
| + return interpreterWrites.some((write) => write.unresolved || write.targets.length === 0); | |
| +} | |
| + | |
| +async function nativeChildAssignmentAllowsMutation( | |
| + payload: CodexHookPayload, | |
| + cwd: string, | |
| + stateDir: string, | |
| + sessionId: string, | |
| + toolName: string, | |
| + mutationTransport: PreToolUseMutationTransport, | |
| +): Promise<boolean> { | |
| + if ( | |
| + mutationTransport === "state" | |
| + || mutationTransport === "orchestration" | |
| + || mutationTransport === "goal" | |
| + || mutationTransport === "unknown" | |
| + ) { | |
| + return false; | |
| + } | |
| + const assignment = await readNativeSubagentAssignment(payload, cwd, stateDir, sessionId); | |
| + if (!assignment) return false; | |
| + | |
| + if (mutationTransport === "path") { | |
| + if (!assignment.allowed_actions.includes("path-write")) return false; | |
| + const candidates = collectImplementationToolPathCandidates( | |
| + payload, | |
| + toolName, | |
| + readPreToolUsePathCandidates(payload), | |
| + ); | |
| + return candidates.length > 0 | |
| + && candidates.every((candidate) => ( | |
| + !isNativeAssignmentPath(stateDir, candidate, cwd) | |
| + && nativeAssignmentTargetIsWithinScope(cwd, candidate, assignment.path_prefixes) | |
| + )); | |
| + } | |
| + if (mutationTransport !== "bash") return false; | |
| + | |
| + const command = readPreToolUseCommand(payload); | |
| + if (nativeAssignedBashHasUnsafeStructure(cwd, command)) return false; | |
| + const action = classifyNativeAssignedBashAction(command); | |
| + if (!assignment.allowed_actions.includes(action)) return false; | |
| + const targets = extractDeepInterviewCommandWriteTargets(command, cwd, cwd); | |
| + if (targets.some((target) => isNativeAssignmentPath(stateDir, target, cwd))) return false; | |
| + if (action !== "bash-write") { | |
| + return assignment.path_prefixes.includes(".") && targets.every((target) => ( | |
| + nativeAssignmentTargetIsWithinScope(cwd, target, assignment.path_prefixes) | |
| + )); | |
| + } | |
| + return targets.length > 0 && targets.every((target) => ( | |
| + nativeAssignmentTargetIsWithinScope(cwd, target, assignment.path_prefixes) | |
| + )); | |
| +} | |
| + | |
| +function canonicalRootTransportEnvironmentIsSafe(words: readonly string[], sessionId: string): boolean { | |
| + for (const rawWord of words) { | |
| + const word = shellWordLiteral(rawWord); | |
| + if (!isEnvironmentAssignmentWord(word)) break; | |
| + const name = shellAssignmentName(word); | |
| + const value = word.slice(word.indexOf("=") + 1); | |
| + if ((name !== "OMX_SESSION_ID" && name !== "GJC_SESSION_ID") || value !== sessionId) return false; | |
| + } | |
| + return true; | |
| +} | |
| + | |
| +function isCanonicalConductorRootTransport( | |
| + cwd: string, | |
| + command: string, | |
| + sessionId: string, | |
| + activeState: ActiveConductorState, | |
| +): boolean { | |
| + if (!sessionId || omxStateTransportHasUnsafeRuntimeWrapper(command)) return false; | |
| + const canonical = canonicalizeOmxStateTransportCommand(command); | |
| + const normalized = normalizeShellLineContinuations(canonical); | |
| + const originalWords = tokenizeConductorShellWords(normalizeShellLineContinuations(command)); | |
| + if (!canonicalRootTransportEnvironmentIsSafe(originalWords, sessionId)) return false; | |
| + const originalLiteralWords = originalWords.map(shellWordLiteral); | |
| + const securityNormalized = normalized.replace( | |
| + /^(?:(?:OMX_SESSION_ID|GJC_SESSION_ID)=[^\s]+\s+)*/, | |
| + "", | |
| + ); | |
| + if ( | |
| + splitConductorShellSegments(stripHeredocBodiesForCommandScan(normalized)).length !== 1 | |
| + || hasUnsafeUnquotedHeredocExpansion(normalized) | |
| + || hasUnquotedShellSubstitution(normalized) | |
| + || hasDynamicNestedShellExecution(normalized) | |
| + || hasUnresolvedShellArithmeticExpansion(normalized) | |
| + || commandHasUnsafeConductorShellState(securityNormalized, cwd) | |
| + || conductorCommandMayMutatePathResolution(securityNormalized) | |
| + || extractDeepInterviewCommandRedirectTargets(normalized).length > 0 | |
| + ) return false; | |
| + | |
| + const words = tokenizeConductorShellWords(normalized); | |
| + if (!canonicalRootTransportEnvironmentIsSafe(words, sessionId)) return false; | |
| + const literalWords = words.map(shellWordLiteral); | |
| + const commandIndex = literalWords.findIndex((word) => !isEnvironmentAssignmentWord(word)); | |
| + if (commandNameFromShellWord(literalWords[commandIndex] ?? "") !== "omx") return false; | |
| + const surface = safeString(literalWords[commandIndex + 1]).trim(); | |
| + const operation = safeString(literalWords[commandIndex + 2]).trim(); | |
| + | |
| + if (surface === "state") { | |
| + if (!["read", "write", "clear"].includes(operation)) return false; | |
| + const allowedFlags = new Set(["--input", "--input-file", "--mode", "--json"]); | |
| + for (let index = commandIndex + 3; index < literalWords.length; index += 1) { | |
| + const word = literalWords[index] ?? ""; | |
| + if (word === "--json") continue; | |
| + const flag = word.includes("=") ? word.slice(0, word.indexOf("=")) : word; | |
| + if (!allowedFlags.has(flag)) return false; | |
| + if (!word.includes("=") && flag !== "--json") index += 1; | |
| + } | |
| + if (operation === "write") { | |
| + const payload = readStateWriteInputPayload(cwd, canonical, command); | |
| + const operations = collectOmxStateCommandOperations(canonical, "write"); | |
| + const stateWrite = operations[0]; | |
| + return operations.length === 1 | |
| + && Boolean(stateWrite) | |
| + && payload !== null | |
| + && stateWrite?.nested !== true | |
| + && !hasPriorExecutableCommand(stateWrite?.prefix ?? "") | |
| + && safeString(payload?.session_id).trim() === sessionId | |
| + && suppliedSessionAliasesMatch(payload, sessionId) | |
| + && safeString(payload?.workingDirectory).trim() !== "" | |
| + && canonicalDirectoriesEqual(safeString(payload?.workingDirectory), cwd) | |
| + && conductorStatePayloadPreservesActiveGuard(payload, activeState); | |
| + } | |
| + const mode = readStateWriteFlagValue(literalWords, "--mode"); | |
| + if (safeString(mode).trim() !== activeState.mode) return false; | |
| + if (operation === "clear") { | |
| + return originalLiteralWords.some((word) => ( | |
| + word === `OMX_SESSION_ID=${sessionId}` || word === `GJC_SESSION_ID=${sessionId}` | |
| + )); | |
| + } | |
| + return true; | |
| + } | |
| + | |
| + if (surface !== "ultragoal" || operation !== "checkpoint" || activeState.mode !== "ultragoal") return false; | |
| + const allowedValueFlags = new Set([ | |
| + "--goal-id", | |
| + "--status", | |
| + "--evidence", | |
| + "--codex-goal-json", | |
| + "--quality-gate-json", | |
| + ]); | |
| + const values = new Map<string, string>(); | |
| + for (let index = commandIndex + 3; index < literalWords.length; index += 1) { | |
| + const word = literalWords[index] ?? ""; | |
| + if (word === "--json") continue; | |
| + const equals = word.indexOf("="); | |
| + const flag = equals >= 0 ? word.slice(0, equals) : word; | |
| + if (!allowedValueFlags.has(flag)) return false; | |
| + const value = equals >= 0 ? word.slice(equals + 1) : literalWords[++index] ?? ""; | |
| + if (!value || values.has(flag)) return false; | |
| + values.set(flag, value); | |
| + } | |
| + return /^[A-Za-z0-9._:-]+$/.test(values.get("--goal-id") ?? "") | |
| + && ["complete", "failed", "blocked"].includes(values.get("--status") ?? ""); | |
| +} | |
| + | |
| function buildConductorSessionProvenanceDeny( | |
| activeState: ActiveConductorState, | |
| @@ -18062,6 +18638,12 @@ export async function buildConductorPreToolUseWriteGuardOutput( | |
| const pathCandidates = readPreToolUsePathCandidates(payload); | |
| const mutationTransport = classifyPreToolUseMutationTransport(payload, toolName, cwd); | |
| + if ( | |
| + writeActor === "main-root" | |
| + && toolName === "Bash" | |
| + && isCanonicalConductorRootTransport(policyCwd, command, sessionId, activeState) | |
| + ) return null; | |
| + | |
| let blocked = false; | |
| let blockedDetail = "Main-root Conductor write is not delegated"; | |
| let nativeChildMutationAttempt = false; | |
| @@ -18104,6 +18686,16 @@ export async function buildConductorPreToolUseWriteGuardOutput( | |
| blocked = true; | |
| blockedDetail = "Structured state writes must preserve the canonical active Conductor guard"; | |
| } | |
| + } else if (mutationTransport === "goal") { | |
| + nativeChildMutationAttempt = true; | |
| + const goalDecision = validateStandaloneUltragoalGoalMutation( | |
| + toolName, | |
| + payload.tool_input, | |
| + activeState, | |
| + writeActor, | |
| + ); | |
| + blocked = !goalDecision.allowed; | |
| + blockedDetail = goalDecision.detail ?? blockedDetail; | |
| } else if (mutationTransport === "orchestration") { | |
| nativeChildMutationAttempt = true; | |
| } else if (mutationTransport === "path") { | |
| @@ -18113,7 +18705,12 @@ export async function buildConductorPreToolUseWriteGuardOutput( | |
| blocked = true; | |
| blockedDetail = describeConductorBlockedWrite(toolName, undefined, toolPathCandidates.length); | |
| } else { | |
| - const blockedPath = toolPathCandidates.find((candidate) => !isAllowedConductorMetadataExecutionPath(cwd, policyCwd, candidate)); | |
| + const blockedPath = toolPathCandidates.find((candidate) => !isAllowedConductorMetadataExecutionPath( | |
| + cwd, | |
| + policyCwd, | |
| + candidate, | |
| + sessionId, | |
| + )); | |
| blocked = blockedPath !== undefined; | |
| if (blockedPath !== undefined) { | |
| blockedDetail = describeConductorBlockedWrite(toolName, blockedPath, toolPathCandidates.length); | |
| @@ -18133,11 +18730,23 @@ export async function buildConductorPreToolUseWriteGuardOutput( | |
| stateDir, | |
| policyCwd, | |
| ); | |
| - if (writeActor === "team-worker" && !teamWorkerProtectedStateTarget) return null; | |
| + if (writeActor === "team-worker" && mutationTransport !== "goal" && !teamWorkerProtectedStateTarget) return null; | |
| if (writeActor === "team-worker" && teamWorkerProtectedStateTarget && !blocked) { | |
| blocked = true; | |
| blockedDetail = "Bash targets protected workflow state outside authorized Team-worker scope"; | |
| } | |
| + if ( | |
| + writeActor === "native-child" | |
| + && nativeChildMutationAttempt | |
| + && await nativeChildAssignmentAllowsMutation( | |
| + payload, | |
| + policyCwd, | |
| + stateDir, | |
| + sessionId, | |
| + toolName, | |
| + mutationTransport, | |
| + ) | |
| + ) return null; | |
| if (!blocked && (writeActor !== "native-child" || !nativeChildMutationAttempt)) return null; | |
| if (!blocked && nativeChildMutationAttempt && writeActor === "native-child") { | |
| blockedDetail = toolName === "Bash" | |
| @@ -20259,7 +20868,13 @@ export async function dispatchCodexNativeHook( | |
| preToolUseSessionId, | |
| policyCwd, | |
| ) | |
| - ?? buildRawProtectedWorkflowStatePathOutput(payload, policyCwd, stateDir) | |
| + ?? await buildRawProtectedWorkflowStatePathOutput( | |
| + payload, | |
| + policyCwd, | |
| + stateDir, | |
| + preToolUseSessionId, | |
| + guardedConductorState !== null, | |
| + ) | |
| ?? await buildNativeSubagentCapacityCloseGuardOutput(payload, policyCwd, stateDir) | |
| ?? buildMalformedPreToolUseBlockTestOutput(payload) | |
| ?? buildNativePreToolUseOutput(payload); | |
| diff --git a/docs/native-subagent-assignments.md b/docs/native-subagent-assignments.md | |
| new file mode 100644 | |
| index 00000000..cb72befc | |
| --- /dev/null | |
| +++ b/docs/native-subagent-assignments.md | |
| @@ -0,0 +1,84 @@ | |
| +# Native subagent assignments | |
| + | |
| +The Codex native hook keeps typed native children read-only unless the active | |
| +root Conductor creates an exact, session-scoped assignment. This transport does | |
| +not grant Team-worker identity or authority. | |
| + | |
| +## Location | |
| + | |
| +For child `<child-agent-id>` in root session `<session-id>`: | |
| + | |
| +```text | |
| +.omx/state/sessions/<session-id>/native-assignments/<encodeURIComponent(child-agent-id)>.json | |
| +``` | |
| + | |
| +The root Conductor is the only actor allowed to create or replace this metadata. | |
| +A native child cannot mutate any `native-assignments` path, even when its | |
| +ordinary path scope would otherwise match. | |
| + | |
| +## Schema | |
| + | |
| +The JSON object must contain exactly these keys: | |
| + | |
| +```json | |
| +{ | |
| + "schema_version": 1, | |
| + "kind": "native-subagent-assignment", | |
| + "lifecycle": "active", | |
| + "root_session_id": "exact-root-session-id", | |
| + "root_thread_id": "exact-root-thread-id", | |
| + "child_agent_id": "exact-canonical-child-agent-id", | |
| + "agent_type": "executor", | |
| + "allowed_actions": ["path-write", "bash-write"], | |
| + "path_prefixes": ["tmp/native-probe"], | |
| + "issued_at": "2026-07-23T20:00:00.000Z", | |
| + "expires_at": "2026-07-23T20:10:00.000Z" | |
| +} | |
| +``` | |
| + | |
| +The hook checks the root session and leader thread against the active session | |
| +and native subagent tracker. The child must be tracked as `kind: "subagent"` | |
| +and must provide one unambiguous installed top-level hook role through the | |
| +native `agent_role`/`agent_type` field aliases; the assignment `agent_type` | |
| +must match that role exactly. Real native tracker entries may omit `mode`; when | |
| +tracker `mode` is present, it must also match. Nested-only role evidence or | |
| +conflicting top-level aliases cannot authorize an assignment. The filename | |
| +must encode that same canonical child ID. | |
| + | |
| +Assignments are fail-closed when unreadable, malformed, expired, issued too far | |
| +in the future, longer than 24 hours, symlinked, hard-linked, larger than 64 KiB, | |
| +or writable by group/other. Unknown keys, duplicate actions or prefixes, | |
| +ambiguous paths, symlink traversal, `.omx`, and `.git` scopes are rejected. | |
| +Deleting the file or changing `lifecycle` away from `active` revokes the grant. | |
| + | |
| +## Actions | |
| + | |
| +- `path-write`: direct file-edit tools within `path_prefixes`. | |
| +- `bash-write`: one statically analyzable shell mutation within | |
| + `path_prefixes`. | |
| +- `vcs-commit`: structural `arc commit` or `git commit`. | |
| +- `vcs-push`: structural `arc push` or `git push`. | |
| +- `pr-update`: structural `gh pr ...` or `ya tool arcanum ...`. | |
| +- `live-operation`: structural `ssh`, `scp`, `rsync`, `curl`, `kubectl`, | |
| + `podman`, or `docker`. | |
| + | |
| +Operational actions require an explicit `"."` prefix. They do not imply state, | |
| +orchestration, goal, assignment, or Team-worker authority. Shell substitution, | |
| +unresolved arithmetic, dynamic nested execution, unsafe heredocs, unsafe | |
| +nameref/allexport state, arbitrary PATH mutation, unresolved interpreter | |
| +writes, and path/symlink escapes remain blocked. | |
| + | |
| +Without a valid assignment, typed native children continue to receive | |
| +`OWNER_CONFIRMATION_REQUIRED`. | |
| + | |
| +## Root orchestration transports | |
| + | |
| +An active, session-bound Main-root Conductor may write the existing bounded | |
| +metadata roots, including `.omx/state`, `.omx/ultragoal`, `.omx/ralph`, | |
| +`.omx/team`, `.omx/mailbox`, `.omx/handoff`, `.omx/handoffs`, `.omx/goals`, | |
| +`.omx/notepad`, `.omx/wiki`, and `.beads`. | |
| + | |
| +The hook structurally recognizes canonical `omx state read`, `omx state write`, | |
| +`omx state clear`, and `omx ultragoal checkpoint` commands before generic shell | |
| +mutation analysis. Exact session/cwd/mode binding and all existing injection, | |
| +PATH, symlink, and protected-state-gate checks still apply. | |
| diff --git a/src/scripts/__tests__/codex-native-assignment.test.ts b/src/scripts/__tests__/codex-native-assignment.test.ts | |
| new file mode 100644 | |
| index 00000000..6db52f0f | |
| --- /dev/null | |
| +++ b/src/scripts/__tests__/codex-native-assignment.test.ts | |
| @@ -0,0 +1,511 @@ | |
| +import assert from "node:assert/strict"; | |
| +import { realpathSync } from "node:fs"; | |
| +import { chmod, mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises"; | |
| +import { tmpdir } from "node:os"; | |
| +import { dirname, join, resolve } from "node:path"; | |
| +import { describe, it } from "node:test"; | |
| +import { dispatchCodexNativeHook } from "../codex-native-hook.js"; | |
| + | |
| +async function writeJson(path: string, value: unknown): Promise<void> { | |
| + await mkdir(dirname(path), { recursive: true }); | |
| + await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf-8"); | |
| +} | |
| + | |
| +async function createActiveConductorFixture(phase = "executing"): Promise<{ | |
| + cwd: string; | |
| + stateDir: string; | |
| + sessionId: string; | |
| + leaderThreadId: string; | |
| + childAgentId: string; | |
| +}> { | |
| + const cwd = await mkdtemp(join(tmpdir(), "omx-native-assignment-")); | |
| + const stateDir = join(cwd, ".omx", "state"); | |
| + const sessionId = "sess-native-assignment"; | |
| + const leaderThreadId = "thread-native-assignment-leader"; | |
| + const childAgentId = "agent-native-assignment-executor"; | |
| + const now = new Date().toISOString(); | |
| + | |
| + await writeJson(join(stateDir, "session.json"), { | |
| + session_id: sessionId, | |
| + native_session_id: leaderThreadId, | |
| + cwd, | |
| + }); | |
| + await writeJson(join(stateDir, "subagent-tracking.json"), { | |
| + schemaVersion: 1, | |
| + sessions: { | |
| + [sessionId]: { | |
| + session_id: sessionId, | |
| + leader_thread_id: leaderThreadId, | |
| + updated_at: now, | |
| + threads: { | |
| + [leaderThreadId]: { | |
| + thread_id: leaderThreadId, | |
| + kind: "leader", | |
| + first_seen_at: now, | |
| + last_seen_at: now, | |
| + turn_count: 1, | |
| + }, | |
| + [childAgentId]: { | |
| + thread_id: childAgentId, | |
| + kind: "subagent", | |
| + mode: "executor", | |
| + leader_thread_id: leaderThreadId, | |
| + first_seen_at: now, | |
| + last_seen_at: now, | |
| + turn_count: 1, | |
| + }, | |
| + }, | |
| + }, | |
| + }, | |
| + }); | |
| + await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { | |
| + active: true, | |
| + skill: "ultragoal", | |
| + phase, | |
| + session_id: sessionId, | |
| + active_skills: [ | |
| + { skill: "ultragoal", phase, active: true, session_id: sessionId }, | |
| + ], | |
| + }); | |
| + await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), { | |
| + active: true, | |
| + mode: "ultragoal", | |
| + current_phase: phase, | |
| + session_id: sessionId, | |
| + }); | |
| + | |
| + return { cwd, stateDir, sessionId, leaderThreadId, childAgentId }; | |
| +} | |
| + | |
| +function preToolUse( | |
| + cwd: string, | |
| + sessionId: string, | |
| + identity: Record<string, unknown>, | |
| + toolName: string, | |
| + toolInput: Record<string, unknown>, | |
| +) { | |
| + return dispatchCodexNativeHook({ | |
| + hook_event_name: "PreToolUse", | |
| + cwd, | |
| + session_id: sessionId, | |
| + ...identity, | |
| + tool_name: toolName, | |
| + tool_input: toolInput, | |
| + }, { cwd }); | |
| +} | |
| + | |
| +describe("native Conductor assignment transport", () => { | |
| + it("accepts real typed-child provenance when the native tracker omits mode but rejects missing or conflicting role evidence", async () => { | |
| + const fixture = await createActiveConductorFixture(); | |
| + const { cwd, stateDir, sessionId, leaderThreadId, childAgentId } = fixture; | |
| + const assignmentPath = join( | |
| + stateDir, | |
| + "sessions", | |
| + sessionId, | |
| + "native-assignments", | |
| + `${encodeURIComponent(childAgentId)}.json`, | |
| + ); | |
| + const issuedAt = new Date(); | |
| + const expiresAt = new Date(issuedAt.getTime() + 10 * 60_000); | |
| + const trackingPath = join(stateDir, "subagent-tracking.json"); | |
| + const trackedChild = { | |
| + thread_id: childAgentId, | |
| + kind: "subagent", | |
| + leader_thread_id: leaderThreadId, | |
| + first_seen_at: issuedAt.toISOString(), | |
| + last_seen_at: issuedAt.toISOString(), | |
| + turn_count: 1, | |
| + }; | |
| + | |
| + try { | |
| + await writeJson(assignmentPath, { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: sessionId, | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: "executor", | |
| + allowed_actions: ["path-write"], | |
| + path_prefixes: ["src/assigned"], | |
| + issued_at: issuedAt.toISOString(), | |
| + expires_at: expiresAt.toISOString(), | |
| + }); | |
| + await writeJson(trackingPath, { | |
| + schemaVersion: 1, | |
| + sessions: { | |
| + [sessionId]: { | |
| + session_id: sessionId, | |
| + leader_thread_id: leaderThreadId, | |
| + updated_at: issuedAt.toISOString(), | |
| + threads: { | |
| + [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader" }, | |
| + [childAgentId]: trackedChild, | |
| + }, | |
| + }, | |
| + }, | |
| + }); | |
| + | |
| + const realTypedChild = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + { agent_id: childAgentId, agent_type: "executor" }, | |
| + "Write", | |
| + { file_path: "src/assigned/real-typed-child.txt", content: "ok\n" }, | |
| + ); | |
| + assert.equal(realTypedChild.outputJson, null); | |
| + | |
| + const missingTypedRole = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + { | |
| + agent_id: childAgentId, | |
| + source: { subagent: { thread_spawn: { agent_role: "executor" } } }, | |
| + }, | |
| + "Write", | |
| + { file_path: "src/assigned/missing-role.txt", content: "must-not-write\n" }, | |
| + ); | |
| + assert.equal(missingTypedRole.outputJson?.decision, "block"); | |
| + assert.match(String(missingTypedRole.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + | |
| + const conflictingTypedAliases = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + { agent_id: childAgentId, agent_role: "executor", agent_type: "writer" }, | |
| + "Write", | |
| + { file_path: "src/assigned/conflicting-aliases.txt", content: "must-not-write\n" }, | |
| + ); | |
| + assert.equal(conflictingTypedAliases.outputJson?.decision, "block"); | |
| + assert.match(String(conflictingTypedAliases.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + | |
| + await writeJson(trackingPath, { | |
| + schemaVersion: 1, | |
| + sessions: { | |
| + [sessionId]: { | |
| + session_id: sessionId, | |
| + leader_thread_id: leaderThreadId, | |
| + updated_at: issuedAt.toISOString(), | |
| + threads: { | |
| + [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader" }, | |
| + [childAgentId]: { ...trackedChild, mode: "writer" }, | |
| + }, | |
| + }, | |
| + }, | |
| + }); | |
| + const conflictingTrackerRole = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + { agent_id: childAgentId, agent_role: "executor" }, | |
| + "Write", | |
| + { file_path: "src/assigned/conflicting-role.txt", content: "must-not-write\n" }, | |
| + ); | |
| + assert.equal(conflictingTrackerRole.outputJson?.decision, "block"); | |
| + assert.match(String(conflictingTrackerRole.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it("allows only an exact live typed-child path grant and keeps all negative contracts fail-closed", async () => { | |
| + const fixture = await createActiveConductorFixture(); | |
| + const { cwd, stateDir, sessionId, leaderThreadId, childAgentId } = fixture; | |
| + const assignmentPath = join( | |
| + stateDir, | |
| + "sessions", | |
| + sessionId, | |
| + "native-assignments", | |
| + `${encodeURIComponent(childAgentId)}.json`, | |
| + ); | |
| + const issuedAt = new Date(); | |
| + const expiresAt = new Date(issuedAt.getTime() + 10 * 60_000); | |
| + | |
| + try { | |
| + await writeJson(assignmentPath, { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: sessionId, | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: "executor", | |
| + allowed_actions: ["path-write"], | |
| + path_prefixes: ["src/assigned"], | |
| + issued_at: issuedAt.toISOString(), | |
| + expires_at: expiresAt.toISOString(), | |
| + }); | |
| + | |
| + const exactChild = { agent_id: childAgentId, agent_role: "executor" }; | |
| + const allowed = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + exactChild, | |
| + "Write", | |
| + { file_path: "src/assigned/result.txt", content: "ok\n" }, | |
| + ); | |
| + assert.equal(allowed.outputJson, null); | |
| + | |
| + for (const [name, identity, filePath] of [ | |
| + ["outside-scope", exactChild, "src/outside.txt"], | |
| + ["assignment-self-mutation", exactChild, assignmentPath], | |
| + ["foreign-child", { agent_id: "agent-native-assignment-foreign", agent_role: "executor" }, "src/assigned/result.txt"], | |
| + ["role-mismatch", { agent_id: childAgentId, agent_role: "writer" }, "src/assigned/result.txt"], | |
| + ["ungranted-writer", { agent_id: "agent-native-assignment-writer", agent_role: "writer" }, "src/assigned/result.txt"], | |
| + ] as const) { | |
| + const denied = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + identity, | |
| + "Write", | |
| + { file_path: filePath, content: `${name}\n` }, | |
| + ); | |
| + assert.equal(denied.outputJson?.decision, "block", name); | |
| + assert.match(String(denied.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/, name); | |
| + } | |
| + | |
| + await writeJson(assignmentPath, { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: "foreign-session", | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: "executor", | |
| + allowed_actions: ["path-write"], | |
| + path_prefixes: ["src/assigned"], | |
| + issued_at: issuedAt.toISOString(), | |
| + expires_at: expiresAt.toISOString(), | |
| + unexpected_scope_expansion: true, | |
| + }); | |
| + const malformed = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + exactChild, | |
| + "Write", | |
| + { file_path: "src/assigned/malformed.txt", content: "malformed\n" }, | |
| + ); | |
| + assert.equal(malformed.outputJson?.decision, "block"); | |
| + assert.match(String(malformed.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + | |
| + await mkdir(join(cwd, "src", "assigned"), { recursive: true }); | |
| + const escapedTarget = await mkdtemp(join(tmpdir(), "omx-native-assignment-escape-")); | |
| + await symlink(escapedTarget, join(cwd, "src", "assigned", "link")); | |
| + await writeJson(assignmentPath, { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: sessionId, | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: "executor", | |
| + allowed_actions: ["path-write", "bash-write"], | |
| + path_prefixes: ["src/assigned"], | |
| + issued_at: issuedAt.toISOString(), | |
| + expires_at: expiresAt.toISOString(), | |
| + }); | |
| + const allowedBash = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + exactChild, | |
| + "Bash", | |
| + { command: "printf ok > src/assigned/bash.txt" }, | |
| + ); | |
| + assert.equal(allowedBash.outputJson, null); | |
| + for (const [name, command] of [ | |
| + ["bash-outside-scope", "printf no > src/outside-bash.txt"], | |
| + ["bash-symlink-escape", "printf no > src/assigned/link/escaped.txt"], | |
| + ["bash-substitution-poison", "printf \"$(printf no)\" > src/assigned/poison.txt"], | |
| + ] as const) { | |
| + const denied = await preToolUse(cwd, sessionId, exactChild, "Bash", { command }); | |
| + assert.equal(denied.outputJson?.decision, "block", name); | |
| + assert.match(String(denied.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/, name); | |
| + } | |
| + await rm(escapedTarget, { recursive: true, force: true }); | |
| + | |
| + await writeJson(assignmentPath, { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: sessionId, | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: "executor", | |
| + allowed_actions: ["path-write"], | |
| + path_prefixes: ["src/assigned"], | |
| + issued_at: new Date(issuedAt.getTime() - 20 * 60_000).toISOString(), | |
| + expires_at: new Date(issuedAt.getTime() - 10 * 60_000).toISOString(), | |
| + }); | |
| + const stale = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + exactChild, | |
| + "Write", | |
| + { file_path: "src/assigned/stale.txt", content: "stale\n" }, | |
| + ); | |
| + assert.equal(stale.outputJson?.decision, "block"); | |
| + assert.match(String(stale.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + | |
| + await writeJson(assignmentPath, { | |
| + schema_version: 1, | |
| + kind: "native-subagent-assignment", | |
| + lifecycle: "active", | |
| + root_session_id: sessionId, | |
| + root_thread_id: leaderThreadId, | |
| + child_agent_id: childAgentId, | |
| + agent_type: "executor", | |
| + allowed_actions: ["path-write"], | |
| + path_prefixes: ["src/assigned"], | |
| + issued_at: issuedAt.toISOString(), | |
| + expires_at: expiresAt.toISOString(), | |
| + }); | |
| + await chmod(assignmentPath, 0o666); | |
| + const writableByOthers = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + exactChild, | |
| + "Write", | |
| + { file_path: "src/assigned/world-writable-grant.txt", content: "must-not-write\n" }, | |
| + ); | |
| + assert.equal(writableByOthers.outputJson?.decision, "block"); | |
| + assert.match(String(writableByOthers.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + } finally { | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it("recognizes root metadata and canonical state/checkpoint transports before generic shell mutation analysis", async () => { | |
| + const fixture = await createActiveConductorFixture(); | |
| + const { cwd, sessionId, leaderThreadId } = fixture; | |
| + const packageCli = realpathSync(resolve(process.cwd(), "dist", "cli", "omx.js")); | |
| + const packageBin = join(cwd, "node_modules", ".bin", "omx"); | |
| + const inheritedPath = process.env.PATH; | |
| + | |
| + try { | |
| + await mkdir(dirname(packageBin), { recursive: true }); | |
| + await symlink(packageCli, packageBin); | |
| + process.env.PATH = `${dirname(packageBin)}:/usr/bin:/bin`; | |
| + const root = { agent_id: leaderThreadId, thread_id: leaderThreadId }; | |
| + | |
| + const rootAssignment = await preToolUse(cwd, sessionId, root, "Write", { | |
| + file_path: `.omx/state/sessions/${sessionId}/native-assignments/root-created.json`, | |
| + content: "{}\n", | |
| + }); | |
| + assert.equal(rootAssignment.outputJson, null, "root-assignment-create"); | |
| + const foreignSessionAssignment = await preToolUse(cwd, sessionId, root, "Write", { | |
| + file_path: ".omx/state/sessions/foreign-session/native-assignments/root-created.json", | |
| + content: "{}\n", | |
| + }); | |
| + assert.equal(foreignSessionAssignment.outputJson?.decision, "block", "foreign-session-assignment"); | |
| + const protectedGate = await preToolUse(cwd, sessionId, root, "Write", { | |
| + file_path: `.omx/state/sessions/${sessionId}/ultragoal-state.json`, | |
| + content: "{}\n", | |
| + }); | |
| + assert.equal(protectedGate.outputJson?.decision, "block", "protected-state-gate"); | |
| + | |
| + for (const prefix of [ | |
| + ".omx/state", | |
| + ".omx/ultragoal", | |
| + ".omx/ralph", | |
| + ".omx/team", | |
| + ".omx/mailbox", | |
| + ".omx/handoff", | |
| + ".omx/handoffs", | |
| + ".omx/goals", | |
| + ".omx/notepad", | |
| + ".omx/wiki", | |
| + ".beads", | |
| + ]) { | |
| + const metadata = await preToolUse( | |
| + cwd, | |
| + sessionId, | |
| + root, | |
| + "Write", | |
| + { file_path: `${prefix}/native-assignment-regression.json`, content: "{}\n" }, | |
| + ); | |
| + assert.equal(metadata.outputJson, null, prefix); | |
| + } | |
| + | |
| + const stateWritePayload = JSON.stringify({ | |
| + mode: "ultragoal", | |
| + active: true, | |
| + current_phase: "executing", | |
| + session_id: sessionId, | |
| + workingDirectory: cwd, | |
| + }); | |
| + for (const [name, command] of [ | |
| + ["state-read", "omx state read --mode ultragoal --json"], | |
| + ["state-write", `omx state write --input '${stateWritePayload}' --json`], | |
| + ["state-clear", `OMX_SESSION_ID=${sessionId} omx state clear --mode ultragoal --json`], | |
| + [ | |
| + "ultragoal-checkpoint", | |
| + "omx ultragoal checkpoint --goal-id G001-regression --status blocked --evidence 'authorization regression' --codex-goal-json '{\"status\":\"in_progress\"}' --json", | |
| + ], | |
| + ] as const) { | |
| + const result = await preToolUse(cwd, sessionId, root, "Bash", { command }); | |
| + assert.equal(result.outputJson, null, name); | |
| + } | |
| + | |
| + for (const [name, command] of [ | |
| + ["path-poison", `PATH=${join(cwd, "src")} omx state read --mode ultragoal --json`], | |
| + ["substitution-poison", "omx state read --mode \"$(printf ultragoal)\" --json"], | |
| + ["nameref-poison", "declare -n OMX_SESSION_ID=POISON; omx state read --mode ultragoal --json"], | |
| + ] as const) { | |
| + const denied = await preToolUse(cwd, sessionId, root, "Bash", { command }); | |
| + assert.equal(denied.outputJson?.decision, "block", name); | |
| + } | |
| + } finally { | |
| + if (inheritedPath === undefined) delete process.env.PATH; | |
| + else process.env.PATH = inheritedPath; | |
| + await rm(cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| + | |
| + it("preserves exact root-only Codex goal transports without lending them to children", async () => { | |
| + const planning = await createActiveConductorFixture("planning"); | |
| + try { | |
| + const root = { agent_id: planning.leaderThreadId, thread_id: planning.leaderThreadId }; | |
| + const child = { agent_id: planning.childAgentId, agent_role: "executor" }; | |
| + const readOnly = await preToolUse(planning.cwd, planning.sessionId, child, "functions.get_goal", {}); | |
| + assert.equal(readOnly.outputJson, null, "get-goal-read-only"); | |
| + const create = await preToolUse(planning.cwd, planning.sessionId, root, "functions.create_goal", { | |
| + objective: "Verify the exact goal transport", | |
| + token_budget: 100, | |
| + }); | |
| + assert.equal(create.outputJson, null, "root-create-goal"); | |
| + const malformedCreate = await preToolUse(planning.cwd, planning.sessionId, root, "functions.create_goal", { | |
| + objective: "Verify the exact goal transport", | |
| + extra_authority: true, | |
| + }); | |
| + assert.equal(malformedCreate.outputJson?.decision, "block", "malformed-create-goal"); | |
| + const childCreate = await preToolUse(planning.cwd, planning.sessionId, child, "functions.create_goal", { | |
| + objective: "Child must not create a goal", | |
| + }); | |
| + assert.equal(childCreate.outputJson?.decision, "block", "child-create-goal"); | |
| + assert.match(String(childCreate.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/); | |
| + } finally { | |
| + await rm(planning.cwd, { recursive: true, force: true }); | |
| + } | |
| + | |
| + const checkpointing = await createActiveConductorFixture("checkpointing"); | |
| + try { | |
| + const root = { agent_id: checkpointing.leaderThreadId, thread_id: checkpointing.leaderThreadId }; | |
| + const complete = await preToolUse( | |
| + checkpointing.cwd, | |
| + checkpointing.sessionId, | |
| + root, | |
| + "functions.update_goal", | |
| + { status: "complete" }, | |
| + ); | |
| + assert.equal(complete.outputJson, null, "root-update-goal-complete"); | |
| + const wrongStatus = await preToolUse( | |
| + checkpointing.cwd, | |
| + checkpointing.sessionId, | |
| + root, | |
| + "functions.update_goal", | |
| + { status: "blocked" }, | |
| + ); | |
| + assert.equal(wrongStatus.outputJson?.decision, "block", "wrong-phase-goal-status"); | |
| + } finally { | |
| + await rm(checkpointing.cwd, { recursive: true, force: true }); | |
| + } | |
| + }); | |
| +}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment