The bridge tier is the one candidate fan-out with no budget: fill_bridge_cands full-scans (lang, kind) once per active bridge #164

Closed
opened 2026-09-05 21:55:08 +02:00 by buildagent · 3 comments
Member

Found while auditing #75's remaining cost surface. Verified with this repo's own tools (search_symbols → read_code → find_callers), not by grep.

The two halves

1. The scan is unbounded. index::fill_bridge_cands (crates/indexer/src/index.rs:2463) selects every symbol matching the bridge's destination language and kind, with no LIMIT and no budget consulted:

SELECT s.id, s.file_id, f.path, s.name
  FROM symbols s
  JOIN files f ON f.id = s.file_id
  {admit_bridge_dest}
 WHERE s.lang = ?1 AND s.kind = ?2

Its size is bounded only by how many symbols the repo happens to have of that (lang, kind) pair. Nothing caps it and nothing reports when it is large.

2. It runs once per active bridge. crates/indexer/src/index.rs:6972:

for b in &active {
    let Some(sc) = b.scope_code() else { continue };
    fill_bridge_refs(tx, padmit, scope_pred, ref_lang, b, sc)?;
    fill_bridge_cands(tx, padmit, b)?;   // <-- full (lang, kind) scan, per bridge
}

So total work is active_bridges × |symbols(dest_lang, dest_kind)|, and active grows with the number of installed packages that declare bridges — exactly the axis #75 is opening up.

Why this is a gap and not just a slow query

resolve_budget::Stage (crates/indexer/src/resolve_budget.rs:90) has five members — Tier3, Tier1rOrigin, Tier1rIdent, CsharpPartial, Tier1qScands — and none of them is the bridge. Every other candidate-relation build in the resolver is budgeted, measured and able to say it stopped early. This one cannot: it has no stage, so it has no ceiling, no disclosure, and no way to appear in the progress the daemon reports.

The enum's own doc records how this family of gaps is found:

Issue #65's "Required design" §2 named FOUR stages to budget — "tier 1R; C# partial; tier 1Q/type-FQN; tier 3". Three shipped. This is the fourth, and by the time it was measured it was the dominant cost of a cold index: 31% of cosi-mcp's wall clock, 61% of rust-analyzer's, 75% of py-django's, growing at N^2.16 in files.

Tier1qScands was invisible until it was measured, and then it was the largest single cost in the build. The bridge fan-out is in the same position now: unbudgeted, unmeasured, and on a growth axis we are deliberately expanding.

This is a gap, not yet a regression. I have not measured its current cost — today active is small, so it is probably cheap. That is precisely why it should be budgeted before runtime plugins make it big, rather than after.

Suggested shape

Add a Stage::Bridge member. The enum documents itself as a wire contract: "stable, never renamed, only added to", so adding a member is the additive change it was designed for, and it gets the ceiling, the early-stop disclosure and the progress reporting the other four already have.

Worth checking at the same time whether the per-bridge loop can share one scan across bridges with the same (dest_lang, dest_symbol_kind) — several bridges from different packages plausibly target the same pair, and today each pays for its own full scan. The decision step downstream already takes uniqueness over the union of every bridge, so per-bridge candidate rows are not load-bearing for correctness in the way the per-bridge pair_key/match_key values are.

Do not fix by

Widening or removing a budget elsewhere to compensate. And when it is measured: a correctness suite cannot see a slowdown, so compare an isolated run against an isolated run, and remember a run above ~88% disk on the build box is not a measurement in either direction.

🤖 Generated with Claude Code

https://claude.ai/code/session_01K1zj5VcFJvJt3pQxe9259K

Found while auditing #75's remaining cost surface. Verified with this repo's own tools (`search_symbols` → `read_code` → `find_callers`), not by grep. ## The two halves **1. The scan is unbounded.** `index::fill_bridge_cands` (`crates/indexer/src/index.rs:2463`) selects *every* symbol matching the bridge's destination language and kind, with no `LIMIT` and no budget consulted: ```sql SELECT s.id, s.file_id, f.path, s.name FROM symbols s JOIN files f ON f.id = s.file_id {admit_bridge_dest} WHERE s.lang = ?1 AND s.kind = ?2 ``` Its size is bounded only by how many symbols the repo happens to have of that `(lang, kind)` pair. Nothing caps it and nothing reports when it is large. **2. It runs once per active bridge.** `crates/indexer/src/index.rs:6972`: ```rust for b in &active { let Some(sc) = b.scope_code() else { continue }; fill_bridge_refs(tx, padmit, scope_pred, ref_lang, b, sc)?; fill_bridge_cands(tx, padmit, b)?; // <-- full (lang, kind) scan, per bridge } ``` So total work is `active_bridges × |symbols(dest_lang, dest_kind)|`, and `active` grows with the number of installed packages that declare bridges — exactly the axis #75 is opening up. ## Why this is a gap and not just a slow query `resolve_budget::Stage` (`crates/indexer/src/resolve_budget.rs:90`) has **five** members — `Tier3`, `Tier1rOrigin`, `Tier1rIdent`, `CsharpPartial`, `Tier1qScands` — and **none of them is the bridge**. Every other candidate-relation build in the resolver is budgeted, measured and able to say it stopped early. This one cannot: it has no stage, so it has no ceiling, no disclosure, and no way to appear in the progress the daemon reports. The enum's own doc records how this family of gaps is found: > Issue #65's "Required design" §2 named FOUR stages to budget — "tier 1R; C# partial; tier 1Q/type-FQN; tier 3". Three shipped. This is the fourth, and by the time it was measured it was the dominant cost of a cold index: 31% of cosi-mcp's wall clock, 61% of rust-analyzer's, 75% of py-django's, growing at N^2.16 in files. `Tier1qScands` was invisible until it was measured, and then it was the largest single cost in the build. The bridge fan-out is in the same position now: unbudgeted, unmeasured, and on a growth axis we are deliberately expanding. **This is a gap, not yet a regression.** I have not measured its current cost — today `active` is small, so it is probably cheap. That is precisely why it should be budgeted *before* runtime plugins make it big, rather than after. ## Suggested shape Add a `Stage::Bridge` member. The enum documents itself as a **wire contract: "stable, never renamed, only added to"**, so adding a member is the additive change it was designed for, and it gets the ceiling, the early-stop disclosure and the progress reporting the other four already have. Worth checking at the same time whether the per-bridge loop can share one scan across bridges with the same `(dest_lang, dest_symbol_kind)` — several bridges from different packages plausibly target the same pair, and today each pays for its own full scan. The decision step downstream already takes uniqueness over the union of every bridge, so per-bridge candidate rows are not load-bearing for correctness in the way the per-bridge `pair_key`/`match_key` values are. ## Do not fix by Widening or removing a budget elsewhere to compensate. And when it is measured: a correctness suite cannot see a slowdown, so compare an isolated run against an isolated run, and remember a run above ~88% disk on the build box is not a measurement in either direction. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01K1zj5VcFJvJt3pQxe9259K
Author
Member

Measured first, then budgeted. And the measurement found a second thing.

Worktree /tmp/cosi-lane-precision off origin/master (f6a878a). All numbers below are from fresh cold indexes of the nine pinned corpus repos in a private copy of the corpus, load and disk stated where it matters.

1. The current cost is not "small". It is exactly ZERO.

language_bridges holds 0 rows on all nine pinned repos, so bridges::load_active returns empty and the block is one indexed read — tier_bridge=0 in every resolve log. stage-baseline.json already said the same thing from the other side: rule 90 (bridge) is the one code in resolved_by::ALL that fires nowhere in it.

So the budget cannot be derived from what the tier costs today. It is derived from what one bridge would scan, which is measurable without a bridge — the largest legal (dest_lang, dest_symbol_kind) population per repo:

repo symbols largest legal destination population
js-express 1 917 javascript/function = 123
ruby-sinatra 1 260 ruby/method = 833
python-flask 1 836 python/function = 1 059
ts-zod 9 672 typescript/field = 1 183
cs-dapper 3 227 csharp/method = 1 675
rust-ripgrep 4 874 rust/method = 2 199
php-guzzle 3 228 php/method = 2 759
rust-analyzer 40 053 rust/function = 12 487
py-django 59 783 python/method = 29 513

One bridge on py-django materialises up to 29 513 candidate rows; n bridges targeting the same pair pay n × that, which is the "once per active bridge" half of the report.

The tree already carried a written decision here, which the issue did not engage with — index.rs, since e6dd5c9 (2026-08-28, the bridge feature itself): "NOT BUDGET-GUARDED, and that is a decision." Its first argument is right and I measured it (estimate 0 everywhere). Its second — "one language's symbols of one kind … far below the cross-products the guarded stages bound" — is the one that misses: the quantity is active_bridges × |symbols(lang, kind)|, and active is the axis #75 widens. The comment is rewritten rather than deleted, so the reasoning that ended in the wrong place stays readable.

2. Budgeted. Stage::Bridge, additively.

  • Stage::Bridge, key bridge, env CODE_INDEX_BRIDGE_WORK_BUDGET, BRIDGE_WORK_BUDGET_DEFAULT = 50_000_000.
  • Unit: SUM over active bridges of |symbols(dest_lang, dest_symbol_kind)| — exactly the unbounded relation. 1 694× the worst single-bridge population any pinned repo can produce.
  • Doc states what it does not bound: the decision join's dot product. Both of that join's inputs are now bounded, and temp.bridge_cands is WITHOUT ROWID with PRIMARY KEY (bridge_id, match_key, sym_id), so the join is a PK-prefix seek per reference row and costs O(output). Said plainly rather than implied.

New API, and the reason is crate::bridges' own invariant. dest_lang and dest_symbol_kind are PACKAGE-SUPPLIED, and that module holds that no package value ever reaches a SQL string. An estimate_sql would have had to interpolate one. So StageBudgets::guard_measured_at takes an estimator closure (bound parameters), and guard_at is now a thin adapter over it — one copy of the >, of the interrupt-vs-degrade error policy, and of "degrade means never start", instead of a second copy per estimate shape.

The estimate's own cost, measured (warm, same DBs; idx_symbols_kind_lang(kind, lang) makes it SEARCH … USING COVERING INDEX while the body's SELECT also seeks files by rowid per row):

population rows estimate body SELECT alone ratio
rust-ripgrep rust/function 791 37 µs 1.3 ms 35×
rust-analyzer rust/function 12 487 889 µs 223.6 ms 252×
py-django python/method 29 513 1 318 µs 338.0 ms 257×

0.4 % of what it guards at real scale — before the body's per-row Rust allocation, match_key call and WITHOUT ROWID insert. A units/s rate is deliberately not claimed: no repository we can measure has an active bridge, so the stage has never run long enough to time. That is Tier1qScands' pre-measurement position, which is why its estimate is persisted on degraded passes — and now so is this one.

3. The gate. xaml_package_e2e::the_bridge_tier_is_budgeted_and_names_itself_when_it_skips

The daemon is spawned with CODE_INDEX_BRIDGE_WORK_BUDGET=0 via spawn_with_env — the production channel, in the daemon's own process, so it cannot leak into this binary's other tests (an in-process set_var would unbind the two positive controls). Asserts: no ref carries by 90; stage_budget.bridge.{degraded,budget}; bridge in stage_budget.measured (which is what read_stages_skipped walks, env by formula → the wire entry); and the estimate equals |symbols(csharp,class)| + |symbols(csharp,method)|.

