Skip to content

Releases

0.25.0

Choose a tag to compare

@github-actions github-actions released this 03 Nov 15:30

Synced to flipcash2-protobuf-api@53316c0e,
picking up #165.
activity.v1, intent.v1 and messaging.v1 moved.

This release lets the receiver of a DM's opening payment send it back. Nothing existing changed
on the wire. The source breaks are three new oneof cases, each appended after the existing ones.

Breaking

  • activity.v1.DirectlySentCryptoNotificationMetadata and ReceivedCryptoNotificationMetadata
    each gain returned_payment (field 5) in their oneof, and intent.v1.ChatMetadata's oneof
    gains return_payment (field 7). An exhaustive switch over the Swift oneof, or a when over
    Kotlin's *Case without an else, needs a case for each before it compiles. Code that reads
    these oneofs through has*() checks compiles unchanged and drops the new case silently.

Added

  • messaging.v1.Messaging.GetCashMessageReturnStatus, with its request and response.
    Result is OK, DENIED, NOT_FOUND (0–2) and Status is UNKNOWN, AVAILABLE,
    RETURNED (0–2). RETURNED never changes again, so a client can stop polling once it sees
    it. payment_intent_id is set only on OK, and only for the message's single_receiver.
  • messaging.v1.CashContent.payment_return (field 7), of type CashContent.Return with
    independent offer and return_of. Return.Offer is empty: its presence is the offer.
    Today only the cash message that opened a DM carries one. A message with return_of set
    returns the payment of the message it names, so a client that sees it can stop offering a
    return on that message.
  • intent.v1.ChatMetadata.ReturnPayment (message_id). The return is a SendPublicPayment
    from the original message's single_receiver back to its sender, in the same DM, for the
    same currency, mint and native amount. Its intent ID must be the server-issued
    payment_intent_id
    from GetCashMessageReturnStatus, not one the client generates. The
    server refuses any other ID, which is what keeps a payment returnable once across retries
    and devices. Treat that ID as private until the payment is made: anyone who uses it first
    blocks the return.
  • activity.v1.ReturnedPayment (chat_id, message_id), naming the DM and the cash message
    whose payment was returned. The server doesn't update it afterwards, so the message may since
    have been deleted.

Unchanged

Nothing was renumbered. No service, RPC, field or enum case was removed or changed type.

0.24.0

Choose a tag to compare

@github-actions github-actions released this 29 Oct 22:56

Synced to flipcash2-protobuf-api@0e167fa6,
picking up #164. Only
chat.v1 moved.

This release adds pinned messages to chats. Nothing existing changed on the wire. The one
source break is a new oneof case, appended like every other addition here.

Breaking

  • chat.v1.MetadataUpdate's kind oneof gains pins_changed (field 9). An exhaustive switch
    over the Swift oneof, or a when over Kotlin's KindCase without an else, needs a case for
    it before it compiles. A when with an else compiles but routes pin events into that
    branch, so check what it does.

Added

  • chat.v1.Chat.PinMessage, UnpinMessage and GetPinnedMessages, with their request and
    response messages. PinMessageResponse.Result is OK, DENIED, NOT_FOUND,
    MESSAGE_NOT_FOUND, TOO_MANY_PINS, MESSAGE_NOT_ALLOWED (0–5); the other two results are
    OK, DENIED, NOT_FOUND (0–2). Pin and unpin are no-ops returning OK when nothing would
    change, and both return the chat's PinSummary on OK. The server caps a chat's pins (5
    today) and may raise the cap, so clients must not hardcode it.
  • chat.v1.Pin (message_id, pinned_by, pinned_at, message) and chat.v1.PinSummary
    (latest, count, version). version moves by one on every real pin or unpin and is
    compared like RosterSummary.version: keep the greater.
  • chat.v1.Metadata.pin_summary (field 19). It may be unset on feed RPCs such as
    GetDmChatFeed and GetGroupChatFeed, and an unset value there must not clear a summary the
    client already holds.
  • chat.v1.MetadataUpdate.PinsChanged, with pinned or unpinned and the summary after the
    change.
  • chat.v1.ViewerState.Permissions.can_pin (field 4). Today it is true for either member of a
    DM and for a group's creator while they are a member.

Unchanged

Nothing was renumbered. No service, RPC, field or enum case was removed or changed type.

0.23.0

Choose a tag to compare

@github-actions github-actions released this 29 Oct 03:44

Synced to flipcash2-protobuf-api@6de4f9ad,
picking up #160,
#161,
#162 and
#163. activity.v1,
chat.v1, event.v1, paymentrequest.v1 and push.v1 moved.

This release makes a public group's minimum balances editable, announces payment request changes
on the chat event stream, links feed payments to the payment request they paid, and adds a test
push. Upgrading breaks source in Swift only, not the wire. One enum gained cases, appended at the
end, so no existing value moved.

Breaking

  • activity.v1.DirectlySentCryptoNotificationMetadata.boosted_reaction and
    ReceivedCryptoNotificationMetadata.boosted_reaction (field 3 in both) moved into a new
    oneof context, next to requested_payment. Swift loses hasBoostedReaction and
    clearBoostedReaction on both; switch on context instead. Kotlin keeps hasBoostedReaction()
    and gains getContextCase().
  • chat.v1.MetadataUpdate's kind oneof gains rules_changed (field 8). An exhaustive switch
    over the Swift oneof, or a when over Kotlin's KindCase without an else, needs a case for
    it before it compiles.

Added

  • activity.v1.RequestedPayment, with chat_id and payment_request_id, set as
    requested_payment (oneof field 4) on both notification metadata messages above when the
    payment paid a share of a payment request. It records what the payment was for and is not kept
    up to date: the request may have closed, and fetching it may be denied.
  • chat.v1.EditChatRequest.rules (field 6), the group's rules in full. Only a public group created
    with a listener minimum balance can be edited, and only its listener and speaker
    MinimumBalanceRequirement amounts may change; the proto docs list the limits.
  • EditChatResponse.Result cases INVALID_RULES = 7 and RULES_NOT_SATISFIED = 8, both
    appended.
  • chat.v1.MetadataUpdate.RulesChanged, carrying new_rules in full, sent on every rules edit.
  • event.v1.ChatUpdate.payment_request_updates (field 10), a
    paymentrequest.v1.PaymentRequestUpdateBatch. Like reaction updates, it is best effort and not
    part of the gap-detected event log.
  • paymentrequest.v1.PaymentRequestUpdate: a request whole when it is created (created), then
    each change as Updates at the version it produced, made of SharePaymentUpdate and
    StatusUpdate. The proto docs give the apply rules: ignore a version already held, apply the
    next one, and refetch on a gap or on an update the client cannot apply. Expiry is never
    announced.
  • push.v1.Push.SendTestPush, with SendTestPushRequest and SendTestPushResponse. It sends a
    fixed notification to every device the caller registered a token for.

Changed

  • A public group's Metadata.rules can now change after creation. A member who no longer
    satisfies the new listener rules stays a member but cannot speak until they do.
  • Payment request changes are announced in real time. Until now the docs told clients to refetch
    a request to see payments and closes land; refetching is now only for reconciling a missed
    update.

Unchanged

Nothing was renumbered. No service, RPC, field or enum case was removed. Apart from the oneof
move above, no field changed type or number.

0.22.0

Choose a tag to compare

@github-actions github-actions released this 27 Oct 19:44

Synced to flipcash2-protobuf-api@b655df17,
picking up #157,
#158 and
#159. chat.v1,
messaging.v1 and common.v1 moved.

This release adds invite links for private groups, plus anonymous reads of an invite and the
chat's roster. Upgrading breaks source in Swift only, not the wire. One enum gained cases,
appended at the end, so no existing value moved.

Breaking

  • chat.v1.GetKeyEnvelopeResponse.key_envelope (field 2) moved into a new oneof envelope, next
    to invite_key_bundle. Swift loses hasKeyEnvelope and clearKeyEnvelope; switch on
    envelope instead. Kotlin keeps hasKeyEnvelope() and gains getEnvelopeCase(). In either
    language, a client that reads only key_envelope sees an empty envelope for a member who joined
    with an invite.
  • common.v1.Username now accepts 2 to 30 characters, up from 15. Nothing fails to compile, but a
    client that validates, matches or encodes usernames at 15 will reject or truncate names the
    server now issues.

Added

  • Invite RPCs on chat.v1.Chat: CreateInvite, GetInvite, GetChatInvites and
    RevokeInvite, with Invite, Invite.Status, InviteId, InviteToken, InviteKeyEnvelope
    and InviteSecretEnvelope. Only a private group's creator may create, list or revoke invites
    today. GetInvite takes optional auth, so a link can be previewed without an account.
  • The Invite docs specify the client-side crypto: how the token, wrap key and invite ID derive
    from the link's secret (HKDF-SHA256 and SHA-256), the XChaCha20-Poly1305 encryption of the
    chat key, and the creator's Ed25519 signature over it. iOS and Android must compute these
    identically.
  • JoinChatRequest.invite (field 2), an InviteParameters with the invite token and the joiner's
    InviteSecretEnvelope. A private group can now be joined with an invite.
  • JoinChatResponse.Result cases INVITE_NOT_FOUND = 5, INVITE_EXHAUSTED = 6,
    INVITE_EXPIRED = 7 and INVITE_REVOKED = 8, all appended. An exhaustive switch or when
    over the result needs them before it compiles.
  • GetKeyEnvelopeResponse.invite_key_bundle (oneof field 5), returned instead of key_envelope
    to a member who joined with an invite and has not yet stored their own envelope with
    SetKeyEnvelope.
  • GetKeyEnvelopeResponse.wrapped_by_public_key (field 4).
  • GetRosterRequest.invite_token (field 3). A link holder can read the roster with it.
  • chat.v1.ViewerState.Permissions.can_invite (field 3).
  • messaging.v1.SystemContent member-joined oneof gains invited (field 4), an Invited naming
    invited_by. A client that renders join messages needs a case for it.

Changed

  • GetRosterRequest.auth is no longer required when invite_token is set. A request with
    neither is still DENIED.
  • GetChat on a private group without auth now returns its Metadata record instead of
    DENIED. Its messaging state stays private.
  • JoinChat on a private group by anyone but the creator is no longer always DENIED: it
    succeeds with an ACTIVE invite. An invite join also removes the user from the lobby and is
    announced as RosterUpdate.MemberJoined, like an admission.

Unchanged

Nothing was renumbered. No service, RPC, field or enum case was removed. Apart from the oneof
move above, no field changed type or number.

0.21.0

Choose a tag to compare

@github-actions github-actions released this 23 Oct 20:24

Synced to flipcash2-protobuf-api@b77bf78f,
picking up #151,
#152 (docs only),
#154 (docs only),
#155 and
#156. paymentrequest.v1 is
new. activity.v1, chat.v1, intent.v1 and messaging.v1 moved.

This release adds payment requests (group splits), payments to several group members at once, and
moderation RPCs for group creators. Upgrading breaks source, not the wire. CashContent renamed
two fields and moved one into a oneof, so code reading amount or receiver stops compiling. Two
enums gained a case, appended at the end, so no existing value moved.

Breaking

  • messaging.v1.CashContent.amount (field 2) is now total_amount. Same type and number. For a
    payment to several people it is the whole payment.
  • messaging.v1.CashContent.receiver (field 3) is now single_receiver, inside a new
    oneof recipient. Kotlin's getReceiver()/hasReceiver() become getSingleReceiver()/
    hasSingleReceiver(); Swift's receiver becomes singleReceiver. An older DM message that
    predates per-message receivers still has neither member of the oneof set.

The binary encoding of both fields is unchanged, so messages written before and after the rename
decode the same way.

Added

  • paymentrequest.v1.PaymentRequest, a new service with five RPCs: CreatePaymentRequest,
    GetPaymentRequest, GetChatPaymentRequests, ClosePaymentRequest and MarkPaid. The one kind
    today is a split in a group chat (GroupChatPaymentRequest with a Split of per-member
    Shares). DMs are not supported. No event announces a change to a request; clients refetch it.
    • In a private group the split's title and description are EncryptedPaymentRequestText under
      the chat key. Everything else, including shares and payments, is visible to the server.
    • GetPaymentRequestResponse.payment_intent_id is the intent ID a debtor's payment of their share
      must use. The server issues one per share, so a share is paid at most once.
  • messaging.v1.PaymentRequestWidget, sent as WidgetContent.payment_request (oneof field 3). It
    carries only the request's ID.
  • intent.v1.ChatMetadata.requested_payment (oneof field 6), a RequestedPayment naming the
    payment_request_id whose share a SendPublicPayment pays.
  • messaging.v1.CashContent.multi_receiver (oneof field 5), a Payments list of 2 to 10
    Payment { receiver, native_amount, quarks }. A group payment to several members is an ocp
    SendPublicMultiPayment.
  • messaging.v1.CashContent.payment_request_id (field 6), set only on the cash message posted when
    a debtor pays their share.
  • Chat moderation RPCs on chat.v1.Chat: SilenceUser, UnsilenceUser, BanUser, UnbanUser and
    GetRestrictedUsers, with SilenceState, BanState and RestrictedUser. The chat's creator
    cannot be silenced or banned.
  • chat.v1.ViewerState.restrictions (field 3), carrying the viewer's silence. A timed silence
    lapses with no event, so the client owns the countdown.
  • chat.v1.ViewerState.Permissions.moderation (field 2), a ModerationPermissions with
    can_silence and can_ban.
  • JoinChatResponse.Result.BANNED = 4 and EnterLobbyResponse.Result.BANNED = 6, both appended.
    An exhaustive switch or when over either needs the new case before it compiles.

Changed

  • Reaction boosts are now allowed in DMs. A DM boost pays the other member the whole amount as one
    SendPublicPayment. No field changed; the activity.v1.BoostedReaction and
    intent.v1.ChatMetadata.ReactionBoost docs now cover DMs.
  • SendMessage and EditMessage document that ENCRYPTION_REQUIRED also applies to DMs.

Unchanged

Nothing was renumbered. No service, RPC or enum case was removed. Apart from the CashContent
rename above, no field was removed or changed type.

0.20.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 21:37

Synced to flipcash2-protobuf-api@733ea7f9,
picking up #147,
#148 and
#149 (docs only). community.v1
is new. activity.v1, chat.v1, intent.v1 and messaging.v1 moved.

This release adds the Community service, paid reaction boosts and a holdings summary on gated group
chats. Everything is additive. Two enums gained a case, appended at the end, so no existing value
moved. An exhaustive switch or when over either enum needs the new case before it compiles.

Added

  • community.v1.Community, a new service with one RPC, GetCommunity. It looks a community up by
    mint and returns CommunityMetadata (mint, name, leader, and up to 25
    top_holder_chats), or NOT_FOUND.
  • Reaction boosts. A payer puts money behind a reaction they already hold on a group message:
    • intent.v1.ChatMetadata.reaction_boost (oneof field 5), a ReactionBoost naming the
      message_id and emoji.
    • A boost that pays two people, the sender and the group's creator split 80/20, is an ocp
      SendPublicMultiPayment, so it needs ocp-client-protocol 0.8.0 or later. A boost that pays
      one person is a SendPublicPayment.
    • messaging.v1.Reactor.boost_usd (field 4), EmojiReaction.boost_usd (field 6),
      ReactionUpdate.boost_usd (field 8) and ReactionUpdate.actor_boost_usd (field 9).
    • ReactionUpdate.Action.BOOSTED = 3.
    • RemoveReactionResponse.Result.CANNOT_REMOVE_BOOSTED = 3. A boosted reaction is permanent.
    • activity.v1.BoostedReaction, set as boosted_reaction (field 3) on both
      DirectlySentCryptoNotificationMetadata and ReceivedCryptoNotificationMetadata when the
      payment was a boost.
  • chat.v1.Metadata.holdings_summary (field 18), a HoldingsSummary of the group members' holdings
    of the currency the group is gated on (mint, quarks, holder_count, as_of). It is set only
    for public groups gated on a single Flipcash-launched currency with enough holders. It is a
    periodic snapshot, and no event announces a change.

Changed

  • Reactor lists, sample_reactors and ReactionSummary.reactions now sort boosted entries first,
    by boost_usd descending, then fall back to the previous order. Nothing changed on the wire, but
    a client that re-sorts by version alone will disagree with the server.
  • GetReactors now documents that a paging token the server no longer recognises, such as one
    issued under the old order, is answered with the first page rather than an error. Don't keep
    tokens across sessions.

Unchanged

Nothing was renumbered. No service, RPC, message, field or enum case was removed or changed type.

0.19.0

Choose a tag to compare

@github-actions github-actions released this 14 Oct 20:58

Synced to flipcash2-protobuf-api@1897108a,
picking up #139 (docs only),
#140,
#141,
#142,
#143,
#144,
#145 and
#146. poll.v1 is new;
intent.v1 and messaging.v1 moved; chat.v1 changed only in comments.

This release adds polls, payments in group chats, link previews and structured system messages. One
enum moved, which is wire-compatible but breaks source in both languages.

Moved

  • ChatMetadata.DmPayment.Action is now the top-level enum PaymentAction, shared by DM and
    group payments. Its cases keep their values (DEFAULT = 0, SEND = 1, TIP = 2) and
    DmPayment.action keeps field 2, so the wire format is unchanged. Code naming the old type has
    to be updated: Flipcash_Intent_V1_ChatMetadata.DmPayment.Action becomes
    Flipcash_Intent_V1_PaymentAction in Swift, and ChatMetadata.DmPayment.Action becomes
    PaymentAction in Kotlin.

Added

  • A Poll service in poll.v1 with CreatePoll, GetPoll, GetPolls and Vote, and its model:
    count or balance-weighted polls of 2 to 10 options, single or multiple choice, a required end
    time 5 minutes to 30 days out, and question text that is plaintext in a group that is not
    private and encrypted in a private one. Results come back as count, balance, ranked or withheld,
    and every vote result model carries total_voters.
  • WidgetContent.poll (field 2), a PollWidget carrying only the poll's ID. In a group that is not
    private, SendMessage accepts it only from the poll's creator, in the poll's own chat, while the
    poll is OPEN. A private group sends it inside EncryptedContent, where clients do those checks
    and render a mismatch as unsupported.
  • ChatMetadata.group_payment (field 4), a GroupPayment for paying another member of a group
    chat. The payment's destination owner names the recipient, who must be a member. Only DEFAULT
    and SEND are accepted; the server denies TIP.
  • CashContent.receiver (field 3, previously reserved), the user who received the cash. It is
    always set in a group chat. In a DM it is set on new messages only, so a client reading older DM
    messages still has to treat the other member as the receiver when it is unset.
  • TextContent.link_previews (field 2), at most one LinkPreview with a url, title, optional
    description and optional image. The sending device builds the preview, so its title,
    description and image are the sender's claim; the comments on LinkPreview spell out the
    validation rules and how clients have to render the host.
  • SystemContent.type, a required oneof whose first case is user_joined (field 2). UserJoined
    names the user and how they joined: direct, or admitted with the user who let them in.
    fallback_text is unchanged and still always set; a client renders it only for a variant it
    does not recognize.

