011: measured, and the premise was wrong — the graph is not missing

The effort was opened to ask whether the catalogue's missing structure is a
graph. It is not missing. 126 manifests, 103 edges, no cycles, nothing dangling,
deepest chain of five — and a resolver in the SDK that topologically sorts them,
already called by the tool loader at startup, the installer when syncing modules
onto a node, and the delivery coordinator when expanding what a change affects.

It already does something this effort assumed would need designing: a
requirement on another module's provision is treated as an implicit edge to the
module that provides it. So "ordering by the graph", which ADR 0043 makes the
control plane's job, is a thing to call rather than a thing to build.

The one place the graph is wrong, it is wrong about the substrate. A module
needing a database declares `provider: postgres` inside `provisions:` — which is
what a module OFFERS — so the resolver, which reads `dependencies:` and
`requires:`, never sees it. Three edges are invisible this way, and they are the
mesh's own database, the mesh's own broker, and the work engine's database.

The consequence is measurable: computing what a working mesh needs from the
declared graph gives registry -> sdk -> mesh -> meshware. Four modules, four
levels, no database. Arithmetically correct and obviously wrong, for exactly one
reason — a field that means "depends on" is not read as one. That is
04-ISSUES/003 in a new form: not a key nothing reads, but a key read as
something other than what it means.

Two latent defects, both contrary to ADR 0008 and both in the component ADR 0043
makes responsible for ordering a host will apply without question: a cycle warns
and falls back to input order, and a dependency that does not exist warns and
continues. Neither has fired, because the catalogue currently has no cycles and
nothing dangling, which is why nobody has noticed.

And placement is decided in 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 — so a second node cannot provide the mesh's database
without editing the module that consumes it.

What the graph would DELETE is currently nothing. What it would add is three
declarations that no manifest uses today: excludes, a required node capability,
and an interface with adapters. Whether they would be used is not measured, and
zero usage is equally consistent with nobody needing them and nobody being able
to express them.
This commit is contained in:
2026-08-26 22:35:39 +02:00
parent 278f7427ed
commit c3a2984b3e
2 changed files with 153 additions and 1 deletions
@@ -17,6 +17,17 @@ touches:
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,
what they offer, and what they exclude — and what that replaces. 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 [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 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 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 | | 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. | | 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. | | 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. | | 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. |
@@ -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: <a named 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.