mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-11-12 19:23:55 +01:00
fix(config): runtime-safe PUT no longer drags pending restart-required changes live
A runtime-safe config PUT rebuilt the effective config from config_base + the FULL persisted overlay and stored it, which live-applied any restart-required section persisted by an earlier PUT (features/relay/ premium) while the boot-built clients still ran the old value — and cleared restart_pending in the process. The exact desync RuntimeClass exists to prevent. Now the hot-swap applies ONLY the (all-runtime-safe) patch onto the currently-running config, and restart_pending is computed honestly via a shared helper (GET and PUT agree). Also reclassify registration. require_payment as restart-required: it gates the boot-built payment client (None unless require_payment||premium at boot), so hot-enabling it would 500 every registration until restart. Regression tests for both.
This commit is contained in:
@@ -1623,6 +1623,43 @@ mod tests {
|
||||
assert_eq!(applied.registration.gate_mode, GateMode::Any);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_safe_put_does_not_drag_pending_restart_required_change_live() {
|
||||
// Reproduces the hot-swap desync: an earlier PUT persists a restart-required
|
||||
// change (chain toggle) that is NOT yet applied to the running config; a later
|
||||
// runtime-safe PUT must hot-swap ONLY its own change onto the RUNNING config,
|
||||
// never rebuild from the full persisted overlay (which would drag the pending
|
||||
// restart-required change live while boot-built clients still run the old one).
|
||||
use crate::config_overlay::SettingsOverlay;
|
||||
let base = valid();
|
||||
assert!(!base.features.chains.btc, "precondition: btc off at boot");
|
||||
let running = base.clone(); // boot state; the restart-required change is NOT here
|
||||
|
||||
// The FIX: hot-swap applies only the runtime-safe patch to the running config.
|
||||
let safe_patch: SettingsOverlay =
|
||||
serde_json::from_str(r#"{"landing":{"title":"hello"}}"#).unwrap();
|
||||
let hot = running.apply_overlay(&safe_patch).unwrap();
|
||||
assert!(
|
||||
!hot.features.chains.btc,
|
||||
"restart-required chain toggle must NOT leak into the runtime-safe hot-swap"
|
||||
);
|
||||
assert_eq!(
|
||||
hot.landing.title.as_deref(),
|
||||
Some("hello"),
|
||||
"runtime-safe change applies"
|
||||
);
|
||||
|
||||
// Regression guard: the OLD path (base + full persisted overlay) WOULD drag it.
|
||||
let buggy_merged: SettingsOverlay =
|
||||
serde_json::from_str(r#"{"features":{"chain_btc":true},"landing":{"title":"hello"}}"#)
|
||||
.unwrap();
|
||||
let buggy = base.apply_overlay(&buggy_merged).unwrap();
|
||||
assert!(
|
||||
buggy.features.chains.btc,
|
||||
"sanity: the old rebuild-from-full-overlay path drags the pending change"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlay_enabling_pow_without_hmac_key_is_rejected() {
|
||||
// Fail-closed: turning the PoW gate on via the console with no HMAC key
|
||||
|
||||
Reference in New Issue
Block a user