Skip to main content
All Docs
FeaturesagentOS Block ManagerUpdated April 12, 2026

Five-Level Entity Hierarchy

Five-Level Entity Hierarchy

The platform organises all data within a strict five-level entity hierarchy. This structure is the foundational architectural constraint — every entity must exist within it, and no entity can be created outside it.

Level 1 — Managing Agent
Level 2 — Owner
Level 3 — Block
Level 4 — Unit / Leaseholder
Level 5 — Management Accounts

Two core data-flow rules apply across the entire platform:

  • Reporting flows upward — child counts and financial data aggregate to parent levels
  • Permissions cascade downward — access granted at a higher level applies to all children

Hierarchy Levels

Level 1 — Managing Agent

The top-level container. Represents the managing agent organisation (one per org/tenant). All other entities sit beneath the agent.

Level 2 — Owner

An individual or company that holds one or more blocks in their portfolio. An owner must exist before a block can be assigned to them.

Level 3 — Block

A residential block. Each block belongs to exactly one owner. A block must have a valid owner assigned before child entities (units, management accounts) can be created beneath it.

Level 4 — Unit / Leaseholder

An individual unit within a block. A unit must belong to a valid block.

Level 5 — Management Accounts

Financial account sources associated with a block (e.g. management account sources, client money accounts). Require the parent block to have a valid owner.


Hierarchy Completeness Score

The platform calculates a completeness percentage (0–100%) for the hierarchy, weighted equally across four checks:

CheckWeight
Managing agent profile is configured25%
At least one owner exists25%
All blocks have an owner assigned25%
All blocks have at least one management account source25%

A score of 100% means the hierarchy is fully established. Scores below 100% indicate missing configuration, shown as warnings on the dashboard.

When no blocks exist yet, the block and management account checks are considered passing — incomplete hierarchy warnings apply only once data starts being added.


Dashboard Widgets

Two widgets on the main dashboard surface hierarchy status at a glance.

Entity Hierarchy (HierarchySummary)

Displays all five hierarchy levels in sequence with per-level status:

IndicatorMeaning
✅ Green checkLevel is fully configured
⚠️ Amber warningLevel has data but with gaps (e.g. unassigned blocks)
○ Empty circleLevel has no data yet

Includes a colour-coded completeness progress bar:

  • Green — 100% complete
  • Amber — 50–99% complete
  • Red — below 50% complete

Portfolio Structure (HierarchyTree)

A collapsible tree showing the live portfolio structure:

● Managing Agent
  ├── Owner A  (3 blocks)
  │     ├── Block 1  🚪 12 units
  │     ├── Block 2  🚪 8 units
  │     └── Block 3  🚪 24 units
  └── Owner B  (1 block)
        └── Block 4  🚪 6 units

Blocks without an assigned owner appear under an "Unassigned Blocks" group, highlighted in amber.


Validation Rules

All entity-creation flows enforce hierarchy integrity through a set of validation utilities. The key business rules are:

  • An owner must exist before a block can be assigned to it.
  • A block must have a valid owner before units or management accounts can be created beneath it. Attempting to create a child entity on an ownerless block returns a PRECONDITION_FAILED error:

    "This block does not have a valid owner assigned. Per the entity hierarchy, a block must belong to an owner before child entities (units, management accounts) can be created."

  • Every entity is scoped to its org — cross-org access is prevented at every validation step.

API Reference

All hierarchy endpoints are available under the hierarchy tRPC router. They require the blocks.read permission and are scoped to the authenticated organisation.

hierarchy.summary

Returns aggregated entity counts and the hierarchy completeness score.

Returns:

{
  agentConfigured: boolean;
  ownerCount: number;
  blockCount: number;
  blocksWithoutOwner: number;
  unitCount: number;
  unitsWithoutBlock: number;
  managementAccountSourceCount: number;
  blocksWithManagementAccount: number;
  blocksWithoutManagementAccount: number;
  hierarchyComplete: boolean;
  completenessPercentage: number; // 0 | 25 | 50 | 75 | 100
}

hierarchy.tree

Returns the full portfolio tree for visualization.

Returns:

{
  agent: {
    orgId: string;
    configured: boolean;
  };
  owners: Array<{
    id: string;
    name: string;
    level: "owner";
    childCount: number;       // number of blocks
    children: Array<{
      id: string;
      name: string;
      level: "block";
      childCount: number;     // number of units
    }>;
  }>;
}

Blocks without an owner are grouped under a synthetic node with id: "__unassigned__".


hierarchy.path

Resolves the full hierarchy path for a block or unit.

Input:

{
  entityType: "block" | "unit";
  entityId: string;
}

Returns:

{
  agent: { orgId: string; agentId: string | null } | null;
  owner: { id: string; name: string } | null;
  block: { id: string; name: string } | null;
  unit: { id: string; unitNumber: string } | null;
}

Useful for breadcrumb navigation and contextual display.


hierarchy.validate

Checks whether an entity is valid and correctly positioned in the hierarchy.

Input:

{
  entityType: "agent" | "owner" | "block" | "unit";
  entityId?: string; // required for owner, block, unit
}

Returns:

{
  valid: boolean;
  level: "agent" | "owner" | "block" | "unit";
  issues: string[]; // human-readable descriptions of any hierarchy problems
}

Example issues returned:

  • "Managing agent profile has not been configured for this organisation."
  • "Block does not have a valid owner assigned. Per the entity hierarchy, blocks must belong to an owner."
  • "Unit's parent block does not have a valid owner assigned. The hierarchy chain is broken at the block → owner level."

Using Validation in Custom Routers

When building new entity-creation endpoints, import the validation utilities from @/lib/hierarchy to enforce hierarchy constraints before inserting records:

import {
  requireBlockHasOwner,
  validateOwnerInHierarchy,
} from "@/lib/hierarchy";

// Gate: block must have an owner before a unit can be created
await requireBlockHasOwner(input.blockId, ctx.orgId);
// Throws PRECONDITION_FAILED if not met

// Soft check: verify owner exists
const { valid, owner } = await validateOwnerInHierarchy(input.ownerId, ctx.orgId);
if (!valid) throw new TRPCError({ code: "NOT_FOUND", message: "Owner not found." });