Files
baya-monorepo/archive/clarify-chain/phase-2-integration.md
T
2026-08-02 18:48:32 +03:30

17 KiB

Phase 2 — Integration & dependency

Depends on: Phase 0 · Can run in parallel with: Phase 1 · Blocks: Phase 3 · Size: one session

Goal

Make the client↔server dependency a thing you can read in one place, and re-sync the contract layer with the code it stopped tracking on 2026-07-13.

Right now the seam is real but scattered: envelope shape and casing in one contract doc, cookies and refresh in client/CLAUDE.md, CORS in a refinement report, NEXT_PUBLIC_API_URL in .env files, the container topology in DEPLOY.md and docker-compose.yml, and the OTP relay in its own README. Nobody can answer "what does the client actually need from the server?" without reading six files.


Inputs

Outputs

docs/integration/
  index.md            THE seam, one page: what crosses the wire and what each side owes the other
  api-contract.md     envelope, casing, pagination, errors, idempotency, auth headers/cookies
  domains/            22 files, one per service domain — refreshed against the live swagger
  openapi/
    swagger.v1.json   (from Phase 0)
    README.md         how to regenerate, when it was last taken, endpoint count
  config-matrix.md    every env var / appsettings key, both projects + docker + the bot
  topology.md         the runtime dependency graph

Also updated: DEPLOY.md — fix the stale user-secrets reference and link to docs/integration/topology.md. It stays at root and stays the deploy procedure.


Steps

1. Write index.md first — the seam on one page

Before touching the per-domain detail, answer these in one page:

  • Transport: HTTP/JSON over NEXT_PUBLIC_API_URL; where gRPC exists and whether it is used.
  • The envelope: ApiResult{ isSuccess, statusCode, message, requestId, data }, payload always under data, requestId is a W3C trace id (refinement-phase-9). Client unwraps in clientFetch/unwrap().
  • Casing: JSON bodies camelCase; URL segments snake_case. (REQ-001 settled this — confirm against the live swagger, don't trust the doc.)
  • Pagination: { items, total, page, pageSize }; the server binds pageSize case-insensitively (REQ-010).
  • Errors: status codes, machine-readable error codes (REQ-003), what the client does with 401 / 403 / 5xx / network.
  • Auth: the cookie set, JWE opacity to the client, silent refresh, session rotation and reuse-detection, /me and me/select_role, role hydration.
  • Idempotency: which endpoints require Idempotency-Key and what the client generates.
  • Money: IRR on the wire, Toman at the UI boundary — link to docs/rules/shared/.
  • What the server owes the client and what the client owes the server, as two short lists.

2. Refresh the 22 domain contracts against the live swagger

For each domain, compare the contract doc against swagger.v1.json and the handler code. Mark each endpoint: matches · drifted (describe) · undocumented (in code, not in contract) · phantom (in contract, not in code).

Fold in the delivered REQs — 17+ were delivered in refinement-phase-3 and amended shapes that the domain docs still describe the old way. The domain doc is the thing that should be true; the REQ ledger is a change log. After this phase, a reader should never need to read the REQ file to know the current shape.

Note the domain-file cleanup: there is both a messaging.md (851 B stub) and a messaging-notifications-admin.md (10.6 KB) — merge. Align the file set with the client's 22 services/ domains so the mapping is one-to-one where it can be.

Anything you cannot verify from swagger or code: mark UNVERIFIED: and add a row to Phase 4's input list. Do not guess a shape.

3. Write config-matrix.md

One table: key · where it's set (appsettings / .env / docker-compose / Caddyfile) · consumed by · required? · default · notes. Cover at minimum:

  • NEXT_PUBLIC_API_URL, NEXT_PUBLIC_NESHAN_KEY, and the rest of the client's NEXT_PUBLIC_*
  • IdentitySettings:SecretKey / :Encryptkey
  • Seams:FieldEncryption:Key / :HashKeyflag as load-bearing and immutable
  • the connection strings (app DB + log DB), and that the DB is remote and not containerised
  • OpenTelemetry:Otlp:Endpoint, the health endpoints (/healthz/live, /healthz/ready)
  • the seam selectors that choose real vendor adapters vs mocks (refinement-phase-8)
  • the Telegram OTP bot's token/chat config
  • CORS origins, and the Caddy hostnames balinyaar.ir / api.balinyaar.ir

State plainly, once, that config lives in files and dotnet user-secrets is not used — and that this is a deliberate pre-launch trade with live credentials in git (link to DEPLOY.md "Going to Production" and to Phase 5's pre-launch.md).

4. Write topology.md

The runtime dependency graph — a mermaid diagram plus a short table:

browser -> Caddy (caddy_net) -> client container (Next.js)  -> server container (ASP.NET)
                              -> server container            -> remote SQL Server (not containerised)
                                                             -> telegram-otp-bot (OTP relay)
                                                             -> object storage / external rails (per seam config)

For each edge: what flows over it, what breaks if it is down, and where it is configured. This is the "dependency between projects, documented in a proper place" deliverable.

5. Point the old locations at the new one

dev/contracts/README.md gets a one-line "moved to docs/integration/" banner (Phase 6 archives the folder; until then, don't leave two live copies competing).


Verification

  • Every endpoint in swagger.v1.json appears in exactly one docs/integration/domains/*.md, or is listed in that file's "undocumented, intentionally" section with a reason.
  • No phantom endpoints remain undeclared — each is either removed from the doc or listed as "planned, not built" with a backlog reference for Phase 4.
  • config-matrix.md accounts for every key in appsettings.Production.json, docker-compose.yml, and both .env files — diff mechanically, don't eyeball.
  • DEPLOY.md no longer instructs anyone to use user-secrets.
  • index.md fits on one screen-and-a-half and answers the seam question without a single click.

Definition of done

A frontend agent can build against the API by reading docs/integration/index.md plus one domain file, and a deploy question is answered by topology.md + config-matrix.md without opening docker-compose.yml.

Handoff

Run 2026-07-30 against commit d3ec723. All verification items pass, checked mechanically (script in the session scratchpad, results reproduced below).

What shipped

docs/integration/
  index.md            153 lines — the whole seam, one screen and a half
  api-contract.md     envelope · casing · status codes · auth · pagination · idempotency · money · enums · rate limits
  config-matrix.md    every key in both appsettings, docker-compose, 3 .env files, the bot, Caddy, CORS
  topology.md         mermaid graph + a 10-edge table + ports + startup order + the local variant
  domains/
    index.md          the census, the route-shape exceptions, the enum map, what-replaced-what
    <22 files>        one per client services/ domain, 1:1
  openapi/README.md   updated: C-9 resolved, the servers-block provenance wrinkle recorded

Also updated: DEPLOY.md (links topology + config-matrix; states there is no appsettings.Production.json so step 2 of "Going to Production" is a create), docs/README.md (integration → written), dev/contracts/README.md + openapi/README.md + domains/messaging.md (moved/merged banners).

Verification

Item Result
Every swagger path declared in exactly one domains/*.md 178/178, 0 unreferenced, 0 duplicated
Operation census adds up 186 = 184 domain + 2 ping in api-contract.md
Every phantom declared with a reason + REQ 24/24
config-matrix.md accounts for every key Diffed mechanically; 5 gaps found and written up
DEPLOY.md no longer instructs user-secrets Pass — it already documented the removal; now also links the new files
index.md fits a screen and a half 153 lines
Every doc carries a Last verified stamp 27/27
No reference file over 400 lines Pass
Relative links resolve 216 checked, 0 broken

Scope note

api-contract.md was merged into index.md's siblings as planned, but the plan's domains/ count of 22 was met by re-cutting the file set along the client's domain boundaries rather than the backend phases the old 17 files used. That is what makes the mapping 1:1 and is why the file names changed.

swagger.v1.json was not re-fetched — Phase 0's 2026-07-29 snapshot is 1 day older than this run and the server was not booted. Everything here is derived from that snapshot plus current source.


Drift list — Phase 4's input

A. Phantom endpoints (24). The client's real API clients call 24 routes the server does not expose. Each is declared in its domain file with its REQ. Grouped by REQ:

REQ Status Phantoms Domain
REQ-031 deferred 3 (admin_roles/*) admin
REQ-061 never filed 2 (admin_users/search, /lookup) admin
REQ-022 partial 3 (checkout_bnpl/options, /schedule, /wallet_installments) bnpl
REQ-032 partial 5 (centers/me/*, partner-centers/{id}/nurses) partner-center
REQ-033 partial 1 (centers/me/settlement) partner-center
REQ-064 open 1 (centers/me/bookings/{id}) partner-center
REQ-034 deferred 3 (admin_verifications documents-url / approve / reject) verification
REQ-035 deferred 3 (admin_refunds preview / approve / reject) refunds
REQ-036 deferred 1 (admin_payouts/{id}/transfer_reference) payouts
REQ-047 open 1 (bookings/payment_history) payment
REQ-048 open 1 (refunds/my) refunds
REQ-063 open 1 (tickets/{id}/assign) tickets

Two are live 404s because their domain's mock is off: bookings/payment_history (payment) and tickets/{id}/assign (tickets). Both are guarded in the client, so neither is user-visible — but they are reachable, not hypothetical.

B. Five REQs whose filed status is wrong or narrower than reality. Each was checked against the swagger, not against another document. Phase 4 should re-triage these rather than copy the ledger.

REQ Filed as Actually
REQ-050 open — "the list DTO carries neither field" BookingRequestListItemDto has variantLabel (and patientAge). It lacks variantPrice/variantPriceUnit and the status=answered group filter. The REQ was verified against the client type, not the wire
REQ-063 open — "no close/reopen/assign routes yet" tickets/{id}/close and /reopen exist and are wired. Only assign is missing. tickets/constants.ts carries the stale reason
REQ-066 open — needs an anonymous search read GET /search/nurses is already anonymous. What is missing is the rate limit — SearchController has no [EnableRateLimiting]
REQ-067 open — needs an anonymous profile read GET /nurses/{id}/profile is already anonymous. What is missing is the privacy review
REQ-029 / REQ-030 delivered Correct — but admin/constants.ts still names them as reasons the admin mock is primary. The only live reasons are REQ-031 and REQ-061

C. Client-side drifts (client work, not contract work).

# Where What
1 client/src/lib/api/types.ts ApiEnvelope<T> omits code, which the server sends and clientFetch already reads at runtime. Incomplete type, no defect
2 client/src/services/admin/apis/clientApi.ts pageQuery() sends page_size; these endpoints declare PageSize. Model binding is case-insensitive, not separator-insensitive, so it binds nothing and every admin list silently gets the default page size. Latent until USE_ADMIN_MOCK flips
3 client/src/services/bookingRequests/types.ts Marks variantLabel as client-augmented/absent; the server serves it. Widening the type would light up the redesigned inbox card today
4 client/src/services/payouts/apis/clientApi.ts Comment says NursePayoutHistoryDto has no failureReason; the wire has it on all three payout read models
5 client/src/services/tickets/constants.ts Says the backend has no close/reopen/assign; close and reopen exist (B, above)
6 client/src/services/payouts/apis/clientApi.ts Sends Idempotency-Key on admin_payouts process/retry, where the server never reads it. Decorative, not harmful
7 client/.env.sample Still NEXT_PUBLIC_API_URL = https://localhost:5002 — the https half of C-3, in the file a fresh clone copies. .env.development is correct
8 client/src/services/profiles/ POST customer_profiles/avatar exists and is live; the client only wires the nurse route, so a customer cannot set a photo

D. Cross-side vocabulary asymmetry (1). The client's TicketAuthorRole includes system; Entities/Messaging/TicketCodes.cs defines only customer/nurse/admin. Safe on the reading side, but a reader of the client types would wrongly conclude the server emits it. All other 17 shared vocabularies match exactly, both directions.

E. Route-shape exceptions (4). admin/partner-centers (+ /set-active, /sponsor-nurse), admin/tickets, admin/reviews/… and internal/bookings/… hardcode route strings instead of using the [controller]/[action] tokens; three introduce hyphens, against the snake_case rule. Because the route also derives the dynamic-permission key, normalising them breaks permissions as well as URLs — recorded, not fixed.

F. Config gaps (5). Written up in config-matrix.md § What the diff found: NEXT_PUBLIC_EVV_MOCK_GPS and NEXT_PUBLIC_VERSION read but declared in no .env; OpenTelemetry:Otlp:Endpoint, Search:Backend and Seed:Admin* read but set nowhere (and a bad Search:Backend throws at startup); .env.sample's stale https; the Telegram shared secret duplicated across two files with nothing keeping them equal; no appsettings.Production.json.

G. Unfiled REQ (1). ui-phase-11-report.md records "REQ-061…064 appended" to the ledger. Only 062/063/064 were. Ten live client files cite REQ-061 for the admin user directory (admin_users/search, admin_users/lookup). Phase 4 must file it, not assume it is tracked.

Contradictions resolved here

C-6 · C-7 · C-8 · C-9 closed; C-2 closed as a documentation decision; C-3's contract-layer half closed. Decisions recorded in open-contradictions.md § Resolved.

What Phase 3 should know

  • The seam facts it needs are settled and mechanically verified: camelCase bodies, snake_case URLs, bearer header (not cookie auth), money as a digit string outbound and an int64 inbound, Idempotency-Key on exactly two endpoints, 20 anonymous operations.
  • GET /api/v1/dev/last_otp/{phone} is live on api.balinyaar.ir — the deployment runs as Development with a capture-safe SMS provider, so both gate conditions hold. Any flow doc that describes logging in should say this is how you do it today, and that it is the deployment's largest exposure.
  • C-4 (the dev-certs step) is confirmed dead: the API binds plain HTTP locally, so there is no certificate to trust. C-5's two bring-up worlds are real and give different data — the committed dev config points at the same remote, already-seeded database the deployment uses.