Files
hq/03-DESIGN/01-to-be/22-the-work-ahead.md
T
jschoubben 59c93dcfe4 109: a package registry seat is one per ecosystem, not one for all of them
Extends ADR 0075. Surfaced fixing builder's hand-faked package-registry
binding tonight: gitea's manifest declares the provision once with a single
npm-path, conflating what should be independently assignable per ecosystem
(npm/cargo/docker/...) the same way artifact-store and package-registry
were themselves split. Cited in 22-the-work-ahead.md's Phase 2, where the
target state this decision points at was already described a week ago.

Numbered 109, not 108: route-proxy's policy feature (mesh-controller PR
still-unwritten decision record — reserved but never committed. Renumbered
around it rather than colliding.
2026-09-25 20:29:22 +02:00

7.3 KiB
Raw Blame History

layer, status, code, updated, decisions
layer status code updated decisions
to-be in-progress
mesh-host
mesh-controller
mesh-catalog
mesh-lab
2026-09-15
02-DECISIONS/0067-genesis-is-a-pivot.md
02-DECISIONS/0074-the-wire-is-specified-not-the-types.md
02-DECISIONS/0075-two-stores-and-which-provides-what.md
02-DECISIONS/0014-no-npm-workspace.md
02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.md

The work ahead

Everything decided this cycle and not yet built, in the order its dependencies allow. Each phase ends at something provable on a running mesh, because a phase that ends at a claim is a phase that went missing without anything complaining.

How this is built, and when it is run

Implemented as code with unit tests, committed per change, and run in the lab ONCE the pieces that would change the outcome are in place. The mistake this corrects: repeatedly running a 20-minute lab against a mesh mid-transformation, debugging paths the next phase deletes. The base-build hang, for instance, is almost certainly the SDK resolving from a git URL inside a docker build (issue 053) — which Phase 2 removes. Debugging it on the current shape is debugging deprecated code.

So the lab run is the acceptance test at the end of the assembled work, not the tool for finding each bug. Where a fault can be reasoned out of the code path, it is — reading, not running.

Phase 0 — folded in

The installer's own regressions (stale carried builder, a diagnostic on the parsed stdout) are fixed and committed. Whether it reaches a full green run is answered by the final lab run below, after the phases that change its build path are in — not before.

Phase 1 — the protocol is one thing, and correct (mostly done: the drift was dead types)

Why here. The Go controller and the TypeScript SDK disagree about what a grant carries (consumer is the module in one, the node in the other). That is exercised by the installer's own provisioning — the catalogue's database — so it belongs before more is built on it.

  • 1.1 the drift was not live — inspection showed the wire agrees (contributions file; envelope required headers). The dead types that disagreed are removed, ADR 0074 and doc 19 corrected
  • 1.2 a conformance fixture for the two live cross-language contracts — the event envelope and the contributions file — checked in both suites, as prevention rather than repair
  • 1.3 (deferred) a full per-capability suite when a third language is actually added; not needed to keep two honest

Done when. A fixture pins the envelope and the contributions file, and a change to either side that breaks agreement fails a test rather than a mesh.

Phase 2 — the private package registry, and the SDK in it

Why here. ADR 0014 says a module consumes its dependencies, the SDK included, from the private registry. Nothing installs one, so the SDK comes from a git URL (issue 053). Needs the installer (Phase 0) and gitea.

  • 2.1 gitea provides package-registry in full — the endpoints, an account the builder may publish with
  • 2.2 the builder publishes the SDK there on build, by version
  • 2.3 modules consume it by version; the git URL and the sibling-path lock are gone
  • 2.4 a second language's SDK published the same way, proving the path is not TypeScript-only

Done when. A module builds against the SDK resolved from the mesh's own registry, and issue 053 closes.

Where it stands (2026-09-16). The decision the bootstrap turned on is settled and recorded (ADR 0076): the SDK is a published package, built on a public base image and published before the toolchain that consumes it; mesh-tools stays the thin toolchain base but npm cis the SDK by version. On the code, the builder now resolves a package-registry credential — from a binding the mesh writes or from the environment for a hand-run or bootstrap build — and injects it into an image build as a buildkit secret, never a layer, so a token is not baked into the toolchain image. Unit-tested. Still ahead: gitea serving the registry in full with a provisioner that mints tokens (2.1), the SDK built and published by the mesh (2.2), the mesh-tools manifest flipped off the git URL to npm ci by version (2.3), and the genesis step that raises gitea and publishes the SDK before the base build. Those close together in one lab run.

Phase 3 — nothing is special after installation

Why last. The hardest and riskiest, and it needs everything above: an installer that completes, a protocol that agrees, a registry to publish to. Issue 051.

  • 3.1 the store is adopted — raised at genesis, then held as the postgres module; one server, the controller's records and the database of each module that asks for one. (Adopted in place: the module's server names the same container and the same pinned upstream image the foundation runs, so the applier reconciles it rather than raising a second postgres. Proven 22/22 in the one-node lab — the catalogue, a postgres-database consumer, gets its database from it. Follow-up: the store binds 0.0.0.0 from genesis so a mesh consumer can reach it, but the packet filter is installed later — a brief pre-filter window where mesh-store is open before from: mesh clamps it; bring the filter up earlier or bind narrower at genesis.)
  • 3.2 the broker is adopted — one lavinmq, the / vhost for the mesh bus, a vhost per consumer that requires amqp; the second server gone. (Adopted in place like the store; the module names mesh-broker with the foundation's TLS spec, the provisioner runs host-networked as lavinmq's default guest, amqp-ping reaches it. Proven 22/22.)
  • 3.3 an upgrade of each, proven: a store the controller reads from, a broker over the broker, each with a stated window. (Lab steps S1/S2: move each module's source, build, roll out, then a full push applies the server change and recreates the container. The store's window is a pool reconnect; the broker's is longer — recreating the bus the push travels over, so the mesh reconnects to the one that returns. Data survives on the named volumes.)
  • 3.4 status can say the foundation is behind its source, which today it cannot form. (Given by the adoption: postgres/lavinmq are ordinary modules with a source now, so module list/status reports them behind or current like any other — the question could not form when they were bundle containers.)

Done when. A mesh built from bare metal runs one postgres and one lavinmq, and can upgrade either — so the twelve-module floor has no specialty left in it.

The through-line

Each phase leaves the mesh more able to describe and rebuild itself, and each ends at a run rather than a paragraph. The order is dependency, not preference: the installer carries everything, the protocol is what everything speaks, the registry is what everything is built from, and adoption is what makes the last two things ordinary.

The one lab run

After Phases 1–3 are implemented and unit-tested and committed: rebuild the images, verify each carries its change, and run the one-node installer once. All 22 checks green, twice, is the acceptance test for the whole cycle — not a debugging loop, a proof that the assembled thing works.