spec: add feature 100 alert target test actions
This commit is contained in:
parent
3ed275cef3
commit
270181d509
4
.github/agents/copilot-instructions.md
vendored
4
.github/agents/copilot-instructions.md
vendored
@ -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 -->
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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`.
|
||||
17
specs/100-alert-target-test-actions/contracts/event-types.md
Normal file
17
specs/100-alert-target-test-actions/contracts/event-types.md
Normal 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.
|
||||
@ -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 Filament’s `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.)
|
||||
79
specs/100-alert-target-test-actions/data-model.md
Normal file
79
specs/100-alert-target-test-actions/data-model.md
Normal 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.)
|
||||
221
specs/100-alert-target-test-actions/plan.md
Normal file
221
specs/100-alert-target-test-actions/plan.md
Normal 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
|
||||
20
specs/100-alert-target-test-actions/quickstart.md
Normal file
20
specs/100-alert-target-test-actions/quickstart.md
Normal 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`
|
||||
68
specs/100-alert-target-test-actions/research.md
Normal file
68
specs/100-alert-target-test-actions/research.md
Normal 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 spec’s `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).
|
||||
156
specs/100-alert-target-test-actions/spec.md
Normal file
156
specs/100-alert-target-test-actions/spec.md
Normal 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 user’s 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.
|
||||
Loading…
Reference in New Issue
Block a user