Skip to content

Stage a change

Before you apply a change, stage it. yen.lib.stage { base; change; rejected; grant; } evaluates the fleet without and with the change. Nothing is applied. ok is true only when nothing new is wrong.

The fleet is the one from Invariants. A change is plain data, so an agent can write it as JSON:

changes/desktop-on-web.json
{ "yen": { "aspects": { "desktop": { "at": [ { "kind": "host", "name": "web" } ] } } } }
stage.nix
# Stage a change file against the fleet. Nothing is applied.
# nix eval --impure --json --file stage.nix --argstr change changes/desktop-on-web.json
{
change,
grant ? null, # where the change may give elements: yen.lib.grant { scopes = [ … ]; }
rejected ? [ ], # sha256 of changes refused before
yen ? builtins.fetchTarball "https://github.com/vic/yen/archive/main.tar.gz",
nixpkgs ? builtins.fetchTarball "https://nixos.org/channels/nixos-25.05/nixexprs.tar.xz",
import-tree ? import (
builtins.fetchTarball "https://github.com/denful/import-tree/archive/main.tar.gz"
),
}:
let
base = {
imports = [ (import-tree ./modules) ];
_module.args = {
lib = import "${nixpkgs}/lib";
};
};
s = (import yen).lib.stage {
inherit base grant rejected;
change = builtins.fromJSON (builtins.readFile (./. + "/${change}"));
};
in
removeAttrs s [ "render" ]
Terminal window
nix eval --impure --json --file stage.nix --argstr change changes/desktop-on-web.json
result
{
diff = {
active = {
added = [
"desktop@fleet:main/host:web"
];
removed = [ ];
};
edges = {
added = [ ];
removed = [ ];
};
scopes = {
added = [ ];
removed = [ ];
};
};
errors = [ ];
ok = false;
refused = [ ];
sha = "a1969acc86001f4528de7a4ec227f9cf7a5af809fd2555b04adeed0dd6c04146";
violations = [
"bad:desktop-on-server:web"
];
}

The change makes desktop active at the server web, and breaks an invariant. The answer has the violation, the diff (scopes, routed elements, active aspects) and the change’s sha.

yen.lib.grant { scopes = [ id prefixes ]; sinks ? null; } says where a change may give elements. narrow g h keeps what both allow. No function makes a grant wider. An element outside the grant is refused, with class = "authority": no edit by this author can fix it.

changes/postgres-on-thinkpad.json
{ "yen": { "aspects": { "postgres": { "at": [ { "kind": "host", "name": "thinkpad" } ] } } } }
result
[
{
class = "authority";
code = "outside-grant";
error = "the change gives services to fleet:main/host:thinkpad, outside the grant";
from = "fleet:main/host:thinkpad";
got = {
from = "fleet:main/host:thinkpad";
origin = "aspect postgres";
sink = "services";
to = "fleet:main/host:thinkpad";
};
path = [ ];
}
]

Pass the sha of changes refused before in rejected. The same change is refused again, whoever sends it:

result
[
{
class = "authority";
code = "rejected";
error = "this change was rejected before";
from = "stage";
got = "a1969acc86001f4528de7a4ec227f9cf7a5af809fd2555b04adeed0dd6c04146";
path = [ ];
}
]
Contribute Community Sponsor