Bare mesh-builder@sha256:... is only resolvable for a module with a build
section — the mesh's own build step rewrites the reference to a real
registry path as part of resolving build.artifacts. builder is handed
over, not built, so nothing ever rewrites it: pushed as written, docker
read it literally and tried Docker Hub. Took the live node's mesh-builder
down for the length of one push-and-fix (docker: pull access denied for
mesh-builder, repository does not exist). novox.internal:5100, not the
literal external IP docker inspect showed live, for the same reason
addresses generally don't get hardcoded in this catalogue.
The 'package-binding' resource was a hardcoded JSON fragment standing in
for a real grant — {"provision": "package-registry", "from": "gitea",
"at": "127.0.0.1", ...} written as if it were mesh-resolved, when nothing
resolved it. Declares requires: package-registry properly instead, with
binds/secrets pointing at the same file paths the resource used to
manually author, so the mesh mints the grant and writes it there.
npm-password renamed to package-registry.secret: it's gitea's generic
user+password, not npm-specific — the same credential works for basic
auth against cargo/PyPI/Go package endpoints too, once gitea's manifest
grows them (novox/hq ADR 0109).
Known gap, not fixed here (novox/hq issue 117): this makes builder
correct for the steady state but breaks a genesis bootstrap — gitea's own
image is built by builder, so builder cannot yet hold this grant the
first time either has to exist. Filed rather than silently accepted.
`at` was settable. A node setting could point the builder's package binding at
any host, and the builder sends its registry credential there as basic auth — so
a setting meant for a port was a way to hand the password to somebody else.
novox/hq 04-ISSUES/085
The forge is raised by hand at genesis, before any module provides
`package-registry`, so the builder carries a binding instead of resolving one —
and the port in it was rewritten, as text, by the installer. A later
registration of this manifest from the catalogue put 3000 back, silently, and
pointed the builder and its registry credential at whatever holds that port.
Made settable instead: the port is a per-node setting the controller holds, and
the catalogue's number is only its default. `provision`, `from` and `as` are
protected — a setting here is about where the forge answers, never about who the
binding is with.
novox/hq 04-ISSUES/085, ADR 0100
The copy between registries (ADR 0096) reaches the mesh's registry over HTTP from
inside the builder's container, where loopback on the default bridge is not the
machine; docker push never noticed because it went through the machine's daemon.
The builder already holds the runtime's socket, so the host network adds nothing
it did not have.
The one-line change e0c9219 parked "until there is a certificate" returns — with the
overlay recorded as the registry's transport security and every node's runtime told the
store speaks plain HTTP, a reference under the provider's internal name is one every
machine can pull. References minted at genesis stay loopback and are valid where they
matter, on the machine that made them.
https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
A docker run -v from inside the builder resolves the path on the host: with the
workspace mounted at a different path inside than out, the SDK publish and any
bundle compile mounted an empty directory. Bind it at the same path both sides.
Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
gitea gains the package-registry provision: serves/receives/grants, an admin
own-secret, a postgres-shaped build, and a provisioner that creates a gitea user
per consumer with the mesh-minted password and seals nothing (hq ADR 0048). The
builder takes its registry credential as an own-secret rather than a resolved
provision, because gitea-as-module needs the base to build its provisioner and so
cannot resolve before the base — a cycle the own-secret avoids.
Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
Naming it from the binding was right and arrived too early. The moment the
machine had a name, the builder pushed to <node>.internal:5000 and the runtime
refused it: "http: server gave HTTP response to HTTPS client". The registry
serves plaintext, and anything that is not loopback is required to be HTTPS.
So there are two phases, and this is the first. Before the mesh has a certificate
authority of its own, loopback is the only trusted path that is honest — it is
trusted because it cannot leave the machine, not because anyone checked
anything. The mesh-reachable name belongs to the second phase, with TLS from the
mesh's own CA, and the binding expression returns then.
Not a revert of the reasoning: novox/hq issue 048 stays open and this is why. The
same one-line change lands again once a certificate module is running.
Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
The builder's environment said MESH_REGISTRY=127.0.0.1:${bound:artifact-store:port}
— the port taken from the binding, the host pinned to loopback. So every artifact
the mesh builds was recorded under an address that means something only on the
machine holding the registry, and nothing else in the mesh could resolve it.
Loopback is correct for exactly one reader and the builder is not special: it
already requires artifact-store, and the binding states where the provider is on
the private network. It now uses both halves of what it was given.
Invisible with one machine, which is the only shape this had been proven in. The
registry module declares that every machine pulls from it and opens its port to
the mesh for that reason, so the reference it is handed has to be one a second
machine can use.
Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
Its account is scoped from what it emits and consumes, and it declared neither —
which is why asking for a generic module account produced one that authenticated
and could do nothing, with the refusal surfacing a layer away as a permissions
error against a queue.
Declaring the announcement is not documentation here. It is what the permission
is derived from.
Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
The code that turns a repository into artifacts already existed and is deliberately
something a machine runs as an ordinary module rather than something the control
plane does. There was no module for it, so it could never be placed and never ran —
which is why nothing in the catalogue could be produced.
It asks for a container runtime because it builds images, requires the artifact
store because it publishes into it, and claims one per machine. Its credential
folder is mounted rather than the file, since a file mount keeps pointing at the
old contents after the mesh writes new ones.
The store is reached on the machine's own loopback: the address in a binding is
where a machine sits on the private network, and the store has to be co-located
anyway because runtimes refuse a plain-HTTP registry anywhere else.
Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx