#!/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 6 PVE nodes" should name them for f in *.prose.md; do [ -f "$f" ] || continue if grep -qE "\b5-node\b|\b5 node\b|\b5 Proxmox\b|all 5 PVE" "$f" 2>/dev/null; then echo " ⚠️ $f: still references 5-node cluster (migrated to 6 nodes 2026-07-20)" WARNINGS=$((WARNINGS + 1)) 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