diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 1f4bf0d612..00f975cb30 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -2957,6 +2957,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = conn' <- updatePeerChatVRange activeConn chatVRange case chatMsgEvent of XInfo p -> do + -- Consume the transient simplex_name carrier from the connection row + -- (set in createConnection_ on the connect-via-plan path) and pass it + -- to createDirectContact; after this point contacts.simplex_name is + -- the source of truth. let Connection {simplexName} = conn' Profile {simplexName = pSimplexName} = p (ct, displaced_) <- withStore $ \db -> createDirectContact db vr user conn' p simplexName diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20260603_simplex_name.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20260603_simplex_name.hs index d7675709c9..f8fa467f37 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/M20260603_simplex_name.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20260603_simplex_name.hs @@ -6,6 +6,17 @@ module Simplex.Chat.Store.Postgres.Migrations.M20260603_simplex_name where import Data.Text (Text) import Text.RawString.QQ (r) +-- contacts.simplex_name and groups.simplex_name are the source of truth for the +-- entity's current name (updated when XInfo/XGrpInfo arrives). +-- +-- connections.simplex_name is a TRANSIENT carrier for the connect-via-plan +-- (connect-by-name) path: when the user initiates a connection by typing +-- #name.simplex, the peer's profile is not yet available, so the name is +-- stashed on the connection row. When XInfo arrives and the Contact row is +-- created, the XInfo handler in Library/Subscriber.hs reads +-- connections.simplex_name and passes it to createDirectContact. After contact +-- creation, contacts.simplex_name is canonical and the connection's value +-- becomes a historical snapshot - it is intentionally never UPDATEd. m20260603_simplex_name :: Text m20260603_simplex_name = [r| diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20260603_simplex_name.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20260603_simplex_name.hs index ec16ea8bf7..552808b27b 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20260603_simplex_name.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20260603_simplex_name.hs @@ -5,6 +5,17 @@ module Simplex.Chat.Store.SQLite.Migrations.M20260603_simplex_name where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) +-- contacts.simplex_name and groups.simplex_name are the source of truth for the +-- entity's current name (updated when XInfo/XGrpInfo arrives). +-- +-- connections.simplex_name is a TRANSIENT carrier for the connect-via-plan +-- (connect-by-name) path: when the user initiates a connection by typing +-- #name.simplex, the peer's profile is not yet available, so the name is +-- stashed on the connection row. When XInfo arrives and the Contact row is +-- created, the XInfo handler in Library/Subscriber.hs reads +-- connections.simplex_name and passes it to createDirectContact. After contact +-- creation, contacts.simplex_name is canonical and the connection's value +-- becomes a historical snapshot - it is intentionally never UPDATEd. m20260603_simplex_name :: Query m20260603_simplex_name = [sql| diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index b216f6060b..b8a7408e46 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -277,6 +277,15 @@ toMaybeConnection vr ((Just connId, Just agentConnId, Just connLevel, viaContact Just $ toConnection vr ((connId, agentConnId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, xContactId) :. (customUserProfileId, connStatus, connType, contactConnInitiated, localAlias) :. (contactId, groupMemberId, userContactLinkId) :. (createdAt, code_, verifiedAt_, pqSupport, pqEncryption, pqSndEnabled_, pqRcvEnabled_, authErrCounter, quotaErrCounter, connChatVersion, minVer, maxVer) :. Only simplexNameRaw) toMaybeConnection _ _ = Nothing +-- | Creates a new connection row. The @simplexName@ argument is a TRANSIENT +-- carrier for the connect-via-plan (connect-by-name) path: when the user +-- initiates a connection by typing #name.simplex, the peer's profile is not +-- yet available. The name is stashed on connections.simplex_name so that, when +-- XInfo arrives and the Contact row is created, the XInfo handler in +-- Library/Subscriber.hs (saveConnInfo) can read it and pass it to +-- createDirectContact. After contact creation, contacts.simplex_name is the +-- source of truth and the connection's value becomes a historical snapshot +-- that is intentionally never updated. createConnection_ :: DB.Connection -> UserId -> ConnType -> Maybe Int64 -> ConnId -> ConnStatus -> VersionChat -> VersionRangeChat -> Maybe ContactId -> Maybe Int64 -> Maybe ProfileId -> Int -> UTCTime -> SubscriptionMode -> PQSupport -> Maybe SimplexNameInfo -> IO Connection createConnection_ db userId connType entityId acId connStatus connChatVersion peerChatVRange@(VersionRange minV maxV) viaContact viaUserContactLink customUserProfileId connLevel currentTs subMode pqSup simplexName = do viaLinkGroupId :: Maybe Int64 <- fmap join . forM viaUserContactLink $ \ucLinkId ->