v0.260.0 — a box ahead of the catalog reads „Naprakész", and the pin never moves backwards (R-524)
gates / gates (push) Successful in 24s
gates / gates (push) Successful in 24s
MEASURED 2026-09-15 (BIGNIGHT Phase 6): privatebin updated 2.0.5 -> 2.0.6, catalog reverted to 2.0.5, and the box read „Frissítés elérhető — ma" over an Update that would have moved the pin BACKWARDS onto a possibly-migrated datadir. - stacks.CatalogOrder: the comparison gains a fourth answer (Ahead) and moves out of web, so the badge and UpdatePreflight cannot drift apart. - The badge: ahead reads „Naprakész"/"Up to date", tag-ok, with a title saying why. - The refusal: UpdatePreflight returns `downgrade` (409), born as a bundle key; the API now renders update refusals through errText so it reaches English households. - Ahead is narrow: every differing service must be orderable AND newer, else Behind. - Ordering is util.Version.Compare behind a tag normaliser — no second comparator. - Three red-proofs, each seen to fail. R-589 was already fixed in v0.258.0; only its register row was stale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/system"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
|
||||
)
|
||||
|
||||
// ── The guarded update (update arc slice 4, controller v0.237.0) ─────────────────────────────────
|
||||
@@ -322,6 +323,24 @@ func (m *Manager) UpdatePreflight(name string) *UpdateRefusal {
|
||||
if m.IsMigrating() {
|
||||
return m.refuseUpdate(name, "migrating", MsgUpdateMigrating, "a data migration is running")
|
||||
}
|
||||
// R-524 — THE PIN NEVER MOVES BACKWARDS WITHOUT THE OPERATOR. MEASURED 2026-09-15 (BIGNIGHT
|
||||
// Phase 6): privatebin was updated 2.0.5 → 2.0.6, the catalog was reverted to 2.0.5, and the
|
||||
// „Frissítés" button behind the badge would have advanced the pin to the OLDER image — on a
|
||||
// datadir the newer version may already have migrated, with §4's ruling saying that cannot be
|
||||
// undone. A catalog revert is an operator act on our side; it must never become a data event on
|
||||
// the customer's side by itself.
|
||||
//
|
||||
// It refuses ONLY the provable case (stacks.CatalogOrder's Ahead arm: every differing service
|
||||
// orderable and newer). Anything unorderable, mixed or equal falls through to the behaviour that
|
||||
// shipped in v0.237.0 — this gate can block an update, so it errs towards letting one run.
|
||||
//
|
||||
// COMPANION RED-PROOF (REPORT.md): make CatalogOrder's Ahead arm return Behind.
|
||||
// TestR524_PreflightRefusesDowngrade then fails — the update is allowed to move the pin back.
|
||||
if CatalogOrder(*st) == UpdateOrderAhead {
|
||||
return m.refuseUpdateErr(name, "downgrade", util.MsgError("err.stacks.update_downgrade"),
|
||||
fmt.Sprintf("installed is provably NEWER than the catalog on every differing service (installed=%v catalog=%v)",
|
||||
st.AppConfig.InstalledImages, st.CatalogImages))
|
||||
}
|
||||
// R-475: any tier counts, and an app with no copy at all is backed up first by the job. So the only
|
||||
// refusal left here is Scenario L — no copy on any tier AND no way to make one now. (With a copy
|
||||
// but no way to back up, the job still applies the age rule and refuses then if the copy is stale.)
|
||||
|
||||
Reference in New Issue
Block a user