TenantAtlas/specs/255-enforce-finding-creation-invariants/data-model.md
ahmido 51ea80ca05
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m5s
Automatische PR: 255-enforce-finding-creation-invariants → platform-dev (#298)
Automatisch erstellt: Commit & Push aus Workspace (WIP)

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #298
2026-04-29 12:26:21 +00:00

130 lines
4.9 KiB
Markdown

# Data Model — Enforce Creation-Time Finding Invariants
**Spec**: [spec.md](spec.md)
This feature introduces no new persisted truth. The data-model impact is to make the existing `Finding` lifecycle contract explicit at create, refresh, and reopen time across the three active writer families.
## Existing Canonical Entities Reused
### Finding (`findings`)
**Purpose**: Tenant-owned findings workflow truth.
**Key fields already in use**:
- `id`
- `workspace_id`
- `tenant_id`
- `finding_type`
- `source`
- `scope_key`
- `fingerprint`
- `recurrence_key`
- `severity`
- `status`
- `first_seen_at`
- `last_seen_at`
- `times_seen`
- `sla_days`
- `due_at`
- `reopened_at`
- `resolved_at`
- `resolved_reason`
- `closed_at`
- `closed_reason`
- `current_operation_run_id`
- `baseline_operation_run_id`
**Feature use**:
- Remains the single persisted source of truth for active findings lifecycle state.
- Continues to require both `workspace_id` and `tenant_id` anchors.
- Keeps the current status families unchanged.
- Carries the lifecycle-ready fields that this feature hardens at write time.
### OperationRun (`operation_runs`)
**Purpose**: Existing execution context for baseline compare and other operational flows.
**Feature use**:
- Remains contextual only.
- `current_operation_run_id` continues to identify the current writer run where the family already sets it.
- No new operation type or new run-tracking artifact is introduced.
### StoredReport (`stored_reports`)
**Purpose**: Existing stored reporting artifact for permission posture output.
**Feature use**:
- Unchanged.
- Mentioned only because permission posture finding generation already correlates lifecycle-ready findings with an existing report artifact.
## Derived Non-Persisted Contracts
### LifecycleReadyFinding (derived contract)
**Definition**: A `Finding` record that is immediately usable by the existing workflow the moment the active writer persists or refreshes it.
**Required fields**:
- active canonical status on first create (`new`)
- `first_seen_at`
- `last_seen_at`
- `times_seen >= 1`
- `sla_days` when the current severity policy returns a value
- `due_at` when the current severity policy requires due-date truth
- existing run correlation fields preserved where the writer already populates them
**Removal rule**:
- no later repair surface may be required for these fields on active writers
### RecurrenceIdentity (derived contract)
**Definition**: The family-owned identity that decides whether a repeated observation refreshes one canonical finding or incorrectly creates a duplicate.
**Family-specific variants**:
- baseline compare: `recurrence_key` and `fingerprint` derived from tenant, baseline profile, policy type, subject key, and change type
- Entra admin roles: existing role-assignment and aggregate fingerprints
- permission posture: existing permission and error fingerprints
**Guarantee**:
- repeated observation of the same canonical issue reuses one finding identity
### ObservationBoundary (derived contract)
**Definition**: The family-specific rule that decides whether `times_seen` should advance.
**Family-specific variants**:
- baseline compare: same `current_operation_run_id` must not increment `times_seen` twice for the same observation
- Entra admin roles: later `observedAt` advances seen history
- permission posture: later `observedAt` advances seen history
**Guarantee**:
- retries and repeated processing do not double count the same observation
## State Transitions Reused
### Create
- missing canonical finding identity -> create one `Finding`
- resulting state remains `new`
- lifecycle-ready fields are populated in the same write path
### Refresh Existing Open Finding
- existing open finding remains in its current active workflow state
- evidence or severity may refresh according to the writer family
- missing lifecycle-ready fields covered by this feature are repaired inline
- valid existing lifecycle fields should not be needlessly reset
### Reopen Existing Terminal Finding
- existing terminal finding transitions through `FindingWorkflowService::reopenBySystem()`
- resulting state becomes `reopened`
- `resolved_*` and `closed_*` markers clear according to the current service behavior
- SLA and due-state truth are recalculated from the later re-observation moment
## Invariant Rules
- No new persisted entity, table, or compatibility artifact may be introduced.
- No new workflow status, reopen reason family, or lifecycle label may be introduced.
- Active writers must repair incomplete lifecycle-ready fields inline rather than relying on CLI repair commands, tenant maintenance actions, or deploy-time hooks.
- Due-state repair should fill missing truth or refresh terminal-to-reopened truth only; it must not silently redesign current due-date semantics for already-healthy open findings.
- A later database constraint is a separate follow-up candidate only if application-level write-path enforcement proves insufficient.