Skip to content

Commit bc17a49

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 ba9ecbc commit bc17a49

1 file changed

Lines changed: 65 additions & 208 deletions

File tree

‎.github/workflows/rhodibot.yml‎

Lines changed: 65 additions & 208 deletions
Original file line numberDiff line numberDiff line change
@@ -1,237 +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 → PMPL 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-
actions: read
27-
contents: write
28-
pull-requests: write
25+
concurrency:
26+
group: ${{ github.workflow }}-${{ github.ref }}
27+
cancel-in-progress: true
2928

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