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.
167 lines
7.2 KiB
Python
167 lines
7.2 KiB
Python
"""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
|
|
to the derived revision means the bytes this push would produce are already
|
|
published, so the build is skipped.
|
|
|
|
That makes the revision load-bearing in a way a version string is not — it is
|
|
compared for equality against a value stamped into a real published artifact.
|
|
Both ways of getting it wrong are silent:
|
|
|
|
* **it does not identify the content** — a revision that moves when the source
|
|
did not (a HEAD-derived value, say) never matches, nothing is ever skipped,
|
|
and the mechanism quietly buys nothing while every lane stays green.
|
|
* **it identifies the wrong content** — a revision that holds still when the
|
|
source DID change matches a stale label, the build is skipped, and the
|
|
channel serves bytes that do not correspond to the commit. This is the
|
|
dangerous direction, and it is what `test_artifact_paths.py` guards from the
|
|
other side by pinning the path sets.
|
|
|
|
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
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
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
|
|
# exercise the contract instead of a Python re-implementation of it — and no
|
|
# other test module in this repo imports another, so a cross-test import would
|
|
# be a new convention introduced for no gain.
|
|
def artifacts(*args: str) -> str:
|
|
return subprocess.run(
|
|
["sh", str(ROOT / "scripts" / "artifacts.sh"), *args],
|
|
capture_output=True, text=True, check=True, cwd=ROOT,
|
|
).stdout
|
|
|
|
|
|
def revision(artifact: str) -> str:
|
|
return artifacts("revision", artifact).strip()
|
|
|
|
|
|
def newest_by_commit_time(artifact: str) -> str:
|
|
"""The full SHA of the newest commit touching this artifact's shipped set.
|
|
|
|
Ordered by committer TIME, matching what artifacts.sh means. Deliberately
|
|
not `git log -1`: git's default order is reverse-chronological only within
|
|
topological constraints, so on a merged history it can name a different
|
|
commit than the newest timestamp does. They agree on this repo today, and
|
|
a test that silently depends on them continuing to agree would be a flake
|
|
waiting for the branch shape that separates them.
|
|
"""
|
|
paths = artifacts("paths", artifact).split()
|
|
log = subprocess.run(
|
|
["git", "log", "--format=%ct %H", "HEAD", "--", *paths],
|
|
capture_output=True, text=True, check=True, cwd=ROOT,
|
|
).stdout.split("\n")
|
|
commits = [line.split(" ", 1) for line in log if line.strip()]
|
|
assert commits, (
|
|
f"no commit in this history touches the {artifact} path set — the "
|
|
f"derivation has nothing to stand on"
|
|
)
|
|
return max(commits, key=lambda c: int(c[0]))[1]
|
|
|
|
|
|
@pytest.mark.parametrize("artifact", ARTIFACTS)
|
|
def test_revision_is_the_commit_its_own_shipped_files_last_changed_in(artifact):
|
|
"""The claim the whole skip decision rests on.
|
|
|
|
Computed from git rather than asked of the script, so it fails if the
|
|
derivation ever stops meaning what it says — switching to HEAD, to a build
|
|
clock, or to a path set it did not actually use. Each of those still
|
|
produces a plausible 12-hex value, which is why this is worth asserting
|
|
rather than eyeballing.
|
|
"""
|
|
expected = newest_by_commit_time(artifact)
|
|
got = revision(artifact)
|
|
assert expected.startswith(got), (
|
|
f"{artifact} derives {got!r}, but the newest commit touching its "
|
|
f"shipped files is {expected[:12]!r}. The label stamped into the image "
|
|
f"would not identify its own content."
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("artifact", ARTIFACTS)
|
|
def test_revision_is_a_legal_label_value_and_is_stable(artifact):
|
|
"""It is stamped as a docker label and compared for string equality, so a
|
|
stray newline or a varying value breaks the comparison rather than the
|
|
build — the mechanism would simply stop hitting, silently."""
|
|
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))
|