# 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) ```text 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) ```text 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. 2) Migrations + models - Create migrations + Eloquent models for destinations/rules/pivot/deliveries. - Add encrypted casts and safe `$hidden` where appropriate. 3) Services - Fingerprint builder - Quiet hours evaluator - Dispatcher to create deliveries and enqueue send jobs 4) Jobs - Evaluate triggers job - Send delivery job with exponential backoff + max attempts 5) Filament UI - Implement Targets/Rules/Deliveries pages with action surfaces and confirmation. 6) 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.