Files
cve-dashboard/.kiro/specs/compliance-duplicate-failing-metrics/tasks.md
2026-05-19 15:01:25 -06:00

26 KiB
Raw Blame History

Implementation Plan

Overview

This implementation plan delivers six SQL-level fixes in backend/routes/compliance.js for the cross-vertical duplicate (hostname, metric_id) bug class documented in bugfix.md and design.md. Each fix targets a single endpoint or persistence block:

  1. GET /itemsDISTINCT ON (hostname, metric_id) plus a defensive groupByHostname Set
  2. GET /items/:hostnameDISTINCT ON (metric_id, status) with JS-side sort
  3. GET /vcl/stats heavy-hitters and per-team totals — device_team CTE
  4. GET /vcl/stats forecast-burndown — DISTINCT ON (hostname, metric_id)
  5. GET /mttrDISTINCT ON (hostname, metric_id)
  6. persistUpload() snapshot block — hostname_status CTE classifying each hostname via MIN(status)

The plan follows the bugfix workflow: a single property-based exploration test (Property 1) covering all six affected sites runs on UNFIXED code first to confirm the bug exists, then preservation property tests (Property 2) capture single-vertical and unique-key behaviour as a baseline. Implementation tasks apply each fix in order (3.13.6), followed by re-running the exploration test (3.7) and the preservation suite (3.8). The checkpoint runs the full backend test suite to confirm no regressions.

Task Dependency Graph

Tasks are organised into execution waves. Tasks within the same wave may run in parallel; tasks in later waves depend on the completion of earlier waves. Sub-tasks 3.13.6 are independent of each other (each touches a distinct query in backend/routes/compliance.js) and form a single parallel wave; sub-tasks 3.7 and 3.8 depend on all six fixes being landed.

{
  "waves": [
    {
      "wave": 1,
      "description": "Write the bug-condition exploration property test on UNFIXED code (Property 1, six slices covering /items, /items/:hostname, /vcl/stats heavy-hitters, /vcl/stats forecast-burndown, /mttr, persistUpload snapshot).",
      "tasks": ["1"]
    },
    {
      "wave": 2,
      "description": "Write the preservation property tests on UNFIXED code (observation-first methodology), recording baseline responses for single-vertical and unique-key fixtures across all five read endpoints and persistUpload(). Property 8.A is written but skipped pending the fix.",
      "tasks": ["2"]
    },
    {
      "wave": 3,
      "description": "Apply the six SQL-level fixes in backend/routes/compliance.js. Each sub-task targets a distinct query and is independent of the others — sub-tasks 3.1 through 3.6 can be implemented in parallel.",
      "tasks": ["3.1", "3.2", "3.3", "3.4", "3.5", "3.6"]
    },
    {
      "wave": 4,
      "description": "Re-run the exploration test (3.7) and preservation tests (3.8) against the fixed code. 3.7 confirms all six bug-condition slices now pass; 3.8 confirms preservation properties still pass and unskips Property 8.A to verify the representative-row policy.",
      "tasks": ["3.7", "3.8"]
    },
    {
      "wave": 5,
      "description": "Checkpoint — run the full backend Jest suite to confirm no regressions in adjacent compliance tests (vcl-compliance-reporting, vcl-aggregated-burndown).",
      "tasks": ["4"]
    }
  ]
}

