TenantAtlas/specs/100-alert-target-test-actions/plan.md
2026-02-19 00:11:21 +01:00

9.3 KiB

Implementation Plan: Alert Targets — Test Actions + Last Test Status

Branch: feat/100-alert-target-test-actions | Date: 2026-02-18 | Spec: specs/100-alert-target-test-actions/spec.md Input: Feature specification from specs/100-alert-target-test-actions/spec.md

Note: This template is filled in by the /speckit.plan command. See .specify/scripts/ for helper scripts.

Summary

Add a derived “Last test status” indicator to Alert Targets (Filament AlertDestinationResource view and edit pages), plus a capability-gated “Send test message” action that enqueues a test delivery (no outbound calls in the request thread). The status is derived solely from the latest alert_deliveries row with event_type = 'alerts.test' for the destination.

Technical Context

Language/Version: PHP 8.4.15 (Laravel 12)
Primary Dependencies: Filament v5, Livewire v4, Laravel Queue, Laravel Notifications
Storage: PostgreSQL (Sail locally)
Testing: Pest v4 (via vendor/bin/sail artisan test)
Target Platform: Laravel web app (Sail-first locally; Dokploy containers in staging/prod) Project Type: Web application (monolith)
Performance Goals: DB-only page rendering; avoid N+1; derived last-test status computed with a single indexed query
Constraints: No outbound network calls in request/response; no “last test” DB field; deterministic mapping (Never/Sent/Failed/Pending)
Scale/Scope: Workspace-scoped admin UX; minimal UI surfaces (view + edit pages)

Constitution Check

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

  • Inventory-first: N/A (no inventory changes)
  • Read/write separation: test request is a small DB-only write; includes confirmation + audit + tests
  • Graph contract path: no Graph calls
  • Deterministic capabilities: uses existing Capabilities::ALERTS_VIEW / Capabilities::ALERTS_MANAGE registry
  • RBAC-UX: existing workspace policy semantics (non-member 404 via policy) + member-without-capability 403 on action execution
  • Workspace isolation: enforced via WorkspaceContext + AlertDestinationPolicy
  • Destructive confirmations: “Send test message” is not destructive but still requires confirmation per spec
  • Tenant isolation: deliveries viewer remains tenant-safe; tenantless test deliveries are treated as workspace-owned and are safe to reveal
  • Run observability: external sends happen via existing alerts.deliver operation run created by DeliverAlertsJob
  • Data minimization: test payload + errors are sanitized (no webhook URL / recipients)
  • Badge semantics: new badge domains/mappers added; no ad-hoc mappings
  • Filament action surface: edits are confined to edit header actions + read-only status section; action is capability-gated and audited

Project Structure

Documentation (this feature)

specs/100-alert-target-test-actions/
├── plan.md
├── spec.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
└── checklists/

Source Code (repository root)

app/
├── Filament/
│   └── Resources/
│       ├── AlertDestinationResource.php
│       ├── AlertDeliveryResource.php
│       └── AlertDestinationResource/Pages/EditAlertDestination.php
├── Jobs/Alerts/
│   └── DeliverAlertsJob.php
├── Models/
│   └── AlertDelivery.php
├── Policies/
│   ├── AlertDestinationPolicy.php
│   └── AlertDeliveryPolicy.php
├── Services/Alerts/
│   ├── AlertSender.php
│   └── AlertDispatchService.php
└── Support/
  ├── Audit/AuditActionId.php
  └── Badges/

database/migrations/

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

Structure Decision: Laravel monolith; Filament resources under app/Filament, domain services/jobs under app/Services and app/Jobs, tests in tests/.

Complexity Tracking

Fill ONLY if Constitution Check has violations that must be justified

Violation Why Needed Simpler Alternative Rejected Because
[e.g., 4th project] [current need] [why 3 projects insufficient]
[e.g., Repository pattern] [specific problem] [why direct DB access insufficient]

Phase 0 — Outline & Research (completed)

Artifacts:

  • specs/100-alert-target-test-actions/research.md

Key outcomes:

  • Deep link uses Filament v5 filters[...] query string.
  • Timestamp mapping uses existing columns (sent_at, updated_at, send_after).
  • Test deliveries must be tenantless to avoid tenant-entitlement divergence.

