259 lines
24 KiB
Markdown
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. |