# Plan: Deactivate 27 of 40 YSP endpoints (active-list gate)

## Context
The YSP simulator implements 40 card APIs, but only 13 are in scope: Create Card, Update Customer, Get Card Details, Block/Unblock, Card Activation, Card Replacement, Query Balance, Get X Transactions, Debit/Credit, Fund Transfer, Card Order, Card Order Status, Status Request. Instead of deleting the other 27, gate them behind an "active" list so they can be re-enabled. Handlers and their tests stay in the repo.

Decisions made with the user:
- Branch from `task/ysp-card-postman-flow` (name e.g. `task/deactivate-ysp-endpoints`). Nothing from the dirty `failure-controls` tree is carried over.
- Approach: active-list gate plus a git tag.
- Load Card (`accountfunding`) is NOT in the 13, so it becomes inactive.

**Design rule: prefer the simplest design.** No environment variables, parsing, modes or extra settings unless something needs them. When two designs work, pick the one with less code and fewer moving parts.

This is a request from the boss, not an independent stretch goal, and it will go through a PR. Keep the diff small and listed (CLAUDE.md constraint).

Known caveat (user was told, chose to proceed): postman-flow lacks the 16 failure-controls commits (`fail_next` modes, `Reconcile`, unique constraint, `reuses_ref?`). Expect conflicts in `router.ex`, `store.ex`, `card_api.ex`, `YspCard.Endpoints` and the failure tests when failure-controls merges. Keep this diff additive to limit them. "Rework load safety" is mostly moot on this base, because the load-twice and dropped-response tests live only on failure-controls. When failure-controls merges, its load tests need the `:inactive` tag, and its `@card_failable` list needs `accountfunding` handled.

## Endpoint lists (verified against postman-flow's router: 40 routes, all 13 keepers present)

Active (13), path under `/naradaapiservices/api/v1/`:
`customerregistration` (Create Card), `customerupdate`, `carddetails`, `cardlockunlock` (Block/Unblock), `cardactivation`, `cardreplacement`, `cardbalance` (Query Balance), `transactiondetails` (Get X Transactions), `debitcreditaccount`, `fundtransfer`, `cardorder`, `cardorderstatus`, `statusrequest`.

Inactive (27):
`accessrights`, `accountaddition`, `accountclosure`, `accountfunding` (Load Card), `alertsettings`, `amountrefund`, `assignoverridesettings`, `atmpinchange`, `atmpinset`, `atmpinverification`, `cardnumberdetail`, `cardreceipt`, `changepassword`, `cscenquiry`, `exchangerate`, `getlimit`, `ivrpinchange`, `ivrpinset`, `ivrpinvalidation`, `newotprequest`, `otpvalidation`, `pinretrieval`, `resetwebpassword`, `retrievewebprofile`, `setlimit`, `stockdetails`, `updatestockdetails`.

Not card endpoints, never gated: `/mercuryapi/notify_payment` and the two merchant-registration routes.

Watch for dependencies: Debit/Credit and Fund Transfer read exchange rates that `exchangerate` writes, and may use products from `accountaddition`. Confirm the seed data covers the keepers before relying on the gate alone in tests. `get_transactions_test` loads cards through `accountfunding`.

