Files
hq/01-RESEARCH/011-the-module-graph/00-overview.md
T
jschoubben b245f5e254 011: measure the graph against every facet a module carries
The five declarations cover relations; a module is more than its
relations. Add the facet-by-facet coverage table so the effort cannot
conclude while tools, verification and contributions are unplaced —
and weigh each candidate gap rather than adopting it: contributions
probably dissolve into declared resources, mandatory verifiers risk
trivial ones, and the tool surface is the one facet with no home.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-01 23:14:03 +02:00

13 KiB

status, initiated, touches
status initiated touches
active 2026-08-25
02-DECISIONS/0002-everything-is-a-module.md
02-DECISIONS/0017-modules-outside-the-core-are-grouped-by-domain.md
02-DECISIONS/0036-a-node-is-a-managed-machine.md
03-DESIGN/00-as-is/02-modules-and-manifests.md
03-DESIGN/00-as-is/10-module-catalogue.md
02-DECISIONS/0006-schema-changes-are-numbered-migrations.md
02-DECISIONS/0043-a-declaration-is-an-ordered-list-of-owned-resources.md
04-ISSUES/003-firewall-scope-is-read-by-no-code/00-report.md
04-ISSUES/007-an-installed-package-is-not-a-capability/00-report.md

011 — The module graph

What is being investigated

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.

ADR 0017 proposes grouping modules by domain. Research 005 measured that proposal and found its evidence holds in exactly one place — reachability — which ADR 0037 has since absorbed into the host. The measured case for domain grouping has therefore been consumed by a decision taken for unrelated reasons, and what remains is fifty modules that co-change with nothing.

That leaves the original complaint unanswered: the catalogue records what was installed rather than what anything is for, and nothing in the system can see a relationship between two modules. Grouping asserts relationships. A graph records them.

Why now

A proposal to split modules into provisioning services and applications was worked through and abandoned in favour of one concept with facets, for a reason worth keeping:

  • The split cannot be filed consistently. A git forge is consumed as a service and operated through a web interface. An analytics service grants tracking identity and is a dashboard somebody reads. An identity provider grants authentication and has an admin console.
  • The operator's correction is the sharper form: what runs on the machine is a supervised container, not something a user started. That is a fact about how a thing runs, not about what kind of thing it is — so it is a facet, not a taxonomy.

Filing decisions that follow from nothing are the disease research 005 measured. A second taxonomy would reproduce it.

So ADR 0002 survives, and the question becomes what a module must be able to declare.

The shape being investigated

Five declarations, of which two exist today.

Declaration Today Notes
requires a resource — a database, a bucket yes provisioning, ADR 0005
provides a resource yes as above
requires another module no the dependency edge — the graph's substance
excludes another module no installing A makes B unavailable
requires a node capability no a graphical session, a container runtime, an architecture

And one structural idea on top: an interface module carries no implementation. Adapters provide it. An assistant interface with several model-provider adapters; a terminal interface with several terminal adapters. A dependent names the interface and never an implementation.

Prior art to measure against, not invent past. This is a package manager's model, and the platform's own package manager already has all of it: depends is the dependency edge, conflicts is exclusion, and provides is the interface — several packages provide one virtual name, and a dependent names the virtual one. That the design arrived at the same shape independently is evidence for it. It is also a warning: dependency resolution, version constraints, conflict handling and rollback are a long-solved and easily-botched problem, and the effort should establish what to delegate rather than reimplement.

Capabilities, and what may be installed

The operator's formulation: system specs are capabilities, and capabilities unlock installable modules — you cannot install a graphical application on a node with no display server.

The question that follows is whether the mesh may install a capability. The effort's working position, to be tested:

  • Intrinsic capabilities — hardware, architecture, network position — are facts about a machine. They are detected, never installed, and a module requiring one it does not have is not unresolved but impossible on that node.
  • Provided capabilities — a display server, a container runtime — are not a separate kind of thing at all. They are modules that provide a capability, and requiring one is an ordinary edge the graph resolves by installing it.

If that holds, "may the mesh install a capability" is not a policy question. It is dependency resolution, and the only genuinely new thing is detection.

Which is where issue 007 bears directly: an installed package is not a capability. A provided capability is not real because a package is present — it is real when it is present, running and working, and the difference is exactly the class of fault this repository keeps recording.

What it touches beyond the catalogue

The operator's assessment is that the machinery around a module is wanted and its integration is not: scheduled tasks, hooks, migrations, configuration and environment settings are worth keeping; seeds are not; and the current integration is wrong enough to need a major refactor.

