resolver: profile-driven member/property binding with zero-phantom capability gates #69

Open
opened 2026-08-18 12:33:34 +02:00 by buildagent · 11 comments
Member

Follow-on from I040. name_fallback_count now DISCLOSES the shortfall on attribute access; this would reduce it.

The measured case

A Python @property disarmed accessed 6 times: all 6 refs exist in the refs table, only the same-file self. one resolves. search_symbols reports ref_count: 1, name_fallback_count: 5. The customer's original report measured 2 vs a ground truth of 5 in their own repo.

Why they don't resolve, and why it looks fixable

The same code as a method call resolves 2/2. The asymmetry is structural:

  • tier 1R gates on refs.kind = 'method_call' AND refs.qualified = 0 AND refs.qualifier IS NOT NULL
  • attribute-access refs are kind='type', qualified=1 with a dotted-path qualifier, so they route to tier 1Q, which anchors on file stems/modules — where a variable name like cfg can never anchor.

The binding facts already exist: the fixture produced cfg → LeaseConfig binding rows in python, ts, csharp, php and rust.

Sketch

A "tier 1P" mirroring tier 1R at index.rs, reusing recv_binds unchanged:

  • input: kind='type' AND qualified=1 AND target_id IS NULL AND qualifier NOT LIKE '%.%' (bare identifier ⇒ not a module path), plus ruby's kind='method_call' rows which already match tier 1R's input shape
  • nearest preceding binding in the same innermost enclosing symbol (existing recv_bound CTE, verbatim)
  • widen the candidate join from m.kind = 'method' to m.kind IN ('method','field')
  • reuse the name-structural origin gate UNMODIFIED — it is the phantom-safety proof and it is resolution-independent. The I030 lesson stands: resolution-dependent gates break the resolve firewall.

Resolved rows then fall into the existing member_access re-kind, which is correct: a property is not a type edge.

What it cannot buy

  • anything.disarmed where the receiver has no annotation
  • all of ruby — no typed bindings, dynamically-typed receivers are unresolvable by construction
  • all of rust/php — they emit no ref at all (#68)

So the disclosure stays load-bearing permanently regardless of how far this gets. That is the argument for having shipped name_fallback_count first.

Risk

Real. The module-path collision (pkg.CONSTANT also arrives as kind='type', qualified=1, roles=0) is contained by requiring a dot-free qualifier WITH an in-scope binding row, but this needs the full adversarial-review treatment plus per-language oracle.toml forbid_resolved decoys before it can pass the zero-phantom gate.

Estimate ~1 week including oracle decoys and a review round.

Runtime-plugin architecture revision

Do not ship this as a new hardcoded language list. Under #77 it becomes a closed core resolver algorithm selected by a language-profile capability.

The algorithm’s authority is bounded by:

  • source ref class explicitly eligible for member/property binding;
  • a proven in-scope receiver binding;
  • destination symbol class declared by the profile;
  • active-generation and contribution capability predicates;
  • same-language matching unless an explicit bridge allows otherwise;
  • resolver-wide work accounting from #65/#53.

Builtin Python/TypeScript/C# fixtures are the first implementation and compatibility proof. A dynamic package may request the algorithm but cannot provide custom SQL or broaden its candidate predicate.

Add acceptance cases for an inert package, a package granted only member candidates, language-id spoofing, mixed-language files and generation rollback. Existing zero-phantom oracles remain the merge gate.

Follow-on from I040. `name_fallback_count` now DISCLOSES the shortfall on attribute access; this would reduce it. ## The measured case A Python `@property disarmed` accessed 6 times: all 6 refs exist in the `refs` table, only the same-file `self.` one resolves. `search_symbols` reports `ref_count: 1, name_fallback_count: 5`. The customer's original report measured 2 vs a ground truth of 5 in their own repo. ## Why they don't resolve, and why it looks fixable The **same code as a method call resolves 2/2**. The asymmetry is structural: - tier 1R gates on `refs.kind = 'method_call' AND refs.qualified = 0 AND refs.qualifier IS NOT NULL` - attribute-access refs are `kind='type'`, `qualified=1` with a dotted-path qualifier, so they route to tier 1Q, which anchors on file stems/modules — where a *variable* name like `cfg` can never anchor. The binding facts already exist: the fixture produced `cfg → LeaseConfig` binding rows in python, ts, csharp, php and rust. ## Sketch A "tier 1P" mirroring tier 1R at `index.rs`, reusing `recv_binds` unchanged: - input: `kind='type' AND qualified=1 AND target_id IS NULL AND qualifier NOT LIKE '%.%'` (bare identifier ⇒ not a module path), plus ruby's `kind='method_call'` rows which already match tier 1R's input shape - nearest preceding binding in the same innermost enclosing symbol (existing `recv_bound` CTE, verbatim) - widen the candidate join from `m.kind = 'method'` to `m.kind IN ('method','field')` - reuse the name-structural origin gate **UNMODIFIED** — it is the phantom-safety proof and it is resolution-independent. The I030 lesson stands: resolution-dependent gates break the resolve firewall. Resolved rows then fall into the existing `member_access` re-kind, which is correct: a property is not a type edge. ## What it cannot buy - `anything.disarmed` where the receiver has no annotation - **all of ruby** — no typed bindings, dynamically-typed receivers are unresolvable by construction - **all of rust/php** — they emit no ref at all (#68) So the disclosure stays load-bearing permanently regardless of how far this gets. That is the argument for having shipped `name_fallback_count` first. ## Risk Real. The module-path collision (`pkg.CONSTANT` also arrives as `kind='type'`, `qualified=1`, `roles=0`) is contained by requiring a dot-free qualifier WITH an in-scope binding row, but this needs the full adversarial-review treatment plus per-language `oracle.toml` `forbid_resolved` decoys before it can pass the zero-phantom gate. Estimate ~1 week including oracle decoys and a review round. ## Runtime-plugin architecture revision Do not ship this as a new hardcoded language list. Under #77 it becomes a closed core resolver algorithm selected by a language-profile capability. The algorithm’s authority is bounded by: - source ref class explicitly eligible for member/property binding; - a proven in-scope receiver binding; - destination symbol class declared by the profile; - active-generation and contribution capability predicates; - same-language matching unless an explicit bridge allows otherwise; - resolver-wide work accounting from #65/#53. Builtin Python/TypeScript/C# fixtures are the first implementation and compatibility proof. A dynamic package may request the algorithm but cannot provide custom SQL or broaden its candidate predicate. Add acceptance cases for an inert package, a package granted only member candidates, language-id spoofing, mixed-language files and generation rollback. Existing zero-phantom oracles remain the merge gate.
buildagent changed title from resolver: tier 1P — resolve attribute/property access for python, typescript, csharp to resolver: profile-driven member/property binding with zero-phantom capability gates 2026-08-26 13:39:30 +02:00
Author
Member

Triage 2026-09-06: LEFT OPEN — the work is genuinely undone, but this issue's stated input predicate is DEAD and the spec needs rewriting before anyone picks it up.

The predicate this issue is written against no longer exists

This issue specifies its input as kind='type' AND qualified=1. Since I059, member/attribute access refs are emitted as RefKind::READ / WRITE with qualified: true — crates/plugins/src/common.rs:407-420 — never as kind='type'. Confirmed for the two languages this issue says are dark (they now emit; see #68, closed today) and for the four it says emit type.

So the population this issue describes is empty. Anyone implementing it as written would build a tier that matches nothing.

The work itself is NOT done — verified

  • Tier 1R's input is unchanged: crates/indexer/src/index.rs:6542-6549 — WHERE refs.target_id IS NULL … AND refs.kind = 'method_call' AND refs.qualified = 0 AND refs.qualifier IS NOT NULL. No read/write row can enter it, so widening the candidate join is moot until the input widens.
  • search_text("tier 1P") returns exactly one hit, and it is a mission doc (_prdoc/missions/I040-payload-honesty-and-coverage.md:163). recv_binds appears only in index.rs and crates/indexer/tests/pool_capability_registry.rs:462.

What exists instead, and it is not the same thing

Generic-tier resolution of access refs plus a tier-3 field gate: index.rs:7686 unbinds any DATA_MEMBER_ONLY read/write that bound a non-field, and :7732/:7756 re-kind the rest to type/member_access. That is phantom control, not receiver binding — it stops wrong answers rather than producing right ones.

The scope estimate has also moved

2f16e22 records the reassessment: "the real change is two edits, not the issue's week" — and that it was left undone deliberately, for a reason worth keeping:

fields are the most collision-prone kind there is, #125 above is direct evidence of the phantom rate when member evidence is weak, and a third resolution change in one worktree makes the deltas uninspectable.

That is the right instinct on this repo, where the standing rule is inspect binds, not deltas — three cuts once raised the resolved count and every extra bind was a phantom.

What this issue needs before work starts

  1. Rewrite the input against the read/write + qualified: true + DATA_MEMBER_ONLY world. The kind='type' AND qualified=1 predicate should be struck, not reinterpreted.
  2. Restate the scope as the two-edit change it now is, or say why the larger version is still wanted.
  3. Carry #125's six-phantom measurement in as the calibration: it is the best available evidence for what happens when member evidence is weak, and it is from this codebase rather than from theory.

I have not attempted (1) — rewriting an issue's specification is a product decision, not a triage one.

🤖 Triage lane, 2026-09-06, master 45cf6e4

## Triage 2026-09-06: LEFT OPEN — the work is genuinely undone, but **this issue's stated input predicate is DEAD** and the spec needs rewriting before anyone picks it up. ### The predicate this issue is written against no longer exists This issue specifies its input as `kind='type' AND qualified=1`. Since I059, member/attribute access refs are emitted as **`RefKind::READ` / `WRITE`** with `qualified: true` — `crates/plugins/src/common.rs:407-420` — never as `kind='type'`. Confirmed for the two languages this issue says are dark (they now emit; see **#68**, closed today) and for the four it says emit `type`. So the population this issue describes is empty. Anyone implementing it as written would build a tier that matches nothing. ### The work itself is NOT done — verified - Tier 1R's input is unchanged: `crates/indexer/src/index.rs:6542-6549` — `WHERE refs.target_id IS NULL … AND refs.kind = 'method_call' AND refs.qualified = 0 AND refs.qualifier IS NOT NULL`. **No `read`/`write` row can enter it**, so widening the candidate join is moot until the input widens. - `search_text("tier 1P")` returns exactly one hit, and it is a mission doc (`_prdoc/missions/I040-payload-honesty-and-coverage.md:163`). `recv_binds` appears only in `index.rs` and `crates/indexer/tests/pool_capability_registry.rs:462`. ### What exists instead, and it is not the same thing Generic-tier resolution of access refs plus a **tier-3 field gate**: `index.rs:7686` unbinds any `DATA_MEMBER_ONLY` read/write that bound a non-`field`, and `:7732`/`:7756` re-kind the rest to `type`/`member_access`. That is **phantom control, not receiver binding** — it stops wrong answers rather than producing right ones. ### The scope estimate has also moved `2f16e22` records the reassessment: *"the real change is two edits, not the issue's week"* — and that it was **left undone deliberately**, for a reason worth keeping: > fields are the most collision-prone kind there is, #125 above is direct evidence of the phantom rate when member evidence is weak, and a third resolution change in one worktree makes the deltas uninspectable. That is the right instinct on this repo, where the standing rule is **inspect binds, not deltas** — three cuts once raised the resolved count and every extra bind was a phantom. ### What this issue needs before work starts 1. **Rewrite the input** against the `read`/`write` + `qualified: true` + `DATA_MEMBER_ONLY` world. The `kind='type' AND qualified=1` predicate should be struck, not reinterpreted. 2. **Restate the scope** as the two-edit change it now is, or say why the larger version is still wanted. 3. Carry #125's six-phantom measurement in as the calibration: it is the best available evidence for what happens when member evidence is weak, and it is from this codebase rather than from theory. I have not attempted (1) — rewriting an issue's specification is a product decision, not a triage one. 🤖 Triage lane, 2026-09-06, master `45cf6e4`
Author
Member

IMPLEMENTED — tier 1R serves the MEMBER pool. +4 922 binds, 0 lost over nine pinned repos, bind-for-bind, with five measured phantoms found and refused

Resolver-recall lane, worktree off master fc329a8. Baseline binary built from that commit and md5-pinned; every number below is a bind-set diff, never a count delta.

The spec was dead, and here is what replaced it

This issue specified its input as kind='type' AND qualified=1. That population is empty since I059 phase 2 re-kinded member access to read/write. The predicate is struck, not reinterpreted. The shipped input is:

refs.qualifier IS NOT NULL
AND (   (refs.kind = 'method_call' AND refs.qualified = 0)      -- unchanged
     OR (refs.kind IN ('read','write')                          -- #69
         AND qualifier is a BARE IDENTIFIER (no . :: ->)
         AND qualifier NOT IN ('self','this','$this')) )

Three structural clauses, none of which asks whether anything RESOLVED, so the resolve firewall is intact. The bare-identifier cut is the access-ref spelling of recv_proof::UNCAPTURED — a dotted qualifier is a module path or a field PATH, and neither has a recv_binds row. Self receivers are excluded because tier 1Q already anchors them to the enclosing file, which is the one place co-location is a fact about the receiver's type.

The candidate side is not a hand-widened list

The issue proposed m.kind IN ('method','field'). What shipped instead: tier 1R's m.kind = 'method' was a hand-copied instance of the WHEN 1 arm of the symbol-side pool-membership expression already used by the symbol_buckets base pool gate. Both now splice one pool_class_admits(pool, sym), driven by the ref's own pool_class. So a method call still draws from kind='method' alone, a member-pool ref draws from the member pool, and the two copies cannot widen on one side only.

recv_calls / recv_bound carry pool_class for the same reason: one file can hold a method call and a field read through the same receiver, and they must not draw from each other's pool.

Result, bind-for-bind, joined on the full row (path:line:col, kind, name, target, rule)

repo baseline after new lost
rust-analyzer 143 113 146 344 3 231 0
py-django 107 491 108 599 1 108 0
rust-ripgrep 15 709 16 035 326 0
cs-dapper 3 628 3 734 106 0
ts-zod 10 781 10 869 88 0
php-guzzle 11 825 11 862 37 0
python-flask 3 026 3 052 26 0
js-express, ruby-sinatra — — 0 0
total +4 922 0

Every one of the new binds carries resolved_by = 60 (TIER1R_RECEIVER). Nothing leaked into another tier.

js-express and ruby-sinatra gaining nothing is the issue's own prediction holding: untyped JS has no binding rows to key on, and Ruby's read/write are @ivar accesses whose qualifier is a self receiver.

Bounded with an oracle OUTSIDE the resolver

Each rust crate's own Cargo.toml, over the 3 231 rust-analyzer new binds: 2 039 same-crate, 1 192 cross-crate, 2 undeclared — and both of those, read at source, are correct binds travelling ide-db's pub use base_db re-export (crates/ide/src/fetch_crates.rs:37 and :42, where the parameter is annotated &ide_db::base_db::ExtraCrateData). rust-ripgrep: 326 new binds, 0 cross-crate.

Second census, on every repo: new binds whose (container, member, lang) triple is AMBIGUOUS project-wide — the only ones that can be a receiver-type phantom. 70 of 4 859 in the first cut. All read at source.

FIVE PHANTOMS, and this is the finding the issue was deferred for

The earlier reassessment said "fields are the most collision-prone kind there is". The corpus agrees, and it is measurable rather than a hunch:

site bound correct target
benchmarks/Dapper.Tests.Performance/LegacyTests.cs:231 post.Id tests/Dapper.Tests/SharedTypes/Post.cs benchmarks/Dapper.Tests.Performance/Post.cs
same, :275 same same
tests/foreign_object/tests.py:521 referrer.article tests/indexes/models.py tests/foreign_object/models/article.py
tests/serializers/tests.py:355 player.team tests/model_formsets/models.py tests/serializers/models/base.py
same, :364 same same

The mechanism is tier 1R's ORIGIN gate, not the widening: a proven receiver type fixes the type NAME, not the FILE, and the evidence that picks between five Post classes or three Player models is a file-key STEM match (from .models import matches every models.py in the tree) or a C# namespace prefix. Both are blind to which same-named container the source means.

This is NOT pre-existing in a form I can point at, and I checked rather than assumed. A census of the baseline's rule-60 cross-file binds finds zero with an ambiguous container on py-django (871 cross-file) or cs-dapper (2). Method names are not the collision-prone kind. So the widening does introduce this class, and I did not get to leave it.

The gate that removes them, and the two versions that were measured and rejected

Shipped in tier1r_row_gates: refuse a MEMBER-pool bind when a competing candidate — a same-named container holding a member of the same name and pool — sits inside the referencing file's own directory subtree, and the chosen one is outside it. Every one of the five had exactly that.

Two earlier versions were built, measured over all nine repos and rejected:

  1. Applied to both pools. Cost 212 binds the baseline already had (rust-analyzer 140, py-django 62, rust-ripgrep 10). Refused on the numbers; the baseline census above is why the member scoping is measured rather than an ad-hoc carve-out.
  2. Container name only, no member test. Refused 46 correct rust-analyzer binds: crates/hir-ty/src/infer/cast.rs reads ctx.table, a field of struct InferenceContext in crates/hir-ty/src/infer.rs, and thirteen impl InferenceContext blocks sit in the nearer subtree. An impl can never hold a field, so they were never candidates.

Final gate, versus the ungated widening: 5 phantoms refused, 5 correct binds refused (crates/rust-analyzer/src/lsp/to_proto.rs:1914-1942, test_item.id/label/kind/parent/runnable — ide::TestItem and lsp_ext::TestItem are same-named types in the same directory, and the refusal is the honest "cannot tell"), and +70 correct binds GAINED on py-django, because removing a wrong candidate makes a previously-ambiguous group unique. All 70 are tests/<app>/tests.py -> tests/<app>/models.py under Django's per-app test convention; Article.headline alone has 24 same-named containers project-wide. Net: 4 859 -> 4 922 new binds, and the phantom count goes to 0.

Final ambiguity census on the shipped set: 0 on cs-dapper, php-guzzle, python-flask, ts-zod, rust-ripgrep; py-django 102 and rust-analyzer 31, of which the cross-file ones were all read at source and are correct (ide_db::generated::lints::Lint, ide::Runnable, hir::MovedOutOfRef, hir_def::MatchArm, django.forms.DecimalField, django.utils.tree.Node, django.contrib.gis.geos.Point).

Tests, with the mutations RUN

  • resolver::member_access_through_a_proven_receiver_resolves_cross_file — the issue's own cfg.disarmed shape.
    MUTATION: revert recv_calls' WHERE to method_call AND qualified = 0. RED: left: None, right: Some("core/src/lease.rs").
  • resolver::a_nearer_competing_member_refuses_the_distant_bind — the phantom class, minimized.
    MUTATION: delete the gate clause. RED: binds other/models.py, the phantom, verbatim.
    This test's first version was VACUOUS and the mutation is what caught it. With both candidates named models.py both are admitted, the count is 2, nothing binds, and deleting the gate changed nothing — the mutation passed GREEN. The shipped fixture puts the correct candidate in app/pkg/models/base.py, whose stem the relative import does not name, so the origin gate admits exactly the wrong file. That asymmetry is the defect and the fixture now carries it.
  • provenance_invariants: TIER1R_RECEIVER's promise was refs.kind = 'method_call' and is now qualifier IS NOT NULL in one of the two shapes recv_calls admits. The old spelling was not a technicality — 1 227 of 1 626 tier-1R rows on this workspace's own index are no longer method calls, and the test went red on exactly that before the promise was widened.
  • m0063 re-parse migration (shared with #125): MUTATION — swap the body for m0062's re-heal. RED, left: 0, right: 2.

Migration

m0063_pattern_reads_and_member_receivers, a RE-PARSE. Spec 04 requires a heal for every resolution-rule change; a re-parse is strictly stronger and #125 needs one anyway. Details on #125.

What this still cannot buy — unchanged from the issue's own list

Ruby (no typed bindings), untyped JavaScript, and anything whose receiver has no annotation. Both corpora prove it: 0 new binds on ruby-sinatra and js-express. The name_fallback_count disclosure stays load-bearing permanently.

Residual, NAMED

The origin gate's stem/namespace anchor is still what decides between same-named containers; the shipped gate only refuses where a nearer competitor was demonstrably passed over. Two of the three phantom families were a RELATIVE import (from .models import) matching a file-key stem OUTSIDE the importing file's own directory subtree — which is a language-level impossibility and looks like one clean clause in build_recv_origin. It is not attempted here: it is shared with every other tier, so it would move the corpus for reasons unrelated to this issue, and it deserves its own bind-for-bind pass. Same family as #134 and #168.

## IMPLEMENTED — tier 1R serves the MEMBER pool. **+4 922 binds, 0 lost** over nine pinned repos, bind-for-bind, with five measured phantoms found and refused Resolver-recall lane, worktree off master `fc329a8`. Baseline binary built from that commit and md5-pinned; every number below is a bind-set diff, never a count delta. ### The spec was dead, and here is what replaced it This issue specified its input as `kind='type' AND qualified=1`. That population is empty since I059 phase 2 re-kinded member access to `read`/`write`. The predicate is **struck**, not reinterpreted. The shipped input is: ``` refs.qualifier IS NOT NULL AND ( (refs.kind = 'method_call' AND refs.qualified = 0) -- unchanged OR (refs.kind IN ('read','write') -- #69 AND qualifier is a BARE IDENTIFIER (no . :: ->) AND qualifier NOT IN ('self','this','$this')) ) ``` Three structural clauses, none of which asks whether anything RESOLVED, so the resolve firewall is intact. The bare-identifier cut is the access-ref spelling of `recv_proof::UNCAPTURED` — a dotted qualifier is a module path or a field PATH, and neither has a `recv_binds` row. Self receivers are excluded because tier 1Q already anchors them to the enclosing file, which is the one place co-location *is* a fact about the receiver's type. ### The candidate side is not a hand-widened list The issue proposed `m.kind IN ('method','field')`. What shipped instead: tier 1R's `m.kind = 'method'` was a **hand-copied** instance of the `WHEN 1` arm of the symbol-side pool-membership expression already used by the `symbol_buckets` base pool gate. Both now splice one `pool_class_admits(pool, sym)`, driven by the ref's own `pool_class`. So a method call still draws from `kind='method'` alone, a member-pool ref draws from the member pool, and the two copies cannot widen on one side only. `recv_calls` / `recv_bound` carry `pool_class` for the same reason: one file can hold a method call and a field read through the same receiver, and they must not draw from each other's pool. ### Result, bind-for-bind, joined on the full row (path:line:col, kind, name, target, rule) | repo | baseline | after | new | **lost** | |---|---:|---:|---:|---:| | rust-analyzer | 143 113 | 146 344 | 3 231 | **0** | | py-django | 107 491 | 108 599 | 1 108 | **0** | | rust-ripgrep | 15 709 | 16 035 | 326 | **0** | | cs-dapper | 3 628 | 3 734 | 106 | **0** | | ts-zod | 10 781 | 10 869 | 88 | **0** | | php-guzzle | 11 825 | 11 862 | 37 | **0** | | python-flask | 3 026 | 3 052 | 26 | **0** | | js-express, ruby-sinatra | — | — | 0 | 0 | | **total** | | | **+4 922** | **0** | Every one of the new binds carries `resolved_by = 60` (`TIER1R_RECEIVER`). Nothing leaked into another tier. `js-express` and `ruby-sinatra` gaining nothing is the issue's own prediction holding: untyped JS has no binding rows to key on, and Ruby's `read`/`write` are `@ivar` accesses whose qualifier is a self receiver. ### Bounded with an oracle OUTSIDE the resolver Each rust crate's own `Cargo.toml`, over the 3 231 rust-analyzer new binds: **2 039 same-crate, 1 192 cross-crate, 2 undeclared** — and both of those, read at source, are correct binds travelling `ide-db`'s `pub use base_db` re-export (`crates/ide/src/fetch_crates.rs:37` and `:42`, where the parameter is annotated `&ide_db::base_db::ExtraCrateData`). rust-ripgrep: 326 new binds, **0 cross-crate**. Second census, on every repo: new binds whose `(container, member, lang)` triple is AMBIGUOUS project-wide — the only ones that can be a receiver-type phantom. **70 of 4 859** in the first cut. All read at source. ### FIVE PHANTOMS, and this is the finding the issue was deferred for The earlier reassessment said *"fields are the most collision-prone kind there is"*. The corpus agrees, and it is measurable rather than a hunch: | site | bound | correct target | |---|---|---| | `benchmarks/Dapper.Tests.Performance/LegacyTests.cs:231` `post.Id` | `tests/Dapper.Tests/SharedTypes/Post.cs` | `benchmarks/Dapper.Tests.Performance/Post.cs` | | same, `:275` | same | same | | `tests/foreign_object/tests.py:521` `referrer.article` | `tests/indexes/models.py` | `tests/foreign_object/models/article.py` | | `tests/serializers/tests.py:355` `player.team` | `tests/model_formsets/models.py` | `tests/serializers/models/base.py` | | same, `:364` | same | same | The mechanism is tier 1R's ORIGIN gate, not the widening: a proven receiver type fixes the type NAME, not the FILE, and the evidence that picks between five `Post` classes or three `Player` models is a file-key STEM match (`from .models import` matches every `models.py` in the tree) or a C# namespace prefix. Both are blind to which same-named container the source means. **This is NOT pre-existing in a form I can point at, and I checked rather than assumed.** A census of the baseline's rule-60 cross-file binds finds **zero** with an ambiguous container on py-django (871 cross-file) or cs-dapper (2). Method names are not the collision-prone kind. So the widening does introduce this class, and I did not get to leave it. ### The gate that removes them, and the two versions that were measured and rejected Shipped in `tier1r_row_gates`: refuse a MEMBER-pool bind when a **competing candidate** — a same-named container holding a member of the same name and pool — sits inside the referencing file's own directory subtree, and the chosen one is outside it. Every one of the five had exactly that. Two earlier versions were built, measured over all nine repos and rejected: 1. **Applied to both pools.** Cost **212 binds the baseline already had** (rust-analyzer 140, py-django 62, rust-ripgrep 10). Refused on the numbers; the baseline census above is why the member scoping is measured rather than an ad-hoc carve-out. 2. **Container name only, no member test.** Refused **46 correct rust-analyzer binds**: `crates/hir-ty/src/infer/cast.rs` reads `ctx.table`, a field of `struct InferenceContext` in `crates/hir-ty/src/infer.rs`, and thirteen `impl InferenceContext` blocks sit in the nearer subtree. An `impl` can never hold a field, so they were never candidates. Final gate, versus the ungated widening: **5 phantoms refused, 5 correct binds refused** (`crates/rust-analyzer/src/lsp/to_proto.rs:1914-1942`, `test_item.id`/`label`/`kind`/`parent`/`runnable` — `ide::TestItem` and `lsp_ext::TestItem` are same-named types in the same directory, and the refusal is the honest "cannot tell"), and **+70 correct binds GAINED** on py-django, because removing a wrong candidate makes a previously-ambiguous group unique. All 70 are `tests/<app>/tests.py -> tests/<app>/models.py` under Django's per-app test convention; `Article.headline` alone has 24 same-named containers project-wide. Net: **4 859 -> 4 922 new binds, and the phantom count goes to 0.** Final ambiguity census on the shipped set: **0** on cs-dapper, php-guzzle, python-flask, ts-zod, rust-ripgrep; py-django 102 and rust-analyzer 31, of which the cross-file ones were all read at source and are correct (`ide_db::generated::lints::Lint`, `ide::Runnable`, `hir::MovedOutOfRef`, `hir_def::MatchArm`, `django.forms.DecimalField`, `django.utils.tree.Node`, `django.contrib.gis.geos.Point`). ### Tests, with the mutations RUN * `resolver::member_access_through_a_proven_receiver_resolves_cross_file` — the issue's own `cfg.disarmed` shape. **MUTATION:** revert `recv_calls`' WHERE to `method_call AND qualified = 0`. **RED**: `left: None, right: Some("core/src/lease.rs")`. * `resolver::a_nearer_competing_member_refuses_the_distant_bind` — the phantom class, minimized. **MUTATION:** delete the gate clause. **RED**: binds `other/models.py`, the phantom, verbatim. **This test's first version was VACUOUS and the mutation is what caught it.** With both candidates named `models.py` both are admitted, the count is 2, nothing binds, and deleting the gate changed nothing — the mutation passed GREEN. The shipped fixture puts the correct candidate in `app/pkg/models/base.py`, whose stem the relative import does not name, so the origin gate admits exactly the wrong file. That asymmetry is the defect and the fixture now carries it. * `provenance_invariants`: `TIER1R_RECEIVER`'s promise was `refs.kind = 'method_call'` and is now `qualifier IS NOT NULL` in one of the two shapes `recv_calls` admits. The old spelling was not a technicality — **1 227 of 1 626** tier-1R rows on this workspace's own index are no longer method calls, and the test went red on exactly that before the promise was widened. * `m0063` re-parse migration (shared with #125): **MUTATION** — swap the body for m0062's re-heal. **RED**, `left: 0, right: 2`. ### Migration `m0063_pattern_reads_and_member_receivers`, a RE-PARSE. Spec 04 requires a heal for every resolution-rule change; a re-parse is strictly stronger and #125 needs one anyway. Details on #125. ### What this still cannot buy — unchanged from the issue's own list Ruby (no typed bindings), untyped JavaScript, and anything whose receiver has no annotation. Both corpora prove it: 0 new binds on `ruby-sinatra` and `js-express`. The `name_fallback_count` disclosure stays load-bearing permanently. ### Residual, NAMED The origin gate's stem/namespace anchor is still what decides between same-named containers; the shipped gate only refuses where a nearer competitor was demonstrably passed over. Two of the three phantom families were a RELATIVE import (`from .models import`) matching a file-key stem OUTSIDE the importing file's own directory subtree — which is a language-level impossibility and looks like one clean clause in `build_recv_origin`. It is not attempted here: it is shared with every other tier, so it would move the corpus for reasons unrelated to this issue, and it deserves its own bind-for-bind pass. Same family as #134 and #168.
Author
Member

Gate results, the FOUR baselines this moves, and a COST finding I did not expect

Follow-up to the implementation comment. Nothing here is blessed — the drifts are reported with their evidence and a draft reason, for a decision by whoever owns the records.

Green

gate exit
cargo fmt --all -- --check 0
cargo clippy --workspace --all-targets -- -D warnings 0
cargo doc --workspace --no-deps --document-private-items (RUSTDOCFLAGS=-D warnings shape) 0
precision_gate 7/7, phantoms=0, recall=1.000 in every language
code-index-indexer --lib 382 passed
resolver 95 passed
provenance_invariants 3 passed
pool_capability_registry 17 passed
code-index-plugins 272 + 8 + 2 passed

precision_gate is cited as the required gate and nothing more: it indexes 0 corpus repositories and scores phantoms only against declared decoys, so it is not evidence about any of the binds above.

The four baselines that move, and every one of them moves for the SAME reason

tests/corpus/baseline.json:

rust-ripgrep: resolved 15709 -> 16035 (+326)  edges 8092 -> 8339   resolved_read +220 resolved_write +106
cs-dapper:    resolved  3628 ->  3734 (+106)  edges 2613 -> 2692   resolved_read  +98 resolved_write   +8
ts-zod:       resolved 10781 -> 10869  (+88)  edges 5238 -> 5279   resolved_read  +28 resolved_member_access +60
php-guzzle:   resolved 11825 -> 11862  (+37)  edges 7727 -> 7761   resolved_read  +27 resolved_write  +10
python-flask: resolved  3026 ->  3052  (+26)  edges 1770 -> 1791   resolved_read  +20 resolved_member_access +6

tests/corpus/stage-baseline.json — only rule.tier1r_receiver moved, on every repo, by exactly the resolved delta:

cs-dapper 38 -> 144 (+106) | php-guzzle 675 -> 712 (+37) | python-flask 7 -> 33 (+26)
rust-ripgrep 387 -> 713 (+326) | ts-zod 53 -> 141 (+88)

tests/corpus/tier3-baseline.json:

py-django:     resolved 107491 -> 108599 (+1108)  rule.tier1r_receiver 1610 -> 2718 (+1108)
rust-analyzer: resolved 143113 -> 146344 (+3231)  rule.tier1r_receiver 1802 -> 5033 (+3231)

No other rule moved anywhere. rule.tier1r_receiver accounts for 100 % of every count in all four files, which is the strongest form of "this is the change and nothing else": if the widening had disturbed another tier, a second rule would have moved.

Draft reason, for whoever blesses

#69: tier 1R serves the MEMBER pool. A read/write through a receiver whose type a binding row proves now binds, so the candidate join splices pool_class_admits instead of a hand-copied m.kind = 'method'. +4 922 binds over nine pinned repos, 0 lost, bind-for-bind against an fc329a8 binary joined on (path, line, col, kind, name, target, rule); 100 % of the gain is rule.tier1r_receiver and no other rule moved. Bounded outside the resolver by each rust crate's Cargo.toml (rust-analyzer: 1 192 cross-crate new binds, 2 undeclared, both read at source and correct re-export binds through ide-db's pub use base_db). Five phantoms found by an ambiguous-container census and read at source (cs-dapper post.Id ×2, py-django referrer.article, player.team ×2) are refused by a nearer-competitor gate scoped to the member pool; that gate also recovers +70 correct py-django binds by disambiguation.

AND A COST FINDING — corpus_cost is red, on SQLite's own counters

This is the one I did not anticipate, and it is a real regression report rather than a loaded runner: vm_step is SQLite's VM step counter, not the clock.

cs-dapper    vm_step 24 519 615 -> 31 047 135  (+26.6 %)
ts-zod       vm_step 114 140 737 -> 154 010 153 (+34.9 %)
php-guzzle   vm_step 44 631 448 -> 53 666 533  (+20.2 %)
python-flask vm_step 14 525 751 -> 16 708 637  (+15.0 %)
rust-ripgrep vm_step 81 381 997 -> 86 815 819  (+6.7 %)

Attributed, by building and measuring the variants. The first cut was worse — cs-dapper +42.1 %, ts-zod +36.1 %, and autoindex and fullscan_step over the band on five repos too. Two mistakes, both found by this gate and by nothing else:

  1. temp.file_ancestor was built for EVERY file, when the only directory prefixes the gate can ask about are those a member-pool recv_bound row names. Restricting it put autoindex and fullscan_step back inside the band on every repo.
  2. temp.recv_nearer was driven off recv_bound (one row per REF) instead of the distinct key set. Fixing that with an inline SELECT DISTINCT made ts-zod worse — +65 % — because SQLite materialised the subquery and re-scanned it. Materialising the key set into its own WITHOUT-ROWID table with a primary key, and pinning the join order with CROSS JOIN, brought it to zero.

With both fixed, the gate machinery costs nothing measurable: the numbers above are within 0.03 % of a build with the gate and both relations disabled entirely. All of the remaining drift is the widening itself — tier 1R's input is now method calls plus member accesses, and member accesses are the more numerous kind.

So the cost question is a clean one, with no implementation waste left in it: is +6.7 % to +34.9 % more SQLite work an acceptable price for +4 922 resolved references and 0 lost? That is a product decision, tests/corpus/cost-baseline.json is bless-gated, and I have not touched it. The bind set is byte-identical before and after both optimisations, verified by re-indexing all nine repos and diffing (TOTAL DIFF = 0).

One test that failed only under load, and passes alone

index_coverage_facts_e2e::a_refused_file_is_not_reported_as_indexed_and_current failed once during a contended full-workspace run with coverage_reasons: ["index_reconciling"] — the daemon was still in RuntimePhase::Reconciling when the assertion ran. It passes in isolation (2 passed). Recording it because a longer reconcile makes that window wider, and the widening does lengthen the reconcile; the test waits for verdict != "pending", which is not the same condition as "the daemon is steady".

## Gate results, the FOUR baselines this moves, and a COST finding I did not expect Follow-up to the implementation comment. **Nothing here is blessed** — the drifts are reported with their evidence and a draft reason, for a decision by whoever owns the records. ### Green | gate | exit | |---|---| | `cargo fmt --all -- --check` | **0** | | `cargo clippy --workspace --all-targets -- -D warnings` | **0** | | `cargo doc --workspace --no-deps --document-private-items` (`RUSTDOCFLAGS=-D warnings` shape) | **0** | | `precision_gate` | **7/7, `phantoms=0`, `recall=1.000` in every language** | | `code-index-indexer --lib` | 382 passed | | `resolver` | 95 passed | | `provenance_invariants` | 3 passed | | `pool_capability_registry` | 17 passed | | `code-index-plugins` | 272 + 8 + 2 passed | `precision_gate` is cited as the required gate and nothing more: it indexes **0** corpus repositories and scores phantoms only against declared decoys, so it is not evidence about any of the binds above. ### The four baselines that move, and every one of them moves for the SAME reason `tests/corpus/baseline.json`: ``` rust-ripgrep: resolved 15709 -> 16035 (+326) edges 8092 -> 8339 resolved_read +220 resolved_write +106 cs-dapper: resolved 3628 -> 3734 (+106) edges 2613 -> 2692 resolved_read +98 resolved_write +8 ts-zod: resolved 10781 -> 10869 (+88) edges 5238 -> 5279 resolved_read +28 resolved_member_access +60 php-guzzle: resolved 11825 -> 11862 (+37) edges 7727 -> 7761 resolved_read +27 resolved_write +10 python-flask: resolved 3026 -> 3052 (+26) edges 1770 -> 1791 resolved_read +20 resolved_member_access +6 ``` `tests/corpus/stage-baseline.json` — **only `rule.tier1r_receiver` moved, on every repo, by exactly the resolved delta**: ``` cs-dapper 38 -> 144 (+106) | php-guzzle 675 -> 712 (+37) | python-flask 7 -> 33 (+26) rust-ripgrep 387 -> 713 (+326) | ts-zod 53 -> 141 (+88) ``` `tests/corpus/tier3-baseline.json`: ``` py-django: resolved 107491 -> 108599 (+1108) rule.tier1r_receiver 1610 -> 2718 (+1108) rust-analyzer: resolved 143113 -> 146344 (+3231) rule.tier1r_receiver 1802 -> 5033 (+3231) ``` No other rule moved anywhere. `rule.tier1r_receiver` accounts for 100 % of every count in all four files, which is the strongest form of "this is the change and nothing else": if the widening had disturbed another tier, a second rule would have moved. ### Draft reason, for whoever blesses > #69: tier 1R serves the MEMBER pool. A `read`/`write` through a receiver whose type a binding row proves now binds, so the candidate join splices `pool_class_admits` instead of a hand-copied `m.kind = 'method'`. +4 922 binds over nine pinned repos, **0 lost**, bind-for-bind against an `fc329a8` binary joined on (path, line, col, kind, name, target, rule); 100 % of the gain is `rule.tier1r_receiver` and no other rule moved. Bounded outside the resolver by each rust crate's `Cargo.toml` (rust-analyzer: 1 192 cross-crate new binds, 2 undeclared, both read at source and correct re-export binds through `ide-db`'s `pub use base_db`). Five phantoms found by an ambiguous-container census and read at source (cs-dapper `post.Id` ×2, py-django `referrer.article`, `player.team` ×2) are refused by a nearer-competitor gate scoped to the member pool; that gate also recovers +70 correct py-django binds by disambiguation. ### AND A COST FINDING — `corpus_cost` is red, on SQLite's own counters This is the one I did not anticipate, and it is a real regression report rather than a loaded runner: `vm_step` is SQLite's VM step counter, not the clock. ``` cs-dapper vm_step 24 519 615 -> 31 047 135 (+26.6 %) ts-zod vm_step 114 140 737 -> 154 010 153 (+34.9 %) php-guzzle vm_step 44 631 448 -> 53 666 533 (+20.2 %) python-flask vm_step 14 525 751 -> 16 708 637 (+15.0 %) rust-ripgrep vm_step 81 381 997 -> 86 815 819 (+6.7 %) ``` **Attributed, by building and measuring the variants.** The first cut was worse — cs-dapper +42.1 %, ts-zod +36.1 %, and `autoindex` and `fullscan_step` over the band on five repos too. Two mistakes, both found by this gate and by nothing else: 1. `temp.file_ancestor` was built for EVERY file, when the only directory prefixes the gate can ask about are those a member-pool `recv_bound` row names. Restricting it put `autoindex` and `fullscan_step` back inside the band on every repo. 2. `temp.recv_nearer` was driven off `recv_bound` (one row per REF) instead of the distinct key set. Fixing that with an inline `SELECT DISTINCT` made ts-zod **worse** — +65 % — because SQLite materialised the subquery and re-scanned it. Materialising the key set into its own WITHOUT-ROWID table with a primary key, and pinning the join order with `CROSS JOIN`, brought it to zero. With both fixed, the gate machinery costs **nothing measurable**: the numbers above are within 0.03 % of a build with the gate and both relations disabled entirely. **All of the remaining drift is the widening itself** — tier 1R's input is now method calls *plus* member accesses, and member accesses are the more numerous kind. So the cost question is a clean one, with no implementation waste left in it: **is +6.7 % to +34.9 % more SQLite work an acceptable price for +4 922 resolved references and 0 lost?** That is a product decision, `tests/corpus/cost-baseline.json` is bless-gated, and I have not touched it. The bind set is byte-identical before and after both optimisations, verified by re-indexing all nine repos and diffing (`TOTAL DIFF = 0`). ### One test that failed only under load, and passes alone `index_coverage_facts_e2e::a_refused_file_is_not_reported_as_indexed_and_current` failed once during a contended full-workspace run with `coverage_reasons: ["index_reconciling"]` — the daemon was still in `RuntimePhase::Reconciling` when the assertion ran. It passes in isolation (`2 passed`). Recording it because a longer reconcile makes that window wider, and the widening does lengthen the reconcile; the test waits for `verdict != "pending"`, which is not the same condition as "the daemon is steady".
Author
Member

RE-MEASURED ON TOP OF 9d0e06a — the cost was 4x overstated, vm_step is now INSIDE the band on every repo, and the residual tracks one number

The earlier cost comment measured against fc329a8. That overstated this change's price by roughly four times, and the reason is mechanical: the widening multiplies the rows entering CREATE TEMP TABLE recv_bound, and on fc329a8 that statement carried an unsargable span test evaluated over every function in the file, per row. My cost was being multiplied by someone else's bug.

Note on provenance: 9d0e06a is NOT on origin/master. Master is 4f866e5, which does not contain it; 9d0e06a sits directly on fc329a8, my own base. I rebased onto it because it is the baseline this will be judged against once it lands, and everything below says so.

Rebase verified three ways before any measurement: 9d0e06a's bind set is byte-identical to fc329a8's on all nine repos (its own claim, independently confirmed, TOTAL DIFF = 0); my rebased lane's bind set is byte-identical to my pre-rebase measurement (TOTAL DIFF = 0); and against the new baseline it is still +4 922 / 0 lost.

1 + 2. Cost and binds, side by side, all seven tier-1 repos

repo base 9d0e06a with #69 delta % binds gained opcodes / bind driver × vs the RECORDED baseline
cs-dapper 21,602,378 23,459,161 +1,856,783 +8.60% 106 17,517 1.84× −4.3%
ts-zod 105,505,483 112,707,893 +7,202,410 +6.83% 88 81,846 1.66× −1.3%
python-flask 13,596,033 14,216,711 +620,678 +4.57% 26 23,872 1.92× −2.1%
php-guzzle 40,312,830 41,336,191 +1,023,361 +2.54% 37 27,658 1.50× −7.4%
rust-ripgrep 72,370,582 73,880,030 +1,509,448 +2.09% 326 4,630 1.30× −9.2%
js-express 16,345,615 16,648,928 +303,313 +1.86% 0 — 1.29× −11.0%
ruby-sinatra 14,922,343 14,961,233 +38,890 +0.26% 0 — 1.00× n/a
total 284,655,264 297,210,147 +12,554,883 +4.41% 583 21,535

driver × is how much bigger tier 1R's input gets: (method_call rows + member rows admitted) / method_call rows, counted on the baseline index.

The last column is the one that decides the trade. Shipped together with 9d0e06a, cold-index vm_step goes DOWN on every repo against tests/corpus/cost-baseline.json — −1.3% to −11.0%. corpus_cost reports exactly one vm_step line now, and it is js-express improving past the −10% floor. The +5% ceiling is not breached on vm_step anywhere.

Answering the question you actually asked: no, the expensive repo is not the one being paid for

cs-dapper pays the highest percentage (+8.60%) for 106 binds. rust-ripgrep pays +2.09% for 326 — the most binds and the cheapest, at 4,630 opcodes each, an order of magnitude better than ts-zod's 81,846.

But the sharper fact is that the cost gate's seven repos hold 583 of the 4 922 binds — 12%. The two repos where 88% of the gain lands, rust-analyzer (+3,231) and py-django (+1,108), are not in corpus_cost's set at all, so the 21,535 opcodes/bind above is computed on the least favourable eighth of the population. I am not going to extrapolate it — they are rust and python, the two languages with the best ratios in the table, but that is an inference and the measurement does not exist. That gap is itself worth recording: the cost gate and the recall gate are measured on different repo sets, so no single number in this project currently prices a bind.

3. Is the residual inherent? Yes, and here is the control that proves it

ruby-sinatra: driver × 1.00, cost +0.26%. Ruby admits no member rows (its access refs are @ivar with self receivers), so the change adds no rows there — and adds essentially no cost. The % column and the driver × column move together across all seven.

Statement-level attribution on cs-dapper, the worst repo, with the instrument 9d0e06a built (cost_attribution.rs), baseline vs lane:

statement base lane delta share of +1.85M
CREATE TEMP TABLE recv_bound 930,924 1,946,266 +1,015,342 55%
INSERT temp.recv_calls 282,343 571,193 +288,850 16%
INSERT temp.recv_decisions (the ident arm) below top-N 436,358 ~+240,000 ~13%
INSERT temp.recv_binds 242,729 255,960 +13,231 0.7%
file_ancestor, recv_nearer, recv_nearer_keys — absent from the table ~0 ~0%

~84% is three tier-1R statements whose input I enlarged. The gate machinery does not reach the top six statements — after the two fixes in the previous comment it costs nothing measurable, and this is the independent confirmation of that.

The third shape, built and refused. The obvious remaining idea: a member ref whose receiver has no binding row of that name anywhere in its file can never survive recv_bound's inner join — Foo.Bar where Foo names a type or a namespace, which is most member access in C# and TypeScript, and it is exactly why js-express pays +1.86% for zero binds. Dropping those rows before recv_bound runs, by a primary-key-prefix seek into recv_binds, cannot change a bind (the condition is the one the join already enforces).

Measured, it is WORSE on every repo: cs-dapper 23,459,161 → 23,516,351, ts-zod 112,707,893 → 112,925,482. The delete costs more than the rows cost the join, because the (file_id, name) primary-key probe was already discarding them for less than a scan of recv_calls. Applied and reverted; index.rs is md5-identical to its pre-experiment snapshot.

So: the residual is one index seek per additional member reference. There is no shape left that I can see which pays for itself, and the control repo says the cost is the row count and nothing structural.

What is still over a ceiling, and what it is

fullscan_step, on five repos: cs-dapper +14.1%, ts-zod +10.5%, python-flask +9.0%, php-guzzle +6.6%, js-express +5.5%. Same cause, not a plan regression — the temp relations tier 1R scans (recv_calls, recv_bound) hold 1.3–1.9× the rows, and a full scan of a bigger table visits more rows by definition. Measured share: with the gate and both its relations disabled entirely, cs-dapper's fullscan_step is 306,775 against 310,861 with them, so the gate accounts for 10% of that delta and the widening for 90%. sort is flat everywhere; autoindex is back inside the band on every repo after the fixes in the previous comment.

The bind-per-opcode case, made rather than argued by analogy

You are right that the m0061 precedent does not transfer: that paid once and repaid per call, and this has no per-call saving. The case here is different in kind and should be judged as such.

  • Combined with 9d0e06a the cold index gets CHEAPER on every repo (−1.3% to −11.0%) while resolving 4 922 more references. If the two land together, there is no cost to weigh at all — the ledger is positive on both axes.
  • Alone, on top of 9d0e06a, it is +4.41% across the seven measured repos. Against m0061's +3.84% that is the same order, for a recall change rather than a latency one.
  • The unit is 4,630 opcodes per bind at best and 81,846 at worst, on the eighth of the population the cost gate can see. Whether that is worth paying is your call and I have not touched cost-baseline.json.

Everything else on the rebased tree

fmt 0 · clippy 0 · cargo doc --document-private-items 0 · indexer --lib 382 · resolver 95 · provenance_invariants 3 · pool_capability_registry 17 · receiver_phantom 25 (9d0e06a's own new suite, green under the widening) · code-index-plugins 272+8+2 · precision_gate 7/7, phantoms=0, recall 1.000.

corpus_ratchet / corpus_stage / corpus_tier3_ratchet drift exactly as reported before — binds are unchanged by the rebase, so those three tables stand verbatim. Nothing blessed.

## RE-MEASURED ON TOP OF `9d0e06a` — the cost was 4x overstated, `vm_step` is now INSIDE the band on every repo, and the residual tracks one number The earlier cost comment measured against `fc329a8`. That overstated this change's price by roughly four times, and the reason is mechanical: the widening multiplies the rows entering `CREATE TEMP TABLE recv_bound`, and on `fc329a8` that statement carried an unsargable span test evaluated over every function in the file, per row. My cost was being multiplied by someone else's bug. **Note on provenance: `9d0e06a` is NOT on `origin/master`.** Master is `4f866e5`, which does not contain it; `9d0e06a` sits directly on `fc329a8`, my own base. I rebased onto it because it is the baseline this will be judged against once it lands, and everything below says so. Rebase verified three ways before any measurement: `9d0e06a`'s bind set is **byte-identical to `fc329a8`'s** on all nine repos (its own claim, independently confirmed, `TOTAL DIFF = 0`); my rebased lane's bind set is **byte-identical to my pre-rebase measurement** (`TOTAL DIFF = 0`); and against the new baseline it is still **+4 922 / 0 lost**. ### 1 + 2. Cost and binds, side by side, all seven tier-1 repos | repo | base `9d0e06a` | with #69 | delta | % | binds gained | opcodes / bind | driver × | vs the RECORDED baseline | |---|---:|---:|---:|---:|---:|---:|---:|---:| | cs-dapper | 21,602,378 | 23,459,161 | +1,856,783 | **+8.60%** | 106 | 17,517 | 1.84× | **−4.3%** | | ts-zod | 105,505,483 | 112,707,893 | +7,202,410 | **+6.83%** | 88 | 81,846 | 1.66× | **−1.3%** | | python-flask | 13,596,033 | 14,216,711 | +620,678 | +4.57% | 26 | 23,872 | 1.92× | **−2.1%** | | php-guzzle | 40,312,830 | 41,336,191 | +1,023,361 | +2.54% | 37 | 27,658 | 1.50× | **−7.4%** | | rust-ripgrep | 72,370,582 | 73,880,030 | +1,509,448 | +2.09% | **326** | **4,630** | 1.30× | **−9.2%** | | js-express | 16,345,615 | 16,648,928 | +303,313 | +1.86% | 0 | — | 1.29× | **−11.0%** | | ruby-sinatra | 14,922,343 | 14,961,233 | +38,890 | **+0.26%** | 0 | — | **1.00×** | n/a | | **total** | 284,655,264 | 297,210,147 | +12,554,883 | **+4.41%** | 583 | 21,535 | | | *driver ×* is how much bigger tier 1R's input gets: `(method_call rows + member rows admitted) / method_call rows`, counted on the baseline index. **The last column is the one that decides the trade.** Shipped together with `9d0e06a`, cold-index `vm_step` goes **DOWN on every repo** against `tests/corpus/cost-baseline.json` — −1.3% to −11.0%. `corpus_cost` reports exactly one `vm_step` line now, and it is js-express **improving** past the −10% floor. The +5% ceiling is not breached on `vm_step` anywhere. ### Answering the question you actually asked: no, the expensive repo is not the one being paid for cs-dapper pays the highest percentage (+8.60%) for **106** binds. rust-ripgrep pays +2.09% for **326** — the most binds and the cheapest, at 4,630 opcodes each, an order of magnitude better than ts-zod's 81,846. But the sharper fact is that **the cost gate's seven repos hold 583 of the 4 922 binds — 12%.** The two repos where 88% of the gain lands, rust-analyzer (+3,231) and py-django (+1,108), are not in `corpus_cost`'s set at all, so the 21,535 opcodes/bind above is computed on the least favourable eighth of the population. I am not going to extrapolate it — they are rust and python, the two languages with the best ratios in the table, but that is an inference and the measurement does not exist. **That gap is itself worth recording: the cost gate and the recall gate are measured on different repo sets, so no single number in this project currently prices a bind.** ### 3. Is the residual inherent? Yes, and here is the control that proves it `ruby-sinatra`: driver × **1.00**, cost **+0.26%**. Ruby admits no member rows (its access refs are `@ivar` with self receivers), so the change adds no rows there — and adds essentially no cost. The `%` column and the `driver ×` column move together across all seven. Statement-level attribution on cs-dapper, the worst repo, with the instrument `9d0e06a` built (`cost_attribution.rs`), baseline vs lane: | statement | base | lane | delta | share of +1.85M | |---|---:|---:|---:|---:| | `CREATE TEMP TABLE recv_bound` | 930,924 | 1,946,266 | +1,015,342 | **55%** | | `INSERT temp.recv_calls` | 282,343 | 571,193 | +288,850 | 16% | | `INSERT temp.recv_decisions` (the ident arm) | below top-N | 436,358 | ~+240,000 | ~13% | | `INSERT temp.recv_binds` | 242,729 | 255,960 | +13,231 | 0.7% | | `file_ancestor`, `recv_nearer`, `recv_nearer_keys` | — | **absent from the table** | ~0 | **~0%** | ~84% is three tier-1R statements whose input I enlarged. The gate machinery does not reach the top six statements — after the two fixes in the previous comment it costs nothing measurable, and this is the independent confirmation of that. **The third shape, built and refused.** The obvious remaining idea: a member ref whose receiver has no `binding` row of that name anywhere in its file can never survive `recv_bound`'s inner join — `Foo.Bar` where `Foo` names a type or a namespace, which is most member access in C# and TypeScript, and it is exactly why js-express pays +1.86% for zero binds. Dropping those rows before `recv_bound` runs, by a primary-key-prefix seek into `recv_binds`, cannot change a bind (the condition is the one the join already enforces). Measured, it is **WORSE on every repo**: cs-dapper 23,459,161 → 23,516,351, ts-zod 112,707,893 → 112,925,482. The delete costs more than the rows cost the join, because the `(file_id, name)` primary-key probe was already discarding them for less than a scan of `recv_calls`. Applied and reverted; `index.rs` is md5-identical to its pre-experiment snapshot. So: **the residual is one index seek per additional member reference.** There is no shape left that I can see which pays for itself, and the control repo says the cost is the row count and nothing structural. ### What is still over a ceiling, and what it is `fullscan_step`, on five repos: cs-dapper +14.1%, ts-zod +10.5%, python-flask +9.0%, php-guzzle +6.6%, js-express +5.5%. Same cause, not a plan regression — the temp relations tier 1R scans (`recv_calls`, `recv_bound`) hold 1.3–1.9× the rows, and a full scan of a bigger table visits more rows by definition. Measured share: with the gate and both its relations disabled entirely, cs-dapper's `fullscan_step` is 306,775 against 310,861 with them, so the gate accounts for **10%** of that delta and the widening for 90%. `sort` is flat everywhere; `autoindex` is back inside the band on every repo after the fixes in the previous comment. ### The bind-per-opcode case, made rather than argued by analogy You are right that the m0061 precedent does not transfer: that paid once and repaid per call, and this has no per-call saving. The case here is different in kind and should be judged as such. - **Combined with `9d0e06a` the cold index gets CHEAPER on every repo** (−1.3% to −11.0%) while resolving 4 922 more references. If the two land together, there is no cost to weigh at all — the ledger is positive on both axes. - **Alone, on top of `9d0e06a`, it is +4.41% across the seven measured repos.** Against m0061's +3.84% that is the same order, for a recall change rather than a latency one. - **The unit is 4,630 opcodes per bind at best and 81,846 at worst**, on the eighth of the population the cost gate can see. Whether that is worth paying is your call and I have not touched `cost-baseline.json`. ### Everything else on the rebased tree `fmt` 0 · `clippy` 0 · `cargo doc --document-private-items` 0 · indexer `--lib` 382 · `resolver` 95 · `provenance_invariants` 3 · `pool_capability_registry` 17 · **`receiver_phantom` 25** (`9d0e06a`'s own new suite, green under the widening) · `code-index-plugins` 272+8+2 · `precision_gate` **7/7, phantoms=0, recall 1.000**. `corpus_ratchet` / `corpus_stage` / `corpus_tier3_ratchet` drift exactly as reported before — binds are unchanged by the rebase, so those three tables stand verbatim. Nothing blessed.
Author
Member

BIND INSPECTION — 18 phantoms found in 4,922. Not a clean bill, and the class that produced them was invisible to the census I built

Asked for binds rather than deltas, weighted to rust-analyzer and py-django. Here they are, including the part I got wrong.

The diff on the requested key

Joined on (path, line, col, kind, occurrence), occurrence assigned within each position/kind group over an id-independent ordering:

repo NEW LOST RETARGETED
rust-analyzer 3,231 0 0
py-django 1,108 0 0
rust-ripgrep 326 0 0
cs-dapper 106 0 0
ts-zod 88 0 0
php-guzzle 37 0 0
python-flask 26 0 0
js-express / ruby-sinatra 0 0 0

RETARGETED is the outcome a whole-row diff hides and I had not reported before: no pre-existing bind was silently re-aimed.

What a phantom of this class structurally needs

A tier-1R member bind is x.m -> T.m where a binding row types x as T and exactly one indexed (container T, member m) survives the origin gate, visibility and the nearer-competitor gate. It can be wrong three ways:

  • P1 — wrong T among indexed rivals. Needs ≥2 indexed containers named T each holding a member m. Enumerable, and enumerated 100%: 133 of 4,922 (2.7%). Of those, 104 have the target INSIDE the referencing file's own subtree (the gate's guarantee); 29 have it outside — the entire decisive set — and I read all 29 at source. All 29 correct. Zero exposure on cs-dapper, ts-zod, php-guzzle, python-flask, rust-ripgrep — and cs-dapper's zero is EARNED by the gate, it was 2.
  • P2 — the binding row is wrong (misread annotation, missed shadow, the provisional T::new() convention). Not enumerable. 41-bind stratified random sample read at source, 41 correct, including two provisional bindings (TypeAliasSignature::of, QualifierCtx::default).
  • P3 — T names something not indexed. The Cargo oracle over all 1,192 rust-analyzer cross-crate binds: 2 undeclared, both read, both correct re-export binds.

AND THE CLASS I MISSED — P1b, 18 phantoms

P1 asks whether the (container, member) PAIR is ambiguous. It is blind when the true owner's member is INHERITED OR IMPLICIT and therefore has no symbol at all. Django is full of this, and an import-statement oracle over py-django's 976 cross-file binds — reading the source, not the index — is what surfaced it:

tests/basic/tests.py:49   `a = Article(id=None, …)`   a.id  ->  tests/prefetch_related/models.py:186
tests/field_defaults/tests.py:36  `a = Article()`     a.id  ->  tests/prefetch_related/models.py:186
tests/foreign_object/tests.py:495 `a3 = Article(…)`   a3.id ->  tests/prefetch_related/models.py:186
tests/composite_pk/test_create.py:34  (from .models import User)  user.email -> tests/select_related_onetoone/models.py:6
tests/auth_tests/test_models.py:281   (django auth User)          user.email -> tests/select_related_onetoone/models.py:6

Article has 47 declarations in this repo and SELECT … WHERE name='id' AND parent='Article' returns exactly one row — tests/prefetch_related/models.py, the only app that writes id out. Every other app's Article.id is Django's implicit primary key: real in the language, absent from the source, therefore absent from the index. Same for User.email, which lives on AbstractUser. So the pair is unique and P1 sees nothing.

Enumerated: 151 of 4,922 binds (3.07%) have an ambiguous CONTAINER NAME with the target outside the ref's subtree — the complete exposed population. Triaged by target family; the cross-app ones read at source:

family binds verdict
Article.id -> tests/prefetch_related/ 12 PHANTOM
User.email -> tests/select_related_onetoone/ 6 PHANTOM
TestModel.lastmod -> tests/sitemaps_tests/models.py 4 correct (same app, ref in its urls/ subdir)
rust-analyzer Runnable / TestItem / Highlight / Attr / PathSegment / MatchArm / Lint / MovedOutOfRef 33 correct — read; the source names the crate (ide::TestItem, hir_expand's Attr)
py-django django/… library targets 96 correct — the test imports the library

18 wrong, 133 correct, in the fully enumerated exposed class.

The gate variant that would catch them — BUILT, MEASURED, REFUSED

Widen the nearer-competitor test from "a same-named container holding a same-named member" to "a same-named container of a data-member-holding kind" (class/struct/trait/module, excluding impl — the reason the first container-level attempt cost 46 binds).

phantoms removed 12 of 18
phantoms surviving 6 (User.email; I did not trace why tests/composite_pk/models/tenant.py's local User fails to fire the gate, and I am flagging that rather than guessing)
correct binds lost 17 — 15 on rust-analyzer (to_proto.rs reading ide::Runnable.nav/kind/cfg and ide::TestItem.file/text_range, all read at source), 2 on py-django (WSGIRequest.environ, Serializer.stream)

17 correct for 12 wrong, with 6 wrong left behind. Refused on the numbers. Reverted; index.rs md5-identical to its pre-experiment snapshot.

The reason it is the wrong instrument: in every rust-analyzer loss the source is EXPLICIT about which type it means (ide::Runnable), and a locality test overrules that. The discriminator these 18 need is import evidence, not proximity — which is #196's clause, not a tuning of this gate.

The sentence you asked for

This change admits 4,922 binds: 4,904 correct and 18 wrong (0.37%).

Basis, stated so the number can be attacked: the 18 are enumerated, not extrapolated — they are the whole of the two cross-app families inside a 151-bind population that was enumerated exhaustively at the container level and 133-bind population enumerated exhaustively at the pair level. The 4,904 is a residual claim, not 4,904 individual verifications: ~79 distinct sites were read at source (41 stratified random, 29 exhaustive P1-outside, ~9 family representatives), zero wrong outside the 18, and the two enumerable phantom classes are closed. A binomial bound from the random arm alone is only ≤7% at 95%; the enumeration is what carries the claim, not the sample.

The #165 question, answered directly

You asked whether the corpus can express the failure. It can, and it did — twice. The pair-level class produced 5 phantoms (caught pre-merge, gate built); the container-level class produced these 18. Unlike #165's hyphenated-crate blind spot, nothing here was invisible to the corpus — it was invisible to my census, and the source-level import oracle is what saw past it. The one thing the corpus still cannot show is a repo where the receiver type comes from a dependency with an in-project namesake in a language with no manifest (python/PHP/TS); rust has the Cargo oracle, those three have nothing.

Provenance and two dogfood findings

9d0e06a was not on origin/master when I checked (master was 4f866e5); it is now, and 9d0e06a..origin/master touches index.rs with comment-only changes, so my measurements stand against current master without re-running.

Our own MCP tools could not do this work. read_code on a corpus path returns path_outside_known_roots and search_symbols("MethodCallee") returns symbol_not_found — the pinned corpus is not a linked project, so every source read here was sed. That is the same gap #175's comment recorded for resolution_gaps, and it now blocks the bind-inspection workflow this project asks every resolver lane to perform.

## BIND INSPECTION — **18 phantoms found** in 4,922. Not a clean bill, and the class that produced them was invisible to the census I built Asked for binds rather than deltas, weighted to rust-analyzer and py-django. Here they are, including the part I got wrong. ### The diff on the requested key Joined on `(path, line, col, kind, occurrence)`, occurrence assigned within each position/kind group over an id-independent ordering: | repo | NEW | LOST | **RETARGETED** | |---|---:|---:|---:| | rust-analyzer | 3,231 | 0 | **0** | | py-django | 1,108 | 0 | **0** | | rust-ripgrep | 326 | 0 | **0** | | cs-dapper | 106 | 0 | **0** | | ts-zod | 88 | 0 | **0** | | php-guzzle | 37 | 0 | **0** | | python-flask | 26 | 0 | **0** | | js-express / ruby-sinatra | 0 | 0 | 0 | `RETARGETED` is the outcome a whole-row diff hides and I had not reported before: **no pre-existing bind was silently re-aimed.** ### What a phantom of this class structurally needs A tier-1R member bind is `x.m -> T.m` where a binding row types `x` as `T` and exactly one indexed `(container T, member m)` survives the origin gate, visibility and the nearer-competitor gate. It can be wrong three ways: * **P1 — wrong `T` among indexed rivals.** Needs ≥2 indexed containers named `T` each holding a member `m`. **Enumerable, and enumerated 100%: 133 of 4,922 (2.7%).** Of those, 104 have the target INSIDE the referencing file's own subtree (the gate's guarantee); **29 have it outside — the entire decisive set — and I read all 29 at source. All 29 correct.** Zero exposure on cs-dapper, ts-zod, php-guzzle, python-flask, rust-ripgrep — and cs-dapper's zero is EARNED by the gate, it was 2. * **P2 — the binding row is wrong** (misread annotation, missed shadow, the provisional `T::new()` convention). Not enumerable. **41-bind stratified random sample read at source, 41 correct**, including two provisional bindings (`TypeAliasSignature::of`, `QualifierCtx::default`). * **P3 — `T` names something not indexed.** The Cargo oracle over all 1,192 rust-analyzer cross-crate binds: 2 undeclared, both read, both correct re-export binds. ### AND THE CLASS I MISSED — P1b, 18 phantoms P1 asks whether the `(container, member)` PAIR is ambiguous. **It is blind when the true owner's member is INHERITED OR IMPLICIT and therefore has no symbol at all.** Django is full of this, and an import-statement oracle over py-django's 976 cross-file binds — reading the source, not the index — is what surfaced it: ``` tests/basic/tests.py:49 `a = Article(id=None, …)` a.id -> tests/prefetch_related/models.py:186 tests/field_defaults/tests.py:36 `a = Article()` a.id -> tests/prefetch_related/models.py:186 tests/foreign_object/tests.py:495 `a3 = Article(…)` a3.id -> tests/prefetch_related/models.py:186 tests/composite_pk/test_create.py:34 (from .models import User) user.email -> tests/select_related_onetoone/models.py:6 tests/auth_tests/test_models.py:281 (django auth User) user.email -> tests/select_related_onetoone/models.py:6 ``` `Article` has **47** declarations in this repo and `SELECT … WHERE name='id' AND parent='Article'` returns exactly **one row** — `tests/prefetch_related/models.py`, the only app that writes `id` out. Every other app's `Article.id` is Django's implicit primary key: real in the language, absent from the source, therefore absent from the index. Same for `User.email`, which lives on `AbstractUser`. So the pair is *unique* and P1 sees nothing. **Enumerated: 151 of 4,922 binds (3.07%) have an ambiguous CONTAINER NAME with the target outside the ref's subtree** — the complete exposed population. Triaged by target family; the cross-app ones read at source: | family | binds | verdict | |---|---:|---| | `Article.id -> tests/prefetch_related/` | **12** | **PHANTOM** | | `User.email -> tests/select_related_onetoone/` | **6** | **PHANTOM** | | `TestModel.lastmod -> tests/sitemaps_tests/models.py` | 4 | correct (same app, ref in its `urls/` subdir) | | rust-analyzer `Runnable` / `TestItem` / `Highlight` / `Attr` / `PathSegment` / `MatchArm` / `Lint` / `MovedOutOfRef` | 33 | correct — read; the source names the crate (`ide::TestItem`, `hir_expand`'s `Attr`) | | py-django `django/…` library targets | 96 | correct — the test imports the library | **18 wrong, 133 correct, in the fully enumerated exposed class.** ### The gate variant that would catch them — BUILT, MEASURED, REFUSED Widen the nearer-competitor test from "a same-named container holding a same-named member" to "a same-named container of a data-member-holding kind" (`class`/`struct`/`trait`/`module`, excluding `impl` — the reason the first container-level attempt cost 46 binds). | | | |---|---:| | phantoms removed | **12 of 18** | | phantoms surviving | **6** (`User.email`; I did not trace why `tests/composite_pk/models/tenant.py`'s local `User` fails to fire the gate, and I am flagging that rather than guessing) | | correct binds lost | **17** — 15 on rust-analyzer (`to_proto.rs` reading `ide::Runnable.nav/kind/cfg` and `ide::TestItem.file/text_range`, all read at source), 2 on py-django (`WSGIRequest.environ`, `Serializer.stream`) | **17 correct for 12 wrong, with 6 wrong left behind.** Refused on the numbers. Reverted; `index.rs` md5-identical to its pre-experiment snapshot. The reason it is the wrong instrument: in every rust-analyzer loss the source is EXPLICIT about which type it means (`ide::Runnable`), and a locality test overrules that. The discriminator these 18 need is import evidence, not proximity — which is #196's clause, not a tuning of this gate. ### The sentence you asked for > **This change admits 4,922 binds: 4,904 correct and 18 wrong (0.37%).** Basis, stated so the number can be attacked: the 18 are enumerated, not extrapolated — they are the whole of the two cross-app families inside a 151-bind population that was enumerated exhaustively at the container level and 133-bind population enumerated exhaustively at the pair level. The 4,904 is a residual claim, not 4,904 individual verifications: **~79 distinct sites were read at source** (41 stratified random, 29 exhaustive P1-outside, ~9 family representatives), zero wrong outside the 18, and the two enumerable phantom classes are closed. A binomial bound from the random arm alone is only ≤7% at 95%; the enumeration is what carries the claim, not the sample. ### The #165 question, answered directly You asked whether the corpus can express the failure. **It can, and it did — twice.** The pair-level class produced 5 phantoms (caught pre-merge, gate built); the container-level class produced these 18. Unlike #165's hyphenated-crate blind spot, nothing here was invisible to the corpus — it was invisible to *my census*, and the source-level import oracle is what saw past it. The one thing the corpus still cannot show is a repo where the receiver type comes from a dependency with an in-project namesake in a language with no manifest (python/PHP/TS); rust has the Cargo oracle, those three have nothing. ### Provenance and two dogfood findings `9d0e06a` was **not** on `origin/master` when I checked (master was `4f866e5`); it is now, and `9d0e06a..origin/master` touches `index.rs` with comment-only changes, so my measurements stand against current master without re-running. **Our own MCP tools could not do this work.** `read_code` on a corpus path returns `path_outside_known_roots` and `search_symbols("MethodCallee")` returns `symbol_not_found` — the pinned corpus is not a linked project, so every source read here was `sed`. That is the same gap #175's comment recorded for `resolution_gaps`, and it now blocks the bind-inspection workflow this project asks every resolver lane to perform.
Author
Member

HELD behind #196 — and the sequencing is now measured rather than assumed

Agreed with the hold. The 18 are new, this issue's title says zero-phantom, and 0.37% is a size not an argument. Recording the check that makes the sequencing evidence-backed instead of resting on my suggestion.

#196's clause reaches all 18

All five referencing files carry a relative import whose normalized key is models, which temp.file_keys holds as the basename stem of every models.py in the repository:

tests/basic/tests.py:29             from .models import (
tests/field_defaults/tests.py:20    from .models import (
tests/foreign_object/tests.py:13    from .models import (
tests/auth_tests/test_models.py:22  from .models import CustomEmailField, IntegerUsernameUser
tests/composite_pk/test_create.py:4 from .models import Post, Tenant, User

Four of the 18 do not merely fall silent — they retarget to the correct symbol: tests/composite_pk/models/tenant.py:20 really is email = models.EmailField(unique=True), so composite_pk's four user.email binds move from a foreign app to their own. The other 14 have no local candidate (Article.id is Django's implicit PK, auth_tests' User is django.contrib.auth's) and become honest silence. Both are right.

And the collateral on this change is bounded

Of the 4,922 new binds, 2,899 have a target outside the referencing file's own subtree. Of those, the number admitted through a relative-import stem — the exact mechanism #196 would withdraw:

rust-analyzer py-django php-guzzle ts-zod python-flask cs-dapper rust-ripgrep
0 32 0 0 0 0 0

18 of the 32 are the phantoms, so at most 14 correct binds are exposed, and 32 is an upper bound. Zero exposure on rust-analyzer, which carries 3,231 of the 4,922. The two changes are close to orthogonal, which is what makes the sequencing cheap rather than a gamble.

What this branch owes on re-merge

+4,922 / 0 lost / 0 retargeted is measured against 9d0e06a. #196 touches temp.file_keys, which feeds four tiers, so that baseline dies when #196 lands and everything here must be re-measured: the bind diff, the P1 and P1b censuses (the exposed populations change), and the cost table. I would rather say that now than have a stale table quoted at merge time.

If #196 is shown not to supply the evidence

Two fallbacks, so the hold has an exit that is not "wait indefinitely":

  1. Ship with the 18 disclosed, recorded the way #168's two LineIndex binds are — in the code, in the bless reason, and with the enumeration that bounds them. Weaker than this issue's title deserves, but it is the project's existing precedent for a measured, named residual.
  2. #199's first direction — make the census honest rather than the gate wider. A same-name count that can say "1 declared, and this container has a base class I cannot see through" turns all 18 from a silent wrong answer into a disclosed one, at the cost of zero binds. It does not fix the binds, but it is the move this project makes everywhere else, and it is the only option on the table that costs no recall.

The proximity variant stays refused: 12 of 18 for 17 correct binds, measured over nine repos, reverted.

Branch unchanged at 54339eb on 9d0e06a, wip-labelled, unpushed, working tree clean.

## HELD behind #196 — and the sequencing is now measured rather than assumed Agreed with the hold. The 18 are new, this issue's title says zero-phantom, and 0.37% is a size not an argument. Recording the check that makes the sequencing evidence-backed instead of resting on my suggestion. ### #196's clause reaches all 18 All five referencing files carry a relative import whose normalized key is `models`, which `temp.file_keys` holds as the basename stem of every `models.py` in the repository: ``` tests/basic/tests.py:29 from .models import ( tests/field_defaults/tests.py:20 from .models import ( tests/foreign_object/tests.py:13 from .models import ( tests/auth_tests/test_models.py:22 from .models import CustomEmailField, IntegerUsernameUser tests/composite_pk/test_create.py:4 from .models import Post, Tenant, User ``` Four of the 18 do not merely fall silent — they **retarget to the correct symbol**: `tests/composite_pk/models/tenant.py:20` really is `email = models.EmailField(unique=True)`, so `composite_pk`'s four `user.email` binds move from a foreign app to their own. The other 14 have no local candidate (`Article.id` is Django's implicit PK, `auth_tests`' `User` is `django.contrib.auth`'s) and become honest silence. Both are right. ### And the collateral on this change is bounded Of the 4,922 new binds, 2,899 have a target outside the referencing file's own subtree. Of those, the number admitted through a relative-import stem — the exact mechanism #196 would withdraw: | rust-analyzer | py-django | php-guzzle | ts-zod | python-flask | cs-dapper | rust-ripgrep | |---:|---:|---:|---:|---:|---:|---:| | **0** | **32** | 0 | 0 | 0 | 0 | 0 | 18 of the 32 are the phantoms, so at most **14 correct binds** are exposed, and 32 is an upper bound. **Zero exposure on rust-analyzer**, which carries 3,231 of the 4,922. The two changes are close to orthogonal, which is what makes the sequencing cheap rather than a gamble. ### What this branch owes on re-merge `+4,922 / 0 lost / 0 retargeted` is measured against `9d0e06a`. #196 touches `temp.file_keys`, which feeds four tiers, so **that baseline dies when #196 lands** and everything here must be re-measured: the bind diff, the P1 and P1b censuses (the exposed populations change), and the cost table. I would rather say that now than have a stale table quoted at merge time. ### If #196 is shown not to supply the evidence Two fallbacks, so the hold has an exit that is not "wait indefinitely": 1. **Ship with the 18 disclosed**, recorded the way #168's two `LineIndex` binds are — in the code, in the bless reason, and with the enumeration that bounds them. Weaker than this issue's title deserves, but it is the project's existing precedent for a measured, named residual. 2. **#199's first direction — make the census honest rather than the gate wider.** A same-name count that can say "1 declared, and this container has a base class I cannot see through" turns all 18 from a silent wrong answer into a disclosed one, at the cost of zero binds. It does not fix the binds, but it is the move this project makes everywhere else, and it is the only option on the table that costs no recall. The proximity variant stays refused: 12 of 18 for 17 correct binds, measured over nine repos, reverted. Branch unchanged at `54339eb` on `9d0e06a`, `wip`-labelled, unpushed, working tree clean.
Author
Member

The bypass is real, the fix is five lines in build_recv_origin — 18 phantoms become 2, and 38 PRE-EXISTING phantoms go with them, at zero cost to correct binds

Your reframing was right and it was worth more than the hold. Rebased onto a80eb61; new baseline built and re-measured from scratch.

The bypass, confirmed at source

build_recv_origin (#65, untouched by this branch — git diff a80eb61 -- index.rs names it nowhere) does this for every import row:

let key_runs = import_key_candidates(&module, max_key_run);   // no relative check
for cand in &key_runs { if let Some(fs) = key_map.get(...) { key_files.extend(fs) } }

Its own comment says the enumeration "is exactly … the same enumeration temp.import_key_rel is built from". It reproduces the enumeration and not the exclusion beside it — index.rs:4771, "relative specifiers (leading dot) are excluded — they carry no whole-segment file key and are handled by temp.import_rel". So from .models import Article lends the key models to every models.py in the tree, through a route that has been open since #65 and that #57 had already closed on the arm next to it.

Three variants built and measured over nine repos

A — skip relative specifiers, no replacement (the literal mirror of :4771). REFUSED. Kills 16 of 18, but build_recv_origin never consulted import_rel at all, so the evidence is removed with nothing put back: 48 pre-existing binds lost and 369 of this change's, and ts-zod collapses from 88 new binds to 3 — TypeScript imports are almost entirely relative.

B — consult temp.import_rel for a relative specifier, keep the stem lookup for absolute ones. Five lines. SHIPPED.

C — the subtree test. Not built: B dominates it on the measurement below, and B is a path-RESOLVED relation rather than a proximity heuristic.

B, bind-for-bind against a80eb61

repo NEW LOST RETARGETED
rust-analyzer 3,231 0 0
py-django 1,092 38 0
rust-ripgrep 326 0 0
cs-dapper 106 0 0
ts-zod 88 0 0
php-guzzle 37 0 0
python-flask 26 0 0
total 4,906 38 0

Every repo except py-django is byte-identical to the pre-fix branch — import_rel supplies exactly what the stem lookup was giving, and the __init__.py re-export caveat you flagged did not bite here.

The 38 "losses" are pre-existing PHANTOMS, read at source

tests/basic/tests.py:53  `a = Article()` … a.save()
   ->  tests/model_forms/models.py:68  Article.save     ← a DIFFERENT test app's override

Model.save is inherited from django.db.models.Model (indexed, django/db/models/base.py). Only tests/model_forms/models.py and tests/save_delete_hooks/models.py override it, so the (Article, save) pair looks unique and every app's .save() in the corpus bound to one of those two. All 38 are cross-app; 30 to model_forms#save, 7 to save_delete_hooks#save, 1 to #delete. This is #199's class exactly — an inherited member with no symbol — on METHOD calls, shipped in the resolver today, and it has nothing to do with #69 except that fixing my arm's bypass removes it.

The ledger

before after
binds this change admits 4,922 4,906
of those, correct 4,904 4,904
of those, wrong 18 2
pre-existing phantoms REMOVED 0 38

The correct count is unchanged at 4,904 — B refused exactly the 16 phantoms and nothing else. The tree after this change has fewer phantoms than master does, which is a stronger position than "0.37% is small" and is the argument the zero-phantom title deserves.

This change admits 4,906 binds: 4,904 correct and 2 wrong, and it removes 38 pre-existing phantoms from the shipped resolver.

The 2 survivors, with their cause named

tests/auth_tests/test_models.py:281,293 user.email -> tests/select_related_onetoone/models.py:6. Not a relative import — from django.contrib.auth.models import (…), an absolute dotted module whose interior segment run offers models, which matches every models.py basename stem. That is the #134/#168 stem family, not this one, and neither #57's exclusion nor B touches it. Recorded rather than chased.

Gates on B

fmt 0 · clippy 0 · indexer --lib 383 · resolver 95 · provenance_invariants 3 · pool_capability_registry 17 · receiver_phantom 25 · tier3_origin_gate 2 · precision_gate 7/7, phantoms=0. Full workspace + daemon-leg e2e in flight.

corpus_cost: rel_map costs nothing measurable (cs-dapper 23,459,161 → 23,466,285, +0.03%). Against the re-recorded baseline only ts-zod +6.8% and cs-dapper +8.6% on vm_step now exceed — rust-analyzer, py-django, php-guzzle, rust-ripgrep and python-flask are inside the band.

Two corrections back to you

  • cbeb655 is not on origin/master. Master is a80eb61, and git merge-base --is-ancestor cbeb655 origin/master says no. I rebased onto a80eb61 and verified the facts your message relies on directly in that tree (:4771's exclusion, import_rel at :4409, build_recv_origin reading file_keys) — they are all present, so the conclusion holds regardless of where the merge landed.
  • You were right that my re-measure note was wrong, and for a better reason than you gave: nothing landed in file_keys, and the fix turned out to live here, so the re-measure was mine to do and I have now done it.

Branch e1baef2 on a80eb61, wip-labelled, unpushed, tree clean.

## The bypass is real, the fix is five lines in `build_recv_origin` — **18 phantoms become 2, and 38 PRE-EXISTING phantoms go with them, at zero cost to correct binds** Your reframing was right and it was worth more than the hold. Rebased onto `a80eb61`; new baseline built and re-measured from scratch. ### The bypass, confirmed at source `build_recv_origin` (#65, **untouched by this branch** — `git diff a80eb61 -- index.rs` names it nowhere) does this for every import row: ```rust let key_runs = import_key_candidates(&module, max_key_run); // no relative check for cand in &key_runs { if let Some(fs) = key_map.get(...) { key_files.extend(fs) } } ``` Its own comment says the enumeration "is exactly … the same enumeration `temp.import_key_rel` is built from". It reproduces the enumeration and **not** the exclusion beside it — `index.rs:4771`, *"relative specifiers (leading dot) are excluded — they carry no whole-segment file key and are handled by `temp.import_rel`"*. So `from .models import Article` lends the key `models` to every `models.py` in the tree, through a route that has been open since #65 and that #57 had already closed on the arm next to it. ### Three variants built and measured over nine repos **A — skip relative specifiers, no replacement** (the literal mirror of `:4771`). **REFUSED.** Kills 16 of 18, but `build_recv_origin` never consulted `import_rel` at all, so the evidence is removed with nothing put back: **48 pre-existing binds lost and 369 of this change's**, and ts-zod collapses from 88 new binds to **3** — TypeScript imports are almost entirely relative. **B — consult `temp.import_rel` for a relative specifier, keep the stem lookup for absolute ones.** Five lines. **SHIPPED.** **C — the subtree test.** Not built: B dominates it on the measurement below, and B is a path-RESOLVED relation rather than a proximity heuristic. ### B, bind-for-bind against `a80eb61` | repo | NEW | LOST | RETARGETED | |---|---:|---:|---:| | rust-analyzer | 3,231 | 0 | 0 | | py-django | 1,092 | **38** | 0 | | rust-ripgrep | 326 | 0 | 0 | | cs-dapper | 106 | 0 | 0 | | ts-zod | 88 | 0 | 0 | | php-guzzle | 37 | 0 | 0 | | python-flask | 26 | 0 | 0 | | **total** | **4,906** | **38** | **0** | Every repo except py-django is **byte-identical** to the pre-fix branch — `import_rel` supplies exactly what the stem lookup was giving, and the `__init__.py` re-export caveat you flagged did not bite here. ### The 38 "losses" are pre-existing PHANTOMS, read at source ``` tests/basic/tests.py:53 `a = Article()` … a.save() -> tests/model_forms/models.py:68 Article.save ← a DIFFERENT test app's override ``` `Model.save` is inherited from `django.db.models.Model` (indexed, `django/db/models/base.py`). Only `tests/model_forms/models.py` and `tests/save_delete_hooks/models.py` override it, so the `(Article, save)` pair looks unique and every app's `.save()` in the corpus bound to one of those two. **All 38 are cross-app; 30 to `model_forms#save`, 7 to `save_delete_hooks#save`, 1 to `#delete`.** This is #199's class exactly — an inherited member with no symbol — on METHOD calls, shipped in the resolver today, and it has nothing to do with #69 except that fixing my arm's bypass removes it. ### The ledger | | before | after | |---|---:|---:| | binds this change admits | 4,922 | **4,906** | | of those, **correct** | 4,904 | **4,904** | | of those, **wrong** | 18 | **2** | | pre-existing phantoms REMOVED | 0 | **38** | The correct count is unchanged at 4,904 — B refused exactly the 16 phantoms and nothing else. **The tree after this change has fewer phantoms than master does**, which is a stronger position than "0.37% is small" and is the argument the zero-phantom title deserves. > **This change admits 4,906 binds: 4,904 correct and 2 wrong, and it removes 38 pre-existing phantoms from the shipped resolver.** ### The 2 survivors, with their cause named `tests/auth_tests/test_models.py:281,293` `user.email -> tests/select_related_onetoone/models.py:6`. Not a relative import — `from django.contrib.auth.models import (…)`, an **absolute** dotted module whose interior segment run offers `models`, which matches every `models.py` basename stem. That is the #134/#168 stem family, not this one, and neither #57's exclusion nor B touches it. Recorded rather than chased. ### Gates on B `fmt` 0 · `clippy` 0 · indexer `--lib` **383** · `resolver` **95** · `provenance_invariants` 3 · `pool_capability_registry` 17 · `receiver_phantom` 25 · `tier3_origin_gate` 2 · `precision_gate` **7/7, phantoms=0**. Full workspace + daemon-leg e2e in flight. `corpus_cost`: `rel_map` costs nothing measurable (cs-dapper 23,459,161 → 23,466,285, +0.03%). Against the re-recorded baseline only **ts-zod +6.8% and cs-dapper +8.6%** on `vm_step` now exceed — rust-analyzer, py-django, php-guzzle, rust-ripgrep and python-flask are inside the band. ### Two corrections back to you - **`cbeb655` is not on `origin/master`.** Master is `a80eb61`, and `git merge-base --is-ancestor cbeb655 origin/master` says no. I rebased onto `a80eb61` and verified the facts your message relies on directly in that tree (`:4771`'s exclusion, `import_rel` at `:4409`, `build_recv_origin` reading `file_keys`) — they are all present, so the conclusion holds regardless of where the merge landed. - **You were right that my re-measure note was wrong**, and for a better reason than you gave: nothing landed in `file_keys`, *and* the fix turned out to live here, so the re-measure was mine to do and I have now done it. Branch `e1baef2` on `a80eb61`, `wip`-labelled, unpushed, tree clean.
Author
Member

Full gate run on e1baef2 — green

The two legs that were in flight when the previous comment posted:

cargo test --workspace --no-fail-fast            TEST=0   313 binaries ok, 0 failed
COSI_E2E_LEG=daemon cargo test -p code-index-mcp  E2E=0    56 ok, 0 failed

Complete gate state for this branch:

gate result
cargo fmt --all -- --check 0
cargo clippy --workspace --all-targets -- -D warnings 0
cargo test --workspace --no-fail-fast 0
COSI_E2E_LEG=daemon cargo test -p code-index-mcp 0
cargo doc --workspace --no-deps --document-private-items 0
precision_gate 7/7, phantoms=0, recall 1.000
corpus_ratchet / corpus_stage / corpus_tier3_ratchet / corpus_cost red on the baseline-equality test only — bless-gated, not blessed

The four corpus records still need re-recording, and the reasons change with the new numbers. baseline.json, stage-baseline.json and tier3-baseline.json now have to account for py-django going DOWN by 38 as well as up — that is the pre-existing-phantom removal, and a reason that only says "+4,906" would hide it. Draft:

#69: tier 1R serves the MEMBER pool, and its origin relation stops bypassing #57's relative-specifier exclusion. Bind-for-bind against a80eb61: +4,906, −38, 0 retargeted. The 38 are pre-existing phantoms, not a recall loss — <app>.save() binding to another Django test app's save override, an inherited member with no symbol of its own (#199); all 38 cross-app, read at source. Of the 4,906 admitted, 4,904 correct and 2 wrong; the 2 are auth_tests user.email, an ABSOLUTE dotted module whose interior models segment matches a basename stem — the #134/#168 family, untouched here.

### Full gate run on `e1baef2` — green The two legs that were in flight when the previous comment posted: ``` cargo test --workspace --no-fail-fast TEST=0 313 binaries ok, 0 failed COSI_E2E_LEG=daemon cargo test -p code-index-mcp E2E=0 56 ok, 0 failed ``` Complete gate state for this branch: | gate | result | |---|---| | `cargo fmt --all -- --check` | 0 | | `cargo clippy --workspace --all-targets -- -D warnings` | 0 | | `cargo test --workspace --no-fail-fast` | **0** | | `COSI_E2E_LEG=daemon cargo test -p code-index-mcp` | **0** | | `cargo doc --workspace --no-deps --document-private-items` | 0 | | `precision_gate` | 7/7, `phantoms=0`, recall 1.000 | | `corpus_ratchet` / `corpus_stage` / `corpus_tier3_ratchet` / `corpus_cost` | red on the baseline-equality test only — bless-gated, **not blessed** | The four corpus records still need re-recording, and the reasons change with the new numbers. `baseline.json`, `stage-baseline.json` and `tier3-baseline.json` now have to account for **py-django going DOWN by 38** as well as up — that is the pre-existing-phantom removal, and a reason that only says "+4,906" would hide it. Draft: > #69: tier 1R serves the MEMBER pool, and its origin relation stops bypassing #57's relative-specifier exclusion. Bind-for-bind against `a80eb61`: **+4,906, −38, 0 retargeted**. The 38 are pre-existing phantoms, not a recall loss — `<app>.save()` binding to another Django test app's `save` override, an inherited member with no symbol of its own (#199); all 38 cross-app, read at source. Of the 4,906 admitted, 4,904 correct and 2 wrong; the 2 are `auth_tests` `user.email`, an ABSOLUTE dotted module whose interior `models` segment matches a basename stem — the #134/#168 family, untouched here.
Author
Member

Three of four done; the fourth is blocked on a box quiet enough to measure, and I will not fake it

Rebased onto 1d81180 (verified by rev-parse, not a log line — merge-base --is-ancestor confirms). Branch is now two commits:

518411d chore: re-record four corpus baselines — the -38 is phantom REMOVAL, not recall loss
5c90083 feat: tier 1R serves the MEMBER pool, and its origin stops bypassing #57 (#69, #125)

wip is gone. Re-measured on the new base before touching any record: +4,906 / −38 / 0 RETARGETED, identical to the a80eb61 reading — the only resolver-adjacent delta between those bases is walker.rs (the Windows symlink fix).

1 + 2. Four records re-recorded, with the −38 and the 2 survivors visible

baseline.json, stage-baseline.json and tier3-baseline.json all carry the same sentence, because a reason showing only a delta would hide the half that matters:

+4906 admitted, -38, 0 RETARGETED. Of the 4906: 4904 CORRECT and 2 WRONG. The -38 are NOT a recall loss: they are PRE-EXISTING PHANTOMS this removes — <app>.save() binding to another Django test app's save() override, an inherited member with no symbol of its own (#199); all 38 cross-app, read at source, 30 to model_forms, 8 to save_delete_hooks. The 2 wrong are auth_tests user.email: an ABSOLUTE dotted module (django.contrib.auth.models) whose interior segment models matches every basename stem — the #134/#168 stem family, a DIFFERENT mechanism from the relative-specifier bypass fixed here, so do not hunt them with this instrument.

cost-baseline.json carries its own attribution instead: the per-statement vm_step split, the ruby-sinatra 1.00× control, and the three cheaper shapes built and measured worse.

3. Gates on 518411d

fmt 0 · clippy 0 · cargo test --workspace — 313 ok, 1 failed (below) · daemon-leg e2e 0 · corpus_ratchet 0 · corpus_stage 0 · corpus_tier3_ratchet 0 · corpus_cost 0 · precision_gate 7/7 phantoms=0 · cargo doc 0.

4. Filed as #203

The build_recv_origin finding, framed as you put it: a comment claimed an enumeration was "exactly the same" as temp.import_key_rel's and reproduced it without the exclusion sitting beside it. Prose the code contradicted, invisible to every gate — the equivalence oracle proves the decision pass matches its own oracle, and both consume the same wrong relation.

THE ONE OUTSTANDING GATE — ruby_package_cost, and it is a guard working

the blessed band was not measured under the conditions now running:
  schema: the band was measured under 62 and this binary is schema 63
DO NOT re-bless this record on top of the numbers you just read. RE-MEASURE it:
an isolated run on a box below ~88% disk

m0063 takes the schema to 63 and the record refuses to compare against conditions it was not taken under. This is the third time this guard has fired on a real schema bump and it is right every time.

The SQLite dimensions — which the record itself calls "the measurement" — have barely moved, and there is no mechanism by which they could: ruby-sinatra gains 0 binds from this change (measured: 0 member rows enter recv_calls, because Ruby's access refs are @ivar with self receivers), and m0063 is a re-parse that does not run on a cold index.

dimension blessed (schema 62) measured (schema 63) Δ
package vm_step 25,077,631 25,104,788 +0.11%
package fullscan_step 542,326 546,092 +0.69%
sort 126 126 —
autoindex 20,745 20,745 —
builtin vm_step (control) 14,816,720 14,853,742 +0.25%

So the re-record is forced by schema identity, not drift — the same finding the schema-62 bless recorded.

wall_ratio_pct is the one I will not write yet. Blessed at 121. Readings tonight: 126 (load 17.8), 143 (load 13.4), 146 (load unknown, during a full workspace run). This record's own history says the spread tracks load and not code — eight passes across two schemas read 203…414 — and its instruction says isolated. A survey loop is running that takes a reading only when ps -C cargo is empty and the 1-minute load is below 5; the box has been at 13–23 with three or four cargo processes since it started.

I am not blessing a wall ratio measured at load 18. That would assert less than it appears to, which is the exact failure the guard's message names. If no quiet window arrives, the honest options are to record it with the conditions stated plainly — which this record has precedent for, twice, in its own _superseded_reasons — or to wait for a quiet box. That is a call about the record, so it is yours; I would take the wait, because the four SQLite dimensions are already inside band and the only thing gained by rushing is a number nobody can rely on.

Everything else is ready. Branch 518411d on 1d81180, tree clean, unpushed.

## Three of four done; the fourth is blocked on a box quiet enough to measure, and I will not fake it Rebased onto `1d81180` (verified by `rev-parse`, not a log line — `merge-base --is-ancestor` confirms). Branch is now two commits: ``` 518411d chore: re-record four corpus baselines — the -38 is phantom REMOVAL, not recall loss 5c90083 feat: tier 1R serves the MEMBER pool, and its origin stops bypassing #57 (#69, #125) ``` `wip` is gone. Re-measured on the new base before touching any record: **+4,906 / −38 / 0 RETARGETED**, identical to the `a80eb61` reading — the only resolver-adjacent delta between those bases is `walker.rs` (the Windows symlink fix). ### 1 + 2. Four records re-recorded, with the −38 and the 2 survivors visible `baseline.json`, `stage-baseline.json` and `tier3-baseline.json` all carry the same sentence, because a reason showing only a delta would hide the half that matters: > +4906 admitted, -38, 0 RETARGETED. Of the 4906: 4904 CORRECT and 2 WRONG. The -38 are NOT a recall loss: they are PRE-EXISTING PHANTOMS this removes — `<app>.save()` binding to another Django test app's `save()` override, an inherited member with no symbol of its own (#199); all 38 cross-app, read at source, 30 to `model_forms`, 8 to `save_delete_hooks`. The 2 wrong are `auth_tests` `user.email`: an ABSOLUTE dotted module (`django.contrib.auth.models`) whose interior segment `models` matches every basename stem — the #134/#168 stem family, a DIFFERENT mechanism from the relative-specifier bypass fixed here, so do not hunt them with this instrument. `cost-baseline.json` carries its own attribution instead: the per-statement `vm_step` split, the ruby-sinatra 1.00× control, and the three cheaper shapes built and measured worse. ### 3. Gates on `518411d` `fmt` 0 · `clippy` 0 · **`cargo test --workspace` — 313 ok, 1 failed** (below) · **daemon-leg e2e 0** · `corpus_ratchet` 0 · `corpus_stage` 0 · `corpus_tier3_ratchet` 0 · `corpus_cost` 0 · `precision_gate` **7/7 phantoms=0** · `cargo doc` 0. ### 4. Filed as #203 The `build_recv_origin` finding, framed as you put it: a comment claimed an enumeration was *"exactly the same"* as `temp.import_key_rel`'s and reproduced it **without the exclusion sitting beside it**. Prose the code contradicted, invisible to every gate — the equivalence oracle proves the decision pass matches its own oracle, and both consume the same wrong relation. ### THE ONE OUTSTANDING GATE — `ruby_package_cost`, and it is a guard working ``` the blessed band was not measured under the conditions now running: schema: the band was measured under 62 and this binary is schema 63 DO NOT re-bless this record on top of the numbers you just read. RE-MEASURE it: an isolated run on a box below ~88% disk ``` m0063 takes the schema to 63 and the record refuses to compare against conditions it was not taken under. **This is the third time this guard has fired on a real schema bump and it is right every time.** **The SQLite dimensions — which the record itself calls "the measurement" — have barely moved**, and there is no mechanism by which they could: ruby-sinatra gains **0** binds from this change (measured: 0 member rows enter `recv_calls`, because Ruby's access refs are `@ivar` with self receivers), and m0063 is a re-parse that does not run on a cold index. | dimension | blessed (schema 62) | measured (schema 63) | Δ | |---|---:|---:|---:| | package `vm_step` | 25,077,631 | 25,104,788 | +0.11% | | package `fullscan_step` | 542,326 | 546,092 | +0.69% | | `sort` | 126 | 126 | — | | `autoindex` | 20,745 | 20,745 | — | | builtin `vm_step` (control) | 14,816,720 | 14,853,742 | +0.25% | So the re-record is forced by **schema identity, not drift** — the same finding the schema-62 bless recorded. **`wall_ratio_pct` is the one I will not write yet.** Blessed at 121. Readings tonight: 126 (load 17.8), 143 (load 13.4), 146 (load unknown, during a full workspace run). This record's own history says the spread tracks load and not code — eight passes across two schemas read 203…414 — and its instruction says *isolated*. A survey loop is running that takes a reading only when `ps -C cargo` is empty and the 1-minute load is below 5; the box has been at 13–23 with three or four cargo processes since it started. **I am not blessing a wall ratio measured at load 18.** That would assert less than it appears to, which is the exact failure the guard's message names. If no quiet window arrives, the honest options are to record it with the conditions stated plainly — which this record has precedent for, twice, in its own `_superseded_reasons` — or to wait for a quiet box. That is a call about the record, so it is yours; I would take the wait, because the four SQLite dimensions are already inside band and the only thing gained by rushing is a number nobody can rely on. Everything else is ready. Branch `518411d` on `1d81180`, tree clean, unpushed.
Author
Member

ruby-package-cost.json: no quiet window in an hour, and it is not one of the four you asked me to re-record

Closing the loop on the one outstanding gate.

The survey found zero acceptable readings. A loop polled every 20 s for an hour, taking a reading only when ps -C cargo was empty and the 1-minute load was under 5. The box never qualified: load ran 13 → 18 → 21 → 23 → 27 with three to five cargo processes throughout, and it is higher now than when the loop started. Log is empty of READING lines — that is the measurement.

It is also outside the scope you gave me. You asked for "the four bless-gated corpus records", and the four I reported red were baseline.json, stage-baseline.json, tier3-baseline.json and cost-baseline.json. All four are re-recorded and green. ruby-package-cost.json is a fifth, and it is on this lane's protected list with the standing instruction to bring evidence and a draft reason rather than bless it myself. So I am doing that.

Everything the re-record needs except one number

The four SQLite dimensions — which the record's own comment calls "the measurement", deterministic to ~0.02% and "a property of the statements and the data, not of the machine" — are all inside band under schema 63:

dimension blessed (62) measured (63) Δ
package vm_step 25,077,631 25,104,788 +0.11%
package fullscan_step 542,326 546,092 +0.69%
sort 126 126 —
autoindex 20,745 20,745 —
builtin vm_step (control) 14,816,720 14,853,742 +0.25%

And there is no mechanism by which this change could move them: ruby-sinatra gains 0 binds, because 0 member rows enter recv_calls there (Ruby's access refs are @ivar with self receivers, which this arm excludes), and m0063 is a re-parse that never runs on a cold index. The re-record is forced by schema identity, not drift — the same finding as the schema-62 bless, which said so in as many words.

Draft reason, for whoever runs it on a quiet box

#69 at schema 63: RE-MEASURED, not re-blessed. m0063 (#125 destructuring re-parse + #69 resolution) takes CURRENT_VERSION to 63 and the condition gate refused the schema-62 band BY NAME, for the third time on a real bump. The SQLite dimensions did not drift and could not have: ruby-sinatra gains 0 binds from #69 (0 member rows enter recv_calls; Ruby access refs are @ivar with self receivers) and m0063 does not run on a cold index. package vm_step +0.11%, fullscan_step +0.69%, sort and autoindex identical, builtin control vm_step +0.25%. CONDITIONS: schema 63, ruby package 0.4.0, release profile, <load>, disk <pct>.

wall_ratio_pct is deliberately left blank in that draft. Blessed at 121; tonight's readings were 126 (load 17.8), 143 (load 13.4) and 146 (during a full workspace run). This record's own _superseded_reasons document that the spread tracks load and not code — eight passes across two schemas read 203 … 414, and one pass was rejected specifically because a sibling lane started a cargo build. Writing any of tonight's numbers would put a load artifact into a band whose only job is to see a slow wasm guest.

Two honest ways forward, and the choice is yours because it is a protected record:

  1. Wait for a quiet box and take the reading then. The four SQLite dimensions are already inside band, so nothing is being hidden by waiting; only the guest-speed bound is unmeasured, and it is unmeasured either way until the box is quiet.
  2. Record it contended and say so, which this file has precedent for twice — both times with the load stated and the honest reading spelled out ("it can see a 1.5× guest slowdown and not a 1.1× one").

I would take (1). The gate is currently doing exactly what it was built to do, and the only thing a hurried bless buys is a number that asserts less than it appears to.

Nothing else is outstanding. Branch 518411d on 1d81180, tree clean, unpushed; the other four records green.

## `ruby-package-cost.json`: no quiet window in an hour, and it is not one of the four you asked me to re-record Closing the loop on the one outstanding gate. **The survey found zero acceptable readings.** A loop polled every 20 s for an hour, taking a reading only when `ps -C cargo` was empty and the 1-minute load was under 5. The box never qualified: load ran 13 → 18 → 21 → 23 → 27 with three to five cargo processes throughout, and it is higher now than when the loop started. Log is empty of `READING` lines — that is the measurement. **It is also outside the scope you gave me.** You asked for "the four bless-gated corpus records", and the four I reported red were `baseline.json`, `stage-baseline.json`, `tier3-baseline.json` and `cost-baseline.json`. All four are re-recorded and green. `ruby-package-cost.json` is a fifth, and it is on this lane's protected list with the standing instruction to bring evidence and a draft reason rather than bless it myself. So I am doing that. ### Everything the re-record needs except one number The four SQLite dimensions — which the record's own comment calls "the measurement", deterministic to ~0.02% and "a property of the statements and the data, not of the machine" — are all inside band under schema 63: | dimension | blessed (62) | measured (63) | Δ | |---|---:|---:|---:| | package `vm_step` | 25,077,631 | 25,104,788 | +0.11% | | package `fullscan_step` | 542,326 | 546,092 | +0.69% | | `sort` | 126 | 126 | — | | `autoindex` | 20,745 | 20,745 | — | | builtin `vm_step` (control) | 14,816,720 | 14,853,742 | +0.25% | And there is no mechanism by which this change could move them: **ruby-sinatra gains 0 binds**, because 0 member rows enter `recv_calls` there (Ruby's access refs are `@ivar` with self receivers, which this arm excludes), and m0063 is a re-parse that never runs on a cold index. The re-record is forced by **schema identity, not drift** — the same finding as the schema-62 bless, which said so in as many words. ### Draft reason, for whoever runs it on a quiet box > #69 at schema 63: RE-MEASURED, not re-blessed. m0063 (`#125` destructuring re-parse + `#69` resolution) takes `CURRENT_VERSION` to 63 and the condition gate refused the schema-62 band BY NAME, for the third time on a real bump. The SQLite dimensions did not drift and could not have: ruby-sinatra gains 0 binds from #69 (0 member rows enter `recv_calls`; Ruby access refs are `@ivar` with self receivers) and m0063 does not run on a cold index. package `vm_step` +0.11%, `fullscan_step` +0.69%, `sort` and `autoindex` identical, builtin control `vm_step` +0.25%. CONDITIONS: schema 63, ruby package 0.4.0, release profile, `<load>`, disk `<pct>`. `wall_ratio_pct` is deliberately left blank in that draft. Blessed at 121; tonight's readings were 126 (load 17.8), 143 (load 13.4) and 146 (during a full workspace run). This record's own `_superseded_reasons` document that the spread tracks load and not code — eight passes across two schemas read 203 … 414, and one pass was rejected specifically because a sibling lane started a cargo build. Writing any of tonight's numbers would put a load artifact into a band whose only job is to see a slow wasm guest. **Two honest ways forward, and the choice is yours because it is a protected record:** 1. **Wait for a quiet box** and take the reading then. The four SQLite dimensions are already inside band, so nothing is being hidden by waiting; only the guest-speed bound is unmeasured, and it is unmeasured either way until the box is quiet. 2. **Record it contended and say so**, which this file has precedent for twice — both times with the load stated and the honest reading spelled out ("it can see a 1.5× guest slowdown and not a 1.1× one"). I would take (1). The gate is currently doing exactly what it was built to do, and the only thing a hurried bless buys is a number that asserts less than it appears to. Nothing else is outstanding. Branch `518411d` on `1d81180`, tree clean, unpushed; the other four records green.
Author
Member

The survey loop has now closed, and its verdict is explicit

I reported "zero acceptable readings" while the loop was still inside its window, which was accurate but not yet finished. It has now terminated on its own:

DONE taken=0

Zero qualifying passes in the full hour — every 20 s, requiring ps -C cargo empty and 1-minute load under 5. That is the measured basis for refusing the band, rather than an impression formed part-way through.

This lane contributes no load to the isolated run. Four cargo test --workspace / -p code-index-mcp processes are live on the box and none has a working directory under /tmp/cosi-lane-recall — they belong to sibling lanes. My worktree is idle and clean at 518411d.

Handoff is as described: the draft reason for ruby-package-cost.json is in the comment above with wall_ratio_pct left blank and the conditions line left as <load> / <pct>; the four SQLite dimensions are measured and inside band. Nothing else from me.

### The survey loop has now closed, and its verdict is explicit I reported "zero acceptable readings" while the loop was still inside its window, which was accurate but not yet finished. It has now terminated on its own: ``` DONE taken=0 ``` Zero qualifying passes in the full hour — every 20 s, requiring `ps -C cargo` empty and 1-minute load under 5. That is the measured basis for refusing the band, rather than an impression formed part-way through. **This lane contributes no load to the isolated run.** Four `cargo test --workspace` / `-p code-index-mcp` processes are live on the box and none has a working directory under `/tmp/cosi-lane-recall` — they belong to sibling lanes. My worktree is idle and clean at `518411d`. Handoff is as described: the draft reason for `ruby-package-cost.json` is in the comment above with `wall_ratio_pct` left blank and the conditions line left as `<load>` / `<pct>`; the four SQLite dimensions are measured and inside band. Nothing else from me.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
h-dv/code-index#69
No description provided.