Files
nixos-config/.sisyphus/plans/dendritic-migration.md
2026-03-05 15:41:41 +00:00

77 KiB

Dendritic Migration: Rewrite NixOS Config with den Framework

TL;DR

Quick Summary: Complete rewrite of the NixOS/darwin configuration from host-centric architecture to feature/aspect-centric using the den framework (vic/den). Replaces specialArgs pass-through, unifies home-manager integration paths, replaces colmena with deploy-rs, and adopts auto-importing via import-tree.

Deliverables:

  • New flake.nix using den + flake-parts + import-tree + flake-file
  • All 38+ profiles converted to den aspects in modules/
  • All 4 hosts (chidi, jason, michael, tahani) building successfully
  • deploy-rs replacing colmena for NixOS deployment
  • Zero specialArgs usage — inputs flow via flake-parts module args
  • Overlays preserved and migrated to den module structure
  • SOPS secrets preserved exactly

Estimated Effort: Large Parallel Execution: YES - 5 waves Critical Path: Task 1 → Task 2 → Task 4 → Tasks 8-11 → Task 20 → Final


Context

Original Request

Migrate NixOS configuration to the dendritic pattern using the den framework. Organize config by feature instead of by tool (nixos vs home-manager). Make every file a flake-parts module. Eliminate specialArgs pass-through hell.

Interview Summary

Key Discussions:

  • Framework choice: User chose den (vic/den) over pure dendritic pattern — wants full aspect/context/batteries system
  • Migration strategy: Clean rewrite, not incremental — fresh start with new structure
  • Deploy: Replace colmena with deploy-rs for NixOS server deployment
  • Input management: Use flake-file (vic/flake-file) to auto-manage flake.nix inputs from modules
  • Custom modules: Absorb my.gitea and my.pgbackrest into den aspects (no separate nixosModules export)
  • Host roles: chidi = work laptop (Slack, tuist.dev email), jason = personal laptop
  • Feature granularity: Trusted to planner — will group by concern where natural

Research Findings:

  • den framework provides: aspects, contexts, batteries (define-user, primary-user, user-shell, inputs')
  • import-tree auto-imports all .nix files, ignores /_ prefixed paths
  • flake-file auto-manages flake.nix from module-level declarations
  • deploy-rs uses deploy.nodes.<name> flake output — limited darwin support, use only for NixOS
  • Real-world den migration examples reviewed (hyperparabolic/nix-config, dendrix community)

Metis Review

Identified Gaps (addressed):

  • local.dock.* is a third custom module namespace that was initially missed — will be migrated
  • profiles/wallpaper.nix, profiles/packages.nix, profiles/open-project.nix are pure functions, NOT modules — must go under _-prefixed paths to avoid import-tree failures
  • Himalaya cross-dependency (tahani reads HM config from NixOS scope) — redesign using overlay package directly
  • zellij.nix uses osConfig.networking.hostName == "tahani" hostname comparison — replace with per-host aspect override
  • pgbackrest module is exported but never imported internally — absorb into aspects per user's decision
  • apps/ directory contains bash scripts — will be rewritten to Nushell as part of this migration
  • Platform-conditional code (stdenv.isDarwin) in HM profiles is correct — keep, don't split into per-class
  • Published flake outputs need explicit preservation decisions

Work Objectives

Core Objective

Rewrite the entire NixOS/darwin configuration to use the den framework's aspect-oriented architecture, eliminating specialArgs pass-through and organizing all configuration by feature/concern instead of by host.

Concrete Deliverables

  • New flake.nix with den + import-tree + flake-aspects + flake-file
  • modules/ directory with all features as den aspects (auto-imported)
  • _lib/ directory for non-module utility functions
  • All 4 hosts building identically to current config
  • deploy-rs configuration for michael and tahani
  • No residual hosts/, profiles/, old modules/ structure

Definition of Done

  • nix build ".#darwinConfigurations.chidi.system" succeeds
  • nix build ".#darwinConfigurations.jason.system" succeeds
  • nix build ".#nixosConfigurations.michael.config.system.build.toplevel" succeeds
  • nix build ".#nixosConfigurations.tahani.config.system.build.toplevel" succeeds
  • nix flake check passes
  • alejandra --check . passes
  • nix eval ".#deploy.nodes.michael" returns valid deploy-rs node
  • nix eval ".#deploy.nodes.tahani" returns valid deploy-rs node
  • Zero uses of specialArgs or extraSpecialArgs in codebase

Must Have

  • Exact behavioral equivalence — same services, packages, config files on all hosts
  • SOPS secret paths preserved exactly (darwin: age keyfile, NixOS: ssh host key)
  • Per-host git email overrides preserved (chidi→tuist.dev, jason/tahani→schmatzler.com)
  • All current overlays functional
  • deploy-rs for NixOS hosts (michael, tahani)
  • Darwin deployment stays as local darwin-rebuild switch (deploy-rs has limited darwin support)
  • All stateVersion values unchanged (darwin: 6, nixos: "25.11", homeManager: "25.11")
  • All perSystem apps (build, apply, build-switch, rollback) preserved or equivalently replaced

Must NOT Have (Guardrails)

  • MUST NOT add new packages, services, or features not in current config
  • MUST NOT split simple single-concern profiles into multiple aspects (atuin.nix → one aspect)
  • MUST NOT add abstractions for "future use" (multi-user support, dynamic host detection, etc.)
  • MUST NOT rewrite app scripts in any language other than Nushell (per AGENTS.md scripting policy)
  • MUST NOT change any stateVersion values
  • MUST NOT re-encrypt or restructure SOPS secrets — paths and key assignments stay identical
  • MUST NOT change any service configuration values (ports, paths, domains, credentials)
  • MUST NOT add den batteries beyond what's needed — each must map to a current requirement
  • MUST NOT create abstractions over overlays (current pattern is clear)
  • MUST NOT add excessive comments, JSDoc, or documentation to nix files
  • MUST NOT use hostname string comparisons in shared aspects (no osConfig.networking.hostName == "tahani")
  • MUST NOT read HM config values from NixOS scope (the himalaya anti-pattern)

Verification Strategy

ZERO HUMAN INTERVENTION — ALL verification is agent-executed. No exceptions.

Test Decision

  • Infrastructure exists: NO (this is a NixOS config, not a software project)
  • Automated tests: None (verification is via nix build / nix eval / nix flake check)
  • Framework: N/A

QA Policy

Every task MUST verify its changes compile via nix eval or nix build --dry-run. Formatting MUST pass alejandra --check . after every file change.


Execution Strategy

Parallel Execution Waves

Wave 1 (Foundation — flake bootstrap + utilities):
├── Task 1: New flake.nix with den + import-tree + flake-file [deep]
├── Task 2: den bootstrap module (hosts, users, defaults, base) [deep]
├── Task 3: Utility functions under _lib/ [quick]
├── Task 4: Overlays module [quick]
├── Task 5: SOPS secrets aspects (all 4 hosts) [quick]
└── Task 6: Deploy-rs module [quick]

Wave 2 (Core shared aspects — no host dependencies):
├── Task 7: Core system aspect (nix settings, shells) [quick]
├── Task 8: Darwin system aspect (system defaults, dock, homebrew, nix-homebrew) [unspecified-high]
├── Task 9: NixOS system aspect (boot, sudo, systemd, users) [unspecified-high]
├── Task 10: User aspect (cschmatzler — define-user, primary-user, shell) [quick]
└── Task 11: perSystem apps module [quick]

Wave 3 (Home-manager aspects — bulk migration, MAX PARALLEL):
├── Task 12: Shell aspects (nushell, bash, zsh, starship) [quick]
├── Task 13: Dev tools aspects (git, jujutsu, lazygit, jjui, direnv, mise) [quick]
├── Task 14: Editor aspects (neovim/nixvim) [unspecified-high]
├── Task 15: Terminal aspects (ghostty, zellij, yazi, bat, fzf, ripgrep, zoxide) [quick]
├── Task 16: Communication aspects (himalaya, mbsync, ssh) [unspecified-high]
├── Task 17: Desktop aspects (aerospace, wallpaper, home base) [quick]
├── Task 18: AI tools aspects (opencode, claude-code) [quick]
└── Task 19: Miscellaneous aspects (atuin, zk, open-project) [quick]

Wave 4 (Host-specific + services + scripts):
├── Task 20: Server aspects — michael (gitea + litestream + restic) [deep]
├── Task 21: Server aspects — tahani (adguard, paperless, docker, inbox-triage) [deep]
├── Task 22: Host aspects — chidi (work-specific) [quick]
├── Task 23: Host aspects — jason (personal-specific) [quick]
├── Task 24: Network aspects (openssh, fail2ban, tailscale, postgresql) [quick]
├── Task 25: Packages aspect (system packages list) [quick]
└── Task 29: Rewrite apps/ scripts from bash to Nushell [quick]

Wave 5 (Cleanup + verification):
├── Task 26: Remove old structure (hosts/, profiles/, old modules/) [quick]
├── Task 27: Full build verification all 4 hosts [deep]
└── Task 28: Formatting pass + final cleanup [quick]

Wave FINAL (After ALL tasks — independent review, 4 parallel):
├── Task F1: Plan compliance audit (oracle)
├── Task F2: Code quality review (unspecified-high)
├── Task F3: Real manual QA (unspecified-high)
└── Task F4: Scope fidelity check (deep)

Critical Path: Task 1 → Task 2 → Task 7 → Tasks 8-9 → Task 10 → Tasks 12-19 → Tasks 20-21 + Task 29 → Task 27 → F1-F4
Parallel Speedup: ~60% faster than sequential
Max Concurrent: 8 (Wave 3)

Dependency Matrix

  • 1: — — 2, 3, 4, 5, 6, 11, W1
  • 2: 1 — 7, 8, 9, 10, W1
  • 3: 1 — 4, 14, 16, 20, 21, W1
  • 4: 1, 3 — 7, 8, 9, W1
  • 5: 1, 2 — 20, 21, 22, 23, W1
  • 6: 1, 2 — 27, W1
  • 7: 2, 4 — 12-19, W2
  • 8: 2, 4 — 17, 22, 23, W2
  • 9: 2, 4 — 20, 21, 24, W2
  • 10: 2 — 12-19, W2
  • 11: 1 — 27, W2
  • 12-19: 7, 10 — 20-25, W3
  • 20: 5, 9, 14, 16 — 27, W4
  • 21: 5, 9, 16, 18 — 27, W4
  • 22: 5, 8, 17 — 27, W4
  • 23: 5, 8, 17 — 27, W4
  • 24: 9 — 27, W4
  • 25: 7 — 27, W4
  • 29: 11 — 27, W4
  • 26: 27 — F1-F4, W5
  • 27: 20-25, 29 — 26, 28, W5
  • 28: 27 — F1-F4, W5

Agent Dispatch Summary

  • W1: 6 — T1 → deep, T2 → deep, T3 → quick, T4 → quick, T5 → quick, T6 → quick
  • W2: 5 — T7 → quick, T8 → unspecified-high, T9 → unspecified-high, T10 → quick, T11 → quick
  • W3: 8 — T12-T13 → quick, T14 → unspecified-high, T15 → quick, T16 → unspecified-high, T17-T19 → quick
  • W4: 7 — T20-T21 → deep, T22-T25 → quick, T29 → quick
  • W5: 3 — T26 → quick, T27 → deep, T28 → quick
  • FINAL: 4 — F1 → oracle, F2 → unspecified-high, F3 → unspecified-high, F4 → deep

TODOs

  • 1. New flake.nix with den + import-tree + flake-file

    What to do:

    • Move existing modules/gitea.nix and modules/pgbackrest.nix to modules/_legacy/ (underscore prefix = ignored by import-tree). These will be absorbed into den aspects in Tasks 20/21 and deleted in Task 26
    • Create new flake.nix that uses flake-parts.lib.mkFlake with inputs.import-tree ./modules
    • Add all required inputs: preserve existing (nixpkgs, flake-parts, sops-nix, home-manager, darwin, nix-homebrew, homebrew-core, homebrew-cask, nixvim, zjstatus, llm-agents, disko, jj-ryu, jj-starship, himalaya, naersk, tuicr) AND add new required inputs (den, import-tree, flake-aspects, flake-file, deploy-rs)
    • Preserve all existing follows declarations and flake = false attributes
    • The outputs should be minimal: inputs.flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./modules)
    • Remove the old outputs block entirely (specialArgs, genAttrs, inline configs — all gone)
    • Create a minimal placeholder module in modules/ (e.g. empty flake-parts module) so import-tree has something to import
    • Ensure nix flake show doesn't error

    Must NOT do:

    • Do not include ANY configuration logic in flake.nix — everything goes in modules/
    • Do not change any existing input URLs or versions
    • Do not add inputs beyond the required new ones (den, import-tree, flake-aspects, flake-file, deploy-rs)

    Recommended Agent Profile:

    • Category: deep
      • Reason: This is the highest-risk task — if the flake bootstrap is wrong, nothing works. Needs careful attention to input declarations, follows chains, and flake-file compatibility.
    • Skills: []

    Parallelization:

    • Can Run In Parallel: NO
    • Parallel Group: Wave 1 (start first)
    • Blocks: All other tasks
    • Blocked By: None

    References:

    Acceptance Criteria:

    • flake.nix has all current inputs preserved with correct follows
    • flake.nix has new required inputs: den, import-tree, flake-aspects, flake-file, deploy-rs
    • flake.nix outputs use import-tree ./modules
    • modules/_legacy/gitea.nix and modules/_legacy/pgbackrest.nix exist (moved from modules/)
    • nix flake show doesn't error (with a minimal placeholder module)

    QA Scenarios:

    Scenario: Flake evaluates without errors
      Tool: Bash
      Preconditions: New flake.nix written, modules/ directory exists with at least one .nix file
      Steps:
        1. Run `nix flake show --json 2>&1`
        2. Assert exit code 0
        3. Run `nix flake check --no-build 2>&1`
      Expected Result: Both commands exit 0 without evaluation errors
      Evidence: .sisyphus/evidence/task-1-flake-eval.txt
    
    Scenario: All inputs preserved
      Tool: Bash
      Preconditions: New flake.nix written
      Steps:
        1. Run `nix flake metadata --json | nu -c '$in | from json | get locks.nodes | columns | sort'`
        2. Compare against expected list: [darwin, disko, deploy-rs, den, flake-aspects, flake-file, flake-parts, himalaya, home-manager, homebrew-cask, homebrew-core, import-tree, jj-ryu, jj-starship, llm-agents, naersk, nix-homebrew, nixpkgs, nixvim, sops-nix, tuicr, zjstatus]
      Expected Result: All inputs present in lock file
      Evidence: .sisyphus/evidence/task-1-inputs.txt
    

    Commit: YES (group with 2, 3)

    • Message: feat(den): bootstrap flake with den + import-tree + flake-file
    • Files: flake.nix, modules/
    • Pre-commit: alejandra --check .
  • 2. Den bootstrap module — hosts, users, defaults, base

    What to do:

    • Create modules/den.nix — import den flakeModule, declare all 4 hosts and their users:
      den.hosts.aarch64-darwin.chidi.users.cschmatzler = {};
      den.hosts.aarch64-darwin.jason.users.cschmatzler = {};
      den.hosts.x86_64-linux.michael.users.cschmatzler = {};
      den.hosts.x86_64-linux.tahani.users.cschmatzler = {};
      
    • Set den.base.user.classes = lib.mkDefault ["homeManager"];
    • Create modules/defaults.nix — define den.default with:
      • includes = [den.provides.define-user den.provides.inputs']
      • State versions: nixos.system.stateVersion = "25.11", homeManager.home.stateVersion = "25.11"
      • Darwin state version handled per-class
    • Wire flake-file module import if needed
    • Create modules/flake-parts.nix to import inputs.flake-parts.flakeModules.modules

    Must NOT do:

    • Do not add multi-user abstractions
    • Do not add batteries not needed
    • Do not change stateVersion values

    Recommended Agent Profile:

    • Category: deep
      • Reason: Core den bootstrap — needs understanding of den's host/user/aspect model and correct battery usage
    • Skills: []

    Parallelization:

    • Can Run In Parallel: NO
    • Parallel Group: Wave 1 (after Task 1)
    • Blocks: Tasks 7-10, all Wave 2+
    • Blocked By: Task 1

    References:

    Acceptance Criteria:

    • nix eval ".#darwinConfigurations" --json shows chidi and jason
    • nix eval ".#nixosConfigurations" --json shows michael and tahani
    • den.default includes define-user and inputs' batteries

    QA Scenarios:

    Scenario: All 4 host configurations exist
      Tool: Bash
      Preconditions: modules/den.nix and modules/defaults.nix created
      Steps:
        1. Run `nix eval ".#darwinConfigurations" --json 2>&1 | nu -c '$in | from json | columns | sort'`
        2. Assert output contains "chidi" and "jason"
        3. Run `nix eval ".#nixosConfigurations" --json 2>&1 | nu -c '$in | from json | columns | sort'`
        4. Assert output contains "michael" and "tahani"
      Expected Result: All 4 hosts registered
      Evidence: .sisyphus/evidence/task-2-hosts.txt
    

    Commit: YES (group with 1, 3)

    • Message: feat(den): bootstrap flake with den + import-tree + flake-file
    • Files: modules/den.nix, modules/defaults.nix, modules/flake-parts.nix
    • Pre-commit: alejandra --check .
  • 3. Utility functions under _lib/

    What to do:

    • Create modules/_lib/ directory (underscore prefix = ignored by import-tree)
    • Move lib/build-rust-package.nixmodules/_lib/build-rust-package.nix (preserve content exactly)
    • Convert profiles/wallpaper.nixmodules/_lib/wallpaper.nix (it's a pure function {pkgs}: ..., not a module)
    • Convert profiles/open-project.nixmodules/_lib/open-project.nix (pure function)
    • Move lib/constants.nixmodules/_lib/constants.nix (preserve content exactly — values used by den.base and aspects)
    • NOTE: profiles/packages.nix is also a function (callPackage-compatible) — convert to modules/_lib/packages.nix

    Must NOT do:

    • Do not modify function signatures or return values
    • Do not place these under non-underscore paths (import-tree would fail)

    Recommended Agent Profile:

    • Category: quick
      • Reason: Simple file moves/copies with path updates
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 4, 5, 6)
    • Blocks: Tasks that reference these utilities (overlays, packages, wallpaper aspects)
    • Blocked By: Task 1

    References:

    • lib/constants.nix:1-14 — Constants to move
    • lib/build-rust-package.nix — Rust package builder overlay helper
    • profiles/wallpaper.nix — Pure function returning derivation
    • profiles/open-project.nix — Pure function returning derivation
    • profiles/packages.nix — callPackage-compatible function returning package list
    • import-tree docs: /_ prefix convention for ignoring paths

    Acceptance Criteria:

    • modules/_lib/ directory exists with all 5 files
    • No function files exist under non-underscore paths in modules/
    • alejandra --check modules/_lib/ passes

    QA Scenarios:

    Scenario: Utility files exist and are ignored by import-tree
      Tool: Bash
      Preconditions: _lib/ directory created with all files
      Steps:
        1. Verify `modules/_lib/constants.nix` exists
        2. Verify `modules/_lib/build-rust-package.nix` exists
        3. Verify `modules/_lib/wallpaper.nix` exists
        4. Verify `modules/_lib/open-project.nix` exists
        5. Verify `modules/_lib/packages.nix` exists
        6. Run `nix flake check --no-build` to confirm import-tree ignores _lib/
      Expected Result: All files present, flake evaluates without trying to import them as modules
      Evidence: .sisyphus/evidence/task-3-lib-files.txt
    

    Commit: YES (group with 1, 2)

    • Message: feat(den): bootstrap flake with den + import-tree + flake-file
    • Files: modules/_lib/*
  • 4. Overlays module

    What to do:

    • Create modules/overlays.nix — a flake-parts module that defines all overlays
    • Port the current overlay pattern: each overlay takes inputs from flake-parts module args and produces a nixpkgs overlay
    • Register overlays via nixpkgs.overlays in den.default or via flake.overlays
    • Port these overlays: himalaya, jj-ryu (uses _lib/build-rust-package.nix), jj-starship (passthrough), tuicr, zjstatus
    • The current dynamic loader pattern (overlays/default.nix) is no longer needed — each overlay is defined inline or imported from _lib/
    • Ensure overlays are applied to all hosts via den.default.nixos and den.default.darwin or equivalent

    Must NOT do:

    • Do not create abstractions over the overlay pattern
    • Do not change what packages the overlays produce
    • Do not publish flake.overlays output (user chose to absorb into aspects — if external consumers need it, reconsider)

    Recommended Agent Profile:

    • Category: quick
      • Reason: Translating existing overlays into a flake-parts module is straightforward
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 3, 5, 6)
    • Blocks: Tasks 7-9 (overlays must be registered before host configs use overlayed packages)
    • Blocked By: Tasks 1, 3

    References:

    • overlays/default.nix:1-18 — Current dynamic overlay loader
    • overlays/himalaya.nix:1-3 — Overlay pattern: {inputs}: final: prev: { himalaya = inputs.himalaya.packages...; }
    • overlays/jj-ryu.nix — Uses _lib/build-rust-package.nix helper with naersk
    • overlays/jj-starship.nix — Passes through upstream overlay
    • overlays/zjstatus.nix — Package from input
    • overlays/tuicr.nix — Package from input

    Acceptance Criteria:

    • modules/overlays.nix defines all 5 overlays
    • Overlays are applied to nixpkgs for all hosts
    • nix eval ".#nixosConfigurations.tahani.config.nixpkgs.overlays" --json shows overlays present

    QA Scenarios:

    Scenario: Overlayed packages are available
      Tool: Bash
      Preconditions: overlays.nix created and hosts building
      Steps:
        1. Run `nix eval ".#nixosConfigurations.tahani.pkgs.himalaya" --json 2>&1`
        2. Assert it evaluates (doesn't error with "himalaya not found")
      Expected Result: himalaya package resolves from overlay
      Evidence: .sisyphus/evidence/task-4-overlays.txt
    

    Commit: NO (groups with Wave 1)

  • 5. SOPS secrets aspects (all 4 hosts)

    What to do:

    • Create modules/secrets.nix — a flake-parts module handling SOPS for all hosts
    • Use den aspects to define per-host secrets:
      • den.aspects.chidi: darwin SOPS — sops.age.keyFile = "/Users/cschmatzler/.config/sops/age/keys.txt", disable ssh/gnupg paths
      • den.aspects.jason: same darwin SOPS pattern
      • den.aspects.michael: NixOS SOPS — sops.age.sshKeyPaths, define secrets (michael-gitea-litestream, michael-gitea-restic-password, michael-gitea-restic-env) with exact same sopsFile paths, owners, groups
      • den.aspects.tahani: NixOS SOPS — define secrets (tahani-paperless-password, tahani-email-password) with exact same paths and owners
    • Import sops-nix modules per-class: den.default.nixos imports inputs.sops-nix.nixosModules.sops, den.default.darwin imports inputs.sops-nix.darwinModules.sops
    • Use flake-file to declare sops-nix input dependency from this module

    Must NOT do:

    • Do not change any sopsFile paths, owners, groups, or formats
    • Do not re-encrypt secrets
    • Do not modify the secrets/ directory structure

    Recommended Agent Profile:

    • Category: quick
      • Reason: Direct translation of existing secrets config into den aspects
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 3, 4, 6)
    • Blocks: Tasks 20-23 (host aspects need secrets available)
    • Blocked By: Tasks 1, 2

    References:

    • hosts/chidi/secrets.nix:1-5 — Darwin SOPS pattern
    • hosts/jason/secrets.nix — Same as chidi
    • hosts/michael/secrets.nix:1-22 — NixOS SOPS with gitea secrets (owners, groups, sopsFile paths)
    • hosts/tahani/secrets.nix:1-13 — NixOS SOPS with paperless + email secrets
    • profiles/nixos.nix:57sops.age.sshKeyPaths for NixOS
    • secrets/ directory — Encrypted secret files (DO NOT MODIFY)

    Acceptance Criteria:

    • nix eval ".#nixosConfigurations.michael.config.sops.secrets" --json | nu -c '$in | from json | columns' contains michael-gitea-litestream, michael-gitea-restic-password, michael-gitea-restic-env
    • nix eval ".#nixosConfigurations.tahani.config.sops.secrets" --json | nu -c '$in | from json | columns' contains tahani-paperless-password, tahani-email-password

    QA Scenarios:

    Scenario: SOPS secrets paths preserved exactly
      Tool: Bash
      Preconditions: secrets.nix module created
      Steps:
        1. Run `nix eval ".#nixosConfigurations.michael.config.sops.secrets" --json 2>&1`
        2. Assert keys include michael-gitea-litestream, michael-gitea-restic-password, michael-gitea-restic-env
        3. Run `nix eval ".#nixosConfigurations.tahani.config.sops.secrets" --json 2>&1`
        4. Assert keys include tahani-paperless-password, tahani-email-password
      Expected Result: All secrets defined with correct paths
      Evidence: .sisyphus/evidence/task-5-sops.txt
    
    Scenario: Darwin SOPS uses age keyfile, not SSH
      Tool: Bash
      Preconditions: secrets.nix module created
      Steps:
        1. Run `nix eval ".#darwinConfigurations.chidi.config.sops.age.keyFile" 2>&1`
        2. Assert output is "/Users/cschmatzler/.config/sops/age/keys.txt"
      Expected Result: Darwin hosts use age keyfile path
      Evidence: .sisyphus/evidence/task-5-sops-darwin.txt
    

    Commit: NO (groups with Wave 1)

  • 6. Deploy-rs module

    What to do:

    • Create modules/deploy.nix — a flake-parts module that configures deploy-rs
    • Add deploy-rs as a flake input (replacing colmena)
    • Define flake.deploy.nodes.michael and flake.deploy.nodes.tahani with:
      • hostname = "<hostname>"
      • profiles.system.user = "root"
      • profiles.system.path = deploy-rs.lib.x86_64-linux.activate.nixos self.nixosConfigurations.<hostname>
      • sshUser = "cschmatzler"
    • Add deploy-rs checks to flake.checks via deploy-rs.lib.x86_64-linux.deployChecks self.deploy
    • Only NixOS hosts — darwin stays local darwin-rebuild switch
    • Use flake-file to declare deploy-rs input from this module

    Must NOT do:

    • Do not add darwin deploy-rs nodes (limited support)
    • Do not remove the perSystem apps that handle darwin apply

    Recommended Agent Profile:

    • Category: quick
      • Reason: Straightforward deploy-rs configuration with two nodes
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 3, 4, 5)
    • Blocks: Task 27 (verification needs deploy-rs nodes)
    • Blocked By: Tasks 1, 2

    References:

    • flake.nix:110-130 — Current colmena config to replace
    • deploy-rs docs: https://github.com/serokell/deploy-rs — Node config format
    • Current deployment users: user = "cschmatzler" for colmena targetUser

    Acceptance Criteria:

    • nix eval ".#deploy.nodes.michael.hostname" returns "michael"
    • nix eval ".#deploy.nodes.tahani.hostname" returns "tahani"
    • deploy-rs checks registered in flake checks

    QA Scenarios:

    Scenario: deploy-rs nodes exist for NixOS hosts only
      Tool: Bash
      Preconditions: deploy.nix module created
      Steps:
        1. Run `nix eval ".#deploy.nodes" --json 2>&1 | nu -c '$in | from json | columns | sort'`
        2. Assert output is ["michael", "tahani"]
        3. Assert no darwin hosts in deploy nodes
      Expected Result: Exactly 2 deploy nodes for NixOS hosts
      Evidence: .sisyphus/evidence/task-6-deploy.txt
    

    Commit: NO (groups with Wave 1)

  • 7. Core system aspect (nix settings, shells)

    What to do:

    • Create modules/core.nix — den aspect for core nix/system settings shared across all hosts
    • Port profiles/core.nix content into den.aspects.core with appropriate per-class configs:
      • os class (or both nixos and darwin): nix settings (experimental-features, auto-optimise-store), fish shell enable, environment.systemPackages (common tools)
    • Include this aspect in den.default.includes so all hosts get it
    • Remove the user and constants args dependency — use den context instead

    Must NOT do:

    • Do not change nix settings values
    • Do not add packages not in current core.nix

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 8-11)
    • Blocks: Tasks 12-19 (Wave 3 aspects may depend on core being applied)
    • Blocked By: Tasks 2, 4

    References:

    • profiles/core.nix — Current core profile with nix settings, fish, systemPackages

    Acceptance Criteria:

    • den.default.includes contains core aspect
    • nix experimental-features setting preserved

    QA Scenarios:

    Scenario: Core nix settings applied to all hosts
      Tool: Bash
      Steps:
        1. Run `nix eval ".#nixosConfigurations.michael.config.nix.settings.experimental-features" --json 2>&1`
        2. Assert contains "nix-command" and "flakes"
      Expected Result: Nix settings from core.nix applied
      Evidence: .sisyphus/evidence/task-7-core.txt
    

    Commit: NO (groups with Wave 2)

  • 8. Darwin system aspect (system defaults, dock, homebrew, nix-homebrew)

    What to do:

    • Create modules/darwin.nix — den aspect for darwin-specific system configuration
    • Port profiles/darwin.nix into den.aspects.darwin-system:
      • darwin class: system.defaults (NSGlobalDomain, dock, finder, trackpad, screencapture, screensaver, loginwindow, spaces, WindowManager, menuExtraClock, CustomUserPreferences), nix GC settings, nix trusted-users
      • User creation for darwin (users.users.${user}) — or rely on den.provides.define-user
      • home-manager.useGlobalPkgs = true
    • Port profiles/dock.nix — the local.dock.* custom option module. Absorb into the darwin aspect or create modules/dock.nix
    • Port profiles/homebrew.nix into modules/homebrew.nix — nix-homebrew configuration with taps, casks, etc.
    • Include darwin-system aspect in darwin host aspects (chidi, jason)
    • Wire nix-homebrew module import via den.aspects.darwin-system.darwin
    • State version: system.stateVersion = 6 (from constants.stateVersions.darwin)

    Must NOT do:

    • Do not change any system.defaults values
    • Do not change dock entries
    • Do not change homebrew taps or cask lists

    Recommended Agent Profile:

    • Category: unspecified-high
      • Reason: Multiple complex darwin profiles need merging into aspects with correct module imports
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 7, 9-11)
    • Blocks: Tasks 17, 22, 23 (desktop and host-specific darwin)
    • Blocked By: Tasks 2, 4

    References:

    • profiles/darwin.nix:1-125 — Full darwin system config (system defaults, users, nix settings)
    • profiles/dock.nix — Custom local.dock.* option module with entries
    • profiles/homebrew.nix — Homebrew config with taps, brews, casks
    • flake.nix:77-94 — Current darwin configuration wiring (nix-homebrew module, homebrew taps)

    Acceptance Criteria:

    • All system.defaults values preserved
    • nix-homebrew configured with same taps
    • dock entries preserved
    • nix eval ".#darwinConfigurations.chidi.config.system.defaults.NSGlobalDomain.AppleInterfaceStyle" returns null

    QA Scenarios:

    Scenario: Darwin system defaults applied
      Tool: Bash
      Steps:
        1. Run `nix eval ".#darwinConfigurations.chidi.config.system.defaults.dock.autohide" 2>&1`
        2. Assert output is "true"
      Expected Result: Dock autohide preserved from darwin.nix
      Evidence: .sisyphus/evidence/task-8-darwin.txt
    

    Commit: NO (groups with Wave 2)

  • 9. NixOS system aspect (boot, sudo, systemd, users)

    What to do:

    • Create modules/nixos-system.nix — den aspect for NixOS-specific system configuration
    • Port profiles/nixos.nix into den.aspects.nixos-system:
      • nixos class: security.sudo, boot (systemd-boot, EFI, kernel modules, latest kernel), nix settings (trusted-users, gc, nixPath), time.timeZone, user creation with groups
      • home-manager.useGlobalPkgs = true and home-manager.sharedModules — but this should now be handled by den's HM integration battery (no manual _module.args)
      • Root user SSH authorized keys from constants
    • State version: system.stateVersion = "25.11"
    • sops.age.sshKeyPaths for NixOS hosts

    Must NOT do:

    • Do not change sudo rules
    • Do not change boot loader settings
    • Do not change user groups

    Recommended Agent Profile:

    • Category: unspecified-high
      • Reason: NixOS system profile has multiple interconnected settings that need careful migration
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 7, 8, 10, 11)
    • Blocks: Tasks 20, 21, 24 (server and network aspects)
    • Blocked By: Tasks 2, 4

    References:

    • profiles/nixos.nix:1-79 — Full NixOS system config (sudo, boot, nix, users, HM wiring)
    • lib/constants.nix:4-7 — SSH keys for root and user

    Acceptance Criteria:

    • sudo rules preserved exactly
    • boot settings (systemd-boot, kernel) preserved
    • No manual _module.args or sharedModules for HM — den handles it
    • Root SSH authorized keys set

    QA Scenarios:

    Scenario: NixOS boot config preserved
      Tool: Bash
      Steps:
        1. Run `nix eval ".#nixosConfigurations.michael.config.boot.loader.systemd-boot.enable" 2>&1`
        2. Assert output is "true"
      Expected Result: Boot settings from nixos.nix preserved
      Evidence: .sisyphus/evidence/task-9-nixos.txt
    

    Commit: NO (groups with Wave 2)

  • 10. User aspect (cschmatzler — primary user)

    What to do:

    • Create modules/user.nix — den aspect for the cschmatzler user
    • Define den.aspects.cschmatzler:
      • includes: den.provides.primary-user, (den.provides.user-shell "nushell"), relevant shared aspects
      • homeManager: basic home config from profiles/home.nix (programs.home-manager.enable, home.packages via callPackage _lib/packages.nix, home.activation for wallpaper on darwin)
      • SSH authorized keys from constants
    • The user aspect is the central hub that includes all feature aspects the user wants
    • Per-host email overrides will be in host aspects (Tasks 22-23), not here
    • Default email: homeManager.programs.git.settings.user.email left for host-specific override

    Must NOT do:

    • Do not add features not in current user config
    • Do not set git email here (it's per-host)

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 7-9, 11)
    • Blocks: Tasks 12-19 (HM aspects)
    • Blocked By: Task 2

    References:

    • profiles/home.nix:1-24 — Home base config (home-manager enable, packages, wallpaper activation)
    • lib/constants.nix:2 — User name "cschmatzler"
    • lib/constants.nix:4-7 — SSH authorized keys
    • den batteries docs: https://den.oeiuwq.com/guides/batteries/ — primary-user, user-shell

    Acceptance Criteria:

    • den.aspects.cschmatzler defined with primary-user and user-shell batteries
    • Home packages list from packages.nix applied

    QA Scenarios:

    Scenario: User aspect creates correct system user
      Tool: Bash
      Steps:
        1. Run `nix eval ".#nixosConfigurations.michael.config.users.users.cschmatzler.isNormalUser" 2>&1`
        2. Assert output is "true"
      Expected Result: User created with isNormalUser
      Evidence: .sisyphus/evidence/task-10-user.txt
    

    Commit: NO (groups with Wave 2)

  • 11. perSystem apps module

    What to do:

    • Create modules/apps.nix — a flake-parts module that defines perSystem apps
    • Port the current perSystem block from flake.nix that creates apps: build, apply, build-switch, rollback
    • These apps reference apps/${system}/${name} shell scripts — keep this mechanism
    • Ensure apps are available for both x86_64-linux and aarch64-darwin

    Must NOT do:

    • Do not change app behavior (the actual script rewrite to Nushell is Task 29)

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 7-10)
    • Blocks: Task 27 (verification)
    • Blocked By: Task 1

    References:

    • flake.nix:143-165 — Current perSystem apps block
    • apps/ directory — Shell scripts for each platform

    Acceptance Criteria:

    • nix eval ".#apps.aarch64-darwin" --json | nu -c '$in | from json | columns' includes build, apply
    • nix eval ".#apps.x86_64-linux" --json | nu -c '$in | from json | columns' includes build, apply

    QA Scenarios:

    Scenario: perSystem apps accessible
      Tool: Bash
      Steps:
        1. Run `nix eval ".#apps.aarch64-darwin" --json 2>&1 | nu -c '$in | from json | columns | sort'`
        2. Assert contains apply, build, build-switch, rollback
      Expected Result: All 4 apps registered
      Evidence: .sisyphus/evidence/task-11-apps.txt
    

    Commit: NO (groups with Wave 2)

  • 12. Shell aspects (nushell, bash, zsh, starship)

    What to do:

    • Create modules/shell.nix — den aspect grouping shell-related HM config
    • Port into den.aspects.shell.homeManager: nushell config (profiles/nushell.nix — programs.nushell with aliases, env, config, platform-conditional PATH), bash config (profiles/bash.nix), zsh config (profiles/zsh.nix), starship config (profiles/starship.nix — programs.starship with settings)
    • Keep stdenv.isDarwin platform checks in HM config — these are correct in HM context
    • Include shell aspect in user aspect (Task 10's den.aspects.cschmatzler.includes)

    Must NOT do:

    • Do not change shell aliases, env vars, or config values
    • Do not split into 4 separate aspects (these are naturally grouped)

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 13-19)
    • Blocks: Tasks 20-25
    • Blocked By: Tasks 7, 10

    References:

    • profiles/nushell.nix — Nushell config with platform conditionals
    • profiles/bash.nix — Bash config
    • profiles/zsh.nix — Zsh config
    • profiles/starship.nix — Starship prompt config

    Acceptance Criteria:

    • All shell programs configured in HM
    • Platform-conditional nushell PATH preserved

    QA Scenarios:

    Scenario: Shell programs enabled in HM
      Tool: Bash
      Steps:
        1. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.nushell.enable" 2>&1`
        2. Assert true
      Expected Result: Nushell enabled
      Evidence: .sisyphus/evidence/task-12-shell.txt
    

    Commit: NO (groups with Wave 3)

  • 13. Dev tools aspects (git, jujutsu, lazygit, jjui, direnv, mise)

    What to do:

    • Create modules/dev-tools.nix — den aspect grouping developer tools HM config
    • Port into den.aspects.dev-tools.homeManager: git config (profiles/git.nix — programs.git with aliases, settings, ignores, diff tools), jujutsu config (profiles/jujutsu.nix), lazygit (profiles/lazygit.nix), jjui (profiles/jjui.nix), direnv (profiles/direnv.nix), mise (profiles/mise.nix)
    • NOTE: Do NOT set programs.git.settings.user.email here — that's per-host
    • Include dev-tools aspect in user aspect

    Must NOT do:

    • Do not change git aliases or settings
    • Do not set git email (per-host override)

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 12, 14-19)
    • Blocks: Tasks 20-25
    • Blocked By: Tasks 7, 10

    References:

    • profiles/git.nix — Git config with delta, aliases, ignores
    • profiles/jujutsu.nix — Jujutsu VCS config
    • profiles/lazygit.nix — Lazygit TUI
    • profiles/jjui.nix — jj TUI
    • profiles/direnv.nix — Direnv with nix-direnv
    • profiles/mise.nix — Mise version manager

    Acceptance Criteria:

    • All dev tools configured
    • Git email NOT set in this aspect

    QA Scenarios:

    Scenario: Git configured without email override
      Tool: Bash
      Steps:
        1. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.git.enable" 2>&1`
        2. Assert true
      Expected Result: Git enabled in HM
      Evidence: .sisyphus/evidence/task-13-devtools.txt
    

    Commit: NO (groups with Wave 3)

  • 14. Editor aspects (neovim/nixvim)

    What to do:

    • Create modules/neovim/ directory with den aspect for neovim/nixvim
    • Port the entire profiles/neovim/ directory (16+ files) into den aspect structure
    • The main module (profiles/neovim/default.nix) imports all plugin configs — replicate this structure
    • Import inputs.nixvim.homeModules.nixvim in the HM class config
    • Use flake-file to declare nixvim input dependency from this module
    • The neovim sub-files can be imported via the module's own imports (NOT via import-tree — they're HM modules, not flake-parts modules). Place them under modules/neovim/_plugins/ or similar to avoid import-tree scanning them, OR use den aspect's homeManager.imports

    Must NOT do:

    • Do not change any neovim plugin configurations
    • Do not restructure plugin files unnecessarily

    Recommended Agent Profile:

    • Category: unspecified-high
      • Reason: neovim config is 16+ files with complex imports — needs careful migration of the import chain
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 12-13, 15-19)
    • Blocks: Tasks 20-23
    • Blocked By: Tasks 7, 10

    References:

    • profiles/neovim/default.nix — Main neovim module (imports all plugins)
    • profiles/neovim/ — 16+ plugin config files
    • nixvim input: inputs.nixvim.homeModules.nixvim

    Acceptance Criteria:

    • All neovim plugins configured
    • nixvim HM module imported

    QA Scenarios:

    Scenario: Nixvim enabled in home-manager
      Tool: Bash
      Steps:
        1. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.nixvim.enable" 2>&1`
        2. Assert true (or check that nixvim module is imported)
      Expected Result: Nixvim working
      Evidence: .sisyphus/evidence/task-14-neovim.txt
    

    Commit: NO (groups with Wave 3)

  • 15. Terminal aspects (ghostty, zellij, yazi, bat, fzf, ripgrep, zoxide)

    What to do:

    • Create modules/terminal.nix — den aspect for terminal tools HM config
    • Port: ghostty (profiles/ghostty.nix), bat (profiles/bat.nix), fzf (profiles/fzf.nix), ripgrep (profiles/ripgrep.nix), zoxide (profiles/zoxide.nix), yazi (profiles/yazi.nix)
    • Create modules/zellij.nix — separate aspect for zellij because it needs per-host behavior
    • Port profiles/zellij.nix but REMOVE the osConfig.networking.hostName == "tahani" check
    • Instead, the tahani-specific zellij auto-start behavior goes in Task 21 (tahani host aspect)

    Must NOT do:

    • Do not use hostname string comparisons
    • Do not change tool configurations

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 12-14, 16-19)
    • Blocks: Tasks 20-25
    • Blocked By: Tasks 7, 10

    References:

    • profiles/zellij.nix — Zellij with host-specific autostart check (line 20: osConfig.networking.hostName == "tahani")
    • profiles/ghostty.nix, profiles/bat.nix, profiles/fzf.nix, profiles/ripgrep.nix, profiles/zoxide.nix, profiles/yazi.nix

    Acceptance Criteria:

    • All terminal tools configured
    • No hostname string comparisons in shared aspects
    • Zellij base config without host-specific auto-start

    QA Scenarios:

    Scenario: Terminal tools enabled
      Tool: Bash
      Steps:
        1. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.bat.enable" 2>&1`
        2. Assert true
      Expected Result: bat enabled in HM
      Evidence: .sisyphus/evidence/task-15-terminal.txt
    

    Commit: NO (groups with Wave 3)

  • 16. Communication aspects (himalaya, mbsync, ssh)

    What to do:

    • Create modules/email.nix — den aspect for email (himalaya + mbsync)
    • Port profiles/himalaya.nix — the himalaya HM module that creates a wrapper script (writeShellApplication around himalaya binary with IMAP password from sops)
    • Port profiles/mbsync.nix — mbsync HM config
    • CRITICAL: The himalaya wrapper package is currently accessed from NixOS scope in tahani (cross-module dependency). In den, instead create the wrapper within the homeManager class AND also make it available to the NixOS nixos class via the overlay package (himalaya overlay already exists). The inbox-triage systemd service in Task 21 should use pkgs.himalaya (from overlay) directly, not reach into HM config.
    • Create modules/ssh-client.nix — den aspect for SSH client config
    • Port profiles/ssh.nix — SSH client HM config with platform-conditional paths

    Must NOT do:

    • Do not create cross-module dependencies (HM → NixOS reads)
    • Do not change himalaya or mbsync settings

    Recommended Agent Profile:

    • Category: unspecified-high
      • Reason: Himalaya cross-dependency redesign is architecturally critical
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 12-15, 17-19)
    • Blocks: Tasks 20, 21
    • Blocked By: Tasks 7, 10

    References:

    • profiles/himalaya.nix — Himalaya HM module with writeShellApplication wrapper
    • profiles/mbsync.nix — mbsync config
    • profiles/ssh.nix — SSH client config with platform paths
    • hosts/tahani/default.nix:9 — The cross-module dependency to eliminate: himalaya = config.home-manager.users.${user}.programs.himalaya.package
    • overlays/himalaya.nix:1-3 — Himalaya overlay (packages from input)

    Acceptance Criteria:

    • Himalaya configured in HM
    • No cross-module HM→NixOS reads
    • SSH client configured with correct platform paths

    QA Scenarios:

    Scenario: Himalaya configured without cross-module dependency
      Tool: Bash
      Steps:
        1. Grep new modules/ for "config.home-manager.users"
        2. Assert zero matches (no HM config reads from NixOS scope)
      Expected Result: No cross-module dependency
      Evidence: .sisyphus/evidence/task-16-email.txt
    

    Commit: NO (groups with Wave 3)

  • 17. Desktop aspects (aerospace, home base)

    What to do:

    • Create modules/desktop.nix — den aspect for desktop/GUI HM config
    • Port profiles/aerospace.nix — AeroSpace tiling WM config (darwin-only HM module)
    • The wallpaper activation from profiles/home.nix is already in _lib/wallpaper.nix — wire it via the user aspect's darwin HM config
    • Include fonts.fontconfig.enable for darwin hosts (currently in chidi/jason host configs)

    Must NOT do:

    • Do not change aerospace settings

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 12-16, 18-19)
    • Blocks: Tasks 22, 23
    • Blocked By: Tasks 7, 8, 10

    References:

    • profiles/aerospace.nix — AeroSpace WM config
    • hosts/chidi/default.nix:50fonts.fontconfig.enable = true

    Acceptance Criteria:

    • AeroSpace configured in darwin HM
    • fonts.fontconfig.enable set for darwin hosts

    QA Scenarios:

    Scenario: AeroSpace configured for darwin hosts
      Tool: Bash
      Steps:
        1. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.aerospace.enable" 2>&1` (or check relevant config)
      Expected Result: AeroSpace enabled
      Evidence: .sisyphus/evidence/task-17-desktop.txt
    

    Commit: NO (groups with Wave 3)

  • 18. AI tools aspects (opencode, claude-code)

    What to do:

    • Create modules/ai-tools.nix — den aspect for AI coding tools
    • Port profiles/opencode.nix — references inputs.llm-agents.packages...opencode
    • Port profiles/claude-code.nix — references inputs.llm-agents.packages...claude-code
    • These need inputs access — use den's inputs' battery or access inputs from flake-parts module args
    • Use flake-file to declare llm-agents input dependency
    • NOTE: The opencode config may include profiles/opencode/ directory content

    Must NOT do:

    • Do not change opencode or claude-code settings

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 12-17, 19)
    • Blocks: Task 21 (tahani uses opencode in systemd service)
    • Blocked By: Tasks 7, 10

    References:

    • profiles/opencode.nix — OpenCode HM config using inputs.llm-agents
    • profiles/claude-code.nix — Claude Code HM config using inputs.llm-agents
    • profiles/opencode/ — OpenCode config directory (if exists)

    Acceptance Criteria:

    • Both AI tools configured in HM
    • inputs.llm-agents accessed correctly (not via specialArgs)

    QA Scenarios:

    Scenario: AI tools packages available
      Tool: Bash
      Steps:
        1. Verify opencode and claude-code are in home packages (nix eval)
      Expected Result: Both tools in user's home packages
      Evidence: .sisyphus/evidence/task-18-ai.txt
    

    Commit: NO (groups with Wave 3)

  • 19. Miscellaneous aspects (atuin, zk)

    What to do:

    • Create modules/atuin.nix — den aspect for atuin (shell history sync)
    • Port profiles/atuin.nix into den.aspects.atuin.homeManager
    • Create modules/zk.nix — den aspect for zk (zettelkasten)
    • Port profiles/zk.nix into den.aspects.zk.homeManager
    • Include both in user aspect

    Must NOT do:

    • Do not change settings

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 12-18)
    • Blocks: Tasks 20-25
    • Blocked By: Tasks 7, 10

    References:

    • profiles/atuin.nix — Atuin config
    • profiles/zk.nix — Zk zettelkasten config

    Acceptance Criteria:

    • Atuin and zk configured in HM

    QA Scenarios:

    Scenario: Atuin enabled
      Tool: Bash
      Steps:
        1. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.atuin.enable" 2>&1`
        2. Assert true
      Expected Result: Atuin enabled
      Evidence: .sisyphus/evidence/task-19-misc.txt
    

    Commit: NO (groups with Wave 3)

  • 20. Server aspects — michael (gitea + litestream + restic)

    What to do:

    • Create modules/michael.nix — den aspect for michael host
    • Port michael-specific config into den.aspects.michael:
      • nixos class: import disko module (inputs.disko.nixosModules.disko), import disk-config.nix and hardware-configuration.nix (place originals under modules/_hosts/michael/ with _ prefix to avoid import-tree)
      • nixos class: gitea service config — absorb the entire modules/gitea.nix custom module into this aspect. The my.gitea options become direct service configuration (services.gitea, services.litestream, services.restic). Use SOPS secrets from Task 5.
      • nixos class: modulesPath imports (installer/scan/not-detected.nix, profiles/qemu-guest.nix)
      • nixos class: networking.hostName = "michael"
      • Include: nixos-system, core, openssh, fail2ban, tailscale aspects
    • HM: minimal imports — only nushell, home base, ssh, nixvim
    • Git email override: homeManager.programs.git.settings.user.email is NOT set (michael has no email override in current config)
    • Wire disko input via flake-file

    Must NOT do:

    • Do not change gitea service configuration values
    • Do not change disk-config or hardware-configuration
    • Do not create abstractions over gitea's backup system

    Recommended Agent Profile:

    • Category: deep
      • Reason: Complex server with custom gitea module absorption, disko, hardware config, and multiple service interactions
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 4 (with Tasks 21-25)
    • Blocks: Task 27
    • Blocked By: Tasks 5, 9, 14, 16

    References:

    • hosts/michael/default.nix:1-48 — Full michael host config
    • hosts/michael/disk-config.nix — Disko partition config
    • hosts/michael/hardware-configuration.nix — Hardware config
    • modules/gitea.nix — Custom my.gitea module (litestream, restic, s3) to absorb
    • hosts/michael/secrets.nix:1-22 — SOPS secrets (already in Task 5)

    Acceptance Criteria:

    • nix build ".#nixosConfigurations.michael.config.system.build.toplevel" succeeds
    • Gitea service configured with litestream and restic
    • Disko disk-config preserved

    QA Scenarios:

    Scenario: Michael host builds successfully
      Tool: Bash
      Steps:
        1. Run `nix build ".#nixosConfigurations.michael.config.system.build.toplevel" --dry-run 2>&1`
        2. Assert exit code 0
      Expected Result: Michael builds without errors
      Evidence: .sisyphus/evidence/task-20-michael.txt
    
    Scenario: Gitea service configured
      Tool: Bash
      Steps:
        1. Run `nix eval ".#nixosConfigurations.michael.config.services.gitea.enable" 2>&1`
        2. Assert true
      Expected Result: Gitea enabled
      Evidence: .sisyphus/evidence/task-20-gitea.txt
    

    Commit: NO (groups with Wave 4)

  • 21. Server aspects — tahani (adguard, paperless, docker, inbox-triage)

    What to do:

    • Create modules/tahani.nix — den aspect for tahani host
    • Port tahani-specific config into den.aspects.tahani:
      • nixos class: import tahani-specific files (adguardhome.nix, cache.nix, networking.nix, paperless.nix — place under modules/_hosts/tahani/)
      • nixos class: networking.hostName = "tahani", virtualisation.docker.enable, docker group for user
      • nixos class: swap device config
      • nixos class: Inbox-triage systemd service — REDESIGNED to avoid cross-module dependency:
        • Use pkgs.himalaya (from overlay, NOT from HM config) for the himalaya binary
        • Use inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.opencode for opencode binary
        • Define systemd service and timer exactly as current
      • Include: nixos-system, core, openssh, tailscale aspects
    • HM: all the profiles that tahani currently imports (most shared aspects + himalaya, mbsync)
    • HM: git email override: homeManager.programs.git.settings.user.email = "christoph@schmatzler.com"
    • HM: zellij auto-start override (the tahani-specific behavior from zellij.nix)

    Must NOT do:

    • Do not read HM config from NixOS scope (no config.home-manager.users...)
    • Do not change service configs (adguard, paperless, docker)

    Recommended Agent Profile:

    • Category: deep
      • Reason: Most complex host — has the himalaya cross-dependency redesign, multiple services, and the most HM profiles
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 4 (with Tasks 20, 22-25)
    • Blocks: Task 27
    • Blocked By: Tasks 5, 9, 16, 18

    References:

    • hosts/tahani/default.nix:1-94 — Full tahani config with cross-module dependency
    • hosts/tahani/adguardhome.nix — AdGuard Home service config
    • hosts/tahani/cache.nix — Cache config
    • hosts/tahani/networking.nix — Network config
    • hosts/tahani/paperless.nix — Paperless-NGX service config
    • profiles/zellij.nix:20 — The hostname check to replace with per-host override
    • overlays/himalaya.nix — Himalaya available as pkgs.himalaya via overlay

    Acceptance Criteria:

    • nix build ".#nixosConfigurations.tahani.config.system.build.toplevel" succeeds
    • Inbox-triage systemd service uses pkgs.himalaya (overlay), NOT HM config read
    • Zero instances of config.home-manager.users in any module
    • Zellij auto-start is tahani-specific (not hostname comparison)

    QA Scenarios:

    Scenario: Tahani builds without cross-module dependency
      Tool: Bash
      Steps:
        1. Run `nix build ".#nixosConfigurations.tahani.config.system.build.toplevel" --dry-run 2>&1`
        2. Assert exit code 0
        3. Grep modules/ for "config.home-manager.users"
        4. Assert zero matches
      Expected Result: Tahani builds, no HM→NixOS cross-reads
      Evidence: .sisyphus/evidence/task-21-tahani.txt
    
    Scenario: Docker enabled on tahani
      Tool: Bash
      Steps:
        1. Run `nix eval ".#nixosConfigurations.tahani.config.virtualisation.docker.enable" 2>&1`
        2. Assert true
      Expected Result: Docker enabled
      Evidence: .sisyphus/evidence/task-21-docker.txt
    

    Commit: NO (groups with Wave 4)

  • 22. Host aspects — chidi (work-specific)

    What to do:

    • Create modules/chidi.nix — den aspect for chidi (work laptop)
    • Define den.aspects.chidi:
      • Include: darwin-system, core, tailscale, all shared user aspects
      • darwin class: environment.systemPackages = [pkgs.slack] (work-specific)
      • darwin class: networking.hostName = "chidi", networking.computerName = "chidi"
      • homeManager class: programs.git.settings.user.email = "christoph@tuist.dev" (work email)
      • homeManager class: fonts.fontconfig.enable = true

    Must NOT do:

    • Do not add work-specific packages beyond Slack
    • Do not change git email

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 4 (with Tasks 20-21, 23-25)
    • Blocks: Task 27
    • Blocked By: Tasks 5, 8, 17

    References:

    • hosts/chidi/default.nix:1-57 — Current chidi config
    • hosts/chidi/secrets.nix:1-5 — Already handled in Task 5

    Acceptance Criteria:

    • nix build ".#darwinConfigurations.chidi.system" succeeds
    • Slack in systemPackages
    • Git email = "christoph@tuist.dev"

    QA Scenarios:

    Scenario: Chidi builds with work email
      Tool: Bash
      Steps:
        1. Run `nix build ".#darwinConfigurations.chidi.system" --dry-run 2>&1`
        2. Assert exit code 0
        3. Run `nix eval ".#darwinConfigurations.chidi.config.home-manager.users.cschmatzler.programs.git.settings.user.email" 2>&1`
        4. Assert output is "christoph@tuist.dev"
      Expected Result: Chidi builds, work email set
      Evidence: .sisyphus/evidence/task-22-chidi.txt
    

    Commit: NO (groups with Wave 4)

  • 23. Host aspects — jason (personal-specific)

    What to do:

    • Create modules/jason.nix — den aspect for jason (personal laptop)
    • Define den.aspects.jason:
      • Include: darwin-system, core, tailscale, all shared user aspects
      • darwin class: networking.hostName = "jason", networking.computerName = "jason"
      • homeManager class: programs.git.settings.user.email = "christoph@schmatzler.com" (personal email)
      • homeManager class: fonts.fontconfig.enable = true

    Must NOT do:

    • Do not add packages not in current jason config

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 4 (with Tasks 20-22, 24-25)
    • Blocks: Task 27
    • Blocked By: Tasks 5, 8, 17

    References:

    • hosts/jason/default.nix:1-52 — Current jason config

    Acceptance Criteria:

    QA Scenarios:

    Scenario: Jason builds with personal email
      Tool: Bash
      Steps:
        1. Run `nix build ".#darwinConfigurations.jason.system" --dry-run 2>&1`
        2. Assert exit code 0
      Expected Result: Jason builds successfully
      Evidence: .sisyphus/evidence/task-23-jason.txt
    

    Commit: NO (groups with Wave 4)

  • 24. Network aspects (openssh, fail2ban, tailscale, postgresql)

    What to do:

    • Create modules/network.nix — den aspect for network services
    • Port profiles/openssh.nixden.aspects.openssh.nixos (SSH server config)
    • Port profiles/fail2ban.nixden.aspects.fail2ban.nixos
    • Port profiles/tailscale.nixden.aspects.tailscale with per-class configs (nixos + darwin support, uses lib.optionalAttrs pkgs.stdenv.isLinux currently — convert to per-class)
    • Port profiles/postgresql.nixden.aspects.postgresql.nixos (if used by any host)
    • Include these in appropriate host aspects

    Must NOT do:

    • Do not change SSH, fail2ban, or tailscale settings

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 4 (with Tasks 20-23, 25)
    • Blocks: Task 27
    • Blocked By: Task 9

    References:

    • profiles/openssh.nix — OpenSSH server config
    • profiles/fail2ban.nix — Fail2ban config
    • profiles/tailscale.nix — Tailscale with platform conditionals
    • profiles/postgresql.nix — PostgreSQL config

    Acceptance Criteria:

    • All network services configured
    • Tailscale works on both darwin and nixos

    QA Scenarios:

    Scenario: Tailscale enabled on all hosts
      Tool: Bash
      Steps:
        1. Run `nix eval ".#nixosConfigurations.michael.config.services.tailscale.enable" 2>&1`
        2. Assert true
      Expected Result: Tailscale enabled
      Evidence: .sisyphus/evidence/task-24-network.txt
    

    Commit: NO (groups with Wave 4)

  • 25. Packages aspect (system packages list)

    What to do:

    • Ensure the home.packages list from _lib/packages.nix is wired into the user aspect
    • The callPackage pattern for packages.nix should be replicated — import _lib/packages.nix and pass required args
    • Ensure platform-conditional packages (lib.optionals stdenv.isDarwin/isLinux) are preserved
    • Remove colmena from the package list, add deploy-rs CLI if needed

    Must NOT do:

    • Do not add packages not in current packages.nix (except deploy-rs CLI)
    • Do not remove colmena replacement from packages without confirming

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 4 (with Tasks 20-24)
    • Blocks: Task 27
    • Blocked By: Task 7

    References:

    • profiles/packages.nix — Full package list (moved to _lib/packages.nix in Task 3)
    • profiles/home.nix:13home.packages = pkgs.callPackage ./packages.nix {inherit inputs;};

    Acceptance Criteria:

    • All packages from current list present
    • Platform-conditional packages preserved

    QA Scenarios:

    Scenario: Home packages include expected tools
      Tool: Bash
      Steps:
        1. Verify packages.nix is loaded and packages are in home.packages
      Expected Result: Packages available
      Evidence: .sisyphus/evidence/task-25-packages.txt
    

    Commit: NO (groups with Wave 4)

  • 26. Remove old structure

    What to do:

    • Delete hosts/ directory entirely
    • Delete profiles/ directory entirely
    • Delete modules/_legacy/ directory (old NixOS modules moved here by Task 1, now fully absorbed into den aspects)
    • Delete overlays/ directory entirely (now in modules/overlays.nix)
    • Delete lib/ directory entirely (now in modules/_lib/)
    • Keep secrets/ directory (encrypted files, unchanged)
    • Keep apps/ directory (rewritten to Nushell by Task 29)
    • Keep .sops.yaml (unchanged)
    • Verify no broken references remain

    Must NOT do:

    • Do not delete secrets/, apps/, .sops.yaml, or alejandra.toml

    Recommended Agent Profile:

    • Category: quick
    • Skills: [git-master]

    Parallelization:

    • Can Run In Parallel: NO
    • Parallel Group: Wave 5 (after Task 27 confirms all builds pass)
    • Blocks: F1-F4
    • Blocked By: Task 27

    References:

    • All old directories to remove: hosts/, profiles/, overlays/, lib/

    Acceptance Criteria:

    • Old directories removed
    • nix flake check still passes after removal
    • No broken file references

    QA Scenarios:

    Scenario: Old structure removed, builds still pass
      Tool: Bash
      Steps:
        1. Verify hosts/ directory doesn't exist
        2. Verify profiles/ directory doesn't exist
        3. Run `nix flake check --no-build 2>&1`
        4. Assert exit code 0
      Expected Result: Clean structure, still evaluates
      Evidence: .sisyphus/evidence/task-26-cleanup.txt
    

    Commit: YES

    • Message: chore: remove old host-centric structure
    • Files: deleted directories
    • Pre-commit: nix flake check --no-build && alejandra --check .
  • 27. Full build verification all 4 hosts

    What to do:

    • Build ALL 4 host configurations (or dry-run if cross-platform):
      • nix build ".#darwinConfigurations.chidi.system" --dry-run
      • nix build ".#darwinConfigurations.jason.system" --dry-run
      • nix build ".#nixosConfigurations.michael.config.system.build.toplevel" --dry-run
      • nix build ".#nixosConfigurations.tahani.config.system.build.toplevel" --dry-run
    • Run nix flake check
    • Run alejandra --check .
    • Verify deploy-rs nodes: nix eval ".#deploy.nodes" --json
    • Verify zero specialArgs: grep for specialArgs in all .nix files
    • Verify no hostname comparisons in shared aspects
    • Verify no HM→NixOS cross-reads

    Must NOT do:

    • Do not skip any host build

    Recommended Agent Profile:

    • Category: deep
      • Reason: Comprehensive verification requiring multiple build commands and assertions
    • Skills: []

    Parallelization:

    • Can Run In Parallel: NO
    • Parallel Group: Wave 5 (after all implementation)
    • Blocks: Tasks 26, 28, F1-F4
    • Blocked By: Tasks 20-25

    References:

    • Definition of Done section of this plan
    • All acceptance criteria from all tasks

    Acceptance Criteria:

    • All 4 hosts build (or dry-run) successfully
    • nix flake check passes
    • alejandra --check . passes
    • deploy-rs nodes exist for michael and tahani
    • Zero specialArgs usage in codebase
    • Zero hostname string comparisons in shared modules

    QA Scenarios:

    Scenario: Complete build verification
      Tool: Bash
      Steps:
        1. Build all 4 hosts (dry-run)
        2. Run nix flake check
        3. Run alejandra --check .
        4. Eval deploy-rs nodes
        5. Grep for specialArgs — assert 0 matches
        6. Grep for "networking.hostName ==" in shared modules — assert 0 matches
      Expected Result: All checks pass
      Evidence: .sisyphus/evidence/task-27-verification.txt
    

    Commit: NO

  • 28. Formatting pass + final cleanup

    What to do:

    • Run alejandra . to format all files
    • Remove any TODO comments or placeholder code
    • Verify modules/ directory structure is clean
    • Ensure .sops.yaml still references correct secret file paths
    • Final nix flake check

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: NO
    • Parallel Group: Wave 5 (after Task 27)
    • Blocks: F1-F4
    • Blocked By: Task 27

    References:

    • alejandra.toml — Formatter config (tabs for indentation)

    Acceptance Criteria:

    • alejandra --check . passes
    • No TODO/FIXME/HACK comments

    QA Scenarios:

    Scenario: All files formatted
      Tool: Bash
      Steps:
        1. Run `alejandra --check . 2>&1`
        2. Assert exit code 0
      Expected Result: All files pass formatter
      Evidence: .sisyphus/evidence/task-28-format.txt
    

    Commit: YES

    • Message: feat: rewrite config with den framework
    • Files: all modules/*, flake.nix, flake.lock
    • Pre-commit: nix flake check && alejandra --check .
  • 29. Rewrite apps/ scripts from bash to Nushell

    What to do:

    • Rewrite apps/common.shapps/common.nu — convert colored output helper functions (print_info, print_success, print_error, print_warning) to Nushell using ansi commands
    • Rewrite all 4 darwin scripts (apps/aarch64-darwin/{build,apply,build-switch,rollback}) from bash to Nushell:
      • build: hostname detection via scutil --get LocalHostName (fallback hostname -s), nix build darwin config, unlink result
      • apply: hostname detection, nix run nix-darwin -- switch
      • build-switch: hostname detection, nix build then sudo darwin-rebuild switch, unlink result
      • rollback: list generations via darwin-rebuild --list-generations, prompt for generation number via input, switch to it
    • Rewrite all 4 linux scripts (apps/x86_64-linux/{build,apply,build-switch,rollback}) from bash to Nushell:
      • build: hostname via hostname, nix build nixos config, unlink result
      • apply: hostname, sudo-aware nixos-rebuild switch
      • build-switch: hostname, nix build then sudo-aware nixos-rebuild switch
      • rollback: list generations via sudo-aware nix-env --profile ... --list-generations, prompt for number via input, sudo-aware switch-generation + switch-to-configuration
    • All scripts get #!/usr/bin/env nu shebang
    • Delete apps/common.sh after apps/common.nu is created
    • Use use ../common.nu * (or equivalent) to import shared helpers in each script
    • Preserve exact same behavior — same commands, same output messages, same error handling
    • Handle sudo checks in linux scripts: use (id -u) or $env.EUID equivalent in Nushell

    Must NOT do:

    • Do not change any nix build/switch/rebuild commands — only the shell scripting around them changes
    • Do not add new functionality beyond what exists in the bash scripts
    • Do not change the file names (the perSystem apps module references them by path)

    Recommended Agent Profile:

    • Category: quick
      • Reason: Straightforward 1:1 rewrite of 9 small scripts (~186 lines total) from bash to Nushell. No architectural decisions needed.
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 4 (with Tasks 20-25)
    • Blocks: Task 27 (build verification should confirm apps still work)
    • Blocked By: Task 11 (perSystem apps module must be in place first)

    References:

    Pattern References:

    • apps/common.sh — Current colored output helpers (23 lines) — rewrite to Nushell ansi equivalents
    • apps/aarch64-darwin/build — Darwin build script (16 lines) — template for all darwin scripts
    • apps/x86_64-linux/build — Linux build script (16 lines) — template for all linux scripts
    • apps/x86_64-linux/rollback — Most complex script (30 lines, sudo checks, interactive input) — key test case

    API/Type References:

    • modules/apps.nix (created by Task 11) — perSystem apps module that references these scripts by path

    External References:

    • Nushell documentation: https://www.nushell.sh/book/ — Language reference for bash→nu translation
    • Nushell ansi command: for colored output (replaces ANSI escape codes)
    • Nushell input command: for interactive prompts (replaces bash read -r)

    Acceptance Criteria:

    • All 9 scripts rewritten with #!/usr/bin/env nu shebang
    • apps/common.nu exists with colored output helpers
    • apps/common.sh deleted
    • nix run ".#build" -- --help 2>&1 doesn't error (script is parseable by nu)
    • nix run ".#apply" -- --help 2>&1 doesn't error
    • No bash files remain in apps/ directory

    QA Scenarios:

    Scenario: All apps/ scripts are valid Nushell
      Tool: Bash
      Preconditions: Task 11 (perSystem apps module) complete, all scripts rewritten
      Steps:
        1. Run `find apps/ -type f -name "*.sh" 2>&1` — assert no .sh files remain
        2. Run `head -1 apps/aarch64-darwin/build` — assert contains "#!/usr/bin/env nu"
        3. Run `head -1 apps/x86_64-linux/build` — assert contains "#!/usr/bin/env nu"
        4. Run `nu -c 'source apps/common.nu; print_info "test"' 2>&1` — assert exit code 0 and output contains "[INFO]"
        5. Run `nu -c 'source apps/common.nu; print_error "test"' 2>&1` — assert exit code 0 and output contains "[ERROR]"
      Expected Result: All scripts are Nushell, common.nu functions work
      Failure Indicators: Any .sh file found, shebang mismatch, nu parse errors
      Evidence: .sisyphus/evidence/task-29-nushell-scripts.txt
    
    Scenario: perSystem apps still reference correct scripts
      Tool: Bash
      Preconditions: Apps module (Task 11) and scripts (Task 29) both complete
      Steps:
        1. Run `nix eval ".#apps.x86_64-linux" --json 2>&1 | nu -c '$in | from json | columns | sort'`
        2. Assert output contains: apply, build, build-switch, rollback
        3. Run `nix eval ".#apps.aarch64-darwin" --json 2>&1 | nu -c '$in | from json | columns | sort'`
        4. Assert output contains: apply, build, build-switch, rollback
      Expected Result: All 4 apps registered on both platforms
      Failure Indicators: Missing app entries, nix eval errors
      Evidence: .sisyphus/evidence/task-29-apps-registered.txt
    

    Commit: YES

    • Message: refactor: rewrite app scripts from bash to nushell
    • Files: apps/common.nu, apps/aarch64-darwin/*, apps/x86_64-linux/*
    • Pre-commit: nu -c 'source apps/common.nu'

Final Verification Wave (MANDATORY — after ALL implementation tasks)

4 review agents run in PARALLEL. ALL must APPROVE. Rejection → fix → re-run.

  • F1. Plan Compliance Auditoracle Read the plan end-to-end. For each "Must Have": verify implementation exists (nix eval, read file). For each "Must NOT Have": search codebase for forbidden patterns (specialArgs, hostname comparisons, HM→NixOS cross-reads). Check all 4 hosts build. Compare deliverables against plan. Output: Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT

  • F2. Code Quality Reviewunspecified-high Run alejandra --check . + nix flake check. Review all new modules for: dead code, unused imports, inconsistent patterns, hardcoded values that should be options. Check den API usage is idiomatic. Verify import-tree conventions (no function files in import paths). Output: Format [PASS/FAIL] | Check [PASS/FAIL] | Files [N clean/N issues] | VERDICT

  • F3. Real Manual QAunspecified-high Start from clean state. Build all 4 hosts. Verify deploy-rs nodes exist via nix eval. Verify SOPS secrets preserved via nix eval. Verify git email overrides. Verify overlays produce correct packages. Verify no specialArgs usage anywhere. Save evidence to .sisyphus/evidence/final-qa/. Output: Builds [4/4 pass] | Deploy [2/2] | Secrets [N/N] | VERDICT

  • F4. Scope Fidelity Checkdeep For each task: read "What to do", verify actual implementation matches 1:1. Check "Must NOT Have" compliance — no new packages, no new services, no abstractions. Verify every current profile has a corresponding den aspect. Flag any behavioral differences. Output: Tasks [N/N compliant] | Scope [CLEAN/N issues] | VERDICT


Commit Strategy

Since this is a clean rewrite, the work should be committed as a small series of logical commits:

  • 1: feat: rewrite config with den framework — All new modules, flake.nix
  • 2: refactor: rewrite app scripts from bash to nushell — apps/ directory
  • 3: chore: remove old host-centric structure — Delete hosts/, profiles/, modules/_legacy/
  • Pre-commit: alejandra --check . && nix flake check

Success Criteria

Verification Commands

nix build ".#darwinConfigurations.chidi.system"         # Expected: builds successfully
nix build ".#darwinConfigurations.jason.system"          # Expected: builds successfully
nix build ".#nixosConfigurations.michael.config.system.build.toplevel"  # Expected: builds successfully
nix build ".#nixosConfigurations.tahani.config.system.build.toplevel"   # Expected: builds successfully
nix flake check                                          # Expected: passes
alejandra --check .                                      # Expected: passes
nix eval ".#deploy.nodes.michael" --json                 # Expected: valid deploy-rs node
nix eval ".#deploy.nodes.tahani" --json                  # Expected: valid deploy-rs node

Final Checklist

  • All "Must Have" present
  • All "Must NOT Have" absent
  • All 4 hosts build
  • Zero specialArgs usage
  • SOPS paths identical to current
  • deploy-rs configured for NixOS hosts
  • All overlays functional
  • Formatting passes