Skip to content

den.policies

See Policies for conceptual background.

Type: lazyAttrsOf policyFunction

A registry of named policies. Each entry is a function from context to a list of policy effects. The module system wraps bare functions into { __isPolicy = true; name; fn; } records automatically.

den.policies.my-policy = { host, ... }: [
(policy.resolve { enriched = true; })
];

Declaring a policy here only registers it. Activation happens via includes — see Activation.

All constructors are accessed via den.lib.policy. Policies return lists of these effects.

Create a new scope with bindings merged into the current context.

policy.resolve { myFlag = true; }

Variants:

  • policy.resolve.shared bindings — shared (non-isolated) fan-out
  • policy.resolve.to kind bindings — target a specific entity kind
  • policy.resolve.shared.to kind bindings — shared fan-out with explicit target kind
  • policy.resolve.withIncludes includes bindings — attach includes to the new scope
  • policy.resolve.to.withIncludes kind includes bindings — both

Inject an aspect into the current resolution:

policy.include { nixos.environment.variables.MARKER = "yes"; }

Remove an aspect via the constraint registry:

policy.exclude den.aspects.unwanted

The user-facing delivery primitive: declare one delivery edge from a source into a target class at a path, with an explicit mode. route and provide (below) are sugar shims over deliver.

# Class source — move a class's collected content (the `route` case):
policy.deliver {
from = "myClass"; # class name → collect that class's bucket
to = "nixos"; # target class
at = [ "services" "myService" ]; # attrpath ([] = merge at the class root)
mode = "nest"; # "merge" | "nest" | "verbatim"
}
# Module source — inject a NEW module (the `provide` case):
policy.deliver {
from = { module = { pkgs, ... }: { ... }; };
to = "nixos";
}

mode is exhaustive and explicit:

  • merge (default) — union into the target class bucket (use with at = []).
  • nest — evaluate and place the source at at.
  • verbatim — place the collected module wrappers by reference so a target whose option merge re-instantiates them (e.g. a microvm guest config) sees the live modules together with its base modules. There is no reinstantiate flag — verbatim is requested by mode directly. verbatim applies only to class sources, not module sources.

Class sources accept two optional fields:

  • guard = args: bool — only deliver when the predicate holds against the target’s module-system args (e.g. { options, ... }: options ? wsl). A failed guard means the delivered content contributes nothing.
  • adaptArgs = args: attrs — an args/specialArgs adapter for the delivered modules: with a non-empty at, the source modules are evaluated in a submodule whose specialArgs are adaptArgs applied to the target’s args (e.g. inject osConfig = config); at the root (at = []), it adapts the args each module function receives.

deliver deliberately does not expose appendToParent (parent-targeting is constructor-internal) — use a class source whose to already names the target.

Route class or quirk content from one scope partition into a target class. A permanent sugar shim over policy.deliverfromClassfrom, intoClassto, pathat, and reinstantiate = truemode = "verbatim":

policy.route {
fromClass = "myClass";
intoClass = "nixos";
path = [ "services" "myService" ];
}

Additional spec fields (passed through to the underlying edge):

  • intoPath = item: path — public alias for path that pairs with fromClass/intoClass (the forward battery’s naming). Passing both path and intoPath is an error.
  • guard / adaptArgs — same semantics as on deliver (see above).
  • collectSubtree = true — also collect the source class’s content from the scope’s descendant scopes, not just the scope itself. Used by the built-in flake-parts policy so per-system packages gather host/user contributions.

Deliver a module directly to a target class, bypassing the aspect tree. A permanent sugar shim over policy.deliver with a module source (classto, modulefrom.module, pathat; mode is derived from pathmerge at the root, nest under a non-empty path):

policy.provide { class = "nixos"; module = { pkgs, ... }: { ... }; }

Request post-pipeline instantiation. Pass an entity to use its own instantiate/intoAttr metadata:

policy.instantiate den.hosts.x86_64-linux.igloo

Or pass a custom spec to instantiate something that isn’t an entity (e.g. a third-party builder fed by a den class — the terranix pattern):

policy.instantiate {
name = "${host.name}-tf";
class = "terranix"; # which class bucket to collect
instantiate = { modules, ... }: modules; # how to build from collected modules
intoAttr = [ "terranixModules" host.name ]; # where the result lands
};

Request a deferred node spawn, resolved post-walk over the parent pipeline’s full scope-tree state (host + siblings) so projected content sees fleet-wide pipe values. classes defaults to the spawning entity’s classes at the drain site when null:

