Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
3bee7ec
bootstrap arbiter into wire-server
battermann Jun 23, 2026
f78d550
migrate the meetings clean up job to arbiter
battermann Jun 23, 2026
c7a7d41
brig internal: route plumbing the arbiter UI
battermann Jun 23, 2026
089dab5
refactoring and implementing admin ui
battermann Jun 23, 2026
92f8ad7
resolve warning, formatting
battermann Jun 24, 2026
594ab9f
inline arbiter ui/api
battermann Jun 24, 2026
c0ae7eb
removed unused code
battermann Jun 24, 2026
6733db4
job store
battermann Jun 24, 2026
f8dba71
schema
battermann Jun 24, 2026
96c1fcd
job subsystem
battermann Jun 24, 2026
761acd7
resolve warning
battermann Jun 25, 2026
f6be631
revomve obsolete job subsystem operations
battermann Jun 25, 2026
42fb6d6
formatting
battermann Jun 25, 2026
14ba21c
linting
battermann Jun 25, 2026
2292bcf
single worker start effect, renaming
battermann Jun 25, 2026
6095211
clean up
battermann Jun 25, 2026
07f2d61
reminder and deletion jobs
battermann Jun 25, 2026
b245390
migrate the meetings clean up to a real arbiter cron
battermann Jun 26, 2026
51d11a9
send reminder events
battermann Jun 26, 2026
1ebce4a
fixed parser for tean feature
battermann Jun 26, 2026
34562ad
remove wire-server scheduled_job entry when job is finished
battermann Jun 26, 2026
933c2d4
test that jobs get created correctly
battermann Jun 26, 2026
46fbe9f
test execution of job
battermann Jun 26, 2026
b89993d
make the arbiter config explicit
battermann Jun 29, 2026
5419ade
make arbiter worker parallelism setting explicit
battermann Jun 29, 2026
1d8cadc
use correct feature store
battermann Jun 29, 2026
a7c55c3
logging
battermann Jun 29, 2026
5bd6df0
poll intervall configurable
battermann Jun 29, 2026
8e95209
comment and formatting
battermann Jun 29, 2026
69744a7
formatting
battermann Jun 29, 2026
5586097
update test for job excecution
battermann Jun 29, 2026
baadc18
fix bw config map and add config options doc
battermann Jun 29, 2026
777495b
changelog
battermann Jun 29, 2026
903b4be
formatting
battermann Jun 29, 2026
3b4eb04
wip: arbiter adapter
battermann Jul 1, 2026
fe879e3
Potential fix for pull request finding
battermann Jul 3, 2026
1e7d687
Potential fix for pull request finding
battermann Jul 3, 2026
b1b1be2
wip: arbiter adapter
battermann Jul 1, 2026
636542b
Revert "wip: arbiter adapter"
battermann Jul 2, 2026
c4a23c2
typo
battermann Jul 3, 2026
acc582e
extract into where
battermann Jul 3, 2026
12186b2
dependencies
battermann Jul 3, 2026
32cd24d
give arbiter a resource pool of conncetion
battermann Jul 3, 2026
0867a36
honor new feature config shape
battermann Jul 3, 2026
4bb51e0
do not use arbiter api in tests, check for reminders and deletion
battermann Jul 3, 2026
a9e399b
remove arbiter API/UI
battermann Jul 3, 2026
69eeecb
removed not needed scheduled jobs table and migrations
battermann Jul 3, 2026
3ee24da
fixed some merge/rebase issues
battermann Jul 6, 2026
23a78d3
fixed postgres schema
battermann Jul 6, 2026
78419c2
fix
battermann Jul 6, 2026
7261074
fix dependencies after rebase
battermann Jul 9, 2026
778589b
small fixes after rebase
battermann Jul 9, 2026
7ddfeea
monad arbeiter adapter
battermann Jul 9, 2026
7c7f99c
wire monad arbiter adapter and remove second conn pool
battermann Jul 9, 2026
124d305
dependency clean up
battermann Jul 9, 2026
ed24355
fix flake.lock
battermann Jul 9, 2026
12d04a8
adapt adapter
battermann Jul 9, 2026
d4d78b4
adress PR findings
battermann Jul 10, 2026
471b651
dedup keys for jobs
battermann Jul 10, 2026
80c2465
reject non positive poll interval
battermann Jul 10, 2026
4667315
Initialize scheduled-job Arbiter schema before startup
battermann Jul 10, 2026
b717dfd
remove redundant db query
battermann Jul 10, 2026
864f421
coordinate arbiter migration runs via advisory locks
battermann Jul 13, 2026
de85b43
linter
battermann Jul 13, 2026
aeba23a
removed unused parameter
battermann Jul 13, 2026
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
12 changes: 12 additions & 0 deletions cabal.project
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
repository hackage.haskell.org
url: https://hackage.haskell.org/
index-state: 2023-10-03T15:17:00Z

