Routing a request for the Owner or a customer
A customer's browser rewrites each path to its customer equivalent before the request leaves; an allowlist in front of the application then decides whether that session may call it. Both refuse rather than guess — an unrecognised path is blocked, and a recognised but unpermitted one is denied. The behaviour behind both doors is one shared implementation.
The flow#
- Trigger — a screen in the application needs data, or a person takes an action on one.
- User or external event — the request is composed in the browser against the path the screen knows.
- Authentication and workspace resolution — the session decides the audience as well as the workspace: an operator session, or a customer one.
- Ingest — for a customer session the path is rewritten to its customer equivalent before it is sent. A path with no rewrite becomes a blocked path rather than being sent as it stands.
- Canonical record — the allowlist in front of the application decides whether this session may call what arrived. Unpermitted paths are refused here.
- Reasoning — none. Routing is a lookup; a routing decision that involved judgement would be a routing decision that could be argued with.
- Knowledge, memory and rules — unaffected by which door was used. Both doors reach the same records through the same scoping.
- Autonomy and approval — identical for both audiences. Autonomy is a workspace's own rule, not a property of being a customer.
- Action through a provider — the same shared module runs, whichever router was entered through.
- Result — the same shape of answer for both audiences, or a refusal that says nothing about what exists behind it.
- Relationship, timeline and memory — written identically. There is no customer-shaped record and operator-shaped record for the same thing.
- Audit, usage and Needs You — recorded through the same service, scoped to the workspace the session resolved to.
| Refusal | Decided by | Means | What to do |
|---|---|---|---|
| Blocked | The rewrite, in the browser | This path has no customer equivalent yet | Report it: a capability with one door is a gap to close |
| Denied | The allowlist, in front of the application | The path exists and this session may not call it | Check whether it should be permitted, or whether this is deliberate |
Why an unmapped path is blocked rather than passed through#
The tempting fallback is to send an unrecognised path unchanged and let the application sort it out. That would mean a customer's request occasionally arriving at the operator's surface — not because anybody intended it, but because a rewrite rule was not written on the day a capability was added.
Failing closed inverts the cost. An unmapped path produces a visible refusal on a screen somebody is looking at, which gets reported and closed. The other arrangement produces no symptom at all in the case that matters, which is the definition of a bad failure mode. A tenant facade that fails closed argues the general case.
One body, two doors#
The rule the codebase follows is that behaviour lives in a module both routers call, and each router adds only the route. There is no customer version of a feature and no operator version of it; there are two ways in to one implementation.
- A second implementation for one audience drifts. One side gains a check, a field or a fix, and nobody notices the other has not.
- A rendered subset for customers — the same screen with parts removed — is only acceptable when the omission is deliberate and written down. Otherwise it is the same drift wearing a different coat.
- Where a customer surface renames something, the rename is recorded as an alias rather than implemented as a different capability. A customer's people are the operator's relationships; the records are the same records.
One implementation, two audiences covers why this is worth the discipline it costs.
How parity is proved rather than asserted#
The claim that every capability reaches both audiences is not a policy somebody remembers. A test reads the route table out of the running application and compares each operator capability with its customer equivalent, using the alias list where a name differs. A capability that exists for one audience and not the other fails the build.
The gaps that remain are enumerated, each with a reason. That list may shrink and must never grow — and the test fails on a new gap and on a gap that has been closed without being removed from the list, which keeps the enumeration honest in both directions.
Three things are correctly operator-only, because they are running the platform rather than using Connect: verifying customer payments, the operator's own pricing, and enquiries addressed to the operator through the public site. None of them is a Connect capability a customer is missing.
What can fail#
- A new screen works for one audience only
- The commonest failure, and almost always a missing rewrite rather than a missing feature.
- A path is permitted but the screen still refuses
- Two boundaries, two refusals. See A screen returned 403.
- An alias is added as a new capability
- Then parity sees two capabilities where there is one, and the enumeration stops meaning anything.
- A customer view that quietly renders less
- Acceptable only when the omission is deliberate and written down; otherwise it is drift that no route table can detect.
Questions#
Do customers get a reduced version of Connect?
No. Every capability is available to both audiences over the same implementation. The difference is commercial rather than functional: a customer's use is bounded by their plan's allowances, and three operator surfaces exist for running the platform rather than for using it.
Why does the rewrite happen in the browser rather than on the server?
Because it is a routing decision about which door to knock on, not a security decision. The security decision is made in front of the application by the allowlist, which does not trust anything the browser did.
If the rewrite is client-side, can it be bypassed?
Bypassing it changes only which path arrives. The allowlist then decides whether that session may call it, and the workspace scoping decides which records exist for it — neither of which the browser participates in.