#!/bin/bash # ==================================================== # PR-D Production Check — OpenClaw Agent OS v2 # ==================================================== # One-shot safety gate: env → baseline → guard → # deprecation → smoke → report. # Every architecture PR must pass this gate first. # ==================================================== set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(dirname "$SCRIPT_DIR")" cd "$ROOT_DIR" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' START_TIME=$(date +%s) # ─── Result state ─────────────────────────────────── ENV_PASS=false BASELINE_PASS=false GUARD_PASS=false DEPRECATION_STATUS="PASS" SMOKE_PASS=false OVERALL_PASS=true FAILED_STEP="" FAIL_REASON="" FAIL_FIX="" BASELINE_COUNT=0 BASELINE_DURATION=0 BASELINE_LOG="" DEPRECATION_COUNT=0 # ─── Helpers ──────────────────────────────────────── fail_step() { local step="$1" reason="$2" fix="$3" FAILED_STEP="$step" FAIL_REASON="$reason" FAIL_FIX="$fix" OVERALL_PASS=false } section() { echo "" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo -e "${CYAN} $1${NC}" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo "" } # ───────────────────────────────────────────────────── # Step 1: Environment Check # ───────────────────────────────────────────────────── section "Step 1: Environment Check" check_ok() { echo -e " ${GREEN}✓${NC} $1"; } check_fail() { echo -e " ${RED}✗${NC} $1"; fail_step "Environment" "$1" "$2"; } # Node.js version NODE_VERSION=$(node --version 2>/dev/null || echo "") if [ -z "$NODE_VERSION" ]; then check_fail "Node.js not found" "Install Node.js >= 18" else NODE_MAJOR=$(echo "$NODE_VERSION" | sed 's/v//' | cut -d. -f1) if [ "$NODE_MAJOR" -lt 18 ]; then check_fail "Node.js $NODE_VERSION too old (need >=18)" "Upgrade Node.js: nvm install 18" else check_ok "Node.js $NODE_VERSION" fi fi # npm if command -v npm &>/dev/null; then NPM_VERSION=$(npm --version 2>/dev/null) check_ok "npm $NPM_VERSION" else check_fail "npm not found" "Install npm (bundled with Node.js)" fi # openclaw CLI if command -v openclaw &>/dev/null; then OC_VERSION=$(openclaw --version 2>/dev/null || echo "unknown") check_ok "openclaw CLI ($OC_VERSION)" else check_fail "openclaw CLI not found" "Install openclaw: npm i -g openclaw" fi # Working directory if [ -f "package.json" ]; then check_ok "Working directory: $ROOT_DIR" else check_fail "Not in workspace root (no package.json)" "cd to workspace root" fi # Required directories for dir in "test/baseline" "test/architecture" "scripts"; do if [ -d "$dir" ]; then check_ok "Directory exists: $dir" else check_fail "Directory missing: $dir" "Create $dir before running" fi done if $OVERALL_PASS; then ENV_PASS=true echo "" echo -e " ${GREEN}Environment: PASS${NC}" else ENV_PASS=false echo "" echo -e " ${RED}Environment: FAIL${NC}" fi # ───────────────────────────────────────────────────── # Step 2: Baseline Tests # ───────────────────────────────────────────────────── section "Step 2: Baseline Tests" BASELINE_START=$(date +%s) # Run baseline tests directly (not via buggy test-baseline.sh which has wrong TEST_DIR) BASELINE_LOG=$(mktemp) BASELINE_FILES=$(ls test/baseline/test-*.test.mjs 2>/dev/null || echo "") BASELINE_TOTAL=$(echo "$BASELINE_FILES" | wc -w | tr -d ' ') if [ -z "$BASELINE_FILES" ] || [ "$BASELINE_TOTAL" -eq 0 ]; then echo -e " ${RED}✗${NC} No baseline test files found" fail_step "Baseline Tests" "No test files in test/baseline/" "Ensure PR-A baseline tests exist" else echo " Running $BASELINE_TOTAL baseline test files..." echo "" PASSED=0 FAILED=0 for f in $BASELINE_FILES; do name=$(basename "$f" .test.mjs) echo -ne " [RUN ] $name ... " if node --test "$f" >> "$BASELINE_LOG" 2>&1; then echo -e "${GREEN}PASS${NC}" PASSED=$((PASSED + 1)) else echo -e "${RED}FAIL${NC}" FAILED=$((FAILED + 1)) fi done echo "" echo " ──────────────────────────────────────────" echo " Test files: $BASELINE_TOTAL" echo " Passed: $PASSED" echo " Failed: $FAILED" echo " Log: $BASELINE_LOG" if [ "$FAILED" -gt 0 ]; then fail_step "Baseline Tests" "$FAILED/$BASELINE_TOTAL tests failed" "Check $BASELINE_LOG for details" BASELINE_PASS=false else BASELINE_PASS=true fi fi BASELINE_END=$(date +%s) BASELINE_DURATION=$((BASELINE_END - BASELINE_START)) BASELINE_COUNT=$BASELINE_TOTAL echo "" echo -e " Duration: ${BASELINE_DURATION}s" if $BASELINE_PASS; then echo -e " ${GREEN}Baseline Tests: PASS${NC}" else echo -e " ${RED}Baseline Tests: FAIL${NC}" fi # ───────────────────────────────────────────────────── # Step 3: Architecture Guards # ───────────────────────────────────────────────────── section "Step 3: Architecture Guards" GUARD_SCRIPT="$SCRIPT_DIR/guard-all.sh" if [ ! -f "$GUARD_SCRIPT" ]; then fail_step "Architecture Guard" "guard-all.sh not found at $GUARD_SCRIPT" "Ensure PR-B guard scripts exist" GUARD_PASS=false else echo " Running: bash $GUARD_SCRIPT" echo "" # Run guard-all but capture exit code — we need to differentiate HARD vs SOFT # guard-all.sh exits 1 if any hard guard fails GUARD_OUTPUT=$(bash "$GUARD_SCRIPT" 2>&1) || GUARD_EXIT=$? echo "$GUARD_OUTPUT" # Parse guard-all output for individual guard statuses GUARD_TOTAL=6 GUARD_PASSED=0 GUARD_FAILED=0 GUARD_WARNED=0 while IFS='|' read -r name result; do case "$result" in PASS) GUARD_PASSED=$((GUARD_PASSED + 1)) ;; FAIL) GUARD_FAILED=$((GUARD_FAILED + 1)) ;; WARN) GUARD_WARNED=$((GUARD_WARNED + 1)) ;; esac done < <(echo "$GUARD_OUTPUT" | grep -E '^\S+\|(PASS|FAIL|WARN)$' || true) # HARD guard failure = production check failure # SOFT guard warning ≠ production check failure if [ "${GUARD_EXIT:-0}" -ne 0 ]; then fail_step "Architecture Guard" "$GUARD_FAILED HARD guard(s) failed" "Review guard output above and fix violations" GUARD_PASS=false else GUARD_PASS=true fi echo "" echo " Guard status: $GUARD_PASSED pass | $GUARD_FAILED fail | $GUARD_WARNED warn" if $GUARD_PASS; then echo -e " ${GREEN}Architecture Guard: PASS${NC}" else echo -e " ${RED}Architecture Guard: FAIL${NC}" fi fi # ───────────────────────────────────────────────────── # Step 4: Deprecation Check # ───────────────────────────────────────────────────── section "Step 4: Deprecation Check" DEPRECATION_SCRIPT="$SCRIPT_DIR/check-deprecations.mjs" if [ ! -f "$DEPRECATION_SCRIPT" ]; then DEPRECATION_STATUS="FAIL" fail_step "Deprecation Check" "check-deprecations.mjs not found" "Ensure PR-C deprecation check script exists" else echo " Running: node $DEPRECATION_SCRIPT" echo "" # Deprecation check never fails on deprecation count, only on script crash DEPRECATION_OUTPUT=$(node "$DEPRECATION_SCRIPT" 2>&1) || DEP_EXIT=$? echo "$DEPRECATION_OUTPUT" # Extract deprecation count and list DEPRECATION_COUNT=$(echo "$DEPRECATION_OUTPUT" | grep "Total warnings:" | grep -oE '[0-9]+' || echo "0") if [ "${DEP_EXIT:-0}" -ne 0 ]; then DEPRECATION_STATUS="FAIL" fail_step "Deprecation Check" "Script crashed (exit $DEP_EXIT)" "Check $DEPRECATION_SCRIPT for errors" elif [ "$DEPRECATION_COUNT" -gt 0 ]; then DEPRECATION_STATUS="WARN" else DEPRECATION_STATUS="PASS" fi echo "" echo " Deprecation items: $DEPRECATION_COUNT" # Print deprecation list if any DEP_LIST=$(echo "$DEPRECATION_OUTPUT" | sed -n '/Deprecated modules:/,/^$/p' || true) if [ -n "$DEP_LIST" ]; then echo "$DEP_LIST" fi case "$DEPRECATION_STATUS" in PASS) echo -e " ${GREEN}Deprecation Check: PASS${NC}" ;; WARN) echo -e " ${YELLOW}Deprecation Check: WARN${NC}" ;; FAIL) echo -e " ${RED}Deprecation Check: FAIL${NC}" ;; esac fi # ───────────────────────────────────────────────────── # Step 5: Smoke Test # ───────────────────────────────────────────────────── section "Step 5: Smoke Test" SMOKE_SCRIPT="$SCRIPT_DIR/smoke-test.mjs" if [ ! -f "$SMOKE_SCRIPT" ]; then fail_step "Smoke Test" "smoke-test.mjs not found at $SMOKE_SCRIPT" "Create scripts/smoke-test.mjs" SMOKE_PASS=false else echo " Running: node $SMOKE_SCRIPT" echo "" SMOKE_OUTPUT=$(node "$SMOKE_SCRIPT" 2>&1) || SMOKE_EXIT=$? echo "$SMOKE_OUTPUT" if [ "${SMOKE_EXIT:-0}" -eq 0 ]; then SMOKE_PASS=true echo -e " ${GREEN}Smoke Test: PASS${NC}" else SMOKE_PASS=false fail_step "Smoke Test" "Smoke test failed (exit $SMOKE_EXIT)" "Check smoke test output above" echo -e " ${RED}Smoke Test: FAIL${NC}" fi fi # ───────────────────────────────────────────────────── # Step 6: Summary Report # ───────────────────────────────────────────────────── END_TIME=$(date +%s) TOTAL_DURATION=$((END_TIME - START_TIME)) section "Step 6: Summary Report" echo "" echo -e " ${BOLD}OpenClaw Production Check Summary${NC}" echo -e " ────────────────────────────────" # Print each step status print_status() { local label="$1" status="$2" case "$status" in PASS) echo -e " ${label}: ${GREEN}PASS${NC}" ;; FAIL) echo -e " ${label}: ${RED}FAIL${NC}" ;; WARN) echo -e " ${label}: ${YELLOW}WARN${NC}" ;; *) echo -e " ${label}: $status" ;; esac } ENV_STR=$($ENV_PASS && echo "PASS" || echo "FAIL") BASELINE_STR=$($BASELINE_PASS && echo "PASS" || echo "FAIL") GUARD_STR=$($GUARD_PASS && echo "PASS" || echo "FAIL") print_status "Environment" "$ENV_STR" print_status "Baseline Tests" "$BASELINE_STR" print_status "Architecture Guard" "$GUARD_STR" print_status "Deprecation Check" "$DEPRECATION_STATUS" print_status "Smoke Test" "$($SMOKE_PASS && echo "PASS" || echo "FAIL")" echo "" OVERALL_STR=$($OVERALL_PASS && echo "PASS" || echo "FAIL") echo -e " ${BOLD}Overall:${NC} $($OVERALL_PASS && echo -e "${GREEN}PASS${NC}" || echo -e "${RED}FAIL${NC}")" echo -e " ${BOLD}Duration:${NC} ${TOTAL_DURATION}s" # Failure details if ! $OVERALL_PASS; then echo "" echo -e " ${RED}${BOLD}Failed Step:${NC} ${FAILED_STEP}" echo -e " ${RED}${BOLD}Reason:${NC} ${FAIL_REASON}" echo -e " ${YELLOW}${BOLD}Suggested Fix:${NC} ${FAIL_FIX}" fi echo "" echo -e "${CYAN}══════════════════════════════════════════════${NC}" if $OVERALL_PASS; then echo -e "${GREEN} ✓ PRODUCTION CHECK PASSED — Safe to proceed${NC}" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo "" exit 0 else echo -e "${RED} ✗ PRODUCTION CHECK FAILED — Fix before proceeding${NC}" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo "" exit 1 fi