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

8.7 KiB

Implementation Plan: 099 — Alerts v1 (Teams + Email)

Branch: 099-alerts-v1-teams-email | Date: 2026-02-16 | Spec: /specs/099-alerts-v1-teams-email/spec.md Input: Feature specification from /specs/099-alerts-v1-teams-email/spec.md

Summary

Implement workspace-scoped alerting with:

  • Destinations (Targets): Microsoft Teams incoming webhook and Email recipients.
  • Rules: route by event type, minimum severity, and tenant scope.
  • Noise controls: deterministic fingerprint dedupe, per-rule cooldown suppression, and quiet-hours deferral.
  • Delivery history: read-only, includes suppressed entries.

Delivery is queue-driven with bounded exponential backoff retries. All alert pages remain DB-only at render time and never expose destination secrets.

Technical Context

Language/Version: PHP 8.4 (Laravel 12) Primary Dependencies: Filament v5 (Livewire v4.0+), Laravel Queue (database default) Storage: PostgreSQL (Sail) Testing: Pest v4 via vendor/bin/sail artisan test --compact Target Platform: Laravel web app (Filament Admin) Project Type: Web application Performance Goals: Eligible alerts delivered within ~2 minutes outside quiet hours (SC-002) Constraints:

  • DB-only rendering for Targets/Rules/Deliveries pages (FR-015)
  • No destination secrets in logs/audit payloads (FR-011)
  • Retries use exponential backoff + bounded max attempts (FR-017) Scale/Scope: Workspace-owned configuration + tenant-owned delivery history (90-day retention) (FR-016)

Constitution Check

GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.

  • Livewire/Filament: Filament v5 implies Livewire v4.0+ (compliant).
  • Provider registration: No new panel provider required; existing registration remains in bootstrap/providers.php.
  • RBAC semantics: Enforce non-member → 404 (deny-as-not-found) and member missing capability → 403.
  • Capability registry: Add ALERTS_VIEW and ALERTS_MANAGE to canonical registry; role maps reference only registry constants.
  • Destructive actions: Deletes and other destructive-like actions use ->requiresConfirmation() and execute via ->action(...).
  • Run observability: Scheduled/queued scanning + deliveries create/reuse OperationRun for Monitoring → Operations visibility.
  • Safe logging: Audit logging uses WorkspaceAuditLogger (sanitizes context) and never records webhook URLs / recipient lists.
  • Global search: No new global search surfaces are required for v1; if enabled later, resources must have Edit/View pages and remain workspace-safe.

Result: PASS, assuming the above constraints are implemented and covered by tests.

Project Structure

Documentation (this feature)

specs/099-alerts-v1-teams-email/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
└── tasks.md  # created later by /speckit.tasks

Source Code (repository root)

app/
├── Filament/
│   ├── Pages/
│   └── Resources/
├── Jobs/
├── Models/
├── Policies/
├── Services/
│   ├── Audit/
│   ├── Auth/
│   └── Settings/
└── Support/
    ├── Auth/
    └── Rbac/

database/
└── migrations/

tests/
├── Feature/
└── Unit/

Structure Decision: Use standard Laravel + Filament discovery conventions. Add Eloquent models + migrations for workspace-owned alert configuration + tenant-owned alert deliveries, queue jobs for evaluation + delivery, and Filament Resources/Pages under the existing Admin panel.

Phase 0 — Outline & Research (output: research.md)

Unknowns / decisions to lock:

  • Teams delivery should use Laravel HTTP client (Http::post()) with timeouts and safe error capture.
  • Email delivery should use Laravel mail/notifications and be queued.
  • Quiet-hours timezone fallback: rule timezone if set; else workspace timezone; if no workspace timezone exists yet, fallback to config('app.timezone').
  • Secrets storage: use encrypted casts (encrypted / encrypted:array) for webhook URLs and recipient lists.
  • Retries/backoff: use job tries and backoff() for exponential backoff with a max attempt cap.

