substrate — 12
Values, graphs, selection, evaluation. The base layer: gen-prelude, gen-identity, gen-algebra, gen-scope, gen-memo, gen-graph, gen-bind, gen-schema, gen-select, gen-dispatch, gen-product, gen-view.
gen is not one library. It is twenty-two, each owning exactly one concern — evaluation, graph queries, pattern matching, module merging, rule dispatch — and each shipping its own test suite and its own CI gate. This hub owns none of those concerns itself. It publishes the roster and wires the ones that need wiring; everything else you can take one flake input at a time.
The roster of record is mkGenLibs’s function signature and the total strata map beside it —
never a count kept anywhere else:
identity = "substrate";algebra = "substrate";types = "modules";merge = "modules";scope = "substrate";memo = "substrate";graph = "substrate";bind = "substrate";schema = "substrate";aspects = "aspects";select = "substrate";dispatch = "substrate";product = "substrate";settings = "framework";assemble = "framework";program = "framework";Every member sits in exactly one of four ranked strata. The order is one-way — a library may depend down the ranking or sideways within it, never up:
substrate — 12
Values, graphs, selection, evaluation. The base layer: gen-prelude, gen-identity, gen-algebra, gen-scope, gen-memo, gen-graph, gen-bind, gen-schema, gen-select, gen-dispatch, gen-product, gen-view.
modules — 2
The module system’s checking and merging halves, built on the substrate: gen-types, gen-merge.
aspects — 3
Composition on top of the module system: gen-aspects, gen-link, gen-class.
framework — 5
Above the whole stack rather than a layer inside it: gen-assemble, gen-program, gen-delivery, gen-settings, gen-inspect.
Exactly one edge runs against that order, and it is ruled rather than hidden: gen-schema sits in substrate by role but declares kinds whose fields are options, so it reaches sideways into gen-merge in modules. The checker that walks the whole graph prints that exception on every run instead of passing quietly around it. The full case for why a member holds exactly one stratum, and what the ordering actually buys, is Strata.
Every library exports exactly one .lib, and every library resolves its own dependencies — there
is nothing this hub does for a single library that the library doesn’t already do for itself. Take
inputs.gen-graph.lib and you have gen-graph; nothing else loads. The three framework-tier
libraries that don’t declare their own substrate as flake inputs (gen-assemble, gen-program,
gen-delivery) get it injected by this hub instead, by construction rather than convention:
# The substrate the hub hands the three members whose flake `.lib` is published UNAPPLIED. A FUNCTION# OF THE HUB'S OWN `members` BINDINGS — never of `inputs` — so there is one expression per member in# this flake and this file re-derives none of them. `inherit` binds without forcing, so a reader# taking `attrNames` never forces a leaf, which is what lets `ci/` read the supplied key sets# hermetically, the same direction `ci/hub-entry.nix` takes on the standalone root.members: { assemble = { inherit (members) prelude scope algebra; }; delivery = { inherit (members) algebra aspects; }; # ★ gen-inspect's fifth declared dependency, gen-program, is NOT here and cannot be: this file is a # function of `members`, which holds exactly the eighteen roster entries whose `.lib` is published # APPLIED, and `program` is one of the three UNAPPLIED members the fold over this very attrset # produces. gen-inspect therefore declares four formals at this gate and takes gen-program at the # gate that builds its program route, when the fold this file feeds is rewritten to be # self-referential or the dependency arrives another way. inspect = { inherit (members) prelude graph select scope ; }; program = { inherit (members) prelude scope; };}That is the entire cost of the hub, for the three libraries that need it. The other nineteen never see this file. Add gen to a flake walks both routes — one library alone, and the full hub — end to end.
| library | stratum | depends on |
|---|---|---|
| gen-prelude | substrate | — |
| gen-identity | substrate | — |
| gen-algebra | substrate | — |
| gen-scope | substrate | graph, identity, prelude, schema |
| gen-memo | substrate | graph, prelude |
| gen-graph | substrate | prelude |
| gen-bind | substrate | graph, prelude |
| gen-schema | substrate | algebra, identity, merge, prelude |
| gen-select | substrate | algebra |
| gen-dispatch | substrate | prelude |
| gen-product | substrate | prelude |
| gen-view | substrate | graph, prelude |
| gen-types | modules | identity, prelude |
| gen-merge | modules | memo, prelude, types |
| gen-aspects | aspects | identity, merge, prelude, schema |
| gen-link | aspects | algebra, aspects, identity, prelude, schema, scope, view |
| gen-class | aspects | prelude |
| gen-assemble | framework | prelude, scope, algebra (injected) |
| gen-program | framework | prelude, scope (injected) |
| gen-delivery | framework | algebra, aspects (injected) |
| gen-settings | framework | algebra, bind, graph, identity, prelude, schema, types |
| gen-inspect | framework | graph, prelude, scope, select |
The “depends on” column is direct dependencies only — gen-scope reaching gen-prelude through
gen-graph doesn’t count twice. Every one of those edges is a follows in the dependent’s own
flake.nix, checked against this hub’s own graph on every run so the two can’t drift apart
unnoticed.
Palettes adapted from Catppuccin (Macchiato) (MIT), Tokyo Night (Apache-2.0), gruvbox (MIT), Catppuccin (Latte) (MIT), Rosé Pine (Dawn) (MIT).