Tasks

  • 1. Write bug condition exploration property test

    • Property 1: Bug Condition - Cross-Vertical Duplicate (hostname, metric_id) Distorts Compliance Endpoints
    • CRITICAL: This test MUST FAIL on unfixed code — failure confirms the bug exists in all six affected sites
    • DO NOT attempt to fix the test or the code when it fails
    • NOTE: This test encodes the expected behaviour (Property 16 from design.md). It will validate the fix when it passes after implementation.
    • GOAL: Surface counterexamples that demonstrate cross-vertical duplicate (hostname, metric_id) rows distort /items, /items/:hostname, /vcl/stats heavy-hitters, /vcl/stats forecast-burndown, /mttr, and the persistUpload() snapshot block
    • Scoped PBT Approach: Six concrete failing scenarios (one per affected site) generated by fast-check from a small fixed input space — each scenario seeds two compliance_items rows for the same (hostname, metric_id) across vertical IS NULL and vertical = 'NTS_AEO'. Use fast-check (already in use under backend/__tests__/*.property.test.js).
    • Place the test at backend/__tests__/compliance-duplicate-failing-metrics.exploration.property.test.js. Mock ../db with jest.mock exactly like vcl-compliance-reporting.property.test.js so route handlers can be invoked against an in-memory fixture, or stand up a transactional pg test schema if the repo already supports one — match whichever convention the existing compliance tests use.
    • Slice 1.A — /items failing-metrics dedup (Bug Condition isBugCondition: two active rows for (STEAM-INTERSIGHT, 7.1.1), one vertical IS NULL, one vertical = 'NTS_AEO', both team = 'STEAM')
      • Seed fixtureCrossVerticalDuplicateActive from design.md §Test Fixtures (different seen_count: 3 and 5)
      • Call GET /items?team=STEAM&status=active
      • Assert response.devices[0].failing_metrics.filter(m => m.metric_id === '7.1.1').length === 1 (Property 1 from design.md)
      • EXPECTED OUTCOME on unfixed code: FAILS — failing_metrics contains two 7.1.1 entries because groupByHostname pushes per row
    • Slice 1.B — /items/:hostname (metric_id, status) dedup (same fixture)
      • Call GET /items/STEAM-INTERSIGHT
      • Assert response.metrics.filter(m => m.metric_id === '7.1.1' && m.status === 'active').length === 1 (Property 2 from design.md)
      • Assert the surviving entry's seen_count === 5 (max across duplicates per Property 8)
      • EXPECTED OUTCOME on unfixed code: FAILS — detail query has no vertical filter and no dedup, returns two 7.1.1/active entries
    • Slice 1.C — /vcl/stats heavy-hitters cross-team (Bug Condition: two active rows for the same (hostname, metric_id) whose team differs across verticals)
      • Seed fixtureCrossVerticalTeamMismatch from design.md §Test Fixtures (team = 'STEAM' legacy, team = 'ACCESS-ENG' NTS_AEO)
      • Call GET /vcl/stats
      • Assert SUM(heavy_hitters[*].non_compliant) === stats.non_compliant (Property 3 from design.md)
      • EXPECTED OUTCOME on unfixed code: FAILS — sum exceeds the global count by 1 because COUNT(DISTINCT hostname) GROUP BY team counts the hostname under both teams
    • Slice 1.D — /vcl/stats forecast-burndown blockers (Bug Condition: two active rows for the same (hostname, metric_id) both with non-null resolution_date, same team)
      • Seed fixtureForecastDuplicateResolutionDate from design.md §Test Fixtures (team = 'STEAM', both resolution_date = '2025-09-30')
      • Call GET /vcl/stats, locate the STEAM entry in vertical_breakdown
      • Assert the unclamped teamNonCompliant - forecastItems.length === blockers AND blockers >= 0 (Property 4 from design.md)
      • EXPECTED OUTCOME on unfixed code: FAILS — unclamped value is -1, route reports 0 after Math.max(blockers, 0), hiding the inconsistency. The unclamped check makes the failure visible
    • Slice 1.E — /mttr aging buckets (same fixture as Slice 1.A, with seen_count = 5 on both rows)
      • Call GET /mttr
      • Assert SUM(aging[*].total) === COUNT(DISTINCT (hostname, metric_id) WHERE status = 'active') over the seeded items (Property 5 from design.md)
      • EXPECTED OUTCOME on unfixed code: FAILS — bucketAgingItems() increments the 46 cycles bucket twice, exceeding the distinct count by 1
    • Slice 1.F — persistUpload() snapshot block (Bug Condition: same (hostname, metric_id, team) with status = 'active' in legacy and status = 'resolved' in NTS_AEO)
      • Seed fixtureCrossVerticalStatusMismatch from design.md §Test Fixtures
      • Run persistUpload() with a no-op upload, read back the compliance_snapshots row for the current month and STEAM
      • Assert compliant + non_compliant <= total_devices (Property 6 from design.md)
      • EXPECTED OUTCOME on unfixed code: FAILS — the hostname is counted in both compliant and non_compliant, sum exceeds total_devices by 1
    • Run on UNFIXED code and capture all six counterexamples in the test output
    • Document the six counterexamples in the test file (a leading comment block listing the failing slice → symptom mapping) so the bug surface is recoverable from the test alone
    • Mark task complete when the test is written, run, and all six slice failures are documented
    • Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7
  • 2. Write preservation property tests (BEFORE implementing fix)

    • Property 2: Preservation - Single-Vertical and Unique-Key Inputs Are Byte-For-Byte Unchanged
    • IMPORTANT: Follow observation-first methodology — observe behaviour on UNFIXED code, then write property tests that capture it
    • Place the test at backend/__tests__/compliance-duplicate-failing-metrics.preservation.property.test.js. Use fast-check and the same jest.mock('../db', ...) pattern as the existing property tests
    • Build the unique-key generator: fc.array(...) of compliance_items rows where every (hostname, metric_id) pair is unique across the array. Mix vertical IS NULL, vertical = 'NTS_AEO', and other verticals; mix status = 'active' and status = 'resolved'; mix teams from ALLOWED_TEAMS
    • Observation step: For each fixture from design.md §Test Fixtures (fixtureLegacyOnly, fixtureNTSAEOOnly, fixtureMultiHostnameMixed restricted to its unique-key subset, plus a hand-built empty-state fixture), run all five read endpoints + persistUpload() on UNFIXED code and capture responses. Persist these as snapshot fixtures alongside the test (e.g., a small JSON file under backend/__tests__/fixtures/compliance-duplicate-failing-metrics/) so the test compares against the recorded baseline rather than against a moving target
    • Property 7.A — /items unique-key preservation: For any unique-key fixture, assert the response from GET /items?team=...&status=... (across team ∈ ALLOWED_TEAMS and status ∈ {'active', 'resolved'}) equals the recorded baseline byte-for-byte (deep equality on the JSON response)
    • Property 7.B — /items/:hostname unique-key preservation: For any unique-key fixture, assert the response from GET /items/:hostname for every seeded hostname equals the recorded baseline. Also assert active-then-resolved ordering is preserved (response.metrics[0..k].status === 'active' then response.metrics[k+1..].status === 'resolved', sorted by metric_id within each group), per design.md §Preservation Requirements item 5
    • Property 7.C — /vcl/stats unique-key preservation: Assert response equality across stats.compliant, stats.non_compliant, the donut block (blocked / in_progress), heavy_hitters (full array), and vertical_breakdown (full array including blockers). Generate inputs that vary resolution_date density to exercise the donut categorisation
    • Property 7.D — /mttr unique-key preservation: Assert response equality on the full aging array (per-bucket per-team totals)
    • Property 7.E — persistUpload() unique-key preservation: For a single-status-per-hostname fixture (every hostname has only active or only resolved rows, never both, within a team), run persistUpload() and assert the compliance_snapshots rows for the current (snapshot_month, vertical) are identical to the recorded baseline
    • Property 7.F — /items query-param validation preservation: Assert that ?team=OTHER (not in ALLOWED_TEAMS) returns HTTP 400 and ?status=invalid returns HTTP 400, on both unfixed and fixed code. Assert /items/:hostname for an unknown hostname returns HTTP 404
    • Property 8.A — Representative-row policy on duplicates: For inputs WITH duplicates, assert the surviving entry carries seen_count = MAX(seen_count), first_seen = MIN(first_seen), last_seen = MAX(last_seen) across the duplicate rows (this is the only preservation property that exercises the duplicate path, since it specifies WHAT the dedup must produce — keep it in the preservation file because it defines the contract the fix must satisfy)
    • Run the full preservation suite on UNFIXED code
    • EXPECTED OUTCOME: Properties 7.A7.F PASS on unfixed code (they describe baseline behaviour to preserve). Property 8.A is the only one expected to FAIL on unfixed code, since it asserts the post-fix representative-row contract — exclude it from the unfixed-code run or mark it as test.skip until the fix lands, with a comment pointing to task 3.5
    • Mark task complete when 7.A7.F are written, run, and passing on unfixed code, and 8.A is written but skipped pending the fix
    • Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7
  • 3. Fix for cross-vertical duplicate (hostname, metric_id) rows distorting compliance endpoints

    • 3.1 Implement Fix 1: GET /items DISTINCT ON (hostname, metric_id) and defensive groupByHostname dedup

      • Edit backend/routes/compliance.js, router.get('/items', ...) (around line 535)
      • Rewrite the items query as SELECT DISTINCT ON (ci.hostname, ci.metric_id) ... ORDER BY ci.hostname, ci.metric_id, ci.seen_count DESC, ci.upload_id DESC keeping the existing WHERE ci.team = $1 AND ci.status = $2 AND (ci.vertical IS NULL OR ci.vertical = 'NTS_AEO') predicate intact (per design.md Fix 1 step 1 and Preservation Requirements bullet 4)
      • Edit groupByHostname() (around line 213) to add a _seenMetricIds Set per device, push failing_metrics only when the metric_id has not been seen, aggregate seen_count via Math.max, first_seen via Math.min, last_seen via Math.max, and strip _seenMetricIds from the returned device object so the response shape is unchanged (per design.md Fix 1 step 2)
      • Bug_Condition: isBugCondition(items) — two or more rows share (hostname, metric_id) across verticals (design.md §Bug Condition)
      • Expected_Behavior: Property 1 — device.failing_metrics.length === new Set(device.failing_metrics.map(m => m.metric_id)).size (design.md §Correctness Properties Property 1)
      • Preservation: Single-vertical and unique-key inputs unchanged; (ci.vertical IS NULL OR ci.vertical = 'NTS_AEO') predicate retained; team/status query-param validation unchanged (design.md §Preservation Requirements bullets 1, 4, 7)
      • Requirements: 2.1, 3.1, 3.3, 3.4
    • 3.2 Implement Fix 2: GET /items/:hostname DISTINCT ON (metric_id, status) and JS sort

      • Edit backend/routes/compliance.js, router.get('/items/:hostname', ...) (around line 575)
      • Rewrite the metrics query as SELECT DISTINCT ON (ci.metric_id, ci.status) ... FROM compliance_items ci ... WHERE ci.hostname = $1 ORDER BY ci.metric_id, ci.status, ci.seen_count DESC, ci.upload_id DESC (per design.md Fix 2 step 1)
      • Add a JS-side sort on the deduped rows that reproduces the original ORDER BY ci.status DESC, ci.metric_id ordering — metricRows.sort((a, b) => a.status !== b.status ? b.status.localeCompare(a.status) : a.metric_id.localeCompare(b.metric_id)) (per design.md Fix 2 step 2)
      • Leave the existing metricRows.find(r => r.status === 'active') || metricRows[0] identity lookup unchanged (per design.md Fix 2 step 3)
      • Bug_Condition: isBugCondition(items) plus duplicate rows for the same hostname across verticals with no vertical filter on the detail query
      • Expected_Behavior: Property 2 — exactly one entry per (metric_id, status) pair, surviving entry carries MAX(seen_count) across duplicates
      • Preservation: active-before-resolved ordering, metric_id ordering within each status group, identity lookup unchanged
      • Requirements: 2.2, 2.3, 3.2, 3.3
    • 3.3 Implement Fix 3: /vcl/stats heavy-hitters and per-team totals via device_team CTE

      • Edit backend/routes/compliance.js, router.get('/vcl/stats', ...) (around line 990, the teamRows query)
      • Replace the heavy-hitters query with a CTE: WITH device_team AS (SELECT DISTINCT ON (hostname) hostname, COALESCE(team, 'Unknown') AS team, resolution_date FROM compliance_items WHERE status = 'active' ORDER BY hostname, seen_count DESC, upload_id DESC) SELECT team, COUNT(DISTINCT hostname)::int AS non_compliant, MAX(resolution_date) AS compliance_date FROM device_team GROUP BY team ORDER BY COUNT(DISTINCT hostname) DESC (per design.md Fix 3 step 1)
      • Rewrite the per-team-total query inside the for (const teamRow of teamRows) loop to use the same device_team-style CTE: WITH device_team AS (SELECT DISTINCT ON (hostname) hostname, COALESCE(team, 'Unknown') AS team FROM compliance_items ORDER BY hostname, seen_count DESC, upload_id DESC) SELECT COUNT(*)::int AS total FROM device_team WHERE team = $1 (per design.md Fix 3 step 3)
      • Leave the global stats.non_compliant query unchanged — it is already correct (per design.md Fix 3 step 2)
      • Bug_Condition: a hostname's team differs across verticals so COUNT(DISTINCT hostname) GROUP BY team counts it under both teams
      • Expected_Behavior: Property 3 — SUM(heavy_hitters[*].non_compliant) === stats.non_compliant and each hostname assigned to exactly one team (the team from its representative row)
      • Preservation: global stats.compliant / stats.non_compliant unchanged, donut categorisation unchanged
      • Requirements: 2.5, 3.6
    • 3.4 Implement Fix 4: /vcl/stats forecast-burndown DISTINCT ON (hostname, metric_id)

      • Edit backend/routes/compliance.js, router.get('/vcl/stats', ...) (around line 1015, the forecastItems query)
      • Rewrite as SELECT DISTINCT ON (hostname, metric_id) resolution_date FROM compliance_items WHERE status = 'active' AND COALESCE(team, 'Unknown') = $1 AND resolution_date IS NOT NULL ORDER BY hostname, metric_id, seen_count DESC, upload_id DESC (per design.md Fix 4 step 1)
      • Leave blockers = teamNonCompliant - forecastItems.length and the Math.max(blockers, 0) clamp unchanged — the clamp becomes a no-op in correct operation but stays as belt-and-braces (per design.md Fix 4 steps 2 and 3)
      • Bug_Condition: duplicate (hostname, metric_id) rows with non-null resolution_date inflate forecastItems.length past teamNonCompliant
      • Expected_Behavior: Property 4 — unclamped teamNonCompliant - dedupedForecastCount === blockers and blockers >= 0
      • Preservation: Math.max(blockers, 0) clamp retained as a no-op safeguard, downstream forecast bucketing unchanged
      • Requirements: 2.6, 3.6
    • 3.5 Implement Fix 5: /mttr DISTINCT ON (hostname, metric_id) in SQL

      • Edit backend/routes/compliance.js, router.get('/mttr', ...) (around line 824)
      • Rewrite the query as SELECT DISTINCT ON (hostname, metric_id) COALESCE(seen_count, 1) AS seen_count, team FROM compliance_items WHERE status = 'active' ORDER BY hostname, metric_id, seen_count DESC, upload_id DESC (per design.md Fix 5 step 1)
      • Leave bucketAgingItems() unchanged — its contract is preserved because it is also called from /vcl/stats (per design.md Fix 5 steps 2 and 3)
      • Bug_Condition: duplicate active rows for the same (hostname, metric_id) are bucketed twice by bucketAgingItems()
      • Expected_Behavior: Property 5 — SUM(aging[*].total) === COUNT(DISTINCT (hostname, metric_id) WHERE status = 'active') and each unique violation bucketed exactly once with a single representative seen_count
      • Preservation: bucketAgingItems() contract unchanged, per-team buckets on single-vertical fixtures unchanged
      • Requirements: 2.7, 3.7
    • 3.6 Implement Fix 6: persistUpload() snapshot via hostname_status CTE

      • Edit backend/routes/compliance.js, persistUpload() (lines 81192), specifically the verticalStats query at line 157
      • Rewrite the snapshot query as WITH hostname_status AS (SELECT team, hostname, MIN(status) AS status FROM compliance_items WHERE team IS NOT NULL GROUP BY team, hostname) SELECT team AS vertical, COUNT(*)::int AS total_devices, COUNT(*) FILTER (WHERE status = 'resolved')::int AS compliant, COUNT(*) FILTER (WHERE status = 'active')::int AS non_compliant FROM hostname_status GROUP BY team (per design.md Fix 6 step 1)
      • Leave the downstream INSERT ... ON CONFLICT (snapshot_month, vertical) DO UPDATE block and compliance_pct calculation unchanged (per design.md Fix 6 steps 2 and 3)
      • Bug_Condition: a hostname has both active and resolved rows for the same team across verticals, so the CASE WHEN status = 'X' THEN hostname END pattern lets it appear in both compliant and non_compliant
      • Expected_Behavior: Property 6 — every snapshot row satisfies compliant + non_compliant === total_devices (active wins over resolved via MIN(status))
      • Preservation: snapshot rows for single-status-per-hostname fixtures unchanged, error-handling try/catch unchanged
      • Requirements: 2.4, 3.5
    • 3.7 Verify bug condition exploration test now passes

      • Property 1: Expected Behavior - Cross-Vertical Duplicate (hostname, metric_id) Distorts Compliance Endpoints
      • IMPORTANT: Re-run the SAME test from task 1 — do NOT write a new test
      • The test from task 1 encodes the expected behaviour for all six slices (1.A1.F). When all six slices pass, the bug is fixed across every affected site
      • Run npx jest backend/__tests__/compliance-duplicate-failing-metrics.exploration.property.test.js --runInBand (or the repo's equivalent jest invocation)
      • EXPECTED OUTCOME: All six slices PASS — Slice 1.A (/items dedup), Slice 1.B (/items/:hostname dedup), Slice 1.C (/vcl/stats heavy-hitters), Slice 1.D (/vcl/stats forecast blockers), Slice 1.E (/mttr aging), Slice 1.F (persistUpload() snapshot invariant)
      • Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7
    • 3.8 Verify preservation tests still pass and unskip Property 8.A

      • Property 2: Preservation - Single-Vertical and Unique-Key Inputs Are Byte-For-Byte Unchanged
      • IMPORTANT: Re-run the SAME tests from task 2 — do NOT write new tests
      • Unskip Property 8.A (the representative-row policy assertion) which was deferred in task 2 because it asserts the post-fix contract
      • Run npx jest backend/__tests__/compliance-duplicate-failing-metrics.preservation.property.test.js --runInBand
      • EXPECTED OUTCOME: Properties 7.A7.F continue to PASS (no regressions on unique-key or single-vertical inputs) and Property 8.A now PASSES on the duplicate path (seen_count = MAX, first_seen = MIN, last_seen = MAX across duplicates)
      • Confirm the recorded baseline JSON snapshots match the post-fix output for every unique-key fixture
      • Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7
  • 4. Checkpoint - Run full backend test suite and confirm all tests pass

    • Run the full backend test suite from backend/: npx jest --runInBand (or the repo's standard test command)
    • Confirm both new property test files pass and that no existing tests under backend/__tests__/ regressed — particularly vcl-compliance-reporting.property.test.js, vcl-aggregated-burndown.property.test.js, and vcl-compliance-reporting.test.js, all of which exercise overlapping compliance code paths
    • If any pre-existing test fails, diagnose whether the failure is a genuine regression introduced by the fix or a pre-existing flake. If a regression, return to the relevant sub-task in step 3 and adjust the fix; do not silence the failing test
    • Ask the user if any unexpected questions arise about test scope, fixture naming, or whether any preservation snapshot needs to be re-recorded against the fixed code
    • Mark complete when the full suite is green

Notes

  • Fix sequencing: Tasks 3.1 through 3.6 are independent — each one targets a distinct query and can be implemented and committed in any order. Implementers may parallelise work across these sub-tasks, but tasks 3.7 and 3.8 depend on all six fixes being landed before they can validate.
  • Test framework: Both new property tests follow the existing backend/__tests__/*.property.test.js convention — fast-check for generators, jest.mock('../db', ...) for mocking the pg pool, and matching helper imports (auditLog, ivantiApi) where required. See vcl-compliance-reporting.property.test.js for the canonical pattern.
  • Fixture location: Place fixtures at backend/__tests__/fixtures/compliance-duplicate-failing-metrics/ if a directory-based layout is preferred, or inline as factory functions in the test files. Match whichever convention the existing compliance test files use — if they inline factories (as vcl-compliance-reporting.test.js does), follow suit.
  • Property 8.A skip: Property 8.A in task 2 is intentionally skipped on unfixed code because it asserts the post-fix representative-row contract. Task 3.8 unskips it. This is the only test that exercises the duplicate path inside the preservation file; it lives there because the contract it captures is precisely the one preservation must not violate after the fix lands.
  • Adjacent spec coordination: Fix 6 (persistUpload() snapshot) is conceptually adjacent to but mechanically distinct from the persistUpload fix in compliance-duplicate-chart-entries. That spec adds a WHERE vertical = $1 filter to scope the snapshot to one vertical; this spec adds the hostname_status CTE so each hostname is classified once within whichever vertical is snapshotted. Both are independently necessary. If both specs land in the same release, ensure the merged query carries both the vertical filter AND the hostname_status CTE.
  • Math.max(blockers, 0) clamp: Left in place as a belt-and-braces safeguard per design.md Fix 4 step 3. After the fix it becomes a no-op; if a future regression reintroduces inconsistency, Property 4 (Slice 1.D) catches it before the clamp can mask the underlying bug.
  • Documentation follow-up: Per .kiro/steering/workflow.md, after the fix lands and is committed to master, add a bug report under docs/bug-reports/ on the ops/records branch using the compliance-duplicate-failing-metrics-<YYYY-MM-DD>.md naming convention. Each of the six fix sites is a separate ## Bug N entry following the Symptom → Cause → Fix triad.