## 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
5.6 KiB
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_idMAY be null.assignee_user_idMAY 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_accountabilityand the visible labelorphaned 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, orowner_and_assigneewhen 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 |