Identity & Hierarchy Service
Owns tenants, people, roles and the org hierarchy. Calculates level_1..level_10 hierarchy paths and publishes identity change events.
Service Overview
The Identity & Hierarchy Service is the system of record for who a person is and where they sit in the org chart. Its README describes it as an “event-driven microservice for organizational identity management and updates in the RIO platform”.
It owns four things:
- Tenants — the customer accounts on the platform, plus their status and subscription plan.
- People — users sourced from the CRM and from identity providers.
- Roles — the RBAC model, including the
can_*permission booleans that other services read in order to authorise a request. - The hierarchy — the manager chain, flattened into
level_1_id…level_10_idand ahierarchy_pathon each person, so any service can answer “who reports up to this manager” with a single query.
Hierarchy recalculation is the heaviest operation. It takes a Postgres advisory lock
(pg_advisory_xact_lock) to avoid deadlocks when two revisions land at once, and it treats people
whose provisioning_source is MANUAL as protected — the CRM sync will not overwrite them.
Two different event envelopes
This service publishes on two source values, with two different payload shapes. That is easy to
trip over, so it is worth stating plainly.
| Audit family | Hierarchy fan-out | |
|---|---|---|
source | rio.core | rio.api.hierarchy_change |
| Defined at | api/core/constants.py:79 | api/services/hierarchy_service.py:2077-2080 |
| Published by | api/events/publisher.py:45-53 | api/services/hierarchy_service.py:2050-2101 |
| Payload | AuditEventDetail (Pydantic, api/events/models.py:88-120) | a plain dict — event_name, domain, version, actor, context, metadata, data |
| Consumed by | the audit service’s catch-all rule | this service’s own hierarchy Lambda |
The hierarchy fan-out is deliberately shaped like the event rio-ingestion-service emits, so that one
Lambda rule can match both. See Hierarchy Updated.
What it listens for
Two rules, both declared in infrastructure/lambda/template.yaml:
| Rule | Matches | Target |
|---|---|---|
${DeployPrefix}-hierarchy-updates (:168-179) | source in rio.glue.crm_sync, rio.api.hierarchy_change; detail-type Hierarchy Updated; detail.event_name rio.user.hierarchy.updated | hierarchy recalculation Lambda |
${DeployPrefix}-tenant-setup (:210-222) | source rio.core; detail-type Tenant Created; detail.event_name rio.core.identity.tenant.created | tenant bootstrap Lambda |
Note that the first rule listens for its own rio.api.hierarchy_change events as well as the CRM
pipeline’s — the service triggers its own recalculation asynchronously.
Data stores
All access is raw SQL through sqlalchemy.text(); there are no ORM models or migrations in this repo.
Table names are injected from settings (api/core/config.py:44-51).
- Postgres (RDS) —
dim_person,dim_tenant_role,dim_tenant,dim_subscription_plan,hierarchy_revision,user_group,tenant_status,tag. - ClickHouse — history tables
dim_person_history,dim_person_role_history,dim_person_hierarchy_history,fact_hierarchy_revision_history; readsdim_territoryfor regions. - DynamoDB — the PII vault holding
email_address,first_name,last_name(api/core/constants.py:95).
A note on this repo’s own docs
events/contracts.md in the service repo documents PascalCase detail-types (UserCreated,
HierarchyUpdated) and omits Tenant Created entirely. The running code uses space-separated Title
Case. The code is authoritative and is what this catalog records.