From 549c19844977339aafc9e55b788e08af8dea04d1 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 25 Oct 2026 20:48:42 +0100 Subject: [PATCH] force each container's unit configs in the checks about it --- nix/module-eval/bao-basics.nix | 7 +++++++ nix/module-eval/bao-controller.nix | 7 +++++++ nix/module-eval/bao-matrix-reader.nix | 7 +++++++ nix/module-eval/bao-otel-collector.nix | 7 +++++++ nix/module-eval/core-toggle.nix | 18 ++---------------- nix/module-eval/lib.nix | 16 ++++++++++++++++ 6 files changed, 46 insertions(+), 16 deletions(-) diff --git a/nix/module-eval/bao-basics.nix b/nix/module-eval/bao-basics.nix index 1ee24246..8aa2a291 100644 --- a/nix/module-eval/bao-basics.nix +++ b/nix/module-eval/bao-basics.nix @@ -24,6 +24,7 @@ let baoSettings baoStream bridgePorts + forceServiceConfigs ; baoPkcs11 = hive { @@ -55,6 +56,12 @@ let tlsDir = "/var/lib/swarm-bao-tls"; cases = [ + { + # Every unit in the container merges without a priority collision, not + # only the keys the cases here read. + name = "the store's container units merge without a priority collision"; + ok = forceServiceConfigs baoPkcs11.containers.swarm-bao.config; + } { # The store's seal is spread over six gates — the stanza, the # provisioning unit, two bind mounts, a device and an EnvironmentFile. diff --git a/nix/module-eval/bao-controller.nix b/nix/module-eval/bao-controller.nix index efca55be..3d00c2ab 100644 --- a/nix/module-eval/bao-controller.nix +++ b/nix/module-eval/bao-controller.nix @@ -20,6 +20,7 @@ let }) hive runGroup + forceServiceConfigs ; # Store and controller on one machine, with a CN no default could supply. @@ -109,6 +110,12 @@ let a: !a.assertion && lib.hasInfix "swarm-controller.baoClientCertFile and" a.message ) m.assertions; cases = [ + { + # Every unit in the container merges without a priority collision, not + # only the keys the cases here read. + name = "the store's container units merge without a priority collision beside the controller"; + ok = forceServiceConfigs baoControllerHere.containers.swarm-bao.config; + } { # The queue client secret lives only in the store, so a controller that # cannot log in there cannot reach the queue. Refused at eval rather than diff --git a/nix/module-eval/bao-matrix-reader.nix b/nix/module-eval/bao-matrix-reader.nix index 595b43fe..f37e7da7 100644 --- a/nix/module-eval/bao-matrix-reader.nix +++ b/nix/module-eval/bao-matrix-reader.nix @@ -21,6 +21,7 @@ let hive hiveWithController runGroup + forceServiceConfigs ; # The store and a service that reads from it, versus the store alone. The @@ -61,6 +62,12 @@ let }; cases = [ + { + # Every unit in the container merges without a priority collision, not + # only the keys the cases here read. + name = "the homeserver's container units merge without a priority collision"; + ok = forceServiceConfigs baoWithMatrix.containers.hive-matrix.config; + } { # A hive holds no shared queue credential for its agents, wherever the # store runs. The control: hive-c0re renders on both hosts. diff --git a/nix/module-eval/bao-otel-collector.nix b/nix/module-eval/bao-otel-collector.nix index a175be00..56564585 100644 --- a/nix/module-eval/bao-otel-collector.nix +++ b/nix/module-eval/bao-otel-collector.nix @@ -21,6 +21,7 @@ let hive runGroup baoSettings + forceServiceConfigs ; baoTwoAddresses = hive { @@ -75,6 +76,12 @@ let scrapeJob = scrapeJobIn "swarm-otel"; baoScrapeJob = scrapeJobIn "swarm-bao"; cases = [ + { + # Every unit in the container merges without a priority collision, not + # only the keys the cases here read. + name = "the store's container units merge without a priority collision"; + ok = forceServiceConfigs baoWithCollector.containers.swarm-bao.config; + } { # Same gap one tier up, and it needs its own arm: this collector # already had six scrape targets, so a pass here is about the seventh diff --git a/nix/module-eval/core-toggle.nix b/nix/module-eval/core-toggle.nix index 86065649..166aa5a9 100644 --- a/nix/module-eval/core-toggle.nix +++ b/nix/module-eval/core-toggle.nix @@ -21,6 +21,7 @@ let hive hiveWithController runGroup + forceServiceConfigs baoNames baoStream bridgePorts @@ -84,21 +85,6 @@ let deploy.review-classifier.enable = true; }; - # A priority collision is a property of the *option*, not - # of the merged value's interior — nix throws the moment the value is - # demanded at all, so `seq`-ing each `serviceConfig` value to WHNF is - # both necessary and sufficient. `deepSeq` over-specifies this: it keeps - # walking *into* the resulting value after the merge already succeeded, - # and a package/derivation-shaped value's `override`/`overrideAttrs` - # self-reference sends it into nixpkgs' fixpoint machinery and blows the - # stack (measured — this is not a hypothetical). - forceCiServiceConfigs = - let - svcs = withCi.containers.swarm-ci-runner.config.systemd.services; - vals = lib.concatMap (s: builtins.attrValues (s.serviceConfig or { })) (builtins.attrValues svcs); - in - builtins.foldl' (acc: v: builtins.seq v acc) true vals; - # The host fetches that restart their consumer when the store's value # changes (../host-modules/lib/refresh-consumer.nix), each on a fixture # that renders it. @@ -692,7 +678,7 @@ let # instantiates a host with `containers.swarm-ci-runner` actually enabled, so # the collision only surfaces on operator deploy, not in CI. name = "the CI container's unit definitions merge without a priority collision"; - ok = forceCiServiceConfigs; + ok = forceServiceConfigs withCi.containers.swarm-ci-runner.config; } { # It spends money on an operator's key, so no host runs it by default. diff --git a/nix/module-eval/lib.nix b/nix/module-eval/lib.nix index 78819d9a..e85641d4 100644 --- a/nix/module-eval/lib.nix +++ b/nix/module-eval/lib.nix @@ -253,6 +253,21 @@ let "echo 'module-eval-${name}: ${toString (builtins.length bad)} of ${toString (builtins.length cases)} properties broke' >&2 && exit 1" } ''; + + # `serviceConfig` is an `attrsOf`: each key merges lazily, and a priority + # collision throws only when that key is demanded. A check reading + # `LoadCredential` therefore says nothing about `Restart`. Takes one + # container's `config`; `true` once every unit's every `serviceConfig` value + # is at WHNF. `seq` per value, not `deepSeq`: walking into a derivation-shaped + # value's `override` self-reference overflows the stack. + forceServiceConfigs = + containerCfg: + let + vals = lib.concatMap (s: builtins.attrValues (s.serviceConfig or { })) ( + builtins.attrValues containerCfg.systemd.services + ); + in + builtins.foldl' (acc: v: builtins.seq v acc) true vals; in { inherit hive; @@ -272,4 +287,5 @@ in inherit carriesJournaldSeverity; inherit swarmServiceEnables; inherit runGroup; + inherit forceServiceConfigs; }