You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
policy: a PR that edits an acceptance gate needs another person's signature #2054
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.
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.
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.
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.
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.
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.
Activity
HugoXK commentedon Nov 1, 2026
Lead review of the first tip (
#2060at2b42c2ec), recorded here so the contract survives the pull request's own history: two changes are required before a signature is asked for.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 tomerge-gate.mjson 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 againstpull.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.Tip SHA: adaad2a0(the local commit) while the pushed head is2b42c2ec; it must name the pushed head.Signer pool, checked against the API on 2026-11-01:
HugoXK(this pull request's author),BirfyandFei2411are allUseraccounts;judge()refuses the author and refusesBotaccounts. So the only workable signature on a pull request authored byHugoXKcomes fromBirfyorFei2411— each side can only co-sign the other's gate change. If that is not the intended policy, the list belongs inpolicy/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'stestjob — so a GitHub API outage reddenstests-okfor every pull request, not only the ones touching a gate file.HugoXK commentedon Nov 1, 2026
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.
judge()in.github/scripts/policy-sign.mjs(as of#2060at2b42c2ec). It reads the named comment's id, its author and itsuser.type, and never compares anything againstpull.head.sha.Policy-sign: <that comment id>in the body →Gate change signaturepasses. 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, soGate change signaturepasses on B as well. The signature a person gave for A now licenses B.pull.head.sha; every new tip needs a fresh signature.#2060carries 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.Bind a gate signature to the tip it signs (ThinkFlowLab#2054)
HugoXK commentedon Nov 1, 2026
Heartbeat
Written-by: FlyBot Lead
Still-working: 6abf694232ca4c0a928d98204711d7e2
At: 2026-11-01T16:49:07Z
HugoXK commentedon Nov 1, 2026
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
#2060is cut fromb99e6c98…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-okcomment 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.Keep the pull request object the API returns (ThinkFlowLab#2060)
HugoXK commentedon Nov 2, 2026
Release
Written-by: FlyBot Lead
Released: 6abf694232ca4c0a928d98204711d7e2
Reason: Work merged; issue closed.
At: 2026-11-02T02:15:05Z