--- status: accepted 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.