Skip to content

feat(treefmt): publish the check of the default configuration, and only that configuration - #108

Closed
clhodapp wants to merge 14 commits into
fix/system-manager-pkgsfrom
feat/treefmt-check
Closed

clhodapp wants to merge 14 commits into
fix/system-manager-pkgsfrom
feat/treefmt-check

Conversation

@clhodapp

Copy link
Copy Markdown
Contributor

A tree with a treefmt configuration named default now publishes two outputs:

Output Value
formatter.<system> its wrapper, which nix fmt runs (unchanged)
checks.<system>.treefmt its check over the root of the tree, root on mkLib, which nix flake check builds

A tree without a root publishes no check. A library composed inside a test is one example.

Second configurations no longer refused. Every treefmt configuration used to become an entry for formatter, which holds a single value. So a second configuration, under any other name, was refused when the top published, with "this output holds a single value". Now only default is published. A configuration under another name is evaluated and passed up, and is reachable only through the manifest.

exportsTo changes. exportsTo may now be a list of exports, for an integration that publishes in more than one output. An export may carry only, a function of the manifest that says whether the configuration is published in that output. exportsOf replaces exportsToOf in the integrations library.

Testing

  • Pinned-world test. treefmtIsTheFormatterTreefmtNixBuilds now declares a second configuration, docs. It checks four things:

    • formatter still holds the wrapper mkWrapper builds;
    • with a root, checks.x86_64-linux holds only treefmt, and it is the configuration's check over that root;
    • without a root, there is no checks output;
    • docs is evaluated, and only the manifest holds it.

    With the previous integration the suite fails, because formatter.x86_64-linux refuses treefmt docs.

  • Built for real from this branch, the published check succeeds on a formatted tree and fails on an unformatted one, printing the diff for flake.nix.

  • nix flake check passes, including the eval-weight gate, and nix flake check --no-build passes in all 18 consumers. The exportsTo change touches how every integration publishes.

Docs: nix-caisson/nix-caisson.github.io, branch docs/treefmt-check.

🤖 Generated with Claude Code

clhodapp and others added 14 commits October 17, 2026 23:37
An nvf configuration is a Neovim written as nvf modules, evaluated at
every system in force on the selected package set. caisson evaluates
nvf's module list with its own `evalModules`, with the special
arguments nvf's `neovimConfiguration` passes, on the composed library
extended with `lib.nvim` from the same nvf flake. The output `neovim`
is published as `packages.<system>.<name>`; the pinned-world suite
checks it is the derivation `neovimConfiguration` builds.

A NixOS machine, a darwin machine, a home and an hjem configuration
install the Neovim of an nvf configuration declared inside them, with
the module `nvf-package` of each class. caisson now registers a
`default` home-manager module and a `default` hjem module, which
import it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A nixvim configuration is a Neovim written as nixvim modules, evaluated
at every system in force on the selected package set. caisson makes the
evaluation `evalNixvim` makes, `evalModules` over `modules/top-level`
with the flake as `flake`, on the composed library extended with the
overlay nixvim publishes and with `nixpkgs.pkgs` set to the selected
package set. The output `package` is the Neovim; the configuration is
published as `nixvimConfigurations.<system>.<name>`, where nixvim's
flake publishes its own. The pinned-world suite checks the package is
the derivation `evalNixvim` builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every evaluation declared `caisson.modules.<class>.export.enabled` and
`caisson.modules.<class>.exported` as options for every registered
class. `caisson.modules` is now one option, an attribute set by class
of the two settings, so a class no module sets costs nothing to
evaluate. The definitions are written as before; a class with no
registered module, or a setting other than the two, is refused when the
exports are read. `exported` is now a value, so two definitions of it
for one class conflict instead of merging.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A topology configuration is the diagram of the NixOS configurations
declared inside it. caisson evaluates nix-topology's `topology` modules
on the composed library and the selected package set extended with
nix-topology's packages, with `nixosConfigurations` set to the machines
declared inside it at its system; those machines get nix-topology's
NixOS module by default. The configuration named `default` is
published as `topology.<system>`, where nix-topology's flake module
publishes it.

A kubenix configuration is Kubernetes resources written as kubenix
modules, evaluated as kubenix's `evalModules` evaluates them, on the
composed library extended with kubenix's `upstreamables.nix`. Its
`result` is published as `packages.<system>.<name>`.

A nixidy configuration is an Argo CD environment, evaluated with
nixidy's own `mkEnv` over the composed library, with the namespaces of
the composed library added to nixidy's through `libOverlay`. It is
published as `nixidyEnvs.<system>.<name>`, which the nixidy CLI reads.

The pinned-world suite checks each against its upstream evaluator: the
same topology nodes and diagram, the same kubenix result and the same
nixidy environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A configuration with `perSystem` has an evaluation for each system in
force; the comments called it "evaluated at a system", which names one
of those evaluations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… declared inside a machine

A system-manager configuration now acts as the parent of the
configurations declared inside it, as a NixOS or darwin machine does.
Its new registered `default` module imports:

- `home-manager-activation` and `hjem-activation`, the NixOS modules
  registered a second time for the `systemManager` class. They import
  the core module of the class they are evaluated in, and the hjem
  module keeps extra dependencies only where the class declares
  `system.extraDependencies`.
- `nvf-package`, which installs the Neovim of each nvf configuration in
  `environment.systemPackages`.
- `caisson.forChildren` settings that give homes and hjem
  configurations the `system-manager-parent` module, the `nixos-parent`
  module under a second name, which reads the nearest NixOS or
  system-manager machine.

system-manager counts as a machine for naming homes (`user@machine`),
for the `osConfig` and `utils` an hjem configuration receives, and for
the check that leaves out what an inner machine activates. A
pinned-world test declares a home, an hjem configuration and an nvf
configuration inside a system-manager machine and checks the units, the
packages and the published home name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and the one above it

Drift detection was written for one pair: a home inside a NixOS
machine compared `/etc/caisson-home-manager/source.json` with the
machine it was built for. It is now a property of the framework.

An integration whose configurations are activated passes `activation`
to `mkModuleConfiguration`: `identity`, the store path of the childless
evaluation, and `marker`, the file its activation writes that identity
into. NixOS, darwin and system-manager record the system and
`/etc/caisson/marker.json`; a home records its activation package and a
file in the state directory of its user; an hjem configuration records
its manifest file and a file in its directory. A machine `standsAlone`:
it is applied on its own host, so it checks nothing above it.

`lib.caisson.integrations` gains `activatingAncestorOf`, `markerOf`,
`checksMarkerOf` and `driftCheckOf`. Each activating class writes its
marker from its `default` module (`activation-marker` for nixos, darwin,
systemManager, homeManager and hjem) when a configuration declared
inside it checks it. A home runs `driftCheckOf` against the nearest
activating configuration above it from the home-manager module
`activation-drift`, now part of the home-manager `default` module, so a
home inside a darwin or system-manager machine is checked as one inside
NixOS is.

The NixOS module `home-manager-source-marker` and the home-manager
module `nixos-source-marker` are removed. The option
`caisson.home-manager.machineDrift.warn` becomes `caisson.drift.warn`,
and the marker field `baseSystemOutPath` becomes `identity`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rker

A drift marker held the store path of the childless evaluation with its
string context dropped, which made it a string that looks like a store
path but is not tracked as one. The marker and the check now hold the
SHA-256 digest of that store path. Computing it evaluates the childless
evaluation and builds nothing, and the digest names no store path, so
nothing depends on it and nothing can read it as a path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test of the units that activate the homes dropped the string
context of the activation package and of the unit script to search one
in the other. It now reads the context of the script, which records the
store paths the script refers to, and checks that it holds the
activation package of the home and not that of the other home.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit b6f1ff3)
…ates

An nvf or nixvim configuration declared inside a machine, a home or an
hjem configuration used to be installed into the packages of the
configuration above. It now relates to that configuration as a home
relates to its machine: it has an activation of its own, which the
configuration above runs each time it is activated itself.

`lib.caisson.integrations.profileOf` gives such a configuration the
outputs `activationPackage` and `profile`. `activate` checks for drift
against the nearest configuration above with an activation, and points
a Nix profile at the Neovim with `nix-env --profile`, which also makes
the profile a garbage-collector root; it adds no generation when the
profile already points there. Inside a machine the profile is a profile
of the system, `/nix/var/nix/profiles/caisson/<kind>/<name>`; inside a
home or an hjem configuration, or at a top, it is a profile of the user,
`$HOME/.local/state/caisson/profiles/<kind>/<name>`. The home marker
moves under `$HOME/.local/state` as well, since a unit of the machine
and a shell may see `$XDG_STATE_HOME` differently.

Each class has a `profile-activation` module in its `default` module:

- nixos and systemManager: a unit per profile wanted by
  `multi-user.target`, run as root for a profile of the system, and as
  the user after `hjem-activate@<user>` for a profile inside an hjem
  configuration, whose own activation has no step to add;
- darwin: steps of `postActivation`, the same way;
- homeManager: an activation step after the files are linked and the
  marker is written, so `home-manager switch`, a switch of the machine
  and a boot all put the profile back;
- hjem: the programs and the variable below.

Each also installs `profileShimsOf`: `nvim` (and `vi`, `vim` where the
Neovim has them) runs the profile `$CAISSON_NVIM_PROFILE` names, read
each time it runs, and the default of the configuration when it is
unset. A home or an hjem configuration sets the variable to its own
profile, and a shell can `export` another. Where several profiles share
the variable, `caisson.profiles.selected` names the default.

The modules `nvf-package` of every class and
`lib.caisson.nvf.packagesModule` are removed. nvf and nixvim get a
`default` module declaring `caisson.drift.warn`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hjem is pronounced with a leading "y" sound, so it takes "a".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The comments of the integrations said a configuration is evaluated at
every system in force, and one in the integrations library said a
configuration is evaluated at a system. They say it is evaluated per
system, once for each system in force.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ly that configuration

A tree with a treefmt configuration named `default` now also publishes
`checks.<system>.treefmt`, the configuration's `check` over the root of
the tree (`root` on `mkLib`), so `nix flake check` fails when the tree
is not formatted. A tree without a root, such as a library composed
inside a test, publishes no check.

Every treefmt configuration used to be an entry for `formatter`, which
holds a single value, so a second configuration, under any other name,
was refused when the top published. Now only the configuration named
`default` is published; one under another name is evaluated, passed
up, and found through the manifest.

`exportsTo` may be a list of exports, for an integration published in
more than one output, and an export may carry `only`, a function of the
manifest saying whether the configuration is published there.
`exportsOf` replaces `exportsToOf` in the integrations library.

The pinned-world test declares a second configuration, `docs`, which
the previous integration refused, and checks the check is published
over the root and absent without one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@clhodapp

Copy link
Copy Markdown
Contributor Author

Folded into #98, the treefmt PR, where the treefmt integration is introduced: the configuration named default is published as checks.<system>.treefmt too, only default is published, and exportsTo takes a list with only. The stack was restacked onto it.

@clhodapp clhodapp closed this Oct 18, 2026
An error occurred while trying to automatically change base from fix/system-manager-pkgs to main October 18, 2026 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant