Invariants
An invariant is a rule about the whole fleet: “no server runs a desktop”, “no two hosts share an address”.
yen.invariants.<name> gives Datalog rules over the fleet’s facts. A true bad: atom is a violation.
{ yen, lib, ... }:{ yen.kinds.fleet = { spawns.host = _: yen.host; sinks.out.reduce = yen.lib.reduce.merge; }; yen.kinds.host.fields = { addr = lib.types.str; form = lib.types.enum [ "server" "laptop" ]; };
yen.fleet.main = { }; yen.host.web = { addr = "10.0.0.1"; form = "server"; }; yen.host.db = { addr = "10.0.0.2"; form = "server"; }; yen.host.thinkpad = { addr = "10.0.0.50"; form = "laptop"; }; yen.aspects.desktop.at = s: (s.form or null) == "laptop"; yen.aspects.postgres.at = yen.host.db; yen.flake = { out, ... }: out;}# Rules over `r.facts`. A true `bad:` atom is a violation. Facts: kind:K:ID, has:ASPECT:ID, val:FIELD:ID:VALUE, …{ yen, lib, ... }:{ # no server runs a desktop yen.invariants.no-desktop-on-servers = { scopes, ... }: map (s: { head = "bad:desktop-on-server:${s.name}"; pos = [ "has:desktop:${s.id}" ]; }) (lib.filter (s: s.kind == "host" && s.form == "server") scopes); # no two hosts share an address: a rule across machines yen.invariants.unique-addr = { scopes, ... }: let hosts = lib.filter (s: s.kind == "host") scopes; in lib.concatMap ( a: map (b: { head = "bad:same-addr:${a.name}:${b.name}"; pos = [ "val:addr:${a.id}:${a.addr}" "val:addr:${b.id}:${a.addr}" ]; }) (lib.filter (b: a.name < b.name) hosts) ) hosts;}The function gets scopes, graph, active and facts. Facts are ground atoms:
| Fact | Holds when |
|---|---|
scope:ID, kind:K:ID | a scope exists, and its kind |
has:ASPECT:ID | the aspect is active at the scope |
edge:SINK:FROM:TO | an element is routed |
val:FIELD:ID:VALUE | a field has a scalar value |
ref:FIELD:ID:KIND:NAME | a field refers to an instance |
error:CODE:FROM | an error record |
This fleet holds:
{ incomplete = [ ]; invariants = [ "no-desktop-on-servers" "unique-addr" ]; ok = true; violations = [ ];}A change that breaks two rules
Section titled “A change that breaks two rules”# A desktop on a server, and a new host with a copied address.{ yen, ... }:{ yen.aspects.desktop.at = yen.host.web; yen.host.spare = { addr = "10.0.0.2"; form = "server"; };}{ ok = false; violations = [ "bad:desktop-on-server:web" "bad:same-addr:db:spare" ];}r.check.why atom gives the rules that prove a violation. whyNot and proof work as in Datalog:
[ { head = "bad:same-addr:db:spare"; pos = [ "val:addr:fleet:main/host:db:10.0.0.2" "val:addr:fleet:main/host:spare:10.0.0.2" ]; }]