Skip to main content

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-to edges 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 ModuleEntitlementGuard with @ModuleKey('ehr.scheduling') on REST and FHIR controllers (verified in apps/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_id as hierarchy node vs facility Location.id, practitioner primary_location_id) require disciplined integration; FKs are not enforced across databases (baseline).
  • BFF permission fallback in ehr-web when 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 nodeId is 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), metadata JSONB, profile_id for HierarchyProfile rules.
  • 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). contains is 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_HOSPITAL in 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.moveNode rejects 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 nodeId is supplied (org ancestor chain merged into resolution).
  • Desire for context_node_id / contextNodeId in 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 tenantId from 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 / producerTypical consumersMechanism
KeycloakAll UIs and APIsOIDC; JWT with sub, realm roles; user attributes (e.g. tenant)
IAMTenant activation, admin APIs, provisioningREST; optional Keycloak Admin API; users table
HierarchyAccess Policy, Licensing, Config Resolver, scheduling/facility by IDREST /internal/hierarchy/* (Kong); node UUID is the structural anchor
LicensingModuleEntitlementGuard, Access Policy, Config ResolverModule active for node with inherit-down along contains
Access PolicyBFF (/internal/access/context), services calling evaluate/internal/access/*
Config ResolverShell/UI, aggregated effective config/api/config, /internal/config, /admin/ui; internal calls to peer services per governance-auth/README.md
Platform AdminFeature evaluation inside resolver flows/api/platform, /internal/admin
Facility ServiceLocation metadata, resource catalog; optional link to hierarchyLocation.hierarchy_node_id
Provider DirectoryScheduling and clinical workflowsPractitioner, PractitionerRole
SchedulingAppointments, slotsAppointment.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:

  1. Identity validation (JWT)
  2. Module entitlement (moduleKey, nodeId)
  3. Feature flag (featureKey, tenantId)
  4. Role / permission baseline
  5. Policy evaluate (/internal/access/evaluate) for governed actions
  6. Overrides and break-glass where applicable
  7. Audit evidence

4.4 Domain integration: organization → facility → ward → room → provider → scheduling​

  1. Organization / network: Modeled as HierarchyNode instances with types and HierarchyEdge relationships per hierarchy SPEC. Profiles (e.g. AFG_MOPH) constrain valid roots and node types.
  2. Facility (business): May correspond to a hospital/clinic node and/or a LocationEntity row in Facility Service. LocationEntity.hierarchy_node_id optionally links a facility-service location to a hierarchy node (apps/services/facility-service/src/locations/entities/location.entity.ts).
  3. 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, BedRecord on nodeId). Facility Service also models LocationType including WARD, ROOM, BED with parent pointers, capacity, timezone, locale, lat/long.
  4. Provider: IAM holds login identity and optional ProviderProfile; Provider Directory holds Practitioner and PractitionerRole. PractitionerRole.facility_id is documented in code as a hierarchy node representing the physical location scope; Practitioner.primary_location_id is a logical FK to facility-service locations.id (cross-DB).
  5. Geo: LocationEntity has latitude / longitude; Practitioner has practiceLatitude / practiceLongitude / practiceAddress for practice location (maps, telehealth, search). Timezone/locale at site level align with FR-FAC-008 in the facility-management spec.
  6. Scheduling: Appointment stores tenant_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):

ServiceExample pathsNotes
access-policy-service/api/access, /internal/access, /internal/governanceInternal routes use ip-restriction (private ranges)
licensing-service/api/licenses, /internal/licensesInternal ip-restriction
platform-admin-service/api/platform, /internal/adminRate limiting on public routes
config-resolver-service/api/config, /admin/ui, /internal/config, /internal/v1/offlineJWT on public/admin; internal ip-restriction
Hierarchy/internal/hierarchyListed 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​

AreaSpec / doc saysCode / infra doesRiskSuggested correction
Config Resolver DAGSPEC chain in config-resolver/SPEC.mdImplementation adds org ancestors when nodeId present (THEMING_IMPLEMENTATION_GAPS.md)Medium — doc driftUpdate normative SPEC to include org chain; keep FR-CFG-DS-002 aligned
Session node contextIAM should issue context_node_id / contextNodeId for facility selection (THEMING_IMPLEMENTATION_GAPS.md)BFF may lack stable node claim; query-param workarounds possibleMediumAdd session/JWT claim contract + e2e tests
Tenant brandingTenant has primaryColor / logoUrlResolver design tokens may overlapLow–MediumDefine precedence (gaps doc suggests resolver wins when present)
ehr-web resolved profileShould reflect Access PolicyIf getBackendGatewayBase() is falsy, returns fixed demo permissions; if gateway returns non-OK, permissions: []High in misconfigured envDisable demo list outside local dev; fail closed or surface error in staging
Strict tenancy vs national supervisionBaseline forbids cross-tenant queriesNo first-class MoH cross-tenant supervisory span (AFG_DAG_IAM_GAP_ASSESSMENT.md)StrategicProduct decision: federation APIs, reporting roles, or separate org model
Facility vs hierarchy namingFacility-management overlay on nodeIdFacility Service uses hierarchy_node_id on LocationEntity; scheduling uses facility_id namingMedium — integrationDocument mapping table; validate in seeds/integration tests
Provider identity splitDirectory + IAM boundariesTwo aggregates (IAM User / profile vs Practitioner) with logical linksMediumDocument linking convention (IDs, FHIR Practitioner linkage) in module specs
Historical audit trackerMany items “Done” in SERVICE_BASELINE_AUDIT_TRACKER.mdRegression possible on future editsLowRe-run tracker prompts on major gateway/guard changes

7. Example flows (narrative)​

7.1 Clinician login → UI permissions​

  1. User authenticates with Keycloak; browser holds session with access token.
  2. ehr-web GET /api/access/resolved-profile loads gateway base URL.
  3. If gateway unset, response is a static demo permission list (not policy-backed).
  4. 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)​

  1. Client calls scheduling API via Kong with JWT.
  2. Controllers apply JwtAuthGuard, RolesGuard, ModuleEntitlementGuard with @ModuleKey('ehr.scheduling').
  3. Service persists Appointment with facility_id and provider_id as 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)​

ServiceRole in governance
IAMCanonical user rows; Keycloak linkage (keycloak_id); provisioning; realm role cache fields
HierarchyDAG nodes/edges; memberships; ancestry for policy and licensing
Access Policyevaluate, /internal/access/context; RBAC+ABAC; decision traces
LicensingNode-attached licenses; inherit-down; feeds ModuleEntitlementGuard
Config ResolverAggregates effective UI/config; depends on peers per governance README
Platform AdminFeature flags evaluated in resolver-related flows
FacilityOperational locations; optional hierarchy_node_id; geo/timezone/locale
Provider DirectoryPractitioners and roles; links to hierarchy node and facility location IDs
SchedulingTime-based care; ehr.scheduling entitlement; references provider and facility columns

  1. Harden ehr-web permissions — Remove or gate demo permission list to local-only builds; surface configuration errors when gateway is missing in non-dev.
  2. Publish ID glossary — hierarchy nodeId vs locations.id vs scheduling column names; link to practitioner roles.
  3. Align config-resolver SPEC with implemented org-chain resolution (THEMING_IMPLEMENTATION_GAPS.md).
  4. IAM session node claim — contextNodeId in JWT or server session for BFF (context_node_id gap).
  5. 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.
  6. Ongoing — Append new gaps to SERVICE_BASELINE_AUDIT_TRACKER.md with resolution notes when closed.

10. Stakeholder review and next steps​

Review checklist:

  • Confirm whether demo permissions in resolved-profile are 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​

TopicPath
As-built overviewIMPLEMENTED_PLATFORM_ARCHITECTURE.md
Governance READMEgovernance-auth/README.md
Baseline tenancy / eventsbaseline/ARCHITECTURE_BASELINE.md
Hierarchy DAGmodules/hierarchy/SPEC.md
Config Resolvermodules/config-resolver/SPEC.md, CONFIG_MODEL.md
Config gapsmodules/config-resolver/THEMING_IMPLEMENTATION_GAPS.md
Access Policymodules/access-policy/SPEC.md
Licensingmodules/licensing/SPEC.md
Facility (spec)modules/facility-management/SPEC.md
Provider Directory (spec)modules/provider-directory/SPEC.md
Schedulingmodules/scheduling/SPEC.md
AFG assessmentplatform/AFG_DAG_IAM_GAP_ASSESSMENT.md
Gap register_generation/SERVICE_BASELINE_AUDIT_TRACKER.md