From 5cf6a87fde76b9626edf34b90492cd8210fee1b1 Mon Sep 17 00:00:00 2001 From: Charlie Team <97194984+ToolboxAid@users.noreply.github.com> Date: Thu, 25 Jun 2026 11:30:44 -0400 Subject: [PATCH] Clean up startup runtime diagnostics --- ...RLIE_034-startup-runtime-report-cleanup.md | 35 ++ ...untime-report-cleanup_branch-validation.md | 14 + ...-report-cleanup_manual-validation-notes.md | 9 + ...e-report-cleanup_requirements-checklist.md | 14 + ...-runtime-report-cleanup_validation-lane.md | 14 + .../dev/reports/codex_changed_files.txt | 53 +- docs_build/dev/reports/codex_review.diff | 479 +++++++++++++++++- .../reports/playwright_v8_coverage_report.txt | 3 +- scripts/start-local-api-server.mjs | 34 +- src/dev-runtime/server/local-api-router.mjs | 101 +++- .../AdminHealthOperations.test.mjs | 7 + .../LocalApiStartupLogging.test.mjs | 32 ++ .../tools/AdminHealthOperationsPage.spec.mjs | 6 + 13 files changed, 772 insertions(+), 29 deletions(-) create mode 100644 docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup.md create mode 100644 docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_branch-validation.md create mode 100644 docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_manual-validation-notes.md create mode 100644 docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_requirements-checklist.md create mode 100644 docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_validation-lane.md diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup.md b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup.md new file mode 100644 index 000000000..a92990053 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup.md @@ -0,0 +1,35 @@ +# PR_26177_CHARLIE_034-startup-runtime-report-cleanup + +Team: Charlie +Branch: pr/26177-CHARLIE-034-startup-runtime-report-cleanup +Base: pr/26177-CHARLIE-032-runtime-health-json-endpoints +Lifecycle: Build / Validation +Repair: Rebased onto repaired PR_26177_CHARLIE_032 branch on 2026-06-25. + +## Scope +- Polished Local API startup diagnostics with explicit Local API URL, local site URL, local site URL port, database mode, and storage status. +- Kept runtime environment variable output deterministic and alphabetized. +- Confirmed secret-like environment keys containing PASSWORD, SECRET, TOKEN, KEY, SERVICE_ROLE, or JWT remain masked. +- Mirrored the safe diagnostics through the System Health API payload so the browser renders service-owned status. + +## Changed Files +- scripts/start-local-api-server.mjs +- src/dev-runtime/server/local-api-router.mjs +- tests/dev-runtime/LocalApiStartupLogging.test.mjs +- tests/dev-runtime/AdminHealthOperations.test.mjs +- tests/api/admin-system-health/contract.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 scripts/start-local-api-server.mjs +- PASS: node --check src/dev-runtime/server/local-api-router.mjs +- PASS: node --test tests/dev-runtime/LocalApiStartupLogging.test.mjs +- 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_034-startup-runtime-report-cleanup_delta.zip diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_branch-validation.md b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_branch-validation.md new file mode 100644 index 000000000..36fe52dfc --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_branch-validation.md @@ -0,0 +1,14 @@ +# PR_26177_CHARLIE_034 Branch Validation + +Branch: pr/26177-CHARLIE-034-startup-runtime-report-cleanup +Base: pr/26177-CHARLIE-032-runtime-health-json-endpoints + +## Results +- PASS: Continued the stacked Charlie workstream from PR_26177_CHARLIE_032. +- PASS: Branch is based on repaired PR_26177_CHARLIE_032 branch. +- PASS: Rebase conflict scope was generated report artifacts only. +- PASS: Worktree was clean before edits. +- PASS: Runtime changes are limited to Local API startup/runtime diagnostics. +- PASS: No start_of_day files were modified. +- PASS: No direct commit to main was made. +- PASS: PR branch will be pushed for draft PR creation. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_manual-validation-notes.md new file mode 100644 index 000000000..83600b226 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_manual-validation-notes.md @@ -0,0 +1,9 @@ +# PR_26177_CHARLIE_034 Manual Validation Notes + +- Confirmed startup log output now prints Local API URL, local site URL, local site URL port, database mode, and storage status. +- Confirmed `.env` diagnostic lines remain alphabetized by key. +- Confirmed secret-like values are masked, including KEY and SERVICE_ROLE variables. +- Confirmed System Health renders the new Local API startup diagnostics from the Local API payload. +- Confirmed no browser-owned infrastructure health state was introduced. +- 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_034-startup-runtime-report-cleanup_requirements-checklist.md b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_requirements-checklist.md new file mode 100644 index 000000000..f40562fc5 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_requirements-checklist.md @@ -0,0 +1,14 @@ +# PR_26177_CHARLIE_034 Requirement Checklist + +- PASS: Polish Local API startup/runtime diagnostics. +- PASS: Keep environment variables alphabetized. +- PASS: Mask secrets containing PASSWORD, SECRET, TOKEN, KEY, SERVICE_ROLE, or JWT. +- PASS: Include clear Local API URL. +- PASS: Include local site URL and port when available. +- PASS: Include database mode without exposing connection credentials. +- PASS: Include storage status without exposing secrets. +- PASS: Preserve existing startup and System Health behavior. +- PASS: Do not modify unrelated files. +- PASS: Do not modify start_of_day folders. +- PASS: Do not introduce MEM DB, fake-login, silent fallbacks, or browser-owned infrastructure state. +- PASS: Rebased onto repaired PR_26177_CHARLIE_032 branch. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_validation-lane.md b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_validation-lane.md new file mode 100644 index 000000000..a48f5434f --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_validation-lane.md @@ -0,0 +1,14 @@ +# PR_26177_CHARLIE_034 Validation Lane + +## Commands +- PASS: node --check scripts/start-local-api-server.mjs +- PASS: node --check src/dev-runtime/server/local-api-router.mjs +- PASS: node --test tests/dev-runtime/LocalApiStartupLogging.test.mjs +- 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 + +## Notes +- `git diff --check` reported only expected Windows LF-to-CRLF working-copy warnings. +- Full samples smoke was not run because this PR is scoped to Local API startup diagnostics and System Health rendering. diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index d23702138..0a2f5d928 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,33 +1,38 @@ -# git diff --name-only pr/26177-CHARLIE-031-environment-health-comparison -- -docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints.md -docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_branch-validation.md -docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_manual-validation-notes.md -docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_requirements-checklist.md -docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_validation-lane.md +# git diff --name-only pr/26177-CHARLIE-032-runtime-health-json-endpoints -- +docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup.md +docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_branch-validation.md +docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_manual-validation-notes.md +docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_requirements-checklist.md +docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_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 +scripts/start-local-api-server.mjs src/dev-runtime/server/local-api-router.mjs -tests/api/admin-system-health/contract.test.mjs tests/dev-runtime/AdminHealthOperations.test.mjs +tests/dev-runtime/LocalApiStartupLogging.test.mjs tests/playwright/tools/AdminHealthOperationsPage.spec.mjs # git status --short -(no output) + M docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup.md + M docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_branch-validation.md + M docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_manual-validation-notes.md + M docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_requirements-checklist.md + M docs_build/dev/reports/codex_review.diff + M docs_build/dev/reports/playwright_v8_coverage_report.txt -# git diff --stat pr/26177-CHARLIE-031-environment-health-comparison -- - ...77_CHARLIE_032-runtime-health-json-endpoints.md | 30 ++++++++++ - ...time-health-json-endpoints_branch-validation.md | 17 ++++++ - ...ealth-json-endpoints_manual-validation-notes.md | 8 +++ - ...health-json-endpoints_requirements-checklist.md | 13 +++++ - ...untime-health-json-endpoints_validation-lane.md | 20 +++++++ - docs_build/dev/reports/codex_changed_files.txt | 66 ++++++++-------------- - docs_build/dev/reports/codex_review.diff | 2 +- - .../dev/reports/coverage_changed_js_guardrail.txt | 1 - - .../dev/reports/playwright_v8_coverage_report.txt | 2 - - src/dev-runtime/server/local-api-router.mjs | 48 ++++++++++++++++ - tests/api/admin-system-health/contract.test.mjs | 9 +++ - tests/dev-runtime/AdminHealthOperations.test.mjs | 12 ++++ - .../tools/AdminHealthOperationsPage.spec.mjs | 2 + - 13 files changed, 185 insertions(+), 45 deletions(-) +# git diff --stat pr/26177-CHARLIE-032-runtime-health-json-endpoints -- + ...7_CHARLIE_034-startup-runtime-report-cleanup.md | 35 ++ + ...tup-runtime-report-cleanup_branch-validation.md | 14 + + ...ntime-report-cleanup_manual-validation-notes.md | 9 + + ...untime-report-cleanup_requirements-checklist.md | 14 + + ...artup-runtime-report-cleanup_validation-lane.md | 14 + + docs_build/dev/reports/codex_changed_files.txt | 59 ++- + docs_build/dev/reports/codex_review.diff | 479 ++++++++++++++++++++- + .../dev/reports/playwright_v8_coverage_report.txt | 3 +- + scripts/start-local-api-server.mjs | 34 +- + src/dev-runtime/server/local-api-router.mjs | 101 ++++- + tests/dev-runtime/AdminHealthOperations.test.mjs | 7 + + tests/dev-runtime/LocalApiStartupLogging.test.mjs | 32 ++ + .../tools/AdminHealthOperationsPage.spec.mjs | 6 + + 13 files changed, 771 insertions(+), 36 deletions(-) diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index d90693b0c..c9f49ac15 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -1 +1,478 @@ -diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints.md b/docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints.md new file mode 100644 index 000000000..7f1f8d884 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints.md @@ -0,0 +1,30 @@ +# PR_26177_CHARLIE_032-runtime-health-json-endpoints + +Team: Charlie +Branch: pr/26177-CHARLIE-032-runtime-health-json-endpoints +Base: pr/26177-CHARLIE-031-environment-health-comparison +Lifecycle: Build / Validation +Repair: Rebased onto repaired PR_26177_CHARLIE_031 branch on 2026-06-25. + +## Scope +- Added GET /api/runtime/health as a structured Local API JSON health endpoint. +- Endpoint includes environment, API status, database status, storage status, and timestamp. +- Endpoint is server-owned, does not expose secrets, and does not give the browser direct database ownership. + +## Changed Files +- 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 --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_032-runtime-health-json-endpoints_delta.zip diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_branch-validation.md b/docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_branch-validation.md new file mode 100644 index 000000000..ffee36713 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_branch-validation.md @@ -0,0 +1,17 @@ +# PR_26177_CHARLIE_032-runtime-health-json-endpoints Branch Validation + +Branch: pr/26177-CHARLIE-032-runtime-health-json-endpoints +Expected stack base: pr/26177-CHARLIE-031-environment-health-comparison +Current status at validation: +## pr/26177-CHARLIE-032-runtime-health-json-endpoints +Updated onto repaired PR_26177_CHARLIE_031 stack base. + +Result: PASS + +Checks: +- PASS: Branch created from PR 031 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 031 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_032-runtime-health-json-endpoints_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_manual-validation-notes.md new file mode 100644 index 000000000..ed6174f57 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_manual-validation-notes.md @@ -0,0 +1,8 @@ +# PR_26177_CHARLIE_032-runtime-health-json-endpoints Manual Validation Notes + +- Confirmed GET /api/runtime/health returns JSON through the Local API router. +- Confirmed payload includes environment, API, database, storage, and timestamp fields. +- Confirmed payload excludes configured API/site credentials and secret values. +- Confirmed System Health API Contract/Admin API Registry list the runtime health endpoint. +- 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_032-runtime-health-json-endpoints_requirements-checklist.md b/docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_requirements-checklist.md new file mode 100644 index 000000000..6a3f730a9 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_requirements-checklist.md @@ -0,0 +1,13 @@ +# PR_26177_CHARLIE_032-runtime-health-json-endpoints Requirement Checklist + +- PASS: Added structured Local API JSON health endpoint. +- PASS: Includes environment details. +- PASS: Includes API status. +- PASS: Includes DB status when available. +- PASS: Includes storage status when available. +- PASS: Includes timestamp. +- PASS: Does not expose secrets or URL credentials. +- PASS: Browser UI remains API/service-contract based. +- PASS: No direct browser database access added. +- PASS: No unrelated files or start_of_day files modified. +- PASS: Rebased onto repaired PR_26177_CHARLIE_031 branch. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_validation-lane.md b/docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_validation-lane.md new file mode 100644 index 000000000..0aa4db67f --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_032-runtime-health-json-endpoints_validation-lane.md @@ -0,0 +1,20 @@ +# PR_26177_CHARLIE_032-runtime-health-json-endpoints Validation Lane Report + +Impacted lanes: +- runtime: Local API route contract. +- contract: Admin System Health API contract tests. +- UI-adjacent: Admin System Health API registry display. +- Playwright: targeted Admin System Health page spec. + +Commands: +- PASS: node --check src/dev-runtime/server/local-api-router.mjs +- 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; no samples/runtime game code changed. +- Full Project Workspace suite skipped; endpoint and Admin registry were covered by targeted tests. + +Result: PASS diff --git a/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.txt index fe02b5ecf..74a29674c 100644 --- a/docs_build/dev/reports/coverage_changed_js_guardrail.txt +++ b/docs_build/dev/reports/coverage_changed_js_guardrail.txt @@ -7,7 +7,6 @@ Source: Playwright/Chromium built-in V8 coverage from the active Playwright run. Changed runtime JS files considered: (0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(80%) assets/theme-v2/js/admin-system-health.js - executed lines 920/920; executed functions 74/92 Guardrail warnings: (0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file missing from coverage; advisory only diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.txt index c6e25e167..e539a3bb1 100644 --- a/docs_build/dev/reports/playwright_v8_coverage_report.txt +++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt @@ -18,7 +18,6 @@ Exercised tool entry points detected: Changed runtime JS files covered: (0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(80%) assets/theme-v2/js/admin-system-health.js - executed lines 920/920; executed functions 74/92 Files with executed line/function counts where available: (36%) src/api/server-api-client.js - executed lines 168/168; executed functions 5/14 @@ -41,4 +40,3 @@ Changed JS files considered: (0%) tests/api/admin-system-health/contract.test.mjs - changed JS file not collected as browser runtime coverage (0%) tests/dev-runtime/AdminHealthOperations.test.mjs - changed JS file not collected as browser runtime coverage (0%) tests/playwright/tools/AdminHealthOperationsPage.spec.mjs - changed JS file not collected as browser runtime coverage -(80%) assets/theme-v2/js/admin-system-health.js - changed JS file with browser V8 coverage diff --git a/src/dev-runtime/server/local-api-router.mjs b/src/dev-runtime/server/local-api-router.mjs index 01702c505..9ffc20905 100644 --- a/src/dev-runtime/server/local-api-router.mjs +++ b/src/dev-runtime/server/local-api-router.mjs @@ -365,11 +365,13 @@ const SYSTEM_HEALTH_MANUAL_ACTION_LABELS = Object.freeze({ "storage-check": "Run Storage Check", }); const SYSTEM_HEALTH_API_ENDPOINTS = Object.freeze([ + Object.freeze({ method: "GET", path: "/api/runtime/health", purpose: "Read public-safe Local API runtime health JSON." }), Object.freeze({ method: "GET", path: "/api/admin/system-health/status", purpose: "Read current deployment System Health status." }), Object.freeze({ method: "POST", path: "/api/admin/system-health/action", purpose: "Run current deployment manual health actions." }), Object.freeze({ method: "POST", path: "/api/admin/system-health/storage-connectivity-action", purpose: "Run current deployment R2 folder diagnostics." }), ]); const ADMIN_API_REGISTRY_ENTRIES = Object.freeze([ + Object.freeze({ method: "GET", owner: "Team Charlie", path: "/api/runtime/health", purpose: "Runtime health JSON contract" }), Object.freeze({ method: "GET", owner: "Team Charlie", path: "/api/admin/system-health/status", purpose: "System Health status contract" }), Object.freeze({ method: "POST", owner: "Team Charlie", path: "/api/admin/system-health/action", purpose: "System Health manual actions" }), Object.freeze({ method: "POST", owner: "Team Charlie", path: "/api/admin/system-health/storage-connectivity-action", purpose: "System Health R2 diagnostics" }), @@ -4865,6 +4867,48 @@ SELECT pg_database_size(current_database()) AS database_size_bytes, }; } + async runtimeHealthJsonStatus() { + const checkedAt = new Date().toISOString(); + const environmentIdentity = systemHealthEnvironmentIdentity(process.env, checkedAt); + const runtimeHealth = systemHealthRuntimeHealth(environmentIdentity, checkedAt); + const databaseStatus = await this.ownerDatabaseStatus(environmentIdentity); + const storageStatus = this.ownerStorageStatus(); + return { + api: { + status: runtimeHealth.status || "WARN", + version: runtimeHealth.apiVersion || "not available", + }, + database: { + connectivity: databaseStatus.connectivity || "not configured", + databaseType: databaseStatus.databaseType || environmentIdentity.databaseModel || "PostgreSQL", + lastChecked: databaseStatus.lastChecked || checkedAt, + responseTimeMs: Number.isFinite(databaseStatus.responseTimeMs) ? databaseStatus.responseTimeMs : null, + status: databaseStatus.connectivityStatus || databaseStatus.status || "WARN", + version: databaseStatus.version || "not available", + }, + environment: { + hostingModel: environmentIdentity.hostingModel || "not configured", + name: environmentIdentity.name || "Unknown", + storageFolder: environmentIdentity.storageFolder || "not configured", + }, + message: "Runtime health JSON is server-owned and safe for Local API status consumers.", + secretEditingAllowed: false, + secretsExposed: false, + status: overallHealthStatus([ + { status: runtimeHealth.status || "WARN" }, + { status: databaseStatus.connectivityStatus || databaseStatus.status || "WARN" }, + { status: storageStatus.status || "WARN" }, + ]), + storage: { + configured: storageStatus.configured === true, + environmentFolder: storageStatus.environmentFolder || environmentIdentity.storageFolder || "not configured", + lastChecked: storageStatus.lastChecked || checkedAt, + status: storageStatus.status || "WARN", + }, + timestamp: checkedAt, + }; + } + projectWorkspaceProjectsForRoute() { const activeProject = this.gameWorkspaceRepository.getActiveGame(); const records = gameWorkspaceProjectRecords(this.gameWorkspaceRepository); @@ -6725,6 +6769,10 @@ export function createLocalApiRouter({ if (request.method === "GET" && await handleAdminNotesDirectoryApiRequest(requestUrl, response, { repoRoot })) { return true; } + if (parts[1] === "runtime" && request.method === "GET" && parts[2] === "health") { + ok(response, await dataSource.runtimeHealthJsonStatus()); + return true; + } if (parts[1] === "session") { if (request.method === "HEAD" && ["current", "modes", "users"].includes(parts[2])) { sendNoContent(response, 200); diff --git a/tests/api/admin-system-health/contract.test.mjs b/tests/api/admin-system-health/contract.test.mjs index 9d54111f2..c82496a7b 100644 --- a/tests/api/admin-system-health/contract.test.mjs +++ b/tests/api/admin-system-health/contract.test.mjs @@ -93,6 +93,15 @@ test("Admin System Health completion contract remains server-owned and current-e }, async () => { const server = await startApiServer(); try { + const runtimeJson = await apiJson(server.baseUrl, "/api/runtime/health"); + assert.equal(runtimeJson.environment.name, "DEV"); + assert.equal(runtimeJson.environment.storageFolder, "/dev"); + assert.equal(runtimeJson.api.status, "PASS"); + assert.equal(Object.hasOwn(runtimeJson, "timestamp"), true); + assert.equal(runtimeJson.secretEditingAllowed, false); + assert.equal(runtimeJson.secretsExposed, false); + assert.equal(JSON.stringify(runtimeJson).includes("api-secret"), false); + assert.equal(JSON.stringify(runtimeJson).includes("site-secret"), false); await apiJson(server.baseUrl, "/api/session/user", { body: { userKey: SEED_DB_KEYS.users.admin }, method: "POST", diff --git a/tests/dev-runtime/AdminHealthOperations.test.mjs b/tests/dev-runtime/AdminHealthOperations.test.mjs index 0c413cfda..8d37ad5c3 100644 --- a/tests/dev-runtime/AdminHealthOperations.test.mjs +++ b/tests/dev-runtime/AdminHealthOperations.test.mjs @@ -242,6 +242,7 @@ test("Admin can view operational health while Creator sessions are blocked", asy assert.deepEqual( health.apiContract.endpoints.map((endpoint) => `${endpoint.method} ${endpoint.path}`), [ + "GET /api/runtime/health", "GET /api/admin/system-health/status", "POST /api/admin/system-health/action", "POST /api/admin/system-health/storage-connectivity-action", @@ -250,6 +251,7 @@ test("Admin can view operational health while Creator sessions are blocked", asy assert.deepEqual( health.adminApiRegistry.rows.map((row) => `${row.method} ${row.path}`), [ + "GET /api/runtime/health", "GET /api/admin/system-health/status", "POST /api/admin/system-health/action", "POST /api/admin/system-health/storage-connectivity-action", @@ -260,6 +262,16 @@ test("Admin can view operational health while Creator sessions are blocked", asy "GET /api/navigation/admin-menu", ], ); + const runtimeJson = await apiJson(server.baseUrl, "/api/runtime/health"); + assert.equal(runtimeJson.environment.name, "Local"); + assert.equal(runtimeJson.api.status, "PASS"); + assert.ok(["PASS", "WARN", "FAIL"].includes(runtimeJson.database.status)); + assert.ok(["PASS", "WARN", "FAIL"].includes(runtimeJson.storage.status)); + assert.equal(typeof runtimeJson.timestamp, "string"); + assert.equal(runtimeJson.secretEditingAllowed, false); + assert.equal(runtimeJson.secretsExposed, false); + assert.equal(JSON.stringify(runtimeJson).includes("api-secret"), false); + assert.equal(JSON.stringify(runtimeJson).includes("site-secret"), false); assert.deepEqual( health.runtimeFeatureFlags.rows.map((row) => `${row.flag}:${row.value}`), [ diff --git a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs index f7f2cb66c..45d704c3e 100644 --- a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +++ b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs @@ -166,8 +166,10 @@ test("Admin System Health renders Postgres diagnostics through the safe status A await expect(apiContractTable).toContainText("2026-06-24.system-health.v1"); await expect(apiContractTable).toContainText("Current deployment only"); await expect(apiContractTable).toContainText("Reference only"); + await expect(apiContractTable).toContainText("GET /api/runtime/health"); await expect(apiContractTable).toContainText("GET /api/admin/system-health/status"); const apiRegistryTable = page.getByRole("table", { name: "Admin API registry" }); + await expect(apiRegistryTable).toContainText("/api/runtime/health"); await expect(apiRegistryTable).toContainText("/api/admin/system-health/status"); await expect(apiRegistryTable).toContainText("/api/admin/system-health/action"); await expect(apiRegistryTable).toContainText("/api/admin/infrastructure/storage-path-status"); +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup.md b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup.md +new file mode 100644 +index 000000000..a92990053 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup.md +@@ -0,0 +1,35 @@ ++# PR_26177_CHARLIE_034-startup-runtime-report-cleanup ++ ++Team: Charlie ++Branch: pr/26177-CHARLIE-034-startup-runtime-report-cleanup ++Base: pr/26177-CHARLIE-032-runtime-health-json-endpoints ++Lifecycle: Build / Validation ++Repair: Rebased onto repaired PR_26177_CHARLIE_032 branch on 2026-06-25. ++ ++## Scope ++- Polished Local API startup diagnostics with explicit Local API URL, local site URL, local site URL port, database mode, and storage status. ++- Kept runtime environment variable output deterministic and alphabetized. ++- Confirmed secret-like environment keys containing PASSWORD, SECRET, TOKEN, KEY, SERVICE_ROLE, or JWT remain masked. ++- Mirrored the safe diagnostics through the System Health API payload so the browser renders service-owned status. ++ ++## Changed Files ++- scripts/start-local-api-server.mjs ++- src/dev-runtime/server/local-api-router.mjs ++- tests/dev-runtime/LocalApiStartupLogging.test.mjs ++- tests/dev-runtime/AdminHealthOperations.test.mjs ++- tests/api/admin-system-health/contract.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 scripts/start-local-api-server.mjs ++- PASS: node --check src/dev-runtime/server/local-api-router.mjs ++- PASS: node --test tests/dev-runtime/LocalApiStartupLogging.test.mjs ++- 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_034-startup-runtime-report-cleanup_delta.zip +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_branch-validation.md b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_branch-validation.md +new file mode 100644 +index 000000000..36fe52dfc +--- /dev/null ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_branch-validation.md +@@ -0,0 +1,14 @@ ++# PR_26177_CHARLIE_034 Branch Validation ++ ++Branch: pr/26177-CHARLIE-034-startup-runtime-report-cleanup ++Base: pr/26177-CHARLIE-032-runtime-health-json-endpoints ++ ++## Results ++- PASS: Continued the stacked Charlie workstream from PR_26177_CHARLIE_032. ++- PASS: Branch is based on repaired PR_26177_CHARLIE_032 branch. ++- PASS: Rebase conflict scope was generated report artifacts only. ++- PASS: Worktree was clean before edits. ++- PASS: Runtime changes are limited to Local API startup/runtime diagnostics. ++- PASS: No start_of_day files were modified. ++- PASS: No direct commit to main was made. ++- PASS: PR branch will be pushed for draft PR creation. +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_manual-validation-notes.md +new file mode 100644 +index 000000000..83600b226 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_manual-validation-notes.md +@@ -0,0 +1,9 @@ ++# PR_26177_CHARLIE_034 Manual Validation Notes ++ ++- Confirmed startup log output now prints Local API URL, local site URL, local site URL port, database mode, and storage status. ++- Confirmed `.env` diagnostic lines remain alphabetized by key. ++- Confirmed secret-like values are masked, including KEY and SERVICE_ROLE variables. ++- Confirmed System Health renders the new Local API startup diagnostics from the Local API payload. ++- Confirmed no browser-owned infrastructure health state was introduced. ++- 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_034-startup-runtime-report-cleanup_requirements-checklist.md b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_requirements-checklist.md +new file mode 100644 +index 000000000..f40562fc5 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_requirements-checklist.md +@@ -0,0 +1,14 @@ ++# PR_26177_CHARLIE_034 Requirement Checklist ++ ++- PASS: Polish Local API startup/runtime diagnostics. ++- PASS: Keep environment variables alphabetized. ++- PASS: Mask secrets containing PASSWORD, SECRET, TOKEN, KEY, SERVICE_ROLE, or JWT. ++- PASS: Include clear Local API URL. ++- PASS: Include local site URL and port when available. ++- PASS: Include database mode without exposing connection credentials. ++- PASS: Include storage status without exposing secrets. ++- PASS: Preserve existing startup and System Health behavior. ++- PASS: Do not modify unrelated files. ++- PASS: Do not modify start_of_day folders. ++- PASS: Do not introduce MEM DB, fake-login, silent fallbacks, or browser-owned infrastructure state. ++- PASS: Rebased onto repaired PR_26177_CHARLIE_032 branch. +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_validation-lane.md b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_validation-lane.md +new file mode 100644 +index 000000000..a48f5434f +--- /dev/null ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_034-startup-runtime-report-cleanup_validation-lane.md +@@ -0,0 +1,14 @@ ++# PR_26177_CHARLIE_034 Validation Lane ++ ++## Commands ++- PASS: node --check scripts/start-local-api-server.mjs ++- PASS: node --check src/dev-runtime/server/local-api-router.mjs ++- PASS: node --test tests/dev-runtime/LocalApiStartupLogging.test.mjs ++- 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 ++ ++## Notes ++- `git diff --check` reported only expected Windows LF-to-CRLF working-copy warnings. ++- Full samples smoke was not run because this PR is scoped to Local API startup diagnostics and System Health rendering. +diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.txt +index e539a3bb1..e00dbc9b0 100644 +--- a/docs_build/dev/reports/playwright_v8_coverage_report.txt ++++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt +@@ -36,7 +36,8 @@ Uncovered or low-coverage changed JS files: + (0%) src/dev-runtime/server/local-api-router.mjs - WARNING: uncovered changed runtime JS file; advisory only + + Changed JS files considered: ++(0%) scripts/start-local-api-server.mjs - changed JS file not collected as browser runtime coverage + (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/dev-runtime/LocalApiStartupLogging.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 +diff --git a/scripts/start-local-api-server.mjs b/scripts/start-local-api-server.mjs +index d9bde5d76..3a4d5ef3e 100644 +--- a/scripts/start-local-api-server.mjs ++++ b/scripts/start-local-api-server.mjs +@@ -4,6 +4,7 @@ import path from "node:path"; + import { pathToFileURL } from "node:url"; + import { databaseSslMode } from "../src/dev-runtime/persistence/postgres-connection-client.mjs"; + import { startLocalApiServer } from "../src/dev-runtime/server/local-api-server.mjs"; ++import { loadStorageConfig } from "../src/dev-runtime/storage/storage-config.mjs"; + + const RUNTIME_ENV_FILE = ".env"; + const NOT_CONFIGURED = "(not configured)"; +@@ -181,6 +182,28 @@ function formatRuntimePortLogLines({ env, localServer }) { + ]; + } + ++function databaseMode(env) { ++ const rawValue = String(env.GAMEFOUNDRY_DATABASE_URL || "").trim(); ++ if (!rawValue) { ++ return "not configured"; ++ } ++ try { ++ const parsedUrl = new URL(rawValue); ++ return ["postgres:", "postgresql:"].includes(parsedUrl.protocol) ? "Postgres" : "invalid database URL"; ++ } catch { ++ return "invalid database URL"; ++ } ++} ++ ++function storageStatus(env) { ++ const storageConfig = loadStorageConfig(env); ++ if (storageConfig.configured) { ++ return `configured (bucket ${storageConfig.safe.bucket}; prefix ${storageConfig.safe.projectsPrefix})`; ++ } ++ const issue = storageConfig.validationError || `missing ${storageConfig.missingKeys?.join(", ") || "storage configuration"}`; ++ return `not configured (${issue})`; ++} ++ + export function formatStartupLogLines({ + accountConnection, + configuredDatabaseSslMode, +@@ -190,14 +213,21 @@ export function formatStartupLogLines({ + localServer, + runtimeEnv, + }) { ++ const configuredApiUrl = String(env.GAMEFOUNDRY_API_URL || "").trim() || defaultApiUrl(localServer.baseUrl); ++ const configuredSiteUrl = configuredValue(env.GAMEFOUNDRY_SITE_URL); + return [ + `GameFoundry API runtime server running at ${localServer.baseUrl}`, +- `Configured site URL: ${configuredValue(env.GAMEFOUNDRY_SITE_URL)}`, +- `Configured API URL: ${String(env.GAMEFOUNDRY_API_URL || "").trim() || defaultApiUrl(localServer.baseUrl)}`, ++ `Configured site URL: ${configuredSiteUrl}`, ++ `Configured API URL: ${configuredApiUrl}`, ++ `Local API URL: ${configuredApiUrl}`, ++ `Local site URL: ${configuredSiteUrl}`, ++ `Local site URL port: ${portFromUrl(env.GAMEFOUNDRY_SITE_URL)}`, + ...formatEnvironmentVariableLogLines(runtimeEnv), + ...formatRuntimePortLogLines({ env, localServer }), + connectionStatusLine("auth", accountConnection), + connectionStatusLine("database", databaseConnection), ++ `Database mode: ${databaseMode(env)}`, ++ `Storage status: ${storageStatus(env)}`, + `Database SSL mode: ${configuredDatabaseSslMode || `invalid (${databaseSslModeError})`}`, + "Press Ctrl+C to stop.", + ]; +diff --git a/src/dev-runtime/server/local-api-router.mjs b/src/dev-runtime/server/local-api-router.mjs +index 9ffc20905..f2f927aff 100644 +--- a/src/dev-runtime/server/local-api-router.mjs ++++ b/src/dev-runtime/server/local-api-router.mjs +@@ -904,6 +904,13 @@ function localApiStartupUrlDisplay(value, fallback = "not configured") { + } + } + ++function localApiStartupPortStatus(port) { ++ if (port === "invalid URL") { ++ return "FAIL"; ++ } ++ return port === "not configured" ? "WARN" : "PASS"; ++} ++ + function localApiStartupBindTarget(env = process.env) { + const host = String(env.GAMEFOUNDRY_LOCAL_API_HOST || LOCAL_API_STARTUP_DEFAULT_HOST).trim() || LOCAL_API_STARTUP_DEFAULT_HOST; + const port = String(env.GAMEFOUNDRY_LOCAL_API_PORT || LOCAL_API_STARTUP_DEFAULT_PORT).trim() || LOCAL_API_STARTUP_DEFAULT_PORT; +@@ -916,11 +923,57 @@ function localApiStartupBindTarget(env = process.env) { + }; + } + ++function localApiStartupDatabaseMode(env = process.env) { ++ const databaseStatus = databaseConfigStatus(env); ++ if (databaseStatus.hostStatus === "FAIL" || databaseStatus.databaseNameStatus === "FAIL") { ++ return { ++ reason: "GAMEFOUNDRY_DATABASE_URL is present but is not a valid Postgres URL.", ++ status: "FAIL", ++ value: "invalid database URL", ++ }; ++ } ++ if (!databaseStatus.configured) { ++ return { ++ reason: "GAMEFOUNDRY_DATABASE_URL is not configured for the Local API startup report.", ++ status: "WARN", ++ value: "not configured", ++ }; ++ } ++ return { ++ reason: "GAMEFOUNDRY_DATABASE_URL is configured with a Postgres protocol; credentials remain hidden.", ++ status: "PASS", ++ value: "Postgres", ++ }; ++} ++ ++function localApiStartupStorageStatus(env = process.env) { ++ const storageConfig = loadStorageConfig(env); ++ if (storageConfig.configured) { ++ return { ++ reason: `Cloudflare R2 configuration is present for bucket ${storageConfig.safe.bucket} and prefix ${storageConfig.safe.projectsPrefix}; credential values remain hidden.`, ++ status: "PASS", ++ value: "configured", ++ }; ++ } ++ const issue = storageConfig.validationError ++ || `missing ${storageConfig.missingKeys?.join(", ") || "storage configuration"}`; ++ return { ++ reason: `Cloudflare R2 storage is not fully configured: ${issue}.`, ++ status: "WARN", ++ value: "not configured", ++ }; ++} ++ + function systemHealthLocalApiStartupDiagnostics(env = process.env) { + const bindTarget = localApiStartupBindTarget(env); + const configuredApiUrl = String(env.GAMEFOUNDRY_API_URL || "").trim(); + const derivedApiUrl = `http://${bindTarget.value}/api`; + const siteUrl = String(env.GAMEFOUNDRY_SITE_URL || "").trim(); ++ const apiUrlDisplay = localApiStartupUrlDisplay(configuredApiUrl || derivedApiUrl); ++ const siteUrlDisplay = localApiStartupUrlDisplay(siteUrl); ++ const siteUrlPort = localApiStartupPortFromUrl(siteUrl); ++ const databaseMode = localApiStartupDatabaseMode(env); ++ const storageStatus = localApiStartupStorageStatus(env); + const rows = [ + { + field: "Approved diagnostics format", +@@ -934,6 +987,18 @@ function systemHealthLocalApiStartupDiagnostics(env = process.env) { + status: "PASS", + value: "masked and redacted", + }, ++ { ++ field: "Environment variable order", ++ reason: "Runtime .env keys are sorted alphabetically before startup diagnostics are printed.", ++ status: "PASS", ++ value: "alphabetical", ++ }, ++ { ++ field: "Secret masking markers", ++ reason: "Startup diagnostics mask variables whose keys contain PASSWORD, SECRET, TOKEN, KEY, SERVICE_ROLE, or JWT.", ++ status: "PASS", ++ value: "PASSWORD, SECRET, TOKEN, KEY, SERVICE_ROLE, JWT", ++ }, + { + field: "Configured startup bind target", + reason: bindTarget.status === "PASS" +@@ -942,6 +1007,28 @@ function systemHealthLocalApiStartupDiagnostics(env = process.env) { + status: bindTarget.status, + value: bindTarget.value, + }, ++ { ++ field: "Local API URL", ++ reason: configuredApiUrl ++ ? "GAMEFOUNDRY_API_URL is configured and displayed without URL credentials." ++ : "GAMEFOUNDRY_API_URL is not configured; startup diagnostics derive /api from the bind target.", ++ status: apiUrlDisplay === "invalid URL" ? "FAIL" : "PASS", ++ value: apiUrlDisplay, ++ }, ++ { ++ field: "Local site URL", ++ reason: siteUrl ++ ? "GAMEFOUNDRY_SITE_URL is available for startup diagnostics." ++ : "GAMEFOUNDRY_SITE_URL is not configured for the Local API startup report.", ++ status: siteUrl ? (siteUrlDisplay === "invalid URL" ? "FAIL" : "PASS") : "WARN", ++ value: siteUrlDisplay, ++ }, ++ { ++ field: "Local site URL port", ++ reason: "Port is derived from GAMEFOUNDRY_SITE_URL for display only.", ++ status: localApiStartupPortStatus(siteUrlPort), ++ value: siteUrlPort, ++ }, + { + field: "Configured site URL", + reason: siteUrl +@@ -956,7 +1043,7 @@ function systemHealthLocalApiStartupDiagnostics(env = process.env) { + ? "GAMEFOUNDRY_API_URL is configured and displayed without URL credentials." + : "GAMEFOUNDRY_API_URL is not configured; startup diagnostics derive /api from the bind target.", + status: "PASS", +- value: localApiStartupUrlDisplay(configuredApiUrl || derivedApiUrl), ++ value: apiUrlDisplay, + }, + { + field: "Configured API URL port", +@@ -964,6 +1051,18 @@ function systemHealthLocalApiStartupDiagnostics(env = process.env) { + status: "PASS", + value: localApiStartupPortFromUrl(configuredApiUrl || derivedApiUrl), + }, ++ { ++ field: "Database mode", ++ reason: databaseMode.reason, ++ status: databaseMode.status, ++ value: databaseMode.value, ++ }, ++ { ++ field: "Storage status", ++ reason: storageStatus.reason, ++ status: storageStatus.status, ++ value: storageStatus.value, ++ }, + { + field: "Configurable multiple runtime ports", + reason: "Configurable multiple runtime ports are explicitly deferred/cancelled for this PR.", +diff --git a/tests/dev-runtime/AdminHealthOperations.test.mjs b/tests/dev-runtime/AdminHealthOperations.test.mjs +index 8d37ad5c3..59bea182c 100644 +--- a/tests/dev-runtime/AdminHealthOperations.test.mjs ++++ b/tests/dev-runtime/AdminHealthOperations.test.mjs +@@ -299,6 +299,13 @@ test("Admin can view operational health while Creator sessions are blocked", asy + health.localApiStartup.rows.some((row) => row.field === "Configurable multiple runtime ports" && row.status === "PENDING" && row.value === "deferred/cancelled"), + true, + ); ++ const startupRows = new Map(health.localApiStartup.rows.map((row) => [row.field, row])); ++ assert.equal(startupRows.get("Environment variable order")?.value, "alphabetical"); ++ assert.equal(startupRows.get("Secret masking markers")?.value, "PASSWORD, SECRET, TOKEN, KEY, SERVICE_ROLE, JWT"); ++ assert.equal(startupRows.get("Local API URL")?.status, "PASS"); ++ assert.equal(startupRows.get("Local site URL port")?.value, "5500"); ++ assert.ok(["Postgres", "not configured", "invalid database URL"].includes(startupRows.get("Database mode")?.value)); ++ assert.equal(startupRows.get("Storage status")?.value, "not configured"); + const startupText = JSON.stringify(health.localApiStartup); + assert.equal(startupText.includes("api-user"), false); + assert.equal(startupText.includes("api-secret"), false); +diff --git a/tests/dev-runtime/LocalApiStartupLogging.test.mjs b/tests/dev-runtime/LocalApiStartupLogging.test.mjs +index 5cafc2f0a..5362b40d2 100644 +--- a/tests/dev-runtime/LocalApiStartupLogging.test.mjs ++++ b/tests/dev-runtime/LocalApiStartupLogging.test.mjs +@@ -17,7 +17,10 @@ test("local API startup log separates bind URL from configured public URLs", () + GAMEFOUNDRY_API_URL: "http://127.0.0.1:5501/api", + GAMEFOUNDRY_DATABASE_URL: "postgres://secret-user:secret-pass@example.invalid/db", + GAMEFOUNDRY_SITE_URL: "http://127.0.0.1:5500", ++ GAMEFOUNDRY_STORAGE_ACCESS_KEY_ID: "storage-access-key", ++ GAMEFOUNDRY_STORAGE_BUCKET: "gamefoundry-test-assets", + GAMEFOUNDRY_STORAGE_ENDPOINT: "http://127.0.0.1:9000", ++ GAMEFOUNDRY_STORAGE_PROJECTS_PREFIX: "/dev/projects/", + GAMEFOUNDRY_STORAGE_SECRET_ACCESS_KEY: "storage-secret", + GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY: "service-role-secret", + GAMEFOUNDRY_SUPABASE_URL: "https://example.supabase.co", +@@ -44,11 +47,26 @@ test("local API startup log separates bind URL from configured public URLs", () + key: "GAMEFOUNDRY_DATABASE_URL", + value: "postgres://secret-user:secret-pass@example.invalid/db", + }, ++ { ++ applied: false, ++ key: "GAMEFOUNDRY_STORAGE_ACCESS_KEY_ID", ++ value: "storage-access-key", ++ }, ++ { ++ applied: true, ++ key: "GAMEFOUNDRY_STORAGE_BUCKET", ++ value: "gamefoundry-test-assets", ++ }, + { + applied: true, + key: "GAMEFOUNDRY_STORAGE_ENDPOINT", + value: "http://127.0.0.1:9000", + }, ++ { ++ applied: true, ++ key: "GAMEFOUNDRY_STORAGE_PROJECTS_PREFIX", ++ value: "/dev/projects/", ++ }, + { + applied: false, + key: "GAMEFOUNDRY_STORAGE_SECRET_ACCESS_KEY", +@@ -62,11 +80,17 @@ test("local API startup log separates bind URL from configured public URLs", () + "GameFoundry API runtime server running at http://127.0.0.1:5501", + "Configured site URL: http://127.0.0.1:5500", + "Configured API URL: http://127.0.0.1:5501/api", ++ "Local API URL: http://127.0.0.1:5501/api", ++ "Local site URL: http://127.0.0.1:5500", ++ "Local site URL port: 5500", + "=========================================", + "Environment Variables", + "=========================================", + "+ GAMEFOUNDRY_DATABASE_URL=postgres://********:********@example.invalid/db", ++ "- GAMEFOUNDRY_STORAGE_ACCESS_KEY_ID=********", ++ "+ GAMEFOUNDRY_STORAGE_BUCKET=gamefoundry-test-assets", + "+ GAMEFOUNDRY_STORAGE_ENDPOINT=http://127.0.0.1:9000", ++ "+ GAMEFOUNDRY_STORAGE_PROJECTS_PREFIX=/dev/projects/", + "- GAMEFOUNDRY_STORAGE_SECRET_ACCESS_KEY=********", + "- GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY=********", + "+ GAMEFOUNDRY_SUPABASE_URL=https://example.supabase.co", +@@ -81,6 +105,8 @@ test("local API startup log separates bind URL from configured public URLs", () + "Storage service port: 9000", + "Configured auth connection: configured.", + "Configured database connection: configured.", ++ "Database mode: Postgres", ++ "Storage status: configured (bucket gamefoundry-test-assets; prefix /dev/projects/)", + "Database SSL mode: require", + "Press Ctrl+C to stop.", + ]); +@@ -88,6 +114,7 @@ test("local API startup log separates bind URL from configured public URLs", () + assert.equal(lines.join("\n").includes(".env loaded for API runtime"), false); + assert.equal(lines.join("\n").includes("secret-user"), false); + assert.equal(lines.join("\n").includes("secret-pass"), false); ++ assert.equal(lines.join("\n").includes("storage-access-key"), false); + assert.equal(lines.join("\n").includes("storage-secret"), false); + assert.equal(lines.join("\n").includes("service-role-secret"), false); + }); +@@ -119,6 +146,9 @@ test("local API startup log shows missing site URL and derives API URL from bind + "GameFoundry API runtime server running at http://127.0.0.1:5599", + "Configured site URL: (not configured)", + "Configured API URL: http://127.0.0.1:5599/api", ++ "Local API URL: http://127.0.0.1:5599/api", ++ "Local site URL: (not configured)", ++ "Local site URL port: not configured", + ".env was not found for API runtime.", + "=========================================", + "All Runtime Ports being used by Service", +@@ -131,6 +161,8 @@ test("local API startup log shows missing site URL and derives API URL from bind + "Storage service port: not configured", + "Configured auth connection: missing GAMEFOUNDRY_SUPABASE_URL.", + "Configured database connection: missing GAMEFOUNDRY_DATABASE_URL.", ++ "Database mode: not configured", ++ "Storage status: not configured (missing GAMEFOUNDRY_STORAGE_ENDPOINT, GAMEFOUNDRY_STORAGE_ACCESS_KEY_ID, GAMEFOUNDRY_STORAGE_SECRET_ACCESS_KEY, GAMEFOUNDRY_STORAGE_BUCKET, GAMEFOUNDRY_STORAGE_PROJECTS_PREFIX)", + "Database SSL mode: invalid (GAMEFOUNDRY_DATABASE_SSL is missing.)", + "Press Ctrl+C to stop.", + ]); +diff --git a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +index 45d704c3e..cf54c49d3 100644 +--- a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs ++++ b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +@@ -218,6 +218,12 @@ test("Admin System Health renders Postgres diagnostics through the safe status A + await expect(notificationsTable).toContainText("Not Configured"); + await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Approved diagnostics format"); + await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Environment Variables + All Runtime Ports"); ++ await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Environment variable order"); ++ await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Secret masking markers"); ++ await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Local API URL"); ++ await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Local site URL port"); ++ await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Database mode"); ++ await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Storage status"); + await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Configurable multiple runtime ports"); + await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("deferred/cancelled"); + await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).not.toContainText("secret"); diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.txt index e539a3bb1..e00dbc9b0 100644 --- a/docs_build/dev/reports/playwright_v8_coverage_report.txt +++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt @@ -36,7 +36,8 @@ Uncovered or low-coverage changed JS files: (0%) src/dev-runtime/server/local-api-router.mjs - WARNING: uncovered changed runtime JS file; advisory only Changed JS files considered: +(0%) scripts/start-local-api-server.mjs - changed JS file not collected as browser runtime coverage (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/dev-runtime/LocalApiStartupLogging.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 diff --git a/scripts/start-local-api-server.mjs b/scripts/start-local-api-server.mjs index d9bde5d76..3a4d5ef3e 100644 --- a/scripts/start-local-api-server.mjs +++ b/scripts/start-local-api-server.mjs @@ -4,6 +4,7 @@ import path from "node:path"; import { pathToFileURL } from "node:url"; import { databaseSslMode } from "../src/dev-runtime/persistence/postgres-connection-client.mjs"; import { startLocalApiServer } from "../src/dev-runtime/server/local-api-server.mjs"; +import { loadStorageConfig } from "../src/dev-runtime/storage/storage-config.mjs"; const RUNTIME_ENV_FILE = ".env"; const NOT_CONFIGURED = "(not configured)"; @@ -181,6 +182,28 @@ function formatRuntimePortLogLines({ env, localServer }) { ]; } +function databaseMode(env) { + const rawValue = String(env.GAMEFOUNDRY_DATABASE_URL || "").trim(); + if (!rawValue) { + return "not configured"; + } + try { + const parsedUrl = new URL(rawValue); + return ["postgres:", "postgresql:"].includes(parsedUrl.protocol) ? "Postgres" : "invalid database URL"; + } catch { + return "invalid database URL"; + } +} + +function storageStatus(env) { + const storageConfig = loadStorageConfig(env); + if (storageConfig.configured) { + return `configured (bucket ${storageConfig.safe.bucket}; prefix ${storageConfig.safe.projectsPrefix})`; + } + const issue = storageConfig.validationError || `missing ${storageConfig.missingKeys?.join(", ") || "storage configuration"}`; + return `not configured (${issue})`; +} + export function formatStartupLogLines({ accountConnection, configuredDatabaseSslMode, @@ -190,14 +213,21 @@ export function formatStartupLogLines({ localServer, runtimeEnv, }) { + const configuredApiUrl = String(env.GAMEFOUNDRY_API_URL || "").trim() || defaultApiUrl(localServer.baseUrl); + const configuredSiteUrl = configuredValue(env.GAMEFOUNDRY_SITE_URL); return [ `GameFoundry API runtime server running at ${localServer.baseUrl}`, - `Configured site URL: ${configuredValue(env.GAMEFOUNDRY_SITE_URL)}`, - `Configured API URL: ${String(env.GAMEFOUNDRY_API_URL || "").trim() || defaultApiUrl(localServer.baseUrl)}`, + `Configured site URL: ${configuredSiteUrl}`, + `Configured API URL: ${configuredApiUrl}`, + `Local API URL: ${configuredApiUrl}`, + `Local site URL: ${configuredSiteUrl}`, + `Local site URL port: ${portFromUrl(env.GAMEFOUNDRY_SITE_URL)}`, ...formatEnvironmentVariableLogLines(runtimeEnv), ...formatRuntimePortLogLines({ env, localServer }), connectionStatusLine("auth", accountConnection), connectionStatusLine("database", databaseConnection), + `Database mode: ${databaseMode(env)}`, + `Storage status: ${storageStatus(env)}`, `Database SSL mode: ${configuredDatabaseSslMode || `invalid (${databaseSslModeError})`}`, "Press Ctrl+C to stop.", ]; diff --git a/src/dev-runtime/server/local-api-router.mjs b/src/dev-runtime/server/local-api-router.mjs index 9ffc20905..f2f927aff 100644 --- a/src/dev-runtime/server/local-api-router.mjs +++ b/src/dev-runtime/server/local-api-router.mjs @@ -904,6 +904,13 @@ function localApiStartupUrlDisplay(value, fallback = "not configured") { } } +function localApiStartupPortStatus(port) { + if (port === "invalid URL") { + return "FAIL"; + } + return port === "not configured" ? "WARN" : "PASS"; +} + function localApiStartupBindTarget(env = process.env) { const host = String(env.GAMEFOUNDRY_LOCAL_API_HOST || LOCAL_API_STARTUP_DEFAULT_HOST).trim() || LOCAL_API_STARTUP_DEFAULT_HOST; const port = String(env.GAMEFOUNDRY_LOCAL_API_PORT || LOCAL_API_STARTUP_DEFAULT_PORT).trim() || LOCAL_API_STARTUP_DEFAULT_PORT; @@ -916,11 +923,57 @@ function localApiStartupBindTarget(env = process.env) { }; } +function localApiStartupDatabaseMode(env = process.env) { + const databaseStatus = databaseConfigStatus(env); + if (databaseStatus.hostStatus === "FAIL" || databaseStatus.databaseNameStatus === "FAIL") { + return { + reason: "GAMEFOUNDRY_DATABASE_URL is present but is not a valid Postgres URL.", + status: "FAIL", + value: "invalid database URL", + }; + } + if (!databaseStatus.configured) { + return { + reason: "GAMEFOUNDRY_DATABASE_URL is not configured for the Local API startup report.", + status: "WARN", + value: "not configured", + }; + } + return { + reason: "GAMEFOUNDRY_DATABASE_URL is configured with a Postgres protocol; credentials remain hidden.", + status: "PASS", + value: "Postgres", + }; +} + +function localApiStartupStorageStatus(env = process.env) { + const storageConfig = loadStorageConfig(env); + if (storageConfig.configured) { + return { + reason: `Cloudflare R2 configuration is present for bucket ${storageConfig.safe.bucket} and prefix ${storageConfig.safe.projectsPrefix}; credential values remain hidden.`, + status: "PASS", + value: "configured", + }; + } + const issue = storageConfig.validationError + || `missing ${storageConfig.missingKeys?.join(", ") || "storage configuration"}`; + return { + reason: `Cloudflare R2 storage is not fully configured: ${issue}.`, + status: "WARN", + value: "not configured", + }; +} + function systemHealthLocalApiStartupDiagnostics(env = process.env) { const bindTarget = localApiStartupBindTarget(env); const configuredApiUrl = String(env.GAMEFOUNDRY_API_URL || "").trim(); const derivedApiUrl = `http://${bindTarget.value}/api`; const siteUrl = String(env.GAMEFOUNDRY_SITE_URL || "").trim(); + const apiUrlDisplay = localApiStartupUrlDisplay(configuredApiUrl || derivedApiUrl); + const siteUrlDisplay = localApiStartupUrlDisplay(siteUrl); + const siteUrlPort = localApiStartupPortFromUrl(siteUrl); + const databaseMode = localApiStartupDatabaseMode(env); + const storageStatus = localApiStartupStorageStatus(env); const rows = [ { field: "Approved diagnostics format", @@ -934,6 +987,18 @@ function systemHealthLocalApiStartupDiagnostics(env = process.env) { status: "PASS", value: "masked and redacted", }, + { + field: "Environment variable order", + reason: "Runtime .env keys are sorted alphabetically before startup diagnostics are printed.", + status: "PASS", + value: "alphabetical", + }, + { + field: "Secret masking markers", + reason: "Startup diagnostics mask variables whose keys contain PASSWORD, SECRET, TOKEN, KEY, SERVICE_ROLE, or JWT.", + status: "PASS", + value: "PASSWORD, SECRET, TOKEN, KEY, SERVICE_ROLE, JWT", + }, { field: "Configured startup bind target", reason: bindTarget.status === "PASS" @@ -942,6 +1007,28 @@ function systemHealthLocalApiStartupDiagnostics(env = process.env) { status: bindTarget.status, value: bindTarget.value, }, + { + field: "Local API URL", + reason: configuredApiUrl + ? "GAMEFOUNDRY_API_URL is configured and displayed without URL credentials." + : "GAMEFOUNDRY_API_URL is not configured; startup diagnostics derive /api from the bind target.", + status: apiUrlDisplay === "invalid URL" ? "FAIL" : "PASS", + value: apiUrlDisplay, + }, + { + field: "Local site URL", + reason: siteUrl + ? "GAMEFOUNDRY_SITE_URL is available for startup diagnostics." + : "GAMEFOUNDRY_SITE_URL is not configured for the Local API startup report.", + status: siteUrl ? (siteUrlDisplay === "invalid URL" ? "FAIL" : "PASS") : "WARN", + value: siteUrlDisplay, + }, + { + field: "Local site URL port", + reason: "Port is derived from GAMEFOUNDRY_SITE_URL for display only.", + status: localApiStartupPortStatus(siteUrlPort), + value: siteUrlPort, + }, { field: "Configured site URL", reason: siteUrl @@ -956,7 +1043,7 @@ function systemHealthLocalApiStartupDiagnostics(env = process.env) { ? "GAMEFOUNDRY_API_URL is configured and displayed without URL credentials." : "GAMEFOUNDRY_API_URL is not configured; startup diagnostics derive /api from the bind target.", status: "PASS", - value: localApiStartupUrlDisplay(configuredApiUrl || derivedApiUrl), + value: apiUrlDisplay, }, { field: "Configured API URL port", @@ -964,6 +1051,18 @@ function systemHealthLocalApiStartupDiagnostics(env = process.env) { status: "PASS", value: localApiStartupPortFromUrl(configuredApiUrl || derivedApiUrl), }, + { + field: "Database mode", + reason: databaseMode.reason, + status: databaseMode.status, + value: databaseMode.value, + }, + { + field: "Storage status", + reason: storageStatus.reason, + status: storageStatus.status, + value: storageStatus.value, + }, { field: "Configurable multiple runtime ports", reason: "Configurable multiple runtime ports are explicitly deferred/cancelled for this PR.", diff --git a/tests/dev-runtime/AdminHealthOperations.test.mjs b/tests/dev-runtime/AdminHealthOperations.test.mjs index 8d37ad5c3..59bea182c 100644 --- a/tests/dev-runtime/AdminHealthOperations.test.mjs +++ b/tests/dev-runtime/AdminHealthOperations.test.mjs @@ -299,6 +299,13 @@ test("Admin can view operational health while Creator sessions are blocked", asy health.localApiStartup.rows.some((row) => row.field === "Configurable multiple runtime ports" && row.status === "PENDING" && row.value === "deferred/cancelled"), true, ); + const startupRows = new Map(health.localApiStartup.rows.map((row) => [row.field, row])); + assert.equal(startupRows.get("Environment variable order")?.value, "alphabetical"); + assert.equal(startupRows.get("Secret masking markers")?.value, "PASSWORD, SECRET, TOKEN, KEY, SERVICE_ROLE, JWT"); + assert.equal(startupRows.get("Local API URL")?.status, "PASS"); + assert.equal(startupRows.get("Local site URL port")?.value, "5500"); + assert.ok(["Postgres", "not configured", "invalid database URL"].includes(startupRows.get("Database mode")?.value)); + assert.equal(startupRows.get("Storage status")?.value, "not configured"); const startupText = JSON.stringify(health.localApiStartup); assert.equal(startupText.includes("api-user"), false); assert.equal(startupText.includes("api-secret"), false); diff --git a/tests/dev-runtime/LocalApiStartupLogging.test.mjs b/tests/dev-runtime/LocalApiStartupLogging.test.mjs index 5cafc2f0a..5362b40d2 100644 --- a/tests/dev-runtime/LocalApiStartupLogging.test.mjs +++ b/tests/dev-runtime/LocalApiStartupLogging.test.mjs @@ -17,7 +17,10 @@ test("local API startup log separates bind URL from configured public URLs", () GAMEFOUNDRY_API_URL: "http://127.0.0.1:5501/api", GAMEFOUNDRY_DATABASE_URL: "postgres://secret-user:secret-pass@example.invalid/db", GAMEFOUNDRY_SITE_URL: "http://127.0.0.1:5500", + GAMEFOUNDRY_STORAGE_ACCESS_KEY_ID: "storage-access-key", + GAMEFOUNDRY_STORAGE_BUCKET: "gamefoundry-test-assets", GAMEFOUNDRY_STORAGE_ENDPOINT: "http://127.0.0.1:9000", + GAMEFOUNDRY_STORAGE_PROJECTS_PREFIX: "/dev/projects/", GAMEFOUNDRY_STORAGE_SECRET_ACCESS_KEY: "storage-secret", GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY: "service-role-secret", GAMEFOUNDRY_SUPABASE_URL: "https://example.supabase.co", @@ -44,11 +47,26 @@ test("local API startup log separates bind URL from configured public URLs", () key: "GAMEFOUNDRY_DATABASE_URL", value: "postgres://secret-user:secret-pass@example.invalid/db", }, + { + applied: false, + key: "GAMEFOUNDRY_STORAGE_ACCESS_KEY_ID", + value: "storage-access-key", + }, + { + applied: true, + key: "GAMEFOUNDRY_STORAGE_BUCKET", + value: "gamefoundry-test-assets", + }, { applied: true, key: "GAMEFOUNDRY_STORAGE_ENDPOINT", value: "http://127.0.0.1:9000", }, + { + applied: true, + key: "GAMEFOUNDRY_STORAGE_PROJECTS_PREFIX", + value: "/dev/projects/", + }, { applied: false, key: "GAMEFOUNDRY_STORAGE_SECRET_ACCESS_KEY", @@ -62,11 +80,17 @@ test("local API startup log separates bind URL from configured public URLs", () "GameFoundry API runtime server running at http://127.0.0.1:5501", "Configured site URL: http://127.0.0.1:5500", "Configured API URL: http://127.0.0.1:5501/api", + "Local API URL: http://127.0.0.1:5501/api", + "Local site URL: http://127.0.0.1:5500", + "Local site URL port: 5500", "=========================================", "Environment Variables", "=========================================", "+ GAMEFOUNDRY_DATABASE_URL=postgres://********:********@example.invalid/db", + "- GAMEFOUNDRY_STORAGE_ACCESS_KEY_ID=********", + "+ GAMEFOUNDRY_STORAGE_BUCKET=gamefoundry-test-assets", "+ GAMEFOUNDRY_STORAGE_ENDPOINT=http://127.0.0.1:9000", + "+ GAMEFOUNDRY_STORAGE_PROJECTS_PREFIX=/dev/projects/", "- GAMEFOUNDRY_STORAGE_SECRET_ACCESS_KEY=********", "- GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY=********", "+ GAMEFOUNDRY_SUPABASE_URL=https://example.supabase.co", @@ -81,6 +105,8 @@ test("local API startup log separates bind URL from configured public URLs", () "Storage service port: 9000", "Configured auth connection: configured.", "Configured database connection: configured.", + "Database mode: Postgres", + "Storage status: configured (bucket gamefoundry-test-assets; prefix /dev/projects/)", "Database SSL mode: require", "Press Ctrl+C to stop.", ]); @@ -88,6 +114,7 @@ test("local API startup log separates bind URL from configured public URLs", () assert.equal(lines.join("\n").includes(".env loaded for API runtime"), false); assert.equal(lines.join("\n").includes("secret-user"), false); assert.equal(lines.join("\n").includes("secret-pass"), false); + assert.equal(lines.join("\n").includes("storage-access-key"), false); assert.equal(lines.join("\n").includes("storage-secret"), false); assert.equal(lines.join("\n").includes("service-role-secret"), false); }); @@ -119,6 +146,9 @@ test("local API startup log shows missing site URL and derives API URL from bind "GameFoundry API runtime server running at http://127.0.0.1:5599", "Configured site URL: (not configured)", "Configured API URL: http://127.0.0.1:5599/api", + "Local API URL: http://127.0.0.1:5599/api", + "Local site URL: (not configured)", + "Local site URL port: not configured", ".env was not found for API runtime.", "=========================================", "All Runtime Ports being used by Service", @@ -131,6 +161,8 @@ test("local API startup log shows missing site URL and derives API URL from bind "Storage service port: not configured", "Configured auth connection: missing GAMEFOUNDRY_SUPABASE_URL.", "Configured database connection: missing GAMEFOUNDRY_DATABASE_URL.", + "Database mode: not configured", + "Storage status: not configured (missing GAMEFOUNDRY_STORAGE_ENDPOINT, GAMEFOUNDRY_STORAGE_ACCESS_KEY_ID, GAMEFOUNDRY_STORAGE_SECRET_ACCESS_KEY, GAMEFOUNDRY_STORAGE_BUCKET, GAMEFOUNDRY_STORAGE_PROJECTS_PREFIX)", "Database SSL mode: invalid (GAMEFOUNDRY_DATABASE_SSL is missing.)", "Press Ctrl+C to stop.", ]); diff --git a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs index 45d704c3e..cf54c49d3 100644 --- a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +++ b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs @@ -218,6 +218,12 @@ test("Admin System Health renders Postgres diagnostics through the safe status A await expect(notificationsTable).toContainText("Not Configured"); await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Approved diagnostics format"); await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Environment Variables + All Runtime Ports"); + await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Environment variable order"); + await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Secret masking markers"); + await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Local API URL"); + await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Local site URL port"); + await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Database mode"); + await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Storage status"); await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("Configurable multiple runtime ports"); await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("deferred/cancelled"); await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).not.toContainText("secret");