v0.261.0 — the controller no longer swaps itself out from under an app update (R-608, R-609)
gates / gates (push) Successful in 23s
gates / gates (push) Successful in 23s
The controller self-updates daily at 04:30 by default, and after any hub report once a floor sits above the box. That swap restarts the controller container. The window proposed for automatic app updates is 02:30-05:00. It contains 04:30. R-608 — a two-way lock, wired in main.go (stacks never imports selfupdate): - stacks.Manager.AnyUpdating() -> Updater.SetAppUpdatingCheck, consulted in the same three places as the existing backupRunning gate. - Updater.IsUpdateRunning -> Manager.SetSelfUpdatingCheck; UpdatePreflight refuses `self_updating`. - MEASURED: the gap was narrower than assumed. The update's `backing-up` phase already takes the backup single-flight, so that one phase was covered. The other six were not, and `starting`/`verifying` are where data may have moved. - The lock must NOT latch: a held app does not block the controller's own updates, including the release that might fix the hold. R-609 — the 409 carries `data.reason`, additively. transient (busy, updating, deploying, migrating, self_updating) vs terminal (held, downgrade). Found while writing the test: the router refuses a HELD app on its own line before the preflight, so `held` would have been the one reason missing. Five red-proofs, each seen to fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -1,3 +1,46 @@
|
||||
## v0.261.0 — the controller no longer swaps itself out from under an app update (2026-09-21, R-608/R-609)
|
||||
|
||||
**MinAgent: 0.131.0** (unchanged). Hungarian pages byte-identical; the two new sentences are BORN AS
|
||||
KEYS in both bundles and accounted for by the Go-parity gate.
|
||||
|
||||
**The collision was found by reading the clock, not by a failure.** The controller updates itself
|
||||
daily at `self_update.auto_update_time` — **04:30 by default** — and again after any hub report once
|
||||
a floor sits above the box, so at any hour. That swap restarts the controller container. The window
|
||||
`09` §3b Q1 proposes for automatic app updates is **02:30–05:00**. It contains 04:30.
|
||||
|
||||
- **`stacks.Manager.AnyUpdating()`** — is a guarded update in flight for ANY app.
|
||||
- **`Updater.SetAppUpdatingCheck`** — a sibling of `SetBackupRunningCheck`, deliberately the same
|
||||
shape, consulted in the SAME three places: the dry run, `TriggerUpdate`, and `maybeAutoUpdate`.
|
||||
One busy-gate pattern in that file, not two.
|
||||
- **`Manager.SetSelfUpdatingCheck`** — the reverse direction. `UpdatePreflight` now refuses with
|
||||
reason `self_updating`: „A vezérlő éppen frissül. Próbáld újra néhány perc múlva." Both halves are
|
||||
wired in `main.go`, the only place holding both objects; `stacks` never imports `selfupdate`.
|
||||
- **The gap was NARROWER than assumed, and measuring it is why the comment is right.** The update's
|
||||
`backing-up` phase takes the backup single-flight (`RunAppBackupNow` → `acquireRunning`), so
|
||||
`backupMgr.IsRunning()` ALREADY covered that one phase. It covered none of `checking`,
|
||||
`safety-dump`, `pinning`, `pulling`, `starting` or `verifying` — and the last two are exactly where
|
||||
the new version may already have touched the customer's data.
|
||||
- **The lock must NOT latch, and that is the test that matters.** `Stack.Updating` is cleared on done,
|
||||
failed AND held, so a held app does not block the controller's own updates — including the release
|
||||
that might fix whatever held it. A latching gate would be a worse failure than the one prevented,
|
||||
and a silent one.
|
||||
|
||||
**R-609 — the 409 carries a machine-readable `reason`, additively.** `UpdateRefusal.Reason` has
|
||||
existed since v0.237.0 and never left the process. The body now carries `data: {"reason": "..."}`
|
||||
beside the unchanged sentence, because the distinction is not decorative: `busy`, `updating`,
|
||||
`deploying`, `migrating`, `self_updating` are **transient**; `held` and `downgrade` are **terminal**
|
||||
until a person acts. A caller that cannot tell them apart either gives up on a passing backup window
|
||||
or presses a refused button for ever.
|
||||
|
||||
- **Found while writing the test, not by reading: the router refuses a HELD app on its own line,
|
||||
BEFORE `UpdatePreflight`** — so `held`, the reason that matters most, would have been the one
|
||||
missing. That line now carries it too.
|
||||
|
||||
**Five red-proofs, each SEEN to fail.** `AnyUpdating` always false → the in-flight test fails;
|
||||
`AnyUpdating` also true for a held app → the release-after-hold test fails; delete the preflight's
|
||||
`self_updating` block → the refusal test fails with `ref = <nil>`; delete `TriggerUpdate`'s app-update
|
||||
block → the swap proceeds over a live app update; drop `Data` from the refusal → `reason = ""`.
|
||||
|
||||
## v0.260.0 — a box AHEAD of the catalog reads „Naprakész", and the pin never moves backwards (2026-09-21, R-524)
|
||||
|
||||
**MinAgent: 0.131.0** (unchanged). Hungarian pages byte-identical; the two new sentences are BORN AS
|
||||
|
||||
Reference in New Issue
Block a user