feat(circuit-breaker): add auto-recovery from OPEN state (#160)

The OPEN state was terminal — once triggered, it persisted across
restarts with no automatic recovery. This adds two recovery mechanisms:

1. Cooldown timer (default): OPEN → HALF_OPEN after CB_COOLDOWN_MINUTES
   (default 30). The existing HALF_OPEN logic handles recovery or re-trip.
2. Auto-reset option: CB_AUTO_RESET=true bypasses cooldown, resets to
   CLOSED on startup for fully unattended operation.

Changes:
- Add parse_iso_to_epoch() to lib/date_utils.sh (cross-platform)
- Add cooldown + auto-reset logic to init_circuit_breaker()
- Add opened_at field to state file when entering/staying OPEN
- Add --auto-reset-circuit CLI flag and .ralphrc config vars
- Add 19 tests in test_circuit_breaker_recovery.bats
- Update CLAUDE.md and README.md documentation
This commit is contained in:
Test User 2026-02-07 01:33:41 -07:00
parent abcfa8b695
commit b4b9db6b76
7 changed files with 626 additions and 6 deletions

View file

@ -46,8 +46,59 @@ get_epoch_seconds() {
date +%s
}
# Convert ISO 8601 timestamp to Unix epoch seconds
# Input: ISO timestamp (e.g., "2025-01-15T10:30:00+00:00")
# Returns: Unix epoch seconds on stdout
# Falls back to current epoch on parse failure (safe default)
parse_iso_to_epoch() {
local iso_timestamp=$1
if [[ -z "$iso_timestamp" || "$iso_timestamp" == "null" ]]; then
date +%s
return
fi
# Try GNU date -d (Linux, macOS with Homebrew coreutils)
local result
if result=$(date -d "$iso_timestamp" +%s 2>/dev/null) && [[ "$result" =~ ^[0-9]+$ ]]; then
echo "$result"
return
fi
# Try BSD date -j (native macOS)
# Strip timezone suffix for BSD compatibility
local stripped="${iso_timestamp%%+*}"
stripped="${stripped%%Z*}"
if result=$(date -j -f "%Y-%m-%dT%H:%M:%S" "$stripped" +%s 2>/dev/null) && [[ "$result" =~ ^[0-9]+$ ]]; then
echo "$result"
return
fi
# Fallback: manual epoch arithmetic from ISO components
# Parse: YYYY-MM-DDTHH:MM:SS (ignore timezone, assume UTC)
local year month day hour minute second
if [[ "$iso_timestamp" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}) ]]; then
year="${BASH_REMATCH[1]}"
month="${BASH_REMATCH[2]}"
day="${BASH_REMATCH[3]}"
hour="${BASH_REMATCH[4]}"
minute="${BASH_REMATCH[5]}"
second="${BASH_REMATCH[6]}"
# Use date with explicit components if available
if result=$(date -u -d "${year}-${month}-${day} ${hour}:${minute}:${second}" +%s 2>/dev/null) && [[ "$result" =~ ^[0-9]+$ ]]; then
echo "$result"
return
fi
fi
# Ultimate fallback: return current epoch (safe default)
date +%s
}
# Export functions for use in other scripts
export -f get_iso_timestamp
export -f get_next_hour_time
export -f get_basic_timestamp
export -f get_epoch_seconds
export -f parse_iso_to_epoch