Skip to content

Impact, without, and time

Three questions an agent asks before a change: what else does this touch, what if this were gone, and what holds on a given day.

modules/fleet.nix
# fleet > environment > host. The fleet says which day it is: pure evaluation has no clock.
{ yen, lib, ... }:
let
inherit (yen.lib) same maybe;
in
{
yen.kinds.fleet = {
fields.today = lib.types.str;
spawns.environment = _: yen.environment;
sinks.out.reduce = yen.lib.reduce.merge;
sinks.dns.out =
{ dns, ... }:
{
inherit dns;
};
};
yen.kinds.environment = {
spawns.host =
s: lib.filterAttrs (_: h: same h.environment s && yen.lib.validAt yen.fleet.main.today h) yen.host;
sinks.backends.dns =
{ environment, backends, ... }:
{
${environment.name} = backends;
};
};
yen.kinds.host.fields = {
environment = yen.kinds.environment;
validFrom = maybe lib.types.str; # null: open, valid since always
validTo = maybe lib.types.str; # null: open, valid until always
};
yen.fleet.main.today = "2026-11-15";
yen.environment.prod = { };
yen.host.web-old = {
environment = yen.environment.prod;
validTo = "2026-10-31";
}; # retired
yen.host.web = {
environment = yen.environment.prod;
validFrom = "2026-10-01";
};
yen.host.web-next = {
environment = yen.environment.prod;
validFrom = "2026-12-01";
}; # not yet
yen.flake = { out, ... }: out;
}
modules/features/backend.nix
{ yen, ... }:
{
yen.aspects.backend = {
at = yen.kinds.host;
backends = { host, ... }: host.name;
};
}

r.impact id { hops; sinks; max; } follows routed elements from a scope, by depth. web gives a backend to its environment, and the environment gives DNS to the fleet:

result
{
hops = 3;
reached = [
{
depth = 1;
scope = "fleet:main/environment:prod";
via = "backends";
}
{
depth = 2;
scope = "fleet:main";
via = "dns";
}
];
truncated = {
of = 2;
shown = 2;
};
}

yen.lib.without { files; path; args; } evaluates the fleet without the files that declare path. A whole file goes: there is no hook to remove part of one.

result
{
dns = null;
dropped = [
"backend.nix"
];
errors = [ ];
}

Pure evaluation has no clock, so the date is data. An instance with validFrom and validTo holds at the date the caller passes: yen.lib.validAt date scope. Here the environment spawns only the hosts valid today, so web-old and web-next have no scope:

result
[
"fleet:main"
"fleet:main/environment:prod"
"fleet:main/environment:prod/host:web"
]

yen.lib.asOf { url; rev; module; } evaluates the fleet at a git revision. yen.lib.history { url; revs; module; } lists each revision with the field changes from the one before (diffData over r.data). The engine’s own tests run both on this repository: see tests/default.nix.

Contribute Community Sponsor