Some checks failed
Main Confidence / confidence (push) Failing after 53s
## 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
121 lines
5.6 KiB
Markdown
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 | |