packages:
integration
, libs/bilge/
Expand Down Expand Up @@ -96,3 +97,14 @@ package wire-message-proto-lens
flags: +nix-dev-env
package types-common-journal
flags: +nix-dev-env

source-repository-package
type: git
location: https://github.com/velveteer/arbiter.git
tag: 96097411a0480e182d0cbce819c45023fe8aeec7
subdir:
arbiter-core
arbiter-hasql
arbiter-migrations
arbiter-worker
arbiter-test-common
1 change: 1 addition & 0 deletions changelog.d/0-release-notes/WPB-26489
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Background-worker scheduled jobs now run through Arbiter. The background-worker configuration includes `scheduledJobs.pollInterval`, which controls how often the scheduler wakes up to look for due jobs.
1 change: 1 addition & 0 deletions changelog.d/2-features/WPB-26489
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Introduce Arbiter-backed schedulable background jobs, migrate meetings cleanup to the new job runner, and add the initial adminless reminder and deletion jobs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure whether it's a feature, nor it's necessary with the other file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Better more information than less, I would say.

Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ data:
backgroundJobs:
{{ toYaml . | indent 6 }}
{{- end }}
scheduledJobs:
# Arbiter dispatcher poll interval for all scheduled jobs.
# Lower values reduce discovery latency, but increase DB polling.
pollInterval: {{ .scheduledJobs.pollInterval }}
{{- with .meetingsCleanup }}
meetingsCleanup:
{{ toYaml . | indent 6 }}
Expand Down
6 changes: 6 additions & 0 deletions charts/wire-server/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,12 @@ background-worker:
# Total attempts, including the first try
maxAttempts: 3

# Scheduled-jobs dispatcher configuration.
scheduledJobs:
# Arbiter dispatcher poll interval for all scheduled jobs.
# Lower values reduce discovery latency, but increase DB polling.
pollInterval: 5s

# Meetings cleanup configuration
meetingsCleanup:
# Delete meetings older than this many hours (48 hours = 2 days)
Expand Down
1 change: 1 addition & 0 deletions docs/src/developer/reference/config-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -2235,3 +2235,4 @@ Notes
- The `migrate...` flags control the corresponding PostgreSQL backfill jobs for the current migration settings; leave them `false` for new installs and after migration.
- `concurrency`, `jobTimeout`, and `maxAttempts` control parallelism and retry behavior of the consumer.
- `brig` and `gundeck` endpoints default to in-cluster services; override via `background-worker.config.brig` and `.gundeck` if your service DNS/ports differ.
- `scheduledJobs.pollInterval` controls how often the background worker wakes up to check Arbiter for due jobs.
18 changes: 18 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@
url = "github:wireapp/hasql-resource-pool?rev=5b5d3df0fff81801986a0110acae5420215f01c5";
flake = false;
};

arbiter = {
url = "github:velveteer/arbiter?rev=96097411a0480e182d0cbce819c45023fe8aeec7";
flake = false;
};
};

outputs = inputs@{ nixpkgs, nixpkgs_24_11, flake-utils, tom-bombadil, sbomnix, ... }:
Expand Down
3 changes: 3 additions & 0 deletions integration/test/Notifications.hs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ isConvCreateNotifNotSelf n =
isConvDeleteNotif :: (HasCallStack, MakesValue a) => a -> App Bool
isConvDeleteNotif n = fieldEquals n "payload.0.type" "conversation.delete"

isConvAdminlessReminderNotif :: (HasCallStack, MakesValue a) => a -> App Bool
isConvAdminlessReminderNotif n = fieldEquals n "payload.0.type" "conversation.adminless-reminder"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

More a note rather than a concern for this PR: we should introduce helpers to assert payloads' type


notifTypeIsEqual :: (HasCallStack, MakesValue a) => String -> a -> App Bool
notifTypeIsEqual typ n = nPayload n %. "type" `isEqual` typ

Expand Down
47 changes: 40 additions & 7 deletions integration/test/Test/AdminlessGroups.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ module Test.AdminlessGroups where
import API.Brig
import API.Galley
import API.GalleyInternal hiding (getConversation)
import qualified API.GalleyInternal as GalleyI
import MLS.Util
import Notifications
import SetupHelpers hiding (deleteUser)
import Testlib.Prelude

Expand Down Expand Up @@ -100,19 +102,50 @@ testOnLastAdminLeaveNoEligibleMembersExist = do
(alice, tid, _) <- createTeam OwnDomain 1

setTeamFeatureLockStatus alice tid "preventAdminlessGroups" "unlocked"
patchTeamFeature OwnDomain tid "preventAdminlessGroups" (object ["status" .= "enabled"]) >>= assertSuccess
patchTeamFeature
OwnDomain
tid
"preventAdminlessGroups"
( object
[ "status" .= "enabled",
"config"
.= object
-- The reminders are due early (+1s and +2s), while deletion is
-- later (+10s). This gives Arbiter's 1s polling and serial
-- grouped-job processing enough room to emit both reminders
-- before the conversation is deleted.
[ "deletionTimeoutDuration" .= "10s",
"reminderTimeoutDurations" .= ["9s", "8s"],
"promotionStrategy" .= "random"
]
]
)
>>= assertSuccess

let newApp :: NewApp
newApp = def {name = "adminless-reminder-app", description = "non-eligible reminder recipient"}
app <- bindResponse (createApp alice tid newApp) $ \resp -> do
resp.status `shouldMatchInt` 200
resp.json %. "user"

alice1 <- createMLSClient def alice
void $ uploadNewKeyPackage def alice1
[alice1, app1] <- traverse (createMLSClient def) [alice, app]
traverse_ (uploadNewKeyPackage def) [alice1, app1]

conv <- postConversation alice defMLS {team = Just tid} >>= getJSON 201
convId <- objConvId conv
createGroup def alice1 convId
void $ createAddCommit alice1 convId [] >>= sendAndConsumeCommitBundle
void $ createAddCommit alice1 convId [app] >>= sendAndConsumeCommitBundle

-- alice leaves the conversation, no error, group will be marked for deletion
bindResponse (removeMember alice conv alice) $ \resp -> do
resp.status `shouldMatchInt` 200
withWebSockets [app] $ \[wsApp] -> do
-- alice leaves the conversation, no error, group will be marked for deletion
bindResponse (removeMember alice conv alice) $ \resp -> do
resp.status `shouldMatchInt` 200

void $ awaitNMatches 2 isConvAdminlessReminderNotif wsApp

retryT $ do
bindResponse (GalleyI.getConversation conv) $ \resp -> do
resp.status `shouldMatchInt` 404

