StellaOps Registry
Verified against commit
942a18727c(2026-09-26, DOC-2 of SPRINT_20260926_001). Re-verify by reading the evidence paths listed in the Evidence section.
Service wiring (image, database and role, compose profile, bundle presence) is generated in the service inventory; this dossier does not restate it.
Audience: operators running the Stella Registry and its token service, and integrators that pull from or publish to it through Docker/OCI clients.
src/Registry/ is the Stella Registry, the product’s own OCI registry. It is one service family with two deployables that share the family database stellaops_registry but no source edge; the contract between them is a signed registry token verified against a certificate.
| Deployable | Source | Role |
|---|---|---|
registry-web | src/Registry/StellaOps.Registry.WebService | Distribution-spec data plane: pull, push, tag listing, referrers, deletion as GC policy, and the registries/<slug> meta-masking facade. Answers on registry.stella-ops.local and stella-registry.stella-ops.local. |
registry-token | src/Registry/StellaOps.Registry.TokenService | Bearer issuer: exchanges an Authority access token for a short-lived registry JWT after plan and licence checks; plan-rule administration API. |
Both deployables ship in the release bundle (devops/release/bundle/docker-compose.yml). The generated OpenAPI reference for the token service lives in registry-token/. Every registry-shaped surface in an estate and its single role is listed in the registry topology; the direction is recorded in ADR-041.
Responsibilities
- registry-web serves the OCI distribution-spec surface, validates registry bearer tokens (issuer,
serviceaudience,accessclaim) against the token service’s public X.509 certificate, stores metadata in schemaregistryand content in its own object-store bucket, and reclaims expired upload sessions. - registry-token validates the caller’s Authority token and required scope (host floor
registry.token.issue; configurable throughAuthority:RequiredScopes), resolves the caller’s plan, and mints an RS256 registry JWT whoseaccessclaim carries only the permitted repository actions. - An Authority-issued
stellaops:planclaim is authoritative. Only an absent or blank claim falls back toDefaultPlan; an unknown claimed plan is refused withplan_unknownand never downgraded. - Issuance is refused for revoked licences (
stellaops:licenseclaim matched againstRevokedLicenses) and for disabled or unknown plans. - Anonymous issuance is off by default. It is available only when
AnonymousPlannames a plan, and an anonymous token can carry onlypull.
Key endpoints
registry-token
GET /token- Docker registry token exchange (endpoint isAllowAnonymous; theregistry.token.issuepolicy is evaluated by hand for callers that send anAuthorizationheader). Requires aservicequery parameter and one or morescopeparameters of the formrepository:<name>:<actions>(space-delimited or repeated; actions comma-separated; a scope without actions requestspull). Returns JSON{ token, expires_in, issued_at, issued_token_type }. Problem responses: 400 for a missingservice, an invalid scope or no scopes; 401 for invalid credentials, or for an anonymous caller that cannot be served; 403 when the service is not inRegistry:AllowedServicesor plan/licence authorization fails; 503 when the host is not provisioned for issuance./api/admin/plans- plan-rule administration (policyregistry.admin): list, get by id, create, update (optimistic concurrency onversion), delete,POST /validate(dry run with optionaltestScopes) andGET /audit(paginated history).GET /healthz,GET /openapi/v1.json(anonymous), and the build-info endpoint (module nameregistry-token).
registry-web
/v2/distribution-spec read and write routes (GET/HEADblobs and manifests, uploads, manifestPUT, tag listing, referrers).DELETEanswers405 UNSUPPORTED: deletion is GC policy.- Repository names under
registries/<slug>/(for example/v2/registries/<slug>/<repo>/manifests/<digest>) are the read-only pull-through facade over a tenant’s integrated registry, off unlessRegistry:Facade:*is configured. GET /healthz,GET /readyz, the build-info endpoint (module nameregistry-web), andGET /doctor/registry-web/checks(Authority scopeops.health).
Both registry.admin and registry.token.issue are defined in the Authority scope catalogue (StellaOpsScopes.RegistryAdmin, StellaOpsScopes.RegistryTokenIssue).
Configuration (registry-token)
- Plan catalogue file:
etc/registry-token.yaml, resolved to an absolute path next to the content root (../etc/registry-token.yaml), optional, no reload-on-change. The unprefixed environment provider is re-applied after the file, so deploymentRegistryTokenService__*variables override it.REGISTRY_TOKEN_-prefixed variables are also read. - Configuration section
RegistryTokenService(RegistryTokenServiceOptions):Authority-Issuer(absolute URI; HTTPS unless loopback orRequireHttpsMetadata=false),MetadataAddress,RequireHttpsMetadata(defaulttrue),Audiences,RequiredScopes(default empty; the host then appliesregistry.token.issue).Signing-Issuer, optionalAudience(defaults to the requestedservice),KeyPath(operator-supplied PEM or PFX; no inline key, fails closed when missing),KeyPassword, optionalKeyId,Lifetime(default00:05:00, between 1 second and 1 hour).Registry-Realm(absolute URI, required),AllowedServices(empty permits any service).Plans- static catalogue:NameandRepositories(Patternwith*wildcard,Actions). Actions have no default: a repository rule without actions fails validation.DefaultPlan,AnonymousPlan(pull-only; unset by default),RevokedLicenses.Postgres-ConnectionString(required outside theTestingenvironment), optionalSchemaName(defaultregistry_token).
- Deployments map
STELLAOPS_POSTGRES_REGISTRY_CONNECTIONintoRegistryTokenService__Postgres__ConnectionString.
registry-web configuration (Registry:Bearer:*, Registry:Storage:*, Registry:Push:*, Registry:Facade:*) is described in architecture.md.
Persistence
- Database
stellaops_registry, owned by the family and resolved fromSTELLAOPS_POSTGRES_REGISTRY_CONNECTION(no shared-connection fallback). - registry-token owns schema
registry_token(plan_rules,plan_audit), migrated on startup throughAddStartupMigrations(moduleRegistry.TokenService) from the SQL embedded inStellaOps.Registry.Token.Persistence. The physicalplan_rules.allowlistcolumn is reserved and never consulted for authorization. - registry-web owns schema
registry(repositories, tags, manifests, blobs, manifest references, upload sessions, GC audit), migrated on startup fromStellaOps.Registry.Persistence. - On an empty durable store, the first-boot catalogue importer copies the shipped plan catalogue once and records a marker. Later file edits never overwrite operator-managed rows, and token authorization never falls back from PostgreSQL to YAML.
Observability
- Meter
StellaOps.Registry.TokenService:registry_token_issued_total(tagplan) andregistry_token_rejected_total(tagreason, for examplelicense_revoked,plan_unknown,plan_disabled,scope_not_permitted,anonymous_disabled). - Token service audit events go to Timeline through
AddAuditEmission; plan changes are recorded inplan_audit. There is no per-token issuance audit table. - registry-web Doctor check
doctor.registry.retention.upload-session-backlogand the owned-state measurement described in content-storage-measurement.
Not implemented
rateLimit(maxRequests,windowSeconds) is stored with plan rules but not enforced atGET /token.
Related docs
- Architecture - both roles, token exchange, plan evaluation, data plane, facade, GC, and the Evidence table.
- Operations: token service - plan catalogue, key management, enablement and verification.
- Registry topology - every registry-shaped surface and its role.
- Content storage measurement.
