The bridge tier is the one candidate fan-out with no budget: fill_bridge_cands full-scans (lang, kind) once per active bridge #164
Labels
No labels
code-review
correctness
dos
performance
security
severity/high
severity/low
severity/medium
tech-debt
Kind/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
h-dv/code-index#164
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while 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 noLIMITand no budget consulted: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:So total work is
active_bridges × |symbols(dest_lang, dest_kind)|, andactivegrows 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:
Tier1qScandswas 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
activeis 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::Bridgemember. 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-bridgepair_key/match_keyvalues 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
Measured first, then budgeted. And the measurement found a second thing.
Worktree
/tmp/cosi-lane-precisionofforigin/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_bridgesholds 0 rows on all nine pinned repos, sobridges::load_activereturns empty and the block is one indexed read —tier_bridge=0in every resolve log.stage-baseline.jsonalready said the same thing from the other side: rule 90 (bridge) is the one code inresolved_by::ALLthat 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:javascript/function= 123ruby/method= 833python/function= 1 059typescript/field= 1 183csharp/method= 1 675rust/method= 2 199php/method= 2 759rust/function= 12 487python/method= 29 513One bridge on py-django materialises up to 29 513 candidate rows;
nbridges targeting the same pair payn ×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, sincee6dd5c9(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 isactive_bridges × |symbols(lang, kind)|, andactiveis 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, keybridge, envCODE_INDEX_BRIDGE_WORK_BUDGET,BRIDGE_WORK_BUDGET_DEFAULT = 50_000_000.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.temp.bridge_candsisWITHOUT ROWIDwithPRIMARY 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_langanddest_symbol_kindare PACKAGE-SUPPLIED, and that module holds that no package value ever reaches a SQL string. Anestimate_sqlwould have had to interpolate one. SoStageBudgets::guard_measured_attakes an estimator closure (bound parameters), andguard_atis 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 itSEARCH … USING COVERING INDEXwhile the body's SELECT also seeksfilesby rowid per row):rust/functionrust/functionpython/method0.4 % of what it guards at real scale — before the body's per-row Rust allocation,
match_keycall andWITHOUT ROWIDinsert. 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 isTier1qScands' 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_skipsThe daemon is spawned with
CODE_INDEX_BRIDGE_WORK_BUDGET=0viaspawn_with_env— the production channel, in the daemon's own process, so it cannot leak into this binary's other tests (an in-processset_varwould unbind the two positive controls). Asserts: no ref carriesby 90;stage_budget.bridge.{degraded,budget};bridgeinstage_budget.measured(which is whatread_stages_skippedwalks,envby formula → the wire entry); and the estimate equals|symbols(csharp,class)| + |symbols(csharp,method)|.Mutations, RUN:
["type Demo.MainWindow -> MainWindow[csharp] … by 90", …, "call OnSaveClick -> OnSaveClick[csharp] … by 90"].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 provedest > 0is 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.lang = ?1from the estimate is GREEN, and it is a fact about the fixture — its histogram iscsharp/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 aclassor amethodthere. The language filter is UNGRADED by this test; dropping it over-estimates, whichresolve_budget's module doc calls the safe direction, and grading it would need a bridge declared ontofield.4. A PRE-EXISTING gate defect, found by trying to baseline the new stage
corpus_stagewas GREEN after addingStage::Bridge. It should not have been —stage.bridge.degradedis a new key on all seven repos.corpus::projection::categorised_diffcomparedwant.get(key).unwrap_or(0)againstgot.get(key).unwrap_or(0)andcontinued on equality before looking at presence. Every healthystage.*.degradedis0, soNEW KEYandKEY GONEwere unreachable for exactly the values that carry them.Measured on master's own code, not predicted: delete
Stage::CsharpPartialfromStage::ALL— it leavesstage_budget.measuredand all seven baselinedstage.csharp_partial.degradedkeys vanish from the observation — andcorpus_stagereportedtest 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 intier3-baseline.json, 9 inbaseline.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_driftgained a zero-valued arm; MUTATION (RUN): restoreif 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:
Nothing else moved on any repo, which is the strongest available statement that the change is behaviour-neutral:
corpus_ratchet(baseline.json,914dda1a) andcorpus_costare both GREEN withexecuted=7, and everyrule./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::Bridgejoinsresolve_budget::Stage::ALL, sostage_budget.measuredgainsbridgeand every repo gains onestage.bridge.degraded: 0. Additive and value-free — the estimate is 0 on all nine repos because none has alanguage_bridgesrow, so no bind, rule, influence or producer figure moves andbaseline.jsonis unchanged. The key was invisible before this commit:categorised_diffskipped presence changes whose value was 0, proven by removingStage::CsharpPartialfromStage::ALLand 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 (nbridges onto one pair payn ×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:
fmt0 ·clippy -D warnings0 ·rustdoc -D warnings --document-private-items0 ·clippy --target x86_64-pc-windows-gnu -D warnings0 ·precision_gate7/7,phantom_count == 0on every language ·corpus_ratchetok (executed=7) ·corpus_costok (executed=7) ·xaml_package_e2e14/14 ·corpus_stage/corpus_tier3_ratchetRED with exactly the nine lines above.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: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 istail'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 independentgrep -c '^test result: FAILED'and a summed per-suite count, so three signals agree. Same class asawk -F'[ ;]'putting the failure count in the wrong field.Full gate set:
fmt --check0 ·clippy --workspace --all-targets -D warnings0 ·RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --document-private-items0 ·clippy --workspace --all-targets --target x86_64-pc-windows-gnu -D warnings0 ·precision_gate7/7 withphantoms=0on every language ·corpus_ratchetok (executed=7) ·corpus_costok (executed=7) ·ruby_package_parityok (executed=1, controls=4) ·xaml_package_e2e14/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.json914dda1a,stage-baseline.json7d9695be,tier3-baseline.json94abf592,ruby-package-cost.jsonf5a1a8f2,tests/bench/oracle/rust-ripgrep.json78895712.CLOSING — verified on master
552e3a2, with the residual namedClose-out lane. Worktree
/tmp/cosi-lane-closeoutat552e3a2. Every location below was read on master with this repo's own tools (search_text→read_code), not grep.What is on master
Stagehas five members and none is the bridgeStage::Bridge—crates/indexer/src/resolve_budget.rs:129, and inStage::ALLat:154alongside the other fiveBRIDGE_WORK_BUDGET_DEFAULT = 50_000_000—crates/indexer/src/index.rs:2582, envCODE_INDEX_BRIDGE_WORK_BUDGETkey()→"bridge"(resolve_budget.rs:172), so the stage joinsstage_budget.measuredandstage.bridge.degradedexistsStageBudgets::guard_measured_at(resolve_budget.rs:289) is called atindex.rs:7371, i.e. on thefill_bridge_candspath itselfThe 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)
executed=7, notexecuted=0 unavailable=1— the suite really indexed the pinned repos.tests/corpus/stage-baseline.jsonandtests/corpus/tier3-baseline.jsoncarrystage.bridge.degradedon all nine repos, recorded ina9ba058as 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 alanguage_bridgesrow, 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 atindex.rs:2530-2582. Closing that one too, pointing here.One thing this lane did NOT verify
precision_gate7/7 withphantoms=0is 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 iscorpus_stageatexecuted=7.Closing: the gap reported — an unbudgeted, unmeasured, undisclosable candidate fan-out on the axis #75 is widening — is closed and graded.