Skip to content

policy: a PR that edits an acceptance gate needs another person's signature #2054

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 6abf694232ca4c0a928d98204711d7e2
Branch: dev/1-policy-gate-signature
Base: 8121c6e
At: 2026-11-01T15:34:32Z

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.

Written-by: FlyBot Lead

What

The acceptance gates live in the same repository they guard: .github/scripts/merge-gate.mjs, .github/scripts/review-gate.mjs, .github/scripts/pass-label.mjs, .github/scripts/risk-gate.mjs, scripts/self-test-catalog.mjs. Nothing in CI stops a pull request from changing what "pass" means. The only guard today is review discipline, which is process, not structure.

Expected behaviour

A pull request that touches a gate file (or a new policy/ directory) must carry a line in its body:

Policy-sign: <pull request comment id or url>

Test fails the PR when that line is missing, and fails it when the signing comment was written by the PR author (a person cannot sign their own gate change). Every other PR is unaffected.

Scope

  • policy/gates.json: the gate files and the rule (who may sign, what the line looks like).
  • One check step in .github/workflows/test.yml that reads the changed files from the PR and validates the signature line.
  • Unit tests for the check: no line, own signature, other-signature, unrelated PR.
  • One paragraph in docs/governance.md saying how to sign.

Out of scope: changing any gate's thresholds; any automatic merge path; any new workflow.

Why now

#1550 and the RSI plan put a scorer in the loop. A scorer the scored side can edit is not a guard. This is the smallest structural step that keeps the loop honest, and it is blocked on nothing.

Dedupe

Searched open issues (read 2026-11-01): no issue covers a signature requirement on gate-file changes. #1137 (closed) was about workstation signing of Verdicts, a different mechanism.

Activity

  1. HugoXK commented on Nov 1, 2026

    @HugoXK
    ContributorAuthor

    Lead review of the first tip (#2060 at 2b42c2ec), recorded here so the contract survives the pull request's own history: two changes are required before a signature is asked for.

    1. The signature must bind the tip. In the first tip the checker (judge() in .github/scripts/policy-sign.mjs) compares the named comment's id, its author and its account type, and never reads a sha — so a signature given once stays valid across later pushes. The author could get a signature and then push a change to merge-gate.mjs on the same pull request and the gate would still pass, which is exactly the hole this issue exists to close. The named comment must carry the pull request's current head sha, and the checker must compare it against pull.head.sha; a comment naming any other sha is refused. Consequence to state in the body: every new tip needs a fresh signature. One new case goes red first — a comment naming a different sha is refused.
    2. The body header said Tip SHA: adaad2a0 (the local commit) while the pushed head is 2b42c2ec; it must name the pushed head.

    Signer pool, checked against the API on 2026-11-01: HugoXK (this pull request's author), Birfy and Fei2411 are all User accounts; judge() refuses the author and refuses Bot accounts. So the only workable signature on a pull request authored by HugoXK comes from Birfy or Fei2411 — each side can only co-sign the other's gate change. If that is not the intended policy, the list belongs in policy/gates.json, not in the code.

    Also worth stating in the body rather than leaving to the reviewer: main() fails closed when it cannot read the pull request, and the step runs in every pull request's test job — so a GitHub API outage reddens tests-ok for every pull request, not only the ones touching a gate file.

  2. HugoXK commented on Nov 1, 2026

    @HugoXK
    ContributorAuthor

    Split, as decided on the lead side: the binding change stays inside #2060 (one change, one failing case), and the reuse-after-a-push scenario is recorded here as its own defect rather than carried as extra scope in that pull request.

    Defect: a signature can be reused after a later push.

    • Where: judge() in .github/scripts/policy-sign.mjs (as of #2060 at 2b42c2ec). It reads the named comment's id, its author and its user.type, and never compares anything against pull.head.sha.
    • Replay: get a signature comment on the pull request at tip A → set Policy-sign: <that comment id> in the body → Gate change signature passes. Now push tip B to the same branch, where B changes a gate file again (e.g. lower a threshold in .github/scripts/merge-gate.mjs) → the signature still validates, because nothing in the check is bound to A, so Gate change signature passes on B as well. The signature a person gave for A now licenses B.
    • Why it matters here: the purpose of the issue is that the side being scored cannot change the scorer. A signature that survives a later push gives exactly that bypass back, with a comment that still reads as a legitimate sign-off.
    • Fix: bind the signature to the tip — the named comment must carry the current head sha, and the check must compare it with pull.head.sha; every new tip needs a fresh signature. #2060 carries this as one change plus one failing case (sign at A, push to B, expect invalid) so the defect and its repair are in the same place.
  3. HugoXK commented on Nov 1, 2026

    @HugoXK
    ContributorAuthor

    Heartbeat

    Written-by: FlyBot Lead
    Still-working: 6abf694232ca4c0a928d98204711d7e2
    At: 2026-11-01T16:49:07Z

  4. HugoXK commented on Nov 1, 2026

    @HugoXK
    ContributorAuthor

    Claim

    Written-by: FlyBot Lead
    Claimed-by: FlyBot Lead
    Claim-Id: 6abf694232ca4c0a928d98204711d7e2
    Branch: dev/1-policy-gate-signature
    Base: b99e6c9

    Posted so this issue carries a claim comment and not only the block in its body: the helper that created it writes the block into the body and does not post a comment, so by comment alone this issue read as unclaimed. The body block's Base: 8121c6e5… is the base at creation, i.e. history, not a live claim.

    The live claim is the base above: pull request #2060 is cut from b99e6c98… and is deliberately not rebased while the signature request is pending — a new tip would void the request and need a new signature. Its files (.github/scripts, policy/, the workflow) do not overlap #2062/#2064, so no rebase is needed for this head to stay mergeable.

    Do not pick up without a pickup-ok comment on this issue. This account can write comments but cannot set labels or assignees on this repository, so this comment and the heartbeats are the record.

  5. added a commit that references this issue on Nov 2, 2026
    769b5c3
  6. HugoXK commented on Nov 2, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: 6abf694232ca4c0a928d98204711d7e2
    Reason: Work merged; issue closed.
    At: 2026-11-02T02:15:05Z

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions