Register a new refresh-token family at initial issue time (e.g., from the authorization_code grant handler).
Delegates to RefreshTokenFamilyStore.registerFamily(family) after
constructing a RefreshTokenFamily aggregate from the inputs.
MUST throw RefreshTokenStorageError({ reason: "duplicate-family" })
if familyId already exists. MUST throw
RefreshTokenStorageError({ reason: "expired-at-issue" }) if
expiresAtMs <= now().
Use this for initial issue, not for rotation.
Compose the storage primitive into the 4-outcome rotation ceremony.
Normal flow does NOT throw RefreshTokenStorageError or any other
domain error — the discriminated outcome union IS the complete
return contract. System errors (Redis network failure, CAS conflict
exhaustion) propagate as native errors / RefreshTokenStorageError( { reason: "conflict-exhausted" }).
Replay handling (NORMATIVE, #274): an implementation that returns
replayed SHOULD have already revoked the family, in the same atomic
store operation that detected the replay, and MUST then set
familyRevoked: true. Detecting a replay and revoking the family as two
separate writes leaves a window in which a concurrent sibling redeems
the still-active token successfully — the defect this contract exists to
prevent. An implementation that genuinely cannot revoke atomically omits
the flag, and the caller falls back to revoking separately.
Per A3 §5.2 + #274.
Rotation ceremony wrapper. Composes
RefreshTokenFamilyStore.updateFamilyinto the 4-outcome union. The default impl (createRefreshTokenFamilyRotation) is shipped asdefaultRefreshTokenFamilyRotationModule; consumers needing custom policy (audit-emitting rotation, grace-period rotation, etc.) replace the module with their own.Per A3 §5.2.