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
|
|
@ -137,19 +137,68 @@ mod reserved_name_tests {
|
|||
use super::{
|
||||
CHILDREN_RECIPIENT, MANAGER_AGENT, OPERATOR_RECIPIENT, PARENT_RECIPIENT, SYSTEM_SENDER,
|
||||
};
|
||||
use hive_types::{Ident, is_reserved_name};
|
||||
use hive_types::{Ident, RESERVED_NAMES_ENV, is_reserved_name};
|
||||
|
||||
/// The sentinels declared here and the reserved-name list in
|
||||
/// `hive-types` are two spellings of one fact, in crates that cannot
|
||||
/// import each other's intent. This pins them together: adding a
|
||||
/// sentinel without reserving it now fails here rather than years
|
||||
/// later, when an agent takes the name.
|
||||
/// The blacklist as nix rendered it for this test run.
|
||||
///
|
||||
/// **Panics when the variable is absent, deliberately.** The list lives
|
||||
/// in `nix/reserved-names.nix` now, so this test can no longer read it
|
||||
/// from the Rust tree; `nix/checks.nix` and `nix/devshell.nix` both
|
||||
/// export it. Skipping instead would turn "nobody wired the variable"
|
||||
/// into a green run — the same shape as a checker that reports clean
|
||||
/// because it crashed, and the whole point of a drift test is that it
|
||||
/// is the thing that notices.
|
||||
fn reserved() -> Vec<String> {
|
||||
let raw = hive_types::reserved_names_raw().unwrap_or_else(|| {
|
||||
panic!(
|
||||
"{RESERVED_NAMES_ENV} is unset or blank, so the drift test cannot run. \
|
||||
nix/checks.nix and nix/devshell.nix are supposed to export it from \
|
||||
nix/reserved-names.nix — fix the plumbing rather than this test."
|
||||
)
|
||||
});
|
||||
hive_types::parse_reserved_names(&raw)
|
||||
.into_iter()
|
||||
.map(str::to_owned)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The sentinels declared here and the blacklist nix owns are two
|
||||
/// spellings of one fact, in places that cannot import each other's
|
||||
/// intent. This pins them together: adding a sentinel without reserving
|
||||
/// it now fails here rather than years later, when an agent takes the
|
||||
/// name.
|
||||
#[test]
|
||||
fn ident_shaped_sentinels_are_reserved() {
|
||||
let owned = reserved();
|
||||
let reserved: Vec<&str> = owned.iter().map(String::as_str).collect();
|
||||
for sentinel in [OPERATOR_RECIPIENT, SYSTEM_SENDER] {
|
||||
assert!(
|
||||
is_reserved_name(sentinel),
|
||||
"{sentinel:?} is a sentinel an agent could be named — it must be in RESERVED_NAMES"
|
||||
is_reserved_name(sentinel, &reserved),
|
||||
"{sentinel:?} is a sentinel an agent could be named — it must be in \
|
||||
nix/reserved-names.nix"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Every entry nix hands us must be a name an agent could actually have
|
||||
/// been given. One that `Ident::parse` rejects is dead weight — nothing
|
||||
/// could ever have been created with it, so listing it implies a guard
|
||||
/// doing nothing. `graceful-stop` is what makes this worth asserting: it
|
||||
/// is hyphenated, and a charset tightening would silently retire it.
|
||||
///
|
||||
/// This assertion used to live beside the list in `hive-types`; it moved
|
||||
/// here because here is where the real list is readable.
|
||||
#[test]
|
||||
fn every_reserved_name_is_a_valid_ident() {
|
||||
let owned = reserved();
|
||||
assert!(
|
||||
!owned.is_empty(),
|
||||
"the blacklist rendered empty — an empty list makes every assertion below vacuous"
|
||||
);
|
||||
for name in &owned {
|
||||
assert!(
|
||||
Ident::parse(name).is_ok(),
|
||||
"{name:?} is reserved but not a parseable ident — one of the two is wrong"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -161,12 +210,14 @@ mod reserved_name_tests {
|
|||
/// collision and this test is what notices.
|
||||
#[test]
|
||||
fn bracketed_recipients_cannot_be_agent_names() {
|
||||
let owned = reserved();
|
||||
let reserved: Vec<&str> = owned.iter().map(String::as_str).collect();
|
||||
for sentinel in [PARENT_RECIPIENT, CHILDREN_RECIPIENT] {
|
||||
assert!(
|
||||
Ident::parse(sentinel).is_err(),
|
||||
"{sentinel:?} parses as an ident now — it is reachable as an agent name and must be reserved"
|
||||
);
|
||||
assert!(!is_reserved_name(sentinel));
|
||||
assert!(!is_reserved_name(sentinel, &reserved));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,7 +227,9 @@ mod reserved_name_tests {
|
|||
/// enforced rather than remembered.
|
||||
#[test]
|
||||
fn manager_name_is_taken_not_reserved() {
|
||||
let owned = reserved();
|
||||
let reserved: Vec<&str> = owned.iter().map(String::as_str).collect();
|
||||
assert!(Ident::parse(MANAGER_AGENT).is_ok());
|
||||
assert!(!is_reserved_name(MANAGER_AGENT));
|
||||
assert!(!is_reserved_name(MANAGER_AGENT, &reserved));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue