A linked project running a different package set is indistinguishable from one running the same #137
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#137
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?
Split out of #72 so its stage-matrix acceptance can close. Measured, blocked by a concrete constraint rather than by difficulty.
The gap
LinkedProjectSummarycarries no generation and no package identity. So a linked project indexed under a different package set — different plugins, different grants, different active generation — presents identically to one indexed under the same set.#72 lists this among the doors a coverage answer must model, and it is the only one of eleven still unmodelled. The others are now covered by
refusal_stage_registry.rs.Why it matters
An agent asking a question that fans out across linked projects gets rows from each, and the answer's meaning differs per project: a symbol absent from a linked project may be absent because nothing declares it, or because the package that would have indexed it is not enabled there. Today nothing in the reply separates those.
That is the same absent-reads-as-negative shape as #124 (a refused package invisible on the MCP surface) and #136 (a validator refusal reported as "indexed and current"), applied across a project boundary.
Why it was not done in #72
Two measured reasons, not reluctance.
linked_projectsrides inproject_overview, which is at 4293 of 4300 tokens — seven tokens of headroom. The block also scales with the number of links, so per-link generation and package identity cannot ship without first deciding what comes out. That is a trim decision with its own trade-offs, not a field addition.LinkedProjectSummary'soutside_primary_rootis part of the containment disclosure work in #79, live at the time. Editing that struct concurrently would have been the sweep mistake this repository has already paid for once today.What closing it needs
project_overviewto make room, or put the per-link identity on a resource rather than the tool reply — #100 took exactly that route when the store inventory would not fit (counts plus a pointer on the tool, rows oncode-index://project/overview), and it is the shape that already works here.Related
#72 (where it was found and deferred), #100 (the split that solved the same budget problem), #124 and #136 (the same asymmetry on other surfaces), #79 (holds
LinkedProjectSummaryuntil its containment work lands).Triage 2026-09-06: LEFT OPEN — 2 of 3 criteria met. Reported as fixed; criterion 1 is not merely unmet, the payload grew, and the budget gate is structurally blind to it.
Criterion 2 — MET
LinkedProjectSummary.package_set—crates/mcp-server/src/server.rs:15788; built bylink_summaryat:15802; rendered byrender_link_package_setat:15847, carryingavailability: reported|unavailable,active_generation,active_activation_digest, andpackages_consultedkeeping its own third state. Never omitted, which is the contract.incl. G1B
every_stats_reading_function_is_a_declared_surface_or_helper, the widening that this issue's work required — and which, perd0279c0, caught a renderer on its first run.Criterion 1 — NOT MET, and this is the finding
This issue asked: decide what leaves
project_overviewto make room, or move per-link identity to a resource. Nothing left. The block grew.LINK_PACKAGE_SET_UNAVAILABLE_SEMANTICS(server.rs:15869) is 422 chars ≈ 106 tokens by the server's ownestimate_tokens(chars/4), emitted per link, againstOVERVIEW_SATURATED_MAX_TOKENS = 4_300.And the gate cannot see it:
linked_projectshas no cap — no row inbounding_site_registry.crates/mcp-server/tests/overview_payload_budget_e2e.rshas zero links. Searching that file forlinkreturns nothing.So the exact scaling this issue named as its blocker is structurally invisible to the budget gate. Four links ≈ 424 tokens of semantics prose, ~10% of the ceiling, on a payload that currently has 25 tokens of headroom (see #160). That is not a hypothetical.
Criterion 3 — NOT MET AS ASKED
This issue explicitly required the mutation to go red "on a payload where the two genuinely differ, not on a hand-built struct."
The only discriminating test is
server::routing_tests::two_links_with_different_package_sets_render_differently(server.rs:18556), which builds twoStatsby hand and calls the renderer directly. The mutation is real and was run — but it is precisely the hand-built struct the issue excluded.crates/mcp-server/tests/workspace_e2e.rsdoes stand up two real links (assert_eq!(linked_projects.len(), 2)at:242) and asserts nothing aboutpackage_set. That is where criterion 3's assertion belongs, and it is a small change.What closing this needs
overview_payload_budget_e2e.rs, so the per-link cost is inside the ceiling the gate enforces — or a cap row forlinked_projectsinbounding_site_registry.workspace_e2e.rs, on the two real links it already builds.Doc drift found while verifying
crates/mcp-server/tests/disclosure_derivation_registry.rs:63-86still presents #137 as an open blind spot ("recorded here rather than stubbed… would light up on the first link") while the same file at:617and:835-910says it is closed and inside the gate. One of the two is wrong; the second is right.🤖 Triage lane, 2026-09-06, master
45cf6e4overview_payload_budget_e2eis safe from the unconsulted-package-set race by luck, not by design — 25 tokens of headroom against a ~190-token block #133code-index://docs/reason-codesis at 3,979 of its 4,000-token cap, so the next reason code this project mints cannot be documented #184disclosure_derivation_registry.rscalls #137 an open blind spot in its header while its own body says the gap is closed and inside the gate #186disclosure_derivation_registry.rscalls #137 an open blind spot in its header while its own body says the gap is closed and inside the gate #186release_gate_e2e.rs:102-122still says musl is continue-on-error and that nothing runs the step-13 migration gate — both false since #84 #187Criterion 3 FIXED, merged as
a80eb61; criterion 1 has a measured answer and is left open deliberately — see below.Criterion 3.
workspace_e2estood up two real links and then asserted nothing aboutpackage_set. Both links now assert the contract on the payload an agent actually receives, not on a hand-built struct. Two mutations run: drop the digest from thereportedarm → RED, printingavailability: reportedon both links, which proves the arm is reached; omitpackage_set→ RED.Criterion 1 (the budget decision) — not done, and the reason is worth recording. The lane that reached it was based on a commit predating the three-bucket payload accounting (
ec6846c, which movedanswer_provenanceout of the content half). Any trim it measured would have been scored against the old accounting, so it handed over the analysis rather than an unverifiable trim. That was the right call.The analysis, for whoever takes it:
LINK_PACKAGE_SET_UNAVAILABLE_SEMANTICS(~106 tokens) is emitted per link, on theunavailablearm, and it is a CONSTANT — it does not vary per link. Emitting it N times is pure duplication, which is exactly the "prose that restates a vocabulary" this project deletes first. Hoisting it to thelinked_projectsblock level — once, and only when ≥1 link is unavailable — makes the block O(1) in prose whileavailabilitystays per link. That is a strict reduction, not a ceiling change.And the alternative an earlier triage suggested is worse: a cap row for
linked_projectswould silently drop projects from a fan-out answer.Context for whoever picks it up:
project_overviewcontent is 4,020 tokens against a 4,050 ceiling — 30 tokens of headroom, onefile_healthrow. The standing rule in that file is "TRIM, do not raise".Leaving open on criterion 1 only.
Closed on
masteratd4f121a(mergedde98a4f). All three criteria hold — and the last blocker turned out not to be the one this issue named.The blocker was never the paragraph's size
This issue recorded budget as blocker 1: "
project_overviewis at 4293 of 4300 tokens — seven tokens of headroom", so per-link identity "cannot ship without first deciding what comes out".That framing sent the earlier triage after the wrong question ("what leaves
project_overview?"). Nothing needed to leave. The real problem was thatlinked_projectsscales with link count, and the only per-link cost that scaled was a constant — the same semantics sentence re-inlined per link. Hoisting it to block level:A wash at one link, −132 per link from two on. The new
link_payload_scaling_e2ebounds the per-link term, isolated as(4 links − 1 link)/3so every once-per-response term cancels — a per-link ceiling rather than a cap on the list, because a cap would silently drop a project from a workspace answer.Criterion 1 was a trim decision only under the wrong model of the cost.
Criteria 2 and 3 were already in place; I checked rather than assumed
linked_projects[].package_set.availability, withlinked_projects_package_set_semanticsexplaining"unavailable"and stating what it is not — "neverthe same package set as the primaryand neverno packages". Verified in a live payload.server::routing_tests::two_links_with_different_package_sets_render_differently, run by me:1 passed. (My first attempt used--liband got exit 101 — wrong target for a--bincrate, not a failing test.)On the
daemon_buildshape I suggested — it does not fit, and the reason is structuralI asked whether #137 could use the pattern where
daemon_buildemits nothing when client and daemon agree, on the ground that "absence has exactly one producer".It cannot, and the argument is worth recording because it bounds where that pattern applies:
And separately: a
matches_primaryboolean cannot answer this issue at all. The discrimination asked for is between two links, and two links that differ from primary in different ways collapse under it.A falsehood corrected in passing
The
linked_projectsdescription claimed "every list here is capped or vocabulary-bounded" whilelinked_projects— the very list this issue is about, named two sentences earlier — is uncapped. Fixed, and the bytes for the fix came from cutting that block's own key enumeration.Mutations
M1(re-inlinesemanticsper link) → RED twice: the ceiling ("OVER its ceiling by 111 tokens (191 of 80)") and the occurrence count (left 5, right 1).M13(link_summarydropspackage_set) → RED on the precondition, not the ceiling, as its doc predicts.Verification:
fmt0 ·clippy -D warnings0 · rustdoc 0 ·cargo test --workspace336 suites 0 failed ·COSI_E2E_LEG=daemon67 suites 0 failed. Post-merge by me:link_payload_scaling_e2e3 passed, and criterion 3's test 1 passed.