Policy Activation Deep Dive
Registry vs activation
Section titled “Registry vs activation”den.policies is a registry — a named collection of policy values.
Registering a policy does not activate it:
# This only registers. The policy will never fire.den.policies.my-policy = { host, ... }: [ (policy.resolve { flag = true; })];Activation happens when a policy value appears in an includes list:
# Now it fires for all hostsden.schema.host.includes = [ den.policies.my-policy ];This separation is the core design principle: policies are reusable values that can be activated, deactivated, and filtered independently of where they’re defined.
How policies become values
Section titled “How policies become values”The module system wraps bare functions into tagged records:
den.policies.foo = { host, ... }: [ ... ] ↓ (module system merge){ __isPolicy = true; name = "foo"; fn = { host, ... }: [ ... ]; }The __isPolicy tag lets the pipeline distinguish policies from aspects
in includes lists. Both are valid include entries — the pipeline
checks the tag and routes accordingly.
Activation paths
Section titled “Activation paths”Policies can be activated at any level of the include hierarchy:
| Level | Where | Scope |
|---|---|---|
| Global | den.default.includes | All entities |
| Per entity kind | den.schema.host.includes | All hosts |
| Per aspect | den.aspects.igloo.includes | The igloo subtree |
| Inline | { includes = [ den.policies.foo ]; } | The enclosing aspect |
Cascading
Section titled “Cascading”When a policy is activated at a scope, it cascades to all descendant
scopes. A policy in den.schema.host.includes fires at the host scope
and is also available at user scopes resolved from that host.
Aspect-level policies
Section titled “Aspect-level policies”Aspects can define policies inline using the policies attribute:
den.aspects.igloo = { policies.to-users = { host, user, ... }: lib.optional someCondition (policy.include { nixos.some.option = true; });
includes = [ den.aspects.igloo.policies.to-users ];};The policy is registered on the aspect and activated via includes in
the same definition.
Deactivation with excludes
Section titled “Deactivation with excludes”excludes is a first-class top-level key on aspects, symmetric with
includes. It prevents policies from firing in the aspect’s subtree:
den.aspects.igloo = { includes = [ den.policies.add-marker ]; excludes = [ den.policies.add-marker ];};Authoritative semantics
Section titled “Authoritative semantics”Parent excludes are authoritative — child scopes cannot re-enable an excluded policy:
parentAspect = { excludes = [ den.policies.blocked ]; includes = [ childAspect ];};
childAspect = { # This does NOT re-enable the policy. The parent's exclude wins. includes = [ den.policies.blocked ];};This prevents downstream aspects from bypassing security or correctness constraints established by parent scopes.
Identity through wrappers
Section titled “Identity through wrappers”Excludes match on policy identity. When a policy is wrapped with
policy.for or policy.when, the inner policy’s identity is preserved:
wrapped = den.lib.policy.for entity den.policies.my-policy;
# This exclude still works — it matches the inner policy identityexcludes = [ den.policies.my-policy ];Conditional firing
Section titled “Conditional firing”den.lib.policy.for entity policy
Section titled “den.lib.policy.for entity policy”Wraps a policy to fire only when a specific entity is in context.
Matching uses id_hash for robust identity comparison:
den.schema.host.includes = [ (den.lib.policy.for den.hosts.x86_64-linux.igloo den.policies.igloo-specific)];The wrapper checks whether any entity kind key in the current context
has a matching id_hash. If not, the policy returns [].
Accepts a single policy value or a list:
den.lib.policy.for entity [ den.policies.policy-a den.policies.policy-b]# Returns a list of two wrapped policiesden.lib.policy.when predicate policy
Section titled “den.lib.policy.when predicate policy”Wraps a policy to fire only when a predicate returns true:
den.schema.host.includes = [ (den.lib.policy.when ({ host, ... }: host.wsl.enable) den.policies.wsl-support)];The predicate receives the full context attrset. If it returns false,
the policy returns [].
Composing wrappers
Section titled “Composing wrappers”Wrappers compose — when wrapping for wrapping a raw policy:
den.lib.policy.when (ctx: ctx.flag or false) (den.lib.policy.for entity den.policies.my-policy)The outer wrapper runs first. If the predicate fails, the inner wrapper never executes.
Dispatch cycle
Section titled “Dispatch cycle”When the pipeline reaches an entity scope, policy dispatch follows a fixed-point iteration:
- Collect — gather all active policies from the scope’s include chain (direct includes, schema includes, default includes).
- Check satisfaction — for each policy, introspect its function args. A policy fires only when all required args (non-default) are present in the current context.
- Fire — call satisfied policies, collect their effects.
- Enrich — if any effects add new context bindings
(
policy.resolvewith enrichment), merge them viascope.provideand drain deferred includes. - Iterate — check if new bindings satisfy previously-unsatisfied policies. If so, fire them and repeat from step 4.
- Converge — when no new policies fire, emit all collected effects.
This fixed-point iteration means policies can depend on context that other policies provide. The iteration is capped at 10 rounds to prevent infinite loops.
Self-exclusion invariant
Section titled “Self-exclusion invariant”A policy does not apply to its own outputs. The source policy name is
threaded through the resolve chain, seeding firedPolicies at child
scopes. This prevents infinite recursion when a policy’s effects would
satisfy its own firing condition.
Policy include dedup
Section titled “Policy include dedup”Policy includes are deduplicated via identity tags. Each policy include
gets a <policy:name:idx> identity key. Anonymous policy includes get
global dedup keys. This prevents the same policy from being registered
multiple times when included from multiple scopes.
See also
Section titled “See also”- Policies — conceptual overview
- den.policies reference — effect types and built-in policies
- CI tests —
policy-excludes.nix,policy-combinators.nix,policy-include-routing.nix