Reconcile: adopt initialization's consolidated HQ as canonical, re-home this session's new work #24
@@ -17,6 +17,14 @@ touches:
|
||||
|
||||
# 011 — The module graph
|
||||
|
||||
> **A third edge was found after this graduated.** This effort established *presence* and
|
||||
> *instantiation*, and both are **runtime** edges — they answer *what does this need in order to
|
||||
> run*. Delivery needs a different question answered — *what has to be rebuilt when this changes*
|
||||
> — and that is a **build** edge, fixed inside an artifact rather than negotiated when it runs.
|
||||
> Recorded by [ADR 0064](../../02-DECISIONS/0064-a-build-edge-is-a-third-kind.md), which also
|
||||
> notes what this effort's three entities turn out to be good for
|
||||
> ([ADR 0065](../../02-DECISIONS/0065-the-core-library-is-the-meshs-domain.md)).
|
||||
|
||||
## What is being investigated
|
||||
|
||||
Whether the catalogue's missing structure is a **graph** — modules declaring what they need,
|
||||
|
||||
@@ -55,7 +55,7 @@ the rule connecting them is stated here.
|
||||
| `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 |
|
||||
| `novox/mesh-sdk` | — | ~~contracts shared across tiers: types, not behaviour~~ → **the mesh's own domain** ([ADR 0065](0065-the-core-library-is-the-meshs-domain.md)) |
|
||||
| `novox/mesh-lab` | — | the lab: scenario lifecycle, networking, placement |
|
||||
| `novox/hq` | — | this repository. Company-scoped ([ADR 0028](0028-hq-is-company-scoped.md)) |
|
||||
|
||||
|
||||
@@ -87,10 +87,12 @@ examined.
|
||||
- **"Is this artifact current?" must be answerable without building it.** A commit recorded
|
||||
against each artifact does it, and that record becomes load-bearing: wrong, and the mesh either
|
||||
rebuilds forever or never rebuilds at all.
|
||||
- **Rebuild storms.** One change to a shared library makes everything out of date at once. The
|
||||
ordering that today's *levels* provide has to come from the module graph
|
||||
([research 011](../01-RESEARCH/011-the-module-graph/00-overview.md)), which is designed and not
|
||||
built.
|
||||
- **Rebuild storms are real and mostly behaviourally empty.** One shared-library commit
|
||||
invalidates nearly everything, and most of those rebuilds produce artifacts that do the same
|
||||
thing they did before — so **the fleet is redeployed for no change in behaviour.** Reproducible
|
||||
builds would stop the cascade at the first module whose output did not move; without them, the
|
||||
storm is in the declarations rather than the builds
|
||||
([ADR 0064](0064-a-build-edge-is-a-third-kind.md)).
|
||||
- **The run identity people actually use is lost.** *Did my change go out?* is answerable today
|
||||
by opening a pipeline. With convergence there is no run to open, and **something has to replace
|
||||
that** — a query over the two comparisons above — or this will be worse to live with than what
|
||||
@@ -101,6 +103,18 @@ examined.
|
||||
the fault this record is removing, reintroduced in a new place. **This is the real risk and it
|
||||
is not solved here.**
|
||||
|
||||
## What must exist first
|
||||
|
||||
Stated as a list because this record cannot be implemented without them, and saying so is better
|
||||
than discovering it:
|
||||
|
||||
1. **The module graph, including build edges** ([ADR 0064](0064-a-build-edge-is-a-third-kind.md)).
|
||||
Designed, not built. Without it there is no rebuild set and no ordering.
|
||||
2. **A recorded input closure per artifact** — its commit and the identity of everything it was
|
||||
built against — so *is this current?* is answerable without building.
|
||||
3. **Something that notices a reconciler is not converging.** Below, and the one that is a risk
|
||||
rather than a cost.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Detection stops being correctness and becomes latency.** The specific faults the as-is
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
status: proposed
|
||||
date: 2026-08-28
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 0044-a-module-declares-presence-instantiation-and-exclusion.md
|
||||
---
|
||||
|
||||
# 64. A build edge is a third kind, and it is the one delivery runs on
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0044](0044-a-module-declares-presence-instantiation-and-exclusion.md) settled what a module
|
||||
declares, and [research 011](../01-RESEARCH/011-the-module-graph/00-overview.md) established the
|
||||
edges: **presence** — the thing exists and is reachable — and **instantiation** — the provider is
|
||||
asked to make something for this consumer and hands back credentials.
|
||||
|
||||
Both are **runtime** edges. They answer *the board needs a database from PostgreSQL.*
|
||||
|
||||
[ADR 0063](0063-delivery-is-reconciliation-not-a-pipeline.md) needs a different question
|
||||
answered: *what has to be rebuilt when this changes?* And that is not something either edge can
|
||||
say. The board being compiled against a shared library is not presence and not instantiation —
|
||||
nothing is provisioned, nothing hands back a credential, and the relationship is fixed inside the
|
||||
artifact rather than negotiated when it runs.
|
||||
|
||||
**So the graph as designed cannot drive delivery**, and finding that out is what stopped ADR 0063
|
||||
being approved as written.
|
||||
|
||||
## Decision
|
||||
|
||||
**A build edge is a third kind: this artifact was compiled against that one.**
|
||||
|
||||
It differs from the runtime edges in the way that matters, which is why it cannot be folded into
|
||||
them:
|
||||
|
||||
| | runtime edges | **build edge** |
|
||||
|---|---|---|
|
||||
| when it is satisfied | at provisioning, and again whenever it must be | **at build, once** |
|
||||
| what it binds | a consumer to a provider that is running | **an artifact to another artifact** |
|
||||
| how it is repaired | re-provision, re-grant | **rebuild — there is no other remedy** |
|
||||
| what changes it | the mesh's decisions | **somebody's commit** |
|
||||
|
||||
### An artifact's currency is its whole input closure
|
||||
|
||||
The correction this forces on [ADR 0063](0063-delivery-is-reconciliation-not-a-pipeline.md),
|
||||
which said an artifact is stale when its source moved. That is half of it.
|
||||
|
||||
> **An artifact is out of date when its source moved, or when anything it was built against
|
||||
> moved.**
|
||||
|
||||
So what is recorded against an artifact is not a commit. It is a commit **and the identity of
|
||||
every artifact it was built against** — which is what makes *is this current?* answerable without
|
||||
building, and what makes the cascade computable.
|
||||
|
||||
### It is derived, not declared
|
||||
|
||||
**Nobody writes a build edge in a manifest.** It is read from what the module actually imports,
|
||||
the way a package manager reads a lock file — because a declared list and the imports it
|
||||
describes drift, and the imports are the ones that are true. That is
|
||||
`how-we-build`'s *features are detected, not declared*, applied to dependencies.
|
||||
|
||||
**The runtime edges stay declared**, and the asymmetry is not an inconsistency: a runtime edge is
|
||||
an intention somebody has about how the mesh should be wired, and nothing but a person can state
|
||||
it. A build edge is a fact about code that already exists.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **The rebuild set becomes computable**, which is what
|
||||
[ADR 0063](0063-delivery-is-reconciliation-not-a-pipeline.md) assumed and did not have: change
|
||||
a module, take its transitive inbound build edges, and that is what is stale. In order, because
|
||||
the edges are directed.
|
||||
- **The graph measures design quality, not just build order.** A module with many inbound build
|
||||
edges is one whose every change is expensive — and *that is a fact about the design, readable
|
||||
before anything is built.* The current shared library is exactly this and nobody could see it,
|
||||
because nothing drew the edges.
|
||||
- **Fan-in becomes a thing that can be watched.** A module acquiring inbound build edges over
|
||||
time is one turning into a hub, and it is visible while it is happening rather than after.
|
||||
- **Reproducible builds would be worth much more than they look.** If rebuilding unchanged source
|
||||
against unchanged inputs produced the same digest, a cascade would stop at the first module
|
||||
whose output did not change. Without that, one shared-library commit redeploys everything
|
||||
behaviourally unchanged. **Not solved here**, and it is the difference between a cascade and a
|
||||
storm.
|
||||
- **Reading the edges needs a language-aware tool per language**, which is a real cost and the
|
||||
reason declaring them looks tempting. It is still wrong for the reason above.
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0044](0044-a-module-declares-presence-instantiation-and-exclusion.md) — the two runtime
|
||||
edges this joins.
|
||||
- [ADR 0063](0063-delivery-is-reconciliation-not-a-pipeline.md) — what needs this to work.
|
||||
- [Research 011](../01-RESEARCH/011-the-module-graph/00-overview.md) — the graph this extends.
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
status: proposed
|
||||
date: 2026-08-28
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 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](0030-the-repository-structure.md) 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](0064-a-build-edge-is-a-third-kind.md) 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](0064-a-build-edge-is-a-third-kind.md) 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](../01-RESEARCH/011-the-module-graph/00-overview.md) 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](0030-the-repository-structure.md)'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
|
||||
|
||||
- [ADR 0030](0030-the-repository-structure.md) — the description this replaces.
|
||||
- [ADR 0064](0064-a-build-edge-is-a-third-kind.md) — what makes fan-in measurable.
|
||||
- [Research 011](../01-RESEARCH/011-the-module-graph/00-overview.md) — module, node, assignment.
|
||||
Reference in New Issue
Block a user