Root cause of push notifications failing while cron works:
- Stalwart sends the verification request (RFC 8620 §7.2) once at
subscription creation; event pushes (§7.3) arrive without
verificationCode and rely on dedup_exists("push:subscription:{id}")
to confirm prior verification.
- TTL was 300s (5 min): key expired → every event push 401'd.
- Cron path bypasses this key entirely (polls /changes directly),
which is why cron-delivered emails still arrived.
Fix: align with PUSH_REGISTRATION_SECONDS (1 week). Stalwart does not
re-verify; the key must survive restarts and any gap between
subscription creation and events.
Also fixes: removed fictional push:subscription-code:{code} from
opengaps TTL table (no such key exists in code); corrected
reference.md caller from register_push→jmap_push verification path;
fixed 前→EX typo in reference.zh-CN.md; design.md updated with
fifth 'why' bullet and historical context.
New mailbox onboarding no longer floods Telegram with the entire
mailbox history. The initial baseline walk (ReconcileCursor::Walk,
any position) now passes an EmailQueryFilter to list_emails_page that
bounds Email/query to messages received within the last 7 days and
not yet seen ($seen keyword, RFC 8621 §4.1.2).
- EmailQueryFilter { received_after: Option<i64>, unread_only: bool }
in src/domain/jmap.rs (backend-agnostic domain type).
- StalwartJmap::fetch_email_page builds a compound Filter::and from
the filter conditions (after + not_keyword "$seen").
- walk_baseline computes the 7-day cutoff once and passes it to
every page fetch, so resumed walks stay filtered.
- Replay(state) path returns early and never calls list_emails_page,
so /changes incremental is unaffected.
- const INITIAL_BASELINE_WINDOW_DAYS: u64 = 7.
- New test first_reconcile_filters_the_initial_baseline_to_recent_unread
pins the filter on every Walk page and the cursor transition to Replay.
- 3 doc anchors re-anchored (worker.rs:227→241, client.rs:485→513 ×2).
GATE-P0: 104 passed / 0 failed / 4 ignored.
The retire-on-change commit inserted ~500 lines into notify.rs and ~110
into state.rs, shifting every anchor at or after the insertion point.
The ANCHOR audit only proves the referenced line exists and is
non-empty, so all of these passed while pointing at the wrong code:
deployment.md/.zh-CN register_telegram_webhook 1619 -> 1623
register_push 1464 -> 1468 (EN)
register_telegram_webhook 1439 -> 1623 (zh)
register_push 1315 -> 1468 (zh)
retired.md/.zh-CN registration_lock fmt 1328-1330 -> 1481-1483
register_push 1315 -> 1468
state.rs read_batch 491 -> 506
state.rs query_async 531 -> 546
state.rs XGROUP CREATE 499-507 -> 514-522
opengaps.md/.zh-CN get_reconcile_state 777 -> 792
set_reconcile_state 785 -> 800
acquire_lock 1076 -> 1130
Every target was verified against the source before editing: two of the
pre-existing anchors pointed inside a different function than the one
they named, and the EN deployment.md had both register anchors off while
the zh twin still carried the original pre-move numbers.
Also documents the new push:registration:current key in the Redis key
table (writer, readers, TTL, and why its literal suffix cannot collide
with the hex session_digest suffix) and carries the zh twin the EN
version already got. Mirror the retire-on-change contract in
deployment.zh-CN.md - the zh twin has its own callback section, which
the EN update skipped.