That is a claim to test rather than adopt. Research 005 already found supporting evidence from a different direction — that the densest apparent coupling in the catalogue is manifest boilerplate churn, cross-cutting changes to the machinery applied N times — which is what an integration being wrong looks like from the outside.

Coverage against what a module carries today

The five declarations describe how modules relate. A module is more than its relations, and the as-is manifest surface (02-modules-and-manifests.md) is the checklist the graph has to be measured against facet by facet — otherwise the effort can look finished while most of what a module actually carries is unplaced. Weighed one at a time:

Facet today Likely landing Weighing
requires / provides a resource the graph exists today; carried over
service shape, data directories, firewall rules, managed configuration, service units typed resources in the host declaration (ADR 0043) covered — and refusal-on-unknown is what closes the class of fault in issue 003, where a manifest key restricted nothing in silence
environment resolved by the control plane; reaches the node inside declared resources covered in principle
migrations module runtime — never host-declared resources ADR 0006 stands. What must survive any re-integration is the two-kinds distinction: migrations against a module's own state, and migrations against a provisioned resource, which run where the resource is consumed
scheduled tasks a declared resource (a timer) or module runtime minor either way
per-node selections partly dissolves: the accelerator variant is requires a node capability; the public-versus-private variant is configuration, not a variant to test — if it dissolves fully, it belongs on the list of what the graph deletes
the tool surface no home — open below
verification open below
cross-component contributions probably dissolves — confirm, don't assume below
lifecycle hooks shrink toward module-side; host-side effects become declared resources inside the recorded "integration is wrong" claim; the code/data boundary is already set by ADRs 0039/0043, not by this effort

Three of these are genuinely open rather than mapping work.

The tool surface has no home in the tier model. Most of the catalogue carries tools — they are how an operator drives a module. Tier 3 is "thin, no logic", but a module's tools are module-specific logic: a media library's queue commands do not belong in a generic surface. No record says whether tools are a facet the module carries — discovered by the control plane, served through tier 3 — or something else. By count of affected modules this is the largest unscoped facet.

When is a provides-edge true? Issue 007 answers negatively: not when the package is installed. The strong candidate — every provides names its verifier, and the resolver trusts only verified edges — would make health checks the graph's truth condition, and satisfies the rule that a rule states how it is checked. The step too far to avoid adopting untested: intrinsic capabilities are already detection, and a mandatory verifier on every edge invites trivial verifiers — which are worse than none, because they are believed. What to require, and on which edges, is open.

Cross-component contributions probably dissolve, and should be confirmed rather than assumed. The candidate sixth relation — module A contributes something to component B's feature: a log pattern to the intrusion filter, a dashboard to the metrics stack — looks like a new edge type. For the intrusion filter it is not: that concern is absorbed into the host, so the contribution is an ordinary typed resource in the declaration and no edge exists. What remains open is whether the module-to-module cases also reduce to a typed resource the consumer reads, or genuinely need an edge. The answer decides whether the vocabulary grows — and per ADR 0043 every added type is reviewed as a security artefact, so it should be measured, not defaulted.

Open questions

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.
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.
Does node adoption scan for capabilities, applications, or both? The operator proposes scanning an adopted node and enabling what it finds. Under the working position above, the scan is for capabilities — but a machine with a terminal already installed is also a module already satisfied, and whether that is adoption or drift is undecided.
One installation image, or several? Proposed: pre-built images carrying different capability sets, so a machine is adopted quickly. Several images bake capability sets at image time, which is the filing problem in a new form and reintroduces what detection exists to avoid. One image carrying the host and nothing else is ADR 0038's one binary installed by hand, automated. The effort should settle which.
What happens to domain grouping? ADR 0017 is still proposed. If the graph is the answer, 0017 is superseded rather than narrowed — its text is never edited.
Where does the tool surface live, and how is it discovered? The largest unscoped facet — see the coverage table. Tier 3's "no logic" rule and module-specific tools pull in opposite directions, and no record resolves them.
When is a provides-edge true? Issue 007 rules out "when installed". Whether every provides must name a verifier — and what stops verifiers from being trivial — is undecided, and the resolver's trust model depends on it.
Do cross-component contributions need an edge? Probably not — host absorption turns the measured case into a declared resource — but the module-to-module cases are unmeasured, and a new edge type versus a new resource type is a security-vocabulary decision either way.