#!/usr/bin/env node import { spawn, spawnSync } from 'node:child_process'; import { createHash, randomBytes } from 'node:crypto'; import { createServer } from 'node:http'; import { existsSync, lstatSync, readFileSync, realpathSync, } from 'node:fs'; import { mkdir, rm, stat, writeFile, } from 'node:fs/promises'; import { homedir, tmpdir } from 'node:os'; import { basename, dirname, isAbsolute, join, relative, resolve, win32, } from 'node:path'; import { fileURLToPath } from 'node:url'; const SCRIPT_PATH = fileURLToPath(import.meta.url); const MAX_CAPTURE_BYTES = 2 * 1024 * 1024; const MAX_REQUEST_BYTES = 2 * 1024 * 1024; const TURN_TIMEOUT_MS = 180_000; const PAID_ACK = 'I_ACKNOWLEDGE_1_CODEX_OFFICIAL_AUTH_CALL'; const DENIAL_REASON = 'Waggle official-auth canary denies every model tool call.'; const WAGGLE_HOOK_EVENTS = [ 'preCompact', 'sessionStart', 'stop', 'userPromptSubmit', ]; const DISABLED_FEATURES = [ 'shell_tool', 'unified_exec', 'apps', 'browser_use', 'browser_use_external', 'browser_use_full_cdp_access', 'computer_use', 'image_generation', 'in_app_browser', 'multi_agent', 'multi_agent_v2', 'goals', 'skill_search', 'tool_suggest', 'workspace_dependencies', 'skill_mcp_dependency_install', 'plugins', 'plugin_sharing', 'remote_plugin', 'mentions_v2', ]; const CONFIG_OVERRIDES = [ 'approval_policy="never"', 'sandbox_mode="read-only"', 'web_search="disabled"', 'orchestrator.skills.enabled=false', 'orchestrator.mcp.enabled=false', 'tools.update_plan.enabled=false', 'tools.experimental_request_user_input.enabled=false', 'project_doc_max_bytes=0', 'history.persistence="none"', 'analytics.enabled=false', ]; const DENIAL_RESPONSE = { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: DENIAL_REASON, }, }; const CANARY_STAGES = new Set([ 'app-server-spawn', 'initialize', 'hooks-list', 'pre-turn-boundary', 'thread-start', 'turn-start', 'turn-completed', 'event-audit', 'app-server-close', 'post-turn-invariants', ]); const EVENT_AUDIT_FAILURE_INVARIANTS = new Set([ 'failure-notification', 'wrong-scope', 'model-reroute', 'unknown-notification-method', 'unknown-item-type', 'forbidden-tool-item', 'agent-message-cardinality', ]); const EVENT_AUDIT_FAILURE_KEYS = [ 'invariant', 'notificationCount', 'failureEventsObserved', 'wrongScopeEventsObserved', 'rerouteEventsObserved', 'unknownNotificationEventsObserved', 'unknownItemEventsObserved', 'toolEventsObserved', 'notificationGraphSha256', 'rejectedMethodSha256', 'rejectedItemTypeSha256', ]; const FAIL_CLOSED_PROJECT_TRUST_WARNING = 'Project-local config, hooks, and exec policies are disabled in the following folders until the project is trusted, but skills still load.'; const FAILURE_NOTIFICATION_METHODS = new Set([ 'error', 'warning', 'guardianWarning', 'configWarning', 'deprecationNotice', ]); const ALLOWED_NOTIFICATION_METHODS = new Set([ 'account/updated', 'account/rateLimits/updated', 'hook/completed', 'hook/started', 'item/agentMessage/delta', 'item/completed', 'item/plan/delta', 'item/reasoning/summaryPartAdded', 'item/reasoning/summaryTextDelta', 'item/reasoning/textDelta', 'item/started', 'item/updated', 'mcpServer/startupStatus/updated', 'model/safetyBuffering/updated', 'model/verification', 'remoteControl/status/changed', 'serverRequest/resolved', 'thread/name/updated', 'thread/settings/updated', 'thread/started', 'thread/status/changed', 'thread/tokenUsage/updated', 'turn/completed', 'turn/diff/updated', 'turn/moderationMetadata', 'turn/plan/updated', 'turn/started', ]); const ALLOWED_ITEM_TYPES = new Set([ 'agentMessage', 'reasoning', 'userMessage', ]); const TERMINAL_TURN_FAILURE_STATUSES = new Set(['failed', 'interrupted']); const SIMPLE_CODEX_ERROR_CODES = new Set([ 'badRequest', 'contextWindowExceeded', 'cyberPolicy', 'internalServerError', 'other', 'sandboxError', 'serverOverloaded', 'sessionBudgetExceeded', 'threadRollbackFailed', 'unauthorized', 'usageLimitExceeded', ]); const HTTP_CODEX_ERROR_CODES = new Set([ 'httpConnectionFailed', 'responseStreamConnectionFailed', 'responseStreamDisconnected', 'responseTooManyFailedAttempts', ]); function assert(condition, message) { if (!condition) throw new Error(message); } function sha256(value) { return createHash('sha256').update(value).digest('hex'); } function sha256File(path) { return sha256(readFileSync(path)); } function canonicalPath(path) { return realpathSync.native(path).toLowerCase(); } function stableValue(value) { if (Array.isArray(value)) return value.map(stableValue); if (value && typeof value === 'object') { return Object.fromEntries( Object.entries(value) .sort(([left], [right]) => left.localeCompare(right)) .map(([key, child]) => [key, stableValue(child)]), ); } return value; } function stableJson(value) { return JSON.stringify(stableValue(value)); } function normalizeCodexErrorInfo(value) { if (value === null || value === undefined) { return { code: null, httpStatusCode: null }; } if (typeof value === 'string') { return SIMPLE_CODEX_ERROR_CODES.has(value) ? { code: value, httpStatusCode: null } : { code: null, httpStatusCode: null }; } if (typeof value !== 'object' || Array.isArray(value)) { return { code: null, httpStatusCode: null }; } const keys = Object.keys(value); if (keys.length !== 1) return { code: null, httpStatusCode: null }; const code = keys[0]; const details = value[code]; if (code === 'activeTurnNotSteerable') { return details !== null && typeof details === 'object' && !Array.isArray(details) && Object.keys(details).length === 1 && ['review', 'compact'].includes(details.turnKind) ? { code, httpStatusCode: null } : { code: null, httpStatusCode: null }; } if (!HTTP_CODEX_ERROR_CODES.has(code) || details === null || typeof details !== 'object' || Array.isArray(details) || !Object.keys(details).every((key) => key === 'httpStatusCode')) { return { code: null, httpStatusCode: null }; } const httpStatusCode = details.httpStatusCode ?? null; if (httpStatusCode !== null && (!Number.isInteger(httpStatusCode) || httpStatusCode < 0 || httpStatusCode > 65_535)) { return { code: null, httpStatusCode: null }; } return { code, httpStatusCode }; } function sanitizeTurnFailure(turn, errorNotification = null) { const status = TERMINAL_TURN_FAILURE_STATUSES.has(turn?.status) ? turn.status : null; const notificationParams = errorNotification?.message?.params ?? null; const turnInfo = normalizeCodexErrorInfo(turn?.error?.codexErrorInfo); const notificationInfo = errorNotification?.message?.method === 'error' ? normalizeCodexErrorInfo(notificationParams?.error?.codexErrorInfo) : { code: null, httpStatusCode: null }; // The final turn is authoritative. A disagreeing or malformed notification is // reduced to the already-sanitized turn result rather than becoming a new error. const selected = turnInfo.code === null ? notificationInfo : turnInfo; const willRetry = notificationParams?.willRetry ?? null; return { status, code: selected.code, httpStatusCode: selected.httpStatusCode, willRetry: typeof willRetry === 'boolean' ? willRetry : null, }; } function sanitizeProtocolCode(value) { return Number.isInteger(value) && value >= -2_147_483_648 && value <= 2_147_483_647 ? value : null; } function sanitizeCanaryStage(value) { return CANARY_STAGES.has(value) ? value : null; } function groupSha256(values) { const counts = new Map(); for (const value of values) { if (typeof value !== 'string') continue; const digest = sha256(value); counts.set(digest, (counts.get(digest) ?? 0) + 1); } return [...counts.entries()] .map(([digest, count]) => ({ sha256: digest, count })) .sort((left, right) => left.sha256.localeCompare(right.sha256)); } function sanitizeSha256Counts(value) { if (!Array.isArray(value) || value.length > 256) return null; let previous = null; const sanitized = []; for (const entry of value) { if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) return null; if (Object.keys(entry).sort().join(',') !== 'count,sha256') return null; if (!/^[0-9a-f]{64}$/.test(entry.sha256)) return null; if (!Number.isInteger(entry.count) || entry.count < 1 || entry.count > 2_147_483_647) return null; if (previous !== null && previous >= entry.sha256) return null; previous = entry.sha256; sanitized.push({ sha256: entry.sha256, count: entry.count }); } return sanitized; } function sanitizeEventAuditFailure(value) { if (value === null || typeof value !== 'object' || Array.isArray(value)) return null; if (Object.keys(value).sort().join(',') !== [...EVENT_AUDIT_FAILURE_KEYS].sort().join(',')) return null; if (!EVENT_AUDIT_FAILURE_INVARIANTS.has(value.invariant)) return null; const countKeys = [ 'notificationCount', 'failureEventsObserved', 'wrongScopeEventsObserved', 'rerouteEventsObserved', 'unknownNotificationEventsObserved', 'unknownItemEventsObserved', 'toolEventsObserved', ]; for (const key of countKeys) { if (!Number.isInteger(value[key]) || value[key] < 0 || value[key] > 2_147_483_647) return null; } if (!/^[0-9a-f]{64}$/.test(value.notificationGraphSha256)) return null; const rejectedMethodSha256 = sanitizeSha256Counts(value.rejectedMethodSha256); const rejectedItemTypeSha256 = sanitizeSha256Counts(value.rejectedItemTypeSha256); if (rejectedMethodSha256 === null || rejectedItemTypeSha256 === null) return null; return { invariant: value.invariant, notificationCount: value.notificationCount, failureEventsObserved: value.failureEventsObserved, wrongScopeEventsObserved: value.wrongScopeEventsObserved, rerouteEventsObserved: value.rerouteEventsObserved, unknownNotificationEventsObserved: value.unknownNotificationEventsObserved, unknownItemEventsObserved: value.unknownItemEventsObserved, toolEventsObserved: value.toolEventsObserved, notificationGraphSha256: value.notificationGraphSha256, rejectedMethodSha256, rejectedItemTypeSha256, }; } function selectLifecycleFailure(primaryError, closeError) { return primaryError ?? closeError ?? null; } function proofStateSatisfied(proof, setupOnly) { if (proof === null || typeof proof !== 'object' || Array.isArray(proof)) return false; return setupOnly ? proof.executed === false && proof.paidCalls === 0 && proof.pass === null : proof.executed === true && proof.paidCalls === 0 && proof.pass === true; } function parseArgs(argv) { const values = {}; const booleanFlags = new Set(['--execute-paid', '--self-test', '--setup-only']); const valueFlags = new Set([ '--ack', '--codex-exe', '--expected-head', '--hive-mind-cli', '--marker', '--model', '--receipt-dir', '--windows-powershell', '--workspace', ]); for (let index = 0; index < argv.length; index += 1) { const flag = argv[index]; if (booleanFlags.has(flag)) { assert(values[flag.slice(2)] === undefined, `duplicate argument: ${flag}`); values[flag.slice(2)] = true; continue; } assert(valueFlags.has(flag), `unsupported argument: ${flag}`); assert(values[flag.slice(2)] === undefined, `duplicate argument: ${flag}`); const value = argv[index + 1]; assert(value && !value.startsWith('--'), `missing value for ${flag}`); values[flag.slice(2)] = value; index += 1; } if (values['self-test']) { assert(Object.keys(values).length === 1, '--self-test cannot be combined with other arguments'); return values; } for (const name of [ 'codex-exe', 'expected-head', 'hive-mind-cli', 'model', 'receipt-dir', 'windows-powershell', 'workspace', ]) { assert(typeof values[name] === 'string', `--${name} is required`); } assert(/^[0-9a-f]{40}$/.test(values['expected-head']), '--expected-head must be a lowercase 40-character Git object id'); assert(values.model.length <= 128 && !/[\x00-\x1f\x7f]/.test(values.model), '--model is invalid'); assert(!(values['execute-paid'] && values['setup-only']), '--setup-only and --execute-paid are mutually exclusive'); if (values['execute-paid']) { assert(typeof values.marker === 'string', '--marker is required with --execute-paid'); assert(values.ack === PAID_ACK, `--ack must be exactly ${PAID_ACK}`); assert(values.marker.length <= 256 && !/[\x00-\x1f\x7f]/.test(values.marker), '--marker is invalid'); } else { assert(values.marker === undefined && values.ack === undefined, '--marker/--ack require --execute-paid'); } return values; } function requireRegularFile(path, label) { assert(existsSync(path), `${label} is missing`); const posture = lstatSync(path); assert(posture.isFile() && !posture.isSymbolicLink(), `${label} must be a regular file`); } function requireDirectory(path, label) { assert(existsSync(path), `${label} is missing`); const posture = lstatSync(path); assert(posture.isDirectory() && !posture.isSymbolicLink(), `${label} must be a regular directory`); } function assertNoExistingReparsePoint(path) { let cursor = resolve(path); while (cursor) { if (existsSync(cursor)) { const posture = lstatSync(cursor); assert(!posture.isSymbolicLink(), 'receipt ancestor must not be a symbolic link'); } const parent = dirname(cursor); if (parent === cursor) break; cursor = parent; } } function runChecked(command, args, options = {}) { const result = spawnSync(command, args, { cwd: options.cwd, env: options.env ?? process.env, encoding: 'utf8', timeout: options.timeoutMs ?? 30_000, maxBuffer: MAX_CAPTURE_BYTES, windowsHide: true, shell: false, }); if (result.status !== 0 || result.signal !== null || result.error) { const code = typeof result.error?.code === 'string' ? result.error.code : null; throw new Error(`${options.label ?? basename(command)} failed (status=${String(result.status)}, code=${String(code)})`); } return { stdout: String(result.stdout ?? ''), stderr: String(result.stderr ?? ''), }; } function verifySourceSnapshot(expectedHead) { const repositoryRoot = resolve(dirname(SCRIPT_PATH), '..'); const relativePath = relative(repositoryRoot, SCRIPT_PATH).replaceAll('\\', '/'); assert(relativePath.length > 0 && !relativePath.startsWith('../') && !isAbsolute(relativePath), 'helper must be inside the repository'); const git = (args, label) => runChecked('git.exe', ['-C', repositoryRoot, ...args], { label, timeoutMs: 30_000, }).stdout.trim(); const observedHead = git(['rev-parse', 'HEAD'], 'repository HEAD'); const tree = git(['rev-parse', 'HEAD^{tree}'], 'repository tree'); const trackedStatus = git(['status', '--porcelain=v1', '--untracked-files=no'], 'tracked repository status'); assert(observedHead === expectedHead, `expected HEAD ${expectedHead} but observed ${observedHead}`); assert(trackedStatus.length === 0, 'tracked repository state must be clean'); const trackedPath = git(['ls-files', '--error-unmatch', '--', relativePath], 'helper tracked-file lookup'); assert(trackedPath === relativePath, 'helper must be tracked at expected HEAD'); const scriptBlob = git(['rev-parse', `${expectedHead}:${relativePath}`], 'helper expected blob'); const workingBlob = git(['hash-object', `--path=${relativePath}`, SCRIPT_PATH], 'helper working blob'); assert(/^[0-9a-f]{40}$/.test(tree), 'repository tree object id is invalid'); assert(/^[0-9a-f]{40}$/.test(scriptBlob), 'helper expected blob object id is invalid'); assert(workingBlob === scriptBlob, 'helper working content differs from expected HEAD'); return { expectedHead, observedHead, tree, scriptBlob, trackedClean: true, }; } function appendBounded(chunks, chunk) { const used = chunks.reduce((sum, item) => sum + item.length, 0); if (used >= MAX_CAPTURE_BYTES) return; chunks.push(Buffer.from(chunk).subarray(0, MAX_CAPTURE_BYTES - used)); } function safeError(error, replacements = []) { let value = error instanceof Error ? error.message : String(error); for (const replacement of replacements) { if (replacement) value = value.replaceAll(replacement, ''); } return value .replace(/\b(?:sk|sess)-[A-Za-z0-9_-]{12,}\b/g, '') .slice(0, 500); } function waitForValue(read, timeoutMs, label) { const deadline = Date.now() + timeoutMs; return new Promise((resolveWait, rejectWait) => { const poll = () => { const value = read(); if (value !== undefined) { resolveWait(value); return; } if (Date.now() >= deadline) { rejectWait(new Error(`${label} timed out`)); return; } setTimeout(poll, 20); }; poll(); }); } function tomlString(value) { return JSON.stringify(value); } function powershellLiteral(value) { return `'${value.replaceAll("'", "''")}'`; } function verifyWindowsPowerShell(powerShell) { requireRegularFile(powerShell, 'Windows PowerShell'); const systemDirectory = runChecked(powerShell, [ '-NoLogo', '-NoProfile', '-NonInteractive', '-Command', '[Console]::Out.Write([Environment]::SystemDirectory)', ], { label: 'Windows system-directory probe', timeoutMs: 20_000, }).stdout.trim(); assert(isAbsolute(systemDirectory), 'Windows system-directory probe returned a non-absolute path'); assert(basename(systemDirectory).toLowerCase() === 'system32', 'Windows system-directory probe did not return System32'); const probed = join(systemDirectory, 'WindowsPowerShell', 'v1.0', 'powershell.exe'); const packageCommandPath = join( dirname(systemDirectory), 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe', ); requireRegularFile(probed, 'OS Windows PowerShell'); requireRegularFile(packageCommandPath, 'package-command Windows PowerShell'); assert(canonicalPath(powerShell) === canonicalPath(probed), 'Windows PowerShell is not under the OS system directory'); assert(canonicalPath(packageCommandPath) === canonicalPath(probed), 'package-command Windows PowerShell is not the probed OS binary'); return { path: packageCommandPath, sha256: sha256File(packageCommandPath), systemDirectorySha256: sha256(canonicalPath(systemDirectory)), }; } function buildExpectedWaggleHookCommand(powerShell, scriptPath, cliPath) { const script = [ "$ErrorActionPreference = 'Stop'", 'try {', ` & ${[ process.execPath, scriptPath, '--cli-path', cliPath, ].map(powershellLiteral).join(' ')}`, ' if ($null -eq $LASTEXITCODE) { exit 1 }', ' exit $LASTEXITCODE', '} catch {', ' [Console]::Error.WriteLine($_.Exception.Message)', ' exit 1', '}', ].join('\r\n'); const encoded = Buffer.from(script, 'utf16le').toString('base64'); return `${powerShell} -NoLogo -NoProfile -NonInteractive -EncodedCommand ${encoded}`; } function buildExpectedWaggleHooks(powerShell, cliPath) { const repoRoot = resolve(dirname(SCRIPT_PATH), '..'); const packageRoot = join(repoRoot, 'packages', 'hive-mind-hooks-codex', 'dist', 'hooks'); const scripts = new Map([ ['preCompact', join(packageRoot, 'pre-compact.js')], ['sessionStart', join(packageRoot, 'session-start.js')], ['stop', join(packageRoot, 'stop.js')], ['userPromptSubmit', join(packageRoot, 'user-prompt-submit.js')], ]); const commands = new Map(); const artifacts = []; requireRegularFile(cliPath, 'hive-mind CLI'); artifacts.push({ kind: 'hive-mind-cli', sha256: sha256File(cliPath) }); for (const [eventName, scriptPath] of scripts) { requireRegularFile(scriptPath, `${eventName} packaged hook`); const command = buildExpectedWaggleHookCommand(powerShell, scriptPath, cliPath); commands.set(eventName, sha256(command)); artifacts.push({ eventName, sha256: sha256File(scriptPath), commandSha256: sha256(command) }); } return { commands, artifactsSha256: sha256(stableJson(artifacts)), cliSha256: sha256File(cliPath), }; } function buildDenyHookConfig(powerShell) { const payload = JSON.stringify(DENIAL_RESPONSE); const encoded = Buffer.from(`[Console]::Out.Write('${payload.replaceAll("'", "''")}')`, 'utf16le') .toString('base64'); const command = `${powerShell} -NoLogo -NoProfile -NonInteractive -EncodedCommand ${encoded}`; assert(!command.includes("'"), 'deny hook command cannot contain a TOML literal quote'); return { command, config: `hooks.PreToolUse=[{matcher='*',hooks=[{type='command',command='false',command_windows='${command}',timeout=5}]}]`, commandSha256: sha256(command), }; } function buildChildEnvironment(tempRoot) { const environment = { ...process.env }; for (const name of [ 'ANTHROPIC_API_KEY', 'CODEX_ACCESS_TOKEN', 'OPENAI_ACCESS_TOKEN', 'OPENAI_API_BASE', 'OPENAI_API_KEY', 'OPENAI_BASE_URL', 'OPENROUTER_API_KEY', ]) { environment[name] = ''; } environment.NO_COLOR = '1'; environment.HIVE_MIND_DATA_DIR = join(tempRoot, 'mind'); environment.HIVE_MIND_EMBEDDING_PROVIDER = 'mock'; environment.HIVE_MIND_NO_SYNTH = '1'; environment.HIVE_MIND_SCOPES = 'memory:read,memory:write'; environment.HIVE_MIND_SHIM_LOG_LEVEL = 'error'; environment.WAGGLE_SIGNAL_EMIT = '0'; environment.WAGGLE_WORKSPACE_ID = `codex-denial-${randomBytes(12).toString('hex')}`; environment.TEMP = join(tempRoot, 'process-temp'); environment.TMP = environment.TEMP; return environment; } function mutateModel(base, sealed) { const model = structuredClone(base); model.apply_patch_tool_type = null; model.input_modalities = sealed ? ['text'] : ['text', 'image']; model.supports_image_detail_original = !sealed; model.supports_search_tool = false; model.experimental_supported_tools = []; model.use_responses_lite = false; return model; } function readBundledModel(codexExecutable, model) { const raw = runChecked(codexExecutable, ['debug', 'models', '--bundled'], { label: 'Codex bundled-model catalog', timeoutMs: 30_000, }).stdout; let catalog; try { catalog = JSON.parse(raw); } catch { throw new Error('Codex bundled-model catalog was not valid JSON'); } assert(Array.isArray(catalog?.models), 'Codex bundled-model catalog omitted models'); const selected = catalog.models.find((entry) => entry?.slug === model); assert(selected && typeof selected === 'object', `Codex bundled catalog omitted model ${model}`); return { selected, bundledSha256: sha256(raw) }; } function writeCatalog(path, model) { const value = `${JSON.stringify({ models: [model] })}\n`; return writeFile(path, value, 'utf8').then(() => sha256(value)); } function buildMcpInventoryArguments() { const args = []; for (const feature of DISABLED_FEATURES) args.push('--disable', feature); return [...args, 'mcp', 'list', '--json']; } function readConfiguredMcpServerNames(codexExecutable, env) { const raw = runChecked(codexExecutable, buildMcpInventoryArguments(), { env, label: 'Codex MCP server inventory', timeoutMs: 30_000, }).stdout; let value; try { value = JSON.parse(raw); } catch { throw new Error('Codex MCP server inventory was not valid JSON'); } assert(Array.isArray(value), 'Codex MCP server inventory was not an array'); const names = value.map((entry) => entry?.name); assert(names.every((name) => typeof name === 'string' && name.length > 0), 'Codex MCP server inventory contained an invalid name'); assert(names.every((name) => !/[\u0000\r\n']/.test(name)), 'Codex MCP server name contains an unsupported character'); assert(new Set(names).size === names.length, 'Codex MCP server inventory contained duplicate names'); return names.sort((left, right) => left.localeCompare(right)); } function captureMcpBoundary(codexExecutable, env, configPath) { requireRegularFile(configPath, 'user Codex config.toml'); const names = readConfiguredMcpServerNames(codexExecutable, env); const namesSha256 = sha256(stableJson(names)); const configSha256 = sha256File(configPath); return { count: names.length, namesSha256, configSha256, boundarySha256: sha256(stableJson({ count: names.length, namesSha256, configSha256 })), }; } function assertSameMcpBoundary(expected, actual, label) { assert( actual.count === expected.count && actual.namesSha256 === expected.namesSha256 && actual.configSha256 === expected.configSha256 && actual.boundarySha256 === expected.boundarySha256, `Codex MCP/config boundary changed ${label}`, ); } function responseEvent(event) { return `event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`; } function completedUsage() { return { input_tokens: 0, input_tokens_details: null, output_tokens: 0, output_tokens_details: null, total_tokens: 0, }; } function messageResponse(text) { const responseId = `resp_${randomBytes(8).toString('hex')}`; const itemId = `msg_${randomBytes(8).toString('hex')}`; const item = { type: 'message', role: 'assistant', id: itemId, status: 'completed', content: [{ type: 'output_text', text }], }; return [ responseEvent({ type: 'response.created', response: { id: responseId, status: 'in_progress' } }), responseEvent({ type: 'response.output_item.done', output_index: 0, item }), responseEvent({ type: 'response.completed', response: { id: responseId, status: 'completed', output: [item], usage: completedUsage() }, }), ].join(''); } function functionCallResponse(name, argumentsJson) { const responseId = `resp_${randomBytes(8).toString('hex')}`; const itemId = `fc_${randomBytes(8).toString('hex')}`; const callId = `call_${randomBytes(8).toString('hex')}`; const item = { type: 'function_call', id: itemId, call_id: callId, name, arguments: argumentsJson, status: 'completed', }; return [ responseEvent({ type: 'response.created', response: { id: responseId, status: 'in_progress' } }), responseEvent({ type: 'response.output_item.added', output_index: 0, item: { ...item, arguments: '', status: 'in_progress' } }), responseEvent({ type: 'response.function_call_arguments.done', item_id: itemId, output_index: 0, arguments: argumentsJson }), responseEvent({ type: 'response.output_item.done', output_index: 0, item }), responseEvent({ type: 'response.completed', response: { id: responseId, status: 'completed', output: [item], usage: completedUsage() }, }), ].join(''); } function toolNamesFromRequest(body) { if (!Array.isArray(body?.tools)) return []; return body.tools.map((tool) => tool?.name ?? tool?.function?.name ?? null); } function additionalToolsFromRequest(body) { const values = []; const visit = (value, insideInput = false) => { if (Array.isArray(value)) { value.forEach((child) => visit(child, insideInput)); return; } if (!value || typeof value !== 'object') return; for (const [key, child] of Object.entries(value)) { if (insideInput && key === 'additional_tools') { values.push(...(Array.isArray(child) ? child : [child])); } visit(child, insideInput || key === 'input'); } }; visit(body, false); return values; } function requestContainsImage(body) { let found = false; const visit = (value) => { if (found) return; if (typeof value === 'string') { if (value.includes('data:image')) found = true; return; } if (Array.isArray(value)) { value.forEach(visit); return; } if (!value || typeof value !== 'object') return; if (value.type === 'input_image') { found = true; return; } Object.values(value).forEach(visit); }; visit(body); return found; } async function startLoopbackProvider(mode, expectedAcknowledgement, sentinelPath) { const records = []; const server = createServer((request, response) => { const chunks = []; let bytes = 0; request.on('data', (chunk) => { bytes += chunk.length; if (bytes <= MAX_REQUEST_BYTES) chunks.push(Buffer.from(chunk)); }); request.on('end', () => { const raw = Buffer.concat(chunks).toString('utf8'); let body = null; try { body = JSON.parse(raw); } catch { /* rejected below */ } const record = { index: records.length, method: request.method ?? null, endpointOk: request.url === '/v1/responses', bytes, sha256: sha256(raw), topLevelToolsIsArray: Array.isArray(body?.tools), toolNames: body ? toolNamesFromRequest(body) : [], additionalToolCount: body ? additionalToolsFromRequest(body).length : -1, containsImage: body ? requestContainsImage(body) : false, }; records.push(record); if ( request.method !== 'POST' || !record.endpointOk || body === null || bytes > MAX_REQUEST_BYTES ) { response.writeHead(400, { 'content-type': 'application/json' }); response.end(JSON.stringify({ error: { message: 'Waggle loopback policy rejected request' } })); return; } let payload; if (mode === 'sealed') { payload = records.length === 1 ? messageResponse(expectedAcknowledgement) : null; } else if (records.length === 1) { payload = functionCallResponse('view_image', JSON.stringify({ path: sentinelPath })); } else if (records.length === 2) { payload = messageResponse(expectedAcknowledgement); } if (payload === null || payload === undefined) { response.writeHead(400, { 'content-type': 'application/json' }); response.end(JSON.stringify({ error: { message: 'Waggle loopback request count exceeded policy' } })); return; } response.writeHead(200, { 'cache-control': 'no-cache', 'content-type': 'text/event-stream; charset=utf-8', }); response.end(payload); }); }); await new Promise((resolveListen, rejectListen) => { server.once('error', rejectListen); server.listen(0, '127.0.0.1', resolveListen); }); const address = server.address(); assert(address && typeof address !== 'string', 'loopback provider did not expose an address'); return { baseUrl: `http://127.0.0.1:${address.port}/v1`, records, close: () => new Promise((resolveClose, rejectClose) => { server.close((error) => error ? rejectClose(error) : resolveClose()); }), }; } function buildInvocationArguments(options) { const args = ['app-server', '--stdio', '--strict-config']; for (const feature of DISABLED_FEATURES) args.push('--disable', feature); assert(Array.isArray(options.mcpServerNames), 'MCP server inventory is missing'); const mcpServerEntries = options.mcpServerNames.map((name) => { assert(!/[\u0000\r\n']/.test(name), 'MCP server name contains an unsupported character'); return `'${name}'={enabled=false}`; }); const hookStateEntries = []; const hookStateKeys = new Set(); for (const key of options.hookDisables ?? []) { assert(!/[\u0000\r\n']/.test(key), 'disabled hook key contains an unsupported character'); assert(!hookStateKeys.has(key), 'duplicate hook-state key'); hookStateKeys.add(key); hookStateEntries.push(`'${key}'={enabled=false}`); } for (const { key, currentHash } of options.hookPins) { assert(!/[\u0000\r\n']/.test(key), 'hook key contains an unsupported character'); assert(!hookStateKeys.has(key), 'duplicate hook-state key'); assert(/^sha256:[0-9a-f]{64}$/.test(currentHash), 'hook currentHash is invalid'); hookStateKeys.add(key); hookStateEntries.push(`'${key}'={enabled=true,trusted_hash='${currentHash}'}`); } const overrides = [ ...CONFIG_OVERRIDES, 'model_reasoning_effort="low"', `mcp_servers={${mcpServerEntries.join(',')}}`, `model_catalog_json=${tomlString(options.catalogPath)}`, ...(options.providerBaseUrl ? [ 'model_provider="waggle_loopback"', `model_providers.waggle_loopback.name="Waggle zero-cost loopback"`, `model_providers.waggle_loopback.base_url=${tomlString(options.providerBaseUrl)}`, 'model_providers.waggle_loopback.wire_api="responses"', 'model_providers.waggle_loopback.request_max_retries=0', 'model_providers.waggle_loopback.stream_max_retries=0', 'model_providers.waggle_loopback.requires_openai_auth=false', 'model_providers.waggle_loopback.supports_websockets=false', ] : [ 'model_provider="openai"', ]), ...(options.includeDeny ? [options.denyConfig] : []), ...(hookStateEntries.length > 0 ? [`hooks.state={${hookStateEntries.join(',')}}`] : []), ]; for (const override of overrides) args.push('-c', override); return args; } function normalizedArguments(args, sensitiveValues) { return args.map((argument) => { if (argument.startsWith('hooks.state={')) { return `hooks.state=`; } if (argument.startsWith('mcp_servers={')) { return `mcp_servers=`; } let normalized = argument; for (const [value, label] of sensitiveValues) { if (value) normalized = normalized.replaceAll(value, label); } return normalized; }); } class AppServerClient { constructor(executable, args, cwd, env, replacements) { this.responses = new Map(); this.notifications = []; this.stdoutChunks = []; this.stderrChunks = []; this.buffer = ''; this.sequence = 0; this.closed = false; this.error = null; this.replacements = replacements; this.child = spawn(executable, args, { cwd, env, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true, shell: false, }); this.child.stdout.on('data', (chunk) => { appendBounded(this.stdoutChunks, chunk); this.buffer += chunk.toString('utf8'); while (this.buffer.includes('\n')) { const newline = this.buffer.indexOf('\n'); const line = this.buffer.slice(0, newline).trim(); this.buffer = this.buffer.slice(newline + 1); this.processLine(line); } }); this.child.stderr.on('data', (chunk) => appendBounded(this.stderrChunks, chunk)); this.child.on('error', (error) => { this.error = safeError(error, replacements); }); this.child.on('close', () => { this.closed = true; this.processLine(this.buffer.trim()); this.buffer = ''; }); } processLine(line) { if (!line) return; let message; try { message = JSON.parse(line); } catch { this.error = this.error ?? 'Codex app-server emitted malformed protocol output'; return; } this.sequence += 1; if (message?.id !== undefined && typeof message?.method !== 'string') { this.responses.set(String(message.id), message); } else if (typeof message?.method === 'string') { this.notifications.push({ sequence: this.sequence, message }); } } send(message) { assert(this.child.stdin.writable, 'Codex app-server stdin is closed'); this.child.stdin.write(`${JSON.stringify(message)}\n`); } response(id, timeoutMs = 20_000) { return waitForValue( () => this.responses.get(String(id)), timeoutMs, `Codex app-server response ${id}`, ); } notification(method, predicate = () => true, timeoutMs = TURN_TIMEOUT_MS) { return waitForValue( () => this.notifications.find((entry) => ( entry.message?.method === method && predicate(entry) )), timeoutMs, `Codex app-server notification ${method}`, ); } async initialize(clientName) { this.send({ method: 'initialize', id: 1, params: { clientInfo: { name: clientName, title: clientName, version: '1.0.0' }, capabilities: { experimentalApi: true }, }, }); const response = await this.response(1); assertProtocolSuccess(response, 'Codex app-server initialize failed'); this.send({ method: 'initialized', params: {} }); } async close() { try { this.child.stdin.end(); } catch { /* already closed */ } try { await waitForValue(() => this.closed ? true : undefined, 5_000, 'Codex app-server close'); } catch { if (this.child.pid) { spawnSync('taskkill.exe', ['/PID', String(this.child.pid), '/T', '/F'], { encoding: 'utf8', timeout: 10_000, windowsHide: true, }); } try { this.child.kill('SIGKILL'); } catch { /* already closed */ } await waitForValue(() => this.closed ? true : undefined, 5_000, 'forced Codex app-server close'); } } transcriptDigest() { return { stdoutBytes: Buffer.concat(this.stdoutChunks).length, stdoutSha256: sha256(Buffer.concat(this.stdoutChunks)), stderrBytes: Buffer.concat(this.stderrChunks).length, stderrSha256: sha256(Buffer.concat(this.stderrChunks)), }; } } function assertProtocolSuccess(response, message) { if (!response?.error) return; const error = new Error(message); error.protocolCode = sanitizeProtocolCode(response.error?.code); throw error; } function hookGraphFromEntry(entry) { const hooks = Array.isArray(entry?.hooks) ? entry.hooks : []; return hooks.map((hook) => ({ eventName: hook?.eventName ?? null, sourcePath: hook?.sourcePath ?? null, sourceCanonical: typeof hook?.sourcePath === 'string' && existsSync(hook.sourcePath) ? canonicalPath(hook.sourcePath) : String(hook?.sourcePath ?? '').toLowerCase(), key: hook?.key ?? null, currentHash: hook?.currentHash ?? null, commandSha256: typeof hook?.command === 'string' ? sha256(hook.command) : null, enabled: hook?.enabled === true, trustStatus: hook?.trustStatus ?? null, })); } function assertExactHookGraph( entry, codexHome, includeDeny, requireTrusted, expectedDenyCommandSha256, expectedWaggleCommands, allowActiveExtras, ) { const graph = hookGraphFromEntry(entry); const expectedCount = WAGGLE_HOOK_EVENTS.length + (includeDeny ? 1 : 0); const warnings = Array.isArray(entry?.warnings) ? entry.warnings.length : -1; const errors = Array.isArray(entry?.errors) ? entry.errors.length : -1; assert(warnings === 0, `hooks/list reported ${warnings} warnings`); assert(errors === 0, `hooks/list reported ${errors} errors`); const waggleSource = canonicalPath(join(codexHome, 'hooks.json')); assert(expectedWaggleCommands instanceof Map, 'expected Waggle hook commands are missing'); const waggle = graph.filter((hook) => ( hook.sourceCanonical === waggleSource && hook.commandSha256 === expectedWaggleCommands.get(hook.eventName) )); const deny = graph.filter((hook) => ( hook.eventName === 'preToolUse' && hook.sourceCanonical === 'c:\\\\config.toml' && hook.commandSha256 === expectedDenyCommandSha256 )); const waggleSourceMatches = graph.filter((hook) => hook.sourceCanonical === waggleSource).length; const waggleCommandMatches = graph.filter((hook) => ( hook.commandSha256 === expectedWaggleCommands.get(hook.eventName) )).length; assert( waggle.length === 4, `hooks/list returned ${waggle.length} Waggle hooks, expected 4 (sourceMatches=${waggleSourceMatches}, commandMatches=${waggleCommandMatches})`, ); assert( JSON.stringify(waggle.map((hook) => hook.eventName).sort()) === JSON.stringify([...WAGGLE_HOOK_EVENTS].sort()), 'hooks/list returned an unexpected Waggle event set', ); assert(deny.length === (includeDeny ? 1 : 0), 'hooks/list returned an unexpected deny-hook count'); const expected = [...waggle, ...deny]; const expectedKeys = new Set(expected.map((hook) => hook.key)); const extras = graph.filter((hook) => !expectedKeys.has(hook.key)); assert(graph.every((hook) => ( typeof hook.key === 'string' && /^sha256:[0-9a-f]{64}$/.test(hook.currentHash ?? '') && typeof hook.commandSha256 === 'string' )), 'hooks/list returned a malformed hook'); assert(expected.every((hook) => hook.enabled), 'an expected hook is disabled'); if (!allowActiveExtras) { assert(extras.every((hook) => !hook.enabled), 'an unapproved hook remains enabled'); } if (requireTrusted) { assert(expected.every((hook) => hook.trustStatus === 'trusted'), 'an expected hook is not hash-trusted'); } const sanitized = graph.map((hook) => ({ eventName: hook.eventName, sourceKind: waggle.includes(hook) ? 'waggle-hooks-json' : deny.includes(hook) ? 'session-flags' : 'disabled-external', sourcePathSha256: sha256(hook.sourceCanonical), keySha256: sha256(hook.key), currentHash: hook.currentHash, commandSha256: hook.commandSha256, enabled: hook.enabled, trustStatus: requireTrusted ? hook.trustStatus : 'pre-pin', })).sort((left, right) => stableJson(left).localeCompare(stableJson(right))); const identity = sanitized.map(({ enabled: _enabled, trustStatus: _trustStatus, ...value }) => value); return { graph, expected, extras, sanitized, graphSha256: sha256(stableJson(sanitized)), identitySha256: sha256(stableJson(identity)), warnings, errors, activeCount: graph.filter((hook) => hook.enabled).length, extraCount: extras.filter((hook) => hook.enabled).length, disabledExtraCount: extras.filter((hook) => !hook.enabled).length, denyHookSha256: includeDeny ? deny[0].currentHash.slice('sha256:'.length) : null, }; } async function listHooks( executable, args, cwd, env, codexHome, includeDeny, requireTrusted, expectedDenyCommandSha256, expectedWaggleCommands, allowActiveExtras, replacements, ) { const client = new AppServerClient(executable, args, cwd, env, replacements); try { await client.initialize('waggle_codex_tool_denial_discovery'); client.send({ method: 'hooks/list', id: 2, params: { cwds: [cwd] } }); const response = await client.response(2); assert(!response.error, 'Codex app-server hooks/list failed'); const entry = response.result?.data?.[0]; const result = assertExactHookGraph( entry, codexHome, includeDeny, requireTrusted, expectedDenyCommandSha256, expectedWaggleCommands, allowActiveExtras, ); assert(client.error === null, client.error ?? 'Codex hook discovery failed'); return { ...result, transcript: client.transcriptDigest() }; } finally { await client.close(); } } function itemType(notification) { return notification?.message?.params?.item?.type ?? null; } function treeContains(value, needle) { if (typeof value === 'string') return value.includes(needle); if (Array.isArray(value)) return value.some((child) => treeContains(child, needle)); if (value && typeof value === 'object') return Object.values(value).some((child) => treeContains(child, needle)); return false; } function notificationScope(notification) { const params = notification?.message?.params ?? {}; return { threadIds: [params.threadId, params.thread?.id, params.turn?.threadId] .filter((value) => typeof value === 'string'), turnIds: [params.turnId, params.turn?.id, params.item?.turnId] .filter((value) => typeof value === 'string'), }; } function isFailClosedProjectTrustWarning(notification) { if (notification?.message?.method !== 'configWarning') return false; const params = notification.message?.params; if (!params || typeof params !== 'object' || Array.isArray(params)) return false; const allowedKeys = new Set(['details', 'path', 'range', 'summary']); const keys = Object.keys(params); if (!keys.includes('summary')) return false; if (!keys.every((key) => allowedKeys.has(key))) return false; if (keys.includes('details') && params.details !== null) return false; if (keys.includes('path') && params.path !== null) return false; if (keys.includes('range') && params.range !== null) return false; if (typeof params.summary !== 'string') return false; const lines = params.summary.split('\n'); if (lines.at(-1) !== '') return false; if (lines[0] !== FAIL_CLOSED_PROJECT_TRUST_WARNING) return false; const warningLines = lines.slice(1, -1); if (warningLines.length < 2 || warningLines.length % 2 !== 0) return false; const folderPattern = /^ ([1-9][0-9]*)\. ([A-Za-z]:[\\/].+[\\/]\.codex)$/; const instructionPattern = /^ To load project-local config, hooks, and exec policies, add ([A-Za-z]:[\\/].+) as a trusted project in ([A-Za-z]:[\\/].+[\\/]\.codex[\\/]config\.toml)\.$/; const normalizePath = (value) => value.replaceAll('/', '\\').replace(/\\+$/, '').toLowerCase(); for (let index = 0; index < warningLines.length; index += 2) { const folderMatch = folderPattern.exec(warningLines[index]); const instructionMatch = instructionPattern.exec(warningLines[index + 1]); if (!folderMatch || !instructionMatch) return false; if (Number(folderMatch[1]) !== (index / 2) + 1) return false; if (normalizePath(folderMatch[2]) !== `${normalizePath(instructionMatch[1])}\\.codex`) { return false; } } return true; } function collectEventAudit(notifications, expectedAcknowledgement, threadId, turnId) { const failureNotifications = notifications.filter((entry) => ( FAILURE_NOTIFICATION_METHODS.has(entry.message?.method) && !isFailClosedProjectTrustWarning(entry) )); const unknownNotifications = notifications.filter((entry) => ( !ALLOWED_NOTIFICATION_METHODS.has(entry.message?.method) && !FAILURE_NOTIFICATION_METHODS.has(entry.message?.method) )); const wrongScopeNotifications = notifications.filter((entry) => { const scope = notificationScope(entry); return scope.threadIds.some((value) => value !== threadId) || scope.turnIds.some((value) => value !== turnId); }); const rerouteNotifications = notifications.filter((entry) => ( entry.message?.method === 'model/rerouted' )); const itemNotifications = notifications.filter((entry) => ( entry.message?.method === 'item/started' || entry.message?.method === 'item/updated' || entry.message?.method === 'item/completed' )); const unknownItems = itemNotifications.filter((entry) => !ALLOWED_ITEM_TYPES.has(itemType(entry))); const completedMessages = notifications.filter((entry) => ( entry.message?.method === 'item/completed' && itemType(entry) === 'agentMessage' )); const exactMessages = completedMessages.filter((entry) => ( entry.message?.params?.item?.text?.trim() === expectedAcknowledgement )); const imageItems = itemNotifications.filter((entry) => ( String(itemType(entry)).toLowerCase().includes('image') || treeContains(entry.message?.params?.item, 'view_image') )); const denyHooks = notifications.filter((entry) => ( entry.message?.method === 'hook/completed' && entry.message?.params?.run?.eventName === 'preToolUse' && treeContains(entry.message?.params?.run, DENIAL_REASON) )); const forbiddenToolItems = itemNotifications.filter((entry) => { const type = String(itemType(entry) ?? '').toLowerCase(); return [ 'command', 'computer', 'dynamictool', 'filechange', 'imagegeneration', 'mcptool', 'websearch', ].some((family) => type.includes(family)); }); const notificationGraph = notifications.map((entry, index) => { const method = entry.message?.method; const type = itemType(entry); return { ordinal: index + 1, methodSha256: typeof method === 'string' ? sha256(method) : null, itemTypeSha256: typeof type === 'string' ? sha256(type) : null, }; }); return { notificationCount: notifications.length, exactMessageCount: exactMessages.length, normalTextCompleted: completedMessages.length === 1 && exactMessages.length === 1, imageItemCount: imageItems.length, firstImageSequence: imageItems[0]?.sequence ?? null, denyHookCount: denyHooks.length, firstDenySequence: denyHooks[0]?.sequence ?? null, toolEventsObserved: forbiddenToolItems.length, unknownEventsObserved: unknownNotifications.length + unknownItems.length, unknownNotificationEventsObserved: unknownNotifications.length, unknownItemEventsObserved: unknownItems.length, failureEventsObserved: failureNotifications.length, wrongScopeEventsObserved: wrongScopeNotifications.length, rerouteEventsObserved: rerouteNotifications.length, notificationGraphSha256: sha256(stableJson(notificationGraph)), rejectionEvidence: { failureMethodSha256: groupSha256(failureNotifications.map((entry) => entry.message?.method)), wrongScopeMethodSha256: groupSha256(wrongScopeNotifications.map((entry) => entry.message?.method)), rerouteMethodSha256: groupSha256(rerouteNotifications.map((entry) => entry.message?.method)), unknownMethodSha256: groupSha256(unknownNotifications.map((entry) => entry.message?.method)), unknownItemTypeSha256: groupSha256(unknownItems.map((entry) => itemType(entry))), toolMethodSha256: groupSha256(forbiddenToolItems.map((entry) => entry.message?.method)), toolItemTypeSha256: groupSha256(forbiddenToolItems.map((entry) => itemType(entry))), agentMessageMethodSha256: groupSha256(completedMessages.map((entry) => entry.message?.method)), agentMessageItemTypeSha256: groupSha256(completedMessages.map((entry) => itemType(entry))), }, }; } function throwEventAuditFailure(invariant, audit) { const evidence = audit.rejectionEvidence; let rejectedMethodSha256 = []; let rejectedItemTypeSha256 = []; if (invariant === 'failure-notification') rejectedMethodSha256 = evidence.failureMethodSha256; if (invariant === 'wrong-scope') rejectedMethodSha256 = evidence.wrongScopeMethodSha256; if (invariant === 'model-reroute') rejectedMethodSha256 = evidence.rerouteMethodSha256; if (invariant === 'unknown-notification-method') rejectedMethodSha256 = evidence.unknownMethodSha256; if (invariant === 'unknown-item-type') rejectedItemTypeSha256 = evidence.unknownItemTypeSha256; if (invariant === 'forbidden-tool-item') { rejectedMethodSha256 = evidence.toolMethodSha256; rejectedItemTypeSha256 = evidence.toolItemTypeSha256; } if (invariant === 'agent-message-cardinality') { rejectedMethodSha256 = evidence.agentMessageMethodSha256; rejectedItemTypeSha256 = evidence.agentMessageItemTypeSha256; } const error = new Error('Codex event audit rejected a sanitized invariant'); error.eventAuditFailure = sanitizeEventAuditFailure({ invariant, notificationCount: audit.notificationCount, failureEventsObserved: audit.failureEventsObserved, wrongScopeEventsObserved: audit.wrongScopeEventsObserved, rerouteEventsObserved: audit.rerouteEventsObserved, unknownNotificationEventsObserved: audit.unknownNotificationEventsObserved, unknownItemEventsObserved: audit.unknownItemEventsObserved, toolEventsObserved: audit.toolEventsObserved, notificationGraphSha256: audit.notificationGraphSha256, rejectedMethodSha256, rejectedItemTypeSha256, }); throw error; } function assertEventAudit(audit, strictAllowlist, requireAcknowledgement = true) { if (audit.failureEventsObserved !== 0) throwEventAuditFailure('failure-notification', audit); if (audit.wrongScopeEventsObserved !== 0) throwEventAuditFailure('wrong-scope', audit); if (audit.rerouteEventsObserved !== 0) throwEventAuditFailure('model-reroute', audit); if (strictAllowlist) { if (audit.unknownNotificationEventsObserved !== 0) { throwEventAuditFailure('unknown-notification-method', audit); } if (audit.unknownItemEventsObserved !== 0) throwEventAuditFailure('unknown-item-type', audit); if (audit.toolEventsObserved !== 0) throwEventAuditFailure('forbidden-tool-item', audit); } if (requireAcknowledgement) { if (!audit.normalTextCompleted) throwEventAuditFailure('agent-message-cardinality', audit); } } function eventAudit( notifications, expectedAcknowledgement, strictAllowlist, threadId = null, turnId = null, requireAcknowledgement = true, ) { const audit = collectEventAudit( notifications, expectedAcknowledgement, threadId, turnId, ); assertEventAudit(audit, strictAllowlist, requireAcknowledgement); return audit; } async function runTurn(options) { const client = new AppServerClient( options.executable, options.args, options.workspace, options.env, options.replacements, ); let threadId = null; let turnId = null; let result = null; let primaryError = null; let closeError = null; let failureStage = 'app-server-spawn'; let completedStage = 'app-server-spawn'; let modelCalls = 0; let turnStartCalls = 0; let closeClean = false; try { failureStage = 'initialize'; await client.initialize(`waggle_codex_tool_denial_${options.lane}`); completedStage = 'initialize'; failureStage = 'hooks-list'; client.send({ method: 'hooks/list', id: 2, params: { cwds: [options.workspace] } }); const hookResponse = await client.response(2); assertProtocolSuccess(hookResponse, 'Codex app-server hooks/list failed'); const hooks = assertExactHookGraph( hookResponse.result?.data?.[0], options.codexHome, options.includeDeny, true, options.expectedDenyCommandSha256, options.expectedWaggleCommands, false, ); assert(hooks.graphSha256 === options.expectedHookGraphSha256, 'hook graph changed after hash pinning'); completedStage = 'hooks-list'; failureStage = 'pre-turn-boundary'; if (options.beforeTurn) await options.beforeTurn(); completedStage = 'pre-turn-boundary'; const threadParams = { model: options.model, cwd: options.workspace, approvalPolicy: 'never', sandbox: 'read-only', ephemeral: true, allowProviderModelFallback: false, ...(options.useDefaultEnvironmentForControl ? {} : { environments: [] }), selectedCapabilityRoots: [], dynamicTools: [], }; failureStage = 'thread-start'; client.send({ method: 'thread/start', id: 3, params: threadParams }); const threadResponse = await client.response(3); assertProtocolSuccess(threadResponse, 'Codex app-server thread/start failed'); threadId = threadResponse.result?.thread?.id ?? null; assert(typeof threadId === 'string' && threadId.length > 0, 'Codex app-server omitted thread id'); completedStage = 'thread-start'; if (options.setupOnly) { result = { hooks, events: null, threadParamsSha256: sha256(stableJson({ ...threadParams, cwd: '' })), turnParamsSha256: null, threadId, threadIdSha256: sha256(threadId), turnIdSha256: null, transcript: client.transcriptDigest(), modelCalls: 0, turnStartCalls: 0, completedStage: 'thread-start', }; } else { const turnParams = { threadId, input: [{ type: 'text', text: options.prompt, text_elements: [] }], model: options.model, approvalPolicy: 'never', sandboxPolicy: { type: 'readOnly', networkAccess: false }, ...(options.useDefaultEnvironmentForControl ? {} : { environments: [] }), }; failureStage = 'turn-start'; turnStartCalls += 1; client.send({ method: 'turn/start', id: 4, params: turnParams }); const turnResponse = await client.response(4); assertProtocolSuccess(turnResponse, 'Codex app-server turn/start failed'); modelCalls += 1; turnId = turnResponse.result?.turn?.id ?? null; assert(typeof turnId === 'string' && turnId.length > 0, 'Codex app-server omitted turn id'); completedStage = 'turn-start'; failureStage = 'turn-completed'; const completed = await client.notification('turn/completed', (entry) => ( entry.message?.params?.threadId === threadId && entry.message?.params?.turn?.id === turnId )); await new Promise((resolveWait) => setTimeout(resolveWait, 100)); const completedTurn = completed.message?.params?.turn; if (completedTurn?.status !== 'completed') { const errorNotification = [...client.notifications].reverse().find((entry) => ( entry.message?.method === 'error' && entry.message?.params?.threadId === threadId && entry.message?.params?.turnId === turnId )) ?? null; const terminalError = new Error('Codex app-server turn did not complete'); terminalError.turnFailure = sanitizeTurnFailure(completedTurn, errorNotification); throw terminalError; } completedStage = 'turn-completed'; failureStage = 'event-audit'; const events = eventAudit( client.notifications, options.expectedAcknowledgement, options.strictAllowlist, threadId, turnId, ); completedStage = 'event-audit'; failureStage = 'post-turn-invariants'; assert(client.error === null, client.error ?? 'Codex app-server protocol failed'); result = { hooks, events, threadParamsSha256: sha256(stableJson({ ...threadParams, cwd: '' })), turnParamsSha256: sha256(stableJson({ ...turnParams, threadId: '', input: '' })), threadId, threadIdSha256: sha256(threadId), turnIdSha256: sha256(turnId), transcript: client.transcriptDigest(), modelCalls, turnStartCalls, completedStage: 'post-turn-invariants', }; completedStage = 'post-turn-invariants'; } } catch (error) { primaryError = error; } try { await client.close(); closeClean = true; } catch (error) { closeError = error; } if (primaryError === null && closeError === null) { try { failureStage = 'event-audit'; const finalEvents = eventAudit( client.notifications, options.setupOnly ? null : options.expectedAcknowledgement, options.strictAllowlist, threadId, options.setupOnly ? null : turnId, !options.setupOnly, ); assert( client.error === null, client.error ?? 'Codex app-server protocol failed after close', ); result.events = finalEvents; } catch (error) { primaryError = error; } } const lifecycleFailure = selectLifecycleFailure(primaryError, closeError); if (lifecycleFailure !== null) { const wrapped = new Error(`Codex app-server canary failed at ${sanitizeCanaryStage( primaryError === null ? 'app-server-close' : failureStage, ) ?? 'app-server-spawn'}`); wrapped.turnFailure = lifecycleFailure?.turnFailure ?? null; wrapped.eventAuditFailure = sanitizeEventAuditFailure(lifecycleFailure?.eventAuditFailure); wrapped.canaryDiagnostic = { failureStage: sanitizeCanaryStage(primaryError === null ? 'app-server-close' : failureStage), protocolCode: sanitizeProtocolCode(lifecycleFailure?.protocolCode), completedStage: sanitizeCanaryStage(completedStage), modelCalls, turnStartCalls, closeClean, }; throw wrapped; } return { ...result, diagnostic: { failureStage: null, protocolCode: null, completedStage: result.completedStage, modelCalls, turnStartCalls, closeClean, }, }; } async function createSentinelPng(path) { const png = Buffer.from( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64', ); await writeFile(path, png); requireRegularFile(path, 'owned sentinel PNG'); return sha256(png); } async function runValidationSelfTest() { const root = join(tmpdir(), `waggle-codex-denial-self-test-${randomBytes(12).toString('hex')}`); const codexHome = join(root, 'codex-home'); const hooksPath = join(codexHome, 'hooks.json'); let cases = 0; const expectThrow = (run, label) => { let threw = false; try { run(); } catch { threw = true; } assert(threw, `negative fixture was accepted: ${label}`); cases += 1; }; const captureEventAuditFailure = (run, label) => { try { run(); } catch (error) { const diagnostic = sanitizeEventAuditFailure(error?.eventAuditFailure); assert(diagnostic !== null, `event-audit fixture omitted sanitized diagnostics: ${label}`); return diagnostic; } throw new Error(`event-audit fixture was accepted: ${label}`); }; try { assert( stableJson([...CANARY_STAGES]) === stableJson([ 'app-server-spawn', 'initialize', 'hooks-list', 'pre-turn-boundary', 'thread-start', 'turn-start', 'turn-completed', 'event-audit', 'app-server-close', 'post-turn-invariants', ]), 'canary stage fixture changed', ); cases += 1; assert( sanitizeProtocolCode(-2_147_483_648) === -2_147_483_648 && sanitizeProtocolCode(2_147_483_647) === 2_147_483_647, 'signed int32 protocol-code fixture failed', ); cases += 1; assert( sanitizeProtocolCode(-2_147_483_649) === null && sanitizeProtocolCode(2_147_483_648) === null && sanitizeProtocolCode(1.5) === null && sanitizeProtocolCode('1') === null, 'invalid protocol-code fixture was accepted', ); cases += 1; const inventoryArguments = buildMcpInventoryArguments(); assert( inventoryArguments.length === (DISABLED_FEATURES.length * 2) + 3 && JSON.stringify(inventoryArguments.slice(-3)) === JSON.stringify(['mcp', 'list', '--json']) && DISABLED_FEATURES.every((feature, index) => ( inventoryArguments[index * 2] === '--disable' && inventoryArguments[(index * 2) + 1] === feature )), 'MCP inventory did not inherit the app-server feature-disable boundary', ); cases += 1; assert( win32.join('C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe') === 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', 'package-command Windows PowerShell spelling fixture failed', ); await mkdir(codexHome, { recursive: true }); await writeFile(hooksPath, '{}\n', 'utf8'); const expectedCommands = new Map(); const hooks = WAGGLE_HOOK_EVENTS.map((eventName, index) => { const command = `fixture-waggle-command-${index}`; expectedCommands.set(eventName, sha256(command)); return { eventName, sourcePath: hooksPath, key: `fixture-waggle-key-${index}`, currentHash: `sha256:${String(index + 1).padStart(64, '0')}`, command, enabled: true, trustStatus: 'trusted', }; }); const denyCommand = 'fixture-deny-command'; hooks.push({ eventName: 'preToolUse', sourcePath: 'C:\\\\config.toml', key: 'fixture-deny-key', currentHash: `sha256:${'f'.repeat(64)}`, command: denyCommand, enabled: true, trustStatus: 'trusted', }); const validEntry = { hooks, warnings: [], errors: [] }; const valid = assertExactHookGraph( validEntry, codexHome, true, true, sha256(denyCommand), expectedCommands, false, ); assert(valid.graph.length === 5, 'valid hook fixture did not pass'); cases += 1; const withExtra = structuredClone(validEntry); withExtra.hooks.push({ ...withExtra.hooks[0], key: 'extra-key' }); expectThrow(() => assertExactHookGraph( withExtra, codexHome, true, true, sha256(denyCommand), expectedCommands, false, ), 'extra hook'); const activeExternal = structuredClone(validEntry); activeExternal.hooks.push({ ...activeExternal.hooks[0], sourcePath: 'C:\\external\\hooks.json', key: 'external-key', command: 'external-command', }); expectThrow(() => assertExactHookGraph( activeExternal, codexHome, true, true, sha256(denyCommand), expectedCommands, false, ), 'active external hook'); const disabledExternal = structuredClone(activeExternal); disabledExternal.hooks[5].enabled = false; const disabledResult = assertExactHookGraph( disabledExternal, codexHome, true, true, sha256(denyCommand), expectedCommands, false, ); assert(disabledResult.extraCount === 0 && disabledResult.disabledExtraCount === 1, 'disabled external hook fixture did not pass'); cases += 1; const wrongDenySource = structuredClone(validEntry); wrongDenySource.hooks[4].sourcePath = 'C:\\temp\\config.toml'; expectThrow(() => assertExactHookGraph( wrongDenySource, codexHome, true, true, sha256(denyCommand), expectedCommands, false, ), 'deny source'); const wrongDenyCommand = structuredClone(validEntry); wrongDenyCommand.hooks[4].command = 'different-deny-command'; expectThrow(() => assertExactHookGraph( wrongDenyCommand, codexHome, true, true, sha256(denyCommand), expectedCommands, false, ), 'deny command'); const wrongWaggleCommand = structuredClone(validEntry); wrongWaggleCommand.hooks[0].command = 'different-waggle-command'; expectThrow(() => assertExactHookGraph( wrongWaggleCommand, codexHome, true, true, sha256(denyCommand), expectedCommands, false, ), 'packaged Waggle command'); const untrusted = structuredClone(validEntry); untrusted.hooks[0].trustStatus = 'untrusted'; expectThrow(() => assertExactHookGraph( untrusted, codexHome, true, true, sha256(denyCommand), expectedCommands, false, ), 'untrusted hook'); const acknowledgement = 'SELF_TEST_ACK'; const safeNotification = { sequence: 1, message: { method: 'item/completed', params: { item: { type: 'agentMessage', text: acknowledgement } }, }, }; assert(eventAudit([safeNotification], acknowledgement, true).normalTextCompleted, 'safe event fixture did not pass'); cases += 1; for (const itemTypeValue of [ 'commandExecution', 'computerUse', 'dynamicToolCall', 'fileChange', 'imageGeneration', 'imageView', 'mcpToolCall', 'toolSearch', 'webSearch', ]) { const bad = { sequence: 2, message: { method: 'item/completed', params: { item: { type: itemTypeValue } } }, }; expectThrow(() => eventAudit([safeNotification, bad], acknowledgement, true), itemTypeValue); } const unknownNotification = { sequence: 2, message: { method: 'future/tool/event', params: {} } }; expectThrow( () => eventAudit([safeNotification, unknownNotification], acknowledgement, true), 'unknown notification', ); const failureNotification = { sequence: 2, message: { method: 'warning', params: {} } }; expectThrow( () => eventAudit([safeNotification, failureNotification], acknowledgement, true), 'failure notification', ); const projectTrustWarning = { sequence: 2, message: { method: 'configWarning', params: { summary: `${FAIL_CLOSED_PROJECT_TRUST_WARNING}\n` + ' 1. C:\\fixture\\.codex\n' + ' To load project-local config, hooks, and exec policies, add c:\\fixture as a trusted project in C:\\Users\\fixture\\.codex\\config.toml.\n', details: null, path: null, range: null, }, }, }; const remoteControlStatus = { sequence: 3, message: { method: 'remoteControl/status/changed', params: { status: 'disconnected' } }, }; const failClosedTrustAudit = eventAudit( [safeNotification, projectTrustWarning, remoteControlStatus], acknowledgement, true, ); assert( failClosedTrustAudit.failureEventsObserved === 0 && failClosedTrustAudit.unknownEventsObserved === 0, 'fail-closed project trust warning or schema-backed remote-control status was rejected', ); cases += 1; const unsafeTrustWarning = { ...projectTrustWarning, message: { ...projectTrustWarning.message, params: { ...projectTrustWarning.message.params, details: 'unexpected detail' }, }, }; const unsafeTrustDiagnostic = captureEventAuditFailure( () => eventAudit([safeNotification, unsafeTrustWarning], acknowledgement, true), 'unsafe project trust warning', ); assert( unsafeTrustDiagnostic.invariant === 'failure-notification' && stableJson(unsafeTrustDiagnostic.rejectedMethodSha256) === stableJson([ { sha256: sha256('configWarning'), count: 1 }, ]), 'non-canonical project trust warning was not rejected', ); cases += 1; const appendedTrustWarning = { ...projectTrustWarning, message: { ...projectTrustWarning.message, params: { ...projectTrustWarning.message.params, summary: `${projectTrustWarning.message.params.summary}unexpected suffix\n`, }, }, }; expectThrow( () => eventAudit([safeNotification, appendedTrustWarning], acknowledgement, true), 'appended project trust warning', ); const extraParamTrustWarning = { ...projectTrustWarning, message: { ...projectTrustWarning.message, params: { ...projectTrustWarning.message.params, extra: null }, }, }; expectThrow( () => eventAudit([safeNotification, extraParamTrustWarning], acknowledgement, true), 'extra-param project trust warning', ); const nearMatchTrustWarning = { ...projectTrustWarning, message: { ...projectTrustWarning.message, params: { ...projectTrustWarning.message.params, summary: projectTrustWarning.message.params.summary.replace( FAIL_CLOSED_PROJECT_TRUST_WARNING, `${FAIL_CLOSED_PROJECT_TRUST_WARNING} Additional guidance follows.`, ), }, }, }; expectThrow( () => eventAudit([safeNotification, nearMatchTrustWarning], acknowledgement, true), 'near-match project trust warning', ); const unknownDiagnostic = captureEventAuditFailure( () => eventAudit([safeNotification, unknownNotification], acknowledgement, true), 'unknown diagnostic', ); assert( unknownDiagnostic.invariant === 'unknown-notification-method' && unknownDiagnostic.notificationCount === 2 && unknownDiagnostic.unknownNotificationEventsObserved === 1 && stableJson(unknownDiagnostic.rejectedMethodSha256) === stableJson([ { sha256: sha256('future/tool/event'), count: 1 }, ]) && Object.keys(unknownDiagnostic).join(',') === EVENT_AUDIT_FAILURE_KEYS.join(',') && !stableJson(unknownDiagnostic).includes('future/tool/event'), 'unknown notification diagnostic was not fixed, hashed, and path-free', ); cases += 1; const secretVariant = { sequence: 2, message: { method: 'future/tool/event', params: { text: 'sk-private-value', path: 'C:\\private\\auth.json', sessionId: 'sess-private' }, }, }; const secretDiagnostic = captureEventAuditFailure( () => eventAudit([safeNotification, secretVariant], acknowledgement, true), 'secret-independent diagnostic', ); assert( stableJson(secretDiagnostic) === stableJson(unknownDiagnostic), 'event-audit diagnostic changed when only untrusted params changed', ); cases += 1; const precedenceDiagnostic = captureEventAuditFailure( () => eventAudit([safeNotification, unknownNotification, failureNotification], acknowledgement, true), 'failure precedence', ); assert( precedenceDiagnostic.invariant === 'failure-notification' && stableJson(precedenceDiagnostic.rejectedMethodSha256) === stableJson([ { sha256: sha256('warning'), count: 1 }, ]), 'failure notification did not retain precedence and hashed evidence', ); cases += 1; const unknownItem = { sequence: 2, message: { method: 'item/completed', params: { item: { type: 'futureItem' } } }, }; const unknownItemDiagnostic = captureEventAuditFailure( () => eventAudit([safeNotification, unknownItem], acknowledgement, true), 'unknown item type', ); assert( unknownItemDiagnostic.invariant === 'unknown-item-type' && stableJson(unknownItemDiagnostic.rejectedItemTypeSha256) === stableJson([ { sha256: sha256('futureItem'), count: 1 }, ]), 'unknown item diagnostic did not retain only hashed type evidence', ); cases += 1; assert( sanitizeEventAuditFailure({ ...unknownDiagnostic, notificationCount: '2' }) === null, 'event-audit diagnostic accepted a non-integer count', ); cases += 1; assert( sanitizeEventAuditFailure({ ...unknownDiagnostic, params: { text: 'private' } }) === null, 'event-audit diagnostic accepted an unexpected raw property', ); cases += 1; const benignNotifications = [ 'account/updated', 'mcpServer/startupStatus/updated', 'model/safetyBuffering/updated', 'model/verification', 'thread/name/updated', 'thread/settings/updated', 'turn/moderationMetadata', ].map((method, index) => ({ sequence: index + 2, message: { method, params: {} } })); assert( eventAudit([safeNotification, ...benignNotifications], acknowledgement, true).unknownEventsObserved === 0, 'schema-backed benign notification fixture failed', ); cases += 1; const wrongScope = { sequence: 2, message: { method: 'item/updated', params: { threadId: 'wrong-thread', turnId: 'turn-1', item: { type: 'reasoning' } }, }, }; expectThrow( () => eventAudit([safeNotification, wrongScope], acknowledgement, true, 'thread-1', 'turn-1'), 'cross-turn notification', ); const reroute = { sequence: 2, message: { method: 'model/rerouted', params: {} } }; expectThrow( () => eventAudit([safeNotification, reroute], acknowledgement, false), 'model reroute', ); const mcpBoundary = { count: 3, namesSha256: 'a'.repeat(64), configSha256: 'b'.repeat(64), boundarySha256: 'c'.repeat(64), }; assertSameMcpBoundary(mcpBoundary, { ...mcpBoundary }, 'self-test'); cases += 1; expectThrow( () => assertSameMcpBoundary(mcpBoundary, { ...mcpBoundary, configSha256: 'd'.repeat(64) }, 'self-test'), 'MCP boundary drift', ); const simpleFailure = sanitizeTurnFailure({ status: 'failed', error: { codexErrorInfo: 'unauthorized' }, }); assert( stableJson(simpleFailure) === stableJson({ status: 'failed', code: 'unauthorized', httpStatusCode: null, willRetry: null, }), 'simple terminal failure was not normalized', ); cases += 1; const httpFailure = sanitizeTurnFailure({ status: 'interrupted' }, { message: { method: 'error', params: { willRetry: false, error: { codexErrorInfo: { responseStreamDisconnected: { httpStatusCode: 429 } }, }, }, }, }); assert( stableJson(httpFailure) === stableJson({ status: 'interrupted', code: 'responseStreamDisconnected', httpStatusCode: 429, willRetry: false, }), 'HTTP terminal failure was not normalized', ); cases += 1; const maliciousFailure = sanitizeTurnFailure({ status: 'failed', error: { message: 'C:\\secret\\auth.json sk-fixture', additionalDetails: 'file:///private/detail', codexErrorInfo: { activeTurnNotSteerable: { turnKind: 'review' } }, }, }); assert( Object.keys(maliciousFailure).sort().join(',') === 'code,httpStatusCode,status,willRetry' && !JSON.stringify(maliciousFailure).includes('secret'), 'terminal failure retained untrusted text', ); cases += 1; assert(sanitizeTurnFailure({ status: 'completed' }).status === null, 'completed turn was not safely normalized'); cases += 1; assert(sanitizeTurnFailure({ status: 'inProgress' }).status === null, 'in-progress turn was not safely normalized'); cases += 1; assert(sanitizeTurnFailure({ status: 'failed', error: { codexErrorInfo: 'futureUnknownCode' }, }).code === null, 'unknown terminal error code was not safely normalized'); cases += 1; assert(sanitizeTurnFailure({ status: 'failed', error: { codexErrorInfo: { responseStreamDisconnected: { httpStatusCode: 429 }, httpConnectionFailed: { httpStatusCode: 503 }, }, }, }).code === null, 'multi-key terminal error was not safely normalized'); cases += 1; assert(sanitizeTurnFailure({ status: 'failed', error: { codexErrorInfo: { httpConnectionFailed: { httpStatusCode: 70_000 } }, }, }).code === null, 'invalid terminal HTTP status was not safely normalized'); cases += 1; const disagreeingFailure = sanitizeTurnFailure({ status: 'failed', error: { codexErrorInfo: 'unauthorized' }, }, { message: { method: 'error', params: { error: { codexErrorInfo: 'usageLimitExceeded' }, willRetry: false }, }, }); assert( disagreeingFailure.code === 'unauthorized' && disagreeingFailure.willRetry === false, 'final turn was not authoritative over a disagreeing notification', ); cases += 1; const malformedFailure = sanitizeTurnFailure(null, { message: { method: 'futureError', params: { willRetry: 'yes', error: { codexErrorInfo: 7 } } }, }); assert( stableJson(malformedFailure) === stableJson({ status: null, code: null, httpStatusCode: null, willRetry: null, }), 'malformed terminal failure was not totally normalized', ); cases += 1; const primaryFixture = new Error('primary'); const closeFixture = new Error('close'); assert( selectLifecycleFailure(primaryFixture, closeFixture) === primaryFixture, 'close failure replaced the primary lifecycle failure', ); cases += 1; const requiredArgs = [ '--codex-exe', 'C:\\codex.exe', '--expected-head', 'a'.repeat(40), '--hive-mind-cli', 'C:\\hive-mind.exe', '--model', 'fixture-model', '--receipt-dir', 'C:\\receipt', '--windows-powershell', 'C:\\powershell.exe', '--workspace', 'C:\\workspace', ]; assert(parseArgs([...requiredArgs, '--setup-only'])['setup-only'] === true, 'setup-only fixture failed'); cases += 1; expectThrow( () => parseArgs([ ...requiredArgs, '--setup-only', '--execute-paid', '--marker', 'fixture', '--ack', PAID_ACK, ]), 'setup-only paid conflict', ); const officialProviderArguments = buildInvocationArguments({ mcpServerNames: [], catalogPath: 'C:\\models.json', providerBaseUrl: null, includeDeny: false, hookDisables: [], hookPins: [], }); const officialProviderOverrides = officialProviderArguments.filter((argument) => ( argument.startsWith('model_provider=') || argument.startsWith('model_providers.') )); assert( stableJson(officialProviderOverrides) === stableJson(['model_provider="openai"']), 'official provider arguments redefined the reserved built-in OpenAI provider', ); cases += 1; assert(cases === 54, `expected exactly 54 self-test cases, observed ${cases}`); return { pass: true, paidCalls: 0, cases }; } finally { await rm(root, { recursive: true, force: true }); } } const flags = parseArgs(process.argv.slice(2)); if (flags['self-test']) { process.stdout.write(`${JSON.stringify(await runValidationSelfTest())}\n`); process.exit(0); } const codexExecutable = resolve(flags['codex-exe']); const hiveMindCli = resolve(flags['hive-mind-cli']); const receiptDir = resolve(flags['receipt-dir']); const windowsPowerShell = resolve(flags['windows-powershell']); const workspace = resolve(flags.workspace); const model = flags.model; const expectedHead = flags['expected-head']; const executePaid = flags['execute-paid'] === true; const setupOnly = flags['setup-only'] === true; const initialExecutableSha256 = existsSync(codexExecutable) ? sha256File(codexExecutable) : null; const initialScriptSha256 = sha256File(SCRIPT_PATH); const report = { schemaVersion: 1, kind: 'codex-tool-denial-and-official-auth', pass: false, paidCalls: 0, proof: { executed: false, paidCalls: 0, pass: null }, source: { expectedHead, observedHead: null, tree: null, scriptBlob: null, trackedClean: false, unchanged: false, }, executable: { version: null, sha256: initialExecutableSha256, unchanged: false, }, model: { idSha256: sha256(model), bundledCatalogSha256: null, }, modelCatalog: { sealedSha256: null, controlSha256: null, }, invocation: { argumentsSha256: null, configSha256: sha256(stableJson(CONFIG_OVERRIDES)), threadParamsSha256: null, turnParamsSha256: null, mcpServerCount: null, mcpServerNamesSha256: null, }, hooks: { expectedCount: 5, extraCount: null, artifactsSha256: null, cliSha256: null, graphSha256: null, denyHookSha256: null, warnings: null, errors: null, allTrusted: false, unchangedAfterPin: false, artifactsUnchanged: false, }, mcpBoundary: { count: null, namesSha256: null, initialSha256: null, prePaidSha256: null, postPaidSha256: null, initialConfigSha256: null, prePaidConfigSha256: null, postPaidConfigSha256: null, unchanged: false, }, sealed: null, red: null, green: null, paidInvocation: { attempted: false, executed: false, arguments: [], argumentsSha256: null, modelCatalogSha256: null, hookGraphSha256: null, markerSha256: null, sessionIdSha256: null, threadParamsSha256: null, turnParamsSha256: null, normalTextCompleted: false, toolEventsObserved: 0, unknownEventsObserved: 0, }, artifacts: { scriptSha256: initialScriptSha256, scriptUnchanged: false, windowsPowerShellSha256: null, windowsSystemDirectorySha256: null, workspaceSha256: sha256(canonicalPath(workspace)), reportPathHash: sha256(join(receiptDir, 'report.json').toLowerCase()), tempRemoved: false, }, diagnostic: { failureStage: null, protocolCode: null, completedStage: null, modelCalls: 0, turnStartCalls: 0, closeClean: false, }, turnFailure: null, eventAuditFailure: null, error: null, }; let rawPaidSessionId = null; let markerMatched = !executePaid; let tempRoot = null; let initialSourceSnapshot = null; let initialMcpBoundary = null; const providers = []; let receiptCreated = false; try { assert(process.platform === 'win32', 'this proof is Windows-only'); assert(isAbsolute(flags['codex-exe']), '--codex-exe must be absolute'); assert(isAbsolute(flags['hive-mind-cli']), '--hive-mind-cli must be absolute'); assert(isAbsolute(flags['receipt-dir']), '--receipt-dir must be absolute'); assert(isAbsolute(flags['windows-powershell']), '--windows-powershell must be absolute'); assert(isAbsolute(flags.workspace), '--workspace must be absolute'); requireRegularFile(codexExecutable, 'Codex executable'); requireRegularFile(hiveMindCli, 'hive-mind CLI'); requireDirectory(workspace, 'workspace'); initialSourceSnapshot = verifySourceSnapshot(expectedHead); Object.assign(report.source, initialSourceSnapshot); assert(!existsSync(receiptDir), '--receipt-dir must be fresh'); assertNoExistingReparsePoint(dirname(receiptDir)); await mkdir(receiptDir, { recursive: false }); receiptCreated = true; const codexHome = resolve(process.env.CODEX_HOME || join(homedir(), '.codex')); requireDirectory(codexHome, 'user Codex profile'); requireRegularFile(join(codexHome, 'hooks.json'), 'user Codex hooks.json'); const codexConfigPath = join(codexHome, 'config.toml'); requireRegularFile(codexConfigPath, 'user Codex config.toml'); const version = runChecked(codexExecutable, ['--version'], { label: 'Codex version', timeoutMs: 20_000, }).stdout.trim(); assert(/^codex(?:-cli)?\s/.test(version), 'Codex returned an unexpected version'); report.executable.version = version; tempRoot = join(tmpdir(), `waggle-codex-denial-${randomBytes(12).toString('hex')}`); await mkdir(tempRoot, { recursive: false }); await mkdir(join(tempRoot, 'mind'), { recursive: true }); await mkdir(join(tempRoot, 'process-temp'), { recursive: true }); const env = buildChildEnvironment(tempRoot); initialMcpBoundary = captureMcpBoundary(codexExecutable, env, codexConfigPath); const mcpServerNames = readConfiguredMcpServerNames(codexExecutable, env); assert( initialMcpBoundary.namesSha256 === sha256(stableJson(mcpServerNames)), 'initial MCP inventory changed while being captured', ); report.invocation.mcpServerCount = mcpServerNames.length; report.invocation.mcpServerNamesSha256 = sha256(stableJson(mcpServerNames)); report.mcpBoundary.count = initialMcpBoundary.count; report.mcpBoundary.namesSha256 = initialMcpBoundary.namesSha256; report.mcpBoundary.initialSha256 = initialMcpBoundary.boundarySha256; report.mcpBoundary.initialConfigSha256 = initialMcpBoundary.configSha256; const sentinelPath = join(tempRoot, 'owned-sentinel.png'); const sentinelSha256 = await createSentinelPng(sentinelPath); const { selected, bundledSha256 } = readBundledModel(codexExecutable, model); report.model.bundledCatalogSha256 = bundledSha256; const sealedCatalogPath = join(tempRoot, 'sealed-models.json'); const controlCatalogPath = join(tempRoot, 'control-models.json'); report.modelCatalog.sealedSha256 = await writeCatalog(sealedCatalogPath, mutateModel(selected, true)); const controlModel = `waggle-control-${sha256(model).slice(0, 16)}`; const controlCatalogModel = mutateModel(selected, false); controlCatalogModel.slug = controlModel; report.modelCatalog.controlSha256 = await writeCatalog(controlCatalogPath, controlCatalogModel); const trustedPowerShell = verifyWindowsPowerShell(windowsPowerShell); report.artifacts.windowsPowerShellSha256 = trustedPowerShell.sha256; report.artifacts.windowsSystemDirectorySha256 = trustedPowerShell.systemDirectorySha256; const expectedWaggleHooks = buildExpectedWaggleHooks(trustedPowerShell.path, hiveMindCli); const denial = buildDenyHookConfig(trustedPowerShell.path); const replacements = [ codexExecutable, hiveMindCli, receiptDir, workspace, codexHome, tempRoot, sentinelPath, trustedPowerShell.path, flags.marker, ]; const discoveryArgs = buildInvocationArguments({ mcpServerNames, catalogPath: sealedCatalogPath, providerBaseUrl: 'http://127.0.0.1:9/v1', includeDeny: true, denyConfig: denial.config, hookPins: [], }); const discovered = await listHooks( codexExecutable, discoveryArgs, workspace, env, codexHome, true, false, denial.commandSha256, expectedWaggleHooks.commands, true, replacements, ); const allPins = discovered.expected.map(({ key, currentHash }) => ({ key, currentHash })); const wagglePins = discovered.expected .filter(({ eventName }) => eventName !== 'preToolUse') .map(({ key, currentHash }) => ({ key, currentHash })); const externalHookDisables = discovered.extras.map(({ key }) => key); const pinnedDiscoveryArgs = buildInvocationArguments({ mcpServerNames, catalogPath: sealedCatalogPath, providerBaseUrl: 'http://127.0.0.1:9/v1', includeDeny: true, denyConfig: denial.config, hookDisables: externalHookDisables, hookPins: allPins, }); const pinned = await listHooks( codexExecutable, pinnedDiscoveryArgs, workspace, env, codexHome, true, true, denial.commandSha256, expectedWaggleHooks.commands, false, replacements, ); assert(discovered.identitySha256 === pinned.identitySha256, 'hook graph identity changed while hash-pinning'); assert(pinned.denyHookSha256 !== null, 'deny hook hash is missing'); report.hooks = { expectedCount: 5, extraCount: pinned.extraCount, disabledExtraCount: pinned.disabledExtraCount, artifactsSha256: expectedWaggleHooks.artifactsSha256, cliSha256: expectedWaggleHooks.cliSha256, graphSha256: pinned.graphSha256, denyHookSha256: pinned.denyHookSha256, warnings: pinned.warnings, errors: pinned.errors, allTrusted: pinned.expected.every((hook) => hook.trustStatus === 'trusted'), unchangedAfterPin: discovered.identitySha256 === pinned.identitySha256, artifactsUnchanged: false, }; if (!setupOnly) { const sealedAck = `WAGGLE_SEALED_ACK_${randomBytes(24).toString('hex')}`; const sealedProvider = await startLoopbackProvider('sealed', sealedAck, sentinelPath); providers.push(sealedProvider); const sealedArgs = buildInvocationArguments({ mcpServerNames, catalogPath: sealedCatalogPath, providerBaseUrl: sealedProvider.baseUrl, includeDeny: true, denyConfig: denial.config, hookDisables: externalHookDisables, hookPins: allPins, }); const sealedRun = await runTurn({ executable: codexExecutable, args: sealedArgs, workspace, env, replacements, codexHome, includeDeny: true, expectedDenyCommandSha256: denial.commandSha256, expectedWaggleCommands: expectedWaggleHooks.commands, expectedHookGraphSha256: pinned.graphSha256, lane: 'sealed', model, prompt: `Return exactly ${sealedAck} and nothing else. Do not call tools.`, expectedAcknowledgement: sealedAck, strictAllowlist: true, }); await sealedProvider.close(); providers.splice(providers.indexOf(sealedProvider), 1); assert(sealedProvider.records.length === 1, `sealed provider saw ${sealedProvider.records.length} requests`); const sealedRequest = sealedProvider.records[0]; assert(sealedRequest.topLevelToolsIsArray, 'sealed provider request omitted the top-level tools array'); assert( sealedRequest.toolNames.length === 0, `sealed provider request exposed top-level tools: ${JSON.stringify(sealedRequest.toolNames)}`, ); assert(sealedRequest.additionalToolCount === 0, 'sealed provider request exposed input additional_tools'); report.sealed = { topLevelToolCount: sealedRequest.toolNames.length, additionalToolCount: sealedRequest.additionalToolCount, emptySchema: true, normalTextCompleted: sealedRun.events.normalTextCompleted, providerRequestSha256: sealedRequest.sha256, }; const controlAck = `WAGGLE_CONTROL_ACK_${randomBytes(24).toString('hex')}`; const redProvider = await startLoopbackProvider('red', controlAck, sentinelPath); providers.push(redProvider); const redArgs = buildInvocationArguments({ mcpServerNames, catalogPath: controlCatalogPath, providerBaseUrl: redProvider.baseUrl, includeDeny: false, denyConfig: denial.config, hookDisables: externalHookDisables, hookPins: wagglePins, }); const redDiscovery = await listHooks( codexExecutable, redArgs, workspace, env, codexHome, false, true, null, expectedWaggleHooks.commands, false, replacements, ); const redRun = await runTurn({ executable: codexExecutable, args: redArgs, workspace, env, replacements, codexHome, includeDeny: false, expectedDenyCommandSha256: null, expectedWaggleCommands: expectedWaggleHooks.commands, expectedHookGraphSha256: redDiscovery.graphSha256, useDefaultEnvironmentForControl: true, lane: 'red', model: controlModel, prompt: 'Follow the provider tool call, then return only the provider acknowledgement.', expectedAcknowledgement: controlAck, strictAllowlist: false, }); await redProvider.close(); providers.splice(providers.indexOf(redProvider), 1); assert(redProvider.records.length === 2, `RED provider saw ${redProvider.records.length} requests`); const redTools = redProvider.records[0].toolNames; assert( JSON.stringify(redTools) === JSON.stringify(['view_image']), `RED control catalog did not expose only view_image: ${JSON.stringify(redTools)}`, ); const redSensitiveDataObserved = redProvider.records[1]?.containsImage === true; const redReadObserved = redRun.events.imageItemCount > 0 || redSensitiveDataObserved; const redToolLoopObserved = redProvider.records.length === 2 && redRun.events.normalTextCompleted; report.red = { toolNames: redTools, recordCount: redProvider.records.length, toolLoopObserved: redToolLoopObserved, readObserved: redReadObserved, sensitiveDataObserved: redSensitiveDataObserved, imageItemCount: redRun.events.imageItemCount, normalTextCompleted: redRun.events.normalTextCompleted, sentinelSha256, }; assert(redToolLoopObserved, 'RED control did not prove the provider-routed view_image tool loop'); const greenProvider = await startLoopbackProvider('green', controlAck, sentinelPath); providers.push(greenProvider); const greenArgs = buildInvocationArguments({ mcpServerNames, catalogPath: controlCatalogPath, providerBaseUrl: greenProvider.baseUrl, includeDeny: true, denyConfig: denial.config, hookDisables: externalHookDisables, hookPins: allPins, }); const greenRun = await runTurn({ executable: codexExecutable, args: greenArgs, workspace, env, replacements, codexHome, includeDeny: true, expectedDenyCommandSha256: denial.commandSha256, expectedWaggleCommands: expectedWaggleHooks.commands, expectedHookGraphSha256: pinned.graphSha256, useDefaultEnvironmentForControl: true, lane: 'green', model: controlModel, prompt: 'Follow the provider tool call, then return only the provider acknowledgement.', expectedAcknowledgement: controlAck, strictAllowlist: false, }); await greenProvider.close(); providers.splice(providers.indexOf(greenProvider), 1); assert(greenProvider.records.length === 2, `GREEN provider saw ${greenProvider.records.length} requests`); assert( JSON.stringify(greenProvider.records[0].toolNames) === JSON.stringify(['view_image']), `GREEN control catalog did not expose only view_image: ${JSON.stringify(greenProvider.records[0].toolNames)}`, ); const greenImageObserved = greenProvider.records.slice(1).some((record) => record.containsImage); const deniedBeforeRead = greenRun.events.denyHookCount === 1 && !greenImageObserved && ( greenRun.events.firstImageSequence === null || greenRun.events.firstDenySequence < greenRun.events.firstImageSequence ); assert(deniedBeforeRead, 'GREEN hook did not deny view_image before sentinel access'); report.green = { deniedBeforeRead, sensitiveDataObserved: false, normalTextCompleted: greenRun.events.normalTextCompleted, denialReasonSha256: sha256(DENIAL_REASON), denyHookReceipts: greenRun.events.denyHookCount, }; report.invocation.argumentsSha256 = sha256(stableJson(normalizedArguments(greenArgs, [ [sealedCatalogPath, ''], [controlCatalogPath, ''], [greenProvider.baseUrl, ''], [denial.command, ''], ]))); report.invocation.threadParamsSha256 = sealedRun.threadParamsSha256; report.invocation.turnParamsSha256 = sealedRun.turnParamsSha256; report.proof = { executed: true, paidCalls: 0, pass: true }; } if (setupOnly) { const setupArgs = buildInvocationArguments({ mcpServerNames, catalogPath: sealedCatalogPath, providerBaseUrl: null, includeDeny: true, denyConfig: denial.config, hookDisables: externalHookDisables, hookPins: allPins, }); let preSetupMcpBoundary = null; const setupRun = await runTurn({ executable: codexExecutable, args: setupArgs, workspace, env, replacements, codexHome, includeDeny: true, expectedDenyCommandSha256: denial.commandSha256, expectedWaggleCommands: expectedWaggleHooks.commands, expectedHookGraphSha256: pinned.graphSha256, beforeTurn: async () => { preSetupMcpBoundary = captureMcpBoundary(codexExecutable, env, codexConfigPath); assertSameMcpBoundary(initialMcpBoundary, preSetupMcpBoundary, 'before setup-only thread/start'); report.mcpBoundary.prePaidSha256 = preSetupMcpBoundary.boundarySha256; report.mcpBoundary.prePaidConfigSha256 = preSetupMcpBoundary.configSha256; }, lane: 'setup', model, setupOnly: true, strictAllowlist: true, }); assert(preSetupMcpBoundary !== null, 'setup-only MCP/config boundary was not captured'); const postSetupMcpBoundary = captureMcpBoundary(codexExecutable, env, codexConfigPath); assertSameMcpBoundary(initialMcpBoundary, postSetupMcpBoundary, 'after setup-only thread/start'); report.mcpBoundary.postPaidSha256 = postSetupMcpBoundary.boundarySha256; report.mcpBoundary.postPaidConfigSha256 = postSetupMcpBoundary.configSha256; report.mcpBoundary.unchanged = true; report.diagnostic = setupRun.diagnostic; report.invocation.argumentsSha256 = sha256(stableJson(normalizedArguments(setupArgs, [ [sealedCatalogPath, ''], [denial.command, ''], ]))); report.invocation.threadParamsSha256 = setupRun.threadParamsSha256; report.invocation.turnParamsSha256 = null; } else if (executePaid) { const captureMindRoot = process.env.HIVE_MIND_DATA_DIR; const captureWorkspaceId = process.env.WAGGLE_WORKSPACE_ID; assert( typeof captureMindRoot === 'string' && isAbsolute(captureMindRoot), 'paid lane requires an absolute HIVE_MIND_DATA_DIR from the parent harness', ); requireDirectory(captureMindRoot, 'paid capture mind root'); assert( typeof captureWorkspaceId === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(captureWorkspaceId), 'paid lane requires a safe WAGGLE_WORKSPACE_ID from the parent harness', ); const paidEnv = { ...env, HIVE_MIND_DATA_DIR: captureMindRoot, WAGGLE_WORKSPACE_ID: captureWorkspaceId, }; const paidArgs = buildInvocationArguments({ mcpServerNames, catalogPath: sealedCatalogPath, providerBaseUrl: null, includeDeny: true, denyConfig: denial.config, hookDisables: externalHookDisables, hookPins: allPins, }); report.paidCalls = 1; report.paidInvocation.attempted = true; let prePaidMcpBoundary = null; const paidRun = await runTurn({ executable: codexExecutable, args: paidArgs, workspace, env: paidEnv, replacements, codexHome, includeDeny: true, expectedDenyCommandSha256: denial.commandSha256, expectedWaggleCommands: expectedWaggleHooks.commands, expectedHookGraphSha256: pinned.graphSha256, beforeTurn: async () => { prePaidMcpBoundary = captureMcpBoundary(codexExecutable, paidEnv, codexConfigPath); assertSameMcpBoundary(initialMcpBoundary, prePaidMcpBoundary, 'before the paid turn'); report.mcpBoundary.prePaidSha256 = prePaidMcpBoundary.boundarySha256; report.mcpBoundary.prePaidConfigSha256 = prePaidMcpBoundary.configSha256; }, lane: 'paid', model, prompt: `Return exactly ${flags.marker} and nothing else. Do not call tools.`, expectedAcknowledgement: flags.marker, strictAllowlist: true, }); report.diagnostic = paidRun.diagnostic; assert(prePaidMcpBoundary !== null, 'paid MCP/config boundary was not captured'); const postPaidMcpBoundary = captureMcpBoundary(codexExecutable, paidEnv, codexConfigPath); assertSameMcpBoundary(initialMcpBoundary, postPaidMcpBoundary, 'after the paid turn'); report.mcpBoundary.postPaidSha256 = postPaidMcpBoundary.boundarySha256; report.mcpBoundary.postPaidConfigSha256 = postPaidMcpBoundary.configSha256; report.mcpBoundary.unchanged = true; rawPaidSessionId = paidRun.threadId; markerMatched = paidRun.events.normalTextCompleted; const paidNormalizedArguments = normalizedArguments(paidArgs, [ [sealedCatalogPath, ''], [denial.command, ''], ]); report.paidInvocation = { attempted: true, executed: true, arguments: paidNormalizedArguments, argumentsSha256: sha256(stableJson(paidNormalizedArguments)), modelCatalogSha256: report.modelCatalog.sealedSha256, hookGraphSha256: paidRun.hooks.graphSha256, markerSha256: sha256(flags.marker), sessionIdSha256: paidRun.threadIdSha256, threadParamsSha256: paidRun.threadParamsSha256, turnParamsSha256: paidRun.turnParamsSha256, normalTextCompleted: paidRun.events.normalTextCompleted, toolEventsObserved: paidRun.events.toolEventsObserved, unknownEventsObserved: paidRun.events.unknownEventsObserved, }; assert(markerMatched, 'paid Codex lane did not return the exact marker'); assert(report.paidInvocation.toolEventsObserved === 0, 'paid Codex lane emitted a tool event'); assert(report.paidInvocation.unknownEventsObserved === 0, 'paid Codex lane emitted an unknown event'); assert( report.paidInvocation.threadParamsSha256 === report.invocation.threadParamsSha256, 'paid Codex thread parameters did not match the sealed proof', ); assert( report.paidInvocation.turnParamsSha256 === report.invocation.turnParamsSha256, 'paid Codex turn parameters did not match the sealed proof', ); } assert(sha256File(codexExecutable) === initialExecutableSha256, 'Codex executable changed during proof'); report.executable.unchanged = true; assert(sha256File(SCRIPT_PATH) === initialScriptSha256, 'Codex tool-denial helper changed during proof'); report.artifacts.scriptUnchanged = true; const finalExpectedWaggleHooks = buildExpectedWaggleHooks(trustedPowerShell.path, hiveMindCli); assert( finalExpectedWaggleHooks.artifactsSha256 === expectedWaggleHooks.artifactsSha256 && finalExpectedWaggleHooks.cliSha256 === expectedWaggleHooks.cliSha256, 'packaged Codex hook or hive-mind CLI artifact changed during proof', ); report.hooks.artifactsUnchanged = true; const finalSourceSnapshot = verifySourceSnapshot(expectedHead); assert( finalSourceSnapshot.observedHead === initialSourceSnapshot.observedHead && finalSourceSnapshot.tree === initialSourceSnapshot.tree && finalSourceSnapshot.scriptBlob === initialSourceSnapshot.scriptBlob, 'repository source snapshot changed during proof', ); report.source.unchanged = true; report.pass = proofStateSatisfied(report.proof, setupOnly) && report.source.unchanged && report.artifacts.scriptUnchanged && report.hooks.artifactsUnchanged && report.eventAuditFailure === null && (!setupOnly || ( report.diagnostic.failureStage === null && report.diagnostic.protocolCode === null && report.diagnostic.completedStage === 'thread-start' && report.diagnostic.modelCalls === 0 && report.diagnostic.turnStartCalls === 0 && report.diagnostic.closeClean && report.mcpBoundary.unchanged )) && (!executePaid || ( report.paidCalls === 1 && report.mcpBoundary.unchanged && report.paidInvocation.normalTextCompleted && report.paidInvocation.toolEventsObserved === 0 && report.paidInvocation.unknownEventsObserved === 0 )); } catch (error) { report.turnFailure = error?.turnFailure ?? null; report.eventAuditFailure = sanitizeEventAuditFailure(error?.eventAuditFailure); if (error?.canaryDiagnostic) { report.diagnostic = { failureStage: sanitizeCanaryStage(error.canaryDiagnostic.failureStage), protocolCode: sanitizeProtocolCode(error.canaryDiagnostic.protocolCode), completedStage: sanitizeCanaryStage(error.canaryDiagnostic.completedStage), modelCalls: Number.isInteger(error.canaryDiagnostic.modelCalls) && error.canaryDiagnostic.modelCalls >= 0 ? error.canaryDiagnostic.modelCalls : 0, turnStartCalls: Number.isInteger(error.canaryDiagnostic.turnStartCalls) && error.canaryDiagnostic.turnStartCalls >= 0 ? error.canaryDiagnostic.turnStartCalls : 0, closeClean: error.canaryDiagnostic.closeClean === true, }; } report.error = safeError(error, [ codexExecutable, hiveMindCli, receiptDir, workspace, windowsPowerShell, tempRoot, flags.marker, ]); } for (const provider of providers.splice(0)) { try { await provider.close(); } catch { /* best-effort loopback cleanup */ } } if (tempRoot !== null) { try { await rm(tempRoot, { recursive: true, force: true }); report.artifacts.tempRemoved = !existsSync(tempRoot); } catch (error) { report.error = report.error ?? safeError(error, [tempRoot]); report.pass = false; } } if (!report.executable.unchanged && existsSync(codexExecutable)) { report.executable.unchanged = sha256File(codexExecutable) === initialExecutableSha256; } if (!report.pass && report.error === null) report.error = 'proof did not satisfy all invariants'; let reportPath = null; let reportSha256 = null; if (receiptCreated) { reportPath = join(receiptDir, 'report.json'); await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8'); const posture = await stat(reportPath); assert(posture.isFile(), 'report.json is not a regular file'); reportSha256 = sha256File(reportPath); } const summary = { pass: report.pass, paidCalls: report.paidCalls, markerMatched, reportPath, reportSha256, sessionId: executePaid ? rawPaidSessionId : null, turnFailure: report.turnFailure, failureStage: report.diagnostic.failureStage, protocolCode: report.diagnostic.protocolCode, completedStage: report.diagnostic.completedStage, modelCalls: report.diagnostic.modelCalls, turnStartCalls: report.diagnostic.turnStartCalls, closeClean: report.diagnostic.closeClean, }; process.stdout.write(`${JSON.stringify(summary)}\n`); if (!report.pass) process.exitCode = 1;