@startuml native_get_consensus
title Native — ConnectToNatives : fetch pool + Phase 1 + Phase 2

participant "Node / Indexer\\n(appelant)" as Caller
participant "Native A" as NA
participant "Native B" as NB
participant "Indexer A\\n(stable voter)" as IA

note over Caller: NativeIndexerAddresses configured\\nConnectToNatives() called from ConnectToIndexers

== Step 1 : heartbeat to the native mesh (nativeHeartbeatOnce) ==
Caller -> NA: SendHeartbeat /opencloud/heartbeat/1.0 
Caller -> NB: SendHeartbeat /opencloud/heartbeat/1.0 

== Step 2 : parrallel fetch pool (timeout 6s) ==
par fetchIndexersFromNative — parallel
    Caller -> NA: NewStream /opencloud/native/indexers/1.0\\nGetIndexersRequest{Count: maxIndexer, From: PeerID}
    NA -> NA: reachableLiveIndexers()\\ntri par w(F) = fillRate×(1−fillRate) desc
    NA --> Caller: GetIndexersResponse{Indexers:[IA,IB], FillRates:{IA:0.3,IB:0.6}}
else
    Caller -> NB: NewStream /opencloud/native/indexers/1.0
    NB -> NB: reachableLiveIndexers()
    NB --> Caller: GetIndexersResponse{Indexers:[IA,IB], FillRates:{IA:0.3,IB:0.6}}
end par

note over Caller: Fusion → candidates=[IA,IB]\\nisFallback=false

alt isFallback=true (native give themself as Fallback indexer)
    note over Caller: resolvePool : avoid consensus\\nadmittedAt = Now (zero)\\nStaticIndexers = {native_addr}
else isFallback=false → Phase 1 + Phase 2
    == Phase 1 — clientSideConsensus (timeout 3s/natif, 4s total) ==
    par Parralel Consensus
        Caller -> NA: NewStream /opencloud/native/consensus/1.0\\nConsensusRequest{Candidates:[IA,IB]}
        NA -> NA: compare with clean liveIndexers
        NA --> Caller: ConsensusResponse{Trusted:[IA,IB], Suggestions:[]}
    else
        Caller -> NB: NewStream /opencloud/native/consensus/1.0
        NB --> Caller: ConsensusResponse{Trusted:[IA], Suggestions:[IC]}
    end par

    note over Caller: IA → 2/2 votes → confirmed ✓\\nIB → 1/2 vote → refusé ✗\\nIC → suggestion → round 2 if confirmed < maxIndexer

    alt confirmed < maxIndexer && available suggestions 
        note over Caller: Round 2 — rechallenge with confirmed + sample(suggestions)\\nclientSideConsensus([IA, IC])
    end

    note over Caller: admittedAt = time.Now()

    == Phase 2 — indexerLivenessVote (timeout 3s/votant, 4s total) ==
    note over Caller: Search for stable voters in Subscribed Indexers\\nAdmittedAt != zero && age >= MinStableAge (2min)

    alt Stable Voters are available
        par Phase 2 parrallel
            Caller -> IA: NewStream /opencloud/indexer/consensus/1.0\\nIndexerConsensusRequest{Candidates:[IA]}
            IA -> IA: StreamRecords[ProtocolHB][candidate]\\ntime.Since(LastSeen) <= 120s && LastScore >= 30.0
            IA --> Caller: IndexerConsensusResponse{Alive:[IA]}
        end par
        note over Caller: alive IA confirmed per quorum > 0.5\\npool = {IA}
    else No voters are stable (startup)
        note over Caller: Phase 1 keep directly\\n(no indexer reaches MinStableAge)
    end

    == Replacement pool ==
    Caller -> Caller: replaceStaticIndexers(pool, admittedAt)\\nStaticIndexerMeta[IA].AdmittedAt = admittedAt
end

== Étape 3 : heartbeat to indexers pool (ConnectToIndexers) ==
Caller -> Caller: SendHeartbeat /opencloud/heartbeat/1.0\\nvers StaticIndexers

@enduml
