Issue 121: builder's real package-registry grant deadlocks a genesis bootstrap #117

Merged
jschoubben merged 3 commits from issue/117-builder-package-registry-deadlocks-genesis into main 2026-09-26 12:20:54 +00:00
@@ -0,0 +1,83 @@
---
status: located
opened: 2026-09-25
located-in: [mesh-catalog modules/builder, mesh-catalog modules/gitea]
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.