···11+# AI Agent Instructions: Mastering Nix Organization with Den
22+33+> You are an AI assistant helping users achieve the best possible Nix configuration
44+> organization using **Den** — the aspect-oriented, context-driven Dendritic Nix framework.
55+> Follow these instructions precisely and completely.
66+77+---
88+99+## Nix Style Guidelines
1010+1111+Prefer using the following style for modules:
1212+1313+```nix
1414+{ den, lib, inputs, ... }:
1515+let
1616+ inherit (den.lib) perHost;
1717+1818+ foo = { host }: {
1919+ nixos = ...;
2020+ };
2121+in
2222+{
2323+ den.aspects.igloo.includes = [
2424+ (perHost foo)
2525+ ];
2626+}
2727+```
2828+2929+Use let-bindings, and keep the last attribute set definition short.
3030+3131+Avoid using inlined anonymous functions at includes. Prefer aspects bound in let or previously assigned to den.aspects.* or any other aspect namespace.
3232+3333+Aspects must be short, focused on re-usability
3434+and only ONE concern across different Nix classes.
3535+3636+3737+## Actively Use Den Sources.
3838+3939+Locate Den source code from the flake inputs.
4040+4141+Actively read Den source code and documentation. Docs are
4242+under ./docs and Den also provides CI tests that serve as
4343+examples of every Den feature under templates/ci/modules,
4444+explore all the codebase to find our what is possible with
4545+Den.
4646+4747+When building always use --show-trace to
4848+see the full trace of the evaluation.
4949+5050+Agents are encouraged to use nix eval to investigate what expressions evaluate to, they also can use it to investigate locations of files from the nix store that need to be loaded into context. Never start interactive repl, only use nix eval for exploration.
5151+5252+When not sure about how to do something always refer to Den source code and CI examples. Den source and documentation is the ultimate source of truth regarding Den capabilities and practices.
5353+5454+5555+## 0. Foundational Mental Model
5656+5757+Before writing a single line of Nix, internalize these axioms:
5858+5959+1. **Features first, hosts second.** Never organize by machine. Organize by concern.
6060+ A "bluetooth" aspect knows how to configure NixOS AND HomeManager. Hosts just
6161+ declare which aspects they include.
6262+2. **Context IS the condition.** Never write `mkIf`, never write `enable = true` guards
6363+ inside aspects. A function that takes `{ host, user }` is automatically skipped in
6464+ `{ host }`-only contexts. The argument signature IS the conditional.
6565+3. **Aspects are functions.** A Dendritic aspect is a Nix function returning per-class
6666+ configs. This makes them composable, type-checkable, and parametric.
6767+4. **Everything is optional and incremental.** Den works with/without flakes,
6868+ with/without flake-parts. Migrate one host at a time.
6969+5. **Testable Aspects** When writing generic re-usable aspects always create Tests for them, see Den own templates/ci for how tests are structured and their test-support files.
7070+7171+---
7272+7373+## 1. Repository / File Layout (Always)
7474+7575+This structure is not mandatory, directory and file naming
7676+in Den serves as documentation, Den does not care about
7777+file location, it is important for the User since paths
7878+effectively document features and semantics.
7979+8080+```
8181+flake.nix ← or default.nix for noflake
8282+modules/
8383+ den.nix ← ONLY file declaring den.flakeModule import + global policy
8484+ hosts.nix ← den.hosts declarations
8585+ users.nix ← den.aspects for users (or split per user)
8686+ schema.nix ← den.schema.{host,user,home,conf}
8787+ defaults.nix ← den.default global configuration
8888+ aspects/
8989+ bluetooth.nix ← one file per cross-cutting concern
9090+ gaming.nix
9191+ dev-tools.nix
9292+ tiling-wm.nix
9393+ ...
9494+ _nixos/ ← legacy NixOS modules (underscore = import-tree ignores)
9595+ _darwin/ ← legacy Darwin modules
9696+```
9797+9898+**Rules:**
9999+- Every file under `modules/` (without `_` prefix) is auto-loaded by `import-tree`.
100100+- Never manually list imports. Create files. Den finds them.
101101+- Use `_` prefix for non-Dendritic NixOS module directories.
102102+- One concern per file. Never monolithic configs.
103103+- Any file can configure any module, incrementally.
104104+- Use file organization to your advantage.
105105+106106+---
107107+108108+## 2. Declaring Hosts and Users
109109+110110+### 2.1 Host Declaration Pattern
111111+112112+Always declare hosts with full metadata:
113113+114114+```nix
115115+# modules/hosts.nix
116116+{ ... }: {
117117+ den.hosts.x86_64-linux.laptop = {
118118+ users.alice = {};
119119+ users.bob.classes = [ "homeManager" "hjem" ];
120120+ gpu = "nvidia"; # freeform metadata readable by aspects
121121+ roles = [ "devops" ]; # custom metadata for role-based dispatch
122122+ };
123123+124124+ den.hosts.aarch64-darwin.mac = {
125125+ users.alice = {};
126126+ };
127127+}
128128+```
129129+130130+**Host schema options to set explicitly:**
131131+- `hostName` — if different from the attrset key
132132+- `users.<name>.classes` — always set explicitly, never rely on defaults blindly
133133+- `users.<name>.roles` — add custom roles for role-based class dispatch
134134+- Any freeform attribute becomes readable as `host.<attr>` in aspects
135135+136136+### 2.2 Standalone Homes
137137+138138+```nix
139139+den.homes.x86_64-linux.alice = {};
140140+den.homes.aarch64-darwin.alice = {};
141141+```
142142+143143+### 2.3 Schema — Shared Metadata for ALL Entities
144144+145145+Always define shared options via `den.schema.*` instead of repeating per host:
146146+147147+```nix
148148+# modules/schema.nix
149149+{ lib, ... }: {
150150+ den.schema.host = { host, lib, ... }: {
151151+ options.hardened = lib.mkEnableOption "hardened profile";
152152+ options.roles = lib.mkOption { default = []; type = lib.types.listOf lib.types.str; };
153153+ config.hardened = lib.mkDefault false;
154154+ };
155155+156156+ den.schema.user = { user, lib, ... }: {
157157+ options.roles = lib.mkOption { default = []; type = lib.types.listOf lib.types.str; };
158158+ config.classes = lib.mkDefault [ "homeManager" ];
159159+ };
160160+161161+ den.schema.conf = { lib, ... }: {
162162+ options.copyright = lib.mkOption { default = "Copy-Left"; };
163163+ };
164164+}
165165+```
166166+167167+---
168168+169169+## 3. Global Defaults — `den.default`
170170+171171+Keep defaults simple, the intention of it is not to be
172172+overloaded with lots of logic but to serve as defaults
173173+for truly global settings and fully parametric aspects.
174174+175175+```nix
176176+# modules/defaults.nix
177177+{ den, ... }: {
178178+ den.default = {
179179+ nixos.system.stateVersion = "25.11";
180180+ homeManager.home.stateVersion = "25.11";
181181+ darwin.system.stateVersion = 5;
182182+ includes = [
183183+ den.provides.define-user # sets users.users.<name> + home dirs
184184+ den.provides.hostname # sets networking.hostName
185185+ den.provides.inputs' # exposes flake-parts inputs' to all modules
186186+ ];
187187+ };
188188+}
189189+```
190190+191191+**Caution:** Owned configs from `den.default` are deduplicated. Parametric functions
192192+in `den.default.includes` run at EVERY context stage. Use `den.lib.take.exactly`
193193+to restrict to specific context shapes.
194194+195195+---
196196+197197+## 4. Aspect Authoring Rules
198198+199199+### 4.1 Anatomy of a Perfect Aspect
200200+201201+```nix
202202+# modules/aspects/bluetooth.nix
203203+{ den, ... }: {
204204+ den.aspects.bluetooth = {
205205+ # Owned configs per class — always prefer attrset form for static data
206206+ nixos.hardware.bluetooth.enable = true;
207207+ nixos.hardware.bluetooth.powerOnBoot = true;
208208+ darwin.services.blueutil.enable = true;
209209+210210+ # os class = applies to BOTH nixos and darwin (built-in Den class)
211211+ # os.some.option = ...;
212212+213213+ # Home Manager owned config
214214+ homeManager.services.blueman-applet.enable = true;
215215+216216+ # Depends on other aspects
217217+ includes = [
218218+ den.aspects.pipewire # full DAG is pulled in
219219+ den.aspects.bluetooth.provides.applet # sub-aspect
220220+ ];
221221+222222+ # Sub-aspects / features within this concern
223223+ provides.applet = {
224224+ homeManager.services.blueman-applet.enable = true;
225225+ };
226226+227227+ # Parametric provides — context-aware
228228+ provides.headset = { host, user, ... }:
229229+ { homeManager.services.easyeffects.enable = host.class == "nixos"; };
230230+ };
231231+}
232232+```
233233+234234+### 4.2 Three Kinds of Includes (know them all)
235235+236236+| Kind | Example | When runs |
237237+|------|---------|-----------|
238238+| Static attrset | `{ nixos.foo = 1; }` | Always, unconditionally |
239239+| Static leaf | `{ class, aspect-chain }: { ${class}.foo = 1; }` | Once, gets class name |
240240+| Parametric | `{ host, user }: { ... }` | Only when context matches args |
241241+242242+### 4.3 Named Aspects Anti-Pattern Warning
243243+244244+**NEVER** inline anonymous functions in `includes`. Always name them:
245245+246246+```nix
247247+# BAD — anonymous, hard to debug
248248+den.aspects.laptop.includes = [ ({ host }: { nixos.networking.hostName = host.hostName; }) ];
249249+250250+# GOOD — named aspect, proper error traces
251251+den.aspects.set-hostname = { host, ... }: { nixos.networking.hostName = host.hostName; };
252252+den.aspects.laptop.includes = [ den.aspects.set-hostname ];
253253+```
254254+255255+### 4.4 Parametric Dispatch Variants
256256+257257+Use the right parametric constructor:
258258+259259+| Constructor | Use case |
260260+|-------------|----------|
261261+| `den.lib.parametric` | Default. Owned + statics + atLeast-matching functions |
262262+| `den.lib.parametric.atLeast` | Only parametric functions, no owned/statics |
263263+| `den.lib.parametric.exactly` | Only exact-match context functions |
264264+| `den.lib.parametric.fixedTo attrs` | Always use given attrs as context |
265265+| `den.lib.parametric.expands attrs` | Extend received context before dispatch |
266266+| `den.lib.perHost` | Wrap an aspect with exatly {host} |
267267+| `den.lib.perUser` | Wrap an aspect with exatly {host,user} |
268268+| `den.lib.perHome` | Wrap an aspect with exatly {home} |
269269+270270+### 4.5 Context-Aware Function Signatures
271271+272272+```nix
273273+# Runs for ANY context (host, user, home)
274274+{ nixos.networking.firewall.enable = true; }
275275+276276+# Runs only in {host} contexts (atLeast: host present)
277277+({ host, ... }: { nixos.networking.hostName = host.hostName; })
278278+279279+# Runs ONLY in {host, user} contexts (atLeast)
280280+({ host, user, ... }: { nixos.users.users.${user.userName}.extraGroups = ["wheel"]; })
281281+282282+# Runs ONLY in exactly {host} — use take.exactly to prevent user context calls
283283+# Prefer: den.lib.perHost
284284+(den.lib.take.exactly ({ host }: { nixos.x = host.hostName; }))
285285+286286+# Runs ONLY in exactly {host, user}
287287+# Prefer: den.lib.perUser
288288+(den.lib.take.exactly ({ host, user }: { nixos.y = user.userName; }))
289289+290290+# Runs only for standalone {home} context
291291+# Prefer den.lib.perHome
292292+({ home }: { homeManager.home.username = home.userName; })
293293+```
294294+295295+---
296296+297297+## 5. Bidirectional Configuration (Critical Advanced Feature)
298298+299299+Den supports two patterns for Host↔User bidirectional configuration.
300300+Read `https://den.oeiuwq.com/guides/bidirectional` and apply these rules:
301301+302302+### 5.1 Built-in Bidirectionality (`den._.bidirectional`)
303303+304304+This makes a HOST contribute configuration TO its users' home environments.
305305+Enable per-user or globally:
306306+307307+```nix
308308+# Only for specific user
309309+den.aspects.alice.includes = [ den._.bidirectional ];
310310+311311+# For ALL users (recommended for host-provides-home-env patterns)
312312+den.ctx.user.includes = [ den._.bidirectional ];
313313+```
314314+315315+**How it works:** When bidirectionality is enabled, the user pipeline calls
316316+`igloo.includes` TWICE: once with `{host}` (for host-only config) and once
317317+with `{host, user}` (so the host can contribute to THIS specific user's home).
318318+319319+**Mandatory: use take guards** in `igloo.includes` to handle dual invocation:
320320+321321+```nix
322322+# In igloo.includes — only runs for host-level context
323323+(den.lib.take.exactly ({ host }: { nixos.networking.hostName = host.hostName; }))
324324+325325+# In igloo.includes — only runs when user is present (host→user home contribution)
326326+(den.lib.take.atLeast ({ host, user }: { homeManager.programs.vim.enable = true; }))
327327+```
328328+329329+This pattern is ideal when: a host wants to provide a common home environment
330330+to ALL of its users (e.g., all users on `igloo` get `vim` configured in their home).
331331+332332+### 5.2 Explicit Mutual Provider (`den._.mutual-provider`)
333333+334334+For EXPLICIT named host↔user pairings. More verbose, more precise:
335335+336336+```nix
337337+# Enable for everything
338338+den.default.includes = [ den._.mutual-provider ];
339339+340340+# Host igloo contributes TO user tux specifically
341341+den.aspects.igloo.provides.tux = { user, ... }: {
342342+ homeManager.programs.helix.enable = true;
343343+};
344344+345345+# User tux contributes TO host igloo specifically
346346+den.aspects.tux.provides.igloo = { host, ... }: {
347347+ nixos.programs.nh.enable = true;
348348+};
349349+```
350350+351351+### 5.3 When to Use Which
352352+353353+| Pattern | Use when |
354354+|---------|----------|
355355+| Built-in bidirectionality | Host provides common env to ALL its users |
356356+| `mutual-provider` | Explicit, named host↔user pair configurations |
357357+| User `nixos` class | User always contributes to any host it's on |
358358+| Host `homeManager` class | Host always contributes to all user homes |
359359+360360+---
361361+362362+## 6. All Built-in Batteries — Use Them All
363363+364364+Always prefer batteries over manual repetition:
365365+366366+```nix
367367+den.default.includes = [
368368+ den.provides.define-user # OS user accounts + home dirs
369369+ den.provides.hostname # sets networking.hostName
370370+ den.provides.inputs' # flake-parts inputs' in all modules
371371+ den.provides.self' # flake-parts self' in all modules
372372+];
373373+374374+# Per user
375375+den.aspects.alice.includes = [
376376+ den.provides.primary-user # wheel, networkmanager, isNormalUser
377377+ (den.provides.user-shell "fish") # shell at OS + HM level
378378+ (den.provides.unfree ["vscode" "spotify"]) # unfree predicate
379379+ (den.provides.tty-autologin "alice") # TTY1 auto-login
380380+];
381381+382382+# For WSL hosts
383383+den.hosts.x86_64-linux.wsl-machine = {
384384+ wsl.enable = true; # activates den.ctx.wsl-host automatically
385385+};
386386+```
387387+388388+Battery reference:
389389+- `define-user` — creates `users.users.<name>` with `isNormalUser`, `home`
390390+- `hostname` — sets `networking.hostName` from `host.hostName`
391391+- `primary-user` — NixOS: `wheel`+`networkmanager`; Darwin: `system.primaryUser`; WSL: `defaultUser`
392392+- `user-shell "bash"|"fish"|"zsh"` — OS shell + HM `programs.<shell>.enable`
393393+- `unfree [...]` — `nixpkgs.config.allowUnfreePredicate` for named packages
394394+- `tty-autologin "user"` — `services.getty.autologinUser`
395395+- `mutual-provider` — explicit named host↔user cross-config
396396+- `bidirectional` — host contributes to user homes in pipeline
397397+- `forward` — creates custom Nix classes (see §7)
398398+- `import-tree` — auto-imports legacy non-dendritic `.nix` directories
399399+- `inputs'` — flake-parts system-qualified inputs
400400+- `self'` — flake-parts system-qualified self
401401+402402+Explore their source code, create new re-usable aspects that can serve without any hardcoded user or host value. Den is about re-usability, use `modules/community/<namespace>` to place aspects under namespace that can be re-used by local infra and outside the flake.
403403+404404+---
405405+406406+## 7. Custom Nix Classes — The `forward` Battery
407407+408408+Whenever you need a new abstraction that maps to a subpath of an existing class,
409409+create a custom class with `den.provides.forward`:
410410+411411+### 7.1 The `os` class (cross-platform, built into Den)
412412+413413+```nix
414414+# Already built-in. Use it for settings applying to BOTH nixos and darwin:
415415+den.aspects.my-laptop.os.networking.hostName = "Yavanna";
416416+```
417417+418418+### 7.2 Role-based class (dynamic dispatch)
419419+420420+```nix
421421+# modules/role-class.nix
422422+{ den, lib, ... }:
423423+let
424424+ roleClass = { host, user }: { class, aspect-chain }:
425425+ den._.forward {
426426+ each = lib.intersectLists (host.roles or []) (user.roles or []);
427427+ fromClass = lib.id;
428428+ intoClass = _: host.class;
429429+ intoPath = _: [];
430430+ fromAspect = _: lib.head aspect-chain;
431431+ };
432432+in {
433433+ den.ctx.user.includes = [ roleClass ];
434434+}
435435+```
436436+437437+### 7.3 Impermanence class with guard
438438+439439+```nix
440440+# modules/persys-class.nix
441441+{ den, lib, ... }:
442442+let
443443+ persys = { class, aspect-chain }: den._.forward {
444444+ each = lib.singleton true;
445445+ fromClass = _: "persys";
446446+ intoClass = _: class;
447447+ intoPath = _: [ "environment" "persistance" "/nix/persist/system" ];
448448+ fromAspect = _: lib.head aspect-chain;
449449+ guard = { options, ... }: options ? environment.persistance;
450450+ };
451451+in {
452452+ den.ctx.host.includes = [ persys ];
453453+ # Aspects just use the class, guard ensures safety
454454+ # den.aspects.laptop.persys.hideMounts = true;
455455+}
456456+```
457457+458458+### 7.4 git class forwarding into home-manager
459459+460460+```nix
461461+{ den, lib, ... }:
462462+let
463463+ gitClass = { class, aspect-chain }: den._.forward {
464464+ each = lib.singleton true;
465465+ fromClass = _: "git";
466466+ intoClass = _: "homeManager";
467467+ intoPath = _: [ "programs" "git" ];
468468+ fromAspect = _: lib.head aspect-chain;
469469+ adaptArgs = lib.id;
470470+ };
471471+in {
472472+ den.ctx.user.includes = [ gitClass ];
473473+}
474474+# Usage: den.aspects.alice.git.userEmail = "alice@example.com";
475475+```
476476+477477+**forward parameters reference:**
478478+- `each` — list of items to iterate (users, roles, `lib.singleton true`)
479479+- `fromClass` — source class name to read from
480480+- `intoClass` — target class to write into
481481+- `intoPath` — attribute path in target class
482482+- `fromAspect` — which aspect to read
483483+- `guard` — `{ options, config, ... } -> bool` — only forward if true
484484+- `adaptArgs` — transform module args before forwarding
485485+- `adapterModule` — custom module for forwarded submodule type
486486+487487+---
488488+489489+## 8. Home Environment Integration
490490+491491+### 8.1 Best Practice: Declare classes explicitly
492492+493493+```nix
494494+# Per user
495495+den.hosts.x86_64-linux.laptop.users.alice.classes = [ "homeManager" ];
496496+den.hosts.x86_64-linux.laptop.users.bob.classes = [ "hjem" ];
497497+498498+# Global default via schema
499499+den.schema.user.classes = lib.mkDefault [ "homeManager" ];
500500+```
501501+502502+### 8.2 Configure homes in user aspects
503503+504504+```nix
505505+den.aspects.alice = {
506506+ homeManager = { pkgs, ... }: {
507507+ home.packages = [ pkgs.htop pkgs.ripgrep ];
508508+ programs.git.enable = true;
509509+ programs.starship.enable = true;
510510+ };
511511+512512+ hjem.files.".envrc".text = "use flake ~/proj";
513513+514514+ # User contributing OS config to any host it lives on
515515+ nixos.users.users.alice.extraGroups = [ "docker" ];
516516+517517+ # User contributing to Darwin hosts specifically
518518+ darwin.services.karabiner-elements.enable = true;
519519+};
520520+```
521521+522522+### 8.3 Host contributing home config to users (bidirectional)
523523+524524+```nix
525525+# Host igloo provides vim to ALL its users' home environments
526526+den.aspects.igloo = {
527527+ homeManager.programs.vim.enable = true; # goes to ALL users on igloo
528528+};
529529+den.ctx.user.includes = [ den._.bidirectional ];
530530+```
531531+532532+### 8.4 Multiple home environments per user
533533+534534+```nix
535535+den.hosts.x86_64-linux.laptop.users.alice.classes = [ "homeManager" "hjem" ];
536536+den.aspects.alice = {
537537+ homeManager = { pkgs, ... }: { home.packages = [ pkgs.vim ]; };
538538+ hjem.files.".bashrc".text = "# managed by hjem";
539539+};
540540+```
541541+542542+---
543543+544544+## 9. Namespaces — Sharing Aspect Libraries
545545+546546+### 9.1 Create and export a namespace
547547+548548+```nix
549549+# modules/namespace.nix
550550+{ inputs, den, ... }: {
551551+ imports = [ (inputs.den.namespace "myorg" true) ]; # true = export
552552+553553+ # Populate
554554+ myorg.bluetooth = { nixos.hardware.bluetooth.enable = true; };
555555+ myorg.gaming = {
556556+ includes = [ myorg.bluetooth ];
557557+ nixos.programs.steam.enable = true;
558558+ };
559559+}
560560+```
561561+562562+### 9.2 Import upstream namespaces
563563+564564+```nix
565565+imports = [ (inputs.den.namespace "shared" [ inputs.team-config ]) ];
566566+# Now: shared.* contains merged aspects from upstream
567567+```
568568+569569+### 9.3 Enable angle bracket syntax
570570+571571+Angle bracket syntax is recommended for large, deep provides hierarchies.
572572+573573+```nix
574574+{ den, ... }: {
575575+ _module.args.__findFile = den.lib.__findFile;
576576+}
577577+```
578578+579579+Then use `<aspect>`, `<aspect/sub>`, `<namespace>`, `<den.provides.battery>`:
580580+581581+```nix
582582+den.aspects.laptop.includes = [
583583+ <tools/editors>
584584+ <alice/work-vpn>
585585+ <myorg/gaming>
586586+ <den.provides.primary-user>
587587+];
588588+```
589589+590590+---
591591+592592+## 10. Custom Context Types — Extending the Pipeline
593593+594594+```nix
595595+# modules/gpu-context.nix
596596+{ den, lib, ... }: {
597597+ den.ctx.gpu-host = {
598598+ description = "GPU-accelerated host";
599599+ _.gpu-host = { host }: { nixos.hardware.nvidia.enable = true; };
600600+ };
601601+602602+ den.ctx.host.into.gpu-host = { host }:
603603+ lib.optional (host ? gpu) { inherit host; };
604604+}
605605+606606+# Usage: just set host.gpu = "nvidia" in hosts.nix
607607+# The context activates automatically.
608608+```
609609+610610+---
611611+612612+## 11. Migration Strategy (Incremental)
613613+614614+If the user has an existing configuration:
615615+616616+1. **Add Den input** to `flake.nix` + import `inputs.den.flakeModule` in one module.
617617+2. **Declare hosts** in `den.hosts` matching existing `nixosConfigurations` names.
618618+3. **Use `import-tree` battery** to load existing NixOS module directories:
619619+ ```nix
620620+ den.ctx.host.includes = [ (den.provides.import-tree._.host ./hosts) ];
621621+ den.ctx.user.includes = [ (den.provides.import-tree._.user ./users) ];
622622+ ```
623623+4. **Extract one concern at a time** into aspects. Start with the most reused feature.
624624+5. **Replace batteries** for manual patterns: `define-user`, `primary-user`, `user-shell`.
625625+6. **Remove legacy** files as aspects cover them.
626626+627627+Never big-bang rewrite. Always keep the build green.
628628+629629+---
630630+631631+## 12. Debugging Checklist
632632+633633+```nix
634634+# Step 1: Expose den for REPL inspection (remove after)
635635+{ den, ... }: { flake.den = den; }
636636+```
637637+638638+```console
639639+nix repl
640640+:lf .
641641+den.aspects.laptop # inspect aspect
642642+den.hosts.x86_64-linux.laptop # inspect host metadata
643643+den.ctx # inspect context pipeline
644644+nixosConfigurations.laptop.config.networking.hostName # verify output
645645+```
646646+647647+```nix
648648+# Step 2: Trace context in an aspect
649649+den.aspects.laptop.includes = [
650650+ ({ host, ... }@ctx: builtins.trace ctx { nixos.networking.hostName = host.hostName; })
651651+];
652652+```
653653+654654+```console
655655+# Step 3: Manually resolve an aspect
656656+nix-repl> aspect = den.aspects.laptop { host = den.hosts.x86_64-linux.laptop; }
657657+nix-repl> module = den.lib.aspects.resolve "nixos" [] aspect
658658+```
659659+660660+**Common issues and fixes:**
661661+- **Duplicate list values** → use `den.lib.take.exactly` in `den.default.includes`
662662+- **Wrong class** → Darwin is `"darwin"` not `"nixos"`, check `host.class`
663663+- **Bidirectional double-invocation** → add `take.exactly`/`take.atLeast` guards
664664+- **Module not found** → remove `_` prefix, or move out of `_nixos/` directory
665665+- **Bidirectional with `{host}` only** → `igloo.includes` called with `{host}` only for OS; add `take.atLeast` for user-context calls
666666+667667+---
668668+669669+## 13. Complete Optimal Structure (Reference Implementation)
670670+671671+```
672672+modules/
673673+ den.nix ← imports den.flakeModule, namespace setup, __findFile
674674+ schema.nix ← den.schema.{host,user,home,conf} with typed options
675675+ defaults.nix ← den.default with stateVersion + global batteries
676676+ hosts.nix ← ALL den.hosts declarations with metadata
677677+ aspects/
678678+ # Cross-cutting concerns (no host/user specifics here)
679679+ bluetooth.nix
680680+ gaming.nix
681681+ dev-tools.nix
682682+ tiling-wm.nix
683683+ networking.nix
684684+ security.nix
685685+ # Platform-specific features
686686+ darwin-specific.nix
687687+ # Custom classes
688688+ role-class.nix
689689+ persys-class.nix
690690+ git-class.nix
691691+ users/
692692+ alice.nix ← den.aspects.alice with homeManager/hjem/user/includes
693693+ bob.nix ← den.aspects.bob
694694+ hosts/
695695+ laptop.nix ← den.aspects.laptop with nixos/darwin/os/includes
696696+ server.nix ← den.aspects.server
697697+ mac.nix ← den.aspects.mac
698698+```
699699+700700+Each file contributes ONLY to `den.aspects.<name>` for that concern.
701701+Any file can contribute to any aspect. No centralized wiring.
702702+703703+---
704704+705705+## 14. Quality Checklist for AI Agents
706706+707707+Before submitting any Den configuration, verify:
708708+709709+- [ ] No `mkIf` used inside aspects — context dispatch handles conditions
710710+- [ ] No anonymous functions in `includes` — all are named aspects
711711+- [ ] `den.schema.*` defines shared options instead of repeating per-host
712712+- [ ] `den.default` sets `stateVersion` for all classes
713713+- [ ] All batteries used instead of manual equivalents
714714+- [ ] `bidirectional` or `mutual-provider` used for host↔user cross-config
715715+- [ ] `take.exactly`/`take.atLeast` guards on bidirectional aspects
716716+- [ ] One file per concern in `modules/aspects/`
717717+- [ ] Non host/user specific re-usable aspects in `modules/community/<namespace>/`
718718+- [ ] No monolithic `configuration.nix` style files
719719+- [ ] Custom metadata in `host.roles`, `host.gpu`, etc. drives parametric dispatch
720720+- [ ] `den.provides.import-tree` used for any legacy non-Dendritic modules
721721+- [ ] Namespaces used for any shared aspect libraries
722722+- [ ] Angle brackets enabled and used for concise references
723723+- [ ] All custom classes use `guard` when depending on optional modules
724724+- [ ] CI tests referenced at `templates/ci/modules/features/` for every feature used
725725+726726+---
727727+728728+## 15. Key Source References
729729+730730+All source truth lives at:
731731+- `https://den.oeiuwq.com` — official documentation
732732+- `https://den.oeiuwq.com/guides/bidirectional` — bidirectional guide, Bidirectionality is an advanced feature not recommended.
733733+- `https://github.com/vic/den/tree/main/templates/ci/modules/features/` — executable feature tests (best learning resource)
734734+- `https://github.com/vic/den/tree/main/modules/aspects/provides/` — all batteries source
735735+- `https://github.com/vic/flake-aspects` — underlying aspect library
736736+- `https://dendrix.oeiuwq.com/Dendritic.html` — Dendritic design advantages
737737+738738+When unsure about any Den feature, consult `templates/ci/modules/features/<feature>.nix`
739739+for a working, tested, executable example. These tests ARE the specification.
740740+741741+``` [1](#0-0) [2](#0-1) [3](#0-2) [4](#0-3) [5](#0-4) [6](#0-5) [7](#0-6) [8](#0-7) [9](#0-8) [10](#0-9) [11](#0-10) [12](#0-11) [13](#0-12) [14](#0-13) [15](#0-14) [16](#0-15) [17](#0-16) [18](#0-17) [19](#0-18) [20](#0-19) [21](#0-20)
742742+743743+### Citations
744744+745745+**File:** docs/src/content/docs/explanation/core-principles.mdx (L9-36)
746746+```text
747747+748748+<Aside title="Recommended Read">
749749+[Flipping the Configuration Matrix](https://not-a-number.io/2025/refactoring-my-infrastructure-as-code-configurations/#flipping-the-configuration-matrix) by [Pol Dellaiera](https://github.com/drupol) was very influential in Den design and is a very recommended read.
750750+751751+See also my [dendrix article](https://dendrix.oeiuwq.com/Dendritic.html) about the advantages of Dendritic Nix.
752752+</Aside>
753753+754754+Traditional Nix configurations start from hosts and push modules downward.
755755+Den follows a Dendritic model that inverts this: **aspects** (features) are the primary organizational unit.
756756+Each aspect declares its behavior per Nix class, and hosts simply select which
757757+aspects apply to them.
758758+759759+```mermaid
760760+flowchart BT
761761+ subgraph "Aspect: bluetooth"
762762+ nixos["nixos: hardware.bluetooth.enable = true"]
763763+ hm["homeManager: services.blueman-applet.enable = true"]
764764+ end
765765+ nixos --> laptop
766766+ nixos --> desktop
767767+ hm --> laptop
768768+ hm --> desktop
769769+```
770770+771771+An aspect consolidates all class-specific configuration for a single concern.
772772+Adding bluetooth to a new host is one line: include the aspect.
773773+Removing it is deleting that line.
774774+775775+```
776776+777777+**File:** docs/src/content/docs/guides/bidirectional.mdx (L60-153)
778778+```text
779779+## What Bidirectionality means
780780+781781+__Bidirectionality__ means that not only a User contributes
782782+configuration to a Host, but **also** that a Host contributes
783783+configurations to a User.
784784+785785+This is useful when the Host wishes to provide a
786786+commmon home environment for its users.
787787+788788+## `den.provides.bidirectional`
789789+790790+Bidirectionality is enabled __per-user__ or for _all_ of them.
791791+792792+```nix
793793+# only tux takes configurations from its hosts
794794+den.aspects.tux.includes = [ den._.bidirectional ];
795795+796796+# for ALL users
797797+den.ctx.user.includes = [ den._.bidirectional ];
798798+```
799799+800800+When Bidirectionality is enabled, the interaction looks like this:
801801+802802+```mermaid
803803+sequenceDiagram
804804+ participant Den
805805+ participant host as den.ctx.host
806806+ participant user as den.ctx.user
807807+ participant igloo as den.aspects.igloo
808808+ participant tux as den.aspects.tux
809809+810810+ Den ->> host : {host = igloo}
811811+812812+ host ->> igloo : request nixos class
813813+ igloo -->> igloo : each igloo.includes takes { host }
814814+ igloo -->> host : { nixos = ... } owned and parametric results
815815+816816+ host ->> user : fan-outs for each user: { host, user }
817817+818818+ user ->> tux : request nixos class
819819+820820+ tux ->> igloo : request home class
821821+ igloo -->> igloo : each igloo.includes takes { host, user }
822822+ igloo -->> tux : { hjem = ... } owned and parametric results
823823+824824+ tux -->> tux : home classes forwarded as nixos class
825825+826826+ tux -->> tux : each tux.includes takes { host, user }
827827+ tux -->> user : { nixos = ... } owned and parametric results
828828+829829+ user -->> host : { nixos = ... } all user contributions
830830+831831+ host -->> Den : complete nixos module for lib.nixosSystem
832832+833833+```
834834+835835+Crucial points here are `igloo.includes takes { host }` and `igloo.includes takes { host, user }`.
836836+837837+Because the list of aspects at `igloo.includes` get invoked twice, with different contexts,
838838+functions at `igloo.includes` must take care of the following:
839839+840840+```nix
841841+# use den.lib.take.exactly to avoid being called with `{host, user}`
842842+take.exactly ({ host }: ...)
843843+844844+# use den.lib.take.atLeast to avoid being called with `{host}`
845845+take.atLeast ({ host, user }: ...)
846846+```
847847+848848+Read the documentation at [`context/user.nix`](https://github.com/vic/den/blob/main/modules/context/user.nix) for all the details.
849849+850850+## `den.provides.mutual-provider`
851851+852852+An alternative to bidirectionality is [`den.provides.mutual-provider`](https://github.com/vic/den/blob/main/modules/aspects/provides/mutual-provider.nix).
853853+854854+This battery is more explicit, since it requires an explicit `.provides.` relationship between users and hosts.
855855+856856+```nix
857857+# Host provides to a particular user
858858+den.aspects.igloo.provides.tux = {
859859+ hjem = ...;
860860+};
861861+862862+# User provides to a particular host
863863+den.aspects.tux.provides.igloo = {
864864+ nixos = ...;
865865+};
866866+```
867867+868868+To enable it for both users and hosts, include at default:
869869+870870+```nix
871871+den.default.includes = [ den._.mutual-provider ];
872872+```
873873+```
874874+875875+**File:** docs/src/content/docs/guides/configure-aspects.mdx (L127-187)
876876+```text
877877+878878+1. Static (plain attribute set): `{ nixos.foo = ...; }`
879879+2. Static (flake-aspects' leaf): `{class, aspect-chain}: { ${class}.foo = ...; }`
880880+3. Parametric (any other function): `{ host, user }: { ${host.class}.foo = ...; }`
881881+882882+`(1)` and `(2)` are termed *static aspects* and are the terminal leafs of flake-aspects DAG.
883883+`(1)` provides configuration unconditionally, and `(2)` gets access to the `class` that is
884884+being resolved and an `aspect-chain` that lead to the current aspect (most recent last).
885885+886886+887887+`(3)` is a more interesting kind of aspect that is used by Den to pass host/user defitions
888888+into these functions, so they can inspect the host features and provide
889889+configuration accordingly.
890890+891891+892892+## Provides
893893+894894+`provides` creates named sub-aspects accessible via `den.aspects.<name>._.<sub>`
895895+or `den.aspects.<name>.provides.<sub>`:
896896+897897+```nix
898898+den.aspects.tools.provides.editors = {
899899+ homeManager.programs.helix.enable = true;
900900+ homeManager.programs.vim.enable = true;
901901+};
902902+903903+# Used elsewhere:
904904+den.aspects.alice.includes = [ den.aspects.tools._.editors ];
905905+```
906906+907907+`provides` can also be parametric functions:
908908+909909+```nix
910910+den.aspects.alice.provides.work-vpn = { host, user, ... }:
911911+ lib.optionalAttrs host.hasVpn {
912912+ nixos.services.openvpn.servers.work.config = "...";
913913+ };
914914+```
915915+916916+## Global Defaults
917917+918918+`den.default` is a special aspect applied to every host, user, and home:
919919+920920+```nix
921921+{
922922+ den.default = {
923923+ nixos.system.stateVersion = "25.11";
924924+ homeManager.home.stateVersion = "25.11";
925925+ includes = [
926926+ den.provides.define-user
927927+ den.provides.inputs'
928928+ ];
929929+ };
930930+}
931931+```
932932+933933+<Aside type="caution">
934934+Owned configs from `den.default` are deduplicated across pipeline stages.
935935+Parametric functions in `den.default.includes` are evaluated at every context
936936+stage. Use `den.lib.take.exactly` if a function should only run in specific contexts.
937937+</Aside>
938938+```
939939+940940+**File:** docs/src/content/docs/explanation/parametric.mdx (L60-113)
941941+```text
942942+943943+`den.lib.parametric` changes an aspect `__functor` which processes
944944+its `includes` list through parametric dispatch:
945945+946946+```nix
947947+den.lib.parametric {
948948+ nixos.foo = 1; # owned config, always included
949949+ includes = [
950950+ { nixos.bar = 2; } # static, always included
951951+ ({ host }: { nixos.x = host.name; }) # parametric, host contexts only
952952+ ({ user }: { homeManager.y = 1; }) # parametric, user contexts only
953953+ ];
954954+}
955955+```
956956+957957+When the aspect's `__functor` is called with a context, it filters `includes`
958958+based on argument compatibility and returns only matching entries.
959959+960960+<Aside type="caution" title="Anonymous functions are an anti-pattern">
961961+It is **not** recommended to have inlined functions on your `.includes` lists.
962962+This guide uses inlined functions only for examples, not as best-practice.
963963+964964+Instead, use named aspects, this will improves readability and the error traces
965965+generated by Nix since those functions have a proper name and location.
966966+967967+```nix
968968+den.aspects.my-laptop.includes = [ foo ];
969969+den.aspects.foo = { host }: { ... };
970970+```
971971+</Aside>
972972+973973+## Parametric Variants
974974+975975+| Constructor | Behavior |
976976+|---|---|
977977+| `parametric` | Default. <br/> Includes owned classes + static includes + function includes with context matching `atLeast`. |
978978+| `parametric.atLeast` | **Does NOT** include owned classes nor static includes. Only function matching atLeast the context. |
979979+| `parametric.exactly` | Like atLeast but using canTake.exactly for match. |
980980+| `parametric.fixedTo attrs` | Like `parametric` default but ignores any context and always uses the given attrs . |
981981+| `parametric.expands attrs` | Like `parametric`, but extends the received context with `attrs` before dispatch. |
982982+983983+## Example: Context-aware battery
984984+985985+This is a pattern used by many of our built-in batteries, be sure to see their code as example.
986986+987987+```nix
988988+den.lib.parametric.exactly {
989989+ includes = [
990990+ ({ user, host }: { ... })
991991+ ({ home }: { ... })
992992+ ];
993993+};
994994+```
995995+996996+```
997997+998998+**File:** docs/src/content/docs/guides/batteries.mdx (L13-205)
999999+```text
10001000+10011001+Batteries are reusable aspects shipped with Den under `den.provides.*`
10021002+(aliased as `den._.*`). They handle common cross-platform configuration
10031003+patterns so you do not have to rewrite them.
10041004+10051005+## Available Batteries
10061006+10071007+### `den.provides.define-user`
10081008+10091009+Creates OS and home-level user account definitions:
10101010+10111011+```nix
10121012+den.default.includes = [ den.provides.define-user ];
10131013+```
10141014+10151015+Sets `users.users.<name>` on NixOS/Darwin and `home.username`/`home.homeDirectory`
10161016+for Home Manager. Works in both host-user and standalone home contexts.
10171017+10181018+### `den.provides.hostname`
10191019+10201020+Sets the system hostname as defined in `den.hosts.<name>.hostName`:
10211021+10221022+```nix
10231023+den.default.includes = [ den.provides.hostname ];
10241024+```
10251025+10261026+### `den.provides.mutual-provider`
10271027+10281028+Allows hosts and users to contribute configuration **to each other** through `provides`:
10291029+10301030+```nix
10311031+den.hosts.x86_64-linux.igloo.users.tux = { };
10321032+den.default.includes = [ den._.mutual-provider ];
10331033+```
10341034+10351035+This is not the same as the built-in bidirectionality:
10361036+10371037+```nix
10381038+# contributes to ALL users of this host
10391039+den.aspects.my-host.homeManager = { ... }
10401040+10411041+# contributes to ALL hosts of where my-user exist
10421042+den.aspects.my-user.nixos = { ... }
10431043+```
10441044+10451045+The difference is that this allows you to wire bidirectionality between
10461046+explictly-named hosts/users pairs.
10471047+10481048+A user providing config TO the host:
10491049+10501050+```nix
10511051+den.aspects.tux = {
10521052+ provides.igloo = { host, ... }: {
10531053+ nixos.programs.nh.enable = host.name == "igloo";
10541054+ };
10551055+};
10561056+```
10571057+10581058+A host providing config TO the user:
10591059+10601060+```nix
10611061+den.aspects.igloo = {
10621062+ provides.tux = { user, ... }: {
10631063+ homeManager.programs.helix.enable = user.name == "alice";
10641064+ };
10651065+};
10661066+```
10671067+10681068+### `den.provides.primary-user`
10691069+10701070+Marks a user as the primary user of the system:
10711071+10721072+```nix
10731073+den.aspects.alice.includes = [ den.provides.primary-user ];
10741074+```
10751075+10761076+- **NixOS**: adds `wheel` and `networkmanager` groups, sets `isNormalUser`.
10771077+- **Darwin**: sets `system.primaryUser`.
10781078+- **WSL**: sets `defaultUser` (if WSL is enabled).
10791079+10801080+### `den.provides.user-shell`
10811081+10821082+Sets the default login shell at both OS and Home Manager levels:
10831083+10841084+```nix
10851085+den.aspects.alice.includes = [ (den.provides.user-shell "fish") ];
10861086+```
10871087+10881088+Enables `programs.<shell>.enable` on the OS and in Home Manager,
10891089+and sets `users.users.<name>.shell`.
10901090+10911091+### `den.provides.forward`
10921092+10931093+Creates custom Nix classes by forwarding module contents into target
10941094+submodule paths. See [Custom Nix Classes](/guides/custom-classes/) for details.
10951095+10961096+### `den.provides.import-tree`
10971097+10981098+Recursively imports non-dendritic `.nix` files, auto-detecting class from
10991099+directory names (`_nixos/`, `_darwin/`, `_homeManager/`):
11001100+11011101+```nix
11021102+# Import per host
11031103+den.ctx.host.includes = [ (den.provides.import-tree._.host ./hosts) ];
11041104+11051105+# Import per user
11061106+den.ctx.user.includes = [ (den.provides.import-tree._.user ./users) ];
11071107+11081108+# Import for a specific aspect
11091109+den.aspects.laptop.includes = [ (den.provides.import-tree ./disko) ];
11101110+```
11111111+11121112+Requires `inputs.import-tree`.
11131113+11141114+### `den.provides.unfree`
11151115+11161116+Enables specific unfree packages by name:
11171117+11181118+```nix
11191119+den.aspects.laptop.includes = [
11201120+ (den.provides.unfree [ "nvidia-x11" "steam" ])
11211121+];
11221122+```
11231123+11241124+Works for any class (`nixos`, `darwin`, `homeManager`). The unfree predicate
11251125+builder is automatically included via `den.default`.
11261126+11271127+### `den.provides.tty-autologin`
11281128+11291129+Enables automatic TTY1 login on NixOS:
11301130+11311131+```nix
11321132+den.aspects.laptop.includes = [ (den.provides.tty-autologin "alice") ];
11331133+```
11341134+11351135+### `den.provides.inputs'`
11361136+11371137+Provides flake-parts `inputs'` (system-specialized inputs) as a module argument:
11381138+11391139+```nix
11401140+den.default.includes = [ den.provides.inputs' ];
11411141+```
11421142+11431143+Requires flake-parts. Works in host, user, and home contexts.
11441144+11451145+### `den.provides.self'`
11461146+11471147+Provides flake-parts `self'` (system-specialized self) as a module argument:
11481148+11491149+```nix
11501150+den.default.includes = [ den.provides.self' ];
11511151+```
11521152+11531153+Requires flake-parts. Works in host, user, and home contexts.
11541154+11551155+## Usage Patterns
11561156+11571157+### Global Batteries
11581158+11591159+Apply to all entities via `den.default`:
11601160+11611161+```nix
11621162+den.default.includes = [
11631163+ den.provides.define-user
11641164+ den.provides.inputs'
11651165+];
11661166+```
11671167+11681168+### Per-Aspect Batteries
11691169+11701170+Apply to specific aspects:
11711171+11721172+```nix
11731173+den.aspects.alice.includes = [
11741174+ den.provides.primary-user
11751175+ (den.provides.user-shell "zsh")
11761176+ (den.provides.unfree [ "vscode" ])
11771177+];
11781178+```
11791179+11801180+### Battery Composition
11811181+11821182+Batteries compose with regular aspects:
11831183+11841184+```nix
11851185+den.aspects.my-admin = den.lib.parametric {
11861186+ includes = [
11871187+ den.provides.primary-user
11881188+ (den.provides.user-shell "fish")
11891189+ { nixos.security.sudo.wheelNeedsPassword = false; }
11901190+ ];
11911191+};
11921192+```
11931193+```
11941194+11951195+**File:** docs/src/content/docs/guides/custom-classes.mdx (L14-270)
11961196+```text
11971197+## What is a Custom Class
11981198+11991199+Den's built-in classes (`nixos`, `darwin`, `homeManager`) map to well-known
12001200+NixOS module systems. But you can define **custom classes** that forward their
12011201+contents into a target submodule path on another class.
12021202+12031203+This is how Den implements:
12041204+- The `user` class (forwards to `users.users.<name>` on the OS)
12051205+- Home Manager integration (forwards `homeManager` to `home-manager.users.<name>`)
12061206+- hjem integration (forwards `hjem` to `hjem.users.<name>`)
12071207+- nix-maid integration (forwards `maid` to `users.users.<name>.maid`)
12081208+12091209+## The `forward` Battery
12101210+12111211+`den.provides.forward` creates a new class by forwarding its module contents
12121212+into a target path on an existing class:
12131213+12141214+```nix
12151215+{ host }:
12161216+den.provides.forward {
12171217+ each = lib.attrValues host.users;
12181218+ fromClass = user: "user";
12191219+ intoClass = user: host.class;
12201220+ intoPath = user: [ "users" "users" user.userName ];
12211221+ fromAspect = user: den.aspects.${user.aspect};
12221222+}
12231223+```
12241224+12251225+| Parameter | Description |
12261226+|---|---|
12271227+| `each` | List of items to forward (typically `[ user ]` or `[ true ]`) |
12281228+| `fromClass` | The custom class name to read from |
12291229+| `intoClass` | The target class to write into |
12301230+| `intoPath` | Target attribute path in the target class |
12311231+| `fromAspect` | The aspect to read the custom class from |
12321232+12331233+## Example: The Built-in `user` Class
12341234+12351235+The `user` class (`modules/aspects/provides/os-user.nix`) forwards OS-level
12361236+user settings without requiring Home Manager:
12371237+12381238+```nix
12391239+# Instead of:
12401240+den.aspects.alice.nixos = { pkgs, ... } {
12411241+ users.users.alice = {
12421242+ packages = [ pkgs.hello ];
12431243+ extraGroups = [ "wheel" ];
12441244+ };
12451245+};
12461246+12471247+# You write:
12481248+den.aspects.alice.user = { pkgs, ... }: {
12491249+ packages = [ pkgs.hello ];
12501250+ extraGroups = [ "wheel" ];
12511251+};
12521252+```
12531253+12541254+The `user` class is automatically forwarded to `users.users.<userName>` on
12551255+whatever OS class the host uses (NixOS or Darwin).
12561256+12571257+## Creating Your Own Class
12581258+12591259+Suppose you want a `container` class that forwards into
12601260+`virtualisation.oci-containers.containers.<name>`:
12611261+12621262+```nix
12631263+{ den, lib, ... }:
12641264+let
12651265+ fwd = { host, user }:
12661266+ den.provides.forward {
12671267+ each = lib.singleton user;
12681268+ fromClass = _: "container";
12691269+ intoClass = _: host.class;
12701270+ intoPath = _: [ "virtualisation" "oci-containers" "containers" user.userName ];
12711271+ fromAspect = _: den.aspects.${user.aspect};
12721272+ };
12731273+in {
12741274+ den.ctx.user.includes = [ fwd ];
12751275+}
12761276+```
12771277+12781278+Now any user aspect can use the `container` class:
12791279+12801280+```nix
12811281+den.aspects.alice.container = {
12821282+ image = "nginx:latest";
12831283+ ports = [ "8080:80" ];
12841284+};
12851285+```
12861286+12871287+## Advanced: Guards and Adapters
12881288+12891289+`forward` supports optional parameters for complex scenarios:
12901290+12911291+```nix
12921292+den.provides.forward {
12931293+ each = lib.singleton true;
12941294+ fromClass = _: "wsl";
12951295+ intoClass = _: host.class;
12961296+ intoPath = _: [ "wsl" ];
12971297+ fromAspect = _: lib.head aspect-chain;
12981298+ # Only forward if target has wsl options
12991299+ guard = { options, ... }: options ? wsl;
13001300+ # Modify module args for the forwarded module
13011301+ adaptArgs = args: args // { osConfig = args.config; };
13021302+ # Custom module type for the forwarded submodule
13031303+ adapterModule = { config._module.freeformType = lib.types.anything; };
13041304+}
13051305+```
13061306+13071307+| Parameter | Description |
13081308+|---|---|
13091309+| `guard` | Only forward when this predicate returns true |
13101310+| `adaptArgs` | Transform module arguments before forwarding |
13111311+| `adapterModule` | Custom module for the forwarded submodule type |
13121312+13131313+## User contributed examples
13141314+13151315+#### Example: Config across `nixos` and `darwin` classes.
13161316+13171317+The `os` forward class ([provided by Den](https://github.com/vic/den/blob/main/modules/aspects/provides/os-class.nix)) can be useful for settings that must be forwarded to both on NixOS and MacOS.
13181318+13191319+> Requested by @Risa-G at [#222](https://github.com/vic/den/discussions/222)
13201320+13211321+```nix
13221322+# Note: this is already provided by Den at provides/os-class.nix
13231323+os-class = { class, aspect-chain }: den._.forward {
13241324+ each = [ "nixos" "darwin" ];
13251325+ fromClass = _: "os";
13261326+ intoClass = lib.id;
13271327+ intoPath = _: [ ]; # top-level
13281328+ fromAspect = _: lib.head aspect-chain;
13291329+};
13301330+13311331+# Note: already enabled by Den
13321332+# den.ctx.host.includes = [ os-class ];
13331333+13341334+den.aspects.my-laptop = {
13351335+ os.networking.hostName = "Yavanna"; # on both NixOS and MacOS
13361336+};
13371337+```
13381338+13391339+#### Example: Role based configuration between users and hosts
13401340+13411341+A dynamic class for matching roles between users and hosts.
13421342+13431343+```nix
13441344+roleClass =
13451345+ { host, user }:
13461346+ { class, aspect-chain }:
13471347+ den._.forward {
13481348+ each = lib.intersectLists (host.roles or []) (user.roles or []);
13491349+ fromClass = lib.id;
13501350+ intoClass = _: host.class;
13511351+ intoPath = _: [ ];
13521352+ fromAspect = _: lib.head aspect-chain;
13531353+ };
13541354+13551355+den.ctx.user.includes = [ roleClass ];
13561356+13571357+den.hosts.x86_64-linux.igloo = {
13581358+ roles = [ "devops" "gaming" ];
13591359+ users = {
13601360+ alice.roles = [ "gaming" ];
13611361+ bob.roles = [ "devops" ];
13621362+ };
13631363+};
13641364+13651365+den.aspects.alice = {
13661366+ # enabled when host supports gaming role
13671367+ gaming = { pkgs, ... }: { programs.steam.enable = true; };
13681368+13691369+ # enabled when host supports devops role
13701370+ devops = { pkgs, ... }: { virtualisation.podman.enable = true; };
13711371+};
13721372+```
13731373+13741374+#### Example: A git class that forwards to home-manager.
13751375+13761376+```nix
13771377+gitClass =
13781378+ { class, aspect-chain }:
13791379+ den._.forward {
13801380+ each = lib.singleton true;
13811381+ fromClass = _: "git";
13821382+ intoClass = _: "homeManager";
13831383+ intoPath = _: [ "programs" "git" ];
13841384+ fromAspect = _: lib.head aspect-chain;
13851385+ adaptArgs = lib.id;
13861386+ };
13871387+13881388+den.aspects.tux = {
13891389+ includes = [ gitClass ];
13901390+ git.userEmail = "root@linux.com";
13911391+};
13921392+```
13931393+13941394+This will set at host: `home-manager.users.tux.programs.git.userEmail`
13951395+13961396+#### Example: A `nix` class that propagates settings to NixOS and HomeManager
13971397+13981398+This can be used when you don't want NixOS and HomeManager to share the
13991399+same pkgs but still configure both at the same time.
14001400+> Contributed by @musjj
14011401+14021402+```nix
14031403+nixClass =
14041404+ { class, aspect-chain }:
14051405+ den._.forward {
14061406+ each = [ "nixos" "homeManager" ];
14071407+ fromClass = _: "nix";
14081408+ intoClass = lib.id;
14091409+ intoPath = _: [ "nix" "settings" ];
14101410+ fromAspect = _: lib.head aspect-chain;
14111411+ adaptArgs = lib.id;
14121412+ };
14131413+14141414+# enable class for all users:
14151415+den.ctx.user.includes = [ nixClass ];
14161416+14171417+# custom aspect that uses the `nix` class.
14181418+nix-allowed = { user, ... }: { nix.allowed-users = [ user.userName ]; };
14191419+14201420+# included at users who can fix things with nix.
14211421+den.aspects.tux.includes = [ nix-allowed ];
14221422+```
14231423+14241424+#### Example: An impermanence class
14251425+14261426+> Suggested by @Doc-Steve
14271427+14281428+The following example, creates a forwarding class that is propagated only
14291429+when `environment.persistance` option is available in the host (the impermanence module was imported in host)
14301430+14311431+One cool thing about these custom classes is that aspects can simply define
14321432+settings at the new class, without having to worry if the options they depend or
14331433+some capability is enabled.
14341434+14351435+The froward-guard itself is reponsible checking in only one place, instead of having `mkIf` in a lot of places.
14361436+14371437+```nix
14381438+# Custom `persys` forwards config into nixos.environment.persistance."/nix/persist/system"
14391439+# only if environment.persistance option is present.
14401440+persys = { class, aspect-chain }: den._.forward {
14411441+ each = lib.singleton true;
14421442+ fromClass = _: "persys";
14431443+ intoClass = _: class;
14441444+ intoPath = _: [ "environment" "persistance" "/nix/persist/system" ];
14451445+ fromAspect = _: lib.head aspect-chain;
14461446+ guard = { options, ... }@osArgs: options ? environment.persistance;
14471447+};
14481448+14491449+den.hosts.my-laptop.includes = [ persys ];
14501450+14511451+# becomes nixos.environment.persistance."/nix/persist/system".hideMounts = true;
14521452+den.aspects.my-laptop.persys.hideMounts = true;
14531453+```
14541454+```
14551455+14561456+**File:** docs/src/content/docs/guides/namespaces.mdx (L13-114)
14571457+```text
14581458+14591459+A **namespace** creates a scoped aspect library under `den.ful.<name>`.
14601460+Namespaces can be:
14611461+- **Local**: defined in your flake, consumed internally.
14621462+- **Exported**: exposed via `flake.denful.<name>` for other flakes to consume.
14631463+- **Imported**: merged from upstream flakes into your local `den.ful`.
14641464+14651465+## Creating a Namespace
14661466+14671467+```nix
14681468+# modules/namespace.nix
14691469+{ inputs, den, ... }: {
14701470+ # Create "my" namespace (not exported to flake outputs)
14711471+ imports = [ (inputs.den.namespace "my" false) ];
14721472+14731473+ # Or create and export "eg" namespace
14741474+ imports = [ (inputs.den.namespace "eg" true) ];
14751475+}
14761476+```
14771477+14781478+This creates:
14791479+- `den.ful.eg` -- the namespace attrset (aspects type).
14801480+- `eg` -- a module argument alias to `den.ful.eg`.
14811481+- `flake.denful.eg` -- flake output (if exported).
14821482+14831483+## Populating a Namespace
14841484+14851485+Define aspects under the namespace using any module:
14861486+14871487+```nix
14881488+# modules/aspects/vim.nix
14891489+{
14901490+ eg.vim = {
14911491+ homeManager.programs.vim.enable = true;
14921492+ };
14931493+}
14941494+```
14951495+14961496+```nix
14971497+# modules/aspects/desktop.nix
14981498+{ eg, ... }: {
14991499+ eg.desktop = {
15001500+ includes = [ eg.vim ];
15011501+ nixos.services.xserver.enable = true;
15021502+ };
15031503+}
15041504+```
15051505+15061506+## Using Namespaced Aspects
15071507+15081508+Reference them by their namespace:
15091509+15101510+```nix
15111511+{ eg, ... }: {
15121512+ den.aspects.laptop.includes = [
15131513+ eg.desktop
15141514+ eg.vim
15151515+ ];
15161516+}
15171517+```
15181518+15191519+## Importing from Upstream
15201520+15211521+Merge aspects from other flakes:
15221522+15231523+```nix
15241524+# modules/namespace.nix
15251525+{ inputs, ... }: {
15261526+ # Import "shared" namespace from upstream, merging with local definitions
15271527+ imports = [ (inputs.den.namespace "shared" [ inputs.team-config ]) ];
15281528+}
15291529+```
15301530+15311531+The namespace function accepts:
15321532+- A **boolean** (`true`/`false`) for local/exported namespaces.
15331533+- A **list of sources** to merge from upstream flakes.
15341534+ Each source's `flake.denful.<name>` is merged into `den.ful.<name>`.
15351535+15361536+## Enabling Angle Brackets
15371537+15381538+When using namespaces, enable angle bracket syntax for terser references:
15391539+15401540+```nix
15411541+{ den, ... }: {
15421542+ _module.args.__findFile = den.lib.__findFile;
15431543+}
15441544+```
15451545+15461546+Then reference deep aspects with `<namespace/path>`:
15471547+15481548+```nix
15491549+den.aspects.laptop.includes = [ <eg/desktop> ];
15501550+```
15511551+15521552+## Architecture
15531553+15541554+```mermaid
15551555+flowchart LR
15561556+ upstream["upstream flake"] -->|"flake.denful.shared"| merge["den.ful.shared"]
15571557+ local["local modules"] -->|"shared.vim = ..."| merge
15581558+ merge --> consumer["den.aspects.*.includes"]
15591559+```
15601560+```
15611561+15621562+**File:** docs/src/content/docs/guides/angle-brackets.mdx (L17-65)
15631563+```text
15641564+15651565+## Enabling
15661566+15671567+Set `__findFile` via module args:
15681568+15691569+```nix
15701570+{ den, ... }: {
15711571+ _module.args.__findFile = den.lib.__findFile;
15721572+}
15731573+```
15741574+15751575+## Resolution Rules
15761576+15771577+The `<name>` expression resolves through these paths in order:
15781578+15791579+1. **`<den.x.y>`** -- resolves to `config.den.x.y`
15801580+2. **`<aspect>`** -- resolves to `config.den.aspects.aspect` (if `aspect` exists in `den.aspects`)
15811581+3. **`<aspect/sub>`** -- resolves to `config.den.aspects.aspect.provides.sub`
15821582+4. **`<namespace>`** -- resolves to `config.den.ful.namespace` (if it is a denful)
15831583+15841584+The `/` separator is translated to `.provides.` in the lookup path.
15851585+15861586+## Examples
15871587+15881588+```nix
15891589+# Without angle brackets
15901590+den.aspects.laptop.includes = [
15911591+ den.aspects.tools.provides.editors
15921592+ den.aspects.alice.provides.work-vpn
15931593+ den.provides.primary-user
15941594+];
15951595+15961596+# With angle brackets
15971597+den.aspects.laptop.includes = [
15981598+ <tools/editors>
15991599+ <alice/work-vpn>
16001600+ <den.provides.primary-user>
16011601+];
16021602+```
16031603+16041604+## When to Use
16051605+16061606+Angle brackets are optional syntactic sugar. They are useful when:
16071607+- You have deeply nested provides and want shorter references.
16081608+- You are working with namespaces and want concise cross-references.
16091609+16101610+They are functionally identical to direct attribute access. The choice
16111611+is a matter of style.
16121612+16131613+```
16141614+16151615+**File:** docs/src/content/docs/guides/declare-hosts.mdx (L17-152)
16161616+```text
16171617+## Host Declaration
16181618+16191619+`den.hosts` is keyed by `<system>.<name>`:
16201620+16211621+```nix
16221622+{
16231623+ den.hosts.x86_64-linux.laptop.users.alice = { };
16241624+16251625+ den.hosts.aarch64-darwin.mac = {
16261626+ users.alice = { };
16271627+ brew.apps = [ "iterm2" ];
16281628+ };
16291629+}
16301630+```
16311631+16321632+Each host entry produces a configuration in `flake.nixosConfigurations` or
16331633+`flake.darwinConfigurations` depending on its `class` (auto-detected from the host platform).
16341634+16351635+## Host Schema
16361636+16371637+<Aside title="Important" icon="nix">
16381638+Be sure to read about the entity [Schemas](/reference/schema/), each of `host`/`user`/`home` has a corresponding `den.schema.*` module for meta-configuration of entity features. Which can later be used in aspects that read from `host`. These schemas are the meta-data equivalent of what Dendritic flake-parts users do with flake-level options.
16391639+</Aside>
16401640+16411641+Hosts have these options (all with sensible defaults):
16421642+16431643+| Option | Default | Description |
16441644+|---|---|---|
16451645+| `name` | attrset key | Configuration name |
16461646+| `hostName` | `name` | Network hostname |
16471647+| `system` | parent key | `x86_64-linux`, `aarch64-darwin`, etc. |
16481648+| `class` | `"nixos"` or `"darwin"` | OS class, auto-detected from system |
16491649+| `aspect` | `name` | Primary aspect name |
16501650+| `instantiate` | class-dependent | `lib.nixosSystem`, `darwinSystem`, etc. |
16511651+| `intoAttr` | class-dependent | Flake output path |
16521652+| `users` | `{}` | User account definitions |
16531653+| `*` | from `den.schema.host` | Any option defined by base module |
16541654+| `*` | | Any other free-form attribute |
16551655+16561656+## User Declaration
16571657+16581658+Users are declared as part of a host:
16591659+16601660+```nix
16611661+den.hosts.x86_64-linux.laptop = {
16621662+ users.alice = { };
16631663+ users.bob.classes = [ "homeManager" "hjem" ];
16641664+};
16651665+```
16661666+16671667+Each user has:
16681668+16691669+| Option | Default | Description |
16701670+|---|---|---|
16711671+| `name` | attrset key | User configuration name |
16721672+| `userName` | `name` | System account name |
16731673+| `aspect` | `name` | Primary aspect name |
16741674+| `classes` | `[ "homeManager" ]` | Nix classes this user participates in |
16751675+| `*` | from `den.schema.user` | Any option defined by base module |
16761676+| `*` | | Any other free-form attribute |
16771677+16781678+## Standalone Homes
16791679+16801680+For systems without root access or for home-manager-only setups:
16811681+16821682+```nix
16831683+{
16841684+ den.homes.x86_64-linux.alice = { };
16851685+ den.homes.aarch64-darwin.alice = { };
16861686+}
16871687+```
16881688+16891689+Standalone homes produce `flake.homeConfigurations.<name>`.
16901690+16911691+Home schema:
16921692+16931693+| Option | Default | Description |
16941694+|---|---|---|
16951695+| `name` | attrset key | Home configuration name |
16961696+| `userName` | `name` | User account name |
16971697+| `system` | parent key | Platform system |
16981698+| `class` | `"homeManager"` | Home class |
16991699+| `aspect` | `name` | Primary aspect name |
17001700+| `pkgs` | `inputs.nixpkgs.legacyPackages.${system}` | nixpkgs instance |
17011701+| `instantiate` | `inputs.home-manager.lib.homeManagerConfiguration` | Builder function |
17021702+| `*` | from `den.schema.host` | Any option defined by base module |
17031703+| `*` | | Any other free-form attribute |
17041704+17051705+## Base Modules
17061706+17071707+`den.schema.{host,user,home}` provides shared configuration applied to all entities of each kind.
17081708+17091709+Some batteries also extend base modules [see `home-env.nix`](https://github.com/vic/den/blob/main/nix/home-env.nix)
17101710+that is used by home-manager/hjem/maid to extend the Host schema with options like `hjem.module`/`home-manager.module`.
17111711+17121712+Another more advanced examples is our [templates/microvm](https://github.com/vic/den/tree/main/templates/microvm/modules/microvm-integration.nix)
17131713+that adds options related to running virtualized OS.
17141714+17151715+```nix
17161716+{
17171717+ # Can be used to specify features of all host
17181718+ den.schema.host.home-manager.enable = true;
17191719+17201720+ # Can be used to add schema options with defaults
17211721+ den.schema.user = { user, lib, ... }: {
17221722+ options.groupName = lib.mkOption { default = user.userName; };
17231723+ };
17241724+17251725+ # Applied to every host and user and home.
17261726+ den.schema.conf = {
17271727+ options.copyright = lib.mkOption { default = "Copy-Left"; };
17281728+ };
17291729+}
17301730+```
17311731+17321732+## Freeform Schema
17331733+17341734+Host and user types use `freeformType`, so you can add arbitrary attributes:
17351735+17361736+```nix
17371737+den.hosts.x86_64-linux.laptop = {
17381738+ users.alice = { };
17391739+ gpu = "nvidia"; # custom attribute, accessible in aspects via host.gpu
17401740+};
17411741+```
17421742+17431743+Access custom attributes in aspects:
17441744+17451745+```nix
17461746+den.aspects.laptop.includes = [
17471747+ ({ host, ... }: lib.optionalAttrs (host ? gpu) {
17481748+ nixos.hardware.nvidia.enable = true;
17491749+ })
17501750+];
17511751+```
17521752+```
17531753+17541754+**File:** docs/src/content/docs/guides/home-manager.mdx (L19-158)
17551755+```text
17561756+17571757+All Home integrations are opt-in and must be enabled explicitly.
17581758+17591759+```nix
17601760+# Per user
17611761+den.hosts.x86_64-linux.igloo.users.tux.classes = [ "homeManager" "hjem" ];
17621762+17631763+# As default for all users, unless they specify other classes.
17641764+den.schema.user.classes = lib.mkDefault [ "homeManager" ];
17651765+```
17661766+17671767+Home integration contexts, like `den.ctx.hm-host` only activate when
17681768+at least one host user has `homeManager` in their `classes`.
17691769+When true, the integration imports the HomeManager OS module,
17701770+and forwards each user's `homeManager` class into `home-manager.users.<userName>`.
17711771+17721772+Same details regarding `home-manager` apply to other home types like `hjem` and `maid`.
17731773+17741774+### Requirements
17751775+17761776+- `inputs.home-manager` must exist in your flake inputs or have custom `host.home-manager.module`.
17771777+- At least one user must have `homeManager` in their `classes`.
17781778+17791779+### How it Works
17801780+17811781+```mermaid
17821782+flowchart TD
17831783+ host["den.ctx.host"] -->|"into.hm-host"| hmhost["den.ctx.hm-host"]
17841784+ hmhost -->|"imports HM module"| mod["home-manager.nixosModules"]
17851785+ hmhost -->|"into.hm-user (per user)"| hmuser["den.ctx.hm-user"]
17861786+ hmuser -->|"forward homeManager class"| target["home-manager.users.alice"]
17871787+```
17881788+17891789+1. `hm-os.nix` detects hosts with HM-enabled users and supported OS class.
17901790+2. It produces `den.ctx.hm-host`, which imports the HM OS-level module.
17911791+3. `hm-integration.nix` creates `den.ctx.hm-user` per HM user, forwarding
17921792+ the `homeManager` class into `home-manager.users.<userName>`.
17931793+17941794+### Configuring Home Manager
17951795+17961796+```nix
17971797+den.aspects.alice = {
17981798+ homeManager = { pkgs, ... }: {
17991799+ home.packages = [ pkgs.htop ];
18001800+ programs.git.enable = true;
18011801+ };
18021802+};
18031803+```
18041804+18051805+The `homeManager` class contents are forwarded to the OS-level
18061806+`home-manager.users.alice` automatically.
18071807+18081808+### Custom HM Module
18091809+18101810+Override the HM module per host if needed:
18111811+18121812+```nix
18131813+den.hosts.x86_64-linux.laptop = {
18141814+ users.vic.classes = [ "home-manager" ];
18151815+ home-manager.module = inputs.home-manager-unstable.nixosModules.home-manager;
18161816+};
18171817+```
18181818+18191819+## Standalone Homes
18201820+18211821+For machines without root access:
18221822+18231823+```nix
18241824+den.homes.x86_64-linux.alice = { };
18251825+```
18261826+18271827+This produces `flake.homeConfigurations.alice`, built with
18281828+`inputs.home-manager.lib.homeManagerConfiguration`.
18291829+18301830+## hjem
18311831+18321832+[hjem](https://github.com/feel-co/hjem) is an alternative, lightweight home environment manager.
18331833+18341834+### Enabling
18351835+18361836+```nix
18371837+# Per host
18381838+den.hosts.x86_64-linux.laptop = {
18391839+ users.alice.classes = [ "hjem" ];
18401840+};
18411841+18421842+# On all hosts
18431843+den.schema.host.hjem.enable = true;
18441844+```
18451845+18461846+### Requirements
18471847+18481848+- `inputs.hjem` must exist.
18491849+- Users must have `hjem` in their `classes`.
18501850+18511851+### Using
18521852+18531853+```nix
18541854+den.aspects.alice.hjem = { };
18551855+```
18561856+18571857+## nix-maid
18581858+18591859+[nix-maid](https://github.com/nix-maid) is another user-environment manager for NixOS.
18601860+18611861+### Enabling
18621862+18631863+```nix
18641864+den.hosts.x86_64-linux.laptop = {
18651865+ users.alice.classes = [ "maid" ];
18661866+};
18671867+```
18681868+18691869+### Requirements
18701870+18711871+- `inputs.nix-maid` must exist.
18721872+- Host class must be `"nixos"`.
18731873+- Users must have `maid` in their `classes`.
18741874+18751875+### Using
18761876+18771877+```nix
18781878+den.aspects.alice.maid = {
18791879+ # nix-maid configuration
18801880+};
18811881+```
18821882+18831883+## Multiple User Environments
18841884+18851885+A user can participate in multiple environments:
18861886+18871887+```nix
18881888+den.hosts.x86_64-linux.laptop = {
18891889+ users.alice.classes = [ "homeManager" "hjem" ];
18901890+ home-manager.enable = true;
18911891+ hjem.enable = true;
18921892+};
18931893+```
18941894+18951895+Both `homeManager` and `hjem` configurations from `den.aspects.alice` will
18961896+```
18971897+18981898+**File:** docs/src/content/docs/explanation/context-system.mdx (L14-95)
18991899+```text
19001900+19011901+A **context** is a named stage in Den's evaluation pipeline. Each context type is declared
19021902+under `den.ctx.<name>` and carries:
19031903+19041904+- **Aspect definitions** (`provides.<name>`) -- what this context contributes to the resolved aspect.
19051905+- **Transformations** (`into.<other>`) -- functions that produce new contexts from the current one.
19061906+- **Provides** (`provides.<other>`) -- cross-context providers injected by other context definitions.
19071907+19081908+## Built-in Context Types
19091909+19101910+```mermaid
19111911+flowchart TD
19121912+ host["{host}"] -->|"into.user (per user)"| user["{host, user}"]
19131913+ host -->|"into.hm-host (if HM enabled)"| hmhost["{host} hm-host"]
19141914+ hmhost -->|"into.hm-user (per HM user)"| hmuser["{host, user} hm-user"]
19151915+ host -->|"into.wsl-host (if WSL enabled)"| wslhost["{host} wsl-host"]
19161916+ host -->|"into.hjem-host (if hjem enabled)"| hjemhost["{host} hjem-host"]
19171917+ hjemhost -->|"into.hjem-user"| hjemuser["{host, user}"]
19181918+ host -->|"into.maid-host (if maid enabled)"| maidhost["{host} maid-host"]
19191919+ maidhost -->|"into.maid-user"| maiduser["{host, user}"]
19201920+```
19211921+19221922+The framework defines these contexts in `modules/context/os.nix` and the various battery modules.
19231923+Each battery (Home Manager, hjem, nix-maid, WSL) registers its own `into.*` transitions on the `host` context.
19241924+19251925+## Context Type Anatomy
19261926+19271927+A context type at `den.ctx.host`:
19281928+19291929+```nix
19301930+den.ctx.host = {
19311931+ description = "OS";
19321932+19331933+ # The main aspect activated by this context
19341934+ provides.host = { host }: den.aspects.${host.aspect};
19351935+19361936+ # How this context contributes an aspect to other derived contexts
19371937+ provides.user = { host, user }: den.aspects.other-aspect;
19381938+19391939+ # How to derive other contexts from this one
19401940+ into.user = { host }:
19411941+ map (user: { inherit host user; }) (lib.attrValues host.users);
19421942+};
19431943+```
19441944+19451945+The `_` (or `provides`) attrset maps context names to functions that take the current
19461946+context data and return aspect fragments. The `into` attrset maps to functions that
19471947+produce lists of new context values.
19481948+19491949+## Context Resolution
19501950+19511951+When Den processes a host, it calls `den.ctx.host { host = ...; }`. This triggers:
19521952+19531953+1. `collectPairs` walks the context type's `into.*` transitions recursively,
19541954+ building a list of `{ ctx, ctxDef, source }` pairs.
19551955+2. `dedupIncludes` processes these pairs, applying `parametric.fixedTo` for the
19561956+ first occurrence of each context type and `parametric.atLeast` for subsequent
19571957+ ones, preventing duplicate owned configs.
19581958+3. The result is a flat list of aspect fragments merged into one `deferredModule`.
19591959+19601960+## Custom Contexts
19611961+19621962+You can define your own alternative context piplelines outside of `den.ctx.host` or
19631963+create custom context types for domain-specific needs like cloud infrastructure:
19641964+19651965+```nix
19661966+den.ctx.my-service = {
19671967+ description = "Custom service context";
19681968+ provides.my-service = den.aspects.my-service;
19691969+};
19701970+19711971+den.ctx.host.into.my-service = { host }:
19721972+ lib.optional host.my-service.enable { inherit host; };
19731973+19741974+den.aspects.my-service = { host }: {
19751975+ nixos.services.my-service.hostName = host.hostName;
19761976+};
19771977+19781978+19791979+19801980+```
19811981+19821982+```
19831983+19841984+**File:** docs/src/content/docs/reference/schema.mdx (L55-163)
19851985+```text
19861986+## `den.schema`
19871987+19881988+Base modules merged into all hosts, users, or homes.
19891989+19901990+| Option | Type | Description |
19911991+|--------|------|-------------|
19921992+| `den.schema.conf` | `deferredModule` | Applied to host, user, and home |
19931993+| `den.schema.host` | `deferredModule` | Applied to all hosts (imports `conf`) |
19941994+| `den.schema.user` | `deferredModule` | Applied to all users (imports `conf`) |
19951995+| `den.schema.home` | `deferredModule` | Applied to all homes (imports `conf`) |
19961996+19971997+```nix
19981998+den.schema.conf = { lib, ... }: {
19991999+ # shared across all host/user/home declarations
20002000+};
20012001+den.schema.host = { ... }: {
20022002+ # host-specific base config
20032003+};
20042004+```
20052005+20062006+20072007+## `den.hosts`
20082008+20092009+Type: `attrsOf systemType`
20102010+20112011+Keyed by system string (e.g., `"x86_64-linux"`). Each system contains
20122012+host definitions as freeform attribute sets.
20132013+20142014+```nix
20152015+den.hosts.x86_64-linux.myhost = {
20162016+ users.vic = {};
20172017+};
20182018+```
20192019+20202020+### Host options
20212021+20222022+| Option | Type | Default | Description |
20232023+|--------|------|---------|-------------|
20242024+| `name` | `str` | attr name | Configuration name |
20252025+| `hostName` | `str` | `name` | Network hostname |
20262026+| `system` | `str` | parent key | Platform (e.g., `x86_64-linux`) |
20272027+| `class` | `str` | auto | `"nixos"` or `"darwin"` based on system |
20282028+| `aspect` | `str` | `name` | Main aspect name for this host |
20292029+| `description` | `str` | auto | `class.hostName@system` |
20302030+| `users` | `attrsOf userType` | `{}` | User accounts on this host |
20312031+| `instantiate` | `raw` | auto | OS builder function |
20322032+| `intoAttr` | `listOf str` | auto | Flake output path |
20332033+| `*` | `den.schema.host` options | | Options from base module |
20342034+| `*` | | | free-form attributes |
20352035+20362036+### `instantiate` defaults
20372037+20382038+| Class | Default |
20392039+|-------|---------|
20402040+| `nixos` | `inputs.nixpkgs.lib.nixosSystem` |
20412041+| `darwin` | `inputs.darwin.lib.darwinSystem` |
20422042+| `systemManager` | `inputs.system-manager.lib.makeSystemConfig` |
20432043+20442044+### `intoAttr` defaults
20452045+20462046+| Class | Default |
20472047+|-------|---------|
20482048+| `nixos` | `[ "nixosConfigurations" name ]` |
20492049+| `darwin` | `[ "darwinConfigurations" name ]` |
20502050+| `systemManager` | `[ "systemConfigs" name ]` |
20512051+20522052+## `den.hosts.<sys>.<host>.users`
20532053+20542054+Type: `attrsOf userType`
20552055+20562056+### User options
20572057+20582058+| Option | Type | Default | Description |
20592059+|--------|------|---------|-------------|
20602060+| `name` | `str` | attr name | User configuration name |
20612061+| `userName` | `str` | `name` | System account name |
20622062+| `classes` | `listOf str` | `[ "homeManager" ]` | Home management classes |
20632063+| `aspect` | `str` | `name` | Main aspect name |
20642064+| `*` | `den.schema.user` options | | Options from base module |
20652065+| `*` | | | free-form attributes |
20662066+20672067+Freeform: additional attributes pass through to the user module.
20682068+20692069+## `den.homes`
20702070+20712071+Type: `attrsOf homeSystemType`
20722072+20732073+Standalone home-manager configurations, keyed by system string.
20742074+20752075+```nix
20762076+den.homes.x86_64-linux.vic = {};
20772077+```
20782078+20792079+### Home options
20802080+20812081+| Option | Type | Default | Description |
20822082+|--------|------|---------|-------------|
20832083+| `name` | `str` | attr name | Home configuration name |
20842084+| `userName` | `str` | `name` | User account name |
20852085+| `system` | `str` | parent key | Platform system |
20862086+| `class` | `str` | `"homeManager"` | Home management class |
20872087+| `aspect` | `str` | `name` | Main aspect name |
20882088+| `description` | `str` | auto | `home.userName@system` |
20892089+| `pkgs` | `raw` | `inputs.nixpkgs.legacyPackages.$sys` | Nixpkgs instance |
20902090+| `instantiate` | `raw` | `inputs.home-manager.lib.homeManagerConfiguration` | Builder |
20912091+| `intoAttr` | `listOf str` | `[ "homeConfigurations" name ]` | Output path |
20922092+| `*` | `den.schema.home` options | | Options from base module |
20932093+| `*` | | | free-form attributes |
20942094+20952095+```
20962096+20972097+**File:** docs/src/content/docs/reference/lib.mdx (L13-134)
20982098+```text
20992099+## `den.lib.parametric`
21002100+21012101+Wraps an aspect with a `__functor` that filters `includes` by argument compatibility.
21022102+21032103+```nix
21042104+den.lib.parametric { nixos.x = 1; includes = [ ... ]; }
21052105+```
21062106+21072107+Default uses `atLeast` matching.
21082108+21092109+### `den.lib.parametric.atLeast`
21102110+21112111+Same as `parametric`. Functions match if all required params are present.
21122112+21132113+### `den.lib.parametric.exactly`
21142114+21152115+Functions match only if required params exactly equal provided params.
21162116+21172117+```nix
21182118+den.lib.parametric.exactly { includes = [ ({ host }: ...) ]; }
21192119+```
21202120+21212121+### `den.lib.parametric.fixedTo`
21222122+21232123+Calls the aspect with a fixed context, ignoring the actual context:
21242124+21252125+```nix
21262126+den.lib.parametric.fixedTo { host = myHost; } someAspect
21272127+```
21282128+21292129+### `den.lib.parametric.expands`
21302130+21312131+Extends the received context with additional attributes before dispatch:
21322132+21332133+```nix
21342134+den.lib.parametric.expands { extra = true; } someAspect
21352135+```
21362136+21372137+### `den.lib.parametric.withOwn`
21382138+21392139+Low-level constructor. Takes a `functor: self -> ctx -> aspect` and wraps
21402140+an aspect so that owned configs and statics are included at the static
21412141+stage, and the functor runs at the parametric stage.
21422142+21432143+## `den.lib.canTake`
21442144+21452145+Function argument introspection.
21462146+21472147+### `den.lib.canTake params fn`
21482148+21492149+Returns `true` if `fn`'s required arguments are satisfied by `params` (atLeast).
21502150+21512151+### `den.lib.canTake.atLeast params fn`
21522152+21532153+Same as `canTake`.
21542154+21552155+### `den.lib.canTake.exactly params fn`
21562156+21572157+Returns `true` only if `fn`'s required arguments exactly match `params`.
21582158+21592159+## `den.lib.take`
21602160+21612161+Conditional function application.
21622162+21632163+### `den.lib.take.atLeast fn ctx`
21642164+21652165+Calls `fn ctx` if `canTake.atLeast ctx fn`, otherwise returns `{}`.
21662166+21672167+### `den.lib.take.exactly fn ctx`
21682168+21692169+Calls `fn ctx` if `canTake.exactly ctx fn`, otherwise returns `{}`.
21702170+21712171+### `den.lib.take.unused`
21722172+21732173+`_unused: used: used` -- ignores first argument, returns second. Used for
21742174+discarding `aspect-chain` in `import-tree`.
21752175+21762176+## `den.lib.statics`
21772177+21782178+Extracts only static includes from an aspect (non-function includes):
21792179+21802180+```nix
21812181+den.lib.statics someAspect { class = "nixos"; aspect-chain = []; }
21822182+```
21832183+21842184+## `den.lib.owned`
21852185+21862186+Extracts owned configs from an aspect (removes `includes`, `__functor`):
21872187+21882188+```nix
21892189+den.lib.owned someAspect
21902190+```
21912191+21922192+## `den.lib.isFn`
21932193+21942194+Returns `true` if the value is a function or has `__functor`:
21952195+21962196+```nix
21972197+den.lib.isFn myValue
21982198+```
21992199+22002200+## `den.lib.isStatic`
22012201+22022202+Returns `true` if the function can take `{ class, aspect-chain }`:
22032203+22042204+```nix
22052205+den.lib.isStatic myFn
22062206+```
22072207+22082208+## `den.lib.__findFile`
22092209+22102210+The angle bracket resolver. See [Angle Brackets Syntax](/guides/angle-brackets/).
22112211+22122212+```nix
22132213+_module.args.__findFile = den.lib.__findFile;
22142214+```
22152215+22162216+## `den.lib.aspects`
22172217+22182218+The full [flake-aspects](https://github.com/vic/flake-aspects) API,
22192219+initialized with the current `lib`. Provides `resolve`, `merge`, type
22202220+definitions, and aspect manipulation functions.
22212221+```
22222222+22232223+**File:** docs/src/content/docs/guides/debug.md (L1-100)
22242224+```markdown
22252225+---
22262226+title: Debug Configurations
22272227+description: Tools and techniques for debugging Den configurations.
22282228+---
22292229+22302230+## REPL Inspection
22312231+22322232+Load your flake and explore interactively:
22332233+22342234+```console
22352235+$ nix repl
22362236+nix-repl> :lf .
22372237+nix-repl> nixosConfigurations.igloo.config.networking.hostName
22382238+"igloo"
22392239+```
22402240+22412241+## Expose `den` for Inspection
22422242+22432243+Temporarily expose the `den` attrset as a flake output:
22442244+22452245+```nix
22462246+{ den, ... }: {
22472247+ flake.den = den; # remove after debugging
22482248+}
22492249+```
22502250+22512251+Then in REPL:
22522252+22532253+```console
22542254+nix-repl> :lf .
22552255+nix-repl> den.aspects.igloo
22562256+nix-repl> den.hosts.x86_64-linux.igloo
22572257+nix-repl> den.ctx
22582258+```
22592259+22602260+## Trace Context
22612261+22622262+Print context values during evaluation:
22632263+22642264+```nix
22652265+den.aspects.laptop.includes = [
22662266+ ({ host, ... }@ctx: builtins.trace ctx {
22672267+ nixos.networking.hostName = host.hostName;
22682268+ })
22692269+];
22702270+```
22712271+22722272+## Break into REPL
22732273+22742274+Drop into a REPL at any evaluation point:
22752275+22762276+```nix
22772277+den.aspects.laptop.includes = [
22782278+ ({ host, ... }@ctx: builtins.break ctx {
22792279+ nixos = { };
22802280+ })
22812281+];
22822282+```
22832283+22842284+## Manually Resolve an Aspect
22852285+22862286+Test how an aspect resolves for a specific class:
22872287+22882288+```console
22892289+nix-repl> module = den.lib.aspects.resolve "nixos" [] den.aspects.laptop;
22902290+nix-repl> config = (lib.evalModules { modules = [ module ]; }).config
22912291+```
22922292+22932293+For parametric aspects, apply context first:
22942294+22952295+```console
22962296+nix-repl> aspect = den.aspects.laptop { host = den.hosts.x86_64-linux.laptop; }
22972297+nix-repl> module = den.lib.aspects.resolve "nixos" [] aspect;
22982298+```
22992299+23002300+## Inspect a Host's Main Module
23012301+23022302+```console
23032303+nix-repl> module = den.hosts.x86_64-linux.igloo.mainModule
23042304+nix-repl> cfg = (lib.nixosSystem { modules = [ module ]; }).config
23052305+nix-repl> cfg.networking.hostName
23062306+```
23072307+23082308+## Common Issues
23092309+23102310+**Duplicate values in lists**: Den deduplicates owned and static configs
23112311+from `den.default`, but parametric functions in `den.default.includes`
23122312+run at every context stage. Use `den.lib.take.exactly` to restrict:
23132313+23142314+```nix
23152315+den.lib.take.exactly ({ host }: { nixos.x = 1; })
23162316+```
23172317+23182318+**Missing attribute**: The context does not have the expected parameter.
23192319+Trace context keys to see what is available.
23202320+23212321+**Wrong class**: Check that `host.class` matches what you expect.
23222322+Darwin hosts have `class = "darwin"`, not `"nixos"`.
23232323+23242324+**Module not found**: Ensure the file is under `modules/` and not
23252325+```
23262326+23272327+**File:** docs/src/content/docs/tutorials/ci.md (L1-164)
23282328+```markdown
23292329+---
23302330+title: "Template: CI Tests"
23312331+description: Den's own test suite — the definitive reference for every feature.
23322332+---
23332333+23342334+The CI template is Den's comprehensive test suite. It tests every feature using [nix-unit](https://github.com/nix-community/nix-unit). This is the **best learning resource** for understanding exactly how Den behaves.
23352335+23362336+## Structure
23372337+23382338+```
23392339+flake.nix
23402340+modules/
23412341+ empty.nix # example test skeleton
23422342+ test-support/
23432343+ eval-den.nix # denTest + evalDen helpers
23442344+ nix-unit.nix # nix-unit integration
23452345+ features/
23462346+ angle-brackets.nix # <den/...> syntax
23472347+ conditional-config.nix # conditional imports/configs
23482348+ default-includes.nix # den.default behavior
23492349+ forward.nix # den._.forward
23502350+ homes.nix # standalone HM
23512351+ host-options.nix # host/user schema options
23522352+ namespaces.nix # namespace define/merge/export
23532353+ os-user-class.nix # user class forwarding
23542354+ parametric.nix # parametric functors
23552355+ schema-base-modules.nix # den.schema modules
23562356+ special-args-custom-instantiate.nix # custom instantiation
23572357+ top-level-parametric.nix # top-level context aspects
23582358+ user-host-bidirectional-config.nix # bidirectional providers
23592359+ batteries/
23602360+ define-user.nix # define-user battery
23612361+ flake-parts.nix # inputs' and self'
23622362+ import-tree.nix # import-tree battery
23632363+ primary-user.nix # primary-user battery
23642364+ tty-autologin.nix # tty-autologin battery
23652365+ unfree.nix # unfree packages
23662366+ user-shell.nix # user-shell battery
23672367+ context/
23682368+ apply.nix # ctx application
23692369+ apply-non-exact.nix # non-exact matching
23702370+ cross-provider.nix # cross-provider mechanism
23712371+ custom-ctx.nix # custom context types
23722372+ den-default.nix # den.default as context
23732373+ host-propagation.nix # full host pipeline
23742374+ named-provider.nix # self-named providers
23752375+ deadbugs/
23762376+ _external-namespace-deep-aspect.nix
23772377+ static-include-dup-package.nix
23782378+ home-manager/
23792379+ home-managed-home.nix
23802380+ use-global-pkgs.nix
23812381+ non-dendritic/ # non-den files for import-tree tests
23822382+ provider/ # external namespace provider flake
23832383+```
23842384+23852385+## Test Categories
23862386+23872387+### Core Features
23882388+23892389+| Test File | What It Tests |
23902390+|-----------|---------------|
23912391+| [conditional-config.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/conditional-config.nix) | Conditional imports using host/user attributes |
23922392+| [default-includes.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/default-includes.nix) | `den.default` applying to all hosts/users |
23932393+| [host-options.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/host-options.nix) | Custom host attributes, hostName, aspect names |
23942394+| [top-level-parametric.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/top-level-parametric.nix) | Context-aware top-level aspects |
23952395+| [parametric.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/parametric.nix) | All parametric functors (atLeast, fixedTo, expands) |
23962396+23972397+### Bidirectional & Providers
23982398+23992399+| Test File | What It Tests |
24002400+|-----------|---------------|
24012401+| [user-host-bidirectional-config.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/user-host-bidirectional-config.nix) | Host→user and user→host config flow |
24022402+| [context/cross-provider.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/cross-provider.nix) | Source providing config to target context |
24032403+| [context/named-provider.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/named-provider.nix) | Self-named provider mechanism |
24042404+24052405+### Context System
24062406+24072407+| Test File | What It Tests |
24082408+|-----------|---------------|
24092409+| [context/apply.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/apply.nix) | Context application mechanics |
24102410+| [context/apply-non-exact.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/apply-non-exact.nix) | Non-exact context matching |
24112411+| [context/custom-ctx.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/custom-ctx.nix) | User-defined context types with `into` |
24122412+| [context/den-default.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/den-default.nix) | `den.default` as a context type |
24132413+| [context/host-propagation.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/host-propagation.nix) | Full host pipeline with all contributions |
24142414+24152415+### Batteries
24162416+24172417+| Test File | What It Tests |
24182418+|-----------|---------------|
24192419+| [batteries/define-user.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/define-user.nix) | User definition across contexts |
24202420+| [batteries/primary-user.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/primary-user.nix) | Primary user groups |
24212421+| [batteries/user-shell.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/user-shell.nix) | Shell configuration |
24222422+| [batteries/unfree.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/unfree.nix) | Unfree package predicates |
24232423+| [batteries/tty-autologin.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/tty-autologin.nix) | TTY autologin service |
24242424+| [batteries/import-tree.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/import-tree.nix) | Auto-importing class dirs |
24252425+| [batteries/flake-parts.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/flake-parts.nix) | `inputs'` and `self'` providers |
24262426+24272427+### Advanced
24282428+24292429+| Test File | What It Tests |
24302430+|-----------|---------------|
24312431+| [angle-brackets.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/angle-brackets.nix) | All bracket resolution paths |
24322432+| [namespaces.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/namespaces.nix) | Local, remote, merged namespaces |
24332433+| [forward-from-custom-class.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/forward-from-custom-class.nix) | Custom class forwarding |
24342434+| [homes.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/homes.nix) | Standalone Home-Manager configs |
24352435+| [schema-base-modules.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/schema-base-modules.nix) | `den.schema.{host,user,home,conf}` |
24362436+24372437+### Bug Regressions
24382438+24392439+| Test File | What It Tests |
24402440+|-----------|---------------|
24412441+| [deadbugs/static-include-dup-package.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/deadbugs/static-include-dup-package.nix) | Duplicate deduplication for packages/lists |
24422442+| [deadbugs/_external-namespace-deep-aspect.nix](https://github.com/vic/den/blob/main/templates/ci/modules/features/deadbugs/_external-namespace-deep-aspect.nix) | Deep aspect access from external flakes |
24432443+24442444+### External Provider
24452445+24462446+The `provider/` subdirectory is a **separate flake** that defines a namespace `provider` with aspects. It's used by the deadbugs test to verify cross-flake namespace consumption:
24472447+24482448+```nix
24492449+# provider/modules/den.nix
24502450+{ inputs, ... }:
24512451+{
24522452+ imports = [ inputs.den.flakeModule (inputs.den.namespace "provider" true) ];
24532453+ provider.tools._.dev._.editors = {
24542454+ nixos.programs.vim.enable = true;
24552455+ };
24562456+}
24572457+```
24582458+24592459+## Running CI Tests
24602460+24612461+From the Den root against your local checkout:
24622462+24632463+```console
24642464+nix flake check --override-input den . ./templates/ci
24652465+```
24662466+24672467+You can also run a single or a subset of tests using:
24682468+24692469+```console
24702470+# You can use any attr-path bellow flake.tests after system-agnositc to run those specific tests:
24712471+nix-unit --override-input den . --flake ./templates/ci#.tests.systems.x86_64-linux.system-agnostic
24722472+```
24732473+24742474+## Writing New Tests
24752475+24762476+Copy `modules/empty.nix` as a starting point:
24772477+24782478+```nix
24792479+{ denTest, ... }:
24802480+{
24812481+ flake.tests.my-feature = {
24822482+ test-name = denTest (
24832483+ { den, igloo, ... }:
24842484+ {
24852485+ den.hosts.x86_64-linux.igloo.users.tux = { };
24862486+ expr = /* what you get */;
24872487+ expected = /* what you expect */;
24882488+ }
24892489+ );
24902490+ };
24912491+}
24922492+```
24932493+```
24942494+24952495+**File:** docs/src/content/docs/guides/migrate.mdx (L9-119)
24962496+```text
24972497+24982498+Migration to Den is incremental. You do not need to rewrite everything at once.
24992499+25002500+<Steps>
25012501+25022502+1. Add Den as Input
25032503+25042504+ Add Den to your flake and import the flake module:
25052505+25062506+ ```nix
25072507+ {
25082508+ inputs.den.url = "github:vic/den";
25092509+ inputs.import-tree.url = "github:vic/import-tree";
25102510+ inputs.flake-aspects.url = "github:vic/flake-aspects";
25112511+25122512+ outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; }
25132513+ (inputs.import-tree ./modules);
25142514+ }
25152515+ ```
25162516+25172517+ ```nix
25182518+ # modules/den.nix
25192519+ { inputs, ... }: {
25202520+ imports = [ inputs.den.flakeModule ];
25212521+ }
25222522+ ```
25232523+25242524+2. Declare Hosts
25252525+25262526+ Move your host declarations into `den.hosts`:
25272527+25282528+ ```nix
25292529+ {
25302530+ den.hosts.x86_64-linux.laptop.users.alice = { };
25312531+ }
25322532+ ```
25332533+25342534+3. Import Existing Modules
25352535+25362536+ Use `den.provides.import-tree` to load your existing non-dendritic modules:
25372537+25382538+ ```nix
25392539+ # modules/legacy.nix
25402540+ { den, ... }: {
25412541+ den.ctx.host.includes = [
25422542+ (den.provides.import-tree._.host ./hosts)
25432543+ ];
25442544+ den.ctx.user.includes = [
25452545+ (den.provides.import-tree._.user ./users)
25462546+ ];
25472547+ }
25482548+ ```
25492549+25502550+ With this directory structure:
25512551+25522552+ ```
25532553+ hosts/
25542554+ laptop/
25552555+ _nixos/
25562556+ hardware.nix
25572557+ networking.nix
25582558+ _homeManager/
25592559+ shell.nix
25602560+ users/
25612561+ alice/
25622562+ _homeManager/
25632563+ git.nix
25642564+ _nixos/
25652565+ groups.nix
25662566+ ```
25672567+25682568+ Files under `_nixos/` are imported as NixOS modules, `_homeManager/` as
25692569+ Home Manager modules, etc. This requires `inputs.import-tree`.
25702570+25712571+4. Extract Aspects
25722572+25732573+ Gradually extract features from your legacy modules into Den aspects:
25742574+25752575+ ```nix
25762576+ # modules/dev-tools.nix
25772577+ {
25782578+ den.aspects.dev-tools = {
25792579+ nixos = { pkgs, ... }: {
25802580+ environment.systemPackages = with pkgs; [ git vim tmux ];
25812581+ };
25822582+ homeManager.programs.git.enable = true;
25832583+ };
25842584+ }
25852585+ ```
25862586+25872587+ ```nix
25882588+ # modules/laptop.nix
25892589+ { den, ... }: {
25902590+ den.aspects.laptop.includes = [ den.aspects.dev-tools ];
25912591+ }
25922592+ ```
25932593+25942594+5. Remove Legacy
25952595+25962596+ As aspects replace legacy modules, remove the corresponding files from
25972597+ `hosts/` and `users/`. Eventually remove `den.provides.import-tree` usage.
25982598+25992599+</Steps>
26002600+26012601+## Tips
26022602+26032603+- **Start with one host.** Migrate a single machine first to learn the pattern.
26042604+- **Keep legacy working.** `import-tree` loads your existing files alongside
26052605+ Den aspects -- they coexist without conflicts.
26062606+- **Use batteries.** Replace manual user/shell/HM setup with `den.provides.*`.
26072607+- **Test with VM.** Use `nix run .#vm` to validate changes before applying to hardware.
26082608+```
26092609+26102610+**File:** docs/src/content/docs/reference/ctx.mdx (L63-142)
26112611+```text
26122612+## Built-in Context Types
26132613+26142614+### `den.ctx.host`
26152615+26162616+Context data: `{ host }`
26172617+26182618+Produced for each `den.hosts.<system>.<name>` entry.
26192619+26202620+Providers:
26212621+- `_.host` -- `fixedTo { host }` on the host's aspect.
26222622+- `_.user` -- `atLeast` on the host's aspect with `{ host, user }`.
26232623+26242624+Transitions:
26252625+- `into.default` -- identity (for default aspect).
26262626+- `into.user` -- one `{ host, user }` per `host.users` entry.
26272627+- `into.hm-host` -- (from `hm-os.nix`) if HM enabled and has HM users.
26282628+- `into.wsl-host` -- (from `wsl.nix`) if WSL enabled on NixOS host.
26292629+- `into.hjem-host` -- (from `hjem-os.nix`) if hjem enabled.
26302630+- `into.maid-host` -- (from `maid-os.nix`) if nix-maid enabled.
26312631+26322632+### `den.ctx.user`
26332633+26342634+Context data: `{ host, user }`
26352635+26362636+Providers:
26372637+- `_.user` -- `fixedTo { host, user }` on the user's aspect.
26382638+26392639+Transitions:
26402640+- `into.default` -- identity.
26412641+26422642+### `den.ctx.home`
26432643+26442644+Context data: `{ home }`
26452645+26462646+Produced for each `den.homes.<system>.<name>` entry.
26472647+26482648+Providers:
26492649+- `_.home` -- `fixedTo { home }` on the home's aspect.
26502650+26512651+### `den.ctx.hm-host`
26522652+26532653+Context data: `{ host }`
26542654+26552655+Providers:
26562656+- `provides.hm-host` -- imports HM OS module.
26572657+26582658+Transitions:
26592659+- `into.hm-user` -- per HM-class user.
26602660+26612661+### `den.ctx.hm-user`
26622662+26632663+Context data: `{ host, user }`
26642664+26652665+Providers:
26662666+- `_.hm-user` -- forwards `homeManager` class to `home-manager.users.<userName>`.
26672667+26682668+### `den.ctx.wsl-host`
26692669+26702670+Context data: `{ host }`
26712671+26722672+Providers:
26732673+- `provides.wsl-host` -- imports WSL module, creates `wsl` class forward.
26742674+26752675+## Custom Context Types
26762676+26772677+Define new contexts to extend the pipeline:
26782678+26792679+```nix
26802680+{
26812681+ den.ctx.gpu = {
26822682+ description = "GPU-enabled host";
26832683+ _.gpu = { host }: {
26842684+ nixos.hardware.nvidia.enable = true;
26852685+ };
26862686+ };
26872687+26882688+ den.ctx.host.into.gpu = { host }:
26892689+ lib.optional (host ? gpu) { inherit host; };
26902690+}
26912691+```
26922692+```
26932693+26942694+**File:** docs/src/content/docs/explanation/context-pipeline.mdx (L15-99)
26952695+```text
26962696+26972697+When Den evaluates a host, it walks a pipeline of context transformations:
26982698+26992699+```mermaid
27002700+flowchart TD
27012701+ start["den.hosts.x86_64-linux.laptop"] --> host["den.ctx.host {host}"]
27022702+ host -->|"_.host"| owned["Owned config: fixedTo {host} aspects.laptop"]
27032703+ host -->|"_.user"| userctx["atLeast aspects.laptop {host, user}"]
27042704+ host -->|"into.user"| user["den.ctx.user {host, user} (per user)"]
27052705+ user -->|"_.user"| userown["fixedTo {host,user} aspects.alice"]
27062706+ host -->|"into.hm-host"| hmhost["den.ctx.hm-host {host}"]
27072707+ hmhost -->|"import HM module"| hmmod["home-manager OS module"]
27082708+ hmhost -->|"into.hm-user"| hmuser["den.ctx.hm-user {host, user}"]
27092709+ hmuser -->|"forward homeManager class"| fwd["home-manager.users.alice"]
27102710+```
27112711+27122712+<Steps>
27132713+1. Host Context
27142714+27152715+ For each entry in `den.hosts.<system>.<name>`, Den creates a `{host}` context.
27162716+ The host context type (`den.ctx.host`) contributes:
27172717+27182718+ - `_.host` -- Applies the host's own aspect with `fixedTo { host }`, making
27192719+ all owned configs available for the host's class.
27202720+ - `_.user` -- For each user, applies the host's aspect with `atLeast { host, user }`,
27212721+ activating parametric includes that need both host and user.
27222722+27232723+2. User Context
27242724+27252725+ `into.user` maps each `host.users` entry into a `{host, user}` context.
27262726+ The user context type (`den.ctx.user`) contributes:
27272727+27282728+ - `_.user` -- Applies the user's own aspect with `fixedTo { host, user }`.
27292729+27302730+3. Derived Contexts
27312731+27322732+ Batteries register additional `into.*` transformations on the host context:
27332733+27342734+ | Transition | Condition | Produces |
27352735+ |---|---|---|
27362736+ | `into.hm-host` | `host.home-manager.enable && hasHmUsers` | `{host}` hm-host |
27372737+ | `into.hm-user` | Per HM-class user on hm-host | `{host, user}` hm-user |
27382738+ | `into.wsl-host` | `host.class == "nixos" && host.wsl.enable` | `{host}` wsl-host |
27392739+ | `into.hjem-host` | `host.hjem.enable && hasHjemUsers` | `{host}` hjem-host |
27402740+ | `into.hjem-user` | Per hjem-class user | `{host, user}` hjem-user |
27412741+ | `into.maid-host` | `host.nix-maid.enable && hasMaidUsers` | `{host}` maid-host |
27422742+ | `into.maid-user` | Per maid-class user | `{host, user}` maid-user |
27432743+27442744+ Each derived context can contribute its own aspect definitions and import
27452745+ the necessary OS-level modules (e.g., `home-manager.nixosModules.home-manager`).
27462746+27472747+3. Deduplication
27482748+27492749+ `dedupIncludes` in `modules/context/types.nix` ensures:
27502750+27512751+ - **First occurrence** of a context type uses `parametric.fixedTo`, which includes
27522752+ owned configs + statics + parametric matches.
27532753+ - **Subsequent occurrences** use `parametric.atLeast`, which only includes
27542754+ parametric matches (owned/statics already applied).
27552755+27562756+ This prevents `den.default` configs from being applied twice when the same
27572757+ aspect appears at multiple pipeline stages.
27582758+27592759+4. Home Configurations
27602760+27612761+ Standalone `den.homes` entries go through a separate path:
27622762+27632763+ ```mermaid
27642764+ flowchart TD
27652765+ home["den.homes.x86_64-linux.alice"] --> homectx["den.ctx.home {home}"]
27662766+ homectx --> resolve["fixedTo {home} aspects.alice"]
27672767+ resolve --> hmc["homeConfigurations.alice"]
27682768+ ```
27692769+27702770+ Home contexts have no host, so functions requiring `{ host }` are not activated.
27712771+ Functions requiring `{ home }` run instead.
27722772+27732773+5. Output
27742774+27752775+ `modules/config.nix` collects all hosts and homes, calls `host.instantiate`
27762776+ (defaults to `lib.nixosSystem`, `darwinSystem`, or `homeManagerConfiguration`
27772777+ depending on class), and places results into `flake.nixosConfigurations`,
27782778+ `flake.darwinConfigurations`, or `flake.homeConfigurations`.
27792779+27802780+</Steps>
27812781+```
27822782+27832783+**File:** docs/src/content/docs/tutorials/overview.md (L38-60)
27842784+```markdown
27852785+## Project Structure
27862786+27872787+Every template follows the same pattern:
27882788+27892789+```
27902790+flake.nix # or default.nix for noflake
27912791+modules/
27922792+ den.nix # host/user declarations + den.flakeModule import
27932793+ *.nix # aspect definitions, one concern per file
27942794+```
27952795+27962796+Den uses [import-tree](https://github.com/vic/import-tree) to recursively load all `.nix` files under `modules/`. You never need to manually list imports — just create files.
27972797+27982798+## What Each Template Demonstrates
27992799+28002800+- **minimal** — The absolute minimum: one host, one user, no extra dependencies
28012801+- **default** — Production-ready structure with Home-Manager, VM testing, dendritic flake-file
28022802+- **example** — Namespaces, angle brackets, cross-platform (NixOS + Darwin), providers
28032803+- **noflake** — Using Den with npins instead of flakes
28042804+- **microvm** — Demostrates Den extensibility showcasing MicroVM virtualization.
28052805+- **bogus** — Creating minimal reproductions for bug reports with nix-unit
28062806+- **ci** — Comprehensive tests covering every Den feature (your best learning resource)
28072807+28082808+```
28092809+28102810+**File:** docs/src/content/docs/reference/aspects.mdx (L59-106)
28112811+```text
28122812+## Aspect structure
28132813+28142814+An aspect is an attribute set with:
28152815+28162816+| Key | Purpose |
28172817+|-----|---------|
28182818+| `<class>` | Config merged into hosts/homes of that class |
28192819+| `includes` | List of modules or functions dispatched by context |
28202820+| `__functor` | Auto-generated by `parametric`; drives dispatch |
28212821+28222822+### Static vs parametric includes
28232823+28242824+Functions in `includes` receiving `{ class, aspect-chain }` are **static** --
28252825+evaluated once during aspect resolution. Functions receiving context
28262826+arguments (`{ host }`, `{ user }`, etc.) are **parametric** -- evaluated
28272827+per context during `ctxApply`.
28282828+28292829+## `den.provides`
28302830+28312831+Type: freeform `attrsOf providerType` (aliased as `den._`)
28322832+28332833+Batteries-included reusable aspects. Each provider is a `providerType`
28342834+from `flake-aspects`. See [Batteries Reference](/reference/batteries/).
28352835+28362836+```nix
28372837+den._ = {
28382838+ my-battery = {
28392839+ nixos.services.something.enable = true;
28402840+ includes = [ ./my-module.nix ];
28412841+ };
28422842+};
28432843+```
28442844+28452845+## Class resolution
28462846+28472847+When aspects are resolved for a host, Den:
28482848+28492849+1. Collects all aspects referenced by the host
28502850+2. Extracts the class-specific config (e.g., `nixos` for NixOS hosts)
28512851+3. Evaluates static includes with `{ class, aspect-chain }`
28522852+4. Builds context pairs from `den.ctx`
28532853+5. Applies parametric includes via `ctxApply`
28542854+6. Merges everything into the host's `evalModules` call
28552855+```
28562856+28572857+**File:** README.md (L113-237)
28582858+```markdown
28592859+## Code example (OS configuration domain)
28602860+28612861+### Defining hosts, users and homes.
28622862+28632863+```nix
28642864+den.hosts.x86_64-linux.lap.users.vic = {};
28652865+den.hosts.aarch64-darwin.mac.users.vic = {};
28662866+den.homes.aarch64-darwin.vic = {};
28672867+```
28682868+28692869+```console
28702870+$ nixos-rebuild switch --flake .#lap
28712871+$ darwin-rebuild switch --flake .#mac
28722872+$ home-manager switch --flake .#vic
28732873+```
28742874+28752875+### Extensible Schemas for hosts, users and homes.
28762876+28772877+```nix
28782878+# extensible base modules for common, typed schemas
28792879+den.schema.user = { user, lib, ... }: {
28802880+ config.classes =
28812881+ if user.userName == "vic" then [ "hjem" "maid" ]
28822882+ else lib.mkDefault [ "homeManager" ];
28832883+28842884+ options.mainGroup = lib.mkOption { default = user.userName; };
28852885+};
28862886+```
28872887+28882888+### Dendritic Multi-Platform Hosts
28892889+28902890+```nix
28912891+# modules/my-laptop.nix
28922892+{ den, inputs, ... }: {
28932893+ den.aspects.my-laptop = {
28942894+ # re-usable configuration aspects. Den batteries and yours.
28952895+ includes = [ den.provides.hostname den.aspects.work-vpn ];
28962896+28972897+ # regular nixos/darwin modules or any other Nix class
28982898+ nixos = { pkgs, ... }: { imports = [ inputs.disko.nixosModules.disko ]; };
28992899+ darwin = { pkgs, ... }: { imports = [ inputs.nix-homebrew.darwinModules.nix-homebrew ]; };
29002900+29012901+ # Custom Nix classes. `os` applies to both nixos and darwin. contributed by @Risa-G.
29022902+ # See https://den.oeiuwq.com/guides/custom-classes/#user-contributed-examples
29032903+ os = { pkgs, ... }: {
29042904+ environment.systemPackages = [ pkgs.direnv ];
29052905+ };
29062906+29072907+ # host can contribute default home environments to all its users.
29082908+ homeManager.programs.vim.enable = true;
29092909+ };
29102910+}
29112911+```
29122912+29132913+### Multiple User Home Environments
29142914+29152915+```nix
29162916+# modules/vic.nix
29172917+{ den, ... }: {
29182918+ den.aspects.vic = {
29192919+ # supports multiple home environments, eg: for migrating from homeManager.
29202920+ homeManager = { pkgs, ... }: { };
29212921+ hjem.files.".envrc".text = "use flake ~/hk/home";
29222922+ maid.kconfig.settings.kwinrc.Desktops.Number = 3;
29232923+29242924+ # user can contribute OS-configurations to any host it lives on
29252925+ darwin.services.karabiner-elements.enable = true;
29262926+29272927+ # user class forwards into {nixos/darwin}.users.users.<userName>
29282928+ user = { pkgs, ... }: {
29292929+ packages = [ pkgs.helix ];
29302930+ description = "oeiuwq";
29312931+ };
29322932+29332933+ includes = [
29342934+ den.provides.primary-user # re-usable batteries
29352935+ (den.provides.user-shell "fish") # parametric aspects
29362936+ den.aspects.tiling-wm # your own aspects
29372937+ den.aspects.gaming.provides.emulators
29382938+ ];
29392939+ };
29402940+}
29412941+```
29422942+29432943+### Custom Dendritic Nix Classes
29442944+29452945+[Custom classes](https://den.oeiuwq.com/guides/custom-classes) is how Den implements `homeManager`, `hjem`, `wsl`, `microvm` support. You can use the very same mechanism to create your own classes.
29462946+29472947+```nix
29482948+# Example: A class for role-based configuration between users and hosts
29492949+29502950+roleClass =
29512951+ { host, user }:
29522952+ { class, aspect-chain }:
29532953+ den._.forward {
29542954+ each = lib.intersectLists (host.roles or []) (user.roles or []);
29552955+ fromClass = lib.id;
29562956+ intoClass = _: host.class;
29572957+ intoPath = _: [ ];
29582958+ fromAspect = _: lib.head aspect-chain;
29592959+ };
29602960+29612961+den.ctx.user.includes = [ roleClass ];
29622962+29632963+den.hosts.x86_64-linux.igloo = {
29642964+ roles = [ "devops" "gaming" ];
29652965+ users = {
29662966+ alice.roles = [ "gaming" ];
29672967+ bob.roles = [ "devops" ];
29682968+ };
29692969+};
29702970+29712971+den.aspects.alice = {
29722972+ # enabled when both support gaming role
29732973+ gaming = { pkgs, ... }: { programs.steam.enable = true; };
29742974+};
29752975+29762976+den.aspects.bob = {
29772977+ # enabled when both support devops role
29782978+ devops = { pkgs, ... }: { virtualisation.podman.enable = true; };
29792979+29802980+ # not enabled at igloo host (bob missing gaming role on that host)
29812981+ gaming = {};
29822982+};
29832983+```