Signals overview
Izri computes four independent signals and aggregates them into a single izri/quality verdict. This page is the aggregation model in full: if you want to predict whether a given finding will block your merge, everything you need is here.
The four children
| Check | Question |
|---|---|
izri/scope |
Does the diff match its stated intent? |
izri/tests |
Did the relevant tests run and pass? |
izri/hallucination |
Did those tests exercise the changed lines? |
izri/visual |
Did the rendered UI change? |
Each publishes as its own GitHub check alongside the izri/quality umbrella.
Two ways to fail
The umbrella aggregates the children through two independent mechanisms. Understanding which one fired is the whole diagnostic.
1. Hard rules — worst-of
One hard finding fails the umbrella regardless of every other signal. There is no averaging, no override, no threshold to clear. The list is deliberately short:
| Hard rule | Source | Why it's absolute |
|---|---|---|
pr_type_forbidden_category |
scope | The diff changed a category the PR's declared type forbids. |
change_uncovered |
hallucination | No test exercised the diff at all. The silent-regression case. |
| analyzer error | any | An analyzer crashed. Signal-agnostic — triggered by the row's error status, not a finding category. |
An analyzer crash being a hard rule is deliberate. A crashed analyzer produces no findings, and "no findings" must never be mistaken for "nothing wrong."
2. Soft signals — weighted composite
Everything else feeds a weighted average that must reach 70.
| Signal | Weight |
|---|---|
| Hallucination coverage | 0.40 |
| Test pass rate | 0.36 |
| Scope alignment | 0.24 |
| Visual diff | 0.15 |
Hallucination carries the most weight on purpose: a partially covered diff should drag the composite down even when scope and tests look clean.
Absent signals redistribute, they don't penalize. Weights are proportionally renormalized across the signals that actually ran. A backend PR that skips izri/visual scores exactly as it would have if the visual signal didn't exist. Skipped is not zero.
Severities
Every finding carries info, warn, or error. Only error blocks by default.
| Severity | Effect |
|---|---|
error |
Blocks. Hard rules are emitted at this level. |
warn |
Contributes to the composite. Does not block on its own. |
info |
Advisory. A "look here" pointer with no gating effect. |
This is why sensitive_path doesn't fail your build — it is emitted at info. It flags that a diff touched something worth a human glance, which is not the same as saying the diff is wrong.
Full findings reference
Every category the analyzers emit, and what each one does:
Scope
| Category | Severity | Meaning |
|---|---|---|
pr_type_forbidden_category |
error |
Change in a category the declared PR type forbids. Hard rule. |
pr_type_mismatch |
warn |
The diff doesn't look like what the PR type claims. |
test_removal |
warn |
Tests were deleted. |
scale_anomaly |
warn / info |
The diff is much larger or smaller than the stated intent implies. |
structural_drift |
info |
Module boundaries shifted. |
sensitive_path |
info |
A path your config marks sensitive was touched. |
Hallucination
| Category | Severity | Meaning |
|---|---|---|
change_uncovered |
error |
No test exercised the changed lines. Hard rule. |
change_partially_covered |
warn |
Some changed lines were exercised, some weren't. |
low_overall_coverage |
warn |
Project coverage is low enough to weaken the signal. |
file_not_instrumented |
info |
The coverage tool never saw the file. Could be a filter, could be a gap. |
The three umbrella states
| State | Meaning |
|---|---|
passing |
No hard rule fired, composite ≥ 70. |
failing |
A hard rule fired, or composite < 70. |
unknown |
Not enough signal — delta in flight, or every child skipped. |
Treat unknown as "wait", never as "go".
Related
- Your first verdict — reading this on a real PR.
- Verdicts and exit codes — the CI mapping.
.izri/scope.yml— tuning the scope signal.
Reading this with an agent? /docs/signals/overview.md serves the raw markdown.
Edit this page on GitHub →All docs