This commit is contained in:
2026-09-22 14:50:15 +07:00
parent 8914685c3d
commit 04fb70e657
7 changed files with 926 additions and 256 deletions
+35 -12
View File
@@ -222,19 +222,39 @@ let messages = fold(&rumors, Timestamp::now(), |actor, citation, author| {
Relay history pages through the local cache:
```rust
use community::history::{self, Window};
use community::history::{self, PageRegistry, Window};
let page = history::page(client, &channel, &held, &relays, Window::newest(), 20, 50).await?;
// The same registry `CommunityRegistry` shares with its notification pump: it is
// what carries each page's EOSE and CLOSED back to the round waiting on it.
let page = history::page(
client,
&pages,
&channel,
&held,
&relays,
Window::opening(cursor),
20,
50,
)
.await?;
let cached = cache::query_rumors(&client, &channel, None, 50, Some(&cord03::ROW_KINDS)).await?;
```
A relay is only ever *asked*: `history::page` subscribes each one to the page's
filter (which is what makes the client verify a wrap, deduplicate it and persist
it), waits for EOSE, and then reads the page back out of the local database —
no wrap is ever consumed straight off the wire, and `fetch_events` is not used
anywhere. The wrap lands in the client's shared event store through that
ordinary ingest path; `cache_rumor` then puts the decrypted rumor beside it, and
only the rumor is ever served to a reader.
A relay is only ever *asked*: `history::page` installs **one REQ per page** over
`ReqTarget::manual` for the community's own relays (which is what makes the client
verify a wrap, deduplicate it and persist it), waits for every relay to settle,
and then reads the page back out of the local database — no wrap is ever consumed
straight off the wire, and `fetch_events` is not used anywhere. The wrap lands in
the client's shared event store through that ordinary ingest path; `cache_rumor`
then puts the decrypted rumor beside it, and only the rumor is ever served to a
reader.
EOSE and CLOSED are **not** watched inside the page. The registry's notification
pump is the only consumer of `client.notifications()`; it delivers one
`PageReport` per settling relay into the `PageRegistry` under the page's
subscription id, and the page waits on that channel with `PAGE_TIMEOUT`. The id
is opaque (`concord-history-<n>`) because a NIP-01 id is capped at 64 characters,
and the registry is what maps it back to the waiting round.
A page ends on EOSE. A CLOSED ends it as unanswered, *except* NIP-42's
`auth-required`: the SDK re-issues that REQ under the same subscription id once
@@ -246,11 +266,14 @@ reports what it saw: `oldest_ms`/`newest_ms` feed the caller's `ChannelCursor`,
`exhausted` is earned only by a short page *after* history was seen, and an
all-empty answer sets `failed` so a later round re-asks instead of sealing the
channel at "no more history". Page down with `Window::older_than(seen.oldest_ms)`,
open a channel with `Window::opening(cursor)` (wide cold, `newest_ms - 60s` warm),
and read the region between two cursors with `Window::between(..)`. `query_rumors`
is the read path when the group keys are gone; pass `kinds` to budget rows apart
from the events that only decorate them. Run `cache::purge_expired(client, &channel, now)`
on the same cadence as any other local sweep — the timer is cooperative, so the
local store is the artifact that has to forget.
from the events that only decorate them. `cache::wrapper_index` reads the cached
rows back keyed by the wrap they came from, which is what lets `sync::fold` observe
author and message times without re-opening a wrap it already cached, and
`cache::purge_expired(client, &channel, now)` runs at the top of every round — the
timer is cooperative, so the local store is the artifact that has to forget.
`ChatAction::TimerNotice { seconds }` is a policy notice, not a message: render it
as an inline row only when its author passes