Repository navigation
Add the NixOps4 integrations, native and upstream-compatible - #142
Merged
Merged
Conversation
This was referenced Oct 26, 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>
A nixops4 configuration, named nixops4, takes the root modules of a NixOps4 deployment as NixOps4 users write them for mkRoot, and caisson evaluates every component, with NixOps4's class name nixops4Component and NixOps4's resource, type and providers options. A composite collects members.<name> and decides each member's integration by upstream's test, with a probe evaluation over NixOps4's base modules: a member whose resourceType is defined is a caisson-nixops4-resource configuration, any other a caisson-nixops4-composite configuration. A member that sets type and declares members is refused. The members module argument of a composite holds its members' configs, as upstream's does, so every composite sets unsafeCrossChildReferences for the class of its members. A member that imports nixops4-nixos gets its nixos.module evaluated as a caisson NixOS configuration beneath it: the module nixos of the nixops4Component class forces nixpkgs to a value whose lib.nixosSystem returns that configuration, since nixos.configuration is read-only, and takes the shell from caisson's package set. cacheableOutputs is the allow-list of outputs a tool may cache, by member path. The eval-weight baseline is re-measured: the core module of every class declares the configurations of every integration, so the four integrations of the NixOps4 work add to caisson's overhead in every evaluation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
…teConfiguration and mkResourceConfiguration They return what a mkConfiguration returns, built on mkModuleConfiguration; the With of compositeWith and resourceWith said nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 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>
…d dumps from the root, and name the errors of members and outputs named from outputs The discovery program looped with `for state in <paths>`, which shellcheck refuses (SC2043) when a root has one state resource, so the program did not build. It loops over a bash array now. It also nested the outputs of a state resource inside a composite twice: `nixops4 state dump` keys the resources from the root, and the program prefixed the path of the composite again. The new `nixops4-discovery` check builds the program of two roots against a stand-in for NixOps4 and compares what it prints from recorded dumps with the paths the root names in `argsNeeded`. A composite that decided its members from `members` or their outputs, and a resource that named its outputs from its own outputs, ended in an infinite recursion. A composite now takes the names of its members from an evaluation in which `members` fails with an error naming the composite, and a resource takes the names of its outputs from one in which its outputs fail with an error naming the resource. The pinned-world suite gains probes for nested composites, a machine in a nested composite, these errors, one member under a name, one root per flake, a root inside a machine, entries a parent does not finalize, `getProviders`, and nested upstream composites. The `deferred-errors` check reads the text of the errors, `caisson-deferred: needs <path>` included, with the real evaluator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ot a set caisson-core b9de3d0 names the path of args given as a non-attrset where argsNeeded declares a set. The deferred-errors check reads that message for output values given as a string. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clhodapp
force-pushed
the
feat/nixops4
branch
from
October 29, 2026 03:21
eec82eb to
f641163
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #141. Implements the NixOps4 part of section 21 of the design document and supersedes #139, which built NixOps4 machines as flake-time children.
The root. A deployment's root is a deferred composite declared at the flake top under the name of the output it fills:
caisson.caisson-nixops4-composite.configurations.nixops4, orcaisson.nixops4.configurations.nixops4for upstream code. Its entry is published as thenixops4output,{ _type; rootFunction; getProviders; }.rootFunctioncalls the entry'sfinalizewith theoutputValuesandresourceProviderSystemNixOps4 passes and returns the export.getProviderscallsfinalizewith the system alone, so every output read throws, and builds the root's providers. Published names continue through members:nixops4,nixops4.web.Native integrations.
caisson-nixops4-compositeandcaisson-nixops4-resourceare deferred configurations (caisson-core'smkDeferredConfiguration). A composite finalizes each member it declares by callingfinalizeon the member's entry, with{ outputValues = args.outputValues.<member>; resourceProviderSystem; }, and returns the finalized members as its children.providersoption and notype. A resource has NixOps4's resource,typeandprovidersoptions and nomembers.membersmodule argument holds, for each member,outputsandabsoluteMemberPath, built from the args and the declared names. No member's config is reachable through it, or through the composite'scaisson.<integration>.configurations, which hold entries.argsNeededlists the deploying system and, per member, one arg per name in a resource'soutputsSkeleton. A resource lists the outputs a tool may cache incacheableOutputs.discoverArgs { system }is a program that runsnixops4 state dumpfor each resource named by a resource'sstateoption, and prints the recorded outputs asoutputValues, with the deploying system.nixosof the resource class, selected by its default module) deploys the NixOS configuration declared beneath a resource as nixops4-nixos does:nix copyandbin/apply switchover SSH, with the same SSH options, andbashfrom the package set caisson selects for the deploying system.defaultPkgs, the package set the root's providers, discovery program and adapters run on.Upstream-compatible integration.
nixops4takes root modules as written for NixOps4'smkRoot. The root and every member are evaluated by caisson with NixOps4's classnixops4Component.options.resourceType.isDefined), through a probe evaluation over NixOps4's base modules. A member that setstypeand also declares members is refused.membersoption is declared again as deferred modules whose value is the finalized members' config, andmembersis the module argument of the same value, soconfig.members.x.outputs.y,members.x.outputs.yandstate = members.xwork as upstream. Every composite setsunsafeCrossChildReferencesfornixops4Component, so the childless view reads the same config.nixos.moduleevaluated as a caisson NixOS configuration beneath it.nixos.configurationis read-only, so the class module forcesnixpkgsto a value whoselib.nixosSystemreturns that configuration. The shell comes from caisson's package set.cacheableOutputsis an allow-list by member path.Integrations builder.
mkModuleConfigurationpassesargsNeededanddiscoverArgsfrom an integration'sevaluateto the manifest, and takeschildrenfrom it when the integration returns them. A deferred entry that carriesexportsTois published like a manifest.Also.
nixops4-applyNixOS module and the test pins of NixOps4 (41858b4) and nixops4-nixos (4c7ceab) carry over from Integrate NixOps4 deployments as caisson-nixops4 #139.Named errors. A composite whose member names or kinds read
membersor outputs, and a resource whoseoutputsSkeletonreads its own outputs, fail with an error naming the composite or resource. Without these errors, both cases end in an infinite recursion. The names are taken from an evaluation in which those reads throw, which costs one extra module evaluation per composite and resource.Tests.
argsNeeded,argsSource,membersof a composite member, a NixOS machine inside), a resource whosetypedepends on another member's output, the nativemembersargument holding only outputs and member paths, the root's providers, a root inside a NixOS machine, entries no parent finalizes, upstream reads throughmembersat two levels, a NixOS member built through nixops4-nixos that matches the systemeval-config.nixbuilds from the same modules, and a second deferred integration besidenixops4in one flake.deferred-errorsrunsnix evalon eleven cases and matches the exact error text: a missing output at the root and inside nested composites (caisson-deferred: needs outputValues.a.b.source.stdout), members or kinds read from outputs, anoutputsSkeletonread from its own outputs, a name declared as composite and resource, two roots, a root under another name, and output values that are not a set.nixops4-discoverybuilds the root's discovery program for one and for two state resources, runs it againstnixops4 state dumpoutput recorded from a real deployment, and compares the JSON with the argsargsNeedednames.nixops4 applyof a native and an upstream-compatible deployment with the local provider, run outside the test suite, applied in output order, passed the values through nested composites, was a no-op the second time, and reported a cycle through outputs asCycle detected.pinned-world,deferred-errors,nixops4-discovery,nix-unit,eval-weight,argument-errorsandtreefmtpass locally. Every x86_64-linux check, the VM tests included, passed ata5204f8; the commits since change only the NixOps4 integrations and tests.🤖 Generated with Claude Code