M2: Server skeleton — config, logging, health, static web app, security headers (ENG-247) #11

Merged demi.dev
targeting main from stoopkidddd/eng-247-m2-server-skeleton-config-logging-health-static-web-app
Merged opened by and assigned to demi.dev
targeting main from stoopkidddd/eng-247-m2-server-skeleton-config-logging-health-static-web-app

Linear: https://linear.app/engineereddev/issue/ENG-247

What's in this PR:

  • Config: zod covers every §12.3 variable with its default, including the RAILWAY_PUBLIC_DOMAIN → PUBLIC_URL and RAILWAY_ENVIRONMENT → TRUST_PROXY fallbacks. Errors list names, never values, and exit 1.
  • Logging: pino with the §12.4 redaction paths and a child logger per request.
  • App and startup: createApp(deps) injects config, storage (stub), clock, logger and instanceState. index.ts uses real deps, @hono/node-server, and a graceful-shutdown hook list (close code 1012, 10 s drain).
  • Middleware: a JSON ErrorBody error handler (via @pneumo/protocol), a 128 KiB body limit → 413, and request logging with no query strings on /api/files.
  • Routes and serving: GET /api/health; static WEB_DIST_DIR with §12.6 cache rules; SPA fallback; JSON 404 for unknown /api.
  • Headers: CSP built from a bucket-origin parameter, HSTS over HTTPS, nosniff, no-referrer and Permissions-Policy.
  • Build (lead decision): tsup bundles @pneumo/* into dist/index.js, so plain node can run it. This fixes the ENG-239 follow-up recorded on this issue. check:server now builds and launches the bundle (health, HTML, config failure, SIGTERM).

Verify (lead, clean install):

pnpm install --frozen-lockfile → exit 0
mise run check:server → exit 0 · Test Files 5 passed · Tests 54 passed
pnpm --filter @pneumo/web build && pnpm --filter @pneumo/server build → exit 0
.env.example + PORT=8099 node apps/server/dist/index.js:
  /api/health → {"ok":true,"version":"0.1.0","initialized":false}
  / → 200, no-cache, CSP "default-src 'self'; script-src 'self'; … connect-src 'self' wss://localhost:8080; … frame-ancestors 'none'; base-uri 'none'; form-action 'self'", nosniff, no-referrer, Permissions-Policy
  /devices → index.html (SPA) · /api/nope → {"error":{"code":"not_found","message":"Not found."}}
  server log: 0 lines containing authorization/bearer
mise run check → exit 0

Deviations:

  • README Development notes were updated; README was added to Touches by the lead.
  • Package version 0.1.0.
  • The spec leaves HSTS and Permissions-Policy values open; this PR uses max-age=31536000; includeSubDomains and camera=(), microphone=(), geolocation=().
Commits 2

M2: Use epoch milliseconds for the server clock (ENG-247)
M2: Add hardened server skeleton and bundled runtime (ENG-247)