# Relationships for the Owner and for a customer

There is one implementation. `relationship_console` answers a question about a relationship, and two thin routers put it in front of two audiences: the operator API for the Owner, `/api/customer/ui/…` for a customer after `tenantAdapt` rewrites the path. The customer surface renames it — a customer's `people` are the Owner's `relationships` — and nothing else differs.

- **Status:** Available
- **Audience:** both
- **In the app:** #/relationships
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/relationships/relationship-owner-tenant/

## Where the two paths diverge, and where they do not

|  | Owner | Customer |
|---|---|---|
| Session | `customer_mode` false | `customer_mode` true |
| Workspace | `owner-jbrh` | `ws_…` |
| Path | `/api/operator/…`, `/api/…` | `/api/customer/ui/…` |
| How it gets there | Direct | `tenantAdapt()` rewrites the path |
| What answers it | `relationship_console` | `relationship_console` |

The last row is the important one. A capability that exists for one audience and not the other is a defect here, not a product decision — the only deliberate differences are commercial, where a customer's use is bounded by their plan's allowances and the Owner has no plan.

## The rename is written down

`ALIASES` in the audience parity suite records that a customer's `people` are the Owner's `relationships`, in the same way it records that `knowledge-sources` are the `knowledge-bank`. The suite reads the flattened route table out of the running application and compares every Owner capability against every customer one, so a rename is a fact the tests hold rather than a convention somebody remembers.

`KNOWN_GAPS` lists what a customer still cannot reach, each with a reason. It may shrink and it must never grow: adding a capability for one audience only fails the suite, and so does leaving a closed gap on the list.

## Why a request from a customer can still fail

**`tenantAdapt` did not recognise the path** — Anything it cannot rewrite becomes `/api/customer/ui/blocked` and answers 403. A new Owner route without a rewrite lands here.
**`customer_safe` did not allow it** — An allowlist in `main.py` refuses any `/api/*` a customer session may not call, independently of the rewrite.
**The workspace kernel filtered it** — Records are scoped by `workspace_id` before the query runs — a row from another workspace is not refused, it is not there.
**Row-level security refused it** — The third enforcement, in the database itself, which holds even if the two above were bypassed.

A feature that works for the Owner and returns 403 for a customer is almost always one of the first two, and almost never the feature. That is worth knowing before anybody starts reading relationship code looking for a bug that is not in it.

## Two things that are correctly Owner-only

- **Verifying customer payments.** Running the platform, not using Connect.
- **JBRH's own pricing.** The same reason.
- **Website enquiries addressed to JBRH.** `connect_inquiries` is a control-plane table outside every workspace. A customer asking why an enquiry is not in their workspace is seeing isolation work.

None of those is a relationship capability. Within this section, what the Owner can do a customer can do — including [merging](/docs/relationships/merging/), which is a decision each workspace makes for its own records.

## Questions

### Why does the customer screen use different words?

Because *relationships* is how the operator talks about the businesses it serves, and *people* is how a business talks about the humans it deals with. The records and the code are identical; the label is chosen for the reader.

### Is there a reduced customer version of the relationship screens?

No, and building one would be the defect. New behaviour lives in the shared module and each router adds only the route — a second implementation for the other audience is what the parity suite exists to catch.

### How would I know if a capability was missing for customers?

The parity suite enumerates rather than asserts: it compares every Owner capability against every customer one and fails on a new one-audience capability, and on a gap that has since been closed but is still listed.

## Related

- [Relationships in Connect](https://connectbyjbrh.com/docs/relationships/)
- [Merging two records](https://connectbyjbrh.com/docs/relationships/merging/)
- [Customer 360](https://connectbyjbrh.com/docs/relationships/customer-360/)
- [Security and isolation](https://connectbyjbrh.com/docs/security/)
- [One implementation, two audiences](https://connectbyjbrh.com/research/two-audiences-one-implementation/)
- [Website enquiries](https://connectbyjbrh.com/docs/support/website-enquiries/)

## What this page is based on

- `docs-source/sources/GENERAL.md` §1 — the two audiences, `ALIASES` and `KNOWN_GAPS`
- `docs-source/sources/GENERAL.md` §2 — how a request flows and the three isolation layers
- `docs-source/sources/CHANNELS.md` §5 — one body, two thin routers
