diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f44b89e..538e5808 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,6 +87,7 @@ jobs: - name: Install clang-format / clang-tidy (PINNED major — see the job comment) run: | + ls /etc/apt/sources.list.d/ && sudo rm -f /etc/apt/sources.list.d/google-chrome* # the runner image ships a Chrome apt source (deb822 .sources on 24.04, not .list); its "Hash Sum mismatch" killed every Linux job on 2026-09-09 wget -qO /tmp/llvm.sh https://apt.llvm.org/llvm.sh chmod +x /tmp/llvm.sh sudo /tmp/llvm.sh "$CLANG_VERSION" all @@ -190,6 +191,7 @@ jobs: - name: Install tooling (Linux) if: runner.os == 'Linux' run: | + ls /etc/apt/sources.list.d/ && sudo rm -f /etc/apt/sources.list.d/google-chrome* # the runner image ships a Chrome apt source (deb822 .sources on 24.04, not .list); its "Hash Sum mismatch" killed every Linux job on 2026-09-09 sudo apt-get update sudo apt-get install -y --no-install-recommends libxml2-utils ripgrep bc clang @@ -268,6 +270,7 @@ jobs: fetch-depth: 0 - name: Install tooling run: | + ls /etc/apt/sources.list.d/ && sudo rm -f /etc/apt/sources.list.d/google-chrome* # the runner image ships a Chrome apt source (deb822 .sources on 24.04, not .list); its "Hash Sum mismatch" killed every Linux job on 2026-09-09 sudo apt-get update sudo apt-get install -y --no-install-recommends libxml2-utils ripgrep bc - name: Configure with the image's stock gcc 13 (plain flavour) @@ -419,6 +422,7 @@ jobs: - name: Install tooling (Linux) if: runner.os == 'Linux' run: | + ls /etc/apt/sources.list.d/ && sudo rm -f /etc/apt/sources.list.d/google-chrome* # the runner image ships a Chrome apt source (deb822 .sources on 24.04, not .list); its "Hash Sum mismatch" killed every Linux job on 2026-09-09 sudo apt-get update sudo apt-get install -y --no-install-recommends libxml2-utils ripgrep bc clang diff --git a/CHANGELOG.md b/CHANGELOG.md index b5aa5e0b..f500c9ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,17 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli ## [Unreleased] +### Fixed — the super-linear warm floor under every graph-building verb (`--grep`, `--callers`, the map) + +On llvm-project (182,555 files, warm cache) a `--grep` for an absent literal took ~157 s, `--callers=main` +~152 s and the default map 248 s, while the same crawl + cache load + model build without the graph took +3.8 s. Profiled to one operation: the resolver rebuilt a receiver type's inheritance cone (two BFS walks +with quadratic dedup) on every still-ambiguous receiver-typed call — 86,667 rebuilds for 2,984 distinct +types, 143 s of the 154 s run. `ChaConeMemo` (`src/graph.h`) computes each cone once with the identical +walk and cap; warm `--grep` is now 9 s, `--callers` 8.6 s, the map 10 s, and default maps are byte-identical +before and after on go and llvm. Gate `test/chaconecheck.sh`; the phase tables are in `bench/PROFILE.md` +and the evidence chain in `docs/EVALS.md` (2026-09-09). + ### Added — a Ruby constant receiver is a dependency (parser version 83) Round two of the Ruby constant work. Parser version 82 gave the declarative spellings — `class X < Base`, diff --git a/README.md b/README.md index e7742d36..548e4fe9 100644 --- a/README.md +++ b/README.md @@ -1130,8 +1130,8 @@ grow — nothing can keep a line number true in a document, so it is not claimed $ ripwire . --callers=rankGraphTeleport - - + + @@ -1800,9 +1800,9 @@ wrong, and it has. These are the results that say so, all in-tree, all published ### In the tests
-566 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +567 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures -`test/regression.sh` names **566 gate scripts** and is the authoritative list; +`test/regression.sh` names **567 gate scripts** and is the authoritative list; `python3 test/pargates.py . ./build/ripwire -j 6` runs the same set in parallel. On top of them sit the contracts that do not fit a unit test: two runs byte-identical, warm output identical to cold, output that pipes clean through `xmllint --noout`, a sanitizer build with `-fno-sanitize-recover=all`, and a diff --git a/bench/PROFILE.md b/bench/PROFILE.md index 12933c46..d4268b87 100644 --- a/bench/PROFILE.md +++ b/bench/PROFILE.md @@ -1077,3 +1077,99 @@ done Name the subtree `ext/`, not `vendor/` — the crawl's taxonomy filter skips a directory called `vendor` outright, so a corpus built under that name silently measures 1,000 files in both arms. + +## 2026-09-09 — the super-linear warm `--grep` floor: one stage, one operation, 143 s of 154 s on llvm-project + +LEDGER row, never a gate (the no-perf-budget rule). The correctness gate this round landed is +`test/chaconecheck.sh`; it asserts sets, never seconds. The question came from the tgrep head-to-head +(docs/EVALS.md, "Head-to-head vs tgrep"): warm `--grep` cost 40 µs/file at 2,240 and 15,865 files and +938 µs/file at 182,555, and the decisive experiment named there was "stub the graph build, re-time". + +### Instrument and argv + +One binary, `cmake -S . -B build_prof -DRIPWIRE_PROFILE=ON` (plain flags otherwise — never Release, `NDEBUG` +compiles `DEGRADED_PATH_ALERT` and the profiler out). This round first added the scopes the ingest path +lacked at the grep path's granularity: `buildGraph/3..8` (the resolve loop and the five passes after it), +the parse pool's worker join and per-thread merge, and the two corpus-wide model post-passes. The +per-reference split inside the loop (six span timers + volume counters) was a scratch patch, not landed. + +Corpora by `rg --files`: `golang/go` `49c3ea64` 15,865 files; `llvm/llvm-project` `2061c237` (shallow) +182,555 files, 2.9 GB — the same checkouts the head-to-head used. Warm = a dedicated `TMPDIR` per corpus, +primed once (llvm cold prime: 231 s wall, 367 s user on 18 cores, peak RSS 6.49 GB). Arms interleaved +`grep, help, callers` × 2 reps; `/usr/bin/time -l` for wall + RSS. 18-core Apple Silicon, 48 GB, shared: +the 1-minute load ranged 2.3–26 across the session, so read the ratios, not the third digit. + +``` +TMPDIR= build_prof/ripwire --grep=zzqxvnotpresentzz # the absent literal (full scan) +TMPDIR= build_prof/ripwire --callers=main # same graph, NO text scan +TMPDIR= build_prof/ripwire --help-task=zzqxvnotpresentzz # crawl + cache + model, NO graph +``` + +`--help-task` returns before `buildGraph` and uses the same lean cache blob as `--grep`, so it IS the +"graph stubbed out" arm the head-to-head asked for, with no code change. + +### Result — the decisive experiment, then the profile, then the operation + +| llvm-project, warm, wall | pre-fix (2 reps) | **post-fix (2 reps)** | +| --- | --- | --- | +| `--grep=` | 159.7 s, 153.9 s | **9.2 s, 9.0 s** | +| `--callers=main` | 152.9 s, 151.8 s | **8.6 s, 8.7 s** | +| `--help-task` (no graph) | 3.8 s, 3.4 s | 3.7 s, 3.4 s | +| default map, `--top-k=100000` | 248 s | **10 s** | +| peak RSS, any arm | 5.9–6.2 GB | 5.8–5.9 GB | + +**The floor is the graph, and only the graph.** The arm that runs the identical crawl, cache load, +validation and model build but never builds the graph took 3.8 s at the same 5.9 GB RSS. The cache load +plus per-file validation is 16 µs/file on llvm against 28 µs/file on go — linear, if anything sub-linear. +The memory-cliff hypothesis is refuted by the same row: RSS is the ingest's reference tables, present with +and without the graph, and wall did not move with it. + +Profile scopes, llvm warm `--grep`, pre-fix (rep 1, 159.7 s wall): `buildGraph` 154.6 s, of which the +per-reference resolve loop 153.2 s; `ingest: total` 4.7 s (crawl 1.8 incl. the git ignore probe 1.3, +model 1.2, parse pool 1.2, loadCache 0.4); `grep/1 grepCollect scan` 1.07 s on its own thread. The 2.9 GB +text scan is 1 s; the answer waited 153 s for the graph. + +Inside the loop, `--callers=main` warm, six spans over the SAME 4,546,850 references: + +| span (per reference) | calls | total | mean | +| --- | ---: | ---: | ---: | +| a: role filter + byName + SCIP/binding tiers | 5,162,745 | 0.11 s | 0.02 µs | +| b: canonical + L3 + ES import + rules 1/2/2b/2c/3 | 4,546,850 | 1.09 s | 0.24 µs | +| c: external veto + candidate spray + namespace gate | 4,517,099 | 2.18 s | 0.48 µs | +| d: tier ladder (same file / same dir / unique) | 4,137,640 | 1.07 s | 0.26 µs | +| **e: CHA-lite cone + arity + locality** | 2,213,632 | **145.1 s** | **65.5 µs** (max 41.7 ms) | +| f: amb + confidence + edge emission | 2,213,632 | 0.31 s | 0.14 µs | + +The obvious suspect was innocent: the five linear passes over the same-name candidate list visited +1,226,680,236 candidates (`test` 502 M of them, 7,405 defs; `S` 291 M; `foo` 76 M) and cost 3.3 s in +total — ~3 ns a visit, sequential ids, the prefetcher's happy case. Splitting span e once more: arity +0.015 s, the S6-C locality tie-break 0.27 s, **the CHA-lite cone ≈ 143 s.** + +**The operation.** For every still-ambiguous call with a known receiver static type, the loop rebuilt the +type's inheritance cone — two BFS walks over the class-NAME graph, `std::vector` with an +O(n²) `std::find` dedup, capped at 4,096 per walk — and tested each tier candidate by another linear +`std::find`. Counters: **86,667 cones for 2,984 distinct receiver types** (each rebuilt ~29×), mean cone +1,075 names (Σ 93,185,627), 1.65 ms a cone. On go the same span is 1.1 ms total — zero cones, because the +model has no class-inheritance edges there — which is why the floor looked flat until the corpus had deep +hierarchies. That is the super-linearity: Σ over calls of (cone size)², where both factors grow with the +tree. + +**The fix** (`src/graph.h`, `ChaConeMemo`): one cone per receiver type, computed on first use over class +names interned to dense ids in byte-sorted order, the walk verbatim (same seed, discovery order and the +outer-loop-only cap), membership by binary search. Post-fix the loop is 4.5 s; span c's 2.2 s of +candidate spray is now the largest remaining item and is a different, linear-per-candidate fix. Default +maps are **byte-identical** pre/post on both corpora (go 10,415,057 B; llvm 21,802,319 B) and the six +resolver gates plus 18 more pass unchanged. Per file, the warm `--grep` floor is now 50 µs at 182,555 +files against 33 µs at 15,865 — the 24× per-file regression is 1.5×. + +### Reproduce + +``` +cmake -S . -B build_prof -DRIPWIRE_PROFILE=ON && cmake --build build_prof -j +git clone --depth 1 https://github.com/llvm/llvm-project /llvm-project # ~2.9 GB, ~182,555 files +export TMPDIR=/tmp-llvm; mkdir -p "$TMPDIR" +build_prof/ripwire /llvm-project --grep=zzqxvnotpresentzz >/dev/null 2>prime.err # cold prime +for rep in 1 2; do for arm in --grep=zzqxvnotpresentzz --help-task=zzq --callers=main; do + /usr/bin/time -l build_prof/ripwire /llvm-project "$arm" >/dev/null 2>"$arm.$rep.err"; done; done +# the stderr report's "hottest scopes" table is the phase split; buildGraph/3 is the resolve loop +``` diff --git a/docs/EVALS.md b/docs/EVALS.md index b461fe09..675a876f 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -21,7 +21,7 @@ section, and it is not an afterthought. | **Co-change / known-item evals** | `--eval`, `--eval-retrieval` (see `bench/ANSWERQUALITY.md`) | Whether the tool surfaces the other files a real historical commit touched; and known-item retrieval across four rankers. | | **Ensemble calibration harness** | `bench/ensemblecal/` | Whether `--ensemble`'s four evidence families are actually orthogonal, how often each fires, how stable each is across commits — and the preset ladder derived from that (§9). | | **Differential argv harness** | `test/argvdiffcheck.sh` | That a refactor changed *nothing observable*: two binaries, every argv vector, stdout + stderr + exit code byte-identical. | -| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 566 gate scripts plus the determinism, cache-transparency and golden contracts. | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 567 gate scripts plus the determinism, cache-transparency and golden contracts. | | **`--quality-delta`** | `src/quality.h` | Ten measured code-quality failure modes, reported only where a change made them worse. | ### The labeling protocol (why the held-out eval is allowed to disagree with the ranker) @@ -5579,7 +5579,7 @@ copy here would be exactly the dialect divergence that gate exists to catch. Com tags, wrap, stable-order defaults), seven individually invoked standalone gates (`g1freshcheck`, `skillscan`, `htmlexport`, `compresscheck`, `handoffcheck`, `releaseinstallcheck`, `taskroutecheck`), and a single loop -naming **566 gate scripts**, all of which exist on disk. +naming **567 gate scripts**, all of which exist on disk. `python3 test/pargates.py . ./build/ripwire -j 6` runs the same scripts in parallel so a full verification fits in one sitting. It does not modify `regression.sh`. @@ -6491,7 +6491,7 @@ Listed because the reason is more useful than the silence. shipped**. See `bench/locbench/anchorhop_calib.json`. The mention anchor's reproducible numbers are the ablations in §4. - **A single round gate-count.** Two in-tree numbers disagree (`test/pargates.py`'s docstring says - ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 566. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 567. The loop is the authority; the stale docstrings are a known drift. `test/manifestcheck.sh` asserts this very number against the loop's actual length, so it cannot go stale silently again. - **"282 argv vectors."** The gate asserts a floor of ≥250 assembled from five sources; 282 was a @@ -13007,3 +13007,89 @@ dumps that the generated-document demotion does not catch (no marker, no fences `classifyGeneratedDoc` states about itself). `.txt` stays prose to every reader-facing lens and an unindexed extension in `unindexed=`, alongside `.log`, `.lock` and `.out`. An evidence-based admission test that reads BYTES rather than the extension is the open follow-up. + +## The super-linear warm `--grep` floor: measured to one operation, fixed, byte-identical (2026-09-09) + +The tgrep head-to-head of 2026-09-09 (its section "Head-to-head vs tgrep (microsoft/tgrep 1.0.5)" lands +with the harvest-tgrep lane) left one item open: warm `--grep` cost 40.2 µs/file at 2,240 files, +39.1 µs at 15,865 and 937.8 µs at 182,555 — a 24× per-file regression across an 11.5× corpus step — and +it named the decisive experiment: time the verb with the graph construction stubbed out. This section +ran that experiment, then followed the house perf reflex (start from the measurement, inspect only the +symbols the profile names). The full phase tables and the reproduce block are in `bench/PROFILE.md` +("2026-09-09 — the super-linear warm `--grep` floor"); this is the evidence chain and the verdict. + +**The stub, without a stub.** `--help-task` returns before `buildGraph` and shares `--grep`'s lean cache +blob, so it is the crawl + cache-load + validation + model-build arm with the graph removed. Warm on +llvm-project (182,555 files, same checkout as the head-to-head), interleaved, two reps each: + +| arm | wall | peak RSS | +| --- | ---: | ---: | +| `--grep=` | 159.7 s, 153.9 s | 6.15 GB | +| `--callers=main` (graph, no scan) | 152.9 s, 151.8 s | 5.91 GB | +| `--help-task` (no graph) | 3.8 s, 3.4 s | 5.93 GB | + +The floor did move — by 150 s. The cost is the graph; the cache load plus per-file validation is +16 µs/file on llvm against 28 µs/file on go, linear; and the memory-cliff hypothesis is refuted on the +same row, since the 5.9 GB is the ingest's own tables and is present in the arm that takes 3.8 s. + +**Which operation.** The ingest path already carried `PROFILE_SCOPE_DESCRIBE` scopes at the grep path's +granularity for crawl, cache and model; `buildGraph` carried one scope for the whole function. This round +added the loop and post-loop scopes that land, plus a scratch six-span split inside the per-reference +loop. Of 153.2 s in the loop, 145.1 s sat in ONE span — CHA-lite cone + arity + locality — across +2,213,632 references at a mean 65.5 µs and a worst case of 41.7 ms. Split again: arity 0.015 s, locality +0.27 s, **the CHA-lite cone ≈ 143 s**. The obvious hypothesis was measured and rejected: the five linear +passes over same-name candidates visited 1.23 billion candidates and cost 3.3 s. + +The cone was recomputed per call: 86,667 BFS pairs for 2,984 distinct receiver types (≈29 rebuilds each), +mean cone 1,075 class names, quadratic `std::find` dedup, 1.65 ms a cone. On go the same span is 1.1 ms +in total because the model has no inheritance edges there — the flat rungs of the ladder were flat because +the corpora had no deep hierarchies, not because the code was linear. + +**The fix and its proof.** `ChaConeMemo` (`src/graph.h`) computes each receiver type's cone once, over +interned class names, with the per-call walk's exact seed, discovery order and 4,096 outer-loop cap, and +answers membership by binary search. Warm llvm: `--grep` 9.2 s / 9.0 s, `--callers` 8.6 s / 8.7 s, the +default map 248 s → 10 s; `--help-task` unchanged. Default maps at `--top-k=100000` are byte-identical +pre/post on go (10,415,057 B) and llvm (21,802,319 B). Gate `test/chaconecheck.sh` pins the set the memo +must reproduce: a cone keyed on the receiver type and not the callee (Dog and Cat on one `speak`), the +memo-hit path from a second file, a receiver with no inheritance facts degrading rather than emptying the +tier, a parameter-receiver control, and the cap's own shape (Base→{A,B}, A→A1..A4095, B→B1: B is never +expanded, so B1::m is outside the cone; count=4095, amb="1"). All arms pass against the pre-fix binary — +the expected values are the per-call walk's own answers — and 24 existing resolver gates pass unchanged. + +**What remains, stated as a floor.** Warm `--grep` on llvm is 9 s: `buildGraph` 5.9 s (the resolve loop +4.5 s, of which the candidate spray over 1.23 billion visits is 2.2 s), ingest 2.8 s, the 2.9 GB scan +1.0 s on its own thread. Per file that is 50 µs at 182,555 files against 33 µs at 15,865 — 1.5×, not 24×. +`rg` answers the same absent literal in 4.3 s; a resident tgrep in 0.018 s. The next rung is the linear +candidate passes, which is a different design (a per-name file/directory index) and is not started here. +The cold parse on llvm carried the same ~186 s cone cost inside its 231 s and was not re-measured. + +**The head-to-head's top rung, re-timed post-fix.** The same six frozen queries the lane declared for the +llvm rung, the same argv (`--grep-in=any`), the plain build with the memo, warm, three reps each, medians: + +| query | ripwire warm, head-to-head (pre-fix) | **ripwire warm, post-fix** | +| --- | ---: | ---: | +| L1 `pthread_mutex_lock` | 193.09 s | **8.99 s** | +| L3 `TODO` | 195.58 s | **9.00 s** | +| L6 `zzqxvnotpresentzz` (absent) | 176.35 s | **9.20 s** | +| R3 `malloc.*free` | 215.34 s | **9.23 s** | +| R4 `[Qq]z[Xx]v.*[Jj]w` (prefilter-defeating) | 171.19 s | **9.11 s** | +| R1 `^#include` | 233.06 s | 10.08 s — not comparable: this branch predates the lane's line-anchor fix | + +Every query is now within a second of the absent literal: the scan is still hidden behind the graph, the +graph is just 17× smaller. + +**A correctness finding the timing table surfaced, stated as one.** The tgrep lane replaces `grepScanText`'s +one-iterator-per-file regex scan with one per LINE, so that `^` and `$` mean line anchors. Priced interleaved on +the scan's own `grep/1` scope with the lane merged onto this fix (host load 31; the pair-wise scope, not the +wall clock, is the comparison): on llvm-project R1 `^#include` returns **1,487 hits on main and 289,646 on +the lane** — 288,159 matches today's shipped binary misses silently, on one query, while reporting a confident +count — and the per-line shape is *faster* (3.40 s against 4.48 s), because bounding each search to a line +stops `.*` from running across lines, so it does strictly less work per attempt. R3 `malloc.*free` 2.28 → 1.68 s; +the prefilter-defeating R4, which scans every byte, 1.27 → 1.50 s (+0.22 s on 2.9 GB, ≈3 ns a line); the literal +control 1.16 → 1.20 s (an unchanged path: the noise floor). On go every pair is within noise or faster, +including a forced full scan. Nothing super-linear, and the largest cost is 18% on the one pattern with no +literal at all. Re-deriving the lane's Q\* formula with its own tgrep numbers (B = 11.113 s, +q_index = 2.7513 s over the same six) and a post-fix q_scan of ≈ 9.3 s gives **Q\* ≈ 1.7 queries against +ripwire-warm** where the lane read 0.1 — the resident index still pays for itself inside a two-query +session at this scale, but no longer "before the first query finishes". tgrep itself was not re-run; only +the ripwire column moved. diff --git a/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index b0a1b7ca..05f321aa 100644 --- a/present/deck5_ripwire_build.js +++ b/present/deck5_ripwire_build.js @@ -708,7 +708,7 @@ function row(s, y, h, cols, opts={}){ kicker(s, "// how it stays true", AMBER); title(s, "Proven, not promised"); const cards = [ - ["566 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], + ["567 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], ["byte-identical, always", "two runs over the same tree produce the same bytes; warm equals cold. Enforced in CI, twice — Release AND a plain flavour, because NDEBUG once blinded a whole class of checks"], ["differential refactoring", "a refactor must prove it changed nothing observable: two binaries, hundreds of argv vectors, stdout + stderr + exit codes byte-identical"], ["held-out labels, authored blind", "eval labels were written by reading source before the ranker ever ran on them — so the eval is allowed to say the ranker is wrong. It has."], @@ -732,7 +732,7 @@ function row(s, y, h, cols, opts={}){ title(s, "Claims you can trust, because we publish what failed", { size: 32 }); card(s, MX, 1.72, 3.86, 1.72); - stat(s, "566", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", + stat(s, "567", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", MX+0.15, 1.86, 3.56, CYAN, { bsize: 42, bh: 0.66, lsize: 9.5 }); card(s, 4.68, 1.72, 3.86, 1.72, CARD2); stat(s, "8", "registered NEGATIVES — changes built, gated green, measured against a band written before the code, and reverted rather than tuned", @@ -971,7 +971,7 @@ function row(s, y, h, cols, opts={}){ ["179 long flags · 29 slides", "bash test/deckclaimcheck.sh"], ["every --flag named here exists", "bash test/deckcheck.sh"], ["74.7% fewer element bytes", "bash test/showcasecapturecheck.sh"], - ["566 gate scripts", "bash test/manifestcheck.sh"], + ["567 gate scripts", "bash test/manifestcheck.sh"], ["43 repos · 69 papers · 237 surveyed","bash test/readmedriftcheck.sh"], ["the ten moments, any row", "ripwire . --callers=SYM | wc -c"], ["the head-to-head table", "bench/headtohead/r4-2026-08-06/"], diff --git a/src/graph.h b/src/graph.h index 44c6dafa..00f20a39 100644 --- a/src/graph.h +++ b/src/graph.h @@ -547,6 +547,143 @@ inline bool keepRustQualifiedCandidates( const IngestResult& ing, const HashMap< return true; } +// ── B2.1 CHA-lite cone memo (perf round 2026-09-09: the super-linear warm --grep floor) ───────────────── +// A receiver type's inheritance CONE — {type} ∪ transitive ancestors ∪ transitive descendants over the +// class-NAME graph — is a pure function of the type name once chaUp/chaDown are built, yet the resolve loop +// recomputed it on EVERY still-ambiguous receiver-typed call: two BFS walks with an O(n²) std::find dedup +// over std::string. Measured warm on llvm-project (182,555 files): 86,667 walks for 2,984 distinct receiver +// types, mean cone 1,075 names, 1.65 ms each — 143 s of a 154 s --callers/--grep run, the whole of the +// floor docs/EVALS.md's tgrep head-to-head left open (bench/PROFILE.md carries the phase table). +// +// The memo computes each cone ONCE. Class names are interned to dense ids assigned in byte-sorted name order, +// the walk is the per-call walk verbatim — same seed, same discovery order, same `out.size() < kChaConeCap` +// test at the OUTER loop only (the adjacency list that crosses the cap is pushed whole; nothing after it is +// expanded) — and membership is a binary search, so the set is exactly the one the per-call walk produced. +// test/chaconecheck.sh pins that, arm 5 being the cap's own shape. A receiver type with no inheritance facts +// is not interned at all: its cone is {itself}, answered by string equality, as the per-call walk did. +struct ChaConeMemo +{ + static constexpr std::size_t kChaConeCap = 4096; // per-walk discovery cap, unchanged from the per-call walk + static constexpr std::uint32_t kNoCone = 0xFFFFFFFFu; + + // A cone handle: an INDEX into the memo's cone table, resolved inside contains() on every use, so no handle + // can dangle when the table grows (a raw pointer would be invalidated by the next fill). `index == kNoCone` + // ⇒ the receiver type has no inheritance facts: membership is equality with the type itself. + struct Cone + { + std::uint32_t index; + }; + + ChaConeMemo( const HashMap>& chaUp, + const HashMap>& chaDown ) + { + for( const auto& [ k, v ] : chaUp ) + { + names_.push_back( k ); + names_.insert( names_.end(), v.begin(), v.end() ); + } + for( const auto& [ k, v ] : chaDown ) + { + names_.push_back( k ); + names_.insert( names_.end(), v.begin(), v.end() ); + } + std::sort( names_.begin(), names_.end() ); + names_.erase( std::unique( names_.begin(), names_.end() ), names_.end() ); + idOf_.reserve( names_.size() ); + for( std::uint32_t i = 0; i < names_.size(); ++i ) + { + idOf_.emplace( names_[ i ], i ); + } + up_.assign( names_.size(), {} ); + down_.assign( names_.size(), {} ); + fillAdjacency( chaUp, up_ ); + fillAdjacency( chaDown, down_ ); + stamp_.assign( names_.size(), 0 ); + coneIndex_.assign( names_.size(), kNoCone ); + } + + Cone coneFor( std::string_view recvType ) + { + key_.assign( recvType ); + const auto rit = idOf_.find( key_ ); + if( rit == idOf_.end() ) + { + return Cone{ kNoCone }; + } + const std::uint32_t root = rit->second; + if( coneIndex_[ root ] == kNoCone ) + { + walk( up_, root, upScratch_ ); // {recvType} ∪ ancestors + walk( down_, root, downScratch_ ); // {recvType} ∪ descendants — a SEPARATE walk, never chaDown out of an ancestor + std::vector cone( upScratch_ ); + cone.insert( cone.end(), downScratch_.begin(), downScratch_.end() ); + std::sort( cone.begin(), cone.end() ); + cone.erase( std::unique( cone.begin(), cone.end() ), cone.end() ); + coneIndex_[ root ] = std::uint32_t( cones_.size() ); + cones_.push_back( std::move( cone ) ); + } + return Cone{ coneIndex_[ root ] }; + } + + // Is a candidate's enclosing scope inside `cone`? A scope no inheritance fact ever named cannot be in any + // interned cone; a scope-less free function is never a member-call target and is correctly excluded. + bool contains( Cone cone, std::string_view recvType, const std::string& scope ) const + { + if( cone.index == kNoCone ) + { + return scope == recvType; + } + const auto it = idOf_.find( scope ); + const std::vector& ids = cones_[ cone.index ]; // resolved NOW, never held across a fill + return it != idOf_.end() && std::binary_search( ids.begin(), ids.end(), it->second ); + } + +private: + void fillAdjacency( const HashMap>& adj, std::vector>& out ) const + { + for( const auto& [ k, v ] : adj ) + { + std::vector& row = out[ idOf_.find( k )->second ]; + row.reserve( v.size() ); + for( const std::string& nm : v ) // v is sorted+unique (built so in buildGraph) ⇒ ascending ids, the per-call order + { + row.push_back( idOf_.find( nm )->second ); + } + } + } + + // The per-call walk, over ids: seeded at `root`, discovery order = adjacency order, dedup by epoch stamp + // (== the old `std::find( out, nm ) == out.end()`), capped at the OUTER loop exactly as before. + void walk( const std::vector>& adj, std::uint32_t root, std::vector& out ) + { + ++epoch_; + out.clear(); + out.push_back( root ); + stamp_[ root ] = epoch_; + for( std::size_t qi = 0; qi < out.size() && out.size() < kChaConeCap; ++qi ) + { + for( const std::uint32_t nb : adj[ out[ qi ] ] ) + { + if( stamp_[ nb ] != epoch_ ) + { + stamp_[ nb ] = epoch_; + out.push_back( nb ); + } + } + } + } + + std::vector names_; // interned class names, byte-sorted (id = index) + HashMap idOf_; + std::vector> up_, down_; // adjacency by id, rows in sorted-name order + std::vector stamp_; // epoch-stamped visited set for walk() + std::uint32_t epoch_ = 0; + std::vector coneIndex_; // root id → index into cones_, or kNoCone + std::vector> cones_; // sorted id sets, one per computed receiver type + std::vector upScratch_, downScratch_; + std::string key_; // reused lookup buffer (no per-call allocation) +}; + // THE TIER-3 CANONICAL RESCUE (H4 V3 M-3) — why `canonical` sits beside `narrowed` in buildGraph's tier-3 // gate. Tier 3 is "a UNIQUE global, else DROP". A Rule-1 narrowed call has always been exempt, because it is // pinned to ONE scope and is therefore resolved rather than guessed. A CANONICAL hit is pinned in exactly the @@ -1699,9 +1836,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null for( auto& [ k, v ] : chaUp ) { std::sort( v.begin(), v.end() ); v.erase( std::unique( v.begin(), v.end() ), v.end() ); } for( auto& [ k, v ] : chaDown ) { std::sort( v.begin(), v.end() ); v.erase( std::unique( v.begin(), v.end() ), v.end() ); } } - std::vector chaAllowed; // reused per-call cone (allowed class-name set) buffer - std::vector chaDesc; // reused per-call descendants-closure scratch (kept separate from the - // ancestors walk so following one direction can never leak siblings) + ChaConeMemo chaCones( chaUp, chaDown ); // one cone per receiver type, computed on first use (see the type) std::vector filtScratch; // reused per-call survivor buffer for CHA-lite / arity filtering // ---- census arming + the ORACLE side (eval-only; src/pincensus.h) ------------------------------ @@ -1735,6 +1870,8 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null g.pinCensus.addOracleRow( nd.from, nd.calleeName, nd.kind ); } } + { + PROFILE_SCOPE_DESCRIBE( "buildGraph/3: resolve loop (per reference)" ); for( const Reference& r : ing.references ) { // file-scope / inheritance / doc-mention / HAS-A → not a call. ABS-3: read/write/import use-sites @@ -2202,51 +2339,17 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null const std::string_view recvType = narrower.receiverStaticType( r, ing.symbols[ r.fromSymbol ].scope ); if( !recvType.empty() ) { - // Build the strict cone = {recvType} ∪ ANCESTORS ∪ DESCENDANTS via TWO fully-independent - // directional closures, each seeded ONLY at recvType. A value/pointer of static type T is - // dynamically T or a subtype, so its target is either an override in T / a subtype OR the - // definition T inherits from an ancestor — but NEVER a sibling's method (a T can never BE a - // sibling). Keeping the closures separate (never following chaDown out of an ancestor) is - // what excludes siblings/cousins for precision, while still never dropping a true target. - // Directional BFS into `out`, seeded at recvType. Index by a COPIED key — push_back may - // reallocate `out`, so a held reference would dangle. - const auto closure = [ & ]( const HashMap>& adj, - std::vector& out ) - { - out.clear(); - out.emplace_back( recvType ); - for( std::size_t qi = 0; qi < out.size() && out.size() < 4096; ++qi ) - { - const std::string cur = out[ qi ]; - const auto it = adj.find( cur ); - if( it == adj.end() ) - { - continue; - } - for( const std::string& nm : it->second ) - { - if( std::find( out.begin(), out.end(), nm ) == out.end() ) - { - out.push_back( nm ); - } - } - } - }; - closure( chaUp, chaAllowed ); // {recvType} ∪ ancestors - closure( chaDown, chaDesc ); // {recvType} ∪ descendants - for( const std::string& nm : chaDesc ) - { // merge descendants into the allowed cone (dedup) - if( std::find( chaAllowed.begin(), chaAllowed.end(), nm ) == chaAllowed.end() ) - { - chaAllowed.push_back( nm ); - } - } - // keep only candidates whose enclosing class name is in the cone (a scope-less free function - // is not a member-call target ⇒ correctly excluded). Degrade if the intersection is empty. + // The strict cone = {recvType} ∪ ANCESTORS ∪ DESCENDANTS, two fully-independent directional + // walks each seeded ONLY at recvType (never chaDown out of an ancestor, so a sibling's method — + // which a T can never BE — is excluded while a true target never is). Computed once per receiver + // type by ChaConeMemo (its header carries the measurement that moved it out of this loop) and + // answered here by membership: keep only candidates whose enclosing class name is in the cone (a + // scope-less free function is not a member-call target ⇒ correctly excluded). Degrade if empty. + const ChaConeMemo::Cone cone = chaCones.coneFor( recvType ); filtScratch.clear(); for( NodeId c : tier ) { - if( std::find( chaAllowed.begin(), chaAllowed.end(), ing.symbols[ c ].scope ) != chaAllowed.end() ) + if( chaCones.contains( cone, recvType, ing.symbols[ c ].scope ) ) { filtScratch.push_back( c ); } @@ -2507,7 +2610,10 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null } } } + } + { + PROFILE_SCOPE_DESCRIBE( "buildGraph/4: flatten edges + out/in CSR" ); // flatten + cap + sort by (from, to) — deterministic regardless of map order struct E { NodeId from, to; float w; }; std::vector edges; @@ -2596,6 +2702,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null for( const E& e : edges ) { const std::uint32_t pos = cur[ e.to ]++; ci[ pos ] = e.from; val[ pos ] = e.w; } } VERIFY( verifyCsr( g.inEdges, N ) ); + } // inheritance edges (Lego view): isInherit refs (derived → base name) → implementors[base] += derived. // Resolve the base name to class-like symbols (any-file, by name); dedup. The socket→bricks relation. @@ -2604,6 +2711,8 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null // than restated, because a second copy of one rule is how the two copies end up disagreeing. const auto isClassLike = []( SymKind k ) noexcept { return namespaceCompatible( RefRole::Extends, k ); }; + { + PROFILE_SCOPE_DESCRIBE( "buildGraph/5: inheritance edges" ); for( const Reference& r : ing.references ) { if( !r.isInherit ) @@ -2668,6 +2777,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null g.implementors[ baseId ].push_back( derived ); } } + } for( std::vector& v : g.implementors ) { std::sort( v.begin(), v.end() ); @@ -2678,6 +2788,8 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null // Resolve the name to real DEFINITIONS (body present), any file; stored OUT of the call graph so a doc // mentioning a symbol never inflates its PageRank / blast radius. "what docs discuss this symbol". g.mentions.assign( N, {} ); + { + PROFILE_SCOPE_DESCRIBE( "buildGraph/6: doc mentions" ); for( const Reference& r : ing.references ) { if( !r.isDocLink || r.fromSymbol == kNoNode ) @@ -2706,6 +2818,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null g.mentions[ def ].push_back( r.fromSymbol ); } } + } for( std::vector& v : g.mentions ) { std::sort( v.begin(), v.end() ); @@ -2715,6 +2828,8 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null // S5-E HAS-A composition edges: isCompose refs (owner class → member type name) → composeEdges. // Resolve the type name to class/struct symbols (any-file, by name); store OUTSIDE the call graph so // PageRank, ranks, and the default map are UNCHANGED. Sorted (ownerSym, typeSym) for determinism. + { + PROFILE_SCOPE_DESCRIBE( "buildGraph/7: HAS-A compose edges" ); for( const Reference& r : ing.references ) { if( !r.isCompose || r.fromSymbol == kNoNode ) @@ -2766,6 +2881,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null g.composeEdges.push_back( std::move( ce ) ); break; } } + } // sort by (ownerSym, typeSym, fieldName) for determinism; dedup on (ownerSym, fieldName) — the // type name is the primary identity (one field has exactly one declared type). std::sort( g.composeEdges.begin(), g.composeEdges.end(), @@ -2796,6 +2912,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null // extends resolvers above use. Ambiguous USEs (matching ≥2 DISTINCT resolved handlers) and unresolved // USEs (matching zero) synthesize NO edge — never a guess (mirrors the amb=/unresolved= honesty posture). { + PROFILE_SCOPE_DESCRIBE( "buildGraph/8: HTTP-route edges" ); const auto isFunctionLike = []( SymKind k ) noexcept { return k == SymKind::Function || k == SymKind::Method; }; std::vector defHandler( ing.routeDefs.size(), kNoNode ); // per-DEF resolved handler symbol diff --git a/src/ingest.cpp b/src/ingest.cpp index 99b4c2ac..8dfef6b4 100644 --- a/src/ingest.cpp +++ b/src/ingest.cpp @@ -325,7 +325,10 @@ IngestResult ingest( const char* rootDir, const std::vector& exclud // macro-edges round: the corpus-wide role="macro" retag (model.h). AFTER the model is assembled and // AFTER saveCache (which stores the per-file truth, role=Call) — a #define added in one file must // re-judge every OTHER file's cached call sites on the next run, so the retag can never be persisted. - retagMacroCallReferences( result ); + { + PROFILE_SCOPE_DESCRIBE( "ingest/build-model: macro retag (corpus-wide post-pass)" ); + retagMacroCallReferences( result ); + } // r9 shadow suppression (model.h): a reference inside a function whose LOCAL declarations bind the same // name as a variable belongs to the local, not to any same-named indexed symbol — erase it here, the one @@ -334,7 +337,10 @@ IngestResult ingest( const char* rootDir, const std::vector& exclud // macro retag (role="macro" is preprocessor evidence and stays) and AFTER saveCache (per-file truth is // persisted unsuppressed; the collision gate depends on the whole corpus' symbols, so the judgment can // never be cached per-file — same reasoning as the retag above). - suppressShadowedReferences( result ); + { + PROFILE_SCOPE_DESCRIBE( "ingest/build-model: shadow suppression (r9 post-pass)" ); + suppressShadowedReferences( result ); + } return result; } diff --git a/src/ingest_parsepool.h b/src/ingest_parsepool.h index 9e6c995e..35309276 100644 --- a/src/ingest_parsepool.h +++ b/src/ingest_parsepool.h @@ -518,6 +518,7 @@ inline void runParseWorker( ParsePoolShared& sh, unsigned t ) // re-sorted by the model-build tail), reserving each family's exact total first. inline RawFacts mergeThreadFacts( std::vector& tFacts ) { + PROFILE_SCOPE_DESCRIBE( "ingest/parse-pool: merge per-thread facts" ); RawFacts raw; std::size_t totDefs = 0, totRefs = 0, totIncs = 0, totBinds = 0, totFfis = 0, totRouteDefs = 0, totRouteUses = 0, totConstOpens = 0; for( const RawFacts& tf : tFacts ) @@ -755,10 +756,13 @@ inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::st installCompiledQueriesAndOpenGate( prewarm ); // join async compiles, publish, open the gate (ingest_prewarm.h) + { + PROFILE_SCOPE_DESCRIBE( "ingest/parse-pool: workers run + join" ); for( std::thread& th : pool ) { th.join(); } + } raw = mergeThreadFacts( tFacts ); diff --git a/test/chaconecheck.sh b/test/chaconecheck.sh new file mode 100755 index 00000000..a8d7da0a --- /dev/null +++ b/test/chaconecheck.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash +# chaconecheck.sh — gate for the B2.1 CHA-lite cone MEMO (fixture names are deliberately UNIQUE across test/: +# recallevalcheck pins --for=Robot repo-wide to chafix, so this hierarchy is Creature/Hound/Lynx/Automaton/Lamp) (perf round 2026-09-09, the super-linear warm +# --grep floor): graph.h::buildGraph used to recompute a receiver type's inheritance cone — {type} ∪ +# ancestors ∪ descendants, two BFS walks over the class-NAME graph with an O(n²) std::find dedup and a +# 4096-entry cap per walk — on EVERY still-ambiguous receiver-typed call. Measured on llvm-project +# (182,555 files, warm): 86,667 cones for 2,984 distinct receiver types, mean cone 1,075 names, +# 1.65 ms each = 143 s of a 154 s --callers/--grep run (bench/PROFILE.md has the phase table). The fix +# computes each receiver type's cone ONCE and answers membership from it. +# +# WHAT THIS GATE PINS: that the memoised cone is the SAME SET the per-call walk produced — including the +# two behaviours a re-implementation is most likely to change silently: +# (a) the cone is keyed on the RECEIVER TYPE, not the callee name: g1/g2 (Hound, two files) and g3 (Lynx) +# call the same `vocalize` and must get their OWN cones; a memo keyed on the callee would hand Lynx the +# Hound answer (both contain Creature — so arm 3 pins the *Lamp* case too, where the answers differ); +# (b) the BFS cap is `out.size() < 4096` checked at the OUTER loop only: the adjacency list that crosses +# the cap is pushed whole, and nothing AFTER it is expanded. Arm 5 builds Base→{A,B}, A→A1..A4095, +# B→B1: Base's list [A,B] is expanded, A's 4095 children cross the cap, B is never expanded, so B1 is +# OUTSIDE the cone and B1::m is dropped while all 4095 A-children survive → count=4095, and the ONE +# call site is a 4095-way split → amb="1" (amb counts ambiguous CALLS, not tier width). +# A cap moved to the inner loop, or to 4097, or a dedup that changes discovery order, turns it red. +# (c) a memo HIT served AFTER the memo has grown is the requester's own cone, not the newest one: g7 asks for +# Hound again after g6 filled Droid's cone {Droid, Machine}; Hound's answer must still be Creature::vocalize +# and never Machine::vocalize. Staleness in the other sense is impossible by construction — the memo lives +# inside ONE buildGraph call and its inputs (chaUp/chaDown) are immutable for that lifetime, so there is +# nothing to invalidate — but a cached cone pointer, an index that goes wrong once the cone vector +# reallocates, or a key collision all present exactly as arm 6 reads: the wrong hierarchy's answer on a hit. +# Plus the degrade rule (a cone that keeps nothing leaves the tier untouched — g4), a control where the cone +# cannot fire (g5), determinism and XML well-formedness. +# +# RED-first proof (2026-09-09): against the pre-memo binary every arm PASSES (the expected values are the +# per-call walk's own answers, hand-derived above); mutating the cap to `< 4097` fails arm 5; keying the memo +# on the callee name fails arm 3; returning the most recently filled cone on a hit (`&cones_.back()`) fails +# arm 6 — observed red, then green after the revert, on 2026-09-09. +# +# test/chaconecheck.sh # uses build/ripwire +# RIPWIRE_BIN=asan/ripwire test/chaconecheck.sh +# Exits non-zero on any failure; prints PASS/FAIL per check, ALL PASS on success. + +set -u +ROOT="$( cd "$( dirname "$0" )/.." && pwd )" +BIN="${1:-${RIPWIRE_BIN:-$ROOT/build/ripwire}}" +[ "${BIN#/}" = "$BIN" ] && BIN="$ROOT/$BIN" +FIX="$ROOT/test/chaconefix" +TMP="$( mktemp -d )"; trap 'rm -rf "$TMP"' EXIT +fail=0 +ok(){ printf ' PASS %s\n' "$*"; } +no(){ printf ' FAIL %s\n' "$*"; fail=1; } + +[ -x "$BIN" ] || { echo "no ripwire binary at $BIN — build first (cmake --build build -j)"; exit 2; } +[ -d "$FIX" ] || { echo "no test/chaconefix dir — fixture missing"; exit 2; } +cd "$ROOT" + +echo "chaconecheck: BIN=$BIN CORPUS=test/chaconefix (+ a generated 4,097-class cap corpus)" + +# def lines derived from the source so the gate survives fixture edits +ANIMAL_LINE="$( grep -n 'inline void Creature::vocalize()' "$FIX/zoo.h" | cut -d: -f1 )" +MACHINE_LINE="$( grep -n 'inline void Machine::vocalize()' "$FIX/zoo.h" | cut -d: -f1 )" +ROBOT_LINE="$( grep -n 'void vocalize() { power' "$FIX/zoo.h" | cut -d: -f1 )" + +# distinct vocalize target lines a caller resolves to (--callees rows carry p="zoo.h:LINE") +targets(){ "$BIN" "$FIX" --callees="$1" --no-cache 2>/dev/null | tr '>' '\n' | grep -oE 'zoo\.h:[0-9]+' | grep -oE '[0-9]+$' | sort -un; } +count(){ "$BIN" "$FIX" --callees="$1" --no-cache 2>/dev/null | grep -oE ' count="[0-9]+"' | head -1 | grep -oE '[0-9]+'; } +hasamb(){ "$BIN" "$FIX" --no-cache --top-k=100000 2>/dev/null | grep -oE "n=\"$1\"[^>]*" | grep -q 'amb='; } + +# ── 1) the first Hound cone (a.cpp): Creature::vocalize ONLY, Automaton dropped, no amb ──────────────────────────── +T="$( targets g1 )" +if [ "$( count g1 )" = 1 ] && printf '%s\n' "$T" | grep -qx "$ANIMAL_LINE" && ! printf '%s\n' "$T" | grep -qx "$ROBOT_LINE" && ! hasamb g1; then + ok "g1 (Hound, a.cpp): Creature::vocalize only (zoo.h:$ANIMAL_LINE), Automaton dropped, no amb=" +else no "g1 (Hound, a.cpp): expected exactly Creature::vocalize — got count=$( count g1 ) lines={$( printf '%s' "$T" | tr '\n' ' ')}"; fi + +# ── 2) the memo HIT (b.cpp asks for the Hound cone again): byte-identical answer ────────────────────────── +T="$( targets g2 )" +if [ "$( count g2 )" = 1 ] && printf '%s\n' "$T" | grep -qx "$ANIMAL_LINE" && ! printf '%s\n' "$T" | grep -qx "$ROBOT_LINE" && ! hasamb g2; then + ok "g2 (Hound, b.cpp — memo hit): Creature::vocalize only, Automaton dropped, no amb=" +else no "g2 (Hound, b.cpp): memo hit differs from g1 — got count=$( count g2 ) lines={$( printf '%s' "$T" | tr '\n' ' ')}"; fi + +# ── 3) a DIFFERENT cone on the same callee name (Lynx), and one with NO inheritance facts (Lamp) ───────── +T="$( targets g3 )" +if [ "$( count g3 )" = 1 ] && printf '%s\n' "$T" | grep -qx "$ANIMAL_LINE" && ! hasamb g3; then + ok "g3 (Lynx): its own cone {Lynx, Creature} → Creature::vocalize only, no amb=" +else no "g3 (Lynx): expected exactly Creature::vocalize — got count=$( count g3 ) lines={$( printf '%s' "$T" | tr '\n' ' ')}"; fi +T="$( targets g4 )" +if [ "$( count g4 )" = 3 ] && printf '%s\n' "$T" | grep -qx "$ANIMAL_LINE" && printf '%s\n' "$T" | grep -qx "$ROBOT_LINE" && printf '%s\n' "$T" | grep -qx "$MACHINE_LINE" && hasamb g4; then + ok "g4 (Lamp): cone {Lamp} keeps nothing → DEGRADE, all three targets kept, amb= honest" +else no "g4 (Lamp): expected the untouched 3-way split — got count=$( count g4 ) lines={$( printf '%s' "$T" | tr '\n' ' ')}"; fi + +# ── 4) control: a parameter receiver has no var→type binding, so no cone can fire ─────────────────────── +T="$( targets g5 )" +if [ "$( count g5 )" = 3 ] && hasamb g5; then + ok "g5 (Hound& parameter): receiver type unknown → 3-way split kept, amb= honest (control)" +else no "g5 (Hound& parameter): control should stay ambiguous — got count=$( count g5 )"; fi + +# ── 6) a hit AFTER the memo grew: g6 fills Droid's cone, then g7 asks for Hound again ────────────────────── +T="$( targets g6 )" +if [ "$( count g6 )" = 1 ] && printf '%s\n' "$T" | grep -qx "$MACHINE_LINE" && ! hasamb g6; then + ok "g6 (Droid): third cone {Droid, Machine} → Machine::vocalize only (zoo.h:$MACHINE_LINE), no amb=" +else no "g6 (Droid): expected exactly Machine::vocalize — got count=$( count g6 ) lines={$( printf '%s' "$T" | tr '\n' ' ')}"; fi +T="$( targets g7 )" +if [ "$( count g7 )" = 1 ] && printf '%s\n' "$T" | grep -qx "$ANIMAL_LINE" && ! printf '%s\n' "$T" | grep -qx "$MACHINE_LINE" && ! hasamb g7; then + ok "g7 (Hound, after the memo grew): its OWN cone again — Creature::vocalize, never Machine::vocalize" +else no "g7 (Hound, after the memo grew): a hit returned the wrong hierarchy — got count=$( count g7 ) lines={$( printf '%s' "$T" | tr '\n' ' ')}"; fi + +# ── 5) the 4096 BFS cap, reproduced exactly: Base→{A,B}, A→A1..A4095, B→B1; B is never expanded ───────── +CAP="$TMP/capfix"; mkdir -p "$CAP" +{ + printf 'struct Base { void driver() { this->m(); } };\n' + printf 'struct A : Base {};\n' + printf 'struct B : Base {};\n' + i=1; while [ "$i" -le 4095 ]; do printf 'struct A%d : A { void m() {} };\n' "$i"; i=$(( i + 1 )); done + printf 'struct B1 : B { void m() {} };\n' +} >"$CAP/cap.cpp" +B1_LINE="$( grep -n '^struct B1 : B' "$CAP/cap.cpp" | cut -d: -f1 )" +A4095_LINE="$( grep -n '^struct A4095 : A' "$CAP/cap.cpp" | cut -d: -f1 )" +OUT="$( "$BIN" "$CAP" --callees=driver --no-cache --limit=100000 2>/dev/null )" +CNT="$( printf '%s' "$OUT" | grep -oE ' count="[0-9]+"' | head -1 | grep -oE '[0-9]+' )" +LINES="$( printf '%s' "$OUT" | tr '>' '\n' | grep -oE 'cap\.cpp:[0-9]+' | grep -oE '[0-9]+$' | sort -un )" +AMB="$( "$BIN" "$CAP" --no-cache --top-k=100000 2>/dev/null | grep -oE 'n="driver"[^>]*' | grep -oE 'amb="[0-9]+"' | grep -oE '[0-9]+' )" +if [ "$CNT" = 4095 ] && printf '%s\n' "$LINES" | grep -qx "$A4095_LINE" && ! printf '%s\n' "$LINES" | grep -qx "$B1_LINE" && [ "${AMB:-0}" = 1 ]; then + ok "cap: Base::driver → A1..A4095::m (count=4095, one call amb=1); B1::m (cap.cpp:$B1_LINE) outside the capped cone" +else no "cap: expected count=4095 amb=1 without B1::m — got count=${CNT:-?} amb=${AMB:-none} B1_present=$( printf '%s\n' "$LINES" | grep -qx "$B1_LINE" && echo yes || echo no )"; fi + +# ── 6) determinism + well-formedness ──────────────────────────────────────────────────────────────────── +A="$( "$BIN" "$FIX" --callees=g2 --no-cache 2>/dev/null )"; B="$( "$BIN" "$FIX" --callees=g2 --no-cache 2>/dev/null )" +[ "$A" = "$B" ] && ok "determinism: --callees=g2 byte-identical run-to-run" || no "non-deterministic --callees output" +if command -v xmllint >/dev/null 2>&1; then + printf '%s' "$A" | xmllint --noout - 2>/dev/null && ok "xml well-formed" || no "xml malformed" +fi + +[ "$fail" = 0 ] && echo "ALL PASS" || echo "FAILURES ABOVE" +exit $fail diff --git a/test/chaconefix/a.cpp b/test/chaconefix/a.cpp new file mode 100644 index 00000000..3cc2c0c9 --- /dev/null +++ b/test/chaconefix/a.cpp @@ -0,0 +1,4 @@ +// chaconefix/a.cpp — the FIRST call whose receiver static type is Hound: the Hound cone is computed here. +#include "zoo.h" + +void g1() { Hound d; d.vocalize(); } // cone {Hound, Creature} → Creature::vocalize only; Automaton::vocalize dropped diff --git a/test/chaconefix/b.cpp b/test/chaconefix/b.cpp new file mode 100644 index 00000000..382af7ea --- /dev/null +++ b/test/chaconefix/b.cpp @@ -0,0 +1,10 @@ +// chaconefix/b.cpp — the SECOND file to ask for the Hound cone (a memo HIT must equal the first answer), +// plus a different cone on the same callee name, a receiver with no inheritance facts, and a control. +#include "zoo.h" + +void g2() { Hound d; d.vocalize(); } // memo hit: byte-identical to g1's answer (Creature::vocalize only) +void g3() { Lynx c; c.vocalize(); } // a DIFFERENT cone {Lynx, Creature} keyed on the same callee `vocalize` +void g4() { Lamp l; l.vocalize(); } // cone {Lamp} keeps nothing → DEGRADE: tier untouched, stays ambiguous +void g5( Hound& p ) { p.vocalize(); } // control: parameter receiver → no var→type binding → cone cannot fire +void g6() { Droid d; d.vocalize(); } // a THIRD cone {Droid, Machine}: the memo grows AFTER Hound's entry exists +void g7() { Hound h; h.vocalize(); } // Hound AGAIN, after the memo grew: a hit must be Hound's cone, not the newest diff --git a/test/chaconefix/zoo.h b/test/chaconefix/zoo.h new file mode 100644 index 00000000..d27e5739 --- /dev/null +++ b/test/chaconefix/zoo.h @@ -0,0 +1,48 @@ +// chaconefix/zoo.h — gate fixture for the CHA-lite cone memo (test/chaconecheck.sh). +// +// The same hierarchy shape as chafix/cha.cpp under UNIQUE names (recallevalcheck pins --for=Robot to chafix), in ONE header so every candidate for `vocalize` sits in +// the SAME directory as every caller (tier 2 of the ladder) and the tier reaches CHA-lite still ambiguous: +// Creature — a base with a bodied vocalize(); Hound and Lynx implement it WITHOUT overriding vocalize(); +// Automaton — UNRELATED, its own vocalize(); Lamp — a class with NO inheritance facts and NO vocalize(). +struct Creature +{ + void vocalize(); // declared here, defined out-of-line below (a real, bodied def) + virtual void move(); // an unrelated virtual so Creature is a genuine polymorphic base + int tag = 0; +}; + +struct Hound : Creature // implementor 1 — does NOT define vocalize (inherits Creature::vocalize) +{ + void move() override { tag = 1; } +}; + +struct Lynx : Creature // implementor 2 — a DIFFERENT cone on the same callee name +{ + void move() override { tag = 2; } +}; + +struct Automaton // UNRELATED — outside every Creature cone +{ + void vocalize() { power = 1; } + int power = 0; +}; + +struct Lamp // no bases, no derived classes, no vocalize(): its cone is {Lamp} alone +{ + int watts = 0; +}; + +struct Machine // a SECOND base with its own bodied vocalize(): the Droid cone excludes Creature +{ + void vocalize(); + int rpm = 0; +}; + +struct Droid : Machine // implementor of Machine — does NOT define vocalize (inherits Machine::vocalize) +{ + int model = 0; +}; + +inline void Creature::vocalize() { tag = 3; } +inline void Machine::vocalize() { rpm = 5; } +inline void Creature::move() { tag = 4; } diff --git a/test/maxfilesizecheck.sh b/test/maxfilesizecheck.sh index 4bea6e9b..80df35b8 100755 --- a/test/maxfilesizecheck.sh +++ b/test/maxfilesizecheck.sh @@ -102,11 +102,38 @@ BIGJSON="$( cd "$ROOT" && git ls-files -z '*.json' | xargs -0 -I{} sh -c 'test $ || no "premise FAILED: no >256K .json in the tree — the invariant arm above cannot discriminate" # and the drop is disclosed at a ceiling that no longer catches it generically (this is the red-first arm: -# the pre-fix binary reports the attribute ABSENT here, so the count reads as a false zero) +# the pre-fix binary reports the attribute ABSENT here, so the count reads as a false zero). +# +# 2026-09-09: the expectation is DERIVED from the tool's own --skipped rows, no longer assumed. The old form +# asserted skipped_oversize == BIGJSON, i.e. "nothing but the JSON lane drops at 1M" — a fact about this +# tree's SHAPE, not about the tool — and it expired the day docs/EVALS.md crossed 1,048,576 B (1M is +# 1024*1024, cli.h parseByteSize): every shard carrying this gate went red on a landing tip, and it read as a +# libstdc++-vs-libc++ split only because the macOS Release leg had been cancelled. Three arms replace it, +# each an invariant of the TOOL: (a) the JSON-lane rows at 1M are exactly the BIGJSON files; (b) every +# OTHER oversize row names a file that really is over 1,048,576 B on disk — a ceiling never drops a file +# under it; (c) the header's skipped_oversize= equals the rows itemized. A tree with no non-JSON file over +# 1 MiB passes (b) and (c) vacuously with OTHERROWS=0, which is the old arm's case, so nothing is weakened. +SKIPPED_ROWS="$( "$BIN" "$ROOT" --max-file-size=1M --skipped 2>/dev/null | tr '>' '\n' | grep 'why="oversize"' )" SKIP1M="$( "$BIN" "$ROOT" --max-file-size=1M --top-k=1 2>/dev/null | grep -oE 'skipped_oversize=[0-9]+' | head -1 | grep -oE '[0-9]+' )" -[ "${SKIP1M:-0}" = "${BIGJSON:-0}" ] \ - && ok "--max-file-size=1M discloses skipped_oversize=$SKIP1M — the $BIGJSON .json the JSON-lane ceiling dropped" \ - || no "--max-file-size=1M reports skipped_oversize=${SKIP1M:-}, expected $BIGJSON (the JSON-lane drop is uncounted)" +JSONROWS="$( printf '%s\n' "$SKIPPED_ROWS" | grep -c 'limit="262144"' | tr -d ' ' )" +ALLROWS="$( printf '%s\n' "$SKIPPED_ROWS" | grep -c 'why="oversize"' | tr -d ' ' )" +OTHERROWS=$(( ALLROWS - JSONROWS )) +[ "${JSONROWS:-0}" = "${BIGJSON:-0}" ] \ + && ok "--max-file-size=1M discloses the $BIGJSON .json the JSON-lane ceiling dropped, one row each" \ + || no "--max-file-size=1M itemizes $JSONROWS JSON-lane rows, expected $BIGJSON (the JSON-lane drop is uncounted)" +UNDER=0 +while IFS= read -r p; do + [ -n "$p" ] || continue + [ "$( wc -c <"$ROOT/$p" | tr -d ' ' )" -gt 1048576 ] || UNDER=$(( UNDER + 1 )) +done <} disagrees with the itemized rows ($JSONROWS + $OTHERROWS)" # ── 3. still valid XML and still deterministic under the ceiling "$BIN" "$ROOT" --max-file-size=8K --top-k=3 >"$TMP/d1" 2>/dev/null diff --git a/test/regression.sh b/test/regression.sh index b1e03e39..e2b9c1c4 100755 --- a/test/regression.sh +++ b/test/regression.sh @@ -265,7 +265,7 @@ else RIPWIRE_BIN="$BIN" bash "$ROOT/test/codexdoctorcheck.sh" 2>&1 | sed 's/^/ | /' fi # retired: cacheexclkeycheck — the per-configuration auto-cache key it pinned is a registered NEGATIVE (docs/EVALS.md, "The auto-cache key ignores --exclude", RUN 2026-09-03: a 158K-file root with >= 12 gate configurations thrashed the 2 GiB sweep); the retry design keeps ONE superset blob per root and will bring its own gate -for _g in a9disclosurecheck abicheck accessshapecheck ackonlycheck adaptivecheck adaptivecutshapecheck affectedcheck agentloopclaudecheck agentloopcodexcheck agentloopeditsuitecheck agentloopfollowupcheck agentloopgradercheck agentlooplockcheck agentloopopencodecheck agenttablecheck aiderbytescheck anchorbodycheck anchorcheck archcheck archmetricscheck argvdiffcheck arisefollowupcheck ariseshimcheck aritycheck artifactcheck atcheck atomscheck attrvocabcheck baselinecheck baselinedirtycheck baselineportcheck bashsourcecheck batchcheck binoverridecheck blindspotcheck bm25boundcheck bm25check bodiesshowncheck bodydialectcheck budgetpolicycheck bundleidcheck cachefuzzcheck cachehashcheck cacheidentitycheck cacheisolationcheck cachelintcheck cacheoffsetcheck cachesplitcheck callerscheck callformcheck candheadcheck candidatescheck canoncheck ccheck ccjsoncheck chacheck chainguardcheck chainidcheck churndecaycheck churnjoincheck churnjsonstampcheck clicheck clonebandcheck clonecachecheck clonededupcheck cloneidiomcheck clonelexcheck clsrecvcheck cochangeboostcheck cochangecliocheck cochangesurprisecheck codexinstallhonestycheck codexplugincheck codexwrapcheck collectioncapcheck columnarattrcheck columnarcheck columnarcommacheck commentcoherencecheck communitydrillcheck communitylabelcheck compactlegendcheck compactroutecheck completecheck composelangcheck connectcheck connectcorecheck connectjoincheck constcheck contextratiocheck coplintcheck cppbenchcheck cppoperatorcheck cppqualcheck crossdirincludecheck crossrefcheck crossrefdegradecheck csharpcheck csharpcondcheck cudacheck cyclecutcheck deadcheck deadfiltercheck deadprecisioncheck deckcheck deckclaimcheck deeptailcheck defaultceilingcheck defoverdeclcheck degradedhintcheck dependencypincheck deplangscheck depsprecisecheck detailcheck didyoumeancheck dispatchordercheck dmmcheck docanchorcheck docdemotecheck docdriftcheck docdriftcommentcheck docmdcachecheck docmentioncheck docscommandscheck doctorcheck donelegendcheck droppedpositivecheck duprowcheck dynmapsimdcheck editcheckcheck editchecknotecheck edithandlehintcheck editpayloadbinarycheck editplancheck editplanpayloadconfinecheck editplanrecheckcheck editplanrollbackmsgcheck editpreviewcheck editroundtripcheck edittargetfileabscheck eliximportcheck elixircheck emittertruthcheck emptycorpuscheck emptyvaluerefusecheck ensembleavailcheck ensemblecheck essentialcxcheck estchargecheck evalcheck evictioncheck exemplarcheck exemplarconfcheck exercisescheck expandcallscheck expandmodecheck expandrangecheck expandsibscheck expandtokencheck expandtopk0check externalvetocheck fficheck fieldaffinitycheck fieldnarrowcheck fieldusescheck filerootcheck fileselectorrefusecheck fillordercheck fixedbufsweep flagscheck flagsnoisecheck flagsurfacecheck flagtablecheck flipcheck floormarkcheck fnptrcheck forautobodycheck forbudgetmonotoncheck forcalibfactscheck forcompresscheck fordisclosurecheck forlenscheck formatgatecheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck freshclonecheck freshnesscheck g1configcheck gateabilitycheck gateexitcheck genrecallcheck githardencheck gitignorecheck gitquotepathcheck gitstampcheck goinstcheck gointerfacecheck graphlegendbudgetcheck graphqueryrefusecheck grepandcheck grepbytescheck grepcheck grepcontextcheck grepcorpuscheck grepfastcheck grepfollowupcheck grepscancheck grepseamcheck greptiercheck guardmsgcheck hasacheck headsnapcachecheck hermesinstallcheck historyoraclecheck hookcheck hostilecheck hotspotsincecheck htmlcolorcheck htmlhostcheck htmlrendercheck identitycheck impactimportcheck impactpartitioncheck importnarrowcheck includeanglecheck includeprecisecheck indexoutcheck infraportcheck isolateprovenancecheck javarubycheck jslangcheck jsmetricscheck jsnestedcheck jsoncheck jsonlangcheck jsonparitycheck jsonredactcheck jsonrefusallegendcheck jsonwalkcheck jsshapecheck jsverbscheck knownitemcheck landingcheck langcensuscheck langcheck layerquerycheck layoutcheck lb3namecheck legendcostcheck legendcoveragecheck legenddriftcheck legobundlecheck legocheck lintbudgetcheck lintcatalogcheck lintcheck lintdedupcheck lintpayloadcapcheck lintprecisioncheck lintrulescheck lintscopecheck lintselectcheck localitycheck localscountcheck loopconservationcheck lpincheck luacheck luarequirecheck macroedgecheck manifestcheck mapdiffcheck matchcapturecheck matchgrammarcheck maxfilesizecheck mcpattrparitycheck mcpaudit4hardencheck mcpclidiffcheck mcpcodexmetacheck mcpcontractcheck mcpdegradedhintcheck mcpeditcheck mcpeditkindcheck mcpeditmodecheck mcpeditpresencecheck mcpeditracecheck mcpflagshipcheck mcpforparitycheck mcpframehonestycheck mcpgrepdegradedcheck mcphandlecheck mcpincrementalcheck mcpmanifestcheck mcprangeedgecheck mcpreadloopcheck mcpredactcheck mcpreloadcheck mcpremotecheck mcprobustcheck mcpslicecheck mcpstalecheck mcpstrictschemacheck mcptoolprunecheck mcptranchecheck mcpverbscheck mcpw2fixcheck mcpw3fixcheck mcpwatchercheck mdembedcheck mdsectioncheck mentioncheck mentionsverbcheck mergechurncheck mergescoutcheck mergescoutlonglinecheck metalcheck meterdisclosurecheck metricscheck modifierguardcheck moduleconstcheck morecontractcheck mrowalkcheck multirootcheck multiswecheck namedfileinputcheck nameinfocheck namingcalibrationcheck namingconsistencycheck naminglenscheck naminglocalscheck narrowcheck narrowlangcheck neighbourcapcheck nestedimportcheck nestedqualcheck nestprofilecheck nextverbcheck nongitqmetricscheck nonlocalstatecheck notecanoncheck notescheck nsfiltercheck nulbytecheck numericrefusecheck objcfieldcheck objcsniffcheck opencodewrapcheck optremarkscheck ordercheck outlinecheck overbudgetcommentcheck ownerscheck packcallersharecheck packtaskcheck packtaskmonotoncheck packtaskquotacheck padscalecheck paginationcheck pagingsweepcheck panellegendcheck pargatescheck parsehealthcheck partitioncheck patterncheck perfharnesscheck phpcheck pincensuscheck planlanescheck planlintcheck pmccheck portablebuildcheck portablecachecheck postingscheck ppaltcheck pranchorcheck prbudgetcheck prcheck prcontextcheck prconvergecheck precedencecheck preproccondcheck prmaskanchorcheck prnestedcapcheck probecheck propcostcheck prrefsafecheck prrenamecheck pyimportprecisecheck pyshapecheck qackconcurrencycheck qackorigincheck qchurncheck qchurnmemocheck qdrefpaircheck qextractionkeycheck qoriginoraclecheck qrevtokencheck qrowlocatorcheck qschemetripcheck qsnapcachecheck qsnapprefetchcheck qualifiedresolvecheck qualitycheck qualitycrosslangcheck qualityexcludecheck qualitykeycheck qualitykindscheck qualityorigincheck qualitypanelcheck qualityscopecheck qualitysignalcheck qualitystalecheck qualitysymcheck qualnewcheck querycheck queryfilescancheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recalltablecheck recalltotalcheck receiptpostcheck redactcheck redactfixcheck refusaltailcheck regexbombcheck regexcheck regexrefusecheck registermacrocheck relevancefloorcheck relinkcheck reportcheck resolvecheck resolverhonestycheck retrievalqualitycheck reusefirstworkflowcheck ripwirepubliccheck rootrelcheck rootrelemitcheck routecheck routeedgecheck routehookcheck routeoncecheck routingreportcheck rubyconstcheck rubymetricscheck rubyrecvcheck rubyrequirecheck rubyscopecheck rubysettercheck runhintcheck runtracecheck rustimportprecisecheck rustqualcheck safedeletecheck sarifcheck savecachecheck scipcheck scipjoincheck scorecardcheck scoutheadconflictcheck scoutkeycheck seedboundscheck selectorchaincheck selectorhonestycheck selectorrefusecheck selectorscopecheck selfcontainedcheck shadowcheck shapingflagcheck shellgateindexcheck showcasecapturecheck sibliftcheck sigredactcheck sincecheck sincecochangecheck sincewindowcheck singledefcheck situdiffcheck skilldescbudgetcheck skillevalcheck skillevalsplitcheck skillinstallcheck skillroutingjudgedcheck skillscanreadcheck skilltruthcheck skippedcheck skipreasoncheck slicecheck slicediffcheck sliceflowcheck sliceflowsenscheck spectimingcheck staleackcheck statgatecheck sublistcountcheck substrfiltercheck subtokencheck svectorcheck swiftcheck swiftmemberscheck swiftshapecheck taskechocheck termmargincheck testedreachcheck testgatecheck testgatelegendbudgetcheck testgatepagecheck testgaterefusecheck testmacrocheck testrowruncheck testscopecheck textdocscheck tokenbudgetcheck tomllangcheck toolcallroutecheck tornreadcheck tracecheck tracehopcheck traceminecheck treecheck truncvocabcheck tsimportprecisecheck tsshapecheck type3check type3clonecheck typerefcheck unreachablecheck unresolvedcheck usescheck usesselectorcheck usingdeclcheck utf8scrubcheck vendoredassetcheck vendoredbundlecheck vendorpatchcheck verifycheck versioncheck w2verbscheck w3fixbudgetcheck w3fixlegendcheck weaksignalcheck withgraphcheck withprofilecheck wrapverbscheck writetargetcheck xmlwellformed yamllangcheck zonecheck zoneconsistencycheck zoomcheck; do +for _g in a9disclosurecheck abicheck accessshapecheck ackonlycheck adaptivecheck adaptivecutshapecheck affectedcheck agentloopclaudecheck agentloopcodexcheck agentloopeditsuitecheck agentloopfollowupcheck agentloopgradercheck agentlooplockcheck agentloopopencodecheck agenttablecheck aiderbytescheck anchorbodycheck anchorcheck archcheck archmetricscheck argvdiffcheck arisefollowupcheck ariseshimcheck aritycheck artifactcheck atcheck atomscheck attrvocabcheck baselinecheck baselinedirtycheck baselineportcheck bashsourcecheck batchcheck binoverridecheck blindspotcheck bm25boundcheck bm25check bodiesshowncheck bodydialectcheck budgetpolicycheck bundleidcheck cachefuzzcheck cachehashcheck cacheidentitycheck cacheisolationcheck cachelintcheck cacheoffsetcheck cachesplitcheck callerscheck callformcheck candheadcheck candidatescheck canoncheck ccheck ccjsoncheck chacheck chaconecheck chainguardcheck chainidcheck churndecaycheck churnjoincheck churnjsonstampcheck clicheck clonebandcheck clonecachecheck clonededupcheck cloneidiomcheck clonelexcheck clsrecvcheck cochangeboostcheck cochangecliocheck cochangesurprisecheck codexinstallhonestycheck codexplugincheck codexwrapcheck collectioncapcheck columnarattrcheck columnarcheck columnarcommacheck commentcoherencecheck communitydrillcheck communitylabelcheck compactlegendcheck compactroutecheck completecheck composelangcheck connectcheck connectcorecheck connectjoincheck constcheck contextratiocheck coplintcheck cppbenchcheck cppoperatorcheck cppqualcheck crossdirincludecheck crossrefcheck crossrefdegradecheck csharpcheck csharpcondcheck cudacheck cyclecutcheck deadcheck deadfiltercheck deadprecisioncheck deckcheck deckclaimcheck deeptailcheck defaultceilingcheck defoverdeclcheck degradedhintcheck dependencypincheck deplangscheck depsprecisecheck detailcheck didyoumeancheck dispatchordercheck dmmcheck docanchorcheck docdemotecheck docdriftcheck docdriftcommentcheck docmdcachecheck docmentioncheck docscommandscheck doctorcheck donelegendcheck droppedpositivecheck duprowcheck dynmapsimdcheck editcheckcheck editchecknotecheck edithandlehintcheck editpayloadbinarycheck editplancheck editplanpayloadconfinecheck editplanrecheckcheck editplanrollbackmsgcheck editpreviewcheck editroundtripcheck edittargetfileabscheck eliximportcheck elixircheck emittertruthcheck emptycorpuscheck emptyvaluerefusecheck ensembleavailcheck ensemblecheck essentialcxcheck estchargecheck evalcheck evictioncheck exemplarcheck exemplarconfcheck exercisescheck expandcallscheck expandmodecheck expandrangecheck expandsibscheck expandtokencheck expandtopk0check externalvetocheck fficheck fieldaffinitycheck fieldnarrowcheck fieldusescheck filerootcheck fileselectorrefusecheck fillordercheck fixedbufsweep flagscheck flagsnoisecheck flagsurfacecheck flagtablecheck flipcheck floormarkcheck fnptrcheck forautobodycheck forbudgetmonotoncheck forcalibfactscheck forcompresscheck fordisclosurecheck forlenscheck formatgatecheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck freshclonecheck freshnesscheck g1configcheck gateabilitycheck gateexitcheck genrecallcheck githardencheck gitignorecheck gitquotepathcheck gitstampcheck goinstcheck gointerfacecheck graphlegendbudgetcheck graphqueryrefusecheck grepandcheck grepbytescheck grepcheck grepcontextcheck grepcorpuscheck grepfastcheck grepfollowupcheck grepscancheck grepseamcheck greptiercheck guardmsgcheck hasacheck headsnapcachecheck hermesinstallcheck historyoraclecheck hookcheck hostilecheck hotspotsincecheck htmlcolorcheck htmlhostcheck htmlrendercheck identitycheck impactimportcheck impactpartitioncheck importnarrowcheck includeanglecheck includeprecisecheck indexoutcheck infraportcheck isolateprovenancecheck javarubycheck jslangcheck jsmetricscheck jsnestedcheck jsoncheck jsonlangcheck jsonparitycheck jsonredactcheck jsonrefusallegendcheck jsonwalkcheck jsshapecheck jsverbscheck knownitemcheck landingcheck langcensuscheck langcheck layerquerycheck layoutcheck lb3namecheck legendcostcheck legendcoveragecheck legenddriftcheck legobundlecheck legocheck lintbudgetcheck lintcatalogcheck lintcheck lintdedupcheck lintpayloadcapcheck lintprecisioncheck lintrulescheck lintscopecheck lintselectcheck localitycheck localscountcheck loopconservationcheck lpincheck luacheck luarequirecheck macroedgecheck manifestcheck mapdiffcheck matchcapturecheck matchgrammarcheck maxfilesizecheck mcpattrparitycheck mcpaudit4hardencheck mcpclidiffcheck mcpcodexmetacheck mcpcontractcheck mcpdegradedhintcheck mcpeditcheck mcpeditkindcheck mcpeditmodecheck mcpeditpresencecheck mcpeditracecheck mcpflagshipcheck mcpforparitycheck mcpframehonestycheck mcpgrepdegradedcheck mcphandlecheck mcpincrementalcheck mcpmanifestcheck mcprangeedgecheck mcpreadloopcheck mcpredactcheck mcpreloadcheck mcpremotecheck mcprobustcheck mcpslicecheck mcpstalecheck mcpstrictschemacheck mcptoolprunecheck mcptranchecheck mcpverbscheck mcpw2fixcheck mcpw3fixcheck mcpwatchercheck mdembedcheck mdsectioncheck mentioncheck mentionsverbcheck mergechurncheck mergescoutcheck mergescoutlonglinecheck metalcheck meterdisclosurecheck metricscheck modifierguardcheck moduleconstcheck morecontractcheck mrowalkcheck multirootcheck multiswecheck namedfileinputcheck nameinfocheck namingcalibrationcheck namingconsistencycheck naminglenscheck naminglocalscheck narrowcheck narrowlangcheck neighbourcapcheck nestedimportcheck nestedqualcheck nestprofilecheck nextverbcheck nongitqmetricscheck nonlocalstatecheck notecanoncheck notescheck nsfiltercheck nulbytecheck numericrefusecheck objcfieldcheck objcsniffcheck opencodewrapcheck optremarkscheck ordercheck outlinecheck overbudgetcommentcheck ownerscheck packcallersharecheck packtaskcheck packtaskmonotoncheck packtaskquotacheck padscalecheck paginationcheck pagingsweepcheck panellegendcheck pargatescheck parsehealthcheck partitioncheck patterncheck perfharnesscheck phpcheck pincensuscheck planlanescheck planlintcheck pmccheck portablebuildcheck portablecachecheck postingscheck ppaltcheck pranchorcheck prbudgetcheck prcheck prcontextcheck prconvergecheck precedencecheck preproccondcheck prmaskanchorcheck prnestedcapcheck probecheck propcostcheck prrefsafecheck prrenamecheck pyimportprecisecheck pyshapecheck qackconcurrencycheck qackorigincheck qchurncheck qchurnmemocheck qdrefpaircheck qextractionkeycheck qoriginoraclecheck qrevtokencheck qrowlocatorcheck qschemetripcheck qsnapcachecheck qsnapprefetchcheck qualifiedresolvecheck qualitycheck qualitycrosslangcheck qualityexcludecheck qualitykeycheck qualitykindscheck qualityorigincheck qualitypanelcheck qualityscopecheck qualitysignalcheck qualitystalecheck qualitysymcheck qualnewcheck querycheck queryfilescancheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recalltablecheck recalltotalcheck receiptpostcheck redactcheck redactfixcheck refusaltailcheck regexbombcheck regexcheck regexrefusecheck registermacrocheck relevancefloorcheck relinkcheck reportcheck resolvecheck resolverhonestycheck retrievalqualitycheck reusefirstworkflowcheck ripwirepubliccheck rootrelcheck rootrelemitcheck routecheck routeedgecheck routehookcheck routeoncecheck routingreportcheck rubyconstcheck rubymetricscheck rubyrecvcheck rubyrequirecheck rubyscopecheck rubysettercheck runhintcheck runtracecheck rustimportprecisecheck rustqualcheck safedeletecheck sarifcheck savecachecheck scipcheck scipjoincheck scorecardcheck scoutheadconflictcheck scoutkeycheck seedboundscheck selectorchaincheck selectorhonestycheck selectorrefusecheck selectorscopecheck selfcontainedcheck shadowcheck shapingflagcheck shellgateindexcheck showcasecapturecheck sibliftcheck sigredactcheck sincecheck sincecochangecheck sincewindowcheck singledefcheck situdiffcheck skilldescbudgetcheck skillevalcheck skillevalsplitcheck skillinstallcheck skillroutingjudgedcheck skillscanreadcheck skilltruthcheck skippedcheck skipreasoncheck slicecheck slicediffcheck sliceflowcheck sliceflowsenscheck spectimingcheck staleackcheck statgatecheck sublistcountcheck substrfiltercheck subtokencheck svectorcheck swiftcheck swiftmemberscheck swiftshapecheck taskechocheck termmargincheck testedreachcheck testgatecheck testgatelegendbudgetcheck testgatepagecheck testgaterefusecheck testmacrocheck testrowruncheck testscopecheck textdocscheck tokenbudgetcheck tomllangcheck toolcallroutecheck tornreadcheck tracecheck tracehopcheck traceminecheck treecheck truncvocabcheck tsimportprecisecheck tsshapecheck type3check type3clonecheck typerefcheck unreachablecheck unresolvedcheck usescheck usesselectorcheck usingdeclcheck utf8scrubcheck vendoredassetcheck vendoredbundlecheck vendorpatchcheck verifycheck versioncheck w2verbscheck w3fixbudgetcheck w3fixlegendcheck weaksignalcheck withgraphcheck withprofilecheck wrapverbscheck writetargetcheck xmlwellformed yamllangcheck zonecheck zoneconsistencycheck zoomcheck; do [ -f "$ROOT/test/$_g.sh" ] || continue if RIPWIRE_BIN="$BIN" bash "$ROOT/test/$_g.sh" >/dev/null 2>&1; then ok "absorb gate ($_g.sh)"