# ABDM Context

This file is the persistent handoff context for the ABDM / ABHA integration work in this repository.

Instruction for future updates:
- When the user says `update the abdm context`, update this file.
- Update this file after every 10 code changes related to ABDM / ABHA work.
- Preserve server ownership notes.
- Always mention which server each changed ABDM-related file belongs to.
- Preserve the paused point and revise it only when the work actually moves forward.

## 1. Purpose

This project currently contains code that is deployed across two different servers:

- **Lightsail server**
  - Hosts the ABDM-facing app.
  - Handles ABHA UI and all outbound ABDM API calls.
  - Must be the only server directly talking to ABDM because the main server is not in India.

- **Main server**
  - Hosts DocBox / HMIS-side APIs.
  - Owns internal patient mapping and DocBox database state.
  - Must not call ABDM APIs directly.

The codebase is still combined in one local workspace, which is why server ownership must always be stated clearly.

## 2. Critical architectural rules finalized

These points were discussed and are considered finalized unless explicitly changed later:

- All direct ABDM API calls must happen from the **Lightsail server**.
- The **main server** holds the internal DocBox database and internal patient/doctor identifiers.
- Internal DocBox identifiers like `patientId`, `doctorId`, raw visit ids, or raw Unix visit versions must **not** be exposed to ABDM.
- For M2 and onward, Lightsail should call the main server through signed internal APIs rather than browser-direct calls where possible.
- The user wants future ABDM work to remain compatible with later WASA/security review.
- For ABDM-related changes, always mention which server each changed file belongs to.

## 3. Current high-level state

- **M1** was reported by the user as functionally complete on deployed servers.
- M2 bridge plumbing and care-context document fetch are working between Lightsail and the main server.
- M2 HIP callback groundwork has now been implemented and tested from the Lightsail server.
- Current blocker is external to code: sandbox client `SBXID_018355` returns `403 / 900908 / API Subscription validation failed` on HIP-side M2 APIs.
- The user has submitted an ABDM support ticket and ABDM work is currently paused waiting for their reply.

## 4. Server ownership map

### 4.1 Lightsail server files

These are the ABDM-facing files and should be deployed on the **Lightsail server**.

#### Lightsail APIs

- `api/abdm_bootstrap.php`
- `api/abdm_abha_request_otp.php`
- `api/abdm_abha_enrol_by_aadhaar.php`
- `api/abdm_verify_abha_by_otp.php`
- `api/abdm_abha_unified_journey.php`
- `api/abdm_abha_address_suggest.php`
- `api/abdm_abha_address_create.php`
- `api/abdm_abha_card_download.php`
- `api/abdm_verify_facility_qr.php`
- `api/abdm_patient_register.php`
- `api/abdm_patient_register_unified.php`
- `api/abdm_internal_care_context_list.php`
- `api/abdm_internal_care_context_document.php`
- `api/abdm_consents_hip_notify.php`
- `api/abdm_m2_api_probe.php`

#### Lightsail pages

- `Public/abdm_abha_single_entry.php`
- `Public/abdm_patient_link_single_entry.php`
- `Public/abdm_abha_journey_start.php`
- `Public/abdm_abha_journey_result.php`
- `Public/abdm_abha_journey_existing_verify.php`
- `Public/abdm_abha_journey_new_address.php`
- `Public/abdm_abha_create_aadhaar.php`
- `Public/abdm_patient_registration.php`

#### Shared config also used on Lightsail

- `config/config.php`

### 4.2 Main server files

These are DocBox-side APIs and should be deployed on the **main server**.

- `api/abha_context_token_create.php`
- `api/abha_context_token_consume.php`
- `api/abha_context_token_status.php`
- `api/abha_context_token_unlink.php`
- `api/patient_abha_status.php`
- `api/internal_care_context_list.php`
- `api/internal_care_context_document.php`
- `api/internal_consent_hip_notify.php`
- `api/_bootstrap.php`
- `config/config.php`
- `lib/AbdmCareContextService.php`
- `lib/AbdmCareContextDocumentService.php`
- `lib/AbdmHipConsentNotificationService.php`
- `lib/AbdmVisitPayloadService.php`
- `lib/AbdmVisitFhirMapperService.php`
- `lib/AbdmPrescriptionFhirBundleService.php`

### 4.3 Main server launch point from Rectify test page

This is the main server script that launches the Lightsail flow:

- `js/patientInfo_oph_rectify_test_vs.js`

What it does:
- creates a handoff token using `api/abha_context_token_create.php` on the **main server**
- opens the Lightsail entry page in a new tab
- passes:
  - `contextToken`
  - `callbackUrl`

Current default Lightsail entry URL inside this JS:
- `https://YOUR-LIGHTSAIL-HOST/Public/abdm_abha_single_entry.php`

The user can also override it through:
- `localStorage.abhaLightsailFlowUrl`

## 5. Work completed in M1

