-module(pharos@alert_manager). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/pharos/alert_manager.gleam"). -export([from_name/1, start_link/3, breach/1, recover/1]). -export_type([alert_manager/0, message/0, data/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Per-threshold alert state machine.\n" "\n" " Wraps an `eparch/state_machine` running the four-state lifecycle\n" " `Clear → Pending → Firing → Cooling → Clear`. The state machine is\n" " registered under a deterministic `Name` so that supervisor restarts\n" " keep `AlertManager` handles valid: callers send `Breach` / `Recover`\n" " casts to the registered name, which the OTP runtime resolves to the\n" " current pid on each call.\n" "\n" " `breach/1` and `recover/1` map physical signals (threshold crossed\n" " up / down) to state transitions. The dispatcher is notified with\n" " `AlertFiring` and `AlertResolved` events on the relevant transitions.\n" ). -opaque alert_manager() :: {alert_manager, gleam@erlang@process:name(message())}. -type message() :: breach | recover. -type data() :: {data, binary(), pharos@alert:alert_level(), integer(), integer(), pharos@event_bus:event_bus()}. -file("src/pharos/alert_manager.gleam", 48). ?DOC( " Wrap a registered name as an `AlertManager`. The supervisor uses this\n" " to build handles before the underlying state machine has been started,\n" " so that the handler-attacher can route breach/recover signals to a\n" " stable address.\n" ). -spec from_name(gleam@erlang@process:name(message())) -> alert_manager(). from_name(Name) -> {alert_manager, Name}. -file("src/pharos/alert_manager.gleam", 187). -spec fire(data()) -> nil. fire(Data) -> pharos@event_bus:notify( erlang:element(6, Data), {alert_firing, erlang:element(2, Data), erlang:element(3, Data), pharos_ffi:collect_diagnostic()} ). -file("src/pharos/alert_manager.gleam", 169). -spec handle_state_timeout(pharos@alert:alert_state(), data()) -> eparch@state_machine:step(pharos@alert:alert_state(), data(), message(), any()). handle_state_timeout(State, Data) -> case State of clear -> eparch@state_machine:keep_state(Data, []); pending -> fire(Data), eparch@state_machine:next_state(firing, Data, []); firing -> eparch@state_machine:keep_state(Data, []); cooling -> pharos@event_bus:notify( erlang:element(6, Data), {alert_resolved, erlang:element(2, Data)} ), eparch@state_machine:next_state(clear, Data, []) end. -file("src/pharos/alert_manager.gleam", 150). -spec handle_recover(pharos@alert:alert_state(), data()) -> eparch@state_machine:step(pharos@alert:alert_state(), data(), message(), any()). handle_recover(State, Data) -> case State of clear -> eparch@state_machine:keep_state(Data, []); pending -> eparch@state_machine:next_state(clear, Data, []); firing -> eparch@state_machine:next_state( cooling, Data, [{state_timeout, {'after', erlang:element(5, Data)}, breach}] ); cooling -> eparch@state_machine:keep_state(Data, []) end. -file("src/pharos/alert_manager.gleam", 119). -spec handle_breach(pharos@alert:alert_state(), data()) -> eparch@state_machine:step(pharos@alert:alert_state(), data(), message(), any()). handle_breach(State, Data) -> case State of clear -> case erlang:element(4, Data) of 0 -> fire(Data), eparch@state_machine:next_state(firing, Data, []); Milliseconds -> eparch@state_machine:next_state( pending, Data, [{state_timeout, {'after', Milliseconds}, breach}] ) end; pending -> eparch@state_machine:keep_state(Data, []); firing -> eparch@state_machine:keep_state(Data, []); cooling -> fire(Data), eparch@state_machine:next_state(firing, Data, []) end. -file("src/pharos/alert_manager.gleam", 97). -spec handle_event( eparch@state_machine:event(pharos@alert:alert_state(), message(), HWZ), pharos@alert:alert_state(), data() ) -> eparch@state_machine:step(pharos@alert:alert_state(), data(), message(), HWZ). handle_event(Event, State, Data) -> case Event of {cast, breach} -> handle_breach(State, Data); {cast, recover} -> handle_recover(State, Data); {timeout, state_timeout_type, _} -> handle_state_timeout(State, Data); {timeout, event_timeout_type, _} -> eparch@state_machine:keep_state(Data, []); {timeout, {generic_timeout_type, _}, _} -> eparch@state_machine:keep_state(Data, []); {info, _} -> eparch@state_machine:keep_state(Data, []); {call, _, _} -> eparch@state_machine:keep_state(Data, []); {enter, _} -> eparch@state_machine:keep_state(Data, []) end. -file("src/pharos/alert_manager.gleam", 55). ?DOC( " Start a state machine for `data`, registering it under `name`. Notifies\n" " `bus` with `AlertFiring` / `AlertResolved` events on the relevant\n" " transitions.\n" ). -spec start_link( pharos@alert:alert_data(), pharos@event_bus:event_bus(), gleam@erlang@process:name(message()) ) -> {ok, alert_manager()} | {error, eparch@state_machine:start_error()}. start_link(Data, Bus, Name) -> {alert_data, Id, Level, Soak_period_ms, Cool_period_ms} = Data, Initial = {data, Id, Level, Soak_period_ms, Cool_period_ms, Bus}, Result = begin _pipe = eparch@state_machine:new(clear, Initial), _pipe@1 = eparch@state_machine:named(_pipe, {local, Name}), _pipe@2 = eparch@state_machine:on_event(_pipe@1, fun handle_event/3), eparch@state_machine:start_link(_pipe@2) end, case Result of {ok, _} -> {ok, {alert_manager, Name}}; {error, Error} -> {error, Error} end. -file("src/pharos/alert_manager.gleam", 93). ?DOC(" Resolve a manager's registered name to a `ServerRef` for casting.\n"). -spec server_ref(alert_manager()) -> eparch@state_machine:server_ref(message()). server_ref(Manager) -> statem_ffi:ref_from_subject( gleam@erlang@process:named_subject(erlang:element(2, Manager)) ). -file("src/pharos/alert_manager.gleam", 83). ?DOC(" Signal that the threshold has been breached.\n"). -spec breach(alert_manager()) -> nil. breach(Manager) -> statem_ffi:cast(server_ref(Manager), breach). -file("src/pharos/alert_manager.gleam", 88). ?DOC(" Signal that the threshold is no longer breached.\n"). -spec recover(alert_manager()) -> nil. recover(Manager) -> statem_ffi:cast(server_ref(Manager), recover).