Repository navigation
fix(quality): a --quality-baseline pin another build computed was the floor, dead set and all - #259
Conversation
…lves calls differently The --quality-delta HEAD snapshot cache (the qsnap blob) holds a dead set, and a dead set is a function of call resolution: quality.h's isDeadCandidate reads g.inEdges. The blob's filename key folds the qsnap scheme, the extraction identity, the excludes, the file-size ceiling and the HEAD sha. A resolution-only change moves none of them, so two builds that resolve calls differently, sharing one cache dir on one HEAD, serve each other's dead set. Verified with two real builds before writing the gate: main a55b118 as built, and the same tree with graph.h's keepStdQualifiedCandidates (d39554d's std:: guard) switched off. Over a two-file fixture where `std::launder( &v )` may bind an in-repo Pool::launder, with a shared XDG cache: guarded build, cold cache regressions="0", exit 0 guarded build after the unguarded one warmed it a gating dead-code row on untouched Pool::launder, exit 2 unguarded build, working tree deleting the call the real gating dead-code row, exit 2 (cold) same run after the guarded build warmed it regressions="0", exit 0 (the regression hidden) One qsnap blob in the shared dir both times. test/qsnapproducercheck.sh forges a real warmed blob by structure (the header ends at the fnv1a64(HEAD sha) field; a parse that does not land on the trailer FAILs). Arms F and G are matched pairs differing in one thing: dead keys dropped (phantom) or added (hidden) with the producer bytes kept, a control that must change the answer, and the same forgery with those bytes flipped, which must be refused and match a cold cache. Arms A-E pin the key, the header, the derivation of the identity from source content, the blob's producer slot and warm reuse. RED against this commit's binary: 8 FAIL / 5 PASS. Both controls fire (F1 exit 0 -> 2, G1 exit 2 -> 0), F2 has no producer bytes to flip, G2 hides the regression (cold exit 2, forged exit 0), and A/B/C/D find no producer identity. Registered in test/regression.sh, the shard weights (5.2 s standalone) and the generated gate count (619). The fix is the next commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… set and all The qsnap and qbody cache keys, and the blob header they share, now carry the PRODUCER IDENTITY: the SHA-256 over every file under src/ and queries/, derived on each build by cmake/source_identity.cmake and compiled in as one generated definition (build/generated/source_identity.cpp, declared by src/sourceidentity.h). serializeSnapshot writes fnv1a64 of it after the extraction identity; deserializeSnapshot refuses a mismatch. qheadsnap keeps its key: the ingest blob holds extraction facts only. Derived, not a kResolverVer to bump. This cache's record: B10.1a's isDeadCandidate exemption and 28c7d32's kParserVer bump each changed what a blob means without the bump that would retire it, none of the twelve scheme versions cites a resolution change, and d39554d weighed the caches in its own message and still missed this. A derived identity renames the blob on any source change, whoever remembers what. A .cpp, not a generated header, so a changed identity recompiles one tiny object instead of main.cpp and ingest.cpp. The header lives in src/, not src/infra/: it names this build, and infraportcheck (C) forbids that in the vendorable layer. Header shape changed: kQSnapCacheScheme 12 -> 13, kQBodyCacheScheme 3 -> 4 (the P0.2 precedent), qschemetrip re-pinned with a log entry. MEASURED. The identity script: 49 ms per build, 191 files, equal to an independent Python derivation. Cost of the rename, ripwire's own tree, private XDG cache, five runs each, stdout asserted identical: warm --quality-delta median 3.04 s (2.78-3.21); qsnap recomputed over a warm ingest median 5.12 s (4.85-5.34). The two-build experiment re-run on this tree (fixed build vs fixed build with the std:: guard off): one qsnap blob per build in the shared dir, both orders byte-identical to a cold cache, no corrupt alert. GATE test/qsnapproducercheck.sh: 16/16 PASS on the plain build (8 FAIL / 5 PASS on the previous commit). Under asan/ripwire, through a wrapper teeing every child's stderr: ALL PASS, 0 sanitizer reports. cachefuzzcheck ALL PASS including its qsnap ASan sweep. Targeted batch, plain build: infraportcheck, qschemetripcheck, qextractionkeycheck, qsnapcachecheck, evictioncheck, qsnapprefetchcheck, editcheckcheck, g1freshcheck, manifestcheck, gatecountcheck, limitstablecheck, gitstampcheck, selfcontainedcheck, xmlwellformed, binoverridecheck, clonededupcheck, fixedbufsweep, portablebuildcheck, g1configcheck, headsnapcachecheck, qualityexcludecheck, qualitycheck, gateexitcheck, pargatescheck, readmedriftcheck, deckcheck, noaliascheck, crossdirincludecheck all rc=0. ripwirepubliccheck arm 7 was red only while src/sourceidentity.h was untracked (it sweeps tracked files); re-run after this commit. Found on the way, not this change's defect: an experiment restore of graph.h landed in the same second as main.cpp.o's write, Apple's make 3.81 compares whole seconds, and a "successful" incremental rebuild kept a guard-off main.cpp.o (--callers=launder count 1, not 0). Deleting the object fixed it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…dentity # Conflicts: # CHANGELOG.md
…dentity # Conflicts: # CHANGELOG.md # test/regression.sh
…nored as this build's floor The sidecar --quality-baseline writes holds `dead <key>` records, and a dead set is a function of call resolution (isDeadCandidate reads g.inEdges). It was stamped with `head <sha>` alone, and selectBaseline compared only that, so at one HEAD a floor pinned by one build was compared against another build's working tree. The qsnap cache had the same defect; this is the file a user writes on purpose. Measured with two real builds of d8c225a, as built and with graph.h's keepStdQualifiedCandidates forced to `return true`, over this gate's two-file fixture (`std::launder( &v )` binds an in-repo Pool::launder only without the guard): phantom: pinned unguarded, delta guarded -> gating dead-code row on untouched Pool::launder, exit 2 (the guarded build against its own pin: exit 0) hidden: pinned guarded, delta unguarded, only std::launder call deleted -> exit 0 (the unguarded build against its own pin: exit 2, the real regression) test/qbaselineproducercheck.sh pins it with matched pairs over a real pin, one thing different per pair and each mutation asserted to have taken: dead records dropped (phantom) or useIt's real dead key added (hidden) with the producer record kept (controls that must change the answer), and the same forgery with one hex digit of the producer flipped (must give the no-sidecar answer, leave the file byte-identical, and name the foreign pin). Beside them: an unstamped v6 pin, a v5 pin, a pin both stale and foreign, a root with no git, the MCP quality_delta verb, the legend and --help=all. Red first: against d8c225a's binary 12 rows fail (format, both flipped-producer arms, unstamped, v5, no-git, MCP, legend, help) and the controls pass (honored pin, C1, D1, stale self-heal, the no-git honored pin). Registered in test/regression.sh and .github/pargates-shard-weights.json; docs/gatecount_build.py wrote 620. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… floor, dead set and all The sidecar is now format v6 and carries `producer <64 hex>`, the build's source identity (producerIdentity, cmake/source_identity.cmake). selectBaseline honors a pin at the current HEAD only when that record names this build. One that is missing or names another build is the new BaselineSource::Foreign: --quality-delta falls back to the HEAD tree this build computes, reported as baseline="git-HEAD (foreign sidecar ignored)" with one CLI stderr line and a full-legend sentence. Unlike a stale pin it is never unlinked, on either arm, because the build that wrote it can still use it (a PATH binary and ./build/ripwire in turn). A root with no git has no fallback and exits 1 naming the foreign pin. A pin at another sha stays stale first and keeps its self-heal. Demoting dead-code rows instead was ruled out: that cannot surface a regression whose row never appears, and another build can compute any kind differently. Every existing sidecar is v5, which only a pre-stamp build can have written, so baselineHeaderIsForeign now refuses it. That refusal was reported as Absent: the CLI printed baseline="git-HEAD" (no sidecar existed) and "no <file>" one line under the refusal naming it (measured on d8c225a with a v4 header). BaselineReadStats::olderFormat now routes it to baseline="git-HEAD (sidecar unreadable)". In a root with no git, both arms' fatal messages now name an unreadable sidecar rather than "no <file>". The MCP messages move into mcpNoBaselineMessage (the CLI twin's shape) instead of a longer nested conditional. Measured on the fixed tree with the same two builds (the fix plus the guard off in the second): all four cross-build runs give the same-build answer. Phantom exits 0, hidden exits 2 with the real dead-code row, both marked foreign, sidecar still on disk. Same-build pairs are unchanged. Gates: test/qbaselineproducercheck.sh 26/26 PASS (12 FAIL on d8c225a). Under ASan it plus qrevtokencheck (25) and qualitystalecheck (37) pass with zero sanitizer logs; a deliberate heap overflow built by the same clang wrote its log to the same place, so the capture is live. Targeted set of 34 gates green. Working-tree --quality-delta: gating="0"; minor rows readBaseline ccx 35->36, emitQualityDeltaLegend 30->31, kHelpHead LOC 1091->1093. test/qrevtokencheck.sh's hand-written sidecars move to the v6 header so its hostile head stamps still reach the head-stamp path. Pins moved: test/printf_parity.manifest help_all only (UPDATE_GOLDEN_EXPECT matched); docs/COMMANDS.md regenerated (the --quality-baseline and --quality-delta rows). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…241, #245, #246) This lane is stacked on the qsnap producer-identity lane, whose one new commit since d8c225a is its own merge of origin/main. Two conflicts, both expected: test/regression.sh — the gate loop is the UNION: 620 each side, qbaselineproducercheck (here) and diagnoticecheck (main), 621 byte-sorted; docs/gatecount_build.py re-run to WRITE 621 into README.md, docs/EVALS.md and present/deck5_ripwire_build.js, then --check. CHANGELOG.md — both sides kept: this lane's entry stays beside the qsnap entry it extends, main's two follow. docs/limits_build.py --check matches the merged src/. The merge touched none of this lane's sources, the printf parity manifest or docs/COMMANDS.md; both are re-checked against a --clean-first rebuild of the merged tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ol and rerouted adaptivecheck's broad query c0c1125's mcpNoBaselineMessage spelled its sidecar name `const std::string file( rw::quality::kBaselineFile );`. Paren initialization reads as a declaration, so extraction indexed a symbol named `file` (mcpverbs.h:3360), and `--for="file"` over src/ routed name-exact to it: `adaptive: kept 1 of 40 - only 1 symbols matched this query`. test/adaptivecheck.sh arm (c) uses "file" as its BROAD query on the live tree and expects "no relevance cliff", so it went red: the one failure in the merged branch's full suite (gates=635 pass=632 skip=2 fail=1, standalone red twice). The local is now `sidecarName`, `=`-initialized. The same probe routes subtoken+body:broad again, "kept 40 of 40 - no relevance cliff". adaptivecheck, qbaselineproducercheck, mcpflagshipcheck, mcpclidiffcheck, mcpcontractcheck, mcpframehonestycheck and gateexitcheck all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (18)
Included review availability: Your plan provides up to 8 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughThe build now generates a source identity and embeds it in both executables. Quality caches and baseline sidecars store and validate this identity. Foreign or unreadable artifacts receive distinct handling. Two producer checks and related documentation are added. ChangesQuality producer identity
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~45 minutes Change: Bug fix Suggested reviewers: Merge Risk: ⚪ Minimal · up to The producer-identity changes have no supported merge-blocking risk in the reviewed scope. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 43.18% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 44 functions across 11 files. (7 skipped: 7 unsupported.)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
Comment |
…ly workflow) into integration/train-1 Main moved after #263's CI went green; merged once with rerere disabled. One textual conflict: - CHANGELOG.md: KEEP ALL. The train's #255 and #259 entries, then #252's "a deep or odd-shaped argument, source file or skills tree could crash or stall a verb"; #262's nightly entry auto-merged. 12 Unreleased entries. Auto-merged and re-verified on the rebuilt binary: README.md, docs/LIMITS.md, docs/TUNING.md, and four src files the train also changed (src/query.h, src/verbs_navigate.h, src/ingest_astquery.h via #253; src/mcpverbs.h via #259). docs/gatecount_build.py --check (624) and docs/limits_build.py --check (217 caps) are clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nary help_all conflicted between the train side (redhat-et#250's --help text, 56f3a50f) and redhat-et#259 (its own cli.h help text, d5c6cea0). Neither describes the merged binary: printffmtparitycheck on the clean-built 9e74894 read 7a34d653ae468c98640688d86f2844b74dba7d5a7202a3b44bec2c65fd2413b8, and every other row passed. Re-recorded with UPDATE_GOLDEN=1 UPDATE_GOLDEN_EXPECT=help_all: moved={help_all}, 41 unchanged, and the expectation matched. printffmtparitycheck is then ALL PASS over 42 verbs. docs/COMMANDS.md needed no regeneration: docscommandscheck is ALL PASS on the same binary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The doc-level items from the independent reviews of redhat-et#253 and redhat-et#259, folded into the train. No behaviour change. redhat-et#253 (CHANGELOG, its "an answer depended on how the root was typed" entry): (a) CodeRabbit's wording: the six spellings agree "byte-identical output, once the printed `root=` and `est_tokens=` are normalised". Raw outputs differ in `root=` on purpose, and `est_tokens=` prices those bytes. (b) New disclosure: `ripwire tests/` no longer counts its own files as tests (no layer="test", no test seeds for --affected/--test-gate, no dead-code exemption); it answers as `cd tests && ripwire .` does. (c) New disclosure: the `.` answer itself moves slightly on byte-capped output, because the byte models charge a path as printed. The default --lint page on this repo went 680 -> 689 rows. redhat-et#259 (legend and guidance text only; wording asked for by its review and its session): (d) kQdBaseHeadUnreadable now says, like the foreign reading, that an allow-dirty pin's absorbed findings are not in force. The v5 upgrade is exactly where an allow-dirty pin gets dropped and an exit flips 0 -> 2. (e) "re-pin" becomes "re-pin on a clean tree (commit or stash first)" in the foreign legend, the unreadable legend, both CLI stderr lines for a foreign pin (git root and no-git root), the MCP no-git twin, and redhat-et#259's CHANGELOG entry. A plain --quality-baseline refuses on a dirty tree, which is where a user meets this message. Legend text carries no double hyphen (it lands in an XML comment). (f) The S2 comment above relForHash (src/model.h, moved there by redhat-et#253) no longer calls the quality baseline committed and portable: it is gitignored, re-pinned before a change, and producer-stamped. The comment keeps the root-spelling reason both sidecars hash root-relative paths. redhat-et#259 CHANGELOG, a published-claim accuracy fix from its session: "A v6 pin read by an older binary is refused the same way ..." is replaced by the exact text supplied. An older binary refuses a v6 pin but reports it the old way, as baseline="git-HEAD" with a "no .ripwire_quality_baseline" line, and the file is intact. A v5 pin at an older commit is no longer self-healed: it is refused as unreadable, with two stderr lines per run, until re-pinned or deleted. Not changed, as asked: the MCP spelling of the unreadable marker (pre-existing, a follow-up lane). redhat-et#255: nothing to fold (its two LOWs are accepted as is). Verified on the rebuilt binary (main.cpp.o/ingest.cpp.o newer than the edits; the new legend string present in the binary): printffmtparitycheck, compactlegendcheck, legendcostcheck, legendcoveragecheck, docscommandscheck, qbaselineproducercheck, xmlwellformed, floormarkcheck, testgatelegendbudgetcheck, mcpattrparitycheck all rc=0, 0 FAIL rows. No pin moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stacked on #255 (
lane/qsnap-producer-identity). Review only5db5f9b9(gate),c0c1125f(fix) and3189ccb6(a one-line follow-up the full suite caught).2a31bc7bis the merge of #255 atae7e1e01, which carriesorigin/main31e788ce. This branch lands in the same integration train as #255, right after it. The coordinator resolves the shared products (gate loop, count, shard weights, CHANGELOG order,COMMANDS.md/printf_paritypins) once, on the merged tree.The defect
--quality-baselinewrites.ripwire_quality_baseline, and itsdead <key>records depend on call resolution (isDeadCandidatereadsg.inEdges). The sidecar was stamped only withhead <sha>, andselectBaselinecompared only that. So at one HEAD, a floor pinned by one build was compared against another build's working-tree dead set. It's the qsnap cache defect again, this time in a file the user writes on purpose. The window is ordinary: pin with the installedripwire, then run the delta with./build/ripwire(or after an upgrade) before the next commit.Measured with two real builds of
d8c225a2: one as built, one withgraph.h'skeepStdQualifiedCandidatesforced toreturn true. Fixture:src/pool.cppstruct Pool { int launder( int x ) {…loop…} };andsrc/use.cppcalling*std::launder( &v ).sidecar, gating dead-code onPool::launder, exit 2std::laundercall deleted)sidecar, exit 0The fix
producer <64 hex>, the build's source identity (producerIdentity(), from the qsnap lane).BaselineSource::Foreign: a pin at the current HEAD whose producer is missing or names another build is not the floor.--quality-deltafalls back to the HEAD tree this build computes and reportsbaseline="git-HEAD (foreign sidecar ignored)". The CLI prints one stderr line and the full legend defines the marker. The pin is never unlinked on either arm, since the build that wrote it can still use it. A root with no git exits 1 naming the foreign pin. A pin at another sha is still stale first and keeps its self-heal.baseline="git-HEAD"plus a stderr line saying "no .ripwire_quality_baseline" right under the refusal naming the file (measured ond8c225a2). It now readsgit-HEAD (sidecar unreadable). In a root with no git, both arms' fatal messages now say "unreadable" instead of "no ". Older binaries refuse v6 the same way.--help=allstates the rule and names the marker.test/qrevtokencheck.sh's hand-written sidecars move to the v6 header so its hostile head stamps still reach the head-stamp path.On the fixed tree, the same two builds give the same-build answer in all four cross-build runs: phantom exit 0, hidden exit 2 with the real row, both marked foreign, sidecar still on disk.
Gates
test/qbaselineproducercheck.sh(new): 26/26 PASS. Againstd8c225a2's binary 12 FAIL: format, both flipped-producer arms, unstamped, v5, no-git, MCP, legend, help. The controls pass there. Matched pairs over a real pin: the controls, with the producer kept, must change the answer and do; the flipped-producer forgeries must give the no-sidecar answer and leave the file byte-identical.gateexitcheck(G2) caught a one-line&& ok || noin the new gate.qrevtokencheck(25) andqualitystalecheck(37) pass with zero sanitizer logs, on the pre-merge and merged trees. Positive control for log capture: a deliberate heap overflow built by the same clang wrote its report to the same place.--json.--quality-delta=$(git merge-base origin/main HEAD)..HEAD: gating="0". Five minor rows:readBaselineccx 35→36,emitQualityDeltaLegend30→31,kHelpHeadLOC +2 (this lane);deserializeSnapshotccx +2, LOC +5 (qsnap lane).2a31bc7b(the merged tree, before3189ccb6):gates=635 pass=632 skip=2 fail=1. The skips are the environmental two (argvdiffcheck,editchecknotecheck);g1freshcheckran against a fresh ASan tree and passed. The one failure,adaptivecheckarm (c), was caused by this lane and was deterministic (red twice standalone).c0c1125f'smcpNoBaselineMessagedeclaredconst std::string file( … ), which extraction indexed as a symbol namedfile, so the gate's broad live-tree query--for="file"routed name-exact and kept 1 of 40.3189ccb6renames the local and uses=initialization. The probe routessubtoken+body:broadagain (kept 40 of 40, no cliff), andadaptivecheck, this lane's gate,mcpflagshipcheck,mcpclidiffcheck,mcpcontractcheck,mcpframehonestycheckandgateexitcheckpass. No local full suite has run on3189ccb6: per the coordinator's plan for a loaded shared machine, CI's legs are that run.Pins moved
test/printf_parity.manifest:help_allonly (UPDATE_GOLDEN_EXPECT=help_allmatched). Still green on the merged tree with no re-record.docs/COMMANDS.md: regenerated (the--quality-baselineand--quality-deltarows).diagnoticecheck+ this lane's gate).🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Documentation
Tests