Skip to content

Interactive per-check risk score in the node posture tab - #986

Merged
javuto merged 1 commit into
developfrom
dynamic-posture-score
Aug 21, 2026
Merged

Interactive per-check risk score in the node posture tab#986
javuto merged 1 commit into
developfrom
dynamic-posture-score

Conversation

@javuto

@javuto javuto commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

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.goControlResult.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.tsControlResult.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.

@javuto javuto added 🔐 security Security related issues ⭐️ frontend Frontend related issues labels Aug 21, 2026
@javuto
javuto merged commit fe03f08 into develop Aug 21, 2026
8 checks passed
@javuto
javuto deleted the dynamic-posture-score branch August 21, 2026 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⭐️ frontend Frontend related issues 🔐 security Security related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant