Files
hq/02-DECISIONS/0030-the-repository-structure.md
T
jschoubben 9d091c81e0 A build edge, a core library that is a domain, and 0063 corrected
Three things from walking a real dev cycle through 0063, all of which Jochen
caught by pushing on where I had glossed.

0064 -- a build edge is a third kind. Research 011 established presence and
instantiation, and both are RUNTIME edges: they answer what a module needs in
order to run. Delivery needs a different question -- what has to be rebuilt when
this changes -- and that relationship is fixed inside an artifact rather than
negotiated when it runs. So the graph as designed could not drive delivery,
which is the real reason 0063 was not approvable.

It is derived rather than declared, read from what a module actually imports,
because a declared list and the imports it describes drift and the imports are
the true ones. The runtime edges stay declared, and that asymmetry is not an
inconsistency: a runtime edge is an intention somebody has, a build edge is a
fact about code that exists.

It also makes design quality measurable. A module with many inbound build edges
is one whose every change is expensive, and the current shared library is
exactly that -- nobody could see it because nothing drew the edges.

0065 -- the core library is the mesh's domain. Jochen disagreed with 0030's
"types, not behaviour" and was right: that guard is aimed at the wrong thing. A
library everything depends on is a hub whether it holds types or code, and the
fan-in is what makes a change expensive. So types ship with the module that
owns them -- trading one wide edge for several narrow ones -- and the core
library holds what is true of the mesh regardless of context, which research
011 already found: a module, a node, an assignment.

The test is "would this still mean the same thing in a context that had never
heard of the one it came from". A node does; a pipeline stage does not.
Domain-driven is the point rather than the label: "who else might want this"
always answers yes, which is how the current one grew.

And it changes the check for the better. "The build output contains no runtime
code" would have enforced a rule now withdrawn. Inbound build edges is a
measurement rather than a prohibition, and it is visible while a hub is forming
rather than after.

0063 revised on both counts, plus a third: I had written "the lab judges it" as
though that were a step. A lab run takes tens of seconds, occupies a VM, and
fails for environmental reasons -- and a shared-library change produces dozens.
One expensive non-deterministic gate fails both ways, and neither failure looks
like itself. Verdicts are now tiered, and a run that failed environmentally is
explicitly not a verdict.

0063 also now carries what must exist before it can be implemented, rather than
leaving that to be discovered.
2026-08-28 18:25:28 +02:00

5.3 KiB

status, date, deciders, reconstructed
status date deciders reconstructed
accepted 2026-08-23 jochen false

30. The repository structure, and the rule that names them

Context

The tiers are settled (research 006) and the product is named (ADR 0027), but the repositories themselves were only ever sketched in research. Two consequences had already appeared.

ADR 0029 makes the lab phase 0 of the entire migration and could not say where it lives, because no record named a repository.

And the research contradicted an accepted record: it listed mesh-hq for this repository, while ADR 0028 had decided novox/hq and explicitly rejected that name. A design resting on research is a design resting on something that can change without a decision.

There is also an implied naming rule that has never been written down. ADR 0027 says repository names take mesh; ADR 0028 gives this repository no prefix at all. Both are right, for a reason neither states.

Considered options

Only the naming rule had genuine alternatives; the tier repositories follow from the tiers.

  1. No prefix — novox/host, novox/control. The organisation already says Novox, so the prefix reads as stutter. Rejected once it was established that Novox delivers more than the mesh: with several products the prefix is not stutter, it is the product namespace doing real work, and the forge has no nested groups to do it instead.
  2. An organisation per product — novox-mesh/host. Puts the product boundary where the forge's only real grouping primitive lives, so permissions and teams attach to it. Rejected for now as premature: no per-product access boundary exists yet, and it costs novox- repeated across every organisation.
  3. Product-prefixed repositories in the company organisation. Chosen.

Decision

The naming rule: a repository that belongs to a product carries that product's prefix. A repository that is company-scoped does not.

That is why this one is hq and the mesh's are mesh-*. Both records were already correct; the rule connecting them is stated here.

The repositories:

Repository Tier Holds
novox/mesh-host 0 the node host — the one binary installed by hand
novox/mesh-substrate 1 the four pinned services, as declarations
novox/mesh-control 2 the control plane and its contexts
novox/mesh-surfaces 3 tools, web, cli — thin, no logic
novox/mesh-sdk — contracts shared across tiers: types, not behaviour → the mesh's own domain (ADR 0065)
novox/mesh-lab — the lab: scenario lifecycle, networking, placement
novox/hq — this repository. Company-scoped (ADR 0028)

The lab is its own repository. Its lifecycle differs from everything else in the list: it is never shipped to a node, it outlives any single tier, and it drives virtualisation on a workstation — which nothing else in the mesh does. Putting it inside the host would couple development tooling to a shipped component; putting it inside the control plane would make the bootstrap scenario depend on a tier that does not exist when it is needed.

Tier 4 is deliberately not decided here. Whether the catalogue is one repository, one per domain, or one per application remains open from ADR 0015 and is blocked on research 005: how many repositories hold domains cannot be answered before what the domains are. Recording the gap is the point — mesh-catalog appears in the research sketch and is not decided by this record.

Consequences

  • ADR 0029 can name its target. Phase 0 has a home, which was the immediate blocker.
  • The research sketch stops being load-bearing. It remains what it is — a sketch — and the design layer can now cite a record instead.
  • Seven repositories where there is currently one, for a mesh that today lives in a single monorepo. That is the cost, and it is not small: seven release cadences, seven sets of dependencies, and cross-repository changes that were previously one commit. The offsetting argument is the tier rule — a boundary that only points downward is enforceable across repositories and merely conventional inside one.
  • The prefix will read as redundant for as long as the mesh is the only product with repositories. That is accepted deliberately: the alternative is renaming everything at the moment a second product appears, which is the class of migration this project is trying to stop performing.
  • Nothing is created yet. This records what the repositories are; creating them is part of phase 0 and after.

References

  • ADR 0027 — the product name the prefix comes from.
  • ADR 0028 — why this repository has no prefix.
  • ADR 0029 — the lab, and why it is first.
  • Research 006 — the tiers, and the sketch this supersedes as a source.