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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
status: active
|
||||
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/0016-a-lab-node-is-a-virtual-machine.md
|
||||
- 03-DESIGN/00-as-is/00-overview.md
|
||||
became: []
|
||||
---
|
||||
|
||||
# 009 — Getting from the mesh that exists to the mesh that is designed
|
||||
|
||||
## What is being investigated
|
||||
|
||||
How a running mesh becomes the one in
|
||||
[research 006](../006-mesh-from-scratch/code-skeleton.md), without losing what it currently
|
||||
carries.
|
||||
|
||||
The proposed shape: **build tiers 0, 1 and 2, then replace the current setup in one move.**
|
||||
|
||||
## Why big-bang is the right instinct here
|
||||
|
||||
Recorded because incremental is the reflex answer and it is wrong in this case.
|
||||
|
||||
- **The two models are structurally incompatible.** The tier rule, the host absorbing what are
|
||||
now modules, the artifact/part split, provisioning generalised to the control plane's own
|
||||
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 0016](../../02-DECISIONS/0016-a-lab-node-is-a-virtual-machine.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.
|
||||
- **Incremental would carry the rot forward.** The as-is layer documents silent failure paths,
|
||||
a dead test harness and unenforced rules. A gradual migration preserves them by definition.
|
||||
|
||||
## The distinction that lowers the risk
|
||||
|
||||
**Replace the control plane; do not move the workloads.**
|
||||
|
||||
The things that would hurt to lose — mail, media, source, databases, their data directories —
|
||||
are not the mesh. They are what the mesh manages. They sit in container volumes on nodes, and
|
||||
they do not need to move for the control plane above them to be replaced.
|
||||
|
||||
So the big-bang is: the old control plane stops managing these nodes, and the new one starts —
|
||||
with the workload data untouched, in place, and re-declared rather than migrated.
|
||||
|
||||
That reframing turns "replace the mesh" into "replace the part with no persistent state of its
|
||||
own", which is a materially smaller act than it first sounds.
|
||||
|
||||
## The tension this exposes
|
||||
|
||||
Taking over already-running workloads is **adoption**, and adoption was ruled out of scope —
|
||||
recorded as a legacy path in
|
||||
[`03-DESIGN/01-to-be/01-end-to-end-testing.md`](../../03-DESIGN/01-to-be/01-end-to-end-testing.md).
|
||||
The migration appears to need exactly the capability the design declared it would not have.
|
||||
|
||||
The way out, to be tested: the new mesh does not adopt anything. It **declares** the workloads
|
||||
from scratch and points them at data directories that already exist. Nothing inspects a running
|
||||
machine to learn what is there; the declarations are written from the as-is layer, which is what
|
||||
that layer is for. Data survives because it was never touched, not because it was adopted.
|
||||
|
||||
If that holds, adoption stays out of scope and the migration is ordinary declaration. If it does
|
||||
not, adoption needs a one-time, explicitly unsupported tool, and that should be a decision rather
|
||||
than a discovery.
|
||||
|
||||
## Sequencing
|
||||
|
||||
| Phase | What | Done when |
|
||||
|---|---|---|
|
||||
| A | Build tier 0. 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 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. |
|
||||
| D | Declare the existing workloads against the new model. | The lab runs them, with copies of real data shapes. |
|
||||
| E | **Rehearse the cutover in the lab** against a mesh built to resemble the real one. | Repeatable, and repeatably reversible. |
|
||||
| F | Cut over. | The real nodes are managed by the new control plane. |
|
||||
| G | Reach self-hosting — re-bind delivery from external providers to the mesh's own forge and registries. | The mesh builds and deploys itself. |
|
||||
|
||||
Phase G is deliberately last. Per research 006, self-hosting is a state the mesh **reaches**;
|
||||
attempting the cutover and the self-hosting transition in the same move recreates exactly the
|
||||
circularity the skeleton removes — and would mean a failed cutover could take away the means to
|
||||
fix it.
|
||||
|
||||
## The questions
|
||||
|
||||
| Question | Why it matters |
|
||||
|---|---|
|
||||
| What state must **survive** the cutover, versus be re-created? | Workload data must. Provisioned credentials could be re-issued. The mesh's own inventory could be re-declared. Each answer changes the risk. |
|
||||
| What is the **way back**? | A cutover with no rollback is not a plan. If workload data is untouched, reverting may be as small as re-pointing the old control plane at it — to be verified, not assumed. |
|
||||
| How is the cutover **rehearsed** against something resembling the real mesh, without copying the real mesh into a repository? | The lab must be able to model the real topology's shape without carrying its identity. |
|
||||
| Does anything have to keep running **during** the cutover? | Mail and source are the obvious candidates. If yes, "big-bang" is really "big-bang with exceptions", and the exceptions should be named now. |
|
||||
| Is the forge inside or outside the cutover? | If the mesh's own forge goes down with the old control plane, the means of deploying a fix goes with it. This is the self-hosting circularity appearing as a migration risk. |
|
||||
Reference in New Issue
Block a user