feat(concord): page channel history across epochs to the true start

The backward "load older" pager REQ'd only the current epoch's Chat Plane, so
deep scroll stopped at the last Refounding and showed "All caught up" while
older messages sat under prior-epoch planes.

Widen the history REQ authors to the union of the channel's plane pubkeys
across every held epoch (ConcordCommunitySession.channelPlaneAddressesAllEpochs).
The relay serves them interleaved by created_at, so one backward `until` sweep
walks the whole cross-Refounding timeline and `exhausted` (the "All caught up"
signal) now means every epoch is drained, not just the current one. The pager
is unchanged — it only tracks until/limit per relay and forwards createdAt; the
prior-epoch wraps decrypt on the normal ingest path (already epoch-aware).

Test: ConcordCommunitySessionTest asserts channelPlaneAddressesAllEpochs returns
current + prior planes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vitor Pamplona
2026-07-17 17:48:45 -04:00
co-authored by Claude Opus 4.8
parent 891e6ced91
commit 602c6a90b2
3 changed files with 35 additions and 5 deletions
@@ -101,17 +101,24 @@ class ConcordChannelHistorySubAssembler(
?.mapNotNullTo(mutableSetOf()) { RelayUrlNormalizer.normalizeOrNull(it) }
?: emptySet()
/** The channel's derived Chat Plane pubkey — the REQ author. Null until the Control Plane folds it. */
private fun planePkFor(key: ConcordChannelHistoryQueryState): String? =
/**
* The channel's derived Chat Plane pubkeys across every epoch (current + each prior epoch we hold
* a root for) — the REQ authors. A CORD-06 Refounding moves the plane per epoch, so requesting the
* union lets one backward `until` sweep walk the whole cross-Refounding timeline and reach messages
* older than the last Refounding. Empty until the Control Plane folds the channel.
*/
private fun planePksFor(key: ConcordChannelHistoryQueryState): List<String> =
key.account.concordSessions
.sessionFor(key.communityId)
?.channelPlaneAddress(key.channelId)
?.channelPlaneAddressesAllEpochs(key.channelId)
.orEmpty()
override fun updateFilter(
key: ConcordChannelHistoryQueryState,
since: SincePerRelayMap?,
): List<RelayBasedFilter>? {
val planePk = planePkFor(key) ?: return emptyList()
val planePks = planePksFor(key)
if (planePks.isEmpty()) return emptyList()
val relays = relaysFor(key)
// Only armed (advanced, not done) relays carry a REQ, each at its own requested cursor. A parked
// relay keeps the same filter here, so re-assembly (another relay advancing) doesn't re-REQ it.
@@ -124,7 +131,10 @@ class ConcordChannelHistorySubAssembler(
filter =
Filter(
kinds = listOf(ConcordStreamEnvelope.KIND_WRAP),
authors = listOf(planePk),
// All epoch planes at once: the relay serves them interleaved by created_at, so
// one backward cursor walks across the Refounding boundaries; "exhausted" then
// means every epoch is drained, not just the current one.
authors = planePks,
until = until,
limit = pager.pageLimit,
),
@@ -200,6 +200,20 @@ class ConcordCommunitySession(
/** The Chat Plane stream address for [channelIdHex], once this community has folded that channel (else null). */
fun channelPlaneAddress(channelIdHex: HexKey): HexKey? = lock.withLock { channelKeysByAddress.entries.firstOrNull { it.value.first == channelIdHex }?.key }
/**
* Every Chat Plane stream address for [channelIdHex] across epochs: the current one plus each
* prior-epoch plane we hold a root for. Used by the history pager as the REQ `authors` set so a
* single backward `until` sweep walks the channel's whole cross-Refounding timeline (older
* messages have smaller `created_at` regardless of epoch), and "All caught up" means every epoch
* is drained — not just the current one. Empty until the Control Plane folds the channel.
*/
fun channelPlaneAddressesAllEpochs(channelIdHex: HexKey): List<HexKey> =
lock.withLock {
val current = channelKeysByAddress.entries.firstOrNull { it.value.first == channelIdHex }?.key
val historical = historicalChannelKeysByAddress.entries.filter { it.value.first == channelIdHex }.map { it.key }
(listOfNotNull(current) + historical)
}
/** The base-rotation rekey [GroupKey] a member opens an inbound Refounding under. */
fun nextBaseRekeyKey(): GroupKey = nextBaseRekeyKey
@@ -70,6 +70,12 @@ class ConcordCommunitySessionTest {
assertTrue(session.channelAddresses().contains(priorGeneral.publicKeyHex), "historical plane not subscribed")
assertTrue(session.streamKeys().any { it.publicKeyHex == priorGeneral.publicKeyHex }, "historical stream key not AUTHed")
// The history pager asks for every epoch's plane at once: current + prior.
val currentGeneral = ConcordActions.publicChannel(community.communityRoot, community.generalChannelId, community.rootEpoch)
val allEpochPlanes = session.channelPlaneAddressesAllEpochs(community.generalChannelIdHex)
assertTrue(allEpochPlanes.contains(currentGeneral.publicKeyHex), "current plane missing from all-epochs")
assertTrue(allEpochPlanes.contains(priorGeneral.publicKeyHex), "prior plane missing from all-epochs")
// A message authored on the prior-epoch plane, bound to the prior epoch, decrypts + emits.
val oldMsg = ConcordActions.buildChannelMessage(owner, priorGeneral, community.generalChannelIdHex, priorEpoch, "gm from the old epoch", 2L)
assertEquals(ConcordIngestOutcome.NON_STRUCTURAL, session.ingest(oldMsg))