Files
mesh-controller/internal/inventory/migrations/0001-nodes.sql
T
jschoubben 306c4ca13b The control plane, as far as identity
Tier 2 exists now. It holds one context of seven, inventory, and does one
thing with it: brings its schema up to date. That is step 3 of the substrate
bootstrap -- the step the first node cannot get past.

Verified against a real PostgreSQL, with the built binary: applied 0001-nodes,
reported 'already up to date' on the second run, and the node table is there
with the index and the unique constraint the migration asks for.

Written in Go, and the image is FROM scratch holding one file. Confirmed by
unpacking it. That is the whole argument of ADR 0024: the bundle pins this
image by digest and runs it where nothing can check it, so everything in it is
something a person has to audit before trusting a first node.

Exclusive store ownership is built as a rule about credentials rather than
about intentions. There is no mesh-wide connection setting and no way to ask
for one -- a context reads MESH_STORE_<ITS OWN NAME> and holds nothing else, so
reaching another context's store needs a new variable, which is visible in the
declaration that runs it.

The migration runner is mostly refusals: an edited migration that already ran,
a migration numbered below one that has run, duplicate numbers, misnamed files,
empty files. All stop rather than warn, because at the moment any of them is
true nobody knows what the database holds.

It stops before identity, deliberately. What a node presents to prove who it is
has not been decided anywhere, and a migration is the most expensive place in
this system to guess.

Two tests did not defend what they claimed, and both are fixed rather than
removed. One asked only whether Open returned an error, which it did either way
-- a bad context name and a missing credential both fail, so deleting the name
check changed nothing. The other claimed to prove the migration runs in a
transaction, but PostgreSQL already wraps a multi-statement query in one of its
own, so it passed with the transaction taken out. What the transaction actually
buys is that the schema change and the row recording it commit together, and
there is now a test for that which fails when they are split.
2026-08-29 02:44:09 +02:00

43 lines
2.3 KiB
SQL

-- The node records: which machines this mesh knows about.
--
-- novox/hq ADR 0006 — inventory holds nodes, modules, assignments and versions, and this is the
-- first of the four. The others arrive with delivery, which is not built; a table nothing writes
-- to is a guess about a shape, and guesses about shapes are what migrations make expensive.
create table node (
id uuid primary key default gen_random_uuid(),
-- What a person calls this machine. Unique because it is how a node is named when a token is
-- issued for it (`token issue --node workstation`), and a name that matched two records would
-- make that command ambiguous at exactly the moment it grants access to the mesh.
name text not null unique,
created timestamptz not null default now(),
-- The last profile the node reported: what it can run, which is the input to deciding what it
-- should run (novox/hq 09-the-node-lifecycle, step 4).
--
-- Held opaquely, as the document the node sent. The host owns that shape and the control
-- plane's job here is to keep the last one faithfully, not to have an opinion about it — so a
-- host that learns to report something new does not need this schema to change first.
profile jsonb,
-- When this node was last heard from.
--
-- There is no `state` column, and its absence is deliberate. The lifecycle has four states,
-- but two of them — unmanaged and hosted — are situations of a *machine* that the mesh has
-- not been told about, so they cannot be rows here. The remaining pair, enrolled and
-- disconnected, are described in novox/hq ADR 0004 as the same node in two situations rather
-- than two kinds of thing, and the difference between them is how long it has been since this
-- column moved.
--
-- Stored as a state it would have to be written by something noticing a node had gone quiet —
-- and nothing notices silence. It would be correct while nodes were talking and wrong exactly
-- when it mattered.
last_seen timestamptz
);
-- "Has this node been unreachable for a week" is the control plane's question by definition
-- (novox/hq ADR 0006 — nobody else is watching), so the column it is asked of is indexed.
create index node_last_seen on node (last_seen nulls first);