From 168a7a2c36faffa5e8b38cf5a2832eb7898a47b4 Mon Sep 17 00:00:00 2001 From: Dylan Garvis Date: Sat, 8 Aug 2026 11:06:06 -0400 Subject: [PATCH] feat(health): report build version --- apps/web/src/app/healthz/route.test.ts | 16 +++++++++++++--- apps/web/src/app/healthz/route.ts | 2 +- design/log.md | 1 + design/us-015-platform-operations.md | 8 +++++--- docs/releases.md | 2 ++ 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/apps/web/src/app/healthz/route.test.ts b/apps/web/src/app/healthz/route.test.ts index 782b885..472c2be 100644 --- a/apps/web/src/app/healthz/route.test.ts +++ b/apps/web/src/app/healthz/route.test.ts @@ -1,12 +1,22 @@ -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { GET } from "./route"; describe("health endpoint", () => { - it("reports process readiness without requiring external services", async () => { + afterEach(() => vi.unstubAllEnvs()); + + it("reports process readiness and the immutable build version without requiring external services", async () => { + vi.stubEnv("APP_VERSION", "1.19.0"); + const response = GET(); expect(response.status).toBe(200); expect(response.headers.get("cache-control")).toBe("no-store"); - expect(await response.json()).toEqual({ status: "ok" }); + expect(await response.json()).toEqual({ status: "ok", version: "1.19.0" }); + }); + + it("reports a development version when no build version is supplied", async () => { + vi.stubEnv("APP_VERSION", ""); + + expect(await GET().json()).toEqual({ status: "ok", version: "development" }); }); }); diff --git a/apps/web/src/app/healthz/route.ts b/apps/web/src/app/healthz/route.ts index 7d34b6e..69625fb 100644 --- a/apps/web/src/app/healthz/route.ts +++ b/apps/web/src/app/healthz/route.ts @@ -1,6 +1,6 @@ export function GET(): Response { return Response.json( - { status: "ok" }, + { status: "ok", version: process.env.APP_VERSION?.trim() || "development" }, { headers: { "Cache-Control": "no-store", diff --git a/design/log.md b/design/log.md index e59c922..5e8d18d 100644 --- a/design/log.md +++ b/design/log.md @@ -10,6 +10,7 @@ * **Refine**: Consolidated RCON connection management into a full-width terminal workspace with header controls, modal add/edit/delete flows, terminal-contained notices, and no duplicate configuration panels. * **Extend**: Added bounded page-memory RCON command recall with Arrow Up/Arrow Down navigation, unsent-draft restoration, and prompt focus retention after results and server changes. * **Extend**: Retained up to 50 chronological page-memory RCON command/response exchanges in the auto-scrolling terminal transcript without persisting them. +* **Extend**: Added the Docker-build-supplied immutable application version to the dependency-free `/healthz` response, with a `development` fallback. ## 2026-08-07 diff --git a/design/us-015-platform-operations.md b/design/us-015-platform-operations.md index c75c4f3..2a3359e 100644 --- a/design/us-015-platform-operations.md +++ b/design/us-015-platform-operations.md @@ -3,7 +3,7 @@ type: User Story title: Deploy and operate the platform securely description: Operators have repeatable builds, migrations, credential provisioning, configuration, and security checks. tags: [operations, security, database, deployment] -timestamp: 2026-08-01T23:10:59Z +timestamp: 2026-08-08T15:05:48Z story_id: US-015 status: verified --- @@ -23,7 +23,7 @@ As a platform operator, I want reproducible deployment and security controls, so - [x] The web application sets CSP, framing, MIME, referrer, and permissions headers. - [x] Database-backed user and administrator pages render as dynamic React Server Components with server-side data access. - [x] Core pages provide keyboard focus indication, a skip link, labelled controls, table semantics, live status messaging, sufficient text contrast, and reduced-motion support. -- [x] The web runtime provides a dependency-free health endpoint for orchestration probes. +- [x] The web runtime provides a dependency-free, uncached health endpoint for orchestration probes that reports readiness and the immutable `APP_VERSION`, falling back to `development` when no build version is supplied. - [x] Web and Discord bot runtimes emit structured Pino logs with credential-field redaction and safe operational context. - [x] npm dependency audit and Semgrep security review complete without findings at the last verified change. - [x] Architecture, Keycloak, API error, security, bot, and Velocity operating documentation is available. @@ -36,12 +36,14 @@ As a platform operator, I want reproducible deployment and security controls, so - [`packages/database/scripts/create-plugin-credential.ts`](../packages/database/scripts/create-plugin-credential.ts) - [`plugins/velocity/build.gradle.kts`](../plugins/velocity/build.gradle.kts) - [`apps/web/next.config.ts`](../apps/web/next.config.ts) +- [`apps/web/src/app/healthz/route.ts`](../apps/web/src/app/healthz/route.ts) +- [`Dockerfile`](../Dockerfile) - [`packages/logging/src/index.ts`](../packages/logging/src/index.ts) - [`docs/accessibility.md`](../docs/accessibility.md) # Validation -Use `npm test`, `npm run typecheck`, `npm run lint`, `npm run build`, `npm run velocity:build`, `npm audit`, and `npm run design:validate`. Structured logging redaction is covered by [`packages/logging/test/logger.test.ts`](../packages/logging/test/logger.test.ts). +Use `npm test`, `npm run typecheck`, `npm run lint`, `npm run build`, `npm run velocity:build`, `npm audit`, and `npm run design:validate`. Structured logging redaction is covered by [`packages/logging/test/logger.test.ts`](../packages/logging/test/logger.test.ts). Health endpoint tests verify the supplied immutable build version, the `development` fallback, readiness status, and uncached response; full workspace tests, type checks, lint, OKF validation, and a production web build passed on 2026-08-08. # Related Stories diff --git a/docs/releases.md b/docs/releases.md index f7c7538..04875d9 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -65,6 +65,8 @@ docker run --rm -p 3000:3000 \ Provide all deployment settings described by [`.env.example`](../.env.example). Only set `TRUST_PROXY=true` behind a proxy that overwrites forwarding headers. +The release workflow passes the semantic version through Docker's `VERSION` build argument, and the final image embeds it as `APP_VERSION`. `GET /healthz` reports this immutable image version with readiness, for example `{"status":"ok","version":"1.19.0"}`. Manual builds that omit the build argument report `development`. + ## Velocity JAR Download the JAR from the matching public Gitea release, copy it to Velocity's `plugins/` directory, and retain the existing `plugins/minecraft-account-manager/config.properties` during upgrades.