diff --git a/internal/pbsdr/rearm_test.go b/internal/pbsdr/rearm_test.go index 57ad1fe..cadd2dd 100644 --- a/internal/pbsdr/rearm_test.go +++ b/internal/pbsdr/rearm_test.go @@ -173,3 +173,29 @@ func TestR39_AuthResultBeforeAnyDescriptorIsIgnored(t *testing.T) { t.Errorf("status invented from a probe with no descriptor: %+v", s) } } + +// Upgrading the AGENT alone must not re-apply anything. +// +// felhom-pve's live descriptor predates v0.68.0 and carries no `secret_generation` key. A 0.91.0 +// agent parses that into a zero field — and `omitempty` means the re-marshal omits it again, so the +// hash it computes equals the one the 0.90.0 agent stored in its marker. Without that property the +// STOP-1 binary swap would itself burn a one-time secret on every box in the fleet. +func TestR39_UpgradingTheAgentAloneDoesNotChangeTheHash(t *testing.T) { + // What a pre-v0.68.0 hub wrote (no secret_generation key at all). + legacy := []byte(`{"enabled":true,"storage_id":"felhom-pbs","pbs_tunnel_ip":"10.77.0.1",` + + `"datastore":"felhom-offsite","namespace":"peti","token_id":"felhom@pbs!peti","fingerprint":"` + testFP + `"}`) + var parsed hub.WirePBSDR + if err := jsonUnmarshal(legacy, &parsed); err != nil { + t.Fatalf("legacy descriptor must decode: %v", err) + } + if parsed.SecretGeneration != 0 { + t.Fatalf("absent key should parse as 0, got %d", parsed.SecretGeneration) + } + + // The struct a 0.90.0 agent would have hashed is byte-identical in every field it knows. + want := testBlock() // no SecretGeneration set → zero + if descriptorHash(&parsed) != descriptorHash(want) { + t.Fatal("a 0.91.0 agent hashes the EXISTING descriptor differently from 0.90.0 — the binary " + + "swap alone would re-apply and burn a one-time secret on every box") + } +}