Since every request gets its own visitor, a poll from the subscriber of an
open stream replaced the stream's visitor as the topic's rate visitor. Only
the stream's keepalives keep a rate visitor alive, so a day later UnifiedPush
publishes failed with 50701 while the stream was still open. A poll from the
same key now keeps the live rate visitor (and keeps it alive) instead.
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
emails_remaining was clamped to what the email bucket holds right now, so a fresh
Supporter saw 10 of 50. It is the daily limit minus today's emails again, as in
v2.34.0 and like messages_remaining; the bucket still refuses a burst past it.
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
A key whose buckets are full again and whose daily-capped counts are zero
looks exactly like a new one, so forgetting it changes nothing. Prune now
drops such keys once unused for a minute; the idle time stays as the
backstop for keys that never settle.
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
The server replays the whole second of a since=<id> marker, so a poll
returns messages the user deleted. Remember deleted notifications and
skip them (and older versions of their sequence), and don't move the
marker back when a poll only replayed known messages, which made each
poll replay a different part of that second.
Fixes#2098
Stale read the limiter key's last use, which every charged publish refreshes, so a topic kept
a vanished subscriber's limits (and its UnifiedPush subscription) for as long as anyone
published to it. The visitor now carries its own keepalive, set when it is built and by the
subscribe handlers' keepalive loops, like the shared visitor's seen was.
Also from the review: Prune returns and the manager logs the number of forgotten keys, the
unused visitorID wrapper goes, a stale comment and test name are updated, and the release
notes get the #1051 entry.
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
The three fixes apply as they are (the email guard without the visitor mutex, which this
branch no longer has). Of the new tests, the two that pin the shared visitor's SetUser flush
and the expunge of visitor objects have no counterpart here; the rest compare visitor keys
where they compared object identity.
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
A tier with a daily email limit of zero sends no emails (the limit service reads a daily
limit of zero as "no cap"), a tier edited in place flushes the visitor's pending user stats
before resetting its local counts like a tier ID change does, and a bucket refill with a
timestamp older than its last one neither charges tokens nor moves the refill point back.
The tests pin what the rework changed and what it must keep: shared keys count once in the
user stats, an expunged visitor continues its counts, the daily reset keeps the buckets,
zero bursts admit nothing, seeds apply once, prefix keying, tier changes in all directions,
concurrency on a shared key, and the account page's remaining values matching admissions.
Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
Visitors are built per request, so what they share (the keying and limit
config, the config-based limits of visitors without a tier, the limiter and
the subscription gauge) is built once in New. Keys are built with string
concatenation instead of fmt.Sprintf.
A visitor's key is touched when the visitor is built, so a subscriber whose
request skipped the limiter (an exempt prefix on an existing topic) is not
dropped as a stale rate visitor. The auth and account limits are per key and
ignore the user, as on main.
Requests from one IP (anonymous and untiered users) shared a single visitor
object with a mutable user, so overlapping requests could be authorized as
each other's user. The visitor is now built per request and never changed;
the limit service, keyed by visitor key, still shares the buckets and counts.
The Firebase penalty becomes a limiter kind, open subscriptions move to a
node-local gauge per key, and tier users' stats are enqueued per admitted
unit.
Fixes#1051
The Matrix gateway built its publish request from the push key without the
router's topic path check. Publishing sanitizes the topic after authorization
(it strips NUL bytes and invalid UTF-8), so an anonymous push key like
/sec%00ret was authorized against an unmatched topic and stored under the
protected topic "secret".
A tier upgrade used to keep the old bucket's tokens, capped at the new\nburst, so a bigger tier did not add its difference. The bucket now shifts\nby the burst difference both ways, down to a debt of one burst.\n\nemails_remaining is now capped by what the email bucket holds, so it no\nlonger shows emails the visitor cannot send (e.g. after the daily reset).\n\nClaude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe