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 claimcontextNodeId 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