Phase 8: push and notifications

Implements PLAN.md Phase 8. internal/pushrules is the rule language: the
spec's default ruleset, all six condition kinds, glob and word-boundary
matching, intentional mentions switching off the legacy body rules, and
rule ordering as Synapse has it. internal/pushserver keeps the rules,
pushers and notifications and talks to push gateways.

Push rules are the m.push_rules account data, written only by the push
server; reads keep the user's own rules and the enabled/actions of each
default and take the rest from the current defaults, so new default rules
reach every user without a migration. PUT account_data/m.push_rules is
refused with 405.

The push server listens on the roomserver's emit stage and evaluates each
event on its own worker for every local member and an invite's target.
Notifications and per-room unread counts are stored in migration 0010;
counts are a seventh sync token stream, so /sync reports
unread_notifications and picks up count changes made after the event or by
a receipt from another device. m.read and m.read.private receipts clear
the counts; leaving a room clears it.

Pushers (http kind only) push through a bounded sender pool with retries,
honour event_id_only, drop push keys the gateway rejects, and go away with
their device. GET /notifications pages the history. New config section
push: enabled, include_content, gateway_timeout, notification_retention.

Account data writes now wake the user's blocked syncs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
tajniak81andClaude Opus 5.5 committed 2026-10-17 11:43:36 +02:00
1 parent 74f7bc8805
commit b507753172
47 files changed
+5136 -50

No files matched your search

+14 -6
View File
@@ -18,12 +18,13 @@ import (
//
// The wire form is
//
// s{pdu}_{receipt}_{to_device}_{account_data}_{presence}_{device_list}
// s{pdu}_{receipt}_{to_device}_{account_data}_{presence}_{device_list}_{notification}
//
// with trailing zeroes left off, so a client that has only ever seen room
// events carries "s12" rather than "s12_0_0_0_0_0". The device list
// position came with Phase 7 and was added at the end, which is what lets
// every token handed out before it go on parsing. The leading "s" is there
// events carries "s12" rather than "s12_0_0_0_0_0_0". The device list
// position came with Phase 7 and the notification count position with
// Phase 8, each added at the end, which is what lets every token handed
// out before them go on parsing. The leading "s" is there
// to make a token recognisable in a log and to leave room for a second
// token shape later — Simplified Sliding Sync (PLAN.md phase 10) has its
// own — without any ambiguity about which is which.
@@ -38,10 +39,13 @@ type Token struct {
AccountData int64
Presence int64
DeviceList int64
// Notification is the push server's notification count stream.
Notification int64
}
// tokenPositions is how many positions a token carries.
const tokenPositions = 6
const tokenPositions = 7
// tokenPrefix marks a classic /sync token.
const tokenPrefix = "s"
@@ -52,7 +56,7 @@ func (t Token) IsInitial() bool { return t == Token{} }
// String renders the wire form.
func (t Token) String() string {
parts := []int64{t.PDU, t.Receipt, t.ToDevice, t.AccountData, t.Presence, t.DeviceList}
parts := []int64{t.PDU, t.Receipt, t.ToDevice, t.AccountData, t.Presence, t.DeviceList, t.Notification}
// Trailing zeroes carry no information, and leaving them off keeps the
// common token short.
last := 0
@@ -117,6 +121,8 @@ func ParseToken(raw string) (Token, error) {
AccountData: positions[3],
Presence: positions[4],
DeviceList: positions[5],
Notification: positions[6],
}, nil
}
@@ -130,6 +136,8 @@ func tokenAt(positions storage.StreamPositions) Token {
AccountData: positions.AccountData,
Presence: positions.Presence,
DeviceList: positions.DeviceList,
Notification: positions.Notification,
}
}