Session
Table of contents
A compliance primitive: a time-limited authenticated channel — a durable record attesting that a given principal was authenticated at a specific moment and that the authentication result remains queryable until the session expires or is explicitly revoked. Each session is an opaque (system-generated, immutable) token; the principal reference, issuer reference, and expiry timestamp are immutable properties set on
issue. The contract the atom enforces is validity bound: a session is valid if and only if it has been issued,now < expires_at, and it has not been revoked.validatereturns four structurally distinct outcomes —valid(principal_ref, expires_at),invalid(expired),invalid(revoked),invalid(not-known)— never collapsed. Expiry timestamps are immutable; extending a session produces a new session record, never a mutation of the prior one.
Intent
Systems that authenticate principals once and then permit them to act across multiple requests for some bounded period — a browser session, an API (Application Programming Interface) access window, a mobile-client login — need a way to answer the question “is this principal still authenticated?” without repeating the full credential verification on every request. The answer to that question is a session: a bounded-lifetime record attesting that a principal completed authentication at a specific time and that the result has not been invalidated since.
The pattern isolates that bounded-lifetime attestation from the surrounding machinery. Session does not perform credential verification — that is Credential’s surface. Session does not decide what the authenticated principal may do — that is Permissions’ surface. Session does not implement the login flow that sequences credential checking, multi-factor challenges, and session issuance — that is Login’s surface (C13). Session answers one structural question: given this session token, is there an active, non-expired, non-revoked session for a known principal? The answer is one of four first-class outcomes, derivable from stored records alone.
The time-bounding discipline is the atom’s core structural commitment. expires_at is set on issue from the caller-supplied session_duration (or the deployment’s default) and is never mutated thereafter. A session that needs a longer lifetime is re-issued — a new record with a new token — not extended in place. This immutability makes every session’s validity window fully auditable from the record alone: there is no need to consult a history table, an event log, or a developer’s account of whether and when extensions were granted. The record says when validity ends; that field never changes.
This is a freestanding atom in the EOS (Essence of Software — Daniel Jackson’s framework for specifying software concepts as freestanding, composable units) sense. It has its own state (the session record and its status), its own actions (issue, validate, expire, revoke), and its own operational principles (immutable expiry, four first-class validation outcomes, revocation is absorbing). It does not implement the credential check that precedes issue, the permission check that follows validate, the multi-device token management that links sessions across devices, or the logout flow that is visible to the end user. Each is a separate composable atom or composing pattern; see Composition notes.
Summary
Session answers the question “is this login still good, and for whom?” without re-checking the password on every request. When a principal (whoever has been authenticated) logs in, a session is issued: a record tying that principal to a time-limited validity window, identified by a random token the caller presents on later requests. Checking a token returns one of four clearly separated answers — valid (and for which principal, and until when), expired, revoked, or unknown — never lumped into a single “no,” because an expired login, a deliberately cancelled one, and a token that never existed call for different responses. A session is Active until it either runs out (Expired) or is deliberately cancelled (Revoked, with who/when/why recorded); both end states are permanent. The expiry time is fixed when the session is issued and never changed — a session that needs to last longer is re-issued as a new record rather than extended in place, which keeps each session’s validity window fully auditable from the record alone. This is the mechanism behind browser sessions, API (Application Programming Interface) access tokens, mobile logins, and short-lived elevated-access windows. It deliberately does not check credentials, decide what the principal may do, or run the login flow — each is a separate pattern.
Structure
Identity model
Every session known to the system has a session_token — an opaque, cryptographically random, immutable, system-generated value produced by issue. The token is both the session’s record identity and the bearer credential the caller presents to validate. Because the token is the bearer credential, its security properties matter: it must be unguessable (sufficient entropy — see Configuration) and unpredictable from any public information about the principal or the issuance time.
Two sessions for the same principal issued at different times have different tokens; there is no relationship between a session’s token and any property of the principal. Tokens are not reused after a session expires or is revoked.
The token-as-identity model is deliberate: it mirrors how session systems actually work (the cookie IS the session identifier) and makes validate a simple lookup — the caller presents a token and the atom looks it up by that token. The alternative (a separate opaque session_id plus a separate session_token) adds indirection without structural benefit for this atom’s scope.
Configuration
Two deployment-set parameters govern the atom; both are named here because other sections depend on them:
- Default session duration — the
expires_atwindow applied whenissueis called with a nullsession_duration. Must be configured; a session store with no duration policy is a deployment misconfiguration, andissuerejects withinvalid-requestwhen the default is absent (see Decision points). - Token entropy — the
session_token’s random material must come from a cryptographically secure random source with at least 128 bits of entropy, sufficient for negligible collision probability (Invariant 7) and unguessability (Identity model). Per the Logic Confinement Principle (seeexecution-contract.md), the random material is an injected input toissue— supplied by the deployment’s entropy source at the seam, never generated inside the core transition — so the transition remains a pure function of its inputs and the entropy source remains auditable at the deployment layer.
Token format and token storage security (raw vs. hashed at rest) remain deployment-configuration concepts; see Edge cases.
Inputs and Outputs
Actions:
issue(principal_ref, issued_by_ref, session_duration) → session_token | rejected(invalid-request | storage-failure)validate(session_token) → valid(principal_ref, expires_at) | invalid(expired | revoked | not-known)expire(session_token) → expired | rejected(invalid-request | not-active | not-known | storage-failure)revoke(session_token, revoked_by_ref, reason) → revoked | rejected(invalid-request | already-terminal | not-known | storage-failure)
Inputs:
principal_ref— an opaque reference to the principal for whom the session is being issued. The atom treats this as opaque; it does not validate that the principal exists in any registry or that they were actually authenticated. The caller (the Login composition, or whatever issues sessions) is responsible for ensuringissueis called only after successful authentication.issued_by_ref— an opaque reference to the mechanism that issued the session (e.g., a Login service, an SSO (Single Sign-On) system, an administrative process). Recorded as an immutable property of the session. Non-null and non-empty required.session_duration— a duration value (in seconds, or a deployment-standard unit) specifying how long the session should be valid. If null, the deployment’s default session duration applies. Must be a positive value; zero or negative is rejected asinvalid-request.session_token— the bearer credential the caller presents tovalidate,expire, andrevoke. Produced byissue; presented by the caller on subsequent calls.revoked_by_ref— an opaque reference to the actor or mechanism performing the revocation. Recorded as an immutable property of the revocation event. Non-null and non-empty required.reason— a caller-supplied reason string for the revocation. Recorded as an immutable property of the revocation event. Non-null and non-empty required.
String input policy (applies to every string input above). Values are treated byte-exact: no trimming, no Unicode normalization, no case folding is applied before storage or comparison — equality (including the principal_ref query surface in Feedback) is byte-for-byte. A whitespace-only string counts as empty and is rejected wherever non-empty is required. The deployment sets a maximum length per string input; a value exceeding it is rejected as invalid-request. The opaque references (principal_ref, issued_by_ref, revoked_by_ref) are caller-supplied identifiers — byte-exactness means callers own canonicalization; two refs differing only in case or normalization form are two distinct principals to this atom.
Outputs:
- The current set of session records. For each:
session_token,principal_ref,issued_by_ref,issued_at,expires_at,status,expired_at(nullable),revoked_at(nullable),revoked_by_ref(nullable),revocation_reason(nullable). issuereturns a newsession_tokenon success, or a rejection naming the failed precondition.validatereturnsvalid(principal_ref, expires_at)orinvalid(reason). No state change.expirereturnsexpiredon success, or a rejection.revokereturnsrevokedon success, or a rejection.
State
Each session record carries a status field. The state machine is:
- Active — the session may be validated.
validatechecks expiry and returnsvalidorinvalid(expired)depending onnowvs.expires_at. This is the only non-terminal state. - Expired —
expires_athas passed. The session can no longer be validated asvalid. Terminal. - Revoked — explicitly revoked. The session can no longer be validated as
valid. Terminal.
Transitions:
issue(principal_ref, issued_by_ref, session_duration)→ a new session record is created inActivewith a freshsession_token, the suppliedprincipal_refandissued_by_ref,issued_at = now, andexpires_at = now + session_duration(or the deployment default if session_duration is null). Returnssession_token.expire(session_token)→ the record’sstatustransitions fromActivetoExpired;expired_at = nowis recorded. No other fields change. May be called by a background scheduler, or triggered lazily as a side effect ofvalidatedetectingnow >= expires_at. In the lazy path, the transition occurs atomically with thevalidatecall that detects expiry; thevalidatereturnsinvalid(expired).revoke(session_token, revoked_by_ref, reason)→ the record’sstatustransitions fromActivetoRevoked;revoked_at = now,revoked_by_ref, andrevocation_reasonare recorded. No other fields change.- (no transitions out of Expired or Revoked)
Each session record carries:
session_token— opaque, cryptographically random, immutable, system-generated. Set onissue. Never changes.principal_ref— opaque reference to the authenticated principal. Set onissue. Never changes.issued_by_ref— opaque reference to the issuing mechanism. Set onissue. Never changes.issued_at— wall-time whenissuewas called. Immutable.expires_at— the time at which this session expires. Set onissueasissued_at + session_duration. Never changes. Never null — every session has a finite lifetime.status— current status. Set toActiveonissue. Transitions toExpiredorRevokedvia their respective actions. Never returns toActiveonce terminal.expired_at— set whenstatustransitions toExpired. Null otherwise. Immutable once set.revoked_at— set whenstatustransitions toRevoked. Null otherwise. Immutable once set.revoked_by_ref— opaque reference to the revoking actor. Null until revocation. Immutable once set.revocation_reason— caller-supplied reason string. Null until revocation. Immutable once set.
Flow
- Principal completes authentication. The composing Login pattern calls
issue(principal_ref, issued_by_ref, session_duration) → session_token. The atom creates the session record and returns the token. Login delivers the token to the caller (typically as a cookie or Authorization header value). - Principal makes a subsequent request. The caller presents the session_token. The composing pattern calls
validate(session_token). Ifvalid(principal_ref, expires_at)is returned, the composing pattern proceeds with the principal_ref as the authenticated identity. Ifinvalid(...)is returned, the composing pattern redirects to re-authentication. - Session approaches or reaches expiry. Either a background scheduler detects sessions where
now >= expires_atand callsexpire(session_token)on each, or the nextvalidatecall for the expired session detects expiry lazily and atomically transitions the record toExpired. Both paths produce the same observable outcome:validatereturnsinvalid(expired). - Principal logs out, or an administrative action invalidates the session. The composing pattern calls
revoke(session_token, revoked_by_ref, reason). The atom records who revoked it, when, and why, and transitions the session toRevoked. Subsequentvalidatecalls returninvalid(revoked). - Principal re-authenticates. The composing Login pattern issues a new session:
issue(...)produces a new token. The prior session (expired or revoked) remains in the record store as an immutable history entry.
Decision points
At issue(principal_ref, issued_by_ref, session_duration):
principal_refandissued_by_refmust be non-null and non-empty; otherwiseinvalid-request.session_durationmust be positive if supplied; null uses the deployment default. A zero or negative value returnsinvalid-request.- The deployment default session duration must be configured; if absent,
issuereturnsinvalid-request. (A session store with no duration policy is a deployment misconfiguration, not a valid operating state.) expires_atis computed asissued_at + session_duration(using the wall clock at the momentissueexecutes). This computation happens once, at issue time, and the result is stored as an immutable field. The atom never re-derivesexpires_atfrom the current time.- If the store write fails,
storage-failureis returned with no partial record in the store.
At validate(session_token):
- The atom looks up the session by
session_token. If no record is found,invalid(not-known). This is a lookup miss — structurally distinct from a validation failure on a known session. - If the record is found and
status = Revoked,invalid(revoked)— regardless of whetherexpires_atis still in the future. Revocation takes precedence over expiry in the outcome vocabulary; a session can be revoked before it expires. - If the record is found,
status != Revoked, and (status = ExpiredORnow >= expires_at),invalid(expired). Thestatus != Revokedguard is syntactic, not just an ordering convention: an implementation must not short-circuit on the cheapnow >= expires_atnumeric test before consultingstatus, or a revoked-and-past-expiry session would returninvalid(expired)in violation of the revoked-takes-precedence rule above. In the lazy-expiry path, the atom may atomically write theExpiredstatus transition at this point. - If the record is found,
status = Active, andnow < expires_at,valid(principal_ref, expires_at). validatedoes not modify any record except for the lazyExpiredtransition described above. It increments no counter, touches no other field, and produces no other side effect.
At expire(session_token): preconditions are evaluated in the order listed — not-known, then not-active, then invalid-request — so any two conformant implementations return the same rejection for the same input (an early-revoked session passed to expire returns not-active, never invalid-request):
session_tokenmust reference a known record; otherwisenot-known.- The referenced session’s
statusmust beActive; otherwisenot-active. A session already inExpiredorRevokedstatus cannot be expired again. - The session’s
expires_atmust have passed (now >= expires_at); otherwiseinvalid-request. A caller who wishes to terminate a session before its natural expiry should userevoke, which records attribution. This check prevents a buggy scheduler or operator from formally terminating a live session through the wrong action. - The transition to
Expiredand the write ofexpired_atare atomic. If the store write fails,storage-failureis returned with no state change committed.
At revoke(session_token, revoked_by_ref, reason):
session_tokenmust reference a known record; otherwisenot-known.- The referenced session’s
statusmust beActive; otherwisealready-terminal. A session whoseexpires_athas passed is treated as terminal for the purpose of this check —revokereturnsalready-terminaland may lazily transition the record toExpired. revoked_by_refandreasonmust be non-null and non-empty; otherwiseinvalid-request. Revocation without attribution and a stated reason is a compliance process violation; the atom enforces the constraint at call time.- The transition to
Revokedand the writes ofrevoked_at,revoked_by_ref, andrevocation_reasonare atomic. If the store write fails,storage-failureis returned with no state change committed.
Behavior
validateis a pure read (with one narrow exception). The atom does not update any counters, touch any fields, or produce any side effects as a result ofvalidate, except: when the lazy-expiry path fires (the session is Active butnow >= expires_at), the atom atomically writes theExpiredstatus transition as part of thevalidatecall. This is the only state changevalidatemay produce, and it is bounded — the transition happens at most once per session, and only in the direction of termination.- Lazy terminal transitions are best-effort bookkeeping;
statusis not the liveness authority. The lazyExpiredwrite (invalidateorrevoke) is a guarded compare-and-set onstatus = Active, so it can never double-fire against a concurrent terminal transition; and it is best-effort — if the write is lost to a crash after the result was returned, the record legitimately showsstatus = Activewithexpires_atin the past until the next touch reaps it. This is harmless because every validity determination derives from the timestamps (expires_at,revoked_at), never fromstatusalone:status = Activedoes not imply non-expired. Any query for live sessions (including the administrative queries in Feedback and the auditor reconstruction in Generation acceptance) must apply the derived predicatestatus = Active AND now < expires_at; a rawstatus = Activewithexpires_atin the past is a not-yet-reaped Expired record, not a live session. nowis an injected input. Every action that reads the clock (issue,validate,expire,revoke) takesnowas an explicit injected input supplied at the deployment seam — per the Logic Confinement Principle (seeexecution-contract.md), the core transition never reads a wall clock internally, so each transition is a pure function of (record state, inputs,now). Clock quality — honesty, monotonicity, skew — is handled at the deployment layer (see Edge cases), but clock access is structurally confined to the seam.- Expiry timestamp immutability is absolute. No action the atom exposes can change
expires_at. Extending a session requires callingissueagain, which produces a new record with a new token. The old record remains in the store. A composing system that wants to refresh a session must issue a new one and deliver the new token to the caller; the old token becomes independently invalid at its ownexpires_at. - Revocation precedes expiry in validation logic. A session that was revoked before its
expires_atreturnsinvalid(revoked), notinvalid(expired), even if both conditions hold at validate time. The distinction matters operationally: a revoked session implies a deliberate decision (logout, compromise response, administrative action); an expired session implies normal end-of-life. The composing system may take different actions in each case. issuemakes no authentication judgment. The atom issues a session for whateverprincipal_refit is given. It does not verify that the principal was actually authenticated, that a valid credential exists, or that any upstream check was performed. That responsibility belongs entirely to the caller. An implementation that callsissuewithout first verifying a credential has a process error that the atom cannot detect or prevent — it is the Login composition’s (C13) wiring that ensuresissueis called only afterCredential.verifyreturnsverified.- Revocation attribution is mandatory and immutable.
revoked_by_refandrevocation_reasonare required inputs torevoke; null or empty values are rejected. Once written, they do not change. An auditor reading the session record after revocation can determine who revoked the session and why without consulting any external system.
Feedback
Each successful action produces an observable, measurable change:
- After
issue— a new session record appears inActivestatus with a freshsession_token, the suppliedprincipal_refandissued_by_ref,issued_at, andexpires_at. Total record count increases by one. The token is returned to the caller. - After
validate— no state change (except possible lazy Expired transition). Returnsvalid(principal_ref, expires_at)orinvalid(reason). - After
expire— the target session’sstatustransitions toExpired;expired_atis set. - After
revoke— the target session’sstatustransitions toRevoked;revoked_at,revoked_by_ref, andrevocation_reasonare set.
Rejected actions produce named rejection codes observable to the caller: invalid-request, storage-failure, not-active, not-known, already-terminal. validate’s four outcomes (valid, invalid(expired), invalid(revoked), invalid(not-known)) are first-class results, not rejections — they are the normal vocabulary of a validation query, each carrying distinct operational meaning.
The session store is queryable. Per-record fields (all fields except the deployment’s internal token storage format) are observable to authorized administrative surfaces. Composing patterns may query sessions by principal_ref to support “show all active sessions for this account” and “revoke all sessions for this account” administrative operations.
Invariants
Invariant 1 — Issue immutability. Once a session record is created, session_token, principal_ref, issued_by_ref, issued_at, and expires_at never change. The only fields that may change after issue are status (and the associated terminal-state timestamp fields set when a terminal transition fires).
Invariant 2 — Expiry timestamp immutability. expires_at is computed once at issue time and never mutated by any action the atom exposes. An expire action does not change expires_at; it changes status to Expired. The distinction matters: expires_at states when validity ends; status = Expired states that the terminal transition has been formally recorded.
Invariant 3 — Validity bound conjunctive. A validate call returns valid(...) if and only if all three conditions hold simultaneously: the session token references a known record, status = Active, and now < expires_at. Any single condition failing produces an invalid(...) result. The conditions are checked in this order: not-known first (lookup miss), then revoked (Revoked status takes precedence), then expired (Expired status or now >= expires_at), then valid.
Invariant 4 — Revocation absorbing. Once a session’s status is Revoked, no subsequent validate call for that token returns valid. Because the terminal state is absorbing (Invariant 5), a revoked session cannot be un-revoked. The invalid(revoked) outcome is permanent for a given token.
Invariant 5 — Terminal state absorbing. A session in Expired or Revoked status admits no further state transitions. expire on a non-Active session returns not-active; revoke on a terminal session returns already-terminal.
Invariant 6 — Four structurally distinct validate outcomes. valid(principal_ref, expires_at), invalid(expired), invalid(revoked), and invalid(not-known) are always structurally distinct result values. No implementation may collapse invalid(expired) and invalid(revoked) into a single invalid result, nor collapse invalid(not-known) with either failure case. Each outcome carries different operational meaning and must be distinguishable by the caller.
Invariant 7 — Session token uniqueness. No two session records share a session_token across the lifetime of the system. Tokens are not reused after a session expires or is revoked. This invariant requires that the token generation mechanism produce values with negligible collision probability — see Configuration.
Invariant 8 — Revocation attribution completeness. Every session record in Revoked status has non-null revoked_at, revoked_by_ref, and revocation_reason. A Revoked record missing any of these fields is evidence of a process violation; the atom’s revoke action enforces the non-null constraint at call time.
Invariant 9 — Session durability. Once issue returns a session_token, the session record is durably persisted. A storage-failure rejection guarantees no partial record was written. The record count is monotonically non-decreasing; the atom provides no deletion surface. Cascading deletion under a retention policy is the composing pattern’s responsibility.
Invariant 10 — Every session has a finite lifetime. expires_at is never null. Every session issued by this atom has a deterministic expiry time. Sessions that do not expire are not expressible; an implementation that issues sessions without an expires_at has violated this invariant.
Invariant 11 — Expiry absorbing. Once a session’s expires_at has passed, no subsequent validate call for that token returns valid. A session past its expires_at cannot satisfy the conjunctive validity condition of Invariant 3 (which requires now < expires_at). Because the terminal Expired state is absorbing (Invariant 5), an expired session cannot be un-expired. The invalid(expired) outcome is permanent for a given token once expires_at is reached. This invariant is the expiry analog of Invariant 4 (Revocation absorbing); both are structural consequences of terminal finality combined with the validity bound, made explicit here so the verification surface is symmetrically stated across both terminal paths that preclude further valid sessions.
Invariants 1, 2, and 10 together give the temporal auditability property — every session’s validity window is fully determined from a single record with no mutable fields. Invariants 3 and 6 give the validation clarity property — the outcome of any validate call is unambiguous, distinguishable, and determined by record state alone. Invariants 4 and 11 give the terminal finality property — neither a revoked nor an expired session can resurface as valid via any path.
Examples
Browser login — issue and validate
A user logs in via the Login composition. After successful credential verification, Login calls issue(principal_ref: user_u91, issued_by_ref: login_svc_l01, session_duration: 3600) → session_token: tok_abc123. The atom creates a session record: status: Active, issued_at: 2026-09-01T10:00:00Z, expires_at: 2026-09-01T11:00:00Z. The token is delivered to the user’s browser as a session cookie.
Twenty minutes later, the user requests a protected page. The host system calls validate(session_token: tok_abc123) → valid(principal_ref: user_u91, expires_at: 2026-09-01T11:00:00Z). The atom finds the record, confirms status = Active and now (10:20Z) < expires_at (11:00Z), and returns the result. No state changes. The host system serves the page to user_u91.
Logout — revoke
The user clicks “Log out.” The host system calls revoke(session_token: tok_abc123, revoked_by_ref: user_u91, reason: "user-initiated-logout") → revoked. The atom transitions the record to Revoked, writing revoked_at: 2026-09-01T10:45:00Z, revoked_by_ref: user_u91, revocation_reason: "user-initiated-logout". The record’s expires_at is unchanged at 2026-09-01T11:00:00Z — that field is immutable.
If the user’s browser re-presents the old cookie: validate(session_token: tok_abc123) → invalid(revoked). The revoked status takes precedence; the atom does not return invalid(expired) even though the session could have expired naturally in 15 minutes.
Session expiry — lazy path
The user closes their browser without logging out. The session expires at 11:00Z. No expire call is made. At 11:30Z, the user opens a new browser tab that still has the cookie and makes a request. The host system calls validate(session_token: tok_abc123) → invalid(expired). The atom finds the record: status = Active (not yet formally transitioned), expires_at = 2026-09-01T11:00:00Z, and now = 2026-09-01T11:30:00Z. Since now >= expires_at, the atom atomically transitions the record to Expired (writing expired_at: 2026-09-01T11:30:00Z) and returns invalid(expired). The host system redirects to re-authentication.
Rejection paths
issue — invalid-request (zero-duration): A host system accidentally calls issue(principal_ref: svc_s03, issued_by_ref: api_gateway_g01, session_duration: 0) → rejected(invalid-request). A zero-duration session is not a valid operating state; the atom rejects it at issue time.
revoke — already-terminal: An incident-response script attempts to revoke a session that already expired naturally: revoke(session_token: tok_abc123, revoked_by_ref: admin_a01, reason: "incident-response") → rejected(already-terminal). The session is already in Expired status; the atom rejects the revoke call. The session’s revocation_reason is not set — the session was not revoked, it expired. If the composing system needs to record an administrative annotation on an expired session, that is a composing-pattern concept outside the atom’s scope.
validate — invalid(not-known): A caller presents a token that was never issued (or was generated by a different system): validate(session_token: tok_forged_xyz) → invalid(not-known). The atom finds no record for this token. This result is structurally distinct from invalid(revoked) — no session was revoked; no session exists.
Regulated adversarial scenarios
Three scenarios the atom must survive in regulated contexts:
Regulator audit. A HIPAA (Health Insurance Portability and Accountability Act) compliance auditor asks “can you prove that access to patient record PR-4411 at 14:32 UTC on 2026-10-15 was under a valid, non-revoked session?” The auditor queries the session store for all sessions active at 14:32Z for the principal involved. The query finds tok_abc123: status: Active (at the time; now Expired), issued_at: 2026-10-15T14:00:00Z, expires_at: 2026-10-15T15:00:00Z, revoked_at: null. Invariant 3 (validity bound conjunctive) is the structural answer: at 14:32Z, the session was Active and expires_at had not yet passed, so validate would have returned valid at that time. The auditor can verify this from the record alone — issued_at, expires_at, and revoked_at: null are sufficient.
Disputed access event. A user claims “I did not access my account at 03:15 AM on 2026-11-20 — someone else was using my session.” The investigator queries the session store for sessions belonging to the user’s principal_ref that were Active at 03:15. The query finds tok_abc123: issued_at: 2026-11-19T22:00:00Z, expires_at: 2026-11-20T06:00:00Z, issued_by_ref: login_svc_l01. The session was active at the time of the disputed access. Whether the session token was stolen (and by whom) is a separate investigation; the atom’s records bound the forensic window: the session was issued through the Login service at 22:00, was valid at 03:15, and was never revoked before the access occurred. The composing Login pattern’s attestation records (from Actor Identity) establish who authenticated at 22:00 and what credential was verified.
Breach investigation. A security team discovers that session tokens were exposed in a log file. They need to know which principals are affected. The investigator queries the session store for all sessions issued_by_ref: api_gateway_g01 between 2026-12-01T00:00:00Z and 2026-12-03T12:00:00Z (the exposure window). The query returns 47 sessions across 31 distinct principal_ref values. The response team calls revoke on all 47 sessions (for those not yet expired or revoked), recording revoked_by_ref: security_team_s01 and reason: "log-exposure-incident-2026-12-03". Invariant 8 (revocation attribution completeness) ensures that an auditor reading the records six months later can reconstruct exactly which sessions were revoked, by whom, when, and why — without consulting the incident response team or their runbooks.
Edge cases and explicit non-goals
What this atom does not cover:
- Credential verification. Whether the principal was actually authenticated before
issuewas called is entirely the caller’s responsibility. The atom issues a session for whateverprincipal_refit receives. The Login composition (C13) is the pattern that wires Credential verification to Session issuance; without that wiring,issuecan be called for any principal by any caller. The atom provides no guard against this. - Multi-factor sequencing. Requiring that a principal present two credentials before a session is issued (password then TOTP (Time-based One-Time Password); hardware key then PIN (Personal Identification Number)) is a composing-pattern concept. Login (C13) is where multi-factor sequencing is expressed. The atom sees only the final
issuecall. - Session renewal and sliding windows. The atom does not implement session renewal (extending
expires_atby some duration on each active request). Sliding-window sessions require the composing pattern to issue a new session (new token, newexpires_at) on some renewal trigger and to invalidate the prior token. Both operations are within scope of the atom’s actions; the renewal policy is the composing pattern’s concept. - Device binding. Whether a session token is bound to a specific device, browser fingerprint, or IP address is a composing-pattern concept. The atom stores no device information; binding a token to a device context is an additional validation step the composing system performs before calling
validate. - Session concurrency limits. Whether a principal may have N concurrent active sessions (and what happens when the limit is exceeded) is a composing-pattern concept. The atom imposes no limit on how many Active sessions a given
principal_refmay have. Enforcing a one-session-per-principal or K-sessions-per-principal rule is the composing Login pattern’s obligation. - Logout propagation across devices. When a user logs out on device A, ensuring their session on device B is also revoked requires the composing system to query sessions by
principal_refand revoke all of them. The atom supports this via itsprincipal_ref-queryable store, but does not implement the propagation automatically. - Permission checking. What the principal identified by
validate’sprincipal_refresult is permitted to do is Permissions’ surface. Session answers “is this principal currently authenticated?”; Permissions answers “is this principal allowed to do this thing?” Session-Gated Authorization (C14) is the composition that wires them: Permissions checks are blocked ifvalidatereturnsinvalid. - Token format and encoding. The atom produces an opaque
session_token. Whether that token is a random hex string, a UUID (Universally Unique Identifier), a JWT (JSON Web Token — a compact, signed token format carrying claims), or an opaque blob is a deployment-configuration concept. If the token is a JWT, the atom’s immutability invariants take precedence over JWT’s native extension claims —expin a JWT must match the storedexpires_atand the token must not be extended without re-issuance. - Token storage security. Whether the
session_tokenis stored in the record store as a raw value or as a cryptographic hash (to prevent a database breach from yielding usable tokens) is a deployment-configuration concept. Storing tokens raw is simpler; storing hashed tokens (and matching presented tokens by hashing them before lookup) is more secure. Both are conformant with the atom’s invariants. The choice should be documented in the composing Login pattern’s configuration. - Clock accuracy.
issued_at,expired_at,revoked_at, andexpires_atare captured from the deployment’s clock. Whether that clock is honest, monotonic, or synchronized is handled at the deployment layer. Trusted timestamping (RFC (Request for Comments — the internet engineering standards series) 3161) is a composing pattern for deployments that require externally verifiable timestamps. - Session store tamper-evidence. As with all atoms in the compliance cluster, the session store can be composed with Tamper Evidence for deployments requiring cryptographic proof that no session record was retroactively altered.
Composition notes
Session is freestanding. It is named by Login (C13) and Session-Gated Authorization (C14) as a constituent atom:
- Credential — Credential verifies the principal’s authentication material; Session persists the result. The Login composition (C13) is the wiring: a successful
Credential.verifyproduces aSession.issuecall. The two atoms are distinct: Credential answers “did the right material arrive?”; Session answers “is this principal still within a valid authentication window?” - Actor Identity — in regulated deployments, the
issuecall may be paired with an Actor Identityattestcall to produce a non-repudiable record that a specific actor initiated the session. The Audit Trail substrate is where this attribution is recorded. - Permissions — Session-Gated Authorization (C14) gates every Permissions query on Session validity. The composing pattern calls
validatebefore calling any Permissions action; ifvalidatereturnsinvalid, the Permissions check is skipped and the action is rejected. - Audit Trail — in regulated deployments,
issueandrevokeevents should be recorded in the Audit Trail. The atom does not mandate this; it is the Login composition’s (C13) obligation to wire session lifecycle events into the audit record. - Tamper Evidence — for regulated deployments, the session store (including the revocation attribution history) should be hash-chained and externally anchored.
- Login — wires Credential verification to Session issuance, both attested under the verified principal. Carries the cascade invariant: revocation of the underlying Credential invalidates every Session derived from it — a property of the Login composition’s emergent state, not of either constituent atom alone.
- Session-Gated Authorization — gates every Permissions check on Session validity. The pre-check fires before the Permissions call; a stale or revoked session rejects the check before Permissions is consulted.
- External Onboarding — registers the principal’s credential during onboarding; the principal then calls Login with that credential to establish their first session. External Onboarding is the identity admission gate; Login is the first-session issuance step. Session is not a constituent of External Onboarding — it enters the picture via Login in the step immediately following a successful
onboardcall. - Privileged Access Provisioning — calls
Session.validate(session_token)as the first step ofexercise_access. A non-validresult — expired, revoked, or not-known — blocks the access exercise before the Capability token is presented. Session is queried read-only; this composition does not issue or revoke sessions.
Standards references
- NIST (National Institute of Standards and Technology — US federal standards body) SP 800-63B §7 (Session Management) — the primary standard for session management in authentication systems. Requirements for session binding, session duration, reauthentication triggers, and session termination correspond directly to this atom’s behavioral commitments. The atom’s
expires_atimmutability and re-issuance discipline implement 800-63B’s requirement that session extensions produce new session identifiers. - OWASP ASVS V3 (Application Security Verification Standard — Session Management) — the OWASP (Open Worldwide Application Security Project) verification standard for session management security. Requirements for session token randomness, expiry, revocation on logout, and protection against fixation attacks are the deployment-configuration surface of this atom.
- RFC 6265 (HTTP State Management Mechanism) — the cookie standard. The atom’s
session_tokenis the value delivered in aSet-Cookieresponse header in browser-based deployments. The atom does not specify cookie attributes (Secure, HttpOnly, SameSite) — those are the composing pattern’s configuration obligations. - SAML 2.0 §4.1.4 (Session Establishment and Termination) — the SAML (Security Assertion Markup Language — an XML standard for exchanging authentication data between identity and service providers) session model maps to this atom:
AuthnStatementissuance corresponds toissue;SessionNotOnOrAftercorresponds toexpires_at;SLO(Single Logout) corresponds torevoke. The atom’s re-issuance discipline aligns with SAML’s prohibition on reusing assertion IDs. - RFC 6819 (OAuth 2.0 Threat Model and Security Considerations) — identifies session-related threats (token theft, session fixation, CSRF — Cross-Site Request Forgery) that the deployment-configuration layer of this atom addresses. The atom’s immutability and revocation invariants mitigate token theft’s blast radius: a stolen token is bounded by
expires_atand can be revoked. - OIDC Session Management 1.0 (OpenID Connect Session Management) — the OpenID Connect (OIDC — an identity layer built on OAuth 2.0) session management specification. The atom’s
session_tokencorresponds to an OIDC session’s identifier;expireandrevokecorrespond to OIDC’s Front-Channel and Back-Channel Logout protocols. - HIPAA §164.312(a)(2)(iii) (Automatic Logoff) — the HIPAA automatic logoff requirement: electronic information systems must terminate after a period of inactivity. The atom’s
expires_atfield is the structural mechanism for this requirement; the composing deployment configures the timeout. - PCI DSS (Payment Card Industry Data Security Standard — the card networks’ mandatory security rules for cardholder data) Requirement 8.6 (Session Management) — session timeout and re-authentication requirements for payment systems. The atom’s
expires_atimmutability and re-issuance discipline satisfy the structural portion of these requirements.
Inherited from:
- Daniel Jackson, The Essence of Software — the freestanding-atom posture; the discipline of keeping credential verification, session persistence, permission checking, and logout propagation as separate composable atoms rather than absorbing them here.
- IETF (Internet Engineering Task Force) RFC 4120 (Kerberos) — Kerberos tickets are the canonical precedent for time-bounded, revocable authentication session records. The atom’s immutable
expires_at, two-terminal-state machine, and revocation attribution discipline are the structured-natural-language expression of Kerberos’ core concepts.
Generation acceptance
A derived implementation of Session is acceptable — in the regulator-acceptance sense — when an external auditor, given the session record store, can do all of the following without recourse to source code, runbooks, or developer narration:
- Confirm that every session has a finite, immutable expiry. For every session record in the store, confirm that
expires_atis non-null and that no record shows two different values forexpires_at(i.e., confirm the field never changes after issue). A session with a null or ever-changingexpires_atviolates Invariants 2 and 10 and is evidence of an implementation defect. - Reconstruct which sessions were active at any historical point in time. Given a timestamp T, query all records where
issued_at <= Tandexpires_at > Tand(revoked_at is null OR revoked_at > T). The result is the set of sessions thatvalidatewould have returnedvalidfor at time T. This reconstruction requires no external data beyond the session store. - Confirm revocation attribution completeness. For every record with
status = Revoked, confirm thatrevoked_at,revoked_by_ref, andrevocation_reasonare all non-null and are consistent with the record’sstatus. ARevokedrecord missing any of these fields is a violation of Invariant 8 and evidence of a process violation. - Confirm the four validate outcomes are structurally distinct. Inspect the implementation’s
validatereturn surface and confirm thatvalid,invalid(expired),invalid(revoked), andinvalid(not-known)are distinguishable values — not collapsed into a boolean or a singleinvalidcode. This check may require examining the implementation’s API contract rather than the record store alone; it is the behavioral commitment of Invariant 6. - Confirm terminal state finality. For any record in a terminal state (
ExpiredorRevoked), confirm that the record’sstatusfield has not been changed back toActive. Because eachsession_tokenis unique (Invariant 7), there is no “second record” to look for; the check is whether the record itself shows a terminal status that is irrevocably set. A record whosestatusappears asActiveafter a terminal transition was recorded is evidence of an implementation defect — terminal states are absorbing and may not be reversed.
This is the generator’s contract: any implementation derived from this atom must produce a session store that passes all five checks above. The five checks operationalize the Intent section’s structural question — given this session token, is there an active, non-expired, non-revoked session for a known principal? — as records-alone verification. The bar is the regulator’s question — “can you prove that authenticated access to protected resources was bounded by valid, non-revoked sessions throughout?” — not the developer’s intuition.
Status
grounded on Final Critique 4 — 2026-06-10 (scheduled rescan, council-run — three rounds to clean, findings folded; see Lineage §Scheduled rescan 2026-06-10. Formal-layer vote reconsidered 2026-06-03 to NO — formal-not-warranted; the aggressive-bar YES was downgraded on a second pass — conjunctive validity is a conjunction of record fields and revoked-precedes-expired is precedence by insertion order, both records-alone; the interleaving worth modeling lives in the Session-Gated Authorization composition, which keeps its model. See Lineage §Formal-layer vote. Cleared grounded (English) — formal layer pending; full prose round was grounded on Final Critique 4.) — three baseline rounds (Pass 1/2/3 each) plus Final Critique 4 complete. Six findings total; all resolved in-pattern. Final Critique 4 closed clean (foundational findings: zero; refining findings: one documented, not blocking).
Classification (post-flatten): stored flat as atoms/session.md — no category folder. Session is an authentication-adjacent primitive with meaningful non-regulated uses (wherever authentication persistence is required), so its regulated and security classifications are overlays derived from its composers, not a folder it is filed under. This resolves the atom’s former provisional compliance/ placement and the question of relocating it to a security or identity folder: under the usage-derived taxonomy, security is an overlay it carries (derived from its identity/access standards), not a domain or a directory.
Lineage notes
Conventions inherited. This atom carries the regulated and security overlays (both derived from its composers) and includes Regulated adversarial scenarios and Generation acceptance from the first draft, per the methodology inherited from pressure-testing.md. These conventions are inherited from the methodology directly, not re-derived from any predecessor atom.
Structural decisions made in draft.
session_tokenas identity. The token presented tovalidateis also the record’s identity — there is no separatesession_id. Rationale: this mirrors how session systems actually operate (the cookie IS the session identifier) and keepsvalidatea simple lookup. The alternative (separate session_id + session_token) adds indirection without structural benefit at the atom layer; token storage security (raw vs. hashed) is a deployment-configuration concern, not an atom-layer distinction.- Four structurally distinct
validateoutcomes.valid,invalid(expired),invalid(revoked),invalid(not-known)are never collapsed. Collapsing expired and revoked into a singleinvalidwould obscure operationally distinct situations — a revoked session implies a deliberate action; an expired session implies normal end-of-life. Collapsing not-known with the failure cases would merge a lookup miss with a validation failure on a known session, losing forensic precision. - Revocation takes precedence over expiry in validation ordering. A session that is both
Revokedand past itsexpires_atreturnsinvalid(revoked), notinvalid(expired). Rationale: revocation is a deliberate act and should surface to the caller as such, even when the session would have expired anyway. The composing system takes a different action (e.g., clearing revocation-reason vs. simply prompting re-authentication) depending on which outcome it receives. expires_atis never null. Every session has a finite lifetime. The atom rejects zero-duration and null-duration sessions at issue time. Infinite sessions are not expressible. This makes “every session eventually terminates” a structural property rather than a policy aspiration.issuemakes no authentication judgment. The atom does not validate that authentication occurred. This is intentional: the atom’s job is time-bounded session management, not authentication. Ensuringissueis called only after authentication is the Login composition’s wiring obligation. The atom trusting the caller is an explicit, defended architectural choice — not an oversight.
Round 1.
Pass 1 — GRID structural (GRID — the nine-node completeness framework: Intent, System, Friction, Flow, Decision, Feedback, State, Behavior, Proof). Two findings. F1 — revoke missing storage-failure: revoke writes four fields atomically but its rejection vocabulary did not include storage-failure, unlike issue. Fixed: storage-failure added to revoke signature and Decision points. F2 — expire missing storage-failure: same pattern — expire writes status and expired_at atomically but had no storage-failure. Fixed: storage-failure added to expire signature and Decision points.
Pass 2 — EOS conceptual independence. Clean. Atom is freestanding; no other atom named in the specification body.
Pass 3 — Linus adversarial. Two findings. F3 — revoke on a session past expires_at but status still Active: If the lazy expiry transition has not yet fired, a session past expires_at has status = Active. The Decision points for revoke only checked status; a session in this state could be revoked, producing status = Revoked on a session that had already effectively expired. This creates the same operational ambiguity caught in Credential (F4). Fixed: revoke Decision points now state that a session whose expires_at has passed is treated as terminal — revoke returns already-terminal and may lazily transition the record to Expired. F4 — expire allows pre-expiry calls: Decision points only checked status = Active; a buggy scheduler or operator could call expire(token) while the session was still within its validity window, formally terminating a live session. Early termination belongs to revoke. Fixed: added pre-expiry check to expire Decision points — now >= expires_at is required; calls where now < expires_at return invalid-request. invalid-request added to expire signature accordingly.
Round 1 closed. Four findings; all resolved in-pattern; none deferred.
Round 2.
Pass 1 — GRID structural. Clean. All nine nodes consistent after Round 1 fixes.
Pass 2 — EOS conceptual independence. Clean. (Note: the Behavior section’s issue-makes-no-authentication-judgment paragraph references C13 and Credential.verify by name; these are parenthetical explanatory references placing the atom’s design choice in context, not dependency declarations. The atom remains freestanding — it does not require Credential or C13 to function.)
Pass 3 — Linus adversarial. Clean. No new findings.
Round 2 closed. Zero findings.
Round 3.
Pass 1 — GRID structural. Clean.
Pass 2 — EOS conceptual independence. Clean.
Pass 3 — Linus adversarial. Clean. All nine invariants consistent with updated action signatures. Validation ordering (not-known → revoked → expired → valid) correctly stated and consistent with validate Decision points.
Round 3 closed. Zero findings. Baseline complete (Rounds 1–3). Proceeding to Final Critique.
Final Critique 4 (Super Torvalds).
One foundational finding fixed; one refining finding fixed for wording; one refining finding noted.
FC1 — Missing “Expiry absorbing” invariant (foundational, fixed in-pattern). Invariant 4 (Revocation absorbing) explicitly states that a Revoked session never returns valid via that token. The symmetric property for Expired sessions — once expires_at is reached, no validate returns valid — was only derivable by chaining Invariant 3 (conjunctive validity bound requiring now < expires_at) with Invariant 5 (terminal state absorbing). The missing explicit invariant left the verification surface asymmetrically stated. Fixed: added Invariant 11 — “Expiry absorbing” — mirroring Invariant 4’s language and confirming the symmetry. Updated the closing property summary paragraph to include Invariant 11 in the terminal finality cluster. As with Credential’s FC1, Invariant 11 is a runtime invariant; it cannot be independently verified from records alone and accordingly no new Generation acceptance check was added.
FC2 — Generation acceptance check 5 wording misleading (refining, fixed in-pattern). Check 5 said “confirm that no subsequent record for the same session_token exists showing status = Active.” But Invariant 7 guarantees session_token uniqueness — no two records share a token, so the check was looking for something that cannot exist by construction. The real check is whether the existing record’s status field was reset to Active after a terminal transition. Fixed: rephrased to “confirm that the record’s status field has not been changed back to Active,” with an explicit note that terminal states are absorbing and irreversible.
FC3 — Generation acceptance has no check for Invariant 1 (issue immutability of non-status fields) (refining, noted, not blocking). The five-check Generation acceptance covers Invariants 2, 3 (partially), 6, 8, 9, 10 but has no check that session_token, principal_ref, issued_by_ref, and issued_at were never modified post-issue. As with Credential FC2, this is verifiable only from store schema (no UPDATE surface) rather than from record content. Not closeable as a record-store check. Noted as a store-design requirement: deployments should constrain the session store schema to prevent UPDATE operations on non-status fields.
Final Critique 4 closed clean. Foundational findings: zero remaining. Refining findings: one noted (FC3), not blocking. Session is grounded on Final Critique 4.
Formal-layer vote — 2026-06-03: YES (model pending). Invariant 3 (conjunctive validity with revoked-takes-precedence-over-expired) and Invariant 4 (revocation absorbing) are reachability/precedence claims — no interleaving of expire/revoke may yield valid. Load-bearing temporal/ordering/safety claims a derived formal model would verify; none exists yet, so the pattern is downgraded to grounded (English) — formal layer pending until the model is authored and verifies (findings flow back into this English spec per the conflict protocol). Vote per pressure-testing.md §Formal models — The formal-layer vote.
Formal-layer vote — reconsidered 2026-06-03: NO (formal-not-warranted); pattern restored to grounded. On a second pass the aggressive-bar YES was downgraded. Invariant 3’s validity is a conjunction of record fields — issued ∧ now < expires_at ∧ ¬revoked — evaluated at query time, not an action-vs-action interleaving; validate cannot return valid for a record whose revoked flag is set or whose expires_at has passed, by direct field inspection. “Revoked precedes expired” is a precedence already fixed by insertion order in the append-only record and surfaced as the invalid(revoked | expired | not-known) result tag; the records carry the answer. Invariant 4 (revocation absorbing) is terminal-absorption, a structural guarantee. The interleaving that genuinely warrants a model — gating a permission check on session validity so a caller cannot interrogate permissions for an arbitrary principal — is Session-Gated Authorization’s emergent property, and that composition keeps its YES vote and its Alloy model. Per the minimum-formalism principle, the atom’s conjunctive-validity claim is prose-and-records sufficient. (Original YES retained above for the decision audit trail.)
Scheduled rescan — 2026-06-10 (council-run; the first rescan batch under the automated-executor convention). Selected by risk-weighted ordering: oldest rescan date (2026-05-19, Final Critique 4), composition fan-in 4 (Login, Session-Gated Authorization, Privileged Access Provisioning, Actor Suspension). Council formula: one agent per pass per round — Pass 1 / Pass 2 claude-sonnet-4-6 (peer-spec verification permitted), Pass 3 claude-opus-4-8 in strict fresh-reader mode (question sets + this spec, nothing else); triage and folds by the conducting session (claude-fable-5). Three rounds to a clean close:
- Round 1. Pass 1: eight reported; five triaged refining and folded — the lede/Intent “three outcomes” miscount corrected to four (a genuine internal contradiction: the lede enumerated four outcomes while calling them three); the two dangling “see Configuration” references resolved by adding the §Configuration subsection (deployment default session duration; token entropy ≥128 bits from a cryptographically secure source, injected per the Logic Confinement Principle); RFC / IETF / PIN first-use glosses; Generation acceptance now names the Intent question it operationalizes. Two rejected in triage: “Friction node absent” (the Edge cases section carries the Friction node — presence, not heading names, is the Pass 1 rule) and “C13/C14 sigil unexplained” (the compositions are named in full at each use; the C-number is a roadmap cross-reference, not an acronym). Pass 2: clean. Pass 3 (fresh-reader): six findings, all refining, all folded —
expireprecondition evaluation order pinned (not-known→not-active→invalid-request);nowdeclared an injected input; lazy terminal transitions specified as best-effort guarded compare-and-set withstatusexplicitly not the liveness authority (the derived predicatestatus = Active AND now < expires_atgoverns all liveness queries); string input policy (byte-exact, whitespace-only = empty, deployment length caps); thevalidateexpired branch syntactically gated behindstatus != Revokedso the cheap numeric short-circuit cannot defeat revoked-takes-precedence; token entropy floor + injected random material. - Round 2. Pass 1: one refining finding folded (“API” glossed inline in the Summary). Pass 2 / Pass 3: clean.
- Round 3. Clean across all three passes — round closed;
groundedretained; Status rescan date bumped to 2026-06-10.
Formal-layer portion: none — the vote stands at NO (formal-not-warranted, 2026-06-03 reconsideration above); no model to re-run. Measured cost (cost-model data point): 9 council-agent invocations (6 Sonnet, 3 Opus) across 3 rounds, ≈470k subagent tokens; also recorded in ai-usage-log.md.