2026-08-26 06:41:07 +08:00
$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'
$key = 'acceptance-snapshot-smoke-' + [ Guid ]:: NewGuid (). ToString ( 'N' )
$requestID = 'acceptance-snapshot-smoke-' + [ Guid ]:: NewGuid (). ToString ( 'N' )
$snapshot = $null
function Get-Token([string]$name ) {
( Invoke-RestMethod -Method Post -Uri $tokenUrl -ContentType 'application/x-www-form-urlencoded' -Body @ {
grant_type = 'password' ; client_id = 'voice-platform-web' ; username = $name ; password = 'LocalOnly123!' ; scope = 'openid'
}). access_token
}
try {
$owner = Get-Token 'owner'
$outsider = Get-Token 'outsider'
$imageDigest = ( docker image inspect data-terminal -cloud-api - -format '{{.Id}}' ). Trim ()
if ( $imageDigest -notmatch '^sha256:[a-f0-9]{64}$' ) { throw '无法取得API候选镜像摘要' }
$body = @ {
candidate_version = '0.51.0-rc.local'
build_digest = $imageDigest
environment = 'LAB'
run_type = 'REAL_DEVICE_PROVIDER'
scope = '本地真实OIDC和平台技术预检'
required_dependencies = @ ( 'oidc_jwks' )
reason = '验证发布候选验收快照的真实登录、门禁聚合和归档输出'
} | ConvertTo-Json -Compress
$headers = @ { Authorization = "Bearer $owner " ; 'X-Tenant-Id' = $tenant ; 'Idempotency-Key' = $key ; 'X-Request-Id' = $requestID }
$createdResponse = Invoke-WebRequest -UseBasicParsing -Method Post -Uri " $api /api/v1/ops/diagnostics/acceptance-snapshots" -Headers $headers -ContentType 'application/json' -Body $body
$snapshot = ( $createdResponse . Content | ConvertFrom-Json ). data
if ( $createdResponse . StatusCode -ne 201 -or $snapshot . redaction_policy -ne 'ops-acceptance-minimal-v1' ) { throw '验收快照首次生成结果不正确' }
if ( $snapshot . database . migration_version -lt 36 -or $snapshot . database . dirty ) { throw '验收快照未正确冻结数据库迁移状态' }
if ( @ ( $snapshot . external_dependencies ). Count -ne 1 -or $snapshot . external_dependencies [ 0 ]. dependency -ne 'oidc_jwks' -or -not $snapshot . external_dependencies [ 0 ]. gate_satisfied ) { throw '验收快照未正确冻结OIDC外部依赖状态' }
2026-08-26 22:56:19 +08:00
if ( @ ( $snapshot . gates ). Count -ne 8 ) { throw '验收快照门禁数量不正确' }
$productionGate = @ ( $snapshot . gates | Where-Object { $_ . code -eq 'production_infrastructure_ready' })
if ( $productionGate . Count -ne 1 -or -not $productionGate [ 0 ]. satisfied -or $snapshot . production_infrastructure . applicable -or -not $snapshot . production_infrastructure . gate_satisfied ) { throw 'LAB快照不应被生产基础设施门禁阻断' }
2026-08-26 06:41:07 +08:00
$failedGates = @ ( $snapshot . gates | Where-Object { -not $_ . satisfied })
2026-08-26 22:56:19 +08:00
if ( $snapshot . overall_gate_satisfied -ne ( $failedGates . Count -eq 0 )) { throw '验收快照总结果与八项门禁不一致' }
2026-08-26 06:41:07 +08:00
$blockingItems = @ ( $snapshot . alerts . blocking_items )
$expectedItems = [ Math ]:: Min ([ int ] $snapshot . alerts . blocking , 100 )
if ( $blockingItems . Count -ne $expectedItems -or $snapshot . alerts . blocking_items_truncated -ne ( $snapshot . alerts . blocking -gt 100 )) { throw '阻断告警定位数量或截断标识不正确' }
foreach ( $item in $blockingItems ) {
if ( $item . id -notmatch '^[0-9a-fA-F-]{36}$' -or -not $item . rule_code -or @ ( 'PRESENT' , 'MISSING' , 'AGGREGATE' , 'UNKNOWN' ) -notcontains $item . resource_state ) { throw '阻断告警定位字段不正确' }
}
if (( $createdResponse . Content -match 'https?://' ) -or ( $createdResponse . Content -match '"endpoint"' )) { throw '验收快照泄露了外部探针端点' }
Start-Sleep -Milliseconds 550
$replayResponse = Invoke-WebRequest -UseBasicParsing -Method Post -Uri " $api /api/v1/ops/diagnostics/acceptance-snapshots" -Headers $headers -ContentType 'application/json' -Body $body
if ( $replayResponse . StatusCode -ne 200 -or $replayResponse . Headers . 'Idempotency-Replayed' -ne 'true' -or ( $replayResponse . Content | ConvertFrom-Json ). data . id -ne $snapshot . id ) { throw '验收快照幂等重放不正确' }
Start-Sleep -Milliseconds 550
try {
Invoke-WebRequest -UseBasicParsing -Method Post -Uri " $api /api/v1/ops/diagnostics/acceptance-snapshots" -Headers @ { Authorization = "Bearer $outsider " ; 'X-Tenant-Id' = $tenant ; 'Idempotency-Key' = ( 'outsider-' + $key )} -ContentType 'application/json' -Body $body | Out-Null
throw '跨客户组织生成验收快照不应成功'
} catch {
if ( $null -eq $_ . Exception . Response -or [ int ] $_ . Exception . Response . StatusCode -ne 403 ) { throw }
}
$failedCodes = if ( $failedGates . Count ) { ( $failedGates . code -join ',' ) } else { 'none' }
2026-08-26 22:56:19 +08:00
Write-Host "发布候选验收快照烟测通过:真实OIDC、迁移冻结、外部依赖、八项门禁、生产条件按环境适用、阻断告警定位、幂等重放、租户隔离和端点脱敏均正确;当前阻断项= $failedCodes 。"
2026-08-26 06:41:07 +08:00
} finally {
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=' $key '; COMMIT;" | Out-Null
Write-Host '已清理验收快照烟测审计和幂等记录;快照本身不在服务端留存。'
}