10 free hours of engineering. · 1-hour scoping call up front — we'll start this week.Claim hours →

The isolation model.

Three concentric guarantees.

Application-level checks are necessary but not sufficient. We back every check with a structural guarantee — one that holds even when application code has a bug.

I

Row-Level Security

Every cross-tenant table on the platform DB has an RLS policy on tenant_id. The session sets it. Postgres enforces it.

II

Dedicated tenant DBs

Module data lives in a per-tenant PostgreSQL database. Cross-tenant queries are physically impossible.

III

Agent context lock

Sessions inherit tenant_id at boot. The executor passes it on every tool call. The tool decorator refuses unset contexts.

Why dedicated tenant DBs.

“Conventional” multi-tenancy gets you to about 80%.

Conventional / single DB
  • Tenant ID is a column. App code adds WHERE tenant_id = ….
  • A missed WHERE clause is a cross-tenant leak.
  • Migrations affect every tenant in one shot.
  • Noisy-neighbor performance bleeds across customers.
  • Per-tenant backup / restore is a custom script.
DataVisions.ai / dedicated DB per tenant
  • A missed WHERE clause queries the wrong table — in your own DB. Caught in dev.
  • Migrations roll out tenant-by-tenant, monitored.
  • Noisy neighbors don't exist — they're in another DB.
  • Backup, restore, export — per tenant, by default.
  • Compliance asks ("show me only this customer's data") have a trivial answer.

Role-Based Access Control.

Roles declared by modules, enforced by the platform.

Each module's module.json declares its roles. Tenants grant roles to users. The platform evaluates every API request, tool call, and UI render against the same RBAC tree.

Module-declared roles

operator, supervisor, admin, viewer — defined where they make sense.

Tenant-scoped grants

A user can be 'admin' in tenant A and 'operator' in tenant B without leakage.

UI + API parity

The console asks the same RBAC engine as the FastAPI guard. The button doesn't render unless the call would succeed.

# every @platform_tool call
if not ctx.user.has_role(tool.roles, tenant=ctx.tenant_id):
    raise Forbidden(tool.name)

# every FastAPI route
@router.get("/machines", dependencies=[Depends(require_role("operator"))])
async def list_machines(ctx: TenantCtx = Depends()):
    return await repo.all(ctx.tenant_db)

# every UI button
<Guarded role="supervisor"><RestartBtn /></Guarded>
Audit log

Every action, in one searchable place.

The audit log is platform-level: any tool call, any role change, any tenant action, any login. Search by tenant, user, agent session, or tool name. Exportable.

WhenTenantActorActionDetail
2026-05-16 09:14:02acme-opsu_07a · operatortool.invokepm.machines.list / shift=west
2026-05-16 09:14:05acme-opsu_07a · operator (via agent)tool.invokepm.alerts.recent / window=8h
2026-05-16 09:11:48acme-opsu_03b · adminrole.grantu_07a → supervisor
2026-05-16 09:08:22metro-millu_22c · adminmodule.subscribescada_ops v2.1.0
2026-05-16 09:02:17acme-opssystemscheduler.runpm.nightly_health → ok