Skip to content

Capability delta reports +0 on a PR that adds a tool: the diff is computed over the analysed surface, which is what the new tool is missing from #437

Closed

Description

@pengfei-threemoonslab

Summary

The PR comment reports Capability delta: +0, 0 modified, -0 on a pull request whose entire content is one added tool. The number is self-consistent — the tool is unbound, so it is not bound capability — but it sits two lines above a gap tally and reads, to a reviewer, as "this PR changed no capability."

Raised in #433 as "worth a separate look — whether an added-but-unbound tool should appear in the delta at all", and deliberately left out of #434: what counts as a delta is a decision change, not a rendering one.

Reproduction

Two commits in an MCP repository. Base declares list_issues and delete_repository and binds both; head adds find_duplicate to tools.json and does not declare it — updating shipgate.yaml is a trust-root edit a coding agent cannot self-approve, so this is the normal shape, not a contrived one. CLI at 16261d99.

capability_review added/modified/removed:  0 0 0

tool_surface_diff.enabled:  True
tool_surface_diff.tools:    []

tool_catalog names:            ['find_duplicate', 'list_issues', 'delete_repository']
tool_surface.total_tools:      2
binding_surface_facts.unbound: ['tool_v2_7cbbbd23…']

The catalog has three tools and the surface has two. The diff ran, found nothing, and is not wrong about what it looked at.

Where the number comes from

capability_review.added is len(capability_change.added) (cli/verify/capability_review.py), and the tool half of capability_change.added is report.tool_surface_diff.tools filtered to kind == "added" (core/findings/verifier_blocks.py::build_capability_change).

tool_surface_diff is compute_tool_surface_diff(public_tool_surface_facts, base_facts, …) (cli/scan/sanitization.py), and public_tool_surface_facts is built from public_tools — the analysed set, which the binding graph has already narrowed to reachable_tool_ids. An unbound tool is exactly the tool that is not in it, on either side. So +0 is structural: the delta cannot see the class of change the exclusion ledger exists to report.

Why the two lines together are the problem

pr-comment.md renders them adjacent:

- Capability delta: +0, 0 modified, -0
- Summary: … 1 of 83 evidence gap(s) are new in this diff. Not fully analysed:
           find_duplicate [github_mcp] — added by this diff and not bound to the root agent.

After #434 the second line names the subject, so the reviewer is no longer left with nothing. But the first line still states a fact that is true of a surface the reader has no reason to know is narrower than the diff — and a reviewer skimming for "did capability change?" gets +0 on a PR that adds a tool.

Options, none of them obviously right

  1. Leave the count and label the surface. Capability delta (bound capability): +0 — cheapest, changes no number, and makes the two lines consistent by saying what the first one measures. Weakest: it relies on the reader connecting two bullets.
  2. Add a third term. Capability delta: +0, 0 modified, -0; 1 added but unanalysed — sourced from the ledger's newly_unbound_tool rows, which are already computed and already the base-comparison-backed set (binding_surface_diff.added_unbound_tool_ids). Keeps added meaning what it means today, so nothing downstream re-interprets an existing number.
  3. Count it in added. Wrong, I think, and worth writing down why: it would make capability_change.added mean "in the catalog" rather than "in the proven capability surface", which is precisely the conflation #385 drew a boundary against — samples/large_multi_framework_agent has 58 unwired OpenAPI operations by design, and a delta of +58 on the PR that declares the spec is not a capability change anyone made.

Option 3 also moves a published count that consumers gate on, so it would need the STABILITY.md treatment; 1 and 2 do not.

My reading is that (2) is the honest one and (1) is the cheap one, and that this should not be decided inside a rendering PR.

Acceptance

  • The reproduction above does not let a reviewer conclude the PR changed no capability.
  • samples/large_multi_framework_agent gains no delta from its 58 by-design unwired operations — whatever is added must key off the base comparison, not off catalog membership (the #403 discriminator).
  • A plain scan, which has no base, reports nothing new here.
  • If capability_change.added moves at all, STABILITY.md carries the before/after and the change is versioned.

Split out of #433, which flagged it and scoped it out. Evidence collected 2026-08-26 while reviewing #434.

Activity

  1. added
    bugSomething isn't working
    P2Queued; valuable but not blocking
    area:verifyverify / preview orchestration and SHIP-VERIFY-* checks
    on Aug 27, 2026
  2. pengfei-threemoonslab commented on Aug 27, 2026

    @pengfei-threemoonslab
    ContributorAuthor

    See #439 — the same field failing the other way. #437 under-counts when the added tool is unbound; #439 over-counts when it is bound, because the count is over changes (tool_added + action_added for one subject) rather than over subjects.

    Worth resolving together: a change addressing only the unbound case would still report +2 for one bound tool, and vice versa. Both tracked under #440.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Queued; valuable but not blockingarea:verifyverify / preview orchestration and SHIP-VERIFY-* checksbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions