Skip to content

den.aspects

An attribute set of aspects. Each aspect key names an aspect; its value is an aspect set containing per-class deferred modules and optional includes and provides:

den.aspects = {
dns = {
nixos.services.resolved.enable = true;
darwin.networking.dns = [ "1.1.1.1" ];
includes = [ ./dns ];
};
};

Den auto-generates den.aspects entries from den.hosts, den.homes, and the users declared under each host’s users. For every declared host/home/user, an aspect is created with the appropriate class configurations. You do not need to declare den.aspects manually unless adding shared aspects.

Type: attrsOf namespaceType (internal)

Namespaced aspect collections. Each key is a namespace name, each value is a namespace container whose freeform keys are aspects (it also carries schema, classes, and _). Populated by den.namespace or by merging upstream denful flake outputs.

den.ful.myns = {
some-aspect = { nixos.services.foo.enable = true; };
};

Type: attrsOf raw

Raw flake output for publishing namespaces. Set automatically by den.namespace; consumed by downstream flakes that import your aspects.

An aspect is an attribute set. Keys are either class keys (matching a registered class), nested aspect keys, or one of these structural keys:

KeyTypePurpose
<class>module / configConfig merged into entities of that class
includeslistOf providerProviders (aspects, sub-aspects, functions) pulled into this aspect
excludeslistOf unspecifiedAspects or policies excluded from this subtree
provides / _submoduleSub-aspect namespace (_ is an alias for provides)
policiespolicy registryNamed policy functions, activated by placing in includes
metasubmoduleAttached metadata (handleWith, provider, collisionPolicy)
classeslazyAttrsOf rawClass schemas declared by this aspect, merged into den.classes
name / descriptionstrAspect name and description

The pipeline also attaches internal __-prefixed fields (e.g. __args on parametric aspects, __functor to make merged aspects callable). These are implementation details, not authoring surface.

A plain module function in includes (module-system signature) is static — emitted once during aspect resolution. A function requesting scope context arguments ({ host }, { user }, { home }, etc.) is parametric — deferred and re-resolved per context as scope widens. See Parametric aspects.

Type: nullOr (handler record or list of handler records). Default: null.

Resolution handlers for the aspect’s subtree (current). Handlers compose down the tree — a parent’s constraint cannot be overridden by children.

Type: listOf str (internal, read-only). Default: the provider prefix.

Tracks the structural origin of an aspect as a path. Top-level aspects have meta.provider = []. An aspect provided by foo (via foo.provides.bar or its alias foo._.bar) has meta.provider = ["foo"]. Deeply nested providers accumulate: foo._.bar._.baz has meta.provider = ["foo" "bar"].

The meta.provider list distinguishes aspects by origin during pipeline resolution.

A collision occurs when a flat-form class module declares an arg (e.g. host) that the module system also provides — via specialArgs or _module.args. The collision policy selects what happens. See Class Modules for the flat-form mechanism.

The policy is resolved from three levels, first match wins:

LevelWhere to setType / defaultScope
Aspectmeta.collisionPolicy on the aspectnullOr (enum), default nullAll class modules in that aspect
EntitycollisionPolicy on the entity schema (den.schema.<kind>)nullOr (enum), default nullAll uses of that entity kind
Globalden.config.classModuleCollisionPolicyenum, default "error"Entire flake

A null at the aspect or entity level falls through to the next level. The global level supplies the effective default of "error".

The enum is the same at every level: "error", "class-wins", "den-wins".

ValueBehavior
"error"Throw an eval error (effective default)
"den-wins"Den value takes precedence; a warning is emitted
"class-wins"Module-system value takes precedence; a warning is emitted
# Aspect level — meta.collisionPolicy
den.aspects.my-aspect.meta.collisionPolicy = "den-wins";
# Entity level — den.schema.<kind>.collisionPolicy (all hosts)
den.schema.host.collisionPolicy = "den-wins";
# Global — den.config.classModuleCollisionPolicy
den.config.classModuleCollisionPolicy = "den-wins";

den.batteries (aliased as den.provides and den._)

Section titled “den.batteries (aliased as den.provides and den._)”

This is the place for Den built-in batteries, reusable aspects that serve as basic utilities and examples.

See Batteries Reference.

Entities (hosts, users, homes) expose .resolved — the aspect produced by their context pipeline. Forwards can use this to pull configuration from one entity into another without manually wiring context calls.

When den.batteries.forward is called without fromAspect, it defaults to item.resolved, resolving the source entity through its own context pipeline:

# Collect SSH host keys from all other hosts
den.aspects.iceberg.includes = [
({ host }:
den.batteries.forward {
each = lib.filter (h: h != host) (lib.attrValues den.hosts.${host.system});
fromClass = _: "ssh-host-key";
intoClass = _: host.class; # "nixos" or "darwin"
intoPath = _: [ ];
}
)
];

Each host in each is resolved via its .resolved attribute, and the ssh-host-key class content is forwarded into the target’s OS config. host.class is the OS class name ("nixos", "darwin"), not the context type.

Resolution is handled internally by the fx pipeline. den.lib.aspects.resolve still exists but is not a public API — call it directly only if you are building custom pipeline stages.

When the pipeline resolves aspects for an entity, it:

  1. Collects all aspects referenced by the entity
  2. Compiles each aspect into an effectful computation (fx.send "resolve")
  3. Evaluates static includes and defers parametric includes until context widens
  4. Walks the policy graph to fan out into derived stages
  5. Extracts the class-specific config (e.g., nixos for NixOS hosts)
  6. Merges everything into the entity’s evalModules call
Contribute Community Sponsor