# Workspace

A workspace is the isolation root. Every record Connect holds — a mailbox, a thread, a call, a person, a memory, an autonomy rule, a follow-up — belongs to exactly one workspace, and no query returns a row from another. One business is one workspace; the platform operator's own is a workspace too.

- **Status:** Available
- **Audience:** both
- **In the app:** #/account
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/glossary/workspace/

## What belongs to a workspace

Nearly every table in Connect carries a `workspace_id` and is filtered on it automatically, from a list of scoped tables held in the isolation kernel. That includes the things you would expect — mailboxes, threads, messages, calls, people, companies, opportunities, cases, files — and the things it is easy to forget are data: memory rows, knowledge sources, autonomy settings, saved views on the data grid, and the audit trail itself.

The practical reading of that is: **a workspace is the whole business account, not a folder inside one**. Switching workspace is not a filter you apply. It is a different set of records, a different plan, a different set of connected mailboxes, and a different Connect with different memory of different people.

## Three enforcements, not one

The boundary is applied three independent times, and each one would be sufficient on a good day. They exist together because the failure modes are different: an allowlist mistake, an ORM query written without the scope, and a raw statement are three different bugs.

| Layer | What it does | What it catches |
|---|---|---|
| The customer-safe allowlist | Refuses an API path a customer session may not call, in the middleware | A route that was never meant to be reachable |
| The workspace kernel | Filters every scoped ORM query by the resolved workspace | A query written without a `where` clause |
| Row-level security | PostgreSQL refuses the rows itself | Anything that reached the database another way |

> **Careful** A scoped table needs row-level security applied in the same release that creates it. The table appears at boot; the policy comes from an operational step. A table with a `workspace_id` that is missing from the kernel's list is scoped by nothing at all.

One consequence surprises people the first time: a row written without a workspace stamp is not an error, it is **invisible** — every reader filters it out. [A row with no workspace stamp is invisible, not missing](/research/invisible-not-missing/) covers why that is a deliberate trade and how it presents.

## What a workspace is not

**A company record** — [Company](/docs/glossary/company/) is a record *inside* a workspace describing an organisation you deal with. Your own business is the workspace; your customer's business is a Company row in it. Two very different things, one English word.
**A mailbox** — A workspace holds many [mailboxes](/docs/glossary/mailbox/), each with its own role, signature and autonomy. Connecting a second mailbox does not create a second workspace, and disconnecting one does not remove its history.
**A team or a seat** — People sign in to a workspace; the workspace is not one of them. Adding a colleague does not partition anything, and there is no per-person view of the records.
**A tenant, exactly** — Every [tenant](/docs/glossary/tenant/) is a workspace, but not every workspace is a tenant: the [Owner](/docs/glossary/owner/) workspace is the platform operator's own and has no plan attached. *Tenant* is the engineering word for the customer case.
**An environment** — A workspace is not staging or production. Testing against Connect safely is a separate matter, covered in [the developer sandbox notes](/developers/sandbox/).

## What sits outside every workspace

A small set of tables is deliberately unscoped, because scoping them would be circular or wrong: platform identity and sessions, billing records, and enquiries sent to JBRH through its own website. An enquiry addressed to the operator is not a customer's mail and must not land in a customer's records, so it lives outside the workspace model entirely rather than in a workspace chosen by default.

## Questions

### Can one business have two workspaces?

Nothing prevents it, and the records will not meet: a person known in one is unknown in the other, and memory does not cross. That is usually a reason to keep one workspace and separate the work with mailbox roles rather than with a second account.

### Does Connect ever see another workspace's data when it answers?

No. Grounding material is resolved inside the workspace that owns the piece of work, through the same filtered queries as the screens, so the model is given one workspace's knowledge, memory and rules.

### Where do I see which workspace I am in?

On the account screen, which names the workspace and its plan. For a customer session the workspace identifier is also what every API path resolves against, which is why an unrecognised path fails closed rather than defaulting.

## Related

- [Tenant](https://connectbyjbrh.com/docs/glossary/tenant/)
- [Owner](https://connectbyjbrh.com/docs/glossary/owner/)
- [Security and isolation](https://connectbyjbrh.com/docs/security/)
- [Three independent layers of tenant isolation](https://connectbyjbrh.com/research/three-layers-of-isolation/)
- [Data that is invisible rather than missing](https://connectbyjbrh.com/research/workspace-stamp/)

## What this page is based on

- docs-source/sources/GENERAL.md §2 — how a request flows
- docs-source/sources/GENERAL.md §3 — the data model and SCOPED_TABLES
- `docs-source/facts.py` — PRODUCTION.isolation_layers
