feat(release): guard the catalog against publishing untrusted registry hosts

Encodes the sequencing rule that nearly shipped a fleet-wide outage today.

The signed catalog is authoritative on every node — catalog_image_override
makes its image refs beat the on-disk manifest. TRUSTED_REGISTRIES in the
working tree describes a binary being built now; nodes run whatever was last
shipped to them. Those two diverge for exactly as long as an OTA takes to
reach the fleet, and that window is when regenerating the catalog silently
breaks every install with "not from a trusted registry".

Regenerating today would have done precisely that: the generator embeds each
app's manifest, and those now name the new registry domain, which no deployed
binary trusts.

- releases/registry-trust-floor.json records the hosts DEPLOYED binaries
  trust, separately from what the source tree accepts, with the new domain
  parked under `pending` until an OTA carries it. The migration order is
  written down there rather than living in someone's memory.
- scripts/check-catalog-registry-trust.py compares the catalog's hosts against
  that floor and explains the ordering fix when they diverge.
- sign-catalog.sh runs it as a preflight BEFORE prompting for the mnemonic, so
  a bad catalog is refused at the last reversible moment.
- CI runs it blocking, plus the drift report advisory (drift between a manifest
  landing and the next signed release is expected, since only the ceremony can
  close it).

Also installs PyYAML in the manifests job. That job passed only because GitHub
runners happen to ship ruby, which the validator used to require; it now needs
python3+PyYAML.

Verified: passes on the published catalog (2 hosts, both trusted); refuses a
simulated full regenerate (79 refs on the untrusted domain) and blocks the
ceremony without requesting the mnemonic.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-07 12:24:34 -04:00
co-authored by Claude Opus 5
parent 7cf6980894
commit d0af38e825
3 changed files with 192 additions and 0 deletions
+12
View File
@@ -23,6 +23,18 @@ if [[ ! -x "$BIN" ]]; then
fi
SIGN=("$BIN" ceremony sign "$CATALOG")
# Preflight BEFORE asking for the mnemonic. Signing is the point of no return:
# a signed catalog is authoritative for every node, and its image refs override
# the on-disk manifests. If it names a registry host the deployed fleet does not
# trust, every install fails "not from a trusted registry" — so catch that here
# rather than after publication.
if ! python3 "$REPO/scripts/check-catalog-registry-trust.py" --repo "$REPO"; then
echo
echo "✋ Refusing to sign. Nothing was changed and your mnemonic was not requested."
exit 1
fi
echo
echo "════════════════════════════════════════════════════════════════"
echo " Paste your 24-word release master mnemonic below, press Enter,"
echo " then press Ctrl-D on a new line."