ADR-041 - Stella Registry (the internal primary registry is provisioned) and zot’s demotion to demo integration

Context

The ruling

The owner ruled (2026-08-03, verbatim, typos preserved): “Sit goes just for demo/development lab. Not part of the release installation … plan how to outline and cleanse our odisy . Present the plan . Do not miss a thing. Rethink when you present a plan. I need good arch where Stella registry is what it take care of all and Zoe is just demo integration” — read with the two preceding owner statements it refines: “zot theoretically is just stella ops and cli, nothing more” (round 96) and the round-100 premise challenge (the bundle’s zot is the evaluator’s scan target, while Stella’s distribution channel is a separately named thing, STELLA_REGISTRY=registry.stella-ops.org). The owner’s standing product want, recorded earlier: “to be able to protect/mask the meta information on top of existing integrated registries that are per installation.”

Interpretation, checked against the estate and adopted as binding:

  1. zot is demo/development-lab material only. It is not part of the release installation. In demos and the lab it appears as a registered integration — a simulated customer registry, a peer of the jenkins/nexus/gitlab/registry:2.8 fixtures in the 127.1.2.x family.
  2. The Stella Registry — Stella’s own product registry service — “takes care of all”: the product-side registry roles zot has been squatting on (plugin-bundle distribution, the estate’s registry surface, the bundle’s registry, the anonymous public namespace) and the meta-masking facade over per-installation integrated registries.
  3. Vendor distribution is unchanged and out of scope: registry.stella-ops.org (CLI/release images, anonymous pull + htpasswd push) and mirrors.stella-ops.org (compact vuln-db).

What the evidence says (verified 2026-08-03 against src/ and devops/)

Decision

D1 - The Stella Registry is built: stella+oci://registry is provisioned as a product component

