Ghasi e-Prescribing Gateway Service — User Stories
Service: ghasi-eprescribing-gateway-service Story prefix: EPRX-US Last updated: 2026-04-18
Stories
EPRX-US-001 — Deploy gateway with gepgw_* schema and Kong routes
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Gateway infrastructure deployed: Postgres schema, Kong routes, Keycloak clients |
| Epic link | EPRX-EPIC-01 |
| Status | Done |
| Priority | Must |
| Story points | 8 |
| Labels | service:eprescribing-gateway, type:infra, slice:S0 |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-001 |
| Legacy FR refs | FR-RX-001 |
| Dependencies | IDENT-US-001, TENANT-US-001 |
User story: As a platform engineer, when preparing for the first-party pilot, I want the gateway deployed with its Postgres schema, Kong routes, and Keycloak client registrations so that EHR and Pharmacy can begin sending requests.
Acceptance criteria (Gherkin):
- Given the service is deployed, when I call
GET /health/ready, then{ status: ok, db: ok, nats: ok }is returned. - Given Kong is configured, when I POST to
/v1/ghasi-e-prescribing-gateway/fhir/MedicationRequest, then the request routes to the service. - Given
gepgw_*migrations run, when I inspect the database, then all tables exist with RLS enabled.
Technical notes:
- Tables:
gepgw_medication_requests,gepgw_medication_dispenses,gepgw_subscriptions,gepgw_idempotency_records,gepgw_outbox. - Module entitlement key:
ehr.ghasi_eprescribing_gateway.
Definition of Done: Standard DoD applies. CI gate: tenant-isolation spec green.
EPRX-US-002 — Configure fhir-gateway interop proxy path
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | fhir-gateway proxies /fhir/R4/interop/ghasi-eprescribing/* to gateway |
| Epic link | EPRX-EPIC-01 |
| Status | Done |
| Priority | Must |
| Story points | 3 |
| Labels | service:eprescribing-gateway, type:infra, slice:S0 |
| Components | fhir-gateway, ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-001 |
| Legacy FR refs | — |
| Dependencies | EPRX-US-001, INTEROP-US-001 |
User story:
As orders-service (EHR backend), when I submit a MedicationRequest to the gateway, I want to use /fhir/R4/interop/ghasi-eprescribing/MedicationRequest so that centralized routing policy and audit apply consistently.
Acceptance criteria (Gherkin):
- Given fhir-gateway is configured with the interop routing table, when orders-service POSTs to
/fhir/R4/interop/ghasi-eprescribing/MedicationRequest, then the request is forwarded toe-prescribing-gatewayand the201response is returned. - Given pharmacy-service POSTs to
/fhir/R4/interop/ghasi-eprescribing/MedicationDispense, then same forwarding applies.
Definition of Done: Standard DoD applies. Integration test verifies proxy routing.
EPRX-US-003 — EHR creates MedicationRequest (idempotent, ETag, correlation ID)
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | EHR backend creates MR; gateway returns 201 with ETag and prescription business ID |
| Epic link | EPRX-EPIC-02 |
| Status | In Progress |
| Priority | Must |
| Story points | 8 |
| Labels | service:eprescribing-gateway, type:backend, type:api, slice:S1 |
| Components | ghasi-eprescribing-gateway-service, orders-service |
| FR references | FR-EPRX-002, FR-EPRX-004, FR-EPRX-006 |
| Legacy FR refs | FR-RX-001, FR-RX-004, FR-RX-006 |
| Dependencies | EPRX-US-001 |
User story: As orders-service (EHR backend), when a clinician activates a medication order, I want to POST a FHIR R4 MedicationRequest to the gateway so that the prescription is durably persisted with a prescription business ID and ETag for downstream pharmacy notification.
Acceptance criteria (Gherkin):
- Given a valid
ehr-backendJWT andIdempotency-Key, when I POST a conformant FHIR MR, then201is returned withETag,Location, andX-Prescription-Business-Idheaders. - Given the same
Idempotency-Keyand payload are retried, when I POST again, then201is returned with the original resource (no duplicate). - Given a
pharmacy-backendJWT, when I POST a MR, then403 FORBIDDEN_WRITE_PERSONAis returned. - Given a valid create, then
eprescribing.medication_request.created.v1is emitted to NATS within 5 s. - Given an audit log query, then the
eprescribing.mr.createdaudit record containstenantId,actorId,prescriptionBusinessId.
Technical notes:
- Persistence:
gepgw_medication_requests. - NATS subject:
eprescribing.medication_request.created.v1. - Golden FHIR fixture:
test/fixtures/fhir/medication-request-valid.json.
Definition of Done: Standard DoD applies. AC-RX-001 satisfied.
EPRX-US-004 — FHIR profile validation on MR (IG package)
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Gateway validates MR against tenant-pinned IG; returns OperationOutcome on failure |
| Epic link | EPRX-EPIC-02 |
| Status | In Progress |
| Priority | Must |
| Story points | 5 |
| Labels | service:eprescribing-gateway, type:backend, slice:S1, type:fhir |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-003 |
| Legacy FR refs | FR-RX-003 |
| Dependencies | EPRX-US-003 |
User story: As the gateway, when a MedicationRequest arrives, I want to validate it against the tenant's pinned IG profile so that only conformant prescriptions are persisted.
Acceptance criteria (Gherkin):
- Given a MR missing a required field (e.g.,
status), when I POST it, then422with FHIROperationOutcomecontaining actionable diagnostics is returned. - Given a valid MR per the pinned IG, when I POST it, then
201is returned. - Given the validator is unavailable, when I POST, then
503 VALIDATOR_UNAVAILABLEis returned (strict mode) or the request proceeds with a warning (permissive mode per tenant config).
Technical notes:
- Phase 1: Zod schema validation. Phase 2: HAPI FHIR validator.
- NFR-RX-004: 100% of golden FHIR fixtures pass validator in CI.
Definition of Done: Standard DoD applies. Golden fixtures CI gate green (NFR-RX-004).
EPRX-US-005 — ETag concurrency on MR update
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | MR update requires If-Match ETag; stale ETag returns 412 |
| Epic link | EPRX-EPIC-02 |
| Status | In Progress |
| Priority | Must |
| Story points | 5 |
| Labels | service:eprescribing-gateway, type:backend, slice:S1 |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-005 |
| Legacy FR refs | FR-RX-005 |
| Dependencies | EPRX-US-003 |
User story: As orders-service, when I need to update a prescription (e.g., dosage change), I want ETag-based concurrency enforcement so that I cannot overwrite a prescription that has already been updated by another process.
Acceptance criteria (Gherkin):
- Given a freshly fetched ETag, when I PUT the MR with
If-Match: <etag>, then200and a new ETag are returned. - Given a stale ETag (another actor updated in between), when I PUT, then
412 PRECONDITION_FAILEDwith current resource in body is returned. - Given a
PUTwithoutIf-Match, then428 PRECONDITION_REQUIREDis returned.
Technical notes:
- ETag =
W/"sha256(canonical_fhir_resource)". - Client recovery path documented in INTEGRATOR_GUIDE.md.
Definition of Done: Standard DoD applies. AC-RX-004 satisfied.
EPRX-US-006 — EHR cancels a prescription
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | EHR backend cancels MR; gateway updates status and notifies pharmacy |
| Epic link | EPRX-EPIC-02 |
| Status | To Do |
| Priority | Must |
| Story points | 3 |
| Labels | service:eprescribing-gateway, type:backend, slice:S1 |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-002 |
| Legacy FR refs | FR-RX-001 |
| Dependencies | EPRX-US-003, EPRX-US-009 |
User story:
As orders-service, when a clinician cancels an active prescription, I want to update the MR status to cancelled so that the pharmacy is notified and stops processing the prescription.
Acceptance criteria (Gherkin):
- Given an active MR, when I PATCH with
status: cancelledand validIf-Match, then200is returned and the pharmacy Subscription fires with the updated status. - Given pharmacy tries to dispense a cancelled MR, then gateway returns
422 PRESCRIPTION_CANCELLED.
Technical notes:
- Emits
eprescribing.medication_request.cancelled.v1.
Definition of Done: Standard DoD applies.
EPRX-US-007 — Pharmacy creates MedicationDispense
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Pharmacy backend records fulfillment; gateway validates and correlates to prescription |
| Epic link | EPRX-EPIC-03 |
| Status | In Progress |
| Priority | Must |
| Story points | 8 |
| Labels | service:eprescribing-gateway, type:backend, type:api, slice:S1 |
| Components | ghasi-eprescribing-gateway-service, pharmacy-service |
| FR references | FR-EPRX-007 |
| Legacy FR refs | FR-RX-002 |
| Dependencies | EPRX-US-003, EPRX-US-009 |
User story: As pharmacy-service, when a pharmacist dispenses a medication, I want to POST a FHIR MedicationDispense to the gateway so that the dispense is recorded, correlated to the prescription, and the EHR is notified.
Acceptance criteria (Gherkin):
- Given a valid
pharmacy-backendJWT and a knownMedicationRequestID, when I POST a conformant FHIR MD, then201is returned with ETag andX-Prescription-Business-Id. - Given an
ehr-backendJWT, when I POST a MD, then403 FORBIDDEN_WRITE_PERSONA. - Given a MD referencing an unknown MR, when I POST, then
422 PRESCRIPTION_NOT_FOUND. - Given a valid create, then
eprescribing.medication_dispense.created.v1is emitted and the EHR Subscription fires within SLO.
Technical notes:
- AC-RX-001 (full end-to-end) and AC-RX-003 (profile validation on MD) satisfied in this story.
Definition of Done: Standard DoD applies.
EPRX-US-008 — Partial fill support
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Pharmacy records partial dispense; gateway allows while tracking remaining quantity |
| Epic link | EPRX-EPIC-03 |
| Status | To Do |
| Priority | Should |
| Story points | 3 |
| Labels | service:eprescribing-gateway, type:backend, slice:S1 |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-008 |
| Legacy FR refs | — |
| Dependencies | EPRX-US-007 |
User story: As pharmacy-service, when stock allows only a partial fill, I want to record a partial MedicationDispense so that the remaining quantity is tracked and the prescriber is notified.
Acceptance criteria (Gherkin):
- Given an MR for 30 tablets, when I POST a MD with
quantity.value: 10, then201is returned withstatus: in-progresson the MD. - Given the partial fill notification fires to EHR, then it includes the dispensed quantity and remaining quantity.
Definition of Done: Standard DoD applies.
EPRX-US-009 — Register and receive Subscription notifications
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | EHR and Pharmacy register Subscriptions; receive signed HTTPS notifications |
| Epic link | EPRX-EPIC-04 |
| Status | In Progress |
| Priority | Must |
| Story points | 8 |
| Labels | service:eprescribing-gateway, type:backend, slice:S1 |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-009 |
| Legacy FR refs | FR-RX-009 |
| Dependencies | EPRX-US-003, EPRX-US-007 |
User story: As EHR/Pharmacy backends, when a prescription event occurs, I want to receive a signed HTTPS notification so that I can react in near-real-time without polling.
Acceptance criteria (Gherkin):
- Given a registered Subscription with a valid HTTPS endpoint, when a MR is created, then an HTTPS POST with
X-Ghasi-Signatureheader is delivered within 10 s (NFR-RX-002). - Given the Subscription fires, when the consumer verifies
HMAC-SHA256(payload, signingKey), then the signature matches. - Given a duplicate delivery (same
X-Ghasi-Delivery-Id), when the consumer deduplicates, then no duplicate side effects.
Technical notes:
- AC-RX-005 satisfied here.
- At-least-once delivery; consumer idempotency on
X-Ghasi-Delivery-Id.
Definition of Done: Standard DoD applies.
EPRX-US-010 — DLQ for failed Subscription deliveries
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Failed notifications enqueue in DLQ; observable and replayable |
| Epic link | EPRX-EPIC-04 |
| Status | In Progress |
| Priority | Must |
| Story points | 5 |
| Labels | service:eprescribing-gateway, type:backend, slice:S1 |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-010 |
| Legacy FR refs | FR-RX-010 |
| Dependencies | EPRX-US-009 |
User story: As an SRE, when a Subscription endpoint is unavailable, I want failed notifications to enter a DLQ so that I can observe, investigate, and replay them once the endpoint recovers.
Acceptance criteria (Gherkin):
- Given a Subscription endpoint returns
500on delivery, when 5 retry attempts exhaust, then the event enters DLQ andeprescribing.subscription.delivery_failed.v1is emitted. - Given a DLQ entry, when I call the admin replay endpoint, then the notification is re-delivered and marked success in DLQ.
Technical notes:
- DLQ observable in
eprescribing-gateway/outbox-subscriptionsdashboard.
Definition of Done: Standard DoD applies. AC-RX-005 DLQ path satisfied.
EPRX-US-011 — Three-party end-to-end correlation test
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | CI gate: EHR creates MR → pharmacy receives notification → pharmacy creates MD → EHR notified |
| Epic link | EPRX-EPIC-04 |
| Status | To Do |
| Priority | Must |
| Story points | 5 |
| Labels | service:eprescribing-gateway, type:test, slice:S1 |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-009, FR-EPRX-010 |
| Legacy FR refs | FR-RX-009 |
| Dependencies | EPRX-US-007, EPRX-US-009 |
User story: As an engineer, I want a contract test that proves the full three-party prescribe → dispense → notify correlation works end-to-end so that regressions are caught before production.
Acceptance criteria (Gherkin):
- Given EHR creates MR and Pharmacy creates MD, when E2E test runs, then both Subscription notifications fire and
prescriptionBusinessIdmatches across all events and audit records.
Technical notes:
- File:
test/e2e/three-party-correlation.e2e.spec.ts. AC-RX-006 satisfied.
Definition of Done: Standard DoD applies. Must be CI gate.
EPRX-US-012 — HIPAA-equivalent audit trail
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | All MR/MD mutations emit audit records with correlation ID |
| Epic link | EPRX-EPIC-05 |
| Status | In Progress |
| Priority | Must |
| Story points | 5 |
| Labels | service:eprescribing-gateway, type:compliance, slice:S0 |
| Components | ghasi-eprescribing-gateway-service, audit-service |
| FR references | FR-EPRX-011, FR-EPRX-012 |
| Legacy FR refs | FR-RX-011, FR-RX-012 |
| Dependencies | EPRX-US-003, EPRX-US-007 |
User story: As a compliance officer, when any prescription or dispense record is created, updated, or deleted, I want a complete audit record with actor, tenant, resource type/ID, action, and prescription business ID so that we satisfy HIPAA Security Rule equivalent audit requirements.
Acceptance criteria (Gherkin):
- Given a MR is created, when I query the audit service, then a record exists with
action: create,resourceType: MedicationRequest,tenantId,actorId,prescriptionBusinessId,timestamp. - Given a MR is cancelled, then audit record with
action: cancelexists. - Given an unauthorized access attempt (403 response), then
eprescribing.auth.forbiddenaudit record is emitted.
Technical notes:
- Security reviewer sign-off required; audit coverage automated test in CI.
Definition of Done: Standard DoD applies. Security reviewer sign-off.
EPRX-US-013 — Tenant isolation adversarial test
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Zero cross-tenant MR/MD leakage proven by CI adversarial test |
| Epic link | EPRX-EPIC-06 |
| Status | In Progress |
| Priority | Must |
| Story points | 3 |
| Labels | service:eprescribing-gateway, type:security, type:test, slice:S0 |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-013 |
| Legacy FR refs | FR-RX-008 |
| Dependencies | EPRX-US-001 |
User story: As a security engineer, I want to verify that a Tenant B JWT cannot access any Tenant A prescriptions so that cross-tenant PHI leakage is provably prevented.
Acceptance criteria (Gherkin):
- Given Tenant A has MR records, when Tenant B JWT searches, then zero results returned.
- Given Tenant B tries to GET a specific
mr_idfrom Tenant A, then404is returned (not403).
Technical notes:
tenant-isolation.integration.spec.ts— mandatory CI gate, never removable.
Definition of Done: Standard DoD applies. CI gate permanent.
EPRX-US-014 — Module entitlement gate
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Unlicensed tenants receive 403 MODULE_NOT_LICENSED on all gateway operations |
| Epic link | EPRX-EPIC-06 |
| Status | In Progress |
| Priority | Must |
| Story points | 2 |
| Labels | service:eprescribing-gateway, type:security, slice:S0 |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-013 |
| Legacy FR refs | — |
| Dependencies | IDENT-US-001 |
User story: As a platform operator, when a tenant has not licensed the e-prescribing gateway module, I want all operations blocked so that billing boundaries are enforced.
Acceptance criteria (Gherkin):
- Given tenant lacks
ehr.ghasi_eprescribing_gatewayentitlement, when any endpoint is called, then403 MODULE_NOT_LICENSED. - Given a licensed tenant, when the same endpoint is called with valid auth, then the operation proceeds normally.
Definition of Done: Standard DoD applies.
EPRX-US-015 — Task renewals and clarifications (P1)
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Pharmacy creates renewal Task; EHR approves/rejects via Task workflow |
| Epic link | EPRX-EPIC-07 |
| Status | To Do |
| Priority | Should |
| Story points | 8 |
| Labels | service:eprescribing-gateway, type:backend, slice:S2 |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-014 |
| Legacy FR refs | FR-RX-020 |
| Dependencies | EPRX-US-006, EPRX-US-007 |
User story: As pharmacy-service, when a patient needs a prescription renewal, I want to create a Task requesting renewal so that the prescriber can approve or reject it in the EHR without out-of-band communication.
Acceptance criteria (Gherkin):
- Given pharmacy creates a
Taskwithintent: orderandrequester: Organization/pharm_01, when EHR POSTs a Task status update toaccepted, then the Task status changes and both parties are notified. - Given EHR rejects the renewal, then Task status is
rejectedand pharmacy is notified.
Definition of Done: Standard DoD applies. FR-RX-020 satisfied.
EPRX-US-016 — Pharmacy directory search (P1)
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | EHR searches pharmacy Organizations and Endpoints for routing |
| Epic link | EPRX-EPIC-07 |
| Status | To Do |
| Priority | Should |
| Story points | 5 |
| Labels | service:eprescribing-gateway, type:api, slice:S2 |
| Components | ghasi-eprescribing-gateway-service, provider-directory-service |
| FR references | FR-EPRX-015 |
| Legacy FR refs | FR-RX-021 |
| Dependencies | EPRX-US-001, PROVDIR-US-001 |
User story: As an EHR clinician, when prescribing, I want to search the pharmacy directory to select the patient's preferred pharmacy so that the prescription is routed to the correct destination.
Acceptance criteria (Gherkin):
- Given pharmacy organizations are registered, when I GET
/fhir/Organization?name=Kabul+Pharmacy, then matching FHIR Organization resources are returned. - Given an Organization, when I GET linked Endpoints, then FHIR Endpoint resources with
connectionTypeandaddressare returned.
Definition of Done: Standard DoD applies. FR-RX-021 satisfied.
EPRX-US-017 — Bulk export for MoH reporting (P2 gated)
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Async bulk FHIR export of prescriptions for MoH/HMIS reporting |
| Epic link | EPRX-EPIC-07 |
| Status | To Do |
| Priority | Could |
| Story points | 13 |
| Labels | service:eprescribing-gateway, type:backend, slice:S3 |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-016 |
| Legacy FR refs | FR-RX-022 |
| Dependencies | EPRX-EPIC-07 |
User story: As a Ministry of Health analyst, when generating national medication use reports, I want to trigger a bulk FHIR export of prescription data so that HMIS/national indicators can be populated.
Acceptance criteria (Gherkin):
- Given
MoH-reportingrole andbulk_export:adminscope, when I POST/$export, then an async job is created; polling returns NDJSON download links when complete. - Given no authorization, when I POST
/$export, then403is returned.
Technical notes:
- Gated in
P2_GATED.md. Requires bulk hosting ADR and licensing decision.
Definition of Done: Standard DoD applies. Gated on ADR acceptance.
EPRX-US-018 — Third-party EMR onboarding (Phase 3)
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Non-Ghasi EMR registers B2B credentials and passes contract tests |
| Epic link | EPRX-EPIC-08 |
| Status | To Do |
| Priority | Should |
| Story points | 8 |
| Labels | service:eprescribing-gateway, type:infra, slice:S3 |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-017 |
| Legacy FR refs | — |
| Dependencies | EPRX-EPIC-07 |
User story: As a national health program manager, when onboarding a third-party EMR to the national e-prescribing gateway, I want a documented registration process with B2B client credentials and contract test harness so that the integration is safe and verifiable.
Acceptance criteria (Gherkin):
- Given a third-party EMR registers OAuth2 client credentials with
ehr-backendpersona scope, when it POSTs a valid MR, then201is returned with prescription business ID. - Given the contract test harness is run against the third-party client simulation, when all three-party golden fixtures pass, then onboarding is approved.
Definition of Done: Standard DoD applies. Partner harness documented.
EPRX-US-019 — External pharmacy onboarding (Phase 3)
| Field | Value |
|---|---|
| Issue type | Story |
| Summary | Non-Ghasi pharmacy registers and receives Subscription notifications |
| Epic link | EPRX-EPIC-08 |
| Status | To Do |
| Priority | Should |
| Story points | 5 |
| Labels | service:eprescribing-gateway, type:infra, slice:S3 |
| Components | ghasi-eprescribing-gateway-service |
| FR references | FR-EPRX-017 |
| Legacy FR refs | — |
| Dependencies | EPRX-US-018, EPRX-US-009 |
User story: As a national health program manager, when onboarding an external pharmacy to receive national prescriptions, I want the pharmacy to register its endpoint and receive signed notifications so that prescription routing works across organizational boundaries.
Acceptance criteria (Gherkin):
- Given an external pharmacy with
pharmacy-backendpersona, when it registers a Subscription and an EHR creates a prescription for that pharmacy, then the Subscription fires to the external endpoint within SLO.
Definition of Done: Standard DoD applies.