# One implementation, two audiences

Connect serves an operator's own workspace and its customers' workspaces from one implementation. Every capability is available to both, over the same code; the difference is commercial, not functional — a customer's use is bounded by their plan's allowances. Behaviour lives in a module both routers call, and each router adds only its route. Parity is then enumerated from the running application rather than asserted.

- **Status:** Available
- **Audience:** both, developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/research/two-audiences-one-implementation/

## What one feature set means, precisely

There is no operator-only feature and no customer-only feature. The same mailbox handling, the same phone behaviour, the same relationships, the same Voice Lab. What differs is the commercial frame: a customer's workspace has a plan with allowances, and the operator's has neither.

| Aspect | Operator | Customer |
|---|---|---|
| Session | `customer_mode` false | `customer_mode` true |
| Workspace | The operator's own | One tenant workspace |
| API paths | The operator paths, called directly | `/api/customer/ui/…`, reached by rewriting the path |
| Limits | No plan, no allowance gates | The plan decides the allowance |
| Vocabulary | `relationships`, `knowledge-bank` | `people`, `knowledge-sources` — recorded as aliases |

Three capabilities are correctly reachable only by the operator, and they are not features being withheld: verifying customer payments, the operator's own pricing, and website enquiries addressed to the operator, which live in a control-plane table outside every workspace. Those are the platform being run rather than Connect being used.

## The seam that keeps it one implementation

New behaviour goes into a module both routers call — `mailbox_console` for mail, `workspace_console` for screen data, `relationship_console` for one relationship, the voice configuration and preview endpoints, the supervisor panel. The operator's router and the customer's router add a route and nothing else.

- **No second implementation for the other audience.** When the operator's own mail handlers were consolidated, they were rewired onto the shared console rather than left running in parallel.
- **No reduced view unless the omission is deliberate and written down.** A customer-facing function that renders a subset is a decision that needs a reason recorded, not a default.
- **A rename is an alias, not a fork.** The customer surface calls relationships *people*; that is a vocabulary difference registered in one place, and the code behind both names is the same.
- **Storage may still differ where moving it would be worse.** Mailbox rows live in two tables; merging them would move live customer credentials and is not required for the capability. One console over one policy module was.

## What drift looks like when it happens

It does not announce itself. The operator's workspace had no way to connect a Google account while every customer did — the capability existed with nothing on screen calling it. Nothing failed, no test complained, and the gap was in the audience whose screen nobody had open.

That is the general shape: a copy stops keeping up rather than breaking, and the difference is discovered by whoever needs the missing half. It is also why the same report from either direction — "this works on the tenant and not on the operator" and its reverse — is treated as one class of defect rather than two unrelated ones.

> **Note** A report about one audience is a question about both. Checking the other side before replying costs a minute and settles whether the finding is a mapping omission, a genuine gap with a reason, or a defect in shared code that both audiences have.

## Enumeration, and what one implementation costs

The rule is checked rather than restated: the parity suite reads the flattened route table out of the running application and compares every operator capability with every customer one, with aliases registered and known gaps listed with reasons — a list that may shrink and must never grow. The mechanism, and how to read a 403 against it, is in [a tenant facade that fails closed](/research/closed-by-default-facade/).

- **A defect in shared code reaches both audiences at once.** That is the deliberate trade against silent divergence, and it raises the value of checking a change on both surfaces before it ships.
- **Every new operator path is a two-part change**: the capability, and its customer mapping or a written reason for not having one.
- **Screens have to be looked at as each audience**, in a browser, because several of these differences are invisible in source — the cases in [defects invisible in the source](/research/invisible-ui-defects/).
- **Shared modules grow.** A console serving two routers accumulates the union of both surfaces' needs, and keeping it coherent is ongoing work rather than a one-off consolidation.

## Questions

### Do customers get a cut-down version of the product?

No. The same implementation serves both, and there is no customer-only or operator-only feature. What a plan bounds is volume — allowances — not which capabilities exist.

### Why are payments and pricing operator-only?

Because they are the platform being run rather than Connect being used: verifying a customer's payment and setting the operator's own prices are not capabilities a tenant is missing. Website enquiries addressed to the operator sit outside every workspace for the same reason.

### Why do the two surfaces use different words for the same thing?

Because the customer-facing vocabulary is chosen for a business rather than for an operator. The renames are registered as aliases so the parity comparison treats them as the same capability, which is what stops a vocabulary decision reading as a missing feature.

## Related

- [A tenant facade that fails closed](https://connectbyjbrh.com/research/closed-by-default-facade/)
- [Defects that are invisible in the source and obvious in a browser](https://connectbyjbrh.com/research/invisible-ui-defects/)
- [One file service for two audiences](https://connectbyjbrh.com/research/files-through-one-service/)
- [Why 'connected' is not enough to prove mailbox health](https://connectbyjbrh.com/research/mailbox-health-beyond-connected/)
- [Three independent layers of tenant isolation](https://connectbyjbrh.com/research/three-layers-of-isolation/)
- [Connect by JBRH](https://connectbyjbrh.com/docs/product/)

## What this page is based on

- `docs-source/sources/GENERAL.md` §1 and §4 — the two audiences, the parity suite and where each concern lives
- `docs-source/sources/CHANNELS.md` §1 and §5 — the mailbox consolidation, the operator's missing Gmail flow, and `relationship_console`
- `docs-source/sources/PHONE.md` §11 — the shared voice and supervisor surfaces
- Connect capability registry (docs-source/facts.py) — `AUDIENCES`, `owner_tenant_routing`
