R-518: a press with the local storage absent backs up nothing (never the off-site tier as a stand-in); an agent that flags no primary keeps the first tier
09 §3 decision 156 says the button makes the local copy only. Test: TestManualRunTiers_NoPrimaryAvailable_BacksUpNothing (red-proved on the previous version). 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:
@@ -453,24 +453,50 @@ func (l *Loop) quiesceAndPoll(ctx context.Context) error {
|
|||||||
// `09` §3 decision 156 (R-518 option A): the press makes the local copy with one short stop; the
|
// `09` §3 decision 156 (R-518 option A): the press makes the local copy with one short stop; the
|
||||||
// off-site tier follows at the next scheduled night run. Pinned by TestManualPress_RunsOnlyLocalTier.
|
// off-site tier follows at the next scheduled night run. Pinned by TestManualPress_RunsOnlyLocalTier.
|
||||||
//
|
//
|
||||||
// The primary is the tier the agent FLAGS Primary — not the first in the list. If no kept tier carries
|
// The primary is the tier the agent FLAGS Primary — not the first in the list. If the agent flags a primary
|
||||||
// the flag (the primary's storage reported absent, or an agent that flags none), the first kept tier is
|
// but its storage is reported absent, the press backs up NOTHING and says so at ERROR: the ruling is „the
|
||||||
// backed up rather than nothing: a press that silently backs up nothing would be the applied-and-empty
|
// local copy only", so the off-site tier is not a stand-in (it still runs at night), and no app is stopped
|
||||||
// fault in a different costume.
|
// (an empty window returns before the stop). Pinned by TestManualRunTiers_NoPrimaryAvailable_BacksUpNothing.
|
||||||
|
// An agent that flags NO tier primary at all (none advertised it) keeps the old order: the first tier.
|
||||||
func (l *Loop) manualRunTiers(ctx context.Context) []dueTier {
|
func (l *Loop) manualRunTiers(ctx context.Context) []dueTier {
|
||||||
all := l.allTiersForManualRun(ctx)
|
all := l.allTiersForManualRun(ctx)
|
||||||
if len(all) <= 1 {
|
if len(all) == 0 {
|
||||||
return all
|
return all
|
||||||
}
|
}
|
||||||
|
if len(all) == 1 && all[0].target == "" {
|
||||||
|
return all // a pre-R-82 agent: the single untargeted tier
|
||||||
|
}
|
||||||
for _, t := range all {
|
for _, t := range all {
|
||||||
if t.primary {
|
if t.primary {
|
||||||
return []dueTier{t}
|
return []dueTier{t}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
l.logger.Printf("[WARN] [quiesce] manual run: no available tier is flagged primary — backing up tier %s (the first available)", tierLabel(all[0].target))
|
if l.primaryAdvertised(ctx) {
|
||||||
|
l.logger.Printf("[ERROR] [quiesce] manual run: the local copy's storage is not available — nothing backed up now; the off-site copy follows at the night run (`09` §3 decision 156)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
l.logger.Printf("[WARN] [quiesce] manual run: the agent flags no tier as the local one — backing up tier %s (the first available)", tierLabel(all[0].target))
|
||||||
return all[:1]
|
return all[:1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// primaryAdvertised reports whether the agent flags ANY tier primary, its storage absent or not.
|
||||||
|
func (l *Loop) primaryAdvertised(ctx context.Context) bool {
|
||||||
|
tb, ok := l.backend.(TieredBackend)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tiers, err := tb.Tiers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, t := range tiers {
|
||||||
|
if t.Primary {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// allTiersForManualRun lists every tier the agent can back up now: all advertised tiers whose storage
|
// allTiersForManualRun lists every tier the agent can back up now: all advertised tiers whose storage
|
||||||
// is not absent on an R-82 agent, or the single untargeted tier otherwise. manualRunTiers picks the
|
// is not absent on an R-82 agent, or the single untargeted tier otherwise. manualRunTiers picks the
|
||||||
// primary from it.
|
// primary from it.
|
||||||
|
|||||||
@@ -82,12 +82,24 @@ func TestManualRun_UnknownStorageNotSkipped(t *testing.T) {
|
|||||||
|
|
||||||
// `09` §3 decision 156: a press backs up ONE tier. When no available tier is flagged primary (the
|
// `09` §3 decision 156: a press backs up ONE tier. When no available tier is flagged primary (the
|
||||||
// primary's storage is absent), the first available tier is backed up — never nothing.
|
// primary's storage is absent), the first available tier is backed up — never nothing.
|
||||||
func TestManualRunTiers_NoPrimaryAvailable_BacksUpFirstAvailable(t *testing.T) {
|
// `09` §3 decision 156: the press makes the LOCAL copy only. With the local storage absent it makes no copy at
|
||||||
|
// all — the off-site tier is never its stand-in (it runs at night) — and stops no app.
|
||||||
|
func TestManualRunTiers_NoPrimaryAvailable_BacksUpNothing(t *testing.T) {
|
||||||
be := newTierBackend()
|
be := newTierBackend()
|
||||||
be.tiers = []BackupTier{{Target: "local", Primary: true, StorageAbsent: true}, {Target: "felhom-pbs"}, {Target: "other"}}
|
be.tiers = []BackupTier{{Target: "local", Primary: true, StorageAbsent: true}, {Target: "felhom-pbs"}, {Target: "other"}}
|
||||||
l := newTierLoop(t, be, &fakeStacks{}, nil)
|
st := &fakeStacks{}
|
||||||
|
l := newTierLoop(t, be, st, nil)
|
||||||
got := l.manualRunTiers(context.Background())
|
got := l.manualRunTiers(context.Background())
|
||||||
if len(got) != 1 || got[0].target != "felhom-pbs" {
|
if len(got) != 0 {
|
||||||
t.Fatalf("want exactly the first available tier, got %+v", got)
|
t.Fatalf("want no tier (the local copy's storage is absent), got %+v", got)
|
||||||
|
}
|
||||||
|
if err := l.quiesceAndPollTiers(context.Background(), got); err != nil {
|
||||||
|
t.Fatalf("empty window: %v", err)
|
||||||
|
}
|
||||||
|
if len(be.started) != 0 {
|
||||||
|
t.Fatalf("an off-site backup was started for a manual press: %v", be.started)
|
||||||
|
}
|
||||||
|
if n := len(st.stoppedNames()); n != 0 {
|
||||||
|
t.Fatalf("%d app(s) were stopped for a press that backs up nothing", n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user