Stella Ops builds its own registry service — the Stella Registry — as the backing of ADR-033’s reserved registry selector. It is the estate’s registry surface and “takes care of all” product-side registry roles:

  1. Stella artifact distribution inside an estate: plugin bundles (stella-ops/plugins/<id>, per ADR-040 D5 — the namespace and plan work transfer verbatim), licensed content (stella-ops/enterprise/*), and the anonymous public namespace (stella-ops/public/**, per ADR-040 D4 — same mechanics, new subject).
  2. The meta-masking facade (D3 below) over per-installation integrated registries.
  3. The release bundle’s registry (D5 below) — zot leaves the bundle.

The service family is Registry (src/Registry/), deployables registry-web (OCI data plane) + registry-token (the existing token service as its issuer role), own database stellaops_registry (ADR-039 §2.7, fail-closed STELLAOPS_POSTGRES_REGISTRY_CONNECTION), own object-store bucket (P16), isolated build graph (P19). Family placement of the token service supersedes EVL-5’s fold into Integrations pending the owner decision in OD-2 below.

Seed code: the OCI read plane (manifest/blob GET/HEAD, digest-verified content store, upstream pull-through) is extracted from StellaOps.Agent.Registry into a domain-neutral shared OCI core library (P19-classified foundation); the agent and the Stella Registry both consume it. What must be built new, sized honestly per ADR-040’s own record: the distribution-spec push path (monolithic + chunked resumable blob-upload sessions, cross-repo mount, manifest PUT with validation), tag listing, the OCI 1.1 Referrers API (native index, no fallback-tag emulation), deletion + GC (policy-driven, not the lab-zot 24h tag-loss trap recorded in sprint 003), bearer-JWT enforcement (validating exactly the token-service claim shape zot consumes today), and standing conformance (the official OCI distribution-spec conformance suite, vendored offline, green in CI as a release gate).

P18 reconciliation — why the service-birth calculus changed. ADR-040 rejected this build because (a) no consumer needed central layer custody and (b) an Apache-2.0 binary already in the bundle served every role. Both premises are gone: the owner mandated the component; the consumer set is real and enumerable (plugin publish + agent pull, the masking facade — the owner’s explicit product want and the token service’s missing subject, the bundle’s registry after zot leaves, the public namespace in estates); and the ruling removes zot from the release installation, so “the third-party binary already ships” is no longer an available alternative for product roles. The costs ADR-040 quantified did not shrink — they are now scheduled, phased, and gated (SPRINT_20260803_004), with zot as the interim host so there is no flag-day (D6).

D2 - Storage: object-store blobs + own Postgres metadata (recommended, owner-confirmable)

Blobs live content-addressed (blobs/sha256/<hex>) in a Registry-owned bucket on the in-stack S3 object store (SeaweedFS, compose key rustfs — present in the flagship stack and the bundle already). Metadata — repositories, tags, manifest index, upload sessions, referrer index, plan/audit tables absorbed from registry_token — lives in stellaops_registry.

Rejected: reusing the Scanner CAS / ADR-035 content broker (another service’s owned storage; P16 forbids shared non-Postgres ownership exactly as §2.7 forbids shared tables) and local filesystem volumes à la zot (couples the data plane to one node, breaks role-replica sharing, adds a second backup surface when the estate already operates an object store).

D3 - The meta-masking facade: ADR-033 D3’s router, re-introduced server-side

One /v2/ surface, three backend classes, dispatched by repository namespace:

NamespaceBackendContent
stella-ops/**own store (D2)Stella’s artifacts: plugins, public, enterprise
registries/<slug>/<repo>the tenant’s integrated registry, resolved from the integration record; credentials via ISecretProvider (ADR-033 D4); layers pulled through digest-verified, never copied into identitythe customer’s images, masked
referrers for any scanned subjectthe meta SoR via Scanner’s owner API (the existing read-only /v2/ endpoints) — never a direct database read (§2.7)SBOM / reachability / decision referrers

This is the owner’s masking want made concrete: a consumer that pulls a customer image through the facade sees Stella’s metadata overlaid — referrers answered from our System of Record — without Stella metadata ever having to be pushed into the customer’s registry. The per-integration external replay (DualWriteOciReferrerCoordinator) remains an opt-in interop projection, unchanged.

ADR-033 bookkeeping: this satisfies D3’s re-introduction trigger (≥ 2 live backends — own store, meta, external integrated — × ≥ 2 consumers — deploy agents/estates, plugin pull, CLI, CI). The re-introduction is a server-side registry facade, not a resurrection of the deleted client-side IImageRegistryGateway; MaskedImageRefResolver stays the client-side resolver and gains the facade as a resolution target for registry and (optionally, per deployment) registries/<slug> refs. ADR-033 D1 (digest is identity), D5 (resolve before Docker), and D6 (loopback agent pull-through for no-direct-registry hosts) are unchanged; the facade complements D6, it does not replace it.

D4 - Auth: the token service is the Stella Registry’s auth plane, unchanged in contract

Everything B1/B1c built transfers as-is: the realm dance, GET /token, the plan model, plan narrowing, registry.token.issue, the admin API, the anonymous pull-only mechanics, 5-minute JWTs, key custody (operator-generated, fail-closed — KEY-1 posture). The Stella Registry validates the same access claim shape zot validates today, so cutover changes the enforcing party, not the contract. Round 92’s control-model statement carries verbatim (total control at issuance; TTL, refusal, and key rotation as the levers).

Two auth-plane decisions ride this ADR:

D5 - zot leaves the release installation; the demo on-ramp is an optional overlay (recommended, owner-confirmable)

The default release bundle ships no zot. Its registry is the Stella Registry (with the token service, the hardened key custody, and named plans RZB-2 established — that work transfers to the bundle’s Stella Registry deployment).

The evaluator on-ramp zot served (“push a test image here to scan it without an external registry”) is preserved as an optional demo overlay (OD-1): an installer flag adds a zot container registered as a registries/<slug> demo integration — the same shape as the lab simulator — so the evaluator’s push-and-scan exercises the real product plane against a stand-in customer registry. Rejected: a sandbox push namespace on the Stella Registry (contradicts the standing 4b ruling that customer/tenant artifact hosting on the product registry is DENIED, and re-conflates the roles this ADR separates); docs-only (guts the evaluation story).

Amended 2026-09-27 (SPRINT_20260927_002 PCD-65): the registration is manual. The installer flag (install.sh --with-demo-registry, install.ps1 -WithDemoRegistry) starts the demo zot and prints the registration step at the end of the run; it does not register the integration. Registering from the installer means calling the Integrations API as some client with some scope, and this ADR never recorded which. Until the owner records that client and scope, the evaluator registers http://demo-registry.stella-ops.local:5000 once as an ordinary registries/<slug> integration. Automating it later takes three things: the owner-recorded client and scope, the installer calling the Integrations API once the stack is healthy, and a proof on an isolated install. A registration seeded by a database migration stays excluded: it would ship a demo integration into every real operator’s estate.

INSTALL_GUIDE and setup tooling drop zot from the “infrastructure” list; RZB-3 absorbs the doc moves and links the registry topology truth doc.

D6 - Staged migration; zot is the interim host; no flag-day

Until the Stella Registry push path is conformant and the cutover is proven live, the stella-infra zot (127.1.1.5) remains the interim plugin-publish target, bearer-gated. The 024 S7 forcing function still flips bearer auth on it — the anonymous-PUSH-open defect (measured 202 on unauthenticated blob upload) is a live integrity hole on the host serving signed plugin bundles, and the flip live-proves the exact token dance the Stella Registry inherits. After relocations, S7’s credential matrix is ~3 rows (RZB-4 re-derivation).

Lab end state: one zot — the customer-registry simulator (127.1.2.9), open, a registered demo integration. The stella-infra zot retires at plugin-distribution cutover (phase R6), its surviving content moved by the digest-preserving copy discipline sprint 003 established. The zot bearer-auth work is not wasted: it hardened the interim period, and its registry-side validation semantics are the executable specification for the Stella Registry’s enforcement.

Phases (program SPRINT_20260803_004; sizes are honest ballparks, not commitments):

PhaseContentSize
R1OCI core library extracted from StellaOps.Agent.Registry (P19-classified; agent unchanged)S
R2Service birth: read plane over own store, own DB + bucket, bearer validation, compose key, isolated publishM
R3Push path + tag listing + conformance suite (pull/push/discovery) green in CIL
R4Referrers API (native) + deletion/GC policyM
R5Plan enforcement end-to-end; plan_rules first-boot import; anonymousPlan mechanics for stella-ops/public/**S–M
R6Plugin-distribution cutover (live forcing function: sign-release → publish → deploy plugin pull, by digest); stella-infra zot retired; lab consolidates to one zotM
R7Bundle swap: Stella Registry replaces zot; demo overlay per OD-1; INSTALL_GUIDE/RZB-3 alignmentM
R8Masking facade: registries/<slug> pull-through + meta overlay via Scanner owner API; registry selector resolution wired into MaskedImageRefResolverL
R9Docs closeout: module dossier, truth doc end-state, knowledge-base verifyS

R8 can proceed in parallel with R6/R7 once R3 lands. Nothing repoints before its forcing function passes; every cutover is a config move (ADR-040 D2’s invariants made this true and they carry forward: no production code binds to a registry host; product OCI features stay registry-agnostic; no registry host enters identity).

D7 - What survives from ADR-040 unchanged

Disposition of all prior registry work

Nothing is silently dropped. KEEP = stands as landed; TRANSFERS = stands, subject/host changes at cutover; RE-SCOPED = task text changes; SUPERSEDED = replaced by this ADR.

#Work itemDisposition
1B1 zot bearer auth (1efb4c7c27…, zot config, X.509 cert mount, realm alignment)KEEP — interim posture + the executable spec for Stella Registry enforcement; retires with the stella-infra zot at R6
2B1c anonymous pull-only tokens (shipped disabled, posture-pinned)TRANSFERS — flips on on the Stella Registry (D4), never on zot
3registry.token.issue scope + S049 seed; gateway realm route + testsKEEP — contract unchanged through cutover
4Config-binder grant-widening fix (f801c33f71)KEEP
5Token-service plan model + admin API + auditKEEP — becomes the Stella Registry’s auth plane (D4)
6KEY-1 signing-key custody (fail-closed operator key)KEEP — same posture binds the Stella Registry
7RZB-1 plugin namespace + plans (stella-ops/plugins/*)TRANSFERS verbatim — same namespaces, same plans, new enforcing party at R6
8RZB-2 bundle hardening (named plans, operator key, realm generation, BundleRegistryTokenConfigTests)RE-SCOPED — the bundle’s zot leaves at R7, but every pattern (fail-closed key, named plans, realm-from-env, posture tests) transfers to the bundle’s Stella Registry deployment; the registry-token.bundle.yaml catalogue carries over minus the zot-specific evaluation plan (demo overlay is open, needs no plan)
9RZB-3 doc driftRE-SCOPED — absorbs zot-leaves-the-infrastructure-list (INSTALL_GUIDE, setup scripts) + linking the truth doc; the 127.1.2.5 conflation fix and testcontainer-skew note stand as written
10RZB-4 credential matrixRE-DERIVED — ~3 rows under the end-state: plugin publisher (plan plugin-publisher), plugin-pulling deploy agents (plugin-consumer), operator/CI admin; fixture lanes are on the open simulator, the bundle’s default install has no zot
11RZB-5 halt + four landed commitsUNBLOCKED, all four KEEP: 3e2278d22f (simulator = the end-state demo zot), e8d1a4f2e2 (scan-target plane → simulator: customer stand-ins belong on the demo integration), 873609c548 (fixture/e2e repoints: same), d3e83784e9 (renames, fail-closed attach-referrers defaults, mirror example, CLI bootstrap endpoint → simulator: the demo integration IS the simulator)
12RZB-5 frozen-fixture blockers (compliance-golden-path, verify-os-analyzer-scan)RIDE SPRINT 003 — FPR-5 re-targets the republish to the simulator and repoints the lane; the uncommitted copy-lab-fixture-to-simulator.sh gets committed as part of that task
13RZB-6 fail-closed Scanner defaultsKEEP — the same fail-closed discipline binds the Stella Registry’s own client configs
14Sprint 003 FPR-1…4 (provenance, preservation, recipes, per-lane analysis)KEEP — unaffected by the ruling, as that sprint predicted
15Sprint 003 FPR-6 (EF5 reference vs bytes)RESOLVED DIRECTION — bytes follow the reference: copy sha256:a9898acd… to customer-registry.stella-ops.local:5000/atlas/pas-policy; the reference already names the end-state registry
16Smoke scriptsKEEP — registry-auth-smoke.sh re-targets the Stella Registry at R6 (same dance); customer-registry-referrers-smoke.sh unchanged; both become regression harnesses
17024 INT-1 S0 design + EVL-5 foldRE-SCOPED pending OD-2 — the availability-isolation guardrail (token issuance must never be taken down by webhook storms) carries regardless of family placement; the DC-15 pin-collapse work is Integrations’ own and proceeds independently
18024 S7 bearer flipRE-TARGETED — guards the interim stella-infra zot until R6; preconditions unchanged (plan rows provisioned, RZB-4 matrix, key material staged); its posture then transfers to the Stella Registry
19northwind/ renames, stellaops/tetragon verdict, dockerhub-mirror exampleKEEP
20StellaOps.Agent.RegistryKEEP + PROMOTED — read plane extracted to the shared OCI core (R1); the agent’s behavior is unchanged and its specs must stay green
21ADR-040 D2 invariants, D5, D6, §R1.2CARRIED FORWARD (D7)
22ADR-040 D1, D3, D4-host, §R1.3SUPERSEDED by this ADR
23Vendor infra (registry.stella-ops.org, mirrors.stella-ops.org)UNCHANGED, out of scope

Owner decisions (each with recommendation; the program’s OD gate)

RULED 2026-08-03 (same day): OD-1/2/3/4/6 approved as recommended; OD-5 approved with a clarification (the import is config→empty-table seeding, never data preservation or overwrite — see SPRINT_20260803_004 SR-0 for the recorded wording); the fixture durable home approved (Registry-owned S3 fixtures/ bucket + committed digest manifest). OD-2 explicitly supersedes EVL-5’s token-service fold into Integrations. The OD gate is CLOSED; SR-1 may proceed.

Consequences

Rejected alternatives