fix(deploy): read the string build stamp, and never compare two unreadable labels (#76)

Get-LabelFromBuildJsonObj knew two stamp shapes and not the third - the string
form the versioning scheme specifies and zbump writes. It fell through to the
legacy branch, where productVersion is null ("") and buildNumber is null (0),
so every string stamp became "v.0".

Deploy verification runs BOTH sides through this function: the local stamp and
the one read back from the running container. So it did not fail loudly - it
collapsed both to "v.0", compared them equal, and printed PASS:

  local 'v1.0.0.0.0' -> 'v.0'   remote 'v9.9.9.9.9' -> 'v.0'   equal? True

A check that cannot fail is worse than no check, because it is believed. It
would report PASS against a container serving a build from weeks ago, which is
the exact case it exists to catch.

- The string form is read first: a stamp that states its version means it,
  even if it also carries stray numeric fields from a half-migration. A
  missing leading v is tolerated so all three shapes stay comparable.
- The legacy branch returns $null when there is nothing to build a label from,
  so a caller sees "no label" instead of a label matching every other
  unreadable stamp.
- zdeploy refuses to verify an unreadable local label, and never treats a null
  remote label as a match - otherwise $null -eq $null restores the same
  vacuous pass one level up.

CHECKSUMS.txt regenerated, since both covered files changed.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kelly Michels 2026-09-10 13:47:25 -05:00 committed by GitHub
parent 16c56dc3af
commit f27f9dc5bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 50 additions and 4 deletions

View File

@ -8,14 +8,14 @@
f1fb8fb35ec468cf9c084e28f77398b59479daab06fe96909c6c2e7e8ea1a561 zchecksums.cmd
7f967d89feeeaf4ca1fbea93a3d013a1a7b0141e11ec6f2888b6d4ac0bdb3485 zchecksums.ps1
6126973cbc0ccb22785340354afb9a1495f3d9798f60d3e6fc1c3d3a92285d67 zdeploy.cmd
bd4868914e022bb1cf4a4e755d69d76a42eddbcfbce5ae441f08969233a4004d zdeploy.ps1
d14f1d1f51b4a82c8166d81ae87f7a13663a2b0386c8c1c94ec556caa9ce62bb zdeploy.ps1
1c2c99cbb986fa8993d47404eaba89ae05b854b6396a541783b46ee22ba359e9 zec2.cmd
6b427881c670e2c1ba2ce95bee1ee446be770da85cde1fd91c36cbf40c39cff0 zec2.ps1
0e4e9cbd19fdd151bdc07df8d72aee577e446e2280dbc7afa0039d503364e991 zec2_rotatekeys.cmd
25068fa51dddd221c0b05628d78c7fa14f1d8c853c6c3f94ec760430122a1589 zec2_rotatekeys.ps1
408d43e37a6febf3543dd74f449b00d6cdb4cfa6d5ae6ab48b6b840bbfd63ecb zec2online.cmd
f1c3a0f911da86e018f9b3d4d7c9be048210aa56c016548570edfbab79fed8ef zec2online.ps1
832ff9fb1f9b318b0868272ba64194f6f7bb4259dea2e6d0417787cec990f2c1 ZHelpers.ps1
e904b06017619f0ea13a79c34f56c70e642a5f0aae7dfab55159885aac2ac384 ZHelpers.ps1
1e1dcdc76250d57b2b393a650fa853452f4cb271b58a05a7dc8c01f436958424 zkill.cmd
1eb4c23bdc0ed1a82dc495c623b49e5dcd4e342f026b4d896e32c79d592667db zkill.ps1
1c908b69fb9200610e080ac6bb8f4c15d3d7edf402d2e119c2405158e1986a52 ZKiller.ps1

View File

@ -563,14 +563,50 @@ function Get-LabelFromVersionJson {
}
function Get-LabelFromBuildJsonObj {
<#
.SYNOPSIS
The build label out of a parsed build-version.json, or $null.
.DESCRIPTION
Three stamp shapes, and this has to read all of them because deploy
verification runs BOTH sides through it - the local stamp and the one read
back from the running container. A shape it cannot read does not fail
loudly; it collapses both sides to the same wrong string and the comparison
passes unconditionally, which is worse than having no check at all.
{ "major":1, "rc":0, "beta":0, "alpha":0, "build":98 } object form
{ "version": "v1.0.0.0.98" } string form
{ "productVersion": "1.2", "buildNumber": 7 } legacy form
The string form is what the versioning scheme specifies and what zbump
writes, so it is checked FIRST - a stamp carrying both an explicit version
and stray numeric fields means the version it states.
Earned the hard way: the string form used to fall through to the legacy
branch, where productVersion is null ("") and buildNumber is null (0), so
EVERY string-form stamp became "v.0". A site verified `expect v.0` against
a live `v.0` and reported PASS while serving whatever it liked.
#>
param($obj)
if (-not $obj) { return $null }
# Five-segment scheme: v{major}.{rc}.{beta}.{alpha}.{build}
# String form: the version is stated, so state it back. Trimmed, and given
# the leading v the other branches add, so all three shapes are comparable.
if (-not [string]::IsNullOrWhiteSpace([string]$obj.version)) {
$v = ([string]$obj.version).Trim()
return $(if ($v -match '^[vV]') { 'v' + $v.Substring(1) } else { "v$v" })
}
# Object form: v{major}.{rc}.{beta}.{alpha}.{build}
if ($null -ne $obj.build -or $null -ne $obj.alpha) {
$alpha = if ($null -ne $obj.alpha) { [int]$obj.alpha } else { 1 }
return "v$([int]$obj.major).$([int]$obj.rc).$([int]$obj.beta).$alpha.$([int]$obj.build)"
}
# Legacy two-part stamp (projects not yet migrated): v{productVersion}.{buildNumber}
# Only reached when there is something to build it from; otherwise $null, so
# a caller sees "no label" instead of a label that matches everything.
if ([string]::IsNullOrWhiteSpace([string]$obj.productVersion) -and $null -eq $obj.buildNumber) { return $null }
return "v$([string]$obj.productVersion).$([int]$obj.buildNumber)"
}

View File

@ -546,6 +546,16 @@ function Wait-VerifyStaticBuild {
# advances deliberately — one version bump per release — so "is the
# build I just packed live?" means an exact match.
$expectedLabel = Get-LabelFromBuildJsonObj $PreZipBuildState
# A stamp we cannot read is not a version to check against. Comparing an
# unreadable local label to an unreadable remote one is how a verification
# once passed while the container served anything it liked, so refuse to
# run rather than run a comparison that cannot fail.
if ([string]::IsNullOrWhiteSpace($expectedLabel)) {
Write-Host "`n--- [$Key version] NOT VERIFIED - build-version.json is present but unreadable ---" -ForegroundColor Yellow
Write-Host " Got: $(($PreZipBuildState | ConvertTo-Json -Compress -Depth 4))" -ForegroundColor DarkGray
Write-Host " Expected one of: {`"version`":`"v1.0.0.0.0`"} | {major,rc,beta,alpha,build} | {productVersion,buildNumber}" -ForegroundColor DarkGray
return
}
Write-Host "`n--- [$Key] Live build verification (expect $expectedLabel) ---" -ForegroundColor Cyan
$containerName = $Proj.remote.containerName
$deadline = (Get-Date).AddSeconds(45)
@ -565,7 +575,7 @@ function Wait-VerifyStaticBuild {
}
if ($r) {
$remoteLabel = Get-LabelFromBuildJsonObj $r
if ($remoteLabel -eq $expectedLabel) {
if ($remoteLabel -and $remoteLabel -eq $expectedLabel) {
Write-Host " PASS - live build $remoteLabel matches expected." -ForegroundColor Green
return
}