diff --git a/AGENTS.md b/AGENTS.md index a12b61f..f17b287 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ all via cron and `claude -p`. The dispatcher executes formula-based operational tasks. > **Note:** The vault is being redesigned as a PR-based approval workflow on the -> ops repo (see issues #73-#77). See [docs/VAULT.md](docs/VAULT.md) for details. Old vault scripts are being removed. +> ops repo (see issues #73-#77). Old vault scripts are being removed. See `README.md` for the full architecture and `disinto-factory/SKILL.md` for setup. @@ -95,7 +95,6 @@ bash dev/phase-test.sh | Predictor | `predictor/` | Infrastructure pattern detection | [predictor/AGENTS.md](predictor/AGENTS.md) | > **Vault:** Being redesigned as a PR-based approval workflow (issues #73-#77). -> See [docs/VAULT.md](docs/VAULT.md) for the vault PR workflow details. See [lib/AGENTS.md](lib/AGENTS.md) for the full shared helper reference. diff --git a/README.md b/README.md index 40c9889..f6a7165 100644 --- a/README.md +++ b/README.md @@ -122,8 +122,6 @@ disinto/ │ └── (formula-driven) # run-planner.toml executed by dispatcher ├── vault/ │ └── vault-env.sh # Shared env setup (vault redesign in progress, see #73-#77) -├── docs/ -│ └── VAULT.md # Vault PR workflow and branch protection documentation └── supervisor/ ├── supervisor-poll.sh # Supervisor: health checks + claude -p ├── update-prompt.sh # Self-learning: append to best-practices @@ -148,7 +146,6 @@ disinto/ | **Planner** | Weekly | Updates AGENTS.md documentation to reflect recent code changes, then gap-analyses VISION.md vs current state and creates up to 5 backlog issues for the highest-leverage gaps. | > **Vault:** Being redesigned as a PR-based approval workflow (issues #73-#77). -> See [docs/VAULT.md](docs/VAULT.md) for the vault PR workflow and branch protection details. ## Design Principles diff --git a/docs/VAULT.md b/docs/VAULT.md deleted file mode 100644 index da2c1a9..0000000 --- a/docs/VAULT.md +++ /dev/null @@ -1,98 +0,0 @@ -# Vault PR Workflow - -This document describes the vault PR-based approval workflow for the ops repo. - -## Overview - -The vault system enables agents to request execution of privileged actions (deployments, token operations, etc.) through a PR-based approval process. This replaces the old vault directory structure with a more auditable, collaborative workflow. - -## Branch Protection - -The `main` branch on the ops repo (`johba/disinto-ops`) is protected via Forgejo branch protection to enforce: - -- **Require 1 approval before merge** — All vault PRs must have at least one approval from an admin user -- **Admin-only merge** — Only users with admin role can merge vault PRs (regular collaborators and bot accounts cannot) -- **Block direct pushes** — All changes to `main` must go through PRs - -### Protection Rules - -| Setting | Value | -|---------|-------| -| `enable_push` | `false` | -| `enable_force_push` | `false` | -| `enable_merge_commit` | `true` | -| `required_approvals` | `1` | -| `admin_enforced` | `true` | - -## Vault PR Lifecycle - -1. **Request** — Agent calls `lib/vault.sh:vault_request()` with action TOML content -2. **Validation** — TOML is validated against the schema in `vault/vault-env.sh` -3. **PR Creation** — A PR is created on `disinto-ops` with: - - Branch: `vault/` - - Title: `vault: ` - - Labels: `vault`, `pending-approval` - - File: `vault/actions/.toml` -4. **Approval** — Admin user reviews and approves the PR -5. **Execution** — Dispatcher (issue #76) polls for approved vault PRs and executes them -6. **Cleanup** — Executed vault items are moved to `fired/` (via PR) - -## Bot Account Behavior - -Bot accounts (dev-bot, review-bot, vault-bot, etc.) **cannot merge vault PRs** even if they have approval, due to the `admin_enforced` setting. This ensures: - -- Only human admins can approve sensitive vault actions -- Bot accounts can only create vault PRs, not execute them -- Manual admin review is always required for privileged operations - -## Setup - -To set up branch protection on the ops repo: - -```bash -# Source environment -source lib/env.sh -source lib/branch-protection.sh - -# Set up protection -setup_vault_branch_protection main - -# Verify setup -verify_branch_protection main -``` - -Or use the CLI directly: - -```bash -export FORGE_TOKEN="" -export FORGE_URL="https://codeberg.org" -export FORGE_OPS_REPO="johba/disinto-ops" - -# Set up protection -bash lib/branch-protection.sh setup main - -# Verify -bash lib/branch-protection.sh verify main -``` - -## Testing - -To verify the protection is working: - -1. **Bot cannot merge** — Attempt to merge a PR with a bot token (should fail with HTTP 405) -2. **Admin can merge** — Attempt to merge with admin token (should succeed) -3. **Direct push blocked** — Attempt `git push origin main` (should be rejected) - -## Related Issues - -- #73 — Vault redesign proposal -- #74 — Vault action TOML schema -- #75 — Vault PR creation helper (`lib/vault.sh`) -- #76 — Dispatcher rewrite (poll for merged vault PRs) -- #77 — Branch protection on ops repo (this issue) - -## See Also - -- [`lib/vault.sh`](../lib/vault.sh) — Vault PR creation helper -- [`vault/vault-env.sh`](../vault/vault-env.sh) — TOML validation -- [`lib/branch-protection.sh`](../lib/branch-protection.sh) — Branch protection helper diff --git a/lib/branch-protection.sh b/lib/branch-protection.sh deleted file mode 100644 index 340d53a..0000000 --- a/lib/branch-protection.sh +++ /dev/null @@ -1,285 +0,0 @@ -#!/usr/bin/env bash -# branch-protection.sh — Helper for setting up branch protection on repos -# -# Source after lib/env.sh: -# source "$(dirname "$0")/../lib/env.sh" -# source "$(dirname "$0")/lib/branch-protection.sh" -# -# Required globals: FORGE_TOKEN, FORGE_URL, FORGE_OPS_REPO -# -# Functions: -# setup_vault_branch_protection — Set up admin-only branch protection for main -# verify_branch_protection — Verify protection is configured correctly -# remove_branch_protection — Remove branch protection (for cleanup/testing) -# -# Branch protection settings: -# - Require 1 approval before merge -# - Restrict merge to admin role (not regular collaborators or bots) -# - Block direct pushes to main (all changes must go through PR) - -set -euo pipefail - -# Internal log helper -_bp_log() { - if declare -f log >/dev/null 2>&1; then - log "branch-protection: $*" - else - printf '[%s] branch-protection: %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*" >&2 - fi -} - -# Get ops repo API URL -_ops_api() { - printf '%s' "${FORGE_URL}/api/v1/repos/${FORGE_OPS_REPO}" -} - -# ----------------------------------------------------------------------------- -# setup_vault_branch_protection — Set up admin-only branch protection for main -# -# Configures the following protection rules: -# - Require 1 approval before merge -# - Restrict merge to admin role (not regular collaborators or bots) -# - Block direct pushes to main (all changes must go through PR) -# -# Returns: 0 on success, 1 on failure -# ----------------------------------------------------------------------------- -setup_vault_branch_protection() { - local branch="${1:-main}" - local api_url - api_url="$(_ops_api)" - - _bp_log "Setting up branch protection for ${branch} on ${FORGE_OPS_REPO}" - - # Check if branch exists - local branch_exists - branch_exists=$(curl -s -o /dev/null -w "%{http_code}" \ - -H "Authorization: token ${FORGE_TOKEN}" \ - "${api_url}/git/branches/${branch}" 2>/dev/null || echo "0") - - if [ "$branch_exists" != "200" ]; then - _bp_log "ERROR: Branch ${branch} does not exist" - return 1 - fi - - # Check if protection already exists - local protection_exists - protection_exists=$(curl -s -o /dev/null -w "%{http_code}" \ - -H "Authorization: token ${FORGE_TOKEN}" \ - "${api_url}/branches/${branch}/protection" 2>/dev/null || echo "0") - - if [ "$protection_exists" = "200" ]; then - _bp_log "Branch protection already exists for ${branch}" - _bp_log "Updating existing protection rules" - fi - - # Create/update branch protection - # Note: Forgejo API uses "require_signed_commits" and "required_approvals" for approval requirements - # The "admin_enforced" field ensures only admins can merge - local protection_json - protection_json=$(cat </dev/null || true) - - if [ -z "$protection_json" ] || [ "$protection_json" = "null" ]; then - _bp_log "ERROR: No branch protection found for ${branch}" - return 1 - fi - - # Extract and validate settings - local enable_push enable_merge_commit required_approvals admin_enforced - enable_push=$(printf '%s' "$protection_json" | jq -r '.enable_push // true') - enable_merge_commit=$(printf '%s' "$protection_json" | jq -r '.enable_merge_commit // false') - required_approvals=$(printf '%s' "$protection_json" | jq -r '.required_approvals // 0') - admin_enforced=$(printf '%s' "$protection_json" | jq -r '.admin_enforced // false') - - local errors=0 - - # Check push is disabled - if [ "$enable_push" = "true" ]; then - _bp_log "ERROR: enable_push should be false" - errors=$((errors + 1)) - else - _bp_log "OK: Pushes are blocked" - fi - - # Check merge commit is enabled - if [ "$enable_merge_commit" != "true" ]; then - _bp_log "ERROR: enable_merge_commit should be true" - errors=$((errors + 1)) - else - _bp_log "OK: Merge commits are allowed" - fi - - # Check required approvals - if [ "$required_approvals" -lt 1 ]; then - _bp_log "ERROR: required_approvals should be at least 1" - errors=$((errors + 1)) - else - _bp_log "OK: Required approvals: ${required_approvals}" - fi - - # Check admin enforced - if [ "$admin_enforced" != "true" ]; then - _bp_log "ERROR: admin_enforced should be true" - errors=$((errors + 1)) - else - _bp_log "OK: Admin enforcement enabled" - fi - - if [ "$errors" -gt 0 ]; then - _bp_log "Verification failed with ${errors} error(s)" - return 1 - fi - - _bp_log "Branch protection verified successfully" - return 0 -} - -# ----------------------------------------------------------------------------- -# remove_branch_protection — Remove branch protection (for cleanup/testing) -# -# Returns: 0 on success, 1 on failure -# ----------------------------------------------------------------------------- -remove_branch_protection() { - local branch="${1:-main}" - local api_url - api_url="$(_ops_api)" - - _bp_log "Removing branch protection for ${branch}" - - # Check if protection exists - local protection_exists - protection_exists=$(curl -s -o /dev/null -w "%{http_code}" \ - -H "Authorization: token ${FORGE_TOKEN}" \ - "${api_url}/branches/${branch}/protection" 2>/dev/null || echo "0") - - if [ "$protection_exists" != "200" ]; then - _bp_log "No branch protection found for ${branch}" - return 0 - fi - - # Delete protection - local http_code - http_code=$(curl -s -o /dev/null -w "%{http_code}" \ - -X DELETE \ - -H "Authorization: token ${FORGE_TOKEN}" \ - "${api_url}/branches/${branch}/protection" 2>/dev/null || echo "0") - - if [ "$http_code" != "204" ]; then - _bp_log "ERROR: Failed to remove branch protection (HTTP ${http_code})" - return 1 - fi - - _bp_log "Branch protection removed successfully for ${branch}" - return 0 -} - -# ----------------------------------------------------------------------------- -# Test mode — run when executed directly -# ----------------------------------------------------------------------------- -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - # Check required env vars - if [ -z "${FORGE_TOKEN:-}" ]; then - echo "ERROR: FORGE_TOKEN is required" >&2 - exit 1 - fi - - if [ -z "${FORGE_URL:-}" ]; then - echo "ERROR: FORGE_URL is required" >&2 - exit 1 - fi - - if [ -z "${FORGE_OPS_REPO:-}" ]; then - echo "ERROR: FORGE_OPS_REPO is required" >&2 - exit 1 - fi - - # Parse command line args - case "${1:-help}" in - setup) - setup_vault_branch_protection "${2:-main}" - ;; - verify) - verify_branch_protection "${2:-main}" - ;; - remove) - remove_branch_protection "${2:-main}" - ;; - help|*) - echo "Usage: $0 {setup|verify|remove} [branch]" - echo "" - echo "Commands:" - echo " setup [branch] Set up branch protection (default: main)" - echo " verify [branch] Verify branch protection is configured correctly" - echo " remove [branch] Remove branch protection (for cleanup/testing)" - echo "" - echo "Required environment variables:" - echo " FORGE_TOKEN Forgejo API token (admin user recommended)" - echo " FORGE_URL Forgejo instance URL (e.g., https://codeberg.org)" - echo " FORGE_OPS_REPO Ops repo in format owner/repo (e.g., johba/disinto-ops)" - exit 0 - ;; - esac -fi