---

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
