Skip to content

Commit cc41a91

Browse files
authored
fix(debug): disambiguate crypto stats drift semantics (#4144)
Expose self-membership, focused 1:1 display names, and CoreCrypto lookup failures separately so debug consumers can distinguish real MLS drift from left conversations or transient lookup errors.
1 parent 96241b8 commit cc41a91

8 files changed

Lines changed: 404 additions & 48 deletions

File tree

data/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,24 @@ WHERE qualified_id = :qualified_id;
244244
selectAllConversations:
245245
SELECT * FROM Conversation WHERE type IS NOT 'CONNECTION_PENDING' AND deleted_locally = 0 ORDER BY last_modified_date DESC, name ASC;
246246

247+
selectAllConversationsWithOtherUserName:
248+
SELECT
249+
Conversation.*,
250+
User.name AS otherUserName,
251+
EXISTS (
252+
SELECT 1 FROM Member
253+
WHERE Member.conversation = Conversation.qualified_id
254+
AND Member.user = :selfUserId
255+
) AS selfIsMember
256+
FROM Conversation
257+
LEFT JOIN Member AS OtherMember ON Conversation.qualified_id = OtherMember.conversation
258+
AND Conversation.type IS 'ONE_ON_ONE'
259+
AND OtherMember.user IS NOT :selfUserId
260+
LEFT JOIN User ON User.qualified_id = OtherMember.user
261+
WHERE Conversation.type IS NOT 'CONNECTION_PENDING'
262+
AND Conversation.deleted_locally = 0
263+
ORDER BY Conversation.last_modified_date DESC, Conversation.name ASC;
264+
247265
selectAllTeamProteusConversationsReadyForMigration:
248266
SELECT
249267
qualified_id,

data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ interface ConversationDAO {
7373
)
7474
suspend fun updateAllConversationsNotificationDate()
7575
suspend fun getAllConversations(): Flow<List<ConversationEntity>>
76+
suspend fun getAllConversationsWithOtherUserName(selfUserId: QualifiedIDEntity): Flow<List<ConversationWithOtherUserNameEntity>>
7677
suspend fun getAllConversationDetails(
7778
fromArchive: Boolean,
7879
filter: ConversationFilterEntity,
@@ -194,6 +195,12 @@ data class NameAndHandleEntity(
194195
val handle: String?
195196
)
196197

198+
data class ConversationWithOtherUserNameEntity(
199+
val conversation: ConversationEntity,
200+
val otherUserName: String?,
201+
val selfIsMember: Boolean,
202+
)
203+
197204
data class EpochChangesDataEntity(
198205
val conversationId: QualifiedIDEntity,
199206
val mlsVerificationStatus: ConversationEntity.VerificationStatus,

data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,16 @@ internal class ConversationDAOImpl internal constructor(
321321
.flowOn(readDispatcher.value)
322322
}
323323

324+
override suspend fun getAllConversationsWithOtherUserName(
325+
selfUserId: QualifiedIDEntity
326+
): Flow<List<ConversationWithOtherUserNameEntity>> {
327+
return conversationQueries
328+
.selectAllConversationsWithOtherUserName(selfUserId, conversationMapper::toConversationWithOtherUserNameEntity)
329+
.asFlow()
330+
.mapToList()
331+
.flowOn(readDispatcher.value)
332+
}
333+
324334
override suspend fun getAllConversationDetails(
325335
fromArchive: Boolean,
326336
filter: ConversationFilterEntity,

data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationMapper.kt

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,93 @@ data object ConversationMapper {
295295
historySharingRetentionSeconds = history_sharing_retention_seconds
296296
)
297297

298+
@Suppress("LongParameterList", "FunctionParameterNaming")
299+
fun toConversationWithOtherUserNameEntity(
300+
qualified_id: QualifiedIDEntity,
301+
name: String?,
302+
type: ConversationEntity.Type,
303+
team_id: String?,
304+
mls_group_id: String?,
305+
mls_group_state: ConversationEntity.GroupState,
306+
mls_epoch: Long,
307+
mls_proposal_timer: String?,
308+
protocol: ConversationEntity.Protocol,
309+
muted_status: ConversationEntity.MutedStatus,
310+
muted_time: Long,
311+
creator_id: String,
312+
last_modified_date: Instant,
313+
last_notified_date: Instant?,
314+
last_read_date: Instant,
315+
access_list: List<ConversationEntity.Access>,
316+
access_role_list: List<ConversationEntity.AccessRole>,
317+
mls_last_keying_material_update_date: Instant,
318+
mls_cipher_suite: ConversationEntity.CipherSuite,
319+
receipt_mode: ConversationEntity.ReceiptMode,
320+
guest_room_link: String?,
321+
message_timer: Long?,
322+
user_message_timer: Long?,
323+
incomplete_metadata: Boolean,
324+
mls_degraded_notified: Boolean,
325+
is_guest_password_protected: Boolean,
326+
archived: Boolean,
327+
archived_date_time: Instant?,
328+
verification_status: ConversationEntity.VerificationStatus,
329+
proteus_verification_status: ConversationEntity.VerificationStatus,
330+
degraded_conversation_notified: Boolean,
331+
legal_hold_status: ConversationEntity.LegalHoldStatus,
332+
is_channel: Boolean,
333+
channel_access: ConversationEntity.ChannelAccess?,
334+
channel_add_permission: ConversationEntity.ChannelAddPermission?,
335+
wireCell: String?,
336+
deleted_locally: Boolean,
337+
history_sharing_retention_seconds: Long,
338+
otherUserName: String?,
339+
selfIsMember: Boolean,
340+
) = ConversationWithOtherUserNameEntity(
341+
conversation = toConversationEntity(
342+
qualified_id = qualified_id,
343+
name = name,
344+
type = type,
345+
team_id = team_id,
346+
mls_group_id = mls_group_id,
347+
mls_group_state = mls_group_state,
348+
mls_epoch = mls_epoch,
349+
mls_proposal_timer = mls_proposal_timer,
350+
protocol = protocol,
351+
muted_status = muted_status,
352+
muted_time = muted_time,
353+
creator_id = creator_id,
354+
last_modified_date = last_modified_date,
355+
last_notified_date = last_notified_date,
356+
last_read_date = last_read_date,
357+
access_list = access_list,
358+
access_role_list = access_role_list,
359+
mls_last_keying_material_update_date = mls_last_keying_material_update_date,
360+
mls_cipher_suite = mls_cipher_suite,
361+
receipt_mode = receipt_mode,
362+
guest_room_link = guest_room_link,
363+
message_timer = message_timer,
364+
user_message_timer = user_message_timer,
365+
incomplete_metadata = incomplete_metadata,
366+
mls_degraded_notified = mls_degraded_notified,
367+
is_guest_password_protected = is_guest_password_protected,
368+
archived = archived,
369+
archived_date_time = archived_date_time,
370+
verification_status = verification_status,
371+
proteus_verification_status = proteus_verification_status,
372+
degraded_conversation_notified = degraded_conversation_notified,
373+
legal_hold_status = legal_hold_status,
374+
is_channel = is_channel,
375+
channel_access = channel_access,
376+
channel_add_permission = channel_add_permission,
377+
wireCell = wireCell,
378+
deleted_locally = deleted_locally,
379+
history_sharing_retention_seconds = history_sharing_retention_seconds,
380+
),
381+
otherUserName = otherUserName,
382+
selfIsMember = selfIsMember,
383+
)
384+
298385
@Suppress("LongParameterList")
299386
fun mapProtocolInfo(
300387
protocol: ConversationEntity.Protocol,

data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,33 @@ class ConversationDAOTest : BaseDatabaseTest() {
11451145
}
11461146
}
11471147

1148+
@Test
1149+
fun givenOneOnOneConversationWithOtherUserAndSelfMember_whenGettingAllWithOtherUserName_thenReturnsNameAndMembership() = runTest {
1150+
val conversation = conversationEntity1.copy(name = null)
1151+
conversationDAO.insertConversation(conversation)
1152+
userDAO.upsertUser(user1.copy(name = "Other User", activeOneOnOneConversationId = conversation.id))
1153+
memberDAO.insertMember(MemberEntity(user1.id, MemberEntity.Role.Member), conversation.id)
1154+
insertSelfMember(conversation.id)
1155+
1156+
val result = conversationDAO.getAllConversationsWithOtherUserName(selfUserId).first()
1157+
1158+
assertEquals(1, result.size)
1159+
assertEquals(conversation.id, result.first().conversation.id)
1160+
assertEquals("Other User", result.first().otherUserName)
1161+
assertEquals(true, result.first().selfIsMember)
1162+
}
1163+
1164+
@Test
1165+
fun givenConversationWithoutSelfMember_whenGettingAllWithOtherUserName_thenReturnsFalseMembership() = runTest {
1166+
conversationDAO.insertConversation(conversationEntity4)
1167+
1168+
val result = conversationDAO.getAllConversationsWithOtherUserName(selfUserId).first()
1169+
1170+
assertEquals(1, result.size)
1171+
assertEquals(conversationEntity4.id, result.first().conversation.id)
1172+
assertEquals(false, result.first().selfIsMember)
1173+
}
1174+
11481175
@Test
11491176
fun givenConnectionRequestAndUserWithName_whenSelectingAllConversationDetails_thenShouldReturnConnectionRequest() = runTest {
11501177
val fromArchive = false

logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ internal interface ConversationRepository {
115115
): Either<CoreFailure, Unit>
116116

117117
suspend fun getConversationList(): Either<StorageFailure, Flow<List<Conversation>>>
118+
suspend fun getConversationListWithOtherUserName(): Either<StorageFailure, Flow<List<ConversationWithOtherUserName>>>
118119
suspend fun observeConversationList(): Flow<List<Conversation>>
119120
suspend fun observeConversationListDetails(
120121
fromArchive: Boolean,
@@ -364,6 +365,12 @@ internal interface ConversationRepository {
364365
suspend fun getMLSConversationsByDomain(domain: String): Either<CoreFailure, List<Conversation>>
365366
}
366367

368+
internal data class ConversationWithOtherUserName(
369+
val conversation: Conversation,
370+
val otherUserName: String?,
371+
val selfIsMember: Boolean,
372+
)
373+
367374
@OptIn(ConversationPersistenceApi::class)
368375
@Suppress("LongParameterList", "TooManyFunctions", "LargeClass")
369376
internal class ConversationDataSource internal constructor(
@@ -491,6 +498,19 @@ internal class ConversationDataSource internal constructor(
491498
observeConversationList()
492499
}
493500

501+
override suspend fun getConversationListWithOtherUserName(): Either<StorageFailure, Flow<List<ConversationWithOtherUserName>>> =
502+
wrapStorageRequest {
503+
conversationDAO.getAllConversationsWithOtherUserName(selfUserId.toDao()).map { conversations ->
504+
conversations.map { conversationWithOtherUserName ->
505+
ConversationWithOtherUserName(
506+
conversation = conversationMapper.fromDaoModel(conversationWithOtherUserName.conversation),
507+
otherUserName = conversationWithOtherUserName.otherUserName,
508+
selfIsMember = conversationWithOtherUserName.selfIsMember,
509+
)
510+
}
511+
}
512+
}
513+
494514
override suspend fun observeConversationList(): Flow<List<Conversation>> {
495515
return conversationDAO.getAllConversations().map { it.map(conversationMapper::fromDaoModel) }
496516
}

0 commit comments

Comments
 (0)