TenantAtlas/specs/099-alerts-v1-teams-email/data-model.md
2026-02-18 15:25:14 +01:00

3.7 KiB

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:

  • queuedsent | failed | suppressed | canceled
  • deferredqueued (when window opens) → sent | failed

Terminal states: sent, failed, suppressed, canceled.