Reconcile: adopt initialization's consolidated HQ as canonical, re-home this session's new work #24
+3
-3
@@ -34,7 +34,7 @@ comparing it against the code rather than by anyone noticing:
|
||||
|
||||
- It described the pipeline as having a separate builder process and a build stage that
|
||||
packages. Neither was true after 2026-08-04; the documents stayed stale until 2026-08-06
|
||||
([ADR 0023](../02-DECISIONS/0023-delivery.md)).
|
||||
([ADR 0010](../02-DECISIONS/0010-delivery.md)).
|
||||
- It listed the mesh as spanning a fixed number of named machines, which is exactly the
|
||||
content this repository cannot carry.
|
||||
|
||||
@@ -44,10 +44,10 @@ symlinks at all — the rule is not merely "only the installer may link", and a
|
||||
elevating linking to a principle points the opposite way from where this is going.
|
||||
|
||||
What exists today is that the installer owns and reconciles every link
|
||||
([ADR 0010](../02-DECISIONS/0010-the-mesh-creates-no-symlinks.md)) — an as-is fact, recorded in
|
||||
([ADR 0012](../02-DECISIONS/0012-the-mesh-creates-no-symlinks.md)) — an as-is fact, recorded in
|
||||
[`03-DESIGN/00-as-is/05-runtime-and-installation.md`](../03-DESIGN/00-as-is/05-runtime-and-installation.md).
|
||||
Centralising who may link narrowed the incident class; it did not close it. The intent is to
|
||||
remove the mechanism, recorded as [ADR 0010](../02-DECISIONS/0010-the-mesh-creates-no-symlinks.md).
|
||||
remove the mechanism, recorded as [ADR 0012](../02-DECISIONS/0012-the-mesh-creates-no-symlinks.md).
|
||||
|
||||
A founding document contradicting the direction of travel is precisely the failure this folder
|
||||
exists to prevent.
|
||||
|
||||
@@ -19,8 +19,8 @@ indistinguishable from one that cannot.
|
||||
|---|---|---|
|
||||
| `links` | every relative link resolves | — (run ad hoc during authoring; now permanent) |
|
||||
| `rests-on` | `decisions:` and `extends:` name records that exist and are **accepted** | the class behind both incidents |
|
||||
| `live-citation` | a governing document citing a **superseded** record names its replacement in the same paragraph | `01-to-be/README.md` citing ADR 0017 as live guidance |
|
||||
| `supersession` | if A says it was superseded by B, B says it supersedes A | ADR 0010 never declared that it superseded 0011 |
|
||||
| `live-citation` | a governing document citing a **superseded** record names its replacement in the same paragraph | `01-to-be/README.md` citing ADR 0022 as live guidance |
|
||||
| `supersession` | if A says it was superseded by B, B says it supersedes A | ADR 0012 never declared that it superseded 0011 |
|
||||
| `numbering` | the number in the filename is the number in the heading | — |
|
||||
|
||||
## What is deliberately not checked
|
||||
@@ -31,7 +31,7 @@ indistinguishable from one that cannot.
|
||||
having it.
|
||||
- **`03-DESIGN/00-as-is/` may rest on a superseded record.** It describes what runs, and what
|
||||
runs was built under whatever was decided at the time
|
||||
([ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md):
|
||||
([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md):
|
||||
*as-is describing a superseded decision is exactly what as-is is for*).
|
||||
- **Whether a citation's prose is still true.** Only whether the record it points at is live.
|
||||
A document can cite an accepted record and describe it wrongly, and nothing here notices.
|
||||
|
||||
+12
-12
@@ -3,7 +3,7 @@ status: canonical
|
||||
updated: 2026-08-23
|
||||
derives: knowledge-base constitution page
|
||||
decisions:
|
||||
- 02-DECISIONS/0005-the-mesh-is-governed-by-a-constitution.md
|
||||
- 02-DECISIONS/0020-the-mesh-is-governed-by-a-constitution.md
|
||||
---
|
||||
|
||||
# How we build
|
||||
@@ -37,13 +37,13 @@ incident behind it is not written down, and the fix is to write it down, not to
|
||||
| Rule | What it means |
|
||||
|---|---|
|
||||
| **Never write to a production database directly** | No insert, update, delete or schema statement executed against production by hand. Schema changes go through numbered migrations; data changes go through application code or the module's own capabilities. Raw statements skip every side effect the proper path has — events, audit, cache invalidation, fan-out. |
|
||||
| **Every schema change is a migration** | Numbered, in the module's own language, compiled with it. Both a baseline for a fresh installation *and* an incremental migration for installations that already exist. If code references a column, the migration creating it must exist. [ADR 0003](../02-DECISIONS/0003-schema-changes-are-numbered-migrations.md) |
|
||||
| **Every schema change is a migration** | Numbered, in the module's own language, compiled with it. Both a baseline for a fresh installation *and* an incremental migration for installations that already exist. If code references a column, the migration creating it must exist. [ADR 0013](../02-DECISIONS/0013-schema-changes-are-numbered-migrations.md) |
|
||||
| **Never bypass the pipeline** | No manual database edit, no manual restart as a workaround. Fix the cause and deploy. A workaround that works is a workaround that is never removed, and the next person cannot tell the node from its declaration. |
|
||||
| **Never create a symlink** | A hand-made link caused production data loss through container volume resolution, and the judgement needed to make a safe exception is exactly the judgement unavailable at the moment it matters. **The mesh creates none at all** ([ADR 0010](../02-DECISIONS/0010-the-mesh-creates-no-symlinks.md), which supersedes [ADR 0010](../02-DECISIONS/0010-the-mesh-creates-no-symlinks.md)). The links the installer still reconciles are a migration, not a permission. |
|
||||
| **Never create a symlink** | A hand-made link caused production data loss through container volume resolution, and the judgement needed to make a safe exception is exactly the judgement unavailable at the moment it matters. **The mesh creates none at all** ([ADR 0012](../02-DECISIONS/0012-the-mesh-creates-no-symlinks.md), which supersedes [ADR 0012](../02-DECISIONS/0012-the-mesh-creates-no-symlinks.md)). The links the installer still reconciles are a migration, not a permission. |
|
||||
| **Never push directly to the main branch** | Branch, push, review, merge. Every merge is a human checkpoint, without exception — **including in this repository**. A documentation repository is not a lower tier of care; a decision record lands the same way a service does. |
|
||||
| **One change per pull request, and never merge unapproved work** | Unrelated improvements bundled together cannot be reviewed or reverted separately. And the checkpoint is **a person deciding, not a person clicking** — work may be merged by whoever wrote it once a human has explicitly approved *that merge*, and never on a standing permission, an instruction to do the work, silence, or the author's own judgement that it is ready. [ADR 0018](../02-DECISIONS/0018-approval-is-the-checkpoint.md) |
|
||||
| **One change per pull request, and never merge unapproved work** | Unrelated improvements bundled together cannot be reviewed or reverted separately. And the checkpoint is **a person deciding, not a person clicking** — work may be merged by whoever wrote it once a human has explicitly approved *that merge*, and never on a standing permission, an instruction to do the work, silence, or the author's own judgement that it is ready. [ADR 0023](../02-DECISIONS/0023-approval-is-the-checkpoint.md) |
|
||||
| **Never open a pull request unprompted** | A permissions list saying it is allowed is not a request. |
|
||||
| **A failed step fails the job** | A sequence that continues past a failure does the next thing in the wrong place. Gate each step on the last. [ADR 0023](../02-DECISIONS/0023-delivery.md), and §5. |
|
||||
| **A failed step fails the job** | A sequence that continues past a failure does the next thing in the wrong place. Gate each step on the last. [ADR 0010](../02-DECISIONS/0010-delivery.md), and §5. |
|
||||
|
||||
### A failed step must stop the steps after it — how it was earned
|
||||
|
||||
@@ -69,7 +69,7 @@ reported failure, nothing stopped, and the damage happened somewhere nobody was
|
||||
- **Every runtime variable is declared.** A variable the module reads and the manifest does not
|
||||
declare is invisible to the mesh: it will not be generated, injected, or audited.
|
||||
- **Provisioned credentials arrive through declared requirements**, never hardcoded in code,
|
||||
compose files or scripts. [ADR 0019](../02-DECISIONS/0019-modules-and-the-graph.md)
|
||||
compose files or scripts. [ADR 0009](../02-DECISIONS/0009-modules-and-the-graph.md)
|
||||
- **Never install a package by hand.** A package is declared in the manifest and arrives the
|
||||
way every other package does. A hand-installed package is invisible to the mesh: it is not
|
||||
declared, not reproduced on the next node, and not present after a rebuild — and the node
|
||||
@@ -82,7 +82,7 @@ reported failure, nothing stopped, and the damage happened somewhere nobody was
|
||||
- **Every standalone application gets its own repository**, with a manifest at its root,
|
||||
registered as a build source. Creating an application directory in the monorepo is a
|
||||
convention violation and reviewers reject it.
|
||||
[ADR 0006](../02-DECISIONS/0006-applications-live-in-their-own-repository.md)
|
||||
[ADR 0015](../02-DECISIONS/0015-applications-live-in-their-own-repository.md)
|
||||
|
||||
### Migrations
|
||||
|
||||
@@ -96,7 +96,7 @@ reported failure, nothing stopped, and the damage happened somewhere nobody was
|
||||
surface is regenerated from the mesh database; a local edit survives one synchronisation and is
|
||||
then silently overwritten, bringing back whatever it fixed. Use the mesh operation that owns
|
||||
the value. If unsure whether a file is managed, ask the tooling — the answer is not visible
|
||||
from the file. [ADR 0002](../02-DECISIONS/0002-managed-files-are-generated-never-edited.md)
|
||||
from the file. [ADR 0011](../02-DECISIONS/0011-managed-files-are-generated-never-edited.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -121,7 +121,7 @@ for them. Do not merge them into one module: they are delivered to different nod
|
||||
that must be assigned where half of it is unwanted is not a boundary either.
|
||||
|
||||
Coherence is a context. Delivery is a module. Relationships are edges, not folders.
|
||||
[ADR 0019](../02-DECISIONS/0019-modules-and-the-graph.md)
|
||||
[ADR 0009](../02-DECISIONS/0009-modules-and-the-graph.md)
|
||||
|
||||
### Contexts integrate through the record, never through a shared schema
|
||||
|
||||
@@ -226,7 +226,7 @@ No drive-by edits. Every change traces to a recorded decision.
|
||||
a design meeting with at least two node operators — which has never been met and cannot be, as
|
||||
there is one operator. A rule that cannot be satisfied is not a high standard; it is a rule
|
||||
everything silently violates. Recorded here as resolved in favour of what is achievable, and
|
||||
what has in fact been practised ([ADR 0017](../02-DECISIONS/0017-the-constitution-absorbs-what-is-enforced.md)).
|
||||
what has in fact been practised ([ADR 0022](../02-DECISIONS/0022-the-constitution-absorbs-what-is-enforced.md)).
|
||||
|
||||
---
|
||||
|
||||
@@ -243,7 +243,7 @@ process. Absence of an override means these rules apply unmodified.
|
||||
## 8. Code quality
|
||||
|
||||
*Absorbed 2026-08-26 from the enforced page, which carried these rules while this document did
|
||||
not — [ADR 0017](../02-DECISIONS/0017-the-constitution-absorbs-what-is-enforced.md).*
|
||||
not — [ADR 0022](../02-DECISIONS/0022-the-constitution-absorbs-what-is-enforced.md).*
|
||||
|
||||
**These rules are recorded because they are enforced, not because this repository earned them.**
|
||||
Every other rule here states the incident or measurement behind it. These state nothing,
|
||||
@@ -272,7 +272,7 @@ Data access, business logic and the interface layer are separate.
|
||||
|
||||
*Scope: the mesh's services and surfaces. Tier 0 is a statically linked binary that must depend
|
||||
on nothing installed first, and is written in Go —
|
||||
[ADR 0016](../02-DECISIONS/0016-the-node-host.md).*
|
||||
[ADR 0005](../02-DECISIONS/0005-the-node-host.md).*
|
||||
|
||||
- TypeScript throughout; no new untyped JavaScript.
|
||||
- Strict, with no implicit `any` and no unchecked index access.
|
||||
|
||||
+7
-7
@@ -15,26 +15,26 @@ and a forge address is an operational detail (see [`README`](../README.md)).
|
||||
| Repository | Owns |
|
||||
|---|---|
|
||||
| `hal` | The monorepo — the node runtime, the module catalogue, the delivery machinery, and the bootstrap scripts. Every core module lives here. |
|
||||
| `hq` | This repository, under the company organisation — mission, research, design, decisions, issue diagnosis. Company-scoped ([ADR 0011](../02-DECISIONS/0011-how-this-repository-works.md)); the mesh is its first product. The source of truth for *why*. Carries no implementation. |
|
||||
| `hq` | This repository, under the company organisation — mission, research, design, decisions, issue diagnosis. Company-scoped ([ADR 0019](../02-DECISIONS/0019-how-this-repository-works.md)); the mesh is its first product. The source of truth for *why*. Carries no implementation. |
|
||||
| *(one per application)* | Every standalone application, site or side-project gets its own repository, with `module.yml` at the root. Registered with the mesh as a build source; built and deployed by the same pipeline as anything in the monorepo. |
|
||||
|
||||
## What the mesh becomes
|
||||
|
||||
[ADR 0011](../02-DECISIONS/0011-how-this-repository-works.md) records the repositories the
|
||||
[ADR 0019](../02-DECISIONS/0019-how-this-repository-works.md) records the repositories the
|
||||
monorepo decomposes into. **`mesh-lab` and `mesh-host` exist so far** — the lab is built first
|
||||
([ADR 0009](../02-DECISIONS/0009-the-lab.md)); the rest are the
|
||||
([ADR 0016](../02-DECISIONS/0016-the-lab.md)); the rest are the
|
||||
target, not the present.
|
||||
|
||||
| Repository | Tier | Holds |
|
||||
|---|---|---|
|
||||
| `mesh-host` | 0 | **exists.** The node host — one statically linked binary, requiring nothing present ([ADR 0016](../02-DECISIONS/0016-the-node-host.md)) |
|
||||
| `mesh-host` | 0 | **exists.** The node host — one statically linked binary, requiring nothing present ([ADR 0005](../02-DECISIONS/0005-the-node-host.md)) |
|
||||
| `mesh-substrate` | 1 | the four pinned services, as declarations |
|
||||
| `mesh-control` | 2 | the control plane and its contexts |
|
||||
| `mesh-surfaces` | 3 | tools, web, cli |
|
||||
| `mesh-sdk` | — | contracts shared across tiers |
|
||||
| `mesh-lab` | — | **exists.** The lab — scenario lifecycle, networking, placement. Ships to nobody; runs on a workstation. |
|
||||
|
||||
Tier 4's shape is open, and deliberately so: see ADR 0011 and
|
||||
Tier 4's shape is open, and deliberately so: see ADR 0019 and
|
||||
[research 005](../01-RESEARCH/005-domain-grouping/00-overview.md).
|
||||
|
||||
## What lives where inside the monorepo
|
||||
@@ -53,7 +53,7 @@ Named by role, because the layout is itself part of the as-is design — see
|
||||
## Why applications do not live in the monorepo
|
||||
|
||||
A standalone application in the monorepo is a convention violation, and reviewers reject it.
|
||||
The reasoning is recorded in [`02-DECISIONS/0010`](../02-DECISIONS/0006-applications-live-in-their-own-repository.md):
|
||||
The reasoning is recorded in [`02-DECISIONS/0010`](../02-DECISIONS/0015-applications-live-in-their-own-repository.md):
|
||||
the mesh installs, provisions for, and ships an application through exactly the same machinery
|
||||
whether or not its source sits beside the mesh's own — so co-location buys nothing and costs
|
||||
the monorepo's review cadence.
|
||||
@@ -64,7 +64,7 @@ Each module is a standalone package that consumes its dependencies from the priv
|
||||
not from a sibling directory. The workspace was removed after it caused build-versus-development
|
||||
divergence — a workspace member importing another resolved to local unbuilt source in the
|
||||
pipeline and to a published version in development. Recorded in
|
||||
[`02-DECISIONS/0007`](../02-DECISIONS/0004-no-npm-workspace.md).
|
||||
[`02-DECISIONS/0007`](../02-DECISIONS/0014-no-npm-workspace.md).
|
||||
|
||||
Consequence, and it is a real one: a cross-package change is two steps — publish, then consume
|
||||
— and a repository-wide `npm install` does not exist.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
status: active
|
||||
initiated: 2026-08-22
|
||||
touches: [03-DESIGN/00-as-is/02-modules-and-manifests.md, 03-DESIGN/00-as-is/10-module-catalogue.md, 03-DESIGN/01-to-be/00-work-breakdown.md]
|
||||
became: [02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md]
|
||||
became: [02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md]
|
||||
---
|
||||
|
||||
# 001 — Module domain decomposition
|
||||
@@ -54,7 +54,7 @@ Tracked in [`analysis.md`](analysis.md) under "Open questions".
|
||||
## Deliberately not decided
|
||||
|
||||
Recorded so they are not mistaken for oversights. Each is open, and each comes out of
|
||||
[ADR 0008](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md); this effort stays
|
||||
[ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md); this effort stays
|
||||
`active` until they are answered.
|
||||
|
||||
| Question | Status |
|
||||
@@ -64,4 +64,4 @@ Recorded so they are not mistaken for oversights. Each is open, and each comes o
|
||||
| Catalogue destination — one repository or many. | Open. Phase 4. |
|
||||
| What the shared library keeps after extraction. | Open. Phase 3. |
|
||||
| Where human agent modality is recorded — which user, on which node, a human agent acts as. | Open. Required by the model; not yet stored. |
|
||||
| Which domains the modules outside the platform core group into. | Open, from [ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md), which settles the principle and deliberately not the list. |
|
||||
| Which domains the modules outside the platform core group into. | Open, from [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md), which settles the principle and deliberately not the list. |
|
||||
|
||||
@@ -237,6 +237,6 @@ returns. That mechanism is the subject of a separate ADR.
|
||||
pipeline resolves dependencies across the registry rather than the filesystem?
|
||||
4. **SDK residue** — after extraction, does `hal/sdk` keep transport (`amqp-client`), or
|
||||
does that belong to `hal/stream`? Everything imports it, which argues both ways.
|
||||
5. **Human agent modality.** ADR 0008 requires a fact the mesh does not record: which
|
||||
5. **Human agent modality.** ADR 0001 requires a fact the mesh does not record: which
|
||||
user, on which node, a human agent acts as. Where does it live — an attribute of the
|
||||
agent, or of the agent-node binding?
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
status: graduated
|
||||
initiated: 2026-08-22
|
||||
touches: [03-DESIGN/00-as-is/04-delivery.md, 03-DESIGN/00-as-is/05-runtime-and-installation.md]
|
||||
became: [03-DESIGN/01-to-be/01-end-to-end-testing.md, 02-DECISIONS/0009-the-lab.md]
|
||||
became: [03-DESIGN/01-to-be/01-end-to-end-testing.md, 02-DECISIONS/0016-the-lab.md]
|
||||
---
|
||||
|
||||
# 002 — A mesh that runs locally
|
||||
@@ -24,7 +24,7 @@ This effort establishes what already runs in a container, what is welded to the
|
||||
what it would take to close the gap. It does **not** choose an approach: the central
|
||||
question — how a containerised node executes a module service, when a module service is
|
||||
defined today as a systemd unit shelling to `docker compose` in `/services/` — is not
|
||||
answered by ADR 0008 and is recorded below rather than decided.
|
||||
answered by ADR 0001 and is recorded below rather than decided.
|
||||
|
||||
## What was established
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ over either way.
|
||||
|
||||
## References
|
||||
|
||||
- [`02-DECISIONS/0001`](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md) — the decision this
|
||||
- [`02-DECISIONS/0001`](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) — the decision this
|
||||
phase unblocks
|
||||
- [`03-DESIGN/00-work-breakdown.md`](../../03-DESIGN/01-to-be/00-work-breakdown.md) — Phase 0 tasks
|
||||
and checkpoint
|
||||
|
||||
@@ -3,8 +3,8 @@ status: graduated
|
||||
initiated: 2026-08-22
|
||||
touches: [03-DESIGN/00-as-is/05-runtime-and-installation.md]
|
||||
became:
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 03-DESIGN/01-to-be/05-the-node-host.md
|
||||
---
|
||||
|
||||
@@ -43,7 +43,7 @@ This effort answers the cost half. It does not choose.
|
||||
- **There is a third option neither of us named**, and it is the one that also solves Phase 0:
|
||||
run HAL's own daemons as containers, making Docker the supervisor for everything. Local and
|
||||
production then have the same shape rather than a translation layer between them.
|
||||
- **It cannot be all-or-nothing**, and ADR 0008 already says why: a human agent acts through a
|
||||
- **It cannot be all-or-nothing**, and ADR 0001 already says why: a human agent acts through a
|
||||
shell and a desktop. Those parts are on the host by definition.
|
||||
- One incidental finding: the automatic node rescue that documentation describes **does not
|
||||
exist**. No unit declares `OnFailure=`, and nothing calls `hal-rescue.sh` on a timer.
|
||||
@@ -57,14 +57,14 @@ which is why the effort sat `active` for five days after being answered. Recorde
|
||||
finding that is the point of a sweep.
|
||||
|
||||
**The third option is what the mesh adopted.** `Docker is the supervisor for everything` is
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-node-host.md): the
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md): the
|
||||
host is a plain process on the machine and everything above tier 0 is a container. The substrate
|
||||
bootstrap declares no service at all — it is package, container, action, container — so the
|
||||
44-of-44 restart policies this effort counted are the supervision, exactly as it argued.
|
||||
|
||||
**Fate-sharing was the hard part, and it is solved the way this effort predicted.** It said any
|
||||
mesh-native supervisor inherits the problem *unless it sits outside the mesh's own process
|
||||
tree*. [ADR 0016](../../02-DECISIONS/0016-the-node-host.md) puts
|
||||
tree*. [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) puts
|
||||
the launcher there: it supervises the host as a child and shares no code with it, so a host that
|
||||
cannot start is still recovered.
|
||||
|
||||
|
||||
@@ -128,10 +128,10 @@ What it costs, honestly:
|
||||
|
||||
---
|
||||
|
||||
## 5. Why it cannot be all-or-nothing — and ADR 0008 already says so
|
||||
## 5. Why it cannot be all-or-nothing — and ADR 0001 already says so
|
||||
|
||||
Some of what runs under systemd today **cannot** be containerised, and the reason is
|
||||
already in the domain model. ADR 0008:
|
||||
already in the domain model. ADR 0001:
|
||||
|
||||
> a non-human agent acts through a spawned session — a human agent acts through a shell or
|
||||
> desktop
|
||||
@@ -223,7 +223,7 @@ fate-sharing reason in §3.
|
||||
## References
|
||||
|
||||
- [`002-local-mesh`](../002-local-mesh/analysis.md) — the effort this came out of
|
||||
- [`02-DECISIONS/0001`](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md) — agent modality, which
|
||||
- [`02-DECISIONS/0001`](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) — agent modality, which
|
||||
decides what cannot leave the host
|
||||
- `modules/hal/meshware/daemon/src/cerebellum.ts:815-828` — the self-restart workaround
|
||||
- `modules/hal/meshware/systemd/hal-module@.service` — the per-module Docker lifecycle
|
||||
|
||||
@@ -3,9 +3,9 @@ status: graduated
|
||||
initiated: 2026-08-22
|
||||
touches: [03-DESIGN/00-as-is/01-mesh-and-transport.md, 03-DESIGN/01-to-be/01-end-to-end-testing.md]
|
||||
became:
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 03-DESIGN/01-to-be/02-scenario-declaration.md
|
||||
- 03-DESIGN/01-to-be/01-end-to-end-testing.md
|
||||
---
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
status: graduated
|
||||
initiated: 2026-08-23
|
||||
touches:
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 03-DESIGN/00-as-is/10-module-catalogue.md
|
||||
- 03-DESIGN/00-as-is/02-modules-and-manifests.md
|
||||
became:
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
---
|
||||
|
||||
# 005 — Which domains the catalogue groups into
|
||||
|
||||
[ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md) settles
|
||||
[ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) settles
|
||||
that modules outside the platform core are grouped by domain rather than by single function,
|
||||
and deliberately does not settle the list. This effort settles the list — and, first, tests
|
||||
whether the premise survives measurement.
|
||||
@@ -25,7 +25,7 @@ together**, measured across the full history of the code repository.
|
||||
|
||||
## Why
|
||||
|
||||
The argument in ADR 0017 is that the catalogue's shape records what was installed rather than
|
||||
The argument in ADR 0022 is that the catalogue's shape records what was installed rather than
|
||||
what anything is for — that four modules constituting "how a node is reachable" have no
|
||||
relationship the mesh can see, so a change to connectivity is made four times.
|
||||
|
||||
@@ -59,12 +59,12 @@ open questions below.
|
||||
this effort — which is why it stayed open after being resolved.
|
||||
|
||||
**Whether provider modules group at all** — *no.*
|
||||
[ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md):
|
||||
[ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md):
|
||||
there is no `networking` thing to install, there are concrete modules named individually. Folders
|
||||
assert relationships; edges record them. *Provider* stops being a category at the same time.
|
||||
|
||||
**Whether "group or leave" is even the right pair of options** — *it was not*, and that is the
|
||||
useful finding. [ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md)
|
||||
useful finding. [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md)
|
||||
reframes it: things that change together share an **authority**, not a package. This effort's own
|
||||
measurement is what that record rests on — reachability being the *only* place modules genuinely
|
||||
co-change is why connectivity is a context and why nothing else needed one.
|
||||
|
||||
@@ -10,7 +10,7 @@ updated: 2026-08-23
|
||||
Every commit in the code repository's main branch that touches the module catalogue, reduced
|
||||
to the set of modules it touched. Platform-namespace modules are excluded — their
|
||||
decomposition is settled by
|
||||
[ADR 0008](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md). Modules that no
|
||||
[ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md). Modules that no
|
||||
longer exist are excluded, because pre-rename names dominate the raw signal and describe a
|
||||
catalogue nobody works in.
|
||||
|
||||
@@ -95,14 +95,14 @@ remainder are genuine:
|
||||
| 2026-08-06 | firewall mesh-only by default, public by declaration |
|
||||
|
||||
Each is one intent — *change how a node is reachable* — landing across the proxy, the
|
||||
resolver, the firewall and the VPN together. That is exactly the shape ADR 0017 describes, and
|
||||
resolver, the firewall and the VPN together. That is exactly the shape ADR 0022 describes, and
|
||||
it is the only place in the catalogue where the measurement finds it.
|
||||
|
||||
The 2026-08-23 scoping commit is the sharpest case: it spans the reachability cluster **and**
|
||||
two providers, because "which network is this exposed on" is a reachability question asked of
|
||||
a database.
|
||||
|
||||
## What this means for ADR 0017
|
||||
## What this means for ADR 0022
|
||||
|
||||
The record's principle stands, and its scope needs narrowing. Grouping by domain is:
|
||||
|
||||
@@ -130,7 +130,7 @@ Asked directly, and stated as an opinion because it is not yet decided.
|
||||
is implementation selection, a substantially larger design with its own failure modes, and
|
||||
nothing currently asks for it.
|
||||
3. **It would hide which implementation serves a requirement** — the one place the mesh most
|
||||
needs to be explicit, and precisely the indirection ADR 0017 warns grouping causes.
|
||||
needs to be explicit, and precisely the indirection ADR 0022 warns grouping causes.
|
||||
|
||||
A provider module is already exactly one purpose: it provisions one resource type. That is a
|
||||
boundary, not an accident of installation.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
status: active
|
||||
initiated: 2026-08-23
|
||||
touches:
|
||||
- 02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 03-DESIGN/00-as-is/00-overview.md
|
||||
- 03-DESIGN/01-to-be/00-work-breakdown.md
|
||||
became: []
|
||||
@@ -24,9 +24,9 @@ disk, and where today's catalogue lands.
|
||||
## Why
|
||||
|
||||
Every structural decision so far has been a **correction**: eight contexts replacing thirty-three
|
||||
modules ([ADR 0008](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md)), domains
|
||||
modules ([ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md)), domains
|
||||
replacing single-function modules
|
||||
([ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md)). A
|
||||
([ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md)). A
|
||||
correction inherits the frame of the thing it corrects, and two of the mesh's oldest problems
|
||||
look unsolvable from inside that frame:
|
||||
|
||||
@@ -68,7 +68,7 @@ against taste:
|
||||
circle, self-hosted. Personal cloud infrastructure.
|
||||
8. **Agents make it self-improving and self-healing.**
|
||||
9. It is **end-to-end testable on one machine**
|
||||
([ADR 0009](../../02-DECISIONS/0009-the-lab.md)).
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-lab.md)).
|
||||
|
||||
## Status
|
||||
|
||||
@@ -76,7 +76,7 @@ A first skeleton exists, with four design moves that the current shape does not
|
||||
`active` because two of them are unproven and one contradicts a record that is already
|
||||
accepted.
|
||||
|
||||
**Finding worth stating up front:** [ADR 0008](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md)
|
||||
**Finding worth stating up front:** [ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md)
|
||||
names nine bounded contexts and **none of them owns connectivity** — no overlay, no resolution,
|
||||
no firewall, no ingress. Requirement 4 has no home in the accepted decomposition, while
|
||||
[research 005](../005-domain-grouping/analysis.md) found reachability to be the *only* part of
|
||||
@@ -87,8 +87,8 @@ the catalogue where modules genuinely change together under one intent. The skel
|
||||
| Question | Why it is open |
|
||||
|---|---|
|
||||
| Does the record — the event log contexts integrate through — belong to the substrate or the control plane? | It is infrastructure by shape and domain by content. Placing it wrong reintroduces a circularity. |
|
||||
| One repository per tier, or per context? | Already open from ADR 0008 as "catalogue destination — one repository or many". The skeleton assumes per tier and does not settle it. |
|
||||
| ~~Does an unprivileged node earn a place in the inventory, or only a presence?~~ | **Answered 2026-08-25** by the operator: a node is a *managed machine inside the mesh*, not an unprivileged something — and a disconnected node is still a node, in a different situation. The question posed a class distinction; the answer is that there is none, and what varies is **state**. Recorded as [ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md). |
|
||||
| ~~Does absorbing overlay, filtering, packages, supervision and the container runtime make the host too large?~~ | **Answered 2026-08-25** — [`host-size.md`](host-size.md). Measured: the absorption is smaller than the machinery that already applies state, and eight of ten adapters already carry no dependency. The risk is not size but direction, and it is two modules wide. The claim survives with its scope corrected — the host carries one concern, *apply declared state on this machine*, of which the six are instances. Recorded as [ADR 0016](../../02-DECISIONS/0016-the-node-host.md), designed in [`05-the-node-host.md`](../../03-DESIGN/01-to-be/05-the-node-host.md). |
|
||||
| One repository per tier, or per context? | Already open from ADR 0001 as "catalogue destination — one repository or many". The skeleton assumes per tier and does not settle it. |
|
||||
| ~~Does an unprivileged node earn a place in the inventory, or only a presence?~~ | **Answered 2026-08-25** by the operator: a node is a *managed machine inside the mesh*, not an unprivileged something — and a disconnected node is still a node, in a different situation. The question posed a class distinction; the answer is that there is none, and what varies is **state**. Recorded as [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md). |
|
||||
| ~~Does absorbing overlay, filtering, packages, supervision and the container runtime make the host too large?~~ | **Answered 2026-08-25** — [`host-size.md`](host-size.md). Measured: the absorption is smaller than the machinery that already applies state, and eight of ten adapters already carry no dependency. The risk is not size but direction, and it is two modules wide. The claim survives with its scope corrected — the host carries one concern, *apply declared state on this machine*, of which the six are instances. Recorded as [ADR 0005](../../02-DECISIONS/0005-the-node-host.md), designed in [`05-the-node-host.md`](../../03-DESIGN/01-to-be/05-the-node-host.md). |
|
||||
| ~~Four substrate services or five?~~ | **Answered conditionally**, which is the honest form — [`07-the-substrate.md`](../../03-DESIGN/01-to-be/07-the-substrate.md). The substrate is *what the control plane consumes and cannot grant itself*. The identity provider qualifies only if the control plane delegates authentication; if it authenticates natively it is an ordinary hosted service. The count follows from a decision not yet taken, and asserting four was asserting that decision. |
|
||||
| Does `feature` survive? | The skeleton splits it in two and argues the conflation is what makes the delivery pipeline hard to reason about. Unproven. |
|
||||
|
||||
@@ -160,8 +160,8 @@ neither option covers, and it is the most common one.
|
||||
| **Absorbed into the host** | It is not a module at all. It is part of what "managing a machine" means, and belongs in tier 0. | overlay membership, packet filtering, package management, service supervision, container runtime, filesystem management |
|
||||
| **Substrate** | The control plane cannot exist without it. Pinned, host-applied. | relational store, bus, object store, image registry |
|
||||
| **Control-plane context** | It decides something across nodes. | connectivity policy, inventory, delivery, provisioning, observability |
|
||||
| **Workload module** | The mesh hosts it. Grouped per [ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md). | media library, desktop session, collaboration tooling |
|
||||
| **Leaves the repository** | A standalone application, per [ADR 0006](../../02-DECISIONS/0006-applications-live-in-their-own-repository.md). | the applications identified in research 005 |
|
||||
| **Workload module** | The mesh hosts it. Grouped per [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md). | media library, desktop session, collaboration tooling |
|
||||
| **Leaves the repository** | A standalone application, per [ADR 0015](../../02-DECISIONS/0015-applications-live-in-their-own-repository.md). | the applications identified in research 005 |
|
||||
|
||||
**The first fate is the finding.** Research 005 measured the reachability cluster — proxy,
|
||||
resolver, firewall, overlay — as the only place in the catalogue where modules genuinely change
|
||||
@@ -256,7 +256,7 @@ mesh-surfaces/ TIER 3
|
||||
mesh-catalog/ TIER 4
|
||||
<domain>/<module>/ layout as above
|
||||
|
||||
mesh-lab/ mesh-sdk/ hq/ (company-scoped — ADR 0011)
|
||||
mesh-lab/ mesh-sdk/ hq/ (company-scoped — ADR 0019)
|
||||
```
|
||||
|
||||
## What this does not settle
|
||||
|
||||
@@ -80,12 +80,12 @@ mesh-surfaces/ TIER 3 — thin; no logic lives here
|
||||
cli/ the shell-facing interface
|
||||
|
||||
mesh-catalog/ TIER 4 — what the mesh hosts
|
||||
<domain>/ grouped per ADR 0017, list per research 005
|
||||
<domain>/ grouped per ADR 0022, list per research 005
|
||||
|
||||
mesh-lab/ the whole mesh, disposable, on one machine
|
||||
mesh-sdk/ contracts shared across tiers — types, not behaviour
|
||||
|
||||
hq/ company-scoped, not a mesh repository — ADR 0011
|
||||
hq/ company-scoped, not a mesh repository — ADR 0019
|
||||
```
|
||||
|
||||
## The dependency rule
|
||||
@@ -97,7 +97,7 @@ a second surface would have to reimplement.
|
||||
This is the whole of the bootstrap answer, and per this repository's own rule it must say how
|
||||
it is checked: a dependency-direction lint in the build, failing on an upward import. A tier
|
||||
rule enforced by intention is the same as no tier rule — that is
|
||||
[ADR 0023](../../02-DECISIONS/0023-delivery.md) applied to architecture.
|
||||
[ADR 0010](../../02-DECISIONS/0010-delivery.md) applied to architecture.
|
||||
|
||||
## Move 1 — the substrate is applied, not delivered
|
||||
|
||||
@@ -144,7 +144,7 @@ assumption that every node is equivalent — already false, and today handled by
|
||||
|
||||
## Move 3 — connectivity becomes a context
|
||||
|
||||
[ADR 0008](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md) names nine contexts
|
||||
[ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) names nine contexts
|
||||
and none of them owns the overlay, the resolver, the firewall or the ingress. `config` owns
|
||||
PKI, which is the closest thing, and it is not close.
|
||||
|
||||
@@ -162,8 +162,8 @@ So the evidence and the gap point the same way. `connectivity` owns:
|
||||
- certificates for both name spaces
|
||||
|
||||
This is an addition to an accepted record, so it is a decision, not a drafting choice. It
|
||||
belongs in a new record that extends ADR 0008 the way
|
||||
[ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md) does —
|
||||
belongs in a new record that extends ADR 0001 the way
|
||||
[ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) does —
|
||||
not written here.
|
||||
|
||||
### Where the networking actually lives
|
||||
@@ -195,7 +195,7 @@ The invitation was to check whether the concept survives. It does not, in one pi
|
||||
|
||||
Today a **feature** means both *a thing built once* and *a thing selected per node*, and the
|
||||
delivery pipeline is hard to reason about precisely because those have different cardinality
|
||||
and one word ([ADR 0023](../../02-DECISIONS/0023-delivery.md)
|
||||
and one word ([ADR 0010](../../02-DECISIONS/0010-delivery.md)
|
||||
is the pipeline half of the same confusion).
|
||||
|
||||
Split it:
|
||||
@@ -230,7 +230,7 @@ the fact that it runs its own development on them is dogfooding, not architectur
|
||||
## What agents are, structurally
|
||||
|
||||
Self-improvement and self-healing are not a tier. Agents are participants
|
||||
([ADR 0007](../../02-DECISIONS/0007-agents-are-persistent-employees.md)) that hold identity in
|
||||
([ADR 0003](../../02-DECISIONS/0003-agents-are-persistent-employees.md)) that hold identity in
|
||||
tier 2, act through tier 3 like any other caller, and run as workloads in tier 4.
|
||||
|
||||
This matters for one reason: **an agent must not have a privileged path**. Anything an agent
|
||||
@@ -241,7 +241,7 @@ no human checkpoint.
|
||||
|
||||
## How this is tested
|
||||
|
||||
The lab ([ADR 0009](../../02-DECISIONS/0009-the-lab.md)) raises the
|
||||
The lab ([ADR 0016](../../02-DECISIONS/0016-the-lab.md)) raises the
|
||||
tree above on one machine: virtual machines as nodes, a real overlay between them, the real
|
||||
substrate bundle, the real control plane, the real delivery path.
|
||||
|
||||
@@ -254,7 +254,7 @@ being the one thing nobody exercises until it breaks.
|
||||
|
||||
- Where the record lives. It is infrastructure by shape and domain by content, and putting it
|
||||
in the substrate risks recreating a circularity in the one place the design just removed one.
|
||||
- Whether tier 2's contexts are one repository or several. Open from ADR 0008 already.
|
||||
- Whether tier 2's contexts are one repository or several. Open from ADR 0001 already.
|
||||
- Whether an `edge` node is in the inventory or merely present — which decides whether "node"
|
||||
is one concept or two.
|
||||
- The migration. Nothing here says how today's mesh becomes this, and the skeleton is worth
|
||||
@@ -266,13 +266,13 @@ The tier-0 binary was first called `mesh-agent`, because "node agent" is the ref
|
||||
else in the industry. That is wrong here, and wrong in the specific way
|
||||
[`how-we-build.md`](../../00-META/how-we-build.md) §4 exists to catch: **Agent** is a
|
||||
first-class concept in this mesh — a participant, some of whom are human, holding identity and
|
||||
memory ([ADR 0007](../../02-DECISIONS/0007-agents-are-persistent-employees.md)). One document
|
||||
memory ([ADR 0003](../../02-DECISIONS/0003-agents-are-persistent-employees.md)). One document
|
||||
carried both meanings.
|
||||
|
||||
It is the same failure as the anatomy naming in the current runtime: an evocative domain word
|
||||
pointing at infrastructure.
|
||||
|
||||
[ADR 0008](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md) supplies the fix in
|
||||
[ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) supplies the fix in
|
||||
its own title — *the mesh brokers capabilities; nodes host; agents think.* Three verbs, three
|
||||
components: the control plane **brokers** (`mesh-control`), the tier-0 binary **hosts**
|
||||
(`mesh-host`), the participant **thinks** (`agents`, untouched).
|
||||
|
||||
@@ -3,7 +3,7 @@ status: active
|
||||
initiated: 2026-08-23
|
||||
touches:
|
||||
- 03-DESIGN/00-as-is/03-provisioning.md
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 01-RESEARCH/006-mesh-from-scratch/code-skeleton.md
|
||||
became: []
|
||||
---
|
||||
@@ -14,7 +14,7 @@ became: []
|
||||
|
||||
Provisioning is the mechanism the whole mesh rests on: a module declares what it needs, and the
|
||||
mesh makes it exist, generates the credential, records the grant, and puts the values where the
|
||||
module will read them. [ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md)
|
||||
module will read them. [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md)
|
||||
calls it the mesh's core concern rather than its plumbing.
|
||||
|
||||
[Research 006](../006-mesh-from-scratch/code-skeleton.md) then asks it to carry **more**: the
|
||||
|
||||
@@ -3,12 +3,12 @@ status: graduated
|
||||
initiated: 2026-08-23
|
||||
touches:
|
||||
- 03-DESIGN/00-as-is/04-delivery.md
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
- 01-RESEARCH/006-mesh-from-scratch/code-skeleton.md
|
||||
became:
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
---
|
||||
|
||||
# 008 — The coordinator: a change checked in becomes a deployed state
|
||||
@@ -49,14 +49,14 @@ working across the transition to self-hosted providers.
|
||||
because the first was honest about what it did not fix.
|
||||
|
||||
**Does the coordinator dispatch stages, or converge nodes on a declaration?** — *Converge.*
|
||||
[ADR 0023](../../02-DECISIONS/0023-delivery.md): a pipeline ends when the
|
||||
[ADR 0010](../../02-DECISIONS/0010-delivery.md): a pipeline ends when the
|
||||
declaration is updated, and the host applies it and reads back — so the reporter is the applier.
|
||||
|
||||
**Does the three-silo split survive?** — *Yes, with the third redefined.* The cardinality
|
||||
observation holds; the third silo is not a stage any more.
|
||||
|
||||
**How does a change become a pipeline, reliably?** — *It does not become a pipeline at all.*
|
||||
[ADR 0023](../../02-DECISIONS/0023-delivery.md) applies 0058's
|
||||
[ADR 0010](../../02-DECISIONS/0010-delivery.md) applies 0058's
|
||||
move one level up: the control plane holds what source exists and what has been built, and builds
|
||||
the difference. **An event makes it fast; nothing makes it necessary.** The failures this effort
|
||||
catalogued — a truncated commit list, a broken path match — become latency rather than silence.
|
||||
@@ -83,7 +83,7 @@ load-bearing question first.
|
||||
|
||||
## What is NOT closed by this
|
||||
|
||||
[ADR 0023](../../02-DECISIONS/0023-delivery.md) names four costs
|
||||
[ADR 0010](../../02-DECISIONS/0010-delivery.md) names four costs
|
||||
and one of them is a real risk rather than a trade: **a reconciler that cannot reach its target
|
||||
retries forever, and without something that notices, the failure is silence** — which is the
|
||||
fault this effort exists to catalogue, reintroduced in a new place. That belongs to observability
|
||||
@@ -96,6 +96,6 @@ and it is not designed.
|
||||
| What is a **deployed state**, and how does the mesh know it is in one? | Everything follows from this. If a stage reports transport, "deployed" is a claim nobody checked. A desired-state model with reconciliation gives a different answer from a job-completion model. |
|
||||
| Does the coordinator dispatch **stages**, or converge nodes on a **declaration**? | The current model is a state machine over stages. The alternative is that a node is told what should be true and reports what is. The second makes drift visible; the first cannot see it. |
|
||||
| How does a change **become** a pipeline, reliably? | Detection has failed for reasons unrelated to the change, silently. |
|
||||
| What produces a **verdict**, and what is it a verdict about? | Ties to the lab ([ADR 0009](../../02-DECISIONS/0009-the-lab.md)) and to a module carrying its own assertions. |
|
||||
| What produces a **verdict**, and what is it a verdict about? | Ties to the lab ([ADR 0016](../../02-DECISIONS/0016-the-lab.md)) and to a module carrying its own assertions. |
|
||||
| How does delivery work **before self-hosting**, and across the transition? | From research 006: source and artifacts start external and are re-bound to internal providers. The coordinator has to be indifferent to which. |
|
||||
| Does the **three-silo** split survive the artifact/part split? | [ADR 0023](../../02-DECISIONS/0023-delivery.md) is cardinality-driven, and research 006 renames the thing the cardinality is about. |
|
||||
| Does the **three-silo** split survive the artifact/part split? | [ADR 0010](../../02-DECISIONS/0010-delivery.md) is cardinality-driven, and research 006 renames the thing the cardinality is about. |
|
||||
|
||||
@@ -4,7 +4,7 @@ initiated: 2026-08-23
|
||||
touches:
|
||||
- 01-RESEARCH/006-mesh-from-scratch/code-skeleton.md
|
||||
- 03-DESIGN/01-to-be/01-end-to-end-testing.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 03-DESIGN/00-as-is/00-overview.md
|
||||
became: []
|
||||
---
|
||||
@@ -28,7 +28,7 @@ Recorded because incremental is the reflex answer and it is wrong in this case.
|
||||
requirements — none of these can half-apply. Running both models at once means the old one's
|
||||
assumptions keep constraining the new one, which is how a migration becomes permanent.
|
||||
- **Nothing external depends on it.** No users outside the operator, no service level to hold.
|
||||
- **The lab exists precisely for this** ([ADR 0009](../../02-DECISIONS/0009-the-lab.md)).
|
||||
- **The lab exists precisely for this** ([ADR 0016](../../02-DECISIONS/0016-the-lab.md)).
|
||||
A big-bang that has been rehearsed end to end, repeatedly, on identical machines is not the
|
||||
same risk as one performed for the first time on the real mesh. This is also why the lab is
|
||||
phase 0 rather than a verification step later: the new mesh is *developed* inside it, so by
|
||||
@@ -70,7 +70,7 @@ than a discovery.
|
||||
|
||||
| Phase | What | Done when |
|
||||
|---|---|---|
|
||||
| **0** | **Build the lab's bootstrap scenario** ([ADR 0009](../../02-DECISIONS/0009-the-lab.md)) — virtual machines, a network, a way to place a binary, snapshot and reset. No forge, no coordinator, no pipeline. | A machine can be raised from nothing, reset, and raised again, repeatably. |
|
||||
| **0** | **Build the lab's bootstrap scenario** ([ADR 0016](../../02-DECISIONS/0016-the-lab.md)) — virtual machines, a network, a way to place a binary, snapshot and reset. No forge, no coordinator, no pipeline. | A machine can be raised from nothing, reset, and raised again, repeatably. |
|
||||
| A | Build tier 0, **inside the lab**. The host's interface first — it carries the skeleton's biggest unproven claim. | A bare machine becomes a managed node with no mesh present. |
|
||||
| B | Build tier 1 and 2. The bootstrap scenario grows into the full one by addition — the same machines, with more placed inside them. | The lab raises a full mesh from nothing, repeatedly, from pinned external artifacts. |
|
||||
| C | Enough of tier 3 to operate it. | The mesh can be driven without direct database access. |
|
||||
|
||||
@@ -3,8 +3,8 @@ status: active
|
||||
initiated: 2026-08-24
|
||||
touches:
|
||||
- 03-DESIGN/01-to-be/03-scenario-lifecycle.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
became: []
|
||||
---
|
||||
|
||||
@@ -23,7 +23,7 @@ Measured on a workstation, 2026-08-24. Numbers in [`measurements.md`](measuremen
|
||||
|
||||
## Why it matters
|
||||
|
||||
[ADR 0009](../../02-DECISIONS/0009-the-lab.md) makes the
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-lab.md) makes the
|
||||
bootstrap scenario the inner development loop for tiers 0 and 1 — the argument being that
|
||||
raising a node from nothing stops being the least-exercised path and becomes the most-exercised
|
||||
one. **That argument is only true if raising and resetting are cheap.** A loop that costs
|
||||
|
||||
@@ -13,7 +13,7 @@ image.
|
||||
|
||||
| Fact | Value | Consequence |
|
||||
|---|---|---|
|
||||
| Hardware virtualisation | present | virtual machines run at native speed; the choice in [ADR 0009](../../02-DECISIONS/0009-the-lab.md) is not paying an emulation penalty |
|
||||
| Hardware virtualisation | present | virtual machines run at native speed; the choice in [ADR 0016](../../02-DECISIONS/0016-the-lab.md) is not paying an emulation penalty |
|
||||
| Storage drivers the daemon offers | **`dir` only** | no copy-on-write, therefore no cheap snapshot |
|
||||
| Host filesystems | ext4 throughout | nothing copy-on-write to put a pool on |
|
||||
| btrfs kernel module | **available** | the kernel can do it |
|
||||
@@ -72,7 +72,7 @@ worst, before any of the mesh's own work begins.
|
||||
|
||||
**This is too slow for an inner loop**, and the reason is not the design.
|
||||
|
||||
[ADR 0009](../../02-DECISIONS/0009-the-lab.md) argues that
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-lab.md) argues that
|
||||
making the bootstrap path the inner development loop turns the least-exercised code in the
|
||||
system into the most-exercised. That argument holds only while resetting is cheap. At a minute
|
||||
and a half a cycle, with occasional multi-minute stalls, the loop is one a person works around
|
||||
@@ -120,7 +120,7 @@ A four-machine reset-and-rerun cycle, the operation the inner loop repeats most:
|
||||
| **cycle** | **~90 s, unbounded at worst** | **~15 s, dominated by boot** |
|
||||
|
||||
At fifteen seconds, dominated by a boot that cannot be avoided, the inner loop is viable and
|
||||
[ADR 0009](../../02-DECISIONS/0009-the-lab.md)'s argument holds.
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-lab.md)'s argument holds.
|
||||
At ninety it did not.
|
||||
|
||||
### One honest counter-observation
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
status: graduated
|
||||
initiated: 2026-08-25
|
||||
became:
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0020-a-context-owns-its-store.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0008-a-context-owns-its-store.md
|
||||
- 03-DESIGN/01-to-be/06-the-control-plane.md
|
||||
- 03-DESIGN/01-to-be/07-the-substrate.md
|
||||
touches:
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0015-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
||||
- 03-DESIGN/00-as-is/02-modules-and-manifests.md
|
||||
- 03-DESIGN/00-as-is/10-module-catalogue.md
|
||||
- 04-ISSUES/007-an-installed-package-is-not-a-capability/00-report.md
|
||||
@@ -21,9 +21,9 @@ touches:
|
||||
> *instantiation*, and both are **runtime** edges — they answer *what does this need in order to
|
||||
> run*. Delivery needs a different question answered — *what has to be rebuilt when this changes*
|
||||
> — and that is a **build** edge, fixed inside an artifact rather than negotiated when it runs.
|
||||
> Recorded by [ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md), which also
|
||||
> Recorded by [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md), which also
|
||||
> notes what this effort's three entities turn out to be good for
|
||||
> ([ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md)).
|
||||
> ([ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md)).
|
||||
|
||||
## What is being investigated
|
||||
|
||||
@@ -74,12 +74,12 @@ What survives is narrower: three declarations that do not exist (`excludes`, a r
|
||||
capability, an interface with adapters), and two defects worth fixing whatever else is
|
||||
concluded — `provider:` is a dependency edge that is not read as one, which makes the closure
|
||||
for a working mesh come out without a database; and the resolver continues past a cycle and
|
||||
past a missing dependency, contrary to ADR 0008.
|
||||
past a missing dependency, contrary to ADR 0001.
|
||||
|
||||
[ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md) proposes
|
||||
[ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) proposes
|
||||
grouping modules by domain. [Research 005](../005-domain-grouping/analysis.md) measured that
|
||||
proposal and found its evidence holds in exactly one place — reachability — which
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-node-host.md) has since absorbed
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md) 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.
|
||||
|
||||
@@ -102,7 +102,7 @@ and abandoned in favour of one concept with facets, for a reason worth keeping:
|
||||
Filing decisions that follow from nothing are the disease research 005 measured. A second
|
||||
taxonomy would reproduce it.
|
||||
|
||||
So [ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md) survives, and the question
|
||||
So [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) survives, and the question
|
||||
becomes what a module must be able to **declare**.
|
||||
|
||||
## The shape being investigated
|
||||
@@ -111,7 +111,7 @@ Five declarations, of which two exist today.
|
||||
|
||||
| Declaration | Today | Notes |
|
||||
|---|---|---|
|
||||
| **requires a resource** — a database, a bucket | yes | provisioning, [ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md) |
|
||||
| **requires a resource** — a database, a bucket | yes | provisioning, [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) |
|
||||
| **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 |
|
||||
@@ -174,12 +174,12 @@ Struck-through rows are answered, with where. The rest are live.
|
||||
| ~~What does the graph **delete**?~~ | For the existing system: nothing, it is already there ([`analysis.md`](analysis.md)). For the design: the module/resource distinction, the interface as a kind of thing, capability checking as a separate mechanism, domain grouping, and — the first clear deletion — **grant kinds**, once a module may only be granted what it exclusively owns ([`worked-provider.md`](worked-provider.md)). |
|
||||
| ~~Is an interface a module, or a name?~~ | A **name**, and only where providers are genuinely substitutable. The adapter is what creates one; without an adapter there is a **tag**, which describes and does not bind ([`proposal.md`](proposal.md)). |
|
||||
| ~~Where do domain modules fit?~~ | They do not. There is core infrastructure — concrete modules named individually, not flavourable, nothing standing in front of them. |
|
||||
| ~~What happens to domain grouping?~~ | Superseded. Folders assert relationships; edges record them. What grouping was for is a tag and a query. [ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md) is `proposed` and should be superseded rather than narrowed. |
|
||||
| ~~What happens to domain grouping?~~ | Superseded. Folders assert relationships; edges record them. What grouping was for is a tag and a query. [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) is `proposed` and should be superseded rather than narrowed. |
|
||||
| ~~Is there one kind of edge?~~ | **No — two.** *Presence*, where a thing must exist, and *instantiation*, where a provider makes something for a consumer and hands back credentials. Instantiation implies presence, not the reverse. |
|
||||
| ~~When two modules provide one name, who chooses?~~ | Neither the consumer naming a node nor the consumer not caring. The consumer declares the **scope of its own need** — shared across its instances, or one each — the mesh binds, and the binding is written down and sticky. Where it is written follows the scope. |
|
||||
| ~~Can several modules share one database?~~ | **No.** A module is granted only what it exclusively owns — no shared writes and no read role on another's store, because reading couples you to its layout just as firmly. |
|
||||
| ~~What about a dashboard reading a dozen stores?~~ | **The rule is about contexts, not processes.** The mesh's own board reading the mesh's own store is the mesh showing its own data — not a boundary crossing. Everything inside a context reads its store freely; what is forbidden is a *different* context reading it. An earlier answer here was wrong. |
|
||||
| ~~Can every registry consumer be served another way?~~ | **Largely dissolves.** Of eighteen direct consumers, the owner keeps its database, node appliers are already stopped by ADR 0016, and the bulk are **foreign tenants** — thirteen tables across three contexts — who need to move out rather than read differently. |
|
||||
| ~~Can every registry consumer be served another way?~~ | **Largely dissolves.** Of eighteen direct consumers, the owner keeps its database, node appliers are already stopped by ADR 0005, and the bulk are **foreign tenants** — thirteen tables across three contexts — who need to move out rather than read differently. |
|
||||
|
||||
### Live
|
||||
|
||||
@@ -190,10 +190,10 @@ Struck-through rows are answered, with where. The rest are live.
|
||||
| What does a provider hand back? | Credentials and an address for a store; a command for a terminal. Same relation, different shape crossing it. |
|
||||
| Is provisioning one mechanism or two? | The mesh's own registry is provisioned **before there is a mesh**, so provisioning is part of the bootstrap and part of what the carried bundle expresses. At bootstrap the store is local; afterwards it is on another node. Same operation, both sides of a tier boundary. |
|
||||
| Is a tool surface one relation with two audiences, or two? | 56 of 126 modules carry tools — more than carry a service — and what consumes them is an **agent**, not a module. |
|
||||
| ~~Do the remaining cross-context reads want an interface or events?~~ | **Derived, not chosen.** Neither is SQL — that only ever runs against your own store. ADR 0015 makes disconnection ordinary, so anything that must work while disconnected cannot use a request and needs a local copy: a subscription. Anything where a stale answer is worse than none cannot use a subscription. |
|
||||
| ~~Do the remaining cross-context reads want an interface or events?~~ | **Derived, not chosen.** Neither is SQL — that only ever runs against your own store. ADR 0004 makes disconnection ordinary, so anything that must work while disconnected cannot use a request and needs a local copy: a subscription. Anything where a stale answer is worse than none cannot use a subscription. |
|
||||
| What does a consumer do about events it missed while disconnected? | Replay from a point, ask once for a full picture and resume, or rebuild. The question every projection has, and unanswered here. |
|
||||
| What happens to a grant when its consumer is removed? | Dropping is data loss; keeping is a leak. ADR 0016's removal rule does not obviously carry, because the thing lives inside another module's state. |
|
||||
| Is a declaration composed per node, from what that node reported? | Some configuration follows the hardware. Either the host fills a blank — deciding, against ADR 0016 — or the control plane composes from the node's inventory first. |
|
||||
| What happens to a grant when its consumer is removed? | Dropping is data loss; keeping is a leak. ADR 0005's removal rule does not obviously carry, because the thing lives inside another module's state. |
|
||||
| Is a declaration composed per node, from what that node reported? | Some configuration follows the hardware. Either the host fills a blank — deciding, against ADR 0005 — or the control plane composes from the node's inventory first. |
|
||||
| Would `excludes` and capability requirements actually be used? | Zero manifests declare either, which is equally consistent with *nobody needs them* and *nobody can express them*. |
|
||||
| What does an exclusion mean for something already installed? | Refuse the install, or surface the conflict and let it be decided. |
|
||||
| Are tiers a view of the graph, or a constraint on it? | If a tier is a computed level the word is a convenience. If *a tier may depend only on tiers below it* is to be enforced, it is a constraint and must be stated as one. |
|
||||
|
||||
@@ -36,7 +36,7 @@ another module's provision is treated as an implicit edge to that module**, so a
|
||||
not have to declare the same relationship twice.
|
||||
|
||||
So *ordering by the graph* — which
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-node-host.md) says
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md) says
|
||||
the control plane will do — is not a thing to build. It is a thing to call.
|
||||
|
||||
## Finding 3 — the most important edges in the mesh are invisible
|
||||
@@ -82,7 +82,7 @@ means.
|
||||
## Finding 4 — the resolver continues past faults it should stop on
|
||||
|
||||
Two behaviours, both contrary to
|
||||
[ADR 0023](../../02-DECISIONS/0023-delivery.md):
|
||||
[ADR 0010](../../02-DECISIONS/0010-delivery.md):
|
||||
|
||||
- **A cycle warns and falls back to input order.** A cycle means no correct order exists; the
|
||||
resolver proceeds with an arbitrary one and logs a line.
|
||||
@@ -91,7 +91,7 @@ Two behaviours, both contrary to
|
||||
|
||||
Neither has fired in the current catalogue — there are no cycles and nothing dangling — which
|
||||
is why nobody has noticed. They are latent, and they are in the component that
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-node-host.md) makes
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md) makes
|
||||
responsible for the ordering a host will apply without question.
|
||||
|
||||
## Finding 5 — placement is decided in the catalogue
|
||||
|
||||
@@ -13,7 +13,7 @@ it is simply up. *A relational store, a message broker, an object store, a dashb
|
||||
|
||||
**2 — A system package with configuration.** Not a container. Installed into the machine,
|
||||
configured through files, run by the service manager. *A firewall, a resolver, an overlay.*
|
||||
Note: [ADR 0016](../../02-DECISIONS/0016-the-node-host.md) says applying
|
||||
Note: [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) says applying
|
||||
these is the host's job — so what the module contributes is the *deciding*, not the doing.
|
||||
|
||||
**3 — An application a person launches.** Installed on a node, started by a human, running only
|
||||
@@ -35,7 +35,7 @@ provider behind an assistant interface.*
|
||||
|
||||
**9 — A standalone application in its own repository.** Same shape as any of the above; the
|
||||
difference is only where its source lives
|
||||
([ADR 0006](../../02-DECISIONS/0006-applications-live-in-their-own-repository.md)). Worth
|
||||
([ADR 0015](../../02-DECISIONS/0015-applications-live-in-their-own-repository.md)). Worth
|
||||
listing because a schema that assumes a monorepo path would exclude it.
|
||||
|
||||
## The cases that break a naive schema
|
||||
|
||||
@@ -55,7 +55,7 @@ registry. **Tier 2, delivery.**
|
||||
|
||||
**Resources — desired state on a machine.** `configs`, `service`, `systemd`, `vhost`, `tools`.
|
||||
Applied, converged, idempotent — which is exactly what
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-node-host.md)
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md)
|
||||
already describes and what the host already does. **Tier 0.**
|
||||
|
||||
**Actions — run once, against something that is not this machine.** `migrations`, `seeds`,
|
||||
|
||||
@@ -132,7 +132,7 @@ The question the effort opened with, answered for the design rather than for wha
|
||||
to install. There is **core infrastructure**, which is a set of concrete modules named
|
||||
individually — a firewall, a store, a resolver — with no flavour and no grouping module
|
||||
standing in front of them.
|
||||
- **Domain grouping as structure** ([ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md)).
|
||||
- **Domain grouping as structure** ([ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md)).
|
||||
Folders assert relationships; edges record them. What grouping was for — finding things,
|
||||
seeing what belongs together — is a **tag** and a *query* over the graph, neither of which
|
||||
anybody has to keep true by hand.
|
||||
|
||||
@@ -149,7 +149,7 @@ Steps 2 and 3 happen **before there is a mesh to do them**. So provisioning is n
|
||||
control-plane service that consumers use; it is part of the bootstrap, and part of what the
|
||||
carried bundle has to be able to express.
|
||||
|
||||
**Which strains what a declaration is.** [ADR 0016](../../02-DECISIONS/0016-the-node-host.md)
|
||||
**Which strains what a declaration is.** [ADR 0005](../../02-DECISIONS/0005-the-node-host.md)
|
||||
has the host applying *declared state on this machine*. A database inside a running store is not
|
||||
a file or a unit — and at bootstrap it is, at least, local: the store is on the same machine as
|
||||
the host applying the bundle.
|
||||
@@ -158,7 +158,7 @@ Later it is not. A consumer on one node provisioned from a store on another is t
|
||||
case, and reaching it is not the host's job.
|
||||
|
||||
**Resolved as two mechanisms, which is the answer rather than a compromise**
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)). The host
|
||||
([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). The host
|
||||
runs bootstrap actions locally from the bundle; the control plane provisions across the mesh
|
||||
afterwards. Different actors, different scopes, different trust paths — so there is no single
|
||||
operation with a tier boundary running through it.
|
||||
@@ -279,7 +279,7 @@ of them is work.
|
||||
| Group | What happens under the rule |
|
||||
|---|---|
|
||||
| **The owner and its machinery** — the mesh module, the SDK, the environment and configuration synchronisers, secrets | Nothing. It owns the database. |
|
||||
| **Node appliers** — the overlay, the shell daemon, the resolver | **Already resolved.** [ADR 0016](../../02-DECISIONS/0016-the-node-host.md) stops the host querying the mesh database, decided for tier reasons with nothing to do with this. |
|
||||
| **Node appliers** — the overlay, the shell daemon, the resolver | **Already resolved.** [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) stops the host querying the mesh database, decided for tier reasons with nothing to do with this. |
|
||||
| **Foreign tenants** — the work engine (10 tables), the knowledge base (2), pipeline logs (1) | They need **their own database**. They are not reading the registry; they are storing their own data in it. |
|
||||
| **Genuine cross-context reads** — the work engine reads `nodes`; two others read a handful | The only ones needing an interface or events. |
|
||||
|
||||
@@ -299,7 +299,7 @@ estimate. **The rule holds.**
|
||||
The remaining cross-context reads need one or the other. **Neither is SQL** — under exclusive
|
||||
ownership a module runs SQL against its own database and nothing else, whatever transport a
|
||||
query might travel over. Both options are the mesh's own channel, and both ride the broker
|
||||
([ADR 0001](../../02-DECISIONS/0001-nodes-communicate-over-a-broker.md)), so the transport is
|
||||
([ADR 0002](../../02-DECISIONS/0002-nodes-communicate-over-a-broker.md)), so the transport is
|
||||
not the distinction.
|
||||
|
||||
**The distinction is where the answer lives when you need it.**
|
||||
@@ -312,7 +312,7 @@ not the distinction.
|
||||
| when the other side is down | you cannot answer | you answer from your copy |
|
||||
| what you must handle | a round trip that can fail | events you missed while you were down |
|
||||
|
||||
**What decides is not taste.** [ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)
|
||||
**What decides is not taste.** [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)
|
||||
makes disconnection an ordinary situation rather than an exception. So:
|
||||
|
||||
> **Anything that must keep working while disconnected cannot use a request** — there is nobody
|
||||
@@ -354,14 +354,14 @@ proves it cannot be a global rule.
|
||||
|
||||
**What happens to a grant when the consumer is removed?** The game is uninstalled. Its database
|
||||
still exists, holding its data. Dropping it silently is data loss; keeping it forever is a leak.
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-node-host.md) says
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md) says
|
||||
the host removes what it applied and no longer declares — but this is not on the host, it is
|
||||
inside another module's state, and the same reasoning does not obviously carry.
|
||||
|
||||
**Where does node-derived configuration come from?** (3) The control plane composes a
|
||||
declaration, and cannot know this machine's memory. Either the host fills in a blank the
|
||||
declaration leaves — which makes the host decide something, against
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-node-host.md) — or the control
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md) — or the control
|
||||
plane reads the node's inventory first and composes with it. The second is consistent and means
|
||||
a declaration is composed *per node from what the node reported*, which is a stronger claim than
|
||||
anything recorded so far.
|
||||
@@ -419,9 +419,9 @@ But two things differ *between* them, and both matter more than the similarity.
|
||||
|
||||
### The broker cannot be managed over the broker
|
||||
|
||||
[ADR 0001](../../02-DECISIONS/0001-nodes-communicate-over-a-broker.md) makes the broker the
|
||||
[ADR 0002](../../02-DECISIONS/0002-nodes-communicate-over-a-broker.md) makes the broker the
|
||||
channel every node takes work from, and
|
||||
[ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md) makes it the security
|
||||
[ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) makes it the security
|
||||
boundary — everything a node applies arrives through it.
|
||||
|
||||
So the module providing the broker is also **the way modules are managed**. A declaration cannot
|
||||
@@ -430,7 +430,7 @@ reconfigured. Nothing else in the catalogue has that property; the store is cons
|
||||
control plane but is not how the control plane *reaches* anything.
|
||||
|
||||
This is exactly what the carried bundle exists for
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)): the broker is raised from
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)): the broker is raised from
|
||||
what the host carries, before there is a channel, because there is no other way to raise it.
|
||||
Recorded here because it is a constraint on *one module*, not a general rule, and a schema with
|
||||
no way to say so hides it.
|
||||
@@ -439,7 +439,7 @@ no way to say so hides it.
|
||||
|
||||
The broker is one per mesh — a single point of failure and a single point of trust, by decision
|
||||
rather than by accident. The store cannot be: a node that must keep working while disconnected
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)) cannot depend on a database
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)) cannot depend on a database
|
||||
somewhere else.
|
||||
|
||||
Same nine properties, opposite answers. Which settles something the cases file left open: **how
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
status: active
|
||||
initiated: 2026-08-26
|
||||
touches:
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0002-managed-files-are-generated-never-edited.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0011-managed-files-are-generated-never-edited.md
|
||||
- 03-DESIGN/01-to-be/05-the-node-host.md
|
||||
- 03-DESIGN/00-as-is/05-runtime-and-installation.md
|
||||
- 01-RESEARCH/011-the-module-graph/00-overview.md
|
||||
@@ -34,7 +34,7 @@ carry everything in the bundle, download at apply time, or have something push t
|
||||
first. Downloading fails on the first node, which cannot fetch the image registry from the image
|
||||
registry it is trying to start.
|
||||
|
||||
> **Qualified by [ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md).** The
|
||||
> **Qualified by [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md).** The
|
||||
> reframing below still holds for what a *tailored installer* contains — the missing pieces for a
|
||||
> given machine. It does **not** have to hold for container images: the installer fetches those
|
||||
> by digest, because a real machine has a network and the sealed case is the lab.
|
||||
@@ -71,13 +71,13 @@ records adoption of a pre-existing machine's configuration as the original mecha
|
||||
legacy and explicitly out of scope for the lab. It returns here for a different reason than it
|
||||
was dropped for, which is a thing to notice rather than to gloss.
|
||||
|
||||
**It creates a state that does not exist today.** [ADR 0002](../../02-DECISIONS/0002-managed-files-are-generated-never-edited.md)
|
||||
**It creates a state that does not exist today.** [ADR 0011](../../02-DECISIONS/0011-managed-files-are-generated-never-edited.md)
|
||||
has managed files generated and never edited; adoption needs a one-time import before that rule
|
||||
starts applying. Three states, and the middle one is new:
|
||||
|
||||
> unmanaged → **adopted once** → generated
|
||||
|
||||
**And it crosses a boundary just drawn.** [ADR 0016](../../02-DECISIONS/0016-the-node-host.md)
|
||||
**And it crosses a boundary just drawn.** [ADR 0005](../../02-DECISIONS/0005-the-node-host.md)
|
||||
says the host never touches what it did not create — the rule that stops a converger deleting
|
||||
what the mesh never put there. Adoption is the deliberate act of taking ownership of exactly
|
||||
that. The rule needs a companion rather than an exception: *never, unless adoption made it the
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 8. The mesh brokers capabilities; nodes host; agents think
|
||||
# 1. The mesh brokers capabilities; nodes host; agents think
|
||||
|
||||
## Context
|
||||
|
||||
@@ -182,7 +182,7 @@ existing pipeline. Nothing here requires a flag day, and nothing here is cheap.
|
||||
- `modules/hal/sdk/src/feature-handlers/index.ts` — `FEATURE_HANDLERS`, the fixed handler
|
||||
array that makes a feature a singleton per module
|
||||
- `modules/postgres/tools/index.ts` — the adoption path that rotates a shared credential
|
||||
- Mediahuis `papa-hq`, ADR 0005 *Composable, independently-shippable modules* — the
|
||||
- Mediahuis `papa-hq`, ADR 0020 *Composable, independently-shippable modules* — the
|
||||
constraints that make a unit independently shippable, applicable unchanged to features
|
||||
- impire.io / soulstream — *the record* as integration substrate, personas over services,
|
||||
and "cheap awareness and expensive thinking"
|
||||
+1
-1
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: true
|
||||
---
|
||||
|
||||
# 1. Nodes communicate over a message broker, not over HTTP
|
||||
# 2. Nodes communicate over a message broker, not over HTTP
|
||||
|
||||
> Reconstructed after the fact from the evidence cited below. The decision was taken in
|
||||
> implementation, not in a record; this document states what was decided and why, not a
|
||||
+2
-2
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: true
|
||||
---
|
||||
|
||||
# 7. An agent is a persistent employee, not an instance of a pool
|
||||
# 3. An agent is a persistent employee, not an instance of a pool
|
||||
|
||||
> Reconstructed after the fact from the evidence cited below.
|
||||
|
||||
@@ -59,7 +59,7 @@ itself is an agent of a kind exempt from the hiring lifecycle.
|
||||
or another agent is hired — both deliberate acts.
|
||||
- The transition was not free. Lifecycle columns had to reach every query that selects an
|
||||
agent, and the ones that were missed failed at the moment of hiring rather than at startup.
|
||||
- This is the decision [ADR 0008](0008-mesh-brokers-nodes-host-agents-think.md) generalises:
|
||||
- This is the decision [ADR 0001](0001-mesh-brokers-nodes-host-agents-think.md) generalises:
|
||||
one kind of participant, differing only in modality.
|
||||
|
||||
## References
|
||||
+1
-1
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 15. A node, and how it joins
|
||||
# 4. A node, and how it joins
|
||||
|
||||
*Consolidated 2026-08-28 from four records.*
|
||||
|
||||
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 16. The node host
|
||||
# 5. The node host
|
||||
|
||||
*Consolidated 2026-08-28 from eight records. Tier 0 is one component and was decided over a
|
||||
week; the reasoning is kept, the fragmentation is not.*
|
||||
+1
-1
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 21. The substrate and the control plane
|
||||
# 6. The substrate and the control plane
|
||||
|
||||
*Consolidated 2026-08-28 from six records.*
|
||||
|
||||
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 22. Connectivity
|
||||
# 7. Connectivity
|
||||
|
||||
*Consolidated 2026-08-28 from three records. Overlay, resolution, exposure, filtering and
|
||||
certificates are one design.*
|
||||
+6
-6
@@ -3,14 +3,14 @@ status: accepted
|
||||
date: 2026-08-26
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 0019-modules-and-the-graph.md
|
||||
extends: 0009-modules-and-the-graph.md
|
||||
---
|
||||
|
||||
# 20. A context owns its store, exclusively
|
||||
# 8. A context owns its store, exclusively
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0019](0019-modules-and-the-graph.md) settles what a module
|
||||
[ADR 0009](0009-modules-and-the-graph.md) settles what a module
|
||||
declares. This settles what a grant may be, and it is the half that **removes** things.
|
||||
|
||||
`how-we-build` §4 already says *contexts integrate through the record, never through a shared
|
||||
@@ -45,7 +45,7 @@ modules is the mesh showing its own data, not a boundary crossing. What is forbi
|
||||
|
||||
### Asking or subscribing is derived, not chosen
|
||||
|
||||
[ADR 0015](0015-a-node-and-how-it-joins.md) makes disconnection an ordinary situation. So:
|
||||
[ADR 0004](0004-a-node-and-how-it-joins.md) makes disconnection an ordinary situation. So:
|
||||
|
||||
- **Anything that must keep working while disconnected cannot ask** — there is nobody to ask. It
|
||||
keeps a local copy, which means subscribing.
|
||||
@@ -79,7 +79,7 @@ The first clear list of what the design deletes rather than adds:
|
||||
- **Three contexts must move out of the registry database**, taking thirteen tables with them.
|
||||
Their dependency on the registry then shrinks to almost nothing — one of them needs a single
|
||||
table.
|
||||
- **The node appliers were already handled.** [ADR 0016](0016-the-node-host.md)
|
||||
- **The node appliers were already handled.** [ADR 0005](0005-the-node-host.md)
|
||||
stopped the host querying the mesh database for tier reasons unrelated to this, and it removes
|
||||
most of the remaining direct readers as a side effect.
|
||||
- **What a consumer does about events missed while disconnected is not decided** — replay from a
|
||||
@@ -90,4 +90,4 @@ The first clear list of what the design deletes rather than adds:
|
||||
- [`how-we-build.md`](../00-META/how-we-build.md) §4 — the rule this makes enforceable.
|
||||
- [Research 011](../01-RESEARCH/011-the-module-graph/worked-provider.md) — the count, the worked
|
||||
provider, and the dashboard case.
|
||||
- [ADR 0015](0015-a-node-and-how-it-joins.md) — why asking or subscribing is derived.
|
||||
- [ADR 0004](0004-a-node-and-how-it-joins.md) — why asking or subscribing is derived.
|
||||
+1
-1
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 19. Modules and the graph
|
||||
# 9. Modules and the graph
|
||||
|
||||
*Consolidated 2026-08-28 from six records.*
|
||||
|
||||
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 23. Delivery
|
||||
# 10. Delivery
|
||||
|
||||
*Consolidated 2026-08-28 from five records.*
|
||||
|
||||
+3
-3
@@ -5,13 +5,13 @@ deciders: jochen
|
||||
reconstructed: true
|
||||
---
|
||||
|
||||
# 2. Managed files are generated onto nodes and never edited there
|
||||
# 11. Managed files are generated onto nodes and never edited there
|
||||
|
||||
> Reconstructed after the fact from the evidence cited below.
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0021](0021-the-substrate-and-the-control-plane.md) put every binding in the mesh
|
||||
[ADR 0006](0006-the-substrate-and-the-control-plane.md) put every binding in the mesh
|
||||
database. But the things that consume those bindings — environment files, service
|
||||
definitions, daemon configuration, firewall rules — are files on a node's disk, because that
|
||||
is what the software reading them requires.
|
||||
@@ -26,7 +26,7 @@ directions at once.
|
||||
1. **Bidirectional sync** — a node's edits flow back to the database. Rejected, and removed.
|
||||
Two writers and no arbiter: whichever synced last wins, and neither is authority.
|
||||
2. **Files are authoritative; the database is a cache of them.** Rejected — it inverts
|
||||
ADR 0003 and returns to state that cannot be reconciled across nodes.
|
||||
ADR 0013 and returns to state that cannot be reconciled across nodes.
|
||||
3. **Strictly one-directional: the database is written, files are generated.** Chosen.
|
||||
|
||||
## Decision
|
||||
+12
-12
@@ -5,11 +5,11 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 10. The mesh creates no symlinks — a derived file is a copy
|
||||
# 12. The mesh creates no symlinks — a derived file is a copy
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0010](0010-the-mesh-creates-no-symlinks.md) responded to production data loss — a hand-made
|
||||
[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.
|
||||
|
||||
@@ -24,7 +24,7 @@ Two things have changed since, and together they remove the argument that kept i
|
||||
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 0002](0002-managed-files-are-generated-never-edited.md) established that
|
||||
**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
|
||||
@@ -33,12 +33,12 @@ 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 0021](0021-the-substrate-and-the-control-plane.md) and ADR 0002 exist to prevent.
|
||||
[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 0011 as the final position** — centralised linking, forbidden elsewhere.
|
||||
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
|
||||
@@ -53,12 +53,12 @@ State is derived onto nodes; it does not reach back.
|
||||
|
||||
**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 0002](0002-managed-files-are-generated-never-edited.md)).
|
||||
([ADR 0011](0011-managed-files-are-generated-never-edited.md)).
|
||||
|
||||
The prohibition in ADR 0011 stands and widens: it ceases to be "only the installer may link"
|
||||
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 0011 becomes superseded rather than edited — its reasoning is why
|
||||
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
|
||||
@@ -72,7 +72,7 @@ the rule exists at all, and the incident behind it is the reason anyone believes
|
||||
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 0023](0023-delivery.md)).
|
||||
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.
|
||||
@@ -89,12 +89,12 @@ the rule exists at all, and the incident behind it is the reason anyone believes
|
||||
- **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.
|
||||
Until those are answered this record stays `proposed`, and ADR 0019 remains the governing rule.
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0010](0010-the-mesh-creates-no-symlinks.md) — the incident, and the rule this widens.
|
||||
- [ADR 0002](0002-managed-files-are-generated-never-edited.md) — the machinery that makes a
|
||||
- [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.
|
||||
+1
-1
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: true
|
||||
---
|
||||
|
||||
# 3. Schema and state changes are numbered migrations, in the same language as the code
|
||||
# 13. Schema and state changes are numbered migrations, in the same language as the code
|
||||
|
||||
> Reconstructed after the fact from the evidence cited below.
|
||||
|
||||
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: true
|
||||
---
|
||||
|
||||
# 4. No workspace — each module is a standalone package consuming published dependencies
|
||||
# 14. No workspace — each module is a standalone package consuming published dependencies
|
||||
|
||||
> Reconstructed after the fact from the evidence cited below.
|
||||
|
||||
@@ -47,7 +47,7 @@ its dependencies' freshly published versions.
|
||||
- Development and the pipeline resolve imports identically. The divergence is gone by
|
||||
construction rather than by discipline.
|
||||
- A module in its own repository is not a special case. It builds exactly as a module in the
|
||||
monorepo does — which is what makes [ADR 0006](0006-applications-live-in-their-own-repository.md)
|
||||
monorepo does — which is what makes [ADR 0015](0015-applications-live-in-their-own-repository.md)
|
||||
cheap.
|
||||
- A cross-package change costs a publish-and-consume round trip. This is the real price, paid
|
||||
on every shared-library change.
|
||||
+3
-3
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: true
|
||||
---
|
||||
|
||||
# 6. Applications live in their own repository; the monorepo is for the mesh
|
||||
# 15. Applications live in their own repository; the monorepo is for the mesh
|
||||
|
||||
> Reconstructed after the fact from the evidence cited below.
|
||||
|
||||
@@ -46,7 +46,7 @@ reject it.
|
||||
- An application's cadence is its own. It is not reviewed as mesh code and does not queue
|
||||
behind mesh work.
|
||||
- The separation is safe **only because** the pipeline and provisioning are identical either
|
||||
side of it — which [ADR 0004](0004-no-npm-workspace.md) is what makes true. Without
|
||||
side of it — which [ADR 0014](0014-no-npm-workspace.md) is what makes true. Without
|
||||
standalone packages this decision would fork the build.
|
||||
- The monorepo stops being an inventory of the installation, which is a precondition for
|
||||
publishing anything about it.
|
||||
@@ -59,6 +59,6 @@ reject it.
|
||||
|
||||
- The rule is stated in the governed constitution page authored 2026-07-10, §3, as a
|
||||
convention violation reviewers must reject.
|
||||
- [ADR 0008](0008-mesh-brokers-nodes-host-agents-think.md) decision 4 extends this from *new*
|
||||
- [ADR 0001](0001-mesh-brokers-nodes-host-agents-think.md) decision 4 extends this from *new*
|
||||
applications to the modules already in the monorepo.
|
||||
- Knowledge base: `troubleshooting/unregistered-module-source`.
|
||||
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 9. The lab
|
||||
# 16. The lab
|
||||
|
||||
*Consolidated 2026-08-28 from five records. The lab is one design and was split across five
|
||||
decisions taken over three days; the reasoning is kept, the fragmentation is not.*
|
||||
+1
-1
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 13. A test defends a decision
|
||||
# 17. A test defends a decision
|
||||
|
||||
## Context
|
||||
|
||||
+5
-5
@@ -3,16 +3,16 @@ status: accepted
|
||||
date: 2026-08-24
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 0013-a-test-defends-a-decision.md
|
||||
extends: 0017-a-test-defends-a-decision.md
|
||||
---
|
||||
|
||||
# 14. A picture of a system is read from the system, never from what asked for it
|
||||
# 18. A picture of a system is read from the system, never from what asked for it
|
||||
|
||||
## Context
|
||||
|
||||
A scenario declaration is a file. A raised scenario is a set of machines, links and rulesets.
|
||||
The two are supposed to correspond, and the entire value of the lab rests on noticing when
|
||||
they do not — [ADR 0013](0013-a-test-defends-a-decision.md) says a claim nothing checks is a
|
||||
they do not — [ADR 0017](0017-a-test-defends-a-decision.md) says a claim nothing checks is a
|
||||
claim that will quietly stop being true.
|
||||
|
||||
Drawing a scenario makes that concrete, and forces a choice that looks cosmetic and is not.
|
||||
@@ -91,8 +91,8 @@ difference read off directly.
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0013](0013-a-test-defends-a-decision.md) — a claim nothing checks stops being true.
|
||||
- [ADR 0009](0009-the-lab.md) — why the lab must not supply what the
|
||||
- [ADR 0017](0017-a-test-defends-a-decision.md) — a claim nothing checks stops being true.
|
||||
- [ADR 0016](0016-the-lab.md) — why the lab must not supply what the
|
||||
mesh is responsible for; the same instinct, applied to facts rather than to configuration.
|
||||
- [04-ISSUES/003](../04-ISSUES/003-firewall-scope-is-read-by-no-code/00-report.md) — the fault in production
|
||||
form.
|
||||
+8
-2
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 11. How this repository works
|
||||
# 19. How this repository works
|
||||
|
||||
*Consolidated 2026-08-28 from ten records that were one decision seen from ten angles. The
|
||||
reasoning is kept; the fragmentation is not.*
|
||||
@@ -36,7 +36,7 @@ company-scoped one does not. So this is `hq` and the mesh's are `mesh-*`.
|
||||
| `novox/mesh-substrate` | 1 | the pinned tier-1 services, as declarations |
|
||||
| `novox/mesh-control` | 2 | the control plane and its contexts |
|
||||
| `novox/mesh-surfaces` | 3 | tools, web, cli — thin, no logic |
|
||||
| `novox/mesh-sdk` | — | the mesh's own domain ([ADR 0019](0019-modules-and-the-graph.md)) |
|
||||
| `novox/mesh-sdk` | — | the mesh's own domain ([ADR 0009](0009-modules-and-the-graph.md)) |
|
||||
| `novox/mesh-lab` | — | the lab: scenario lifecycle, networking, placement |
|
||||
| `novox/hq` | — | this one |
|
||||
|
||||
@@ -77,6 +77,12 @@ settled. Everything else belongs in the design document, where the reasoning is
|
||||
**There is no ledger** — no separate document summarising, ranking or tracking decisions. A
|
||||
chronological view is generated from frontmatter, which is what a ledger was actually for.
|
||||
|
||||
**The numbering is the flow here too.** Records are ordered the way somebody would learn the
|
||||
system — what the mesh is, then its tiers from the bottom up, then what runs on them and how it
|
||||
gets there, then how it is built, how it is checked, and how we work. **Not chronologically**: the
|
||||
date is in the frontmatter and a consolidated record holds decisions taken across a week, so
|
||||
ordering by age would order by an accident that no longer exists.
|
||||
|
||||
**The design layer is what you read.** These records explain *why* a thing is as it is. They are
|
||||
not a description of the system, and needing to read them to understand it would mean the design
|
||||
documents had failed.
|
||||
+1
-1
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: true
|
||||
---
|
||||
|
||||
# 5. The mesh is governed by a constitution, injected where work is decided
|
||||
# 20. The mesh is governed by a constitution, injected where work is decided
|
||||
|
||||
> Reconstructed after the fact from the evidence cited below.
|
||||
|
||||
+5
-5
@@ -5,11 +5,11 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 12. HQ is the source of the mesh constitution
|
||||
# 21. HQ is the source of the mesh constitution
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0005](0005-the-mesh-is-governed-by-a-constitution.md) established a canonical rule set,
|
||||
[ADR 0020](0020-the-mesh-is-governed-by-a-constitution.md) established a canonical rule set,
|
||||
injected into every eligible design session and checked before output is accepted. It lives in
|
||||
the knowledge base, where the orchestrator reads it.
|
||||
|
||||
@@ -39,7 +39,7 @@ directly.
|
||||
Publishing is a playbook step, not a manual act, and it ends with **reading the page back and
|
||||
verifying the change is present**. A publish that reported success and did nothing is exactly
|
||||
the failure class this mesh keeps producing
|
||||
([ADR 0023](0023-delivery.md)).
|
||||
([ADR 0010](0010-delivery.md)).
|
||||
|
||||
Section numbering is stable, because the orchestrator and the review fragments cite sections by
|
||||
number.
|
||||
@@ -47,7 +47,7 @@ number.
|
||||
## Consequences
|
||||
|
||||
- One source, many surfaces — the same argument HQ's separation already rests on
|
||||
([ADR 0011](0011-how-this-repository-works.md)), applied to the rules themselves.
|
||||
([ADR 0019](0019-how-this-repository-works.md)), applied to the rules themselves.
|
||||
- Each rule keeps the incident that earned it, in a place that is reviewed as a diff.
|
||||
- An edit to the derived page survives until the next sync and then vanishes. The playbook says
|
||||
so, and nothing mechanically prevents it.
|
||||
@@ -63,5 +63,5 @@ number.
|
||||
|
||||
- [`00-META/process/05-constitution-sync.md`](../00-META/process/05-constitution-sync.md) —
|
||||
the sync, including the read-back.
|
||||
- [ADR 0005](0005-the-mesh-is-governed-by-a-constitution.md) — the governed page and why it
|
||||
- [ADR 0020](0020-the-mesh-is-governed-by-a-constitution.md) — the governed page and why it
|
||||
exists.
|
||||
+6
-6
@@ -5,11 +5,11 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 17. The constitution absorbs what is already enforced
|
||||
# 22. The constitution absorbs what is already enforced
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0012](0012-hq-is-the-source-of-the-constitution.md) makes this repository the source
|
||||
[ADR 0021](0021-hq-is-the-source-of-the-constitution.md) makes this repository the source
|
||||
and the knowledge base a derived copy, and playbook
|
||||
[05](../00-META/process/05-constitution-sync.md) publishes the copy whenever a rule changes.
|
||||
|
||||
@@ -97,8 +97,8 @@ trusting the second success message either.
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0012](0012-hq-is-the-source-of-the-constitution.md) — source and copy.
|
||||
- [ADR 0005](0005-the-mesh-is-governed-by-a-constitution.md) — why the copy is injected at all.
|
||||
- [ADR 0021](0021-hq-is-the-source-of-the-constitution.md) — source and copy.
|
||||
- [ADR 0020](0020-the-mesh-is-governed-by-a-constitution.md) — why the copy is injected at all.
|
||||
- [Playbook 05](../00-META/process/05-constitution-sync.md) — the sync this record interrupts.
|
||||
- [ADR 0013](0013-a-test-defends-a-decision.md), [ADR 0014](0014-a-picture-is-read-from-what-runs.md),
|
||||
[ADR 0010](0010-the-mesh-creates-no-symlinks.md) — the three rules whose sync surfaced this.
|
||||
- [ADR 0017](0017-a-test-defends-a-decision.md), [ADR 0018](0018-a-picture-is-read-from-what-runs.md),
|
||||
[ADR 0012](0012-the-mesh-creates-no-symlinks.md) — the three rules whose sync surfaced this.
|
||||
+2
-2
@@ -5,7 +5,7 @@ deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 18. The approval is the checkpoint, not the second pair of hands
|
||||
# 23. The approval is the checkpoint, not the second pair of hands
|
||||
|
||||
## Context
|
||||
|
||||
@@ -73,5 +73,5 @@ the previous sync reported success and changed nothing.
|
||||
## References
|
||||
|
||||
- [`how-we-build.md`](../00-META/how-we-build.md) §2 — the rule, now carrying this.
|
||||
- [ADR 0023](0023-delivery.md) — the standard a checkpoint is held to: a
|
||||
- [ADR 0010](0010-delivery.md) — the standard a checkpoint is held to: a
|
||||
step that reports success without doing anything is the fault, not the shortcut.
|
||||
@@ -15,7 +15,7 @@ The records run in the order the decisions were taken, oldest first.
|
||||
|
||||
**Every decision is a record.** There is no ledger and no index file — if a decision is worth
|
||||
recording it is worth a record, and if it is not worth a record it is not recorded
|
||||
([ADR 0011](0011-how-this-repository-works.md)). A "decision" small enough to be one line is
|
||||
([ADR 0019](0019-how-this-repository-works.md)). A "decision" small enough to be one line is
|
||||
almost always a **rule**, and a rule belongs in
|
||||
[`00-META/how-we-build.md`](../00-META/how-we-build.md), where it is enforced and keeps the
|
||||
incident that earned it.
|
||||
|
||||
@@ -4,9 +4,9 @@ status: implemented
|
||||
code: [hal]
|
||||
updated: 2026-08-23
|
||||
decisions:
|
||||
- 02-DECISIONS/0001-nodes-communicate-over-a-broker.md
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0021-the-substrate-and-the-control-plane.md
|
||||
- 02-DECISIONS/0002-nodes-communicate-over-a-broker.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
|
||||
---
|
||||
|
||||
# The mesh as it stands
|
||||
@@ -28,11 +28,11 @@ onto it and can be regenerated.
|
||||
containerised service is a module. A set of capabilities with no service behind them is a
|
||||
module. A bare marker whose whole content is that a node has it is a module. The mesh's own
|
||||
components are modules on exactly the same terms as everything else it carries
|
||||
([ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md)).
|
||||
([ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md)).
|
||||
|
||||
**An agent** is a participant. Some agents are human. What differs is modality — how the agent
|
||||
acts — and not category: both hold identity, both act, both accumulate memory
|
||||
([ADR 0007](../../02-DECISIONS/0007-agents-are-persistent-employees.md)).
|
||||
([ADR 0003](../../02-DECISIONS/0003-agents-are-persistent-employees.md)).
|
||||
|
||||
## Where truth lives
|
||||
|
||||
@@ -40,10 +40,10 @@ The repository defines **what exists**: the modules, what each declares, how eac
|
||||
|
||||
The mesh database defines **what runs where**: which node is assigned which module, at which
|
||||
selection, with which overrides, plus the settings every node reads. No node-to-module mapping
|
||||
is ever committed ([ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)).
|
||||
is ever committed ([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)).
|
||||
|
||||
Everything on a node's disk is **derived** from those two, and is regenerated rather than
|
||||
edited ([ADR 0002](../../02-DECISIONS/0002-managed-files-are-generated-never-edited.md)). A node that
|
||||
edited ([ADR 0011](../../02-DECISIONS/0011-managed-files-are-generated-never-edited.md)). A node that
|
||||
loses its database keeps running from a local cache, which is deliberate and has the obvious
|
||||
cost: the cache carries no indication of its own age.
|
||||
|
||||
@@ -51,7 +51,7 @@ cost: the cache carries no indication of its own age.
|
||||
|
||||
Nothing dials a node. Every node dials the broker outbound, owns an exchange named for itself,
|
||||
and consumes from its own request queue
|
||||
([ADR 0001](../../02-DECISIONS/0001-nodes-communicate-over-a-broker.md)). Three message shapes carry
|
||||
([ADR 0002](../../02-DECISIONS/0002-nodes-communicate-over-a-broker.md)). Three message shapes carry
|
||||
everything: requests expecting a reply, commands instructing that a stage of work be done, and
|
||||
events stating that something happened.
|
||||
|
||||
@@ -65,9 +65,9 @@ goes to where the capability is.
|
||||
A push to the forge is the only trigger. What follows is three silos with deliberately
|
||||
different cardinality: compile once, package and upload once, then install-configure-start-
|
||||
verify **on every assigned node**
|
||||
([ADR 0023](../../02-DECISIONS/0023-delivery.md)). What travels between
|
||||
([ADR 0010](../../02-DECISIONS/0010-delivery.md)). What travels between
|
||||
build and node is a self-contained build output, so a deploy is extract-and-run and touches no
|
||||
network ([ADR 0023](../../02-DECISIONS/0023-delivery.md)).
|
||||
network ([ADR 0010](../../02-DECISIONS/0010-delivery.md)).
|
||||
|
||||
Modules are resolved into dependency levels and a level completes before the next begins, so a
|
||||
module always builds against its dependencies as they were just published.
|
||||
@@ -78,7 +78,7 @@ A module declares what it **provides** and what it **requires**. The mesh satisf
|
||||
requirement: it creates the resource, generates the credential, records the grant, and writes
|
||||
the values where the module will read them. The module never learns which node its database
|
||||
lives on, and nobody ever writes a credential by hand
|
||||
([ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md)).
|
||||
([ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md)).
|
||||
|
||||
This is the property the mesh's whole shape rests on, and it is why provisioning is treated as
|
||||
a core concern rather than as plumbing.
|
||||
@@ -92,7 +92,7 @@ named for a feature the module does not declare, a stage that reported it had di
|
||||
message rather than that the effect happened, a package that 404ed from every mirror while the
|
||||
job went green.
|
||||
|
||||
[ADR 0023](../../02-DECISIONS/0023-delivery.md) is the response, and it is applied
|
||||
[ADR 0010](../../02-DECISIONS/0010-delivery.md) is the response, and it is applied
|
||||
instance by instance rather than enforced by a mechanism. New instances are still being found.
|
||||
That is an as-is fact, not a criticism: it is the single most useful thing to know about this
|
||||
system before changing it.
|
||||
|
||||
@@ -4,8 +4,8 @@ status: implemented
|
||||
code: [hal]
|
||||
updated: 2026-08-23
|
||||
decisions:
|
||||
- 02-DECISIONS/0001-nodes-communicate-over-a-broker.md
|
||||
- 02-DECISIONS/0021-the-substrate-and-the-control-plane.md
|
||||
- 02-DECISIONS/0002-nodes-communicate-over-a-broker.md
|
||||
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
|
||||
---
|
||||
|
||||
# The mesh and its transport
|
||||
|
||||
@@ -4,9 +4,9 @@ status: implemented
|
||||
code: [hal]
|
||||
updated: 2026-08-23
|
||||
decisions:
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0003-schema-changes-are-numbered-migrations.md
|
||||
- 02-DECISIONS/0004-no-npm-workspace.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0013-schema-changes-are-numbered-migrations.md
|
||||
- 02-DECISIONS/0014-no-npm-workspace.md
|
||||
---
|
||||
|
||||
# Modules, manifests and features
|
||||
@@ -81,7 +81,7 @@ recorded in the knowledge base; both presented as "the change did not apply" wit
|
||||
## Dependencies between modules
|
||||
|
||||
Modules depend on each other, above all on the shared library they all build against. There is
|
||||
**no workspace** ([ADR 0004](../../02-DECISIONS/0004-no-npm-workspace.md)): each module is a standalone
|
||||
**no workspace** ([ADR 0014](../../02-DECISIONS/0014-no-npm-workspace.md)): each module is a standalone
|
||||
package consuming published dependencies, including the mesh's own.
|
||||
|
||||
The pipeline resolves modules into dependency **levels** and completes a level before starting
|
||||
@@ -96,7 +96,7 @@ since (see
|
||||
|
||||
A module that owns state owns its migrations: numbered, written in the module's own language,
|
||||
compiled with it, frozen once they have run anywhere, and idempotent so that re-running is safe
|
||||
([ADR 0003](../../02-DECISIONS/0003-schema-changes-are-numbered-migrations.md)).
|
||||
([ADR 0013](../../02-DECISIONS/0013-schema-changes-are-numbered-migrations.md)).
|
||||
|
||||
Two kinds exist and the distinction matters: migrations against the module's **own** local
|
||||
state, and migrations against a **provisioned** resource, which run on the node that consumes
|
||||
|
||||
@@ -4,8 +4,8 @@ status: implemented
|
||||
code: [hal]
|
||||
updated: 2026-08-23
|
||||
decisions:
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0002-managed-files-are-generated-never-edited.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0011-managed-files-are-generated-never-edited.md
|
||||
---
|
||||
|
||||
# Provisioning
|
||||
|
||||
@@ -4,9 +4,9 @@ status: implemented
|
||||
code: [hal]
|
||||
updated: 2026-08-23
|
||||
decisions:
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
---
|
||||
|
||||
# Delivery — from a push to a running node
|
||||
@@ -31,7 +31,7 @@ merge that created no pipeline, and nothing said so**.
|
||||
## Three silos
|
||||
|
||||
Cardinality is the whole point, and the three differ
|
||||
([ADR 0023](../../02-DECISIONS/0023-delivery.md)):
|
||||
([ADR 0010](../../02-DECISIONS/0010-delivery.md)):
|
||||
|
||||
| Silo | Runs | Where | Does |
|
||||
|---|---|---|---|
|
||||
@@ -50,7 +50,7 @@ later stage runs.
|
||||
## The artifact
|
||||
|
||||
The artifact is **build output** — compiled and bundled with its dependency graph inlined —
|
||||
never a filtered copy of source ([ADR 0023](../../02-DECISIONS/0023-delivery.md)).
|
||||
never a filtered copy of source ([ADR 0010](../../02-DECISIONS/0010-delivery.md)).
|
||||
A deploy is extract-and-run and touches no network.
|
||||
|
||||
The consequence is the whole cost of the decision: **anything not in the build output does not
|
||||
|
||||
@@ -4,8 +4,8 @@ status: implemented
|
||||
code: [hal]
|
||||
updated: 2026-08-23
|
||||
decisions:
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0010-the-mesh-creates-no-symlinks.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0012-the-mesh-creates-no-symlinks.md
|
||||
---
|
||||
|
||||
# The node runtime, and how a node comes into being
|
||||
@@ -38,7 +38,7 @@ suggestive word in the system names the node runtime, and the component whose ma
|
||||
"mesh messaging" is documented elsewhere as the interactive runtime. Anatomy makes attractive
|
||||
names and poor boundaries.
|
||||
|
||||
[ADR 0008](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md) replaces this with names
|
||||
[ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) replaces this with names
|
||||
taken from what each part owns. Until then, this is the vocabulary in the code.
|
||||
|
||||
## Starting a module
|
||||
@@ -57,14 +57,14 @@ outstanding local migrations, create data directories with the right ownership,
|
||||
service under supervision.
|
||||
|
||||
**The installer is the only thing that creates a link** ([ADR
|
||||
0011](../../02-DECISIONS/0010-the-mesh-creates-no-symlinks.md)). It reconciles rather than assumes: a
|
||||
0011](../../02-DECISIONS/0012-the-mesh-creates-no-symlinks.md)). It reconciles rather than assumes: a
|
||||
missing link is created, a stale one repointed, and a real file found where a link belongs is
|
||||
adopted into the node's override area and replaced. Nothing else — not a hook, not a fix, not a
|
||||
person debugging — creates one.
|
||||
|
||||
That is the as-is. The intent is to remove linking altogether and derive a real file instead,
|
||||
which the reconciliation machinery already makes possible
|
||||
([ADR 0010](../../02-DECISIONS/0010-the-mesh-creates-no-symlinks.md), proposed). What is described above
|
||||
([ADR 0012](../../02-DECISIONS/0012-the-mesh-creates-no-symlinks.md), proposed). What is described above
|
||||
is what runs today.
|
||||
|
||||
## Supervision
|
||||
|
||||
@@ -4,8 +4,8 @@ status: implemented
|
||||
code: [hal]
|
||||
updated: 2026-08-23
|
||||
decisions:
|
||||
- 02-DECISIONS/0002-managed-files-are-generated-never-edited.md
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0011-managed-files-are-generated-never-edited.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
---
|
||||
|
||||
# Configuration and secrets
|
||||
@@ -17,7 +17,7 @@ files is **generated**.
|
||||
|
||||
A managed file is derived from the mesh database. A synchroniser rewrites it when the values
|
||||
behind it change. The write path is the mesh operation that owns the value; the file is an
|
||||
output ([ADR 0002](../../02-DECISIONS/0002-managed-files-are-generated-never-edited.md)).
|
||||
output ([ADR 0011](../../02-DECISIONS/0011-managed-files-are-generated-never-edited.md)).
|
||||
|
||||
An edit to a managed file survives until the next synchronisation and is then overwritten
|
||||
silently, taking whatever it was fixing with it — bringing back the bug the edit had removed,
|
||||
@@ -61,7 +61,7 @@ are both left behind. Configuration is additive in practice, whatever the manife
|
||||
Generated secrets are produced by the mesh, never authored. Provisioned credentials arrive as
|
||||
database overrides written by the provisioner and are marked as such, so they can be
|
||||
distinguished from a deliberate override and cleaned up when the grant is removed
|
||||
([ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md)).
|
||||
([ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md)).
|
||||
|
||||
Nothing in the repository contains a credential. The repository has no per-node content at all,
|
||||
which is what makes that guarantee structural rather than a matter of care.
|
||||
|
||||
@@ -40,7 +40,7 @@ owning approval and promotion at the boundary. Proposals to edit are reviewed ra
|
||||
applied.
|
||||
|
||||
This is where the mesh's **governed** documents live, including the constitution injected into
|
||||
design sessions ([ADR 0005](../../02-DECISIONS/0005-the-mesh-is-governed-by-a-constitution.md)).
|
||||
design sessions ([ADR 0020](../../02-DECISIONS/0020-the-mesh-is-governed-by-a-constitution.md)).
|
||||
|
||||
## Why both
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ status: implemented
|
||||
code: [hal]
|
||||
updated: 2026-08-23
|
||||
decisions:
|
||||
- 02-DECISIONS/0007-agents-are-persistent-employees.md
|
||||
- 02-DECISIONS/0005-the-mesh-is-governed-by-a-constitution.md
|
||||
- 02-DECISIONS/0003-agents-are-persistent-employees.md
|
||||
- 02-DECISIONS/0020-the-mesh-is-governed-by-a-constitution.md
|
||||
---
|
||||
|
||||
# Agents and work
|
||||
@@ -17,7 +17,7 @@ model they run under is the employee model, not a worker pool.
|
||||
|
||||
An agent is a singular named identity with a home node, a workspace on that node, accumulating
|
||||
memory, and an explicit lifecycle
|
||||
([ADR 0007](../../02-DECISIONS/0007-agents-are-persistent-employees.md)).
|
||||
([ADR 0003](../../02-DECISIONS/0003-agents-are-persistent-employees.md)).
|
||||
|
||||
| Property | Meaning |
|
||||
|---|---|
|
||||
@@ -45,7 +45,7 @@ Both hold identity, both act, both accumulate memory.
|
||||
|
||||
The mesh does not currently record modality completely. Which user, on which node, a human
|
||||
agent acts as is **required by the model and not stored** — an open question carried over from
|
||||
[ADR 0008](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md).
|
||||
[ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md).
|
||||
|
||||
## Work
|
||||
|
||||
@@ -70,7 +70,7 @@ template that names the phases.
|
||||
This is where governance meets execution. The constitution is injected into every eligible
|
||||
meeting turn — agents do not fetch it, it arrives — and a check phase verifies the meeting's
|
||||
output against it before the meeting may proceed
|
||||
([ADR 0005](../../02-DECISIONS/0005-the-mesh-is-governed-by-a-constitution.md)). A named violation
|
||||
([ADR 0020](../../02-DECISIONS/0020-the-mesh-is-governed-by-a-constitution.md)). A named violation
|
||||
blocks progress.
|
||||
|
||||
Meeting turns run on the orchestrator's node regardless of where the participating agents are
|
||||
@@ -84,5 +84,5 @@ integrate through the record, never through a shared schema* — being violated
|
||||
own largest component, and it is the reason work that belongs to one domain keeps having to be
|
||||
implemented in another.
|
||||
|
||||
[ADR 0008](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md) dissolves that arrangement.
|
||||
[ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) dissolves that arrangement.
|
||||
Until it does, this is the shape.
|
||||
|
||||
@@ -4,8 +4,8 @@ status: implemented
|
||||
code: [hal]
|
||||
updated: 2026-08-23
|
||||
decisions:
|
||||
- 02-DECISIONS/0001-nodes-communicate-over-a-broker.md
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0002-nodes-communicate-over-a-broker.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
---
|
||||
|
||||
# Interfaces and observability
|
||||
|
||||
@@ -4,16 +4,16 @@ status: implemented
|
||||
code: [hal]
|
||||
updated: 2026-08-23
|
||||
decisions:
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0006-applications-live-in-their-own-repository.md
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0015-applications-live-in-their-own-repository.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
---
|
||||
|
||||
# The catalogue, and what its shape says
|
||||
|
||||
The catalogue holds **124 modules**. Thirty-three belong to the mesh's own domain; the other
|
||||
ninety-one run *on* the mesh rather than being *of* it
|
||||
([ADR 0008](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md)).
|
||||
([ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md)).
|
||||
|
||||
The count is not the finding. The **shape** is.
|
||||
|
||||
@@ -55,11 +55,11 @@ connectivity is made four times.
|
||||
the unit of one piece of software, because that is the only granularity the module system
|
||||
offers.
|
||||
|
||||
This is the same failure [ADR 0008](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md)
|
||||
This is the same failure [ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md)
|
||||
names for the platform core — *boundaries drawn by deployment accident rather than by domain* —
|
||||
appearing outside it, at four times the scale. The core is being recomposed; the flat level is
|
||||
addressed in principle by
|
||||
[ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md), which
|
||||
[ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md), which
|
||||
deliberately does not yet settle the domain list.
|
||||
|
||||
## Where the shape came from
|
||||
@@ -90,7 +90,7 @@ never stated as assumptions — they were just how the thing already worked.
|
||||
|
||||
**This is the most useful single fact for anyone changing the catalogue**, and it is why the
|
||||
linking principle in particular reads as a deliberate architectural choice when it is an
|
||||
inheritance. See [ADR 0010](../../02-DECISIONS/0010-the-mesh-creates-no-symlinks.md), whose case
|
||||
inheritance. See [ADR 0012](../../02-DECISIONS/0012-the-mesh-creates-no-symlinks.md), whose case
|
||||
this strengthens: the argument for links was never made *for a mesh*.
|
||||
|
||||
It also explains the measurement in
|
||||
@@ -108,7 +108,7 @@ which is what makes dogfooding structural rather than a discipline, and what mak
|
||||
module out of the repository safe.
|
||||
|
||||
**Placement is already decided.** A standalone application belongs in its own repository
|
||||
([ADR 0006](../../02-DECISIONS/0006-applications-live-in-their-own-repository.md)), and reviewers reject
|
||||
([ADR 0015](../../02-DECISIONS/0015-applications-live-in-their-own-repository.md)), and reviewers reject
|
||||
it in the monorepo. The catalogue's flat level is not a dumping ground by policy; it is one by
|
||||
history.
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ status: implemented
|
||||
code: [mesh-lab]
|
||||
updated: 2026-08-25
|
||||
decisions:
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
---
|
||||
|
||||
# The lab, as it stands
|
||||
@@ -63,7 +63,7 @@ is built.
|
||||
|
||||
**The drawing was never designed.** `diagram` renders a scenario as draw.io, from the
|
||||
declaration or from the running instance, and it exists because it was asked for during the
|
||||
build. It has tests and a decision record ([ADR 0014](../../02-DECISIONS/0014-a-picture-is-read-from-what-runs.md),
|
||||
build. It has tests and a decision record ([ADR 0018](../../02-DECISIONS/0018-a-picture-is-read-from-what-runs.md),
|
||||
proposed) but no document in the to-be layer. It is recorded here because it runs, not because
|
||||
it was planned.
|
||||
|
||||
@@ -117,7 +117,7 @@ and snapshots roughly 76× slower, which does not make the lab slow, it makes it
|
||||
|
||||
`npm run check` — typecheck over source *and* tests, then the offline suite, then integration
|
||||
against a real hypervisor. Mocking the hypervisor is forbidden
|
||||
([ADR 0013](../../02-DECISIONS/0013-a-test-defends-a-decision.md), proposed): a test that fakes
|
||||
([ADR 0017](../../02-DECISIONS/0017-a-test-defends-a-decision.md), proposed): a test that fakes
|
||||
the system under integration asserts that the fake behaves as expected.
|
||||
|
||||
Integration tests **skip with a reason** on a machine that cannot raise scenarios, rather than
|
||||
|
||||
@@ -3,12 +3,12 @@ layer: to-be
|
||||
status: designed
|
||||
code: [hal]
|
||||
updated: 2026-08-23
|
||||
decisions: [02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md]
|
||||
decisions: [02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md]
|
||||
---
|
||||
|
||||
# Work breakdown — the decomposition
|
||||
|
||||
How [ADR 0008](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md) gets built, in what order, and where a human must look.
|
||||
How [ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) gets built, in what order, and where a human must look.
|
||||
|
||||
Ordering is not preference. Each phase removes a constraint the next one needs gone.
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ status: in-progress
|
||||
code: [mesh-lab]
|
||||
updated: 2026-08-23
|
||||
decisions:
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0011-how-this-repository-works.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0019-how-this-repository-works.md
|
||||
---
|
||||
|
||||
# End-to-end testing
|
||||
@@ -37,7 +37,7 @@ today, that is a gap in the vocabulary rather than a reason to privilege that sh
|
||||
|
||||
The design below describes a scenario as a complete mesh — forge (Gitea), coordinator,
|
||||
delivery cascade — because what it tests is a module. **That is the larger of two classes, and
|
||||
not the first one built** ([ADR 0009](../../02-DECISIONS/0009-the-lab.md)).
|
||||
not the first one built** ([ADR 0016](../../02-DECISIONS/0016-the-lab.md)).
|
||||
|
||||
| | **Bootstrap scenario** | **Full scenario** |
|
||||
|---|---|---|
|
||||
@@ -127,7 +127,7 @@ drifts.
|
||||
a mesh named by the request instead.
|
||||
- **Scenarios must be concurrent and cheap.** Several agents working means several scenarios
|
||||
at once, each needing its own network and nodes. A lab node is a virtual machine
|
||||
([ADR 0009](../../02-DECISIONS/0009-the-lab.md)), and snapshots are
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-lab.md)), and snapshots are
|
||||
what make repetition cheap — restoring a scenario costs far less than building one. The
|
||||
earlier argument here, that only system containers made this affordable, was superseded: the
|
||||
scale it assumed was invented rather than required.
|
||||
|
||||
@@ -4,9 +4,9 @@ status: in-progress
|
||||
code: [mesh-lab]
|
||||
updated: 2026-08-25
|
||||
decisions:
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
---
|
||||
|
||||
# The scenario declaration
|
||||
@@ -15,7 +15,7 @@ A scenario is a **declaration of an underlay**, plus what to put on it. It is th
|
||||
everything in the lab hangs off, so it is worth getting small.
|
||||
|
||||
It states what a hosting provider and a home router would provide, and nothing the mesh is
|
||||
responsible for ([ADR 0009](../../02-DECISIONS/0009-the-lab.md)).
|
||||
responsible for ([ADR 0016](../../02-DECISIONS/0016-the-lab.md)).
|
||||
|
||||
## Public networks are unrelated, and routed rather than bridged
|
||||
|
||||
@@ -87,7 +87,7 @@ Four consequences follow, and every one of them shapes this design:
|
||||
address stop corresponding.
|
||||
|
||||
This is why the mesh dials outward and never inward
|
||||
([ADR 0001](../../02-DECISIONS/0001-nodes-communicate-over-a-broker.md)), why a hub exists at
|
||||
([ADR 0002](../../02-DECISIONS/0002-nodes-communicate-over-a-broker.md)), why a hub exists at
|
||||
all, and why a node's endpoint is something a peer **learns** from arriving packets rather than
|
||||
something anyone configures.
|
||||
|
||||
@@ -258,7 +258,7 @@ otherwise explicit declaration, and it exists because NAT has to run somewhere.
|
||||
|
||||
It is a **container, not a virtual machine** — a router is scenery rather than something under
|
||||
test, so the fidelity argument that makes a node a virtual machine does not reach it
|
||||
([ADR 0009](../../02-DECISIONS/0009-the-lab.md)). What a router must
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-lab.md)). What a router must
|
||||
reproduce is kernel behaviour, and a container has the same kernel.
|
||||
|
||||
**`machines[].at`** — segment and addresses, or a **list** of them for a machine on several
|
||||
@@ -299,7 +299,7 @@ belongs to a router it does not control, and asleep.
|
||||
|
||||
Whether the overlay survives that, re-forms, and is noticed to have changed endpoint is
|
||||
**observed**, never arranged
|
||||
([ADR 0009](../../02-DECISIONS/0009-the-lab.md)).
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-lab.md)).
|
||||
|
||||
## Why the addresses are load-bearing
|
||||
|
||||
@@ -320,13 +320,13 @@ it must be.
|
||||
The format should make getting this wrong hard rather than merely documented: a segment without
|
||||
a `gateway:` is a public segment, and an address in it — including a gateway's `address:` — that
|
||||
is not documentation space is a declaration error, refused before anything is raised. That is
|
||||
[ADR 0023](../../02-DECISIONS/0023-delivery.md) applied to a configuration
|
||||
[ADR 0010](../../02-DECISIONS/0010-delivery.md) applied to a configuration
|
||||
file: the failure it prevents is silent, so the check has to be loud.
|
||||
|
||||
## The same declaration serves both classes
|
||||
|
||||
The bootstrap and full scenarios differ **only in `place:`**
|
||||
([ADR 0009](../../02-DECISIONS/0009-the-lab.md)). Everything
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-lab.md)). Everything
|
||||
about the underlay is identical, which is what makes one a strict subset of the other rather
|
||||
than a fork.
|
||||
|
||||
@@ -354,7 +354,7 @@ not first.
|
||||
## What a scenario deliberately cannot say
|
||||
|
||||
- **Overlay addresses, the hub, peer configuration.** Outcomes, not inputs
|
||||
([ADR 0009](../../02-DECISIONS/0009-the-lab.md)).
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-lab.md)).
|
||||
- **What a machine is in mesh terms** — server or workstation, its site, its names. Mesh
|
||||
configuration, established by the mesh.
|
||||
- **A host's capability profile.** Detected, never declared.
|
||||
@@ -543,7 +543,7 @@ cannot yet express.
|
||||
|
||||
Nothing here mentions overlay addresses, which node is the hub, who peers with whom, any name,
|
||||
or any certificate. Research 004 recorded all of those for this topology, and **a scenario must
|
||||
not state them** ([ADR 0009](../../02-DECISIONS/0009-the-lab.md)): they
|
||||
not state them** ([ADR 0016](../../02-DECISIONS/0016-the-lab.md)): they
|
||||
are what the mesh does, and a scenario that supplied them would be certifying its own work.
|
||||
|
||||
The absence is the point. Given the declaration above, whether a hub is elected, whether the
|
||||
|
||||
@@ -4,16 +4,16 @@ status: in-progress
|
||||
code: [mesh-lab]
|
||||
updated: 2026-08-25
|
||||
decisions:
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
---
|
||||
|
||||
# Scenario lifecycle
|
||||
|
||||
The first thing the lab must do, and the only thing it must do before anything else can be
|
||||
written: **materialise a mesh, return it to a known state, and destroy it**
|
||||
([ADR 0009](../../02-DECISIONS/0009-the-lab.md)).
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-lab.md)).
|
||||
|
||||
A [declaration](02-scenario-declaration.md) describes a scenario. This describes what happens
|
||||
to one.
|
||||
@@ -39,7 +39,7 @@ The order is not arbitrary — each step needs the one before it to exist:
|
||||
|
||||
1. **Segments.** Isolated links, one per declared segment, belonging to this instance and
|
||||
joined to nothing outside it
|
||||
([ADR 0009](../../02-DECISIONS/0009-the-lab.md)).
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-lab.md)).
|
||||
2. **Gateways.** Derived, never declared as machines: a gateway is materialised for each
|
||||
distinct `gateway:` declaration, sitting on both its segment and its parent, carrying the
|
||||
translation, forwarding and mapping-expiry the declaration asked for.
|
||||
@@ -60,7 +60,7 @@ habit.
|
||||
## A failed raise leaves the wreckage
|
||||
|
||||
A step that fails stops the raise
|
||||
([ADR 0023](../../02-DECISIONS/0023-delivery.md)) — and **does not tear
|
||||
([ADR 0010](../../02-DECISIONS/0010-delivery.md)) — and **does not tear
|
||||
down**.
|
||||
|
||||
Tearing down on failure destroys the only evidence of what went wrong, which is precisely
|
||||
@@ -100,7 +100,7 @@ made after it, and returning undoes it like any other change.
|
||||
## Reaching in
|
||||
|
||||
Everything the lab does to a machine goes through the virtualisation layer, never over IP
|
||||
([ADR 0009](../../02-DECISIONS/0009-the-lab.md)). `exec` runs a
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-lab.md)). `exec` runs a
|
||||
command on a machine and returns its output.
|
||||
|
||||
This has one consequence worth stating plainly: **a reachability question is asked from inside**.
|
||||
|
||||
@@ -4,15 +4,15 @@ status: designed
|
||||
code: [mesh-lab]
|
||||
updated: 2026-08-24
|
||||
decisions:
|
||||
- 02-DECISIONS/0009-the-lab.md
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0016-the-lab.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
---
|
||||
|
||||
# Installing the lab on a clean machine
|
||||
|
||||
The lab has prerequisites — a virtualisation daemon, copy-on-write storage, a pool, an identity
|
||||
permitted to talk to it — and it cannot get them from the mesh, because it is where the mesh is
|
||||
built ([ADR 0009](../../02-DECISIONS/0009-the-lab.md)).
|
||||
built ([ADR 0016](../../02-DECISIONS/0016-the-lab.md)).
|
||||
|
||||
So the lab needs an install path of its own. This describes it, and the shape it has to take is
|
||||
determined by two failures observed while measuring
|
||||
@@ -55,7 +55,7 @@ and unbounded at worst.
|
||||
|
||||
**The lab refuses to run degraded.** It does not warn and continue: a warning about a slow inner
|
||||
loop is read once and ignored forever, and the loop stays slow. This is
|
||||
[ADR 0023](../../02-DECISIONS/0023-delivery.md) applied where the failure is
|
||||
[ADR 0010](../../02-DECISIONS/0010-delivery.md) applied where the failure is
|
||||
performance rather than an error.
|
||||
|
||||
## Two ways the prerequisites arrive
|
||||
|
||||
@@ -4,17 +4,17 @@ status: in-progress
|
||||
code: [mesh-host]
|
||||
updated: 2026-08-27
|
||||
decisions:
|
||||
- 02-DECISIONS/0011-how-this-repository-works.md
|
||||
- 02-DECISIONS/0015-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0015-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0015-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0019-how-this-repository-works.md
|
||||
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
---
|
||||
|
||||
# The node host
|
||||
@@ -25,11 +25,11 @@ Tier 0. The one thing ever installed by hand, and the only thing that changes a
|
||||
|
||||
A **statically linked binary that requires nothing to be present** — copy it onto a machine and
|
||||
run it, and that is the whole installation
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)). Written in Go, because the
|
||||
([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). Written in Go, because the
|
||||
job is system-level and because the host shares no code with any other tier.
|
||||
|
||||
A single binary with one job: **apply declared state on this machine**
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)). Overlay
|
||||
([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). Overlay
|
||||
membership, packet filtering, packages, services, containers and filesystems are not six
|
||||
concerns it carries; they are six instances of the one.
|
||||
|
||||
@@ -61,7 +61,7 @@ returns it.
|
||||
Three properties, each following a recorded decision:
|
||||
|
||||
**A failed step fails the apply.** Not "logs and continues"
|
||||
([ADR 0023](../../02-DECISIONS/0023-delivery.md)). A partial apply that
|
||||
([ADR 0010](../../02-DECISIONS/0010-delivery.md)). A partial apply that
|
||||
reports success is the mesh's most expensive shape.
|
||||
|
||||
**Each applier reads back.** Setting a value is not evidence the value took. The firewall is
|
||||
@@ -69,7 +69,7 @@ asked whether the rule loaded; conntrack is asked what timeout it holds. This is
|
||||
§5 as a component requirement rather than a review habit.
|
||||
|
||||
**What was applied is recorded after it works, never before**
|
||||
([ADR 0014](../../02-DECISIONS/0014-a-picture-is-read-from-what-runs.md)). A failed apply leaves
|
||||
([ADR 0018](../../02-DECISIONS/0018-a-picture-is-read-from-what-runs.md)). A failed apply leaves
|
||||
the machine in whatever state it reached, and nothing must claim otherwise.
|
||||
|
||||
### store
|
||||
@@ -78,17 +78,17 @@ Local, and **authoritative while disconnected**. Not a cache of the control plan
|
||||
of what this node has applied and what it currently holds.
|
||||
|
||||
This is structural rather than convenient: if disconnection is an ordinary situation rather
|
||||
than an exception ([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)), the
|
||||
than an exception ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)), the
|
||||
store is what makes it ordinary. A laptop shut for a week comes back and reconciles; it does
|
||||
not come back and ask what it is.
|
||||
|
||||
### link
|
||||
|
||||
The node's one connection to the control plane, and its security boundary
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)).
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)).
|
||||
|
||||
It is the broker connection that already exists
|
||||
([ADR 0001](../../02-DECISIONS/0001-nodes-communicate-over-a-broker.md)) — outbound,
|
||||
([ADR 0002](../../02-DECISIONS/0002-nodes-communicate-over-a-broker.md)) — outbound,
|
||||
node-initiated, per-node addressed — carrying **per-node identity instead of a shared
|
||||
credential**. The node owns no password. It owns an identity, and that identity is what it
|
||||
presents.
|
||||
@@ -108,7 +108,7 @@ architecture, a network position.
|
||||
capability is real when it is present, running and working, and the difference is the whole
|
||||
point of detecting it.
|
||||
|
||||
The profile is what makes [ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)
|
||||
The profile is what makes [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)
|
||||
work: a node is a node, and what varies between them is here rather than in the definition.
|
||||
|
||||
### inventory
|
||||
@@ -133,7 +133,7 @@ is the component; that one is what happens to it.
|
||||
## Where a declaration comes from
|
||||
|
||||
One behaviour, two sources
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)):
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)):
|
||||
|
||||
| Situation | Source |
|
||||
|---|---|
|
||||
@@ -150,7 +150,7 @@ the mesh, and the full peer set arrives derived.
|
||||
|
||||
## What a declaration is
|
||||
|
||||
Settled by [ADR 0016](../../02-DECISIONS/0016-the-node-host.md).
|
||||
Settled by [ADR 0005](../../02-DECISIONS/0005-the-node-host.md).
|
||||
|
||||
**JSON**, because the host has no dependencies to spend and the standard library carries no
|
||||
YAML. **An ordered list of typed resources**, each with a stable identity — the order is stated
|
||||
@@ -187,8 +187,8 @@ Raising the substrate needs six shapes in the host's vocabulary, and **all six a
|
||||
| `directory`, `file` | **built** | no machine dependency at all |
|
||||
| `service` | **built** | running/stopped **and** enabled/disabled at boot — a unit started but not enabled stops being true at the next reboot |
|
||||
| `package` | **built** | present, never upgraded, and **never uninstalled** — the host cannot know what else needs it, so dropping one is *forgotten*, not *removed* |
|
||||
| `container` | **built** | pinned by digest ([ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)); identified by a label carrying a digest of the declaration that made it, because a runtime normalises what it is given and that is indistinguishable from drift |
|
||||
| `action` | **built** | bundle-only ([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)); verify is mandatory and is the idempotency check as well as the read-back |
|
||||
| `container` | **built** | pinned by digest ([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)); identified by a label carrying a digest of the declaration that made it, because a runtime normalises what it is given and that is indistinguishable from drift |
|
||||
| `action` | **built** | bundle-only ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)); verify is mandatory and is the idempotency check as well as the read-back |
|
||||
|
||||
**The parser enforces the boundary rather than the caller remembering it.** `Parse` refuses an
|
||||
action and is what the link uses; `ParseTrusted` permits one and is what the bundle uses. The
|
||||
@@ -205,7 +205,7 @@ until it is done the substrate bootstrap has no end-to-end test.
|
||||
**3 — link and store.** The node connects, receives declarations, and holds what it applied.
|
||||
|
||||
**4 — enrolment.** The one genuinely new mechanism in
|
||||
[ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md); everything else there
|
||||
[ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md); everything else there
|
||||
is configuration of what already runs.
|
||||
|
||||
Stage 1 is deliberately the smallest useful thing. The lab currently raises **empty machines**
|
||||
@@ -216,7 +216,7 @@ that, and every later stage is tested by a lab that already works.
|
||||
|
||||
**The lab is the harness.** A scenario places a host on a machine and asserts what it did —
|
||||
against a real hypervisor, with the boundary never mocked
|
||||
([ADR 0013](../../02-DECISIONS/0013-a-test-defends-a-decision.md)).
|
||||
([ADR 0017](../../02-DECISIONS/0017-a-test-defends-a-decision.md)).
|
||||
|
||||
Each decision above owes a test:
|
||||
|
||||
@@ -239,7 +239,7 @@ Each decision above owes a test:
|
||||
package, a unit, a container and a dataset *are*. Nothing has measured that surface, and it is
|
||||
the residue of the question [`host-size.md`](../../01-RESEARCH/006-mesh-from-scratch/host-size.md)
|
||||
answered.
|
||||
- **Rescue.** [ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md) suggests it is
|
||||
- **Rescue.** [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) suggests it is
|
||||
a node whose local state is discarded so the mesh re-derives it, and does not decide it.
|
||||
- **What may expire.** An identity needing refresh to stay valid would make a laptop fail for
|
||||
being a laptop ([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)).
|
||||
being a laptop ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)).
|
||||
|
||||
@@ -4,11 +4,11 @@ status: designed
|
||||
code: []
|
||||
updated: 2026-08-27
|
||||
decisions:
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0021-the-substrate-and-the-control-plane.md
|
||||
- 02-DECISIONS/0011-how-this-repository-works.md
|
||||
- 02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md
|
||||
- 02-DECISIONS/0021-the-substrate-and-the-control-plane.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
|
||||
- 02-DECISIONS/0019-how-this-repository-works.md
|
||||
- 02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md
|
||||
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
|
||||
---
|
||||
|
||||
# The control plane
|
||||
@@ -24,7 +24,7 @@ This document defines it. It does **not** design the contexts inside it; those a
|
||||
> **The control plane is everything that needs to know about more than one node.**
|
||||
|
||||
That is the whole test, and it is not arbitrary — it follows from
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-node-host.md). The host applies and
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md). The host applies and
|
||||
does not decide *because deciding needs knowledge the machine does not have*. So the line falls
|
||||
exactly there:
|
||||
|
||||
@@ -44,7 +44,7 @@ catch it because the dependency direction is still correct.
|
||||
## What is inside it
|
||||
|
||||
**Seven contexts and one interface**
|
||||
([ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)) —
|
||||
([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)) —
|
||||
each one earning its place by the test above rather than by being ours:
|
||||
|
||||
| | | needs to know about more than one node because |
|
||||
@@ -64,7 +64,7 @@ anything else does. A task does not need to know a node exists, and *being ours
|
||||
something infrastructure*. `ai` is folded into `config`: a provider licence is an ordinary grant.
|
||||
|
||||
**`record` is an open question rather than an eighth entry.** Contexts integrate through it
|
||||
([ADR 0020](../../02-DECISIONS/0020-a-context-owns-its-store.md)), which makes it load-bearing,
|
||||
([ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md)), which makes it load-bearing,
|
||||
and [research 006](../../01-RESEARCH/006-mesh-from-scratch/skeleton.md) leaves *where it lives*
|
||||
unresolved — putting it in the substrate risks recreating the circularity the tier design just
|
||||
removed. Listing it here would settle by naming what has not been settled by arguing.
|
||||
@@ -88,10 +88,10 @@ because each one alone reads like a detail:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| [ADR 0016](../../02-DECISIONS/0016-the-node-host.md) | the host never queries the mesh database |
|
||||
| [ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md) | a node holds its own identity **and nothing else** — the shared database credential every node carries today is the exposure this exists to remove |
|
||||
| [ADR 0020](../../02-DECISIONS/0020-a-context-owns-its-store.md) | a context is granted only what it **exclusively** owns: no shared writes, no read-only roles |
|
||||
| [ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md) | there is no single mesh database, and nothing reads one |
|
||||
| [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) | the host never queries the mesh database |
|
||||
| [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) | a node holds its own identity **and nothing else** — the shared database credential every node carries today is the exposure this exists to remove |
|
||||
| [ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md) | a context is granted only what it **exclusively** owns: no shared writes, no read-only roles |
|
||||
| [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) | there is no single mesh database, and nothing reads one |
|
||||
|
||||
### So how does anything get in
|
||||
|
||||
@@ -123,17 +123,17 @@ node ──► broker ──► the control plane, consuming
|
||||
Seven contexts, **one deployable** — they are not separate services, so this is one process
|
||||
consuming and dispatching internally, not seven consumers racing. Each context then writes only
|
||||
the store it exclusively owns
|
||||
([ADR 0020](../../02-DECISIONS/0020-a-context-owns-its-store.md)).
|
||||
([ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md)).
|
||||
|
||||
**One consumer is a property worth having**, not just a consequence of
|
||||
[ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md). The as-is records that
|
||||
[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md). The as-is records that
|
||||
*two consumers accidentally sharing one queue silently split the traffic between them, each
|
||||
receiving half of what it expects* — which has happened, between a module's daemon and its
|
||||
capability server. With one consumer that class of fault cannot arise.
|
||||
|
||||
**And the broker is the buffer while the control plane is down.** Nodes go on publishing;
|
||||
messages queue; the control plane drains them when it returns. That is what makes
|
||||
[ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)'s single control plane
|
||||
[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)'s single control plane
|
||||
tolerable — an outage delays the mesh's *knowledge* rather than losing it.
|
||||
|
||||
**With one consequence that must be bounded before it is discovered:** a queue with no limit
|
||||
@@ -149,12 +149,12 @@ before designing for throughput.** The registry is `inventory`'s store: nodes, m
|
||||
assignments, versions. Those change when somebody changes something.
|
||||
|
||||
**Logs, metrics and health checks belong to `observability`**, which owns a different store
|
||||
([ADR 0020](../../02-DECISIONS/0020-a-context-owns-its-store.md)). Sending them to the registry
|
||||
([ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md)). Sending them to the registry
|
||||
would be exactly the shared-schema mistake 0045 exists to stop, arriving through the back door
|
||||
marked *performance*.
|
||||
|
||||
That leaves one genuine funnel: every context's writes go through the process that owns it, and
|
||||
[ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md) says there is one of it.
|
||||
[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) says there is one of it.
|
||||
For a mesh of a handful of machines this is not a scaling problem, and **it should not be solved
|
||||
by giving nodes database credentials** — that trades a bounded problem for an unbounded one. If
|
||||
it ever binds, the answers are at the consumer: batch, apply backpressure, or move the highest
|
||||
@@ -170,10 +170,10 @@ volume genuinely argues against a relational store.
|
||||
- **Not a surface.** Tier 3 is how people and agents reach it. It has one interface; the
|
||||
surfaces are what speak to that interface.
|
||||
- **Not the substrate.** It *runs on* tier 1 — PostgreSQL, LavinMQ, MinIO, an OCI registry
|
||||
([ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)) — and cannot start without
|
||||
([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)) — and cannot start without
|
||||
them, which is what makes them a lower tier.
|
||||
- **Not privileged on a node.** It has no more access to a machine than the declaration
|
||||
vocabulary allows ([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)).
|
||||
vocabulary allows ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)).
|
||||
|
||||
## It is also a consumer
|
||||
|
||||
@@ -184,7 +184,7 @@ module needs, granted the same way.
|
||||
That is the circularity the tiers exist to resolve rather than hide: the control plane cannot
|
||||
provision its own database, because it is not running yet. So its **store** is raised from the
|
||||
bundle the host carries, before there is a control plane to ask
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md),
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md),
|
||||
[research 011](../../01-RESEARCH/011-the-module-graph/worked-provider.md)).
|
||||
|
||||
Its virtual host and its bucket are **not** in the bundle — by the time they are wanted there is
|
||||
@@ -198,15 +198,15 @@ it.
|
||||
hosts, assigned to nodes by the same mechanism as everything else.
|
||||
|
||||
**One node runs it, and nothing takes over**
|
||||
([ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)). The node is assigned,
|
||||
([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)). The node is assigned,
|
||||
never elected — no promotion, no quorum, no split brain.
|
||||
|
||||
That is sound rather than merely cheap, because the design already tolerates the control plane
|
||||
being absent by construction: a node reconciles from **its own** store
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)) and
|
||||
([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)) and
|
||||
never needed to ask anybody to hold the state it was last given. So the control plane being down
|
||||
is not a new failure mode — it is
|
||||
[ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)'s ordinary disconnected
|
||||
[ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)'s ordinary disconnected
|
||||
situation, happening to every node at once. **What is lost is change, not operation.**
|
||||
|
||||
The honest half: this node is a single point of failure, recovery is restore rather than
|
||||
@@ -216,14 +216,14 @@ every public name.
|
||||
## Open
|
||||
|
||||
- ~~**The contexts themselves.**~~ **Decided** — seven, by
|
||||
[ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md).
|
||||
[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md).
|
||||
What remains open is narrower and named there: **where the record lives**, which research 006
|
||||
leaves unresolved because the substrate is the one place it must not go.
|
||||
- **How far it may be split.** One deployable today. Splitting a context out costs the single
|
||||
interface a surface depends on
|
||||
([research 011](../../01-RESEARCH/011-the-module-graph/00-overview.md)).
|
||||
- ~~**How many run, and what a node does without one.**~~ **Resolved** by
|
||||
[ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md). What remains is
|
||||
[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md). What remains is
|
||||
measurement: nothing reports how long the control plane has been unreachable, or how close a
|
||||
certificate is to expiry — both needed for restore-not-failover to be a plan rather than a
|
||||
hope.
|
||||
|
||||
@@ -4,14 +4,14 @@ status: designed
|
||||
code: []
|
||||
updated: 2026-08-27
|
||||
decisions:
|
||||
- 02-DECISIONS/0011-how-this-repository-works.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0015-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0021-the-substrate-and-the-control-plane.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0021-the-substrate-and-the-control-plane.md
|
||||
- 02-DECISIONS/0022-connectivity.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0019-how-this-repository-works.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
|
||||
- 02-DECISIONS/0007-connectivity.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
---
|
||||
|
||||
# The substrate
|
||||
@@ -27,22 +27,22 @@ Every module that needs a database asks the control plane's provisioning for one
|
||||
plane needs a database too — and it cannot ask itself, because it is not running yet. That
|
||||
circularity is not an awkwardness to work around; it *is* the definition. Anything on the wrong
|
||||
side of it must be raised some other way, and the other way is the bundle the host carries
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)).
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)).
|
||||
|
||||
The test, applied:
|
||||
|
||||
| | control plane needs it | can it grant itself one? | |
|
||||
|---|---|---|---|
|
||||
| a relational store — **PostgreSQL** | its own state lives there | no — provisioning needs the store | **substrate** |
|
||||
| a message bus — **LavinMQ** | it reaches nodes over it ([ADR 0001](../../02-DECISIONS/0001-nodes-communicate-over-a-broker.md)) | no — it cannot grant itself a virtual host | **substrate** |
|
||||
| a message bus — **LavinMQ** | it reaches nodes over it ([ADR 0002](../../02-DECISIONS/0002-nodes-communicate-over-a-broker.md)) | no — it cannot grant itself a virtual host | **substrate** |
|
||||
| an object store — **MinIO** | artifacts and blobs it delivers | no — it needs a bucket to hold them | **substrate** |
|
||||
| an image registry — **the OCI registry** | images it delivers to nodes | no — it needs a repository | **substrate** |
|
||||
| an identity provider | only if it delegates authentication | — | **conditional, below** |
|
||||
| ingress — **Traefik** | not to start; only to be reached by name | — it grants itself one afterwards | **not substrate** ([ADR 0022](../../02-DECISIONS/0022-connectivity.md)) |
|
||||
| ingress — **Traefik** | not to start; only to be reached by name | — it grants itself one afterwards | **not substrate** ([ADR 0007](../../02-DECISIONS/0007-connectivity.md)) |
|
||||
| anything else the mesh hosts | no | — | not substrate |
|
||||
|
||||
**The role and the product are both written**, here and everywhere
|
||||
([ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)). The role is what the argument
|
||||
([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)). The role is what the argument
|
||||
turns on — the test above works on roles, and would give the same answers for a different store.
|
||||
The product is what actually gets installed and pinned, and a design that names only the role
|
||||
does not record that the choice was ever made.
|
||||
@@ -96,19 +96,19 @@ Being substrate and being in the bundle are two different questions:
|
||||
| PostgreSQL | yes — the control plane's own state lives in it | **yes** — there is nowhere to put that state otherwise |
|
||||
| LavinMQ | yes — it cannot grant itself a virtual host | **not established** — see below |
|
||||
| MinIO | yes — it cannot grant itself a bucket | no — nothing is delivered before the mesh exists |
|
||||
| the OCI registry | yes — it cannot grant itself a repository | no — the first node fetches upstream ([ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)) |
|
||||
| the OCI registry | yes — it cannot grant itself a repository | no — the first node fetches upstream ([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)) |
|
||||
|
||||
The three on the bottom rows are **substrate by role and ordinary by delivery**: by the time
|
||||
they are wanted there is a control plane, and it provisions them the way it provisions anything.
|
||||
That keeps the bundle to roughly one image rather than four, which is what makes it small enough
|
||||
for the review [ADR 0016](../../02-DECISIONS/0016-the-node-host.md)
|
||||
for the review [ADR 0005](../../02-DECISIONS/0005-the-node-host.md)
|
||||
requires.
|
||||
|
||||
**Why pinned:** the bundle is applied when no mesh exists, so nothing can resolve a version, ask
|
||||
a registry, or check a constraint. What the host carries must already be exact.
|
||||
|
||||
**Why references and not payload:** the bundle names images by **digest** and the host fetches
|
||||
them ([ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)). A first node is
|
||||
them ([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)). A first node is
|
||||
a real machine with a network; the sealed case is the lab, and the lab places images itself.
|
||||
|
||||
Reproducibility comes from pinning the identity of a thing rather than carrying its bytes, which
|
||||
@@ -136,7 +136,7 @@ container, so a container runtime must be working before anything else happens
|
||||
is a *package*, not a container.
|
||||
|
||||
**Which runtime is detected, not chosen**
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)): a machine that
|
||||
([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)): a machine that
|
||||
already has one keeps it. On a machine with none, the control plane names the package, because
|
||||
what it is called differs per system. It is:
|
||||
|
||||
@@ -145,7 +145,7 @@ what it is called differs per system. It is:
|
||||
- **adopted rather than installed** when the machine already has one with configuration somebody
|
||||
chose ([research 012](../../01-RESEARCH/012-the-minimum-viable-node/00-overview.md));
|
||||
- a package, which needs the machine's own package manager and a network — both permitted by
|
||||
[ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md).
|
||||
[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md).
|
||||
|
||||
So the host's bootstrap vocabulary is six shapes: **package**, **container**, **file**,
|
||||
**directory**, **service**, and **action**. **All six are built**
|
||||
@@ -155,7 +155,7 @@ blocked on the host any longer.
|
||||
**Steps 2 and 3 happen before there is a mesh to do them**, which is why provisioning is part of
|
||||
the bootstrap rather than a service consumers use later. They are **actions** the bundle
|
||||
declares and the host runs
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)) — so the
|
||||
([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)) — so the
|
||||
host's vocabulary grows by one shape rather than by one resource type per substrate service.
|
||||
|
||||
## Open
|
||||
@@ -170,7 +170,7 @@ host's vocabulary grows by one shape rather than by one resource type per substr
|
||||
question about the control plane's internal shape, not about the substrate**, which is why it is
|
||||
not answered here.
|
||||
- ~~**Whether the host can do step 2.**~~ **Resolved** by
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-node-host.md). A service
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md). A service
|
||||
running on this machine is part of this machine, so the scope was never in question — the real
|
||||
question was whether the host must learn what a database is, and it must not. The bundle
|
||||
declares an **action**; the host runs it and verifies it, and what a database means stays with
|
||||
|
||||
@@ -4,13 +4,13 @@ status: designed
|
||||
code: []
|
||||
updated: 2026-08-27
|
||||
decisions:
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0015-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0022-connectivity.md
|
||||
- 02-DECISIONS/0022-connectivity.md
|
||||
- 02-DECISIONS/0015-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0022-connectivity.md
|
||||
- 02-DECISIONS/0021-the-substrate-and-the-control-plane.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0007-connectivity.md
|
||||
- 02-DECISIONS/0007-connectivity.md
|
||||
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0007-connectivity.md
|
||||
- 02-DECISIONS/0006-the-substrate-and-the-control-plane.md
|
||||
---
|
||||
|
||||
# Connectivity
|
||||
@@ -31,7 +31,7 @@ node* — to each responsibility:
|
||||
|---|---|---|
|
||||
| **overlay** — who peers with whom, at what address | **every node**, and which of them can be dialled | control plane |
|
||||
| **resolution** — which name is which node | **every node** | control plane |
|
||||
| **exposure** — which public name reaches which container | **which node is publicly reachable** ([ADR 0022](../../02-DECISIONS/0022-connectivity.md)) | control plane |
|
||||
| **exposure** — which public name reaches which container | **which node is publicly reachable** ([ADR 0007](../../02-DECISIONS/0007-connectivity.md)) | control plane |
|
||||
| **filtering** — which port is open, to whom | what is assigned here, and the overlay's shape | control plane decides, host applies |
|
||||
| **certificates** — who may present which name | which name belongs to which node | control plane |
|
||||
|
||||
@@ -53,7 +53,7 @@ It is also what removes the last two upward dependencies.
|
||||
[Research 006](../../01-RESEARCH/006-mesh-from-scratch/host-size.md) counted exactly two modules
|
||||
opening a direct connection to the control plane's database — `wireguard` and `traefik` — and
|
||||
they are the reason every node permanently holds a credential to it
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)). Both are connectivity
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)). Both are connectivity
|
||||
modules. **Closing this context closes that set.**
|
||||
|
||||
## The order it comes up in
|
||||
@@ -63,7 +63,7 @@ The one thing to get right, because everything else depends on it:
|
||||
```
|
||||
0 the node has an underlay address the machine's own — DHCP, or a provider gave it one
|
||||
1 the node dials the mesh OVER THE UNDERLAY, at the address in its token
|
||||
2 it proves itself, and is proved to the link exists (ADR 0015, ADR 0015)
|
||||
2 it proves itself, and is proved to the link exists (ADR 0004, ADR 0004)
|
||||
3 the mesh grants it an identity and an overlay address
|
||||
4 the overlay comes up peer graph delivered as files
|
||||
5 names resolve resolver config delivered as files
|
||||
@@ -77,7 +77,7 @@ never be established on a new node. The link stays on the underlay permanently
|
||||
outbound-only and carries its own identity, so it needs nothing the overlay provides.
|
||||
|
||||
**Nothing before step 3 can resolve a mesh name**, which is why the token carries an *address*
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)). Today this is
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)). Today this is
|
||||
patched with an `/etc/hosts` floor written underneath the resolver; under this design there is
|
||||
nothing to patch.
|
||||
|
||||
@@ -89,7 +89,7 @@ which of those it may dial, and which must dial it.
|
||||
**Inputs, all declared:**
|
||||
|
||||
- **reachability** — an endpoint, or none
|
||||
([ADR 0022](../../02-DECISIONS/0022-connectivity.md)). Not
|
||||
([ADR 0007](../../02-DECISIONS/0007-connectivity.md)). Not
|
||||
inferred from the address shape, which is wrong for carrier-grade NAT, wrong for IPv6, and
|
||||
wrong for a routable address behind a closed firewall.
|
||||
- **site** — where the machine physically is, or nothing if it roams.
|
||||
@@ -98,7 +98,7 @@ which of those it may dial, and which must dial it.
|
||||
|
||||
**Keys.** Each node generates its own keypair. **The private key never leaves the machine**; the
|
||||
public key is published to the mesh. This is already true and it is already right — it is
|
||||
[ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)'s *a node holds its own
|
||||
[ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)'s *a node holds its own
|
||||
identity* applied to the overlay, and it means the control plane computes a graph it cannot
|
||||
itself impersonate.
|
||||
|
||||
@@ -141,17 +141,17 @@ expensively enough to be worth restating:
|
||||
name and overlay address.
|
||||
|
||||
**What goes away:** the `/etc/hosts` floor. It exists because a node had to reach the mesh
|
||||
database before its own DNS existed; with [ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)
|
||||
database before its own DNS existed; with [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)
|
||||
nothing needs a name before the link, and a fallback nothing needs is a path nothing tests.
|
||||
|
||||
## 3 — Exposure
|
||||
|
||||
Settled by [ADR 0022](../../02-DECISIONS/0022-connectivity.md); summarised here because
|
||||
Settled by [ADR 0007](../../02-DECISIONS/0007-connectivity.md); summarised here because
|
||||
this is where it belongs.
|
||||
|
||||
**A route is a grant.** A module that must be reachable declares it needs one; the proxy provides
|
||||
it and hands back the public name. Ordinary
|
||||
[ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md)
|
||||
[ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md)
|
||||
vocabulary — the mirror of a database grant, where the consumer supplies a target and receives a
|
||||
name rather than supplying nothing and receiving credentials.
|
||||
|
||||
@@ -164,14 +164,14 @@ the case is a mesh-level fact, which is the fourth reason exposure is control-pl
|
||||
consequence of what runs on it and who must reach it, not an independent declaration to keep in
|
||||
step by hand.
|
||||
|
||||
**A rule names its source** ([ADR 0022](../../02-DECISIONS/0022-connectivity.md)).
|
||||
**A rule names its source** ([ADR 0007](../../02-DECISIONS/0007-connectivity.md)).
|
||||
A rule with no source is open, and must say so rather than appear to restrict something. `scope:`
|
||||
is removed rather than implemented: five manifests carry it today, it is referenced by no code,
|
||||
and it is the clearest instance in the repository of *an unenforced rule is indistinguishable
|
||||
from a wrong one, and costs more, because people believe it.*
|
||||
|
||||
**Unknown keys are refused** — the discipline the host's declaration parser already has
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)), and
|
||||
([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)), and
|
||||
the one manifests lack. `scope:` survived because nothing rejected it.
|
||||
|
||||
## 5 — Certificates
|
||||
@@ -197,7 +197,7 @@ worse than the lab problem that found it — every certificate experiment on a r
|
||||
production issuance quota, and a retry loop can exhaust it for a week.
|
||||
|
||||
**The mesh CA is not a bootstrap concern.** A joining node verifies the control plane against the
|
||||
fingerprint in its token ([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)),
|
||||
fingerprint in its token ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)),
|
||||
so nothing needs the CA before membership. It certifies internal names afterwards, and that is
|
||||
all it does.
|
||||
|
||||
@@ -208,7 +208,7 @@ The list is worth having in one place, because it is most of the argument:
|
||||
- **The last two direct database connections from nodes** — `wireguard` and `traefik`, the only
|
||||
two, both connectivity.
|
||||
- **Therefore the database credential on every node**, and the object-store credential beside it.
|
||||
[ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)'s central claim becomes
|
||||
[ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)'s central claim becomes
|
||||
true rather than aspirational.
|
||||
- **The `/etc/hosts` floor**, and the bootstrap circularity it patched.
|
||||
- **Hub election by address prefix**, and the silent no-hub failure when nobody knew the
|
||||
@@ -219,7 +219,7 @@ The list is worth having in one place, because it is most of the argument:
|
||||
## Open
|
||||
|
||||
- ~~**What happens when the hub is down.**~~ **Resolved** by
|
||||
[ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md), together with `06`'s
|
||||
[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md), together with `06`'s
|
||||
matching question — they were one question. Nothing takes over. WireGuard has no failover, the
|
||||
hub is declared rather than elected, and non-co-located paths stop while co-located direct peers
|
||||
and every already-assigned workload keep running. The recovery path is restore, and its deadline
|
||||
@@ -227,9 +227,9 @@ The list is worth having in one place, because it is most of the argument:
|
||||
- **Renumbering the overlay.** Made *possible* by declaring the hub rather than inferring it from
|
||||
an address, but no procedure exists, and a graph delivered node by node has an ordering problem
|
||||
while it is half-applied.
|
||||
- **Revoking a route** when a module is unassigned ([ADR 0022](../../02-DECISIONS/0022-connectivity.md)).
|
||||
- **Revoking a route** when a module is unassigned ([ADR 0007](../../02-DECISIONS/0007-connectivity.md)).
|
||||
A stale public name pointing at nothing fails more visibly than a stale grant.
|
||||
- **IPv6.** [ADR 0022](../../02-DECISIONS/0022-connectivity.md) makes
|
||||
- **IPv6.** [ADR 0007](../../02-DECISIONS/0007-connectivity.md) makes
|
||||
it expressible; nothing here says the overlay or the resolver handle it.
|
||||
- **Reporting declared-versus-observed.** ADR 0022 makes the disagreement detectable and does not
|
||||
- **Reporting declared-versus-observed.** ADR 0007 makes the disagreement detectable and does not
|
||||
say who looks or what they are told.
|
||||
|
||||
@@ -4,17 +4,17 @@ status: designed
|
||||
code: []
|
||||
updated: 2026-08-27
|
||||
decisions:
|
||||
- 02-DECISIONS/0015-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0015-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0015-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0015-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0016-the-node-host.md
|
||||
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
---
|
||||
|
||||
# The node lifecycle
|
||||
@@ -42,11 +42,11 @@ questions that were not being asked live.
|
||||
|
||||
**Only `enrolled` and `disconnected` are nodes**, and they are the same node in two situations
|
||||
rather than two kinds of thing
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)). **`hosted` is not a
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)). **`hosted` is not a
|
||||
node** — it is a machine with a program on it that has not been told which mesh it belongs to.
|
||||
|
||||
There is no state for *the first node*. That is the point of
|
||||
[ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md): the first node walks the
|
||||
[ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md): the first node walks the
|
||||
same path, in an unusual order.
|
||||
|
||||
---
|
||||
@@ -54,7 +54,7 @@ same path, in an unusual order.
|
||||
## unmanaged → hosted: installing
|
||||
|
||||
In the machine's own idiom, because the package manager and the init file are the system's
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)):
|
||||
([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)):
|
||||
|
||||
```
|
||||
# Alpine — the intended first node
|
||||
@@ -67,7 +67,7 @@ systemctl enable --now nox-mesh-host
|
||||
```
|
||||
|
||||
Two lines each, and the init file behind them is four
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)) — it says
|
||||
([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)) — it says
|
||||
*run the launcher at boot* and nothing else, so a third system is transcription rather than a
|
||||
port.
|
||||
|
||||
@@ -103,7 +103,7 @@ WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
**Two lines of policy, and that is deliberate**
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)). The init is
|
||||
([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). The init is
|
||||
asked to *start this at boot* and *start it again if it exits*, and nothing else. Both are
|
||||
expressible in OpenRC, runit, s6 and an Android `init.rc`, so porting this file is transcription
|
||||
rather than design.
|
||||
@@ -135,7 +135,7 @@ nox-mesh-host enrol --token <one-time token>
|
||||
```
|
||||
|
||||
The token carries three things and is carried by a person
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)): the broker's
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)): the broker's
|
||||
address, the fingerprint to expect, and the right to join once.
|
||||
|
||||
What happens, in order:
|
||||
@@ -153,7 +153,7 @@ a container runtime, an architecture. The profile is not a diagnostic; it is the
|
||||
|
||||
**The node computes nothing about the mesh.** It needs one peer to reach; the whole overlay is
|
||||
derived centrally and pushed down
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md),
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md),
|
||||
[`08-connectivity.md`](08-connectivity.md)).
|
||||
|
||||
### The first declaration is the overlay, and nothing else
|
||||
@@ -172,7 +172,7 @@ Three reasons, and the third is the one that matters when something goes wrong:
|
||||
address and peer set are *assigned* — it generates a keypair, publishes the public half, and
|
||||
receives the rest ([`08-connectivity.md`](08-connectivity.md)). So the overlay is the first
|
||||
thing the mesh can give it, and it should be.
|
||||
- **It is what [ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md) already
|
||||
- **It is what [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) already
|
||||
says:** *a joining node does the minimum to be reachable, and nothing else.*
|
||||
- **It is the way back in.** Once the overlay is up, the node is reachable over it — by SSH, by
|
||||
anything. If a later declaration breaks the machine, there is a route to it that does not
|
||||
@@ -187,10 +187,10 @@ Worth stating plainly, because the two rules read as a contradiction and are not
|
||||
| | |
|
||||
|---|---|
|
||||
| **every node reaches every other node** | over the overlay — SSH, services, ordinary traffic. This is the point of having one |
|
||||
| **every node consumes from the broker** | its own queue, over its own outbound connection ([ADR 0001](../../02-DECISIONS/0001-nodes-communicate-over-a-broker.md)) |
|
||||
| **nothing dials a node to control it** | the host has no inbound control surface ([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)) |
|
||||
| **every node consumes from the broker** | its own queue, over its own outbound connection ([ADR 0002](../../02-DECISIONS/0002-nodes-communicate-over-a-broker.md)) |
|
||||
| **nothing dials a node to control it** | the host has no inbound control surface ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)) |
|
||||
|
||||
**[ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md) is about the control
|
||||
**[ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) is about the control
|
||||
channel, not about network reachability.** What it forbids is a listening thing that accepts
|
||||
instructions and changes the machine. A node being reachable on the overlay — the whole purpose
|
||||
of the overlay — is untouched by it, and so is a person opening a shell on it.
|
||||
@@ -232,7 +232,7 @@ used months later on node two.
|
||||
## Two kinds of host
|
||||
|
||||
Everything above assumes a machine with an init that runs the host at boot. Not every machine
|
||||
has one ([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)).
|
||||
has one ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)).
|
||||
|
||||
| | **resident** | **episodic** |
|
||||
|---|---|---|
|
||||
@@ -245,7 +245,7 @@ has one ([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)).
|
||||
| can be the first node | yes | **no** |
|
||||
|
||||
**An episodic host being killed is disconnection, not failure.** That is
|
||||
[ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md) doing the work it was written
|
||||
[ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) doing the work it was written
|
||||
for: reachability is state, not class. Everything the design already does for a laptop that
|
||||
closes — an authoritative local store, reconcile on start, *last heard from* reported without an
|
||||
alarm — is what an episodic host needs, at a shorter period.
|
||||
@@ -258,7 +258,7 @@ empty placeholder waiting to be filled in.
|
||||
**Two things this changes for anything reading the mesh.** *Last heard from* is a much weaker
|
||||
signal on an episodic host — a healthy phone looks like a dead server — so a reader has to know
|
||||
which kind it is looking at. And a declaration may take a long time to land, which makes
|
||||
[ADR 0023](../../02-DECISIONS/0023-delivery.md)'s separation of
|
||||
[ADR 0010](../../02-DECISIONS/0010-delivery.md)'s separation of
|
||||
*outstanding* from *failed* load-bearing rather than tidy.
|
||||
|
||||
**Still open:** how an episodic host is started in practice — an APK with a foreground service,
|
||||
@@ -272,7 +272,7 @@ Adoption is not a state. It is what the **first apply** does when it is told to
|
||||
machine already has ([research 012](../../01-RESEARCH/012-the-minimum-viable-node/00-overview.md)).
|
||||
|
||||
A candidate machine is not empty. It has a package manager, probably a container runtime,
|
||||
configuration somebody chose. [ADR 0016](../../02-DECISIONS/0016-the-node-host.md)
|
||||
configuration somebody chose. [ADR 0005](../../02-DECISIONS/0005-the-node-host.md)
|
||||
says the host never touches what it did not create — adoption is the deliberate act of taking
|
||||
ownership of exactly that, so it is a companion to that rule rather than an exception:
|
||||
|
||||
@@ -298,8 +298,8 @@ outcome **derived** from the worst line rather than stated alongside it.
|
||||
|
||||
**Changes are pushed, not polled.** A declaration arrives as a message on the link and the host
|
||||
applies it then. The link is already open and outbound
|
||||
([ADR 0001](../../02-DECISIONS/0001-nodes-communicate-over-a-broker.md),
|
||||
[ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)) — asking it
|
||||
([ADR 0002](../../02-DECISIONS/0002-nodes-communicate-over-a-broker.md),
|
||||
[ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)) — asking it
|
||||
repeatedly whether anything has changed would be slower to land *and* constant traffic to learn
|
||||
nothing.
|
||||
|
||||
@@ -326,11 +326,11 @@ So the two periodic things do different jobs and should not be conflated:
|
||||
without a heartbeat that is indistinguishable from a node that stopped. With one, *last heard
|
||||
from* is a fact beside every node — which is what
|
||||
[how long disconnected](#how-long-disconnected-and-who-is-told) reports and what
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-node-host.md) exists
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md) exists
|
||||
because a stuck node cannot send.
|
||||
|
||||
**Rebooting mid-apply is safe by construction.** The store records each resource *after* it
|
||||
worked ([ADR 0014](../../02-DECISIONS/0014-a-picture-is-read-from-what-runs.md)), so a host that
|
||||
worked ([ADR 0018](../../02-DECISIONS/0018-a-picture-is-read-from-what-runs.md)), so a host that
|
||||
dies half way through comes back, finds the completed ones already matching, and applies the
|
||||
rest. The rule that exists to stop the host lying about what it did also makes it crash-safe.
|
||||
|
||||
@@ -357,7 +357,7 @@ runtime because a declaration changed would stop every container on the node.
|
||||
## enrolled ⇄ disconnected
|
||||
|
||||
Not a failure. Not degraded. A situation
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)).
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)).
|
||||
|
||||
A disconnected node **keeps reconciling against its own store**, so it goes on holding its
|
||||
machine in the last state it was told to hold. A laptop shut for a week comes back and
|
||||
@@ -365,7 +365,7 @@ reconciles; it does not come back and ask what it is.
|
||||
|
||||
What it cannot do: receive new declarations, be granted anything new, or have its certificates
|
||||
renewed — which is the clock on the whole arrangement
|
||||
([ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)).
|
||||
([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)).
|
||||
|
||||
**How long it has been disconnected is a fact the mesh must hold**, and nothing holds it today.
|
||||
Without it, a node running last month's assignments looks exactly like one that is current.
|
||||
@@ -384,7 +384,7 @@ nox-mesh-host profile # what can this machine actually do?
|
||||
|
||||
`apply FILE` accepts actions, because someone who can write that file and run this binary as
|
||||
root can already do anything it can. The bound in
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-node-host.md) is on what a
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md) is on what a
|
||||
**remote** party may push, not on what a person at the machine may do.
|
||||
|
||||
This replaces the three hand-run scripts that exist today — first node, joining, rescue — with
|
||||
@@ -401,14 +401,14 @@ what it owns by the table above, reports, and drops its identity. The machine ke
|
||||
installed and is back to `hosted`. Nothing is left behind that anybody has to remember.
|
||||
|
||||
**The node is gone.** Stolen, dead, or simply unreachable. The mesh cannot tell it anything, and
|
||||
by [ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md) it will go on reconciling
|
||||
by [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) it will go on reconciling
|
||||
its last declaration **forever**.
|
||||
|
||||
That is the honest consequence of making disconnection ordinary, and the answer is not to make
|
||||
the host expire. It is that **the node holds nothing that outlives revocation**: its identity is
|
||||
its own, and every grant it holds is a per-node credential at the provider
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md),
|
||||
[ADR 0020](../../02-DECISIONS/0020-a-context-owns-its-store.md)). Revoking is done at the
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md),
|
||||
[ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md)). Revoking is done at the
|
||||
database, the broker, the object store — not on the machine.
|
||||
|
||||
So a lost node keeps *running* and stops being able to *reach* anything. That is the best
|
||||
@@ -438,7 +438,7 @@ remains locally authoritative for *operating*; the copy exists only for this.
|
||||
## Upgrading the host
|
||||
|
||||
The host is delivered like anything else
|
||||
([ADR 0023](../../02-DECISIONS/0023-delivery.md)), and this is worth
|
||||
([ADR 0010](../../02-DECISIONS/0010-delivery.md)), and this is worth
|
||||
walking through because tier 0 looks like it should be special and is not.
|
||||
|
||||
```
|
||||
@@ -471,7 +471,7 @@ the test of whether this is really uniform.
|
||||
3 it finishes the apply and reports never mid-way
|
||||
4 it exits 0 having finished, not having been stopped
|
||||
5 the launcher starts it again on the new binary — it supervises the host
|
||||
rather than exec'ing it (ADR 0016), so this
|
||||
rather than exec'ing it (ADR 0005), so this
|
||||
needs nothing from the init
|
||||
6 the new host reconciles on start trigger 1, confirming the machine still matches
|
||||
```
|
||||
@@ -489,7 +489,7 @@ own apply completes. A node must therefore report the version it is **running**,
|
||||
installed — otherwise the mesh believes an upgrade landed at step 1.
|
||||
|
||||
**A version that crashes on start rolls itself back**
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-node-host.md)).
|
||||
([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)).
|
||||
|
||||
What the init starts is not the host but a **launcher**, and the launcher is where the policy
|
||||
lives:
|
||||
@@ -551,7 +551,7 @@ credentials still valid — the case
|
||||
**The host reports what it owns, and the mesh keeps the last report.**
|
||||
|
||||
The store stays locally authoritative — a node operates from its own copy and needs nothing to
|
||||
do so ([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)). What changes is that
|
||||
do so ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)). What changes is that
|
||||
the mesh holds a **copy for recovery**, refreshed on every apply report.
|
||||
|
||||
So a node that loses its state file re-enrols, receives both the declaration *and* the record of
|
||||
@@ -616,12 +616,12 @@ keeps cataloguing.
|
||||
### Where the enrolment token comes from
|
||||
|
||||
**`mesh-control token issue` prints it once**, to the person running it. Single-use, and it
|
||||
expires whether used or not ([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)).
|
||||
expires whether used or not ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)).
|
||||
|
||||
It is carried by hand — read off a screen, pasted into a terminal. That is the design rather than
|
||||
a gap in it: its authenticity comes from the channel it travelled, which is what
|
||||
lets a node verify a mesh it has never spoken to
|
||||
([ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md)). A token emailed,
|
||||
([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)). A token emailed,
|
||||
committed, or dropped in shared storage has lost the only property that makes it worth carrying.
|
||||
|
||||
**On the first node it comes from the control plane that was raised two commands ago**, which is
|
||||
@@ -632,10 +632,10 @@ the same command against a mesh that is one machine old.
|
||||
## Still open
|
||||
|
||||
- ~~**Automatic rollback of a bad host version.**~~ **Resolved** by
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-node-host.md): a launcher
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md): a launcher
|
||||
counts failed starts and rolls back — shipped by the package, not the host binary, because a
|
||||
binary that will not start cannot recover itself. It rolls back once; a second failure means
|
||||
the machine is the problem, not the binary.
|
||||
- **How a previous declaration is retained and chosen**, which is what rollback of anything else
|
||||
would use ([ADR 0023](../../02-DECISIONS/0023-delivery.md)).
|
||||
would use ([ADR 0010](../../02-DECISIONS/0010-delivery.md)).
|
||||
- **A node returning after months** applies a very large jump in one go. Correct, and untested.
|
||||
|
||||
@@ -4,13 +4,13 @@ status: designed
|
||||
code: []
|
||||
updated: 2026-08-28
|
||||
decisions:
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0023-delivery.md
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0019-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
- 02-DECISIONS/0010-delivery.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
---
|
||||
|
||||
# Modules and delivery
|
||||
|
||||
@@ -9,27 +9,27 @@ document is written and this one's status becomes `implemented`.
|
||||
|
||||
| Document | Covers | Rests on |
|
||||
|---|---|---|
|
||||
| [`00-work-breakdown.md`](00-work-breakdown.md) | How the decomposition gets built, in what order, and where a human must look | [ADR 0008](../../02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md) |
|
||||
| [`01-end-to-end-testing.md`](01-end-to-end-testing.md) | The lab: a real mesh a change can be run against before it reaches nodes | [ADR 0009](../../02-DECISIONS/0009-the-lab.md), [0029](../../02-DECISIONS/0009-the-lab.md) |
|
||||
| [`02-scenario-declaration.md`](02-scenario-declaration.md) | What a scenario declares — the underlay, and what to place on it | [ADR 0009](../../02-DECISIONS/0009-the-lab.md) |
|
||||
| [`03-scenario-lifecycle.md`](03-scenario-lifecycle.md) | What happens to a scenario — raise, snapshot, restore, move, destroy | [ADR 0009](../../02-DECISIONS/0009-the-lab.md) |
|
||||
| [`04-lab-installation.md`](04-lab-installation.md) | Getting the lab onto a clean machine, and why it verifies capability rather than installation | [ADR 0023](../../02-DECISIONS/0023-delivery.md) |
|
||||
| [`05-the-node-host.md`](05-the-node-host.md) | Tier 0 — the one thing installed by hand, and the only thing that changes a machine | [ADR 0016](../../02-DECISIONS/0016-the-node-host.md) |
|
||||
| [`06-the-control-plane.md`](06-the-control-plane.md) | Tier 2 — what the term means, and the test for what belongs in it | [ADR 0016](../../02-DECISIONS/0016-the-node-host.md) |
|
||||
| [`07-the-substrate.md`](07-the-substrate.md) | Tier 1 — what the control plane consumes and cannot grant itself | [ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md), [0048](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md) |
|
||||
| [`08-connectivity.md`](08-connectivity.md) | One context in full — overlay, resolution, exposure, filtering, certificates | [ADR 0022](../../02-DECISIONS/0022-connectivity.md), [0050](../../02-DECISIONS/0022-connectivity.md), [0051](../../02-DECISIONS/0015-a-node-and-how-it-joins.md), [0055](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md) |
|
||||
| [`09-the-node-lifecycle.md`](09-the-node-lifecycle.md) | How a machine becomes a node, stays one, and stops being one | [ADR 0015](../../02-DECISIONS/0015-a-node-and-how-it-joins.md), [0051](../../02-DECISIONS/0015-a-node-and-how-it-joins.md) |
|
||||
| [`10-delivery.md`](10-delivery.md) | Modules, the three edges, and how a change becomes a running thing | [ADR 0023](../../02-DECISIONS/0023-delivery.md), [0064](../../02-DECISIONS/0019-modules-and-the-graph.md), [0065](../../02-DECISIONS/0019-modules-and-the-graph.md) |
|
||||
| [`00-work-breakdown.md`](00-work-breakdown.md) | How the decomposition gets built, in what order, and where a human must look | [ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) |
|
||||
| [`01-end-to-end-testing.md`](01-end-to-end-testing.md) | The lab: a real mesh a change can be run against before it reaches nodes | [ADR 0016](../../02-DECISIONS/0016-the-lab.md), [0029](../../02-DECISIONS/0016-the-lab.md) |
|
||||
| [`02-scenario-declaration.md`](02-scenario-declaration.md) | What a scenario declares — the underlay, and what to place on it | [ADR 0016](../../02-DECISIONS/0016-the-lab.md) |
|
||||
| [`03-scenario-lifecycle.md`](03-scenario-lifecycle.md) | What happens to a scenario — raise, snapshot, restore, move, destroy | [ADR 0016](../../02-DECISIONS/0016-the-lab.md) |
|
||||
| [`04-lab-installation.md`](04-lab-installation.md) | Getting the lab onto a clean machine, and why it verifies capability rather than installation | [ADR 0010](../../02-DECISIONS/0010-delivery.md) |
|
||||
| [`05-the-node-host.md`](05-the-node-host.md) | Tier 0 — the one thing installed by hand, and the only thing that changes a machine | [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) |
|
||||
| [`06-the-control-plane.md`](06-the-control-plane.md) | Tier 2 — what the term means, and the test for what belongs in it | [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) |
|
||||
| [`07-the-substrate.md`](07-the-substrate.md) | Tier 1 — what the control plane consumes and cannot grant itself | [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [0048](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) |
|
||||
| [`08-connectivity.md`](08-connectivity.md) | One context in full — overlay, resolution, exposure, filtering, certificates | [ADR 0007](../../02-DECISIONS/0007-connectivity.md), [0050](../../02-DECISIONS/0007-connectivity.md), [0051](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [0055](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) |
|
||||
| [`09-the-node-lifecycle.md`](09-the-node-lifecycle.md) | How a machine becomes a node, stays one, and stops being one | [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [0051](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) |
|
||||
| [`10-delivery.md`](10-delivery.md) | Modules, the three edges, and how a change becomes a running thing | [ADR 0010](../../02-DECISIONS/0010-delivery.md), [0064](../../02-DECISIONS/0009-modules-and-the-graph.md), [0065](../../02-DECISIONS/0009-modules-and-the-graph.md) |
|
||||
|
||||
## Not yet written
|
||||
|
||||
- **The remaining six contexts.**
|
||||
[ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)
|
||||
[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)
|
||||
settles the list at seven; `connectivity` is the first written in full
|
||||
([`08`](08-connectivity.md)) and the other six do not exist yet. The work breakdown says in
|
||||
what order they are needed.
|
||||
- ~~**Domain grouping outside the core.**~~ **Not needed.**
|
||||
[ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md) is
|
||||
superseded by [ADR 0019](../../02-DECISIONS/0019-modules-and-the-graph.md):
|
||||
[ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) is
|
||||
superseded by [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md):
|
||||
there is no domain module to group into, so there is no domain list to settle. Relationships
|
||||
are edges, and grouping is a tag and a query.
|
||||
|
||||
@@ -31,14 +31,14 @@ exists to catch — a step that failed, reported success, and left the next step
|
||||
state that was never produced.
|
||||
|
||||
It is also a direct violation of a decision already taken and recorded:
|
||||
[ADR 0023](../../02-DECISIONS/0023-delivery.md) says a step that fails must fail the
|
||||
[ADR 0010](../../02-DECISIONS/0010-delivery.md) says a step that fails must fail the
|
||||
job. That record notes the rule is applied instance by instance and enforced by no mechanism.
|
||||
This is an instance where it was never applied.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Observed 2026-08-22 while declaring the virtualisation package required by
|
||||
[ADR 0009](../../02-DECISIONS/0009-the-lab.md).
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-lab.md).
|
||||
- A fix is written and open as a pull request, unmerged since 2026-08-20.
|
||||
|
||||
## Open questions
|
||||
|
||||
@@ -21,7 +21,7 @@ recoverable by retrying — it removes the ability to issue a certificate anyone
|
||||
|
||||
The consequence lands hardest on exactly the work most likely to iterate: standing up a new
|
||||
node, changing how names resolve, or testing the lab's certificate authority split
|
||||
([ADR 0009](../../02-DECISIONS/0009-the-lab.md)).
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-lab.md)).
|
||||
|
||||
## Evidence
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ coverage was assumed, not checked.
|
||||
## Evidence
|
||||
|
||||
- The workspace was removed by pull request #240 on 2026-06-04
|
||||
([ADR 0004](../../02-DECISIONS/0004-no-npm-workspace.md)).
|
||||
([ADR 0014](../../02-DECISIONS/0014-no-npm-workspace.md)).
|
||||
- The harness has not built since that date.
|
||||
- Recorded in the knowledge base as a standing entry, not as a fixed incident.
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ checked it — including in the same commit that wrote the rule.
|
||||
## Proposed direction — Nox is the search
|
||||
|
||||
*Added 2026-08-23.* Rather than syncing these documents into the knowledge base, **Nox
|
||||
([ADR 0011](../../02-DECISIONS/0011-how-this-repository-works.md)) works from within this
|
||||
([ADR 0019](../../02-DECISIONS/0019-how-this-repository-works.md)) works from within this
|
||||
repository and holds its knowledge directly.** Retrieval becomes an agent reading the source,
|
||||
not a copy living in a second store.
|
||||
|
||||
@@ -71,7 +71,7 @@ This is a better answer than the one the README originally promised, on three co
|
||||
was that it adds a fourth knowledge *system*. An agent with read access adds no store at all.
|
||||
- **It is always current**, including for uncommitted work in progress.
|
||||
|
||||
**But it changes the promise, and that is worth stating rather than glossing.** ADR 0011's
|
||||
**But it changes the promise, and that is worth stating rather than glossing.** ADR 0019's
|
||||
answer was that these documents would be returned *beside everything else* in a symptom search.
|
||||
An agent that must be **asked** is reachable; it is not surfacing. The two differ in exactly
|
||||
the case the operational memory is designed for: someone debugging an error who has no reason
|
||||
@@ -82,9 +82,9 @@ So the open question narrows to one thing:
|
||||
> When a symptom is searched and the answer happens to live in a design document or a decision
|
||||
> record here, does the searcher find it without already suspecting it exists?
|
||||
|
||||
If Nox is the only path, the answer is no, and the reasoning in ADR 0011 needs amending rather
|
||||
If Nox is the only path, the answer is no, and the reasoning in ADR 0019 needs amending rather
|
||||
than satisfying. If Nox also contributes what it knows to a symptom search — or the search
|
||||
consults Nox — the answer is yes and the original promise holds.
|
||||
|
||||
That is a design question for Nox, not a defect in this repository, and it should be settled
|
||||
before ADR 0011 is treated as answered.
|
||||
before ADR 0019 is treated as answered.
|
||||
|
||||
@@ -44,7 +44,7 @@ The distance between the two is the same one the delivery layer already has a na
|
||||
## Why it matters now
|
||||
|
||||
This is the first requirement of the lab
|
||||
([ADR 0009](../../02-DECISIONS/0009-the-lab.md)), which is
|
||||
([ADR 0016](../../02-DECISIONS/0016-the-lab.md)), which is
|
||||
phase 0 of the entire migration. The first capability the new work depends on is present,
|
||||
declared, and unusable — and would have stayed unusable silently.
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ node recovers itself.
|
||||
## Scope
|
||||
|
||||
**The as-is only.** The design being built has a different answer:
|
||||
[ADR 0016](../../02-DECISIONS/0016-the-node-host.md) puts recovery
|
||||
[ADR 0005](../../02-DECISIONS/0005-the-node-host.md) puts recovery
|
||||
in a launcher that supervises the host, and that recovery is tested — 32 assertions, each
|
||||
confirmed to fail when the behaviour is removed.
|
||||
|
||||
@@ -53,7 +53,7 @@ is, which is a scheduling question rather than a technical one.
|
||||
## What it would take to be sure
|
||||
|
||||
Read back rather than assumed
|
||||
([ADR 0014](../../02-DECISIONS/0014-a-picture-is-read-from-what-runs.md)): list every unit on a
|
||||
([ADR 0018](../../02-DECISIONS/0018-a-picture-is-read-from-what-runs.md)): list every unit on a
|
||||
node and grep for `OnFailure=`; list every timer and check what each one calls. The finding above
|
||||
came from reading the repository, and confirming it against a running node is the difference
|
||||
between *no unit declares this* and *no unit in the source declares this*.
|
||||
|
||||
@@ -12,7 +12,7 @@ amended-design:
|
||||
|
||||
Two accepted decisions collide, and the collision makes one resource shape untestable.
|
||||
|
||||
- **[ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)** pins images by
|
||||
- **[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)** pins images by
|
||||
digest, and the host **refuses** an image reference that is not pinned:
|
||||
|
||||
```
|
||||
@@ -66,7 +66,7 @@ for.
|
||||
## The shape of a resolution
|
||||
|
||||
**A registry inside the scenario**, on its public segment, that machines pull from. That is not a
|
||||
workaround: it is what the real mesh does — [ADR 0021](../../02-DECISIONS/0021-the-substrate-and-the-control-plane.md)
|
||||
workaround: it is what the real mesh does — [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)
|
||||
names an OCI registry as substrate, and every node after the first pulls from the mesh's own.
|
||||
Testing against a registry is testing the real path rather than a stand-in for it.
|
||||
|
||||
@@ -74,7 +74,7 @@ It also removes the lab's export-and-push mechanism rather than fixing it, which
|
||||
outcome: pushing image tarballs over the hypervisor was always a lab-only invention.
|
||||
|
||||
**Not decided here**, because it is design rather than repair: where the registry runs, whether
|
||||
it is scenery like the router ([ADR 0009](../../02-DECISIONS/0009-the-lab.md))
|
||||
it is scenery like the router ([ADR 0016](../../02-DECISIONS/0016-the-lab.md))
|
||||
or a placed artifact, and how images get into it.
|
||||
|
||||
## Incidental, and already fixed
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Agent instructions — Novox HQ
|
||||
|
||||
This repository is the source of truth for Novox's mission, research, design and decisions —
|
||||
today almost entirely those of **Novox Mesh**, its first product ([ADR 0011](02-DECISIONS/0011-how-this-repository-works.md)). Implementation lives in the code repositories (see
|
||||
today almost entirely those of **Novox Mesh**, its first product ([ADR 0019](02-DECISIONS/0019-how-this-repository-works.md)). Implementation lives in the code repositories (see
|
||||
[`00-META/repos.md`](00-META/repos.md)).
|
||||
|
||||
Before changing anything here, read the playbooks in
|
||||
|
||||
@@ -100,7 +100,7 @@ Answered separately, a repository of its own is the better home:
|
||||
changes. Tying documents to a code branch means they merge on the code's schedule.
|
||||
- **The reviewers are different.** A design argument is not reviewed the way an
|
||||
implementation is, and it should not queue behind a build.
|
||||
- **The scope is wider than one repository.** [ADR 0008](02-DECISIONS/0008-mesh-brokers-nodes-host-agents-think.md) sends most modules out of the
|
||||
- **The scope is wider than one repository.** [ADR 0001](02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) sends most modules out of the
|
||||
monorepo entirely. Documentation that governs several repositories cannot live inside one
|
||||
of them.
|
||||
|
||||
@@ -110,4 +110,4 @@ than a mechanism — which is why every decision is recorded in
|
||||
[`02-DECISIONS`](02-DECISIONS/) as it is taken, and why a document that states a rule should
|
||||
say how the rule is checked.
|
||||
|
||||
Recorded as [ADR 0011](02-DECISIONS/0011-how-this-repository-works.md).
|
||||
Recorded as [ADR 0019](02-DECISIONS/0019-how-this-repository-works.md).
|
||||
|
||||
Reference in New Issue
Block a user