From 9b9ee97f850443187ec0918d0cacb221d4c7915b Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 24 Mar 2026 15:49:29 +0000 Subject: [PATCH] Route mock alert cleanup through managed runtime --- .../subsystems/deployment-installability.md | 5 + scripts/clean-mock-alerts.sh | 27 +++- scripts/tests/test-hot-dev-bg.sh | 115 ++++++++++++++++++ 3 files changed, 142 insertions(+), 5 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index ce2f2b94b..acaa03f74 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -199,6 +199,11 @@ Makefile targets, `scripts/toggle-mock.sh`, and `scripts/clean-mock-alerts.sh` m route through the managed runtime control plane when they are operating on the local dev stack, instead of resurrecting lane-local `hot-dev.sh` or raw Vite process management through separate shell folklore. +When `scripts/clean-mock-alerts.sh` needs to quiesce a local dev runtime, it +must stop the managed session through `hot-dev-bg` before touching legacy +compatibility services, and its operator recovery guidance must point back to +the canonical repo-root `npm run dev` and `npm run dev:foreground` controls +instead of treating `pulse-hot-dev` service management as the primary dev path. That same rule now extends to the macOS auto-start surface. The launchd helper may not boot a separate legacy foreground runtime beside the managed dev stack: `scripts/dev-launchd-wrapper.sh`, `scripts/dev-launchd-setup.sh`, and the diff --git a/scripts/clean-mock-alerts.sh b/scripts/clean-mock-alerts.sh index ee794539d..492199b4e 100755 --- a/scripts/clean-mock-alerts.sh +++ b/scripts/clean-mock-alerts.sh @@ -9,7 +9,9 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color -ALERT_HISTORY="/etc/pulse/alerts/alert-history.json" +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +HOT_DEV_BG_PATH="${HOT_DEV_BG_PATH:-${ROOT_DIR}/scripts/hot-dev-bg.sh}" +ALERT_HISTORY="${ALERT_HISTORY:-/etc/pulse/alerts/alert-history.json}" if ! command -v jq >/dev/null 2>&1; then echo -e "${RED}Error: jq is required but not installed${NC}" @@ -33,6 +35,16 @@ run_privileged() { fi } +managed_runtime_running() { + if [ ! -x "$HOT_DEV_BG_PATH" ]; then + return 1 + fi + + local status_output + status_output="$("$HOT_DEV_BG_PATH" status 2>/dev/null || true)" + [[ "$status_output" == *"[hot-dev-bg] Running (pid:"* ]] +} + if [ ! -f "$ALERT_HISTORY" ]; then echo -e "${RED}Error: Alert history file not found at $ALERT_HISTORY${NC}" exit 1 @@ -57,7 +69,12 @@ echo -e "${GREEN}✓ Backup created: $BACKUP_FILE${NC}" # Stop backend to prevent writes during cleanup echo "Stopping backend..." +if managed_runtime_running; then + echo "Stopping managed development runtime..." + "$HOT_DEV_BG_PATH" stop +fi pkill -x pulse 2>/dev/null || true +# Legacy compatibility for service-based deployments that are not using the managed dev runtime. run_privileged systemctl stop pulse-hot-dev 2>/dev/null || true run_privileged systemctl stop pulse 2>/dev/null || true run_privileged systemctl stop pulse-backend 2>/dev/null || true @@ -84,7 +101,7 @@ run_privileged chown pulse:pulse "$ALERT_HISTORY" echo -e "${GREEN}✓ Mock alerts removed successfully${NC}" echo "" echo "To restart the backend, run:" -echo " npm run dev (managed development runtime)" -echo " ./scripts/hot-dev.sh (foreground development escape hatch)" -echo " sudo systemctl start pulse (systemd)" -echo " sudo systemctl start pulse-backend (legacy)" +echo " (cd \"${ROOT_DIR}\" && npm run dev) (managed development runtime)" +echo " (cd \"${ROOT_DIR}\" && npm run dev:foreground) (foreground development escape hatch)" +echo " sudo systemctl start pulse (systemd)" +echo " sudo systemctl start pulse-backend (legacy)" diff --git a/scripts/tests/test-hot-dev-bg.sh b/scripts/tests/test-hot-dev-bg.sh index 06d00160f..0a245687e 100755 --- a/scripts/tests/test-hot-dev-bg.sh +++ b/scripts/tests/test-hot-dev-bg.sh @@ -6,6 +6,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" HOT_DEV_BG="${ROOT_DIR}/scripts/hot-dev-bg.sh" +CLEAN_MOCK_ALERTS="${ROOT_DIR}/scripts/clean-mock-alerts.sh" PACKAGE_JSON="${ROOT_DIR}/package.json" FRONTEND_PACKAGE_JSON="${ROOT_DIR}/frontend-modern/package.json" DEV_LAUNCHD_WRAPPER="${ROOT_DIR}/scripts/dev-launchd-wrapper.sh" @@ -251,6 +252,119 @@ PY assert_contains "frontend package keeps explicit frontend-only escape hatch" "${output}" "dev:frontend-only=vite" } +test_clean_mock_alerts_prefers_managed_runtime() { + local test_dir fake_bin alert_history fake_hot_dev_bg action_log output + test_dir="$(mktemp -d)" + temp_dirs+=("${test_dir}") + fake_bin="${test_dir}/bin" + mkdir -p "${fake_bin}" + action_log="${test_dir}/actions.log" + alert_history="${test_dir}/alert-history.json" + fake_hot_dev_bg="${test_dir}/hot-dev-bg.sh" + + cat > "${alert_history}" <<'EOF' +[ + { + "alert": { + "resourceId": "mock-resource-1" + } + }, + { + "alert": { + "resourceId": "real-resource-1" + } + } +] +EOF + + cat > "${fake_hot_dev_bg}" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +printf 'hot-dev-bg %s\n' "$*" >> "${ACTION_LOG}" +case "${1:-}" in + status) + echo "[hot-dev-bg] Running (pid: 12345)" + ;; + stop) + echo "[hot-dev-bg] Stopped" + ;; + *) + echo "unexpected hot-dev-bg command: $*" >&2 + exit 1 + ;; +esac +EOF + chmod +x "${fake_hot_dev_bg}" + + cat > "${fake_bin}/sudo" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +printf 'sudo %s\n' "$*" >> "${ACTION_LOG}" +exec "$@" +EOF + chmod +x "${fake_bin}/sudo" + + cat > "${fake_bin}/systemctl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +printf 'systemctl %s\n' "$*" >> "${ACTION_LOG}" +exit 0 +EOF + chmod +x "${fake_bin}/systemctl" + + cat > "${fake_bin}/chown" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +printf 'chown %s\n' "$*" >> "${ACTION_LOG}" +exit 0 +EOF + chmod +x "${fake_bin}/chown" + + cat > "${fake_bin}/pkill" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +printf 'pkill %s\n' "$*" >> "${ACTION_LOG}" +exit 0 +EOF + chmod +x "${fake_bin}/pkill" + + output="$( + PATH="${fake_bin}:$PATH" \ + ACTION_LOG="${action_log}" \ + ALERT_HISTORY="${alert_history}" \ + HOT_DEV_BG_PATH="${fake_hot_dev_bg}" \ + "${CLEAN_MOCK_ALERTS}" + )" + + local actions + actions="$(cat "${action_log}")" + assert_contains "clean-mock-alerts checks managed runtime state" "${actions}" "hot-dev-bg status" + assert_contains "clean-mock-alerts stops managed runtime first" "${actions}" "hot-dev-bg stop" + assert_contains "clean-mock-alerts still stops compatibility services" "${actions}" "systemctl stop pulse-hot-dev" + assert_contains "clean-mock-alerts advertises managed restart" "${output}" "npm run dev" + assert_contains "clean-mock-alerts advertises foreground escape hatch" "${output}" "npm run dev:foreground" + + local order_output + order_output="$( + ACTION_LOG_PATH="${action_log}" python3 - <<'PY' +import os +from pathlib import Path + +lines = Path(os.environ["ACTION_LOG_PATH"]).read_text(encoding="utf-8").splitlines() +stop_index = next(i for i, line in enumerate(lines) if line == "hot-dev-bg stop") +systemctl_index = next(i for i, line in enumerate(lines) if line == "systemctl stop pulse-hot-dev") +print(f"managed_before_systemctl={stop_index < systemctl_index}") +PY + )" + assert_contains "managed runtime stop precedes legacy service stop" "${order_output}" "managed_before_systemctl=True" + + local cleaned_count mock_count + cleaned_count="$(jq 'length' "${alert_history}")" + mock_count="$(jq '[.[] | select((.alert.resourceId // "" | contains("mock")))] | length' "${alert_history}")" + assert_contains "clean-mock-alerts removes only mock alerts" "${cleaned_count}" "1" + assert_contains "clean-mock-alerts leaves no mock alerts" "${mock_count}" "0" +} + test_backend_restart_requires_managed_runtime() { local frontend_port backend_port output local state_dir @@ -330,6 +444,7 @@ main() { test_launchd_wrapper_uses_managed_supervisor test_root_package_exposes_managed_runtime_entrypoints test_frontend_package_exposes_managed_runtime_entrypoints + test_clean_mock_alerts_prefers_managed_runtime test_backend_restart_requires_managed_runtime test_status_without_runtime test_detects_unmanaged_listeners