Skip to content

fix(ci): decide truth-gate applicability from PR provenance or a declared issue contract - #1364

Closed
groupthinking wants to merge 1 commit into
mainfrom
agent-completiontruth-gate-is-permanently-unsatisfiable-grv-196-8883
Closed

fix(ci): decide truth-gate applicability from PR provenance or a declared issue contract#1364
groupthinking wants to merge 1 commit into
mainfrom
agent-completiontruth-gate-is-permanently-unsatisfiable-grv-196-8883

Conversation

@groupthinking

Copy link
Copy Markdown
Owner

Canonical issue

Closes #1130

Outcome

Human PRs closing issues that label automation mislabelled agent-task/mcp-agent are no longer judged agent completions, so agent-completion/truth-gate stops emitting permanent, unsatisfiable blocked/invalid_payload verdicts on them. Genuine agent work stays gated exactly as before.

Scope

  • Included: both agentTaskApplicable() copies in .github/workflows/pr-checks.yml (kept byte-identical); a mislabelled_agent_task core.notice in the collector; regression tests in tests/unit/test_agent_completion_gate.py.
  • Explicitly excluded: the linear-code[bot] labelling rule (external), trust-policy provisioning (Report Agent Lock gate neutral when trust policy unprovisioned #1222 / GRV-240), and verdict diagnostics (GRV-271).

Applicability now requires either PR provenance (known agent author, agent branch prefix, agent label on the PR, agent-lock-manifest in the body) or a genuine issue-side dispatch: an agent-task/mcp-agent label and declared Agent Run ID + Agent Login headings. A bare label with no contract no longer arms the gate.

This supersedes stalled draft #1154 and resolves both of its unresolved review findings:

  • PRRT_kwDORAYbZs6Vqi8J — the issue-side check accepts only the two contract labels (agent-task, mcp-agent); the generic agent label stays PR-side only, since the snapshot job and collector never recognise it on issues and arming from it would block permanently as linked_issue_not_agent_task.
  • PRRT_kwDORAYbZs6Vqi8L — the mislabel notice is keyed on the missing contract itself rather than on !applicable, so a Dependabot PR linked to a valid contract never receives a false notice.

Risk

  • Risk level: medium (governance gate logic)
  • Failure mode: an agent completion evading the gate would need to hide every PR-side provenance signal and close an issue with no declared contract — in which case there is no contract to verify against and the old behaviour was an unresolvable block, not a verification. Fail-closed behaviour is retained for all declared contracts and all PR-provenance paths.
  • Rollback: revert the single commit.

Verification

  • Focused tests: pytest tests/unit/test_agent_completion_gate.py — 106 passed, 79 subtests passed, including new test_agent_applicability_requires_provenance_or_declared_contract, test_agent_applicability_copies_stay_identical, and test_mislabelled_agent_task_is_reported_rather_than_blocked; workflow YAML validated.
  • Required CI
  • Review threads resolved

Note: this PR is itself armed by the truth gate (branch prefix agent-…, under both old and new logic) while pr-checks.yml runs from the base branch, so its own agent-completion/truth-gate status is expected to stay red until maintainers handle it (the check is not merge-blocking; see the self-blocking discussion on #1130).

Production evidence

Not applicable — CI workflow logic only; behaviour is exercised by the node -e extraction harness in the unit tests.

Agent handoff

Agent provenance

No agent-lock-manifest is declared: the linked issue #1130 declares no dispatch contract (Agent Run ID / Agent Login), and self-issuing one for a dispatch that never happened is exactly the fabrication the gate exists to reject.

agentTaskApplicable() unioned PR labels with linked-issue labels, so a
bare agent-task/mcp-agent label applied by label automation to an issue
that never declared a contract judged any closing PR an agent
completion. The gate then required an Agent Run ID / Agent Login the
issue never declared, yielding a permanent blocked/invalid_payload
verdict no author could satisfy.

Applicability now comes from PR provenance (known agent author, agent
branch prefix, agent label on the PR, lock manifest) or from a genuine
issue-side dispatch: an agent-task/mcp-agent label AND declared Agent
Run ID + Agent Login headings. The generic agent label remains a
PR-side signal only, since the snapshot job and collector never
recognise it issue-side — arming from it would block permanently as
linked_issue_not_agent_task with no snapshot to satisfy.

The collector now emits a mislabelled_agent_task core.notice when a
linked issue carries a contract label without declaring the contract,
keyed on the missing contract itself (not inapplicability) so
Dependabot PRs linked to valid contracts never get a false notice.

Generated with [Linear](https://linear.app/myxstack/issue/GRV-196/agent-completiontruth-gate-is-permanently-unsatisfiable-for-any-pr#agent-session-8e5d62f0)

Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
@linear-code

linear-code Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

GRV-196

@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v0-uvai Canceled Canceled v0 Aug 4, 2026 6:10am

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • [‘architecture-gap’, ‘bug’, ‘ci-cd’, ‘ci/cd’, ‘copilot-rabbit’, ‘documentation’, ‘duplicate’, ‘enhancement’, ‘frontend’, ‘github_actions’, ‘good first issue’, ‘help wanted’, ‘high-priority’, ‘invalid’, ‘javascript’, ‘ml-model’, ‘needs-triage’, ‘pipeline-critical’, ‘placeholder-code’, ‘priority:high’, ‘python’, ‘python:uv’, ‘question’, ‘styling’, ‘tests’, ‘v0’]

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 51c8e0f5-1c3e-4c51-bcb7-bb41dc0b90e1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA d477f48.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Agent Completion Truth Gate: BLOCKED

Reasons: invalid_payload

Machine-readable verdict
{
  "details": {
    "collection_errors": [
      "incomplete_linked_issue_contract",
      "missing_intent_snapshot",
      "missing_agent_run_id",
      "missing_agent_login"
    ],
    "invalid_fields": [
      "policy.agent_login",
      "policy.run_id"
    ]
  },
  "reasons": [
    "invalid_payload"
  ],
  "verdict": "blocked"
}

Workflow evidence

groupthinking pushed a commit that referenced this pull request Aug 5, 2026
The truth gate scores a pull request against the frozen intent snapshot on
its linked issue. That snapshot is only ever written by
snapshot-agent-task-intent, which runs on `issues` events alone -- it never
runs on pull_request_target. So a pull request with no linked issue has no
snapshot, no declared agent_login and no declared run_id, which means
policy.agent_login, policy.run_id and issue.number can never be populated
and the verdict is permanently `invalid_payload` no matter what the author
does.

Arming that unsatisfiable state from a branch-name prefix made the check red
on pull requests that never had a contract to satisfy. It is red on merged
commits too: #1368, the current tip of main, merged with
agent-completion/truth-gate/pr-1368 failing on exactly this. A check that is
red on everything gates nothing and buries real failures, which is the
failure mode agent-completion-enforcement.yml already warns about in its own
comments.

Pull-side provenance now arms the gate only when a linked issue exists to
verify against. With none there is nothing to measure, so the verdict is
not_applicable rather than blocked. This is not an escape hatch: a pull
request that links a dispatched issue is gated exactly as before, and the
requirement to bind a pull request to a focused issue at all is separately
owned by the `Canonical issue and evidence` check, which states a
requirement an author can actually meet.

Consolidates the two competing open implementations of this fix. #1364's
commit is cherry-picked here with authorship intact; #1154 carried the same
intent but had drifted to 117 files and 14k lines of unrelated changes.
Both should close in favour of this.

Full unit suite: 8079 passed, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YcHjCZ6pGn6A5BeeoZ6eZi

Copy link
Copy Markdown
Owner Author

Closing — consolidated into #1377, with this commit cherry-picked intact

Your approach won. Of the two competing implementations of this fix, this one is clearly the better:

#1364 (this) #1154
Files changed 2 117
Lines +335 / −44 +14,266 / −4,056
Commits ahead 1 1,462
Added test coverage +192 lines buried in drift

Commit d477f485b has been cherry-picked into #1377 with authorship preserved — the declaresAgentContract / carriesLabel logic and its test rows are carried over unchanged.

#1377 adds the one piece this PR did not cover. This PR correctly stopped a bare issue label from arming the gate, but pull-side provenance (agentBranch, knownAgents, manifestPresent) still arms it. That leaves an agent-named branch with no linked issue permanently blocked: snapshot-agent-task-intent runs on issues events only, so with no linked issue there is no snapshot, and policy.agent_login / policy.run_id / issue.number can never be populated. #1377 requires a linked issue before provenance arms the gate, so that case resolves not_applicable instead of invalid_payload.

That case is not hypothetical — it is why agent-completion/truth-gate/pr-1368 failed on #1368, which merged and is the current tip of main.

Closing here only because the work now lives in #1377; consolidating rather than stacking a third PR on the same surface. #1154 is closed for the same fix.


Generated by Claude Code

groupthinking added a commit that referenced this pull request Aug 5, 2026
… audit 337 branches (#1377)

* test: stop asserting a CPython-version-specific rmtree detail

test_cleanup_is_total_for_non_oserror_failures failed on Python 3.11.15:
its premise asserted that shutil.rmtree(path, ignore_errors=True) raises
ValueError on a NUL-byte path. That is an implementation detail which has
changed -- ignore_errors now absorbs the non-OSError as well, so the
assertion no longer holds.

The contract under test is unaffected: _cleanup_download_artifacts must
swallow non-OSError failures because it runs from a finally block and
would otherwise replace the in-flight exception. Establish that premise
against the unguarded rmtree call, which still raises, so the test proves
the helper's own defensiveness rather than the stdlib's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YcHjCZ6pGn6A5BeeoZ6eZi

* fix(ci): stop arming truth gate from bare agent labels on linked issues

agentTaskApplicable() unioned PR labels with linked-issue labels, so a
bare agent-task/mcp-agent label applied by label automation to an issue
that never declared a contract judged any closing PR an agent
completion. The gate then required an Agent Run ID / Agent Login the
issue never declared, yielding a permanent blocked/invalid_payload
verdict no author could satisfy.

Applicability now comes from PR provenance (known agent author, agent
branch prefix, agent label on the PR, lock manifest) or from a genuine
issue-side dispatch: an agent-task/mcp-agent label AND declared Agent
Run ID + Agent Login headings. The generic agent label remains a
PR-side signal only, since the snapshot job and collector never
recognise it issue-side — arming from it would block permanently as
linked_issue_not_agent_task with no snapshot to satisfy.

The collector now emits a mislabelled_agent_task core.notice when a
linked issue carries a contract label without declaring the contract,
keyed on the missing contract itself (not inapplicability) so
Dependabot PRs linked to valid contracts never get a false notice.

Generated with [Linear](https://linear.app/myxstack/issue/GRV-196/agent-completiontruth-gate-is-permanently-unsatisfiable-for-any-pr#agent-session-8e5d62f0)

Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>

* fix(ci): require a linked issue before arming the truth gate

The truth gate scores a pull request against the frozen intent snapshot on
its linked issue. That snapshot is only ever written by
snapshot-agent-task-intent, which runs on `issues` events alone -- it never
runs on pull_request_target. So a pull request with no linked issue has no
snapshot, no declared agent_login and no declared run_id, which means
policy.agent_login, policy.run_id and issue.number can never be populated
and the verdict is permanently `invalid_payload` no matter what the author
does.

Arming that unsatisfiable state from a branch-name prefix made the check red
on pull requests that never had a contract to satisfy. It is red on merged
commits too: #1368, the current tip of main, merged with
agent-completion/truth-gate/pr-1368 failing on exactly this. A check that is
red on everything gates nothing and buries real failures, which is the
failure mode agent-completion-enforcement.yml already warns about in its own
comments.

Pull-side provenance now arms the gate only when a linked issue exists to
verify against. With none there is nothing to measure, so the verdict is
not_applicable rather than blocked. This is not an escape hatch: a pull
request that links a dispatched issue is gated exactly as before, and the
requirement to bind a pull request to a focused issue at all is separately
owned by the `Canonical issue and evidence` check, which states a
requirement an author can actually meet.

Consolidates the two competing open implementations of this fix. #1364's
commit is cherry-picked here with authorship intact; #1154 carried the same
intent but had drifted to 117 files and 14k lines of unrelated changes.
Both should close in favour of this.

Full unit suite: 8079 passed, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YcHjCZ6pGn6A5BeeoZ6eZi

* chore(maintenance): audit all 337 remote branches and add a prune script

Classifies every remote branch using the one signal that stays honest after
the secret-purge force-push: whether it shares any ancestry with `main`.

    $ git merge-base origin/main origin/<branch>
    (empty)

275 of 337 branches return empty -- they predate the rewrite and no rebase
recovers them. The signals the branch-cleanup harness normally leans on all
mislead here, so they are deliberately not used:

  * `git merge-tree` calls these orphans a CLEAN merge; unrelated trees do
    not textually conflict, they would clobber.
  * A two-dot diff against an empty merge base silently degrades to a
    working-tree diff, which is why a two-line Dependabot bump measures as
    111 files / 15,650 lines.
  * The purge rewrote committer dates, so every branch reads as under 30
    days old and no staleness threshold ever fires.

Running the stock harness on this repo produced 263 REVIEW off those bad
signals. The ancestry test resolves the same set into:

    KEEP-OPEN-PR     28
    REVIEW-SHARED    29   real shared ancestry, no open PR -- not pruned
    CLOSE-MERGED      1   tip is an ancestor of main
    CLOSE-ORPHANED  275

The script prunes only the last two groups (276 branches) and archive-tags
each one first, verifying every tag is on the remote before deleting
anything. It defaults to a dry run.

That dry run caught the audit classifying `main` itself as CLOSE-MERGED --
`git merge-base --is-ancestor origin/main origin/main` is trivially true.
The row is removed, and a protected-ref guard plus a default-branch check
now abort regardless of what the CSV contains.

Not executed here: this session's credentials are scoped to one branch and
tag creation fails with HTTP 403, so the archive tags cannot be written.
Deleting without them would remove the only durable recovery path, so
nothing was deleted. Run the script with tag-write credentials.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YcHjCZ6pGn6A5BeeoZ6eZi

* docs(maintenance): correct prune-script header counts to match the CSV

The header summary read "CLOSE-MERGED 2 / 277 branches" while the shipped
docs/branch-audit-2026-08-05.csv has 1 CLOSE-MERGED and 276 prunable. The
header was written before the `main` row was dropped from the CSV and was
never updated.

Comment only -- the selection logic already read from the CSV, so the script
was correctly selecting 276 the whole time. Verified: header, CSV tally, and
the script's own dry-run count now all agree at 276.

Reported by the Vercel review bot on #1377.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YcHjCZ6pGn6A5BeeoZ6eZi

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

agent-completion/truth-gate is permanently unsatisfiable for any PR closing an [aw] issue

1 participant