Merge branch stable-3.14 into master

Change-Id: I6892d63055c2d7008fca06b9e722a46ad7e3a972
diff --git a/.bazelversion b/.bazelversion
new file mode 100644
index 0000000..47da986
--- /dev/null
+++ b/.bazelversion
@@ -0,0 +1 @@
+9.1.0
diff --git a/BUILD b/BUILD
index da759b9..581e688 100644
--- a/BUILD
+++ b/BUILD
@@ -1,20 +1,9 @@
 load(
     "@com_googlesource_gerrit_bazlets//:gerrit_plugin.bzl",
     "gerrit_plugin",
+    "gerrit_plugin_dependency_tests",
     "gerrit_plugin_tests",
 )
-load(
-    "@com_googlesource_gerrit_bazlets//tools:in_gerrit_tree.bzl",
-    "in_gerrit_tree_enabled",
-)
-load(
-    "@com_googlesource_gerrit_bazlets//tools:runtime_jars_allowlist.bzl",
-    "runtime_jars_allowlist_test",
-)
-load(
-    "@com_googlesource_gerrit_bazlets//tools:runtime_jars_overlap.bzl",
-    "runtime_jars_overlap_test",
-)
 load("@rules_java//java:defs.bzl", "java_library")
 
 gerrit_plugin(
@@ -23,6 +12,7 @@
     manifest_entries = [
         "Gerrit-PluginName: high-availability",
         "Gerrit-Module: com.ericsson.gerrit.plugins.highavailability.Module",
+        "Gerrit-ApiModule: com.ericsson.gerrit.plugins.highavailability.ApiModule",
         "Gerrit-HttpModule: com.ericsson.gerrit.plugins.highavailability.HttpModule",
         "Gerrit-InitStep: com.ericsson.gerrit.plugins.highavailability.Setup",
         "Gerrit-ReloadMode: restart",
@@ -71,17 +61,4 @@
     ],
 )
 
-runtime_jars_allowlist_test(
-    name = "check_high-availability_third_party_runtime_jars",
-    allowlist = ":high-availability_third_party_runtime_jars.allowlist.txt",
-    hint = "plugins/high-availability:check_high-availability_third_party_runtime_jars_manifest",
-    target = ":high-availability__plugin",
-)
-
-runtime_jars_overlap_test(
-    name = "high-availability_no_overlap_with_gerrit",
-    against = "//:headless.war.jars.txt",
-    hint = "Exclude overlaps via maven.install(excluded_artifacts=[...]) and re-run this test.",
-    target = ":high-availability__plugin",
-    target_compatible_with = in_gerrit_tree_enabled(),
-)
+gerrit_plugin_dependency_tests(plugin = "high-availability")
diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock
index 8c48193..0ff8811 100644
--- a/MODULE.bazel.lock
+++ b/MODULE.bazel.lock
@@ -1,5 +1,5 @@
 {
-  "lockFileVersion": 13,
+  "lockFileVersion": 26,
   "registryFileHashes": {
     "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
     "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
@@ -11,15 +11,21 @@
     "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed",
     "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16",
     "https://bcr.bazel.build/modules/abseil-cpp/20250127.0/MODULE.bazel": "d1086e248cda6576862b4b3fe9ad76a214e08c189af5b42557a6e1888812c5d5",
-    "https://bcr.bazel.build/modules/abseil-cpp/20250127.0/source.json": "1b996859f840d8efc7c720efc61dcf2a84b1261cb3974cbbe9b6666ebf567775",
+    "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1",
+    "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215",
+    "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2",
+    "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c",
     "https://bcr.bazel.build/modules/abseil-py/2.1.0/MODULE.bazel": "5ebe5bf853769c65707e5c28f216798f7a4b1042015e6a36e6d03094d94bec8a",
     "https://bcr.bazel.build/modules/abseil-py/2.1.0/source.json": "0e8fc4f088ce07099c1cd6594c20c7ddbb48b4b3c0849b7d94ba94be88ff042b",
     "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896",
     "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85",
-    "https://bcr.bazel.build/modules/apple_support/1.15.1/source.json": "517f2b77430084c541bc9be2db63fdcbb7102938c5f64c17ee60ffda2e5cf07b",
-    "https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef",
+    "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e",
+    "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1",
+    "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4",
+    "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d",
     "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b",
     "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd",
+    "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101",
     "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
     "https://bcr.bazel.build/modules/bazel_features/1.13.0/MODULE.bazel": "c14c33c7c3c730612bdbe14ebbb5e61936b6f11322ea95a6e91cd1ba962f94df",
     "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d",
@@ -28,11 +34,14 @@
     "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
     "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
     "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a",
+    "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65",
     "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d",
     "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9",
     "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
-    "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc",
+    "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6",
     "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
+    "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080",
+    "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309",
     "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a",
     "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
     "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e",
@@ -45,6 +54,7 @@
     "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917",
     "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d",
     "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b",
+    "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6",
     "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67",
     "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/MODULE.bazel": "72997b29dfd95c3fa0d0c48322d05590418edef451f8db8db5509c57875fb4b7",
     "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/source.json": "7ad77c1e8c1b84222d9b3f3cae016a76639435744c19330b0b37c0a3c9da7dc0",
@@ -53,8 +63,8 @@
     "https://bcr.bazel.build/modules/bazel_worker_api/0.0.4/source.json": "d353c410d47a8b65d09fa98e83d57ebec257a2c2b9c6e42d6fda1cb25e5464a5",
     "https://bcr.bazel.build/modules/bazel_worker_java/0.0.4/MODULE.bazel": "82494a01018bb7ef06d4a17ec4cd7a758721f10eb8b6c820a818e70d669500db",
     "https://bcr.bazel.build/modules/bazel_worker_java/0.0.4/source.json": "a2d30458fd86cf022c2b6331e652526fa08e17573b2f5034a9dbcacdf9c2583c",
-    "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84",
-    "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8",
+    "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f",
+    "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd",
     "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8",
     "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350",
     "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a",
@@ -66,7 +76,8 @@
     "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6",
     "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
     "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108",
-    "https://bcr.bazel.build/modules/googletest/1.15.2/source.json": "dbdda654dcb3a0d7a8bc5d0ac5fc7e150b58c2a986025ae5bc634bb2cb61f470",
+    "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46",
+    "https://bcr.bazel.build/modules/googletest/1.17.0/source.json": "38e4454b25fc30f15439c0378e57909ab1fd0a443158aa35aec685da727cd713",
     "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075",
     "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0",
     "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000",
@@ -77,13 +88,14 @@
     "https://bcr.bazel.build/modules/package_metadata/0.0.7/source.json": "50639625e937b56115012674c797cca7a05a96b4878c87d803c13dc2b31de8a0",
     "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5",
     "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f",
-    "https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29",
     "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
     "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
     "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
     "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814",
     "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d",
     "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc",
+    "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580",
+    "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96",
     "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7",
     "https://bcr.bazel.build/modules/protobuf/23.1/MODULE.bazel": "88b393b3eb4101d18129e5db51847cd40a5517a53e81216144a8c32dfeeca52a",
     "https://bcr.bazel.build/modules/protobuf/24.4/MODULE.bazel": "7bc7ce5f2abf36b3b7b7c8218d3acdebb9426aeb35c2257c96445756f970eb12",
@@ -92,11 +104,14 @@
     "https://bcr.bazel.build/modules/protobuf/27.2/MODULE.bazel": "32450b50673882e4c8c3d10a83f3bc82161b213ed2f80d17e38bece8f165c295",
     "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df",
     "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92",
+    "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95",
     "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0",
     "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573",
     "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858",
     "https://bcr.bazel.build/modules/protobuf/31.1/MODULE.bazel": "379a389bb330b7b8c1cdf331cc90bf3e13de5614799b3b52cdb7c6f389f6b38e",
-    "https://bcr.bazel.build/modules/protobuf/31.1/source.json": "25af5d0219da0c0fc4d1191a24ce438e6ca7f49d2e1a94f354efeba6ef10426f",
+    "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d",
+    "https://bcr.bazel.build/modules/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42",
+    "https://bcr.bazel.build/modules/protobuf/33.4/source.json": "555f8686b4c7d6b5ba731fbea13bf656b4bfd9a7ff629c1d9d3f6e1d6155de79",
     "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e",
     "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34",
     "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680",
@@ -108,7 +123,8 @@
     "https://bcr.bazel.build/modules/rules_android/0.6.6/MODULE.bazel": "b0fb569752aab65ab1a9db0a8f6cfaf5aa1754965e17e95dcf0e4d88e192a68d",
     "https://bcr.bazel.build/modules/rules_android/0.6.6/source.json": "a9d8dc2d5a102dc03269a94acc886a4cab82cdcb9ccbc77b0f665d6d17a6ae09",
     "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a",
-    "https://bcr.bazel.build/modules/rules_apple/3.16.0/source.json": "d8b5fe461272018cc07cfafce11fe369c7525330804c37eec5a82f84cd475366",
+    "https://bcr.bazel.build/modules/rules_apple/4.1.0/MODULE.bazel": "76e10fd4a48038d3fc7c5dc6e63b7063bbf5304a2e3bd42edda6ec660eebea68",
+    "https://bcr.bazel.build/modules/rules_apple/4.1.0/source.json": "8ee81e1708756f81b343a5eb2b2f0b953f1d25c4ab3d4a68dc02754872e80715",
     "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647",
     "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002",
     "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191",
@@ -120,8 +136,13 @@
     "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
     "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
     "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
+    "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0",
     "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8",
-    "https://bcr.bazel.build/modules/rules_cc/0.1.5/source.json": "4bb4fed7f5499775d495739f785a5494a1f854645fa1bac5de131264f5acdf01",
+    "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c",
+    "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37",
+    "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84",
+    "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07",
+    "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642",
     "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
     "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
     "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8",
@@ -140,13 +161,13 @@
     "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab",
     "https://bcr.bazel.build/modules/rules_java/7.4.0/MODULE.bazel": "a592852f8a3dd539e82ee6542013bf2cadfc4c6946be8941e189d224500a8934",
     "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe",
-    "https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1",
     "https://bcr.bazel.build/modules/rules_java/8.13.0/MODULE.bazel": "0444ebf737d144cf2bb2ccb368e7f1cce735264285f2a3711785827c1686625e",
-    "https://bcr.bazel.build/modules/rules_java/8.13.0/source.json": "4605c0f676b87dd9d1fabd4d743b71f04d97503bd1a79aad53f87399fb5396de",
     "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017",
     "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939",
     "https://bcr.bazel.build/modules/rules_java/8.6.0/MODULE.bazel": "9c064c434606d75a086f15ade5edb514308cccd1544c2b2a89bbac4310e41c71",
     "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2",
+    "https://bcr.bazel.build/modules/rules_java/9.1.0/MODULE.bazel": "ee63f27e36a3fada80342869361182f120a9819c74320e8e65b1e04ba0cd7a9d",
+    "https://bcr.bazel.build/modules/rules_java/9.1.0/source.json": "da589573c1dee2c9ac4a568b301269a2e8191110ff0345c1a959fa7ea6c4dfd6",
     "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
     "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909",
     "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036",
@@ -173,9 +194,9 @@
     "https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f",
     "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73",
     "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2",
-    "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1",
+    "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96",
+    "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e",
     "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f",
-    "https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7",
     "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300",
     "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382",
     "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed",
@@ -185,6 +206,9 @@
     "https://bcr.bazel.build/modules/rules_python/0.37.2/MODULE.bazel": "b5ffde91410745750b6c13be1c5dc4555ef5bc50562af4a89fd77807fdde626a",
     "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
     "https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43",
+    "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13",
+    "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6",
+    "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8",
     "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8",
     "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32",
     "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/MODULE.bazel": "d44fec647d0aeb67b9f3b980cf68ba634976f3ae7ccd6c07d790b59b87a4f251",
@@ -193,10 +217,13 @@
     "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c",
     "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b",
     "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592",
-    "https://bcr.bazel.build/modules/rules_shell/0.4.1/source.json": "4757bd277fe1567763991c4425b483477bb82e35e777a56fd846eb5cceda324a",
+    "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b",
+    "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c",
     "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca",
     "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046",
-    "https://bcr.bazel.build/modules/rules_swift/2.1.1/source.json": "40fc69dfaac64deddbb75bd99cdac55f4427d9ca0afbe408576a65428427a186",
+    "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd",
+    "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400",
+    "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66",
     "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
     "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c",
     "https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd",
@@ -204,254 +231,99 @@
     "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5",
     "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
     "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91",
-    "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb",
+    "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f",
+    "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b",
     "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
     "https://bcr.bazel.build/modules/upb/0.0.0-20230516-61a97ef/MODULE.bazel": "c0df5e35ad55e264160417fd0875932ee3c9dda63d9fccace35ac62f45e1b6f9",
     "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
     "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27",
-    "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79",
     "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
     "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806",
     "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198"
   },
   "selectedYankedVersions": {},
   "moduleExtensions": {
-    "@@apple_support~//crosstool:setup.bzl%apple_cc_configure_extension": {
+    "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": {
       "general": {
-        "bzlTransitiveDigest": "ltCGFbl/LQQZXn/LEMXfKX7pGwyqNiOCHcmiQW0tmjM=",
-        "usagesDigest": "eXsWd8YDJDt7+F94YtjKuTAIRMWBi9JLS8hUiqynRIY=",
-        "recordedFileInputs": {},
-        "recordedDirentsInputs": {},
-        "envVariables": {},
-        "generatedRepoSpecs": {
-          "local_config_apple_cc_toolchains": {
-            "bzlFile": "@@apple_support~//crosstool:setup.bzl",
-            "ruleClassName": "_apple_cc_autoconf_toolchains",
-            "attributes": {}
-          },
-          "local_config_apple_cc": {
-            "bzlFile": "@@apple_support~//crosstool:setup.bzl",
-            "ruleClassName": "_apple_cc_autoconf",
-            "attributes": {}
-          }
-        },
-        "recordedRepoMappingEntries": [
-          [
-            "apple_support~",
-            "bazel_tools",
-            "bazel_tools"
-          ]
-        ]
-      }
-    },
-    "@@pybind11_bazel~//:internal_configure.bzl%internal_configure_extension": {
-      "general": {
-        "bzlTransitiveDigest": "CyAKLVVonohnkTSqg9II/HA7M49sOlnMkgMHL3CmDuc=",
-        "usagesDigest": "mFrTHX5eCiNU/OIIGVHH3cOILY9Zmjqk8RQYv8o6Thk=",
-        "recordedFileInputs": {
-          "@@pybind11_bazel~//MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34"
-        },
-        "recordedDirentsInputs": {},
-        "envVariables": {},
+        "bzlTransitiveDigest": "b+RP7Sgl8KN0VHamrgTqzGLuYPcQ/Mo4ptNkkHUIIlA=",
+        "usagesDigest": "D1r3lfzMuUBFxgG8V6o0bQTLMk3GkaGOaPzw53wrwyw=",
+        "recordedInputs": [
+          "REPO_MAPPING:pybind11_bazel+,bazel_tools bazel_tools",
+          "FILE:@@pybind11_bazel+//MODULE.bazel e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34"
+        ],
         "generatedRepoSpecs": {
           "pybind11": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
-              "build_file": "@@pybind11_bazel~//:pybind11-BUILD.bazel",
+              "build_file": "@@pybind11_bazel+//:pybind11-BUILD.bazel",
               "strip_prefix": "pybind11-2.12.0",
               "urls": [
                 "https://github.com/pybind/pybind11/archive/v2.12.0.zip"
               ]
             }
           }
-        },
-        "recordedRepoMappingEntries": [
-          [
-            "pybind11_bazel~",
-            "bazel_tools",
-            "bazel_tools"
-          ]
-        ]
+        }
       }
     },
