v0.201.0 — a correct recovery code is never called wrong again (CAMPAIGN-11) — MinAgent 0.125.0
gates / gates (push) Successful in 9s

R-216: the offsite key recovery is a coupled feature and now says so. featureProbes +
featureMinAgent 0.125.0 + a Supports gate at the unlock entry point, FAILING CLOSED — an
agent that cannot answer is named as such instead of the customer's code being blamed.
Measured live: a 404 from agent 0.120.0 came back as "we did not accept your recovery
code, check that all ten words", in 0.134 s, against a perfect code.

R-218: delete the repo-password short-circuit in needsOffsiteCredential. The declaration
stops when the TIER WORKS, not when a key exists — installing a key is the recovery
screen's whole job, so succeeding at recovery was switching off the mechanism that would
have delivered the coordinates to use it.

R-219: the unlock finishes the job — place the key, bring the tier up, then list. Without
it the promised listing could never render on the shape the screen exists for.

R-217: an unreadable store no longer claims to have opened with unattributable content
(the OffsiteInventory{} zero value). Opened / empty / unreadable are three states.

R-222: a code that is right about a RETAINED earlier package is named, not blamed. States
what the hub knows and promises nothing — no read path exists.

R-215: GET /recovery is gated on the same predicate as the interception.

Five red-proofs, each demonstrated failing and restored.
This commit is contained in:
2026-08-05 17:48:08 +02:00
parent a315d623b8
commit a3499d1807
12 changed files with 778 additions and 20 deletions
@@ -88,23 +88,60 @@ func TestOffsiteDeclare_NeverHadOffsiteSaysNothing(t *testing.T) {
}
}
// The other half of the conjunction: a box that still holds its repository password is NOT stranded,
// even though the hub holds an escrow for it. That is simply a healthy box between configurations.
func TestOffsiteDeclare_BoxThatStillHoldsItsRepoPasswordDoesNotDeclare(t *testing.T) {
// SCENARIO D (R-218) — THE DECLARATION STOPS WHEN THE TIER WORKS, NOT WHEN A KEY EXISTS.
//
// ⚠ THIS TEST ASSERTED THE OPPOSITE until v0.201.0, and it was green the whole time. It required a
// box holding a repository password to stay SILENT — which reads as a sound freshness test and is the
// exact opposite on the one path that matters, because installing a repository password is the
// RECOVERY SCREEN'S WHOLE JOB. Measured live 2026-08-05 (CAMPAIGN-11 Phase 1): 32 seconds after the
// hub re-staged the credential, the customer's successful unlock switched off the mechanism that
// would have delivered the coordinates for the key they had just recovered. Deadlock, both halves.
//
// RED-PROOF: restore the `if _, ok := m.OffboxRepoPasswordHash(); ok { return false }` short-circuit
// in needsOffsiteCredential and this test FAILS — the box goes silent again with no target, which is
// the deadlock. Demonstrated failing before this test was kept.
func TestOffsiteDeclare_StillDeclaresAfterARecoveredKeyIsPlaced(t *testing.T) {
m, sett := bareManager(t)
if err := sett.SetHubEscrowIdentityPresent(true); err != nil {
t.Fatal(err)
}
// Place a repository password exactly where the manager looks for it.
// The post-unlock shape: the recovered repository password is on disk, and there is STILL no
// off-site target — so the box cannot use what it just recovered.
if err := os.MkdirAll(m.offboxDir(), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(m.offboxPwPath(), []byte("a-repository-password"), 0o600); err != nil {
if err := os.WriteFile(m.offboxPwPath(), []byte("a-recovered-repository-password"), 0o600); err != nil {
t.Fatal(err)
}
if st := m.OffboxReportStatus(); st != nil {
t.Fatalf("a box holding its repository password declared a need: %+v", st)
st := m.OffboxReportStatus()
if st == nil {
t.Fatal("R-218: the box went SILENT after recovering its key while still having no off-site target — the hub's staged credential is never collected and nothing ever asks again")
}
if st.State != OffsiteStateNeedsCredential {
t.Fatalf("declared state = %q, want %q", st.State, OffsiteStateNeedsCredential)
}
}
// SCENARIO E — and once the tier ACTUALLY WORKS the box goes quiet. This is the condition that
// replaces the deleted one, and the pair above/below is what makes the deletion safe.
func TestOffsiteDeclare_ConfiguredTierIsSilent(t *testing.T) {
m, sett := bareManager(t)
if err := sett.SetHubEscrowIdentityPresent(true); err != nil {
t.Fatal(err)
}
if err := sett.SetOffboxTarget(&settings.OffboxTarget{
Enabled: true, Host: "nas.local", Port: 22, User: "felhom", RepoPath: "/srv/repo",
Schedule: "daily", EscrowState: "escrowed",
}); err != nil {
t.Fatal(err)
}
st := m.OffboxReportStatus()
if st == nil {
t.Fatal("a configured tier must still report its ordinary off-site object")
}
if st.State == OffsiteStateNeedsCredential {
t.Fatal("a box whose tier is configured must not keep asking for a credential")
}
}