fix(release): enforce August 1 business readiness
This commit is contained in:
@@ -7,6 +7,8 @@ param(
|
||||
|
||||
[string]$ContentCandidate = 'docs\content-candidates\aug1-life-advisor-content-candidates-v0.1.json',
|
||||
|
||||
[string]$ReleaseApproval = 'docs\content-candidates\aug1-release-approval.json',
|
||||
|
||||
[string]$RuntimeCommit = 'af8af2f6dd169e4616a4ca0ca724e8b809502ab3',
|
||||
|
||||
[string]$OperationsCommit = 'HEAD',
|
||||
@@ -71,12 +73,14 @@ function Ensure-UnderOutput {
|
||||
$evidenceDirectoryPath = Resolve-ProjectPath $EvidenceDirectory
|
||||
$backendJarPath = Resolve-ProjectPath $BackendJar
|
||||
$contentCandidatePath = Resolve-ProjectPath $ContentCandidate
|
||||
$releaseApprovalPath = Resolve-ProjectPath $ReleaseApproval
|
||||
$outputDirectoryPath = Ensure-UnderOutput (Resolve-ProjectPath $OutputDirectory)
|
||||
$evidencePath = Join-Path $evidenceDirectoryPath 'release-evidence.json'
|
||||
|
||||
Require-File $evidencePath
|
||||
Require-File $backendJarPath
|
||||
Require-File $contentCandidatePath
|
||||
Require-File $releaseApprovalPath
|
||||
|
||||
$runtimeCommitSha = Read-Git @('rev-parse', '--verify', "$RuntimeCommit^{commit}")
|
||||
$operationsCommitSha = Read-Git @('rev-parse', '--verify', "$OperationsCommit^{commit}")
|
||||
@@ -108,6 +112,7 @@ Require-File $apkPath
|
||||
$apkSha256 = Get-Sha256 $apkPath
|
||||
$backendSha256 = Get-Sha256 $backendJarPath
|
||||
$contentSha256 = Get-Sha256 $contentCandidatePath
|
||||
$releaseApprovalSha256 = Get-Sha256 $releaseApprovalPath
|
||||
if ($apkSha256 -ne ([string]$evidence.artifacts.apk.sha256).ToLowerInvariant()) {
|
||||
throw "Frozen APK hash mismatch: $apkSha256"
|
||||
}
|
||||
@@ -117,6 +122,20 @@ if ($backendSha256 -ne ([string]$evidence.artifacts.backendJarSha256).ToLowerInv
|
||||
if ($contentSha256 -ne ([string]$evidence.contentCandidates.sha256).ToLowerInvariant()) {
|
||||
throw "Content candidate hash mismatch: $contentSha256"
|
||||
}
|
||||
$releaseApprovalManifest = Get-Content -Raw -Encoding UTF8 -LiteralPath $releaseApprovalPath | ConvertFrom-Json
|
||||
if ([string]$releaseApprovalManifest.candidateSha256 -ne $contentSha256) {
|
||||
throw "Release approval candidate hash mismatch: $($releaseApprovalManifest.candidateSha256)"
|
||||
}
|
||||
& node (Join-Path $projectRoot 'scripts\verify-aug1-formal-content.mjs') $releaseApprovalPath | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw 'Release approval structure audit failed.'
|
||||
}
|
||||
$approvedScenarioCount = @($releaseApprovalManifest.scenarioApprovals.PSObject.Properties |
|
||||
Where-Object { [string]$_.Value.status -eq 'APPROVED' }).Count
|
||||
$approvedQuestionCount = @($releaseApprovalManifest.policyQuestionApprovals.PSObject.Properties |
|
||||
Where-Object { [string]$_.Value.status -eq 'APPROVED' }).Count
|
||||
$approvedSignoffCount = @($releaseApprovalManifest.signoffs.PSObject.Properties |
|
||||
Where-Object { [string]$_.Value.status -eq 'APPROVED' }).Count
|
||||
|
||||
$goNoGoCandidates = @(Get-ChildItem -LiteralPath (Join-Path $projectRoot 'docs') -File -Filter '*Android*Go-No-Go-20260728.md')
|
||||
if ($goNoGoCandidates.Count -ne 1) {
|
||||
@@ -143,6 +162,8 @@ $operationSources = [ordered]@{
|
||||
'operations/verify-aug1-production-api-readonly.sh' = Join-Path $projectRoot 'scripts\verify-aug1-production-api-readonly.sh'
|
||||
'operations/verify-aug1-authenticated-production.sh' = Join-Path $projectRoot 'scripts\verify-aug1-authenticated-production.sh'
|
||||
'operations/verify-aug1-content-candidates.mjs' = Join-Path $projectRoot 'scripts\verify-aug1-content-candidates.mjs'
|
||||
'operations/verify-aug1-formal-content.mjs' = Join-Path $projectRoot 'scripts\verify-aug1-formal-content.mjs'
|
||||
'operations/verify-aug1-release-readiness.sh' = Join-Path $projectRoot 'scripts\verify-aug1-release-readiness.sh'
|
||||
'operations/capture-android-acceptance.ps1' = Join-Path $projectRoot 'scripts\capture-android-acceptance.ps1'
|
||||
'operations/go-no-go.md' = $goNoGoCandidates[0].FullName
|
||||
'operations/business-content-and-five-channel-signoff.md' = $businessSignoffPath
|
||||
@@ -164,8 +185,9 @@ Write-Output "operations_commit=$operationsCommitSha"
|
||||
Write-Output "apk_sha256=$apkSha256"
|
||||
Write-Output "backend_sha256=$backendSha256"
|
||||
Write-Output "content_sha256=$contentSha256"
|
||||
Write-Output "release_approval_sha256=$releaseApprovalSha256"
|
||||
Write-Output "package_path=$packagePath"
|
||||
Write-Output 'package_entries=17'
|
||||
Write-Output 'package_entries=20'
|
||||
if ($PlanOnly) {
|
||||
exit 0
|
||||
}
|
||||
@@ -191,6 +213,7 @@ try {
|
||||
Copy-Item -LiteralPath $apkPath -Destination (Join-Path $stagingDirectory $apkName)
|
||||
Copy-Item -LiteralPath $backendJarPath -Destination (Join-Path $stagingDirectory $backendName)
|
||||
Copy-Item -LiteralPath $contentCandidatePath -Destination (Join-Path $stagingDirectory 'aug1-life-advisor-content-candidates-v0.1.json')
|
||||
Copy-Item -LiteralPath $releaseApprovalPath -Destination (Join-Path $stagingDirectory 'aug1-release-approval.json')
|
||||
Copy-Item -LiteralPath $evidencePath -Destination (Join-Path $stagingDirectory 'release-evidence.json')
|
||||
|
||||
foreach ($entry in $operationSources.GetEnumerator()) {
|
||||
@@ -216,6 +239,7 @@ try {
|
||||
"| $apkName | $apkSha256 |",
|
||||
"| $backendName | $backendSha256 |",
|
||||
"| aug1-life-advisor-content-candidates-v0.1.json | $contentSha256 |",
|
||||
"| aug1-release-approval.json | $releaseApprovalSha256 |",
|
||||
'',
|
||||
'The APK uses a DCloud test signer and is limited to a small controlled internal test. Admin/H5 are deferred, schema is unchanged, and content candidates must not be imported or enabled.',
|
||||
'',
|
||||
@@ -227,6 +251,8 @@ try {
|
||||
'',
|
||||
'Recheck the bundled disabled content candidates with `node operations/verify-aug1-content-candidates.mjs aug1-life-advisor-content-candidates-v0.1.json`. Complete `operations/business-content-and-five-channel-signoff.md` before enabling content or binding handlers.',
|
||||
'',
|
||||
'Audit business evidence with `node operations/verify-aug1-formal-content.mjs aug1-release-approval.json`. The strict command and `operations/verify-aug1-release-readiness.sh --execute` must pass before the final Go decision; the latter is read-only and verifies fixed-code mode, two active handlers per channel, and immutable production scenario snapshots.',
|
||||
'',
|
||||
'The fixed-code authenticated smoke is bundled as `operations/verify-aug1-authenticated-production.sh`. It requires the explicit `--execute` flag, selects an existing eligible APP identity without printing it, never enables or sends real SMS, and does not submit business records.',
|
||||
'',
|
||||
'This package does not authorize production deployment, service restart, database change, Git push or public distribution.'
|
||||
@@ -245,6 +271,8 @@ try {
|
||||
'- Production schema: 64/64 and runtime schema bootstrap disabled/default.',
|
||||
'- Production GET-only route probe: 20/20 (`1x200`, `6x401`, `13x405`).',
|
||||
'- Separate fixed-code authenticated smoke passed request/login, read-only business routes, logout, old-token rejection and re-login; application logs confirmed that no real SMS was sent.',
|
||||
"- Formal approval audit: $approvedScenarioCount/5 scenarios, $approvedQuestionCount/30 policy questions, $approvedSignoffCount/5 owner sign-offs.",
|
||||
'- Strict August 1 readiness is expected to fail closed until formal evidence and real channel handlers are complete.',
|
||||
'- Backend normalized AIHR module remains mismatched until an authorized deploy.',
|
||||
'- Overall result: **No-Go** pending the manual and authorization gates in `operations/go-no-go.md`.'
|
||||
) -join [Environment]::NewLine
|
||||
@@ -257,12 +285,15 @@ try {
|
||||
"- Status: $($evidence.contentCandidates.candidateStatus)",
|
||||
"- Scenario candidates: $($evidence.contentCandidates.scenarioCandidates)",
|
||||
"- Policy-question candidates: $($evidence.contentCandidates.policyQuestionCandidates)",
|
||||
'- Formally publishable scenarios: 0',
|
||||
'- Formally sourced standard answers: 0',
|
||||
"- Formally approved scenarios: $approvedScenarioCount/5",
|
||||
"- Formally evidenced policy questions: $approvedQuestionCount/30",
|
||||
"- Owner sign-offs: $approvedSignoffCount/5",
|
||||
'',
|
||||
'Candidates are for business review only. Do not import, enable or count them toward August 1 acceptance until formal sources and item-level sign-off are complete.',
|
||||
'',
|
||||
'Run `node operations/verify-aug1-content-candidates.mjs aug1-life-advisor-content-candidates-v0.1.json` after extraction, then use `operations/business-content-and-five-channel-signoff.md` for item-level review and handler assignment.'
|
||||
'Run `node operations/verify-aug1-content-candidates.mjs aug1-life-advisor-content-candidates-v0.1.json` after extraction, then use `operations/business-content-and-five-channel-signoff.md` for item-level review and handler assignment.',
|
||||
'',
|
||||
'Record only source/evidence references and hashes in `aug1-release-approval.json`; do not copy formal answer text or identities into it. Run `node operations/verify-aug1-formal-content.mjs aug1-release-approval.json` for progress and add `--strict` only after every item is signed.'
|
||||
) -join [Environment]::NewLine
|
||||
Write-Utf8 -Path (Join-Path $stagingDirectory 'content-review-readme.md') -Content ($contentReview + [Environment]::NewLine)
|
||||
|
||||
@@ -312,8 +343,17 @@ try {
|
||||
publishable = $false
|
||||
scenarioCandidates = [int]$evidence.contentCandidates.scenarioCandidates
|
||||
policyQuestionCandidates = [int]$evidence.contentCandidates.policyQuestionCandidates
|
||||
formalPublishableScenarios = 0
|
||||
formallySourcedStandardAnswers = 0
|
||||
formalPublishableScenarios = $approvedScenarioCount
|
||||
formallySourcedStandardAnswers = $approvedQuestionCount
|
||||
}
|
||||
formalApproval = [ordered]@{
|
||||
artifact = 'aug1-release-approval.json'
|
||||
sha256 = $releaseApprovalSha256
|
||||
status = [string]$releaseApprovalManifest.releaseStatus
|
||||
formalContentVersion = [string]$releaseApprovalManifest.formalContentVersion
|
||||
approvedScenarios = $approvedScenarioCount
|
||||
approvedPolicyQuestions = $approvedQuestionCount
|
||||
approvedOwnerSignoffs = $approvedSignoffCount
|
||||
}
|
||||
operations = @($operationSources.Keys)
|
||||
manualGatesRemaining = @($evidence.manualGatesRemaining)
|
||||
@@ -331,8 +371,8 @@ try {
|
||||
|
||||
$allFiles = Get-ChildItem -LiteralPath $stagingDirectory -Recurse -File |
|
||||
Sort-Object { $_.FullName.Substring($stagingDirectory.Length + 1) }
|
||||
if ($allFiles.Count -ne 17) {
|
||||
throw "Release package expected 17 files, found $($allFiles.Count)."
|
||||
if ($allFiles.Count -ne 20) {
|
||||
throw "Release package expected 20 files, found $($allFiles.Count)."
|
||||
}
|
||||
foreach ($file in $allFiles) {
|
||||
$file.LastWriteTimeUtc = $entryTimestamp.UtcDateTime
|
||||
@@ -369,8 +409,8 @@ try {
|
||||
|
||||
$verificationArchive = [IO.Compression.ZipFile]::OpenRead($partialPackagePath)
|
||||
try {
|
||||
if ($verificationArchive.Entries.Count -ne 17) {
|
||||
throw "ZIP verification expected 17 entries, found $($verificationArchive.Entries.Count)."
|
||||
if ($verificationArchive.Entries.Count -ne 20) {
|
||||
throw "ZIP verification expected 20 entries, found $($verificationArchive.Entries.Count)."
|
||||
}
|
||||
$entryNames = @($verificationArchive.Entries | ForEach-Object { $_.FullName })
|
||||
foreach ($requiredEntry in @(
|
||||
@@ -381,6 +421,9 @@ try {
|
||||
'operations/release-backend.sh',
|
||||
'operations/verify-aug1-authenticated-production.sh',
|
||||
'operations/verify-aug1-content-candidates.mjs',
|
||||
'operations/verify-aug1-formal-content.mjs',
|
||||
'operations/verify-aug1-release-readiness.sh',
|
||||
'aug1-release-approval.json',
|
||||
'operations/business-content-and-five-channel-signoff.md'
|
||||
)) {
|
||||
if ($entryNames -notcontains $requiredEntry) {
|
||||
|
||||
Reference in New Issue
Block a user