contracts: init - #506343
Draft
KiaraGrouwstra wants to merge 19 commits into
Draft
Conversation
KiaraGrouwstra
marked this pull request as ready for review
April 3, 2026 16:53
13 tasks
KiaraGrouwstra
force-pushed
the
contracts-automated
branch
2 times, most recently
from
April 3, 2026 17:23
47917e6 to
2d43014
Compare
|
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: |
KiaraGrouwstra
force-pushed
the
contracts-automated
branch
from
April 3, 2026 17:39
2d43014 to
262c8a1
Compare
KiaraGrouwstra
force-pushed
the
contracts-automated
branch
from
April 3, 2026 17:49
262c8a1 to
1c0ed60
Compare
Contributor
Author
|
KiaraGrouwstra
force-pushed
the
contracts-automated
branch
from
April 4, 2026 06:17
1c0ed60 to
c80a599
Compare
13 tasks
KiaraGrouwstra
force-pushed
the
contracts-automated
branch
5 times, most recently
from
April 4, 2026 09:45
331e0da to
47d0440
Compare
KiaraGrouwstra
force-pushed
the
contracts-automated
branch
3 times, most recently
from
April 28, 2026 13:34
1f5d306 to
1fa0650
Compare
KiaraGrouwstra
force-pushed
the
contracts-automated
branch
4 times, most recently
from
May 17, 2026 09:04
60fee56 to
bd15605
Compare
13 tasks
Contributor
Author
Contributor
|
On the latest change related to hardcoded secrets and systemd, although technically correct to rely on systemd, the usefulness is limited because the harcoded secret module is only intended to be used in tests, which can probably all be written acceptably using the system activation script. |
This initial change demonstrates the concept of a contract: an expectation as verified by a test (here: `fileSecrets` test), that may be fulfilled by a provider (in our example here: `hardcoded-secret`). This simple example demonstrates that at their core, contracts do not technically require any machinery: without the test, our `hardcoded-secret` 'contract provider' module would still just work: This just uses existing NixOS machinery of options, types (with those of the abstract expectation in this change still left implicit: the interface used by the test to verify expected behavior), and tests. What this achieves is also just things the module system does: given the options, one may put info in, which may generate configuration, as well as lead to calculated options yielding information one may take back out again.
Add `lib.types.nestedAttrsOf`, as a nested attribute set of a given type. Further add functions in `lib.attrsets` to process values of this type: - `isNestedAttrsLeaf` - `mapNestedAttrsWith` - `mapNestedAttrs'` - `concatMapNestedAttrsWith` - `concatMapNestedAttrs'` Note that recursion over this type may either require all or any of the leaf types to be present. While this type and its helpers are generic in function, these will later be used for abstracting overc variable-depth nested attrsets used in name-spacing for contract instances. In that particular use-case, we may only presume for part of the leaf type's keys to be present. Signed-off-by: cinereal <cinereal@riseup.net>
Adds module system helper functions intended to facilitate contracts: - `lib.evalOption`: evaluate config in the context of an option, which allows using the type checks (and calculated options) of the module system as a function, e.g. for use in `lib`. Further adds two functions intended to facilitate extending submodule options/types, so as to resolve contracts' challenge of deduplicating types that may differ largely in overriding `default` values. - `lib.extendOption`: extend a submodule option with overrides - `lib.extendSubmodule`: extend a submodule type with overrides Signed-off-by: cinereal <cinereal@riseup.net>
Adds a central place for storing contracts, alongside a way to aggregate requests to be handled by preferred (default) providers for the contract. The contracts are presented as typed request/result interfaces between consumers and providers. Storing the contracts themselves in `lib` is technically optional, but any stored in there we can generate documentation for. One may nevertheless extend this nixpkgs-provided set of contracts with their own in `config.contracts`. Note that our contracts module is intended to be environment-agnostic, that is, should work not just in NixOS, but also in modular services and e.g. home-manager. That said, providers are expected to be specific to an environment: each environment exposes a different set of configuration options (while if we had no need to set configuration options, one might as well use plain functions over contracts). `lib/contracts` uses the following structure: - `default.nix`: entrypoint for `lib.contract` - `helpers.nix`: helper functions for use with contracts: so far `isInstance` - `module.nix`: contracts module with `contractDefinitions` and `contracts` - `templates/default.nix`: exposes contract templates in this directory as `lib.contracts` - `templates/file-secrets.nix`: example contract template `fileSecrets` Signed-off-by: cinereal <cinereal@riseup.net>
Rename the raw `mkProviderType` on the contract-definition type to `_mkProviderType` and introduce `config.contracts.<name>.mkProviderType` as a bridge that pre-binds `_requests`. The bridge re-applies the consumer's want-derived request data at `mkDefault` priority at each nestedAttrsOf leaf, so deployer writes at provider-specific options (`providerOptions`) no longer mask consumer wants via leaf-level priority filtering. Adds a regression test covering exactly that path (`contracts-partial-overrides`). Test modules using `mkProviderType` switch to the bridge form (`config.contracts.<X>.mkProviderType`). Assisted-by: Claude:claude-opus-4-7[1m]
`lib.contract.forModule config` rebinds every contract's `mkProviderType` against the module's `config`, returning an attrset shaped like `lib.contracts` and extended with any inline contracts on `config.contractDefinitions`. For each contract, it prefers the bridge (`config.contracts.<name>.mkProviderType`) when available and falls back to the raw lib function -- so modules built inside the NixOS-manual sandbox (where `contracts` is absent) still get a valid `mkProviderType`. Switches the submodule-request test to consume `mkProviderType` through `forModule`. Assisted-by: Claude:claude-opus-4-7[1m]
Add `interface.extraImports.{request,result}` so contracts can ship
backward-compatible aliases for renamed request/result options, and
expose the pattern for renaming a whole contract via a deprecated
`contractType` plus a warning. Includes lib tests covering both cases.
Assisted-by: Claude:claude-sonnet-4-6
Adds the NixOS contracts wrapper module that imports the generic `lib.contract.module` and seeds `config.contractDefinitions` with `lib.contracts`, making all nixpkgs contract types available in NixOS alongside user-defined types. Assisted-by: Claude:claude-sonnet-4-6
…r services - `lib/contracts/module.nix`: remove `readOnly` from `requests`/`results` so `mkForce` seeding works; extend `results` description with modular consumer and bridge path examples - `lib/services/lib.nix`: add `evalServices` for peer-to-peer contract resolution across a service set (no parent NixOS context required); update `configure` to accept `contracts`/`upstreamContractDefinitions` and seed each service's `contracts.*` via an upstream seed module - `lib/services/service.nix`: import `lib.contract.module`; propagate `contractDefinitions` and scoped `contracts.*` (results, defaultProvider) to sub-services via direct injection
The `behaviorTest` framework used to write directly to the provider option at a caller-supplied `providerRoot`. That bypassed the canonical consumer `want` flow and made the test machinery hostile to the leaf-priority handling that `nestedAttrsOf` performs on provider-side instances. Switch the framework to declare its request at `contracts.fileSecrets.want.<wantPath>.request` (a normal consumer write) and read the resolved result back from `contracts.fileSecrets.results.<wantPath>`. Callers now pass a `wantPath` rooted under `want`, not the full provider-option path. Assisted-by: Claude:claude-opus-4-7[1m]
Extract the contract-name prefix, want-path wiring, and result extraction from `behaviorTest` into a reusable `lib.contract.mkBehaviorTest` helper parameterized by `contractName`, `testName`, and `wantPath`. Refactor the file-secrets `behaviorTest` to call the helper, leaving only per-contract pieces (test options, request shape, nodeModule, testScript body) inline. Assisted-by: Claude:claude-opus-4-7[1m]
Rewrites `testing.hardcoded-secret` onto `mkProviderType` so it is a proper `fileSecrets` provider rather than a bespoke implementation. Adds `nixos/tests/contracts/arithmetic-contract.nix` — a reusable module that registers the `arithmetic` contract type for NixOS tests. Uses a per-secret systemd oneshot service rather than an activation script: activation scripts cannot reliably order against `systemd-sysusers.service` (both land in `sysinit.target` without an explicit dependency edge), so `chown` in an activation script silently fails when the target user is created by sysusers. - `after = ["systemd-sysusers.service" "nixos-activation.service"]` ensures both user creation paths have completed before the file is written. - `before = ["multi-user.target"]` ensures the secret is in place before any normal service starts. - `requires/after` injected into the consumer service provide an explicit dependency edge for services that start before `multi-user.target` is fully reached. Assisted-by: Claude:claude-sonnet-4-6
Introduce `secretOptionType` (a `oneOf [ path contractSecretsType ]`) for the three secret options and update the startup script to use `lib.contract.isInstance` to branch between a plain path and a contract result. Rename `jwtSecretKeyFile`/`sessionStoreKeyFile` to `jwtSecretKey`/`sessionStoreKey`, keeping the old names working via `mkRenamedOptionModule` so existing configurations continue to evaluate with a deprecation warning. Register `passwordFile`, `jwtSecretKey`, and `sessionStoreKey` with the fileSecrets contract system via `contracts.fileSecrets.want.stash` so providers can fulfill them, and default `jwtSecretKey`/`sessionStoreKey` to the contract result so users relying on a fileSecrets provider need not set them explicitly. Assisted-by: Claude:claude-opus-4-8
Add a `Development` chapter covering motivation, consumer/provider patterns, provider selection, adding a new contract type, chaining, cross-node contracts, and renames. Wire it into `development.md`'s chapter list and register the section anchors in `redirects.json`.
Also collapses the `byRef`/`byName` test groups in `contracts-provider-selection.nix` into a single shared `by` contract type and switches the file to consume `mkProviderType` via `lib.contract.forModule`. Assisted-by: Claude:claude-opus-4-7[1m]
Adds two NixOS VM tests demonstrating advanced contract patterns: - `chaining`: a provider that is also a consumer of another contract - `cross-node`: using a shared `evalModules` to wire a contract across two separate NixOS nodes - `cross-node-modular-services`: similar but using modular services Assisted-by: Claude:claude-sonnet-4-6
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.
Implements contracts (NixOS/rfcs#189) by building on the work by @ibizaman (and earlier work by @fricklerhandwerk).
Closes NixOS/rfcs#189.
For more info, see the manual entry or options reference.
Supersedes: #432529, #485453, #495303, #500287.
Disclaimer: I have made use of a coding agent to get things to this state.
Use-cases
Differences from the RFC
contracts.<type>.want, providers register intocontracts.<type>.providers, and adefaultProvider/defaultProviderNameselects which provider fulfills all requests. This avoids the 'dual-link' problem the RFC mentioned.lib/contracts/module.nixandlib/services/are system-agnostic, designed to pave the way for contracts support from other systems managing nix (modular) services, e.g. home-manager, nimi, nix-darwin, finix, NixBSD, etc.contracts.<name>withdeferredModuletypes. We separate type definitions (contractTypes) from instances (contracts), with types shipped inlib.contracts(usable from the sandbox for docs builds) and extensible from outside nixpkgs viaconfig.contractTypes(without such docs).Tests to run on changes
bash lib/tests/modules.shnix-instantiate --eval lib/services/test.nixnix-build -A nixosTests.contractsnix-build -A stash.passthru.tests.stashnix-build nixos/release.nix -A manual.x86_64-linuxNext steps
None of these are breaking changes necessarily, so may not need to be in-scope for the initial PR:
Things done
passthru.tests.nixpkgs-reviewon this PR. See nixpkgs-review usage../result/bin/.