Files
waggle-os/scripts/test-windows-official-auth-canaries.ps1
Oleg Maslov b20b138fe4 moving
2026-09-02 10:14:22 +02:00

2240 lines
108 KiB
PowerShell

[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.'
}
}