-    "@@rules_android~//bzlmod_extensions:apksig.bzl%apksig_extension": {
+    "@@rules_android+//bzlmod_extensions:apksig.bzl%apksig_extension": {
       "general": {
-        "bzlTransitiveDigest": "+fNvC5PNWx1aA64JjkW33+1zGbG9xgfi70xQhxbglhY=",
-        "usagesDigest": "TaGkNxVMBZxl1ANdUNPmIjKrA33bO+BpI06yTlpHdos=",
-        "recordedFileInputs": {},
-        "recordedDirentsInputs": {},
-        "envVariables": {},
+        "bzlTransitiveDigest": "IiT2UgJGnHaKiyP2A1yh3U/QWN4W9g/Byolrm78hC/s=",
+        "usagesDigest": "xq6OVkELeJvOgYo3oY/sUBsGFbcqdV+9BYiNgSPV/po=",
+        "recordedInputs": [
+          "REPO_MAPPING:rules_android+,bazel_tools bazel_tools"
+        ],
         "generatedRepoSpecs": {
           "apksig": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://android.googlesource.com/platform/tools/apksig/+archive/24e3075e68ebe17c0b529bb24bfda819db5e2f3b.tar.gz",
-              "build_file": "@@rules_android~//bzlmod_extensions:apksig.BUILD"
+              "build_file": "@@rules_android+//bzlmod_extensions:apksig.BUILD"
             }
           }
-        },
-        "recordedRepoMappingEntries": [
-          [
-            "rules_android~",
-            "bazel_tools",
-            "bazel_tools"
-          ]
-        ]
+        }
       }
     },
-    "@@rules_android~//bzlmod_extensions:com_android_dex.bzl%com_android_dex_extension": {
+    "@@rules_android+//bzlmod_extensions:com_android_dex.bzl%com_android_dex_extension": {
       "general": {
-        "bzlTransitiveDigest": "/KJvxHLYhvSLJihEHN0e5Uzdd7OOMvpFb0BFUl1RUTs=",
-        "usagesDigest": "fh77SU1OUlWN5ivrGpY93+t0MipuVj89/MMUgfTpSa8=",
-        "recordedFileInputs": {},
-        "recordedDirentsInputs": {},
-        "envVariables": {},
+        "bzlTransitiveDigest": "toD4gOZwGNfBEM+xec6eRi3BM2Z9EZ4oQrORZQH3sNY=",
+        "usagesDigest": "toF8IFMu98H/VU2p1sfVC5fVXVYJunpbbmtM6tOsQXY=",
+        "recordedInputs": [
+          "REPO_MAPPING:rules_android+,bazel_tools bazel_tools"
+        ],
         "generatedRepoSpecs": {
           "com_android_dex": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://android.googlesource.com/platform/dalvik/+archive/5a81c499a569731e2395f7c8d13c0e0d4e17a2b6.tar.gz",
-              "build_file": "@@rules_android~//bzlmod_extensions:com_android_dex.BUILD"
+              "build_file": "@@rules_android+//bzlmod_extensions:com_android_dex.BUILD"
             }
           }
-        },
-        "recordedRepoMappingEntries": [
-          [
-            "rules_android~",
-            "bazel_tools",
-            "bazel_tools"
-          ]
-        ]
+        }
       }
     },
-    "@@rules_android~//rules/android_sdk_repository:rule.bzl%android_sdk_repository_extension": {
+    "@@rules_android+//rules/android_sdk_repository:rule.bzl%android_sdk_repository_extension": {
       "general": {
         "bzlTransitiveDigest": "NAy+0M15JNVEBb8Tny6t7j3lKqTnsAMjoBB6LJ+C370=",
-        "usagesDigest": "weZLGEpNa+fTJZ9CEljWxpN3/kuPzj/ULgebzOt2h4g=",
-        "recordedFileInputs": {},
-        "recordedDirentsInputs": {},
-        "envVariables": {},
+        "usagesDigest": "g9Ur6X6qhf9a8MmY9qXU/jFjkyk/aZVBegI0yVMF0z4=",
+        "recordedInputs": [],
         "generatedRepoSpecs": {
           "androidsdk": {
-            "bzlFile": "@@rules_android~//rules/android_sdk_repository:rule.bzl",
-            "ruleClassName": "_android_sdk_repository",
+            "repoRuleId": "@@rules_android+//rules/android_sdk_repository:rule.bzl%_android_sdk_repository",
             "attributes": {}
           }
-        },
-        "recordedRepoMappingEntries": []
+        }
       }
     },
-    "@@rules_apple~//apple:apple.bzl%provisioning_profile_repository_extension": {
+    "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
       "general": {
-        "bzlTransitiveDigest": "69pHX7bLYS/DuYEt2ts3jHV2ut8C5uih4oI+b/tjJZk=",
-        "usagesDigest": "cLx5XGjlbSDOpyA053y/jlr4GcaXFVTeHQic6eyG38E=",
-        "recordedFileInputs": {},
-        "recordedDirentsInputs": {},
-        "envVariables": {},
-        "generatedRepoSpecs": {
-          "local_provisioning_profiles": {
-            "bzlFile": "@@rules_apple~//apple/internal:local_provisioning_profiles.bzl",
-            "ruleClassName": "provisioning_profile_repository",
-            "attributes": {}
-          }
-        },
-        "recordedRepoMappingEntries": [
-          [
-            "apple_support~",
-            "bazel_skylib",
-            "bazel_skylib~"
-          ],
-          [
-            "rules_apple~",
-            "bazel_skylib",
-            "bazel_skylib~"
-          ],
-          [
-            "rules_apple~",
-            "bazel_tools",
-            "bazel_tools"
-          ],
-          [
-            "rules_apple~",
-            "build_bazel_apple_support",
-            "apple_support~"
-          ],
-          [
-            "rules_apple~",
-            "build_bazel_rules_swift",
-            "rules_swift~"
-          ],
-          [
-            "rules_swift~",
-            "bazel_skylib",
-            "bazel_skylib~"
-          ],
-          [
-            "rules_swift~",
-            "bazel_tools",
-            "bazel_tools"
-          ],
-          [
-            "rules_swift~",
-            "build_bazel_apple_support",
-            "apple_support~"
-          ],
-          [
-            "rules_swift~",
-            "build_bazel_rules_swift",
-            "rules_swift~"
-          ],
-          [
-            "rules_swift~",
-            "build_bazel_rules_swift_local_config",
-            "rules_swift~~non_module_deps~build_bazel_rules_swift_local_config"
-          ]
-        ]
-      }
-    },
-    "@@rules_apple~//apple:extensions.bzl%non_module_deps": {
-      "general": {
-        "bzlTransitiveDigest": "LJ3bhNqfW9y6MZFgtKQoPJyUMg/aEZ8H9QXSzK1VOBk=",
-        "usagesDigest": "5FfEPy/Z0Y2V9RbdJRDabgADFdvlfIIrps/5XYOnMNI=",
-        "recordedFileInputs": {},
-        "recordedDirentsInputs": {},
-        "envVariables": {},
-        "generatedRepoSpecs": {
-          "xctestrunner": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
-            "attributes": {
-              "urls": [
-                "https://github.com/google/xctestrunner/archive/b7698df3d435b6491b4b4c0f9fc7a63fbed5e3a6.tar.gz"
-              ],
-              "strip_prefix": "xctestrunner-b7698df3d435b6491b4b4c0f9fc7a63fbed5e3a6",
-              "sha256": "ae3a063c985a8633cb7eb566db21656f8db8eb9a0edb8c182312c7f0db53730d"
-            }
-          }
-        },
-        "recordedRepoMappingEntries": [
-          [
-            "rules_apple~",
-            "bazel_tools",
-            "bazel_tools"
-          ]
-        ]
-      }
-    },
-    "@@rules_kotlin~//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
-      "general": {
-        "bzlTransitiveDigest": "BSndOxBXlbe5qZf1NRxf828dpEK6tM429pCnaFoUFYs=",
-        "usagesDigest": "Dg5Nih77FaLAipQB7wgwsAy553a3po0eNGQVTZKgCM8=",
-        "recordedFileInputs": {},
-        "recordedDirentsInputs": {},
-        "envVariables": {},
+        "bzlTransitiveDigest": "1r52/tc/bAGKspbAAb3WPDdgb3K3Yfb4gZibMxvAaIs=",
+        "usagesDigest": "qTwqmKKUfWcPdvM0waG+CPWrxsbeAWVeUxavm7tEk9E=",
+        "recordedInputs": [
+          "REPO_MAPPING:rules_kotlin+,bazel_tools bazel_tools"
+        ],
         "generatedRepoSpecs": {
           "com_github_jetbrains_kotlin_git": {
-            "bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:compiler.bzl",
-            "ruleClassName": "kotlin_compiler_git_repository",
+            "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository",
             "attributes": {
               "urls": [
                 "https://github.com/JetBrains/kotlin/releases/download/v2.1.0/kotlin-compiler-2.1.0.zip"
@@ -460,16 +332,14 @@
             }
           },
           "com_github_jetbrains_kotlin": {
-            "bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:compiler.bzl",
-            "ruleClassName": "kotlin_capabilities_repository",
+            "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository",
             "attributes": {
               "git_repository_name": "com_github_jetbrains_kotlin_git",
               "compiler_version": "2.1.0"
             }
           },
           "com_github_google_ksp": {
-            "bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:ksp.bzl",
-            "ruleClassName": "ksp_compiler_plugin_repository",
+            "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository",
             "attributes": {
               "urls": [
                 "https://github.com/google/ksp/releases/download/2.1.0-1.0.28/artifacts.zip"
@@ -479,8 +349,7 @@
             }
           },
           "com_github_pinterest_ktlint": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_file",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file",
             "attributes": {
               "sha256": "a9f923be58fbd32670a17f0b729b1df804af882fa57402165741cb26e5440ca1",
               "urls": [
@@ -490,8 +359,7 @@
             }
           },
           "kotlinx_serialization_core_jvm": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_jar",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar",
             "attributes": {
               "sha256": "29c821a8d4e25cbfe4f2ce96cdd4526f61f8f4e69a135f9612a34a81d93b65f1",
               "urls": [
@@ -500,8 +368,7 @@
             }
           },
           "kotlinx_serialization_json": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_jar",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar",
             "attributes": {
               "sha256": "8c0016890a79ab5980dd520a5ab1a6738023c29aa3b6437c482e0e5fdc06dab1",
               "urls": [
@@ -510,8 +377,7 @@
             }
           },
           "kotlinx_serialization_json_jvm": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_jar",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar",
             "attributes": {
               "sha256": "d3234179bcff1886d53d67c11eca47f7f3cf7b63c349d16965f6db51b7f3dd9a",
               "urls": [
@@ -519,35 +385,41 @@
               ]
             }
           }
-        },
-        "recordedRepoMappingEntries": [
-          [
-            "rules_kotlin~",
-            "bazel_tools",
-            "bazel_tools"
-          ]
-        ]
+        }
       }
     },