testOnLastAdminLeaveFeatureDisabled :: (HasCallStack) => App ()
testOnLastAdminLeaveFeatureDisabled = do
Expand Down
14 changes: 13 additions & 1 deletion libs/extended/src/Hasql/Pool/Extended.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
module Hasql.Pool.Extended where

import Data.Aeson
import Data.Map as Map
import Data.Map qualified as Map
import Data.Misc
import Data.Secret (SecretText, secretText)
import Hasql.Connection qualified
import Hasql.Connection.Settings qualified as HasqlConnSettings
import Hasql.Pool qualified as HasqlPool
Expand Down Expand Up @@ -47,6 +48,17 @@ instance FromJSON PoolConfig where
<*> o .: "acquisitionTimeout"
<*> o .: "idlenessTimeout"

-- | Render a PostgreSQL connection string in libpq key-value format.
--
-- Passwords from the optional secret file are inserted into the key-value map
-- before rendering. The result is wrapped because it may contain the password.
postgresqlConnectionStringWithPassword :: Map Text Text -> Maybe FilePathSecrets -> IO SecretText
postgresqlConnectionStringWithPassword pgConfig mFpSecrets = do
mPw <- for mFpSecrets initCredentials
let pgConfig' = maybe pgConfig (\pw -> Map.insert "password" pw pgConfig) mPw
pure . secretText . PostgresqlConnectionString.toKeyValueString $
PostgresqlConnectionString.fromKeyValueParams pgConfig'

data HasqlPoolMetrics = HasqlPoolMetrics
{ readyForUseGauge :: Gauge,
inUseGauge :: Gauge,
Expand Down
43 changes: 43 additions & 0 deletions libs/types-common/src/Data/Secret.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2026 Wire Swiss GmbH <[email protected]>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option)
-- any later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module Data.Secret
( SecretText,
secretText,
revealSecretText,
)
where

import Imports

-- | Text that may contain credentials or other sensitive material.
--
-- The constructor is intentionally opaque. 'revealSecretText' should only be
-- used at the narrow boundary where an external API requires the plaintext
-- representation.
newtype SecretText = SecretText Text

instance Show SecretText where
show _ = "<redacted>"

-- | Wrap sensitive text without exposing it through the public constructor.
secretText :: Text -> SecretText
secretText = SecretText

-- | Reveal sensitive text for an API that explicitly requires plaintext.
revealSecretText :: SecretText -> Text
revealSecretText (SecretText value) = value
4 changes: 3 additions & 1 deletion libs/types-common/test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ where
import Imports
import Test.Data.Mailbox qualified as Mailbox
import Test.Data.PEMKeys qualified as PEMKeys
import Test.Data.Secret qualified as Secret
import Test.Domain qualified as Domain
import Test.Handle qualified as Handle
import Test.Properties qualified as Properties
Expand All @@ -41,5 +42,6 @@ main =
Handle.tests,
Qualified.tests,
PEMKeys.tests,
Mailbox.tests
Mailbox.tests,
Secret.tests
]
31 changes: 31 additions & 0 deletions libs/types-common/test/Test/Data/Secret.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2026 Wire Swiss GmbH <[email protected]>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option)
-- any later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module Test.Data.Secret (tests) where

import Data.Secret
import Imports
import Test.Tasty
import Test.Tasty.HUnit

tests :: TestTree
tests =
testGroup
"SecretText"
[ testCase "does not expose its value through Show" $
show (secretText "database-password") @?= "<redacted>"
]
2 changes: 2 additions & 0 deletions libs/types-common/types-common.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ library
Data.Qualified
Data.Range
Data.RetryAfter
Data.Secret
Data.SizedHashMap
Data.Text.Ascii
Data.UUID.Tagged
Expand Down Expand Up @@ -160,6 +161,7 @@ test-suite types-common-tests
Paths_types_common
Test.Data.Mailbox
Test.Data.PEMKeys
Test.Data.Secret
Test.Domain
Test.Handle
Test.Properties
Expand Down
Loading
Loading