Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,79 @@ called out explicitly even when nothing else did.
release notes, so a version with no entry here does not release. Write the entry in the same PR that
syncs the contract, while the diff is still in front of you.

## 0.6.0

Synced to [`flipcash2-protobuf-api@35f99814`](https://github.com/code-payments/flipcash2-protobuf-api/commit/35f9981400947921bbe2872be63b0bb77a569e34),
picking up six upstream changes: [#93](https://github.com/code-payments/flipcash2-protobuf-api/pull/93)
through [#98](https://github.com/code-payments/flipcash2-protobuf-api/pull/98).

Three fields were renamed in place. Nothing was renumbered, so the wire format is unchanged and an
old client keeps decoding new messages — but the generated accessors move, and upgrading will not
compile until you rename with them. Details under Changed.

### Changed

- `messaging.v1.EmojiReaction.sequence` and `messaging.v1.ReactionUpdate.sequence` are both now
`version`. **Source-breaking.** Field numbers (5 and 6), types and semantics are untouched; the
name was wrong for what the value does. It is a per-aggregate version advanced by one on every
change, not a position in a sequence, and it was already documented as opaque and ordering-only.
Swift `.sequence` becomes `.version`; Kotlin `getSequence()`/`setSequence` become
`getVersion()`/`setVersion`.

Keep applying reaction updates last-writer-wins by this value per (message, emoji), and per actor
for `reacted_by_self`. It is still not the chat event sequence and still not gapless.

- `blob.v1.AccessContext.profile` is now `user_profile`, and the `scope` oneof gains a third arm.
**Source-breaking twice over.** The rename keeps field number 2 and type `common.v1.UserId`, so
Swift's oneof case `.profile` becomes `.userProfile` and Kotlin's `ScopeCase.PROFILE` becomes
`ScopeCase.USER_PROFILE`. Separately, an exhaustive `switch` or `when` over `scope` needs the new
`chat_profile` arm below. Code that only sets one arm sees the rename; code that reads the oneof
exhaustively sees both.

### Added

- `chat_profile` on `blob.v1.AccessContext`, field 3, typed `common.v1.ChatId` — the third `scope`
arm. It authorizes reading a chat's public profile picture, and only that: the blob must be a
rendition of the chat's *current* picture, so renditions of a superseded picture stop resolving
through it. That expiry is the difference from the `chat` scope, which does not narrow to one blob.

- `picture` on `chat.v1.Metadata`, field 9, typed `blob.v1.Media`. Group chats only, and optional.
This is what makes `chat/v1/model.proto` import `blob/v1/model.proto` for the first time, so a
build that compiles the chat package now needs the blob package alongside it.

- `roster_summary` on `chat.v1.Metadata`, field 10, typed `chat.v1.RosterSummary` and marked
required. `RosterSummary` describes a chat's member list without containing it: `member_count`
(field 1) is the roster's true size, where `Metadata.members` is only a subset for large group
chats, and `version` (field 2) advances by one on every membership-record change — a join, a
leave, and in future anything the chat records about a member, such as a role.

`version` is opaque. Compare it against the last value you held: different means your cached member
list may be stale and should be refetched. On a stream, apply the greater value and drop the rest,
so delivery order stops mattering. There is no delta to fetch against it, only a refetch. It never
moves for a profile change — profiles are hydrated fresh onto every response carrying a member.

- `rules` on `chat.v1.Metadata`, field 11, typed `chat.v1.Rules`. Group chats only, and unset means
no participation requirements, so existing chats are unaffected.

`Rules` splits into two independently optional classes: `listener` (field 1, up to 32) gates
reading and joining, `speaker` (field 2, up to 32) gates sending. Empty `listener` means anyone can
read and join; empty `speaker` means any member can send. All rules within a class must be
satisfied, and speaker rules apply on top of listener rules — a user has to be able to listen
before they can speak.

`ListenerRules` and `SpeakerRules` are separate messages with identical `kind` oneofs, each
requiring one of `minimum_balance` (field 1) or `staff` (field 2). `StaffRequirement` is empty and
means `UserFlags.is_staff`. `MinimumBalanceRequirement` carries a required fiat `amount` and a
`mints` list that is currently capped at one entry — empty applies the requirement across all
mints, and the field is repeated only so more can be allowed later.

### Unchanged

No field or enum was renumbered, and no result enum gained, lost or reordered a case — the three
renames all keep their field numbers, and the new `chat_profile` arm appends at 3. No service, RPC or
message was removed, and no existing message changed the type of an existing field. Every break in
this release is a name your compiler will point at, not a value that silently means something else.

## 0.5.0

Synced to [`flipcash2-protobuf-api@797052dd`](https://github.com/code-payments/flipcash2-protobuf-api/commit/797052dd1070662f97407427665fd48967abfac6),
Expand Down
52 changes: 43 additions & 9 deletions Sources/Flipcash2ClientProtocol/blob_v1_model.pb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -726,12 +726,24 @@ public struct Flipcash_Blob_V1_AccessContext: Sendable {
///
/// A caller never needs this for its OWN profile picture, since it owns
/// those blobs.
public var profile: Flipcash_Common_V1_UserId {
public var userProfile: Flipcash_Common_V1_UserId {
get {
if case .profile(let v)? = scope {return v}
if case .userProfile(let v)? = scope {return v}
return Flipcash_Common_V1_UserId()
}
set {scope = .profile(newValue)}
set {scope = .userProfile(newValue)}
}

/// The caller is accessing these blobs from this chat's public profile.
/// Authorized iff the blob is a rendition of that chat's CURRENT profile
/// picture — a profile grants nothing else, and a superseded picture's
/// renditions stop resolving through it.
public var chatProfile: Flipcash_Common_V1_ChatId {
get {
if case .chatProfile(let v)? = scope {return v}
return Flipcash_Common_V1_ChatId()
}
set {scope = .chatProfile(newValue)}
}

public var unknownFields = SwiftProtobuf.UnknownStorage()
Expand All @@ -747,7 +759,12 @@ public struct Flipcash_Blob_V1_AccessContext: Sendable {
///
/// A caller never needs this for its OWN profile picture, since it owns
/// those blobs.
case profile(Flipcash_Common_V1_UserId)
case userProfile(Flipcash_Common_V1_UserId)
/// The caller is accessing these blobs from this chat's public profile.
/// Authorized iff the blob is a rendition of that chat's CURRENT profile
/// picture — a profile grants nothing else, and a superseded picture's
/// renditions stop resolving through it.
case chatProfile(Flipcash_Common_V1_ChatId)

}

Expand Down Expand Up @@ -1388,7 +1405,7 @@ extension Flipcash_Blob_V1_RejectionMetadata: SwiftProtobuf.Message, SwiftProtob

extension Flipcash_Blob_V1_AccessContext: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".AccessContext"
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}chat\0\u{1}profile\0")
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}chat\0\u{3}user_profile\0\u{3}chat_profile\0")

public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
Expand All @@ -1414,12 +1431,25 @@ extension Flipcash_Blob_V1_AccessContext: SwiftProtobuf.Message, SwiftProtobuf._
var hadOneofValue = false
if let current = self.scope {
hadOneofValue = true
if case .profile(let m) = current {v = m}
if case .userProfile(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.scope = .userProfile(v)
}
}()
case 3: try {
var v: Flipcash_Common_V1_ChatId?
var hadOneofValue = false
if let current = self.scope {
hadOneofValue = true
if case .chatProfile(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.scope = .profile(v)
self.scope = .chatProfile(v)
}
}()
default: break
Expand All @@ -1437,10 +1467,14 @@ extension Flipcash_Blob_V1_AccessContext: SwiftProtobuf.Message, SwiftProtobuf._
guard case .chat(let v)? = self.scope else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
}()
case .profile?: try {
guard case .profile(let v)? = self.scope else { preconditionFailure() }
case .userProfile?: try {
guard case .userProfile(let v)? = self.scope else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
}()
case .chatProfile?: try {
guard case .chatProfile(let v)? = self.scope else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
}()
case nil: break
}
try unknownFields.traverse(visitor: &visitor)
Expand Down
Loading
Loading