Coverage — what we looked at

Every Whisper verdict answers two questions: how bad, and what we actually looked at. Read coverage before band.

Coverage — what we looked at Documentation

Every Whisper verdict answers two independent questions. band tells you how bad. coverage tells you what we actually looked at. Read both. They are a grid, not a ladder.

Only known-clean licenses the word "clean". Every other value is not-clean — and no-data and deadline-hit mean unknown, which is a different thing again.

whisper.assess and whisper.assessUrl return coverage. whisper.explain does not — see the procedure contract below before you rely on either.

The four values whisper.assess returns

Measured on production whisper-ng/4.220.5, 2026-08-09.

coverageWhat it meansWhat to do
known-cleanWe hold data at this granularity and nothing malicious is in it.Treat as clean. This is the only value that licenses closing a ticket on "clean."
malicious-evidencedSome positive evidence of malice exists. It may be a single feed at weight 0.5. It does not mean the band is high.Read evidence[] for feed-source-count, then run explain() for the per-feed provenance, weights and timestamps. A count of 1 on a low-weight aggregate list is a lead, not a finding.
ambiguousThe evidence points both ways — for example an anonymising-egress signal alongside generic abuse listings.Escalate to a human. Do not automate a decision on this value.
no-dataWe have never observed this host.Unknown. Never benign. Follow the no-data playbook — do not simply escalate.

Every one of these arrives as a populated row. no-data is a row that says no-data; it is never an empty result set. If a query returns zero rows, the first hypothesis is that the query is wrong, not that the host is clean.

The case that proves the rule

CALL whisper.assess(["140.82.121.3"]) YIELD host, band, coverage, evidence
RETURN host, band, coverage, evidence
{"host":"140.82.121.3","band":"LOW","coverage":"malicious-evidenced",
 "evidence":["coverage:malicious-evidenced","band:LOW","host-class:unknown",
             "feed-source:listed","feed-source-count:1"]}

band: LOW beside positive evidence of malice. A pipeline that gates on band alone treats this row as unremarkable. The band is low because the verdict reconciler de-escalates for a multi-tenant apex, for evidence older than 90 days, and for a zero base score — none of which change what we looked at.

That is why the rule is key on coverage, never on band.

no-data is common — here is what to do with it

no-data is not exotic. Measured 2026-08-09: CALL whisper.assess(["104.16.132.229","104.16.123.96"]) — two ordinary Cloudflare edge addresses — returns band: UNKNOWN, coverage: no-data for both. Hostnames that appear in DNS generally carry real coverage; a bare IPv4 address often does not.

So "escalate everything that says no-data" is not a workable rule at volume, and we are not going to pretend it is. When you get no-data, ask a different question rather than a louder one.

  1. Ask about the container. CALL explain("<enclosing /24>") and the announcing ASN. A host we have never seen inside a heavily-listed block is a different fact from the same host inside a clean one. (Read the caveat in the procedure contract first: on a CIDR or ASN indicator the score column reads 0.0 and the real value is inside explanation and factors[].)
  2. Ask who runs it. CALL whisper.identify(["<host>"]). A no-data verdict on infrastructure that identifies as a known SaaS vendor is expected. On infrastructure that identifies as nothing, the no-data is the finding.
  3. Ask when it appeared. CALL whisper.history.whois("<domain>"). A domain registered this week has no feed history by construction — that is the signal, not the absence of one.
  4. Only then escalate — and escalate with the sentence "we have no observation of this host", never with "it came back clean."

The other three declared values

CoveragePresence.java declares seven wire values. whisper.assess emitted four of them on 2026-08-09. The other three are real, and they are not assess values.

