Non-mutating lookup of the family aggregate.
Returns null if no record exists OR the record is expired (lazy GC).
Per A3 §5.1.
Atomically register a new refresh-token family.
MUST throw RefreshTokenStorageError({ reason: "duplicate-family" })
if a family with the same familyId already exists (regardless of
revoke / TTL state — duplicate familyId indicates RNG collision or
programming bug).
MUST throw RefreshTokenStorageError({ reason: "expired-at-issue" })
if family.expiresAtMs <= now() at call time.
Concurrency contract: N concurrent calls with the same familyId
MUST result in exactly one success and N-1 throws of
"duplicate-family".
Per A3 §5.1.
Atomically read-modify-write the family aggregate.
Adapter performs:
updater(current).RefreshTokenStorageError({ reason: "conflict-exhausted" }).Updater contract (NORMATIVE):
{ outcome: "not-found" } directly without invoking the updater).readonly at the type level; runtime adapters MAY freeze it
additionally as defence-in-depth).createRefreshTokenFamilyRotation to translate "aborted"
results into "replayed" or "revoked" outcomes.expiresAtMs is
<= now(). Both adapters fail-closed by throwing
RefreshTokenStorageError({ reason: "expired-at-issue" }) —
symmetric with registerFamily and prevents committing a
dead-on-arrival entry. Callers shrinking TTL during rotation
should compute the new expiresAtMs from a forward window.Return value:
{ outcome: "committed", family } — CAS succeeded; family is the
newly-persisted state.{ outcome: "not-found" } — family did not exist; updater not
invoked.{ outcome: "aborted" } — updater returned null; no state change.Per A3 §5.1.
Storage primitive for refresh-token families.
Theme A: this interface exposes only single-key atomic primitives. The 4-outcome rotation ceremony (
rotated | replayed | revoked | unknown_family) is composed in the wrapper layer (RefreshTokenFamilyRotation), NOT classified by the adapter.Per A3 §5.1.