From dbc3f3f7c6bf4c67757b7d46a91bcaec7d91216b Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 19 Sep 2026 18:37:44 +0000 Subject: [PATCH] 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). --- .github/workflows/actions.lock | 2 +- .github/workflows/rhodibot.yml | 272 ++++++++------------------------- 2 files changed, 66 insertions(+), 208 deletions(-) diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index ff6ce6a..3c2e2c5 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -43,7 +43,7 @@ workflows: - 'actions/upload-artifact@v4.6.2' - 'softprops/action-gh-release@v2.5.0' '.github/workflows/rhodibot.yml': - - 'actions/checkout@v4.3.1' + - 'actions/checkout@v7.0.1' '.github/workflows/rsr-antipattern.yml': - 'actions/checkout@v6.0.2' '.github/workflows/runtime-policy.yml': diff --git a/.github/workflows/rhodibot.yml b/.github/workflows/rhodibot.yml index 17f3c04..29dc13e 100644 --- a/.github/workflows/rhodibot.yml +++ b/.github/workflows/rhodibot.yml @@ -1,236 +1,94 @@ -# SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. -# rhodibot.yml — Automated RSR compliance enforcement +# SPDX-License-Identifier: MPL-2.0 +# rhodibot.yml — RSR compliance CANARY (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 +# Rhodibot does NOT mutate this repository. It never deletes, renames, +# rewrites SPDX headers, creates files, or opens PRs. Instead it DETECTS +# what an auto-fixer would have changed and reports it. # -# Runs weekly and on Hypatia scan completion. - -name: "🤖 Rhodibot — RSR Auto-Fix" +# Design intent (owner): if rhodibot "feels the desire to edit" — i.e. it +# detects something it considers non-compliant — that is itself a MAJOR +# WARNING. Either the repo has drifted, OR rhodibot's own rules have +# diverged from the normative style it is meant to enforce. Both warrant +# a human look, so the canary FAILS the run when it finds would-mutate +# drift. Dangerous-pattern hits are advisory warnings only. +# +# Licence note: SPDX/licence drift is reported for MANUAL, owner-only +# correction. Rhodibot must never edit a licence header (estate directive). +name: "\U0001F916 Rhodibot — RSR Compliance Canary" on: schedule: - - cron: '0 6 * * 1' # Every Monday at 06:00 UTC - workflow_dispatch: # Manual trigger - workflow_run: - workflows: ["Hypatia Neurosymbolic Analysis"] - types: [completed] + - cron: '0 6 * * 1' # Every Monday at 06:00 UTC + workflow_dispatch: # Manual trigger -permissions: - contents: write - pull-requests: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read jobs: - rhodibot: + canary: runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Checkout - uses: actions/checkout@v4.3.1 + uses: actions/checkout@v7.0.1 with: fetch-depth: 1 - - - name: Rhodibot — Scan and Fix - id: fix + - name: Rhodibot — detect drift (no mutations) run: | - set -euo pipefail - FIXES="" - ISSUES="" - CHANGED=false + set -uo pipefail + DRIFT=0 + warn() { echo "::warning title=Rhodibot canary::$*"; DRIFT=$((DRIFT+1)); } + note() { echo "::warning title=Rhodibot advisory::$*"; } - # --- 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 - done + echo "## 🤖 Rhodibot canary — report only (no edits made)" >> "$GITHUB_STEP_SUMMARY" - # Delete stale snapshot files + # --- would-DELETE: banned files --- + for f in AI.djot NEXT_STEPS.md TODO.md NOTES.md TASKS.md; do + [ -f "$f" ] && warn "banned file present: $f (an auto-fixer would delete it)" + done + # would-DELETE: 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 + [ -f "$f" ] && warn "stale snapshot present: $f (would be deleted)" done - - # --- 2. Rename misnamed files --- + # would-RENAME: legacy manifest name 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 + warn "AI.a2ml present without 0-AI-MANIFEST.a2ml (would be renamed)" 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 + # would-DELETE: duplicate community files + [ -f "CONTRIBUTING.md" ] && [ -f "CONTRIBUTING.adoc" ] && warn "duplicate CONTRIBUTING.md + CONTRIBUTING.adoc (one would be removed)" + if [ -f "README.md" ] && [ -f "README.adoc" ] && [ "$(wc -l < README.md)" -lt 5 ]; then + warn "stub README.md alongside README.adoc (would be removed)" 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 --- + # SPDX drift — MANUAL owner-only fix, never auto-edited 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 + if [ -f "$dotfile" ] && grep "AGPL-3.0" "$dotfile" 2>/dev/null | grep -v "AGPL-3.0-or-later" | grep -q .; then + warn "$dotfile carries an AGPL-3.0 SPDX header; estate policy is MPL-2.0 — fix MANUALLY (owner-only, never auto-edited)" fi done - - # --- 5. Create missing required files --- - if [ ! -f "SECURITY.md" ]; then - cat > SECURITY.md << 'SECEOF' - - # 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' - - # 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 - 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 + # would-CREATE: missing required files + [ -f "SECURITY.md" ] || [ -f ".github/SECURITY.md" ] || warn "no SECURITY.md (would be created)" + [ -f "CONTRIBUTING.md" ] || [ -f ".github/CONTRIBUTING.md" ] || warn "no CONTRIBUTING.md (would be created)" + + # --- unfixable compliance gaps (also drift) --- + [ -f "0-AI-MANIFEST.a2ml" ] || [ -f "AI.a2ml" ] || warn "missing AI manifest (0-AI-MANIFEST.a2ml)" + [ -f "LICENSE" ] || [ -f "LICENSE.md" ] || [ -f "LICENSE.txt" ] || warn "missing LICENSE file" + [ -f "README.adoc" ] || [ -f "README.md" ] || warn "missing README" + + # --- advisory only: dangerous verification-bypass patterns --- + for pattern in believe_me assert_total Admitted sorry unsafeCoerce Obj.magic; do + 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) + [ "$count" -gt 0 ] && note "verification-bypass pattern '$pattern' in $count file(s) (advisory)" done - # Output results - echo "CHANGED=$CHANGED" >> $GITHUB_OUTPUT - { - echo "FIXES<> $GITHUB_OUTPUT - { - echo "ISSUES<> $GITHUB_OUTPUT - { - echo "DANGEROUS<> $GITHUB_OUTPUT - - - name: Create PR with fixes - if: steps.fix.outputs.CHANGED == 'true' - run: | - git config user.name "rhodibot" - git config user.email "rhodibot@hyperpolymath.dev" - BRANCH="rhodibot/rsr-compliance-$(date +%Y%m%d)" - git checkout -b "$BRANCH" - git add -A - git commit -m "fix(rhodibot): automated RSR compliance fixes - - ${{ steps.fix.outputs.FIXES }} - - Co-Authored-By: rhodibot " - - git push origin "$BRANCH" - - BODY="## 🤖 Rhodibot — RSR Compliance Fixes - - ### Changes Made - ${{ steps.fix.outputs.FIXES }} - " - - if [ -n "${{ steps.fix.outputs.ISSUES }}" ]; then - BODY="$BODY - ### Issues Found (manual fix needed) - ${{ steps.fix.outputs.ISSUES }} - " - fi - - if [ -n "${{ steps.fix.outputs.DANGEROUS }}" ]; then - BODY="$BODY - ### ⚠️ Dangerous Patterns Detected - ${{ steps.fix.outputs.DANGEROUS }} - - _These bypass formal verification. See \`proven\` repo for alternatives._ - " - fi - - gh pr create \ - --title "🤖 Rhodibot: RSR compliance fixes" \ - --body "$BODY" \ - --base main \ - --head "$BRANCH" - 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 }}" + echo "" >> "$GITHUB_STEP_SUMMARY" + if [ "$DRIFT" -gt 0 ]; then + 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" + echo "::error title=Rhodibot canary::$DRIFT would-mutate finding(s) detected — rhodibot wants to edit. Investigate; nothing was changed." + exit 1 fi + echo "✅ Canary clean — rhodibot has no desire to edit. Repository matches the norm." >> "$GITHUB_STEP_SUMMARY" + echo "✅ Rhodibot canary clean — no drift, no mutations."