Skip to content

feat: add the shared PR governance action - #48

Closed
Pigbibi wants to merge 2 commits into
mainfrom
governance/unified-action
Closed

Pigbibi wants to merge 2 commits into
mainfrom
governance/unified-action

Conversation

@Pigbibi

@Pigbibi Pigbibi commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

AI assistance

Codex and the Qoder agent wrote this change. The reviewer is Codex; any coding agent may submit a pull request.

What changed

Adds the organization-wide pr-governance action and its caller templates.

  • Owner identity comes from organization variables RISK_GATE_OWNER_ID / RISK_GATE_OWNER; committed policy files carry no account identity, and a missing configuration fails the check.
  • Fork and Dependabot review events are relayed by the permissionless PR review signal workflow and evaluated by a trusted workflow_run.
  • secret_scan.py no longer decodes git patch output as UTF-8, so repositories with non-UTF-8 history scan cleanly.
  • Review-thread collection asks only for fields the GraphQL Actor type exposes, so evaluations no longer fail with Evaluation failed.
  • The scanner keeps the baseline identity counting, artifact gating and pinned Gitleaks 8.30.1 install.

How verified

  • node --test six suites: 159 tests pass.
  • test_secret_scan.py: 40 tests pass against Gitleaks 8.30.1.
  • The fixed thread query returns without error against a real repository.
  • actionlint reports no errors on the templates or the test workflow.

Risk

R2: this is the trust anchor for consumer repositories. Consumer changes follow separately.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-07T13:02:36.616027Z 4ac348e New commits
🔒 Security Review ✅ Completed 2026-11-07T13:08:20.762546Z 4ac348e New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b62ff313d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pr-governance/scripts/runner.cjs Outdated
const data = await github.graphql(`query($owner:String!,$repo:String!,$number:Int!,$cursor:String) {
repository(owner:$owner,name:$repo) { pullRequest(number:$number) {
reviewThreads(first:100,after:$cursor) {
nodes { id isResolved isOutdated comments(first:1) { nodes { author { login databaseId } } } }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Query database IDs through concrete actor types

Every gate evaluation that reaches collect() sends this query, but author has the GraphQL Actor interface, whose documented fields do not include databaseId. GitHub rejects the query even when the PR has no review threads, so collection fails and the gate publishes errors instead of evaluating any PR. Request the numeric ID inside appropriate concrete-type fragments, or omit it and use the existing login fallback.

Useful? React with 👍 / 👎.

Comment on lines +39 to +40
if (context.eventName === 'pull_request_target' && context.payload?.action === 'labeled') {
return recordLabel({ github, context, core });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish label recording from label-triggered evaluation

When a clearing label is added, both record_label and its dependent evaluate job in templates/risk-gate.yml invoke this same entry point with mode: gate and the same event payload. Consequently, both calls return from this branch after recording the label; neither evaluates the PR or updates risk-gate. An otherwise mergeable high-risk PR remains blocked until another event, manual dispatch, or the six-hour sweep. Give recording a separate operation so the dependent evaluation actually runs.

AGENTS.md reference: AGENTS.md:L92-L92

Useful? React with 👍 / 👎.

if (context.eventName === 'push' || context.eventName === 'schedule') {
return (await openPullRequests()).map(pull => pull.number);
}
if (context.eventName === 'pull_request_target' || context.eventName === 'pull_request') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle the review events enabled by the caller workflow

The inspected templates/risk-gate.yml subscribes to both pull_request_review and pull_request_review_comment, but neither event is accepted here, so each reaches UNSUPPORTED_EVENT before evaluation or blocking-status publication. If Codex adds an unresolved review thread after a successful verdict, the old successful ai-review and risk-gate statuses remain until another supported event or the scheduled sweep, despite unresolvedReviewerThreads() being intended to block that PR. Route both events to their payload's PR number.

AGENTS.md reference: AGENTS.md:L92-L92

Useful? React with 👍 / 👎.

Comment on lines +43 to +44
const { policy, policyCommit, policySha } = await loadPolicy(github, context, branch);
return run({ github, context, core, policy, policySha, policyCommit, defaultBranch: branch });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Publish blocking statuses when trusted policy loading fails

If the policy API read fails or the trusted policy becomes invalid after this head previously received successful statuses, loadPolicy() throws before runner.run() is entered. This bypasses the runner's existing publishUnavailable() path and leaves the old successful required contexts untouched. The README's branch-protection setup requires the three published contexts, not the Evaluate PR governance job, so failing that job alone does not invalidate the previous verdict. Catch policy-loading failures and invoke the unavailable-policy handling so affected heads receive blocking statuses.

Useful? React with 👍 / 👎.

verify_range(root, base, head)
with tempfile.TemporaryDirectory(prefix='gitleaks-', dir=os.environ.get('RUNNER_TEMP')) as directory:
findings = scan(binary, 'git', root, Path(directory) / 'range.json',
config=config, log_opts=f'{base}..{head}')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include merge diffs in the required range secret scan

If a credential is introduced only while resolving a merge conflict, the range scan used by templates/gitleaks.yml misses it: this explicit log_opts replaces scan()'s merge-aware default with only the revision range. The pinned Gitleaks implementation appends those options to git log -p -U0, and the Git documentation states that “merge commits will not show a diff” without an explicit merge-diff option. A local conflicting-merge reproduction confirmed that the synthetic credential appears only after adding --diff-merges=first-parent. Preserve that option alongside the range so the required scan cannot pass a merge-only credential that the weekly history scan would later detect.

Useful? React with 👍 / 👎.

@Pigbibi

Pigbibi commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

用同内容的单提交 PR #49 取代:旧分支的首个提交主题超过 commitlint 的 72 字符上限,且内容仍是修复前的门禁实现。

@Pigbibi Pigbibi closed this Nov 7, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4ac348ed11

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +474 to +477
let review = risk.tier === 'R0'
? { provider: policy.reviewer.provider, state: 'success', reason: 'review_not_required', comment_id: null,
format: null, commit_sha: null, coverage: null, unresolved: null }
: reviewerEvidence(snapshot, policy, now);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the current-head review requirement for R0 changes

For an R0 prose-only change, this branch publishes successful ai-review and risk-gate statuses without inspecting the reviewer receipt—even if the newest receipt reports unresolved findings. Evaluating the supplied README fixture with no comments also returns allowed: true. This bypasses the current-head Codex review requirement in AGENTS.md and the README's stated requirement that R0 changes have a reviewer receipt. Evaluate reviewer evidence for R0 changes as well, rather than synthesizing a successful review.

AGENTS.md reference: AGENTS.md:L92-L92

Useful? React with 👍 / 👎.

history = before.statuses;
if (before.pr.head.sha !== head) throw new Error('Head changed during collection');
const dependabot = gate.dependabotEvidence(before, policy);
if (before.pr.base.sha !== policyCommit) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Verify that the reviewed head contains the live base

When the PR API reports the current target-branch tip in base.sha, this equality passes even if the PR head diverged before that tip and has not incorporated it. The later readback repeats the same comparison, so an existing head-only receipt can still produce successful governance statuses after the base advances. The canonical .github/workflows/risk-gate.yml explicitly checks this through headContainsBase() (lines 251–255), using the compare API's merge base; a repository-wide search finds no equivalent ancestry check in the new shared action. Restore that check against policyCommit before accepting reviewer evidence. Strict branch protection may independently block merging, but it does not make these published review verdicts accurate.

Useful? React with 👍 / 👎.

if mode == 'git':
# Plain `git log -p` hides merge diffs, so a credential added only while
# resolving a conflict would never appear in the history scan.
command.append('--log-opts=' + (log_opts or '--all --diff-merges=first-parent'))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Prevent source attributes from suppressing secret-scan patches

A repository can contain credentials.properties -diff in .gitattributes, causing both range and history scans to miss credentials in that file despite the trusted scanner configuration. Git documents that this produces “Binary files differ” instead of a textual patch, and the pinned Gitleaks implementation skips non-archive binary files. A local Git reproduction confirmed that the credential disappears from the exact patch command used here and reappears with --text; the existing history verification also succeeds because the suppressed diff is valid Git output. Prevent source-controlled attributes from hiding scan input, for example by forcing textual patches in both modes or scanning the underlying blobs.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant