···6464 return blobStore
6565 }
66666767- // For pull operations, check database for hold DID from the most recent manifest
6868- // This ensures blobs are fetched from the hold recorded in the manifest, not re-discovered
6767+ // Determine if this is a pull (GET) or push (PUT/POST/HEAD/etc) operation
6868+ // Pull operations use the historical hold DID from the database (blobs are where they were pushed)
6969+ // Push operations use the discovery-based hold DID from user's profile/default
7070+ // This allows users to change their default hold and have new pushes go there
7171+ isPull := false
7272+ if method, ok := ctx.Value("http.request.method").(string); ok {
7373+ isPull = method == "GET"
7474+ }
7575+6976 holdDID := r.Ctx.HoldDID // Default to discovery-based DID
7077 holdSource := "discovery"
71787272- if r.Ctx.Database != nil {
7979+ // Only query database for pull operations
8080+ if isPull && r.Ctx.Database != nil {
7381 // Query database for the latest manifest's hold DID
7482 if dbHoldDID, err := r.Ctx.Database.GetLatestHoldDIDForRepo(r.Ctx.DID, r.Ctx.Repository); err == nil && dbHoldDID != "" {
7583 // Use hold DID from database (pull case - use historical reference)
7684 holdDID = dbHoldDID
7785 holdSource = "database"
7878- slog.Debug("Using hold from database manifest", "component", "storage/blobs", "did", r.Ctx.DID, "repo", r.Ctx.Repository, "hold", dbHoldDID)
8686+ slog.Debug("Using hold from database manifest (pull)", "component", "storage/blobs", "did", r.Ctx.DID, "repo", r.Ctx.Repository, "hold", dbHoldDID)
7987 } else if err != nil {
8088 // Log error but don't fail - fall back to discovery-based DID
8189 slog.Warn("Failed to query database for hold DID", "component", "storage/blobs", "error", err)
+80-11
pkg/appview/storage/routing_repository_test.go
···109109 assert.NotNil(t, repo.manifestStore)
110110}
111111112112-// TestRoutingRepository_Blobs_WithDatabase tests blob store with database hold DID
113113-func TestRoutingRepository_Blobs_WithDatabase(t *testing.T) {
112112+// TestRoutingRepository_Blobs_PullUsesDatabase tests that GET (pull) uses database hold DID
113113+func TestRoutingRepository_Blobs_PullUsesDatabase(t *testing.T) {
114114 dbHoldDID := "did:web:database.hold.io"
115115+ discoveryHoldDID := "did:web:discovery.hold.io"
115116116117 ctx := &RegistryContext{
117118 DID: "did:plc:test123",
118119 Repository: "myapp",
119119- HoldDID: "did:web:default.hold.io", // Discovery-based hold (should be overridden)
120120+ HoldDID: discoveryHoldDID, // Discovery-based hold (should be overridden for pull)
120121 ATProtoClient: atproto.NewClient("https://pds.example.com", "did:plc:test123", ""),
121122 Database: &mockDatabase{holdDID: dbHoldDID},
122123 }
123124124125 repo := NewRoutingRepository(nil, ctx)
126126+127127+ // Create context with GET method (pull operation)
128128+ pullCtx := context.WithValue(context.Background(), "http.request.method", "GET")
129129+ blobStore := repo.Blobs(pullCtx)
130130+131131+ assert.NotNil(t, blobStore)
132132+ // Verify the hold DID was updated to use the database value for pull
133133+ assert.Equal(t, dbHoldDID, repo.Ctx.HoldDID, "pull (GET) should use database hold DID")
134134+}
135135+136136+// TestRoutingRepository_Blobs_PushUsesDiscovery tests that push operations use discovery hold DID
137137+func TestRoutingRepository_Blobs_PushUsesDiscovery(t *testing.T) {
138138+ dbHoldDID := "did:web:database.hold.io"
139139+ discoveryHoldDID := "did:web:discovery.hold.io"
140140+141141+ testCases := []struct {
142142+ name string
143143+ method string
144144+ }{
145145+ {"PUT", "PUT"},
146146+ {"POST", "POST"},
147147+ {"HEAD", "HEAD"},
148148+ {"PATCH", "PATCH"},
149149+ {"DELETE", "DELETE"},
150150+ }
151151+152152+ for _, tc := range testCases {
153153+ t.Run(tc.name, func(t *testing.T) {
154154+ ctx := &RegistryContext{
155155+ DID: "did:plc:test123",
156156+ Repository: "myapp-" + tc.method, // Unique repo to avoid caching
157157+ HoldDID: discoveryHoldDID,
158158+ ATProtoClient: atproto.NewClient("https://pds.example.com", "did:plc:test123", ""),
159159+ Database: &mockDatabase{holdDID: dbHoldDID},
160160+ }
161161+162162+ repo := NewRoutingRepository(nil, ctx)
163163+164164+ // Create context with push method
165165+ pushCtx := context.WithValue(context.Background(), "http.request.method", tc.method)
166166+ blobStore := repo.Blobs(pushCtx)
167167+168168+ assert.NotNil(t, blobStore)
169169+ // Verify the hold DID remains the discovery-based one for push operations
170170+ assert.Equal(t, discoveryHoldDID, repo.Ctx.HoldDID, "%s should use discovery hold DID, not database", tc.method)
171171+ })
172172+ }
173173+}
174174+175175+// TestRoutingRepository_Blobs_NoMethodUsesDiscovery tests that missing method defaults to discovery
176176+func TestRoutingRepository_Blobs_NoMethodUsesDiscovery(t *testing.T) {
177177+ dbHoldDID := "did:web:database.hold.io"
178178+ discoveryHoldDID := "did:web:discovery.hold.io"
179179+180180+ ctx := &RegistryContext{
181181+ DID: "did:plc:test123",
182182+ Repository: "myapp-nomethod",
183183+ HoldDID: discoveryHoldDID,
184184+ ATProtoClient: atproto.NewClient("https://pds.example.com", "did:plc:test123", ""),
185185+ Database: &mockDatabase{holdDID: dbHoldDID},
186186+ }
187187+188188+ repo := NewRoutingRepository(nil, ctx)
189189+190190+ // Context without HTTP method (shouldn't happen in practice, but test defensive behavior)
125191 blobStore := repo.Blobs(context.Background())
126192127193 assert.NotNil(t, blobStore)
128128- // Verify the hold DID was updated to use the database value
129129- assert.Equal(t, dbHoldDID, repo.Ctx.HoldDID, "should use database hold DID")
194194+ // Without method, should default to discovery (safer for push scenarios)
195195+ assert.Equal(t, discoveryHoldDID, repo.Ctx.HoldDID, "missing method should use discovery hold DID")
130196}
131197132198// TestRoutingRepository_Blobs_WithoutDatabase tests blob store with discovery-based hold
···292358 assert.NotNil(t, cachedBlobStore)
293359}
294360295295-// TestRoutingRepository_Blobs_Priority tests that database hold DID takes priority over discovery
296296-func TestRoutingRepository_Blobs_Priority(t *testing.T) {
361361+// TestRoutingRepository_Blobs_PullPriority tests that database hold DID takes priority for pull (GET)
362362+func TestRoutingRepository_Blobs_PullPriority(t *testing.T) {
297363 dbHoldDID := "did:web:database.hold.io"
298364 discoveryHoldDID := "did:web:discovery.hold.io"
299365300366 ctx := &RegistryContext{
301367 DID: "did:plc:test123",
302302- Repository: "myapp",
368368+ Repository: "myapp-priority",
303369 HoldDID: discoveryHoldDID, // Discovery-based hold
304370 ATProtoClient: atproto.NewClient("https://pds.example.com", "did:plc:test123", ""),
305371 Database: &mockDatabase{holdDID: dbHoldDID}, // Database has a different hold DID
306372 }
307373308374 repo := NewRoutingRepository(nil, ctx)
309309- blobStore := repo.Blobs(context.Background())
375375+376376+ // For pull (GET), database should take priority
377377+ pullCtx := context.WithValue(context.Background(), "http.request.method", "GET")
378378+ blobStore := repo.Blobs(pullCtx)
310379311380 assert.NotNil(t, blobStore)
312312- // Database hold DID should take priority over discovery
313313- assert.Equal(t, dbHoldDID, repo.Ctx.HoldDID, "database hold DID should take priority over discovery")
381381+ // Database hold DID should take priority over discovery for pull operations
382382+ assert.Equal(t, dbHoldDID, repo.Ctx.HoldDID, "database hold DID should take priority over discovery for pull (GET)")
314383}