Skip to content
SENS Platform Docs

Tenancy and Organization Model

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.

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.

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

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.

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.

Can administer the entire platform.

Can administer one tenant.

Can administer an assigned organization unit and optionally its subtree.

Can view assigned data and request exports, but cannot administer users or devices.

Capabilityplatform_admintenant_adminorg_adminviewer
Platform and tenant managementallown tenantnono
Organization readallwhole tenantown scope/subtreeown scope/subtree
Organization managementallwhole tenantown scope/subtreeno
Users and membershipsallown tenantnono
Device readallwhole tenantown scope/subtreeown scope/subtree
Device managementallwhole tenantown scope/subtreeno
Device type readallown tenantown tenantown tenant
Device type managementallown tenantnono
Flow read/manage/activateallown tenantnono
Scripts/parametersallown tenantnono
Global blocks/templatesallnonono
Actions/Dead Letter redriveallown tenantnono
Telemetry read and exportallwhole tenantown scope/subtreeown scope/subtree
Audit readallown tenantnono

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.

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 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.

  • Whether devices can belong to multiple organization units after V1.