@@ -535,7 +535,7 @@ pub async fn fetch_channel<T: Transport + ?Sized>(
535535 channel_id: &ChannelId,
536536 limit: usize,
537537) -> Result<Vec<FetchedEvent>, String> {
538- fetch_channel_history(transport, community, channel_id, limit, 1, |_| true).await
538+ fetch_channel_history(transport, community, channel_id, limit, 1, None, crate::community::transport::Evidence::Quorum, |_| true).await
539539}
540540
541541/// Walk a channel's history newest-first (CORD-03 §3 "clients load a Channel
@@ -557,6 +557,8 @@ pub async fn fetch_channel_history<T: Transport + ?Sized>(
557557 channel_id: &ChannelId,
558558 page: usize,
559559 max_pages: usize,
560+ since: Option<u64>,
561+ evidence: crate::community::transport::Evidence,
560562 mut keep_paging: impl FnMut(&[FetchedEvent]) -> bool,
561563) -> Result<Vec<FetchedEvent>, String> {
562564 // Guards the opportunistic scrub-key heals below — the fetch loop straddles
@@ -609,8 +611,10 @@ pub async fn fetch_channel_history<T: Transport + ?Sized>(
609611 let q = Query {
610612 kinds: vec![stream::KIND_WRAP],
611613 authors: vec![plane.pk_hex()],
614+ since,
612615 until,
613616 limit: Some(page),
617+ evidence,
614618 ..Default::default()
615619 };
616620 if let Ok(evs) = transport.fetch_plane(plane.keys(), &q, &community.relays).await {
@@ -1049,11 +1053,15 @@ pub async fn community_is_public<T: Transport + ?Sized>(transport: &T, community
10491053 let mut oldest: Option<u64> = None;
10501054 let mut until: Option<u64> = None;
10511055 for page in 0..COMPACT_MAX_PAGES {
1056+ // Quorum, DECLARED (the until→Full transport floor is gone): these
1057+ // control reads tolerate a partial union — their fold semantics are
1058+ // fail-safe on gaps (seeded banlists, withheld roster cache).
10521059 let query = Query {
10531060 kinds: vec![stream::KIND_WRAP],
10541061 authors: vec![control.pk_hex()],
10551062 until,
10561063 limit: Some(FOLLOW_PAGE),
1064+ evidence: crate::community::transport::Evidence::Quorum,
10571065 ..Default::default()
10581066 };
10591067 let Ok(wraps) = transport.fetch(&query, &community.relays).await else { return true };
@@ -1282,10 +1290,11 @@ async fn verify_owner_root_and_reconcile<T: Transport + ?Sized>(
12821290 // T's genesis onto a fake root to MITM another T-joiner — is closed only by
12831291 // binding the root into community_id (protocol, deferred).
12841292 //
1285- // Seed `until` with a FAR-FUTURE constant (NOT now-based): `until.is_some()` takes
1286- // the transport's AUTHORITATIVE drain-ALL-relays path (an open `until` returns only
1287- // a fast relay's partial window and misses a genesis on a lagging relay — routine
1288- // over Tor), while a constant beyond any real created_at clips NOTHING — so neither
1293+ // Seed `until` with a FAR-FUTURE constant (NOT now-based), and request
1294+ // Evidence::Full EXPLICITLY below: this walk draws an ABSENCE verdict (no
1295+ // owner-signed genesis ⇒ reject), which trusts only the completest union —
1296+ // an open partial window misses a genesis on a lagging relay (routine over
1297+ // Tor). A constant beyond any real created_at clips NOTHING — so neither
12891298 // a clock-skewed future-dated genesis nor a >1h-slow-clock joiner is excluded (a
12901299 // now-based bound could clip either). Break on an EMPTY page (a short page is a
12911300 // relay cap). A forged root walks to exhaustion and rejects; a flood/deep plane
@@ -1323,6 +1332,7 @@ async fn verify_owner_root_and_reconcile<T: Transport + ?Sized>(
13231332 authors: vec![control_pk.clone()],
13241333 until,
13251334 limit: Some(PAGE),
1335+ evidence: crate::community::transport::Evidence::Full,
13261336 ..Default::default()
13271337 };
13281338 let wraps = transport.fetch(&query, &community.relays).await?;
@@ -1720,11 +1730,15 @@ pub async fn fetch_authority<T: Transport + ?Sized>(transport: &T, community: &C
17201730 // silently un-ban on withheld data.
17211731 let mut a = fold_authority(community, &[], &floors);
17221732 for _ in 0..FOLLOW_MAX_PAGES {
1733+ // Quorum, DECLARED (the until→Full transport floor is gone): these
1734+ // control reads tolerate a partial union — their fold semantics are
1735+ // fail-safe on gaps (seeded banlists, withheld roster cache).
17231736 let query = Query {
17241737 kinds: vec![stream::KIND_WRAP],
17251738 authors: vec![control.pk_hex()],
17261739 until,
17271740 limit: Some(FOLLOW_PAGE),
1741+ evidence: crate::community::transport::Evidence::Quorum,
17281742 ..Default::default()
17291743 };
17301744 let Ok(wraps) = transport.fetch(&query, &community.relays).await else { break };
@@ -1785,7 +1799,9 @@ async fn fetch_guestbook_events<T: Transport + ?Sized>(
17851799 let mut until: Option<u64> = None;
17861800 let mut oldest: Option<u64> = None;
17871801 for _ in 0..GB_MAX_PAGES {
1788- let query = Query { kinds: vec![stream::KIND_WRAP], authors: vec![gb_group.pk_hex()], until, limit: Some(GB_PAGE), ..Default::default() };
1802+ // Full: this set becomes the refound's recipient list — a member's
1803+ // Join visible only on a minority relay must not be severed.
1804+ let query = Query { kinds: vec![stream::KIND_WRAP], authors: vec![gb_group.pk_hex()], until, limit: Some(GB_PAGE), evidence: crate::community::transport::Evidence::Full, ..Default::default() };
17891805 let wraps = transport.fetch(&query, &community.relays).await?;
17901806 let mut fresh = 0usize;
17911807 for wrap in &wraps {
@@ -2155,11 +2171,15 @@ pub async fn refound_community<T: Transport + ?Sized>(transport: &T, community:
21552171 // coverage test, so stopping there could compact it away.
21562172 let mut truncated = false;
21572173 for page in 0..COMPACT_MAX_PAGES {
2174+ // Full: compaction re-wraps the head set it can SEE — a control
2175+ // edition (a ban head) reachable only on a minority relay must not be
2176+ // compacted away by a partial union.
21582177 let query = Query {
21592178 kinds: vec![stream::KIND_WRAP],
21602179 authors: vec![current_control.pk_hex()],
21612180 until,
21622181 limit: Some(FOLLOW_PAGE),
2182+ evidence: crate::community::transport::Evidence::Full,
21632183 ..Default::default()
21642184 };
21652185 let wraps = transport.fetch(&query, &community.relays).await?;
@@ -2444,7 +2464,7 @@ pub async fn refound_at_birth<T: Transport + ?Sized>(
24442464 // Exhaustion, not coverage — see the sibling read in `refound_community`.
24452465 let mut truncated = false;
24462466 for page in 0..COMPACT_MAX_PAGES {
2447- let query = Query { kinds: vec![stream::KIND_WRAP], authors: vec![current_control.pk_hex()], until, limit: Some(FOLLOW_PAGE), ..Default::default() };
2467+ let query = Query { kinds: vec![stream::KIND_WRAP], authors: vec![current_control.pk_hex()], until, limit: Some(FOLLOW_PAGE), evidence: crate::community::transport::Evidence::Full, ..Default::default() };
24482468 let wraps = transport.fetch(&query, &community.relays).await?;
24492469 let mut fresh = 0usize;
24502470 for w in &wraps {
@@ -3541,11 +3561,15 @@ pub async fn follow_control<T: Transport + ?Sized>(
35413561 // device's baseline is the one step that outlives the round.
35423562 let mut truncated = true;
35433563 for _ in 0..FOLLOW_MAX_PAGES {
3564+ // Quorum, DECLARED (the until→Full transport floor is gone): these
3565+ // control reads tolerate a partial union — their fold semantics are
3566+ // fail-safe on gaps (seeded banlists, withheld roster cache).
35443567 let query = Query {
35453568 kinds: vec![stream::KIND_WRAP],
35463569 authors: vec![control.pk_hex()],
35473570 until,
35483571 limit: Some(FOLLOW_PAGE),
3572+ evidence: crate::community::transport::Evidence::Quorum,
35493573 ..Default::default()
35503574 };
35513575 let wraps = transport.fetch(&query, &community.relays).await?;
@@ -8139,7 +8163,7 @@ mod tests {
81398163 let general = community.channels[0].id;
81408164 flood_general(&relay, &community, &owner, 120, 10_000).await;
81418165
8142- let all = fetch_channel_history(&relay, &community, &general, 50, 8, |_| true).await.unwrap();
8166+ let all = fetch_channel_history(&relay, &community, &general, 50, 8, None, crate::community::transport::Evidence::Quorum, |_| true).await.unwrap();
81438167 assert_eq!(all.len(), 120, "the walk pages the whole burst");
81448168 // Oldest→newest, no duplicates.
81458169 let contents: Vec<String> = all.iter().map(|f| f.event.opened().rumor.content.clone()).collect();
@@ -8164,7 +8188,7 @@ mod tests {
81648188
81658189 // The caller says "I hold everything" after the first page — no deeper fetch.
81668190 let mut pages = 0usize;
8167- let got = fetch_channel_history(&relay, &community, &general, 50, 8, |_| {
8191+ let got = fetch_channel_history(&relay, &community, &general, 50, 8, None, crate::community::transport::Evidence::Quorum, |_| {
81688192 pages += 1;
81698193 false
81708194 })
@@ -8190,7 +8214,7 @@ mod tests {
81908214 let (wrap, _) = chat::seal_chat_rumor(&rumor, &group, &owner, Timestamp::from_secs(5_000), false).unwrap();
81918215 relay.publish(&wrap, &community.relays).await.unwrap();
81928216 }
8193- let got = fetch_channel_history(&relay, &community, &general, 25, 8, |_| true).await.unwrap();
8217+ let got = fetch_channel_history(&relay, &community, &general, 25, 8, None, crate::community::transport::Evidence::Quorum, |_| true).await.unwrap();
81948218 assert!(got.len() >= 25, "at least the relay page is read");
81958219 assert!(got.len() <= 60, "sane bound");
81968220 // Termination is the assertion: reaching here means the wall didn't loop.
0 commit comments