Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 58 additions & 164 deletions .github/workflows/rhodibot.yml
Original file line number Diff line number Diff line change
@@ -1,189 +1,83 @@
# SPDX-License-Identifier: MPL-2.0
# rhodibot.yml — Automated RSR compliance enforcement
# rhodibot.yml — AUDIT MODE (report-only)
#
# Reads root-hygiene rules and auto-fixes what it can:
# - Delete banned files (AI.djot, duplicate CONTRIBUTING.adoc, stale snapshots)
# - Rename misnamed files (AI.a2ml → 0-AI-MANIFEST.a2ml)
# - Fix SPDX headers (AGPL → PMPL in dotfiles)
# - Create missing required files (SECURITY.md, CONTRIBUTING.md)
# - Report unfixable issues as PR comments
#
# Runs weekly and on Hypatia scan completion.

name: "⚙ Auto: Rhodibot RSR auto-fix"
# rhodibot is temporarily in AUDIT MODE in this repo: it DETECTS RSR-compliance drift
# and LOGS what it WOULD do, but performs NO mutations (no delete / rename / SPDX edit /
# file create) and opens NO PR. Permissions are read-only. This neutralises the
# directive-blind inline auto-fix (including the guardless SPDX sed) pending
# convergence onto the deterministic gitbot-fleet engine. Tracked HIGH PRIORITY in
# gitbot-fleet. Per-repo policy: .machine_readable/bot_directives/rhodibot.a2ml.

name: "⚙ Auto: Rhodibot RSR audit (report-only)"
on:
schedule:
- cron: '0 6 * * 1' # Every Monday at 06:00 UTC
workflow_dispatch: # Manual trigger
workflow_run:
workflows: ["Hypatia Neurosymbolic Analysis"]
types: [completed]
workflow_dispatch:
permissions:
contents: write
pull-requests: write
contents: read
jobs:
rhodibot:
rhodibot-audit:
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 1
- name: Rhodibot — Scan and Fix
id: fix
- name: Rhodibot — AUDIT (detect + log; NO mutation, NO PR)
run: |
set -euo pipefail
FIXES=""
WOULD=""
ISSUES=""
CHANGED=false
note() { WOULD="$WOULD\n- $1"; echo "AUDIT — would: $1"; }
issue() { ISSUES="$ISSUES\n- $1"; echo "ISSUE: $1"; }

# --- 1. Delete banned files ---
for pattern in "AI.djot" "NEXT_STEPS.md" "TODO.md" "NOTES.md" "TASKS.md"; do
if [ -f "$pattern" ]; then
rm "$pattern"
FIXES="$FIXES\n- Deleted \`$pattern\` (superseded)"
CHANGED=true
fi
# 1. Banned files
for p in AI.djot NEXT_STEPS.md TODO.md NOTES.md TASKS.md; do
if [ -f "$p" ]; then note "delete banned file \`$p\`"; fi
done

# Delete stale snapshot files
# 2. Stale snapshots
for f in *-STATUS-*.md *-COMPLETION-*.md *-COMPLETE.md *-VERIFIED-*.md; do
if [ -f "$f" ]; then
rm "$f"
FIXES="$FIXES\n- Deleted stale snapshot \`$f\`"
CHANGED=true
fi
if [ -f "$f" ]; then note "delete stale snapshot \`$f\`"; fi
done

# --- 2. Rename misnamed files ---
if [ -f "AI.a2ml" ] && [ ! -f "0-AI-MANIFEST.a2ml" ]; then
mv AI.a2ml 0-AI-MANIFEST.a2ml
FIXES="$FIXES\n- Renamed \`AI.a2ml\` → \`0-AI-MANIFEST.a2ml\`"
CHANGED=true
fi

# --- 3. Delete duplicate format files ---
if [ -f "CONTRIBUTING.md" ] && [ -f "CONTRIBUTING.adoc" ]; then
rm CONTRIBUTING.adoc
FIXES="$FIXES\n- Deleted duplicate \`CONTRIBUTING.adoc\` (keeping .md for GitHub)"
CHANGED=true
fi

if [ -f "README.md" ] && [ -f "README.adoc" ]; then
# Only delete README.md if it's a stub (<5 lines)
lines=$(wc -l < README.md)
if [ "$lines" -lt 5 ]; then
rm README.md
FIXES="$FIXES\n- Deleted stub \`README.md\` (keeping .adoc)"
CHANGED=true
fi
fi

# --- 4. Fix SPDX headers in dotfiles ---
for dotfile in .gitignore .gitattributes .editorconfig; do
if [ -f "$dotfile" ] && grep -q "AGPL-3.0" "$dotfile" 2>/dev/null; then
sed -i 's/AGPL-3.0-or-later/MPL-2.0/g; s/AGPL-3.0/MPL-2.0/g' "$dotfile"
FIXES="$FIXES\n- Fixed SPDX header in \`$dotfile\` (AGPL → PMPL)"
CHANGED=true
# 3. Misnamed
if [ -f AI.a2ml ] && [ ! -f 0-AI-MANIFEST.a2ml ]; then note "rename \`AI.a2ml\` -> \`0-AI-MANIFEST.a2ml\`"; fi
# 4. Duplicate format files
if [ -f CONTRIBUTING.md ] && [ -f CONTRIBUTING.adoc ]; then note "remove duplicate \`CONTRIBUTING.adoc\`"; fi
if [ -f README.md ] && [ -f README.adoc ] && [ "$(wc -l < README.md)" -lt 5 ]; then note "remove stub \`README.md\`"; fi
# 5. SPDX drift in dotfiles — DETECT ONLY. Licence normalisation is the
# deterministic engine's job (bot_directives/rhodibot.a2ml); never auto-edit here.
for d in .gitignore .gitattributes .editorconfig; do
if [ -f "$d" ] && grep -q "AGPL-3.0" "$d" 2>/dev/null; then
note "flag SPDX drift in \`$d\` (AGPL-3.0 present) — DEFER to the deterministic engine; do NOT auto-edit"
fi
done

# --- 5. Create missing required files ---
if [ ! -f "SECURITY.md" ]; then
cat > SECURITY.md << 'SECEOF'
<!-- SPDX-License-Identifier: MPL-2.0 -->
# Security Policy

## Reporting a Vulnerability

**Email:** j.d.a.jewell@open.ac.uk

**Response timeline:**
- Acknowledgement within 48 hours
- Initial assessment within 7 days
- Fix or mitigation within 90 days

**Safe harbour:** We will not pursue legal action against security researchers who follow responsible disclosure.
SECEOF
FIXES="$FIXES\n- Created missing \`SECURITY.md\`"
CHANGED=true
fi

if [ ! -f "CONTRIBUTING.md" ]; then
cat > CONTRIBUTING.md << 'CONTEOF'
<!-- SPDX-License-Identifier: MPL-2.0 -->
# Contributing

1. Fork the repository
2. Create a feature branch
3. Ensure SPDX headers on all files
4. Submit a pull request

**Author:** Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
CONTEOF
FIXES="$FIXES\n- Created missing \`CONTRIBUTING.md\`"
CHANGED=true
fi

# --- 6. Check for issues we can't auto-fix ---
if [ ! -f "0-AI-MANIFEST.a2ml" ] && [ ! -f "AI.a2ml" ]; then
ISSUES="$ISSUES\n- Missing AI manifest (0-AI-MANIFEST.a2ml)"
fi

if [ ! -f "LICENSE" ] && [ ! -f "LICENSE.md" ] && [ ! -f "LICENSE.txt" ]; then
ISSUES="$ISSUES\n- Missing LICENSE file"
fi

if [ ! -f "README.adoc" ] && [ ! -f "README.md" ]; then
ISSUES="$ISSUES\n- Missing README"
fi

# Check for third-party fork (skip SPDX enforcement)
if [ -f "LICENSE" ] && grep -q "multiple licenses\|LGPL\|Apache" LICENSE 2>/dev/null; then
echo "FORK=true" >> $GITHUB_OUTPUT
fi

# --- 7. Check dangerous patterns ---
DANGEROUS=""
for pattern in "believe_me" "assert_total" "Admitted" "sorry" "unsafeCoerce" "Obj.magic"; do
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)
if [ "$count" -gt 0 ]; then
DANGEROUS="$DANGEROUS\n- \`$pattern\`: $count occurrences"
fi
# 6. Missing required files
if [ ! -f SECURITY.md ]; then note "create missing \`SECURITY.md\`"; fi
if [ ! -f CONTRIBUTING.md ]; then note "create missing \`CONTRIBUTING.md\`"; fi
# 7. Unfixable / report-only
if [ ! -f 0-AI-MANIFEST.a2ml ] && [ ! -f AI.a2ml ]; then issue "missing AI manifest (0-AI-MANIFEST.a2ml)"; fi
if [ ! -f LICENSE ] && [ ! -f LICENSE.md ] && [ ! -f LICENSE.txt ]; then issue "missing LICENSE file"; fi
if [ ! -f README.adoc ] && [ ! -f README.md ]; then issue "missing README"; fi
# 8. Dangerous patterns (soundness escape hatches)
for pat in believe_me assert_total Admitted sorry unsafeCoerce Obj.magic; do
c=$(grep -rl "$pat" --include='*.idr' --include='*.v' --include='*.lean' --include='*.hs' --include='*.ml' --include='*.res' . 2>/dev/null | grep -cv node_modules || true)
if [ "${c:-0}" -gt 0 ]; then issue "dangerous pattern \`$pat\` in ${c} file(s)"; fi
done

# Output results
echo "CHANGED=$CHANGED" >> $GITHUB_OUTPUT
{
echo "FIXES<<EOF"
echo -e "$FIXES"
echo "EOF"
} >> $GITHUB_OUTPUT
{
echo "ISSUES<<EOF"
echo -e "$ISSUES"
echo "EOF"
} >> $GITHUB_OUTPUT
{
echo "DANGEROUS<<EOF"
echo -e "$DANGEROUS"
echo "EOF"
} >> $GITHUB_OUTPUT
- name: Create PR with fixes
if: steps.fix.outputs.CHANGED == 'true'
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"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Report (no changes needed)
if: steps.fix.outputs.CHANGED != 'true'
run: |
echo "✅ Repository is RSR-compliant. No fixes needed."
if [ -n "${{ steps.fix.outputs.ISSUES }}" ]; then
echo "⚠️ Issues found (manual fix needed):"
echo -e "${{ steps.fix.outputs.ISSUES }}"
fi
if [ -n "${{ steps.fix.outputs.DANGEROUS }}" ]; then
echo "⚠️ Dangerous patterns:"
echo -e "${{ steps.fix.outputs.DANGEROUS }}"
fi
echo "## 🔎 Rhodibot — AUDIT MODE (report-only; NO changes made)"
echo ""
if [ -n "$WOULD" ]; then
echo "### Would auto-fix (SUPPRESSED in audit mode)"
echo -e "$WOULD"
else
echo "_No auto-fixes would be applied._"
fi
echo ""
if [ -n "$ISSUES" ]; then
echo "### Issues (manual / deterministic engine)"
echo -e "$ISSUES"
fi
} >> "$GITHUB_STEP_SUMMARY"
echo "Rhodibot audit complete (report-only). No mutations, no PR."
26 changes: 26 additions & 0 deletions .machine_readable/bot_directives/rhodibot.a2ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# SPDX-FileCopyrightText: © 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
# SPDX-License-Identifier: MPL-2.0
# Media-Type: application/vnd.bot-directive+a2ml
#
# Per-repo directive for rhodibot — the DETERMINISTIC, symbolic RSR verifier
# (gitbot-fleet/bots/rhodibot). rhodibot acts from hard-coded rules + these guards;
# it is NOT a neural agent, so the no-automated-licence-edits rule (which scopes to
# neural agents) does not forbid its bounded SPDX normalisation. Repo kind: software.
#
# OPERATING MODE (fleet-standard; ALL gitbot-fleet bots honour 'mode'):
# active — perform actions: mutate the repo / open PRs.
# audit — dry-run: do everything EXCEPT mutate; emit FULL forensic/debug output of
# what it WOULD have done to a log/artefact (a pretend run).
# passive — normal-mode reporting only: summarise repo state; no pretend-run forensics.
# ignore — skip this repo entirely; do not even scan.

schema_version = "1.0"
directive_type = "bot-directive"
bot = "rhodibot"
scope = "rsr-compliance"
mode = "audit"
enforcement = "full"
repo-kind = "software"
allow = ["metadata", "docs", "repo-structure checks", "policy validation", "SPDX normalisation to the repo's declared licences (code/config -> MPL-2.0; prose docs -> CC-BY-SA-4.0)"]
deny = ["destructive edits without approval", "relicensing third-party / vendored / forked files", "any SPDX change whose correct licence cannot be deterministically determined (leave untouched)", "converting AGPL or other deliberate (non-drift) licences"]
notes = "ACK: haec rhodibot directive loaded (mode=audit, FULL enforcement, software/language repo). While mode=audit rhodibot mutates nothing and only logs. When active: docs/metadata formatting + deterministic SPDX normalisation; never edit design/ or examples/ semantics without approval; leave undeterminable licences untouched."
Loading