mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
fix(push): preserve baseline policies at lease capacity
Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
@@ -54,6 +54,12 @@ push capability. Supporting mobile clients add a single self-scoped
|
||||
for the first activation. They do not enumerate or synchronize thread follows.
|
||||
Old relays retain the existing mention/DM policy.
|
||||
|
||||
The existing lease capacity limits still apply. If the authored rule cannot
|
||||
represent all channel mutes (more than 350) or no subscription slot remains,
|
||||
the client omits that rule while continuing to synchronize mention/DM policies.
|
||||
It never truncates mute exclusions or abandons the whole update. The authored
|
||||
rule returns when capacity is available again.
|
||||
|
||||
The authenticated `/query` bridge accepts the same predicate and derives the
|
||||
recipient from the authenticated identity. Historical query filtering and batch
|
||||
wake matching share the database root-author relation, including community,
|
||||
|
||||
@@ -528,26 +528,6 @@ List<BuzzPushSubscription> buildDesiredBuzzPushSubscriptions({
|
||||
),
|
||||
];
|
||||
|
||||
if (authoredThreadReplies) {
|
||||
final muted = normalizedMuted.toList()..sort();
|
||||
subscriptions.add(
|
||||
BuzzPushSubscription(
|
||||
filter: BuzzPushFilter(
|
||||
kinds: buzzPushEligibleKinds,
|
||||
authoredThreadReplies: true,
|
||||
since: authoredThreadRepliesSince,
|
||||
),
|
||||
notificationClass: 'default',
|
||||
ignore: [
|
||||
selfAuthored,
|
||||
for (final chunk in _chunks(muted, buzzPushChannelChunkSize))
|
||||
BuzzPushFilter(kinds: buzzPushEligibleKinds, hTags: chunk),
|
||||
],
|
||||
suppress: suppression,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final channels = normalizedChannelIds.difference(normalizedMuted).toList()
|
||||
..sort();
|
||||
for (final chunk in _chunks(channels, buzzPushChannelChunkSize)) {
|
||||
@@ -563,6 +543,32 @@ List<BuzzPushSubscription> buildDesiredBuzzPushSubscriptions({
|
||||
if (subscriptions.length > buzzPushMaxSubscriptions) {
|
||||
throw const FormatException('Too many channels for a push lease.');
|
||||
}
|
||||
// Existing relay limits may leave no room for a fully muted authored rule.
|
||||
// Omit that rule rather than dropping exclusions or aborting mention/DM sync.
|
||||
final muted = normalizedMuted.toList()..sort();
|
||||
final authoredMuteChunks = _chunks(muted, buzzPushChannelChunkSize);
|
||||
if (authoredThreadReplies &&
|
||||
authoredMuteChunks.length + 1 <= buzzPushMaxIgnoreFilters &&
|
||||
subscriptions.length < buzzPushMaxSubscriptions) {
|
||||
subscriptions.insert(
|
||||
1,
|
||||
BuzzPushSubscription(
|
||||
filter: BuzzPushFilter(
|
||||
kinds: buzzPushEligibleKinds,
|
||||
authoredThreadReplies: true,
|
||||
since: authoredThreadRepliesSince,
|
||||
),
|
||||
notificationClass: 'default',
|
||||
ignore: [
|
||||
selfAuthored,
|
||||
for (final chunk in authoredMuteChunks)
|
||||
BuzzPushFilter(kinds: buzzPushEligibleKinds, hTags: chunk),
|
||||
],
|
||||
suppress: suppression,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return List.unmodifiable(subscriptions);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,62 @@ void main() {
|
||||
const channelA = '123e4567-e89b-42d3-a456-426614174000';
|
||||
const channelB = '123e4567-e89b-42d3-a456-426614174001';
|
||||
|
||||
List<String> channels(int count) => [
|
||||
for (var i = 0; i < count; i++)
|
||||
'00000000-0000-4000-8000-${i.toString().padLeft(12, '0')}',
|
||||
];
|
||||
|
||||
test(
|
||||
'authored mute capacity preserves every exclusion or omits the rule',
|
||||
() {
|
||||
final mutes = channels(351);
|
||||
final atLimit = buildDesiredBuzzPushSubscriptions(
|
||||
myPubkey: me,
|
||||
authoredThreadReplies: true,
|
||||
mutedChannelIds: mutes.take(350),
|
||||
).singleWhere((s) => s.filter.authoredThreadReplies);
|
||||
expect(atLimit.ignore, hasLength(buzzPushMaxIgnoreFilters));
|
||||
expect(atLimit.ignore.skip(1).expand((f) => f.hTags!), mutes.take(350));
|
||||
|
||||
final overLimit = buildDesiredBuzzPushSubscriptions(
|
||||
myPubkey: me,
|
||||
channelIds: [channelA],
|
||||
authoredThreadReplies: true,
|
||||
mutedChannelIds: mutes,
|
||||
);
|
||||
final baseline = buildDesiredBuzzPushSubscriptions(
|
||||
myPubkey: me,
|
||||
channelIds: [channelA],
|
||||
mutedChannelIds: mutes,
|
||||
);
|
||||
expect(
|
||||
buzzPushSubscriptionsFingerprint(overLimit),
|
||||
buzzPushSubscriptionsFingerprint(baseline),
|
||||
);
|
||||
expect(overLimit.last.filter.hTags, [channelA]);
|
||||
},
|
||||
);
|
||||
|
||||
test('authored rule never consumes a required DM subscription slot', () {
|
||||
final dms = channels(750);
|
||||
final withRoom = buildDesiredBuzzPushSubscriptions(
|
||||
myPubkey: me,
|
||||
channelIds: dms.take(700),
|
||||
authoredThreadReplies: true,
|
||||
);
|
||||
expect(withRoom, hasLength(buzzPushMaxSubscriptions));
|
||||
expect(withRoom.any((s) => s.filter.authoredThreadReplies), isTrue);
|
||||
|
||||
final full = buildDesiredBuzzPushSubscriptions(
|
||||
myPubkey: me,
|
||||
channelIds: dms,
|
||||
authoredThreadReplies: true,
|
||||
);
|
||||
expect(full, hasLength(buzzPushMaxSubscriptions));
|
||||
expect(full.any((s) => s.filter.authoredThreadReplies), isFalse);
|
||||
expect(full.skip(1).expand((s) => s.filter.hTags!), dms);
|
||||
});
|
||||
|
||||
test(
|
||||
'authored replies opt in without enumerating roots and honor all mutes',
|
||||
() {
|
||||
|
||||
Reference in New Issue
Block a user