v0.297.0: burn-down round 2 — 24 small rows (R-591 R-568 R-567 R-363 R-547 R-10 R-552 R-251 R-104 R-619 R-362 R-675 R-256 R-257 R-240 R-365 R-425 R-565 R-564 R-603 R-454 R-208 R-457-swept) + the banner countdown and deepCopyStack twins; MinAgent 0.131.0
gates / gates (push) Failing after 50s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-05 20:12:56 +02:00
parent 114ff2761a
commit 1453cfc69b
68 changed files with 3953 additions and 116 deletions
+19 -8
View File
@@ -789,7 +789,7 @@ func main() {
}
if backupMgr != nil && cfg.Offsite.Enabled && cfg.Hub.URL != "" && cfg.Hub.APIKey != "" {
bridge := &offsiteapply.Bridge{
Cfg: cfg,
Cfg: cfg,
// Decision 69 (v0.289.0): the box sends its PUBLIC key to the hub's registrar, which pins it
// append-only; the box never receives the sub-account password.
Registrar: offsiteRegistrar,
@@ -1507,13 +1507,18 @@ func main() {
// every driveless app's recovery unit) were invisible, and it never reported a free-byte figure or
// named a drive.
//
// CADENCE: DAILY, at 03:30. A fill is a slow-moving quantity — the thing that fills a disk is a
// customer's photo library or a nightly backup, not a spike — so a shorter interval buys no
// earlier warning and only costs statfs calls. 03:30 is deliberately BEFORE the nightly app-data
// legs (db-dump / tier2 / offbox), so a customer who is about to lose a backup to lack of space
// hears about it while there is still a night's margin, rather than after the failure.
// The interval is NOT a cooldown: repeats are impossible because the check is edge-triggered per
// filesystem, and the hub owns cooldown regardless.
// CADENCE: every fillWatchInterval (10 min), PLUS the daily 03:30 run, PLUS once after startup.
// The original reasoning was "a fill is slow-moving, so daily is enough"; two measurements proved
// it wrong. R-363 (2026-08-21): a filesystem filled to 99% just after a daily run and the watcher
// said nothing while the backup reserve was already refusing apps. R-547 (2026-09-17): a root
// filesystem held at 96% for ten minutes raised no alarm of any kind. A daily sweep cannot carry
// the word BEFORE; an interval no longer than the shortest measured window can. The cost is a
// statfs per watched filesystem and one summary log line per run.
// The interval is NOT a cooldown and adds no repeat mails: the check is edge-triggered against
// PERSISTED bands with a hysteresis gap (fillwatch TestWarnsOnceThenIsSilent,
// TestThresholdsKeepTheirHysteresisGap), and the hub owns cooldown regardless. The 03:30 run is
// kept so the check still lands just before the nightly app-data legs. Pinned by
// TestFillWatchRunsOnAnInterval.
fillWatcher := fillwatch.New(
filepath.Join(cfg.Paths.DataDir, "fillwatch-state.json"), logger,
func() []fillwatch.Target { return fillTargets(cfg, sett) },
@@ -1544,6 +1549,7 @@ func main() {
// space the household can free on the kept-data list. Nothing is deleted by the box (D3).
fillWatcher.SetExtra(func(t fillwatch.Target) string { return keptSpaceSentence(stackMgr, t.Path) })
sched.Daily("fill-watch", "03:30", func(ctx context.Context) error { return fillWatcher.Check() })
sched.Every("fill-watch-interval", fillWatchInterval, func(ctx context.Context) error { return fillWatcher.Check() })
// AND ONCE SHORTLY AFTER STARTUP. A box that BOOTS with a filesystem already over the line must
// warn now, not up to 24 hours later — that is the R-100 shape, a real fault visible only after a
@@ -2437,6 +2443,11 @@ func (s gatedAppStopStarter) StartStack(name string) error {
// as unreadable (and be skipped, §8.4), never as a filesystem worth warning about.
const fillWatchStartupDelay = 90 * time.Second
// fillWatchInterval is how often the fill check runs between the daily sweeps (R-363, R-547). It must
// not exceed the shortest fill window that was measured going unannounced (ten minutes, R-547): any
// window at least this long then contains a check. Pinned by TestFillWatchRunsOnAnInterval.
const fillWatchInterval = 10 * time.Minute
// fillTargets is §8.1's watch list: the app-data volume, the system-data volume, and every
// registered drive. Resolved at CHECK time, not at startup, so a drive added or decommissioned
// between checks is picked up without a controller restart.