# MirroredImage Specification ## Boundary | Concern | Requirement | | --------------- | ------------------------------------------------------------------------------------------------------------------------- | | Controller | A dedicated controller Deployment and service account watch `MirroredImage`; the Project Delivery controller does not. | | Isolation | Mirrored Image and Project Delivery controller pods remain separate because their permissions and owned resources differ. | | Reuse | Generic Go libraries and the F# runner may be shared; controller state machines remain separate. | | Intent | One `MirroredImage` owns one upstream repository. | | Destination | The corresponding Quay repositories are `candidates/` and `mirror/`. | | Credentials | Two Zitadel-backed Quay personas isolate OAuth authority by organization; four assigned tokens isolate it by privilege. | | New work | The `MirroredImage` API, controller state machine, Argo Workflows, and tests are independent work. | | Excluded input | Smeltery specifications, implementation, workflows, and tests are not inputs. | | Workload policy | Workload image-reference policy is outside the controller; Kyverno governs workload declarations. | ## Declaration ```yaml kind: MirroredImage metadata: name: ingress-nginx spec: upstream: registry.k8s.io/ingress-nginx/controller policy: rejectAtOrAbove: High digests: - digest: sha256:... tag: v1.13.0 - digest: sha256:... ``` ## Quay authorization OAuth application scopes are privilege boundaries, not organization boundaries. Each token acts as its assigned persona, and each persona has Quay authority only within its corresponding organization. | Persona | Quay resource boundary | Token | OAuth scope | | ---------------- | ---------------------- | ------------------- | --------------------------- | | `mirror-bot` | `mirror` only | `mirror-reader` | `repo:read` | | `mirror-bot` | `mirror` only | `mirror-writer` | `repo:create`, `repo:write` | | `candidates-bot` | `candidates` only | `candidates-reader` | `repo:read` | | `candidates-bot` | `candidates` only | `candidates-writer` | `repo:create`, `repo:write` | The reader and writer tokens are distinct credentials despite being assigned to the same persona. Neither persona has Quay authority in `quorate` or in the other mirror organization. Quay treats `repo:write` as including `repo:read`; each writer token therefore has create, read, and write authority inside its persona's organization. | Runtime | Quay credentials | | ---------------- | ------------------------------------ | | Controller | `candidates-reader`, `mirror-reader` | | Prepare Workflow | `candidates-writer` | | Promote Workflow | `candidates-reader`, `mirror-writer` | | Candidate GC | `candidates-writer` | | Mirror GC | `mirror-writer` | ## Models - [MirroredImage.tla](mirrored-image/MirroredImage.tla) - [RetentionTags.tla](mirrored-image/RetentionTags.tla) - [TagExpiry.tla](mirrored-image/TagExpiry.tla) ## Workflow interface | Argo Workflow | Stages | | ------------- | ----------------------- | | Prepare | Prepare | | Promotion | Promote | | Cleanup | Candidate GC, Mirror GC | | Field | Type | | -------------------------- | ----------------------- | | `retryOf` | Workflow-name reference | | `mirror.quorate.dev/retry` | Workflow-name reference | | `status.lastHandledRetry` | Workflow-name reference | ## Scan inputs | Image shape | `ClairInputs` | | --------------- | ------------------ | | Single manifest | Candidate manifest | | OCI index | Child manifests | ```math \mathrm{Safe} \iff \forall m \in \mathrm{ClairInputs}:\ \mathrm{ReportAvailable}(m) \land \forall v \in \mathrm{Findings}(m):\ \mathrm{severity}(v) < \mathrm{spec.policy.rejectAtOrAbove} ```