# GuruRMM — Role-Aware Offline Alerting + Alert Ignore/Mute (+ MSP360 API probe, Jupiter VM diag)

## User
- **User:** Mike Swanson (mike)
- **Machine:** GURU-5070
- **Role:** admin

## Session Summary

Shipped two GuruRMM alerting features end-to-end and investigated an MSP360 API
capability question, then diagnosed an unrelated Unraid VM network fault. After the
morning backup-alert cleanup (separate log), Mike asked whether the MSP360 API could
delete plans / trigger backups / configure storage for the three console follow-ups.
Probed `api.mspbackups.com` with the vaulted Provider token: it is monitoring-only
(Companies/Users/Monitoring, GET; every management path 404s; OPTIONS confirms
read-only). The agent-CLI fallback also failed -- the white-labeled MSP360 agent on
SERVER has no `cbb.exe` at any standard path. Concluded the three items stay
MSP360-console tasks and filed them as coord todos for Mike.

Built **role-aware offline alerting + correlated mass-offline detection** (spec
`role-aware-offline-alerting`). Servers offline are incidents; workstations offline
are routine. Implemented server-side (Tasks 1-5): migration 054 `role_override`
column + a shared `agent_role` classifier delegating to the canonical
`agent_is_server`; `OfflineAlertingConfig` in the policy JSONB; a new scheduled
offline-sweep evaluator generating server-only `agent_offline` alerts plus site
(>=50% + >=3) and fleet (>=10) `mass_offline_*` aggregates; a warm-up restart guard;
and `PUT /api/agents/:id/role-override`. Code Review REJECTED the first cut for a
CRITICAL defect in the spec's own restart guard (`last_seen < started_at`
permanently disabled detection in steady state because `last_seen` advances on every
heartbeat); fixed by making the warm-up window the sole guard. Re-review APPROVED.
Then the dashboard unit (Tasks 6-7): role-aware triage (offline servers individual +
elevated; offline workstations collapsed into a quiet "N workstations offline"
roll-up) and the role-override control on the agent detail page; plus a small server
DTO fix so `GET /api/agents/:id` returns `role_override`. Verified live: a real
offline server (WIN-TG2STMODJG8, "Windows Server 2019 Standard Evaluation",
auto-classified via `os_name`) fired `agent_offline`; zero false mass-offline despite
~55 chronically-offline agents; restart guard held across deploys.

Built **alert ignore/mute (perma-silence)** (spec `alert-mute`). Distinct from
ack/resolve, which only quiet the current cycle: a mute keyed on `dedup_key`
suppresses a recurring condition until un-ignored, with a required reason. Server
unit (Tasks 1-4): migration 055 `alert_mutes` table + `muted` status; an
`is_dedup_muted` gate inserted into the universal `create_or_update_alert` AND the
one `create_check_alert` bypass so muted conditions write `status='muted'` (never
active, never email); transactional `mute_condition`/`unmute_condition`;
`POST /api/alerts/:id/mute` (reason required -> 400) and `/unmute`. Code Review
APPROVED (the muted gate is a top-of-function early return, so the active path is
byte-for-byte unchanged for normal alerts). Verified live against WIN-TG2STMODJG8's
re-firing `agent_offline`: mute -> `muted`, survived a full offline sweep without
re-firing, unmute -> `active`. The dashboard unit (Task 5) is NOT yet built.

Finally, diagnosed an Unraid VM network fault on Jupiter (172.16.3.20): the libvirt
domain "Windows Server 2016" (guest hostname `ACG-DWP-X-BB`) had no LAN. Found the
host side healthy (vnet8 bridged to br0, forwarding, receiving LAN broadcast), and
the guest holding APIPA `169.254.157.152` with no gateway -- its single e1000 NIC is
DHCP-enabled but not getting a lease from pfSense (172.16.0.1). Paused for Mike's
call on DHCP-vs-static before changing the guest.

## Key Decisions

- Offline classification = auto (`os_product_type` 2/3, else `os_name`/`os_version`
  ~/server/i) + a manual `role_override` column. `os_product_type` is populated on
  only 16/168 agents, so `os_name` is the workhorse signal; inventory-less offline
  servers (SIF-SERVER, Server2013) auto-classify as workstations and need the manual
  override -- Mike held on tagging them.
- Mass/aggregate alerts pin to a representative offline agent with a site/fleet
  `dedup_key` rather than making `alerts.agent_id` nullable (avoids a rippling
  migration across every alert path).
- Restart guard = warm-up window only (max grace after boot). Dropped the broken
  `last_seen < started_at` clause. Individual server alerts fire independently of the
  site-outage alert (so a still-down server keeps paging after an outage clears).
- Alert mute keyed on `dedup_key` (universal recurring-condition id, always set);
  permanent until un-ignored; stays muted on severity escalation. Gate placed at the
  two creation choke points so it is universal across every alert type.
- MSP360 console items stay manual: the Provider API token is monitoring-tier only;
  no REST path for plan delete / run / storage config at our access level.

## Problems Encountered

- CRITICAL restart-guard defect (my spec error): `last_seen < started_at` silently
  disabled offline detection in steady state. Caught in Code Review; fixed to
  warm-up-only and re-reviewed. The bug-enshrining unit test was rewritten.
