The front end hardcoded /opt/homebrew/opt/llvm and lib/libclang.dylib, so
on Linux it died before parsing anything:
FileNotFoundError: [Errno 2] No such file or directory:
'/opt/homebrew/opt/llvm/bin/clang'
PIPE_LLVM could move the prefix but not the file name, so no value of it
made the tool run there.
Discovery is now per platform: Homebrew's LLVM on macOS, the system prefix
elsewhere, PIPE_LLVM still winning over both. The library is looked up as
.dylib or .so, exact name first and then highest version, and under
lib/*-linux-gnu as well, because distributions ship
libclang.so.<major>.<minor> without always adding the unversioned symlink
and Debian keeps it in a multiarch subdirectory. -print-resource-dir falls
back to clang on PATH when the prefix ships only the library. When nothing
matches, the conventional name is handed to clang.cindex so its own error
names the library rather than a path this code invented.
_sdk_root already degrades to None off macOS, since xcrun is absent.
test/cpp_front_test.dart looked for the same Homebrew dylib, so on Linux
it reported "All tests skipped" rather than failing. That is why the gap
went unnoticed: the suite was green because it ran nothing. Its detection
now mirrors the front end's, and on this Linux machine the four cases go
from skipped to run and passing.
Verified end to end against an unrelated 87-translation-unit C++ project:
87/87 parsed, 0 TUs failed, 0 clang errors, 26 state enums and 879
emissions recovered.
Both suites fail test/stdout_json_test.dart under the parallel runner on
this machine, pristine main included, and it passes in isolation on both
branches; a serial run failed a different test instead. Pre-existing and
unrelated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
tool/cpp_front.py is a second front end written against the state model,
not the rules: it reads a compile database with libclang (Homebrew LLVM)
and writes the JSON that `pipe check --model` takes. Every rule,
baseline and gate works on it unchanged.
Mapping, per project `enum class` (identity from clang's resolved AST,
so two enums both named QueryIntent are never confused):
- a reference that produces a value (assignment, atomic store, argument,
return, initialiser) is an emission; a cast from an int is an indirect
one;
- a `case`, `==` or `!=` is an observation;
- a busy enumerator written to the method's OWN state, followed by a
top-level try whose catch neither writes state nor rethrows, is a
swallowed failure;
- sources the build does not compile are recorded as unresolved, so
findings naming them drop a confidence step.
One rule-side change, optional and Dart-neutral: an EmissionSite may name
its stateType. rendered-not-emitted attributed indirect emissions by owner
(a bloc owns one state type), so a cast to one enum made "never emitted"
unprovable for every other enum in the same C++ class. With stateType
set, the suppression is scoped to that enum. Dart output is still byte
identical on the fixture and all four dogfood apps.
Run on ~/work/tools/yams (375 translation units from build/debug, 0 clang
errors, about two minutes): 118 state enums, 82 findings. A grep-based
hand check of 13 found 12 factually correct - e.g. the IPC connection FSM
never enters ImplState::ReadingHeader, the downloader never reports
ProgressStage::Verifying though three CLI commands render it,
ExtractScopeType qualifiers are parsed but never acted on - and one false
positive (a nested JSON-parse fallback taken for a stuck busy state),
now excluded by requiring the method's own state and a top-level try.
Bugs found on the way and fixed: ccache/compiler wrappers passed as
inputs, relative -I paths resolved against the wrong directory (7 TUs
silently lost third_party headers), a symlinked path putting every TU
"outside" the project, and an empty compile database producing an empty
"clean" model with exit 0 - all now fail loudly or are handled.
test/cpp_front_test.dart runs the front end on test/fixtures/cpp_state
with positive and negative controls for each rule; skipped without LLVM.
138 tests pass.
The adjudicator printed "controls FAILED" or "NO SIGNAL" and then exited
0, so a CI step reading the exit status took an uncertified run as a
pass. It now exits 1 unless the controls are meaningful and pass, in both
text and json output.
It was also described as independent. Its evidence is independent (its
own regex over source), but the list of unions, variants, events and
owners it checks comes from pipe's `state` output, so a variant pipe
fails to enumerate is never checked. The docstring and README now say so
instead of "independent adjudicator".
The independent precision adjudicator reported a pipe defect that did not exist.
Three defects in the checker, all found by chasing its own output against the
source:
1. It matched variant names by UNQUALIFIED name, so `is ExportSuccess` in
analytics_tab.dart was credited to ArtistStoreManagementState.ExportSuccess -
the very defect pipe's own per-union scoping had just fixed. Those three files
reference AdminAnalyticsState, ContentModerationState and
HelpCenterManagementState, and none of them mentions
ArtistStoreManagementState, so pipe's finding was correct and the checker's
"contradiction" was false. Evidence about a variant now requires a file that
also names the union or its owner - unless the name is declared by only one
union, in which case it is unambiguous and needs no scoping (event
construction sites carry no type annotation, and demanding one rejected sound
evidence: the dispatched control fell from 162/162 to 143/162).
2. Its notion of "rendered" was type-tests only, missing switch-expression arms
(`ChatListOperationInProgress value =>` has no `case` keyword in Dart 3) and
base-class payload reads (`state.upcomingBookings`, where BookingsLoaded
passes `required super.upcomingBookings`). Both are real ways a variant reaches
the screen; pipe models both.
3. `controls_pass` was `len(control1) == len(rendered) and ...`, which on a
corpus with no control material is `0 == 0` - a vacuous pass. word_alive
printed "controls pass: True" with 0/0 and a pristine-looking 100%. The control
is now three-way (found / unattributable / missing), only `missing` fails, and
an empty corpus exits NO SIGNAL with precision reported as UNDEFINED rather
than perfect.
Also fixed: the scoping pass looked for union names in `all_names`, which holds
variant names only, so `union in file_ids` could never be true and every control
silently read 0.
Result on clean nuwayze main: controls 131/131 rendered, 143/162 dispatched (19
unattributable, 0 missing) - controls pass, and precision is 79/79 + 77/77 with
zero contradictions. No pipe defect was found this round; the adjudicator's one
apparent finding was its own.
84 tests still pass; dart analyze clean.
An independent Gemini 3.8 Flash audit of this repo found real problems. Each was
reproduced before being fixed.
Confirmed defects:
- tool/adjudicate.py crashed with KeyError once R001 started grouping variants
under 'variants' instead of 'variant'. Fixing that exposed a second bug of my
own: it then divided verified VARIANTS by FINDINGS and reported 216% precision.
Counts are now coherent (findings vs variant-level checks) and a clean repo
reports 0/0 rather than a fake 100%.
- GuideCommand called _readPolicy but never registered --policy, so
exited 64 despite the README documenting it.
- loading-flag-not-reset treated a constructor-invoked method as inheriting the
loading flag without checking flag.defaultsTrue, so a default-FALSE flag was
reported as in flight.
- Finding.fingerprint embedded the line number for rules whose only identity was
, so moving code broke baseline entries. is now a
discriminating key; only loading-not-reset, which can emit two findings from one
method, carries an explicit catchLine.
- suggest grouped unrelated provider-read-in-build findings by file; the group key,
label and target list now use valueType/call.
Recall defect found while checking the auditor's own claim: UI variant mentions
were collected project-wide by unqualified name, so ExportSuccess - which appears
in FOUR unions on nuwayze - was cross-credited. Mentions are now scoped per union
to files that also name the state type or its owner, which surfaced
ArtistStoreManagementState[ExportSuccess] as a real finding.
Five negative controls could not fail (an event that is never handled can never be
reported as unhandled; a union with no render sites can never be reported as
rendered-not-emitted). Added fixture cases that make each one bite: HandledTap
(handled AND dispatched), PatternState render sites, GuardedCubit (guarded loading
path), QuietCubit._warmUp (constructor-invoked await on a default-false flag).
Root cause of a recurring mystery: this repo EXCLUDED test/fixtures/** from
analysis, which made any analyzer running in the repo context parse the fixture
without resolving its imports and report phantom undefined-class errors. I had
dismissed these as stale cache several times; they were my own exclusion. The
exclude is gone and the fixture governs itself. A new test asserts the fixture has
zero analyzer errors, not merely that it resolves, so IR distortion cannot hide.
84 tests (was 81).