Skip to content

fix: applyDeep skips static subs that don't consume parametric ctx#426

Merged
sini merged 1 commit intovic:mainfrom
sini:fix/static-sub-aspect-parametric-include
Apr 10, 2026
Merged

fix: applyDeep skips static subs that don't consume parametric ctx#426
sini merged 1 commit intovic:mainfrom
sini:fix/static-sub-aspect-parametric-include

Conversation

@sini
Copy link
Copy Markdown
Collaborator

@sini sini commented Apr 10, 2026

PR #419 introduced applyDeep to propagate parametric context into bare-result sub-includes. But it called takeFn unconditionally on every sub, including full static aspects whose default functor (withOwn parametric.atLeast) discards owned class configs in a non-static branch. Result: den.aspects.role._.sub.nixos.x = true was silently dropped when role was a parametric parent that included its sub-aspect.

Gate the inner re-application on canTake.upTo: a static aspect's default functor has empty functionArgs, so upTo is false and the sub is left alone for the static resolve pass. User-provided provider fns (e.g. { host, ... }: { nixos = ...; }) have host in functionArgs, so upTo fires and their config is materialized. This preserves the #419 fix while restoring pre-#419 behavior for static sub-aspects.

Fixes #423.

@sini
Copy link
Copy Markdown
Collaborator Author

sini commented Apr 10, 2026

I, Jason Bowman, explicitly acknowledge the following:
- [x] My contribution is AI generated and I mention which models/mcp-servers/tools were used.
- [x] I recognize AI generated contribution as my own and am myself legally accountable for it.
- [x] My contribution aligns with Den LICENSE and does not violate any other projects licenses (e.g. GPL)
- [x] I will truthfully answer and provide any requested details on how contribution was generated.

Filling this out to show that it's not that scary, I had claude code adapt the test from the bug report for me and write the commit message since it's more descriptive than I can manage. :-)

PR vic#419 introduced applyDeep to propagate parametric context into
bare-result sub-includes. But it called takeFn unconditionally on every
sub, including full static aspects whose default functor (withOwn
parametric.atLeast) discards owned class configs in a non-static branch.
Result: `den.aspects.role._.sub.nixos.x = true` was silently dropped
when role was a parametric parent that included its sub-aspect.

Gate the inner re-application on canTake.upTo: a static aspect's
default functor has empty functionArgs, so upTo is false and the sub is
left alone for the static resolve pass. User-provided provider fns
(e.g. `{ host, ... }: { nixos = ...; }`) have host in functionArgs,
so upTo fires and their config is materialized. This preserves the

Fixes vic#423.
@sini sini force-pushed the fix/static-sub-aspect-parametric-include branch from ed50b73 to 72b7f45 Compare April 10, 2026 07:41
@sini sini requested a review from theutz April 10, 2026 07:41
Comment thread nix/lib/parametric.nix
in
if sr != { } then sr else sub
) r.includes;
# Only recurse into subs whose functor actually consumes this ctx.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for this comment. Very helpful!

@sini sini merged commit 8d2fe01 into vic:main Apr 10, 2026
25 of 27 checks passed
sini added a commit to sini/den that referenced this pull request Apr 13, 2026
, meta carryover

Add bare function include handling to resolveDeep — wraps lambdas in
minimal aspect envelopes so they participate in recursive resolution.

Regression tests cover:
- Provider sub-aspect with bare parametric includes (vic#413/vic#423)
- Static sub inside parametric parent preserves owned config (vic#426)
- Factory function not incorrectly coerced (vic#437)
- meta.provider survives deep resolution
- 3-level deep parametric nesting
sini added a commit to sini/den that referenced this pull request Apr 14, 2026
, meta carryover

Add bare function include handling to resolveDeep — wraps lambdas in
minimal aspect envelopes so they participate in recursive resolution.

Regression tests cover:
- Provider sub-aspect with bare parametric includes (vic#413/vic#423)
- Static sub inside parametric parent preserves owned config (vic#426)
- Factory function not incorrectly coerced (vic#437)
- meta.provider survives deep resolution
- 3-level deep parametric nesting
sini added a commit to sini/den that referenced this pull request Apr 14, 2026
, meta carryover

Add bare function include handling to resolveDeep — wraps lambdas in
minimal aspect envelopes so they participate in recursive resolution.

Regression tests cover:
- Provider sub-aspect with bare parametric includes (vic#413/vic#423)
- Static sub inside parametric parent preserves owned config (vic#426)
- Factory function not incorrectly coerced (vic#437)
- meta.provider survives deep resolution
- 3-level deep parametric nesting
sini added a commit to sini/den that referenced this pull request Apr 14, 2026
, meta carryover

Add bare function include handling to resolveDeep — wraps lambdas in
minimal aspect envelopes so they participate in recursive resolution.

Regression tests cover:
- Provider sub-aspect with bare parametric includes (vic#413/vic#423)
- Static sub inside parametric parent preserves owned config (vic#426)
- Factory function not incorrectly coerced (vic#437)
- meta.provider survives deep resolution
- 3-level deep parametric nesting
sini added a commit to sini/den that referenced this pull request Apr 14, 2026
, meta carryover

Add bare function include handling to resolveDeep — wraps lambdas in
minimal aspect envelopes so they participate in recursive resolution.

Regression tests cover:
- Provider sub-aspect with bare parametric includes (vic#413/vic#423)
- Static sub inside parametric parent preserves owned config (vic#426)
- Factory function not incorrectly coerced (vic#437)
- meta.provider survives deep resolution
- 3-level deep parametric nesting
@sini sini deleted the fix/static-sub-aspect-parametric-include branch April 14, 2026 21:27
sini added a commit to sini/den that referenced this pull request Apr 17, 2026
- Move tests from batteries/, context/, perf/, home-manager/ subdirs
  to flat features/ directory
- Add fx-specific test suites: fx-aspect, fx-constraints, fx-handlers,
  fx-resolve, fx-trace, fx-e2e, fx-full-pipeline, fx-identity,
  fx-includeIf, fx-integration, fx-flag, fx-ctx-apply, fx-ctx-parametric,
  fx-effectful-resolve, fx-parametric-meta, fx-regressions,
  fx-adapter-integration
- Update existing tests for constraint terminology and provides. alias
- Add fxPipeline=false for legacy adapter tests
- Add regression reproductions for issues vic#413, vic#423, vic#426, vic#437
@sini sini mentioned this pull request Apr 17, 2026
5 tasks
vic added a commit that referenced this pull request Apr 17, 2026
## Summary

Replace den's legacy recursive tree-walking resolution with an
effects-based pipeline. Aspects compile into effectful computations via
`aspectToEffect` — the tree structure emerges from effect composition,
not explicit recursion. All resolution strategy (constraint checking,
dedup, tracing, context dispatch) lives in handlers.

- **`aspectToEffect` compiler** — single function compiles any aspect
into a computation that emits `emit-class`, `register-constraint`,
`emit-include`, and `resolve-complete` effects
- **Handler-owned recursion** — `emit-include` handler checks
constraints and recurses via effectful resume; `into-transition` handler
processes context transitions with `scope.stateful`
- **Constraint system** — `meta.handleWith` / `meta.excludes` replace fx
usage of `meta.adapter`; scoped constraints via includes-chain ancestry;
`exclude`, `substitute`, `filterBy` constructors with subtree/global
variants
- **Includes chain provenance** — `chain-push`/`chain-pop` effects
replace `__parent` string tracking; observable by any handler for
diagrams, scope visualization, composable analysis
- **Module wiring** — `{ lib, den }` only, no `init` function, barrel
`default.nix`; nix-effects accessed as `den.lib.fx`
- **`den.fxPipeline` option** — defaults to `true`; legacy tests using
`resolve.withAdapter` run with `fxPipeline = false`

### Pipeline architecture

```
nix/lib/aspects/fx/
  aspect.nix        — aspectToEffect compiler
  pipeline.nix      — mkPipeline, defaultHandlers, fxResolve
  identity.nix      — path/identity utilities, collectPathsHandler, pathSetHandler
  constraints.nix   — exclude, substitute, filterBy constructors
  includes.nix      — includeIf conditional inclusion
  trace.nix         — structuredTraceHandler, tracingHandler
  handlers/
    include.nix     — emit-include handler (owns recursion)
    transition.nix  — into-transition handler (scope.stateful)
    ctx.nix         — constantHandler, ctxSeenHandler
    tree.nix        — constraintRegistryHandler, chainHandler, classCollectorHandler
```

### Effect protocol

| Effect | Handler responsibility |
|---|---|
| `emit-class` | Accumulate modules by class |
| `emit-include` | Check constraints, recurse via `aspectToEffect` |
| `register-constraint` | Store in registry with scope/ownerChain |
| `check-constraint` | Query registry, first-registered-wins |
| `chain-push` / `chain-pop` | Track includes-path stack |
| `into-transition` | Walk context transitions with scoped handlers |
| `ctx-seen` | Dedup context stages |
| `resolve-complete` | Emit trace entries, accumulate paths |
| `get-path-set` | Return accumulated path set for `includeIf` guards |
| `<arg-name>` | `constantHandler` resumes with context value |

### Compatibility shims

The type system operates at declaration time and cannot be gated on
`config.den.fxPipeline` without circular evaluation:

- `aspect-chain` in `constantHandler` — provider functions from
`providerFnType.merge` create `{ class, aspect-chain }` functors
- `options.nix` uses legacy `ctxApply` — `config.resolved` can't access
`config.den` without circularity
- Legacy adapter tests run with `fxPipeline = false`

### Also in this PR

- `refactor: replace _. alias with provides.` across all
aspect/output/template modules
- `feat: ci-fast recipe` using nix-eval-jobs for parallel test eval
- Test restructuring: `batteries/`, `context/`, `perf/`, `home-manager/`
subdirs flattened to `features/`
- Regression reproductions for issues #413, #423, #426, #437
- Consolidated design spec at `docs/design/fx-pipeline-spec.md`

## Test plan

- [x] `nix develop -c just ci ""` — 488/488 pass
- [x] `nix develop -c just ci-fast` — parallel eval, 488/488 pass
- [x] `nix flake check` — templates, packages, devShells all pass
- [x] Checkmate `system-agnostic` tests — 15/15 pass
- [x] Legacy tests with `fxPipeline = false` verify backward
compatibility

---------

Co-authored-by: Victor Borja <vborja@apache.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants