Skip to content

Commit de674b7

Browse files
committed
ci(rhodibot): switch to the report-only canary (standards#759)
The RSR workflow here is the mutating variant: weekly cron, write permissions, glob deletes, a bulk SPDX `sed` sweep the licence policy forbids, a `${{ steps.fix.outputs.FIXES }}` injection sink, and a hardcoded personal e-mail. Replaced with the canary the template ships: same schedule, same drift signal, reports instead of mutating. Refs hyperpolymath/standards#759 (option (a), canary propagation).
1 parent 995e695 commit de674b7

1 file changed

Lines changed: 66 additions & 206 deletions

File tree

‎.github/workflows/rhodibot.yml‎

Lines changed: 66 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -1,234 +1,94 @@
1+
# This workflow is managed by gh actions-lock.
12
# SPDX-License-Identifier: MPL-2.0
2-
# rhodibot.yml — Automated RSR compliance enforcement
3+
# rhodibot.yml — RSR compliance CANARY (report-only)
34
#
4-
# Reads root-hygiene rules and auto-fixes what it can:
5-
# - Delete banned files (AI.djot, duplicate CONTRIBUTING.adoc, stale snapshots)
6-
# - Rename misnamed files (AI.a2ml → 0-AI-MANIFEST.a2ml)
7-
# - Fix SPDX headers (AGPL → MPL-2.0 in dotfiles)
8-
# - Create missing required files (SECURITY.md, CONTRIBUTING.md)
9-
# - 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.
108
#
11-
# Runs weekly and on Hypatia scan completion.
12-
13-
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).
1418

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

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

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