skip to content

Stable Identity for Everything

A fleet declares its machines as hosts. Each host has a system with a default and a role, and gen needs a way to tell two hosts apart that does not depend on where or when they were evaluated.

A kind declares what a host looks like. A registry holds the hosts. Each host it holds gets its defaults filled in and an identity, id_hash, computed from its definition.

let
gen = inputs.gen.lib.mkGenLibs { };
m = gen.merge;
schema = gen.schema.evalSchema { } [
{
config.schema.host = {
options.system = m.mkOption { type = m.types.str; default = "x86_64-linux"; };
options.role = m.mkOption { type = m.types.str; };
};
}
];
fleet = m.evalModuleTree { } [
{
options.hosts = gen.schema.mkInstanceRegistry { } schema.host;
config.hosts.web-1.role = "webserver";
config.hosts.db-1.role = "database";
}
];
in
{
web = fleet.config.hosts.web-1.id_hash;
db = fleet.config.hosts.db-1.id_hash;
dbSystem = fleet.config.hosts.db-1.system;
}
{ db = "host:edc7c2e86f31825b78559ec082722706ad14e07903785c2e3ae52bf3d2b64333";
dbSystem = "x86_64-linux";
web = "host:89c7faee4c1b03a58bf63e504be108df3b4d90818f487a2614eec179b00ca2c8"; }

An identity is a hash over the kind name and the values of the kind’s declared options, the host’s name among them. Evaluate the same definition twice, on any machine, and you get the same identity. Change a host’s role and its identity changes with it.

One function computes every identity in gen, hashIdentity. You can call it directly:

gen.identity.hashIdentity "host" [ "name" ] (label: { name = "db-1"; }.${label})
# => "host:f7667c0783634b1119fef6262e89bfb5cd0b3628948abf41c2f1291ba75e6735" on every call

A relation that carries data of its own becomes a node too, called a binding node. A user’s account on a host has a shell, so it is an account node related to the user and to the host.

let
gen = inputs.gen.lib.mkGenLibs { };
minted = gen.scope.mintStrata { } [
{ pass = 0; identifier = "web-1"; kind = "host"; relata = { }; content = { }; site = "web-1"; }
{ pass = 0; identifier = "deploy"; kind = "user"; relata = { }; content = { }; site = "deploy"; }
{ pass = 1; identifier = "account:deploy:web-1"; kind = "account";
relata = { user = "deploy"; host = "web-1"; }; content = { shell = "bash"; }; site = "account"; }
];
in
{ inherit (minted) edges strata; }
{ edges = [ { from = "account:deploy:web-1"; label = "host"; to = "web-1"; }
{ from = "account:deploy:web-1"; label = "user"; to = "deploy"; } ];
strata = 2; }

The account’s identity is a hash over its kind, its identifier and the identities of the nodes it relates, never their names. Those nodes must therefore be identified first, in an earlier pass. An entry that relates a node from its own pass fails with an error. gen also turns each relatum into a labelled edge, so the account appears in graph queries like any other node.

  • Reuse. Equal definitions have equal identities, so an identity works as a cache key: a result computed for one identity is valid wherever that identity appears, in this evaluation or the next. See Rebuilding Only What Changed.
  • Error messages. Nix’s own errors point at a line of source code. An error from gen can also name the part of the configuration it concerns, such as the option that got a wrong value (An Enhanced Module Type System) or the source of a bad argument (Automatic Argument Injection).
  • Introspection. Tools can ask about a node by its identity: what it relates to, and why an edge exists. Inspection shows the queries gen supports today. Interactive graph tools are on the Roadmap.

An intensional identity comes from how a thing is defined. Two values built the same way compare equal, even when one of them is a function, which Nix cannot compare. Palmer et al. describe functions that carry a name and their captured data so they can be identified, inspected and compared. gen applies the idea to every node, and to functions built from a named constructor and its arguments. See our reading of Palmer et al. (2024).

Next: An Enhanced Module Type System.

palette
dark
light
↑↓ select↵ apply esc close

Palettes adapted from Catppuccin (Macchiato) (MIT), Tokyo Night (Apache-2.0), gruvbox (MIT), Catppuccin (Latte) (MIT), Rosé Pine (Dawn) (MIT).