TenantAtlas/specs/099-alerts-v1-teams-email/data-model.md
ahmido 3ed275cef3 feat(alerts): Monitoring cluster + v1 resources (spec 099) (#121)
Implements spec `099-alerts-v1-teams-email`.

- Monitoring navigation: Alerts as a cluster under Monitoring; default landing is Alert deliveries.
- Tenant panel: Alerts points to `/admin/alerts` and the cluster navigation is hidden in tenant panel.
- Guard compliance: removes direct `Gate::` usage from Alert resources so `NoAdHocFilamentAuthPatternsTest` passes.

Verification:
- Full suite: `1348 passed, 7 skipped` (EXIT=0).

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #121
2026-02-18 15:20:43 +00:00

117 lines
3.7 KiB
Markdown

# Data Model — 099 Alerts v1 (Teams + Email)
Scope: **workspace-owned** configuration and **tenant-owned** delivery history. Deliveries are always tenant-scoped (`tenant_id` NOT NULL) and must only be listed/viewed for tenants the actor is entitled to (non-entitled tenants are treated as not found / 404 semantics).
## Entities
## AlertDestination (workspace-owned)
Purpose: reusable delivery target.
Fields:
- `id`
- `workspace_id` (FK, required)
- `name` (string, required)
- `type` (enum: `teams_webhook` | `email`, required)
- `is_enabled` (bool, default true)
- `config` (encrypted:array, required)
- for `teams_webhook`: `{ "webhook_url": "https://..." }`
- for `email`: `{ "recipients": ["a@example.com", "b@example.com"] }`
- timestamps
Validation rules:
- `name`: required, max length
- `type`: required, in allowed values
- `config.webhook_url`: required if type is teams; must be URL
- `config.recipients`: required if type is email; array of valid email addresses; must be non-empty
Security:
- `config` must never be logged or included in audit metadata.
## AlertRule (workspace-owned)
Purpose: routing + noise controls.
Fields:
- `id`
- `workspace_id` (FK, required)
- `name` (string, required)
- `is_enabled` (bool, default true)
- `event_type` (enum: `high_drift` | `compare_failed` | `sla_due`, required)
- `minimum_severity` (enum: `low` | `medium` | `high` | `critical`, required)
- `tenant_scope_mode` (enum: `all` | `allowlist`, required)
- `tenant_allowlist` (array, default empty)
- `cooldown_seconds` (int, nullable)
- `quiet_hours_enabled` (bool, default false)
- `quiet_hours_start` (string, e.g. `22:00`, nullable)
- `quiet_hours_end` (string, e.g. `06:00`, nullable)
- `quiet_hours_timezone` (IANA TZ string, nullable)
- timestamps
Validation rules:
- `name`: required
- `event_type`: required
- `minimum_severity`: required
- `tenant_allowlist`: required if `tenant_scope_mode=allowlist`
- quiet hours:
- if enabled: start/end required, valid HH:MM, timezone optional
Notes:
- Quiet-hours timezone resolution:
- rule timezone if set
- else workspace timezone
- else `config('app.timezone')`
## AlertRuleDestination (workspace-owned pivot)
Fields:
- `id`
- `workspace_id` (FK, required)
- `alert_rule_id` (FK)
- `alert_destination_id` (FK)
- timestamps
Constraints:
- Unique `(alert_rule_id, alert_destination_id)`
## AlertDelivery (tenant-owned history)
Purpose: immutable record of queued/sent/failed/deferred/suppressed deliveries.
Fields:
- `id`
- `workspace_id` (FK, required)
- `tenant_id` (FK, required)
- `alert_rule_id` (FK, required)
- `alert_destination_id` (FK, required)
- `fingerprint_hash` (string, required)
- `status` (enum: `queued` | `deferred` | `sent` | `failed` | `suppressed` | `canceled`)
- `send_after` (timestamp, nullable)
- `attempt_count` (int, default 0)
- `last_error_code` (string, nullable)
- `last_error_message` (string, nullable; sanitized)
- `sent_at` (timestamp, nullable)
- timestamps
Indexes:
- `(workspace_id, created_at)` for history listing
- `(workspace_id, status, send_after)` for dispatching due deferred deliveries
- `(workspace_id, alert_rule_id, fingerprint_hash)` for dedupe/cooldown checks
Retention:
- Default prune: 90 days.
## Relationships
- `AlertRule` hasMany `AlertRuleDestination` and belongsToMany `AlertDestination`.
- `AlertDestination` belongsToMany `AlertRule`.
- `AlertDelivery` belongsTo `AlertRule`, belongsTo `AlertDestination`, and belongsTo `Tenant`.
## State transitions
`AlertDelivery.status` transitions:
- `queued``sent` | `failed` | `suppressed` | `canceled`
- `deferred``queued` (when window opens) → `sent` | `failed`
Terminal states: `sent`, `failed`, `suppressed`, `canceled`.