From fcfd001dc03fc19f61f66a68aae6154176eb314e Mon Sep 17 00:00:00 2001 From: ethernet Date: Mon, 21 Sep 2026 21:10:49 -0400 Subject: [PATCH] feat(release): stamp canary Windows builds as yy.mmdd.hh.mmss --- apps/desktop/scripts/msix-shared.test.mjs | 10 +++--- apps/desktop/scripts/native-quad.test.mjs | 27 +++++++++------ scripts/msix-shared.mjs | 33 ++++++++++++++----- tests/ci/test_protected_canary_publication.py | 8 ++--- 4 files changed, 51 insertions(+), 27 deletions(-) diff --git a/apps/desktop/scripts/msix-shared.test.mjs b/apps/desktop/scripts/msix-shared.test.mjs index 57e49cfe49..be88f9d1a9 100644 --- a/apps/desktop/scripts/msix-shared.test.mjs +++ b/apps/desktop/scripts/msix-shared.test.mjs @@ -1,6 +1,6 @@ -// msix-shared — the native quad (the build time) is the package version for -// stable and canary both. The git-backed lookup is deterministic here because -// node:child_process.execFileSync is mocked. +// msix-shared — native package versions come from the immutable build time. +// Stable keeps the Store quad; canary uses yy.mmdd.hh.mmss. The git-backed +// lookup is deterministic here because node:child_process.execFileSync is mocked. import assert from 'node:assert/strict' import fs from 'node:fs' import os from 'node:os' @@ -48,7 +48,7 @@ test('explicit stable tag owns the package version, independent of checkout meta } }) -test('a stable tag and a canary stamp the same quad when built together', () => { +test('stable and canary use their own native build-time quads', () => { const desktop = makeFakeDesktop('0.27.1') const epoch = Math.floor(Date.UTC(2026, 7, 29, 1, 2, 3) / 1000) execFileSync.mockImplementation((cmd, args) => { @@ -59,7 +59,7 @@ test('a stable tag and a canary stamp the same quad when built together', () => const stable = msix.appIdentity(desktop, 'v0.27.1') const canary = msix.appIdentity(desktop, 'v0.27.2-canary.20260829010203') assert.equal(stable.version, '2026.5761.123.0') - assert.equal(canary.version, stable.version) + assert.equal(canary.version, '26.829.1.203') assert.equal(canary.fileVersion, '0.27.2-canary.20260829010203') } finally { fs.rmSync(desktop, { recursive: true, force: true }) diff --git a/apps/desktop/scripts/native-quad.test.mjs b/apps/desktop/scripts/native-quad.test.mjs index c27ef34b71..d76d01c62e 100644 --- a/apps/desktop/scripts/native-quad.test.mjs +++ b/apps/desktop/scripts/native-quad.test.mjs @@ -8,24 +8,31 @@ import { nativeQuad } from '../../../scripts/msix-shared.mjs' // holds 14*60+3 = 843 seconds. const EPOCH = Date.parse('2026-09-22T00:14:03Z') / 1000 -test('a stable release and a canary built at the same instant share one quad', () => { +test('stable keeps the store quad while canary uses yy.mmdd.hh.mmss', () => { assert.equal(nativeQuad('v0.21.5', EPOCH), '2026.6336.843.0') - assert.equal(nativeQuad('v0.21.4+canary.20260922T001403Z', EPOCH), '2026.6336.843.0') + assert.equal(nativeQuad('v0.21.4+canary.20260922T001403Z', EPOCH), '26.922.0.1403') }) -test('a later build sorts above an earlier one, stable or canary', () => { - const earlier = nativeQuad('v0.21.5', EPOCH).split('.').map(Number) - const later = nativeQuad('v0.21.4+canary.20260922T001404Z', EPOCH + 1).split('.').map(Number) +test('a later canary sorts above an earlier canary across a month boundary', () => { + const earlier = nativeQuad( + 'v0.21.4+canary.20260131T235959Z', + Date.parse('2026-01-31T23:59:59Z') / 1000 + ).split('.').map(Number) + const later = nativeQuad( + 'v0.21.4+canary.20260201T000000Z', + Date.parse('2026-02-01T00:00:00Z') / 1000 + ).split('.').map(Number) const first = later.findIndex((value, index) => value !== earlier[index]) assert.ok(first >= 0) assert.ok(later[first] > earlier[first]) }) -test('every field stays inside 16 bits and the revision stays zero', () => { +test('every field stays inside 16 bits', () => { for (const stamp of ['2026-01-01T00:00:00Z', '2026-12-31T23:59:59Z', '2028-12-31T23:59:59Z']) { - const parts = nativeQuad('v0.21.5', Date.parse(stamp) / 1000).split('.').map(Number) - assert.equal(parts.length, 4) - assert.ok(parts.every(value => value >= 0 && value <= 65535)) - assert.equal(parts[3], 0) + for (const ref of ['v0.21.5', `v0.21.4+canary.${stamp.replace(/[-:T]/g, '').slice(0, 15)}Z`]) { + const parts = nativeQuad(ref, Date.parse(stamp) / 1000).split('.').map(Number) + assert.equal(parts.length, 4) + assert.ok(parts.every(value => value >= 0 && value <= 65535)) + } } }) diff --git a/scripts/msix-shared.mjs b/scripts/msix-shared.mjs index 6813b12166..6952c642eb 100644 --- a/scripts/msix-shared.mjs +++ b/scripts/msix-shared.mjs @@ -109,18 +109,35 @@ export function storePackageVersionAt(epochSeconds) { return `${year}.${hourOfYear}.${secondOfHour}.0` } -/** The native quad for a release, stable or canary: ``year.hourOfYear.secondOfHour.0``. +/** Canary package quad: ``yy.mmdd.hh.mmss`` in UTC. + * @param {number} epochSeconds immutable build time in UTC + * @returns {string} + */ +export function canaryPackageVersionAt(epochSeconds) { + const date = new Date(epochSeconds * 1000) + if (!Number.isInteger(epochSeconds) || !Number.isFinite(date.getTime())) { + throw new Error('Canary package version needs a valid immutable build timestamp') + } + const yy = date.getUTCFullYear() % 100 + const mmdd = (date.getUTCMonth() + 1) * 100 + date.getUTCDate() + const hh = date.getUTCHours() + const mmss = date.getUTCMinutes() * 100 + date.getUTCSeconds() + return `${yy}.${mmdd}.${hh}.${mmss}` +} -One derivation for every Windows consumer. The quad is the build time, so a -later build always sorts above an earlier one and a canary shares its stable's -quad when they are built at the same instant. There is no minutes-since-stable -counter, so there is no 45-day cap. -@param {string} _ref the release ref; accepted so callers name what they stamp +/** The native quad for a release. + +Stable Store packages retain ``year.hourOfYear.secondOfHour.0``. Canary uses +``yy.mmdd.hh.mmss`` so its four numeric fields show the UTC build time directly +while remaining monotonic across month boundaries. +@param {string} ref the release ref whose native package is being stamped @param {number} epochSeconds the build time, UTC @returns {string} */ -export function nativeQuad(_ref, epochSeconds) { - return storePackageVersionAt(epochSeconds) +export function nativeQuad(ref, epochSeconds) { + return /(?:\+|-)canary\./.test(ref) + ? canaryPackageVersionAt(epochSeconds) + : storePackageVersionAt(epochSeconds) } /** The build time of a release tag: the stamp embedded in a canary tag, or diff --git a/tests/ci/test_protected_canary_publication.py b/tests/ci/test_protected_canary_publication.py index 2060eb5e01..d94376a144 100644 --- a/tests/ci/test_protected_canary_publication.py +++ b/tests/ci/test_protected_canary_publication.py @@ -52,7 +52,7 @@ def canary(tmp_path, r2_server, monkeypatch): f"import {{nativeQuad}} from {json.dumps((ROOT / 'scripts/msix-shared.mjs').as_uri())};" f"console.log(nativeQuad({json.dumps(tag)}, Date.parse('2026-09-13T00:10:00Z') / 1000))", ], text=True).strip() - assert windows_version == "2026.6120.600.0" + assert windows_version == "26.913.0.1000" tools = tmp_path / "tools" tools.mkdir() driver = tools / "driver.py" @@ -222,7 +222,7 @@ def test_published_canary_workflow_advances_only_after_every_gate(canary, r2_ser assert result.returncode == 0, result.stdout + result.stderr resolved = ChannelReader(env["CLOUDFLARE_R2_PUBLIC_URL"], repository=env["GITHUB_REPOSITORY"]).resolve("canary") assert resolved.manifest is not None - assert resolved.manifest["request"]["windowsVersion"] == "2026.6120.600.0" + assert resolved.manifest["request"]["windowsVersion"] == "26.913.0.1000" assert resolved.manifest["request"]["commit"] == env["RELEASE_COMMIT"] assert len(resolved.manifest["packages"]) == 4 # Bootstrap reads actual receipt-bound artifacts and the promoted native feeds, @@ -264,14 +264,14 @@ def test_published_canary_workflow_advances_only_after_every_gate(canary, r2_ser _git("tag", newer_tag, cwd=clone) _git("push", "origin", newer_tag, cwd=clone) newer = {**env, "RELEASE_TAG": newer_tag, "HERMES_PAYLOAD_TAG": newer_tag, - "FIXTURE_WINDOWS_VERSION": "2026.6120.660.0"} + "FIXTURE_WINDOWS_VERSION": "26.913.0.1100"} stage_canary(clone, identity, newer, run) release.write_text(json.dumps({**published, "tagName": newer_tag})) result = run(script, **newer) assert result.returncode == 0, result.stdout + result.stderr advanced = ChannelReader(env["CLOUDFLARE_R2_PUBLIC_URL"], repository=env["GITHUB_REPOSITORY"]).resolve("canary") assert advanced.manifest is not None - assert advanced.manifest["request"]["windowsVersion"] == "2026.6120.660.0" + assert advanced.manifest["request"]["windowsVersion"] == "26.913.0.1100" assert advanced.terminal["head"]["sequence"] > resolved.terminal["head"]["sequence"] after = dict(r2_server.store) release.write_text(json.dumps(published))