src/severities.js
Rama main · réplica de lectura del repositorio original.
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* The single source of truth for severity semantics: the fixed set of levels,
* their display labels, the numeric weights used for dedupe tie-breaking, and
* the health-score penalties. Every module that must reason about severity
* (dedupe, scoring, counts, rendering) imports from here instead of hardcoding
* its own copy.
*/
export const SEV_ORDER = ["high", "medium", "info"];
export const SEV_LABEL = {
high: "🔴 HIGH",
medium: "🟡 MEDIUM",
info: "⚪ INFO",
};
/** Numeric weight per severity — higher wins on a dedupe conflict. */
export const SEV_WEIGHT = { high: 3, medium: 2, info: 1 };
/** Health-score penalty per finding of this severity (info is free). */
export const SEV_PENALTY = { high: 15, medium: 8, info: 0 };
/**
* Escalation on top of SEV_PENALTY: problems compound, and the score should
* say so. The n-th finding of a tier (n counted within that tier) costs
* SEV_PENALTY + SEV_ESCALATION × max(0, n − (SEV_ESCALATE_FROM − 1)).
* high: each additional critical beyond the first costs +5 more
* (15, 20, 25, 30 …) — four failed units must not land anywhere
* near a pile of annoyances.
* medium: the first three stay at the flat 8; from the fourth on each one
* costs +1 more (a growing pile of "minor" issues is itself a
* problem).
* Without this, 4 high (40) and 7 medium (44) scored almost identically,
* which read as "critical ≈ cosmetic".
*/
export const SEV_ESCALATION = { high: 5, medium: 1, info: 0 };
/** The finding ordinal within its tier where SEV_ESCALATION kicks in. */
export const SEV_ESCALATE_FROM = { high: 2, medium: 4, info: Number.POSITIVE_INFINITY };
/** [{ severity, count }] for every severity, in canonical order. */
export function countBySeverity(findings) {
return SEV_ORDER.map((s) => ({ severity: s, count: findings.filter((f) => f.severity === s).length }));
}
/**
* Urgency, which is not severity. Severity says how bad a finding is; urgency
* says whether a human has to act on it now. A degraded array that has run out
* of redundancy and a full disk are "now"; a single corrected ECC error and a
* pile of pending updates are not, however alarming their wording looks.
* Grading everything by "how far from normal" is how a report teaches people
* to stop reading it.
*
* Membership is an explicit list on purpose. Deriving it from
* `severity === "high"` would let every future high finding claim a deadline
* nobody reviewed it for, and would let an unknown (plugin) code into the
* callout. A code joins this list when its grading is reviewed, the same way
* the rest of the rubric was. A deadline is not urgency: an expiring
* certificate or a disk filling up have a date, not a slope, so they are a
* watch item with a date rather than a "drop what you are doing".
*/
const NOW_CODES = new Set([
"disk/full", // nothing can be written; services start failing
"inodes/full", // same failure mode, different counter
"raid/degraded", // one more disk and the data is gone
"smart/failing", // the drive is saying it is about to die
"fs/io-errors", // the filesystem is losing writes
"fs/btrfs-errors",
"fs/readonly-remount",
"hardware/mce", // uncorrected hardware fault
"hardware/ecc", // only at high: an uncorrected error is data loss
]);
/** "now" | "watch" | "fyi" for one finding. Unknown codes are never "now". */
export function urgencyOf(finding) {
if (!finding || finding.severity === "info") return "fyi";
if (NOW_CODES.has(finding.code) && finding.severity === "high") return "now";
return "watch";
}
/** Every finding that needs a human now, in report order. */
export function needsYouNow(findings) {
return (findings || []).filter((f) => urgencyOf(f) === "now");
}