Skip to content

Commit ca6c504

Browse files
hyperpolymathRhodibot canon update
andauthored
fix(rhodibot): stop demanding filenames the estate abandoned (#534)
The RSR required-file table predated the Markdown/TXT → AsciiDoc migration (#486, 2026-08), so rhodibot was demanding filenames the estate had deliberately moved away from. Measured against 269 estate repositories: | Demanded | Repos that have it | What they actually have | |---|---|---| | `LICENSE.txt` | **0/269** | `LICENSE` 269/269 | | `CONTRIBUTING.md` | 25/269 | `CONTRIBUTING.adoc` 255 | | `CODE_OF_CONDUCT.md` | 15/269 | `CODE_OF_CONDUCT.adoc` 240 | | `SECURITY.md` | 35/269 | `SECURITY.adoc` 250 | | lowercase `justfile` | 7/269 | `Justfile` 267 | | `.machine_readable/STATE.scm` | 1/269 | `.a2ml` spelling 13 | A bot whose stated job is "structural checks, directory layout" was telling essentially every repository to reintroduce the pre-migration filenames — i.e. to undo the AsciiDoc migration. ### What changed Required files now name the current convention, with the superseded spellings accepted as **alternates**, so a completed rename is never reported as a regression. `CheckDef` gains `alternates`, and `check_path_exists()` tries the canonical path first and only falls back, so the common case still costs one request. Adds `RSR-016` (`www/.well-known/security.txt`, canonical since #53) and `RSR-017` (legacy root `.well-known/`, remediation "move, do not delete"). Both advisory at minimal/standard while the stage-5 migration window is open. Rule IDs `RSR-002`…`RSR-015` are unchanged and still resolve from the old spellings, so existing reports and `.rsr.toml` overrides keep working. ### Two defects found while in here, both fixed 1. **`is_fixable()` advertised four auto-fixes with no template behind them.** It listed 7 files; `robot-repo-automaton` ships 3 templates, and `get_template_content()` falls through to `String::new()` for anything it does not recognise — so it would open PRs creating `.gitattributes`, `.gitignore`, `.claude/CLAUDE.md` and `.machine_readable/bot_directives` **empty**. `inbox-steward.yml` auto-merges PRs that pass CI. Narrowed to the three that have templates. 2. **No `SECURITY.adoc` arm in `get_template_content()`**, so the renamed check would also have produced an empty file. Added, sharing `SECURITY.tmpl`. ### Tests - rhodibot **50 passed / 0 failed** (was 45 passing / 3 failing). The 3 failures were the tests asserting the stale filenames — the stale contract written down in code — brought to the current canon. Two regression tests added: superseded spellings still pass, and the `www/` check is present and honestly fails when absent. - robot-repo-automaton **114 passed / 0 failed**. ### Caveat on deployment **This PR does not by itself change what the deployed bot does.** `deploy-bot-fleet.k9.ncl` sets `rhodibot.enabled = true` and points at `.github/workflows/rhodibot.yml`, which does not exist in this repo — the live instance is most likely a service under `deploy/systemd/`. Someone with host access needs to confirm where it runs. ### Root cause, not addressed `standards` has zero references to rhodibot; its rules are hardcoded in Rust, which is how they went seven months stale. The durable fix is to make the required-file table *data* with `standards` as the source of truth. Worth doing as a follow-up. Co-authored-by: Rhodibot canon update <bot-canon@example.invalid>
1 parent e3ea5aa commit ca6c504

5 files changed

Lines changed: 298 additions & 75 deletions

File tree

‎bots/rhodibot/src/fleet.rs‎

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,23 @@ fn rule_id(check_name: &str) -> String {
4848
// Map known checks to stable rule IDs
4949
match check_name {
5050
"README.adoc" => format!("{}-001", RULE_PREFIX),
51-
"LICENSE.txt" => format!("{}-002", RULE_PREFIX),
52-
"SECURITY.md" => format!("{}-003", RULE_PREFIX),
53-
"CONTRIBUTING.md" => format!("{}-004", RULE_PREFIX),
54-
"CODE_OF_CONDUCT.md" => format!("{}-005", RULE_PREFIX),
51+
"LICENSE.txt" | "LICENSE" => format!("{}-002", RULE_PREFIX),
52+
"SECURITY.md" | "SECURITY.adoc" => format!("{}-003", RULE_PREFIX),
53+
"CONTRIBUTING.md" | "CONTRIBUTING.adoc" => format!("{}-004", RULE_PREFIX),
54+
"CODE_OF_CONDUCT.md" | "CODE_OF_CONDUCT.adoc" => format!("{}-005", RULE_PREFIX),
5555
".claude/CLAUDE.md" => format!("{}-006", RULE_PREFIX),
56-
".machine_readable/STATE.scm" => format!("{}-007", RULE_PREFIX),
57-
".machine_readable/META.scm" => format!("{}-008", RULE_PREFIX),
58-
".machine_readable/ECOSYSTEM.scm" => format!("{}-009", RULE_PREFIX),
56+
".machine_readable/STATE.scm" | ".machine_readable/STATE.a2ml" => format!("{}-007", RULE_PREFIX),
57+
".machine_readable/META.scm" | ".machine_readable/META.a2ml" => format!("{}-008", RULE_PREFIX),
58+
".machine_readable/ECOSYSTEM.scm" | ".machine_readable/ECOSYSTEM.a2ml" => format!("{}-009", RULE_PREFIX),
5959
".github/workflows" => format!("{}-010", RULE_PREFIX),
6060
".editorconfig" => format!("{}-011", RULE_PREFIX),
6161
".gitattributes" => format!("{}-012", RULE_PREFIX),
6262
".gitignore" => format!("{}-013", RULE_PREFIX),
63-
"justfile" => format!("{}-014", RULE_PREFIX),
63+
"justfile" | "Justfile" => format!("{}-014", RULE_PREFIX),
6464
".machine_readable/bot_directives" => format!("{}-015", RULE_PREFIX),
65+
"0-AI-MANIFEST.a2ml" => format!("{}-016", RULE_PREFIX),
66+
"www/.well-known/security.txt" => format!("{}-017", RULE_PREFIX),
67+
".machine_readable/root-allow.txt" => format!("{}-018", RULE_PREFIX),
6568
"no-.bot_directives" => format!("{}-LEGACY-001", RULE_PREFIX),
6669
"license-type" => format!("{}-LIC-001", RULE_PREFIX),
6770
name if name.starts_with("no-") => {
@@ -75,21 +78,27 @@ fn rule_id(check_name: &str) -> String {
7578
fn suggestion_for(check_name: &str) -> Option<String> {
7679
match check_name {
7780
"README.adoc" => Some("Create a README.adoc file with project documentation".to_string()),
78-
"LICENSE.txt" => Some("Add a LICENSE.txt file with MPL-2.0".to_string()),
79-
"SECURITY.md" => Some("Add a SECURITY.md with vulnerability reporting instructions".to_string()),
80-
"CONTRIBUTING.md" => Some("Add a CONTRIBUTING.md with contribution guidelines".to_string()),
81-
"CODE_OF_CONDUCT.md" => Some("Add a CODE_OF_CONDUCT.md (Contributor Covenant recommended)".to_string()),
81+
"LICENSE.txt" | "LICENSE" => Some("Add a LICENSE file with MPL-2.0 (LICENSE is the current spelling)".to_string()),
82+
"SECURITY.adoc" | "SECURITY.md" => Some("Add a SECURITY.adoc with vulnerability reporting instructions".to_string()),
83+
"CONTRIBUTING.adoc" | "CONTRIBUTING.md" => Some("Add a CONTRIBUTING.adoc with contribution guidelines".to_string()),
84+
"CODE_OF_CONDUCT.adoc" | "CODE_OF_CONDUCT.md" => Some("Add a CODE_OF_CONDUCT.adoc (Contributor Covenant recommended)".to_string()),
8285
".claude/CLAUDE.md" => Some("Create .claude/CLAUDE.md with AI assistant instructions".to_string()),
83-
".machine_readable/STATE.scm" => Some("Add .machine_readable/STATE.scm with project state".to_string()),
84-
".machine_readable/META.scm" => Some("Add .machine_readable/META.scm with meta information".to_string()),
85-
".machine_readable/ECOSYSTEM.scm" => Some("Add .machine_readable/ECOSYSTEM.scm with ecosystem position".to_string()),
86+
".machine_readable/STATE.a2ml" | ".machine_readable/STATE.scm" => Some("Add .machine_readable/STATE.a2ml with project state".to_string()),
87+
".machine_readable/META.a2ml" | ".machine_readable/META.scm" => Some("Add .machine_readable/META.a2ml with meta information".to_string()),
88+
".machine_readable/ECOSYSTEM.a2ml" | ".machine_readable/ECOSYSTEM.scm" => Some("Add .machine_readable/ECOSYSTEM.a2ml with ecosystem position".to_string()),
8689
".github/workflows" => Some("Add GitHub Actions workflows in .github/workflows/".to_string()),
8790
".editorconfig" => Some("Add .editorconfig for consistent formatting".to_string()),
8891
".gitattributes" => Some("Add .gitattributes for line ending and diff config".to_string()),
8992
".gitignore" => Some("Add .gitignore for build artifacts".to_string()),
90-
"justfile" => Some("Add a justfile as the primary build system".to_string()),
93+
"Justfile" | "justfile" => Some("Add a Justfile as the primary build system".to_string()),
9194
".machine_readable/bot_directives" => Some("Create .machine_readable/bot_directives/ for bot configs".to_string()),
9295
"no-.bot_directives" => Some("Migrate legacy .bot_directives/ to .machine_readable/bot_directives/".to_string()),
96+
"0-AI-MANIFEST.a2ml" => Some("Add a 0-AI-MANIFEST.a2ml machine-readable manifest at the repository root".to_string()),
97+
"www/.well-known/security.txt" => Some("Create www/.well-known/security.txt; run scripts/migrate-wellknown-to-www.sh to move an existing root .well-known/".to_string()),
98+
".machine_readable/root-allow.txt" => Some("Add .machine_readable/root-allow.txt declaring the permitted root entries".to_string()),
99+
// Move, do not delete: the generic no- arm below would advise
100+
// removing the file, which loses live security-contact metadata.
101+
"no-.well-known/security.txt" => Some("Migrate the root .well-known/ to www/.well-known/ with scripts/migrate-wellknown-to-www.sh - move it, do not delete it".to_string()),
93102
"license-type" => Some("Set repository license to an approved type (MPL-2.0 recommended)".to_string()),
94103
name if name.starts_with("no-") => {
95104
let banned_file = name.strip_prefix("no-").unwrap_or(name);
@@ -106,10 +115,20 @@ fn suggestion_for(check_name: &str) -> Option<String> {
106115
/// fixable. Files without templates (CONTRIBUTING.md, CODE_OF_CONDUCT.md)
107116
/// would produce empty boilerplate and are excluded.
108117
fn is_fixable(check_name: &str) -> bool {
118+
// Must match the arms of robot-repo-automaton's get_template_content()
119+
// exactly. That function falls through to String::new() for anything it
120+
// does not recognise, so advertising a check as fixable without a
121+
// template behind it makes the automaton open a PR that creates the file
122+
// EMPTY - and inbox-steward auto-merges PRs that pass CI.
123+
//
124+
// This list was previously four entries wider than the templates
125+
// (.gitattributes, .gitignore, .claude/CLAUDE.md and
126+
// .machine_readable/bot_directives), which is how an empty-file PR could
127+
// reach a repository and be merged without anyone reading it.
109128
matches!(check_name,
110-
"SECURITY.md" |
111-
".editorconfig" | ".gitattributes" | ".gitignore" |
112-
".claude/CLAUDE.md" | ".machine_readable/bot_directives"
129+
"LICENSE" | "LICENSE.txt" |
130+
"SECURITY.adoc" | "SECURITY.md" |
131+
".editorconfig"
113132
)
114133
}
115134

0 commit comments

Comments
 (0)