Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
ff84f9e
feat: add gen-schema flake input to CI template
sini May 20, 2026
3a51e30
feat: port den.schema to gen-schema mkSchemaOption
sini May 21, 2026
828fba0
feat: support flat host declarations alongside legacy two-level form
sini May 21, 2026
a4280bd
feat: port den.homes to support flat and legacy two-level forms
sini May 21, 2026
ba06283
test: add entity gen-schema port tests
sini May 21, 2026
04af0f4
chore: rename den-schema to gen-schema
sini May 21, 2026
06f71f3
feat: isEntity as settable sidecar with computed fallback
sini May 22, 2026
c6c1090
feat: update gen-schema, add parent sidecars for user/home, fix compu…
sini May 22, 2026
c3e18be
feat: reserve 'settings' as structural key on aspects
sini May 22, 2026
cf913f7
feat: den.reservedKeys — user-extensible structural keys for aspects
sini May 22, 2026
a448877
fix: update for gen-schema collections rename and nix-effects v0.12
sini May 25, 2026
3db285a
chore: update gen-schema
sini May 27, 2026
6bd8a08
chore: update flake.lock
sini May 28, 2026
dca996c
chore: update flake.lock
sini May 28, 2026
c48412c
refactor(entities): own id_hash via gen-schema mkInstanceType
sini Jun 10, 2026
dac6c26
fix(aspects): reserved keys pass their values through unwrapped
sini Jun 10, 2026
3aa188d
fix(policy): restore `self` guard dropped in rebase
sini Jun 10, 2026
a9b5c26
refactor(schema): derive kind lists from gen-schema _kindNames
sini Jun 10, 2026
658c13d
feat(delivered-child-host): dedicated guest kind + route delivery (on…
sini Jun 11, 2026
f740356
feat(aspects): restore entity.aspects accessor (dropped in rebase)
sini Jun 11, 2026
418c6ea
feat(schema): kind-level isolated marker, recorded per scope by push-…
sini Jun 11, 2026
35ed459
feat(fx): isolation-aware subtree collection + appendToParent route d…
sini Jun 11, 2026
a621056
feat(delivered-child-host): isolated nixos-class guest, drop guest-os…
sini Jun 11, 2026
a5cf9c9
docs: spawn-node isolation invariant + guest-os removal breaking note
sini Jun 11, 2026
d278135
fix(route): deliver re-instantiating routes verbatim and keyed
sini Jun 11, 2026
5777b4e
test(delivered-child-host): re-instantiation regression guard
sini Jun 11, 2026
58cb699
fix(hasAspect): drop ancestor entity-kinds from projected scopeId
sini Jun 12, 2026
ab212dc
refactor(route): keep reinstantiate flag in core, drop delivered-chil…
sini Jun 12, 2026
48bf339
refactor(hasAspect): key projected path-set by entity id_hash, not sc…
sini Jun 12, 2026
1f9584d
feat(fx): pure arg classifier over the schema entity DAG
sini Jun 12, 2026
761c90f
feat(fx): synchronous relationship fan-out for descendant entity args
sini Jun 12, 2026
13e79ca
refactor(fx)!: remove cross-scope entity-arg deferral; class-local em…
sini Jun 12, 2026
643d805
refactor(context)!: remove deprecated perCtx/perHost/perUser/perHome
sini Jun 12, 2026
fa53f19
refactor(fx): kind-generic spawn materialization (host-aspects decoup…
sini Jun 12, 2026
ae40856
refactor(fx): generic root-owner lookup for projected hasAspect
sini Jun 12, 2026
ee4299f
docs+test(fx): correct fan-out emission-scope doc; isolate shared-inc…
sini Jun 12, 2026
ff49ac3
style: fix indentation from rebase auto-merge in home.nix
sini Jun 12, 2026
821ab6a
feat(schema): resolve gen-schema with CI-lock fallback, project via d…
sini Jun 12, 2026
ac8a479
feat(fx): edge-trace extractor v0 + delivery-edges suite
sini Jun 12, 2026
ccdd8eb
refactor(fx): edge-trace reuses exported dedupProvides
sini Jun 12, 2026
339bce2
test(fx): rewalk edge fixture; harden isolation corollary; edge-trace…
sini Jun 13, 2026
1988b55
refactor(fx): delete write-only delivery state
sini Jun 13, 2026
955f350
test(fx): drop dead scopedConstraintFilters seeds from fixtures
sini Jun 13, 2026
1c989c2
refactor(fx): unify scope-subtree walks + key-dedup
sini Jun 13, 2026
8c6c0a1
refactor(fx): single stage interpreter, provenance as a value functor
sini Jun 13, 2026
52b6df2
docs(fx): note bug-for-bug fidelity of provenance passthrough
sini Jun 13, 2026
888cd26
feat(fx): edge materializer + default-fold port
sini Jun 13, 2026
c222774
fix(fx): assembleSubtree consumes shared default-fold constructor; pi…
sini Jun 13, 2026
04f4e5f
feat(fx): simple routes as delivery edges; triple-pass dissolved
sini Jun 13, 2026
3ebea25
chore(fx): route-port hygiene — dead branches, stale comments
sini Jun 13, 2026
90edb08
feat(fx): provides + complex forwards as delivery edges
sini Jun 13, 2026
faf69fe
docs(fx): fix stale post-move comment paths
sini Jun 13, 2026
8d71925
feat(fx): spawn projection as assembleSubtree over blind dedup-free e…
sini Jun 13, 2026
383c752
docs(fx): clarify mode-switch invariant carve-out for Pi-builders
sini Jun 13, 2026
f643ad5
refactor(fx): dissolve findHostScopeId via scope-creation link
sini Jun 13, 2026
389ea6a
feat(fx): instantiates as flake-output T-arm edges via shared constru…
sini Jun 13, 2026
75cd440
fix(fx): B′ peer configs over assembled contexts, not raw (§A #8/#2/#7)
sini Jun 13, 2026
4332b29
refactor(fx): delivery-half dead-code sweep + extractor convergence
sini Jun 13, 2026
ecf0097
docs(fx): de-stale pi.scopeContexts comment (tasks done)
sini Jun 13, 2026
cf3bab6
docs(fx): replace bare task tag with sourceVia rationale
sini Jun 13, 2026
b02827c
feat(fx): deliver primitive; route/provides as shims
sini Jun 13, 2026
8610aff
test+docs(fx): positive controls on deliver throw tests; provide mode…
sini Jun 13, 2026
4bb2deb
docs(fx): correct B′ fix label to option b (was mislabeled option c)
sini Jun 13, 2026
b02d56d
chore(entities,fx): drop unused reservedSystems inherit + orphaned bl…
sini Jun 13, 2026
f426083
feat(context): restore perHost/perUser/perHome shim (rule-correct); d…
sini Jun 13, 2026
7761d08
fix(templates): flake-parts-modules pins github:denful/den, not path:…
sini Jun 13, 2026
a7e86b9
chore(templates): refresh stale den pins; noflake npins vic→denful
sini Jun 13, 2026
c3e9ca0
fix(fx): scope conditional-guard hasAspect to the entity's subtree (#…
sini Jun 13, 2026
3d1cd17
docs(fx): correct guard/pathSetByScope comments post-#613; mark get-p…
sini Jun 13, 2026
9548cd9
test(deadbugs): move issue-609 to deadbugs; docs: drop explicit bug ref
sini Jun 14, 2026
944141e
docs(fx): drop task-ledger refs from doc comments
sini Jun 14, 2026
5e760d9
refactor(fx): remove dead get-path-set effect + toPathSet
sini Jun 14, 2026
99910a5
refactor(fx): trim internal-only exports from edge modules
sini Jun 14, 2026
2f02090
test: remove empty legacy test stubs
sini Jun 14, 2026
80ce28c
fix(hasAspect): key in-context projection by active scope, not per-en…
sini Jun 14, 2026
82b8b57
test(hasAspect): matrix coverage for projected host-binding axis
sini Jun 14, 2026
eb29097
test(fanout): cover cartesian independent-descendant product (+neg)
sini Jun 14, 2026
21b0602
fix(fanout): transitive descendant fan-out (DAG nesting)
sini Jun 14, 2026
29f4c0c
refactor(fanout): lift parent-kind + fan-order into arg-class
sini Jun 14, 2026
1d2b6f7
refactor(fx): derive flat pathSet from pathSetByScope; drop the field
sini Jun 14, 2026
56cc4f7
refactor: collapse duplicated ancestry walks + deepMergeAttrs
sini Jun 14, 2026
954e783
feat(fx): mkStaticPi builder for the static Π(root) projection (Task 15)
sini Jun 14, 2026
48ebf84
refactor(fx): both inline Π sites build on mkStaticPi (Task 15, byte-…
sini Jun 14, 2026
020e85c
feat(fx): assembleSpawnSubtree surfaces its constructed edge set (Tas…
sini Jun 14, 2026
567af71
feat(fx): mkInstantiateEdges surfaces per-host + B' edge sets (Task 16)
sini Jun 14, 2026
60189fd
feat(fx): unifiedEdges(root) collector — union incl. surfaced fold ed…
sini Jun 14, 2026
3ddc518
feat(fx): topoSortEdges — record-level cross-kind/field/root toposort…
sini Jun 14, 2026
206aa52
test(fx): Task 16 edge-unification gate — completeness + valid order …
sini Jun 14, 2026
ef9c2f6
feat(fx): materializeUnified — ordered-dispatch engine + stable topoS…
sini Jun 14, 2026
681885c
refactor(fx): fxResolveFull folds materializeUnified, drops phase2/3 …
sini Jun 14, 2026
4cfe13b
refactor(fx): fxResolveImports folds materializeUnified (Task 17)
sini Jun 14, 2026
ef2abb9
refactor(fx): per-host mkInstantiateArgs folds materializeUnified (Ta…
sini Jun 14, 2026
114a478
refactor(fx): spawn assembleSpawnSubtree folds materializeUnified (Ta…
sini Jun 14, 2026
ba8dbc7
refactor(fx): collapse per-host/spawn phase folds into materializeUni…
sini Jun 15, 2026
170e996
feat(fx): materializeUnified exposes its folded edge records (Task 18)
sini Jun 15, 2026
765f034
feat(fx): edgeTrace renders captured production edges; legacy as oracle
sini Jun 15, 2026
869e16e
test(fx): harden oracle≡production differential — strip suppressed tw…
sini Jun 15, 2026
acf6cef
docs(fx): correct oracle/production doc-comments post-Task-18
sini Jun 15, 2026
e543c55
feat(fx): assertEdgeParity helper + parity corpus identity gate (Task…
sini Jun 15, 2026
551ccd5
fix(fx): entity-isolate exclude/substitute application (#613 analog)
sini Jun 15, 2026
4d6aba9
refactor(fx): unify constraint registry — one entity-scoped lookup, d…
sini Jun 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/src/content/docs/explanation/parametric.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,35 @@ den.aspects.firewall = {
};
```

## The binding rule

When a parametric aspect at some scope **S** destructures an **entity-kind** arg
(`host`, `user`, `home`, …), exactly one of three things happens:

1. **In-context → bind once at S.** If the kind is already in S's context (e.g.
a `{ user, … }` aspect included at a user scope), the arg binds and the aspect
emits once, at S.
2. **Schema-DAG descendant → fan out, emit at S.** If the kind is a *descendant*
of S in the entity schema (e.g. a `{ user, … }` aspect included at the **host**
scope — users live under hosts), the aspect fans out once per matching
descendant, each emitting **class-locally at S**. This is how a host-scope
`{ user, … }` aspect produces per-user content on the host.
3. **Neither → inert, silently.** If the kind is neither in-context nor a
descendant of S — including a misplaced arg, or **any entity-kind arg at the
root/flake scope** — the aspect contributes nothing. There is **no warning**
(a warning was considered and deliberately rejected: legitimate fan-out and
misplacement are indistinguishable without whole-fleet context, so a warning
would fire on correct code).

<Aside type="caution" title="The silent-inert footgun">
Because case 3 is silent, a parametric aspect that destructures an entity kind
**not reachable from where it is included** simply does nothing — no error, no
warning. If a parametric aspect "isn't applying," check that the arg's kind is
in-context or a schema descendant of the include scope. Cross-entity delivery
(e.g. a host wanting to configure a *sibling* host) is **not** expressible this
way — use `provides` or a policy.
</Aside>

## Where Parametric Args Work

<Aside type="tip" title="Aspect level only">
Expand Down
10 changes: 6 additions & 4 deletions docs/src/content/docs/guides/debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,14 @@ nix-repl> cfg.networking.hostName
from `den.default`, but parametric functions in `den.default.includes`
run at every context stage. The pipeline handles dispatch automatically
based on function argument shape — write a bare function with the context
args you need. (`den.lib.perHost` is deprecated.)
args you need. The `den.lib.perHost`, `perUser`, and `perHome` shims still
exist but are deprecated (they warn and forward to the current binding rule);
prefer plain parametric aspects:

```nix
# Deprecated: den.lib.perHost ({ host }: { nixos.x = 1; })
# Modernbare function; only runs in host contexts:
({ host }: { nixos.x = 1; })
# Deprecated (warns): den.lib.perHost, den.lib.perUser, den.lib.perHome
# Preferredplain parametric aspect; binds host at its scope / fans out:
({ host, ... }: { nixos.x = 1; })
```

**Missing attribute**: The context does not have the expected parameter.
Expand Down
13 changes: 13 additions & 0 deletions docs/src/content/docs/guides/home-manager.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,16 @@ den.hosts.x86_64-linux.laptop = {

Both `homeManager` and `hjem` configurations from `den.aspects.alice` will
be forwarded to their respective targets.

## Host-scope parametric aspects no longer deliver homeManager content to users

Earlier releases let a `{ user, ... }` aspect at host scope leak its
`homeManager` content to each of the host's users. A host-scope parametric
aspect now fans out over the users but emits class-locally **on the host**,
where `homeManager` is inert -- so that content never reaches the users'
Home Manager evaluation. This matches the resolver's emission rule: the bound
user is the arg source, not the output target.

If you were relying on that leak to push home content from a host-scope aspect,
route it through an explicit `to-users` policy, `provides`, or the host-aspects
battery instead.
Comment thread
sini marked this conversation as resolved.
24 changes: 17 additions & 7 deletions docs/src/content/docs/reference/lib-deprecated.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -105,22 +105,32 @@ helper (does not warn).

## Context shortcuts

Run an aspect only at a specific context level. Each returns a parametric wrapper
(declaring deeper context keys as optional) that yields `{}` when resolved at a
deeper level, and warns. Prefer a plain function with the level's keys, e.g.
`({ host, user, ... }: { ... })`.
Wrap an aspect so it requires a specific set of context keys. Each returns a
parametric wrapper and **warns**. They are now thin aliases for a plain function
with the same keys — i.e. `den.lib.perHost f` behaves as `({ host, ... }: f { … })`:
the arg binds once at the emitting scope if in-ctx, fans out class-locally over
that scope's descendants otherwise, and is inert if misplaced. Prefer the plain
function form, e.g. `({ host, user, ... }: { ... })`.

<Aside type="caution" title="Semantics changed">
Earlier releases made these wrappers *self-suppress* — yield `{}` when resolved
at a deeper context level. That was the cross-scope-deferral behavior fixed in
the binding-half rewrite (see [Parametric Aspects](/explanation/parametric/)).
The shims no longer self-suppress; they deliver the current binding rule. A
config that relied on the old silent `{}` will now see the wrapped aspect bind.
</Aside>

### `den.lib.perHost aspect`

Run `aspect` only in `{ host }` contexts.
Wrap `aspect` to require `{ host }`.

### `den.lib.perUser aspect`

Run `aspect` only in `{ host, user }` contexts.
Wrap `aspect` to require `{ host, user }`.

### `den.lib.perHome aspect`

Run `aspect` only in `{ home }` contexts.
Wrap `aspect` to require `{ home }`.

## `den.lib.resolveStage`

Expand Down
45 changes: 43 additions & 2 deletions docs/src/content/docs/reference/policies.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,47 @@ Remove an aspect via the constraint registry:
policy.exclude den.aspects.unwanted
```

### `policy.deliver spec`

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`.

```nix
# 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.

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

### `policy.route spec`

Route class or quirk content from one scope partition into a target class:
Route class or quirk content from one scope partition into a target class. A
permanent sugar shim over [`policy.deliver`](#policydeliver-spec) —
`fromClass`→`from`, `intoClass`→`to`, `path`→`at`, and `reinstantiate = true`→
`mode = "verbatim"`:

```nix
policy.route {
Expand All @@ -80,7 +118,10 @@ policy.route {

### `policy.provide spec`

Deliver a module directly to a target class, bypassing the aspect tree:
Deliver a module directly to a target class, bypassing the aspect tree. A
permanent sugar shim over [`policy.deliver`](#policydeliver-spec) with a module
source (`class`→`to`, `module`→`from.module`, `path`→`at`; `mode` is derived
from `path` — `merge` at the root, `nest` under a non-empty `path`):

```nix
policy.provide { class = "nixos"; module = { pkgs, ... }: { ... }; }
Expand Down
99 changes: 65 additions & 34 deletions modules/context/has-aspect.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,45 @@ let

entityModule =
{ config, ... }:
let
# Prefer `classes` (list), fall back to `[class]`, else error.
classes =
config.classes or (
if config ? class then
[ config.class ]
else
throw "den.schema.conf.hasAspect: entity has no `class` or `classes`"
);
primaryClass =
if classes == [ ] then
throw "den.schema.conf.hasAspect: entity has empty `classes` list"
else
lib.head classes;
# Lazy thunk throwing at call time (not attribute access), so the
# accessors can be referenced safely by tooling and only fire when
# actually invoked.
err = throw (
"hasAspect: ${config.name or "<unnamed entity>"} has no config.resolved "
+ "(no matching den.schema.<kind> defined)."
);
# Shared record: `hasAspect` (functor) and `aspects` (node list) are both
# read off `info`, so the per-class fxFullResolve runs once and is reused.
info =
if config ? resolved then
den.lib.aspects.mkEntityHasAspect {
tree = config.resolved;
inherit primaryClass classes;
}
else
{
__functor = _: _: err;
forClass = _: _: err;
forAnyClass = _: err;
aspects = err;
aspectsForClass = _: err;
allAspects = err;
};
in
{
options.hasAspect = lib.mkOption {
description = ''
Expand All @@ -30,40 +69,32 @@ let
readOnly = true;
type = lib.types.raw;
defaultText = lib.literalMD "Computed from `config.resolved` and the entity's class/classes.";
default =
let
# Prefer `classes` (list), fall back to `[class]`, else error.
classes =
config.classes or (
if config ? class then
[ config.class ]
else
throw "den.schema.conf.hasAspect: entity has no `class` or `classes`"
);
primaryClass =
if classes == [ ] then
throw "den.schema.conf.hasAspect: entity has empty `classes` list"
else
lib.head classes;
# Lazy thunk throwing at call time (not attribute access), so
# entity.hasAspect / .forClass / .forAnyClass can be referenced
# safely by tooling and only fire when actually invoked.
err = throw (
"hasAspect: ${config.name or "<unnamed entity>"} has no config.resolved "
+ "(no matching den.schema.<kind> defined)."
);
in
if config ? resolved then
den.lib.aspects.mkEntityHasAspect {
tree = config.resolved;
inherit primaryClass classes;
}
else
{
__functor = _: _: err;
forClass = _: _: err;
forAnyClass = _: err;
};
default = info;
};

options.aspects = lib.mkOption {
description = ''
The flat list of all resolved aspect nodes on this entity (every
depth), each the resolved node augmented with:
.identity # base FQN, ctx-stripped — e.g. "roles/workstation"
.identityKey # full unique key incl {ctxId} (distinguishes anons)
.isNamed # false for anonymous aspects

Each node also retains its `.name`, `.meta`, and `.includes` (its
resolved subtree), so callers can inspect, navigate, and re-include
it. Excludes the entity root and excluded/tombstoned aspects;
anonymous aspects are included.

Same cyclic caveat as `hasAspect`: do not use to decide an aspect's
own `includes`. Reading it post-resolution (module bodies, batteries)
is safe.
'';
internal = true;
visible = false;
readOnly = true;
type = lib.types.raw;
defaultText = lib.literalMD "Computed from `config.resolved` and the entity's primary class.";
default = info.aspects;
};
};
in
Expand Down
56 changes: 26 additions & 30 deletions modules/context/perHost-perUser.nix
Original file line number Diff line number Diff line change
@@ -1,45 +1,41 @@
# DEPRECATED: scheduled for removal after first stable release post-fx-pipeline merge.
# Migration: use den.schema.host / den.schema.user directly.
# Deprecated context-level guards.
# Under handler-based resolution, bind.fn resolves each arg independently.
# Optional args with no handler are skipped (nix-effects c7931d7), so
# the function body can detect context level by checking which keys
# were resolved.
# DEPRECATED context guards — kept as a thin compatibility shim.
#
# `den.lib.perHost` / `perUser` / `perHome` shipped in earlier releases. They
# are RESTORED here as aliases over the current binding rule so existing configs
# keep evaluating (and get a deprecation warning steering them to plain
# functions). Migration: use a plain function — `({ host, ... }: { ... })`.
#
# IMPORTANT — semantics changed (this is the #609 fix): the old shim returned
# `{}` whenever a deeper context key was present (a self-suppressing emulation
# of cross-scope deferral). That behavior was the bug the binding-half rewrite
# removed. The restored shim drops the self-suppression: an entity-kind arg now
# binds once at the emitting scope if in-ctx, fans out class-locally over the
# scope's descendants otherwise, and is inert if misplaced — exactly as a plain
# `{ host, ... }:` function does. So `den.lib.perHost f` is now an alias for
# `{ host, ... }: f { inherit host; }`, not the old suppress-at-deeper-scope
# guard. Configs relying on the old silent suppression were relying on #609.
{ lib, ... }:
let
# Known context keys. Keys not in the required set are declared as
# optional — if their handlers exist (deeper level), the function
# detects the extras and returns {} (no-op).
allContextKeys = [
"host"
"user"
"home"
];

# Build a parametric wrapper requiring exactly `requiredKeys` (all required —
# no optional "extra" keys, hence no self-suppression). The fx bind handler
# binds these per the current rule and applies `__fn` with the resolved args.
perCtx =
requiredKeys: aspect:
let
reqKeysSorted = builtins.sort builtins.lessThan requiredKeys;
extraKeys = builtins.filter (k: !(builtins.elem k reqKeysSorted)) allContextKeys;
# Required keys as required (false), extra keys as optional (true)
funcArgs = lib.genAttrs reqKeysSorted (_: false) // lib.genAttrs extraKeys (_: true);
reqSorted = builtins.sort builtins.lessThan requiredKeys;
in
lib.warn
"den.lib.perCtx [${lib.concatStringsSep "," reqKeysSorted}] is deprecated — use a plain function ({ ${lib.concatStringsSep ", " reqKeysSorted}, ... }: ...) instead; handler-based resolution resolves context args automatically"
"den.lib.perCtx [${lib.concatStringsSep "," reqSorted}] is deprecated — use a plain function ({ ${lib.concatStringsSep ", " reqSorted}, ... }: ...) instead; handler-based resolution binds context args automatically"
{
__args = lib.genAttrs reqSorted (_: false);
__fn =
resolvedArgs:
let
# If any extra key was resolved (handler exists), we're at a deeper level
hasExtras = builtins.any (k: resolvedArgs ? ${k}) extraKeys;
in
if hasExtras then
{ }
else if lib.isFunction aspect && !builtins.isAttrs aspect then
aspect (lib.intersectAttrs (lib.genAttrs reqKeysSorted (_: null)) resolvedArgs)
if lib.isFunction aspect && !builtins.isAttrs aspect then
aspect (lib.intersectAttrs (lib.genAttrs reqSorted (_: null)) resolvedArgs)
else
aspect;
__args = funcArgs;
name = aspect.name or "<perCtx>";
meta = aspect.meta or { };
};

perHost = perCtx [ "host" ];
Expand Down
Loading
Loading