Tenancy and Organization Model
Purpose
Section titled “Purpose”This document defines the initial tenancy model.
The platform must support multiple customers and flexible nested customer structures without assuming that every customer uses the same hierarchy.
Key Concepts
Section titled “Key Concepts”Tenant
Section titled “Tenant”A tenant is a hard security and data isolation boundary.
Examples:
- A direct customer.
- A customer environment.
- A legally or operationally separate data boundary.
Tenant data must not leak into another tenant.
Organization Unit
Section titled “Organization Unit”An organization unit is a flexible hierarchical structure inside a tenant.
Examples:
- Customer group.
- Sub-customer.
- Region.
- Branch.
- Store.
- Building.
- Site.
- Department.
- Warehouse.
- Custom grouping.
Not every tenant has the same structure.
Example structures:
- Tenant A -> McDonalds -> Store 1
- Tenant A -> SPAR Österreich -> Region East -> Branch 123
- Tenant B -> Lagerhalle 1 -> Sensor group
- Tenant C -> Direct devices without deeper hierarchy
Design Decision
Section titled “Design Decision”Use tenants for hard isolation.
Use organization units for flexible nested business structure.
Do not model every hierarchy level as a separate tenant unless a hard security boundary is required.
Access Model
Section titled “Access Model”A user may have access to:
- A whole tenant.
- One organization unit.
- Multiple organization units.
- A subtree below an organization unit.
Sub-customers and organization units must not see each other unless the user has explicit access.
Initial Roles
Section titled “Initial Roles”platform_admin
Section titled “platform_admin”Can administer the entire platform.
tenant_admin
Section titled “tenant_admin”Can administer one tenant.
org_admin
Section titled “org_admin”Can administer an assigned organization unit and optionally its subtree.
viewer
Section titled “viewer”Can view assigned data and request exports, but cannot administer users or devices.
Accepted permission matrix
Section titled “Accepted permission matrix”| Capability | platform_admin | tenant_admin | org_admin | viewer |
|---|---|---|---|---|
| Platform and tenant management | all | own tenant | no | no |
| Organization read | all | whole tenant | own scope/subtree | own scope/subtree |
| Organization management | all | whole tenant | own scope/subtree | no |
| Users and memberships | all | own tenant | no | no |
| Device read | all | whole tenant | own scope/subtree | own scope/subtree |
| Device management | all | whole tenant | own scope/subtree | no |
| Device type read | all | own tenant | own tenant | own tenant |
| Device type management | all | own tenant | no | no |
| Flow read/manage/activate | all | own tenant | no | no |
| Scripts/parameters | all | own tenant | no | no |
| Global blocks/templates | all | no | no | no |
| Actions/Dead Letter redrive | all | own tenant | no | no |
| Telemetry read and export | all | whole tenant | own scope/subtree | own scope/subtree |
| Audit read | all | own tenant | no | no |
Several memberships are evaluated as a union. includeDescendants=false
grants only the exact organization unit; true includes its recursive subtree.
Ancestors can be returned for breadcrumb navigation only and do not grant
access to ancestor or sibling devices. Tenant-wide viewers have no organization
ID. Devices without an active organization assignment are visible only to
platform and tenant administrators.
Organization administrators may create devices only with an active assignment
inside their managed scope. A move requires device:manage for both the current
and destination organization. Only platform and tenant administrators may end
an assignment without a replacement. Historical assignment reads apply the
same organization grants in SQL and omit every foreign organization ID; a user
who previously had scope may still read only those historical periods that were
inside that scope.
Tenants have two distinct identifiers. TenantId is the internal UUID used by
foreign keys, events, audits, authorization, and every tenant-aware repository.
TenantNumber is a positive, immutable, globally unique integer used for public
Tenant selection. Numbers are labels, never authorization evidence.
Tenant collections, creates, projections, and singletons use explicit numeric
paths such as /v1/tenants/17/devices. Once a globally unique aggregate ID is
known, the canonical member path is flat, for example
/v1/devices/{deviceId}. A typed resource locator resolves its internal Tenant
UUID before authorization; the service and repository still query by both UUID
and aggregate ID. The API does not trust an X-Tenant-ID request header or UI
session scope. IDs belonging to another Tenant or outside the actor’s
organization scope return 404 rather than disclosing their existence.
Tenant, membership, and organization status is re-evaluated from PostgreSQL on every authenticated request. A disabled tenant removes every tenant grant. A disabled organization unit makes its complete subtree effectively disabled without changing child status values. Platform and tenant administrators retain the administrative view required for reactivation; organization administrators and viewers lose that scope immediately.
Organization reparenting takes a tenant-specific transaction advisory lock and is also protected by a database cycle trigger. An organization administrator must administer every source-subtree node before the move and must retain that coverage in the projected destination. Multiple grants may jointly satisfy these checks. Moving to root remains restricted to platform and tenant administrators.
Device Assignment
Section titled “Device Assignment”V1 uses one active organization assignment per device. The schema stores
assignment history with valid_from and valid_to, and prevents overlapping
assignments for the same device.
Measurement rows store the tenant and organization scope resolved at write time. This preserves historical correctness when a device later moves between organizations.
The schema keeps the assignment model separate from the device row so future multi-organization assignment or a separate measurement-point model can be added without rewriting telemetry storage.
Device types are tenant-wide read resources. Tenant administrators manage their device types, while organization administrators and viewers have read-only access. Platform and tenant administrators can manage Device-Type field contracts, their unique telemetry Flows, tenant-owned script blocks, and Actions inside the selected tenant. Global script blocks and templates remain platform-admin assets. Every executable Flow belongs to exactly one tenant and one Device Type, and repository calls require that tenant explicitly.
Tenant collection routes derive scope from the numeric path. Flat member routes
derive scope exclusively from the addressed aggregate. Request bodies cannot
override either resolution. Platform custom-block and template routes reject
Tenant IDs in bodies. Tenant custom schemas may reference built-ins, released platform
schemas or released schemas from the same tenant. A foreign schema, Raw row,
Flow, execution, Action, fixture, parameter scope, recipient scope or Dead
Letter is returned as not found. Database triggers independently validate
polymorphic Device and Device-Type scope IDs against tenant_id.
Tenant Isolation Rules
Section titled “Tenant Isolation Rules”Tenant scope must be explicit in:
- API routes.
- Repository methods.
- SQL queries.
- Internal events.
- Export jobs.
- Audit logs.
- Authorization checks.
Cross-tenant access is a security defect.
Tenant audit views are intentionally narrower than global audit views. They contain only events whose tenant scope was established after a platform-admin check or a successful tenant authorization decision. Rejected requests before that point remain global and cannot leak through a tenant audit query.
Open Decisions
Section titled “Open Decisions”- Whether devices can belong to multiple organization units after V1.