Skip to content

integration: train 2 — param receiver binding (#248), untyped receiver locality + final-segment disclosure (#254), std:: field compose (#257) - #265

Merged
joyful-ii-V-I merged 20 commits into
mainfrom
integration/train-2
Sep 17, 2026
Merged

joyful-ii-V-I merged 20 commits into
mainfrom
integration/train-2

Conversation

@joyful-ii-V-I

@joyful-ii-V-I joyful-ii-V-I commented Sep 17, 2026 •

Copy link
Copy Markdown
Collaborator

Integration train 2. It lands three reviewed resolver PRs together, so the shared parser version, the extraction-manifest hash and CHANGELOG are reconciled once and CI runs once.

Each had an independent review (#254 re-reviewed after its disclosure rework). #257's CodeRabbit review covered the stack's full range and had 0 actionable findings.

Resolutions (each is explained in its commit):

Verification on 92b4c91d:

  • Full suite: 635 gates, 632 pass, 3 environmental skips, 0 fail.
  • Resolver, legend and pin gates all pass.
  • --quality-delta (merge-base range) reports gating="0".

Train 1 (#263) is ahead of this train. When it merges, this branch merges main once more and re-derives the hash.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved call resolution for typed parameters, lambdas, range-based loops, and reference locals by using their declared types within the correct lexical scope.
    • Qualified types now resolve using their final name segment when appropriate, with resolution provenance clearly reported.
    • Standard-library-qualified types no longer incorrectly narrow to similarly named project types or create misleading relationships.
    • Unknown or untyped receivers now remain honestly ambiguous instead of receiving inappropriate locality preference.
    • Updated parser and cache handling preserves correct results after these resolution changes.

joyful-ii-V-I and others added 20 commits September 16, 2026 13:39
…he caller's own class

`int Decoy::plainCaller( Target& other ) { return other.pick( 1 ); }` answered
--callees=plainCaller with ONE edge to Decoy::pick: precise, no amb=, wrong. Rule 2 narrowed
a receiver only through a typed local (LocalBindKind::Type); a parameter's written type
(ParamType) was captured but read only by collectFieldUseSites, so the call fell through to
the name ladder and the S6-C locality tie-break gave the caller's own class the scope credit
(pin census: mech=locality, 2 -> 1).

The fix: Rule 2, and CHA-lite through the same lookup (Narrower::recvVarTypeName), reads
ParamType records LEXICALLY. graph.h buildScopedRecvDecls lists, for every name with a
ParamType record, all its declarations in the definition (each VarDecl with its scope span)
and attaches each Type/ParamType record to the declaration whose VarDecl shares its record
position (Binding::startByte, new, in the padding after `kind`: sizeof unchanged, pinned).
The innermost declaration covering the call site decides; an untyped one, a tie, or no
covering declaration answers nothing. Names with no ParamType record keep the flat varType
table byte-identically.

Why not the obvious fold into varType: measured first. It fixed arms 7/8/11/15 and minted
three NEW precise wrong edges on the gate fixture (arms 12-14: a range-for variable's type
reaching a later `auto` loop of the same name, a same-named field read after the loop, a
parameter hidden by an untyped loop variable), and arm 10's tombstone kept the locality pin.

Qualifier guard: a written type is its final segment and class names carry no namespace, so
`const std::map<K, V>& ref; ref.lower_bound( q )` narrowed to an unrelated in-repo `map` —
three such edges on a private 129,759-call-site C++/ObjC++ corpus. A declaration's qualified
written type now rides its Type/ParamType RawBind (importedName, ingest_binds.h
qualifiedNameText) and the lexical lookup refuses it: removes all three, forgoes 11 correct
narrows through qualified in-repo types (those keep main's answer). kParserVer 96 -> 97 (+
quality.h mirror): the record format is unchanged, its content is not, and a warm 96 blob
would narrow a qualified type. An include-visibility guard was measured first and rejected:
path-precise includes miss include-root spellings and it refused ~150 correct narrows there.

Measured, --pin-census --no-cache, main f8e6087 binary vs this change:
  private corpus: 587 of 129,759 sites change target — 373 splits narrow (300 Rule 2,
    73 CHA cone), 147 declined calls gain an edge (bound 80,432 -> 80,583), 66 pins/splits
    move to the parameter's type, 1 edge lost (a friend function scoped inside its class
    self-loops); 956 more keep their target, now decided by Rule 2. Every category sampled
    and read against the source. Wall time within noise (3 cold runs each, 1.66-2.51 s).
  this repo src/: 53 splits -> one Rule-2 pin; nothing else moves target.

Gate: test/narrowcheck.sh arms 7-18 (generated fixture). RED on main: 10 rows (7, 8, 10 x2,
11, 12, 13, 14, 15). RED on the flat-table fold: 10 x2, 12, 13, 14. RED on the lexical lookup
without the qualifier guard: 17. GREEN on this commit: 23 PASS, and under ASan.
Controls that encoded "a parameter has no binding" moved to an untyped `auto` receiver:
narrowcheck (control/param.cpp -> control/untyped.cpp), chacheck, chaconecheck g5,
localitycheck (its call no longer reached the tie-break it tests), resolverhonestycheck F9
(check_signal had gone vacuous on one edge). fieldnarrowcheck (h) 7 -> 6: shadowParam(
Decoy& m_x ) now resolves to the parameter's type; (s1) also asserts Pool::acquire is not
linked (red on main).

Out of scope, stated: an untyped receiver still reaches the locality tie-break; typed locals
keep the flat table and its qualified-type collision. The reported arity split on a typed
local (pick(int) + pick(int,int) for a 1-arg call) is B2.2's documented argCount > params
rule and reproduces identically on the bare-name ladder.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CHANGELOG.md: both Unreleased entries kept (#240's Changed, this lane's Fixed).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ument constructor and a 30-complexity builder

quality-delta over the merge-base range reported gating=3 on this lane's own rows: ctorTypeOf's
byte-bounds tail cloned nodeTextOf (duplication + new-clone-of-reused-helper), and Narrower's
constructor went 4 -> 6 parameters against a bar of 5. Non-gating but real: the new
buildScopedRecvDecls at cognitive complexity 30, and bindsVisitNode 88 -> 94 with three new
parameters across pushRawBind/emitBind/emitDeclBinds.

- ctorTypeOf and qualifiedNameText read through nodeTextOf.
- ScopedRecvDecls is one struct holding the table and the bindings its indices point into, so the
  Narrower takes one argument for both (5 parameters).
- buildScopedRecvDecls is three single-loop steps: addParamTypedNames, addRecvDeclScopes,
  attachRecvDeclTypes.
- DeclType { name, qualified } travels as one argument. pushTypedBind is now the ONE record body
  and pushRawBind a one-line wrapper with no qualified text, so pushRawBind and emitBind keep
  their signatures, and declaredTypeOf takes the declaration branch's two ternaries out of
  bindsVisitNode. (A first cut stamped importedName after the push, emitFnBind's shape — and
  quality-delta reported it as a 70-token clone of emitFnBind.)

Zero behaviour change, measured: --pin-census and the default map on the private corpus are
byte-identical (cmp) to the previous commit's binary, as is the census over src/; narrowcheck,
chacheck, fieldnarrowcheck, resolverhonestycheck, fieldusescheck, shadowcheck and
cacheidentitycheck ALL PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
quality-delta --quality-delta=$(git merge-base origin/main HEAD)..HEAD reported gating=2 after the
refactor: api-surface contract-change on Narrower::Narrower and its one call site in buildGraph
(4 -> 5 parameters). That is the fix itself — the Narrower has to be handed the lexical
declaration table — so it is acked through the binary (--quality-ack --ack-only=api-surface),
which wrote exactly two +ack rows, one per symbol, and nothing else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d, a moved parser-version pin, two one-line verdicts

Full suite at 17a9f5f: gates=632 pass=627 skip=2 fail=3, all three this lane's own.

showcasecapturecheck (H): the 0.6.1 release capture publishes --at=src/graph.h:3406 as the FIRST
body line of rankGraphTeleport, so it has zero slack — the ~100 lines this lane added to graph.h
above it made 3406 resolve to buildGraph. The capture is the release PR's to regenerate (a lane
recording it publishes local branch names), and the code had a better home anyway: the lexical
table builder (buildScopedRecvDecls and its three steps) moves from graph.h to resolve.h, beside
the ScopedRecvDecls type and Narrower::recvVarTypeName that reads it. graph.h's diff is now five
lines in, five out, at the Narrower construction; rankGraphTeleport is back at 3404 on this tree.

qschemetripcheck: its manifest hashes ingest_cache.h's kParserVer declaration, which moved 96 -> 97
with the quality.h mirror in the same diff (qextractionkeycheck green), so this is the gate's own
re-pin case: UPDATE_GOLDEN=1, and the new pin equals the current= hash the suite printed.

gateexitcheck (G2): narrowcheck's expectRows helper and arm 18 reported through a one-line
'A && ok || no'; both are if/else now.

No behaviour change: --pin-census and the default map over the private corpus are byte-identical
(cmp) to the fix commit's binary. qschemetripcheck, gateexitcheck, showcasecapturecheck,
narrowcheck, chacheck, chaconecheck, localitycheck, fieldnarrowcheck, resolverhonestycheck,
fieldusescheck, shadowcheck, fnptrcheck, cacheidentitycheck, qextractionkeycheck, manifestcheck
ALL PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…caller's own class

`auto other = make(); return other->pick( 1 );` inside Decoy answered --callees with one
precise edge to Decoy::pick, no amb=. The S6-C locality tie-break excluded field-of-this,
field-of-var and super() receivers but kept every NamedVar receiver on the premise "a typed
var already narrowed above" — and a receiver no rule typed never narrowed. The scope-segment
credit then handed the same-file tie to the caller's own class.

The fix: a NamedVar receiver whose type no receiver rule (2, 2c, 2b) established keeps the
tie-break's credit only up to the end of the caller's FILE segment (`path::`), so same-file
and same-directory locality still count and the class segment does not. That covers an
untyped local, a member Rule 2b cannot read (`std::unique_ptr<Target> rep_; rep_->pick()`)
and a typed variable whose type defines no such method. `this->` and bare calls keep the
whole tie-break.

Measured first, with --pin-census --no-cache, the lane tip's binary against this logic built
behind a temporary environment switch (removed; the final binary is measured in the next
commit): rocksdb 121 call sites change target (114 locality pins -> splits, 7 splits widen),
a private C++ corpus 71 (51 + 20), django 72, rails 145 (119 + 26), this repo's src/, vue-core
and go/net 0. Samples read against the source: 14 of 14 rocksdb pins wrong, 14 of 16 on the
private corpus — mostly delegation (`rep_->Name()` in Wrapper::Name, `tree_.upper_bound( key )`
in btree_container::upper_bound); one right pin is now a 3-way split that keeps it.
Skipping the tie-break entirely for these receivers moved NO target on any of the seven
corpora, but relabelled every {caller itself, X} tier from locality to unique (262 rocksdb,
53 private, 24 django, 101 rails) and dropped its lpin= disclosure — so the file credit stays.
Stated floor, not closed: tier 1 still admits same-file candidates alone, so a delegation
whose true target lives in another file lands on a same-file namesake before the tie-break.

Gate: test/localitycheck.sh arms 5-9 on a generated fixture. RED on the lane tip: 5
(untyped local), 6 (member of an unreadable type), 7 (typed, no such method) — each a
locality pin to the caller's class. Arm 8 (file credit kept, mech=locality) is RED on the
skip-the-tie-break variant; arm 9 is the Rule-2 control. GREEN on this commit.
Controls moved for the reason the fix exists: localitycheck's HIGH-1 probe was an untyped
local that no longer earns scope credit, so it could not tell a byte-prefix tie-break from a
segment-aware one; it is `this->go()` in a class template with a dependent base now, plus a
census liveness row (pre=2). clsrecvcheck (B)/(C)/(E) asserted the caller-own-class pin
STANDS for `item.validate( v )`; they assert the honest two-way split now (the contrast with
the firing route is unchanged) and (F) reads ambiguous=3 with no locality pin. canoncheck's
comment describing the fixture is updated. chacheck, chaconecheck, resolverhonestycheck and
fieldnarrowcheck pass unchanged: their untyped controls are scope-less free functions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…esake, and an in-repo qualifier refused a parameter

`std::map<int, int> table; table.find( k )` pinned an in-repo map::find: Rule 2's flat
per-function table records a written type's final segment, and class names carry no
namespace. The parameter-receiver lane refused every QUALIFIED parameter type for the same
reason; typed locals, constructor-inferred locals and C++ assignments kept narrowing.

Measured before choosing the rule, --pin-census --no-cache, lane tip vs switches (removed):
refusing ANY qualifier on locals moved rocksdb 424 sites, a private C++ corpus 27, src/ 9.
Joined to each receiver's qualified text: every rocksdb and src/ one was a non-std qualifier
(ROCKSDB_NAMESPACE::Status, test::SleepingBackgroundTask, rw::notes::NoteIndex), every sampled
one read correct, and it minted new wrong ladder pins where the refused narrow fell to a same-dir
namesake (`value_slice.data()` -> MultiGetJNIKeys::data). The only wrong narrows it removed on
the three corpora were six `std::map` locals on the private corpus. The same join on the
lane's parameter guard: refusing only std:: instead moves rocksdb 94 sites (e.g.
`WriteBatch::Handler* handler; handler->MarkCommit( xid )` off WriteBatchInternal::MarkCommit),
private 12, src/ 5, all sampled correct but ONE: an `ankerl::unordered_dense::map<…>&` alias
template that narrows to the in-repo map again.

The fix: `std` is reserved to the implementation, so no in-repo class IS a std:: type.
resolve.h namesStdType is the one predicate: the lexical (parameter) lookup refuses a std::
written type, and the flat table tombstones the variable on one (a tombstone, because the flat
table cannot tell which declaration of the name is in scope). Every other qualifier narrows on
its final segment again, parameters included. The C++ assignment `x = std::map<K, V>()`
recorded no qualified text; it goes through pushTypedBind with declaredTypeOf's constructor
DeclType now, so kParserVer 97 -> 98 (+ quality.h mirror): format unchanged, content changed.
Stated floor, pinned by narrowcheck arm 24: a qualifier that is neither std nor the class's
own namespace still narrows by name; closing it needs the namespace chain in Symbol::scope.

Final binary (this commit, both fixes) vs the lane tip, --pin-census --no-cache: rocksdb 211
sites change target (117 locality, 94 qualified parameters; bound= 200,009 -> 200,036), private
88 (70 locality, 6 std::map locals stop narrowing, 12 qualified parameters), django 72, rails
145, src/ 5, vue-core 0, go/net 0. The assignment capture moved no site on these corpora.
Wall time on rocksdb within noise (3 cold runs each, load avg 42: 1.69-2.19 s vs 1.73-2.80 s).

Gate: test/narrowcheck.sh arms 17-24. RED on the lane tip: 19 (typed local), 20 (constructor
local), 21 (assignment), 23 (in-repo qualified parameter), 24 (the floor: the lane refused it).
RED without the assignment capture: 21. GREEN on this commit. Arm 17 moves from `ext::map` to
`std::map` (the refused shape) and 22 is the in-repo qualified local control.
quality-delta (working tree vs the previous commit): gating=0; a first cut's prefix-strip loop
cloned lintdetail::ltrim and is gone (qualifiedNameText already strips a leading `::`).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…yped-locality

The lane pushed a fix commit for three full-suite reds after this branch was cut; two of them
bind this branch's change as well, and the resolution keeps both sides' intent.

src/graph.h: the lane moved the lexical receiver-table builder to resolve.h so graph.h stays
line-neutral above rankGraphTeleport (the 0.6.1 showcase capture seeds --at=src/graph.h:3406
with zero slack). Took theirs, re-applied this branch's std-guard comment to the moved block,
and made this branch's own graph.h change line-neutral too (+9/-9 against the lane tip,
rankGraphTeleport back at 3404): the tie-break rationale moves into resolve.h
receiverLocalityCap (the cap itself, beside sharedLocality), the flat varType key uses the
existing buildShadowKey (identical bytes), and the flat-table tombstone note joins
namesStdType's comment. No behaviour change: --pin-census over src/, rocksdb and the private
corpus is byte-identical (cmp) to f346283's binary.

test/narrowcheck.sh: took this branch's arms 17-24 with expectNarrow's verdict as if/else
(gateexitcheck G2, the lane's same fix to expectRows and arm 18).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… line this branch moved

qschemetripcheck hashes ingest_cache.h's kParserVer declaration; f346283 moved it 97 -> 98 with
the quality.h mirror in the same diff (qextractionkeycheck green). This is the gate's own re-pin
case: UPDATE_GOLDEN=1, and the new pin (8ca11f67…) equals the current= hash the red run printed.
kQSnapCacheScheme stays 12 and kCacheVersion 22 — record layouts and snapshot semantics are
unchanged. The RE-PIN LOG entry says so, and names the 96 -> 97 move the parameter-receiver lane
re-pinned without one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… class named string

`struct Record { std::string name_; int nameLength() { return name_.size(); } };`
pinned name_.size() to an in-repo `string::size` (census mech=receiver-rule, no
amb=). The field capture (ingest_relations.h captureFields) keeps a qualified
type's final segment, and all three readers of that record took `string` for
any in-repo class of the name: Rule 2b's field-type table, the HAS-A edges
(Record -> string), and the member index (--uses=string.len pinned name_.len and
this->name_.len). The previous commit refused a std:: written type for locals
and parameters; fields were the third place a written type is read.

The fix: a field's compose record carries the namespace its type was written in
as its qualifier (writtenTypeNamespace) — the (name, immediate qualifier) pair a
call ref already carries, so `std::string` is `string` in `std`. resolve.h
fieldTypeWrittenInStd is the one predicate; buildFieldNarrowTables records ""
for such a field and the HAS-A loop draws no edge. The member index reads the
same table, so it needs no change of its own. Every other qualifier narrows as
before (`store::Text body_; body_.size()` still reaches Text::size).
kParserVer 98 -> 99 + the quality.h mirror: format unchanged, content changed.

It records "" rather than skipping the field, because a skip is a measured
wrong answer: rocksdb's test_util/testutil.h `StringSource { std::string
contents_; }` and db/log_test.cc `StringSource { Slice& contents_; }` share the
key StringSource#contents_ (class names carry no namespace), the disagreement
tombstones it, and dropping the std record at capture pinned four testutil.h
contents_.size() calls to Slice::size. An empty type tombstones the entry
exactly as a second real type does.

Audit before touching a compose ref's fields: every reader of
Reference::qualifier excludes compose refs — the resolve loop and pin census
(isResolvableCallReference), model.h macro retag and shadow suppression
(isCompose filters), the inherit-only loops (graph.h isInherit), and the
Python/Rust/Elixir-only readers (quality.h, keepRustQualifiedCandidates,
elixir_resolve.h, expandElixirImplementationReferences). The other loops over
ing.references that do not filter isCompose never read qualifier (eval.h,
main.cpp byte stats, impurity and LCOM4 name scans, scip/filter/workspace
copies); the ref sort key is (fileId, startByte, name, role, isInherit). The
qualifier already rides the cache record, so no layout change.

Measured, --pin-census --no-cache and the default map, cfa5792's binary vs
this commit: rocksdb, a private C++ corpus and src/ (a frozen copy, one tree
for both binaries) are byte-identical, and rocksdb's --metrics is too. Counted
with --match, the corpora have 1,258 / 143 / 1,987 fields written `std::X`;
only rocksdb's 1,116 std::string/wstring fields share a name with an in-repo
type (gtest's `typedef ::std::string string`, no members, no HAS-A edge). So a
probe measured the reach: rocksdb plus one `namespace shim { struct string {
size empty data c_str clear append len } }`. The previous binary pins 384 call
sites to shim::string by receiver-rule; this commit leaves 7, all gtest
parameters written `const ::string&` (global namespace, not std, not fields).
357 sites move: 279 land on exactly the unmodified rocksdb census row, 78 are
c_str calls the probe's own second c_str makes ambiguous. --metrics: 455 class
rows lose a composed type from cbo= (446 by one, 9 by two).

Gate: test/fieldnarrowcheck.sh arm q (new fixtures, so (h)'s gauge is
untouched). RED on cfa5792 and on f346283: q1 (Rule 2b), q3 (HAS-A), q5 x2
(--uses, bare and this->). RED on a refuse-every-qualifier capture variant:
controls q2, q4, q6 and the four q7 tombstone rows. RED on the skip-at-capture
variant: exactly the four q7 rows — StringSource/StringSink collisions in both
record orders. (Variants measured on the f346283-based tree; the rebase onto
cfa5792 touched neither captureFields nor the two readers.) GREEN here, 44
PASS. qschemetripcheck re-pinned for the parser version with a dated note, as
its message directs (new pin == the current= it printed).

graph.h stays line-neutral above rankGraphTeleport (3404), so the 0.6.1
capture's published seed still resolves (showcasecapturecheck green). A first
cut hoisted the std-qualified call guard's lambda into a shared function;
--quality-delta read it as a 47-token clone of docparse.h's extension
lookups (gating=3), so the call guard keeps its lambda and the field readers
use a Reference-level predicate. quality-delta (working tree vs HEAD):
gating=0, six minor rows (churn; captureFields LOC 120 -> 121).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t-parameter floor was unstated

Review of #248 (CI coordinator, 2026-09-16) found two text defects in this lane's entry; both
re-measured here rather than copied.

Count: test/narrowcheck.sh at this branch against the main binary (f8e6087) exits rc=1 with
NINE FAIL rows — (7) (8) (10)x2 (11) (12) (13) (14) (15). The fix commit's own list named nine;
'ten' was a miscount.

Floor: a parameter typed as an interface whose methods are pure-virtual declarations cannot narrow
to it (Rule 2 resolves against definitions only), and the narrow lands on unrelated classes that
share the final name segment. rocksdb @ 0e2801ac3, --pin-census --no-cache, main f8e6087 vs
eaadb9d: 79 sites (88 rows) through an Iterator* parameter now split five ways over the nested
memtable/ Iterator classes, none right — 25 were a unique override pin, 26 a split over
overrides, 28 had no edge. Receivers read at the source are all 'Iterator* iter' parameters.
(The review's 32 counts unique->disjoint-split only, main 31e788c against the #254 tip.) Also
stated: a member-initializer call through a typed parameter is outside the body span and keeps
main's answer — measured on this binary as main's own wrong locality pin.

CHANGELOG-reading gates (anchorbodycheck, deckcheck, docdemotecheck, prcontextcheck,
recallevalcheck, ripwirepubliccheck, rubysettercheck, traceminecheck) ALL PASS. No code, pin,
kParserVer or qschemetrip.hash change; main is deliberately not merged (integration train 2).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s a uniquely resolved edge

Relaxing the parameter guard to refuse only std:: types keeps 111 correct narrows through
qualified in-repo types (rocksdb 94, a private C++ corpus 12, src/ 5) and one known wrong one:
narrowcheck arm 24's `ext::map<int, int>& table; table.find( 1 )` pins an unrelated in-repo
map::find where main declines. The typed local twin (`ext::map<int, int> m; m.find()`) did the
same on main already. Nothing on either edge said the qualifier was never checked against the
class's namespace, so a guess read exactly like a uniquely resolved name.

The fix (owner decision relayed by the CI coordinator, scope B): every edge a receiver's
QUALIFIED, non-std written type chose — Rule 2's narrow, parameter or local, or CHA-lite's cone
prune by that type — carries prov="final-segment". The flat per-function table records whether a
declaration wrote the type qualified (resolve.h FlatRecvType / recordFlatRecvType); the lexical
table reads it off the declaration; Narrower::finalSegmentTypeAt answers it per site. The value
takes precedence below split (resolve.h edgeProvenance, which now owns the whole scip > binding
> import > split > final-segment chain graph.h spelled inline), and both the map legend and the
compact legend define it. graph.h stays line-neutral (+32/-32 against #248; rankGraphTeleport at
3404), so the showcase seed --at=src/graph.h:3406 still resolves.

Gate: narrowcheck arm 25 — RED before: (25a) arm 24's wrong edge, (25b) arm 23's correct
qualified parameter narrow, (25c) arm 22's local twin, (25f) map legend, (25g) compact legend.
Controls green before and after: (25d) arm 18's unqualified narrow and (25e) a uniquely named
call carry no prov=. localitycheck, clsrecvcheck, chacheck, chaconecheck, fieldnarrowcheck,
resolverhonestycheck and showcasecapturecheck pass. Byte cost is measured with the collision
guard that follows, on the final binary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d locality pins, and supersede #248's parameter rule by name

Only the C++ precision of the removed locality pins was published (14 of 14 rocksdb, 14 of 16
private), which invites that reading for every language. An independent review sampled the
dynamic-language sites: django 8 of 14 wrong and 6 right, rails 8 of 12 wrong and 4 right, every
right target kept inside the split that replaces it. Both rates are in the entry now, with
Python's `cls` receivers (2 of django's 72) named.

The entry also overturns #248's parameter rule in the same Unreleased section; it now says so
explicitly ("only a written, unqualified type narrows" and "keep their previous answer" are
superseded: a parameter refuses only a std:: type, as a local does). #248's own paragraph is left
to that lane.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…yped-locality

#248's docs-only follow-up (nine rows red on main, not ten; the abstract-parameter and
member-initializer floors stated). CHANGELOG.md: union — #248's new floors paragraph closes its
own entry, this branch's entry follows unchanged. No #248 line is removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ap; a shorter reading lands it at 1.10%

Measured on rocksdb @ 0e2801ac3 against cfa5792's binary: the default map grew 31,366 -> 31,770 B
(+404), of which ~110 B was the legend term and the rest 14 marked edges. The map legend reading
shrinks to final-segment(last-name-match;namespace-unchecked) and the compact one to "a qualified
type matched by last name only": default map 31,711 B (+345, +1.10%), --for bundle unchanged at
8,746 B, whole graph (--top-k=100000) +7,506 B (+0.14%, 355 marked edges), a private corpus's
default map +51 B (legend only). A normalised diff confirms the attribute, the legend term and
est_tokens are the only differences. Coordinator decision: keep the value name, take the shorter
reading, accept the cost — disclosure is the owner's rule.

CHANGELOG: the disclosure paragraph with that before/after table, arm 25 in the gates paragraph,
and one sentence on the name-only collision guard, measured and rejected (967 rocksdb rows fixed;
58 platform-alternate splits and 8 correct log::Writer narrows declined, 10 new wrong unique pins,
132 correct django Template.render splits declined).

Gates: narrowcheck (arm 25 still reads final-segment( in the map legend), compactlegendcheck,
legendcoveragecheck, legendcostcheck, legenddriftcheck, printffmtparitycheck, docscommandscheck,
mcpclidiffcheck, mcpattrparitycheck, floormarkcheck, testgatelegendbudgetcheck, xmlwellformed,
jsoncheck, attrvocabcheck, connectcheck, connectjoincheck: all rc=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ain-2 on main 9f44a36

# Conflicts:
#	CHANGELOG.md
#	test/qschemetrip.hash
…in-2

# Conflicts:
#	test/qschemetrip.hash
#	test/qschemetripcheck.sh
# Conflicts:
#	src/resolve.h
#	test/qschemetrip.hash
#	test/qschemetripcheck.sh
…no side hashed the merged manifest

Main's pin (37dac79a) hashed parserVer 96 over #249's new deserializeSnapshot; #248 (db0e8cce, 97), #254
(8ca11f67, 98) and #257 (01ea4c63, 99) each hashed their own number over the manifest before #249. The merged
tree declares 99 over #249's function, which none of them hashed. kParserVer is assigned in merge order and
equals what each lane already declared (#248 97, #254 98, #257 99); kCacheVersion stays 22, kQSnapCacheScheme 12.
New pin 95818fe5 == the current= the gate printed before UPDATE_GOLDEN; qschemetripcheck and qextractionkeycheck
ALL PASS on this tree. RE-PIN LOG entry added.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 918ac756-c468-4c34-a118-6f46da7dc6d0

📥 Commits

Reviewing files that changed from the base of the PR and between 316dbf2 and 92b4c91.

⛔ Files ignored due to path filters (1)
  • test/qschemetrip.hash is excluded by !test/*.hash
📒 Files selected for processing (28)
  • .ripwire_quality_acks
  • CHANGELOG.md
  • docs/EVALS.md
  • src/compactlegend.h
  • src/graph.h
  • src/ingest_binds.h
  • src/ingest_cache.h
  • src/ingest_model.h
  • src/ingest_relations.h
  • src/model.h
  • src/quality.h
  • src/resolve.h
  • src/serialize.h
  • test/canoncheck.sh
  • test/chacheck.sh
  • test/chaconecheck.sh
  • test/chaconefix/b.cpp
  • test/chafix/cha.cpp
  • test/clsrecvcheck.sh
  • test/clsrecvfix/boxes.py
  • test/fieldnarrowcheck.sh
  • test/localitycheck.sh
  • test/localityfix/loc.cpp
  • test/narrowcheck.sh
  • test/narrowfix/control/param.cpp
  • test/narrowfix/control/untyped.cpp
  • test/qschemetripcheck.sh
  • test/resolverhonestycheck.sh
💤 Files with no reviewable changes (1)
  • test/narrowfix/control/param.cpp

Included review availability: Your plan provides up to 8 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

Changes

The change adds lexically scoped receiver-type resolution for parameters and locals. It preserves qualified type text, excludes std:: types from narrowing, adds final-segment provenance, limits locality credit for unknown receivers, updates field handling, advances parser versions, and expands regression coverage.

Receiver declaration capture
ingest_binds.h, ingest_relations.h, ingest_model.h, model.h
Typed bindings now retain qualified type text and declaration positions.

Lexical receiver resolution
resolve.h, graph.h
Rule 2 resolves the innermost declaration, handles conflicts and std:: exclusions, and updates locality and provenance decisions.

Field and graph handling
graph.h, resolve.h, serialize.h, src/compactlegend.h
Final-segment edges are labeled explicitly. std::-qualified fields do not create narrowing or HAS-A edges.

Tests and documentation
test/*, CHANGELOG.md, docs/EVALS.md
Coverage now includes lexical scope, qualified types, ambiguity controls, locality, field collisions, determinism, and cache behavior.

Priority: ⬇️ Low

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Bug fix

Suggested reviewers: quaterniondrift

Merge Risk: ⚪ Minimal · up to 92b4c

The resolver, cache-version, provenance, and regression-test changes are consistent with the stated behavior and are ready for normal merge checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.78% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 72 functions across 24 files. (3 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the integration train and summarizes the three main resolver changes: parameter receiver binding, untyped receiver locality and final-segment disclosure, and std:: field c…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 52.78% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 72 functions across 24 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch integration/train-2

Comment @coderabbitai help to get the list of available commands.

@joyful-ii-V-I
joyful-ii-V-I merged commit bcd3b01 into main Sep 17, 2026
60 of 61 checks passed
joyful-ii-V-I added a commit that referenced this pull request Sep 17, 2026
…n/train-1

Train 2 landed first, per the orchestrator's order; merged once with rerere disabled. Conflicts:
- test/qschemetrip.hash: NEITHER side. Train 1 had a37d1539 (scheme 14, parserVer 96) and main had 95818fe5
  (parserVer 99, scheme 12). Re-derived with UPDATE_GOLDEN=1 on the merged tree =
  72f930b8171166d0a605cf27b610d1b3ade194f7703bd976904df78f350c38f3; qschemetripcheck ALL PASS.
- test/qschemetripcheck.sh re-pin log, newest-first: a new 2026-09-17 TRAIN 1 x TRAIN 2 entry explaining the
  re-derivation, then train 2's 09-17 entry and its lane entries, then train 1's 09-16 entries (#255 v14, #253 v13),
  then #249's.
Auto-merged (no markers): src/graph.h, src/resolve.h, src/model.h, src/serialize.h, src/quality.h (kParserVer mirror
99 from train 2, kQSnapCacheScheme 14 / kQBodyCacheScheme 4 from train 1), CHANGELOG.md, docs/EVALS.md.
docs/gatecount_build.py --check and docs/limits_build.py --check are clean on the merged tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
joyful-ii-V-I added a commit that referenced this pull request Sep 17, 2026
…ype, arms renumbered after #254's 25, identity claims carry no prov="final-segment"

origin/main bcd3b01 brings #248, #254 and #257. Two files conflicted; rerere was off and no recorded resolution was used.

- src/resolve.h: #254 added a namespace-level RecvVarType {name, writtenQualified}, and this lane added a nested
  Narrower::RecvVarType {name, declared}. Git merged the second in beside the first, so it would have shadowed it. Both
  are now ONE namespace-level struct {name, writtenQualified, declared}; recvVarType fills all three, and
  finalSegmentTypeAt is #254's own.
- test/narrowcheck.sh: #254's arm 25 (prov="final-segment") keeps its number. This lane's arms 25-34 become 26-35;
  expectProv/provOf take an optional map file ("${5:-}" under set -u).
- src/graph.h (line-neutral; rankGraphTeleport stays at 3404): an identity CLAIM verified its one class before
  answering, so its edges are not a last-name guess. Two lines change in place: `&& !identityClaim` joins the
  finalSegmentType condition, and its comment is reworded. A class-qualified step-1 narrow keeps the disclosure.
- Arm 36, `hs::DiskHealth& d; d.fine()` claiming the inherited HealthBase::fine through namespace evidence:
  (36a) the edge, (36b) no prov=, (36c) `Skip::Iterator& it; it.key()` keeps prov="final-segment". A build without
  the graph.h condition fails (36b) with prov=[final-segment].
- CHANGELOG entry and ack text: the arm numbers only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
joyful-ii-V-I added a commit that referenced this pull request Sep 17, 2026
Takes train 2 (#265), #262 and #252. No conflicts: CHANGELOG.md keeps every
entry, and the lane's CONTRIBUTING, mcpverbs.h, quality.h and serialize.h
hunks sit apart from main's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andriytyurnikov pushed a commit to andriytyurnikov/ripwire that referenced this pull request Sep 17, 2026
…ts, and the default window's oracle was the wall clock

The live audit passed --since="2 weeks ago" to ripwire and "2 weeks ago" to
the git oracle, and each evaluated it when it ran. A commit on the edge fell
inside one window and outside the other: redhat-et#265 went red on src/slice.h churn
15 vs 14 (commit 16efdbc at the two-week boundary), a rerun cause and not a
defect.

The default-window oracles had a slower version of the same bug. ripwire
anchors its default churn window on HEAD's committer epoch minus 12 calendar
months (gitmine.h defaultWindowSince, stamped 12mo@HEAD), but all ten oracles
asked git for wall-clock "12 months ago". That is a different window which
holds the same commits today and stops on 2027-06-01, when the fixtures'
2026-06 commits leave the wall clock's year.

The fix, test-only:
- One clock read (NOW_EPOCH) gives SINCE_2W as an ISO-8601 UTC instant, and
  that identical string goes to both ripwire and git.
- defaultWindowStart REPO computes the HEAD-anchored start by
  monthsBeforeEpoch's rule, and every default-window oracle uses it.
- New arm 4b pins both rules to the second:
  - a commit dated exactly on the two-week edge is in on both sides, and out
    on both sides one second later;
  - a commit exactly on the anchored edge is counted, and one a second
    earlier is not;
  - two controls stay red on purpose. "2 weeks ago" read by ripwire at N and
    by git at N+1 (GIT_TEST_DATE_NOW, which ripwire's git child inherits)
    disagrees, and the wall-clock oracle already drops the anchored edge
    commit.
- The offset arm (10) still selected the retired p="./..." spelling. It read
  0 rows and passed "all 0 rows re-derive". It now reads 163 rows, and the row
  count must equal ranked=.

Red first, on main's gate with GIT_TEST_DATE_NOW staging:
- ripwire at 8083ea9's epoch + 14 d and git one second later: rc=1 with 4
  FAIL (src/slice.h churn 12 vs 11, verbs_report.h 36 vs 35, sortutil.h 2 vs
  1, "3 of 406 rows disagree").
- git's clock at 2027-07-01: rc=1 with 12 FAIL.
Green under the same stagings and unstaged: rc=0, 78 PASS, 0 FAIL, 0 SKIP
(main's gate had 72 PASS). gateexitcheck, manifestcheck, skipclassifycheck
and gatecountcheck pass. No other gate reads a relative date twice: the sweep
of test/ found labels, exit codes and dates computed once (sincecheck,
hotspotsincecheck, modifierguardcheck, recentscopecheck, gitstampcheck,
qddialscheck).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant