carlos / platform Public

lease: export ReadReplicaClient for the db door

The console's carlos db door (spec 2026-09-02 §6) reads the same replica
prefixes the activator writes. It takes the writer's own construction,
endpoint override and region resolution included, so the two cannot sign
or address the bucket differently.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 27883b08da7363726d2a532e9ad2f655830e2304 parent 5b15b06
2 files changed, +56 −0
  • internal/lease/endpoint.go +19 −0
  • internal/lease/endpoint_test.go +37 −0
diff --git a/internal/lease/endpoint.go b/internal/lease/endpoint.go
index c945f0dd1..d01061b8c 100644
--- a/internal/lease/endpoint.go
+++ b/internal/lease/endpoint.go
@@ -128,3 +128,22 @@ func newReplicaClient(bucket, prefix, region string) *lss3.ReplicaClient {
}
return c
}
+
+// ReadReplicaClient is newReplicaClient for READERS outside this package —
+// the console's db door (spec 2026-09-02 §6). Identical construction on
+// purpose, region resolution included (Restore's resolveRegion): a second
+// hand-rolled client is how the endpoint override, the path-style flag and
+// the signing region drift apart between the writer and the reader of the
+// same bucket.
+//
+// It returns the same type the writer uses, so nothing here can enforce
+// read-only by construction; that guarantee lives in internal/dbdoor's
+// client, which has no write methods at all, and in the door only ever
+// calling LTXFiles and OpenLTXFile on what this returns.
+func ReadReplicaClient(ctx context.Context, bucket, prefix, region string) (*lss3.ReplicaClient, error) {
+ region, err := resolveRegion(ctx, region)
+ if err != nil {
+ return nil, err
+ }
+ return newReplicaClient(bucket, prefix, region), nil
+}
diff --git a/internal/lease/endpoint_test.go b/internal/lease/endpoint_test.go
index 75fb3f9f1..68346f032 100644
--- a/internal/lease/endpoint_test.go
+++ b/internal/lease/endpoint_test.go
@@ -301,3 +301,40 @@ func TestRestoreCreatesTheOutputPathsParentDirectory(t *testing.T) {
t.Fatalf("Restore did not create the output path's parent directory: %v", err)
}
}
+
+// ReadReplicaClient is the db door's reader (spec 2026-09-02 §6). It must
+// carry the coordinates it was given and sign for the region lease.Restore
+// would: an explicit region wins, and an empty one falls to the same chain
+// resolution the writer uses — the endpoint override's non-AWS default when
+// nothing else answers.
+func TestReadReplicaClientCarriesBucketPrefixRegion(t *testing.T) {
+ fakeCreds(t)
+ c, err := ReadReplicaClient(context.Background(), "data-bucket", "instances/a.example.com", "eu-west-1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if c.Bucket != "data-bucket" {
+ t.Errorf("Bucket = %q, want data-bucket", c.Bucket)
+ }
+ if c.Path != "instances/a.example.com" {
+ t.Errorf("Path = %q, want instances/a.example.com", c.Path)
+ }
+ if c.Region != "eu-west-1" {
+ t.Errorf("Region = %q, want eu-west-1", c.Region)
+ }
+}
+
+func TestReadReplicaClientResolvesAnEmptyRegionLikeRestore(t *testing.T) {
+ fakeCreds(t)
+ t.Setenv("CARLOS_S3_ENDPOINT", "http://127.0.0.1:1")
+ c, err := ReadReplicaClient(context.Background(), "data-bucket", "instances/a.example.com", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if c.Region != lss3.DefaultRegion {
+ t.Errorf("Region = %q, want litestream's non-AWS default %q (the writer's resolution)", c.Region, lss3.DefaultRegion)
+ }
+ if c.Endpoint != "http://127.0.0.1:1" || !c.ForcePathStyle {
+ t.Errorf("Endpoint = %q ForcePathStyle = %v, want the override applied", c.Endpoint, c.ForcePathStyle)
+ }
+}