Stabilize managed hot-dev startup

This commit is contained in:
rcourtman 2026-05-14 21:40:07 +01:00
parent 7d6b447c59
commit fbcb77d635
8 changed files with 138 additions and 52 deletions

View file

@ -63,46 +63,47 @@ server-side update execution surfaces.
38. `scripts/hot-dev-bg.sh`
39. `scripts/hot-dev.sh`
40. `scripts/lib/hot-dev-runtime.sh`
40. `scripts/install-container-agent.sh`
41. `install.sh`
42. `scripts/install.ps1`
43. `scripts/install.sh`
44. `scripts/install-mcp.sh`
45. `scripts/install-mcp.ps1`
46. `cmd/pulse-mcp/`
47. `scripts/pulse-auto-update.sh`
48. `scripts/release_control/internal/record_rc_to_ga_rehearsal.py`
49. `scripts/release_control/record_rc_to_ga_rehearsal.py`
50. `scripts/release_control/release_promotion_policy_support.py`
51. `scripts/release_control/resolve_release_promotion.py`
52. `scripts/release_ldflags.sh`
53. `scripts/run_cloud_public_signup_smoke.sh`
54. `scripts/run_demo_public_browser_smoke.sh`
55. `scripts/demo_public_browser_smoke.cjs`
56. `scripts/run_hosted_staging_smoke.sh`
57. `scripts/trigger-release-dry-run.sh`
58. `scripts/trigger-release.sh`
59. `scripts/toggle-mock.sh`
60. `deploy/helm/pulse/`
61. `tests/integration/playwright.config.ts`
62. `tests/integration/QUICK_START.md`
63. `tests/integration/README.md`
64. `tests/integration/scripts/bootstrap-hosted-mobile-onboarding.mjs`
65. `tests/integration/scripts/hosted-mobile-token-runtime.mjs`
66. `tests/integration/scripts/hosted-tenant-approval-store.mjs`
67. `tests/integration/scripts/hosted-tenant-runtime.mjs`
68. `tests/integration/scripts/hosted-tenant-runtime-restart.mjs`
69. `tests/integration/scripts/managed-dev-runtime.mjs`
70. `tests/integration/scripts/relay-mobile-token-helper.go`
71. `tests/integration/tests/helpers.ts`
72. `tests/integration/tests/runtime-defaults.ts`
73. `docker-compose.yml`
74. `scripts/install-docker.sh`
75. `scripts/validate-published-release.sh`
76. `scripts/validate-release.sh`
77. `scripts/release_asset_common.sh`
78. `scripts/backfill-release-assets.sh`
79. `.github/workflows/backfill-release-assets.yml`
41. `scripts/lib/hot-dev-auth.sh`
42. `scripts/install-container-agent.sh`
43. `install.sh`
44. `scripts/install.ps1`
45. `scripts/install.sh`
46. `scripts/install-mcp.sh`
47. `scripts/install-mcp.ps1`
48. `cmd/pulse-mcp/`
49. `scripts/pulse-auto-update.sh`
50. `scripts/release_control/internal/record_rc_to_ga_rehearsal.py`
51. `scripts/release_control/record_rc_to_ga_rehearsal.py`
52. `scripts/release_control/release_promotion_policy_support.py`
53. `scripts/release_control/resolve_release_promotion.py`
54. `scripts/release_ldflags.sh`
55. `scripts/run_cloud_public_signup_smoke.sh`
56. `scripts/run_demo_public_browser_smoke.sh`
57. `scripts/demo_public_browser_smoke.cjs`
58. `scripts/run_hosted_staging_smoke.sh`
59. `scripts/trigger-release-dry-run.sh`
60. `scripts/trigger-release.sh`
61. `scripts/toggle-mock.sh`
62. `deploy/helm/pulse/`
63. `tests/integration/playwright.config.ts`
64. `tests/integration/QUICK_START.md`
65. `tests/integration/README.md`
66. `tests/integration/scripts/bootstrap-hosted-mobile-onboarding.mjs`
67. `tests/integration/scripts/hosted-mobile-token-runtime.mjs`
68. `tests/integration/scripts/hosted-tenant-approval-store.mjs`
69. `tests/integration/scripts/hosted-tenant-runtime.mjs`
70. `tests/integration/scripts/hosted-tenant-runtime-restart.mjs`
71. `tests/integration/scripts/managed-dev-runtime.mjs`
72. `tests/integration/scripts/relay-mobile-token-helper.go`
73. `tests/integration/tests/helpers.ts`
74. `tests/integration/tests/runtime-defaults.ts`
75. `docker-compose.yml`
76. `scripts/install-docker.sh`
77. `scripts/validate-published-release.sh`
78. `scripts/validate-release.sh`
79. `scripts/release_asset_common.sh`
80. `scripts/backfill-release-assets.sh`
81. `.github/workflows/backfill-release-assets.yml`
## Shared Boundaries
@ -170,7 +171,7 @@ server-side update execution surfaces.
enrollment runtime tokens while keeping deploy binding metadata limited to
deploy facts such as cluster, job, target, source agent, and expected node.
4. Add or change server update transport through `internal/api/updates.go` and `frontend-modern/src/api/updates.ts`
5. Add or change local dev-runtime orchestration, managed ownership, browser-runtime proof wiring, frontend/backend coherence diagnostics, canonical developer entry wrappers, dependency manifest floors, frontend build chunking, or dev-runtime helper control surfaces through `scripts/hot-dev.sh`, `scripts/hot-dev-bg.sh`, `scripts/lib/hot-dev-runtime.sh`, `scripts/dev-deploy-agent.sh`, `Makefile`, `package.json`, `package-lock.json`, `frontend-modern/package.json`, `frontend-modern/package-lock.json`, `frontend-modern/vite.config.ts`, `go.mod`, `go.sum`, `scripts/dev-check.sh`, `scripts/toggle-mock.sh`, `scripts/clean-mock-alerts.sh`, `scripts/dev-launchd-setup.sh`, `scripts/dev-launchd-wrapper.sh`, `scripts/run_demo_public_browser_smoke.sh`, `scripts/demo_public_browser_smoke.cjs`, `scripts/com.pulse.hot-dev.plist.template`, `tests/integration/scripts/managed-dev-runtime.mjs`, `tests/integration/playwright.config.ts`, `tests/integration/tests/helpers.ts`, `tests/integration/tests/runtime-defaults.ts`, `tests/integration/README.md`, and `tests/integration/QUICK_START.md`
5. Add or change local dev-runtime orchestration, managed ownership, browser-runtime proof wiring, frontend/backend coherence diagnostics, canonical developer entry wrappers, deterministic dev auth seeding, dependency manifest floors, frontend build chunking, or dev-runtime helper control surfaces through `scripts/hot-dev.sh`, `scripts/hot-dev-bg.sh`, `scripts/lib/hot-dev-runtime.sh`, `scripts/lib/hot-dev-auth.sh`, `scripts/dev-deploy-agent.sh`, `Makefile`, `package.json`, `package-lock.json`, `frontend-modern/package.json`, `frontend-modern/package-lock.json`, `frontend-modern/vite.config.ts`, `go.mod`, `go.sum`, `scripts/dev-check.sh`, `scripts/toggle-mock.sh`, `scripts/clean-mock-alerts.sh`, `scripts/dev-launchd-setup.sh`, `scripts/dev-launchd-wrapper.sh`, `scripts/run_demo_public_browser_smoke.sh`, `scripts/demo_public_browser_smoke.cjs`, `scripts/com.pulse.hot-dev.plist.template`, `tests/integration/scripts/managed-dev-runtime.mjs`, `tests/integration/playwright.config.ts`, `tests/integration/tests/helpers.ts`, `tests/integration/tests/runtime-defaults.ts`, `tests/integration/README.md`, and `tests/integration/QUICK_START.md`
6. Add or change governed release-promotion workflow inputs, operator-facing promotion metadata, the canonical version file, prerelease feedback intake prompts, artifact publication lineage enforcement, release note or changelog packet composition, or stable-promotion rehearsal summaries through `.github/workflows/create-release.yml`, `.github/workflows/helm-pages.yml`, `.github/workflows/publish-docker.yml`, `.github/workflows/publish-helm-chart.yml`, `.github/workflows/promote-floating-tags.yml`, `.github/workflows/release-dry-run.yml`, `.github/workflows/update-demo-server.yml`, `.github/ISSUE_TEMPLATE/v6_rc_feedback.yml`, `docs/RELEASE_NOTES.md`, `docs/releases/`, `docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md`, `docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md`, `docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md`, `scripts/check-workflow-dispatch-inputs.py`, `scripts/release_control/render_release_body.py`, `scripts/release_control/record_rc_to_ga_rehearsal.py`, `scripts/release_control/internal/record_rc_to_ga_rehearsal.py`, `scripts/release_control/release_promotion_policy_support.py`, `scripts/trigger-release.sh`, and `scripts/trigger-release-dry-run.sh`
That release-promotion boundary also owns prerelease note packet lineage:
shipped RC notes must remain historically accurate, the top-level

