Skip to content

Routing and walls

An aspect does not say who receives its elements. The tree does. An element goes to the nearest ancestor-or-self scope that has the sink, and where the sink’s at holds.

This example has a fleet, two environments, two hosts, a user and a home:

modules/fleet.nix
# fleet > environment > host > user and home. Who receives an element depends on where the sinks are.
{ yen, lib, ... }:
let
inherit (yen.lib) same opt;
under =
field: kind: s:
lib.filterAttrs (_: x: same x.${field} s) kind;
in
{
yen.kinds.fleet = {
spawns.environment = _: yen.environment;
sinks.out.reduce = yen.lib.reduce.merge;
sinks.dns.out =
{ dns, ... }:
{
dns.fleet = dns;
}; # the fallback receiver
};
yen.kinds.environment = {
fields.public = opt lib.types.bool false;
spawns.host = under "environment" yen.host;
sinks.dns = {
at = s: s.public; # a conditional receiver: only public environments
out =
{ environment, dns, ... }:
{
dns.${environment.name} = dns;
};
};
};
yen.kinds.host = {
fields.environment = yen.kinds.environment;
spawns = {
user = under "on" yen.user;
home = under "on" yen.home;
};
sinks.os.out =
{ host, os, ... }:
{
os.${host.name} = os;
};
};
yen.kinds.user.fields.on = yen.kinds.host;
yen.kinds.home = {
fields.on = yen.kinds.host;
sinks.os.out =
{ home, os, ... }:
{
os.${home.name} = os;
}; # a home builds its own config
};
yen.fleet.main = { };
yen.flake = { out, ... }: out;
}
modules/instances.nix
{ yen, ... }:
{
yen.environment.prod.public = true;
yen.environment.lab = { };
yen.host.web.environment = yen.environment.prod;
yen.host.box.environment = yen.environment.lab;
yen.user.alice.on = yen.host.web;
yen.home.bob.on = yen.host.web;
}
modules/aspects.nix
{ yen, ... }:
{
yen.aspects.accounts = {
at = [
yen.kinds.user
yen.kinds.home
];
os = { scope, ... }: "account ${scope.name}";
};
yen.aspects.records = {
at = yen.kinds.host;
dns = { host, ... }: "${host.name} A";
};
}
result
{
dns = {
fleet = [
"box A"
];
prod = [
"web A"
];
};
os = {
bob = [
"account bob"
];
web = [
"account alice"
];
};
packages = {
web = [
"vim for alice"
];
};
}
ElementGiven atReceived atWhy
osuser alicehost weba user has no os sink; the host above has one
oshome bobhome bobthe home has its own os sink: ancestor-or-self
dnshost webenvironment prodprod is public, so its dns sink holds
dnshost boxfleet mainlab is not public; the next receiver is the fleet

The routes, drawn from r.graph.edges:

flowchart BT
  n6c4c773b6d["fleet main"]
  nf631382c77["environment lab"]
  nc33ee814ad["host box"]
  n4a308930d1["environment prod"]
  n4019172c9d["host web"]
  n85c31fb914["home bob"]
  n8bd47cb553["user alice"]
  nc33ee814ad -->|dns| n6c4c773b6d
  n4019172c9d -->|dns| n4a308930d1
  n8bd47cb553 -->|os| n4019172c9d
  n8bd47cb553 -->|packages| n4019172c9d
  n4a308930d1 -->|out| n6c4c773b6d
  n85c31fb914 -->|out| n6c4c773b6d
  n4019172c9d -->|out| n6c4c773b6d

A sink with no keys is a wall. It receives elements and gives nothing on. A home under a host can keep its packages away from the host:

modules/walls.nix
# A sink with no keys is a wall: a home's packages stop at the home and never reach the host.
{ yen, ... }:
{
yen.kinds.host.sinks.packages.out =
{ host, packages, ... }:
{
packages.${host.name} = packages;
};
yen.kinds.home.sinks.packages = { };
yen.aspects.editor = {
at = [
yen.kinds.user
yen.kinds.home
];
packages = { scope, ... }: "vim for ${scope.name}";
};
}

r.reduced.packages shows both receivers. Only the host gives its packages to the flake:

result
{
"fleet:main/environment:prod/host:web" = [
"vim for alice"
];
"fleet:main/environment:prod/host:web/home:bob" = [
"vim for bob"
];
}
Contribute Community Sponsor