MCP resources bypass the evidence_gaps grader, so the same data is served graded through tools and ungraded through resources #107
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#107
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 #101/#99, and named there as a deliberate scope call rather than discovered afterwards.
The gap
annotate_evidence_gapssits inCodeIndexServer::call_tool, above the router. That placement is what makes it cover all 24 tools without any of them opting in — a future tool 25 is graded by construction.read_resourceis a different entry point and does not pass through it. These serve the same underlying data ungraded:code-index://project/overviewcode-index://project/statscode-index://file/{path}code-index://symbol/{id}So a client reading
code-index://file/UI/wndBeleg.xamlon a truncated file gets exactly the silent partial answer #101 was filed about, whilefile_outlineon the same path now discloses it. One surface tells the truth and its neighbour does not — which is the shape of #99 and #101 themselves, one level up.code-index://file/{path}andcode-index://symbol/{id}are the two that matter most: both are per-entity, so both can name the specific partial source in the way the tool replies now do.Why it was scoped out and why that is still worth revisiting
Both #101 and #99 are, as filed, about tool reports, and widening mid-fix would have meant a second grader placement with its own coverage argument — exactly the per-surface patching the one-mechanism design was avoiding. That was the right call for that change.
But the reason the grader was put above the router was that no surface should have to opt in. Resources are a surface that currently has to, and does not.
Shape of the fix
The same grader, called once in
read_resource, on the same three-state contract: absent = measured and clean,partial_sources_unmeasured= could not be asked,partial_sources= the finding. Not a second implementation — if it needs different code, the placement is wrong.The gate should be a registry test over the resource URIs, in the shape of
crates/mcp-server/tests/disclosure_surface_registry.rs: every resource this server serves must be graded, so adding a resource without grading it fails the build rather than shipping a silent surface. A test that merely checks the four current URIs would go stale the first time a fifth is added — which is the failure this project has already paid for more than once.Mutation: delete the grader call from
read_resource; the registry test must go red naming the ungraded URIs, not merely one e2e.Related
Follow-up to #101 and #99. Same family as #97 (which covered all tools by construction for the same reason).
Fixed, with the same grader — which was the condition this issue set.
The grader was extracted out of
annotate_evidence_gapsasCodeIndexServer::evidence_gaps_for, andgrade_resource_bodycalls it once inread_resource, on the onebodythat every arm of the URI match produces. If it had needed different code, the placement would have been wrong; it did not.Three details worth recording:
docs/{topic}is graded and finds no object — a measurement, not an exemption. An arm that skipped grading would be indistinguishable from one that graded and found nothing.disclosure_surface_registry.rs), and one fixture serves both suites (shared withdisclosure_contract_e2e.rs). No second definition of either.The registry gate, which is what this issue actually asked for
crates/mcp-server/tests/resource_grading_registry.rs— five tests: registry in both directions, placement, one-grader-two-entry-points, and a runtime sweep driven from the registry rows against truncated and clean databases.Placement is proven by a line-indentation scan rather than brace counting, because the match arms contain
format!("… {uri}")and a brace counter walks straight into them. Recording that because the obvious implementation is wrong in a way that would still pass.Mutations, all RUN
Delete the
.grade_resource_body(call — the registry test goes red naming every URI, which is the behaviour this issue specified over "merely one e2e":Also run: move the call inside the
Statsarm → RED on placement, naming the same six; add arest == "provenance"arm → RED for a missing registry row (so a fifth resource added without grading fails the build, which was the requirement); make the tool leg stop calling the shared grader → RED withhas 1 call sites; there are two entry points.Note the URI list came back as six, not the four this issue named —
code-index://telemetryanddocs/{topic}were not in the filing. That is the registry earning its place immediately: an enumeration written by hand today would already have been two short.read_coderejects the integer symbol id thatsearch_symbolsjust handed it, breaking the one documented zero-hop read #121read_coderejects the integer symbol id thatsearch_symbolsjust handed it, breaking the one documented zero-hop read #121archive_refusedreaches nocoverage_reasonscode, so an agent's answer is qualified by nothing #124