Here’s the pre-mortem. I’ll assume the three services are something like an auth/login service (issues tokens), a token-validation/gateway layer, and a user/account service, with a shared JWT signing secret and Redis holding sessions. Tell me if the split is different — it changes some answers.
The single points of failure
1. Redis is your hard SPOF — and it’s worse than it looks
A shared JWT plus a Redis session store usually means you’ve built stateful tokens wearing a stateless costume. If every request validates the JWT and checks Redis (for revocation, session liveness, or refresh state), then Redis down = total auth outage across all three services. You haven’t gained JWT’s main benefit (stateless validation) but you’ve kept its main cost (revocation is hard).
- Single Redis node: it dies, everyone is logged out / locked out simultaneously.
- Redis Sentinel/Cluster: failover is 1–30s of write unavailability, and clients that cache the old master will hammer a read-only replica. Test the failover, don’t assume it.
- Memory pressure: Redis with
maxmemory and an eviction policy of allkeys-lru will silently evict live sessions under load → random logouts that look like a security bug. Sessions must be in a volatile-* policy with TTLs, or in an instance that never evicts.
2. The shared JWT signing secret is a blast-radius multiplier
One secret across three services means:
- Any one of the three services is compromised → attacker can mint tokens accepted by all three. The shared secret makes your weakest service the security level of the whole system.
- Secret rotation is a synchronized-deploy problem. If you rotate and even one service lags, it rejects valid tokens. Most teams discover they have no rotation procedure during the incident, not before. You need overlapping key validity (accept old+new during a window), which means key IDs (
kid header) — which most “shared secret” designs skip.
- HS256 (symmetric) vs RS256/EdDSA (asymmetric): if you’re using a symmetric shared secret, every validating service can also forge tokens. Switch to asymmetric — auth service holds the private key, the other two only get the public key. This removes “shared secret” as a SPOF entirely.
3. The token-issuing service is a SPOF for new logins
Validation may be distributed, but issuance is centralized. If the login service is down, existing sessions survive (good) but no one can log in, and — critically — no one can refresh. So an issuer outage longer than your access-token TTL becomes a total outage on a delay. Short TTLs (15 min) that feel secure will convert a 20-minute issuer blip into a system-wide logout cascade.
Where it breaks that isn’t a “node”
4. Clock skew — JWT exp/nbf/iat are wall-clock. If the three services drift (no NTP, or containers with bad clocks), you get tokens that are “not yet valid” or “expired” on one service but fine on another. Intermittent, host-specific, miserable to debug. Allow a small leeway and pin NTP.
5. Revocation is the design’s silent hole — JWTs can’t be un-issued. If your security story is “we revoke via Redis,” then see SPOF #1: revocation availability == Redis availability. If your story is “short TTL,” then logout/ban isn’t immediate — a stolen token works until exp. Decide explicitly which one you’re accepting, because right now the architecture implies both and guarantees neither.
6. The thundering-herd / refresh stampede — Token expiry is correlated. Deploy at 09:00, tokens issued en masse, they all expire ~15 min later → synchronized refresh spike hits the issuer + Redis at the same instant. Add jitter to TTLs.
7. Cache/validation inconsistency — If services cache JWKS (public keys) or session lookups, a rotation or revocation propagates unevenly. Service A sees the revoked session, Service B serves cached “valid” for the TTL of its cache. Define the staleness window deliberately.
8. Cascading failure via retry — Redis slow (not down) → validation latency climbs → upstream timeouts → clients retry → more load on Redis → it gets slower. Without circuit breakers and a fail-mode decision (fail-closed = secure but total outage; fail-open = available but unauthenticated requests leak through), a slow Redis takes down everything. You must choose fail-open or fail-closed per endpoint and write it down.
The three questions that decide if this survives
- On a request, do you hit Redis every time, or only on refresh? If every time, Redis is a synchronous dependency of all traffic and you should either accept it as a true SPOF (and HA it accordingly) or move revocation to short-TTL-only and make Redis async/best-effort.
- Symmetric or asymmetric JWT? If symmetric, fix that first — it’s the cheapest blast-radius reduction available.
- Fail-open or fail-closed when Redis is unreachable? If you can’t answer instantly, that’s the gap that will define the outage.
Highest-leverage hardening, ranked
- Go asymmetric (RS256/EdDSA) with
kid + a key-rotation procedure you’ve actually rehearsed — kills SPOF #2.
- Decide and implement the Redis fail-mode + circuit breaker — kills the cascade (#8) and bounds #1.
- Make Redis HA and give sessions TTLs in a non-evicting instance — bounds #1.
- Add TTL jitter — kills #6.
- Pin NTP + add validation leeway — kills #4.
Want me to turn this into a one-page failure-mode table (component → failure → blast radius → mitigation), or pressure-test a specific piece — e.g. your exact request path and whether Redis is on the hot path?
This is a classic “Shared Trust/ShutDown” Anti-Pattern waiting for a specific trigger.
Here is a pre-mortem simulation. I am writing this from the perspective of being 3 months post-launch on Day 42.
The Failure
Incident Report: At 2:00 AM EST, the application returns 401 Unauthorized to 40% of legitimate users immediately upon refresh. The root cause was identified in the maintenance logs: the Redis Cluster node responsible for the primary session cache failed. Concurrently, the central JWT signing key rotated successfully on Service A, but failed to propagate the new public key to Service B due to a misconfigured config map. Users lost their sessions. Security audit failed due to session state leakage in Redis due to memory limits.
The Single Points of Failure (SPOFs)
Here are the critical failure points in your current “Three Microservices, Shared JWT, Redis” design, ranked by severity.
1. The Redis Cluster (The “Glass” Stateful)
You have declared JWT is stateless, yet you rely on Redis for session management. This is your biggest contradiction.
- The Failure Moment: Redis enters a rebalance or memory limit state (out of memory). Redis is the only place where you can enforce “logout elsewhere” or token blacklisting. When Redis stalls, your “logout” feature breaks, leaving users stuck in active sessions with revoked tokens.
- The SPOF Risk: Single Master. If you share one Redis key space across three services, and one service writes high-volume session invalidation, it starves the others.
- The Fix: Redis must be AOF + RDB dual persistence and configured with a Cluster/Sharded architecture. Crucially: Ensure the JWT verification is stateless. If you don’t need Redis to sign tokens, Reduxing it helps security (if redis down, app remains up). If you do need Redis, your “Shared JWT” design is invalid.
2. The “Shared JWT” Trust Boundary
Sharing a signing key across three microservices creates a hidden dependency chain.
- The Failure Moment: You rotate the main signing key. Service A rotates it, but Service B hasn’t been restarted. Service C hasn’t been restarted. Users who logged into Service A are logged out by Service C immediately because Signature Verification Fails. This causes “Password Reset” micro-service to work in Service A but fail at Service C.
- The SPOF Risk: Key Management Synchronization. If your key rotation strategy relies on a shared Vault or manual update across three languages (e.g., Node, Go, Python), the deployment window becomes the operational bottleneck. If one service fails to pick up the new key, you effectively lock out users trying to switch services.
- The Fix: Asymmetric Keys. Service A signs; Service B/C/R verifies. But even better: One Authority Service. All JWTs are signed by a single, centralized Authority Service. If any other service needs to verify, it fetches the keys from that Authority Service. If the Authority dies, you block access; otherwise, ensure the services share a config-sync (e.g., Consul/Kubernetes Shared Config) for public keys.
3. The “Central” Auth Microservice
You have three microservices. Do they all handle auth, or does one act as the Gatekeeper (Microservice X)?
- The Failure Moment: Microservice X is the bottleneck. It holds the only shared JWT signatures. If Microservice X’s deployment pipeline is throttled (CI/CD failure) or crashes under load (Rate Limiting), the entire identity layer of the company goes dark.
- The SPOF Risk: Deployment Coupling. A bad DB migration in the shared service affects all three downsquoked microservices.
- The Fix: Decoupled Identity Verification. Each of the three microservices maintains its own local cache of public keys (shared secret) to verify tokens without querying a shared service. If they share a secret key, that key must be rotated independently in a way that doesn’t require a single service to restart simultaneously.
4. Redis Session Memory Bloat (The “Zombie” Token)
- The Failure Moment: Users create session paths. Service A writes to Redis. Service B reads. Redis
MAXMEMORY is reached. Expired tokens stick around in Redis because the TTL isn’t set correctly, or the eviction policy (LRU/LFU) is misconfigured.
- The SPOF Risk: Redis Data Volatility. If the wrong eviction policy is chosen (e.g., “No Eviction” during high load), Redis crashes completely. If LRU is chosen, active user sessions get dropped because they are “least recently used” (incorrect if a user is just waiting on a POST request).
- The Fix: Use a Key TTL strategy with a default value of 5 minutes for sessions, plus a Redis Cluster setup that doesn’t rely on a single node.
5. Cross-Service Latency in Validation
- The Failure Moment: Microservice A (App) sends a JWT to Microservice B (Logic) to verify user role access. The network is congested. Microservice B takes 5 seconds to validate. The user sees a loading spinner for 5 seconds, then gets an error.
- The SPOF Risk: Network Partitioning. If the internal mesh network (Service Mesh) has a “Bike Chain” issue (partition), users can log in locally but cannot verify roles with downstream services.
- The Fix: Local Caching of User Identity. Do not sign JWT with tenant-level data that microservice B needs to re-verify on every request. Verify at the perimeter entry point and pass claims (e.g., scopes) through.
The Checklist for Hardening
If you proceed with this design, you must specifically address these three issues to avoid the “Day 42” failure described above:
- Key Sync is Not a Secret: The public keys used for JWT validation must be synced instantly across all three services. Use an environment variable injection service (like Vault Agent, Istio Config, or Kubernetes ConfigMaps) that auto-rotates public keys without manual intervention.
- Redis Slaves/Clonality: Redis must not be a single instance. You need Redis Sentinel or Redis Cluster. You must also have a pure in-memory fallback. If Redis dies 100%, the system should still function with only the JWT in the user’s browser, even if validation takes longer.
- Hybrid Validation Strategy:
- Perimeter (Login): JWT verification.
- Internal: Redis for user context (who am I? what resources can I access?), but do not store credentials in Redis.
- Failing Gracefully: If Redis is missing, do not fail the user. Deny access only if JWT claims indicate a critical security threat (like rotation check), allow login flow, and log to “Audit” instead of “Redis”.
The Verdict
Where will it break?
It will break when Redis evicts your active session keys due to a spike in activation events (e.g., a marketing campaign sends 1k logins in 1 sec), combined with the fact that Service B is still waiting to sync the signing key from Service A.
Recommendation:
Shift to OIDC/OAuth2 Flow.
- Move Away from: Shared JWT Signing Key across services.
- Move To: One central OAuth2 Provider (Auth0, Keycloak, or a dedicated Backdoor Auth Service).
- All 3 Microservices act as Confidential Clients to this single token issuer.
- The Redis is no longer for sessions (JWT is stateless), but only for Rate Limiting and Blacklisting tokens (using Redis as an expiring cache for claims not sessions).
If you must keep the current design: Ensure all three services share a single config Sync (Consul/Zookeeper) for the JWT Keys, Isolate Redis with Sentinel, and Ensure the App doesn’t require Redis for initial session validation.
Imagined Breakage Narrative
One year post-deployment, incident reports filed under this architecture reveal a consistent failure pattern: the system displaced state persistence to a single external tier (redis) while auth-gateway and token-issuance maintained strict adherence to synchronized state expectations. When redis cluster ingress lagged beyond sentinel quorum timing thresholds, session keys expired globally simultaneously. When token-issuance rotated signing keys ahead of auth-gateway readiness, authentication requests were rejected without user notification. When session-validation reached concurrency limits concurrent with token-issuance signaling driveness, the application rejected concurrent writes and reads, causing transaction aborts.
Structural Fragility Inventory
-
SF-01 — dependency — Located in: redis tier (sentinel) Sentinel Quorum Point. Yield mechanism: Master node recovery with disconnected sentinel nodes caused split-brain where promoted replica and original master accepted conflicting writes.
-
SF-02 — dependency — Located in: Pod lifecycle (k8s) PVC bind mount. Yield mechanism: Automatic scaling detached PVC storage, new instance initialized with master_replid mismatch, invalidating pending session recovery back to stale state.
-
SF-03 — load — Located in: Token verification chain Concurrency Bound. Yield mechanism: JWT verification queue depth exceeded circuit breaker threshold during traffic surge, invalidating auth-gateway responses.
-
SF-04 — interface — Located in: JWT state synchronization between token-issuance and auth-gateway. Yield mechanism: Key rotation drift causing signature verification failures across service tiers.
-
SF-05 — state — Located in: Session-expiration Clock Skew. Yield mechanism: Redis server clock skew ranged >5 minutes from client application server, causing global logout of active internal sessions aligned to client validation.
-
SF-06 — emergent — Located in: Distributed session validation without atomic isolation. Yield mechanism: Concurrent SETnx and session-update operations targeting same user-id caused session data invalidation and orphaned state.
Load Pathways to Breakage
SF-01 — Operating envelope condition that triggered it: Network partition during maintenance. Structural property that yielded: master/replica failover timing mismatched with sentinel voter count. Immediate consequence: Connect_exceeded observed; session recovery blocked. Cascade through dependencies: Subsequent requests flushed; clients assumed system failure.
SF-02 — Operating envelope condition that triggered it: Autoscaling with growth_coeff > 2. Structural property that yielded: Pod ID respawn detached PVC inode from session state. Immediate consequence: master_repl_offset reset to 0. Cascade through dependencies: slave0 offset logged divergence; session cache lost across instance spin-up.
SF-03 — Operating envelope condition that triggered it: Traffic surge at 45k rps. Structural property that yielded: Load balancer round-robin selection queue; http_requests_total exceeded limits. Immediate consequence: Connection timeout; circuit breaker triggered. Cascade through dependencies: Validation policies rejected; auth-gateway responses invalidated.
SF-04 — Operating envelope condition that triggered it: Configuration synchronization drift. Structural property that yielded: Kubernetes secret rollout latency vs pod readiness. Immediate consequence: Signature validation errors. Cascade through dependencies: Auth-gateway returned 401 responses; token-issuance rejected re-verification attempts.
SF-05 — Operating envelope condition that triggered it: Scheduled cluster maintenance. Structural property that yielded: Infrastructure time sync NTP drift. Immediate consequence: Session-write timestamp invalid. Cascade through dependencies: Session-validation returns false negative; active internal sessions logged out globally.
SF-06 — Operating envelope condition that triggered it: High concurrency at 15k queue depth. Structural property that yielded: Redis session-backlog saturation. Immediate consequence: Atomic isolation failure. Cascade through dependencies: user-id reservation collision; data lost for concurrent operations.
Leading Indicators Per Fragility
SF-01 — Observable signals approaching failure: Sentinel quorum count drift detected via local error log. Signal-acquisition cost: Medium via CloudWatch and log aggregation. Lead time before yield: 0-5 minutes pre-failure.
SF-02 — Observable signals approaching failure: master_repl_offset divergence (replica offset > master offset). Signal-acquisition cost: High via schema query. Lead time before yield: Immediate/real-time.
SF-03 — Observable signals approaching failure: validation_queue_length > 15k pending writes. Signal-acquisition cost: Medium via app metrics. Lead time before yield: Short, sub-minute.
SF-04 — Observable signals approaching failure: key-version-mismatch error rate spike in logs. Signal-acquisition cost: Medium via log analysis. Lead time before yield: <5 minutes post-key-drift starts.
SF-05 — Observable signals approaching failure: redis-server clock offset warning or session_write_timestamp_drift. Signal-acquisition cost: Low via metric threshold. Lead time before yield: <5 minutes pre-failure.
SF-06 — Observable signals approaching failure: redis_write_latency > 200ms spike; connection timeouts. Signal-acquisition cost: High via schema query. Lead time before yield: <1 minute pre-failure.
Structural Mitigations
-
MM-01 — Sentinel deployment separation — Addresses SF-01. Structural component change: Sentinel nodes distributed across 3 distinct subnets/racks with quorum requirement explicitly set to 2/3. Tradeoffs introduced: Increased orchestration complexity; geographic distribution adds configuration overhead. Implementation cost: Medium (requires infrastructure re-architecture).
-
MM-02 — Upstream LB response on quorum loss — Addresses SF-01. Structural component change: Upstream LB configured for 429 responses on sentinel quorum loss; client library implements circuit-breaker fallback. Tradeoffs introduced: Increased client-side complexity; potential user-visible latency during failover. Implementation cost: Low (client library configuration).
-
MM-03 — Persistent volume separation — Addresses SF-02. Structural component change: Pod scheduling includes podAntiAffinity rules; PVC bind enforced to semantic deployment ID over instance node ID. Tradeoffs introduced: Reduced resource density; scheduling constraints may limit cluster utilization. Implementation cost: Medium (K8s scheduling policy updates).
-
MM-04 — Decoupled token verification layer — Addresses SF-03. Structural component change: Token-issuance decoupled from auth-gateway; local token cache implemented with 5-minute TTL on client side. Tradeoffs introduced: Increased client-side storage requirements; cache consistency requires additional logic. Implementation cost: High (service boundary re-architecture).
-
MM-05 — Time-synchronized environment — Addresses SF-04. Structural component change: NTP infrastructure-level mandate for all auth-gateway, token-issuance, and session-validation tiers with clock drift logging enabled. Tradeoffs introduced: Infrastructure complexity for time synchronization monitoring. Implementation cost: Low (NTP policy enforcement).
-
MM-06 — JWT payload self-containment — Addresses SF-05. Structural component change: JWT payload includes independent session claims rather than external session-lookup requirements. Tradeoffs introduced: Increased JWT payload size; cryptographic operation overhead on every validation. Implementation cost: Low (JWT claims modification).
-
MM-07 — Atomic session validation — Addresses SF-06. Structural component change: Redis SETnx with atomic locking enforced at token-issuance tier before write operation completes. Tradeoffs introduced: Increased write latency; potential session bottleneck under high concurrency. Implementation cost: Medium (redis client logic updates).
-
MM-08 — Independent namespace separation — Addresses SF-06. Structural component change: 3 microservices operate in independent authentication namespaces with cross-service TTL checks. Tradeoffs introduced: Federation complexity for session state across namespaces. Implementation cost: Medium (namespace isolation architecture).
-
MM-09 — Geographic redundancy — Addresses Residual. Structural component change: Redis cluster split across 3 zones with write-quorum-per-zone enforcement. Tradeoffs introduced: Increased replication lag across zones; higher cost for distributed storage. Implementation cost: High (multi-zone Redis cluster deployment).
Residual Unmitigated Fragilities
-
RF-01 — network partition recovery — Structural gap: Clock synchronization not enforced at infrastructure level. Operating envelope: Split-brain scenario remains possible if quorum distribution fails in specific network segments. Structural mitigations don’t fully eliminate this risk because quorum distribution in specific segments can still fail.
-
RF-02 — cross-service drift — Structural gap: Distributed locking mechanism not sufficient for multi-tier concurrency scaling beyond high thresholds. Operating envelope: 45k rps load sustained for extended period. Structural mitigations don’t fully eliminate this risk because locking mechanisms have inherent capacity limits under sustained high-load.
-
RF-03 — time-delta monitoring — Structural gap: Session expiration remains sensitive to infrastructure time drift; NTP quality variance across regions. Operating envelope: Global logout cascade potential. Structural mitigations don’t fully eliminate this risk because NTP quality variance across regions is hard to enforce through structural means alone.
Confidence Per Finding
-
Imagined Breakage Narrative: Confidence Medium-High. Grounded in pre-mortem synthesis of available system constraints with specific failure hypotheses derived from architectural patterns.
-
Structural Fragilities: Confidence High. Derived from standard architectural patterns for Redis Sentinel and Kubernetes StatefulSet lifecycles.
-
Load Pathways: Confidence Medium. Specific thresholds like “45k rps” are unverified per system constraints; stored as hazard patterns rather than verified measurements.
-
Leading Indicators: Confidence Medium. Signal acquisition costs vary by monitoring implementation; lead times are estimates based on similar Redis architectures.
-
Structural Mitigations: Confidence High. Mitigations are standard structural changes for the mode and documented across industry sources.
-
Residual Risks: Confidence High. Gap analysis confirms structural mitigations do not fully eliminate network partition risks due to inherent distributed system properties.
Imagined breakage narrative
The system shipped and, read as a post-incident report, broke not on a single service failure but on the relationship between two fused authorities. The JWT is self-validating — signature verification needs no Redis. Revocation and session state require Redis. The design merged signing authority (the shared secret) and session authority (Redis) into one validation step without deciding what the system does when only one of the two is available. When Redis was lost while the services stayed up, the architecture entered a state no single component reports as broken: every token was signature-valid and unrevocable at once.
The apparent triple-redundancy was illusory — three services share one secret and one Redis, so a fault in either substrate hit all three identically and correlated. The headline “Redis is a single point of failure” was the cheapest and best-understood part of the failure; the dangerous part was the undecided, zero-signal degradation behavior and the silent re-validation of revoked tokens under memory pressure.
Structural fragility inventory
LOAD CLASS
- LD-1 — Redis connection-pool saturation on the validation hot path — class: load. Located in: the synchronous Redis round-trip every service performs on the validation critical path. Yield mechanism: Redis sits on the critical path of 100% of authenticated traffic, so concurrent validations crossing roughly the per-service pool ceiling × 3 cause connections to queue and validation p99 to climb across all three services in lockstep (they share one Redis, not three).
- LD-2 — Refresh storm after synchronized token expiry — class: load. Located in: the issuance/refresh path and the tokens’
exp distribution. Yield mechanism: a cohort of tokens minted in a narrow window expires together one token-lifetime later and refreshes together, hitting issuance and Redis at once.
DEPENDENCY CLASS
- DP-1 — Single unreplicated Redis was the sole session/revocation authority for all three services — class: dependency. Located in: the single Redis node with no replica or Sentinel quorum. Yield mechanism: loss of the node leaves no authority to promote; the three services provide zero redundancy because they all point at the same node, so one VM loss becomes a three-service outage.
- DP-2 — Validation hard-depended on Redis with no defined absence behavior — class: dependency. Located in: the validator’s revocation-read branch when Redis is absent. Yield mechanism: the design never specified fail-open vs fail-closed, so behavior was whatever each validator library defaulted to and differed across the three services. This dependency gap is the substrate of EM-1.
- DP-3 — Issuance topology was undecided: issuer-role concentration and policy uniformity — class: dependency. Located in: the (unstated) split between issuing and validating roles. Yield mechanism: if centralized, the issuing service is a dependency SPOF for all new logins/refreshes; if decentralized (all three mint, since all hold the secret), a policy change deployed to one issuer but not the others mints tokens with divergent properties.
STATE CLASS
- ST-1 — Redis crossed
maxmemory and the policy-in-force either silently re-validated revoked tokens or refused new revocations — class: state. Located in: the single unbounded keyspace holding sessions plus a growing revocation list. Yield mechanism: writes accumulated faster than expiry until used_memory crossed maxmemory; under an eviction policy (allkeys-* / volatile-*, including managed Memorystore’s volatile-lru default) a revoked token’s marker was evicted and the token silently re-validated, while under OSS Redis’s actual default (noeviction) the store refused new revocations and sessions at the moment they were most needed.
INTERFACE CLASS
- IF-1 — The secret-distribution joint had no key-versioning seam, so rotation became a synchronized cutover producing split-brain validation — class: interface. Located in: the joint by which the single shared secret reaches three independently-deployed services. Yield mechanism: with one secret and no
kid header, each validator holds exactly one key, so rotation requires an atomic all-three swap; deploys are not simultaneous, so during propagation the issuer signs with key-new while a not-yet-redeployed validator still holds key-old and rejects every fresh token.
- IF-2 — The
exp-comparison joint failed under clock divergence between issuer and validator hosts — class: interface. Located in: the implicit assumption that now() is the same value on every host. Yield mechanism: exp is set by the issuer’s clock and checked against each validator’s independent local clock; a validator drifting ahead rejects un-expired tokens, one drifting behind honors expired ones, and containerized services without guaranteed time sync make the drift structural.
- IF-3 — The shared validation contract drifted across independently-deployed services — class: interface. Located in: the per-service copies of validation logic and config (accepted-algorithm list,
exp leeway, revocation-check behavior). Yield mechanism: across successive independent deploys these copies drift, so the same token is accepted by one service and rejected — or validated under different rules — by another, with no single component reporting an error. The defining property of the design (three independently-deployed services) actively undermines the implicit “validation contract is byte-identical” assumption.
- IF-4 — The session-payload serialization joint mis-parsed across schema-version skew — class: interface. Located in: cross-service reads of session blobs (exists only if services read each other’s sessions). Yield mechanism: one service’s deploy changes the session-payload schema and a sibling still on the old schema mis-parses it. If the design keeps sessions opaque per-service, IF-4 does not arise (flagged for confirmation).
EMERGENT CLASS
- EM-1 — Authority split: the system has no coherent failure posture though every component is individually fine — class: emergent. Located in: the fusion of signing authority (the secret) and session authority (Redis) into one validation step. Yield mechanism: when Redis is absent the design forces a global choice it never made — fail-open (every signature-valid token honored, revocation ignored, silent security breach) or fail-closed (all auth rejected, total outage) — and because all three share one Redis and one secret, the branches fail fully correlated. LD-1, LD-2, and DP-2 are the same authority-split root viewed through other class-lenses, not independent roots. This is the central finding.
- EM-2 — Correlated-failure domain masquerading as redundancy — class: emergent. Located in: the shared secret and shared Redis behind “three microservices.” Yield mechanism: the redundancy lives in the request-serving tier, not the auth-dependency tier, so a single fault in either substrate hits all three identically — the apparent triple-redundancy is illusory for auth.
- EM-3 — Retry amplification turned a recoverable Redis blip into a metastable lockup — class: emergent. Located in: the identical retry defaults across all three services’ Redis clients. Yield mechanism: all three retry in unison; the synchronized retry load arrives as Redis is recovering and pushes it back over, so the system stays wedged after the original trigger is gone — no component individually misbehaves.
Load pathways to breakage
LD-1 — operating-envelope condition that triggered it: traffic spike (marketing event, dependent-fleet restart re-authing, retry storm). Structural property that yielded: concurrent validations exceeded aggregate pool size. Immediate consequence: connections queued → validation p99 climbed from single-digit ms to seconds → upstream timeouts fired. Cascade through dependencies: clients retried → queue deepened → all three degraded in lockstep because they share one Redis.
LD-2 — operating-envelope condition that triggered it: a large cohort minted in a narrow window W (post-deploy re-login, marketing spike, scheduled job, or fixed TTL anchored to one wall-clock origin). Structural property that yielded: all exp fell in W+lifetime, producing a synchronized refresh burst. Immediate consequence: Redis connection pool hit ceiling → refresh p99 climbed. Cascade through dependencies: clients timed out and retried → offered load rose → pool stayed saturated → the spike fed on itself.
DP-1 — operating-envelope condition that triggered it: VM loss / OOM-kill / AZ partition. Structural property that yielded: Redis socket dead with no replica to promote. Immediate consequence: validators issued a revocation read → read blocked to client timeout → request threads piled up → per-service pool exhausted. Cascade through dependencies: services stopped serving all traffic → cascade to every caller downstream of auth; MTTR was bounded by manual restart/restore, not automatic failover.
DP-2 — operating-envelope condition that triggered it: Redis absent (per DP-1). Structural property that yielded: the validator’s revocation read failed and took an undecided branch per-library. Immediate consequence: inconsistent behavior across the mesh. Cascade through dependencies: this dependency gap is the substrate of the emergent failure EM-1.
DP-3 (centralized branch) — operating-envelope condition that triggered it: issuer instances lost. Structural property that yielded: no mint path while existing self-contained tokens still validated. Immediate consequence: login endpoint errored; existing token holders kept working → partial, confusing outage where authenticated users are fine but no one new can log in. Cascade through dependencies: as the live cohort’s tokens expired the authenticated population bled toward zero over one token lifetime; on-call read “auth is up” from validator metrics while login conversion dropped to zero. The fragility is asymmetric: validator-role loss degrades part of the request mesh, whereas issuer-role loss removes the entire ingress for new sessions. (Decentralized branch: a policy change — TTL, claim set, scopes — deployed to one issuer but not the others minted tokens with divergent properties, producing inconsistent, service-dependent enforcement.)
ST-1 — operating-envelope condition that triggered it: sustained write rate > expiry rate (refresh churn; revocation entries whose TTL outlived the token exp, or had no TTL). Structural property that yielded: used_memory climbed monotonically and crossed maxmemory, engaging the policy-in-force. Immediate consequence: under the eviction branch a revocation key for a compromised account was evicted and the next validation found no marker → token accepted, no exception thrown; under the noeviction branch a write was refused → revocation/session creation blocked. Cascade through dependencies: both are structural failures of using one unbounded keyspace as a revocation authority — eviction presents as normal operation (silent security regression), noeviction surfaces as write errors / OOM command not allowed.
IF-1 — operating-envelope condition that triggered it: the first secret rotation. Structural property that yielded: with one secret and no kid, each validator held exactly one key, requiring an atomic swap across non-simultaneous deploys. Immediate consequence: during propagation service A redeployed with key-new and issued key-new tokens while service B still on key-old received a key-new token → signature check failed → user authenticated at A, rejected at B. Cascade through dependencies: asymmetric, endpoint-dependent auth failures across the mesh for the staggered-rollout duration, indistinguishable from a bug.
IF-2 — operating-envelope condition that triggered it: NTP sync lost on one host. Structural property that yielded: exp comparison on that host diverged from the issuer’s intent. Immediate consequence: intermittent early-rejection (availability hit) or late-acceptance (security hit). Cascade through dependencies: failures correlated to which host served the request, hence hard to reproduce.
IF-3 — operating-envelope condition that triggered it: independent deploy of A bumps its validator library (narrows accepted algs, or changes leeway) while B/C lag on the prior version. Structural property that yielded: a token at the boundary of the changed rule. Immediate consequence: accepted at B, rejected at A → asymmetric, version-correlated failures. Cascade through dependencies: failures map to no single config.
IF-4 — operating-envelope condition that triggered it: single-service deploy changed the session schema. Structural property that yielded: a sibling on the prior schema read the blob. Immediate consequence: deserialization mismatch → mis-parse / error on session read. Cascade through dependencies: contingent on whether services read each other’s session blobs.
EM-1 — operating-envelope condition that triggered it: Redis down + services up. Structural property that yielded: the validator’s revocation read failed and the architecture had never decided the global posture. Immediate consequence: the fail-open path accepted tokens on signature with security degradation invisible to monitoring (during the exact window — an outage — when attackers are most active), OR the fail-closed path rejected all auth for a total outage despite secret and tokens being intact. Cascade through dependencies: neither Redis, nor the secret, nor any single service is “broken”; the failure is a property of the topology, and the undecided failure mode is itself the fragility.
EM-2 — operating-envelope condition that triggered it: a fault in one shared substrate (secret or Redis). Structural property that yielded: all three services depend on it identically. Immediate consequence: simultaneous correlated degradation, not isolated single-service failure. Cascade through dependencies: the request-serving redundancy provides no isolation for the auth-dependency tier.
EM-3 — operating-envelope condition that triggered it: transient Redis unavailability (a brief blip). Structural property that yielded: all validators retried on the same schedule. Immediate consequence: recovering Redis received the full synchronized retry load and re-saturated. Cascade through dependencies: retries continued → metastable failure persisting past the trigger.
Leading indicators per fragility
LD-1 — observable signals approaching failure: Redis connected_clients approaching maxclients; validation-call p99 drift; blocked_clients > 0; client-side pool-wait time rising. Signal-acquisition cost: low (Redis INFO). Lead time before yield: minutes if graphed, none if unmonitored.
LD-2 — observable signals approaching failure: histogram of token exp values (clustering = the latent fault, visible before the storm — rarely instrumented); refresh-request rate spiking at TTL boundaries; issuance-endpoint queue depth; Redis connected_clients vs maxclients; refresh-endpoint p99; retry rate on refresh. Signal-acquisition cost: low–moderate (the exp histogram requires deliberate instrumentation). Lead time before yield: one token lifetime if the exp clustering is graphed.
DP-1 — observable signals approaching failure: Redis uptime resets; absence of any replication-link status (the absence is itself the signal); replica-lag / redis_replica_up absence; revocation-read p99 creeping; host-level memory/CPU saturation on the Redis box. Config-time tell (binary, visible at review today): replica count = 0 in the topology inventory; redis_up carries no redis_replica_up companion. Signal-acquisition cost: low. Lead time before yield: the config tell tells you the fragility exists now; the runtime drift signals tell you it is about to fire.
DP-2 — observable signals approaching failure: absence of a written, tested degradation contract; differing validator configs across the three repos. Signal-acquisition cost: low (code review). Lead time before yield: detectable indefinitely ahead — it is a design-time absence.
DP-3 — observable signals approaching failure: divergence between login-success rate and token-validation-success rate (validation flat-healthy while login success falls = issuer-role-loss signature); issuance-endpoint availability tracked per service; divergence in issued-token TTL/claim set across services (decentralized case); a dependency map showing whether one service is the sole issuer (config tell, drawable today). Signal-acquisition cost: low–moderate. Lead time before yield: the centralized-branch bleed unfolds over one token lifetime.
ST-1 — observable signals approaching failure: used_memory / maxmemory ratio approaching 1.0 (the single most important graph, likely absent today); evicted_keys > 0 (should be flat-zero for a security keyspace) in the eviction branch, or rising write-error / OOM rate in the noeviction branch; revocation-keyspace cardinality growing without a TTL bound; ratio of keys-with-TTL to keys-without-TTL. Signal-acquisition cost: low (Redis INFO + keyspace stats). Lead time before yield: hours to days if graphed — gradual growth is exactly why a threshold alarm catches it ahead.
IF-1 — observable signals approaching failure: signature-verification-failure rate by service spiking during any deploy window (runtime drift signal, observable on every rollout); rejection-rate divergence between services for the same token. Config tell (binary, visible now): distinct keys a validator can accept = 1, and absence of a kid header in issued tokens. Signal-acquisition cost: low. Lead time before yield: the config tell is visible today; the runtime spike is concurrent with the rotation.
IF-2 — observable signals approaching failure: per-host clock offset vs NTP reference (directly measurable drift signal); rejection rate correlating with a specific host/pod; auth failures clustered by node; offset crossing the leeway budget is the alarm. Signal-acquisition cost: low. Lead time before yield: drift accumulates gradually, so the offset graph leads the failure.
IF-3 — observable signals approaching failure: validator-contract version fingerprint per service (drift visible the moment versions diverge); auth-failure rate correlated with which service handled the request and which deploy is live; absence of a contract-version field in validation telemetry today. Signal-acquisition cost: low–moderate (requires a version fingerprint to exist). Lead time before yield: visible at deploy divergence.
IF-4 — observable signals approaching failure: deserialization-error rate on session reads after any single-service deploy. Signal-acquisition cost: low. Lead time before yield: concurrent with the divergent deploy.
EM-1 — observable signals approaching failure: revocation-read error rate > 0 while token-acceptance rate stays flat (the signature of fail-open); divergence between “logouts requested” and “sessions actually invalidated”; design-time absence of a written degradation contract; a chaos test that kills Redis and observes whether the three services agree on behavior (they won’t today); divergence in validator fail-mode config across repos. Signal-acquisition cost: moderate — these require instrumenting the relationship, which is why emergent fragilities go unmonitored. Lead time before yield: the design-time absence is visible now; the chaos test surfaces it on demand.
EM-2 — observable signals approaching failure: a dependency map showing fan-in of all three services to one Redis and one secret (drawable today — a config tell). Signal-acquisition cost: low. Lead time before yield: visible at design review.
EM-3 — observable signals approaching failure: aggregate retry rate vs baseline request rate; Redis load failing to drain after the trigger clears; retry-to-success ratio — a retry rate that spikes and stays high after recovery is the tell. Signal-acquisition cost: low. Lead time before yield: the metastable signature appears as the blip clears.
Structural mitigations
These are gated in priority order under a finite budget. Every entry is a structural change; no operational workaround appears as a mitigation. Cost and risk-reduction are relative, not absolute. “Gate” = should block deployment.
-
Collapse the two authorities via a signed per-user epoch claim in the token (decoupling — highest-leverage change) — fragility it addresses: EM-1 (and resolves LD-1 and DP-2 outright, shrinks ST-1). Mechanism: at per-request validation the validator does zero revocation work — verifies the signature, trusts the embedded epoch, never touches Redis; the Redis read happens only at the refresh endpoint, which compares the token’s epoch against the user’s stored epoch and re-issues (match) or refuses (epoch bumped). Make the epoch per-user so revoking one user bumps only that user’s epoch — blast radius exactly one principal; a single global epoch (which would log the entire fleet out on every revocation) is the wrong default, reserved only for a deliberate “log everyone out” operation. Redis absence then degrades to bounded-staleness revocation — one designed, uniform posture across all three services. Pair with declaring the fail mode explicitly: encode whether validators fail-closed (reject on revocation-read failure; availability cost, security preserved) or fail-open with a bounded degraded window (accept only tokens whose age is under a short ceiling, capping exposure), so the mode is a designed property, not a timeout-handling accident. Tradeoffs introduced: residual bounded-staleness window (R-staleness). Implementation cost: Low–Med (validator logic). Gate / fix first.
-
noeviction on the security keyspace + TTL = token-life bound + keyspace separation (state-bound enforcement) — fragility it addresses: ST-1. Mechanism: (1) set TTL on every revocation entry equal to the token’s remaining exp — after exp the token is dead by signature anyway, so the entry is provably redundant — bounding the revocation keyspace by construction so the threshold is never approached; (2) explicitly set the auth keyspace to noeviction (or isolate auth into its own logical DB / instance) so the store refuses writes and surfaces an error rather than silently dropping security state — fail-loud over fail-silent — and pair with (1) so refusal-under-pressure also never triggers. Separating the cache keyspace from the security keyspace is the stronger version, so cache pressure cannot evict security state. Tradeoffs introduced: under genuine memory pressure Redis now refuses new sessions (R-noeviction). Implementation cost: Low (config + issuance logic). Gate.
-
kid header + validator key-sets (interface hardening) — fragility it addresses: IF-1. Mechanism: add a kid header and make every validator hold a set of accepted keys keyed by kid. Rotation becomes: distribute key-new to all validators’ key sets (still accepting key-old) → flip the issuer to sign with key-new → after max token lifetime, retire key-old. The seam tolerates overlap by construction, and a leaked key can be retired without a synchronized deploy. Deeper structural escape — asymmetric signing, issuer holds private key, validators hold only the public key via JWKS — narrows the shared-secret blast radius but introduces a new dependency (see R-JWKS). Tradeoffs introduced: key-set management; the deeper escape adds a JWKS dependency. Implementation cost: Med. Gate.
-
Replicated Redis topology across failure domains (redundancy-with-independence) — fragility it addresses: DP-1. Mechanism: replace the single instance with primary + replica across distinct failure domains plus Sentinel (≥1 replica + 3 Sentinels for quorum) or managed Redis Cluster, replicas pinned to distinct zones via anti-affinity. A topology change adding an independent counterpart, not “monitor Redis better.” Web context confirms a single VM failure can take down many co-located Redis processes and that OSS/GKE deployments lack failure-domain awareness by default — so “add a replica” is insufficient unless the replica is in a separate domain. Tradeoffs introduced: failover promotion latency (R-promotion) and partition split-brain (R-splitbrain); leaves ST-1/EM-1/IF-1 intact. Implementation cost: Med–High (infra). Necessary, not sufficient.
-
Backoff + jitter + circuit breaker in the Redis client wrapper (interface hardening) — fragility it addresses: EM-3. Mechanism: build exponential backoff with jitter and a circuit breaker into the Redis client wrapper, so retries desynchronize and a tripped breaker sheds load to let Redis recover — a structural change to the client interaction pattern, identical across all three services, not an operator runbook. Tradeoffs introduced: a tripped breaker temporarily sheds load. Implementation cost: Low–Med. After gates.
-
exp jitter at issuance + bounded connection pool (state-bound enforcement) — fragility it addresses: LD-2. Mechanism: bounded jitter on exp at issuance (lifetime ± a random spread) so a cohort minted together expires spread across time; pair with a connection pool sized to a declared concurrency budget rather than defaults. Both are structural (issuance logic + pool config), not “scale up when it happens.” Tradeoffs introduced: marginal token-lifetime variance. Implementation cost: Low. After gates.
-
Skew tolerance in the validation contract (interface hardening) — fragility it addresses: IF-2. Mechanism: build a bounded exp leeway / skew tolerance into the validation contract itself as a fixed protocol parameter, so the joint absorbs physically-unavoidable drift up to a declared budget. NTP is the operational version and is not sufficient — it relies on operator discipline; the leeway is the structural floor, a property of the code. Tradeoffs introduced: tokens honored slightly past true exp within the leeway budget. Implementation cost: Low. After gates.
-
≥2 issuer-capable instances across zones + shared signed issuance-policy config (redundancy-with-independence / dependency removal) — fragility it addresses: DP-3. Mechanism: if centralized, deploy ≥2 issuer-capable instances across distinct failure domains under the same separation discipline as DP-1; if issuance must stay centralized for policy uniformity, make it a consciously owned dependency with its own redundancy budget. If decentralized, factor issuance policy (TTL, claim set) into a shared signed config the issuers load at runtime, so policy is single-sourced even though issuance is distributed. Tradeoffs introduced: depends on the role split. Implementation cost: Low–Med. After gates.
-
Versioned shared validation contract + startup assertion (interface hardening) — fragility it addresses: IF-3. Mechanism: pin the validation contract as a single shared, versioned artifact (one library version + config bundle); each validator asserts agreement on a contract-version constant at startup and refuses to serve on mismatch — a structural invariant enforced by code rather than deploy discipline. Tradeoffs introduced: a mismatched service refuses to serve until reconciled. Implementation cost: Med. After gates.
-
Versioned session schema / keyspace partition (interface hardening) — fragility it addresses: IF-4. Mechanism: version the session schema and make reads tolerant of N−1, or partition the keyspace so no service reads another’s session blobs. Tradeoffs introduced: schema-version maintenance. Implementation cost: Low. After gates, if applicable.
For EM-2, the structural mitigation is to introduce independence where redundancy is claimed — failure-domain-separated Redis (mitigation 4) and per-kid key rotation (mitigation 3) — so a fault in one key version or one Redis replica does not equal a fault in all. A shorter JWT lifetime is an additional EM-1 variant: shorten the lifetime so the signature-validity window is the session-validity window within a tight bound, collapsing the two authorities toward one and shrinking the half-valid gap to the token TTL (this trades Redis-dependence for issuer load — residual R-TTL-load).
Residual unmitigated fragilities
- R-staleness — Bounded-staleness revocation window (from the EM-1 fix) — why structural mitigations don’t reach it: even with epoch-in-token, a revoked token stays valid until the next refresh, and returning Redis to the hot path is the only way to close it. Operating-envelope conditions under which it would yield: “instant revocation required for active compromise.” Whether it should warrant rethinking the design: it is a deliberate, structurally irreducible trade — document the maximum exposure window (= refresh interval) as the security SLA.
- R-TTL-load — Shortening JWT lifetime shifts load onto the issuer (from the EM-1 fix variant) — why structural mitigations don’t reach it: frequent refresh concentrates load on the issuing service and Redis, re-loading the components LD-1/LD-2 and DP-1 stressed. Operating-envelope conditions under which it would yield: when refresh cadence × active-token count exceeds issuer/Redis throughput. Whether it should warrant rethinking the design: the two strong mitigations (shorten TTL vs reduce Redis dependence) are in tension; the design must consciously pick a point on the curve.
- R-noeviction —
noeviction converts silent failure into hard write-rejection (from the ST-1 fix) — why structural mitigations don’t reach it: correct trade (fail-loud), but under genuine memory pressure Redis now refuses new sessions — new logins fail rather than old sessions silently dropping. Operating-envelope conditions under which it would yield: when memory is exhausted and the TTL=token-life bound was insufficient because token lifetimes are long. Whether it should warrant rethinking the design: reinforces R-TTL-load’s case for short lifetimes; note that the EM-1 epoch keyspace still lives in Redis and is read at refresh, so noeviction still applies to it — ST-1 is reduced in magnitude, not dissolved.
- R-secret — Secret compromise remains catastrophic and global (confidence: high) — why structural mitigations don’t reach it:
kid versioning makes rotation survivable, but a leaked symmetric secret still forges valid tokens across all three services until rotation completes. Operating-envelope conditions under which it would yield: any leak of the shared symmetric secret. Whether it should warrant rethinking the design: the structural escape (asymmetric RS256: private issuer + public verifiers) is a larger redesign — the next fragility to retire after EM-1 lands.
- R-JWKS — Asymmetric signing introduces a JWKS dependency joint (if the R-secret escape is adopted) — why structural mitigations don’t reach it: validators then depend on fetching public keys; the endpoint’s availability and validators’ key-cache freshness become a new interface fragility. Operating-envelope conditions under which it would yield: when the JWKS endpoint is unreachable during a validator cold-start with an empty cache. Whether it should warrant rethinking the design: mitigable by bundling a bootstrap public key and treating JWKS as refresh-only, but the joint remains.
- R-promotion — Redis failover promotion latency (from the DP-1 fix) — why structural mitigations don’t reach it: Sentinel promotion is not instantaneous; during the several-second promotion window, writes (new revocations, new sessions) are rejected. Operating-envelope conditions under which it would yield: “revocation needed during the failover window.” Whether it should warrant rethinking the design: bounded by promotion time; acceptable if EM-1’s in-token epoch covers the gap.
- R-splitbrain — Redis partition split-brain (from the DP-1 fix, qualitatively distinct from R-promotion) — why structural mitigations don’t reach it: the DP-1 mitigation introduces a failure mode the single-node design did not have — under a network partition, Sentinel can promote a replica while the old primary still accepts writes, yielding two primaries with divergent revocation lists (the CAP trade); a token revoked against one primary may stay live against the other until the partition heals. The per-user epoch design does not cover this, because the epoch authority itself lives in the partitioned store. Operating-envelope conditions under which it would yield: a network partition, for the partition + reconciliation duration. Whether it should warrant rethinking the design: honest mitigation is quorum-aware writes (refuse revocation writes without majority), accepting the write-availability cost.
Confidence per finding
- High: LD-1 (Redis on 100% of hot-path traffic — structural fact of the design); DP-1 (single node, no replica stated — structural fact + web-confirmed loss mode); DP-2 (no degradation contract — absence is observable); IF-1 (one secret, no
kid — structural fact); EM-1 (authority fusion — derivable from the two-authority design, central finding); EM-2 (shared substrates — structural fact); R-secret (a leaked symmetric secret forges across all three until rotation completes).
- Medium-high: ST-1 (mechanism real both ways; confidence below high only because the branch depends on the unstated
maxmemory-policy).
- Medium: LD-2 (refresh storm depends on issuance burst pattern); DP-3 (hinges on the unstated centralized/decentralized role split); IF-2 (depends on time-sync posture); IF-3 (depends on per-service deploy independence and library-version posture); EM-3 (depends on retry-default uniformity).
- Low: IF-4 (exists only if services read each other’s session blobs — contingent and unconfirmed).
- Open facts that would sharpen confidence: (a) the service-role topology — which service issues, which validate (sharpens DP-3, EM-1); (b) the deployed
maxmemory-policy via CONFIG GET maxmemory-policy (selects the ST-1 branch); (c) whether sessions are opaque per-service or shared-read (determines whether IF-4 exists); (d) the team’s current Redis/auth telemetry inventory (determines which leading indicators are observable today); (e) the shared-validation-contract distribution mechanism and per-service library-version posture (sharpens IF-3).
Additional considerations
Two genuine tensions survive in this analysis and are worth surfacing rather than collapsing. First, on the dominance of ST-1: one reading foregrounds ST-1 (silent state eviction) and EM-1 (authority split) as co-dominant zero-signal security failures that, with IF-1, should gate deployment; the other foregrounds EM-1 as the singularly dominant root, with ST-1 reduced and shrunk by the EM-1 epoch fix to a small bounded keyspace. The substantive agreement: EM-1 is the highest-leverage fix and the dominant fragility is not the headline Redis-SPOF (DP-1). The unresolved nuance is whether ST-1 warrants independent gating status or is largely absorbed once EM-1 is fixed — both gate-paths are preserved above (ST-1 listed as its own gate and as shrunk-by-EM-1). Second, on classification: the undecided fail-open/fail-closed behavior is treated in one view as an emergent property folded into EM-1, and in another as a distinct dependency-class fragility (DP-2) that is the substrate of EM-1. It is preserved as DP-2 with an explicit cross-link to EM-1 — the disagreement reveals it is genuinely a dependency gap whose consequence is emergent.
Several external claims that ground the mitigations were web-verified. Managed Sentinel/Cluster failover lands in the low-single-minutes range — ~5–15s detection, election “in seconds,” default sentinel failover-timeout 180000 ms (3 min) — versus an unbounded manual recovery for a single instance with no automatic promotion path. The standard quorum-failover topology is ≥1 replica + 3 Sentinel instances on independent hosts/AZs (redis.io HA guide; corroborated). A single VM failure can affect many co-located Redis processes, and OSS Redis Cluster and GKE pod-to-VM assignment lack failure-domain awareness by default (Google Cloud Memorystore four-nines material; K8s topology-spread docs). On the budget arithmetic: 99.99% allows 4.38 min downtime/month, and achieving it requires increasing MTBF (e.g., to ~6 months) or decreasing MTTR to ~4 min — manual single-node restart/restore MTTR routinely exceeds 4 min (a 4-minute Redis loss can cascade to hours), confirming automatic cross-domain failover is required. Finally, the maxmemory-policy default split underpinning the ST-1 two-branch analysis is real: OSS Redis’s true default is noeviction (refuses writes) while managed Memorystore for Redis defaults to volatile-lru (evicts), so the ST-1 mechanism is interpretation-dependent on the policy in force — both branches are genuine failures, specified rather than asserted.
On actor-modeling: none detected. No fragility is narrated as an adversary trying to defeat the system; every breakage is a structural yield under operating-envelope conditions (load, memory threshold, clock drift, deploy skew, Redis loss). The compromise/revocation scenarios (ST-1, EM-1, R-secret) reference consequences for a compromised account but do not model an active attacker as the mechanism — they remain structural-fragility territory. No sideways-route escalation is required.