# Implementer — F-003 HTTP foundation and request context done -> work/artifacts/F-003/implementer.md ## Files created - src/infrastructure/logging/logger.ts (pino factory; JSON only; injectable destination for tests) - src/shared/http-input.ts (parseJson zod helper -> AppError 400 VALIDATION_ERROR with field paths) - src/app/tests/http-foundation.test.ts (9 tests: id generation/propagation/sanitization, log correlation, envelope stability, no stack leak, one log line per request) - src/shared/tests/http-input.test.ts (3 tests) ## Files modified - src/shared/errors.ts: AppError class + error envelope v2 `{ error: { statusCode, code, message, details? }, requestId }` (additive over F-001 shape) - src/app/build-app.ts: genReqId sanitization (^[A-Za-z0-9._-]{1,128}$ else randomUUID), onRequest/onResponse hooks (x-request-id header + one JSON log line per request), explicit setErrorHandler/setNotFoundHandler with requestId; buildApp({ logger? }) injectable - src/infrastructure/http/server.ts: startup/shutdown logging via injected logger - spec/tech.md: pino + zod justification - package.json: deps pino, zod ## API changes - Every response now carries `x-request-id` header - Error body v2: `{ error: { statusCode, code, message, details? }, requestId }` - GET /health body unchanged ## Tests passed (evidence) ``` npm run lint:boundaries -> Boundary check OK: 16 file(s) checked npm run lint -> OK npm run typecheck -> exit 0 npm run build -> exit 0 npm test -> 6 files passed, 2 skipped (integration, no DB in unit run); 23 passed | 6 skipped live smoke (PORT=3998): GET /health -> 200, x-request-id: 65b38aee-..., body {"status":"ok"} GET /nope -> 404 envelope v2 with requestId matching log line POST /health bad json -> 400; log code FST_ERR_CTP_INVALID_JSON_BODY with same requestId as response server logs: one "request completed" JSON line per request carrying requestId ``` ## Known limitations - Fastify 4xx parse-error messages are exposed as-is (e.g. "Unexpected token"); they carry no internals and are standard. - Validation hook is exercised via a test-only route (/__test/echo registered in composition test); first production consumer arrives with F-005. ## Follow-up work - F-004 config/flags will centralize LOG_LEVEL handling. - F-005 identity first real parseJson consumer.