Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion .github/workflows/pr-policy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,18 @@ jobs:
policy:
runs-on: ubuntu-latest
permissions:
# `pull-requests: read` — read PR body, files, draft state.
# `checks: read` — required by the evidence parser's `location: ci`
# handling, which calls `checks.listForRef` for the PR head SHA.
# `contents: read` — required to fetch the head commit via
# `git.getCommit` for the parser's freshness/now comparison, and
# for `actions/checkout` of this workflow's repo for the parser
# script. When `permissions:` is declared, anything unlisted
# becomes `none` — so both must be explicit. Source: PR #19
# adversarial review (2026-05-20), finding C2.
pull-requests: read
checks: read
contents: read
steps:
# Check out THIS reusable workflow's repo (ArchonVII/github-workflows)
# into a sibling path so the evidence-check step can dynamic-import
Expand All @@ -83,7 +94,20 @@ jobs:
# match. Without an explicit ref, actions/checkout would default to
# the repository's default branch (main), creating "workflow from v1,
# parser from main" drift.
# Skip the parser checkout for draft PRs — the evidence-check step
# early-returns for drafts (see the `pr.draft` block below) so the
# parser is never invoked, and the ~5-10s clone is pure waste.
#
# We do NOT also skip for doc-only PRs at this stage: doc-only
# detection requires `pulls.listFiles` (an API call) which can't
# be evaluated at `if:`-expression time without a preceding step.
# The doc-only short-circuit still fires inside the evidence step;
# we just pay the checkout cost. Future refactor: an explicit
# "classify PR" step that exports outputs for both subsequent
# steps to gate on. Source: PR #19 adversarial review
# (2026-05-20), finding M1.
- name: Check out github-workflows for parser script
if: github.event.pull_request.draft == false
uses: actions/checkout@v4
with:
repository: ArchonVII/github-workflows
Expand Down Expand Up @@ -167,6 +191,7 @@ jobs:
DOC_EXT_LIST: ${{ inputs.doc-only-extensions }}
DOC_PREFIXES: ${{ inputs.doc-only-path-prefixes }}
ENFORCE_EVIDENCE: ${{ inputs.enforce-evidence }}
WORKFLOW_LIBRARY_REF: ${{ inputs.workflow-library-ref }}
with:
script: |
const pr = context.payload.pull_request;
Expand Down Expand Up @@ -229,13 +254,34 @@ jobs:
const headCommitTime = headCommit.data.committer.date;

// Dynamic-import the pure parser so this step has no module wiring.
// Wrap the import in try/catch so a missing parser file (e.g.
// checkout skipped, or workflow-library-ref points at a pre-parser
// ref) degrades to a job-summary warning instead of an unhandled
// step failure. Phase 1 is warning-only — even when
// enforce-evidence is true, a missing parser is an ecosystem
// config problem (wrong ref/permissions) not a PR-author
// problem, so we still emit core.warning, never
// core.setFailed. Source: PR #19 adversarial review
// (2026-05-20), finding M2.
const path = require('path');
const url = require('url');
const parserPath = path.resolve(
process.env.GITHUB_WORKSPACE,
'__github-workflows__/scripts/parse-evidence.mjs',
);
const { parseEvidence } = await import(url.pathToFileURL(parserPath).href);
const workflowRef = process.env.WORKFLOW_LIBRARY_REF || '(unset)';
let parseEvidence;
try {
({ parseEvidence } = await import(url.pathToFileURL(parserPath).href));
} catch (err) {
const msg = `Could not load evidence parser at ${parserPath} (workflow-library-ref=${workflowRef}): ${err && err.message ? err.message : err}. Evidence check skipped.`;
core.warning(msg);
await summary
.addHeading('Evidence check (skipped)', 3)
.addRaw(msg)
.write();
return;
}

const result = parseEvidence(pr.body || '', {
headCommitTime,
Expand Down
12 changes: 12 additions & 0 deletions examples/pr-policy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,21 @@ on:
pull_request:
types: [opened, edited, reopened, synchronize]

# `permissions:` here scopes the GITHUB_TOKEN passed into the reusable
# workflow. The reusable workflow itself sets a stricter job-level block,
# but a reusable workflow can never grant itself MORE than the caller
# grants — so every scope the reusable workflow needs must also be
# present here:
# - pull-requests: read → read PR body / files / draft state
# - contents: read → checkout + `git.getCommit` for head commit time
# - checks: read → required by the F2/F10 evidence parser to look
# up CI check-runs for `location: ci` evidence
# rows via `checks.listForRef`. Source: PR #19
# adversarial review (2026-05-20), finding C2.
permissions:
pull-requests: read
contents: read
checks: read

jobs:
policy:
Expand Down