Skip to content

The headline names the blocking cause only when a governance notice is also in play; an ordinary blocked PR still gets a bare count #436

Closed

Description

@pengfei-threemoonslab

Summary

#365 taught the verifier headline to name the worst blocker rather than only count it. Its own commit message states why:

That leading line also names the worst blocker instead of only counting it — a count reads the same whether the agent is missing a docstring or can move funds with no enforced control.

That leading line is _report_primary_headline, and _verifier_headline reaches it from two branches only — the newly-introduced-manifest branch and the self-approval branch. Every other run returns report.agent_summary.headline directly:

# cli/verify/orchestrator.py::_verifier_headline
if manifest_introduced and not pure_adoption_review and report is not None:
    ...
    return _compose_with_reserved_suffix(_report_lead(report, suffix), suffix)   # <- named
note = _self_approval_note(...)
if note is not None:
    if report is not None and _blockers_outrank_governance(report.release_decision):
        return _compose_with_reserved_suffix(_report_lead(report, note), note)   # <- named
    return _compose_with_reserved_suffix(..., note)
if report is not None and report.agent_summary is not None:
    return _lead(_one_clause(report.agent_summary.headline))                     # <- counted

So the case #365 was written for — a PR that blocks release on critical findings — gets the named cause only if it also touches the trust root or introduces a manifest. An ordinary blocked PR, which is the common one, still gets the count.

Reproduction

Two commits in an MCP repository whose manifest declares delete_repository; the head commit adds one unrelated tool and touches nothing else. verify --base HEAD~1, CLI at 16261d99.

The blockers are there, both critical, and the picker finds them:

blockers: [('critical', 'SHIP-POLICY-APPROVAL-MISSING',
            'delete_repository lacks a declared approval policy'),
           ('critical', 'SHIP-ACTION-DESTRUCTIVE-ROLLBACK-MISSING',
            'delete_repository has destructive capability without required controls')]

_worst_blocker(...).title
  -> 'delete_repository has destructive capability without required controls'

_report_primary_headline(report)
  -> '2 active finding(s) block release; 3 review item(s) accepted as debt.
      Most severe: delete_repository has destructive capability without required controls.'

And the line that actually ships:

verifier.headline
  -> '2 active finding(s) block release; 3 review item(s) accepted as debt. ...'

"Most severe" in verifier.headline  ->  False

_report_primary_headline computed exactly the right sentence and nothing published it. Two critical blockers, and the one line that reaches the PR comment, control.reason, control.next_action.why and any triage list names neither.

Why this is worth fixing rather than leaving

The ranking half of #365 shipped and works: when a governance notice competes with a critical blocker, the blocker leads. The naming half shipped only on the routes where that competition exists. A reviewer's exposure to the improvement is therefore inversely related to how ordinary their PR is — the repository that has already adopted, changes no policy, and simply blocks on a destructive tool is the one that still reads a count.

It also makes the surrounding line harder to interpret. On #433's reproduction the headline now carries Not fully analysed: find_duplicate [github_mcp] — … next to 14 active finding(s) block release, and a reader has no way to tell whether the 14 are about the named subject or about something else entirely. Most severe: is exactly the clause that would answer that.

Suggested direction

Route the plain branch through _report_primary_headline as well — it already falls back to agent_summary.headline when there is no blocker, so the no-blocker case is unchanged by construction:

if report is not None and report.agent_summary is not None:
    return _lead(_report_primary_headline(report))

Two things to settle deliberately rather than by accident:

  • Budget. _lead fits its context with _fit_sentences against MAX_ENVELOPE_PROSE_BYTES - len(primary), and primary grows by up to _HEADLINE_TITLE_MAX_CHARS (120) here. On the plain branch there is no reserved suffix, so nothing is at risk of being cut — but the evidence-gap context will yield more often. That is the documented priority order (verdict, then cause, then context), so it is probably right; it should be stated rather than discovered.
  • Blast radius. Every blocked run's verifier.headline, control.reason and control.next_action.why gains a clause. It is prose, not a documented identifier, but it is worth a ## Migration Note and a check of the PR-comment goldens.

Acceptance

  • The reproduction above puts Most severe: delete_repository has destructive capability without required controls. in verifier.headline, control.reason and control.next_action.why.
  • A blocked run that touches no trust root and introduces no manifest names its worst blocker — the case with no existing test.
  • A run with no blockers gains no clause (_report_primary_headline already returns the summary unchanged; the point is to pin it).
  • The picker stays deterministic across the branches: severity, then check id, then title, same row on the plain path as on the governance paths.

Found 2026-08-26 while reviewing #434 (#433). Noted there as out of scope: it is a severity-attribution question rather than a naming one, and it moves every blocked headline.

Activity

  1. added
    bugSomething isn't working
    P2Queued; valuable but not blocking
    area:verifyverify / preview orchestration and SHIP-VERIFY-* checks
    area:agent-modeAgent-mode contract: control envelope, next actions, command strings
    on Aug 27, 2026
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:agent-modeAgent-mode contract: control envelope, next actions, command stringsarea: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