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.
107 lines
6.2 KiB
Markdown
107 lines
6.2 KiB
Markdown
---
|
|
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.
|