ValueObserved on production 2026-08-09Treat it as
deadline-hitYes — from whisper.walk. CALL whisper.walk("google.com", 2, 1) returns coverage: "deadline-hit" with arms: {arms_completed: 4, arms_truncated: 2, deadline_hit: true}Not a verdict. The time budget expired before coverage could be established. Retry with a larger budget. Never read it as clean and never read it as bad
partialNot observed. No probe run for this page produced it. Its status on production is UNVERIFIEDNot-clean. We covered only part of what you asked about — narrow the scope and re-ask
structural-onlyYes — from whisper.walk only. See belowNot a verdict at all. Do not gate on it

Treat any value other than known-clean as not-clean, and no-data, deadline-hit and partial as unknown, which is a different thing again.

The word UNVERIFIED above is deliberate. We did not observe partial; we are not asserting it is unreachable. If a later measurement produces it, this table gains a row.


structural-only is a whisper.walk value. It is not an assess value.

Do not gate on structural-only. If your code has a branch for it on an assess result, that branch is unreachable — and the branch you are missing is malicious-evidenced.

The seven values split across two axes, and the split is enforced in the engine:

AxisValuesWhat a value asserts
Verdict axisknown-clean · malicious-evidenced · ambiguousA claim about malice
Presence axisstructural-only · partial · no-data · deadline-hitA claim about whether we looked — and nothing about malice

whisper.assess is a verdict procedure: it emits the three verdict-axis values, plus no-data. whisper.walk is not a verdict procedure: it may emit presence-axis values only, so that a tool which is not authorised to render a verdict cannot leak one through this vocabulary.

whisper.walk's coverage therefore describes atlas and vendor adjacency, not threat coverage — whether the host is reachable in the graph's structure. Its full column set makes this explicit (no_atlas_match, nearest_known_vendors, siblings, arms).

The same indicator can carry two different coverage values from two different procedures, and for one indicator the two flatly disagree. Measured 2026-08-09:

CALL whisper.assess(["185.220.101.1"])  ->  coverage: "ambiguous"       (there is evidence)
CALL whisper.walk("185.220.101.1")      ->  coverage: "no-data"         (no atlas match)

Both are correct. They answer different questions. Read the procedure before you read the column.

One more trap in walk: a truncated walk still returns structural-only. Measured 2026-08-09, CALL whisper.walk("github.com", 2, 3) returns coverage: "structural-only" with arms: {arms_completed: 7, arms_truncated: 5, arms_excluded: 1, deadline_hit: false} — five arms dropped and no deadline flag. Read arms before you read coverage on a walk row.

Which procedure carries coverage

All rows measured anonymously against production whisper-ng/4.220.5, 2026-08-09.

ProcedureReturns coverage?What its coverage is about, and the gotcha
whisper.assessYesThreat coverage — the four values above. Its full output contract is host, label, band, sub_labels, signals, coverage, evidence; YIELD anything else and you get HTTP 400 query-error naming the valid columns.
whisper.assessUrlYesA path axis, not a host axis. Its coverage describes what was checked about the URL path, so the standing rule does not transfer unchanged — read this row before gating on it, and prefer whisper.assess for any decision that turns on host coverage.
whisper.walkYes, but not a verdictAtlas and vendor adjacency. Presence-axis values only. Read arms first.
whisper.explainNoReturns indicator, type, available, cached, found, score, level, explanation, factors, sources, advisory. There is no coverage column at all, so a level: NONE from explain() is not a clean verdict — it is a score, and the question of whether we looked is simply unanswered.

Two gotchas on explain() worth stating plainly, both measured 2026-08-09:

  • On a CIDR or an ASN the score column reads 0.0 while the real finding sits in explanation and factors[]. CALL explain("3.64.0.0/12") returns score: 0.0, level: "CRITICAL" and an explanation naming 1,000 listed IPs. A pipeline reading score alone scores a critical network at zero.
  • advisory is usually null and occasionally load-bearing. explain("1.1.1.1") returns advisory: "allowlist-vouched"; most indicators return null. Select it by name if you need it, and do not treat a blank as "no advisory applies" without checking the indicator type.

The rule key on coverage, never on band is published per procedure, never as a blanket rule, because it is only true of the procedures that return a coverage column.