-    "@@rules_python~//python/extensions:config.bzl%config": {
+    "@@rules_python+//python/extensions:config.bzl%config": {
       "general": {
-        "bzlTransitiveDigest": "JEJFuaDD7VQV641t2xy3kzc2EBtIxzHUAHwUerkS5RM=",
-        "usagesDigest": "n1K7/nlkMpUURDkWJsNS8Nr0tmxSDkFevx8GXKG82eA=",
-        "recordedFileInputs": {},
-        "recordedDirentsInputs": {},
-        "envVariables": {},
+        "bzlTransitiveDigest": "iibnRYgg8LpcfmH7EAnVwYePC3jsVaJ6Id8XxUjSZps=",
+        "usagesDigest": "ZVSXMAGpD+xzVNPuvF1IoLBkty7TROO0+akMapt1pAg=",
+        "recordedInputs": [
+          "REPO_MAPPING:rules_python+,bazel_tools bazel_tools",
+          "REPO_MAPPING:rules_python+,pypi__build rules_python++config+pypi__build",
+          "REPO_MAPPING:rules_python+,pypi__click rules_python++config+pypi__click",
+          "REPO_MAPPING:rules_python+,pypi__colorama rules_python++config+pypi__colorama",
+          "REPO_MAPPING:rules_python+,pypi__importlib_metadata rules_python++config+pypi__importlib_metadata",
+          "REPO_MAPPING:rules_python+,pypi__installer rules_python++config+pypi__installer",
+          "REPO_MAPPING:rules_python+,pypi__more_itertools rules_python++config+pypi__more_itertools",
+          "REPO_MAPPING:rules_python+,pypi__packaging rules_python++config+pypi__packaging",
+          "REPO_MAPPING:rules_python+,pypi__pep517 rules_python++config+pypi__pep517",
+          "REPO_MAPPING:rules_python+,pypi__pip rules_python++config+pypi__pip",
+          "REPO_MAPPING:rules_python+,pypi__pip_tools rules_python++config+pypi__pip_tools",
+          "REPO_MAPPING:rules_python+,pypi__pyproject_hooks rules_python++config+pypi__pyproject_hooks",
+          "REPO_MAPPING:rules_python+,pypi__setuptools rules_python++config+pypi__setuptools",
+          "REPO_MAPPING:rules_python+,pypi__tomli rules_python++config+pypi__tomli",
+          "REPO_MAPPING:rules_python+,pypi__wheel rules_python++config+pypi__wheel",
+          "REPO_MAPPING:rules_python+,pypi__zipp rules_python++config+pypi__zipp"
+        ],
         "generatedRepoSpecs": {
           "rules_python_internal": {
-            "bzlFile": "@@rules_python~//python/private:internal_config_repo.bzl",
-            "ruleClassName": "internal_config_repo",
+            "repoRuleId": "@@rules_python+//python/private:internal_config_repo.bzl%internal_config_repo",
             "attributes": {
               "transition_setting_generators": {},
               "transition_settings": []
             }
           },
           "pypi__build": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl",
               "sha256": "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4",
@@ -556,8 +428,7 @@
             }
           },
           "pypi__click": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl",
               "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
@@ -566,8 +437,7 @@
             }
           },
           "pypi__colorama": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl",
               "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6",
@@ -576,8 +446,7 @@
             }
           },
           "pypi__importlib_metadata": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl",
               "sha256": "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570",
@@ -586,8 +455,7 @@
             }
           },
           "pypi__installer": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl",
               "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53",
@@ -596,8 +464,7 @@
             }
           },
           "pypi__more_itertools": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl",
               "sha256": "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684",
@@ -606,8 +473,7 @@
             }
           },
           "pypi__packaging": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl",
               "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5",
@@ -616,8 +482,7 @@
             }
           },
           "pypi__pep517": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl",
               "sha256": "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721",
@@ -626,8 +491,7 @@
             }
           },
           "pypi__pip": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl",
               "sha256": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc",
@@ -636,8 +500,7 @@
             }
           },
           "pypi__pip_tools": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl",
               "sha256": "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9",
@@ -646,8 +509,7 @@
             }
           },
           "pypi__pyproject_hooks": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl",
               "sha256": "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2",
@@ -656,8 +518,7 @@
             }
           },
           "pypi__setuptools": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl",
               "sha256": "c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561",
@@ -666,8 +527,7 @@
             }
           },
           "pypi__tomli": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl",
               "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
@@ -676,8 +536,7 @@
             }
           },
           "pypi__wheel": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl",
               "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81",
@@ -686,8 +545,7 @@
             }
           },
           "pypi__zipp": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
+            "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
             "attributes": {
               "url": "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl",
               "sha256": "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e",
@@ -695,109 +553,27 @@
               "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n    name = \"lib\",\n    srcs = glob([\"**/*.py\"]),\n    data = glob([\"**/*\"], exclude=[\n        # These entries include those put into user-installed dependencies by\n        # data_exclude to avoid non-determinism.\n        \"**/*.py\",\n        \"**/*.pyc\",\n        \"**/*.pyc.*\",  # During pyc creation, temp files named *.pyc.NNN are created\n        \"**/*.dist-info/RECORD\",\n        \"BUILD\",\n        \"WORKSPACE\",\n    ]),\n    # This makes this directory a top-level in the python import\n    # search path for anything that depends on this.\n    imports = [\".\"],\n)\n"
             }
           }
-        },
-        "recordedRepoMappingEntries": [
-          [
-            "rules_python~",
-            "bazel_tools",
-            "bazel_tools"
-          ],
-          [
-            "rules_python~",
-            "pypi__build",
-            "rules_python~~config~pypi__build"
-          ],
-          [
-            "rules_python~",
-            "pypi__click",
-            "rules_python~~config~pypi__click"
-          ],
-          [
-            "rules_python~",
-            "pypi__colorama",
-            "rules_python~~config~pypi__colorama"
-          ],
-          [
-            "rules_python~",
-            "pypi__importlib_metadata",
-            "rules_python~~config~pypi__importlib_metadata"
-          ],
-          [
-            "rules_python~",
-            "pypi__installer",
-            "rules_python~~config~pypi__installer"
-          ],
-          [
-            "rules_python~",
-            "pypi__more_itertools",
-            "rules_python~~config~pypi__more_itertools"
-          ],
-          [
-            "rules_python~",
-            "pypi__packaging",
-            "rules_python~~config~pypi__packaging"
-          ],
-          [
-            "rules_python~",
-            "pypi__pep517",
-            "rules_python~~config~pypi__pep517"
-          ],
-          [
-            "rules_python~",
-            "pypi__pip",
-            "rules_python~~config~pypi__pip"
-          ],
-          [
-            "rules_python~",
-            "pypi__pip_tools",
-            "rules_python~~config~pypi__pip_tools"
-          ],
-          [
-            "rules_python~",
-            "pypi__pyproject_hooks",
-            "rules_python~~config~pypi__pyproject_hooks"
-          ],
-          [
-            "rules_python~",
-            "pypi__setuptools",
-            "rules_python~~config~pypi__setuptools"
-          ],
-          [
-            "rules_python~",
-            "pypi__tomli",
-            "rules_python~~config~pypi__tomli"
-          ],
-          [
-            "rules_python~",
-            "pypi__wheel",
-            "rules_python~~config~pypi__wheel"
-          ],
-          [
-            "rules_python~",
-            "pypi__zipp",
-            "rules_python~~config~pypi__zipp"
-          ]
-        ]
+        }
       }
     },
-    "@@rules_python~//python/uv:uv.bzl%uv": {
+    "@@rules_python+//python/uv:uv.bzl%uv": {
       "general": {
-        "bzlTransitiveDigest": "K6zoA/u57+d21o50BApale3BrKDIB1vWM6q455k772s=",
-        "usagesDigest": "PAumpjVacknBm1rrOwOjhYqel5KPCDgZNHNmdVc9/WY=",
-        "recordedFileInputs": {},
-        "recordedDirentsInputs": {},
-        "envVariables": {},
+        "bzlTransitiveDigest": "ijW9KS7qsIY+yBVvJ+Nr1mzwQox09j13DnE3iIwaeTM=",
+        "usagesDigest": "H8dQoNZcoqP+Mu0tHZTi4KHATzvNkM5ePuEqoQdklIU=",
+        "recordedInputs": [
+          "REPO_MAPPING:rules_python+,bazel_tools bazel_tools",
+          "REPO_MAPPING:rules_python+,platforms platforms"
+        ],
         "generatedRepoSpecs": {
           "uv": {
-            "bzlFile": "@@rules_python~//python/uv/private:uv_toolchains_repo.bzl",
-            "ruleClassName": "uv_toolchains_repo",
+            "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo",
             "attributes": {
-              "toolchain_type": "'@@rules_python~//python/uv:uv_toolchain_type'",
+              "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'",
               "toolchain_names": [
                 "none"
               ],
               "toolchain_implementations": {
-                "none": "'@@rules_python~//python:none'"
+                "none": "'@@rules_python+//python:none'"
               },
               "toolchain_compatible_with": {
                 "none": [
@@ -807,192 +583,9 @@
               "toolchain_target_settings": {}
             }
           }
-        },
-        "recordedRepoMappingEntries": [
-          [
-            "rules_python~",
-            "bazel_tools",
-            "bazel_tools"
-          ],
-          [
-            "rules_python~",
-            "platforms",
-            "platforms"
-          ]
-        ]
-      }
-    },
-    "@@rules_swift~//swift:extensions.bzl%non_module_deps": {
-      "general": {
-        "bzlTransitiveDigest": "vhXfCi07IAmWf5hLHcxh5xCtTwvEGrmMkOavEMD5pzs=",
-        "usagesDigest": "/dVEtCLqIAaLXjfRXsJupTS9pTgmYVN/tFdJlGgE5NA=",
-        "recordedFileInputs": {},
-        "recordedDirentsInputs": {},
-        "envVariables": {},
-        "generatedRepoSpecs": {
-          "com_github_apple_swift_protobuf": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
-            "attributes": {
-              "urls": [
-                "https://github.com/apple/swift-protobuf/archive/1.20.2.tar.gz"
-              ],
-              "sha256": "3fb50bd4d293337f202d917b6ada22f9548a0a0aed9d9a4d791e6fbd8a246ebb",
-              "strip_prefix": "swift-protobuf-1.20.2/",
-              "build_file": "@@rules_swift~//third_party:com_github_apple_swift_protobuf/BUILD.overlay"
-            }
-          },
-          "com_github_grpc_grpc_swift": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
-            "attributes": {
-              "urls": [
-                "https://github.com/grpc/grpc-swift/archive/1.16.0.tar.gz"
-              ],
-              "sha256": "58b60431d0064969f9679411264b82e40a217ae6bd34e17096d92cc4e47556a5",
-              "strip_prefix": "grpc-swift-1.16.0/",
-              "build_file": "@@rules_swift~//third_party:com_github_grpc_grpc_swift/BUILD.overlay"
-            }
-          },
-          "com_github_apple_swift_docc_symbolkit": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
-            "attributes": {
-              "urls": [
-                "https://github.com/apple/swift-docc-symbolkit/archive/refs/tags/swift-5.10-RELEASE.tar.gz"
-              ],
-              "sha256": "de1d4b6940468ddb53b89df7aa1a81323b9712775b0e33e8254fa0f6f7469a97",
-              "strip_prefix": "swift-docc-symbolkit-swift-5.10-RELEASE",
-              "build_file": "@@rules_swift~//third_party:com_github_apple_swift_docc_symbolkit/BUILD.overlay"
-            }
-          },
-          "com_github_apple_swift_nio": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
-            "attributes": {
-              "urls": [
-                "https://github.com/apple/swift-nio/archive/2.42.0.tar.gz"
-              ],
-              "sha256": "e3304bc3fb53aea74a3e54bd005ede11f6dc357117d9b1db642d03aea87194a0",
-              "strip_prefix": "swift-nio-2.42.0/",
-              "build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio/BUILD.overlay"
-            }
-          },
-          "com_github_apple_swift_nio_http2": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
-            "attributes": {
-              "urls": [
-                "https://github.com/apple/swift-nio-http2/archive/1.26.0.tar.gz"
-              ],
-              "sha256": "f0edfc9d6a7be1d587e5b403f2d04264bdfae59aac1d74f7d974a9022c6d2b25",
-              "strip_prefix": "swift-nio-http2-1.26.0/",
-              "build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio_http2/BUILD.overlay"
-            }
-          },
-          "com_github_apple_swift_nio_transport_services": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
-            "attributes": {
-              "urls": [
-                "https://github.com/apple/swift-nio-transport-services/archive/1.15.0.tar.gz"
-              ],
-              "sha256": "f3498dafa633751a52b9b7f741f7ac30c42bcbeb3b9edca6d447e0da8e693262",
-              "strip_prefix": "swift-nio-transport-services-1.15.0/",
-              "build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio_transport_services/BUILD.overlay"
-            }
-          },
-          "com_github_apple_swift_nio_extras": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
-            "attributes": {
-              "urls": [
-                "https://github.com/apple/swift-nio-extras/archive/1.4.0.tar.gz"
-              ],
-              "sha256": "4684b52951d9d9937bb3e8ccd6b5daedd777021ef2519ea2f18c4c922843b52b",
-              "strip_prefix": "swift-nio-extras-1.4.0/",
-              "build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio_extras/BUILD.overlay"
-            }
-          },
-          "com_github_apple_swift_log": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
-            "attributes": {
-              "urls": [
-                "https://github.com/apple/swift-log/archive/1.4.4.tar.gz"
-              ],
-              "sha256": "48fe66426c784c0c20031f15dc17faf9f4c9037c192bfac2f643f65cb2321ba0",
-              "strip_prefix": "swift-log-1.4.4/",
-              "build_file": "@@rules_swift~//third_party:com_github_apple_swift_log/BUILD.overlay"
-            }
-          },
-          "com_github_apple_swift_nio_ssl": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
-            "attributes": {
-              "urls": [
-                "https://github.com/apple/swift-nio-ssl/archive/2.23.0.tar.gz"
-              ],
-              "sha256": "4787c63f61dd04d99e498adc3d1a628193387e41efddf8de19b8db04544d016d",
-              "strip_prefix": "swift-nio-ssl-2.23.0/",
-              "build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio_ssl/BUILD.overlay"
-            }
-          },
-          "com_github_apple_swift_collections": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
-            "attributes": {
-              "urls": [
-                "https://github.com/apple/swift-collections/archive/1.0.4.tar.gz"
-              ],
-              "sha256": "d9e4c8a91c60fb9c92a04caccbb10ded42f4cb47b26a212bc6b39cc390a4b096",
-              "strip_prefix": "swift-collections-1.0.4/",
-              "build_file": "@@rules_swift~//third_party:com_github_apple_swift_collections/BUILD.overlay"
-            }
-          },
-          "com_github_apple_swift_atomics": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
-            "attributes": {
-              "urls": [
-                "https://github.com/apple/swift-atomics/archive/1.1.0.tar.gz"
-              ],
-              "sha256": "1bee7f469f7e8dc49f11cfa4da07182fbc79eab000ec2c17bfdce468c5d276fb",
-              "strip_prefix": "swift-atomics-1.1.0/",
-              "build_file": "@@rules_swift~//third_party:com_github_apple_swift_atomics/BUILD.overlay"
-            }
-          },
-          "build_bazel_rules_swift_index_import": {
-            "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
-            "ruleClassName": "http_archive",
-            "attributes": {
-              "build_file": "@@rules_swift~//third_party:build_bazel_rules_swift_index_import/BUILD.overlay",
-              "canonical_id": "index-import-5.8",
-              "urls": [
-                "https://github.com/MobileNativeFoundation/index-import/releases/download/5.8.0.1/index-import.tar.gz"
-              ],
-              "sha256": "28c1ffa39d99e74ed70623899b207b41f79214c498c603915aef55972a851a15"
-            }
-          },
-          "build_bazel_rules_swift_local_config": {
-            "bzlFile": "@@rules_swift~//swift/internal:swift_autoconfiguration.bzl",
-            "ruleClassName": "swift_autoconfiguration",
-            "attributes": {}
-          }
-        },
-        "recordedRepoMappingEntries": [
-          [
-            "rules_swift~",
-            "bazel_tools",
-            "bazel_tools"
-          ],
-          [
-            "rules_swift~",
-            "build_bazel_rules_swift",
-            "rules_swift~"
-          ]
-        ]
+        }
       }
     }
