TenantAtlas/specs/111-findings-workflow-sla/data-model.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

99 lines
4.0 KiB
Markdown

# Data Model: 111 — Findings Workflow V2 + SLA
**Date**: 2026-02-24
**Branch**: `111-findings-workflow-sla`
---
## Modified Entities
### 1. `findings` Table (Lifecycle + SLA + Recurrence)
This feature evolves `findings` from v1 (`new|acknowledged|resolved`) to a v2 workflow and adds lifecycle metadata and SLA fields.
#### New Columns
| Column | Type | Constraints | Notes |
|--------|------|-------------|-------|
| `first_seen_at` | `timestampTz` | nullable initially; NOT NULL after backfill | Set on first observation; backfill from `created_at` where possible |
| `last_seen_at` | `timestampTz` | nullable initially; NOT NULL after backfill | Updated on every observation (including terminal findings) |
| `times_seen` | `integer` | default `0`, NOT NULL (after backfill enforce) | Incremented on every observation |
| `sla_days` | `smallint` | nullable | SLA policy value applied when `due_at` was set/reset |
| `due_at` | `timestampTz` | nullable | Only “open” findings participate in SLA due evaluation |
| `owner_user_id` | `bigint` | FK → users, nullable | Retained even if user is no longer a tenant member |
| `assignee_user_id` | `bigint` | FK → users, nullable | Retained even if user is no longer a tenant member |
| `triaged_at` | `timestampTz` | nullable | Set on `new|reopened → triaged` |
| `in_progress_at` | `timestampTz` | nullable | Set on `triaged → in_progress` |
| `reopened_at` | `timestampTz` | nullable | Set when transitioning into `reopened` (manual or automatic) |
| `closed_at` | `timestampTz` | nullable | Used for both `closed` and `risk_accepted` terminal outcomes |
| `closed_by_user_id` | `bigint` | FK → users, nullable | Actor for `closed` / `risk_accepted` |
| `closed_reason` | `string` | nullable | Reason required for `closed` and `risk_accepted` |
| `recurrence_key` | `string(64)` | nullable; indexed | Stable identity for drift recurrence (v2) |
#### Existing Columns Used/Extended
| Column | Notes |
|--------|------|
| `status` | Extended v2 statuses (see below). Legacy `acknowledged` is mapped to v2 `triaged` in the UI and migrated during backfill. |
| `resolved_at` / `resolved_reason` | Remains the terminal “resolved” record with reason. |
| `acknowledged_at` / `acknowledged_by_user_id` | Retained for historical reference; `acknowledged` status is legacy. |
| `fingerprint` | Remains unique per tenant. For canonical drift rows going forward, the fingerprint is stable (aligned to recurrence identity). |
#### Status Values (Canonical)
Open statuses:
- `new`
- `triaged`
- `in_progress`
- `reopened`
Terminal statuses:
- `resolved`
- `closed`
- `risk_accepted`
Legacy status (migration window):
- `acknowledged` (treated as `triaged` in v2 surfaces)
#### Indexes
New/updated indexes to support list filters and alert evaluation:
| Index | Type | Purpose |
|------|------|---------|
| `(tenant_id, status, due_at)` | btree | Open/overdue filtering in tenant UI |
| `(tenant_id, assignee_user_id)` | btree | “My assigned” filter |
| `(tenant_id, recurrence_key)` | btree | Drift recurrence lookups and consolidation |
| `(workspace_id, status, due_at)` | btree | Workspace-scoped SLA due producer query |
Existing index `(tenant_id, status)` remains valid.
---
## Configuration Keys (No Schema Change)
### Settings: Findings SLA policy
Add a SettingsRegistry entry (workspace-resolvable):
- `findings.sla_days` (JSON object): severity → days
- Default:
- critical: 3
- high: 7
- medium: 14
- low: 30
Stored in existing `workspace_settings` / `tenant_settings` tables; no new tables required.
---
## State Machine (High-Level)
```
new ──triage──> triaged ──start──> in_progress ──resolve──> resolved
└───────────────────────────────close/risk_accept──────────────> closed|risk_accepted
resolved ──(auto or manual)──> reopened ──triage──> triaged ...
closed|risk_accepted ──(manual only)──> reopened
```