Skip to content

WPB-21964: move conversation creation to wire-subsystems#4977

Open
blackheaven wants to merge 39 commits intodevelopfrom
gdifolco/WPB-21964-extract-create-conversations-wire-subsystems
Open

WPB-21964: move conversation creation to wire-subsystems#4977
blackheaven wants to merge 39 commits intodevelopfrom
gdifolco/WPB-21964-extract-create-conversations-wire-subsystems

Conversation

@blackheaven
Copy link
Contributor

@blackheaven blackheaven commented Jan 22, 2026

https://wearezeta.atlassian.net/browse/WPB-21964

Checklist

  • Add a new entry in an appropriate subdirectory of changelog.d
  • Read and follow the PR guidelines

@blackheaven blackheaven requested review from a team as code owners January 22, 2026 17:01
@zebot zebot added the ok-to-test Approved for running tests in CI, overrides not-ok-to-test if both labels exist label Jan 22, 2026
@blackheaven blackheaven force-pushed the gdifolco/WPB-21964-extract-create-conversations-wire-subsystems branch 5 times, most recently from 1e8e214 to e7b9141 Compare January 28, 2026 11:14
@battermann battermann requested a review from Copilot January 29, 2026 07:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors conversation creation logic by moving it from Galley to the wire-subsystems library, aligning with the architectural goal of separating concerns into reusable subsystems.

Changes:

  • Moved conversation creation logic from Galley.API.Create to Wire.ConversationSubsystem.Interpreter
  • Relocated utility modules: Galley.API.ErrorGalley.Types.Error, Galley.API.One2OneWire.ConversationSubsystem.One2One, Galley.API.UtilWire.ConversationSubsystem.Util, Galley.Effects.ClientStoreWire.Effects.ClientStore
  • Removed Galley.Validation module (functionality moved to interpreter)
  • Updated background-worker to include new dependencies and error handlers
  • Updated imports across numerous files to reflect module movements

Reviewed changes

Copilot reviewed 63 out of 64 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
services/galley/src/Galley/API/Create.hs Simplified to delegate conversation creation to subsystem
libs/wire-subsystems/src/Wire/ConversationSubsystem/Interpreter.hs New implementation containing conversation creation logic
libs/wire-subsystems/src/Wire/ConversationSubsystem/Util.hs Utility functions moved from Galley
libs/wire-subsystems/src/Wire/ConversationSubsystem/Federation.hs New module for federation-related conversation logic
libs/wire-subsystems/src/Wire/ConversationSubsystem/Types.hs Configuration types for conversation subsystem
services/background-worker/src/Wire/BackgroundWorker/Jobs/Registry.hs Updated to support new conversation subsystem dependencies
libs/galley-types/src/Galley/Types/Error.hs Error types moved to galley-types library
services/galley/src/Galley/Effects.hs Reordered effect stack and added new error types
Multiple import updates Updated imports across ~30 files to reflect module movements

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@battermann battermann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great effort! Thanks!. I feel there is more to do and to be said to get this really clean, so here are a few comments already.

