Config Service — Service Overview
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 03 platform-services · 02 DDD
1. Purpose
The config-service is the single authoritative resolver for runtime configuration state across the entire Ghasi eHealth platform. Every service and frontend client calls it to answer the compound question:
"Given user U at facility node N, for module M, feature F, and action A: is the action permitted, what UI elements are visible, and which design tokens apply?"
It traverses a Directed Acyclic Graph (DAG) that encodes the full configuration inheritance chain from platform global defaults down to per-user overrides, resolving licensing, RBAC, ABAC, UI visibility, and design tokens in a single call.
2. Bounded Context
| Aspect | Detail |
|---|---|
| DDD context | Platform Configuration |
| Service registry | 03-platform-services.md |
| Internal base URL | http://config-service:3015 |
| Admin base URL (via Kong) | https://api.ghasi-health.af/api/config |
| Package name | @ghasi/service-config |
3. Responsibilities
| Responsibility | Notes |
|---|---|
| Runtime config resolution — 9-step pipeline | License → feature flag → role graph → ABAC → user overrides → UI → tokens |
| ConfigNode DAG management | 13 node types; acyclic invariant enforced at write time |
| Feature definition CRUD | Per-tenant features with allowedActions and dataScopeType |
| Role definition + inheritance graph | BFS expansion, max depth 10, cycle detection at definition time |
| Role feature grants | grantedActions / deniedActions per role per feature |
| User node overrides | ExplicitAllow / ExplicitDeny at any hierarchy node scope |
| UI element visibility configuration | Per role and per user, optionally scoped to a node |
| Design token management | Global → Tenant → OrgNode → Module → User merge chain with LWW |
| Redis cache with TTL + event-driven invalidation | Namespace cfg:*; eviction triggered by NATS mutation events |
| Audit event emission | All mutations; 1 % sampling on resolution calls for diagnostics |
4. Non-Responsibilities
| Not owned here | Owned by |
|---|---|
| JWT issuance and OIDC flows | Keycloak / identity-service |
| Base RBAC role assignment CRUD | identity-service |
| ABAC policy definition + evaluation endpoint | access-policy (POST /internal/access/evaluate) |
| FHIR resource-level consent enforcement | future consent service |
| Kong routing, rate limiting, auth gateway | Kong |
| Secrets and KMS key management | KMS + env vars |
| Hierarchy node CRUD (org structure) | facility-service |
| Module license CRUD | platform-admin-service |
| Tenant branding primitive storage (logoUrl, etc.) | tenant-service |
5. Resolution Inheritance Chain
Global → Tenant → OrgNode → Module → Feature → Action
→ Role (+ inheritance) → User override
→ UIScreen → UIComponent → UIElement → ActionBinding
→ DesignSystem tokens
6. Resolution Algorithm — 9-Step Flow
7. Upstream / Downstream Dependencies
| Dependency | Direction | Purpose |
|---|---|---|
| facility-service | outbound HTTP | Hierarchy spine + node ancestor chain (Step 1) |
| platform-admin-service | outbound HTTP | Module license check (Step 2) + feature flag (Step 3) |
| access-policy | outbound HTTP | ABAC policy evaluation (Step 6) |
| identity-service | outbound HTTP | User membership validation for override creation |
| Redis | infrastructure | TTL cache + event-driven invalidation |
| NATS JetStream | outbound events | Config mutation audit events |
| audit-service | event consumer | Persists all mutation audit records |
| Every platform service | inbound API consumer | GET /internal/config/resolve per request |
Frontend SDK (@ghasi/ui-config-client) | inbound API consumer | useConfigResolution() hook via BFF |
8. Platform Gap Resolution
| Gap ID | Description | Config-service solution |
|---|---|---|
| L-01 | Feature flags were binary; no per-feature data-scope config | FeatureDefinition.dataScopeType enum |
| L-02 | ABAC policies were JSONB blobs, not composable | DAG node model + delegation to access-policy |
| L-03 | No role inheritance (Nurse ≠ ClinicalStaff) | RoleInheritance edges + BFS expansion |
| L-04 | No UI visibility config per role/user | UIVisibilityRule + UIDefinition |
| L-05 | No design system theming at module/user level | DesignToken scope chain with LWW merge |
| L-06 | Every UI boundary made 5–7 API calls | Single GET /internal/config/resolve |
| L-07 | No user-level explicit allow/deny | UserNodeOverride aggregate |
| L-08 | DataScope not first-class | FeatureDefinition.dataScopeType |
| L-09 | No action bindings to UI elements | ACTION_BINDING node type |
9. Key Architectural Decisions
| Decision | Rationale |
|---|---|
| Single compound resolution endpoint | Eliminates N-call fan-out; one cache key per session context |
| DAG over flat key-value store | Enables inheritance, composability, and auditable ancestry |
| Redis cache with event-driven eviction | p95 < 50 ms target; correctness maintained via NATS mutation events |
| Delegate ABAC to access-policy service | Single ownership of ABAC contract; no parallel evaluate surface |
| Soft-delete only on ConfigNodes | Audit integrity; hard-delete rejected with 405 |
| BFS role graph max depth 10 | Prevents unbounded traversal; depth violation rejected at definition time |
tenantId only from JWT claims | Never from query params or body in user-facing path |
10. Source Reconciliation
Single source module: services/config-service/_sources/config-resolver/. No multi-module merge was required. Legacy module ID PLAT-CONFIG is preserved in legacy FR references; all new identifiers use the CONFIG-* prefix per the platform ID registry.