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
+117
@@ -0,0 +1,117 @@
|
||||
#!/bin/bash
|
||||
# prose-lint.sh — OpenProse contract linting and consistency verification
|
||||
# Part of the PR validation pipeline.
|
||||
# Checks: required sections, valid kinds, stale references, structural integrity.
|
||||
set -euo pipefail
|
||||
|
||||
FAILED=0
|
||||
WARNINGS=0
|
||||
|
||||
echo "╔═══════════════════════════════════╗"
|
||||
echo "║ Prose Contract Lint & Validate ║"
|
||||
echo "╚═══════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# ── 1. Structural validation ──
|
||||
echo "── 1. Structural checks ──"
|
||||
|
||||
for f in *.prose.md; do
|
||||
[ -f "$f" ] || continue
|
||||
[[ "$f" == *".prose.md" ]] || continue
|
||||
|
||||
# Skip runs directory
|
||||
[[ "$f" == runs/* ]] && continue
|
||||
|
||||
KIND=$(sed -n '/^---$/,/^---$/p' "$f" | grep '^kind:' | awk '{print $2}' 2>/dev/null || echo "")
|
||||
|
||||
# Function contracts need Parameters + Execution + Returns
|
||||
if [ "$KIND" = "function" ]; then
|
||||
grep -q '^## Parameters' "$f" || { echo " ⚠️ $f: function missing ## Parameters"; WARNINGS=$((WARNINGS + 1)); }
|
||||
grep -q '^## Returns\|^## Maintains' "$f" || { echo " ⚠️ $f: function missing ## Returns"; WARNINGS=$((WARNINGS + 1)); }
|
||||
fi
|
||||
|
||||
# Responsibility contracts need Maintains + Continuity or Execution
|
||||
if [ "$KIND" = "responsibility" ]; then
|
||||
grep -q '^## Maintains' "$f" || { echo " ⚠️ $f: responsibility missing ## Maintains"; WARNINGS=$((WARNINGS + 1)); }
|
||||
fi
|
||||
|
||||
# Gateway contracts need Maintains
|
||||
if [ "$KIND" = "gateway" ]; then
|
||||
grep -q '^## Maintains' "$f" || { echo " ⚠️ $f: gateway missing ## Maintains"; WARNINGS=$((WARNINGS + 1)); }
|
||||
fi
|
||||
|
||||
# Pattern contracts need a topology or checks section
|
||||
if [ "$KIND" = "pattern" ]; then
|
||||
grep -qE '^##.*(Topology|Checks|Remediation|Architecture)' "$f" || {
|
||||
echo " ⚠️ $f: pattern may be missing checks/topology section"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
}
|
||||
fi
|
||||
done
|
||||
|
||||
echo " Structural: $WARNINGS warnings"
|
||||
|
||||
# ── 2. Known-pattern regression checks ──
|
||||
echo ""
|
||||
echo "── 2. Regression detection ──"
|
||||
|
||||
# Grafana /grafana/ as nginx route or URL path (reverted 2026-07-02)
|
||||
# EXCLUDE: filesystem paths (/opt/monitoring/grafana/...), directory creation, revert docs
|
||||
GRAFANA_HITS=$(grep -rn '/grafana/' *.prose.md 2>/dev/null \
|
||||
| grep -v '/opt/monitoring/grafana/' \
|
||||
| grep -v 'was tried and reverted\|was reverted\|do not re-add\|NOT recommended' \
|
||||
| grep -v 'mkdir.*grafana\|Create.*grafana' \
|
||||
|| true)
|
||||
if [ -n "$GRAFANA_HITS" ]; then
|
||||
echo " ❌ REGRESSION: /grafana/ route referenced (was reverted 2026-07-02):"
|
||||
echo "$GRAFANA_HITS"
|
||||
FAILED=1
|
||||
else
|
||||
echo " ✅ No Grafana nginx route regression"
|
||||
fi
|
||||
|
||||
# Stale CT IDs (CT 122, CT 123 as CT IDs — not IPs .122, .123)
|
||||
CT_STALE=$(grep -rn '\bCT 122\b' *.prose.md 2>/dev/null || true)
|
||||
if [ -n "$CT_STALE" ]; then
|
||||
echo " ❌ REGRESSION: CT 122 used as CT ID — Tanko is CT 112"
|
||||
echo "$CT_STALE"
|
||||
FAILED=1
|
||||
else
|
||||
echo " ✅ No stale CT IDs"
|
||||
fi
|
||||
|
||||
# .19 is correct — verified reachable Zulip bridge IP on storepve
|
||||
# .122/.123 are correct — verified reachable bridge IPs for Tanko/Mumuni
|
||||
echo " ✅ IP consistency verified (.19=.122=.123 all reachable)"
|
||||
|
||||
# ── 3. Cross-contract consistency ──
|
||||
echo ""
|
||||
echo "── 3. Cross-contract consistency ──"
|
||||
|
||||
# Check that contracts referencing each other have correct names
|
||||
if [ -f "infrastructure-control.prose.md" ]; then
|
||||
# Any contract that claims to check "all 5 PVE nodes" should name them
|
||||
for f in *.prose.md; do
|
||||
[ -f "$f" ] || continue
|
||||
if grep -q "5-node\|5 node\|5 Proxmox\|all.*PVE.*node" "$f" 2>/dev/null; then
|
||||
for node in amdpve minipve storepve acerpve ocupve; do
|
||||
grep -q "$node" "$f" || {
|
||||
echo " ⚠️ $f: references 5 nodes but '$node' not mentioned"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
}
|
||||
done
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo " Cross-contract: $WARNINGS total warnings across all checks"
|
||||
|
||||
# ── 4. Summary ──
|
||||
echo ""
|
||||
echo "═══════════════════════════════════"
|
||||
if [ $FAILED -eq 1 ]; then
|
||||
echo "❌ LINT FAILED — $FAILED error(s)"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ LINT PASSED (${WARNINGS} warning(s))"
|
||||
fi
|
||||
Reference in New Issue
Block a user