Platform governance and architecture audit
Document type: As-of-repository audit (specs + code + infra evidence)
Date: 2026-04-06
Audience: Platform architects, security/governance leads, service owners
Primary references: IMPLEMENTED_PLATFORM_ARCHITECTURE.md, governance-auth/README.md, baseline/ARCHITECTURE_BASELINE.md, module SPEC.md files under specs/modules/, _generation/SERVICE_BASELINE_AUDIT_TRACKER.md.
Limits: This document does not claim regulatory certification. It summarizes directional alignment and known gaps grounded in repository artifacts.
1. Executive summary
The Ghasi EHR platform separates identity transport (Keycloak OIDC/JWT), structural org scope (Hierarchy Service DAG), authorization (Access Policy Service), commercial/functional entitlements (Licensing Service), aggregated session configuration (Config Resolver Service), operational facility overlay (Facility Service + facility-management spec), clinical directory (Provider Directory Service), scheduling (Scheduling Service), and platform controls (Platform Admin Service). The baseline architecture requires tenant isolation (tenant_id on persisted data) and hierarchy-scoped operations (HierarchyNode DAG), not a flat facility-only model.
Strengths (evidence-based):
- Normative hierarchy model is DAG-capable with
contains/manages/refers-toedges and cycle prevention on structural moves (see §3.1). - Governance documentation in governance-auth/README.md describes end-to-end evaluation ordering and Kong exposure patterns.
- Kong production config exposes governance-related routes for
access-policy-service,licensing-service,platform-admin-service,config-resolver-service, and internal hierarchy paths (see §5). - Scheduling module wires
ModuleEntitlementGuardwith@ModuleKey('ehr.scheduling')on REST and FHIR controllers (verified inapps/services/scheduling-service).
Gaps and risks (must be managed explicitly):
- Two different “DAG” meanings (org graph vs config inheritance chain) are easy to conflate in conversation; both are documented here (§3–4).
- Cross-service identifiers (
facility_idas hierarchy node vs facilityLocation.id, practitionerprimary_location_id) require disciplined integration; FKs are not enforced across databases (baseline). - BFF permission fallback in
ehr-webwhen the gateway URL is unset returns broad demo permissions (§6.2). - National / cross-tenant supervision and federation are not first-class in the strict tenant model; see platform/AFG_DAG_IAM_GAP_ASSESSMENT.md.
- Config Resolver normative chain vs implemented traversal (org ancestors when
nodeIdis present) is tracked in config-resolver/THEMING_IMPLEMENTATION_GAPS.md.
2. DAG models (two distinct concepts)
2.1 Organisation / clinical scope DAG — Hierarchy Service
Specification: modules/hierarchy/SPEC.md
- HierarchyNode:
tenant_id,type(e.g.hospital,clinic,department,ward,virtual-clinic),metadataJSONB,profile_idforHierarchyProfilerules. - HierarchyEdge:
parent_node_id,child_node_id,relationship_type∈ {contains,manages,refers-to}. - DAG properties: The graph is a directed acyclic graph (not a strict tree). Multiple parents are allowed (e.g.
refers-to).containsis the primary edge for ownership and downward traversal (licensing inheritance, rollups). - HierarchyProfile: Constrains
allowed_node_types,allowed_root_types,allowed_relationships(e.g.AFG_MOPH,PRIVATE_HOSPITALin the spec’s profile table).
Implementation notes:
- DAG overlay is documented on the edge entity:
apps/services/hierarchy/src/entities/hierarchy-edge.entity.ts. - Cycle prevention:
HierarchyService.moveNoderejects moves that would place a node into its own subtree (apps/services/hierarchy/src/hierarchy.service.ts).
Worked example (spec-consistent): A district hospital node contains outpatient clinics and refers-to a regional specialist centre for shared services—two different edge semantics; traversal for “ownership” follows contains.
Out of scope in code (product gap): A first-class national MoH supervisory span across many legal tenants is called out as a gap in AFG_DAG_IAM_GAP_ASSESSMENT.md relative to strict tenant_id immutability.
2.2 Configuration inheritance DAG — Config Resolver Service
Specification: modules/config-resolver/SPEC.md, CONFIG_MODEL.md
The resolver answers: given user U at node N, module M, feature F, action A — is the action permitted, what UI is visible, which design tokens apply?
Normative inheritance chain (SPEC):
Global → Tenant → OrgNode → Module → Feature → Action → Role → User → UIScreen → UIComponent → UIElement → ActionBinding → DesignSystem
Implementation alignment: Org-aware token resolution and admin surfaces exist; remaining documentation and governance gaps are listed in THEMING_IMPLEMENTATION_GAPS.md, including:
- FR-CFG-DS-002 text vs behavior when
nodeIdis supplied (org ancestor chain merged into resolution). - Desire for
context_node_id/contextNodeIdin JWT or session store when the user selects a facility. - Precedence between Tenant Service branding fields and resolver design tokens.
3. Tenancy
3.1 Baseline (enforced rules)
From ARCHITECTURE_BASELINE.md §2:
- Every persisted entity carries
tenant_id; cross-tenant queries are forbidden. - Org scoping uses
HierarchyNode(DAG) via Hierarchy Service—not a standalone flat facility model as the sole structural authority. - Requests must carry
tenantIdfrom JWT and, where applicable,nodeId.
3.2 Tenant lifecycle (spec intent)
modules/tenant/SPEC.md defines tenant fields including root_node_id (set on activation), hierarchy_profile_id, subscription fields, and orchestration on activation (root hierarchy node, tenant admin user, default licenses)—see FR-TEN-002 in that spec.
Implementation: apps/services/tenant/src/entities/tenant.entity.ts includes rootNodeId, hierarchyProfileId, branding, and status-related fields.
3.3 Strategic posture (non-normative)
platform/TENANCY_DECISION_MATRIX.md recommends a hybrid: shared national/platform capabilities with tenant-isolated operational domains and a controlled federation layer for cross-tenant workflows. This is product strategy, not fully encoded as enforceable rules in every service.
4. Cross-service interactions, dependencies, and identifiers
4.1 Dependency direction (who consumes whom)
| Authority / producer | Typical consumers | Mechanism |
|---|---|---|
| Keycloak | All UIs and APIs | OIDC; JWT with sub, realm roles; user attributes (e.g. tenant) |
| IAM | Tenant activation, admin APIs, provisioning | REST; optional Keycloak Admin API; users table |
| Hierarchy | Access Policy, Licensing, Config Resolver, scheduling/facility by ID | REST /internal/hierarchy/* (Kong); node UUID is the structural anchor |
| Licensing | ModuleEntitlementGuard, Access Policy, Config Resolver | Module active for node with inherit-down along contains |
| Access Policy | BFF (/internal/access/context), services calling evaluate | /internal/access/* |
| Config Resolver | Shell/UI, aggregated effective config | /api/config, /internal/config, /admin/ui; internal calls to peer services per governance-auth/README.md |
| Platform Admin | Feature evaluation inside resolver flows | /api/platform, /internal/admin |
| Facility Service | Location metadata, resource catalog; optional link to hierarchy | Location.hierarchy_node_id |
| Provider Directory | Scheduling and clinical workflows | Practitioner, PractitionerRole |
| Scheduling | Appointments, slots | Appointment.facility_id, Appointment.provider_id |
4.2 End-to-end governance flow (canonical)
Aligned with governance-auth/README.md:
4.3 Request-time authorization stack
From governance-auth/README.md — practical ordering:
- Identity validation (JWT)
- Module entitlement (
moduleKey,nodeId) - Feature flag (
featureKey,tenantId) - Role / permission baseline
- Policy evaluate (
/internal/access/evaluate) for governed actions - Overrides and break-glass where applicable
- Audit evidence
4.4 Domain integration: organization → facility → ward → room → provider → scheduling
- Organization / network: Modeled as
HierarchyNodeinstances with types andHierarchyEdgerelationships per hierarchy SPEC. Profiles (e.g.AFG_MOPH) constrain valid roots and node types. - Facility (business): May correspond to a hospital/clinic node and/or a
LocationEntityrow in Facility Service.LocationEntity.hierarchy_node_idoptionally links a facility-service location to a hierarchy node (apps/services/facility-service/src/locations/entities/location.entity.ts). - Wards / rooms / beds: Structural placement is a hierarchy concern (node types such as ward/department); operational bed state and overlays are specified under facility-management SPEC (
FacilityNodeConfig,BedRecordonnodeId). Facility Service also modelsLocationTypeincludingWARD,ROOM,BEDwith parent pointers, capacity, timezone, locale, lat/long. - Provider: IAM holds login identity and optional
ProviderProfile; Provider Directory holdsPractitionerandPractitionerRole.PractitionerRole.facility_idis documented in code as a hierarchy node representing the physical location scope;Practitioner.primary_location_idis a logical FK tofacility-servicelocations.id(cross-DB). - Geo:
LocationEntityhaslatitude/longitude;PractitionerhaspracticeLatitude/practiceLongitude/practiceAddressfor practice location (maps, telehealth, search). Timezone/locale at site level align with FR-FAC-008 in the facility-management spec. - Scheduling:
Appointmentstorestenant_id,patient_id,facility_id,provider_id, times, status (apps/services/scheduling-service/src/appointments/entities/appointment.entity.ts). These are logical references to hierarchy and directory aggregates, not enforced foreign keys across databases.
Integration risk: The same English word “facility” may mean hierarchy node UUID (scheduling facility_id, practitioner role), locations.id (primary location), or business concept—teams should publish a short ID glossary per deployment.
5. Kong and internal route evidence (verification snapshot)
From infra/kong/kong.yml (2026-04-06 snapshot):
| Service | Example paths | Notes |
|---|---|---|
access-policy-service | /api/access, /internal/access, /internal/governance | Internal routes use ip-restriction (private ranges) |
licensing-service | /api/licenses, /internal/licenses | Internal ip-restriction |
platform-admin-service | /api/platform, /internal/admin | Rate limiting on public routes |
config-resolver-service | /api/config, /admin/ui, /internal/config, /internal/v1/offline | JWT on public/admin; internal ip-restriction |
| Hierarchy | /internal/hierarchy | Listed in kong service block (see kong.yml hierarchy-internal route) |
Note: The declarative contract test infra/kong/kong.declarative.contract.test.mjs asserts that /internal/access/evaluate is not exposed as a separate Kong path on config-resolver; access evaluation lives under Access Policy’s /internal/access prefix. Config Resolver aggregates policy/licensing/hierarchy via internal calls, not by duplicating that path on the gateway.
6. Spec vs implementation — deviation matrix
| Area | Spec / doc says | Code / infra does | Risk | Suggested correction |
|---|---|---|---|---|
| Config Resolver DAG | SPEC chain in config-resolver/SPEC.md | Implementation adds org ancestors when nodeId present (THEMING_IMPLEMENTATION_GAPS.md) | Medium — doc drift | Update normative SPEC to include org chain; keep FR-CFG-DS-002 aligned |
| Session node context | IAM should issue context_node_id / contextNodeId for facility selection (THEMING_IMPLEMENTATION_GAPS.md) | BFF may lack stable node claim; query-param workarounds possible | Medium | Add session/JWT claim contract + e2e tests |
| Tenant branding | Tenant has primaryColor / logoUrl | Resolver design tokens may overlap | Low–Medium | Define precedence (gaps doc suggests resolver wins when present) |
| ehr-web resolved profile | Should reflect Access Policy | If getBackendGatewayBase() is falsy, returns fixed demo permissions; if gateway returns non-OK, permissions: [] | High in misconfigured env | Disable demo list outside local dev; fail closed or surface error in staging |
| Strict tenancy vs national supervision | Baseline forbids cross-tenant queries | No first-class MoH cross-tenant supervisory span (AFG_DAG_IAM_GAP_ASSESSMENT.md) | Strategic | Product decision: federation APIs, reporting roles, or separate org model |
| Facility vs hierarchy naming | Facility-management overlay on nodeId | Facility Service uses hierarchy_node_id on LocationEntity; scheduling uses facility_id naming | Medium — integration | Document mapping table; validate in seeds/integration tests |
| Provider identity split | Directory + IAM boundaries | Two aggregates (IAM User / profile vs Practitioner) with logical links | Medium | Document linking convention (IDs, FHIR Practitioner linkage) in module specs |
| Historical audit tracker | Many items “Done” in SERVICE_BASELINE_AUDIT_TRACKER.md | Regression possible on future edits | Low | Re-run tracker prompts on major gateway/guard changes |
7. Example flows (narrative)
7.1 Clinician login → UI permissions
- User authenticates with Keycloak; browser holds session with access token.
ehr-webGET /api/access/resolved-profileloads gateway base URL.- If gateway unset, response is a static demo permission list (not policy-backed).
- If gateway set, server calls
{gateway}/internal/access/context?userId=&tenantId=with bearer token and maps permission strings for the AccessGuard.
Evidence: apps/ehr-web/src/app/api/access/resolved-profile/route.ts.
7.2 Book appointment (simplified)
- Client calls scheduling API via Kong with JWT.
- Controllers apply
JwtAuthGuard,RolesGuard,ModuleEntitlementGuardwith@ModuleKey('ehr.scheduling'). - Service persists
Appointmentwithfacility_idandprovider_idas stored UUIDs; validation against hierarchy and directory may be layered in application logic or future strict checks.
7.3 Tenant activation (spec intent)
Per tenant SPEC, activation orchestrates hierarchy root creation, admin user creation, and license seeding—implemented in Tenant Service workflows (see spec FR-TEN-002 and tenant.entity fields).
8. Service notes (governance touchpoints only)
| Service | Role in governance |
|---|---|
| IAM | Canonical user rows; Keycloak linkage (keycloak_id); provisioning; realm role cache fields |
| Hierarchy | DAG nodes/edges; memberships; ancestry for policy and licensing |
| Access Policy | evaluate, /internal/access/context; RBAC+ABAC; decision traces |
| Licensing | Node-attached licenses; inherit-down; feeds ModuleEntitlementGuard |
| Config Resolver | Aggregates effective UI/config; depends on peers per governance README |
| Platform Admin | Feature flags evaluated in resolver-related flows |
| Facility | Operational locations; optional hierarchy_node_id; geo/timezone/locale |
| Provider Directory | Practitioners and roles; links to hierarchy node and facility location IDs |
| Scheduling | Time-based care; ehr.scheduling entitlement; references provider and facility columns |
9. Recommended correction backlog (prioritized themes)
- Harden ehr-web permissions — Remove or gate demo permission list to local-only builds; surface configuration errors when gateway is missing in non-dev.
- Publish ID glossary — hierarchy
nodeIdvslocations.idvs scheduling column names; link to practitioner roles. - Align config-resolver SPEC with implemented org-chain resolution (THEMING_IMPLEMENTATION_GAPS.md).
- IAM session node claim —
contextNodeIdin JWT or server session for BFF (context_node_idgap). - National / multi-tenant governance — Decide product scope using AFG_DAG_IAM_GAP_ASSESSMENT.md and TENANCY_DECISION_MATRIX.md; track as architecture epics, not ad-hoc service patches.
- Ongoing — Append new gaps to
SERVICE_BASELINE_AUDIT_TRACKER.mdwith resolution notes when closed.
10. Stakeholder review and next steps
Review checklist:
- Confirm whether demo permissions in
resolved-profileare acceptable in any shared environment. - Approve ID glossary for engineering and onboarding.
- Prioritize context node in JWT/session vs query parameters for facility selection.
- Reconcile strategic tenancy (hybrid/federation) with baseline enforcement for the next release train.
- Assign owners to rows in §6 that apply to your deployment.
Suggested meeting output: A short decision log (ADR-style) for tenancy/federation and one integration epic for identifier alignment across scheduling, facility, hierarchy, and provider-directory.
Appendix A — Key file index
| Topic | Path |
|---|---|
| As-built overview | IMPLEMENTED_PLATFORM_ARCHITECTURE.md |
| Governance README | governance-auth/README.md |
| Baseline tenancy / events | baseline/ARCHITECTURE_BASELINE.md |
| Hierarchy DAG | modules/hierarchy/SPEC.md |
| Config Resolver | modules/config-resolver/SPEC.md, CONFIG_MODEL.md |
| Config gaps | modules/config-resolver/THEMING_IMPLEMENTATION_GAPS.md |
| Access Policy | modules/access-policy/SPEC.md |
| Licensing | modules/licensing/SPEC.md |
| Facility (spec) | modules/facility-management/SPEC.md |
| Provider Directory (spec) | modules/provider-directory/SPEC.md |
| Scheduling | modules/scheduling/SPEC.md |
| AFG assessment | platform/AFG_DAG_IAM_GAP_ASSESSMENT.md |
| Gap register | _generation/SERVICE_BASELINE_AUDIT_TRACKER.md |