Files
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

4.9 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
mesh-controller internal/catalogue/resolve.go

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.