fix: stop the exclusion scan writing the rendered list's report (#1909)

Copilot round 10.

The scan no longer publishes its malformed entries into the tools/list report
at all. Round 9 only stopped it overwriting the aggregate's; the remaining
case is unsalvageable for a stronger reason than 'two different requests':

- a FAILED strict aggregate runs salvage, which writes the report itself;
- a SUCCESSFUL one means every entry parsed, so the rendered list contains no
  malformed entries.

So by the time the scan finds something, either the aggregate already reported
it with indices matching the screen, or the aggregate succeeded and what the
scan found is provably not in the list being displayed. Publishing could only
ever name entries the user cannot see.

Nothing is lost: listToolsForScan still marks its own refused response, so the
finding surfaces on the Protocol entry for the exchange that carried it, where
it is true. The per-page accumulation and raw-entry offset existed only to feed
the removed publish, and go with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDXCSrnSBV7vZfxycG5Z6E
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-15 20:38:10 -04:00
commit 77ff30bbc9

View file

@ -535,10 +535,12 @@ describe("InspectorClient list salvage (#1909)", () => {
log.destroy();
});
it("reports scan-dropped tools at their position in the whole list", async () => {
// The exclusion scan walks pages of its own, so its report has to be
// offset by the RAW entries of PRIOR pages. An unlabeled entry's index is
// all the user has to go on, so a wrong offset points at nothing.
it("indexes a dropped tool against the whole aggregate, across pages", async () => {
// The reported index has to be offset by the RAW entries of PRIOR pages —
// valid and malformed alike, not just the kept ones. An unlabeled entry's
// index is all the user has to go on, so a wrong offset points at nothing.
// This is the aggregate salvage's walk: it owns the `tools/list` report,
// because it is the walk that produced the list being rendered.
const server = await startMalformedServer(
{
tools: [
@ -610,12 +612,12 @@ describe("InspectorClient list salvage (#1909)", () => {
expect(connected.getMalformedListItems()).toEqual([]);
});
it("does not let the scan overwrite the aggregate's report", async () => {
it("keeps the aggregate's report when the scan sees a different list", async () => {
// Both walks run on a modern connection and they are separate, uncached
// exchanges. The aggregate is the list being RENDERED, so its report owns
// the indices; the scan must not replace them with its own — the server can
// answer the second request differently, and then the warning would point
// into a list nobody is looking at.
// exchanges. The aggregate is the list being RENDERED, so it owns the
// report; the scan never writes it. Here the server answers the scan's
// request with a different malformed shape — which, if the scan published,
// would leave the warning pointing into a list nobody is looking at.
const server = await startMalformedServer(
{
tools: [