New specs: archer-template-library, ccp-metrics-view-restructure, compliance-list-stale-after-sidebar-edit, compliance-metric-estimated-resolution-date, compliance-remediation-display-fix, flexible-jira-ticket-creation, forecast-burndown-chart, granite-loader-export, ivanti-queue-clear-completed-fix, multi-item-jira-ticket, queue-collapsible-sections, vendor-issue-type-dropdown New steering: archer-template-gen.md Updated: migration-registration-check hook, remediation-plan-history spec, gitlab-workflow, tech, versioning steering files
8.4 KiB
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
-
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_dateequals the first non-null resolution_date across the hostname's rows - Assert:
device.remediation_planequals 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
-
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
-
3. Fix for resolution_date and remediation_plan missing from compliance list endpoint
-
3.1 Add resolution_date and remediation_plan to the GET /items SQL SELECT clause
- In
backend/routes/compliance.jsaround line 601, addci.resolution_date, ci.remediation_planto the SELECT column list - The columns already exist in the
compliance_itemstable — 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
- In
-
3.2 Update groupByHostname() to propagate resolution_date and remediation_plan
- In
backend/routes/compliance.jsaround line 220, addresolution_date: null, remediation_plan: nullto 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
- In
-
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
-
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)
-
-
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.
- Run full test suite:
Task Dependency Graph
{
"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.