service

Identity & Hierarchy Service

Owns tenants, people, roles and the org hierarchy. Calculates level_1..level_10 hierarchy paths and publishes identity change events.

Service

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:

  1. Tenants — the customer accounts on the platform, plus their status and subscription plan.
  2. People — users sourced from the CRM and from identity providers.
  3. Roles — the RBAC model, including the can_* permission booleans that other services read in order to authorise a request.
  4. The hierarchy — the manager chain, flattened into level_1_idlevel_10_id and a hierarchy_path on 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 familyHierarchy fan-out
sourcerio.corerio.api.hierarchy_change
Defined atapi/core/constants.py:79api/services/hierarchy_service.py:2077-2080
Published byapi/events/publisher.py:45-53api/services/hierarchy_service.py:2050-2101
PayloadAuditEventDetail (Pydantic, api/events/models.py:88-120)a plain dict — event_name, domain, version, actor, context, metadata, data
Consumed bythe audit service’s catch-all rulethis 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:

RuleMatchesTarget
${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.updatedhierarchy recalculation Lambda
${DeployPrefix}-tenant-setup (:210-222)source rio.core; detail-type Tenant Created; detail.event_name rio.core.identity.tenant.createdtenant 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; reads dim_territory for 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.

Inbound and Outbound Message Flow

Event-driven architecture documentation: RIO