carlos / platform Public

provision: ListRecordReplicas gains a judged sibling, and the unjudged listing is it under a nil judge

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 7ce649e26b78bb0a4be6b3769661478c073be10f parent ede1421
2 files changed, +42 −1
  • internal/provision/lifetime_judge_test.go +34 −0
  • internal/provision/replica.go +8 −1
diff --git a/internal/provision/lifetime_judge_test.go b/internal/provision/lifetime_judge_test.go
index 24fd37f79..11121c7aa 100644
--- a/internal/provision/lifetime_judge_test.go
+++ b/internal/provision/lifetime_judge_test.go
@@ -891,3 +891,37 @@ func TestInstanceRecordKeyOfIsTheRecordKeyShape(t *testing.T) {
}
}
}
+
+// TestListRecordReplicasJudged: the judged replica listing leaves out a
+// replica record its slot no longer vouches for; a nil judge is
+// ListRecordReplicas.
+func TestListRecordReplicasJudged(t *testing.T) {
+ ctx := context.Background()
+ s := release.NewDirStore(t.TempDir())
+ stale := Record{Schema: 1, Account: "bab", App: "shop", Host: judgeHost, Channel: "edge", Role: "replica"}
+ legacy := Record{Schema: 1, Account: "bab", App: "shop", Host: "eu." + judgeHost, Channel: "edge", Role: "replica"}
+ home := Record{Schema: 1, Account: "bab", App: "shop", Host: "home." + judgeHost, Channel: "edge"}
+ for _, rec := range []Record{stale, legacy, home} {
+ judgePut(t, s, RecordKey("bab", "shop", rec.Host), rec)
+ }
+ judgePut(t, s, HostIncarnationKey(judgeHost), judgeSlot(1, SlotRetired))
+ hosts := func(recs []Record) []string {
+ var out []string
+ for _, r := range recs {
+ out = append(out, r.Host)
+ }
+ return out
+ }
+ judged, err := ListRecordReplicasJudged(ctx, s, NewLifetimeJudge(), "bab", "shop", 10)
+ if err != nil || !reflect.DeepEqual(hosts(judged), []string{legacy.Host}) {
+ t.Fatalf("judged replicas %v, %v; want only %s", hosts(judged), err, legacy.Host)
+ }
+ plain, err := ListRecordReplicas(ctx, s, "bab", "shop", 10)
+ if err != nil {
+ t.Fatal(err)
+ }
+ nilJudged, err := ListRecordReplicasJudged(ctx, s, nil, "bab", "shop", 10)
+ if err != nil || !reflect.DeepEqual(nilJudged, plain) || !reflect.DeepEqual(hosts(plain), []string{legacy.Host, judgeHost}) {
+ t.Fatalf("nil judge %v (%v), unjudged %v", hosts(nilJudged), err, hosts(plain))
+ }
+}
diff --git a/internal/provision/replica.go b/internal/provision/replica.go
index e2283bf86..c274855e8 100644
--- a/internal/provision/replica.go
+++ b/internal/provision/replica.go
@@ -91,7 +91,14 @@ func CreateRecordReplica(ctx context.Context, s release.Store, rec ReplicaRecord
// role=replica, so a home instance can never mistake a sibling home for its
// own replica even before it checks the role on the wire view.
func ListRecordReplicas(ctx context.Context, s release.Store, acct, app string, max int) ([]Record, error) {
- recs, _, err := ListRecords(ctx, s, acct, app, max)
+ return ListRecordReplicasJudged(ctx, s, nil, acct, app, max)
+}
+
+// ListRecordReplicasJudged is ListRecordReplicas under a lifetime judge: the
+// listing is ListRecordsJudged, so a replica record its slot no longer
+// vouches for is left out. A nil judge is ListRecordReplicas.
+func ListRecordReplicasJudged(ctx context.Context, s release.Store, j *LifetimeJudge, acct, app string, max int) ([]Record, error) {
+ recs, _, err := ListRecordsJudged(ctx, s, j, acct, app, max)
if err != nil {
return nil, err
}