Connection Hub Storage: Tokens, Edges, and the Line Between Them
No magic token table: handles, secrets, metadata, edges, and sessions each live where their lifecycle fits. The storage model is the design.
Connection Hub exists because integration authority arrives from several directions. A browser user signs in through platform authority. An external tool such as Claude Code asks for delegated access to a KDCube resource. A KDCube app connects to a user's Gmail, Slack, or custom OAuth account. A Telegram Mini App proves a channel actor and then asks the platform user to attach that actor to the platform account.
Those journeys should not share one storage bucket. The records look related because all of them answer "who may act?", but they answer it at different boundaries.
Different authority facts live in different places because they have different lifecycles: delegated access is a server-side grant behind an opaque handle; connected provider accounts split metadata from raw credentials; a Telegram link is an identity edge; deployment secrets remain deployment secrets. The storage model is the design.
descriptors + secrets authority/provider policy → deployment plane Redis grant/session stores delegated credentials, → delegated-by-KDCube opaque handles ConnectionStore metadata connected provider → delegated-to accounts provider account Connection edge store linked identities → identity projection and challenges telegram → platform
This article is the storage map. It complements the broader Connection Hub architecture article by showing where the bytes and authority records live, why that split exists, and which record to inspect when a link, token, or delegated client does not behave as expected.
One terminology note: current routes and code still use
bundle_id, bundles.yaml, and
/api/integrations/bundles/.... In product prose, the deployable
unit is an app. Literal identifiers keep their historical
names.
01 The four records people confuse
The quickest way to debug Connection Hub is to name the record before looking for it.
| Question | Record | Storage owner |
|---|---|---|
| "Claude Code got an OAuth token. Where is it?" | Delegated OAuth grant/session behind an opaque bearer | Redis GrantStore plus delegated session authority |
| "I created a manual Delegated by KDCube token. Where is it?" | Delegated access grant/session behind an opaque bearer | Same delegated credential authority as OAuth, displayed once to the user |
| "A user connected Gmail or another provider. Where are those provider tokens?" | Connected account metadata plus raw provider token secret | ConnectionStore metadata + user-scoped secrets |
| "A Telegram user linked to a platform user. Where is that link?" | Connection Hub identity edge | Connection edge store; JSON-backed today |
The first two are inbound delegated access. They let an external actor call KDCube. The third is outbound provider access. It lets KDCube call another provider for a user. The fourth is neither a token nor a provider account. It is a relationship between two independently proven identities.
02 Delegated by KDCube: the token is a handle
For delegated OAuth, an external client discovers a protected KDCube resource, registers or identifies itself, sends the user through the authorization flow, and exchanges a code for a bearer. Claude Code follows this shape when it uses Connection Hub as an OAuth authorization server for a protected MCP surface.
external MCP client
→ protected resource metadata
→ authorize user and collect consent
→ token endpoint returns bearer handle
→ later request: Authorization: Bearer <opaque handle>
→ managed guard hashes handle
→ loads server-side grant and session
→ checks resource, tool, grants, expiry, revocation
→ projects delegate actor + approving platform user
The bearer string is not the authority model. It is a handle into server-side state. The server-side record carries the resource grants, selected operations or tools, grantor identity, delegate identity, identity scope, expiry, and revocation state.
Bearer token bytes displayed to client sent over Authorization header never treated as the source of policy Server-side grant/session selected resources selected tools/operations resource grants grantor platform user delegate actor expiry + revocation
Manual "Delegated by KDCube" access uses the same principle. The UI may show the generated token once because the user must copy it to an external script or tool. After that, listings expose metadata such as label, status, expiry, and last-four style hints. They do not need to expose raw bearer bytes again. The runtime validates the presented bearer by resolving the corresponding server-side grant and session.
03 Connected provider accounts: metadata is not credential material
When KDCube is delegated to a provider account, the storage is different. The user is not giving an external client a KDCube bearer. The user is giving KDCube a provider credential so an app, tool, or named service can call that provider.
ConnectionStore owns the metadata surface:
<app_storage_root>/connections/<safe_user_id>/accounts.json <app_storage_root>/connections/_oauth_states/<sha256(state)>.json
That metadata can contain provider id, account id, display name, provider
subject, approved claims, credential health, and has_token. It
is not the raw provider token store.
Raw provider access/refresh tokens, app passwords, and equivalent credential bytes live in user-scoped secrets. By default, Connection Hub stores them in shared user scope so another app acting for the same platform user can resolve the account through the broker without copying token bytes into its own app storage.
connected account metadata accounts.json provider, account_id, label, claims, health, has_token provider credential bytes user-scoped secret connections.accounts.<safe_account_id>.tokens
This split lets the UI say "Gmail is connected and has
gmail:send" without placing Gmail tokens in the UI payload, the
app descriptor, the agent prompt, or the connected-account metadata
file.
04 Telegram linking is an edge, not a session
Telegram linking is the record most easily mistaken for authentication
state. The Telegram Mini App proves a Telegram actor with
initData; the browser proves the platform user with the normal
KDCube session; Connection Hub writes an edge only after the user explicitly
approves the relationship and selected grants.
Telegram Mini App proves telegram:<telegram_user_id> ↓ pending provider-claim challenge ↓ KDCube browser session proves platform:<platform_user_id> ↓ user selects grants and confirms ↓ connection edge: telegram.kdcube_ref:<telegram_user_id> → platform:<platform_user_id>
Current implementation stores the temporary challenge and final edge in the Connection Hub app's storage:
<connection-hub app storage>/connections/connection-edge-challenges.json <connection-hub app storage>/connections/connection-edges.json
The final edge is conceptually:
{{
"schema": "connection_hub.edge.v1",
"relationship": "delegates_to",
"from": {{
"authority_id": "telegram.kdcube_ref",
"provider": "telegram",
"subject": "100200300",
"user_id": "telegram_100200300"
}},
"to": {{
"authority_id": "platform",
"provider": "platform",
"subject": "02e53484-...",
"user_id": "02e53484-..."
}},
"grants": [
"identity:family",
"economics:platform-user"
],
"proof": {{ "challenge_id": "..." }},
"metadata": {{
"source": "telegram_miniapp",
"telegram": {{ "id": "100200300", "username": "..." }}
}}
}}
The edge does not make Telegram become the platform browser session. The actor for Telegram-originated work remains Telegram. The edge gives a platform boundary a way to ask: "does this Telegram actor have an approved projection to this platform subject, and which grants may cross?" That is why the grants matter:
| Grant | What it enables |
|---|---|
identity:family | Identity-family reads can include runtime identities connected through this edge. |
economics:platform-user | Economics can evaluate spend against the linked platform user. |
kdcube:role:* or permission grants | A boundary can project only roles or permissions explicitly allowed by the edge and currently held by the platform identity. |
Without the edge, Telegram is still externally authenticated by Telegram proof, but it is not a platform user. Without the selected grant, the edge may exist but not satisfy the boundary that wants platform authority, economics authority, or a specific role projection.
05 One storage surface per lifecycle
The storage split gives each revocation and refresh operation a precise target.
| Record | Current storage | Sensitive bytes? | Typical revoke/repair |
|---|---|---|---|
| Authority provider config | App descriptors and platform configuration | Secret refs only | Update descriptor/config; rotate referenced secret separately |
| Deployment OAuth/client secrets | Platform secrets lifecycle | Yes | Rotate platform secret |
| Delegated OAuth code/CSRF/access grant | Redis GrantStore keys | Yes, auth state | Expire/revoke grant; fail closed on missing record |
| Delegated bearer session | Redis app-session authority | Yes, session state | Invalidate session or let TTL expire |
| Manual delegated token | Opaque handle plus server-side delegated record | Raw handle shown once | Revoke delegated access record |
| Connected account metadata | ConnectionStore account JSON | No raw provider token | Disconnect account or change approved claims |
| Connected account provider token | User-scoped secrets | Yes | Delete/rotate user secret; reconnect provider |
| Telegram/platform link challenge | Connection edge challenge store | No provider token | Expire or complete challenge |
| Telegram/platform link | Connection edge store | No provider token | Remove edge |
This is intentionally not "everything in Postgres" yet. The current
reference implementation keeps connection edges JSON-backed behind the
ConnectionEdgeStore contract. The important contract is the
edge shape and API semantics, not the file. A production storage backend can
move the edge and challenge records to a database without changing what an
app asks Connection Hub to resolve.
06 What to inspect when something breaks
Debugging is faster when the failure is mapped to the right record.
OAuth client receives bearer, but MCP call is denied inspect delegated grant/session: resource match · selected tool or operation · grant list · expiry/revocation Connected Gmail tool says consent is missing inspect connected account metadata: account exists · approved claims cover the tool · has_token and health then inspect user-scoped secret only from server-side tooling Telegram Mini App says linked=false inspect connection edge: from provider=telegram · from subject=<telegram_user_id> to authority=platform · status active/linked Telegram automation runs as registered but not privileged inspect selected edge grants and the current platform roles the edge cannot project roles the platform identity does not hold and cannot project roles not allowed by the edge
The pattern is consistent: the proof that entered the system is not the authority by itself. The runtime validates proof, resolves the relevant Connection Hub record, intersects that record with current platform/provider state, and carries the projection only to the boundary that asked for it.
07 Why the boundary matters
Putting every credential-like object into one table would make the system harder to reason about: a delegated bearer would look like a provider account token; a Telegram identity edge would look like a platform login session; a provider refresh token would look like a resource grant; a copied secret would become the accidental source of authority.
Connection Hub separates handles, secrets, metadata, edges, and sessions. Each surface has one owner, one revocation path, and one boundary where it is allowed to project authority.
The result is boring in the best engineering sense. A bearer token is a handle. A connected account is metadata plus a user secret. A Telegram link is an edge. A platform session is platform authority. Product code can then ask Connection Hub for the projection it needs instead of becoming a private credential store.
Implementation paths to inspect when working in the platform tree: the edge store and the Connection Hub app entrypoint.