Milestone 318 step 8 — extension CalVer, and the #3190 mitigation's first main build #241

Merged
bvandeusen merged 3 commits from dev into main 2026-08-29 13:53:59 -04:00
Owner

Three commits, and the merge itself is the test for two of them.

41f2bec#3190 mitigation, unexercised until now

The build pushes exactly one tag (the channel's) and the repoint step writes every other tag registry-side. This only operates on main, so it has never actually run: on dev there is one tag and nothing to repoint. This merge is its first real exercise. build-web will rebuild (its revision moved), push :latest, then read it back and write :c-<sha> from it.

The accepted cost, restated so it is not rediscovered: :c-<sha> becomes a manifest index, so fc.revision does not resolve through it. Harmless — only the channel tag is ever inspected, and the reuse path already produced :c-<sha> that way.

2e01242 — milestone 318 step 8 (Scribe #3138)

The hold was an unanswered question about AMO, not a decision. Mozilla publishes the answer:

^(0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}$
  • Four all-numeric segments — accepted.
  • Leading zeros — rejected. A segment is the single digit 0 or starts 1-9.

So the fallback the task pre-specified applies, extension only: the same numbers unpadded. 2026.8.29.201 where the family says 2026.08.29.0201. Rule 148 defines comparison as numeric per dot-segment, under which the two are equal — nothing published is reordered, and left-padding each segment recovers the family string exactly.

MAJOR.MINOR is gone with it: cmd_major_minor, cmd_patch and VERSION_EPOCH deleted, the committed version wholly inert, and ci.yml's agreement check retired rather than left checking a fact that stopped existing.

Already verified on dev:

AMO signed the derived value ext-2026.8.29.1743, XPI cached (31,364 bytes)
a later push re-signs? no — Sign via AMO skipped, cache hit
#3156 moves web with the extension both → 2e0124238156
ml / agent untouched, both skipped

What this merge still has to show is the other half of milestone 271's prize: main derives the same number and reuses dev's signature with no second AMO call.

1a941e9 — the assertions step 8 had to move

tests/test_artifact_identity.py pinned zero-padded CalVer for all four artifacts and failed exactly as written. The exception is pinned to the constraint that justifies it — the unpadded artifact is asserted against AMO's own grammar, so a regression to padding fires immediately rather than waiting for the first single-digit month and surfacing as a burned AMO version.

Expected on merge

  • build-web rebuilds — revision moved to 2e0124238156.
  • build-ml, build-agent skip — bce894ba2499 / 57e52433d03a unchanged.
  • sign-extension hits the ext-2026.8.29.1743 cache; no AMO call.
  • Plain merge commit, per rule 153, so the pathspec prunes it and no derived version moves.

Scribe #3138, #3190, #3156.

Three commits, and the merge itself is the test for two of them. ## `41f2bec` — #3190 mitigation, unexercised until now The build pushes exactly one tag (the channel's) and the repoint step writes every other tag registry-side. This only operates on `main`, so it has never actually run: on `dev` there is one tag and nothing to repoint. **This merge is its first real exercise.** `build-web` will rebuild (its revision moved), push `:latest`, then read it back and write `:c-<sha>` from it. The accepted cost, restated so it is not rediscovered: `:c-<sha>` becomes a manifest index, so `fc.revision` does not resolve through it. Harmless — only the channel tag is ever inspected, and the reuse path already produced `:c-<sha>` that way. ## `2e01242` — milestone 318 step 8 (Scribe #3138) The hold was an unanswered question about AMO, not a decision. Mozilla publishes the answer: ``` ^(0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}$ ``` * Four all-numeric segments — **accepted**. * Leading zeros — **rejected**. A segment is the single digit `0` or starts 1-9. So the fallback the task pre-specified applies, extension only: the same numbers unpadded. `2026.8.29.201` where the family says `2026.08.29.0201`. Rule 148 defines comparison as numeric per dot-segment, under which the two are equal — nothing published is reordered, and left-padding each segment recovers the family string exactly. `MAJOR.MINOR` is gone with it: `cmd_major_minor`, `cmd_patch` and `VERSION_EPOCH` deleted, the committed version wholly inert, and ci.yml's agreement check retired rather than left checking a fact that stopped existing. **Already verified on `dev`:** | | | |---|---| | AMO signed the derived value | `ext-2026.8.29.1743`, XPI cached (31,364 bytes) | | a later push re-signs? | no — `Sign via AMO` **skipped**, cache hit | | #3156 moves web with the extension | both → `2e0124238156` | | ml / agent | untouched, both skipped | What this merge still has to show is the other half of milestone 271's prize: **`main` derives the same number and reuses `dev`'s signature with no second AMO call.** ## `1a941e9` — the assertions step 8 had to move `tests/test_artifact_identity.py` pinned zero-padded CalVer for all four artifacts and failed exactly as written. The exception is pinned to the constraint that justifies it — the unpadded artifact is asserted against **AMO's own grammar**, so a regression to padding fires immediately rather than waiting for the first single-digit month and surfacing as a burned AMO version. ## Expected on merge * `build-web` rebuilds — revision moved to `2e0124238156`. * `build-ml`, `build-agent` skip — `bce894ba2499` / `57e52433d03a` unchanged. * `sign-extension` hits the `ext-2026.8.29.1743` cache; **no AMO call**. * Plain merge commit, per rule 153, so the pathspec prunes it and no derived version moves. Scribe #3138, #3190, #3156.
bvandeusen added 3 commits 2026-08-29 13:53:54 -04:00
fix(ci): the build pushes one tag; the rest are written registry-side (#3190)
CI / lint (push) Successful in 3s
Build images / sign-extension (push) Successful in 4s
Build images / build-ml (push) Successful in 4s
CI / extension-version (push) Successful in 3s
Build images / build-web (push) Successful in 6s
Build images / build-agent (push) Successful in 5s
CI / frontend-build (push) Successful in 23s
extension / lint (push) Successful in 28s
CI / backend-lint-and-test (push) Successful in 46s
CI / integration (push) Successful in 3m56s
41f2bec3af
buildx on this runner pushes the first tag to the registry and then
re-pushes the remaining ones through the DOCKER driver, reading them out
of a local image store that a registry-direct build never populated:

  #27 pushing …/fabledcurator:latest           DONE 15.8s
  #28 pushing …/fabledcurator:c-0e15c44 with docker
  #28 ERROR: tag does not exist: …:c-0e15c44

It is intermittent — build-ml made the identical two-tag push seconds
later in the same run and succeeded — and the consequence is worse than
the red job suggests. `:latest` had already published, so production was
correct while the immutable rollback tag rule 145 requires of every main
push simply did not exist. Nothing else would ever have noticed: a
missing :c-<sha> has no consumer that fails, so it surfaces at the moment
somebody needs to roll back, which is the worst time to learn a rollback
target was never written.

So the build now pushes exactly one ref — the channel's — and the
existing repoint step, which already excluded the source tag and already
ran on every reuse, now runs on the build path too and owns every other
tag. `imagetools create` is a registry-side manifest copy: no local
daemon, nothing that can be absent. This adds no new code path; it puts
the build case onto the one that was already proven.

Chosen over the alternative of asserting each tag resolves after the
build, which would have made the failure loud without making it rarer.

The cost, accepted: `imagetools create` wraps its source in an index, so
:c-<sha> is an index rather than a plain image and fc.revision does not
resolve through it. Nothing reads that label off :c-<sha> — the reuse
check only ever inspects the CHANNEL tag — and the index names the same
manifest, so a pull is byte-identical. The reuse path already produced
:c-<sha> this way; this only makes it uniform.

`build_tags` goes with it — the tag list now has exactly one consumer.
feat(extension): derive the version as unpadded CalVer (milestone 318 step 8)
Build images / build-ml (push) Successful in 4s
CI / lint (push) Successful in 4s
Build images / build-agent (push) Successful in 5s
CI / extension-version (push) Successful in 4s
CI / frontend-build (push) Successful in 22s
extension / lint (push) Successful in 22s
CI / backend-lint-and-test (push) Failing after 33s
Build images / sign-extension (push) Successful in 2m24s
Build images / build-web (push) Successful in 2m38s
CI / integration (push) Successful in 5m15s
2e01242381
`1.0.<minutes since 2020>` -> `YYYY.M.D.HHMM` UTC, from the commit time of
the newest change to a packaged extension file. Same clock and same commit as
before; readable instead of opaque, and the same value the rest of the family
derives.

The hold on this step was two questions about AMO, and Mozilla's own docs
answer both:

    ^(0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}$

  1. four all-numeric segments -> ACCEPTED ({0,3} more after the first).
  2. leading zeros              -> REJECTED. A segment is the single digit
     `0` or starts 1-9, so `08` and `0201` are refused. MDN says it in prose
     too: "Non-zero numbers must not include a leading zero."

So the documented fallback applies, extension only: the same numbers rendered
without the family's zero-padding. `2026.08.29.0201` and `2026.8.29.201` are
one value in two renderings — rule 148 defines comparison as numeric per
segment, under which they are equal — so nothing already published is
reordered, and left-padding each segment recovers the family string exactly.
HHMM stays one segment because AMO allows at most four.

The transition is safe in the other direction too: 2026 > 1, so every CalVer
outranks every published 1.0.x. build.yml's downgrade guard confirms it.

Also in scope:

* MAJOR.MINOR is gone. `cmd_major_minor`, `cmd_patch` and VERSION_EPOCH go
  with it, the committed version in manifest.json / package.json is now
  wholly inert, and ci.yml's MAJOR.MINOR-agreement check is retired rather
  than left running beside a fact that stopped existing (rule 22).
* ci.yml's `extension-version` lane now asserts Mozilla's regex verbatim
  instead of a loose `^[0-9]+(\.[0-9]+)*$` — which would have passed the
  padded shape. It also asserts YYYY.M.D.HHMM, because AMO would accept a
  regression to `1.0.<minutes>` while that orders below everything signed
  since. Checking here is the point: AMO 409s on re-signing, so a version it
  rejects is burned and cannot be reused.
* `artifacts.sh version extension` delegates to packaging.sh, so the two
  cannot answer differently. The direction matches the existing one —
  artifacts.sh already asks packaging.sh for the extension's path set.

#3156 is what makes this commit safe to make: packaging.sh is in web's path
set, so the web revision moves with the extension version and build-web
rebuilds instead of republishing an image bundling the previous XPI.

Scribe #3138.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
test: encode the extension's AMO rendering exception (milestone 318 step 8)
Build images / sign-extension (push) Successful in 4s
Build images / build-agent (push) Successful in 4s
Build images / build-ml (push) Successful in 5s
CI / lint (push) Successful in 2s
Build images / build-web (push) Successful in 3s
CI / extension-version (push) Successful in 3s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 31s
CI / integration (push) Successful in 3m40s
extension / lint (pull_request) Successful in 21s
1a941e900b
Missed in 2e01242. This module pinned zero-padded `YYYY.MM.DD.HHMM` for all
four artifacts, which is the family shape and was right until the extension
acquired a documented reason not to use it. Both assertions failed exactly as
written, on the value they were written to catch.

Rather than exempt the extension, the exception is pinned to the constraint
that justifies it:

* `test_version_is_zero_padded_calver` now covers the three padded artifacts.
* A new sibling covers the unpadded one against **AMO's own grammar** —
  `2026.08.29.0201` fails it, so a regression to padding fires immediately.
  Matching only `YYYY.M.D.HHMM` would not: on a date with no leading zeros the
  two renderings are the same string, so a padding regression would sit unseen
  until the first single-digit month, and surface as a burned AMO version
  rather than a red lane.
* `test_version_and_revision_describe_the_same_commit` compares NUMBERS, per
  rule 148's own definition of comparison — so one assertion covers both
  renderings and says the real thing: whatever the padding, the extension must
  denote exactly the value its own commit stamps. Exact-string equality is
  still asserted for everything not in AMO_UNPADDED, so the exception cannot
  quietly spread.

Scribe #3138.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bvandeusen merged commit bd24f4e876 into main 2026-08-29 13:53:59 -04:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/FabledCurator#241