109: a package registry seat is one per ecosystem, not one for all of them
Extends ADR 0075. Surfaced fixing builder's hand-faked package-registry binding tonight: gitea's manifest declares the provision once with a single npm-path, conflating what should be independently assignable per ecosystem (npm/cargo/docker/...) the same way artifact-store and package-registry were themselves split. Cited in 22-the-work-ahead.md's Phase 2, where the target state this decision points at was already described a week ago. Numbered 109, not 108: route-proxy's policy feature (mesh-controller PR still-unwritten decision record — reserved but never committed. Renumbered around it rather than colliding.
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
---
|
||||
topic: the tiers
|
||||
status: accepted
|
||||
date: 2026-09-25
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 0075-two-stores-and-which-provides-what.md
|
||||
---
|
||||
|
||||
# 109. A package registry seat is one per ecosystem, not one for all of them
|
||||
|
||||
## Context
|
||||
|
||||
Fixing `builder`'s consumption of `package-registry` tonight surfaced the shape ADR 0075 actually
|
||||
left implicit. 0075 split `artifact-store` from `package-registry` and said the second is "an
|
||||
ecosystem's own registry — npm, cargo, PyPI, Go" — but it defined one provision for all four,
|
||||
not one each.
|
||||
|
||||
What that produces, read from the manifests as they stand:
|
||||
|
||||
- `gitea`'s `module.json` declares `provides: package-registry` once, and its `serves` block
|
||||
carries exactly one path: `npm-path`. Nothing names a cargo or PyPI endpoint, though gitea's own
|
||||
package API serves both.
|
||||
- `builder` had, until tonight, a hand-written JSON fragment standing in for a real grant —
|
||||
`{"provision": "package-registry", "from": "gitea", "at": "127.0.0.1", ...}` — because nothing
|
||||
in the interface gave it a real one to ask for. The fragment named `npm-path` specifically; there
|
||||
was nowhere to put a second ecosystem's endpoint even if one had been wired.
|
||||
- The fix applied tonight declares `requires: ["artifact-store", "package-registry"]` and lets the
|
||||
mesh mint the grant properly — correct for what exists today, but it is one seat standing in for
|
||||
what should be several, the same conflation 0075 itself named and did not resolve for this
|
||||
provision specifically.
|
||||
|
||||
**The version-skew problem 0075 wrote down for the artifact-store/package-registry split repeats
|
||||
one level down, inside "package registry" itself:** npm resolves by name and range from one
|
||||
namespace, cargo from another, and a single grant conflates them exactly the way one store for
|
||||
both digests and ranges would have.
|
||||
|
||||
## Considered Options
|
||||
|
||||
**1. One `package-registry` provision, gitea answers every ecosystem it can.** What exists today.
|
||||
Simplest to grant — one credential, one binding, done once per consumer. Rejected: a consumer
|
||||
that only ever needs npm still receives a grant shaped to cover cargo and PyPI, and there is no way
|
||||
to hand off *only* npm to a different provider (verdaccio, say) without renegotiating the whole
|
||||
provision for every consumer of any ecosystem.
|
||||
|
||||
**2. One provision, parameterised by ecosystem.** `requires: package-registry` plus a declared
|
||||
`ecosystem: npm` alongside it, still one interface. Rejected: the `provides`/`requires` refusal
|
||||
mechanism this mesh already uses (two providers of one provision is a naming conflict until
|
||||
resolved) would need to become conditional on a parameter it does not otherwise carry anywhere in
|
||||
the mesh's resolution — a special case for exactly one provision, rather than the mesh's existing
|
||||
mechanism applied again.
|
||||
|
||||
**3. One provision per ecosystem — `npm-package-registry`, `cargo-package-registry`,
|
||||
`docker-package-registry`, and so on, each independently `provides`/`requires`.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
**A package registry seat is one per ecosystem.** `npm-package-registry`, `cargo-package-registry`,
|
||||
`docker-package-registry` — each its own provision, resolved, granted, and refused exactly the way
|
||||
`artifact-store` and today's single `package-registry` already are. Adding an ecosystem is adding a
|
||||
provision, not widening one.
|
||||
|
||||
**Gitea may hold several seats at once.** Nothing here says gitea answers only one; ADR 0075
|
||||
already established that a provider may answer more than one named thing on one machine ("a mesh
|
||||
running gitea for git and packages alongside a registry serving artifacts is an ordinary
|
||||
arrangement"). Gitea fulfilling `npm-package-registry` and `cargo-package-registry` both is the
|
||||
expected shape, not an exception.
|
||||
|
||||
**Each seat's grant is independent.** A consumer that only needs npm holds only the
|
||||
`npm-package-registry` grant. Moving that one ecosystem to a different provider — verdaccio,
|
||||
named directly as the motivating case — means assigning `npm-package-registry` to verdaccio and
|
||||
leaving every other seat exactly where it was. No consumer of `cargo-package-registry` observes
|
||||
the change; no manifest naming `package-registry` broadly needs to be found and re-read.
|
||||
|
||||
**`builder`'s fix tonight is the interim shape, not the target.** It correctly consumes the one
|
||||
seat that exists today (`package-registry`, npm in practice). Splitting it becomes, later,
|
||||
replacing that one line with the ecosystems `builder` actually uses — a manifest change, not a
|
||||
redesign of how `builder` asks for anything.
|
||||
|
||||
## Consequences
|
||||
|
||||
- `gitea`'s `module.json` gains a `provides` entry per ecosystem it actually serves, each with its
|
||||
own `serves` block (`npm-path`, a cargo path, a PyPI path) in place of the one `package-registry`
|
||||
entry with a single `npm-path` inside it.
|
||||
- `gitea`'s provisioner (`modules/gitea/provisioner/index.ts`) currently runs one `runProvisioner`
|
||||
registration for `package-registry`; each seat needs its own registration, or one provisioner
|
||||
keyed by which seat's `create`/`remove` fired — the mesh's `Provision` type does not yet carry
|
||||
which named provision a call is for when a module answers more than one, and that is worth
|
||||
checking before assuming the harness already supports it.
|
||||
- Every consumer's `requires` moves from the one name to however many ecosystems it actually uses.
|
||||
`builder` is the only known consumer today; widening later is one manifest line per module, not
|
||||
a migration.
|
||||
- `verdaccio`'s role sharpens: not "package-registry, an alternative for npm alone" (0075's phrasing)
|
||||
but a named `npm-package-registry` *provider*, a straight swap against gitea's answer to the same
|
||||
seat.
|
||||
- Not solved here: whether `cargo-package-registry` and `pypi-package-registry` are needed at all
|
||||
before something actually consumes them. This record names the shape; building unused seats is
|
||||
its own decision.
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0075](0075-two-stores-and-which-provides-what.md) — the record this extends; defined
|
||||
`package-registry` as the second provision without splitting it per ecosystem.
|
||||
- `mesh-catalog modules/builder/module.json` — tonight's fix, the interim single-seat shape.
|
||||
- `mesh-catalog modules/gitea/module.json`, `modules/gitea/provisioner/index.ts` — today's
|
||||
single-provision, npm-only implementation.
|
||||
@@ -124,6 +124,7 @@ python3 00-META/checks/index.py fail if stale
|
||||
- **0095** — [The control plane is the way to ask a module](0095-the-control-plane-is-the-way-to-ask-a-module.md)
|
||||
- **0098** — [A fact a provider makes at first start is fetched from it, not carried in its manifest](0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md)
|
||||
- **0108** — [A route carries the policy applied to a request, and names a secret rather than holding one](0108-a-route-carries-the-policy-applied-to-a-request.md)
|
||||
- **0109** — [A package registry seat is one per ecosystem, not one for all of them](0109-a-package-registry-seat-is-one-per-ecosystem.md)
|
||||
|
||||
### What runs on them, and how it gets there
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ decisions:
|
||||
- 02-DECISIONS/0074-the-wire-is-specified-not-the-types.md
|
||||
- 02-DECISIONS/0075-two-stores-and-which-provides-what.md
|
||||
- 02-DECISIONS/0014-no-npm-workspace.md
|
||||
- 02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.md
|
||||
---
|
||||
|
||||
# The work ahead
|
||||
|
||||
Reference in New Issue
Block a user