diff --git a/.env.example b/.env.example index ba4c9cf230..98916ec279 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/crates/buzz-relay/src/api/operator.rs b/crates/buzz-relay/src/api/operator.rs index 8672aed591..9af82c6d92 100644 --- a/crates/buzz-relay/src/api/operator.rs +++ b/crates/buzz-relay/src/api/operator.rs @@ -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. diff --git a/crates/buzz-relay/src/api/operator/community_policy.rs b/crates/buzz-relay/src/api/operator/community_policy.rs index 7e6c61d5be..622aec2b3e 100644 --- a/crates/buzz-relay/src/api/operator/community_policy.rs +++ b/crates/buzz-relay/src/api/operator/community_policy.rs @@ -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) diff --git a/crates/buzz-relay/src/api/operator/postgres_tests/policy_tests.rs b/crates/buzz-relay/src/api/operator/postgres_tests/policy_tests.rs index 5c06953a00..19b1e0823d 100644 --- a/crates/buzz-relay/src/api/operator/postgres_tests/policy_tests.rs +++ b/crates/buzz-relay/src/api/operator/postgres_tests/policy_tests.rs @@ -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(); diff --git a/crates/buzz-relay/src/config.rs b/crates/buzz-relay/src/config.rs index fc0ea1e62c..3c2deba25f 100644 --- a/crates/buzz-relay/src/config.rs +++ b/crates/buzz-relay/src/config.rs @@ -409,8 +409,8 @@ pub struct Config { pub nip_fi_provision_binding: Option, /// 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"), } } diff --git a/crates/buzz-relay/src/router/tests/policy_sweep_postgres_tests.rs b/crates/buzz-relay/src/router/tests/policy_sweep_postgres_tests.rs index 433c1f8164..5c3426d376 100644 --- a/crates/buzz-relay/src/router/tests/policy_sweep_postgres_tests.rs +++ b/crates/buzz-relay/src/router/tests/policy_sweep_postgres_tests.rs @@ -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, diff --git a/docs/community-auth-upgrades.md b/docs/community-auth-upgrades.md index 751fc536da..602d389b68 100644 --- a/docs/community-auth-upgrades.md +++ b/docs/community-auth-upgrades.md @@ -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. diff --git a/docs/operator-nip-fi-upgrade.md b/docs/operator-nip-fi-upgrade.md index a13edd7bd1..1ef4d1bad2 100644 --- a/docs/operator-nip-fi-upgrade.md +++ b/docs/operator-nip-fi-upgrade.md @@ -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