refactor: den-fx - #462
Merged
Merged
refactor: den-fx#462
Conversation
Add nix-effects as fx library accessible via den.lib.fx. Handlers can now return computations as resume values (effectful handlers), enabling handler-owned recursion in the fx pipeline. Loaded from flake input when available, falls back to locked fetchTarball.
…ompat shims
- aspects/default.nix: fxResolveTree entry point, fxPipeline gate,
defaultFunctor from parametric.withOwn
- aspects/types.nix: meta.handleWith + meta.excludes options
- parametric.nix: carry handleWith in identity envelope alongside adapter
- statics.nix: legacy pipeline marker
- aspects/resolve.nix: legacy pipeline marker
- aspects/adapters.nix: legacy pipeline marker, revert meta.adapter to
function-only
- ctx-apply.nix: __ctxStage/__ctxKind/__ctxAspect tag propagation
- home-env.nix: uses { class, aspect-chain } provider function
- default.nix: wire fx into den.lib
Rename the underscore alias to the explicit provides. accessor in all aspect modules, output modules, and templates for clarity.
- 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 denful#413, denful#423, denful#426, denful#437
Consolidated spec for the unified effects pipeline architecture: aspectToEffect compiler, handler set, effect protocol, constraints, includes chain provenance, context transitions, compatibility shims, and followup work items.
…fields The aspect type system now includes meta.handleWith and meta.excludes in all aspects. Update the identity fixture in aspect-functor tests to include these fields.
…ine. See notes here: Handler state must be deepSeq-safe. https://github.com/kleisli-io/nix-effects
Add parens around (result.state.imports null) in builtins.length calls. Without parens, Nix parses as (builtins.length result.state.imports) null which passes the thunk to length instead of evaluating it first.
vic
approved these changes
Apr 17, 2026
charlesfire
pushed a commit
to charlesfire/den
that referenced
this pull request
May 16, 2026
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`
```
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 | 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 |
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`
- `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 denful#413, denful#423, denful#426, denful#437
- Consolidated design spec at `docs/design/fx-pipeline-spec.md`
- [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 <[email protected]>
charlesfire
pushed a commit
to charlesfire/den
that referenced
this pull request
May 16, 2026
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`
```
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 | 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 |
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`
- `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 denful#413, denful#423, denful#426, denful#437
- Consolidated design spec at `docs/design/fx-pipeline-spec.md`
- [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 <[email protected]>
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.
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.aspectToEffectcompiler — single function compiles any aspect into a computation that emitsemit-class,register-constraint,emit-include, andresolve-completeeffectsemit-includehandler checks constraints and recurses via effectful resume;into-transitionhandler processes context transitions withscope.statefulmeta.handleWith/meta.excludesreplace fx usage ofmeta.adapter; scoped constraints via includes-chain ancestry;exclude,substitute,filterByconstructors with subtree/global variantschain-push/chain-popeffects replace__parentstring tracking; observable by any handler for diagrams, scope visualization, composable analysis{ lib, den }only, noinitfunction, barreldefault.nix; nix-effects accessed asden.lib.fxden.fxPipelineoption — defaults totrue; legacy tests usingresolve.withAdapterrun withfxPipeline = falsePipeline architecture
Effect protocol
emit-classemit-includeaspectToEffectregister-constraintcheck-constraintchain-push/chain-popinto-transitionctx-seenresolve-completeget-path-setincludeIfguards<arg-name>constantHandlerresumes with context valueCompatibility shims
The type system operates at declaration time and cannot be gated on
config.den.fxPipelinewithout circular evaluation:aspect-chaininconstantHandler— provider functions fromproviderFnType.mergecreate{ class, aspect-chain }functorsoptions.nixuses legacyctxApply—config.resolvedcan't accessconfig.denwithout circularityfxPipeline = falseAlso in this PR
refactor: replace _. alias with provides.across all aspect/output/template modulesfeat: ci-fast recipeusing nix-eval-jobs for parallel test evalbatteries/,context/,perf/,home-manager/subdirs flattened tofeatures/docs/design/fx-pipeline-spec.mdTest plan
nix develop -c just ci ""— 488/488 passnix develop -c just ci-fast— parallel eval, 488/488 passnix flake check— templates, packages, devShells all passsystem-agnostictests — 15/15 passfxPipeline = falseverify backward compatibility