Skip to content

Resolution Pipeline

When Den evaluates a host, it runs a resolution pipeline driven by policies and entities. Policies are directed edges that fan context out to downstream entity kinds; each entity kind binds behavior for its resolved context.

flowchart TD
  start["den.hosts.x86_64-linux.laptop"] --> host["host {host}"]
  host -->|"host-to-users"| user["user {host, user} (per user)"]
  host -->|"host-to-hm-users"| hmfwd["forward each homeManager user"]
  hmfwd --> fwd["into home-manager.users.alice"]
  1. Host resolution

    For each entry in den.hosts.<system>.<name>, the pipeline creates a host scope. The host’s own aspect is resolved via den.schema.host.includes, binding owned configs for the host’s class.

  2. Core policy fans out to users

    The one core traversal edge (modules/policies/core.nix) is host-to-users, registered as a den.schema.host.includes entry:

    PolicyFromToResolve
    host-to-usershostuserOne edge per host.users entry

    A policy receives the current context and returns a list of downstream resolves. host-to-users fans out one { host, user } pair per user declared on the host (via resolve.shared). If the host aspect has a freeform key matching a user’s name, that sub-aspect is also included in the user scope.

  3. Battery policies forward user environments

    The home-environment batteries share a factory, makeHomeEnv (nix/lib/home-env.nix), which produces a host-scope fan-out policy and a user-scope detect policy. The fan-out policy fires when the battery is enabled, the host OS is supported, and the host has at least one user of the battery’s class:

    PolicyConditionEffect
    host-to-hm-usersHM enabled, host has homeManager-class usersForward each HM user into home-manager.users.<name>
    hm-user-detectPer homeManager-class userApply user schema includes + forward
    host-to-hjem-usershjem enabled, host has hjem-class usersForward each user into hjem.users.<name>
    hjem-user-detectPer hjem-class userApply user schema includes + forward
    host-to-maid-usersnix-maid enabled (NixOS), host has maid-class usersForward each user into users.users.<name>.maid
    maid-user-detectPer maid-class userApply user schema includes + forward

    Rather than creating a two-stage *-host*-user entity-kind chain, the fan-out policy resolves each matching user directly and emits a forward into the target namespace. The battery’s OS module (e.g. home-manager.nixosModules.home-manager) is imported once via a keyed module wrapper, so it fires a single time even when included from multiple user resolves.

    WSL is the exception: host-to-wsl-host (modules/aspects/batteries/wsl.nix) does create a wsl-host entity kind (resolve.to "wsl-host") when a NixOS host sets wsl.enable, importing the NixOS-WSL module into the host class.

  4. Deduplication

    The pipeline tracks a seen set of include keys, each keyed by "${scope}/${identityKey}" (the current scope plus the aspect’s identity). The first time a named aspect is included in a scope, the full aspect (owned configs + statics + parametric matches) is resolved. Subsequent includes of the same aspect in the same scope are skipped, so a shared aspect (e.g. one reachable from den.default) is not applied twice within a scope.

    Because the key is scope-prefixed, an aspect can still be included in different scopes — entity levels reached through different policies are isolated and each gets its own copy.

  5. Home configurations

    Standalone den.homes entries follow a separate path. They are resolved by the flake policies (see step 6) rather than via a host, and their aspect resolves through den.schema.home includes:

    flowchart TD
      home["den.homes.x86_64-linux.alice"] --> homestage["home {home}"]
      homestage --> hmc["homeConfigurations.alice"]

    Home scopes have no host in context, so policies and provides requiring { host } are not activated. Shared defaults still apply because den.default is one of the home schema includes.

  6. Output

    Flake-level policies (modules/policies/flake.nix) drive the final assembly. flake-to-systems fans out one flake-system scope per entry in den.systems. For each system, system-to-os-outputs resolves the declared hosts and system-to-hm-outputs resolves the standalone homes. Each entity is instantiated by its instantiate function (defaulting to nixosSystem/darwinSystem for hosts and homeManagerConfiguration for homes, depending on class) and placed at the entity’s intoAttr path — flake.nixosConfigurations, flake.darwinConfigurations, or flake.homeConfigurations.

Contribute Community Sponsor