- Split-brain classifier: the new `agent_role` diverged from the existing
  `agent_is_server`. Unified by delegating to the canonical fn (threaded
  `os_version`).
- `GET /api/agents/:id` omitted `role_override` (returns `AgentResponse`, not
  `AgentWithDetails`), so the override card rendered blind. Fixed by adding the field
  to the base `Agent`/`AgentResponse` DTOs (all `Agent` SELECTs use `SELECT *`).
- Orphaned offline alerts + per-agent policy N+1 in the sweep: replaced scattered
  resolves with one authoritative resolve-except pass; read per-agent grace only for
  servers; mass membership uses global grace/window.
- MSP360 white-label: no `cbb.exe` on SERVER, so the agent-CLI automation route for
  the backup console tasks was not viable without per-box fingerprinting.
- Submodule gitlink: detached to the pinned commit before `/save` so the session-log
  commit does not fold a gitlink bump.

## Configuration Changes

GuruRMM submodule (`azcomputerguru/gururmm`), all merged to `main`:
- `server/migrations/054_agent_role_override.sql` (new) -- `role_override` column.
- `server/migrations/055_alert_mutes.sql` (new) -- `alert_mutes` table + partial
  unique index `ON dedup_key WHERE active`.
- `server/src/alerts/offline.rs` (new) -- offline sweep + mass detection.
- `server/src/db/alerts.rs` -- `AlertStatus::Muted`; `is_dedup_muted`/
  `mute_condition`/`unmute_condition`; muted gate in `create_or_update_alert`;
  `create_or_update_alert` title/message/severity refresh (from morning).
- `server/src/alerts/check_alerts.rs` -- mute gate + notify suppression.
- `server/src/db/agents.rs` -- `role_override` on `Agent`/`AgentResponse`/
  `AgentWithDetails`; `agent_role` + `set_agent_role_override`.
- `server/src/db/policies.rs`, `server/src/policy/{effective,merge}.rs` --
  `OfflineAlertingConfig` + merge.
- `server/src/main.rs` -- offline-sweep spawn (60s).
- `server/src/api/{alerts,agents,mod}.rs` -- mute/unmute + role-override endpoints.
- `dashboard/src/lib/agentRole.ts` (new), `dashboard/src/components/ExceptionStream.tsx`,
  `dashboard/src/pages/AgentDetail.tsx`, `dashboard/src/api/client.ts` -- triage +
  override control.
- specs/`role-aware-offline-alerting/` and specs/`alert-mute/` (new spec folders).

## Credentials & Secrets

No new secrets. MSP360 Provider API: vault `msp-tools/msp360-api.sops.yaml`
(`credentials.login` / `credentials.password`); base `https://api.mspbackups.com`,
`POST /api/Provider/Login` -> `access_token`; monitoring-only tier. GuruRMM API:
vault `infrastructure/gururmm-server.sops.yaml`. Jupiter: vault
`infrastructure/jupiter-unraid-primary.sops.yaml` (root@172.16.3.20:22, key auth
works from GURU-5070).

## Infrastructure & Servers

- GuruRMM API/server: 172.16.3.30:3001 (Linux VM on Jupiter). Dashboards
  rmm-beta / rmm.azcomputerguru.com (shared API serves both).
- Jupiter Unraid: 172.16.3.20, root:22, Unraid 6.12.85. VMs via virsh; bridge `br0`
  (uplink eth2), `172.16.3.20/22`. VM "Windows Server 2016" = guest `ACG-DWP-X-BB`,
  vnet8/br0/e1000, MAC 52:54:00:d4:8e:59, APIPA 169.254.157.152 (no DHCP lease).
- Office LAN 172.16.0.0/22; pfSense 172.16.0.1 (router + DNS + DHCP).

## Commands & Outputs

- Offline alerting commits: `f1cdf5d`, `30e4f23` (fix), `21d63bd` (DTO), `3eedf91`
  (dashboard). Alert-mute: `29c405e` (spec), `a120e71` (server).
- Live mute verify: empty-reason 400; mute -> status=muted; after a 60s sweep the
  muted alert did NOT re-fire active (count=0); unmute -> active.
- Jupiter diag: `virsh domiflist 9` (vnet8/br0/e1000); `virsh domifaddr 9 --source
  agent` -> 169.254.157.152; guest-exec `ipconfig /all` -> DHCP Enabled: Yes, APIPA,
  no gateway.

## Pending / Incomplete Tasks

- **alert-mute dashboard (Task 5)** -- NOT started. Ignore button + required-reason
  prompt + Muted filter + Un-ignore, on the alerts page AND the agent Alerts tab;
  muted rows show Un-ignore instead of Ack/Resolve. (Mike: "then continue" -> do this
  next.) Plus alert-mute Task 6 (roadmap) + Task 7 (final doc).
- **offline alerting** -- Task 8 (roadmap entry) outstanding; classification gap:
  tag SIF-SERVER / Server2013 (and WIN-TG2STMODJG8 if it should be a workstation) via
  role_override -- held for Mike.
