scripts/t060-local-macos-smoke.ps1
|
#requires -Version 7.0 [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('Preflight', 'Invoke')] [string] $Mode, [Parameter(Mandatory)] [string] $RepoRoot, [Parameter(Mandatory)] [ValidatePattern('^[0-9a-f]{40}$')] [string] $ExpectedCommit, [Parameter(Mandatory)] [string] $OutputDirectory, [ValidatePattern('^run-[a-z0-9][a-z0-9-]{0,63}$')] [string] $RunId, [ValidateLength(1, 256)] [string] $AuthorizationRef, [ValidateRange(1, 7200)] [int] $TimeoutSeconds = 600, [switch] $AcknowledgeProviderInvocation, [string] $ExpectedRepositoryUrl = 'https://github.com/alonf/specrew.git' ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest function Invoke-T060Git { param( [Parameter(Mandatory)][string] $Root, [Parameter(Mandatory)][string[]] $ArgumentList ) $output = @(& git -C $Root @ArgumentList 2>&1) if ($LASTEXITCODE -ne 0) { throw ('t060-git-failed:{0}:{1}' -f ($ArgumentList -join '-'), (($output | ForEach-Object { [string]$_ }) -join ' ')) } return (($output | ForEach-Object { [string]$_ }) -join "`n").Trim() } function Write-T060JsonFile { param( [Parameter(Mandatory)][string] $Path, [Parameter(Mandatory)] $Value ) $json = $Value | ConvertTo-Json -Depth 30 [IO.File]::WriteAllText($Path, $json + "`n", [Text.UTF8Encoding]::new($false)) } function Get-T060FileSha256 { param([Parameter(Mandatory)][string] $Path) return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant() } if (-not $IsMacOS) { throw 't060-local-macos-smoke-requires-macos' } if ($Mode -ceq 'Invoke') { if ([string]::IsNullOrWhiteSpace($RunId)) { throw 't060-invoke-run-id-required' } if ([string]::IsNullOrWhiteSpace($AuthorizationRef)) { throw 't060-invoke-authorization-ref-required' } if (-not $AcknowledgeProviderInvocation) { throw 't060-invoke-explicit-acknowledgement-required' } } elseif ($AcknowledgeProviderInvocation -or -not [string]::IsNullOrWhiteSpace($RunId) -or -not [string]::IsNullOrWhiteSpace($AuthorizationRef)) { throw 't060-preflight-rejects-provider-authorization-inputs' } $root = (Resolve-Path -LiteralPath $RepoRoot).Path $loadPath = Join-Path $root 'scripts/internal/continuous-co-review/_load.ps1' if (-not [IO.File]::Exists($loadPath)) { throw "t060-review-runtime-missing:$loadPath" } . $loadPath $topLevel = Invoke-T060Git -Root $root -ArgumentList @('rev-parse', '--show-toplevel') $pathComparison = if ($IsWindows) { [StringComparison]::OrdinalIgnoreCase } else { [StringComparison]::Ordinal } if (-not ([IO.Path]::GetFullPath($topLevel)).Equals([IO.Path]::GetFullPath($root), $pathComparison)) { throw 't060-repo-root-mismatch' } $headBefore = (Invoke-T060Git -Root $root -ArgumentList @('rev-parse', 'HEAD^{commit}')).ToLowerInvariant() if ($headBefore -cne $ExpectedCommit) { throw "t060-pinned-commit-mismatch:expected=$ExpectedCommit:actual=$headBefore" } $originUrl = Invoke-T060Git -Root $root -ArgumentList @('remote', 'get-url', 'origin') if ($originUrl -cne $ExpectedRepositoryUrl) { throw "t060-origin-url-mismatch:expected=$ExpectedRepositoryUrl:actual=$originUrl" } $statusBefore = Invoke-T060Git -Root $root -ArgumentList @('status', '--porcelain=v1', '--untracked-files=all') if (-not [string]::IsNullOrEmpty($statusBefore)) { throw 't060-repo-must-be-clean-before-run' } $output = [IO.Path]::GetFullPath($OutputDirectory) if (Test-ContinuousCoReviewPathUnderRoot -Path $output -Root $root) { throw 't060-output-directory-must-be-outside-repo' } if ([IO.Directory]::Exists($output) -and @(Get-ChildItem -LiteralPath $output -Force).Count -gt 0) { throw 't060-output-directory-must-be-empty' } [IO.Directory]::CreateDirectory($output) | Out-Null $codexCommand = Get-Command -Name codex -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 if ($null -eq $codexCommand) { throw 't060-codex-cli-not-installed' } $versionOutput = @(& $codexCommand.Source --version 2>&1) if ($LASTEXITCODE -ne 0) { throw 't060-codex-version-check-failed' } $codexVersion = [string](@($versionOutput | ForEach-Object { [string]$_ } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1)) if ([string]::IsNullOrWhiteSpace($codexVersion) -or $codexVersion.Length -gt 160 -or $codexVersion -match '[\r\n]') { throw 't060-codex-version-output-invalid' } $null = @(& $codexCommand.Source login status 2>&1) if ($LASTEXITCODE -ne 0) { throw 't060-codex-not-authenticated' } $digestEvidence = Get-ContinuousCoReviewReviewedStateDigest -RepoRoot $root if (-not $digestEvidence.ok) { throw ('t060-reviewed-state-digest-failed:' + [string]$digestEvidence.failure_reason) } $reviewedDigest = [string]$digestEvidence.tree_id $platform = [pscustomobject][ordered]@{ os = 'macos' os_version = [Runtime.InteropServices.RuntimeInformation]::OSDescription architecture = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant() } $preflightRoot = Join-Path ([IO.Path]::GetTempPath()) ('specrew-t060-macos-preflight-' + [guid]::NewGuid().ToString('N')) [IO.Directory]::CreateDirectory((Join-Path $preflightRoot 'candidate')) | Out-Null try { $preflightInvocation = [pscustomobject][ordered]@{ schema_version = '1.0' campaign_id = 'cmp-t060-macos-preflight' run_id = 'run-t060-macos-preflight' target_digest = $reviewedDigest snapshot_path = $root review_scope = 'T060 no-spend local macOS production-port preflight.' prompt_path = (Join-Path $root 'scripts/internal/continuous-co-review/reviewer-candidate-prompt.md') candidate_result_path = (Join-Path $preflightRoot 'candidate/candidate.json') candidate_report_path = (Join-Path $preflightRoot 'candidate/candidate.md') deadline = [DateTimeOffset]::UtcNow.AddSeconds($TimeoutSeconds).ToString('o') } $harness = New-ReviewProductionHarnessPort -HostName codex -TimeoutSeconds $TimeoutSeconds $runtime = New-ReviewProductionRuntimePort -TimeoutSeconds $TimeoutSeconds $harnessPreflight = & $harness.preflight $preflightInvocation $runtimePreflight = & $runtime.preflight $preflightInvocation } finally { Remove-Item -LiteralPath $preflightRoot -Recurse -Force -ErrorAction SilentlyContinue } $preflight = [pscustomobject][ordered]@{ schema_version = '1.0' evidence_kind = 't060-local-macos-preflight' evidence_source = 'local-machine' generated_at = [DateTimeOffset]::UtcNow.ToString('o') provider_invoked = $false target = [pscustomobject][ordered]@{ repository_url = $originUrl head_commit = $headBefore reviewed_state_digest = $reviewedDigest clean = $true } platform = $platform harness = [pscustomobject][ordered]@{ host = 'codex' harness_id = [string]$harness.id cli_version = $codexVersion auth_status = 'authenticated' ready = [bool]$harnessPreflight.ok reason = [string]$harnessPreflight.reason } runtime = [pscustomobject][ordered]@{ runtime_id = [string]$runtime.id ready = [bool]$runtimePreflight.ok reason = [string]$runtimePreflight.reason } } $preflightPath = Join-Path $output 'preflight.json' Write-T060JsonFile -Path $preflightPath -Value $preflight if (-not $harnessPreflight.ok -or -not $runtimePreflight.ok) { throw ('t060-production-preflight-failed:harness={0}:runtime={1}' -f $harnessPreflight.reason, $runtimePreflight.reason) } if ($Mode -ceq 'Preflight') { $preflight | ConvertTo-Json -Depth 20 return } $authorityConfigPath = Join-Path $output 'campaign-authority.json' Write-T060JsonFile -Path $authorityConfigPath -Value ([pscustomobject][ordered]@{ schema_version = '1.0'; mode = 'campaign' }) $storeRoot = Join-Path $output 'authority' $stagingRoot = Join-Path $output '.staging' $progressEvents = [Collections.Generic.List[object]]::new() $progressSink = { param($event) $progressEvents.Add($event) | Out-Null $findingText = if ($null -ne $event.validated_finding_count) { "; findings=$($event.validated_finding_count)" } else { '' } Write-Host ("T060 macOS [{0}] elapsed={1}ms{2} {3}" -f $event.stage, $event.elapsed_ms, $findingText, $event.message) }.GetNewClosure() # This is the only provider-capable call in this package. The production command itself is # synchronous and has no hidden retry; a second attempt requires a new run ID and human grant. $t060ReviewScope = @' Review the implemented code and tests in the complete frozen target for correctness, security, failure semantics, and conformance with the resolved design context. This T060 run is one serialized live harness proof executed before the remaining T060 harnesses, campaign-authority cutover, T061 independent signoff, retrospective, and closeout. Treat plan/state entries that accurately identify those later steps or an external provider-quota constraint as execution context, not code-review findings. Report a pending or deferred item only when a grounded defect in the frozen code makes that step unsafe or impossible, or when the implementation contradicts an approved requirement. Do not review project-completion or gate status in this code-review run. '@ $campaignRun = Invoke-ReviewCampaignCommand -RepoRoot $root -FeatureId '198-beta2-hardening' -IterationNumber '007' ` -RunId $RunId -ReviewerHost codex -GrantAuthorizationRef $AuthorizationRef -TimeoutSeconds $TimeoutSeconds ` -AuthorityConfigPath $authorityConfigPath -StoreRoot $storeRoot -StagingRoot $stagingRoot ` -ReviewScope $t060ReviewScope -ProgressSink $progressSink $progressPath = Join-Path $output 'progress.json' Write-T060JsonFile -Path $progressPath -Value @($progressEvents) $resultPath = Join-Path $output 'result.json' $reportPath = Join-Path $output 'report.md' if ([string]::IsNullOrWhiteSpace([string]$campaignRun.result_path) -or -not [IO.File]::Exists([string]$campaignRun.result_path)) { throw 't060-terminal-result-file-missing' } if ([string]::IsNullOrWhiteSpace([string]$campaignRun.report_path) -or -not [IO.File]::Exists([string]$campaignRun.report_path)) { throw 't060-terminal-report-file-missing' } [IO.File]::Copy([string]$campaignRun.result_path, $resultPath, $false) [IO.File]::Copy([string]$campaignRun.report_path, $reportPath, $false) $headAfter = (Invoke-T060Git -Root $root -ArgumentList @('rev-parse', 'HEAD^{commit}')).ToLowerInvariant() $statusAfter = Invoke-T060Git -Root $root -ArgumentList @('status', '--porcelain=v1', '--untracked-files=all') $cleanAfter = [string]::IsNullOrEmpty($statusAfter) $headUnchanged = $headAfter -ceq $headBefore $spends = @(Get-ReviewAuthorityCampaignFacts -StoreRoot $storeRoot -CampaignId ([string]$campaignRun.campaign_id) -Kind spend | Where-Object { [string]$_.run_id -ceq $RunId }) $resultJson = [IO.File]::ReadAllText($resultPath, [Text.UTF8Encoding]::new($false, $true)) $resultValidation = Test-ReviewAuthorityContractJson -ContractName ReviewResult -Json $resultJson ` -ExpectedCampaignId ([string]$campaignRun.campaign_id) -ExpectedRunId $RunId -ExpectedTargetDigest $reviewedDigest $manifest = [pscustomobject][ordered]@{ schema_version = '1.0' evidence_kind = 't060-local-macos-smoke' evidence_source = 'local-machine' generated_at = [DateTimeOffset]::UtcNow.ToString('o') target = [pscustomobject][ordered]@{ repository_url = $originUrl head_commit = $headBefore reviewed_state_digest = $reviewedDigest clean_before = $true clean_after = $cleanAfter head_unchanged = $headUnchanged } platform = $platform harness = [pscustomobject][ordered]@{ host = 'codex' harness_id = [string]$harness.id cli_version = $codexVersion auth_status = 'authenticated' } authorization = [pscustomobject][ordered]@{ reference = $AuthorizationRef invocation_count = $spends.Count } run = [pscustomobject][ordered]@{ campaign_id = [string]$campaignRun.campaign_id run_id = $RunId status = [string]$campaignRun.status reason = $(if ($null -eq $campaignRun.reason) { $null } else { [string]$campaignRun.reason }) invoked = [bool]$campaignRun.invoked preflight_file = 'preflight.json' result_file = 'result.json' report_file = 'report.md' progress_file = 'progress.json' preflight_sha256 = Get-T060FileSha256 -Path $preflightPath result_sha256 = Get-T060FileSha256 -Path $resultPath report_sha256 = Get-T060FileSha256 -Path $reportPath progress_sha256 = Get-T060FileSha256 -Path $progressPath } controller = [pscustomobject][ordered]@{ authority_mode = 'external-t060-campaign-config' authority_config_file = 'campaign-authority.json' authority_config_sha256 = Get-T060FileSha256 -Path $authorityConfigPath runtime_id = [string]$runtime.id timeout_seconds = $TimeoutSeconds terminal_result_contract_valid = [bool]$resultValidation.valid } } Write-T060JsonFile -Path (Join-Path $output 'manifest.json') -Value $manifest Remove-Item -LiteralPath $stagingRoot -Recurse -Force -ErrorAction SilentlyContinue if (-not $headUnchanged -or -not $cleanAfter) { throw 't060-origin-repository-mutated' } if ($spends.Count -ne 1) { throw "t060-provider-invocation-count-invalid:$($spends.Count)" } if (-not $resultValidation.valid) { throw ('t060-terminal-result-contract-invalid:' + ($resultValidation.errors -join ',')) } $manifest | ConvertTo-Json -Depth 20 |