Skip to content

fix(ci): truth gate arms on any linked issue, not one that declares an agent contract — still unsatisfiable for agent-branch PRs #1401

Description

@groupthinking

Summary

#1377 fixed the case where agent-completion/truth-gate armed on a branch-name prefix with no linked issue. The same defect survives one level down: the gate arms on a linked issue that is not an agent task, and is then just as unsatisfiable.

agentTaskApplicable() (.github/workflows/pr-checks.yml, both copies) ends:

return login !== 'dependabot[bot]' &&
  (issueDispatch || (pullProvenance && Boolean(selectedIssue)));

issueDispatch is correct — it requires the agent-task label and declaresAgentContract(selectedIssue), i.e. the issue actually declares the run id and login the gate goes on to require.

The second disjunct is not. pullProvenance is true for any branch matching /^(?:agent|claude|codex|copilot|jules)[/-]/, and Boolean(selectedIssue) is true for any linked issue. So a claude/… branch that closes an ordinary issue arms the gate, which then demands policy.agent_login and policy.run_id. Those are only ever populated from the frozen intent snapshot written by snapshot-agent-task-intent, which runs on issues events for dispatched agent tasks. An ordinary issue has no snapshot, so the verdict is permanently invalid_payload no matter what the author does.

The inline comment above the return states the correct rule — "provenance arms the gate only once a linked issue exists to verify against" — but Boolean(selectedIssue) tests only that an issue exists, not that it carries anything to verify against.

Reproduction

Live on #1400 (branch claude/clever-heisenberg-8k227t, closes ordinary issue #1399):

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

The collector already diagnoses this precisely — linked_issue_not_agent_task and missing_intent_snapshot — the applicability predicate just does not consult it.

Replaying both predicates verbatim against #1400's PR and issue:

agentBranch           = true
pullProvenance        = true
declaresAgentContract = false
issueDispatch         = false
current  (pullProvenance && Boolean(selectedIssue))            -> applicable = true   # armed, unsatisfiable
proposed (pullProvenance && declaresAgentContract(selected))   -> applicable = false  # not_applicable

Proposed fix

 return login !== 'dependabot[bot]' &&
-  (issueDispatch || (pullProvenance && Boolean(selectedIssue)));
+  (issueDispatch || (pullProvenance && declaresAgentContract(selectedIssue)));

The predicate then reduces to declaresAgentContract(selectedIssue) && (agentTaskLabel || pullProvenance) — the gate arms exactly when a contract exists to measure against, which is what the comment already claims.

This narrows a permanently-failing check, not a working one. A PR linking a genuinely dispatched issue is gated exactly as before, because declaresAgentContract is the same function issueDispatch already relies on. Binding a PR to a focused issue remains separately enforced by Canonical issue and evidence and PR Governance.

Acceptance criteria

  • Both copies of agentTaskApplicable() use declaresAgentContract(selectedIssue) in place of Boolean(selectedIssue), and remain byte-identical to each other (the existing test asserts this).
  • A PR on an agent-prefixed branch closing an issue with no agent contract resolves not_applicable.
  • A PR closing an issue that declares run id + login is still fully gated (regression guard, both directions).
  • tests/unit/test_agent_completion_gate.py covers both directions in both assertion blocks.

Test paths

  • tests/unit/test_agent_completion_gate.py

Note on rollout

pull_request_target runs the workflow from the base branch, so — as with #1377 — the fix cannot green its own PR. It takes effect for everything else once merged.

Related

Activity

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

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions