Skip to content

fix: perHost/perUser/perHome preserve aspect identity#410

Merged
vic merged 3 commits intomainfrom
fix/perHost-identity
Apr 9, 2026
Merged

fix: perHost/perUser/perHome preserve aspect identity#410
vic merged 3 commits intomainfrom
fix/perHost-identity

Conversation

@sini
Copy link
Copy Markdown
Collaborator

@sini sini commented Apr 8, 2026

Return { includes = [fn] } instead of a bare function, matching the POC pattern. The function result lives in includes where parametric.applyIncludes handles it with withIdentity, preserving the aspect name through resolution. Fixes #408.

@sini sini requested a review from vic April 8, 2026 23:42
Return { includes = [fn] } instead of a bare function, matching the
POC pattern. The function result lives in includes where
parametric.applyIncludes handles it with withIdentity, preserving the
aspect name through resolution. Fixes #408.
@sini sini force-pushed the fix/perHost-identity branch from b8bce20 to e8c6bf2 Compare April 8, 2026 23:43
vic
vic previously approved these changes Apr 8, 2026
Copy link
Copy Markdown
Owner

@vic vic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:O

@sini
Copy link
Copy Markdown
Collaborator Author

sini commented Apr 8, 2026

Don't merge yet, it fixed the test but theres one other edgecase I want to check.

@vic
Copy link
Copy Markdown
Owner

vic commented Apr 8, 2026

merge at your discretion

@vic
Copy link
Copy Markdown
Owner

vic commented Apr 8, 2026

please also include a deadbug test with the original code.

Bare function aspects (e.g., { host, ... }: { nixos... }) merged with
static config from separate modules now work correctly. Fixes #408.

Three changes:

- take.nix: carryAttrs preserves name through successful take calls
- types.nix: coercedTo in aspectsType wraps non-module functions into
  { includes = [fn] } so they aren't treated as module functions
- perHost-perUser.nix: returns { includes = [fn] } matching the POC
  pattern, so parametric.applyIncludes handles identity via withIdentity
@sini
Copy link
Copy Markdown
Collaborator Author

sini commented Apr 9, 2026

So this was an interaction with:

  providerFnType =
    cnf:
    let
      eth = lib.types.either (leafProviderFnType cnf) (curriedProviderFnType cnf);
    in
    eth
    // { # <-- This bit here was the culprit, the updated signatures allow us to keep it and should work now
      merge =
        loc: defs:
        (aspectType cnf).merge loc [
          {
            file = (lib.last defs).file;
            value = {
              __functor = _: eth.merge loc defs;
            };
          }
        ];
    };
    ```

@sini sini requested a review from vic April 9, 2026 00:34
@vic
Copy link
Copy Markdown
Owner

vic commented Apr 9, 2026

Thanks, @sini

@vic vic merged commit d266c3a into main Apr 9, 2026
26 of 27 checks passed
@vic vic deleted the fix/perHost-identity branch April 9, 2026 00:43
sini added a commit to sini/den that referenced this pull request Apr 11, 2026
PR vic#410 added coercedProviderType to make `{host, ...}: {nixos = ...}`
aspects merge with sibling static `.nixos = ...` defs (fixes vic#408). The
predicate was too broad: any non-module function got wrapped into
`{ includes = [fn] }`, which silently broke "factory" aspects like:

    den.aspects.facter = reportPath: { nixos = ...; };
    den.aspects.igloo.includes = [ (den.aspects.facter "/path") ];

Coercing turns den.aspects.facter into a full aspect with the default
functor. Calling `(facter "/path")` invokes the default functor in a
non-static branch with the string as `ctx` — the user's `reportPath`
is discarded and the config body never materializes.

Narrow the predicate with `lib.functionArgs v != {}`. Context fns have
destructured named args (non-empty functionArgs) and still get coerced.
Factory fns with a bare positional arg stay typed as providerFnType,
whose merge wraps the underlying function via
`__functor = _: eth.merge loc defs` so `(aspect arg)` correctly
dispatches to the user function.

Bisected to d266c3a (PR vic#410). Minimal repro verified broken at that
commit and working at v0.15.0. Fix passes the new deadbugs test plus
all 278 existing CI tests including deadbugs-issue-408 (context-fn
+ static merge still works).

Fixes vic#429.
vic pushed a commit that referenced this pull request Apr 11, 2026
PR #410 added coercedProviderType to make `{host, ...}: {nixos = ...}`
aspects merge with sibling static `.nixos = ...` defs (fixes #408). The
predicate was too broad: any non-module function got wrapped into `{
includes = [fn] }`, which silently broke "factory" aspects like:

    den.aspects.facter = reportPath: { nixos = ...; };
    den.aspects.igloo.includes = [ (den.aspects.facter "/path") ];

Coercing turns den.aspects.facter into a full aspect with the default
functor. Calling `(facter "/path")` invokes the default functor in a
non-static branch with the string as `ctx` — the user's `reportPath` is
discarded and the config body never materializes.

Narrow the predicate with `lib.functionArgs v != {}`. Context fns have
destructured named args (non-empty functionArgs) and still get coerced.
Factory fns with a bare positional arg stay typed as providerFnType,
whose merge wraps the underlying function via
`__functor = _: eth.merge loc defs` so `(aspect arg)` correctly
dispatches to the user function.

Bisected to d266c3a (PR #410). Minimal repro verified broken at that
commit and working at v0.15.0. Fix passes the new deadbugs test plus all
278 existing CI tests including deadbugs-issue-408 (`context-fn +
static` merge still works).

Fixes #429.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants