Skip to content

gateway: surface DIFC filtering metadata so agents can distinguish filtered from empty#4427

Merged
lpcox merged 3 commits intomainfrom
copilot/add-filtering-metadata-to-results
Apr 23, 2026
Merged

gateway: surface DIFC filtering metadata so agents can distinguish filtered from empty#4427
lpcox merged 3 commits intomainfrom
copilot/add-filtering-metadata-to-results

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 23, 2026

When DIFC integrity/secrecy filtering removes all items from a tool response, agents receive a result indistinguishable from a genuinely empty repo — causing targeted-dispatch workflows to drift into scheduled mode and report-generating agents to create duplicate issues.

Changes

internal/server/difc_log.go — new helpers

  • buildDIFCSingleItemFilteredError(): builds a structured error for the single-item-entirely-filtered case with a [Filtered] marker, resource description, policy type (secrecy/integrity/access), and denial reason
  • isSingularReadTool(toolName): returns true for singular-read tools (get_*, *_read) and false for collection tools (list_*, search_*), ensuring the MCP error path only fires for tools that are expected to return a single item

internal/server/unified.go — Phase 5 single-item branch

  • When accessible == 0 && filtered == 1 and the tool is a singular-read (get_* / *_read): return IsError MCP result via buildDIFCSingleItemFilteredError instead of empty array + text notice
  • When the tool is a collection tool (list_*, search_*) with any number of filtered items, or when filtered > 1: existing notice behavior preserved — agents see an empty/partial list with a [Filtered] text notice appended
[Filtered] issue:org/repo#42 exists but is not accessible — filtered by integrity policy (integrity too low for agent context)

internal/server/call_backend_tool_difc_test.go — 4 new Phase 5 tests

  • AllItemsFiltered_NoticePresent: multi-item list entirely filtered → notice in content, IsError=false
  • SingleItemFiltered_ReturnsMCPError: singular-read tool with 1 filtered item → IsError=true, content contains [Filtered]
  • GenuinelyEmptyCollection_NoNotice: truly empty backend result → no notice, no error
  • ListToolSingleItemFiltered_NoticeNotError: list_issues returning exactly 1 filtered item → notice only, IsError=false (regression guard)

internal/server/difc_log_test.go — unit tests

  • 4 tests for buildDIFCSingleItemFilteredError covering integrity violation, secrecy violation, missing description, and missing reason
  • TestIsSingularReadTool: table-driven test covering all key tool name patterns

Copilot AI changed the title [WIP] Add filtering metadata to DIFC tool results gateway: surface DIFC filtering metadata so agents can distinguish filtered from empty Apr 23, 2026
Copilot AI requested a review from lpcox April 23, 2026 22:53
@lpcox lpcox marked this pull request as ready for review April 23, 2026 22:56
Copilot AI review requested due to automatic review settings April 23, 2026 22:56
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts DIFC filter-mode behavior so agents can distinguish “no results” from “results existed but were withheld by DIFC”, specifically by turning the “single expected item was fully filtered” case into an MCP error instead of an empty success response.

Changes:

  • Add buildDIFCSingleItemFilteredError() to produce a consistent [Filtered] ... filtered by <policy> (<reason>) error message for a fully-blocked single item.
  • Update Phase 5 DIFC filtering in callBackendTool to return an IsError MCP result (HTTP 403) when a collection ends up with exactly one filtered item and zero accessible items.
  • Add/extend tests covering: multi-item all-filtered notice behavior, single-item filtered error behavior, and genuinely empty collections.
Show a summary per file
File Description
internal/server/unified.go Returns an MCP error (IsError) for the “single item fully filtered” case during Phase 5 filtering.
internal/server/difc_log.go Adds helper to build the single-item filtered error message with [Filtered] marker and policy labeling.
internal/server/call_backend_tool_difc_test.go Adds Phase 5 tests for multi-item all-filtered notices, single-item filtered errors, and true empty results.
internal/server/difc_log_test.go Adds unit tests for the single-item filtered error message formatting across policy/reason/description cases.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment on lines +629 to +637
// **Single-item entirely filtered**: return a structured MCP error so the agent
// cannot misinterpret "filtered" as "resource not found" (e.g. issue_read).
// When exactly one item was expected and it is blocked, an error is clearer
// than an empty array accompanied by a text notice.
if filtered.GetAccessibleCount() == 0 && filtered.GetFilteredCount() == 1 {
filteredErr := buildDIFCSingleItemFilteredError(filtered.Filtered[0])
logger.LogWarn("difc", "Single item filtered — returning MCP error: %v", filteredErr)
httpStatusCode = 403
return mcp.NewErrorCallToolResult(filteredErr)
@lpcox
Copy link
Copy Markdown
Collaborator

lpcox commented Apr 23, 2026

@copilot address the review feedback #4427 (review)

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 23, 2026

@copilot address the review feedback #4427 (review)

Fixed in 2686cf0. Added isSingularReadTool(toolName) that returns false for list_* and search_* prefixes (collection tools) and true for everything else (get_*, *_read). The single-item MCP error path in Phase 5 is now gated on this check, so list_issues returning exactly 1 filtered item gets the notice-only behavior, while issue_read / get_issue still return the structured [Filtered] MCP error. Added a regression test (TestCallBackendTool_Phase5_FilterMode_ListToolSingleItemFiltered_NoticeNotError) and unit tests for isSingularReadTool covering all key tool names.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[gateway] DIFC-filtered tool results should include filtering metadata to prevent agent misinterpretation

3 participants