ARCHITECTURE_ESSENTIALS.md
Quick-reference only. Full reasoning lives in ARCHITECTURE.md. Full product scope in PRD.md. If this file and ARCHITECTURE.md conflict, ARCHITECTURE.md wins and this file is stale — fix it in the same PR.
Stack (don't relitigate without an ADR in docs/adr/)
- Backend: Node.js + TypeScript + Express/Fastify — modular monolith, not microservices.
- DB: PostgreSQL via Prisma (
backend/prisma/schema.prisma= source of truth for models). - Auth: JWT access + refresh, roles =
patient | doctor | facility | admin. The refresh cookie isSameSite=Nonecross-site, so/auth/refresh+/auth/logoutrequire anX-CSRF-Tokenheader (a per-session token bound into the refresh JWT) and an allowlistedOrigin— seebackend/src/middleware/csrf.ts. CORS is an explicit per-environment allowlist (CORS_ORIGINS). - Frontend: React + TypeScript + Vite, React Query + Zustand.
- Video/voice, SMS/email, file storage: third-party, behind an interface — never build custom.
- Jobs: keep it simple (cron/interval) until a real job exists; don't pre-install a queue.
Hard rules (violating these = request changes on the PR)
- Never mutate a
PrescriptionorLabOrderafter issuance. Corrections = new row withsupersedes_*_id. - Availability booking must use a DB-level conditional update/transaction. No app-level "check then write" for slot booking — that's the #1 predicted break (double-booking).
- A
Referralsnapshots intake + notes at creation time. Never let it read live from the originalConsultation— later edits must not rewrite what a specialist already saw. - Cross-module access only through a module's service interface. Never import another module's
repositories/*directly. - Facilities write to their own
FulfillmentRecordonly — never directly toPrescription.status/LabOrder.status. - Every module =
routes/ controllers/ services/ repositories/. Keep that shape.
Data model map (see ARCHITECTURE.md §4 for full field lists)
User → PatientProfile | DoctorProfile | FacilityProfileDoctorProfile —< DoctorSpecialty >— SpecialtyAvailabilitySlot (doctor's calendar) → Appointment → IntakeForm, ConsultationConsultation → (outcome=referred) → Referral (snapshot!) → new AppointmentConsultation → Prescription(+PrescriptionItem) / LabOrder(+LabResult) FulfillmentRecord (facility-owned) references an order but doesn't mutate it. User → EmailVerificationToken (Phase 8, issue #34 — hashed/expiring, single-use; User.emailVerifiedAt gates booking + payment mutations, never login/browsing). LoginAttempt (Phase 8, issue #35 — Postgres-backed login throttle buckets, one per normalised email and per client IP; counters move only via a conditional upsert). FacilityProfile → WalletLink (payout address) ; PatientProfile/FacilityProfile → PaymentIntent (opaque orderType/orderId — see docs/adr/0004)
Payments (Phase 5.5 — see docs/adr/0004-custodial-payments-v1.md)
- Custodial v1:
docforum-coreholds its own Stellar payer + releaser identities (PLATFORM_PAYER_SECRET/PLATFORM_RELEASER_SECRETenv vars). Patients never hold or sign with a wallet — the only reason this is possible without violatingdocforum-web's "no direct Stellar calls" hard rule. WalletLinkis scoped to the facility (the on-chain payee), not the patient.PaymentIntent.orderType/orderIdare opaque, unvalidated —Prescription/LabOrderaren't real records yet (Phase 4). Don't build on top of these assuming they're FK-validated.release/refundare admin-triggered HTTP actions in v1 (POST /payments/intents/:id/{release,refund}) — there's noFulfillmentRecord.status == fulfilledto trigger off of yet. Don't treat this as the final design.- The actual Stellar contract calls go through
@docforum/escrow-sdk(backend/src/modules/payments/services/escrow-sdk-loader.ts— dynamicimport(), the SDK is ESM-only and this backend is CommonJS). Never reimplement contract-calling logic in this repo.
Known unresolved design gaps (do not silently "fix" without flagging in PR description)
- No routing rule yet for a referral with no specific target doctor (which specialist gets it?).
- No expiry/TTL job for held slots or pending referrals — currently just an enum value with no worker.
- Polymorphic
FulfillmentRecord.order_idis a sketch, not final — may need splitting into two tables. AuditEventfoundation exists (Phase 8, issue #43 — ARCHITECTURE.md §4.10): append-only (DB triggers), written byauditService.record(tx, …)inside the same transaction as the change. Wired for booking, doctor verification and payment fund/release/refund only — Phase 3/4 transitions must call it when they're built. Never put PHI inmetadata.- No email/SMS provider integrated yet (Phase 6, issue #17) — email verification (issue #34) therefore can't deliver its link on a hosted deployment;
backend/scripts/mark-email-verified.mjsis the interim operator path. - One doctor = one calendar assumed; multi-facility doctors not modeled.
Overengineering guardrails (things NOT to build yet)
- No custom video/WebRTC.
- No ML-based doctor-matching/recommendation.
- No configurable RBAC matrix — 4 fixed roles only.
- No chat delivery receipts/typing indicators — plain message log.
- No custom scheduling recurrence engine — fixed-length slot generation only.
- No microservice split, no separate CI/repos per module.
Where things live
- Backend modules:
backend/src/modules/<name>/{routes,controllers,services,repositories} - Data model source of truth:
backend/prisma/schema.prisma - Frontend:
frontend/src/{pages,components,features,hooks,services,store,types} - Roadmap / status:
ROADMAP.md— must be updated on every contribution that changes scope or completes work.