HQ: the as-is base layer, the process, and the names #1
@@ -40,7 +40,7 @@ incident behind it is not written down, and the fix is to write it down, not to
|
||||
| **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 0006](../02-DECISIONS/0006-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. Today the installer owns and reconciles the links the mesh still uses [ADR 0011](../02-DECISIONS/0011-the-installer-owns-linking.md); the intent is that the mesh creates none at all [ADR 0018](../02-DECISIONS/0018-the-mesh-creates-no-symlinks.md). Neither reading permits you to make one. |
|
||||
| **Never push directly to the main branch** | Branch, push, review, merge. Every merge is a human checkpoint, without exception. |
|
||||
| **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 your own** | Unrelated improvements bundled together cannot be reviewed or reverted separately. Self-merging removes the checkpoint that is the entire point. |
|
||||
| **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 0008](../02-DECISIONS/0008-a-failed-step-fails-the-job.md), and §5. |
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
status: active
|
||||
initiated: 2026-08-23
|
||||
touches:
|
||||
- 02-DECISIONS/0015-mesh-brokers-nodes-host-agents-think.md
|
||||
- 02-DECISIONS/0017-modules-outside-the-core-are-grouped-by-domain.md
|
||||
- 03-DESIGN/00-as-is/00-overview.md
|
||||
- 03-DESIGN/01-to-be/00-work-breakdown.md
|
||||
became: []
|
||||
---
|
||||
|
||||
# 006 — The mesh designed from nothing
|
||||
|
||||
## What is being investigated
|
||||
|
||||
What the mesh would look like if it were laid out today, with the requirements known and none
|
||||
of the accumulated shape — expressed as a **skeleton**: repositories at the root, modules
|
||||
inside them, and whatever turns out to be the right leaf unit below that.
|
||||
|
||||
The deliverable is [`skeleton.md`](skeleton.md).
|
||||
|
||||
## Why
|
||||
|
||||
Every structural decision so far has been a **correction**: eight contexts replacing thirty-three
|
||||
modules ([ADR 0015](../../02-DECISIONS/0015-mesh-brokers-nodes-host-agents-think.md)), domains
|
||||
replacing single-function modules
|
||||
([ADR 0017](../../02-DECISIONS/0017-modules-outside-the-core-are-grouped-by-domain.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:
|
||||
|
||||
- **The bootstrap circularity.** The mesh needs a database, a bus, a registry and an identity
|
||||
provider. Those are modules the mesh installs. The mesh cannot install them before it exists.
|
||||
This has been worked around repeatedly and never designed away.
|
||||
- **Participation requires privilege.** Everything assumes root on a machine whose packages and
|
||||
services the mesh owns. A phone cannot participate on those terms, and neither can a machine
|
||||
someone else administers.
|
||||
|
||||
Designing from nothing is a way to find out which parts of the current shape are requirements
|
||||
and which are residue.
|
||||
|
||||
## The requirements this is designed against
|
||||
|
||||
Stated by the operator, recorded here so the skeleton can be checked against them rather than
|
||||
against taste:
|
||||
|
||||
1. The mesh manages multiple computers — **full control**, through modules installed to nodes.
|
||||
2. Mesh state lives in a **database**: which modules on which nodes, logs, configuration.
|
||||
3. Configuration has **several touchpoints** — tool surface, web interface, others — all hosted
|
||||
by the mesh itself.
|
||||
4. **Connectivity** is core: every node reachable from every other over a shared overlay, some
|
||||
nodes publicly exposed, firewalls configured.
|
||||
5. The mesh **hosts applications** — and requires some of them itself. This is the circularity.
|
||||
6. **Arch Linux only for now**; ideally any device, including phones, on lighter terms.
|
||||
7. The end goal is to **operate an IT company** on it — development, design, deployment, full
|
||||
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 0016](../../02-DECISIONS/0016-a-lab-node-is-a-virtual-machine.md)).
|
||||
|
||||
## Status
|
||||
|
||||
A first skeleton exists, with four design moves that the current shape does not have. It is
|
||||
`active` because two of them are unproven and one contradicts a record that is already
|
||||
accepted.
|
||||
|
||||
**Finding worth stating up front:** [ADR 0015](../../02-DECISIONS/0015-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
|
||||
the catalogue where modules genuinely change together under one intent. The skeleton adds it.
|
||||
|
||||
## Open questions
|
||||
|
||||
| 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 0015 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? | Decides whether "node" means one thing or two. |
|
||||
| Does `feature` survive? | The skeleton splits it in two and argues the conflation is what makes the delivery pipeline hard to reason about. Unproven. |
|
||||
@@ -0,0 +1,202 @@
|
||||
---
|
||||
effort: 006-mesh-from-scratch
|
||||
updated: 2026-08-23
|
||||
---
|
||||
|
||||
# The skeleton
|
||||
|
||||
Repositories at the root, modules inside them, parts at the leaf. Four tiers, and a dependency
|
||||
rule that only points downward.
|
||||
|
||||
## The tree
|
||||
|
||||
```
|
||||
hal-agent/ TIER 0 — the only thing ever installed by hand
|
||||
apply/ reconcile declared state on this machine
|
||||
inventory/ what this node is, has, and is capable of
|
||||
link/ the single outbound connection to the control plane
|
||||
store/ embedded local state — authoritative while disconnected
|
||||
profile/ capability detection: managed · user · edge
|
||||
substrate.lock pinned tier-1 descriptor, appliable with no mesh present
|
||||
|
||||
hal-substrate/ TIER 1 — declarations only, no logic of its own
|
||||
store/ relational state
|
||||
bus/ commands and events
|
||||
objects/ blobs and build artifacts
|
||||
images/ container images
|
||||
identity/ the identity provider
|
||||
bundle.yml the pinned set tier 0 can raise alone
|
||||
|
||||
hal-mesh/ TIER 2 — the control plane
|
||||
record/ the event log every context integrates through
|
||||
inventory/ nodes · modules · assignments · versions
|
||||
config/ settings · secrets · derivation onto nodes
|
||||
connectivity/ overlay · resolution · exposure · filtering · certificates
|
||||
provisioning/ resource grants between modules
|
||||
delivery/ source → artifact → node
|
||||
observability/ health · logs · metrics · alerts
|
||||
identity/ agents · humans · services · authorisation
|
||||
work/ tasks · workflows · runs
|
||||
knowledge/ memory · documents · retrieval
|
||||
api/ the one interface every surface speaks to
|
||||
|
||||
hal-surfaces/ TIER 3 — thin; no logic lives here
|
||||
tools/ the agent-facing tool surface
|
||||
web/ the operator-facing interface
|
||||
cli/ the shell-facing interface
|
||||
|
||||
hal-catalog/ TIER 4 — what the mesh hosts
|
||||
<domain>/ grouped per ADR 0017, list per research 005
|
||||
|
||||
hal-lab/ the whole mesh, disposable, on one machine
|
||||
hal-sdk/ contracts shared across tiers — types, not behaviour
|
||||
hal-hq/ this repository
|
||||
```
|
||||
|
||||
## The dependency rule
|
||||
|
||||
**A tier may depend only on tiers below it.** Substrate never references the control plane.
|
||||
The control plane never reaches into a node except through the agent. A surface holds no logic
|
||||
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 0008](../../02-DECISIONS/0008-a-failed-step-fails-the-job.md) applied to architecture.
|
||||
|
||||
## Move 1 — the substrate is applied, not delivered
|
||||
|
||||
**The problem.** The mesh needs a database, a bus, an object store, a registry and an identity
|
||||
provider. Today those are modules, and modules are installed by the delivery pipeline, which
|
||||
needs the database and the bus. The first node is therefore raised by a special script that
|
||||
exists only because of the circularity, and every later change to the substrate has to pretend
|
||||
the circularity is not there.
|
||||
|
||||
**The move.** The agent can apply a declaration without anyone telling it to. The substrate is
|
||||
a **pinned bundle** the agent carries: a fixed, versioned, self-contained descriptor of the
|
||||
five services and nothing else. Raising a first node is `agent apply substrate.lock` — not a
|
||||
special path, just the ordinary one with no control plane on the other end.
|
||||
|
||||
The circularity disappears rather than being worked around: **the substrate is applied by tier
|
||||
0, the mesh is delivered by tier 2, and they are different mechanisms on purpose.**
|
||||
|
||||
The price is real and should be named: the substrate is upgraded by bumping a pin and
|
||||
re-applying, not by the pipeline. It gets less machinery than everything else — no per-node
|
||||
selection, no provisioning, no fan-out — and that is the point. Five services justify a
|
||||
simpler mechanism than a hundred.
|
||||
|
||||
## Move 2 — the agent is one binary with capability profiles
|
||||
|
||||
**The problem.** Everything assumes root on a machine whose packages, services and network the
|
||||
mesh owns. A phone cannot offer that, and neither can a work laptop. The current answer would
|
||||
be a lightweight fork, which means two implementations and one of them rotting.
|
||||
|
||||
**The move.** One agent, one binary, and a **profile** it detects rather than is told:
|
||||
|
||||
| Profile | Can | Typical |
|
||||
|---|---|---|
|
||||
| `managed` | packages, services, network, filesystem — the full surface | a machine the mesh owns |
|
||||
| `user` | user-level services and tools; no package or network management | a shared or administered machine |
|
||||
| `edge` | report presence, relay, expose a tool surface; hold nothing | a phone |
|
||||
|
||||
A module declares which profiles it can land on. Assignment to an incapable node fails at
|
||||
declaration time, not at deploy time — a phone is not a machine that fails to install a
|
||||
firewall; it is a node the firewall module cannot be assigned to.
|
||||
|
||||
This makes the phone case a **capability question rather than a platform question**, which is
|
||||
what keeps it from becoming a second implementation. It also removes the current unstated
|
||||
assumption that every node is equivalent — already false, and today handled by remembering.
|
||||
|
||||
## Move 3 — connectivity becomes a context
|
||||
|
||||
[ADR 0015](../../02-DECISIONS/0015-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.
|
||||
|
||||
Meanwhile [research 005](../005-domain-grouping/analysis.md) measured the whole catalogue and
|
||||
found that reachability is the **only** place where modules genuinely change together under one
|
||||
intent — the proxy with the resolver, the firewall with the overlay, repeatedly, because *how a
|
||||
node is reachable* is one question asked in four places.
|
||||
|
||||
So the evidence and the gap point the same way. `connectivity` owns:
|
||||
|
||||
- the overlay every node joins, and the addresses on it
|
||||
- name resolution, internal and public
|
||||
- exposure — which services answer from outside, on which names
|
||||
- filtering — what may reach a node at all
|
||||
- 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 0015 the way
|
||||
[ADR 0017](../../02-DECISIONS/0017-modules-outside-the-core-are-grouped-by-domain.md) does —
|
||||
not written here.
|
||||
|
||||
## Move 4 — `feature` splits in two
|
||||
|
||||
The invitation was to check whether the concept survives. It does not, in one piece.
|
||||
|
||||
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 0014](../../02-DECISIONS/0014-build-publish-and-deploy-are-three-silos.md)
|
||||
is the pipeline half of the same confusion).
|
||||
|
||||
Split it:
|
||||
|
||||
| Concept | Is | Cardinality |
|
||||
|---|---|---|
|
||||
| **artifact** | something built and published — an image, a bundle, a package | once per module version |
|
||||
| **part** | an independently selectable piece of a module's desired state | chosen per node, per assignment |
|
||||
|
||||
A module declares desired state in parts, and produces artifacts. An assignment names the node
|
||||
and the parts. Delivery builds artifacts once and applies parts per node — and the two words
|
||||
now carry the two cardinalities that the pipeline already has.
|
||||
|
||||
This keeps what features are genuinely for — per-node opt-in of *some* of a module, which the
|
||||
work breakdown already calls for — and drops the conflation that makes the current model
|
||||
confusing.
|
||||
|
||||
## What the mesh is, versus what it hosts
|
||||
|
||||
Tiers 0–3 are the mesh. Tier 4 is everything it carries, and the boundary is stated by
|
||||
requirement rather than by taste: **a module is part of the mesh if removing it stops the mesh
|
||||
managing nodes.** A media server does not. An identity provider does — which is why identity
|
||||
sits in the substrate and not the catalogue, despite being, in every other respect, an
|
||||
application like any other.
|
||||
|
||||
That test also settles the IT-company goal without a special category. Development, design and
|
||||
deployment tooling are **workloads** — tier 4, hosted, provisioned, delivered like anything
|
||||
else. The mesh does not grow a "company" feature; it hosts the tools a company runs on, and
|
||||
the fact that it runs its own development on them is dogfooding, not architecture.
|
||||
|
||||
## What agents are, structurally
|
||||
|
||||
Self-improvement and self-healing are not a tier. Agents are participants
|
||||
([ADR 0012](../../02-DECISIONS/0012-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
|
||||
can do to the mesh, a person can do through the same surface, and anything it cannot express
|
||||
through the tool surface is a gap in the surface rather than a reason for a back door. Self-
|
||||
healing built on a private channel is unreviewable, and would be the one part of the mesh with
|
||||
no human checkpoint.
|
||||
|
||||
## How this is tested
|
||||
|
||||
The lab ([ADR 0016](../../02-DECISIONS/0016-a-lab-node-is-a-virtual-machine.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.
|
||||
|
||||
The tier rule is what makes that affordable. A scenario needing only tiers 0 and 1 is one
|
||||
virtual machine and a pinned bundle — which is also, exactly, the bootstrap path. **The
|
||||
hardest thing to test becomes the cheapest scenario to run**, and the first-node path stops
|
||||
being the one thing nobody exercises until it breaks.
|
||||
|
||||
## What this skeleton does not answer
|
||||
|
||||
- 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 0015 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
|
||||
little until that is costed.
|
||||
Reference in New Issue
Block a user