-
-
Notifications
You must be signed in to change notification settings - Fork 0
ci(rhodibot): switch to the report-only canary (standards#759) #104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+64
−205
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,235 +1,94 @@ | ||
| # This workflow is managed by gh actions-lock. | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
| # rhodibot.yml — Automated RSR compliance enforcement | ||
| # 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 | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| rhodibot: | ||
| canary: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 | ||
| 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 | ||
| 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 | ||
| # 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 | ||
|
|
||
| # --- 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' | ||
| <!-- 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 | ||
| # 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<<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" | ||
| 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 <rhodibot@hyperpolymath.dev>" | ||
|
|
||
| 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." | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: hyperpolymath/vcl-ut
Length of output: 8504
🏁 Script executed:
Repository: hyperpolymath/vcl-ut
Length of output: 13680
🤖 get_repo_knowledge executed:
get_repo_knowledge hyperpolymath/vcl-ut /tmp/coderabbit-repo-knowledge/hyperpolymath-vcl-ut-7b10900c/conventionsLength of output: 2765
Count logical lines instead of newline characters.
When both README files exist, a five-line
README.mdwithout a final newline produces4fromwc -l. The canary then reports valid content as a stub and fails the workflow. Useawkso the check counts logical lines.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents