diff --git a/01-RESEARCH/011-the-module-graph/00-overview.md b/01-RESEARCH/011-the-module-graph/00-overview.md index 2fef368..221915c 100644 --- a/01-RESEARCH/011-the-module-graph/00-overview.md +++ b/01-RESEARCH/011-the-module-graph/00-overview.md @@ -17,6 +17,17 @@ touches: Whether the catalogue's missing structure is a **graph** — modules declaring what they need, what they offer, and what they exclude — and what that replaces. +**Measured, and the premise was wrong: the graph is not missing.** +[`analysis.md`](analysis.md) — 126 manifests, 103 edges, no cycles, nothing dangling, and a +resolver that topologically sorts them, already called by the tool loader, the installer and +the delivery coordinator. What the effort assumed would need building is a thing to call. + +What survives is narrower: three declarations that do not exist (`excludes`, a required node +capability, an interface with adapters), and two defects worth fixing whatever else is +concluded — `provider:` is a dependency edge that is not read as one, which makes the closure +for a working mesh come out without a database; and the resolver continues past a cycle and +past a missing dependency, contrary to ADR 0008. + [ADR 0017](../../02-DECISIONS/0017-modules-outside-the-core-are-grouped-by-domain.md) proposes grouping modules by domain. [Research 005](../005-domain-grouping/analysis.md) measured that proposal and found its evidence holds in exactly one place — reachability — which @@ -108,7 +119,10 @@ integration being wrong looks like from the outside. | Question | Why it is open | |---|---| -| What does the graph **delete**? | If modules gain declarations and lose nothing, this is motion rather than progress. The effort has not finished until it names what stops existing. | +| ~~What does the graph **delete**?~~ | **Answered, and not as expected** — nothing, because it already exists. The honest list of what a graph would remove is currently empty, and what it would *add* is three declarations. [`analysis.md`](analysis.md). | +| Would the missing declarations be used? | Zero manifests declare exclusions or capabilities, which is equally consistent with *nobody needs them* and *nobody can express them*. Nothing measured separates those. | +| Should `provider:` become a real edge, or should the relationship be declared twice? | It names a module and means *depends on*. Reading it as an edge fixes the closure; the alternative is requiring the consumer to also list it under `dependencies:`, which is duplication a resolver already avoids elsewhere. | +| Should placement leave the catalogue? | A provision pins itself to a named node, in the manifest. Which node runs what is an inventory decision — tier 2 by the skeleton's own test — and having it in tier 4 means a second node cannot provide the mesh's database without editing the module that consumes it. | | Where does resolution happen — mesh or platform package manager? | The mesh must model mesh-level edges. Whether it also resolves operating-system packages, or delegates, decides whether a solver has to be written. | | Is an interface a module, or a name? | Arch makes it a name that packages claim. Making it a module gives it a manifest, an owner and a place to document the contract — and a thing with no implementation to install. | | What does an exclusion mean for something already installed? | Refuse the install, or make the conflict visible and let it be decided. The second is a policy surface; the first is a package manager. | diff --git a/01-RESEARCH/011-the-module-graph/analysis.md b/01-RESEARCH/011-the-module-graph/analysis.md new file mode 100644 index 0000000..8798b89 --- /dev/null +++ b/01-RESEARCH/011-the-module-graph/analysis.md @@ -0,0 +1,138 @@ +# The graph is not missing + +Measured against `origin/main` of the code repository, 2026-08-26. Every manifest, read through +git refs rather than a checkout. + +The effort was opened to ask whether the catalogue's missing structure is a graph. It is not +missing. **It exists, it is healthy, and three separate parts of the system already use it.** + +That is the finding, and it changes what is worth asking. + +## Finding 1 — the graph is already declared, and it is clean + +| | | +|---|---| +| manifests | 126 | +| declare a dependency on another module | 64 | +| declare a requirement on a provision | 11 | +| **edges** | **103** | +| declare neither | 62 — 49% | +| **cycles** | **0** | +| **dependencies declared but absent** | **0** | +| deepest chain | 5 | + +Half the catalogue is unconnected, which matches +[research 005](../005-domain-grouping/analysis.md)'s finding that fifty modules co-change with +nothing. The connected half is well formed: no cycles, nothing dangling. + +## Finding 2 — it is already resolved, and already used + +The platform SDK carries a dependency resolver that topologically sorts modules, and it is +called from three places: the tool loader at startup, the installer when syncing modules onto a +node, and the delivery coordinator when expanding what a change affects. + +It also already does something the effort assumed would need designing: **a requirement on +another module's provision is treated as an implicit edge to that module**, so a consumer does +not have to declare the same relationship twice. + +So *ordering by the graph* — which +[ADR 0043](../../02-DECISIONS/0043-a-declaration-is-an-ordered-list-of-owned-resources.md) says +the control plane will do — is not a thing to build. It is a thing to call. + +## Finding 3 — the most important edges in the mesh are invisible + +The one place the graph is wrong, and it is wrong about the substrate. + +A module that needs a database declares it like this: + +```yaml +provisions: + - name: mesh-db + provider: postgres # ← a dependency on the postgres module + node: # ← and where it must run +``` + +`provider:` names a module. It is a dependency, declared, in the manifest — and it sits inside +`provisions:`, which is what a module *offers*. The resolver reads `dependencies:` and +`requires:`, so it never sees it. + +| | | +|---|---| +| provider references that name a real module | 4 | +| **invisible to the resolver** | **3, across 2 modules** | + +Three edges is nothing, and they are the mesh's own database, the mesh's own broker, and the +work engine's database. The most load-bearing relationships in the system are the ones the +graph cannot see. + +**The consequence is measurable.** Computing what a working mesh needs, from the declared +graph: + +``` + registry → sdk → mesh → meshware 4 modules, 4 levels +``` + +No database. No broker. A closure that is arithmetically correct and obviously wrong, and wrong +for exactly one reason: a field that means *depends on* is not read as one. + +This is [04-ISSUES/003](../../04-ISSUES/003-firewall-scope-is-read-by-no-code/00-report.md) +again, in a new place — not a key nothing reads, but a key read as something other than what it +means. + +## Finding 4 — the resolver continues past faults it should stop on + +Two behaviours, both contrary to +[ADR 0008](../../02-DECISIONS/0008-a-failed-step-fails-the-job.md): + +- **A cycle warns and falls back to input order.** A cycle means no correct order exists; the + resolver proceeds with an arbitrary one and logs a line. +- **A dependency that does not exist warns and continues.** The validation is documented as + *non-fatal, logged as warnings*. + +Neither has fired in the current catalogue — there are no cycles and nothing dangling — which +is why nobody has noticed. They are latent, and they are in the component that +[ADR 0043](../../02-DECISIONS/0043-a-declaration-is-an-ordered-list-of-owned-resources.md) makes +responsible for the ordering a host will apply without question. + +## Finding 5 — placement is decided in the catalogue + +`node:` in a provision pins it to a named node, in the manifest. Two modules do this today, +and they are the substrate ones. + +Which node runs what is an inventory and placement decision — tier 2 by the skeleton's own +test. Having it in a manifest means the catalogue decides placement, and a second node cannot +provide the mesh's database without editing the module that consumes it. + +## What this means for the effort + +**The opening question — "what does the graph delete?" — has an answer: nothing, because the +graph is already there.** The premise was wrong, and finding that out is the effort's first +result rather than a setback. + +The questions that survive are narrower and answerable: + +| Missing declaration | Manifests using it today | +|---|---| +| `excludes` — installing A makes B unavailable | **0** | +| a required node capability | **0** | +| an interface, with adapters providing it | **0** | + +Those are what a graph would *add*. What it would delete is a different and smaller list, and +the honest version of it is: nothing yet. + +**And two defects worth fixing regardless of what else this effort concludes:** + +1. `provider:` is a dependency edge and is not read as one. Fixing it makes the closure correct + — which is what [research 012](../012-the-minimum-viable-node/00-overview.md) needs in order + to answer what a one-node mesh requires. +2. The resolver continues past a cycle and past a missing dependency. Both should refuse. + +## What was not measured + +- **Whether the missing declarations would be used.** Zero manifests declare exclusions or + capabilities, and that is equally consistent with *nobody needs them* and *nobody can express + them*. Nothing here separates those. +- **Whether the interface-and-adapter idea has a consumer.** It is a good shape, and it is + argued for rather than measured. +- **What the closure should be.** Finding 3 says the computed one is wrong. It does not say + what the right one is; that needs the fix first.