From 0d2ed587c129d2b961c2c943e506d7dff9e9e02a Mon Sep 17 00:00:00 2001 From: Agent Date: Wed, 1 Apr 2026 08:22:36 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20feat(20d):=20branch=20protection=20on=20?= =?UTF-8?q?.profile=20repos=20=E2=80=94=20admin-only=20formula=20merge=20(?= =?UTF-8?q?#87)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/disinto | 47 ++++++++++-- lib/branch-protection.sh | 149 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 186 insertions(+), 10 deletions(-) diff --git a/bin/disinto b/bin/disinto index aea40aa..cc9a95d 100755 --- a/bin/disinto +++ b/bin/disinto @@ -2530,15 +2530,24 @@ Agent profile repository for ${agent_name}. \`\`\` ${agent_name}/.profile/ ├── formula.toml # Agent's role formula -├── journal/ # Issue-by-issue log files +├── journal/ # Issue-by-issue log files (journal branch) │ └── .gitkeep -└── knowledge/ # Shared knowledge and best practices - └── .gitkeep +├── knowledge/ # Shared knowledge and best practices +│ └── .gitkeep +└── README.md \`\`\` +## Branches + +- \`main\` — Admin-only merge for formula changes (requires 1 approval) +- \`journal\` — Agent branch for direct journal entries + - Agent can push directly to this branch + - Formula changes must go through PR to \`main\` + ## Branch protection -- \`main\`: Admin-only merge for formula changes +- \`main\`: Protected — requires 1 admin approval for merges +- \`journal\`: Unprotected — agent can push directly EOF fi @@ -2556,9 +2565,35 @@ EOF rm -rf "$clone_dir" - # Step 4: Create state marker + # Step 4: Set up branch protection echo "" - echo "Step 4: Creating state marker..." + echo "Step 4: Setting up branch protection..." + + # Source branch-protection.sh helper + local bp_script="${FACTORY_ROOT}/lib/branch-protection.sh" + if [ -f "$bp_script" ]; then + # Source required environment + if [ -f "${FACTORY_ROOT}/lib/env.sh" ]; then + source "${FACTORY_ROOT}/lib/env.sh" + fi + + # Set up branch protection for .profile repo + if source "$bp_script" 2>/dev/null && setup_profile_branch_protection "${agent_name}/.profile" "main"; then + echo " Branch protection configured for main branch" + echo " - Requires 1 approval before merge" + echo " - Admin-only merge enforcement" + echo " - Journal branch created for direct agent pushes" + else + echo " Warning: could not configure branch protection (Forgejo API may not be available)" + echo " Note: Branch protection can be set up manually later" + fi + else + echo " Warning: branch-protection.sh not found at ${bp_script}" + fi + + # Step 5: Create state marker + echo "" + echo "Step 5: Creating state marker..." local state_dir="${FACTORY_ROOT}/state" mkdir -p "$state_dir" diff --git a/lib/branch-protection.sh b/lib/branch-protection.sh index 340d53a..6c27cd9 100644 --- a/lib/branch-protection.sh +++ b/lib/branch-protection.sh @@ -10,6 +10,7 @@ # Functions: # setup_vault_branch_protection — Set up admin-only branch protection for main # verify_branch_protection — Verify protection is configured correctly +# setup_profile_branch_protection — Set up admin-only branch protection for .profile repos # remove_branch_protection — Remove branch protection (for cleanup/testing) # # Branch protection settings: @@ -197,6 +198,138 @@ verify_branch_protection() { return 0 } +# ----------------------------------------------------------------------------- +# setup_profile_branch_protection — Set up admin-only branch protection for .profile repos +# +# 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) +# +# Also creates a 'journal' branch for direct agent journal pushes +# +# Args: +# $1 - Repo path in format 'owner/repo' (e.g., 'dev-bot/.profile') +# $2 - Branch to protect (default: main) +# +# Returns: 0 on success, 1 on failure +# ----------------------------------------------------------------------------- +setup_profile_branch_protection() { + local repo="${1:-}" + local branch="${2:-main}" + + if [ -z "$repo" ]; then + _bp_log "ERROR: repo path required (format: owner/repo)" + return 1 + fi + + _bp_log "Setting up branch protection for ${branch} on ${repo}" + + local api_url + api_url="${FORGE_URL}/api/v1/repos/${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 on ${repo}" + 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 + local protection_json + protection_json=$(cat </dev/null || echo "0") + + if [ "$journal_exists" != "200" ]; then + # Create journal branch from main + # Get the commit hash of main + local main_commit + main_commit=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \ + "${api_url}/git/refs/heads/${branch}" 2>/dev/null | jq -r '.object.sha' || echo "") + + if [ -n "$main_commit" ]; then + curl -sf -X POST \ + -H "Authorization: token ${FORGE_TOKEN}" \ + -H "Content-Type: application/json" \ + "${api_url}/git/refs" \ + -d "{\"ref\":\"refs/heads/${journal_branch}\",\"sha\":\"${main_commit}\"}" >/dev/null 2>&1 || { + _bp_log "Warning: failed to create journal branch (may already exist)" + } + fi + fi + + _bp_log "Journal branch '${journal_branch}' ready for direct pushes" + + return 0 +} + # ----------------------------------------------------------------------------- # remove_branch_protection — Remove branch protection (for cleanup/testing) # @@ -261,6 +394,13 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then setup) setup_vault_branch_protection "${2:-main}" ;; + setup-profile) + if [ -z "${2:-}" ]; then + echo "ERROR: repo path required (format: owner/repo)" >&2 + exit 1 + fi + setup_profile_branch_protection "${2}" "${3:-main}" + ;; verify) verify_branch_protection "${2:-main}" ;; @@ -268,12 +408,13 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then remove_branch_protection "${2:-main}" ;; help|*) - echo "Usage: $0 {setup|verify|remove} [branch]" + echo "Usage: $0 {setup|setup-profile|verify|remove} [args...]" 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 " setup [branch] Set up branch protection on ops repo (default: main)" + echo " setup-profile [branch] Set up branch protection on .profile repo" + 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)"