|
1 | 1 | # SPDX-License-Identifier: MPL-2.0 |
2 | | -# rhodibot.yml — Automated RSR compliance enforcement |
| 2 | +# rhodibot.yml — AUDIT MODE (report-only) |
3 | 3 | # |
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 → PMPL in dotfiles) |
8 | | -# - Create missing required files (SECURITY.md, CONTRIBUTING.md) |
9 | | -# - Report unfixable issues as PR comments |
10 | | -# |
11 | | -# Runs weekly and on Hypatia scan completion. |
12 | | - |
13 | | -name: "⚙ Auto: Rhodibot RSR auto-fix" |
| 4 | +# rhodibot is temporarily in AUDIT MODE in this repo: it DETECTS RSR-compliance drift |
| 5 | +# and LOGS what it WOULD do, but performs NO mutations (no delete / rename / SPDX edit / |
| 6 | +# file create) and opens NO PR. Permissions are read-only. This neutralises the |
| 7 | +# directive-blind inline auto-fix (including the guardless SPDX sed) pending |
| 8 | +# convergence onto the deterministic gitbot-fleet engine. Tracked HIGH PRIORITY in |
| 9 | +# gitbot-fleet. Per-repo policy: .machine_readable/bot_directives/rhodibot.a2ml. |
| 10 | + |
| 11 | +name: "⚙ Auto: Rhodibot RSR audit (report-only)" |
14 | 12 | on: |
15 | 13 | schedule: |
16 | 14 | - cron: '0 6 * * 1' # Every Monday at 06:00 UTC |
17 | | - workflow_dispatch: # Manual trigger |
18 | | - workflow_run: |
19 | | - workflows: ["Hypatia Neurosymbolic Analysis"] |
20 | | - types: [completed] |
| 15 | + workflow_dispatch: |
21 | 16 | permissions: |
22 | | - contents: write |
23 | | - pull-requests: write |
| 17 | + contents: read |
24 | 18 | jobs: |
25 | | - rhodibot: |
| 19 | + rhodibot-audit: |
26 | 20 | runs-on: ubuntu-latest |
27 | | - timeout-minutes: 15 |
| 21 | + timeout-minutes: 10 |
28 | 22 | steps: |
29 | 23 | - name: Checkout |
30 | 24 | uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 |
31 | 25 | with: |
32 | 26 | fetch-depth: 1 |
33 | | - - name: Rhodibot — Scan and Fix |
34 | | - id: fix |
| 27 | + - name: Rhodibot — AUDIT (detect + log; NO mutation, NO PR) |
35 | 28 | run: | |
36 | 29 | set -euo pipefail |
37 | | - FIXES="" |
| 30 | + WOULD="" |
38 | 31 | ISSUES="" |
39 | | - CHANGED=false |
| 32 | + note() { WOULD="$WOULD\n- $1"; echo "AUDIT — would: $1"; } |
| 33 | + issue() { ISSUES="$ISSUES\n- $1"; echo "ISSUE: $1"; } |
40 | 34 |
|
41 | | - # --- 1. Delete banned files --- |
42 | | - for pattern in "AI.djot" "NEXT_STEPS.md" "TODO.md" "NOTES.md" "TASKS.md"; do |
43 | | - if [ -f "$pattern" ]; then |
44 | | - rm "$pattern" |
45 | | - FIXES="$FIXES\n- Deleted \`$pattern\` (superseded)" |
46 | | - CHANGED=true |
47 | | - fi |
| 35 | + # 1. Banned files |
| 36 | + for p in AI.djot NEXT_STEPS.md TODO.md NOTES.md TASKS.md; do |
| 37 | + if [ -f "$p" ]; then note "delete banned file \`$p\`"; fi |
48 | 38 | done |
49 | | -
|
50 | | - # Delete stale snapshot files |
| 39 | + # 2. Stale snapshots |
51 | 40 | for f in *-STATUS-*.md *-COMPLETION-*.md *-COMPLETE.md *-VERIFIED-*.md; do |
52 | | - if [ -f "$f" ]; then |
53 | | - rm "$f" |
54 | | - FIXES="$FIXES\n- Deleted stale snapshot \`$f\`" |
55 | | - CHANGED=true |
56 | | - fi |
| 41 | + if [ -f "$f" ]; then note "delete stale snapshot \`$f\`"; fi |
57 | 42 | done |
58 | | -
|
59 | | - # --- 2. Rename misnamed files --- |
60 | | - if [ -f "AI.a2ml" ] && [ ! -f "0-AI-MANIFEST.a2ml" ]; then |
61 | | - mv AI.a2ml 0-AI-MANIFEST.a2ml |
62 | | - FIXES="$FIXES\n- Renamed \`AI.a2ml\` → \`0-AI-MANIFEST.a2ml\`" |
63 | | - CHANGED=true |
64 | | - fi |
65 | | -
|
66 | | - # --- 3. Delete duplicate format files --- |
67 | | - if [ -f "CONTRIBUTING.md" ] && [ -f "CONTRIBUTING.adoc" ]; then |
68 | | - rm CONTRIBUTING.adoc |
69 | | - FIXES="$FIXES\n- Deleted duplicate \`CONTRIBUTING.adoc\` (keeping .md for GitHub)" |
70 | | - CHANGED=true |
71 | | - fi |
72 | | -
|
73 | | - if [ -f "README.md" ] && [ -f "README.adoc" ]; then |
74 | | - # Only delete README.md if it's a stub (<5 lines) |
75 | | - lines=$(wc -l < README.md) |
76 | | - if [ "$lines" -lt 5 ]; then |
77 | | - rm README.md |
78 | | - FIXES="$FIXES\n- Deleted stub \`README.md\` (keeping .adoc)" |
79 | | - CHANGED=true |
80 | | - fi |
81 | | - fi |
82 | | -
|
83 | | - # --- 4. Fix SPDX headers in dotfiles --- |
84 | | - for dotfile in .gitignore .gitattributes .editorconfig; do |
85 | | - if [ -f "$dotfile" ] && grep -q "AGPL-3.0" "$dotfile" 2>/dev/null; then |
86 | | - sed -i 's/AGPL-3.0-or-later/MPL-2.0/g; s/AGPL-3.0/MPL-2.0/g' "$dotfile" |
87 | | - FIXES="$FIXES\n- Fixed SPDX header in \`$dotfile\` (AGPL → PMPL)" |
88 | | - CHANGED=true |
| 43 | + # 3. Misnamed |
| 44 | + if [ -f AI.a2ml ] && [ ! -f 0-AI-MANIFEST.a2ml ]; then note "rename \`AI.a2ml\` -> \`0-AI-MANIFEST.a2ml\`"; fi |
| 45 | + # 4. Duplicate format files |
| 46 | + if [ -f CONTRIBUTING.md ] && [ -f CONTRIBUTING.adoc ]; then note "remove duplicate \`CONTRIBUTING.adoc\`"; fi |
| 47 | + if [ -f README.md ] && [ -f README.adoc ] && [ "$(wc -l < README.md)" -lt 5 ]; then note "remove stub \`README.md\`"; fi |
| 48 | + # 5. SPDX drift in dotfiles — DETECT ONLY. Licence normalisation is the |
| 49 | + # deterministic engine's job (bot_directives/rhodibot.a2ml); never auto-edit here. |
| 50 | + for d in .gitignore .gitattributes .editorconfig; do |
| 51 | + if [ -f "$d" ] && grep -q "AGPL-3.0" "$d" 2>/dev/null; then |
| 52 | + note "flag SPDX drift in \`$d\` (AGPL-3.0 present) — DEFER to the deterministic engine; do NOT auto-edit" |
89 | 53 | fi |
90 | 54 | done |
91 | | -
|
92 | | - # --- 5. Create missing required files --- |
93 | | - if [ ! -f "SECURITY.md" ]; then |
94 | | - cat > SECURITY.md << 'SECEOF' |
95 | | - <!-- SPDX-License-Identifier: MPL-2.0 --> |
96 | | - # Security Policy |
97 | | -
|
98 | | - ## Reporting a Vulnerability |
99 | | -
|
100 | | - **Email:** j.d.a.jewell@open.ac.uk |
101 | | -
|
102 | | - **Response timeline:** |
103 | | - - Acknowledgement within 48 hours |
104 | | - - Initial assessment within 7 days |
105 | | - - Fix or mitigation within 90 days |
106 | | -
|
107 | | - **Safe harbour:** We will not pursue legal action against security researchers who follow responsible disclosure. |
108 | | - SECEOF |
109 | | - FIXES="$FIXES\n- Created missing \`SECURITY.md\`" |
110 | | - CHANGED=true |
111 | | - fi |
112 | | -
|
113 | | - if [ ! -f "CONTRIBUTING.md" ]; then |
114 | | - cat > CONTRIBUTING.md << 'CONTEOF' |
115 | | - <!-- SPDX-License-Identifier: MPL-2.0 --> |
116 | | - # Contributing |
117 | | -
|
118 | | - 1. Fork the repository |
119 | | - 2. Create a feature branch |
120 | | - 3. Ensure SPDX headers on all files |
121 | | - 4. Submit a pull request |
122 | | -
|
123 | | - **Author:** Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> |
124 | | - CONTEOF |
125 | | - FIXES="$FIXES\n- Created missing \`CONTRIBUTING.md\`" |
126 | | - CHANGED=true |
127 | | - fi |
128 | | -
|
129 | | - # --- 6. Check for issues we can't auto-fix --- |
130 | | - if [ ! -f "0-AI-MANIFEST.a2ml" ] && [ ! -f "AI.a2ml" ]; then |
131 | | - ISSUES="$ISSUES\n- Missing AI manifest (0-AI-MANIFEST.a2ml)" |
132 | | - fi |
133 | | -
|
134 | | - if [ ! -f "LICENSE" ] && [ ! -f "LICENSE.md" ] && [ ! -f "LICENSE.txt" ]; then |
135 | | - ISSUES="$ISSUES\n- Missing LICENSE file" |
136 | | - fi |
137 | | -
|
138 | | - if [ ! -f "README.adoc" ] && [ ! -f "README.md" ]; then |
139 | | - ISSUES="$ISSUES\n- Missing README" |
140 | | - fi |
141 | | -
|
142 | | - # Check for third-party fork (skip SPDX enforcement) |
143 | | - if [ -f "LICENSE" ] && grep -q "multiple licenses\|LGPL\|Apache" LICENSE 2>/dev/null; then |
144 | | - echo "FORK=true" >> $GITHUB_OUTPUT |
145 | | - fi |
146 | | -
|
147 | | - # --- 7. Check dangerous patterns --- |
148 | | - DANGEROUS="" |
149 | | - for pattern in "believe_me" "assert_total" "Admitted" "sorry" "unsafeCoerce" "Obj.magic"; do |
150 | | - 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) |
151 | | - if [ "$count" -gt 0 ]; then |
152 | | - DANGEROUS="$DANGEROUS\n- \`$pattern\`: $count occurrences" |
153 | | - fi |
| 55 | + # 6. Missing required files |
| 56 | + if [ ! -f SECURITY.md ]; then note "create missing \`SECURITY.md\`"; fi |
| 57 | + if [ ! -f CONTRIBUTING.md ]; then note "create missing \`CONTRIBUTING.md\`"; fi |
| 58 | + # 7. Unfixable / report-only |
| 59 | + if [ ! -f 0-AI-MANIFEST.a2ml ] && [ ! -f AI.a2ml ]; then issue "missing AI manifest (0-AI-MANIFEST.a2ml)"; fi |
| 60 | + if [ ! -f LICENSE ] && [ ! -f LICENSE.md ] && [ ! -f LICENSE.txt ]; then issue "missing LICENSE file"; fi |
| 61 | + if [ ! -f README.adoc ] && [ ! -f README.md ]; then issue "missing README"; fi |
| 62 | + # 8. Dangerous patterns (soundness escape hatches) |
| 63 | + for pat in believe_me assert_total Admitted sorry unsafeCoerce Obj.magic; do |
| 64 | + c=$(grep -rl "$pat" --include='*.idr' --include='*.v' --include='*.lean' --include='*.hs' --include='*.ml' --include='*.res' . 2>/dev/null | grep -cv node_modules || true) |
| 65 | + if [ "${c:-0}" -gt 0 ]; then issue "dangerous pattern \`$pat\` in ${c} file(s)"; fi |
154 | 66 | done |
155 | 67 |
|
156 | | - # Output results |
157 | | - echo "CHANGED=$CHANGED" >> $GITHUB_OUTPUT |
158 | | - { |
159 | | - echo "FIXES<<EOF" |
160 | | - echo -e "$FIXES" |
161 | | - echo "EOF" |
162 | | - } >> $GITHUB_OUTPUT |
163 | | - { |
164 | | - echo "ISSUES<<EOF" |
165 | | - echo -e "$ISSUES" |
166 | | - echo "EOF" |
167 | | - } >> $GITHUB_OUTPUT |
168 | 68 | { |
169 | | - echo "DANGEROUS<<EOF" |
170 | | - echo -e "$DANGEROUS" |
171 | | - echo "EOF" |
172 | | - } >> $GITHUB_OUTPUT |
173 | | - - name: Create PR with fixes |
174 | | - if: steps.fix.outputs.CHANGED == 'true' |
175 | | - run: "git config user.name \"rhodibot\"\ngit config user.email \"rhodibot@hyperpolymath.dev\"\nBRANCH=\"rhodibot/rsr-compliance-$(date +%Y%m%d)\"\ngit checkout -b \"$BRANCH\"\ngit add -A\ngit commit -m \"fix(rhodibot): automated RSR compliance fixes\n\n${{ steps.fix.outputs.FIXES }}\n\nCo-Authored-By: rhodibot <rhodibot@hyperpolymath.dev>\"\n\ngit push origin \"$BRANCH\"\n\nBODY=\"## \U0001F916 Rhodibot — RSR Compliance Fixes\n\n### Changes Made\n${{ steps.fix.outputs.FIXES }}\n\"\n\nif [ -n \"${{ steps.fix.outputs.ISSUES }}\" ]; then\n BODY=\"$BODY\n### Issues Found (manual fix needed)\n${{ steps.fix.outputs.ISSUES }}\n\"\nfi\n\nif [ -n \"${{ steps.fix.outputs.DANGEROUS }}\" ]; then\n BODY=\"$BODY\n### ⚠️ Dangerous Patterns Detected\n${{ steps.fix.outputs.DANGEROUS }}\n\n_These bypass formal verification. See \\`proven\\` repo for alternatives._\n\"\nfi\n\ngh pr create \\\n --title \"\U0001F916 Rhodibot: RSR compliance fixes\" \\\n --body \"$BODY\" \\\n --base main \\\n --head \"$BRANCH\"\n" |
176 | | - env: |
177 | | - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
178 | | - - name: Report (no changes needed) |
179 | | - if: steps.fix.outputs.CHANGED != 'true' |
180 | | - run: | |
181 | | - echo "✅ Repository is RSR-compliant. No fixes needed." |
182 | | - if [ -n "${{ steps.fix.outputs.ISSUES }}" ]; then |
183 | | - echo "⚠️ Issues found (manual fix needed):" |
184 | | - echo -e "${{ steps.fix.outputs.ISSUES }}" |
185 | | - fi |
186 | | - if [ -n "${{ steps.fix.outputs.DANGEROUS }}" ]; then |
187 | | - echo "⚠️ Dangerous patterns:" |
188 | | - echo -e "${{ steps.fix.outputs.DANGEROUS }}" |
189 | | - fi |
| 69 | + echo "## 🔎 Rhodibot — AUDIT MODE (report-only; NO changes made)" |
| 70 | + echo "" |
| 71 | + if [ -n "$WOULD" ]; then |
| 72 | + echo "### Would auto-fix (SUPPRESSED in audit mode)" |
| 73 | + echo -e "$WOULD" |
| 74 | + else |
| 75 | + echo "_No auto-fixes would be applied._" |
| 76 | + fi |
| 77 | + echo "" |
| 78 | + if [ -n "$ISSUES" ]; then |
| 79 | + echo "### Issues (manual / deterministic engine)" |
| 80 | + echo -e "$ISSUES" |
| 81 | + fi |
| 82 | + } >> "$GITHUB_STEP_SUMMARY" |
| 83 | + echo "Rhodibot audit complete (report-only). No mutations, no PR." |
0 commit comments