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.

  1. oras push an artifact, then oras attach --artifact-type application/spdx+json an SBOM to it by digest.
  2. GET /v2/$SLUG/container/r/img/referrers/$DIGmanifests: [application/spdx+json → sha256:bbcd667337e9]
  3. DELETE /v2/$SLUG/container/r/img/manifests/$DIG202
  4. GET /v2/$SLUG/container/r/img/manifests/$DIG404, so the subject is gone
  5. GET /v2/$SLUG/container/r/img/referrers/$DIGmanifests: [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 in container_manifests and 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.

  1. 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.
  2. 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.

Edited by Suleimi Ahmed