feat(compliance): lens as default score everywhere, OS-resolved, framework-scoped trend (v0.6.0) - #748
Merged
Conversation
The effective compliance lens was auto-applied on only ONE surface — the host-detail hero tile. The hosts-list column, Scans Coverage column, and the fleet KPI all showed the raw all-corpus score unless the frontend passed an explicit ?framework=, so one host could read (e.g.) 78% STIG on its detail page but 70.2% all-rules everywhere else. Now, with no explicit ?framework=: - GET /hosts list compliance_summary scores each host against its OWN effective target (host_effective_target -> org default), resolved per host in one grouped query (reuses framework.MatchSQL with a column reference). The Scans Coverage tab inherits this (same endpoint). - GET /fleet/score defaults to the org default lens (ComplianceConfig.DefaultFramework). Per-host targets are not aggregated into one fleet number (mixed-fleet cohorts are a later phase). An explicit ?framework= still overrides uniformly; with neither a target nor an org default set, both stay All rules (backward compatible). Backend-only: the frontend already renders compliance_summary + the fleet score, so the lens-scoped numbers appear with no UI or schema change. The compliance TREND card stays all-rules (its posture snapshots carry no framework dimension — a separate storage change, deferred). Spec: system-compliance-lens v1.4.0 (C-06, AC-09, AC-10). specter check + check --test clean; spec at 100% annotation coverage. New API tests verify per-host default, org-default fleet, and explicit-override on both.
…hmark A per-host compliance score was resolving a family lens with the family-UNION match (framework.MatchSQL): "stig" matched stig_rhel9 + stig_rhel10 + stig_rhel8 + stig_ubuntu* all at once. A single host carries mapped rules for several OS benchmarks, so this graded (e.g.) a RHEL 9 host partly against the RHEL 10 STIG — the hosts list showed 70% (400/571, family union) while the host-detail hero tile showed 88% (353/401, stig_rhel9) for the SAME host and lens. Fix: the three per-host SCORE surfaces now resolve a family to the host's OWN OS-specific key via new framework.OSResolvedMatchSQL(famRef, osFamily, osVersion): a RHEL 9 host's "stig" -> stig_rhel9 only. An OS-neutral family (nist_800_53, pci_dss_4, srg) and an explicit specific key resolve to the bare key. - loadHostListComplianceByIDs (hosts list + Scans Coverage): joins hosts, OS-resolves per row. - loadHostComplianceSummary (GetHostByID compliance_summary): OS-resolves. - fleetrollup.FleetComplianceScore: joins hosts, each measured against its own OS benchmark then aggregated (a mixed-OS fleet's "stig" still spans rhel9+rhel10, each via its own key). The family-union MatchSQL is retained for the family-scoped rule LISTS (fleet top-failing-rules, recent-changes), where "any host's STIG rule" is the intent. Result: the list column, host-detail tile, and fleet score all agree for a host (owas-el9-ks01 reads 88% everywhere, not 70% on the list / 88% on detail). Spec system-compliance-lens: C-06 amended (OS-resolved scores), C-02 clarified (family union scopes to rule-lists), AC-03/AC-09/AC-10 updated to assert the wrong-OS variant is excluded. Backend-only; no schema change. specter clean, 100% coverage; framework + fleetrollup + server suites green.
…lows the lens The compliance trend card read all-rules posture snapshots (one score per host per day, no framework dimension), so it showed 68% all-rules while the host-detail tile / list / coverage showed 88% STIG for the same host once a lens was the default. Store the snapshot per lens instead. Migration 0053 adds a `framework` column (PK now host_id, snapshot_date, framework). The daily Rollup writes the all-rules series (framework='') PLUS one row per framework FAMILY the host has rules in, each OS-RESOLVED via the same rule as the score surfaces: a RHEL 9 host's "stig" row scores stig_rhel9 only (a stig_rhel10 rule it carries is excluded), OS-neutral families (nist_800_53, pci_dss_4, srg) resolve via the bare key. A rule counts at most once per family. The trend endpoints resolve the effective lens server-side (host: effective target; fleet: org default) and read that series — so the trend line now agrees with the tile. Storing every family means switching the org default just selects a different pre-computed series with no recompute. History is start-fresh: pre-0053 rows are the all-rules series, per-family accrues forward. HostTrend/FleetTrend gain a lens param (a specific key normalizes to its family, stig_rhel9 -> stig). No API schema change (lens is resolved server-side, not a query param). Specs: system-posture-snapshots v1.1.0 (C-01 amended, C-04, AC-03), api-compliance-trend v1.1.0 (C-03, AC-05); compliance-lens C-06 updated (trend now in scope). specter clean, 100% coverage; posture + trend-handler suites green.
The Groups page compliance numbers were the only score surface still fully UNLENSED — both the per-group AvgCompliancePct and the groups-page fleet AvgCompliancePct counted raw passing/(pass+fail) over all rules, ignoring the org default lens and per-host/group targets. So a Groups page showed all-rules while the hosts list / tile / trend showed STIG. Now both are lens-scoped and OS-resolved (framework.OSResolvedMatchSQL): - Per-group AVG: each member scored against its EFFECTIVE target (host_effective_target, else the org default), resolved to that host's OS key (a RHEL 9 member's stig -> stig_rhel9), so a group's AVG matches the hosts list / host-detail tile. Connectivity counts (hosts/online/down) stay OS-agnostic. - Groups-page fleet AVG: the org default lens, each host OS-resolved — the same rule as GET /fleet/score, so the two fleet KPIs agree. Empty target + empty org default = all rules (backward compatible; NULLIF guards the empty-string case). The handler loads the org default (h.sysCfg.LoadCompliance) and threads it into group.List / group.Summary. Backend-only, no schema/API change. Spec system-compliance-lens AC-11 (groups lens-scoped + OS-resolved). specter clean, 100% coverage; group + server-group suites green.
Compliance-lens Phase 3 completion: the framework lens is the default score on every surface (list, Coverage, fleet, Groups, host trend), per-host scores resolve to the host's OS-specific benchmark (a RHEL 9 host's STIG = stig_rhel9, not the family union), and framework-scoped posture snapshots (migration 0053) make the compliance trend follow the lens.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reported from three screenshots: the same host shows the all-corpus compliance score everywhere except its detail-page hero tile. For
owas-tst02: hero tile could read a framework-scoped %, but the hosts list showed 70.2% (401/571 all rules), Scans Coverage 70%, and the trend 69.8% — the raw all-rules number.Root cause: the effective-lens resolver (
framework.EffectiveTarget) was wired into exactly one production path —GetHostByID(the hero tile). Every other score surface ignored the lens unless the frontend passed an explicit?framework=.Fix (backend-only)
With no explicit
?framework=:compliance_summaryscores each host against its own effective target (host_effective_target→ org default), resolved per host in one grouped query. Implemented by reusingframework.MatchSQLwith a column reference (eff.lens) instead of a bind placeholder, so the family-aware match runs per row. → the list column now matches the hero tile./hostsendpoint).ComplianceConfig.DefaultFramework). Per-host targets are not aggregated into one fleet number — mixed-fleet cohorts are a later phase; the org default is the single-lens fleet view.An explicit
?framework=still overrides uniformly. With neither a target nor an org default set, both stay All rules (backward compatible — existing tests unaffected).The frontend already renders
compliance_summary+ the fleet score, so the lens-scoped numbers appear with no UI or schema change.Explicitly out of scope (deferred)
The compliance TREND card stays all-rules:
posture_snapshotscarries a single all-rules score per host per day with no framework dimension. Making the trend lens-scoped needs new storage (migration + rollup change) — the known "Phase 3c framework-scoped trend". Documented in spec C-06.Testing
go build ./...· vet clean · no openapi/generated/frontend drift.specter check116/116;check --test0 errors;coverage --strictness annotation100% —system-compliance-lens10/10.TestAPI_HostsList_DefaultsToPerHostEffectiveTarget(AC-09 — two hosts with different targets each reflect their own; explicit override),TestAPI_FleetScore_DefaultsToOrgLens(AC-10 — org-default applied, explicit override).TestAPI_Fleet*,TestAPI_Hosts*,TestAPI_Compliance*,TestFleetHandlers_NoSQL_NoPoolAccess(AC-13) all green; AC-17 (empty == no-param) holds since both default identically.🤖 Generated with Claude Code