gen-settings
gen-settings resolves an aspect’s settings — a static, introspectable schema of { default; merge } leaves — against an ordered list of override layers, producing a resolved value plus a
full per-field provenance chain.
The problem it owns: “why does this field have this value” needs a real answer once more than one layer can set it — env defaults, host overrides, an explicit per-node value. gen-settings resolves the merge and keeps the chain that produced it, so the answer to “why” is a lookup, not an investigation.
It has the largest dependency footprint on the roster: gen-algebra, gen-bind, gen-graph, gen-identity, gen-prelude, gen-schema, and gen-types. No roster member currently builds on gen-settings.
Take it alone
Section titled “Take it alone”let genSettings = (import ./lib) { prelude = gen-prelude.lib; algebra = gen-algebra.lib; bind = gen-bind.lib; graph = gen-graph.lib; }; inherit (genSettings) mkSchema resolveOne;
schema = mkSchema { aspect = config.aspects.firewall; # a registry entry (carries id_hash) fields = { "allowed-tcp" = { default = [ 22 ]; merge = "append"; }; hostname = { default = "unset"; }; # replace (implicit) }; };
resolved = resolveOne { inherit schema; layers = [ { scope = { env = config.envs.prod; }; rendered = "prod"; via = null; value = { "allowed-tcp" = [ 80 ]; }; } { scope = { host = den.hosts.axon-01; }; rendered = "axon-01"; via = null; value = { "allowed-tcp" = [ 443 ]; hostname = "axon-01"; }; } ]; };inresolved.value # => { "allowed-tcp" = [ 22 80 443 ]; hostname = "axon-01"; }Palettes adapted from Catppuccin (Macchiato) (MIT), Tokyo Night (Apache-2.0), gruvbox (MIT), Catppuccin (Latte) (MIT), Rosé Pine (Dawn) (MIT).