TenantAtlas/specs/151-findings-workflow-backstop/data-model.md
ahmido ec71c2d4e7 feat: harden findings workflow and audit backstop (#181)
## Summary
- harden finding lifecycle changes behind the canonical `FindingWorkflowService` gateway
- route automated resolve and reopen flows through the same audited workflow path
- tighten tenant and workspace scope checks on finding actions and audit visibility
- add focused spec artifacts, workflow regression coverage, automation coverage, and audit visibility tests
- update legacy finding model tests to use the workflow service after direct lifecycle mutators were removed

## Testing
- `vendor/bin/sail bin pint --dirty --format agent`
- focused findings and audit slices passed during implementation
- `vendor/bin/sail artisan test --compact tests/Feature/Models/FindingResolvedTest.php`
- full repository suite passed: `2757 passed`, `8 skipped`, `14448 assertions`

## Notes
- Livewire v4.0+ compliance preserved
- no new Filament assets or panel providers introduced; provider registration remains in `bootstrap/providers.php`
- findings stay on existing Filament action surfaces, with destructive actions still confirmation-gated
- no global search behavior was changed for findings resources

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #181
2026-03-18 12:57:23 +00:00

137 lines
5.0 KiB
Markdown

# Data Model: Findings Workflow Enforcement and Audit Backstop
## Overview
This feature does not introduce a new primary persistence model. It hardens the existing relationship between tenant-owned `Finding` records and workspace-owned audit history so that lifecycle truth is enforced consistently across UI and automation paths.
## Entities
### Finding
**Type**: Existing tenant-owned aggregate
**Storage**: `findings` table
**Ownership**: `workspace_id` + `tenant_id` required via tenant ownership rules
**Relevant fields**:
- `id`
- `workspace_id`
- `tenant_id`
- `status`
- `severity`
- `due_at`
- `sla_days`
- `assignee_user_id`
- `owner_user_id`
- `triaged_at`
- `in_progress_at`
- `reopened_at`
- `resolved_at`
- `resolved_reason`
- `closed_at`
- `closed_reason`
- `closed_by_user_id`
- `first_seen_at`
- `last_seen_at`
- `times_seen`
- `evidence_jsonb`
**Validation rules**:
- `tenant_id` must match the active tenant scope for any tenant-context mutation.
- `workspace_id` must match the owning tenant workspace.
- `status` must remain within the canonical Spec 111 lifecycle state set, with legacy `acknowledged` treated as compatibility input rather than a preferred new write target.
- `resolved_reason` is required for resolve operations.
- `closed_reason` is required for close and risk-accept operations.
- `assignee_user_id` and `owner_user_id`, when present, must reference current tenant members.
**State transitions**:
| From | To | Trigger | Notes |
|---|---|---|---|
| `new` | `triaged` | Human triage | Canonical replacement for legacy acknowledge semantics |
| `reopened` | `triaged` | Human triage | Same workflow rule as Spec 111 |
| `acknowledged` | `triaged` | Human triage or compatibility normalization | Legacy compatibility path |
| `triaged` | `in_progress` | Human start progress | Existing workflow rule |
| `acknowledged` | `in_progress` | Human start progress | Legacy compatibility path |
| `new`,`triaged`,`in_progress`,`reopened`,`acknowledged` | `resolved` | Human resolve or system auto-resolve | Reason required; system-origin audit must remain explicit |
| `new`,`triaged`,`in_progress`,`reopened`,`acknowledged` | `closed` | Human close | Reason required |
| `new`,`triaged`,`in_progress`,`reopened`,`acknowledged` | `risk_accepted` | Human risk accept | Reason required |
| `resolved`,`closed`,`risk_accepted` | `reopened` | Human reopen or recurrence reopen | Recomputes SLA and due date |
**Out-of-scope transitions**:
- Any direct write that changes lifecycle truth without using the canonical gateway
- Any new active status value beyond the Spec 111 set
### Finding Workflow Mutation
**Type**: Domain command, not a table
**Purpose**: Encapsulates the requested lifecycle change and the context needed to validate and audit it
**Fields**:
- `finding_id`
- `tenant_id`
- `workspace_id`
- `requested_transition`
- `actor_kind` (`human` or `system`)
- `actor_id` or system-origin identity
- `reason_text` when required
- `assignee_user_id` and `owner_user_id` when assignment changes
- `observation_timestamp` for automation paths that depend on current observation truth
**Validation rules**:
- Must resolve to one owned `Finding` within the current tenant scope.
- Must satisfy the allowed transition matrix.
- Must carry all required metadata for the requested transition.
- Must reject no-op transitions that do not materially change lifecycle truth.
### Audit Log Entry for Findings Workflow
**Type**: Existing workspace-owned historical record
**Storage**: `audit_logs` table
**Relevant fields**:
- `workspace_id`
- `tenant_id`
- `actor_id`
- `actor_email`
- `actor_name`
- `action`
- `resource_type`
- `resource_id`
- `status`
- `metadata`
- `recorded_at`
**Required metadata shape for this feature**:
- `finding_id`
- `before_status`
- `after_status`
- `before`
- `after`
- transition-specific reason or assignment fields when applicable
- system-origin marker when the mutation was not user-driven
**Constraints**:
- Must never expose `evidence_jsonb`, secrets, or oversized raw payloads.
- Must remain intelligible even if the live `Finding` later changes or becomes inaccessible.
- Must not duplicate history for the same successful covered mutation.
## Relationships
- One `Finding` belongs to one `Tenant` and one `Workspace` through tenant ownership.
- Many audit log entries may reference one `Finding` over time.
- One workflow mutation command targets exactly one `Finding` but may be invoked repeatedly across the lifecycle.
## Consistency Rules
- A lifecycle mutation is valid only if both the persisted `Finding` and the requested transition still satisfy the gateway rules at execution time.
- Human-driven and system-driven lifecycle changes share the same transition rules but differ in actor semantics and audit labeling.
- Assignment changes are part of workflow history and must be auditable, but they do not introduce a separate lifecycle state.
- Legacy `acknowledged` values remain readable and migratable, but new writes should converge on canonical statuses.