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
117 lines
3.7 KiB
Markdown
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`.
|