Modular, context-aware and aspect-oriented dendritic Nix configurations. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ den.oeiuwq.com
configurations den dendritic nix aspect oriented
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: provider sub-aspect functions receive parametric context (#419)

Fixes #413

### Problem

Provider sub-aspects defined as bare context functions don't receive `{
host }` during resolution:

```nix
den.aspects.foo._.sub = { host, ... }: {
nixos = lib.optionalAttrs (host.hostName != "whatever") {
networking.networkmanager.enable = true;
};
};
```

When a parent function returns `{ includes = [foo._.sub] }`, the
sub-aspect is resolved by the adapter system which only passes `{ class,
aspect-chain }` — the `{ host }` context from the pipeline never reaches
nested includes of function results.

### Fix

`applyDeep` in `parametric.applyIncludes` — when `takeFn` succeeds and
returns a bare result with sub-includes (no `meta`, no `__functor`),
also apply `takeFn` to those sub-includes. This propagates context to
provider sub-aspects nested inside function results without
double-applying to parametric wrappers or `deepRecurse` outputs.

The key discriminator: bare provider results carry only `includes` (+
`name` from `carryAttrs`). Results from `withOwn`/`withIdentity` have
`meta`; deferred `deepRecurse` wrappers have `__functor`. Neither should
be re-resolved.

### Test coverage

- `deadbugs/issue-413-provider-bare-function.nix` — reproduces the
original report
- `deadbugs/issue-413-provider-sub-aspect-function.nix` — variant with
`lib.optionalAttrs` guard
- `provides-parametric.nix` — provider sub-aspects with parametric
context in various configurations

cc: @kalyanoliveira for the report

---------

Co-authored-by: horyzon <kalyan.coliveira@gmail.com>

authored by

Jason Bowman
horyzon
and committed by
GitHub
afbe47f1 f6c6c432

+186 -1
+32 -1
nix/lib/parametric.nix
··· 20 20 } 21 21 // extra; 22 22 23 + # When takeFn succeeds and returns a result with sub-includes, 24 + # also try to resolve those sub-includes with takeFn. This handles 25 + # provider sub-aspect functions nested inside include results: 26 + # e.g. wrapped_fn returns { includes = [foo._.sub]; } where foo._.sub 27 + # needs parametric context applied before reaching the static pipeline. 28 + applyDeep = 29 + takeFn: ctx: fn: 30 + let 31 + r = takeFn fn ctx; 32 + # Bare provider results carry only includes (+ name from carryAttrs). 33 + # Results from withOwn/withIdentity have meta; deferred deepRecurse 34 + # wrappers have __functor. Re-resolving either would double-apply 35 + # context and duplicate modules. 36 + isBareResult = builtins.isAttrs r && r ? includes && !(r ? meta) && !(r ? __functor); 37 + in 38 + if r == { } then 39 + r 40 + else if isBareResult then 41 + r 42 + // { 43 + includes = map ( 44 + sub: 45 + let 46 + sr = takeFn sub ctx; 47 + in 48 + if sr != { } then sr else sub 49 + ) r.includes; 50 + } 51 + else 52 + r; 53 + 23 54 parametric.applyIncludes = 24 55 takeFn: aspect: 25 56 aspect ··· 27 58 __functor = 28 59 self: ctx: 29 60 withIdentity self { 30 - includes = builtins.filter (x: x != { }) (map (fn: takeFn fn ctx) (self.includes or [ ])); 61 + includes = builtins.filter (x: x != { }) (map (applyDeep takeFn ctx) (self.includes or [ ])); 31 62 }; 32 63 }; 33 64
+52
templates/ci/modules/features/deadbugs/issue-413-provider-bare-function.nix
··· 1 + # Provider sub-aspect as bare function with host context. 2 + # https://github.com/vic/den/pull/413 3 + { denTest, lib, ... }: 4 + { 5 + flake.tests.deadbugs-issue-413 = { 6 + test-provider-sub-aspect-bare-function = denTest ( 7 + { den, igloo, ... }: 8 + { 9 + den.hosts.x86_64-linux.igloo.users.tux = { }; 10 + 11 + imports = 12 + let 13 + a = { 14 + den.aspects.foo = 15 + { host, ... }: 16 + { 17 + includes = lib.optionals (host.foo._.sub.enable == true) [ 18 + den.aspects.foo._.sub 19 + ]; 20 + }; 21 + }; 22 + b = { 23 + den.schema.host.options.foo._.sub.enable = lib.mkEnableOption "sub-aspect toggle"; 24 + }; 25 + c = { 26 + den.hosts.x86_64-linux.igloo.foo._.sub.enable = true; 27 + }; 28 + d = { 29 + den.aspects.foo._.sub = 30 + { host, ... }: 31 + { 32 + nixos = lib.optionalAttrs (host.hostName != "whatever") { 33 + networking.networkmanager.enable = true; 34 + }; 35 + }; 36 + }; 37 + in 38 + [ 39 + a 40 + b 41 + c 42 + d 43 + ]; 44 + 45 + den.aspects.igloo.includes = [ den.aspects.foo ]; 46 + 47 + expr = igloo.networking.networkmanager.enable; 48 + expected = true; 49 + } 50 + ); 51 + }; 52 + }
+36
templates/ci/modules/features/deadbugs/issue-413-provider-sub-aspect-function.nix
··· 1 + # Simplified variant of issue-413 provider sub-aspect bug. 2 + # https://github.com/vic/den/pull/413 3 + { denTest, ... }: 4 + { 5 + flake.tests.deadbugs-issue-413 = { 6 + 7 + # Parametric parent unconditionally includes parametric sub 8 + test-parametric-parent-parametric-sub = denTest ( 9 + { den, igloo, ... }: 10 + { 11 + imports = [ 12 + { 13 + den.aspects.foo = 14 + { host, ... }: 15 + { 16 + includes = [ den.aspects.foo._.sub ]; 17 + }; 18 + } 19 + { 20 + den.aspects.foo._.sub = 21 + { host, ... }: 22 + { 23 + nixos.networking.networkmanager.enable = true; 24 + }; 25 + } 26 + ]; 27 + 28 + den.hosts.x86_64-linux.igloo.users.tux = { }; 29 + den.aspects.igloo.includes = [ den.aspects.foo ]; 30 + 31 + expr = igloo.networking.networkmanager.enable; 32 + expected = true; 33 + } 34 + ); 35 + }; 36 + }
+66
templates/ci/modules/features/provides-parametric.nix
··· 42 42 43 43 }; 44 44 45 + # Bare function sub-aspects receive parametric context from parent. 46 + flake.tests.provides-parametric-bare-fn = { 47 + 48 + test-bare-fn-sub-aspect-receives-host = denTest ( 49 + { 50 + den, 51 + igloo, 52 + ... 53 + }: 54 + { 55 + den.hosts.x86_64-linux.igloo.users.tux = { }; 56 + 57 + imports = [ 58 + { 59 + den.aspects.monitoring = 60 + { host, ... }: 61 + { 62 + includes = [ den.aspects.monitoring._.node-exporter ]; 63 + }; 64 + } 65 + { 66 + den.aspects.monitoring._.node-exporter = 67 + { host, ... }: 68 + { 69 + nixos.networking.hostName = "${host.name}-monitored"; 70 + }; 71 + } 72 + ]; 73 + 74 + den.aspects.igloo.includes = [ den.aspects.monitoring ]; 75 + 76 + expr = igloo.networking.hostName; 77 + expected = "igloo-monitored"; 78 + } 79 + ); 80 + 81 + test-static-parent-bare-fn-sub = denTest ( 82 + { 83 + den, 84 + igloo, 85 + ... 86 + }: 87 + { 88 + den.hosts.x86_64-linux.igloo.users.tux = { }; 89 + 90 + imports = [ 91 + { 92 + den.aspects.monitoring.includes = [ den.aspects.monitoring._.agent ]; 93 + } 94 + { 95 + den.aspects.monitoring._.agent = 96 + { host, ... }: 97 + { 98 + nixos.networking.hostName = "${host.name}-agent"; 99 + }; 100 + } 101 + ]; 102 + 103 + den.aspects.igloo.includes = [ den.aspects.monitoring ]; 104 + 105 + expr = igloo.networking.hostName; 106 + expected = "igloo-agent"; 107 + } 108 + ); 109 + }; 110 + 45 111 }