build: zero-pad the derived version to YYYY.MM.DD.HHMM (318 step 5)
Build images / sign-extension (push) Successful in 4s
CI / lint (push) Successful in 4s
CI / extension-version (push) Successful in 5s
Build images / build-ml (push) Successful in 4s
Build images / build-agent (push) Successful in 5s
Build images / build-web (push) Successful in 4s
CI / frontend-build (push) Successful in 17s
CI / backend-lint-and-test (push) Successful in 32s
CI / integration (push) Successful in 3m50s
Build images / sign-extension (push) Successful in 4s
CI / lint (push) Successful in 4s
CI / extension-version (push) Successful in 5s
Build images / build-ml (push) Successful in 4s
Build images / build-agent (push) Successful in 5s
Build images / build-web (push) Successful in 4s
CI / frontend-build (push) Successful in 17s
CI / backend-lint-and-test (push) Successful in 32s
CI / integration (push) Successful in 3m50s
`2026.8.28.1249` becomes `2026.08.28.1249`. Note #3127 §1 and rule 148 both specify the padded form. The old reasoning was that each segment should read as a plain integer, and it never held — comparison strips leading zeros on parse anyway, which the same paragraph said. What stripping actually bought was this project emitting `2026.8.28.1432` while a sibling emitted `2026.08.28.1432`: two shapes one character apart, which is the hard kind of difference to notice. Two obviously different formats would be safer than two nearly identical ones, and identical is safer still. Nothing already published is reordered: comparison is numeric per dot-segment, so `08` and `8` are equal. strip0 goes, and with it three of the four git calls per version — git's format-local takes the whole format string, and splitting it into pieces only ever existed to strip the padding between them. It also fixes a real edge the old helper mangled. A commit at 03:22 UTC derived `322` for its HHMM field, silently turning a four-digit field into three; it now derives `0322`. Verified against a real commit rather than reasoned about. Checked before relying on it, since step 8 feeds this to Firefox: the extension's comparator is `parseInt(n, 10)` with an explicit radix, so `08` reads as 8 and there is no octal hazard (rule 150). Two tests added. One pins the padded shape — the only thing keeping the family's projects emitting one string is an assertion that they do. The other asserts version and revision describe the same commit: they are derived independently, and a divergence would mean an instance naming one commit while carrying another's bytes, which is unfalsifiable from outside because both values still look well-formed.
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
"""`artifacts.sh revision` is what decides whether a build gets skipped.
|
||||
"""The two values `artifacts.sh` derives, and what each of them promises.
|
||||
|
||||
`revision` decides whether a build gets skipped; `version` is what an instance
|
||||
reports about itself and what a release tag is named after. Neither has a
|
||||
consumer that would notice it going subtly wrong.
|
||||
|
||||
## revision
|
||||
|
||||
Milestone 318 step 3: each image carries its revision as an `fc.revision`
|
||||
label, and build.yml reads that label back off the moving channel tag. Equal
|
||||
@@ -21,12 +27,20 @@ Both ways of getting it wrong are silent:
|
||||
This module owns the narrower claim: whatever the path sets say, the revision
|
||||
is genuinely the commit those paths last changed in.
|
||||
|
||||
## version
|
||||
|
||||
`YYYY.MM.DD.HHMM`, zero-padded, UTC — one shape across the family (note #3127
|
||||
§1, rule 148), so the string this project emits is the same string its siblings
|
||||
emit. Two nearly-identical formats are more dangerous than two obviously
|
||||
different ones, and the only thing keeping them identical is a test.
|
||||
|
||||
The identity-TAG tests this file used to hold are gone with the tag. There is
|
||||
no longer a `CHANNELLED` list to drift (the channel is which tag you inspect),
|
||||
and no `identity` subcommand to refuse an unqualified call.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
@@ -40,6 +54,9 @@ ARTIFACTS = ("web", "ml", "agent", "extension")
|
||||
# 12 hex chars — the prefix build.yml stamps and compares.
|
||||
_REVISION = re.compile(r"^[0-9a-f]{12}$")
|
||||
|
||||
# YYYY.MM.DD.HHMM, every segment zero-padded to its full width.
|
||||
_VERSION = re.compile(r"^\d{4}\.\d{2}\.\d{2}\.\d{4}$")
|
||||
|
||||
|
||||
# Everything here goes through artifacts.sh rather than importing a sibling
|
||||
# test module. That is the interface build.yml actually calls, so the tests
|
||||
@@ -107,3 +124,43 @@ def test_revision_is_a_legal_label_value_and_is_stable(artifact):
|
||||
first = revision(artifact)
|
||||
assert _REVISION.match(first), f"{first!r} is not a 12-char hex revision"
|
||||
assert first == revision(artifact), "revision is not stable across calls"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("artifact", ARTIFACTS)
|
||||
def test_version_is_zero_padded_calver(artifact):
|
||||
"""The family shape, pinned.
|
||||
|
||||
Padding was stripped until 2026-08-28 on the reasoning that each segment
|
||||
should read as a plain integer — which never held, since comparison strips
|
||||
leading zeros on parse anyway. What it did do was make this project emit
|
||||
`2026.8.28.1432` while a sibling emitted `2026.08.28.1432`: two shapes one
|
||||
character apart, which is the hard kind of difference to notice.
|
||||
|
||||
Also catches the midnight case. A `%H%M` of `0322` must survive as `0322`;
|
||||
the old strip-leading-zeros helper turned it into `322`, silently changing
|
||||
a four-digit field into three.
|
||||
"""
|
||||
value = artifacts("version", artifact).strip()
|
||||
assert _VERSION.match(value), (
|
||||
f"{artifact} derives {value!r}, which is not zero-padded "
|
||||
f"YYYY.MM.DD.HHMM. Note #3127 §1 and rule 148 both specify the padded "
|
||||
f"form, and a release tag is this string with a `v` in front."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("artifact", ARTIFACTS)
|
||||
def test_version_and_revision_describe_the_same_commit(artifact):
|
||||
"""They are derived independently and must not be able to disagree.
|
||||
|
||||
A build reports the version and skips on the revision, so a divergence
|
||||
would mean an instance naming one commit while carrying another's bytes —
|
||||
unfalsifiable from outside, since both values look perfectly well-formed.
|
||||
"""
|
||||
sha = newest_by_commit_time(artifact)
|
||||
stamped = subprocess.run(
|
||||
["git", "show", "-s", "--format=%cd", "--date=format-local:%Y.%m.%d.%H%M", sha],
|
||||
capture_output=True, text=True, check=True, cwd=ROOT,
|
||||
env={"TZ": "UTC", "PATH": os.environ.get("PATH", "")},
|
||||
).stdout.strip()
|
||||
assert artifacts("version", artifact).strip() == stamped
|
||||
assert sha.startswith(revision(artifact))
|
||||
|
||||
Reference in New Issue
Block a user