TenantAtlas/specs/231-finding-outcome-taxonomy/plan.md
Ahmed Darrazi 807578dd9c
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 4m16s
feat: implement finding outcome taxonomy
2026-04-23 09:24:59 +02:00

259 lines
24 KiB
Markdown

# Implementation Plan: Finding Outcome Taxonomy & Verification Semantics
**Branch**: `231-finding-outcome-taxonomy` | **Date**: 2026-04-22 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/231-finding-outcome-taxonomy/spec.md`
**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/231-finding-outcome-taxonomy/spec.md`
**Note**: This plan keeps the work inside the existing findings workflow, tenant findings resource, current risk-governance interpretation, shared findings action catalog copy, pre-production lifecycle normalization jobs, system resolve/reopen paths, and current review/report consumers. The intended implementation adds one bounded outcome-semantics seam over existing `Finding` records and existing transition services. It does not add a new findings queue, a new panel, a new asset family, a second workflow state store, or a new primary findings status.
## Summary
Introduce one bounded findings outcome taxonomy that sits on top of the existing lifecycle in `FindingWorkflowService` and current `Finding` records. Keep `status` unchanged, tighten `resolved_reason`, `closed_reason`, and `reopen_reason` into canonical keys, distinguish operator-declared resolution from later trusted system-cleared outcomes, and apply that same contract to `FindingResource`, the existing list and detail narratives, system resolve/reopen producers such as `BaselineAutoCloseService`, `EntraAdminRolesFindingGenerator`, `PermissionPostureFindingGenerator`, and `CompareBaselineToTenantJob`, supporting pre-production normalization jobs, shared findings action copy in `GovernanceActionCatalog`, the risk-governance interpreter in `FindingRiskGovernanceResolver`, plus current findings-derived review/report consumers such as `ReviewRegister` and `TenantReviewResource`.
## Technical Context
**Language/Version**: PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade
**Primary Dependencies**: `App\Models\Finding`, `App\Filament\Resources\FindingResource`, `App\Services\Findings\FindingWorkflowService`, `App\Services\Findings\FindingRiskGovernanceResolver`, `App\Services\Baselines\BaselineAutoCloseService`, `App\Services\EntraAdminRoles\EntraAdminRolesFindingGenerator`, `App\Services\PermissionPosture\PermissionPostureFindingGenerator`, `App\Jobs\CompareBaselineToTenantJob`, `App\Jobs\BackfillFindingLifecycleJob`, `App\Jobs\BackfillFindingLifecycleTenantIntoWorkspaceRunJob`, `App\Filament\Pages\Reviews\ReviewRegister`, `App\Filament\Resources\TenantReviewResource`, `App\Support\Ui\GovernanceActions\GovernanceActionCatalog`, `BadgeCatalog`, `BadgeRenderer`, `AuditLog` metadata via `AuditLogger`
**Storage**: PostgreSQL via existing `findings`, `finding_exceptions`, `tenant_reviews`, `stored_reports`, and audit-log tables; no schema changes planned
**Testing**: Pest v4 feature tests with Filament/Livewire assertions and workflow-service coverage
**Validation Lanes**: fast-feedback, confidence
**Target Platform**: Dockerized Laravel web application via Sail locally and Linux containers in deployment
**Project Type**: Laravel monolith inside the `wt-plattform` monorepo
**Performance Goals**: Keep findings list and review/report rollups free of new N+1 behavior, keep terminal-outcome rendering derived from existing record state, and avoid new background or polling work
**Constraints**: No new primary findings status, no new queue surface, no new Graph calls, no new asset family, no cross-tenant leakage, no comments or attachments scope, and no second reporting-only persistence layer
**Scale/Scope**: One narrow semantics helper or equivalent local mapping seam, one workflow-service hardening slice, one risk-governance alignment slice, four existing system producer paths, two pre-production normalization jobs, one shared action-catalog touchpoint, two existing operator surfaces, and a small set of focused test suites
## UI / Surface Guardrail Plan
- **Guardrail scope**: changed surfaces
- **Native vs custom classification summary**: native Filament resource list, native record detail, and existing review/report surfaces only
- **Shared-family relevance**: findings workflow surfaces, status messaging, filters, review/report viewers, centralized badge semantics
- **State layers in scope**: page, detail, shell
- **Handling modes by drift class or surface**: review-mandatory
- **Repository-signal treatment**: review-mandatory
- **Special surface test profiles**: standard-native-filament
- **Required tests or manual smoke**: functional-core, state-contract
- **Exception path and spread control**: none; the feature must converge all in-scope outcome language on one bounded semantics seam rather than permitting page-local synonyms
- **Active feature PR close-out entry**: Guardrail
## Shared Pattern & System Fit
- **Cross-cutting feature marker**: yes
- **Systems touched**:
- `App\Filament\Resources\FindingResource`
- `App\Services\Findings\FindingWorkflowService`
- `App\Services\Findings\FindingRiskGovernanceResolver`
- `App\Models\Finding`
- `App\Services\Baselines\BaselineAutoCloseService`
- `App\Services\EntraAdminRoles\EntraAdminRolesFindingGenerator`
- `App\Services\PermissionPosture\PermissionPostureFindingGenerator`
- `App\Jobs\CompareBaselineToTenantJob`
- `App\Jobs\BackfillFindingLifecycleJob`
- `App\Jobs\BackfillFindingLifecycleTenantIntoWorkspaceRunJob`
- `App\Filament\Pages\Reviews\ReviewRegister`
- `App\Filament\Resources\TenantReviewResource`
- `App\Support\Ui\GovernanceActions\GovernanceActionCatalog`
- centralized badge and audit metadata paths
- **Shared abstractions reused**: existing `FindingWorkflowService`, existing `Finding` model lifecycle constants, existing `BadgeCatalog` and `BadgeRenderer`, existing `GovernanceActionCatalog` copy contracts, existing review/register consumers, and current audit metadata structure in `FindingWorkflowService`
- **New abstraction introduced? why?**: one narrow `FindingOutcomeSemantics`-style helper or equivalent local seam is justified because the same terminal-outcome meaning must be reused by workflow mutations, list/detail narratives, filters, and review/report rollups
- **Why the existing abstraction was sufficient or insufficient**: the existing findings lifecycle is sufficient as the primary workflow status layer, but it is insufficient for terminal-outcome meaning because free-form reasons and page-local narratives cannot reliably separate operator-resolved, system-verified, and non-remediation closure outcomes
- **Bounded deviation / spread control**: no parallel presentation path is allowed; if a review/report consumer needs compressed wording, it must still derive from the same canonical keys and verified-clear rules as the operator surfaces
## Constitution Check
*GATE: Passed before implementation design. Re-check after any scope expansion.*
| Principle | Status | Notes |
|-----------|--------|-------|
| Read/write separation | PASS | The feature only tightens meaning on existing finding transitions and existing read models; no new write family is introduced |
| Single contract path / no Graph bypass | PASS | No Graph calls or contract-registry changes are involved |
| Deterministic capabilities / RBAC-UX | PASS | Existing tenant findings permissions remain authoritative; no new capability family or plane change is introduced |
| Workspace and tenant isolation | PASS | All touched operator surfaces remain tenant-entitlement scoped; review/report rollups must continue to hide unauthorized tenant data |
| No new persisted truth without need | PASS | Existing `resolved_reason`, `closed_reason`, audit metadata, and exception validity semantics are reused; no new table or artifact is planned |
| No new state without behavioral consequence | PASS | The plan keeps the primary lifecycle statuses unchanged and limits new semantics to terminal-outcome meaning that changes filters, audit reading, and reporting buckets |
| No premature abstraction / few layers | PASS | One narrow helper is the maximum justified addition because at least four producer paths and multiple readers need the same semantics |
| Shared pattern first | PASS | One shared outcome semantics seam will feed workflow actions, UI narratives, filters, and reporting rather than separate local mappings |
| Badge semantics (BADGE-001) | PASS | Existing centralized badge/rendering rules remain authoritative; no page-local color language is planned |
| Filament-native UI (UI-FIL-001) | PASS | Findings list/detail and review surfaces stay on existing Filament resources/pages with updated filters, modals, and narratives only |
| Livewire v4.0+ / Filament v5 compliance | PASS | The plan stays within existing Filament v5 and Livewire v4 surface patterns |
| Provider registration / global search / assets | PASS | Panel providers remain in `apps/platform/bootstrap/providers.php`; no new globally searchable resource, no new asset family, and no deploy-step change beyond the existing `cd apps/platform && php artisan filament:assets` policy |
| Test governance (TEST-GOV-001) | PASS | Proof stays in focused feature suites; no browser or heavy-governance expansion is planned |
## Test Governance Check
- **Test purpose / classification by changed surface**: `Feature` for workflow-service semantics, system resolve/reopen paths, list/detail terminal-outcome presentation, filters, and findings-derived review/report buckets
- **Affected validation lanes**: `fast-feedback`, `confidence`
- **Why this lane mix is the narrowest sufficient proof**: The risk is integrated business meaning across existing findings mutations and existing operator or report consumers. Focused feature tests prove that meaning without adding browser rendering or heavy-governance breadth.
- **Narrowest proving command(s)**:
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingWorkflowServiceTest.php tests/Feature/Findings/FindingRecurrenceTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingsListFiltersTest.php tests/Feature/Filament/FindingResolvedReferencePresentationTest.php tests/Feature/Findings/FindingOutcomeSummaryReportingTest.php tests/Feature/Findings/FindingRiskGovernanceProjectionTest.php`
- **Fixture / helper / factory / seed / context cost risks**: Moderate. Tests need open, resolved, closed, reopened, and risk-accepted findings; system actor paths that clear or reopen findings; valid and invalid exception coverage; and at least one findings-derived review/report scenario.
- **Expensive defaults or shared helper growth introduced?**: no; any new helpers should stay findings-local and reuse existing findings workflow test concerns
- **Heavy-family additions, promotions, or visibility changes**: none
- **Surface-class relief / special coverage rule**: `standard-native-filament`; keep coverage centered on existing resource and service seams instead of adding browser tests
- **Closing validation and reviewer handoff**: Reviewers should verify that no new primary findings status was added, that canonical keys replaced free-form reporting meaning, that `risk_accepted` stayed governed by exception validity, that system-cleared findings are distinct from operator-resolved findings, and that the same semantics appear in workflow service, list filters, detail narrative, and report buckets.
- **Budget / baseline / trend follow-up**: none
- **Review-stop questions**: Did the implementation add a new status instead of a derived outcome layer? Did any producer path keep a free-form-only reason as the primary meaning? Did review/report code invent a second bucket taxonomy? Did risk-accepted findings collapse into verified-clear logic?
- **Escalation path**: document-in-feature unless implementation pressure requires a schema change or a second cross-domain presenter, in which case split or follow up with a dedicated spec
- **Active feature PR close-out entry**: Guardrail
- **Why no dedicated follow-up spec is needed**: The current operator and reporting gap can be closed inside the existing findings workflow and report seams without broader framework work
## Project Structure
### Documentation (this feature)
```text
specs/231-finding-outcome-taxonomy/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ └── finding-outcome-taxonomy.logical.openapi.yaml
├── checklists/
│ └── requirements.md
└── tasks.md
```
### Source Code (repository root)
```text
apps/platform/
├── app/
│ ├── Filament/
│ │ ├── Pages/
│ │ │ └── Reviews/
│ │ │ └── ReviewRegister.php
│ │ └── Resources/
│ │ ├── FindingResource.php
│ │ └── TenantReviewResource.php
│ ├── Jobs/
│ │ └── CompareBaselineToTenantJob.php
│ ├── Models/
│ │ ├── Finding.php
│ │ └── FindingException.php
│ ├── Services/
│ │ ├── Baselines/
│ │ │ └── BaselineAutoCloseService.php
│ │ ├── EntraAdminRoles/
│ │ │ └── EntraAdminRolesFindingGenerator.php
│ │ ├── Findings/
│ │ │ └── FindingWorkflowService.php
│ │ └── PermissionPosture/
│ │ └── PermissionPostureFindingGenerator.php
│ └── Support/
│ ├── Badges/
│ │ ├── BadgeCatalog.php
│ │ ├── BadgeDomain.php
│ │ └── BadgeRenderer.php
│ └── Findings/
│ └── FindingOutcomeSemantics.php
└── tests/
└── Feature/
├── Filament/
│ └── FindingResolvedReferencePresentationTest.php
└── Findings/
├── FindingWorkflowServiceTest.php
├── FindingRecurrenceTest.php
├── FindingRiskGovernanceProjectionTest.php
├── FindingOutcomeSummaryReportingTest.php
└── FindingsListFiltersTest.php
```
**Structure Decision**: Standard Laravel monolith. The work stays inside the existing findings workflow, system finding-generator seams, and current report/register consumers. No new base directory, no new panel, and no new persistence layer are required.
## Complexity Tracking
| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| One narrow findings-outcome semantics seam | Multiple producer and consumer paths need the same terminal-outcome meaning | Keeping meaning inside `FindingResource` static methods or free-form workflow-service strings would let reporting and automation drift immediately |
## Proportionality Review
- **Current operator problem**: Terminal findings are currently too ambiguous for operators and reviewers to tell whether the issue was fixed, only declared fixed, system-confirmed clear, or closed as non-actionable.
- **Existing structure is insufficient because**: The current lifecycle status plus free-form reasons cannot safely drive filters, audit interpretation, and report rollups with the same meaning.
- **Narrowest correct implementation**: Preserve the existing status model and existing finding rows, add bounded canonical reason keys plus one narrow verified-clear interpretation seam, and reuse it across workflow producers and readers.
- **Ownership cost created**: One findings-local semantics helper or equivalent seam, updates to workflow-service validation and audit metadata, updates to current generators and readers, and focused regression coverage.
- **Alternative intentionally rejected**: A new primary findings status such as `verified`, or a generic governance-case framework, was rejected because both add broader workflow complexity than current release truth requires.
- **Release truth**: Current-release truth. This feature corrects the meaning of today's findings workflow and today's downstream review/report consumers.
## Planned Phase Outputs
- `research.md`: map current free-form reason usage in `FindingWorkflowService`, current system resolve/reopen producers, and current report/list consumers that read terminal findings
- `data-model.md`: document the existing `Finding` fields, the canonical reason families, the derived verification meaning, and the report-bucket mapping
- `quickstart.md`: record the narrowest implementation and validation workflow for service, UI, and reporting changes
- `contracts/finding-outcome-taxonomy.logical.openapi.yaml`: describe the logical transition inputs and terminal-outcome outputs for list/detail/report consumers
## Implementation Strategy
### Phase A - Define the bounded outcome taxonomy close to the finding domain
**Goal**: Create one canonical source for resolve, close, reopen, and verification meaning without changing primary lifecycle status.
| Step | File | Change |
|------|------|--------|
| A.1 | `apps/platform/app/Models/Finding.php` | Add or centralize canonical reason-key lists and any small helper methods needed to describe terminal-outcome meaning while preserving existing status constants |
| A.2 | `apps/platform/app/Support/Findings/FindingOutcomeSemantics.php` | Add one narrow findings-local helper that maps current finding state, reason keys, and exception validity into operator-safe outcome labels, verification meaning, and report buckets |
| A.3 | `apps/platform/app/Services/Findings/FindingRiskGovernanceResolver.php` | Keep accepted-risk historical context and warning semantics aligned to the same canonical outcome buckets without collapsing exception validity into verified-clear meaning |
| A.4 | `apps/platform/app/Support/Badges/*` or existing finding narrative paths | Reuse centralized badge and narrative rules for any new emphasis instead of page-local color or label mappings |
### Phase B - Harden workflow transitions and audit metadata around canonical reasons
**Goal**: Make manual and system transitions persist canonical meaning instead of free-form semantics.
| Step | File | Change |
|------|------|--------|
| B.1 | `apps/platform/app/Services/Findings/FindingWorkflowService.php` | Replace free-form-only `validatedReason()` behavior for resolve, close, and reopen with canonical bounded keys plus optional secondary note support if needed |
| B.2 | `apps/platform/app/Services/Findings/FindingWorkflowService.php` | Extend audit metadata and snapshots so resolve, close, reopen, and system-origin transitions preserve structured outcome meaning and verification-safe context |
| B.3 | `apps/platform/app/Services/Findings/FindingWorkflowService.php` | Keep `status` unchanged, but ensure system resolve and system reopen paths clear or set the derived verified-clear interpretation consistently |
| B.4 | `apps/platform/app/Jobs/BackfillFindingLifecycleJob.php` and `apps/platform/app/Jobs/BackfillFindingLifecycleTenantIntoWorkspaceRunJob.php` | Replace legacy reason strings in current pre-production lifecycle normalization paths so canonical keys stay single-source during backfill and workspace-run repair flows |
### Phase C - Align system producers to the same system-clear and recurrence semantics
**Goal**: Remove semantic drift between manual workflow actions and automated finding producers.
| Step | File | Change |
|------|------|--------|
| C.1 | `apps/platform/app/Services/Baselines/BaselineAutoCloseService.php` | Use canonical system resolve reasons for auto-cleared findings |
| C.2 | `apps/platform/app/Services/EntraAdminRoles/EntraAdminRolesFindingGenerator.php` and `apps/platform/app/Services/PermissionPosture/PermissionPostureFindingGenerator.php` | Route system resolve and reopen paths through the same canonical keys and verified-clear semantics |
| C.3 | `apps/platform/app/Jobs/CompareBaselineToTenantJob.php` | Keep recurrence-driven reopen behavior aligned with the same structured reopen reasons and verified-clear reset rules |
### Phase D - Update operator surfaces without creating a second findings UX language
**Goal**: Make list, detail, and action modals speak the same terminal-outcome language.
| Step | File | Change |
|------|------|--------|
| D.1 | `apps/platform/app/Support/Ui/GovernanceActions/GovernanceActionCatalog.php` | Align shared findings action labels, helper copy, and reason policy metadata with the canonical resolve, close, and reopen vocabulary |
| D.2 | `apps/platform/app/Filament/Resources/FindingResource.php` | Update grouped resolve, close, and reopen actions to present canonical options and secondary help text instead of free-form-only terminal meaning |
| D.3 | `apps/platform/app/Filament/Resources/FindingResource.php` | Update list filters, default-visible terminal summaries, and detail narratives to consume the shared semantics helper |
| D.4 | `apps/platform/app/Filament/Resources/FindingResource.php` | Preserve open-backlog defaults from Spec 111 and keep `risk_accepted` distinct through existing governance-validity signals |
### Phase E - Align findings-derived review and report consumers
**Goal**: Keep downstream consumers from inventing a second outcome taxonomy.
| Step | File | Change |
|------|------|--------|
| E.1 | `apps/platform/app/Filament/Pages/Reviews/ReviewRegister.php` | Update any findings-derived bucket or label usage to consume the shared outcome semantics instead of local wording |
| E.2 | `apps/platform/app/Filament/Resources/TenantReviewResource.php` | Keep review-pack, executive-pack export cues, and tenant review summary presentation aligned with the same terminal-outcome buckets and verified-clear rules |
### Phase F - Prove the taxonomy through focused regression coverage
**Goal**: Lock workflow, UI, and reporting semantics together with the smallest sufficient test set.
| Step | File | Change |
|------|------|--------|
| F.1 | `apps/platform/tests/Feature/Findings/FindingWorkflowServiceTest.php` | Extend transition tests for canonical resolve, close, and reopen reasons plus audit metadata |
| F.2 | `apps/platform/tests/Feature/Findings/FindingRecurrenceTest.php` | Prove recurrence reopens remove verified-clear interpretation and keep structured reopen reasons |
| F.3 | `apps/platform/tests/Feature/Findings/FindingsListFiltersTest.php` and `apps/platform/tests/Feature/Filament/FindingResolvedReferencePresentationTest.php` | Prove list filters and detail presentation distinguish `Resolved pending verification`, `Verified cleared`, and non-remediation closure |
| F.4 | `apps/platform/tests/Feature/Findings/FindingOutcomeSummaryReportingTest.php` and `apps/platform/tests/Feature/Findings/FindingRiskGovernanceProjectionTest.php` | Prove report and governance projections keep `risk_accepted`, verified-cleared, and closed-non-remediation buckets distinct |
| F.5 | `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` plus the focused Pest commands above | Run formatting and the narrow proving set before implementation close-out |
## Post-Design Constitution Re-check
- **Read/write separation**: PASS. The design still changes only existing findings transitions and existing read consumers.
- **Persisted truth**: PASS. No new table, entity, or second semantic store was introduced by the design artifacts.
- **Behavioral state**: PASS. Verification meaning stays derived from bounded keys and existing status, not from a new primary state.
- **Abstraction control**: PASS. The design still justifies only one findings-local semantics helper.
- **Filament / Livewire / panel safety**: PASS. The design remains within Filament v5 and Livewire v4, keeps provider registration unchanged in `bootstrap/providers.php`, does not add a new globally searchable resource, and does not introduce a new asset family beyond the existing `filament:assets` deploy policy.
- **Testing governance**: PASS. Proof remains in focused feature suites and does not widen into browser or heavy-governance lanes.