-  }
+  },
+  "facts": {}
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/ApiModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ApiModule.java
new file mode 100644
index 0000000..fbf5ab5
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/ApiModule.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2026 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.CommandProcessor;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.ForwarderCommandsModule;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.inject.AbstractModule;
+
+public class ApiModule extends AbstractModule {
+
+  @Override
+  protected void configure() {
+    DynamicItem.itemOf(binder(), Forwarder.class);
+    DynamicItem.itemOf(binder(), CommandProcessor.class);
+    install(new ForwarderCommandsModule());
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
index f75bdaa..15425d3 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -88,7 +88,8 @@
 
   public enum Transport {
     HTTP,
-    JGROUPS
+    JGROUPS,
+    PROVIDED
   }
 
   @Inject
@@ -515,6 +516,8 @@
     static final String MAX_TRIES_KEY = "maxTries";
     static final String RETRY_INTERVAL_KEY = "retryInterval";
     static final String THREAD_POOL_SIZE_KEY = "threadPoolSize";
+    static final String REUSE_CONNECTION_AFTER_503_KEY = "reuseConnectionAfter503";
+    static final boolean DEFAULT_REUSE_CONNECTION_AFTER_503 = true;
 
     private final String user;
     private final String password;
@@ -523,6 +526,7 @@
     private final int maxTries;
     private final Duration retryInterval;
     private final int threadPoolSize;
+    private final boolean reuseConnectionAfter503;
 
     private Http(Config cfg) {
       user = Strings.nullToEmpty(cfg.getString(HTTP_SECTION, null, USER_KEY));
@@ -532,6 +536,9 @@
       maxTries = getMaxTries(cfg, HTTP_SECTION, MAX_TRIES_KEY, DEFAULT_MAX_TRIES);
       retryInterval = getDuration(cfg, HTTP_SECTION, RETRY_INTERVAL_KEY, DEFAULT_RETRY_INTERVAL);
       threadPoolSize = getInt(cfg, HTTP_SECTION, THREAD_POOL_SIZE_KEY, DEFAULT_THREAD_POOL_SIZE);
+      reuseConnectionAfter503 =
+          cfg.getBoolean(
+              HTTP_SECTION, REUSE_CONNECTION_AFTER_503_KEY, DEFAULT_REUSE_CONNECTION_AFTER_503);
     }
 
     public String user() {
@@ -561,6 +568,10 @@
     public int threadPoolSize() {
       return threadPoolSize;
     }
+
+    public boolean reuseConnectionAfter503() {
+      return reuseConnectionAfter503;
+    }
   }
 
   /** Common parameters to cache, event, index and websession */
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
index 6a90e73..6227787 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
@@ -17,7 +17,11 @@
 import com.ericsson.gerrit.plugins.highavailability.autoreindex.AutoReindexModule;
 import com.ericsson.gerrit.plugins.highavailability.cache.CacheModule;
 import com.ericsson.gerrit.plugins.highavailability.event.EventModule;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwarderModule;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.CommandProcessor;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.CommandProcessorImpl;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.ForwarderCommandsModule;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups.JGroupsForwarderModule;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.RestForwarderModule;
 import com.ericsson.gerrit.plugins.highavailability.index.IndexModule;
@@ -26,6 +30,7 @@
 import com.ericsson.gerrit.plugins.highavailability.peers.PeerInfoModule;
 import com.gerritforge.gerrit.globalrefdb.validation.ProjectDeletedSharedDbCleanup;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.inject.Inject;
@@ -49,16 +54,22 @@
     install(new ForwarderModule());
     install(new FileBasedLockManager.Module());
 
+    DynamicItem.bind(binder(), CommandProcessor.class).to(CommandProcessorImpl.class);
+
     switch (config.main().transport()) {
-      case HTTP:
+      case HTTP -> {
         install(new RestForwarderModule());
         install(new PeerInfoModule(config.peerInfo().strategy()));
-        break;
-      case JGROUPS:
+      }
+      case JGROUPS -> {
+        install(new ForwarderCommandsModule());
         install(new JGroupsForwarderModule());
-        break;
-      default:
-        throw new IllegalArgumentException("Unsupported transport: " + config.main().transport());
+      }
+      case PROVIDED -> {
+        // Bind default no-op implementation
+        // Transport will be provided by another plugin
+        DynamicItem.bind(binder(), Forwarder.class).to(NoForwarder.class);
+      }
     }
 
     if (config.cache().synchronize()) {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/NoForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/NoForwarder.java
new file mode 100644
index 0000000..8e29fcf
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/NoForwarder.java
@@ -0,0 +1,96 @@
+// Copyright (C) 2026 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Project.NameKey;
+import com.google.gerrit.server.events.Event;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * No-Op forwarder implementation
+ *
+ * <p>This is used only as a temporary placeholder to avoid null checks until another plugin
+ * provides a real Forwarder implementation
+ */
+public class NoForwarder implements Forwarder {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  @Override
+  public CompletableFuture<Result> indexAccount(int accountId, IndexEvent indexEvent) {
+    logger.atWarning().log("NoForwarder: indexAccount called with accountId=%d", accountId);
+    return recoverableFailure(EventType.INDEX_ACCOUNT_UPDATE);
+  }
+
+  @Override
+  public CompletableFuture<Result> indexChange(
+      String projectName, int changeId, IndexEvent indexEvent) {
+    return recoverableFailure(EventType.INDEX_CHANGE_UPDATE);
+  }
+
+  @Override
+  public CompletableFuture<Result> batchIndexChange(
+      String projectName, int changeId, IndexEvent indexEvent) {
+    return recoverableFailure(EventType.INDEX_CHANGE_UPDATE_BATCH);
+  }
+
+  @Override
+  public CompletableFuture<Result> deleteChangeFromIndex(
+      String projectName, int changeId, IndexEvent indexEvent) {
+    return recoverableFailure(EventType.INDEX_CHANGE_DELETION);
+  }
+
+  @Override
+  public CompletableFuture<Result> indexGroup(String uuid, IndexEvent indexEvent) {
+    return recoverableFailure(EventType.INDEX_GROUP_UPDATE);
+  }
+
+  @Override
+  public CompletableFuture<Result> indexProject(String projectName, IndexEvent indexEvent) {
+    return recoverableFailure(EventType.INDEX_PROJECT_UPDATE);
+  }
+
+  @Override
+  public CompletableFuture<Result> send(Event event) {
+    return recoverableFailure(EventType.EVENT_SENT);
+  }
+
+  @Override
+  public CompletableFuture<Result> evict(String cacheName, Object key) {
+    return recoverableFailure(EventType.CACHE_EVICTION);
+  }
+
+  @Override
+  public CompletableFuture<Result> addToProjectList(String projectName) {
+    return recoverableFailure(EventType.PROJECT_LIST_ADDITION);
+  }
+
+  @Override
+  public CompletableFuture<Result> removeFromProjectList(String projectName) {
+    return recoverableFailure(EventType.PROJECT_LIST_DELETION);
+  }
+
+  @Override
+  public CompletableFuture<Result> deleteAllChangesForProject(NameKey projectName) {
+    return recoverableFailure(EventType.INDEX_CHANGE_DELETION_ALL_OF_PROJECT);
+  }
+
+  private CompletableFuture<Result> recoverableFailure(EventType eventType) {
+    return CompletableFuture.completedFuture(new Result(eventType, false, true));
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java
index 75a9681..23a568e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java
@@ -146,7 +146,7 @@
   }
 
   @Override
-  public void onChangeDeleted(int id) {
+  public void onChangeDeleted(String projectName, int id) {
     currCtx.onlyWithContext((ctx) -> update(IndexName.CHANGE, LocalDateTime.now()));
   }
 
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandler.java
index 53bc3a1..43b6e4c 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandler.java
@@ -17,15 +17,16 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
 import com.google.common.cache.RemovalNotification;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.cache.CacheRemovalListener;
 import com.google.inject.Inject;
 
 class CacheEvictionHandler<K, V> implements CacheRemovalListener<K, V> {
-  private final Forwarder forwarder;
+  private final DynamicItem<Forwarder> forwarder;
   private final CachePatternMatcher matcher;
 
   @Inject
-  CacheEvictionHandler(Forwarder forwarder, CachePatternMatcher matcher) {
+  CacheEvictionHandler(DynamicItem<Forwarder> forwarder, CachePatternMatcher matcher) {
     this.forwarder = forwarder;
     this.matcher = matcher;
   }
@@ -33,7 +34,7 @@
   @Override
   public void onRemoval(String plugin, String cache, RemovalNotification<K, V> notification) {
     if (!Context.isForwardedEvent() && !notification.wasEvicted() && matcher.matches(cache)) {
-      forwarder.evict(cache, notification.getKey());
+      forwarder.get().evict(cache, notification.getKey());
     }
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListUpdateHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListUpdateHandler.java
index 9ce9536..4281226 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListUpdateHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListUpdateHandler.java
@@ -19,16 +19,17 @@
 import com.google.gerrit.extensions.events.NewProjectCreatedListener;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.events.ProjectEvent;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
 @Singleton
 public class ProjectListUpdateHandler implements NewProjectCreatedListener, ProjectDeletedListener {
 
-  private final Forwarder forwarder;
+  private final DynamicItem<Forwarder> forwarder;
 
   @Inject
-  public ProjectListUpdateHandler(Forwarder forwarder) {
+  public ProjectListUpdateHandler(DynamicItem<Forwarder> forwarder) {
     this.forwarder = forwarder;
   }
 
@@ -47,9 +48,9 @@
   private void process(ProjectEvent event, boolean delete) {
     if (!Context.isForwardedEvent()) {
       if (delete) {
-        forwarder.removeFromProjectList(event.getProjectName());
+        forwarder.get().removeFromProjectList(event.getProjectName());
       } else {
-        forwarder.addToProjectList(event.getProjectName());
+        forwarder.get().addToProjectList(event.getProjectName());
       }
     }
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java
index 5ce12df..3060f5f 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandler.java
@@ -16,23 +16,24 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventListener;
 import com.google.gerrit.server.events.ProjectEvent;
 import com.google.inject.Inject;
 
 class EventHandler implements EventListener {
-  private final Forwarder forwarder;
+  private final DynamicItem<Forwarder> forwarder;
 
   @Inject
-  EventHandler(Forwarder forwarder) {
+  EventHandler(DynamicItem<Forwarder> forwarder) {
     this.forwarder = forwarder;
   }
 
   @Override
   public void onEvent(Event event) {
     if (!Context.isForwardedEvent() && event instanceof ProjectEvent) {
-      forwarder.send(event);
+      forwarder.get().send(event);
     }
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
index b93ff18..3bf5872 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
@@ -104,7 +104,7 @@
 
     } catch (Exception e) {
       if (isCausedByNoSuchChangeException(e)) {
-        indexer.delete(parseChangeId(id));
+        indexer.delete(parseProject(id), parseChangeId(id));
         log.atWarning().withCause(e).log("Error trying to index Change %s. Deleted from index", id);
         return true;
       }
@@ -114,15 +114,15 @@
   }
 
   private void reindex(ChangeNotes notes) {
-    notes.reload();
-    indexer.reindexIfStale(notes.getProjectName(), notes.getChangeId());
+    var unused = notes.reload();
+    var unusedResult = indexer.reindexIfStale(notes.getProjectName(), notes.getChangeId());
   }
 
   @Override
   protected CompletableFuture<Boolean> doDelete(String id, Optional<IndexEvent> indexEvent)
       throws IOException {
+    Project.NameKey projectName = parseProject(id);
     if (ALL_CHANGES_FOR_PROJECT.equals(extractChangeId(id))) {
-      Project.NameKey projectName = parseProject(id);
       try {
         indexer.deleteAllForProject(projectName);
         log.atFine().log("All %s changes successfully deleted from index", projectName.get());
@@ -133,7 +133,7 @@
       }
     } else {
       try {
-        indexer.delete(parseChangeId(id));
+        indexer.delete(projectName, parseChangeId(id));
         log.atFine().log("Change %s successfully deleted from index", id);
       } catch (RuntimeException e) {
         log.atFine().log("Change %s could not be deleted from index", id);
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
index 3bf08b4..088f0fa 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
@@ -68,7 +68,8 @@
    * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
    *     false.
    */
-  CompletableFuture<Result> deleteChangeFromIndex(int changeId, IndexEvent indexEvent);
+  CompletableFuture<Result> deleteChangeFromIndex(
+      String projectName, int changeId, IndexEvent indexEvent);
 
   /**
    * Forward a group indexing event to the other primary.
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/AddToProjectList.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/AddToProjectList.java
similarity index 93%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/AddToProjectList.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/AddToProjectList.java
index 6f47da3..4883dda 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/AddToProjectList.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/AddToProjectList.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import java.time.Instant;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/Command.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/Command.java
similarity index 92%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/Command.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/Command.java
index 3abad1e..1b7b86e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/Command.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/Command.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import java.time.Instant;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializer.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandDeserializer.java
similarity index 97%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializer.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandDeserializer.java
index cdd217c..1100284 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializer.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandDeserializer.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import com.google.gson.JsonDeserializationContext;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandProcessor.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandProcessor.java
new file mode 100644
index 0000000..6c40356
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandProcessor.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2026 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
+
+/** Processes commands received from other nodes */
+public interface CommandProcessor {
+
+  /**
+   * Processes the given command.
+   *
+   * @param cmd the command to process
+   * @return true if the command was successfully processed, false otherwise
+   */
+  boolean handle(Command cmd);
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessor.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandProcessorImpl.java
similarity index 88%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessor.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandProcessorImpl.java
index 489e070..cadfaea 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessor.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandProcessorImpl.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.CacheEntry;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
@@ -25,23 +25,20 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedProjectListUpdateHandler;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.server.events.Event;
-import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.time.Instant;
 import java.util.Optional;
-import org.jgroups.Message;
-import org.jgroups.blocks.RequestHandler;
 
 @Singleton
-public class MessageProcessor implements RequestHandler {
+public class CommandProcessorImpl implements CommandProcessor {
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
-  private final Gson gson;
   private final ForwardedIndexChangeHandler indexChangeHandler;
   private final ForwardedIndexBatchChangeHandler indexBatchChangeHandler;
   private final ForwardedIndexAccountHandler indexAccountHandler;
@@ -51,8 +48,8 @@
   private final ProcessorMetricsRegistry metricRegistry;
 
   @Inject
-  MessageProcessor(
-      @JGroupsGson Gson gson,
+  @VisibleForTesting
+  public CommandProcessorImpl(
       ForwardedIndexChangeHandler indexChangeHandler,
       ForwardedIndexBatchChangeHandler indexBatchChangeHandler,
       ForwardedIndexAccountHandler indexAccountHandler,
@@ -60,7 +57,6 @@
       ForwardedEventHandler eventHandler,
       ForwardedProjectListUpdateHandler projectListUpdateHandler,
       ProcessorMetricsRegistry metricRegistry) {
-    this.gson = gson;
     this.indexChangeHandler = indexChangeHandler;
     this.indexBatchChangeHandler = indexBatchChangeHandler;
     this.indexAccountHandler = indexAccountHandler;
@@ -71,8 +67,7 @@
   }
 
   @Override
-  public Object handle(Message msg) {
-    Command cmd = getCommand(msg);
+  public boolean handle(Command cmd) {
     ProcessorMetrics metrics = metricRegistry.get(cmd.type);
     Instant startTime = Instant.now();
     boolean success = false;
@@ -117,7 +112,7 @@
       } else if (cmd instanceof PostEvent) {
         Event event = ((PostEvent) cmd).getEvent();
         eventHandler.dispatch(event);
-
+        log.atFine().log("Dispatching event %s done", event);
       } else if (cmd instanceof AddToProjectList) {
         String projectName = ((AddToProjectList) cmd).getProjectName();
         projectListUpdateHandler.update(projectName, false);
@@ -145,15 +140,4 @@
       throw new IllegalArgumentException("Unknown type of IndexChange command " + cmd.getClass());
     }
   }
-
-  private Command getCommand(Message msg) {
-    try {
-      String s = (String) msg.getObject();
-      log.atFine().log("Received message: %s", s);
-      return gson.fromJson(s, Command.class);
-    } catch (RuntimeException e) {
-      log.atSevere().withCause(e).log("Error parsing message %s", msg.getObject());
-      throw e;
-    }
-  }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsGson.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandsGson.java
similarity index 87%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsGson.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandsGson.java
index a4ca15a..d381402 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsGson.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandsGson.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@@ -21,4 +21,4 @@
 
 @BindingAnnotation
 @Retention(RUNTIME)
-public @interface JGroupsGson {}
+public @interface CommandsGson {}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/DeleteAllProjectChangesFromIndex.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/DeleteAllProjectChangesFromIndex.java
similarity index 86%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/DeleteAllProjectChangesFromIndex.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/DeleteAllProjectChangesFromIndex.java
index 81ce445..532708b 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/DeleteAllProjectChangesFromIndex.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/DeleteAllProjectChangesFromIndex.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import com.google.gerrit.entities.Project;
@@ -23,7 +23,7 @@
 
   private final Project.NameKey projectName;
 
-  protected DeleteAllProjectChangesFromIndex(Project.NameKey projectName, Instant createdOn) {
+  public DeleteAllProjectChangesFromIndex(Project.NameKey projectName, Instant createdOn) {
     super(TYPE, createdOn);
     this.projectName = projectName;
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/EvictCache.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/EvictCache.java
similarity index 82%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/EvictCache.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/EvictCache.java
index 9ca05f1..151b839 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/EvictCache.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/EvictCache.java
@@ -12,18 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import java.time.Instant;
 
 public class EvictCache extends Command {
-  static final EventType TYPE = EventType.CACHE_EVICTION;
+  public static final EventType TYPE = EventType.CACHE_EVICTION;
 
   private final String cacheName;
   private final String keyJson;
 
-  protected EvictCache(String cacheName, String keyJson, Instant eventCreatedOn) {
+  public EvictCache(String cacheName, String keyJson, Instant eventCreatedOn) {
     super(TYPE, eventCreatedOn);
     this.cacheName = cacheName;
     this.keyJson = keyJson;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/ForwarderCommandsModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/ForwarderCommandsModule.java
new file mode 100644
index 0000000..7a1b039
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/ForwarderCommandsModule.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.InstantTypeAdapter;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.server.events.EventGson;
+import com.google.gson.Gson;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import java.time.Instant;
+
+public class ForwarderCommandsModule extends AbstractModule {
+  @Provides
+  @Singleton
+  @CommandsGson
+  @VisibleForTesting
+  public Gson buildCommandsGson(@EventGson Gson eventGson) {
+    return eventGson
+        .newBuilder()
+        .registerTypeAdapter(Command.class, new CommandDeserializer())
+        .registerTypeAdapter(Instant.class, new InstantTypeAdapter())
+        .create();
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexAccount.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/IndexAccount.java
similarity index 92%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexAccount.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/IndexAccount.java
index 55cadda..6c95d1d 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexAccount.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/IndexAccount.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import java.time.Instant;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexChange.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/IndexChange.java
similarity index 92%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexChange.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/IndexChange.java
index 075bfd7..df79eca 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexChange.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/IndexChange.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import com.google.common.base.Strings;
@@ -58,10 +58,6 @@
   public static class Delete extends IndexChange {
     static final EventType TYPE = EventType.INDEX_CHANGE_DELETION;
 
-    public Delete(int id, Instant eventCreatedOn) {
-      this("", id, eventCreatedOn);
-    }
-
     public Delete(String projectName, int id, Instant eventCreatedOn) {
       super(TYPE, projectName, id, false, eventCreatedOn);
     }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexGroup.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/IndexGroup.java
similarity index 87%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexGroup.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/IndexGroup.java
index 9f49060..3ae8458 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexGroup.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/IndexGroup.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import java.time.Instant;
@@ -22,7 +22,7 @@
 
   private final String uuid;
 
-  protected IndexGroup(String uuid, Instant eventCreatedOn) {
+  public IndexGroup(String uuid, Instant eventCreatedOn) {
     super(TYPE, eventCreatedOn);
     this.uuid = uuid;
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexProject.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/IndexProject.java
similarity index 87%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexProject.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/IndexProject.java
index 40e400d..7feffc4 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexProject.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/IndexProject.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import java.time.Instant;
@@ -22,7 +22,7 @@
 
   private String projectName;
 
-  protected IndexProject(String projectName, Instant eventCreatedOn) {
+  public IndexProject(String projectName, Instant eventCreatedOn) {
     super(TYPE, eventCreatedOn);
     this.projectName = projectName;
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/PostEvent.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/PostEvent.java
similarity index 88%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/PostEvent.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/PostEvent.java
index 3f50b0a..18c17c7 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/PostEvent.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/PostEvent.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import com.google.gerrit.server.events.Event;
@@ -23,7 +23,7 @@
 
   private final Event event;
 
-  protected PostEvent(Event event, Instant eventCreatedOn) {
+  public PostEvent(Event event, Instant eventCreatedOn) {
     super(TYPE, eventCreatedOn);
     this.event = event;
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/RemoveFromProjectList.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/RemoveFromProjectList.java
similarity index 93%
rename from src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/RemoveFromProjectList.java
rename to src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/RemoveFromProjectList.java
index 1a43ac8..b1ee7df 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/RemoveFromProjectList.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/RemoveFromProjectList.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import java.time.Instant;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarder.java
index 0bd8c5d..225738f 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarder.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarder.java
@@ -19,6 +19,17 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwarderMetricsRegistry;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.AddToProjectList;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.Command;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.CommandsGson;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.DeleteAllProjectChangesFromIndex;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.EvictCache;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.IndexAccount;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.IndexChange;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.IndexGroup;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.IndexProject;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.PostEvent;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.RemoveFromProjectList;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.events.Event;
@@ -53,7 +64,7 @@
   JGroupsForwarder(
       MessageDispatcher dispatcher,
       Configuration cfg,
-      @JGroupsGson Gson gson,
+      @CommandsGson Gson gson,
       @JGroupsForwarderExecutor FailsafeExecutor<Result> executor,
       ForwarderMetricsRegistry metricsRegistry) {
     this.dispatcher = dispatcher;
@@ -86,8 +97,9 @@
   }
 
   @Override
-  public CompletableFuture<Result> deleteChangeFromIndex(int changeId, IndexEvent indexEvent) {
-    return execute(new IndexChange.Delete(changeId, indexEvent.eventCreatedOn));
+  public CompletableFuture<Result> deleteChangeFromIndex(
+      String projectName, int changeId, IndexEvent indexEvent) {
+    return execute(new IndexChange.Delete(projectName, changeId, indexEvent.eventCreatedOn));
   }
 
   @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java
index 0895088..3d3a5b7 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java
@@ -16,17 +16,12 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result;
-import com.ericsson.gerrit.plugins.highavailability.forwarder.InstantTypeAdapter;
 import com.ericsson.gerrit.plugins.highavailability.peers.jgroups.JChannelProviderModule;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.server.events.EventGson;
-import com.google.gson.Gson;
-import com.google.inject.Provides;
 import com.google.inject.Scopes;
-import com.google.inject.Singleton;
 import com.google.inject.TypeLiteral;
 import dev.failsafe.FailsafeExecutor;
-import java.time.Instant;
 import org.jgroups.blocks.MessageDispatcher;
 import org.jgroups.blocks.RequestHandler;
 
@@ -34,9 +29,9 @@
 
   @Override
   protected void configure() {
-    bind(Forwarder.class).to(JGroupsForwarder.class);
+    DynamicItem.bind(binder(), Forwarder.class).to(JGroupsForwarder.class);
     bind(MessageDispatcher.class).toProvider(MessageDispatcherProvider.class).in(Scopes.SINGLETON);
-    bind(RequestHandler.class).to(MessageProcessor.class);
+    bind(RequestHandler.class).to(JGroupsMessageProcessor.class);
     install(new JChannelProviderModule());
     listener().to(OnStartStop.class);
 
@@ -45,15 +40,4 @@
         .toProvider(FailsafeExecutorProvider.class)
         .in(Scopes.SINGLETON);
   }
-
-  @Provides
-  @Singleton
-  @JGroupsGson
-  Gson buildJGroupsGson(@EventGson Gson eventGson) {
-    return eventGson
-        .newBuilder()
-        .registerTypeAdapter(Command.class, new CommandDeserializer())
-        .registerTypeAdapter(Instant.class, new InstantTypeAdapter())
-        .create();
-  }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsMessageProcessor.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsMessageProcessor.java
new file mode 100644
index 0000000..ff091b9
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsMessageProcessor.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.Command;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.CommandProcessor;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.CommandsGson;
+import com.google.common.flogger.FluentLogger;
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.jgroups.Message;
+import org.jgroups.blocks.RequestHandler;
+
+@Singleton
+public class JGroupsMessageProcessor implements RequestHandler {
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
+
+  private final Gson gson;
+  private final CommandProcessor processor;
+
+  @Inject
+  JGroupsMessageProcessor(@CommandsGson Gson gson, CommandProcessor processor) {
+    this.gson = gson;
+    this.processor = processor;
+  }
+
+  @Override
+  public Object handle(Message msg) {
+    return processor.handle(getCommand(msg));
+  }
+
+  private Command getCommand(Message msg) {
+    try {
+      String s = (String) msg.getObject();
+      log.atFine().log("Received message: %s", s);
+      return gson.fromJson(s, Command.class);
+    } catch (RuntimeException e) {
+      log.atSevere().withCause(e).log("Error parsing message %s", msg.getObject());
+      throw e;
+    }
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java
index a71e03f..fc90a10 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProvider.java
@@ -14,6 +14,8 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
+import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
+
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.google.common.flogger.FluentLogger;
 import com.google.inject.Inject;
@@ -24,6 +26,7 @@
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
+import org.apache.http.ConnectionReuseStrategy;
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.config.RequestConfig;
@@ -34,6 +37,7 @@
 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
 import org.apache.http.conn.ssl.NoopHostnameVerifier;
 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.DefaultConnectionReuseStrategy;
 import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
@@ -64,6 +68,7 @@
         .setConnectionManager(customConnectionManager())
         .setDefaultCredentialsProvider(buildCredentials())
         .setDefaultRequestConfig(customRequestConfig())
+        .setConnectionReuseStrategy(customConnectionReuseStrategy())
         .build();
   }
 
@@ -77,6 +82,10 @@
   }
 
   private HttpClientConnectionManager customConnectionManager() {
+    return buildConnectionManager();
+  }
+
+  PoolingHttpClientConnectionManager buildConnectionManager() {
     Registry<ConnectionSocketFactory> socketFactoryRegistry =
         RegistryBuilder.<ConnectionSocketFactory>create()
             .register("https", sslSocketFactory)
@@ -90,6 +99,16 @@
     return connManager;
   }
 
+  private ConnectionReuseStrategy customConnectionReuseStrategy() {
+    return (response, context) -> {
+      if (response.getStatusLine().getStatusCode() == SC_SERVICE_UNAVAILABLE
+          && !cfg.http().reuseConnectionAfter503()) {
+        return false;
+      }
+      return DefaultConnectionReuseStrategy.INSTANCE.keepAlive(response, context);
+    };
+  }
+
   private static SSLConnectionSocketFactory buildSslSocketFactory() {
     return new SSLConnectionSocketFactory(buildSslContext(), NoopHostnameVerifier.INSTANCE);
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
index ea783a6..06df702 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
@@ -120,13 +120,14 @@
   }
 
   @Override
-  public CompletableFuture<Result> deleteChangeFromIndex(final int changeId, IndexEvent event) {
+  public CompletableFuture<Result> deleteChangeFromIndex(
+      String projectName, final int changeId, IndexEvent event) {
     return execute(
         RequestMethod.DELETE,
         EventType.INDEX_CHANGE_DELETION,
         "delete change",
         "index/change",
-        buildIndexEndpoint(changeId),
+        buildIndexEndpoint(projectName, changeId),
         event,
         event.eventCreatedOn);
   }
@@ -143,10 +144,6 @@
         event.eventCreatedOn);
   }
 
-  private String buildIndexEndpoint(int changeId) {
-    return buildIndexEndpoint("", changeId);
-  }
-
   private String buildIndexEndpoint(String projectName, int changeId) {
     String escapedProjectName = Url.encode(projectName);
     return escapedProjectName + '~' + changeId;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java
index 712e7f4..f1c58e6 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java
@@ -17,6 +17,7 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.InstantTypeAdapter;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.events.EventGson;
 import com.google.gson.Gson;
 import com.google.inject.AbstractModule;
@@ -34,7 +35,7 @@
   protected void configure() {
     bind(CloseableHttpClient.class).toProvider(HttpClientProvider.class).in(Scopes.SINGLETON);
     bind(HttpSession.class);
-    bind(Forwarder.class).to(RestForwarder.class);
+    DynamicItem.bind(binder(), Forwarder.class).to(RestForwarder.class);
 
     bind(new TypeLiteral<FailsafeExecutor<Result>>() {})
         .annotatedWith(RestForwarderExecutor.class)
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
index e4de705..32d425d 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
@@ -27,7 +27,6 @@
 import com.google.inject.assistedinject.Assisted;
 import java.io.IOException;
 import java.time.Instant;
-import java.util.Objects;
 import java.util.Optional;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
@@ -101,8 +100,8 @@
         try (Repository repo = gitRepoMgr.openRepository(changeNotes.get().getProjectName())) {
           IndexEvent indexEvent = indexEventOption.get();
           return computedChangeTs.get().compareTo(indexEvent.eventCreatedOn) >= 0
-              && (Objects.isNull(indexEvent.targetSha) || repositoryHas(repo, indexEvent.targetSha))
-              && (Objects.isNull(indexEvent.metaSha) || repositoryHas(repo, indexEvent.metaSha));
+              && (indexEvent.targetSha == null || repositoryHas(repo, indexEvent.targetSha))
+              && (indexEvent.metaSha == null || repositoryHas(repo, indexEvent.metaSha));
         }
       }
       return true;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
index 6475d99..d69bb42 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.extensions.events.ChangeIndexedListener;
 import com.google.gerrit.extensions.events.GroupIndexedListener;
 import com.google.gerrit.extensions.events.ProjectIndexedListener;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.inject.Inject;
 import java.util.Optional;
 
@@ -32,13 +33,15 @@
         GroupIndexedListener,
         ProjectIndexedListener {
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
-  private final Forwarder forwarder;
+  private final DynamicItem<Forwarder> forwarder;
   private final ChangeCheckerImpl.Factory changeChecker;
   private final CurrentRequestContext currCtx;
 
   @Inject
   IndexEventHandler(
-      Forwarder forwarder, ChangeCheckerImpl.Factory changeChecker, CurrentRequestContext currCtx) {
+      DynamicItem<Forwarder> forwarder,
+      ChangeCheckerImpl.Factory changeChecker,
+      CurrentRequestContext currCtx) {
     this.forwarder = forwarder;
     this.changeChecker = changeChecker;
     this.currCtx = currCtx;
@@ -49,7 +52,7 @@
     currCtx.onlyWithContext(
         (ctx) -> {
           if (!Context.isForwardedEvent()) {
-            forwarder.indexAccount(id, new IndexEvent());
+            forwarder.get().indexAccount(id, new IndexEvent());
           }
         });
   }
@@ -66,7 +69,7 @@
 
   private void executeAllChangesDeletedForProject(String projectName) {
     if (!Context.isForwardedEvent()) {
-      forwarder.deleteAllChangesForProject(Project.nameKey(projectName));
+      forwarder.get().deleteAllChangesForProject(Project.nameKey(projectName));
     }
   }
 
@@ -80,9 +83,9 @@
         }
 
         if (Thread.currentThread().getName().contains("Batch")) {
-          forwarder.batchIndexChange(projectName, id, indexEvent.get());
+          forwarder.get().batchIndexChange(projectName, id, indexEvent.get());
         } else {
-          forwarder.indexChange(projectName, id, indexEvent.get());
+          forwarder.get().indexChange(projectName, id, indexEvent.get());
         }
       } catch (Exception e) {
         log.atWarning().withCause(e).log("Unable to create task to reindex change %s", changeId);
@@ -91,23 +94,23 @@
   }
 
   @Override
-  public void onChangeDeleted(int id) {
+  public void onChangeDeleted(String projectName, int id) {
     if (!Context.isForwardedEvent()) {
-      forwarder.deleteChangeFromIndex(id, new IndexEvent());
+      forwarder.get().deleteChangeFromIndex(projectName, id, new IndexEvent());
     }
   }
 
   @Override
   public void onProjectIndexed(String projectName) {
     if (!Context.isForwardedEvent()) {
-      forwarder.indexProject(projectName, new IndexEvent());
+      forwarder.get().indexProject(projectName, new IndexEvent());
     }
   }
 
   @Override
   public void onGroupIndexed(String groupUUID) {
     if (!Context.isForwardedEvent()) {
-      forwarder.indexGroup(groupUUID, new IndexEvent());
+      forwarder.get().indexGroup(groupUUID, new IndexEvent());
     }
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/IndexSyncRunner.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/IndexSyncRunner.java
index 69bc2e4..c0e6451 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/IndexSyncRunner.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/indexsync/IndexSyncRunner.java
@@ -203,7 +203,7 @@
       Project.NameKey project = Project.nameKey(parts.get(0));
       Change.Id changeId = Change.id(Integer.parseInt(parts.get(1)));
       log.atInfo().log("Change %s present in index but not in noteDb. Deleting from index", id);
-      indexer.deleteAsync(project, changeId);
+      var unused = indexer.deleteAsync(project, changeId);
     }
   }
 }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index b089091..1a3a498 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -73,6 +73,18 @@
   maxTries = 100
 ```
 
+### Transport implementation provided by another plugin
+
+In this case another plugin extends the @PLUGIN@ plugin and provides messaging
+implementation.
+```
+[main]
+  transport = provided
+  sharedDirectory = /directory/accessible/from/both/instances
+[autoReindex]
+  enabled = false
+```
+
 ```main.sharedDirectory```
 :   Path to a directory accessible from both instances.
     When given as a relative path, then it is resolved against the $SITE_PATH
@@ -82,9 +94,10 @@
     is "shared".
 
 ```main.transport```
-:   Message transport layer. Could be: `http` or `jgroups`.
+:   Message transport layer. Could be: `http`, `jgroups` or `provided`.
     When not specificed the default is `http`.
-    When set to `jgroups` then all `peerInfo.*` sections are unnecessary and ignored.
+    When set to `jgroups` or `provided` then all `peerInfo.*` sections are
+    unnecessary and ignored.
 
 ```autoReindex.enabled```
 :   Enable the tracking of the latest change indexed under data/high-availability
@@ -281,6 +294,14 @@
     Value is expressed in Gerrit time values as in [websession.cleanupInterval](#websessioncleanupInterval).
     When not specified, the default value is set to 10 seconds.
 
+```http.reuseConnectionAfter503```
+:   Whether to reuse the HTTP connection to the peer instance after receiving a
+    503 (Service Unavailable) response over it. When set to `false`, the plugin
+    will close the connection immediately on a 503 response and open a fresh one
+    for the next retry, which can help recover from situations where a peer has
+    transiently become unavailable without properly closing existing connections.
+    When not specified, the default value is `true`.
+
 ```http.threadPoolSize```
 :   Maximum number of threads used to execute REST calls towards target instances.
 
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandlerTest.java
index 3d091a0..cd5a477 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionHandlerTest.java
@@ -20,6 +20,7 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
 import com.google.common.cache.RemovalCause;
 import com.google.common.cache.RemovalNotification;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.config.SitePaths;
 import java.io.IOException;
@@ -34,7 +35,7 @@
 
 @RunWith(MockitoJUnitRunner.class)
 public class CacheEvictionHandlerTest {
-  @Mock private Forwarder forwarder;
+  @Mock private DynamicItem<Forwarder> forwarder;
   @Mock private PluginConfigFactory pluginConfigFactoryMock;
 
   private static final Path SITE_PATH = Paths.get("/site_path");
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java
index 180c68f..f44be49 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java
@@ -48,6 +48,7 @@
 @UseSsh
 @TestPlugin(
     name = "high-availability",
+    apiModule = "com.ericsson.gerrit.plugins.highavailability.ApiModule",
     sysModule = "com.ericsson.gerrit.plugins.highavailability.Module",
     httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule")
 public class CacheEvictionIT extends LightweightPluginDaemonTest {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListIT.java
index 36c775c..6fa508e 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListIT.java
@@ -38,6 +38,7 @@
 
 @TestPlugin(
     name = "high-availability",
+    apiModule = "com.ericsson.gerrit.plugins.highavailability.ApiModule",
     sysModule = "com.ericsson.gerrit.plugins.highavailability.Module",
     httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule")
 public class ProjectListIT extends LightweightPluginDaemonTest {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListUpdateHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListUpdateHandlerTest.java
index 1bd2aed..e4fea74 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListUpdateHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/ProjectListUpdateHandlerTest.java
@@ -23,6 +23,8 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
 import com.google.gerrit.extensions.events.NewProjectCreatedListener;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.inject.Inject;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,10 +36,12 @@
   private ProjectListUpdateHandler handler;
 
   @Mock private Forwarder forwarder;
+  @Inject DynamicItem<Forwarder> forwarderItem;
 
   @Before
   public void setUp() {
-    handler = new ProjectListUpdateHandler(forwarder);
+    forwarderItem = DynamicItem.itemOf(Forwarder.class, forwarder);
+    handler = new ProjectListUpdateHandler(forwarderItem);
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandlerTest.java
index 988064e..6c12b1d 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventHandlerTest.java
@@ -20,8 +20,10 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.ProjectEvent;
+import com.google.inject.Inject;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -33,10 +35,12 @@
   private EventHandler eventHandler;
 
   @Mock private Forwarder forwarder;
+  @Inject DynamicItem<Forwarder> forwarderItem;
 
   @Before
   public void setUp() {
-    eventHandler = new EventHandler(forwarder);
+    forwarderItem = DynamicItem.itemOf(Forwarder.class, forwarder);
+    eventHandler = new EventHandler(forwarderItem);
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
index 446a1e9..2ad8fed 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
@@ -64,7 +64,6 @@
 
   @Mock private ChangeIndexer indexerMock;
   @Mock private ChangeNotes changeNotes;
-  @Mock private Project.NameKey projectName;
 
   @Mock(answer = RETURNS_DEEP_STUBS)
   private Configuration configMock;
@@ -76,10 +75,12 @@
   @Mock private ForwardedIndexExecutorProvider indexExecutorProviderMock;
   private ForwardedIndexChangeHandler handler;
   private Change.Id id;
+  private Project.NameKey projectName;
 
   @Before
   public void setUp() throws Exception {
     id = Change.id(TEST_CHANGE_NUMBER);
+    projectName = Project.nameKey(TEST_PROJECT);
     when(configMock.index().threadPoolSize()).thenReturn(4);
     when(configMock.index().maxTries()).thenReturn(3);
     when(configMock.index().retryInterval()).thenReturn(Duration.ofMillis(10));
@@ -129,7 +130,7 @@
   @Test
   public void changeIsDeletedFromIndex() throws Exception {
     handler.index(TEST_CHANGE_ID, Operation.DELETE, Optional.empty()).get(10, SECONDS);
-    verify(indexerMock, times(1)).delete(id);
+    verify(indexerMock, times(1)).delete(projectName, id);
   }
 
   @Test
@@ -144,7 +145,7 @@
   public void changeToIndexDoesNotExist() throws Exception {
     setupChangeAccessRelatedMocks(CHANGE_DOES_NOT_EXIST, CHANGE_OUTDATED);
     handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty()).get(10, SECONDS);
-    verify(indexerMock, times(0)).delete(id);
+    verify(indexerMock, times(0)).delete(projectName, id);
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandDeserializerTest.java
similarity index 97%
rename from src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializerTest.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandDeserializerTest.java
index 63e6f46..8818905 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/commands/CommandDeserializerTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
+package com.ericsson.gerrit.plugins.highavailability.forwarder.commands;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -36,7 +36,7 @@
   @Before
   public void setUp() {
     Gson eventGson = new EventGsonProvider().get();
-    this.gson = new JGroupsForwarderModule().buildJGroupsGson(eventGson);
+    this.gson = new ForwarderCommandsModule().buildCommandsGson(eventGson);
     this.cacheKeyParser = new CacheKeyJsonParser(eventGson, DynamicMap.emptyMap());
   }
 
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderTest.java
index e7542b4..1a926bf 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderTest.java
@@ -27,6 +27,7 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwarderMetrics;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwarderMetricsRegistry;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.ForwarderCommandsModule;
 import com.google.gerrit.server.events.EventGsonProvider;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gson.Gson;
@@ -65,7 +66,7 @@
   @Before
   public void setUp() throws Exception {
     Gson eventGson = new EventGsonProvider().get();
-    Gson gson = new JGroupsForwarderModule().buildJGroupsGson(eventGson);
+    Gson gson = new ForwarderCommandsModule().buildCommandsGson(eventGson);
     Configuration cfg = mock(Configuration.class, RETURNS_DEEP_STUBS);
     when(cfg.jgroups().maxTries()).thenReturn(MAX_TRIES);
     when(cfg.jgroups().retryInterval()).thenReturn(Duration.ofMillis(1));
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessorTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessorTest.java
index 91d291c..bca7801 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessorTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessorTest.java
@@ -33,6 +33,14 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedProjectListUpdateHandler;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.AddToProjectList;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.CommandProcessorImpl;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.EvictCache;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.ForwarderCommandsModule;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.IndexAccount;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.IndexChange;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.PostEvent;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.RemoveFromProjectList;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.server.events.Event;
@@ -55,7 +63,7 @@
 @RunWith(org.mockito.junit.MockitoJUnitRunner.class)
 public class MessageProcessorTest {
 
-  private MessageProcessor processor;
+  private JGroupsMessageProcessor processor;
   private Gson gson;
 
   private ForwardedIndexChangeHandler indexChangeHandler;
@@ -74,7 +82,7 @@
   public void setUp() {
     when(metricsRegistry.get(any())).thenReturn(processorMetrics);
     Gson eventGson = new EventGsonProvider().get();
-    gson = new JGroupsForwarderModule().buildJGroupsGson(eventGson);
+    gson = new ForwarderCommandsModule().buildCommandsGson(eventGson);
 
     indexChangeHandler = createHandlerMock(ForwardedIndexChangeHandler.class);
     indexBatchChangeHandler = createHandlerMock(ForwardedIndexBatchChangeHandler.class);
@@ -84,15 +92,16 @@
     projectListUpdateHandler = createHandlerMock(ForwardedProjectListUpdateHandler.class);
 
     processor =
-        new MessageProcessor(
+        new JGroupsMessageProcessor(
             gson,
-            indexChangeHandler,
-            indexBatchChangeHandler,
-            indexAccountHandler,
-            cacheEvictionHandler,
-            eventHandler,
-            projectListUpdateHandler,
-            metricsRegistry);
+            new CommandProcessorImpl(
+                indexChangeHandler,
+                indexBatchChangeHandler,
+                indexAccountHandler,
+                cacheEvictionHandler,
+                eventHandler,
+                projectListUpdateHandler,
+                metricsRegistry));
   }
 
   private <T> T createHandlerMock(Class<T> handlerClass) {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ForwardedCacheEvictionHandlerIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ForwardedCacheEvictionHandlerIT.java
index 1e5cc3d..4488cc4 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ForwardedCacheEvictionHandlerIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ForwardedCacheEvictionHandlerIT.java
@@ -40,6 +40,7 @@
 
 @TestPlugin(
     name = "high-availability",
+    apiModule = "com.ericsson.gerrit.plugins.highavailability.ApiModule",
     sysModule = "com.ericsson.gerrit.plugins.highavailability.Module",
     httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule")
 public class ForwardedCacheEvictionHandlerIT extends LightweightPluginDaemonTest {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProviderConnectionReuseTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProviderConnectionReuseTest.java
new file mode 100644
index 0000000..3bd3b14
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpClientProviderConnectionReuseTest.java
@@ -0,0 +1,121 @@
+// Copyright (C) 2026 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import java.time.Duration;
+import java.util.List;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Answers;
+
+public class HttpClientProviderConnectionReuseTest {
+
+  private static final String ENDPOINT = "/test";
+  private static final Duration TIMEOUT = Duration.ofMillis(1000);
+
+  @Rule public WireMockRule wireMock = new WireMockRule(0);
+
+  private Configuration mockConfig(boolean reuseConnectionAfter503) {
+    Configuration cfg = mock(Configuration.class, Answers.RETURNS_DEEP_STUBS);
+    when(cfg.http().user()).thenReturn("");
+    when(cfg.http().password()).thenReturn("");
+    when(cfg.http().connectionTimeout()).thenReturn(TIMEOUT);
+    when(cfg.http().socketTimeout()).thenReturn(TIMEOUT);
+    when(cfg.http().reuseConnectionAfter503()).thenReturn(reuseConnectionAfter503);
+    return cfg;
+  }
+
+  @Test
+  public void connectionIsReturnedToPoolAfter503WhenReuseIsEnabled() throws Exception {
+    wireMock.givenThat(get(urlEqualTo(ENDPOINT)).willReturn(aResponse().withStatus(503)));
+
+    TestableHttpClientProvider provider = new TestableHttpClientProvider(mockConfig(true));
+    try (CloseableHttpClient client = provider.get()) {
+      executeConsuming(client);
+      // Connection must be back in the pool (available == 1) because reuseConnectionAfter503=true
+      assertThat(provider.connectionManager().getTotalStats().getAvailable()).isEqualTo(1);
+    }
+  }
+
+  @Test
+  public void connectionIsDiscardedAfter503WhenReuseIsDisabled() throws Exception {
+    wireMock.givenThat(get(urlEqualTo(ENDPOINT)).willReturn(aResponse().withStatus(503)));
+
+    TestableHttpClientProvider provider = new TestableHttpClientProvider(mockConfig(false));
+    try (CloseableHttpClient client = provider.get()) {
+      executeConsuming(client);
+      // Connection must have been discarded (available == 0) because reuseConnectionAfter503=false
+      assertThat(provider.connectionManager().getTotalStats().getAvailable()).isEqualTo(0);
+    }
+  }
+
+  @Test
+  public void connectionIsReturnedToPoolAfter500RegardlessOfReuseOption() throws Exception {
+    wireMock.givenThat(get(urlEqualTo(ENDPOINT)).willReturn(aResponse().withStatus(500)));
+
+    for (boolean reuseFlag : List.of(true, false)) {
+      TestableHttpClientProvider provider = new TestableHttpClientProvider(mockConfig(reuseFlag));
+      try (CloseableHttpClient client = provider.get()) {
+        executeConsuming(client);
+        // 500 is not 503 — the reuseConnectionAfter503 option must have no effect
+        assertThat(provider.connectionManager().getTotalStats().getAvailable()).isEqualTo(1);
+      }
+    }
+  }
+
+  private void executeConsuming(CloseableHttpClient client) throws Exception {
+    String url = "http://localhost:" + wireMock.port() + ENDPOINT;
+    try (CloseableHttpResponse response = client.execute(new HttpGet(url))) {
+      if (response.getEntity() != null) {
+        response.getEntity().getContent().close();
+      }
+    }
+  }
+
+  /**
+   * Extends HttpClientProvider to capture the PoolingHttpClientConnectionManager so tests can
+   * inspect pool statistics after requests complete.
+   */
+  private static class TestableHttpClientProvider extends HttpClientProvider {
+    private PoolingHttpClientConnectionManager connManager;
+
+    TestableHttpClientProvider(Configuration cfg) {
+      super(cfg);
+    }
+
+    @Override
+    PoolingHttpClientConnectionManager buildConnectionManager() {
+      connManager = super.buildConnectionManager();
+      return connManager;
+    }
+
+    PoolingHttpClientConnectionManager connectionManager() {
+      return connManager;
+    }
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListRestApiServletIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListRestApiServletIT.java
index e838cb3..caea2aa 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListRestApiServletIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListRestApiServletIT.java
@@ -25,6 +25,7 @@
 
 @TestPlugin(
     name = "high-availability",
+    apiModule = "com.ericsson.gerrit.plugins.highavailability.ApiModule",
     sysModule = "com.ericsson.gerrit.plugins.highavailability.Module",
     httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule")
 public class ProjectListRestApiServletIT extends LightweightPluginDaemonTest {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModuleIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModuleIT.java
index 2406443..880ec04 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModuleIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModuleIT.java
@@ -25,6 +25,7 @@
 
 @TestPlugin(
     name = "high-availability",
+    apiModule = "com.ericsson.gerrit.plugins.highavailability.ApiModule",
     sysModule = "com.ericsson.gerrit.plugins.highavailability.Module",
     httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule")
 public class RestForwarderServletModuleIT extends LightweightPluginDaemonTest {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
index 78d21ef..88318bc 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
@@ -82,8 +82,6 @@
               PLUGIN_NAME,
               "index/change/batch",
               PROJECT_NAME_URL_END + "~" + CHANGE_NUMBER);
-  private static final String DELETE_CHANGE_ENDPOINT =
-      Joiner.on("/").join(URL, PLUGINS, PLUGIN_NAME, "index/change", "~" + CHANGE_NUMBER);
   private static final String DELETE_ALL_CHANGES_ENDPOINT =
       Joiner.on("/")
           .join(
@@ -283,11 +281,11 @@
 
   @Test
   public void testChangeDeletedFromIndexOK() throws Exception {
-    when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT), any()))
+    when(httpSessionMock.delete(eq(INDEX_CHANGE_ENDPOINT), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
     assertThat(
             forwarder
-                .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())
+                .deleteChangeFromIndex(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
                 .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
                 .result())
         .isTrue();
@@ -307,11 +305,11 @@
 
   @Test
   public void testChangeDeletedFromIndexFailed() throws Exception {
-    when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT), any()))
+    when(httpSessionMock.delete(eq(INDEX_CHANGE_ENDPOINT), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
     assertThat(
             forwarder
-                .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())
+                .deleteChangeFromIndex(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
                 .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
                 .result())
         .isFalse();
@@ -319,10 +317,10 @@
 
   @Test
   public void testChangeDeletedFromThrowsException() throws Exception {
-    when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT), any())).thenThrow(IOException.class);
+    when(httpSessionMock.delete(eq(INDEX_CHANGE_ENDPOINT), any())).thenThrow(IOException.class);
     assertThat(
             forwarder
-                .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())
+                .deleteChangeFromIndex(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
                 .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
                 .result())
         .isFalse();
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java
index fe6cfce..3010d9c 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AbstractIndexForwardingIT.java
@@ -46,6 +46,7 @@
 @NoHttpd
 @TestPlugin(
     name = "high-availability",
+    apiModule = "com.ericsson.gerrit.plugins.highavailability.ApiModule",
     sysModule = "com.ericsson.gerrit.plugins.highavailability.Module",
     httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule")
 public abstract class AbstractIndexForwardingIT extends LightweightPluginDaemonTest {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerIT.java
index febf8f0..ebf966f 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerIT.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
 import com.google.gerrit.acceptance.PushOneCommit.Result;
 import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.UseLocalDisk;
 import com.google.gerrit.entities.RefNames;
 import java.io.IOException;
 import java.util.Optional;
@@ -29,6 +30,7 @@
 
 @TestPlugin(
     name = "high-availability",
+    apiModule = "com.ericsson.gerrit.plugins.highavailability.ApiModule",
     sysModule = "com.ericsson.gerrit.plugins.highavailability.Module",
     httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule")
 public class ChangeCheckerIT extends LightweightPluginDaemonTest {
@@ -42,6 +44,7 @@
   }
 
   @Test
+  @UseLocalDisk
   public void shouldPopulateMetaSha() throws Exception {
     Result change = createChange();
     ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
@@ -54,6 +57,7 @@
   }
 
   @Test
+  @UseLocalDisk
   public void shouldReturnIsUpToDateTrueWhenEventContainsCorrectMetaAndTargetSha()
       throws Exception {
     Result change = createChange();
@@ -64,6 +68,7 @@
   }
 
   @Test
+  @UseLocalDisk
   public void shouldReturnIsUpToDateTrueWhenTargetShaIsNull() throws Exception {
     Result change = createChange();
     ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
@@ -80,6 +85,7 @@
   }
 
   @Test
+  @UseLocalDisk
   public void shouldReturnFalseWhenMetaShaIsNotUpToDate() throws Exception {
     String testMetaRefSha = "6212efebe6e8b9f439a8ad013243e602afab7441";
     Result change = createChange();
@@ -97,6 +103,7 @@
   }
 
   @Test
+  @UseLocalDisk
   public void shouldReturnFalseWhenTargetShaIsNotUpToDate() throws Exception {
     String testTargetRefSha = "abed47baf2818a86b68cf712073a748a6b5b293e";
     Result change = createChange();
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
index a986b90..95e627b 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
@@ -31,9 +31,11 @@
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AccountGroup;
 import com.google.gerrit.entities.Change;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.inject.Inject;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
@@ -52,6 +54,7 @@
 
   private IndexEventHandler indexEventHandler;
   @Mock private Forwarder forwarder;
+  @Inject private DynamicItem<Forwarder> forwarderItem;
   @Mock private ChangeCheckerImpl.Factory changeCheckerFactoryMock;
   @Mock private ChangeChecker changeCheckerMock;
   private Change.Id changeId;
@@ -79,7 +82,7 @@
     when(forwarder.indexAccount(eq(ACCOUNT_ID), any()))
         .thenReturn(
             CompletableFuture.completedFuture(new Result(EventType.INDEX_ACCOUNT_UPDATE, true)));
-    when(forwarder.deleteChangeFromIndex(eq(CHANGE_ID), any()))
+    when(forwarder.deleteChangeFromIndex(eq(PROJECT_NAME), eq(CHANGE_ID), any()))
         .thenReturn(
             CompletableFuture.completedFuture(new Result(EventType.INDEX_CHANGE_DELETION, true)));
     when(forwarder.indexGroup(eq(UUID), any()))
@@ -89,11 +92,12 @@
         .thenReturn(
             CompletableFuture.completedFuture(new Result(EventType.INDEX_CHANGE_UPDATE, true)));
 
+    forwarderItem = DynamicItem.itemOf(Forwarder.class, forwarder);
     setUpIndexEventHandler(currCtx);
   }
 
   public void setUpIndexEventHandler(CurrentRequestContext currCtx) throws Exception {
-    indexEventHandler = new IndexEventHandler(forwarder, changeCheckerFactoryMock, currCtx);
+    indexEventHandler = new IndexEventHandler(forwarderItem, changeCheckerFactoryMock, currCtx);
   }
 
   @Test
@@ -137,8 +141,8 @@
 
   @Test
   public void shouldDeleteFromIndexInRemoteOnChangeDeletedEvent() throws Exception {
-    indexEventHandler.onChangeDeleted(changeId.get());
-    verify(forwarder).deleteChangeFromIndex(eq(CHANGE_ID), any());
+    indexEventHandler.onChangeDeleted(PROJECT_NAME, changeId.get());
+    verify(forwarder).deleteChangeFromIndex(eq(PROJECT_NAME), eq(CHANGE_ID), any());
     verifyNoInteractions(changeCheckerMock); // Deleted changes should not be checked against NoteDb
   }
 
@@ -152,7 +156,7 @@
   public void shouldNotCallRemoteWhenChangeEventIsForwarded() throws Exception {
     Context.setForwardedEvent(true);
     indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
-    indexEventHandler.onChangeDeleted(changeId.get());
+    indexEventHandler.onChangeDeleted(PROJECT_NAME, changeId.get());
     Context.unsetForwardedEvent();
     verifyNoInteractions(forwarder);
   }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsKubernetesPeerInfoProviderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsKubernetesPeerInfoProviderTest.java
index 0c53959..b1cd172 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsKubernetesPeerInfoProviderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsKubernetesPeerInfoProviderTest.java
@@ -29,14 +29,19 @@
 import static org.mockito.Mockito.when;
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.github.rholder.retry.RetryerBuilder;
+import com.github.rholder.retry.StopStrategies;
+import com.github.rholder.retry.WaitStrategies;
 import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import com.google.common.base.Predicates;
 import com.google.common.io.Resources;
 import com.google.gerrit.extensions.restapi.Url;
 import java.net.Inet4Address;
-import java.net.NetworkInterface;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.TimeUnit;
 import org.apache.http.HttpStatus;
+import org.jgroups.JChannel;
 import org.jgroups.Message;
 import org.junit.After;
 import org.junit.Before;
@@ -57,24 +62,33 @@
   @Mock private InetAddressFinder finder;
   private JGroupsPeerInfoProvider firstJGroupsPeerInfoProvider;
   private JGroupsPeerInfoProvider secondJGroupsPeerInfoProvider;
+  private JChannel firstChannel;
+  private JChannel secondChannel;
   @Mock private MyUrlProvider myUrlProvider;
 
   @Rule public WireMockRule kubeApiMock = new WireMockRule(options().port(48443));
 
   @Before
   public void setUp() throws Exception {
-    System.setProperty("KUBERNETES_MASTER_PROTOCOL", "http");
-    System.setProperty("KUBERNETES_SERVICE_HOST", "localhost");
-    System.setProperty("KUBERNETES_SERVICE_PORT", "48443");
     System.setProperty("java.net.preferIPv4Stack", "true");
   }
 
   @After
-  public void shutdown() {
-    System.clearProperty("KUBERNETES_MASTER_PROTOCOL");
-    System.clearProperty("KUBERNETES_SERVICE_HOST");
-    System.clearProperty("KUBERNETES_SERVICE_PORT");
+  public void shutdown() throws Exception {
     System.clearProperty("java.net.preferIPv4Stack");
+
+    if (firstJGroupsPeerInfoProvider != null) {
+      firstJGroupsPeerInfoProvider.stop();
+    }
+    if (secondJGroupsPeerInfoProvider != null) {
+      secondJGroupsPeerInfoProvider.stop();
+    }
+    if (firstChannel != null && firstChannel.isConnected()) {
+      firstChannel.close();
+    }
+    if (secondChannel != null && secondChannel.isConnected()) {
+      secondChannel.close();
+    }
   }
 
   @Test
@@ -89,21 +103,20 @@
 
     when(myUrlProvider.get()).thenReturn("http://127.0.0.1:7800");
 
-    NetworkInterface eth0 = NetworkInterface.getByName("eth0");
-    if (eth0 != null) {
-      when(finder.findAddress()).thenReturn(eth0.inetAddresses().findFirst());
-    } else {
-      when(finder.findAddress()).thenReturn(Optional.of(Inet4Address.getByName("127.0.0.1")));
-    }
-    JChannelProvider channelProvider = new JChannelProvider(pluginConfigurationMock);
+    when(finder.findAddress()).thenReturn(Optional.of(Inet4Address.getByName("127.0.0.1")));
+
+    // Create two separate channels with different ports
+    firstChannel = new JChannel(getClass().getResource("test_kubernetes.xml").toString());
+    secondChannel = new JChannel(getClass().getResource("test_kubernetes.xml").toString());
+
     firstJGroupsPeerInfoProvider =
         Mockito.spy(
             new JGroupsPeerInfoProvider(
-                pluginConfigurationMock, finder, myUrlProvider, channelProvider.get()));
+                pluginConfigurationMock, finder, myUrlProvider, firstChannel));
     secondJGroupsPeerInfoProvider =
         Mockito.spy(
             new JGroupsPeerInfoProvider(
-                pluginConfigurationMock, finder, myUrlProvider, channelProvider.get()));
+                pluginConfigurationMock, finder, myUrlProvider, secondChannel));
 
     StringBuilder kubeApiUrlBuilder = new StringBuilder();
     kubeApiUrlBuilder.append("/api/v1/namespaces/");
@@ -121,11 +134,21 @@
                 aResponse()
                     .withJsonBody(new ObjectMapper().readTree(respJson))
                     .withStatus(HttpStatus.SC_OK)));
+
     firstJGroupsPeerInfoProvider.connect();
     verify(getRequestedFor(urlEqualTo(kubeApiUrl)));
+
+    RetryerBuilder.<Boolean>newBuilder()
+        .retryIfResult(Predicates.equalTo(false))
+        .withStopStrategy(StopStrategies.stopAfterDelay(10, TimeUnit.SECONDS))
+        .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
+        .build()
+        .call(() -> firstChannel.isConnected());
+
     secondJGroupsPeerInfoProvider.connect();
 
-    verify(firstJGroupsPeerInfoProvider, timeout(10000)).receive(any(Message.class));
+    verify(firstJGroupsPeerInfoProvider, timeout(10000).atLeastOnce()).receive(any(Message.class));
+    verify(secondJGroupsPeerInfoProvider, timeout(10000).atLeastOnce()).receive(any(Message.class));
 
     assertThat(firstJGroupsPeerInfoProvider.get()).isNotEmpty();
     assertThat(firstJGroupsPeerInfoProvider.get()).hasSize(1);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProviderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProviderTest.java
index a098be6..1e64918 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProviderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProviderTest.java
@@ -169,7 +169,7 @@
   public void testStop() throws Exception {
     jGroupsPeerInfoProvider.addPeer(peerAddress, peerInfo);
     jGroupsPeerInfoProvider.stop();
-    assertThat(jGroupsPeerInfoProvider.getPeers().isEmpty());
+    assertThat(jGroupsPeerInfoProvider.getPeers()).isEmpty();
     Set<PeerInfo> testPeerInfoSet = jGroupsPeerInfoProvider.get();
     assertThat(testPeerInfoSet).isEmpty();
   }
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/pod-list.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/pod-list.json
index 4be7c10..e7efdd1 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/pod-list.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/pod-list.json
@@ -17,7 +17,14 @@
         "containers": [
           {
             "image": "gerrit:test",
-            "name": "gerrit"
+            "name": "gerrit",
+            "ports": [
+              {
+                "containerPort": 7800,
+                "name": "jgroups",
+                "protocol": "TCP"
+              }
+            ]
           }
         ],
         "hostname": "gerrit-primary-0"
@@ -67,7 +74,14 @@
         "containers": [
           {
             "image": "gerrit:test",
-            "name": "gerrit"
+            "name": "gerrit",
+            "ports": [
+              {
+                "containerPort": 7801,
+                "name": "jgroups",
+                "protocol": "TCP"
+              }
+            ]
           }
         ],
         "hostname": "gerrit-primary-1"
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/test_kubernetes.xml b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/test_kubernetes.xml
new file mode 100644
index 0000000..ff1bebf
--- /dev/null
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/test_kubernetes.xml
@@ -0,0 +1,53 @@
+<!--
+    Based on a configuration written by Bela Ban [1].
+
+    [1] https://koudingspawn.de/jgroups-on-kubernetes/
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns="urn:org:jgroups"
+  xsi:schemaLocation="urn:org:jgroups http://www.jgroups.org/schema/jgroups.xsd"
+>
+  <TCP bind_addr="127.0.0.1"
+    bind_port="7800"
+    port_range="2"
+    external_addr="127.0.0.1"
+    thread_pool.min_threads="0"
+    thread_pool.max_threads="200"
+    thread_pool.keep_alive_time="30000" />
+  <RED />
+
+  <org.jgroups.protocols.kubernetes.KUBE_PING
+    port_range="2"
+    namespace="gerrit"
+    labels="app=gerrit,mode=primary"
+    masterProtocol="http"
+    masterHost="localhost"
+    masterPort="48443"
+  />
+
+  <MERGE3 max_interval="30000"
+    min_interval="10000" />
+  <FD_SOCK external_addr="${JGROUPS_EXTERNAL_ADDR}"
+    start_port="${FD_SOCK_PORT:9000}" />
+  <FD_ALL timeout="30000" interval="5000" />
+  <VERIFY_SUSPECT timeout="1500" />
+  <BARRIER />
+  <pbcast.NAKACK2 xmit_interval="500"
+    xmit_table_num_rows="100"
+    xmit_table_msgs_per_row="2000"
+    xmit_table_max_compaction_time="30000"
+    use_mcast_xmit="false"
+    discard_delivered_msgs="true" />
+  <UNICAST3
+    xmit_table_num_rows="100"
+    xmit_table_msgs_per_row="1000"
+    xmit_table_max_compaction_time="30000" />
+  <pbcast.STABLE desired_avg_gossip="50000" max_bytes="8m" />
+  <pbcast.GMS print_local_addr="true" print_physical_addrs="true" join_timeout="3000" />
+  <MFC max_credits="2M"
+    min_threshold="0.4" />
+  <FRAG2 frag_size="60K" />
+  <pbcast.STATE_TRANSFER />
+  <CENTRAL_LOCK />
+  <COUNTER />
+</config>