main gained six PRs (#1-#6) while the audit fixes and the state-map seam were in progress. Four of main's commits fixed the same bugs this branch did, independently; where they overlap, the more complete version wins: - Fingerprints: main's (a0844bb) - catchSite (`member#index` among all catches), union/owner as identity, a legacy fingerprint so version-1 baselines still match, baseline "version": 2. This branch's `catch` ordinal is dropped; its fingerprint_test still guards the result. - verify --rule: main's verifyAnalysis (cf9e0b7), which also reports findings in skipped files as NOT SCANNED. - Unreadable SDK: both kept - this branch's up-front probe gives the early, specific error; main's catch around the analyzer (d6b6bcb) is the backstop. - Riverpod: main's "a ref.listen is not a render" (f6411dc) and this branch's AsyncError patterns / stored-value tracking compose; the fixture keeps both sets of cases. Ported onto the model-only rules (the seam), since main edited code this branch had moved into front-end facts: - excerpt.dart's literal-free, bounded quoting (a prompt-injection guard) now also covers front-end facts: SwallowedFailure.handler and NonSubscribingRead.excerpt, and the C++ front end elides string literals in handler evidence the same way. - ScanCoverage: runChecks stays source-free; the model carries coverage (`scanCoverage` in the state JSON, written by both front ends), so `check --model` states coverage like a source check. - main's softened wording (rendered-not-emitted "probably unreachable", the remove-policy dead-handler text, notifier-field-unread) and the backticked flag message. Docs: main's README grew the Firestore query inventory and the output contract; the README stays short, the inventory moves to docs/queries.md, and the contract, exit codes, verify and baseline details join docs/usage.md. Checked against origin/main on resolved mirrors of the dogfood apps: nuwayze (102), dex (0) and word_alive (0) identical; tick differs only by the intended riverpod stored-value fix. On the fixture every difference is one of this branch's fixes. 214 tests pass.
4.7 KiB
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<Base> 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 <repo> 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
- Rule contracts.
test/rule_contract_test.dartrequires each rule to fire on a positive control and stay silent on an adjacent negative control. A rule without both fails the build. - Labelled real bugs.
pipe recallevaluatesrecall/manifest.json. Anopencase must be found; afixedcase 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).