feat(capture): OS notifications on critical live failures#69
Merged
Conversation
The last S5 issue. When live detection raises a critical failure, fire an OS notification so an operator watching a session gets pinged without staring at the timeline (macOS-first, ADR-0002). Reaching the notification service (spike, ADR-0011): the effects channel (fx) exposes no notification verb, and PlatformServices is built inside the runner, never handed to main. But Effects binds the platform services as a public field (`fx.services`), set on the loop thread before dispatch — so update_fx reaches `fx.services.?.showNotification(...)` directly, on the loop thread where services are meant to be called. Guarded, so a null- services build (tests / null platform) is a silent no-op. Decision logic is pure and headless-tested (workspace.zig): LiveCapture tracks the critical codes already notified this session (std.EnumSet), enqueues one notification per new code (title/body copied into fixed buffers, deduped per code, reset on start); update_fx fires and clears the queue each turn. Split pendingNotifications (const) / clearNotifications (void) so the value-returning accessor stays const — the runtime reflects model accessors and requires it. Notify on explicit faults only: connector_fault (a "Faulted" status) and diagnostics_failure (a failed upload). The absence-based criticals — unresponsive_csms (an in-flight Call) and station_offline_during_session (an unclosed transaction) — are the normal mid-session state of a growing live prefix and would ping on nearly every session before resolving, so they stay in the failure panel but don't notify. Declares the `notifications` permission (app.zon + the runtime permission set); no notification is delivered without it. Tests: a critical fault queues one notification (deduped per code); non-critical severities don't notify; prefix-transient criticals don't notify though they show as failures; a new session resets the dedup. 165/165 green; ReleaseFast build clean; app.zon valid. Closes #60.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The last S5 issue: OS notifications when live detection raises a critical failure, so an operator watching a session gets pinged without staring at the timeline (macOS-first, ADR-0002). Depends on the effects channel (#58) and the live failure stream (#56/#59).
Spike: reaching the notification service (ADR-0011)
The issue flagged a contingency — "if no notification API is reachable in the zero-config runner, move to the #33 runner-eject." The spike found a seam, so this lands in-runner:
fx) exposesspawn/fetch/ timers / … but no notification verb, andPlatformServicesis constructed inside the runner, never handed tomain.Effectsbinds the platform services as a public field —fx.services: ?*const PlatformServices, set on the loop thread before dispatch. Zig has no private fields, soupdate_fxreachesfx.services.?.showNotification(...)directly — on the loop thread, where services are meant to be called. Guarded, so a null-services build (tests / null platform) is a silent no-op.Decision logic (pure, headless-tested)
Lives in
workspace.zig, no platform involved:LiveCapturetracks the critical failure codes already notified this session (std.EnumSet), and each reload enqueues oneNotificationper new code (title/body copied into fixed buffers so nothing dangles into the live arena; deduped per code; reset on start).update_fxfires the pending set throughfx.servicesand clears it each turn. Read/clear are split (pendingNotificationsconst /clearNotificationsvoid) because the runtime reflects value-returning model accessors and requires them*const.Notify on explicit faults only
A live session is detected on a growing prefix, and two of the four critical rules are inferred from a not-yet-arrived message:
unresponsive_csms(a Call awaiting its response) andstation_offline_during_session(a StartTransaction with no StopTransaction). Both are the normal mid-session state — every in-flight request and open transaction trips them, then they resolve. So the notifier fires only on criticals the station explicitly reports:connector_fault(a "Faulted" status) anddiagnostics_failure(a failed upload). The absence-based ones still show in the live failure panel; they just don't ping (Model.isLiveNotifiable).Permission
Declares
notifications(security.permission_notifications) in the runtime permission set andapp.zon— no notification is delivered without it.Tests (165/165, +4)
Faultedduring a transaction →CONNECTOR_FAULT) queues exactly one notification, titled with the code; duplicates within the session are suppressed.Verification
native test -Dplatform=null→ 165/165.native build(ReleaseFast) → clean (thefx.servicesfiring compiles into the app, not just tests).zig fmt --checkclean;native check→app.zon is valid;studio ciexit 0; the GUI launches without a startup crash.S5 complete
This closes the flagship milestone — the WS proxy, the
studio captureCLI, and the live inspector surface with notifications (#54–#60). Next is S6 — public release & launch (0.5.0).Closes #60.