Problem
The mirror action (mirror-image / _mirror-image.yml) decides idempotency by comparing the source manifest digest with the destination (quarantine/<image>:<tag>) digest. It copies whenever they differ or the destination is absent.
This is stateless. Once an image is promoted out of quarantine, the promotion workflow deletes the tag from quarantine — and because GHCR deletes the whole package when the last tagged version is removed, the quarantine/<image> repo disappears. On the next scheduled mirror run the destination is absent, so the digest comparison mismatches and the already-processed, already-promoted digest is re-synchronized into quarantine. The image then loops back through the pipeline.
Goal
Give the mirror a durable memory of what it has already synchronized so a digest that was mirrored once is not mirrored again, even after it has been promoted and deleted from quarantine.
Proposed approach
Store a mirror-history OCI artifact in each synchronized repo (ghcr.io/<owner>/quarantine/<image>), under a reserved tag mirror-history. It is a small, append-only JSON log (one entry per synchronized source digest, keyed by source tag) wrapped as an OCI artifact. Because it is a separate tag, it survives image-tag deletion during promotion — and, as a bonus, keeps the quarantine package alive so promotion no longer hits the "cannot delete the last tagged version" edge case.
The mirror flow gains a step: before copying, read the history artifact; if the resolved source digest for that source tag is already recorded, skip the copy (unless force). After a successful copy (or a force run), append the digest to the history and push the updated artifact.
Decisions (agreed)
- Location: colocated in
quarantine/<image>, reserved tag mirror-history.
- History key: source manifest digest, scoped per source tag.
force: bypasses the history check and still records the digest.
- Retention: append-only, unbounded.
Scope
Design first. A design doc will be added under docs/architecture/acquire/, reviewed, then broken into child work items before implementation.
Problem
The mirror action (
mirror-image/_mirror-image.yml) decides idempotency by comparing the source manifest digest with the destination (quarantine/<image>:<tag>) digest. It copies whenever they differ or the destination is absent.This is stateless. Once an image is promoted out of quarantine, the promotion workflow deletes the tag from quarantine — and because GHCR deletes the whole package when the last tagged version is removed, the
quarantine/<image>repo disappears. On the next scheduled mirror run the destination is absent, so the digest comparison mismatches and the already-processed, already-promoted digest is re-synchronized into quarantine. The image then loops back through the pipeline.Goal
Give the mirror a durable memory of what it has already synchronized so a digest that was mirrored once is not mirrored again, even after it has been promoted and deleted from quarantine.
Proposed approach
Store a mirror-history OCI artifact in each synchronized repo (
ghcr.io/<owner>/quarantine/<image>), under a reserved tagmirror-history. It is a small, append-only JSON log (one entry per synchronized source digest, keyed by source tag) wrapped as an OCI artifact. Because it is a separate tag, it survives image-tag deletion during promotion — and, as a bonus, keeps the quarantine package alive so promotion no longer hits the "cannot delete the last tagged version" edge case.The mirror flow gains a step: before copying, read the history artifact; if the resolved source digest for that source tag is already recorded, skip the copy (unless
force). After a successful copy (or aforcerun), append the digest to the history and push the updated artifact.Decisions (agreed)
quarantine/<image>, reserved tagmirror-history.force: bypasses the history check and still records the digest.Scope
Design first. A design doc will be added under
docs/architecture/acquire/, reviewed, then broken into child work items before implementation.