--- description: "Task list for Findings Workflow V2 + SLA (111)" --- # Tasks: Findings Workflow V2 + SLA (111) **Input**: Design documents from `specs/111-findings-workflow-sla/` **Prerequisites**: `specs/111-findings-workflow-sla/plan.md` (required), `specs/111-findings-workflow-sla/spec.md` (required for user stories), `specs/111-findings-workflow-sla/research.md`, `specs/111-findings-workflow-sla/data-model.md`, `specs/111-findings-workflow-sla/contracts/api-contracts.md`, `specs/111-findings-workflow-sla/quickstart.md` **Tests**: REQUIRED (Pest) — runtime behavior + UX contract enforcement. **Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. ## Phase 1: Setup (Shared Infrastructure) **Purpose**: Confirm a clean baseline before large schema + workflow changes. - [X] T001 [P] Start local environment (Sail) and confirm containers are healthy via `vendor/bin/sail` - [X] T002 [P] Run baseline Pest suite for current Findings/Drift flows and record failures (if any) in `tests/Feature/Drift/` --- ## Phase 2: Foundational (Blocking Prerequisites) **Purpose**: Core data model + RBAC + settings + badges that MUST be complete before ANY user story can ship. **⚠️ CRITICAL**: No user story work can begin until this phase is complete. - [X] T003 Add v2 lifecycle/SLA/ownership columns to findings in `database/migrations/2026_02_24_160000_add_finding_lifecycle_v2_fields_to_findings_table.php` - [X] T004 Add recurrence_key + SLA/query indexes for findings in `database/migrations/2026_02_24_160001_add_finding_recurrence_key_and_sla_indexes_to_findings_table.php` - [X] T005 Update v2 Finding model (statuses, casts, relationships, open/terminal status helpers, legacy acknowledged mapping) in `app/Models/Finding.php` - [X] T006 Update Finding status badge mapping for v2 statuses (incl legacy `acknowledged` → `triaged` surface) in `app/Support/Badges/Domains/FindingStatusBadge.php` - [X] T007 [P] Update status badge unit tests for v2 mapping in `tests/Unit/Badges/FindingBadgesTest.php` - [X] T008 [P] Update status badge feature tests for v2 mapping in `tests/Feature/Support/Badges/FindingBadgeTest.php` - [X] T009 Update Finding factory defaults/states to populate new lifecycle fields for tests in `database/factories/FindingFactory.php` - [X] T010 Add configurable Findings SLA policy setting (`findings.sla_days`) with defaults + validation + normalizer in `app/Support/Settings/SettingsRegistry.php` - [X] T011 Expose Findings SLA policy setting in Workspace Settings UI in `app/Filament/Pages/Settings/WorkspaceSettings.php` - [X] T012 [P] Add settings tests for findings SLA policy validation/normalization in `tests/Unit/Settings/FindingsSlaDaysSettingTest.php` - [X] T013 Add v2 Findings capabilities (view/triage/assign/resolve/close/risk_accept) to registry in `app/Support/Auth/Capabilities.php` - [X] T014 Map v2 Findings capabilities to tenant roles and keep `TENANT_FINDINGS_ACKNOWLEDGE` as deprecated alias for triage in `app/Services/Auth/RoleCapabilityMap.php` - [X] T015 Update FindingPolicy to require `TENANT_FINDINGS_VIEW` and expose per-action authorization for workflow mutations in `app/Policies/FindingPolicy.php` - [X] T016 Update review pack open-finding selection to use v2 open statuses helper in `app/Jobs/GenerateReviewPackJob.php` - [X] T017 Update review pack fingerprint computation to use v2 open statuses helper in `app/Services/ReviewPackService.php` **Checkpoint**: Foundation ready; user story implementation can now begin. --- ## Phase 3: User Story 1 - See Open Findings (Priority: P1) 🎯 MVP **Goal**: Default Findings list shows open findings across all finding types (not drift-only), with quick filters and due/assignee visibility. **Independent Test**: Seed a tenant with findings across multiple types and statuses, then verify the default list shows open workflow statuses across all types without adjusting filters. ### Tests for User Story 1 - [X] T018 [P] [US1] Add default list visibility test (open statuses across all types) in `tests/Feature/Findings/FindingsListDefaultsTest.php` - [X] T019 [P] [US1] Add quick filter tests (Overdue, High severity, My assigned) in `tests/Feature/Findings/FindingsListFiltersTest.php` ### Implementation for User Story 1 - [X] T020 [US1] Update FindingResource access gating to use `TENANT_FINDINGS_VIEW` in `app/Filament/Resources/FindingResource.php` - [X] T021 [US1] Remove drift-only + status=new default filters and default to open v2 statuses in `app/Filament/Resources/FindingResource.php` - [X] T022 [US1] Add quick filters (Open/Overdue/High severity/My assigned) and due_at/assignee columns in `app/Filament/Resources/FindingResource.php` - [X] T023 [US1] Update ListFindings filter helpers to match new filter shapes/defaults in `app/Filament/Resources/FindingResource/Pages/ListFindings.php` **Checkpoint**: User Story 1 is functional and independently testable. --- ## Phase 4: User Story 2 - Triage, Assign, And Resolve (Priority: P1) **Goal**: Consistent v2 workflow actions (single-record) with server-side enforcement, reasons, timestamps, audit logging, and correct 404/403 semantics. **Independent Test**: Create an open finding, execute each allowed status transition, and verify transitions are enforced server-side, recorded with timestamps/actors, and audited. ### Tests for User Story 2 - [X] T024 [P] [US2] Add workflow service unit tests (transition validation, reasons required, due_at reset on reopen, due_at stability across severity changes while open, assignment targets must be current tenant members) in `tests/Unit/Findings/FindingWorkflowServiceTest.php` - [X] T025 [P] [US2] Add Livewire row-action workflow tests (triage/start/assign/resolve/close/risk accept/reopen; assignee/owner picker limited to current tenant members; non-member IDs rejected) in `tests/Feature/Findings/FindingWorkflowRowActionsTest.php` - [X] T026 [P] [US2] Add Livewire view-header workflow tests (same action set; assignee/owner picker limited to current tenant members) in `tests/Feature/Findings/FindingWorkflowViewActionsTest.php` - [X] T027 [P] [US2] Add RBAC 404/403 matrix tests for workflow mutations (non-member 404; member missing cap 403) in `tests/Feature/Findings/FindingRbacTest.php` - [X] T028 [P] [US2] Add audit log tests for workflow mutations (before/after + reasons + actor; assert evidence payloads are never included) in `tests/Feature/Findings/FindingAuditLogTest.php` ### Implementation for User Story 2 - [X] T029 [US2] Implement SLA resolver service (reads `findings.sla_days` via SettingsResolver) in `app/Services/Findings/FindingSlaPolicy.php` - [X] T030 [US2] Implement workflow transition service (enforced map + timestamps + reason validation + due_at semantics + assignee/owner tenant membership validation + AuditLogger) in `app/Services/Findings/FindingWorkflowService.php` - [X] T031 [US2] Update Finding model legacy methods/compat helpers for v2 workflow (keep legacy `acknowledged` readable) in `app/Models/Finding.php` - [X] T032 [US2] Replace acknowledge row action with v2 workflow row actions (UiEnforcement, confirmations for destructive, assignee/owner options limited to current tenant members) in `app/Filament/Resources/FindingResource.php` - [X] T033 [US2] Add v2 workflow actions to ViewFinding header actions (same capability gates + confirmations; assignee/owner options limited to current tenant members) in `app/Filament/Resources/FindingResource/Pages/ViewFinding.php` - [X] T034 [US2] Expand ViewFinding infolist to show lifecycle + assignment + SLA fields (first/last seen, times_seen, due_at, assignee/owner, timestamps, reasons; preserve historical assignee/owner display even if membership is later removed) in `app/Filament/Resources/FindingResource.php` - [X] T035 [US2] Update FindingResource ActionSurface declaration to satisfy v2 UI Action Matrix (detail header actions now present) in `app/Filament/Resources/FindingResource.php` - [X] T036 [US2] Update legacy drift row-action test from acknowledge to triage in `tests/Feature/Drift/DriftAcknowledgeTest.php` - [X] T037 [US2] Update legacy drift row-action authorization test from acknowledge to triage capability semantics in `tests/Feature/Drift/DriftAcknowledgeAuthorizationTest.php` - [X] T038 [US2] Update Finding model behavior tests for v2 workflow semantics in `tests/Feature/Models/FindingResolvedTest.php` **Checkpoint**: Single-record workflow is functional, enforced, and audited. --- ## Phase 5: User Story 3 - SLA Due Visibility And Alerts (Priority: P1) **Goal**: SLA due alert producer emits one tenant-level event when newly-overdue open findings exist; AlertRule UI allows selecting “SLA due”. **Independent Test**: Create newly-overdue open findings for a tenant, run alert evaluation, and verify a single tenant-level SLA due event is produced and can match an enabled alert rule. ### Tests for User Story 3 - [X] T039 [P] [US3] Add SLA due producer tests (tenant-level aggregation + newly overdue gating using `window_start`; payload includes overdue_total + overdue_by_severity; idempotent per tenant+window) in `tests/Feature/Alerts/SlaDueAlertTest.php` - [X] T040 [P] [US3] Update test asserting sla_due is hidden to now assert it is selectable in `tests/Feature/ReviewPack/ReviewPackPruneTest.php` - [X] T041 [P] [US3] Extend event type options test coverage to include sla_due label in `tests/Feature/EntraAdminRoles/AdminRolesAlertIntegrationTest.php` ### Implementation for User Story 3 - [X] T042 [US3] Implement `slaDueEvents()` producer and include in EvaluateAlertsJob event list in `app/Jobs/Alerts/EvaluateAlertsJob.php` (use `window_start` from alert evaluation; `fingerprint_key` stable per tenant+window; event metadata contains overdue_total + overdue_by_severity) - [X] T043 [US3] Re-enable `sla_due` option in AlertRuleResource event type options in `app/Filament/Resources/AlertRuleResource.php` **Checkpoint**: SLA due alerting is end-to-end functional. --- ## Phase 6: User Story 4 - Recurrence Reopens (Priority: P2) **Goal**: Recurring findings reopen (from resolved only), lifecycle counters update, drift uses stable recurrence identity, and stale drift auto-resolves. **Independent Test**: Simulate a finding being resolved and then being detected again, verifying it transitions to `reopened`, counters update, and due date resets. ### Tests for User Story 4 - [X] T044 [P] [US4] Add drift recurrence tests (resolved→reopened; closed/risk_accepted stays terminal; times_seen increments; due_at resets; concurrency: do not auto-reopen if `resolved_at` is after observation time) in `tests/Feature/Findings/FindingRecurrenceTest.php` - [X] T045 [P] [US4] Add stale drift auto-resolve test (not detected in latest run → resolved_reason=no_longer_detected) in `tests/Feature/Findings/DriftStaleAutoResolveTest.php` - [X] T046 [P] [US4] Update permission posture generator tests for reopened + lifecycle fields in `tests/Feature/PermissionPosture/PermissionPostureFindingGeneratorTest.php` - [X] T047 [P] [US4] Update Entra admin roles generator tests for reopened + lifecycle fields in `tests/Feature/EntraAdminRoles/EntraAdminRolesFindingGeneratorTest.php` - [X] T048 [P] [US4] Update alert evaluation tests to include status=reopened where appropriate in `tests/Feature/Alerts/PermissionMissingAlertTest.php` - [X] T049 [P] [US4] Update alert evaluation tests to include status=reopened where appropriate in `tests/Feature/EntraAdminRoles/AdminRolesAlertIntegrationTest.php` ### Implementation for User Story 4 - [X] T050 [US4] Add recurrence_key computation helpers (dimension + subject identity) for drift findings in `app/Services/Drift/DriftFindingGenerator.php` - [X] T051 [US4] Upsert drift findings by `(tenant_id, recurrence_key)` and set canonical `fingerprint = recurrence_key` in `app/Services/Drift/DriftFindingGenerator.php` - [X] T052 [US4] Maintain lifecycle fields (first/last seen, times_seen) and set due_at on create/reset on reopen for drift findings in `app/Services/Drift/DriftFindingGenerator.php` (do not retroactively change due_at on severity changes) - [X] T053 [US4] Implement drift auto-reopen only from resolved → reopened (closed/risk_accepted remain terminal; still update seen fields; do not reopen if `resolved_at` is after observation time) in `app/Services/Drift/DriftFindingGenerator.php` - [X] T054 [US4] Implement stale drift auto-resolve for open drift findings not seen in run (resolved_reason=no_longer_detected) in `app/Services/Drift/DriftFindingGenerator.php` - [X] T055 [US4] Update permission posture generator to set lifecycle fields + due_at semantics + reopened status handling in `app/Services/PermissionPosture/PermissionPostureFindingGenerator.php` (do not retroactively change due_at on severity changes) - [X] T056 [US4] Update Entra admin roles generator to set lifecycle fields + due_at semantics + reopened status handling in `app/Services/EntraAdminRoles/EntraAdminRolesFindingGenerator.php` (do not retroactively change due_at on severity changes) - [X] T057 [US4] Update EvaluateAlertsJob existing producers to include status `reopened` where “new/re-detected” should alert in `app/Jobs/Alerts/EvaluateAlertsJob.php` **Checkpoint**: Recurrence behavior is correct and drift stops creating “new row per re-drift” noise. --- ## Phase 7: User Story 6 - Backfill Existing Findings (Priority: P2) **Goal**: One-time backfill/consolidation operation upgrades legacy findings to v2 fields, maps acknowledged→triaged, sets due_at from backfill time, and consolidates drift duplicates. **Independent Test**: Seed legacy findings (missing lifecycle fields, `acknowledged` status, drift duplicates), run the backfill operation, and verify fields are populated, statuses are mapped, and duplicates are consolidated. ### Tests for User Story 6 - [X] T058 [P] [US6] Add backfill tests (ack→triaged, lifecycle fields, due_at from backfill time, drift duplicate consolidation) in `tests/Feature/Findings/FindingBackfillTest.php` ### Implementation for User Story 6 - [X] T059 [US6] Register operation type `findings.lifecycle.backfill` in OperationCatalog (label + duration) in `app/Support/OperationCatalog.php` - [X] T060 [US6] Add backfill artisan command entrypoint (OperationRun-backed; deduped; dispatches job) in `app/Console/Commands/TenantpilotBackfillFindingLifecycle.php` - [X] T061 [US6] Implement backfill job skeleton (lock + chunking + summary_counts updates via OperationRunService) in `app/Jobs/BackfillFindingLifecycleJob.php` - [X] T062 [US6] Implement backfill mapping (acknowledged→triaged; set first_seen_at/last_seen_at/times_seen; due_at from backfill time + SLA days for legacy open) in `app/Jobs/BackfillFindingLifecycleJob.php` - [X] T063 [US6] Implement drift recurrence_key computation for legacy drift evidence in `app/Jobs/BackfillFindingLifecycleJob.php` - [X] T064 [US6] Implement drift duplicate consolidation (canonical row; duplicates resolved_reason=consolidated_duplicate; clear recurrence_key) in `app/Jobs/BackfillFindingLifecycleJob.php` - [X] T065 [US6] Add tenant-context Filament action to trigger backfill with Ops-UX queued toast + View run link in `app/Filament/Resources/FindingResource/Pages/ListFindings.php` **Checkpoint**: Backfill operation is observable, safe, and upgrades legacy data correctly. --- ## Phase 8: User Story 5 - Bulk Manage Findings (Priority: P3) **Goal**: Bulk workflow actions (triage/assign/resolve/close/risk accept) are safe, audited, and efficient for high volumes. **Independent Test**: Select multiple findings and run each bulk action, verifying that all selected findings update consistently and each change is audited. ### Tests for User Story 5 - [X] T066 [P] [US5] Add bulk workflow action tests (bulk triage/assign/resolve/close/risk accept + audit per record; cover >=100 records for at least one bulk action) in `tests/Feature/Findings/FindingBulkActionsTest.php` - [X] T067 [P] [US5] Update legacy bulk acknowledge selected test to bulk triage selected in `tests/Feature/Drift/DriftBulkAcknowledgeTest.php` - [X] T068 [P] [US5] Update legacy “acknowledge all matching” test to “triage all matching” in `tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingTest.php` - [X] T069 [P] [US5] Update legacy bulk authorization tests to new bulk action names/capabilities in `tests/Feature/Drift/DriftBulkAcknowledgeAuthorizationTest.php` - [X] T070 [P] [US5] Update legacy “all matching requires typed confirmation >100” test to triage-all-matching in `tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php` ### Implementation for User Story 5 - [X] T071 [US5] Replace bulk acknowledge actions with bulk workflow actions (UiEnforcement + confirmations + AuditLogger) in `app/Filament/Resources/FindingResource.php` - [X] T072 [US5] Implement “Triage all matching” header action (typed confirm >100; audited; respects current filters) in `app/Filament/Resources/FindingResource/Pages/ListFindings.php` **Checkpoint**: Bulk management is safe and supports high-volume operations. --- ## Phase 9: Polish & Cross-Cutting Concerns **Purpose**: Repo hygiene and end-to-end validation. - [X] T073 [P] Run Pint on touched files and fix formatting via `./vendor/bin/sail bin pint --dirty` - [X] T074 Run full test suite via Sail and fix failures in `tests/` - [X] T075 Run quickstart validation for feature 111 in `specs/111-findings-workflow-sla/quickstart.md` - [X] T076 Add post-backfill hardening migration to enforce NOT NULL on finding seen fields (`first_seen_at`, `last_seen_at`, `times_seen`) in `database/migrations/2026_02_24_160002_enforce_not_null_on_finding_seen_fields.php` - [X] T077 [P] Verify no new external API calls were introduced (Graph/HTTP) by scanning touched files for `GraphClientInterface` usage and direct HTTP clients (e.g., `Http::`, `Guzzle`) and confirming all Findings/Alerts logic remains DB-only --- ## Dependencies & Execution Order ### Phase Dependencies - **Setup (Phase 1)**: No dependencies - **Foundational (Phase 2)**: Depends on Setup completion; BLOCKS all user stories - **User Stories (Phase 3+)**: - US1/US2/US3 can proceed in parallel after Foundational - US4 depends on Foundational (and should align with US2 status model) - US6 depends on Foundational and should follow US4’s recurrence_key definition for drift - US5 depends on US2 (bulk actions reuse the workflow service + action builders) - **Polish (Phase 9)**: Depends on all desired user stories being complete ### User Story Dependencies - **US1 (P1)**: No dependencies besides Foundational - **US2 (P1)**: No dependencies besides Foundational - **US3 (P1)**: Depends on Foundational (due_at exists + open status semantics defined) - **US4 (P2)**: Depends on Foundational (v2 statuses + lifecycle fields exist) - **US6 (P2)**: Depends on Foundational; recommended after US4 so drift recurrence_key matches generator semantics - **US5 (P3)**: Depends on US2 (workflow service + single-record semantics must be correct first) ### Within Each User Story - Tests MUST be written and FAIL before implementation - Core services/helpers before UI actions - UI actions before bulk mutations - Story complete before moving to next priority --- ## Parallel Examples ### Parallel Example: User Story 1 ```bash # Tests can run in parallel: T018 # default list visibility test T019 # quick filter tests # Implementation can be split by file: T020 # resource access gating T023 # list filter helper adjustments ``` ### Parallel Example: User Story 2 ```bash # Tests can run in parallel: T024 # workflow service unit tests T025 # row action tests T026 # view header action tests T027 # RBAC semantics tests T028 # audit log tests # Implementation can be split by file: T029 # SLA resolver service T033 # view infolist expansion T036 # legacy drift test migration ``` ### Parallel Example: User Story 3 ```bash # Tests can run in parallel: T039 # SLA due producer tests T040 # AlertRuleResource option visibility test update T041 # alert options label assertions ``` ### Parallel Example: User Story 4 ```bash # Generator test updates can run in parallel: T046 # permission posture generator tests T047 # entra admin roles generator tests T048 # permission_missing alert evaluation tests T049 # entra admin roles alert evaluation tests ``` ### Parallel Example: User Story 6 ```bash # Backfill code and tests can be split: T058 # backfill tests T059 # OperationCatalog registration T060 # artisan command entrypoint ``` ### Parallel Example: User Story 5 ```bash # Bulk tests can run in parallel with legacy test migrations: T066 # bulk actions tests T067 # migrate bulk-selected test T068 # migrate triage-all-matching test T069 # migrate bulk auth tests ``` --- ## Implementation Strategy ### MVP First (User Story 1 Only) 1. Complete Phase 1: Setup 2. Complete Phase 2: Foundational 3. Complete Phase 3: User Story 1 4. STOP and validate User Story 1 independently ### Incremental Delivery 1. Setup + Foundational 2. US1 → validate 3. US2 → validate 4. US3 → validate 5. US4 + US6 → validate 6. US5 → validate --- ## Notes - `[P]` tasks = can run in parallel (different files, no dependencies) - `[US#]` label maps tasks to user stories for traceability - Destructive-like actions MUST use `->requiresConfirmation()` and be capability-gated (UiEnforcement) - Operations MUST comply with Ops-UX 3-surface contract when OperationRun is used