66 lines
3.5 KiB
Markdown
66 lines
3.5 KiB
Markdown
---
|
|
status: located
|
|
opened: 2026-09-23
|
|
located-in: [mesh-controller cmd/mesh-controller/adoption.go (take previews nothing), mesh-host internal/apply (the comparison and the record)]
|
|
fixed-by:
|
|
amended-design:
|
|
---
|
|
|
|
# 096 — A setting that cannot work is accepted where it is set, and stops the node where it is read
|
|
|
|
## What was observed
|
|
|
|
On the control-node during the first module's migration, 2026-09-23, and reproduced since against
|
|
the controller's own tests.
|
|
|
|
A per-node port setting was accepted and stored. Composing that node's declaration then failed on
|
|
it, and because a node is told everything or nothing, **every push to that machine was refused**
|
|
until somebody found the setting and removed it. The message named a port number. It did not name
|
|
the setting, the layer it was stored in, or the node it had stopped; nothing said that a stored
|
|
statement was the reason the machine had gone quiet.
|
|
|
|
The specific reason that setting could not work is [issue
|
|
094](../094-a-port-published-as-the-machine-side-cannot-be-moved/00-report.md), and it is fixed.
|
|
This issue is the shape that surrounded it, which is not:
|
|
|
|
- **The place that stores a setting has no manifest in view.** It checks what it can without one —
|
|
that a value is a port, that ssh keeps its own, that two modules on the machine do not claim the
|
|
same machine port — and leaves anything that needs the module's own declaration to composition.
|
|
So a key naming a port the module does not publish is stored today, and a typo is stored today,
|
|
and both are found later, from the far end.
|
|
- **Composition fails the node, not the module.** One unusable statement about one module refuses
|
|
the whole declaration, so the other modules on that machine stop being told anything either —
|
|
including modules that were fine before the setting existed.
|
|
|
|
## Why it matters beyond this instance
|
|
|
|
The two together turn a typo into an outage of the control link for a machine, at a distance from
|
|
the thing that caused it. The delay is the damage: a refusal at the moment of setting is a
|
|
correction, and the same refusal a push later is a machine nobody can talk to, found by whoever
|
|
next notices it is not being updated.
|
|
|
|
It generalises past ports. Any setting whose validity depends on the module's declaration has this
|
|
shape — a value that is well-formed on its own and impossible against the manifest. Ports are
|
|
merely where the mesh found it first, because migrating a service is when settings get written.
|
|
|
|
It also touches a rule the mesh states elsewhere: what refuses, refuses early and by name. A
|
|
refusal that names a number rather than the statement that produced it cannot be acted on without
|
|
knowing the code.
|
|
|
|
## Open questions
|
|
|
|
- Should storing a setting compose it against the module's manifest first — and if so, against
|
|
which version, given the catalogue moves and a manifest that was right when the setting was
|
|
written may not be later?
|
|
- Or should the guard be at the far end: composition refuses that **module** and sends the rest of
|
|
the node, so an impossible statement costs one service and not the machine?
|
|
- Either way, what does a refusal have to name — the node, the module, the layer and the key — for
|
|
an operator to undo it without reading the source?
|
|
- Is there anything a node must never be pushed without, such that sending a partial declaration is
|
|
worse than sending none?
|
|
|
|
## Decided, 2026-10-01
|
|
|
|
[ADR 0163](../../02-DECISIONS/0163-taking-a-module-over-is-a-comparison.md), rule 6: judged where stored; an impossible statement costs a module. Building follows,
|
|
host first, then the controller's `take`.
|