types: let nix own the reserved-name blacklist
One list, in nix/reserved-names.nix, handed to everything that needs it as HIVE_RESERVED_NAMES. Keeping it current becomes a config change rather than a rebuild, and hive names and agent names -- one namespace going forward -- are checked against the same file: swarm-otel.nix's hand-written reservedOwners is gone. Whitespace-separated rather than JSON, deliberately, unlike the structured env vars beside it. Every entry is an Ident ([a-z0-9-]), so whitespace cannot occur inside a name and the encoding is provably lossless; JSON would mean either a parser dependency in a crate whose purpose is to have none, or a copy of the parse in every consumer. An UNSET variable is not "nothing is reserved". Both creation sites log an error and return a warning saying the check did not run, so a misconfigured deployment says so instead of silently accepting every name. A blank value folds into unset: nix always renders a non-empty list, so present-but-empty is a rendering fault, not a declaration. Two guards whose subject moved out of their own file now assert their own case is still in it, because a guard that can be retired by an edit elsewhere is not a guard: - swarm-otel.nix asserts reserved-names.nix still contains its swarmTierName. - hive-sh4re's sentinel drift test PANICS when the variable is missing rather than skipping -- a drift test that quietly does nothing still reports green. checks.nix and devshell.nix both export it so CI and a local cargo test agree. Verified as a pair: with the variable set, 8 tests pass; with it unset, exactly the 4 drift tests fail and the unrelated ones still pass.
This commit is contained in:
parent
7bb68fe819
commit
27932ec631
10 changed files with 326 additions and 87 deletions
|
|
@ -143,6 +143,21 @@ in
|
|||
# mandatory, so this is unconditional (the whole env block is already
|
||||
# gated on hyperhive being enabled). See `docs/gateway.md::HIVE_FORGE_URL`.
|
||||
HIVE_FORGE_URL = "http://${config.services.hyperhive.swarm.forge.domain}";
|
||||
|
||||
# The one blacklist of names an agent may not take — see
|
||||
# `nix/reserved-names.nix`, which is also read by the swarm controller, by
|
||||
# the swarm collector's owner assertion, and by the test suite. Nix owns it
|
||||
# so that keeping it current is a config change, not a rebuild of a binary.
|
||||
#
|
||||
# Whitespace-separated rather than JSON: every entry is an `Ident`
|
||||
# (`[a-z0-9-]`), so a space can never occur inside a name and the encoding
|
||||
# cannot be lossy. Spelling it as JSON would put a parser in the crate whose
|
||||
# whole point is to have no dependencies.
|
||||
#
|
||||
# Unconditional on purpose. The consumer treats an ABSENT variable as "I was
|
||||
# never told" and says so out loud, which is the correct reading — but it is
|
||||
# a reading no correctly-built hive should ever have to make.
|
||||
HIVE_RESERVED_NAMES = lib.concatStringsSep " " (import ../../reserved-names.nix);
|
||||
}
|
||||
//
|
||||
lib.optionalAttrs
|
||||
|
|
|
|||
|
|
@ -648,6 +648,13 @@ in
|
|||
# `swarm.peerHives`, `swarm.hives` minus this hive) rather than
|
||||
# peers-minus-self. Consumed by `GET /api/hives`
|
||||
# (swarm-controller/src/main.rs::load_hives).
|
||||
# The one blacklist of names an agent may not take, shared verbatim
|
||||
# with hive-c0re and with the collector's owner assertion — see
|
||||
# `nix/reserved-names.nix`. Whitespace-separated rather than JSON
|
||||
# like its neighbour below, because every entry is an `Ident` and so
|
||||
# cannot contain a space; the neighbour carries objects and has no
|
||||
# such option.
|
||||
HIVE_RESERVED_NAMES = lib.concatStringsSep " " (import ../reserved-names.nix);
|
||||
SWARM_CONTROLLER_HIVES = builtins.toJSON (
|
||||
lib.mapAttrsToList (name: h: {
|
||||
inherit name;
|
||||
|
|
|
|||
|
|
@ -55,9 +55,16 @@ let
|
|||
# repeated at each site would let the guard and the config drift apart, which
|
||||
# is the failure this guard exists to prevent.
|
||||
swarmTierName = "swarm";
|
||||
# Every `<owner>` this module claims for itself. One entry today; a second
|
||||
# swarm-tier pipeline would be added here and inherit the check for free.
|
||||
reservedOwners = [ swarmTierName ];
|
||||
# Every `<owner>` no hive may take. Read from `nix/reserved-names.nix`, the
|
||||
# same file the daemons are handed as `HIVE_RESERVED_NAMES`, because agent
|
||||
# names and hive names are ONE namespace going forward — a locally-owned
|
||||
# list here would be a second copy to keep in step, which is the failure a
|
||||
# single blacklist exists to prevent.
|
||||
#
|
||||
# The assertion below still checks the string this module emits: the file
|
||||
# is asserted to CONTAIN `swarmTierName`, so a rename that dropped it from
|
||||
# the file would be an eval error rather than a silently missing guard.
|
||||
reservedOwners = import ../reserved-names.nix;
|
||||
|
||||
# A published target is declared as ONE url, because that url is also the
|
||||
# audience its token is minted for — but prometheus wants the same fact in
|
||||
|
|
@ -581,6 +588,23 @@ in
|
|||
List the swarm's hives.
|
||||
'';
|
||||
}
|
||||
{
|
||||
# The blacklist now lives in a shared file, so this module no longer
|
||||
# controls its contents — and a guard whose subject can be edited
|
||||
# elsewhere has to assert that its own case is still in there. Without
|
||||
# this, deleting one line from `reserved-names.nix` would silently
|
||||
# retire the check below rather than fail anything.
|
||||
assertion = lib.elem swarmTierName reservedOwners;
|
||||
message = ''
|
||||
nix/reserved-names.nix no longer contains '${swarmTierName}', which
|
||||
the swarm collector needs reserved: it names components
|
||||
`<kind>/<owner>` and uses the hive name as the owner, so a hive
|
||||
called '${swarmTierName}' would replace the swarm tier's own
|
||||
pipelines and lose its own.
|
||||
|
||||
Put it back, or give this module a different swarmTierName.
|
||||
'';
|
||||
}
|
||||
{
|
||||
# A hive whose name is one this module claims for itself collides in
|
||||
# the collector's component namespace, and `//` resolves it silently:
|
||||
|
|
|
|||
Loading…
Reference in a new issue