- 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
52 lines
5.0 KiB
Markdown
52 lines
5.0 KiB
Markdown
# Research: Finding Ownership Semantics Clarification
|
||
|
||
**Date**: 2026-04-20
|
||
**Branch**: `219-finding-ownership-semantics`
|
||
|
||
## Decision 1: Reuse the existing finding owner and assignee fields as the only responsibility truth
|
||
|
||
- **Decision**: Keep `findings.owner_user_id` and `findings.assignee_user_id` as the sole persisted responsibility fields and derive any responsibility-state wording from them.
|
||
- **Rationale**: The current model, workflow service, migrations, and tests already support independent owner and assignee assignment. The spec problem is semantic ambiguity, not missing persistence.
|
||
- **Alternatives considered**:
|
||
- Add a new persisted responsibility status column. Rejected because the state is directly derivable from existing nullable fields.
|
||
- Introduce a separate responsibility aggregate/service object. Rejected because one resource and one model can express the needed truth without adding a new layer.
|
||
|
||
## Decision 2: Treat assignee-without-owner as an accountability gap, not a healthy assigned state
|
||
|
||
- **Decision**: Use a derived responsibility contract where owner presence determines accountability. `owner != null && assignee == null` is `owned but unassigned`; `owner != null && assignee != null` is `assigned`; any `owner == null` case is an accountability gap, even if an assignee exists.
|
||
- **Rationale**: The spec defines owner as accountable for the outcome and assignee as actively executing work. Existing resolver logic already treats missing owner or missing assignee as follow-up needing attention.
|
||
- **Alternatives considered**:
|
||
- Treat any assignee as `assigned` even without an owner. Rejected because it hides the exact accountability gap the feature exists to surface.
|
||
- Introduce a fourth persisted state family. Rejected because the business consequence is presentation and next-action guidance, not new workflow routing.
|
||
|
||
## Decision 3: Keep exception owner explicitly separate from finding owner
|
||
|
||
- **Decision**: Preserve `finding_exceptions.owner_user_id` as a distinct governance concept and label it only as `Exception owner` when shown in a finding context.
|
||
- **Rationale**: The current resource already surfaces exception owner separately, and the spec explicitly calls out the risk of accepted-risk flows reintroducing ambiguity under a second `Owner` label.
|
||
- **Alternatives considered**:
|
||
- Collapse exception owner into finding owner language. Rejected because it would blur responsibility for the finding with responsibility for the exception artifact.
|
||
- Rename finding owner around exception flows only. Rejected because local relabeling would increase rather than reduce semantic drift.
|
||
|
||
## Decision 4: Keep the implementation local to the existing findings resource and current workflow services
|
||
|
||
- **Decision**: Make the semantics change in `FindingResource`, with only small supporting adjustments in `Finding`, `FindingWorkflowService`, `FindingExceptionService`, and `FindingRiskGovernanceResolver` if wording or derived next-action copy needs alignment.
|
||
- **Rationale**: The current UI already displays owner, assignee, and exception owner. The gap is where those semantics are explained, prioritized, and tested.
|
||
- **Alternatives considered**:
|
||
- Add a new presenter or explanation layer for responsibility semantics. Rejected because a local derived helper is sufficient and aligned with the constitution’s anti-layering bias.
|
||
- Add a dedicated responsibility page. Rejected because the findings list/detail surfaces are already the operator decision context.
|
||
|
||
## Decision 5: Use focused Pest feature + Livewire coverage, not browser or heavy-governance tests
|
||
|
||
- **Decision**: Cover the feature with focused findings resource and workflow feature tests, including explicit tenant-context setup and Filament routing discipline.
|
||
- **Rationale**: Existing tests already prove tenant-member validation and row-action behavior. The missing coverage is list/detail semantics and owner-only versus assignee-only feedback.
|
||
- **Alternatives considered**:
|
||
- Browser tests. Rejected because the behavior is visible and provable through current Filament Livewire test patterns.
|
||
- Unit-only tests. Rejected because they would miss the operator-facing semantics that motivated the spec.
|
||
|
||
## Decision 6: Preserve current tenant-panel and canonical-admin context rules during testing
|
||
|
||
- **Decision**: Keep tenant context explicit in tests and use explicit panel selection for tenant-route assertions when necessary.
|
||
- **Rationale**: Repository memory shows two relevant pitfalls: tenant-scoped Filament list pages can lose context on later Livewire interactions, and `Resource::getUrl(..., tenant: $tenant)` defaults to the admin panel unless `panel: 'tenant'` is passed.
|
||
- **Alternatives considered**:
|
||
- Rely on implicit panel resolution in tests. Rejected because it can produce false redirects to `/admin/choose-tenant` instead of the intended authorization result.
|
||
- Broaden shared fixtures to always set tenant context. Rejected because the constitution prefers opt-in context rather than expensive defaults. |