Skip to content

About blast-radius ranking

When a scan turns up a hundred findings, the order they arrive in is not a cosmetic detail — it is the scanner’s opinion about what to fix first. b8e orders findings primarily by severity, and then, among findings of equal severity, by blast radius: how many places in the codebase transitively render the component the defect lives in. A missing label on a shared <Button> rendered by forty screens outranks the same missing label on a one-off page nobody reuses. This document is about why that second axis exists, what it deliberately does not do, and the tension it is trying to resolve.

Severity answers “how bad”; reach answers “how much”

Section titled “Severity answers “how bad”; reach answers “how much””

Severity is a property of a single defect: a critical contrast failure is worse than a minor one, independent of where it sits. It is the right primary sort, and blast-radius ranking never touches it. But severity alone flattens a real distinction. Two findings can be equally severe and still be worth very different amounts of your attention, because one sits on a leaf component and the other sits on something the whole app is built out of. Fixing the widely-rendered one clears the defect everywhere it is instantiated in a single edit; fixing the leaf clears it once.

Blast radius is the name for that “how much” dimension. Concretely it is the transitive render fan-in of the file a finding lives in: the count of distinct files that render that component, directly or through a chain of other components. A shared primitive at the bottom of the render tree has a large fan-in; a route rendered by nothing else has a fan-in of zero. Ranking on it surfaces the defects whose fixes pay off the most, without pretending they are more severe than they are.

Severity stays strictly primary — and why that matters

Section titled “Severity stays strictly primary — and why that matters”

The single most important thing about the design is a line it refuses to cross: reach never overrides severity. A serious defect on a component rendered by ninety files still sorts below a critical defect on a component rendered by nobody. Reach is only ever a tiebreak within a severity band, never a way to promote a less-severe finding above a more-severe one.

This restraint is deliberate, and it is worth sitting with the alternative to see why. One could imagine a single blended score — some product of severity and reach — that lets a very widely-rendered “serious” finding leapfrog a “critical” one-off. That design is seductive and wrong. It would mean a user can no longer trust that the top of the list is the most severe thing in their codebase; the ordering would encode a value judgment (“breadth can outweigh depth”) that the tool has no business making on the user’s behalf. Keeping severity strictly primary keeps the list legible: the reader always knows the first axis is “how bad,” and reach only breaks ties that severity left even.

Why fan-in is a tiebreak, not a headline metric

Section titled “Why fan-in is a tiebreak, not a headline metric”

Blast radius is intentionally a quiet signal. It reorders within a band and it shows up as context on a finding; it does not become a score the product reports or a number teams are asked to drive down. That reticence is on purpose. Render fan-in is a genuinely useful prioritization heuristic, but it is a poor goal — optimizing for “reduce total blast radius” would reward architectural churn that has nothing to do with accessibility. It earns its place by helping you pick the next fix, and it stays in that lane.

There is also an honesty constraint that shapes how far the signal is allowed to reach. Fan-in is only knowable when the scanner can resolve which components render which — the same cross-file resolution described in About component-to-host resolution. That resolution depends on the render graph being buildable, which needs a resolvable project.

The no-graph invariant: an additive signal that costs nothing when absent

Section titled “The no-graph invariant: an additive signal that costs nothing when absent”

This is where the design shows its discipline. When the code graph is unavailable — a cold scan, a shallow clone, no reachable tsconfig — there is no fan-in to compute, so every finding’s reach collapses to the same value and the ordering falls back to severity-only, byte-for-byte identical to a scanner with no blast-radius feature at all. Reach is a purely additive tiebreak: it can only refine the order when a graph exists, and it can never perturb it when one doesn’t.

That invariant is what makes the feature safe to ship. A user whose environment can’t build a graph loses nothing — they get exactly the severity-ordered list they would have gotten anyway. A user whose environment can build one gets the same list, refined so that equal-severity findings on high-reach components float to the top of their band. There is no configuration to get wrong and no regression to fear, because the absence of the signal is indistinguishable from the feature not existing. The b8e scan output surfaces the reach context when it is available; see the scan reference and output formats for how it appears.

Blast-radius ranking is a small, careful answer to a real question — of two equally-bad findings, which should I fix first? — that stays scrupulously inside its bounds. It never argues that a widely-rendered defect is more severe than it is, never becomes a metric to game, and never changes the answer for someone whose environment can’t feed it a graph. It is the kind of feature that is valuable precisely because of everything it declines to do.