diff --git a/01-RESEARCH/011-the-module-graph/00-overview.md b/01-RESEARCH/011-the-module-graph/00-overview.md index c5f629a..87a682d 100644 --- a/01-RESEARCH/011-the-module-graph/00-overview.md +++ b/01-RESEARCH/011-the-module-graph/00-overview.md @@ -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, diff --git a/02-DECISIONS/0030-the-repository-structure.md b/02-DECISIONS/0030-the-repository-structure.md index 4dca3ae..54e4543 100644 --- a/02-DECISIONS/0030-the-repository-structure.md +++ b/02-DECISIONS/0030-the-repository-structure.md @@ -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)) | diff --git a/02-DECISIONS/0063-delivery-is-reconciliation-not-a-pipeline.md b/02-DECISIONS/0063-delivery-is-reconciliation-not-a-pipeline.md index 16e78cb..86b8654 100644 --- a/02-DECISIONS/0063-delivery-is-reconciliation-not-a-pipeline.md +++ b/02-DECISIONS/0063-delivery-is-reconciliation-not-a-pipeline.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 diff --git a/02-DECISIONS/0064-a-build-edge-is-a-third-kind.md b/02-DECISIONS/0064-a-build-edge-is-a-third-kind.md new file mode 100644 index 0000000..844cd9b --- /dev/null +++ b/02-DECISIONS/0064-a-build-edge-is-a-third-kind.md @@ -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. diff --git a/02-DECISIONS/0065-the-core-library-is-the-meshs-domain.md b/02-DECISIONS/0065-the-core-library-is-the-meshs-domain.md new file mode 100644 index 0000000..13a98d4 --- /dev/null +++ b/02-DECISIONS/0065-the-core-library-is-the-meshs-domain.md @@ -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.