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
|
# 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
|
## What is being investigated
|
||||||
|
|
||||||
Whether the catalogue's missing structure is a **graph** — modules declaring what they need,
|
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-substrate` | 1 | the four pinned services, as declarations |
|
||||||
| `novox/mesh-control` | 2 | the control plane and its contexts |
|
| `novox/mesh-control` | 2 | the control plane and its contexts |
|
||||||
| `novox/mesh-surfaces` | 3 | tools, web, cli — thin, no logic |
|
| `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/mesh-lab` | — | the lab: scenario lifecycle, networking, placement |
|
||||||
| `novox/hq` | — | this repository. Company-scoped ([ADR 0028](0028-hq-is-company-scoped.md)) |
|
| `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
|
- **"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
|
against each artifact does it, and that record becomes load-bearing: wrong, and the mesh either
|
||||||
rebuilds forever or never rebuilds at all.
|
rebuilds forever or never rebuilds at all.
|
||||||
- **Rebuild storms.** One change to a shared library makes everything out of date at once. The
|
- **Rebuild storms are real and mostly behaviourally empty.** One shared-library commit
|
||||||
ordering that today's *levels* provide has to come from the module graph
|
invalidates nearly everything, and most of those rebuilds produce artifacts that do the same
|
||||||
([research 011](../01-RESEARCH/011-the-module-graph/00-overview.md)), which is designed and not
|
thing they did before — so **the fleet is redeployed for no change in behaviour.** Reproducible
|
||||||
built.
|
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
|
- **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
|
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
|
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
|
the fault this record is removing, reintroduced in a new place. **This is the real risk and it
|
||||||
is not solved here.**
|
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
|
## Consequences
|
||||||
|
|
||||||
- **Detection stops being correctness and becomes latency.** The specific faults the as-is
|
- **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