### 5.1 ABHA single-entry UI redesign on Lightsail

The user requested a step-based UI resembling ABDM’s Aadhaar flow screenshots.
The following changes were implemented in the **Lightsail server** single-entry page:

- multi-step visual flow
- Aadhaar-based ABHA creation UX
- captcha removed
- operational notes section removed
- large Terms and Conditions block replaced with ABDM wording provided by user
- terms box constrained to roughly 5-6 visible lines with internal scroll
- step 1 compressed to better fit one screen
- floating auto-dismissing status alerts added
- alert redesigned to appear near current scroll position, non-transparent, fixed-width
- integration log removed from on-page UI
- more user-friendly explanatory copy used instead of developer-style messages
- scrolling to error messages added for actual errors only
- no scroll jump on successful responses

Primary file:
- **Lightsail server** `Public/abdm_abha_single_entry.php`

### 5.2 Aadhaar step behavior finalized

User decisions:
- no phone number entry on step 1 for Aadhaar flow
- OTP should be sent directly to the Aadhaar-linked mobile
- masked mobile should be shown only if present in ABDM response
- hard-coded temporary test Aadhaar value was intentionally kept for some time:
  - `581215556650`
- later issue noticed where it was not auto-filling consistently

Known response shape confirmed by user:
- Aadhaar OTP response may contain a message like:
  - `OTP sent to Aadhaar registered mobile number ending with ******4111`

### 5.3 Step 2 / existing ABHA verification path

The user wanted a UI like ABDM step 2 screenshot.
Current verification branch supports:

- known ABHA address entry
- verification mode dropdown
- `Mobile OTP`
- `Aadhaar OTP`
- `Verify ABHA`
- disabled verified card and continue buttons until verification succeeds

Important discussion:
- We confirmed that for current code, `Aadhaar OTP` mode in the existing ABHA verification branch does **not** ask for Aadhaar number.
- Backend currently sends ABHA address as `loginId` and uses `aadhaar-verify` scope.
- Therefore `Mobile OTP` is the safer and clearer path in the current implementation.
- Aadhaar OTP is not mandatory for existing ABHA verification in the current flow.

Relevant files:
- **Lightsail server** `Public/abdm_abha_single_entry.php`
- **Lightsail server** `api/abdm_verify_abha_by_otp.php`
- **Lightsail server** `lib/AbhaVerificationService.php`

### 5.4 Step 3 / communication details

User wanted UI similar to ABDM screenshot.
Decision:
- if ABDM does not provide a native email-addition flow, use local custom flow

### 5.5 Step 4 / ABHA address creation

User wanted:
- address creation view when address is not found
- keep old behavior if ABHA address already exists

### 5.6 Existing ABHA card / continue buttons

User required:
- `Download Verified ABHA Card` and `Continue to Patient Linking` disabled until verification is complete

This was implemented on the **Lightsail server** UI.

## 6. Patient linking and DocBox mapping work completed

### 6.1 Mapping rule finalized

User requirement:
- for every ABHA address, only one patient should be mapped

This rule is enforced in the DocBox-side mapping logic.

### 6.2 Handoff token approach

Because internal IDs should not be exposed directly in URL state, a handoff token mechanism was implemented.

Main-server APIs:
- `api/abha_context_token_create.php`
- `api/abha_context_token_consume.php`
- `api/abha_context_token_status.php`
- `api/abha_context_token_unlink.php`

Behavior:
- Rectify page requests a context token from main server
- Lightsail flow receives that token
- after successful ABHA verification / creation, Lightsail sends verified ABHA details back through callback API on main server

### 6.3 ABHA button on Rectify test page

The user asked to place an ABHA button in the Rectify test page button container and open the ABDM flow in a new tab.

Implemented in:
- **Main server** `js/patientInfo_oph_rectify_test_vs.js`

UI status behavior added:
- yellow exclamation mark on top-right of button if ABHA not linked
- green tick if ABHA linked
- tooltip with ABHA Number / ABHA Address when available

### 6.4 ABHA linked/unlinked state on Rectify test page

The user wanted:
- show whether ABHA is linked or not in Rectify test page
- show ABHA ID if found
- avoid ugly badge UI

Current approach:
- status dot on ABHA Flow button
- linked status is loaded from main server using:
  - `api/patient_abha_status.php`

### 6.5 Remove link / profile already linked behavior in Lightsail summary

User wanted in the Lightsail flow summary:
- `Link ABHA to Patient` button
- if already linked, disable button and show `Profile Already Linked`
- if linked, show `Remove Link`
- info button next to actions explaining behavior

Implemented on:
- **Lightsail server** `Public/abdm_abha_single_entry.php`

Behavior expectations finalized:
- after successful verification, link status should hydrate without full page reload
- remove-link should be red
- info helper should work on click, not hover only

### 6.6 Important DB note from user

User clarified:
- table `abdm_patient_abha_map` already exists on the **main server**
- all real SQL tables live on the main server, not on Lightsail

A key earlier issue was that mapping was accidentally using autogenerated local patient ids instead of DocBox internal ids.
This was discussed and corrected toward using DocBox-side handoff context values.

## 7. Copy and wording changes finalized

User requested:
- always show `DocBox` instead of `HMIS` in visible messages

This was applied to user-facing copy where relevant.

## 8. OTP resend / cooldown logic history

### 8.1 Original finalized behavior

User requested:
- resend cooldown = 60 seconds
- max resends = 2
- clear error message when resend limit is hit

This was implemented through:
- **Lightsail server** `config/config.php`
- **Lightsail server** `api/abdm_bootstrap.php`

### 8.2 Later bug discovered

User reported:
- receiving `429 Too Many Requests`
- message:
  - `You have already used the maximum 2 OTP resends for this attempt. Please restart the ABHA flow to request a fresh OTP.`
- even when OTP had not been requested recently
- and even on the first stage where OTP was being requested for the first time

### 8.3 Fixes implemented

#### Fix A: stale attempt auto-expiry

Implemented on the **Lightsail server**:
- `config/config.php`
- `api/abdm_bootstrap.php`

Finalized behavior:
- `otp_attempt_window_seconds = 1800`
- if last OTP attempt is older than 30 minutes, the old rate-limit row is deleted automatically
- a fresh attempt is then allowed

#### Fix B: stage 1 should always be a fresh attempt

User requirement:
- first stage of Aadhaar OTP request should not be blocked by old cooldown state
- if flow is on stage 1, it should reset the cooldown

Implemented on the **Lightsail server**:
- `api/abdm_abha_unified_journey.php`

Finalized behavior:
- in `request_aadhaar_otp`, the old OTP cycle is explicitly reset before rate-limit check
- therefore step 1 always starts fresh
- resend rules still apply after the new OTP request inside the current journey

## 9. File renaming work completed to reduce confusion

User requested:
- rename only the **Lightsail server** scripts to make server ownership clearer
- add `abdm_` prefix on Lightsail-side files
- leave **main server** scripts unchanged

Completed rename set on **Lightsail server**:

- `api/_bootstrap.php` -> `api/abdm_bootstrap.php`
- `api/abha_request_otp.php` -> `api/abdm_abha_request_otp.php`
- `api/abha_enrol_by_aadhaar.php` -> `api/abdm_abha_enrol_by_aadhaar.php`
- `api/verify_abha_by_otp.php` -> `api/abdm_verify_abha_by_otp.php`
- `api/abha_unified_journey.php` -> `api/abdm_abha_unified_journey.php`
- `api/abha_address_suggest.php` -> `api/abdm_abha_address_suggest.php`
- `api/abha_address_create.php` -> `api/abdm_abha_address_create.php`
- `api/abha_card_download.php` -> `api/abdm_abha_card_download.php`
- `api/verify_facility_qr.php` -> `api/abdm_verify_facility_qr.php`
- `api/patient_register.php` -> `api/abdm_patient_register.php`
- `api/patient_register_unified.php` -> `api/abdm_patient_register_unified.php`
- `Public/abha_single_entry.php` -> `Public/abdm_abha_single_entry.php`
- `Public/patient_link_single_entry.php` -> `Public/abdm_patient_link_single_entry.php`
- `Public/abha_journey_start.php` -> `Public/abdm_abha_journey_start.php`
- `Public/abha_journey_result.php` -> `Public/abdm_abha_journey_result.php`
- `Public/abha_journey_existing_verify.php` -> `Public/abdm_abha_journey_existing_verify.php`
- `Public/abha_journey_new_address.php` -> `Public/abdm_abha_journey_new_address.php`
- `Public/abha_create_aadhaar.php` -> `Public/abdm_abha_create_aadhaar.php`
- `Public/patient_registration.php` -> `Public/abdm_patient_registration.php`

Important note:
- `api/_bootstrap.php` was restored as a separate main-server file because main-server scripts still use that original name.
- `api/abdm_bootstrap.php` is the renamed Lightsail-side bootstrap.

### 9.1 Rectify launch URL update

After renaming Lightsail files, the main-server Rectify test script was updated so the default Lightsail entry URL now points to:

- `https://YOUR-LIGHTSAIL-HOST/Public/abdm_abha_single_entry.php`

File:
- **Main server** `js/patientInfo_oph_rectify_test_vs.js`

## 10. M2 planning and security work completed

### 10.1 M2 direction finalized

User clarified:
- earlier mandatory screenshot shared was actually M1, not M2
- M2 should move toward ABDM HIP / care-context style integration

User also confirmed:
- every patient visit has a `version`
- it is a Unix timestamp
- all OPD clinical data is linked by that version
- some billing/documents are not linked by that version

Recommended and accepted design:
- one visit version can act as one internal care-context unit
- but raw internal visit version / patient id / doctor id must not be sent directly to ABDM
- ABDM-safe care-context references should be generated later

### 10.2 M2 DB schema

User said the required M2 tables had been created in DB.
The tables discussed earlier for M2 included:

- `abdm_patient_care_context`
- `abdm_care_context_link_transaction`
- `abdm_callback_audit_log`
- `abdm_bridge_service_config`
- `abdm_internal_client`
- `abdm_patient_link_snapshot`
- `abdm_profile_share_log`

### 10.3 Secure bridge foundation implemented

Because the user wanted secure / certifiable architecture, the next agreed step was to secure Lightsail -> main server communication first.

Implemented in code:

#### Main server

- `config/config.php`
- `api/_bootstrap.php`
- `api/internal_care_context_list.php`

What was added:
- bridge config section
- HMAC request signing helpers
- request headers:
  - `X-DocBox-Client`
  - `X-Request-Id`
  - `X-Timestamp`
  - `X-Body-Sha256`
  - `X-Signature`
- timestamp drift checks
- replay detection using `abdm_txn_log`
- helper for signed server-to-server calls
- first secure internal API:
  - `api/internal_care_context_list.php`

Important nuance:
- these bridge helpers were added to the main-server bootstrap
- matching bridge config keys were also added to `config/config.php`

### 10.4 Secure bridge config keys finalized

Current config keys:
- `ABDM_BRIDGE_CLIENT_ID`
- `ABDM_BRIDGE_SHARED_SECRET`
- `ABDM_MAIN_SERVER_BASE_URL`
- `ABDM_BRIDGE_ALLOWED_DRIFT_SECONDS`
- `ABDM_BRIDGE_REPLAY_WINDOW_SECONDS`

## 11. Known open questions and not-yet-completed work

### 11.1 Linked patient shortcut flow

Important design discussion reached but not yet implemented:

User asked:
- if patient already has linked ABHA address, should the flow open directly at stage 2 instead of stage 1?

Final design direction agreed:
- do **not** jump to the Aadhaar creation step-2 blindly
- instead go directly into the **existing linked ABHA verification branch**
- prefill the known linked ABHA address from DocBox
- skip Aadhaar consent step for such patients

Recommended future implementation:

#### Main server

- `js/patientInfo_oph_rectify_test_vs.js`

Should pass additional launch state like:
- `launchMode=linked_abha`
- `abhaAddress=<linked_abha_address>`

#### Lightsail server

- `Public/abdm_abha_single_entry.php`

Should:
- detect linked launch mode
- prefill known ABHA address
- skip step 1
- land directly on existing ABHA verification screen

Status:
- **not implemented yet**

### 11.2 Verification mode simplification

Current state:
- existing ABHA verification dropdown still exposes both `Mobile OTP` and `Aadhaar OTP`

Discussion outcome:
- `Mobile OTP` is the safer path in current implementation
- Aadhaar OTP may remain confusing because no Aadhaar number is collected there

Status:
- **not yet simplified**

Possible future action:
- hide or remove `Aadhaar OTP` option from the existing-ABHA verification branch unless confirmed necessary

### 11.3 Full M2 care-context wiring

Secure bridge foundation exists, but not yet complete:

Partially done:
- Lightsail-side server call using signed bridge helper to fetch care contexts from main server

Still not done:
- ABDM-facing care-context linking transaction creation
- callback handling for M2 linking flows
- later profile/data sharing flow

New implementation completed on 2026-04-16:

#### Main server

- `api/internal_care_context_list.php`

Change:
- this API now accepts either:
  - `patientId`
  - or `contextToken`
- if `contextToken` is provided, it resolves the DocBox patient from `abdm_handoff_context`
- this keeps raw HMIS identifiers out of the browser for the first M2 bridge fetch

#### Lightsail server

- `api/abdm_internal_care_context_list.php`
- `Public/abdm_abha_single_entry.php`

Changes:
- added a new Lightsail-side proxy API:
  - `api/abdm_internal_care_context_list.php`
- this endpoint:
  - requires `contextToken`
  - uses `bridgeRequest(...)`
  - calls the main-server signed API:
    - `/api/internal_care_context_list.php`
- wired the new API into the ABDM step-3 UI on:
  - `Public/abdm_abha_single_entry.php`
- a new UI card was added:
  - `Available DocBox Care Contexts`
- it auto-loads when:
  - linked-ABHA shortcut launch is used
  - Aadhaar OTP continuation succeeds
  - existing ABHA OTP verification succeeds
  - the page loads with a handoff token present
- a `Refresh` button was added for manual reload
- explicit UI messaging was added so the card no longer disappears silently when token/bridge state is missing

Important testing outcome:
- after a hard reload, the new care-context card appeared correctly
- clicking `Refresh` returned:
  - `ABDM main-server bridge URL is not configured.`

