From ffda249617bcc3a940fc58b2809b76110804949b Mon Sep 17 00:00:00 2001 From: implementer Date: Sun, 30 Aug 2026 04:42:06 +0000 Subject: [PATCH] docs: document HOST validation and bind control (E00-S04-T04) The non-container guide now notes that HOST is validated at the adapter boundary as a hostname/IP (invalid values fail startup naming the field) and that the server passes config.host to server.listen, so a configured HOST binds exactly that interface and the startup log reflects the actual bind. The config-env-adapter CI job comment is refreshed to describe the extended suite (HOST validation + loopback-only boot probe). --- .gitea/workflows/ci.yml | 14 +++++++++----- docs/development/non-container.md | 10 ++++++++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 30916ad..230eaff 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -233,11 +233,15 @@ jobs: # validated config) with a comment-stripped workspace scan, mutation probes # (injecting a direct process.env read into any other module fails the scan), # a deterministic boundary probe (full env mapping, defaults, bad-PORT - # fallback, missing required secret -> MissingRequiredSettingError) and - # server-boot probes (a PORT/HOST override shows up in the resolved - # configuration; a missing required secret still fails startup). The job - # installs the frozen workspace and builds the config and database-postgres - # packages because the probes boot the committed server which imports them. + # fallback, HOST validated as hostname/IP with invalid values throwing a + # field-specific startup error, missing required secret -> + # MissingRequiredSettingError) and server-boot probes (a PORT/HOST override + # shows up in the resolved configuration; HOST=127.0.0.1 binds loopback only + # and the startup log reflects the actual bind; an invalid HOST fails startup + # naming the field without echoing the raw value; a missing required secret + # still fails startup). The job installs the frozen workspace and builds the + # config and database-postgres packages because the probes boot the committed + # server which imports them. config-env-adapter: name: Env adapter owns process.env (E00-S04-T04) runs-on: ubuntu-latest diff --git a/docs/development/non-container.md b/docs/development/non-container.md index 196de6d..34e3f8e 100644 --- a/docs/development/non-container.md +++ b/docs/development/non-container.md @@ -17,7 +17,7 @@ The workspace is a pnpm monorepo with three package groups: | --- | --- | --- | | `apps/` | `apps/server` (`@personal-blog/server`) | Public server application. Serves the application health endpoint (E00-S02-T03) gated on the startup migration run (E00-S03-T06), fails fast at startup with a field-specific error when a required setting is missing (E00-S04-T02), redacts secret values from all log output (E00-S04-T03), and reads all of its settings through the config package's environment adapter — no `process.env` reads in the server (E00-S04-T04); the Fastify 5 application shell lands in a later story. | | `packages/` | `packages/core` (`@personal-blog/core`) | Application core (site identity, content primitives). Bootstrap placeholder. | -| `packages/` | `packages/config` (`@personal-blog/config`) | Configuration service. Owns the TypeBox/Ajv configuration schema for the validated config fields (E00-S04-T01), the field-specific startup error for a missing required setting (E00-S04-T02), the secret redaction layer (E00-S04-T03) and the environment adapter — the workspace's single owner of `process.env` reads, mapping `HOST`/`PORT`/`DATABASE_URL`/`EPPP_SESSION_SECRET` onto the validated config (E00-S04-T04); the `.env.example` template (E00-S04-T05) lands in a later task. | +| `packages/` | `packages/config` (`@personal-blog/config`) | Configuration service. Owns the TypeBox/Ajv configuration schema for the validated config fields (E00-S04-T01), the field-specific startup error for a missing required setting (E00-S04-T02), the secret redaction layer (E00-S04-T03) and the environment adapter — the workspace's single owner of `process.env` reads, mapping `HOST`/`PORT`/`DATABASE_URL`/`EPPP_SESSION_SECRET` onto the validated config (E00-S04-T04) and validating `HOST` as a hostname or IP address at the adapter boundary; the `.env.example` template (E00-S04-T05) lands in a later task. | | `packages/` | `packages/database-postgres` (`@personal-blog/database-postgres`) | PostgreSQL database adapter package. Single owner of the `pg`/Kysely driver imports (E00-S03-T02); the migration ledger (`schema_migrations`, E00-S03-T03), the migration advisory lock (E00-S03-T04) and the migration runner with its failure diagnostic (E00-S03-T05) are implemented here. The server depends on this package to run the startup migrations behind its readiness gate (E00-S03-T06). | | `extensions/` | `extensions/example` (`@personal-blog/example-extension`) | Example extension exercising the `extensions/` group. Bootstrap placeholder. | @@ -125,7 +125,13 @@ compiled application entrypoint. Three things to know: `DATABASE_URL` and `EPPP_SESSION_SECRET` are mapped onto the validated config shape (defaults: `host` `0.0.0.0`, `port` 3000, no `databaseUrl`) and validated at startup — no module outside the config package reads - `process.env` directly. + `process.env` directly. `HOST` is validated at the adapter boundary as a + hostname or IP address (an invalid value fails startup with a + field-specific error naming `host` instead of being logged) and the server + passes `config.host` to `server.listen`, so a configured `HOST` binds + exactly that interface — e.g. `HOST=127.0.0.1` binds loopback only — and + the startup log line (`@personal-blog/server listening on + http://:`) reflects the actual bind. To run the compiled output of any other workspace package directly: