feat(release): one native quad, the build time, for stable and canary

Windows VERSIONINFO and the MSIX package version were two derivations of
one fact, and the sideload one counted minutes since the last stable, which
overflows a 16-bit field after 45 days. Both now come from
storePackageVersionAt's year.hourOfYear.secondOfHour.0, so a later build
always sorts above an earlier one and the cap has nothing left to cap.
This commit is contained in:
ethernet
2026-09-21 20:28:41 -04:00
parent e10ce050af
commit c4d2bb5575
10 changed files with 115 additions and 238 deletions

View File

@@ -226,13 +226,10 @@ module.exports = {
displayName,
publisher: store ? mustStoreMsix(storeMsixWhenStore).publisher : OUT_OF_STORE_PUBLISHER,
publisherDisplayName: store ? mustStoreMsix(storeMsixWhenStore).publisherDisplayName : 'Nous Research',
// Sideload canary MSIX versions are `X.Y.Z.<minutes-since-stable>` (see
// scripts/msix-shared.mjs). setBuildNumber makes getVersionInWeirdWindowsForm
// use the BUILD_NUMBER env (4th component) instead of hardcoding ".0" — a
// stable build sets no BUILD_NUMBER and stays X.Y.Z.0, a canary build sets
// it via scripts/bundles/desktop.py so App Installer updates over equal
// canary-over-canary versions instead of refusing them.
setBuildNumber: !store && !channelRequest,
// The native quad is the build time (scripts/msix-shared.mjs::nativeQuad),
// baked into the manifest template, so the builder's own build-number
// override would stamp a second, conflicting version.
setBuildNumber: false,
// Store versions are baked into a build-time template. App semver and
// artifact filenames stay unchanged; the Store reserves revision zero.
// Floor Windows 11 22H2. Below build 18307 the manifest schema caps

View File

@@ -85,9 +85,8 @@ const manifest = substituteManifestMacros(template, (m) => {
switch (m) {
case "publisher": return options.publisher
case "publisherDisplayName": return options.publisherDisplayName
// Same 4-part derivation the real build uses (msix-shared::appIdentity) —
// a canary shows its minutes-since-stable component here too, so this
// inspection tool never disagrees with what electron-builder shipped.
// Same quad the real build stamps (msix-shared::nativeQuad) — the build
// time, so this inspection tool never disagrees with what shipped.
case "version": return appIdentity(desktop, process.env.HERMES_PAYLOAD_TAG).version
case "applicationId": return resolvePackageApplicationId(options.applicationId, options.identityName, appInfoName, "MSIX")
case "identityName": return resolvePackageIdentityName(options.identityName, appInfoName, "MSIX")

View File

@@ -45,10 +45,9 @@ function makeFakeDesktop(version) {
return dir
}
function gitMock(tags, stableEpoch) {
function gitMock(epoch) {
execFileSync.mockImplementation((cmd, args) => {
if (args[0] === 'tag') return `${tags.join('\n')}\n`
if (args[0] === 'log') return `${stableEpoch}\n`
if (args[0] === 'for-each-ref') return `${epoch}\n`
throw new Error(`unexpected git call ${cmd} ${args.join(' ')}`)
})
}
@@ -73,28 +72,23 @@ beforeEach(() => {
execFileSync.mockReset()
})
test('stable tag: appIdentity derivation round-trips into the manifest Version', () => {
test('stable tag: the build-time quad round-trips into the manifest Version', () => {
const app = makeFakeDesktop('0.27.1')
gitMock(Math.floor(Date.UTC(2026, 7, 29, 1, 2, 3) / 1000))
const { version, xml } = buildManifest(app, 'v0.27.1')
assert.equal(version, '0.27.1.0')
assert.equal(version, '2026.5761.123.0')
assert.equal(identityVersion(xml), version)
})
test('canary tag: minutes-since-stable build number round-trips into the manifest Version', () => {
test('canary tag: the build-time quad round-trips into the manifest Version', () => {
const app = makeFakeDesktop('0.27.1')
// Stable v0.27.1 committed 2026-08-01T00:00:00Z; canary cut 2026-08-29T01:02:03Z.
const stableEpoch = Math.floor(Date.UTC(2026, 7, 1) / 1000)
gitMock(['v0.27.1', 'v0.27.2-canary.20260829010203'], stableEpoch)
const { version, xml } = buildManifest(app, 'v0.27.2-canary.20260829010203')
const expectedMinutes = Math.floor((Date.UTC(2026, 7, 29, 1, 2, 3) - Date.UTC(2026, 7, 1)) / 60000)
assert.equal(version, `0.27.2.${expectedMinutes}`)
assert.equal(version, '2026.5761.123.0')
assert.equal(identityVersion(xml), version)
})
test('manifest Version components are 16-bit (makeappx rejects anything larger)', () => {
const app = makeFakeDesktop('0.27.1')
const stableEpoch = Math.floor(Date.UTC(2026, 7, 1) / 1000)
gitMock(['v0.27.1', 'v0.27.2-canary.20260829010203'], stableEpoch)
const { xml } = buildManifest(app, 'v0.27.2-canary.20260829010203')
for (const part of identityVersion(xml).split('.')) {
const n = Number(part)
@@ -102,15 +96,9 @@ test('manifest Version components are 16-bit (makeappx rejects anything larger)'
}
})
test('a later canary stamps a strictly larger BUILD_NUMBER than an earlier one', () => {
// The build number exists so Windows can order canaries on the same
// base version; monotonicity across the stamp is the actual contract.
test('a later build stamps a strictly larger quad than an earlier one', () => {
const app = makeFakeDesktop('0.27.1')
const stableEpoch = Math.floor(Date.UTC(2026, 7, 1) / 1000)
const early = 'v0.27.2-canary.20260815080000'
const late = 'v0.27.2-canary.20260829010203'
gitMock([early, late, 'v0.27.1'], stableEpoch)
const earlyBuild = Number(buildManifest(app, early).version.split('.')[3])
const lateBuild = Number(buildManifest(app, late).version.split('.')[3])
assert.ok(lateBuild > earlyBuild, `${lateBuild} must exceed ${earlyBuild}`)
const early = Number(buildManifest(app, 'v0.27.2-canary.20260815080000').version.split('.')[1])
const late = Number(buildManifest(app, 'v0.27.2-canary.20260829010203').version.split('.')[1])
assert.ok(late > early, `${late} must exceed ${early}`)
})

View File

@@ -1,7 +1,6 @@
// msix-shared — the 4-part MSIX version derivation (minutes-since-stable
// for canaries). The git-backed lookup is deterministic here because
// node:child_process.execFileSync is mocked; the math it feeds is the
// contract App Installer compares.
// 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.
import assert from 'node:assert/strict'
import fs from 'node:fs'
import os from 'node:os'
@@ -28,23 +27,19 @@ function makeFakeDesktop(version) {
return dir
}
function gitMock(tags, stableEpoch) {
execFileSync.mockImplementation((cmd, args) => {
if (args[0] === 'tag') return `${tags.join('\n')}\n`
if (args[0] === 'log') return `${stableEpoch}\n`
throw new Error(`unexpected git call ${cmd} ${args.join(' ')}`)
})
}
beforeEach(() => {
execFileSync.mockReset()
})
test('explicit stable tag owns the package version, independent of checkout metadata', () => {
const desktop = makeFakeDesktop('0.1.0')
execFileSync.mockImplementation((cmd, args) => {
if (args[0] === 'for-each-ref') return `${Math.floor(Date.UTC(2026, 7, 29, 1, 2, 3) / 1000)}\n`
throw new Error(`unexpected git call ${cmd} ${args.join(' ')}`)
})
try {
const { version, fileVersion } = msix.appIdentity(desktop, 'v0.27.1')
assert.equal(version, '0.27.1.0')
assert.equal(version, '2026.5761.123.0')
assert.equal(fileVersion, '0.27.1')
assert.equal(msix.appIdentity(desktop, '').version, '0.1.0.0')
assert.throws(() => msix.appIdentity(desktop, 'v0.27.1-invalid'), /release tag/)
@@ -53,39 +48,20 @@ test('explicit stable tag owns the package version, independent of checkout meta
}
})
test('canary version is tag base + minutes since the same-minor stable', () => {
test('a stable tag and a canary stamp the same quad when built together', () => {
const desktop = makeFakeDesktop('0.27.1')
// Stable v0.27.1 committed 2026-08-01T00:00:00Z; canary cut 2026-08-29T01:02:03Z.
const stableEpoch = Math.floor(Date.UTC(2026, 7, 1) / 1000)
gitMock(['v0.27.1', 'v0.27.2-canary.20260829010203'], stableEpoch)
const { version, fileVersion } = msix.appIdentity(desktop, 'v0.27.2-canary.20260829010203')
const expectedMinutes = Math.floor((Date.UTC(2026, 7, 29, 1, 2, 3) - Date.UTC(2026, 7, 1)) / 60000)
assert.equal(version, `0.27.2.${expectedMinutes}`)
// The artifact FILENAME carries the full canary string (appInfo.version),
// not the 4-part feed version.
assert.equal(fileVersion, '0.27.2-canary.20260829010203')
})
test('canaryBuildMinutesFor is pure minutes math', () => {
const base = Date.UTC(2026, 7, 1) / 1000
const minutes = msix.canaryBuildMinutesFor('v0.27.2-canary.20260829010203', base)
assert.equal(minutes, Math.floor((Date.UTC(2026, 7, 29, 1, 2, 3) - Date.UTC(2026, 7, 1)) / 60000))
})
test('canaryBuildMinutesFor returns null for a stable tag', () => {
assert.equal(msix.canaryBuildMinutesFor('v0.27.1', 0), null)
})
test('canaryBuildMinutesFor rejects a stable base older than 45 days', () => {
const base = Date.UTC(2026, 5, 1) / 1000 // 2026-06-01
assert.throws(
() => msix.canaryBuildMinutesFor('v0.27.2-canary.20260829010203', base),
/45 days|16-bit/i
)
})
test('legacy 8-digit canary stamp still computes minutes (midnight of that day)', () => {
const base = Date.UTC(2026, 7, 1) / 1000
const minutes = msix.canaryBuildMinutesFor('v0.27.2-canary.20260801', base)
assert.equal(minutes, 0)
const epoch = Math.floor(Date.UTC(2026, 7, 29, 1, 2, 3) / 1000)
execFileSync.mockImplementation((cmd, args) => {
if (args[0] === 'for-each-ref') return `${epoch}\n`
throw new Error(`unexpected git call ${cmd} ${args.join(' ')}`)
})
try {
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.fileVersion, '0.27.2-canary.20260829010203')
} finally {
fs.rmSync(desktop, { recursive: true, force: true })
}
})

View File

@@ -0,0 +1,31 @@
import assert from 'node:assert/strict'
import { test } from 'vitest'
import { nativeQuad } from '../../../scripts/msix-shared.mjs'
// 2026-09-22T00:14:03Z, worked by hand: day-of-year 264 (2026 is not a leap
// year; Jan 1 is hour 0), so hour-of-year is 264*24 = 6336. The 14th minute
// 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', () => {
assert.equal(nativeQuad('v0.21.5', EPOCH), '2026.6336.843.0')
assert.equal(nativeQuad('v0.21.4+canary.20260922T001403Z', EPOCH), '2026.6336.843.0')
})
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)
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', () => {
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)
}
})

View File

@@ -1,42 +0,0 @@
// windows-file-version.mjs — the Windows VERSIONINFO quad for a release tag.
//
// Windows VERSIONINFO holds four 16-bit fields, so a canary's semver
// string cannot go in it. resedit splits whatever it is handed on "." and
// clamps each token to [0, 65535], so `0.28.0-canary.20260819171926`
// becomes 0.28.0.65535: "0-canary" parses as NaN and falls to the min,
// and the timestamp saturates at the max. Every canary of a minor then
// shows the same meaningless quad on the Details tab.
//
// The timestamp is the only part worth showing there — the semver line is
// identical across a whole canary series — so pack it as
// yyyy.mmdd.hhmm.ss. Every field stays under 65536, and the quad compares
// in timestamp order, which is the order Windows sorts versions in.
//
// Display metadata only: electron-updater keys on the semver version in
// extraMetadata, never on this quad.
/**
* The `yyyy.mmdd.hhmm.ss` quad for a canary tag, or null for a stable tag
* (whose version is already four legal fields or fewer, so app-builder-lib's
* own handling is correct).
*
* @param {string} releaseTag A release tag, with the leading `v`.
* @returns {string | null}
*/
export function windowsFileVersion(releaseTag) {
const stamp = /-canary\.(20\d{6}(?:\d{6})?)$/.exec(String(releaseTag))?.[1]
if (!stamp) {
return null
}
// The legacy date-only shape (YYYYMMDD) is midnight of that day.
const parts = /^(\d{4})(\d{2})(\d{2})(\d{2})?(\d{2})?(\d{2})?$/.exec(stamp)
if (!parts) {
return null
}
const [, year, month, day, hour, minute, second] = parts
const field = (a, b) => Number(`${a ?? '00'}${b ?? '00'}`)
return `${Number(year)}.${field(month, day)}.${field(hour, minute)}.${Number(second ?? 0)}`
}

View File

@@ -1,14 +0,0 @@
import assert from 'node:assert/strict'
import { test } from 'vitest'
import { windowsFileVersion } from './windows-file-version.mjs'
test('stable tags leave VERSIONINFO to electron-builder', () => {
assert.equal(windowsFileVersion('v0.20.5'), null)
assert.equal(windowsFileVersion('v0.28.0'), null)
})
test('canary tags pack the timestamp into a legal quad', () => {
assert.equal(windowsFileVersion('v0.28.0-canary.20260819171926'), '2026.819.1719.26')
assert.equal(windowsFileVersion('v0.20.5-canary.20260826'), '2026.826.0.0')
})

View File

@@ -97,22 +97,13 @@ def _build_prepared(prepared, builder_args: list[str], variant: str | None) -> N
run([node, "scripts/build/desktop.mjs", "--source", str(repo), "--icons", str(icons),
"--stamp", str(desktop / "build/install-stamp.json"), "--native-deps", str(prepared.native),
"--out", str(desktop / "dist")], cwd=repo, env=env)
# Windows file-version and MSIX build-number policy remains with its packager.
# The native quad is the build time, for stable and canary both, so there
# is no per-channel build number to inject.
version_args = []
if sys.platform == "win32":
if request.channel_request is not None:
metadata = {"file": request.channel_request["windowsVersion"], "build": None}
elif request.tag is None:
# The plain version needs no canary build-number override.
metadata = {"file": None, "build": None}
else:
script = "const w=require('./apps/desktop/scripts/windows-file-version.mjs');const m=require('./scripts/msix-shared.mjs');console.log(JSON.stringify({file:w.windowsFileVersion(process.argv[1]),build:process.argv[2]!=='store'&&process.argv[1].includes('-canary.')?m.canaryBuildMinutes(process.argv[1],process.cwd()):null}))"
metadata = json.loads(capture([node, "-e", script, request.tag, variant], repo))
env.pop("BUILD_NUMBER", None)
if metadata["build"] is not None and variant != "store":
env["BUILD_NUMBER"] = str(metadata["build"])
if metadata["file"]:
version_args = [f'-c.extraMetadata.shortVersion={metadata["file"]}', f'-c.extraMetadata.shortVersionWindows={metadata["file"]}']
if sys.platform == "win32" and request.channel_request is None and request.tag is not None:
script = "const m=require('./scripts/msix-shared.mjs');console.log(m.nativeQuad(process.argv[1], Math.floor(Date.now()/1000)))"
quad = capture([node, "-e", script, request.tag], repo).strip()
version_args = [f'-c.extraMetadata.shortVersion={quad}', f'-c.extraMetadata.shortVersionWindows={quad}']
require_source(repo, request.commit)
run([node, "scripts/run-electron-builder.mjs", *package_args, *version_args, *builder_args], cwd=desktop,
env=packaging_environment(env, os.environ, request.target))

View File

@@ -81,12 +81,6 @@ export function contentTypeFor(filename) {
const CANARY_TAG_RE = /^v(\d+\.\d+\.\d+)-canary\.(20\d{6}(?:\d{6})?)$/
const STABLE_TAG_RE = /^v\d+\.\d+\.\d+$/
// MSIX version components are 16-bit (makeappx rejects >65535). Minutes
// since the last stable cross it at 45.5 days; a canary cut more than 45
// days after its stable is a process failure worth surfacing loudly, not a
// number to clamp (a clamped number would break monotonicity).
const MAX_BUILD_MINUTES = 45 * 24 * 60
/**
* @param {string} stamp YYYYMMDD[HHMMSS] UTC stamp
* @returns {number} epoch seconds
@@ -98,65 +92,6 @@ function stampToEpoch(stamp) {
return Date.UTC(Number(y), Number(mo) - 1, Number(d), Number(h ?? 0), Number(mi ?? 0), Number(s ?? 0)) / 1000
}
/**
* List git tags matching `pattern`, newest-first (git's -v:refname sort).
* @param {string} gitRoot the repo root
* @param {string} pattern git tag glob, e.g. "v0.27.*"
* @returns {string[]}
*/
export function listGitTags(gitRoot, pattern) {
return execFileSync('git', ['tag', '--list', pattern, '--sort=-v:refname'], { cwd: gitRoot, encoding: 'utf8' })
.split('\n').filter(Boolean)
}
/**
* The commit time (epoch seconds) of `tag`, for minutes-since-stable math.
* @param {string} gitRoot the repo root
* @param {string} tag a git tag
* @returns {number}
*/
export function gitTagCommitTime(gitRoot, tag) {
return Number(execFileSync('git', ['log', '-1', '--format=%ct', tag], { cwd: gitRoot, encoding: 'utf8' }).trim())
}
/**
* Minutes between a canary tag's UTC stamp and the given stable epoch —
* the MSIX 4th version component. Null for a stable tag; throws when the
* stable base is older than 45 days (16-bit component would overflow).
* @param {string} tag the release tag
* @param {number} stableEpoch stable tag commit time, epoch seconds
* @returns {number | null}
*/
export function canaryBuildMinutesFor(tag, stableEpoch) {
const m = CANARY_TAG_RE.exec(String(tag || ''))
if (!m) return null
const minutes = Math.floor((stampToEpoch(m[2]) - stableEpoch) / 60)
if (minutes < 0) return 0
if (minutes > MAX_BUILD_MINUTES) {
throw new Error(
`canary ${tag} is ${Math.floor(minutes / 1440)} days past its stable base — ` +
`MSIX versions cap at 16 bits (45 days); cut a stable first`
)
}
return minutes
}
/**
* Minutes-since-stable for a canary tag, resolving the stable base from
* the repo's tags on the same major.minor line.
* @param {string} tag the release tag
* @param {string} gitRoot the repo root
* @returns {number | null}
*/
export function canaryBuildMinutes(tag, gitRoot) {
const m = CANARY_TAG_RE.exec(String(tag || ''))
if (!m) return null
const majorMinor = m[1].split('.').slice(0, 2).join('.')
const stable = listGitTags(gitRoot, `v${majorMinor}.*`).find(t => STABLE_TAG_RE.test(t))
if (!stable) return 0 // degenerate: no stable on this line; build number restarts
return canaryBuildMinutesFor(tag, gitTagCommitTime(gitRoot, stable))
}
/** Store reserves revision for itself. Keep its package sequence separate
* from the app's displayed semver and the sideload update sequence.
* Calendar fields retain second precision without an epoch offset.
@@ -174,21 +109,43 @@ export function storePackageVersionAt(epochSeconds) {
return `${year}.${hourOfYear}.${secondOfHour}.0`
}
/** @param {string} tag @param {string} gitRoot */
export function storePackageVersion(tag, gitRoot) {
/** The native quad for a release, stable or canary: ``year.hourOfYear.secondOfHour.0``.
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
@param {number} epochSeconds the build time, UTC
@returns {string}
*/
export function nativeQuad(_ref, epochSeconds) {
return storePackageVersionAt(epochSeconds)
}
/** The build time of a release tag: the stamp embedded in a canary tag, or
* the tag's own creation time for a stable tag.
* @param {string} tag @param {string} gitRoot @returns {number} epoch seconds
*/
function releaseEpoch(tag, gitRoot) {
const canary = CANARY_TAG_RE.exec(tag)
if (canary) {
const epoch = stampToEpoch(canary[2])
const roundtrip = new Date(epoch * 1000).toISOString().replace(/[-:T]/g, '').slice(0, canary[2].length)
if (roundtrip !== canary[2]) throw new Error('Invalid canary calendar timestamp')
return storePackageVersionAt(epoch)
return epoch
}
if (!STABLE_TAG_RE.test(tag)) throw new Error('A Store build requires an exact release tag')
const timestamp = execFileSync('git', ['for-each-ref', '--format=%(creatordate:unix)', `refs/tags/${tag}`], {
cwd: gitRoot, encoding: 'utf8'
}).trim()
if (!/^\d+$/.test(timestamp)) throw new Error(`No immutable release timestamp for ${tag}`)
return storePackageVersionAt(Number(timestamp))
return Number(timestamp)
}
/** @param {string} tag @param {string} gitRoot */
export function storePackageVersion(tag, gitRoot) {
if (!CANARY_TAG_RE.test(tag) && !STABLE_TAG_RE.test(tag)) throw new Error('A Store build requires an exact release tag')
return nativeQuad(tag, releaseEpoch(tag, gitRoot))
}
/** The custom template controls package identity, not executable VERSIONINFO.
@@ -256,23 +213,18 @@ export function appIdentity(desktopDir, tag = process.env.HERMES_PAYLOAD_TAG ||
return { identity, version: `${version}.0`, fileVersion: version, name: identity.artifactNamePascal }
}
if (identity.store) {
return { identity, version: storePackageVersion(String(tag), repoRoot),
return { identity, version: nativeQuad(String(tag), releaseEpoch(String(tag), repoRoot)),
fileVersion: String(tag).slice(1), name: identity.artifactNamePascal }
}
const canary = CANARY_TAG_RE.exec(String(tag))
if (canary) {
// Manifest + feed version: tag base (0.27.2) + minutes-since-stable.
// The artifact FILENAME carries electron-builder's appInfo.version — the
// full canary string (HermesBundled-0.27.2-canary.X-win-x64.msix) — so
// callers that look files up by name need that string separately.
if (CANARY_TAG_RE.test(String(tag)) || (tag && STABLE_TAG_RE.test(tag))) {
return {
identity,
version: `${canary[1]}.${canaryBuildMinutes(String(tag), repoRoot)}`,
version: nativeQuad(String(tag), releaseEpoch(String(tag), repoRoot)),
fileVersion: String(tag).slice(1),
name: identity.artifactNamePascal,
}
}
if (tag && !STABLE_TAG_RE.test(tag)) throw new Error(`Invalid release tag: ${tag}`)
if (tag) throw new Error(`Invalid release tag: ${tag}`)
// Release builds override Electron's version without rewriting package.json.
const version = tag ? tag.slice(1) : pkg.version
return {

View File

@@ -47,13 +47,12 @@ def canary(tmp_path, r2_server, monkeypatch):
(desktop / "product-identity.cjs").symlink_to(ROOT / "apps/desktop/product-identity.cjs")
monkeypatch.chdir(clone)
identity = channel_releases.product_identity(tag)
minutes = subprocess.check_output([
windows_version = subprocess.check_output([
"node", "--input-type=module", "-e",
f"import {{canaryBuildMinutesFor}} from {json.dumps((ROOT / 'scripts/msix-shared.mjs').as_uri())};"
f"console.log(canaryBuildMinutesFor({json.dumps(tag)}, Date.UTC(2026, 8, 13) / 1000))",
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()
windows_version = tag[1:].split("-", 1)[0] + "." + minutes
assert windows_version == "0.1.3.10"
assert windows_version == "2026.6120.600.0"
tools = tmp_path / "tools"
tools.mkdir()
driver = tools / "driver.py"
@@ -223,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"] == "0.1.3.10"
assert resolved.manifest["request"]["windowsVersion"] == "2026.6120.600.0"
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,
@@ -265,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": "0.1.3.11"}
"FIXTURE_WINDOWS_VERSION": "2026.6120.660.0"}
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"] == "0.1.3.11"
assert advanced.manifest["request"]["windowsVersion"] == "2026.6120.660.0"
assert advanced.terminal["head"]["sequence"] > resolved.terminal["head"]["sequence"]
after = dict(r2_server.store)
release.write_text(json.dumps(published))