Skip to content

Commit 377403c

Browse files
ci(rhodibot): switch to the report-only canary (standards#759) (#80)
The RSR workflow in this repository is the **mutating** variant of rhodibot: it runs on a weekly cron with `contents: write` + `pull-requests: write`, deletes files by glob, and bulk-rewrites SPDX headers — which the standing licence policy forbids. It also interpolates `${{ steps.fix.outputs.FIXES }}` into a `run:` block (repo-derived filenames, so attacker-influenceable) and hardcodes a personal e-mail address. This replaces it with the **report-only canary** that the estate template already ships — the already-approved design, not a new one. Same weekly schedule, same drift signal, no mutation: it reports what an auto-fixer *would* have changed and fails the run when it finds drift, rather than editing anything. Licence/SPDX drift is reported for manual, owner-only correction; rhodibot must never edit a licence header. Part of the `standards#759` migration (canary propagation, option (a)). The workflow's `uses:` pins are unchanged, so `actions.lock` is unaffected.
1 parent 52edd61 commit 377403c

1 file changed

Lines changed: 65 additions & 207 deletions

File tree

‎.github/workflows/rhodibot.yml‎

Lines changed: 65 additions & 207 deletions
Original file line numberDiff line numberDiff line change
@@ -1,236 +1,94 @@
1-
# SPDX-License-Identifier: MPL-2.0
2-
# This workflow is managed by gh actions-lock.
31
# This workflow is managed by gh actions-lock.
4-
# rhodibot.yml — Automated RSR compliance enforcement
2+
# SPDX-License-Identifier: MPL-2.0
3+
# rhodibot.yml — RSR compliance CANARY (report-only)
54
#
6-
# Reads root-hygiene rules and auto-fixes what it can:
7-
# - Delete banned files (AI.djot, duplicate CONTRIBUTING.adoc, stale snapshots)
8-
# - Rename misnamed files (AI.a2ml → 0-AI-MANIFEST.a2ml)
9-
# - Fix SPDX headers (AGPL → MPL-2.0 in dotfiles)
10-
# - Create missing required files (SECURITY.md, CONTRIBUTING.md)
11-
# - Report unfixable issues as PR comments
5+
# Rhodibot does NOT mutate this repository. It never deletes, renames,
6+
# rewrites SPDX headers, creates files, or opens PRs. Instead it DETECTS
7+
# what an auto-fixer would have changed and reports it.
128
#
13-
# Runs weekly and on Hypatia scan completion.
14-
15-
name: "🤖 Rhodibot — RSR Auto-Fix"
9+
# Design intent (owner): if rhodibot "feels the desire to edit" — i.e. it
10+
# detects something it considers non-compliant — that is itself a MAJOR
11+
# WARNING. Either the repo has drifted, OR rhodibot's own rules have
12+
# diverged from the normative style it is meant to enforce. Both warrant
13+
# a human look, so the canary FAILS the run when it finds would-mutate
14+
# drift. Dangerous-pattern hits are advisory warnings only.
15+
#
16+
# Licence note: SPDX/licence drift is reported for MANUAL, owner-only
17+
# correction. Rhodibot must never edit a licence header (estate directive).
1618

19+
name: "\U0001F916 Rhodibot — RSR Compliance Canary"
1720
on:
1821
schedule:
19-
- cron: '0 6 * * 1' # Every Monday at 06:00 UTC
20-
workflow_dispatch: # Manual trigger
21-
workflow_run:
22-
workflows: ["Hypatia Neurosymbolic Analysis"]
23-
types: [completed]
22+
- cron: '0 6 * * 1' # Every Monday at 06:00 UTC
23+
workflow_dispatch: # Manual trigger
2424

25-
permissions:
26-
contents: write
27-
pull-requests: write
25+
concurrency:
26+
group: ${{ github.workflow }}-${{ github.ref }}
27+
cancel-in-progress: true
2828

29+
permissions:
30+
contents: read
2931
jobs:
30-
rhodibot:
32+
canary:
3133
runs-on: ubuntu-latest
34+
timeout-minutes: 15
3235
steps:
3336
- name: Checkout
3437
uses: actions/checkout@v7.0.1
3538
with:
3639
fetch-depth: 1
37-
38-
- name: Rhodibot — Scan and Fix
39-
id: fix
40+
- name: Rhodibot — detect drift (no mutations)
4041
run: |
41-
set -euo pipefail
42-
FIXES=""
43-
ISSUES=""
44-
CHANGED=false
42+
set -uo pipefail
43+
DRIFT=0
44+
warn() { echo "::warning title=Rhodibot canary::$*"; DRIFT=$((DRIFT+1)); }
45+
note() { echo "::warning title=Rhodibot advisory::$*"; }
4546
46-
# --- 1. Delete banned files ---
47-
for pattern in "AI.djot" "NEXT_STEPS.md" "TODO.md" "NOTES.md" "TASKS.md"; do
48-
if [ -f "$pattern" ]; then
49-
rm "$pattern"
50-
FIXES="$FIXES\n- Deleted \`$pattern\` (superseded)"
51-
CHANGED=true
52-
fi
53-
done
47+
echo "## 🤖 Rhodibot canary — report only (no edits made)" >> "$GITHUB_STEP_SUMMARY"
5448
55-
# Delete stale snapshot files
49+
# --- would-DELETE: banned files ---
50+
for f in AI.djot NEXT_STEPS.md TODO.md NOTES.md TASKS.md; do
51+
[ -f "$f" ] && warn "banned file present: $f (an auto-fixer would delete it)"
52+
done
53+
# would-DELETE: stale snapshots
5654
for f in *-STATUS-*.md *-COMPLETION-*.md *-COMPLETE.md *-VERIFIED-*.md; do
57-
if [ -f "$f" ]; then
58-
rm "$f"
59-
FIXES="$FIXES\n- Deleted stale snapshot \`$f\`"
60-
CHANGED=true
61-
fi
55+
[ -f "$f" ] && warn "stale snapshot present: $f (would be deleted)"
6256
done
63-
64-
# --- 2. Rename misnamed files ---
57+
# would-RENAME: legacy manifest name
6558
if [ -f "AI.a2ml" ] && [ ! -f "0-AI-MANIFEST.a2ml" ]; then
66-
mv AI.a2ml 0-AI-MANIFEST.a2ml
67-
FIXES="$FIXES\n- Renamed \`AI.a2ml\` → \`0-AI-MANIFEST.a2ml\`"
68-
CHANGED=true
59+
warn "AI.a2ml present without 0-AI-MANIFEST.a2ml (would be renamed)"
6960
fi
70-
71-
# --- 3. Delete duplicate format files ---
72-
if [ -f "CONTRIBUTING.md" ] && [ -f "CONTRIBUTING.adoc" ]; then
73-
rm CONTRIBUTING.adoc
74-
FIXES="$FIXES\n- Deleted duplicate \`CONTRIBUTING.adoc\` (keeping .md for GitHub)"
75-
CHANGED=true
61+
# would-DELETE: duplicate community files
62+
[ -f "CONTRIBUTING.md" ] && [ -f "CONTRIBUTING.adoc" ] && warn "duplicate CONTRIBUTING.md + CONTRIBUTING.adoc (one would be removed)"
63+
if [ -f "README.md" ] && [ -f "README.adoc" ] && [ "$(wc -l < README.md)" -lt 5 ]; then
64+
warn "stub README.md alongside README.adoc (would be removed)"
7665
fi
77-
78-
if [ -f "README.md" ] && [ -f "README.adoc" ]; then
79-
# Only delete README.md if it's a stub (<5 lines)
80-
lines=$(wc -l < README.md)
81-
if [ "$lines" -lt 5 ]; then
82-
rm README.md
83-
FIXES="$FIXES\n- Deleted stub \`README.md\` (keeping .adoc)"
84-
CHANGED=true
85-
fi
86-
fi
87-
88-
# --- 4. Fix SPDX headers in dotfiles ---
66+
# SPDX drift — MANUAL owner-only fix, never auto-edited
8967
for dotfile in .gitignore .gitattributes .editorconfig; do
90-
if [ -f "$dotfile" ] && grep -q "AGPL-3.0" "$dotfile" 2>/dev/null; then
91-
sed -i 's/AGPL-3.0-or-later/MPL-2.0/g; s/AGPL-3.0/MPL-2.0/g' "$dotfile"
92-
FIXES="$FIXES\n- Fixed SPDX header in \`$dotfile\` (AGPL → MPL-2.0)"
93-
CHANGED=true
68+
if [ -f "$dotfile" ] && grep "AGPL-3.0" "$dotfile" 2>/dev/null | grep -v "AGPL-3.0-or-later" | grep -q .; then
69+
warn "$dotfile carries an AGPL-3.0 SPDX header; estate policy is MPL-2.0 — fix MANUALLY (owner-only, never auto-edited)"
9470
fi
9571
done
96-
97-
# --- 5. Create missing required files ---
98-
if [ ! -f "SECURITY.md" ]; then
99-
cat > SECURITY.md << 'SECEOF'
100-
<!-- SPDX-License-Identifier: MPL-2.0 -->
101-
# Security Policy
102-
103-
## Reporting a Vulnerability
104-
105-
**Email:** j.d.a.jewell@open.ac.uk
106-
107-
**Response timeline:**
108-
- Acknowledgement within 48 hours
109-
- Initial assessment within 7 days
110-
- Fix or mitigation within 90 days
111-
112-
**Safe harbour:** We will not pursue legal action against security researchers who follow responsible disclosure.
113-
SECEOF
114-
FIXES="$FIXES\n- Created missing \`SECURITY.md\`"
115-
CHANGED=true
116-
fi
117-
118-
if [ ! -f "CONTRIBUTING.md" ]; then
119-
cat > CONTRIBUTING.md << 'CONTEOF'
120-
<!-- SPDX-License-Identifier: MPL-2.0 -->
121-
# Contributing
122-
123-
1. Fork the repository
124-
2. Create a feature branch
125-
3. Ensure SPDX headers on all files
126-
4. Submit a pull request
127-
128-
**Author:** Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
129-
CONTEOF
130-
FIXES="$FIXES\n- Created missing \`CONTRIBUTING.md\`"
131-
CHANGED=true
132-
fi
133-
134-
# --- 6. Check for issues we can't auto-fix ---
135-
if [ ! -f "0-AI-MANIFEST.a2ml" ] && [ ! -f "AI.a2ml" ]; then
136-
ISSUES="$ISSUES\n- Missing AI manifest (0-AI-MANIFEST.a2ml)"
137-
fi
138-
139-
if [ ! -f "LICENSE" ] && [ ! -f "LICENSE.md" ] && [ ! -f "LICENSE.txt" ]; then
140-
ISSUES="$ISSUES\n- Missing LICENSE file"
141-
fi
142-
143-
if [ ! -f "README.adoc" ] && [ ! -f "README.md" ]; then
144-
ISSUES="$ISSUES\n- Missing README"
145-
fi
146-
147-
# Check for third-party fork (skip SPDX enforcement)
148-
if [ -f "LICENSE" ] && grep -q "multiple licenses\|LGPL\|Apache" LICENSE 2>/dev/null; then
149-
echo "FORK=true" >> $GITHUB_OUTPUT
150-
fi
151-
152-
# --- 7. Check dangerous patterns ---
153-
DANGEROUS=""
154-
for pattern in "believe_me" "assert_total" "Admitted" "sorry" "unsafeCoerce" "Obj.magic"; do
155-
count=$(grep -r "$pattern" --include='*.idr' --include='*.v' --include='*.lean' --include='*.hs' --include='*.ml' --include='*.res' . 2>/dev/null | grep -v node_modules | wc -l || echo 0)
156-
if [ "$count" -gt 0 ]; then
157-
DANGEROUS="$DANGEROUS\n- \`$pattern\`: $count occurrences"
158-
fi
72+
# would-CREATE: missing required files
73+
[ -f "SECURITY.md" ] || [ -f ".github/SECURITY.md" ] || warn "no SECURITY.md (would be created)"
74+
[ -f "CONTRIBUTING.md" ] || [ -f ".github/CONTRIBUTING.md" ] || warn "no CONTRIBUTING.md (would be created)"
75+
76+
# --- unfixable compliance gaps (also drift) ---
77+
[ -f "0-AI-MANIFEST.a2ml" ] || [ -f "AI.a2ml" ] || warn "missing AI manifest (0-AI-MANIFEST.a2ml)"
78+
[ -f "LICENSE" ] || [ -f "LICENSE.md" ] || [ -f "LICENSE.txt" ] || warn "missing LICENSE file"
79+
[ -f "README.adoc" ] || [ -f "README.md" ] || warn "missing README"
80+
81+
# --- advisory only: dangerous verification-bypass patterns ---
82+
for pattern in believe_me assert_total Admitted sorry unsafeCoerce Obj.magic; do
83+
count=$(grep -rl "$pattern" --include='*.idr' --include='*.v' --include='*.lean' --include='*.hs' --include='*.ml' --include='*.res' . 2>/dev/null | grep -v node_modules | wc -l || true)
84+
[ "$count" -gt 0 ] && note "verification-bypass pattern '$pattern' in $count file(s) (advisory)"
15985
done
16086
161-
# Output results
162-
echo "CHANGED=$CHANGED" >> $GITHUB_OUTPUT
163-
{
164-
echo "FIXES<<EOF"
165-
echo -e "$FIXES"
166-
echo "EOF"
167-
} >> $GITHUB_OUTPUT
168-
{
169-
echo "ISSUES<<EOF"
170-
echo -e "$ISSUES"
171-
echo "EOF"
172-
} >> $GITHUB_OUTPUT
173-
{
174-
echo "DANGEROUS<<EOF"
175-
echo -e "$DANGEROUS"
176-
echo "EOF"
177-
} >> $GITHUB_OUTPUT
178-
179-
- name: Create PR with fixes
180-
if: steps.fix.outputs.CHANGED == 'true'
181-
run: |
182-
git config user.name "rhodibot"
183-
git config user.email "rhodibot@hyperpolymath.dev"
184-
BRANCH="rhodibot/rsr-compliance-$(date +%Y%m%d)"
185-
git checkout -b "$BRANCH"
186-
git add -A
187-
git commit -m "fix(rhodibot): automated RSR compliance fixes
188-
189-
${{ steps.fix.outputs.FIXES }}
190-
191-
Co-Authored-By: rhodibot <rhodibot@hyperpolymath.dev>"
192-
193-
git push origin "$BRANCH"
194-
195-
BODY="## 🤖 Rhodibot — RSR Compliance Fixes
196-
197-
### Changes Made
198-
${{ steps.fix.outputs.FIXES }}
199-
"
200-
201-
if [ -n "${{ steps.fix.outputs.ISSUES }}" ]; then
202-
BODY="$BODY
203-
### Issues Found (manual fix needed)
204-
${{ steps.fix.outputs.ISSUES }}
205-
"
206-
fi
207-
208-
if [ -n "${{ steps.fix.outputs.DANGEROUS }}" ]; then
209-
BODY="$BODY
210-
### ⚠️ Dangerous Patterns Detected
211-
${{ steps.fix.outputs.DANGEROUS }}
212-
213-
_These bypass formal verification. See \`proven\` repo for alternatives._
214-
"
215-
fi
216-
217-
gh pr create \
218-
--title "🤖 Rhodibot: RSR compliance fixes" \
219-
--body "$BODY" \
220-
--base main \
221-
--head "$BRANCH"
222-
env:
223-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
224-
225-
- name: Report (no changes needed)
226-
if: steps.fix.outputs.CHANGED != 'true'
227-
run: |
228-
echo "✅ Repository is RSR-compliant. No fixes needed."
229-
if [ -n "${{ steps.fix.outputs.ISSUES }}" ]; then
230-
echo "⚠️ Issues found (manual fix needed):"
231-
echo -e "${{ steps.fix.outputs.ISSUES }}"
232-
fi
233-
if [ -n "${{ steps.fix.outputs.DANGEROUS }}" ]; then
234-
echo "⚠️ Dangerous patterns:"
235-
echo -e "${{ steps.fix.outputs.DANGEROUS }}"
87+
echo "" >> "$GITHUB_STEP_SUMMARY"
88+
if [ "$DRIFT" -gt 0 ]; then
89+
echo "🔴 **Canary tripped: $DRIFT would-mutate finding(s).** Either the repo drifted or rhodibot's rules diverged from the norm — investigate (no edits were made)." >> "$GITHUB_STEP_SUMMARY"
90+
echo "::error title=Rhodibot canary::$DRIFT would-mutate finding(s) detected — rhodibot wants to edit. Investigate; nothing was changed."
91+
exit 1
23692
fi
93+
echo "✅ Canary clean — rhodibot has no desire to edit. Repository matches the norm." >> "$GITHUB_STEP_SUMMARY"
94+
echo "✅ Rhodibot canary clean — no drift, no mutations."

0 commit comments

Comments
 (0)