Files
story/scripts/smoke-release-evidence.ps1
T

101 lines
10 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
$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.64.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 '已清理证据包烟测的临时文件、审计和幂等记录。'
}