From b20d3114cb37d6660331dbf46e2796a93839ce3a Mon Sep 17 00:00:00 2001 From: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz> Date: Mon, 27 Jul 2026 20:20:31 -0700 Subject: [PATCH] fix(mobile/ios): reject events below composite cursor Co-authored-by: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz> Signed-off-by: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz> Co-authored-by: Cursor Ai-assisted: true --- mobile/ios/.gitignore | 1 + .../BuzzPushKit/PushConsumptionState.swift | 2 +- .../PushConsumptionStateTests.swift | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/mobile/ios/.gitignore b/mobile/ios/.gitignore index 6547384f3..d0234ff66 100644 --- a/mobile/ios/.gitignore +++ b/mobile/ios/.gitignore @@ -9,6 +9,7 @@ .tags* **/.vagrant/ **/DerivedData/ +BuzzPushKit/Package.resolved Icon? **/Pods/ **/.symlinks/ diff --git a/mobile/ios/BuzzPushKit/Sources/BuzzPushKit/PushConsumptionState.swift b/mobile/ios/BuzzPushKit/Sources/BuzzPushKit/PushConsumptionState.swift index 0eb15a6dd..640477a9e 100644 --- a/mobile/ios/BuzzPushKit/Sources/BuzzPushKit/PushConsumptionState.swift +++ b/mobile/ios/BuzzPushKit/Sources/BuzzPushKit/PushConsumptionState.swift @@ -112,7 +112,7 @@ public struct PushConsumptionState: Codable, Equatable, Sendable { ) -> Bool { let originState = state(for: origin) return position.createdAt <= now + allowedFutureSkew - && position.createdAt >= (originState.cursor?.createdAt ?? position.createdAt) + && position >= (originState.cursor ?? position) && !originState.contains(eventID: position.id) } diff --git a/mobile/ios/BuzzPushKit/Tests/BuzzPushKitTests/PushConsumptionStateTests.swift b/mobile/ios/BuzzPushKit/Tests/BuzzPushKitTests/PushConsumptionStateTests.swift index 8b8d2d171..7866e7a46 100644 --- a/mobile/ios/BuzzPushKit/Tests/BuzzPushKitTests/PushConsumptionStateTests.swift +++ b/mobile/ios/BuzzPushKit/Tests/BuzzPushKitTests/PushConsumptionStateTests.swift @@ -44,6 +44,25 @@ final class PushConsumptionStateTests: XCTestCase { XCTAssertEqual(state.state(for: "origin").delivered.count, 64) } + func testEvictedDeliveredEventBelowCompositeCursorIsNeverSelectedAgain() { + var state = PushConsumptionState() + for index in 0..<70 { + state.consume( + PushEventPosition(createdAt: 1_000, id: String(format: "%064x", index)), + for: "origin" + ) + } + let evicted = PushEventPosition(createdAt: 1_000, id: String(format: "%064x", 0)) + let sameSecondSuccessor = PushEventPosition( + createdAt: 1_000, + id: String(format: "%064x", 70) + ) + + XCTAssertFalse(state.hasConsumed(eventID: evicted.id, for: "origin")) + XCTAssertFalse(state.canSelect(evicted, for: "origin", now: 2_000)) + XCTAssertTrue(state.canSelect(sameSecondSuccessor, for: "origin", now: 2_000)) + } + func testEventOlderThanCompositeCursorIsNotSelected() { var state = PushConsumptionState() state.consume(PushEventPosition(createdAt: 2_000, id: "newer"), for: "origin")