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
154 changes: 154 additions & 0 deletions .github/workflows/pr-policy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,51 @@ on:
description: "Also run actionlint on the PR."
type: boolean
default: true
enforce-evidence:
description: |
Phase 1 default is false: the F2/F10 evidence parser runs but only
emits warnings to the job summary; malformed/missing evidence does
NOT fail the workflow. Phase 2+ callers may opt in by setting this
to true, in which case errors from scripts/parse-evidence.mjs hard-
fail the job.
type: boolean
default: false
workflow-library-ref:
description: |
Git ref (tag, branch, or SHA) of ArchonVII/github-workflows used to
source scripts/parse-evidence.mjs. MUST match the ref the caller
pins this reusable workflow to (e.g. @v1). Defaulting to v1 keeps
parser+workflow versioned together; callers using @main or a SHA
should pass workflow-library-ref to match. Source: PR #19 review
patch 2 (2026-05-20).
type: string
default: v1

jobs:
policy:
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
# Check out THIS reusable workflow's repo (ArchonVII/github-workflows)
# into a sibling path so the evidence-check step can dynamic-import
# scripts/parse-evidence.mjs. We deliberately don't check out the caller
# repo here — the existing policy step uses the GitHub API and doesn't
# need a working tree.
#
# Pin parser source to the same ref callers pin this reusable workflow
# to. Defaulting to v1 keeps parser+workflow versioned together;
# callers using @main or @sha should pass workflow-library-ref to
# match. Without an explicit ref, actions/checkout would default to
# the repository's default branch (main), creating "workflow from v1,
# parser from main" drift.
- name: Check out github-workflows for parser script
uses: actions/checkout@v4
with:
repository: ArchonVII/github-workflows
ref: ${{ inputs.workflow-library-ref }}
path: __github-workflows__

- name: Enforce ready-for-review PR policy
uses: actions/github-script@v7
env:
Expand Down Expand Up @@ -114,6 +152,122 @@ jobs:
core.setFailed(failures.join('\n'));
}

# ----------------------------------------------------------------------
# F2 + F10 evidence parser (amendment 2026-05-19).
#
# NOTE: Owner Maintenance Lane is direct-commit-only in Phase 1 and
# never produces a PR, so it cannot reach this workflow. No PR-path
# exemption for owner-maintenance is implemented here on purpose —
# do NOT add a label/author check without a corresponding policy
# update. The exemption lives upstream in the commit path.
# ----------------------------------------------------------------------
- name: Evidence check (warning-only by default)
uses: actions/github-script@v7
env:
DOC_EXT_LIST: ${{ inputs.doc-only-extensions }}
DOC_PREFIXES: ${{ inputs.doc-only-path-prefixes }}
ENFORCE_EVIDENCE: ${{ inputs.enforce-evidence }}
with:
script: |
const pr = context.payload.pull_request;
const summary = core.summary;

if (pr.draft) {
core.info('Draft PR; evidence parser is advisory.');
await summary
.addHeading('Evidence check (advisory)', 3)
.addRaw('Draft PR — evidence parser skipped.')
.write();
return;
}

// Share the doc-only detection with the existing policy step.
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
per_page: 100,
});
const docExtRe = new RegExp(`\\.(${process.env.DOC_EXT_LIST})$`, 'i');
const prefixes = process.env.DOC_PREFIXES
.split('\n')
.map((s) => s.trim())
.filter(Boolean);
const isDocFile = (p) => docExtRe.test(p) || prefixes.some((pre) => p.startsWith(pre));
if (files.length > 0 && files.every((f) => isDocFile(f.filename))) {
core.info('Doc-only PR; evidence parser skipped.');
await summary
.addHeading('Evidence check', 3)
.addRaw('Doc-only PR — evidence parser skipped.')
.write();
return;
}

// Fetch check-runs for the PR head SHA (parser authority for ci rows).
const checkRunsResp = await github.paginate(
github.rest.checks.listForRef,
{
owner: context.repo.owner,
repo: context.repo.repo,
ref: pr.head.sha,
per_page: 100,
},
);
const checkRuns = checkRunsResp.map((r) => ({
name: r.name,
completed_at: r.completed_at,
conclusion: r.conclusion,
}));

// Head commit time. context.payload.pull_request only carries the
// head SHA; fetch the commit to get its committer date.
const headCommit = await github.rest.git.getCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: pr.head.sha,
});
const headCommitTime = headCommit.data.committer.date;

// Dynamic-import the pure parser so this step has no module wiring.
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 result = parseEvidence(pr.body || '', {
headCommitTime,
now: new Date().toISOString(),
checkRuns,
});

await summary.addHeading('Evidence check', 3).write();
if (result.errors.length > 0) {
await summary
.addHeading('Errors', 4)
.addList(result.errors)
.write();
for (const e of result.errors) core.warning(`evidence: ${e}`);
}
if (result.warnings.length > 0) {
await summary
.addHeading('Warnings', 4)
.addList(result.warnings)
.write();
for (const w of result.warnings) core.warning(`evidence: ${w}`);
}
if (result.errors.length === 0 && result.warnings.length === 0) {
await summary.addRaw('All checked verification items have well-formed evidence.').write();
}

if (process.env.ENFORCE_EVIDENCE === 'true' && result.errors.length > 0) {
core.setFailed(`Evidence parser found ${result.errors.length} error(s).`);
} else if (result.errors.length > 0) {
core.info('enforce-evidence is false; reporting errors as warnings only.');
}

actionlint:
if: inputs.run-actionlint
runs-on: ubuntu-latest
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/self-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Self-test (scripts)

# Non-reusable workflow that runs the github-workflows repo's own unit
# tests when scripts/** changes on a PR or push to main. Other reusable
# workflows in this repo are workflow_call-only and don't run here.

on:
pull_request:
paths:
- "scripts/**"
- "package.json"
- "package-lock.json"
- ".github/workflows/self-test.yml"
push:
branches: [main]
paths:
- "scripts/**"
- "package.json"
- "package-lock.json"
- ".github/workflows/self-test.yml"

concurrency:
group: self-test-${{ github.ref }}
cancel-in-progress: true

jobs:
vitest:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"

- name: Install
run: npm ci

- name: Run vitest
run: npm test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
Loading
Loading