## Steps
1. [DONE] **Safety net.** On postman-flow, tag the current commit `ysp-simulator-all-40-endpoints` (local tag only, no push). Annotated tag on `8a961ba` (postman-flow tip). To get the old code back: `git checkout ysp-simulator-all-40-endpoints`.
2. **Simulator gate, in atomic commits.** Each commit leaves `cd ysp-simulator && mix test` green, and each one is also a place to stop. Handlers and `router.ex` routes stay unchanged; the gate is one place. Run the suite before each commit and stage files by explicit path.
   - 2.1 [DONE] **`ActiveEndpoints` module, not wired in.** New `ysp-simulator/lib/ysp_simulator/active_endpoints.ex`: the 13 path names and `active?/1`, nothing else (no `default/0`, no parsing, no environment variable). New `test/ysp_simulator/active_endpoints_test.exs`: the 13 are active, a few of the 27 are not, unknown names and non-strings are not. No behaviour change. Commit: "Simulator: add ActiveEndpoints (13 of 40, not wired in yet)".
   - 2.2 [DONE] **The gate and its tests.** One on/off setting: `config :ysp_simulator, gate_endpoints: true` in `config/config.exs` (so `mix run` serves only the 13) and `gate_endpoints: false` in `config/test.exs` (so the existing 44 test files keep passing untouched). In `ysp-simulator/lib/ysp_simulator/card_api.ex` `run/4`, when the setting is on, take the endpoint name from the last segment of `conn.path_info` and check `ActiveEndpoints.active?/1` before validation. If inactive, send HTTP 404 with `{"error": "endpoint_not_enabled", "message": "... not enabled in this simulator"}` (confirmed by the user). No invented YSP response code, and do not claim the txnRefNo. `notify_payment` and merchant registration are not gated. To switch endpoints back on, add the name to the list in `active_endpoints.ex`. New `test/ysp_simulator/endpoint_gate_test.exs` turns the setting on with `Application.put_env` and restores it on exit: an inactive endpoint gives 404, the same txnRefNo is still usable afterwards, each of the 13 does not give 404, and `notify_payment` is unaffected. Tests here use `async: false`. Commit: "Simulator: return 404 for endpoints that are not active".
   - 2.3 [DONE] **Behaviour docs.** `ysp-simulator/docs/behaviour/` (CLAUDE.md requires it for any behaviour change), the endpoint table in `README.md` (Active / Inactive column), (`docs/simulator-routes.md` was left alone: it only covers `/_sim/*` routes, and the gate is not one.) Commit: "Simulator docs: active and inactive endpoints".
   - 2.4 [DONE] **`demo.sh`.** It calls all 40 endpoints, including Load Card (4.8), whose txnRefNo `DEMO08` the Status Request step refers to, and the PIN, OTP and limit flows (around lines 112-125). Those are now inactive. Drop those steps so the demo only calls the 13 (Status Request needs a txnRefNo from an active call, for example a Debit/Credit). Run it against a live simulator. Commit: "Simulator demo.sh: only the 13 active endpoints".
3. (Folded into step 2: no tagging, and the gate tests live in 2.2.)
4. **Middle layer mirror, in atomic commits.** The middle layer (`DaProductApp.YspCard.Endpoints`) mirrors the 13/27 split, so `/api/card/*` refuses the 27 itself and never calls YSP. Simplest design: keep the 40 entries and add one `@inactive` list; do not delete entries, and do not touch `YspCard.call/2`, so code and tests that call it directly keep working. Run the tests before each commit and stage by explicit path.
   - 4.0 [DONE 2026-11-12] **Baseline (no commit unless something tracked changes).** RESULT: 231 tests, 0 failures when the integration tests are included; 128 run by default (103 are tagged `:integration` and skipped). Commands, from the repo root: (a) default: `MQTT_CLIENT_ID=rohan_deactivate_baseline MIX_TEST_PARTITION=_calllog mix test <the YSP test files>`; the private database `da_product_app_test_calllog` already records every migration, so `ecto.migrate` does nothing. (b) with integration: first start a full simulator on 4181, `cd ysp-simulator && YSP_SIM_PORT=4181 mix run --no-halt -e 'Application.put_env(:ysp_simulator, :gate_endpoints, false)'` (the `-e` turns the gate off, because these tests call all 40 endpoints), then add `YSP_CARD_BASE_URL=http://localhost:4181` and `--include integration`. WARNING: the integration tests call `/_sim/reset`, and `YSP_CARD_BASE_URL` defaults to `http://localhost:4180`, the shared service. Never run `--include integration` without that variable set. Original text: Middle-layer `mix test` failed earlier on a test-database migration error (`transaction_operations` foreign key, plus the duplicate `20250707` migrations). Find a way to run the YSP tests (SESSION_NOTES: `MIX_TEST_PARTITION=_calllog` with its own database and `MQTT_CLIENT_ID=<your own id>`), run `test/da_product_app/ysp_card*`, `test/da_product_app_web/*ysp*` and `controllers/ysp_card_*`, and write down how many pass. Without a green baseline nothing below can be trusted.
   - 4.1 [DONE 2026-11-12] **`Endpoints` knows what is inactive.** Done as: `all/0` and `names/0` = 13, new `inactive/0` (27) and `inactive?/1`, `fetch/1` finds all 40. The Postman builder got a temporary `every_name/0` so the committed collection stayed valid; 4.4 must switch it back to `Endpoints.names()` when it drops the 27 requests. `Request.actions/0` now lists 13. Add the `@inactive` list (the 27 names; `load` is the middle layer's name for `accountfunding`; `balance` and `block` stay active as `cardbalance` and `cardlockunlock`), `inactive?/1`, and make `all/0` and `names/0` return only the 13. `fetch/1` still finds all 40, so the controller can tell "switched off" from "unknown". Update `test/da_product_app/ysp_card/endpoints_test.exs` (it asserts `length == 40` on line 10 and checks the simulator's 40 routes on line 260). Commit: "YspCard.Endpoints: 13 active, 27 inactive".
   - 4.2 [DONE 2026-11-12] **The controller refuses inactive endpoints.** Done for balance, load, block and proxy through one helper `if_active/3`; `ysp_card_routes_test.exs` needed no change (routing only). In `YspCardController`, `proxy/2` and the `load` action answer 404 `{"error": "endpoint_not_enabled", "message": "<name> is not enabled in Bank Bridge."}` (same error name as the simulator) before validating or forwarding. Keep the `/load` route; do not delete it. Fix `test/da_product_app_web/controllers/ysp_card_controller_test.exs`, `ysp_card_proxy_test.exs` and `test/da_product_app_web/ysp_card_routes_test.exs` (the load tests now expect the 404). Commit: "YspCardController: 404 for inactive endpoints".
   - 4.3 [DONE 2026-11-12] **Fix the remaining tests.** RESULT: no test needed rewriting. With the integration tests included, all 237 YSP tests pass against a simulator with the gate off. Against a normally started simulator (gate on) 64 of the 103 integration tests fail with the 404, almost all being tests of the 27 inactive endpoints (a few of the 13 depend on them for setup: debitcreditaccount converted at a saved rate, transactiondetails and statusrequest, cardorder tests that use cardreceipt). Those tests check the whole table against a full simulator, so they stay; the commit fixes the instructions (moduledoc and failure messages) to say: private simulator, gate off, YSP_CARD_BASE_URL set. Original text: (Integration tests that call inactive endpoints through `YspCard.call/2` still need a simulator with the gate off, see 4.0(b); the tests that go through the controller now expect the 404.) `ysp_card_sweep_integration_test.exs` iterates `all/0`, so it shrinks by itself. The batch tests (`ysp_card_batch_a` to `d`), `ysp_card_integration_test.exs` and `ysp_pin_block_test.exs` may go through the controller for endpoints that are now off. Where a test fails, call `YspCard.call/2` directly instead of the controller, or drop the test if it only exercised an inactive endpoint. One commit per file that needs it, or one commit if only a few do. Commit: "Tests: stop going through the controller for inactive endpoints".
   - 4.4 [DONE 2026-11-12, chain verified live in 4.6] **Postman collection and README tables.** Done as: 18 requests (0, 1, A0-A5, B1-B4, C1-C5, 4-7). A0 is a debitcreditaccount credit (feeds A1 status request and A3 transactions); replay shown by C2/C3. Top-level names 0 and 7 kept for the script on failure-controls. The collection variable `simUrl` still defaults to the shared simulator's port: request 0 resets it, so change the variable before running the collection. Original text: `docs/postman/builder.exs` (901 lines) checks that the endpoints it groups match `Endpoints.names()` (around lines 90 and 871), so remove the 27 inactive requests from its groups. Regenerate with `mix run --no-start docs/postman/generate.exs` (never hand-edit the JSON). `test/da_product_app/ysp_card/postman_collection_test.exs` fails if the committed files are stale, and checks the README against `Endpoints.names()`. Commit: "Postman collection and README: 13 active endpoints".
   - 4.5 [DONE 2026-11-12] **Docs.** Done: notice at the top of `ysp-simulator/docs/endpoints.md` with the 13 section numbers (4.1-4.6, 4.9, 4.12, 4.13, 4.14, 4.16, 4.20, 4.30). No other doc claimed 40 live endpoints. Simulator `docs/endpoints.md` gets a short notice that only 13 are active and the rest answer 404. This replaces the old step 5. Commit: "Simulator docs: endpoints.md notes the 13 active endpoints".
   - 4.6 [DONE 2026-11-12] **End-to-end check (no commit).** RESULT: Newman (cached in ~/.npm/_npx, run offline) against a private simulator (gate ON) and the middle layer on dev port 4006 with its own MQTT id: 21 requests, 26 assertions, 0 failures, so A0 feeds A1 and A3 correctly. Manually: `/api/card/load` and `/api/card/getlimit` answer 404 endpoint_not_enabled and the simulator's request log showed only the balance call (the middle layer refused before sending); the simulator itself answers 404 for an inactive endpoint called directly. The shared simulator was never contacted (`simUrl` overridden to the private port). Original text: Simulator on port 4181 (`YSP_SIM_PORT=4181 mix run --no-halt` in `ysp-simulator/`; never touch the shared 4180), the middle layer with `YSP_CARD_BASE_URL=http://localhost:4181`, your own `MQTT_CLIENT_ID`, on dev port 4006, then run the regenerated Postman collection with Newman if it is installed. All 13 return normally; an inactive name returns the 404 body from the middle layer without reaching the simulator.
   - Not on this branch: the simulator Postman collection (`ysp-simulator.postman_collection.json`, `make_simulator_collection.py`) is committed on `task/ysp-card-failure-controls`, not on postman-flow. Regenerate it there when the branches meet.
5. (Folded into 4.5.)
6. **Simplification: out of this PR.** The boss asked for a reduction, so a table-driven `router.ex` rewrite is scope creep. Leave the router blocks alone. `Store`, `pins.ex`, `limits.ex` and `ResponseCodes` entries stay too, since inactive endpoints still use them. Revisit as a separate PR if wanted.
7. **Commits.** One for the gate and tests, one for the middle-layer mirror, one for docs/Postman. Commit per the standing authorisation. No push.
8. **PR (user pushes / opens when ready).** This is requested work from the boss, so the PR is small and reviewable. Base it on `task/ysp-card-postman-flow`, which has open PR #30 into `task/add_ysp-simulator`. A PR into `main` would include all of postman-flow's commits. The description lists the 13 active endpoints and the 27 now inactive, how to re-enable them (add the name to `active_endpoints.ex`, or `git checkout` the `ysp-simulator-all-40-endpoints` tag), and how to run the tests. If asked, draft the PR text and end it with the attribution line from the session reminder.

## Parked: store the request and response logs (separate branch and PR, later)
The big boss asked why the request and response bodies cannot be seen, and expected it to exist already. Facts (checked 2026-11-12):
- On this branch (postman-flow) the middle layer logs only errors, with no bodies (four `Logger.error` lines in `YspCardController`).
- `task/ysp-card-failure-controls` already has the DB call log `ysp_api_calls` with `request_body` and `response_body`, plus the unique constraint and `Reconcile`. It is not merged, and there is no viewer. So the first thing to find out is whether the complaint is "it doesn't exist" or "it exists but isn't merged or visible". That decides the size of the work.
- The simulator already keeps the last 200 requests and responses at `GET /_sim/requests`.
- Branch from `task/ysp-card-failure-controls`, not from this branch. The CLAUDE.md "Sprint 4 polish" item (a read-only list of `ysp_api_calls` filterable by `txnRefNo` or correlation id) is probably what is being asked for.
- Masking comes first: bodies are stored in plain text (PIN, CVV, OTP, `cardNumber`). With the 27 endpoints off, the remaining sensitive fields are mostly `cardNumber` and `activationCode`. Confirm the approach with the lead first (CLAUDE.md open item).
- Keep it simple (see the design rule above).

## Verification
- `cd ysp-simulator && mix test` is green with the default exclusion. `mix test --include inactive` is also green.
- Root `mix test` (note the duplicate-20250707-migration issue in memory; it may need the user's fix) passes with the new Endpoints expectations.
- End to end: `mix run --no-halt` in `ysp-simulator/` (port 4180). Run the middle layer with `YSP_CARD_BASE_URL=http://localhost:4180` and your own `MQTT_CLIENT_ID`. Run the regenerated Postman collection. All 13 return normally, and the rest give the inactive response. Check `curl` on one inactive endpoint directly (404 body) and adding the name to the list in `active_endpoints.ex` re-enables it.
- Remind the user afterwards: `/api/card/*` is unauthenticated, and PIN/CVV bodies are stored in plain text in `ysp_api_calls`. Both are open items before real YSP.
