TenantAtlas/specs/111-findings-workflow-sla/contracts/api-contracts.md
ahmido 7ac53f4cc4 feat(111): findings workflow + SLA settings (#135)
Implements spec 111 (Findings workflow + SLA) and fixes Workspace findings SLA settings UX/validation.

Key changes:
- Findings workflow service + SLA policy and alerting.
- Workspace settings: allow partial SLA overrides without auto-filling unset severities in the UI; effective values still resolve via defaults.
- New migrations, jobs, command, UI/resource updates, and comprehensive test coverage.

Tests:
- `vendor/bin/sail artisan test --compact` (1779 passed, 8 skipped).

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #135
2026-02-25 01:48:01 +00:00

123 lines
5.3 KiB
Markdown

# API Contracts: 111 — Findings Workflow V2 + SLA
**Date**: 2026-02-24
**Branch**: `111-findings-workflow-sla`
---
## Overview
This feature does not introduce new external REST endpoints. User interaction is through Filament/Livewire actions on the Findings Resource and Alert Rules configuration. The only new cross-module “contract” is the `sla_due` alert event produced during scheduled alert evaluation and consumed by the Alerts dispatch pipeline.
---
## 1. Alert Event Contract: `sla_due`
**Producer:** `App\Jobs\Alerts\EvaluateAlertsJob`
**Event Type:** `AlertRule::EVENT_SLA_DUE` (`sla_due`)
**Event Cardinality:** At most 1 event per tenant per evaluation window (when newly-overdue findings exist)
### Eligibility (Per Tenant)
An event is produced when a tenant has one or more **newly-overdue** open findings since the previous evaluation window:
- `status IN (new, triaged, in_progress, reopened)`
- `due_at <= now()`
- `due_at > windowStart`
Terminal statuses (`resolved`, `closed`, `risk_accepted`) never contribute to overdue evaluation.
### Event Payload Shape
```json
{
"event_type": "sla_due",
"tenant_id": 123,
"severity": "high",
"fingerprint_key": "sla_due:tenant:123",
"title": "SLA overdue findings detected",
"body": "Tenant Contoso has 5 overdue open findings (critical: 1, high: 2, medium: 2, low: 0).",
"metadata": {
"overdue_total": 5,
"overdue_by_severity": {
"critical": 1,
"high": 2,
"medium": 2,
"low": 0
}
}
}
```
### Severity Semantics
`severity` is the maximum severity among overdue open findings for that tenant at evaluation time.
Rationale: alert rules can use `minimum_severity`; a critical-overdue case can bypass a “high-only” rule.
---
## 2. Filament Findings Resource: Workflow Actions Contract
All workflow actions:
- enforce tenant membership as deny-as-not-found (404) for non-members
- enforce capability checks (403 for members lacking capability)
- write an audit log entry with before/after and any reason fields
### List Defaults
Default list shows:
- all finding types (no drift-only default)
- open statuses only: `new`, `triaged`, `in_progress`, `reopened`
Quick filters:
- Open
- Overdue (`due_at < now()` and open statuses)
- High severity (high + critical)
- My assigned (`assignee_user_id = current user`)
### Row Actions (More menu)
| Action | Allowed From Status | To Status | Capability | Confirmation | Notes |
|--------|---------------------|----------|------------|--------------|------|
| Triage | `new`, `reopened` | `triaged` | `TENANT_FINDINGS_TRIAGE` (or legacy `TENANT_FINDINGS_ACKNOWLEDGE` alias) | No | Sets `triaged_at` |
| Start progress | `triaged` | `in_progress` | `TENANT_FINDINGS_TRIAGE` (or legacy alias) | No | Sets `in_progress_at` |
| Assign | open statuses | unchanged | `TENANT_FINDINGS_ASSIGN` | No | Sets `assignee_user_id` and optional `owner_user_id` (picker limited to tenant members) |
| Resolve | open statuses | `resolved` | `TENANT_FINDINGS_RESOLVE` | Yes | Requires `resolved_reason`; sets `resolved_at` |
| Close | any status | `closed` | `TENANT_FINDINGS_CLOSE` | Yes | Requires `closed_reason`; sets `closed_at` + `closed_by_user_id` |
| Risk accept | any status | `risk_accepted` | `TENANT_FINDINGS_RISK_ACCEPT` | Yes | Requires reason (stored as `closed_reason`); sets `closed_at` + `closed_by_user_id` |
| Reopen | `resolved`, `closed`, `risk_accepted` | `reopened` | `TENANT_FINDINGS_TRIAGE` (or legacy alias) | Yes | Manual reopen only. Automatic reopen is allowed only from `resolved` during detection. |
### Bulk Actions
Bulk actions are all-or-nothing (if any record is unauthorized for the current tenant context, the action is disabled):
| Action | Capability | Confirmation | Notes |
|--------|------------|--------------|------|
| Bulk triage | `TENANT_FINDINGS_TRIAGE` (or legacy alias) | Yes (typed confirm for large selections) | Moves `new|reopened → triaged` |
| Bulk assign | `TENANT_FINDINGS_ASSIGN` | Yes (typed confirm for large selections) | Assignee/owner pickers limited to tenant members |
| Bulk resolve | `TENANT_FINDINGS_RESOLVE` | Yes | Reason required |
| Bulk close | `TENANT_FINDINGS_CLOSE` | Yes | Reason required |
| Bulk risk accept | `TENANT_FINDINGS_RISK_ACCEPT` | Yes | Reason required |
---
## 3. Backfill/Consolidation Operation Contract
Backfill is a tenant-context operation that upgrades legacy findings to v2 lifecycle fields and consolidates drift duplicates. It MUST be OperationRun-backed and use OPS-UX feedback surfaces (queued toast, progress surfaces, initiator-only completion notification).
**OperationRun type:** `findings.lifecycle.backfill` (label registered in OperationCatalog)
**Idempotency:** One active run per tenant (deduped by OperationRun identity)
Summary counts use canonical numeric keys only (e.g., `total`, `processed`, `updated`, `failed`, `skipped`).
---
## 4. Audit Contract (Tenant Scope)
Every workflow mutation writes a tenant audit record (via `App\Services\Intune\AuditLogger`) with:
- `action` string (e.g., `finding.triaged`, `finding.resolved`, `finding.closed`, `finding.risk_accepted`, `finding.reopened`, `findings.lifecycle.backfill.started`)
- `metadata` including: `finding_id`, `before_status`, `after_status`, and any reason fields or assignment deltas
Audit payloads must remain sanitized and must not include secrets/tokens.