diff --git a/admin/system-health.html b/admin/system-health.html index b62f232d7..5294d7d6e 100644 --- a/admin/system-health.html +++ b/admin/system-health.html @@ -40,6 +40,7 @@

Admin

Environment Identity

Environment Map

+

Environment Health Comparison

Environment Capabilities

Health API Contract

Admin API Registry

@@ -110,6 +111,25 @@

System Health Tables

+
+ + + + + + + + + + + + + + + + +
Environment Health Comparison
EnvironmentHosting modelRuntime expectationDatabase modelStorage folderStateStatus
Environment comparisonReference-onlyWaiting for safe API statusReference-onlyReference-onlyUnavailablePENDING
+
diff --git a/assets/theme-v2/js/admin-system-health.js b/assets/theme-v2/js/admin-system-health.js index 21bc50a63..49e0593b1 100644 --- a/assets/theme-v2/js/admin-system-health.js +++ b/assets/theme-v2/js/admin-system-health.js @@ -69,6 +69,7 @@ class AdminSystemHealthController { this.historyRows = root.querySelector("[data-admin-system-health-history-rows]"); this.apiContractRows = root.querySelector("[data-admin-system-health-api-contract-rows]"); this.apiRegistryRows = root.querySelector("[data-admin-system-health-api-registry-rows]"); + this.environmentComparisonRows = root.querySelector("[data-admin-system-health-environment-comparison-rows]"); this.capabilityRows = root.querySelector("[data-admin-system-health-capability-rows]"); this.featureFlagRows = root.querySelector("[data-admin-system-health-feature-flag-rows]"); this.actionRows = root.querySelector("[data-admin-system-health-action-rows]"); @@ -160,6 +161,7 @@ class AdminSystemHealthController { this.renderPostgresMetricsPending(reason); this.renderStoragePending(reason); this.renderRuntimeHealthPending(reason); + this.renderEnvironmentComparisonPending(reason); this.renderEnvironmentCapabilitiesPending(reason); this.renderApiContractPending(reason); this.renderAdminApiRegistryPending(reason); @@ -203,6 +205,53 @@ class AdminSystemHealthController { this.setEnvironmentStatus("lastHealthCheck", environmentIdentity.lastHealthCheck ? "PASS" : "WARN", reason); } + renderEnvironmentComparisonPending(reason) { + if (!this.environmentComparisonRows) { + return; + } + const row = document.createElement("tr"); + row.append( + this.createCell("Environment comparison"), + this.createCell("Reference-only"), + this.createCell("not available"), + this.createCell("Reference-only"), + this.createCell("Reference-only"), + this.createCell("Unavailable"), + this.createStatusCell("PENDING", reason), + ); + this.environmentComparisonRows.replaceChildren(row); + } + + renderEnvironmentComparison(environmentComparison = {}) { + if (!this.environmentComparisonRows) { + return; + } + if (environmentComparison?.secretsExposed === true || environmentComparison?.secretEditingAllowed === true) { + this.renderEnvironmentComparisonPending("Safe environment comparison response was blocked because it exposed secret controls."); + return; + } + const rows = Array.isArray(environmentComparison.rows) ? environmentComparison.rows : []; + if (!rows.length) { + this.renderEnvironmentComparisonPending("Safe Admin System Health API returned no environment comparison rows."); + return; + } + const fragment = document.createDocumentFragment(); + rows.forEach((comparisonRow) => { + const row = document.createElement("tr"); + row.append( + this.createCell(comparisonRow.displayName), + this.createCell(comparisonRow.hostingModel), + this.createCell(comparisonRow.runtimeExpectation), + this.createCell(comparisonRow.databaseModel), + this.createCell(comparisonRow.storageFolder), + this.createCell(comparisonRow.state), + this.createStatusCell(comparisonRow.status, comparisonRow.summary || environmentComparison.message), + ); + fragment.append(row); + }); + this.environmentComparisonRows.replaceChildren(fragment); + } + renderStoragePending(reason) { ["bucket", "list", "upload", "read", "delete", "lastChecked"].forEach((key) => { this.setStorageStatus(key, "PENDING", reason); @@ -893,6 +942,7 @@ class AdminSystemHealthController { return; } this.renderEnvironmentIdentity(data?.environmentIdentity || {}); + this.renderEnvironmentComparison(data?.environmentComparison || {}); this.renderPostgresStatus(data?.databaseStatus || {}); this.renderStartupDiagnostics(data?.localApiStartup || {}); this.renderStorageStatus(data?.storageStatus || {}); diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison.md b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison.md new file mode 100644 index 000000000..a21a4baff --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison.md @@ -0,0 +1,33 @@ +# PR_26177_CHARLIE_031-environment-health-comparison + +Team: Charlie +Branch: pr/26177-CHARLIE-031-environment-health-comparison +Base: pr/26177-CHARLIE-030-r2-storage-health-expanded-validation +Lifecycle: Build / Validation +Repair: Rebased onto repaired PR_26177_CHARLIE_030 branch on 2026-06-25. + +## Scope +- Added a server-owned Environment Health Comparison payload and System Health table. +- Shows Local (VS Code), DEV, IST, UAT, and PROD as a reference comparison view. +- Marks only the current deployment as actively checked; peer environments are reference-only Not Configured or Unavailable. + +## Changed Files +- admin/system-health.html +- assets/theme-v2/js/admin-system-health.js +- src/dev-runtime/server/local-api-router.mjs +- tests/api/admin-system-health/contract.test.mjs +- tests/dev-runtime/AdminHealthOperations.test.mjs +- tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +- docs_build/dev/reports/coverage_changed_js_guardrail.txt +- docs_build/dev/reports/playwright_v8_coverage_report.txt + +## Validation +- PASS: node --check src/dev-runtime/server/local-api-router.mjs +- PASS: node --check assets/theme-v2/js/admin-system-health.js +- PASS: node --test tests/api/admin-system-health/contract.test.mjs +- PASS: node --test tests/dev-runtime/AdminHealthOperations.test.mjs +- PASS: npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line +- PASS: git diff --check + +## ZIP +- Generated after repair: C:\Users\DavidQ\Documents\GitHub\HTML-JavaScript-Gaming\tmp\PR_26177_CHARLIE_031-environment-health-comparison_delta.zip diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_branch-validation.md b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_branch-validation.md new file mode 100644 index 000000000..c6b7a27f6 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_branch-validation.md @@ -0,0 +1,17 @@ +# PR_26177_CHARLIE_031-environment-health-comparison Branch Validation + +Branch: pr/26177-CHARLIE-031-environment-health-comparison +Expected stack base: pr/26177-CHARLIE-030-r2-storage-health-expanded-validation +Current status at validation: +## pr/26177-CHARLIE-031-environment-health-comparison +Updated onto repaired PR_26177_CHARLIE_030 stack base. + +Result: PASS + +Checks: +- PASS: Branch created from PR 030 branch for the approved stacked chain. +- PASS: Active branch matches PR identity. +- PASS: Worktree contained only scoped PR changes and generated validation reports. +- PASS: Branch is based on repaired PR 030 branch. +- PASS: Rebase conflict scope was generated report artifacts only. +- PASS: No start_of_day files modified. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_manual-validation-notes.md new file mode 100644 index 000000000..0b516491f --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_manual-validation-notes.md @@ -0,0 +1,9 @@ +# PR_26177_CHARLIE_031-environment-health-comparison Manual Validation Notes + +- Confirmed Environment Health Comparison appears as a reference-only table. +- Confirmed DEV current deployment shows Current in the Playwright environment. +- Confirmed peer rows show Not Configured or Unavailable and are not actively checked. +- Confirmed PROD displays with R2 /prd without changing the existing PRD Environment Map. +- Confirmed no secrets or peer-environment health probes are exposed. +- Confirmed branch repair conflict was limited to generated report artifacts. +- Confirmed no start_of_day files changed. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_requirements-checklist.md b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_requirements-checklist.md new file mode 100644 index 000000000..4ca4e8f53 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_requirements-checklist.md @@ -0,0 +1,12 @@ +# PR_26177_CHARLIE_031-environment-health-comparison Requirement Checklist + +- PASS: Added a single System Health comparison view. +- PASS: Includes Local (VS Code), DEV, IST, UAT, and PROD. +- PASS: Shows clear state/status indicators per environment. +- PASS: Local (VS Code) represents local static server/API/developer runtime expectations. +- PASS: Unavailable or non-current environments do not have to pass. +- PASS: No silent defaults; peer rows explicitly show Not Configured or Unavailable. +- PASS: No cross-environment active checks added. +- PASS: Browser UI consumes API/service contract. +- PASS: No unrelated files or start_of_day files modified. +- PASS: Rebased onto repaired PR_26177_CHARLIE_030 branch. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_validation-lane.md b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_validation-lane.md new file mode 100644 index 000000000..3730a9234 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_validation-lane.md @@ -0,0 +1,21 @@ +# PR_26177_CHARLIE_031-environment-health-comparison Validation Lane Report + +Impacted lanes: +- runtime: Local API Admin System Health status payload. +- contract: Admin System Health API contract tests. +- UI: Admin System Health Theme V2 table rendering. +- Playwright: targeted Admin System Health page spec. + +Commands: +- PASS: node --check src/dev-runtime/server/local-api-router.mjs +- PASS: node --check assets/theme-v2/js/admin-system-health.js +- PASS: node --test tests/api/admin-system-health/contract.test.mjs +- PASS: node --test tests/dev-runtime/AdminHealthOperations.test.mjs +- PASS: npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line +- PASS: git diff --check + +Skipped lanes: +- Full samples smoke skipped; comparison view is Admin System Health only. +- Full Project Workspace suite skipped; targeted Admin/System Health coverage was sufficient. + +Result: PASS diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index ae2d25d52..fe0c7b785 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,45 +1,44 @@ -# git diff --name-only pr/26177-CHARLIE-029-system-health-postgres-metrics-panel -- +# git diff --name-only pr/26177-CHARLIE-030-r2-storage-health-expanded-validation -- admin/system-health.html assets/theme-v2/js/admin-system-health.js -docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation.md -docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_branch-validation.md -docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_manual-validation-notes.md -docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_requirements-checklist.md -docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_validation-lane.md +docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison.md +docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_branch-validation.md +docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_manual-validation-notes.md +docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_requirements-checklist.md +docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_validation-lane.md docs_build/dev/reports/codex_changed_files.txt docs_build/dev/reports/codex_review.diff docs_build/dev/reports/coverage_changed_js_guardrail.txt docs_build/dev/reports/playwright_v8_coverage_report.txt -src/api/admin-system-health-api-client.js src/dev-runtime/server/local-api-router.mjs tests/api/admin-system-health/contract.test.mjs tests/dev-runtime/AdminHealthOperations.test.mjs tests/playwright/tools/AdminHealthOperationsPage.spec.mjs # git status --short - M docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation.md - M docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_branch-validation.md - M docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_manual-validation-notes.md - M docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_requirements-checklist.md - M docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_validation-lane.md + M docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison.md + M docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_branch-validation.md + M docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_manual-validation-notes.md + M docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_requirements-checklist.md + M docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_validation-lane.md M docs_build/dev/reports/codex_review.diff + M docs_build/dev/reports/coverage_changed_js_guardrail.txt M docs_build/dev/reports/playwright_v8_coverage_report.txt -# git diff --stat pr/26177-CHARLIE-029-system-health-postgres-metrics-panel -- - admin/system-health.html | 13 +- - assets/theme-v2/js/admin-system-health.js | 41 +- - ...IE_030-r2-storage-health-expanded-validation.md | 35 + - ...health-expanded-validation_branch-validation.md | 17 + - ...-expanded-validation_manual-validation-notes.md | 9 + - ...h-expanded-validation_requirements-checklist.md | 13 + - ...e-health-expanded-validation_validation-lane.md | 22 + - docs_build/dev/reports/codex_changed_files.txt | 70 +- - docs_build/dev/reports/codex_review.diff | 988 +++++++++++---------- - .../dev/reports/coverage_changed_js_guardrail.txt | 6 +- - .../dev/reports/playwright_v8_coverage_report.txt | 17 +- - src/api/admin-system-health-api-client.js | 4 + - src/dev-runtime/server/local-api-router.mjs | 105 ++- - tests/api/admin-system-health/contract.test.mjs | 19 + - tests/dev-runtime/AdminHealthOperations.test.mjs | 6 + - .../tools/AdminHealthOperationsPage.spec.mjs | 8 +- - 16 files changed, 851 insertions(+), 522 deletions(-) +# git diff --stat pr/26177-CHARLIE-030-r2-storage-health-expanded-validation -- + admin/system-health.html | 20 + + assets/theme-v2/js/admin-system-health.js | 50 ++ + ...77_CHARLIE_031-environment-health-comparison.md | 33 + + ...ironment-health-comparison_branch-validation.md | 17 + + ...nt-health-comparison_manual-validation-notes.md | 9 + + ...ent-health-comparison_requirements-checklist.md | 12 + + ...nvironment-health-comparison_validation-lane.md | 21 + + docs_build/dev/reports/codex_changed_files.txt | 71 +-- + docs_build/dev/reports/codex_review.diff | 663 +-------------------- + .../dev/reports/coverage_changed_js_guardrail.txt | 3 +- + .../dev/reports/playwright_v8_coverage_report.txt | 8 +- + src/dev-runtime/server/local-api-router.mjs | 76 +++ + tests/api/admin-system-health/contract.test.mjs | 13 + + tests/dev-runtime/AdminHealthOperations.test.mjs | 9 + + .../tools/AdminHealthOperationsPage.spec.mjs | 11 + + 15 files changed, 305 insertions(+), 711 deletions(-) diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index 8ce24fdc9..9445ae236 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -1,662 +1 @@ -diff --git a/admin/system-health.html b/admin/system-health.html -index 5aa9fa80a..b62f232d7 100644 ---- a/admin/system-health.html -+++ b/admin/system-health.html -@@ -316,16 +316,17 @@ - - - -+ - - - - -- -- -- -- -- -- -+ -+ -+ -+ -+ -+ - -
Environment Capabilities
CheckTargetTimingStatus
Bucket connectivityLoadingPENDING
ListLoadingPENDING
UploadLoadingPENDING
ReadLoadingPENDING
DeleteLoadingPENDING
Last checkedLoadingPENDING
Bucket connectivityLoadingLoadingPENDING
ListLoadingLoadingPENDING
UploadLoadingLoadingPENDING
ReadLoadingLoadingPENDING
DeleteLoadingLoadingPENDING
Last checkedLoadingLoadingPENDING
-
-diff --git a/assets/theme-v2/js/admin-system-health.js b/assets/theme-v2/js/admin-system-health.js -index 746d47753..21bc50a63 100644 ---- a/assets/theme-v2/js/admin-system-health.js -+++ b/assets/theme-v2/js/admin-system-health.js -@@ -1,7 +1,7 @@ - import { - readAdminSystemHealthStatus, - runAdminSystemHealthAction, -- runAdminSystemHealthStorageConnectivityAction, -+ runAdminSystemHealthStorageExpandedValidation, - } from "../../../src/api/admin-system-health-api-client.js"; - import { - applyStatusNode, -@@ -54,6 +54,10 @@ class AdminSystemHealthController { - node.dataset.adminSystemHealthStorageStatus, - node, - ])); -+ this.storageTimings = new Map(Array.from(root.querySelectorAll("[data-admin-system-health-storage-timing]")).map((node) => [ -+ node.dataset.adminSystemHealthStorageTiming, -+ node, -+ ])); - this.runtimeHealthValues = new Map(Array.from(root.querySelectorAll("[data-admin-system-health-runtime-health-value]")).map((node) => [ - node.dataset.adminSystemHealthRuntimeHealthValue, - node, -@@ -122,6 +126,13 @@ class AdminSystemHealthController { - this.setStatusNode(node, status, reason); - } - -+ setStorageTiming(key, value, fallback) { -+ const node = this.storageTimings.get(key); -+ if (node) { -+ node.textContent = asText(value, fallback); -+ } -+ } -+ - setRuntimeHealthValue(key, value, fallback) { - const node = this.runtimeHealthValues.get(key); - if (node) { -@@ -195,6 +206,7 @@ class AdminSystemHealthController { - renderStoragePending(reason) { - ["bucket", "list", "upload", "read", "delete", "lastChecked"].forEach((key) => { - this.setStorageStatus(key, "PENDING", reason); -+ this.setStorageTiming(key, "not available"); - }); - this.renderRuntimePending(reason); - } -@@ -261,8 +273,10 @@ class AdminSystemHealthController { - const reason = storageStatus.message || "Cloudflare R2 configuration status returned by the safe Admin System Health API."; - this.setStorageValue("bucket", storageStatus.environmentFolder ? `${storageStatus.bucket || "not configured"} ${storageStatus.environmentFolder}` : storageStatus.bucket, "not configured"); - this.setStorageStatus("bucket", storageStatus.bucketStatus || storageStatus.status, reason); -+ this.setStorageTiming("bucket", storageStatus.lastChecked ? "configuration status" : "not available"); - this.setStorageValue("lastChecked", storageStatus.lastChecked, "not available"); - this.setStorageStatus("lastChecked", storageStatus.lastChecked ? "PASS" : "WARN", reason); -+ this.setStorageTiming("lastChecked", storageStatus.lastChecked ? "configuration status" : "not available"); - } - - renderRuntimeHealthPending(reason) { -@@ -684,9 +698,11 @@ class AdminSystemHealthController { - } - this.setStorageValue(key, this.storageResultTarget(result)); - this.setStorageStatus(key, result.status, result.message || "R2 diagnostic returned without a message."); -+ this.setStorageTiming(key, Number.isFinite(Number(result.durationMs)) ? `${result.durationMs} ms` : "not available"); - if (result.lastChecked) { - this.setStorageValue("lastChecked", result.lastChecked); - this.setStorageStatus("lastChecked", "PASS", "Most recent current-environment R2 health check timestamp."); -+ this.setStorageTiming("lastChecked", Number.isFinite(Number(result.durationMs)) ? `${result.durationMs} ms` : "not available"); - } - } - -@@ -732,15 +748,24 @@ class AdminSystemHealthController { - runStorageDiagnostics() { - STORAGE_DIAGNOSTIC_ACTIONS.forEach(({ key }) => { - this.setStorageStatus(key, "PENDING", "R2 diagnostic is running through the safe Admin System Health API."); -+ this.setStorageTiming(key, "running"); - }); -- STORAGE_DIAGNOSTIC_ACTIONS.forEach(({ actionId, key }) => { -- try { -- this.renderStorageResult(key, runAdminSystemHealthStorageConnectivityAction(actionId)); -- } catch (error) { -- const message = error instanceof Error ? error.message : "Safe R2 diagnostic API is unavailable."; -+ try { -+ const validation = runAdminSystemHealthStorageExpandedValidation(); -+ const diagnostics = Array.isArray(validation.storageDiagnostics) ? validation.storageDiagnostics : []; -+ diagnostics.forEach((storageResult) => { -+ const key = STORAGE_DIAGNOSTIC_ACTION_KEY_BY_ID.get(storageResult.actionId); -+ if (key) { -+ this.renderStorageResult(key, storageResult); -+ } -+ }); -+ } catch (error) { -+ const message = error instanceof Error ? error.message : "Safe R2 diagnostic API is unavailable."; -+ STORAGE_DIAGNOSTIC_ACTIONS.forEach(({ key }) => { - this.setStorageStatus(key, "PENDING", message); -- } -- }); -+ this.setStorageTiming(key, "not available"); -+ }); -+ } - } - - createCell(text) { -diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation.md b/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation.md -new file mode 100644 -index 000000000..61eba9efb ---- /dev/null -+++ b/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation.md -@@ -0,0 +1,35 @@ -+# PR_26177_CHARLIE_030-r2-storage-health-expanded-validation -+ -+Team: Charlie -+Branch: pr/26177-CHARLIE-030-r2-storage-health-expanded-validation -+Base: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel -+Lifecycle: Build / Validation -+Repair: Rebased onto repaired PR_26177_CHARLIE_029 branch on 2026-06-25. -+ -+## Scope -+- Expanded System Health R2 validation into an API-owned current-environment validation run. -+- Added safe list/upload/read/delete diagnostics with duration, operation label, cleanup status, and permanent-object metadata. -+- Updated the System Health UI to show storage timing without adding inline styles, script blocks, or page-local CSS. -+ -+## Changed Files -+- admin/system-health.html -+- assets/theme-v2/js/admin-system-health.js -+- src/api/admin-system-health-api-client.js -+- src/dev-runtime/server/local-api-router.mjs -+- tests/api/admin-system-health/contract.test.mjs -+- tests/dev-runtime/AdminHealthOperations.test.mjs -+- tests/playwright/tools/AdminHealthOperationsPage.spec.mjs -+- docs_build/dev/reports/coverage_changed_js_guardrail.tx -+- docs_build/dev/reports/playwright_v8_coverage_report.tx -+ -+## Validation -+- PASS: node --check src/dev-runtime/server/local-api-router.mjs -+- PASS: node --check assets/theme-v2/js/admin-system-health.js -+- PASS: node --check src/api/admin-system-health-api-client.js -+- PASS: node --test tests/api/admin-system-health/contract.test.mjs -+- PASS: node --test tests/dev-runtime/AdminHealthOperations.test.mjs -+- PASS: npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line -+- PASS: git diff --check -+ -+## ZIP -+- Generated after repair: C:\Users\DavidQ\Documents\GitHub\HTML-JavaScript-Gaming\tmp\PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_delta.zip -diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_branch-validation.md b/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_branch-validation.md -new file mode 100644 -index 000000000..0c0387294 ---- /dev/null -+++ b/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_branch-validation.md -@@ -0,0 +1,17 @@ -+# PR_26177_CHARLIE_030-r2-storage-health-expanded-validation Branch Validation -+ -+Branch: pr/26177-CHARLIE-030-r2-storage-health-expanded-validation -+Expected stack base: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel -+Current status at validation: -+## pr/26177-CHARLIE-030-r2-storage-health-expanded-validation -+Updated onto repaired PR_26177_CHARLIE_029 stack base. -+ -+Result: PASS -+ -+Checks: -+- PASS: Branch created from PR 029 branch for the approved stacked chain. -+- PASS: Active branch matches PR identity. -+- PASS: Worktree contained only scoped PR changes and generated validation reports. -+- PASS: Branch is based on repaired PR 029 branch. -+- PASS: Rebase conflict scope was generated report artifacts only. -+- PASS: No start_of_day files modified. -diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_manual-validation-notes.md -new file mode 100644 -index 000000000..36806ff77 ---- /dev/null -+++ b/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_manual-validation-notes.md -@@ -0,0 +1,9 @@ -+# PR_26177_CHARLIE_030-r2-storage-health-expanded-validation Manual Validation Notes -+ -+- Confirmed the browser calls one expanded R2 validation action instead of owning the list/upload/read/delete sequence itself. -+- Confirmed Storage Health displays timing values for list, upload, read, and delete rows. -+- Confirmed storage validation payload includes cleanup status and permanent-object metadata. -+- Confirmed storage diagnostics stay scoped to the current environment folder. -+- Confirmed no storage credentials or secrets are displayed. -+- Confirmed branch repair conflict was limited to generated report artifacts. -+- Confirmed no start_of_day files changed. -diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_requirements-checklist.md b/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_requirements-checklist.md -new file mode 100644 -index 000000000..2dda36cc8 ---- /dev/null -+++ b/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_requirements-checklist.md -@@ -0,0 +1,13 @@ -+# PR_26177_CHARLIE_030-r2-storage-health-expanded-validation Requirement Checklis -+ -+- PASS: Expanded Cloud/R2 storage health validation. -+- PASS: Included safe bucket/list/write/read/delete validation through API-owned action contract. -+- PASS: Included timing/status fields where available. -+- PASS: Does not create browser-owned storage health state. -+- PASS: Test object cleanup is attempted in the same expanded validation run. -+- PASS: Does not expose secrets. -+- PASS: Current environment folder only. -+- PASS: No unrelated files modified. -+- PASS: No start_of_day files modified. -+- PASS: Rebased onto repaired PR_26177_CHARLIE_029 branch. -+- PASS: Shows unavailable/not configured states explicitly. -diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_validation-lane.md b/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_validation-lane.md -new file mode 100644 -index 000000000..0dbf85ec0 ---- /dev/null -+++ b/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_validation-lane.md -@@ -0,0 +1,22 @@ -+# PR_26177_CHARLIE_030-r2-storage-health-expanded-validation Validation Lane Repor -+ -+Impacted lanes: -+- runtime: Local API Admin System Health R2 action contract. -+- contract: Admin System Health API contract tests. -+- UI: Admin System Health Theme V2 page controller and markup. -+- Playwright: targeted Admin System Health page spec. -+ -+Commands: -+- PASS: node --check src/dev-runtime/server/local-api-router.mjs -+- PASS: node --check assets/theme-v2/js/admin-system-health.js -+- PASS: node --check src/api/admin-system-health-api-client.js -+- PASS: node --test tests/api/admin-system-health/contract.test.mjs -+- PASS: node --test tests/dev-runtime/AdminHealthOperations.test.mjs -+- PASS: npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line -+- PASS: git diff --check -+ -+Skipped lanes: -+- Full samples smoke skipped; not impacted by System Health storage diagnostics. -+- Full Project Workspace suite skipped; targeted Admin/System Health coverage was sufficient for this scoped page/API change. -+ -+Result: PASS -diff --git a/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.tx -index 7b1c51f19..7211f2375 100644 ---- a/docs_build/dev/reports/coverage_changed_js_guardrail.tx -+++ b/docs_build/dev/reports/coverage_changed_js_guardrail.tx -@@ -6,7 +6,9 @@ Missing changed runtime JS files are WARN, not FAIL. - Source: Playwright/Chromium built-in V8 coverage from the active Playwright run. - - Changed runtime JS files considered: --(100%) none changed - no changed runtime JS files -+(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -+(81%) assets/theme-v2/js/admin-system-health.js - executed lines 872/872; executed functions 72/89 -+(100%) src/api/admin-system-health-api-client.js - executed lines 31/31; executed functions 5/5 - - Guardrail warnings: --(100%) none changed - no changed runtime JS files -+(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file missing from coverage; advisory only -diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.tx -index 627ed631f..2a2982e6a 100644 ---- a/docs_build/dev/reports/playwright_v8_coverage_report.tx -+++ b/docs_build/dev/reports/playwright_v8_coverage_report.tx -@@ -17,7 +17,9 @@ Exercised tool entry points detected: - (78%) Theme V2 Shared JS - exercised 5 runtime JS files - - Changed runtime JS files covered: --(100%) none changed - no changed runtime JS files -+(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -+(81%) assets/theme-v2/js/admin-system-health.js - executed lines 872/872; executed functions 72/89 -+(100%) src/api/admin-system-health-api-client.js - executed lines 31/31; executed functions 5/5 - - Files with executed line/function counts where available: - (36%) src/api/server-api-client.js - executed lines 168/168; executed functions 5/14 -@@ -27,13 +29,18 @@ Files with executed line/function counts where available: - (77%) assets/theme-v2/js/tool-display-mode.js - executed lines 304/304; executed functions 23/30 - (80%) assets/theme-v2/js/theme-icons.js - executed lines 69/69; executed functions 4/5 - (80%) src/api/admin-owner-navigation.js - executed lines 42/42; executed functions 4/5 --(81%) assets/theme-v2/js/admin-system-health.js - executed lines 848/848; executed functions 70/86 -+(81%) assets/theme-v2/js/admin-system-health.js - executed lines 872/872; executed functions 72/89 - (83%) assets/js/shared/status.js - executed lines 37/37; executed functions 5/6 - (91%) assets/theme-v2/js/admin-owner-navigation.js - executed lines 58/58; executed functions 10/11 --(100%) src/api/admin-system-health-api-client.js - executed lines 28/28; executed functions 4/4 -+(100%) src/api/admin-system-health-api-client.js - executed lines 31/31; executed functions 5/5 - - Uncovered or low-coverage changed JS files: --(100%) none changed - no changed runtime JS files -+(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: uncovered changed runtime JS file; advisory only - - Changed JS files considered: --(100%) none - no changed JS files -+(0%) src/dev-runtime/server/local-api-router.mjs - changed JS file not collected as browser runtime coverage -+(0%) tests/api/admin-system-health/contract.test.mjs - changed JS file not collected as browser runtime coverage -+(0%) tests/dev-runtime/AdminHealthOperations.test.mjs - changed JS file not collected as browser runtime coverage -+(0%) tests/playwright/tools/AdminHealthOperationsPage.spec.mjs - changed JS file not collected as browser runtime coverage -+(81%) assets/theme-v2/js/admin-system-health.js - changed JS file with browser V8 coverage -+(100%) src/api/admin-system-health-api-client.js - changed JS file with browser V8 coverage -diff --git a/src/api/admin-system-health-api-client.js b/src/api/admin-system-health-api-client.js -index 9b166d097..49101c59f 100644 ---- a/src/api/admin-system-health-api-client.js -+++ b/src/api/admin-system-health-api-client.js -@@ -20,6 +20,10 @@ export function runAdminSystemHealthStorageConnectivityAction(actionId) { - ); - } - -+export function runAdminSystemHealthStorageExpandedValidation() { -+ return runAdminSystemHealthStorageConnectivityAction("storage-expanded-validation"); -+} -+ - export function runAdminSystemHealthAction(actionId) { - return requireServerApiData( - safeRequestServerApi("/admin/system-health/action", { -diff --git a/src/dev-runtime/server/local-api-router.mjs b/src/dev-runtime/server/local-api-router.mjs -index 462d8b7a0..6dbb8a106 100644 ---- a/src/dev-runtime/server/local-api-router.mjs -+++ b/src/dev-runtime/server/local-api-router.mjs -@@ -356,6 +356,7 @@ const SYSTEM_HEALTH_STORAGE_ACTION_IDS = Object.freeze([ - "storage-read-test-object", - "storage-delete-test-object", - ]); -+const SYSTEM_HEALTH_STORAGE_EXPANDED_VALIDATION_ACTION_ID = "storage-expanded-validation"; - const SYSTEM_HEALTH_MANUAL_ACTION_LABELS = Object.freeze({ - "database-check": "Run Database Check", - "full-health-check": "Run Full Health Check", -@@ -4046,43 +4047,75 @@ class ApiRuntimeDataSource { - - storageConnectivityConfigFailure(actionId, storage, scope = "project-prefix") { - const missing = storage.config?.missingKeys?.join(", ") || storage.config?.validationError || "storage configuration incomplete"; -+ const action = STORAGE_CONNECTIVITY_ACTIONS.find((candidate) => candidate.id === actionId); - return { - actionId, -+ cleanupStatus: "not-run", -+ durationMs: 0, - environmentFolder: scope === "environment-folder" ? this.storageConnectivityEnvironmentFolder() : "", - executed: false, - lastChecked: new Date().toISOString(), - message: `Storage connectivity requires configured storage and GAMEFOUNDRY_STORAGE_PROJECTS_PREFIX before this action can run. Missing or invalid: ${missing}.`, -+ operation: action?.operation || "configuration", -+ operationLabel: action?.label || "Storage configuration", -+ permanentObjectCreated: false, - projectsPrefix: String(storage.config?.projectsPrefix || "").trim(), - secretEditingAllowed: false, - secretsExposed: false, - status: "FAIL", - storageStatus: this.ownerStorageStatus(), -+ targetPrefix: scope === "environment-folder" ? this.storageConnectivityEnvironmentFolder() : String(storage.config?.projectsPrefix || "").trim(), - }; - } - -- storageConnectivityResult({ actionId, environmentFolder = "", executed = true, keysListed = 0, message, objectKey, projectsPrefix, status }) { -+ storageConnectivityResult({ -+ action, -+ actionId, -+ cleanupStatus = "not-applicable", -+ durationMs = 0, -+ environmentFolder = "", -+ executed = true, -+ keysListed = 0, -+ message, -+ objectKey, -+ permanentObjectCreated = false, -+ projectsPrefix, -+ status, -+ targetPrefix = "", -+ }) { -+ const resolvedAction = action || STORAGE_CONNECTIVITY_ACTIONS.find((candidate) => candidate.id === actionId) || {}; - return { - actionId, -+ cleanupStatus, -+ durationMs, - environmentFolder, - executed, - keysListed, - lastChecked: new Date().toISOString(), - message, -+ operation: resolvedAction.operation || "", -+ operationLabel: resolvedAction.label || actionId, -+ permanentObjectCreated, - projectsPrefix, - secretEditingAllowed: false, - secretsExposed: false, - status, - storageStatus: this.ownerStorageStatus(), -+ targetPrefix, - testObjectKey: objectKey, - }; - } - - async runStorageConnectivityAction(actionId, options = {}) { -+ if (actionId === SYSTEM_HEALTH_STORAGE_EXPANDED_VALIDATION_ACTION_ID) { -+ return this.runStorageExpandedValidation(options); -+ } - const action = STORAGE_CONNECTIVITY_ACTIONS.find((candidate) => candidate.id === actionId); - if (!action) { - throw new Error(`Unknown storage connectivity action: ${actionId || "missing actionId"}.`); - } - -+ const startedAt = Date.now(); - const scope = options.scope === "environment-folder" ? "environment-folder" : "project-prefix"; - const storage = createConfiguredProjectAssetStorage(); - const projectsPrefix = String(storage.config?.projectsPrefix || "").trim(); -@@ -4098,7 +4131,9 @@ class ApiRuntimeDataSource { - const result = await storage.listObjects(targetPrefix); - const keysListed = Array.isArray(result.keys) ? result.keys.length : 0; - return this.storageConnectivityResult({ -+ action, - actionId: action.id, -+ durationMs: Date.now() - startedAt, - environmentFolder, - keysListed, - message: result.ok -@@ -4107,6 +4142,7 @@ class ApiRuntimeDataSource { - objectKey, - projectsPrefix, - status: result.ok ? "PASS" : "FAIL", -+ targetPrefix, - }); - } - -@@ -4116,7 +4152,9 @@ class ApiRuntimeDataSource { - : await storage.listProjectObjects(); - const keysListed = Array.isArray(result.keys) ? result.keys.length : 0; - return this.storageConnectivityResult({ -+ action, - actionId: action.id, -+ durationMs: Date.now() - startedAt, - environmentFolder, - keysListed, - message: result.ok -@@ -4125,6 +4163,7 @@ class ApiRuntimeDataSource { - objectKey, - projectsPrefix, - status: result.ok ? "PASS" : "FAIL", -+ targetPrefix, - }); - } - -@@ -4135,14 +4174,19 @@ class ApiRuntimeDataSource { - objectKey, - }); - return this.storageConnectivityResult({ -+ action, - actionId: action.id, -+ cleanupStatus: result.ok ? "requires-delete-action" : "not-created", -+ durationMs: Date.now() - startedAt, - environmentFolder, - message: result.ok - ? `Upload test object completed at ${objectKey}. - : `${result.message || "Storage upload failed."} Verify upload permission for ${targetPrefix}.`, - objectKey, -+ permanentObjectCreated: result.ok === true, - projectsPrefix, - status: result.ok ? "PASS" : "FAIL", -+ targetPrefix, - }); - } - -@@ -4151,7 +4195,9 @@ class ApiRuntimeDataSource { - const text = result.ok ? Buffer.from(result.bytes || []).toString("utf8") : ""; - const contentMatches = text === STORAGE_CONNECTIVITY_TEST_OBJECT_CONTENT; - return this.storageConnectivityResult({ -+ action, - actionId: action.id, -+ durationMs: Date.now() - startedAt, - environmentFolder, - message: result.ok && contentMatches - ? `Read test object completed from ${objectKey}. -@@ -4159,32 +4205,77 @@ class ApiRuntimeDataSource { - objectKey, - projectsPrefix, - status: result.ok && contentMatches ? "PASS" : "FAIL", -+ targetPrefix, - }); - } - - const result = await storage.deleteObject(objectKey); - return this.storageConnectivityResult({ -+ action, - actionId: action.id, -+ cleanupStatus: result.ok ? "completed" : "failed", -+ durationMs: Date.now() - startedAt, - environmentFolder, - message: result.ok - ? `Delete test object completed at ${objectKey}. - : `${result.message || "Storage delete failed."} Verify delete permission for ${targetPrefix}.`, - objectKey, -+ permanentObjectCreated: false, - projectsPrefix, - status: result.ok ? "PASS" : "FAIL", -+ targetPrefix, - }); - } catch (error) { - return this.storageConnectivityResult({ -+ action, - actionId: action.id, -+ durationMs: Date.now() - startedAt, - environmentFolder, - message: `Storage connectivity action failed: ${error instanceof Error ? error.message : String(error || "Unknown storage error.")}`, - objectKey, - projectsPrefix, - status: "FAIL", -+ targetPrefix, - }); - } - } - -+ async runStorageExpandedValidation(options = {}) { -+ const startedAt = Date.now(); -+ const scope = options.scope === "environment-folder" ? "environment-folder" : "project-prefix"; -+ const storage = createConfiguredProjectAssetStorage(); -+ const targetPrefix = this.storageConnectivityTargetPrefix(storage, scope); -+ const objectKey = this.storageConnectivityTestObjectKey(storage, scope); -+ const results = []; -+ for (const actionId of SYSTEM_HEALTH_STORAGE_ACTION_IDS) { -+ results.push(await this.runStorageConnectivityAction(actionId, { scope })); -+ } -+ const uploadResult = results.find((result) => result.actionId === "storage-upload-test-object"); -+ const deleteResult = results.find((result) => result.actionId === "storage-delete-test-object"); -+ const permanentObjectCreated = uploadResult?.status === "PASS" && deleteResult?.status !== "PASS"; -+ const status = overallHealthStatus(results); -+ return { -+ actionId: SYSTEM_HEALTH_STORAGE_EXPANDED_VALIDATION_ACTION_ID, -+ cleanupStatus: permanentObjectCreated ? "failed" : "completed", -+ durationMs: Date.now() - startedAt, -+ environmentFolder: scope === "environment-folder" ? this.storageConnectivityEnvironmentFolder() : "", -+ executed: true, -+ lastChecked: new Date().toISOString(), -+ message: permanentObjectCreated -+ ? `Expanded R2 validation detected a cleanup failure for ${objectKey}; review storage credentials and delete permission. -+ : `Expanded R2 validation completed against ${targetPrefix}; test object cleanup was attempted in the same validation run.`, -+ permanentObjectCreated, -+ projectsPrefix: String(storage.config?.projectsPrefix || "").trim(), -+ secretEditingAllowed: false, -+ secretsExposed: false, -+ status, -+ storageDiagnostics: results, -+ storageStatus: this.ownerStorageStatus(), -+ targetPrefix, -+ testObjectKey: objectKey, -+ }; -+ } -+ - ownerPromotionFoundation() { - const steps = [ - { -@@ -4424,20 +4515,20 @@ SELECT pg_database_size(current_database()) AS database_size_bytes, - } - - async adminSystemHealthStorageHealthCheck() { -- const results = []; -- for (const actionId of SYSTEM_HEALTH_STORAGE_ACTION_IDS) { -- results.push(await this.runStorageConnectivityAction(actionId, { scope: "environment-folder" })); -- } -+ const validation = await this.runStorageExpandedValidation({ scope: "environment-folder" }); -+ const results = Array.isArray(validation.storageDiagnostics) ? validation.storageDiagnostics : []; - return { - actionId: "storage-check", - checkedAt: new Date().toISOString(), - label: SYSTEM_HEALTH_MANUAL_ACTION_LABELS["storage-check"], -- message: "Storage health check executed bucket connectivity, list, upload, read, and delete through the current deployment API.", -+ message: validation.message || "Storage health check executed bucket connectivity, list, upload, read, and delete through the current deployment API.", -+ permanentObjectCreated: validation.permanentObjectCreated === true, - secretEditingAllowed: false, - secretsExposed: false, -- status: overallHealthStatus(results.map((result) => ({ status: result.status }))), -+ status: validation.status || overallHealthStatus(results.map((result) => ({ status: result.status }))), - storageDiagnostics: results, - storageStatus: this.ownerStorageStatus(), -+ validationDurationMs: validation.durationMs, - }; - } - -diff --git a/tests/api/admin-system-health/contract.test.mjs b/tests/api/admin-system-health/contract.test.mjs -index 25aa84b34..54e821f13 100644 ---- a/tests/api/admin-system-health/contract.test.mjs -+++ b/tests/api/admin-system-health/contract.test.mjs -@@ -146,6 +146,25 @@ test("Admin System Health completion contract remains server-owned and current-e - assert.equal(healthText.includes("/uat/projects"), false); - assert.equal(health.secretEditingAllowed, false); - assert.equal(health.secretsExposed, false); -+ const expandedStorage = await apiJson(server.baseUrl, "/api/admin/system-health/storage-connectivity-action", { -+ body: { actionId: "storage-expanded-validation" }, -+ method: "POST", -+ }); -+ assert.deepEqual( -+ expandedStorage.storageDiagnostics.map((row) => row.actionId), -+ [ -+ "storage-bucket-connectivity", -+ "storage-list", -+ "storage-upload-test-object", -+ "storage-read-test-object", -+ "storage-delete-test-object", -+ ], -+ ); -+ assert.equal(expandedStorage.storageDiagnostics.every((row) => row.environmentFolder === "/dev"), true); -+ assert.equal(expandedStorage.storageDiagnostics.every((row) => typeof row.durationMs === "number"), true); -+ assert.equal(expandedStorage.permanentObjectCreated, false); -+ assert.equal(JSON.stringify(expandedStorage).includes("api-secret"), false); -+ assert.equal(JSON.stringify(expandedStorage).includes("site-secret"), false); - } finally { - await server.close(); - } -diff --git a/tests/dev-runtime/AdminHealthOperations.test.mjs b/tests/dev-runtime/AdminHealthOperations.test.mjs -index 99fef0757..9ea40988a 100644 ---- a/tests/dev-runtime/AdminHealthOperations.test.mjs -+++ b/tests/dev-runtime/AdminHealthOperations.test.mjs -@@ -311,6 +311,12 @@ test("Admin can view operational health while Creator sessions are blocked", asy - ], - ); - assert.equal(storageAction.storageDiagnostics.every((row) => row.environmentFolder === "/local"), true); -+ assert.equal(storageAction.permanentObjectCreated, false); -+ assert.equal(typeof storageAction.validationDurationMs, "number"); -+ assert.equal(storageAction.storageDiagnostics.every((row) => typeof row.durationMs === "number"), true); -+ assert.equal(storageAction.storageDiagnostics.every((row) => typeof row.operationLabel === "string"), true); -+ assert.equal(storageAction.storageDiagnostics.every((row) => typeof row.cleanupStatus === "string"), true); -+ assert.equal(storageAction.storageDiagnostics.every((row) => row.permanentObjectCreated === false), true); - const refreshAction = await apiJson(server.baseUrl, "/api/admin/system-health/action", { - body: { actionId: "refresh" }, - method: "POST", -diff --git a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs -index 56733ce6a..713344ac0 100644 ---- a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs -+++ b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs -@@ -233,6 +233,10 @@ test("Admin System Health renders Postgres diagnostics through the safe status A - await expect(page.locator("[data-admin-system-health-storage-value='upload']")).toContainText("/dev"); - await expect(page.locator("[data-admin-system-health-storage-value='read']")).not.toHaveText("Health object"); - await expect(page.locator("[data-admin-system-health-storage-value='delete']")).not.toHaveText("Health object"); -+ await expect(page.locator("[data-admin-system-health-storage-timing='list']")).not.toHaveText("Loading"); -+ await expect(page.locator("[data-admin-system-health-storage-timing='upload']")).not.toHaveText("Loading"); -+ await expect(page.locator("[data-admin-system-health-storage-timing='read']")).not.toHaveText("Loading"); -+ await expect(page.locator("[data-admin-system-health-storage-timing='delete']")).not.toHaveText("Loading"); - await expect(page.getByRole("table", { name: "Runtime health" })).toContainText("Runtime Health"); - await expect(page.locator("[data-admin-system-health-runtime-health-value='environment']")).toHaveText("DEV"); - await expect(page.locator("[data-admin-system-health-runtime-health-value='appVersion']")).toHaveText("1.0.0"); -@@ -286,7 +290,7 @@ test("Admin System Health renders Postgres diagnostics through the safe status A - expect((title || ariaLabel || "").trim()).not.toEqual(""); - } - expect(context.requestUrls.some((url) => url.includes("/api/admin/system-health/status"))).toBe(true); -- expect(context.requestUrls.filter((url) => url.includes("/api/admin/system-health/storage-connectivity-action"))).toHaveLength(5); -+ expect(context.requestUrls.filter((url) => url.includes("/api/admin/system-health/storage-connectivity-action"))).toHaveLength(1); - await page.getByRole("button", { name: "Run Runtime Check" }).click(); - await expect(page.getByRole("table", { name: "Manual health action results" })).toContainText("Run Runtime Check"); - expect(context.requestUrls.some((url) => url.includes("/api/admin/system-health/action"))).toBe(true); -@@ -353,5 +357,5 @@ test("Admin System Health operations page keeps scripts and styles external", as - expect(runtimeSource).not.toContain("localStorage"); - expect(runtimeSource).not.toContain("sessionStorage"); - expect(runtimeSource).toContain("runAdminSystemHealthAction"); -- expect(runtimeSource).toContain("runAdminSystemHealthStorageConnectivityAction"); -+ expect(runtimeSource).toContain("runAdminSystemHealthStorageExpandedValidation"); - }); +diff --git a/admin/system-health.html b/admin/system-health.html index b62f232d7..5294d7d6e 100644 --- a/admin/system-health.html +++ b/admin/system-health.html @@ -40,6 +40,7 @@

Environment Identity

Environment Map

+

Environment Health Comparison

Environment Capabilities

Health API Contract

Admin API Registry

@@ -110,6 +111,25 @@
+
+ + + + + + + + + + + + + + + + +
Environment Health Comparison
EnvironmentHosting modelRuntime expectationDatabase modelStorage folderStateStatus
Environment comparisonReference-onlyWaiting for safe API statusReference-onlyReference-onlyUnavailablePENDING
+
diff --git a/assets/theme-v2/js/admin-system-health.js b/assets/theme-v2/js/admin-system-health.js index 21bc50a63..49e0593b1 100644 --- a/assets/theme-v2/js/admin-system-health.js +++ b/assets/theme-v2/js/admin-system-health.js @@ -69,6 +69,7 @@ class AdminSystemHealthController { this.historyRows = root.querySelector("[data-admin-system-health-history-rows]"); this.apiContractRows = root.querySelector("[data-admin-system-health-api-contract-rows]"); this.apiRegistryRows = root.querySelector("[data-admin-system-health-api-registry-rows]"); + this.environmentComparisonRows = root.querySelector("[data-admin-system-health-environment-comparison-rows]"); this.capabilityRows = root.querySelector("[data-admin-system-health-capability-rows]"); this.featureFlagRows = root.querySelector("[data-admin-system-health-feature-flag-rows]"); this.actionRows = root.querySelector("[data-admin-system-health-action-rows]"); @@ -160,6 +161,7 @@ class AdminSystemHealthController { this.renderPostgresMetricsPending(reason); this.renderStoragePending(reason); this.renderRuntimeHealthPending(reason); + this.renderEnvironmentComparisonPending(reason); this.renderEnvironmentCapabilitiesPending(reason); this.renderApiContractPending(reason); this.renderAdminApiRegistryPending(reason); @@ -203,6 +205,53 @@ class AdminSystemHealthController { this.setEnvironmentStatus("lastHealthCheck", environmentIdentity.lastHealthCheck ? "PASS" : "WARN", reason); } + renderEnvironmentComparisonPending(reason) { + if (!this.environmentComparisonRows) { + return; + } + const row = document.createElement("tr"); + row.append( + this.createCell("Environment comparison"), + this.createCell("Reference-only"), + this.createCell("not available"), + this.createCell("Reference-only"), + this.createCell("Reference-only"), + this.createCell("Unavailable"), + this.createStatusCell("PENDING", reason), + ); + this.environmentComparisonRows.replaceChildren(row); + } + + renderEnvironmentComparison(environmentComparison = {}) { + if (!this.environmentComparisonRows) { + return; + } + if (environmentComparison?.secretsExposed === true || environmentComparison?.secretEditingAllowed === true) { + this.renderEnvironmentComparisonPending("Safe environment comparison response was blocked because it exposed secret controls."); + return; + } + const rows = Array.isArray(environmentComparison.rows) ? environmentComparison.rows : []; + if (!rows.length) { + this.renderEnvironmentComparisonPending("Safe Admin System Health API returned no environment comparison rows."); + return; + } + const fragment = document.createDocumentFragment(); + rows.forEach((comparisonRow) => { + const row = document.createElement("tr"); + row.append( + this.createCell(comparisonRow.displayName), + this.createCell(comparisonRow.hostingModel), + this.createCell(comparisonRow.runtimeExpectation), + this.createCell(comparisonRow.databaseModel), + this.createCell(comparisonRow.storageFolder), + this.createCell(comparisonRow.state), + this.createStatusCell(comparisonRow.status, comparisonRow.summary || environmentComparison.message), + ); + fragment.append(row); + }); + this.environmentComparisonRows.replaceChildren(fragment); + } + renderStoragePending(reason) { ["bucket", "list", "upload", "read", "delete", "lastChecked"].forEach((key) => { this.setStorageStatus(key, "PENDING", reason); @@ -893,6 +942,7 @@ class AdminSystemHealthController { return; } this.renderEnvironmentIdentity(data?.environmentIdentity || {}); + this.renderEnvironmentComparison(data?.environmentComparison || {}); this.renderPostgresStatus(data?.databaseStatus || {}); this.renderStartupDiagnostics(data?.localApiStartup || {}); this.renderStorageStatus(data?.storageStatus || {}); diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison.md b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison.md new file mode 100644 index 000000000..a21a4baff --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison.md @@ -0,0 +1,33 @@ +# PR_26177_CHARLIE_031-environment-health-comparison + +Team: Charlie +Branch: pr/26177-CHARLIE-031-environment-health-comparison +Base: pr/26177-CHARLIE-030-r2-storage-health-expanded-validation +Lifecycle: Build / Validation +Repair: Rebased onto repaired PR_26177_CHARLIE_030 branch on 2026-06-25. + +## Scope +- Added a server-owned Environment Health Comparison payload and System Health table. +- Shows Local (VS Code), DEV, IST, UAT, and PROD as a reference comparison view. +- Marks only the current deployment as actively checked; peer environments are reference-only Not Configured or Unavailable. + +## Changed Files +- admin/system-health.html +- assets/theme-v2/js/admin-system-health.js +- src/dev-runtime/server/local-api-router.mjs +- tests/api/admin-system-health/contract.test.mjs +- tests/dev-runtime/AdminHealthOperations.test.mjs +- tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +- docs_build/dev/reports/coverage_changed_js_guardrail.txt +- docs_build/dev/reports/playwright_v8_coverage_report.txt + +## Validation +- PASS: node --check src/dev-runtime/server/local-api-router.mjs +- PASS: node --check assets/theme-v2/js/admin-system-health.js +- PASS: node --test tests/api/admin-system-health/contract.test.mjs +- PASS: node --test tests/dev-runtime/AdminHealthOperations.test.mjs +- PASS: npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line +- PASS: git diff --check + +## ZIP +- Generated after repair: C:\Users\DavidQ\Documents\GitHub\HTML-JavaScript-Gaming\tmp\PR_26177_CHARLIE_031-environment-health-comparison_delta.zip diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_branch-validation.md b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_branch-validation.md new file mode 100644 index 000000000..c6b7a27f6 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_branch-validation.md @@ -0,0 +1,17 @@ +# PR_26177_CHARLIE_031-environment-health-comparison Branch Validation + +Branch: pr/26177-CHARLIE-031-environment-health-comparison +Expected stack base: pr/26177-CHARLIE-030-r2-storage-health-expanded-validation +Current status at validation: +## pr/26177-CHARLIE-031-environment-health-comparison +Updated onto repaired PR_26177_CHARLIE_030 stack base. + +Result: PASS + +Checks: +- PASS: Branch created from PR 030 branch for the approved stacked chain. +- PASS: Active branch matches PR identity. +- PASS: Worktree contained only scoped PR changes and generated validation reports. +- PASS: Branch is based on repaired PR 030 branch. +- PASS: Rebase conflict scope was generated report artifacts only. +- PASS: No start_of_day files modified. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_manual-validation-notes.md new file mode 100644 index 000000000..0b516491f --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_manual-validation-notes.md @@ -0,0 +1,9 @@ +# PR_26177_CHARLIE_031-environment-health-comparison Manual Validation Notes + +- Confirmed Environment Health Comparison appears as a reference-only table. +- Confirmed DEV current deployment shows Current in the Playwright environment. +- Confirmed peer rows show Not Configured or Unavailable and are not actively checked. +- Confirmed PROD displays with R2 /prd without changing the existing PRD Environment Map. +- Confirmed no secrets or peer-environment health probes are exposed. +- Confirmed branch repair conflict was limited to generated report artifacts. +- Confirmed no start_of_day files changed. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_requirements-checklist.md b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_requirements-checklist.md new file mode 100644 index 000000000..4ca4e8f53 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_requirements-checklist.md @@ -0,0 +1,12 @@ +# PR_26177_CHARLIE_031-environment-health-comparison Requirement Checklist + +- PASS: Added a single System Health comparison view. +- PASS: Includes Local (VS Code), DEV, IST, UAT, and PROD. +- PASS: Shows clear state/status indicators per environment. +- PASS: Local (VS Code) represents local static server/API/developer runtime expectations. +- PASS: Unavailable or non-current environments do not have to pass. +- PASS: No silent defaults; peer rows explicitly show Not Configured or Unavailable. +- PASS: No cross-environment active checks added. +- PASS: Browser UI consumes API/service contract. +- PASS: No unrelated files or start_of_day files modified. +- PASS: Rebased onto repaired PR_26177_CHARLIE_030 branch. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_validation-lane.md b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_validation-lane.md new file mode 100644 index 000000000..3730a9234 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_031-environment-health-comparison_validation-lane.md @@ -0,0 +1,21 @@ +# PR_26177_CHARLIE_031-environment-health-comparison Validation Lane Report + +Impacted lanes: +- runtime: Local API Admin System Health status payload. +- contract: Admin System Health API contract tests. +- UI: Admin System Health Theme V2 table rendering. +- Playwright: targeted Admin System Health page spec. + +Commands: +- PASS: node --check src/dev-runtime/server/local-api-router.mjs +- PASS: node --check assets/theme-v2/js/admin-system-health.js +- PASS: node --test tests/api/admin-system-health/contract.test.mjs +- PASS: node --test tests/dev-runtime/AdminHealthOperations.test.mjs +- PASS: npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line +- PASS: git diff --check + +Skipped lanes: +- Full samples smoke skipped; comparison view is Admin System Health only. +- Full Project Workspace suite skipped; targeted Admin/System Health coverage was sufficient. + +Result: PASS diff --git a/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.txt index 7211f2375..fe02b5ecf 100644 --- a/docs_build/dev/reports/coverage_changed_js_guardrail.txt +++ b/docs_build/dev/reports/coverage_changed_js_guardrail.txt @@ -7,8 +7,7 @@ Source: Playwright/Chromium built-in V8 coverage from the active Playwright run. Changed runtime JS files considered: (0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(81%) assets/theme-v2/js/admin-system-health.js - executed lines 872/872; executed functions 72/89 -(100%) src/api/admin-system-health-api-client.js - executed lines 31/31; executed functions 5/5 +(80%) assets/theme-v2/js/admin-system-health.js - executed lines 920/920; executed functions 74/92 Guardrail warnings: (0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file missing from coverage; advisory only diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.txt index 2a2982e6a..c6e25e167 100644 --- a/docs_build/dev/reports/playwright_v8_coverage_report.txt +++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt @@ -18,8 +18,7 @@ Exercised tool entry points detected: Changed runtime JS files covered: (0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(81%) assets/theme-v2/js/admin-system-health.js - executed lines 872/872; executed functions 72/89 -(100%) src/api/admin-system-health-api-client.js - executed lines 31/31; executed functions 5/5 +(80%) assets/theme-v2/js/admin-system-health.js - executed lines 920/920; executed functions 74/92 Files with executed line/function counts where available: (36%) src/api/server-api-client.js - executed lines 168/168; executed functions 5/14 @@ -27,9 +26,9 @@ Files with executed line/function counts where available: (65%) src/api/public-config-client.js - executed lines 209/209; executed functions 17/26 (75%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 76/102 (77%) assets/theme-v2/js/tool-display-mode.js - executed lines 304/304; executed functions 23/30 +(80%) assets/theme-v2/js/admin-system-health.js - executed lines 920/920; executed functions 74/92 (80%) assets/theme-v2/js/theme-icons.js - executed lines 69/69; executed functions 4/5 (80%) src/api/admin-owner-navigation.js - executed lines 42/42; executed functions 4/5 -(81%) assets/theme-v2/js/admin-system-health.js - executed lines 872/872; executed functions 72/89 (83%) assets/js/shared/status.js - executed lines 37/37; executed functions 5/6 (91%) assets/theme-v2/js/admin-owner-navigation.js - executed lines 58/58; executed functions 10/11 (100%) src/api/admin-system-health-api-client.js - executed lines 31/31; executed functions 5/5 @@ -42,5 +41,4 @@ Changed JS files considered: (0%) tests/api/admin-system-health/contract.test.mjs - changed JS file not collected as browser runtime coverage (0%) tests/dev-runtime/AdminHealthOperations.test.mjs - changed JS file not collected as browser runtime coverage (0%) tests/playwright/tools/AdminHealthOperationsPage.spec.mjs - changed JS file not collected as browser runtime coverage -(81%) assets/theme-v2/js/admin-system-health.js - changed JS file with browser V8 coverage -(100%) src/api/admin-system-health-api-client.js - changed JS file with browser V8 coverage +(80%) assets/theme-v2/js/admin-system-health.js - changed JS file with browser V8 coverage diff --git a/src/dev-runtime/server/local-api-router.mjs b/src/dev-runtime/server/local-api-router.mjs index 6dbb8a106..01702c505 100644 --- a/src/dev-runtime/server/local-api-router.mjs +++ b/src/dev-runtime/server/local-api-router.mjs @@ -983,6 +983,80 @@ function systemHealthEnvironmentMap() { return SYSTEM_HEALTH_ENVIRONMENT_MODELS.map((model) => ({ ...model })); } +function systemHealthEnvironmentComparison({ checkedAt = new Date().toISOString(), environmentIdentity = {} } = {}) { + const currentEnvironmentName = normalizeEnvironmentName(environmentIdentity.name); + const comparisonModels = [ + { + canonicalName: "Local", + databaseModel: "Local Docker PostgreSQL", + displayName: "Local (VS Code)", + hostingModel: "VS Code + Local API", + runtimeExpectation: "Local static server + Local API + developer workstation", + storageFolder: "/local", + }, + { + canonicalName: "DEV", + databaseModel: "Local Docker PostgreSQL", + displayName: "DEV", + hostingModel: "Local Docker", + runtimeExpectation: "Local Docker runtime + Local API", + storageFolder: "/dev", + }, + { + canonicalName: "IST", + databaseModel: "Local Docker PostgreSQL", + displayName: "IST", + hostingModel: "Local Docker", + runtimeExpectation: "Local Docker runtime + Local API", + storageFolder: "/ist", + }, + { + canonicalName: "UAT", + databaseModel: "Supabase PostgreSQL", + displayName: "UAT", + hostingModel: "Cloudflare", + runtimeExpectation: "Cloudflare deployment + server API contract", + storageFolder: "/uat", + }, + { + canonicalName: "PRD", + databaseModel: "Supabase PostgreSQL", + displayName: "PROD", + hostingModel: "Cloudflare", + runtimeExpectation: "Cloudflare deployment + server API contract", + storageFolder: "/prd", + }, + ]; + const rows = comparisonModels.map((model) => { + const current = normalizeEnvironmentName(model.canonicalName) === currentEnvironmentName; + const state = current + ? "Current" + : model.canonicalName === "Local" + ? "Unavailable" + : "Not Configured"; + return { + ...model, + activeCheck: current, + checkedAt: current ? checkedAt : "", + state, + status: current ? environmentIdentity.status || "PASS" : "WARN", + summary: current + ? `This deployment actively checks only ${environmentIdentity.name || model.displayName}.` + : `${model.displayName} is reference-only from this deployment; no active health check was run.`, + }; + }); + return { + currentEnvironment: environmentIdentity.name || "Unknown", + lastChecked: checkedAt, + message: "Environment Health Comparison is reference-only for peer environments; only the current deployment is actively checked.", + noCrossEnvironmentChecks: true, + rows, + secretEditingAllowed: false, + secretsExposed: false, + status: rows.some((row) => row.activeCheck) ? "PASS" : "WARN", + }; +} + function topLevelStorageFolder(value) { const segment = String(value || "") .trim() @@ -4595,6 +4669,7 @@ SELECT pg_database_size(current_database()) AS database_size_bytes, const runtimeFeatureFlags = systemHealthRuntimeFeatureFlags(checkedAt); const environmentIdentity = systemHealthEnvironmentIdentity(process.env, checkedAt); const environmentMap = systemHealthEnvironmentMap(); + const environmentComparison = systemHealthEnvironmentComparison({ checkedAt, environmentIdentity }); const databaseStatus = await this.ownerDatabaseStatus(environmentIdentity); const postgresMetrics = databaseStatus.postgresMetrics || systemHealthPostgresMetrics(databaseStatus, checkedAt); const storageStatus = this.ownerStorageStatus(); @@ -4763,6 +4838,7 @@ SELECT pg_database_size(current_database()) AS database_size_bytes, message: "Admin System Health loaded safe status only.", environmentIdentity, environmentMap, + environmentComparison, environmentCapabilities, healthCheckHistory, notificationsFoundation, diff --git a/tests/api/admin-system-health/contract.test.mjs b/tests/api/admin-system-health/contract.test.mjs index 54e821f13..9d54111f2 100644 --- a/tests/api/admin-system-health/contract.test.mjs +++ b/tests/api/admin-system-health/contract.test.mjs @@ -107,6 +107,7 @@ test("Admin System Health completion contract remains server-owned and current-e [ "apiContract", "adminApiRegistry", + "environmentComparison", "environmentCapabilities", "runtimeFeatureFlags", "serviceHealth", @@ -118,6 +119,7 @@ test("Admin System Health completion contract remains server-owned and current-e [ "apiContract", "adminApiRegistry", + "environmentComparison", "environmentCapabilities", "runtimeFeatureFlags", "serviceHealth", @@ -127,6 +129,17 @@ test("Admin System Health completion contract remains server-owned and current-e "notificationsFoundation", ], ); + assert.equal(health.environmentComparison.noCrossEnvironmentChecks, true); + assert.deepEqual( + health.environmentComparison.rows.map((row) => `${row.displayName}:${row.state}`), + [ + "Local (VS Code):Unavailable", + "DEV:Current", + "IST:Not Configured", + "UAT:Not Configured", + "PROD:Not Configured", + ], + ); assert.deepEqual( health.postgresMetrics.rows.map((row) => row.metric), [ diff --git a/tests/dev-runtime/AdminHealthOperations.test.mjs b/tests/dev-runtime/AdminHealthOperations.test.mjs index 9ea40988a..0c413cfda 100644 --- a/tests/dev-runtime/AdminHealthOperations.test.mjs +++ b/tests/dev-runtime/AdminHealthOperations.test.mjs @@ -226,6 +226,15 @@ test("Admin can view operational health while Creator sessions are blocked", asy health.environmentMap.map((row) => row.name), ["Local", "DEV", "IST", "UAT", "PRD"], ); + assert.equal(health.environmentComparison.noCrossEnvironmentChecks, true); + assert.deepEqual( + health.environmentComparison.rows.map((row) => row.displayName), + ["Local (VS Code)", "DEV", "IST", "UAT", "PROD"], + ); + assert.equal(health.environmentComparison.rows.filter((row) => row.activeCheck === true).length, 1); + assert.equal(health.environmentComparison.rows.find((row) => row.displayName === "Local (VS Code)").state, "Current"); + assert.equal(health.environmentComparison.rows.find((row) => row.displayName === "DEV").state, "Not Configured"); + assert.equal(health.environmentComparison.rows.find((row) => row.displayName === "PROD").storageFolder, "/prd"); assert.equal(health.apiContract.contractVersion, "2026-06-24.system-health.v1"); assert.equal(health.apiContract.currentDeploymentOnly, true); assert.equal(health.apiContract.noCrossEnvironmentChecks, true); diff --git a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs index 713344ac0..f7f2cb66c 100644 --- a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +++ b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs @@ -145,6 +145,16 @@ test("Admin System Health renders Postgres diagnostics through the safe status A await expect(page.getByRole("table", { name: "Environment map" })).toContainText("UAT"); await expect(page.getByRole("table", { name: "Environment map" })).toContainText("PRD"); await expect(page.getByRole("table", { name: "Environment map" }).locator("[data-health-status]")).toHaveCount(0); + const comparisonTable = page.getByRole("table", { name: "Environment health comparison" }); + await expect(comparisonTable).toContainText("Local (VS Code)"); + await expect(comparisonTable).toContainText("Local static server + Local API"); + await expect(comparisonTable).toContainText("DEV"); + await expect(comparisonTable).toContainText("Current"); + await expect(comparisonTable).toContainText("IST"); + await expect(comparisonTable).toContainText("UAT"); + await expect(comparisonTable).toContainText("PROD"); + await expect(comparisonTable).toContainText("Not Configured"); + await expect(comparisonTable).toContainText("Unavailable"); const capabilitiesTable = page.getByRole("table", { name: "Environment capabilities" }); await expect(capabilitiesTable).toContainText("Hosting"); await expect(capabilitiesTable).toContainText("API"); @@ -335,6 +345,7 @@ test("Admin System Health operations page keeps scripts and styles external", as expect(pageSource).not.toContain("SQLite"); expect(pageSource).toContain("Environment Identity"); expect(pageSource).toContain("Environment Map"); + expect(pageSource).toContain("Environment Health Comparison"); expect(pageSource).toContain("Environment Capabilities"); expect(pageSource).toContain("Health API Contract"); expect(pageSource).toContain("Admin API Registry"); diff --git a/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.txt index 7211f2375..fe02b5ecf 100644 --- a/docs_build/dev/reports/coverage_changed_js_guardrail.txt +++ b/docs_build/dev/reports/coverage_changed_js_guardrail.txt @@ -7,8 +7,7 @@ Source: Playwright/Chromium built-in V8 coverage from the active Playwright run. Changed runtime JS files considered: (0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(81%) assets/theme-v2/js/admin-system-health.js - executed lines 872/872; executed functions 72/89 -(100%) src/api/admin-system-health-api-client.js - executed lines 31/31; executed functions 5/5 +(80%) assets/theme-v2/js/admin-system-health.js - executed lines 920/920; executed functions 74/92 Guardrail warnings: (0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file missing from coverage; advisory only diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.txt index 2a2982e6a..c6e25e167 100644 --- a/docs_build/dev/reports/playwright_v8_coverage_report.txt +++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt @@ -18,8 +18,7 @@ Exercised tool entry points detected: Changed runtime JS files covered: (0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(81%) assets/theme-v2/js/admin-system-health.js - executed lines 872/872; executed functions 72/89 -(100%) src/api/admin-system-health-api-client.js - executed lines 31/31; executed functions 5/5 +(80%) assets/theme-v2/js/admin-system-health.js - executed lines 920/920; executed functions 74/92 Files with executed line/function counts where available: (36%) src/api/server-api-client.js - executed lines 168/168; executed functions 5/14 @@ -27,9 +26,9 @@ Files with executed line/function counts where available: (65%) src/api/public-config-client.js - executed lines 209/209; executed functions 17/26 (75%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 76/102 (77%) assets/theme-v2/js/tool-display-mode.js - executed lines 304/304; executed functions 23/30 +(80%) assets/theme-v2/js/admin-system-health.js - executed lines 920/920; executed functions 74/92 (80%) assets/theme-v2/js/theme-icons.js - executed lines 69/69; executed functions 4/5 (80%) src/api/admin-owner-navigation.js - executed lines 42/42; executed functions 4/5 -(81%) assets/theme-v2/js/admin-system-health.js - executed lines 872/872; executed functions 72/89 (83%) assets/js/shared/status.js - executed lines 37/37; executed functions 5/6 (91%) assets/theme-v2/js/admin-owner-navigation.js - executed lines 58/58; executed functions 10/11 (100%) src/api/admin-system-health-api-client.js - executed lines 31/31; executed functions 5/5 @@ -42,5 +41,4 @@ Changed JS files considered: (0%) tests/api/admin-system-health/contract.test.mjs - changed JS file not collected as browser runtime coverage (0%) tests/dev-runtime/AdminHealthOperations.test.mjs - changed JS file not collected as browser runtime coverage (0%) tests/playwright/tools/AdminHealthOperationsPage.spec.mjs - changed JS file not collected as browser runtime coverage -(81%) assets/theme-v2/js/admin-system-health.js - changed JS file with browser V8 coverage -(100%) src/api/admin-system-health-api-client.js - changed JS file with browser V8 coverage +(80%) assets/theme-v2/js/admin-system-health.js - changed JS file with browser V8 coverage diff --git a/src/dev-runtime/server/local-api-router.mjs b/src/dev-runtime/server/local-api-router.mjs index 6dbb8a106..01702c505 100644 --- a/src/dev-runtime/server/local-api-router.mjs +++ b/src/dev-runtime/server/local-api-router.mjs @@ -983,6 +983,80 @@ function systemHealthEnvironmentMap() { return SYSTEM_HEALTH_ENVIRONMENT_MODELS.map((model) => ({ ...model })); } +function systemHealthEnvironmentComparison({ checkedAt = new Date().toISOString(), environmentIdentity = {} } = {}) { + const currentEnvironmentName = normalizeEnvironmentName(environmentIdentity.name); + const comparisonModels = [ + { + canonicalName: "Local", + databaseModel: "Local Docker PostgreSQL", + displayName: "Local (VS Code)", + hostingModel: "VS Code + Local API", + runtimeExpectation: "Local static server + Local API + developer workstation", + storageFolder: "/local", + }, + { + canonicalName: "DEV", + databaseModel: "Local Docker PostgreSQL", + displayName: "DEV", + hostingModel: "Local Docker", + runtimeExpectation: "Local Docker runtime + Local API", + storageFolder: "/dev", + }, + { + canonicalName: "IST", + databaseModel: "Local Docker PostgreSQL", + displayName: "IST", + hostingModel: "Local Docker", + runtimeExpectation: "Local Docker runtime + Local API", + storageFolder: "/ist", + }, + { + canonicalName: "UAT", + databaseModel: "Supabase PostgreSQL", + displayName: "UAT", + hostingModel: "Cloudflare", + runtimeExpectation: "Cloudflare deployment + server API contract", + storageFolder: "/uat", + }, + { + canonicalName: "PRD", + databaseModel: "Supabase PostgreSQL", + displayName: "PROD", + hostingModel: "Cloudflare", + runtimeExpectation: "Cloudflare deployment + server API contract", + storageFolder: "/prd", + }, + ]; + const rows = comparisonModels.map((model) => { + const current = normalizeEnvironmentName(model.canonicalName) === currentEnvironmentName; + const state = current + ? "Current" + : model.canonicalName === "Local" + ? "Unavailable" + : "Not Configured"; + return { + ...model, + activeCheck: current, + checkedAt: current ? checkedAt : "", + state, + status: current ? environmentIdentity.status || "PASS" : "WARN", + summary: current + ? `This deployment actively checks only ${environmentIdentity.name || model.displayName}.` + : `${model.displayName} is reference-only from this deployment; no active health check was run.`, + }; + }); + return { + currentEnvironment: environmentIdentity.name || "Unknown", + lastChecked: checkedAt, + message: "Environment Health Comparison is reference-only for peer environments; only the current deployment is actively checked.", + noCrossEnvironmentChecks: true, + rows, + secretEditingAllowed: false, + secretsExposed: false, + status: rows.some((row) => row.activeCheck) ? "PASS" : "WARN", + }; +} + function topLevelStorageFolder(value) { const segment = String(value || "") .trim() @@ -4595,6 +4669,7 @@ SELECT pg_database_size(current_database()) AS database_size_bytes, const runtimeFeatureFlags = systemHealthRuntimeFeatureFlags(checkedAt); const environmentIdentity = systemHealthEnvironmentIdentity(process.env, checkedAt); const environmentMap = systemHealthEnvironmentMap(); + const environmentComparison = systemHealthEnvironmentComparison({ checkedAt, environmentIdentity }); const databaseStatus = await this.ownerDatabaseStatus(environmentIdentity); const postgresMetrics = databaseStatus.postgresMetrics || systemHealthPostgresMetrics(databaseStatus, checkedAt); const storageStatus = this.ownerStorageStatus(); @@ -4763,6 +4838,7 @@ SELECT pg_database_size(current_database()) AS database_size_bytes, message: "Admin System Health loaded safe status only.", environmentIdentity, environmentMap, + environmentComparison, environmentCapabilities, healthCheckHistory, notificationsFoundation, diff --git a/tests/api/admin-system-health/contract.test.mjs b/tests/api/admin-system-health/contract.test.mjs index 54e821f13..9d54111f2 100644 --- a/tests/api/admin-system-health/contract.test.mjs +++ b/tests/api/admin-system-health/contract.test.mjs @@ -107,6 +107,7 @@ test("Admin System Health completion contract remains server-owned and current-e [ "apiContract", "adminApiRegistry", + "environmentComparison", "environmentCapabilities", "runtimeFeatureFlags", "serviceHealth", @@ -118,6 +119,7 @@ test("Admin System Health completion contract remains server-owned and current-e [ "apiContract", "adminApiRegistry", + "environmentComparison", "environmentCapabilities", "runtimeFeatureFlags", "serviceHealth", @@ -127,6 +129,17 @@ test("Admin System Health completion contract remains server-owned and current-e "notificationsFoundation", ], ); + assert.equal(health.environmentComparison.noCrossEnvironmentChecks, true); + assert.deepEqual( + health.environmentComparison.rows.map((row) => `${row.displayName}:${row.state}`), + [ + "Local (VS Code):Unavailable", + "DEV:Current", + "IST:Not Configured", + "UAT:Not Configured", + "PROD:Not Configured", + ], + ); assert.deepEqual( health.postgresMetrics.rows.map((row) => row.metric), [ diff --git a/tests/dev-runtime/AdminHealthOperations.test.mjs b/tests/dev-runtime/AdminHealthOperations.test.mjs index 9ea40988a..0c413cfda 100644 --- a/tests/dev-runtime/AdminHealthOperations.test.mjs +++ b/tests/dev-runtime/AdminHealthOperations.test.mjs @@ -226,6 +226,15 @@ test("Admin can view operational health while Creator sessions are blocked", asy health.environmentMap.map((row) => row.name), ["Local", "DEV", "IST", "UAT", "PRD"], ); + assert.equal(health.environmentComparison.noCrossEnvironmentChecks, true); + assert.deepEqual( + health.environmentComparison.rows.map((row) => row.displayName), + ["Local (VS Code)", "DEV", "IST", "UAT", "PROD"], + ); + assert.equal(health.environmentComparison.rows.filter((row) => row.activeCheck === true).length, 1); + assert.equal(health.environmentComparison.rows.find((row) => row.displayName === "Local (VS Code)").state, "Current"); + assert.equal(health.environmentComparison.rows.find((row) => row.displayName === "DEV").state, "Not Configured"); + assert.equal(health.environmentComparison.rows.find((row) => row.displayName === "PROD").storageFolder, "/prd"); assert.equal(health.apiContract.contractVersion, "2026-06-24.system-health.v1"); assert.equal(health.apiContract.currentDeploymentOnly, true); assert.equal(health.apiContract.noCrossEnvironmentChecks, true); diff --git a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs index 713344ac0..f7f2cb66c 100644 --- a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +++ b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs @@ -145,6 +145,16 @@ test("Admin System Health renders Postgres diagnostics through the safe status A await expect(page.getByRole("table", { name: "Environment map" })).toContainText("UAT"); await expect(page.getByRole("table", { name: "Environment map" })).toContainText("PRD"); await expect(page.getByRole("table", { name: "Environment map" }).locator("[data-health-status]")).toHaveCount(0); + const comparisonTable = page.getByRole("table", { name: "Environment health comparison" }); + await expect(comparisonTable).toContainText("Local (VS Code)"); + await expect(comparisonTable).toContainText("Local static server + Local API"); + await expect(comparisonTable).toContainText("DEV"); + await expect(comparisonTable).toContainText("Current"); + await expect(comparisonTable).toContainText("IST"); + await expect(comparisonTable).toContainText("UAT"); + await expect(comparisonTable).toContainText("PROD"); + await expect(comparisonTable).toContainText("Not Configured"); + await expect(comparisonTable).toContainText("Unavailable"); const capabilitiesTable = page.getByRole("table", { name: "Environment capabilities" }); await expect(capabilitiesTable).toContainText("Hosting"); await expect(capabilitiesTable).toContainText("API"); @@ -335,6 +345,7 @@ test("Admin System Health operations page keeps scripts and styles external", as expect(pageSource).not.toContain("SQLite"); expect(pageSource).toContain("Environment Identity"); expect(pageSource).toContain("Environment Map"); + expect(pageSource).toContain("Environment Health Comparison"); expect(pageSource).toContain("Environment Capabilities"); expect(pageSource).toContain("Health API Contract"); expect(pageSource).toContain("Admin API Registry");
Environment Capabilities