Referrers of a hard-deleted manifest stay listed against the deleted subject
Summary
Hard-deleting a container manifest leaves its referrers listed against the deleted digest. S12 says the referrers query against a deleted subject returns the empty set; it returns the referrer.
Reproduction
Against a bootstrap caproni rig, over raw HTTP — no test code involved. $DIG is
the subject manifest's digest, $SLUG/r/img a hosted oci repository.
oras pushan artifact, thenoras attach --artifact-type application/spdx+jsonan SBOM to it by digest.GET /v2/$SLUG/container/r/img/referrers/$DIG→manifests: [application/spdx+json → sha256:bbcd667337e9]DELETE /v2/$SLUG/container/r/img/manifests/$DIG→ 202GET /v2/$SLUG/container/r/img/manifests/$DIG→ 404, so the subject is goneGET /v2/$SLUG/container/r/img/referrers/$DIG→manifests: [application/spdx+json → sha256:bbcd667337e9]— unchanged
Step 5 is the defect. Step 4 confirms the subject really was deleted, and the
referrer remains pullable by its own digest (200), which the spec calls correct.
What the spec says
S12 makes the empty-set claim three times:
:1340— "Referrers of the deleted manifest ... are not cascaded; they stay incontainer_manifestsand remain reachable by their own digest, just no longer surfaced by referrers queries against the now-deleted subject.":1406— "The referrers query against the deleted subject returns the empty set because there is no row to match.":1680— "the referrers query against the deleted digest returns the empty set."
Cause
listReferrersPageStmt (internal/datastore/container_manifest.go:658) filters
on namespace_id, container_image_id and subject_digest, plus an optional
artifact type and cursor. Nothing requires the subject manifest row to still
exist, so referrer rows keep matching after the subject is hard-deleted.
The spec's rationale does not hold either
:1406 justifies the empty set with "because there is no row to match". The rows
the query matches are the referrers, and the same paragraph states that a
hard delete deliberately does not cascade to them — so rows do remain. Whichever
way this is resolved, that sentence needs correcting: it argues from a premise the
paragraph above it contradicts.
Two candidate resolutions
This needs the spec author, per the guardrail on ambiguous specs.
- Implementation follows the spec. Add a subject-existence predicate to the referrers query, so a deleted subject lists nothing. Note this makes the referrers list depend on a row the referrer does not reference by id.
- Spec follows the implementation. Amend the three passages: referrers of a hard-deleted subject stay listed until S28's reachability traversal reclaims them. This is closer to the OCI distribution spec, which derives the list from manifests declaring that subject and does not require the subject to exist.
Impact
Low for correctness of served content, and no new information is exposed: the
referrer is independently pullable by its own digest either way. It matters for
clients that treat an empty referrers list as "the subject is gone", and for the
e2e catalog, where e2e.oci.lifecycle.delete-artifact cannot be marked
implemented while the spec and the implementation disagree.
Regression test, already written
The e2e test for e2e.oci.lifecycle.delete-artifact exists and asserts all three
clauses of that catalog row. Its first two — pulls by the deleted digest and by
its tags fail — pass today; the referrers clause is what fails. It reads the
referrers index over raw HTTP through registry.Client.Referrers, because
oras discover resolves the subject before it lists and so cannot observe this at
all. The test is held back rather than committed red, and lands with whichever
resolution is chosen.