Files
hq/02-DECISIONS/0065-the-core-library-is-the-meshs-domain.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

4.5 KiB

status, date, deciders, reconstructed, extends
status date deciders reconstructed extends
proposed 2026-08-28 jochen false 0064-a-build-edge-is-a-third-kind.md

65. The core library is the mesh's domain, and types ship with their modules

Context

ADR 0030 describes the shared library as "contracts shared across tiers: types, not behaviour" — a guard against what the current one became, which is a package holding too much code and, with it, everybody's dependencies.

The guard is aimed at the wrong thing. Types, not behaviour sounds safe and does not address the fault: a library everything depends on is a hub whether it holds types or code. The fan-in is what makes a change expensive, and types not behaviour leaves the fan-in exactly where it was.

ADR 0064 makes this measurable rather than a matter of taste: a module's cost is its inbound build edges, and a type hub has as many as a code hub.

Decision

Types ship with the module they belong to

A type is part of a module's contract, so it travels with the module. A consumer needing inventory's types depends on inventory — a real edge, narrow and visible — instead of both depending on a hub where the relationship cannot be seen.

This trades one wide edge for several narrow ones, and that is the improvement. Under ADR 0064 a change to one module's types now invalidates its actual consumers, rather than everything that touched the hub.

The core library holds the mesh's own domain, and that is the test

Not shared code. A drawer labelled shared is a drawer everything goes in, which is how the current one grew.

The core library holds what is true of the mesh regardless of which context you are in.

Research 011 already found what that is: a module, a node, and an assignment. Those three are the mesh's domain — every context speaks about them, and none of them belongs to one context.

The test, applied to a candidate: 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 — that is delivery's. A grant does not — that is provisioning's.

Domain-driven is the point rather than the label. The current library is what happens when the organising idea is who else might want this: the answer is always yes, so everything is admitted. Is this the mesh's domain has a defensible no.

Why this stays small on its own

A domain model changes when what the mesh is changes, which is rare. A shared-code drawer changes whenever anybody writes something reusable, which is constantly. So the core library inherits the property the graph is supposed to reveal — few changes, many dependants — rather than fighting for it.

Consequences

  • ADR 0030's description of the shared library is superseded. Types, not behaviour is replaced by the mesh's domain, and its types move to the modules that own them. The rest of 0030 — the naming rule and the repository list — stands.
  • A module now publishes its own contract, which it does not do today. That is real work and it is the same work as making a module a self-contained artifact, so it is not additional.
  • The check is not the one that was proposed and is better. The build output contains no runtime code would have enforced types, not behaviour — a rule now withdrawn. What replaces it is inbound build edges, which is a measurement rather than a prohibition: the core library should have many, and anything else acquiring many is turning into a hub. That is visible while it happens rather than after.
  • Fewer things will be shared, and some code will be written twice. That is the trade and it should be said plainly: the current library exists because sharing felt free. Under this it has a name, an owner and a visible edge, and two similar functions in two modules is often the better answer — how-we-build §8 already says three similar lines beat a premature abstraction.
  • Nothing here says how a module publishes its types, and the answer differs per language. That belongs with delivery.

References