Merge main: the trunk renamed the seats and made them data

Both branches changed the seat set from the same starting point, so every number
collided and every `mesh-*` name existed twice. The trunk's numbers and names win:
this branch's records became 0129/0130 and its migrations 0037/0038, and the
hardcoded rename map gave way to the trunk's `seat_alias` table — a rename is a
row now (ADR 0122), not a recompile.

Three of my checks were wrong and the merge is what showed it:

A seat with an empty protocol is a marker, not an incomplete declaration. Most
node-scoped seats are markers — which module is this machine's packet filter —
and refusing one refused most of the set, the showcase module included. A
mistyped field name is already refused by the parser, so an empty protocol was
written as one deliberately.

A claim on a seat this manifest does not declare is not the parser's to judge. A
module may hold a seat another module declared; that is the whole reason ADR 0126
has callers name the seat and not its provider. Whether the seat exists is a fact
about the catalogue, so the refusal is at registration, where every declaration
is in view.

And a seat may share a name with the provision it delivers. `git`, the npm
registry and the artifact store still do, because renaming a delivering seat
cascades to every consumer requiring it, with a window where a holder stops
resolving mid-flight. The trunk deferred exactly those three on purpose.

Full suite green against a real NATS and store.
This commit is contained in:
2026-09-27 18:50:18 +02:00
71 changed files with 2084 additions and 615 deletions
@@ -0,0 +1,36 @@
-- Every bus user's password hash, because the file has to be written again.
--
-- novox/hq design 25 §4, task 1.7. On the bus the mesh runs on today an account is created by a
-- management call: the mesh mints a password, hands it over, seals the plaintext to whoever will
-- use it, and keeps nothing. That works because the broker remembers.
--
-- The bus being built has no management call — its users are a file the controller composes, and
-- **the whole file is written every time any of it changes**. So the first person's access change
-- would silently blank every module's password. The hash has to outlive its own minting, which is
-- state the mesh did not need before and does now.
--
-- Keyed by username, because the username is exactly what the composed file needs and what a
-- principal derives from its own identity. Nothing else about the user is here: **permissions are
-- not stored.** They are derived from what each module declares, every time the file is written
-- (ADR 0043) — a stored copy would be a second account of a user's authority, able to disagree
-- with the first, and the disagreement would be invisible until somebody compared a file with a
-- manifest.
--
-- The hash and not the password. A file on a node's disk holds the hash, and so does this: a
-- credential recoverable from the mesh's store is one whose blast radius is the store's.
create table bus_user (
username text primary key,
-- kind and what it names, so a user whose subject is gone can be found and removed: a module
-- unassigned, a node forgotten, a token spent. Recorded rather than parsed back out of the
-- username, because a name is for the server and a parser over it would be a second grammar.
kind text not null,
node text not null default '',
module text not null default '',
password_hash text not null,
minted_at timestamptz not null default now()
);
-- Finding every user of one kind, and every user belonging to one node — which is what removing a
-- node, or composing after an assignment, asks.
create index bus_user_kind on bus_user (kind);
create index bus_user_node on bus_user (node) where node <> '';