commits
This introduces `parametric.withOwn` that is just a `parametric.atLeast`
that also includes the aspect owned configs.
See discussion:
https://github.com/vic/den/discussions/91#discussioncomment-14985716
Calling `parametric x` itself is an alias for `parametric.withOwn x`.
So that both syntax do the same:
```
den.aspects.foo = {
__functor = den.lib.parametric;
nixos.foo = 1;
includes = [ bar ];
};
```
and also, **this is the preferred* syntax that will be documented, since
it is more intuitive and does not surfaces `__functor`:
```
den.aspects.foo = den.lib.parametric {
nixos.foo = 1;
includes = [ bar ];
};
```
Still have to update documentation about this combinator.
Fixes #97.
See https://github.com/vic/den/discussions/91 for context.
See
[failure](https://github.com/vic/bugs-den/actions/runs/19377336327/job/55448108522)
for
[reproduction](https://github.com/vic/bugs-den/commit/f81dca61403c7a08e5381b61bcb2fedc1994efb4)
Support for top-level contextual aspects was added at
https://github.com/vic/flake-aspects/pull/14.
Closes #92.
Fixes #87
Edit `modules/vm.nix`
Closes #82
to be more precise about it providing a fixed context to all its
included functions.
Fixes #84.
This PR removes the directional contexts `fromHost` and `fromUser`. Now
people can include funcitons like:
```nix
den.default.includes = [
({ user, ...}: { nixos.some-array = [ user.name ]; })
];
```
And values are not duplicated now. Added test based on report from #84.
This also simplified a lot the number of contexts we have. Still have to
update documentation about contexts.
This also removes outdated `_profile` directory. Closes #78. Instead our
default template now includes an `eg/routes.nix` example router and
exercises namespaces and angle-brackets, ensuring via tests that they
work.
split into several files, more dendritic.
This helps people find aspect implementation, aspect usage and test in one file.
Closes #73
Available at https://vic.github.io/den
Closes #66. Partially, still need to split README into several pages.
Our new default template is much better suited to be used as a starting
point for people trying out den.
Our previous default template is now named `examples`. It is still
useful as reference of how to use den and is used for CI.
Fixes #67
( Closes #56, #55 ) -- (Pending #58)
This PR introduces a fundamental refactoring of the aspect dependency and context management system. The primary goal is to provide a more robust, explicit, and powerful way to handle how different parts of the configuration (hosts, users, home-manager) interact with each other. This leads to clearer and more predictable evaluations, but also introduces significant breaking changes from previous unified context `userToHost` and `hostToUser`.
> [!IMPORTANT]
> The previous context objects `userToHost` and `hostToUser` prove to be harder to use and have been **completely removed**.
Now contexts are incremental and context now are used for **configuration intent**. For example, a NixOS configuration starts with context `{ OS }` but later expands to `{ OS, user, host }` as soon as the OS has information of which users and hosts it is being applied to. See the examples on aspects.nix for how this replaces `userToHost` and `hostToUser`.
> [!IMPORTANT]
> Since contexts are now _incremental_, they are also no longer _exact_ by default.
What this means: In previous releases, a function `{ host }: { }` context had to match exactly. For example if applied to `{ host = ...; foo = ... }` it would not apply since `foo` is unexpected. This was as to avoid `function called with unexpectd "foo"` errors. However now contexts can be incremental.
A function `{ host, ... }: { }` provides anytime a `host` is in context.
A function `{ host, user, ... }: { }` provides when at least both are in context.
For example, you can use `{ user, ... }: ...` to provide configurations based on user data anytime it is in context.
**OS/HM Intent Contexts**
OS and HM configurations now start with a single `{ OS }` and `{ HM }` contexts. They are higher-level contexts used to describe the **Intent** of the configuration being produced. This, in hand with incremental contexts, allows to get rid of `hostToUser` and `userToHost`.
For example a context `{ OS, user, host }` is explicit about it being a configuration depending on host/user data but also that it is **being used** for providing NixOS/Darwin configuration specifically.
`OS`/`HM` are not used in our provided batteries at all. Instead the batteries rely only on `host`/`user`/`home` data being in context, fixing the complexity that was introduced by previous `hostToUser` and `userToHost`.
`OS`/`HM` are useful for opt-in bi-directional dependencies, this is how we avoid duplicating settings now. Since a function `{ user, host, ...}: { }` can be installed in `den.default`, and `den.default` is included for any User and Host. That function would produce duplicate configurations - this is correct since the function is **explicitly set in den.default** which is included twice: once for the user and once for the host. To avoid this, either install the function on a specific `den.aspect.<host/user>.includes` or use a higher **intent context** like `OS`/`HM`. See how our aspects.nix examples were updated for this.
## refactor: Dependency Resolution Engine
The core dependency resolution logic in `modules/aspects/dependencies.nix` has been rewritten to be more explicit and prevent recursion.
- It now uses `take.exactly` with the new `OS` and `HM` contexts to ensure that dependency-gathering functions run only once at the appropriate level, preventing evaluation loops.
- The logic for how users contribute to a host's configuration (`osIncludesFromUsers`) and how a host contributes to a user's home-manager configuration (`hmIncludesFromHost`) is now clearly defined and separated.
- The new engine correctly passes the intent `OS` and `HM` contexts down to the aspects being resolved.
## refactor: `provides` Modules Updated for New Context
All built-in `provides` modules have been updated to use the new intent-based context system.
- `provides/define-user`: Now uses generic data-specific `{ host, user, ... }` and `{ home, ... }` contexts directly, removing the need for the intermediate `userToHostContext`.
- `provides/home-manager`: Now passes the `{ HM }` **intent** context when resolving a user's `homeManager` aspect.
- `provides/primary-user`: Logic simplified to expect `{ user, host, ... }` instead of `userToHost`.
- `provides/user-shell`: The logic has been unified. A single `userShell` function now correctly applies shell configuration to `nixos`, `darwin`, and `homeManager` attributes based on the context it receives (`{ user, ... }` or `{ home, ... }`).
## test: Enhanced `canTake` Tests
The test suite for `function_can_take.nix` has been expanded to include assertions for the new `takes.exactly` functionality, ensuring that it correctly identifies functions that take an exact set of arguments.
## feat: Granular Context Control with `take` and `parametric`
A new set of library functions, `den.lib.take` and a redesigned `den.lib.parametric`, have been introduced to give aspect authors fine-grained control over how and when their functions are applied.
- **`den.lib.take`**: This is a new functor that allows a function to declare precisely what context it expects.
- `take.atLeast`: The function is applied if the context provides *at least* the required arguments. This is the default behavior for most parametric functions.
- `take.exactly`: The function is applied only if the context provides the *exact* set of required arguments, with no extras. This is crucial for preventing infinite recursion in dependency resolution.
- `take.unused`: A utility to signal which arguments from the context are "consumed" by the function, allowing for clearer composition.
- **`den.lib.parametric`**: The `parametric` functor has been completely overhauled to leverage `take`.
- It now offers `atLeast` and `exactly` variants to create aspects that apply their includes based on these context matching rules.
- The syntax `parametric true` is now an alias for `parametric.atLeast`.
This new system replaces the older, less precise `canTake` logic, providing a more declarative and safer way to write parametric aspects.
## BREAKING CHANGE: Intent Context (`OS` and `HM`)
The context passed to parametric aspects is no longer a flat attribute set. It has been restructured into namespaced contexts to clearly separate concerns between operating system configuration and home-manager configuration.
- **`OS` Context**: When evaluating for a host's operating system configuration (NixOS or `nix-darwin`), aspects now receive an `OS` attribute in their context. The original `host` object is available as `OS.host`.
- **`HM` Context**: When evaluating for a user's `home-manager` configuration, aspects receive an `HM` attribute. This context contains both the `user` and the `host` they belong to (`HM.user` and `HM.host`).
The previous context objects `userToHost` and `hostToUser` proven to be harder to use and have been **completely removed**. And now contexts are incremental. For example, a NixOS configuration starts with context `{ OS }` but later expands to `{ OS, user, host }` as soon as the OS has information of which users and hosts it is being applied to. See the examples on aspects.nix for how this replaces `userToHost` and `hostToUser`.
### Migration Guide
All parametric aspects must be updated to expect the new context structure.
**Before:**
```nix
my-aspect = { userToHost, ... }: {
${userToHost.host.class}.services.foo.enable = true;
};
my-other-aspect = { hostToUser, ... }: {
homeManager.programs.bar.enable = true;
};
```
**After:**
```nix
# OS context is needed only if my-aspect is installed at den.default
my-aspect = { OS, user, host, ... }: {
${host.class}.services.foo.enable = true;
};
# HM intent context is needed only if installed into den.default.
my-other-aspect = { HM, user, host, ... }: {
homeManager.programs.bar.enable = true;
};
```
Instead of previous: `{ fromUser, toHost }` and `{ fromHost, toUser }`.
The reason is detailed at #47.
Closes #47.
This is in preparation for contexts being incremental,
an aspect can add (merge) additional data to an existing context.
for example, a host configuration starts with { host } but later for each of it users it merges currentContext // { user } leading to { host, user } augmented context.
This allows contexts to be incremental, so parametric-aspects (functions) can specify what is the context they need:
{ user, ...} - At least user data in context.
{ user, host, gaming } - Need the three of them.
Will invalidate and close #51.
Breaking Change
Your existing functions taking context like { host } will likely need to be { host, ... } now. See this PR diff for how batteries were updated.
Enhance README with clearer aspect descriptions
Updated README to clarify aspects and configurations.
See #40, #42.
### Changes for fix:
Our [dependencies](https://github.com/vic/den/blob/deps/modules/aspects/dependencies.nix) system is now separate from the actual aspect [definitions](https://github.com/vic/den/blob/deps/modules/aspects/definition.nix).
Added a [test](https://github.com/vic/den/blob/deps/templates/default/modules/_example/aspects.nix#L42) that checks only [one hello package](https://github.com/vic/den/blob/deps/templates/default/modules/_example/ci.nix#L100) is contributed to the config.
Basically we have more types of context now to avoid the recursion:
`{ host }` is for Host configuration (likely nixos/darwin)
`{ host, user }` is for User config (most likely homeManager)
`{ home }` is for standalone home-manager.
`{ fromUser, toHost }` is for users contributing configuration to hosts.
`{ fromHost, toUser }` is for hosts contributing to its users.
Using these more explicit contexts instead of how we previously only used `{ host, user }` for the bi-directional dependencies. Fixed this.
So basically, contexts need to be exact, `{ host, ...}` is just `{ host }`, according to `lib.functionArgs` there's no way to tell them apart.
If you look at the definition of host/user/home aspects, they only include `den.default`.
And we have some parametric defaults that add configuration depending on context,
for example, [this one](https://github.com/vic/den/blob/deps/modules/aspects/dependencies.nix#L16) just adds the host-same-class values from `den.defaults` into a host.
our `den.default` is how dependencies work via parametric functions. So, in den the argument names (the context) of configuration providers is quite important, that's how they know what kind of configuration to produce.
Here's the output of running [my fork](https://github.com/Cybolic/nix-den-double-import-demo/pull/1) of the reproduction repository:
```console
vic@nargun ~/h/nix-den-double-import-demo> nix eval --override-input den github:vic/den/deps --refresh .#.nixosConfigurations.test-host.config.users.users.test-user.packages
warning: not writing modified lock file of flake 'git+file:///home/vic/hk/nix-den-double-import-demo':
• Updated input 'den':
'github:vic/den/04dfe099cc7d4f773f4fd50bdf0571791a2e9fad?narHash=sha256-pM0/ri%2B4LPrZUIv9c8Bh4%2BRyB2VMGyChXrxyIP4mbjs%3D' (2025-11-05) → 'github:vic/den/3dfb65c872655fe0f3853a6229ceda32cd4045ed?narHash=sha256-1UUIU8nddcNTqGr2Wz3ASVXxbBIafeU/fGSelhd1GpA%3D' (2025-11-06)
trace: [ "host" ]
trace: [ "aspect-chain" "class" ]
trace: [ "fromUser" "toHost" ]
[ «derivation /nix/store/dzgpbp0vp7lj7lgj26rjgmnjicq2wf4k-hello-2.12.2.drv» ]
```
Allows writing `<foo/bar/baz>` instead of `den.aspects.foo.provides.bar.provides.baz` or even `den.aspects.foo._.bar._.baz`.
See the [tested example usage](templates/default/modules/_example/import-non-dendritic.nix)
This introduces `parametric.withOwn` that is just a `parametric.atLeast`
that also includes the aspect owned configs.
See discussion:
https://github.com/vic/den/discussions/91#discussioncomment-14985716
Calling `parametric x` itself is an alias for `parametric.withOwn x`.
So that both syntax do the same:
```
den.aspects.foo = {
__functor = den.lib.parametric;
nixos.foo = 1;
includes = [ bar ];
};
```
and also, **this is the preferred* syntax that will be documented, since
it is more intuitive and does not surfaces `__functor`:
```
den.aspects.foo = den.lib.parametric {
nixos.foo = 1;
includes = [ bar ];
};
```
Still have to update documentation about this combinator.
Fixes #97.
See https://github.com/vic/den/discussions/91 for context.
See
[failure](https://github.com/vic/bugs-den/actions/runs/19377336327/job/55448108522)
for
[reproduction](https://github.com/vic/bugs-den/commit/f81dca61403c7a08e5381b61bcb2fedc1994efb4)
Support for top-level contextual aspects was added at
https://github.com/vic/flake-aspects/pull/14.
Closes #92.
Fixes #84.
This PR removes the directional contexts `fromHost` and `fromUser`. Now
people can include funcitons like:
```nix
den.default.includes = [
({ user, ...}: { nixos.some-array = [ user.name ]; })
];
```
And values are not duplicated now. Added test based on report from #84.
This also simplified a lot the number of contexts we have. Still have to
update documentation about contexts.
( Closes #56, #55 ) -- (Pending #58)
This PR introduces a fundamental refactoring of the aspect dependency and context management system. The primary goal is to provide a more robust, explicit, and powerful way to handle how different parts of the configuration (hosts, users, home-manager) interact with each other. This leads to clearer and more predictable evaluations, but also introduces significant breaking changes from previous unified context `userToHost` and `hostToUser`.
> [!IMPORTANT]
> The previous context objects `userToHost` and `hostToUser` prove to be harder to use and have been **completely removed**.
Now contexts are incremental and context now are used for **configuration intent**. For example, a NixOS configuration starts with context `{ OS }` but later expands to `{ OS, user, host }` as soon as the OS has information of which users and hosts it is being applied to. See the examples on aspects.nix for how this replaces `userToHost` and `hostToUser`.
> [!IMPORTANT]
> Since contexts are now _incremental_, they are also no longer _exact_ by default.
What this means: In previous releases, a function `{ host }: { }` context had to match exactly. For example if applied to `{ host = ...; foo = ... }` it would not apply since `foo` is unexpected. This was as to avoid `function called with unexpectd "foo"` errors. However now contexts can be incremental.
A function `{ host, ... }: { }` provides anytime a `host` is in context.
A function `{ host, user, ... }: { }` provides when at least both are in context.
For example, you can use `{ user, ... }: ...` to provide configurations based on user data anytime it is in context.
**OS/HM Intent Contexts**
OS and HM configurations now start with a single `{ OS }` and `{ HM }` contexts. They are higher-level contexts used to describe the **Intent** of the configuration being produced. This, in hand with incremental contexts, allows to get rid of `hostToUser` and `userToHost`.
For example a context `{ OS, user, host }` is explicit about it being a configuration depending on host/user data but also that it is **being used** for providing NixOS/Darwin configuration specifically.
`OS`/`HM` are not used in our provided batteries at all. Instead the batteries rely only on `host`/`user`/`home` data being in context, fixing the complexity that was introduced by previous `hostToUser` and `userToHost`.
`OS`/`HM` are useful for opt-in bi-directional dependencies, this is how we avoid duplicating settings now. Since a function `{ user, host, ...}: { }` can be installed in `den.default`, and `den.default` is included for any User and Host. That function would produce duplicate configurations - this is correct since the function is **explicitly set in den.default** which is included twice: once for the user and once for the host. To avoid this, either install the function on a specific `den.aspect.<host/user>.includes` or use a higher **intent context** like `OS`/`HM`. See how our aspects.nix examples were updated for this.
## refactor: Dependency Resolution Engine
The core dependency resolution logic in `modules/aspects/dependencies.nix` has been rewritten to be more explicit and prevent recursion.
- It now uses `take.exactly` with the new `OS` and `HM` contexts to ensure that dependency-gathering functions run only once at the appropriate level, preventing evaluation loops.
- The logic for how users contribute to a host's configuration (`osIncludesFromUsers`) and how a host contributes to a user's home-manager configuration (`hmIncludesFromHost`) is now clearly defined and separated.
- The new engine correctly passes the intent `OS` and `HM` contexts down to the aspects being resolved.
## refactor: `provides` Modules Updated for New Context
All built-in `provides` modules have been updated to use the new intent-based context system.
- `provides/define-user`: Now uses generic data-specific `{ host, user, ... }` and `{ home, ... }` contexts directly, removing the need for the intermediate `userToHostContext`.
- `provides/home-manager`: Now passes the `{ HM }` **intent** context when resolving a user's `homeManager` aspect.
- `provides/primary-user`: Logic simplified to expect `{ user, host, ... }` instead of `userToHost`.
- `provides/user-shell`: The logic has been unified. A single `userShell` function now correctly applies shell configuration to `nixos`, `darwin`, and `homeManager` attributes based on the context it receives (`{ user, ... }` or `{ home, ... }`).
## test: Enhanced `canTake` Tests
The test suite for `function_can_take.nix` has been expanded to include assertions for the new `takes.exactly` functionality, ensuring that it correctly identifies functions that take an exact set of arguments.
## feat: Granular Context Control with `take` and `parametric`
A new set of library functions, `den.lib.take` and a redesigned `den.lib.parametric`, have been introduced to give aspect authors fine-grained control over how and when their functions are applied.
- **`den.lib.take`**: This is a new functor that allows a function to declare precisely what context it expects.
- `take.atLeast`: The function is applied if the context provides *at least* the required arguments. This is the default behavior for most parametric functions.
- `take.exactly`: The function is applied only if the context provides the *exact* set of required arguments, with no extras. This is crucial for preventing infinite recursion in dependency resolution.
- `take.unused`: A utility to signal which arguments from the context are "consumed" by the function, allowing for clearer composition.
- **`den.lib.parametric`**: The `parametric` functor has been completely overhauled to leverage `take`.
- It now offers `atLeast` and `exactly` variants to create aspects that apply their includes based on these context matching rules.
- The syntax `parametric true` is now an alias for `parametric.atLeast`.
This new system replaces the older, less precise `canTake` logic, providing a more declarative and safer way to write parametric aspects.
## BREAKING CHANGE: Intent Context (`OS` and `HM`)
The context passed to parametric aspects is no longer a flat attribute set. It has been restructured into namespaced contexts to clearly separate concerns between operating system configuration and home-manager configuration.
- **`OS` Context**: When evaluating for a host's operating system configuration (NixOS or `nix-darwin`), aspects now receive an `OS` attribute in their context. The original `host` object is available as `OS.host`.
- **`HM` Context**: When evaluating for a user's `home-manager` configuration, aspects receive an `HM` attribute. This context contains both the `user` and the `host` they belong to (`HM.user` and `HM.host`).
The previous context objects `userToHost` and `hostToUser` proven to be harder to use and have been **completely removed**. And now contexts are incremental. For example, a NixOS configuration starts with context `{ OS }` but later expands to `{ OS, user, host }` as soon as the OS has information of which users and hosts it is being applied to. See the examples on aspects.nix for how this replaces `userToHost` and `hostToUser`.
### Migration Guide
All parametric aspects must be updated to expect the new context structure.
**Before:**
```nix
my-aspect = { userToHost, ... }: {
${userToHost.host.class}.services.foo.enable = true;
};
my-other-aspect = { hostToUser, ... }: {
homeManager.programs.bar.enable = true;
};
```
**After:**
```nix
# OS context is needed only if my-aspect is installed at den.default
my-aspect = { OS, user, host, ... }: {
${host.class}.services.foo.enable = true;
};
# HM intent context is needed only if installed into den.default.
my-other-aspect = { HM, user, host, ... }: {
homeManager.programs.bar.enable = true;
};
```
This is in preparation for contexts being incremental,
an aspect can add (merge) additional data to an existing context.
for example, a host configuration starts with { host } but later for each of it users it merges currentContext // { user } leading to { host, user } augmented context.
This allows contexts to be incremental, so parametric-aspects (functions) can specify what is the context they need:
{ user, ...} - At least user data in context.
{ user, host, gaming } - Need the three of them.
Will invalidate and close #51.
Breaking Change
Your existing functions taking context like { host } will likely need to be { host, ... } now. See this PR diff for how batteries were updated.
See #40, #42.
### Changes for fix:
Our [dependencies](https://github.com/vic/den/blob/deps/modules/aspects/dependencies.nix) system is now separate from the actual aspect [definitions](https://github.com/vic/den/blob/deps/modules/aspects/definition.nix).
Added a [test](https://github.com/vic/den/blob/deps/templates/default/modules/_example/aspects.nix#L42) that checks only [one hello package](https://github.com/vic/den/blob/deps/templates/default/modules/_example/ci.nix#L100) is contributed to the config.
Basically we have more types of context now to avoid the recursion:
`{ host }` is for Host configuration (likely nixos/darwin)
`{ host, user }` is for User config (most likely homeManager)
`{ home }` is for standalone home-manager.
`{ fromUser, toHost }` is for users contributing configuration to hosts.
`{ fromHost, toUser }` is for hosts contributing to its users.
Using these more explicit contexts instead of how we previously only used `{ host, user }` for the bi-directional dependencies. Fixed this.
So basically, contexts need to be exact, `{ host, ...}` is just `{ host }`, according to `lib.functionArgs` there's no way to tell them apart.
If you look at the definition of host/user/home aspects, they only include `den.default`.
And we have some parametric defaults that add configuration depending on context,
for example, [this one](https://github.com/vic/den/blob/deps/modules/aspects/dependencies.nix#L16) just adds the host-same-class values from `den.defaults` into a host.
our `den.default` is how dependencies work via parametric functions. So, in den the argument names (the context) of configuration providers is quite important, that's how they know what kind of configuration to produce.
Here's the output of running [my fork](https://github.com/Cybolic/nix-den-double-import-demo/pull/1) of the reproduction repository:
```console
vic@nargun ~/h/nix-den-double-import-demo> nix eval --override-input den github:vic/den/deps --refresh .#.nixosConfigurations.test-host.config.users.users.test-user.packages
warning: not writing modified lock file of flake 'git+file:///home/vic/hk/nix-den-double-import-demo':
• Updated input 'den':
'github:vic/den/04dfe099cc7d4f773f4fd50bdf0571791a2e9fad?narHash=sha256-pM0/ri%2B4LPrZUIv9c8Bh4%2BRyB2VMGyChXrxyIP4mbjs%3D' (2025-11-05) → 'github:vic/den/3dfb65c872655fe0f3853a6229ceda32cd4045ed?narHash=sha256-1UUIU8nddcNTqGr2Wz3ASVXxbBIafeU/fGSelhd1GpA%3D' (2025-11-06)
trace: [ "host" ]
trace: [ "aspect-chain" "class" ]
trace: [ "fromUser" "toHost" ]
[ «derivation /nix/store/dzgpbp0vp7lj7lgj26rjgmnjicq2wf4k-hello-2.12.2.drv» ]
```