papa-hq reads 01 research -> 03 decision -> 02 design. The order is a scar, not a choice: 02-DESIGN existed from its initial commit, and when adr/ was finally promoted on 2026-07-13 it took the next free number rather than its place in the sequence. By then design was too settled to renumber. hal-hq was three commits old, so it is not. adr/ becomes 02-DECISIONS and 02-DESIGN becomes 03-DESIGN, and following the folder numbers now walks the process in the order it happens: research produces a decision, the decision authorises a design. 00-GENESIS becomes 00-META, matching papa's rename from the same restructure. Every path reference rewritten across documents, frontmatter, playbooks and skills. All links resolve; all 58 frontmatter blocks parse and their path fields still point at files that exist.
102 lines
5.5 KiB
Markdown
102 lines
5.5 KiB
Markdown
---
|
|
status: proposed
|
|
date: 2026-08-23
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 0011-the-installer-owns-linking.md
|
|
---
|
|
|
|
# 18. The mesh creates no symlinks — a derived file is a copy
|
|
|
|
## Context
|
|
|
|
[ADR 0011](0011-the-installer-owns-linking.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 0004](0004-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 0003](0003-the-mesh-database-is-the-source-of-truth.md) and ADR 0004 exist to prevent.
|
|
State is derived onto nodes; it does not reach back.
|
|
|
|
## Considered options
|
|
|
|
1. **Keep ADR 0011 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
|
|
|
|
*Proposed — 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 0004](0004-managed-files-are-generated-never-edited.md)).
|
|
|
|
The prohibition in ADR 0011 stands and widens: it ceases to be "only the installer may link"
|
|
and becomes "nothing links, the installer included".
|
|
|
|
When this is accepted, ADR 0011 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 0008](0008-a-failed-step-fails-the-job.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 0011 remains the governing rule.
|
|
|
|
## References
|
|
|
|
- [ADR 0011](0011-the-installer-owns-linking.md) — the incident, and the rule this widens.
|
|
- [ADR 0004](0004-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.
|