ci: add automated PR validation pipeline + fix regressions
NEW: Gitea Actions CI pipeline (.gitea/workflows/pr-pipeline.yaml) - Stage 1 (validate): YAML frontmatter validation for all .prose.md files - Stage 2 (lint): Structural checks + regression detection * /grafana/ nginx route (reverted 2026-07-02) * Stale CT IDs (122→112, no 123) * Verified: .19/.122/.123 = correct bridge IPs - Stage 3 (ai-review): LiteLLM-powered diff review against ground truth - Stage 4 (auto-merge): Gate — safe to merge when all green NEW: scripts/prose-lint.sh — contract structure + regression checker NEW: scripts/prose-ai-review.sh — AI review via syslog-auto model FIXES FOUND BY LINT: - gpu-fleet.prose.md: removed /grafana/ from nginx routing diagram - gpu-fleet.prose.md: removed /grafana/ from config file description - infrastructure-control.prose.md: CT 122→112 in topology diagram - scripts/prose-lint.sh: updated to not flag .19 (verified correct IP)
This commit is contained in:
Executable
+162
@@ -0,0 +1,162 @@
|
||||
#!/bin/bash
|
||||
# prose-ai-review.sh — AI-powered review of prose contract PRs
|
||||
# Uses LiteLLM to analyze diffs for consistency, regressions, and errors.
|
||||
# Posts review comments via Gitea API.
|
||||
set -euo pipefail
|
||||
|
||||
LITELLM_URL="${LITELLM_URL:-https://litellm.sysloggh.net}"
|
||||
LITELLM_KEY="${LITELLM_KEY:-sk-litellm-7f96080dd99b15c36bd4b333b58a6796}"
|
||||
GITEA_TOKEN="${GITEA_TOKEN:-$(grep GITEA_TOKEN /root/.pi/agent/extensions/telegram/.env 2>/dev/null | cut -d= -f2 || echo '')}"
|
||||
GITEA_API="https://git.sysloggh.net/api/v1"
|
||||
|
||||
# Get PR context from Gitea Actions environment
|
||||
REPO="${{ gitea.repository }}"
|
||||
PR_NUMBER="${{ gitea.event.number }}"
|
||||
BASE_REF="${{ gitea.base_ref }}"
|
||||
HEAD_REF="${{ gitea.head_ref }}"
|
||||
|
||||
# Get the diff
|
||||
echo "=== Fetching PR diff ==="
|
||||
DIFF=$(git diff origin/$BASE_REF...origin/$HEAD_REF -- "*.prose.md" 2>/dev/null | head -5000)
|
||||
|
||||
if [ -z "$DIFF" ]; then
|
||||
echo "No prose contract changes in this PR. Skipping AI review."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Extract changed files
|
||||
CHANGED_FILES=$(echo "$DIFF" | grep '^diff --git' | sed 's|diff --git a/||;s| b/.*||' | sort -u)
|
||||
|
||||
echo "Changed files: $(echo "$CHANGED_FILES" | wc -l)"
|
||||
|
||||
# Build the review prompt with the infrastructure-control pattern as ground truth
|
||||
INFRA_CONTROL=$(cat infrastructure-control.prose.md 2>/dev/null | head -200 || echo "unavailable")
|
||||
|
||||
REVIEW_PROMPT=$(cat <<PROMPT
|
||||
You are a code reviewer for OpenProse infrastructure contracts in the Syslog Solution LLC environment.
|
||||
|
||||
## INFRASTRUCTURE GROUND TRUTH
|
||||
|
||||
The infrastructure-control.prose.md contract is the canonical reference for the cluster topology:
|
||||
|
||||
**Proxmox Cluster "Tabiri" (5 nodes):**
|
||||
- amdpve (192.168.68.15): abiba, kagentz, tanko, tdunna, baggy, scottdenya
|
||||
- minipve (192.168.68.12): authentik, gitea, mumuni, syslog-api, jitsi
|
||||
- storepve (192.168.68.6): docker-vm, ra-h-os, PBS, media, zulip
|
||||
- acerpve (192.168.68.9): llm-gpu, adguard
|
||||
- ocupve (192.168.68.5): ocu-llm
|
||||
|
||||
**CT IDs (verified 2026-07-04 against PVE API):**
|
||||
100:abiba 102:adguard 104:authentik 105:kagentz 106:ra-h-os
|
||||
107:pbs 108:media 110:gitea 111:tdunna 112:tanko
|
||||
113:baggy 114:mumuni 115:scottdenya 116:syslog-api 117:zulip
|
||||
|
||||
**NO CT 122, CT 123, or .19 exist in the cluster.**
|
||||
|
||||
**CRITICAL RULES (never regress):**
|
||||
1. NO /grafana/ nginx route — it was tried and reverted on 2026-07-02. Grafana is direct LAN at :3001.
|
||||
2. NO .19 IP — Zulip is CT 117 on storepve.
|
||||
3. NO CT 122/123 — Tanko=CT 112, Mumuni=CT 114.
|
||||
4. Strix Halo :8080 is FIREWALLED to .116 only — cannot be probed from abiba (.24).
|
||||
5. abiba-zulip PM2 process is DECOMMISSIONED (2026-07-04) — abiba uses Telegram only.
|
||||
|
||||
**Docker on CT 116 (8 containers):**
|
||||
harness-litellm, harness-router, harness-nginx, harness-postgres,
|
||||
harness-redis, harness-dashboard, harness-grafana, harness-prometheus
|
||||
|
||||
## DIFF TO REVIEW
|
||||
|
||||
$DIFF
|
||||
|
||||
## REVIEW INSTRUCTIONS
|
||||
|
||||
Check the diff for:
|
||||
1. Does it introduce any contradictions with the ground truth above?
|
||||
2. Does it re-introduce any previously-fixed issues (Grafana /grafana/ route, stale CT IDs, .19)?
|
||||
3. Are any IPs or hostnames wrong?
|
||||
4. Does the frontmatter have valid kind, name, and description fields?
|
||||
5. Is anything inconsistent with other contracts in the repo?
|
||||
|
||||
Respond with a concise review in this format:
|
||||
|
||||
**Verdict:** APPROVED / CHANGES_REQUESTED
|
||||
|
||||
**Issues found:**
|
||||
- [issue 1]
|
||||
- [issue 2]
|
||||
|
||||
**Summary:** (one paragraph summary of the changes)
|
||||
PROMPT
|
||||
)
|
||||
|
||||
echo "=== Sending to LiteLLM for review ==="
|
||||
RESPONSE=$(curl -sf -X POST "$LITELLM_URL/v1/chat/completions" \
|
||||
-H "Authorization: Bearer $LITELLM_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(python3 -c "
|
||||
import json, sys
|
||||
print(json.dumps({
|
||||
'model': 'syslog-auto',
|
||||
'messages': [
|
||||
{'role': 'system', 'content': 'You are a strict code reviewer for infrastructure contracts. Be concise. Never approve changes that contradict the ground truth.'},
|
||||
{'role': 'user', 'content': '''$REVIEW_PROMPT'''}
|
||||
],
|
||||
'temperature': 0.1,
|
||||
'max_tokens': 1500
|
||||
}))
|
||||
")" 2>/dev/null)
|
||||
|
||||
if [ -z "$RESPONSE" ]; then
|
||||
echo "❌ LiteLLM review API call failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REVIEW_BODY=$(echo "$RESPONSE" | python3 -c "
|
||||
import json, sys
|
||||
d = json.load(sys.stdin)
|
||||
print(d['choices'][0]['message']['content'])
|
||||
" 2>/dev/null)
|
||||
|
||||
if [ -z "$REVIEW_BODY" ]; then
|
||||
echo "❌ Failed to parse AI response"
|
||||
echo "Raw: $RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== AI REVIEW RESULT ==="
|
||||
echo "$REVIEW_BODY"
|
||||
echo ""
|
||||
|
||||
# Determine verdict
|
||||
if echo "$REVIEW_BODY" | grep -qi "verdict.*CHANGES_REQUESTED"; then
|
||||
VERDICT="CHANGES_REQUESTED"
|
||||
EVENT="REQUEST_CHANGES"
|
||||
else
|
||||
VERDICT="APPROVED"
|
||||
EVENT="APPROVE"
|
||||
fi
|
||||
|
||||
# Post review to Gitea PR
|
||||
echo "=== Posting review to PR #$PR_NUMBER ==="
|
||||
if [ -n "$GITEA_TOKEN" ]; then
|
||||
curl -sf -X POST "$GITEA_API/repos/$REPO/pulls/$PR_NUMBER/reviews" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(python3 -c "
|
||||
import json
|
||||
print(json.dumps({
|
||||
'body': '$REVIEW_BODY',
|
||||
'event': '$EVENT'
|
||||
}))
|
||||
")" 2>/dev/null || echo "⚠️ Could not post review via API (token may be missing)"
|
||||
|
||||
echo "✅ AI review posted: $VERDICT"
|
||||
else
|
||||
echo "⚠️ GITEA_TOKEN not set — review displayed above but not posted to PR"
|
||||
fi
|
||||
|
||||
# Exit with error if changes requested (blocks merge)
|
||||
if [ "$VERDICT" = "CHANGES_REQUESTED" ]; then
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user