KDCube
← Engineering
KDCube Engineering · Concept

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.

16 July 2026Engineering11 minConceptPunch Card
Connection Hubtoken storageidentity edgesdelegated credentialsConnectionStoreTelegram linkGrantStoreuser-scoped secrets

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.

THE FILING RULE

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.

the.archive.mapMAP
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.

QuestionRecordStorage owner
"Claude Code got an OAuth token. Where is it?"Delegated OAuth grant/session behind an opaque bearerRedis GrantStore plus delegated session authority
"I created a manual Delegated by KDCube token. Where is it?"Delegated access grant/session behind an opaque bearerSame 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 secretConnectionStore metadata + user-scoped secrets
"A Telegram user linked to a platform user. Where is that link?"Connection Hub identity edgeConnection 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.

delegated.oauthJOURNEY
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.

handle.vs.recordSPLIT
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.

DELEGATED BY KDCUBE · A HANDLE, NOT POLICYexternal clientClaude Code · a script · CIBearer <opaque handle>SHOWN ONCE · SENT ON EVERY CALLNEVER THE SOURCE OF POLICYConnection Hubhashes the handle · resolves the recordGRANT/SESSION DRAWERselected resourcesselected tools / operationsresource grantsgrantor platform userdelegate actoridentity scopeexpiry · revocation stateREDIS GRANTSTORE + DELEGATED SESSION AUTHORITYREVOKE THE DRAWER AND THE HANDLE STOPS RESOLVING
Fig. 1 — the bearer is a handle into the drawer; the drawer holds the policy.

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:

connection.storePATHS
<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.

An OAuth reapproval for an existing account carries two additional pieces of short-lived state: the existing account_id and claims_mode: replace. They make the callback update the same account and store exactly the checked claim set, including removals, instead of silently unioning old and new claims. A new connection or an incremental demand may still use additive claims. The durable account record stores only the resulting claims; the OAuth state that authorized the transition remains short-lived and single-use.

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.

two.drawersSPLIT
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.

Conversation file references follow the same handle rule. A conv:fi: string names an artifact under its owner, tenant, project, conversation, and turn; it does not grant cross-user access by itself. When an account-backed action needs that file, the provider side materializes the bytes through the artifact service under the current actor and grant authority, then streams them to the backing provider. The temporary workspace path produced for an agent is disposable process state, not a record in Connection Hub and not portable authorization.

CONNECTED ACCOUNT · METADATA DRAWER, SECRET DRAWERCONNECTIONSTORE · accounts.jsonprovider · account_id · labelprovider subject · approved claimscredential health · has_tokenSAYS A TOKEN EXISTS · HOLDS NO TOKENUSER-SCOPED SECRETconnections.accounts. <safe_account_id>.tokensaccess / refresh token bytesserver-side resolvertrusted SDK · broker onlyUI: “Gmail connected · gmail:send”your app · tool · named servicecalls the provider through the broker,never holding the bytesNO BROWSER / MODEL PATHTO THE SECRET DRAWERMETADATA FOR THE UI · BYTES FOR THE BROKER · NEVER THE SAME DRAWER
Fig. 2 — the metadata drawer may say a token exists; only the secret drawer holds its bytes.

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.

link.journeyJOURNEY
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:

edge.pathsPATHS
<connection-hub app storage>/connections/connection-edge-challenges.json
<connection-hub app storage>/connections/connection-edges.json

The final edge is conceptually:

connection_hub.edge.v1JSON
{{
  "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:

GrantWhat it enables
identity:familyIdentity-family reads can include runtime identities connected through this edge.
economics:platform-userEconomics can evaluate spend against the linked platform user.
kdcube:role:* or permission grantsA 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.

TELEGRAM LINK · AN EDGE, NOT A SESSIONTelegram actorproven by initDatatelegram:100200300platform userproven by the browser sessionplatform:02e53484-…the connection edgedelegates_to · approved by the usergrants: identity:family · economics:platform-userPENDINGconnection-edge-challenges.jsonCOMPLETEconnection-edges.jsonschema: connection_hub.edge.v1actor · provenance stay Telegramprojection crosses by grant onlyNO EDGE → NO PLATFORM PROJECTION · NO GRANT → THAT BOUNDARY STAYS CLOSED
Fig. 3 — the edge is a filed relationship; only its selected grants cross.

05 One storage surface per lifecycle

The storage split gives each revocation and refresh operation a precise target.

RecordCurrent storageSensitive bytes?Typical revoke/repair
Authority provider configApp descriptors and platform configurationSecret refs onlyUpdate descriptor/config; rotate referenced secret separately
Deployment OAuth/client secretsPlatform secrets lifecycleYesRotate platform secret
Delegated OAuth code/consent-CSRF/access grantRedis GrantStore keysYes, auth stateExpire/revoke grant; fail closed on missing record
Delegated bearer sessionRedis app-session authorityYes, session stateInvalidate session or let TTL expire
Browser operation-CSRF proofShared request-proof storeYes, short-lived request stateConsume once for the exact subject/app/operation; fail closed if unavailable
Manual delegated tokenOpaque handle plus server-side delegated recordRaw handle shown onceRevoke delegated access record
Per-account agent binding (account_scope)Field on the delegated grant record (Redis GrantStore / delegated credential authority)No raw provider tokenEdit the agent's grant card (Delegated by KDCube)
Connected account metadataConnectionStore account JSONNo raw provider tokenDisconnect account or change approved claims
Connected account provider tokenUser-scoped secretsYesDelete/rotate user secret; reconnect provider
Telegram/platform link challengeConnection edge challenge storeNo provider tokenExpire or complete challenge
Telegram/platform linkConnection edge storeNo provider tokenRemove edge

The records that authorize one-time OAuth transitions are consumed atomically. Authorization-code exchange and consent-CSRF validation use async Redis Lua compare-and-consume operations, so two workers cannot both accept the same record. Refresh rotation first checks current live authority, then atomically replaces the matching old refresh record with one successor. Browser operation-CSRF is a separate request-proof lifecycle: its token is bound to the authenticated subject and exact app operation and is also consumed once.

The same state boundary protects claim replacement. account_id and claims_mode travel inside the signed OAuth state, not in a trusted browser field at callback time, so a reapproval cannot be redirected to another account or silently changed from replacement to union.

Pointer-backed delegated credentials keep the card as the live authority after issuance. Managed REST/MCP calls and refresh resolve and validate that current card again, so narrowing or revocation applies on the next request. Missing or expired means revoked. On managed MCP/REST calls, an unreadable, malformed, or mismatched card produces a logged 503 temporarily_unavailable; the guard never substitutes authority from an older token snapshot and never enters application code. Refresh refuses to rotate untrusted state, returning temporarily_unavailable for store failure or invalid_grant for invalid authority.

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.

triage.cardsINSPECTION
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

Connected account holds the claim, but the agent still cannot use it
  symptom: agent_grant_required
  the account is connected and holds the claim, but this agent's grant is not bound to use it there
  fix on the agent's grant card (Delegated by KDCube), not by reconnecting the provider

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.

ONE DRAWER EACH

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.

· Related articles

· Documentation on GitHub

KDCube Engineering
№ 2026-07-16 · kdcube.tech