Repository: wireapp/wire-ios
Pull request #2913: feat: update capabilities with consumable notifications - WPB-17092
Author: login: netbe
Created: 2025-04-17T07:15:03Z | Merged: 2025-04-18T16:34:44Z

<!--do not remove this marker, its needed to replace info when ticket title is updated -->
<!--jira-description-action-hidden-marker-start-->

<table>
<td>
  <a href="https://wearezeta.atlassian.net/browse/WPB-17092" title="WPB-17092" target="_blank"><img alt="Task" src="https://wearezeta.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10818?size=medium" />WPB-17092</a>  [iOS] Update client capabilities 
  </td></table>
  <br />
 

<!--jira-description-action-hidden-marker-end-->
<!--do not remove the jira markers to link tickets automatically -->


### Issue

In order to use the new sync websocket, clients must update their capibilities. This PR registers the new capabitiliy: `consumable-notifications`

* add new developer flag: `asyncStreamNotifications`
* register new `consumable-notifications` when flag on and apiV8
* save capability under `asyncStreamCapable` attribute in UserClient (db migration)
* prepare to use new sync if `asyncStreamCapable` is true
* show information in DeveloperTools

### Testing

1. enable `asyncStreamNotifications` flag
2. login
Verify in DeveloperTools `asyncStreamCapable` is set

---

### Checklist

- [x] Title contains a reference JIRA issue number like `[WPB-XXX]`.
- [x] Description is filled and free of optional paragraphs.
- [x] Adds/updates automated tests.



Diff:
diff --git a/WireAPI/Sources/WireAPI/Models/UserClient/UserClientCapability.swift b/WireAPI/Sources/WireAPI/Models/UserClient/UserClientCapability.swift
index aeb34141174..1203cace6dd 100644
--- a/WireAPI/Sources/WireAPI/Models/UserClient/UserClientCapability.swift
+++ b/WireAPI/Sources/WireAPI/Models/UserClient/UserClientCapability.swift
@@ -25,4 +25,8 @@ public enum UserClientCapability: String, Codable, Sendable {
 
     case legalholdConsent = "legalhold-implicit-consent"
 
+    /// The client is able to use new incremental sync from server using websocket acknowledgement (async notifications)
+
+    case consumableNotifications = "consumable-notifications"
+
 }
diff --git a/WireDomain/Sources/WireDomain/Repositories/UserClients/Models/UserClientInfo.swift b/WireDomain/Sources/WireDomain/Repositories/UserClients/Models/UserClientInfo.swift
index 240fed7ba83..2c6539cf03b 100644
--- a/WireDomain/Sources/WireDomain/Repositories/UserClients/Models/UserClientInfo.swift
+++ b/WireDomain/Sources/WireDomain/Repositories/UserClients/Models/UserClientInfo.swift
@@ -16,6 +16,7 @@
 // along with this program. If not, see http://www.gnu.org/licenses/.
 //
 
+import WireAPI
 import WireDataModel
 
 public struct UserClientInfo: Sendable {
@@ -28,6 +29,7 @@ public struct UserClientInfo: Sendable {
     let deviceClass: WireDataModel.DeviceClass?
     let lastActiveDate: Date?
     let mlsPublicKeys: UserClientInfo.MLSPublicKeys?
+    let capabilities: [UserClientCapability]
 
     struct MLSPublicKeys {
         let ed25519: String?
@@ -36,5 +38,4 @@ public struct UserClientInfo: Sendable {
         let p384: String?
         let p512: String?
     }
-
 }
diff --git a/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsLocalStore.swift b/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsLocalStore.swift
index 98a6103180e..3a0bea5e552 100644
--- a/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsLocalStore.swift
+++ b/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsLocalStore.swift
@@ -131,6 +131,7 @@ public final class UserClientsLocalStore: UserClientsLocalStoreProtocol {
             localClient.activationDate = userClientInfo.activationDate
             localClient.lastActiveDate = userClientInfo.lastActiveDate
             localClient.remoteIdentifier = userClientInfo.id
+            localClient.asyncStreamCapable = userClientInfo.capabilities.contains(.consumableNotifications)
 
             let selfUser = ZMUser.selfUser(in: context)
             localClient.user = localClient.user ?? selfUser
diff --git a/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsModelMappings.swift b/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsModelMappings.swift
index c6974d6215b..3d0b5cf47f2 100644
--- a/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsModelMappings.swift
+++ b/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsModelMappings.swift
@@ -35,7 +35,7 @@ extension WireAPI.SelfUserClient {
                 p256: mlsPublicKeys?.p256,
                 p384: mlsPublicKeys?.p384,
                 p512: mlsPublicKeys?.p512
-            )
+            ), capabilities: capabilities
         )
     }
 
diff --git a/WireDomain/Tests/WireDomainTests/LocalStores/UserClientsLocalStoreTests.swift b/WireDomain/Tests/WireDomainTests/LocalStores/UserClientsLocalStoreTests.swift
index e6faf37b429..86709d53316 100644
--- a/WireDomain/Tests/WireDomainTests/LocalStores/UserClientsLocalStoreTests.swift
+++ b/WireDomain/Tests/WireDomainTests/LocalStores/UserClientsLocalStoreTests.swift
@@ -201,7 +201,8 @@ final class UserClientsLocalStoreTests: XCTestCase {
             model: "test",
             deviceClass: .phone,
             lastActiveDate: nil,
-            mlsPublicKeys: nil
+            mlsPublicKeys: nil,
+            capabilities: [.legalholdConsent]
         )
 
     }
diff --git a/wire-ios-data-model/Resources/Databases Changelog.md b/wire-ios-data-model/Resources/Databases Changelog.md
index c9334512728..e145d3d6aea 100644
--- a/wire-ios-data-model/Resources/Databases Changelog.md	
+++ b/wire-ios-data-model/Resources/Databases Changelog.md	
@@ -4,6 +4,10 @@ As it is hard to spot changes from version to version of database models (.xcdat
 
 ## zmessaging
 
+### 2.125.0
+
+* added `asyncStreamCapable` attribute on the UserClient entity
+
 ### 2.124.0
 
 * added `migratedToMLS` attribute on the Conversation entity
@@ -24,6 +28,8 @@ Removed `pushToken` attribute from `UserClient`.
 
 ### 2.120.0
 
+* Added `ToDeleted` entity
+
 PostAction to fix issue with federation migration. It triggers a resyncResources to make sure users and conversations get the domain.
 
 ### 2.119.0
diff --git a/wire-ios-data-model/Resources/zmessaging.xcdatamodeld/.xccurrentversion b/wire-ios-data-model/Resources/zmessaging.xcdatamodeld/.xccurrentversion
index 7fffc2183dc..364c0500273 100644
--- a/wire-ios-data-model/Resources/zmessaging.xcdatamodeld/.xccurrentversion
+++ b/wire-ios-data-model/Resources/zmessaging.xcdatamodeld/.xccurrentversion
@@ -3,6 +3,6 @@
 <plist version="1.0">
 <dict>
 	<key>_XCCurrentVersionName</key>
-	<string>zmessaging2.124.0.xcdatamodel</string>
+	<string>zmessaging2.125.0.xcdatamodel</string>
 </dict>
 </plist>
diff --git a/wire-ios-data-model/Resources/zmessaging.xcdatamodeld/zmessaging2.125.0.xcdatamodel/contents b/wire-ios-data-model/Resources/zmessaging.xcdatamodeld/zmessaging2.125.0.xcdatamodel/contents
new file mode 100644
index 00000000000..05b8fe202a4
--- /dev/null
+++ b/wire-ios-data-model/Resources/zmessaging.xcdatamodeld/zmessaging2.125.0.xcdatamodel/contents
@@ -0,0 +1,411 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23605" systemVersion="23H527" minimumToolsVersion="Xcode 11.0" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="2.125.0">
+    <entity name="Action" representedClassName=".Action" syncable="YES">
+        <attribute name="name" optional="YES" attributeType="String"/>
+        <relationship name="role" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Role" inverseName="actions" inverseEntity="Role"/>
+    </entity>
+    <entity name="AddressBookEntry" representedClassName=".AddressBookEntry" syncable="YES">
+        <attribute name="cachedName" optional="YES" attributeType="String"/>
+        <attribute name="localIdentifier" optional="YES" attributeType="String"/>
+        <relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="addressBookEntry" inverseEntity="User"/>
+        <fetchIndex name="byLocalIdentifierIndex">
+            <fetchIndexElement property="localIdentifier" type="Binary" order="ascending"/>
+        </fetchIndex>
+    </entity>
+    <entity name="AssetClientMessage" representedClassName=".ZMAssetClientMessage" parentEntity="Message" syncable="YES">
+        <attribute name="assetId" optional="YES" transient="YES"/>
+        <attribute name="assetId_data" optional="YES" attributeType="Binary"/>
+        <attribute name="associatedTaskIdentifier" optional="YES" transient="YES"/>
+        <attribute name="associatedTaskIdentifier_data" optional="YES" attributeType="Binary"/>
+        <attribute name="isDownloading" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="preprocessedSize" optional="YES" transient="YES"/>
+        <attribute name="preprocessedSize_data" optional="YES" attributeType="Binary"/>
+        <attribute name="progress" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="NO"/>
+        <attribute name="transferState" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="uploadState" optional="YES" attributeType="Integer 16" usesScalarValueType="NO"/>
+        <attribute name="version" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <relationship name="dataSet" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="GenericMessageData" inverseName="asset" inverseEntity="GenericMessageData"/>
+    </entity>
+    <entity name="ButtonState" representedClassName=".ButtonState" syncable="YES">
+        <attribute name="isExpired" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="remoteIdentifier" optional="YES" attributeType="String"/>
+        <attribute name="stateValue" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <relationship name="message" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Message" inverseName="buttonStates" inverseEntity="Message"/>
+    </entity>
+    <entity name="ClientMessage" representedClassName=".ZMClientMessage" parentEntity="Message" syncable="YES">
+        <attribute name="linkPreviewState" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="updatedTimestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <relationship name="dataSet" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="GenericMessageData" inverseName="message" inverseEntity="GenericMessageData"/>
+    </entity>
+    <entity name="Connection" representedClassName="ZMConnection" syncable="YES">
+        <attribute name="existsOnBackend" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="lastUpdateDateInGMT" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="message" optional="YES" attributeType="String"/>
+        <attribute name="modifiedKeys" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <attribute name="needsToBeUpdatedFromBackend" attributeType="Boolean" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="status" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <relationship name="to" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="connection" inverseEntity="User"/>
+    </entity>
+    <entity name="Conversation" representedClassName="ZMConversation" syncable="YES">
+        <attribute name="accessModeStrings" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <attribute name="accessRoleString" optional="YES" attributeType="String"/>
+        <attribute name="accessRoleStringsV2" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <attribute name="archivedChangedTimestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="ciphersuite" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="clearedTimeStamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="commitPendingProposalDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="conversationType" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="domain" optional="YES" attributeType="String"/>
+        <attribute name="draftMessageData" optional="YES" attributeType="Binary"/>
+        <attribute name="draftMessageNonce" optional="YES" attributeType="Binary"/>
+        <attribute name="epoch" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="groupType" optional="YES" attributeType="Integer 16" usesScalarValueType="NO"/>
+        <attribute name="hasReadReceiptsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="NO"/>
+        <attribute name="hasUnreadUnsentMessage" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="internalEstimatedUnreadCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="internalEstimatedUnreadSelfMentionCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="internalEstimatedUnreadSelfReplyCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="internalIsArchived" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="isDeletedRemotely" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="isForcedReadOnly" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="isPendingInitialFetch" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
+        <attribute name="isPendingMetadataRefresh" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="isSelfAnActiveMember" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="NO"/>
+        <attribute name="language" optional="YES" attributeType="String"/>
+        <attribute name="lastModifiedDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="lastReadServerTimeStamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="lastServerTimeStamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="lastUnreadKnockDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="lastUnreadMissedCallDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="legalHoldStatus" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="localMessageDestructionTimeout" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="NO" elementID="messageDestructionTimeout"/>
+        <attribute name="messageProtocol" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="migratedToMLS" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
+        <attribute name="mlsGroupID" optional="YES" attributeType="Binary"/>
+        <attribute name="mlsStatus" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="mlsVerificationStatus" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
+        <attribute name="modifiedKeys" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <attribute name="mutedStatus" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="NO" elementID="mutedStatus"/>
+        <attribute name="needsToBeUpdatedFromBackend" attributeType="Boolean" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="needsToCalculateUnreadMessages" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="needsToDownloadRoles" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="needsToVerifyLegalHold" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="normalizedUserDefinedName" optional="YES" attributeType="String"/>
+        <attribute name="primaryKey" optional="YES" attributeType="String"/>
+        <attribute name="privateChannelPermission" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
+        <attribute name="remoteIdentifier" optional="YES" transient="YES"/>
+        <attribute name="remoteIdentifier_data" optional="YES" attributeType="Binary"/>
+        <attribute name="securityLevel" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="silencedChangedTimestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="syncedMessageDestructionTimeout" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO"/>
+        <attribute name="teamRemoteIdentifier" optional="YES" transient="YES"/>
+        <attribute name="teamRemoteIdentifier_data" optional="YES" attributeType="Binary"/>
+        <attribute name="userDefinedName" optional="YES" attributeType="String"/>
+        <attribute name="voiceChannel" optional="YES" transient="YES"/>
+        <relationship name="allMessages" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Message" inverseName="visibleInConversation" inverseEntity="Message" elementID="messages"/>
+        <relationship name="creator" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="conversationsCreated" inverseEntity="User"/>
+        <relationship name="hiddenMessages" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Message" inverseName="hiddenInConversation" inverseEntity="Message"/>
+        <relationship name="labels" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Label" inverseName="conversations" inverseEntity="Label"/>
+        <relationship name="lastServerSyncedActiveParticipants" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="User" inverseName="lastServerSyncedActiveConversations" inverseEntity="User"/>
+        <relationship name="nonTeamRoles" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Role" inverseName="conversation" inverseEntity="Role"/>
+        <relationship name="oneOnOneUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="oneOnOneConversation" inverseEntity="User"/>
+        <relationship name="participantRoles" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="ParticipantRole" inverseName="conversation" inverseEntity="ParticipantRole"/>
+        <relationship name="team" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Team" inverseName="conversations" inverseEntity="Team"/>
+        <fetchIndex name="byNormalizedUserDefinedNameIndex">
+            <fetchIndexElement property="normalizedUserDefinedName" type="Binary" order="ascending"/>
+        </fetchIndex>
+        <fetchIndex name="byRemoteIdentifier_dataIndex">
+            <fetchIndexElement property="remoteIdentifier_data" type="Binary" order="ascending"/>
+        </fetchIndex>
+        <fetchIndex name="compoundIndex">
+            <fetchIndexElement property="internalIsArchived" type="Binary" order="ascending"/>
+            <fetchIndexElement property="lastModifiedDate" type="Binary" order="ascending"/>
+        </fetchIndex>
+        <uniquenessConstraints>
+            <uniquenessConstraint>
+                <constraint value="primaryKey"/>
+            </uniquenessConstraint>
+        </uniquenessConstraints>
+    </entity>
+    <entity name="Feature" representedClassName=".Feature" syncable="YES">
+        <attribute name="configData" optional="YES" attributeType="Binary"/>
+        <attribute name="hasInitialDefault" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="modifiedKeys" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <attribute name="nameValue" attributeType="String"/>
+        <attribute name="needsToNotifyUser" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="statusValue" attributeType="String"/>
+    </entity>
+    <entity name="GenericMessageData" representedClassName="ZMGenericMessageData" syncable="YES">
+        <attribute name="data" attributeType="Binary"/>
+        <attribute name="nonce" optional="YES" attributeType="Binary"/>
+        <relationship name="asset" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="AssetClientMessage" inverseName="dataSet" inverseEntity="AssetClientMessage"/>
+        <relationship name="message" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ClientMessage" inverseName="dataSet" inverseEntity="ClientMessage"/>
+    </entity>
+    <entity name="ImageMessage" representedClassName="ZMImageMessage" parentEntity="Message" syncable="YES">
+        <attribute name="imageType" optional="YES" attributeType="String"/>
+        <attribute name="isAnimatedGIF" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="mediumDataLoaded" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="mediumRemoteIdentifier" optional="YES" transient="YES"/>
+        <attribute name="mediumRemoteIdentifier_data" optional="YES" attributeType="Binary"/>
+        <attribute name="originalDataProcessed" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="originalSize" optional="YES" transient="YES"/>
+        <attribute name="originalSize_data" optional="YES" attributeType="Binary"/>
+    </entity>
+    <entity name="KnockMessage" representedClassName="ZMKnockMessage" parentEntity="Message" syncable="YES"/>
+    <entity name="Label" representedClassName=".Label" syncable="YES">
+        <attribute name="markedForDeletion" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="modifiedKeys" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <attribute name="name" optional="YES" attributeType="String"/>
+        <attribute name="remoteIdentifier" optional="YES" transient="YES"/>
+        <attribute name="remoteIdentifier_data" optional="YES" attributeType="Binary"/>
+        <attribute name="type" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <relationship name="conversations" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Conversation" inverseName="labels" inverseEntity="Conversation"/>
+    </entity>
+    <entity name="Member" representedClassName=".Member" syncable="YES">
+        <attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="needsToBeUpdatedFromBackend" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="permissionsRawValue" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="remoteIdentifier" optional="YES" transient="YES"/>
+        <attribute name="remoteIdentifier_data" optional="YES" attributeType="Binary"/>
+        <relationship name="createdBy" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="createdTeamMembers" inverseEntity="User"/>
+        <relationship name="team" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Team" inverseName="members" inverseEntity="Team"/>
+        <relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="membership" inverseEntity="User"/>
+        <fetchIndex name="byRemoteIdentifier_dataIndex">
+            <fetchIndexElement property="remoteIdentifier_data" type="Binary" order="ascending"/>
+        </fetchIndex>
+    </entity>
+    <entity name="Message" representedClassName="ZMMessage" isAbstract="YES" syncable="YES">
+        <attribute name="cachedCategory" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
+        <attribute name="delivered" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="destructionDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="expectsReadConfirmation" optional="YES" attributeType="Boolean" usesScalarValueType="NO"/>
+        <attribute name="expirationDate" optional="YES" attributeType="Date" usesScalarValueType="NO" versionHashModifier="add index"/>
+        <attribute name="expirationReasonCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="isExpired" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="isObfuscated" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="linkAttachments" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <attribute name="modifiedKeys" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <attribute name="needsLinkAttachmentsUpdate" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="needsToBeUpdatedFromBackend" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="nonce" optional="YES" transient="YES"/>
+        <attribute name="nonce_data" optional="YES" attributeType="Binary" versionHashModifier="add_index1"/>
+        <attribute name="normalizedText" optional="YES" attributeType="String"/>
+        <attribute name="senderClientID" optional="YES" attributeType="String"/>
+        <attribute name="serverTimestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="shouldExpire" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
+        <relationship name="buttonStates" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="ButtonState" inverseName="message" inverseEntity="ButtonState"/>
+        <relationship name="confirmations" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MessageConfirmation" inverseName="message" inverseEntity="MessageConfirmation"/>
+        <relationship name="discoveredClients" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="UserClient" inverseName="discoveredByMessage" inverseEntity="UserClient"/>
+        <relationship name="failedToSendRecipients" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="User" inverseName="messagesFailedToSendRecipient" inverseEntity="User"/>
+        <relationship name="hiddenInConversation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Conversation" inverseName="hiddenMessages" inverseEntity="Conversation"/>
+        <relationship name="missingRecipients" optional="YES" transient="YES" toMany="YES" deletionRule="Nullify" destinationEntity="UserClient" inverseName="messagesMissingRecipient" inverseEntity="UserClient"/>
+        <relationship name="quote" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Message" inverseName="replies" inverseEntity="Message"/>
+        <relationship name="reactions" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Reaction" inverseName="message" inverseEntity="Reaction"/>
+        <relationship name="replies" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Message" inverseName="quote" inverseEntity="Message"/>
+        <relationship name="sender" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User"/>
+        <relationship name="visibleInConversation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Conversation" inverseName="allMessages" inverseEntity="Conversation"/>
+        <fetchIndex name="byDestructionDateIndex">
+            <fetchIndexElement property="destructionDate" type="Binary" order="ascending"/>
+        </fetchIndex>
+        <fetchIndex name="byExpirationDateIndex">
+            <fetchIndexElement property="expirationDate" type="Binary" order="ascending"/>
+        </fetchIndex>
+        <fetchIndex name="byNonce_dataIndex">
+            <fetchIndexElement property="nonce_data" type="Binary" order="ascending"/>
+        </fetchIndex>
+        <fetchIndex name="byNormalizedTextIndex">
+            <fetchIndexElement property="normalizedText" type="Binary" order="ascending"/>
+        </fetchIndex>
+        <fetchIndex name="compoundIndex">
+            <fetchIndexElement property="visibleInConversation" type="Binary" order="ascending"/>
+            <fetchIndexElement property="serverTimestamp" type="Binary" order="ascending"/>
+            <fetchIndexElement property="isExpired" type="Binary" order="ascending"/>
+            <fetchIndexElement property="delivered" type="Binary" order="ascending"/>
+        </fetchIndex>
+        <fetchIndex name="compoundIndex1">
+            <fetchIndexElement property="hiddenInConversation" type="Binary" order="ascending"/>
+            <fetchIndexElement property="isExpired" type="Binary" order="ascending"/>
+            <fetchIndexElement property="serverTimestamp" type="Binary" order="ascending"/>
+            <fetchIndexElement property="delivered" type="Binary" order="ascending"/>
+        </fetchIndex>
+    </entity>
+    <entity name="MessageConfirmation" representedClassName="ZMMessageConfirmation" syncable="YES">
+        <attribute name="serverTimestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="type" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <relationship name="message" maxCount="1" deletionRule="Nullify" destinationEntity="Message" inverseName="confirmations" inverseEntity="Message"/>
+        <relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User"/>
+    </entity>
+    <entity name="MLSGroup" representedClassName=".MLSGroup" syncable="YES">
+        <attribute name="id" optional="YES" attributeType="Binary"/>
+        <attribute name="lastKeyMaterialUpdate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+    </entity>
+    <entity name="ParticipantRole" representedClassName=".ParticipantRole" syncable="YES">
+        <attribute name="modifiedKeys" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <relationship name="conversation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Conversation" inverseName="participantRoles" inverseEntity="Conversation"/>
+        <relationship name="role" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Role" inverseName="participantRoles" inverseEntity="Role"/>
+        <relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="participantRoles" inverseEntity="User"/>
+    </entity>
+    <entity name="Reaction" representedClassName=".Reaction" syncable="YES">
+        <attribute name="firstReactionDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="modifiedKeys" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <attribute name="unicodeValue" optional="YES" attributeType="String"/>
+        <relationship name="message" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Message" inverseName="reactions" inverseEntity="Message"/>
+        <relationship name="users" toMany="YES" deletionRule="Nullify" destinationEntity="User" inverseName="reactions" inverseEntity="User"/>
+    </entity>
+    <entity name="Role" representedClassName=".Role" syncable="YES">
+        <attribute name="name" optional="YES" attributeType="String"/>
+        <relationship name="actions" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Action" inverseName="role" inverseEntity="Action"/>
+        <relationship name="conversation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Conversation" inverseName="nonTeamRoles" inverseEntity="Conversation"/>
+        <relationship name="participantRoles" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="ParticipantRole" inverseName="role" inverseEntity="ParticipantRole"/>
+        <relationship name="team" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Team" inverseName="roles" inverseEntity="Team"/>
+    </entity>
+    <entity name="Session" representedClassName="ZMSession" syncable="YES">
+        <relationship name="selfUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User"/>
+    </entity>
+    <entity name="SystemMessage" representedClassName="ZMSystemMessage" parentEntity="Message" syncable="YES">
+        <attribute name="allTeamUsersAdded" optional="YES" attributeType="Boolean" usesScalarValueType="NO"/>
+        <attribute name="decryptionErrorCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="domains" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <attribute name="duration" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO"/>
+        <attribute name="messageTimer" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO"/>
+        <attribute name="needsUpdatingUsers" optional="YES" attributeType="Boolean" usesScalarValueType="NO"/>
+        <attribute name="numberOfGuestsAdded" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="participantsRemovedReason" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="relevantForConversationStatus" optional="YES" attributeType="Boolean" usesScalarValueType="NO"/>
+        <attribute name="systemMessageType" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="text" optional="YES" attributeType="String"/>
+        <relationship name="addedUsers" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="User" inverseName="showingUserAdded" inverseEntity="User"/>
+        <relationship name="childMessages" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="SystemMessage" inverseName="parentMessage" inverseEntity="SystemMessage"/>
+        <relationship name="clients" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="UserClient" inverseName="addedOrRemovedInSystemMessages" inverseEntity="UserClient"/>
+        <relationship name="parentMessage" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SystemMessage" inverseName="childMessages" inverseEntity="SystemMessage"/>
+        <relationship name="removedUsers" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="User" inverseName="showingUserRemoved" inverseEntity="User"/>
+        <relationship name="users" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="User" inverseName="systemMessages" inverseEntity="User"/>
+    </entity>
+    <entity name="Team" representedClassName=".Team" syncable="YES">
+        <attribute name="name" optional="YES" attributeType="String"/>
+        <attribute name="needsToBeUpdatedFromBackend" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="needsToDownloadRoles" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="NO"/>
+        <attribute name="needsToRedownloadMembers" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="pictureAssetId" optional="YES" attributeType="String"/>
+        <attribute name="pictureAssetKey" optional="YES" attributeType="String"/>
+        <attribute name="remoteIdentifier" optional="YES" transient="YES"/>
+        <attribute name="remoteIdentifier_data" optional="YES" attributeType="Binary"/>
+        <relationship name="conversations" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Conversation" inverseName="team" inverseEntity="Conversation"/>
+        <relationship name="creator" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="createdTeams" inverseEntity="User"/>
+        <relationship name="members" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Member" inverseName="team" inverseEntity="Member"/>
+        <relationship name="roles" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Role" inverseName="team" inverseEntity="Role"/>
+        <fetchIndex name="byRemoteIdentifier_dataIndex">
+            <fetchIndexElement property="remoteIdentifier_data" type="Binary" order="ascending"/>
+        </fetchIndex>
+        <uniquenessConstraints>
+            <uniquenessConstraint>
+                <constraint value="remoteIdentifier_data"/>
+            </uniquenessConstraint>
+        </uniquenessConstraints>
+    </entity>
+    <entity name="TextMessage" representedClassName="ZMTextMessage" parentEntity="Message" syncable="YES">
+        <attribute name="text" optional="YES" attributeType="String"/>
+    </entity>
+    <entity name="User" representedClassName="ZMUser" syncable="YES">
+        <attribute name="accentColorValue" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="analyticsIdentifier" optional="YES" attributeType="String"/>
+        <attribute name="availability" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="completeProfileAssetIdentifier" optional="YES" attributeType="String"/>
+        <attribute name="domain" optional="YES" attributeType="String"/>
+        <attribute name="emailAddress" optional="YES" attributeType="String"/>
+        <attribute name="expiresAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="handle" optional="YES" attributeType="String"/>
+        <attribute name="isAccountDeleted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="isPendingMetadataRefresh" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="legalHoldRequest" optional="YES" attributeType="Binary"/>
+        <attribute name="managedBy" optional="YES" attributeType="String"/>
+        <attribute name="modifiedKeys" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <attribute name="name" optional="YES" attributeType="String"/>
+        <attribute name="needsPropertiesUpdate" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="NO"/>
+        <attribute name="needsRichProfileUpdate" optional="YES" attributeType="Boolean" usesScalarValueType="NO"/>
+        <attribute name="needsToAcknowledgeLegalHoldStatus" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="needsToBeUpdatedFromBackend" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="needsToRefetchLabels" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="normalizedEmailAddress" optional="YES" attributeType="String"/>
+        <attribute name="normalizedName" optional="YES" attributeType="String"/>
+        <attribute name="previewProfileAssetIdentifier" optional="YES" attributeType="String"/>
+        <attribute name="primaryKey" optional="YES" attributeType="String"/>
+        <attribute name="providerIdentifier" optional="YES" attributeType="String"/>
+        <attribute name="readReceiptsEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="readReceiptsEnabledChangedRemotely" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="remoteIdentifier" optional="YES" transient="YES"/>
+        <attribute name="remoteIdentifier_data" optional="YES" attributeType="Binary"/>
+        <attribute name="richProfile" optional="YES" attributeType="Binary"/>
+        <attribute name="serviceIdentifier" optional="YES" attributeType="String"/>
+        <attribute name="supportedProtocols" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <attribute name="teamIdentifier" optional="YES" transient="YES"/>
+        <attribute name="teamIdentifier_data" optional="YES" attributeType="Binary"/>
+        <attribute name="usesCompanyLogin" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <relationship name="addressBookEntry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="AddressBookEntry" inverseName="user" inverseEntity="AddressBookEntry"/>
+        <relationship name="clients" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="UserClient" inverseName="user" inverseEntity="UserClient"/>
+        <relationship name="connection" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Connection" inverseName="to" inverseEntity="Connection"/>
+        <relationship name="conversationsCreated" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Conversation" inverseName="creator" inverseEntity="Conversation"/>
+        <relationship name="createdTeamMembers" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Member" inverseName="createdBy" inverseEntity="Member"/>
+        <relationship name="createdTeams" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Team" inverseName="creator" inverseEntity="Team"/>
+        <relationship name="lastServerSyncedActiveConversations" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="Conversation" inverseName="lastServerSyncedActiveParticipants" inverseEntity="Conversation"/>
+        <relationship name="membership" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Member" inverseName="user" inverseEntity="Member"/>
+        <relationship name="messagesFailedToSendRecipient" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Message" inverseName="failedToSendRecipients" inverseEntity="Message"/>
+        <relationship name="oneOnOneConversation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Conversation" inverseName="oneOnOneUser" inverseEntity="Conversation"/>
+        <relationship name="participantRoles" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="ParticipantRole" inverseName="user" inverseEntity="ParticipantRole"/>
+        <relationship name="reactions" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Reaction" inverseName="users" inverseEntity="Reaction"/>
+        <relationship name="showingUserAdded" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="SystemMessage" inverseName="addedUsers" inverseEntity="SystemMessage"/>
+        <relationship name="showingUserRemoved" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="SystemMessage" inverseName="removedUsers" inverseEntity="SystemMessage"/>
+        <relationship name="systemMessages" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="SystemMessage" inverseName="users" inverseEntity="SystemMessage"/>
+        <fetchIndex name="byNormalizedEmailAddressIndex">
+            <fetchIndexElement property="normalizedEmailAddress" type="Binary" order="ascending"/>
+        </fetchIndex>
+        <fetchIndex name="byNormalizedNameIndex">
+            <fetchIndexElement property="normalizedName" type="Binary" order="ascending"/>
+        </fetchIndex>
+        <fetchIndex name="byRemoteIdentifier_dataIndex">
+            <fetchIndexElement property="remoteIdentifier_data" type="Binary" order="ascending"/>
+        </fetchIndex>
+        <uniquenessConstraints>
+            <uniquenessConstraint>
+                <constraint value="primaryKey"/>
+            </uniquenessConstraint>
+        </uniquenessConstraints>
+    </entity>
+    <entity name="UserClient" representedClassName=".UserClient" syncable="YES">
+        <attribute name="activationDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="asyncStreamCapable" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
+        <attribute name="deviceClass" optional="YES" attributeType="String"/>
+        <attribute name="discoveryDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="label" optional="YES" attributeType="String"/>
+        <attribute name="lastActiveDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="markedToDelete" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="mlsPublicKeys" optional="YES" attributeType="Binary"/>
+        <attribute name="model" optional="YES" attributeType="String"/>
+        <attribute name="modifiedKeys" optional="YES" attributeType="Transformable" valueTransformerName="ExtendedSecureUnarchiveFromData"/>
+        <attribute name="needsSessionMigration" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="NO"/>
+        <attribute name="needsToBeUpdatedFromBackend" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="needsToNotifyOtherUserAboutSessionReset" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="needsToNotifyUser" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="needsToUpdateCapabilities" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="NO"/>
+        <attribute name="needsToUploadMLSPublicKeys" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO"/>
+        <attribute name="numberOfKeysRemaining" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="preKeysRangeMax" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="NO"/>
+        <attribute name="remoteIdentifier" optional="YES" attributeType="String"/>
+        <attribute name="type" attributeType="String" defaultValueString="permanent"/>
+        <relationship name="addedOrRemovedInSystemMessages" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="SystemMessage" inverseName="clients" inverseEntity="SystemMessage"/>
+        <relationship name="discoveredByMessage" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Message" inverseName="discoveredClients" inverseEntity="Message"/>
+        <relationship name="ignoredByClients" toMany="YES" deletionRule="Nullify" destinationEntity="UserClient" inverseName="ignoredClients" inverseEntity="UserClient"/>
+        <relationship name="ignoredClients" toMany="YES" deletionRule="Nullify" destinationEntity="UserClient" inverseName="ignoredByClients" inverseEntity="UserClient"/>
+        <relationship name="messagesMissingRecipient" optional="YES" transient="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Message" inverseName="missingRecipients" inverseEntity="Message"/>
+        <relationship name="missedByClient" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserClient" inverseName="missingClients" inverseEntity="UserClient"/>
+        <relationship name="missingClients" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="UserClient" inverseName="missedByClient" inverseEntity="UserClient"/>
+        <relationship name="trustedByClients" toMany="YES" deletionRule="Nullify" destinationEntity="UserClient" inverseName="trustedClients" inverseEntity="UserClient"/>
+        <relationship name="trustedClients" toMany="YES" deletionRule="Nullify" destinationEntity="UserClient" inverseName="trustedByClients" inverseEntity="UserClient"/>
+        <relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="clients" inverseEntity="User"/>
+        <uniquenessConstraints>
+            <uniquenessConstraint>
+                <constraint value="remoteIdentifier"/>
+            </uniquenessConstraint>
+        </uniquenessConstraints>
+    </entity>
+</model>
\ No newline at end of file
diff --git a/wire-ios-data-model/Source/ManagedObjectContext/Migration/CoreDataMessagingMigrationVersion.swift b/wire-ios-data-model/Source/ManagedObjectContext/Migration/CoreDataMessagingMigrationVersion.swift
index 8de3b2c3c8c..af26f1caf9f 100644
--- a/wire-ios-data-model/Source/ManagedObjectContext/Migration/CoreDataMessagingMigrationVersion.swift
+++ b/wire-ios-data-model/Source/ManagedObjectContext/Migration/CoreDataMessagingMigrationVersion.swift
@@ -29,6 +29,7 @@ enum CoreDataMessagingMigrationVersion: String, CoreDataMigrationVersion {
     // MARK: -
 
     // Note: add new versions here in first position!
+    case v125 = "zmessaging2.125.0"
     case v124 = "zmessaging2.124.0"
     case v123 = "zmessaging2.123.0"
     case v122 = "zmessaging2.122.0"
@@ -77,14 +78,10 @@ enum CoreDataMessagingMigrationVersion: String, CoreDataMigrationVersion {
 
     var nextVersion: Self? {
         switch self {
-        case .v124:
+        case .v125:
             nil
-        case .v123:
-            .v124
-        case .v122:
-            .v123
-        case .v120, .v121:
-            .v122
+        case .v120, .v121, .v122, .v123, .v124:
+            .v125
         case .v119:
             .v120 // destination version runs custom migration actions
         case .v116, .v117, .v118:
diff --git a/wire-ios-data-model/Source/Model/UserClient/UserClient.swift b/wire-ios-data-model/Source/Model/UserClient/UserClient.swift
index 127ceb4aef1..0ff45d226db 100644
--- a/wire-ios-data-model/Source/Model/UserClient/UserClient.swift
+++ b/wire-ios-data-model/Source/Model/UserClient/UserClient.swift
@@ -65,6 +65,9 @@ public class UserClient: ZMManagedObject, UserClientType {
     @NSManaged public var needsSessionMigration: Bool
     @NSManaged public var discoveredByMessage: ZMOTRMessage?
 
+    /// Client has the capability to use new asyncStream (v8+) aka `consumable-notifications`
+    @NSManaged public var asyncStreamCapable: Bool
+
     /// Clients that are trusted by self client.
     @NSManaged public var trustedClients: Set<UserClient>
 
@@ -421,6 +424,9 @@ public extension UserClient {
         client.activationDate = activationDate
         client.lastActiveDate = lastActiveDate
         client.remoteIdentifier = id
+        if let capabilities = payloadAsDictionary.optionalArray(forKey: "capabilities") as? [String] {
+            client.asyncStreamCapable = capabilities.contains("consumable-notifications")
+        }
 
         let selfUser = ZMUser.selfUser(in: context)
         client.user = client.user ?? selfUser
diff --git a/wire-ios-data-model/Tests/Resources/store2-125-0.wiredatabase b/wire-ios-data-model/Tests/Resources/store2-125-0.wiredatabase
new file mode 100644
index 00000000000..cb2c497c180
Binary files /dev/null and b/wire-ios-data-model/Tests/Resources/store2-125-0.wiredatabase differ
diff --git a/wire-ios-data-model/Tests/Source/Model/UserClient/UserClientTests.swift b/wire-ios-data-model/Tests/Source/Model/UserClient/UserClientTests.swift
index 0b396b0a5e1..17767d745bc 100644
--- a/wire-ios-data-model/Tests/Source/Model/UserClient/UserClientTests.swift
+++ b/wire-ios-data-model/Tests/Source/Model/UserClient/UserClientTests.swift
@@ -595,6 +595,29 @@ extension UserClientTests {
         XCTAssertEqual(newClient.user, ZMUser.selfUser(in: uiMOC))
         XCTAssertNotNil(newClient.sessionIdentifier)
         XCTAssertEqual(newClient.mlsPublicKeys.ed25519, "some key")
+        XCTAssertEqual(newClient.asyncStreamCapable, false)
+    }
+
+    func test_createOrUpdateSelfUserClient_WithAsyncStreamCapableTrue() {
+        // given
+        _ = createSelfClient()
+        let newClientPayload: [String: AnyObject] = [
+            "id": UUID().transportString() as AnyObject,
+            "type": "permanent" as AnyObject,
+            "time": Date().transportString() as AnyObject,
+            "capabilities": ["consumable-notifications"] as AnyObject
+        ]
+
+        // when
+        var newClient: UserClient!
+        performPretendingUiMocIsSyncMoc {
+            newClient = UserClient.createOrUpdateSelfUserClient(newClientPayload, context: self.uiMOC)
+            XCTAssert(self.waitForAllGroupsToBeEmpty(withTimeout: 0.5))
+        }
+
+        // then
+        XCTAssertNotNil(newClient)
+        XCTAssertTrue(newClient.asyncStreamCapable)
     }
 
     func testThatItSetsTheUserWhenInsertingANewSelfUserClient_NoExistingSelfClient() {
diff --git a/wire-ios-data-model/WireDataModel.xcodeproj/project.pbxproj b/wire-ios-data-model/WireDataModel.xcodeproj/project.pbxproj
index c6102f012ba..da1f0d6c068 100644
--- a/wire-ios-data-model/WireDataModel.xcodeproj/project.pbxproj
+++ b/wire-ios-data-model/WireDataModel.xcodeproj/project.pbxproj
@@ -17,6 +17,7 @@
 		013887AB2B9A5C8B00323DD0 /* CoreDataMigrationActionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 013887A92B9A5C8B00323DD0 /* CoreDataMigrationActionFactory.swift */; };
 		013887AC2B9A5C8B00323DD0 /* CoreDataMigrationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 013887AA2B9A5C8B00323DD0 /* CoreDataMigrationAction.swift */; };
 		013887AE2B9A5CA800323DD0 /* CoreDataMigrationActionFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 013887AD2B9A5CA800323DD0 /* CoreDataMigrationActionFactoryTests.swift */; };
+		01423BC92DB013DF00C49567 /* store2-125-0.wiredatabase in Resources */ = {isa = PBXBuildFile; fileRef = 01423BC82DB013DF00C49567 /* store2-125-0.wiredatabase */; };
 		01482E8A2B10ED0800F3B2CB /* MLSSubgroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63F0781129F6C59D0031E19D /* MLSSubgroup.swift */; };
 		01482E8B2B10EEB000F3B2CB /* FetchSubgroupAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63F0780C29F292770031E19D /* FetchSubgroupAction.swift */; };
 		014DD8D42B6D1FF6007ECFD1 /* UUID+SafeLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014DD8D22B6D1FE9007ECFD1 /* UUID+SafeLogging.swift */; };
@@ -926,6 +927,8 @@
 		013887A92B9A5C8B00323DD0 /* CoreDataMigrationActionFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataMigrationActionFactory.swift; sourceTree = "<group>"; };
 		013887AA2B9A5C8B00323DD0 /* CoreDataMigrationAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataMigrationAction.swift; sourceTree = "<group>"; };
 		013887AD2B9A5CA800323DD0 /* CoreDataMigrationActionFactoryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataMigrationActionFactoryTests.swift; sourceTree = "<group>"; };
+		01423BC72DB00D8200C49567 /* zmessaging2.125.0.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = zmessaging2.125.0.xcdatamodel; sourceTree = "<group>"; };
+		01423BC82DB013DF00C49567 /* store2-125-0.wiredatabase */ = {isa = PBXFileReference; lastKnownFileType = file; path = "store2-125-0.wiredatabase"; sourceTree = "<group>"; };
 		014DD8D22B6D1FE9007ECFD1 /* UUID+SafeLogging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UUID+SafeLogging.swift"; sourceTree = "<group>"; };
 		0153CA842B8540F0000000CA /* zmessaging2.114.0.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = zmessaging2.114.0.xcdatamodel; sourceTree = "<group>"; };
 		0153CA8F2B855456000000CA /* store2-113-0.wiredatabase */ = {isa = PBXFileReference; lastKnownFileType = file; path = "store2-113-0.wiredatabase"; sourceTree = "<group>"; };
@@ -3686,6 +3689,7 @@
 		F9C9A83D1CAEDBC40039E10C /* Resources */ = {
 			isa = PBXGroup;
 			children = (
+				01423BC82DB013DF00C49567 /* store2-125-0.wiredatabase */,
 				01A4709F2DAD6F0C00B1B2E7 /* store2-124-0.wiredatabase */,
 				CB417B072D9FDA2B00F0701B /* store2-123-0.wiredatabase */,
 				0176BDD02D515764002C33DE /* store2-122-0.wiredatabase */,
@@ -4019,6 +4023,7 @@
 				EE6CB3DC24E2A4E500B0EADD /* store2-83-0.wiredatabase in Resources */,
 				166EC36E26C50E960043ED01 /* store2-94-0.wiredatabase in Resources */,
 				F91EAAC61D885D7B0010ACBE /* video.mp4 in Resources */,
+				01423BC92DB013DF00C49567 /* store2-125-0.wiredatabase in Resources */,
 				16F7341624F95F9D00AB93B1 /* store2-85-0.wiredatabase in Resources */,
 				F9A706F71CAEE31C00C2F5FE /* animated.gif in Resources */,
 				63340BBD241C2BC5004ED87C /* store2-80-0.wiredatabase in Resources */,
@@ -5268,6 +5273,7 @@
 		E6F443042B16294000D2B08A /* zmessaging.xcdatamodeld */ = {
 			isa = XCVersionGroup;
 			children = (
+				01423BC72DB00D8200C49567 /* zmessaging2.125.0.xcdatamodel */,
 				01A4709E2DAD4A1D00B1B2E7 /* zmessaging2.124.0.xcdatamodel */,
 				CBED6B422D91A3FC00328BED /* zmessaging2.123.0.xcdatamodel */,
 				0176BDCF2D515224002C33DE /* zmessaging2.122.0.xcdatamodel */,
@@ -5314,7 +5320,7 @@
 				E6F443102B16294000D2B08A /* zmessaging2.81.0.xcdatamodel */,
 				E6F443182B16294000D2B08A /* zmessaging2.80.0.xcdatamodel */,
 			);
-			currentVersion = 01A4709E2DAD4A1D00B1B2E7 /* zmessaging2.124.0.xcdatamodel */;
+			currentVersion = 01423BC72DB00D8200C49567 /* zmessaging2.125.0.xcdatamodel */;
 			path = zmessaging.xcdatamodeld;
 			sourceTree = "<group>";
 			versionGroupType = wrapper.xcdatamodel;
diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientRequestFactory.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientRequestFactory.swift
index 5592ba198a3..0a243886c00 100644
--- a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientRequestFactory.swift
+++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientRequestFactory.swift
@@ -47,6 +47,11 @@ extension UserClientRequestFactory {
         let preKeysPayloadData = payloadForPreKeys(prekeys)
         let lastPreKeyPayloadData = payloadForLastPreKey(lastRestortPrekey)
 
+        var capabilities = ["legalhold-implicit-consent"]
+        if DeveloperFlag.asyncStreamNotifications.isOn, apiVersion >= .v8 {
+            capabilities.append("consumable-notifications")
+        }
+
         var payload: [String: Any] = [
             "type": client.type.rawValue,
             "label": client.label ?? "",
@@ -54,7 +59,7 @@ extension UserClientRequestFactory {
             "class": (client.deviceClass?.rawValue ?? DeviceClass.phone.rawValue),
             "lastkey": lastPreKeyPayloadData,
             "prekeys": preKeysPayloadData,
-
+            "capabilities": capabilities,
             "cookie": cookieLabel
         ]
 
@@ -122,6 +127,7 @@ extension UserClientRequestFactory {
         }
 
         let preKeysPayloadData = payloadForPreKeys(prekeys)
+
         let payload: [String: Any] = [
             "prekeys": preKeysPayloadData
         ]
@@ -169,9 +175,17 @@ extension UserClientRequestFactory {
         guard let remoteIdentifier = client.remoteIdentifier else {
             throw UserClientRequestError.clientNotRegistered
         }
-        let payload: [String: Any] = [
-            "capabilities": ["legalhold-implicit-consent"]
+        // TODO: [WPB-17223] recheck this when this should be triggered `WireDataModel.UserClient.triggerSelfClientCapabilityUpdate(syncContext)`
+
+        var capabilities = ["legalhold-implicit-consent"]
+        if DeveloperFlag.asyncStreamNotifications.isOn, apiVersion >= .v8 {
+            capabilities.append("consumable-notifications")
+        }
+
+        var payload: [String: Any] = [
+            "capabilities": capabilities
         ]
+
         let request = ZMTransportRequest(
             path: "/clients/\(remoteIdentifier)",
             method: .put,
diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift
index 8b0e2085f44..1dc196d6c47 100644
--- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift
+++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift
@@ -552,12 +552,12 @@ public final class ZMUserSession: NSObject {
 
             // Create and perform sync if there is a self client.
             if let selfClientID = selfUserClient.remoteIdentifier {
-                setUpSyncAgent(clientID: selfClientID)
+                setUpSyncAgent(clientID: selfClientID, asyncStreamEnabled: selfUserClient.asyncStreamCapable)
             }
         }
     }
 
-    private func setUpSyncAgent(clientID: String) {
+    private func setUpSyncAgent(clientID: String, asyncStreamEnabled: Bool) {
         let onSelfClientInvalidated: () async -> Void = { [self] in
             await syncContext.perform { [self] in
                 syncContext.tearDownCryptoStack()
@@ -585,15 +585,24 @@ public final class ZMUserSession: NSObject {
             onSelfClientInvalidated: onSelfClientInvalidated
         )
 
+        let incrementalSyncProvider: IncrementalSyncProvider = if !asyncStreamEnabled {
+            clientSessionComponent
+        } else {
+            // TODO: [WPB-17225] replace syncProvider here
+            clientSessionComponent
+        }
+
         let syncAgent = SyncAgent(
             lastUpdateEventIDRepository: lastEventIDRepository,
             initialSyncProvider: clientSessionComponent,
-            incrementalSyncProvider: clientSessionComponent,
+            incrementalSyncProvider: incrementalSyncProvider,
             legacySyncStatus: applicationStatusDirectory.syncStatus
         )
         applicationStatusDirectory.syncStatus.syncStateDelegate = syncAgent
         self.syncAgent = syncAgent
         syncAgent.delegate = self
+
+        // TODO: [WPB-17223] remove `resume` call from here
         syncAgent.resume()
     }
 
@@ -1307,8 +1316,6 @@ extension ZMUserSession: ZMClientRegistrationStatusDelegate {
         registerCurrentPushToken()
         renewAccessTokenIfNeeded(for: userClient)
 
-        WireDataModel.UserClient.triggerSelfClientCapabilityUpdate(syncContext)
-
         managedObjectContext.performGroupedBlock { [weak self] in
             guard
                 let context = self?.managedObjectContext,
@@ -1326,7 +1333,7 @@ extension ZMUserSession: ZMClientRegistrationStatusDelegate {
         // The client was just registered and still needs to perform the
         // initial sync.
         if let selfClientID = userClient.remoteIdentifier {
-            setUpSyncAgent(clientID: selfClientID)
+            setUpSyncAgent(clientID: selfClientID, asyncStreamEnabled: userClient.asyncStreamCapable)
         }
     }
 
diff --git a/wire-ios-sync-engine/Tests/Source/E2EE/UserClientRequestFactoryTests.swift b/wire-ios-sync-engine/Tests/Source/E2EE/UserClientRequestFactoryTests.swift
index 55d399304b5..15d51b7a9ca 100644
--- a/wire-ios-sync-engine/Tests/Source/E2EE/UserClientRequestFactoryTests.swift
+++ b/wire-ios-sync-engine/Tests/Source/E2EE/UserClientRequestFactoryTests.swift
@@ -65,6 +65,29 @@ final class UserClientRequestFactoryTests: MessagingTest {
 
     // MARK: - Registration request creation
 
+    func testThatItCreatesRegistrationRequestWithConsumableNotificationsCapabitilityCorrectly() throws {
+        // GIVEN
+        let credentials = UserEmailCredentials(email: "some@example.com", password: "123")
+        DeveloperFlag.asyncStreamNotifications.enable(true, storage: .temporary())
+
+        try testThatItCreatesRegistrationRequestCorrectly(
+            credentials: credentials,
+            usingProteusService: true,
+            apiVersion: .v8
+        )
+    }
+
+    func testThatItCreatesRegistrationRequestWithLegalholdCapabitilityCorrectly() throws {
+        // GIVEN
+        let credentials = UserEmailCredentials(email: "some@example.com", password: "123")
+
+        try testThatItCreatesRegistrationRequestCorrectly(
+            credentials: credentials,
+            usingProteusService: true,
+            apiVersion: .v0
+        )
+    }
+
     func testThatItCreatesRegistrationRequestWithEmailCorrectly() throws {
         let credentials = UserEmailCredentials(email: "some@example.com", password: "123")
 
@@ -111,7 +134,8 @@ final class UserClientRequestFactoryTests: MessagingTest {
 
     private func testThatItCreatesRegistrationRequestCorrectly(
         credentials: UserEmailCredentials?,
-        usingProteusService: Bool
+        usingProteusService: Bool,
+        apiVersion: APIVersion = .v0
     ) throws {
         let request = try syncMOC.performAndWait {
             // given
@@ -126,14 +150,18 @@ final class UserClientRequestFactoryTests: MessagingTest {
                 cookieLabel: "mycookie",
                 prekeys: prekeys,
                 lastRestortPrekey: lastRestortPrekey,
-                apiVersion: .v0
+                apiVersion: apiVersion
             )
 
         }
 
         // then
         let transportRequest = try XCTUnwrap(request.transportRequest)
-        assertRequest(transportRequest, path: "/clients", method: .post)
+        if apiVersion >= .v8 {
+            assertRequest(transportRequest, path: "/v8/clients", method: .post)
+        } else {
+            assertRequest(transportRequest, path: "/clients", method: .post)
+        }
 
         let payload = try XCTUnwrap(payload(from: transportRequest))
 
@@ -146,6 +174,12 @@ final class UserClientRequestFactoryTests: MessagingTest {
         if let emailVerificationCode = credentials?.emailVerificationCode {
             XCTAssertEqual(payload.verificationCode, emailVerificationCode)
         }
+
+        if apiVersion >= .v8, DeveloperFlag.asyncStreamNotifications.isOn {
+            XCTAssertEqual(payload.capabilities, ["legalhold-implicit-consent", "consumable-notifications"])
+        } else {
+            XCTAssertEqual(payload.capabilities, ["legalhold-implicit-consent"])
+        }
     }
 
     func testThatItReturnsNilForRegisterClientRequest_PrekeyError() {
@@ -364,6 +398,11 @@ private extension [String: Any] {
         case mackey
         case mlsPublicKeys = "mls_public_keys"
         case ed25519
+        case capabilities
+    }
+
+    var capabilities: [String]? {
+        value(forKey: .capabilities)
     }
 
     var type: String? {
diff --git a/wire-ios-utilities/Source/DeveloperFlag.swift b/wire-ios-utilities/Source/DeveloperFlag.swift
index bcafa65f6a1..f3c99ffaa62 100644
--- a/wire-ios-utilities/Source/DeveloperFlag.swift
+++ b/wire-ios-utilities/Source/DeveloperFlag.swift
@@ -33,6 +33,7 @@ public enum DeveloperFlag: String, CaseIterable {
     case newInitialSync
     case useWireAuthentication
     case wireCellsAttachmentsPreviews
+    case asyncStreamNotifications
 
     public var description: String {
         switch self {
@@ -68,6 +69,9 @@ public enum DeveloperFlag: String, CaseIterable {
 
         case .wireCellsAttachmentsPreviews:
             "Use the new WireCells previews for conversations attachments"
+
+        case .asyncStreamNotifications:
+            "Turn on to enable new sync with consumable notifications"
         }
     }
 
diff --git a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeveloperToolsViewModel.swift b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeveloperToolsViewModel.swift
index ce37e427281..40923fb9005 100644
--- a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeveloperToolsViewModel.swift
+++ b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeveloperToolsViewModel.swift
@@ -176,8 +176,11 @@ final class DeveloperToolsViewModel: ObservableObject {
                         title: "MLS public key",
                         value: selfClient?.mlsPublicKeys.allKeys.first?.uppercased() ?? "None"
                     )),
-                    .text(TextItem(title: "1-1 MLS Conversations", value: oneOnOneMLSConversationsCount()))
-
+                    .text(TextItem(title: "1-1 MLS Conversations", value: oneOnOneMLSConversationsCount())),
+                    .text(TextItem(
+                        title: "Async Stream Enabled",
+                        value: selfClient?.asyncStreamCapable == true ? "Yes" : "No"
+                    ))
                 ]
             ))
         }


Reviews:
user: login: jullianm
body: LGTM 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T07:34:34Z
user: login: johnxnguyen
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-18T14:43:16Z
user: login: netbe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T14:50:12Z
user: login: johnxnguyen
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T14:55:58Z
user: login: netbe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T14:59:00Z

Review comments:
diff_hunk: @@ -4,6 +4,10 @@ As it is hard to spot changes from version to version of database models (.xcdat
 
 ## zmessaging
 
+### 2.125.0
+
+* added `asyncStreamCapable attribute on the UserClient entity
path: wire-ios-data-model/Resources/Databases Changelog.md
user: login: jullianm
body: ```suggestion
* added `asyncStreamCapable` attribute on the UserClient entity
```
created_at: 2025-04-18T07:28:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -421,6 +424,10 @@ public extension UserClient {
         client.activationDate = activationDate
         client.lastActiveDate = lastActiveDate
         client.remoteIdentifier = id
+        if let capabilities = payloadAsDictionary.optionalArray(forKey: "capabilities") as? [String],
+           capabilities.contains("consumable-notifications") {
+            client.asyncStreamCapable = true
path: wire-ios-data-model/Source/Model/UserClient/UserClient.swift
user: login: jullianm
body: ```suggestion
            client.asyncStreamCapable = capabilities.contains("consumable-notifications")
```
created_at: 2025-04-18T07:30:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -421,6 +424,10 @@ public extension UserClient {
         client.activationDate = activationDate
         client.lastActiveDate = lastActiveDate
         client.remoteIdentifier = id
+        if let capabilities = payloadAsDictionary.optionalArray(forKey: "capabilities") as? [String],
path: wire-ios-data-model/Source/Model/UserClient/UserClient.swift
user: login: johnxnguyen
body: question: does this also need to be added to `UseClientLocalStore` when we update a client?
created_at: 2025-04-18T14:43:11Z
author_association: COLLABORATOR
diff_hunk: @@ -421,6 +424,10 @@ public extension UserClient {
         client.activationDate = activationDate
         client.lastActiveDate = lastActiveDate
         client.remoteIdentifier = id
+        if let capabilities = payloadAsDictionary.optionalArray(forKey: "capabilities") as? [String],
path: wire-ios-data-model/Source/Model/UserClient/UserClient.swift
user: login: netbe
body: I updated here https://github.com/wireapp/wire-ios/pull/2913/files#diff-f50c0403b3bff1afe8cce7f24ecc20342a242724f7cb2bdaae4d6f5a2223bb41R134, did you mean another place?
created_at: 2025-04-18T14:50:12Z
author_association: COLLABORATOR
diff_hunk: @@ -24,5 +24,6 @@ public enum UserClientCapability: String, Codable, Sendable {
     /// (directly or indirectly).
 
     case legalholdConsent = "legalhold-implicit-consent"
+    case consumableNotifications = "consumable-notifications"
path: WireAPI/Sources/WireAPI/Models/UserClient/UserClientCapability.swift
user: login: johnxnguyen
body: suggestion: add a doc comment describing the new capability.
created_at: 2025-04-18T14:55:52Z
author_association: COLLABORATOR
diff_hunk: @@ -421,6 +424,10 @@ public extension UserClient {
         client.activationDate = activationDate
         client.lastActiveDate = lastActiveDate
         client.remoteIdentifier = id
+        if let capabilities = payloadAsDictionary.optionalArray(forKey: "capabilities") as? [String],
+           capabilities.contains("consumable-notifications") {
+            client.asyncStreamCapable = true
path: wire-ios-data-model/Source/Model/UserClient/UserClient.swift
user: login: netbe
body: done in [8fe5131287bfac23d3140df253852d33b430ec3a](https://github.com/wireapp/wire-ios/pull/2913/commits/8fe5131287bfac23d3140df253852d33b430ec3a)
created_at: 2025-04-18T14:59:00Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-17T07:40:11Z
body: ## Test Results
    7 files    863 suites   8m 47s ⏱️
6 374 tests 6 347 ✅ 27 💤 0 ❌
6 395 runs  6 368 ✅ 27 💤 0 ❌

Results for commit 4df0e6ea.

[test-results]:data:application/gzip;base64,H4sIAFtwAmgC/02MQQ6DIBQFr2JYdyEKCL2MQT4/IVVpEFamdy9SKy5nXt7sBN1sN/JshkdDtuRiASn6jJCCjs6vWfDu2PMYj1n0A/vjuCVjimNDdS/3zq6eRtRuzqa9hA3Bh9OEtP6yip9Uq0Je6hYt4tYsfE8avywuZiAMsLXCauCUMeyoQksn4Ib2QqHCSYIFAKPI5wvBixu8DQEAAA==

:recycle: This comment has been updated with latest results.
author_association: CONTRIBUTOR
user: login: datadog-wireapp[bot]
created_at: 2025-04-17T07:42:08Z
body: 
## Datadog Report

Branch report: [`feat/update-capabilities-WPB-17092`](https://app.datadoghq.eu/ci/test-branch/github.com%2Fwireapp%2Fwire-ios/wire-ios-mono/feat%2Fupdate-capabilities-WPB-17092?env=ci&index=citest&_user_click_origin_=github)
Commit report: [`4ff264b`](https://app.datadoghq.eu/ci/test-commit/github.com%2Fwireapp%2Fwire-ios/wire-ios-mono/feat%2Fupdate-capabilities-WPB-17092/4ff264b003fbdbae3be2b32cfb9f5d84d6243447?env=ci&index=citest&_user_click_origin_=github)
Test service: `wire-ios-mono`

:white_check_mark: 0 Failed, 6368 Passed, 27 Skipped, 4m 0.86s Total Time




author_association: NONE