Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6639af3
hardcoded-secret: init with contract for secrets
ibizaman Feb 27, 2026
e1d3439
lib.attrsets: add helpers for nested attribute sets
KiaraGrouwstra Apr 3, 2026
fe2762b
lib: add module system extensions for contracts
KiaraGrouwstra Apr 15, 2026
e495f39
lib/contracts: add contracts system
KiaraGrouwstra Apr 15, 2026
cab39ca
lib/contracts: add defaultProvider
KiaraGrouwstra May 17, 2026
d49cf42
lib/contracts: add mkContract helper
KiaraGrouwstra May 20, 2026
8adbcd6
lib/contracts: add mkProviderType helper
KiaraGrouwstra May 20, 2026
6a42e65
lib/contracts: add `_mkProviderType` and want-forwarding bridge
KiaraGrouwstra May 25, 2026
e1796cf
lib/contracts: add `forModule` helper
KiaraGrouwstra May 25, 2026
8dd03ad
lib/contracts: add request and contract rename machinery
KiaraGrouwstra May 18, 2026
14af2dd
nixos/modules/contracts: init
KiaraGrouwstra May 18, 2026
b0fdcd4
lib/services: add `evalServices` and contracts integration for modula…
KiaraGrouwstra May 21, 2026
6d7d70a
lib/contracts: route file-secrets behaviorTest through want
KiaraGrouwstra May 25, 2026
e67bccb
lib/contracts: factor `mkBehaviorTest` helper
KiaraGrouwstra May 25, 2026
7a1ba10
nixos/modules/testing/hardcoded-secret: switch to contracts
KiaraGrouwstra May 18, 2026
e798b06
stash: migrate secrets to the fileSecrets contract
KiaraGrouwstra May 20, 2026
58358e3
nixos/doc/manual: document the contracts system
KiaraGrouwstra Apr 26, 2026
ea61965
lib/contracts: add defaultProviderName
KiaraGrouwstra May 21, 2026
67e9410
nixos/tests/contracts: add chaining and cross-node tests
KiaraGrouwstra May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 219 additions & 0 deletions lib/attrsets.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2258,4 +2258,223 @@ rec {
) intersection;
in
(x // y) // mask;

/**
Determine whether a value is a leaf node when traversing a `nestedAttrsOf elemType` structure.

`isNestedAttrsLeaf :: ((String -> Bool) -> [String] -> Bool) -> OptionType -> Any -> Bool`

For scalar element types (e.g. `int`, `str`), uses the type's `.check`.
For submodule types, given their native type check works only after merge,
instead check key presence, controlled by `predicate` (`lib.any` or `lib.all`)
depending on whether the element submodule should allow additional keys.

# Inputs

`predicate`

: 1\. A predicate combinator: `lib.any` treats a value as a leaf if *any* key matches a declared option; `lib.all` requires *all* keys to match

`elemType`

: 2\. The element option type of the `nestedAttrsOf` structure

`v`

: 3\. The value to test

# Example

```nix
let
elementType = types.submodule { options.result = mkOption { type = types.int; }; };
in
isNestedAttrsLeaf lib.any elementType { result = 1; }
# => true
isNestedAttrsLeaf lib.any elementType { a.b = { result = 1; }; }
# => false (intermediate attrset, no key matches "result")
```
*/
isNestedAttrsLeaf =
predicate: elemType: v:
let
# Top-level option names of the element type, used to detect leaves.
# Mirrors the heuristic in `nestedAttrsOf.merge`.
elemOptAttrs = elemType.getSubOptions [ ];
in
if elemOptAttrs == { } then
elemType.check v
else
lib.isAttrs v && predicate (k: elemOptAttrs ? ${k}) (lib.attrNames v);

/**
Map a function over all leaves in a `nestedAttrsOf` structure.

`mapNestedAttrsWith :: ((String -> Bool) -> [String] -> Bool) -> nestedAttrsOf a -> (a -> b) -> nestedAttrsOf a -> nestedAttrsOf b`

Traverses the nested attribute set recursively, applying `fn` only at leaf
values as determined by `isNestedAttrsLeaf predicate`. Intermediate attrset
nodes are preserved as-is in the output structure.

# Inputs

`predicate`

: 1\. Leaf-detection combinator: `lib.any` or `lib.all`

`type`

: 2\. A `nestedAttrsOf` option type whose element type identifies leaves

`fn`

: 3\. Function applied to each leaf value

`value`

: 4\. The nested attribute set (or leaf) to traverse

# Example

```nix
mapNestedAttrsWith lib.any (types.nestedAttrsOf types.int) (x: x * 2) { a.b = 1; a.c = 3; d = 5; }
# => { a.b = 2; a.c = 6; d = 10; }
```
*/
mapNestedAttrsWith =
predicate:
let
recurse =
type: fn: value:
if isNestedAttrsLeaf predicate type.nestedTypes.elemType value then
fn value
else
mapAttrs (_: recurse type fn) value;
in
recurse;

/**
Map a function over all leaves in a `nestedAttrsOf` structure, preserving the structure.

`mapNestedAttrs' :: nestedAttrsOf a -> (a -> b) -> nestedAttrsOf a -> nestedAttrsOf b`

Traverses the nested attribute set recursively,
applying `fn` only at leaf values.
Intermediate attrset nodes are preserved as-is.
For submodule element types allowing additional keys,
while presuming no key of the nestred attrset structure is contained in the element submodule type.

# Inputs

`type`

: 1\. A `nestedAttrsOf` option type whose element type identifies leaves

`fn`

: 2\. Function applied to each leaf value; result replaces the leaf in the output

`value`

: 3\. The nested attribute set to traverse

# Example

```nix
mapNestedAttrs' (types.nestedAttrsOf types.int) (x: x * 2) { a.b = 1; a.c = 3; d = 5; }
# => { a.b = 2; a.c = 6; d = 10; }
```
*/
mapNestedAttrs' = mapNestedAttrsWith lib.any;

/**
Like `concatMapNestedAttrs'`, but with a configurable leaf-detection predicate.

`concatMapNestedAttrsWith :: ((String -> Bool) -> [String] -> Bool) -> nestedAttrsOf elemType -> ([String] -> elemType -> AttrSet b) -> nestedAttrsOf elemType -> AttrSet b`

The `predicate` argument controls how submodule-typed leaves are detected from
intermediate namespace attrsets: pass `lib.any` (used by `concatMapNestedAttrs'`)
to treat a value as a leaf if *any* of its keys is a declared option of the element
type, or `lib.all` to require *all* keys to match.

# Inputs

`predicate`

: 1\. Leaf-detection combinator: `lib.any` or `lib.all`

`type`

: 2\. A `nestedAttrsOf` option type whose element type identifies leaves

`fn`

: 3\. Function called at each leaf: receives the path (list of strings) and leaf value, returns an attrset

`attrs`

: 4\. The nested attribute set to traverse

# Example

```nix
concatMapNestedAttrsWith lib.all (nestedAttrsOf (submodule { options.result = mkOption { type = int; }; }))
(path: v: { ${concatStringsSep "." path} = v.result; })
{ a.b = { result = 1; }; a.c = { result = 2; }; }
# => { "a.b" = 1; "a.c" = 2; }
```
*/
concatMapNestedAttrsWith =
predicate: type: fn:
let
recurse =
path: attrs:
lib.concatMapAttrs (
name: v:
if isNestedAttrsLeaf predicate type.nestedTypes.elemType v then
fn (path ++ [ name ]) v
else
recurse (path ++ [ name ]) v
) attrs;
in
recurse [ ];

/**
Like `concatMapAttrs`, but recurses into nested attribute sets, calling `fn`
only at leaves as determined by the element type of the given `nestedAttrsOf` type.

The function `fn` receives the full path (as a list of strings) to the leaf
and the leaf value, and must return an attribute set to be merged into the result.

Leaf detection mirrors `nestedAttrsOf.merge`: for simple element types (e.g. `int`)
`elemType.check` is used directly; for submodule element types (whose `check = isAttrs`
cannot distinguish leaves from intermediate nodes) a value is treated as a leaf when
all of its keys are declared options of the element type.

`concatMapNestedAttrs' :: nestedAttrsOf elemType -> ([String] -> elemType -> AttrSet b) -> nestedAttrsOf elemType -> AttrSet b`

# Inputs

`type`

: 1\. A `nestedAttrsOf` option type whose element type identifies leaves

`fn`

: 2\. Function called at each leaf: receives the path (list of strings) and leaf value, returns an attrset

`attrs`

: 3\. The nested attribute set to traverse

# Example

```nix
concatMapNestedAttrs' (nestedAttrsOf (submodule { options.result = mkOption { type = int; }; }))
(path: v: { ${concatStringsSep "." path} = v.result; })
{ a.b = { result = 1; }; a.c = { result = 2; }; }
# => { "a.b" = 1; "a.c" = 2; }
```
*/
concatMapNestedAttrs' = concatMapNestedAttrsWith lib.any;
}
11 changes: 11 additions & 0 deletions lib/contracts/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# set of utilities for using contracts.
# templates of individual contracts are stored in `./templates`.
{ lib, ... }:
let
callLibs = file: import file { inherit lib; };
in
{
module = ./module.nix;
definitionType = callLibs ./definition-type.nix;
}
// callLibs ./helpers.nix
Loading
Loading