Part of #115 (userver-vs-morph survey).
Observation
Every *Config type in morph (ReconnectCoordinatorConfig, NetworkMonitorConfig, QtWebSocketServerConfig, QtWebSocketBackendConfig, SocketBackendConfig/SocketServerConfig, Lightweight's PoolConfig) is a plain aggregate consumed once at construction time. Changing a value means reconstructing the object — no file-watching, no reload signal, no config-service client. userver's dynamic_config::Source/Snapshot lets a whole fleet of service instances pick up new config values (kill-switches, timeouts, experiment flags) without a redeploy, backed by a polling components::DynamicConfigClient against a reference config service.
Status: plain absence, not currently a scope gap
This is a genuine absence rather than a scope substitute worth stretching for right now — morph has no long-lived fleet of server processes to reconfigure without redeploy, which is the exact problem this subsystem solves.
Revisit only if
morph grows a long-lived, multi-instance server deployment story of its own (multiple RemoteServer processes that need centrally-controlled runtime behavior changes without a redeploy).
Reference
Full comparison: #115, §3.5 of the attached document (docs/superpowers/findings/userver-vs-morph-2026-08-17.md).
Deep analysis: #117 dynamic config / hot-reload
Verification against current source (2026-08-19). The deferral's factual basis still holds, and has strengthened:
- All six in-repo
*Config types remain plain aggregates consumed at construction, with no setter or reload path: SocketServerConfig/SocketBackendConfig (include/morph/net/socket_server.hpp, socket_backend.hpp), ReconnectCoordinatorConfig/NetworkMonitorConfig (include/morph/offline/), QtWebSocketServerConfig/QtWebSocketBackendConfig (include/morph/qt/). Lightweight's PoolConfig is a template non-type parameter (Pool<PoolConfig{...}>) in a third-party dependency — not reloadable even in principle, and not morph's code to change.
RemoteServer::setLimitPolicy() (include/morph/core/remote.hpp:361) is still the one production-proven hot-swap mechanism: mutex-guarded install, locked-copy read on every register/execute/shared-acquire. It is still called exactly once per server, from hardcoded constexpr literals (examples/polls/src/app/app.cpp:57, examples/bookmarks/src/app/app.cpp:90) — never from env.
- The trigger ("long-lived multi-instance server deployment") remains unmet: the examples ladder has grown to eleven rungs, yet still only pastebin, bookmarks, and polls ship a server binary, each reading env vars once at the top of
main(). docs/spec/core/backend.md still states restart-to-reconfigure as design: "there is no un-shutdown; a restarted service constructs a fresh RemoteServer."
Refinement of the sketched minimal design. Two findings argue against the SIGHUP half of the sketch:
- Platform fit. morph runs a first-class Windows CI job (
.github/workflows/ci.yml), and SIGHUP does not exist on Windows. The Qt-safe signal pattern (volatile sig_atomic_t + QTimer poll, already used for SIGINT in examples/pastebin/src/server/main.cpp) works only on POSIX and only in the embedding app — a library-level SIGHUP story would be half-platform.
- A better in-tree trigger exists. Every
execute already passes the IAuthorizer choke point (docs/spec/security.md), and the wire protocol carries typed actions with an action log. An operator-authorized admin action ("set limits") delivered over the existing wire is portable, testable with the existing harness, auditable, and multi-instance-ready — strictly dominating SIGHUP here.
Which values would actually benefit: only server-side, live-behavior-gating ones — LimitPolicy (mechanism already done) and QtWebSocketServerConfig's messagesPerSecond/maxConnections/idleTimeout, which the event-loop thread reads live per-message (src/qt/qt_websocket_server.cpp:194-297), so a queued setter needs no locking. The client-side reconnect/monitor configs are read lock-free on background threads; retrofitting setters there adds synchronization cost for values a client fixes at startup anyway.
Cost/benefit: S–M effort for the narrowed version (one setter on QtWebSocketServer, env-or-action plumbing in example servers), but non-trivial spec/docs burden per field under morph's spec-sync discipline, benefiting no current user.
Recommendation: defer, with the trigger sharpened. Revisit when a rung's server needs an operator-flippable value (rate-limit or kill-switch) without dropping connections — and implement it then as an authorized admin action over the existing wire protocol calling setLimitPolicy-style setters, not as a SIGHUP/env re-read.
Part of #115 (userver-vs-morph survey).
Observation
Every
*Configtype in morph (ReconnectCoordinatorConfig,NetworkMonitorConfig,QtWebSocketServerConfig,QtWebSocketBackendConfig,SocketBackendConfig/SocketServerConfig, Lightweight'sPoolConfig) is a plain aggregate consumed once at construction time. Changing a value means reconstructing the object — no file-watching, no reload signal, no config-service client. userver'sdynamic_config::Source/Snapshotlets a whole fleet of service instances pick up new config values (kill-switches, timeouts, experiment flags) without a redeploy, backed by a pollingcomponents::DynamicConfigClientagainst a reference config service.Status: plain absence, not currently a scope gap
This is a genuine absence rather than a scope substitute worth stretching for right now — morph has no long-lived fleet of server processes to reconfigure without redeploy, which is the exact problem this subsystem solves.
Revisit only if
morph grows a long-lived, multi-instance server deployment story of its own (multiple
RemoteServerprocesses that need centrally-controlled runtime behavior changes without a redeploy).Reference
Full comparison: #115, §3.5 of the attached document (
docs/superpowers/findings/userver-vs-morph-2026-08-17.md).Deep analysis: #117 dynamic config / hot-reload
Verification against current source (2026-08-19). The deferral's factual basis still holds, and has strengthened:
*Configtypes remain plain aggregates consumed at construction, with no setter or reload path:SocketServerConfig/SocketBackendConfig(include/morph/net/socket_server.hpp,socket_backend.hpp),ReconnectCoordinatorConfig/NetworkMonitorConfig(include/morph/offline/),QtWebSocketServerConfig/QtWebSocketBackendConfig(include/morph/qt/). Lightweight'sPoolConfigis a template non-type parameter (Pool<PoolConfig{...}>) in a third-party dependency — not reloadable even in principle, and not morph's code to change.RemoteServer::setLimitPolicy()(include/morph/core/remote.hpp:361) is still the one production-proven hot-swap mechanism: mutex-guarded install, locked-copy read on everyregister/execute/shared-acquire. It is still called exactly once per server, from hardcodedconstexprliterals (examples/polls/src/app/app.cpp:57,examples/bookmarks/src/app/app.cpp:90) — never from env.main().docs/spec/core/backend.mdstill states restart-to-reconfigure as design: "there is no un-shutdown; a restarted service constructs a freshRemoteServer."Refinement of the sketched minimal design. Two findings argue against the SIGHUP half of the sketch:
.github/workflows/ci.yml), and SIGHUP does not exist on Windows. The Qt-safe signal pattern (volatile sig_atomic_t+ QTimer poll, already used for SIGINT inexamples/pastebin/src/server/main.cpp) works only on POSIX and only in the embedding app — a library-level SIGHUP story would be half-platform.executealready passes theIAuthorizerchoke point (docs/spec/security.md), and the wire protocol carries typed actions with an action log. An operator-authorized admin action ("set limits") delivered over the existing wire is portable, testable with the existing harness, auditable, and multi-instance-ready — strictly dominating SIGHUP here.Which values would actually benefit: only server-side, live-behavior-gating ones —
LimitPolicy(mechanism already done) andQtWebSocketServerConfig'smessagesPerSecond/maxConnections/idleTimeout, which the event-loop thread reads live per-message (src/qt/qt_websocket_server.cpp:194-297), so a queued setter needs no locking. The client-side reconnect/monitor configs are read lock-free on background threads; retrofitting setters there adds synchronization cost for values a client fixes at startup anyway.Cost/benefit: S–M effort for the narrowed version (one setter on
QtWebSocketServer, env-or-action plumbing in example servers), but non-trivial spec/docs burden per field under morph's spec-sync discipline, benefiting no current user.Recommendation: defer, with the trigger sharpened. Revisit when a rung's server needs an operator-flippable value (rate-limit or kill-switch) without dropping connections — and implement it then as an authorized admin action over the existing wire protocol calling
setLimitPolicy-style setters, not as a SIGHUP/env re-read.