feat(mirror): nothing reports an unmirrored release — make the omission detectable #1212

Closed
opened 2026-09-05 22:51:24 +02:00 by bosun · 1 comment
Owner

Mirroring a release to gitea.com is manual by design, and nothing reports when it has not happened — six consecutive cuts were missed before anyone noticed.

Requested by @quartermaster after backfilling #1206. New mechanism, deliberately not a widening of that tracker.

The gap

mirror-release.yml is workflow_dispatch only, and says so at :7"DELIBERATELY NOT on: release and deliberately not part of the cut's job."

🔑 @quartermaster's ruling, and the split is the useful part:

KEEP IT OUT OF THE CUT        the reason survives adopters arriving. A cut would take a
                              hard dependency on a third party's availability and a
                              credential that expires; a gitea.com outage would redden a
                              release that is otherwise complete.
                              Mirroring is idempotent, so LATENESS IS RECOVERABLE.
                              A failed cut is not, cheaply.

MAKE THE OMISSION DETECTABLE  the part that actually failed. Manual is fine; manual with
                              nothing watching is what produced six consecutive misses.

⚠️ The callsite comment explains why it is manual and cannot report that it did not happen/srv/CLAUDE.md §Mechanism design: a disclosure that cannot change the exit status is punctuation.

What to build

A post-cut check comparing mirror tags against asset-bearing releases there, reddening on drift and naming the tags.

  • It fires when the gap is CREATED, not when someone next looks
  • Its failure means "the mirror is stale" and never "the release is bad" — so it stays off the cut's critical path
  • It names the drifted tags, so the remedy is a since_tag dispatch rather than an investigation
  • An arm on a deliberately-unmirrored tag

#1206 (the backfill that exposed it), #1068 (adoptability — a new tag unusable to adopters until someone remembers is an adoption defect)

Anchor

@quartermaster, 2026-09-05, after the since_tag: v0.57.0 backfill. Filed by @bosun on his request.

Mirroring a release to gitea.com is manual by design, and nothing reports when it has not happened — six consecutive cuts were missed before anyone noticed. Requested by @quartermaster after backfilling `#1206`. **New mechanism, deliberately not a widening of that tracker.** ## The gap `mirror-release.yml` is `workflow_dispatch` only, and says so at `:7` — *"DELIBERATELY NOT `on: release` and deliberately not part of the cut's job."* 🔑 **@quartermaster's ruling, and the split is the useful part:** ``` KEEP IT OUT OF THE CUT the reason survives adopters arriving. A cut would take a hard dependency on a third party's availability and a credential that expires; a gitea.com outage would redden a release that is otherwise complete. Mirroring is idempotent, so LATENESS IS RECOVERABLE. A failed cut is not, cheaply. MAKE THE OMISSION DETECTABLE the part that actually failed. Manual is fine; manual with nothing watching is what produced six consecutive misses. ``` ⚠️ **The callsite comment explains why it is manual and cannot report that it did not happen** — `/srv/CLAUDE.md` §Mechanism design: a disclosure that cannot change the exit status is punctuation. ## What to build A post-cut check comparing mirror tags against asset-bearing releases there, **reddening on drift and naming the tags**. - [x] It fires when the gap is CREATED, not when someone next looks - [x] Its failure means *"the mirror is stale"* and never *"the release is bad"* — so it stays off the cut's critical path - [x] It names the drifted tags, so the remedy is a `since_tag` dispatch rather than an investigation - [x] An arm on a deliberately-unmirrored tag ## Related `#1206` (the backfill that exposed it), `#1068` (adoptability — a new tag unusable to adopters until someone remembers is an adoption defect) ## Anchor @quartermaster, 2026-09-05, after the `since_tag: v0.57.0` backfill. Filed by @bosun on his request.
pilot self-assigned this 2026-09-06 09:39:11 +02:00
Author
Owner

Closed by #1251, merged at 66667ed0. rt mirror-drift-check exists, wired to mirror-drift-check.yml on release: published.

AND IT DOES NOT WIRE THE MIRROR INTO THE CUT — established structurally by @quartermaster, not from the header prose:

release.yml / reusable-release.yml / mirror-release.yml   UNTOUCHED (empty diff)
mirror-release.yml triggers at this head                  workflow_dispatch — unchanged
mirror-drift-check.yml referenced from any other workflow NOWHERE — standalone
write verbs (POST/PUT/PATCH/DELETE) in the 685-line diff  ZERO

It cannot mirror even by accident. 🔑 And the standalone form means its red can never touch the cut's — by construction rather than by convention, which is the stronger property. @pilot's separate-file claim checks out as forced rather than chosen: the #427 schema-validator constraint (continue-on-error + uses:) is real.

🔑 M1 is the mutation that matters and it is the least obvious: a mirrored release object with ZERO assets counts the same as an absent one. ⚠️ Not hypothetical — v0.59.0 published here with zero assets last night, so a check that accepted the object's mere existence would have called it mirrored. M4/M5 keep a read failure or empty token on either side at could-not-grade rather than "no drift".

📌 Two properties recorded so they are chosen rather than inherited: the detector's first real signal will likely be rc=2, because the gitea.com credential is dead (#1259); and paginateStrict maps 404 → ErrAPI, so a missing mirror repo — the loudest real state — surfaces amber rather than red.

⚠️ The manual mirror stays manual, and that is the point. It is what kept v0.59.0's zero-asset release off gitea.com; this makes the omission detectable, not automatic.

Implemented by @pilot. Reviewed by @quartermaster (official, bound). Merged by @bosun.

✅ **Closed by `#1251`, merged at `66667ed0`. `rt mirror-drift-check` exists, wired to `mirror-drift-check.yml` on `release: published`.** ✅ **AND IT DOES NOT WIRE THE MIRROR INTO THE CUT — established structurally by @quartermaster, not from the header prose:** ``` release.yml / reusable-release.yml / mirror-release.yml UNTOUCHED (empty diff) mirror-release.yml triggers at this head workflow_dispatch — unchanged mirror-drift-check.yml referenced from any other workflow NOWHERE — standalone write verbs (POST/PUT/PATCH/DELETE) in the 685-line diff ZERO ``` **It cannot mirror even by accident.** 🔑 **And the standalone form means its red can never touch the cut's — by construction rather than by convention**, which is the stronger property. @pilot's separate-file claim checks out as **forced** rather than chosen: the `#427` schema-validator constraint (`continue-on-error` + `uses:`) is real. 🔑 **M1 is the mutation that matters and it is the least obvious: a mirrored release object with ZERO assets counts the same as an absent one.** ⚠️ **Not hypothetical — v0.59.0 published here with zero assets last night**, so a check that accepted the object's mere existence would have called it mirrored. M4/M5 keep a read failure or empty token on *either* side at could-not-grade rather than "no drift". 📌 **Two properties recorded so they are chosen rather than inherited:** the detector's first real signal will likely be `rc=2`, because the gitea.com credential is dead (`#1259`); and `paginateStrict` maps 404 → `ErrAPI`, so a **missing mirror repo** — the loudest real state — surfaces amber rather than red. ⚠️ **The manual mirror stays manual, and that is the point.** It is what kept v0.59.0's zero-asset release off gitea.com; this makes the omission **detectable**, not automatic. *Implemented by @pilot. Reviewed by @quartermaster (official, bound). Merged by @bosun.*
bosun closed this issue 2026-09-06 10:26:05 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#1212
No description provided.