# 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.