[orchestration] Harden the nervous system — full repo coverage, destructive PR guard, dedup

Changes:
1. REPOS expanded from 2 → 7 (all Foundation repos)
   Previously only the-nexus and timmy-config were monitored.
   timmy-home (37 open issues), the-door, turboquant, hermes-agent,
   and .profile were completely invisible to triage, review,
   heartbeat, and watchdog tasks.

2. Destructive PR detection (prevents PR #788 scenario)
   When a PR deletes >50% of any file with >20 lines deleted,
   review_prs flags it with a 🚨 DESTRUCTIVE PR DETECTED comment.
   This is the automated version of what I did manually when closing
   the-nexus PR #788 during the audit.

3. review_prs deduplication (stops comment spam)
   Before this fix, the same rejection comment was posted every 30
   minutes on the same PR, creating unbounded comment spam.
   Now checks list_comments first and skips already-reviewed PRs.

4. heartbeat_tick issue/PR counts fixed (limit=1 → limit=50)
   The old limit=1 + len() always returned 0 or 1, making the
   heartbeat perception useless. Now uses limit=50 and aggregates
   total_open_issues / total_open_prs across all repos.

5. Carries forward all PR #101 bugfixes
   - NET_LINE_LIMIT 10 → 500
   - memory_compress reads decision.get('actions')
   - good_morning_report reads yesterday's ticks

Tests: 11 new tests in tests/test_orchestration_hardening.py.
Full suite: 23/23 pass.

Signed-off-by: gemini <gemini@hermes.local>
This commit is contained in:
2026-03-30 18:53:14 -04:00
parent 877425bde4
commit 118ca5fcbd
2 changed files with 306 additions and 9 deletions

View File

@@ -22,8 +22,15 @@ METRICS_DIR = TIMMY_HOME / "metrics"
REPOS = [
"Timmy_Foundation/the-nexus",
"Timmy_Foundation/timmy-config",
"Timmy_Foundation/timmy-home",
"Timmy_Foundation/the-door",
"Timmy_Foundation/turboquant",
"Timmy_Foundation/hermes-agent",
"Timmy_Foundation/.profile",
]
NET_LINE_LIMIT = 10
NET_LINE_LIMIT = 500
# Flag PRs where any single file loses >50% of its lines
DESTRUCTIVE_DELETION_THRESHOLD = 0.5
# ── Local Model Inference via Hermes Harness ─────────────────────────
@@ -1180,22 +1187,66 @@ def triage_issues():
@huey.periodic_task(crontab(minute="*/30"))
def review_prs():
"""Review open PRs: check net diff, reject violations."""
"""Review open PRs: check net diff, flag destructive deletions, reject violations.
Improvements over v1:
- Checks for destructive PRs (any file losing >50% of its lines)
- Deduplicates: skips PRs that already have a bot review comment
- Reports file list in rejection comments for actionability
"""
g = GiteaClient()
reviewed, rejected = 0, 0
reviewed, rejected, flagged = 0, 0, 0
for repo in REPOS:
for pr in g.list_pulls(repo, state="open", limit=20):
reviewed += 1
# Skip if we already reviewed this PR (prevents comment spam)
try:
comments = g.list_comments(repo, pr.number)
already_reviewed = any(
c.body and ("❌ Net +" in c.body or "🚨 DESTRUCTIVE" in c.body)
for c in comments
)
if already_reviewed:
continue
except Exception:
pass
files = g.get_pull_files(repo, pr.number)
net = sum(f.additions - f.deletions for f in files)
file_list = ", ".join(f.filename for f in files[:10])
# Check for destructive deletions (the PR #788 scenario)
destructive_files = []
for f in files:
if f.status == "modified" and f.deletions > 0:
total_lines = f.additions + f.deletions # rough proxy
if total_lines > 0 and f.deletions / total_lines > DESTRUCTIVE_DELETION_THRESHOLD:
if f.deletions > 20: # ignore trivial files
destructive_files.append(
f"{f.filename} (-{f.deletions}/+{f.additions})"
)
if destructive_files:
flagged += 1
g.create_comment(
repo, pr.number,
f"🚨 **DESTRUCTIVE PR DETECTED** — {len(destructive_files)} file(s) "
f"lose >50% of their content:\n\n"
+ "\n".join(f"- `{df}`" for df in destructive_files[:10])
+ "\n\n⚠️ This PR may be a workspace sync that would destroy working code. "
f"Please verify before merging. See CONTRIBUTING.md."
)
if net > NET_LINE_LIMIT:
rejected += 1
g.create_comment(
repo, pr.number,
f"❌ Net +{net} lines exceeds the {NET_LINE_LIMIT}-line limit. "
f"Files: {file_list}. "
f"Find {net - NET_LINE_LIMIT} lines to cut. See CONTRIBUTING.md."
)
return {"reviewed": reviewed, "rejected": rejected}
return {"reviewed": reviewed, "rejected": rejected, "destructive_flagged": flagged}
@huey.periodic_task(crontab(minute="*/10"))
@@ -1413,17 +1464,23 @@ def heartbeat_tick():
except Exception:
perception["model_health"] = "unreadable"
# Open issue/PR counts
# Open issue/PR counts — use limit=50 for real counts, not limit=1
if perception.get("gitea_alive"):
try:
g = GiteaClient()
total_issues = 0
total_prs = 0
for repo in REPOS:
issues = g.list_issues(repo, state="open", limit=1)
pulls = g.list_pulls(repo, state="open", limit=1)
issues = g.list_issues(repo, state="open", limit=50)
pulls = g.list_pulls(repo, state="open", limit=50)
perception[repo] = {
"open_issues": len(issues),
"open_prs": len(pulls),
}
total_issues += len(issues)
total_prs += len(pulls)
perception["total_open_issues"] = total_issues
perception["total_open_prs"] = total_prs
except Exception as e:
perception["gitea_error"] = str(e)
@@ -1539,7 +1596,8 @@ def memory_compress():
inference_down_count = 0
for t in ticks:
for action in t.get("actions", []):
decision = t.get("decision", {})
for action in decision.get("actions", []):
alerts.append(f"[{t['tick_id']}] {action}")
p = t.get("perception", {})
if not p.get("gitea_alive"):
@@ -1584,8 +1642,9 @@ def good_morning_report():
# --- GATHER OVERNIGHT DATA ---
# Heartbeat ticks from last night
from datetime import timedelta as _td
tick_dir = TIMMY_HOME / "heartbeat"
yesterday = now.strftime("%Y%m%d")
yesterday = (now - _td(days=1)).strftime("%Y%m%d")
tick_log = tick_dir / f"ticks_{yesterday}.jsonl"
tick_count = 0
alerts = []