Phase 1 — Design & Contracts

Data model changes

The current alert_deliveries table enforces tenant and rule FKs as required. For test sends:

  • Make tenant_id nullable (test deliveries have no tenant context)
  • Make alert_rule_id nullable (test deliveries are not tied to a routing rule)

Also add an index to support the derived “last test” lookup:

  • (workspace_id, alert_destination_id, event_type, created_at)

Backend design

  1. Add a small resolver to compute LastTestStatus from alert_deliveries:
  • Input: workspace_id, alert_destination_id
  • Query: latest delivery with event_type = 'alerts.test'
  • Output: {status, timestamp, delivery_id?}
  1. Add a service for creating a test delivery:
  • Authorize using existing AlertDestinationPolicy::update (manage capability)
  • Enforce rate limit (60s) by checking latest test delivery created_at
  • Create delivery with:
    • workspace_id = current workspace
    • tenant_id = null
    • alert_rule_id = null
    • alert_destination_id = destination id
    • event_type = alerts.test
    • status = queued
    • fingerprint_hash = deterministic stable string (no secrets) (e.g. test:{destination_id})
    • payload = minimal safe text
  1. Queue execution:
  • Dispatch DeliverAlertsJob($workspaceId) so the test is processed without waiting for a scheduler.

Filament UI design

  1. Alert Targets (View page)
  • Provide a read-only view for view-only users.
  • Render the derived “Last test” status (badge + timestamp).
  • Add header actions:
    • Send test message: visible but disabled when user lacks ALERTS_MANAGE.
    • View last delivery: visible only if at least one test delivery exists.
  1. Alert Targets (Edit page)
  • Add a read-only “Last test” status at the top of the edit form using a form component that can render as a badge.
  • Add header actions:
    • Send test message (mutating): Action::make(...)->requiresConfirmation()->action(...)
    • Visible for workspace members with ALERTS_VIEW.
    • Disabled via UI enforcement when user lacks ALERTS_MANAGE.
    • View last delivery (navigation): Action::make(...)->url(...)
    • Visible only if at least one test delivery exists.
  1. Deliveries viewer (List page)
  • Add table filters:
    • event_type (SelectFilter)
    • alert_destination_id (SelectFilter)
  • Adjust tenant entitlement filter to include tenantless deliveries (tenant_id IS NULL).

Authorization & semantics

  • Non-member: existing policies return denyAsNotFound().
  • Member without ALERTS_MANAGE: action execution must result in 403; UI remains visible but disabled.

Audit logging

  • Add a new AuditActionId value: alert_destination.test_requested.
  • Log when a test is requested with redacted metadata (no webhook URL / recipients).

Badge semantics

  • Add badge domain(s) and tests:
    • Alert delivery status badge mapping (to replace the ad-hoc mapping in AlertDeliveryResource).
    • Alert destination last-test status badge mapping.

Contracts

  • Deep link contract + event type contract live in specs/100-alert-target-test-actions/contracts/.

Post-Design Constitution Re-check

  • RBAC-UX: enforced via policies + capability registry; test action server-authorized.
  • BADGE-001: new badge domains + tests planned; no ad-hoc mappings.
  • OPS/run observability: outbound delivery occurs only in queued job; alerts.deliver operation run remains the monitoring source.
  • DB-only rendering: derived status and links use indexed DB queries; no external calls.

Phase 2 — Implementation planning (for tasks.md)

Next steps (to be expanded into tasks.md):

  1. DB migration: make alert_deliveries.tenant_id and alert_rule_id nullable + add supporting index.
  2. Update AlertDelivery model/relationships/casts if needed for tenantless + ruleless deliveries.
  3. Update AlertDeliveryPolicy + AlertDeliveryResource query to allow tenantless deliveries.
  4. Add badge domains + mapping tests.
  5. Add “Send test message” header action + “Last test” badge section to EditAlertDestination.
  6. Add feature tests (Pest) for:
  • derived status mapping (Never/Sent/Failed/Pending)
  • rate limiting
  • RBAC (manage vs view)
  • deep link visibility