# Rules Every rule states a factual, whole-program claim ("no UI code matches this state"), scoped to the code that was scanned. Evidence is `path:line:` plus a bounded source excerpt with string literals elided. Precision below is **factual** precision: how often the claim holds, not how often it is worth acting on. A rule that cannot be validated is demoted or removed. ## Evidence per rule Measured on the dogfood apps (nuwayze, dex, word_alive, tick). | Rule | Evidence | | --- | --- | | `emitted-not-rendered` | 79/79 claims factual on nuwayze main (adjudicator with passing controls). One finding per unobserved union and per unobserved marker class. Drops to info when the UI reads the union's payload through base-class fields. | | `rendered-not-emitted` | Fixture contract; suppressed with a reason when the owner emits through helper methods. Worded as "probably unreachable": only the scanned code is known. | | `loading-not-reset` | Fixture contract; the dogfood apps guard correctly. | | `loading-flag-not-reset` | 7/7 on dex, hand-adjudicated. | | `event-without-handler` | Fixture contract; 168 dispatch sites on nuwayze all map to a handler. Follows the event hierarchy (`on` covers subtypes) and blocs sharing an event type. | | `dead-handler` | 77/77 on nuwayze (adjudicator). The message names the directories scanned; events built only in tests are not features. | | `riverpod-async-gap` | 10 hand-adjudicated findings (word_alive, tick). Recognises `.when`, `AsyncError`/`AsyncLoading` patterns and values stored in locals. `bool` providers are downgraded to info. | | `notifier-never-notifies` | Fixture contract only; no real-code true positives yet. Limited to observable fields; lifecycle hooks excluded. | | `notifier-inert-consumer` | Fixture contract only. | | `provider-read-in-build` | 1 finding from 447 `context.read` calls on nuwayze. Reads inside closures are excluded by design. | | `notifier-field-unread` | 3 on nuwayze; medium confidence, since a name match elsewhere can mask an unread member. | | `provider-created-in-build` | Info, once per build method: the claim is factual, but impact depends on how often the widget rebuilds. | ## A factual finding is not always a bug `emitted-not-rendered` at high confidence can be deliberate: dormant scaffolding or a retired feature. Those findings are one decision per union, and the remediation asks to check the repo's docs and action contracts before deleting anything. ## How precision is measured `tool/adjudicate.py ` re-derives the two highest-volume claims (`emitted-not-rendered`, `dead-handler`) with its own regex evidence, and prints controls first: it must find evidence for every variant pipe calls rendered and every event pipe calls dispatched. Controls are three-way (found / unattributable / missing), and only *missing* fails. A corpus with no control material reports **NO SIGNAL** and undefined precision. The script exits non-zero whenever it cannot certify. Limits: it is only partly independent (the list of variants, events and owners to check comes from pipe's `state` output), it judges the factual claim only, not severity, and it needs a clean working tree. ## Recall 1. **Rule contracts.** `test/rule_contract_test.dart` requires each rule to fire on a positive control and stay silent on an adjacent negative control. A rule without both fails the build. 2. **Labelled real bugs.** `pipe recall` evaluates `recall/manifest.json`. An `open` case must be found; a `fixed` case must stay quiet, and reappearing is a **REGRESSION**. Repos missing from the machine are *skipped*, never passed. Results reflect each repo's working tree. The labelled set is small: it bounds the risk of false negatives, it does not prove their absence. ## provider and ChangeNotifier Modelled: every provider-package declaration type (`Provider`, `ChangeNotifierProvider`, `ListenableProvider`, `ValueListenableProvider`, `FutureProvider`, `StreamProvider`, `StateProvider`, `ProxyProvider` and variants, `.value`), and `Consumer`, `Selector`, `ListenableBuilder` and `AnimatedBuilder` as consumers. Declarations are told apart from Riverpod by `create:`/`update:`/`value:` arguments, not by import, so an unrelated class named `Provider` taking `create:` would be miscounted. Not modelled: `ProxyProvider` `update` functions returning unchanged values, and rebuild frequency. ## Other limits Not modelled: `bloc_concurrency` transformers, freezed unions whose generated file is absent, and `ref.read` call sites. pipe deliberately does not reimplement per-file checks the ecosystem already does well: `unawaited_futures`, `use_build_context_synchronously`, `cancel_subscriptions`, `close_sinks`, `bloc_lint`, or declaration-level dead code (`undead`, `dallow`).