Files
cve-dashboard/.kiro/specs/compliance-remediation-display-fix/tasks.md

112 lines
8.4 KiB
Markdown
Raw Normal View History

# Implementation Plan: Compliance Remediation Display Fix
## Overview
Fix the compliance main table to display Resolution Date and Remediation Plan values. The `GET /items` endpoint omits `ci.resolution_date` and `ci.remediation_plan` from its SELECT clause, and `groupByHostname()` does not propagate these fields into device objects. The frontend already renders them correctly when present. This is a backend-only fix: add the columns to the SELECT and update the grouping helper to aggregate them as first-non-null across each hostname's metric rows.
## Tasks
- [x] 1. Write bug condition exploration test
- **Property 1: Bug Condition** - Resolution Date and Remediation Plan Missing from GET /items Response
- **CRITICAL**: This test MUST FAIL on unfixed code - failure confirms the bug exists
- **DO NOT attempt to fix the test or the code when it fails**
- **NOTE**: This test encodes the expected behavior - it will validate the fix when it passes after implementation
- **GOAL**: Surface counterexamples that demonstrate the bug exists (resolution_date and remediation_plan are undefined in grouped device objects)
- **Scoped PBT Approach**: Generate compliance_items rows where resolution_date and/or remediation_plan are non-null, pass them through groupByHostname(), and assert the output device objects contain those fields
- **Bug Condition**: `isBugCondition(row) = row.resolution_date != null OR row.remediation_plan != null` — any row with metadata set will lose it through groupByHostname()
- **Test file**: `backend/__tests__/compliance-remediation-display-fix.exploration.property.test.js`
- **Test structure**:
- Use fast-check to generate arbitrary hostname strings, date strings for resolution_date, and non-empty strings for remediation_plan
- Construct rows array with generated values present in resolution_date and remediation_plan columns
- Call groupByHostname() with those rows
- Assert: `device.resolution_date` equals the first non-null resolution_date across the hostname's rows
- Assert: `device.remediation_plan` equals the first non-null remediation_plan across the hostname's rows
- Run test on UNFIXED code
- **EXPECTED OUTCOME**: Test FAILS because groupByHostname() does not propagate resolution_date or remediation_plan (they are undefined on the output objects)
- Document counterexamples found (e.g., "groupByHostname([{hostname:'H1', resolution_date:'2025-06-01', ...}]) returns device with resolution_date === undefined")
- Mark task complete when test is written, run, and failure is documented
- _Requirements: 1.1, 1.2, 2.2_
- [x] 2. Write preservation property tests (BEFORE implementing fix)
- **Property 2: Preservation** - Existing groupByHostname Fields Unchanged
- **IMPORTANT**: Follow observation-first methodology
- **Test file**: `backend/__tests__/compliance-remediation-display-fix.preservation.property.test.js`
- Observe: groupByHostname() correctly aggregates hostname, ip_address, device_type, team, status, failing_metrics, seen_count, first_seen, last_seen, resolved_on, has_notes on unfixed code
- Observe: groupByHostname() deduplicates metrics by metric_id (no duplicate failing_metrics entries)
- Observe: groupByHostname() picks the maximum seen_count across rows for the same hostname
- Observe: groupByHostname() picks the earliest first_seen and latest last_seen across rows
- **Test structure**:
- Use fast-check to generate arrays of compliance rows with varying hostnames, metric_ids, seen_counts, first_seen/last_seen dates
- All generated rows have resolution_date = null and remediation_plan = null (non-bug-condition inputs)
- Call groupByHostname() and assert:
- Property: each device.hostname appears exactly once in output
- Property: device.failing_metrics contains no duplicate metric_ids
- Property: device.seen_count >= every row's seen_count for that hostname
- Property: device.first_seen <= every row's first_seen for that hostname
- Property: device.last_seen >= every row's last_seen for that hostname
- Property: device.has_notes matches noteHostnames membership
- Verify test passes on UNFIXED code
- **EXPECTED OUTCOME**: Tests PASS (confirms baseline behavior to preserve)
- Mark task complete when tests are written, run, and passing on unfixed code
- _Requirements: 3.3, 3.5_
- [x] 3. Fix for resolution_date and remediation_plan missing from compliance list endpoint
- [x] 3.1 Add resolution_date and remediation_plan to the GET /items SQL SELECT clause
- In `backend/routes/compliance.js` around line 601, add `ci.resolution_date, ci.remediation_plan` to the SELECT column list
- The columns already exist in the `compliance_items` table — they just need to be selected
- _Bug_Condition: isBugCondition(row) = row.resolution_date != null OR row.remediation_plan != null — these columns are not selected so they are always absent_
- _Expected_Behavior: The query result rows include resolution_date and remediation_plan values from the database_
- _Preservation: All other selected columns remain unchanged; query WHERE/ORDER BY clauses unchanged_
- _Requirements: 1.1, 2.1_
- [x] 3.2 Update groupByHostname() to propagate resolution_date and remediation_plan
- In `backend/routes/compliance.js` around line 220, add `resolution_date: null, remediation_plan: null` to the initial device object
- In the row iteration loop, aggregate as first-non-null: `if (row.resolution_date && !dev.resolution_date) dev.resolution_date = row.resolution_date;`
- Same for remediation_plan: `if (row.remediation_plan && !dev.remediation_plan) dev.remediation_plan = row.remediation_plan;`
- This matches the "first non-null value across the hostname's metric rows" aggregation strategy from the requirements
- _Bug_Condition: isBugCondition(device) = device has rows with non-null resolution_date or remediation_plan but groupByHostname() drops them_
- _Expected_Behavior: device.resolution_date = first non-null resolution_date across hostname's rows (or null if all null); same for remediation_plan_
- _Preservation: All other device fields (hostname, ip_address, device_type, team, status, failing_metrics, seen_count, first_seen, last_seen, resolved_on, has_notes) remain unchanged_
- _Requirements: 1.2, 2.2, 3.3, 3.5, 3.6_
- [x] 3.3 Verify bug condition exploration test now passes
- **Property 1: Expected Behavior** - Resolution Date and Remediation Plan Present in GET /items Response
- **IMPORTANT**: Re-run the SAME test from task 1 — do NOT write a new test
- The test from task 1 encodes the expected behavior (resolution_date and remediation_plan propagated through groupByHostname)
- When this test passes, it confirms the expected behavior is satisfied
- Run: `npx jest backend/__tests__/compliance-remediation-display-fix.exploration.property.test.js --run`
- **EXPECTED OUTCOME**: Test PASSES (confirms bug is fixed)
- _Requirements: 2.1, 2.2, 2.3, 2.4_
- [x] 3.4 Verify preservation tests still pass
- **Property 2: Preservation** - Existing groupByHostname Fields Unchanged
- **IMPORTANT**: Re-run the SAME tests from task 2 — do NOT write new tests
- Run: `npx jest backend/__tests__/compliance-remediation-display-fix.preservation.property.test.js --run`
- **EXPECTED OUTCOME**: Tests PASS (confirms no regressions — existing field aggregation unchanged)
- Confirm all tests still pass after fix (no regressions)
- [x] 4. Checkpoint - Ensure all tests pass
- Run full test suite: `npx jest backend/__tests__/compliance-remediation-display-fix --run`
- Verify no other compliance tests regressed: `npx jest backend/__tests__/compliance --run`
- Ensure all tests pass, ask the user if questions arise.
## Task Dependency Graph
```json
{
"waves": [
{ "id": 0, "tasks": ["1", "2"] },
{ "id": 1, "tasks": ["3.1", "3.2"] },
{ "id": 2, "tasks": ["3.3", "3.4"] },
{ "id": 3, "tasks": ["4"] }
]
}
```
## Notes
- This is a backend-only fix. The frontend (CompliancePage.js lines 883-890) already renders resolution_date and remediation_plan correctly when the data is present.
- The `groupByHostname()` function is a pure helper (no DB access), making it ideal for direct property-based testing without HTTP mocking.
- The "first non-null" aggregation strategy handles per-metric metadata scoping: different metrics on the same hostname may have different resolution_date values, but the list view shows the first encountered non-null for display purposes.