round(perf/strings/caps/quality/routing): the 2026-09-10 full-audit execution — one header of SIMD string kernels, the O(C²) child walks, the cache that evicted itself, per-kind --quality-delta dials, --help-task precision - #127
Merged
Merged
Conversation
… mirrored paths, gated
src/infra/strkern.h is the single home the owner asked for: NEON, AVX2 and the scalar/SWAR
reference written side by side, every kernel taking (const char*, size_t) so nothing needs a
NUL terminator. Kernels: classMasks (Lemire two-stage nibble classification -> per-byte
upper/lower/digit/alnum bitmasks), lowerFoldAscii / lowerFoldedEquals (Tempesta's
(unsigned)(c-'A')<26 fold, with the 0x80-bias spelling for AVX2's signed compare),
findByte / find3 / findByteset (StringZilla's (b>>3,b&7) set decomposition and NEON vshrn
movemask, Mula's first/last-byte filter). Lane M appends its find kernels here, additively.
Two things the audit's technique map got wrong on contact, both recorded in the header:
* the familiar has-zero-byte `( x - 0x0101.. ) & ~x & 0x8080..` cannot ship under G1. Its
subtraction WRAPS and -fsanitize=integer -fno-sanitize-recover=all turns that into an
abort; the harness caught it on the first run. The exact, wrap-free variant
`~( ( ( x & 0x7F7F.. ) + 0x7F7F.. ) | x ) & 0x8080..` replaces it and is also strictly
better: it has no false positives, so findByte_scalar needs no verify pass at all.
* NEON's movemask is used in BOTH forms on purpose. The find kernels take StringZilla's
vshrn_n_u16 nibble mask (one shift-and-narrow; ctz>>2 is the byte index), because they
only ever want the first match. classMasks takes a true one-bit-per-byte mask, because
the tokenizer's boundary algebra needs a shift of one to MEAN one byte, and a nibble
form would put a different scale factor in the NEON and AVX2 spellings of every
expression that follows.
CMake: the x86-64 floor is -march=x86-64-v3 unconditionally for x86-64 targets (owner,
2026-09-10: AVX2 + BMI1/2 + FMA + LZCNT + MOVBE, the RHEL 10 level; never v4). Without it
__AVX2__ is undefined and every x86-64 build silently runs the scalar twins. The FMA caveat
and its remedy (-ffp-contract=off on the pagerank TU, never lowering the floor) are written
into the branch. Apple Silicon and aarch64 Linux are untouched — NEON is baseline there.
GATE (written before the wiring it will measure): test/strkerncheck.sh + strkern_harness.cpp.
Four arms, all green on this box:
PASS 13 harness arms green (path=NEON block=16)
PASS non-vacuity: strkern path: NEON on arm64
PASS can-go-red: -DSTRKERN_MUTATE=1 fails 8 arm(s) as designed
PASS x86_64/AVX2 mirror runs green under Rosetta 2 (13 arms)
Corpora: 100k fixed-seed random buffers over four alphabets (identifier, full ASCII,
high-bit, camel/acronym-dense), lengths 0..300 so every 16- and 32-byte boundary is
straddled repeatedly; all 256 byte values at every offset and length; and every byte of
src/ and docs/ (751 files). The x86_64 arm compiles the same harness -arch x86_64
-march=x86-64-v3 and runs it under Rosetta 2, so the AVX2 mirror is proven HERE and not
only on CI's ubuntu legs. -DSTRKERN_MUTATE=1 perturbs the SIMD tables only (one nibble-table
bit, the fold range by one, findByteset's high half) and must fail: it fails 8 arms.
test/portablebuildcheck.sh gains arms #2b/#2c: an x86-64 target must carry the v3 floor and
neither v4 nor an Apple flag; an aarch64 target must not be handed an x86 -march. Both drive
the REAL module via a CMAKE_SYSTEM_PROCESSOR override, not a reimplementation.
Registered in test/regression.sh, exempted in binoverridecheck (it builds its own harnesses),
gate count regenerated by docs/gatecount_build.py: 586 -> 587.
…dow rewrites `--quality-delta`'s short-horizon-churn kind gated on churn="self" — "this uncommitted edit modifies a line that was itself committed inside the 14-day window". On an active branch that is the agent's own footprint by construction: the symbol you wrote this week and are touching again satisfies it. Audit lane Q1 measured what that costs on twelve LANDED, reviewed, merged commits of this repo, replayed in the working-tree form an agent actually runs at a "done" checkpoint: 135 of 171 gating rows were churn="self", and the labelled precision of that population was 0% — not one row a reviewer would act on. The repo had already written the verdict down itself, in commit 65d98b7's message: "that is this branch touching the symbol twice within its window, not new debt". BOTH facets are now informational. What gates is the narrower fact the kind was always about: the edited lines were rewritten by >= kShortHorizonMinCommits (2) COMMITTED commits inside the window, the working edit never counted. That number falls out of the blame this pass ALREADY spawns — gitBlameRangeHasWindowCommit answered a bool and short-circuited at the first hot line; it now collects the distinct in-window commit shas over the same ranges, and churnEditWindowCommitCount unions them across a symbol's hunks. No new subprocess, one extra pass over blame output already being read. Blame runs on HEAD, so "not counting the working edit" is by construction, not by subtraction. WORKING-TREE REPLAY — 12 landed commits (bc517e0 03ec6f1 dfcb57b e5f9bff 65d98b7 4ee920e d7873f8 b392b29 e5b2ad8 9e9fce1 4c24b8d 7d5dd20), ack-free, labelled with Q1 §2d's rules: | | before | after | | rows | 266 | 266 | | gating rows | 171 | 54 | | of which short-horizon-churn | 135 | 18 | | commits that gate | 12/12 | 9/12 | | gating precision TRUE | 2% | 7% | | gating precision TRUE+chronic | 16% | 50% | | WRONG rows (gating) | 1 | 1 | Every TRUE row survives: 27 gating rows labelled TRUE or TRUE-chronic before, 27 after — 0 lost, 0 demoted (exec/qrep/cmp.py). Rows are unchanged in COUNT: this dial demotes, it never drops, so the churn information stays in the document. GATE (red-first, against the pre-change binary): test/qddialscheck.sh §1 builds one file with two multi-line functions and a history that differs only in how many committed in-window commits wrote the lines the working edit touches — once() blames to one in-window commit plus one backdated out-of-window commit, twice() to two in-window commits. The pre-change binary gates BOTH ("once() must NOT gate" FAILs, "once() should be sev=minor" FAILs); after, once() is reported sev="minor" and twice() still carries gating="1" and still fires exit 2. test/qualitykindscheck.sh §2's fixture had a ONE-LINE hot() rewritten by a single commit, which can never blame more than one commit and so could no longer exercise the gating arm at all; it is now multi-line with two in-window commits on two different lines, and §4a's "genuine thrash stays MAJOR" assertion is inverted with the reason written beside it. qualitysignalcheck, churndecaycheck, churnjoincheck, qchurnmemocheck, mergechurncheck, manifestcheck, gatecountcheck, docscommandscheck, printffmtparitycheck, xmlwellformed: PASS. docs/COMMANDS.md regenerated (docs_commands_build.py) for the one-clause legend change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ures GATE FIRST. The run-copy rewrite of the three emit escapers (rw::escapeXml, rw::appendCdataSafe, rw::jsonesc::escapeInto) is a pure performance change whose whole risk is a byte set that is one member short — output that still LOOKS like text, with a raw '<' where an entity belonged. Nothing in a golden map exercises that: an escaper is only interesting on the bytes a repo does not normally hold. So the comparison arrives first, against the CURRENT per-byte loops, where it passes trivially — and any later divergence is the rewrite's, not the gate's. test/emitescape_harness.cpp freezes the three per-byte loops verbatim as *Ref and asserts byte-identity over 222,682 adversarial inputs: all 256 byte values alone and concatenated; a special byte at EVERY offset of a filler run up to two 32-byte AVX2 blocks (the block-boundary sweep a SIMD run loop plus its scalar tail must survive); overlong 2/3/4-byte forms, UTF-16 surrogate halves, >U+10FFFF, sequences truncated at end-of-buffer, a lone continuation byte as the final byte, a BOM; "]]>" at start/middle/end, "]]]]>", a trailing "]]"; all eight escapeInto flag combinations; and 200k deterministic fuzz strings over an alphabet biased to the special set. It also re-asserts §B12.7's scrub-disclosure predicate against what the escapers actually do, since xmlScrubIsLossy classifies the same byte classes the run loop will skip in bulk. CAN-GO-RED, proved not asserted: the same harness rebuilt with -DEMITESCAPE_MUTATE_BYTESET=1 adds a byteset with '<' DROPPED and requires it to DISAGREE with the reference — 168,423 of 222,682 inputs differ. A comparison blind to a missing set member would report zero and this arm would fail, which is what makes the first arm worth anything. End to end, in a temp dir (never inside the repo): a fixture whose doc-comment carries every byte 0x01..0xFF except '\n' goes through --for (escapeXml, entities + 	/ + the invalid-UTF-8 '?' scrub), --expand (appendCdataSafe, including the ]]> split), and their --json twins (escapeInto). XML piped through `xmllint --noout`; JSON through python3's parser. Each arm first proves the byte soup actually reached the output, so a fixture that silently stopped being ingested cannot pass by emitting nothing. Registered in test/regression.sh's absorb loop; count regenerated by docs/gatecount_build.py (586 -> 587, 8 marked sites). manifestcheck, gatecountcheck, gateexitcheck, shellgateindexcheck, binoverridecheck all green (binoverride sees the new gate among the 556 that go red against the sentinel).
…nd its own output was in the corpus
Six defects in one instrument, every one of them the shape the instrument was built to prevent.
Arms A-F of test/capsweepcheck.sh are source-level and NEVER execute a corpus row, which is exactly
how all six shipped inside the phase they were meant to guard.
1. shlex.split RAISES ValueError on the two corpus rows carrying an unbalanced quote, and run_corpus
caught only TimeoutExpired — so the fix that rescued 37 mangled rows turned `screen` into a hard
crash. Now recorded UNPARSEABLE. The handler is spelled `as parseErr`: the child environment two
lines above is named `e`, Python DELETES an except-name at block end, and `except ValueError as e`
kills the NEXT row with UnboundLocalError.
2. run_corpus recorded len(stdout) and discarded returncode, so a REFUSAL (exit 1, no output) and an
ANSWER OF NOTHING were the same measurement. 57 zero-byte baselines sat in the record with no way
to classify them. Every row now carries a state (ok / rc=N / unparseable / unexpanded / timeout)
and only `ok` carries a byte count.
3. A run in which NOTHING answered printed `cap-sensitive: 0 (0%)` and exited 0. It now refuses to
report a split or write records.
4. The denominator counted 56 rows that emit nothing at all. It is now the ANSWERING rows, and the
TSV records carry the recipe: a row that emits nothing cannot respond to a cap.
5. os.path.expandvars reads os.environ, not the dict run_corpus builds. With RIPWIRE_CAPSWEEP_TMP
unset in the operator's shell — the normal case — nine rows ran with the LITERAL string, and
--cache=/--export=/--html= wrote it as a relative path INSIDE the frozen corpus: a 10.4 MB cache
blob that --batch= then read back, "responding" to 103 of 108 caps. Expansion now reads the child
env, an UNDEFINED variable is refused rather than passed through, and the destination must resolve
outside the corpus.
6. `git archive HEAD` leaves no .git, so every git verb measured its degraded path — while the
RECORDED run measured a foreign repository, because ripwire walks up for .git in its own code
(src/gitmine.h:2792, src/ingest_crawl.h:929): the record has --stray-content=lane/ --plan at
11,670,369 B on a corpus with no branches at all. Two guards, because one is not enough:
assert_corpus_clean now refuses a corpus with a git repo in any STRICT ancestor (ripwire honours
no ceiling variable), and run_corpus sets GIT_CEILING_DIRECTORIES for the git processes ripwire
spawns. The corpus then gets its OWN tiny history — three commits over the same four markdown
files plus a dirty working tree — so the git verbs measure their real path:
verb git archive HEAD + 3-commit fixture
. --handoff 2,476 B 3,918 B (changed=0 -> real <f> rows)
. --cochange 0 B (rc=1) 2,937 B
. --situ 0 B (rc=1) 1,705 B
. --pr-context 150 B 7,947 B
. --quality-delta 0 B (rc=1) 7,255 B
. --merge-scout=HEAD~2,HEAD~1 0 B (rc=1) 3,299 B
. --dmm 0 B (rc=1) 3,045 B
The fixture touches four markdown files with a comment line, so it adds no symbol to the map. It
does NOT exercise per-file symbol caps — one appended line is one changed symbol — and the
docstring says so rather than letting a silence be read as evidence.
Plus the guard for the class rather than the instance: a file-list fingerprint of the corpus taken
after freeze_corpus and re-checked after EVERY arm, aborting with the path. assert_corpus_clean
guards one hardcoded directory name; defect 5 arrived through a name it could never have known.
GATE. New arms G-M in test/capsweepcheck.sh drive the production screen_core through two new phases
(`run-corpus` against a stub binary over a six-row synthetic corpus, `plant-history` against a
synthetic tree) — still no build. Each was proven red by mutating the fix it covers:
mutation arm that reds
no ValueError handler (G) run-corpus failed on the synthetic corpus
refusal recorded as len(stdout) (H) the refusing row was not recorded as a distinct state
no green-while-inert refusal (I) control: the inert run failed for the wrong reason
denominator is every row (I) the split was not reported over the answering rows
os.path.expandvars (G) a row wrote into the corpus and the fingerprint fired
no fingerprint re-check (both sites) (K) a row that wrote a file into the corpus was measured anyway
no ancestor scan (L) a corpus with a git repository ABOVE it was ACCEPTED
fixture files absent (M) control: an empty history was planted silently
docs/TUNING.md is NOT regenerated here: the census and the values must settle first, and `emit`
refuses a sweep whose measured cap value no longer matches src/. The re-run lands at the end of this
branch, with the split before and after.
`isDeadCandidate` answered false for any symbol whose file ended .h/.hpp/.hh/.hxx — "header-exported by convention". On a header-only C++ codebase that is not a filter, it is a blindfold: 153,650 of this repo's 158,700 src LOC live in headers, so 96.8% of the source was invisible to the kind, and audit lane Q1's synthetic S6 (delete the sole caller of a header function) was silently missed. Meanwhile the 3.2% it could see produced ten rows across 40 replayed commits and ALL TEN were wrong for one reason: they named symbols the LANGUAGE invokes — `operator new`/`delete`/`delete[]`, constructors, a functor's `operator()`, a bare type — for which zero in-edges in a name-based call graph is evidence of nothing at all. The proxy is replaced by the rule it stood for. languageInvokedSymbol excludes a type (never called), `main` (the runtime calls it), `operator...` (invoked by the operator's own syntax), a leading-tilde destructor, a member sharing its type's name (a constructor in every language that spells one that way), a Python dunder, and a Method named init/deinit/constructor. Each clause names a call site the parser cannot see as a CALL, and the rule errs toward false-LIVE, the only safe direction for a deletion candidate. REF-PAIR REPLAY — 40 commits, ack-free root: | | before | after | | rows | 259 | 251 | | dead-code rows | 10 | 2 | | gating rows | 69 | 69 | | commits that gate | 20/40 | 20/40 | | gating precision TRUE | 10% | 10% | | gating precision TRUE+chronic| 71% | 71% | All ten previous dead-code rows are gone (four test-harness constructors, one bare type, five operator new/delete pairs). The two that survive are the `infra::sort::stable` overloads at 0841640 — the two Q1 labelled genuinely uncalled. Working-tree replay is unchanged (266 rows / 54 gating / 9 of 12): the kind produced zero rows there before and after. 27 of 27 TRUE-or-chronic gating rows survive in the working-tree population, 49 of 49 in the ref-pair one, 0 lost, 0 demoted. RECALL: synthetic S6 turns from a silent miss into a reported row — test/qddialscheck.sh §2 deletes the sole caller of a header function and asserts the row, beside the opposite arm that a brand-new type's ctor, dtor and operator produce NO row. Both are RED on the pre-change binary and for opposite reasons: it misses usedHelper entirely, and it reports three Extra:: rows a .cpp made visible to it. kQSnapCacheScheme 8 -> 9. The dead SET changed meaning in BOTH directions, and the direction is what makes the bump load-bearing: a v8 blob's dead set was computed while the header population was invisible, so served to this binary every newly-eligible dead symbol would read as absent from the baseline and be reported as freshly dead — a tree of phantom regressions on the first run after an upgrade. No extraction change, so kParserVer and its mirrors deliberately did not move. deadcheck, deadfiltercheck, deadprecisioncheck (the --dead-code verb runs through deadCodeEligibleKind and is untouched), registermacrocheck, qsnapcachecheck, qextractionkeycheck, qualitycheck, safedeletecheck, qddialscheck: PASS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…equires
`does` was a symbol-slot cue AND `how does` is the understand-symbol gate, so every English
question of the form "how does <indexed-word> …?" minted the very symbol the gate then demanded —
the same two words playing both parts. 13 of 25 adversarial prose prompts recommended
`--expand=<English word>`, and the Codex UserPromptSubmit hook injects that answer into a live
session at confidence="high" (2026-09-10 audit F-R1-01/04). Six of those thirteen names existed
ONLY as t="sec" rows — JSON keys and markdown headings — so `--expand='version'` answered with
`"version": "1.2.3"` out of a package.json at exit 0 with no disclosure (F-R1-02).
Two rules, one invariant each:
cueOccurrenceIsIntentGate — an intent word is evidence about what the user WANTS; it may never
double as the positional evidence that they NAMED something. The cue OCCURRENCE that satisfies
the gate is disqualified, never the word, so a later independent cue in the same task still
resolves the name.
weakEvidenceKind — a weak (all-lowercase, cue-positioned) reading must be backed by a non-Section
definition. Identifier-shaped mentions are untouched: there the SHAPE is the evidence. Rank is
deliberately not part of the test — k is 0.0000 for nearly every row of any large corpus, so
gating on it would make resolution depend on corpus size.
WHY THE CORPUS SAID harmful=0.000: bench/taskroute_eval.py::make_repo built a fixture repo whose
every symbol was camelCase or Pascal. The weak tier only fires on all-lowercase names, so no row
could reach it — the class was invisible by construction. The fixture repo now carries both halves
of the collision class (nine lowercase code definitions; a package.json whose keys index as
t="sec"), and test/taskroutecheck.sh's repo carries the same.
pre-change binary -> post-change binary, same corpus, same day
| set | rows | metric | before | after |
| audit set A (08-28 shape) | 25 | false recommends | 0 | 0 |
| audit set B (word after cue) | 25 | false recommends | 13 | 0 |
| prompts.tsv test | 89 | precision/harmful/neg-spec | .797/.135/.657 | 1.000/.000/1.000|
| prompts.tsv test | 89 | accuracy/coverage | .787/.870 | .921/.870 |
| prompts.tsv dev | 100 | precision/harmful/neg-spec | — | 1.000/.000/1.000|
| prompts.tsv dev | 100 | accuracy/coverage | — | .940/.920 |
| prompts.tsv all | 189 | precision/harmful | .879/.085 | 1.000/.000 |
| 158 pre-existing rows | 158 | (status,intent,resolved) diff | — | 0 differing |
RED-FIRST: four new taskroutecheck arms fail against the pre-change binary (each recommended
understand-symbol with an --expand), and `bench/taskroute_eval.py --split test` EXITS 1 on the grown
corpus (precision under the 0.90 floor, harm over 0.02, specificity under 0.90). The extended
fixture repo alone changes nothing: all 158 pre-existing rows are byte-identical on
(status, intent, resolved_symbols) across it, and again across the code change.
COVERAGE COST, named: exactly one shape is given up — the bare "How does <lowercase-name> work?"
spelling now abstains, and the gate arm for it is inverted into an assertion of the new invariant.
The same weak name still routes to --expand through any cue the gate does not consume ("the
implementation of classify"), which is what keeps this a rule about self-confirmation rather than a
retreat from the weak tier. No corpus row lost its route: every confusion line on both splits is
identical to the pre-round run.
Map output is untouched — default map, --for, --grep and --pack-task byte-identical between the two
binaries; taskroute.h is included by exactly one translation unit. Determinism and xmllint clean.
Corpus +31 rows (25 audit-set-B negatives verbatim as evidence, 3 kind-only negatives, 3 positives
that hold the recall), seal 25283f2eba85aad889fe3746308df76ed8b1244529f44986c936eb6ef60b0b53,
screen flags 1 pre-existing + 1 new (a negative row carrying a live cue phrase — adversarial
pressure, not self-quotation), both stated in PROVENANCE.md rather than reworded away.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ape of one habit, not of a population
docs/LIMITS.md opens "Every compile-time cap in `src/`". docs/limits_build.py's DECL required the
literal `inline constexpr` with the value on the SAME line, and KEY named a cap by keyword. Between
them, 92 declarations under 81 distinct names were invisible to a register every cap round has been
run against. `inline` is optional at namespace scope and FORBIDDEN on a class member, so
"inline constexpr" was never the shape of the population.
census before after
cap declarations parsed 120 212
distinct names 119 200
caps (truncating) 114 205
ranking parameters 6 7
classified in the sidecar 26 108
What was outside it, with the verb each reaches:
kType3MaxBucket / kType3MaxTokensForLcs src/clones.h bound clone DETECTION — clone_groups and
dup_pct are floors, and nothing said so
kSkillScanFindingCap src/skillscan.h bounds a SECURITY verdict (--scan-skills)
kHandoffSymbolsPerFile src/handoff.h truncates output and DISCLOSES syms_capped
kMaxFlipRows / kMaxNearMisses src/flipimpact.h a file that emits no disclosure at all
kMaxSitesShown src/darkflags.h likewise
kChaConeCap src/graph.h the CHA cone memo behind --grep
kMaxAnchorsShown src/docdrift.h --doc-drift anchors
kRenameMaxPairs / kRenameMaxChain src/quality.h --quality-delta's rename-ack window
kSituBlastFilesShown src/situ.h the "showing 8 of 69 files" in --situ
DECL now accepts `(?:static\s+)?(?:inline\s+)?constexpr` and a wrapped initializer (the scan moved to
the file text with re.M); KEY gained `Shown|PerFile|Hits`, which is what made kHandoffSymbolsPerFile
invisible to this register AND to docs/TUNING.md simultaneously.
A THIRD CLASS, named in review on #108. `kUnitSizeLowRiskMax = 15` decides which SIDE of a rule a unit
falls on; `kMaxNameLen = 96` decides that a 97-character backticked token is a sentence, not an
identifier; `kMaxPartitions = 16` bounds a hand-written `--partition=N`. None truncates anything, so
none can be judged by shown/total and none should ever emit `capped="1"` — labelling them OUTPUT would
ask for a disclosure that could never honestly fire. BOUNDARY is now a value docs/limits_classes.tsv
accepts, and 25 rows carry it. 82 new rows were classified by reading the call site, not the name:
27 INDEXING, 24 OUTPUT, 25 BOUNDARY (plus 6 pre-existing rows re-tagged BOUNDARY). Nine constants I
could not classify honestly render `—`, which the document defines as NOT YET CLASSIFIED, never
"neither": kMaxGitWorkers is a worker count, kCapPerThread a batch size, kCap a buffer.
`kMaxCacheBlobAgeDays` (30.0) and `kRadixThreshold` would have landed in the "ranking and apportionment
parameters" table — the first because it is fractional, the second because its name says Threshold —
and each would have rendered **unsourced**, which is a false claim about both. Thirty days is not a
proportion and a radix cutover apportions nothing. NOT_A_WEIGHT keeps them in the cap table as
BOUNDARY.
GATE. New arm (H) in test/limitstablecheck.sh plants a plain `constexpr`, a `static constexpr` member
and a wrapped initializer — each alone in a synthetic --root tree, each required to appear, with a
non-cap constant beside it required NOT to — plus a control that the NAME filter admits `*PerFile`.
Red-proven by reverting the generator:
mutation result
DECL back to `inline constexpr`, same line (H) plain/static/wrapped all red + (B) 68 sidecar rows
name a cap the register can no longer see
KEY without Shown|PerFile|Hits (H) control red + (B) 7 sidecar rows orphaned
Arm (G) reads the class cell back out of the rendered markdown and its vocabulary now includes
BOUNDARY, so a class cannot be right in the sidecar and wrong on the page.
docs/EVALS.md carried "the 114 caps in src/ … the 120 cap-shaped constants": corrected, with the
recipe and the reason the old number was wrong, because a count published without its recipe is one
list counted four defensible ways.
…e exactly, load-bearing differentially #112 restored four frontmatter STOP RULES and no row in test/skillevalfix/prompts.tsv could see any of them: stripping all four left split=test bm25-desc hit@1 byte-identical at 63.8% and split=dev 1.4pp BETTER, with skillevalcheck 15/15 green either way (2026-09-10 audit F-R1-03). That is the same failure #112 itself repaired — the fix restored the TEXT without adding a MEASUREMENT. The new stop-rule arm asserts two different things, because a stop rule can fail two ways: PRESENCE, exact — each sentence pinned in the gate verbatim; the arm's strip must actually remove it from that skill's SKILL.md, so a rewrite that drops OR REWORDS a rule makes the strip a no-op and the gate names which rule and stops. Words matched exactly, whitespace as \s+ (frontmatter folds; where the wrap falls is formatting, not the thing measured). LOAD-BEARING, differential — the 16 stop-rule rows scored against skills/ and against a stripped copy the gate builds itself; the real tree must win by >= 12.5pp. bm25-desc hit@1 with rules stripped the 16 stop-rule rows 75.0% 50.0% - the 8 that echo the rules (provenance=desc) 100.0% 50.0% - the 8 written to AVOID them (judged) 50.0% 50.0% whole corpus split=dev (n=99) 76.2% 72.6% whole corpus split=test (n=183, FROZEN) 63.8% 63.8% THE NULL IS REPORTED, NOT BURIED. The audit proposed rows "phrased without quoting it". Eight were written and measured: zero discrimination. A BM25 arm scores description TEXT, so it can only detect a sentence's removal through rows that share that sentence's words — "phrase it without quoting the rule" is not available to this instrument, and the exact-PRESENCE assertion is what covers what a lexical corpus cannot. Those 8 are kept as ordinary hard judged rows (4/8 route correctly; misses go to find-bug, write-tests, handoff, navigate). RED-FIRST: against a skills tree with the four sentences mechanically stripped, six arms fail (four PRESENCE, the absolute floor, the differential) while ALL 15 pre-existing arms still pass — precisely the F-R1-03 finding, now closed by construction. Corpus +16 rows, all split=dev by the header's own rule that the test split is FROZEN; skillevalsplitcheck confirms split=test hit@1 unchanged at 63.8%. Rows ASCII per the corpus rule. Seal 74953fd1a5e494f2805cb9c51cc828d59a4bf1f2c24912a069e1acdd2fa2a8ba (266 -> 282 rows). FLOORS NOT MOVED (a floor move is a deliberate recalibration commit). Slack as measured now: test hit@1 +11.8pp over floor 52.0, test sep-auc +0.071 over 0.83, dev hit@1 +17.2pp over floor 59.0, dev sep-auc +0.176 over 0.75. The dev pair is outside this gate file's own stated ~10pp / ~0.06-0.07 policy and is left as a NAMED owner decision (audit F-R1-10). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…4 -> 170.46 s CPU, map byte-identical
collectPreprocDeadRanges (src/preprocdead.h) read its children with ts_node_child( n, i ), which
restarts tree-sitter's child iterator from the first child on every call, so the walk was O(C^2) in
a node's child count — audit P1-0, the highest finding of the 2026-09-10 round. The repo already
states that rule and ships the cursor helper for it, but the helper lived inside ingest.cpp's unnamed
namespace (ingest_metrics.h) where preprocdead.h — compiled into slice.h too — could not reach it.
So the helper moves to src/infra/tschildren.h, gains an appendChildren form for the DFS-stack case,
and the walk uses it. Same child set, same left-to-right collection order, same reverse-child visit
order, same emitted ranges.
Why it hid: preprocdead.h short-circuits on src.find("#if") == npos, so Go/Python/JS corpora never
enter the walk and test/padscalecheck.sh's comment-flood fixture (which has no #if) cannot reach it.
Every C/C++ INCLUDE GUARD opens that gate and makes the guard's preproc_ifdef node one node whose
child list is the whole file.
A/B, one interleaved cold pair, llvm-project (2.9 GB, 182k files), same box, load ~12:
arm CPU (user+sys) wall max RSS map
pre 202.14 s 26.60 s 6.16 GB 43485 B
new 170.46 s 18.99 s 6.23 GB 43485 B byte-identical
delta -15.7% -28.6%
Leaf attribution, 12 s sample of a cold llvm run (audit pre-figures vs this lane's post-figures):
ts_node_child_iterator_next 62.99% -> 13.44% of busy
collectPreprocDeadRanges (incl.) 56.67% -> 2.07% of busy
The 31.7 s realised is short of the 107 s the 56.67% share implies: that share was read from a 12 s
window of a 26 s run, and a leaf share is not a whole-run share. The A/B is the number to believe.
Generated fixture (include guard + N line comments + an #if 0/#else pair), pre binary:
N pre CPU new CPU
1000 0.01 s 0.01 s
4000 0.09 s 0.01 s
16000 1.24 s 0.01 s 124x
Isolating control — the identical 16000-line flood with the guard REMOVED (no #if text, so the walk
never runs) costs 0.02 s on the pre binary, i.e. the whole 1.24 s was this one loop.
Narrow-tree corpora are inside the box's noise band, in both directions: paired interleaved cold runs
gave ripwire's own tree +2.8% median (20 pairs) and the go corpus -2.1% median (12 pairs) — and go
contains no #if at all, so the walk provably never runs there. No claim is made either way.
Non-degradation — every one byte-identical, pre binary vs new, --no-cache:
corpus map(--top-k=100000) --for --grep --pack-task --dead-code --lint
ripwire (this tree) ok ok ok ok ok ok
go (227 MB) ok ok ok ok ok ok
canyonraid48/canyon (C++) ok ok ok ok ok ok
llvm-project (map) ok
Plus determinism (two cold runs cmp-equal) and xmllint --noout on the map.
Gate (written first, red before the code): test/preprocdeadscalecheck.sh. Arms (B) 62.0x CPU for 16x
the child width and (C) an include guard costing 62.0x the identical unguarded flood both FAILED
against the pre-change binary and PASS after; (A) asserts the dead-range set through --uses on the
flooded fixture (both halves: the #if 0 call absent, the live call present) plus determinism;
(D1)/(D2) hold byte-identity against RIPWIRE_REF_BIN on the generated fixtures and on five committed
C/C++ fixture trees; (E) shows every verdict and row reader able to fail. Its header carries the
FOLLOW-UP enumeration of the 52 surviving indexed ts_node_child( n, i ) sites in three trip classes —
10 unbounded (class 1, the same defect), 5 input-controlled but small (class 2), ~37 grammar-bounded
and correct as written (class 3). Not converted here: they live in files other lanes are editing.
Verified: preprocdeadscalecheck, padscalecheck, preproccondcheck, blindspotcheck, slicecheck,
includeprecisecheck, rustimportprecisecheck, loopconservationcheck, readmedriftcheck,
cacheidentitycheck, limitstablecheck, infraportcheck, includeanglecheck, selfcontainedcheck,
shellgateindexcheck, binoverridecheck, nodekindcheck, xmlwellformed, manifestcheck, gatecountcheck —
all ALL PASS. ASan (-fno-sanitize-recover=all, LSan with the committed suppressions) clean on the
generated fixtures, test/preproccondfix and ripwire's own tree, and the gate passes under it — the
cursor's ts_tree_cursor_delete lifetime is RAII-held by ChildCursor. --quality-delta gating=0
(5 minor new-symbol api-surface rows: the moved helper is now a named rw:: symbol instead of an
internal-linkage one — my footprint, deliberately not acked). Gate count regenerated to 587 by
docs/gatecount_build.py, never hand-written.
…AST node
`ts_node_child_by_field_name( n, "name", 4 )` — 199 sites across the ingest walk sections,
--slice and the preprocessor reader — does not look a field up. It looks the field's NAME up
first, by a linear `strncmp` scan over the grammar's whole field table
(third_party/.../language.c:226), and then calls `ts_node_child_by_field_id` with the answer
(node.c:773). The answer is a pure function of ( grammar, field name ) and never changes, so
this is loop-invariant work recomputed per AST node — and it is OPTREMARKS F3's defect one
layer down: `strncmp` is an EXTERNAL libc symbol, so LTO cannot reach it, and on macOS each
comparison goes through DYLD-STUB$$strncmp then DYLD-STUB$$_platform_strncmp first.
MEASURED BEFORE (1 ms `sample`, go corpus cold --no-cache, 18,963 busy leaf samples):
`strncmp` + both dyld stubs 3.44% of busy; the `ts_node_child_by_field_name` subtree 4.70%,
of which 93.6% is owned by ONE caller — cc_boolOp, which cc_walk asks twice per AST node.
src/infra/fieldid.h resolves all 41 field spellings once per grammar into a [grammar][field]
TSFieldId table at the ingest prewarm (warmFieldIdTable(), beside the compiled-query prewarm
and under the same single-writer / lock-free-reader invariant), and `fieldChild( n,
NodeField::Name )` reads it. An unwarmed grammar falls back to resolving by name: today's path,
today's answer, today's cost — never a wrong node.
A/B — CPU (user+sys via rusage), interleaved with the arms swapped at the half, one unrecorded
warm-up per arm, `--no-cache --top-k=100000`:
| corpus | n | A median | B median | delta median | delta min | B.med<A.min | load |
| --------------------- | -- | -------- | -------- | ------------ | --------- | ----------- | ---- |
| go (15,865 files) | 6 | 18.214 s | 16.609 s | -8.81% | -7.76% | yes | 16.8 |
| go (15,865 files) | 12 | 19.177 s | 17.858 s | -6.88% | +1.21% | no | 32.7 |
| rocksdb | 12 | 8.204 s | 7.762 s | -5.40% | -6.06% | yes | 34.9 |
| ripwire tree | 12 | 4.541 s | 4.486 s | -1.21% | -1.47% | no | 31.7 |
The two go rows are the same arms at two machine loads; the 12-run pair ran with five other
lanes on the box (load 33) and its min is noise, which is why the quiet 6-run pair is printed
beside it rather than instead of it. The ripwire tree's smaller win is honest and the sampler
says why: that corpus's own strncmp share is 2.56% of busy, not go's 3.44% — it is
markdown-heavy, so less of it is AST walk.
BYTE-IDENTICAL, 22 of 22 arms, three corpora (go / rocksdb / a pristine ripwire tree), seven
verbs: default map --top-k=100000, --lint, --grep, --for, --pack-task, --match, --clones,
--hotspots, --slice, --uses. go --top-k=100000 = 10,415,057 B, matching the figure the audit
round recorded. Determinism: two runs byte-identical; `xmllint --noout` clean.
GATE FIRST — test/fieldidcheck.sh, seven arms, all with can-go-red controls:
A0 the UNWARMED lookup equals ts_language_field_id_for_name (943 pairs) — "a missed warm is
slower, never wrong" is tested, not asserted in a comment.
A the warm table equals it too, over 23 grammars x 41 fields harvested FROM THE TREE
(kLangTable and the header, at gate time — not a list frozen in the gate).
B enumerated pairs: 1,189,205 ( node, field ) comparisons over 29,005 nodes of 32 fixture
files, fieldChild vs ts_node_child_by_field_name, node-for-node.
C unknown-field parity, and it is NOT vacuous: 583 of the 943 grammar-field pairs resolve
to id 0, and ts_node_child_by_field_id( n, 0 ) is the null node by node.c:602.
D1/D2 mutation: misspelling one row of kNodeFieldNames (the data) and shifting the field index
inside the lookup (the code) each turn arms A/B red — 677 and 21,465 disagreements.
E/E-warm population: zero ts_node_child_by_field_name left, 199 fieldChild sites, the warm
wired over kLangTable and called from ingest(); both with revert controls that prove the
arms are not green in both directions. Arm E reads SOURCE, and says so.
F capacity — now also a static_assert in ingest_crawl.h, so a 65th language is a compile
error rather than a run that silently keeps the by-name path.
Also here, because --quality-delta found it as a duplication the moment the two call sites
normalised alike: the `spelling.empty() ?` guard in preprocIncludeTarget/preprocImportTarget was
dead — includePathOf( "" ) already returns "" through its own size < 2 arm and leaves isAngleOut
alone. Removing it clears the one new gating clone pair the conversion introduced.
--quality-delta after: 97 gating rows, 96 of them short-horizon-churn "self" on the nine files a
199-site mechanical conversion necessarily touches (the change's own footprint, unavoidable and
not acked), and one api-surface contract-change — nodeFieldText losing its fieldLen parameter,
4 params to 3, which is the point. --test-gate: exit 4 as any change gives; the 21 named tests
plus the slice/preproc family were run and are green.
…at changed — 6 becomes 50 code / 12 prose
kHandoffSymbolsPerFile = 6 cut the DISK-TRUTH half of the packet, the section whose whole contract is
"this is what the change set is", and it fired on the typical case rather than a tail. Measured over
12 real commits replayed as working-tree diffs (39 changed files, 1,406 symbols): the cap fired on
27 of 39 files — 69% — and per-commit containment ran 6.6% / 8.4% / 10.0% / 14.6% / 17.8% / 21.6% /
46.2% / 79.2% / 80.0% / 100%.
TWO CAPS, NOT ONE. A single number is decided by the wrong population. 26 of those 39 files were
markdown carrying 1,009 of the 1,406 symbols (72%), and the whole heavy tail is documentation:
docs/EVALS.md 217 sections, docs/LIMITS.md 55, README.md 35. Code: p50=21, p90=57, tail
src/mcpverbs.h 104. A uniform 50 costs +51.4% bytes for 59.0% containment; the split buys more of the
half that matters for a quarter of the price, because a continuation packet wants a document's NAME
and first sections, not its table of contents.
kHandoffSymbolsPerCodeFile 6 -> 50 just under the code p90 of 57
kHandoffSymbolsPerDocFile 6 -> 12
A/B, both binaries built from this tree, 37 real commits of this repository replayed as working-tree
diffs (72 changed files, 2,179 symbols):
cap 6 50/12
symbols shown 354 1175 of 2179 that exist
containment 16.2% 53.9%
files cut 51 15 of 72
bytes 121,043 135,434 (+14,391, +11.9%; +388 B per handoff)
a narrower, code-heavy window (13 commits / 23 files): 32.4% -> 81.4%, +191 B per handoff (+6.0%)
PURELY ADDITIVE, which is the owner's bar for a raise: over both windows, 0 <f> rows removed, 0 <s>
rows replaced (every old row is a prefix of the new one), 0 non-disclosure attribute changes. The only
attribute movement is syms_capped/syms_total RETIRING as the cap stops firing, which is the disclosure
behaving correctly.
--token-budget is unaffected in mechanism and honest in its labels: on the same diff the verified floor
grows 1360 -> 1417 est_tokens, --token-budget=3000 is still honoured, and --token-budget=500 still
reports over_ceiling="1" with the verified rows intact.
The legend clause is now FORMATTED from the two constants instead of carrying the literal "6" in its
sentence — it had already gone stale once by construction, and a legend naming a value it does not
read is the one-number-in-six-artifacts shape.
GATE. test/tracehandoffcapcheck.sh arm (B) sized its fixture with a literal 12-against-6: at a cap of
50 that fixture tests NOTHING and still prints PASS. It now reads both caps out of src/handoff.h and
computes four files from them — wide.c at cap+10 functions, narrow.c at 3, wide.md at cap+10 sections,
narrow.md at 2 — so the fixture can never fall back under the cap it is checking. Six assertions
became seven: crossing/disclosure/silence for EACH cap, plus B7, that the two caps differ and the
prose file is cut at the smaller one — the one assertion a build ignoring the split cannot satisfy.
Red-proven against the pre-change binary (RIPWIRE_BIN=<cap-6 build>):
FAIL B1 crossing: packet lists 6 of the map's 61 symbols in wide.c (cap 50)
FAIL B4 crossing: packet lists 6 of the map's 25 symbols in wide.md (cap 12)
FAIL B7 split: the two caps differ (code 50, prose 12) …
docs/limits_build.py's NAME filter reads `Per\w*File`, not the compound `PerFile`: a filter that turns
on an exact spelling is the defect the widening exists to remove, not a smaller instance of it.
docs/LIMITS.md and docs/limits_classes.tsv regenerated; both new caps classified OUTPUT.
Green: tracehandoffcapcheck, handoffcheck, budgetpolicycheck, emittertruthcheck, printffmtparitycheck
(42 verbs byte-identical), limitstablecheck.
… read, all off the per-byte path
Three of the audit's ranked findings, in the one place they all live — the pass-2 BM25 scan, which
is 81.7% of `--pack-task`'s busy time and reads every doc-comment and body byte of the corpus.
S1/S2 — THE TOKENIZER (src/lexindex.h). forEachLexSubtoken and forEachLexSubtokenHashed were TWO
hand-kept copies of one per-byte state machine (the shape the file's own header warns about: the
2026-08-19 acronym bug lived in one copy and not the other). Both are now thin callers of a single
block walk over rw::strkern::classMasks, 16 bytes at a time on NEON and 32 on AVX2. The boundary
rule is unchanged and is now stated as mask algebra, derived in the header and PROVEN by gate:
inToken(k) == A[k-1] (the walker sets a start at every alnum byte and
clears it at every separator, so "in a token"
IS "the previous byte was alnum" — and prevUpper
is U[k-1] wherever A[k-1] already gates the term)
split = U & (A<<1) & ( ~(U<<1) | (L>>1) )
starts = ( A & ~(A<<1) ) | split
cuts = starts | ~A a token runs from each start to the next cut
Three carries cross each block seam (A and U of the byte before, L of the byte after) and a
`pending` start carries a token across any number of blocks. lexUpperOpensToken is gone from
shipped code: the algebra is the rule now, and a second statement of it is the drift risk.
The fused rolling hash is gone with the second state machine; the hash runs over the token's
bytes after its span is known, with the branchless S2 fold `c | ( ( c & 0x40 ) >> 1 )` — EXACT
for [A-Za-z0-9] (digits carry no 0x40 bit) and only for it, which is why lexSubtokenHash keeps
the general range-tested form for callers that have not classified their bytes.
P2-3 — THE MATCH LOOP (src/lexical.h). The scan ran the WHOLE match table for every corpus
subtoken: linear in a table that grows with the query and doubles again under RIPWIRE_QSTEM.
LexHeadIndex precomputes, once per query, a 256-bit set of the table's head bytes (a
strkern::Byteset256 — the type the header already owns, not a second bitmap) and the rows
bucketed by length, CSR-style. A corpus token whose head is absent touches no string at all.
The surviving predicate is character for character the original one — length, head, memcmp fast
path, lexTokenEqualsLowered acronym fallback, ascending m — so byte-identity is structural.
P2-4 — THE FILE READ (src/lexical.h). lexicalScanText read every file through ifstream +
ostringstream << rdbuf() + str(): two full copies per file. Now docparse::detail::readWholeFile,
the documented never-re-roll helper, whose clear-on-failure is exactly the empty-string
"skip this file" contract that was already there. <fstream> and <sstream> leave the header with it.
THE OTHER EIGHT rdbuf() SITES, audited, not converted (none is on a per-byte or per-file hot
path; each reads ONE file, once, per invocation):
src/recall.h:74 the maxBytes==0 arm of a bounded reader whose other arm needs the stream
src/recall.h:593 one --recall doc body
src/recall.h:1238 one section-granular body
src/eval.h:233 one eval fixture, harness-only
src/eval.h:612 one symbol's file during eval scoring, harness-only
src/skilleval.h:74 one SKILL.md
src/skillscan.h:757 one .mcp.json
src/skillscan.h:797 one skill manifest
NON-DEGRADATION. 18/18 outputs byte-identical, before vs after, over three corpora (a frozen
git-archive of this tree, the go corpus, rocksdb) x six verbs (--top-k=100000 default map,
--for conceptual, --for name-exact, --grep, --pack-task, --recall). Determinism (two runs cmp)
and xmllint clean. Gates green: strkerncheck (13 arms, NEON + the AVX2 mirror under Rosetta 2,
mutation reds 10 arms), subtokencheck, postingscheck, bm25check, querycheck, recallparitycheck,
xmlwellformed, plus every test --test-gate named (adaptivecutshape, includeprecise,
rustimportprecise, fixedbufsweep, optremarks).
--quality-delta: 5 gating rows on the first cut, 4 of them fixed rather than acked —
lexicalScoresTiered's complexity (431->434) and verbosity (918->929) by moving the head index and
the match predicate out to namespace scope (both now BELOW baseline), and a duplication pair by
making the head set a strkern::Byteset256 instead of a second hand-rolled bitmap. What remains is
one short-horizon-churn row with churn="self" on lexicalScanText — it says "you edited this
symbol", which no edit can make untrue, and it is the exact labelled-noise class Q1's E11 dial
proposes to stop gating. Not acked: an ack would hide a row that is honest about a real edit.
…he code
Two gates for the 2026-09-10 full audit's P1-1 (highest) and P1-11 (high), written
first and proved red against the pre-change binary.
test/evictioncheck.sh gains three arms for P1-1 — the dir-wide 2 GB oldest-first
sweep evicts the SIBLING FAMILY OF THE ROOT THE USER IS WORKING IN:
(h) two roots, the MRU root's sibling family seeded as the OLDEST blob in the dir:
it must survive and the other root's blob must be what goes, with one
`ripwire: cache …` line on stderr.
(i) the pinned set ALONE over the budget: kept anyway, said once on stderr.
(j) a sweep that evicts nothing: ZERO stderr bytes (the disclosure is conditional,
so no ordinary run and no stderr-comparing gate grows a line).
The arms exercise the REAL 2 GB constant with sparse `truncate -s` fillers, so no
test-only override env var is introduced and the number under test is the shipped
one. Red against 05f4b89's binary:
FAIL (h) the MRU root's sibling family was EVICTED
FAIL (h) the other root's blob survived
FAIL (h) an eviction happened with ZERO disclosure
FAIL (i) the pinned set was evicted when nothing else could be freed
FAIL (i) the pinned set exceeded the budget with no disclosure
... (j) and every pre-existing arm PASS on that same binary
test/cachereservecheck.sh is new: `warm_growths=` on the RIPWIRE_CACHE_STATS line
must be 0 on a single-threaded warm run (one-file Ruby and JS fixtures, so each
family's per-thread reserve IS its exact total), the warm map must be byte-identical
to the --no-cache map, and the observable must stay OFF by default. Red twice: the
pre-change binary emits no `warm_growths=` field at all, and a mutation that deletes
only the four added family reserves reports warm_growths=1 on both fixtures.
Listed in test/regression.sh in this commit; docs/gatecount_build.py regenerated
(586 -> 587) rather than hand-edited.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ys what it took P1-1 (highest, 2026-09-10 full audit). One llvm-project root needs 1.76 GB of cache for its OWN two families — rich 1.19 GB + lean 0.57 GB — against a dir-wide 2 GB oldest-first sweep. Add a second corpus, or one --edit-check HEAD snapshot (0.52 GB), and the sweep deletes the SIBLING FAMILY OF THAT SAME ROOT: the one blob the user is certain to need next. Measured, identical argv, same session, same binary: --grep 20 s -> 206 s, --for 19 s -> 268 s, self-sustaining (each cold run's own save evicts the other family again), and SILENT — all four 250 s runs wrote 0 bytes to stderr. `keepPath` never covered it: the blob being written is the family we are NOT about to need. src/main.cpp:181-189 already carries the same mechanism as a registered negative for a different key change. The budget is NOT lowered (owner: quality-first-caps-are-blowup-guards). The ORDER is what changes, and it needs no new state, no plumbing and no extra stat: every family's filename already carries the same 16-hex fnv1a64(realpath(root)) field — lean/rich from defaultCachePath, qheadsnap/qsnap/qbody/qhist/qms/qchurn/stier from shaKeyedCachePath, because headSnapRepoHex hashes exactly the material defaultCachePath does. cacheBlobRootKey reads it off `keepPath`, so whoever is writing IS the most-recently-used root. The size pass then takes other roots first, oldest-first among them, and the pinned set last; if the pinned set alone still exceeds the budget it is kept, and one plain stderr line says so. Both lines are conditional and are plain emits, never DEGRADED_PATH_ALERT — NDEBUG compiles that out and a Release binary is where a 10x slowdown must be visible. The AGE pass stays unpinned deliberately: 30 days untouched is stale by that policy's own definition and costs one cold parse, not a ping-pong. llvm-project (182,555 files, 2.9 GB), private TMPDIR, real blobs, load 16-21. Same starting cache (rich 1,188,813,175 B), same 600 MB other-root filler, same commands: arm before (05f4b89) after output --grep=SmallVector (cold, sweeps) 231.08 s CPU 230.16 s byte-identical -> rich blob after that sweep EVICTED, 0 B stderr KEPT, 1 line --for="how are pass pipelines 274.00 s CPU 26.42 s byte-identical registered" (the next call) (52.38 s wall) (27.06 s) 10.4x P1-11 (high): the same audit read ~30% of a warm llvm --grep as un-reserved vector growth (RawRef/RawDef/RawBind push_back 21.7% of busy + 8.4% memmove) and proposed reserve() before the loops. Reading the code, TWO of the three named paths already reserve exactly — readFileRecord sizes every one of a file's eight families from the record's own count, and mergeThreadFacts reserves each family's exact cross-thread total. The one real omission was the per-thread warm-hit accumulator: it reserved four families of eight, so ffis/routeDefs/routeUses/constOpens doubled up from zero on every warm run. Fixed (the cached FileFacts carry exact counts; reserve(0) is a no-op, so an empty family costs nothing), and made executable: warm_growths= on the RIPWIRE_CACHE_STATS line counts, once per family per file, an append about to cross capacity. THE 30% READING DOES NOT SURVIVE MEASUREMENT, and is not claimed. Warm llvm --grep, 4 interleaved pairs, load 8-19: A 26.79/26.89/26.62/26.42, B 26.67/26.05/26.50/26.23 s CPU — median 26.71 vs 26.37, inside the noise. The residual push_back/memmove share is real but is NOT reallocation: re-sampled after (8 s at 1 ms, 26,160 samples, ~7,923 busy) RawRef push_back 9.2% / RawDef 2.7% / RawBind 2.3% / memmove 4.5% of busy, with warm_growths=49 across 8 families x 18 workers on 81,440 files — i.e. a few dozen reallocations in the whole run, and the rest is the fast-path element move that a reserve cannot remove. golang/go, 11,003 files warm on 18 threads: 29-36. What is left is queue skew (a worker drawing more than its 1/nthreads share), not a missing reserve; covering it would cost 25% more accumulator memory for an effect inside the noise, so it is not done. NON-DEGRADATION. Byte-identical A vs B, 3 corpora x 4 verbs, 12/12 cmp-clean: ripwire's own tree, golang/go (11,003 files), rocksdb — --top-k=100000, --for=..., --grep=reserve, --pack-task=... Determinism (two runs cmp) and xmllint --noout clean; xmlwellformed ALL PASS. Warm default map A/B on go (10 pairs, load 10-13): 0.725 vs 0.710 s CPU median. go --grep (10 pairs): 2.14 vs 2.135. ripwire tree map: 0.12 both. G1: asan/ripwire (address,undefined,integer + LSan suppressions) clean on a cold map, a warm map, and both gates end to end. Gates: evictioncheck (all arms incl. the three new), cachereservecheck, savecachecheck, cacheoffsetcheck, cachesplitcheck, cachefuzzcheck, cacheidentitycheck, cacheisolationcheck, portablecachecheck, headsnapcachecheck, qsnapcachecheck, racymtimecheck, statgatecheck, tornreadcheck, qextractionkeycheck, qschemetripcheck, printffmtparitycheck, limitstablecheck, manifestcheck, gatecountcheck, binoverridecheck, mcpverbscheck, and the rest of --test-gate's 22 named rows — all PASS. --quality-delta: every complexity/verbosity/params/api-surface regression this change first raised was removed by extracting evictBySizeBudget and markCacheHits/reserveWarmFamilies; what remains is short-horizon-churn on the three functions the change must touch, an artifact of the dirty tree against a git-HEAD baseline that disappears once this lands. Nothing acked. KNOWN GAP, stated rather than left to be found: the pin covers blobs that spell the SAME root key. llvm's qchurn blob keys on a different root spelling (6b73c58ba5897c7a vs 4280d3ca01d82374) and is therefore not pinned — 10 MB there, but a family that ever grows large under a divergent root spelling would still be evictable. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two defects, one commit, because they are the same defect seen from both ends: the kinds judged WHERE a number landed and never HOW MUCH this change added. VERBOSITY COUNTED PHYSICAL LINES. Audit lane Q1 added 60 pure BLANK lines inside an 18-LOC body and got `verbosity was="18" now="78"`, gating, exit 2 — and the identical result for 60 pure COMMENT lines, in a repo whose CONTRIBUTING.md requires the reasoning to be written down. Not hypothetical: landed commit 7d5dd20 ("comment(caps): update three stale cap justifications") added 7 comment lines and 1 code line and produced two verbosity rows. Measured composition of what the kind judges over 60 rows: 72.8% code, 23.3% comment, 3.9% blank. The metric is CODE lines now (codeLinesInBody — a documented line heuristic, per-language comment markers, not a lexer; both sides of every comparison run the identical rule). A markdown SECTION keeps its physical span: prose has no code/comment line to separate, and counting its non-blank lines made an in-place docs correction (03ec6f1) read as three verbosity rows. GROWTH WAS NEVER A SIGNAL. `now > was && now > BAR` gated +3% on a 1,068-line function while 6 -> 55 LOC (9x) and ccx 5 -> 13 (+160%) were invisible. Median growth of a gating verbosity row: 6%. Of a gating complexity row: 6%. Over the bar, a row now gates on a CROSSING or on growth >= kMaterialGrowthPct (25%); anything else is real, printed, and sev="minor" — chronic debt the change did not create. Under the bar, a DOUBLING that clears two thirds of the bar is a minor row instead of silence, which is synthetics S4b and S8-sub-bar. `was > 0` is a precondition there and it is load-bearing: growth is a ratio and a new symbol has nothing to double from — without it every added 40-line function reported "grew 4200%", 38 of the first 57 rows this tier produced. params and nesting are untouched (77% precision and no measured false positive respectively; neither moves on a hunch). | | wt before | wt after | ref before | ref after | | rows | 266 | 255 | 259 | 239 | | verbosity rows | 29 | 18 | 60 | 47 | | complexity rows | 11 | 11 | 30 | 31 | | gating rows | 171 | 42 | 69 | 41 | | verbosity gating | 9 | 1 | 19 | 3 | | complexity gating | 6 | 2 | 17 | 5 | | commits that gate | 12/12 | 9/12 | 20/40 | 17/40 | | gating precision TRUE | 2% | 10% | 10% | 17% | | gating precision TRUE+chronic| 16% | 36% | 71% | 51% | (wt = 12 landed commits, working-tree form, cumulative with the churn dial; ref = 40 commits, ack-free, cumulative with the dead-code dial.) ONE TRUE ROW WITHHELD, stated rather than buried: `forLensJsonHeader` (verbosity 56 -> 66) was labelled TRUE by Q1's rule "the symbol CROSSED its bar", and it no longer reports at all. The crossing was of a bar applied to PHYSICAL lines; on code lines the body is still under 60, so by the new measurement nothing crossed. It is the only TRUE row lost in either population. 26 of 27 (wt) and 48 of 49 (ref) TRUE-or-chronic gating rows survive; the 12 and 28 that are demoted are all TRUE-chronic and all still printed, which is exactly what the growth tier is for. Sidecar baseline v4 -> v5 and kQSnapCacheScheme 9 -> 10: locBySym's VALUES changed meaning with the keys untouched, which is what makes a stale one dangerous rather than obvious — it deserializes cleanly, every symbol reads as having SHRUNK, and the verbosity kind reports NOTHING while saying nothing about why. A v4 sidecar is refused by name, like v3 before it. GATE: test/qddialscheck.sh §3, ten arms over one fixture, six of them RED on the pre-change binary — the blank-line and comment-line bodies (gating rows before, no row after), the two over-bar chronic bodies (+5% and +10%, gating before, minor after), and the two sub-bar doublings (silent before, minor rows after) — beside the two crossings that must still gate and the two bar= attributes that must not move. qualitycheck's version-refusal arm follows the wording change. 22 arms, ALL PASS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…bout code that no longer rolls Comment-only. The hashed walker stopped being a second copy of the state machine with a rolling FNV inside it at 33ea149; it is now one block walk and a per-token fold, so the two places that describe it as "the fused rolling hash" describe code that is not there. lexSubtokenHash gains the note that matters more than the wording: it KEEPS its range test on purpose. The hashed walker folds with the branchless `c | ( ( c & 0x40 ) >> 1 )`, which is exact for [A-Za-z0-9] and wrong for anything else ('@' would fold to '`'), and this entry point is the one external callers reach with bytes nothing has classified. Re-verified after the edit: 18/18 byte-identical over three corpora x six verbs, strkerncheck, subtokencheck and g1freshcheck green, --quality-delta regressions=0 gating=0.
Four findings from audit lane Q1's replay, all on one kind, all the same mistake in different clothes: the kind reported everything it could SEE about the public surface instead of what a change made WORSE. (1) 103 of 119 api-surface rows over 40 replayed commits carried origin="new-symbol" — a row per new export, which the legend itself says can never gate. 193 of the 1,177 rows in this repo's own committed ack ledger are that shape, acked by hand one at a time for a fact the header can state in one attribute. They are api-new-surface="N" on the root now: never gating, never counted in regressions=, printed even at zero. (2) Three rows over 40 commits reported an arity DROP as a regression (probeBodyCost 7->5, selectMonotoneBodySubset 7->5, liftPackageDirMention 4->3) in a document whose first sentence is "only what a change made WORSE". A smaller surface is no longer a row. (3) 113 of this repo's 132 api-surface acks say the same sentence: "one trailing DEFAULTED parameter, every existing caller compiles unchanged". That shape is read off the signature (trailingParamHasDefault — a documented brace-depth scan, not a parser) and reported sev="minor". Still a row: the contract did move. (4) One parameter change emitted TWO rows, under `params` and again under `api-surface` (synthetic S3, 3 -> 7 parameters). The arity row folds into `params`, which at 77% TRUE is the highest-precision kind in the table. | | wt before | wt after | ref before | ref after | | rows | 266 | 209 | 259 | 127 | | api-surface rows | 52 | 6 | 119 | 7 | | gating rows | 171 | 32 | 69 | 26 | | api-surface gating | 11 | 1 | 16 | 1 | | commits that gate | 12/12 | 8/12 | 20/40 | 13/40 | | gating precision TRUE | 2% | 12% | 10% | 27% | | WRONG rows (gating) | 1 | 0 | 6 | 3 | (cumulative with the three dials before it.) NO FACT IS LOST. Five gating api-surface rows disappear in the working-tree population and six in the ref-pair one; every single one has a surviving `params` row carrying the identical was/now (editCheckBundleText 8->10 and editpreview::run 10->12 still gating, gitLogFileSets/gitRecentCommitFileSets 5->6 and packSignatures 20->21 and packBodies 14->15 now minor). The only genuinely lost row in this branch remains dial 3's forLensJsonHeader. WRONG rows on the working-tree population reach ZERO here. test/qackorigincheck.sh's FIXTURE MOVED, and this is the interesting consequence. Its invariant — a zero-magnitude ack must never become a permanent blank check — was driven through api-surface's new-symbol row, which no longer exists. `dead-code` is the other zero-magnitude kind with both origins (born uncalled vs lost its last caller), so all ten checks are re-pointed there, including both halves of the legacy-bare-token migration. The mechanism is unchanged; only the kind that reaches it is. test/qdrefpaircheck.sh's recorded literal moved 18 -> 8 for the 2026-08-15 wave shas, with the reason written beside it: 18 is what the kinds reported before this dial round. dmm for the same pair is still 0.530, which is the cross-check that the corpus did not move — only the tiers did. GATE: test/qddialscheck.sh §4, seven arms, five RED on the pre-change binary (the shrink row, the defaulted row's severity, the doubled `wide` row, the new-export row, and api-new-surface= itself). mcpattrparitycheck, mcpclidiffcheck, jsonparitycheck, attrvocabcheck, legendcoveragecheck, legenddriftcheck, printffmtparitycheck, docscommandscheck, qualnewcheck, qualitypanelcheck, staleackcheck, qualityscopecheck, qdrefpaircheck, qackorigincheck, xmlwellformed: PASS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ld never say yes
THREE FLAGS THAT SAY SOMETHING WAS WITHHELD AND NOTHING ELSE.
1-2. mention_files_capped= and doc_mentions_capped= were noteCap( …, nullptr, … ) — bare booleans, in a
file whose sibling caps mention_tokens_capped= and mention_syms_capped= have always carried a total.
docs/METHODOLOGY.md §9-3 makes a cut terminal only when the caller can finish in one more KNOWN call;
"an indexing cap dropped content not shown here" with no count is not that. Both now carry one:
mention_files_total= every DISTINCT file the task's mentions name, kept and refused together, so
total - lifted is what kMentionMaxFiles withheld. Computed only when the list
is full, so the common anchored query pays the same one size test it always did.
namesFileNotKept is now the predicate over mentionUnkeptFiles' output rather
than a separate walk — verdict-equivalent by construction, same resolution
order, same three rules, a level that resolves on KEPT matches alone still
appends nothing.
doc_mentions_total= lifted + refused: how many docs the caps had to choose from, beside the
doc_mentions= the root already carries. The refused set is now collected
instead of broken out of at the first hit — a bare "something was cut" can stop
early, a total cannot — and deduped, because one doc under two anchors is one
refusal. Nothing in that path touches lensRank, so the lift is unchanged.
3. --test-gate's tests_capped= was the string literal "0" in BOTH dialects (src/situ.h:1096, :1166): a
disclosure that could never become "1", so a <t> row cap added later would keep saying nothing was cut
while something was. It is now shown_tests < tests, and shown_tests= is the rows the document ACTUALLY
emits (r.testRows + shell-gate obligations) rather than a count asserted beside them. It stays PRESENT
at 0 rather than being omitted: pageview.h rule 1 pairs shown_*/*_capped per listing, and its sibling
untested_capped="0" is pinned by testgatepagecheck (a')/(a-json') and impactpartitioncheck — dropping
one half of a documented pair would be a new inconsistency, not a fix for this one.
A/B, this branch's binary vs the pre-change build, 11 invocations over two corpora (this tree and the
go corpus): 8 BYTE-IDENTICAL, 3 differ ONLY by the added total, 0 unexplained.
--for="…src/lexical.h chooseForRanker…" +46 B doc_mentions_total="5"
--for="wire src/mcp.h, src/mcpverbs.h, …" +48 B mention_files_total="5"
--for="pagerank power iteration" +46 B doc_mentions_total="5"
--for="incremental cache invalidation…", --pack-task, --top-k=100000 (both corpora),
--test-gate and --test-gate --json byte-identical
The attribute rides both the root and the [cut: …] note, which is why the cost is ~46 B and not ~23.
est_tokens moves with it, honestly.
GATES. mentioncapcheck arm (H): H1/H3 require the total beside each flag AND require it to exceed the
shown count; H2 requires it ABSENT when the cap did not fire, so an uncut answer stays byte-identical.
Red-proven against the pre-change binary:
FAIL H1 the file cut disclosed no usable total (total='' lifted='4')
FAIL H3 the doc cut disclosed no usable total (total='' shown='4')
testgatepagecheck arm (d): its own fixture (2 test files, 3 symbols) because $R has no test file at all
and an arm whose every number is 0 cannot tell a derivation from a literal; shown_tests= is checked
against the <t> rows COUNTED in the document, and the JSON dialect against the XML key-for-key. Control
on a synthetic COPY, because a literal "0" satisfies the arm on every tree where nothing is cut — which
is every tree today: rewrite tests="2" to tests="99" and require the check to reject the contradiction.
PASS (d) control: a document claiming tests_capped="0" with shown_tests=2 of 99 is REJECTED
NOT LANDED, and it is the other half of C2 F16: the flags still fire when a refused doc or file was
never going to reach the answer — C2 measured the doc caps adding ZERO <doc> rows at 1x/4x/16x budget
while doc_mentions_capped="1" and the legend's "an indexing cap dropped content not shown here" both
fired. "Would have ENTERED the answer" is not knowable where the fact is computed: applyDocMentionBoost
runs before the ranker and the payload budget decide what is emitted, and absorbCapDisclosure takes the
verdict at that same point. Making it knowable means carrying the refused set to all four emit surfaces
(--for, --pack-task, and the two MCP verbs) and re-proving byte identity on each — a lane, not a patch.
Recorded here rather than approximated, because a disclosure that guesses is the defect twice.
Green: mentioncapcheck, testgatepagecheck, printffmtparitycheck (42 verbs), jsoncheck,
testgatelegendbudgetcheck, legendcoveragecheck, impactpartitioncheck, mcpclidiffcheck.
… the whole skill catalog The 2026-09-10 audit measured --help-task at 3 recommends over 39 phrasings of the 13 surfaces added since 2026-08-28 (F-R1-08), and found the router able to name 8 of the 16 shipped skills (F-R1-09) — nine skill directories no --help-task answer could ever point at. Three of the unrouted surfaces are VERBS, not shaping flags: --handoff (which has its own shipped skill), --plan-lint, and the PROSE form of --from-trace (looksLikeTrace matches a PASTED artifact; "I have a sanitizer report" carries none of its literals, so the #108 name-ladder work was unreachable from prose). Ten intents in a catalogTaskChoice tier placed LAST in directTaskChoice, so every older and more specific route keeps its rows: handoff-brief --handoff, plan-lint --plan-lint=FILE, trace-prose --from-trace=-, scan-skills/scan-skill, opt-remark --for=TASK, architecture-health --deps, quality-check --quality-delta, perf-symbol --around=SYM, graph-query --graph-query=EXPR, maintenance-risk --hotspots. Conjunctive evidence in the instrumentedTaskChoice shape throughout; the value-carrying ones fire only when the task supplies the value. Two worth naming: opt-remark is the ONLY skill with no verb of its own (a contributor workflow around clang remarks and a profiling build), so it routes to the ranked lens and its reason says exactly that rather than implying a dedicated surface; graph-query COMPOSES an expression out of what the task supplied — the symbol it named, the direction it asked for — with a stated default depth, and the gate unquotes what the router emitted and runs it through the real verb. measurement before after skills the router can name (of 16) 8 16 the audit's 39 surface phrasings, recommends 3 9 corpus split=test (n=114) accuracy / coverage 0.754/0.627 0.939/0.907 corpus split=dev (n=111) accuracy / coverage — 0.946/0.929 corpus split=all (n=225) accuracy / coverage 0.809/0.730 0.942/0.918 precision / harmful / neg-specificity, all splits 1.0/0/1.0 1.0/0/1.0 the 189 rows that predate this tier — 0 differing RED-FIRST IS THE GATE, NOT THE EVAL: coverage has no floor by the round-1 rule, so the eval exits 0 either way. Eleven taskroutecheck arms fail against the pre-change binary (each abstained with score="0"), plus the two execution arms; and the skill-vocabulary arm — which reads BOTH sides from disk, the skill directories and the names src/taskroute.h can emit — fails against the pre-change source, naming all eight unreachable skills. That arm is the durable half: a new skill shipping without a route now fails as loudly as a route naming a skill with no directory. The 30 phrasings still declined are declined BY DESIGN and gated as such: six shaping flags (--scope, --slice-depth, --slice-flow, --allow-dirty, --no-ignore, --no-post-check) are modifiers on other verbs, --pin-census is eval-only, and the value-carrying abstentions keep the 2026-08-28 rule that the router may not emit a command the verb would refuse. Corpus +36 rows (30 positives, 3 per intent; 6 negatives that must NOT route), every routing outcome verified against a live binary before insertion. Two corrections that verification caught, recorded in PROVENANCE rather than smoothed over: a file that names ITSELF a plan (PLAN_*.md/DESIGN_*.md) is now surface evidence the prose need not repeat, and "before i commit" was re-weighted below the quality-check floor — a TIMING word, not a quality word, which at its first weight stole "lint the plan file layout before I commit it" from the plan-lint abstention. Map untouched: default map and --for byte-identical to the pre-change binary, xmllint clean. Seal 1719aea95449e222718ec38151d2bd6998a95e1dd070038baa0b6e28fd0c9cf5 (189 -> 225 rows); screen unchanged at 2 flagged lines despite the large amount of new card vocabulary. quality-delta gating=0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… per match
`passesPredicates` ran `std::regex_search( lhs, std::regex( rhs ) )` once per query MATCH, per
file (src/ingest_astquery.h, called from astQueryGrouped's two exec loops). `rhs` for a
String-typed argument is a CONSTANT owned by the TSQuery — `ts_query_string_value_for_id` hands
back the same bytes every time — so the most expensive constructor in the standard library was
answering a question whose answer never changes.
MEASURED BEFORE (1 ms `sample`, `--lint` over the go corpus, 44,376 busy leaf samples):
the `std::basic_regex` subtree is 13.52% of busy CPU and 100% of it is owned by
passesPredicates; the whole predicate evaluator is 20.65%. The leaves are CONSTRUCTION, not
matching — `__parse_ERE_dupl_symbol` 253, `__parse_atom` 209, the `__state` vector's growth 178+174,
against `__match_at_start_ecma` 273 — and regex construction is ~46.7% of every malloc leaf in
the run.
PredicateRegexTable holds one compiled `std::regex` per ( query, string id ), built by
buildPredicateRegexTable when the query is compiled and hung off GroupedQuery / GrammarQueries
beside the TSQuery it belongs to. `value_id` indexes the query's own string table, so
`ts_query_string_count` sizes an exact O(1) lookup: no hashing, no comparison, no allocation
left on the per-match path.
THREE STATES, and the middle one is the contract: slot >= 0 is the precompiled constant;
slot == -2 is a constant `std::regex` REFUSED, which must filter NOTHING — exactly what the old
per-match `catch( ... ) { ok = true; }` did; slot == -1 is a Capture-typed argument, whose
pattern is per-match text and stays dynamic. Getting -2 wrong would turn a broken rule from one
that keeps every row into one that drops every row, which is why it is its own gate arm.
A/B — CPU (user+sys via rusage), 12 interleaved runs per arm with the arms swapped at the half,
one unrecorded warm-up per arm:
| corpus | argv | A median | B median | delta med | delta min | B.med<A.min | load |
| ------------ | ------------------------------------- | -------- | -------- | --------- | --------- | ----------- | ---- |
| go | `--lint` (warm) | 18.055 s | 15.809 s | -12.44% | -8.67% | yes | 15.4 |
| rocksdb | `--lint` (warm) | 6.817 s | 5.788 s | -15.10% | -16.91% | yes | 21.0 |
| ripwire tree | `--lint` (warm) | 4.398 s | 4.084 s | -7.14% | -3.32% | no | 20.9 |
| go (n=10) | `--match=… (#match? @c "^(make\|append\|copy\|len)$")` | 13.835 s | 12.703 s | -8.18% | -7.66% | yes | 19.6 |
NON-DEGRADATION on a path with no predicates — the table is still built when the query compiles:
go cold `--no-cache --top-k=100000`, n=10, 18.241 s -> 18.074 s (-0.92%, inside the noise at
load 40).
The A/B beats the audit round's own -5.93% estimate because that arm memoised inside
passesPredicates; this one precompiles every predicate of the COMBINED per-grammar query at
compile time, which is the query the workers actually run.
BYTE-IDENTICAL, 21 of 21 arms against the binary this lane started from (i.e. both of the lane's
commits together), three corpora — go, rocksdb, a pristine ripwire tree — over --lint,
--lint-select, --match (plain, #match? and #not-match?), the default map --top-k=100000, --for,
--pack-task, --grep, --clones, --hotspots and --slice. Determinism: two runs byte-identical;
`xmllint --noout` clean.
GATE FIRST — test/astqueryregexcheck.sh + test/astqueryregex_golden.txt, the golden RECORDED FROM
THE PRE-CHANGE BINARY over a fixture the script materialises itself (so the corpus cannot drift
out from under the golden):
A --lint + 7 --match probes byte-identical to that golden (14,915 B).
B non-vacuity: the two #match?-only lint rules are live on the fixture (7 and 2 findings), so
arm A is not a golden of an empty filter.
C1-C4 the four semantics a "compile it once" change can silently move, each as a differential
probe: case sensitivity, regex_search vs regex_match, #not-match? as the exact complement,
and the malformed-pattern refusal filtering nothing.
D a Capture-typed #match? argument still evaluates per match.
E mutation control for C: all four expectations, inverted, are false — so no arm in C is
comparing a value with itself. (This arm caught a real defect while the gate was being
written: the map carries no trailing newline, so a blank-line section delimiter did not
exist and one differential arm was reading the whole file on both sides.)
F determinism under the query pool: 5 --lint runs byte-identical. The compiled regex is now
SHARED read-only across workers instead of built per match.
THE REBUILD MUTATION, run once by hand rather than in the gate (a gate that rebuilds the binary
is super-linear in CI contention): a scratch build with `std::regex::icase` added to the
precompiled construction turns arm A red and both C1 arms red — "^Foo" and "^foo" each return 2
hits instead of 1, and select the same two functions. Reverted and rebuilt before landing.
SANITIZERS on the changed paths: ASan+UBSan+LSan (`-fno-sanitize-recover=all`, committed
lsan_suppressions.txt) clean over the map, --lint, --match with a valid and a MALFORMED #match?
pattern, and --slice. ThreadSanitizer clean over --lint on the repo, --lint on the
multi-language test fixtures, and --match with a #match? predicate — the empirical half of the
[res.on.data.races] claim that a const std::regex may be shared across the worker pool.
--quality-delta: 5 gating rows, 4 of them short-horizon-churn on the one file this touches, and
one api-surface contract-change — passesPredicates taking the table, 3 params to 4. The first
draft also scored complexity 29 -> 48 and verbosity 70 -> 84 on passesPredicates; the three-state
decision moved into evalMatchPredicate and both regressions are gone.
P1-1 landed the byte-budget pin but stated its own gap: llvm's qchurn blob keyed on a DIFFERENT root
spelling (6b73c58ba5897c7a) from the family key (4280d3ca01d82374), so a divergent-spelling family that
grew large was still evictable by the very root writing it. Two new arms in test/evictioncheck.sh make
that answerable by a gate rather than by an eyeball on one corpus:
(k) prime every family a normal session writes (default map, --for, --edit-check, --quality-delta,
--cochange) against ONE root, then read the 16-hex root field off every blob name by the SAME rule
cacheBlobRootKey uses. There must be EXACTLY ONE distinct value, and qchurn must carry it.
(l) the key is a property of the ROOT, not its SPELLING: re-prime through `$R/` (trailing slash) and
through a symlink; no new key may appear.
RED-FIRST against the pre-change binary (5723b2c, lane C's tip) — 3 FAILs, every pre-existing arm (a)-(j)
still PASS:
FAIL (k) 2 distinct root keys for ONE root — a family outside the winning key is unpinnable:
ripwire-2dcb80adfcee28f9-rich.bin key=2dcb80adfcee28f9
ripwire-2dcb80adfcee28f9-lean.bin key=2dcb80adfcee28f9
ripwire-qchurn-127417b581e58c53--56b15799358c22a5.bin key=127417b581e58c53
ripwire-qsnap-127417b581e58c53-…-b4749535f4b10bf8.bin key=127417b581e58c53
ripwire-qheadsnap-127417b581e58c53-…-b4749535f4b10bf8.bin key=127417b581e58c53
FAIL (k) qchurn key '127417b581e58c53' != lean key '2dcb80adfcee28f9' — the pin cannot reach it
FAIL (l) 2 distinct root keys after re-priming through '$R/' and a symlink
So it is not an llvm accident and not a qchurn accident: the split reproduces on a four-file fixture, on
every root, and it is lean/rich vs EVERY sha-keyed family — the gate says which half of the dir the pin
was covering. The arm needs git (the qchurn/qheadsnap/qsnap families do not exist without it) and says so
rather than comparing one family against itself and passing blind.
No new gate file, so test/regression.sh and the gate count are unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…riable
The executability census earned its keep on its first real run. Re-running `screen` on the fixed
harness printed:
EXECUTABILITY (baseline arm): 150/195 answered | 2 unparseable | 1 unexpanded variable | …
unexpanded: $A, B, C . --pattern='rankGraphTeleport($A, $B, $C)'
$A/$B/$C are tree-sitter METAVARIABLES. The refusal rule added one commit earlier — "an unresolved
variable RAISES rather than passing through as a literal" — turned a legitimate corpus row into a
non-answer, which is the same class of defect as the one it was written to remove: a row that measures
nothing while the run reports success. It is only visible at all because the census now prints WHY
each row failed to answer; under the old harness it would have been another silent 0.
shlex.split has already discarded the quoting by the time expandvars_from sees the word, so
single-quoted (no shell expansion) and double-quoted cannot be told apart there. Naming the namespace
is what makes the rule decidable: a $NAME matching RIPWIRE_ is this harness's to bind and an unbound
one is refused (the F17 shape — $RIPWIRE_CAPSWEEP_TMP written into the frozen corpus as a relative
path); anything else passes through exactly as written, which is what the shell would have done.
GATE. capsweepcheck's synthetic corpus grows a seventh row, `--stub-metavar='fn($A, $B, $C)'`, which
must ANSWER, and the refusal row is respelled $RIPWIRE_CAPSWEEP_NO_SUCH_VAR so it still exercises the
namespace it is about. Red-proven by reverting the namespace test:
FAIL (J) a tree-sitter metavariable was refused as an unexpanded environment variable
FAIL (G) 3/7 answered | 2 unexpanded variable (the row stopped answering)
FAIL (I) the split was not reported over the answering rows (1 of 3, not 1 of 4)
The interrupted sweep was killed rather than published around: its screen.tsv had one of 195 rows
recorded as unexpanded, and a record with a known hole in it is worth less than the twenty minutes it
costs to take again.
… and the SIMD scan refused
S5 of the string-technique map, MEASURED BEFORE TOUCHED, as the audit's own condition demands
("adopt only if >=3% busy"). `sample` at 1 ms over warm `--top-k=100000` runs, inclusive share of
the call graph:
corpus surface runs busy escaper share
ripwire's tree XML 48 1516 escapeXml 4.62%
ripwire's tree JSON 48 1578 jsonesc::escapeInto 6.34%
go XML 8 4908 escapeXml 1.43%
go JSON 8 4845 escapeInto 1.88%
django XML 8 2633 escapeXml 1.67%
django JSON 8 2628 escapeInto 2.93%
Above the bar on one of the two named corpora, below it on the other — so the rewrite proceeds and
the win is stated for what it is: corpus-shaped. Escaping scales with emitted map bytes while the
rest of a warm run scales with graph size, so a doc-comment-dense tree pays 3-4x the share go does.
WHAT CHANGED. Both escapers keep their switch byte-for-byte and gain a run-copy skip in the for
loop's INIT and INCREMENT: appendCleanRun finds the next byte the switch has an opinion about,
appends everything before it in one memcpy, and returns that index. Placing it in the increment (not
the body) is why jsonesc::escapeInto keeps every one of its `continue` arms and why neither function
gains a branch — escapeXml's complexity is unchanged at 14, escapeInto's moves 31 -> 33 (minor).
The byte set is derived from the switch and documented as derivable WITH it.
AFTER, same instrument, same box: escapeXml 4.62% -> 1.97%, escapeInto 6.34% -> 2.61%.
Whole-verb CPU (user+sys, 12 interleaved batched arms, median | min, load 29-39 the whole time):
ripwire tree XML 4.44 -> 4.43 s (-0.2% med, -5.6% min)
ripwire tree JSON 4.18 -> 3.99 s (-4.5% med, -3.9% min)
django XML 2.96 -> 2.86 s (-3.5% med)
django JSON 4.24 -> 4.11 s (-3.1% med, -6.7% min)
go XML 6.45 -> 6.28 s (-2.7% med)
go JSON 5.78 -> 5.71 s (-1.0% med)
Honest reading: the escaper itself is ~2.3x cheaper; the verb is 0-4.5% cheaper, which on a loaded
box is at the edge of what an A/B can resolve. The halved sampler share is the claim; the whole-run
number is reported, not leaned on.
THE SIMD SCAN IS REFUSED, WITH NUMBERS. Routing this through strkern::findByteset made it WORSE, not
better: escapeXml went to 22.46% of a warm map (from 4.62%) and the whole map got 6-18% slower.
Cause: findByteset ends every call in findByteset_scalar, which is the harness ORACLE — it re-derives
a four-word bitmap from the 32-byte set on every call, 256 iterations, deliberately in a different
representation so a packing bug cannot hide behind it. Right for a gate, fatal for a hot path whose
inputs are 6-40 bytes. So src/infra/strkern_find.h (a sibling, per the lane's brief; strkern.h is not
touched) carries the shipped scan: one O(1) bit test per byte over the SAME Byteset256, no preamble.
No NEON/AVX2 block loop was written either, and that is also a measurement: benchmarked beside the
scalar scan across three length bands (6..40, 60..200, 200..900) and two special-byte densities, a
NEON scan is a wash below ~200 bytes — the lengths ripwire actually emits — and worth at most ~1.3x
on long sparse text, i.e. a slice of a slice of a 4.62% site. Duplicating another lane's block loop
for that would buy a clone. The run-copy SHAPE is where the win is (1.5x-3x in every band); the scan
under it is not. Headroom recorded in the header instead of taken.
appendCdataSafe is NOT rewritten: measured 0.00% of busy on `--pack-task` over both go (102,550
samples) and ripwire's own tree, and 0.02% on llvm `--grep`. It is covered by the gate anyway, so a
later lane that finds it hot inherits the proof.
BYTE-IDENTICAL, PROVED: 24 of 24 outputs cmp-equal against the pre-change binary — 3 corpora
(ripwire's tree, go, django) x 8 surfaces (--top-k XML/JSON, --for XML/JSON, --pack-task, --grep,
--lint, --hotspots), stdout and stderr and exit code. Plus test/emitescapecheck.sh's 222,682
adversarial inputs against the frozen per-byte references (landed in the previous commit, before
this code existed), determinism (two runs cmp-equal, XML and JSON), and `xmllint --noout` clean.
--quality-delta gating=0.
… not your duplication
Duplication's gating precision over 40 replayed commits was ZERO — 11 gating rows, 9 noise
and 2 wrong — and the three mechanisms behind them are properties of the GROUP, not of its
text, so all three are decidable without touching the clone matcher:
(a) ONE OVERLOAD SET — every member shares one canonical id. `emitTo|emitTo`,
`sort::stable|sort::stable`: overloads are near-identical by construction, and
reporting them as a copy is reporting the language.
(b) ONE FILE AND NO REUSED MEMBER — a sibling pair inside one body of code
(`mergeHi|mergeLo`, `gallopLeft|gallopRight`) is an alternate implementation the
author is looking at while writing it. The second half of that clause is not a hedge:
copying a helper three call sites already use is a real erosion whether the copy lands
next door or across the tree. Dropping on file identity ALONE silently retired
test/clonededupcheck.sh's entire positive case, which is how the clause was found —
the gate went red, and it was right.
(c) VENDORED — every member sits under a vendored path. No such notion existed anywhere
in quality.h, and one commit (0841640, the timsort landing) produced 9 duplication
rows, 8 of 8 dead-code:new-symbol acks and 37 api-surface acks against an upstream
body whose shape is not this repo's to fix. `.ripwire_config` grew its SECOND key,
`vendored_paths = PATH[, PATH...]`, beside four built-in conventions. Honest scope,
measured while writing the gate: the CRAWLER already drops third_party/, vendor/ and
node_modules/, so `external/` is the only built-in the indexer reaches and everything
else vendored is reached through the config key.
NO TOKEN FLOOR. Raising kMinCloneTokens was measured and REFUTED: the canonical true
positive (synthetic S1, a 12-line copy of a reused helper) is 59 tokens while the idiom
collisions in the same replay run 22, 24, 31, 36, 56, 65, 66, 74, 78, 91, 92, 96, 114 and
127. A floor above 22 loses true positives before it clears any noise; token count is the
wrong axis and the comment in the code says so.
| | wt before | wt after | ref before | ref after |
| rows | 266 | 208 | 259 | 117 |
| duplication rows | 9 | 8 | 23 | 14 |
| new-clone rows | 1 | 1 | 3 | 2 |
| gating rows | 171 | 32 | 69 | 23 |
| commits that gate | 12/12 | 8/12 | 20/40 | 11/40 |
| gating precision TRUE | 2% | 12% | 10% | 30% |
| WRONG rows (gating) | 1 | 0 | 6 | 0 |
| WRONG rows (all) | 1 | 0 | 26 | 6 |
(cumulative with the four dials before it; "before" is the pre-branch binary.)
WRONG reaches ZERO on the gating population of both replays here. 6 of 6 and 9 of 9
TRUE-or-chronic gating rows survive this dial with nothing lost and nothing demoted.
WHAT IS NOT FIXED, and it is the honest remainder: 9 gating duplication rows survive in the
ref-pair population and every one is the same shape — a one-line AST-tag predicate matched
against another one in a different language (`sliceIsJsPatternKind|cc_isParamList`). They
share no domain identifier, which is the discriminator the ledger's own acks use in words.
cloneidiom.h exists for exactly this and recognizes three closed shapes; it emitted `idiom=`
on 0 of 32 clone rows across 52 replayed documents, so its shape set matches none of what
this repo actually produces. That is a cloneidiom.h round, not a filter this commit can add.
GATE: test/qddialscheck.sh §5, nine arms over four STRUCTURALLY distinct clone shapes —
four copies of ONE body collapse into a single six-member group and made the first draft of
this fixture vacuous in every arm. Three arms RED on the pre-change binary (one-file,
external/, and the config key, which the old binary also reported as an unrecognized key).
The cross-file copy — synthetic S1's shape, the reason these kinds exist — must survive all
three drops, and one arm proves external/ is actually indexed so the vendored arm is not
passing on a directory the crawler never enters. clonebandcheck, cloneidiomcheck,
clonededupcheck, type3clonecheck, freshclonecheck, registermacrocheck: PASS.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e gate that catches all ten GATE FIRST. test/childwalkscalecheck.sh (gate #588) covers the ten surviving unbounded `ts_node_child( n, i )` walks that audit P1-0's follow-up table named. Six isolation arms, each the SAME fixture width with the walk entered and not entered, so a red arm names ONE walk rather than "the verb got slower". Proven RED against the pre-change binary (lane W's tip e246ca2), all six: (B1) sliceWalk --slice vs the plain map 64.0 x 1.28s / 0.02s (B2) collectSpanTiers --grep vs the plain map 61.5 x 1.23s / 0.01s (B3) measureFileHealth one error token vs none 60.5 x 1.21s / 0.01s (B4) ffiVisitNode extern "C" vs the same flood 11.2 x 1.34s / 0.12s (B5) ln_collectLocalDecls --naming-locals vs --lint 11.3 x 1.35s / 0.12s (B6) findMatches/matchChildren --pattern vs the plain map 125.3 x 3.76s / 0.03s WHAT MAKES IT QUADRATIC, MEASURED. `ts_node_child` is O(C) only when the child list is FLAT. A grammar REPEAT is a balanced tree of invisible nodes that `ts_node__child` skips in O(1), so a root of 128 000 DECLARATIONS is dead linear (8k/64k/128k = 0.04 / 0.33 / 0.63 s on the pre binary). What is not balanced is what the parser splices into the child array: EXTRAS — comments above all. A root of 16 000 COMMENTS is 117 x its control. Every fixture here is therefore a comment flood; a declaration flood of the same width ships a green gate over a live defect. Recorded on src/infra/tschildren.h and in the gate header. THIS COMMIT converts src/slice.h's two walks (sliceWalk, sliceWalkPreproc) and adds `forEachChild` to src/infra/tschildren.h — one spelling of the cursor idiom, `fn -> bool` so a filtering or searching walk gets its `break`; `appendChildren` is rewritten on top of it. Both slice walks recurse from inside the loop, so each frame owns its cursor; the header says why the cursor is an explicit parameter rather than an implicit one. sliceWalkPreproc has NO scaling arm and the gate header carries both measurements that say why: its natural control (the same flood, `#if` removed) routes through sliceWalk's own quadratic loop and reads 0.98 x on BOTH binaries, and a 1k->16k ratio cannot go green because --slice's rung-3 flow walk (SliceRdWalker, ~11 `ts_node_named_child` loops) is a LARGER quadratic on the same path that this lane does not own. --slice over a 16 000-comment definition went 2.38 s -> 1.21 s here; the residual 1.21 s is `ts_node_named_child` in a sample of the fixed binary. Handed to the next lane whole. Byte-identical, --no-cache, new binary vs e246ca2: ripwire / go / canyonraid48 x {map --top-k=100000, --for, --grep, --pack-task, --match, --lint --naming-locals, --dead-code, --pattern} = 24/24, plus --slice on four ripwire symbols and one canyon symbol = 5/5, plus the gate's own 30 generated and 21 committed fixture x verb pairs. Gates: childwalkscale, slice, sliceflow, sliceflowsens, slicediff, pattern, matchgrammar, lint, naminglocals, naminglens, ffi, greptier, grepfast, parsehealth, pyimportprecise, route, padscale, preprocdeadscale, manifest, gatecount, xmlwellformed, shellgateindex, binoverride, infraport, includeangle — ALL PASS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ildren — --pattern 3.76s -> 0.06s Four sites in src/pattern.h, all of audit P1-0's class 1. findMatches and matchChildren take their width from the CORPUS: findMatches is a stack walk from each file's root, matchChildren collects one candidate node's children. A comment is an extra spliced straight into the child array, so a 16 000-comment file gave both of them a 16 000-wide flat list. Gate arm (B6), test/childwalkscalecheck.sh, against the pre-change binary: --pattern over that file was 125.3 x the plain map of the SAME file (3.76 s vs 0.03 s); it is now 0.06 s vs 0.03 s. findMatches hoists one cursor and one reused `kids` vector out of the loop and pushes in REVERSE, so the stack still pops left to right — the visit order is unchanged, which is what makes the output byte-identical rather than merely equivalent. matchChildren owns its cursor per frame because matchAt recurses back into it. smallestContaining and snapshotNode index the children of the PATTERN's own parse tree, and pattern.h caps a pattern at kMaxPatternBytes = 4096 on the one path every caller takes (--pattern and --lint-rules alike, pattern.h:683) — ~2 000 children, ~2e6 iterator steps, ~1 ms. They are converted for uniformity and CANNOT get a scaling arm; the cap is why they were never hot, and the gate header says so instead of implying an arm exists. smallestContaining now uses ONE cursor for the whole descent (each level finishes before the next begins) and stops on the first containing child through forEachChild's false return. Byte-identical, --no-cache, vs lane W's tip e246ca2: --pattern and --match on ripwire / go / canyonraid48 (6/6, part of the 24/24 sweep in the previous commit), the gate's 30 generated and 21 committed fixture x verb pairs. patterncheck, matchgrammarcheck, lintcheck, lintrulescheck — ALL PASS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ss-2 sites worth converting CLASS 1 — the remaining four of audit P1-0's follow-up table. Each is one node whose child list is as wide as the FILE lets it be, and each measured red as an isolation pair against lane W's tip e246ca2 (test/childwalkscalecheck.sh; the walk-entered arm vs the identical fixture with the walk not entered): collectSpanTiers ingest_astquery.h --grep's tier pass vs the plain map 61.5 x 1.23s/0.01s measureFileHealth ingest_crawl.h one error token vs none 60.5 x 1.21s/0.01s ffiVisitNode ingest_sidecap.h extern "C" vs the same flood outside 11.2 x 1.34s/0.12s ln_collectLocalDecls ingest_metrics.h --naming-locals vs --lint 11.3 x 1.35s/0.12s All four are now 0.01-0.14 s, i.e. indistinguishable from their own controls. The two stack walks (collectSpanTiers, measureFileHealth) hoist one cursor out of the loop — neither recurses, so one cursor serves every node — and keep their exact visit order: collectSpanTiers collects once and pushes in REVERSE (its pop order feeds a stable_sort whose input order is part of the output), while measureFileHealth filters straight into the work list. ffiVisitNode's inner DFS uses appendChildren because `inner` IS the frontier. ln_collectLocalDecls recurses, so its cursor is per frame. CLASS 2 — converted, both pure iterations whose width comes from the input and whose child list a comment can lengthen: routesVisitNode (decorators of one definition) and capturePythonImportBinds (clauses of one import statement). Neither can get a scaling arm — a 16 000-decorator definition is not a shape any corpus produces — so both are gated by the byte-identical arms only, and the gate header says that rather than implying otherwise. collectGatedLocalNames' own top-level loop goes with them (it also called ts_node_child TWICE per index). NOT converted, with the reason recorded in the gate header: bindsVisitNode needs the INDEX for `ts_node_field_name_for_child( n, i )`, which is itself index-based — collecting the children would leave the loop quadratic in the field lookup, and the cursor's O(1) field-name accessor is a semantic change that needs its own gate, not a fold into a no-output-change lane. firstChildOfType is class 3 in practice: both callers hand it a using_declaration / qualified_identifier, a grammar-bounded width, and a per-call cursor allocation would cost more than the scan it replaces. Byte-identical, --no-cache, vs e246ca2: ripwire / go / canyonraid48 x {map --top-k=100000, --for, --grep, --pack-task, --match, --lint --naming-locals, --dead-code, --pattern} = 24/24; the gate's 30 generated and 21 committed fixture x verb pairs. Gates listed in the first commit of this lane — ALL PASS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…whole root, not half of it
P1-1 pinned "this root's blobs" through the byte-budget sweep by reading a 16-hex root field off the blob
name. It landed with a stated gap: llvm's qchurn blob carried 6b73c58ba5897c7a while the family key was
4280d3ca01d82374, so that family was not pinned. This closes it.
WHY qchurn DIVERGED — not a different input, a different CONSTANT. Both builders hash realpath(root) with
FNV-1a over the same material, through the same fnv1aAbsorb. They seeded it differently:
src/main.cpp::defaultCachePath 1469598103934665603 (17 digits — a TRUNCATED FNV-1a basis)
src/quality.h::headSnapRepoHex 14695981039346656037 (the real FNV-1a-64 offset basis)
so ONE root minted TWO key families, on every corpus, always. It is not an llvm accident and not a qchurn
accident: reproduced on a four-file fixture in one command —
ripwire-844a155665d606eb-lean.bin ripwire-qchurn-526f2ad625b9f069--01e462c9dce39b67.bin
ripwire-844a155665d606eb-rich.bin ripwire-qsnap-526f2ad625b9f069-…-83137139056a0e11.bin
ripwire-qheadsnap-526f2ad625b9f069-…-83137139056a0e11.bin
and 844a155665d606eb / 526f2ad625b9f069 are exactly python's fnv1a(realpath, basis) under the two seeds.
The split is lean+rich vs EVERY sha-keyed family (qheadsnap, qsnap, qbody, qhist, qms, qchurn, stier), so
the pin was covering half the directory. mcpCachePath diverged twice over: the same truncated seed AND no
realpath at all, so a trailing slash or a symlinked checkout minted a second MCP blob.
THE FIX. `quality::cacheRootKeyHex` is now the ONE derivation (headSnapRepoHex renamed — it keys seven
families, not one), and `rootKeyedCachePath` the one builder for the two families whose whole key IS the
root; defaultCachePath and mcpCachePath call it instead of open-coding a hash each. realpath-normalized,
so symlinks, `.`/`..`, `//` and a trailing '/' all fold; when realpath fails the same folding is done
lexically via resolve.h's `lexicalNormalize`.
WHICH SEED SURVIVED, AND WHY IT IS THE TRUNCATED ONE. A key change orphans every blob spelled the old way.
Taking fnv1a64's basis would rename the MAIN PARSE CACHE — 1.76 GB on llvm alone (rich 1.19 + lean 0.57), a
full cold re-parse per root on the machine. Taking defaultCachePath's renames only the git-metadata
families: kilobytes, one `git log` walk. The constant is an IDENTITY, not a digest, and FNV-1a's avalanche
comes from the prime multiply, so nothing is weaker — only naming compatibility differs, by three orders of
magnitude. It is now `kCacheRootKeySeed` with that argument beside it, because "fixing" it back would
silently throw away every warm parse cache in existence.
NO SCHEME BUMP, deliberately. kQChurnCacheScheme / kQSnapCacheScheme / kHeadSnapCacheScheme exist so a blob
whose CONTENT MEANING changed becomes a clean miss. No content changes here — only the root FIELD of the
NAME, so every old blob is already never NAMED again, which is what a bump buys. Bumping would assert a
content change that did not happen. The old-spelling blobs are ordinary orphans: the "ripwire-" sweep still
matches them by prefix and the 30-day age pass deletes them on schedule — verified by seeding a backdated
`ripwire-qchurn-<old-key>-….bin` and watching a later run remove it. That pass is silent for EVERY blob it
takes (it has no disclosure line at all, by P1-1's design), so an orphan is treated exactly as any other
aged-out blob, with no special case either way.
TWO FAMILIES ARE NOT ROOT-KEYED, and the llvm run is what surfaced the second one: `ripwire-docmd-` is
content-addressed (the document's bytes) and `ripwire-stier-` is FILE-addressed (one span-tier memo per
source file above 32 KiB — an llvm --for leaves ~30 of them, each with its own key). Their 16-hex field is
real, just not a key over a root, and cacheBlobRootKey was reading it as one. They are now named in
`kNonRootKeyedBlobPrefixes` and read as UNOWNED rather than renamed: their names are correct for what they
identify, and renaming would orphan the most expensive blob in the directory to rebuild (docmd costs a
markitdown popen and a Python start, seconds per file). test/evictioncheck.sh mirrors the list in shell and
fails on drift.
LLVM-PROJECT, private TMPDIR, real blobs, LLVM_LOCK held 18:59–19:03:
E1 --for="how are pass pipelines registered" cold, 138.74 s wall / 290.39 s user / 29.78 s sys
-> ripwire-4280d3ca01d82374-rich.bin 1,188,813,175 B
-> ripwire-qchurn-4280d3ca01d82374--7a4a….bin 10,226,984 B <- WAS 6b73c58ba5897c7a
seed a foreign root's 1500M blob (dir = 2,771,904,159 B, over the 2 GiB budget)
E2 --grep=SmallVector cold; its saveCache sweep runs with rich + qchurn already on disk
stderr, EXACTLY ONE line:
ripwire: cache …: over its 2048 MiB budget — evicted 1 blob(s) of other roots (this root's own
families are kept)
surviving: rich 1,188,813,175 B + lean 568,688,230 B + qchurn 10,226,984 B — no llvm family evicted.
Byte-identical A/B vs lane C's binary (5723b2c): 12/12 — ripwire tree, golang/go, rocksdb x
--top-k=100000, --for, --grep, --pack-task. Determinism (two runs cmp) identical, xmllint clean.
--quality-delta: the first cut regressed duplication (mcpCachePath vs defaultCachePath, 54 tokens) and then
complexity on cacheBlobRootKey (14 -> 18, bar 15); both were REMOVED by extracting rootKeyedCachePath and
isNonRootKeyedBlob rather than acked. What is left is short-horizon-churn on the functions this change must
touch, which is the dirty-tree-vs-git-HEAD artifact. Nothing acked.
test/fixedbufsweep.sh's census is re-derived, not bumped: main.cpp's `tail` drops from 2 call sites to 1
(defaultCachePath's assembly moved to quality.h::rootKeyedCachePath, and its prose moved with it),
quality.h's `tail` rises 1 -> 2 (shaKeyedCachePath's tail[96] plus rootKeyedCachePath's tail[64]), and
mcpindex.h's `name` row is deleted because that buffer no longer exists. Those three account exactly for
the pinned enumeration going 213 -> 212 calls/sites and 89 -> 88 rows.
README.md's recorded --for capture named `headSnapRepoHex` in its prose bullet and in one <c n=…> row.
The rename is carried through so the README does not name a symbol the repo no longer has (a reader's
--grep would come back empty). Only the IDENTIFIER moved: no measured value in that capture is touched,
and its line numbers were already historical before this change.
On the committed tree --quality-delta reports regressions="0" gating="0".
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The error-masking kind fired ZERO times across 52 replayed documents and once in 1,177
committed acks. Not because this repo swallows no errors — because all seven of its rules
require a LITERALLY empty block: `errorMaskBlockIsEmpty` strips whitespace and compares the
collapsed text to `"{}"`. Audit lane Q1's synthetic S2 (`catch(...){}`) was caught; S2b,
`catch( const std::exception& ) { /* ignore */ }`, walked straight past — and the comment is
where a deliberate swallow is most likely to be WRITTEN DOWN. The one spelling the kind
could not see is the one a person reaches for when they mean it.
A block whose only content is a comment counts now. `//`, `/* … */` and `#` all open one; a
';' or a '{' anywhere inside means a statement survives and the block is not a swallow,
which is what keeps `catch { std::fprintf( stderr, "bad" ); return -2; }` out.
MEASURED, and this is the whole point of shipping it: +0 rows over the 40-commit ref-pair
replay and +0 over the 12-commit working-tree replay. The two recorded document sets are
BYTE-IDENTICAL to the previous dial's, per-commit and row for row (117 rows / 23 gating /
11 of 40, and 208 / 32 / 8 of 12). This widening finds nothing in this history; it turns a
synthetic miss into a reported row and costs nothing.
TWO FLOORS, written beside the code. astQuery truncates the captured span at 120 characters,
so a comment-only block longer than that does not end in '}' here and is not recognized — a
miss, never a false hit. And the scan is over flattened text, so a semicolon inside the
comment PROSE also keeps the block out. Both directions of the imprecision lose recall
rather than manufacturing a finding, which is the only acceptable direction for a kind whose
output accuses code of hiding an error.
WITHHELD, with the reason. Q1's dial table also proposed widening kErrorMaskRules to a
`catch` that only LOGS and to a dropped `std::error_code`. Neither is a query-table addition:
"only logs" is a judgement about a block's whole statement list, and "never tested" is
data flow. Both need real analysis and, more to the point, a noise measurement that does not
exist yet — and a kind that has never produced a false positive is the worst possible place
to guess. Recorded in the lane report rather than shipped half-built.
GATE: test/qddialscheck.sh §6 — one fixture, two catch blocks, one comment-only and one that
logs and returns. RED on the pre-change binary for the comment-only block; the logging block
must stay silent on both. lintrulescheck, lintcheck, lintcatalogcheck, lintprecisioncheck,
qualitykindscheck (whose Python `except: pass` arm exercises the same predicate): PASS.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`--legend=compact` already exists, and nothing that tells an agent to run `--quality-delta` mentions it. CLAUDE.md's Verify block and the ripwire-quality-bar skill both mandate the verb at every "done" moment; neither named the flag that makes it cheap. Measured with this branch's binary: a clean two-function checkpoint goes 2,776 B to 454 B (-83.6%), and this repo mid-change goes 15,601 B to 8,775 B (-43.8%). The rows are byte-identical either way — only the dictionary in front of them is shorter, and an agent that has read it once does not need it on every iteration of a refine loop. This is the cheapest item in Q1's whole report: no code, no gate, a measured 84% saving on the run CLAUDE.md tells every agent to make. WITHHELD, with the reason. Q1-12 also proposed folding the `<sa>` stale-ack rows to a count when regressions="0" (up to 124 of them on a clean report; the live ledger makes the mean document 2.8x larger). It is not shipped, and the argument against it is stronger than the byte count for it: test/staleackcheck.sh §(3) and §(4) exist to pin that a stale ack is visible as a ROW naming WHICH ack it is, and both of its fixtures are reports whose finding is gone — i.e. exactly the regressions="0" documents the fold would empty. A stale ack is most actionable on the clean report, because that is the run where you could clear it. Making the ledger's hygiene invisible precisely when it is cheap to fix is a worse trade than the bytes, so the rows stay and the lane report records the number instead. skilltruthcheck, skilldescbudgetcheck, skillscanreadcheck, readmedriftcheck, compactlegendcheck, legendcostcheck: PASS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… 2 without AVX2, not a red Run 4 of PR #127 turned both macOS legs red on the arm that run 3's fix had just tightened: the x86_64 slice RAN under Rosetta 2 on the macos-14 runners and exited 132 (SIGILL) with no output. Rosetta 2 gained AVX2 in macOS 15; on macOS 14 a -march=x86-64-v3 slice is illegal at its first vector instruction. That is the emulator lacking the ISA — the same class as 'cannot execute binary file' — so it is a SKIP with the reason printed; a SIGILL after the slice has printed anything remains a FAIL. The run-3 tightening stands: every other nonzero exit of a slice that ran is still a red (control arm 3c).
…TED an AVX2 instruction Run 4 of PR #127: CI's macos-14 runners SIGILL'd the -march=x86-64-v3 slice at its first vector instruction (Rosetta 2 gained AVX2 in macOS 15), and control 3c 'passed' on that SIGILL (rc 132) — it was seeing the emulator, not the mutation (#128's lane and the coordinator spotted it). The sysctl probes cannot decide this: on a macOS 26 host whose Rosetta runs the v3 slice green, hw.optional.avx2_0 still reads 0 and leaf7_features lists no AVX2. So the gate compiles and RUNS a one-instruction AVX2 probe under Rosetta first: rc 0 + 'avx2 ok' => arms 3/3b/3c run and every nonzero exit of a slice (SIGILL included) is a FAIL; anything else => the three arms SKIP as rosetta_no_avx2 with the probe's exit printed. Control 3c now requires the mutation's OWN failure — rc != 0, rc != 132, and assertion output — so a SIGILL can never satisfy it. Verified locally in both states (probe available: 3/3b/3c PASS with rc=1 on the mutation; probe forced unavailable: the three arms SKIP, the gate passes on its six host arms).
…e where Rosetta 2 has no AVX2 Owner (2026-09-11): a non-AVX2 build of the mirror is fine for the hosts that cannot run v3. strkern.h has no SSE2 path, so on a host whose AVX2 execution probe fails the three mirror arms compile the x86-64 BASELINE slice instead of skipping — the scalar twins and the tokenizer on x86, under the same 26 assertions, the same -fsanitize=undefined,integer, and the same mutation control — and the arms name which slice ran. CI's macos-14 legs (Rosetta 2 without AVX2) therefore keep real x86 kernel coverage; the ubuntu legs run the v3 slice natively; the release floor (-march=x86-64-v3) does not move. Verified locally in both states: probe available → v3 slice, path=AVX2, mutation rc=1; probe forced off → baseline slice, path=scalar, mutation rc=1. A slice that RAN and exited nonzero is a FAIL in every arm; only an exec-format failure skips.
…eature set, not one AVX2 instruction Run 7: the macos-14 runner's Rosetta 2 executed the one-instruction AVX2 probe and then SIGILL'd the v3 slice — so 'can run vpaddb' is not 'can run -march=x86-64-v3'. The probe is now compiled with the floor itself and touches every extension it implies (AVX2 broadcast/add, BMI2 pdep/pext and variable shifts, LZCNT/TZCNT, FMA, F16C, a MOVBE-eligible swap), every value through a volatile so nothing folds; any SIGILL in it routes the mirror to the baseline (scalar) slice. Verified locally: probe green → v3 slice (path=AVX2); probe forced off → baseline slice (path=scalar); 3c fails on its own assertions in both.
…sembles the probe to prove it carries the v3 opcodes Correction of a fact the run-8 commit stated: run 7's probe did NOT execute an AVX2 instruction that Rosetta then survived — clang had folded the one-add probe to a scalar addb despite its volatile (otool: zero ymm/VEX opcodes), so it printed 'ok' on every runtime and never chose the baseline slice. Which x86-64-v3 extension Sonoma's Rosetta 2 lacks is not known; only that the v3 slice SIGILLs there. Hardening (coordinator's suggestion): after building the probe the gate disassembles it (otool -tv, or objdump -d) and requires one opcode of each class — ymm, pdep, pext, lzcnt, tzcnt, vfmadd, vcvtph2ps — so a future compiler that folds the probe makes it route to the baseline slice rather than lie; with no disassembler on the host the probe is treated as unverified. Run 8 (150fb6d) proved the routing on the macos-14 legs: 30/31 green with the baseline slice there.
joyful-ii-V-I
added a commit
that referenced
this pull request
Sep 11, 2026
…s/LIMITS.md regenerated over the merged tree, not hand-merged Only docs/LIMITS.md conflicted. docs/limits_build.py and test/limitstablecheck.sh auto-merged. `## Caps, by file` still sits right before render()'s per-file loop, beside #127's widened census and BOUNDARY class, and arm (K) sits beside #127's (H). Resolution: `python3 docs/limits_build.py` over the merged tree gives 208 caps, 7 parameters and 111 classified. --check rc 0, and all 83 per-file tables sit under `## Caps, by file`. Against main, the regenerated file differs only by that heading and its sentence. limitstablecheck 23/23 PASS, including (H) and (K). gatecountcheck 0, manifestcheck 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
joyful-ii-V-I
added a commit
that referenced
this pull request
Sep 11, 2026
…#127 landed Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
joyful-ii-V-I
added a commit
that referenced
this pull request
Sep 11, 2026
…o file in common, nothing to regenerate Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 11, 2026
andriytyurnikov
pushed a commit
to andriytyurnikov/ripwire
that referenced
this pull request
Sep 11, 2026
…edhat-et#127 head this lane stacks on (test/fieldidcheck.sh only) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andriytyurnikov
pushed a commit
to andriytyurnikov/ripwire
that referenced
this pull request
Sep 11, 2026
…dhat-et#131 third-party row) joins the std::-guard fix Textual conflicts, all at gate-count and gate-list sites: README.md, docs/EVALS.md, present/deck5_ripwire_build.js main's text taken, then the count sites regenerated by docs/gatecount_build.py on the merged loop: 597 + stdqualcheck = 598 test/regression.sh union of member sets — every main entry kept (redhat-et#127's nine appended gates included), stdqualcheck in its alphabetical place docs/COMMANDS.md and docs/captures/COMMANDS_showcase_2026-09-11.md merged clean as TEXT but describe neither parent's binary: the capture was recorded on the pre-redhat-et#127 tree, and redhat-et#127 changed --help (--limit now honored by --flags/--situ). They are regenerated on the merged, rebuilt tree in the next commit, never hand-merged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andriytyurnikov
pushed a commit
to andriytyurnikov/ripwire
that referenced
this pull request
Sep 11, 2026
… tree — the 09-11 capture predated redhat-et#127 The 09-11 capture merged clean as text but was recorded on the pre-redhat-et#127 tree (built_from=d39554ddb), so it described neither parent: redhat-et#127 changed --help (--limit now honored by --flags/--situ, the answer-rows-never- page sentence) and a long list of verb outputs. Regenerated, never hand-merged: docs/captures/COMMANDS_showcase_2026-09-11.md test/showcase_capture.py on the CLEAN merge commit 4f9c715, binary stamped built_from=4f9c71537 after a --clean-first rebuild; 253 commands, the non-zero exits the captions promise (refusal demos) and no others; PYTHONDONTWRITEBYTECODE=1 with docs/__pycache__ removed first, so the crawl it records carries no bytecode dir. bodySeed re-derived the seed as src/graph.h:3194 and it publishes sym="rankGraphTeleport" (main did not touch graph.h, so the seed did not move) docs/COMMANDS.md docs/docs_commands_build.py --bin build/ripwire from that capture (175 flags, 161 samples; --check clean) Same file name as before: the capture is dated by the day it runs, and the newest capture is the one the gates and the generator read. main carries no newer capture (its newest is 09-10, kept as history). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
joyful-ii-V-I
added a commit
to xCatG/ripwire
that referenced
this pull request
Sep 11, 2026
…t#130 child walks, redhat-et#132 out-param readers, redhat-et#127, redhat-et#128, redhat-et#129, redhat-et#131, redhat-et#133) joins the tier-3 disclosure fix Three textual conflicts, each resolved from both sides: test/regression.sh the gate loop is the union: main's line (stdqualcheck in its sorted place) plus this branch's declinecheck at the tail; docs/gatecount_build.py re-stamped README, EVALS and the deck at 599 (--check clean) docs/COMMANDS.md, main's side taken verbatim (add/add on the capture); both are regenerated on the rebuilt docs/captures/COMMANDS_showcase_2026-09-11.md merged tree in a later commit, not hand-merged Auto-merged without conflict: src/graph.h, src/mcp.h, src/mcpverbs.h. main's side of mcp.h/mcpverbs.h is the redhat-et#132 out-param returns in the quality verbs; this branch's side is the declined_calls description and keys. They touch different hunks and share no function. NOT FINISHED BY THIS COMMIT, on purpose so the next commit carries the evidence: redhat-et#134's std:: guard exit in buildGraph's resolve loop calls vetoExternal( r ) and discards the CallDisposition this branch made it return, so every site the guard refuses leaves the loop Unaccounted and a plain build raises DEGRADED_PATH_ALERT. The next commit names it, with a declinecheck arm shown red on this tree first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
joyful-ii-V-I
added a commit
to xCatG/ripwire
that referenced
this pull request
Sep 11, 2026
…ted External, as the external= it raises redhat-et#134's std:: guard (keepStdQualifiedCandidates) entered buildGraph's resolve loop in the merge above as `vetoExternal( r ); continue;`. On this branch vetoExternal returns the CallDisposition it counts, and the guard threw it away, so every site the guard refused left the loop Unaccounted. The conservation line did not balance, the census's external fell below the header's by exactly those sites, and every plain build raised DEGRADED_PATH_ALERT. The exit now reads `disposition = vetoExternal( r );`. External, not QualifiedExternal: the guard raises external= and writes one `C external` census row, and arm F pins census external == header external. qualified_external stays the Rust guard's bucket, which raises no gauge. pincensus.h's External comment now names every vetoExternal caller, the std:: guard among them. LOOP-EXIT AUDIT over origin/main 5c80848..766913d: the std:: guard is the only exit main added to the resolve loop. redhat-et#127, redhat-et#130 and redhat-et#132 changed no line inside it; graph.h's only other main hunk is resolveAtSeed's readWholeFile (redhat-et#132). Every exit after the tally names a disposition. The `continue`s in the multi-root admission loop and in the edge accumulation loop, and the two one-line `break`s, end INNER loops; the loop has no break, return or goto at its own level. ARM (test/declinecheck.sh F): a third conservation run, over redhat-et#134's own fixture test/stdqualfix, whose std::move and std::swap sites have lone in-repo decoys and so reach the guard with candidates. Premise: takeTwice's two std::move sites are external census rows, which only the guard can write for a qualified call (the Phase-5 veto requires an empty qualifier). Then the line conserves, census external == header external, and stderr carries no alert. test/declinefix and its pins are untouched. RED, merge commit 3cc236e (plain build --clean-first, built_from=3cc236e9e), without this line: declinecheck 3 FAIL / 101 PASS: exactly the three new rows; their premise passes stdqualfix calls=18 bound=6 external=2 unaccounted=10, header external=12, alert at graph.h:2812 ripwire . calls=135449 external=1005 unaccounted=1495 (header external=2500), alert memgraph calls=295086 external=7404 unaccounted=4966 (header external=12370), alert GREEN, this commit (plain build --clean-first): declinecheck 104 PASS / 0 FAIL; stdqualcheck PASS ripwire . # dispositions calls=135449 bound=27704 self=42 external=2500 unresolved=4145 undefined=21105 other_root=0 qualified_external=6 declined=6263 file_scope=73684 unaccounted=0 memgraph # dispositions calls=295086 bound=150305 self=1122 external=12370 unresolved=9811 undefined=41388 other_root=0 qualified_external=105 declined=65516 file_scope=14469 unaccounted=0 no DEGRADED_PATH_ALERT on either. memgraph's map is byte-identical red vs green: the line counts, it moves no edge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
joyful-ii-V-I
added a commit
that referenced
this pull request
Sep 11, 2026
Both notes beside the Round 4 tables now say how big the tree behind the 194.1 s to 155.6 s cold-parse figure is (PR #127: "llvm-project (182,555 files) as the scale rung"). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
joyful-ii-V-I
added a commit
that referenced
this pull request
Sep 11, 2026
The sentence "Every install route ... is in INSTALL.md" sat right under the quick-install block on main before PR #127 (b01df96^1, README line 81). The #127 head never had it, the merge took that side, and main has not linked INSTALL.md from the README since. It goes back in the same place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
joyful-ii-V-I
added a commit
to xCatG/ripwire
that referenced
this pull request
Sep 11, 2026
…rse lane Main moved from 5afadec through redhat-et#127 (b01df96), redhat-et#131, redhat-et#133, redhat-et#132, redhat-et#128, redhat-et#129 and redhat-et#130. Git merged every file but one as text; the one conflict is a pin, re-derived rather than picked. - test/qschemetrip.hash: CONFLICT, both sides re-pinned for their own half of the manifest (redhat-et#132 moved readRegisterMacrosConfig's read; this lane moved kCacheVersion 18 -> 20 and kParserVer 88 -> 90). Re-pinned on the merged tree with UPDATE_GOLDEN=1 -> d8dcd5c0; the RE-PIN LOG in test/qschemetripcheck.sh now records the lane's bumps and why neither side's hash was carried. - Auto-merged, read hunk by hunk: src/quality.h (redhat-et#132's optional<string> reads vs this lane's mirror block), src/ingest_names.h (redhat-et#130's cursor walks vs parseRecoveredBits), src/ingest_sidecap.h (redhat-et#130's pybindDefParts vs the re-parse wiring), README.md (redhat-et#133's hero vs the gate-count sites). Disjoint regions; nothing reinterpreted. - Version constants stay this lane's: kParserVer 90, kCacheVersion 20, and quality.h's kIngestParserVerMirror / kIngestCacheVersionMirror 90 / 20 (main is still 88 / 18). - Generated sites checked on the merged tree: docs/gatecount_build.py --check (599 = main's 597 plus extentcheck and macroreparsecheck), docs/limits_build.py --check (208 caps). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
joyful-ii-V-I
added a commit
to xCatG/ripwire
that referenced
this pull request
Sep 11, 2026
…64-v3 binary it builds; its Xcode and minimum macOS are pinned 88a8f65 makes the macos-x64 binary x86-64-v3, and this leg EXECUTES it on its arm64 runner under Rosetta 2: pgobuild's nine training runs, the determinism diff, --version and the smoke test. The binary contains AVX2 (44 vpmovmskb on ymm), FMA, BMI1/2, LZCNT (314) and MOVBE (108). macos-14's Rosetta SIGILLs a v3 slice (test/strkerncheck.sh, PR redhat-et#127 run 4), so on that runner the leg fails at training. macOS 15's Rosetta gained AVX2, but its LZCNT/MOVBE coverage is unverified. macOS 26's is verified: on a macOS 26.5.1 host with Xcode 26.6 (17F113, the macos-26 image default) this leg's exact pgobuild (Release, CMAKE_OSX_ARCHITECTURES=x86_64, MACOSX_DEPLOYMENT_TARGET=14.0) collected a profile under Rosetta, and build_pgo/ripwire passed every post-step (emit=std::print, minos 14.0, determinism diff, xmllint) with output byte-identical to an arm64 build on test/fixture, the repo map and a --for query. - macos-x64: os macos-14 -> macos-26; developer_dir Xcode 26.6. - deployment_target "14.0": exported by a step (a leg without the key never sees the variable) and read back off the binary (otool minos). Without it clang takes the lower of the runner's macOS and the SDK default, 14.x on macos-14 but 26.0 on macos-26, so the move alone would drop macOS 14/15 Intel users. - DEVELOPER_DIR is per leg (matrix.developer_dir); macos-arm64 keeps Xcode 16.2 on macos-14, unchanged. Gate first: test/portablebuildcheck.sh #2h (text-level, every host). Red on 88a8f65 (the leg on macos-14; no pin); ALL PASS here. actionlint clean; test/releaseinstallcheck.sh ALL PASS. quality-delta: two minor, non-gating verbosity growths on the already-over-bar jobs/build YAML keys (rationale comments). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 11, 2026
joyful-ii-V-I
added a commit
to andriytyurnikov/ripwire
that referenced
this pull request
Sep 12, 2026
…ty half leads The showcase cover now matches docs/assets/banner.svg: - a terminal card with the three dots; - the two-tone name, rip in cyan and wire in amber; - "The ripgrep of AI context."; - the wave from the README hero directly under that line; - the one-sentence description in the right column. Below the card the two halves are weighted on purpose. rip, the speed half, is the narrower card. wire, "the other half: honesty", is the wide amber-framed card: floors, "a zero means none found", every truncation disclosed, and every guess saying how many it chose from. The wave moves out of the README (its hero now carries only the Trendshift badge under the intro). docs/assets/paddle-out.svg stays in the repo. The deck embeds present/assets/paddle-out.png, rendered from it at 4x on the card colour with Arial, because pptxgenjs writes an SVG's bytes into the PNG fallback slot, which older viewers cannot draw. The grammar count said 21 in four places. It is 23: 22 upstream projects under third_party/deps/, with tree-sitter-typescript supplying both typescript and tsx. The deck's language row gains Elixir and Dart, in the README's order. Slide count is unchanged; deckcheck and deckclaimcheck pass. The pptx/pdf regenerate when this lands after redhat-et#127. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
joyful-ii-V-I
added a commit
to andriytyurnikov/ripwire
that referenced
this pull request
Sep 12, 2026
…three honesty stories, a --quality-delta examples slot The deck grows from 29 slides to 33. Every figure on the new slides is quoted, with its origin, in that slide's speaker notes. Everything here is merged on main 766913d; claims about redhat-et#135, redhat-et#136 and redhat-et#126 are in the next commit. - 0.6.0 at a glance: Dart, the 23rd grammar (6f91fed), and the six hand-sized language arrays its landing caught; Rip'n Fast (redhat-et#83 warm --grep 159.7 -> 9.2 s; redhat-et#127 llvm cold parse -19.9% CPU, go --pack-task -27.6%); honest where it counts (redhat-et#134 2,107 -> 3; redhat-et#127 per-kind --quality-delta 12/12 -> 8/12, --help-task 13/25 -> 0); fewer tokens, nothing hidden (redhat-et#92 --help 46,385 -> 4,473 tokens; redhat-et#127 --handoff 6 -> 50, listing verbs page). Footer: the x86-64-v3 upgrade note from redhat-et#127's body. - The scale rung: the O(C^2) child walks (redhat-et#127, redhat-et#130), the inheritance cone (redhat-et#83) and the cache that evicted itself (redhat-et#127), each with its llvm-project number; a per-corpus bar row from redhat-et#127's table. The title says "found at llvm-project scale", because only the child walk is sourced as invisible on the standard corpora. One callout lists what is built on or with LLVM. Each item was checked against a primary page, and the URL and quote are in the notes. XLA was dropped: no fetched openxla.org page says it is MLIR-based. The PS4 is the only console named, because only Sony's 2013 PS4 toolchain slides were read. - Honesty stories: std::move's 2,107 callers (redhat-et#134); the probe that lied (b4ebf0b corrects 150fb6d's reason: clang folded the one-add probe to a scalar addb, and the gate now disassembles its own probe); the audit's instrument was wrong twice (the redhat-et#127 brief, 209f97a, 5723b2c, redhat-et#127 lane H's 59/195 -> 64/151). - What --quality-delta catches: a marked PLACEHOLDER. qdExamples() renders QD_EXAMPLES, a data-only array at the top of the generator, as 1-3 wide or 4-6 two-column before/after cards in the mono face. The array is empty until docs/QUALITY_DELTA_CATALOG.md lands, and the helper refuses more than 6 entries or an empty field. The two drawing helpers (storyCards, qdExamples) take a slide their caller added. That keeps one literal addSlide call per slide, which is what test/deckclaimcheck.sh counts. A first cut added the slide inside the helper: the count still read 33, but only by coincidence, and a second call to the helper would have drifted from the pages while the gate stayed green. The cover and its wave are 680a0a3's, unchanged apart from notes that source its 23-grammar count. The slide count moves in README.md (two places), present/README.md and the re-derive row. The pptx and PDF are regenerated (pptxgenjs 4.0.1; LibreOffice 26.2 impress_pdf_Export; 33 pages). Gates on this tree with a main-tip binary (built_from=766913d02): deckcheck ALL PASS (0 bad values, 0 stale), deckclaimcheck ALL PASS (179 long flags, 33 slides), readmedriftcheck ALL PASS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
joyful-ii-V-I
added a commit
to andriytyurnikov/ripwire
that referenced
this pull request
Sep 12, 2026
…strument, the scale rung, count every omission prompts/full-audit.md was last changed on 2026-08-08. This folds in what the 2026-09-10 round (redhat-et#127 and follow-ups redhat-et#128..redhat-et#136) taught: - A "measure the instrument" section before any lens: cache eviction that faked super-linearity, a cap sweep on a population of zero, and a capability probe folded to scalar. - Lens 1: hostile inputs (vendored-scanner aborts, redhat-et#126's review) and conservation lines (redhat-et#136's dispositions). - Lens 2: the scale rung and red-first isolation arms. - Lens 3: the adversarial prose set. - Lens 6, new: the honesty of the output. - Process: regenerate generated artifacts on the merged tree; a targeted gate list is not the suite (redhat-et#132); cross-ISA slices carry sanitizer flags. - How to run the audit with ripwire on your own large repository, and how to file findings. prompts/README.md: the full-audit row describes the new lenses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 9d931b0b6a69ae0f64989e3b679c0d9f7e568625)
neoneye
pushed a commit
to agent-memory-atlas-archive/redhat-et--ripwire
that referenced
this pull request
Sep 13, 2026
…dule/name/arity resolution) — parser version 95 REHEARSAL ONLY — local branch pr81-rehearsal, never pushed. Resolves the 15 conflicted files of merging main into @henry-hz's head 9046edc, assuming PR redhat-et#139 (kParserVer 93, kCacheVersion 21) and PR redhat-et#172 (kParserVer 94) land first: - src/ingest_cache.h, src/quality.h: kParserVer / kIngestParserVerMirror 95 (branch carried 87, main 92); kCacheVersion / kIngestCacheVersionMirror 21 (redhat-et#139's format; redhat-et#81 changes no record shape). - src/ingest_elixir.h, src/ingest_sidecap.h: the branch's semantics kept (ElixirContext scopeOf/moduleOf, attribute refs, alias/atom/dot/__MODULE__ receivers, the refined @-attribute filter); converted to main's redhat-et#127 API (fieldChild/NodeField, kindIs) since the by-name nodeFieldText overload is gone on main. NodeField gains Operand (src/infra/fieldid.h). #include <charconv> added (review item 1). - src/graph.h: PROVISIONAL accounting on the Elixir no-candidate drop — disposition Undefined/Unresolved instead of Unaccounted, so main's 6b8dc9d conservation alert stops firing on every Elixir corpus (it fired on test/elixirfix: 10 of 19 sites). No edge changes; review item 2's design choice stays open. - test/regression.sh: gate-list union (606 + elixirsemanticcheck = 607); test/gateexitcheck.sh: both rows. - test/qschemetrip.hash, test/printf_parity.manifest, gate-count sites, docs/COMMANDS.md: regenerated from the merged build. docs/captures/*: main's side (the 09-12 capture's graph.h seed now needs a ref-clean re-record: rankGraphTeleport moved 3357 -> 3371). - CHANGELOG.md, README.md, docs/ARCHITECTURE.md: both sides, renumbered to 95/21/10. Wanted gate set after this commit: gates=12 pass=11 fail=1 — the one failure is showcasecapturecheck arm (H), the seed line, which only a re-recorded capture can fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integration of the 2026-09-10 full-audit execution round: performance at scale, the string-handling class, cap correctness,
--quality-deltaper-kind dials, and--help-taskprecision. Twelve lanes, each built, gated red-first and byte-verified on its own branch, merged here--no-ffin dependency order sogit log --first-parentnames the lane behind any gate. Base: mainedbb978d(one merge-main, after the eight PRs landed tonight). The audit that produced the findings ran read-only on main + #108 with llvm-project (182,555 files) as the scale rung; its plan and results stay local per house rule.Owner decisions carried in this PR
-march=x86-64-v3(AVX2, BMI1/2, FMA — RHEL 10's own floor). Prebuilt Linux x86 binaries now require AVX2-class CPUs. NEON on arm64. Every SIMD kernel ships a NEON path, an AVX2 path and a scalar twin, in ONE header.src/infra/strkern.h: nibble-table byte classification, A–Z fold, byteset/byte/3-byte find (NEON/AVX2/scalar); the query-time tokenizer rewritten as mask algebra, proven against verbatim copies of the old walkers; BM25 head-mask index;readWholeFileon the scan path--pack-taskgo −27.7% CPU, rocksdb −10.3%, ripwire −13.3%; 18/18 byte-identical; AVX2 mirror green under Rosetta 2Byteset256carries its own derived tables (the tail no longer rebuilds the set per call); one header; kernel tests as a doctest TU (test/verify_strkern.cpp, targetripwire_test_strkern)--grepSIMD scan was REFUTED (the verb is file-open bound; scan is 0.4% of busy)ts_node_child_by_field_namesites → a per-grammarTSFieldIdtable (F3 one layer down);std::regexcompiled once per predicate--lint−12 to −15%; 1,189,205 enumerated (node, field) pairs; ASan+TSan cleancollectPreprocDeadRanges's indexed child walk was O(C²) (the vendored iterator restarts per call)bindsVisitNode,captureBases,callArity, the--sliceflow walker) onsrc/infra/tschildren.h--for274 → 26 s CPU); one root key for all seven cache families (two FNV seeds, one truncated)--quality-deltaper kind:churn="self"informational; dead-code sees headers (excludes language-invoked symbols instead); verbosity counts code lines; cx/verbosity gate on a crossing or ≥25% growth; api-surface new symbols → a count; overload-set/vendored clone drops; comment-only swallow--help-task: "how does …" no longer mints a symbol; JSON keys never resolve; stop rules gated as present and load-bearing; router names 16/16 skills; MCPno_routekHandoffSymbolsPerFile6 → 50 code / 12 prose (containment 16 → 54%, additive); disclosure totals;tests_cappedderived--doc-drift,--flags/--flip,--situdisclose their cuts and page; answer rows never pageGate pins this PR moves (for review, not just the commit messages):
donelegendcheckqd_dirty ceiling 3800 → 3900 B (a loosening). The--quality-deltalegend gains two facts a reader needs to act on a row: theapi-new-surface=count (one sentence; its "printed even at zero" phrase isemittertruthcheck's roster key, probed by the new Z2g arm) and the churn facets' gating rule (one clause). Measured on the gate's own fixture, pre-round binary → this tip: clean 2177 → 2282 (ceiling 2300, unchanged), dirty 3642 → 3855 (ceiling 3800 → 3900), scope 4916 → 5129 (5200, unchanged), refpair 4058 → 4271 (4300, unchanged). Lane Q's first cut of those sentences was 476 B larger; they were trimmed to one clause each so three of the four forms stay under their old ceilings, and the dirty form's headroom shrinks from 158 B to 45 B — the ceiling moves by less than the growth.w3fixlegendcheck--situsection [2]: the 25-row cap on tests-to-run was RETIRED (answer rows never page;listingpagingcheckarm D pins the retirement red-first), so the arm now asserts every row listed AND no cut disclosed — listed rows equal the header count, and no cap attribute or wording remains in the section, the legend, orsrc/(kSituTestRowsShownis gone).cppqualchecklive-repo pin--uses=readWholeFile22 → 23 (src/lexical.hadopted the documented reader on the BM25 scan path; history line added in the gate).mcpmanifestcheckceiling 41,650/42,000 → 42,200 (attributed tool by tool in the gate header, above).fieldidchecklinks its harness against the build's grammar objects; a Release build leaves them as LTO bitcode, so the gate now retries the identical link with-flto(macOS's linker read them transparently, which is why only the ubuntu Release legs were red).Regenerated at the tip, never merged by hand: gate count (597, generator),
docs/LIMITS.md(with #123's by-name pins and H's widened census),docs/TUNING.md(from the clean sweep),docs/COMMANDS.md,test/printf_parity.manifest, the ack ledger healed through the binary (1,249 → 1,233 rows), the MCP manifest ceiling re-anchored at 42,200 with both lanes' bytes attributed,docs/LINEAGE.mdrows for the sources whose technique is in the shipped code (49 repositories / 70 papers).Upgrade note for the release: the cache root key is now one derivation for every family (the lean/rich builder had hashed the root with a truncated FNV basis, the git-metadata families with the real one), so after upgrading, existing lean/rich/qchurn/MCP cache blobs written by older builds are clean cache misses — one cold parse per root — and are swept by the age pass. No scheme constant moved: blob contents are unchanged, only their names.
Not in this PR, recorded: ~25 further indexed
ts_node_childloops in six files W3 listed for a follow-up (the parent-chain family now owns the #1 leaf); the second half of C2 F16 ("would have entered the answer" for the doc-mention flag); the deck.pptxis regenerated from its generator at release time.Before/after, main's binary (9356cf23 source) vs this tip — same argv, interleaved arms, CPU = user+sys, median over n pairs (min in the Δmin column), on a shared box (load 7–14). Every row
cmp-identical between the two binaries.Reading the table: the cold parse (
--no-cache) carries the child-walk and field-id lanes (−2% on this tree, −5% go min, −7% rocksdb, −20% llvm — the O(C²) walks only bite on wide flat child lists, which C/C++ include guards and comment floods produce);--pack-taskcarries the tokenizer + head-mask lane (−28% go, −16% ripwire, −11% rocksdb, −5% llvm);--lintcarries the regex hoist (−7% / −18% / −17%); warm map,--forand--grepare within noise (their floors are the serial resolve loop and file opens, both named in the audit as the next rung). Not in the table: the cache-eviction fix (a same-argv--foron llvm went 274 → 26 s CPU when the sibling family had been evicted; it now never is), and the verbs whose OUTPUT this PR changes by design (--quality-delta,--help-task,--handoff,--doc-drift,--flags,--situ).🤖 Generated with Claude Code