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.
84 lines
4.9 KiB
Markdown
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.
|