This confirms:
- the Lightsail UI wiring is working
- the Lightsail proxy API is being called
- the next blocker is bridge configuration on the Lightsail server, not frontend behavior

Required config still pending on Lightsail:
- `ABDM_MAIN_SERVER_BASE_URL`
- value should be the public DocBox main-server base URL, for example:
  - `https://www.docbox.asia`

Also required for bridge auth to work:
- `ABDM_BRIDGE_CLIENT_ID`
- `ABDM_BRIDGE_SHARED_SECRET`

These must match the main-server bridge verification settings.

Status:
- **first M2 care-context fetch is implemented**
- **bridge config is still incomplete on deployed Lightsail**
- **full ABDM care-context linking/share flow is not yet implemented**

### 11.4 Care-context lifecycle behavior updated

The user updated the M2 care-context lifecycle rules on 2026-04-23:

- Create/update care contexts for **every saved visit**, regardless of whether the patient has an ABHA linked.
- This rule is only for **care-context creation**, not ABHA linking.
- Care-context linking still requires a linked ABHA and a valid ABDM link token.
- The main-server Rectify test-vs save flow creates/refreshes the care context after `Save Changes`.
- When ABHA is linked for a patient:
  - existing visits may still be backfilled/refreshed with the linked ABHA address.
- When a new or existing visit is saved later:
  - create the care context if missing
  - refresh the existing care context if the same patient + visit version already exists
- When ABHA is delinked:
  - hard delete that patient's care contexts for the linked ABHA
- When ABHA is relinked later:
  - create a completely fresh set of care contexts again

Important discussion outcome:
- old care contexts should not be retained for relink reuse
- user explicitly chose hard delete on delink instead of soft-retain/history

Server ownership:
- **Main server**: `js/patientInfo_oph_rectify_test_vs.js`, `api/internal_care_context_upsert.php`, `lib/AbdmCareContextService.php`
- **Lightsail server**: no care-context creation ownership; it only reads care contexts and executes ABDM-side link/data APIs.

### 11.5 Existing-visit care-context source finalized

The user clarified that the practical visit source is:
- table: `followUp`
- one usable visit row per version should be taken from:
  - `FieldName = 'Chief Complaint'`

Important nuance:
- `followUp` has multiple rows per visit version for different field names
- for M2 care-context creation, only the `Chief Complaint` row should be used
- this gives a single row per visit version in practice

Relevant source columns from `followUp`:
- `DoctorId`
- `PatientId`
- `Date`
- `Version`
- `FieldName`
- `FieldValue`

### 11.6 Actual target care-context table structure confirmed

The user provided the real main-server table columns for:
- `abdm_patient_care_context`

Confirmed columns:
- `id`
- `hmis_patient_id`
- `hmis_doctor_id`
- `visit_version`
- `care_context_reference`
- `care_context_display`
- `care_context_type`
- `abha_address`
- `link_status`
- `hip_link_txn_id`
- `link_token_ref`
- `metadata_json`
- `linked_at`
- `last_synced_at`
- `created_at`
- `updated_at`

Confirmed indexes:
- primary key on `id`
- unique key on `care_context_reference`
- unique key on `(hmis_patient_id, visit_version)`
- supporting indexes on patient / doctor / abha / link_status

Important design implication:
- table schema allows only one care-context row per patient + visit version at a time
- this is acceptable because user chose hard delete on delink

### 11.7 Care-context reference security finalized

The user explicitly rejected any care-context reference derived from:
- internal patient id
- internal visit version

Final rule:
- `care_context_reference` must be random and stable
- do **not** use patient id or visit version inside the external reference

Final implementation direction used in code:
- `CC_` + random 16-byte hex

The UI on the Lightsail page was also updated so it no longer shows the internal visit version in the care-context card.

### 11.8 Link-time care-context backfill implemented

Implemented on the **main server**:

- `lib/AbdmCareContextService.php`
- `api/_bootstrap.php`
- `api/abha_context_token_consume.php`

What this now does:
- after ABHA link succeeds in `api/abha_context_token_consume.php`
- the code calls `AbdmCareContextService`
- it backfills care contexts for that linked patient's existing visits
- source:
  - `followUp`
  - only `FieldName = 'Chief Complaint'`
- newly created care contexts:
  - get random safe `care_context_reference`
  - get current `abha_address`
  - use display like `OPD Visit - <Date>`
  - set `care_context_type = 'OPD'`
  - set `link_status = 'pending'`

The API response now also includes:
- `careContextsBackfilled`

### 11.8.1 Visit-save care-context creation implemented

Implemented on the **main server**:

- `js/patientInfo_oph_rectify_test_vs.js`
- `api/internal_care_context_upsert.php`
- `lib/AbdmCareContextService.php`

Behavior:
- when the user clicks `Save Changes` for a visit in Rectify test-vs
- after `sajid/insertFollowUp.php` succeeds
- the JS calls `api/internal_care_context_upsert.php`
- the API creates or refreshes the row in `abdm_patient_care_context`
- matching key is `hmis_patient_id + visit_version`
- this runs for every patient, even if `abha_address` is empty

No Lightsail UI is involved in care-context creation.

### 11.8.2 No-Lightsail-UI linking orchestration started

Implemented on the **main server**:

- `api/abdm_care_context_link_flow.php`
- `js/patientInfo_oph_rectify_test_vs.js`
- `config/config.php`

Behavior after visit save:
- main-server page checks whether the patient has a linked ABHA
- if not linked, it notifies the user and stops
- if linked, it asks whether to link the saved care context
- all temporary user confirmations happen on the main-server page
- the main-server endpoint calls Lightsail backend APIs for ABDM operations:
  - `api/abdm_generate_link_token.php`
  - `api/abdm_link_care_context.php`
- the Lightsail UI page is not opened for this flow

Server ownership:
- **Main server**: prompts, orchestration, DB checks, care-context creation
- **Lightsail server**: ABDM token generation and care-context link API execution only

Current confirmed behavior as of 2026-04-23:
- visit save creates or refreshes the care context
- if the care context is already `linked`, no ABDM token/link flow is performed
- if the patient has no linked ABHA, the user is notified and no link flow is performed
- if the patient has linked ABHA, all temporary user prompts happen on the main-server page
- the Lightsail UI is not opened for the save-triggered care-context linking flow
- the main-server orchestration endpoint calls Lightsail backend APIs only:
  - **Lightsail server**: `api/abdm_generate_link_token.php`
  - **Lightsail server**: `api/abdm_link_care_context.php`
- prescription print prompt now waits until the ABDM post-save flow finishes or is skipped/cancelled

Duplicate-link fix:
- when ABDM returns `ABDM-9999 Duplicate HIP link request`
- **Lightsail server**: `api/abdm_link_care_context.php` treats it as already linked
- **Main server**: `api/internal_care_context_link_status_update.php` updates `abdm_patient_care_context.link_status = linked`
- fixed PDO placeholder issue in `api/internal_care_context_link_status_update.php` that previously caused `SQLSTATE[HY093]: Invalid parameter number`

Already-linked care-context update notification:
- when an already linked care context is refreshed after visit `Save Changes`
- the care context is updated locally first
- then ABDM/HIUs are notified using:
  - `POST /api/hiecm/hip/v3/link/context/notify`
- **Main server**:
  - `js/patientInfo_oph_rectify_test_vs.js` detects the returned `LINKED` care-context status after save
  - `api/abdm_care_context_link_flow.php` action `notify_context_update` validates local linked status and calls Lightsail
- **Lightsail server**:
  - `api/abdm_link_context_notify.php` sends the ABDM `link/context/notify` request
  - `notification.careContext.patientReference` must match the patient reference used during linking; for the current DocBox link payload this is the stable `CC_...` care-context reference, not the ABHA address
  - `notification.careContext.careContextReference` must also use the same stable linked `CC_...` care-context reference; do not autogenerate a new reference for update notify
- no Lightsail UI is opened for this update-notify path
- prescription print prompt still waits for this ABDM notification attempt to finish

Temporary callback debug logging:
- **Lightsail server**: `api/abdm_consents_hip_notify.php`
- writes exact callback payloads to:
  - `api/abdm_callback_debug_log.jsonl`
- currently in append mode for data-transfer debugging
- each callback entry has a separator field and blank line so multiple ABDM callbacks can be differentiated
- the endpoint still forwards callbacks to the main server persistence path

User-initiated discovery implemented:
- ABDM/PHR discovery requests still arrive on the single registered callback URL:
  - **Lightsail server**: `api/abdm_consents_hip_notify.php`
- discovery callbacks are detected when the callback route contains `discover`, has a `transactionId`, has patient search data, and is not already an `on-discover` response
- **Lightsail server**:
  - `api/abdm_consents_hip_notify.php` forwards discovery search data to the main server and posts ABDM `on-discover`
  - `api/abdm_discovery_on_discover.php` provides a direct/replayable endpoint for the same discovery response behavior
  - outbound ABDM API used:
    - `POST /api/hiecm/user-initiated-linking/v3/patient/care-context/on-discover`
- **Main server**:
  - `api/internal_discovery_care_contexts.php` is the signed bridge API used by Lightsail
  - `lib/AbdmDiscoveryService.php` implements the ABDM discovery matching algorithm
  - `lib/AbdmCareContextService.php` can backfill/sync care contexts for a patient without requiring an ABHA address, so discovery can return existing visit care contexts even for patients not already linked to ABHA
  - `api/_bootstrap.php` loads `AbdmDiscoveryService`
