This document defines the runtime architecture of SubZeroFramework.
Use ReleasePlan.md as the execution roadmap and priority list.
Use FunctionalitySpecification.md as the source of truth for intended menu-item and page behavior.
Use this document as the source of truth for the service/client split, privilege boundary, IPC ownership, process lifecycle, and multi-instance assumptions.
SubZeroFramework is split into two major processes:
- a long-lived local background service
- a user-facing client application
The background service owns privileged Framework EC access and local hardware polling.
The client application owns UI, user interaction, local presentation state, and subscriptions to service-exposed status and telemetry streams.
The boundary between them is local-only IPC over gRPC.
The service is the only process that should talk directly to FrameworkDotnet, the EC connection, and other privileged hardware access paths.
Platform intent is:
- on Linux, the service runs under systemd as root because Framework EC access requires elevated privileges
- on Windows, the service runs as a Windows service in an elevated service context with the rights needed for EC access
The service is independent from any interactive user session.
The client application runs as the normal interactive user.
The user can start and stop the client whenever they want without changing the intended lifetime of the service.
The client must not assume it has direct permission to access the EC or to bypass the service boundary.
The service owns:
- Framework EC connection lifecycle
- telemetry polling and retained snapshots
- hardware inventory collection and fallback mapping
- fan-control command execution
- authorization checks for mutating fan-control commands
- service self-registration, update, removal, and autorun changes when launched in service-management mode
- restore-to-auto safety behavior during controlled shutdown paths
- the shared source of truth for current telemetry, fan state, command state, and fan safety state such as active overrides and restore failures
The client owns:
- page composition and navigation
- view models and local DynamicData projections for presentation
- presentation formatting against client-local display-unit preferences
- subscribing to status, telemetry, and fan-control state streams from the service
- rendering charts, cards, legends, and selection state
- initiating user-requested commands through the service boundary
- discovering packaged service-bundle readiness and surfacing lifecycle guidance or quick actions without becoming the privileged worker
The client should treat the service as the authority for runtime hardware state.
Display-unit selection is client-only. It is a presentation concern, never travels to the service, and
is persisted per-user by LocalUserUnitPreferencesClient to the app-data folder
(%LOCALAPPDATA%\SubZeroFramework\display-unit-preferences.json). The UI formats every value and axis
label through IUnitFormattingService.
Reversed 2026-07-03: an earlier design put these on the service behind a
FrameworkUserPreferencesServicegRPC surface so every client on the machine shared one setting. That vertical was removed — the type no longer exists anywhere in the codebase. Treat any doc still describing a machine-wide, service-owned units surface as out of date.
Service snapshots and gRPC telemetry payloads always remain in canonical units; conversion happens at the presentation layer only.
Separately from the above, the service owns its own runtime configuration — polling cadence, Hardware.Info cadence, and fan-command authorization — with Apply/Save/Load semantics:
- Apply updates the live runtime and broadcasts to every subscribed client without writing to disk.
- Save persists the current runtime values to the service-approved JSON path.
- Load re-reads the persisted JSON, applies it to runtime, and broadcasts to every client.
Custom service-approved settings paths are deferred; the service currently uses fixed paths
(%ProgramData%\SubZeroFramework\service-settings.json on Windows, /etc/subzeroframework/ on Linux)
with atomic writes.
SubZeroFramework uses local-only gRPC over a Unix domain socket path on both Windows and Linux.
The service publishes:
- system status
- telemetry channels and current values
- telemetry history streams
- fan capability and fan-control state streams, including authoritative override and restore-failure state
- explicit fan command RPCs
- hardware inventory snapshots
The client consumes these through typed client abstractions rather than through direct provider access.
Telemetry, inventory, and hardware values are transported in canonical units. The client may convert them locally for presentation, chart axes, and copyable labels without changing transport or persistence semantics.
Install, update, uninstall, shutdown, restart, and autorun changes are intentionally not normal gRPC operations.
Those operations must still work when the service is offline, not yet installed, or in the middle of stopping itself, and the client must remain unelevated.
The current path is:
- publish a packaged service bundle under
service-package/windowsorservice-package/linux - let the client discover that bundle and launch the packaged service executable with
--service-management <operation> - use the OS privilege prompt on the service executable rather than on the Uno client process
- let the service executable perform the underlying
sc.exe,net.exe,systemctl, and file-installation work on its own behalf
This keeps lifecycle control local and explicit without weakening the service or client privilege boundary.
The service is intended to be long-lived and OS-managed.
It may start before any UI instance exists, and it should continue running after all UI instances are closed.
The service should remain the single owner of hardware polling and command execution throughout its lifetime.
The client is user-driven and disposable.
Users may:
- launch the client when they want
- close it when they want
- relaunch it later
- potentially run multiple client instances at the same time
Closing the client should not stop the service.
Closing the client should not directly change EC control state unless the client explicitly requested a command before closing.
Client instances should tolerate service restarts.
When the service restarts, clients should reconnect and rebuild their local projections from service streams instead of assuming in-process persistence.
Lifecycle requests that affect service registration or availability should be treated as out-of-band local management actions.
The client may surface those actions in Settings or Warnings and Issues, but the actual privileged work should continue to happen through the packaged service executable and the platform service manager rather than through a long-lived mutating gRPC channel.
Multiple UI instances should be treated as a realistic edge case, not an impossible one.
For read-only status and telemetry:
- multiple client instances may subscribe at the same time
- each client instance may build its own local DynamicData caches from the service streams
- this is acceptable as long as each individual client instance still shares its own subscriptions internally and does not create unnecessary duplicate gRPC streams per view model
DynamicData is a good fit for this because each client instance can keep local observable caches while the service remains the authoritative publisher.
For mutating fan-control commands:
- the service remains the only writer to hardware
- client pages must not assume exclusive ownership of fan control
- current control mode and fan-control state should always be taken from the service, not from local optimistic UI state alone
- active override and restore-failure indicators should also be taken from the service-published fan-control state stream
- custom-curve and manual-control features must assume another client instance could have changed state
This means command UI should stay conservative and should refresh from service state after commands are applied.
Concurrent command ownership across multiple client instances is not fully defined yet.
Until a stronger ownership or leasing model exists, the service should remain conservative and authoritative, and UI command flows should assume last-writer-wins behavior unless the service adds stricter coordination.
DynamicData patterns should be preserved at both layers:
- the service should expose shared, reusable observables and change-set streams rather than per-consumer polling loops
- each client instance should share subscriptions internally so multiple view models do not create duplicate service streams
- UI collections should preserve stable item identity using persistent mutable card or row models exposed through
ReadOnlyObservableCollection<T>
This is important both for performance and for correctness when more than one UI instance is running.
The service is responsible for restoring automatic fan control on controlled shutdown paths.
That includes:
- service stop requests
- host application stopping events
- controlled polling shutdown paths
- service disposal paths when process state still allows restore
The service should avoid duplicate restore attempts and should target fans with explicit active overrides instead of blindly restoring every available fan.
The client should not own restore-to-auto behavior for process shutdown.
When implementing new features:
- keep privileged hardware access in the service
- keep the client unprivileged and replace direct provider usage with typed IPC clients
- keep install, update, shutdown, restart, and uninstall flows out of the normal gRPC command surface
- assume multiple UI instances can exist at once
- design read-only pages to tolerate concurrent listeners
- design command pages so service state is authoritative after every mutation
- surface fan safety state from service-owned streams rather than inventing local UI-only fan override state
- preserve reconnect behavior after service restarts
- prefer shared reactive streams and DynamicData projections over ad hoc polling in the UI
ReleasePlan.mdtracks what should be done next and what is still incompleteFunctionalitySpecification.mddescribes what each menu item and page should doSubZeroFramework/Docs/IpcAuthorizationAndUiCadence.mddescribes current IPC authorization limits and UI cadence rulesSubZeroFramework/Docs/FanSafetyShutdownChecklist.mdcaptures the manual verification steps for service stop, restart, machine shutdown, and multi-instance fan-safety behaviorSubZeroFramework.Service/README.mddocuments the packaged service bundle layout and--service-managementlifecycle operations