# Refinement Phase 9 — Observability, ops hardening, docs honesty & scale-later

> **Mission:** make the running platform diagnosable and honest, and record the explicitly-deferred scale work
> so nobody mistakes it for missing MVP scope. This is the "finish before launch, then keep for later" bucket.
>
> **Track:** backend (observability/docs) + explicit deferrals · **Depends on:** nothing hard (start anytime;
> finish the observability items before launch) · **Unlocks:** production diagnosability
> **Before you start, read [../../phases/_shared/agent-operating-rules.md](../../phases/_shared/agent-operating-rules.md).**

## 1. Context

This phase **is** the server audit's **post-phase-7 (observability & ops hardening)** + **post-phase-8 (scale &
later)**. Full evidence & fixes:
**[../server/post-phase-backend-plan.md](../server/post-phase-backend-plan.md) § post-phase-7/8**.

## 2. Required reading

- **[../server/post-phase-backend-plan.md](../server/post-phase-backend-plan.md)** § post-phase-7 (7.1–7.6) and
  § post-phase-8 (8.1–8.5).
- **[../server/runtime-services.md](../server/runtime-services.md)** § 4 (Prometheus), § 17 (Elasticsearch —
  deliberately later).

## 3. Scope

### Observability & ops hardening (post-phase-7 — finish before launch)
- **9.1 — add tracing + consolidate the two metric stacks.** OTel is metrics-only (no `WithTracing`/OTLP); two
  overlapping Prometheus stacks run. Add `WithTracing` (AspNetCore + EF) exporting OTLP; pick **one** metrics
  stack; wire trace-id into `ApiResult.requestId` for support correlation.
- **9.2 — broaden health checks + split readiness/liveness.** The single check is app-DB only; add log-DB,
  object-storage (write probe), Redis (when [Phase 7](refinement-phase-7-unattended-ops.md) lands); expose
  `/healthz/live` vs `/healthz/ready`; remove the dead `currentUrl` line.
- **9.3 — revisit prod log levels + notification channels.** Deployed envs write **Warning+ only** (every
  Information-level audit trail dropped); raise to Information+ with retention (or a file/OTLP sink); ensure
  **no PII is logged** (the SMS OTP log disappears with [Phase 8](refinement-phase-8-external-rails.md) 5.1);
  delete the dead Elasticsearch sink block + package or revive it deliberately.
- **9.4 — audit-log growth & archival.** `audit_logs` is append-only with no retention (and Phase 6 §6.3 grows
  it); add a retention/archival policy as a [Phase 7](refinement-phase-7-unattended-ops.md) job; define legal
  retention for money/verification rows first.
- **9.5 — decide `TicketMessage.Body` encryption + the gRPC plugin's fate.** Ticket bodies are the refund/dispute
  paper trail (users type phone numbers, addresses, clinical detail) and are plaintext with no documented
  decision — recommend encrypting via the existing converter pattern. The gRPC plugin duplicates only the
  OTP/token flow, forces the HTTP/2 posture, and enables reflection unconditionally — remove it or give it a
  dedicated HTTP/2 endpoint + disable reflection outside Development.
- **9.6 — keep the docs honest.** Prune the stale mocks-registry duplicate rows, rename the `IJobScheduler` row
  to "recurring jobs", mark delivered/answered REQs, note `IPaymentCaptureSimulator`'s removal. (Root `CLAUDE.md`
  rule 7 — stale instructions are worse than none.)

### Scale & later (post-phase-8 — explicitly NOT MVP; record, don't build)
- **9.7 — Elasticsearch read backend + outbox feeder.** `SqlNurseSearch` is real and correct; `Search:Backend`
  fails fast on any non-`sql` value. Build `ElasticNurseSearch` + the outbox/CDC feeder **only when SQL search
  shows strain**. Not now.
- **9.8 — analytics pipeline.** `IAnalyticsSink` writes `ops.SystemEvents` fire-and-forget; pipe to a
  warehouse/stream when product needs it.
- **9.9 — holiday-calendar feed.** The table is manually maintained; a yearly ops-checklist item is an
  acceptable alternative to a lunar-Hijri drift feed.
- **9.10 — push/SMS notification channels.** `InAppNotificationDispatcher` drops non-InApp channels; add
  fan-out (SMS via [Phase 8](refinement-phase-8-external-rails.md) 5.1's sender, FCM push) when the UX demands.
- **9.11 — deferred product tables** (`organizations`, `organization_nurses`, `fraud_flags`,
  `recurring_booking_schedules`, `bnpl_settlement_entries`, availability slots, customer national-ID KYC, geo
  bulk import) — all verified absent and all pure additive migrations when product pulls them. Leave documented.

## 4. Mocks & seams

None new. 9.7/9.10 are the make-it-real steps for the `INurseSearch` (Elastic backend) and
`INotificationDispatcher` (SMS/push channels) rows — deferred by design.

## 5. Critical rules

- **No PII in logs** — verify after 9.3 (the OTP log must be gone; clinical text and IBANs are never logged).
- **Deferrals are documented, not silent** — 9.7–9.11 stay explicitly out of MVP with a written "when to pull
  it" trigger, so a future reader doesn't mistake them for gaps.
- **Don't build Elasticsearch/analytics/push "because"** — each has a concrete trigger (search strain, product
  need, UX demand). Until then, SQL search / in-app notifications are the real, correct MVP.

## 6. Definition of Done

- [ ] Tracing exports OTLP; one metrics stack; `requestId` carries the trace id.
- [ ] Health checks cover the real dependencies with `/healthz/live` vs `/healthz/ready`.
- [ ] Prod logs Information+ with retention and **no PII**; the dead ES sink is resolved.
- [ ] Audit retention policy exists; ticket-body encryption + gRPC decisions are made and documented.
- [ ] The mocks-registry / REQ tracker / architecture maps are honest and current.
- [ ] 9.7–9.11 are recorded as deferred with explicit pull-triggers. `dotnet build`/`dotnet test` green.

## 7. How to test

- A request produces a trace with a `requestId` that matches the `ApiResult.requestId` in the response.
- `/healthz/ready` fails when a dependency (object storage / Redis) is down; `/healthz/live` stays up.
- Grep the logs after an OTP + a booking + a payout → no phone number, OTP, IBAN, or clinical text appears.

## 8. Hand off & document

- Update `server/CLAUDE.md` (observability wiring, ticket encryption, gRPC decision), the mocks-registry, and
  the REQ tracker. Update [../server/runtime-services.md](../server/runtime-services.md) for any new
  observability service. Save a memory note capturing the encryption/gRPC decisions and the deferral triggers.