- discovery matching algorithm implemented:
  - first try ABHA address and return matching records immediately
  - ABHA-address match is only from `abdm_patient_abha_map.abha_address`
  - no `abdm_patient_care_context.abha_address` fallback is used for ABHA-address matching
  - if no ABHA address match, try verified mobile
  - if mobile is absent or does not match, try medical record number / registration number
  - then require gender match
  - then require year of birth within +/- 5 years
  - then require name to be phonetically similar
  - debug responses now also expose stage-wise filter counts:
    - `initialCandidateCount`
    - `afterGenderCount`
    - `afterYearCount`
    - `afterNameCount`
  - extra discovery caps were removed:
    - no `LIMIT 25` on phone search
    - no `LIMIT 25` on MR search
  - discovery facility scoping is now supported through:
    - table: `abdm_hip_facility_map`
    - callback header: `X-HIP-ID`
    - mapped DocBox field: `hmis_facility_id`
  - current implementation uses `hmis_facility_id` to scope discovery to one DocBox facility/hospital:
    - ABHA address search: `abdm_patient_abha_map.hmis_doctor_id`
    - mobile / MR search: `followUp.DoctorId`
    - returned care contexts: `abdm_patient_care_context.hmis_doctor_id`
  - testing-only callback short-circuit:
    - **Lightsail server** `api/abdm_consents_hip_notify.php`
    - header `X-DocBox-Test-Search-Only: 1`
    - runs real discovery search path but skips the outbound ABDM `on-discover` API call
    - returns only the exact `on-discover` request body that would be sent to ABDM
  - discovery response only includes:
    - patient display
    - care-context reference
    - care-context display
    - hi type
    - count
  - discovery response builder now aligns more strictly to the ABDM sample shape:
    - `patient.referenceNumber` uses patient-level reference preferring `OpdIpdNumber`, then `Patient Id`, then fallback care-context reference
    - `patient.hiType` is normalized to uppercase ABDM-style values such as `OPCONSULTATION`
    - each `careContexts[]` item now contains only:
      - `referenceNumber`
      - `display`
  - discovery failure response body now follows:
    - `transactionId`
    - `error.code = ABDM-1010`
    - `error.message = Patient not found`
    - `response.requestId`
- discovery response must not include clinical data such as complaints, findings, diagnosis, prescription, or documents

### 11.9 Delink behavior implemented

Implemented on the **main server**:
- `api/abha_context_token_unlink.php`

Implemented on the **Lightsail server**:
- `Public/abdm_abha_single_entry.php`

Final behavior:
- unlink now runs transactionally on the main server
- it hard-deletes care contexts from `abdm_patient_care_context` for:
  - current `hmis_patient_id`
  - current `abha_address`
- then deletes the ABHA mapping row
- main-server unlink response now includes:
  - `careContextsDeleted`

Lightsail UI behavior:
- after unlink succeeds, the loaded care-context card is cleared immediately
- user sees a message that care contexts were removed because the ABHA link was deleted

### 11.10 Immediate UI refresh after link/delink implemented

Implemented on the **Lightsail server**:
- `Public/abdm_abha_single_entry.php`

Behavior now:
- after clicking `Link ABHA to Patient`, care contexts are reloaded automatically
- after delink, the care-context card is cleared immediately

This means the user no longer needs manual refresh to see care-context changes after successful link/unlink operations.

### 11.11 User-friendly handoff-token expiry message implemented

Problem observed:
- after delink and relink within the same opened Lightsail tab, the old handoff token is no longer valid
- raw error shown:
  - `ABHA handoff token is invalid, expired, or already used.`

Implemented on the **Lightsail server**:
- `Public/abdm_abha_single_entry.php`

Behavior now:
- the raw backend message is normalized to:
  - `This ABHA linking session has expired after the previous link or delink action. Close this page and start the ABHA flow again from DocBox.`
- for this case, the floating alert also shows:
  - `Close This Tab`

Important browser note:
- `window.close()` only works reliably for a tab opened by script, which is normally true for this flow

### 11.12 M2 care-context fetch now working end-to-end

User confirmed final working state:
- care contexts load correctly in the Lightsail step-3 card
- bridge config and shared secret are now working
- link-time backfill and relink lifecycle are functioning

This means:
- the first secure M2 care-context fetch path is operational
- current remaining M2 work is no longer about bridge plumbing
- current remaining M2 work is about ongoing visit-triggered care-context creation and later ABDM linking/share APIs

### 11.13 HIP-side M2 consent callback groundwork implemented

New work completed after the earlier pause:

#### Main server

Implemented DocBox-side persistence for inbound HIP consent notifications.

Changed / added:
- **Main server** `api/_bootstrap.php`
- **Main server** `lib/AbdmHipConsentNotificationService.php`
- **Main server** `api/internal_consent_hip_notify.php`

What this does:
- accepts a signed bridge request from the Lightsail server
- persists raw HIP consent notification payloads into `abdm_txn_log`
- resolves `patientId` / `abhaAddress` from `care_context_reference` when possible
- stores the event under:
  - `flow_name = hip_consent_notify`

#### Lightsail server

Implemented ABDM-facing callback handling for HIP consent notifications.

Changed / added:
- **Lightsail server** `api/abdm_consents_hip_notify.php`

What this does:
- receives inbound HIP consent notification payloads
- forwards them to the main server through the signed bridge
- attempts acknowledgement back to ABDM gateway at:
  - `POST /v0.5/consents/hip/on-notify`

The acknowledgement payload was refined to match the documentation sample shape:
- `requestId`
- `timestamp`
- `acknowledgement.status = SUCCESS`
- `response.requestId`

### 11.14 HIP-side M2 probe endpoint implemented on Lightsail

To diagnose whether the blocker was URL/auth/payload/subscription, a dedicated M2 probe endpoint was added.

Changed / added:
- **Lightsail server** `api/abdm_m2_api_probe.php`

What this probe does:
- uses the active sandbox token on the Lightsail server
- calls selected HIP-side M2 APIs
- returns:
  - resolved `gatewayUrl`
  - sample payload used
  - request auth debug
  - status code
  - ABDM response body

### 11.15 Exact HIP-side M2 sandbox test results

Runtime client verified on Lightsail:
- `env = SBX`
- `xCmId = sbx`
- `abdmClientId = SBXID_018355`
- bearer authorization header is present
- token is being issued and is not expired at request time

The following HIP-side M2 APIs were tested from the **Lightsail server** and all failed with the same authorization response:

- `POST https://dev.abdm.gov.in/gateway/v0.5/consents/hip/on-notify`
- `POST https://dev.abdm.gov.in/gateway/v0.5/health-information/hip/on-request`
- `POST https://dev.abdm.gov.in/gateway/v0.5/health-information/notify`
- `POST https://dev.abdm.gov.in/gateway/v0.5/links/context/notify`

Common ABDM response:
- HTTP `403`
- code `900908`
- message `API Subscription validation failed`

Interpretation agreed during debugging:
- this is **not** a token-expiry issue
- this is **not** a missing bearer-header issue
- this is **not** the earlier wrong-URL issue anymore
- strongest current explanation is ABDM-side sandbox subscription / bridge / facility linkage for HIP-side M2 APIs is incomplete for client `SBXID_018355`

### 11.16 ABDM support ticket status

The user has already raised a support ticket with ABDM and included:
- failing HIP-side M2 API URLs
- sandbox client id `SBXID_018355`
- callback URL on Lightsail:
  - `http://13.233.244.166:8080/abdm-app/api/abdm_consents_hip_notify.php`
- request/transaction id details
- request headers and request body sample
- failing response:
  - `403 / 900908 / API Subscription validation failed`

## 12. Exact paused point

Work is currently paused here:

1. **M1 flow is functionally complete on deployed servers**
2. **Secure Lightsail <-> main-server bridge for M2 is working**
3. **Care-context fetch and FHIR document generation are working**
4. **HIP consent notification persistence is implemented**
5. **HIP-side M2 API probe endpoint is implemented**
6. **Sandbox client confirmed at runtime: `SBXID_018355`**
7. **Current blocker is external:**
   - HIP-side M2 ABDM APIs return `403 / 900908 / API Subscription validation failed`
8. **User has already submitted ABDM support ticket**
9. **Current pause point:**
   - wait for ABDM reply
10. **Immediate next step after ABDM replies:**
   - rerun the Lightsail M2 probe endpoint
11. **If ABDM enables access, next engineering step:**
   - implement / continue `health-information/hip/request` receive flow on Lightsail
12. **Later engineering step after that:**
   - fetch care-context documents from main server, package/encrypt them, push data, and notify ABDM transfer status

## 13. Operational cautions

- Always mention the server for every ABDM-related changed file.
- Do not rename main-server APIs unless explicitly requested.
- Do not expose internal patient/doctor ids to ABDM.
- When resuming ABDM work, check whether the deployed Lightsail file names already match the `abdm_` renamed versions.
- If `config/config.php` is ever renamed later, all `lib/*` classes that currently `require ../config/config.php` must be updated together.

## 14. Fast resume checklist for any future LLM

If resuming ABDM work, do this first:

1. Confirm whether the deployment already uses:
   - `Public/abdm_abha_single_entry.php`
   - `api/abdm_*` file names
2. Confirm whether the Lightsail server has:
   - updated `config/config.php`
   - updated `api/abdm_bootstrap.php`
   - updated `api/abdm_abha_unified_journey.php`
3. Confirm whether main server has:
   - `api/internal_care_context_list.php`
   - updated `api/_bootstrap.php`
   - updated `config/config.php`
4. Ask whether the next step should be:
   - wait for ABDM ticket reply
   - rerun `api/abdm_m2_api_probe.php` on Lightsail
   - continue `health-information/hip/request` only if sandbox access is enabled
