Order the records the way the system is learned
Jochen asked whether the order made sense. It did not -- it followed when things happened to be decided, which after consolidation is fictional anyway since record 5 alone folds decisions taken across a week. Concretely wrong before: the domain statement sat at 8, after five engineering rules; the constitution was scattered across 5, 12 and 17; the tiers landed at 15, 16, 21 and 22 with process records in between. Now it walks: what the mesh is (1-3), its tiers from the bottom up (4-8), what runs on them and how it gets there (9-10), how it is built (11-16), how it is checked (17-18), how we work (19-23). Two things made this safe rather than free. It is a permutation, not a compaction, so the renames go through temporary names -- otherwise two files want one slot and one is lost. And the reference rewrite is a single simultaneous pass, because almost every number moved into a slot another number was vacating; replacing one at a time would have cascaded and pointed things at the wrong record while still resolving. Verified: 284 [ADR NNNN](path) links across the repository, all with matching text and target. The ordering principle is now stated in 19 rather than left implicit -- the repository already said "the numbering is the flow" about its folders, and there was no reason for the records to be the exception.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
---
|
||||
status: accepted
|
||||
date: 2026-08-23
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 12. The mesh creates no symlinks — a derived file is a copy
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0012](0012-the-mesh-creates-no-symlinks.md) responded to production data loss — a hand-made
|
||||
link, resolved through a container engine's volume handling, pointing a mount somewhere it
|
||||
should not have — by centralising linking in the installer and forbidding it everywhere else.
|
||||
|
||||
That narrowed the incident class. It did not close it. The hazard is not *who* made the link;
|
||||
it is that a path can resolve somewhere other than where it appears to. A link made by the
|
||||
installer resolves exactly the same way as a link made by hand. The rule made the mechanism
|
||||
rarer and better-governed while leaving the mechanism in place.
|
||||
|
||||
Two things have changed since, and together they remove the argument that kept it.
|
||||
|
||||
**The original case for linking was staleness.** A copy of a service definition goes stale
|
||||
silently while the catalogue moves on, so a link was the cheap way to guarantee the running
|
||||
node reads a current definition. That argument assumes the node's copy is unmanaged.
|
||||
|
||||
**It is not.** [ADR 0011](0011-managed-files-are-generated-never-edited.md) established that
|
||||
everything on a node's disk is derived from the mesh and regenerated when its inputs change,
|
||||
and the installer already **reconciles** links rather than assuming them — repointing stale
|
||||
ones, adopting real files it finds where a link belongs. Reconciling content is the same
|
||||
operation as reconciling a pointer, plus a comparison.
|
||||
|
||||
So the mesh already has the machinery that makes a copy safe, and is using a link to solve a
|
||||
problem that machinery solves better. Worse, a link is conceptually the wrong shape: it makes
|
||||
the node's runtime state a *pointer into source*, which is the one thing
|
||||
[ADR 0006](0006-the-substrate-and-the-control-plane.md) and ADR 0011 exist to prevent.
|
||||
State is derived onto nodes; it does not reach back.
|
||||
|
||||
## Considered options
|
||||
|
||||
1. **Keep ADR 0019 as the final position** — centralised linking, forbidden elsewhere.
|
||||
Rejected as the status quo. It governs the mechanism rather than removing it, and the
|
||||
failure it was written for remains reachable by any code path the installer trusts.
|
||||
2. **Keep links but harden them** — canonicalise before mounting, refuse a link that escapes
|
||||
an expected root. Rejected: it is a check bolted onto a hazard, and it has to be correct in
|
||||
every consumer, including container engines the mesh does not control.
|
||||
3. **Copy, reconciled by the installer, with staleness detected rather than assumed away.**
|
||||
Proposed here.
|
||||
|
||||
## Decision
|
||||
|
||||
*Accepted 2026-08-25. The position is settled; the migration is not designed. See "Open" below.*
|
||||
|
||||
**The mesh creates no symlinks.** A file a node needs is placed on that node as a real file,
|
||||
derived from the mesh and reconciled by the installer like every other managed file
|
||||
([ADR 0011](0011-managed-files-are-generated-never-edited.md)).
|
||||
|
||||
The prohibition in ADR 0019 stands and widens: it ceases to be "only the installer may link"
|
||||
and becomes "nothing links, the installer included".
|
||||
|
||||
When this is accepted, ADR 0019 becomes superseded rather than edited — its reasoning is why
|
||||
the rule exists at all, and the incident behind it is the reason anyone believes either record.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The path-resolution hazard is removed rather than governed. There is no link for a container
|
||||
engine to resolve, so the class of failure that cost production data is closed by
|
||||
construction.
|
||||
- A node's runtime state stops pointing into source. What a node holds is derived output, which
|
||||
is what the mesh's model already says it is everywhere else.
|
||||
- **Staleness becomes a real problem that must be answered, not assumed away.** This is the
|
||||
cost, and it is the whole cost: today a link cannot be stale, and a copy can. The answer has
|
||||
to be detection — the installer comparing what is on disk against what the mesh says should
|
||||
be — and it must be loud, because a silently stale definition is exactly the failure shape
|
||||
this mesh keeps producing ([ADR 0010](0010-delivery.md)).
|
||||
- Reconciliation gets more expensive: comparing content rather than checking a pointer's
|
||||
target, on every module, on every node.
|
||||
- Disk usage rises, trivially, and is not a consideration.
|
||||
- Existing links must be converted. A node mid-migration holds both forms, so reconciliation
|
||||
has to handle finding a link where a file now belongs — the mirror image of the adoption it
|
||||
already does.
|
||||
|
||||
## Open
|
||||
|
||||
- **How staleness is detected.** Content hash, version marker, or regeneration on every
|
||||
reconcile. This is the decision that makes or breaks the change and it is not taken here.
|
||||
- **Whether anything must keep a link** for reasons outside the mesh's control. If something
|
||||
does, that is a finding worth recording rather than an exception worth granting quietly.
|
||||
- **Migration order.** Converting a node's links is a change to how its services resolve their
|
||||
own definitions, which is not a change to make everywhere at once.
|
||||
|
||||
Until those are answered this record stays `proposed`, and ADR 0019 remains the governing rule.
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0012](0012-the-mesh-creates-no-symlinks.md) — the incident, and the rule this widens.
|
||||
- [ADR 0011](0011-managed-files-are-generated-never-edited.md) — the machinery that makes a
|
||||
copy safe.
|
||||
- [`03-DESIGN/00-as-is/05-runtime-and-installation.md`](../03-DESIGN/00-as-is/05-runtime-and-installation.md)
|
||||
— what the installer does today, including reconciliation and adoption.
|
||||
Reference in New Issue
Block a user