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_MANAGEregistry - 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.deliveroperation run created byDeliverAlertsJob - 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_idnullable (test deliveries have no tenant context) - Make
alert_rule_idnullable (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
- Add a small resolver to compute
LastTestStatusfromalert_deliveries:
- Input:
workspace_id,alert_destination_id - Query: latest delivery with
event_type = 'alerts.test' - Output:
{status, timestamp, delivery_id?}
- 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 workspacetenant_id= nullalert_rule_id= nullalert_destination_id= destination idevent_type=alerts.teststatus=queuedfingerprint_hash= deterministic stable string (no secrets) (e.g.test:{destination_id})payload= minimal safe text
- Queue execution:
- Dispatch
DeliverAlertsJob($workspaceId)so the test is processed without waiting for a scheduler.
Filament UI design
- 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.
- Send test message: visible but disabled when user lacks
- 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.
- Send test message (mutating):
- 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
AuditActionIdvalue: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.
- Alert delivery status badge mapping (to replace the ad-hoc mapping in
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.deliveroperation 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):
- DB migration: make
alert_deliveries.tenant_idandalert_rule_idnullable + add supporting index. - Update
AlertDeliverymodel/relationships/casts if needed for tenantless + ruleless deliveries. - Update
AlertDeliveryPolicy+AlertDeliveryResourcequery to allow tenantless deliveries. - Add badge domains + mapping tests.
- Add “Send test message” header action + “Last test” badge section to
EditAlertDestination. - Add feature tests (Pest) for:
- derived status mapping (Never/Sent/Failed/Pending)
- rate limiting
- RBAC (manage vs view)
- deep link visibility