spec: add feature 100 alert target test actions

This commit is contained in:
Ahmed Darrazi 2026-02-19 00:11:21 +01:00
parent 3ed275cef3
commit 270181d509
10 changed files with 630 additions and 2 deletions

View File

@ -29,6 +29,7 @@ ## Active Technologies
- PHP 8.4 (Laravel 12) + Filament v5, Livewire v4, Laravel Sail, Tailwind CSS v4 (085-tenant-operate-hub)
- PostgreSQL (Sail), SQLite in tests (087-legacy-runs-removal)
- PHP 8.4.x + Laravel 12, Filament v5, Livewire v4, Microsoft Graph integration via `GraphClientInterface` (095-graph-contracts-registry-completeness)
- PHP 8.4.15 (Laravel 12) + Filament v5, Livewire v4, Laravel Queue, Laravel Notifications (100-alert-target-test-actions)
- PHP 8.4.15 (feat/005-bulk-operations)
@ -48,8 +49,7 @@ ## Code Style
PHP 8.4.15: Follow standard conventions
## Recent Changes
- 100-alert-target-test-actions: Added PHP 8.4.15 (Laravel 12) + Filament v5, Livewire v4, Laravel Queue, Laravel Notifications
- 095-graph-contracts-registry-completeness: Added PHP 8.4.x + Laravel 12, Filament v5, Livewire v4, Microsoft Graph integration via `GraphClientInterface`
- 090-action-surface-contract-compliance: Added PHP 8.4.15
- 087-legacy-runs-removal: Added PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4
<!-- MANUAL ADDITIONS START -->
<!-- MANUAL ADDITIONS END -->

View File

@ -70,6 +70,7 @@ ### Tenant Isolation is Non-negotiable
- Tenant-owned tables MUST include workspace_id and tenant_id as NOT NULL.
- Workspace-owned tables MUST include workspace_id and MUST NOT include tenant_id.
- Exception: OperationRun MAY have tenant_id nullable to support canonical workspace-context monitoring views; however, revealing any tenant-bound runs still MUST enforce entitlement checks to the referenced tenant scope.
- Exception: AlertDelivery MAY have tenant_id nullable for workspace-scoped, non-tenant-operational artifacts (e.g., `event_type=alerts.test`). Tenant-bound delivery records still MUST enforce tenant entitlement checks, and tenantless delivery rows MUST NOT contain tenant-specific data.
### RBAC & UI Enforcement Standards (RBAC-UX)

View File

@ -0,0 +1,34 @@
# Specification Quality Checklist: Alert Targets Test Actions
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-02-18
**Feature**: [specs/100-alert-target-test-actions/spec.md](../spec.md)
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- Spec is ready for `/speckit.plan`.

View File

@ -0,0 +1,17 @@
# Contract — Alert Event Types (workspace alerts)
## Overview
This add-on introduces a single new event type used for *test deliveries*.
## Event Type
- `alerts.test`
- Purpose: user-triggered “Send test message” action on an alert target.
- Storage: `alert_deliveries.event_type`
- Delivery: sent via the existing alert delivery pipeline (`DeliverAlertsJob` + `AlertSender`).
## Notes
- This is an internal event type string; it is not a Microsoft Graph contract.
- No secrets are stored in payload/error text.

View File

@ -0,0 +1,32 @@
# Contract — Filament deep link into Alert Deliveries
## Goal
Allow a deterministic deep link from an Alert Target (destination) to the Deliveries viewer filtered to the most relevant rows.
## Target
- Resource: `AlertDeliveryResource`
- Page: list/index
## Query-string filter contract (Filament v5)
Use Filaments `filters[...]` query-string state to pre-apply table filters:
- Filter by event type:
- `filters[event_type][value]=alerts.test`
- Filter by destination:
- `filters[alert_destination_id][value]={DESTINATION_ID}`
Combined example:
`/admin/alert-deliveries?filters[event_type][value]=alerts.test&filters[alert_destination_id][value]=123`
## Required table filters
The Deliveries list must define these filters with matching keys:
- `SelectFilter::make('event_type')`
- `SelectFilter::make('alert_destination_id')`
(These are additive to the existing `status` filter.)

View File

