ReadonlyfederationTokenStore: FederationTokenStoreOptional Readonlylogger?: LoggerOptional structured logger for warning emissions on best-effort ops.
Defaults to console. Provide a pino/winston/etc instance with a compatible
warn(message, ...args) signature to route failures into your observability stack.
ReadonlyrefreshTokenFamilyRevocation: RefreshTokenFamilyRevocationReadonlysessionFamilyIndex: SessionFamilyIndexReadonlysessionFederationIndex: SessionFederationIndexReadonlysessionRPRegistry: SessionRPRegistryReadonlysid: stringReadonlyuserSessionStore: UserSessionStore
Executes the four-step logout cascade in the fixed order mandated by A4 §6.2: Step 1: Read fanout context — sessionFamilyIndex.listFamilyIds(sid). Fail → return failed step:1 (steps 2–4 skipped; retry safe). Step 2: Fanout — collect-and-tally. - revokeFamily loop (per family; continues on per-op failure, tallies errors) - federationTokenStore.removeBySid (tallied on failure) If ANY failure → return failed step:2 with all errors (HALT before step 3). §6.2 critical rule: HALT preserves bookkeeping for retry. Running step 3 would erase reverse-index entries and silently mark the cascade complete despite an un-revoked family. Step 3: Reverse-index cleanup — only if step 2 fully succeeded. Per-op best-effort (log + continue; orphan entries bounded by TTL). - sessionRPRegistry.removeBySid - sessionFamilyIndex.removeBySid - sessionFederationIndex.removeBySid Never returns failed (step 3 best-effort; hence step range is 1|2|4 not 1|2|3|4). Step 4: Primary invalidation — userSessionStore.delete LAST (must succeed). Fail → return failed step:4.
Note: broadcastBackchannelLogout is invoked by routes/logout.mts BEFORE cascadeLogout (it is "best-effort — never throws" per its own contract), so its position is functionally equivalent to being a no-throw step-2 op inside the cascade per §6.2 model. Moving it here would require pulling in sub/issuer/keyStore/fetchImpl/auditSink as new deps — a surface change beyond this helper's scope.
Caller responsibilities:
outcome: "failed"to HTTP 503.