Files
hq/02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.md
jschoubben 59c93dcfe4 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.
2026-09-25 20:29:22 +02:00

6.2 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
the tiers accepted 2026-09-25 jochen false 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 — 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.