Skip to content

Add deferred configurations, direct-children additions and cross-child references - #57

Merged
clhodapp merged 6 commits into
mainfrom
feat/deferred-children
Oct 29, 2026
Merged

clhodapp merged 6 commits into
mainfrom
feat/deferred-children

Conversation

@clhodapp

@clhodapp clhodapp commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Implements the caisson-core part of section 21 of the caisson design document (deferred evaluations, cross-child references).

Deferred configurations. A deferred configuration is a function of exactly { name, parent, args }, where a configuration is a function of { name, parent }. The args are values a deployment tool supplies after the flake is evaluated, and they reach the configuration only through args. mkDeferredConfiguration { type; class; record; configuration; } makes one. A parent cannot call it, because it has no args to give, so it holds an entry that deferChild makes: the record, the name, type, class and parent, and finalize { args, argsSource }, which returns the manifest. The parent's history, its inputs and the grouping by system leave the entry out.

Args on the manifest. Every manifest carries argsNeeded, the args its evaluation takes as an attrset of mkArg { cacheable } leaves, and discoverArgs, a function of { system } returning a derivation whose program prints the args it finds as JSON, or null. Both are empty or null for a configuration that takes no args. finalize fills the args against the argsNeeded of the manifest it returns: a given arg is passed on unevaluated, and an arg the caller did not give throws caisson-deferred: needs <path> when it is read. The manifest finalize returns carries argsSource, a function from an arg path to where its value came from, and never the values. A tool and a parent that gives its deferred children their args both call finalize; there is no other way to finalize a deferred configuration.

forDirectChildren. A third container beside forChildren and forNearestChildren, with the per-class extraDefaultModuleImports. It reaches the direct children of the class only (a child evaluated per system included), after the nearest additions, marked direct = true, and nothing beneath them.

unsafeCrossChildReferences. Each container may hold it by class. A configuration it reaches reads the full evaluation of the level that set it, as crossChildLevel (and crossChildLevelPath). forDirectChildren beats forNearestChildren, which beats forChildren; within one container the nearest parent wins; a forNearestChildren setting connects at its class whether or not it wins. The childless manifest of a level that sets it carries its full manifest as crossChildFull.

The hermetic tests cover these, including args that depend on the args above them and an arg that was not given failing where it is read; all 137 pass. caisson's use of these follows in nix-caisson/caisson#141 (the colmena nodes fix) and #142 (the NixOps4 integrations), pinned to this branch.

🤖 Generated with Claude Code

…references

A deferred configuration (mkDeferredConfiguration) waits for args supplied after flake evaluation. A parent holds it as an entry (deferChild) whose finalize, through finalizeDeferredChild, returns its manifest against the parent's childless view; finalizeDeferredTop does the same under the lib's manifest. History, inputs and the grouping by system leave the entries out. Manifests record the names and source of the args, not their values. mkArg, argsNeededFor, argNamesOf and standInsFor serve tools that evaluate deferred configurations ahead of time.

forDirectChildren is a third container beside forChildren and forNearestChildren, reaching only the direct children of a class. Each container may hold unsafeCrossChildReferences by class: the configurations it reaches read the full evaluation of the level that set it as crossChildLevel. forDirectChildren beats forNearestChildren, which beats forChildren, and within one container the nearest parent wins.

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

Which outputs a NixOps4 composite needs depends on the members it declares, which only an evaluation against its parent shows. A function argsNeeded now receives { name, parent, args }, as the deferred configuration does, and argsNeededFor reads name and parent from the entry.

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

A discovery program runs on the system of the tool that runs it, and its package set depends on what is in force at the parent, which the deferred configuration does not know when it is made. discoverArgs may now be derivations by system, or a function of { name, parent } returning a derivation or derivations by system; discoverArgsFor reads it from an entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clhodapp added a commit to nix-caisson/caisson that referenced this pull request Oct 26, 2026
A root composite, native or upstream-compatible, takes stateResources, the member paths of the resources that hold the state of the deployment, as nixops4 state dump takes them. It then gives discoverArgs: for each system a package config builds a set for, a program that runs nixops4 state dump for each of them in the working directory and prints the recorded outputs as outputValues, with the deploying system. A finalized composite publishes deferredChildArgs, the args it gives a member, so a tool can finalize a member's entry itself.

caisson-core is pinned to the head of nix-caisson/caisson-core#57, where discoverArgs may be derivations by system or a function of the entry's name and parent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iscovery takes the system

argsNeeded is always a function of { name, parent, args }, and discoverArgs always a function of { name, parent, system } returning a derivation built for that system, or null; the default returns null. mkDeferredConfiguration refuses either one that takes other arguments, as it refuses such a configuration. argsNeededFor entry known and discoverArgsFor entry system call them with the name and parent the entry holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clhodapp added a commit to nix-caisson/caisson that referenced this pull request Oct 27, 2026
caisson-core now calls discoverArgs with the system of the tool that runs it, so a root composite builds one discovery program, for that system, rather than one for every system its package configs build a set for.

caisson-core is pinned to the head of nix-caisson/caisson-core#57, where argsNeeded and discoverArgs are always functions.

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

A deferred configuration is now only `type`, `class`, `record` and a
function of `{ name, parent, args }`. The manifest it returns carries
`argsNeeded` and `discoverArgs`, as every manifest does, and
`finalize { args, argsSource }` fills the args against that
`argsNeeded`: an arg the caller did not give throws
`caisson-deferred: needs <path>` when it is read. `discoverArgs` is a
function of `{ system }` on the manifest, and `argsSource` is a
function from an arg path to where its value came from.

`finalize` is the one way to finalize a deferred configuration, for a
tool and for a parent that gives its deferred children their args.
`finalizeDeferredChild`, `finalizeDeferredTop`, `argsNeededFor`,
`discoverArgsFor`, `argNamesOf` and `standInsFor` are removed, and the
manifest no longer records `deferred`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clhodapp added a commit to nix-caisson/caisson that referenced this pull request Oct 27, 2026
… finalize members with their part of its args

The root of a deployment is a deferred caisson-nixops4-composite configuration declared under the name `nixops4`, for the output it fills, and the upstream-compatible nixops4 configuration is such a root of class nixops4Component. The caisson-nixops4 declaration integration is removed. Its helpers are published by the resource integration.

A composite evaluates with its args, `outputValues` and `resourceProviderSystem`, finalizes each member once with that member's part of them, and returns the members' manifests as its children. Its manifest names the args it takes in `argsNeeded`: the deploying system and, by member, the outputs each resource names, cacheable where the resource lists them. The root gives `discoverArgs`, which finds the resources that hold state from their `state` option. A native composite's `members` holds each member's outputs and `absoluteMemberPath`, from the args and the declared names. An upstream composite declares `members` again as deferred modules, and reading it gives the members' configs, so `members.x.outputs.y`, `config.members.x.outputs.y` and `state = members.x` work.

The integrations builder passes `argsNeeded`, `discoverArgs` and `children` through from an integration's evaluate, publishes the entry of a deferred configuration from the `exportsTo` its record holds, and passes no registries up from an entry.

caisson-core is pinned to 2293708 on nix-caisson/caisson-core#57, where `finalize { args, argsSource }` fills the args from the manifest's own `argsNeeded`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gsNeeded holds a set

A deferred configuration given a string, list or other non-attrset where
its `argsNeeded` declares a set of args failed with Nix's `expected a set`
from `//`, which `builtins.tryEval` cannot catch and which names no arg.
`fillArgs` now throws `caisson-deferred: the args given at <path> are a
<type>, ...`, which does not start `caisson-deferred: needs`, so a tool
reports it as a failure rather than a missing arg.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@clhodapp
clhodapp merged commit 6868bca into main Oct 29, 2026
2 checks passed
clhodapp added a commit to nix-caisson/caisson that referenced this pull request Oct 29, 2026
A caisson-nixops4 configuration, named nixops4 for the output it fills, publishes the root NixOps4 reads: rootFunction and getProviders over the root composite, a deferred caisson-nixops4-composite configuration declared under the name root. NixOps4 evaluates the root when it deploys, so the composite is finalized with the args NixOps4 passes: the output values and the deploying system.

A composite holds its members as deferred caisson-nixops4-composite and caisson-nixops4-resource configurations beneath it and finalizes each with its part of those args. Composite and resource are two classes. A composite has NixOps4's providers option and no type; a resource has NixOps4's resource, type and providers options and no members. A component reads the outputs of its composite's members, and nothing else of them, through the members module argument.

The NixOS adapter, the module nixos of the resource class, deploys the NixOS configuration declared beneath a resource as nixops4-nixos does, with the same SSH options, and runs the script with bash from the package set caisson selects for the deploying system. The default module of the resource class selects it and gives the NixOS configurations beneath the nixops4-apply NixOS module, which imports the apply module of nixops4-nixos.

argsNeeded names the deploying system and the outputs of the members, and a resource lists the outputs a tool may cache in cacheableOutputs.

caisson-core is pinned to the head of nix-caisson/caisson-core#57, where a function argsNeeded receives the name and parent of the entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clhodapp added a commit to nix-caisson/caisson that referenced this pull request Oct 29, 2026
A root composite, native or upstream-compatible, takes stateResources, the member paths of the resources that hold the state of the deployment, as nixops4 state dump takes them. It then gives discoverArgs: for each system a package config builds a set for, a program that runs nixops4 state dump for each of them in the working directory and prints the recorded outputs as outputValues, with the deploying system. A finalized composite publishes deferredChildArgs, the args it gives a member, so a tool can finalize a member's entry itself.

caisson-core is pinned to the head of nix-caisson/caisson-core#57, where discoverArgs may be derivations by system or a function of the entry's name and parent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clhodapp added a commit to nix-caisson/caisson that referenced this pull request Oct 29, 2026
caisson-core now calls discoverArgs with the system of the tool that runs it, so a root composite builds one discovery program, for that system, rather than one for every system its package configs build a set for.

caisson-core is pinned to the head of nix-caisson/caisson-core#57, where argsNeeded and discoverArgs are always functions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clhodapp added a commit to nix-caisson/caisson that referenced this pull request Oct 29, 2026
… finalize members with their part of its args

The root of a deployment is a deferred caisson-nixops4-composite configuration declared under the name `nixops4`, for the output it fills, and the upstream-compatible nixops4 configuration is such a root of class nixops4Component. The caisson-nixops4 declaration integration is removed. Its helpers are published by the resource integration.

A composite evaluates with its args, `outputValues` and `resourceProviderSystem`, finalizes each member once with that member's part of them, and returns the members' manifests as its children. Its manifest names the args it takes in `argsNeeded`: the deploying system and, by member, the outputs each resource names, cacheable where the resource lists them. The root gives `discoverArgs`, which finds the resources that hold state from their `state` option. A native composite's `members` holds each member's outputs and `absoluteMemberPath`, from the args and the declared names. An upstream composite declares `members` again as deferred modules, and reading it gives the members' configs, so `members.x.outputs.y`, `config.members.x.outputs.y` and `state = members.x` work.

The integrations builder passes `argsNeeded`, `discoverArgs` and `children` through from an integration's evaluate, publishes the entry of a deferred configuration from the `exportsTo` its record holds, and passes no registries up from an entry.

caisson-core is pinned to 2293708 on nix-caisson/caisson-core#57, where `finalize { args, argsSource }` fills the args from the manifest's own `argsNeeded`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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