TenantAtlas/specs/219-finding-ownership-semantics/data-model.md
ahmido c86b399b43
Some checks failed
Main Confidence / confidence (push) Failing after 53s
feat(219): Finding ownership semantics + LEAN-001 constitution + backup_set unification (#256)
## Summary

This PR delivers three related improvements:

### 1. Finding Ownership Semantics (Spec 219)
- Add responsibility/accountability labels to findings and finding exceptions
- `owner_user_id` = accountable party (governance owner)
- `assignee_user_id` = responsible party (technical implementer)
- Expose Assign/Reassign actions in FindingResource with audit logging
- Add ownership columns and filters to finding list
- Propagate owner from finding to exception on creation
- Tests: ownership semantics, assignment audit, workflow actions

### 2. Constitution v2.7.0 — LEAN-001 Pre-Production Lean Doctrine
- New principle forbidding legacy aliases, migration shims, dual-write logic, and compatibility fixtures in a pre-production codebase
- AI-agent 4-question verification gate before adding any compatibility path
- Review rule: compatibility shims without answering the gate questions = merge blocker
- Exit condition: LEAN-001 expires at first production deployment
- Spec template: added default "Compatibility posture" block
- Agent instructions: added "Pre-production compatibility check" section

### 3. Backup Set Operation Type Unification
- Unified `backup_set.add_policies` and `backup_set.remove_policies` into single canonical `backup_set.update`
- Removed all legacy aliases, constants, and test fixtures
- Added lifecycle coverage for `backup_set.update` in config
- Updated all 14+ test files referencing legacy types

### Spec Artifacts
- `specs/219-finding-ownership-semantics/` — full spec, plan, tasks, research, data model, contracts, checklist

### Tests
- All affected tests pass (OperationCatalog, backup set, finding workflow, ownership semantics)

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #256
2026-04-20 17:54:33 +00:00

121 lines
5.6 KiB
Markdown

# Data Model: Finding Ownership Semantics Clarification
**Date**: 2026-04-20
**Branch**: `219-finding-ownership-semantics`
## Overview
This feature introduces no new persisted entities. It clarifies responsibility semantics over existing finding and finding-exception records and adds one derived responsibility-state projection for operator-facing surfaces.
## Entity: Finding
**Represents**: A tenant-owned operational governance finding that moves through the findings workflow and may carry both accountable ownership and active remediation assignment.
### Key Fields
| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | bigint | yes | Primary key |
| `workspace_id` | bigint | yes | Derived tenant ownership boundary |
| `tenant_id` | bigint | yes | Tenant isolation boundary |
| `status` | string | yes | Existing findings lifecycle state |
| `severity` | string | yes | Existing severity dimension |
| `owner_user_id` | bigint nullable | no | Accountable person for the finding outcome |
| `assignee_user_id` | bigint nullable | no | Active remediation executor / coordinator |
| `due_at` | datetime nullable | no | Existing SLA/follow-up deadline |
| `resolved_reason` | string nullable | no | Existing closure context |
| `closed_reason` | string nullable | no | Existing closure/governance context |
### Relationships
| Relationship | Target | Cardinality | Purpose |
|---|---|---|---|
| `tenant()` | `Tenant` | belongsTo | Tenant ownership and authorization |
| `ownerUser()` | `User` | belongsTo | Accountable owner |
| `assigneeUser()` | `User` | belongsTo | Active remediation assignee |
| `findingException()` | `FindingException` | hasOne | Optional exception artifact for accepted-risk governance |
### Validation Rules
- `owner_user_id` MAY be null.
- `assignee_user_id` MAY be null.
- If present, either user ID MUST reference a current member of the active tenant.
- Responsibility changes are allowed only on open findings, matching the current `FindingWorkflowService::assign()` rule.
## Entity: FindingException
**Represents**: A tenant-owned exception artifact attached to a finding when governance coverage is requested or granted.
### Key Fields
| Field | Type | Required | Notes |
|---|---|---|---|
| `id` | bigint | yes | Primary key |
| `finding_id` | bigint | yes | Owning finding |
| `tenant_id` | bigint | yes | Tenant isolation boundary |
| `owner_user_id` | bigint nullable | no | Accountable owner of the exception artifact, not of the finding itself |
| `status` | string | yes | Existing exception lifecycle state |
| `current_validity_state` | string nullable | no | Existing governance-validity dimension |
| `request_reason` | text | yes | Existing request context |
### Relationships
| Relationship | Target | Cardinality | Purpose |
|---|---|---|---|
| `finding()` | `Finding` | belongsTo | Parent finding context |
| `owner()` | `User` | belongsTo | Exception artifact owner |
### Validation Rules
- Exception-owner selection continues to use current tenant-member validation.
- Exception ownership MUST remain semantically distinct from finding ownership on all mixed-context surfaces.
## Derived Projection: ResponsibilityState
**Represents**: An operator-facing derived state computed from `owner_user_id` and `assignee_user_id` without new persistence.
**Naming convention**:
- Operator-facing UI label: `orphaned accountability`
- Internal derived-state and contract slug: `orphaned_accountability`
### Derived Values
| Derived State | Rule | Operator Meaning |
|---|---|---|
| `orphaned_accountability` | `owner_user_id == null` | No accountable owner is set. This remains true even if an assignee exists. |
| `owned_unassigned` | `owner_user_id != null && assignee_user_id == null` | Someone owns the outcome, but active remediation work is not assigned. |
| `assigned` | `owner_user_id != null && assignee_user_id != null` | Accountability and active remediation assignment are both set. |
### Rendering Notes
- If owner and assignee are the same user, the state remains `assigned`; the UI should show both roles satisfied without implying a data problem.
- If both are null, the finding still uses the slug `orphaned_accountability` and the visible label `orphaned accountability`.
- If assignee is present but owner is null, the finding remains `orphaned_accountability`; the UI may also show that remediation is assigned without accountable ownership.
## Mutation Contract: ResponsibilityUpdate
**Represents**: The input/output contract of the existing assignment action.
### Input Shape
| Field | Type | Required | Notes |
|---|---|---|---|
| `owner_user_id` | bigint nullable | no | Set, change, or clear finding owner |
| `assignee_user_id` | bigint nullable | no | Set, change, or clear finding assignee |
### Behavioral Rules
- The existing `FindingWorkflowService::assign()` method remains the mutation boundary.
- The service MUST continue to write both fields explicitly to the finding.
- Operator feedback and audit-facing wording should classify the result as `owner_only`, `assignee_only`, `clear_owner`, `clear_assignee`, or `owner_and_assignee` when both fields change in one update.
## State and Lifecycle Impact
This feature does not add a new lifecycle family. It overlays responsibility semantics on top of existing findings lifecycle states.
| Existing Lifecycle State | Responsibility Impact |
|---|---|
| `new`, `triaged`, `in_progress`, `reopened`, `acknowledged` | Responsibility state is actionable and visible by default |
| `resolved`, `closed` | Responsibility remains historical context only |
| `risk_accepted` | Responsibility remains visible, but exception-owner context may also appear and must remain separate |