Changed

  • EncryptedContent.ciphertext may now be up to 28672 bytes, up from 17408, to fit a maximum-length
    text with a link preview.

Unchanged

Nothing was renumbered. Every result enum in this release is new, so no existing case moved. Apart
from the PaymentAction move, no existing field changed type or meaning, and no service, RPC,
message or enum case was removed.

0.18.0

Choose a tag to compare

@github-actions github-actions released this 06 Oct 17:09

Synced to flipcash2-protobuf-api@9d44350b,
picking up #135,
#136,
#137 and
#138. Only chat.v1 moved;
blob.v1 changed only in comments.

Renumbered

  • EditChatResponse.Result: COVER_PICTURE_BLOB_NOT_ACCEPTED is inserted at 5, and
    DESCRIPTION_MODERATED moves from 5 to 6. 0.17.0 shipped DESCRIPTION_MODERATED = 5, so a
    0.17.0 client reads a cover-picture rejection from a current server as a moderated description,
    and does not recognize 6 at all. Code that maps this enum by raw value, such as iOS's
    Error*(rawValue:), has to be re-checked case by case, not only extended.

No other field or enum moved. StartChatResponse.Result gains its new case at the end.

Renamed

The group picture becomes the profile picture, to tell it apart from the new cover picture. Field
numbers are unchanged, so this is wire-compatible but breaks source in both languages.

  • Metadata.picture → profile_picture (= 9).
  • picture → profile_picture (= 2) on PublicGroupChatParameters and
    PrivateGroupChatParameters.
  • EditChatRequest.picture → profile_picture (= 3), and its type Picture → ProfilePicture.
  • MetadataUpdate.picture_changed → profile_picture_changed (= 5), and PictureChanged →
    ProfilePictureChanged with new_picture → new_profile_picture.
  • PICTURE_BLOB_NOT_ACCEPTED → PROFILE_PICTURE_BLOB_NOT_ACCEPTED on StartChatResponse.Result
    (= 3) and EditChatResponse.Result (= 4).

Added

  • Group cover pictures, the banner behind a chat's profile view.
    • Metadata.cover_picture (= 17). The feed RPCs may leave it unset even when one is set, so a
      feed result must not clear a cover picture the client already holds; GetChat returns it.
    • Set at creation through cover_picture on PublicGroupChatParameters (= 5) and
      PrivateGroupChatParameters (= 4), and changed through EditChatRequest.cover_picture
      (= 5, a CoverPicture { blob_id } wrapper).
    • Announced as MetadataUpdate.CoverPictureChanged (= 7, new_cover_picture).
    • COVER_PICTURE_BLOB_NOT_ACCEPTED on StartChatResponse.Result (= 7) and
      EditChatResponse.Result (= 5, see above).
  • RosterUpdate.MembershipChanged (= 3), an empty case for a roster change the recipient is not
    shown. Apply its roster_summary by version and leave the cached member list alone.
  • Chat.SampleChatters, a public sample of up to 100 members of a public group: its creator, then
    the most recent senders. Auth is optional. Returns SampledChatter (user_profile,
    last_sent_at, is_creator) and has_more.
  • Chat.SetFeaturedGroups and Chat.GetFeaturedGroups, an ordered list of up to 10 public groups a
    user shows on their profile. GetFeaturedGroups looks the user up by username, takes optional
    auth, and returns each group as a list view shows it, without cover_picture or any viewer or
    messaging state.

Changed behavior

  • GetRoster now requires membership. A non-member whom a group's listener rules let read its
    messages used to get the roster and now gets DENIED. SampleChatters is the public
    alternative.

Upgrading

Expect compile errors from the renames. Beyond those, an exhaustive MetadataUpdate switch needs
CoverPictureChanged, an exhaustive RosterUpdate switch needs MembershipChanged, and anything
that calls GetRoster for a group the user has not joined needs to stop.

0.17.0

Choose a tag to compare

@github-actions github-actions released this 05 Oct 21:40

