Proving a phone number belongs to a workspace
A phone number is not evidence of anything on its own. Before Connect treats a line as a workspace's own, the number is resolved to a record inside that workspace, and a request that cannot produce one is refused rather than served from the nearest match. On an inbound call the workspace comes from the line that was dialled; the caller's number is only ever data about the call.
What the check actually proves#
A workspace's number is a record with a workspace stamp on it, not a string of digits. Two questions therefore have to be answered separately, and confusing them is where trouble starts: *which workspace is this request for*, and *does this number belong to that workspace*. The first is answered by the session or, for a carrier callback, by what was dialled. The second is answered by a lookup that can only see rows carrying the first answer.
That ordering is the whole protection. A lookup performed before the workspace is known has nothing to filter by, so it answers for whatever row matches the identifier, including a row belonging to somebody else.
Where a number arrives from#
| Arrives as | What is supplied | How the workspace is decided | If it does not resolve |
|---|---|---|---|
| An inbound call | The dialled number and the caller's number, from the carrier, with no session | From the dialled line's own record | The call belongs to no workspace and is not attributed to one |
| A screen acting on a number | An identifier from the browser, inside a session | From the session, before the record is looked up | No row is returned and the action does not run |
| An outbound call the engine places | A number the workspace already resolved | Already settled upstream | The call is not placed |
| A person's softphone | A channel_routes row for the softphone channel | The row carries the stamp | The softphone does not register |
The caller's number never decides the workspace. It is matched against relationships after the workspace is known, which is why a caller who has spoken to two different businesses using Connect is two separate people with two separate histories, and neither side can see the other.
Why fetching by identifier is not a check#
The dangerous version of this code reads a line identifier out of a request and asks the database for that row directly. It looks safe, because the workspace filter is applied everywhere else. It is not safe, for a reason that has nothing to do with intent: an object-relational mapper keeps an identity map of rows already loaded in the current unit of work, and a fetch by primary key can be answered out of that map without a query ever reaching the database. No query means no filter, and no query means the database's own row-level policy never sees the read either.
The defect this rule exists to stop#
The documented instance was not on a line but on a mailbox, and the shape is identical: four routes took a mailbox identifier from a browser and read the row directly, so each of them would answer for any workspace's row. They now go through the one scoped lookup. Proving a mailbox belongs to a workspace has that case in full, including the single exception that remains and why.
The same rule governs every route that takes a line identifier from outside: resolve the workspace first, then resolve the line within it. Neither half is optional, and the second half is not satisfied by the identifier being hard to guess.
Failure modes worth recognising#
- A call arrives for a number nobody claims
- Nothing is attributed. The record of the attempt is operational, not a workspace's conversation.
- A number behaves as though it had been removed
- The row is present but its workspace stamp is empty, so it matches no scope. See Data that should be here is not.
- One screen shows a number another screen cannot
- The two are reading through different paths. Only one of them is scoped, and the unscoped one is the defect.
- A number changes hands at the carrier
- Ownership is a property of the record, not of the digits. Until the record moves, the line resolves where the record says it does.
Questions#
Does the caller's number decide which workspace answers?
No. The dialled line does. The caller's number is matched to a person only after the workspace is settled, so the same caller reaching two businesses is two unrelated relationships with no shared history between them.
Can the same number belong to two workspaces?
Ownership is decided by the record and its workspace stamp, so a line resolves inside exactly one workspace at a time. A number that resolves to no row in the workspace being served is not that workspace's line, whatever it is elsewhere.
Is a hard-to-guess identifier enough on its own?
No, and treating it as enough is the mistake. Identifiers travel: in links, in exports, in support conversations. The check has to be a scoped lookup, because only that still holds once an identifier has been seen by somebody else.