Compare commits
No commits in common. "6dce181330da6ee5fbc64d1f7eaac4affdc1612b" and "2722795c828a58f6b43a7c630dbb007e38908544" have entirely different histories.
6dce181330
...
2722795c82
4 changed files with 1 additions and 388 deletions
|
|
@ -10,7 +10,7 @@ all via cron and `claude -p`. The dispatcher executes formula-based operational
|
||||||
tasks.
|
tasks.
|
||||||
|
|
||||||
> **Note:** The vault is being redesigned as a PR-based approval workflow on the
|
> **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.
|
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) |
|
| Predictor | `predictor/` | Infrastructure pattern detection | [predictor/AGENTS.md](predictor/AGENTS.md) |
|
||||||
|
|
||||||
> **Vault:** Being redesigned as a PR-based approval workflow (issues #73-#77).
|
> **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.
|
See [lib/AGENTS.md](lib/AGENTS.md) for the full shared helper reference.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,8 +122,6 @@ disinto/
|
||||||
│ └── (formula-driven) # run-planner.toml executed by dispatcher
|
│ └── (formula-driven) # run-planner.toml executed by dispatcher
|
||||||
├── vault/
|
├── vault/
|
||||||
│ └── vault-env.sh # Shared env setup (vault redesign in progress, see #73-#77)
|
│ └── 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/
|
||||||
├── supervisor-poll.sh # Supervisor: health checks + claude -p
|
├── supervisor-poll.sh # Supervisor: health checks + claude -p
|
||||||
├── update-prompt.sh # Self-learning: append to best-practices
|
├── 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. |
|
| **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).
|
> **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
|
## Design Principles
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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/<action-id>`
|
|
||||||
- Title: `vault: <action-id>`
|
|
||||||
- Labels: `vault`, `pending-approval`
|
|
||||||
- File: `vault/actions/<action-id>.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="<admin-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
|
|
||||||
|
|
@ -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 <<EOF
|
|
||||||
{
|
|
||||||
"enable_push": false,
|
|
||||||
"enable_force_push": false,
|
|
||||||
"enable_merge_commit": true,
|
|
||||||
"enable_rebase": true,
|
|
||||||
"enable_rebase_merge": true,
|
|
||||||
"required_approvals": 1,
|
|
||||||
"required_signatures": false,
|
|
||||||
"admin_enforced": true,
|
|
||||||
"required_status_checks": false,
|
|
||||||
"required_linear_history": false
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
local http_code
|
|
||||||
if [ "$protection_exists" = "200" ]; then
|
|
||||||
# Update existing protection
|
|
||||||
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
||||||
-X PUT \
|
|
||||||
-H "Authorization: token ${FORGE_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${api_url}/branches/${branch}/protection" \
|
|
||||||
-d "$protection_json" || echo "0")
|
|
||||||
else
|
|
||||||
# Create new protection
|
|
||||||
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
||||||
-X POST \
|
|
||||||
-H "Authorization: token ${FORGE_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${api_url}/branches/${branch}/protection" \
|
|
||||||
-d "$protection_json" || echo "0")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$http_code" != "200" ] && [ "$http_code" != "201" ]; then
|
|
||||||
_bp_log "ERROR: Failed to set up branch protection (HTTP ${http_code})"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
_bp_log "Branch protection configured successfully for ${branch}"
|
|
||||||
_bp_log " - Pushes blocked: true"
|
|
||||||
_bp_log " - Force pushes blocked: true"
|
|
||||||
_bp_log " - Required approvals: 1"
|
|
||||||
_bp_log " - Admin enforced: true"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# verify_branch_protection — Verify protection is configured correctly
|
|
||||||
#
|
|
||||||
# Returns: 0 if protection is configured correctly, 1 otherwise
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
verify_branch_protection() {
|
|
||||||
local branch="${1:-main}"
|
|
||||||
local api_url
|
|
||||||
api_url="$(_ops_api)"
|
|
||||||
|
|
||||||
_bp_log "Verifying branch protection for ${branch}"
|
|
||||||
|
|
||||||
# Get current protection settings
|
|
||||||
local protection_json
|
|
||||||
protection_json=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
|
|
||||||
"${api_url}/branches/${branch}/protection" 2>/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
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue