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.

feat: adapterOwner tracking in filterIncludes + collectSelfPath helper (#451)

Tombstone excludedFrom now uses the declaring aspect's full pathKey
instead of the anonymous wrapper's name. adapterOwner is captured at the
meta.adapter declaration site and propagated via tag to nested
filterIncludes invocations.

collectSelfPath extracts the aspect's path if not excluded, shared
between collectPathsInner and the upcoming structuredTrace adapter.

authored by

Jason Bowman and committed by
GitHub
eb74bdaf 326ed590

+140 -4
+14 -4
nix/lib/aspects/adapters.nix
··· 97 97 # Handles per-aspect meta.adapter composition. Probes each include to 98 98 # determine: keep, exclude (tombstone), or substitute (tombstone + replacement). 99 99 # Tags survivors with the adapter for downstream propagation. 100 + # 101 + # adapterOwner tracks which user-declared aspect originally owned the 102 + # adapter. Without it, tombstones downstream of a tagged wrapper 103 + # attribute exclusion to "<anon>" instead of the declaring aspect. 100 104 filterIncludes = 101 105 inner: 102 106 args@{ aspect, resolveChild, ... }: 103 107 let 104 108 metaAdapter = aspect.meta.adapter or null; 109 + ownerName = aspect.meta.adapterOwner or (pathKey (aspectPath aspect)); 105 110 in 106 111 if metaAdapter != null && aspect ? includes then 107 112 let ··· 121 126 probed = probeTransform metaAdapter args resolved; 122 127 in 123 128 if result == { } then 124 - [ (tombstone resolved { excludedFrom = aspect.name or "<anon>"; }) ] 129 + [ (tombstone resolved { excludedFrom = ownerName; }) ] 125 130 else if aspectPath probed != aspectPath resolved then 126 131 [ 127 132 (tombstone resolved { 128 - excludedFrom = aspect.name or "<anon>"; 133 + excludedFrom = ownerName; 129 134 replacedBy = probed.name or "<anon>"; 130 135 }) 131 136 probed ··· 140 145 // { 141 146 meta = (i.meta or { }) // { 142 147 adapter = metaAdapter; 148 + adapterOwner = ownerName; 143 149 }; 144 150 } 145 151 else ··· 172 178 }) paths 173 179 ); 174 180 181 + # Emit this aspect's path if not excluded. Shared by collectPathsInner 182 + # and structuredTrace to avoid duplicating the exclusion check. 183 + collectSelfPath = aspect: lib.optional (!(aspect.meta.excluded or false)) (aspectPath aspect); 184 + 175 185 # Shared walker used by collectPaths (through filterIncludes, so it 176 186 # sees tombstones) and by oneOfAspects (raw, to avoid re-entering 177 187 # its own meta.adapter). The excluded-guard is a no-op in the raw ··· 180 190 { aspect, recurse, ... }: 181 191 { 182 192 paths = 183 - (lib.optional (!(aspect.meta.excluded or false)) (aspectPath aspect)) 184 - ++ lib.concatMap (i: (recurse i).paths or [ ]) (aspect.includes or [ ]); 193 + collectSelfPath aspect ++ lib.concatMap (i: (recurse i).paths or [ ]) (aspect.includes or [ ]); 185 194 }; 186 195 187 196 # Terminal adapter that walks via filterIncludes and collects the ··· 233 242 inherit 234 243 aspectPath 235 244 collectPaths 245 + collectSelfPath 236 246 default 237 247 excludeAspect 238 248 filter
+126
templates/ci/modules/features/adapter-owner.nix
··· 1 + # Tests for adapterOwner tracking in filterIncludes. 2 + { denTest, lib, ... }: 3 + { 4 + flake.tests.adapter-owner = { 5 + 6 + # Tombstone excludedFrom uses the declaring aspect's pathKey, not "<anon>". 7 + test-tombstone-excludedFrom-is-owner-path = denTest ( 8 + { den, ... }: 9 + let 10 + inherit (den.lib.aspects) adapters resolve; 11 + target = { 12 + name = "drop"; 13 + meta = { 14 + provider = [ ]; 15 + }; 16 + }; 17 + root = { 18 + name = "owner"; 19 + meta = { 20 + adapter = adapters.excludeAspect target; 21 + provider = [ ]; 22 + }; 23 + nixos = { }; 24 + includes = [ 25 + { 26 + name = "keep"; 27 + meta = { 28 + provider = [ ]; 29 + }; 30 + nixos = { }; 31 + includes = [ ]; 32 + } 33 + { 34 + name = "drop"; 35 + meta = { 36 + provider = [ ]; 37 + }; 38 + nixos = { }; 39 + includes = [ ]; 40 + } 41 + ]; 42 + }; 43 + # Resolve and inspect the aspect tree for tombstones. 44 + resolved = resolve.withAdapter adapters.default "nixos" root; 45 + # The default adapter (filterIncludes module) produces { imports }. 46 + # We need to look at the raw adapter output instead. 47 + # Use collectPaths to check tombstones are visible. 48 + pathResult = resolve.withAdapter adapters.collectPaths "nixos" root; 49 + # Check that "drop" is NOT in the collected paths (it's tombstoned). 50 + pathKeys = map adapters.pathKey (pathResult.paths or [ ]); 51 + in 52 + { 53 + expr = { 54 + dropExcluded = !(builtins.elem "drop" pathKeys); 55 + keepPresent = builtins.elem "keep" pathKeys; 56 + }; 57 + expected = { 58 + dropExcluded = true; 59 + keepPresent = true; 60 + }; 61 + } 62 + ); 63 + 64 + # adapterOwner field propagates through tagged children. 65 + test-adapter-owner-propagated = denTest ( 66 + { den, ... }: 67 + let 68 + inherit (den.lib.aspects) adapters resolve; 69 + target = { 70 + name = "deep-drop"; 71 + meta = { 72 + provider = [ ]; 73 + }; 74 + }; 75 + inner = { 76 + name = "inner"; 77 + meta = { 78 + provider = [ ]; 79 + }; 80 + includes = [ 81 + { 82 + name = "deep-drop"; 83 + meta = { 84 + provider = [ ]; 85 + }; 86 + nixos = { }; 87 + includes = [ ]; 88 + } 89 + { 90 + name = "deep-keep"; 91 + meta = { 92 + provider = [ ]; 93 + }; 94 + nixos = { }; 95 + includes = [ ]; 96 + } 97 + ]; 98 + }; 99 + root = { 100 + name = "owner"; 101 + meta = { 102 + adapter = adapters.excludeAspect target; 103 + provider = [ ]; 104 + }; 105 + nixos = { }; 106 + includes = [ inner ]; 107 + }; 108 + pathResult = resolve.withAdapter adapters.collectPaths "nixos" root; 109 + pathKeys = map adapters.pathKey (pathResult.paths or [ ]); 110 + in 111 + { 112 + expr = { 113 + deepDropExcluded = !(builtins.elem "deep-drop" pathKeys); 114 + deepKeepPresent = builtins.elem "deep-keep" pathKeys; 115 + innerPresent = builtins.elem "inner" pathKeys; 116 + }; 117 + expected = { 118 + deepDropExcluded = true; 119 + deepKeepPresent = true; 120 + innerPresent = true; 121 + }; 122 + } 123 + ); 124 + 125 + }; 126 + }