<#986 Interactive per-check risk score in the node...
# osctrl
g
#986 Interactive per-check risk score in the node posture tab Pull request opened by javuto Interactive per-check risk score in the node posture tab Each control in a node's posture tab now has a checkbox. Unchecking one recomputes the risk score, level, and pass/warn/fail counts live in the browser — no request round-trip — so an operator can answer "what's my score if I ignore this check?" on the spot. How it works The heavy lifting (parsing raw osquery rows, deciding pass/warn/fail per control) still happens once, server-side, in
pkg/posture
. The frontend does not reimplement that evaluation logic — it only re-sums numbers the server already computed: • Added
max_score
to
ControlResult
(
pkg/posture/scoring.go
): the risk points a control would contribute if it failed, now reported even when it passes. This was the only piece missing —
status
,
score
, and
severity
were already in the API response. • New `frontend/src/features/nodes/postureScore.ts`: a small pure function mirroring only the aggregation math from
ScoreCalculator.Score
(earned/possible normalization) and `riskLevel`'s critical/high escalation rule — applied to whichever controls are currently checked. • `NodeDetailPage.tsx`: each control row is a checkbox, checked by default. The gauge, risk badge, and pass/warn/fail counts always reflect the checked subset; with everything checked this is byte-for-byte the server's
PostureScore
. Why this is safe from drift The per-rule
Evaluate()
functions (dozens of them, one per compliance control) never leave the Go backend. If a rule's logic ever changes, the frontend doesn't need to change — it only consumes each control's already-decided `status`/`score`/`max_score`. Files •
pkg/posture/scoring.go
—
ControlResult.MaxScore
•
frontend/src/features/nodes/postureScore.ts
(new) —
recomputePostureScore
,
riskLevelFromScore
,
controlKey
•
frontend/src/features/nodes/NodeDetailPage.tsx
— checkbox UI, live recompute wiring in
PostureScorePanel
•
frontend/src/api/types.ts
—
ControlResult.max_score
•
osctrl-api.yaml
— regenerated (
make openapi
) Testing • `pkg/posture/scoring_test.go`: asserts
MaxScore
sums to the same normalization denominator the score itself was computed from. •
frontend/src/features/nodes/postureScore.test.ts
(new): threshold table, critical/high escalation, empty-selection (no divide-by-zero), and that
recomputePostureScore
reproduces the server score when nothing is excluded. • `NodeDetailPage.test.tsx`: new interaction test — unchecking a failing critical control drops the score/level live, re-checking restores it, and
getNodePostureScore
is called exactly once (proving no extra network round-trip). • Full suite green: `go build`/`go test`/`go vet`,
make openapi-check
, frontend 255 tests,
tsc
. jmpsec/osctrl