-
Notifications
You must be signed in to change notification settings - Fork 52
162 lines (148 loc) · 6.08 KB
/
Copy pathcitation-audit.yml
File metadata and controls
162 lines (148 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
name: Citation Audit
# Enforces CLAUDE.md §10 "zero-hallucination" red line. Four gates run
# in dependency order — Gate N only runs if Gate 0..N-1 passed:
#
# 0. pytest on the auditor test suites themselves — if a regex / stopword
# tweak broke the auditors, all downstream verdicts are meaningless.
#
# 1. tools/audit_bib_duplicates.py — paper.bib must have no duplicate
# bib keys (biblatex error), no duplicate DOIs, no duplicate arXiv
# ids (same paper under two keys = citation drift).
#
# 2. tools/audit_bib_coverage.py — every ``[@bibkey]`` in src/ / docs/ /
# paper.md must resolve to a paper.bib entry (dangling = hard fail,
# would render as ``???`` in pandoc). Orphans (unused bib entries)
# are reported but do not fail CI — they're valuable methodological
# references that just haven't been wired back to docstrings yet.
#
# 3. tools/audit_citations.py — every arXiv / NBER / DOI reference in
# src/ and docs/ must match primary-source metadata (authors /
# year / title). Fails on MISMATCH or UNRESOLVED under --strict.
#
# Audit reports are uploaded as artifacts so reviewers can see the
# full verdict.
#
# Security: no untrusted PR inputs flow into `run:` commands — only the
# repo's own scripts under tools/ are executed.
on:
pull_request:
branches: [ main ]
paths:
- 'src/**'
- 'docs/**'
- 'paper.bib'
- 'paper.md'
- 'tools/audit_citations.py'
- 'tools/audit_bib_duplicates.py'
- 'tools/audit_bib_coverage.py'
- 'tools/suggest_bibkey_backfills.py'
- 'tests/test_audit_citations.py'
- 'tests/test_audit_bib_duplicates.py'
- 'tests/test_audit_bib_coverage.py'
- 'tests/test_suggest_bibkey_backfills.py'
- '.github/workflows/citation-audit.yml'
push:
branches: [ main ]
paths:
- 'src/**'
- 'docs/**'
- 'paper.bib'
- 'paper.md'
- 'tools/audit_citations.py'
- 'tools/audit_bib_duplicates.py'
- 'tools/audit_bib_coverage.py'
- 'tools/suggest_bibkey_backfills.py'
- 'tests/test_audit_citations.py'
- 'tests/test_audit_bib_duplicates.py'
- 'tests/test_audit_bib_coverage.py'
- 'tests/test_suggest_bibkey_backfills.py'
- '.github/workflows/citation-audit.yml'
workflow_dispatch:
# Opt into Node 24 runtime early for all JavaScript actions.
# GitHub's documented migration path (blog post 2025-09-19) before the
# hard cutover on 2026-06-02. Keeps existing action versions intact.
# Security: pure runner config, no user input.
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
concurrency:
group: citation-audit-${{ github.ref }}
cancel-in-progress: true
jobs:
audit:
name: Audit arXiv / NBER / DOI citations
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install certifi + pytest
# certifi for the citation auditor's HTTPS calls; pytest to run
# the auditor test suite that pins the §10 regex regressions.
run: python -m pip install --quiet certifi pytest
- name: Gate 0 — auditor test suite
# Fast (<2s) stdlib-only tests guarding DOI regex / Q-network
# stopword / parse_bib brace-balance / [@bibkey] extractor
# regressions. Runs BEFORE the live auditors so a broken auditor
# can't silently pass.
#
# -o 'addopts=' overrides the project-wide pyproject.toml addopts
# (which pull in --cov=statspai for local dev); the auditor tests
# are stdlib-only and don't need the full coverage toolchain.
#
# --noconftest skips tests/conftest.py, which pre-imports
# ``scipy.optimize`` to stabilise the PyO3 type registry for the
# full pytest session. The auditor tests are stdlib-only and
# never touch scipy, and this Gate-0 image deliberately doesn't
# install scipy to keep the image small.
run: |
python -m pytest \
tests/test_audit_citations.py \
tests/test_audit_bib_duplicates.py \
tests/test_audit_bib_coverage.py \
tests/test_suggest_bibkey_backfills.py \
--noconftest -o 'addopts=' -q
- name: Gate 1 — paper.bib duplicate auditor (strict)
id: bib_audit
# Stdlib only, no network. A broken bibliography invalidates
# downstream checks.
run: python tools/audit_bib_duplicates.py --strict
- name: Gate 2 — paper.bib coverage auditor (strict-dangling)
id: coverage_audit
# Stdlib only. Every ``[@bibkey]`` in src/ / docs/ / paper.md
# must resolve to a paper.bib entry — a dangling ref would
# render as ``???`` in pandoc's paper.md build.
#
# Orphans are deliberately NOT gated: paper.bib carries
# methodological references from JOSS submission that haven't
# been wired back to docstrings yet. Those will be retired as
# the bibkey graph is backfilled.
run: python tools/audit_bib_coverage.py --strict-dangling --hide-orphans
- name: Gate 3 — citation auditor (strict)
id: citation_audit
# Live verification against arXiv / NBER / Crossref. --strict:
# unresolved IDs fail alongside mismatches, so a typo that
# breaks primary-source lookup is caught early.
run: python tools/audit_citations.py --strict --out audit_report.md
- name: Upload citation audit report
if: always()
uses: actions/upload-artifact@v4
with:
name: citation-audit-report
path: audit_report.md
retention-days: 30
- name: Summarise to GitHub step summary
if: always()
run: |
{
echo "### Citation Audit"
echo ""
if [ -f audit_report.md ]; then
sed -n '3p' audit_report.md
fi
echo ""
echo "Full citation report is attached as the \`citation-audit-report\` artifact."
} >> "$GITHUB_STEP_SUMMARY"