From 87e36656ce30a2fc3a155190a6d5d87a3882a476 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 05:05:43 +0000 Subject: [PATCH] chore(fleet): deletion-guard PR-body marker, BotId::Oikosbot, rhodibot link fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-ups after the sustainabot→oikosbot extraction (#297). Matter 1 — Repo Integrity Guard: - Implement the documented-but-missing escape hatch: the guard now honours `[mass-delete-ok]` in the PR title/body (via env + grep -F, injection-safe), not only in commit messages. Accidental stale-base gut-merges are still blocked; only explicitly-marked intentional removals pass. The guard is NOT weakened (threshold and critical-file checks unchanged). - CLAUDE.md: new invariant #7 documenting the tripwire + marker convention. Matter 2b — fix rhodibot's broken sibling link: - `bots/rhodibot/README.adoc` linked the non-existent `hyperpolymath/sustainabot` repo. Repoint to the in-repo slot `bots/sustainabot/` and clarify it is the reserved fleet slot, NOT the standalone OikosBot (avoids re-conflation). Matter 2c (fleet side) — distinct fleet identity for OikosBot: - Add `BotId::Oikosbot` to shared-context so the optional `oikosbot-fleet` bridge can publish under its own identity instead of borrowing `BotId::Sustainabot`. Modelled on `Custom`: it is deliberately excluded from `BotId::all()`, so the 11-bot roster and coordinator dispatch are unchanged — OikosBot is an external publisher, not a managed roster bot. - shared-context: 84 tests pass. Note: robot-repo-automaton has a pre-existing, unrelated build error in src/detector.rs (regex `is_match` wants &str, gets &Vec); out of scope here. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01RozeeLxpJsd3WWFngaZWz3 --- .claude/CLAUDE.md | 7 +++++++ .github/workflows/repo-integrity-guard.yml | 13 +++++++++--- bots/rhodibot/README.adoc | 4 ++-- shared-context/src/bot.rs | 23 +++++++++++++++++++++- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 07fa1654..236ff51e 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -92,6 +92,13 @@ Control (report < 0.85) → Human review required full copy of OikosBot once lived in `bots/sustainabot/`; it was extracted to `hyperpolymath/oikosbot` and the slot reset to a placeholder. `oikos` is a separate DSL (`hyperpolymath/oikos-economics-accounting-dsl`). +7. **Mark intentional mass-deletions.** The Repo Integrity Guard + (`.github/workflows/repo-integrity-guard.yml`) fails any change that deletes + more than `MAX_DELETIONS` (50) tracked files vs. base — it exists because + `main` was once silently gutted from 1777 files to 2. For a *deliberate* + large removal, include the literal marker `[mass-delete-ok]` in a commit + message in the range, **or** in the PR title/body. Never weaken the guard or + trim its critical-file list just to get a change through. ## Repo health diff --git a/.github/workflows/repo-integrity-guard.yml b/.github/workflows/repo-integrity-guard.yml index 4702b0c7..8d10cc47 100644 --- a/.github/workflows/repo-integrity-guard.yml +++ b/.github/workflows/repo-integrity-guard.yml @@ -65,6 +65,9 @@ jobs: BASE_SHA: ${{ github.event.pull_request.base.sha }} BEFORE_SHA: ${{ github.event.before }} HEAD_SHA: ${{ github.sha }} + # Untrusted PR text — consumed only via env + grep -F (never eval'd). + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} run: | set -euo pipefail @@ -86,14 +89,18 @@ jobs: echo "Deleted tracked files vs $base: $deleted (limit ${MAX_DELETIONS})" # Intentional large removals: put [mass-delete-ok] in any commit - # message in the range (or the PR title/body). + # message in the range, or in the PR title/body. if git log --format='%B' "${base}..${HEAD_SHA}" | grep -qF '[mass-delete-ok]'; then - echo "Override marker [mass-delete-ok] present — tripwire bypassed by intent." + echo "Override marker [mass-delete-ok] present in a commit message — tripwire bypassed by intent." + exit 0 + fi + if printf '%s\n%s\n' "${PR_TITLE:-}" "${PR_BODY:-}" | grep -qF '[mass-delete-ok]'; then + echo "Override marker [mass-delete-ok] present in the PR title/body — tripwire bypassed by intent." exit 0 fi if [ "$deleted" -gt "${MAX_DELETIONS}" ]; then - echo "::error::$deleted files deleted vs base (limit ${MAX_DELETIONS}). If intentional, add [mass-delete-ok] to a commit message in this change. Refusing to let main be silently gutted again." + echo "::error::$deleted files deleted vs base (limit ${MAX_DELETIONS}). If intentional, add [mass-delete-ok] to a commit message OR the PR title/body. Refusing to let main be silently gutted again." git diff --diff-filter=D --name-only "$base" "$HEAD_SHA" | head -40 | sed 's/^/ deleted: /' exit 1 fi diff --git a/bots/rhodibot/README.adoc b/bots/rhodibot/README.adoc index 5851d7e6..b923496b 100644 --- a/bots/rhodibot/README.adoc +++ b/bots/rhodibot/README.adoc @@ -287,9 +287,9 @@ Rhodibot is a Tier 1 Verifier in the gitbot-fleet hierarchy: | Tier 1 Verifier | Sibling (mathematical/formal verification) -| https://github.com/hyperpolymath/sustainabot[sustainabot] +| link:../sustainabot/README.adoc[sustainabot] | Tier 1 Verifier -| Sibling (ecological/economic standards) +| Sibling (ecological/economic standards) — reserved fleet slot, _not_ the standalone OikosBot (`hyperpolymath/oikosbot`) | glambot | Tier 2 diff --git a/shared-context/src/bot.rs b/shared-context/src/bot.rs index 7d563b96..d8df54e2 100644 --- a/shared-context/src/bot.rs +++ b/shared-context/src/bot.rs @@ -30,6 +30,12 @@ pub enum BotId { Cipherbot, /// Targeted audit bot wrapping panic-attack static analysis Panicbot, + /// External ecological/economic code-analysis App (OikosBot, + /// `hyperpolymath/oikosbot`) that publishes findings via the optional + /// `oikosbot-fleet` bridge. NOT the reserved `Sustainabot` fleet slot and + /// NOT a fleet-managed roster bot — like `Custom`, it is deliberately + /// excluded from `all()` so the coordinator never dispatches it. + Oikosbot, /// Custom/external bot Custom(u32), } @@ -48,6 +54,7 @@ impl fmt::Display for BotId { BotId::Accessibilitybot => write!(f, "accessibilitybot"), BotId::Cipherbot => write!(f, "cipherbot"), BotId::Panicbot => write!(f, "panicbot"), + BotId::Oikosbot => write!(f, "oikosbot"), BotId::Custom(id) => write!(f, "custom-{}", id), } } @@ -57,7 +64,7 @@ impl BotId { /// Get the tier this bot belongs to pub fn tier(&self) -> Tier { match self { - BotId::Rhodibot | BotId::Echidnabot | BotId::Sustainabot | BotId::Panicbot => Tier::Verifier, + BotId::Rhodibot | BotId::Echidnabot | BotId::Sustainabot | BotId::Oikosbot | BotId::Panicbot => Tier::Verifier, BotId::Glambot | BotId::Seambot | BotId::Finishbot | BotId::Accessibilitybot => Tier::Finisher, BotId::Cipherbot => Tier::Specialist, BotId::RobotRepoAutomaton => Tier::Executor, @@ -99,6 +106,7 @@ impl BotId { "accessibilitybot" | "accessibility-bot" => Some(BotId::Accessibilitybot), "cipherbot" | "cipher-bot" => Some(BotId::Cipherbot), "panicbot" | "panic-bot" => Some(BotId::Panicbot), + "oikosbot" | "oikos-bot" => Some(BotId::Oikosbot), _ => None, } } @@ -354,6 +362,19 @@ impl BotInfo { can_fix: false, depends_on: vec![BotId::Rhodibot], }, + BotId::Oikosbot => Self { + id, + name: "OikosBot".to_string(), + description: "External ecological/economic code-analysis App (hyperpolymath/oikosbot) that publishes via the optional oikosbot-fleet bridge — distinct from the reserved Sustainabot slot".to_string(), + version: "0.1.0".to_string(), + categories: vec![ + "sustainability".to_string(), + "ecological".to_string(), + "economic".to_string(), + ], + can_fix: false, + depends_on: vec![], + }, BotId::Custom(_) => Self { id, name: "Custom Bot".to_string(),