Mutations, RUN:

  • call the tier body directly again (the pre-#164 loop) → RED: ["type Demo.MainWindow -> MainWindow[csharp] … by 90", …, "call OnSaveClick -> OnSaveClick[csharp] … by 90"].
  • estimate closure returns Ok(0) → predicted red on the estimate assertion, GREEN on the edges. RESULT: RED ON THE EDGES, same two rows — a zero estimate never exceeds any budget, so nothing degrades. The prediction was wrong in the way that matters: it proved est > 0 is implied by the edge assertion and can never fail on its own. That assertion is now an equality.
  • for b in active.iter().take(1) in the estimate → RED, left: 2, right: 5. This is the mutation the equality exists for; "once per active bridge" is the growth half of this issue and nothing else grades it.
  • SURVIVOR, recorded rather than hidden: dropping lang = ?1 from the estimate is GREEN, and it is a fact about the fixture — its histogram is csharp/method=3, csharp/class=2, de.h-dv.xaml/xaml/field=2, csharp/module=2, csharp/field=1, so C# is the only language holding a class or a method there. The language filter is UNGRADED by this test; dropping it over-estimates, which resolve_budget's module doc calls the safe direction, and grading it would need a bridge declared onto field.

4. A PRE-EXISTING gate defect, found by trying to baseline the new stage

corpus_stage was GREEN after adding Stage::Bridge. It should not have been — stage.bridge.degraded is a new key on all seven repos.

corpus::projection::categorised_diff compared want.get(key).unwrap_or(0) against got.get(key).unwrap_or(0) and continued on equality before looking at presence. Every healthy stage.*.degraded is 0, so NEW KEY and KEY GONE were unreachable for exactly the values that carry them.

Measured on master's own code, not predicted: delete Stage::CsharpPartial from Stage::ALL — it leaves stage_budget.measured and all seven baselined stage.csharp_partial.degraded keys vanish from the observation — and corpus_stage reported test result: ok. That directly falsifies the projection's own written claim that "a stage that stopped speaking leaves the roster and shows here as a vanished key."

Blast radius: 56 zero-valued keys across three protected records were ungraded for presence — 35 in stage-baseline.json, 12 in tier3-baseline.json, 9 in baseline.json.

Fixed with one clause (if a == b && note.is_empty()), covering every category rather than the stage one. a_key_on_one_side_only_is_drift gained a zero-valued arm; MUTATION (RUN): restore if a == b { continue } → RED with []. Both suites' drift messages now distinguish a VALUE change (a stage degraded) from a NEW KEY / KEY GONE at the same value (the roster changed) — the old text said "a guarded stage degraded" about a roster addition.

5. Protected records move. I did NOT bless them.

With the gate fixed, the only drift is the new stage's own, and it is additive and value-free:

corpus_stage (tests/corpus/stage-baseline.json), 7 lines:
  [stage] cs-dapper:    stage.bridge.degraded 0 -> 0 (+0) NEW KEY
  [stage] js-express:   stage.bridge.degraded 0 -> 0 (+0) NEW KEY
  [stage] php-guzzle:   stage.bridge.degraded 0 -> 0 (+0) NEW KEY
  [stage] python-flask: stage.bridge.degraded 0 -> 0 (+0) NEW KEY
  [stage] ruby-sinatra: stage.bridge.degraded 0 -> 0 (+0) NEW KEY
  [stage] rust-ripgrep: stage.bridge.degraded 0 -> 0 (+0) NEW KEY
  [stage] ts-zod:       stage.bridge.degraded 0 -> 0 (+0) NEW KEY

corpus_tier3_ratchet (tests/corpus/tier3-baseline.json), 2 lines:
  [stage] py-django:     stage.bridge.degraded 0 -> 0 (+0) NEW KEY
  [stage] rust-analyzer: stage.bridge.degraded 0 -> 0 (+0) NEW KEY

Nothing else moved on any repo, which is the strongest available statement that the change is behaviour-neutral: corpus_ratchet (baseline.json, 914dda1a) and corpus_cost are both GREEN with executed=7, and every rule./influence./producer./count. figure is byte-identical. That is what an estimate of 0 on every repo predicts, confirmed rather than assumed.

The two blesses are the integrator's call, not this lane's. The shape of the reason each needs: "#164: Stage::Bridge joins resolve_budget::Stage::ALL, so stage_budget.measured gains bridge and every repo gains one stage.bridge.degraded: 0. Additive and value-free — the estimate is 0 on all nine repos because none has a language_bridges row, so no bind, rule, influence or producer figure moves and baseline.json is unchanged. The key was invisible before this commit: categorised_diff skipped presence changes whose value was 0, proven by removing Stage::CsharpPartial from Stage::ALL and watching this suite stay green."

6. Not done

The issue's second suggestion — share one scan across bridges with the same (dest_lang, dest_symbol_kind) — is not implemented. It is a real saving (n bridges onto one pair pay n × today) but it is an optimisation with no measurable input: every repo we have measures 0. Budgeting it first is what makes the input collectable, which is the whole argument of this issue. Left open.

Gates: fmt 0 · clippy -D warnings 0 · rustdoc -D warnings --document-private-items 0 · clippy --target x86_64-pc-windows-gnu -D warnings 0 · precision_gate 7/7, phantom_count == 0 on every language · corpus_ratchet ok (executed=7) · corpus_cost ok (executed=7) · xaml_package_e2e 14/14 · corpus_stage / corpus_tier3_ratchet RED with exactly the nine lines above.

## Measured first, then budgeted. And the measurement found a second thing. Worktree `/tmp/cosi-lane-precision` off `origin/master` (`f6a878a`). All numbers below are from fresh cold indexes of the nine pinned corpus repos in a private copy of the corpus, load and disk stated where it matters. ### 1. The current cost is not "small". It is exactly ZERO. `language_bridges` holds **0 rows on all nine** pinned repos, so `bridges::load_active` returns empty and the block is one indexed read — `tier_bridge=0` in every resolve log. `stage-baseline.json` already said the same thing from the other side: rule 90 (`bridge`) is the one code in `resolved_by::ALL` that fires nowhere in it. So the budget cannot be derived from what the tier costs today. It is derived from **what one bridge would scan**, which is measurable without a bridge — the largest legal `(dest_lang, dest_symbol_kind)` population per repo: | repo | symbols | largest legal destination population | |---|---:|---| | js-express | 1 917 | `javascript`/`function` = 123 | | ruby-sinatra | 1 260 | `ruby`/`method` = 833 | | python-flask | 1 836 | `python`/`function` = 1 059 | | ts-zod | 9 672 | `typescript`/`field` = 1 183 | | cs-dapper | 3 227 | `csharp`/`method` = 1 675 | | rust-ripgrep | 4 874 | `rust`/`method` = 2 199 | | php-guzzle | 3 228 | `php`/`method` = 2 759 | | rust-analyzer | 40 053 | `rust`/`function` = 12 487 | | py-django | 59 783 | `python`/`method` = **29 513** | One bridge on py-django materialises up to 29 513 candidate rows; `n` bridges targeting the same pair pay `n ×` that, which is the "once per active bridge" half of the report. **The tree already carried a written decision here**, which the issue did not engage with — `index.rs`, since `e6dd5c9` (2026-08-28, the bridge feature itself): *"NOT BUDGET-GUARDED, and that is a decision."* Its first argument is right and I measured it (estimate 0 everywhere). Its second — "one language's symbols of one kind … far below the cross-products the guarded stages bound" — is the one that misses: the quantity is `active_bridges × |symbols(lang, kind)|`, and `active` is the axis #75 widens. The comment is rewritten rather than deleted, so the reasoning that ended in the wrong place stays readable. ### 2. Budgeted. `Stage::Bridge`, additively. - `Stage::Bridge`, key `bridge`, env `CODE_INDEX_BRIDGE_WORK_BUDGET`, `BRIDGE_WORK_BUDGET_DEFAULT = 50_000_000`. - Unit: `SUM over active bridges of |symbols(dest_lang, dest_symbol_kind)|` — exactly the unbounded relation. **1 694×** the worst single-bridge population any pinned repo can produce. - Doc states what it does **not** bound: the decision join's dot product. Both of that join's inputs are now bounded, and `temp.bridge_cands` is `WITHOUT ROWID` with `PRIMARY KEY (bridge_id, match_key, sym_id)`, so the join is a PK-prefix seek per reference row and costs O(output). Said plainly rather than implied. **New API, and the reason is `crate::bridges`' own invariant.** `dest_lang` and `dest_symbol_kind` are PACKAGE-SUPPLIED, and that module holds that no package value ever reaches a SQL string. An `estimate_sql` would have had to interpolate one. So `StageBudgets::guard_measured_at` takes an estimator **closure** (bound parameters), and `guard_at` is now a thin adapter over it — **one** copy of the `>`, of the interrupt-vs-degrade error policy, and of "degrade means never start", instead of a second copy per estimate shape. **The estimate's own cost, measured** (warm, same DBs; `idx_symbols_kind_lang(kind, lang)` makes it `SEARCH … USING COVERING INDEX` while the body's SELECT also seeks `files` by rowid per row): | population | rows | estimate | body SELECT alone | ratio | |---|---:|---:|---:|---:| | rust-ripgrep `rust`/`function` | 791 | 37 µs | 1.3 ms | 35× | | rust-analyzer `rust`/`function` | 12 487 | 889 µs | 223.6 ms | 252× | | py-django `python`/`method` | 29 513 | 1 318 µs | 338.0 ms | 257× | 0.4 % of what it guards at real scale — before the body's per-row Rust allocation, `match_key` call and `WITHOUT ROWID` insert. A units/s rate is deliberately **not** claimed: no repository we can measure has an active bridge, so the stage has never run long enough to time. That is `Tier1qScands`' pre-measurement position, which is why its estimate is persisted on degraded passes — and now so is this one. ### 3. The gate. `xaml_package_e2e::the_bridge_tier_is_budgeted_and_names_itself_when_it_skips` The daemon is spawned with `CODE_INDEX_BRIDGE_WORK_BUDGET=0` via `spawn_with_env` — the production channel, in the daemon's **own process**, so it cannot leak into this binary's other tests (an in-process `set_var` would unbind the two positive controls). Asserts: no ref carries `by 90`; `stage_budget.bridge.{degraded,budget}`; `bridge` in `stage_budget.measured` (which is what `read_stages_skipped` walks, `env` by formula → the wire entry); and the estimate **equals** `|symbols(csharp,class)| + |symbols(csharp,method)|`. Mutations, RUN: * **call the tier body directly again** (the pre-#164 loop) → **RED**: `["type Demo.MainWindow -> MainWindow[csharp] … by 90", …, "call OnSaveClick -> OnSaveClick[csharp] … by 90"]`. * **estimate closure returns `Ok(0)`** → predicted red on the estimate assertion, GREEN on the edges. **RESULT: RED ON THE EDGES**, same two rows — a zero estimate never exceeds any budget, so nothing degrades. The prediction was wrong in the way that matters: it proved `est > 0` is *implied* by the edge assertion and can never fail on its own. That assertion is now an **equality**. * **`for b in active.iter().take(1)`** in the estimate → **RED, `left: 2, right: 5`**. This is the mutation the equality exists for; "once per active bridge" is the growth half of this issue and nothing else grades it. * **SURVIVOR, recorded rather than hidden**: dropping `lang = ?1` from the estimate is **GREEN**, and it is a fact about the fixture — its histogram is `csharp/method=3, csharp/class=2, de.h-dv.xaml/xaml/field=2, csharp/module=2, csharp/field=1`, so C# is the only language holding a `class` or a `method` there. The language filter is UNGRADED by this test; dropping it over-estimates, which `resolve_budget`'s module doc calls the safe direction, and grading it would need a bridge declared onto `field`. ### 4. A PRE-EXISTING gate defect, found by trying to baseline the new stage `corpus_stage` was **GREEN** after adding `Stage::Bridge`. It should not have been — `stage.bridge.degraded` is a new key on all seven repos. `corpus::projection::categorised_diff` compared `want.get(key).unwrap_or(0)` against `got.get(key).unwrap_or(0)` and `continue`d on equality **before** looking at presence. Every healthy `stage.*.degraded` is `0`, so `NEW KEY` and `KEY GONE` were unreachable for exactly the values that carry them. Measured on master's own code, not predicted: **delete `Stage::CsharpPartial` from `Stage::ALL`** — it leaves `stage_budget.measured` and all seven baselined `stage.csharp_partial.degraded` keys vanish from the observation — and `corpus_stage` reported `test result: ok`. That directly falsifies the projection's own written claim that *"a stage that stopped speaking leaves the roster and shows here as a vanished key."* Blast radius: **56 zero-valued keys** across three protected records were ungraded for presence — 35 in `stage-baseline.json`, 12 in `tier3-baseline.json`, 9 in `baseline.json`. Fixed with one clause (`if a == b && note.is_empty()`), covering every category rather than the stage one. `a_key_on_one_side_only_is_drift` gained a zero-valued arm; MUTATION (RUN): restore `if a == b { continue }` → **RED with `[]`**. Both suites' drift messages now distinguish a VALUE change (a stage degraded) from a NEW KEY / KEY GONE at the same value (the roster changed) — the old text said "a guarded stage degraded" about a roster addition. ### 5. Protected records move. I did NOT bless them. With the gate fixed, the only drift is the new stage's own, and it is additive and value-free: ``` corpus_stage (tests/corpus/stage-baseline.json), 7 lines: [stage] cs-dapper: stage.bridge.degraded 0 -> 0 (+0) NEW KEY [stage] js-express: stage.bridge.degraded 0 -> 0 (+0) NEW KEY [stage] php-guzzle: stage.bridge.degraded 0 -> 0 (+0) NEW KEY [stage] python-flask: stage.bridge.degraded 0 -> 0 (+0) NEW KEY [stage] ruby-sinatra: stage.bridge.degraded 0 -> 0 (+0) NEW KEY [stage] rust-ripgrep: stage.bridge.degraded 0 -> 0 (+0) NEW KEY [stage] ts-zod: stage.bridge.degraded 0 -> 0 (+0) NEW KEY corpus_tier3_ratchet (tests/corpus/tier3-baseline.json), 2 lines: [stage] py-django: stage.bridge.degraded 0 -> 0 (+0) NEW KEY [stage] rust-analyzer: stage.bridge.degraded 0 -> 0 (+0) NEW KEY ``` **Nothing else moved on any repo**, which is the strongest available statement that the change is behaviour-neutral: `corpus_ratchet` (`baseline.json`, `914dda1a`) and `corpus_cost` are both GREEN with `executed=7`, and every `rule.`/`influence.`/`producer.`/`count.` figure is byte-identical. That is what an estimate of 0 on every repo predicts, confirmed rather than assumed. The two blesses are the integrator's call, not this lane's. The shape of the reason each needs: *"#164: `Stage::Bridge` joins `resolve_budget::Stage::ALL`, so `stage_budget.measured` gains `bridge` and every repo gains one `stage.bridge.degraded: 0`. Additive and value-free — the estimate is 0 on all nine repos because none has a `language_bridges` row, so no bind, rule, influence or producer figure moves and `baseline.json` is unchanged. The key was invisible before this commit: `categorised_diff` skipped presence changes whose value was 0, proven by removing `Stage::CsharpPartial` from `Stage::ALL` and watching this suite stay green."* ### 6. Not done The issue's second suggestion — **share one scan across bridges with the same `(dest_lang, dest_symbol_kind)`** — is not implemented. It is a real saving (`n` bridges onto one pair pay `n ×` today) but it is an optimisation with no measurable input: every repo we have measures 0. Budgeting it first is what makes the input collectable, which is the whole argument of this issue. Left open. Gates: `fmt` 0 · `clippy -D warnings` 0 · `rustdoc -D warnings --document-private-items` 0 · `clippy --target x86_64-pc-windows-gnu -D warnings` 0 · `precision_gate` **7/7, `phantom_count == 0`** on every language · `corpus_ratchet` ok (executed=7) · `corpus_cost` ok (executed=7) · `xaml_package_e2e` 14/14 · `corpus_stage` / `corpus_tier3_ratchet` RED with exactly the nine lines above.
Author
Member

Gate exit codes, completed — and one correction to how I read two of them

COSI_CORPUS_DIR=… COSI_CORPUS_REQUIRE=1 cargo test --workspace --no-fail-fast → exit 101, 301 suites, 3232 passed, 2 failed. The two failures are exactly the two expected baseline ratchets, and the drift across the whole run is exactly nine lines, all the same key:

[stage] cs-dapper|js-express|php-guzzle|python-flask|ruby-sinatra|rust-ripgrep|ts-zod:
        stage.bridge.degraded 0 -> 0 (+0) NEW KEY      (corpus_stage)
[stage] py-django|rust-analyzer:
        stage.bridge.degraded 0 -> 0 (+0) NEW KEY      (corpus_tier3_ratchet)

Nothing else — no [rule], [count], [influence], [producer], [parse] or [activation] line anywhere in the run.

COSI_E2E_LEG=daemon cargo test -p code-index-mcp --no-fail-fast → exit 0, 50 suites, 641 passed, 0 failed.

The correction, because it is the trap this lane is supposed to catch. My first two runs of those gates were written as cargo test … 2>&1 | tail -N, and the harness reported "exit code 0" for both — that is tail's exit code, not cargo's. The daemon leg really was green; the workspace one was not, and reading the pipe's status would have let me report 3233/0 for a run that ends 101. Both were re-run writing to a file with $? captured directly, plus an independent grep -c '^test result: FAILED' and a summed per-suite count, so three signals agree. Same class as awk -F'[ ;]' putting the failure count in the wrong field.

Full gate set: fmt --check 0 · clippy --workspace --all-targets -D warnings 0 · RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --document-private-items 0 · clippy --workspace --all-targets --target x86_64-pc-windows-gnu -D warnings 0 · precision_gate 7/7 with phantoms=0 on every language · corpus_ratchet ok (executed=7) · corpus_cost ok (executed=7) · ruby_package_parity ok (executed=1, controls=4) · xaml_package_e2e 14/14 · daemon-leg e2e 0 · workspace 101 with the nine lines above and nothing else.

All five protected records are byte-identical to master and none was blessed: baseline.json 914dda1a, stage-baseline.json 7d9695be, tier3-baseline.json 94abf592, ruby-package-cost.json f5a1a8f2, tests/bench/oracle/rust-ripgrep.json 78895712.

### Gate exit codes, completed — and one correction to how I read two of them `COSI_CORPUS_DIR=… COSI_CORPUS_REQUIRE=1 cargo test --workspace --no-fail-fast` → **exit 101**, `301 suites, 3232 passed, 2 failed`. The two failures are exactly the two expected baseline ratchets, and the drift across the whole run is exactly nine lines, all the same key: ``` [stage] cs-dapper|js-express|php-guzzle|python-flask|ruby-sinatra|rust-ripgrep|ts-zod: stage.bridge.degraded 0 -> 0 (+0) NEW KEY (corpus_stage) [stage] py-django|rust-analyzer: stage.bridge.degraded 0 -> 0 (+0) NEW KEY (corpus_tier3_ratchet) ``` Nothing else — no `[rule]`, `[count]`, `[influence]`, `[producer]`, `[parse]` or `[activation]` line anywhere in the run. `COSI_E2E_LEG=daemon cargo test -p code-index-mcp --no-fail-fast` → **exit 0**, 50 suites, 641 passed, 0 failed. **The correction, because it is the trap this lane is supposed to catch.** My first two runs of those gates were written as `cargo test … 2>&1 | tail -N`, and the harness reported "exit code 0" for both — that is `tail`'s exit code, not cargo's. The daemon leg really was green; the workspace one was **not**, and reading the pipe's status would have let me report 3233/0 for a run that ends 101. Both were re-run writing to a file with `$?` captured directly, plus an independent `grep -c '^test result: FAILED'` and a summed per-suite count, so three signals agree. Same class as `awk -F'[ ;]'` putting the failure count in the wrong field. Full gate set: `fmt --check` 0 · `clippy --workspace --all-targets -D warnings` 0 · `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --document-private-items` 0 · `clippy --workspace --all-targets --target x86_64-pc-windows-gnu -D warnings` 0 · `precision_gate` 7/7 with `phantoms=0` on every language · `corpus_ratchet` ok (executed=7) · `corpus_cost` ok (executed=7) · `ruby_package_parity` ok (executed=1, controls=4) · `xaml_package_e2e` 14/14 · daemon-leg e2e 0 · workspace 101 with the nine lines above and nothing else. All five protected records are byte-identical to master and **none was blessed**: `baseline.json` `914dda1a`, `stage-baseline.json` `7d9695be`, `tier3-baseline.json` `94abf592`, `ruby-package-cost.json` `f5a1a8f2`, `tests/bench/oracle/rust-ripgrep.json` `78895712`.
Author
Member

CLOSING — verified on master 552e3a2, with the residual named

Close-out lane. Worktree /tmp/cosi-lane-closeout at 552e3a2. Every location below was read on master with this repo's own tools (search_text → read_code), not grep.

What is on master

the gap as filed what answers it now
Stage has five members and none is the bridge Stage::Bridge — crates/indexer/src/resolve_budget.rs:129, and in Stage::ALL at :154 alongside the other five
no ceiling BRIDGE_WORK_BUDGET_DEFAULT = 50_000_000 — crates/indexer/src/index.rs:2582, env CODE_INDEX_BRIDGE_WORK_BUDGET
no disclosure key() → "bridge" (resolve_budget.rs:172), so the stage joins stage_budget.measured and stage.bridge.degraded exists
the scan is unguarded StageBudgets::guard_measured_at (resolve_budget.rs:289) is called at index.rs:7371, i.e. on the fill_bridge_cands path itself

The unit is the relation this issue named — SUM over active bridges of |symbols(dest_lang, dest_symbol_kind)| — so the "once per active bridge" half is inside the estimate, not outside it.

Gates re-run here, exit codes captured directly (no pipe)

cargo test -p code-index-daemon --test xaml_package_e2e   EXIT=0   14 passed, 0 failed  (62.07s)
  includes the_bridge_tier_is_budgeted_and_names_itself_when_it_skips
        (crates/daemon/tests/xaml_package_e2e.rs:697)

COSI_CORPUS_DIR=$HOME/.cache/cosi-corpus COSI_CORPUS_REQUIRE=1 \
cargo test --release -p code-index-indexer --test corpus_stage   EXIT=0
  corpus[stage]: executed=7 unavailable=0 not_applicable=0 controls=14 (require=true)
  test result: ok. 11 passed; 0 failed

executed=7, not executed=0 unavailable=1 — the suite really indexed the pinned repos. tests/corpus/stage-baseline.json and tests/corpus/tier3-baseline.json carry stage.bridge.degraded on all nine repos, recorded in a9ba058 as an additive roster key; no value was blessed away.

RESIDUAL, recorded rather than carried by this issue

The second suggestion — share one scan across bridges with the same (dest_lang, dest_symbol_kind) — is not implemented, and the lane said so. It stays unimplemented deliberately: the estimate is 0 units on all nine pinned repos because none holds a language_bridges row, so there is no input to optimise against and no way to measure a saving. Budgeting first is what makes that input collectable. When a project with several bridges onto one pair exists, that is a scale-ceiling fixture and belongs to #41, which already lists the bridge candidate fan-out as having no fixture.

Also closed by this work

#110 is the same defect filed from the other side ("the one resolver fan-out stage with no budget, and its exemption argument is untested"). Its option 2 — give it a Stage — is what shipped, and its other requirement, that the reasoning live next to the code, is met at index.rs:2530-2582. Closing that one too, pointing here.

One thing this lane did NOT verify

precision_gate 7/7 with phantoms=0 is cited in the lane's account. That gate is ~47 probes over 54 fixture files and never indexes a corpus repo (#188), so it is not evidence about corpus-scale resolution and is not being read as such here. The corpus evidence above is corpus_stage at executed=7.

Closing: the gap reported — an unbudgeted, unmeasured, undisclosable candidate fan-out on the axis #75 is widening — is closed and graded.

## CLOSING — verified on master `552e3a2`, with the residual named Close-out lane. Worktree `/tmp/cosi-lane-closeout` at `552e3a2`. Every location below was read on master with this repo's own tools (`search_text` → `read_code`), not grep. ### What is on master | the gap as filed | what answers it now | |---|---| | `Stage` has five members and none is the bridge | `Stage::Bridge` — `crates/indexer/src/resolve_budget.rs:129`, and in `Stage::ALL` at `:154` alongside the other five | | no ceiling | `BRIDGE_WORK_BUDGET_DEFAULT = 50_000_000` — `crates/indexer/src/index.rs:2582`, env `CODE_INDEX_BRIDGE_WORK_BUDGET` | | no disclosure | `key()` → `"bridge"` (`resolve_budget.rs:172`), so the stage joins `stage_budget.measured` and `stage.bridge.degraded` exists | | the scan is unguarded | `StageBudgets::guard_measured_at` (`resolve_budget.rs:289`) is called at `index.rs:7371`, i.e. on the `fill_bridge_cands` path itself | The unit is the relation this issue named — `SUM over active bridges of |symbols(dest_lang, dest_symbol_kind)|` — so the "once per active bridge" half is inside the estimate, not outside it. ### Gates re-run here, exit codes captured directly (no pipe) ``` cargo test -p code-index-daemon --test xaml_package_e2e EXIT=0 14 passed, 0 failed (62.07s) includes the_bridge_tier_is_budgeted_and_names_itself_when_it_skips (crates/daemon/tests/xaml_package_e2e.rs:697) COSI_CORPUS_DIR=$HOME/.cache/cosi-corpus COSI_CORPUS_REQUIRE=1 \ cargo test --release -p code-index-indexer --test corpus_stage EXIT=0 corpus[stage]: executed=7 unavailable=0 not_applicable=0 controls=14 (require=true) test result: ok. 11 passed; 0 failed ``` `executed=7`, not `executed=0 unavailable=1` — the suite really indexed the pinned repos. `tests/corpus/stage-baseline.json` and `tests/corpus/tier3-baseline.json` carry `stage.bridge.degraded` on all nine repos, recorded in `a9ba058` as an additive roster key; no value was blessed away. ### RESIDUAL, recorded rather than carried by this issue The second suggestion — **share one scan across bridges with the same `(dest_lang, dest_symbol_kind)`** — is **not** implemented, and the lane said so. It stays unimplemented deliberately: the estimate is **0 units on all nine pinned repos** because none holds a `language_bridges` row, so there is no input to optimise against and no way to measure a saving. Budgeting first is what makes that input collectable. When a project with several bridges onto one pair exists, that is a scale-ceiling fixture and belongs to **#41**, which already lists the bridge candidate fan-out as having no fixture. ### Also closed by this work **#110** is the same defect filed from the other side ("the one resolver fan-out stage with no budget, and its exemption argument is untested"). Its option 2 — *give it a `Stage`* — is what shipped, and its other requirement, that the reasoning live next to the code, is met at `index.rs:2530-2582`. Closing that one too, pointing here. ### One thing this lane did NOT verify `precision_gate` 7/7 with `phantoms=0` is cited in the lane's account. That gate is ~47 probes over 54 fixture files and never indexes a corpus repo (**#188**), so it is not evidence about corpus-scale resolution and is not being read as such here. The corpus evidence above is `corpus_stage` at `executed=7`. Closing: the gap reported — an unbudgeted, unmeasured, undisclosable candidate fan-out on the axis #75 is widening — is closed and graded.
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.

Dependencies

No dependencies set

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