Fold a set of base32 change hashes into the view's order-invariant [`SetId`](atomic_core::types::SetId) string. Returns `None` if any hash is not valid base32, in which case the caller must fall back to the Merkle dichotomy rather than risk a false "in sync" from a partially-folded set. The domain is the local view's applyable change set — it must match byte-for-byte the domain the server folds
(local_hashes: &HashSet<String>)
| 476 | /// (changes and tags, sidecars excluded); see the cross-producer contract |
| 477 | /// on [`atomic_core::types::SetId`]. |
| 478 | fn local_set_id(local_hashes: &HashSet<String>) -> Option<String> { |
| 479 | use atomic_core::types::{Base32, Merkle, SetId}; |
| 480 | let mut acc = SetId::ZERO; |
| 481 | for hash in local_hashes { |
| 482 | let merkle = Merkle::from_base32(hash.as_bytes())?; |
| 483 | acc = acc.add(&merkle); |
| 484 | } |
| 485 | Some(acc.to_base32()) |
| 486 | } |
| 487 | |
| 488 | // Dichotomy Algorithm — O(log n) divergence detection |
| 489 |