hub v0.41.0: OffsiteChecker (fill 90/95 + staleness >48h) + operator freeze lever (SLICE 4)

Sibling checker over the controller report's offsite object: quota-fill
warn/crit + the silently-stuck staleness detector (escrowed-only,
red-proofed; nil-safe on pre-v0.109 reports; same-second tie-guard).
SetOffsiteFrozen flips ONLY readonly on the exactly-1 labelled sub-account
(SSH preserved); Freeze/Unfreeze buttons — manual only, never automatic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-09 23:57:12 +02:00
parent cb26dc7e83
commit fad5573dd3
10 changed files with 543 additions and 0 deletions
+36
View File
@@ -211,6 +211,42 @@ func TestReissue_RefusesAmbiguousLookup(t *testing.T) {
}
}
// Scenario E (SLICE 4) — the freeze lever flips ONLY readonly on the exactly-1 labelled sub-account
// (SSH stays on — a freeze must not cut access, just writes); ambiguity refuses; unfreeze reverses.
func TestFreeze_SharedTogglesReadonlyOnly(t *testing.T) {
p, fake, _ := newTestProvisioner(t)
if _, err := p.ProvisionOffsite(context.Background(), "cust-fz", Input{Enabled: true, Type: "shared", QuotaGB: 10}); err != nil {
t.Fatal(err)
}
if err := p.SetOffsiteFrozen(context.Background(), "cust-fz", true); err != nil {
t.Fatalf("freeze: %v", err)
}
subs, _ := fake.ListSubaccounts(context.Background(), 611421, "felhom-customer=cust-fz")
if len(subs) != 1 || !subs[0].AccessSettings.Readonly {
t.Fatalf("freeze must set readonly on the labelled sub-account: %+v", subs)
}
if !subs[0].AccessSettings.SSHEnabled {
t.Fatal("freeze must NOT disable SSH — only readonly flips")
}
if err := p.SetOffsiteFrozen(context.Background(), "cust-fz", false); err != nil {
t.Fatalf("unfreeze: %v", err)
}
subs, _ = fake.ListSubaccounts(context.Background(), 611421, "felhom-customer=cust-fz")
if subs[0].AccessSettings.Readonly {
t.Fatal("unfreeze must clear readonly")
}
// nothing provisioned → refuse
if err := p.SetOffsiteFrozen(context.Background(), "cust-none", true); err == nil {
t.Fatal("freeze with no labelled sub-account must refuse")
}
// ambiguous → refuse
fake.Subaccounts[71] = hetznerapi.Subaccount{ID: 71, StorageBox: 611421, Labels: map[string]string{"felhom-customer": "cust-amb2"}}
fake.Subaccounts[72] = hetznerapi.Subaccount{ID: 72, StorageBox: 611421, Labels: map[string]string{"felhom-customer": "cust-amb2"}}
if err := p.SetOffsiteFrozen(context.Background(), "cust-amb2", true); err == nil || !strings.Contains(err.Error(), "exactly 1") {
t.Fatalf("ambiguous freeze must refuse, got %v", err)
}
}
// Scenario B — enable dedicated → box provisioned.
func TestProvision_Dedicated(t *testing.T) {
p, fake, st := newTestProvisioner(t)