A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io

hold/pds: fix scan broadcaster predecessor check to compare successor did #49

Merged evan.jarrett.net
targeting main from deleted fork:main
Merged opened by and assigned to evan.jarrett.net
targeting main from deleted fork:main

ScanBroadcaster.checkPredecessor (pkg/hold/pds/scan_broadcaster.go, ~line 1449) returns true whenever a hold's captain record has a non-empty successor field, without checking whether that successor is this hold. Result: every hold on the network that has declared a successor (i.e. has been migrated) gets its manifests queued for scanning by every other hold running proactive discovery.

Repro

  1. Hold A (did:web:hold01.atcr.io) has captain.successor = "did:plc:wnbpdx...".
  2. Hold B (did:web:hold.styx.mrijke.nl) runs with scanner.rescan_interval > 0.
  3. A pusher pushes an image to hold A; manifest.holdDid = did:web:hold01.atcr.io.
  4. Hold B's discovery loop walks the pusher's PDS, sees the manifest, calls isOurManifest("did:web:hold01.atcr.io").
  5. isOurManifest misses the direct match, falls through to checkPredecessor, fetches hold A's captain record, sees successor != "", returns true.
  6. Hold B queues the manifest for scanning. Scanner fetches config blob from hold B's S3 → 404 (blob was never in hold B's storage).

Impact Any operator running a scanner with rescan_interval > 0 ends up scanning the migration backlog of every other migrated hold on the network. Scanner logs flood with 404 errors for foreign images; operator pays for Syft/Grype CPU and bandwidth on other people's images.

Fix: checkPredecessor should compare captain.Successor == sb.holdDID before returning true.

Commits 0