## Summary - implement the finding outcome taxonomy end-to-end with canonical resolve, close, reopen, and verification semantics - align finding UI, filters, audit metadata, review summaries, and export/read-model consumers to the shared outcome semantics - add focused Pest coverage and complete the spec artifacts for feature 231 ## Details - manual resolve is limited to the canonical `remediated` outcome - close and reopen flows now use bounded canonical reasons - trusted system clear and reopen distinguish verified-clear from verification-failed and recurrence paths - duplicate lifecycle backfill now closes findings canonically as `duplicate` - accepted-risk recording now uses the canonical `accepted_risk` reason - finding detail and list surfaces now expose terminal outcome and verification summaries - review, snapshot, and review-pack consumers now propagate the same outcome buckets ## Filament / Platform Contract - Livewire v4.0+ compatibility remains intact - provider registration is unchanged and remains in `bootstrap/providers.php` - no new globally searchable resource was introduced; `FindingResource` still has a View page and `TenantReviewResource` remains globally searchable false - lifecycle mutations still run through confirmed Filament actions with capability enforcement - no new asset family was added; the existing `filament:assets` deploy step is unchanged ## Verification - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingWorkflowServiceTest.php tests/Feature/Findings/FindingRecurrenceTest.php tests/Feature/Findings/FindingsListFiltersTest.php tests/Feature/Filament/FindingResolvedReferencePresentationTest.php tests/Feature/Findings/FindingOutcomeSummaryReportingTest.php tests/Feature/Findings/FindingRiskGovernanceProjectionTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings tests/Feature/Filament/FindingResolvedReferencePresentationTest.php tests/Feature/Models/FindingResolvedTest.php tests/Unit/Findings/FindingWorkflowServiceTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/TenantReview/TenantReviewExplanationSurfaceTest.php tests/Feature/TenantReview/TenantReviewRegisterTest.php tests/Feature/ReviewPack/TenantReviewDerivedReviewPackTest.php` - browser smoke: `/admin/findings/my-work` -> finding detail resolve flow -> queue regression check passed ## Notes - this commit also includes the existing `.github/agents/copilot-instructions.md` workspace change that was already present in the worktree when all changes were committed Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #267
24 KiB
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\FindingResourceApp\Services\Findings\FindingWorkflowServiceApp\Services\Findings\FindingRiskGovernanceResolverApp\Models\FindingApp\Services\Baselines\BaselineAutoCloseServiceApp\Services\EntraAdminRoles\EntraAdminRolesFindingGeneratorApp\Services\PermissionPosture\PermissionPostureFindingGeneratorApp\Jobs\CompareBaselineToTenantJobApp\Jobs\BackfillFindingLifecycleJobApp\Jobs\BackfillFindingLifecycleTenantIntoWorkspaceRunJobApp\Filament\Pages\Reviews\ReviewRegisterApp\Filament\Resources\TenantReviewResourceApp\Support\Ui\GovernanceActions\GovernanceActionCatalog- centralized badge and audit metadata paths
- Shared abstractions reused: existing
FindingWorkflowService, existingFindingmodel lifecycle constants, existingBadgeCatalogandBadgeRenderer, existingGovernanceActionCatalogcopy contracts, existing review/register consumers, and current audit metadata structure inFindingWorkflowService - 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:
Featurefor 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.phpcd 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_acceptedstayed 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)
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)
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 inFindingWorkflowService, current system resolve/reopen producers, and current report/list consumers that read terminal findingsdata-model.md: document the existingFindingfields, the canonical reason families, the derived verification meaning, and the report-bucket mappingquickstart.md: record the narrowest implementation and validation workflow for service, UI, and reporting changescontracts/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 existingfilament:assetsdeploy policy. - Testing governance: PASS. Proof remains in focused feature suites and does not widen into browser or heavy-governance lanes.