From d9a46cdde7f41e7554eddec386cf099d2b83cc38 Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Wed, 9 Sep 2026 22:20:12 -0700 Subject: [PATCH 1/8] refactor(lib): nest every integration under lib.caisson. with uniform names The flake-parts integration used to export its functions flat under lib.caisson (mkFlake, mkFlakeModule, types) while the other integrations each had a namespace, and every integration named its entry points differently (mkSystem, mkHomeConfiguration, mkColmenaHive, mkSystemConfig, mkTerranixConfiguration). Every integration now lives at lib.caisson. and exports the same two names: mkModule (the class-bound caisson-core.mkModule) and mkConfiguration (evaluate the target's module system with the selected class modules). Variants keep the suffix (mkConfigurationFull, mkConfigurationMinimal); target-specific helpers (the home-manager adapters, mkSourceMeta, assertSourceCoherence) keep their names. lib.caisson.mkFlake -> lib.caisson.flake-parts.mkConfiguration lib.caisson.mkFlakeModule -> lib.caisson.flake-parts.mkModule lib.caisson.types -> lib.caisson.flake-parts.types lib.caisson.nixos.mkSystem{,Full,Minimal} -> lib.caisson.nixos.mkConfiguration{,Full,Minimal} lib.caisson.nixos.mkNixosModule -> lib.caisson.nixos.mkModule lib.caisson.home-manager.mkHomeConfiguration{,Minimal} -> lib.caisson.home-manager.mkConfiguration{,Minimal} lib.caisson.home-manager.mkHomeManagerModule -> lib.caisson.home-manager.mkModule lib.caisson.colmena.mkColmenaHive -> lib.caisson.colmena.mkConfiguration lib.caisson.colmena.mkColmenaModule -> lib.caisson.colmena.mkModule lib.caisson.terranix.mkTerranixConfiguration -> lib.caisson.terranix.mkConfiguration lib.caisson.terranix.mkTerranixModule -> lib.caisson.terranix.mkModule lib.caisson.system-manager.mkSystemConfig -> lib.caisson.system-manager.mkConfiguration lib.caisson.system-manager.mkSystemManagerModule -> lib.caisson.system-manager.mkModule This is a breaking change with no compatibility layer: the old names are gone. lib.caisson.nixpkgs and the tooling (eval-weight, mkMemoizedDerivationRead) are unchanged. Co-Authored-By: Claude Fable 5.1 --- README.md | 14 +-- docs/development/testing-architecture.md | 10 +- examples/literate-flake/flake.nix | 12 +- .../modules/flake-parts/default/default.nix | 2 +- flake.nix | 10 +- lib-overlays/colmena/default.nix | 10 +- lib-overlays/flake-parts/default.nix | 20 ++-- lib-overlays/home-manager/default.nix | 22 ++-- lib-overlays/nixos/default.nix | 18 +-- lib-overlays/system-manager/default.nix | 10 +- lib-overlays/terranix/default.nix | 10 +- .../flake-parts/core/caisson/libOverlays.nix | 2 +- modules/flake-parts/core/caisson/manifest.nix | 2 +- modules/flake-parts/core/default.nix | 2 +- tests/integration/basic-composition/flake.nix | 4 +- .../final-consumer/flake.nix | 4 +- .../lib-consumer-chain/middle-flake/flake.nix | 6 +- tests/integration/minimal-consumer/flake.nix | 4 +- .../integration/module-class-export/flake.nix | 4 +- tests/integration/nixpkgs-consumer/flake.nix | 4 +- .../nixpkgs-interface-consumer/flake.nix | 4 +- .../integration/nixpkgs-no-pkg-sets/flake.nix | 4 +- .../nixpkgs-overlay-export/flake.nix | 4 +- tests/unit/flake.nix | 4 +- tests/unit/lib-overlays.nix | 104 +++++++++--------- 25 files changed, 148 insertions(+), 142 deletions(-) diff --git a/README.md b/README.md index b6fca04..89c5156 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ fit together instead of colliding. ## Quick start -Use `caisson-core.mkLib` to compose your library, then `mkFlake` to -produce the flake outputs. By convention, your primary configuration +Use `caisson-core.mkLib` to compose your library, then +`lib.caisson.flake-parts.mkConfiguration` to produce the flake outputs. By convention, your primary configuration lives in `configs/flake-parts/`. ```nix @@ -43,7 +43,7 @@ lives in `configs/flake-parts/`. # Compose a library: the machinery lands under lib.caisson-core, # and caisson's flake-parts integration overlay contributes - # lib.caisson (mkFlake and friends). + # lib.caisson (one namespace per integration target). lib = caisson.lib.caisson-core.mkLib { inherit inputs; @@ -52,8 +52,8 @@ lives in `configs/flake-parts/`. flake = { # The flake-parts modules this flake defines: closed over your # inputs, importable here, exportable to downstream consumers. - default = lib.caisson.mkFlakeModule ./modules/flake-parts/default; - # other = lib.caisson.mkFlakeModule inputs.other-flake.flakeModules.default; + default = lib.caisson.flake-parts.mkModule ./modules/flake-parts/default; + # other = lib.caisson.flake-parts.mkModule inputs.other-flake.flakeModules.default; }; }; @@ -71,10 +71,10 @@ lives in `configs/flake-parts/`. }; - in lib.caisson.mkFlake { + in lib.caisson.flake-parts.mkConfiguration { # Convention: your primary config lives in configs/flake-parts/ - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/my-flake; + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/my-flake; # Select which modules (yours or your dependencies') this flake composes. moduleImports = modules: { inherit (modules) default; }; diff --git a/docs/development/testing-architecture.md b/docs/development/testing-architecture.md index ad2272c..99a6d72 100644 --- a/docs/development/testing-architecture.md +++ b/docs/development/testing-architecture.md @@ -150,8 +150,8 @@ lib = inputs.caisson-core.lib.caisson-core.mkLib { }; }; -lib.caisson.mkFlake { - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/unit-tests; +lib.caisson.flake-parts.mkConfiguration { + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/unit-tests; }; ``` @@ -163,7 +163,7 @@ overlay files register from that path because a flake cannot reference files outside its own source tree. The tests are not only testing library functions in isolation; the composition -and `mkFlake` path is the same one a downstream consumer exercises, so they +and `lib.caisson.flake-parts.mkConfiguration` path is the same one a downstream consumer exercises, so they verify that the framework's composition machinery works end-to-end. ### nix-unit integration @@ -213,7 +213,7 @@ appear to a consumer. ### Purpose Integration test flakes verify that caisson works correctly when consumed as a -dependency: that `mkLib`, `mkFlake`, class-keyed module registration, and +dependency: that `mkLib`, `lib.caisson.flake-parts.mkConfiguration`, class-keyed module registration, and module composition behave as expected from a consumer's perspective. ### Structure @@ -222,7 +222,7 @@ Each integration test is a standalone flake under `tests/integration//` th 1. Takes `parent` (caisson's evaluated outputs, from the pool) as an input 2. Calls `parent.lib.caisson-core.mkLib { inherit inputs; ... }` to bootstrap -3. Uses `lib.caisson.mkFlake` to compose a flake +3. Uses `lib.caisson.flake-parts.mkConfiguration` to compose a flake 4. Defines a `checks..` derivation that succeeds if composition worked diff --git a/examples/literate-flake/flake.nix b/examples/literate-flake/flake.nix index ecfa2d4..3a8bb53 100644 --- a/examples/literate-flake/flake.nix +++ b/examples/literate-flake/flake.nix @@ -56,7 +56,7 @@ project's exported overlays and modules become available under `/`, and the usual selections pick from them per item. Registering caisson this way brings in its integrations - (`lib.caisson`, mkFlake included) and its exported modules. + (`lib.caisson`, the flake-parts one included) and its exported modules. - `modules` is a function from the composed `lib`, used to register this flake's own class-keyed modules. - `libOverlays` is a function from an input-closed `mkLibOverlay` @@ -73,7 +73,7 @@ modules = lib: { # Demonstrate class-keyed module registration. - # This class is not imported by mkFlake in this example. + # This class is not imported by the flake-parts mkConfiguration in this example. generic = { noop = lib.caisson-core.mkModule "generic" ({ ... }: { }); }; @@ -82,7 +82,7 @@ # takes the closure attrset ({ closure-inputs, closure-lib, # mkModule, ... }) as its first arg list; files that don't need it # take `{ ... }:`. - default = lib.caisson.mkFlakeModule ./modules/flake-parts/default; + default = lib.caisson.flake-parts.mkModule ./modules/flake-parts/default; }; }; @@ -96,7 +96,7 @@ /* Step 2: Create the flake outputs. - `mkFlake` wraps flake-parts' mkFlake, injecting the framework's core + `lib.caisson.flake-parts.mkConfiguration` wraps flake-parts' mkFlake, injecting the framework's core module and threading `lib` as a special arg so modules receive the fully composed library. @@ -108,8 +108,8 @@ ("caisson/default" is caisson's default module, providing configInfo and the export options). */ - lib.caisson.mkFlake { - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/literate-flake; + lib.caisson.flake-parts.mkConfiguration { + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/literate-flake; moduleImports = modules: [ modules."caisson/default" diff --git a/examples/literate-flake/modules/flake-parts/default/default.nix b/examples/literate-flake/modules/flake-parts/default/default.nix index 5777cdd..3f7ee1b 100644 --- a/examples/literate-flake/modules/flake-parts/default/default.nix +++ b/examples/literate-flake/modules/flake-parts/default/default.nix @@ -2,7 +2,7 @@ /* A flake module defines options, config, and per-system outputs. - Because mkFlake threads the composed `lib` as a special arg, modules + Because lib.caisson.flake-parts.mkConfiguration threads the composed `lib` as a special arg, modules receive the full library -- including overlays registered by this flake. Here we use `lib.literate-flake.greet` which was added by our library overlay. diff --git a/flake.nix b/flake.nix index bef7f73..9000d79 100644 --- a/flake.nix +++ b/flake.nix @@ -70,7 +70,7 @@ modules = composedLib: { flake = { - default = composedLib.caisson.mkFlakeModule ./modules/flake-parts/default; + default = composedLib.caisson.flake-parts.mkModule ./modules/flake-parts/default; # flake-parts' partitions module, registered so consumers # can select it from the registry instead of declaring a # flake-parts input of their own. @@ -79,8 +79,8 @@ # registry (nixpkgs-interface) and the package-set # machinery that reifies `caisson.nixpkgs.pkgSets` per # system (nixpkgs, which imports the interface). - nixpkgs = composedLib.caisson.mkFlakeModule ./modules/flake-parts/nixpkgs; - nixpkgs-interface = composedLib.caisson.mkFlakeModule ./modules/flake-parts/nixpkgs-interface; + nixpkgs = composedLib.caisson.flake-parts.mkModule ./modules/flake-parts/nixpkgs; + nixpkgs-interface = composedLib.caisson.flake-parts.mkModule ./modules/flake-parts/nixpkgs-interface; }; }; @@ -100,11 +100,11 @@ in let - flakeOutputs = lib.caisson.mkFlake { + flakeOutputs = lib.caisson.flake-parts.mkConfiguration { name = "caisson"; - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/caisson; + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/caisson; moduleImports = modules: [ modules.default diff --git a/lib-overlays/colmena/default.nix b/lib-overlays/colmena/default.nix index 2baefd2..847b925 100644 --- a/lib-overlays/colmena/default.nix +++ b/lib-overlays/colmena/default.nix @@ -7,7 +7,7 @@ overlay = final: prev: let - mkColmenaModule = final.caisson-core.mkModule "colmena"; + mkModule = final.caisson-core.mkModule "colmena"; resolveEcosystemSrc = import ../resolve-ecosystem-src.nix { name = "colmena"; @@ -20,7 +20,7 @@ if ecosystemSrc ? lib && ecosystemSrc.lib ? makeHive then ecosystemSrc else - throw "lib.caisson.colmena.mkColmenaHive requires `ecosystemSrc.lib.makeHive`."; + throw "lib.caisson.colmena.mkConfiguration requires `ecosystemSrc.lib.makeHive`."; mkCommonArgs = args@{ @@ -42,7 +42,7 @@ // specialArgs; }; - mkColmenaHive = + mkConfiguration = args@{ ecosystemSrc ? null, ... @@ -80,8 +80,8 @@ caisson = (prev.caisson or { }) // { colmena = ((prev.caisson or { }).colmena or { }) // { inherit - mkColmenaHive - mkColmenaModule + mkConfiguration + mkModule ; }; }; diff --git a/lib-overlays/flake-parts/default.nix b/lib-overlays/flake-parts/default.nix index 6111b55..f6cfc68 100644 --- a/lib-overlays/flake-parts/default.nix +++ b/lib-overlays/flake-parts/default.nix @@ -1,8 +1,8 @@ # SPDX-License-Identifier: MIT # # The flake-parts integration: projecting a composition into flake -# outputs. A peer of the other integrations, it carries mkFlake, the -# `flake` module class (mkFlakeModule), the option types (option +# outputs. A peer of the other integrations, it carries mkConfiguration, the +# `flake` module class (mkModule), the option types (option # types are this integration's medium), the export machinery (the # core flake-parts module reads the composition's manifest at # `caisson-core.manifest` and projects the `libOverlays` and @@ -30,7 +30,9 @@ && builtins.isList (v.imports or [ ]) && builtins.all isLibOverlay (v.imports or [ ]); - types = ((prev.caisson or { }).types or { }) // { + prevNs = (prev.caisson or { }).flake-parts or { }; + + types = (prevNs.types or { }) // { libOverlay = final.mkOptionType { name = "libOverlay"; @@ -60,7 +62,7 @@ }; - mkFlake = + mkConfiguration = args@{ configModule, @@ -85,13 +87,13 @@ }: ( if builtins.hasAttr "inputs" args then - builtins.abort "inputs were passed to mkFlake. This is an easy mistake to make, but they should be passed to mkLib." + builtins.abort "inputs were passed to lib.caisson.flake-parts.mkConfiguration. This is an easy mistake to make, but they should be passed to mkLib." else let manifest = final.caisson-core.manifest or (throw '' - caisson.mkFlake projects a composition's manifest into flake + caisson.flake-parts.mkConfiguration projects a composition's manifest into flake outputs, but this composed library carries no manifest at `caisson-core.manifest`. Compose the library with caisson-core.mkLib, which captures one. @@ -143,8 +145,10 @@ { caisson = (prev.caisson or { }) // { - inherit mkFlake types; - mkFlakeModule = final.caisson-core.mkModule "flake"; + flake-parts = prevNs // { + inherit mkConfiguration types; + mkModule = final.caisson-core.mkModule "flake"; + }; }; flake-parts = (prev.flake-parts or { }) // closure-inputs.flake-parts.lib; diff --git a/lib-overlays/home-manager/default.nix b/lib-overlays/home-manager/default.nix index 16035dd..e216568 100644 --- a/lib-overlays/home-manager/default.nix +++ b/lib-overlays/home-manager/default.nix @@ -7,7 +7,7 @@ overlay = final: prev: let - mkHomeManagerModule = final.caisson-core.mkModule "homeManager"; + mkModule = final.caisson-core.mkModule "homeManager"; mkNixosModule = final.caisson-core.mkModule "nixos"; resolveEcosystemSrc = import ../resolve-ecosystem-src.nix { @@ -27,7 +27,7 @@ if pkgSets ? pkgs then pkgSets else - throw "lib.caisson.home-manager.mkHomeConfiguration requires `pkgSets.pkgs` to be defined."; + throw "lib.caisson.home-manager.mkConfiguration requires `pkgSets.pkgs` to be defined."; resolveOutPath = value: @@ -80,7 +80,7 @@ mkSourceMetaModule = sourceMeta: - mkHomeManagerModule ( + mkModule ( { ... }: { config, @@ -198,7 +198,7 @@ evaluatorPath = "${hmSource}/modules"; }; - mkHomeConfiguration = + mkConfiguration = args: let common = mkCommonArgs args; @@ -214,7 +214,7 @@ ; }; - mkHomeConfigurationMinimal = args: mkHomeConfiguration (args // { minimal = true; }); + mkConfigurationMinimal = args: mkConfiguration (args // { minimal = true; }); mkStandaloneAdapter = args@{ @@ -226,7 +226,7 @@ in { homeModules = selectedModules; - buildHome = configModule: mkHomeConfiguration (args // { inherit configModule moduleImports; }); + buildHome = configModule: mkConfiguration (args // { inherit configModule moduleImports; }); }; mkNixosAdapter = @@ -317,7 +317,7 @@ # a `users.users.` entry (its injected defs dereference the # user record), and creating one would conflict with # systemd-homed's ownership of the account. Instead each user is - # evaluated with the same standalone evaluator (mkHomeConfiguration) + # evaluated with the same standalone evaluator (mkConfiguration) # that `home-manager switch` uses (the embedded generation is the # standalone one by construction), and a complete /etc user unit # runs its activation when the user's service manager starts. @@ -326,7 +326,7 @@ username = builtins.head (builtins.attrNames users); userActivations = builtins.mapAttrs ( _username: userArgs: - (mkHomeConfiguration { + (mkConfiguration { inherit ecosystemSrc extraSpecialArgs; pkgSets = checkedPkgSets; configModule = @@ -466,9 +466,9 @@ home-manager = ((prev.caisson or { }).home-manager or { }) // { inherit assertSourceCoherence - mkHomeConfiguration - mkHomeConfigurationMinimal - mkHomeManagerModule + mkConfiguration + mkConfigurationMinimal + mkModule mkNixosAdapter mkSourceMeta mkStandaloneAdapter diff --git a/lib-overlays/nixos/default.nix b/lib-overlays/nixos/default.nix index 90a759c..1e84d67 100644 --- a/lib-overlays/nixos/default.nix +++ b/lib-overlays/nixos/default.nix @@ -7,7 +7,7 @@ overlay = final: prev: let - mkNixosModule = final.caisson-core.mkModule "nixos"; + mkModule = final.caisson-core.mkModule "nixos"; resolveEcosystemSrc = import ../resolve-ecosystem-src.nix { name = "nixpkgs"; @@ -26,7 +26,7 @@ if pkgSets ? pkgs then pkgSets else - throw "lib.caisson.nixos.mkSystem requires `pkgSets.pkgs` to be defined."; + throw "lib.caisson.nixos.mkConfiguration requires `pkgSets.pkgs` to be defined."; mkFrameworkModule = pkgSets: { _file = "caisson-nixos:framework"; @@ -67,7 +67,7 @@ // specialArgs; }; - mkSystem = + mkConfiguration = args@{ ecosystemSrc ? null, ... @@ -93,7 +93,7 @@ } ); - mkSystemFull = + mkConfigurationFull = args@{ ecosystemSrc ? null, ... @@ -120,7 +120,7 @@ } ); - mkSystemMinimal = + mkConfigurationMinimal = args@{ ecosystemSrc ? null, prefix ? [ ], @@ -141,10 +141,10 @@ caisson = (prev.caisson or { }) // { nixos = ((prev.caisson or { }).nixos or { }) // { inherit - mkNixosModule - mkSystem - mkSystemMinimal - mkSystemFull + mkModule + mkConfiguration + mkConfigurationMinimal + mkConfigurationFull ; }; }; diff --git a/lib-overlays/system-manager/default.nix b/lib-overlays/system-manager/default.nix index 888c87b..8940508 100644 --- a/lib-overlays/system-manager/default.nix +++ b/lib-overlays/system-manager/default.nix @@ -7,7 +7,7 @@ overlay = final: prev: let - mkSystemManagerModule = final.caisson-core.mkModule "systemManager"; + mkModule = final.caisson-core.mkModule "systemManager"; resolveEcosystemSrc = import ../resolve-ecosystem-src.nix { name = "system-manager"; @@ -20,7 +20,7 @@ if ecosystemSrc ? lib && ecosystemSrc.lib ? makeSystemConfig then ecosystemSrc else - throw "lib.caisson.system-manager.mkSystemConfig requires `ecosystemSrc.lib.makeSystemConfig`."; + throw "lib.caisson.system-manager.mkConfiguration requires `ecosystemSrc.lib.makeSystemConfig`."; mkCommonArgs = args@{ @@ -42,7 +42,7 @@ // specialArgs; }; - mkSystemConfig = + mkConfiguration = args@{ ecosystemSrc ? null, ... @@ -147,8 +147,8 @@ caisson = (prev.caisson or { }) // { system-manager = ((prev.caisson or { }).system-manager or { }) // { inherit - mkSystemConfig - mkSystemManagerModule + mkConfiguration + mkModule ; }; }; diff --git a/lib-overlays/terranix/default.nix b/lib-overlays/terranix/default.nix index 38011cf..4881e8e 100644 --- a/lib-overlays/terranix/default.nix +++ b/lib-overlays/terranix/default.nix @@ -7,7 +7,7 @@ overlay = final: prev: let - mkTerranixModule = final.caisson-core.mkModule "terranix"; + mkModule = final.caisson-core.mkModule "terranix"; resolveEcosystemSrc = import ../resolve-ecosystem-src.nix { name = "terranix"; @@ -20,7 +20,7 @@ if ecosystemSrc ? lib && ecosystemSrc.lib ? terranixConfiguration then ecosystemSrc else - throw "lib.caisson.terranix.mkTerranixConfiguration requires `ecosystemSrc.lib.terranixConfiguration`."; + throw "lib.caisson.terranix.mkConfiguration requires `ecosystemSrc.lib.terranixConfiguration`."; mkCommonArgs = args@{ @@ -42,7 +42,7 @@ // extraArgs; }; - mkTerranixConfiguration = + mkConfiguration = args@{ ecosystemSrc ? null, ... @@ -74,8 +74,8 @@ caisson = (prev.caisson or { }) // { terranix = ((prev.caisson or { }).terranix or { }) // { inherit - mkTerranixConfiguration - mkTerranixModule + mkConfiguration + mkModule ; }; }; diff --git a/modules/flake-parts/core/caisson/libOverlays.nix b/modules/flake-parts/core/caisson/libOverlays.nix index 136df4e..4d2a295 100644 --- a/modules/flake-parts/core/caisson/libOverlays.nix +++ b/modules/flake-parts/core/caisson/libOverlays.nix @@ -5,7 +5,7 @@ export.enabled = lib.mkEnableOption "lib overlay export"; exported = lib.mkOption { - type = lib.types.functionTo (lib.types.attrsOf lib.caisson.types.libOverlay); + type = lib.types.functionTo (lib.types.attrsOf lib.caisson.flake-parts.types.libOverlay); description = '' Function that selects which registered library overlays to export as flake outputs. Receives the set of overlays registered via `mkLib` diff --git a/modules/flake-parts/core/caisson/manifest.nix b/modules/flake-parts/core/caisson/manifest.nix index fcfa1c5..9847917 100644 --- a/modules/flake-parts/core/caisson/manifest.nix +++ b/modules/flake-parts/core/caisson/manifest.nix @@ -2,7 +2,7 @@ { lib, ... }: { options.caisson.manifest = lib.mkOption { - type = lib.caisson.types.manifest; + type = lib.caisson.flake-parts.types.manifest; readOnly = true; default = lib.caisson-core.manifest; defaultText = "the composed library's caisson-core.manifest"; diff --git a/modules/flake-parts/core/default.nix b/modules/flake-parts/core/default.nix index 0b67332..5d16af3 100644 --- a/modules/flake-parts/core/default.nix +++ b/modules/flake-parts/core/default.nix @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MIT # -# The core flake-parts module, wired into every mkFlake evaluation by +# The core flake-parts module, wired into every lib.caisson.flake-parts.mkConfiguration evaluation by # the flake-parts integration. Plain modules on purpose: everything # they need comes through `lib` (the composed library in specialArgs), # so they are imported directly rather than through mkModule. diff --git a/tests/integration/basic-composition/flake.nix b/tests/integration/basic-composition/flake.nix index b379a34..a46af80 100644 --- a/tests/integration/basic-composition/flake.nix +++ b/tests/integration/basic-composition/flake.nix @@ -35,8 +35,8 @@ }; in - lib.caisson.mkFlake { + lib.caisson.flake-parts.mkConfiguration { # Convention: config lives in configs/flake-parts/ - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/basic-composition; + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/basic-composition; }; } diff --git a/tests/integration/lib-consumer-chain/final-consumer/flake.nix b/tests/integration/lib-consumer-chain/final-consumer/flake.nix index 099ce38..ebde106 100644 --- a/tests/integration/lib-consumer-chain/final-consumer/flake.nix +++ b/tests/integration/lib-consumer-chain/final-consumer/flake.nix @@ -27,7 +27,7 @@ }; }; in - lib.caisson.mkFlake { - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/final-consumer; + lib.caisson.flake-parts.mkConfiguration { + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/final-consumer; }; } diff --git a/tests/integration/lib-consumer-chain/middle-flake/flake.nix b/tests/integration/lib-consumer-chain/middle-flake/flake.nix index 5dbd24b..80788c2 100644 --- a/tests/integration/lib-consumer-chain/middle-flake/flake.nix +++ b/tests/integration/lib-consumer-chain/middle-flake/flake.nix @@ -19,7 +19,7 @@ modules = lib: { flake = { - default = lib.caisson.mkFlakeModule ./modules/flake-parts/default; + default = lib.caisson.flake-parts.mkModule ./modules/flake-parts/default; }; }; @@ -29,7 +29,7 @@ }; }; in - lib.caisson.mkFlake { - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/middle-flake; + lib.caisson.flake-parts.mkConfiguration { + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/middle-flake; }; } diff --git a/tests/integration/minimal-consumer/flake.nix b/tests/integration/minimal-consumer/flake.nix index 007a187..026385f 100644 --- a/tests/integration/minimal-consumer/flake.nix +++ b/tests/integration/minimal-consumer/flake.nix @@ -33,8 +33,8 @@ }; }; in - lib.caisson.mkFlake { - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/minimal-consumer; + lib.caisson.flake-parts.mkConfiguration { + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/minimal-consumer; moduleImports = modules: [ modules.caisson-default ]; }; diff --git a/tests/integration/module-class-export/flake.nix b/tests/integration/module-class-export/flake.nix index 5c8e033..5498c21 100644 --- a/tests/integration/module-class-export/flake.nix +++ b/tests/integration/module-class-export/flake.nix @@ -41,7 +41,7 @@ }; }; in - lib.caisson.mkFlake { - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/module-class-export; + lib.caisson.flake-parts.mkConfiguration { + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/module-class-export; }; } diff --git a/tests/integration/nixpkgs-consumer/flake.nix b/tests/integration/nixpkgs-consumer/flake.nix index 040fc70..686822a 100644 --- a/tests/integration/nixpkgs-consumer/flake.nix +++ b/tests/integration/nixpkgs-consumer/flake.nix @@ -24,9 +24,9 @@ }; }; in - lib.caisson.mkFlake { + lib.caisson.flake-parts.mkConfiguration { # The default moduleImports selects every registered flake module, # so the nixpkgs machinery arrives through the projects channel. - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/nixpkgs-consumer; + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/nixpkgs-consumer; }; } diff --git a/tests/integration/nixpkgs-interface-consumer/flake.nix b/tests/integration/nixpkgs-interface-consumer/flake.nix index ceb16d6..e493ae2 100644 --- a/tests/integration/nixpkgs-interface-consumer/flake.nix +++ b/tests/integration/nixpkgs-interface-consumer/flake.nix @@ -24,8 +24,8 @@ }; }; in - lib.caisson.mkFlake { - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/nixpkgs-interface-consumer; + lib.caisson.flake-parts.mkConfiguration { + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/nixpkgs-interface-consumer; # Interface only: the registry option without the package-set # machinery. diff --git a/tests/integration/nixpkgs-no-pkg-sets/flake.nix b/tests/integration/nixpkgs-no-pkg-sets/flake.nix index d52ebbe..37be37c 100644 --- a/tests/integration/nixpkgs-no-pkg-sets/flake.nix +++ b/tests/integration/nixpkgs-no-pkg-sets/flake.nix @@ -24,9 +24,9 @@ }; }; in - lib.caisson.mkFlake { + lib.caisson.flake-parts.mkConfiguration { # The default moduleImports applies the nixpkgs machinery through # the projects channel; this flake configures none of it. - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/nixpkgs-no-pkg-sets; + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/nixpkgs-no-pkg-sets; }; } diff --git a/tests/integration/nixpkgs-overlay-export/flake.nix b/tests/integration/nixpkgs-overlay-export/flake.nix index a05ffee..a5bcbf0 100644 --- a/tests/integration/nixpkgs-overlay-export/flake.nix +++ b/tests/integration/nixpkgs-overlay-export/flake.nix @@ -24,7 +24,7 @@ }; }; in - lib.caisson.mkFlake { - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/nixpkgs-overlay-export; + lib.caisson.flake-parts.mkConfiguration { + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/nixpkgs-overlay-export; }; } diff --git a/tests/unit/flake.nix b/tests/unit/flake.nix index 3dfe1ee..3a3ae7a 100644 --- a/tests/unit/flake.nix +++ b/tests/unit/flake.nix @@ -55,7 +55,7 @@ }; }; in - lib.caisson.mkFlake { - configModule = lib.caisson.mkFlakeModule ./configs/flake-parts/unit-tests; + lib.caisson.flake-parts.mkConfiguration { + configModule = lib.caisson.flake-parts.mkModule ./configs/flake-parts/unit-tests; }; } diff --git a/tests/unit/lib-overlays.nix b/tests/unit/lib-overlays.nix index 0c42854..fa6ca3c 100644 --- a/tests/unit/lib-overlays.nix +++ b/tests/unit/lib-overlays.nix @@ -7,7 +7,7 @@ let # The passed lib is the composed library from the unit test flake, # carrying both framework namespaces: `caisson-core` (machinery, # registry, manifest) and `caisson` (the integrations). - mkFlakeModule = lib.caisson.mkFlakeModule; + mkFlakePartsModule = lib.caisson.flake-parts.mkModule; mkLibOverlay = lib.caisson-core.mkLibOverlay; mkModule = lib.caisson-core.mkModule; @@ -18,7 +18,7 @@ let # Test-facing mkLib: registers the flake-parts integration into # every test composition (so composed test libraries carry - # caisson.mkFlake), and otherwise defers to caisson-core.mkLib. + # caisson.flake-parts.mkConfiguration), and otherwise defers to caisson-core.mkLib. # Malformed arguments pass through untouched so the machinery's own # shape errors stay observable. testMkLib = @@ -242,7 +242,7 @@ in hasSelfModules = builtins.isAttrs closure-self-modules; hasMkMod = builtins.isFunction mkModule; }; - result = mkFlakeModule module; + result = mkFlakePartsModule module; evaluated = result { config = { }; }; in evaluated.hasInputs && evaluated.hasLib && evaluated.hasSelfModules && evaluated.hasMkMod; @@ -258,14 +258,14 @@ in { ok = true; }; - evaluated = (mkFlakeModule module) { config = { }; }; + evaluated = (mkFlakePartsModule module) { config = { }; }; in evaluated.ok; expected = true; }; "test: throws on a plain attrset module" = { - expr = builtins.tryEval (mkFlakeModule { + expr = builtins.tryEval (mkFlakePartsModule { options = { }; }); expected = { @@ -280,7 +280,7 @@ in modulePath = builtins.toFile "mk-module-path-module.nix" '' { ... }: { config, ... }: { ok = true; } ''; - result = mkFlakeModule modulePath; + result = mkFlakePartsModule modulePath; in { file = builtins.toString result._file; @@ -306,8 +306,8 @@ in modulePath = builtins.toFile "mk-module-dedup-module.nix" '' { ... }: { config, ... }: { ok = true; } ''; - a = mkFlakeModule modulePath; - b = mkFlakeModule modulePath; + a = mkFlakePartsModule modulePath; + b = mkFlakePartsModule modulePath; in a.key == b.key; expected = true; @@ -326,7 +326,7 @@ in _file = "test-wrapper"; imports = [ innerModule ]; }; - result = mkFlakeModule wrapped; + result = mkFlakePartsModule wrapped; in builtins.isAttrs result && builtins.hasAttr "_file" result && builtins.hasAttr "imports" result; expected = true; @@ -414,7 +414,7 @@ in }; }; - "test: mkFlakeModule is equivalent to mkModule \"flake\"" = { + "test: flake-parts.mkModule is equivalent to mkModule \"flake\"" = { expr = let module = @@ -439,7 +439,7 @@ in { config = { }; }; }; - viaAlias = (caisson.mkFlakeModule module) { config = { }; }; + viaAlias = (caisson.flake-parts.mkModule module) { config = { }; }; viaFactory = ((caisson.mkModule "flake") module) { config = { }; }; in { @@ -918,13 +918,13 @@ in inputs = mockInputs; modules = callbackLib: { flake = { - inspect = callbackLib.caisson.mkFlakeModule ( + inspect = callbackLib.caisson.flake-parts.mkModule ( { ... }: { flake.inspect = { hasNamespace = callbackLib ? caisson; hasMkModule = builtins.isFunction callbackLib.caisson-core.mkModule; - hasMkFlakeModule = builtins.isFunction callbackLib.caisson.mkFlakeModule; + hasMkFlakeModule = builtins.isFunction callbackLib.caisson.flake-parts.mkModule; hasMkLibOverlay = builtins.isFunction callbackLib.caisson-core.mkLibOverlay; hasImportApply = builtins.isFunction callbackLib.caisson-core.importApply; }; @@ -933,8 +933,8 @@ in }; }; }; - outputs = myLib.caisson.mkFlake { - configModule = myLib.caisson.mkFlakeModule ( + outputs = myLib.caisson.flake-parts.mkConfiguration { + configModule = myLib.caisson.flake-parts.mkModule ( { ... }: { systems = [ "x86_64-linux" ]; @@ -970,7 +970,7 @@ in }; modules = callbackLib: { flake = { - inspect = callbackLib.caisson.mkFlakeModule ( + inspect = callbackLib.caisson.flake-parts.mkModule ( { ... }: { flake.callbackSawOverlay = callbackLib.fromCallbackOverlay or "missing"; @@ -979,8 +979,8 @@ in }; }; }; - outputs = myLib.caisson.mkFlake { - configModule = myLib.caisson.mkFlakeModule ( + outputs = myLib.caisson.flake-parts.mkConfiguration { + configModule = myLib.caisson.flake-parts.mkModule ( { ... }: { systems = [ "x86_64-linux" ]; @@ -1010,7 +1010,7 @@ in }; modules = callbackLib: { flake = { - inspect = callbackLib.caisson.mkFlakeModule ( + inspect = callbackLib.caisson.flake-parts.mkModule ( { ... }: { lib, ... }: { @@ -1023,8 +1023,8 @@ in }; }; }; - outputs = myLib.caisson.mkFlake { - configModule = myLib.caisson.mkFlakeModule ( + outputs = myLib.caisson.flake-parts.mkConfiguration { + configModule = myLib.caisson.flake-parts.mkModule ( { ... }: { systems = [ "x86_64-linux" ]; @@ -1040,7 +1040,7 @@ in }; }; - "test: modules.flake attrset receives working modules in mkFlake" = { + "test: modules.flake attrset receives working modules in flake-parts.mkConfiguration" = { expr = let myLib = mkTestLib { @@ -1065,14 +1065,14 @@ in expected = true; }; - "test: modules registered via lib aliases work in mkFlake" = { + "test: modules registered via lib aliases work in flake-parts.mkConfiguration" = { expr = let myLib = caisson.mkLib { inputs = mockInputs; modules = callbackLib: { flake = { - fromAlias = callbackLib.caisson.mkFlakeModule ( + fromAlias = callbackLib.caisson.flake-parts.mkModule ( { ... }: { flake.fromAlias = true; @@ -1081,8 +1081,8 @@ in }; }; }; - outputs = myLib.caisson.mkFlake { - configModule = myLib.caisson.mkFlakeModule ( + outputs = myLib.caisson.flake-parts.mkConfiguration { + configModule = myLib.caisson.flake-parts.mkModule ( { ... }: { systems = [ "x86_64-linux" ]; @@ -1154,7 +1154,7 @@ in }; modules = runtimeLib: { flake = { - inspect = runtimeLib.caisson.mkFlakeModule ( + inspect = runtimeLib.caisson.flake-parts.mkModule ( { ... }: { lib, ... }: { @@ -1167,8 +1167,8 @@ in }; }; }; - outputs = myLib.caisson.mkFlake { - configModule = myLib.caisson.mkFlakeModule ( + outputs = myLib.caisson.flake-parts.mkConfiguration { + configModule = myLib.caisson.flake-parts.mkModule ( { ... }: { systems = [ "x86_64-linux" ]; @@ -1261,11 +1261,11 @@ in }; }; - mkFlake = { + flake-parts-mkConfiguration = { "test: rejects inputs arg via hasAttr guard" = { - # mkFlake uses builtins.abort (not throw) when inputs is present, + # flake-parts.mkConfiguration uses builtins.abort (not throw) when inputs is present, # so tryEval cannot catch it. Instead we test the guard condition - # directly: mkFlake checks builtins.hasAttr "inputs" args. + # directly: flake-parts.mkConfiguration checks builtins.hasAttr "inputs" args. expr = builtins.hasAttr "inputs" { inputs = { }; configModule = { }; @@ -1279,7 +1279,7 @@ in }; "test: filteredArgs strips reserved keys" = { - # mkFlake removes configModule, modules, and moduleImports before + # flake-parts.mkConfiguration removes configModule, modules, and moduleImports before # forwarding to flake-parts. Verify the stripping logic in isolation. expr = let @@ -1313,7 +1313,7 @@ in }; "test: specialArgs merges with user-provided specialArgs" = { - # mkFlake merges { lib = final; } with any specialArgs the caller provides. + # flake-parts.mkConfiguration merges { lib = final; } with any specialArgs the caller provides. expr = let filteredArgs = { @@ -1410,8 +1410,8 @@ in }; }; - outputs = myLib.caisson.mkFlake { - configModule = myLib.caisson.mkFlakeModule ( + outputs = myLib.caisson.flake-parts.mkConfiguration { + configModule = myLib.caisson.flake-parts.mkModule ( { ... }: { systems = [ "x86_64-linux" ]; @@ -1443,8 +1443,8 @@ in }; }; - outputs = myLib.caisson.mkFlake { - configModule = myLib.caisson.mkFlakeModule ( + outputs = myLib.caisson.flake-parts.mkConfiguration { + configModule = myLib.caisson.flake-parts.mkModule ( { ... }: { systems = [ "x86_64-linux" ]; @@ -1457,7 +1457,7 @@ in expected = true; }; - "test: mkFlake works when modules.flake is absent" = { + "test: flake-parts.mkConfiguration works when modules.flake is absent" = { expr = let myLib = caisson.mkLib { @@ -1468,8 +1468,8 @@ in }; }; }; - outputs = myLib.caisson.mkFlake { - configModule = myLib.caisson.mkFlakeModule ( + outputs = myLib.caisson.flake-parts.mkConfiguration { + configModule = myLib.caisson.flake-parts.mkModule ( { ... }: { systems = [ "x86_64-linux" ]; @@ -1494,7 +1494,7 @@ in "test: mkModule with null throws" = { # null is not a function taking the closure attrset; plain values must # be imported/registered directly, so mkModule rejects them loudly. - expr = builtins.tryEval (mkFlakeModule null); + expr = builtins.tryEval (mkFlakePartsModule null); expected = { success = false; value = false; @@ -1504,12 +1504,14 @@ in types = { "test: libOverlay type accepts a built overlay" = { - expr = caisson.types.libOverlay.check (mkLibOverlay ({ ... }: { overlay = final: prev: { }; })); + expr = caisson.flake-parts.types.libOverlay.check ( + mkLibOverlay ({ ... }: { overlay = final: prev: { }; }) + ); expected = true; }; "test: libOverlay type accepts nested imports" = { - expr = caisson.types.libOverlay.check { + expr = caisson.flake-parts.types.libOverlay.check { imports = [ { imports = [ ]; @@ -1522,12 +1524,12 @@ in }; "test: libOverlay type rejects a bare overlay function" = { - expr = caisson.types.libOverlay.check (final: prev: { }); + expr = caisson.flake-parts.types.libOverlay.check (final: prev: { }); expected = false; }; "test: libOverlay type rejects a malformed import" = { - expr = caisson.types.libOverlay.check { + expr = caisson.flake-parts.types.libOverlay.check { imports = [ (final: prev: { }) ]; overlay = final: prev: { }; }; @@ -1588,7 +1590,7 @@ in let myLib = mkResolutionLib { ecosystems.colmena = colmenaStub "declared"; }; in - (myLib.caisson.colmena.mkColmenaHive { }).stubbed; + (myLib.caisson.colmena.mkConfiguration { }).stubbed; expected = "declared"; }; @@ -1597,7 +1599,7 @@ in let myLib = mkResolutionLib { ecosystems.colmena = colmenaStub "declared"; }; in - (myLib.caisson.colmena.mkColmenaHive { ecosystemSrc = colmenaStub "explicit"; }).stubbed; + (myLib.caisson.colmena.mkConfiguration { ecosystemSrc = colmenaStub "explicit"; }).stubbed; expected = "explicit"; }; @@ -1610,7 +1612,7 @@ in }; }; in - (myLib.caisson.colmena.mkColmenaHive { }).stubbed; + (myLib.caisson.colmena.mkConfiguration { }).stubbed; expected = "input"; }; @@ -1624,12 +1626,12 @@ in ecosystems.colmena = colmenaStub "declared"; }; in - (myLib.caisson.colmena.mkColmenaHive { }).stubbed; + (myLib.caisson.colmena.mkConfiguration { }).stubbed; expected = "declared"; }; "test: a full miss throws at the adapter" = { - expr = builtins.tryEval ((mkResolutionLib { }).caisson.colmena.mkColmenaHive { }).stubbed; + expr = builtins.tryEval ((mkResolutionLib { }).caisson.colmena.mkConfiguration { }).stubbed; expected = { success = false; value = false; From 1de6f2680e67cea710db527342e491d3dc2262b4 Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Wed, 9 Sep 2026 23:07:06 -0700 Subject: [PATCH 2/8] refactor(lib): give every mkConfiguration the same argument shape The entry points now share one contract: ecosystemSrc, configModule (the configuration's single top-level module, required), moduleImports, specialArgs, and pkgSets where the target consumes a package set (nixos, home-manager); anything else forwards to the evaluator as before. That removes the per-target spellings: colmena, terranix and system-manager took a `modules` list instead of configModule; terranix took `extraArgs` and home-manager took `extraSpecialArgs` (on mkConfiguration and mkNixosAdapter) where the others took specialArgs. The evaluators still receive their own names; the translation happens inside the integration. Breaking, with no compatibility layer: `modules`, `extraArgs` and `extraSpecialArgs` are gone from the caisson surface. Co-Authored-By: Claude Fable 5.1 --- lib-overlays/colmena/default.nix | 6 +++--- lib-overlays/home-manager/default.nix | 17 +++++++++-------- lib-overlays/system-manager/default.nix | 6 +++--- lib-overlays/terranix/default.nix | 17 +++++++++-------- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/lib-overlays/colmena/default.nix b/lib-overlays/colmena/default.nix index 847b925..49ad009 100644 --- a/lib-overlays/colmena/default.nix +++ b/lib-overlays/colmena/default.nix @@ -24,7 +24,7 @@ mkCommonArgs = args@{ - modules ? [ ], + configModule, moduleImports ? builtins.attrValues, specialArgs ? { }, ... @@ -33,7 +33,7 @@ selectedModules = moduleImports (final.caisson-core.modules.colmena or { }); in { - modules = selectedModules ++ modules; + modules = selectedModules ++ [ configModule ]; # Framework defaults first; caller's specialArgs wins on conflict. # This is intentional and normal in the Nix ecosystem. specialArgs = { @@ -55,7 +55,7 @@ common = mkCommonArgs args; passthroughArgs = builtins.removeAttrs args [ "ecosystemSrc" - "modules" + "configModule" "moduleImports" "specialArgs" ]; diff --git a/lib-overlays/home-manager/default.nix b/lib-overlays/home-manager/default.nix index e216568..b1423f6 100644 --- a/lib-overlays/home-manager/default.nix +++ b/lib-overlays/home-manager/default.nix @@ -152,7 +152,7 @@ pkgSets, configModule, moduleImports ? builtins.attrValues, - extraSpecialArgs ? { }, + specialArgs ? { }, osConfig ? null, check ? true, minimal ? false, @@ -187,14 +187,15 @@ ]; }; pkgs = checkedPkgSets.pkgs; - # Framework defaults first; caller's extraSpecialArgs wins on conflict. - # This is intentional and normal in the Nix ecosystem. + # Framework defaults first; caller's specialArgs wins on conflict. + # This is intentional and normal in the Nix ecosystem. home-manager + # calls these extraSpecialArgs; the caisson surface uses one name. extraSpecialArgs = { pkgSets = checkedPkgSets; inherit osConfig; sourceMeta = resolvedSourceMeta; } - // extraSpecialArgs; + // specialArgs; evaluatorPath = "${hmSource}/modules"; }; @@ -259,7 +260,7 @@ # only mounted at login anyway. Currently limited to exactly one # hosted user (one shared unit cannot carry per-user ExecStarts). activationMode ? "upstream", - extraSpecialArgs ? { }, + specialArgs ? { }, ... }: assert final.assertMsg (builtins.elem activationMode [ @@ -327,7 +328,7 @@ userActivations = builtins.mapAttrs ( _username: userArgs: (mkConfiguration { - inherit ecosystemSrc extraSpecialArgs; + inherit ecosystemSrc specialArgs; pkgSets = checkedPkgSets; configModule = if userArgs ? configModule then @@ -392,13 +393,13 @@ useGlobalPkgs useUserPackages ; - # Framework defaults first; caller's extraSpecialArgs wins on conflict. + # Framework defaults first; caller's specialArgs wins on conflict. # This is intentional and normal in the Nix ecosystem. extraSpecialArgs = { pkgSets = checkedPkgSets; sourceMeta = resolvedSourceMeta; } - // extraSpecialArgs; + // specialArgs; # The extra entries mirror mkCommonArgs/standalone defaults so a # hosted user generation evaluates to the same derivation as the # standalone profile built from the same source. diff --git a/lib-overlays/system-manager/default.nix b/lib-overlays/system-manager/default.nix index 8940508..77e60c5 100644 --- a/lib-overlays/system-manager/default.nix +++ b/lib-overlays/system-manager/default.nix @@ -24,7 +24,7 @@ mkCommonArgs = args@{ - modules ? [ ], + configModule, moduleImports ? builtins.attrValues, specialArgs ? { }, ... @@ -33,7 +33,7 @@ selectedModules = moduleImports (final.caisson-core.modules.systemManager or { }); in { - modules = selectedModules ++ modules; + modules = selectedModules ++ [ configModule ]; # Framework defaults first; caller's specialArgs wins on conflict. # This is intentional and normal in the Nix ecosystem. specialArgs = { @@ -55,7 +55,7 @@ common = mkCommonArgs args; passthroughArgs = builtins.removeAttrs args [ "ecosystemSrc" - "modules" + "configModule" "moduleImports" "specialArgs" ]; diff --git a/lib-overlays/terranix/default.nix b/lib-overlays/terranix/default.nix index 4881e8e..0d8a22f 100644 --- a/lib-overlays/terranix/default.nix +++ b/lib-overlays/terranix/default.nix @@ -24,22 +24,23 @@ mkCommonArgs = args@{ - modules ? [ ], + configModule, moduleImports ? builtins.attrValues, - extraArgs ? { }, + specialArgs ? { }, ... }: let selectedModules = moduleImports (final.caisson-core.modules.terranix or { }); in { - modules = selectedModules ++ modules; - # Framework defaults first; caller's extraArgs wins on conflict. - # This is intentional and normal in the Nix ecosystem. + modules = selectedModules ++ [ configModule ]; + # Framework defaults first; caller's specialArgs wins on conflict. + # This is intentional and normal in the Nix ecosystem. terranix + # calls these extraArgs; the caisson surface uses one name. extraArgs = { inputs = closure-inputs; } - // extraArgs; + // specialArgs; }; mkConfiguration = @@ -55,9 +56,9 @@ common = mkCommonArgs args; passthroughArgs = builtins.removeAttrs args [ "ecosystemSrc" - "extraArgs" + "configModule" "moduleImports" - "modules" + "specialArgs" ]; in checkedEcosystemSrc.lib.terranixConfiguration ( From 1d4232ee23d47da57732db001b85f203e540a8ef Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Wed, 9 Sep 2026 23:22:22 -0700 Subject: [PATCH 3/8] feat(lib): accept pkgSets on every mkConfiguration nixos and home-manager already took pkgSets because their evaluators need a package set. The other four entry points now accept it too, optionally: it is passed through as the `pkgSets` special argument everywhere, and where the evaluator has a package-set slot of its own, pkgSets.pkgs is that slot's default (terranix `pkgs`, colmena `meta.nixpkgs`, system-manager's `nixpkgs.hostPlatform`); an explicit value wins. flake-parts only forwards it. Co-Authored-By: Claude Fable 5.1 --- lib-overlays/colmena/default.nix | 12 +++++++++++- lib-overlays/flake-parts/default.nix | 9 +++++++++ lib-overlays/system-manager/default.nix | 19 ++++++++++++++++++- lib-overlays/terranix/default.nix | 14 +++++++++++++- 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lib-overlays/colmena/default.nix b/lib-overlays/colmena/default.nix index 49ad009..6f6a437 100644 --- a/lib-overlays/colmena/default.nix +++ b/lib-overlays/colmena/default.nix @@ -27,6 +27,7 @@ configModule, moduleImports ? builtins.attrValues, specialArgs ? { }, + pkgSets ? null, ... }: let @@ -39,7 +40,9 @@ specialArgs = { inputs = closure-inputs; } + // (if pkgSets != null then { inherit pkgSets; } else { }) // specialArgs; + inherit pkgSets; }; mkConfiguration = @@ -58,8 +61,15 @@ "configModule" "moduleImports" "specialArgs" + "pkgSets" ]; - baseMeta = passthroughArgs.meta or { }; + # The hive's package set is meta.nixpkgs; pkgSets.pkgs is its + # default, an explicit meta.nixpkgs wins. + baseMeta = + ( + if common.pkgSets != null && common.pkgSets ? pkgs then { nixpkgs = common.pkgSets.pkgs; } else { } + ) + // (passthroughArgs.meta or { }); baseDefaults = passthroughArgs.defaults or { }; in checkedEcosystemSrc.lib.makeHive ( diff --git a/lib-overlays/flake-parts/default.nix b/lib-overlays/flake-parts/default.nix index f6cfc68..a63dd9a 100644 --- a/lib-overlays/flake-parts/default.nix +++ b/lib-overlays/flake-parts/default.nix @@ -83,6 +83,13 @@ # exists. name ? null, + # Package sets for the flake evaluation itself, handed to the + # flake-class modules as the `pkgSets` special argument. Per + # system package sets are the nixpkgs integration's business + # (caisson.nixpkgs.pkgSets); this is the flake-level slot the + # other integrations also carry. + pkgSets ? null, + ... }: ( @@ -104,6 +111,7 @@ "modules" "moduleImports" "name" + "pkgSets" ]; finalArgs = @@ -114,6 +122,7 @@ specialArgs = { lib = final; } + // (if pkgSets != null then { inherit pkgSets; } else { }) // filteredArgs.specialArgs or { }; }; diff --git a/lib-overlays/system-manager/default.nix b/lib-overlays/system-manager/default.nix index 77e60c5..c82e92f 100644 --- a/lib-overlays/system-manager/default.nix +++ b/lib-overlays/system-manager/default.nix @@ -27,18 +27,34 @@ configModule, moduleImports ? builtins.attrValues, specialArgs ? { }, + pkgSets ? null, ... }: let selectedModules = moduleImports (final.caisson-core.modules.systemManager or { }); + # system-manager instantiates its own nixpkgs from + # `nixpkgs.hostPlatform`; a supplied package set seeds that + # platform (an explicit hostPlatform wins) and rides along as + # the `pkgSets` module argument. + pkgSetsModule = + if pkgSets != null && pkgSets ? pkgs then + [ + { + _file = "caisson-system-manager:pkgSets"; + nixpkgs.hostPlatform = final.mkDefault pkgSets.pkgs.stdenv.hostPlatform.system; + } + ] + else + [ ]; in { - modules = selectedModules ++ [ configModule ]; + modules = selectedModules ++ pkgSetsModule ++ [ configModule ]; # Framework defaults first; caller's specialArgs wins on conflict. # This is intentional and normal in the Nix ecosystem. specialArgs = { inputs = closure-inputs; } + // (if pkgSets != null then { inherit pkgSets; } else { }) // specialArgs; }; @@ -58,6 +74,7 @@ "configModule" "moduleImports" "specialArgs" + "pkgSets" ]; # system-manager imports selected NixOS modules from its own diff --git a/lib-overlays/terranix/default.nix b/lib-overlays/terranix/default.nix index 0d8a22f..cd54c95 100644 --- a/lib-overlays/terranix/default.nix +++ b/lib-overlays/terranix/default.nix @@ -27,6 +27,7 @@ configModule, moduleImports ? builtins.attrValues, specialArgs ? { }, + pkgSets ? null, ... }: let @@ -34,12 +35,14 @@ in { modules = selectedModules ++ [ configModule ]; + inherit pkgSets; # Framework defaults first; caller's specialArgs wins on conflict. # This is intentional and normal in the Nix ecosystem. terranix # calls these extraArgs; the caisson surface uses one name. extraArgs = { inputs = closure-inputs; } + // (if pkgSets != null then { inherit pkgSets; } else { }) // specialArgs; }; @@ -59,10 +62,19 @@ "configModule" "moduleImports" "specialArgs" + "pkgSets" ]; + # terranix evaluates against `pkgs`; pkgSets.pkgs is its default, + # an explicit pkgs (or system) wins. + pkgsDefault = + if common.pkgSets != null && common.pkgSets ? pkgs && !(passthroughArgs ? pkgs) then + { pkgs = common.pkgSets.pkgs; } + else + { }; in checkedEcosystemSrc.lib.terranixConfiguration ( - passthroughArgs + pkgsDefault + // passthroughArgs // { inherit (common) extraArgs From 86cb77d7339d21fcbfd8aeb1f5c615e4d3299b0e Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Wed, 9 Sep 2026 23:41:05 -0700 Subject: [PATCH 4/8] refactor(lib): close every mkConfiguration signature; add the unsupervised twins Each entry point now takes exactly the caisson arguments (ecosystemSrc, configModule, moduleImports, specialArgs, pkgSets, and a target's own few: nixos `system` or `prefix`, colmena `meta` and `nodes`) and composes the evaluator's call from them. Nothing else forwards: an unknown argument is an error naming the caisson argument to use where one exists (`modules`, `pkgs`, `extraArgs`, `extraSpecialArgs`, `inputs`, ...) and pointing at the twin otherwise. That removes the ways an evaluator argument used to misbehave when handed in directly: `modules` silently overwritten, anything the minimal evaluator does not take silently dropped, `pkgs` conflicting with the framework's `nixpkgs.pkgs`, `baseModules` overwritten by mkConfigurationFull. The evaluator's full surface stays reachable on purpose through mkConfigurationUnsupervised (and mkConfigurationMinimalUnsupervised for nixos): the same arguments plus `evaluatorArgs`, merged over the composed evaluator call verbatim, last, so anything the evaluator accepts can be set or replaced there. Also: the minimal NixOS evaluator hands pkgSets.pkgs over as the `pkgs` module argument instead of defining `nixpkgs.pkgs`, which has no declaring module there; colmena takes its nodes as `nodes` and the config module becomes the hive's `defaults`; terranix requires pkgSets in the closed form (it evaluates against a package set). Co-Authored-By: Claude Fable 5.1 --- lib-overlays/check-args.nix | 36 +++++ lib-overlays/colmena/default.nix | 120 +++++++++------- lib-overlays/flake-parts/default.nix | 135 ++++++++++-------- lib-overlays/home-manager/default.nix | 77 ++++++++-- lib-overlays/nixos/default.nix | 179 +++++++++++++++--------- lib-overlays/system-manager/default.nix | 69 ++++++--- lib-overlays/terranix/default.nix | 113 +++++++++------ tests/unit/lib-overlays.nix | 58 +++++--- 8 files changed, 527 insertions(+), 260 deletions(-) create mode 100644 lib-overlays/check-args.nix diff --git a/lib-overlays/check-args.nix b/lib-overlays/check-args.nix new file mode 100644 index 0000000..04263a0 --- /dev/null +++ b/lib-overlays/check-args.nix @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: MIT +# +# Every integration's entry point takes exactly the caisson-shaped +# arguments (configModule, moduleImports, specialArgs, pkgSets, +# ecosystemSrc, and a target's own few) and composes the evaluator's +# call from them. Nothing else is forwarded: an evaluator argument +# handed in directly would be silently overwritten, silently dropped, +# or surface as a conflict deep inside the evaluator. The +# `...Unsupervised` twin of each entry point is the way to the +# evaluator's full surface: it takes the same arguments plus +# `evaluatorArgs`, merged over the composed call verbatim, last. +# +# context: the entry point, for the message. +# accepted: the argument names it takes. +# hints: per-name pointers for the common mistakes (an evaluator +# name where a caisson name exists). +# open: the twin's name, or null when checking the twin itself. +{ + context, + accepted, + hints ? { }, + open ? null, +}: +args: +let + unknown = builtins.filter (name: !(builtins.elem name accepted)) (builtins.attrNames args); + name = builtins.head unknown; + message = + if hints ? ${name} then + "${context} does not accept `${name}`: ${hints.${name}}" + else if open != null then + "${context} does not accept `${name}`; it takes ${builtins.concatStringsSep ", " accepted}. The evaluator's own arguments are available through ${open}, in `evaluatorArgs`." + else + "${context} does not accept `${name}`; it takes ${builtins.concatStringsSep ", " accepted}. Evaluator arguments go in `evaluatorArgs`."; +in +if unknown == [ ] then args else throw message diff --git a/lib-overlays/colmena/default.nix b/lib-overlays/colmena/default.nix index 6f6a437..e791b84 100644 --- a/lib-overlays/colmena/default.nix +++ b/lib-overlays/colmena/default.nix @@ -22,32 +22,46 @@ else throw "lib.caisson.colmena.mkConfiguration requires `ecosystemSrc.lib.makeHive`."; - mkCommonArgs = - args@{ + accepted = [ + "ecosystemSrc" + "configModule" + "moduleImports" + "specialArgs" + "pkgSets" + "meta" + "nodes" + ]; + hints = { + modules = "pass the hive-wide module as `configModule`; registered class modules are selected with `moduleImports`."; + defaults = "pass the hive-wide module as `configModule`."; + network = "pass hive metadata as `meta`."; + }; + checkArgs = import ../check-args.nix { + context = "lib.caisson.colmena.mkConfiguration"; + inherit accepted hints; + open = "lib.caisson.colmena.mkConfigurationUnsupervised"; + }; + checkOpenArgs = import ../check-args.nix { + context = "lib.caisson.colmena.mkConfigurationUnsupervised"; + accepted = accepted ++ [ "evaluatorArgs" ]; + inherit hints; + }; + + # The hive makeHive receives, composed from the caisson arguments: + # the selected class modules and the config module become + # `defaults`, framework special arguments merge into + # `meta.specialArgs` (the caller's win on conflict, as is normal + # in the Nix ecosystem), and pkgSets.pkgs is the default + # `meta.nixpkgs`. + compose = + { + ecosystemSrc ? null, configModule, moduleImports ? builtins.attrValues, specialArgs ? { }, pkgSets ? null, - ... - }: - let - selectedModules = moduleImports (final.caisson-core.modules.colmena or { }); - in - { - modules = selectedModules ++ [ configModule ]; - # Framework defaults first; caller's specialArgs wins on conflict. - # This is intentional and normal in the Nix ecosystem. - specialArgs = { - inputs = closure-inputs; - } - // (if pkgSets != null then { inherit pkgSets; } else { }) - // specialArgs; - inherit pkgSets; - }; - - mkConfiguration = - args@{ - ecosystemSrc ? null, + meta ? { }, + nodes ? { }, ... }: let @@ -55,42 +69,54 @@ explicit = ecosystemSrc; manifest = final.caisson-core.manifest or { }; }); - common = mkCommonArgs args; - passthroughArgs = builtins.removeAttrs args [ - "ecosystemSrc" - "configModule" - "moduleImports" - "specialArgs" - "pkgSets" - ]; - # The hive's package set is meta.nixpkgs; pkgSets.pkgs is its - # default, an explicit meta.nixpkgs wins. - baseMeta = - ( - if common.pkgSets != null && common.pkgSets ? pkgs then { nixpkgs = common.pkgSets.pkgs; } else { } - ) - // (passthroughArgs.meta or { }); - baseDefaults = passthroughArgs.defaults or { }; + selectedModules = moduleImports (final.caisson-core.modules.colmena or { }); in - checkedEcosystemSrc.lib.makeHive ( - passthroughArgs - // { - meta = baseMeta // { - specialArgs = (baseMeta.specialArgs or { }) // common.specialArgs; - }; + { + inherit checkedEcosystemSrc; + hive = nodes // { + meta = + (if pkgSets != null && pkgSets ? pkgs then { nixpkgs = pkgSets.pkgs; } else { }) + // meta + // { + specialArgs = { + inputs = closure-inputs; + } + // (if pkgSets != null then { inherit pkgSets; } else { }) + // (meta.specialArgs or { }) + // specialArgs; + }; defaults = { ... }: { - imports = common.modules ++ [ baseDefaults ]; + imports = selectedModules ++ [ configModule ]; }; - } - ); + }; + }; + + mkConfiguration = + rawArgs: + let + composed = compose (checkArgs rawArgs); + in + composed.checkedEcosystemSrc.lib.makeHive composed.hive; + + # The same composition, then `evaluatorArgs` merged over the hive + # verbatim: every attribute makeHive reads (meta, defaults, the + # nodes) can be set or replaced there. + mkConfigurationUnsupervised = + rawArgs: + let + args = checkOpenArgs rawArgs; + composed = compose args; + in + composed.checkedEcosystemSrc.lib.makeHive (composed.hive // (args.evaluatorArgs or { })); in { caisson = (prev.caisson or { }) // { colmena = ((prev.caisson or { }).colmena or { }) // { inherit mkConfiguration + mkConfigurationUnsupervised mkModule ; }; diff --git a/lib-overlays/flake-parts/default.nix b/lib-overlays/flake-parts/default.nix index a63dd9a..9274575 100644 --- a/lib-overlays/flake-parts/default.nix +++ b/lib-overlays/flake-parts/default.nix @@ -62,7 +62,37 @@ }; - mkConfiguration = + # flake-parts' own mkFlake arguments this entry point composes are + # refused with a pointer to the caisson argument; the rest forward. + accepted = [ + "configModule" + "moduleImports" + "name" + "specialArgs" + "pkgSets" + ]; + hints = { + inputs = "the flake's inputs come from the composition's manifest; pass them to caisson-core.mkLib."; + self = "the flake's own outputs come from the composition's manifest; pass inputs (self included) to caisson-core.mkLib."; + modules = "pass the configuration's module as `configModule`; registered flake-class modules are selected with `moduleImports`."; + moduleLocation = "pass the flake's canonical name as `name`."; + }; + checkArgs = import ../check-args.nix { + context = "lib.caisson.flake-parts.mkConfiguration"; + inherit accepted hints; + open = "lib.caisson.flake-parts.mkConfigurationUnsupervised"; + }; + checkOpenArgs = import ../check-args.nix { + context = "lib.caisson.flake-parts.mkConfigurationUnsupervised"; + accepted = accepted ++ [ "evaluatorArgs" ]; + inherit hints; + }; + mkConfiguration = rawArgs: mkConfigurationChecked (checkArgs rawArgs); + # The same composition, then `evaluatorArgs` merged over the + # flake-parts mkFlake call verbatim: everything it takes (inputs, + # specialArgs, self, moduleLocation) can be set or replaced there. + mkConfigurationUnsupervised = rawArgs: mkConfigurationChecked (checkOpenArgs rawArgs); + mkConfigurationChecked = args@{ configModule, @@ -90,64 +120,55 @@ # other integrations also carry. pkgSets ? null, - ... + specialArgs ? { }, + + evaluatorArgs ? { }, }: ( - if builtins.hasAttr "inputs" args then - builtins.abort "inputs were passed to lib.caisson.flake-parts.mkConfiguration. This is an easy mistake to make, but they should be passed to mkLib." - else - let - - manifest = - final.caisson-core.manifest or (throw '' - caisson.flake-parts.mkConfiguration projects a composition's manifest into flake - outputs, but this composed library carries no manifest at - `caisson-core.manifest`. Compose the library with - caisson-core.mkLib, which captures one. - ''); - - filteredArgs = builtins.removeAttrs args [ - "configModule" - "modules" - "moduleImports" - "name" - "pkgSets" - ]; - - finalArgs = - (if name != null then { moduleLocation = name; } else { }) - // filteredArgs - // { - inputs = manifest.inputs; - specialArgs = { - lib = final; - } - // (if pkgSets != null then { inherit pkgSets; } else { }) - // filteredArgs.specialArgs or { }; - }; - - # Selection over the flake class of the registry, the - # same source every adapter selects from, so modules - # arriving by any channel (local registration, overlay - # contribution, consumed project) are selectable here. - importedModules = moduleImports (final.caisson-core.modules.flake or { }); - - finalModule = ( - { lib, ... }: - { - imports = [ - closure-inputs.flake-parts.flakeModules.flakeModules - closure-inputs.flake-parts.flakeModules.modules - ../../modules/flake-parts/core - ] - ++ importedModules - ++ [ configModule ] - ++ (if name != null then [ { caisson.configInfo.configName = lib.mkDefault name; } ] else [ ]); + let + + manifest = + final.caisson-core.manifest or (throw '' + caisson.flake-parts.mkConfiguration projects a composition's manifest into flake + outputs, but this composed library carries no manifest at + `caisson-core.manifest`. Compose the library with + caisson-core.mkLib, which captures one. + ''); + + finalArgs = + (if name != null then { moduleLocation = name; } else { }) + // { + inputs = manifest.inputs; + specialArgs = { + lib = final; } - ); - - in - closure-inputs.flake-parts.lib.mkFlake finalArgs finalModule + // (if pkgSets != null then { inherit pkgSets; } else { }) + // specialArgs; + } + // evaluatorArgs; + + # Selection over the flake class of the registry, the + # same source every adapter selects from, so modules + # arriving by any channel (local registration, overlay + # contribution, consumed project) are selectable here. + importedModules = moduleImports (final.caisson-core.modules.flake or { }); + + finalModule = ( + { lib, ... }: + { + imports = [ + closure-inputs.flake-parts.flakeModules.flakeModules + closure-inputs.flake-parts.flakeModules.modules + ../../modules/flake-parts/core + ] + ++ importedModules + ++ [ configModule ] + ++ (if name != null then [ { caisson.configInfo.configName = lib.mkDefault name; } ] else [ ]); + } + ); + + in + closure-inputs.flake-parts.lib.mkFlake finalArgs finalModule ); in @@ -155,7 +176,7 @@ caisson = (prev.caisson or { }) // { flake-parts = prevNs // { - inherit mkConfiguration types; + inherit mkConfiguration mkConfigurationUnsupervised types; mkModule = final.caisson-core.mkModule "flake"; }; }; diff --git a/lib-overlays/home-manager/default.nix b/lib-overlays/home-manager/default.nix index b1423f6..eeb06a5 100644 --- a/lib-overlays/home-manager/default.nix +++ b/lib-overlays/home-manager/default.nix @@ -199,21 +199,68 @@ evaluatorPath = "${hmSource}/modules"; }; + # home-manager's evaluator takes exactly the arguments mkCommonArgs + # composes, so there is nothing to forward: the evaluator's own + # names are refused with a pointer to the caisson argument, and any + # other name is refused as unknown. + hints = { + configuration = "pass the configuration's module as `configModule`; registered class modules are selected with `moduleImports`."; + modules = "pass the configuration's module as `configModule`; registered class modules are selected with `moduleImports`."; + pkgs = "pass the package set as `pkgSets.pkgs`."; + extraSpecialArgs = "pass extra module arguments as `specialArgs`."; + }; + configurationArgs = [ + "ecosystemSrc" + "pkgSets" + "configModule" + "moduleImports" + "specialArgs" + "osConfig" + "check" + "minimal" + "sourceMeta" + ]; + checkArgs = import ../check-args.nix { + context = "lib.caisson.home-manager.mkConfiguration"; + accepted = configurationArgs; + inherit hints; + open = "lib.caisson.home-manager.mkConfigurationUnsupervised"; + }; + checkOpenArgs = import ../check-args.nix { + context = "lib.caisson.home-manager.mkConfigurationUnsupervised"; + accepted = configurationArgs ++ [ "evaluatorArgs" ]; + inherit hints; + }; + evaluatorArgsOf = common: { + inherit (common) + check + configuration + extraSpecialArgs + minimal + pkgs + ; + }; + mkConfiguration = - args: + rawArgs: + let + common = mkCommonArgs (checkArgs rawArgs); + evaluator = import common.evaluatorPath; + in + evaluator (evaluatorArgsOf common); + + # The same composition, then `evaluatorArgs` merged over the + # evaluator call verbatim: everything home-manager's evaluator + # takes (configuration, pkgs, lib, minimal, check, + # extraSpecialArgs) can be set or replaced there. + mkConfigurationUnsupervised = + rawArgs: let + args = checkOpenArgs rawArgs; common = mkCommonArgs args; evaluator = import common.evaluatorPath; in - evaluator { - inherit (common) - check - configuration - extraSpecialArgs - minimal - pkgs - ; - }; + evaluator (evaluatorArgsOf common // (args.evaluatorArgs or { })); mkConfigurationMinimal = args: mkConfiguration (args // { minimal = true; }); @@ -230,7 +277,16 @@ buildHome = configModule: mkConfiguration (args // { inherit configModule moduleImports; }); }; + # The adapter keeps `...` (its extras are NixOS-module options, + # not evaluator arguments); only the old special-arguments name is + # refused. mkNixosAdapter = + rawArgs: + if rawArgs ? extraSpecialArgs then + throw "lib.caisson.home-manager.mkNixosAdapter does not accept `extraSpecialArgs`: pass extra module arguments as `specialArgs`." + else + mkNixosAdapterChecked rawArgs; + mkNixosAdapterChecked = args@{ users, ecosystemSrc ? null, @@ -469,6 +525,7 @@ assertSourceCoherence mkConfiguration mkConfigurationMinimal + mkConfigurationUnsupervised mkModule mkNixosAdapter mkSourceMeta diff --git a/lib-overlays/nixos/default.nix b/lib-overlays/nixos/default.nix index 1e84d67..89b24b3 100644 --- a/lib-overlays/nixos/default.nix +++ b/lib-overlays/nixos/default.nix @@ -28,15 +28,53 @@ else throw "lib.caisson.nixos.mkConfiguration requires `pkgSets.pkgs` to be defined."; + # eval-config evaluations carry the nixpkgs module, so the package + # set lands on `nixpkgs.pkgs`; the minimal evaluator has no such + # module, so there it is the `pkgs` module argument instead. mkFrameworkModule = pkgSets: { _file = "caisson-nixos:framework"; config = { nixpkgs.pkgs = pkgSets.pkgs; }; }; + mkMinimalFrameworkModule = pkgSets: { + _file = "caisson-nixos:framework-minimal"; + config = { + _module.args.pkgs = final.mkDefault pkgSets.pkgs; + }; + }; + + commonAccepted = [ + "ecosystemSrc" + "pkgSets" + "configModule" + "moduleImports" + "specialArgs" + ]; + hints = { + modules = "pass the configuration's module as `configModule`; registered class modules are selected with `moduleImports`."; + pkgs = "pass the package set as `pkgSets.pkgs`."; + baseModules = "the base module list belongs to the variant: mkConfiguration and mkConfigurationFull evaluate with NixOS' module list, mkConfigurationMinimal without it."; + }; + mkCheck = + name: extra: open: + import ../check-args.nix { + context = "lib.caisson.nixos.${name}"; + accepted = commonAccepted ++ extra; + inherit hints open; + }; - mkCommonArgs = - args@{ + # What every variant composes from the caisson arguments: the + # selected class modules, the config module and the framework + # module as `modules`, and pkgSets threaded through `specialArgs` + # (framework defaults first; the caller's win on conflict, as is + # normal in the Nix ecosystem). + compose = + { + minimal ? false, + }: + { + ecosystemSrc ? null, pkgSets, configModule, moduleImports ? builtins.attrValues, @@ -45,97 +83,102 @@ }: let checkedPkgSets = assertPkgSets pkgSets; - resolvedSystem = - args.system - or (checkedPkgSets.pkgs.stdenv.hostPlatform.system or (checkedPkgSets.pkgs.system or null)); selectedModules = moduleImports (final.caisson-core.modules.nixos or { }); - extraModules = selectedModules; - frameworkModule = mkFrameworkModule checkedPkgSets; + frameworkModule = + if minimal then mkMinimalFrameworkModule checkedPkgSets else mkFrameworkModule checkedPkgSets; in { + src = resolveSrc ecosystemSrc; inherit checkedPkgSets; - system = resolvedSystem; - modules = extraModules ++ [ + modules = selectedModules ++ [ configModule frameworkModule ]; - # Framework defaults first; caller's specialArgs wins on conflict. - # This is intentional and normal in the Nix ecosystem. specialArgs = { pkgSets = checkedPkgSets; } // specialArgs; }; + # eval-config evaluations. `system` defaults to the package set's + # host platform. + evalConfigArgs = args: common: { + modules = common.modules; + specialArgs = common.specialArgs; + system = + args.system or (common.checkedPkgSets.pkgs.stdenv.hostPlatform.system + or (common.checkedPkgSets.pkgs.system or null) + ); + }; + evalConfig = common: import "${common.src}/nixos/lib/eval-config.nix"; + mkConfiguration = - args@{ - ecosystemSrc ? null, - ... - }: + rawArgs: let - src = resolveSrc ecosystemSrc; - common = mkCommonArgs args; - passthroughArgs = builtins.removeAttrs args [ - "ecosystemSrc" - "pkgSets" - "configModule" - "moduleImports" - "specialArgs" - ]; - evalConfig = import "${src}/nixos/lib/eval-config.nix"; + args = mkCheck "mkConfiguration" [ + "system" + ] "lib.caisson.nixos.mkConfigurationUnsupervised" rawArgs; + common = compose { } args; in - evalConfig ( - passthroughArgs - // { - modules = common.modules; - specialArgs = common.specialArgs; - system = common.system; - } - ); + evalConfig common (evalConfigArgs args common); + # eval-config with nixpkgs' module list passed explicitly as + # `baseModules`. mkConfigurationFull = - args@{ - ecosystemSrc ? null, - ... - }: + rawArgs: let - src = resolveSrc ecosystemSrc; - common = mkCommonArgs args; - passthroughArgs = builtins.removeAttrs args [ - "ecosystemSrc" - "pkgSets" - "configModule" - "moduleImports" - "specialArgs" - ]; - evalConfig = import "${src}/nixos/lib/eval-config.nix"; + args = mkCheck "mkConfigurationFull" [ + "system" + ] "lib.caisson.nixos.mkConfigurationUnsupervised" rawArgs; + common = compose { } args; in - evalConfig ( - passthroughArgs + evalConfig common ( + evalConfigArgs args common // { - baseModules = import "${src}/nixos/modules/module-list.nix"; - modules = common.modules; - specialArgs = common.specialArgs; - system = common.system; + baseModules = import "${common.src}/nixos/modules/module-list.nix"; } ); + # The same composition as mkConfiguration, then `evaluatorArgs` + # merged over the eval-config call verbatim: everything + # eval-config takes (system, pkgs, baseModules, specialArgs, + # modules, modulesLocation, prefix, lib, extraModules) can be set + # or replaced there. + mkConfigurationUnsupervised = + rawArgs: + let + args = mkCheck "mkConfigurationUnsupervised" [ "system" "evaluatorArgs" ] null rawArgs; + common = compose { } args; + in + evalConfig common (evalConfigArgs args common // (args.evaluatorArgs or { })); + + # nixos/lib's evalModules: no NixOS base modules, so the config + # module declares any options it uses; the package set arrives as + # the `pkgs` module argument. + evalMinimalArgs = args: common: { + prefix = args.prefix or [ ]; + modules = common.modules; + specialArgs = common.specialArgs; + }; + evalMinimal = common: (import "${common.src}/nixos/lib" { }).evalModules; + mkConfigurationMinimal = - args@{ - ecosystemSrc ? null, - prefix ? [ ], - ... - }: + rawArgs: let - src = resolveSrc ecosystemSrc; - common = mkCommonArgs args; - nixosLib = import "${src}/nixos/lib" { }; + args = mkCheck "mkConfigurationMinimal" [ + "prefix" + ] "lib.caisson.nixos.mkConfigurationMinimalUnsupervised" rawArgs; + common = compose { minimal = true; } args; in - nixosLib.evalModules { - inherit prefix; - modules = common.modules; - specialArgs = common.specialArgs; - }; + evalMinimal common (evalMinimalArgs args common); + + mkConfigurationMinimalUnsupervised = + rawArgs: + let + args = mkCheck "mkConfigurationMinimalUnsupervised" [ "prefix" "evaluatorArgs" ] null rawArgs; + common = compose { minimal = true; } args; + in + evalMinimal common (evalMinimalArgs args common // (args.evaluatorArgs or { })); in { caisson = (prev.caisson or { }) // { @@ -143,8 +186,10 @@ inherit mkModule mkConfiguration - mkConfigurationMinimal mkConfigurationFull + mkConfigurationMinimal + mkConfigurationUnsupervised + mkConfigurationMinimalUnsupervised ; }; }; diff --git a/lib-overlays/system-manager/default.nix b/lib-overlays/system-manager/default.nix index c82e92f..4daeadd 100644 --- a/lib-overlays/system-manager/default.nix +++ b/lib-overlays/system-manager/default.nix @@ -58,24 +58,37 @@ // specialArgs; }; - mkConfiguration = - args@{ - ecosystemSrc ? null, - ... - }: + accepted = [ + "ecosystemSrc" + "configModule" + "moduleImports" + "specialArgs" + "pkgSets" + ]; + hints = { + modules = "pass the configuration's module as `configModule`; registered class modules are selected with `moduleImports`."; + extraSpecialArgs = "pass extra module arguments as `specialArgs`."; + }; + checkArgs = import ../check-args.nix { + context = "lib.caisson.system-manager.mkConfiguration"; + inherit accepted hints; + open = "lib.caisson.system-manager.mkConfigurationUnsupervised"; + }; + checkOpenArgs = import ../check-args.nix { + context = "lib.caisson.system-manager.mkConfigurationUnsupervised"; + accepted = accepted ++ [ "evaluatorArgs" ]; + inherit hints; + }; + # makeSystemConfig's arguments, composed from the caisson arguments, + # plus the compatibility bridge below. + compose = + args: let checkedEcosystemSrc = assertSystemManagerEcosystemSrc (resolveEcosystemSrc { - explicit = ecosystemSrc; + explicit = args.ecosystemSrc or null; manifest = final.caisson-core.manifest or { }; }); common = mkCommonArgs args; - passthroughArgs = builtins.removeAttrs args [ - "ecosystemSrc" - "configModule" - "moduleImports" - "specialArgs" - "pkgSets" - ]; # system-manager imports selected NixOS modules from its own # nixpkgs input; current nixos-unstable restructured @@ -152,12 +165,33 @@ final.optional (!smDeclaresDisplayManager) displayManagerSinkModule ++ final.optional nixosNixOwnsDaemonOptions nixStubReplacementModule; in - checkedEcosystemSrc.lib.makeSystemConfig ( - passthroughArgs - // { + { + inherit checkedEcosystemSrc; + evaluatorArgs = { inherit (common) specialArgs; modules = common.modules ++ compatModules; - } + }; + }; + + mkConfiguration = + rawArgs: + let + composed = compose (checkArgs rawArgs); + in + composed.checkedEcosystemSrc.lib.makeSystemConfig composed.evaluatorArgs; + + # The same composition, then `evaluatorArgs` merged over the + # evaluator call verbatim: everything makeSystemConfig takes + # (modules, overlays, specialArgs, allowUnsupportedNixpkgs) can be + # set or replaced there. + mkConfigurationUnsupervised = + rawArgs: + let + args = checkOpenArgs rawArgs; + composed = compose args; + in + composed.checkedEcosystemSrc.lib.makeSystemConfig ( + composed.evaluatorArgs // (args.evaluatorArgs or { }) ); in { @@ -165,6 +199,7 @@ system-manager = ((prev.caisson or { }).system-manager or { }) // { inherit mkConfiguration + mkConfigurationUnsupervised mkModule ; }; diff --git a/lib-overlays/terranix/default.nix b/lib-overlays/terranix/default.nix index cd54c95..0f30cef 100644 --- a/lib-overlays/terranix/default.nix +++ b/lib-overlays/terranix/default.nix @@ -22,8 +22,40 @@ else throw "lib.caisson.terranix.mkConfiguration requires `ecosystemSrc.lib.terranixConfiguration`."; - mkCommonArgs = - args@{ + accepted = [ + "ecosystemSrc" + "configModule" + "moduleImports" + "specialArgs" + "pkgSets" + ]; + hints = { + modules = "pass the configuration's module as `configModule`; registered class modules are selected with `moduleImports`."; + extraArgs = "pass extra module arguments as `specialArgs`."; + pkgs = "pass the package set as `pkgSets.pkgs`."; + system = "pass the package set as `pkgSets.pkgs`; terranix evaluates against it."; + }; + checkArgs = import ../check-args.nix { + context = "lib.caisson.terranix.mkConfiguration"; + inherit accepted hints; + open = "lib.caisson.terranix.mkConfigurationUnsupervised"; + }; + checkOpenArgs = import ../check-args.nix { + context = "lib.caisson.terranix.mkConfigurationUnsupervised"; + accepted = accepted ++ [ "evaluatorArgs" ]; + inherit hints; + }; + + # terranixConfiguration's arguments, composed from the caisson + # arguments: pkgSets.pkgs is `pkgs` (terranix evaluates against + # a package set, so mkConfiguration requires one), the selected + # class modules and the config module are `modules`, and + # specialArgs becomes terranix's `extraArgs` (framework defaults + # first; the caller's win on conflict, as is normal in the Nix + # ecosystem). + compose = + { + ecosystemSrc ? null, configModule, moduleImports ? builtins.attrValues, specialArgs ? { }, @@ -31,56 +63,48 @@ ... }: let + checkedEcosystemSrc = assertTerranixEcosystemSrc (resolveEcosystemSrc { + explicit = ecosystemSrc; + manifest = final.caisson-core.manifest or { }; + }); selectedModules = moduleImports (final.caisson-core.modules.terranix or { }); in { - modules = selectedModules ++ [ configModule ]; - inherit pkgSets; - # Framework defaults first; caller's specialArgs wins on conflict. - # This is intentional and normal in the Nix ecosystem. terranix - # calls these extraArgs; the caisson surface uses one name. - extraArgs = { - inputs = closure-inputs; - } - // (if pkgSets != null then { inherit pkgSets; } else { }) - // specialArgs; + inherit checkedEcosystemSrc; + evaluatorArgs = (if pkgSets != null && pkgSets ? pkgs then { pkgs = pkgSets.pkgs; } else { }) // { + modules = selectedModules ++ [ configModule ]; + extraArgs = { + inputs = closure-inputs; + } + // (if pkgSets != null then { inherit pkgSets; } else { }) + // specialArgs; + }; }; mkConfiguration = - args@{ - ecosystemSrc ? null, - ... - }: + rawArgs: let - checkedEcosystemSrc = assertTerranixEcosystemSrc (resolveEcosystemSrc { - explicit = ecosystemSrc; - manifest = final.caisson-core.manifest or { }; - }); - common = mkCommonArgs args; - passthroughArgs = builtins.removeAttrs args [ - "ecosystemSrc" - "configModule" - "moduleImports" - "specialArgs" - "pkgSets" - ]; - # terranix evaluates against `pkgs`; pkgSets.pkgs is its default, - # an explicit pkgs (or system) wins. - pkgsDefault = - if common.pkgSets != null && common.pkgSets ? pkgs && !(passthroughArgs ? pkgs) then - { pkgs = common.pkgSets.pkgs; } - else - { }; + args = checkArgs rawArgs; + composed = compose args; + in + if !(args ? pkgSets && args.pkgSets ? pkgs) then + throw "lib.caisson.terranix.mkConfiguration requires `pkgSets.pkgs` to be defined." + else + composed.checkedEcosystemSrc.lib.terranixConfiguration composed.evaluatorArgs; + + # The same composition, then `evaluatorArgs` merged over the + # evaluator call verbatim: everything terranixConfiguration takes + # (system, pkgs, modules, extraArgs, strip_nulls) can be set or + # replaced there; pkgSets is optional here since `system` or + # `pkgs` may come that way. + mkConfigurationUnsupervised = + rawArgs: + let + args = checkOpenArgs rawArgs; + composed = compose args; in - checkedEcosystemSrc.lib.terranixConfiguration ( - pkgsDefault - // passthroughArgs - // { - inherit (common) - extraArgs - modules - ; - } + composed.checkedEcosystemSrc.lib.terranixConfiguration ( + composed.evaluatorArgs // (args.evaluatorArgs or { }) ); in { @@ -88,6 +112,7 @@ terranix = ((prev.caisson or { }).terranix or { }) // { inherit mkConfiguration + mkConfigurationUnsupervised mkModule ; }; diff --git a/tests/unit/lib-overlays.nix b/tests/unit/lib-overlays.nix index fa6ca3c..d7d40f0 100644 --- a/tests/unit/lib-overlays.nix +++ b/tests/unit/lib-overlays.nix @@ -1262,25 +1262,42 @@ in }; flake-parts-mkConfiguration = { - "test: rejects inputs arg via hasAttr guard" = { - # flake-parts.mkConfiguration uses builtins.abort (not throw) when inputs is present, - # so tryEval cannot catch it. Instead we test the guard condition - # directly: flake-parts.mkConfiguration checks builtins.hasAttr "inputs" args. - expr = builtins.hasAttr "inputs" { - inputs = { }; - configModule = { }; - }; - expected = true; + "test: refuses inputs (they belong to mkLib)" = { + expr = + (builtins.tryEval ( + lib.caisson.flake-parts.mkConfiguration { + inputs = { }; + configModule = { }; + } + )).success; + expected = false; }; - "test: does not reject args without inputs" = { - expr = builtins.hasAttr "inputs" { configModule = { }; }; + "test: refuses modules (configModule and moduleImports carry them)" = { + expr = + (builtins.tryEval ( + lib.caisson.flake-parts.mkConfiguration { + modules = [ ]; + configModule = { }; + } + )).success; + expected = false; + }; + + "test: refuses evaluator arguments outside the unsupervised twin" = { + expr = + (builtins.tryEval ( + lib.caisson.flake-parts.mkConfiguration { + configModule = { }; + evaluatorArgs = { }; + } + )).success; expected = false; }; "test: filteredArgs strips reserved keys" = { - # flake-parts.mkConfiguration removes configModule, modules, and moduleImports before - # forwarding to flake-parts. Verify the stripping logic in isolation. + # The composed mkFlake call is built from named arguments; this + # checks the removeAttrs idiom in isolation. expr = let args = { @@ -1590,7 +1607,7 @@ in let myLib = mkResolutionLib { ecosystems.colmena = colmenaStub "declared"; }; in - (myLib.caisson.colmena.mkConfiguration { }).stubbed; + (myLib.caisson.colmena.mkConfiguration { configModule = { }; }).stubbed; expected = "declared"; }; @@ -1599,7 +1616,10 @@ in let myLib = mkResolutionLib { ecosystems.colmena = colmenaStub "declared"; }; in - (myLib.caisson.colmena.mkConfiguration { ecosystemSrc = colmenaStub "explicit"; }).stubbed; + (myLib.caisson.colmena.mkConfiguration { + ecosystemSrc = colmenaStub "explicit"; + configModule = { }; + }).stubbed; expected = "explicit"; }; @@ -1612,7 +1632,7 @@ in }; }; in - (myLib.caisson.colmena.mkConfiguration { }).stubbed; + (myLib.caisson.colmena.mkConfiguration { configModule = { }; }).stubbed; expected = "input"; }; @@ -1626,12 +1646,14 @@ in ecosystems.colmena = colmenaStub "declared"; }; in - (myLib.caisson.colmena.mkConfiguration { }).stubbed; + (myLib.caisson.colmena.mkConfiguration { configModule = { }; }).stubbed; expected = "declared"; }; "test: a full miss throws at the adapter" = { - expr = builtins.tryEval ((mkResolutionLib { }).caisson.colmena.mkConfiguration { }).stubbed; + expr = + builtins.tryEval + ((mkResolutionLib { }).caisson.colmena.mkConfiguration { configModule = { }; }).stubbed; expected = { success = false; value = false; From 795960960f1843baf512be69dd880eb65bf1aaff Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Wed, 9 Sep 2026 23:45:32 -0700 Subject: [PATCH 5/8] refactor(lib): name the open twins mkConfigurationWithEcosystemArgs The deliberate way to the evaluator's full surface is named for what it takes: `ecosystemArgs`, the arguments of the ecosystem's own evaluator, beside `ecosystemSrc`. Co-Authored-By: Claude Fable 5.1 --- lib-overlays/check-args.nix | 8 ++++---- lib-overlays/colmena/default.nix | 14 +++++++------- lib-overlays/flake-parts/default.nix | 16 ++++++++-------- lib-overlays/home-manager/default.nix | 18 +++++++++--------- lib-overlays/nixos/default.nix | 24 ++++++++++++------------ lib-overlays/system-manager/default.nix | 18 +++++++++--------- lib-overlays/terranix/default.nix | 18 +++++++++--------- tests/unit/lib-overlays.nix | 4 ++-- 8 files changed, 60 insertions(+), 60 deletions(-) diff --git a/lib-overlays/check-args.nix b/lib-overlays/check-args.nix index 04263a0..756cabb 100644 --- a/lib-overlays/check-args.nix +++ b/lib-overlays/check-args.nix @@ -6,9 +6,9 @@ # call from them. Nothing else is forwarded: an evaluator argument # handed in directly would be silently overwritten, silently dropped, # or surface as a conflict deep inside the evaluator. The -# `...Unsupervised` twin of each entry point is the way to the +# `...WithEcosystemArgs` twin of each entry point is the way to the # evaluator's full surface: it takes the same arguments plus -# `evaluatorArgs`, merged over the composed call verbatim, last. +# `ecosystemArgs`, merged over the composed call verbatim, last. # # context: the entry point, for the message. # accepted: the argument names it takes. @@ -29,8 +29,8 @@ let if hints ? ${name} then "${context} does not accept `${name}`: ${hints.${name}}" else if open != null then - "${context} does not accept `${name}`; it takes ${builtins.concatStringsSep ", " accepted}. The evaluator's own arguments are available through ${open}, in `evaluatorArgs`." + "${context} does not accept `${name}`; it takes ${builtins.concatStringsSep ", " accepted}. The evaluator's own arguments are available through ${open}, in `ecosystemArgs`." else - "${context} does not accept `${name}`; it takes ${builtins.concatStringsSep ", " accepted}. Evaluator arguments go in `evaluatorArgs`."; + "${context} does not accept `${name}`; it takes ${builtins.concatStringsSep ", " accepted}. Evaluator arguments go in `ecosystemArgs`."; in if unknown == [ ] then args else throw message diff --git a/lib-overlays/colmena/default.nix b/lib-overlays/colmena/default.nix index e791b84..7f7c2e2 100644 --- a/lib-overlays/colmena/default.nix +++ b/lib-overlays/colmena/default.nix @@ -39,11 +39,11 @@ checkArgs = import ../check-args.nix { context = "lib.caisson.colmena.mkConfiguration"; inherit accepted hints; - open = "lib.caisson.colmena.mkConfigurationUnsupervised"; + open = "lib.caisson.colmena.mkConfigurationWithEcosystemArgs"; }; checkOpenArgs = import ../check-args.nix { - context = "lib.caisson.colmena.mkConfigurationUnsupervised"; - accepted = accepted ++ [ "evaluatorArgs" ]; + context = "lib.caisson.colmena.mkConfigurationWithEcosystemArgs"; + accepted = accepted ++ [ "ecosystemArgs" ]; inherit hints; }; @@ -100,23 +100,23 @@ in composed.checkedEcosystemSrc.lib.makeHive composed.hive; - # The same composition, then `evaluatorArgs` merged over the hive + # The same composition, then `ecosystemArgs` merged over the hive # verbatim: every attribute makeHive reads (meta, defaults, the # nodes) can be set or replaced there. - mkConfigurationUnsupervised = + mkConfigurationWithEcosystemArgs = rawArgs: let args = checkOpenArgs rawArgs; composed = compose args; in - composed.checkedEcosystemSrc.lib.makeHive (composed.hive // (args.evaluatorArgs or { })); + composed.checkedEcosystemSrc.lib.makeHive (composed.hive // (args.ecosystemArgs or { })); in { caisson = (prev.caisson or { }) // { colmena = ((prev.caisson or { }).colmena or { }) // { inherit mkConfiguration - mkConfigurationUnsupervised + mkConfigurationWithEcosystemArgs mkModule ; }; diff --git a/lib-overlays/flake-parts/default.nix b/lib-overlays/flake-parts/default.nix index 9274575..7d4dac2 100644 --- a/lib-overlays/flake-parts/default.nix +++ b/lib-overlays/flake-parts/default.nix @@ -80,18 +80,18 @@ checkArgs = import ../check-args.nix { context = "lib.caisson.flake-parts.mkConfiguration"; inherit accepted hints; - open = "lib.caisson.flake-parts.mkConfigurationUnsupervised"; + open = "lib.caisson.flake-parts.mkConfigurationWithEcosystemArgs"; }; checkOpenArgs = import ../check-args.nix { - context = "lib.caisson.flake-parts.mkConfigurationUnsupervised"; - accepted = accepted ++ [ "evaluatorArgs" ]; + context = "lib.caisson.flake-parts.mkConfigurationWithEcosystemArgs"; + accepted = accepted ++ [ "ecosystemArgs" ]; inherit hints; }; mkConfiguration = rawArgs: mkConfigurationChecked (checkArgs rawArgs); - # The same composition, then `evaluatorArgs` merged over the + # The same composition, then `ecosystemArgs` merged over the # flake-parts mkFlake call verbatim: everything it takes (inputs, # specialArgs, self, moduleLocation) can be set or replaced there. - mkConfigurationUnsupervised = rawArgs: mkConfigurationChecked (checkOpenArgs rawArgs); + mkConfigurationWithEcosystemArgs = rawArgs: mkConfigurationChecked (checkOpenArgs rawArgs); mkConfigurationChecked = args@{ @@ -122,7 +122,7 @@ specialArgs ? { }, - evaluatorArgs ? { }, + ecosystemArgs ? { }, }: ( let @@ -145,7 +145,7 @@ // (if pkgSets != null then { inherit pkgSets; } else { }) // specialArgs; } - // evaluatorArgs; + // ecosystemArgs; # Selection over the flake class of the registry, the # same source every adapter selects from, so modules @@ -176,7 +176,7 @@ caisson = (prev.caisson or { }) // { flake-parts = prevNs // { - inherit mkConfiguration mkConfigurationUnsupervised types; + inherit mkConfiguration mkConfigurationWithEcosystemArgs types; mkModule = final.caisson-core.mkModule "flake"; }; }; diff --git a/lib-overlays/home-manager/default.nix b/lib-overlays/home-manager/default.nix index eeb06a5..90f44a8 100644 --- a/lib-overlays/home-manager/default.nix +++ b/lib-overlays/home-manager/default.nix @@ -224,14 +224,14 @@ context = "lib.caisson.home-manager.mkConfiguration"; accepted = configurationArgs; inherit hints; - open = "lib.caisson.home-manager.mkConfigurationUnsupervised"; + open = "lib.caisson.home-manager.mkConfigurationWithEcosystemArgs"; }; checkOpenArgs = import ../check-args.nix { - context = "lib.caisson.home-manager.mkConfigurationUnsupervised"; - accepted = configurationArgs ++ [ "evaluatorArgs" ]; + context = "lib.caisson.home-manager.mkConfigurationWithEcosystemArgs"; + accepted = configurationArgs ++ [ "ecosystemArgs" ]; inherit hints; }; - evaluatorArgsOf = common: { + ecosystemArgsOf = common: { inherit (common) check configuration @@ -247,20 +247,20 @@ common = mkCommonArgs (checkArgs rawArgs); evaluator = import common.evaluatorPath; in - evaluator (evaluatorArgsOf common); + evaluator (ecosystemArgsOf common); - # The same composition, then `evaluatorArgs` merged over the + # The same composition, then `ecosystemArgs` merged over the # evaluator call verbatim: everything home-manager's evaluator # takes (configuration, pkgs, lib, minimal, check, # extraSpecialArgs) can be set or replaced there. - mkConfigurationUnsupervised = + mkConfigurationWithEcosystemArgs = rawArgs: let args = checkOpenArgs rawArgs; common = mkCommonArgs args; evaluator = import common.evaluatorPath; in - evaluator (evaluatorArgsOf common // (args.evaluatorArgs or { })); + evaluator (ecosystemArgsOf common // (args.ecosystemArgs or { })); mkConfigurationMinimal = args: mkConfiguration (args // { minimal = true; }); @@ -525,7 +525,7 @@ assertSourceCoherence mkConfiguration mkConfigurationMinimal - mkConfigurationUnsupervised + mkConfigurationWithEcosystemArgs mkModule mkNixosAdapter mkSourceMeta diff --git a/lib-overlays/nixos/default.nix b/lib-overlays/nixos/default.nix index 89b24b3..9eebc30 100644 --- a/lib-overlays/nixos/default.nix +++ b/lib-overlays/nixos/default.nix @@ -117,7 +117,7 @@ let args = mkCheck "mkConfiguration" [ "system" - ] "lib.caisson.nixos.mkConfigurationUnsupervised" rawArgs; + ] "lib.caisson.nixos.mkConfigurationWithEcosystemArgs" rawArgs; common = compose { } args; in evalConfig common (evalConfigArgs args common); @@ -129,7 +129,7 @@ let args = mkCheck "mkConfigurationFull" [ "system" - ] "lib.caisson.nixos.mkConfigurationUnsupervised" rawArgs; + ] "lib.caisson.nixos.mkConfigurationWithEcosystemArgs" rawArgs; common = compose { } args; in evalConfig common ( @@ -139,18 +139,18 @@ } ); - # The same composition as mkConfiguration, then `evaluatorArgs` + # The same composition as mkConfiguration, then `ecosystemArgs` # merged over the eval-config call verbatim: everything # eval-config takes (system, pkgs, baseModules, specialArgs, # modules, modulesLocation, prefix, lib, extraModules) can be set # or replaced there. - mkConfigurationUnsupervised = + mkConfigurationWithEcosystemArgs = rawArgs: let - args = mkCheck "mkConfigurationUnsupervised" [ "system" "evaluatorArgs" ] null rawArgs; + args = mkCheck "mkConfigurationWithEcosystemArgs" [ "system" "ecosystemArgs" ] null rawArgs; common = compose { } args; in - evalConfig common (evalConfigArgs args common // (args.evaluatorArgs or { })); + evalConfig common (evalConfigArgs args common // (args.ecosystemArgs or { })); # nixos/lib's evalModules: no NixOS base modules, so the config # module declares any options it uses; the package set arrives as @@ -167,18 +167,18 @@ let args = mkCheck "mkConfigurationMinimal" [ "prefix" - ] "lib.caisson.nixos.mkConfigurationMinimalUnsupervised" rawArgs; + ] "lib.caisson.nixos.mkConfigurationMinimalWithEcosystemArgs" rawArgs; common = compose { minimal = true; } args; in evalMinimal common (evalMinimalArgs args common); - mkConfigurationMinimalUnsupervised = + mkConfigurationMinimalWithEcosystemArgs = rawArgs: let - args = mkCheck "mkConfigurationMinimalUnsupervised" [ "prefix" "evaluatorArgs" ] null rawArgs; + args = mkCheck "mkConfigurationMinimalWithEcosystemArgs" [ "prefix" "ecosystemArgs" ] null rawArgs; common = compose { minimal = true; } args; in - evalMinimal common (evalMinimalArgs args common // (args.evaluatorArgs or { })); + evalMinimal common (evalMinimalArgs args common // (args.ecosystemArgs or { })); in { caisson = (prev.caisson or { }) // { @@ -188,8 +188,8 @@ mkConfiguration mkConfigurationFull mkConfigurationMinimal - mkConfigurationUnsupervised - mkConfigurationMinimalUnsupervised + mkConfigurationWithEcosystemArgs + mkConfigurationMinimalWithEcosystemArgs ; }; }; diff --git a/lib-overlays/system-manager/default.nix b/lib-overlays/system-manager/default.nix index 4daeadd..3b68afc 100644 --- a/lib-overlays/system-manager/default.nix +++ b/lib-overlays/system-manager/default.nix @@ -72,11 +72,11 @@ checkArgs = import ../check-args.nix { context = "lib.caisson.system-manager.mkConfiguration"; inherit accepted hints; - open = "lib.caisson.system-manager.mkConfigurationUnsupervised"; + open = "lib.caisson.system-manager.mkConfigurationWithEcosystemArgs"; }; checkOpenArgs = import ../check-args.nix { - context = "lib.caisson.system-manager.mkConfigurationUnsupervised"; - accepted = accepted ++ [ "evaluatorArgs" ]; + context = "lib.caisson.system-manager.mkConfigurationWithEcosystemArgs"; + accepted = accepted ++ [ "ecosystemArgs" ]; inherit hints; }; # makeSystemConfig's arguments, composed from the caisson arguments, @@ -167,7 +167,7 @@ in { inherit checkedEcosystemSrc; - evaluatorArgs = { + ecosystemArgs = { inherit (common) specialArgs; modules = common.modules ++ compatModules; }; @@ -178,20 +178,20 @@ let composed = compose (checkArgs rawArgs); in - composed.checkedEcosystemSrc.lib.makeSystemConfig composed.evaluatorArgs; + composed.checkedEcosystemSrc.lib.makeSystemConfig composed.ecosystemArgs; - # The same composition, then `evaluatorArgs` merged over the + # The same composition, then `ecosystemArgs` merged over the # evaluator call verbatim: everything makeSystemConfig takes # (modules, overlays, specialArgs, allowUnsupportedNixpkgs) can be # set or replaced there. - mkConfigurationUnsupervised = + mkConfigurationWithEcosystemArgs = rawArgs: let args = checkOpenArgs rawArgs; composed = compose args; in composed.checkedEcosystemSrc.lib.makeSystemConfig ( - composed.evaluatorArgs // (args.evaluatorArgs or { }) + composed.ecosystemArgs // (args.ecosystemArgs or { }) ); in { @@ -199,7 +199,7 @@ system-manager = ((prev.caisson or { }).system-manager or { }) // { inherit mkConfiguration - mkConfigurationUnsupervised + mkConfigurationWithEcosystemArgs mkModule ; }; diff --git a/lib-overlays/terranix/default.nix b/lib-overlays/terranix/default.nix index 0f30cef..15d260a 100644 --- a/lib-overlays/terranix/default.nix +++ b/lib-overlays/terranix/default.nix @@ -38,11 +38,11 @@ checkArgs = import ../check-args.nix { context = "lib.caisson.terranix.mkConfiguration"; inherit accepted hints; - open = "lib.caisson.terranix.mkConfigurationUnsupervised"; + open = "lib.caisson.terranix.mkConfigurationWithEcosystemArgs"; }; checkOpenArgs = import ../check-args.nix { - context = "lib.caisson.terranix.mkConfigurationUnsupervised"; - accepted = accepted ++ [ "evaluatorArgs" ]; + context = "lib.caisson.terranix.mkConfigurationWithEcosystemArgs"; + accepted = accepted ++ [ "ecosystemArgs" ]; inherit hints; }; @@ -71,7 +71,7 @@ in { inherit checkedEcosystemSrc; - evaluatorArgs = (if pkgSets != null && pkgSets ? pkgs then { pkgs = pkgSets.pkgs; } else { }) // { + ecosystemArgs = (if pkgSets != null && pkgSets ? pkgs then { pkgs = pkgSets.pkgs; } else { }) // { modules = selectedModules ++ [ configModule ]; extraArgs = { inputs = closure-inputs; @@ -90,21 +90,21 @@ if !(args ? pkgSets && args.pkgSets ? pkgs) then throw "lib.caisson.terranix.mkConfiguration requires `pkgSets.pkgs` to be defined." else - composed.checkedEcosystemSrc.lib.terranixConfiguration composed.evaluatorArgs; + composed.checkedEcosystemSrc.lib.terranixConfiguration composed.ecosystemArgs; - # The same composition, then `evaluatorArgs` merged over the + # The same composition, then `ecosystemArgs` merged over the # evaluator call verbatim: everything terranixConfiguration takes # (system, pkgs, modules, extraArgs, strip_nulls) can be set or # replaced there; pkgSets is optional here since `system` or # `pkgs` may come that way. - mkConfigurationUnsupervised = + mkConfigurationWithEcosystemArgs = rawArgs: let args = checkOpenArgs rawArgs; composed = compose args; in composed.checkedEcosystemSrc.lib.terranixConfiguration ( - composed.evaluatorArgs // (args.evaluatorArgs or { }) + composed.ecosystemArgs // (args.ecosystemArgs or { }) ); in { @@ -112,7 +112,7 @@ terranix = ((prev.caisson or { }).terranix or { }) // { inherit mkConfiguration - mkConfigurationUnsupervised + mkConfigurationWithEcosystemArgs mkModule ; }; diff --git a/tests/unit/lib-overlays.nix b/tests/unit/lib-overlays.nix index d7d40f0..06907c9 100644 --- a/tests/unit/lib-overlays.nix +++ b/tests/unit/lib-overlays.nix @@ -1284,12 +1284,12 @@ in expected = false; }; - "test: refuses evaluator arguments outside the unsupervised twin" = { + "test: refuses evaluator arguments outside the ecosystem-args twin" = { expr = (builtins.tryEval ( lib.caisson.flake-parts.mkConfiguration { configModule = { }; - evaluatorArgs = { }; + ecosystemArgs = { }; } )).success; expected = false; From 099bd6b4df643b4eb6e7680786321bef545c9657 Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Thu, 10 Sep 2026 00:36:29 -0700 Subject: [PATCH 6/8] refactor(colmena): compose hive nodes as the NixOS configurations they are A colmena hive's nodes are NixOS configurations, but the integration treated them as a module class of their own with separate package-set plumbing, so a consumer had to mirror the nixos composition by hand inside the hive (selection, package set, special arguments) and prove afterwards that the two evaluations agreed. The nixos composition now lives in one shared definition (lib-overlays/nixos/compose.nix) that both integrations import: the selected nixos-class modules, the hive-wide configModule and the framework's nixpkgs.pkgs module become the hive's `defaults`, and each `nodes. = { configModule, deployment? }` adds its own module. Colmena is handed the package set's identity (path, lib, stdenv) with empty overlays and config, which is what it reads from meta.nixpkgs and seeds every node with; nixpkgs only allows nixpkgs.pkgs beside empty seeding, so the nodes then take the real instance the way every other caisson NixOS evaluation does. A node's toplevel is therefore the derivation lib.caisson.nixos.mkConfiguration builds from the same modules, by construction. The colmena module class and lib.caisson.colmena.mkModule are gone (nothing registered under the class). meta.nixpkgs, meta.specialArgs and their per-node forms are refused with hints; the per-node forms remain reachable through mkConfigurationWithEcosystemArgs. Co-Authored-By: Claude Fable 5.1 --- lib-overlays/colmena/default.nix | 102 +++++++++++++++++++++++-------- lib-overlays/nixos/compose.nix | 58 ++++++++++++++++++ lib-overlays/nixos/default.nix | 60 +++--------------- tests/unit/lib-overlays.nix | 21 +++++-- 4 files changed, 159 insertions(+), 82 deletions(-) create mode 100644 lib-overlays/nixos/compose.nix diff --git a/lib-overlays/colmena/default.nix b/lib-overlays/colmena/default.nix index 7f7c2e2..74e5d14 100644 --- a/lib-overlays/colmena/default.nix +++ b/lib-overlays/colmena/default.nix @@ -1,5 +1,14 @@ # SPDX-License-Identifier: MIT -{ closure-inputs, ... }: +# +# The colmena integration. A hive's nodes are NixOS configurations, so +# the integration composes them exactly as lib.caisson.nixos does (the +# shared composition in ../nixos/compose.nix): the selected nixos-class +# modules, the hive-wide config module and the framework's package-set +# module become the hive's `defaults`, and each node adds its own +# config module and deployment settings. A node's toplevel is therefore +# the same derivation lib.caisson.nixos.mkConfiguration builds from the +# same modules; there is no colmena module class. +{ ... }: { imports = [ ]; @@ -7,8 +16,6 @@ overlay = final: prev: let - mkModule = final.caisson-core.mkModule "colmena"; - resolveEcosystemSrc = import ../resolve-ecosystem-src.nix { name = "colmena"; context = "caisson.colmena"; @@ -22,17 +29,19 @@ else throw "lib.caisson.colmena.mkConfiguration requires `ecosystemSrc.lib.makeHive`."; + composeNixos = import ../nixos/compose.nix { inherit final; }; + accepted = [ "ecosystemSrc" + "pkgSets" "configModule" "moduleImports" "specialArgs" - "pkgSets" "meta" "nodes" ]; hints = { - modules = "pass the hive-wide module as `configModule`; registered class modules are selected with `moduleImports`."; + modules = "pass the hive-wide module as `configModule`; registered nixos-class modules are selected with `moduleImports`."; defaults = "pass the hive-wide module as `configModule`."; network = "pass hive metadata as `meta`."; }; @@ -47,19 +56,45 @@ inherit hints; }; - # The hive makeHive receives, composed from the caisson arguments: - # the selected class modules and the config module become - # `defaults`, framework special arguments merge into - # `meta.specialArgs` (the caller's win on conflict, as is normal - # in the Nix ecosystem), and pkgSets.pkgs is the default - # `meta.nixpkgs`. + # The parts of colmena's `meta` the integration composes itself. + metaHints = { + nixpkgs = "pass the nodes' package set as `pkgSets.pkgs`."; + nodeNixpkgs = "every node evaluates against `pkgSets.pkgs`; a per-node package set is an ecosystem argument (mkConfigurationWithEcosystemArgs)."; + specialArgs = "pass extra module arguments as `specialArgs`."; + nodeSpecialArgs = "per-node module arguments are an ecosystem argument (mkConfigurationWithEcosystemArgs)."; + }; + checkMeta = + meta: + let + offending = builtins.filter (name: builtins.hasAttr name meta) (builtins.attrNames metaHints); + in + if offending == [ ] then + meta + else + throw "lib.caisson.colmena.mkConfiguration does not accept `meta.${builtins.head offending}`: ${ + metaHints.${builtins.head offending} + }"; + + mkNode = + name: node: + if !(builtins.isAttrs node && node ? configModule) then + throw "lib.caisson.colmena.mkConfiguration requires `nodes.${name}.configModule`." + else + { + _file = "caisson-colmena:node-${name}"; + imports = [ + node.configModule + ] + ++ (if node ? deployment then [ { inherit (node) deployment; } ] else [ ]); + }; + compose = { ecosystemSrc ? null, + pkgSets, configModule, moduleImports ? builtins.attrValues, specialArgs ? { }, - pkgSets ? null, meta ? { }, nodes ? { }, ... @@ -69,26 +104,42 @@ explicit = ecosystemSrc; manifest = final.caisson-core.manifest or { }; }); - selectedModules = moduleImports (final.caisson-core.modules.colmena or { }); + nixos = composeNixos { context = "lib.caisson.colmena.mkConfiguration"; } { + inherit + pkgSets + configModule + moduleImports + specialArgs + ; + }; + pkgs = nixos.checkedPkgSets.pkgs; in { inherit checkedEcosystemSrc; - hive = nodes // { - meta = - (if pkgSets != null && pkgSets ? pkgs then { nixpkgs = pkgSets.pkgs; } else { }) - // meta - // { - specialArgs = { - inputs = closure-inputs; - } - // (if pkgSets != null then { inherit pkgSets; } else { }) - // (meta.specialArgs or { }) - // specialArgs; + hive = builtins.mapAttrs mkNode nodes // { + meta = checkMeta meta // { + # colmena wants an initialized nixpkgs it reads `path`, + # `lib` and `stdenv.hostPlatform.system` from, and it + # seeds every node's `nixpkgs.overlays` and + # `nixpkgs.config` from the instance's own. The nodes take + # the package set the way every caisson NixOS evaluation + # does, through the framework's `nixpkgs.pkgs`, which + # nixpkgs allows only beside empty seeding; so colmena is + # handed the package set's identity with nothing to seed, + # and the nodes' nixpkgs module resolves to the very + # instance. + nixpkgs = { + inherit (pkgs) path lib stdenv; + overlays = [ ]; + config = { }; }; + inherit (nixos) specialArgs; + }; defaults = { ... }: { - imports = selectedModules ++ [ configModule ]; + _file = "caisson-colmena:defaults"; + imports = nixos.modules; }; }; }; @@ -117,7 +168,6 @@ inherit mkConfiguration mkConfigurationWithEcosystemArgs - mkModule ; }; }; diff --git a/lib-overlays/nixos/compose.nix b/lib-overlays/nixos/compose.nix new file mode 100644 index 0000000..e1b6b7a --- /dev/null +++ b/lib-overlays/nixos/compose.nix @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: MIT +# +# The composition every NixOS evaluation caisson performs shares: the +# nixos integration's entry points and the colmena integration's hive +# nodes (NixOS configurations that colmena evaluates). One definition, +# so the two cannot express different machines from the same arguments. +{ final }: +let + assertPkgSets = + context: pkgSets: + if pkgSets ? pkgs then pkgSets else throw "${context} requires `pkgSets.pkgs` to be defined."; + + # eval-config evaluations carry the nixpkgs module, so the package + # set lands on `nixpkgs.pkgs`; the minimal evaluator has no such + # module, so there it is the `pkgs` module argument instead. + mkFrameworkModule = pkgSets: { + _file = "caisson-nixos:framework"; + config = { + nixpkgs.pkgs = pkgSets.pkgs; + }; + }; + mkMinimalFrameworkModule = pkgSets: { + _file = "caisson-nixos:framework-minimal"; + config = { + _module.args.pkgs = final.mkDefault pkgSets.pkgs; + }; + }; +in +{ + context, + minimal ? false, +}: +{ + pkgSets, + configModule, + moduleImports ? builtins.attrValues, + specialArgs ? { }, + ... +}: +let + checkedPkgSets = assertPkgSets context pkgSets; + selectedModules = moduleImports (final.caisson-core.modules.nixos or { }); + frameworkModule = + if minimal then mkMinimalFrameworkModule checkedPkgSets else mkFrameworkModule checkedPkgSets; +in +{ + inherit checkedPkgSets; + modules = selectedModules ++ [ + configModule + frameworkModule + ]; + # Framework defaults first; the caller's win on conflict, as is + # normal in the Nix ecosystem. + specialArgs = { + pkgSets = checkedPkgSets; + } + // specialArgs; +} diff --git a/lib-overlays/nixos/default.nix b/lib-overlays/nixos/default.nix index 9eebc30..1437f30 100644 --- a/lib-overlays/nixos/default.nix +++ b/lib-overlays/nixos/default.nix @@ -21,28 +21,7 @@ manifest = final.caisson-core.manifest or { }; }; - assertPkgSets = - pkgSets: - if pkgSets ? pkgs then - pkgSets - else - throw "lib.caisson.nixos.mkConfiguration requires `pkgSets.pkgs` to be defined."; - - # eval-config evaluations carry the nixpkgs module, so the package - # set lands on `nixpkgs.pkgs`; the minimal evaluator has no such - # module, so there it is the `pkgs` module argument instead. - mkFrameworkModule = pkgSets: { - _file = "caisson-nixos:framework"; - config = { - nixpkgs.pkgs = pkgSets.pkgs; - }; - }; - mkMinimalFrameworkModule = pkgSets: { - _file = "caisson-nixos:framework-minimal"; - config = { - _module.args.pkgs = final.mkDefault pkgSets.pkgs; - }; - }; + composeNixos = import ./compose.nix { inherit final; }; commonAccepted = [ "ecosystemSrc" @@ -64,40 +43,17 @@ inherit hints open; }; - # What every variant composes from the caisson arguments: the - # selected class modules, the config module and the framework - # module as `modules`, and pkgSets threaded through `specialArgs` - # (framework defaults first; the caller's win on conflict, as is - # normal in the Nix ecosystem). compose = { minimal ? false, }: - { - ecosystemSrc ? null, - pkgSets, - configModule, - moduleImports ? builtins.attrValues, - specialArgs ? { }, - ... - }: - let - checkedPkgSets = assertPkgSets pkgSets; - selectedModules = moduleImports (final.caisson-core.modules.nixos or { }); - frameworkModule = - if minimal then mkMinimalFrameworkModule checkedPkgSets else mkFrameworkModule checkedPkgSets; - in - { - src = resolveSrc ecosystemSrc; - inherit checkedPkgSets; - modules = selectedModules ++ [ - configModule - frameworkModule - ]; - specialArgs = { - pkgSets = checkedPkgSets; - } - // specialArgs; + args: + composeNixos { + context = "lib.caisson.nixos.mkConfiguration"; + inherit minimal; + } args + // { + src = resolveSrc (args.ecosystemSrc or null); }; # eval-config evaluations. `system` defaults to the package set's diff --git a/tests/unit/lib-overlays.nix b/tests/unit/lib-overlays.nix index 06907c9..14a5450 100644 --- a/tests/unit/lib-overlays.nix +++ b/tests/unit/lib-overlays.nix @@ -1607,7 +1607,10 @@ in let myLib = mkResolutionLib { ecosystems.colmena = colmenaStub "declared"; }; in - (myLib.caisson.colmena.mkConfiguration { configModule = { }; }).stubbed; + (myLib.caisson.colmena.mkConfiguration { + configModule = { }; + pkgSets.pkgs = { }; + }).stubbed; expected = "declared"; }; @@ -1619,6 +1622,7 @@ in (myLib.caisson.colmena.mkConfiguration { ecosystemSrc = colmenaStub "explicit"; configModule = { }; + pkgSets.pkgs = { }; }).stubbed; expected = "explicit"; }; @@ -1632,7 +1636,10 @@ in }; }; in - (myLib.caisson.colmena.mkConfiguration { configModule = { }; }).stubbed; + (myLib.caisson.colmena.mkConfiguration { + configModule = { }; + pkgSets.pkgs = { }; + }).stubbed; expected = "input"; }; @@ -1646,14 +1653,20 @@ in ecosystems.colmena = colmenaStub "declared"; }; in - (myLib.caisson.colmena.mkConfiguration { configModule = { }; }).stubbed; + (myLib.caisson.colmena.mkConfiguration { + configModule = { }; + pkgSets.pkgs = { }; + }).stubbed; expected = "declared"; }; "test: a full miss throws at the adapter" = { expr = builtins.tryEval - ((mkResolutionLib { }).caisson.colmena.mkConfiguration { configModule = { }; }).stubbed; + ((mkResolutionLib { }).caisson.colmena.mkConfiguration { + configModule = { }; + pkgSets.pkgs = { }; + }).stubbed; expected = { success = false; value = false; From 5334ae93f36e1611212baa1d5ab4775874aba6bc Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Sat, 12 Sep 2026 12:17:22 -0700 Subject: [PATCH 7/8] refactor(colmena): evaluate nodes as NixOS configurations; project the hive The integration no longer hands colmena a hive to evaluate. A node is lib.caisson.colmena.mkNixosConfiguration: nixos.mkConfiguration's signature and composition, plus colmena's public node modules (deploymentOptions, keyChownModule, keyServiceModule, assertionModule) from the colmena ecosystem source, so the result is an ordinary NixOS configuration that also declares `deployment` and can be the host's nixosConfigurations entry, one evaluation for nixos-rebuild and colmena apply. In this namespace `ecosystemSrc` is colmena's; nixpkgs resolves as nixos.mkConfiguration resolves it without an explicit source. The hive is a module of class `colmena` (meta: name, description, machinesFile, allowApplyAll; nodes.: configurations from mkNixosConfiguration), evaluated by mkConfiguration and projected onto colmena's hive schema, the nine attributes its binary reads. The schema version is asserted against the ecosystem source's own makeHive, so a colmena revision that moves it fails at evaluation. A node that did not come from mkNixosConfiguration is refused. pkgSets on the hive only serves `colmena eval`. lib.makeHive, meta.nixpkgs, the package-set stub and the shared composition file's colmena use are gone; the ecosystem-resolution unit tests probe through the terranix adapter, which still returns its evaluator's result. Co-Authored-By: Claude Fable 5.1 --- lib-overlays/colmena/default.nix | 293 ++++++++++++++++++++----------- lib-overlays/nixos/compose.nix | 7 +- tests/unit/lib-overlays.nix | 41 ++--- 3 files changed, 217 insertions(+), 124 deletions(-) diff --git a/lib-overlays/colmena/default.nix b/lib-overlays/colmena/default.nix index 74e5d14..6961459 100644 --- a/lib-overlays/colmena/default.nix +++ b/lib-overlays/colmena/default.nix @@ -1,13 +1,22 @@ # SPDX-License-Identifier: MIT # -# The colmena integration. A hive's nodes are NixOS configurations, so -# the integration composes them exactly as lib.caisson.nixos does (the -# shared composition in ../nixos/compose.nix): the selected nixos-class -# modules, the hive-wide config module and the framework's package-set -# module become the hive's `defaults`, and each node adds its own -# config module and deployment settings. A node's toplevel is therefore -# the same derivation lib.caisson.nixos.mkConfiguration builds from the -# same modules; there is no colmena module class. +# The colmena integration. A hive is a set of NixOS configurations plus +# deployment metadata, and colmena's binary reads it through a small +# versioned attrset (the hive schema). +# +# mkNixosConfiguration evaluates a node: lib.caisson.nixos's +# composition plus colmena's public node modules (deploymentOptions, +# keyChownModule, keyServiceModule, assertionModule) from the colmena +# ecosystem source, with nixos.mkConfiguration's signature. The +# result is an ordinary NixOS configuration that also declares +# `deployment`, so `nixosConfigurations.` and the hive node can +# be one evaluation. +# +# mkConfiguration evaluates the hive: a module of class `colmena` with +# `meta` (the metadata colmena's binary reads) and `nodes.` +# (configurations from mkNixosConfiguration), projected onto the +# schema. Projects can contribute hive modules through the registry +# like any other class. { ... }: { @@ -16,34 +25,153 @@ overlay = final: prev: let + mkHiveModule = final.caisson-core.mkModule "colmena"; + resolveEcosystemSrc = import ../resolve-ecosystem-src.nix { name = "colmena"; context = "caisson.colmena"; resolve = final.caisson-core.resolve; }; + resolveSrc = + explicit: + resolveEcosystemSrc { + inherit explicit; + manifest = final.caisson-core.manifest or { }; + }; assertColmenaEcosystemSrc = ecosystemSrc: - if ecosystemSrc ? lib && ecosystemSrc.lib ? makeHive then + if ecosystemSrc ? lib && ecosystemSrc.lib ? makeHive && ecosystemSrc ? nixosModules then ecosystemSrc else - throw "lib.caisson.colmena.mkConfiguration requires `ecosystemSrc.lib.makeHive`."; + throw "lib.caisson.colmena requires `ecosystemSrc.lib.makeHive` and `ecosystemSrc.nixosModules` (a colmena flake)."; - composeNixos = import ../nixos/compose.nix { inherit final; }; + # The hive schema this integration emits. Colmena's binary asserts + # the version; mkConfiguration asserts it against the ecosystem + # source's own makeHive, so a colmena revision that moves the + # schema fails loudly at evaluation rather than at deploy time. + schema = "v0.5"; + metaConfigKeys = [ + "name" + "description" + "machinesFile" + "allowApplyAll" + ]; - accepted = [ + # colmena's public node modules, as one NixOS module. + mkDeploymentModule = src: { + _file = "caisson-colmena:deployment"; + imports = [ + src.nixosModules.deploymentOptions + src.nixosModules.assertionModule + src.nixosModules.keyChownModule + src.nixosModules.keyServiceModule + ]; + }; + + nodeAccepted = [ "ecosystemSrc" "pkgSets" "configModule" "moduleImports" "specialArgs" - "meta" - "nodes" + "system" + ]; + nodeHints = { + modules = "pass the host's module as `configModule`; registered nixos-class modules are selected with `moduleImports`."; + pkgs = "pass the package set as `pkgSets.pkgs`."; + deployment = "set `deployment.*` in the host's configModule; the node declares those options."; + }; + checkNodeArgs = import ../check-args.nix { + context = "lib.caisson.colmena.mkNixosConfiguration"; + accepted = nodeAccepted; + hints = nodeHints; + open = "lib.caisson.colmena.mkNixosConfigurationWithEcosystemArgs"; + }; + checkOpenNodeArgs = import ../check-args.nix { + context = "lib.caisson.colmena.mkNixosConfigurationWithEcosystemArgs"; + accepted = nodeAccepted ++ [ "ecosystemArgs" ]; + hints = nodeHints; + }; + + # A node: nixos.mkConfiguration over the host's module plus the + # deployment module. `ecosystemSrc` is colmena's here; nixpkgs + # resolves as nixos.mkConfiguration resolves it without an + # explicit source (the declared `ecosystems.nixpkgs`, else the + # input named nixpkgs). + nodeArgsOf = + args: + let + src = assertColmenaEcosystemSrc (resolveSrc (args.ecosystemSrc or null)); + in + builtins.removeAttrs args [ "ecosystemSrc" ] + // { + configModule = { + _file = "caisson-colmena:node"; + imports = [ + args.configModule + (mkDeploymentModule src) + ]; + }; + }; + mkNixosConfiguration = + rawArgs: final.caisson.nixos.mkConfiguration (nodeArgsOf (checkNodeArgs rawArgs)); + mkNixosConfigurationWithEcosystemArgs = + rawArgs: + final.caisson.nixos.mkConfigurationWithEcosystemArgs (nodeArgsOf (checkOpenNodeArgs rawArgs)); + + # The hive's options. `meta` mirrors the keys colmena's binary + # reads (its metaOptions also declare per-node package sets and + # special arguments, which have no meaning here: every node is an + # evaluated configuration already). + hiveOptions = + { lib, ... }: + { + options = { + meta = { + name = lib.mkOption { + type = lib.types.str; + default = "hive"; + description = "The name of the hive."; + }; + description = lib.mkOption { + type = lib.types.str; + default = "A Colmena Hive"; + description = "A short description of the hive."; + }; + machinesFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + apply = value: if value == null then null else toString value; + description = "The machines file passed to nix-store as `builders` when realizing this hive."; + }; + allowApplyAll = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether `colmena apply` without a node filter is allowed."; + }; + }; + nodes = lib.mkOption { + type = lib.types.attrsOf lib.types.raw; + default = { }; + description = "The hive's nodes: NixOS configurations from lib.caisson.colmena.mkNixosConfiguration."; + }; + }; + }; + + accepted = [ + "ecosystemSrc" + "configModule" + "moduleImports" + "specialArgs" + "pkgSets" ]; hints = { - modules = "pass the hive-wide module as `configModule`; registered nixos-class modules are selected with `moduleImports`."; - defaults = "pass the hive-wide module as `configModule`."; - network = "pass hive metadata as `meta`."; + modules = "pass the hive module as `configModule`; registered colmena-class modules are selected with `moduleImports`."; + meta = "hive metadata is the hive module's `meta`."; + nodes = "the nodes are the hive module's `nodes`."; + defaults = "there is no hive-wide module: every node is an evaluated NixOS configuration (mkNixosConfiguration); select shared modules there."; + network = "hive metadata is the hive module's `meta`."; }; checkArgs = import ../check-args.nix { context = "lib.caisson.colmena.mkConfiguration"; @@ -56,118 +184,83 @@ inherit hints; }; - # The parts of colmena's `meta` the integration composes itself. - metaHints = { - nixpkgs = "pass the nodes' package set as `pkgSets.pkgs`."; - nodeNixpkgs = "every node evaluates against `pkgSets.pkgs`; a per-node package set is an ecosystem argument (mkConfigurationWithEcosystemArgs)."; - specialArgs = "pass extra module arguments as `specialArgs`."; - nodeSpecialArgs = "per-node module arguments are an ecosystem argument (mkConfigurationWithEcosystemArgs)."; - }; - checkMeta = - meta: - let - offending = builtins.filter (name: builtins.hasAttr name meta) (builtins.attrNames metaHints); - in - if offending == [ ] then - meta - else - throw "lib.caisson.colmena.mkConfiguration does not accept `meta.${builtins.head offending}`: ${ - metaHints.${builtins.head offending} - }"; - - mkNode = + checkNode = name: node: - if !(builtins.isAttrs node && node ? configModule) then - throw "lib.caisson.colmena.mkConfiguration requires `nodes.${name}.configModule`." + if builtins.isAttrs node && node ? config && node.config ? deployment then + node else - { - _file = "caisson-colmena:node-${name}"; - imports = [ - node.configModule - ] - ++ (if node ? deployment then [ { inherit (node) deployment; } ] else [ ]); - }; + throw '' + lib.caisson.colmena.mkConfiguration: nodes.${name} is not a colmena node. Evaluate the host with lib.caisson.colmena.mkNixosConfiguration. + ''; compose = { ecosystemSrc ? null, - pkgSets, configModule, moduleImports ? builtins.attrValues, specialArgs ? { }, - meta ? { }, - nodes ? { }, + pkgSets ? null, ... }: let - checkedEcosystemSrc = assertColmenaEcosystemSrc (resolveEcosystemSrc { - explicit = ecosystemSrc; - manifest = final.caisson-core.manifest or { }; - }); - nixos = composeNixos { context = "lib.caisson.colmena.mkConfiguration"; } { - inherit - pkgSets - configModule - moduleImports - specialArgs - ; - }; - pkgs = nixos.checkedPkgSets.pkgs; + src = assertColmenaEcosystemSrc (resolveSrc ecosystemSrc); + selectedModules = moduleImports (final.caisson-core.modules.colmena or { }); + hive = + (final.evalModules { + class = "colmena"; + modules = [ hiveOptions ] ++ selectedModules ++ [ configModule ]; + specialArgs = (if pkgSets != null then { inherit pkgSets; } else { }) // specialArgs; + }).config; + nodes = builtins.mapAttrs checkNode hive.nodes; + upstreamSchema = (src.lib.makeHive { }).__schema; in - { - inherit checkedEcosystemSrc; - hive = builtins.mapAttrs mkNode nodes // { - meta = checkMeta meta // { - # colmena wants an initialized nixpkgs it reads `path`, - # `lib` and `stdenv.hostPlatform.system` from, and it - # seeds every node's `nixpkgs.overlays` and - # `nixpkgs.config` from the instance's own. The nodes take - # the package set the way every caisson NixOS evaluation - # does, through the framework's `nixpkgs.pkgs`, which - # nixpkgs allows only beside empty seeding; so colmena is - # handed the package set's identity with nothing to seed, - # and the nodes' nixpkgs module resolves to the very - # instance. - nixpkgs = { - inherit (pkgs) path lib stdenv; - overlays = [ ]; - config = { }; - }; - inherit (nixos) specialArgs; - }; - defaults = - { ... }: - { - _file = "caisson-colmena:defaults"; - imports = nixos.modules; + if upstreamSchema != schema then + throw "lib.caisson.colmena.mkConfiguration emits hive schema ${schema}, but this colmena expects ${upstreamSchema}." + else + rec { + __schema = schema; + inherit nodes; + toplevel = builtins.mapAttrs (_: node: node.config.system.build.toplevel) nodes; + deploymentConfig = builtins.mapAttrs (_: node: node.config.deployment) nodes; + deploymentConfigSelected = + names: final.filterAttrs (name: _: builtins.elem name names) deploymentConfig; + evalSelected = names: final.filterAttrs (name: _: builtins.elem name names) toplevel; + evalSelectedDrvPaths = names: builtins.mapAttrs (_: drv: drv.drvPath) (evalSelected names); + metaConfig = final.getAttrs metaConfigKeys hive.meta; + introspect = + f: + f { + lib = final; + pkgs = + if pkgSets != null && pkgSets ? pkgs then + pkgSets.pkgs + else + throw "lib.caisson.colmena: `colmena eval` needs a package set; pass `pkgSets.pkgs` to mkConfiguration."; + inherit nodes; }; }; - }; - mkConfiguration = - rawArgs: - let - composed = compose (checkArgs rawArgs); - in - composed.checkedEcosystemSrc.lib.makeHive composed.hive; + mkConfiguration = rawArgs: compose (checkArgs rawArgs); - # The same composition, then `ecosystemArgs` merged over the hive - # verbatim: every attribute makeHive reads (meta, defaults, the - # nodes) can be set or replaced there. + # The same hive, then `ecosystemArgs` merged over it verbatim: the + # hive attrset is what colmena's binary reads, so any of its + # attributes can be set or replaced there. mkConfigurationWithEcosystemArgs = rawArgs: let args = checkOpenArgs rawArgs; - composed = compose args; in - composed.checkedEcosystemSrc.lib.makeHive (composed.hive // (args.ecosystemArgs or { })); + compose args // (args.ecosystemArgs or { }); in { caisson = (prev.caisson or { }) // { colmena = ((prev.caisson or { }).colmena or { }) // { + mkModule = mkHiveModule; inherit mkConfiguration mkConfigurationWithEcosystemArgs + mkNixosConfiguration + mkNixosConfigurationWithEcosystemArgs ; }; }; diff --git a/lib-overlays/nixos/compose.nix b/lib-overlays/nixos/compose.nix index e1b6b7a..ea326fc 100644 --- a/lib-overlays/nixos/compose.nix +++ b/lib-overlays/nixos/compose.nix @@ -1,9 +1,8 @@ # SPDX-License-Identifier: MIT # -# The composition every NixOS evaluation caisson performs shares: the -# nixos integration's entry points and the colmena integration's hive -# nodes (NixOS configurations that colmena evaluates). One definition, -# so the two cannot express different machines from the same arguments. +# The composition the nixos integration's entry points share: one +# definition of the module list and special arguments, so the variants +# cannot express different machines from the same arguments. { final }: let assertPkgSets = diff --git a/tests/unit/lib-overlays.nix b/tests/unit/lib-overlays.nix index 14a5450..f90af3a 100644 --- a/tests/unit/lib-overlays.nix +++ b/tests/unit/lib-overlays.nix @@ -1577,15 +1577,16 @@ in ecosystemResolution = let - # A minimal colmena "ecosystem": the adapter only needs - # lib.makeHive, so a stub shows which channel resolution chose. - colmenaStub = probe: { - lib.makeHive = hiveArgs: { + # A minimal terranix "ecosystem": the adapter only needs + # lib.terranixConfiguration, so a stub shows which channel + # resolution chose. + terranixStub = probe: { + lib.terranixConfiguration = evaluatorArgs: { stubbed = probe; - inherit hiveArgs; + inherit evaluatorArgs; }; }; - # Test compositions register caisson's real colmena integration + # Test compositions register caisson's real terranix integration # (built from its source file, like the flake-parts # registration) alongside the harness's flake-parts # registration. @@ -1595,7 +1596,7 @@ in { inputs = mockInputs; libOverlays = _mkLibOverlay: { - colmena = mkLibOverlay (inputs.parent.outPath + "/lib-overlays/colmena"); + terranix = mkLibOverlay (inputs.parent.outPath + "/lib-overlays/terranix"); }; } // extra @@ -1605,9 +1606,9 @@ in "test: a declared ecosystem resolves for an adapter" = { expr = let - myLib = mkResolutionLib { ecosystems.colmena = colmenaStub "declared"; }; + myLib = mkResolutionLib { ecosystems.terranix = terranixStub "declared"; }; in - (myLib.caisson.colmena.mkConfiguration { + (myLib.caisson.terranix.mkConfiguration { configModule = { }; pkgSets.pkgs = { }; }).stubbed; @@ -1617,10 +1618,10 @@ in "test: an explicit ecosystemSrc beats the declaration" = { expr = let - myLib = mkResolutionLib { ecosystems.colmena = colmenaStub "declared"; }; + myLib = mkResolutionLib { ecosystems.terranix = terranixStub "declared"; }; in - (myLib.caisson.colmena.mkConfiguration { - ecosystemSrc = colmenaStub "explicit"; + (myLib.caisson.terranix.mkConfiguration { + ecosystemSrc = terranixStub "explicit"; configModule = { }; pkgSets.pkgs = { }; }).stubbed; @@ -1632,11 +1633,11 @@ in let myLib = mkResolutionLib { inputs = mockInputs // { - colmena = colmenaStub "input"; + terranix = terranixStub "input"; }; }; in - (myLib.caisson.colmena.mkConfiguration { + (myLib.caisson.terranix.mkConfiguration { configModule = { }; pkgSets.pkgs = { }; }).stubbed; @@ -1648,12 +1649,12 @@ in let myLib = mkResolutionLib { inputs = mockInputs // { - colmena = colmenaStub "input"; + terranix = terranixStub "input"; }; - ecosystems.colmena = colmenaStub "declared"; + ecosystems.terranix = terranixStub "declared"; }; in - (myLib.caisson.colmena.mkConfiguration { + (myLib.caisson.terranix.mkConfiguration { configModule = { }; pkgSets.pkgs = { }; }).stubbed; @@ -1663,7 +1664,7 @@ in "test: a full miss throws at the adapter" = { expr = builtins.tryEval - ((mkResolutionLib { }).caisson.colmena.mkConfiguration { + ((mkResolutionLib { }).caisson.terranix.mkConfiguration { configModule = { }; pkgSets.pkgs = { }; }).stubbed; @@ -1676,9 +1677,9 @@ in "test: declarations join the manifest" = { expr = let - myLib = mkResolutionLib { ecosystems.colmena = colmenaStub "declared"; }; + myLib = mkResolutionLib { ecosystems.terranix = terranixStub "declared"; }; in - (myLib.caisson-core.manifest.ecosystems.colmena.lib.makeHive { }).stubbed; + (myLib.caisson-core.manifest.ecosystems.terranix.lib.terranixConfiguration { }).stubbed; expected = "declared"; }; }; From 7b57bcd2353a394f97a14a78bdcdb5fc3bf6db6d Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Sat, 12 Sep 2026 14:03:01 -0700 Subject: [PATCH 8/8] refactor(colmena): the node constructor is a hive module argument A NixOS configuration's ecosystem source is nixpkgs; colmena's is the hive's. So mkNixosConfiguration leaves the namespace and reaches the hive module as a module argument, closed over the hive's colmena source, with nixos.mkConfiguration's signature: its `ecosystemSrc` is nixpkgs like any other NixOS evaluation. Nodes are built inside the hive module; a consumer that exports one as nixosConfigurations. reads it back from hive.nodes. mkNixosConfigurationWithEcosystemArgs arrives the same way. Co-Authored-By: Claude Fable 5.1 --- lib-overlays/colmena/default.nix | 85 ++++++++++++++++---------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/lib-overlays/colmena/default.nix b/lib-overlays/colmena/default.nix index 6961459..38a4b45 100644 --- a/lib-overlays/colmena/default.nix +++ b/lib-overlays/colmena/default.nix @@ -4,18 +4,18 @@ # deployment metadata, and colmena's binary reads it through a small # versioned attrset (the hive schema). # -# mkNixosConfiguration evaluates a node: lib.caisson.nixos's -# composition plus colmena's public node modules (deploymentOptions, -# keyChownModule, keyServiceModule, assertionModule) from the colmena -# ecosystem source, with nixos.mkConfiguration's signature. The -# result is an ordinary NixOS configuration that also declares -# `deployment`, so `nixosConfigurations.` and the hive node can -# be one evaluation. -# # mkConfiguration evaluates the hive: a module of class `colmena` with -# `meta` (the metadata colmena's binary reads) and `nodes.` -# (configurations from mkNixosConfiguration), projected onto the -# schema. Projects can contribute hive modules through the registry +# `meta` (the metadata colmena's binary reads) and `nodes.`, +# projected onto the schema. The hive module receives +# `mkNixosConfiguration` as a module argument, closed over the hive's +# colmena source: lib.caisson.nixos's composition plus colmena's public +# node modules (deploymentOptions, keyChownModule, keyServiceModule, +# assertionModule), with nixos.mkConfiguration's signature, so its +# `ecosystemSrc` is nixpkgs as for any NixOS configuration. A node is +# an ordinary NixOS configuration that also declares `deployment`; a +# consumer that exports it as `nixosConfigurations.` reads it +# back from the hive, one evaluation for nixos-rebuild and colmena +# apply. Projects can contribute hive modules through the registry # like any other class. { ... }: { @@ -83,42 +83,44 @@ deployment = "set `deployment.*` in the host's configModule; the node declares those options."; }; checkNodeArgs = import ../check-args.nix { - context = "lib.caisson.colmena.mkNixosConfiguration"; + context = "mkNixosConfiguration (the hive module argument)"; accepted = nodeAccepted; hints = nodeHints; - open = "lib.caisson.colmena.mkNixosConfigurationWithEcosystemArgs"; + open = "mkNixosConfigurationWithEcosystemArgs"; }; checkOpenNodeArgs = import ../check-args.nix { - context = "lib.caisson.colmena.mkNixosConfigurationWithEcosystemArgs"; + context = "mkNixosConfigurationWithEcosystemArgs (the hive module argument)"; accepted = nodeAccepted ++ [ "ecosystemArgs" ]; hints = nodeHints; }; - # A node: nixos.mkConfiguration over the host's module plus the - # deployment module. `ecosystemSrc` is colmena's here; nixpkgs - # resolves as nixos.mkConfiguration resolves it without an - # explicit source (the declared `ecosystems.nixpkgs`, else the - # input named nixpkgs). - nodeArgsOf = - args: + # The node constructors a hive module receives, closed over the + # hive's colmena source: nixos.mkConfiguration over the host's + # module plus the deployment module. Their `ecosystemSrc` is + # nixpkgs, as for any NixOS configuration. + mkNodeConstructors = + src: let - src = assertColmenaEcosystemSrc (resolveSrc (args.ecosystemSrc or null)); + nodeArgsOf = + args: + args + // { + configModule = { + _file = "caisson-colmena:node"; + imports = [ + args.configModule + (mkDeploymentModule src) + ]; + }; + }; in - builtins.removeAttrs args [ "ecosystemSrc" ] - // { - configModule = { - _file = "caisson-colmena:node"; - imports = [ - args.configModule - (mkDeploymentModule src) - ]; - }; + { + mkNixosConfiguration = + rawArgs: final.caisson.nixos.mkConfiguration (nodeArgsOf (checkNodeArgs rawArgs)); + mkNixosConfigurationWithEcosystemArgs = + rawArgs: + final.caisson.nixos.mkConfigurationWithEcosystemArgs (nodeArgsOf (checkOpenNodeArgs rawArgs)); }; - mkNixosConfiguration = - rawArgs: final.caisson.nixos.mkConfiguration (nodeArgsOf (checkNodeArgs rawArgs)); - mkNixosConfigurationWithEcosystemArgs = - rawArgs: - final.caisson.nixos.mkConfigurationWithEcosystemArgs (nodeArgsOf (checkOpenNodeArgs rawArgs)); # The hive's options. `meta` mirrors the keys colmena's binary # reads (its metaOptions also declare per-node package sets and @@ -154,7 +156,7 @@ nodes = lib.mkOption { type = lib.types.attrsOf lib.types.raw; default = { }; - description = "The hive's nodes: NixOS configurations from lib.caisson.colmena.mkNixosConfiguration."; + description = "The hive's nodes: NixOS configurations from the hive module's `mkNixosConfiguration` argument."; }; }; }; @@ -170,7 +172,7 @@ modules = "pass the hive module as `configModule`; registered colmena-class modules are selected with `moduleImports`."; meta = "hive metadata is the hive module's `meta`."; nodes = "the nodes are the hive module's `nodes`."; - defaults = "there is no hive-wide module: every node is an evaluated NixOS configuration (mkNixosConfiguration); select shared modules there."; + defaults = "there is no hive-wide module: every node is an evaluated NixOS configuration (the hive module's mkNixosConfiguration); select shared modules there."; network = "hive metadata is the hive module's `meta`."; }; checkArgs = import ../check-args.nix { @@ -190,7 +192,7 @@ node else throw '' - lib.caisson.colmena.mkConfiguration: nodes.${name} is not a colmena node. Evaluate the host with lib.caisson.colmena.mkNixosConfiguration. + lib.caisson.colmena.mkConfiguration: nodes.${name} is not a colmena node. Evaluate the host with the hive module's `mkNixosConfiguration` argument. ''; compose = @@ -209,7 +211,8 @@ (final.evalModules { class = "colmena"; modules = [ hiveOptions ] ++ selectedModules ++ [ configModule ]; - specialArgs = (if pkgSets != null then { inherit pkgSets; } else { }) // specialArgs; + specialArgs = + mkNodeConstructors src // (if pkgSets != null then { inherit pkgSets; } else { }) // specialArgs; }).config; nodes = builtins.mapAttrs checkNode hive.nodes; upstreamSchema = (src.lib.makeHive { }).__schema; @@ -259,8 +262,6 @@ inherit mkConfiguration mkConfigurationWithEcosystemArgs - mkNixosConfiguration - mkNixosConfigurationWithEcosystemArgs ; }; };