222 lines
9.3 KiB
Markdown
222 lines
9.3 KiB
Markdown
# 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
|
|
|
|
<!--
|
|
ACTION REQUIRED: Replace the content in this section with the technical details
|
|
for the project. The structure here is presented in advisory capacity to guide
|
|
the iteration process.
|
|
-->
|
|
|
|
**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)
|
|
|
|
```text
|
|
specs/100-alert-target-test-actions/
|
|
├── plan.md
|
|
├── spec.md
|
|
├── research.md
|
|
├── data-model.md
|
|
├── quickstart.md
|
|
├── contracts/
|
|
└── checklists/
|
|
```
|
|
|
|
### Source Code (repository root)
|
|
<!--
|
|
ACTION REQUIRED: Replace the placeholder tree below with the concrete layout
|
|
for this feature. Delete unused options and expand the chosen structure with
|
|
real paths (e.g., apps/admin, packages/something). The delivered plan must
|
|
not include Option labels.
|
|
-->
|
|
|
|
```text
|
|
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?}`
|
|
|
|
2. 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
|
|
|
|
3. 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.
|
|
|
|
2. 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.
|
|
|
|
3. 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
|