|
|
|
@@ -0,0 +1,182 @@
|
|
|
|
|
---
|
|
|
|
|
effort: 006-mesh-from-scratch
|
|
|
|
|
updated: 2026-08-23
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# The code skeleton
|
|
|
|
|
|
|
|
|
|
[`skeleton.md`](skeleton.md) laid out tiers and repositories. This is the level below: what a
|
|
|
|
|
module looks like on disk, and — the question that forced a correction — where a given piece of
|
|
|
|
|
today's catalogue actually lands.
|
|
|
|
|
|
|
|
|
|
## The tier test
|
|
|
|
|
|
|
|
|
|
A decision procedure, so placement is answerable rather than argued. Ask in order; first match
|
|
|
|
|
wins:
|
|
|
|
|
|
|
|
|
|
1. **Does it apply state on a machine?** → tier 0, inside the host.
|
|
|
|
|
2. **Can the control plane exist without it?** If *no* → tier 1, substrate.
|
|
|
|
|
3. **Does it decide what should be true across nodes?** → tier 2, a control-plane context.
|
|
|
|
|
4. **Is it a way to talk to tier 2, holding no logic of its own?** → tier 3, a surface.
|
|
|
|
|
5. **Otherwise** → tier 4, a workload.
|
|
|
|
|
|
|
|
|
|
## Worked example — where postgres ends up
|
|
|
|
|
|
|
|
|
|
The obvious answer is "twice": once as the mesh's own database in the substrate, once as a
|
|
|
|
|
hosted database in the catalogue. That answer is wrong, and seeing why fixes something.
|
|
|
|
|
|
|
|
|
|
Run the test. *Can the control plane exist without a relational store?* No. **Postgres is
|
|
|
|
|
tier 1.** It lives once:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
hal-substrate/store/postgres/
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
There is no second copy in the catalogue, because the thing that differs between the mesh's own
|
|
|
|
|
database and a project's database is **not the module**. It is how that instance is brought up:
|
|
|
|
|
|
|
|
|
|
| | The mesh's own instance | A project's database |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| Brought up by | the host, from the pinned bundle, with no control plane present | the ordinary delivery and provisioning path |
|
|
|
|
|
| Declared in | `hal-substrate/bundle.yml` | the consuming module's `requires:` |
|
|
|
|
|
| Exists because | the control plane cannot start without it | something asked for it |
|
|
|
|
|
|
|
|
|
|
Same module, two roles. **Tier is a property of the module** — what must exist before what — and
|
|
|
|
|
**the bundle is a property of the mesh's own instance.**
|
|
|
|
|
|
|
|
|
|
This is also legal under the dependency rule, which is worth checking rather than assuming: a
|
|
|
|
|
tier-4 workload that requires a database depends on tier 1, which points *downward*. The
|
|
|
|
|
inverse — substrate reaching into the catalogue for a module — would not be, and is the shape
|
|
|
|
|
the naive "twice" answer would have created.
|
|
|
|
|
|
|
|
|
|
The same reasoning places the rest of the substrate: the bus, the object store, the image
|
|
|
|
|
registry. Each fails step 2, each lives once, each is pinned.
|
|
|
|
|
|
|
|
|
|
**One genuine boundary case, flagged rather than decided.** The identity provider passes step 2
|
|
|
|
|
only if the control plane delegates authentication rather than doing it natively. If tier 2
|
|
|
|
|
authenticates callers itself, the identity provider drops to tier 4 and the substrate has four
|
|
|
|
|
services instead of five. The test does not answer this; it turns it into a question with a
|
|
|
|
|
clear shape, which is what a test is for.
|
|
|
|
|
|
|
|
|
|
## The five fates of a module
|
|
|
|
|
|
|
|
|
|
Research 005 asked whether a catalogue module should be **grouped into a domain** or **leave
|
|
|
|
|
the repository**. Working the tier test across the catalogue surfaces a third answer that
|
|
|
|
|
neither option covers, and it is the most common one.
|
|
|
|
|
|
|
|
|
|
| Fate | Means | Examples from today |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| **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 0017](../../02-DECISIONS/0017-modules-outside-the-core-are-grouped-by-domain.md). | media library, desktop session, collaboration tooling |
|
|
|
|
|
| **Leaves the repository** | A standalone application, per [ADR 0010](../../02-DECISIONS/0010-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
|
|
|
|
|
together under one intent. The skeleton explains *why*: they are not four modules that ought to
|
|
|
|
|
be one domain module. They are four facets of one thing the host should own, currently
|
|
|
|
|
expressed as modules because a module was the only unit available.
|
|
|
|
|
|
|
|
|
|
Under this skeleton the overlay module and the firewall module **stop existing**. The host holds
|
|
|
|
|
membership and applies filtering; tier 2 decides the policy; the swappable backends stay
|
|
|
|
|
modules. That is a different and better answer than grouping them, and it was not visible from
|
|
|
|
|
inside the current frame.
|
|
|
|
|
|
|
|
|
|
It also partly answers research 005's other open question — the fifty modules that co-change
|
|
|
|
|
with nothing. Several are host concerns rather than domains: package management, container
|
|
|
|
|
runtime, filesystem tooling. Silence was the right signal after all; the wrong conclusion was
|
|
|
|
|
that grouping was the only available fix.
|
|
|
|
|
|
|
|
|
|
## What a module looks like on disk
|
|
|
|
|
|
|
|
|
|
Per [`skeleton.md`](skeleton.md) Move 4, a module declares **parts** — independently selectable
|
|
|
|
|
pieces of desired state — and produces **artifacts** — things built once per version.
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
<module>/
|
|
|
|
|
module.yml identity, what it provides, what it requires,
|
|
|
|
|
which host profiles it can land on
|
|
|
|
|
parts/
|
|
|
|
|
service/ desired state: container, volumes, exposure
|
|
|
|
|
provisioner/ how it grants its resource to consumers
|
|
|
|
|
migrations/ its own persistent state
|
|
|
|
|
tools/ capabilities it contributes
|
|
|
|
|
artifacts/
|
|
|
|
|
<name>/ source for something built and published
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
A module with no artifacts consumes an upstream image and builds nothing. A module with no
|
|
|
|
|
parts is not a module.
|
|
|
|
|
|
|
|
|
|
Worked through for the substrate's relational store:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
hal-substrate/store/postgres/
|
|
|
|
|
module.yml provides: database · profiles: [managed]
|
|
|
|
|
parts/
|
|
|
|
|
service/ the container, its volume, its network exposure
|
|
|
|
|
provisioner/ grants a database and role to a consumer
|
|
|
|
|
migrations/ none — it holds no state of its own
|
|
|
|
|
artifacts/ none — upstream image, pinned by digest in bundle.yml
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## The tree, at file level
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
hal-host/ TIER 0
|
|
|
|
|
cmd/host/
|
|
|
|
|
internal/
|
|
|
|
|
apply/ reconcile declared state
|
|
|
|
|
inventory/ what this machine is and can do
|
|
|
|
|
link/ outbound connection to the control plane
|
|
|
|
|
overlay/ membership: address, keys, tunnel
|
|
|
|
|
filter/ packet filtering from tier-2 policy
|
|
|
|
|
packages/ package management
|
|
|
|
|
services/ supervision
|
|
|
|
|
containers/ container runtime
|
|
|
|
|
store/ embedded local state
|
|
|
|
|
profile/ managed · user · edge
|
|
|
|
|
substrate.lock pinned tier-1 descriptor
|
|
|
|
|
|
|
|
|
|
hal-substrate/ TIER 1
|
|
|
|
|
bundle.yml the pinned set, by digest
|
|
|
|
|
store/postgres/
|
|
|
|
|
bus/<broker>/
|
|
|
|
|
objects/<object-store>/
|
|
|
|
|
images/<registry>/
|
|
|
|
|
identity/<idp>/ boundary case — see above
|
|
|
|
|
|
|
|
|
|
hal-mesh/ TIER 2
|
|
|
|
|
record/ the event log contexts integrate through
|
|
|
|
|
inventory/ nodes · modules · assignments · versions
|
|
|
|
|
config/ settings · secrets · derivation
|
|
|
|
|
connectivity/ addresses · resolution · exposure · filtering · certificates
|
|
|
|
|
provisioning/ grants between modules
|
|
|
|
|
delivery/ source → artifact → node
|
|
|
|
|
observability/ health · logs · metrics
|
|
|
|
|
identity/ agents · humans · services · authorisation
|
|
|
|
|
work/ tasks · workflows · runs
|
|
|
|
|
knowledge/ memory · documents · retrieval
|
|
|
|
|
api/ the one interface surfaces speak to
|
|
|
|
|
|
|
|
|
|
hal-surfaces/ TIER 3
|
|
|
|
|
tools/ web/ cli/
|
|
|
|
|
|
|
|
|
|
hal-catalog/ TIER 4
|
|
|
|
|
<domain>/<module>/ layout as above
|
|
|
|
|
|
|
|
|
|
hal-lab/ hal-sdk/ hal-hq/
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## What this does not settle
|
|
|
|
|
|
|
|
|
|
- **The identity boundary case.** Four substrate services or five.
|
|
|
|
|
- **Where the record lives.** Still the open question from
|
|
|
|
|
[`00-overview.md`](00-overview.md), and the tier test does not resolve it: the record is
|
|
|
|
|
needed by tier 2 and is *of* tier 2, which is exactly the shape that produces a circularity.
|
|
|
|
|
- **Whether absorbing into the host makes the host too large.** Six internal concerns is already
|
|
|
|
|
a lot for a binary whose whole argument is that it has no dependencies. The counter-argument
|
|
|
|
|
is that each is small and none can be optional — but this is the skeleton's biggest unproven
|
|
|
|
|
claim, and it should be tested by writing the host's interface before anything else.
|
|
|
|
|
- **The migration.** Nothing here says how today becomes this.
|