Glozr docs

Architecture

Multi-tenancy

Multi-tenancy is the most important invariant in the codebase. Data leakage across workspace boundaries is a security incident, not a bug. The system enforces separation through layered mechanisms: traits, global scopes, policies, middleware, and a dedicated regression test.

Overview

Every tenant-scoped Eloquent model carries one of two traits. Both apply a global scope to all queries and auto-fill the workspace id at creation time, so accidental cross-workspace inserts are blocked at the ORM layer rather than relying on developers remembering to filter.

The trait pattern

  • BelongsToWorkspace — for models with a direct workspace_id column (agents, sources, leads, usage events).
  • BelongsToAgent — for models that hang off an agent (documents, chunks, conversations, messages). The trait resolves the workspace through the agent relationship.

Both traits register a global scope that adds where workspace_id = ? to every query, and a creating hook that auto-fills the column from the resolved current workspace.

Workspace resolution

The current workspace is determined once per request and frozen in a request-scoped resolver:

Request typeWorkspace source
Authenticated dashboard requestRead from users.default_workspace_id; membership in workspace_users is verified.
Widget requestDerived from the agent_id claim inside the signed JWT; never trusted from request params.
System / queued jobSet explicitly when the job is dispatched and restored in the job handler.

The CurrentWorkspace resolver clears state at the end of each request, which matters under Octane because workers are long-lived.

Tenant vs cross-workspace tables

Tenant-scoped (direct or via agent): agents, agent_versions, sources, documents, chunks, conversations, messages, leads, curated_answers, usage_events, knowledge_gaps.

Cross-workspace (no scoping): users (a user may belong to many workspaces), workspaces, plans, and the standard Laravel infrastructure tables (jobs, failed_jobs, cache, sessions).

Vector store isolation

Every vector written to Vectorize or Qdrant carries agent_id and workspace_id in its metadata. Queries filter by agent_id, using the vector store's native metadata filter — there is no application-level post-filter that could be bypassed.

Testing & enforcement

MultiTenancyTest creates overlapping data in two workspaces and asserts that no query in either workspace surfaces the other's rows. It also reflects every Eloquent model to confirm that any model with a workspace_id column declares the matching trait. The test is part of the required CI suite.

Note. The only legitimate way to opt out of the global scope is the platform-admin context, which uses withoutGlobalScope explicitly with a comment justifying the cross-workspace read.