Conversation
vic
commented
Feb 18, 2026
vic
commented
Feb 18, 2026
765a1f6 to
bef3f80
Compare
vic
commented
Feb 18, 2026
2ec0c05 to
c73014f
Compare
98f02d5 to
5a698df
Compare
c4c5a84 to
9bf8d2d
Compare
5e1a391 to
855fa67
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related #174
Declarative Context Definitions
This introduces
den.ctx, a declarative system for defining how context (data) is transformed and which aspects are applied at every stage of the configuration pipeline.The Problem
Before this release,
den.defaultserved two purposes:host,user, andhomeentities.{ host }to{ host, user }to{ host, user }in the HM pipeline, etc.As a consequence,
den.default.includeswas abused by many of us, including Den itself, because it was where context transformation happened [2]. This "dependency system" — parametric aspects installed unconditionally atden.default.includes— was hard to reason about, hard to document, and hard for people to understand.The symptoms were duplicate configuration values, caused by lax parametric functions matching too many pipeline stages.
What den.ctx Provides
den.defaultfor what it's good at: global settings. You can still useden.default.includes, but there are better alternatives now.den.default.includes: those parametric aspects were not individually testable, and you couldn't change how data flows. They were Den's hardcoded backbone.host, you declare how to enumerate users, detect HM support, etc.attrNames—{ host },{ host, user }. Now they have names:ctx.host,ctx.hm-host. Names allow different contexts with the same structural shape but different semantic guarantees.Named Contexts: Transform, Don't Validate
Named contexts carry semantic meaning beyond their structure.
ctx.host { host }andctx.hm-host { host }hold the same data, buthm-hostguarantees that home-manager support was validated:inputs.home-managerexists (or the host has a customhm-module)class = "homeManager"You cannot obtain an
hm-hostcontext unless these conditions hold. This follows the transform-don't-validate principle.How a Context Type Works
A context type has four components:
desc,conf,includes, andinto.When
ctx.foois applied — it works like a function taking{ foo }— it locates the responsible aspect viaconf. For example,ctx.foo { foo.name = "bar"; }usesmy-aspects.bar. The aspect's owned config, static includes, and parametric includes matching{ foo }all contribute to whateverctx.foois being used to configure.Context types are independent of NixOS. Den can be used as a library for network topologies, declarative cloud infrastructure, or anything describable as data transformations.
How a NixOS Configuration Is Built
The initial data for
nixosConfigurations.igloois the host itself:The result of
ctxApplyis a new aspect that includesden.aspects.iglooplus the entire transformation chain — user enumeration, HM detection, defaults.These two steps can be adapted for any class, for anything Nix-configurable.
Context Propagation
Context transformation is declarative. If your data fans out to other contexts, you specify the transformations using
.into:All
<source>.into.<target>transformations are taken into account byctxApply.Why Lists?
Transformations have the type
source → [ target ]. This enables:{ host, user }contexts (map)lib.optional)lib.singleton)For example, HM detection uses conditional propagation:
Same data, but the named context guarantees validation passed.
Contexts as Aspect Cutting-Points
Contexts are aspect-like themselves. They have owned configs and
.includes:This is like
den.default.includesbut scoped — it only activates for hosts with validated home-manager support.Extending the Context Flow
You can add new transformations to any existing context type:
The module system merges these definitions. You can extend the pipeline without modifying any built-in file.
Custom Context Flows
Each host has a
mainModuleoption that defaults to:You can override
mainModuleto use a completely alternative context flow, independent ofctx.host. Custom flows can be designed and tested in isolation — Den's CI uses afunny.namesclass that has nothing to do with NixOS to verify context mechanics independently.What Happened to den.default?
den.defaultstays and is still useful for truly global settings. The issue was abusingden.default.includesas the context propagation backbone.Internal Changes
Previously, all host, user, and home aspects had:
Now they no longer include
den.defaultdirectly. Includingden.defaultexplicitly is discouraged.How Defaults Are Applied Now
Each context type transforms into
default:den.defaultis now an alias forden.ctx.default. The data that flows intoden.default.includescomes from these declarative transformations, not from direct aspect inclusion.Best Practices
den.default.includes = [ hostFunc ]den.ctx.host.includes = [ hostFunc ]den.default.includes = [ hmFunc ]den.ctx.hm-host.includes = [ hmFunc ]den.default.nixos.x = 1den.ctx.host.nixos.x = 1den.defaultremains the right place for values that genuinely apply everywhere — likestateVersion. Use context-specific includes for anything that belongs to a particular pipeline stage.