Some checks failed
Main Confidence / confidence (push) Failing after 48s
## 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
174 lines
9.6 KiB
Markdown
174 lines
9.6 KiB
Markdown
# Data Model: Finding Outcome Taxonomy & Verification Semantics
|
|
|
|
## Overview
|
|
|
|
This feature does not add a new table or a new top-level persisted entity. It reuses the current `findings` row as the source of truth for current terminal meaning, keeps reopen rationale in audit metadata, and derives verification or reporting buckets through one findings-local semantics helper.
|
|
|
|
## Entity: Finding
|
|
|
|
**Persistence**: existing `findings` table
|
|
**Owner**: tenant-owned record
|
|
**Primary responsibility**: current workflow status, current terminal-outcome key, timestamps, and tenant-scoped operational ownership
|
|
|
|
### Relevant persisted fields
|
|
|
|
| Field | Type | Source | Notes |
|
|
|------|------|--------|-------|
|
|
| `id` | integer | existing | Primary key |
|
|
| `workspace_id` | integer | existing | Workspace isolation boundary |
|
|
| `tenant_id` | integer | existing | Tenant isolation boundary |
|
|
| `status` | string | existing | Primary lifecycle status; remains one of `new`, `acknowledged`, `triaged`, `in_progress`, `reopened`, `resolved`, `closed`, `risk_accepted` |
|
|
| `severity` | string | existing | Existing priority and SLA signal |
|
|
| `resolved_reason` | string nullable | existing | Becomes a bounded canonical resolve key instead of free-form prose |
|
|
| `closed_reason` | string nullable | existing | Becomes a bounded canonical close key and remains the stored reason for `risk_accepted` |
|
|
| `resolved_at` | datetime nullable | existing | Marks the current resolved terminal timestamp |
|
|
| `closed_at` | datetime nullable | existing | Marks the current closed or risk-accepted terminal timestamp |
|
|
| `reopened_at` | datetime nullable | existing | Marks the current reopen timestamp |
|
|
| `closed_by_user_id` | integer nullable | existing | Current actor for close and risk-accept paths |
|
|
| `owner_user_id` | integer nullable | existing | Accountability owner |
|
|
| `assignee_user_id` | integer nullable | existing | Active assignee |
|
|
| `evidence_jsonb` | jsonb | existing | Current supporting evidence; unchanged in this slice |
|
|
|
|
### Relationships
|
|
|
|
| Relationship | Type | Notes |
|
|
|-------------|------|-------|
|
|
| `tenant()` | belongsTo | Tenant scope owner |
|
|
| `ownerUser()` | belongsTo | Accountability owner |
|
|
| `assigneeUser()` | belongsTo | Active assignee |
|
|
| `closedByUser()` | belongsTo | Actor for close/risk accept |
|
|
| `findingException()` | hasOne | Existing risk-governance truth from Spec 154 |
|
|
|
|
### Validation rules introduced by this feature
|
|
|
|
| Rule | Description |
|
|
|------|-------------|
|
|
| `status` | No new status is introduced; all existing transition rules stay in force |
|
|
| `resolved_reason` | Required for resolve and system-clear transitions; must be a canonical key from the resolve-reason family |
|
|
| `closed_reason` | Required for close and risk-accept transitions; must be a canonical key from the close-reason family or the risk-accept key |
|
|
| `reopen_reason` | Required for reopen transitions; remains audit metadata and must be a canonical key from the reopen-reason family |
|
|
|
|
### Canonical reason families
|
|
|
|
#### Resolve reason keys
|
|
|
|
| Key | Meaning | Derived verification state |
|
|
|-----|---------|----------------------------|
|
|
| `remediated` | Operator declares that remediation work was completed | `pending_verification` |
|
|
| `no_longer_drifting` | Trusted compare no longer reproduces prior drift | `verified_cleared` |
|
|
| `permission_granted` | Trusted permission evidence no longer reproduces the finding condition | `verified_cleared` |
|
|
| `permission_removed_from_registry` | Trusted permission evidence confirms the triggering permission was removed | `verified_cleared` |
|
|
| `role_assignment_removed` | Trusted role evidence confirms the triggering assignment was removed | `verified_cleared` |
|
|
| `ga_count_within_threshold` | Trusted role evidence confirms the triggering count is now safe | `verified_cleared` |
|
|
|
|
#### Close reason keys
|
|
|
|
| Key | Meaning | Outcome family |
|
|
|-----|---------|----------------|
|
|
| `false_positive` | The finding should not have been actionable | `administrative_closure` |
|
|
| `duplicate` | The finding duplicates another case | `administrative_closure` |
|
|
| `no_longer_applicable` | The finding no longer applies to the tenant context | `administrative_closure` |
|
|
| `accepted_risk` | Existing accepted-risk path only; still governed by exception validity | `accepted_risk` |
|
|
|
|
#### Reopen reason keys
|
|
|
|
| Key | Meaning | Persistence |
|
|
|-----|---------|-------------|
|
|
| `recurred_after_resolution` | A previously addressed condition reappeared | audit metadata only |
|
|
| `verification_failed` | Trusted evidence contradicted the earlier resolved outcome | audit metadata only |
|
|
| `manual_reassessment` | An operator reopened the finding after review | audit metadata only |
|
|
|
|
### Derived facets from the current row
|
|
|
|
| Derived facet | Source | Meaning |
|
|
|--------------|--------|---------|
|
|
| `verification_state` | `status` + `resolved_reason` | `pending_verification`, `verified_cleared`, or `not_applicable` |
|
|
| `terminal_outcome_key` | `status` + canonical reason key | Stable UI/reporting key such as `resolved_pending_verification`, `verified_cleared`, `closed_false_positive`, `closed_duplicate`, `closed_no_longer_applicable`, or `risk_accepted` |
|
|
| `report_bucket` | `terminal_outcome_key` + governance validity | Report-friendly aggregation bucket |
|
|
| `outcome_label` | `terminal_outcome_key` | Canonical operator wording |
|
|
|
|
### State transitions
|
|
|
|
| From | Action | Stored result | Derived outcome |
|
|
|------|--------|---------------|-----------------|
|
|
| `new`, `triaged`, `in_progress`, `reopened`, `acknowledged` | `resolve(remediated)` | `status=resolved`, `resolved_reason=remediated` | `Resolved pending verification` |
|
|
| open statuses | `close(false_positive)` | `status=closed`, `closed_reason=false_positive` | `Closed as false positive` |
|
|
| open statuses | `close(duplicate)` | `status=closed`, `closed_reason=duplicate` | `Closed as duplicate` |
|
|
| open statuses | `close(no_longer_applicable)` | `status=closed`, `closed_reason=no_longer_applicable` | `Closed as no longer applicable` |
|
|
| open statuses | `riskAccept(accepted_risk)` | `status=risk_accepted`, `closed_reason=accepted_risk` | `Risk accepted` with separate governance validity |
|
|
| `resolved` with `resolved_reason=remediated` | trusted system clear | `status=resolved`, `resolved_reason=<system clear key>` | `Verified cleared` |
|
|
| open statuses | direct trusted system clear | `status=resolved`, `resolved_reason=<system clear key>` | `Verified cleared` |
|
|
| `resolved`, `closed`, `risk_accepted` | `reopen(<canonical reopen key>)` | `status=reopened`, terminal reason fields cleared, reopen reason recorded in audit metadata | open finding again |
|
|
|
|
## Entity: FindingException
|
|
|
|
**Persistence**: existing `finding_exceptions` table
|
|
**Owner**: tenant-owned record
|
|
**Primary responsibility**: governance validity for `risk_accepted`
|
|
|
|
### Relevant fields consumed by this feature
|
|
|
|
| Field | Type | Notes |
|
|
|------|------|-------|
|
|
| `status` | string | Existing workflow for exception requests and approvals |
|
|
| `current_validity_state` | string | Current validity used by `FindingRiskGovernanceResolver` |
|
|
| `effective_from` | datetime nullable | Existing validity window |
|
|
| `expires_at` | datetime nullable | Existing validity window |
|
|
| `review_due_at` | datetime nullable | Existing governance follow-up signal |
|
|
|
|
### Rule in this feature
|
|
|
|
- `FindingException` continues to determine whether `risk_accepted` is governed safely.
|
|
- `FindingException` does not contribute to `verified_cleared` and does not change remediation buckets.
|
|
|
|
## Derived read model: FindingOutcomeSemantics
|
|
|
|
**Persistence**: not persisted
|
|
**Owner**: findings-local support helper
|
|
**Primary responsibility**: unify list, detail, filter, and reporting semantics from current finding truth
|
|
|
|
### Inputs
|
|
|
|
| Input | Source |
|
|
|------|--------|
|
|
| `status` | `Finding` row |
|
|
| `resolved_reason` | `Finding` row |
|
|
| `closed_reason` | `Finding` row |
|
|
| `findingException` validity | `FindingException` relationship via existing resolver |
|
|
| `system_origin` and prior workflow steps | audit metadata, only when reconstructing history or detailed provenance |
|
|
|
|
### Outputs
|
|
|
|
| Output | Type | Notes |
|
|
|-------|------|-------|
|
|
| `terminalOutcomeKey` | string | Stable internal key |
|
|
| `label` | string | Canonical operator-facing wording |
|
|
| `verificationState` | string | `pending_verification`, `verified_cleared`, `not_applicable` |
|
|
| `reportBucket` | string | Aggregation bucket for reviews and exports |
|
|
| `historicalContext` | string nullable | Reuses current resolver-style explanatory text where appropriate |
|
|
|
|
### Report bucket mapping
|
|
|
|
| Terminal outcome key | Report bucket |
|
|
|----------------------|---------------|
|
|
| `resolved_pending_verification` | `remediation_pending_verification` |
|
|
| `verified_cleared` | `remediation_verified` |
|
|
| `closed_false_positive` | `administrative_closure` |
|
|
| `closed_duplicate` | `administrative_closure` |
|
|
| `closed_no_longer_applicable` | `administrative_closure` |
|
|
| `risk_accepted` | `accepted_risk` |
|
|
|
|
## Audit metadata additions or constraints
|
|
|
|
| Key | Existing/New | Purpose |
|
|
|-----|--------------|---------|
|
|
| `resolved_reason` | existing | Canonical current resolve key |
|
|
| `closed_reason` | existing | Canonical current close or risk-accept key |
|
|
| `reopened_reason` | existing | Canonical reopen key for reviewability |
|
|
| `system_origin` | existing | Provenance flag for system transitions |
|
|
| `resolved_at`, `closed_at`, `reopened_at` | existing | Timeline reconstruction |
|
|
|
|
### Audit rule
|
|
|
|
- Audit metadata remains the place to reconstruct path history, such as whether a verified-clear outcome happened directly through automation or after a prior manual `remediated` transition.
|
|
- The current row remains the source of truth for current filters and summaries. |