Skip to content

Story 9-7 KF: live-borrow refactor for closure-captured AppConfig fields (covers AC#10 + credential-rotation v1 limitation) #113

Description

@guycorbaz

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 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

  1. 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.

  2. 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)
  • Story 9-7 docs: docs/security.md § Configuration hot-reload (v1 limitations 2 + 3)
  • Story 9-0 spike: _bmad-output/implementation-artifacts/9-0-async-opcua-runtime-address-space-mutation-spike.md (read-callback closure pattern)
  • Refs Story 9-7: Configuration Hot-Reload #112 (Story 9-7 tracker)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions