Connect by JBRH Open Connect

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 What this means
Audience
both
In the app
#/relationships
Last verified
Product version
6.3.2

Where the two paths diverge, and where they do not#

OwnerCustomer
Sessioncustomer_mode falsecustomer_mode true
Workspaceowner-jbrhws_…
Path/api/operator/…, /api/…/api/customer/ui/…
How it gets thereDirecttenantAdapt() rewrites the path
What answers itrelationship_consolerelationship_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, 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.