Sync .kiro/ from master — v2.2.0 release batch

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
This commit is contained in:
Jordan Ramos
2026-06-04 11:27:31 -06:00
parent 8ebd7e4d5e
commit a61d254ff9
54 changed files with 6992 additions and 59 deletions

View File

@@ -0,0 +1 @@
{"specId": "bfe6410f-227a-4c38-8e56-e6e6fe3bab50", "workflowType": "requirements-first", "specType": "bugfix"}

View File

@@ -0,0 +1,41 @@
# Bugfix Requirements Document
## Introduction
The compliance main table (CompliancePage.js) always displays "—" for the Resolution Date and Remediation Plan columns, even after a user edits these fields in the ComplianceDetailPanel. The detail panel correctly saves and reads per-device metadata via `PATCH /api/compliance/items/:hostname/metadata` and `GET /api/compliance/items/:hostname`, but the list endpoint `GET /api/compliance/items?team=X&status=Y` omits `resolution_date` and `remediation_plan` from its SELECT query, and the `groupByHostname()` helper does not propagate these fields into the device objects returned to the frontend.
## Bug Analysis
### Current Behavior (Defect)
1.1 WHEN the main compliance table fetches devices via `GET /api/compliance/items?team=X&status=Y` THEN the system does not include `resolution_date` or `remediation_plan` in the SQL SELECT clause, so these columns are absent from the query result rows.
1.2 WHEN `groupByHostname()` constructs per-device objects from the query rows THEN the system does not include `resolution_date` or `remediation_plan` fields in the device objects, resulting in `undefined` values on the frontend.
1.3 WHEN the frontend renders the Resolution Date column using `device.resolution_date || '—'` THEN the system always displays "—" because the field is `undefined`.
1.4 WHEN the frontend renders the Remediation Plan column using `truncateText(device.remediation_plan)` THEN the system always displays "—" because the field is `undefined`.
### Expected Behavior (Correct)
2.1 WHEN the main compliance table fetches devices via `GET /api/compliance/items?team=X&status=Y` THEN the system SHALL include `ci.resolution_date` and `ci.remediation_plan` in the SQL SELECT clause so these values are present in the query result rows.
2.2 WHEN `groupByHostname()` constructs per-device objects from the query rows THEN the system SHALL include `resolution_date` and `remediation_plan` fields in each device object, aggregated as the first non-null value encountered across the hostname's metric rows (or null if all are null).
2.3 WHEN the frontend renders the Resolution Date column and `device.resolution_date` contains a valid date string THEN the system SHALL display that date value instead of "—".
2.4 WHEN the frontend renders the Remediation Plan column and `device.remediation_plan` contains a non-empty string THEN the system SHALL display the truncated plan text instead of "—".
### Unchanged Behavior (Regression Prevention)
3.1 WHEN the detail panel saves metadata via `PATCH /api/compliance/items/:hostname/metadata` THEN the system SHALL CONTINUE TO persist `resolution_date` and `remediation_plan` to the `compliance_items` table correctly.
3.2 WHEN the detail panel reads a single device via `GET /api/compliance/items/:hostname` THEN the system SHALL CONTINUE TO return `resolution_date` and `remediation_plan` for that device's metrics.
3.3 WHEN `groupByHostname()` processes rows for devices that have no `resolution_date` or `remediation_plan` set (all null) THEN the system SHALL CONTINUE TO return `null` for those fields, and the frontend SHALL CONTINUE TO display "—".
3.4 WHEN the VCL reporting endpoints read `resolution_date` from `compliance_items` for burndown forecasts THEN the system SHALL CONTINUE TO function identically, as this fix only affects the list endpoint's SELECT and the grouping helper.
3.5 WHEN the main compliance table displays hostname, IP address, device type, failing metrics, and seen count THEN the system SHALL CONTINUE TO display these fields correctly without any change in behavior.
3.6 WHEN per-metric metadata scoping is used (different resolution_date values per metric on the same hostname) THEN the system SHALL CONTINUE TO store per-metric values correctly; the list endpoint SHALL aggregate by selecting the first non-null value across the hostname's metrics for display purposes.

View File

@@ -0,0 +1,111 @@
# 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.