Novox Mesh, Nox, and HQ becomes company-scoped

ADR 0027 — the product is Novox Mesh, shortened to mesh internally. HAL was
never chosen: it arrived with the dotfiles repository this grew out of, it
is borrowed, and it is borrowed from the canonical untrustworthy machine
intelligence, which is an odd flag for infrastructure trusted with
credentials. Timing is the substance of the decision, not an aside — the
skeleton is not built, so renaming costs a search and replace now and a
migration later.

Nox is an identity of Novox, and specifically the agent of the MESH rather
than of a node. Nodes keep their own identities. Nox addresses them, and a
human mostly talks to Nox — which makes it the concrete form of the
mission's vision: state an intent, and the mesh works out which node holds
the thing. It holds no private channel. The gap this opens is recorded:
ADR 0012 binds every agent to a home node, and a mesh-scoped agent has
none, so the model needs extending.

ADR 0028 — HQ is company-scoped, novox/hq, with the mesh as its first
product. Checked rather than assumed: the company organisation already
holds live projects that the mesh builds and deploys, so they are tenants
rather than peers, and the mesh is the ground they stand on. There is also
company work outside the mesh already, which strengthens the case and means
the eventual split is closer than "some day" — so each document's scope is
fixed now, in a table, making that split mechanical instead of
archaeological. The folders are deliberately not restructured yet.

The skeleton takes the new vocabulary: mesh-host, mesh-substrate,
mesh-control, mesh-surfaces, mesh-catalog. Substrate drops to four services
now that identity is a hosted workload rather than a dependency.

Research 009 opens the migration, with the reframing that lowers its risk:
replace the control plane, do not move the workloads. Their data never
moves, so it is re-declared rather than adopted — which keeps adoption out
of scope, as the lab design requires. Self-hosting is the last phase, or a
failed cutover takes away the means to fix it.
This commit is contained in:
2026-08-23 21:20:35 +02:00
parent daf3e17c32
commit 87f4f29cc6
17 changed files with 340 additions and 47 deletions
@@ -31,7 +31,7 @@ Run the test. *Can the control plane exist without a relational store?* No. **Po
tier 1.** It lives once:
```
hal-substrate/store/postgres/
mesh-substrate/store/postgres/
```
There is no second copy in the catalogue, because the thing that differs between the mesh's own
@@ -40,7 +40,7 @@ database and a project's database is **not the module**. It is how that instance
| | 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:` |
| Declared in | `mesh-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
@@ -66,7 +66,7 @@ feels.
## Becoming self-hosting — the forge and the registries
Self-improvement means the mesh hosts the things it improves itself with: a forge, an image
registry, a package registry. The obvious worry is that these duplicate — a `hal-mesh-gitea`
registry, a package registry. The obvious worry is that these duplicate — a `mesh-gitea`
for the mesh and a `gitea` for everyone else. **They do not, and the reason is worth stating
carefully, because it is the same reason the bootstrap keeps failing today.**
@@ -203,7 +203,7 @@ parts is not a module.
Worked through for the substrate's relational store:
```
hal-substrate/store/postgres/
mesh-substrate/store/postgres/
module.yml provides: database · profiles: [managed]
parts/
service/ the container, its volume, its network exposure
@@ -215,7 +215,7 @@ hal-substrate/store/postgres/
## The tree, at file level
```
hal-host/ TIER 0
mesh-host/ TIER 0
cmd/host/
internal/
apply/ reconcile declared state
@@ -230,14 +230,14 @@ hal-host/ TIER 0
profile/ managed · user · edge
substrate.lock pinned tier-1 descriptor
hal-substrate/ TIER 1
mesh-substrate/ TIER 1
bundle.yml the pinned set, by digest
store/postgres/
bus/<broker>/
objects/<object-store>/
images/<registry>/
hal-mesh/ TIER 2
mesh-control/ TIER 2
record/ the event log contexts integrate through
inventory/ nodes · modules · assignments · versions
config/ settings · secrets · derivation
@@ -250,13 +250,13 @@ hal-mesh/ TIER 2
knowledge/ memory · documents · retrieval
api/ the one interface surfaces speak to
hal-surfaces/ TIER 3
mesh-surfaces/ TIER 3
tools/ web/ cli/
hal-catalog/ TIER 4
mesh-catalog/ TIER 4
<domain>/<module>/ layout as above
hal-lab/ hal-sdk/ hal-hq/
mesh-lab/ mesh-sdk/ mesh-hq/
```
## What this does not settle
+21 -21
View File
@@ -20,8 +20,8 @@ bare machine
│ one command lands ONE binary. Nothing else exists. TIER 0 host
│
│ it reads a pinned file it already carries and raises a
│ database, a bus, an object store, a registry, an
│ identity provider — locally, alone. TIER 1 substrate
│ database, a bus, an object store and an image
│ registry — locally, alone. TIER 1 substrate
│
│ on those, the mesh's brain starts: which nodes exist,
│ what runs where, what is reachable. TIER 2 control plane
@@ -46,7 +46,7 @@ A tier is not a repository and not a bounded context. Those are different cuts:
## The tree
```
hal-host/ TIER 0 — the only thing ever installed by hand
mesh-host/ 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
@@ -54,15 +54,14 @@ hal-host/ TIER 0 — the only thing ever installed by hand
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
mesh-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
mesh-control/ TIER 2 — the control plane
record/ the event log every context integrates through
inventory/ nodes · modules · assignments · versions
config/ settings · secrets · derivation onto nodes
@@ -75,17 +74,17 @@ hal-mesh/ TIER 2 — the control plane
knowledge/ memory · documents · retrieval
api/ the one interface every surface speaks to
hal-surfaces/ TIER 3 — thin; no logic lives here
mesh-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
mesh-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
mesh-lab/ the whole mesh, disposable, on one machine
mesh-sdk/ contracts shared across tiers — types, not behaviour
mesh-hq/ this repository
```
## The dependency rule
@@ -101,15 +100,15 @@ rule enforced by intention is the same as no tier rule — that is
## 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
**The problem.** The mesh needs a database, a bus, an object store and an image registry.
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 host can apply a declaration without anyone telling it to. The substrate is
a **pinned bundle** the host carries: a fixed, versioned, self-contained descriptor of the
five services and nothing else. Raising a first node is `host apply substrate.lock` — not a
four services and nothing else. Raising a first node is `host 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
@@ -217,9 +216,10 @@ confusing.
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.
managing nodes.** A media server does not. A relational store does — which is why it sits in the
substrate and not the catalogue, despite being, in every other respect, an application like any
other. An identity provider, notably, does **not**: the control plane authenticates its own
callers, so identity is a hosted service like the media server.
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
@@ -261,7 +261,7 @@ being the one thing nobody exercises until it breaks.
## A naming near-miss, recorded
The tier-0 binary was first called `hal-agent`, because "node agent" is the reflex everywhere
The tier-0 binary was first called `mesh-agent`, because "node agent" is the reflex everywhere
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
@@ -273,8 +273,8 @@ pointing at infrastructure.
[ADR 0015](../../02-DECISIONS/0015-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** (`hal-mesh`), the tier-0 binary **hosts**
(`hal-host`), the participant **thinks** (`agents`, untouched).
components: the control plane **brokers** (`mesh-control`), the tier-0 binary **hosts**
(`mesh-host`), the participant **thinks** (`agents`, untouched).
`hal-node` was the alternative and was rejected: *Node* is the aggregate in the inventory — the
`mesh-node` was the alternative and was rejected: *Node* is the aggregate in the inventory — the
record of a machine — while the binary is what runs on it and does the hosting.