-- A config value comes from four sources, the last set winning (novox/hq issue 630): the module's -- default, mesh config, node config, and the module's own value on the node. Mesh config and node config -- hold values only for the predefined keys, a fixed list kept in the catalogue (`predefined.json`). An -- assignment is one module on one node: there is no module layer for the whole mesh any more. -- The list of predefined keys, as the catalogue's predefined.json last said it. Replaced whole when a -- build of the catalogue carries the file; `repository` and `commit` say which. create table predefined_key ( key text primary key, type text not null check (type in ('number', 'string', 'boolean')), why text not null, repository text, commit text, listed_at timestamptz not null default now() ); -- Mesh config (node null) and node config: one value per predefined key, and per node. Not a foreign key -- to predefined_key: a key the list drops keeps its value, said as reaching nothing, rather than losing it. create table config ( key text not null, node uuid references node(id) on delete cascade, value jsonb not null, set_at timestamptz not null default now(), set_by text, why text not null ); create unique index config_for_the_mesh on config (key) where node is null; create unique index config_for_a_node on config (key, node) where node is not null; -- Every value that was replaced or cleared, with who set it and why, and who replaced it and why. create table config_history ( key text not null, node uuid references node(id) on delete cascade, value jsonb not null, set_at timestamptz, set_by text, why text, replaced_at timestamptz not null default now(), -- set · clear replaced_by text not null, replaced_why text ); create index config_history_by_key on config_history (key, node, replaced_at desc); -- The module layer for the whole mesh goes: each one is laid under the layer of every node the module is -- assigned to — the mesh's values first, the node's over them, maps merged key by key as composition -- merged them — so every machine composes what it did. Every layer replaced, and the mesh-wide layer -- itself, is kept in settings_history (ADR 0217) with the reason `moved-to-nodes`. A mesh-wide layer of -- a module assigned nowhere is kept in the history only: no machine read it. create function pg_temp.config_630_merge(under jsonb, over jsonb) returns jsonb language plpgsql immutable as $$ declare merged jsonb; k text; begin if jsonb_typeof(under) is distinct from 'object' or jsonb_typeof(over) is distinct from 'object' then return over; end if; merged := under; for k in select jsonb_object_keys(over) loop if merged ? k then merged := jsonb_set(merged, array[k], pg_temp.config_630_merge(merged -> k, over -> k)); else merged := merged || jsonb_build_object(k, over -> k); end if; end loop; return merged; end $$; insert into settings_history (node, module, values, set_at, replaced_by, set_by) select t.node, t.module, t.values, t.set_at, 'moved-to-nodes', t.set_by from settings t join settings s on s.module = t.module and s.node is null where t.node is not null and exists (select 1 from assignment a where a.node = t.node and a.module = t.module); insert into settings_history (node, module, values, set_at, replaced_by, set_by) select null, module, values, set_at, 'moved-to-nodes', set_by from settings where node is null; insert into settings (node, module, values, set_at, set_by) select a.node, s.module, s.values, now(), coalesce(s.set_by || '; ', '') || 'moved from the whole mesh''s layer to this node''s (novox/hq issue 630)' from settings s join assignment a on a.module = s.module where s.node is null on conflict (node, module) where node is not null do update set values = pg_temp.config_630_merge(excluded.values, settings.values), set_at = now(), set_by = coalesce(settings.set_by || '; ', '') || 'the whole mesh''s layer laid under it (novox/hq issue 630)'; delete from settings where node is null;