From 9fb7c15ebfcf52118803c246be7af35bc1a8653c Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 24 Mar 2026 16:21:24 +0000 Subject: [PATCH] Govern dashboard storage presentation helper --- .../subsystems/deployment-installability.md | 4 ++ .../v6/internal/subsystems/registry.json | 5 +- .../internal/subsystems/storage-recovery.md | 6 ++- scripts/hot-dev.sh | 7 +-- .../canonical_completion_guard_test.py | 50 +++++++++++++++++++ .../release_control/subsystem_lookup_test.py | 44 ++++++++++++++++ scripts/tests/test-hot-dev-bg.sh | 25 ++++++++++ 7 files changed, 135 insertions(+), 6 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 0435860784..1d952b9a4a 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -199,6 +199,10 @@ operational parity with the repo-root entry surface for the canonical controls: start, status, logs, stop, restart, managed backend restart, verification, and the explicit foreground escape hatch. The only intentionally narrower frontend workspace exception is the named `dev:frontend-only` raw Vite escape hatch. +That foreground escape hatch contract also applies to `scripts/hot-dev.sh` +itself: its self-description and usage guidance must point operators back to +the canonical managed `npm run dev` path for normal work and reserve +`hot-dev.sh` for explicit foreground/manual troubleshooting. That same dev-runtime helper boundary also includes the auxiliary operator controls that start, stop, restart, or recover local development. The repo-root Makefile targets, `scripts/toggle-mock.sh`, and `scripts/clean-mock-alerts.sh` must diff --git a/docs/release-control/v6/internal/subsystems/registry.json b/docs/release-control/v6/internal/subsystems/registry.json index 18cb083715..0287e77426 100644 --- a/docs/release-control/v6/internal/subsystems/registry.json +++ b/docs/release-control/v6/internal/subsystems/registry.json @@ -3719,6 +3719,7 @@ "frontend-modern/src/pages/Storage.tsx", "frontend-modern/src/types/recovery.ts", "frontend-modern/src/utils/dashboardRecoveryPresentation.ts", + "frontend-modern/src/utils/dashboardStoragePresentation.ts", "frontend-modern/src/utils/recoveryActionPresentation.ts", "frontend-modern/src/utils/recoveryArtifactModePresentation.ts", "frontend-modern/src/utils/recoveryDatePresentation.ts", @@ -3829,7 +3830,8 @@ ], "match_files": [ "frontend-modern/src/components/Storage/DashboardStoragePanel.tsx", - "frontend-modern/src/pages/Storage.tsx" + "frontend-modern/src/pages/Storage.tsx", + "frontend-modern/src/utils/dashboardStoragePresentation.ts" ], "allow_same_subsystem_tests": false, "test_prefixes": [], @@ -3849,6 +3851,7 @@ "frontend-modern/src/features/storageBackups/__tests__/storagePagePresentation.test.ts", "frontend-modern/src/features/storageBackups/__tests__/storagePoolsTablePresentation.test.ts", "frontend-modern/src/pages/__tests__/Storage.helpers.test.ts", + "frontend-modern/src/utils/__tests__/dashboardStoragePresentation.test.ts", "frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts" ] } diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index 3697f283d3..73ab56420a 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -250,9 +250,11 @@ coverage only through the full recovery route or broader dashboard shells. The storage dashboard entry point must be treated the same way on the storage side: `frontend-modern/src/pages/Dashboard.tsx`, `frontend-modern/src/features/dashboardOverview/dashboardWidgets.ts`, and -`frontend-modern/src/components/Storage/DashboardStoragePanel.tsx` must stay on +`frontend-modern/src/components/Storage/DashboardStoragePanel.tsx` and +`frontend-modern/src/utils/dashboardStoragePresentation.ts` must stay on explicit direct dashboard/storage proof routing instead of borrowing release- -control coverage only from the broader storage page and model surfaces. +control coverage only from the broader storage page, component, and model +surfaces. That route shell now also composes the recent-alerts widget directly from the alert-owned `frontend-modern/src/components/Alerts/RecentAlertsPanel.tsx` surface instead of via a dashboard-panels-local alert implementation, so the diff --git a/scripts/hot-dev.sh b/scripts/hot-dev.sh index 508a5a408b..d9f769031a 100755 --- a/scripts/hot-dev.sh +++ b/scripts/hot-dev.sh @@ -1,5 +1,5 @@ #!/bin/bash -# hot-dev.sh - Development server with hot-reload for Pulse +# hot-dev.sh - Foreground Pulse dev runtime escape hatch # # This script runs a local development environment with: # - Go backend with auto-rebuild on file changes (via inotifywait) @@ -24,8 +24,9 @@ # - HMAC event signing for tamper detection # # Usage: -# ./scripts/hot-dev.sh # Standard dev mode -# HOT_DEV_USE_PROD_DATA=true ./scripts/hot-dev.sh # Use production data +# npm run dev # Canonical managed dev runtime +# ./scripts/hot-dev.sh # Foreground/manual runtime troubleshooting +# HOT_DEV_USE_PROD_DATA=true ./scripts/hot-dev.sh # Foreground/manual runtime with production data # set -euo pipefail diff --git a/scripts/release_control/canonical_completion_guard_test.py b/scripts/release_control/canonical_completion_guard_test.py index 3da8f3b52e..cf0e10aab7 100644 --- a/scripts/release_control/canonical_completion_guard_test.py +++ b/scripts/release_control/canonical_completion_guard_test.py @@ -990,6 +990,56 @@ class CanonicalCompletionGuardTest(unittest.TestCase): "frontend-modern/src/features/storageBackups/__tests__/storagePagePresentation.test.ts", "frontend-modern/src/features/storageBackups/__tests__/storagePoolsTablePresentation.test.ts", "frontend-modern/src/pages/__tests__/Storage.helpers.test.ts", + "frontend-modern/src/utils/__tests__/dashboardStoragePresentation.test.ts", + "frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts", + ], + } + ], + ) + + def test_dashboard_storage_presentation_change_requires_storage_recovery_contract(self): + required = infer_impacted_subsystems( + ["frontend-modern/src/utils/dashboardStoragePresentation.ts"] + ) + self.assertEqual(set(required), {"storage-recovery"}) + + recovery = required["storage-recovery"] + self.assertEqual( + recovery["contract"], + "docs/release-control/v6/internal/subsystems/storage-recovery.md", + ) + self.assertEqual( + recovery["touched_runtime_files"], + ["frontend-modern/src/utils/dashboardStoragePresentation.ts"], + ) + self.assertEqual( + recovery["verification_requirements"], + [ + { + "id": "storage-product-surface", + "label": "storage product surface proof", + "touched_runtime_files": [ + "frontend-modern/src/utils/dashboardStoragePresentation.ts" + ], + "allow_same_subsystem_tests": False, + "test_prefixes": [], + "exact_files": [ + "frontend-modern/src/components/Storage/__tests__/DashboardStoragePanel.test.tsx", + "frontend-modern/src/components/Storage/__tests__/DiskList.test.tsx", + "frontend-modern/src/components/Storage/__tests__/Storage.test.tsx", + "frontend-modern/src/components/Storage/__tests__/StorageControls.test.tsx", + "frontend-modern/src/components/Storage/__tests__/StorageGroupRow.test.tsx", + "frontend-modern/src/components/Storage/__tests__/StoragePoolDetail.test.tsx", + "frontend-modern/src/components/Storage/code_standards.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/resourceStorageMapping.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/storageAdapters.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/storageAlertState.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/storageDomain.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/storageModelCore.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/storagePagePresentation.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/storagePoolsTablePresentation.test.ts", + "frontend-modern/src/pages/__tests__/Storage.helpers.test.ts", + "frontend-modern/src/utils/__tests__/dashboardStoragePresentation.test.ts", "frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts", ], } diff --git a/scripts/release_control/subsystem_lookup_test.py b/scripts/release_control/subsystem_lookup_test.py index 61dbb2bbf5..2f30a5b3c1 100644 --- a/scripts/release_control/subsystem_lookup_test.py +++ b/scripts/release_control/subsystem_lookup_test.py @@ -894,6 +894,50 @@ class SubsystemLookupTest(unittest.TestCase): "frontend-modern/src/features/storageBackups/__tests__/storagePagePresentation.test.ts", "frontend-modern/src/features/storageBackups/__tests__/storagePoolsTablePresentation.test.ts", "frontend-modern/src/pages/__tests__/Storage.helpers.test.ts", + "frontend-modern/src/utils/__tests__/dashboardStoragePresentation.test.ts", + "frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts", + ], + ) + + def test_lookup_paths_assigns_dashboard_storage_presentation_to_storage_recovery(self) -> None: + result = lookup_paths(["frontend-modern/src/utils/dashboardStoragePresentation.ts"]) + self.assertEqual(result["unowned_runtime_files"], []) + self.assertEqual( + {item["subsystem"] for item in result["impacted_subsystems"]}, + {"storage-recovery"}, + ) + file_entry = result["files"][0] + self.assertEqual(file_entry["classification"], "runtime") + self.assertEqual( + {match["subsystem"] for match in file_entry["matches"]}, + {"storage-recovery"}, + ) + match = file_entry["matches"][0] + self.assertEqual( + match["contract"], + "docs/release-control/v6/internal/subsystems/storage-recovery.md", + ) + self.assertEqual(match["lane_context"]["lane_id"], "L15") + self.assertEqual(match["verification_requirement"]["id"], "storage-product-surface") + self.assertEqual( + match["verification_requirement"]["exact_files"], + [ + "frontend-modern/src/components/Storage/__tests__/DashboardStoragePanel.test.tsx", + "frontend-modern/src/components/Storage/__tests__/DiskList.test.tsx", + "frontend-modern/src/components/Storage/__tests__/Storage.test.tsx", + "frontend-modern/src/components/Storage/__tests__/StorageControls.test.tsx", + "frontend-modern/src/components/Storage/__tests__/StorageGroupRow.test.tsx", + "frontend-modern/src/components/Storage/__tests__/StoragePoolDetail.test.tsx", + "frontend-modern/src/components/Storage/code_standards.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/resourceStorageMapping.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/storageAdapters.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/storageAlertState.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/storageDomain.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/storageModelCore.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/storagePagePresentation.test.ts", + "frontend-modern/src/features/storageBackups/__tests__/storagePoolsTablePresentation.test.ts", + "frontend-modern/src/pages/__tests__/Storage.helpers.test.ts", + "frontend-modern/src/utils/__tests__/dashboardStoragePresentation.test.ts", "frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts", ], ) diff --git a/scripts/tests/test-hot-dev-bg.sh b/scripts/tests/test-hot-dev-bg.sh index 9c313d6a44..97decd171d 100755 --- a/scripts/tests/test-hot-dev-bg.sh +++ b/scripts/tests/test-hot-dev-bg.sh @@ -48,6 +48,20 @@ assert_contains() { fi } +assert_not_contains() { + local desc="$1" + local haystack="$2" + local needle="$3" + + if [[ "${haystack}" == *"${needle}"* ]]; then + echo "[FAIL] ${desc}" >&2 + echo "Did not expect to find: ${needle}" >&2 + ((failures++)) + else + echo "[PASS] ${desc}" + fi +} + pick_free_port() { python3 - <<'PY' import socket @@ -297,6 +311,16 @@ PY assert_contains "frontend package keeps explicit frontend-only escape hatch" "${output}" "dev:frontend-only=vite" } +test_hot_dev_script_advertises_foreground_escape_hatch() { + local output + output="$(sed -n '1,30p' "${ROOT_DIR}/scripts/hot-dev.sh")" + + assert_contains "hot-dev header identifies foreground escape hatch" "${output}" "hot-dev.sh - Foreground Pulse dev runtime escape hatch" + assert_contains "hot-dev usage points to managed runtime first" "${output}" "npm run dev # Canonical managed dev runtime" + assert_contains "hot-dev usage reserves direct script for manual troubleshooting" "${output}" "./scripts/hot-dev.sh # Foreground/manual runtime troubleshooting" + assert_not_contains "hot-dev usage no longer claims standard dev mode" "${output}" "Standard dev mode" +} + 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)" @@ -525,6 +549,7 @@ main() { test_launchd_setup_advertises_managed_runtime_controls test_root_package_exposes_managed_runtime_entrypoints test_frontend_package_exposes_managed_runtime_entrypoints + test_hot_dev_script_advertises_foreground_escape_hatch test_clean_mock_alerts_prefers_managed_runtime test_dev_check_uses_managed_runtime_status test_backend_restart_requires_managed_runtime