- **Jupiter VM** -- decide DHCP vs static for ACG-DWP-X-BB; if DHCP, run `ipconfig
  /renew` via guest agent; if static, set the intended IP. Paused for Mike.
- **MSP360 console (Mike-side todos):** delete SERVER's Nov-2024 plan; AD1 full
  backup for retention; LAB-Becky storage-or-delete.

## Reference Information

- Commits (main): `f1cdf5d`,`30e4f23`,`21d63bd`,`3eedf91`,`29c405e`,`a120e71`.
- Specs: `projects/msp-tools/guru-rmm/specs/role-aware-offline-alerting/`,
  `.../specs/alert-mute/`.
- MSP360 PlanType map: 3=Files,7=SQL,8=Restore,11=Image,13=Consistency,16=HyperV.
- New alert types: `agent_offline`, `mass_offline_site`, `mass_offline_fleet`; new
  status `muted`; new tables `alert_mutes`; new columns `agents.role_override`.
- Test box: WIN-TG2STMODJG8 agent id b6c715df-09fe-4e97-b09a-82a1b535f041 (offline
  eval server, used for live verification).

---

## Update: 00:44 PT — alert-mute dashboard shipped, beta->prod promotion, MSI dedup cleanup, parked design

### Completed since the initial save
- **Alert ignore/mute dashboard (Task 5) — shipped + deployed** (commit `63d7c5d`). Reused the existing Radix Dialog for the required-reason prompt; shared `AlertRow` so both the alerts page and the agent Alerts tab get Ignore/Un-ignore + the Muted filter; muted rows show Un-ignore (not Ack/Resolve) + reason/who/when; status badge labels `muted` as "Ignored". Code Review APPROVED; `npm run build` clean. The alert-mute feature is now complete end-to-end (server `a120e71` + dashboard `63d7c5d`).
- **Roadmap entries** (commit `5f1d9f0`): both offline alerting and alert ignore/mute added to FEATURE_ROADMAP.md Alerting section; specs' roadmap tasks marked DONE.
- **Dashboard beta -> production promotion.** Prod (`/var/www/gururmm/dashboard`, rmm.azcomputerguru.com) was stuck at the 2026-06-04 build. Ran `sudo /opt/gururmm/promote-dashboard.sh --confirm` on 172.16.3.30 (the sanctioned promote: backs up prod first, keeps 10 backups, rsync beta->prod, `--rollback` to undo). Backup `dashboard-20260608-000926`; prod now byte-identical to beta. This shipped ALL accumulated beta work to prod: this session's offline triage + role-override + alert mute UI, plus other sessions' credential-inheritance mgmt + clickable alert badges. Coord component gururmm/dashboard -> deployed.
- **MSI BSOD duplicate cleanup.** MSI (Safesite/Glendale, agent a685af29...) had 4 BSOD VIDEO_TDR_FAILURE (0x116) alerts, all with `dedup_key = NULL` (legacy, from the early-June BSOD implementation before the dedup key was added). Mike had muted one; the other 3 active duplicates could not be spanned by the mute (NULL key falls back to single-row). Resolved the 3 duplicates (final: 0 active, 1 muted "Client to replace unit", 3 resolved). Two resolve calls hit transient HTTP 000; retried clean.

### Parked design conversation (NOT approved — captured for later)
A stream-of-thought design discussion produced two parked features, captured in
`projects/msp-tools/guru-rmm/docs/PARKED_alert-lifecycle-and-telemetry-cadence.md`
(commit `82a7b14`) + two coord todos (`eb14a4d2`, `bacc4f12`):
1. **Stateful alert lifecycle + occurrence history** (server): one alert per dedup_key spanning all statuses; re-fire reopens only when "substantively different" (severity escalation or re-fire after clear; not per-1%); occurrence count+first/last on the row, dated history via existing agent_events (no new table); ack reopens on substantive change, mute is absolute; server-side per-condition cooldown (~15min default, escalations bypass, recovery always prompt); subsumes BSOD per-bugcheck dedup + mute-across-refire + legacy null-key cleanup. Root cause it fixes: dedup currently matches only status='active', so ack/resolve breaks dedup and spawns duplicates.
2. **Tiered agent telemetry cadence** (agent): per-collector collection intervals (live/on-demand ~5-10s only when watched; standard ~30-60s; slow ~5-15min; daily). Disk consumption (slow) vs disk I/O (fast) are different collectors; fast diagnostics as on-demand/live mode; cadence config in policy, server-pushed; cross-platform scheduler. Larger, separate agent-side feature.

Saved a feedback memory (`.claude/memory/feedback_stream_of_thought_design.md`): Mike
prefers free-form design conversations; Claude sharpens as a design partner but does
not build until an explicit go, then decomposes into specs.

### Still open / parked
- Jupiter VM `ACG-DWP-X-BB` no-DHCP (diagnosed, paused for Mike's DHCP-vs-static call; see wiki/systems/jupiter.md).
- Offline classification overrides (SIF-SERVER/Server2013/WIN-TG2STMODJG8) — taggable from the agent detail role-override control; held.
- The two parked design features above; MSP360 console todos (3) from the morning.