@ -0,0 +1,79 @@
# Phase 1 — Data Model (099.1 Add-on)
## Entities
### AlertDestination (existing)
- **Table**: `alert_destinations`
- **Ownership**: workspace-owned
- **Key fields (relevant here)**:
- `id`
- `workspace_id`
- `type` (e.g. Teams webhook, Email)
- `config` (Teams webhook URL or list of recipients)
- `is_enabled`
### AlertDelivery (existing, extended for test sends)
- **Table**: `alert_deliveries`
- **Ownership**:
- v1 deliveries for real alerts remain tenant-associated
- **test deliveries for this add-on are tenantless** (workspace-only)
- **Key fields (relevant here)**:
- `id`
- `workspace_id` (required)
- `tenant_id` (**nullable for test deliveries**)
- `alert_rule_id` (**nullable for test deliveries**)
- `alert_destination_id` (required)
- `event_type` (string, includes `alerts.test`)
- `status` (`queued|deferred|sent|failed|suppressed|canceled`)
- `send_after` (nullable; used for deferral/backoff)
- `sent_at` (nullable)
- `attempt_count`
- `last_error_code`, `last_error_message` (sanitized)
- `payload` (array/json)
- timestamps: `created_at`, `updated_at`
## Relationships
- `AlertDelivery``AlertDestination` (belongsTo via `alert_destination_id`)
- `AlertDelivery``AlertRule` (belongsTo via `alert_rule_id`, nullable)
- `AlertDelivery``Tenant` (belongsTo via `tenant_id`, nullable)
## New derived concepts (no storage)
### LastTestStatus (derived)
Derived from the most recent `alert_deliveries` record where:
- `alert_destination_id = {destination}`
- `event_type = 'alerts.test'`
Mapping:
- no record → `Never`
- `status in (queued, deferred)``Pending`
- `status = sent``Sent`
- `status = failed``Failed`
Associated timestamp (derived):
- Sent → `sent_at`
- Failed → `updated_at`
- Pending → `send_after` (fallback `created_at`)
## Validation / invariants
- Creating a test delivery requires:
- `alert_destination_id` exists and belongs to current workspace
- destination is enabled (if disabled, refuse test request)
- rate limit: no prior test delivery for this destination in last 60 seconds
- Test delivery record must not persist secrets in payload or error message.
## Migration notes
To support tenantless test deliveries:
- Make `alert_deliveries.tenant_id` nullable and adjust the FK behavior.
- Make `alert_deliveries.alert_rule_id` nullable and adjust the FK behavior.
- Add or adjust indexes for efficient status lookup per destination + event type:
- `(workspace_id, alert_destination_id, event_type, created_at)`
(Exact migration steps and DB constraint changes are specified in the implementation plan.)

View File

@ -0,0 +1,221 @@
# 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

View File

@ -0,0 +1,20 @@
# Quickstart (099.1 Add-on)
## Prereqs
- Laravel Sail is used for local dev.
## Run locally
- Start containers: `vendor/bin/sail up -d`
- Run migrations: `vendor/bin/sail artisan migrate`
## Run tests (focused)
- `vendor/bin/sail artisan test --compact --filter=AlertTargetTest`
(Replace filter/name with the final Pest test names once implemented.)
## Format
- `vendor/bin/sail bin pint --dirty`

View File

