Fixing builder's hand-faked package-registry binding tonight (requires: package-registry, a real mesh grant instead of a hardcoded JSON fragment) broke three tests describing a deliberate carried-binding fallback for exactly this: gitea's own image is built by builder, so builder cannot yet hold a real grant from gitea the first time either has to exist. Invisible on novox (already bootstrapped, gitea already live) — real on any genesis from scratch. Fix left in place, tests left failing rather than reverted or hacked, so the gap stays visible.
84 lines
4.8 KiB
Markdown
84 lines
4.8 KiB
Markdown
---
|
|
status: located
|
|
opened: 2026-09-25
|
|
located-in: [mesh-catalog modules/builder, mesh-catalog modules/gitea]
|
|
fixed-by:
|
|
amended-design:
|
|
---
|
|
|
|
# 117 — `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/novox/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 novox 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 novox 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 novox — with the three tests above left failing rather than
|
|
reverted or hacked to pass, so the gap stays visible rather than quietly patched over.
|