Connected Identities Are Not One User Id
The same person is not the same as one user_id. They arrive through many channels, each with its own
verified identity. The platform keeps those ids separate, links them into a family,
and asks two different questions of that family: who is this for? and what may this execution do?
This Deep piece defines the foundational vocabulary the rest of the Connection Hub series builds on — actor, platform
identity, connection edge, identity family, authority projection, and provenance.
In a real product, the same person does not arrive as one user. They arrive through a browser session, a Telegram Mini App, a Slack command, a delegated external app, a webhook, an API key — and tomorrow through a channel that does not exist yet. Each arrival carries its own verified identity and its own user id. None of them is "the" user.
A platform has two tempting ways to handle this, and both are wrong. It can flatten every arrival into one
user_id — and lose track of which identity an action actually came from. Or it can keep the identities
unrelated — and the product fragments, so the memory you wrote from your phone is invisible in the browser.
Connection Hub holds the shape in between: many verified identities, connected by explicit edges, with the
platform asking different questions of that connected set depending on the boundary a request is crossing.
The thesis of this piece — and the foundation for the rest of the series — is one sentence:
Those two questions have different answers, even though they read the same links. This article defines the vocabulary the rest of the Connection Hub series builds on: actor, platform identity, connection edge, identity family, authority projection, provenance.
The vocabulary
Six terms. Each is a different fact about a request, and the rest of the series assumes you can tell them apart.
Actor — the runtime identity a request arrives as. It is whatever authenticated proof the
request carried: a Telegram Mini App arrives as telegram_434804821; a browser session arrives as a
platform uuid like 02e53484-…; a future provider arrives as provider:subject. The actor is
the ground truth of "who started this." It is never rewritten.
Platform identity — the KDCube platform user (authority_id = "platform"). This is the
identity that owns platform roles, economics, and platform-protected operations. A Telegram actor is not a
platform identity; it may be linked to one.
Connection edge — a stored, proven link from one identity to another, carrying the grants that link allows. It is the one graph primitive Connection Hub stores. An edge says: this verified identity may be treated as connected to that identity, in these ways. It does not erase the identity that entered the system, and it does not, by itself, mint roles.
Identity family — the resolved set of runtime user ids that are the same person, for product aggregation. This is what a memory feature needs to show "all my memories" after a phone and a browser are linked. It answers: which user ids is this for?
Authority projection — the roles, permissions, and economics subject that apply at a protected boundary. It answers a different question: what may this execution do, and who pays? It reads the same edges, but it is not the identity family.
Provenance — which identity an action or record actually came from. The actor is preserved through every projection so that a record written from Telegram stays attributable to Telegram, not silently rewritten to the platform user.
The whole article is these six terms and how they relate. If you remember one relationship, remember that identity family and authority projection are two different questions asked of the same connected set.
One person, many ids, linked
One person arrives as several separate verified identities, each linked by a connection edge that carries its grants. Connection Hub resolves them into one platform identity and one identity family — the set of runtime user ids that are the same person.
identity:family grant.Three identities in one request
When a Telegram user opens the chat app, three distinct facts are in play. They look similar and are routinely confused; keeping them apart is the entire point.
actor identity
who started this request?
telegram_434804821
platform identity
which platform user is this actor connected to?
02e53484-0081-70ce-11c1-e96706b1a182
authority projection
which roles / economics subject apply at this boundary?
platform grants selected on the connection edge
The actor identity is not rewritten. The request stays attributable to telegram_434804821 — for audit,
conversation binding, product provenance, and debugging. But when the request reaches a platform-protected boundary,
Connection Hub can answer, for that boundary only:
Can telegram_434804821 represent a platform identity here?
If yes, which platform identity?
Which selected grants may be projected?
Which economics subject should pay?
This is the difference between a connected identity and an impersonated session. Connection Hub does not turn the Telegram actor into the browser session. It keeps Telegram as the actor and projects platform authority only through the explicit edge, only at the boundary that needs it. In the projected runtime shape, both facts are still visible:
identity_authority
actor_user_id = telegram_434804821 (who started it / where storage lives)
platform_user_id = 02e53484-... (linked platform principal)
economics_user_id = 02e53484-... (who pays)
platform_roles = [...] (only roles both held AND on the edge)
Grounded in the Authority Projection doc: "The actor and the authority source can be different… an app keeps Telegram-scoped storage or product behavior while platform role checks and economics use the linked platform principal."
The edge is the load-bearing object
A connection edge is the one thing Connection Hub actually stores. A "linked identity" and a "delegation" are not
two storage models — they are two resolver views of the same edge graph. The edge has a direction
(from an actor identity, to a target authority identity), a relationship, a proof, and a
list of grants.
from authority identity
-- relationship + grants + proof -->
to authority identity
The grants are why an edge is more than an alias table. It is tempting to say "if Telegram is linked to the platform user, Telegram can do everything the platform user can." That is too broad. Linking proves a relationship between identities; the grants decide what may be derived through that relationship. The edge stores only what the target identity allowed the source identity to derive — roles, permissions, and economics authority still come from the authority that owns the target identity, intersected with the edge grants.
A platform edge typically carries grants like these:
| Grant | What it allows |
|---|---|
identity:family |
product reads may aggregate across the target platform user's connected runtime identities |
economics:platform-user |
economics may charge/check the target platform user while preserving the external actor as provenance |
kdcube:role:* |
the external actor may derive this platform role — only if the platform identity currently holds it and it is on the edge |
| platform permission strings | the external actor may derive this platform permission under the same rule |
So a concrete edge looks like a link and a consent record at once:
{
"schema": "connection_hub.edge.v1",
"relationship": "delegates_to",
"from": {
"authority_id": "telegram.kdcube_ref",
"provider": "telegram",
"subject": "434804821",
"user_id": "telegram_434804821"
},
"to": {
"authority_id": "platform",
"user_id": "02e53484-0081-70ce-11c1-e96706b1a182"
},
"grants": ["identity:family", "economics:platform-user", "kdcube:role:chat-user"],
"proof": { "challenge_id": "..." }
}
The edge is written only when both sides are proven: the actor proves its provider identity, and a browser claim proves the platform identity, with an explicit consent step that selects the grants. The Telegram Mini App never silently becomes the browser platform session.
Identity family: one product view, many owners
The first question the platform asks of a connected set is the product one: which user ids belong to this person, so a feature can show one coherent view? That is the identity family.
The clearest example is memory. A user creates memories from the browser and from Telegram. Each record keeps its original owner, because that is where provenance lives:
memory created in browser
user_id = 02e53484-...
memory created in Telegram
user_id = telegram_434804821
After the accounts are connected, the user expects one memory list. The memory feature should not hardcode
"Telegram" or parse provider prefixes. It asks the Identity Family Resolver — SDK code in
...connections.hub.resolver — a single question:
Given this actor/session, which runtime user ids are the same person
for product reads?
The resolver returns schema connection_hub.identity_family.v1:
{
"ok": true,
"schema": "connection_hub.identity_family.v1",
"linked": true,
"platform_user_id": "02e53484-0081-70ce-11c1-e96706b1a182",
"identities": [
{ "kind": "authority", "authority_id": "platform",
"user_id": "02e53484-0081-70ce-11c1-e96706b1a182" },
{ "kind": "integration", "provider": "telegram",
"provider_subject": "434804821", "user_id": "telegram_434804821" }
],
"memory_user_ids": [
"02e53484-0081-70ce-11c1-e96706b1a182",
"telegram_434804821"
]
}
The feature then reads with a server-resolved scope:
WHERE user_id IN memory_user_ids
The client never chooses the user ids. The backend resolves the family and uses the result as a read scope. That is the whole pattern for features that store records under runtime actors but need one user-visible view.
An edge is not enough — the grant gates the family
This is the subtle, load-bearing rule, and it is easy to get wrong. An edge linking Telegram to a platform
user does not, by itself, let memory reads cross identities. The edge must carry the identity:family
grant. Without it, the resolver returns only the actor's own one-id family:
edge exists, grants = [] -> family = [telegram_434804821]
edge exists, grants = [identity:family] -> family = [telegram_434804821, 02e53484-...]
no edge at all -> family = [telegram_434804821]
The reasoning is exact: a connection edge means "these identities were proven and connected"; the
identity:family grant means "this source identity may use the target platform user's family for product
reads." Aggregation requires the grant, not merely the existence of a link. (If a linked memory request shows only the
Telegram id when you expected both, the usual cause is an old edge with "grants": [].)
Writes stay actor-scoped — that is provenance
Identity family is a read behavior. Writes are where provenance is created, and provenance is the reason the platform refuses to flatten identities in the first place.
A write records the actor that caused it:
Telegram-originated write -> user_id = telegram_434804821
Browser-originated write -> user_id = 02e53484-...
This keeps the system honest. If a note was created from a Telegram bot, the record says so. Connected views aggregate those records later without rewriting history. Reads widen across the family; writes never do.
Deletes follow the read rule, then act on the real owner:
1. Resolve the identity family.
2. Load the requested record where its owner is in that family.
3. Delete the actual stored owner record.
The delete must not trust a client-supplied owner id. The backend resolves the family and uses the stored record's
real owner. Provenance is preserved at every step: even when a delegated external app reads across the family, the
projection carries explicit provenance (delegate identity, grantor, credential) so the record of "who did
this" is never lost.
Authority projection: same links, a different question
The second question the platform asks of the connected set is not "who is this for" but "what may this execution do, and who pays?" That is authority projection, and it is deliberately separate from the identity family.
Identity Family Resolver
"which user ids belong to this person for product data?"
-> read scope: memory_user_ids
Authority Projection
"which roles / permissions / economics authority may this execution use?"
-> roles, permissions, economics subject
They often read the same connection edges. They are still different answers, and one must never substitute
for the other. memory_user_ids is a product read scope; it is not a set of authorization grants.
A product record that happens to fall inside the family still gets its normal access checks. Conversely, holding a
platform role does not widen which records a feature reads — that is the family's job.
Keeping them separate is what lets a request stay precise. A single request may cross several boundaries, and each boundary asks only for the authority it needs:
incoming request
actor = telegram_434804821 (verified by Telegram authenticator)
|
v
Boundary: memory read -> identity family resolves memory_user_ids
|
v
Boundary: economics -> projects the platform user as the charge subject
|
v
Boundary: platform-admin op -> checks the selected role grant on the edge
The request does not eagerly become every identity it is connected to. It resolves only the identity or authority required at the boundary it is crossing. That keeps latency low and avoids accidental privilege expansion — a request that only reads memory never acquires admin authority along the way.
One note for the rest of the series: an external app calling KDCube through a delegated credential uses this same model — it remains its own actor, and the consenting platform user is the grantor reached through a stored edge. The mechanics of delegated credentials and MCP connectors are their own pieces; here it is enough that delegation is one more edge-shaped relationship asking the same two questions.
Identity edges are not transport sessions
One last distinction, because it is a common category error. Connection Hub stores identity and delegation facts. That is not the same as protocol session memory.
identity edge (Connection Hub):
telegram:434804821 -> platform:02e53484-...
selected grants / identity-family scope
durable, proven, consent-backed
transport session (ingress layer):
websocket connection
SSE stream
MCP mcp-session-id
per-connection, ephemeral
A request carries its identity authority through Connection Hub credentials and runtime context on each request; it does not depend on a long-lived protocol session to know who the actor is or what platform identity it is connected to. Where stateful protocol sessions are needed, that state belongs to a platform-owned ingress layer, not to the identity graph. The identity graph answers "who, connected to whom, with what grants" — and stays the same whether the transport is stateful or not.
What this buys the series
This is the foundation. With these six terms fixed, the rest of the Connection Hub series can build without re-deriving them:
- An actor is preserved as provenance through every projection.
- A connection edge is the one stored primitive — a proven link plus consented grants.
- The identity family answers who is this for (product read scope), gated by the
identity:familygrant. - Authority projection answers what may this do, and who pays — same links, a different question, never a substitute for the family.
- Same person ≠ one user id. The platform keeps the ids separate, links them into a family, and asks each boundary only the question it needs.
Related publications
mem, task, and cnv namespaces.Documentation on GitHub
The live docs behind this entry: