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 @@
Manual Health Actions
| Check |
Target |
+ Timing |
Status |
- | Bucket connectivity | Loading | PENDING |
- | List | Loading | PENDING |
- | Upload | Loading | PENDING |
- | Read | Loading | PENDING |
- | Delete | Loading | PENDING |
- | Last checked | Loading | PENDING |
+ | Bucket connectivity | Loading | Loading | PENDING |
+ | List | Loading | Loading | PENDING |
+ | Upload | Loading | Loading | PENDING |
+ | Read | Loading | Loading | PENDING |
+ | Delete | Loading | Loading | PENDING |
+ | Last checked | Loading | Loading | PENDING |
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.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 --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 Checklist
+
+- 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 Report
+
+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/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt
index 79c9ad1c4..ae2d25d52 100644
--- a/docs_build/dev/reports/codex_changed_files.txt
+++ b/docs_build/dev/reports/codex_changed_files.txt
@@ -1,44 +1,45 @@
-# git diff --name-only origin/main --
+# git diff --name-only pr/26177-CHARLIE-029-system-health-postgres-metrics-panel --
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/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/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_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/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/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(-)
+# 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(-)
diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff
index 1f8c7b688..8ce24fdc9 100644
--- a/docs_build/dev/reports/codex_review.diff
+++ b/docs_build/dev/reports/codex_review.diff
@@ -1,126 +1,155 @@
diff --git a/admin/system-health.html b/admin/system-health.html
-index abe5c4f97..5aa9fa80a 100644
+index 5aa9fa80a..b62f232d7 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 @@
+@@ -316,16 +316,17 @@
+
+ | Check |
+ Target |
++ Timing |
+ Status |
+
+
+
+- | Bucket connectivity | Loading | PENDING |
+- | List | Loading | PENDING |
+- | Upload | Loading | PENDING |
+- | Read | Loading | PENDING |
+- | Delete | Loading | PENDING |
+- | Last checked | Loading | PENDING |
++ | Bucket connectivity | Loading | Loading | PENDING |
++ | List | Loading | Loading | PENDING |
++ | Upload | Loading | Loading | PENDING |
++ | Read | Loading | Loading | PENDING |
++ | Delete | Loading | Loading | PENDING |
++ | Last checked | Loading | Loading | PENDING |
-+
-+
-+ Postgres Metrics
-+
-+
-+ | Metric |
-+ Safe Value |
-+ Status |
-+
-+
-+
-+ | Postgres metrics | Unavailable until safe API status loads | PENDING |
-+
-+
-+
-
-
- Storage Health - Cloudflare R2
diff --git a/assets/theme-v2/js/admin-system-health.js b/assets/theme-v2/js/admin-system-health.js
-index 656394104..746d47753 100644
+index 746d47753..21bc50a63 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]");
+@@ -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);
}
-@@ -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;
++ setStorageTiming(key, value, fallback) {
++ const node = this.storageTimings.get(key);
++ if (node) {
++ node.textContent = asText(value, fallback);
+ }
-+ 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;
+ 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");
++ });
+ }
-+ 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
+ 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..7ff7fe32a
+index 000000000..61eba9efb
--- /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
++++ 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-029-system-health-postgres-metrics-panel
-+Base: main
++Branch: pr/26177-CHARLIE-030-r2-storage-health-expanded-validation
++Base: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel
+Lifecycle: Build / Validation
-+Repair: Updated from origin/main on 2026-06-25 after PR #177 reported draft=true and mergeable=false.
++Repair: Rebased onto repaired PR_26177_CHARLIE_029 branch on 2026-06-25.
+
+## 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.
++- 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
@@ -131,89 +160,81 @@ index 000000000..7ff7fe32a
+## 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
+
-+## 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
++- 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..9364d3f7c
+index 000000000..0c0387294
--- /dev/null
-+++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md
++++ b/docs_build/dev/reports/PR_26177_CHARLIE_030-r2-storage-health-expanded-validation_branch-validation.md
@@ -0,0 +1,17 @@
-+# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Branch Validation
++# PR_26177_CHARLIE_030-r2-storage-health-expanded-validation Branch Validation
+
-+Branch: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel
-+Expected start branch: main, then PR branch for build
++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-029-system-health-postgres-metrics-panel
-+Updated from origin/main for PR #177 repair.
++## pr/26177-CHARLIE-030-r2-storage-health-expanded-validation
++Updated onto repaired PR_26177_CHARLIE_029 stack base.
+
+Result: PASS
+
+Checks:
-+- PASS: Started from synchronized main.
++- PASS: Branch created from PR 029 branch for the approved stacked chain.
+- 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: 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_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
+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..1c30d0c93
+index 000000000..36806ff77
--- /dev/null
-+++ b/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_030-r2-storage-health-expanded-validation_manual-validation-notes.md
@@ -0,0 +1,9 @@
-+# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Manual Validation Notes
++# PR_26177_CHARLIE_030-r2-storage-health-expanded-validation 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 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_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
+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..f99698e88
+index 000000000..2dda36cc8
--- /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 Checklis
++++ 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: 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: 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: 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
++- 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..e1ca3d673
+index 000000000..0dbf85ec0
--- /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
++++ 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 status contract.
++- 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.
@@ -221,358 +242,421 @@ index 000000000..e1ca3d673
+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 API/UI panel change.
-+- Full workspace suite skipped; targeted Project Workspace coverage was sufficient for this scoped Admin page.
++- 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 4cf3859da..7b1c51f19 100644
+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,7 @@ Missing changed runtime JS files are WARN, not FAIL.
+@@ -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:
--(63%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 62/98
-+(100%) none changed - no changed runtime JS files
+-(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 - no changed runtime JS coverage warnings
-+(100%) none changed - no changed runtime JS files
+-(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 8f1bdb531..627ed631f 100644
+index 627ed631f..2a2982e6a 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.
-
- 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
+@@ -17,7 +17,9 @@ Exercised tool entry points detected:
+ (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
+-(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:
--(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
+ (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
--(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
+ (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 - no low-coverage changed runtime JS files
-+(100%) none changed - no changed runtime 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:
--(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
+-(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 993e14a59..462d8b7a0 100644
+index 462d8b7a0..6dbb8a106 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) {
+@@ -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(),
+ };
}
- }
-+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",
+- 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,
};
- 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,
+ }
+
+ 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",
- };
-+ return {
-+ ...failedStatus,
-+ postgresMetrics: systemHealthPostgresMetrics(failedStatus, failedStatus.lastChecked),
-+ };
++ targetPrefix,
+ });
}
}
-@@ -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,
++ 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 e9e366882..25aa84b34 100644
+index 25aa84b34..54e821f13 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",
- ],
- );
+@@ -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(
-+ health.postgresMetrics.rows.map((row) => row.metric),
++ expandedStorage.storageDiagnostics.map((row) => row.actionId),
+ [
-+ "Connection status",
-+ "Database name",
-+ "Current schema",
-+ "Migration status",
-+ "Last migration",
-+ "Table count",
-+ "Database size",
-+ "Last checked",
++ "storage-bucket-connectivity",
++ "storage-list",
++ "storage-upload-test-object",
++ "storage-read-test-object",
++ "storage-delete-test-object",
+ ],
+ );
- const healthText = JSON.stringify(health);
- assert.equal(healthText.includes("api-secret"), false);
- assert.equal(healthText.includes("site-secret"), false);
++ 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 e682005b2..99fef0757 100644
+index 99fef0757..9ea40988a 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();
+@@ -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 52d7acf50..56733ce6a 100644
+index 56733ce6a..713344ac0 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");
+@@ -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/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.txt
index 7b1c51f19..7211f2375 100644
--- a/docs_build/dev/reports/coverage_changed_js_guardrail.txt
+++ b/docs_build/dev/reports/coverage_changed_js_guardrail.txt
@@ -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.txt
index 627ed631f..2a2982e6a 100644
--- a/docs_build/dev/reports/playwright_v8_coverage_report.txt
+++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt
@@ -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");
});