A failed federation sync existed only as a `debug!` line on the node, so a peer that had not synced in days looked identical in the UI to one that synced a minute ago. Now the failure is persisted per peer and rendered. - `FederatedNode.last_sync_error` / `.last_sync_error_at` — the failure-side mirror of the existing `last_transport` / `last_transport_at` pair. - `federation::record_sync_result(data_dir, did, outcome)` — records the message on `Err`, CLEARS both fields on `Ok` so the badge disappears when the peer recovers. Runs under FEDERATION_STORE_LOCK via the `*_inner` load/save convention established by plan 01-01. An unknown DID is a silent Ok that writes nothing, so a peer removed mid-pass is never resurrected by an in-flight sync's error write. Skips the save entirely when nothing changed, keeping the steady state read-only rather than rewriting nodes.json (and contending for the lock) every 90s. - Message truncated to MAX_SYNC_ERROR_CHARS (256), counted in chars not bytes so truncation cannot split a UTF-8 sequence (T-01-18). - The 90s auto-sync loop calls it on both arms; the existing `debug!` line is kept — persisting is additive, not a replacement for logs. - `federation.list-nodes` emits both fields when set, omits them when unset. - NodeList renders a red SYNC badge beside the transport badge on both the trusted-node and peer rows, message + age in the `title` so the row stays single-line. Tests (written first, confirmed failing — 16 compile errors, E0425 on `record_sync_result` and E0609 on `last_sync_error`): - persists_error / success_clears_error / missing_did_is_noop / on_empty_store_is_noop / truncates_long_error - NodeList: badge present when set, ABSENT when unset (the guard against a badge that always renders), and present on an observer peer row. cargo test -p archipelago federation — 42 passed, 0 failed. vitest NodeList.test.ts — 4 passed. npm run build — green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
28 lines
1.2 KiB
Rust
28 lines
1.2 KiB
Rust
//! Node federation: trusted multi-node clusters with state sync.
|
|
//!
|
|
//! Nodes federate by exchanging invite codes containing DID + onion address.
|
|
//! Trust is bilateral — both sides must agree. Federated nodes periodically
|
|
//! sync container status, health metrics, and availability.
|
|
|
|
mod invites;
|
|
pub mod pending;
|
|
mod storage;
|
|
mod sync;
|
|
mod types;
|
|
|
|
// Re-export all public items so `crate::federation::*` continues to work.
|
|
pub use invites::{accept_invite, create_invite, parse_invite};
|
|
// Crate-internal: used by the periodic federation auto-sync to re-assert
|
|
// membership to peers that don't list us back (asymmetry self-heal).
|
|
pub(crate) use invites::notify_join;
|
|
// Crate-internal: peer-joined resolves the granted trust level by matching
|
|
// the acceptor's invite_token against our stored outgoing invites.
|
|
pub(crate) use storage::load_invites;
|
|
#[allow(unused_imports)]
|
|
pub use storage::{
|
|
add_node, fips_npub_for_onion, load_nodes, load_removed_dids, record_peer_transport,
|
|
record_sync_result, remove_node, save_nodes, set_trust_level, update_node,
|
|
};
|
|
pub use sync::{build_local_state, deploy_to_peer, sync_with_peer, sync_with_peer_by_did};
|
|
pub use types::{AppStatus, FederatedNode, NodeStateSnapshot, TrustLevel};
|