Today the stable-master health indicator reports `yellow` health if it sees the elected master change 4 times in 30 minutes. However, this could be caused by planned activity such as a rolling restart, and in that case we should not consider a sequence of master failovers as a sign of ill health.

I think we can solve this by refining this check to disregard master nodes that have restarted since they were master, or are no longer in the cluster at all. Restarting the master will always trigger a failover, and the stable-master indicator need not worry about whether the node restart was planned or not; separately, unplanned node restarts are definitely a sign of ill health, but that applies whether the node was the elected master or not. Similarly, if a node is no longer in the cluster then either it is no longer running (again, whether that's planned or not is irrelevant to the stable-master indicator) or else it is running and failing to join the cluster (and hence reporting its own ill health).
server/src/main/java/org/elasticsearch/cluster/coordination/MasterHistory.java
* node1 -> null -> node1 is 0 identity changes (transitions from a node to itself, even with null in the middle, do not count)
     * node1 -> null -> node2 is 1 identity change
     * @param masterHistory The list of nodes that have been master
     * @return The number of master identity changes as defined above
     */
    public static int getNumberOfMasterIdentityChanges(List<DiscoveryNode> masterHistory) {
        int identityChanges = 0;
        List<DiscoveryNode> nonNullHistory = masterHistory.stream().filter(Objects::nonNull).toList();
        DiscoveryNode previousNode = null;
        for (DiscoveryNode node : nonNullHistory) {
            if (previousNode != null && previousNode.equals(node) == false) {
                identityChanges++;
            }
            previousNode = node;
        }
        return identityChanges;
    }
    /**
     * Returns true if a non-null master existed at any point in the last nSeconds seconds. Note that this could be a master whose start
     * time was more than nSeconds ago, as long as either it is still master or the next master took over less than nSeconds ago.
     * @param nSeconds The number of seconds to look back
     * @return true if the current master is non-null or if a non-null master was seen in the last nSeconds seconds
     */
    public boolean hasSeenMasterInLastNSeconds(int nSeconds) {
        if (getMostRecentMaster() != null) {
            return true;
        }
        List<TimeAndMaster> masterHistoryCopy = getRecentMasterHistory(masterHistory);
        long now = currentTimeMillisSupplier.getAsLong();
        TimeValue nSecondsTimeValue = new TimeValue(nSeconds, TimeUnit.SECONDS);