scripts/t060-local-platform-smoke.ps1
|
#requires -Version 7.0 [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('Preflight', 'Invoke')] [string] $Mode, [Parameter(Mandatory)] [ValidateSet('cursor-agent', 'antigravity', 'copilot')] [string] $HostName, [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, [Parameter(Mandatory)] [ValidateRange(1, 7200)] [int] $TimeoutSeconds, [string] $Model, [switch] $AcknowledgeProviderInvocation, [string] $ExpectedRepositoryUrl = 'https://github.com/alonf/specrew.git' ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest function Invoke-T060LocalGit { 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-T060LocalJsonFile { 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-T060LocalFileSha256 { param([Parameter(Mandatory)][string] $Path) return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant() } function Get-T060LocalCliEvidence { param( [Parameter(Mandatory)][string] $SelectedHost, [Parameter(Mandatory)] $Definition, [string] $SelectedModel ) $command = Get-Command -Name ([string]$Definition.command) -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 if ($null -eq $command) { throw "t060-reviewer-cli-not-installed:$SelectedHost" } $versionOutput = @(& $command.Source --version 2>&1) if ($LASTEXITCODE -ne 0) { throw "t060-reviewer-version-check-failed:$SelectedHost" } $version = [string](@($versionOutput | ForEach-Object { [string]$_ } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1)) if ([string]::IsNullOrWhiteSpace($version) -or $version.Length -gt 160 -or $version -match '[\r\n]') { throw "t060-reviewer-version-output-invalid:$SelectedHost" } $authStatus = 'credential-state-not-exposed' $modelEvidence = 'configured-by-user' if ($SelectedHost -ceq 'cursor-agent') { $null = @(& $command.Source status 2>&1) if ($LASTEXITCODE -ne 0) { throw 't060-cursor-not-authenticated' } $availableModels = @(& $command.Source models 2>&1) if ($LASTEXITCODE -ne 0) { throw 't060-cursor-model-list-probe-failed' } $modelPattern = '^{0}\s+-\s+' -f [regex]::Escape($SelectedModel) if (@($availableModels | Where-Object { [string]$_ -cmatch $modelPattern }).Count -ne 1) { throw "t060-cursor-model-unavailable:$SelectedModel" } $authStatus = 'authenticated-probe-passed' $modelEvidence = $SelectedModel } elseif ($SelectedHost -ceq 'antigravity') { $null = @(& $command.Source models 2>&1) if ($LASTEXITCODE -ne 0) { throw 't060-antigravity-model-list-probe-failed' } $authStatus = 'authenticated-model-list-probe-passed' } elseif ($SelectedHost -ceq 'copilot') { $homePath = [Environment]::GetFolderPath([Environment+SpecialFolder]::UserProfile) if ([string]::IsNullOrWhiteSpace($homePath) -or -not [IO.Directory]::Exists((Join-Path $homePath '.copilot'))) { throw 't060-copilot-credential-state-missing' } $authStatus = 'credential-state-present-no-read-only-status-command' } return [pscustomobject][ordered]@{ version = $version auth_status = $authStatus model = $modelEvidence } } if (-not $IsWindows -and -not $IsLinux) { throw 't060-local-platform-smoke-requires-windows-or-linux' } $platformName = if ($IsWindows) { 'windows' } else { 'linux' } $allowedPlatform = if ($HostName -cin @('cursor-agent', 'antigravity')) { 'windows' } else { 'linux' } if ($platformName -cne $allowedPlatform) { throw "t060-host-platform-mismatch:host=$HostName:required=$allowedPlatform:actual=$platformName" } if ($HostName -ceq 'cursor-agent') { if ([string]::IsNullOrWhiteSpace($Model)) { throw 't060-cursor-explicit-model-required' } if ($Model -cnotmatch '^[a-z0-9][a-z0-9.-]{0,127}$') { throw 't060-cursor-model-invalid' } } elseif (-not [string]::IsNullOrWhiteSpace($Model)) { throw 't060-model-override-only-supported-for-cursor' } 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-T060LocalGit -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-T060LocalGit -Root $root -ArgumentList @('rev-parse', 'HEAD^{commit}')).ToLowerInvariant() if ($headBefore -cne $ExpectedCommit) { throw "t060-pinned-commit-mismatch:expected=$ExpectedCommit:actual=$headBefore" } $originUrl = Invoke-T060LocalGit -Root $root -ArgumentList @('remote', 'get-url', 'origin') if ($originUrl -cne $ExpectedRepositoryUrl) { throw "t060-origin-url-mismatch:expected=$ExpectedRepositoryUrl:actual=$originUrl" } $statusBefore = Invoke-T060LocalGit -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 $definition = Get-ContinuousCoReviewProductionHarnessDefinition -HostName $HostName if ($null -eq $definition) { throw "t060-production-harness-definition-missing:$HostName" } $cliEvidence = Get-T060LocalCliEvidence -SelectedHost $HostName -Definition $definition -SelectedModel $Model $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 = $platformName os_version = [Runtime.InteropServices.RuntimeInformation]::OSDescription architecture = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant() } $preflightRoot = Join-Path ([IO.Path]::GetTempPath()) ('specrew-t060-local-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-local-preflight' run_id = 'run-t060-local-preflight' target_digest = $reviewedDigest snapshot_path = $root review_scope = "T060 no-spend $platformName/$HostName 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 $HostName -TimeoutSeconds $TimeoutSeconds -Model $Model $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-platform-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 = $HostName harness_id = [string]$harness.id cli_version = [string]$cliEvidence.version model = [string]$cliEvidence.model auth_status = [string]$cliEvidence.auth_status 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-T060LocalJsonFile -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-T060LocalJsonFile -Path $authorityConfigPath -Value ([pscustomobject][ordered]@{ schema_version = '1.0'; mode = 'campaign' }) $storeRoot = Join-Path $output 'authority' $externalParent = Split-Path -Parent $root $targetRoot = Join-Path $externalParent '.t060-targets' $stagingRoot = Join-Path $externalParent '.t060-staging' $ports = [pscustomobject][ordered]@{ target = New-GitReviewTargetPort -OriginRepo $root -ExternalRoot $targetRoot harness = $harness runtime = $runtime clock = New-ReviewSystemClockPort prompt_path = (Join-Path $root 'scripts/internal/continuous-co-review/reviewer-candidate-prompt.md') } $progressEvents = [Collections.Generic.List[object]]::new() $lastHeartbeatPrinted = -60000L $progressSink = { param($event) $progressEvents.Add($event) | Out-Null $isHeartbeat = [string]$event.stage -ceq 'running' -and $null -eq $event.validated_finding_count if (-not $isHeartbeat -or ([long]$event.elapsed_ms - $lastHeartbeatPrinted) -ge 60000) { $findingText = if ($null -ne $event.validated_finding_count) { "; findings=$($event.validated_finding_count)" } else { '' } Write-Host ("T060 {0}/{1} [{2}] elapsed={3}ms{4} {5}" -f $platformName, $HostName, $event.stage, $event.elapsed_ms, $findingText, $event.message) if ($isHeartbeat) { $lastHeartbeatPrinted = [long]$event.elapsed_ms } } }.GetNewClosure() # This is the only provider-capable call in this package. It is synchronous and has no hidden # retry; every further attempt requires a new run ID and a new explicit human authorization. $t060ReviewScope = @" Complete one bounded risk-based code review of the implemented code and tests in the frozen target for correctness, security, failure semantics, and conformance with the resolved design context. Selected live path: harness=$HostName; platform=$platformName. Prioritize the common campaign/file contract plus the selected harness adapter and selected OS runtime integration; use adjacent tests and deterministic evidence to choose additional high-risk checks. T061 is the separate final independent signoff. 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 $HostName -GrantAuthorizationRef $AuthorizationRef -TimeoutSeconds $TimeoutSeconds ` -AuthorityConfigPath $authorityConfigPath -StoreRoot $storeRoot -StagingRoot $stagingRoot -Ports $ports ` -ReviewScope $t060ReviewScope -ProgressSink $progressSink $progressPath = Join-Path $output 'progress.json' Write-T060LocalJsonFile -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-T060LocalGit -Root $root -ArgumentList @('rev-parse', 'HEAD^{commit}')).ToLowerInvariant() $statusAfter = Invoke-T060LocalGit -Root $root -ArgumentList @('status', '--porcelain=v1', '--untracked-files=all') $cleanAfter = [string]::IsNullOrEmpty($statusAfter) $headUnchanged = $headAfter -ceq $headBefore $campaignId = [string]$campaignRun.campaign_id $grants = @(Get-ReviewAuthorityCampaignFacts -StoreRoot $storeRoot -CampaignId $campaignId -Kind grants | Where-Object { [string]$_.authorization_ref -ceq $AuthorizationRef }) $reservations = @(Get-ReviewAuthorityCampaignFacts -StoreRoot $storeRoot -CampaignId $campaignId -Kind reservations | Where-Object { [string]$_.run_id -ceq $RunId }) $spends = @(Get-ReviewAuthorityCampaignFacts -StoreRoot $storeRoot -CampaignId $campaignId -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 $campaignId -ExpectedRunId $RunId -ExpectedTargetDigest $reviewedDigest $result = if ($resultValidation.valid) { $resultJson | ConvertFrom-Json -Depth 30 } else { $null } $findingCount = if ($null -eq $result) { $null } else { @($result.findings).Count } $smokeClean = $null -ne $result -and [string]$result.completion -ceq 'complete' -and [string]$result.verdict -ceq 'pass' -and ` [string]$result.runtime_outcome -ceq 'completed' -and [bool]$result.termination_verified -and [string]$result.containment -ceq 'verified' -and ` [string]$result.currentness -ceq 'current' -and [string]$result.validation -ceq 'valid' -and [bool]$result.can_approve_current -and $findingCount -eq 0 $manifest = [pscustomobject][ordered]@{ schema_version = '1.0' evidence_kind = 't060-local-platform-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 = $HostName; harness_id = [string]$harness.id; cli_version = [string]$cliEvidence.version model = [string]$cliEvidence.model; auth_status = [string]$cliEvidence.auth_status } authorization = [pscustomobject][ordered]@{ reference = $AuthorizationRef; grant_count = $grants.Count; reservation_count = $reservations.Count; invocation_count = $spends.Count } run = [pscustomobject][ordered]@{ campaign_id = $campaignId; run_id = $RunId; status = [string]$campaignRun.status reason = $(if ($null -eq $campaignRun.reason) { $null } else { [string]$campaignRun.reason }) invoked = [bool]$campaignRun.invoked; smoke_clean = [bool]$smokeClean; finding_count = $findingCount preflight_file = 'preflight.json'; result_file = 'result.json'; report_file = 'report.md'; progress_file = 'progress.json' preflight_sha256 = Get-T060LocalFileSha256 -Path $preflightPath result_sha256 = Get-T060LocalFileSha256 -Path $resultPath report_sha256 = Get-T060LocalFileSha256 -Path $reportPath progress_sha256 = Get-T060LocalFileSha256 -Path $progressPath } controller = [pscustomobject][ordered]@{ authority_mode = 'external-t060-campaign-config'; authority_config_file = 'campaign-authority.json' authority_config_sha256 = Get-T060LocalFileSha256 -Path $authorityConfigPath runtime_id = [string]$runtime.id; timeout_seconds = $TimeoutSeconds; terminal_result_contract_valid = [bool]$resultValidation.valid } } Write-T060LocalJsonFile -Path (Join-Path $output 'manifest.json') -Value $manifest Remove-Item -LiteralPath $stagingRoot -Recurse -Force -ErrorAction SilentlyContinue if ([IO.Directory]::Exists($targetRoot) -and @(Get-ChildItem -LiteralPath $targetRoot -Force).Count -eq 0) { Remove-Item -LiteralPath $targetRoot -Force -ErrorAction SilentlyContinue } if (-not $headUnchanged -or -not $cleanAfter) { throw 't060-origin-repository-mutated' } if ($grants.Count -ne 1 -or $reservations.Count -ne 1 -or $spends.Count -ne 1) { throw "t060-provider-authority-count-invalid:grants=$($grants.Count):reservations=$($reservations.Count):spends=$($spends.Count)" } if (-not $resultValidation.valid) { throw ('t060-terminal-result-contract-invalid:' + ($resultValidation.errors -join ',')) } if (-not $smokeClean) { $verdict = if ($null -eq $result) { 'unavailable' } else { [string]$result.verdict } throw ('t060-smoke-not-clean:verdict={0}:findings={1}' -f $verdict, $findingCount) } $manifest | ConvertTo-Json -Depth 20 |