Rename NIP-FI policy mutation gate

Co-authored-by: Honor <e0bf479eeebe47dde1e35705212a4df70616a7bc834d907ddfb18cf0405f8b1b@seiler-test.communities.public.builderlabstaging.xyz>
Signed-off-by: Brad Seiler <seiler@squareup.com>
This commit is contained in:
Brad Seiler
2026-11-06 12:34:19 -05:00
parent a024ea8b69
commit 1e930a3f9b
8 changed files with 30 additions and 39 deletions

View File

@@ -472,7 +472,7 @@ RUST_LOG=buzz_relay=debug,buzz_datastore=info,buzz_db=debug,buzz_auth=debug,buzz
# Permit community FI policy enables and disables only after the whole fleet
# supports policy management. A closed gate preserves receipt recovery/status.
BUZZ_NIP_FI_UPGRADES_ENABLED=false
BUZZ_NIP_FI_CHANGES=false
# NIP-CL compact membership discovery. Deploy fleet-wide dark before enabling.
BUZZ_MEMBERSHIP_SNAPSHOT_ENABLED=false

View File

@@ -1284,7 +1284,7 @@ pub async fn enable_community_nip_fi(
let host = normalize_candidate_host(&request.expected_host).map_err(|_| invalid())?;
let actor = validate_pubkey_hex(&request.actor_pubkey).ok_or_else(invalid)?;
let owner = validate_pubkey_hex(&request.expected_owner_pubkey).ok_or_else(invalid)?;
let trust = if state.config.nip_fi_upgrades_enabled {
let trust = if state.config.nip_fi_changes {
upgrade_configured_trust(&state, &host, &request.expected_issuer)
} else {
Err("upgrades_disabled")
@@ -3164,9 +3164,8 @@ mod postgres_tests {
.nip_fi
.registry
.insert(issuer_policy);
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_upgrades_enabled =
false;
assert!(!state.config.nip_fi_upgrades_enabled);
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_changes = false;
assert!(!state.config.nip_fi_changes);
assert_eq!(
provision_community(state.clone(), &operator, &host, &owner)
@@ -3237,7 +3236,7 @@ mod postgres_tests {
.await
.unwrap()
.is_none());
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_upgrades_enabled = true;
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_changes = true;
let response =
fresh_upgrade_request(state.clone(), &operator, "POST", path, Some(body.clone())).await;
assert_eq!(response.status(), StatusCode::OK);
@@ -3313,8 +3312,7 @@ mod postgres_tests {
.policy_generation,
1
);
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_upgrades_enabled =
false;
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_changes = false;
let while_off =
fresh_upgrade_request(state.clone(), &operator, "POST", path, Some(body.clone())).await;
assert_eq!(while_off.status(), StatusCode::OK);
@@ -3358,7 +3356,7 @@ mod postgres_tests {
.await
.unwrap()
.is_none());
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_upgrades_enabled = true;
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_changes = true;
// Exercise the real HTTP-to-config-to-transaction seam: no raw trust
// precondition can bypass the deployment's configured issuer selection.

View File

@@ -112,7 +112,7 @@ pub async fn set_community_nip_fi_policy(
expected_issuer: request.expected_issuer.unwrap_or_default(),
operator_pubkey: operator.to_hex(),
};
let preflight = if !state.config.nip_fi_upgrades_enabled {
let preflight = if !state.config.nip_fi_changes {
Err("upgrades_disabled")
} else if request.requires_nip_fi {
upgrade_configured_trust(&state, &tuple.expected_host, &tuple.expected_issuer)

View File

@@ -29,7 +29,7 @@ mod postgres_tests {
let config = Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config);
config.nip_fi.registry.insert(issuer_policy);
config.nip_fi.mode = buzz_auth::NipFiMode::Shadow;
config.nip_fi_upgrades_enabled = false;
config.nip_fi_changes = false;
assert_eq!(
provision_community(state.clone(), &operator, &host, &owner)
.await
@@ -66,7 +66,7 @@ mod postgres_tests {
.await;
assert_eq!(disabled.status(), StatusCode::SERVICE_UNAVAILABLE);
assert_eq!(read_json(disabled).await["code"], "upgrades_disabled");
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_upgrades_enabled = true;
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_changes = true;
let mut history = Vec::new();
for (generation, desired) in [(1, true), (2, false), (3, true)] {
body["operation_id"] = Uuid::new_v4().to_string().into();
@@ -175,8 +175,7 @@ mod postgres_tests {
)
.await;
assert_eq!(read_json(response).await["code"], "idempotency_conflict");
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_upgrades_enabled =
false;
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_changes = false;
let response = fresh_upgrade_request(
state.clone(),
&operator,
@@ -197,7 +196,7 @@ mod postgres_tests {
assert_eq!(current["effective_required"], mode.restricts());
}
// Enforce remains a global floor even after a stored disable.
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_upgrades_enabled = true;
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_changes = true;
body["operation_id"] = Uuid::new_v4().to_string().into();
body["requires_nip_fi"] = false.into();
body["expected_policy_generation"] = 3.into();

View File

@@ -409,8 +409,8 @@ pub struct Config {
pub nip_fi_provision_binding: Option<buzz_auth::ProvisionBindingPolicy>,
/// Permit new community FI policy enables and disables. Default off; receipt
/// recovery/status remain available (`BUZZ_NIP_FI_UPGRADES_ENABLED`).
pub nip_fi_upgrades_enabled: bool,
/// recovery/status remain available (`BUZZ_NIP_FI_CHANGES`).
pub nip_fi_changes: bool,
/// Database resolver bounds (`BUZZ_NIP_FI_RESOLVER_*`,
/// `BUZZ_NIP_FI_SNAPSHOT_*`). Validated in every mode, so a bad value
@@ -1508,7 +1508,7 @@ impl Config {
web_dir,
serve_git_web_gui,
nip_fi_provision_binding,
nip_fi_upgrades_enabled: parse_bool("BUZZ_NIP_FI_UPGRADES_ENABLED", false)?,
nip_fi_changes: parse_bool("BUZZ_NIP_FI_CHANGES", false)?,
nip_fi_resolver_bounds,
nip_fi,
})
@@ -1582,33 +1582,27 @@ mod tests {
}
#[test]
fn nip_fi_upgrade_acceptance_is_explicitly_default_off() {
fn nip_fi_changes_are_explicitly_default_off() {
let (_fi, _cfg) = env_guards();
let before = std::env::var("BUZZ_NIP_FI_UPGRADES_ENABLED").ok();
std::env::remove_var("BUZZ_NIP_FI_UPGRADES_ENABLED");
assert!(!Config::from_env().unwrap().nip_fi_upgrades_enabled);
let before = std::env::var("BUZZ_NIP_FI_CHANGES").ok();
std::env::remove_var("BUZZ_NIP_FI_CHANGES");
assert!(!Config::from_env().unwrap().nip_fi_changes);
for value in ["true", "TRUE", "1", "on", " On "] {
std::env::set_var("BUZZ_NIP_FI_UPGRADES_ENABLED", value);
assert!(
Config::from_env().unwrap().nip_fi_upgrades_enabled,
"{value}"
);
std::env::set_var("BUZZ_NIP_FI_CHANGES", value);
assert!(Config::from_env().unwrap().nip_fi_changes, "{value}");
}
for value in ["false", "FALSE", "0", "off", "", " Off "] {
std::env::set_var("BUZZ_NIP_FI_UPGRADES_ENABLED", value);
assert!(
!Config::from_env().unwrap().nip_fi_upgrades_enabled,
"{value}"
);
std::env::set_var("BUZZ_NIP_FI_CHANGES", value);
assert!(!Config::from_env().unwrap().nip_fi_changes, "{value}");
}
std::env::set_var("BUZZ_NIP_FI_UPGRADES_ENABLED", "treu");
std::env::set_var("BUZZ_NIP_FI_CHANGES", "treu");
assert!(
matches!(Config::from_env(), Err(ConfigError::InvalidValue(message))
if message.contains("BUZZ_NIP_FI_UPGRADES_ENABLED must be true or false"))
if message.contains("BUZZ_NIP_FI_CHANGES must be true or false"))
);
match before {
Some(value) => std::env::set_var("BUZZ_NIP_FI_UPGRADES_ENABLED", value),
None => std::env::remove_var("BUZZ_NIP_FI_UPGRADES_ENABLED"),
Some(value) => std::env::set_var("BUZZ_NIP_FI_CHANGES", value),
None => std::env::remove_var("BUZZ_NIP_FI_CHANGES"),
}
}

View File

@@ -121,7 +121,7 @@ mod postgres_tests {
let mut configured = (*base).clone();
let config = Arc::make_mut(&mut configured.config);
config.nip_fi.mode = buzz_auth::NipFiMode::Shadow;
config.nip_fi_upgrades_enabled = false;
config.nip_fi_changes = false;
config.nip_fi.max_connection_lifetime_secs = 3600;
config.nip_fi.communities = crate::nip_fi_config::NipFiCommunities::for_test_any_host(
TEST_COMMUNITY_URI,

View File

@@ -86,7 +86,7 @@ Migration 0084 adds desired action and nullable CAS preconditions to existing
receipts. True/default and null preconditions preserve old receipt meaning and
old INSERT compatibility. No data backfill, trigger, function or foreign key is
added. Apply schema first, then compatible management writers across the **whole
fleet** with `BUZZ_NIP_FI_UPGRADES_ENABLED=false`, then explicitly enable mutations.
fleet** with `BUZZ_NIP_FI_CHANGES=false`, then explicitly enable mutations.
The gate controls both directions; reads and committed replay remain available.
Do not roll back to an unfenced management writer after the first general operation.

View File

@@ -6,7 +6,7 @@ configured `RELAY_OPERATOR_API_ORIGIN` and an allowlisted operator signing key
in a protected file. It sends one signed request and never retries automatically.
Before changing policy, apply the schema and roll compatible management writers
across the whole fleet with `BUZZ_NIP_FI_UPGRADES_ENABLED=false`. Then explicitly
across the whole fleet with `BUZZ_NIP_FI_CHANGES=false`. Then explicitly
enable that gate, which controls both enable and disable. Enable additionally
requires the deployment-selected issuer to be trusted and already bound to the
community. The active community must have exactly one owner; the authenticated