doc clean up phase 2

This commit is contained in:
hamid
2026-07-30 12:49:46 +03:30
parent c889c46110
commit c841bded26
36 changed files with 2970 additions and 51 deletions
+2 -2
View File
@@ -153,8 +153,8 @@ stays small.
| Phase | Status | Finished | Notes |
| --- | --- | --- | --- |
| 0 Inventory & scaffold | **done** | 2026-07-29 | 275 files surveyed (not ~260). OpenAPI refreshed — drift is **1 endpoint**, not a rewrite. 3 of 5 seeded contradictions were narrower than stated; see [open-contradictions.md](open-contradictions.md#corrections-to-the-seeded-list). Phase 4 owns 149 files |
| 1 Rules consolidation | not started | | |
| 2 Integration & dependency | not started | | |
| 1 Rules consolidation | **done** | 2026-07-30 | Row reconstructed by phase 2 — phase 1 finished without updating it. Evidence: all 18 `docs/rules/**` files exist and are stamped `2026-07-30 against d3ec723`, `docs/README.md` marks the section **written**, and C-11 + R-1…R-6 are recorded as resolved-by-phase-1 |
| 2 Integration & dependency | **done** | 2026-07-30 | 27 files under `docs/integration/`. All 186 operations assigned to exactly one of 22 domain files (1:1 with `client/src/services/`). **Main finding: 24 phantom endpoints** — the client↔swagger axis nobody had checked; 2 are live 404s. Also 5 mis-filed REQs, 1 never-filed REQ (**C-15**), 8 client drifts, 5 config gaps. Resolved C-2, C-6, C-7, C-8, C-9 + C-3's contract half |
| 3 Flow atlas | not started | | |
| 4 Backlog reconciliation | not started | | |
| 5 Roadmap | not started | | |
+28 -11
View File
@@ -2,6 +2,8 @@
> Opened 2026-07-29 by [phase 0](phase-0-inventory-and-scaffold.md), against commit `c99e3f4`.
> **Phase 0 only finds these. It resolves none of them.**
> Last updated 2026-07-30 by [phase 2](phase-2-integration.md), against commit `d3ec723`:
> **C-2, C-6, C-7, C-8 and C-9 resolved**, C-3's contract-layer half resolved, **C-15 and C-16 added**.
Each row is *claim A* vs *claim B* with a location for both, and the phase that owns the resolution.
When a phase resolves one, it moves the row to **Resolved** at the bottom with the decision, and — per
@@ -17,18 +19,15 @@ written up as they actually are, with the evidence, in [§ Corrections to the se
| # | Claim A | Claim B | Owner | Status |
| --- | --- | --- | --- | --- |
| C-1 | **Set the crypto keys with `dotnet user-secrets`** — [manual-testing-plan.md:22](../../dev/post-phase/manual-testing-plan.md), [:233](../../dev/post-phase/manual-testing-plan.md) | **`user-secrets` is not used; `<UserSecretsId>` was removed so the store is not read** — [CLAUDE.md:90](../../CLAUDE.md), [DEPLOY.md:20](../../DEPLOY.md), [server/CLAUDE.md:70](../../server/CLAUDE.md), and `Baya.Web.Api.csproj` (no `UserSecretsId` element) | 3 | open |
| C-2 | **Placeholder value is literally `SET_VIA_USER_SECRETS_OR_ENV`**`server/src/API/Baya.Web.Api/appsettings.json` (6 occurrences), enforced by `StartupSecretsGuard` and by the pre-commit hook | **That store does not exist any more** (C-1's B side). The name instructs a reader to use a removed mechanism | 2 | open — naming only, behaviour is correct |
| C-3 | **The server listens on `https://localhost:5002`** — [CLAUDE.md:62](../../CLAUDE.md), [:135](../../CLAUDE.md), [api-conventions.md:6](../../dev/contracts/conventions/api-conventions.md), [RUNBOOK.md:7](../../dev/post-phase/refinement/RUNBOOK.md), [:84](../../dev/post-phase/refinement/RUNBOOK.md), [:112](../../dev/post-phase/refinement/RUNBOOK.md), + 12 more | **It listens on `http://localhost:5002`**`launchSettings.json:25` (`"applicationUrl": "http://localhost:5002"`), and `client/.env.development:20` (`NEXT_PUBLIC_API_URL = http://localhost:5002`) | 2 + 3 | **partly resolved by phase 1** — the three rule-file occurrences (root `CLAUDE.md` ×2, `server/CLAUDE.md`) now say `http`. `api-conventions.md` (phase 2) and `RUNBOOK.md` + the remaining 12 (phase 3) are untouched |
| C-3 | **The server listens on `https://localhost:5002`** — [CLAUDE.md:62](../../CLAUDE.md), [:135](../../CLAUDE.md), [api-conventions.md:6](../../dev/contracts/conventions/api-conventions.md), [RUNBOOK.md:7](../../dev/post-phase/refinement/RUNBOOK.md), [:84](../../dev/post-phase/refinement/RUNBOOK.md), [:112](../../dev/post-phase/refinement/RUNBOOK.md), + 12 more | **It listens on `http://localhost:5002`**`launchSettings.json:25` (`"applicationUrl": "http://localhost:5002"`), and `client/.env.development:20` (`NEXT_PUBLIC_API_URL = http://localhost:5002`) | 2 + 3 | **partly resolved by phases 1 + 2** — the three rule-file occurrences now say `http`; phase 2's [api-contract.md](../integration/api-contract.md) states `http` and banner-marks `api-conventions.md` as history. **Phase 2 also found a new occurrence the survey missed: `client/.env.sample:20`**, the file a fresh clone copies. `RUNBOOK.md` + the remaining occurrences are phase 3 |
| C-4 | **RUNBOOK's `dotnet dev-certs https --trust` step is required**, because the browser would otherwise reject the API — [RUNBOOK.md:26](../../dev/post-phase/refinement/RUNBOOK.md) | **The API is plain HTTP locally** (C-3's B side), so there is no certificate to trust | 3 | open — likely a dead step |
| C-5 | **Bring-up starts a local SQL Server in Docker on `localhost:1433`** — [RUNBOOK.md:18](../../dev/post-phase/refinement/RUNBOOK.md), [:3548](../../dev/post-phase/refinement/RUNBOOK.md), [:63](../../dev/post-phase/refinement/RUNBOOK.md) | **The committed dev config points at a remote SQL Server**`appsettings.Development.json` (`Server=87.107.152.16,1433`), and [manual-testing-plan.md:20](../../dev/post-phase/manual-testing-plan.md) calls the remote one "currently" the target | 3 | open — the two bring-up paths give *different worlds*: the remote DB is already seeded, a fresh local one is not |
| C-6 | **`GET /api/v1/webhooks/payouts/{provider}` does not exist** — absent from `dev/contracts/openapi/swagger.v1.json` (frozen 2026-07-13) and from every `dev/contracts/domains/*.md` | **It exists** — present in the fresh snapshot `docs/integration/openapi/swagger.v1.json` (2026-07-29). See [§ OpenAPI drift](#openapi-drift) | 2 | open |
| C-7 | **`GeneratePayoutBatchCommand`'s shape** per the 07-13 snapshot | **A different shape** in the 07-29 snapshot — the only schema of 339 whose definition changed | 2 | open |
| C-8 | **Two contract files describe the same domain** — [`dev/contracts/domains/messaging.md`](../../dev/contracts/domains/messaging.md) is a headerless 851-byte fragment ("Refinement phase 3 additions (REQ-028)") sitting beside the 10.8 K [`messaging-notifications-admin.md`](../../dev/contracts/domains/messaging-notifications-admin.md), which it silently amends | — | 2 | open — merge, don't move both |
| C-9 | **The OpenAPI folder publishes documents `v1` *and* `v1.1`** — [openapi/README.md:3](../../dev/contracts/openapi/README.md) | **Only `swagger.v1.json` has ever been committed**; `v1.1` was not fetched during this survey | 2 | open — `UNVERIFIED`, check `/swagger/v1.1/swagger.json` when the server is next up |
| C-10 | **18 hardening items are open** — [issues.md](../../dev/post-phase/hardening/issues.md), 18 of 18 checkboxes unticked, last touched 2026-07-17 | **Fourteen UI phases, two manual-testing iterations, a Telegram integration and a deploy commit ran afterwards** (`12ce7fa``96b57eb`, 07-20 → 07-28) without ticking any box | 4 | open — Phase 4 must re-verify each item against code, not trust the checkbox |
| C-12 | **The skill is current** | **It is exactly one iteration behind the code.** SKILL.md's last commit is `baa3cc6` ("manual improvement 1"); `client/CLAUDE.md`'s is `e6a8f93` ("manual improvement 2"), which changed **44 files, +3419/2449** under `client/src`. Anything iteration 2 changed is absent from the skill | 7 | **partly resolved by phase 1** — the design-language half is corrected (see R-2); phase 7 still owns the skill's own workflow/procedure content |
| C-13 | **`dev/` is "the plan for building Balinyaar"**, written in the imperative — [dev/README.md:3](../../dev/README.md) | **It is a record of work already done.** `dev/phases/` last touched 2026-06-28; the code it describes shipped weeks ago | 6 | open — resolved by the archive banner, not by editing 199 files |
| C-14 | **`temp/swagger.json` is a stale committed duplicate** — [_plan/README.md](README.md) diagnosis table | **It is not committed at all**`.gitignore:1` ignores `temp`, and `git ls-files temp/` is empty. It is local clutter, not repo content | 0 | see [§ Corrections](#corrections-to-the-seeded-list) |
| C-15 | **`ui-phase-11-report.md:235` records "REQ-061…064 appended"** to the frontend request ledger | **REQ-061 was never appended.** The ledger goes 060 → 062; **ten live client files** cite REQ-061 for the admin user directory (`admin_users/search`, `admin_users/lookup`), which are 2 of the 24 phantom endpoints | 4 | **found by phase 2** — phase 4 must *file* it, not assume it is tracked |
| C-16 | **Five REQs' filed status** — REQ-050, REQ-063, REQ-066, REQ-067 (open, as filed) and the reasons `admin/constants.ts` gives for the admin mock | **All five are wrong or narrower than filed**, checked against the live swagger rather than against another document. `variantLabel` already ships; `tickets/close`+`reopen` already ship; `search/nurses` and `nurses/{id}/profile` are already anonymous; REQ-029/030 already shipped | 4 | **found by phase 2** — the table is in [phase-2's handoff § B](phase-2-integration.md) |
---
@@ -52,14 +51,27 @@ at `dev/contracts/openapi/swagger.v1.json`:
**Schema definitions changed (1)**
- `GeneratePayoutBatchCommand`
- `GeneratePayoutBatchCommand` — gained `systemInitiated: boolean` (**C-7**, resolved)
> **This is Phase 2's worklist for the machine contract, and it is two lines long.** The 16-day gap
> between the snapshot and the code produced almost no wire-level drift. Phase 2 should therefore spend
> its budget on the **hand-written** `dev/contracts/domains/*.md` — 17 files, frozen the same day, never
> mechanically checked against anything — rather than on the JSON. See
> **This was Phase 2's worklist for the machine contract, and it was two lines long.** The 16-day gap
> between the snapshot and the code produced almost no wire-level drift. Phase 2 therefore spent its
> budget on the **hand-written** contract docs instead. See
> [§ Corrections](#corrections-to-the-seeded-list) C-14b.
### What phase 2 found once it looked
Re-scoping was right, but the conclusion "the hand-written docs are where the drift is" turned out to be
only half true, and the more useful axis was a third one nobody had checked:
| Axis | Result |
| --- | --- |
| Old contract docs → live swagger, at **route** level | **Clean.** Every route named across the 17 files exists in the live API (the two apparent misses are prose counter-examples: "X, *not* Y") |
| Old contract docs → live swagger, at **convention** level | **Drifted.** Body casing ("typically snake_case" vs. proven camelCase), the server's local URL scheme, the envelope's field count (5 vs. 6 — `code` was added), and the enum vocabularies |
| **Client's real API clients → live swagger** | **24 phantom routes** — calls to endpoints the server does not expose, 2 of them reachable today. This axis had never been checked and is where the real gap was |
The third row is the phase's main finding, and it is in
[phase-2's handoff § A](phase-2-integration.md).
---
## Corrections to the seeded list
@@ -105,6 +117,11 @@ when phase 4 creates it** — that file does not exist yet, so this table is the
| # | Was | Decision | By |
| --- | --- | --- | --- |
| **C-11** | The frontend-designer skill and `client/CLAUDE.md` both claimed the design language, with no stated precedence | **Precedence is now stated in both directions.** The skill is the **design** contract (brand, tone, logo construction, the visual decisions, and the workflow for turning a design into a screen); [`docs/rules/client/`](../rules/client/) is the **engineering** contract and **wins on every overlap** — tokens, typography, the component library, shells, icons. SKILL.md's header carries the precedence statement plus a table pointing at the four files it defers to, and the overlapping detail was removed from the skill rather than duplicated. `client/CLAUDE.md` no longer restates design content at all. | phase 1 |
| **C-2** | The placeholder `SET_VIA_USER_SECRETS_OR_ENV` names a store that was removed, so its *name* instructs a reader to use a removed mechanism | **The name stays; the mechanism is documented authoritatively elsewhere.** The string is a load-bearing sentinel in **seven live files**`appsettings.json` (×6), `StartupSecretsGuard.PlaceholderMarkers`, `.githooks/pre-commit`, `.githooks/README.md`, `Baya.Test.Api/StartupSecretsGuardTests.cs` (×2), `docs/rules/shared/git-and-gates.md`, `docs/rules/server/structure.md`. Renaming it is a server-code + git-hook + test change needing `dotnet build`/`dotnet test` to prove the gate still fires — outside a documentation phase's scope. [config-matrix.md](../integration/config-matrix.md#the-placeholders-name) is now the single authoritative statement that **config lives in appsettings files and environment variables and `user-secrets` is not read**, and `DEPLOY.md` says the same. **The rename is filed for phase 4 with that exact seven-file worklist.** | phase 2 |
| **C-6** | `POST /api/v1/webhooks/payouts/{provider}` was absent from the frozen contract | **It exists and is now documented** — the payout transferor's reconciliation callback, anonymous, `webhook` rate-limit policy, in [domains/payouts.md](../integration/domains/payouts.md). | phase 2 |
| **C-7** | `GeneratePayoutBatchCommand` changed shape between the two snapshots, unexplained | **It gained `systemInitiated: boolean`** alongside `periodStart`/`periodEnd`. That is the refinement-phase-7 scheduler flag distinguishing a job-generated draft batch from an admin-generated one — which is what keeps the "generation may be automatic, *processing* is always an explicit admin action" rule auditable. Recorded in [domains/payouts.md](../integration/domains/payouts.md). | phase 2 |
| **C-8** | Two live contract files for one domain: a headerless 851-byte `messaging.md` silently amending the 10.6 K `messaging-notifications-admin.md` | **Merged and split three ways along the client's real domain boundaries** — tickets → [domains/tickets.md](../integration/domains/tickets.md), the notification feed → [domains/notifications.md](../integration/domains/notifications.md), config/holidays/audit/alerts → [domains/admin.md](../integration/domains/admin.md). Every REQ-028 amendment is folded in as current fact, not as a change log. Both old files carry a merged-into banner so neither reads as live; phase 6 archives them. | phase 2 |
| **C-9** | The OpenAPI folder claimed documents `v1` **and** `v1.1`; only `v1` was ever committed | **Both documents genuinely are registered** (`AddSwagger("v1","v1.1")` in `Program.cs`), so `/swagger/v1.1/swagger.json` is served — **and it contains zero paths.** `ApiVersionDocumentProcessor` drops every path whose URL lacks the document's version segment, and all **55 controllers are `[ApiVersion("1")]`** on the route template `api/v{version:apiVersion}/…`. The old claim was literally true and substantively empty. `v1` is the contract; only `v1` is worth committing. **Resolved by reading the code — no server boot needed, so the `UNVERIFIED` marker is retired.** | phase 2 |
### Corrections landed by phase 1 that were not on the seeded list
+133 -2
View File
@@ -145,5 +145,136 @@ file, and a deploy question is answered by `topology.md` + `config-matrix.md` wi
## Handoff
_(filled in by the agent that runs this phase — especially: the drift list, since Phase 4 turns the
`phantom` and `drifted` rows into backlog items)_
> 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](../integration/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](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.