Repository: patroni/patroni

Issue:
wrong role in patronictl list
### What happened?

We have cluster with 3 nodes: leader, sync and async replicas. After add standby leader and restart postgres in patronictl list one of replica show as Sync Standby, but in pg_stat_replication it's async for all.

### How can we reproduce it (as minimally and precisely as possible)?

patronictl pause
pg_ctl stop (on each node)
add standby leader in patroni.yaml
pg_ctl start
patronictl resume
patronictl list

| Member        | Host          | Role           | State     | TL | Lag in MB |


+---------------+---------------+----------------+-----------+----+-----------+


| host1 | 10.1.1.1 | Standby Leader | streaming | 59 |           |


| host2 | 10.1.1.2 | Replica        | streaming | 59 |         0 |


| host3 | 10.1.1.3| Sync Standby   | streaming | 59 |         0 |


+---------------+---------------+----------------+-----------+----+-----------+

### What did you expect to happen?

role for replica = Replica

### Patroni/PostgreSQL/DCS version

-   Patroni version: 3.2.2
-   PostgreSQL version:  15.5
-   DCS (and its version): etcd 3.5.0


### Patroni configuration file

```yaml
scope: drdbmr
namespace: /pg/
name: host2

restapi:
listen: 0.0.0.0:8008
connect_address: ip:8008
etcd3:
hosts:
    host1:2379  
    host2:2379  
    host3:2379  

bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
synchronous_mode: "on"
synchronous_node_count: 1
synchronous_mode_strict: "on"
standby_cluster:
host: 206
port: 5000
postgresql:
use_slots: true
parameters:
krb_caseins_users: 'on'
archive_command: '/bin/true'
archive_mode: always
wal_level: replica
hot_standby: "on"
max_wal_senders: 10
max_replication_slots: 10
chekpoint_timeout: 30
synchronous_commit: "on"
synchronous_standby_names: "\*"
max_wal_size: 5GB
min_wal_size: 1GB
wal_keep_size: 5GB
seq_page_cost: 1
random_page_cost: 1.1
work_mem: 50MB
effective_cache_size: 6GB
checkpoint_completion_target: 0.9
default_statistics_target: 300
shared_buffers: 1GB
effective_io_concurrency: 200
idle_in_transaction_session_timeout: '0'
idle_session_timeout: '0'
idle_pool_backend_timeout: '0'
hold_prepared_transactions: on
session_pool_size: 0
maintenance_work_mem: 2GB
max_connections: 1655
max_sessions: 1000
max_prepared_transactions: 1655
bgwriter_delay: 20ms
bgwriter_lru_maxpages: 400
bgwriter_lru_multiplier: 4.0
wal_sender_timeout: 10s
page_repair: on
synchronous_standby_gap: 10240
logging_collector: on
log_checkpoints: on
log_replication_commands: on
log_connections: on
deadlock_timeout: 1s
log_lock_waits: on
log_line_prefix: '%m %u@%d [%p] %r'
track_activities: on
track_counts: on
track_wal_io_timing: on
track_io_timing: on
track_functions: all
log_autovacuum_min_duration: 0
log_statement: 'none'
log_timezone: 'Europe/Moscow'
log_directory: '/var/log/postgres'
log_disconnections: 'on'
log_file_mode: '0644'
log_filename: 'postgresql.log'
max_worker_processes: 30
max_parallel_workers: 30
max_parallel_workers_per_gather: 4
autovacuum_max_workers: 6
autovacuum_naptime: 20s
autovacuum_freeze_max_age: 1000000000
search_path: '"$user",public'
shared_preload_libraries: pg_wait_sampling, pgpro_stats, pg_proaudit
enable_partitionwise_aggregate: 'on'
enable_partition_pruning: 'on'
enable_partitionwise_join: 'on'
autovacuum_vacuum_scale_factor: '0.02'
vacuum_cost_limit: '409'
autovacuum_analyze_scale_factor: '0.03'
krb_server_keyfile: '/etc/postgres.keytab'
max_locks_per_transaction: '64'
track_commit_timestamp: 'off'
wal_log_hints: 'on'

pgpro_pwr.max: '30'
pgpro_pwr.max_sample_age: '14'
initdb:
    auth-host: scram-sha-256  
    auth-local: peer  
    encoding: UTF8  
    data-cheksums  
    locale: ru_RU.UTF-8  
pg_hba:
   local   all             backup                                  peer map=backup-map  
    local   replication     backup                                  peer map=backup-map  
    local   all             profile_mon                             peer map=profile-map  
    local   replication     repl                                    peer map=repl-map  
    host    all             zbx_monitor     127.0.0.1/32            trust  
   local   all             all                                     peer  
    host    all             all             127.0.0.1/32            scram-sha-256  
    host    all             postgres        0.0.0.0/0               scram-sha-256  
    host    replication     repl            206/32        trust  
    host    replication     repl            205/32        trust  
    host    replication     repl            204/32        trust  
   local   replication     all                                     peer  
    host    replication     all             127.0.0.1/32            trust  


pg_ident:


    backup-map      postgres               backup  
    repl-map        postgres               repl  
    zabbix-map      zabbix                 zbx_monitor  
    profile-map     postgres               profile_mon  


postgresql:


listen: 0.0.0.0:5432

connect_address: 206:5432
data_dir: /pgdata/drdb
config_dir: /pgdata/drdb

bin_dir: /opt/pgpro/ent-15/bin


pgpass: /var/lib/postgresql/.pgpass


use_unix_socket: true
use_unix_socket_repl: true
authentication:
replication:
username: repl
password: repl1
superuser:
username: postgres
assword: postgres
parameters:
unix_socket_directories: '/tmp'
basebackup:
    waldir: /pgdata/pg_wal/drdb  

tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
patroni
level: DEBUG
```


### patronictl show-config

```yaml
---
```


### Patroni log files

```shell
---
```


### PostgreSQL log files

```shell
---
```


### Have you tried to use GitHub issue search?

- [X] Yes

### Anything else we need to know?

_No response_


Discussion:

Patch:
diff --git a/patroni/utils.py b/patroni/utils.py
index c40e1248e..560d25701 100644
--- a/patroni/utils.py
+++ b/patroni/utils.py
@@ -952,7 +952,7 @@ def cluster_as_json(cluster: 'Cluster') -> Dict[str, Any]:
     for m in cluster.members:
         if m.name == leader_name:
             role = 'standby_leader' if config.is_standby_cluster else 'leader'
-        elif cluster.sync.matches(m.name):
+        elif config.is_synchronous_mode and cluster.sync.matches(m.name):
             role = 'sync_standby'
         else:
             role = 'replica'


Test patch:
diff --git a/tests/test_ctl.py b/tests/test_ctl.py
index 5b56c93ab..d5d83740e 100644
--- a/tests/test_ctl.py
+++ b/tests/test_ctl.py
@@ -503,6 +503,14 @@ def test_list_extended(self):
         assert '2100' in result.output
         assert 'Scheduled restart' in result.output
 
+    def test_list_standby_cluster(self):
+        cluster = get_cluster_initialized_without_leader(leader=True, sync=('leader', 'other'))
+        cluster.config.data.update(synchronous_mode=True, standby_cluster={'port': 5433})
+        with patch('patroni.dcs.AbstractDCS.get_cluster', Mock(return_value=cluster)):
+            result = self.runner.invoke(ctl, ['list'])
+            self.assertEqual(result.exit_code, 0)
+            self.assertNotIn('Sync Standby', result.output)
+
     def test_topology(self):
         cluster = get_cluster_initialized_with_leader()
         cluster.members.append(Member(0, 'cascade', 28,