Files
hq/04-ISSUES/121-builders-real-package-registry-grant-deadlocks-genesis/00-report.md
T
jochen 09e502058b Issue 121: builder's real package-registry grant deadlocks a genesis bootstrap
Renumbered from 117, which is taken on main by 'a module's own code is a container in one record
and a process in another' — two reports claimed the same number and git would not have said so.

Scrubbed the node's name and a real registry path; this repository is public.
2026-09-26 14:20:05 +02:00

4.8 KiB

status, opened, located-in, fixed-by, amended-design
status opened located-in fixed-by amended-design
located 2026-09-25
mesh-catalog modules/builder
mesh-catalog modules/gitea

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.