-- A declaration says the assignment generation it was composed from, and every send is recorded -- (novox/hq issue 234). -- -- On 2026-10-04 a declaration newer in sequence than every other named four fewer modules than the -- assignments held, and the machine undeclared all four. The sequence orders arrival; it cannot tell a -- later send that carries an older view of the assignments. And nothing the mesh kept said which process -- sent it, from what view: the node row holds only the digest of the last send. -- The assignment generation: one counter for the mesh, raised in the same transaction as every change to -- what is assigned where. A trigger, not a line in each writer: an assignment is also taken by a node's -- removal (on delete cascade) and renamed with its module (on update cascade), which no writer in Go -- sees, and a rule kept only by the writers that remember it is the rule this issue found broken. Seeded -- at 1, so every declaration composed after this claims a generation and none claims zero ("none"). create table assignment_generation ( one boolean primary key default true check (one), generation bigint not null ); insert into assignment_generation (one, generation) values (true, 1); create function raise_assignment_generation() returns trigger language plpgsql as $$ begin update assignment_generation set generation = generation + 1; return null; end $$; -- Per row, so a statement that changes nothing raises nothing: an assignment repeated (`on conflict do -- nothing` inserts no row) and an update that leaves a row as it was (the WHEN below; a plain UPDATE fires a -- row trigger whether or not it changed anything). A statement that changes several rows raises once per -- row, which only ever moves it forward. Two triggers, because only an update has both OLD and NEW. create trigger assignment_generation_raised after insert or delete on assignment for each row execute function raise_assignment_generation(); create trigger assignment_generation_raised_by_update after update on assignment for each row when (old is distinct from new) execute function raise_assignment_generation(); -- The generation a machine was last sent, written in the same transaction as the digest of that send, so -- what the mesh WOULD send is stamped the same way and reads as byte for byte what it DID send when -- nothing else changed (as sent_epoch, migration 0068). Null for a send without one. alter table node add column sent_generation bigint; -- Whether the machine's node-engine said it reads a generation (its reports' `reads_generation`): until -- it has, it is sent none, because an older node-engine refuses a key it does not know, whole. alter table node add column reads_generation boolean not null default false; -- Every send: the machine, its sequence, who sent it — the lease epoch the sender acted under and the -- caller, process and build — the generation it was composed from, its digest and the modules it named. -- Written after the declaration is away, with the node row's record of it. A refusal by the machine of a -- send for its generation is kept on the send it refused, so the condition it raises names the sender -- from here; a refusal of a sequence the mesh has no send for is kept as a row of its own, `recorded` -- false, whose sender is said to be unknown. `counter_raised_to` is the generation the mesh's counter was -- raised to when the refusal showed the counter behind the machine (a store put back from a backup). The -- newest 200 per machine are kept. create table declaration_send ( id bigserial primary key, node uuid not null references node (id) on delete cascade, sequence bigint, epoch bigint, sender text not null, generation bigint, digest text not null, modules text[] not null default '{}', sent_at timestamptz not null default now(), refused_at timestamptz, -- refused_applied is the generation the machine said it had applied when it refused this send. refused_applied bigint, counter_raised_to bigint, recorded boolean not null default true ); create index declaration_send_node on declaration_send (node, id desc); create index declaration_send_sequence on declaration_send (node, sequence); create index declaration_send_refused on declaration_send (refused_at) where refused_at is not null;