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 |
Are the tests for this change any good — do they pass, run the changed lines, and check anything? |
izri/hallucination |
Is what the change says about itself true? |
izri/visual |
Did the rendered UI change? |
All four report through one GitHub check run, Izri, whose conclusion is the izri/quality umbrella verdict and whose body lists each signal.
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 |
tests | Executed coverage shows no test ran the changed lines of a file. The silent-regression case. |
test_failure |
tests | The test run failed. One red test fails the verdict, however high the composite. |
runner_error |
tests | The run broke before producing results. No evidence never resolves to "safe". |
evidence_missing |
tests | The project uploads from CI and nothing arrived for this commit within the grace window. |
| 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."
A project can add opt-in hard rules through its merge policy — a minimum diff coverage (block.diff_coverage_below), zero-test runs, sensitive paths, a visual diff limit. They are off by default.
2. Soft signals — weighted composite
Everything else feeds a weighted average that must reach 70.
| Signal | Weight |
|---|---|
| Hallucination score | 40 |
| Tests score — the lower of pass rate and diff coverage | 36 |
| Scope alignment | 24 |
| Visual diff | 15 |
Weights are relative, not fractions: with all four signals present, hallucination is 40 of 115. These are the defaults; a project can change them in its merge policy.
Izri judges the delta a PR introduces, so every number here is about the changed lines. The rest of the project is context, never what's graded — coverage means diff coverage.
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 |
Lowers its signal's score, except advisory test-quality findings, which are shown but not scored. Does not block on its own. |
info |
A "look here" pointer. Never blocks; costs 2 points where findings are scored. |
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.
An LLM can't block. A finding backed only by a model's judgment is capped at warn, whatever severity the model assigned. Only findings with tool evidence — coverage, an import resolver, test results — can reach error.
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. |
Tests
| Category | Severity | Meaning |
|---|---|---|
test_failure |
error |
The test run failed. Hard rule. |
runner_error |
error |
The run broke before producing results. Hard rule. |
evidence_missing |
error |
No upload arrived from CI within the grace window. Hard rule. |
test_evidence_untrusted |
warn |
Green evidence from a PR that edited what produced it. Hard, opt-in via block.untrusted_evidence. |
change_uncovered |
error |
No test exercised the changed lines. Hard rule. |
change_partially_covered |
warn |
Some changed lines were exercised, some weren't. |
low_diff_coverage |
warn |
Under 60% of the added lines were executed. Named low_overall_coverage before September 2026. |
file_not_instrumented |
info |
The coverage tool never saw the file. Could be a filter, could be a gap. |
diff_coverage_below |
error |
Diff coverage is under the project's configured minimum. Hard rule, opt-in via block.diff_coverage_below. Silent when change_uncovered already fired. |
zero_tests |
error |
A passing run executed no tests. Hard rule, opt-in via block.zero_tests. |
change_lacks_test |
warn |
Advisory, not scored. A changed source file has no changed test importing it. |
weak_assertion |
warn |
Advisory, not scored. An LLM judged the assertions too weak to catch a regression. |
assertion_free_test |
warn |
Advisory, not scored. A test block this PR touched contains no assertion. |
tautological_assertion |
warn |
Advisory, not scored. An assertion that cannot fail. |
test_mocks_subject |
warn |
Advisory, not scored. A test mocks a module this PR changed that no test in the PR runs unmocked. |
test_passes_without_change |
warn |
Advisory, not scored. A test this PR wrote passes with the change reverted. |
Hallucination
| Category | Severity | Meaning |
|---|---|---|
import_unresolved |
warn |
An import on an added line matches no file in the repository. |
export_missing |
warn |
A named import a first-party module does not export. |
dependency_undeclared |
warn |
A bare import of a package no package.json declares. |
package_not_found |
warn |
A newly added dependency the npm registry has never seen. |
package_suspicious |
warn |
A newly added dependency under 30 days old with under 1,000 weekly downloads. |
env_undeclared |
info |
An env var read that no template, env config or workflow declares. |
reference_unparsed_import |
info |
An import whose specifier is computed (not a string literal) was not checked. |
reference_file_unparseable |
info |
The file has syntax errors; its import list may be incomplete. |
change_unverified |
warn |
Every selected test still passed with the change reverted: nothing depends on this change. |
verification_evidence_untrusted |
warn |
The PR could have shaped its own test evidence; the revert check is inconclusive. |
verification_skipped_budget |
info |
Too many tests selected for the revert check; nothing ran. |
analyzer_stage_error |
info |
One analysis stage crashed; the others still reported. Appears on the signal that owns the stage. |
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/config.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