@ -0,0 +1,68 @@
# Phase 0 — Research (099.1 Add-on)
This research resolves the open technical questions from the feature spec and anchors decisions to existing code in this repository.
## Decision 1 — Where “Alert Target View/Edit” lives in code
- **Decision**: Implement “Last test status” + “Send test message” on the Alert Target **Edit** page only.
- **Rationale**: The current Filament resource for alert targets is `AlertDestinationResource` and only defines `index/create/edit` pages (no View page).
- **Alternatives considered**:
- Add a new View page for `AlertDestinationResource` (rejected: expands UX surface and is not required for this add-on).
## Decision 2 — Canonical event type string
- **Decision**: Use a single canonical event type string for test sends: `alerts.test`.
- **Rationale**: The add-on spec requires deriving status from `alert_deliveries.event_type`. Keeping this string stable allows deterministic filtering and deep links.
- **Alternatives considered**:
- Use `test` or `destination_test` (rejected: deviates from spec and makes intent less explicit).
## Decision 3 — Timestamp mapping vs actual schema
- **Decision**: Map timestamps using existing columns:
- **Sent**`sent_at`
- **Failed**`updated_at` (no `failed_at` column exists)
- **Pending**`send_after` if set, else `created_at`
- **Rationale**: The `alert_deliveries` schema only has `send_after`, `sent_at`, and `updated_at`. The specs `deliver_at` / `failed_at` naming is treated as conceptual.
- **Alternatives considered**:
- Add `failed_at` / `deliver_at` columns (rejected: violates “no new DB fields”).
## Decision 4 — How to represent test deliveries given current tenant enforcement
- **Decision**: Store test deliveries in `alert_deliveries` as **workspace-scoped, tenantless** records:
- `workspace_id` set
- `tenant_id` nullable
- `alert_rule_id` nullable
- `alert_destination_id` set
- **Rationale**:
- Alert targets (`AlertDestination`) are workspace-owned and accessible without a selected tenant.
- Current enforcement (`DerivesWorkspaceIdFromTenant`) requires a tenant and would make test deliveries invisible/uneven across users due to tenant entitlements.
- Deliver job execution (`DeliverAlertsJob`) does not require a tenant; it only needs the destination + payload.
- **Alternatives considered**:
- Pick an arbitrary tenant ID for test deliveries (rejected: would be invisible to some operators and breaks “single truth” per target).
- Create a synthetic “workspace tenant” record (rejected: adds data-model complexity).
## Decision 5 — Deep link mechanics for the Deliveries viewer
- **Decision**: Deep link into `AlertDeliveryResource` list view using Filament v5 filter query-string binding:
- `?filters[event_type][value]=alerts.test&filters[alert_destination_id][value]={DESTINATION_ID}`
- **Rationale**: Filament v5 `ListRecords` binds `tableFilters` to the URL under the `filters` key; `SelectFilter` uses `value`.
- **Alternatives considered**:
- Custom dedicated “Test deliveries” page (rejected: new surface beyond spec).
## Decision 6 — Badge semantics centralization (BADGE-001)
- **Decision**: Introduce centralized badge domains/mappers for:
- `AlertDeliveryStatus` (queued/deferred/sent/failed/suppressed/canceled)
- `AlertDestinationLastTestStatus` (never/pending/sent/failed)
- **Rationale**: Current `AlertDeliveryResource` implements ad-hoc status label/color helpers. This add-on must comply with BADGE-001 and should not add more local mappings.
- **Alternatives considered**:
- Keep ad-hoc mappings in the new code (rejected: violates BADGE-001).
## Decision 7 — OperationRun usage
- **Decision**: Do **not** create a new per-test `OperationRun`.
- **Rationale**:
- The user-triggered action is DB-only (authorization + rate limit + create delivery + audit), typically <2s.
- The external work runs via the existing `alerts.deliver` operation run (created by `DeliverAlertsJob`).
- **Alternatives considered**:
- Create a dedicated operation run type (rejected: higher complexity for small UX add-on).

View File

@ -0,0 +1,156 @@
# Feature Specification: Alert Targets Test Actions
**Feature Branch**: `feat/100-alert-target-test-actions`
**Created**: 2026-02-18
**Status**: Draft
**Input**: User description: "099.1 — Alert Targets: Send Test Message + Last Test Status (Teams + Email)"
## Spec Scope Fields *(mandatory)*
- **Scope**: workspace
- **Primary Routes**: Alert Target View, Alert Target Edit, Deliveries viewer (filtered deep links)
- **Data Ownership**:
- Workspace-owned: alert targets
- Tenant-owned: alert delivery history (non-test)
- Workspace-scoped: test deliveries (`event_type=alerts.test`) may be tenantless (`tenant_id` nullable)
- **RBAC**:
- Workspace membership is required to access Alert Targets and Deliveries.
- Users with manage capability can request a test send.
- Users with view-only capability can see test status but cannot request a test send.
For canonical-view specs, the spec MUST define:
- **Default filter behavior when tenant-context is active**: Not applicable (workspace scope).
- **Explicit entitlement checks preventing cross-tenant leakage**: Targets and deliveries are only visible within the current workspace; non-members must not receive any existence hints.
## Clarifications
### Session 2026-02-18
- Q: Which timestamps should the “Last test … at <timestamp>” subtext use? → A: Sent → `sent_at`; Failed → `updated_at`; Pending → `send_after`.
- Q: What should the test-send rate limit be per target? → A: 60 seconds per target.
- Q: What should “View last delivery” open? → B: Deliveries list viewer filtered to `alert_destination_id` + `event_type=alerts.test`.
- Q: When should “View last delivery” be shown on the Alert Target pages? → B: Show only if at least one test delivery exists.
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Send a test message for a target (Priority: P1)
As an admin, I want to send a test alert to a configured Alert Target so I can verify the integration works before relying on it in production.
**Why this priority**: This is the fastest way to detect misconfiguration (wrong destination, blocked network path, invalid credentials) and reduce support/incident time.
**Independent Test**: Can be fully tested by requesting a test send and confirming a new test delivery record exists and can be inspected.
**Acceptance Scenarios**:
1. **Given** I am a workspace member with manage permission, **When** I confirm “Send test message” on an Alert Target, **Then** the system creates exactly one new test delivery record for that target and indicates the request was queued.
2. **Given** I am a workspace member without manage permission, **When** I attempt to execute “Send test message”, **Then** the action is blocked and no delivery record is created.
3. **Given** I have requested a test very recently for the same target, **When** I attempt another test immediately, **Then** the system refuses the request and does not create a new delivery record.
---
### User Story 2 - See the last test status at a glance (Priority: P2)
As an admin, I want to see whether the last test send for an Alert Target succeeded, failed, was never executed, or is still pending so I can assess health without digging through deliveries.
**Why this priority**: Health visibility reduces troubleshooting time and prevents silent failures.
**Independent Test**: Can be fully tested by viewing a target with (a) no test deliveries, (b) a successful test delivery, (c) a failed test delivery and verifying the badge and timestamp.
**Acceptance Scenarios**:
1. **Given** a target has no test delivery records, **When** I open the target View or Edit page, **Then** I see a badge “Last test: Never”.
2. **Given** the most recent test delivery record is successful, **When** I open the target View or Edit page, **Then** I see a badge “Last test: Sent” and an associated timestamp.
3. **Given** the most recent test delivery record is failed, **When** I open the target View or Edit page, **Then** I see a badge “Last test: Failed” and an associated timestamp.
4. **Given** the most recent test delivery record is queued or deferred, **When** I open the target View or Edit page, **Then** I see a badge “Last test: Pending”.
---
### User Story 3 - Jump from target to the relevant delivery details (Priority: P3)
As an admin, I want a quick link from the Alert Target to the most recent test delivery details so I can troubleshoot outcomes efficiently.
**Why this priority**: It reduces clicks and prevents mistakes when searching through delivery history.
**Independent Test**: Can be tested by clicking a “View last delivery” link and verifying the deliveries view is pre-filtered for this target and the test event type.
**Acceptance Scenarios**:
1. **Given** a target has at least one test delivery record, **When** I click “View last delivery” from the target page, **Then** I am taken to the deliveries list viewer scoped to the same workspace and filtered to that target and the test event type.
### Edge Cases
- Target exists but has never been tested.
- Target has multiple test deliveries; only the most recent one is used for status.
- The most recent test delivery is queued/deferred; status must show pending without implying success.
- Users without workspace membership attempt to access targets or deliveries (must be deny-as-not-found).
- Failure details must not expose secrets (destination URLs, recipients).
- Rapid repeated test requests (anti-spam / rate limiting) must not create additional delivery records.
## Requirements *(mandatory)*
**Constitution alignment (required):** This feature introduces a user-triggered action that creates a delivery record and schedules work to be executed asynchronously.
The spec requires: confirmation, RBAC enforcement, anti-spam rate limiting, audit logging, tenant/workspace isolation, and tests.
**Constitution alignment (RBAC-UX):**
- Authorization planes involved: Admin UI (workspace-scoped).
- 404 vs 403 semantics:
- Non-member / not entitled to workspace scope → 404 (deny-as-not-found)
- Workspace member but missing manage capability → 403 for executing the test action
- All mutations (test request) require server-side authorization.
**Constitution alignment (BADGE-001):** “Last test: Sent/Failed/Pending/Never” MUST use centralized badge semantics (no ad-hoc mappings).
**Constitution alignment (Filament Action Surfaces):** This feature modifies Filament pages (Alert Target view/edit) and therefore includes a UI Action Matrix.
### Functional Requirements
- **FR-001 (Derived status, no new fields)**: The system MUST display a “Last test status” indicator on Alert Target View and Edit pages derived solely from existing alert delivery records.
- **FR-002 (Deterministic selection)**: The “Last test status” MUST be derived from the single most recent delivery record for the given target where the delivery event type is “alerts.test”, ordered by `created_at` (desc) then `id` (desc).
- **FR-003 (Status mapping)**: The system MUST map the most recent test delivery record to one of: Never (no record), Sent, Failed, or Pending.
- **FR-004 (Timestamp semantics)**: The UI MUST display a timestamp that reflects when the outcome occurred: Sent → `sent_at`; Failed → `updated_at`; Pending → `send_after`.
- **FR-005 (DB-only UI)**: Requesting a test send MUST not perform synchronous external delivery attempts in the users request/response flow.
- **FR-006 (Confirmation)**: The “Send test message” action MUST require explicit confirmation, explaining that it will contact the configured destination.
- **FR-007 (Anti-spam rate limit)**: The system MUST prevent repeated test requests for the same target within 60 seconds.
- **FR-008 (RBAC)**: Only workspace members with manage permission can request a test send; view-only users can see the action but cannot execute it.
- **FR-009 (Deny-as-not-found)**: Users without workspace membership MUST receive deny-as-not-found behavior for targets and deliveries.
- **FR-010 (Auditability)**: The system MUST record an audit event when a test is requested, without including destination secrets.
- **FR-011 (Deep link)**: The system SHOULD provide a “View last delivery” link from the target to the Deliveries list viewer filtered to that target and `event_type=alerts.test`.
- **FR-012 (Deep link visibility)**: The system SHOULD show “View last delivery” only when at least one test delivery exists for the target.
### Assumptions
- Alerts v1 already provides Alert Targets, Alert Deliveries, and a Deliveries viewer.
- A test send is represented as a delivery record with event type “alerts.test”.
### Non-Goals
- No new database fields for storing “last test status”.
- No bulk “test all targets” feature.
- No destination setup wizard.
- No per-row list view badge (avoids performance/N+1 concerns in v1).
## UI Action Matrix *(mandatory when Filament is changed)*
| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|---|---|---|---|---|---|---|---|---|---|---|
| Alert Target (View) | Alert Target view page | Send test message (confirm, manage-only), View last delivery (navigate) | Not changed in this feature | Not changed in this feature | None | None | Same as Header Actions | Not applicable | Yes | View-only users see disabled “Send test message”. |
| Alert Target (Edit) | Alert Target edit page | Send test message (confirm, manage-only), View last delivery (navigate) | Not changed in this feature | Not changed in this feature | None | None | Same as Header Actions | Existing save/cancel | Yes | “Last test status” appears above the form. |
| Deliveries viewer | Deliveries list/details | None (existing) | Filtered via deep link | Existing row actions | Existing | Existing | Existing | Not applicable | Existing | Must remain workspace-scoped. |
### Key Entities *(include if feature involves data)*
- **Alert Target**: A destination configuration for alerts (e.g., Teams webhook or email destination), scoped to a workspace.
- **Alert Delivery**: A delivery attempt record that captures event type (including “alerts.test”), status, timestamps, and safe diagnostic details.
- **Audit Event**: A workspace-scoped audit entry representing a user-triggered test request.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: An admin can determine “last test status” for a target within 5 seconds from the target page.
- **SC-002**: An admin can request a test send in under 30 seconds (including confirmation) without leaving the target page.
- **SC-003**: A test request always results in either (a) a new test delivery record being created, or (b) a clear refusal due to rate limiting or missing permissions.
- **SC-004**: Troubleshooting time for “alerts not delivered” issues is reduced because the last test outcome and a direct link to details are immediately available.