Appearance
Artifact Registry — indexed plexd releases, Sigstore-verified before serving
This document is the authoritative bounded-context reference for the Artifact Registry — the seam between the upstream plexd release stream (versioned, per-architecture, Sigstore-signed binaries published into an OCI registry) and the verified, versioned release records the control plane serves over two read endpoints and exposes to the node-upgrade path. The context consumes and verifies that upstream stream: for each version it fetches the per-architecture checksums and Sigstore bundles, runs every artifact through a staged verification gate pinned to the upstream release-signing identity, and — only when every architecture passes — indexes a trusted record carrying the verification verdict the service.upgrade path consults. The domain root that pins the ubiquitous language is ../../internal/artifacts/doc.go.
The registry deliberately does not build or sign plexd binaries — that is the upstream plexd release pipeline, which signs keylessly via GitHub Actions OIDC → Fulcio with Rekor inclusion, and pins the release-signing identity into plexd at build time. It does not execute the node-side upgrade, and it does not store or serve the binary blob itself: it catalogues the binary's SHA-256 checksum and the Sigstore bundle, never the binary bytes (the binary stays upstream). The README's broader design vocabulary of a "signed HTTPS redirect", an artifact URL, and a minimum-compatible-API-version field is not part of this read-and-verify slice; this context defines, persists, and serves the verification gate only.
Upstream release-process contract
The OCI release layout the context consumes is the contract the OCI source adapter ../../internal/artifacts/ociclient/source.go replicates and every downstream wiring (the production composition root, the refresh reconciler) depends on:
- A version tag resolves to an OCI image index (manifest list) at
<repo>:<version>. A reference that resolves to anything that is not an index — a single image manifest, for example — is a layout error: the registry must publish one manifest per architecture. - Each index entry carries a
Platform.Architecture— the per-arch catalogue key. The OS is expected to belinux, but the architecture alone keys the catalogue. An index entry with no platform (or a blank architecture) is a layout error, not a silently-skipped entry: the plexd layout publishes exactly one platform-keyed image per architecture, so a platform-less entry means the upstream layout drifted from the contract and an incomplete release must never be indexed as if complete. - Each per-arch image manifest has exactly two layers, selected by media type (order-independent, never by index, so the upstream packer may emit them in any order):
- the plexd binary layer
application/vnd.plexsphere.plexd.binary.layer.v1— its descriptor digest IS the binary's SHA-256 checksum. The adapter reads the digest from the manifest and never pulls the blob: the binary stays in the upstream registry, and only its checksum is catalogued. A descriptor whose algorithm is notsha256, or whose hex does not decode to exactly 32 bytes, is a layout error. - the Sigstore bundle layer
application/vnd.dev.sigstore.bundle.v0.3+json— the adapter does pull this blob; its verbatim bytes become the bundle the verifier validates and the.sigstoreendpoint serves back byte-for-byte.
- the plexd binary layer
DECISION: the OCI adapter relies on go-containerregistry's native
pkg/v1/typesmedia-type constants andv1.Hashdigest type rather than importingopencontainers/{image-spec,go-digest}directly. Alternative considered: promote those opencontainers packages to direct imports for the media-type / digest abstractions. Rejected because go-containerregistry already exposes everything the adapter needs, image-spec is not in the module graph, and forcing the import would add a dependency with no call site.
Fetch returns the untrusted projection — see FetchedRelease in the ubiquitous-language table — never a constructed aggregate; the index service is the only seam that turns verified bytes into a trusted PlexdRelease.
Ubiquitous language
The terms below travel verbatim across the domain root, the persistence layer, the verification gate, the OCI source, the read endpoints, and the two domain events. Internal code never paraphrases them; documentation, JSON fields, and database columns adopt the exact spelling.
| Term | Definition | Code anchor |
|---|---|---|
| PlexdRelease | The aggregate the registry indexes: one plexd release line keyed by Version, carrying every per-architecture ReleaseArtifact, the lifecycle SupportStatus, and the VerificationVerdict the gate pronounced. The aggregate boundary is the version; the per-arch uniqueness invariant lives inside it. | ../../internal/artifacts/plexdrelease.go (PlexdRelease) |
| Version | Value object identifying one upstream release tag (e.g. v1.4.2). Trimmed string, structural equality; the zero value is "no version" and is rejected by every aggregate write path. | ../../internal/artifacts/value_objects.go (Version) |
| Architecture | Value object identifying the target CPU architecture of one artifact (e.g. amd64, arm64). Not a closed enum: the constructor enforces only non-emptiness because the canonical architecture set is owned upstream, not by plexsphere — pinning a closed set here would force a plexsphere release whenever the upstream pipeline added a target (e.g. riscv64) and the registry would silently refuse to index a genuinely-signed new artifact. | ../../internal/artifacts/value_objects.go (Architecture) |
| Checksum | Value object holding the raw 32-byte SHA-256 digest of an artifact's plexd binary. Stored as raw bytes (not hex), defensively copied in by the constructor and out by Bytes(), mirroring the raw-digest form every neighbouring aggregate uses. | ../../internal/artifacts/value_objects.go (Checksum) |
| ReleaseArtifact | The per-architecture build of one release: the Architecture, the Checksum, and the SigstoreBundle attesting it. A value object whose every field is itself an immutable value object owning its defensive copies. | ../../internal/artifacts/plexdrelease.go (ReleaseArtifact) |
| SigstoreBundle | Value object holding the keyless attestation cached for one artifact, as three byte payloads in lockstep: Raw (the verbatim bundle the .sigstore endpoint serves back), FulcioCertificate (the Fulcio code-signing leaf), and RekorInclusion (the transparency-log inclusion proof). All three are non-empty; the constructor rejects a bundle missing any of the three. | ../../internal/artifacts/sigstore_bundle.go (SigstoreBundle) |
| SupportStatus | The lifecycle verdict for an indexed release line. Closed set supported / deprecated / withdrawn; byte-stable lowercase strings that round-trip through the database CHECK and the JSON field. Operator-owned axis, orthogonal to the verification verdict. | ../../internal/artifacts/support_status.go (SupportStatus) |
| VerificationVerdict | Whether a release passed the Sigstore + checksum gate before being indexed. Closed set verified / unverified, held once on the aggregate, never per-artifact. | ../../internal/artifacts/plexdrelease.go (VerificationVerdict) |
| ReleaseSigningIdentity | The pinned upstream keyless-signing identity: the Fulcio certificate SAN and the OIDC issuer that vouched for it. The verifier accepts an artifact only when the verified leaf certificate's SAN and issuer match this pin. Lives in the verify/ sub-package alongside the gate that consumes it. | ../../internal/artifacts/verify/release_signing_identity.go (ReleaseSigningIdentity) |
| FetchedRelease / FetchedArtifact | The untrusted projection OCISource.Fetch returns for one version: the version tag plus per-arch FetchedArtifact{architecture, raw checksum bytes, raw bundle bytes}. Deliberately not a domain aggregate — constructing a verdict-carrying aggregate before the verifier runs would let an unverified record masquerade as trusted, exactly the failure the gate exists to prevent. | ../../internal/artifacts/ports.go (FetchedRelease, FetchedArtifact) |
| PlexdReleaseIndexed | Domain event emitted when a release passes the gate and is indexed. Stable discriminator artifacts.PlexdReleaseIndexed. | ../../internal/artifacts/events/events.go (PlexdReleaseIndexed) |
| PlexdReleaseRejected | Domain event emitted when a release artifact fails the gate and is rejected. Stable discriminator artifacts.PlexdReleaseRejected. | ../../internal/artifacts/events/events.go (PlexdReleaseRejected) |
Aggregate and invariants
PlexdRelease enforces the following invariants at construction time; each failure wraps a distinct sentinel so the transport layer can route it onto a specific Problem code without parsing strings:
Versionis non-zero.- At least one
ReleaseArtifactis present. - No two artifacts share an
Architecture— "a release admits at most one build per architecture". - Every
ReleaseArtifactis itself well-formed (non-zero architecture, checksum, and bundle). SupportStatusandVerificationVerdictare each in their closed set.
The aggregate groups every per-architecture build under one Version, and the per-arch uniqueness invariant lives inside that boundary — not solely in the database.
DECISION: the aggregate groups every per-architecture artifact under one Version rather than modelling one PlexdRelease per (version, arch). Alternative considered: a flat aggregate keyed by (version, arch) so the in-memory shape mirrors the one-row-per-(version, arch) table exactly. Chosen the version-keyed grouping because the ubiquitous language and the read API treat "a plexd release" as a version with several architecture builds —
GET /v1/artifacts/plexd/{version}returns one record listing every arch — so the aggregate boundary is the version. The persistence layer still stores one row per (version, arch) with a UNIQUE index; the repository re-groups rows into the aggregate on read. This keeps the per-arch uniqueness invariant on the aggregate per the DDD principle rather than relying solely on the database constraint.
The verification verdict is held once on the aggregate, not per artifact, because a release is verified or rejected as a whole: a single failing architecture rejects the entire version. The index service never persists a partially-trusted record — if any architecture fails verification, no row and no event for that version is written.
Verification pinning
The verification gate lives in the verify/ sub-package so the heavy sigstore-go parser stays out of the domain root (depguard confines it there). ReleaseSigningIdentity pins the upstream Fulcio SAN and OIDC issuer; the gate accepts an artifact only when the verified leaf certificate matches both, so an attacker holding a validly-chained Fulcio certificate for a different identity still fails the gate.
The sigstore-go-backed BundleVerifier (../../internal/artifacts/verify/bundle_verifier.go) runs a staged gate. Order matters — the cheap, deterministic content and presence checks run before the cryptographic work so a content mismatch or a missing log entry is attributed precisely instead of surfacing as a generic signature failure:
- Checksum pre-check — the digest the bundle attests must equal the artifact's expected SHA-256 checksum, else
ErrChecksumMismatch. - Rekor presence — a bundle carrying no transparency-log entry at all is rejected before any crypto work with
ErrMissingRekorProof. - Cryptographic verification under the full policy (Rekor inclusion and observer timestamp). The verifier owns the identity comparison separately (see step 4), so this stage runs without an identity constraint. On failure, a differential attribution step decides the cause: a second "relaxed" verifier — identical to the full one except it drops the transparency-log / observer-timestamp requirement and verifies the certificate chain against the current time — is consulted only to attribute the failure. If the relaxed verifier accepts the same entity, only the tlog requirement failed →
ErrBadRekorProof; otherwise the signature or chain itself failed →ErrBadSignature. The relaxed verifier never accepts an artifact; it is purely an attribution probe. - Strict-claim identity post-check on the verified leaf certificate — the SAN (URI or email) and the OIDC issuer must match the pin, else
ErrBadFulcioIdentity. Running this as a post-check on the already-verified certificate (rather than inside the policy) yields clean per-failure-mode attribution and mirrors the established strict-claim post-check pattern in the OIDC identity context.
An unparseable bundle collapses onto ErrBadSignature: a bundle that cannot be parsed cannot be cryptographically attributed to any signer, so from the gate's perspective it is indistinguishable from an invalid signature and the response (reject, do not index) is identical. Each stage maps to exactly one named sentinel in ../../internal/artifacts/verify/errors.go, so the index service can attribute the rejection via errors.Is without parsing strings.
DECISION: two verifiers rather than one. Alternative considered: a single full verifier and attribute every failure to ErrBadSignature. Rejected because an operator needs to tell "the binary is not in the transparency log / the inclusion proof is bad" (a Rekor failure) apart from "the signature itself does not verify" (a signing-key failure) — the two have different operational responses. Matching on sigstore-go's wrapped error text to make the distinction is brittle across library versions; running a second verifier whose ONLY difference is the dropped tlog/observer requirement isolates that one variable cleanly.
The trusted material the verifiers anchor on is fetched once at boot via sigstore-go root.FetchTrustedRoot() — the static public-good TUF snapshot.
DECISION: fetch the Sigstore trusted material via FetchTrustedRoot (the static snapshot) rather than NewLiveTrustedRoot (which keeps a background goroutine auto-refreshing it). Rejected the live root for KISS at this slice — the Fulcio CA roots rarely rotate, and short-lived certificate verification anchors on the bundle's own timestamp rather than the wall clock, so a static snapshot fetched once at boot verifies a genuinely signed release correctly. The cost is one network call at wiring time; on failure the pool is closed and the wiring refuses to construct so the operator sees the gap at boot. A future story needing auto-rotating roots swaps this one call for NewLiveTrustedRoot.
Persistence
The schema lives in migration ../../internal/platform/db/migrations/0037_plexd_artifacts.sql, which creates the single table plexsphere.plexd_artifacts carrying one row per (version, arch) build. The repository (../../internal/artifacts/repo/repo.go) writes the rows and re-groups them back into the aggregate on read.
Table shape
| Column | Type | Notes |
|---|---|---|
id | uuid | PRIMARY KEY. Application-allocated UUIDv7 the repository sets per row at INSERT time. |
version | text NOT NULL | CHECK (length(trim(version)) > 0) — the SQL mirror of the Version value object's non-empty invariant. |
arch | text NOT NULL | CHECK (length(trim(arch)) > 0) — the SQL mirror of the Architecture invariant. |
checksum | bytea NOT NULL | CHECK (length(checksum) = 32) — the raw 32-byte SHA-256 digest; NOT NULL because every catalogued artifact has a checksum. |
sigstore_bundle | bytea NOT NULL | CHECK (length(sigstore_bundle) > 0) — the verbatim raw bundle. |
fulcio_certificate | bytea NOT NULL | CHECK (length(fulcio_certificate) > 0) — the cached Fulcio leaf. |
rekor_inclusion | bytea NOT NULL | CHECK (length(rekor_inclusion) > 0) — the cached Rekor inclusion proof. The three payloads are persisted as three columns because the domain root cannot import the sigstore-go parser to re-extract them from the raw bundle. |
support_status | text NOT NULL | CHECK (support_status IN ('supported', 'deprecated', 'withdrawn')) — the SQL mirror of the closed SupportStatus set. |
verified_at | timestamptz NULL | Doubles as the VerificationVerdict discriminator (see DECISION below). |
created_at / updated_at | timestamptz NOT NULL DEFAULT now() | Set on INSERT. |
The UNIQUE index plexd_artifacts_version_arch_uq on (version, arch) is the SQL mirror of the aggregate's per-arch uniqueness invariant. Its name is load-bearing: the repository's classifyPgError maps the SQLSTATE 23505 unique-violation raised against this named constraint onto ErrReleaseConflict, so the index must not be renamed without updating the error classifier in lockstep.
DECISION: there is no verdict column; verified_at IS NULL encodes the unverified verdict, a non-NULL timestamp encodes verified AND records when verification last succeeded. Alternative considered: a separate
verdict text CHECK (verdict IN ('verified', 'unverified'))column alongside verified_at — rejected as redundant. The closed two-value verdict set maps cleanly onto the presence/absence of verified_at, so a dedicated column would carry no information verified_at does not already and would invite the two to disagree (averifiedverdict with a NULL timestamp, or vice versa) — an invariant the single NULLable column cannot violate.
Atomic write and read shape
Repo.Upsert writes every per-arch row plus exactly one outbox event row in one transaction, so the index mutation and its domain event commit atomically. The outbox aggregate_type is the hardcoded literal plexd_release — every row this adapter writes is a PlexdRelease event, so a per-call label would only invite a typo that fragments the outbox stream the relay reads. GetByVersion re-groups the per-arch rows into one aggregate and surfaces ErrReleaseNotFound when a version has no rows; List keyset-paginates the catalogue by version (one page bounds a whole number of versions, not rows).
The migration's Down arm DROPs the index and the table. The catalogue is a derived projection of the upstream OCI stream — every row is reconstructible by re-running the refresh/verify path — and the meaningful state-change history survives in plexsphere.outbox_events, anchored to its own aggregate identifier. So dropping the projection is not a regulatory regression, which is why this migration DROPs rather than RAISEing the way the regulatory-retention migrations do.
Domain events
Both events live in ../../internal/artifacts/events/events.go and use the aggregate-prefixed CamelCase discriminator form (a fresh top-level context with no prior wire contract to preserve, matching the tenancy.DomainCreated / tenancy.NodeRegistered shape):
PlexdReleaseIndexed(discriminatorartifacts.PlexdReleaseIndexed) — emitted on a successful index. The payload carriesevent_id,occurred_at,release_id,version, the sortedarchitectureslist, andsupport_status— enough for a downstream upgrade-gate projector or dashboard to react without joining back to the row. It deliberately carries no Sigstore bundle bytes or checksums: the bus is not the place to re-distribute the attestation.PlexdReleaseRejected(discriminatorartifacts.PlexdReleaseRejected) — emitted when an artifact fails the gate. The payload carriesversion,architecture, and a semantic reason label (e.g.checksum_mismatch,bad_fulcio_identity,missing_rekor_proof,bad_rekor_proof,bad_signature) — never a trace identifier and never any key material. It feeds the audit trail and pairs with the verification-failure metric so an operator can reconstruct which version/arch was rejected and why without parsing free-text logs.
Seeding
PlatformSeeds (../../internal/artifacts/services/seeds.go) returns the canonical in-support release set — today one genesis release v1.0.0 / amd64, supported, and crucially unverified. The seed declares the expected set — which (version, arch) the platform considers in-support, so the readiness reconciler knows the genesis target before any refresh has run — without claiming a Sigstore verification it has not performed: its checksum and the three Sigstore byte payloads are deterministic placeholders that exist only to satisfy the NOT NULL + length CHECK constraints for a structurally-valid row. The migration seeds the same row (id 00000000-0000-0000-0000-0000000000c1, placeholder bytes, ON CONFLICT (version, arch) DO NOTHING).
ReconcileSeeds is the idempotent lookup-compare-heal reconciler run at boot after migrations. The happy path is "the genesis row is already present and untouched" (the migration installed it); the healing branch fires only when the row was deleted out-of-band or a test started from a wiped schema, in which case it heals via Upsert (synthesizing the indexed event). seedDrift compares immutable identity fields only — version, the architecture set, and support_status — and never the placeholder checksum/bundle bytes or the verdict, so the first real refresh that overwrites the placeholders with verified material does not trip a false drift signal. The seed reconcile gates /readyz under the probe name artifacts-seeds.
Deletion vs. drift — what actually flips /readyz. The two mutation cases are handled differently, and the readiness contract follows the reconciler, not the colloquial "a seed row deleted or mutated turns /readyz red":
- A deleted seed self-heals. When the row is missing,
ReconcileSeedsre-creates it viaUpsertand returnsnil, so theartifacts-seedsprobe stays green. (A healed row carries a fresh application-allocated id rather than the migration's…00c1literal — harmless, since nothing keys off the artifact row id; see the row-idDECISIONinseeds.go/ migration0037.) - An identity-field mutation flips
/readyzto503. When the persisted row exists but its version, architecture set, or support_status no longer matches canon,seedDriftreturns a non-empty diff,ReconcileSeedsreturns an error, and theartifacts-seedsprobe reports503on the next tick. The reconciler deliberately does not silently overwrite an identity-drifted row — an operator must reconcile it. - A mutation confined to the placeholder checksum / bundle bytes or the verdict is not drift (those are the refresh path's to own), so it never flips
/readyz.
Refresh
IndexService (../../internal/artifacts/services/index_service.go) is the application-layer write side. It orchestrates Fetch (OCISource) → Verify (BundleVerifier) → Upsert (Repo):
IndexVersionfetches one version, runs every per-architecture artifact through the gate, and — only when all architectures pass — constructs the trusted aggregate and persists it with its outbox event in one transaction, emitting an audit entry on success. A single failing architecture rejects the whole version:IndexVersionreturns a wrapped error without upserting rather than indexing a partially-trusted record.RefreshrunsIndexVersionover a version set as a best-effort sweep: a per-version failure is logged and skipped so an unreachable or rejected version never blocks re-indexing the others. At the end it republishes the catalogue size gauge fromrepo.List(the persisted total, not the sweep-local count, so a steady-state re-index does not flap the gauge).- On re-index, an existing
SupportStatusis preserved — it is an operator-owned axis, so a refresh that re-verifies the same upstream bundle must not silently resurrect awithdrawnrelease tosupported. A brand-new version defaults tosupported. The verdict becomesverifiedonly when every architecture passes; this is how the genesis seed transitionsunverified→verifiedonce the real upstream bundle is fetched and verified.
Composition is opt-in via PLEXSPHERE_ARTIFACTS_OCI_REGISTRY — the registry's own feature-specific switch, deliberately notPLEXSPHERE_DSN. Gating on the DSN (which every database-backed deployment sets) would make the registry a hard boot dependency for the whole control-plane API, so a deployment that set a DSN but never configured the registry would crash-loop the binary and take down every unrelated /v1 surface with it; keying the switch on the registry pin instead leaves a registry-less deployment booting with the /v1/artifacts surface inert on its 501 stub. Once the switch is set, three further pins are required — PLEXSPHERE_DSN, PLEXSPHERE_ARTIFACTS_FULCIO_SAN, and PLEXSPHERE_ARTIFACTS_OIDC_ISSUER — and the build fails fast if any is empty (the BundleVerifier and OCISource cannot be constructed without them). Two optional knobs round out the surface: PLEXSPHERE_ARTIFACTS_REFRESH_INTERVAL_SECS (defaults to 15 minutes) tunes the refresh sweep, and PLEXSPHERE_ARTIFACTS_CURSOR_HMAC_KEY (hex-encoded, ≥ 32 bytes) binds the GET /v1/artifacts/plexd list cursor to the presenting caller — when unset the per-version GETs still boot and only the paginated list falls back to the unsigned identity codec. The composition root (../../cmd/plexsphere/artifacts_factory_prod.go) registers two boot + /readyz probes — artifacts-seeds and artifacts-refresh — plus a steady-state refresh ticker run under the goroutine supervisor.
Observability and metrics
The application services emit three Prometheus collectors (../../internal/artifacts/services/metrics.go):
plexsphere_artifacts_verification_total{result}— a counter split byresult(verifiedorrejected), incremented once per artifact the gate accepts or rejects.plexsphere_artifacts_indexed_records— a gauge of the current number of release records in the catalogue, set from the persisted catalogue size after each refresh sweep.plexsphere_artifacts_refresh_duration_seconds— a histogram of one refresh cycle's duration.
A process-wide audit-record-failure counter follows the loud-but-non-fatal idiom: an audit-chain outage increments the counter and emits a structured slog.Error but never rolls back a committed index mutation. The verification-rejection slog line carries emptyfeature / requirement fields on purpose — the identifier rule forbids trace identifiers in any logged string, so the requirement that motivates the rejection lives in the code's doc-comments, never in the log line.
HTTP read surface
The transport surface (../../internal/transport/http/v1/artifacts/) exposes two read operations:
GET /v1/artifacts/plexd/{version}— the JSON release record.GET /v1/artifacts/plexd/{version}/sigstore— the verbatim cached Sigstore bundle asapplication/octet-stream, so an operator can re-verify the attestation independently with cosign or a sigstore-go verifier.
Both authenticate the principal (a 401 otherwise) then authorise before the persistence read against the single platform-global ReBAC object platform:plexsphere with relation read. An authz denial maps to 404 release_not_found, not a 403: the platform-global registry withholds the existence side-channel, so a caller who lacks read cannot distinguish "you may not see this" from "no such release", and the contract declares no 403. When the dependency bundle is not wired at the composition root, both operations fail closed with 501 artifacts_not_provisioned. For the full wire contract — parameters, schemas, and the exhaustive Problem-code detail — see the API reference at ../reference/api/artifacts.md.
Downstream consumers
The verified record this context indexes feeds two downstream surfaces:
- Node upgrade. The node
service.upgradeverification path consults the verification verdict plus the known-good checksum set before admitting an artifact for the upgrade swap. This context produces the verdict; it does not execute the upgrade. - Capability & integrity correlation. The Capability & Hook Registry and the integrity-violation pipeline (see
./policy/integrity.md) compare reported binary / hook checksums against the registry's known-good set; a divergence raises anintegrity_alert. The cross-check belongs to that consumer arm, not to this context's ingest path.
Out of scope
This slice is the read-and-verify producer side. The following are deliberately not part of it:
- Building or signing plexd binaries — that is the upstream plexd release pipeline (keyless GitHub Actions OIDC → Fulcio → Rekor); the release-signing identity is pinned into plexd at build time and this context only verifies against that pin.
- Node-side upgrade execution — this context exposes the verdict; the node performs the swap.
- Storing or serving the binary blob — only the checksum and the Sigstore bundle are catalogued; the binary stays in the upstream registry.
- The README's broader registry vocabulary — the "signed HTTPS redirect", the artifact URL, and the minimum-compatible-API-version field are not modelled in this read-and-verify slice.
Cross-references
../../internal/artifacts/doc.go— the ubiquitous-language boundary and the in-scope / out-of-scope statement for the context.../../internal/artifacts/plexdrelease.go— thePlexdReleaseaggregate,ReleaseArtifact, andVerificationVerdict, with the version-keyed-grouping and verdict-on-the-aggregate DECISION blocks.../../internal/artifacts/value_objects.go—Version,Architecture(and the not-a-closed-enum DECISION), andChecksum.../../internal/artifacts/sigstore_bundle.go— theSigstoreBundlevalue object and the three-payloads-in-lockstep DECISION.../../internal/artifacts/support_status.go— the closedSupportStatusset.../../internal/artifacts/ports.go— theRepo,BundleVerifier, andOCISourceoutbound ports plusFetchedRelease/FetchedArtifactand the untrusted-projection DECISION.../../internal/artifacts/repo/repo.go— the single-transactionUpsert,GetByVersionre-grouping, and the keyset-paginatingList.../../internal/artifacts/verify/bundle_verifier.go— the staged gate, the full / relaxed differential attribution, and the strict-claim identity post-check.../../internal/artifacts/verify/release_signing_identity.go— the pinned Fulcio SAN + OIDC issuer value object.../../internal/artifacts/ociclient/source.go— the OCI image-index layout contract, the media-type layer selection, and the digest-as-checksum / never-pull-the-blob posture.../../internal/artifacts/services/index_service.go— the Fetch → Verify → Upsert orchestration, whole-version reject, the best-effort refresh sweep, and the support-status-preserved DECISION.../../internal/artifacts/services/seeds.go—PlatformSeeds, the idempotentReconcileSeeds, and the identity-fields-onlyseedDrift.../../internal/artifacts/services/metrics.go— the verification / indexed-records / refresh-duration collectors and the identifier-free rejection slog line.../../internal/artifacts/events/events.go— thePlexdReleaseIndexed/PlexdReleaseRejectedevents and their discriminators.../../cmd/plexsphere/artifacts_factory_prod.go— the composition root: the env contract, the build-time pin validation, the trusted-root fetch, and the probe + ticker wiring.../../internal/platform/db/migrations/0037_plexd_artifacts.sql— theplexsphere.plexd_artifactstable, the load-bearing unique index name, the verdict-as-timestamp encoding, and the DROP-on-Down posture.../../api/openapi/plexsphere-v1.yaml— theGetPlexdArtifact/GetPlexdArtifactSignatureoperations and their response schemas.../reference/api/artifacts.md— the wire-level API reference for the two read endpoints.../contributing/layout.md— the package-to-subsystem map and the depguard rules that enforce the context's isolation.../../README.md#artifact-registry— the system-level Artifact Registry subsystem description../policy/integrity.md— the integrity violation ingest context that compares reported checksums against this registry's known-good set.