Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
53 changes: 29 additions & 24 deletions docs_build/dev/reports/codex_changed_files.txt
Original file line number Diff line number Diff line change
@@ -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(-)
479 changes: 478 additions & 1 deletion docs_build/dev/reports/codex_review.diff

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion docs_build/dev/reports/playwright_v8_coverage_report.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
34 changes: 32 additions & 2 deletions scripts/start-local-api-server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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)";
Expand Down Expand Up @@ -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,
Expand All @@ -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}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Report the actual local API URL

When GAMEFOUNDRY_API_URL points somewhere other than the server that just bound locally (for example a shared/dev API URL while localServer.baseUrl is http://127.0.0.1:5599), this new Local API URL line prints the configured public URL instead of the local endpoint. That duplicates Configured API URL and hides the actual local /api URL/port users need for startup troubleshooting; derive this line from localServer.baseUrl instead.

Useful? React with 👍 / 👎.

`Local site URL: ${configuredSiteUrl}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Redact credentials from the local site URL

When GAMEFOUNDRY_SITE_URL includes URL credentials, such as http://site-user:site-secret@127.0.0.1:5500, this new Local site URL line writes the raw value to startup logs. That bypasses the URL-credential redaction used for environment diagnostics and exposes the embedded username/password; redact URL credentials before printing this diagnostic.

Useful? React with 👍 / 👎.

`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.",
];
Expand Down
101 changes: 100 additions & 1 deletion src/dev-runtime/server/local-api-router.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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",
Expand All @@ -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"
Expand All @@ -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
Expand All @@ -956,14 +1043,26 @@ 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",
reason: "Port is derived from the configured or startup-derived API URL for display only.",
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.",
Expand Down
7 changes: 7 additions & 0 deletions tests/dev-runtime/AdminHealthOperations.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading