- Add LEAN-001 to constitution after BIAS-001: forbids legacy aliases, migration shims, dual-write logic, and compatibility fixtures in a pre-production codebase - Add compatibility posture default block to spec template - Add pre-production compatibility check to agent instructions - Unify backup_set operation type to canonical backup_set.update - Remove all legacy backup_set.add_policies/remove_policies references - Add finding ownership semantics (responsibility/accountability labels) - Clean up roadmap.md and spec-candidates.md
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 |