Synced to flipcash2-protobuf-api@3bb442d3,
picking up #134. Only chat.v1
moved.

Added

  • Group descriptions, up to 160 characters and moderated like the title.
    • chat.v1.Metadata.description (= 16).
    • Set at creation through description on PublicGroupChatParameters (= 4) and
      PrivateGroupChatParameters (= 3). Optional; empty sets none.
    • Changed through EditChatRequest.description (= 4), a Description { value } wrapper: unset
      leaves the description alone, and a set wrapper with an empty value clears it.
    • Announced as MetadataUpdate.DescriptionChanged (= 6, new_description).
  • DESCRIPTION_MODERATED on StartChatResponse.Result (= 6) and EditChatResponse.Result (= 5).
    The response's flagged_category is set for it, as it is for TITLE_MODERATED.

Upgrading

Nothing existing changed: no renames, and no field or enum renumbering. Both new result cases come
after the existing ones. A client that ignores description keeps working, but one with an
exhaustive MetadataUpdate switch needs a DescriptionChanged case.

0.16.0

Choose a tag to compare

@github-actions github-actions released this 05 Oct 20:57

Synced to flipcash2-protobuf-api@ec66e6b1,
picking up #132 and
#133. chat.v1, event.v1,
messaging.v1 and profile.v1 moved; blob.v1 changed only in comments.

Changed

  • StartChatRequest's group oneof case is now public_group, and its message
    GroupChatParameters is now PublicGroupChatParameters. The field number is still 1, so this is
    wire-compatible, but every call site that sets or reads it stops compiling on upgrade
    (setGroup/group in Kotlin, .group in Swift).

Added

  • Private groups. StartChatRequest gains a private_group case (= 2) with
    PrivateGroupChatParameters (title, 1–64 characters; optional picture). A private group is
    visible once created, but nothing can happen in it until its creator stores the group's key.
  • Seven Chat RPCs for the lobby and keys: EnterLobby, LeaveLobby, GetLobbyMembers,
    AdmitLobbyMember, DenyLobbyMember, SetKeyEnvelope and GetKeyEnvelope, each with its own
    Result enum. A user waits in the lobby until the creator admits or denies them; admitting is
    the only way to hand another user a key envelope, and the admission arrives as an ordinary
    RosterUpdate.MemberJoined. A denial is not announced to the denied user.
  • chat.v1.KeyEnvelope (scheme, 24-byte nonce, 48-byte ciphertext): the group's 32-byte chat
    key, wrapped per member. The first envelope a caller stores for themself stands; a different one
    afterwards returns ALREADY_SET. There is no forward secrecy, and a member who leaves still knows
    the chat key.
  • chat.v1.Lobby, LobbyMember, LobbyUpdate and LobbyUpdateBatch, and
    event.v1.ChatUpdate.lobby_updates (= 9) carrying members entering and leaving a lobby.
  • chat.v1.Metadata.is_private (= 14) and in_lobby (= 15). in_lobby is per-viewer: there is no
    RPC that lists the lobbies a caller is waiting in, so a client reads it from GetChat.
  • messaging.v1.EncryptedContent.Scheme.CHAT_KEY_XCHACHA20POLY1305 (= 2), the scheme private groups
    use. Clients render a scheme they don't recognize as unsupported.
  • ENCRYPTION_REQUIRED on SendMessageResponse.Result (= 3) and EditMessageResponse.Result
    (= 6): the chat is a private group and the content is not EncryptedContent.
  • Profile bios and cover pictures: UserProfile.bio (= 12, up to 160 characters) and
    cover_picture (= 13), set through the new Profile.SetBio and Profile.SetCoverPicture RPCs.
    SetBio can come back FAILED_MODERATED with a flagged_category; SetCoverPicture reports blob
    state the same way SetProfilePicture does.

Upgrading

The public_group rename is the one source break. No field or enum value was renumbered: every new
enum case is appended after the existing ones, so Error*(rawValue:) mappings keep their meaning.