Comment on lines 122 to 142
. mapError (const ("Invalid input" :: Text) :: InvalidInput -> Text)
. mapError @MigrationError (T.pack . show)
. mapError @InternalError (TL.toStrict . internalErrorDescription)
. mapError @UnreachableBackends (T.pack . show)
. mapError @NonFederatingBackends (T.pack . show)
. mapError @TeamCollaboratorsError (const ("Team collaborators error" :: Text))
. mapError @TeamFeatureStoreError (const ("Team feature store error" :: Text))
. mapError @(Tagged OperationDenied ()) (const ("Operation denied" :: Text))
. mapError @(Tagged 'NotATeamMember ()) (const ("Not a team member" :: Text))
. mapError @(Tagged 'ConvAccessDenied ()) (const ("Conversation access denied" :: Text))
. mapError @(Tagged 'NotConnected ()) (const ("Not connected" :: Text))
. mapError @(Tagged 'MLSNotEnabled ()) (const ("MLS not enabled" :: Text))
. mapError @(Tagged 'MLSNonEmptyMemberList ()) (const ("MLS non-empty member list" :: Text))
. mapError @(Tagged 'MissingLegalholdConsent ()) (const ("Missing legalhold consent" :: Text))
. mapError @(Tagged 'NonBindingTeam ()) (const ("Non-binding team" :: Text))
. mapError @(Tagged 'NoBindingTeamMembers ()) (const ("No binding team members" :: Text))
. mapError @(Tagged 'TeamNotFound ()) (const ("Team not found" :: Text))
. mapError @(Tagged 'InvalidOperation ()) (const ("Invalid operation" :: Text))
. mapError @(Tagged 'ConvNotFound ()) (const ("Conversation not found" :: Text))
. mapError @(Tagged 'ChannelsNotEnabled ()) (const ("Channels not enabled" :: Text))
. mapError @(Tagged 'NotAnMlsConversation ()) (const ("Not an MLS conversation" :: Text))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems verbose and like some duplication. Don't we have instances for these somewhere. Can we reuse them? Or can this be extracted into a singel mapping function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in galley they are converted to http errors, should we somehow group them?

Comment on lines +79 to +81
let remoteDomains = void <$> snd (partitionQualified lusr $ newConv.newConvQualifiedUsers)
enforceFederationProtocol (baseProtocolToProtocol newConv.newConvProtocol) remoteDomains
checkFederationStatus (RemoteDomains $ Set.fromList remoteDomains)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if checking the federation status should be implemented in a Federation subsystem, and if the conversation subsystem interpreter should be using it instead of having this here? Should that be part of this PR, too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the new design answers your questions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the FederationSubsystem, however here we still have code that integrates conversation and federation subsystems, my understanding is that we want to remove this responsibility from the services (galley in this case).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly agree, but there are places we use it directly:

  • services/galley/src/Galley/API/Internal.hs:188:getFederationStatus
  • services/galley/src/Galley/API/Action.hs:585:enforceFederationProtocol
  • services/galley/src/Galley/API/Action.hs:607:checkFederationStatus

What should I do then?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i say leave it for later.

@blackheaven blackheaven force-pushed the gdifolco/WPB-21964-extract-create-conversations-wire-subsystems branch 3 times, most recently from 7b92819 to c7b2a42 Compare February 3, 2026 21:05
@battermann battermann requested a review from Copilot February 4, 2026 13:46
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 79 out of 80 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 98 to 101
testInternalGetConfiguredFeatureFlags :: (HasCallStack) => App ()
testInternalGetConfiguredFeatureFlags = do
response <- Internal.getConfiguredFeatureFlags
response.status `shouldMatchInt` 200
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test implementation: The test case testInternalGetConfiguredFeatureFlags is added but there's no verification of the response content beyond the status code. Consider adding assertions to verify the structure and content of the returned feature flags to ensure the endpoint returns valid data.

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +81
let remoteDomains = void <$> snd (partitionQualified lusr $ newConv.newConvQualifiedUsers)
enforceFederationProtocol (baseProtocolToProtocol newConv.newConvProtocol) remoteDomains
checkFederationStatus (RemoteDomains $ Set.fromList remoteDomains)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the FederationSubsystem, however here we still have code that integrates conversation and federation subsystems, my understanding is that we want to remove this responsibility from the services (galley in this case).

@supersven
Copy link
Contributor

@blackheaven some compilation error slipped in:

background-worker> src/Wire/BackgroundWorker/Jobs/Registry.hs:71:50: error: [GHC-61689]
background-worker>     Module ‘Wire.FeaturesConfigSubsystem.Interpreter’ does not export ‘TeamFeatureStoreError’.
background-worker>    |
background-worker> 71 | import Wire.FeaturesConfigSubsystem.Interpreter (TeamFeatureStoreError, runFeaturesConfigSubsystem)
background-worker>    |                                                  ^^^^^^^^^^^^^^^^^^^^^
background-worker> 

(https://concourse.ops.zinfra.io/builds/118785733#L696f7b80:14799)

@blackheaven blackheaven force-pushed the gdifolco/WPB-21964-extract-create-conversations-wire-subsystems branch from 4d46c23 to 5d9b4f7 Compare February 9, 2026 17:08
Copy link
Contributor

@fisx fisx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not done, but i have read all the easy parts, and i'll keep reading for a little longer.

there are some types in wire-api that weren't there now, or that didn't have aeson instances. maybe add roundtrip tests, and possibly golden tests for those? especially if they are not fully implemented in schema-profunctor.

feel free to fix all my comments in separate PRs, or ignore them if they are not worth the trouble! :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't you say something about moving conversation stuff to wire-subsystems here?

gundeck:
host: gundeck
port: 8080
spar:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this also be mentioned in the changelog?

Comment on lines +449 to +450
class FeatureFlagsToPair f cfgs where
featureFlagsToPair :: NP f cfgs -> [A.Pair]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class FeatureFlagsToPair f cfgs where
featureFlagsToPair :: NP f cfgs -> [A.Pair]
class FeatureFlagsToPairs f cfgs where
featureFlagsToPairs :: NP f cfgs -> [A.Pair]

Comment on lines +33 to +34
firstConflictOrFullyConnected :: [Remote NonConnectedBackends] -> FederationStatus
firstConflictOrFullyConnected =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
firstConflictOrFullyConnected :: [Remote NonConnectedBackends] -> FederationStatus
firstConflictOrFullyConnected =
firstMissingConnectionOrFullyConnected :: [Remote NonConnectedBackends] -> FederationStatus
firstMissingConnectionOrFullyConnected =

this would make the explanation in the comment redundant.

zusrMembership <- join <$> forM storedConv.metadata.cnvmTeam (TeamSubsystem.internalGetTeamMember (qUnqualified origUser))
for_ zusrMembership $ \tm -> unless (tm `hasPermission` ModifyConvName) $ throwS @'InvalidOperation
cn <- rangeChecked (cupName action)
cn <- either (throw . InvalidRange . fromString) pure $ checkedEither (cupName action)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? i liked rangeChecked...

. runInputConst (e ^. cstate)
. mapError toResponse
. mapError toResponse
. mapError toResponse
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this is a good reason to invent callNTimesConscutively? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are various types, it can be a fun challenge to make a piece of code for this.

NotConnectedDomains dom1 dom2 -> throw (NonFederatingBackends dom1 dom2)
GetFederationStatus req -> getFederationStatus' req
where
getFederationStatus' ::
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the pattern in many other places in wire-subsystems is to call it getFederationStatusImpl and declare it at the top level of the module.

GetFeatureConfigForTeam tid -> pure $ getFeatureConfigForTeamImpl configs tid
GetConfiguredFeatureFlags ->
pure $
FeatureLegalHoldDisabledByDefault
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this the Default instance from Wire.API.Team.Feature? if not: why? could one be defined there, or just a defaultThing, without the type class complicating things?

@blackheaven
Copy link
Contributor Author

I'll have a look, thanks for the review!

Copy link
Contributor

@fisx fisx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still happy with everything, but i need to go. i'll continue reading later.

thoughts for next time: this PR was way too big (yes, i know, i'm no better :). i think some changes could have easily been factored out (federation subsystem?), and i'm tempted to that we need a rule to separate refactoring PRs from PRs that change semantics, and not mix both in one.


----------------------------------------------------------------------------
-- Notifications
notifyConversationUpdated ::
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
notifyConversationUpdated ::
notifyConversationUpdated ::

Comment on lines +79 to +81
let remoteDomains = void <$> snd (partitionQualified lusr $ newConv.newConvQualifiedUsers)
enforceFederationProtocol (baseProtocolToProtocol newConv.newConvProtocol) remoteDomains
checkFederationStatus (RemoteDomains $ Set.fromList remoteDomains)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i say leave it for later.

@blackheaven
Copy link
Contributor Author

thoughts for next time: this PR was way too big (yes, i know, i'm no better :). i think some changes could have easily been factored out (federation subsystem?), and i'm tempted to that we need a rule to separate refactoring PRs from PRs that change semantics, and not mix both in one.

I know, I'm down the rabbit hole, it's an excerpt, of an excerpt, of an excerpt, of an excerpt, of a PR (not kidding).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ok-to-test Approved for running tests in CI, overrides not-ok-to-test if both labels exist

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants