diff --git a/admin/system-health.html b/admin/system-health.html index abe5c4f97..5aa9fa80a 100644 --- a/admin/system-health.html +++ b/admin/system-health.html @@ -51,6 +51,7 @@

Admin

Notifications & Alerts

Local API Startup

Database Health

+

Postgres Metrics

Storage Health

Runtime Health

Health Check History

@@ -293,6 +294,21 @@

Manual Health Actions

+
+ + + + + + + + + + + + +
Postgres Metrics
MetricSafe ValueStatus
Postgres metricsUnavailable until safe API status loadsPENDING
+
diff --git a/assets/theme-v2/js/admin-system-health.js b/assets/theme-v2/js/admin-system-health.js index 656394104..746d47753 100644 --- a/assets/theme-v2/js/admin-system-health.js +++ b/assets/theme-v2/js/admin-system-health.js @@ -74,6 +74,7 @@ class AdminSystemHealthController { this.notificationRows = root.querySelector("[data-admin-system-health-notification-rows]"); this.serviceCards = root.querySelector("[data-admin-system-health-service-cards]"); this.startupRows = root.querySelector("[data-admin-system-health-startup-rows]"); + this.postgresMetricRows = root.querySelector("[data-admin-system-health-postgres-metric-rows]"); this.runtimeRows = root.querySelector("[data-admin-system-health-runtime-rows]"); } @@ -145,6 +146,7 @@ class AdminSystemHealthController { this.setStatus(key, "PENDING", reason); }); this.renderStartupPending(reason); + this.renderPostgresMetricsPending(reason); this.renderStoragePending(reason); this.renderRuntimeHealthPending(reason); this.renderEnvironmentCapabilitiesPending(reason); @@ -213,6 +215,46 @@ class AdminSystemHealthController { this.setStatus("version", databaseStatus.versionStatus, reason); this.setValue("lastChecked", databaseStatus.lastChecked, "not available"); this.setStatus("lastChecked", databaseStatus.lastChecked ? "PASS" : "WARN", reason); + this.renderPostgresMetrics(databaseStatus.postgresMetrics || {}); + } + + renderPostgresMetricsPending(reason) { + if (!this.postgresMetricRows) { + return; + } + const row = document.createElement("tr"); + row.append( + this.createCell("Postgres metrics"), + this.createCell("Unavailable"), + this.createStatusCell("PENDING", reason), + ); + this.postgresMetricRows.replaceChildren(row); + } + + renderPostgresMetrics(postgresMetrics = {}) { + if (!this.postgresMetricRows) { + return; + } + if (postgresMetrics?.secretsExposed === true || postgresMetrics?.secretEditingAllowed === true) { + this.renderPostgresMetricsPending("Safe Postgres metrics response was blocked because it exposed secret controls."); + return; + } + const rows = Array.isArray(postgresMetrics.rows) ? postgresMetrics.rows : []; + if (!rows.length) { + this.renderPostgresMetricsPending("Safe Admin System Health API returned no Postgres metric rows."); + return; + } + const fragment = document.createDocumentFragment(); + rows.forEach((metricRow) => { + const row = document.createElement("tr"); + row.append( + this.createCell(metricRow.metric), + this.createCell(metricRow.value), + this.createStatusCell(metricRow.status, postgresMetrics.message), + ); + fragment.append(row); + }); + this.postgresMetricRows.replaceChildren(fragment); } renderStorageStatus(storageStatus = {}) { diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md new file mode 100644 index 000000000..7ff7fe32a --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md @@ -0,0 +1,39 @@ +# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel + +Team: Charlie +Branch: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel +Base: main +Lifecycle: Build / Validation +Repair: Updated from origin/main on 2026-06-25 after PR #177 reported draft=true and mergeable=false. + +## Scope +- Added a System Health Postgres Metrics panel backed by the server-owned Admin System Health API. +- Added safe current-environment metrics for connection status, database name, current schema, migration status, last migration, table count, database size, and last checked. +- Preserved current-environment-only behavior and did not add cross-environment database checks. + +## 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 + +## Repair Notes +- PASS: Merged current origin/main into the PR branch. +- PASS: Merge conflict was limited to generated report artifacts: codex_changed_files.txt and codex_review.diff. +- PASS: No runtime, UI, API, database, or product-data conflict required a product decision. +- PASS: Scope remains Postgres metrics panel only. + +## ZIP +- Generated after repair: C:\Users\DavidQ\Documents\GitHub\HTML-JavaScript-Gaming\tmp\PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_delta.zip diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md new file mode 100644 index 000000000..9364d3f7c --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md @@ -0,0 +1,17 @@ +# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Branch Validation + +Branch: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel +Expected start branch: main, then PR branch for build +Current status at validation: +## pr/26177-CHARLIE-029-system-health-postgres-metrics-panel +Updated from origin/main for PR #177 repair. + +Result: PASS + +Checks: +- PASS: Started from synchronized main. +- PASS: Active branch matches PR identity. +- PASS: Current origin/main merged into branch. +- PASS: Merge conflict scope was generated report artifacts only. +- PASS: Worktree contains only scoped PR repair/report changes after report refresh. +- PASS: No start_of_day files modified. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md new file mode 100644 index 000000000..1c30d0c93 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md @@ -0,0 +1,9 @@ +# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Manual Validation Notes + +- Confirmed Postgres Metrics appears as a separate System Health table. +- Confirmed metric values come from the server-owned Admin System Health API response. +- Confirmed unavailable metrics render visibly as Unavailable/WARN rather than silently falling back. +- Confirmed page retains external scripts/styles only; no inline style/script/handler additions. +- Confirmed no secrets or database URLs are shown in the Database Health or Postgres Metrics tables. +- Confirmed current main merge conflict was limited to generated report artifacts. +- Confirmed no start_of_day files changed. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md new file mode 100644 index 000000000..f99698e88 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md @@ -0,0 +1,16 @@ +# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Requirement Checklist + +- PASS: Add/extend System Health Postgres metrics panel. +- PASS: Include connection status. +- PASS: Include database name. +- PASS: Include current schema/migration status when available. +- PASS: Include table count when available. +- PASS: Include database size when available. +- PASS: Do not add expensive queries. +- PASS: Show explicit Unavailable status when a metric is unavailable. +- PASS: Do not expose secrets. +- PASS: Keep browser UI consuming API/service contract. +- PASS: No cross-environment checks added. +- PASS: No start_of_day files modified. +- PASS: Repaired PR #177 branch from current main. +- PASS: Mark ready for review after validation/reporting is complete. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md new file mode 100644 index 000000000..e1ca3d673 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md @@ -0,0 +1,21 @@ +# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Validation Lane Report + +Impacted lanes: +- runtime: Local API Admin System Health status 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 --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 API/UI panel change. +- Full workspace suite skipped; targeted Project Workspace coverage was sufficient for this scoped Admin page. + +Result: PASS diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index 19ffd212e..79c9ad1c4 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,10 +1,44 @@ -docs_build/dev/ProjectInstructions/backlog/BACKLOG_MASTER.md -docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-branch-validation.md -docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-manual-validation-notes.md -docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-requirement-checklist.md -docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-validation.md -docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup.md +# git diff --name-only origin/main -- +admin/system-health.html +assets/theme-v2/js/admin-system-health.js +docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md +docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md +docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md +docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md +docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md docs_build/dev/reports/codex_changed_files.txt docs_build/dev/reports/codex_review.diff -src/engine/runtime/runtimeEventSystem.js -tests/engine/RuntimeEventSystem.test.mjs +docs_build/dev/reports/coverage_changed_js_guardrail.txt +docs_build/dev/reports/playwright_v8_coverage_report.txt +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_029-system-health-postgres-metrics-panel.md + M docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md + M docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md + M docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md + M docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_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 origin/main -- + admin/system-health.html | 16 + + assets/theme-v2/js/admin-system-health.js | 42 ++ + ...LIE_029-system-health-postgres-metrics-panel.md | 39 + + ...lth-postgres-metrics-panel_branch-validation.md | 17 + + ...stgres-metrics-panel_manual-validation-notes.md | 9 + + ...ostgres-metrics-panel_requirements-checklist.md | 16 + + ...ealth-postgres-metrics-panel_validation-lane.md | 21 + + docs_build/dev/reports/codex_changed_files.txt | 42 +- + docs_build/dev/reports/codex_review.diff | 794 +++++++++++++++------ + .../dev/reports/coverage_changed_js_guardrail.txt | 4 +- + .../dev/reports/playwright_v8_coverage_report.txt | 24 +- + src/dev-runtime/server/local-api-router.mjs | 115 ++- + tests/api/admin-system-health/contract.test.mjs | 15 + + tests/dev-runtime/AdminHealthOperations.test.mjs | 20 + + .../tools/AdminHealthOperationsPage.spec.mjs | 11 + + 15 files changed, 924 insertions(+), 261 deletions(-) diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index 8f1753adf..1f8c7b688 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -1,246 +1,578 @@ -diff --git a/docs_build/dev/ProjectInstructions/backlog/BACKLOG_MASTER.md b/docs_build/dev/ProjectInstructions/backlog/BACKLOG_MASTER.md -index 0dc1ef5ec..de7ad4681 100644 ---- a/docs_build/dev/ProjectInstructions/backlog/BACKLOG_MASTER.md -+++ b/docs_build/dev/ProjectInstructions/backlog/BACKLOG_MASTER.md -@@ -249,7 +249,8 @@ Current OWNER clarification: - - Completion reference: PR_26175_DELTA_001_Runtime_Performance_Optimization. - - [x] Delta - Engine test coverage improvements - - Completed by PR_26175_DELTA_004_Runtime_Test_Expansion. --- [ ] Delta - Event system audit -+- [x] Delta - Event system audit -+ - Completed by PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup. - - [ ] Delta - Controls runtime framework audit - - [ ] Delta - Object runtime framework audit - - [ ] Delta - World runtime framework audit -diff --git a/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-branch-validation.md b/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-branch-validation.md -new file mode 100644 -index 000000000..c86256005 ---- /dev/null -+++ b/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-branch-validation.md -@@ -0,0 +1,15 @@ -+# PR_26175_DELTA_005 Branch Validation -+ -+| Gate | Status | Evidence | -+| --- | --- | --- | -+| Current branch before work | PASS | `main` after PR_004 merge | -+| Worktree before work | PASS | Clean | -+| Local/origin sync before work | PASS | `0 0` | -+| Team ownership | PASS | Team Delta owns Runtime, Event systems, Shared JS, Performance, and technical debt remediation. | -+| Work branch | PASS | `PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup` | -+| Previous Delta PR closed | PASS | PR_004 was merged and `main` was verified before PR_005 started. | -+| Scope boundary | PASS | Runtime event clone cleanup, focused test update, backlog, and reports only. | -+ -+## Instruction Reads -+ -+PASS - All files under `docs_build/dev/ProjectInstructions/` were read before the Delta sequence, and updated instructions were reread after pulling latest `main`. -diff --git a/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-manual-validation-notes.md b/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-manual-validation-notes.md +diff --git a/admin/system-health.html b/admin/system-health.html +index abe5c4f97..5aa9fa80a 100644 +--- a/admin/system-health.html ++++ b/admin/system-health.html +@@ -51,6 +51,7 @@ +

Notifications & Alerts

+

Local API Startup

+

Database Health

++

Postgres Metrics

+

Storage Health

+

Runtime Health

+

Health Check History

+@@ -293,6 +294,21 @@ + +
Storage Health - Cloudflare R2
+
++
++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
Postgres Metrics
MetricSafe ValueStatus
Postgres metricsUnavailable until safe API status loadsPENDING
++
+
+ + +diff --git a/assets/theme-v2/js/admin-system-health.js b/assets/theme-v2/js/admin-system-health.js +index 656394104..746d47753 100644 +--- a/assets/theme-v2/js/admin-system-health.js ++++ b/assets/theme-v2/js/admin-system-health.js +@@ -74,6 +74,7 @@ class AdminSystemHealthController { + this.notificationRows = root.querySelector("[data-admin-system-health-notification-rows]"); + this.serviceCards = root.querySelector("[data-admin-system-health-service-cards]"); + this.startupRows = root.querySelector("[data-admin-system-health-startup-rows]"); ++ this.postgresMetricRows = root.querySelector("[data-admin-system-health-postgres-metric-rows]"); + this.runtimeRows = root.querySelector("[data-admin-system-health-runtime-rows]"); + } + +@@ -145,6 +146,7 @@ class AdminSystemHealthController { + this.setStatus(key, "PENDING", reason); + }); + this.renderStartupPending(reason); ++ this.renderPostgresMetricsPending(reason); + this.renderStoragePending(reason); + this.renderRuntimeHealthPending(reason); + this.renderEnvironmentCapabilitiesPending(reason); +@@ -213,6 +215,46 @@ class AdminSystemHealthController { + this.setStatus("version", databaseStatus.versionStatus, reason); + this.setValue("lastChecked", databaseStatus.lastChecked, "not available"); + this.setStatus("lastChecked", databaseStatus.lastChecked ? "PASS" : "WARN", reason); ++ this.renderPostgresMetrics(databaseStatus.postgresMetrics || {}); ++ } ++ ++ renderPostgresMetricsPending(reason) { ++ if (!this.postgresMetricRows) { ++ return; ++ } ++ const row = document.createElement("tr"); ++ row.append( ++ this.createCell("Postgres metrics"), ++ this.createCell("Unavailable"), ++ this.createStatusCell("PENDING", reason), ++ ); ++ this.postgresMetricRows.replaceChildren(row); ++ } ++ ++ renderPostgresMetrics(postgresMetrics = {}) { ++ if (!this.postgresMetricRows) { ++ return; ++ } ++ if (postgresMetrics?.secretsExposed === true || postgresMetrics?.secretEditingAllowed === true) { ++ this.renderPostgresMetricsPending("Safe Postgres metrics response was blocked because it exposed secret controls."); ++ return; ++ } ++ const rows = Array.isArray(postgresMetrics.rows) ? postgresMetrics.rows : []; ++ if (!rows.length) { ++ this.renderPostgresMetricsPending("Safe Admin System Health API returned no Postgres metric rows."); ++ return; ++ } ++ const fragment = document.createDocumentFragment(); ++ rows.forEach((metricRow) => { ++ const row = document.createElement("tr"); ++ row.append( ++ this.createCell(metricRow.metric), ++ this.createCell(metricRow.value), ++ this.createStatusCell(metricRow.status, postgresMetrics.message), ++ ); ++ fragment.append(row); ++ }); ++ this.postgresMetricRows.replaceChildren(fragment); + } + + renderStorageStatus(storageStatus = {}) { +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md new file mode 100644 -index 000000000..835bc982d +index 000000000..7ff7fe32a --- /dev/null -+++ b/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-manual-validation-notes.md -@@ -0,0 +1,15 @@ -+# PR_26175_DELTA_005 Manual Validation Notes -+ -+## Manual Review ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md +@@ -0,0 +1,39 @@ ++# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel + -+- Confirmed the removed local clone helper was replaced with `cloneRuntimeValue(...)`. -+- Confirmed event output clone/freeze expectations remain covered by tests. -+- Confirmed no Theme V2, UI, API client, browser-owned data, or status bar files changed. ++Team: Charlie ++Branch: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel ++Base: main ++Lifecycle: Build / Validation ++Repair: Updated from origin/main on 2026-06-25 after PR #177 reported draft=true and mergeable=false. + -+## Manual Validation -+ -+PASS - Code review found no unrelated runtime behavior change. -+ -+## Follow-Up -+ -+- The requested five-PR Delta sequence is complete after this PR merges and `main` is verified. -diff --git a/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-requirement-checklist.md b/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-requirement-checklist.md ++## Scope ++- Added a System Health Postgres Metrics panel backed by the server-owned Admin System Health API. ++- Added safe current-environment metrics for connection status, database name, current schema, migration status, last migration, table count, database size, and last checked. ++- Preserved current-environment-only behavior and did not add cross-environment database checks. ++ ++## 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.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 --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 ++ ++## Repair Notes ++- PASS: Merged current origin/main into the PR branch. ++- PASS: Merge conflict was limited to generated report artifacts: codex_changed_files.txt and codex_review.diff. ++- PASS: No runtime, UI, API, database, or product-data conflict required a product decision. ++- PASS: Scope remains Postgres metrics panel only. ++ ++## ZIP ++- Generated after repair: C:\Users\DavidQ\Documents\GitHub\HTML-JavaScript-Gaming\tmp\PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_delta.zip +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md new file mode 100644 -index 000000000..3488f5872 +index 000000000..9364d3f7c --- /dev/null -+++ b/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-requirement-checklist.md -@@ -0,0 +1,19 @@ -+# PR_26175_DELTA_005 Requirement Checklist -+ -+| Requirement | Status | Notes | -+| --- | --- | --- | -+| Team Delta ownership only | PASS | Runtime event-system cleanup is Delta-owned. | -+| One PR purpose | PASS | Shared runtime clone adoption in event publishing only. | -+| Preserve backward compatibility | PASS | Event, trigger, action, and final systems validation passed. | -+| Update backlog | PASS | `Delta - Event system audit` marked complete. | -+| Update tool state if applicable | PASS | Not applicable; no tool tile/status changed. | -+| Produce governance reports | PASS | Summary, branch validation, checklist, validation lane, manual notes, Codex diff, changed-file list, and ZIP. | -+| Runtime validation | PASS | Focused runtime tests and final systems test passed. | -+| No unrelated files | PASS | Changes are limited to event runtime cleanup, focused test, backlog, and reports. | -+| No branch deletion | PASS | Source branch retained. | -+ -+## Compatibility Notes -+ -+- `publishRuntimeEvents(...)` public output shape is unchanged. -+- The shared runtime clone helper keeps the existing JSON fallback path when `structuredClone` is unavailable. -+- PR_005 does not touch status bar, Theme V2, browser-owned data, or unrelated tool runtime code. -diff --git a/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-validation.md b/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-validation.md ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md +@@ -0,0 +1,17 @@ ++# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Branch Validation ++ ++Branch: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel ++Expected start branch: main, then PR branch for build ++Current status at validation: ++## pr/26177-CHARLIE-029-system-health-postgres-metrics-panel ++Updated from origin/main for PR #177 repair. ++ ++Result: PASS ++ ++Checks: ++- PASS: Started from synchronized main. ++- PASS: Active branch matches PR identity. ++- PASS: Current origin/main merged into branch. ++- PASS: Merge conflict scope was generated report artifacts only. ++- PASS: Worktree contains only scoped PR repair/report changes after report refresh. ++- PASS: No start_of_day files modified. +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md new file mode 100644 -index 000000000..1285e5bb6 +index 000000000..1c30d0c93 --- /dev/null -+++ b/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-validation.md -@@ -0,0 +1,33 @@ -+# PR_26175_DELTA_005 Validation Lane -+ -+## Commands -+ -+```powershell -+node --check src/engine/runtime/runtimeEventSystem.js -+node --check tests/engine/RuntimeEventSystem.test.mjs -+node tests/engine/RuntimeEventSystem.test.mjs -+node tests/engine/RuntimeTriggerProcessing.test.mjs -+node tests/engine/RuntimeActionSystem.test.mjs -+node tests/final/FinalSystems.test.mjs -+git diff --check -+``` -+ -+## Results -+ -+| Command | Status | -+| --- | --- | -+| `node --check src/engine/runtime/runtimeEventSystem.js` | PASS | -+| `node --check tests/engine/RuntimeEventSystem.test.mjs` | PASS | -+| `node tests/engine/RuntimeEventSystem.test.mjs` | PASS | -+| `node tests/engine/RuntimeTriggerProcessing.test.mjs` | PASS | -+| `node tests/engine/RuntimeActionSystem.test.mjs` | PASS | -+| `node tests/final/FinalSystems.test.mjs` | PASS | -+| `git diff --check` | PASS | -+ -+## Browser Validation -+ -+SKIP - No browser UI files changed. -+ -+## Playwright Validation -+ -+SKIP - Runtime event technical debt cleanup is covered by focused Node tests. -diff --git a/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup.md b/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup.md ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md +@@ -0,0 +1,9 @@ ++# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Manual Validation Notes ++ ++- Confirmed Postgres Metrics appears as a separate System Health table. ++- Confirmed metric values come from the server-owned Admin System Health API response. ++- Confirmed unavailable metrics render visibly as Unavailable/WARN rather than silently falling back. ++- Confirmed page retains external scripts/styles only; no inline style/script/handler additions. ++- Confirmed no secrets or database URLs are shown in the Database Health or Postgres Metrics tables. ++- Confirmed current main merge conflict was limited to generated report artifacts. ++- Confirmed no start_of_day files changed. +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md new file mode 100644 -index 000000000..ca5c32c49 +index 000000000..f99698e88 --- /dev/null -+++ b/docs_build/dev/reports/PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup.md -@@ -0,0 +1,52 @@ -+# PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup -+ -+## Summary -+ -+Team Delta cleaned up runtime event-system clone debt by moving event publishing onto the shared runtime clone helper. -+ -+`runtimeEventSystem.js` now uses `cloneRuntimeValue(...)` from `src/shared/runtime/snapshotClone.js`, removing its local JSON clone helper. The runtime event test now covers the shared-helper fallback path with `structuredClone` unavailable. -+ -+## Scope -+ -+- Team: Delta -+- Backlog item: `Delta - Event system audit` -+- Runtime file changed: `src/engine/runtime/runtimeEventSystem.js` -+- Test file changed: `tests/engine/RuntimeEventSystem.test.mjs` -+- Backlog updated: `docs_build/dev/ProjectInstructions/backlog/BACKLOG_MASTER.md` -+ -+## Runtime Impact -+ -+PASS - Event publishing behavior remains backward compatible. -+ -+- Published event payloads are still cloned before output. -+- Runtime event output records remain frozen. -+- Existing event, trigger, action, and final systems validation continues to pass. -+- Runtime event cloning now uses the same shared clone path as other Delta runtime consolidation work. -+ -+## Backlog Update -+ -+PASS - `Delta - Event system audit` is marked complete with this PR as the completion reference. -+ -+## Team Delta Sequence Completion -+ -+| PR | Status | Result | -+| --- | --- | --- | -+| `PR_26175_DELTA_001_Runtime_Performance_Optimization` | PASS | Merged to `main`. | -+| `PR_26175_DELTA_002_Shared_Runtime_Consolidation` | PASS | Merged to `main`. | -+| `PR_26175_DELTA_003_API_Client_Standardization` | PASS | Merged to `main`. | -+| `PR_26175_DELTA_004_Runtime_Test_Expansion` | PASS | Merged to `main`. | -+| `PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup` | PASS | Prepared for validation and merge. | -+ -+## Tool State Update -+ -+SKIP - No Build Path tool status or tool tile state changed. -+ -+## Validation Summary -+ -+PASS - Focused runtime event, trigger, action, and final systems validation completed. -+ -+See `PR_26175_DELTA_005_Runtime_Technical_Debt_Cleanup-validation.md` for command details. -+ -+## Branch Disposition -+ -+Source branch should be retained after merge unless OWNER later approves branch deletion. -diff --git a/src/engine/runtime/runtimeEventSystem.js b/src/engine/runtime/runtimeEventSystem.js -index eba44a68f..87736411d 100644 ---- a/src/engine/runtime/runtimeEventSystem.js -+++ b/src/engine/runtime/runtimeEventSystem.js -@@ -5,6 +5,8 @@ David Quesenberry - runtimeEventSystem.js - */ ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md +@@ -0,0 +1,16 @@ ++# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Requirement Checklis ++ ++- PASS: Add/extend System Health Postgres metrics panel. ++- PASS: Include connection status. ++- PASS: Include database name. ++- PASS: Include current schema/migration status when available. ++- PASS: Include table count when available. ++- PASS: Include database size when available. ++- PASS: Do not add expensive queries. ++- PASS: Show explicit Unavailable status when a metric is unavailable. ++- PASS: Do not expose secrets. ++- PASS: Keep browser UI consuming API/service contract. ++- PASS: No cross-environment checks added. ++- PASS: No start_of_day files modified. ++- PASS: Repaired PR #177 branch from current main. ++- PASS: Mark ready for review after validation/reporting is complete. +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md +new file mode 100644 +index 000000000..e1ca3d673 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md +@@ -0,0 +1,21 @@ ++# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Validation Lane Repor ++ ++Impacted lanes: ++- runtime: Local API Admin System Health status 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 --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 API/UI panel change. ++- Full workspace suite skipped; targeted Project Workspace coverage was sufficient for this scoped Admin page. ++ ++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 4cf3859da..7b1c51f19 100644 +--- a/docs_build/dev/reports/coverage_changed_js_guardrail.tx ++++ b/docs_build/dev/reports/coverage_changed_js_guardrail.tx +@@ -6,7 +6,7 @@ Missing changed runtime JS files are WARN, not FAIL. + Source: Playwright/Chromium built-in V8 coverage from the active Playwright run. -+import { cloneRuntimeValue } from "../../shared/runtime/snapshotClone.js"; -+ - export const RUNTIME_EVENT_ERRORS = Object.freeze({ - CONDITION_MATCHES_INVALID: "RUNTIME_EVENT_CONDITION_MATCHES_INVALID", - RUNTIME_EVENTS_INVALID: "RUNTIME_EVENT_RUNTIME_EVENTS_INVALID", -@@ -53,11 +55,11 @@ export function publishRuntimeEvents(conditionMatches, runtimeEvents) { - eventId: `event.${conditionMatch.eventType}.${conditionMatch.conditionId}.${index}`, - eventType: conditionMatch.eventType, - conditionId: conditionMatch.conditionId, -- payload: Object.freeze(cloneJson(conditionMatch.payload)), -+ payload: Object.freeze(cloneRuntimeValue(conditionMatch.payload)), - })); + Changed runtime JS files considered: +-(63%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 62/98 ++(100%) none changed - no changed runtime JS files - return createEventPublishResult({ -- runtimeEvents: [...runtimeEvents, ...publishedEvents].map((runtimeEvent) => Object.freeze(cloneJson(runtimeEvent))), -+ runtimeEvents: [...runtimeEvents, ...publishedEvents].map((runtimeEvent) => Object.freeze(cloneRuntimeValue(runtimeEvent))), - publishedEvents, - errors, - }); -@@ -146,10 +148,6 @@ function createEventError(code, message, path) { - return Object.freeze({ code, message, path }); - } + Guardrail warnings: +-(100%) none - no changed runtime JS coverage warnings ++(100%) none changed - no changed runtime JS files +diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.tx +index 8f1bdb531..627ed631f 100644 +--- a/docs_build/dev/reports/playwright_v8_coverage_report.tx ++++ b/docs_build/dev/reports/playwright_v8_coverage_report.tx +@@ -12,22 +12,28 @@ Note: entry percentages use function coverage when available, otherwise line cov + Note: coverage entries are aggregated across every page/tool where coverageReporter.start(page) and coverageReporter.stop(page) ran. --function cloneJson(value) { -- return JSON.parse(JSON.stringify(value)); --} -- - function isRecord(value) { - return value !== null && typeof value === "object" && !Array.isArray(value); - } -diff --git a/tests/engine/RuntimeEventSystem.test.mjs b/tests/engine/RuntimeEventSystem.test.mjs -index 1cb9ca85d..9a0b1fc01 100644 ---- a/tests/engine/RuntimeEventSystem.test.mjs -+++ b/tests/engine/RuntimeEventSystem.test.mjs -@@ -87,6 +87,25 @@ export function run() { - assert.throws(() => { - clonedResult.runtimeEvents[0].eventId = "event.changed"; - }, TypeError); -+ -+ const nativeStructuredClone = globalThis.structuredClone; -+ globalThis.structuredClone = undefined; -+ try { -+ const fallbackRuntimeEvent = { -+ eventId: "event.runtime.frameStart.3", -+ eventType: "event.frameStart", -+ payload: { -+ tick: 3, -+ }, -+ }; -+ const fallbackResult = publishRuntimeEvents([], [fallbackRuntimeEvent]); -+ fallbackRuntimeEvent.payload.tick = 333; -+ -+ assert.equal(fallbackResult.valid, true); -+ assert.equal(fallbackResult.runtimeEvents[0].payload.tick, 3); -+ } finally { -+ globalThis.structuredClone = nativeStructuredClone; -+ } + Exercised tool entry points detected: +-(0%) Toolbox Index - not exercised by this Playwright run ++(46%) Toolbox Index - exercised 1 runtime JS files + (0%) Tool Template V2 - not exercised by this Playwright run +-(64%) Theme V2 Shared JS - exercised 2 runtime JS files ++(78%) Theme V2 Shared JS - exercised 5 runtime JS files + + Changed runtime JS files covered: +-(63%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 62/98 ++(100%) none changed - no changed runtime JS files + + Files with executed line/function counts where available: +-(63%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 62/98 ++(36%) src/api/server-api-client.js - executed lines 168/168; executed functions 5/14 ++(46%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 12/26 ++(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/theme-icons.js - executed lines 69/69; executed functions 4/5 +-(100%) legal/legal-nav.js - executed lines 34/34; executed functions 3/3 ++(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 ++(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 + + Uncovered or low-coverage changed JS files: +-(100%) none - no low-coverage changed runtime JS files ++(100%) none changed - no changed runtime JS files + + Changed JS files considered: +-(0%) tests/playwright/tools/RemainingLegalPages.spec.mjs - changed JS file not collected as browser runtime coverage +-(63%) assets/theme-v2/js/gamefoundry-partials.js - changed JS file with browser V8 coverage +-(100%) legal/legal-nav.js - changed JS file with browser V8 coverage ++(100%) none - no changed JS files +diff --git a/src/dev-runtime/server/local-api-router.mjs b/src/dev-runtime/server/local-api-router.mjs +index 993e14a59..462d8b7a0 100644 +--- a/src/dev-runtime/server/local-api-router.mjs ++++ b/src/dev-runtime/server/local-api-router.mjs +@@ -650,6 +650,63 @@ function databaseConfigStatus(env = process.env) { + } } - if (import.meta.url === `file://${process.argv[1]}`) { ++function systemHealthPostgresMetrics(databaseStatus = {}, checkedAt = new Date().toISOString()) { ++ const reason = databaseStatus.message || "Postgres metrics are reported only when the current environment database reader returns safe values."; ++ const tableCount = Number(databaseStatus.tableCount); ++ const metricRows = [ ++ { ++ metric: "Connection status", ++ status: databaseStatus.connectivityStatus || databaseStatus.status || "WARN", ++ value: databaseStatus.connectivity || "Unavailable", ++ }, ++ { ++ metric: "Database name", ++ status: databaseStatus.currentDatabaseNameStatus || databaseStatus.databaseNameStatus || "WARN", ++ value: databaseStatus.currentDatabaseName || databaseStatus.databaseName || "Unavailable", ++ }, ++ { ++ metric: "Current schema", ++ status: databaseStatus.currentSchemaStatus || "WARN", ++ value: databaseStatus.currentSchema || "Unavailable", ++ }, ++ { ++ metric: "Migration status", ++ status: databaseStatus.migrationStatus || "WARN", ++ value: databaseStatus.migrationStatus === "PASS" ++ ? `DDL=${databaseStatus.migrationCounts?.DDL || 0}; DML=${databaseStatus.migrationCounts?.DML || 0} ++ : "Unavailable", ++ }, ++ { ++ metric: "Last migration", ++ status: databaseStatus.lastMigrationStatus || "WARN", ++ value: databaseStatus.lastMigration?.name || "Unavailable", ++ }, ++ { ++ metric: "Table count", ++ status: Number.isFinite(tableCount) ? "PASS" : "WARN", ++ value: Number.isFinite(tableCount) ? String(tableCount) : "Unavailable", ++ }, ++ { ++ metric: "Database size", ++ status: databaseStatus.databaseSizeStatus || "WARN", ++ value: databaseStatus.databaseSize || "Unavailable", ++ }, ++ { ++ metric: "Last checked", ++ status: databaseStatus.lastChecked ? "PASS" : "WARN", ++ value: databaseStatus.lastChecked || checkedAt || "Unavailable", ++ }, ++ ]; ++ return { ++ lastChecked: databaseStatus.lastChecked || checkedAt, ++ message: reason, ++ rows: metricRows, ++ secretEditingAllowed: false, ++ secretsExposed: false, ++ status: overallHealthStatus(metricRows), ++ }; ++} ++ + function projectPackageReadinessStatus() { + const decisionPath = path.join(process.cwd(), "docs_build", "codex", "decisions", "project-packages.md"); + const contract = projectPackageReadinessContract(); +@@ -4232,31 +4289,60 @@ class ApiRuntimeDataSource { + migrationStatus: "WARN", + responseTimeMs: null, + status: "WARN", ++ currentDatabaseName: "", ++ currentDatabaseNameStatus: "WARN", ++ currentSchema: "", ++ currentSchemaStatus: "WARN", ++ databaseSize: "", ++ databaseSizeBytes: null, ++ databaseSizeStatus: "WARN", ++ tableCount: null, + version: "", + versionStatus: "WARN", + }; + try { + const adapter = this.supabaseDatabaseAdapter("Reading Admin System Health migration history"); +- const versionRows = await adapter.databaseClient().query("SELECT version() AS version;"); +- const countRows = await adapter.databaseClient().query( ++ const databaseClient = adapter.databaseClient(); ++ const versionRows = await databaseClient.query("SELECT version() AS version;"); ++ const currentRows = await databaseClient.query("SELECT current_database() AS database_name, current_schema() AS schema_name;"); ++ const countRows = await databaseClient.query( + SELECT "migrationType", count(*)::int AS coun + FROM schema_migrations + GROUP BY "migrationType" + ORDER BY "migrationType"; + `); +- const lastRows = await adapter.databaseClient().query( ++ const lastRows = await databaseClient.query( + SELECT "migrationType", "migrationName", "appliedAt" + FROM schema_migrations + ORDER BY "appliedAt" DESC, key DESC + LIMIT 1; ++`); ++ const tableCountRows = await databaseClient.query( ++SELECT count(*)::int AS table_coun ++FROM information_schema.tables ++WHERE table_schema NOT IN ('pg_catalog', 'information_schema') ++ AND table_type = 'BASE TABLE'; ++`); ++ const databaseSizeRows = await databaseClient.query( ++SELECT pg_database_size(current_database()) AS database_size_bytes, ++ pg_size_pretty(pg_database_size(current_database())) AS database_size; + `); + const counts = new Map(countRows.map((row) => [String(row.migrationType || ""), Number(row.count || 0)])); ++ const currentRow = currentRows[0] || {}; + const lastRow = lastRows[0] || {}; +- const version = String(versionRows[0]?.version || "").trim(); +- return { ++ const tableCount = Number(tableCountRows[0]?.table_count); ++ const databaseSizeBytes = Number(databaseSizeRows[0]?.database_size_bytes); ++ const connectedStatus = { + ...databaseStatus, + connectivity: "connected", + connectivityStatus: "PASS", ++ currentDatabaseName: String(currentRow.database_name || ""), ++ currentDatabaseNameStatus: currentRow.database_name ? "PASS" : "WARN", ++ currentSchema: String(currentRow.schema_name || ""), ++ currentSchemaStatus: currentRow.schema_name ? "PASS" : "WARN", ++ databaseSize: String(databaseSizeRows[0]?.database_size || ""), ++ databaseSizeBytes: Number.isFinite(databaseSizeBytes) ? databaseSizeBytes : null, ++ databaseSizeStatus: databaseSizeRows[0]?.database_size ? "PASS" : "WARN", + lastMigration: { + appliedAt: String(lastRow.appliedAt || ""), + name: String(lastRow.migrationName || ""), +@@ -4268,14 +4354,19 @@ LIMIT 1; + DML: counts.get("DML") || 0, + }, + migrationStatus: "PASS", +- message: "Current environment database connection responded through the safe Admin System Health API.", ++ message: "Current environment database connection and safe Postgres metrics responded through the Admin System Health API.", + responseTimeMs: Date.now() - startedAt, + status: databaseStatus.configured === true ? "PASS" : "WARN", +- version: version || "not available", +- versionStatus: version ? "PASS" : "WARN", ++ tableCount: Number.isFinite(tableCount) ? tableCount : null, ++ version: String(versionRows[0]?.version || "").trim() || "not available", ++ versionStatus: versionRows[0]?.version ? "PASS" : "WARN", + }; +- } catch (error) { + return { ++ ...connectedStatus, ++ postgresMetrics: systemHealthPostgresMetrics(connectedStatus, connectedStatus.lastChecked), ++ }; ++ } catch (error) { ++ const failedStatus = { + ...databaseStatus, + connectivity: "failed", + connectivityStatus: "FAIL", +@@ -4283,6 +4374,10 @@ LIMIT 1; + responseTimeMs: Date.now() - startedAt, + status: "FAIL", + }; ++ return { ++ ...failedStatus, ++ postgresMetrics: systemHealthPostgresMetrics(failedStatus, failedStatus.lastChecked), ++ }; + } + } + +@@ -4410,6 +4505,7 @@ LIMIT 1; + const environmentIdentity = systemHealthEnvironmentIdentity(process.env, checkedAt); + const environmentMap = systemHealthEnvironmentMap(); + const databaseStatus = await this.ownerDatabaseStatus(environmentIdentity); ++ const postgresMetrics = databaseStatus.postgresMetrics || systemHealthPostgresMetrics(databaseStatus, checkedAt); + const storageStatus = this.ownerStorageStatus(); + const environmentStatus = storageProjectsPrefixStatus(); + const localApiStartup = systemHealthLocalApiStartupDiagnostics(); +@@ -4581,6 +4677,7 @@ LIMIT 1; + notificationsFoundation, + operationsHealth, + overview, ++ postgresMetrics, + pressureLabels: SYSTEM_HEALTH_LIMIT_PRESSURE_LABELS, + connectionSummary: this.ownerConnectionSummary(), + databaseStatus, +diff --git a/tests/api/admin-system-health/contract.test.mjs b/tests/api/admin-system-health/contract.test.mjs +index e9e366882..25aa84b34 100644 +--- a/tests/api/admin-system-health/contract.test.mjs ++++ b/tests/api/admin-system-health/contract.test.mjs +@@ -111,6 +111,7 @@ test("Admin System Health completion contract remains server-owned and current-e + "runtimeFeatureFlags", + "serviceHealth", + "configurationSummary", ++ "postgresMetrics", + "scheduledMonitoring", + "notificationsFoundation", + ].filter((key) => Object.hasOwn(health, key)), +@@ -121,10 +122,24 @@ test("Admin System Health completion contract remains server-owned and current-e + "runtimeFeatureFlags", + "serviceHealth", + "configurationSummary", ++ "postgresMetrics", + "scheduledMonitoring", + "notificationsFoundation", + ], + ); ++ assert.deepEqual( ++ health.postgresMetrics.rows.map((row) => row.metric), ++ [ ++ "Connection status", ++ "Database name", ++ "Current schema", ++ "Migration status", ++ "Last migration", ++ "Table count", ++ "Database size", ++ "Last checked", ++ ], ++ ); + const healthText = JSON.stringify(health); + assert.equal(healthText.includes("api-secret"), false); + assert.equal(healthText.includes("site-secret"), false); +diff --git a/tests/dev-runtime/AdminHealthOperations.test.mjs b/tests/dev-runtime/AdminHealthOperations.test.mjs +index e682005b2..99fef0757 100644 +--- a/tests/dev-runtime/AdminHealthOperations.test.mjs ++++ b/tests/dev-runtime/AdminHealthOperations.test.mjs +@@ -142,6 +142,24 @@ test("Admin can view operational health while Creator sessions are blocked", asy + assert.equal(typeof health.databaseStatus.lastChecked, "string"); + assert.equal(typeof health.databaseStatus.responseTimeMs === "number" || health.databaseStatus.responseTimeMs === null, true); + assert.equal(typeof health.databaseStatus.version, "string"); ++ assert.equal(health.postgresMetrics.secretEditingAllowed, false); ++ assert.equal(health.postgresMetrics.secretsExposed, false); ++ assert.deepEqual( ++ health.postgresMetrics.rows.map((row) => row.metric), ++ [ ++ "Connection status", ++ "Database name", ++ "Current schema", ++ "Migration status", ++ "Last migration", ++ "Table count", ++ "Database size", ++ "Last checked", ++ ], ++ ); ++ assert.equal(health.postgresMetrics.rows.every((row) => typeof row.value === "string"), true); ++ assert.equal(health.postgresMetrics.rows.some((row) => row.value === "Unavailable"), true); ++ assert.equal(health.databaseStatus.postgresMetrics.rows.length, health.postgresMetrics.rows.length); + assert.equal(health.runtimeHealth.environmentName, "Local"); + assert.equal(health.runtimeHealth.appVersion, "1.0.0"); + assert.equal(health.runtimeHealth.apiVersion, "1.0.0"); +@@ -318,6 +336,8 @@ test("Admin can view operational health while Creator sessions are blocked", asy + const healthText = JSON.stringify(health.operationsHealth); + assert.equal(healthText.includes("monthlyPriceCents"), false); + assert.equal(healthText.includes("priceCents"), false); ++ assert.equal(JSON.stringify(health.postgresMetrics).includes("postgres://"), false); ++ assert.equal(JSON.stringify(health.postgresMetrics).includes("postgresql://"), false); + assert.equal(health.secretEditingAllowed, false); + } finally { + await server.close(); +diff --git a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +index 52d7acf50..56733ce6a 100644 +--- a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs ++++ b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +@@ -217,6 +217,16 @@ test("Admin System Health renders Postgres diagnostics through the safe status A + await expect(page.locator("[data-admin-system-health-db-value='lastChecked']")).not.toHaveText("Loading"); + await expect(page.getByRole("table", { name: "Database health" })).not.toContainText("postgres://"); + await expect(page.getByRole("table", { name: "Database health" })).not.toContainText("postgresql://"); ++ const postgresMetricsTable = page.getByRole("table", { name: "Postgres metrics" }); ++ await expect(postgresMetricsTable).toContainText("Connection status"); ++ await expect(postgresMetricsTable).toContainText("Database name"); ++ await expect(postgresMetricsTable).toContainText("Current schema"); ++ await expect(postgresMetricsTable).toContainText("Migration status"); ++ await expect(postgresMetricsTable).toContainText("Last migration"); ++ await expect(postgresMetricsTable).toContainText("Table count"); ++ await expect(postgresMetricsTable).toContainText("Database size"); ++ await expect(postgresMetricsTable).not.toContainText("postgres://"); ++ await expect(postgresMetricsTable).not.toContainText("postgresql://"); + await expect(page.getByRole("table", { name: "Storage health" })).toContainText("Cloudflare R2"); + await expect(page.locator("[data-admin-system-health-storage-value='bucket']")).toContainText("/dev"); + await expect(page.locator("[data-admin-system-health-storage-value='list']")).toContainText("/dev"); +@@ -333,6 +343,7 @@ test("Admin System Health operations page keeps scripts and styles external", as + expect(pageSource).toContain("Runtime Health"); + expect(pageSource).toContain("Diagnostics Plan"); + expect(pageSource).toContain("Local API Startup Diagnostics"); ++ expect(pageSource).toContain("Postgres Metrics"); + expect(pageSource).toContain("Server-owned Postgres health reader"); + expect(pageSource).toContain("Server-owned Cloudflare R2 storage diagnostic"); + expect(pageSource).toContain("assets/theme-v2/js/admin-system-health.js"); diff --git a/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.txt index 4cf3859da..7b1c51f19 100644 --- a/docs_build/dev/reports/coverage_changed_js_guardrail.txt +++ b/docs_build/dev/reports/coverage_changed_js_guardrail.txt @@ -6,7 +6,7 @@ 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: -(63%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 62/98 +(100%) none changed - no changed runtime JS files Guardrail warnings: -(100%) none - no changed runtime JS coverage warnings +(100%) none changed - no changed runtime JS files diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.txt index 8f1bdb531..627ed631f 100644 --- a/docs_build/dev/reports/playwright_v8_coverage_report.txt +++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt @@ -12,22 +12,28 @@ Note: entry percentages use function coverage when available, otherwise line cov Note: coverage entries are aggregated across every page/tool where coverageReporter.start(page) and coverageReporter.stop(page) ran. Exercised tool entry points detected: -(0%) Toolbox Index - not exercised by this Playwright run +(46%) Toolbox Index - exercised 1 runtime JS files (0%) Tool Template V2 - not exercised by this Playwright run -(64%) Theme V2 Shared JS - exercised 2 runtime JS files +(78%) Theme V2 Shared JS - exercised 5 runtime JS files Changed runtime JS files covered: -(63%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 62/98 +(100%) none changed - no changed runtime JS files Files with executed line/function counts where available: -(63%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 62/98 +(36%) src/api/server-api-client.js - executed lines 168/168; executed functions 5/14 +(46%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 12/26 +(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/theme-icons.js - executed lines 69/69; executed functions 4/5 -(100%) legal/legal-nav.js - executed lines 34/34; executed functions 3/3 +(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 +(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 Uncovered or low-coverage changed JS files: -(100%) none - no low-coverage changed runtime JS files +(100%) none changed - no changed runtime JS files Changed JS files considered: -(0%) tests/playwright/tools/RemainingLegalPages.spec.mjs - changed JS file not collected as browser runtime coverage -(63%) assets/theme-v2/js/gamefoundry-partials.js - changed JS file with browser V8 coverage -(100%) legal/legal-nav.js - changed JS file with browser V8 coverage +(100%) none - no changed JS files diff --git a/src/dev-runtime/server/local-api-router.mjs b/src/dev-runtime/server/local-api-router.mjs index 993e14a59..462d8b7a0 100644 --- a/src/dev-runtime/server/local-api-router.mjs +++ b/src/dev-runtime/server/local-api-router.mjs @@ -650,6 +650,63 @@ function databaseConfigStatus(env = process.env) { } } +function systemHealthPostgresMetrics(databaseStatus = {}, checkedAt = new Date().toISOString()) { + const reason = databaseStatus.message || "Postgres metrics are reported only when the current environment database reader returns safe values."; + const tableCount = Number(databaseStatus.tableCount); + const metricRows = [ + { + metric: "Connection status", + status: databaseStatus.connectivityStatus || databaseStatus.status || "WARN", + value: databaseStatus.connectivity || "Unavailable", + }, + { + metric: "Database name", + status: databaseStatus.currentDatabaseNameStatus || databaseStatus.databaseNameStatus || "WARN", + value: databaseStatus.currentDatabaseName || databaseStatus.databaseName || "Unavailable", + }, + { + metric: "Current schema", + status: databaseStatus.currentSchemaStatus || "WARN", + value: databaseStatus.currentSchema || "Unavailable", + }, + { + metric: "Migration status", + status: databaseStatus.migrationStatus || "WARN", + value: databaseStatus.migrationStatus === "PASS" + ? `DDL=${databaseStatus.migrationCounts?.DDL || 0}; DML=${databaseStatus.migrationCounts?.DML || 0}` + : "Unavailable", + }, + { + metric: "Last migration", + status: databaseStatus.lastMigrationStatus || "WARN", + value: databaseStatus.lastMigration?.name || "Unavailable", + }, + { + metric: "Table count", + status: Number.isFinite(tableCount) ? "PASS" : "WARN", + value: Number.isFinite(tableCount) ? String(tableCount) : "Unavailable", + }, + { + metric: "Database size", + status: databaseStatus.databaseSizeStatus || "WARN", + value: databaseStatus.databaseSize || "Unavailable", + }, + { + metric: "Last checked", + status: databaseStatus.lastChecked ? "PASS" : "WARN", + value: databaseStatus.lastChecked || checkedAt || "Unavailable", + }, + ]; + return { + lastChecked: databaseStatus.lastChecked || checkedAt, + message: reason, + rows: metricRows, + secretEditingAllowed: false, + secretsExposed: false, + status: overallHealthStatus(metricRows), + }; +} + function projectPackageReadinessStatus() { const decisionPath = path.join(process.cwd(), "docs_build", "codex", "decisions", "project-packages.md"); const contract = projectPackageReadinessContract(); @@ -4232,31 +4289,60 @@ class ApiRuntimeDataSource { migrationStatus: "WARN", responseTimeMs: null, status: "WARN", + currentDatabaseName: "", + currentDatabaseNameStatus: "WARN", + currentSchema: "", + currentSchemaStatus: "WARN", + databaseSize: "", + databaseSizeBytes: null, + databaseSizeStatus: "WARN", + tableCount: null, version: "", versionStatus: "WARN", }; try { const adapter = this.supabaseDatabaseAdapter("Reading Admin System Health migration history"); - const versionRows = await adapter.databaseClient().query("SELECT version() AS version;"); - const countRows = await adapter.databaseClient().query(` + const databaseClient = adapter.databaseClient(); + const versionRows = await databaseClient.query("SELECT version() AS version;"); + const currentRows = await databaseClient.query("SELECT current_database() AS database_name, current_schema() AS schema_name;"); + const countRows = await databaseClient.query(` SELECT "migrationType", count(*)::int AS count FROM schema_migrations GROUP BY "migrationType" ORDER BY "migrationType"; `); - const lastRows = await adapter.databaseClient().query(` + const lastRows = await databaseClient.query(` SELECT "migrationType", "migrationName", "appliedAt" FROM schema_migrations ORDER BY "appliedAt" DESC, key DESC LIMIT 1; +`); + const tableCountRows = await databaseClient.query(` +SELECT count(*)::int AS table_count +FROM information_schema.tables +WHERE table_schema NOT IN ('pg_catalog', 'information_schema') + AND table_type = 'BASE TABLE'; +`); + const databaseSizeRows = await databaseClient.query(` +SELECT pg_database_size(current_database()) AS database_size_bytes, + pg_size_pretty(pg_database_size(current_database())) AS database_size; `); const counts = new Map(countRows.map((row) => [String(row.migrationType || ""), Number(row.count || 0)])); + const currentRow = currentRows[0] || {}; const lastRow = lastRows[0] || {}; - const version = String(versionRows[0]?.version || "").trim(); - return { + const tableCount = Number(tableCountRows[0]?.table_count); + const databaseSizeBytes = Number(databaseSizeRows[0]?.database_size_bytes); + const connectedStatus = { ...databaseStatus, connectivity: "connected", connectivityStatus: "PASS", + currentDatabaseName: String(currentRow.database_name || ""), + currentDatabaseNameStatus: currentRow.database_name ? "PASS" : "WARN", + currentSchema: String(currentRow.schema_name || ""), + currentSchemaStatus: currentRow.schema_name ? "PASS" : "WARN", + databaseSize: String(databaseSizeRows[0]?.database_size || ""), + databaseSizeBytes: Number.isFinite(databaseSizeBytes) ? databaseSizeBytes : null, + databaseSizeStatus: databaseSizeRows[0]?.database_size ? "PASS" : "WARN", lastMigration: { appliedAt: String(lastRow.appliedAt || ""), name: String(lastRow.migrationName || ""), @@ -4268,14 +4354,19 @@ LIMIT 1; DML: counts.get("DML") || 0, }, migrationStatus: "PASS", - message: "Current environment database connection responded through the safe Admin System Health API.", + message: "Current environment database connection and safe Postgres metrics responded through the Admin System Health API.", responseTimeMs: Date.now() - startedAt, status: databaseStatus.configured === true ? "PASS" : "WARN", - version: version || "not available", - versionStatus: version ? "PASS" : "WARN", + tableCount: Number.isFinite(tableCount) ? tableCount : null, + version: String(versionRows[0]?.version || "").trim() || "not available", + versionStatus: versionRows[0]?.version ? "PASS" : "WARN", }; - } catch (error) { return { + ...connectedStatus, + postgresMetrics: systemHealthPostgresMetrics(connectedStatus, connectedStatus.lastChecked), + }; + } catch (error) { + const failedStatus = { ...databaseStatus, connectivity: "failed", connectivityStatus: "FAIL", @@ -4283,6 +4374,10 @@ LIMIT 1; responseTimeMs: Date.now() - startedAt, status: "FAIL", }; + return { + ...failedStatus, + postgresMetrics: systemHealthPostgresMetrics(failedStatus, failedStatus.lastChecked), + }; } } @@ -4410,6 +4505,7 @@ LIMIT 1; const environmentIdentity = systemHealthEnvironmentIdentity(process.env, checkedAt); const environmentMap = systemHealthEnvironmentMap(); const databaseStatus = await this.ownerDatabaseStatus(environmentIdentity); + const postgresMetrics = databaseStatus.postgresMetrics || systemHealthPostgresMetrics(databaseStatus, checkedAt); const storageStatus = this.ownerStorageStatus(); const environmentStatus = storageProjectsPrefixStatus(); const localApiStartup = systemHealthLocalApiStartupDiagnostics(); @@ -4581,6 +4677,7 @@ LIMIT 1; notificationsFoundation, operationsHealth, overview, + postgresMetrics, pressureLabels: SYSTEM_HEALTH_LIMIT_PRESSURE_LABELS, connectionSummary: this.ownerConnectionSummary(), databaseStatus, diff --git a/tests/api/admin-system-health/contract.test.mjs b/tests/api/admin-system-health/contract.test.mjs index e9e366882..25aa84b34 100644 --- a/tests/api/admin-system-health/contract.test.mjs +++ b/tests/api/admin-system-health/contract.test.mjs @@ -111,6 +111,7 @@ test("Admin System Health completion contract remains server-owned and current-e "runtimeFeatureFlags", "serviceHealth", "configurationSummary", + "postgresMetrics", "scheduledMonitoring", "notificationsFoundation", ].filter((key) => Object.hasOwn(health, key)), @@ -121,10 +122,24 @@ test("Admin System Health completion contract remains server-owned and current-e "runtimeFeatureFlags", "serviceHealth", "configurationSummary", + "postgresMetrics", "scheduledMonitoring", "notificationsFoundation", ], ); + assert.deepEqual( + health.postgresMetrics.rows.map((row) => row.metric), + [ + "Connection status", + "Database name", + "Current schema", + "Migration status", + "Last migration", + "Table count", + "Database size", + "Last checked", + ], + ); const healthText = JSON.stringify(health); assert.equal(healthText.includes("api-secret"), false); assert.equal(healthText.includes("site-secret"), false); diff --git a/tests/dev-runtime/AdminHealthOperations.test.mjs b/tests/dev-runtime/AdminHealthOperations.test.mjs index e682005b2..99fef0757 100644 --- a/tests/dev-runtime/AdminHealthOperations.test.mjs +++ b/tests/dev-runtime/AdminHealthOperations.test.mjs @@ -142,6 +142,24 @@ test("Admin can view operational health while Creator sessions are blocked", asy assert.equal(typeof health.databaseStatus.lastChecked, "string"); assert.equal(typeof health.databaseStatus.responseTimeMs === "number" || health.databaseStatus.responseTimeMs === null, true); assert.equal(typeof health.databaseStatus.version, "string"); + assert.equal(health.postgresMetrics.secretEditingAllowed, false); + assert.equal(health.postgresMetrics.secretsExposed, false); + assert.deepEqual( + health.postgresMetrics.rows.map((row) => row.metric), + [ + "Connection status", + "Database name", + "Current schema", + "Migration status", + "Last migration", + "Table count", + "Database size", + "Last checked", + ], + ); + assert.equal(health.postgresMetrics.rows.every((row) => typeof row.value === "string"), true); + assert.equal(health.postgresMetrics.rows.some((row) => row.value === "Unavailable"), true); + assert.equal(health.databaseStatus.postgresMetrics.rows.length, health.postgresMetrics.rows.length); assert.equal(health.runtimeHealth.environmentName, "Local"); assert.equal(health.runtimeHealth.appVersion, "1.0.0"); assert.equal(health.runtimeHealth.apiVersion, "1.0.0"); @@ -318,6 +336,8 @@ test("Admin can view operational health while Creator sessions are blocked", asy const healthText = JSON.stringify(health.operationsHealth); assert.equal(healthText.includes("monthlyPriceCents"), false); assert.equal(healthText.includes("priceCents"), false); + assert.equal(JSON.stringify(health.postgresMetrics).includes("postgres://"), false); + assert.equal(JSON.stringify(health.postgresMetrics).includes("postgresql://"), false); assert.equal(health.secretEditingAllowed, false); } finally { await server.close(); diff --git a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs index 52d7acf50..56733ce6a 100644 --- a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +++ b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs @@ -217,6 +217,16 @@ test("Admin System Health renders Postgres diagnostics through the safe status A await expect(page.locator("[data-admin-system-health-db-value='lastChecked']")).not.toHaveText("Loading"); await expect(page.getByRole("table", { name: "Database health" })).not.toContainText("postgres://"); await expect(page.getByRole("table", { name: "Database health" })).not.toContainText("postgresql://"); + const postgresMetricsTable = page.getByRole("table", { name: "Postgres metrics" }); + await expect(postgresMetricsTable).toContainText("Connection status"); + await expect(postgresMetricsTable).toContainText("Database name"); + await expect(postgresMetricsTable).toContainText("Current schema"); + await expect(postgresMetricsTable).toContainText("Migration status"); + await expect(postgresMetricsTable).toContainText("Last migration"); + await expect(postgresMetricsTable).toContainText("Table count"); + await expect(postgresMetricsTable).toContainText("Database size"); + await expect(postgresMetricsTable).not.toContainText("postgres://"); + await expect(postgresMetricsTable).not.toContainText("postgresql://"); await expect(page.getByRole("table", { name: "Storage health" })).toContainText("Cloudflare R2"); await expect(page.locator("[data-admin-system-health-storage-value='bucket']")).toContainText("/dev"); await expect(page.locator("[data-admin-system-health-storage-value='list']")).toContainText("/dev"); @@ -333,6 +343,7 @@ test("Admin System Health operations page keeps scripts and styles external", as expect(pageSource).toContain("Runtime Health"); expect(pageSource).toContain("Diagnostics Plan"); expect(pageSource).toContain("Local API Startup Diagnostics"); + expect(pageSource).toContain("Postgres Metrics"); expect(pageSource).toContain("Server-owned Postgres health reader"); expect(pageSource).toContain("Server-owned Cloudflare R2 storage diagnostic"); expect(pageSource).toContain("assets/theme-v2/js/admin-system-health.js");
Storage Health - Cloudflare R2