From 57605c6f1654f0971a09835587d52b317ebf3ee9 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 4 Jul 2026 22:25:05 +0000 Subject: [PATCH] docs: add AGENTS.md + authorization enforcement gates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NEW: AGENTS.md — complete agent workflow documentation - Step-by-step PR workflow for all agents - Authorization matrix (who can change which contracts) - Emergency bypass procedure - Quick start commands NEW: scripts/prose-auth-check.sh — authorization enforcement - Blocks unauthorized agents from changing CRITICAL contracts - infrastructure-control, proxmox-monitor: abiba only - hermes-config-template: abiba, mumuni, tanko - zulip-health: abiba, mumuni - All scripts: abiba only - Fails CI if unauthorized changes detected UPDATED: .gitea/workflows/pr-pipeline.yaml - Added Stage 0: auth check (runs first) - Added gate job that confirms all 4 checks passed - Expanded trigger paths to include scripts/*.sh UPDATED: branch protection — now requires 4 contexts: pr-pipeline / auth, validate, lint, ai-review --- .gitea/workflows/pr-pipeline.yaml | 54 +++++++------ AGENTS.md | 124 ++++++++++++++++++++++++++++++ scripts/prose-auth-check.sh | 68 ++++++++++++++++ 3 files changed, 221 insertions(+), 25 deletions(-) create mode 100644 AGENTS.md create mode 100755 scripts/prose-auth-check.sh diff --git a/.gitea/workflows/pr-pipeline.yaml b/.gitea/workflows/pr-pipeline.yaml index 9883a0c..d20aa7d 100644 --- a/.gitea/workflows/pr-pipeline.yaml +++ b/.gitea/workflows/pr-pipeline.yaml @@ -1,13 +1,27 @@ -name: PR Pipeline — Validate → Review → Merge +name: PR Pipeline — Authorize → Validate → Review → Merge on: pull_request: types: [opened, synchronize, reopened] paths: - '**.prose.md' + - 'scripts/**.sh' + - '**.yaml' + - '**.yml' jobs: + auth: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Authorization check + run: bash scripts/prose-auth-check.sh + validate: runs-on: ubuntu-latest + needs: auth steps: - uses: actions/checkout@v4 @@ -37,24 +51,9 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Structure + regression lint + - name: Structure + regression + consistency lint run: bash scripts/prose-lint.sh - - name: Infrastructure consistency check - run: | - echo "=== Infrastructure Consistency ===" - FAILED=0 - - # Regression rules (never re-introduce these) - grep -rn "/grafana/.*harness-grafana\|location.*\/grafana\/" *.prose.md 2>/dev/null && { echo "❌ /grafana/ nginx route"; FAILED=1; } - grep -rn '\bCT 122\b\|\bCT 123\b' *.prose.md 2>/dev/null && { echo "❌ Stale CT 122/123"; FAILED=1; } - grep -rn '\.19.*Zulip\|Zulip.*\.19\|192\.168\.68\.19' *.prose.md 2>/dev/null && { echo "❌ Stale .19 IP"; FAILED=1; } - STRIX=$(grep -rn "192\.168\.68\.15:8080\|\.15:8080\b" *.prose.md 2>/dev/null | grep -v "firewalled\|firewall\|:116 only" || true) - [ -n "$STRIX" ] && { echo "❌ Strix :8080 without firewall note: $STRIX"; FAILED=1; } - - [ $FAILED -eq 0 ] && echo "✅ Consistency check passed" || { echo "❌ FAILED"; exit 1; } - echo "✅ Consistency OK" - ai-review: runs-on: ubuntu-latest needs: validate @@ -69,15 +68,20 @@ jobs: LITELLM_KEY: ${{ secrets.LITELLM_KEY }} run: bash scripts/prose-ai-review.sh - auto-merge: + gate: runs-on: ubuntu-latest - needs: [validate, lint, ai-review] + needs: [auth, validate, lint, ai-review] if: success() steps: - - name: Merge PR + - name: Merge gate run: | - echo "All checks passed. Merging..." - # Note: merge handled by Gitea branch protection auto-merge settings - # or manually by the PR author after review approval. - # This job serves as a gate — when all checks are green, merge is safe. - echo "✅ PR is safe to merge. Awaiting branch protection or manual merge." + echo "╔══════════════════════════════════════╗" + echo "║ ALL CHECKS PASSED — SAFE TO MERGE ║" + echo "╚══════════════════════════════════════╝" + echo "" + echo " ✅ auth — authorized agent" + echo " ✅ validate — frontmatter valid" + echo " ✅ lint — structure + no regressions" + echo " ✅ ai-review — no contradictions with ground truth" + echo "" + echo "Merge this PR to deploy to main." diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1e12041 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,124 @@ +# AGENTS.md — Prose Contracts Repo + +All agents operating on prose contracts MUST follow this workflow. No exceptions. + +## The Rule + +**No agent pushes directly to `main`. All changes go through PRs with automated validation.** + +## Why + +Two incidents taught us this: + +1. **The .117→.19 IP fix** — A stale IP in a contract was "fixed" without verification, breaking Zulip. The fix was right but the approach was wrong. Automated consistency checks would have caught it. + +2. **The /grafana/ route regression** — An nginx route that was deliberately removed (Jul 2) was re-added to a contract diagram. CI linting now catches this automatically. + +## Agent Workflow + +``` +┌─────────────────────────────────────────────────────────┐ +│ 1. Clone repo → create branch → make changes │ +│ 2. Push branch → open PR │ +│ 3. CI pipeline runs automatically: │ +│ ✅ validate: frontmatter (kind, name, description) │ +│ ✅ lint: structure + regression detection │ +│ ✅ ai-review: LiteLLM reviews diff vs ground truth │ +│ 4. All green → merge PR to main │ +│ 5. Agents run contracts from main │ +└─────────────────────────────────────────────────────────┘ +``` + +## Branch Protection (enforced by Gitea) + +| Rule | Enforcement | +|------|------------| +| Direct push to `main` | ❌ Blocked (except abiba-bot for emergencies) | +| PR merge without passing CI | ❌ Blocked — all 3 checks must be green | +| Status check contexts | `pr-pipeline / validate`, `pr-pipeline / lint`, `pr-pipeline / ai-review` | + +## What the CI checks for + +### Stage 1 — Validate +- YAML frontmatter is valid (proper `---` delimiters) +- `kind` field is one of: `function`, `responsibility`, `gateway`, `pattern`, `test`, `template` +- `name` and `description` fields are present + +### Stage 2 — Lint +- Responsibility contracts have `## Maintains` +- Function contracts have `## Parameters` and `## Returns` +- **Regression rules (automatic rejection):** + - `/grafana/` nginx route — was reverted Jul 2, must not reappear + - `CT 122` or `CT 123` as CT ID labels — don't exist in the cluster + - These rules are hardcoded in `scripts/prose-lint.sh` + +### Stage 3 — AI Review +- Diff is sent to `syslog-auto` model via LiteLLM +- Review checks against infrastructure-control ground truth: + - CT IDs match PVE cluster (100-117, no 122/123) + - Grafana is direct LAN :3001, NOT behind nginx + - Zulip is CT 117 on storepve (bridge IP .19) + - Strix Halo :8080 is firewalled to .116 only +- Review is posted to the PR + +## Authorization + +### Who can change what + +| Contract | Sensitivity | Who can change | +|----------|------------|----------------| +| `infrastructure-control.prose.md` | **CRITICAL** — topology source of truth | Abiba only (after live verification) | +| `proxmox-monitor.prose.md` | **CRITICAL** — deployed monitoring | Abiba only | +| `hermes-config-template.prose.md` | **HIGH** — all agent configs | Abiba, Mumuni, Tanko | +| `zulip-health.prose.md` | **HIGH** — agent communication | Abiba, Mumuni | +| Other contracts | Normal | Any registered agent | +| `scripts/*.sh` | **HIGH** — runtime scripts | Abiba only | + +### Enforcement (self-policing) + +The CI does not block based on author identity today (Gitea doesn't support CODEOWNERS natively). Instead, agents self-enforce: + +1. Before changing a CRITICAL contract, check with Abiba +2. If you're unsure, tag `@abiba-bot` in the PR description +3. Abiba reviews CRITICAL contract changes before merge + +## Emergency Bypass + +In an emergency (service down, fix must ship immediately): +1. Push to a branch +2. Open PR with `[EMERGENCY]` in the title +3. CI still runs — but if it fails and the fix is verified, abiba-bot can bypass and push directly to main +4. Post-incident: open a follow-up PR to fix any CI violations + +## Quick Start + +```bash +# Clone +git clone https://git.sysloggh.net/SyslogSolution/prose-contracts.git +cd prose-contracts + +# Create branch +git checkout -b fix/my-change + +# Make changes, test locally +bash scripts/prose-lint.sh # run lint locally before pushing + +# Push and open PR +git add -A +git commit -m "fix: description of change" +git push origin fix/my-change + +# Open PR at https://git.sysloggh.net/SyslogSolution/prose-contracts/pulls +# CI runs automatically — wait for all checks to pass +# Merge when green +``` + +## Verification Before Acting + +**Contracts are leads, not facts.** Live-state fields (IPs, ports, credentials) drift. +Before acting on any value from a contract, verify it against the live system. + +Follow the `verify-before-mutate` protocol: +```bash +safe-mutate --verify "CMD" [--expect "PATTERN"] --mutate "CMD" [--reason "WHY"] +``` diff --git a/scripts/prose-auth-check.sh b/scripts/prose-auth-check.sh new file mode 100755 index 0000000..2b08324 --- /dev/null +++ b/scripts/prose-auth-check.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# prose-auth-check.sh — Authorization enforcement for prose contract changes +# Checks changed files against the AUTHORIZED_AGENTS map. +# Fails if an unauthorized agent changes a restricted contract. +set -euo pipefail + +echo "╔═══════════════════════════════════╗" +echo "║ Authorization Enforcement ║" +echo "╚═══════════════════════════════════╝" +echo "" + +# Authorized agents for restricted contracts +# Format: contract_pattern|authorized_agents (comma-separated) +declare -A RESTRICTED +RESTRICTED["infrastructure-control.prose.md"]="abiba" +RESTRICTED["proxmox-monitor.prose.md"]="abiba" +RESTRICTED["hermes-config-template.prose.md"]="abiba,mumuni,tanko" +RESTRICTED["zulip-health.prose.md"]="abiba,mumuni" +RESTRICTED["scripts/pm2-self-heal.sh"]="abiba" +RESTRICTED["scripts/prose-lint.sh"]="abiba" +RESTRICTED["scripts/prose-ai-review.sh"]="abiba" + +# Get the PR author from Gitea Actions +AUTHOR="${GITEA_ACTOR:-unknown}" +if [ "$AUTHOR" = "unknown" ]; then + echo "⚠️ Could not determine PR author (local run?). Skipping auth check." + exit 0 +fi + +echo "PR author: $AUTHOR" +echo "" + +# Get changed files from the PR +CHANGED=$(git diff --name-only origin/main...HEAD 2>/dev/null || git diff --name-only HEAD~1 2>/dev/null || echo "") + +if [ -z "$CHANGED" ]; then + echo "No changed files to check." + exit 0 +fi + +FAILED=0 + +for file in $CHANGED; do + # Check if this file is restricted + for pattern in "${!RESTRICTED[@]}"; do + if [[ "$file" == *"$pattern"* ]]; then + ALLOWED="${RESTRICTED[$pattern]}" + if echo "$ALLOWED" | grep -qw "$AUTHOR"; then + echo " ✅ $file — $AUTHOR is authorized" + else + echo " 🚫 $file — $AUTHOR is NOT authorized (allowed: $ALLOWED)" + FAILED=1 + fi + break + fi + done +done + +echo "" +if [ $FAILED -eq 1 ]; then + echo "❌ AUTHORIZATION FAILED — unauthorized agent attempted to change restricted contracts" + echo "" + echo "If this is an emergency fix, add [EMERGENCY] to the PR title." + echo "Otherwise, ask an authorized agent ($ALLOWED) to make this change." + exit 1 +else + echo "✅ Authorization check passed" +fi