When caching something per role, we're usually doing that because of checking permissions. But that's an indirect representation of the permissions. That means that if the set of permissions assigned to a role changes, that anything that is cached per role is not invalidated.
The root cause of the problem is that level of invalidation: we vary by the user.roles cache context when actually varying by permissions. Why? Because of historical/legacy reasons.
Removing that indirection means that the problem goes away: by varying by the list of permissions of a user, then changing a role's permissions also changes the new user.permissions cache context, thus working as expected.
Use this new cache context whenever varying something by permissions.
Also see the docs: https://www.drupal.org/developing/api/8/cache/contexts
API changes
- Removed
AccessResult::cachePerRole() - Added
AccessResult::cachePerPermissions()
(We are collectively so very used to varying per role, that that may get in the way. To further communicate that you'd rarely need that, removes AccessResult::cachePerRole() was removed in favor of AccessResult::cachePerPermissions().)