Phase 1 — Design & Contracts (outputs: data-model.md, contracts/*, quickstart.md)

Data model

Workspace-owned entities:

  • AlertDestination:

    • workspace_id
    • name
    • type (teams_webhook | email)
    • is_enabled
    • config (encrypted array; contains webhook URL or recipient list)
  • AlertRule:

    • workspace_id
    • name
    • is_enabled
    • event_type (high_drift | compare_failed | sla_due)
    • minimum_severity
    • tenant_scope_mode (all | allowlist)
    • tenant_allowlist (array of tenant IDs)
    • cooldown_seconds
    • quiet_hours_enabled, quiet_hours_start, quiet_hours_end, quiet_hours_timezone
  • AlertRuleDestination (pivot): workspace_id, alert_rule_id, alert_destination_id

  • AlertDelivery (history):

    • workspace_id
    • tenant_id
    • alert_rule_id, alert_destination_id
    • fingerprint_hash
    • status (queued | deferred | sent | failed | suppressed | canceled)
    • send_after (for quiet-hours deferral)
    • attempt_count, last_error_code, last_error_message (sanitized)
    • timestamps

Retention: prune deliveries older than 90 days (default).

Contracts

Create explicit schema/contracts for:

  • Alert rule/destination create/edit payloads (validation expectations)
  • Delivery record shape (what UI displays)
  • Domain event shapes used for fingerprinting (no secrets)

Filament surfaces

  • Targets: CRUD destinations. Confirm on delete. Never display secrets once saved.
  • Rules: CRUD rules, enable/disable. Confirm destructive actions.
  • Deliveries: read-only viewer.

RBAC enforcement:

  • Page access: ALERTS_VIEW.
  • Mutations: ALERTS_MANAGE.
  • Non-member: deny-as-not-found (404) consistently.
  • Non-member: deny-as-not-found (404) consistently.
  • Deliveries are tenant-owned and MUST only be listed/viewable for tenants the actor is entitled to; non-entitled tenants are filtered and treated as not found (404 semantics).
  • If a tenant-context is active in the current session, the Deliveries view SHOULD default-filter to that tenant.

Background processing (jobs + OperationRuns)

  • alerts.evaluate run: scans for new triggering events and creates AlertDelivery rows (including suppressed).
  • alerts.deliver run: sends due deliveries (respecting send_after).

Trigger sources (repo-grounded):

  • High Drift: derived from persisted drift findings (Finding records) with severity High/Critical where the finding is in status=new (unacknowledged). “Newly active/visible” means the finding first appears (a new Finding row is created), not that the same existing finding is re-alerted on every evaluation cycle.
  • Compare Failed: derived from failed drift-generation operations (OperationRun where type = drift_generate_findings and outcome = failed).
  • SLA Due: v1 implements this trigger as a safe no-op unless/until the underlying data model provides a due-date signal.

Scheduling convention:

  • A scheduled console command (tenantpilot:alerts:dispatch) runs every minute (registered in routes/console.php) and dispatches the evaluate + deliver work idempotently.

Idempotency:

  • Deterministic fingerprint; unique constraints where appropriate.
  • Delivery send job transitions statuses atomically; if already terminal (sent/failed/canceled), it no-ops.

Audit logging

All destination/rule mutations log via WorkspaceAuditLogger with redacted metadata:

  • Record IDs, names, types, enabled flags, rule criteria.
  • Never include webhook URLs or recipient lists.

Phase 2 — Task Planning (outline; tasks.md comes next)

  1. Capabilities & policies
  • Add ALERTS_VIEW / ALERTS_MANAGE to App\Support\Auth\Capabilities.
  • Update WorkspaceRoleCapabilityMap.
  • Add Policies for new models and enforce 404/403 semantics.
  1. Migrations + models
  • Create migrations + Eloquent models for destinations/rules/pivot/deliveries.
  • Add encrypted casts and safe $hidden where appropriate.
  1. Services
  • Fingerprint builder
  • Quiet hours evaluator
  • Dispatcher to create deliveries and enqueue send jobs
  1. Jobs
  • Evaluate triggers job
  • Send delivery job with exponential backoff + max attempts
  1. Filament UI
  • Implement Targets/Rules/Deliveries pages with action surfaces and confirmation.
  1. Tests (Pest)
  • RBAC: 404 for non-members; 403 for members missing capability.
  • Cooldown/dedupe: persists suppressed delivery history.
  • Retry policy: transitions to failed after bounded attempts.

Complexity Tracking

No constitution violations are required for this feature.