The mesh runs its own registry, certifies its own names, and computes its own filtering

Issue 003 is answered in both halves: manifests are parsed strictly, and a
module says what it listens on and from where rather than carrying a key
nothing reads. The design records what was built and how each part is checked.

Issue 013 is new, found by reading while writing the first module that has
both a computed file and a service that needs it. The file arrived second.
It failed, then the next reconcile fixed it, which is why nothing caught it.
This commit is contained in:
2026-08-31 00:37:34 +02:00
parent ba14e2b629
commit 778efaba8b
4 changed files with 213 additions and 7 deletions
@@ -1,9 +1,9 @@
---
status: open
status: resolved
opened: 2026-08-22
located-in: []
fixed-by:
amended-design:
located-in: [mesh-control]
fixed-by: mesh-control — a machine's filtering is computed from what it was assigned
amended-design: 03-DESIGN/01-to-be/08-connectivity.md
---
# 003 — A firewall rule's `scope:` is read by no code
@@ -38,3 +38,29 @@ any check.
- Were the five declarations intended to restrict something that is currently open? Each needs
checking against what the node actually exposes — the declaration cannot be trusted either
way.
## How it is answered
*2026-08-31.* Both halves, in Novox Mesh. HAL keeps the fault until its provisioning is switched
off, which is what this issue is now waiting on rather than a fix of its own — patching `scope:`
into something that works would mean implementing it twice, in the system being replaced.
**The unknown key.** A manifest is parsed strictly: an unknown key is refused with the key named,
the discipline the host's declaration parser has always had. `scope:` would not survive being
written today, and neither would a misspelling of anything else. This is the general fix — the
issue's own observation was that *any* invented key behaved this way, and that the one instance
was found by reading rather than by any check.
**The rule that restricts nothing.** `scope:` is not reimplemented. A module says what it listens
on and **who may reach it**, and saying from where is required rather than defaulted: a rule with
no source is open, and must say so rather than appear to restrict something. A machine's whole
rule set is then derived from every module assigned to it — so there is no second list to keep in
step, which is the condition that let the first one drift out of use unnoticed.
**And it is enforced, which is the part that makes this different from before.** The mesh renders
the rule set; a service on the node is declared to reflect that file, so replacing it restarts
what loads it. Proven in the lab against two real ports on a real machine: the declared one
answers from another machine, the undeclared one does not, and removing the module that wanted the
port closes it with nobody editing a rule.
The design is [`03-DESIGN/01-to-be/08-connectivity.md`](../../03-DESIGN/01-to-be/08-connectivity.md) §4.
@@ -0,0 +1,56 @@
---
status: resolved
opened: 2026-08-31
located-in: [mesh-control]
fixed-by: mesh-control — what the mesh computes is applied before what the module declared
amended-design:
---
# 013 — A file the mesh computes arrives after the service that needs it
## Symptom
Everything the control plane computes for a module — a certificate, a sealed credential, a bound
file, a rule set — was placed **after** that module's own resources in the declaration. The host
applies resources in the order it is given and
[does not sort](../../02-DECISIONS/0005-the-node-host.md), so a service or container declared in a
manifest was applied **before** the file it depends on existed.
On the first apply the service starts against a missing file and fails. The next reconcile finds
the file there and starts it.
## Why this matters
**It repairs itself, which is why nothing caught it.** A fault that is gone by the second attempt
is worse than one that persists: what gets remembered is that the thing works, and the failed
first apply is read as a machine that was briefly slow. The mesh reports a failure, then reports
success, and nobody looks again.
It was also invisible to every test that existed, because none of them combined the two halves.
Modules with computed files declared no service; modules with a service needed no computed file.
The fault lived exactly in the gap between two repositories' assumptions — the control plane
deciding an order, the host promising not to change it — which is the shape this folder exists for.
Found by reading, while writing the first module that has both: a firewall whose service must
reflect a rule set the mesh computes.
## Evidence
`internal/catalogue/declaration.go` built each module's resource list as
`append(module's own, computed...)` in six places — certificate, authority, needs, secrets, grants,
bindings. `internal/apply/apply.go` iterates `d.Resources` in order, and
`internal/declaration/declaration_test.go` states the rule directly: *order is stated, not derived.
The host must not sort.*
## What was done
The computed resources are assembled first and the module's own resources follow. Nothing the mesh
computes is derived from a module's resources, so the order is unconditionally right rather than a
heuristic — there is no case where a module's resource must precede a file the mesh made for it.
Merged after the computed-resources branch, which replaces a module's resources wholesale and
would otherwise discard everything the mesh had made for it.
*Checked by a module declaring a service that reflects a rule set, asserting the rule set is first;
and by a module whose resources are computed elsewhere, asserting its credential survives and is
still first — the case the merge point exists for.*