Per-account state
One call returns every per-account flag the installed modules maintain, and it is always safe to make.
All docs
Restrictions and exemptions are per-account, so knowing what a token can do is not the same as knowing what it will do to one particular address. This answers that in a single call.
AccountState memory state = IERC20AccountState(token).accountState(account);
// state.frozen - barred from transferring
// state.feeExempt - transfers touching this account are not charged
// state.configuredAt - when any of the above last changed, 0 if neverFields belonging to modules that are not installed read as false rather than reverting, so the call is safe on any token in the framework regardless of what it declares. A token with no restriction module reports every account as unfrozen, which is true.
configuredAt is the one to watch
It is a timestamp, set whenever an authority changes anything account-specific, and zero if nothing ever has. It is the cheapest way to detect that a counterparty's status moved since you last looked, without diffing every field or subscribing to events.
A frozen account is not a static fact. If you hold a position against a counterparty, treat frozen as a signal that its balance can be burned out from under you — see pause and blocklist.