The environment adapter now resolves HOST through resolveHost, validating it
at the adapter boundary as a hostname (RFC 1123) or IP address (IPv4/IPv6,
node:net isIP); an invalid HOST throws a field-specific ConfigStartupError
naming host, so arbitrary env content is never used for binding or echoed
verbatim into the startup log (issue acceptance criterion, resolving security
review finding SEC-3).
The server passes config.host to server.listen(config.port, config.host, ...),
so a configured HOST binds exactly that interface and the startup log never
claims a bind the process does not enforce (resolving SEC-2).
- packages/config: add src/startup.ts exposing assertValidConfig (builds on
the T01 TypeBox/Ajv schema) and the field-specific startup errors
(MissingRequiredSettingError names the missing field; ConfigStartupError
names each violating field); re-export from the package boundary
- apps/server: validate the startup configuration (including the required
EPPP_SESSION_SECRET) before the server binds, so a missing required
setting crashes the process at startup naming the field; depends on
@personal-blog/config
- compose.yaml: provide EPPP_SESSION_SECRET for the app service (dev-only
>= 32 char default; override via .env / shell)
- Dockerfile: ship the compiled packages/config in the image (build source +
runtime dist), matching the server's new workspace dependency
- pnpm-lock.yaml: apps/server importer gains @personal-blog/config
Adds packages/config (@personal-blog/config) — the EPPP configuration
service foundation. The package defines the configuration schema with
TypeBox (configSchema: host, port, databaseUrl, sessionSecret — the
validated config fields, golden-tuple pins @sinclair/typebox@0.34.52 and
ajv@8.20.0) and compiles it with Ajv (validateConfig). The environment
adapter (T04), field-specific startup errors (T02) and secret redaction
(T03) build on this boundary in later tasks; nothing reads process.env yet.
Wiring for the new workspace package: lockfile importer + resolved
typebox/ajv tree, apps/server/Dockerfile manifest copy (frozen in-image
install must match the lockfile importers), config-schema CI job, package
set fixtures (workspace-layout, workspace-config, strict-tsconfig,
typescript-pin), probe-file gitignore entry.