ruby_package_parity is RED at integration HEAD: #134's tier-3 origin gate reads a manifest relation the packaged leg structurally cannot have #167
Labels
No labels
code-review
correctness
dos
performance
security
severity/high
severity/low
severity/medium
tech-debt
Kind/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
h-dv/code-index#167
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while landing #102's packaged half. This is not that change — bisected below against an unmodified tree.
The bisect
Both runs
COSI_CORPUS_DIR=… COSI_CORPUS_REQUIRE=1 cargo test --release -p code-index-indexer --test ruby_package_parity, on worktrees at those exact commits with no local modifications. The regressing commit is2f16e22— "destructuring member reads and a tier-3 origin gate, both bind-inspected" (#125 + #134). Ruby emits no destructuring member reads by that commit's own account, so #134's gate is the half that can reach this.Scale, and it is not one row
The suite panics on the first unexplained delta. Patched locally to collect instead (reverted, md5-verified), it reports 17 unexplained ref deltas in two shapes, and they point in OPPOSITE directions:
Shape A — the package leg loses a bind (cols {8}, target only), 11 rows.
Basetype refs inlib/sinatra/main.rbandsinatra-contrib/lib/sinatra/*.rbresolve tolib/sinatra/base.rb#Base@971on the builtin leg and~UNRESOLVED~on the package leg.Shape B — the package leg keeps a bind the builtin leg lost (cols {2,8}), 6 rows. e.g.
The
kindcolumn moves with the target because themember_accessre-kind is downstream of resolution, so both shapes are one phenomenon: the two legs are running the origin gate against different data.The mechanism, from the code
manifest_package_dirs(crates/indexer/src/index.rs:1801) discovers package boundaries by BASENAME, and its list containsGemfile:The parity harness stages every file the builtin Ruby claim owns — and
builtin_claim_table()claimsGemfile— throughshadow(), which replaces the extension. So on the package leg everyGemfileis on disk asGemfile.rbx,manifest_package_dirsreturns the empty list, andtemp.file_pkg.pkg_diris''for every file in the tree.#134's gate then reads a column that means something different on each leg:
On the builtin leg the disjunct discriminates between
sinatra,sinatra-contribandrack-protection. On the package leg it is vacuously true everywhere. That is shape B exactly, and it says the two legs are no longer comparable on any rule keyed on manifest discovery.Shape A is the opposite direction and this diagnosis does not yet explain it — stated rather than implied. The two
INNER JOIN temp.file_pkgadditions (reachable_predicateatindex.rs:2040and Edge 2 at:5992) can DROP a candidate that has nofile_pkgrow at all, which is the other way a leg loses binds, and that is where I would look next.Why it matters beyond a red suite
#84's whole anti-vacuity claim rests on the two legs agreeing. #102's own comment states it: "a divergence between the two Ruby implementations cannot be silent in this tree" — that property is currently unavailable, because the suite is red for a reason that is not a divergence between the implementations.
And there is a product finding under the harness one: manifest discovery is basename-keyed, so it is blind to any packaged language whose files a package claims under a different extension. That is the same family as #112's three language-id-keyed allowlists — a host gate that silently means something different for a packaged producer. A real
.cipclaiming.rboutright would not hit theGemfile.rbxcase, but any package whose claim set covers a manifest name would.Not attempted here, and why
index.rs's origin gate is being worked in another lane right now (#165, the kebab/snake package-directory comparison in the same #134 code). Two lanes editing that relation concurrently is a merge hazard, and the fix has to choose between two very different answers — exclude manifests from the shadow map (harness), or make manifest discovery producer-aware (product) — which is the #134/#165 owner's call, not this lane's.What must not happen is a new
Mechanismvariant pinning these 17 rows.ruby_package_parity's own doc forbids it: aMechanismis a host-side gate a package cannot close, and pinning a host-gate asymmetry as a permitted normalisation is how the gate stops grading the thing it exists for.Repro
COSI_CORPUS_DIRis not optional: without it the suite reportsexecuted=0 unavailable=1and passes, which is its own trap.Related
#134 (the gate), #165 (the other defect in it), #112 (the same family, closed), #84, #102.
🤖 Generated with Claude Code
https://claude.ai/code/session_01K1zj5VcFJvJt3pQxe9259K
private_class_method :namemarks the INSTANCE method private, not the class method #102ruby_package_parity: theSelfQualifierarm absorbs resolution differences, and closing #167's mechanism made 11 of them visible #170Green, and your diagnosis was right about both shapes — including the one you said it did not explain. But the fix is in the harness, not the product, and I want to show the work because I tried the product answer twice and it was refuted twice.
Shape A is the same mechanism, in the other direction
You wrote that the vacuous
pkg_dirdisjunct explains shape B and that shape A "is the opposite direction and this diagnosis does not yet explain it". It does. I dumped both legs and read the row:Basehas three definitions in the tree; the import isrequire 'sinatra/base', whose keybasereaches two of them (lib/sinatra/base.rbandrack-protection/lib/rack/protection/base.rb). On the builtin leg the origin gate refuses rack-protection's —pkg_tail = rack-protection,pkg_dir = rack-protection≠ the ref's''— so the reachable set collapses to one and tier 3 decides. On the packaged legpkg_diris''on both sides, the disjunct is true, both candidates are admitted, the pool is ambiguous and tier 3 declines.So the vacuous disjunct adds reachability, and added reachability withdraws binds as readily as it creates them. One mechanism, both shapes, all 17 rows. (The same effect shows up at scale in #165: 1 471 binds withdrawn against 16 717 added, on one repo.)
The product fix was implemented and REFUSED, with numbers
''really does mean two things — "the root manifest covers me" and "no manifest does" — and splitting them is half a fix. The other half is a fallback partition for manifest-less trees, and there is no ground truth for it. Two candidates, both implemented, both measured:package_root_of(parent dir)EcosystemMarker4→0tier3_indexed_join_equals_cross_product_oracle,tier3_indexed_join_work_is_bounded_by_reachability,a_degraded_stage_does_not_merely_report_it_skipsEcosystemMarker4→0csharp_monorepo_binds_partial_scope_and_refuses_the_decoy,csharp_monorepo_stages_do_not_scale_quadratically,every_guarded_stage_measures_real_work…,tier1r_indexed_join_equals_cross_product_oraclebreak insteadThe first splits
myproj/app/caller.rsfrom themyproj/p1/util.rsits ownuse myproj::utilnames — the same split I046 measured as 67 lost ripgrep resolutions when it triedpkg_rootin place ofpkg_dir. The second is coarser and admits a bind a fixture calls a phantom in so many words:And neither is measurable on the pinned corpus: all nine repos carry discoverable manifests, so the fallback never fires and the full bind digest is byte-identical under both, on every repo. The reason every fixture disagrees is that the origin gate has never been exercised in a manifest-less fixture — the old
'' = ''kept it switched off in all of them, so each was written under a different unstated assumption.Choosing between those partitions on no evidence is exactly what this repo says not to do, so I reverted the product change and left
nearest_package_dirwith a doc that records both refutations and names what would settle it: a manifest-less corpus repo. That is a corpus change, not a resolver change.The harness fix, and why it is not a third normalisation
ruby_parity_fixture::stagenow skips files whose basenamemanifest_package_dirsrecognises. A file whose identity is its NAME cannot surviveshadow; staging it gives the two legs different worlds rather than one world under two spellings. It is not a normalisation of the comparison — no row is excluded from the diff — and it is applied identically to both legs.This is the suite's own argument for REPLACE-vs-APPEND, applied one step further:
shadowalready documents 122 resolutions that were "an artifact of the harness and not a fact about the package".Only
Gemfileis affected.Rakefileand*.gemspecare Ruby claims but not manifest names, so the awkward files that staging exists to include are still included.On your constraint, which I have kept
No
Mechanismvariant pins these rows. The opposite:Mechanism::EcosystemMarkeris deleted, arm and variant, exactly as #112's three were — so a row of that shape now reachesclassify'sNoneand panics with its own text rather than being absorbed.Measured, both legs, at ruby-sinatra's pinned sha:
FLOOR_REFSmoved with it)SelfQualifierEcosystemMarkerSymbols and imports unchanged is the check that this is a corpus change and not a producer regression — a Gemfile declares nothing and imports nothing.
SelfQualifierholding at exactly 876 across a corpus that lost three files is what makes the run evidence rather than a coincidence.Mutation RUN: remove the skip and a new control fires on the staged tree, before any comparison —
and behind it the 12 unexplained ref deltas return.
One thing closing this exposed, filed as #170
classifytriesSelfQualifierfirst and its predicate admitsdiff ⊆ {qualifier, target}, so 11 of the 876 carryEcosystemMarker's exact shape (builtin unresolved, package resolved by the file-key import arm) and have been counted as qualifier-text deltas all along — before this change as much as after. Pinned asSELF_QUALIFIER_ROWS_WITH_A_RESOLUTION_DELTA = 11so it cannot grow in silence, and filed rather than widened.Verified the way you asked
executed=1, not a skip._prdoc/records/84-P4-parity-deltas.mdcarries the derivation, the refuted product attempts and the new numbers.Also worth knowing while you are here:
corpus_stagehas the same trap and it was firing.tests/corpus/stage-baseline.jsonhad drifted (rule.tier3_import_boost,rule.tier1q_pass1,influence.builtin_onlyon four repos, all #134's) and sat red under three separate "green"cargo test --workspaceruns, because withoutCOSI_CORPUS_DIRit reportsunavailableand passes. Re-recorded with a reason.🤖 Generated with Claude Code
https://claude.ai/code/session_01K1zj5VcFJvJt3pQxe9259K
private_class_method :namemarks the INSTANCE method private, not the class method #102