diff --git a/CLAUDE.md b/CLAUDE.md
index e96b96aa0..93e1be4d9 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -17,7 +17,7 @@ nix/ — all library and flake-module code
aspect/ — children, normalize, provide
policy/ — policy dispatch and effects
entities/ — host.nix, home.nix entity kind definitions
- diag/ — diagram generation (c4, mermaid, dot, fleet views)
+ diag/capture.nix — trace capture (graph/rendering moved to denful/den-diagram)
nixModule/ — den.aspects, den.policies, den.lib option declarations
modules/ — NixOS-module-style option declarations and batteries
options.nix — den.hosts, den.homes, den.schema, den.classes, den.quirks
@@ -129,6 +129,35 @@ New test files must be `git add`'d before nix can evaluate them. Use `--override
- **den-debugging** (`.claude/skills/den-debugging.md`) — structured workflow for reproducing, isolating, and fixing bugs. Guides through: understand report → trace code path → write failing test → fix → validate. Includes an entry point table mapping symptoms to source files.
+## Diagrams (den-diagram)
+
+Diagram rendering lives in a separate repo: [denful/den-diagram](https://github.com/denful/den-diagram). Den keeps only the capture layer (`nix/lib/diag/capture.nix`) which runs the fx pipeline with tracing handlers.
+
+**Capture** stays in den — `den.lib.capture.*`:
+
+- `capture`, `captureAll`, `captureWithPaths`, `captureWithPathsWith`, `captureFleet`
+
+**Rendering** lives in den-diagram — added as `inputs.den-diagram` in templates that need it:
+
+```nix
+diagram = inputs.den-diagram.lib;
+
+# Two-step: capture in den, render in den-diagram
+captured = den.lib.capture.captureWithPathsWith {
+ classes = [ "nixos" "homeManager" ];
+ root = den.lib.resolveEntity "host" { inherit host; };
+ ctx = { inherit host; };
+};
+g = diagram.context {
+ entries = captured.entries;
+ ctxTrace = captured.ctxTrace;
+ name = host.name;
+};
+rendered = diagram.toMermaid g;
+```
+
+Templates using den-diagram: `diagram-demo`, `fleet-demo`. Den's core flake and CI have no den-diagram dependency.
+
## Debugging and tracing
For pipeline debugging, use `builtins.trace` temporarily to inspect values flowing through handlers:
diff --git a/README.md b/README.md
index 9a7e9a8f8..40e9843c9 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
**Write a feature once. Run it on every host, user, and platform you have — and share it with anyone, flake or not.**
-Den turns Nix configuration into composable **features** instead of per-host piles of modules. A Den *aspect* is a plain function: give it context (your hosts and users) and it returns configuration for every Nix class it touches — `nixos`, `darwin`, `homeManager`, `hjem`, or a class you invent.
+Den turns Nix configuration into composable **features** instead of per-host piles of modules. A Den _aspect_ is a plain function: give it context (your hosts and users) and it returns configuration for every Nix class it touches — `nixos`, `darwin`, `homeManager`, `hjem`, or a class you invent.
```nix
# An aspect is a function of context that returns
@@ -33,8 +33,8 @@ That one idea — **a feature as a function** — is what makes the rest possibl
## What Den makes possible
- **One feature, everywhere, in one place.** Stop scattering a single concern across separate `nixos`, `darwin`, and `homeManager` files. An aspect holds all of it together.
-- **Reuse across hosts, users — and across projects.** Share aspects between machines, between people, and between *flake and non-flake* setups, without forcing everyone to download each other's inputs.
-- **No `mkIf` / `enable` clutter.** The shape of the context *is* the condition — a function that asks for `{ host, user }` simply doesn't run where there's no user. Conditionals disappear.
+- **Reuse across hosts, users — and across projects.** Share aspects between machines, between people, and between _flake and non-flake_ setups, without forcing everyone to download each other's inputs.
+- **No `mkIf` / `enable` clutter.** The shape of the context _is_ the condition — a function that asks for `{ host, user }` simply doesn't run where there's no user. Conditionals disappear.
- **Hosts shape their users, users shape their hosts.** Cross-entity configuration flows both ways, without coupling them together.
- **Add a capability in one line; remove it by deleting that line.** Hosts just pick the aspects they want.
- **Bring your own classes and whole pipelines.** Custom Nix classes, machine fleets, MicroVM guests, terranix, standalone neovim — if you can walk it as data, Den can configure it.
@@ -43,10 +43,10 @@ That one idea — **a feature as a function** — is what makes the rest possibl
Four concepts, one job each:
-- **Entity** — *what exists*: a host, user, or home.
-- **Aspect** — *what it does*: a feature, spanning Nix classes.
-- **Policy** — *how entities relate*: topology and routing between them.
-- **Quirk** — *structured data aspects share*, without coupling.
+- **Entity** — _what exists_: a host, user, or home.
+- **Aspect** — _what it does_: a feature, spanning Nix classes.
+- **Policy** — _how entities relate_: topology and routing between them.
+- **Quirk** — _structured data aspects share_, without coupling.
**Feature-first, not host-first.** Traditional setups start from hosts and push modules down; Den [flips that](https://den.denful.dev/explanation/core-principles/) — features are primary, hosts just select them.
@@ -75,11 +75,13 @@ nix run github:denful/den
## [Documentation](https://den.denful.dev)
**Start here**
+
- [From Zero to Den](https://den.denful.dev/guides/from-zero-to-den/)
- [From Flake to Den](https://den.denful.dev/guides/from-flake-to-den/)
- [Core Principles](https://den.denful.dev/explanation/core-principles/)
**Go further**
+
- [Custom Nix Classes](https://den.denful.dev/guides/custom-classes/)
- [Homes Integration](https://den.denful.dev/guides/home-manager/)
- [Batteries](https://den.denful.dev/guides/batteries/)
@@ -89,6 +91,7 @@ nix run github:denful/den
- [Tests as Code Examples](https://den.denful.dev/tutorials/ci/)
**Project**
+
- [Motivation](https://den.denful.dev/motivation/)
- [Versioning](https://den.denful.dev/releases/)
- [Community](https://den.denful.dev/community/)
diff --git a/checkmate/modules/formatter.nix b/checkmate/modules/formatter.nix
index ebbe71fbb..0cf87037f 100644
--- a/checkmate/modules/formatter.nix
+++ b/checkmate/modules/formatter.nix
@@ -6,6 +6,7 @@
"docs/*"
"Justfile"
"AGENT*.md"
+ "CLAUDE.md"
"*.txt"
"*.svg"
"ci.bash"
diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs
index ffa1b19e2..ae94f3549 100644
--- a/docs/astro.config.mjs
+++ b/docs/astro.config.mjs
@@ -178,7 +178,7 @@ export default defineConfig({
{ label: 'den.policies', slug: 'reference/policies' },
{ label: 'den.lib', slug: 'reference/lib' },
{ label: 'den.lib', slug: 'reference/lib-deprecated', badge: { text: 'legacy', variant: 'note' } },
- { label: 'den.lib.diag', slug: 'reference/diag' },
+ { label: 'den.lib.capture & den-diagram', slug: 'reference/diag' },
{ label: 'flake.*', slug: 'reference/output' },
{ label: 'Glossary', slug: 'reference/glossary' },
],
diff --git a/docs/src/content/docs/explanation/diagrams.mdx b/docs/src/content/docs/explanation/diagrams.mdx
index d952688da..c86cac4cf 100644
--- a/docs/src/content/docs/explanation/diagrams.mdx
+++ b/docs/src/content/docs/explanation/diagrams.mdx
@@ -1,12 +1,13 @@
---
title: Diagrams
-description: Visualizing aspect resolution graphs with den.lib.diag.
+description: Visualizing aspect resolution graphs with den-diagram.
---
import { Aside } from '@astrojs/starlight/components';
## What it does
diff --git a/docs/src/content/docs/guides/debug.md b/docs/src/content/docs/guides/debug.md
index fe5e2408b..4a21ca42c 100644
--- a/docs/src/content/docs/guides/debug.md
+++ b/docs/src/content/docs/guides/debug.md
@@ -58,15 +58,21 @@ This returns a set of matching policies with their targets, routing type, and so
## Trace Aspect Includes
-The resolution pipeline includes built-in tracing via the `diag` library.
-Use `den.lib.diag.hostContext` to capture a full trace of which aspects are
-included and how they resolve. See [Diagrams](/explanation/diagrams/) for details.
+The resolution pipeline includes built-in tracing via `den.lib.capture` and the
+[den-diagram](https://github.com/denful/den-diagram) library. Capture trace data
+in den, then render with den-diagram. See [Diagrams](/explanation/diagrams/) for details.
```nix
-# In a REPL:
-diag = den.lib.diag
-g = diag.hostContext { host = den.hosts.x86_64-linux.laptop; }
-diag.toMermaid g # renders the full aspect graph
+# In a REPL (with den-diagram available as `diagram`):
+diagram = inputs.den-diagram.lib;
+host = den.hosts.x86_64-linux.laptop;
+captured = den.lib.capture.captureWithPathsWith {
+ classes = [ "nixos" "homeManager" ];
+ root = den.lib.resolveEntity "host" { inherit host; };
+ ctx = { inherit host; };
+};
+g = diagram.context { entries = captured.entries; name = host.name; };
+diagram.toMermaid g # renders the full aspect graph
```
## Trace Context
diff --git a/docs/src/content/docs/overview.mdx b/docs/src/content/docs/overview.mdx
index 49d0e672f..c62f2107d 100644
--- a/docs/src/content/docs/overview.mdx
+++ b/docs/src/content/docs/overview.mdx
@@ -74,7 +74,7 @@ Option namespaces and library functions.
-
+
diff --git a/docs/src/content/docs/reference/diag.mdx b/docs/src/content/docs/reference/diag.mdx
index 62b30704a..89be65059 100644
--- a/docs/src/content/docs/reference/diag.mdx
+++ b/docs/src/content/docs/reference/diag.mdx
@@ -1,51 +1,52 @@
---
-title: den.lib.diag
-description: Diagram library for visualizing aspect resolution graphs.
+title: den.lib.capture & den-diagram
+description: Trace capture and diagram rendering for aspect resolution graphs.
---
import { Aside } from '@astrojs/starlight/components';
-The diagramming library provides a composable pipeline for rendering aspect-resolution
-graphs: **trace capture** collects structured trace entries from resolved aspects,
-**graph construction** builds a format-agnostic IR, **filters** prune and reshape the IR,
-and **renderers** emit Mermaid, DOT, PlantUML, or JSON strings.
+Diagram rendering is split across two packages:
-See [Diagrams explanation](/explanation/diagrams/) for concepts and usage patterns.
-
-## Convenience wrappers
-
-High-level entry points that resolve an entity and return a graph IR in one call.
-
-### `hostContext { host, classes?, direction? }`
-
-Build a host-scoped graph. When `classes` is omitted, auto-discovers from `[ "nixos" "homeManager" "user" ]` plus each user's declared classes. Returns the graph IR plus `rootAspect`, `pathSets`, and `classes`.
-
-### `userContext { host, user, classes?, direction? }`
-
-Build a user-scoped graph. Defaults to `[ "homeManager" "user" ]` plus the user's own classes.
-
-### `homeContext { home, classes?, direction? }`
+- **`den.lib.capture`** (in den) — runs the fx pipeline with tracing handlers, produces structured trace entries
+- **`den-diagram`** (separate repo) — graph IR, filters, renderers. Pure library, depends only on `nixpkgs.lib`
-Build a home-scoped graph. Defaults to `[ "homeManager" ]` plus the home's own classes.
+See [Diagrams explanation](/explanation/diagrams/) for concepts and usage patterns.
-### `context { root, name, classes, direction? }`
+## Two-step pattern
-Generic entry point for any entity kind. `root` is a resolved entity (from `den.lib.resolveEntity`), `name` is used as the graph root label, and `classes` lists the aspect classes to trace. `direction` defaults to `"LR"`.
+Capture in den, render in den-diagram:
```nix
-root = den.lib.resolveEntity "user" { inherit host user; };
-g = diag.context { inherit root; name = user.name; classes = [ "homeManager" ]; };
+diagram = inputs.den-diagram.lib;
+
+# 1. Capture
+captured = den.lib.capture.captureWithPathsWith {
+ classes = [ "nixos" "homeManager" ];
+ root = den.lib.resolveEntity "host" { inherit host; };
+ ctx = { inherit host; };
+};
+
+# 2. Build graph IR
+g = diagram.context {
+ entries = captured.entries;
+ ctxTrace = captured.ctxTrace;
+ name = host.name;
+};
+
+# 3. Render
+diagram.toMermaid g
```
## Graph construction
### `graph.build { entries, rootName, ctxTrace?, direction? }`
-Core IR builder. Transforms a list of structured trace entries into a graph record containing `nodes`, `edges`, `entityKinds`, `entityEdges`, `entityInstances`, `rootId`, `rootName`, and `direction`. This is the lowest-level constructor -- convenience wrappers call it internally.
+Core IR builder. Transforms a list of structured trace entries into a graph record containing `nodes`, `edges`, `stages`, `stageEdges`, `rootId`, `rootName`, and `direction`. This is the lowest-level constructor -- convenience wrappers call it internally.
### `graph.ofHost args`
@@ -57,7 +58,7 @@ Static namespace graph built from `den.aspects` declarations (no host resolution
## Filters
-All filters are available on the `graph` attrset (e.g., `diag.graph.aspectsOnly`). Each takes a graph IR and returns a new graph IR.
+All filters are available on the `graph` attrset (e.g., `diagram.graph.aspectsOnly`). Each takes a graph IR and returns a new graph IR.
### Predicate filters
@@ -93,7 +94,7 @@ All filters are available on the `graph` attrset (e.g., `diag.graph.aspectsOnly`
|---|---|---|
| `foldWrappers` | `graph -> graph` | Remove wrapper/context nodes, rewire edges to their children |
| `foldProviders` | `graph -> graph` | Collapse provider chains into single edges |
-| `flattenEntityKinds` | `graph -> graph` | Remove entity-kind subgraph grouping |
+| `flattenStages` | `graph -> graph` | Remove stage subgraph grouping |
### Composite filters
@@ -101,7 +102,7 @@ All filters are available on the `graph` attrset (e.g., `diag.graph.aspectsOnly`
|---|---|---|
| `filterMeaningful` | `graph -> graph` | Drop anonymous and definition-stub nodes |
| `filterUserAspects` | `graph -> graph` | `foldWrappers` composed with `filterMeaningful` |
-| `simplified` | `graph -> graph` | `foldProviders` + `flattenEntityKinds` + `aspectsOnly` |
+| `simplified` | `graph -> graph` | `foldProviders` + `flattenStages` + `aspectsOnly` |
### Structural
@@ -136,7 +137,7 @@ Every renderer has two forms: `toFoo` uses the default theme and `toFooWith { th
| `toSequenceMermaid` / `toSequenceMermaidWith` | Mermaid sequence | Stage sequence diagram |
| `toSequenceMermaidExpanded` / `toSequenceMermaidExpandedWith` | Mermaid sequence | Expanded with per-aspect detail |
| `toPolicySequenceMermaid` / `toPolicySequenceMermaidWith` | Mermaid sequence | Policy-level sequence |
-| `toScopeEdgesMermaid` / `toScopeEdgesMermaidWith` | Mermaid | Scope topology edges |
+| `toStageEdgesMermaid` / `toStageEdgesMermaidWith` | Mermaid | Stage topology edges |
### C4 renderers
@@ -173,19 +174,6 @@ Every renderer has two forms: `toFoo` uses the default theme and `toFooWith { th
| `toStateMermaid` / `toStateMermaidWith` | Mermaid state | State diagram |
| `toJSON` | JSON | Graph IR serialized to JSON (no theme) |
-### Fleet flow renderers
-
-These consume fleet capture data (from `captureFleet`) rather than a graph IR. The fleet capture walks the entire flake scope tree (flake → environment → host → user).
-
-| Renderer | Format | Notes |
-|---|---|---|
-| `toPipeFlowMermaid` / `toPipeFlowMermaidWith` | Mermaid flowchart | Cross-host quirk/pipe data flows with environment subgraphs |
-| `toScopeTopologyMermaid` / `toScopeTopologyMermaidWith` | Mermaid flowchart | Fleet scope tree (fleet → environment → host → user) |
-| `toAspectMatrixMermaid` / `toAspectMatrixMermaidWith` | Mermaid | Aspect coverage matrix -- which aspects land on which hosts |
-| `toPolicyResolutionMapMermaid` / `toPolicyResolutionMapMermaidWith` | Mermaid | Policy entity resolution map |
-| `toPipeSequenceMermaid` / `toPipeSequenceMermaidWith` | Mermaid sequence | Pipe emit -> collect flow per pipe |
-| `toFleetDagMermaid` / `toFleetDagMermaidWith` | Mermaid flowchart | Fleet-wide DAG composing all hosts' aspect trees |
-
### `renderers { theme?, mermaidConfig? }`
Factory that returns an attrset of all pre-configured renderers. Each key is a `toFoo` function with the given theme/config baked in. Includes `toJSON`.
@@ -201,7 +189,7 @@ Hard-coded github-light palette. No `pkgs` required -- the library is usable wit
Build a theme from a [base16](https://github.com/tinted-theming/schemes) palette. Converts the YAML scheme to JSON via `yj` at build time.
```nix
-theme = diag.themeFromBase16 { inherit pkgs; scheme = "catppuccin-mocha"; };
+theme = diagram.themeFromBase16 { inherit pkgs; scheme = "catppuccin-mocha"; };
```
### `themeFromPalette palette`
@@ -218,9 +206,9 @@ A theme record contains: `palette`, `background`, `foreground`, `mutedForeground
## Fleet
-### `fleet.of { hosts?, flakeName? }`
+### `fleet.of { hosts, flakeName? }`
-Build fleet-wide data from the host registry. `hosts` defaults to `den.hosts`; `flakeName` defaults to `"den flake"`.
+Build fleet-wide data from a host registry. `hosts` is required (pass `den.hosts`); `flakeName` defaults to `"den flake"`.
Returns:
@@ -230,7 +218,7 @@ Returns:
| `hosts` | `[{ name, description }]` | Host records (description is the system string) |
| `users` | `[{ name }]` | Deduplicated user records across all hosts |
| `relations` | `[{ from, to, label }]` | User-to-host edges labeled with class names |
-| `providerSubAspects` | `[{ provider, subAspect, hostName }]` | Lazily evaluated provider sub-aspects per host |
+| `providerSubAspects` | `[{ provider, subAspect, hostName }]` | Optional — callers pre-compute via `den.lib.capture` and pass in |
## Render context
@@ -250,15 +238,15 @@ Returns:
| `pumlSourceToSvg` | `base -> source -> derivation` -- PlantUML source to SVG |
```nix
-rc = diag.renderContext { inherit pkgs; mermaidConfig = { layout = "elk"; }; };
+rc = diagram.renderContext { inherit pkgs; mermaidConfig = { layout = "elk"; }; };
rendered = rc.render.toMermaid myGraph;
```
## Export helpers
-Utilities for turning view definitions into derivations, packages, and write scripts. Available under `diag.export`.
+Utilities for turning view definitions into derivations, packages, and write scripts. Available under `diagram.export`.
-### `entityEntries { pkgs, rc, diag } { entity, name, dir, viewDefs, galleryDrv? }`
+### `entityEntries { pkgs, rc } { entity, name, dir, viewDefs, galleryDrv? }`
Generate all derivation entries (markdown + SVG) for a single entity. `entity` must be a pre-computed graph.
@@ -288,11 +276,11 @@ View definitions describe what to compute from a graph IR and how to present it.
### `views.core rc`
-Essential views shared by all entity kinds: aspect hierarchy, scope sequence, expanded scope sequence, policy sequence, provider tree, and IR JSON.
+Essential views shared by all entity kinds: aspect hierarchy, stage sequence, expanded stage sequence, policy sequence, provider tree, and IR JSON.
### `views.extended rc`
-Optional extra views: context hierarchy, simplified, scope topology, providers resolved, adapter impact, structural decisions, user-declared aspects, and class diff.
+Optional extra views: context hierarchy, simplified, stage topology, providers resolved, adapter impact, structural decisions, and user-declared aspects.
### `views.host rc` / `views.user rc` / `views.home rc`
@@ -300,7 +288,7 @@ Entity-specific view sets. Currently all return `views.core rc`.
### `views.fleet rc`
-Fleet-wide views: aspect namespace (from `graph.ofNamespace`) and fleet provider matrix.
+Fleet-wide views: aspect namespace, fleet C4 context (PlantUML), and fleet provider matrix.
### `views.classViews rc classes`
@@ -308,7 +296,7 @@ Dynamic per-class views. Generates a class-slice view for each class name in the
## Trace capture
-Low-level capture functions used internally by the convenience wrappers. Available at the top level of `den.lib.diag`.
+Capture functions that run the fx pipeline with tracing handlers. Available at `den.lib.capture`.
### `capture class root`
@@ -322,10 +310,6 @@ Capture entries for multiple classes, concatenated.
Returns `{ entries, pathsByClass, ctxTrace }` -- entries plus per-class path sets needed by presence filters.
-### `captureWithPathsWith { classes, root, ctx?, extraHandlers? }`
-
-Extended form accepting a scope context and additional trace handlers.
-
-### `captureFleet { class?, extraHandlers? }`
+### `captureWithPathsWith { classes, root, extraHandlers? }`
-Fleet-level capture. Runs the full pipeline from the flake root, walking the entire flake scope tree (flake → environment → host → user). `class` defaults to `"nixos"`. Returns a record with `entries`, `ctxTrace`, and post-pipeline scope/pipe-flow data (`scopeParent`, `scopeContexts`, `scopeEntityKind`, `scopedPipeEffects`, `scopedClassImports`, `pipeProducers`, `pipeConsumers`) consumed by the fleet flow renderers.
+Extended form accepting additional trace handlers.
diff --git a/docs/superpowers/plans/2026-05-07-diagram-identity-resolution.md b/docs/superpowers/plans/2026-05-07-diagram-identity-resolution.md
deleted file mode 100644
index 18c00cd49..000000000
--- a/docs/superpowers/plans/2026-05-07-diagram-identity-resolution.md
+++ /dev/null
@@ -1,753 +0,0 @@
-# Diagram Identity Resolution Overhaul — Implementation Plan
-
-> **For agentic workers:** REQUIRED: Use superpowers-extended-cc:subagent-driven-development (if subagents available) or superpowers-extended-cc:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
-
-**Goal:** Fix the diagram system's broken trace handler so entity kinds, scope bounding, policy nodes, and sequence diagrams all work again.
-
-**Architecture:** The trace handler (`trace.nix`) needs three fixes: seed entityKind from `param.__entityKind`, populate `ctxTrace` for sequence diagrams, and add a `record-fired` handler for policy nodes. The graph builder (`graph.nix`) needs entity instance grouping. The mermaid renderer needs per-instance subgraphs. View names need updating from "stage" to "scope" vocabulary.
-
-**Tech Stack:** Pure Nix. Tests via nix-unit (`just ci`). Diagrams via mermaid-cli for SVG rendering.
-
-**Spec:** `docs/superpowers/specs/2026-05-07-diagram-identity-resolution-design.md`
-
----
-
-### Task 1: Fix entity kind seeding and ctxTrace population in trace handler
-
-**Goal:** Make `tracingHandler` produce entries with correct `entityKind` and populate `ctxTrace` for sequence diagrams.
-
-**Files:**
-- Modify: `nix/lib/aspects/fx/trace.nix:102-148` (tracingHandler resolve-complete handler)
-- Test: `templates/ci/modules/features/fx-trace.nix`
-
-**Acceptance Criteria:**
-- [ ] Entity boundary aspects (those with `__entityKind`) produce entries with non-null `entityKind`
-- [ ] Descendant aspects inherit `entityKind` via `deriveEntityKind`
-- [ ] `ctxTrace` accumulates one entry per entity kind with `{ key, selfName, entityKind, ctxKeys }`
-- [ ] ctxTrace entries are deduplicated by entity kind
-- [ ] Existing trace tests still pass
-
-**Verify:** `nix develop -c just ci fx-trace` → summary line shows all pass
-
-**Steps:**
-
-- [ ] **Step 1: Fix entityKind and add ctxTrace in tracingHandler, then write test**
-
-Note: The test and implementation are done together because the ctxTrace assertions depend on the ctxTrace implementation. Write the implementation first, then the test.
-
-- [ ] **Step 2: Fix entityKind in tracingHandler**
-
-In `nix/lib/aspects/fx/trace.nix`, in the `tracingHandler` function's `resolve-complete` handler, change:
-
-```nix
-# Old (line ~108):
-entityKind = deriveEntityKind state;
-```
-
-to:
-
-```nix
-entityKind =
- let direct = param.__entityKind or null;
- in if direct != null then direct else deriveEntityKind state;
-```
-
-- [ ] **Step 3: Add ctxTrace population**
-
-In the same handler, after computing `entityKind` and `name`, add ctxTrace logic. Add a `resolveEntityName` helper above the handler:
-
-```nix
-resolveEntityName = ek: scopeCtx:
- let entity = scopeCtx.${ek} or null;
- in if entity != null && entity ? name then entity.name
- else ek;
-```
-
-Then in the state update section, add:
-
-```nix
-scope = state.currentScope;
-scopeCtx = if scope == null then {} else ((state.scopeContexts or (_: {})) null).${scope} or {};
-isNewKind = !(builtins.any (e: e.key == entityKind) (state.ctxTrace or []));
-ctxEntry = {
- key = entityKind;
- selfName = resolveEntityName entityKind scopeCtx;
- entityKind = entityKind;
- ctxKeys = builtins.attrNames scopeCtx;
-};
-```
-
-And in the state merge:
-
-```nix
-state = state // {
- entries = (state.entries or []) ++ [ entry ];
-} // lib.optionalAttrs (entityKind != null && isNewKind) {
- ctxTrace = (state.ctxTrace or []) ++ [ ctxEntry ];
-};
-```
-
-- [ ] **Step 4: Write test for entityKind seeding and ctxTrace**
-
-Add to `templates/ci/modules/features/fx-trace.nix`:
-
-```nix
-test-tracingHandler-entity-kind-seeded = denTest (
- { den, ... }:
- let
- entity = {
- name = "host";
- __entityKind = "host";
- meta = { provider = []; };
- nixos = { a = 1; };
- includes = [
- {
- name = "child";
- meta = { provider = []; };
- nixos = { b = 2; };
- includes = [];
- }
- ];
- };
- result = den.lib.aspects.fx.pipeline.mkPipeline {
- class = "nixos";
- extraHandlers = den.lib.aspects.fx.trace.tracingHandler "nixos";
- extraState = { entries = []; ctxTrace = []; };
- } {
- self = entity // { into = _: {}; provides = {}; };
- ctx = {};
- };
- hostEntry = lib.findFirst (e: e.name == "host") null result.state.entries;
- childEntry = lib.findFirst (e: e.name == "child") null result.state.entries;
- in {
- expr = {
- hostEntityKind = hostEntry.entityKind;
- childEntityKind = childEntry.entityKind;
- ctxTraceLength = builtins.length result.state.ctxTrace;
- ctxTraceKey = (builtins.head result.state.ctxTrace).key;
- };
- expected = {
- hostEntityKind = "host";
- childEntityKind = "host";
- ctxTraceLength = 1;
- ctxTraceKey = "host";
- };
- }
-);
-```
-
-- [ ] **Step 5: Run tests, format, and commit**
-
-Run: `nix develop -c just fmt && nix develop -c just ci fx-trace`
-Expected: All tests PASS including the new entityKind test.
-
-```bash
-git add nix/lib/aspects/fx/trace.nix templates/ci/modules/features/fx-trace.nix
-git commit -m "fix(diag): seed entityKind from param.__entityKind, populate ctxTrace"
-```
-
----
-
-### Task 2: Add record-fired handler for policy trace entries
-
-**Goal:** Capture fired policy names as trace entries so the graph builder can create policy dispatch nodes.
-
-**Files:**
-- Modify: `nix/lib/aspects/fx/trace.nix` (add `record-fired` to tracingHandler)
-- Test: `templates/ci/modules/features/fx-trace.nix`
-
-**Acceptance Criteria:**
-- [ ] `tracingHandler` returns a handler set with both `resolve-complete` and `record-fired`
-- [ ] Fired policies produce entries with `isPolicyDispatch = true`, `policyName`, `from`, `entityKind`
-- [ ] `to` is `null` (inferred later in graph builder)
-- [ ] Composing with `defaultHandlers` doesn't break `record-fired` resume (must be `null`)
-
-**Verify:** `nix develop -c just ci fx-trace` → all pass
-
-**Steps:**
-
-- [ ] **Step 1: Write test for record-fired handler**
-
-Add to `templates/ci/modules/features/fx-trace.nix`:
-
-```nix
-# Test that tracingHandler's record-fired creates policy entries
-test-tracingHandler-record-fired = denTest (
- { den, ... }:
- let
- fx = den.lib.fx;
- comp = fx.send "record-fired" {
- entityKind = "host";
- firedPolicies = { host-to-users = true; host-to-default = true; };
- };
- result = fx.handle {
- handlers = den.lib.aspects.fx.pipeline.composeHandlers
- (den.lib.aspects.fx.pipeline.defaultHandlers { class = "nixos"; ctx = {}; })
- (den.lib.aspects.fx.trace.tracingHandler "nixos");
- state = den.lib.aspects.fx.pipeline.defaultState // {
- entries = []; ctxTrace = [];
- };
- } comp;
- policyEntries = builtins.filter (e: e.isPolicyDispatch or false) result.state.entries;
- policyNames = lib.sort (a: b: a < b) (map (e: e.policyName) policyEntries);
- in {
- expr = {
- count = builtins.length policyEntries;
- names = policyNames;
- fromKind = (builtins.head policyEntries).from;
- toIsNull = (builtins.head policyEntries).to == null;
- };
- expected = {
- count = 2;
- names = [ "host-to-default" "host-to-users" ];
- fromKind = "host";
- toIsNull = true;
- };
- }
-);
-```
-
-- [ ] **Step 2: Add record-fired handler to tracingHandler**
-
-In `nix/lib/aspects/fx/trace.nix`, change `tracingHandler` from returning a single-key handler set to a two-key handler set. Add `record-fired` alongside `resolve-complete`:
-
-```nix
-tracingHandler = class: {
- "resolve-complete" = { param, state }: /* ... existing handler ... */;
-
- "record-fired" = { param, state }:
- let
- firedNames = builtins.attrNames param.firedPolicies;
- policyEntries = map (policyName: {
- name = policyName;
- class = "";
- parent = null;
- provider = [];
- excluded = false;
- excludedFrom = null;
- replacedBy = null;
- isProvider = false;
- handlers = [];
- hasClass = false;
- isParametric = false;
- fnArgNames = [];
- entityKind = param.entityKind;
- isPolicyDispatch = true;
- policyName = policyName;
- from = param.entityKind;
- to = null;
- }) firedNames;
- in {
- resume = null;
- state = state // {
- entries = (state.entries or []) ++ policyEntries;
- };
- };
-};
-```
-
-- [ ] **Step 3: Run tests and commit**
-
-Run: `nix develop -c just fmt && nix develop -c just ci fx-trace`
-Expected: All tests PASS.
-
-```bash
-git add nix/lib/aspects/fx/trace.nix templates/ci/modules/features/fx-trace.nix
-git commit -m "feat(diag): add record-fired handler to capture policy dispatch entries"
-```
-
----
-
-### Task 3: Add entity instance tracking to trace entries and graph IR
-
-**Goal:** Each trace entry carries an `entityInstance` field (e.g., `"host:laptop"`) so the graph builder can group nodes by specific entity instances. The graph IR gains an `entityInstances` list.
-
-**Files:**
-- Modify: `nix/lib/aspects/fx/trace.nix:102-148` (add `entityInstance` to entry)
-- Modify: `nix/lib/diag/graph.nix:23-51` (add `entityInstance` to `emptyNode` and `stubEntry`)
-- Modify: `nix/lib/diag/graph.nix:100-493` (build `entityInstances` list in `buildGraph`, add `entityInstance` to `mkNode`)
-- Modify: `nix/lib/diag/json.nix:41-51` (add `entityInstances` to serialized output)
-
-**Acceptance Criteria:**
-- [ ] Trace entries include `entityInstance` field (e.g., `"host:laptop"` or `null`)
-- [ ] `emptyNode` and `stubEntry` have `entityInstance = null`
-- [ ] `buildGraph` output includes `entityInstances` list with `{ id, kind, name, label }` records
-- [ ] `buildGraph` output retains `entityKinds` for sequence diagram compatibility
-- [ ] Nodes without entity kind get `entityInstance = "flake"` when there are other entity instances in the graph
-
-**Verify:** `nix develop -c just ci fx-trace` → all pass; `nix build --override-input den . ./templates/diagram-demo#laptop-ir --no-link --print-out-paths` → IR JSON has `entityInstances` array with entries
-
-**Steps:**
-
-- [ ] **Step 1: Add entityInstance to trace entries**
-
-In `nix/lib/aspects/fx/trace.nix`, in the `tracingHandler`'s `resolve-complete` handler, compute `entityInstance` alongside `entityKind`:
-
-```nix
-entityInstance =
- if entityKind != null then
- let
- scope = state.currentScope;
-scopeCtx = if scope == null then {} else ((state.scopeContexts or (_: {})) null).${scope} or {};
- eName = resolveEntityName entityKind scopeCtx;
- in "${entityKind}:${eName}"
- else null;
-```
-
-Add `inherit entityInstance;` to the entry record.
-
-Also add `entityInstance` to the `record-fired` policy entries, using the same derivation from state at that point.
-
-- [ ] **Step 2: Add entityInstance to graph.nix emptyNode/stubEntry**
-
-In `nix/lib/diag/graph.nix`, add to `emptyNode` (after `entityKind = null;`):
-
-```nix
-entityInstance = null;
-```
-
-Add the same to `stubEntry`.
-
-- [ ] **Step 3: Add entityInstance to mkNode and build entityInstances list**
-
-In `buildGraph`, after computing `mkNode`, read `entityInstance` from the entry:
-
-```nix
-mkNode = entry:
- let
- # ... existing code ...
- in {
- # ... existing fields ...
- entityInstance = entry.entityInstance or null;
- };
-```
-
-After `finalNodes`, build `entityInstances`:
-
-```nix
-# Assign "flake" instance to unscoped nodes when entity instances exist.
-hasAnyInstances = builtins.any (n: n.entityInstance != null) finalNodes;
-taggedNodes = if hasAnyInstances then
- map (n: if n.entityInstance == null then n // { entityInstance = "flake"; } else n) finalNodes
-else finalNodes;
-
-entityInstanceNames = lib.unique (
- builtins.filter (s: s != null) (map (n: n.entityInstance) taggedNodes)
-);
-entityInstances = map (inst:
- let
- parts = lib.splitString ":" inst;
- kind = builtins.head parts;
- name = if builtins.length parts > 1 then lib.concatStringsSep ":" (lib.tail parts) else inst;
- in {
- id = sanitize "ctx_${inst}";
- inherit kind name;
- label = if inst == "flake" then "flake" else "${kind}: ${name}";
- }
-) entityInstanceNames;
-```
-
-Update the return record:
-
-```nix
-{
- inherit rootName direction;
- rootId = sanitize rootName;
- nodes = taggedNodes; # was: finalNodes
- edges = /* ... unchanged ... */;
- entityKinds = map mkEntityKind entityKindNames; # retained for sequence diagrams
- inherit entityEdges entityInstances;
-}
-```
-
-- [ ] **Step 4: Update json.nix to serialize entityInstances**
-
-In `nix/lib/diag/json.nix`, add `entityInstances` to the `toJSON` function's output record (line 49, after `entityEdges`):
-
-```nix
-entityInstances = g.entityInstances or [];
-```
-
-- [ ] **Step 5: Also add entityInstance to record-fired entries**
-
-In `nix/lib/aspects/fx/trace.nix`, update the `record-fired` handler's policy entries to include `entityInstance`:
-
-```nix
-scope = state.currentScope;
-scopeCtx = if scope == null then {} else ((state.scopeContexts or (_: {})) null).${scope} or {};
-entityInstance =
- if param.entityKind != null then
- "${param.entityKind}:${resolveEntityName param.entityKind scopeCtx}"
- else null;
-```
-
-Add `inherit entityInstance;` to each policy entry.
-
-- [ ] **Step 6: Run tests and verify IR output**
-
-Run: `nix develop -c just fmt && nix develop -c just ci fx-trace`
-Then: `nix build --override-input den . ./templates/diagram-demo#laptop-ir --no-link --print-out-paths` and inspect the JSON for `entityInstances`.
-
-```bash
-git add nix/lib/aspects/fx/trace.nix nix/lib/diag/graph.nix nix/lib/diag/json.nix
-git commit -m "feat(diag): add entityInstance tracking to trace entries and graph IR"
-```
-
----
-
-### Task 4: Update mermaid renderer for entity instance subgraphs and policy bridges
-
-**Goal:** DAG diagrams group nodes into per-instance subgraphs (`host:laptop`, `user:alice`) with policy nodes as bridges between them.
-
-**Files:**
-- Modify: `nix/lib/diag/mermaid.nix:70-275` (replace `entitySubgraph` with instance-based grouping, update policy edge rendering)
-
-**Acceptance Criteria:**
-- [ ] DAG renders `subgraph` blocks per entity instance (e.g., `host: laptop`, `user: alice`)
-- [ ] Unscoped nodes render in a `flake` subgraph
-- [ ] Policy dispatch nodes render outside all subgraphs with bridge edges
-- [ ] Cross-instance edges render at top level
-- [ ] Flat views (no entity instances) still work correctly
-
-**Verify:** `nix build --override-input den . ./templates/diagram-demo#laptop-dag --no-link --print-out-paths` → DAG contains `subgraph` blocks with entity instance labels
-
-**Steps:**
-
-- [ ] **Step 1: Replace entitySubgraph with instanceSubgraph**
-
-In `nix/lib/diag/mermaid.nix`, replace the `entitySubgraph` function and related code. The key changes:
-
-Replace `hasEntityKinds` with:
-```nix
-hasEntityInstances = graph.entityInstances or [] != [];
-```
-
-Replace `entitySubgraph` with:
-```nix
-instanceSubgraph = inst:
- let
- instNodes = builtins.filter (n:
- n.entityInstance == "${inst.kind}:${inst.name}"
- && n.id != rootId
- && !(n.isPolicyDispatch or false)
- ) nodes;
- instEdges = builtins.filter (e:
- let
- fromNode = nodeById.${e.from} or null;
- toNode = nodeById.${e.to} or null;
- fromInst = if fromNode != null then fromNode.entityInstance else null;
- toInst = if toNode != null then toNode.entityInstance else null;
- thisInst = "${inst.kind}:${inst.name}";
- in
- fromNode != null
- && fromInst == thisInst
- && (toInst == null || toInst == thisInst)
- && (e.style or "normal") != "policy"
- ) edges;
- in
- lib.optional (instNodes != []) (
- " subgraph ${inst.id}[\"${inst.label}\"]\n"
- + lib.concatMapStringsSep "\n" nodeDecl instNodes
- + "\n"
- + lib.concatMapStringsSep "\n" edgeDecl instEdges
- + "\n end"
- );
-```
-
-- [ ] **Step 2: Update topLevelNodes and unmappedEdges for instances**
-
-```nix
-topLevelNodes =
- if hasEntityInstances then
- builtins.filter (n:
- n.entityInstance == null
- && n.id != rootId
- && !(n.isPolicyDispatch or false)
- ) nodes
- else
- builtins.filter (n: n.id != rootId) nodes;
-
-policyNodes = builtins.filter (n: n.isPolicyDispatch or false) nodes;
-
-unmappedEdges = builtins.filter (e:
- let
- fromNode = nodeById.${e.from} or null;
- toNode = nodeById.${e.to} or null;
- fromInst = if fromNode != null then fromNode.entityInstance else null;
- toInst = if toNode != null then toNode.entityInstance else null;
- isCrossInst = fromInst != null && toInst != null && fromInst != toInst;
- in
- (fromNode != null && fromInst == null)
- || (isCrossInst && (e.style or "normal") != "policy")
-) edges;
-```
-
-- [ ] **Step 3: Update the diagram assembly**
-
-Replace the `hasEntityKinds` branch in the final `renderMermaid` call with `hasEntityInstances`:
-
-```nix
-if hasEntityInstances then
- lib.concatMap instanceSubgraph (graph.entityInstances or [])
- ++ [ "" ]
- ++ map nodeDecl policyNodes
- ++ map edgeDecl (builtins.filter (e: (e.style or "normal") == "policy") edges)
- ++ map edgeDecl unmappedEdges
-else
- map edgeDecl edges
-```
-
-Update `kindSuffix` to use `entityInstance` instead of `entityKind` for flat views. Update the subgraph style lines to iterate `entityInstances` instead of `entityKinds`.
-
-- [ ] **Step 4: Verify and commit**
-
-Run: `nix develop -c just fmt`
-Then: `nix build --override-input den . ./templates/diagram-demo#laptop-dag --no-link --print-out-paths` and read the output.
-Expected: Mermaid source contains `subgraph ctx_host_laptop["host: laptop"]` and similar blocks.
-
-```bash
-git add nix/lib/diag/mermaid.nix
-git commit -m "feat(diag): render entity instance subgraphs with policy bridges"
-```
-
----
-
-### Task 5: Investigate and fix anonymous nodes
-
-**Goal:** Determine what the `host/:3`, `user/:2`, `insecure-predicate/:1` nodes actually are, then either prune internal artifacts or enrich their labels.
-
-**Files:**
-- Modify: `nix/lib/aspects/fx/trace.nix` (improve anon naming with entityKind now working)
-- Possibly modify: `nix/lib/diag/filters/predicate.nix` or `fold.nix` (if pruning needed)
-
-**Acceptance Criteria:**
-- [ ] Anonymous nodes that are entity resolution boundaries get `entityKind/resolve(ctxAspect)` names
-- [ ] Anonymous nodes from policy-emitted includes get `policy:` prefix where possible
-- [ ] Pure internal plumbing nodes (no class content, no children) are identified and documented
-- [ ] `insecure-predicate/:1,2` nodes are explained and either named or pruned
-
-**Verify:** `nix build --override-input den . ./templates/diagram-demo#laptop-dag --no-link --print-out-paths` → no unexplained `:N` nodes
-
-**Steps:**
-
-- [ ] **Step 1: Verify entityKind disambiguation now works**
-
-After Task 1, rebuild the laptop DAG and check which anon nodes remain. Many should now have `entityKind/resolve(...)` names since entityKind is no longer null.
-
-Read the IR JSON and list remaining anonymous nodes. For each, check:
-- Does it have class content (`hasClass`)? → real entity, needs a name
-- Does it have children in the edge list? → structural node, needs a name
-- Neither? → plumbing artifact, candidate for pruning
-
-- [ ] **Step 2: Investigate insecure-predicate/unfree-predicate anon children**
-
-Read the demo aspects that define `insecure-predicate` and `unfree-predicate` in `templates/diagram-demo/modules/aspects/den.nix`. Check if their `includes` contain anonymous functions (compile-conditional guards). The `:N` children are likely the guard branches.
-
-If they are conditional guard branches with no class content: these are structural artifacts of `compile-conditional`. They should be folded out in the `foldWrappers` filter or the existing `aspectsOnly` filter.
-
-If they have class content: they need names derived from their parent and role.
-
-- [ ] **Step 3: Improve naming for remaining anon nodes**
-
-In `nix/lib/aspects/fx/trace.nix`, after the existing entity-kind disambiguation block, add handling for policy-sourced aspects:
-
-```nix
-else if isAnon && (param.__sourcePolicyName or null) != null then
- "policy:${param.__sourcePolicyName}"
-```
-
-Verify `__sourcePolicyName` propagation: check `nix/lib/aspects/fx/policy/classify.nix` for where this is tagged.
-
-- [ ] **Step 4: Commit findings and fixes**
-
-Document which anon nodes were real vs artifacts. Commit the naming improvements.
-
-```bash
-git add nix/lib/aspects/fx/trace.nix
-git commit -m "fix(diag): improve anonymous node naming with entityKind disambiguation"
-```
-
----
-
-### Task 6: Rename views from "stage" to "scope" vocabulary
-
-**Goal:** Update view identifiers, titles, and internal function names from the removed "stage" concept to "scope".
-
-**Files:**
-- Modify: `nix/lib/diag/views.nix:96-108,162-166` (view IDs and titles)
-- Modify: `nix/lib/diag/sequence.nix:319-359` (rename `toStageEdgesMermaid` → `toScopeEdgesMermaid`)
-- Modify: `nix/lib/diag/default.nix:208-210` (renderer spec key)
-- Modify: `templates/diagram-demo/modules/diagrams.nix` (if it references stage-* views)
-
-**Acceptance Criteria:**
-- [ ] `stage-seq` → `scope-seq`, `stage-seq-full` → `scope-seq-full`, `stage-edges` → `scope-edges`
-- [ ] View titles updated: "Stage Sequence" → "Scope Sequence" etc.
-- [ ] Internal function `toStageEdgesMermaid` → `toScopeEdgesMermaid` (and `With` variant)
-- [ ] Renderer spec key in `default.nix` updated
-- [ ] No remaining "stage" references in view/sequence code (except comments explaining the rename)
-
-**Verify:** `nix build --override-input den . ./templates/diagram-demo#laptop-scope-seq --no-link --print-out-paths` → builds successfully with "Scope Sequence" title
-
-**Steps:**
-
-- [ ] **Step 1: Rename in views.nix**
-
-```nix
-# Line 96-108: rename view IDs and titles
-view = "scope-seq";
-title = "Scope Sequence";
-altText = "Scope sequence";
-
-view = "scope-seq-full";
-title = "Scope Sequence (expanded)";
-altText = "Scope sequence expanded";
-
-# Line 162-166: extended views
-view = "scope-edges";
-title = "Scope Topology";
-altText = "Scope edges";
-```
-
-- [ ] **Step 2: Rename in sequence.nix**
-
-Rename the functions and their `With` variants:
-- `toStageEdgesMermaidWith` → `toScopeEdgesMermaidWith`
-- `toStageEdgesMermaid` → `toScopeEdgesMermaid`
-
-Update the export block at the bottom of the file.
-
-- [ ] **Step 3: Update default.nix renderer spec**
-
-In `nix/lib/diag/default.nix`, line 208-210:
-
-```nix
-# Old:
-toStageEdgesMermaid = { withFn = sequence.toStageEdgesMermaidWith; mc = true; };
-# New:
-toScopeEdgesMermaid = { withFn = sequence.toScopeEdgesMermaidWith; mc = true; };
-```
-
-- [ ] **Step 4: Update diagram-demo template if needed**
-
-Check `templates/diagram-demo/modules/diagrams.nix` for any references to `stage-seq`, `stage-seq-full`, or `stage-edges` view names and update them.
-
-- [ ] **Step 5: Run full CI and commit**
-
-Run: `nix develop -c just fmt && nix develop -c just ci`
-Expected: All tests pass. The diagram packages now use `scope-seq` etc.
-
-Note: Also check `diagrams.nix` README text (writeText derivation) for hardcoded `stage-seq` strings in the output table and update them.
-
-Note: Package names change from `laptop-stage-seq` to `laptop-scope-seq`. This is acceptable since the branch hasn't shipped.
-
-```bash
-git add nix/lib/diag/views.nix nix/lib/diag/sequence.nix nix/lib/diag/default.nix templates/diagram-demo/modules/diagrams.nix
-git commit -m "refactor(diag): rename stage-* views to scope-* vocabulary"
-```
-
----
-
-### Task 7: Update filters for entityInstances and run full verification
-
-**Goal:** Ensure all graph filters handle the new `entityInstances` field, then do a full verification pass.
-
-**Files:**
-- Modify: `nix/lib/diag/filters/fold.nix:178-185` (`flattenEntityKinds`)
-- Modify: `nix/lib/diag/filters/reshape.nix:20-49` (`contextOnly`)
-- Modify: `nix/lib/diag/filters/closure.nix:25-35` (`neighborhoodOf`)
-- Modify: `nix/lib/diag/filters/predicate.nix:55-62` (if it zeros entityKinds)
-- Modify: `nix/lib/diag/filters/diff.nix:80-85` (carry entityInstances)
-
-**Acceptance Criteria:**
-- [ ] `flattenEntityKinds` also zeros `entityInstances` and nulls `entityInstance` on nodes
-- [ ] `contextOnly` handles entity instances (or zeros them since it replaces all nodes)
-- [ ] `neighborhoodOf` zeros `entityInstances`
-- [ ] `diffGraphs` carries `entityInstances` from the `a` graph
-- [ ] Predicate filters zero `entityInstances`
-- [ ] All diagram packages build without errors
-- [ ] Full CI passes
-
-**Verify:** `nix develop -c just ci` → all pass; `nix run --override-input den . ./templates/diagram-demo#write-diagrams` → regenerates all diagrams successfully
-
-**Steps:**
-
-- [ ] **Step 1: Update flattenEntityKinds**
-
-In `nix/lib/diag/filters/fold.nix`:
-
-```nix
-flattenEntityKinds = graph:
- graph // {
- nodes = map (n: n // { entityKind = null; entityInstance = null; }) graph.nodes;
- entityKinds = [];
- entityEdges = [];
- entityInstances = [];
- };
-```
-
-- [ ] **Step 2: Update contextOnly**
-
-In `nix/lib/diag/filters/reshape.nix`, `contextOnly` replaces all nodes, so add:
-
-```nix
-entityInstances = [];
-```
-
-to the result attrset.
-
-- [ ] **Step 3: Update neighborhoodOf and closure filters**
-
-In `nix/lib/diag/filters/closure.nix`, `neighborhoodOf` already zeros `entityKinds` and `entityEdges`. Add:
-
-```nix
-entityInstances = [];
-```
-
-- [ ] **Step 4: Update diffGraphs**
-
-In `nix/lib/diag/filters/diff.nix`, carry `entityInstances` from graph `a`:
-
-```nix
-entityInstances = a.entityInstances or [];
-```
-
-- [ ] **Step 5: Update predicate filters**
-
-In `nix/lib/diag/filters/predicate.nix`, add `entityInstances = [];` where `entityKinds = [];` already appears.
-
-- [ ] **Step 6: Full CI and regenerate diagrams**
-
-Run: `nix develop -c just fmt && nix develop -c just ci`
-Expected: All 753+ tests pass.
-
-Run: `nix run --override-input den . ./templates/diagram-demo#write-diagrams`
-Expected: All diagrams regenerate without errors.
-
-Verify key outputs:
-- `laptop-dag` has entity instance subgraphs
-- `laptop-scope-seq` has entity kind participants
-- `laptop-policy-seq` has policy dispatch nodes
-- `home-alice-dag` has home entity instance subgraph
-
-```bash
-git add nix/lib/diag/filters/
-git commit -m "fix(diag): update filters to handle entityInstances field"
-```
-
----
-
-## Task Dependencies
-
-```
-Task 1 (entityKind + ctxTrace)
- ↓
-Task 2 (record-fired)
- ↓
-Task 3 (entityInstance in trace + graph IR)
- ↓
-Task 4 (mermaid renderer) Task 5 (anon nodes) Task 6 (view rename)
- ↓ ↓ ↓
-Task 7 (filters + full verification)
-```
-
-Tasks 4, 5, and 6 can run in parallel after Task 3. Task 7 depends on all of them.
diff --git a/docs/superpowers/plans/2026-05-07-diagram-identity-resolution.md.tasks.json b/docs/superpowers/plans/2026-05-07-diagram-identity-resolution.md.tasks.json
deleted file mode 100644
index f4dcc089a..000000000
--- a/docs/superpowers/plans/2026-05-07-diagram-identity-resolution.md.tasks.json
+++ /dev/null
@@ -1,89 +0,0 @@
-{
- "planPath": "docs/superpowers/plans/2026-05-07-diagram-identity-resolution.md",
- "tasks": [
- {
- "id": 2,
- "subject": "Task 1: Fix entityKind seeding and ctxTrace population",
- "status": "pending",
- "description": "**Goal:** Make tracingHandler produce entries with correct entityKind and populate ctxTrace.\n\n**Files:**\n- Modify: nix/lib/aspects/fx/trace.nix\n- Test: templates/ci/modules/features/fx-trace.nix\n\n**Verify:** nix develop -c just ci fx-trace",
- "metadata": {
- "files": ["nix/lib/aspects/fx/trace.nix", "templates/ci/modules/features/fx-trace.nix"],
- "verifyCommand": "nix develop -c just ci fx-trace",
- "acceptanceCriteria": ["entityKind seeded from param.__entityKind", "ctxTrace populated with dedup", "existing tests pass"]
- }
- },
- {
- "id": 3,
- "subject": "Task 2: Add record-fired handler for policy trace entries",
- "status": "pending",
- "blockedBy": [2],
- "description": "**Goal:** Capture fired policy names as trace entries for policy dispatch nodes.\n\n**Files:**\n- Modify: nix/lib/aspects/fx/trace.nix\n- Test: templates/ci/modules/features/fx-trace.nix\n\n**Verify:** nix develop -c just ci fx-trace",
- "metadata": {
- "files": ["nix/lib/aspects/fx/trace.nix", "templates/ci/modules/features/fx-trace.nix"],
- "verifyCommand": "nix develop -c just ci fx-trace",
- "acceptanceCriteria": ["record-fired handler added", "policy entries have isPolicyDispatch=true", "compose with defaultHandlers works"]
- }
- },
- {
- "id": 4,
- "subject": "Task 3: Add entityInstance tracking to trace and graph IR",
- "status": "pending",
- "blockedBy": [3],
- "description": "**Goal:** Each trace entry carries entityInstance field, graph IR gains entityInstances list.\n\n**Files:**\n- Modify: nix/lib/aspects/fx/trace.nix\n- Modify: nix/lib/diag/graph.nix\n\n**Verify:** nix develop -c just ci fx-trace + build laptop-ir",
- "metadata": {
- "files": ["nix/lib/aspects/fx/trace.nix", "nix/lib/diag/graph.nix", "nix/lib/diag/json.nix"],
- "verifyCommand": "nix develop -c just ci fx-trace",
- "acceptanceCriteria": ["entityInstance on trace entries", "entityInstances in graph IR", "entityKinds retained", "json.nix serializes entityInstances"]
- }
- },
- {
- "id": 5,
- "subject": "Task 4: Update mermaid renderer for instance subgraphs",
- "status": "pending",
- "blockedBy": [4],
- "description": "**Goal:** DAG diagrams group nodes into per-instance subgraphs with policy bridges.\n\n**Files:**\n- Modify: nix/lib/diag/mermaid.nix\n\n**Verify:** Build laptop-dag, check for subgraph blocks",
- "metadata": {
- "files": ["nix/lib/diag/mermaid.nix"],
- "verifyCommand": "nix build --override-input den . ./templates/diagram-demo#laptop-dag --no-link --print-out-paths",
- "acceptanceCriteria": ["per-instance subgraphs", "flake subgraph for unscoped", "policy bridges"]
- }
- },
- {
- "id": 6,
- "subject": "Task 5: Investigate and fix anonymous nodes",
- "status": "pending",
- "blockedBy": [4],
- "description": "**Goal:** Determine what anon nodes are, then prune artifacts or enrich labels.\n\n**Files:**\n- Modify: nix/lib/aspects/fx/trace.nix\n\n**Verify:** Build laptop-dag, check for unexplained anon nodes",
- "metadata": {
- "files": ["nix/lib/aspects/fx/trace.nix"],
- "verifyCommand": "nix build --override-input den . ./templates/diagram-demo#laptop-dag --no-link --print-out-paths",
- "acceptanceCriteria": ["no unexplained anon nodes", "entityKind disambiguation works", "policy naming works"]
- }
- },
- {
- "id": 7,
- "subject": "Task 6: Rename views from stage to scope vocabulary",
- "status": "pending",
- "blockedBy": [4],
- "description": "**Goal:** Update view IDs, titles, and function names from stage to scope.\n\n**Files:**\n- Modify: nix/lib/diag/views.nix, sequence.nix, default.nix, diagrams.nix\n\n**Verify:** Build laptop-scope-seq",
- "metadata": {
- "files": ["nix/lib/diag/views.nix", "nix/lib/diag/sequence.nix", "nix/lib/diag/default.nix", "templates/diagram-demo/modules/diagrams.nix"],
- "verifyCommand": "nix build --override-input den . ./templates/diagram-demo#laptop-scope-seq --no-link --print-out-paths",
- "acceptanceCriteria": ["view IDs renamed", "function names renamed", "titles updated"]
- }
- },
- {
- "id": 8,
- "subject": "Task 7: Update filters and full verification",
- "status": "pending",
- "blockedBy": [5, 6, 7],
- "description": "**Goal:** Ensure all filters handle entityInstances, full CI + diagram regen passes.\n\n**Files:**\n- Modify: nix/lib/diag/filters/\n\n**Verify:** nix develop -c just ci + write-diagrams",
- "metadata": {
- "files": ["nix/lib/diag/filters/fold.nix", "nix/lib/diag/filters/reshape.nix", "nix/lib/diag/filters/closure.nix", "nix/lib/diag/filters/predicate.nix", "nix/lib/diag/filters/diff.nix"],
- "verifyCommand": "nix develop -c just ci",
- "acceptanceCriteria": ["filters handle entityInstances", "full CI passes", "all diagrams build"]
- }
- }
- ],
- "lastUpdated": "2026-05-07T00:00:00Z"
-}
diff --git a/docs/superpowers/plans/2026-05-07-hero-canvas-animation.md b/docs/superpowers/plans/2026-05-07-hero-canvas-animation.md
deleted file mode 100644
index 28386d7d6..000000000
--- a/docs/superpowers/plans/2026-05-07-hero-canvas-animation.md
+++ /dev/null
@@ -1,283 +0,0 @@
-# Hero Canvas Animation Implementation Plan
-
-> **For agentic workers:** REQUIRED: Use superpowers-extended-cc:subagent-driven-development (if subagents available) or superpowers-extended-cc:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
-
-**Goal:** Replace the static hero image on the docs landing page with an animated canvas showing a branching topology.
-
-**Architecture:** Override Starlight's Hero.astro component to conditionally render a LogoAnimation.astro component on the index page. LogoAnimation is self-contained: canvas markup, scoped styles, and a client-side script with the topology animation algorithm adapted from ~/foo-wip.html.
-
-**Tech Stack:** Astro components, HTML Canvas 2D API, Catppuccin CSS custom properties, IntersectionObserver, MutationObserver
-
-**Spec:** `docs/superpowers/specs/2026-05-07-hero-canvas-animation-design.md`
-
----
-
-### Task 1: Create LogoAnimation.astro component
-
-**Goal:** Create the self-contained canvas animation component with topology algorithm, theme-aware colors, visibility pausing, and SPA cleanup.
-
-**Files:**
-- Create: `docs/src/components/LogoAnimation.astro`
-- Reference: `/home/sini/foo-wip.html` (source algorithm, outside repo)
-
-**Acceptance Criteria:**
-- [ ] Canvas renders at 400×400
-- [ ] Animation cycles: grow topology → photon walk → rewind → regenerate (infinite loop)
-- [ ] Colors read from `--sl-color-blue`, `--sl-color-purple`, `--sl-color-white` CSS custom properties
-- [ ] Theme toggle (light/dark) updates colors via MutationObserver on `data-theme`
-- [ ] IntersectionObserver pauses animation when canvas is off-screen
-- [ ] `astro:before-swap` listener cleans up (cancelAnimationFrame, clearTimeouts, disconnect observers)
-- [ ] No "den" text overlay
-- [ ] Component wrapper has correct sizing styles for hero slot
-
-**Verify:** Visual inspection — component can be tested by temporarily importing it directly in index.mdx body.
-
-**Steps:**
-
-- [ ] **Step 1: Create LogoAnimation.astro with markup and scoped styles**
-
-```astro
----
-// No server-side logic needed
----
-
-
-
-
-
-
-```
-
-- [ ] **Step 2: Add client-side script with color resolution and observers**
-
-Add a `