$ErrorActionPreference = 'Stop' $PSNativeCommandUseErrorActionPreference = $true $root = Split-Path -Parent $PSScriptRoot Set-Location -LiteralPath $root $api = 'http://localhost:58080' $tokenUrl = 'http://localhost:58180/realms/voice-local/protocol/openid-connect/token' $tenant = '10000000-0000-0000-0000-000000000010' $candidate = '0.54.0-rc.local' $key = 'release-evidence-smoke-' + [Guid]::NewGuid().ToString('N') $idempotencyKeys = @($key) $requestID = 'release-evidence-smoke-' + [Guid]::NewGuid().ToString('N') $temporaryRoot = Join-Path ([System.IO.Path]::GetTempPath()) ('voice-release-evidence-' + [Guid]::NewGuid().ToString('N')) $snapshotPath = Join-Path $temporaryRoot 'acceptance.json' $indexPath = Join-Path $temporaryRoot 'evidence-index.json' $bundlePath = Join-Path $temporaryRoot 'candidate-evidence.zip' $expandedPath = Join-Path $temporaryRoot 'expanded' $weekdayOffset = (([int](Get-Date).DayOfWeek) + 6) % 7 $completedWeekStart = (Get-Date).Date.AddDays(-$weekdayOffset - 7) function Write-JsonUtf8([string]$path, $value, [int]$depth = 20) { $json = $value | ConvertTo-Json -Depth $depth [System.IO.File]::WriteAllText($path, $json, [System.Text.UTF8Encoding]::new($false)) } try { New-Item -ItemType Directory -Path $temporaryRoot | Out-Null $token = (Invoke-RestMethod -Method Post -Uri $tokenUrl -ContentType 'application/x-www-form-urlencoded' -Body @{ grant_type = 'password'; client_id = 'voice-platform-web'; username = 'owner'; password = 'LocalOnly123!'; scope = 'openid' }).access_token $buildDigest = (docker image inspect data-terminal-cloud-api --format '{{.Id}}').Trim() if ($buildDigest -notmatch '^sha256:[a-f0-9]{64}$') { throw '无法取得API候选镜像摘要' } $body = @{ candidate_version = $candidate; build_digest = $buildDigest; environment = 'LAB'; run_type = 'PILOT' scope = '本地PILOT候选证据包纵向验证'; required_dependencies = @('oidc_jwks') reason = '验证技术验收快照与阶段8外部证据索引可形成带摘要的候选交付包' } | ConvertTo-Json -Compress $response = Invoke-RestMethod -Method Post -Uri "$api/api/v1/ops/diagnostics/acceptance-snapshots" -Headers @{ Authorization = "Bearer $token"; 'X-Tenant-Id' = $tenant; 'Idempotency-Key' = $key; 'X-Request-Id' = $requestID } -ContentType 'application/json' -Body $body if (-not $response.data.overall_gate_satisfied) { throw '当前技术验收快照门禁未通过,不能生成候选证据包' } Write-JsonUtf8 $snapshotPath $response.data $dailyPaths = @() for ($day = 0; $day -lt 7; $day++) { $localDate = $completedWeekStart.AddDays($day).ToString('yyyy-MM-dd') $dailyKey = 'release-evidence-daily-' + [Guid]::NewGuid().ToString('N') $idempotencyKeys += $dailyKey $dailyBody = @{candidate_version = $candidate; build_digest = $buildDigest; local_date = $localDate; timezone = 'Asia/Shanghai'; reason = '生成已结束试点自然周的候选证据包日报'} | ConvertTo-Json -Compress $dailyResponse = Invoke-RestMethod -Method Post -Uri "$api/api/v1/ops/diagnostics/pilot-daily-snapshots" -Headers @{ Authorization = "Bearer $token"; 'X-Tenant-Id' = $tenant; 'Idempotency-Key' = $dailyKey; 'X-Request-Id' = $requestID } -ContentType 'application/json' -Body $dailyBody $dailyPath = Join-Path $temporaryRoot "pilot-daily-$localDate.json" Write-JsonUtf8 $dailyPath $dailyResponse.data $dailyPaths += $dailyPath } $weeklyKey = 'release-evidence-weekly-' + [Guid]::NewGuid().ToString('N') $idempotencyKeys += $weeklyKey $weekStartText = $completedWeekStart.ToString('yyyy-MM-dd') $weeklyBody = @{candidate_version = $candidate; build_digest = $buildDigest; week_start = $weekStartText; timezone = 'Asia/Shanghai'; reason = '生成已结束试点自然周的候选证据包周报'} | ConvertTo-Json -Compress $weeklyResponse = Invoke-RestMethod -Method Post -Uri "$api/api/v1/ops/diagnostics/pilot-weekly-snapshots" -Headers @{ Authorization = "Bearer $token"; 'X-Tenant-Id' = $tenant; 'Idempotency-Key' = $weeklyKey; 'X-Request-Id' = $requestID } -ContentType 'application/json' -Body $weeklyBody if (-not $weeklyResponse.data.is_complete) { throw '已结束自然周不应生成周中快照' } $weeklyPath = Join-Path $temporaryRoot "pilot-weekly-$weekStartText.json" Write-JsonUtf8 $weeklyPath $weeklyResponse.data $codes = @('REMOTE_CI_SUPPLY_CHAIN','REAL_DEVICE_RTC','REAL_DEVICE_FIRMWARE','REAL_AGENT_MODELS','PRODUCTION_INFRASTRUCTURE','PRODUCTION_RECOVERY','KMS_ROTATION','REAL_ALERT_RECEIVER','SAFARI_COMPATIBILITY','BUSINESS_APPROVAL') $index = @{ schema_version = 'release-evidence-index-v2'; candidate_version = $candidate; build_digest = $buildDigest items = @($codes | ForEach-Object { @{code = $_; status = 'PENDING'; owner = '待外部责任人确认'; note = '本地证据包保留此外部验收缺口,不以模拟结果代替'; files = @()} }) } Write-JsonUtf8 $indexPath $index 8 $bundleArguments = @('run','./cmd/release-evidence','-root','.', '-candidate-version',$candidate,'-build-digest',$buildDigest,'-source-commit','local-uncommitted','-acceptance-snapshot',$snapshotPath,'-evidence-index',$indexPath,'-pilot-weekly-snapshot',$weeklyPath,'-out',$bundlePath) foreach ($dailyPath in $dailyPaths) { $bundleArguments += @('-pilot-daily-snapshot',$dailyPath) } $resultLine = & go @bundleArguments $result = $resultLine | ConvertFrom-Json if ($result.status -ne 'EXTERNAL_VALIDATION_PENDING' -or $result.pending -ne 10 -or $result.verified -ne 0 -or $result.sha256 -notmatch '^sha256:[a-f0-9]{64}$') { throw '证据包生成结果不正确' } Expand-Archive -LiteralPath $bundlePath -DestinationPath $expandedPath $manifest = Get-Content -Raw -LiteralPath (Join-Path $expandedPath 'release-manifest.json') | ConvertFrom-Json if (-not $manifest.technical_gate_satisfied -or $manifest.evidence_status -ne 'EXTERNAL_VALIDATION_PENDING' -or $manifest.pending_count -ne 10 -or $manifest.openapi_version -ne '0.67.0' -or $manifest.schema_version -ne 'release-candidate-evidence-bundle-v3' -or -not $manifest.pilot_evidence_included -or $manifest.pilot_daily_count -ne 7 -or $manifest.pilot_week_start -ne $weekStartText -or $manifest.pilot_timezone -ne 'Asia/Shanghai') { throw '证据包清单内容不正确' } if ($manifest.firmware_validation.status -ne 'PENDING' -or $manifest.firmware_validation.coverage_complete -or $manifest.firmware_validation.required_device_count -ne 3 -or @($manifest.firmware_validation.required_scenarios).Count -ne 4 -or @($manifest.firmware_validation.gaps).Count -ne 5) { throw '真机固件场景缺口未在证据包中保留' } if ($manifest.alert_receiver_validation.status -ne 'PENDING' -or $manifest.alert_receiver_validation.coverage_complete -or $manifest.alert_receiver_validation.report_present -or @($manifest.alert_receiver_validation.gaps).Count -ne 2) { throw '真实告警接收器与正式演练缺口未在证据包中保留' } if ($manifest.kms_rotation_validation.status -ne 'PENDING' -or $manifest.kms_rotation_validation.coverage_complete -or $manifest.kms_rotation_validation.report_present -or @($manifest.kms_rotation_validation.gaps).Count -ne 3) { throw '真实KMS/HSM两类轮换缺口未在证据包中保留' } if ($manifest.production_recovery_validation.status -ne 'PENDING' -or $manifest.production_recovery_validation.coverage_complete -or $manifest.production_recovery_validation.report_present -or @($manifest.production_recovery_validation.gaps).Count -ne 5) { throw '生产等价四类恢复演练缺口未在证据包中保留' } if ($manifest.production_infrastructure_validation.status -ne 'PENDING' -or $manifest.production_infrastructure_validation.coverage_complete -or $manifest.production_infrastructure_validation.report_present -or @($manifest.production_infrastructure_validation.gaps).Count -ne 8) { throw '生产基础设施七类组件缺口未在证据包中保留' } if ($manifest.safari_compatibility_validation.status -ne 'PENDING' -or $manifest.safari_compatibility_validation.coverage_complete -or $manifest.safari_compatibility_validation.report_present -or @($manifest.safari_compatibility_validation.gaps).Count -ne 3) { throw '最近两个真实Safari版本缺口未在证据包中保留' } if ($manifest.real_device_rtc_validation.status -ne 'PENDING' -or $manifest.real_device_rtc_validation.coverage_complete -or $manifest.real_device_rtc_validation.report_present -or @($manifest.real_device_rtc_validation.gaps).Count -ne 10) { throw '真实终端、三模式和四类RTC场景缺口未在证据包中保留' } if ($manifest.real_agent_models_validation.status -ne 'PENDING' -or $manifest.real_agent_models_validation.coverage_complete -or $manifest.real_agent_models_validation.report_present -or @($manifest.real_agent_models_validation.gaps).Count -ne 16) { throw '真实生成、Embedding和内容安全质量缺口未在证据包中保留' } if ($manifest.remote_ci_supply_chain_validation.status -ne 'PENDING' -or $manifest.remote_ci_supply_chain_validation.coverage_complete -or $manifest.remote_ci_supply_chain_validation.report_present -or @($manifest.remote_ci_supply_chain_validation.gaps).Count -ne 20) { throw '远程CI、镜像摘要和SBOM缺口未在证据包中保留' } foreach ($required in @('evidence-index.json','acceptance/technical-acceptance-snapshot.json','baseline/openapi/openapi.yaml','baseline/docs/external-acceptance-checklist.md',"pilot/weekly/$weekStartText.json",("pilot/daily/" + $completedWeekStart.AddDays(6).ToString('yyyy-MM-dd') + '.json'))) { if (-not (Test-Path -LiteralPath (Join-Path $expandedPath $required))) { throw "证据包缺少文件:$required" } } Write-Host "发布候选证据包烟测通过:文件=$($result.files),PILOT日报=7,周报=$weekStartText,外部待验=$($result.pending),远程CI、真机RTC/固件、Agent模型、告警、KMS、恢复、基础设施和Safari保持PENDING,包摘要=$($result.sha256)。" } finally { $quotedKeys = ($idempotencyKeys | ForEach-Object { "'$_'" }) -join ',' docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -c "BEGIN; DELETE FROM audit_event WHERE request_id='$requestID'; DELETE FROM idempotency_record WHERE idempotency_key IN ($quotedKeys); COMMIT;" | Out-Null if (Test-Path -LiteralPath $temporaryRoot) { Remove-Item -LiteralPath $temporaryRoot -Recurse -Force } Write-Host '已清理证据包烟测的临时文件、审计和幂等记录。' }