137 lines
5.0 KiB
Markdown
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. |