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.
83 lines
4.7 KiB
Markdown
83 lines
4.7 KiB
Markdown
# 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
|
|
|
|
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`).
|