diff --git a/.woodpecker/agent-smoke.sh b/.woodpecker/agent-smoke.sh index 3c24112..89cb74e 100644 --- a/.woodpecker/agent-smoke.sh +++ b/.woodpecker/agent-smoke.sh @@ -100,6 +100,7 @@ echo "=== 2/2 Function resolution ===" # lib/secret-scan.sh — sourced by file-action-issue.sh, phase-handler.sh (scan_for_secrets, redact_secrets) # lib/formula-session.sh — sourced by formula-driven agents (acquire_cron_lock, run_formula_and_monitor, etc.) # lib/mirrors.sh — sourced by merge sites (mirror_push) +# lib/guard.sh — sourced by all cron entry points (check_active) # # Excluded — not sourced inline by agents: # lib/ci-debug.sh — standalone CLI tool, run directly (not sourced) @@ -110,7 +111,7 @@ echo "=== 2/2 Function resolution ===" # If a new lib file is added and sourced by agents, add it to LIB_FUNS below # and add a check_script call for it in the lib files section further down. LIB_FUNS=$( - for f in lib/agent-session.sh lib/env.sh lib/ci-helpers.sh lib/load-project.sh lib/secret-scan.sh lib/file-action-issue.sh lib/formula-session.sh lib/mirrors.sh; do + for f in lib/agent-session.sh lib/env.sh lib/ci-helpers.sh lib/load-project.sh lib/secret-scan.sh lib/file-action-issue.sh lib/formula-session.sh lib/mirrors.sh lib/guard.sh; do if [ -f "$f" ]; then get_fns "$f"; fi done | sort -u ) @@ -181,6 +182,7 @@ check_script lib/file-action-issue.sh lib/secret-scan.sh check_script lib/formula-session.sh lib/agent-session.sh check_script lib/load-project.sh check_script lib/mirrors.sh +check_script lib/guard.sh # Standalone lib scripts (not sourced by agents; run directly or as services). # Still checked for function resolution against LIB_FUNS + own definitions. diff --git a/action/action-poll.sh b/action/action-poll.sh index 61c26e8..5d7f3dc 100755 --- a/action/action-poll.sh +++ b/action/action-poll.sh @@ -13,6 +13,9 @@ set -euo pipefail export PROJECT_TOML="${1:-}" source "$(dirname "$0")/../lib/env.sh" +# shellcheck source=../lib/guard.sh +source "$(dirname "$0")/../lib/guard.sh" +check_active action LOGFILE="${FACTORY_ROOT}/action/action-poll-${PROJECT_NAME:-default}.log" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" diff --git a/dev/dev-poll.sh b/dev/dev-poll.sh index 83b76d8..ef03a9b 100755 --- a/dev/dev-poll.sh +++ b/dev/dev-poll.sh @@ -22,6 +22,9 @@ source "$(dirname "$0")/../lib/env.sh" source "$(dirname "$0")/../lib/ci-helpers.sh" # shellcheck source=../lib/mirrors.sh source "$(dirname "$0")/../lib/mirrors.sh" +# shellcheck source=../lib/guard.sh +source "$(dirname "$0")/../lib/guard.sh" +check_active dev # Gitea labels API requires []int64 — look up the "underspecified" label ID once UNDERSPECIFIED_LABEL_ID=$(forge_api GET "/labels" 2>/dev/null \ diff --git a/gardener/gardener-run.sh b/gardener/gardener-run.sh index 39f5928..50de542 100755 --- a/gardener/gardener-run.sh +++ b/gardener/gardener-run.sh @@ -28,6 +28,8 @@ source "$FACTORY_ROOT/lib/formula-session.sh" source "$FACTORY_ROOT/lib/ci-helpers.sh" # shellcheck source=../lib/mirrors.sh source "$FACTORY_ROOT/lib/mirrors.sh" +# shellcheck source=../lib/guard.sh +source "$FACTORY_ROOT/lib/guard.sh" LOG_FILE="$SCRIPT_DIR/gardener.log" # shellcheck disable=SC2034 # consumed by run_formula_and_monitor @@ -52,6 +54,7 @@ _GARDENER_CRASH_COUNT=0 log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; } # ── Guards ──────────────────────────────────────────────────────────────── +check_active gardener acquire_cron_lock "/tmp/gardener-run.lock" check_memory 2000 diff --git a/lib/guard.sh b/lib/guard.sh new file mode 100644 index 0000000..172bf60 --- /dev/null +++ b/lib/guard.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# guard.sh — Active-state guard for cron entry points +# +# Each agent checks for a state file before running. If the file +# doesn't exist, the agent logs a skip and exits cleanly. +# +# State files live in $FACTORY_ROOT/state/: +# .dev-active, .reviewer-active, .planner-active, etc. +# +# Presence = permission to run. Absence = skip (factory off by default). + +# check_active +# Exit 0 (skip) if the state file is absent. +check_active() { + local agent_name="$1" + local state_file="${FACTORY_ROOT}/state/.${agent_name}-active" + if [ ! -f "$state_file" ]; then + log "${agent_name} not active — skipping" + exit 0 + fi +} diff --git a/planner/planner-run.sh b/planner/planner-run.sh index 103588c..c26e74d 100755 --- a/planner/planner-run.sh +++ b/planner/planner-run.sh @@ -22,6 +22,8 @@ source "$FACTORY_ROOT/lib/env.sh" source "$FACTORY_ROOT/lib/agent-session.sh" # shellcheck source=../lib/formula-session.sh source "$FACTORY_ROOT/lib/formula-session.sh" +# shellcheck source=../lib/guard.sh +source "$FACTORY_ROOT/lib/guard.sh" LOG_FILE="$SCRIPT_DIR/planner.log" # shellcheck disable=SC2034 # consumed by run_formula_and_monitor @@ -36,6 +38,7 @@ SCRATCH_FILE="/tmp/planner-${PROJECT_NAME}-scratch.md" log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; } # ── Guards ──────────────────────────────────────────────────────────────── +check_active planner acquire_cron_lock "/tmp/planner-run.lock" check_memory 2000 diff --git a/predictor/predictor-run.sh b/predictor/predictor-run.sh index a7f332e..ccee3d5 100755 --- a/predictor/predictor-run.sh +++ b/predictor/predictor-run.sh @@ -24,6 +24,8 @@ source "$FACTORY_ROOT/lib/env.sh" source "$FACTORY_ROOT/lib/agent-session.sh" # shellcheck source=../lib/formula-session.sh source "$FACTORY_ROOT/lib/formula-session.sh" +# shellcheck source=../lib/guard.sh +source "$FACTORY_ROOT/lib/guard.sh" LOG_FILE="$SCRIPT_DIR/predictor.log" # shellcheck disable=SC2034 # consumed by run_formula_and_monitor @@ -38,6 +40,7 @@ SCRATCH_FILE="/tmp/predictor-${PROJECT_NAME}-scratch.md" log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; } # ── Guards ──────────────────────────────────────────────────────────────── +check_active predictor acquire_cron_lock "/tmp/predictor-run.lock" check_memory 2000 diff --git a/review/review-poll.sh b/review/review-poll.sh index 4eb217b..d21dac5 100755 --- a/review/review-poll.sh +++ b/review/review-poll.sh @@ -11,6 +11,9 @@ set -euo pipefail export PROJECT_TOML="${1:-}" source "$(dirname "$0")/../lib/env.sh" source "$(dirname "$0")/../lib/ci-helpers.sh" +# shellcheck source=../lib/guard.sh +source "$(dirname "$0")/../lib/guard.sh" +check_active reviewer REPO_ROOT="${PROJECT_REPO_ROOT}" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" diff --git a/state/.gitignore b/state/.gitignore new file mode 100644 index 0000000..0a0c1e8 --- /dev/null +++ b/state/.gitignore @@ -0,0 +1,2 @@ +# Active-state files are runtime state, not committed +.*-active diff --git a/supervisor/supervisor-run.sh b/supervisor/supervisor-run.sh index a18914f..6cd67ed 100755 --- a/supervisor/supervisor-run.sh +++ b/supervisor/supervisor-run.sh @@ -28,6 +28,8 @@ source "$FACTORY_ROOT/lib/env.sh" source "$FACTORY_ROOT/lib/agent-session.sh" # shellcheck source=../lib/formula-session.sh source "$FACTORY_ROOT/lib/formula-session.sh" +# shellcheck source=../lib/guard.sh +source "$FACTORY_ROOT/lib/guard.sh" LOG_FILE="$SCRIPT_DIR/supervisor.log" # shellcheck disable=SC2034 # consumed by run_formula_and_monitor @@ -42,6 +44,7 @@ SCRATCH_FILE="/tmp/supervisor-${PROJECT_NAME}-scratch.md" log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; } # ── Guards ──────────────────────────────────────────────────────────────── +check_active supervisor acquire_cron_lock "/tmp/supervisor-run.lock" check_memory 2000