You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Surfaced during Story 9-7 (Configuration Hot-Reload) code review iter-1, 2026-05-06. Two of Story 9-7's documented v1 limitations share the same root cause and the same eventual fix: certain AppConfig fields are captured by value at construction time into long-lived closures that the runtime cannot reach to mutate. Story 9-7's tokio::sync::watch::Sender<Arc<AppConfig>> propagation channel cannot reach those closures.
Affected v1 limitations
AC#10 downgrade — OPC UA [opcua].stale_threshold_seconds hot-reload affects only the web dashboard. Story 9-7's spec (AC#10) mandates per-NodeId staleness cache flush on threshold change. The OPC UA path captures the threshold into per-variable read-callback closures at src/opc_ua.rs:1017 (frozen at startup). AC#8 forbids modifying src/opc_ua.rs, so 9-7 shipped propagate-only behaviour and renamed the test from stale_threshold_change_flushes_cache (spec) to stale_threshold_change_propagates_to_subscribers (tests/config_hot_reload.rs:453). Spec amendment landed in 9-7 review iter-1.
Credential-rotation v1 limitation — [opcua].user_name / user_password and [web].auth_realm classified as restart-required. The auth middleware captures Arc<WebAuthState> at router-build time via from_fn_with_state(auth_state, basic_auth_middleware); hot-swapping requires changing the middleware signature to State<Arc<AppState>>, which would modify src/web/auth.rs (forbidden by AC#8). Documented in 9-7 dev-notes Debug Log.
Pattern: live-borrow refactor
Both limitations dissolve once the closure-capture pattern is replaced with a live-borrow pattern that reaches AppState (or an equivalent Arc<RwLock> / Arc<ArcSwap>):
Web auth middleware: refactor from_fn_with_state(auth_state, basic_auth_middleware) → from_fn_with_state(app_state, basic_auth_middleware); middleware reads state.auth.load() per request.
OPC UA read callbacks: refactor add_variables(...) to install a callback that captures Arc<AppState> (or a dedicated Arc<ArcSwap<AppConfig>>) and reads stale_threshold_seconds per call rather than baking it in.
Acceptance criteria (high level)
[opcua].stale_threshold_seconds change is observed by every OPC UA Read on the next call (no cache; or explicit cache flush hook).
[opcua].user_name/user_password and [web].auth_realm rotation succeeds without process restart; existing in-flight requests authenticate against the OLD credentials and complete normally; new requests use NEW credentials.
Story 9-7's classifier (src/config_reload.rs::classify_diff) upgrades these knobs from `restart-required` to `hot-reload-safe` (or `auth-rotating` per the spec § Knob Taxonomy).
AC#8 file invariants for files OTHER than src/web/auth.rs and src/opc_ua.rs continue to hold.
3 new integration tests covering the hot-reload paths.
References
Story 9-7: _bmad-output/implementation-artifacts/9-7-configuration-hot-reload.md § Dev Notes Debug Log + Review Findings (decision-needed Implement storage #1)
Context
Surfaced during Story 9-7 (Configuration Hot-Reload) code review iter-1, 2026-05-06. Two of Story 9-7's documented v1 limitations share the same root cause and the same eventual fix: certain
AppConfigfields are captured by value at construction time into long-lived closures that the runtime cannot reach to mutate. Story 9-7'stokio::sync::watch::Sender<Arc<AppConfig>>propagation channel cannot reach those closures.Affected v1 limitations
AC#10 downgrade — OPC UA
[opcua].stale_threshold_secondshot-reload affects only the web dashboard. Story 9-7's spec (AC#10) mandates per-NodeId staleness cache flush on threshold change. The OPC UA path captures the threshold into per-variable read-callback closures atsrc/opc_ua.rs:1017(frozen at startup). AC#8 forbids modifyingsrc/opc_ua.rs, so 9-7 shipped propagate-only behaviour and renamed the test fromstale_threshold_change_flushes_cache(spec) tostale_threshold_change_propagates_to_subscribers(tests/config_hot_reload.rs:453). Spec amendment landed in 9-7 review iter-1.Credential-rotation v1 limitation —
[opcua].user_name/user_passwordand[web].auth_realmclassified as restart-required. The auth middleware capturesArc<WebAuthState>at router-build time viafrom_fn_with_state(auth_state, basic_auth_middleware); hot-swapping requires changing the middleware signature toState<Arc<AppState>>, which would modifysrc/web/auth.rs(forbidden by AC#8). Documented in 9-7 dev-notes Debug Log.Pattern: live-borrow refactor
Both limitations dissolve once the closure-capture pattern is replaced with a live-borrow pattern that reaches
AppState(or an equivalentArc<RwLock>/Arc<ArcSwap>):from_fn_with_state(auth_state, basic_auth_middleware)→from_fn_with_state(app_state, basic_auth_middleware); middleware readsstate.auth.load()per request.add_variables(...)to install a callback that capturesArc<AppState>(or a dedicatedArc<ArcSwap<AppConfig>>) and readsstale_threshold_secondsper call rather than baking it in.Acceptance criteria (high level)
[opcua].stale_threshold_secondschange is observed by every OPC UA Read on the next call (no cache; or explicit cache flush hook).[opcua].user_name/user_passwordand[web].auth_realmrotation succeeds without process restart; existing in-flight requests authenticate against the OLD credentials and complete normally; new requests use NEW credentials.src/config_reload.rs::classify_diff) upgrades these knobs from `restart-required` to `hot-reload-safe` (or `auth-rotating` per the spec § Knob Taxonomy).src/web/auth.rsandsrc/opc_ua.rscontinue to hold.References
_bmad-output/implementation-artifacts/9-7-configuration-hot-reload.md§ Dev Notes Debug Log + Review Findings (decision-needed Implement storage #1)docs/security.md§ Configuration hot-reload (v1 limitations 2 + 3)_bmad-output/implementation-artifacts/9-0-async-opcua-runtime-address-space-mutation-spike.md(read-callback closure pattern)