TenantAtlas/specs/231-finding-outcome-taxonomy/plan.md
ahmido 421261a517
Some checks failed
Main Confidence / confidence (push) Failing after 48s
feat: implement finding outcome taxonomy (#267)
## 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
2026-04-23 07:29:05 +00:00

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\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)

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 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.