dev→main: milestone 318 steps 5–7 and 9, plus #3202 #240
+24
-16
@@ -100,13 +100,6 @@ fmt() {
|
||||
(cd "$ROOT" && TZ=UTC git show -s --format=%cd --date="format-local:$2" "$1")
|
||||
}
|
||||
|
||||
# Leading zeros stripped so every segment is a plain integer — some version
|
||||
# validators reject `08`, and a leading zero buys nothing. `0000` (midnight)
|
||||
# must survive as `0`, not as the empty string.
|
||||
strip0() {
|
||||
printf '%s' "$1" | sed -e 's/^0*//' -e 's/^$/0/'
|
||||
}
|
||||
|
||||
# The IDENTITY of an artifact's content: the commit its shipped files last
|
||||
# changed in. This is what decides whether a build can be skipped.
|
||||
#
|
||||
@@ -124,17 +117,32 @@ cmd_revision() {
|
||||
echo "$(newest "$1")" | cut -d' ' -f2 | cut -c1-12
|
||||
}
|
||||
|
||||
# The ORDERING KEY: full precision, YYYY.M.D.HHMM. Used by the extension,
|
||||
# where the value is what Firefox compares to decide whether an update exists
|
||||
# — two same-day builds MUST be distinguishable or the second never reaches
|
||||
# anyone.
|
||||
# The VERSION: `YYYY.MM.DD.HHMM`, zero-padded, UTC. One shape across the whole
|
||||
# family (note #3127 §1, rule 148) — the number an instance reports about
|
||||
# itself, and, with a `v` in front, the release tag naming the same build.
|
||||
#
|
||||
# Zero-padded since 2026-08-28. This stripped leading zeros until then, on the
|
||||
# reasoning that every segment should read as a plain integer — which never
|
||||
# held, since comparison strips them on parse anyway. Padding costs nothing,
|
||||
# sorts lexically as well as numerically, and keeps this project emitting the
|
||||
# same string as its siblings: unpadded, a `2026.8.28.1432` here sits beside a
|
||||
# `2026.08.28.1432` there, two shapes one character apart. Two obviously
|
||||
# different formats are safer than two nearly identical ones.
|
||||
#
|
||||
# Comparison is numeric per dot-segment, so `08` and `8` are equal and nothing
|
||||
# already published is reordered by the change.
|
||||
#
|
||||
# HHMM is not decoration: it is what makes the value unique per build with no
|
||||
# lookup. A date alone collides on the second build of a day, and resolving
|
||||
# that needs a `.N` suffix, which needs asking the registry what already
|
||||
# exists — at which point two lanes derive different answers for one source
|
||||
# and the shared-signature property is lost.
|
||||
cmd_version() {
|
||||
sha=$(echo "$(newest "$1")" | cut -d' ' -f2)
|
||||
printf '%s.%s.%s.%s\n' \
|
||||
"$(fmt "$sha" %Y)" \
|
||||
"$(strip0 "$(fmt "$sha" %m)")" \
|
||||
"$(strip0 "$(fmt "$sha" %d)")" \
|
||||
"$(strip0 "$(fmt "$sha" %H%M)")"
|
||||
# One git call for the whole string rather than four and a sed. git's
|
||||
# format-local takes the complete format, and doing it in pieces was only
|
||||
# ever there to strip the padding between them.
|
||||
fmt "$sha" '%Y.%m.%d.%H%M'
|
||||
}
|
||||
|
||||
[ $# -ge 2 ] || usage
|
||||
|
||||
@@ -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