policy.spawn { }
policy.spawn { classes = [ "homeManager" ]; }

See den.quirks reference for the pipe builder API.

Tag a value with collisionPolicy = "class-wins" — when the value collides with a module-system arg (e.g., NixOS provides lib), the class value wins silently.


Policies are activated by including them in includes lists. The pipeline distinguishes policies from aspects by the __isPolicy tag.

# For all entities of a kind
den.schema.host.includes = [ den.policies.my-policy ];
# For a specific aspect's subtree
den.aspects.igloo.includes = [ den.policies.my-policy ];
# For all entities globally
den.default.includes = [ den.policies.my-policy ];

Policies and aspects mix freely in includes.

Create a named policy record for direct use in includes. Use this when defining inline policies that don’t need the den.policies registry:

den.default.includes = [
(den.lib.policy.mkPolicy "host-guards" ({ host, ... }: [
(den.lib.policy.resolve {
isNixos = host.class == "nixos";
isDarwin = host.class == "darwin";
})
]))
];

Returns { __isPolicy = true; name; fn; } — the pipeline recognizes this as a policy and routes it to the dispatch system.

Use excludes to prevent a policy from firing in a subtree:

den.aspects.igloo = {
excludes = [ den.policies.blocked ];
};

Excludes are authoritative — parent excludes cannot be overridden by child includes. The constraint registry uses the policy’s identity for matching.


Wrap a policy to fire only when a specific entity is in context, matched by id_hash:

den.lib.policy.for den.hosts.x86_64-linux.igloo den.policies.igloo-only

Accepts a single policy or a list. Identity is preserved through the wrapper.

den.lib.policy.when predicate policyOrList

Section titled “den.lib.policy.when predicate policyOrList”

Wrap a policy to fire only when a predicate over the current context returns true:

den.lib.policy.when ({ host, ... }: host.wsl.enable) den.policies.wsl-setup

Accepts a single policy or a list. Identity is preserved.

Combinators compose:

den.lib.policy.when (ctx: ctx.flag or false)
(den.lib.policy.for entity den.policies.my-policy)

PolicyFromToBehavior
host-to-usershostuserOne { host, user } per host.users entry

These fire automatically with their battery (most via den.default.includes, so they are active without any explicit inclusion):

PolicySourceBehavior
os-to-hostbatteries/os-classRoute os class content to the host’s class (nixos/darwin) in every host/user scope
user-to-hostbatteries/os-userRoute user class content to users.users.<userName>, injecting osConfig
host-to-hm-users / hm-user-detectbatteries/home-managerImport the HM OS module; forward each HM user’s homeManager class into home-manager.users.<name>. The *-user-detect policy covers users resolved by registry/policy rather than host.users. hjem and maid batteries register the same family (host-to-hjem-users, host-to-maid-users, …)
host-to-wsl-host / wsl-to-hostbatteries/wslResolve the wsl-host entity when host.wsl.enable; route wsl class content to the host’s OS at wsl

Flake-parts (policies/flake-parts.nix, opt-in via den.schema)

Section titled “Flake-parts (policies/flake-parts.nix, opt-in via den.schema)”
PolicyFromToBehavior
system-to-flake-partsflake-systemflake-partsOne { flake-parts } per system
packages-to-flake-partsflake-systemflake-partsRoute packages class into perSystem packages (collectSubtree)

The flake-scope battery carries its own internal policy (den-flake-scope) for pipeline-arg enrichment — see Batteries.

PolicyFromToBehavior
flake-to-systemsflakeflake-systemOne { system } per den.systems entry
system-to-os-outputsflake-systemhostOne { host } per host + instantiate
system-to-hm-outputsflake-systemhomeOne { home } per home + instantiate
packages-to-flakeflake-systemRoute packages class to flake output
apps-to-flakeflake-systemRoute apps class to flake output
checks-to-flakeflake-systemRoute checks class to flake output
devShells-to-flakeflake-systemRoute devShells class to flake output
legacyPackages-to-flakeflake-systemRoute legacyPackages class to flake output

Lightweight inspection utility that calls resolve functions directly without running the full pipeline:

den.lib.policyInspect.inspect {
kind = "host";
context = { host = den.hosts.x86_64-linux.igloo; };
}

Parameters:

  • kind — entity kind string
  • context — attrset representing the current pipeline context

Returns an attrset keyed by policy name with resolved targets, routing type, and other metadata.

Contribute Community Sponsor