View file

@ -2716,6 +2716,7 @@
"scripts/install-docker.sh",
"scripts/install.ps1",
"scripts/install.sh",
"scripts/lib/hot-dev-auth.sh",
"scripts/lib/hot-dev-runtime.sh",
"scripts/pulse-auto-update.sh",
"scripts/release_asset_common.sh",
@ -2970,11 +2971,12 @@
"scripts/dev-deploy-agent.sh",
"scripts/dev-launchd-setup.sh",
"scripts/dev-launchd-wrapper.sh",
"scripts/hot-dev-bg.sh",
"scripts/hot-dev.sh",
"scripts/lib/hot-dev-runtime.sh",
"scripts/toggle-mock.sh",
"tests/integration/playwright.config.ts",
"scripts/hot-dev-bg.sh",
"scripts/hot-dev.sh",
"scripts/lib/hot-dev-auth.sh",
"scripts/lib/hot-dev-runtime.sh",
"scripts/toggle-mock.sh",
"tests/integration/playwright.config.ts",
"tests/integration/QUICK_START.md",
"tests/integration/README.md",
"tests/integration/scripts/managed-dev-runtime.mjs",
@ -2984,10 +2986,11 @@
"allow_same_subsystem_tests": false,
"test_prefixes": [],
"exact_files": [
"scripts/release_control/ssh_host_key_policy_test.py",
"scripts/tests/test-hot-dev-bg.sh",
"scripts/tests/test-hot-dev-runtime.sh",
"scripts/tests/test-toggle-mock.sh",
"scripts/release_control/ssh_host_key_policy_test.py",
"scripts/tests/test-hot-dev-auth.sh",
"scripts/tests/test-hot-dev-bg.sh",
"scripts/tests/test-hot-dev-runtime.sh",
"scripts/tests/test-toggle-mock.sh",
"tests/integration/tests/16-dev-runtime-recovery.spec.ts"
]
},

View file

@ -431,6 +431,7 @@ start_hot_dev_child() {
PULSE_DEV_API_PORT="${PULSE_DEV_API_PORT}" \
PULSE_DEV_API_URL="${PULSE_DEV_API_URL}" \
PULSE_DEV_WS_URL="${PULSE_DEV_WS_URL}" \
HOT_DEV_SKIP_NPM_CLEANUP=true \
ROOT_DIR="${ROOT_DIR}" \
python3 - <<'PY' &
import os

View file

@ -258,6 +258,47 @@ kill_port() {
lsof -i :"${port}" 2>/dev/null | awk 'NR>1 {print $2}' | xargs -r kill -9 2>/dev/null || true
}
process_parent_id() {
local pid=$1
ps -o ppid= -p "${pid}" 2>/dev/null | tr -d '[:space:]'
}
is_current_shell_descendant_of() {
local target_pid=$1
local current_pid=$$
local parent_pid
[[ -n "${target_pid}" ]] || return 1
while [[ -n "${current_pid}" && "${current_pid}" != "1" ]]; do
parent_pid="$(process_parent_id "${current_pid}")"
[[ -n "${parent_pid}" && "${parent_pid}" != "${current_pid}" ]] || break
if [[ "${parent_pid}" == "${target_pid}" ]]; then
return 0
fi
current_pid="${parent_pid}"
done
return 1
}
kill_stale_npm_dev_wrappers() {
local pid
if [[ "${HOT_DEV_SKIP_NPM_CLEANUP:-false}" == "true" ]]; then
return 0
fi
while IFS= read -r pid; do
[[ -n "${pid}" ]] || continue
[[ "${pid}" != "$$" ]] || continue
if is_current_shell_descendant_of "${pid}"; then
continue
fi
kill "${pid}" 2>/dev/null || true
done < <(pgrep -f "npm run dev" 2>/dev/null || true)
}
log_info "Cleaning up existing processes..."
# OS-Specific Cleanup
@ -271,7 +312,7 @@ fi
pkill -f "backend-watch.sh" 2>/dev/null || true
# Only kill vite/npm processes that look like ours (simple check)
pkill -f "vite" 2>/dev/null || true
pkill -f "npm run dev" 2>/dev/null || true
kill_stale_npm_dev_wrappers
pkill -x "pulse" 2>/dev/null || true
sleep 1

View file

@ -56,7 +56,7 @@ hot_dev_sync_auth_env_file() {
printf "PULSE_AUTH_PASS='%s'\n" "$(hot_dev_single_quote "${auth_pass}")"
if [[ -f "${runtime_env}" ]]; then
grep -v -E '^(# Managed by hot-dev\.sh for deterministic dev auth|PULSE_AUTH_USER=|PULSE_AUTH_PASS=)' "${runtime_env}"
grep -v -E '^(# Managed by hot-dev\.sh for deterministic dev auth|PULSE_AUTH_USER=|PULSE_AUTH_PASS=)' "${runtime_env}" || true
fi
} > "${tmp_file}"

View file

@ -104,10 +104,39 @@ EOF
assert_contains "sync preserves mock settings" "${output}" "PULSE_MOCK_MODE=false"
}
test_sync_auth_env_file_handles_managed_only_env_under_errexit() {
local state_dir runtime_env output
state_dir="$(make_temp_dir)"
runtime_env="${state_dir}/.env"
cat > "${runtime_env}" <<'EOF'
# Managed by hot-dev.sh for deterministic dev auth
PULSE_AUTH_USER='admin'
PULSE_AUTH_PASS='stale-pass'
EOF
output="$(
HOT_DEV_AUTH_LIB="${HOT_DEV_AUTH_LIB}" \
RUNTIME_ENV_PATH="${runtime_env}" \
bash -lc '
set -euo pipefail
source "${HOT_DEV_AUTH_LIB}"
hot_dev_sync_auth_env_file "${RUNTIME_ENV_PATH}" "admin" "${HOT_DEV_DEFAULT_AUTH_HASH}"
printf "survived=yes\n"
cat "${RUNTIME_ENV_PATH}"
'
)"
assert_contains "managed-only env does not trip errexit" "${output}" "survived=yes"
assert_contains "managed-only sync keeps auth user" "${output}" "PULSE_AUTH_USER='admin'"
assert_contains "managed-only sync rewrites auth password hash" "${output}" "PULSE_AUTH_PASS='${HOT_DEV_DEFAULT_AUTH_HASH}'"
}
source "${HOT_DEV_AUTH_LIB}"
test_default_auth_contract
test_custom_auth_banner_contract
test_sync_auth_env_file_preserves_non_auth_settings
test_sync_auth_env_file_handles_managed_only_env_under_errexit
if (( failures > 0 )); then
echo "FAIL: ${failures} hot-dev auth assertions failed" >&2

View file

@ -720,6 +720,7 @@ test_hot_dev_bg_script_advertises_managed_entrypoint() {
assert_contains "hot-dev-bg routes log guidance to managed wrapper" "${output}" "Check logs with: npm run dev:logs"
assert_contains "hot-dev-bg routes verify guidance to managed wrapper" "${output}" "Rerun with: npm run dev:verify"
assert_contains "hot-dev-bg routes launchd supervision guidance to managed wrapper" "${output}" "Rerun with: npm run dev"
assert_contains "hot-dev-bg managed child skips npm wrapper cleanup" "${output}" "HOT_DEV_SKIP_NPM_CLEANUP=true"
}
test_hot_dev_bg_usage_prefers_managed_wrappers() {

View file

@ -127,11 +127,21 @@ test_hot_dev_keeps_backend_launch_errors_in_debug_log() {
assert_contains "backend LOG_FILE follows debug log override" "${output}" "LOG_FILE=\"\${BACKEND_DEBUG_LOG}\""
}
test_hot_dev_avoids_self_killing_npm_wrapper() {
local output
output="$(sed -n '1,330p' "${HOT_DEV}")"
assert_contains "hot-dev routes npm cleanup through a guarded helper" "${output}" "kill_stale_npm_dev_wrappers"
assert_contains "hot-dev supports managed skip for npm cleanup" "${output}" "HOT_DEV_SKIP_NPM_CLEANUP"
assert_not_contains "hot-dev no longer broad-kills npm dev wrappers" "${output}" 'pkill -f "npm run dev"'
}
source "${HOT_DEV_RUNTIME_LIB}"
test_pulse_process_count_handles_zero_matches_under_pipefail
test_pulse_process_count_counts_matching_processes
test_hot_dev_uses_resilient_backend_process_count
test_hot_dev_keeps_backend_launch_errors_in_debug_log
test_hot_dev_avoids_self_killing_npm_wrapper
if (( failures > 0 )); then
echo "FAIL: ${failures} hot-dev runtime assertions failed" >&2