# F-153 — Implementer Evidence ## Build Feature: F-153 — Orders: associate the linked customer's email to the order read model and display it (customer email was missing despite the user having an email). ### Changes (production) - `src/modules/orders/domain/order.ts` — added `email?: string | null` to `OrderView` (read model). `Order` core aggregate is untouched (email is a denormalized read attr). - `src/modules/orders/infrastructure/pg-order-repository.ts`: - added `email: string | null` to `OrderRow`; - added `toOrderView(row, items)` helper; - `findById`, `findByIdAndUserId`, `findAll`, `search` now `SELECT o.*, u.email` with `LEFT JOIN identity_users u ON u.id = o.user_id` (reuses the existing `search` join pattern); - every `OrderView` is built via `toOrderView`, so `email` is always populated (`string | null`; `null` when the order has no linked identity_user). - `src/modules/orders/api/orders.routes.ts`: - `serializeOrder` now emits `email: order.email ?? null` (detail + list); - admin force-transition (`POST /orders/:id/transitions/admin`) consumes `order.email` (the associated email) and no longer issues a separate inline `SELECT email FROM identity_users` lookup → no N+1, single source of truth. ### Changes (tests) - new `src/modules/orders/infrastructure/pg-order-repository.test.ts`: - `findById` resolves `email` via the `identity_users` join (asserts the query contains `identity_users` + `u.email` and returns the row's email); - returns `null` when the order has no linked identity_user; - returns `undefined` when the order does not exist. - existing `OrderView` literals (`order-service`, `payments-service`, `checkout-service`) needed no change (`email` is optional on the view; fixtures omit it → serialized as `null`). ### Boundary / compatibility notes - `identity_users` is referenced only as a SQL table name (pre-existing pattern in `search`); no TypeScript import crosses the identity↔orders boundary → `lint:boundaries` unchanged. - No migration (`identity_users.email` already `citext NOT NULL UNIQUE`). - Additive API field; no state-machine / endpoint / contract change. ## Evidence (commands + results) - `npx tsc --noEmit` → **0 errors** (`TSC_EXIT:0`). - `npx vitest run` → **200 passed | 56 skipped (50 files)**; new F-153 suite `orders/infrastructure/pg-order-repository.test.ts` → **3 passed**. - `npx eslint` (touched files) → **0 errors / 0 warnings** (`ESLINT_EXIT:0`). - `npx prettier --check` on touched files → only **pre-existing baseline warnings** (identical to `HEAD` for both `pg-order-repository.ts` and `orders.routes.ts`; my added lines are short / long string literals accepted by Prettier; no new reformatting). - `npm run lint:boundaries` → **1 violation, pre-existing** (`security/routes` R1); **0 new violations** from F-153. - `git diff --check` → **clean** (no whitespace errors). - `./scripts/verify.sh` → **exit 0** (backlog 270 features valid; runtime-status advance to build/running with F-153 in_progress — see below). ## Decisions - Resolved via the read model (Option A in ADR) rather than route-layer lookups (Option B) because `orders` already joins `identity_users` in SQL for `search`; generalizing that join to all order reads gives the email as a first-class read attribute and removes the per-request lookup from the admin transition.