Skip to main content

Sessions across a node's cells

The question every architect asks about a multi-cell platform is some version of this: if I sign in against one cell and my next request lands on another, what happens? The unsatisfying answers are a sticky load balancer, which turns a cell into a single point of failure, or a shared session cache, which turns one box into the thing the whole estate depends on.

System9 answers it in the data model instead. A session is node state, not cell state. It converges across the cells of its node, so any cell of that node can validate it locally, with no session-affinity routing and no shared cache to lose.

What converges, and how far​

Session material is not one undifferentiated blob. Different artifacts have different blast radii, and each is declared separately:

ArtifactConvergesScopeWhy that scope
Live sessionsYesAcross the node's cellsAny cell of the home node validates locally, so a request can land anywhere
Session revocationsYesAcross the node's cellsA revoked session must not validate on a peer that has not heard yet
Account-recovery tokensYesAcross the node's cellsRecovery survives a home-cell failure, and the consumed state converges so the token cannot be replayed on a second cell
OAuth authorization codesYesAcross the node's cellsA code issued on one cell is exchanged on another; consumed state converges so it is genuinely single-use
OAuth device codesYesAcross the node's cellsMint on one cell, approve on a second screen served by another, poll from a third
OAuth consentsYesAcross the node's cellsAn approval granted once is honoured on whichever cell serves the next authorize call
OAuth token revocationsYesAcross the node's cellsOffline token verification is stateless, so a converged denylist is the only revocation signal there is
Password credentialsYesAcross the organization's nodesThe stronger guarantee: any cell of any node can authenticate the user through a cell or a whole-node failure
Auth event streamNoStays on its cellEach cell owns its own hash-chained audit stream; merging chains across cells would break the chain invariant

Two rows deserve the emphasis.

Credentials converge further than sessions. A session is scoped to the node that issued it, but the password credential converges across every node in the organization, because the user record it belongs to is already organization-wide. That is deliberately the stronger resilience guarantee: losing a node costs the sessions it issued, but never the ability to sign in again. The stored hash is organization-private and never crosses an organization boundary.

Single-use really is single-use. For recovery tokens, authorization codes and device codes, it is not the token that must converge so much as the fact that it has been spent. Replicating the consumed state is what stops the classic multi-cell replay: redeem on cell A, redeem the same code again on cell B before anything catches up.

Revocation is the load-bearing direction​

Convergence for availability is the easy half. The half that has to be right is the one that takes authority away, because a delay there is a window in which a revoked session still works.

Revocations therefore travel by two paths at once:

  • Convergence, as a denylist that broadcasts to every cell of the node, the same way a certificate revocation list works. This is the durable path and it is the one that is eventually authoritative.
  • A revocation event on the bus, published by a revocation worker and applied by a consumer on each cell. This is the fast path, and it exists so the practical latency of a logout is not the replication interval.

Neither path is trusted to be instant, which is why the fallback below is written to fail closed against revocation rather than against availability.

The convergence window, and the fallback that closes it​

Replication is fast, not instantaneous. There is a narrow window between a login on one cell and that session being present on its peers. A cross-cell session validator closes it.

When a cell is handed a session it does not have locally, it may ask its peers:

Figure I4: the cross-cell session fallback. The first gate is the one that matters. Before a cell will ask anyone about an unknown session, it checks its own converged revocation store, and a hit there ends the request without a single peer being contacted. That ordering is the whole design: a peer that has not yet converged a revocation must never be able to hand back a session this cell already knows is dead. Peers are only ever queried within the node, because that is the boundary sessions are scoped to.

Table equivalent

StepBehaviour
Local revocation checkRuns first. A known revocation refuses immediately and skips the peer query entirely
Peer selectionSame node only, capped at a small number of peers
TimeoutShort, per peer, so an unreachable peer cannot stall a request
On successThe session is imported locally and briefly cached, so the next request needs no fan-out
On failureTreated as no session. There is no partial or optimistic grant
This is a seam, not the mechanism

The fallback is explicitly a fallback for the convergence window and should rarely fire in steady state. If it is firing constantly, the thing to investigate is replication health, not session handling. It is also configurable and can be switched off entirely, in which case a session is simply unavailable until it converges.

How a peer verifies a token without a shared secret​

A session token has to be verifiable by whichever cell receives it, and the naive way to arrange that is to give every cell the same signing secret. That makes the compromise of any one cell the compromise of all of them.

System9 splits the token by who needs to verify it:

TokenSignatureHow it is verified
Same-cellSymmetric (HS256)The issuing cell verifies with its own key, which never leaves it
Cross-cellAsymmetric (EdDSA), signed with the node's sign identityThe peer resolves the public half from the issuer's published key set, matched by key ID

Figure I5: cross-cell token verification. No shared secret crosses the boundary. The issuer signs with a private key it keeps; the verifier fetches only public material from a standard key set endpoint and selects the right key by its ID, which is what makes key rotation a non-event. Verification is fail-closed: anything wrong with the token, the issuer, or the key lookup yields no identity at all rather than a partially trusted one.

Identity is stamped only from the verified claims of a genuinely signed token. A claimed identity in a header is not an identity.

Beyond the node: sessions do not cross, verification does​

A session converges across the cells of its node and stops there. That boundary is intentional, and it is the same distinction the platform draws everywhere:

State that must converge is replicated. Everything else is verified where it is presented.

Across nodes, authentication is served by signed-token verification against published keys, not by copying live session rows into every node in the organization. Sessions are high-churn state, and broadcasting them organization-wide would multiply the most volatile rows in the estate to answer a question that public-key verification already answers.

What does converge organization-wide is the credential, so a user whose home node is gone can still authenticate somewhere else and obtain a new session there.

What a session carries​

A session is more than an identifier, which is what lets authorization be re-derived per request rather than trusted from the token:

RecordedUsed for
Entity and userThe principal the request acts as
Session type: user, agent, service, or API keyHow the session was established, which is distinct from what kind of principal owns it
Entity kindThe owning principal's kind, drawn from the same canonical identity vocabulary the rest of the directory uses
Issuing node, and the originating node and cellProvenance, and the scope the session is valid across
Federation provider, when the session came from oneSo disabling or revoking a provider can drain exactly the sessions it issued
User agent, address, last seen, expiryExpiry enforcement and anomaly detection

The provider field is the one that is easy to miss and expensive to lack. When an upstream identity provider is disconnected, the sessions it vouched for are identifiable as a set and can be drained, instead of surviving until they happen to expire.

Session type is not an identity kind

"How the session was established" and "what kind of principal owns it" are separate questions and are stored separately. An agent session and an agent entity are not the same statement, and conflating them is how an API-key session quietly acquires a human's authority. The platform keeps one vocabulary for principals and a different one for auth flows.

Anomaly detection on live sessions​

Sessions are watched while they are alive, not only checked at creation. Detected anomalies include a surge in concurrent sessions, a device switch within a session, a binding violation where a session is presented against material it was not bound to, and rapid regeneration. These feed the same governance and activity record as any other security signal.