/** * Category scoring for `vigiles audit` — the Lighthouse rings. * * A single structural-health number (the leaderboard's `scoreReport`) ranks * plugins, but it hides WHERE a harness is weak. This buckets the SAME * deterministic findings into six categories — Truthfulness, Triggering, * Structure, Safety, Tested, Evaluated — each a 0–100 ring, as a DIAGNOSTIC breakdown * beneath one headline `overall` = `100 − Σ(all graded penalties)` (the SAME * summed model as the leaderboard's single health number, via the shared * `computeIntegrityScore` — so the two surfaces never disagree). Same detectors, * no re-detection (one-detector-no-drift); all deterministic, no execution. * * SAFETY is fed by the STATIC lethal-trifecta capability check * (`lethalTrifectaIssues` → `report.trifectaFindings`): a unit holding all three * capability legs is a prompt-injection exfil path detectable from the tool-SET * alone — nothing executes, so it sidesteps the confinement blocker. A `"hard"` * (explicit all-three) finding AND an `"advisory"` (inherits-all) one are BOTH * graded — an inherited contract holds the three legs implicitly and every other * capability besides, so grading only the explicit case made hardening LOWER the * score. The cost is capped against the SHARE of the surface exposed * (`W_TRIFECTA=10` per unit, `W_TRIFECTA_MAX=30` total — a DING, not a fail: it * dents the grade without a catastrophic F for a pattern official plugins ship by * design). NB the EXECUTING * "do your hooks actually block?" disaster-battery is STILL not an `audit` ring: * running arbitrary hooks safely needs cross-platform confinement that isn't * shipped yet, so the battery lives in the `vigiles` testing API via * `guardrail-check`/`assertBlocksDisasters`, where you opt in explicitly. * * TESTED vs EVALUATED are two rings, not one, because a harness and an eval differ * on COST (free vs paid model calls), on CADENCE (every push vs scheduled) and on * the QUESTION ANSWERED (does this gate still catch what it claims? vs does this * skill fire at all?). Folded together, a repo with complete deterministic coverage * and no evals scored identically to a repo with neither. * * A category that can't be assessed scores `null` (n/a) and is EXCLUDED from the * overall — never a false 0. `Evaluated` adds a THIRD state on top of that * (`notMeasured`): the run never asked the firing question, which is not the same * fact as "asked and got zero". Pure over the `ScanReport`, so it's fully testable. */ import { type PluginScore } from "./score-core.js"; import type { ScanReport } from "./scan.js"; import type { GradedView } from "./core/apply-rules.js"; export type CategoryKey = "Truthfulness" | "Triggering" | "Structure" | "Safety" | "Tested" | "Evaluated"; export interface CategoryScore { readonly key: CategoryKey; /** 0–100, or `null` when the category isn't assessable (n/a — excluded from overall). */ readonly score: number | null; /** Relative weight in the overall (equal by default — tune later). */ readonly weight: number; /** * `score: null` because this run never ASKED the question — as distinct from * `null` because there was nothing to ask about (plain n/a) and from a measured * `0`. Reporting the absence of a check as the result of a check is exactly what * this tool flags in other people's harnesses; the `Evaluated` ring would do it * if a headless read scored 0 for "no firing measured". A `notMeasured` ring * carries a finding NAMING the command that would measure it. */ readonly notMeasured?: boolean; /** * Advisory categories are shown but EXCLUDED from the overall grade. An untested * surface (or any best-practice gap) is a HARDENING signal, not a broken harness * — it must never drag the grade down, so `audit` doesn't read as F on a clean * repo that simply hasn't written tests yet. The grade reflects what's BROKEN. */ readonly advisory?: boolean; /** Human-readable deductions / notes, worst first; empty when clean. */ readonly findings: readonly string[]; } export interface AuditScore { /** * The headline score — `100 − Σ(all graded penalties)`, clamped to [0,100] * (the SAME summed model as the leaderboard's single health number, computed by * the shared {@link computeIntegrityScore}, so the two surfaces never disagree). * The per-category rings below are a DIAGNOSTIC breakdown, not the headline: a * plugin whose only issue is Structure −30 shows Structure 70 in the breakdown * AND overall 70 (averaging the rings would dilute that to ~90). 0 when empty. */ readonly overall: number; readonly grade: PluginScore["grade"]; readonly categories: readonly CategoryScore[]; /** No loadable surface at all — overall 0, every category n/a. */ readonly empty: boolean; /** * An instruction file was read and NOT ONE surface was — the state #240 was * filed about. It is not `empty` (there is something to grade: refs, layout), * so it carries a real score; but a score over zero skills, zero agents and * zero commands must not be printed as if the machine had been inspected. */ readonly instructionsOnly: boolean; } export interface AuditScoreOptions { /** * Did THIS run actually execute the skill-firing tier? A plain `audit` is a * deterministic read: headless (`--json`, `--no-interactive`, a pipe) it never * runs the executing checks and prints "Executing checks (safety battery · live * MCP · skill firing) skipped". That skip is a real signal and it is threaded * here, so the `Evaluated` ring can say "not measured" instead of scoring a 0 it * did not earn. Default false — the honest default for a pure read. */ readonly firingMeasured?: boolean; } /** * Bucket a scan report into the six Lighthouse categories as a DIAGNOSTIC * breakdown, with the headline `overall` = `100 − Σ(all graded penalties)` (the * shared summed model — NOT the average of the rings — so it equals the * leaderboard's single health number). The advisory Tested / Evaluated rings and * any n/a ring are shown but excluded from the headline — the grade weighting is * UNCHANGED by the Tested/Evaluated split. */ export declare function auditScore( /** The graded view: an ungraded group's findings never reach a ring. */ report: GradedView, opts?: AuditScoreOptions): AuditScore; /** * The ring's number as the reader sees it — THREE distinct labels, never two. * A measured `0` and an unasked question are different facts and must not render * the same; `n/a` (nothing to assess) is a third, also distinct. */ export declare function categoryScoreLabel(c: CategoryScore): string; /** Render the category rings (diagnostic) + the summed overall for the terminal. */ export declare function formatAuditScore(s: AuditScore): string; //# sourceMappingURL=audit-score.d.ts.map