Files
hq/04-ISSUES/121-builders-real-package-registry-grant-deadlocks-genesis/00-report.md
T
jochen 9766a3afce Issue 121 diagnosed: the seats work renamed the requirement, not the order
Asked first whether the seats change fixed this in passing, since it landed the same day and
touches both manifests the report names. It did not: the seat's holder is consulted only where
several nodes provide the thing, and with none providing it resolution refuses outright. Genesis
has no exemption — the unchecked first pass exists to learn what each node offers, and a
declaration is never built from it.

Records the part that did change: the three tests left failing on purpose were deleted by the
controller's seats PR and replaced with passing seat-based ones, so the gap is invisible again.
Adds the resolver to located-in, since that is where the refusal is.
2026-09-26 14:47:45 +02:00

84 lines
4.9 KiB
Markdown

---
status: located
opened: 2026-09-25
located-in: [mesh-catalog modules/builder, mesh-catalog modules/gitea, mesh-controller internal/catalogue/resolve.go]
fixed-by:
amended-design:
---
# 121 — `builder` requiring a real `package-registry` grant deadlocks a genesis bootstrap
## What was observed
On the control-node, 2026-09-25, fixing `builder`'s hand-faked `package-registry` binding (a
hardcoded JSON fragment standing in for a real grant, found live-blocking nothing but carrying no
correctness at all — see the migration log's account of the same night). The fix declared
`requires: ["artifact-store", "package-registry"]` on `builder` and let the mesh mint the grant
properly, mirroring how `artifact-store` was already required.
Running `mesh-controller`'s own test suite against the catalogue as changed surfaced three tests
that pinned a different, deliberate design:
```
--- FAIL: TestTheBuildersCarriedPackageBindingTakesThePortFromTheNode
--- FAIL: TestTheBuildersCarriedBindingStartsWhereTheForgeServes
--- FAIL: TestTheBuildersPackageBindingKeepsItsIdentity
```
Read together with their own comment in `foundation_manifests_test.go`:
> The builder carries a binding because at genesis nothing provides `package-registry` to resolve
> one from; the day the forge is a module, the same consumer is told what the forge serves.
The tests expect `builder` to carry its own `package-binding` resource — a `merge: json` file with
`protected: [provision, from, at, as]`, settable only on `port` via the ordinary settings layers —
matching defaults (`port: 3000`, `scheme: http`, `npm-path: /api/packages/<owner>/npm/`, `as:
mesh-builder`, `from: gitea`) that agree with what `gitea`'s own `serves.package-registry` declares.
Tonight's fix removed that resource entirely.
**The dependency this protects against is real, not hypothetical.** `gitea`'s own `module.json`
carries a `build` section — its runtime image is compiled from a Dockerfile via `mesh-tools`'s
build/runtime bases, the same as every other built module. `builder` is what runs that build. So:
- `builder` now `requires: package-registry`, satisfiable only by `gitea`.
- `gitea` cannot run — cannot exist as a container at all — until `builder` has built its image.
On the control-node tonight this is invisible: the mesh is already running, `gitea` is already built and
assigned, and the grant resolves immediately. A genesis bootstrap — a new mesh raised from nothing,
or that node fully re-raised for disaster recovery — hits the order the tests describe: `builder` is
needed to build `gitea`'s image before `gitea` can be assigned, so `builder` cannot yet hold a real
`package-registry` grant, so (as tonight's fix has it) `builder` cannot start.
## Why it matters beyond this instance
This is the same shape ADR 0075 already named for the mesh as a whole ("the bootstrap still has no
package registry... that is the same pivot as everything else and it is not solved here") — but it
now has a concrete second collision: the one module capable of building a package-registry
provider's own image would refuse to run before that provider exists, precisely because it was
made to depend on it correctly.
It is also a near miss worth writing down for its own reason: the fix that broke this was reviewed
against `go build`, the full test suite (which flagged it immediately — three tests, not zero), and
matched an explicitly-stated target design in `22-the-work-ahead.md` written a week earlier. Nothing
about the fix was careless; the deadlock was invisible because the node it ran against had already
crossed the point where it would bite.
## What is not decided here
- Whether `builder` should carry both — its own default/fallback binding for when no real grant can
be resolved yet, and a real `requires: package-registry` for when one can — and if so, what
decides which one is live at a given moment. Nothing in this mesh's `requires`/`provides`
resolution currently expresses "this, or a carried fallback if it cannot resolve" for any
provision; this would be the first.
- Whether the fallback belongs on `builder` specifically (matching what the removed tests already
encoded) or is a shape the mesh should offer any module bootstrapping a provider it also depends
on — the "builder builds the thing it needs to ask a favour of" pattern is not unique to
`package-registry` and could recur.
- Whether genesis itself should sequence around this instead (build gitea before granting anything,
with `builder` never resolving `package-registry` during that specific window) rather than the
manifest carrying two shapes of one credential.
Tonight's `builder` fix (`mesh-catalog modules/builder/module.json`) is left in place — correct for
the steady state, live and working on the control-node — with the three tests above left failing rather than
reverted or hacked to pass, so the gap stays visible rather than quietly patched over.