[CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidatePattern('^[0-9a-f]{40}$')] [string]$ExpectedHead, [Parameter(Mandatory = $true)] [string]$ReceiptDir, [string]$ClaudeModel = 'claude-haiku-4-5-20251001', [ValidateRange(0.01, 1.00)] [decimal]$ClaudeMaxUsd = [decimal]0.05, [string]$CodexModel = 'gpt-5.5', [ValidateSet('openai-codex')] [string]$HermesProvider = 'openai-codex', [string]$HermesModel = 'gpt-5.5', [switch]$CodexProofValidatorSelfTest, [switch]$StaticPreflightOnly, [switch]$Execute, [string]$PaidRunAck = '' ) $ErrorActionPreference = 'Stop' $executionAcknowledgement = 'I_ACKNOWLEDGE_3_OFFICIAL_AUTH_CALLS' $evidenceRoot = [IO.Path]::GetFullPath('C:\tmp\waggle-readiness-evidence') $profileEnvironmentNames = @( 'USERPROFILE', 'HOME', 'HOMEDRIVE', 'HOMEPATH', 'APPDATA', 'LOCALAPPDATA', 'CLAUDE_CONFIG_DIR', 'CODEX_HOME', 'HERMES_HOME', 'HERMES_PROFILE' ) $claudeAlternativeAuthNames = @( 'ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'CLAUDE_CODE_OAUTH_TOKEN', 'ANTHROPIC_BASE_URL', 'CLAUDE_CODE_USE_BEDROCK', 'CLAUDE_CODE_USE_VERTEX', 'CLAUDE_CODE_USE_FOUNDRY', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN', 'AWS_PROFILE', 'AWS_CONFIG_FILE', 'AWS_SHARED_CREDENTIALS_FILE', 'GOOGLE_API_KEY', 'GOOGLE_APPLICATION_CREDENTIALS', 'CLOUDSDK_CONFIG' ) $codexAlternativeAuthNames = @( 'OPENAI_API_KEY', 'OPENAI_ACCESS_TOKEN', 'CODEX_ACCESS_TOKEN', 'OPENAI_BASE_URL', 'OPENAI_API_BASE' ) $hermesAlternativeAuthNames = @( 'OPENAI_API_KEY', 'OPENAI_ACCESS_TOKEN', 'CODEX_ACCESS_TOKEN', 'OPENAI_BASE_URL', 'OPENAI_API_BASE', 'OPENROUTER_API_KEY', 'OPENROUTER_BASE_URL', 'HERMES_API_KEY', 'LLM_API_KEY' ) function Assert-PlainIdentifier([string]$Value, [string]$Name) { if ([string]::IsNullOrWhiteSpace($Value) -or $Value.Length -gt 128 -or $Value -match '[\x00-\x1f\x7f]') { throw "$Name must be a non-empty, single-line identifier of at most 128 characters." } } function Assert-NoExistingReparsePoint([string]$Path, [string]$FailureMessage) { $cursor = [IO.Path]::GetFullPath($Path) while (-not [string]::IsNullOrWhiteSpace($cursor)) { if (Test-Path -LiteralPath $cursor) { $item = Get-Item -LiteralPath $cursor -Force if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw "$FailureMessage`: $cursor" } } $parent = [IO.Path]::GetDirectoryName($cursor) if ([string]::IsNullOrWhiteSpace($parent) -or $parent -eq $cursor) { break } $cursor = $parent } } function Resolve-ReceiptLayout([string]$RequestedPath) { $rootPrefix = $evidenceRoot.TrimEnd( [IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar ) + [IO.Path]::DirectorySeparatorChar $fullPath = [IO.Path]::GetFullPath($RequestedPath).TrimEnd( [IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar ) if (-not $fullPath.StartsWith($rootPrefix, [StringComparison]::OrdinalIgnoreCase)) { throw "ReceiptDir must be a child of $evidenceRoot." } if (Test-Path -LiteralPath $fullPath) { throw 'ReceiptDir must be a fresh path owned by this run.' } Assert-NoExistingReparsePoint -Path ([IO.Path]::GetDirectoryName($fullPath)) ` -FailureMessage 'ReceiptDir ancestor is a reparse point' $leaf = [IO.Path]::GetFileName($fullPath) if ([string]::IsNullOrWhiteSpace($leaf)) { throw 'ReceiptDir must have a final directory name.' } return [pscustomobject]@{ Root = $fullPath Staging = Join-Path ([IO.Path]::GetDirectoryName($fullPath)) ".$leaf.staging-$([guid]::NewGuid().ToString('N'))" } } function Get-Sha256Text([string]$Value) { $bytes = [Text.Encoding]::UTF8.GetBytes($Value) return [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData($bytes)).ToLowerInvariant() } function Assert-JsonBoolean([object]$Value, [bool]$Expected, [string]$Label) { if (-not ($Value -is [bool]) -or $Value -ne $Expected) { throw "$Label must be the JSON boolean $($Expected.ToString().ToLowerInvariant())." } } function Assert-JsonInteger([object]$Value, [long]$Expected, [string]$Label) { if ((-not ($Value -is [int])) -and (-not ($Value -is [long]))) { throw "$Label must be a JSON integer." } if ([long]$Value -ne $Expected) { throw "$Label must equal $Expected." } } function Assert-Sha256([object]$Value, [string]$Label) { if (-not ($Value -is [string]) -or [string]$Value -cnotmatch '^[0-9a-f]{64}$') { throw "$Label must be a lowercase SHA-256 value." } } function Assert-CodexToolDenialProof( [object]$Proof, [string]$ExpectedExecutableSha256, [string]$ExpectedHelperSha256, [string]$ExpectedHead, [string]$ExpectedHiveMindCliSha256, [string]$ExpectedMarkerSha256, [string]$ExpectedPowerShellSha256, [string]$ExpectedSessionIdSha256 ) { Assert-JsonInteger -Value $Proof.schemaVersion -Expected 1 -Label 'Codex proof schemaVersion' if (-not ($Proof.kind -is [string]) -or [string]$Proof.kind -cne 'codex-tool-denial-and-official-auth') { throw 'Codex proof kind is invalid.' } Assert-JsonBoolean -Value $Proof.pass -Expected $true -Label 'Codex proof pass' Assert-JsonInteger -Value $Proof.paidCalls -Expected 1 -Label 'Codex proof paidCalls' Assert-JsonInteger -Value $Proof.diagnostic.modelCalls -Expected 1 -Label 'Codex proof modelCalls' Assert-JsonInteger -Value $Proof.diagnostic.turnStartCalls -Expected 1 -Label 'Codex proof turnStartCalls' Assert-JsonBoolean -Value $Proof.diagnostic.closeClean -Expected $true -Label 'Codex proof closeClean' if ($null -ne $Proof.diagnostic.failureStage -or $null -ne $Proof.diagnostic.protocolCode -or [string]$Proof.diagnostic.completedStage -cne 'post-turn-invariants') { throw 'Codex proof did not complete all paid-turn invariants cleanly.' } Assert-JsonBoolean -Value $Proof.proof.executed -Expected $true -Label 'Codex proof-only executed' Assert-JsonBoolean -Value $Proof.proof.pass -Expected $true -Label 'Codex proof-only pass' Assert-JsonInteger -Value $Proof.proof.paidCalls -Expected 0 -Label 'Codex proof-only paidCalls' if (-not ($Proof.source.expectedHead -is [string]) -or [string]$Proof.source.expectedHead -cne $ExpectedHead) { throw 'Codex proof expected HEAD did not match the outer harness.' } if (-not ($Proof.source.observedHead -is [string]) -or [string]$Proof.source.observedHead -cne $ExpectedHead) { throw 'Codex proof observed HEAD did not match the outer harness.' } foreach ($entry in @( [pscustomobject]@{ Value = $Proof.source.tree; Label = 'Codex proof source tree' } [pscustomobject]@{ Value = $Proof.source.scriptBlob; Label = 'Codex proof helper blob' } )) { if (-not ($entry.Value -is [string]) -or [string]$entry.Value -cnotmatch '^[0-9a-f]{40}$') { throw "$($entry.Label) must be a lowercase 40-character Git object id." } } Assert-JsonBoolean -Value $Proof.source.trackedClean -Expected $true -Label 'Codex proof trackedClean' Assert-JsonBoolean -Value $Proof.source.unchanged -Expected $true -Label 'Codex proof source unchanged' Assert-Sha256 -Value $Proof.executable.sha256 -Label 'Codex executable hash' if ([string]$Proof.executable.sha256 -cne $ExpectedExecutableSha256) { throw 'Codex executable did not match tool-denial proof.' } Assert-JsonBoolean -Value $Proof.executable.unchanged -Expected $true -Label 'Codex executable unchanged' foreach ($entry in @( [pscustomobject]@{ Value = $Proof.modelCatalog.sealedSha256; Label = 'Codex sealed model catalog hash' } [pscustomobject]@{ Value = $Proof.modelCatalog.controlSha256; Label = 'Codex control model catalog hash' } [pscustomobject]@{ Value = $Proof.invocation.argumentsSha256; Label = 'Codex invocation arguments hash' } [pscustomobject]@{ Value = $Proof.invocation.configSha256; Label = 'Codex invocation config hash' } [pscustomobject]@{ Value = $Proof.invocation.threadParamsSha256; Label = 'Codex thread parameters hash' } [pscustomobject]@{ Value = $Proof.invocation.turnParamsSha256; Label = 'Codex turn parameters hash' } [pscustomobject]@{ Value = $Proof.invocation.mcpServerNamesSha256; Label = 'Codex MCP server-name inventory hash' } [pscustomobject]@{ Value = $Proof.hooks.graphSha256; Label = 'Codex hook graph hash' } [pscustomobject]@{ Value = $Proof.hooks.denyHookSha256; Label = 'Codex deny-hook hash' } [pscustomobject]@{ Value = $Proof.hooks.artifactsSha256; Label = 'Codex packaged hook artifact hash' } [pscustomobject]@{ Value = $Proof.hooks.cliSha256; Label = 'Codex hive-mind CLI hash' } [pscustomobject]@{ Value = $Proof.green.denialReasonSha256; Label = 'Codex denial reason hash' } [pscustomobject]@{ Value = $Proof.paidInvocation.argumentsSha256; Label = 'Codex paid invocation hash' } [pscustomobject]@{ Value = $Proof.paidInvocation.modelCatalogSha256; Label = 'Codex paid model catalog hash' } [pscustomobject]@{ Value = $Proof.paidInvocation.hookGraphSha256; Label = 'Codex paid hook graph hash' } [pscustomobject]@{ Value = $Proof.paidInvocation.markerSha256; Label = 'Codex paid marker hash' } [pscustomobject]@{ Value = $Proof.paidInvocation.sessionIdSha256; Label = 'Codex paid session hash' } [pscustomobject]@{ Value = $Proof.paidInvocation.threadParamsSha256; Label = 'Codex paid thread parameters hash' } [pscustomobject]@{ Value = $Proof.paidInvocation.turnParamsSha256; Label = 'Codex paid turn parameters hash' } [pscustomobject]@{ Value = $Proof.mcpBoundary.namesSha256; Label = 'Codex MCP boundary server-name hash' } [pscustomobject]@{ Value = $Proof.mcpBoundary.initialSha256; Label = 'Codex initial MCP boundary hash' } [pscustomobject]@{ Value = $Proof.mcpBoundary.prePaidSha256; Label = 'Codex pre-paid MCP boundary hash' } [pscustomobject]@{ Value = $Proof.mcpBoundary.postPaidSha256; Label = 'Codex post-paid MCP boundary hash' } [pscustomobject]@{ Value = $Proof.mcpBoundary.initialConfigSha256; Label = 'Codex initial config hash' } [pscustomobject]@{ Value = $Proof.mcpBoundary.prePaidConfigSha256; Label = 'Codex pre-paid config hash' } [pscustomobject]@{ Value = $Proof.mcpBoundary.postPaidConfigSha256; Label = 'Codex post-paid config hash' } [pscustomobject]@{ Value = $Proof.artifacts.scriptSha256; Label = 'Codex helper script hash' } [pscustomobject]@{ Value = $Proof.artifacts.windowsPowerShellSha256; Label = 'Codex Windows PowerShell hash' } [pscustomobject]@{ Value = $Proof.artifacts.windowsSystemDirectorySha256; Label = 'Codex Windows system-directory hash' } )) { Assert-Sha256 -Value $entry.Value -Label ([string]$entry.Label) } if ((-not ($Proof.invocation.mcpServerCount -is [int])) -and (-not ($Proof.invocation.mcpServerCount -is [long]))) { throw 'Codex MCP server count must be a JSON integer.' } if ([long]$Proof.invocation.mcpServerCount -lt 0) { throw 'Codex MCP server count must not be negative.' } if ((-not ($Proof.mcpBoundary.count -is [int])) -and (-not ($Proof.mcpBoundary.count -is [long]))) { throw 'Codex MCP boundary count must be a JSON integer.' } if ([long]$Proof.mcpBoundary.count -ne [long]$Proof.invocation.mcpServerCount -or [string]$Proof.mcpBoundary.namesSha256 -cne [string]$Proof.invocation.mcpServerNamesSha256) { throw 'Codex MCP boundary inventory did not match the invocation inventory.' } if ([string]$Proof.mcpBoundary.prePaidSha256 -cne [string]$Proof.mcpBoundary.initialSha256 -or [string]$Proof.mcpBoundary.postPaidSha256 -cne [string]$Proof.mcpBoundary.initialSha256 -or [string]$Proof.mcpBoundary.prePaidConfigSha256 -cne [string]$Proof.mcpBoundary.initialConfigSha256 -or [string]$Proof.mcpBoundary.postPaidConfigSha256 -cne [string]$Proof.mcpBoundary.initialConfigSha256) { throw 'Codex MCP/config boundary changed around the paid turn.' } Assert-JsonBoolean -Value $Proof.mcpBoundary.unchanged -Expected $true -Label 'Codex MCP boundary unchanged' Assert-JsonInteger -Value $Proof.hooks.expectedCount -Expected 5 -Label 'Codex hook expectedCount' Assert-JsonInteger -Value $Proof.hooks.extraCount -Expected 0 -Label 'Codex hook extraCount' Assert-JsonInteger -Value $Proof.hooks.warnings -Expected 0 -Label 'Codex hook warnings' Assert-JsonInteger -Value $Proof.hooks.errors -Expected 0 -Label 'Codex hook errors' Assert-JsonBoolean -Value $Proof.hooks.allTrusted -Expected $true -Label 'Codex hooks allTrusted' Assert-JsonBoolean -Value $Proof.hooks.unchangedAfterPin -Expected $true -Label 'Codex hooks unchangedAfterPin' Assert-JsonBoolean -Value $Proof.hooks.artifactsUnchanged -Expected $true -Label 'Codex hook artifacts unchanged' Assert-JsonBoolean -Value $Proof.artifacts.scriptUnchanged -Expected $true -Label 'Codex helper script unchanged' if ([string]$Proof.artifacts.scriptSha256 -cne $ExpectedHelperSha256) { throw 'Codex tool-denial helper did not match the outer harness artifact.' } if ([string]$Proof.hooks.cliSha256 -cne $ExpectedHiveMindCliSha256) { throw 'Codex hive-mind CLI did not match the packaged artifact.' } if ([string]$Proof.artifacts.windowsPowerShellSha256 -cne $ExpectedPowerShellSha256) { throw 'Codex deny-hook PowerShell did not match the OS executable.' } Assert-JsonInteger -Value $Proof.sealed.topLevelToolCount -Expected 0 -Label 'Codex sealed topLevelToolCount' Assert-JsonInteger -Value $Proof.sealed.additionalToolCount -Expected 0 -Label 'Codex sealed additionalToolCount' Assert-JsonBoolean -Value $Proof.sealed.emptySchema -Expected $true -Label 'Codex sealed emptySchema' Assert-JsonBoolean -Value $Proof.sealed.normalTextCompleted -Expected $true -Label 'Codex sealed normalTextCompleted' $redTools = @($Proof.red.toolNames) if ($redTools.Count -ne 1 -or -not ($redTools[0] -is [string]) -or [string]$redTools[0] -cne 'view_image') { throw 'Codex RED control did not expose exactly view_image.' } Assert-JsonBoolean -Value $Proof.red.toolLoopObserved -Expected $true -Label 'Codex RED toolLoopObserved' if (-not ($Proof.red.readObserved -is [bool])) { throw 'Codex RED readObserved must be a JSON boolean.' } if (-not ($Proof.red.sensitiveDataObserved -is [bool])) { throw 'Codex RED sensitiveDataObserved must be a JSON boolean.' } Assert-JsonBoolean -Value $Proof.green.deniedBeforeRead -Expected $true -Label 'Codex GREEN deniedBeforeRead' Assert-JsonBoolean -Value $Proof.green.sensitiveDataObserved -Expected $false -Label 'Codex GREEN sensitiveDataObserved' Assert-JsonBoolean -Value $Proof.green.normalTextCompleted -Expected $true -Label 'Codex GREEN normalTextCompleted' Assert-JsonBoolean -Value $Proof.paidInvocation.attempted -Expected $true -Label 'Codex paid attempted' Assert-JsonBoolean -Value $Proof.paidInvocation.executed -Expected $true -Label 'Codex paid executed' Assert-JsonBoolean -Value $Proof.paidInvocation.normalTextCompleted -Expected $true -Label 'Codex paid normalTextCompleted' Assert-JsonInteger -Value $Proof.paidInvocation.toolEventsObserved -Expected 0 -Label 'Codex paid toolEventsObserved' Assert-JsonInteger -Value $Proof.paidInvocation.unknownEventsObserved -Expected 0 -Label 'Codex paid unknownEventsObserved' if ([string]$Proof.paidInvocation.hookGraphSha256 -cne [string]$Proof.hooks.graphSha256) { throw 'Codex paid hook graph did not match the pinned proof.' } if ([string]$Proof.paidInvocation.modelCatalogSha256 -cne [string]$Proof.modelCatalog.sealedSha256) { throw 'Codex paid model catalog did not match the sealed proof.' } if ([string]$Proof.paidInvocation.threadParamsSha256 -cne [string]$Proof.invocation.threadParamsSha256 -or [string]$Proof.paidInvocation.turnParamsSha256 -cne [string]$Proof.invocation.turnParamsSha256) { throw 'Codex paid turn parameters did not match the sealed proof.' } if ([string]$Proof.paidInvocation.markerSha256 -cne $ExpectedMarkerSha256) { throw 'Codex paid marker hash did not match.' } if ([string]$Proof.paidInvocation.sessionIdSha256 -cne $ExpectedSessionIdSha256) { throw 'Codex paid session identifier hash did not match.' } } function Assert-SanitizedCodexTurnFailure([object]$Failure) { if ($null -eq $Failure) { return $null } $expectedProperties = @('code', 'httpStatusCode', 'status', 'willRetry') $actualProperties = @($Failure.PSObject.Properties.Name | Sort-Object) if ([string]::Join(',', $actualProperties) -cne [string]::Join(',', $expectedProperties)) { throw 'Codex turn failure contains unexpected or missing properties.' } if (-not ($Failure.status -is [string]) -or [string]$Failure.status -cnotin @('failed', 'interrupted')) { throw 'Codex turn failure status is invalid.' } $simpleCodes = @( 'badRequest', 'contextWindowExceeded', 'cyberPolicy', 'internalServerError', 'other', 'sandboxError', 'serverOverloaded', 'sessionBudgetExceeded', 'threadRollbackFailed', 'unauthorized', 'usageLimitExceeded' ) $httpCodes = @( 'httpConnectionFailed', 'responseStreamConnectionFailed', 'responseStreamDisconnected', 'responseTooManyFailedAttempts' ) $code = $Failure.code if ($null -ne $code -and (-not ($code -is [string]) -or ([string]$code -cnotin $simpleCodes -and [string]$code -cnotin $httpCodes -and [string]$code -cne 'activeTurnNotSteerable'))) { throw 'Codex turn failure code is invalid.' } $httpStatusCode = $Failure.httpStatusCode if ($null -ne $httpStatusCode -and ((-not ($httpStatusCode -is [int])) -and (-not ($httpStatusCode -is [long])))) { throw 'Codex turn failure HTTP status must be a JSON integer or null.' } if ($null -ne $httpStatusCode -and ([long]$httpStatusCode -lt 0 -or [long]$httpStatusCode -gt 65535)) { throw 'Codex turn failure HTTP status is out of range.' } if ($null -ne $httpStatusCode -and [string]$code -cnotin $httpCodes) { throw 'Only Codex HTTP failure codes may carry an HTTP status.' } if ($null -ne $Failure.willRetry -and -not ($Failure.willRetry -is [bool])) { throw 'Codex turn failure retry flag must be a JSON boolean or null.' } return [pscustomobject][ordered]@{ status = [string]$Failure.status code = if ($null -eq $code) { $null } else { [string]$code } httpStatusCode = if ($null -eq $httpStatusCode) { $null } else { [long]$httpStatusCode } willRetry = if ($null -eq $Failure.willRetry) { $null } else { [bool]$Failure.willRetry } } } function Assert-CodexFailureStage([object]$Value, [bool]$AllowNull = $false) { if ($null -eq $Value) { if ($AllowNull) { return $null } throw 'Codex failure stage must not be null.' } $stages = @( 'app-server-spawn', 'initialize', 'hooks-list', 'pre-turn-boundary', 'thread-start', 'turn-start', 'turn-completed', 'event-audit', 'app-server-close', 'post-turn-invariants' ) if (-not ($Value -is [string]) -or [string]$Value -cnotin $stages) { throw 'Codex failure stage is invalid.' } return [string]$Value } function Assert-NullableProtocolCode([object]$Value) { if ($null -eq $Value) { return $null } if ((-not ($Value -is [int])) -and (-not ($Value -is [long]))) { throw 'Codex protocol code must be a JSON integer or null.' } $code = [long]$Value if ($code -lt [int]::MinValue -or $code -gt [int]::MaxValue) { throw 'Codex protocol code must fit in a signed 32-bit integer.' } return [int]$code } function Assert-SanitizedCodexFailureProjection([object]$Failure) { $expectedProperties = @('class', 'lane', 'protocolCode', 'stage', 'turn') $actualProperties = @($Failure.PSObject.Properties.Name | Sort-Object) if ([string]::Join(',', $actualProperties) -cne [string]::Join(',', $expectedProperties)) { throw 'Sanitized Codex failure contains unexpected or missing properties.' } if (-not ($Failure.class -is [string]) -or [string]$Failure.class -cnotin @( 'codex-paid-turn-incomplete', 'codex-post-turn-invariant-failed', 'codex-proof-incomplete' )) { throw 'Sanitized Codex failure class is invalid.' } if (-not ($Failure.lane -is [string]) -or [string]$Failure.lane -cne 'codex') { throw 'Sanitized Codex failure lane is invalid.' } $null = Assert-CodexFailureStage ` -Value $Failure.stage ` -AllowNull ([string]$Failure.class -ceq 'codex-proof-incomplete') $null = Assert-NullableProtocolCode -Value $Failure.protocolCode $null = Assert-SanitizedCodexTurnFailure -Failure $Failure.turn } function Assert-CodexSetupPreflight( [object]$Summary, [object]$Report, [string]$ExpectedHead, [string]$ExpectedTree, [string]$ExpectedHelperBlob, [string]$ExpectedHelperSha256, [string]$ExpectedExecutableSha256, [string]$ExpectedReportPath, [string]$ExpectedReportSha256 ) { $expectedSummaryProperties = @( 'closeClean', 'completedStage', 'failureStage', 'markerMatched', 'modelCalls', 'paidCalls', 'pass', 'protocolCode', 'reportPath', 'reportSha256', 'sessionId', 'turnFailure', 'turnStartCalls' ) $actualSummaryProperties = @($Summary.PSObject.Properties.Name | Sort-Object) if ([string]::Join(',', $actualSummaryProperties) -cne [string]::Join(',', $expectedSummaryProperties)) { throw 'Codex setup summary contains unexpected or missing properties.' } Assert-JsonBoolean -Value $Summary.pass -Expected $true -Label 'Codex setup summary pass' Assert-JsonInteger -Value $Summary.paidCalls -Expected 0 -Label 'Codex setup summary paidCalls' Assert-JsonInteger -Value $Summary.modelCalls -Expected 0 -Label 'Codex setup summary modelCalls' Assert-JsonInteger -Value $Summary.turnStartCalls -Expected 0 -Label 'Codex setup summary turnStartCalls' Assert-JsonBoolean -Value $Summary.closeClean -Expected $true -Label 'Codex setup summary closeClean' if (-not ($Summary.markerMatched -is [bool])) { throw 'Codex setup summary markerMatched must be a JSON boolean.' } if ($null -ne $Summary.sessionId -or $null -ne $Summary.turnFailure) { throw 'Codex setup summary must not contain a session identifier or turn failure.' } $null = Assert-CodexFailureStage -Value $Summary.failureStage -AllowNull $true $null = Assert-NullableProtocolCode -Value $Summary.protocolCode if ($null -ne $Summary.failureStage -or $null -ne $Summary.protocolCode -or -not ($Summary.completedStage -is [string]) -or [string]$Summary.completedStage -cne 'thread-start') { throw 'Codex setup summary did not stop cleanly at thread-start.' } Assert-Sha256 -Value $Summary.reportSha256 -Label 'Codex setup summary report hash' if (-not ($Summary.reportPath -is [string]) -or [IO.Path]::GetFullPath([string]$Summary.reportPath) -cne $ExpectedReportPath -or [string]$Summary.reportSha256 -cne $ExpectedReportSha256) { throw 'Codex setup summary report binding is invalid.' } Assert-JsonBoolean -Value $Report.pass -Expected $true -Label 'Codex setup report pass' Assert-JsonInteger -Value $Report.schemaVersion -Expected 1 -Label 'Codex setup report schemaVersion' if (-not ($Report.kind -is [string]) -or [string]$Report.kind -cne 'codex-tool-denial-and-official-auth') { throw 'Codex setup report kind is invalid.' } Assert-JsonInteger -Value $Report.paidCalls -Expected 0 -Label 'Codex setup report paidCalls' Assert-JsonInteger -Value $Report.diagnostic.modelCalls -Expected 0 -Label 'Codex setup report modelCalls' Assert-JsonInteger -Value $Report.diagnostic.turnStartCalls -Expected 0 -Label 'Codex setup report turnStartCalls' Assert-JsonBoolean -Value $Report.diagnostic.closeClean -Expected $true -Label 'Codex setup report closeClean' Assert-JsonBoolean -Value $Report.proof.executed -Expected $false ` -Label 'Codex setup offline proof executed' if ($null -ne $Report.proof.pass) { throw 'Codex setup report must record the skipped offline proof as null.' } Assert-JsonInteger -Value $Report.proof.paidCalls -Expected 0 -Label 'Codex setup offline proof paidCalls' if ($null -ne $Report.diagnostic.failureStage -or $null -ne $Report.diagnostic.protocolCode -or [string]$Report.diagnostic.completedStage -cne 'thread-start') { throw 'Codex setup report did not stop cleanly at thread-start.' } if ([string]$Report.source.expectedHead -cne $ExpectedHead -or [string]$Report.source.observedHead -cne $ExpectedHead -or [string]$Report.source.tree -cne $ExpectedTree -or [string]$Report.source.scriptBlob -cne $ExpectedHelperBlob) { throw 'Codex setup report source did not match the sealed revision.' } Assert-JsonBoolean -Value $Report.source.trackedClean -Expected $true ` -Label 'Codex setup report trackedClean' if ([string]$Report.executable.sha256 -cne $ExpectedExecutableSha256 -or [string]$Report.artifacts.scriptSha256 -cne $ExpectedHelperSha256) { throw 'Codex setup report did not match the sealed executable or helper.' } } function New-SanitizedCodexFailureReceipt( [object]$TurnFailure, [object]$FailureStage, [object]$ProtocolCode, [object]$ChildResult, [object]$GitState, [string]$ExpectedHead, [string]$ScriptSha256, [string]$ScriptBlob, [string]$HelperSha256, [string]$HelperBlob, [string]$ExecutableSha256, [string]$ReportSha256, [object]$ProofSummary, [bool]$TemporaryRootRemoved ) { $turnFailure = Assert-SanitizedCodexTurnFailure -Failure $TurnFailure $failureClass = if ($null -ne $turnFailure) { 'codex-paid-turn-incomplete' } elseif ($ProofSummary.executed -eq $true) { 'codex-post-turn-invariant-failed' } else { 'codex-proof-incomplete' } $failureStage = Assert-CodexFailureStage ` -Value $FailureStage ` -AllowNull ($failureClass -eq 'codex-proof-incomplete') $protocolCode = Assert-NullableProtocolCode -Value $ProtocolCode foreach ($entry in @( [pscustomobject]@{ Value = $ScriptSha256; Label = 'failure receipt script hash' } [pscustomobject]@{ Value = $HelperSha256; Label = 'failure receipt helper hash' } [pscustomobject]@{ Value = $ExecutableSha256; Label = 'failure receipt executable hash' } [pscustomobject]@{ Value = $ReportSha256; Label = 'failure receipt child report hash' } [pscustomobject]@{ Value = $ChildResult.StdoutSha256; Label = 'failure receipt stdout hash' } [pscustomobject]@{ Value = $ChildResult.StderrSha256; Label = 'failure receipt stderr hash' } )) { Assert-Sha256 -Value $entry.Value -Label ([string]$entry.Label) } foreach ($entry in @( [pscustomobject]@{ Value = $ScriptBlob; Label = 'failure receipt script blob' } [pscustomobject]@{ Value = $HelperBlob; Label = 'failure receipt helper blob' } [pscustomobject]@{ Value = $GitState.Tree; Label = 'failure receipt source tree' } )) { if (-not ($entry.Value -is [string]) -or [string]$entry.Value -cnotmatch '^[0-9a-f]{40}$') { throw "$($entry.Label) must be a lowercase 40-character Git object id." } } if (-not $TemporaryRootRemoved) { throw 'Codex failure receipt requires completed temporary cleanup.' } $failureProjection = [ordered]@{ class = $failureClass lane = 'codex' protocolCode = $protocolCode stage = $failureStage turn = if ($null -eq $turnFailure) { $null } else { [pscustomobject]$turnFailure } } Assert-SanitizedCodexFailureProjection -Failure ([pscustomobject]$failureProjection) return [ordered]@{ schemaVersion = 1 kind = 'windows-official-auth-codex-failure' pass = $false source = [ordered]@{ expectedHead = $ExpectedHead observedHead = [string]$GitState.Head tree = [string]$GitState.Tree trackedClean = [bool]$GitState.TrackedClean scriptSha256 = $ScriptSha256 scriptBlob = $ScriptBlob codexHelperSha256 = $HelperSha256 codexHelperBlob = $HelperBlob codexExecutableSha256 = $ExecutableSha256 } child = [ordered]@{ exitCode = $ChildResult.ExitCode timedOut = [bool]$ChildResult.TimedOut durationMs = [long]$ChildResult.DurationMs stdoutBytes = [long]$ChildResult.StdoutBytes stdoutSha256 = [string]$ChildResult.StdoutSha256 stderrBytes = [long]$ChildResult.StderrBytes stderrSha256 = [string]$ChildResult.StderrSha256 reportSha256 = $ReportSha256 } failure = $failureProjection proof = [ordered]@{ pass = [bool]$ProofSummary.pass paidCalls = [long]$ProofSummary.paidCalls offlinePass = [bool]$ProofSummary.offlinePass offlinePaidCalls = [long]$ProofSummary.offlinePaidCalls attempted = [bool]$ProofSummary.attempted executed = [bool]$ProofSummary.executed normalTextCompleted = [bool]$ProofSummary.normalTextCompleted toolEventsObserved = [long]$ProofSummary.toolEventsObserved unknownEventsObserved = [long]$ProofSummary.unknownEventsObserved mcpBoundaryUnchanged = [bool]$ProofSummary.mcpBoundaryUnchanged } cleanup = [ordered]@{ temporaryRootRemoved = $true rawOutputsPersisted = $false childReportPersisted = $false } } } function Invoke-CodexProofValidatorSelfTest { $executableHash = 'a' * 64 $cliHash = 'b' * 64 $powerShellHash = 'c' * 64 $markerHash = 'd' * 64 $sessionHash = 'e' * 64 $graphHash = 'f' * 64 $catalogHash = '1' * 64 $otherHash = '2' * 64 $helperHash = '3' * 64 $expectedHead = '0' * 40 $gitObject = 'a' * 40 $validValue = [ordered]@{ schemaVersion = 1 kind = 'codex-tool-denial-and-official-auth' pass = $true paidCalls = 1 diagnostic = [ordered]@{ modelCalls = 1 turnStartCalls = 1 failureStage = $null protocolCode = $null completedStage = 'post-turn-invariants' closeClean = $true } proof = [ordered]@{ executed = $true; pass = $true; paidCalls = 0 } source = [ordered]@{ expectedHead = $expectedHead observedHead = $expectedHead tree = $gitObject scriptBlob = $gitObject trackedClean = $true unchanged = $true } executable = [ordered]@{ sha256 = $executableHash; unchanged = $true } modelCatalog = [ordered]@{ sealedSha256 = $catalogHash; controlSha256 = $otherHash } invocation = [ordered]@{ argumentsSha256 = $otherHash configSha256 = $otherHash threadParamsSha256 = $otherHash turnParamsSha256 = $otherHash mcpServerCount = 4 mcpServerNamesSha256 = $otherHash } hooks = [ordered]@{ expectedCount = 5 extraCount = 0 warnings = 0 errors = 0 allTrusted = $true unchangedAfterPin = $true artifactsUnchanged = $true graphSha256 = $graphHash denyHookSha256 = $otherHash artifactsSha256 = $otherHash cliSha256 = $cliHash } mcpBoundary = [ordered]@{ count = 4 namesSha256 = $otherHash initialSha256 = $otherHash prePaidSha256 = $otherHash postPaidSha256 = $otherHash initialConfigSha256 = $otherHash prePaidConfigSha256 = $otherHash postPaidConfigSha256 = $otherHash unchanged = $true } sealed = [ordered]@{ topLevelToolCount = 0 additionalToolCount = 0 emptySchema = $true normalTextCompleted = $true } red = [ordered]@{ toolNames = @('view_image') toolLoopObserved = $true readObserved = $false sensitiveDataObserved = $false } green = [ordered]@{ deniedBeforeRead = $true sensitiveDataObserved = $false normalTextCompleted = $true denialReasonSha256 = $otherHash } paidInvocation = [ordered]@{ attempted = $true executed = $true normalTextCompleted = $true toolEventsObserved = 0 unknownEventsObserved = 0 argumentsSha256 = $otherHash modelCatalogSha256 = $catalogHash hookGraphSha256 = $graphHash markerSha256 = $markerHash sessionIdSha256 = $sessionHash threadParamsSha256 = $otherHash turnParamsSha256 = $otherHash } artifacts = [ordered]@{ scriptSha256 = $helperHash scriptUnchanged = $true windowsPowerShellSha256 = $powerShellHash windowsSystemDirectorySha256 = $otherHash } } $copy = { param($Value) return ($Value | ConvertTo-Json -Depth 50 | ConvertFrom-Json -Depth 50) } $validate = { param($Value) Assert-CodexToolDenialProof -Proof $Value ` -ExpectedExecutableSha256 $executableHash ` -ExpectedHelperSha256 $helperHash ` -ExpectedHead $expectedHead ` -ExpectedHiveMindCliSha256 $cliHash ` -ExpectedMarkerSha256 $markerHash ` -ExpectedPowerShellSha256 $powerShellHash ` -ExpectedSessionIdSha256 $sessionHash } $valid = & $copy $validValue & $validate $valid $cases = 1 $reject = { param($Fixture, [string]$Label) $accepted = $true try { & $validate $Fixture } catch { $accepted = $false } if ($accepted) { throw "Codex proof validator accepted invalid fixture: $Label" } $script:codexProofSelfTestCases += 1 } $script:codexProofSelfTestCases = $cases $fixture = & $copy $validValue; $fixture.kind = 'wrong'; & $reject $fixture 'kind' $fixture = & $copy $validValue; $fixture.schemaVersion = '1'; & $reject $fixture 'string schemaVersion' $fixture = & $copy $validValue; $fixture.pass = 1; & $reject $fixture 'numeric pass' $fixture = & $copy $validValue; $fixture.paidCalls = '1'; & $reject $fixture 'string paidCalls' $fixture = & $copy $validValue; $fixture.invocation.mcpServerCount = '4'; & $reject $fixture 'string MCP server count' $fixture = & $copy $validValue; $fixture.invocation.mcpServerNamesSha256 = 'bad'; & $reject $fixture 'MCP server inventory hash' $fixture = & $copy $validValue; $fixture.hooks.extraCount = 1; & $reject $fixture 'extra hook' $fixture = & $copy $validValue; $fixture.hooks.warnings = 1; & $reject $fixture 'hook warning' $fixture = & $copy $validValue; $fixture.hooks.allTrusted = 1; & $reject $fixture 'numeric hook trust' $fixture = & $copy $validValue; $fixture.sealed.topLevelToolCount = 1; & $reject $fixture 'non-empty tools' $fixture = & $copy $validValue; $fixture.red.toolLoopObserved = $false; & $reject $fixture 'RED tool loop' $fixture = & $copy $validValue; $fixture.green.sensitiveDataObserved = $true; & $reject $fixture 'GREEN data leak' $fixture = & $copy $validValue; $fixture.paidInvocation.toolEventsObserved = 1; & $reject $fixture 'paid tool event' $fixture = & $copy $validValue; $fixture.paidInvocation.unknownEventsObserved = 1; & $reject $fixture 'unknown event' $fixture = & $copy $validValue; $fixture.executable.sha256 = $otherHash; & $reject $fixture 'executable mismatch' $fixture = & $copy $validValue; $fixture.hooks.cliSha256 = $otherHash; & $reject $fixture 'CLI mismatch' $fixture = & $copy $validValue; $fixture.artifacts.windowsPowerShellSha256 = $otherHash; & $reject $fixture 'PowerShell mismatch' $fixture = & $copy $validValue; $fixture.paidInvocation.modelCatalogSha256 = $otherHash; & $reject $fixture 'catalog mismatch' $fixture = & $copy $validValue; $fixture.paidInvocation.hookGraphSha256 = $otherHash; & $reject $fixture 'graph mismatch' $fixture = & $copy $validValue; $fixture.paidInvocation.markerSha256 = $otherHash; & $reject $fixture 'marker mismatch' $fixture = & $copy $validValue; $fixture.paidInvocation.sessionIdSha256 = $otherHash; & $reject $fixture 'session mismatch' $fixture = & $copy $validValue; $fixture.source.expectedHead = 'f' * 40; & $reject $fixture 'source HEAD mismatch' $fixture = & $copy $validValue; $fixture.artifacts.scriptSha256 = $otherHash; & $reject $fixture 'helper hash mismatch' $fixture = & $copy $validValue; $fixture.hooks.artifactsUnchanged = $false; & $reject $fixture 'hook artifact drift' $fixture = & $copy $validValue; $fixture.paidInvocation.threadParamsSha256 = $graphHash; & $reject $fixture 'paid thread mismatch' $fixture = & $copy $validValue; $fixture.mcpBoundary.prePaidSha256 = $graphHash; & $reject $fixture 'pre-paid MCP drift' $validFailure = [pscustomobject][ordered]@{ status = 'failed' code = 'unauthorized' httpStatusCode = $null willRetry = $false } $validChild = [pscustomobject]@{ ExitCode = 1 TimedOut = $false DurationMs = 123 Stdout = 'C:\secret\auth.json sk-self-test' Stderr = 'file:///private/detail sess-self-test' StdoutBytes = 40 StderrBytes = 40 StdoutSha256 = $otherHash StderrSha256 = $catalogHash } $validGit = [pscustomobject]@{ Head = $expectedHead Tree = $gitObject TrackedClean = $true } $validProofSummary = [pscustomobject]@{ pass = $false paidCalls = 1 offlinePass = $true offlinePaidCalls = 0 attempted = $true executed = $false normalTextCompleted = $false toolEventsObserved = 0 unknownEventsObserved = 0 mcpBoundaryUnchanged = $false } $failureReceipt = New-SanitizedCodexFailureReceipt ` -TurnFailure $validFailure -FailureStage 'thread-start' -ProtocolCode ([int]-32603) ` -ChildResult $validChild -GitState $validGit ` -ExpectedHead $expectedHead -ScriptSha256 $helperHash -ScriptBlob $gitObject ` -HelperSha256 $helperHash -HelperBlob $gitObject -ExecutableSha256 $executableHash ` -ReportSha256 $otherHash -ProofSummary $validProofSummary -TemporaryRootRemoved $true $failureReceiptJson = $failureReceipt | ConvertTo-Json -Depth 20 -Compress if ($failureReceipt.kind -cne 'windows-official-auth-codex-failure' -or $failureReceipt.pass -ne $false -or $failureReceiptJson -match '(?i)(?:auth\.json|file:///|sk-|sess-)') { throw 'Sanitized Codex failure receipt retained untrusted diagnostic text.' } $script:codexProofSelfTestCases += 1 $normalizedFailure = Assert-SanitizedCodexTurnFailure -Failure $validFailure $roundTripReceipt = New-SanitizedCodexFailureReceipt ` -TurnFailure $normalizedFailure -FailureStage 'thread-start' -ProtocolCode ([int]-32603) ` -ChildResult $validChild -GitState $validGit ` -ExpectedHead $expectedHead -ScriptSha256 $helperHash -ScriptBlob $gitObject ` -HelperSha256 $helperHash -HelperBlob $gitObject -ExecutableSha256 $executableHash ` -ReportSha256 $otherHash -ProofSummary $validProofSummary -TemporaryRootRemoved $true if ($roundTripReceipt.failure.turn.status -cne 'failed' -or $roundTripReceipt.failure.turn.code -cne 'unauthorized') { throw 'Sanitized Codex failure receipt did not accept normalized turn failure.' } $script:codexProofSelfTestCases += 1 $failureReject = { param($Fixture, [string]$Label) $accepted = $true try { $null = Assert-SanitizedCodexTurnFailure -Failure $Fixture } catch { $accepted = $false } if ($accepted) { throw "Codex turn-failure validator accepted invalid fixture: $Label" } $script:codexProofSelfTestCases += 1 } $fixture = & $copy $validFailure; $fixture.status = 'completed'; & $failureReject $fixture 'completed status' $fixture = & $copy $validFailure; $fixture.code = 'futureCode'; & $failureReject $fixture 'unknown code' $fixture = & $copy $validFailure; $fixture.code = 'httpConnectionFailed'; $fixture.httpStatusCode = 70000; & $failureReject $fixture 'HTTP status range' $fixture = & $copy $validFailure; $fixture.willRetry = 'false'; & $failureReject $fixture 'string retry flag' $fixture = & $copy $validFailure; $fixture | Add-Member -NotePropertyName message -NotePropertyValue 'C:\secret\auth.json sk-fixture'; & $failureReject $fixture 'message property' $fixture = & $copy $validFailure; $fixture | Add-Member -NotePropertyName additionalDetails -NotePropertyValue 'file:///private/detail'; & $failureReject $fixture 'additional details property' $fixture = & $copy $validFailure; $fixture.httpStatusCode = 401; & $failureReject $fixture 'HTTP status on simple code' if ([string]$failureReceipt.failure.stage -cne 'thread-start' -or [int]$failureReceipt.failure.protocolCode -ne -32603 -or [string]::Join(',', @($failureReceipt.failure.Keys | Sort-Object)) -cne 'class,lane,protocolCode,stage,turn') { throw 'Valid staged Codex failure receipt did not retain the safe fixed diagnostics.' } $script:codexProofSelfTestCases += 1 $proofIncompleteSummary = & $copy $validProofSummary $proofIncompleteSummary.paidCalls = 0 $proofIncompleteSummary.attempted = $false $proofIncompleteSummary.executed = $false $proofIncompleteReceipt = New-SanitizedCodexFailureReceipt ` -TurnFailure $null -FailureStage $null -ProtocolCode $null ` -ChildResult $validChild -GitState $validGit ` -ExpectedHead $expectedHead -ScriptSha256 $helperHash -ScriptBlob $gitObject ` -HelperSha256 $helperHash -HelperBlob $gitObject -ExecutableSha256 $executableHash ` -ReportSha256 $otherHash -ProofSummary $proofIncompleteSummary -TemporaryRootRemoved $true $proofIncompleteFailure = [pscustomobject]$proofIncompleteReceipt.failure Assert-SanitizedCodexFailureProjection -Failure $proofIncompleteFailure if ([string]$proofIncompleteFailure.class -cne 'codex-proof-incomplete' -or $null -ne $proofIncompleteFailure.stage -or $null -ne $proofIncompleteFailure.protocolCode) { throw 'Codex proof-incomplete failure receipt did not preserve safe null lifecycle diagnostics.' } $script:codexProofSelfTestCases += 1 $postTurnInvariantSummary = & $copy $validProofSummary $postTurnInvariantSummary.executed = $true $postTurnAccepted = $true try { $null = New-SanitizedCodexFailureReceipt ` -TurnFailure $null -FailureStage $null -ProtocolCode $null ` -ChildResult $validChild -GitState $validGit ` -ExpectedHead $expectedHead -ScriptSha256 $helperHash -ScriptBlob $gitObject ` -HelperSha256 $helperHash -HelperBlob $gitObject -ExecutableSha256 $executableHash ` -ReportSha256 $otherHash -ProofSummary $postTurnInvariantSummary -TemporaryRootRemoved $true } catch { $postTurnAccepted = $false } if ($postTurnAccepted) { throw 'Codex post-turn invariant failure accepted null lifecycle diagnostics.' } $script:codexProofSelfTestCases += 1 $stageReject = { param($Value, [string]$Label) $accepted = $true try { $null = Assert-CodexFailureStage -Value $Value } catch { $accepted = $false } if ($accepted) { throw "Codex stage validator accepted invalid fixture: $Label" } $script:codexProofSelfTestCases += 1 } & $stageReject 'future-stage' 'unknown stage' $protocolReject = { param($Value, [string]$Label) $accepted = $true try { $null = Assert-NullableProtocolCode -Value $Value } catch { $accepted = $false } if ($accepted) { throw "Codex protocol-code validator accepted invalid fixture: $Label" } $script:codexProofSelfTestCases += 1 } & $protocolReject '-32603' 'string code' & $protocolReject ([double]-32603.5) 'fractional code' & $protocolReject ([long][int]::MinValue - 1) 'low overflow' & $protocolReject ([long][int]::MaxValue + 1) 'high overflow' $projectionReject = { param($Fixture, [string]$Label) $accepted = $true try { Assert-SanitizedCodexFailureProjection -Failure $Fixture } catch { $accepted = $false } if ($accepted) { throw "Codex failure projection accepted invalid fixture: $Label" } $script:codexProofSelfTestCases += 1 } $fixture = & $copy $failureReceipt.failure $fixture | Add-Member -NotePropertyName message -NotePropertyValue 'C:\secret\auth.json' & $projectionReject $fixture 'vendor message' $fixture = & $copy $failureReceipt.failure $fixture | Add-Member -NotePropertyName data -NotePropertyValue 'file:///private/detail' & $projectionReject $fixture 'vendor data' $setupReportPath = [IO.Path]::GetFullPath((Join-Path ([IO.Path]::GetTempPath()) 'codex-setup-selftest\report.json')) $validSetupSummary = [pscustomobject][ordered]@{ pass = $true paidCalls = 0 markerMatched = $false reportPath = $setupReportPath reportSha256 = $otherHash sessionId = $null turnFailure = $null failureStage = $null protocolCode = $null completedStage = 'thread-start' modelCalls = 0 turnStartCalls = 0 closeClean = $true } $validSetupReport = [pscustomobject][ordered]@{ schemaVersion = 1 kind = 'codex-tool-denial-and-official-auth' pass = $true paidCalls = 0 diagnostic = [pscustomobject][ordered]@{ modelCalls = 0 turnStartCalls = 0 failureStage = $null protocolCode = $null completedStage = 'thread-start' closeClean = $true } proof = [pscustomobject][ordered]@{ executed = $false; pass = $null; paidCalls = 0 } source = [pscustomobject][ordered]@{ expectedHead = $expectedHead observedHead = $expectedHead tree = $gitObject scriptBlob = $gitObject trackedClean = $true } executable = [pscustomobject][ordered]@{ sha256 = $executableHash } artifacts = [pscustomobject][ordered]@{ scriptSha256 = $helperHash } } $validateSetup = { param($Summary, $Report) Assert-CodexSetupPreflight -Summary $Summary -Report $Report ` -ExpectedHead $expectedHead -ExpectedTree $gitObject -ExpectedHelperBlob $gitObject ` -ExpectedHelperSha256 $helperHash -ExpectedExecutableSha256 $executableHash ` -ExpectedReportPath $setupReportPath -ExpectedReportSha256 $otherHash } & $validateSetup (& $copy $validSetupSummary) (& $copy $validSetupReport) $script:codexProofSelfTestCases += 1 $setupReject = { param($Summary, [string]$Label) $accepted = $true try { & $validateSetup $Summary (& $copy $validSetupReport) } catch { $accepted = $false } if ($accepted) { throw "Codex setup validator accepted invalid fixture: $Label" } $script:codexProofSelfTestCases += 1 } $fixture = & $copy $validSetupSummary; $fixture.paidCalls = 1; & $setupReject $fixture 'paid call' $fixture = & $copy $validSetupSummary; $fixture.modelCalls = 1; & $setupReject $fixture 'model call' $fixture = & $copy $validSetupSummary; $fixture.turnStartCalls = 1; & $setupReject $fixture 'turn start call' $setupReportReject = { param($Report, [string]$Label) $accepted = $true try { & $validateSetup (& $copy $validSetupSummary) $Report } catch { $accepted = $false } if ($accepted) { throw "Codex setup validator accepted invalid report fixture: $Label" } $script:codexProofSelfTestCases += 1 } $fixture = & $copy $validSetupReport; $fixture.proof.executed = $true & $setupReportReject $fixture 'offline proof executed' $fixture = & $copy $validSetupReport; $fixture.proof.pass = $true & $setupReportReject $fixture 'offline proof pass' return [pscustomobject]@{ pass = $true paidCalls = 0 cases = $script:codexProofSelfTestCases } } function Get-Utf8ByteCount([string]$Value) { return [Text.Encoding]::UTF8.GetByteCount($Value) } function Get-RegularFileHash([string]$Path, [string]$Label) { if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "$Label is missing." } $item = Get-Item -LiteralPath $Path -Force if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw "$Label must be a regular file, not a reparse point." } return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant() } function Resolve-Application([string]$Name) { $command = Get-Command -Name $Name -CommandType Application -ErrorAction Stop | Select-Object -First 1 $null = Get-RegularFileHash -Path $command.Source -Label "$Name executable" return $command.Source } function Get-GitState([string]$RepositoryRoot) { $head = (& git.exe -C $RepositoryRoot rev-parse HEAD 2>$null).Trim() if ($LASTEXITCODE -ne 0) { throw 'Could not read repository HEAD.' } $tree = (& git.exe -C $RepositoryRoot rev-parse 'HEAD^{tree}' 2>$null).Trim() if ($LASTEXITCODE -ne 0) { throw 'Could not read repository tree.' } $status = [string]::Join("`n", @(& git.exe -C $RepositoryRoot status --porcelain=v1 --untracked-files=no 2>$null)).Trim() if ($LASTEXITCODE -ne 0) { throw 'Could not read tracked repository status.' } return [pscustomobject]@{ Head = $head; Tree = $tree; TrackedClean = $status.Length -eq 0 } } function Get-ScriptProvenance([string]$RepositoryRoot, [string]$ScriptPath) { $relativePath = [IO.Path]::GetRelativePath($RepositoryRoot, $ScriptPath).Replace('\', '/') if ($relativePath.StartsWith('../', [StringComparison]::Ordinal) -or [IO.Path]::IsPathRooted($relativePath)) { throw 'Official-auth canary script must be inside the repository.' } $trackedPath = [string]::Join("`n", @( & git.exe -C $RepositoryRoot ls-files --error-unmatch -- $relativePath 2>$null )).Trim() if ($LASTEXITCODE -ne 0 -or $trackedPath -cne $relativePath) { throw 'Official-auth canary script must be tracked at ExpectedHead.' } $expectedBlob = (& git.exe -C $RepositoryRoot rev-parse "$ExpectedHead`:$relativePath" 2>$null).Trim() if ($LASTEXITCODE -ne 0 -or $expectedBlob -notmatch '^[0-9a-f]{40}$') { throw 'Could not resolve the official-auth canary script blob at ExpectedHead.' } $workingBlob = (& git.exe -C $RepositoryRoot hash-object "--path=$relativePath" -- $ScriptPath 2>$null).Trim() if ($LASTEXITCODE -ne 0 -or $workingBlob -notmatch '^[0-9a-f]{40}$') { throw 'Could not hash the filtered official-auth canary script working file.' } if ($workingBlob -cne $expectedBlob) { throw 'Official-auth canary script working blob does not match ExpectedHead.' } return [pscustomobject]@{ RelativePath = $relativePath Blob = $expectedBlob } } function Assert-ExpectedGitState($State) { if ($State.Head -cne $ExpectedHead) { throw "Expected HEAD $ExpectedHead but observed $($State.Head)." } if (-not $State.TrackedClean) { throw 'Tracked repository state must be clean.' } } function Invoke-CapturedProcess( [string]$FilePath, [string[]]$ArgumentList, [string]$WorkingDirectory, [hashtable]$EnvironmentOverrides, [string[]]$BlankEnvironmentNames, [int]$TimeoutSeconds ) { $start = [Diagnostics.ProcessStartInfo]::new() $start.FileName = $FilePath $start.WorkingDirectory = $WorkingDirectory $start.UseShellExecute = $false $start.CreateNoWindow = $true $start.RedirectStandardOutput = $true $start.RedirectStandardError = $true foreach ($argument in $ArgumentList) { $null = $start.ArgumentList.Add($argument) } foreach ($entry in $EnvironmentOverrides.GetEnumerator()) { $start.Environment[[string]$entry.Key] = [string]$entry.Value } foreach ($name in @($start.Environment.Keys)) { if ([string]$name -match '(?i)(?:API_KEY|AUTH_TOKEN|ACCESS_TOKEN|_TOKEN)$') { $start.Environment[[string]$name] = '' } } foreach ($name in $BlankEnvironmentNames) { $start.Environment[$name] = '' } $profilePreserved = $true foreach ($name in $profileEnvironmentNames) { $parentHas = [Environment]::GetEnvironmentVariables().Contains($name) $childHas = $start.Environment.ContainsKey($name) if ($parentHas -ne $childHas) { $profilePreserved = $false; continue } if ($parentHas -and $start.Environment[$name] -cne [Environment]::GetEnvironmentVariable($name)) { $profilePreserved = $false } } if (-not $profilePreserved) { throw 'A child process would override a user profile environment variable.' } $process = [Diagnostics.Process]::new() $process.StartInfo = $start $startedAt = [DateTimeOffset]::UtcNow try { if (-not $process.Start()) { throw 'Process did not start.' } $stdoutTask = $process.StandardOutput.ReadToEndAsync() $stderrTask = $process.StandardError.ReadToEndAsync() $timedOut = -not $process.WaitForExit($TimeoutSeconds * 1000) if ($timedOut) { try { $process.Kill($true) } catch { } $process.WaitForExit() } $stdout = $stdoutTask.GetAwaiter().GetResult() $stderr = $stderrTask.GetAwaiter().GetResult() return [pscustomobject]@{ ExitCode = if ($timedOut) { $null } else { $process.ExitCode } TimedOut = $timedOut DurationMs = [int]([DateTimeOffset]::UtcNow - $startedAt).TotalMilliseconds Stdout = $stdout Stderr = $stderr StdoutBytes = Get-Utf8ByteCount $stdout StderrBytes = Get-Utf8ByteCount $stderr StdoutSha256 = Get-Sha256Text $stdout StderrSha256 = Get-Sha256Text $stderr ProfileEnvironmentPreserved = $profilePreserved } } finally { $process.Dispose() } } function Assert-ProcessPassed($Result, [string]$Label) { if ($Result.TimedOut) { throw "$Label timed out." } if ($Result.ExitCode -ne 0) { throw "$Label exited non-zero ($($Result.ExitCode))." } } function Remove-OwnedFile([string]$Path, [string]$OwnedRoot) { if (-not (Test-Path -LiteralPath $Path)) { return } $resolved = [IO.Path]::GetFullPath($Path) $prefix = [IO.Path]::GetFullPath($OwnedRoot).TrimEnd( [IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar ) + [IO.Path]::DirectorySeparatorChar if (-not $resolved.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) { throw 'Refusing to remove a file outside the run-owned root.' } $item = Get-Item -LiteralPath $resolved -Force if ($item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'Refusing to remove a non-regular run artifact.' } [IO.File]::Delete($resolved) } function Remove-OwnedDirectory([string]$Path, [string]$RequiredParent) { if (-not (Test-Path -LiteralPath $Path)) { return } $resolved = [IO.Path]::GetFullPath($Path) $prefix = [IO.Path]::GetFullPath($RequiredParent).TrimEnd( [IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar ) + [IO.Path]::DirectorySeparatorChar if (-not $resolved.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) { throw 'Refusing to remove a directory outside the run-owned parent.' } $item = Get-Item -LiteralPath $resolved -Force if (-not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'Refusing to remove an unsafe run directory.' } $reparse = Get-ChildItem -LiteralPath $resolved -Force -Recurse -Attributes ReparsePoint -ErrorAction SilentlyContinue | Select-Object -First 1 if ($null -ne $reparse) { throw 'Refusing cleanup because the run directory contains a reparse point.' } $lastError = $null for ($attempt = 0; $attempt -lt 20; $attempt += 1) { try { [IO.Directory]::Delete($resolved, $true) return } catch { $lastError = $_ Start-Sleep -Milliseconds 250 } } throw $lastError } function Write-JsonCreateNew([string]$Path, $Value) { $json = ConvertTo-Json -InputObject $Value -Depth 20 $stream = [IO.File]::Open($Path, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None) try { $writer = [IO.StreamWriter]::new($stream, [Text.UTF8Encoding]::new($false)) try { $writer.Write($json); $writer.Write("`n"); $writer.Flush(); $stream.Flush($true) } finally { $writer.Dispose() } } finally { $stream.Dispose() } } function Initialize-IsolatedMindWorkspace([string]$MindRoot, [string]$WorkspaceId) { $workspaceDir = Join-Path (Join-Path $MindRoot 'workspaces') $WorkspaceId $null = [IO.Directory]::CreateDirectory($workspaceDir) Write-JsonCreateNew -Path (Join-Path $workspaceDir 'workspace.json') -Value ([ordered]@{ id = $WorkspaceId }) return Join-Path $workspaceDir 'workspace.mind' } function Get-MarkerCaptureEvidence( [string]$NodeExe, [string]$BetterSqliteEntry, [string]$MindPath, [string]$Marker, [string]$Source, [string]$SessionId, [string]$WorkingDirectory, [hashtable]$EnvironmentOverrides, [string[]]$BlankEnvironmentNames ) { $mindSha256 = Get-RegularFileHash -Path $MindPath -Label "$Source isolated workspace mind" $inspectionSource = @' const crypto = require('node:crypto'); const [modulePath, mindPath, marker, source, sessionId] = process.argv.slice(1); const Database = require(modulePath); const db = new Database(mindPath, { readonly: true, fileMustExist: true }); try { db.pragma('query_only = ON'); const rows = db.prepare( 'SELECT content, importance, source FROM memory_frames WHERE instr(content, ?) > 0 ORDER BY id' ).all(marker); const belongsToSession = (content) => content.startsWith(`[hm session:${sessionId} `) && content.includes(` src:${source} event:`); const matchingSession = rows.filter((row) => belongsToSession(row.content)); const prompts = rows.filter((row) => belongsToSession(row.content) && row.content.includes(` src:${source} event:user-prompt-submit] `) && row.importance === 'temporary' && row.source === 'system' ); const responses = rows.filter((row) => belongsToSession(row.content) && row.content.includes(` src:${source} event:stop] `) && (row.importance === 'important' || row.importance === 'critical') && row.source === 'system' ); const hash = (value) => crypto.createHash('sha256').update(value, 'utf8').digest('hex'); process.stdout.write(JSON.stringify({ markerFrames: rows.length, matchingSessionFrames: matchingSession.length, promptFrames: prompts.length, responseFrames: responses.length, systemSourceFrames: rows.filter((row) => row.source === 'system').length, temporaryFrames: rows.filter((row) => row.importance === 'temporary').length, importantOrCriticalFrames: rows.filter((row) => row.importance === 'important' || row.importance === 'critical' ).length, promptContentSha256: prompts[0] ? hash(prompts[0].content) : null, responseContentSha256: responses[0] ? hash(responses[0].content) : null, })); if (rows.length !== 2 || matchingSession.length !== 2 || prompts.length !== 1 || responses.length !== 1) process.exitCode = 2; } finally { db.close(); } '@ $inspection = Invoke-CapturedProcess -FilePath $NodeExe -ArgumentList @( '-e', $inspectionSource, '--', $BetterSqliteEntry, $MindPath, $Marker, $Source, $SessionId ) -WorkingDirectory $WorkingDirectory -EnvironmentOverrides $EnvironmentOverrides ` -BlankEnvironmentNames $BlankEnvironmentNames -TimeoutSeconds 30 try { $evidence = $inspection.Stdout | ConvertFrom-Json -Depth 10 } catch { throw "$Source marker capture inspection did not return valid JSON." } if ($inspection.TimedOut -or $inspection.ExitCode -ne 0 -or [int]$evidence.markerFrames -ne 2 -or [int]$evidence.matchingSessionFrames -ne 2 -or [int]$evidence.promptFrames -ne 1 -or [int]$evidence.responseFrames -ne 1 -or [string]$evidence.promptContentSha256 -notmatch '^[0-9a-f]{64}$' -or [string]$evidence.responseContentSha256 -notmatch '^[0-9a-f]{64}$') { throw "$Source marker capture evidence did not satisfy the two-frame contract " + "(exit=$($inspection.ExitCode), timeout=$($inspection.TimedOut), marker=$($evidence.markerFrames), " + "session=$($evidence.matchingSessionFrames), prompt=$($evidence.promptFrames), " + "response=$($evidence.responseFrames), system=$($evidence.systemSourceFrames), " + "temporary=$($evidence.temporaryFrames), important=$($evidence.importantOrCriticalFrames))." } return [pscustomobject]@{ MindSha256 = $mindSha256 MarkerFrames = [int]$evidence.markerFrames PromptFrames = [int]$evidence.promptFrames ResponseFrames = [int]$evidence.responseFrames PromptContentSha256 = [string]$evidence.promptContentSha256 ResponseContentSha256 = [string]$evidence.responseContentSha256 } } if ($env:OS -ne 'Windows_NT') { throw 'Official-auth canaries are Windows-only.' } if ($PSVersionTable.PSVersion.Major -lt 7) { throw 'Official-auth canaries require PowerShell 7 or newer.' } if ($CodexProofValidatorSelfTest) { Invoke-CodexProofValidatorSelfTest | ConvertTo-Json -Compress return } if ([bool]$StaticPreflightOnly -eq [bool]$Execute) { throw 'Choose exactly one mode: -StaticPreflightOnly or -Execute.' } if ($Execute -and $PaidRunAck -cne $executionAcknowledgement) { throw "-Execute requires -PaidRunAck $executionAcknowledgement." } Assert-PlainIdentifier -Value $ClaudeModel -Name 'ClaudeModel' Assert-PlainIdentifier -Value $CodexModel -Name 'CodexModel' Assert-PlainIdentifier -Value $HermesProvider -Name 'HermesProvider' Assert-PlainIdentifier -Value $HermesModel -Name 'HermesModel' $repoRoot = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot '..')).Path $receiptLayout = Resolve-ReceiptLayout -RequestedPath $ReceiptDir $initialGit = Get-GitState -RepositoryRoot $repoRoot Assert-ExpectedGitState -State $initialGit $scriptProvenance = Get-ScriptProvenance -RepositoryRoot $repoRoot -ScriptPath $PSCommandPath $claudeExe = Resolve-Application -Name 'claude' $codexExe = Resolve-Application -Name 'codex.exe' $hermesExe = Resolve-Application -Name 'hermes' $nodeExe = Resolve-Application -Name 'node' $windowsPowerShell = Join-Path ([Environment]::SystemDirectory) 'WindowsPowerShell\v1.0\powershell.exe' $null = Get-RegularFileHash -Path $windowsPowerShell -Label 'OS Windows PowerShell' $scriptSha256 = Get-RegularFileHash -Path $PSCommandPath -Label 'official-auth canary script' $codexExecutableSha256 = Get-RegularFileHash -Path $codexExe -Label 'Codex executable' $artifactPaths = [ordered]@{ claudeInstaller = Join-Path $repoRoot 'packages\hive-mind-hooks-claude-code\dist\bin\claude-code-hooks-cli.js' claudeSessionStart = Join-Path $repoRoot 'packages\hive-mind-hooks-claude-code\dist\hooks\session-start.js' claudeUserPromptSubmit = Join-Path $repoRoot 'packages\hive-mind-hooks-claude-code\dist\hooks\user-prompt-submit.js' claudeStop = Join-Path $repoRoot 'packages\hive-mind-hooks-claude-code\dist\hooks\stop.js' codexInstaller = Join-Path $repoRoot 'packages\hive-mind-hooks-codex\dist\bin\codex-hooks.js' codexSessionStart = Join-Path $repoRoot 'packages\hive-mind-hooks-codex\dist\hooks\session-start.js' codexUserPromptSubmit = Join-Path $repoRoot 'packages\hive-mind-hooks-codex\dist\hooks\user-prompt-submit.js' codexStop = Join-Path $repoRoot 'packages\hive-mind-hooks-codex\dist\hooks\stop.js' codexPreCompact = Join-Path $repoRoot 'packages\hive-mind-hooks-codex\dist\hooks\pre-compact.js' codexToolDenial = Join-Path $repoRoot 'scripts\verify-codex-tool-denial.mjs' hiveMindCli = Join-Path $repoRoot 'packages\hive-mind-cli\dist\index.js' hermesInstaller = Join-Path $repoRoot 'packages\hive-mind-hooks-hermes\dist\bin\hermes-hooks.js' hermesSessionStart = Join-Path $repoRoot 'packages\hive-mind-hooks-hermes\dist\hooks\session-start.js' hermesUserPromptSubmit = Join-Path $repoRoot 'packages\hive-mind-hooks-hermes\dist\hooks\user-prompt-submit.js' hermesStop = Join-Path $repoRoot 'packages\hive-mind-hooks-hermes\dist\hooks\stop.js' betterSqlite3Entry = Join-Path $repoRoot 'node_modules\better-sqlite3\lib\index.js' } $artifactHashes = [ordered]@{} foreach ($entry in $artifactPaths.GetEnumerator()) { $artifactHashes[$entry.Key] = Get-RegularFileHash -Path $entry.Value -Label $entry.Key } $codexHelperProvenance = Get-ScriptProvenance -RepositoryRoot $repoRoot -ScriptPath $artifactPaths.codexToolDenial if ($StaticPreflightOnly) { [pscustomobject]@{ schemaVersion = 1 mode = 'static-preflight' source = [ordered]@{ head = $initialGit.Head tree = $initialGit.Tree trackedClean = $initialGit.TrackedClean scriptSha256 = $scriptSha256 scriptBlob = $scriptProvenance.Blob codexHelperSha256 = $artifactHashes.codexToolDenial codexHelperBlob = $codexHelperProvenance.Blob } receipt = [ordered]@{ freshAllowedPath = $true; created = $false } clients = [ordered]@{ claudeResolved = $true codexResolved = $true hermesResolved = $true nodeResolved = $true claudeExecutableSha256 = Get-RegularFileHash -Path $claudeExe -Label 'Claude executable' codexExecutableSha256 = $codexExecutableSha256 hermesExecutableSha256 = Get-RegularFileHash -Path $hermesExe -Label 'Hermes executable' } artifacts = $artifactHashes authCalls = 0 modelCalls = 0 } | ConvertTo-Json -Depth 10 return } $tempParent = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()) Assert-NoExistingReparsePoint -Path $tempParent -FailureMessage 'Temporary parent is a reparse point' $tempRoot = Join-Path $tempParent "waggle-official-auth-$([guid]::NewGuid().ToString('N'))" $stagingOwned = $false $tempOwned = $false $published = $false $hermesUsageRemoved = $false try { $null = [IO.Directory]::CreateDirectory($tempRoot) $tempOwned = $true $claudeWorkspace = Join-Path $tempRoot 'claude-workspace' $codexWorkspace = Join-Path $tempRoot 'codex-workspace' $hermesWorkspace = Join-Path $tempRoot 'hermes-workspace' $mindRoot = Join-Path $tempRoot 'mind' $processTemp = Join-Path $tempRoot 'process-temp' $null = [IO.Directory]::CreateDirectory($claudeWorkspace) $null = [IO.Directory]::CreateDirectory($codexWorkspace) $null = [IO.Directory]::CreateDirectory($hermesWorkspace) $null = [IO.Directory]::CreateDirectory($mindRoot) $null = [IO.Directory]::CreateDirectory($processTemp) $claudeWorkspaceId = "official-auth-claude-$([guid]::NewGuid().ToString('N'))" $codexWorkspaceId = "official-auth-codex-$([guid]::NewGuid().ToString('N'))" $hermesWorkspaceId = "official-auth-hermes-$([guid]::NewGuid().ToString('N'))" $claudeMindPath = Initialize-IsolatedMindWorkspace -MindRoot $mindRoot -WorkspaceId $claudeWorkspaceId $codexMindPath = Initialize-IsolatedMindWorkspace -MindRoot $mindRoot -WorkspaceId $codexWorkspaceId $hermesMindPath = Initialize-IsolatedMindWorkspace -MindRoot $mindRoot -WorkspaceId $hermesWorkspaceId $commonEnvironment = @{ HIVE_MIND_DATA_DIR = $mindRoot HIVE_MIND_SCOPES = 'memory:read,memory:write' HIVE_MIND_SHIM_LOG_LEVEL = 'error' WAGGLE_SIGNAL_EMIT = '0' NO_COLOR = '1' TEMP = $processTemp TMP = $processTemp } $claudeEnvironment = $commonEnvironment.Clone() $claudeEnvironment.WAGGLE_WORKSPACE_ID = $claudeWorkspaceId $codexEnvironment = $commonEnvironment.Clone() $codexEnvironment.WAGGLE_WORKSPACE_ID = $codexWorkspaceId $hermesEnvironment = $commonEnvironment.Clone() $hermesEnvironment.WAGGLE_WORKSPACE_ID = $hermesWorkspaceId $claudeVersionRaw = Invoke-CapturedProcess -FilePath $claudeExe -ArgumentList @('--version') ` -WorkingDirectory $claudeWorkspace -EnvironmentOverrides $claudeEnvironment ` -BlankEnvironmentNames $claudeAlternativeAuthNames -TimeoutSeconds 30 Assert-ProcessPassed -Result $claudeVersionRaw -Label 'Claude version preflight' $claudeVersion = ($claudeVersionRaw.Stdout -split '\r?\n' | Where-Object { $_.Trim() } | Select-Object -First 1).Trim() if ($claudeVersion -notmatch '^2\.') { throw 'Claude version preflight returned an unexpected version.' } $codexVersionRaw = Invoke-CapturedProcess -FilePath $codexExe -ArgumentList @('--version') ` -WorkingDirectory $codexWorkspace -EnvironmentOverrides $codexEnvironment ` -BlankEnvironmentNames $codexAlternativeAuthNames -TimeoutSeconds 30 Assert-ProcessPassed -Result $codexVersionRaw -Label 'Codex version preflight' $codexVersion = (($codexVersionRaw.Stdout + "`n" + $codexVersionRaw.Stderr) -split '\r?\n' | Where-Object { $_.Trim() } | Select-Object -First 1).Trim() if ($codexVersion -notmatch '^codex(?:-cli)?\s') { throw 'Codex version preflight returned an unexpected version.' } $hermesVersionRaw = Invoke-CapturedProcess -FilePath $hermesExe -ArgumentList @('--version') ` -WorkingDirectory $hermesWorkspace -EnvironmentOverrides $hermesEnvironment ` -BlankEnvironmentNames $hermesAlternativeAuthNames -TimeoutSeconds 30 Assert-ProcessPassed -Result $hermesVersionRaw -Label 'Hermes version preflight' $hermesVersion = ($hermesVersionRaw.Stdout -split '\r?\n' | Where-Object { $_ -match '^Hermes Agent v' } | Select-Object -First 1).Trim() if ([string]::IsNullOrWhiteSpace($hermesVersion)) { throw 'Hermes version preflight returned an unexpected version.' } $hookBlankNames = @( $claudeAlternativeAuthNames + $codexAlternativeAuthNames + $hermesAlternativeAuthNames | Select-Object -Unique ) $claudeHookVerify = Invoke-CapturedProcess -FilePath $nodeExe ` -ArgumentList @($artifactPaths.claudeInstaller, 'verify') -WorkingDirectory $repoRoot ` -EnvironmentOverrides $commonEnvironment -BlankEnvironmentNames $hookBlankNames -TimeoutSeconds 60 Assert-ProcessPassed -Result $claudeHookVerify -Label 'Claude hook verification' $claudeHookPasses = [regex]::Matches($claudeHookVerify.Stdout, '(?m)^\s*\[PASS\]').Count if ($claudeHookPasses -lt 11 -or $claudeHookVerify.Stdout -notmatch 'All checks passed\.') { throw 'Claude hook verification did not satisfy the 11-check contract.' } $codexHookVerify = Invoke-CapturedProcess -FilePath $nodeExe ` -ArgumentList @($artifactPaths.codexInstaller, 'verify') -WorkingDirectory $repoRoot ` -EnvironmentOverrides $commonEnvironment -BlankEnvironmentNames $hookBlankNames -TimeoutSeconds 60 Assert-ProcessPassed -Result $codexHookVerify -Label 'Codex hook verification' $codexHookPasses = [regex]::Matches($codexHookVerify.Stdout, '(?m)^\s*\[PASS\]').Count if ($codexHookPasses -lt 9 -or $codexHookVerify.Stdout -notmatch 'All checks passed\.') { throw 'Codex hook verification did not satisfy the 9-check contract.' } $hermesHookVerify = Invoke-CapturedProcess -FilePath $nodeExe ` -ArgumentList @($artifactPaths.hermesInstaller, 'verify') -WorkingDirectory $repoRoot ` -EnvironmentOverrides $commonEnvironment -BlankEnvironmentNames $hookBlankNames -TimeoutSeconds 60 Assert-ProcessPassed -Result $hermesHookVerify -Label 'Hermes hook verification' $hermesHookPasses = [regex]::Matches($hermesHookVerify.Stdout, '(?m)^\s*\[PASS\]').Count if ($hermesHookPasses -lt 8 -or $hermesHookVerify.Stdout -notmatch 'All checks passed\.') { throw 'Hermes hook verification did not satisfy the 8-check contract.' } $claudeAuthRaw = Invoke-CapturedProcess -FilePath $claudeExe -ArgumentList @('auth', 'status', '--json') ` -WorkingDirectory $claudeWorkspace -EnvironmentOverrides $claudeEnvironment ` -BlankEnvironmentNames $claudeAlternativeAuthNames -TimeoutSeconds 30 Assert-ProcessPassed -Result $claudeAuthRaw -Label 'Claude official-auth preflight' try { $claudeAuthJson = $claudeAuthRaw.Stdout | ConvertFrom-Json -Depth 20 } catch { throw 'Claude official-auth preflight did not return valid JSON.' } if ($claudeAuthJson.loggedIn -ne $true -or $claudeAuthJson.authMethod -cne 'claude.ai' -or $claudeAuthJson.apiProvider -cne 'firstParty') { throw 'Claude is not authenticated through the required first-party claude.ai client session.' } $codexAuthRaw = Invoke-CapturedProcess -FilePath $codexExe -ArgumentList @('login', 'status') ` -WorkingDirectory $codexWorkspace -EnvironmentOverrides $codexEnvironment ` -BlankEnvironmentNames $codexAlternativeAuthNames -TimeoutSeconds 30 Assert-ProcessPassed -Result $codexAuthRaw -Label 'Codex official-auth preflight' $codexAuthText = "$($codexAuthRaw.Stdout)`n$($codexAuthRaw.Stderr)" if ($codexAuthText -notmatch '(?m)^\s*Logged in using ChatGPT\s*$') { throw 'Codex is not authenticated through the required first-party ChatGPT client session.' } $hermesAuthRaw = Invoke-CapturedProcess -FilePath $hermesExe ` -ArgumentList @('auth', 'status', $HermesProvider) -WorkingDirectory $hermesWorkspace ` -EnvironmentOverrides $hermesEnvironment -BlankEnvironmentNames $hermesAlternativeAuthNames -TimeoutSeconds 30 Assert-ProcessPassed -Result $hermesAuthRaw -Label 'Hermes official-auth preflight' $escapedHermesProvider = [regex]::Escape($HermesProvider) if ($hermesAuthRaw.Stdout -notmatch "(?m)^$escapedHermesProvider`:\s+logged in\s*$") { throw 'Hermes is not logged in to the required provider.' } $codexSetupDir = Join-Path $tempRoot 'codex-setup-preflight' $codexSetupRaw = Invoke-CapturedProcess -FilePath $nodeExe -ArgumentList @( $artifactPaths.codexToolDenial, '--codex-exe', $codexExe, '--hive-mind-cli', $artifactPaths.hiveMindCli, '--receipt-dir', $codexSetupDir, '--expected-head', $ExpectedHead, '--model', $CodexModel, '--windows-powershell', $windowsPowerShell, '--workspace', $codexWorkspace, '--setup-only' ) -WorkingDirectory $codexWorkspace -EnvironmentOverrides $codexEnvironment ` -BlankEnvironmentNames $codexAlternativeAuthNames -TimeoutSeconds 120 Assert-ProcessPassed -Result $codexSetupRaw -Label 'Codex zero-inference setup preflight' try { $codexSetupSummary = $codexSetupRaw.Stdout | ConvertFrom-Json -Depth 30 } catch { throw 'Codex zero-inference setup preflight did not return valid JSON.' } $expectedCodexSetupReportPath = [IO.Path]::GetFullPath((Join-Path $codexSetupDir 'report.json')) if (-not ($codexSetupSummary.reportPath -is [string]) -or [IO.Path]::GetFullPath([string]$codexSetupSummary.reportPath) -cne $expectedCodexSetupReportPath -or -not (Test-Path -LiteralPath $expectedCodexSetupReportPath -PathType Leaf)) { throw 'Codex setup report path was missing or escaped the owned directory.' } $codexSetupReportItem = Get-Item -LiteralPath $expectedCodexSetupReportPath -Force if (($codexSetupReportItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 -or $codexSetupReportItem.Length -gt 2MB) { throw 'Codex setup report is unsafe or too large.' } $codexSetupReportSha256 = Get-RegularFileHash ` -Path $expectedCodexSetupReportPath -Label 'Codex setup report' $codexSetupReportBytes = [IO.File]::ReadAllBytes($expectedCodexSetupReportPath) try { $codexSetupReport = [Text.Encoding]::UTF8.GetString($codexSetupReportBytes) | ConvertFrom-Json -Depth 50 } catch { throw 'Codex zero-inference setup report was not valid JSON.' } Assert-CodexSetupPreflight -Summary $codexSetupSummary -Report $codexSetupReport ` -ExpectedHead $ExpectedHead -ExpectedTree $initialGit.Tree ` -ExpectedHelperBlob $codexHelperProvenance.Blob ` -ExpectedHelperSha256 $artifactHashes.codexToolDenial ` -ExpectedExecutableSha256 $codexExecutableSha256 ` -ExpectedReportPath $expectedCodexSetupReportPath ` -ExpectedReportSha256 $codexSetupReportSha256 $claudeMarker = "WAGGLE_CLAUDE_OFFICIAL_AUTH_$([Security.Cryptography.RandomNumberGenerator]::GetHexString(24).ToLowerInvariant())" $claudePrompt = "Return exactly $claudeMarker and nothing else. Do not call tools." $claudeBudget = $ClaudeMaxUsd.ToString('0.00', [Globalization.CultureInfo]::InvariantCulture) $claudeRaw = Invoke-CapturedProcess -FilePath $claudeExe -ArgumentList @( '-p', $claudePrompt, '--model', $ClaudeModel, '--max-budget-usd', $claudeBudget, '--output-format', 'stream-json', '--include-hook-events', '--verbose', '--permission-mode', 'plan', '--disable-slash-commands', '--tools', '', '--no-session-persistence', '--setting-sources', 'user', '--no-chrome' ) -WorkingDirectory $claudeWorkspace -EnvironmentOverrides $claudeEnvironment ` -BlankEnvironmentNames $claudeAlternativeAuthNames -TimeoutSeconds 180 Assert-ProcessPassed -Result $claudeRaw -Label 'Claude official-auth canary' $claudeEvents = [Collections.Generic.List[object]]::new() $invalidClaudeLines = 0 foreach ($line in ($claudeRaw.Stdout -split '\r?\n')) { if ([string]::IsNullOrWhiteSpace($line)) { continue } try { $claudeEvents.Add(($line | ConvertFrom-Json -Depth 100)) } catch { $invalidClaudeLines += 1 } } if ($invalidClaudeLines -ne 0) { throw 'Claude emitted malformed stream JSON.' } $claudeResult = @($claudeEvents | Where-Object { $_.type -eq 'result' }) | Select-Object -Last 1 if ($null -eq $claudeResult -or $claudeResult.is_error -ne $false -or ([string]$claudeResult.result).Trim() -cne $claudeMarker) { throw 'Claude official-auth canary did not return the exact marker.' } $claudeCost = [decimal]$claudeResult.total_cost_usd if ($claudeCost -lt 0 -or $claudeCost -gt $ClaudeMaxUsd) { throw 'Claude exceeded the declared cost cap.' } if ([int]$claudeResult.num_turns -ne 1) { throw 'Claude official-auth canary was not exactly one turn.' } $claudeHookEvents = @($claudeEvents | Where-Object { $_.type -eq 'system' -and $_.subtype -in @('hook_started', 'hook_response') } | ForEach-Object { [string]$_.hook_event } | Where-Object { $_ } | Sort-Object -Unique) foreach ($requiredHook in @('SessionStart', 'UserPromptSubmit', 'Stop')) { if ($claudeHookEvents -notcontains $requiredHook) { throw "Claude did not report $requiredHook hook execution." } } $claudeSessionId = [string]$claudeResult.session_id if ([string]::IsNullOrWhiteSpace($claudeSessionId)) { $claudeSessionId = [string](@($claudeEvents | Where-Object { $_.session_id } | Select-Object -First 1).session_id) } if ([string]::IsNullOrWhiteSpace($claudeSessionId)) { throw 'Claude omitted the session identifier.' } $claudeCapture = Get-MarkerCaptureEvidence -NodeExe $nodeExe ` -BetterSqliteEntry $artifactPaths.betterSqlite3Entry -MindPath $claudeMindPath ` -Marker $claudeMarker -Source 'claude-code' -SessionId $claudeSessionId ` -WorkingDirectory $repoRoot -EnvironmentOverrides $claudeEnvironment ` -BlankEnvironmentNames $hookBlankNames $codexMarker = "WAGGLE_CODEX_OFFICIAL_AUTH_$([Security.Cryptography.RandomNumberGenerator]::GetHexString(24).ToLowerInvariant())" $codexProofDir = Join-Path $tempRoot 'codex-tool-denial' $codexRaw = Invoke-CapturedProcess -FilePath $nodeExe -ArgumentList @( $artifactPaths.codexToolDenial, '--codex-exe', $codexExe, '--hive-mind-cli', $artifactPaths.hiveMindCli, '--receipt-dir', $codexProofDir, '--expected-head', $ExpectedHead, '--model', $CodexModel, '--windows-powershell', $windowsPowerShell, '--workspace', $codexWorkspace, '--execute-paid', '--marker', $codexMarker, '--ack', 'I_ACKNOWLEDGE_1_CODEX_OFFICIAL_AUTH_CALL' ) -WorkingDirectory $codexWorkspace -EnvironmentOverrides $codexEnvironment ` -BlankEnvironmentNames $codexAlternativeAuthNames -TimeoutSeconds 300 if ($codexRaw.TimedOut -or $codexRaw.ExitCode -ne 0) { try { $codexFailureSummary = $codexRaw.Stdout | ConvertFrom-Json -Depth 30 } catch { throw 'Failed Codex child did not return a valid JSON summary.' } $summaryProperties = @($codexFailureSummary.PSObject.Properties.Name | Sort-Object) $expectedSummaryProperties = @( 'closeClean', 'completedStage', 'failureStage', 'markerMatched', 'modelCalls', 'paidCalls', 'pass', 'protocolCode', 'reportPath', 'reportSha256', 'sessionId', 'turnFailure', 'turnStartCalls' ) if ([string]::Join(',', $summaryProperties) -cne [string]::Join(',', $expectedSummaryProperties)) { throw 'Failed Codex child summary contains unexpected or missing properties.' } Assert-JsonBoolean -Value $codexFailureSummary.pass -Expected $false -Label 'failed Codex summary pass' if ((-not ($codexFailureSummary.paidCalls -is [int])) -and (-not ($codexFailureSummary.paidCalls -is [long]))) { throw 'Failed Codex summary paidCalls must be a JSON integer.' } if ([long]$codexFailureSummary.paidCalls -lt 0 -or [long]$codexFailureSummary.paidCalls -gt 1) { throw 'Failed Codex summary paidCalls is out of range.' } if (-not ($codexFailureSummary.markerMatched -is [bool])) { throw 'Failed Codex summary markerMatched must be a JSON boolean.' } $summaryFailureStage = Assert-CodexFailureStage ` -Value $codexFailureSummary.failureStage -AllowNull $true $summaryProtocolCode = Assert-NullableProtocolCode -Value $codexFailureSummary.protocolCode $summaryCompletedStage = Assert-CodexFailureStage ` -Value $codexFailureSummary.completedStage -AllowNull $true foreach ($entry in @( [pscustomobject]@{ Value = $codexFailureSummary.modelCalls; Label = 'modelCalls' } [pscustomobject]@{ Value = $codexFailureSummary.turnStartCalls; Label = 'turnStartCalls' } )) { if ((-not ($entry.Value -is [int])) -and (-not ($entry.Value -is [long])) -or [long]$entry.Value -lt 0 -or [long]$entry.Value -gt 1) { throw "Failed Codex summary $($entry.Label) must be a JSON integer from zero to one." } } if (-not ($codexFailureSummary.closeClean -is [bool])) { throw 'Failed Codex summary closeClean must be a JSON boolean.' } Assert-Sha256 -Value $codexFailureSummary.reportSha256 -Label 'failed Codex report hash' if (-not ($codexFailureSummary.reportPath -is [string])) { throw 'Failed Codex summary reportPath must be a string.' } $expectedCodexFailureReportPath = [IO.Path]::GetFullPath((Join-Path $codexProofDir 'report.json')) $codexFailureReportPath = [IO.Path]::GetFullPath([string]$codexFailureSummary.reportPath) if ($codexFailureReportPath -cne $expectedCodexFailureReportPath -or -not (Test-Path -LiteralPath $codexFailureReportPath -PathType Leaf)) { throw 'Failed Codex child report path was missing or escaped the owned directory.' } $codexFailureReportItem = Get-Item -LiteralPath $codexFailureReportPath -Force if (($codexFailureReportItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 -or $codexFailureReportItem.Length -gt 2MB) { throw 'Failed Codex child report is unsafe or too large.' } $codexFailureReportSha256 = Get-RegularFileHash ` -Path $codexFailureReportPath -Label 'failed Codex child report' if ($codexFailureReportSha256 -cne [string]$codexFailureSummary.reportSha256) { throw 'Failed Codex child report hash did not match its summary.' } $codexFailureReportBytes = [IO.File]::ReadAllBytes($codexFailureReportPath) try { $codexFailureReport = [Text.Encoding]::UTF8.GetString($codexFailureReportBytes) | ConvertFrom-Json -Depth 50 } catch { throw 'Failed Codex child report was not valid JSON.' } Assert-JsonInteger -Value $codexFailureReport.schemaVersion -Expected 1 ` -Label 'failed Codex report schemaVersion' if (-not ($codexFailureReport.kind -is [string]) -or [string]$codexFailureReport.kind -cne 'codex-tool-denial-and-official-auth') { throw 'Failed Codex child report kind is invalid.' } Assert-JsonBoolean -Value $codexFailureReport.pass -Expected $false -Label 'failed Codex report pass' Assert-JsonInteger -Value $codexFailureReport.paidCalls ` -Expected ([long]$codexFailureSummary.paidCalls) -Label 'failed Codex report paidCalls' if ([string]$codexFailureReport.source.expectedHead -cne $ExpectedHead -or [string]$codexFailureReport.source.observedHead -cne $ExpectedHead -or [string]$codexFailureReport.source.tree -cne $initialGit.Tree -or [string]$codexFailureReport.source.scriptBlob -cne $codexHelperProvenance.Blob) { throw 'Failed Codex child report source did not match the sealed revision.' } Assert-JsonBoolean -Value $codexFailureReport.source.trackedClean -Expected $true ` -Label 'failed Codex report trackedClean' if ([string]$codexFailureReport.executable.sha256 -cne $codexExecutableSha256 -or [string]$codexFailureReport.artifacts.scriptSha256 -cne $artifactHashes.codexToolDenial) { throw 'Failed Codex child report did not match the sealed executable or helper.' } $summaryTurnFailure = Assert-SanitizedCodexTurnFailure -Failure $codexFailureSummary.turnFailure $reportTurnFailure = Assert-SanitizedCodexTurnFailure -Failure $codexFailureReport.turnFailure if (($summaryTurnFailure | ConvertTo-Json -Compress) -cne ($reportTurnFailure | ConvertTo-Json -Compress)) { throw 'Failed Codex child summary and report disagree on the terminal status.' } $reportFailureStage = Assert-CodexFailureStage ` -Value $codexFailureReport.diagnostic.failureStage -AllowNull $true $reportProtocolCode = Assert-NullableProtocolCode -Value $codexFailureReport.diagnostic.protocolCode $reportCompletedStage = Assert-CodexFailureStage ` -Value $codexFailureReport.diagnostic.completedStage -AllowNull $true if ([string]$reportFailureStage -cne [string]$summaryFailureStage -or $reportProtocolCode -ne $summaryProtocolCode -or [string]$reportCompletedStage -cne [string]$summaryCompletedStage) { throw 'Failed Codex child summary and report disagree on lifecycle diagnostics.' } Assert-JsonInteger -Value $codexFailureReport.diagnostic.modelCalls ` -Expected ([long]$codexFailureSummary.modelCalls) -Label 'failed Codex report modelCalls' Assert-JsonInteger -Value $codexFailureReport.diagnostic.turnStartCalls ` -Expected ([long]$codexFailureSummary.turnStartCalls) -Label 'failed Codex report turnStartCalls' Assert-JsonBoolean -Value $codexFailureReport.diagnostic.closeClean ` -Expected ([bool]$codexFailureSummary.closeClean) -Label 'failed Codex report closeClean' Assert-JsonBoolean -Value $codexFailureReport.proof.executed -Expected $true ` -Label 'failed Codex offline proof executed' Assert-JsonBoolean -Value $codexFailureReport.proof.pass -Expected $true ` -Label 'failed Codex offline proof pass' Assert-JsonInteger -Value $codexFailureReport.proof.paidCalls -Expected 0 ` -Label 'failed Codex offline proof paidCalls' Assert-JsonBoolean -Value $codexFailureReport.paidInvocation.attempted -Expected $true ` -Label 'failed Codex paid attempted' foreach ($entry in @( [pscustomobject]@{ Value = $codexFailureReport.paidInvocation.executed; Label = 'executed' } [pscustomobject]@{ Value = $codexFailureReport.paidInvocation.normalTextCompleted; Label = 'normalTextCompleted' } [pscustomobject]@{ Value = $codexFailureReport.mcpBoundary.unchanged; Label = 'MCP boundary unchanged' } )) { if (-not ($entry.Value -is [bool])) { throw "Failed Codex report $($entry.Label) must be a JSON boolean." } } foreach ($entry in @( [pscustomobject]@{ Value = $codexFailureReport.paidInvocation.toolEventsObserved; Label = 'tool events' } [pscustomobject]@{ Value = $codexFailureReport.paidInvocation.unknownEventsObserved; Label = 'unknown events' } )) { if ((-not ($entry.Value -is [int])) -and (-not ($entry.Value -is [long])) -or [long]$entry.Value -lt 0) { throw "Failed Codex report $($entry.Label) must be a non-negative JSON integer." } } $failureGit = Get-GitState -RepositoryRoot $repoRoot Assert-ExpectedGitState -State $failureGit if ($failureGit.Tree -cne $initialGit.Tree) { throw 'Repository tree changed during the failed Codex canary.' } $failureScriptProvenance = Get-ScriptProvenance -RepositoryRoot $repoRoot -ScriptPath $PSCommandPath $failureHelperProvenance = Get-ScriptProvenance ` -RepositoryRoot $repoRoot -ScriptPath $artifactPaths.codexToolDenial if ($failureScriptProvenance.Blob -cne $scriptProvenance.Blob -or $failureHelperProvenance.Blob -cne $codexHelperProvenance.Blob) { throw 'Official-auth source provenance changed during the failed Codex canary.' } foreach ($entry in $artifactPaths.GetEnumerator()) { if ((Get-RegularFileHash -Path $entry.Value -Label $entry.Key) -cne [string]$artifactHashes[$entry.Key]) { throw "$($entry.Key) changed during the failed Codex canary." } } $failureProofSummary = [pscustomobject]@{ pass = $false paidCalls = [long]$codexFailureReport.paidCalls offlinePass = $true offlinePaidCalls = 0 attempted = [bool]$codexFailureReport.paidInvocation.attempted executed = [bool]$codexFailureReport.paidInvocation.executed normalTextCompleted = [bool]$codexFailureReport.paidInvocation.normalTextCompleted toolEventsObserved = [long]$codexFailureReport.paidInvocation.toolEventsObserved unknownEventsObserved = [long]$codexFailureReport.paidInvocation.unknownEventsObserved mcpBoundaryUnchanged = [bool]$codexFailureReport.mcpBoundary.unchanged } Remove-OwnedDirectory -Path $tempRoot -RequiredParent $tempParent $tempOwned = $false if (Test-Path -LiteralPath $tempRoot) { throw 'Official-auth temporary root survived failed Codex cleanup.' } $failureReceipt = New-SanitizedCodexFailureReceipt ` -TurnFailure $summaryTurnFailure -FailureStage $summaryFailureStage ` -ProtocolCode $summaryProtocolCode -ChildResult $codexRaw -GitState $failureGit ` -ExpectedHead $ExpectedHead -ScriptSha256 $scriptSha256 -ScriptBlob $scriptProvenance.Blob ` -HelperSha256 $artifactHashes.codexToolDenial -HelperBlob $codexHelperProvenance.Blob ` -ExecutableSha256 $codexExecutableSha256 -ReportSha256 $codexFailureReportSha256 ` -ProofSummary $failureProofSummary -TemporaryRootRemoved $true $null = [IO.Directory]::CreateDirectory($evidenceRoot) Assert-NoExistingReparsePoint -Path $evidenceRoot -FailureMessage 'Evidence root is a reparse point' $null = [IO.Directory]::CreateDirectory($receiptLayout.Staging) $stagingOwned = $true $failureReceiptPath = Join-Path $receiptLayout.Staging 'official-auth-failure.json' Write-JsonCreateNew -Path $failureReceiptPath -Value $failureReceipt $failureReceiptText = [IO.File]::ReadAllText($failureReceiptPath) if ($failureReceiptText -match '(?i)(?:[A-Z]:[\\/]|file:///|\\\\[^\\/\s]+[\\/]|WAGGLE_(?:CLAUDE|CODEX|HERMES)_OFFICIAL_AUTH_|(?:sk|sess)-[A-Za-z0-9_-]+)') { throw 'Sanitized failure receipt contains a forbidden path, marker, session, or credential token.' } if ($failureReceiptText -match '(?i)"(?:message|additionalDetails|reportPath|arguments|model|marker|sessionId|error|rawStdout|rawStderr)"\s*:') { throw 'Sanitized failure receipt contains a forbidden property.' } foreach ($rawOutput in @($codexRaw.Stdout, $codexRaw.Stderr)) { if (-not [string]::IsNullOrWhiteSpace([string]$rawOutput) -and $failureReceiptText.Contains([string]$rawOutput, [StringComparison]::Ordinal)) { throw 'Sanitized failure receipt retained raw child output.' } } if (Test-Path -LiteralPath $receiptLayout.Root) { throw 'Receipt destination became occupied.' } [IO.Directory]::Move($receiptLayout.Staging, $receiptLayout.Root) $stagingOwned = $false $published = $true Assert-ProcessPassed -Result $codexRaw -Label 'Codex zero-cost tool-denial proof and official-auth canary' } Assert-ProcessPassed -Result $codexRaw -Label 'Codex zero-cost tool-denial proof and official-auth canary' try { $codexSummary = $codexRaw.Stdout | ConvertFrom-Json -Depth 30 } catch { throw 'Codex zero-cost tool-denial proof did not return valid JSON.' } $codexSummaryProperties = @($codexSummary.PSObject.Properties.Name | Sort-Object) $expectedCodexSummaryProperties = @( 'closeClean', 'completedStage', 'failureStage', 'markerMatched', 'modelCalls', 'paidCalls', 'pass', 'protocolCode', 'reportPath', 'reportSha256', 'sessionId', 'turnFailure', 'turnStartCalls' ) if ([string]::Join(',', $codexSummaryProperties) -cne [string]::Join(',', $expectedCodexSummaryProperties)) { throw 'Codex success summary contains unexpected or missing properties.' } Assert-JsonBoolean -Value $codexSummary.pass -Expected $true -Label 'Codex summary pass' Assert-JsonInteger -Value $codexSummary.paidCalls -Expected 1 -Label 'Codex summary paidCalls' Assert-JsonInteger -Value $codexSummary.modelCalls -Expected 1 -Label 'Codex summary modelCalls' Assert-JsonInteger -Value $codexSummary.turnStartCalls -Expected 1 -Label 'Codex summary turnStartCalls' Assert-JsonBoolean -Value $codexSummary.closeClean -Expected $true -Label 'Codex summary closeClean' if ($null -ne $codexSummary.failureStage -or $null -ne $codexSummary.protocolCode -or $null -ne $codexSummary.turnFailure -or [string]$codexSummary.completedStage -cne 'post-turn-invariants') { throw 'Codex success summary did not complete all paid-turn invariants cleanly.' } Assert-JsonBoolean -Value $codexSummary.markerMatched -Expected $true -Label 'Codex summary markerMatched' Assert-Sha256 -Value $codexSummary.reportSha256 -Label 'Codex summary report hash' if (-not ($codexSummary.reportPath -is [string])) { throw 'Codex summary reportPath must be a string.' } if (-not ($codexSummary.sessionId -is [string])) { throw 'Codex summary sessionId must be a string.' } $codexSessionId = [string]$codexSummary.sessionId Assert-PlainIdentifier -Value $codexSessionId -Name 'Codex sessionId' $expectedCodexReportPath = [IO.Path]::GetFullPath((Join-Path $codexProofDir 'report.json')) $codexReportPath = [IO.Path]::GetFullPath([string]$codexSummary.reportPath) if ($codexReportPath -cne $expectedCodexReportPath -or -not (Test-Path -LiteralPath $codexReportPath -PathType Leaf)) { throw 'Codex proof report path was missing or escaped the owned directory.' } $codexReportSha256 = Get-RegularFileHash -Path $codexReportPath -Label 'Codex tool-denial proof report' if ($codexReportSha256 -cne [string]$codexSummary.reportSha256) { throw 'Codex proof report hash did not match the process summary.' } $codexReportBytes = [IO.File]::ReadAllBytes($codexReportPath) try { $codexDenialProof = [Text.Encoding]::UTF8.GetString($codexReportBytes) | ConvertFrom-Json -Depth 50 } catch { throw 'Codex tool-denial proof report was not valid JSON.' } Assert-CodexToolDenialProof -Proof $codexDenialProof ` -ExpectedExecutableSha256 $codexExecutableSha256 ` -ExpectedHelperSha256 $artifactHashes.codexToolDenial ` -ExpectedHead $ExpectedHead ` -ExpectedHiveMindCliSha256 $artifactHashes.hiveMindCli ` -ExpectedMarkerSha256 (Get-Sha256Text $codexMarker) ` -ExpectedPowerShellSha256 (Get-RegularFileHash -Path $windowsPowerShell -Label 'OS Windows PowerShell') ` -ExpectedSessionIdSha256 (Get-Sha256Text $codexSessionId) $codexCapture = Get-MarkerCaptureEvidence -NodeExe $nodeExe ` -BetterSqliteEntry $artifactPaths.betterSqlite3Entry -MindPath $codexMindPath ` -Marker $codexMarker -Source 'codex' -SessionId $codexSessionId ` -WorkingDirectory $repoRoot -EnvironmentOverrides $codexEnvironment ` -BlankEnvironmentNames $hookBlankNames $hermesMarker = "WAGGLE_HERMES_OFFICIAL_AUTH_$([Security.Cryptography.RandomNumberGenerator]::GetHexString(24).ToLowerInvariant())" $hermesPrompt = "Return exactly $hermesMarker and nothing else. Do not call tools." $hermesUsagePath = Join-Path $tempRoot 'hermes-usage.json' $hermesRaw = Invoke-CapturedProcess -FilePath $hermesExe -ArgumentList @( '-z', $hermesPrompt, '--usage-file', $hermesUsagePath, '--provider', $HermesProvider, '-m', $HermesModel, '--ignore-rules' ) -WorkingDirectory $hermesWorkspace -EnvironmentOverrides $hermesEnvironment ` -BlankEnvironmentNames $hermesAlternativeAuthNames -TimeoutSeconds 180 Assert-ProcessPassed -Result $hermesRaw -Label 'Hermes official-auth canary' if ($hermesRaw.Stdout.Trim() -cne $hermesMarker) { throw 'Hermes official-auth canary did not return the exact marker.' } if (-not (Test-Path -LiteralPath $hermesUsagePath -PathType Leaf)) { throw 'Hermes did not write its usage receipt.' } $hermesUsageBytes = [IO.File]::ReadAllBytes($hermesUsagePath) $hermesUsageSha256 = [Convert]::ToHexString( [Security.Cryptography.SHA256]::HashData($hermesUsageBytes) ).ToLowerInvariant() try { $hermesUsage = [Text.Encoding]::UTF8.GetString($hermesUsageBytes) | ConvertFrom-Json -Depth 20 } catch { throw 'Hermes usage receipt was not valid JSON.' } if ($hermesUsage.completed -ne $true -or $hermesUsage.failed -eq $true -or [int]$hermesUsage.api_calls -ne 1) { throw 'Hermes usage receipt did not prove exactly one completed API call.' } if ([string]$hermesUsage.provider -cne $HermesProvider -or [string]$hermesUsage.model -cne $HermesModel) { throw 'Hermes usage receipt did not match the requested provider and model.' } $hermesSessionId = [string]$hermesUsage.session_id if ([string]::IsNullOrWhiteSpace($hermesSessionId)) { throw 'Hermes usage receipt omitted the session identifier.' } $hermesCapture = Get-MarkerCaptureEvidence -NodeExe $nodeExe ` -BetterSqliteEntry $artifactPaths.betterSqlite3Entry -MindPath $hermesMindPath ` -Marker $hermesMarker -Source 'hermes' -SessionId $hermesSessionId ` -WorkingDirectory $repoRoot -EnvironmentOverrides $hermesEnvironment ` -BlankEnvironmentNames $hookBlankNames Remove-OwnedFile -Path $hermesUsagePath -OwnedRoot $tempRoot $hermesUsageRemoved = -not (Test-Path -LiteralPath $hermesUsagePath) if (-not $hermesUsageRemoved) { throw 'Hermes raw usage receipt survived cleanup.' } $finalGit = Get-GitState -RepositoryRoot $repoRoot Assert-ExpectedGitState -State $finalGit if ($finalGit.Tree -cne $initialGit.Tree) { throw 'Repository tree changed during official-auth canaries.' } $finalScriptProvenance = Get-ScriptProvenance -RepositoryRoot $repoRoot -ScriptPath $PSCommandPath if ($finalScriptProvenance.Blob -cne $scriptProvenance.Blob) { throw 'Official-auth canary script provenance changed during execution.' } $finalCodexHelperProvenance = Get-ScriptProvenance -RepositoryRoot $repoRoot -ScriptPath $artifactPaths.codexToolDenial if ($finalCodexHelperProvenance.Blob -cne $codexHelperProvenance.Blob) { throw 'Codex tool-denial helper provenance changed during execution.' } foreach ($entry in $artifactPaths.GetEnumerator()) { $finalArtifactHash = Get-RegularFileHash -Path $entry.Value -Label $entry.Key if ($finalArtifactHash -cne [string]$artifactHashes[$entry.Key]) { throw "$($entry.Key) changed during official-auth canaries." } } Remove-OwnedDirectory -Path $tempRoot -RequiredParent $tempParent $tempOwned = $false if (Test-Path -LiteralPath $tempRoot) { throw 'Official-auth temporary root survived cleanup.' } $null = [IO.Directory]::CreateDirectory($evidenceRoot) Assert-NoExistingReparsePoint -Path $evidenceRoot -FailureMessage 'Evidence root is a reparse point' $null = [IO.Directory]::CreateDirectory($receiptLayout.Staging) $stagingOwned = $true $receipt = [ordered]@{ schemaVersion = 1 kind = 'windows-official-auth-canaries' pass = $true source = [ordered]@{ expectedHead = $ExpectedHead observedHead = $finalGit.Head tree = $finalGit.Tree trackedCleanBefore = $initialGit.TrackedClean trackedCleanAfter = $finalGit.TrackedClean unchangedDuringRun = $finalGit.Head -ceq $initialGit.Head -and $finalGit.Tree -ceq $initialGit.Tree scriptSha256 = $scriptSha256 scriptBlob = $scriptProvenance.Blob codexHelperSha256 = $artifactHashes.codexToolDenial codexHelperBlob = $codexHelperProvenance.Blob } execution = [ordered]@{ serialLanes = $true modelCalls = 3 authStatusCalls = 3 freshTemporaryWorkspace = $true distinctIsolatedMindWorkspaces = $true captureInspectionReadOnly = $true rawStdoutPersisted = $false rawStderrPersisted = $false rawUsagePersisted = $false } credentials = [ordered]@{ mode = 'user-auth-in-place' authFilesReadByHarness = 0 authFilesCopied = 0 authContentsSerialized = $false profileEnvironmentOverrides = 0 profileEnvironmentPreserved = $true ambientApiTokenEnvironmentNamesBlankedByPattern = $true claudeAlternativeEnvironmentNamesBlanked = @($claudeAlternativeAuthNames) codexAlternativeEnvironmentNamesBlanked = @($codexAlternativeAuthNames) hermesAlternativeEnvironmentNamesBlanked = @($hermesAlternativeAuthNames) } clients = [ordered]@{ claude = [ordered]@{ version = $claudeVersion executableSha256 = Get-RegularFileHash -Path $claudeExe -Label 'Claude executable' auth = [ordered]@{ loggedIn = $true authMethod = [string]$claudeAuthJson.authMethod apiProvider = [string]$claudeAuthJson.apiProvider subscriptionType = [string]$claudeAuthJson.subscriptionType outputBytes = $claudeAuthRaw.StdoutBytes outputSha256 = $claudeAuthRaw.StdoutSha256 } model = $ClaudeModel maxBudgetUsd = $ClaudeMaxUsd reportedCostUsd = $claudeCost exitCode = $claudeRaw.ExitCode durationMs = $claudeRaw.DurationMs markerSha256 = Get-Sha256Text $claudeMarker markerMatched = $true numTurns = [int]$claudeResult.num_turns sessionIdSha256 = Get-Sha256Text $claudeSessionId hookEvents = $claudeHookEvents capture = [ordered]@{ markerFrames = $claudeCapture.MarkerFrames promptFrames = $claudeCapture.PromptFrames responseFrames = $claudeCapture.ResponseFrames promptContentSha256 = $claudeCapture.PromptContentSha256 responseContentSha256 = $claudeCapture.ResponseContentSha256 mindSha256 = $claudeCapture.MindSha256 } stdoutBytes = $claudeRaw.StdoutBytes stdoutSha256 = $claudeRaw.StdoutSha256 stderrBytes = $claudeRaw.StderrBytes stderrSha256 = $claudeRaw.StderrSha256 } codex = [ordered]@{ version = $codexVersion executableSha256 = $codexExecutableSha256 auth = [ordered]@{ loggedIn = $true method = 'chatgpt' stdoutBytes = $codexAuthRaw.StdoutBytes stdoutSha256 = $codexAuthRaw.StdoutSha256 stderrBytes = $codexAuthRaw.StderrBytes stderrSha256 = $codexAuthRaw.StderrSha256 } model = $CodexModel exitCode = $codexRaw.ExitCode durationMs = $codexRaw.DurationMs markerSha256 = Get-Sha256Text $codexMarker markerMatched = $true sessionIdSha256 = Get-Sha256Text $codexSessionId containment = [ordered]@{ oneShot = $true timeoutSeconds = 300 exactMarkerRequired = $true harnessProcessInvocationsRequired = 1 paidModelCallsRequired = 1 usageReceiptRequired = $false preCallCostCapAvailable = $false emptyToolSchemaRequired = $true wildcardPreToolUseBackstopRequired = $true threadEnvironmentsEmpty = $true turnEnvironmentsEmpty = $true dynamicToolsEmpty = $true selectedCapabilityRootsEmpty = $true } toolDenialProof = [ordered]@{ schemaVersion = [int]$codexDenialProof.schemaVersion proofPaidCalls = [int]$codexDenialProof.proof.paidCalls reportSha256 = $codexReportSha256 executableSha256 = [string]$codexDenialProof.executable.sha256 modelCatalogSha256 = [string]$codexDenialProof.paidInvocation.modelCatalogSha256 invocationArgumentsSha256 = [string]$codexDenialProof.invocation.argumentsSha256 configSha256 = [string]$codexDenialProof.invocation.configSha256 threadParamsSha256 = [string]$codexDenialProof.invocation.threadParamsSha256 turnParamsSha256 = [string]$codexDenialProof.invocation.turnParamsSha256 paidThreadParamsSha256 = [string]$codexDenialProof.paidInvocation.threadParamsSha256 paidTurnParamsSha256 = [string]$codexDenialProof.paidInvocation.turnParamsSha256 mcpServerCount = [int]$codexDenialProof.invocation.mcpServerCount mcpServerNamesSha256 = [string]$codexDenialProof.invocation.mcpServerNamesSha256 mcpBoundarySha256 = [string]$codexDenialProof.mcpBoundary.postPaidSha256 mcpConfigSha256 = [string]$codexDenialProof.mcpBoundary.postPaidConfigSha256 hookGraphSha256 = [string]$codexDenialProof.hooks.graphSha256 denyHookSha256 = [string]$codexDenialProof.hooks.denyHookSha256 packagedHookArtifactsSha256 = [string]$codexDenialProof.hooks.artifactsSha256 hiveMindCliSha256 = [string]$codexDenialProof.hooks.cliSha256 windowsPowerShellSha256 = [string]$codexDenialProof.artifacts.windowsPowerShellSha256 windowsSystemDirectorySha256 = [string]$codexDenialProof.artifacts.windowsSystemDirectorySha256 exactHookCount = [int]$codexDenialProof.hooks.expectedCount extraHookCount = [int]$codexDenialProof.hooks.extraCount allHooksTrusted = [bool]$codexDenialProof.hooks.allTrusted hookGraphUnchanged = [bool]$codexDenialProof.hooks.unchangedAfterPin packagedArtifactsUnchanged = [bool]$codexDenialProof.hooks.artifactsUnchanged helperScriptSha256 = [string]$codexDenialProof.artifacts.scriptSha256 helperScriptBlob = [string]$codexDenialProof.source.scriptBlob helperScriptUnchanged = [bool]$codexDenialProof.artifacts.scriptUnchanged emptySchema = [bool]$codexDenialProof.sealed.emptySchema guardedToolDeniedBeforeExecution = [bool]$codexDenialProof.green.deniedBeforeRead sensitiveDataObservedAfterDenial = [bool]$codexDenialProof.green.sensitiveDataObserved normalTextSucceeded = [bool]$codexDenialProof.green.normalTextCompleted toolEventsObserved = [int]$codexDenialProof.paidInvocation.toolEventsObserved unknownEventsObserved = [int]$codexDenialProof.paidInvocation.unknownEventsObserved } capture = [ordered]@{ markerFrames = $codexCapture.MarkerFrames promptFrames = $codexCapture.PromptFrames responseFrames = $codexCapture.ResponseFrames promptContentSha256 = $codexCapture.PromptContentSha256 responseContentSha256 = $codexCapture.ResponseContentSha256 mindSha256 = $codexCapture.MindSha256 } stdoutBytes = $codexRaw.StdoutBytes stdoutSha256 = $codexRaw.StdoutSha256 stderrBytes = $codexRaw.StderrBytes stderrSha256 = $codexRaw.StderrSha256 } hermes = [ordered]@{ version = $hermesVersion executableSha256 = Get-RegularFileHash -Path $hermesExe -Label 'Hermes executable' auth = [ordered]@{ provider = $HermesProvider loggedIn = $true outputBytes = $hermesAuthRaw.StdoutBytes outputSha256 = $hermesAuthRaw.StdoutSha256 } provider = $HermesProvider model = $HermesModel exitCode = $hermesRaw.ExitCode durationMs = $hermesRaw.DurationMs markerSha256 = Get-Sha256Text $hermesMarker markerMatched = $true sessionIdSha256 = Get-Sha256Text $hermesSessionId containment = [ordered]@{ oneShot = $true timeoutSeconds = 180 exactMarkerRequired = $true apiCallsRequired = 1 usageReceiptRequired = $true preCallCostCapAvailable = $false } capture = [ordered]@{ markerFrames = $hermesCapture.MarkerFrames promptFrames = $hermesCapture.PromptFrames responseFrames = $hermesCapture.ResponseFrames promptContentSha256 = $hermesCapture.PromptContentSha256 responseContentSha256 = $hermesCapture.ResponseContentSha256 mindSha256 = $hermesCapture.MindSha256 } usage = [ordered]@{ apiCalls = [int]$hermesUsage.api_calls inputTokens = $hermesUsage.input_tokens outputTokens = $hermesUsage.output_tokens reasoningTokens = $hermesUsage.reasoning_tokens totalTokens = $hermesUsage.total_tokens estimatedCostUsd = $hermesUsage.estimated_cost_usd costStatus = $hermesUsage.cost_status costSource = $hermesUsage.cost_source serviceTier = $hermesUsage.service_tier rawBytes = $hermesUsageBytes.Length rawSha256 = $hermesUsageSha256 } stdoutBytes = $hermesRaw.StdoutBytes stdoutSha256 = $hermesRaw.StdoutSha256 stderrBytes = $hermesRaw.StderrBytes stderrSha256 = $hermesRaw.StderrSha256 } } hooks = [ordered]@{ claude = [ordered]@{ passedChecks = $claudeHookPasses minimumChecks = 11 stdoutSha256 = $claudeHookVerify.StdoutSha256 stderrSha256 = $claudeHookVerify.StderrSha256 } codex = [ordered]@{ passedChecks = $codexHookPasses minimumChecks = 9 stdoutSha256 = $codexHookVerify.StdoutSha256 stderrSha256 = $codexHookVerify.StderrSha256 } hermes = [ordered]@{ passedChecks = $hermesHookPasses minimumChecks = 8 stdoutSha256 = $hermesHookVerify.StdoutSha256 stderrSha256 = $hermesHookVerify.StderrSha256 } artifacts = $artifactHashes } cleanup = [ordered]@{ temporaryRootRemoved = $true hermesRawUsageRemoved = $hermesUsageRemoved rawOutputsPersisted = $false } } $receiptPath = Join-Path $receiptLayout.Staging 'official-auth-receipt.json' Write-JsonCreateNew -Path $receiptPath -Value $receipt $receiptText = [IO.File]::ReadAllText($receiptPath) if ($receiptText -match '(?i)(?:[A-Z]:[\\/]|file:///|\\\\[^\\/\s]+[\\/])') { throw 'Whitelisted receipt unexpectedly contained an absolute path.' } if (Test-Path -LiteralPath $receiptLayout.Root) { throw 'Receipt destination became occupied.' } [IO.Directory]::Move($receiptLayout.Staging, $receiptLayout.Root) $stagingOwned = $false $published = $true Write-Host "Official-auth canaries passed. Receipt: $($receiptLayout.Root)" } finally { if ($tempOwned -and (Test-Path -LiteralPath $tempRoot)) { Remove-OwnedDirectory -Path $tempRoot -RequiredParent $tempParent } if ($stagingOwned -and (Test-Path -LiteralPath $receiptLayout.Staging)) { Remove-OwnedDirectory -Path $receiptLayout.Staging -RequiredParent $evidenceRoot } if (-not $published -and (Test-Path -LiteralPath $receiptLayout.Root)) { throw 'A failed run must not leave a final receipt directory.' } }