[ { "id": "F001", "description": "Corrective migration adds activity_type, acknowledged_at, acknowledged_by columns to rmm_alerts", "implemented": true, "prdRefs": [ "FR-1" ] }, { "id": "F002", "description": "Migration adds dedup_key to rmm_alerts with index on (tenant, integration_id, dedup_key)", "implemented": true, "prdRefs": [ "FR-1" ] }, { "id": "F003", "description": "Migration adds occurrence_count (default 1) and last_occurrence_at to rmm_alerts", "implemented": true, "prdRefs": [ "FR-1" ] }, { "id": "F004", "description": "Migration adds nullable matched_rule_id to rmm_alerts", "implemented": true, "prdRefs": [ "FR-1" ] }, { "id": "F005", "description": "Migration adds auto_ticket_created (default false) to rmm_alerts", "implemented": true, "prdRefs": [ "FR-1" ] }, { "id": "F006", "description": "Migration adds conditions and actions jsonb (default {}) to rmm_alert_rules", "implemented": true, "prdRefs": [ "FR-1" ] }, { "id": "F007", "description": "Migration drops the eleven flat filter/action columns from rmm_alert_rules", "implemented": true, "prdRefs": [ "FR-1" ] }, { "id": "F008", "description": "Migration down() restores the prior schema", "implemented": true, "prdRefs": [ "FR-1" ] }, { "id": "F009", "description": "NormalizedRmmAlertEvent contract defined in shared/rmm/alerts/contracts.ts with kind triggered|reset|acknowledged", "implemented": true, "prdRefs": [ "FR-2" ] }, { "id": "F010", "description": "RmmAlertOutboundAdapter interface with per-provider resolution; providers without an adapter are skipped", "implemented": true, "prdRefs": [ "FR-2", "FR-5" ] }, { "id": "F011", "description": "Shared Zod schemas for rule conditions and actions JSONB shapes", "implemented": true, "prdRefs": [ "FR-2", "FR-6" ] }, { "id": "F012", "description": "mapNinjaOneWebhookToAlertEvent normalizer: field mapping, severity normalization, condition identity from statusCode falling back to activityType", "implemented": true, "prdRefs": [ "FR-2" ] }, { "id": "F013", "description": "TacticalRMM payload normalizer producing NormalizedRmmAlertEvent", "implemented": true, "prdRefs": [ "FR-2" ] }, { "id": "F014", "description": "NinjaOne webhook routes CONDITION TRIGGERED/RESET/ACKNOWLEDGED through processRmmAlertEvent, replacing inline rmm_alerts writes; auth/tenant-resolution/tier gating unchanged", "implemented": true, "prdRefs": [ "FR-2", "FR-3" ] }, { "id": "F015", "description": "Tactical webhook routes alert payloads through processRmmAlertEvent, replacing its direct rmm_alerts writes", "implemented": true, "prdRefs": [ "FR-2", "FR-3" ] }, { "id": "F016", "description": "processRmmAlertEvent entry point dispatches by event kind", "implemented": true, "prdRefs": [ "FR-3" ] }, { "id": "F017", "description": "Triggered events upsert rmm_alerts on (tenant, integration_id, external_alert_id) with raw payload stored in metadata jsonb", "implemented": true, "prdRefs": [ "FR-3" ] }, { "id": "F018", "description": "dedup_key computed from device + condition identity and stored on the alert row", "implemented": true, "prdRefs": [ "FR-3" ] }, { "id": "F019", "description": "Rule evaluation selects active rules for the integration ordered by priority_order; first match wins", "implemented": true, "prdRefs": [ "FR-3" ] }, { "id": "F020", "description": "All present condition fields must match: severities, activityTypes, alertClasses, sourceTypes, organizationIds, keywords", "implemented": true, "prdRefs": [ "FR-3" ] }, { "id": "F021", "description": "messagePattern regex condition evaluated; a rule that fails to evaluate is logged and skipped without aborting the pipeline", "implemented": true, "prdRefs": [ "FR-3" ] }, { "id": "F022", "description": "A rule with an empty conditions object matches every alert (catch-all)", "implemented": true, "prdRefs": [ "FR-3" ] }, { "id": "F023", "description": "matched_rule_id persisted on the alert row for later lifecycle decisions", "implemented": true, "prdRefs": [ "FR-3" ] }, { "id": "F024", "description": "No matching rule, or matched rule with createTicket false, stores the alert without creating a ticket", "implemented": true, "prdRefs": [ "FR-3", "FR-4" ] }, { "id": "F025", "description": "Dedup: alert whose dedup_key matches an alert with a still-open linked ticket joins that ticket and increments occurrence_count / last_occurrence_at", "implemented": true, "prdRefs": [ "FR-4" ] }, { "id": "F026", "description": "Dedup repeat adds an internal 're-triggered — Nth occurrence' comment to the existing ticket", "implemented": true, "prdRefs": [ "FR-4" ] }, { "id": "F027", "description": "No open dedup ticket: a new ticket is created via the shared alert ticket creator", "implemented": true, "prdRefs": [ "FR-4" ] }, { "id": "F028", "description": "Ticket creation honors rule actions boardId, priorityOverride, and assignToUserId", "implemented": true, "prdRefs": [ "FR-4" ] }, { "id": "F029", "description": "Severity-to-priority fallback mapping applies when no priorityOverride is set", "implemented": true, "prdRefs": [ "FR-4" ] }, { "id": "F030", "description": "Ticket templates render {{device}}, {{message}}, {{severity}}, {{organization}} placeholders, with sensible defaults when no template is set", "implemented": true, "prdRefs": [ "FR-4" ] }, { "id": "F031", "description": "Created tickets get source + source_reference, an asset association, and an initial internal comment with alert details", "implemented": true, "prdRefs": [ "FR-4" ] }, { "id": "F032", "description": "Client resolution for the ticket: the asset's client, else the organization mapping's client", "implemented": true, "prdRefs": [ "FR-4" ] }, { "id": "F033", "description": "auto_ticket_created set and ticket_id stored on the alert row after ticket creation", "implemented": true, "prdRefs": [ "FR-4" ] }, { "id": "F034", "description": "Reset events mark the alert resolved and set resolved_at", "implemented": true, "prdRefs": [ "FR-5" ] }, { "id": "F035", "description": "Reset with a linked ticket and rule autoResolveTicket always adds a resolution comment; without the flag the ticket is untouched", "implemented": true, "prdRefs": [ "FR-5" ] }, { "id": "F036", "description": "Untouched determination: no human-authored comments, no time entries, no manual status change; rule auto-assignment does not count", "implemented": true, "prdRefs": [ "FR-5" ] }, { "id": "F037", "description": "Untouched tickets close via actions.autoResolveStatusId, else the tenant's first is_closed status", "implemented": true, "prdRefs": [ "FR-5" ] }, { "id": "F038", "description": "Touched tickets stay open after alert reset (comment only)", "implemented": true, "prdRefs": [ "FR-5" ] }, { "id": "F039", "description": "Acknowledged events set alert status acknowledged with acknowledged_at/acknowledged_by", "implemented": true, "prdRefs": [ "FR-5" ] }, { "id": "F040", "description": "Ticket-closed event-bus subscriber finds unresolved linked alerts by ticket_id and honors the matched rule's resetAlertOnTicketClose (default true)", "implemented": true, "prdRefs": [ "FR-5" ] }, { "id": "F041", "description": "NinjaOne outbound adapter resets the alert via NinjaOneClient.resetAlert and updates the local alert status", "implemented": true, "prdRefs": [ "FR-5" ] }, { "id": "F042", "description": "Outbound reset failures are logged and stamped into alert metadata and never block the ticket close", "implemented": true, "prdRefs": [ "FR-5" ] }, { "id": "F043", "description": "Tactical outbound adapter implemented if its API supports alert resolution; otherwise the provider ships without an adapter and the pipeline skips outbound reset", "implemented": true, "prdRefs": [ "FR-5" ] }, { "id": "F044", "description": "listRmmAlertRules server action returns rules for an integration ordered by priority_order", "implemented": true, "prdRefs": [ "FR-6" ] }, { "id": "F045", "description": "createRmmAlertRule server action validates conditions/actions against the shared Zod schemas", "implemented": true, "prdRefs": [ "FR-6" ] }, { "id": "F046", "description": "updateRmmAlertRule server action", "implemented": true, "prdRefs": [ "FR-6" ] }, { "id": "F047", "description": "deleteRmmAlertRule server action", "implemented": true, "prdRefs": [ "FR-6" ] }, { "id": "F048", "description": "reorderRmmAlertRules server action updates priority_order", "implemented": true, "prdRefs": [ "FR-6" ] }, { "id": "F049", "description": "All rule CRUD actions are admin-gated and tenant-scoped", "implemented": true, "prdRefs": [ "FR-6" ] }, { "id": "F050", "description": "Save-time validation rejects an invalid messagePattern regex", "implemented": true, "prdRefs": [ "FR-6" ] }, { "id": "F051", "description": "Alert Rules section renders in RMM integration settings for both NinjaOne and TacticalRMM", "implemented": true, "prdRefs": [ "FR-7" ] }, { "id": "F052", "description": "Rules list is priority-ordered with active toggle, reorder controls, edit and delete", "implemented": true, "prdRefs": [ "FR-7" ] }, { "id": "F053", "description": "Rule editor Match group: severities, activity types, alert classes, source types, keywords, message regex", "implemented": true, "prdRefs": [ "FR-7" ] }, { "id": "F054", "description": "Rule editor organization picker fed from rmm_organization_mappings", "implemented": true, "prdRefs": [ "FR-7" ] }, { "id": "F055", "description": "Rule editor Actions group: create-ticket toggle, board picker, priority override, assignee picker", "implemented": true, "prdRefs": [ "FR-7" ] }, { "id": "F056", "description": "Rule editor title/description template inputs with placeholder hints", "implemented": true, "prdRefs": [ "FR-7" ] }, { "id": "F057", "description": "Rule editor autoResolveTicket and resetAlertOnTicketClose toggles and notify-users picker", "implemented": true, "prdRefs": [ "FR-7" ] }, { "id": "F058", "description": "Rule editor surfaces validation errors inline; delete requires confirmation", "implemented": true, "prdRefs": [ "FR-7" ] }, { "id": "F059", "description": "RMM_ALERT_TRIGGERED registered in the workflow v2 event catalog with a provider-generic payload schema", "implemented": true, "prdRefs": [ "FR-8" ] }, { "id": "F060", "description": "RMM_ALERT_RESOLVED registered in the workflow v2 event catalog", "implemented": true, "prdRefs": [ "FR-8" ] }, { "id": "F061", "description": "Pipeline publishes the workflow v2 alert events, replacing the orphaned legacy-bus publishes", "implemented": true, "prdRefs": [ "FR-8" ] }, { "id": "F062", "description": "rmm.alerts.create_ticket workflow action invokes the shared ticket creator by alert ID", "implemented": true, "prdRefs": [ "FR-8" ] }, { "id": "F063", "description": "rmm-alert notification category registered and honoring per-user notification preferences", "implemented": true, "prdRefs": [ "FR-8" ] }, { "id": "F064", "description": "Matched rule notifyUserIds produce in-app notifications", "implemented": true, "prdRefs": [ "FR-8" ] }, { "id": "F065", "description": "Matched rule notifyUserIds produce email notifications per user preference", "implemented": true, "prdRefs": [ "FR-8" ] }, { "id": "F066", "description": "CSRF validation implemented in the NinjaOne OAuth callback using the state payload's csrf and timestamp", "implemented": true, "prdRefs": [ "FR-9" ] }, { "id": "F067", "description": "ninjaone/alerts modules moved into shared/rmm/alerts with imports updated and the superseded resetInNinjaOne TODO removed", "implemented": true, "prdRefs": [ "FR-9" ] }, { "id": "F068", "description": "rmm_organization_mappings.auto_create_tickets deprecated: no read paths remain; rules are the sole gate for ticket creation", "implemented": true, "prdRefs": [ "FR-9" ] }, { "id": "F069", "description": "Migration creates rmm_maintenance_windows (optional integration/client/asset scopes, one-off starts_at/ends_at, weekly recurrence jsonb, name, is_active)", "implemented": true, "prdRefs": [ "FR-1", "FR-10" ] }, { "id": "F070", "description": "Migration adds suppressed_by_window_id to rmm_alerts; alert status supports 'suppressed'", "implemented": true, "prdRefs": [ "FR-1", "FR-10" ] }, { "id": "F071", "description": "Pipeline checks maintenance windows before rule matching: an active window matching all its non-null scopes at occurredAt suppresses the alert", "implemented": true, "prdRefs": [ "FR-10" ] }, { "id": "F072", "description": "Weekly recurring windows evaluate day and time range in the window's timezone; one-off windows by starts_at/ends_at", "implemented": true, "prdRefs": [ "FR-10" ] }, { "id": "F073", "description": "Suppressed alerts are stored with status suppressed and suppressed_by_window_id: no ticket, no notifications, no workflow events", "implemented": true, "prdRefs": [ "FR-10" ] }, { "id": "F074", "description": "A reset for a suppressed alert resolves it quietly with no ticket actions", "implemented": true, "prdRefs": [ "FR-10" ] }, { "id": "F075", "description": "Maintenance window CRUD server actions (list/create/update/delete), admin-gated, Zod-validated", "implemented": true, "prdRefs": [ "FR-10" ] }, { "id": "F076", "description": "Maintenance Windows settings subsection beside Alert Rules: list plus editor with client/asset scope pickers and one-off or weekly recurring schedule with timezone", "implemented": false, "prdRefs": [ "FR-7", "FR-10" ] }, { "id": "F077", "description": "Per-integration Temporal schedule runs alert reconciliation every N minutes (default 15, configurable 5–60, default on for connected integrations)", "implemented": true, "prdRefs": [ "FR-11" ] }, { "id": "F078", "description": "Poll cycle fetches RMM-active alerts and upserts ones missing locally as triggered events through the shared pipeline", "implemented": true, "prdRefs": [ "FR-11" ] }, { "id": "F079", "description": "Poll cycle synthesizes reset events for local active alerts no longer active in the RMM", "implemented": true, "prdRefs": [ "FR-11" ] }, { "id": "F080", "description": "Poll cycle processes still-active suppressed alerts whose window has ended through the normal rules path", "implemented": true, "prdRefs": [ "FR-10", "FR-11" ] }, { "id": "F081", "description": "Polling enable/disable and interval setting exposed in integration settings UI with 5–60 minute bounds", "implemented": true, "prdRefs": [ "FR-7", "FR-11" ] }, { "id": "F082", "description": "TacticalRMM reconciliation uses its alerts API through the same scheduled workflow", "implemented": true, "prdRefs": [ "FR-11" ] }, { "id": "F083", "description": "Reconciliation schedule is created when an integration connects and removed when it disconnects", "implemented": true, "prdRefs": [ "FR-11" ] } ]