# F-153 — Architectural Decision Record

## Status: accepted (architect)

## Context
The order read model (`OrderView`) carries the customer's `userId` but never the `email`, even
though `orders_orders.user_id` references `identity_users` whose `email citext NOT NULL UNIQUE`
always exists. As a result:

- Order serialization (`serializeOrder`) never displays the customer email — the reported
  "customer email missing" symptom.
- The admin force-transition (`POST /orders/:id/transitions/admin`) worked around the gap with
  an inline `SELECT email FROM identity_users WHERE id = order.userId`, a fragile per-request
  lookup that produced "El cliente no tiene email asociado" whenever the view itself did not
  carry the association (and would N+1 if reused broadly).

## Decision
Associate the customer's email to the order **read model** and resolve it once, in the orders
repository, by joining `identity_users` (the same `LEFT JOIN` already used by the `search`
method):

1. Add `email: string | null` to `OrderView` (`identity/domain` vs `orders` boundary untouched;
   `Order` core aggregate stays without email — email is a denormalized read attribute).
2. Add `email` to `OrderRow`; extend `findById`, `findByIdAndUserId`, `findAll`, and `search` to
   `SELECT o.*, u.email FROM orders_orders o LEFT JOIN identity_users u ON u.id = o.user_id`,
   and build the view through a single `toOrderView(row, items)` helper.
3. Surface `email` in `serializeOrder` (detail + list).
4. In the admin force-transition, consume `order.email` as the single source of truth and remove
   the inline lookup.

## Rationale
- **Single source of truth**: once the email is on the view, both the API serialization and the
  admin notification read it from one place — no divergent lookups.
- **Reuses an established pattern**: `orders/infrastructure/pg-order-repository.ts` `search`
  already joins `identity_users`; F-153 generalizes that to all order reads. No new pattern.
- **Boundary-safe**: `identity_users` is referenced only as a SQL table-name string at the orders
  infrastructure layer (no TypeScript import of `identity` from `orders`). `identity` does not
  import `orders`/`payments`. `lint:boundaries` R1/R2 unaffected.
- **No migration / backward compatible**: `identity_users.email` already exists and is `NOT
  NULL`; `email: string | null` on the view is null only when an order has no linked user.
  Serialization only adds a field.
- **No N+1 on lists**: `findAll`/`search` resolve email in the same SELECT, so the admin list
  returns emails without per-item lookups.

## Consequences
- Additive API field `email` on order DTOs; frontend/admin consumers gain the value for free.
- `order-service.test.ts`, `payments-service.test.ts`, `checkout-service.test.ts`
  `OrderView` literals gain `email: null`.
- New `orders/infrastructure/pg-order-repository.test.ts` covers email resolution (happy path
  and no-linked-user).
- Admin transition no longer depends on a raw `identity_users` query in the route layer.
