Initial version

Change-Id: I37f26dfb2aa6c2a5aefb14c43006ff8ffb8343ca
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
new file mode 100644
index 0000000..6cc61e9
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,80 @@
+load(
+    "@com_googlesource_gerrit_bazlets//:gerrit_plugin.bzl",
+    "gerrit_plugin",
+    "gerrit_plugin_dependency_tests",
+    "gerrit_plugin_ext_test_deps",
+    "gerrit_plugin_test_util",
+    "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")
+
+java_library(
+    name = "high-availability-plugin-neverlink",
+    neverlink = True,
+    exports = ["//plugins/high-availability"],
+)
+
+PLUGIN = "high-availability-gcp"
+
+PLUGIN_DEPS = [
+    "com.google.api:api.common",
+    "com.google.api:gax",
+    "com.google.api:gax.grpc",
+    "com.google.api:grpc.proto.google.cloud.pubsub.v1",
+    "com.google.auth:google.auth.library.oauth2.http",
+    "com.google.cloud:google.cloud.pubsub",
+    "io.grpc:grpc.api",
+    "io.grpc:grpc.netty.shaded",
+]
+
+gerrit_plugin(
+    srcs = glob(["src/main/java/**/*.java"]),
+    ext_deps = PLUGIN_DEPS,
+    license = "LICENSE",
+    manifest_entries = [
+        "Gerrit-PluginName: %s" % PLUGIN,
+        "Gerrit-Module: com.googlesource.gerrit.plugins.highavailability.gcp.Module",
+        "Implementation-Title: high-availability gcp pubsub provider",
+        "Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/%s" % PLUGIN,
+    ],
+    plugin = PLUGIN,
+    resources = glob(["src/main/resources/**/*"]),
+    deps = ["high-availability-plugin-neverlink"],
+)
+
+PLUGIN_TEST_DEPS = PLUGIN_DEPS + [
+    "com.google.http.client:google.http.client",
+    "org.testcontainers:gcloud",
+    "org.testcontainers:testcontainers",
+]
+
+gerrit_plugin_ext_test_deps(
+  plugin = PLUGIN,
+  ext_deps = PLUGIN_TEST_DEPS,
+)
+
+gerrit_plugin_tests(
+    name = "tests",
+    srcs = glob(["src/test/java/**/*.java"]),
+    ext_deps_label = ":high-availability-gcp__plugin_test_deps",
+    javacopts = ["-Xep:DoNotMock:OFF"],
+    plugin = PLUGIN,
+    resources = glob(["src/test/resources/**/*"]),
+    tags = [
+        PLUGIN,
+        "local",
+    ],
+    deps = ["//plugins/high-availability"],
+)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..11069ed
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+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.
diff --git a/MODULE.bazel b/MODULE.bazel
new file mode 100644
index 0000000..c7faf88
--- /dev/null
+++ b/MODULE.bazel
@@ -0,0 +1,77 @@
+module(name = "gerrit-high-availability-gcp")
+
+bazel_dep(name = "rules_jvm_external", version = "6.10")
+bazel_dep(name = "com_googlesource_gerrit_bazlets")
+git_override(
+    module_name = "com_googlesource_gerrit_bazlets",
+    commit = "fe7c19542f8dc0a5720be3a93dbbdd05551003ec",
+    remote = "https://gerrit.googlesource.com/bazlets",
+)
+
+GERRIT_API_VERS = "3.13.5"
+
+gerrit_api_version = use_repo_rule("@com_googlesource_gerrit_bazlets//:gerrit_api_version.bzl", "gerrit_api_version")
+
+gerrit_api_version(
+    name = "gerrit_api_version",
+    version = GERRIT_API_VERS,
+    visibility = ["//visibility:public"],
+)
+
+maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
+maven.install(
+    name = "external_plugin_deps",
+    artifacts = [
+        "com.google.gerrit:gerrit-plugin-api:" + GERRIT_API_VERS,
+        "com.google.gerrit:gerrit-acceptance-framework:" + GERRIT_API_VERS,
+    ],
+    # IMPORTANT:
+    # Do NOT set lock_file here.
+    #
+    # This repository is intentionally shared across plugins and contains
+    # only Gerrit-provided APIs (gerrit-plugin-api and acceptance framework)
+    # that are provided by Gerrit at runtime and must not be bundled into
+    # the plugin JAR.
+    #
+    # When built in-tree, rules_jvm_external merges maven.install() tags
+    # with the same name across all modules. Defining a lock_file for this
+    # shared repository in more than one plugin causes multiple lock files
+    # to be contributed for the same repository name, which is rejected by
+    # rules_jvm_external during module extension evaluation.
+    #
+    # Plugin runtime dependencies must instead be declared in a
+    # plugin-specific repository (e.g. oauth_plugin_deps) that owns
+    # its own lock file.
+)
+use_repo(maven, "external_plugin_deps")
+
+maven.install(
+    name = "high-availability-gcp_plugin_deps",
+    artifacts = [
+        "com.google.cloud:google-cloud-pubsub:1.141.2",
+        "io.grpc:grpc-netty-shaded:1.74.0",
+        "org.testcontainers:gcloud:1.21.3",
+        "org.testcontainers:testcontainers:1.21.3",
+    ],
+    duplicate_version_warning = "error",
+    excluded_artifacts = [
+        "org.slf4j:slf4j-api",
+        "commons-codec:commons-codec",
+        "com.google.guava:failureaccess",
+        "com.google.code.gson:gson",
+        "org.apache.httpcomponents:httpclient",
+        "org.apache.httpcomponents:httpcore",
+        "com.google.j2objc:j2objc-annotations",
+        "com.google.code.findbugs:jsr305",
+        "com.google.protobuf:protobuf-java",
+    ],
+    fail_if_repin_required = True,
+    fail_on_missing_checksum = True,
+    fetch_sources = True,
+    lock_file = "//:high-availability-gcp_plugin_deps.lock.json",
+    repositories = [
+        "https://repo1.maven.org/maven2",
+    ],
+    version_conflict_policy = "pinned",
+)
+use_repo(maven, "high-availability-gcp_plugin_deps")
diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock
new file mode 100644
index 0000000..ea6318c
--- /dev/null
+++ b/MODULE.bazel.lock
@@ -0,0 +1,247 @@
+{
+  "lockFileVersion": 26,
+  "registryFileHashes": {
+    "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
+    "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
+    "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589",
+    "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0",
+    "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb",
+    "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16",
+    "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915",
+    "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.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.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",
+    "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d",
+    "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a",
+    "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.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",
+    "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686",
+    "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a",
+    "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5",
+    "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d",
+    "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651",
+    "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138",
+    "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",
+    "https://bcr.bazel.build/modules/bazel_worker_api/0.0.1/MODULE.bazel": "02a13b77321773b2042e70ee5e4c5e099c8ddee4cf2da9cd420442c36938d4bd",
+    "https://bcr.bazel.build/modules/bazel_worker_api/0.0.4/MODULE.bazel": "460aa12d01231a80cce03c548287b433b321d205b0028ae596728c35e5ee442e",
+    "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/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",
+    "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0",
+    "https://bcr.bazel.build/modules/gazelle/0.40.0/MODULE.bazel": "42ba5378ebe845fca43989a53186ab436d956db498acde790685fe0e8f9c6146",
+    "https://bcr.bazel.build/modules/gazelle/0.40.0/source.json": "1e5ef6e4d8b9b6836d93273c781e78ff829ea2e077afef7a57298040fa4f010a",
+    "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb",
+    "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4",
+    "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.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",
+    "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902",
+    "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74",
+    "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9",
+    "https://bcr.bazel.build/modules/package_metadata/0.0.7/MODULE.bazel": "7adb03933fc8401f495800cf4eafcff0edc6da0ff55c7db223ef69d19f689486",
+    "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.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",
+    "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c",
+    "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d",
+    "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/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",
+    "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206",
+    "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a",
+    "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/source.json": "2ff292be6ef3340325ce8a045ecc326e92cbfab47c7cbab4bd85d28971b97ac4",
+    "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa",
+    "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8",
+    "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/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",
+    "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc",
+    "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87",
+    "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a",
+    "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
+    "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
+    "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.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",
+    "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270",
+    "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd",
+    "https://bcr.bazel.build/modules/rules_go/0.50.1/MODULE.bazel": "b91a308dc5782bb0a8021ad4330c81fea5bda77f96b9e4c117b9b9c8f6665ee0",
+    "https://bcr.bazel.build/modules/rules_go/0.51.0-rc2/MODULE.bazel": "edfc3a9cea7bedb0eaaff37b0d7817c1a4bf72b3c615580b0ffcee6c52690fd4",
+    "https://bcr.bazel.build/modules/rules_go/0.51.0-rc2/source.json": "6b5cd0b3da2bd0e6949580851db990a04af0a285f072b9a0f059424457cd8cc9",
+    "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74",
+    "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86",
+    "https://bcr.bazel.build/modules/rules_java/6.3.0/MODULE.bazel": "a97c7678c19f236a956ad260d59c86e10a463badb7eb2eda787490f4c969b963",
+    "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31",
+    "https://bcr.bazel.build/modules/rules_java/7.1.0/MODULE.bazel": "30d9135a2b6561c761bd67bd4990da591e6bdc128790ce3e7afd6a3558b2fb64",
+    "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a",
+    "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6",
+    "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/8.13.0/MODULE.bazel": "0444ebf737d144cf2bb2ccb368e7f1cce735264285f2a3711785827c1686625e",
+    "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",
+    "https://bcr.bazel.build/modules/rules_jvm_external/6.10/MODULE.bazel": "33e636ca6bc9ee0fa090a38aa33c631ded2d8cf6fead4124181d1b35dc474f7c",
+    "https://bcr.bazel.build/modules/rules_jvm_external/6.10/source.json": "c191249787625db72616a3fb3cc2786ab57355a2e3b615402b8b3b66b0f995b7",
+    "https://bcr.bazel.build/modules/rules_jvm_external/6.2/MODULE.bazel": "36a6e52487a855f33cb960724eb56547fa87e2c98a0474c3acad94339d7f8e99",
+    "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0",
+    "https://bcr.bazel.build/modules/rules_jvm_external/6.6/MODULE.bazel": "153042249c7060536dc95b6bb9f9bb8063b8a0b0cb7acdb381bddbc2374aed55",
+    "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd",
+    "https://bcr.bazel.build/modules/rules_kotlin/1.9.5/MODULE.bazel": "043a16a572f610558ec2030db3ff0c9938574e7dd9f58bded1bb07c0192ef025",
+    "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3",
+    "https://bcr.bazel.build/modules/rules_kotlin/2.1.3/MODULE.bazel": "ce7def6d576aa8d3a9c6d10e13b4d157296229674371f67dbf788dae0afae3d5",
+    "https://bcr.bazel.build/modules/rules_kotlin/2.1.3/source.json": "0b0dc9400f14b5fbb13d278ad3bf0413cdbaf0da0db337e055b855e35b878a3b",
+    "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0",
+    "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d",
+    "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c",
+    "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb",
+    "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
+    "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff",
+    "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a",
+    "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06",
+    "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7",
+    "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483",
+    "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.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.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",
+    "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58",
+    "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937",
+    "https://bcr.bazel.build/modules/rules_python/0.37.1/MODULE.bazel": "3faeb2d9fa0a81f8980643ee33f212308f4d93eea4b9ce6f36d0b742e71e9500",
+    "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",
+    "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/source.json": "37c10335f2361c337c5c1f34ed36d2da70534c23088062b33a8bdaab68aa9dea",
+    "https://bcr.bazel.build/modules/rules_shell/0.1.2/MODULE.bazel": "66e4ca3ce084b04af0b9ff05ff14cab4e5df7503973818bb91cbc6cda08d32fc",
+    "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.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.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",
+    "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c",
+    "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.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.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": {},
+  "facts": {}
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..99ed80b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,52 @@
+# Google GCP pubsub messaging for the Gerrit's high-availability plugin
+
+This plugin provides an extension of the Gerrit's high-availability plugin:
+it provides GCP pubsub based message forwarding.
+
+## License
+
+This plugin is released under the same Apache 2.0 license and copyright holders
+as of the Gerrit Code Review project.
+
+## How to build
+
+Refer to the [build instructions in the plugin documentation](src/main/resources/Documentation/build.md).
+
+## Installation
+
+Install
+
+- the `high-availability-gcp` plugin in the Gerrit site's `plugins` folder.
+
+## Sample configuration
+
+This is a minimal configuration:
+```
+[pubsub]
+  projectId = my-gcp-project
+  privateKeyLocation = /path/to/private/key/my-gcp-project-6f0d80a3f655.json
+```
+
+A complete set of configuration options is given in the following example.
+The example values given here are also the default values:
+```
+[pubsub]
+  projectId = my-gcp-project
+  privateKeyLocation = /path/to/private/key/my-gcp-project-6f0d80a3f655.json
+[pubsub "topics"]
+  main = default
+  streamEvents = stream-events
+[pubsub "limits"]
+  subscriptionTimeout = 10s
+  shutdownTimeout = 10s
+  publisherThreadPoolSize = 4
+  subscriberThreadPoolSize = 4
+[pubsub "retention"]
+  ackDeadline = 10s
+  messageRetentionDuration = 7d
+  retainAckedMessages = false
+[pubsub "retry"]
+  maxDeliveryAttempts = 5
+  minimumBackoff = 10s
+  maximumBackoff = 10m
+```
diff --git a/external_plugin_deps.MODULE.bazel b/external_plugin_deps.MODULE.bazel
new file mode 100644
index 0000000..86e1baf
--- /dev/null
+++ b/external_plugin_deps.MODULE.bazel
@@ -0,0 +1,19 @@
+# Module fragment for wiring this plugin's Bazel module into the
+# Gerrit in-tree build when using Bzlmod.
+#
+# This file is included from Gerrit's plugins/external_plugin_deps.MODULE.bazel.
+# It wires the plugin module into the root dependency graph and exposes
+# any repositories it creates (for example via rules_jvm_external).
+#
+# NOTE: This file must be self-contained: bindings do not cross include()
+# boundaries, so it re-declares the rules_jvm_external extension handle.
+
+maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
+
+bazel_dep(name = "gerrit-high-availability-gcp")
+local_path_override(
+    module_name = "gerrit-high-availability-gcp",
+    path = "plugins/high-availability-gcp",
+)
+
+use_repo(maven, "high-availability-gcp_plugin_deps")
diff --git a/high-availability-gcp_plugin_deps.lock.json b/high-availability-gcp_plugin_deps.lock.json
new file mode 100644
index 0000000..90c47c7
--- /dev/null
+++ b/high-availability-gcp_plugin_deps.lock.json
@@ -0,0 +1,1964 @@
+{
+  "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
+  "__INPUT_ARTIFACTS_HASH": {
+    "com.google.cloud:google-cloud-pubsub": -666200535,
+    "com.google.code.findbugs:jsr305": -313338248,
+    "com.google.code.gson:gson": -1752757951,
+    "com.google.guava:failureaccess": -1493823684,
+    "com.google.j2objc:j2objc-annotations": 1772577837,
+    "com.google.protobuf:protobuf-java": 1230241843,
+    "commons-codec:commons-codec": 284109768,
+    "io.grpc:grpc-netty-shaded": 155833209,
+    "org.apache.httpcomponents:httpclient": 1407535949,
+    "org.apache.httpcomponents:httpcore": -1485416135,
+    "org.slf4j:slf4j-api": 2145585153,
+    "org.testcontainers:gcloud": -1907533275,
+    "org.testcontainers:testcontainers": -14143717,
+    "repositories": -1949687017
+  },
+  "__RESOLVED_ARTIFACTS_HASH": {
+    "com.fasterxml.jackson.core:jackson-annotations": 895697199,
+    "com.fasterxml.jackson.core:jackson-annotations:jar:sources": 486697886,
+    "com.github.docker-java:docker-java-api": 1886720023,
+    "com.github.docker-java:docker-java-api:jar:sources": 1672381514,
+    "com.github.docker-java:docker-java-transport": -1400224799,
+    "com.github.docker-java:docker-java-transport-zerodep": 2022449988,
+    "com.github.docker-java:docker-java-transport-zerodep:jar:sources": -887161140,
+    "com.github.docker-java:docker-java-transport:jar:sources": -2085127734,
+    "com.google.android:annotations": 2096496032,
+    "com.google.android:annotations:jar:sources": -1797343139,
+    "com.google.api.grpc:proto-google-cloud-pubsub-v1": 1690392012,
+    "com.google.api.grpc:proto-google-cloud-pubsub-v1:jar:sources": -669655515,
+    "com.google.api.grpc:proto-google-common-protos": -1441949323,
+    "com.google.api.grpc:proto-google-common-protos:jar:sources": 1215634766,
+    "com.google.api.grpc:proto-google-iam-v1": -558695712,
+    "com.google.api.grpc:proto-google-iam-v1:jar:sources": 1976296040,
+    "com.google.api:api-common": 1628753455,
+    "com.google.api:api-common:jar:sources": 1715693714,
+    "com.google.api:gax": 1730969374,
+    "com.google.api:gax-grpc": -1868240732,
+    "com.google.api:gax-grpc:jar:sources": 1912967538,
+    "com.google.api:gax-httpjson": 1430230691,
+    "com.google.api:gax-httpjson:jar:sources": 1762375533,
+    "com.google.api:gax:jar:sources": 795439571,
+    "com.google.auth:google-auth-library-credentials": 1377746169,
+    "com.google.auth:google-auth-library-credentials:jar:sources": -1077072211,
+    "com.google.auth:google-auth-library-oauth2-http": -851052611,
+    "com.google.auth:google-auth-library-oauth2-http:jar:sources": 4332143,
+    "com.google.auto.value:auto-value-annotations": 641018093,
+    "com.google.auto.value:auto-value-annotations:jar:sources": -315473772,
+    "com.google.cloud:google-cloud-pubsub": 1109898268,
+    "com.google.cloud:google-cloud-pubsub:jar:sources": 904098729,
+    "com.google.errorprone:error_prone_annotations": 1571630350,
+    "com.google.errorprone:error_prone_annotations:jar:sources": -1864520284,
+    "com.google.guava:guava": 311887683,
+    "com.google.guava:guava:jar:sources": -1734233239,
+    "com.google.guava:listenablefuture": 1079558157,
+    "com.google.http-client:google-http-client": -1810196085,
+    "com.google.http-client:google-http-client-gson": -1986082497,
+    "com.google.http-client:google-http-client-gson:jar:sources": 1471652239,
+    "com.google.http-client:google-http-client:jar:sources": -688965411,
+    "com.google.protobuf:protobuf-java-util": 1292251753,
+    "com.google.protobuf:protobuf-java-util:jar:sources": 799127235,
+    "com.google.re2j:re2j": -1688126233,
+    "com.google.re2j:re2j:jar:sources": 485338798,
+    "io.grpc:grpc-alts": -1260622287,
+    "io.grpc:grpc-alts:jar:sources": -1070738474,
+    "io.grpc:grpc-api": 76065503,
+    "io.grpc:grpc-api:jar:sources": -57794343,
+    "io.grpc:grpc-auth": -412554713,
+    "io.grpc:grpc-auth:jar:sources": 1647941284,
+    "io.grpc:grpc-context": 985134038,
+    "io.grpc:grpc-context:jar:sources": 1947839703,
+    "io.grpc:grpc-core": 1134603238,
+    "io.grpc:grpc-core:jar:sources": 975172552,
+    "io.grpc:grpc-googleapis": 1053062773,
+    "io.grpc:grpc-googleapis:jar:sources": 483689619,
+    "io.grpc:grpc-grpclb": 1592256060,
+    "io.grpc:grpc-grpclb:jar:sources": -1258079685,
+    "io.grpc:grpc-inprocess": 1288958811,
+    "io.grpc:grpc-inprocess:jar:sources": -368478985,
+    "io.grpc:grpc-netty-shaded": -121118512,
+    "io.grpc:grpc-netty-shaded:jar:sources": 1947839703,
+    "io.grpc:grpc-protobuf": -126028522,
+    "io.grpc:grpc-protobuf-lite": -446621013,
+    "io.grpc:grpc-protobuf-lite:jar:sources": -591062309,
+    "io.grpc:grpc-protobuf:jar:sources": -1494054936,
+    "io.grpc:grpc-services": 186142787,
+    "io.grpc:grpc-services:jar:sources": -35620260,
+    "io.grpc:grpc-stub": -1726880885,
+    "io.grpc:grpc-stub:jar:sources": 648807495,
+    "io.grpc:grpc-util": -680552857,
+    "io.grpc:grpc-util:jar:sources": 1289119925,
+    "io.grpc:grpc-xds": -532069961,
+    "io.grpc:grpc-xds:jar:sources": -25639278,
+    "io.opencensus:opencensus-api": -26497261,
+    "io.opencensus:opencensus-api:jar:sources": 1085904850,
+    "io.opencensus:opencensus-contrib-http-util": -28003592,
+    "io.opencensus:opencensus-contrib-http-util:jar:sources": -879181311,
+    "io.opentelemetry:opentelemetry-api": -1533216960,
+    "io.opentelemetry:opentelemetry-api:jar:sources": 249136,
+    "io.opentelemetry:opentelemetry-context": 11027445,
+    "io.opentelemetry:opentelemetry-context:jar:sources": -97118549,
+    "io.opentelemetry:opentelemetry-semconv": 450634812,
+    "io.opentelemetry:opentelemetry-semconv:jar:sources": 1211550242,
+    "io.perfmark:perfmark-api": -1795841558,
+    "io.perfmark:perfmark-api:jar:sources": -1882329067,
+    "javax.annotation:javax.annotation-api": -1009230154,
+    "javax.annotation:javax.annotation-api:jar:sources": -1912277688,
+    "junit:junit": -1256429642,
+    "junit:junit:jar:sources": 940567721,
+    "net.java.dev.jna:jna": 82863035,
+    "net.java.dev.jna:jna:jar:sources": -1071055013,
+    "org.apache.commons:commons-compress": 176829,
+    "org.apache.commons:commons-compress:jar:sources": -2012564756,
+    "org.checkerframework:checker-qual": -1482757721,
+    "org.checkerframework:checker-qual:jar:sources": -677321624,
+    "org.codehaus.mojo:animal-sniffer-annotations": -1619058809,
+    "org.codehaus.mojo:animal-sniffer-annotations:jar:sources": 83684044,
+    "org.conscrypt:conscrypt-openjdk-uber": 742580967,
+    "org.conscrypt:conscrypt-openjdk-uber:jar:sources": 2090280717,
+    "org.hamcrest:hamcrest-core": 649657847,
+    "org.hamcrest:hamcrest-core:jar:sources": -1646511374,
+    "org.jetbrains:annotations": -845966580,
+    "org.jetbrains:annotations:jar:sources": 582621361,
+    "org.rnorth.duct-tape:duct-tape": -1059401544,
+    "org.rnorth.duct-tape:duct-tape:jar:sources": -1798015648,
+    "org.testcontainers:gcloud": 174145667,
+    "org.testcontainers:gcloud:jar:sources": -490222159,
+    "org.testcontainers:testcontainers": -186126913,
+    "org.testcontainers:testcontainers:jar:sources": 41064557,
+    "org.threeten:threetenbp": 1406815789,
+    "org.threeten:threetenbp:jar:sources": 1625133572
+  },
+  "artifacts": {
+    "com.fasterxml.jackson.core:jackson-annotations": {
+      "shasums": {
+        "jar": "49dfdc4cfa46d165ecfed630ba164b6641d59d5fe1aa698a19c13f966d3f13cf",
+        "sources": "6310be2ded72eed441291587ed4513c93566b5057b67d05d04cb6d1db4c726b2"
+      },
+      "version": "2.10.3"
+    },
+    "com.github.docker-java:docker-java-api": {
+      "shasums": {
+        "jar": "6789d68f95904cf274a6410fdfcc5530976bc94197c856909459bb8c64db557b",
+        "sources": "1a407574c5c7bf2170d7c9bb76db7341b43b3ee06dc44e215c31356a1d9f3c91"
+      },
+      "version": "3.4.2"
+    },
+    "com.github.docker-java:docker-java-transport": {
+      "shasums": {
+        "jar": "bc9981224fdc2d4726566fc723470601ceb14378265c865b4c203146a4e39765",
+        "sources": "a50fd13efc29146e047ac2a34403172ed62bf52752252286335df1b3e5cf1d18"
+      },
+      "version": "3.4.2"
+    },
+    "com.github.docker-java:docker-java-transport-zerodep": {
+      "shasums": {
+        "jar": "582b772e0c52fa9d24710617ef610655465edd9a2930b0db75fc8667d6a8d02b",
+        "sources": "c88c4745337a54eeae73788cdba9b1a80cebbd3cadfc701c3f8a9e7d217da314"
+      },
+      "version": "3.4.2"
+    },
+    "com.google.android:annotations": {
+      "shasums": {
+        "jar": "ba734e1e84c09d615af6a09d33034b4f0442f8772dec120efb376d86a565ae15",
+        "sources": "e9b667aa958df78ea1ad115f7bbac18a5869c3128b1d5043feb360b0cfce9d40"
+      },
+      "version": "4.1.1.4"
+    },
+    "com.google.api.grpc:proto-google-cloud-pubsub-v1": {
+      "shasums": {
+        "jar": "986670409707c11d0904a8a486a44040e2b1e50e60263fd075f2665d06113216",
+        "sources": "553a710bbb211c82e59f27969d7632d0c551fa1cbe96075c1672ad57362d03e4"
+      },
+      "version": "1.123.2"
+    },
+    "com.google.api.grpc:proto-google-common-protos": {
+      "shasums": {
+        "jar": "6655d67f916b4afebc8096c9a311b75a9cbcc1327ff2660681f7de8802d541e9",
+        "sources": "e9aba88ca9c4561205ed42023a650837d429ed3e27e2f21c333840fad1d03745"
+      },
+      "version": "2.60.0"
+    },
+    "com.google.api.grpc:proto-google-iam-v1": {
+      "shasums": {
+        "jar": "c09717925cd7280d3734dcce2f457aca98b56298d011da56e23314e8c7aafe18",
+        "sources": "999eb7be405e798c0489edfb6f4d505162dd7736894e80c5b5284f1a24d71ee6"
+      },
+      "version": "1.55.0"
+    },
+    "com.google.api:api-common": {
+      "shasums": {
+        "jar": "ace161b3aaa028ab54531e7c723a6f29c9fbc2db985b96412b2d3f17083f6937",
+        "sources": "cdcaa91e85bfefa42eedf594170da2354932aead98908e3b64955ea2cba03688"
+      },
+      "version": "2.52.0"
+    },
+    "com.google.api:gax": {
+      "shasums": {
+        "jar": "a0d891df8be06cebd8d05cd9c203cfeae166dd4d827730b58c4c859ac65f4a0b",
+        "sources": "5fa890b3faa9226cca3ac874c0c32881d942bc7fc60712d8a6e05cd24b4b5097"
+      },
+      "version": "2.69.0"
+    },
+    "com.google.api:gax-grpc": {
+      "shasums": {
+        "jar": "14ba424415c7cb370270375361d9d9288e015e8ccf59dd6ad37351b8b928b6a3",
+        "sources": "637747f8e32dca055ec75b5f57be27b01f94378f20ba7b71c953b7bccd33ace1"
+      },
+      "version": "2.69.0"
+    },
+    "com.google.api:gax-httpjson": {
+      "shasums": {
+        "jar": "c407b230c2ccce390caabfb7248f71b218e06fa46b313086300a6812565773e6",
+        "sources": "b604b0dba283a28e43a4d70e940e767d59f544159fd872da9495b1b4e50fc9fe"
+      },
+      "version": "2.69.0"
+    },
+    "com.google.auth:google-auth-library-credentials": {
+      "shasums": {
+        "jar": "1b498e9aaaf661b24afc460939055df3f4147a458ca453f22f710f1ef48fcee9",
+        "sources": "c084573686f0f1f4ff0a585dff5fa2a3511b483087b2bfc016812ab3fb17115e"
+      },
+      "version": "1.37.1"
+    },
+    "com.google.auth:google-auth-library-oauth2-http": {
+      "shasums": {
+        "jar": "bbb0425a2f95fdb3012785b6e5d20a75026a0b1596b9f5a6a40539f513c12da0",
+        "sources": "51e6b2489360aa3b990409d4e2f142d4f34e6fa93ed443a2d4d13e84de329434"
+      },
+      "version": "1.37.1"
+    },
+    "com.google.auto.value:auto-value-annotations": {
+      "shasums": {
+        "jar": "5a055ce4255333b3346e1a8703da5bf8ff049532286fdcd31712d624abe111dd",
+        "sources": "d7941e5f19bb38afcfa85350d57e5245856c23c98c2bbe32f6d31b5577f2bc33"
+      },
+      "version": "1.11.0"
+    },
+    "com.google.cloud:google-cloud-pubsub": {
+      "shasums": {
+        "jar": "2547f9ba75eeff6d9b175a3e72e796bc9da6d3b5f57f7a5d637f3722dc5762fe",
+        "sources": "80bf17ce3348b3d2ba01efcd20f6dbf7c7f99a70b2c45367fd9cc7e44d9f2889"
+      },
+      "version": "1.141.2"
+    },
+    "com.google.errorprone:error_prone_annotations": {
+      "shasums": {
+        "jar": "6661d5335090a5fc61dd869d2095bc6c1e2156e3aa47a6e4ababdf64c99a7889",
+        "sources": "bc4e1535cf5a236ca2e2cfb66d76da4cea99ae52d7447f57a2b05943cac21747"
+      },
+      "version": "2.38.0"
+    },
+    "com.google.guava:guava": {
+      "shasums": {
+        "jar": "b918c98a7e44dbe94ebd9fe3e40cddaadb5a93e6a78eb6008b42df237241e538",
+        "sources": "55ef6603b6ab1f6e3ae810b127561650ed682eb5f3fb50a212a658a74087b457"
+      },
+      "version": "33.4.0-jre"
+    },
+    "com.google.guava:listenablefuture": {
+      "shasums": {
+        "jar": "b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99"
+      },
+      "version": "9999.0-empty-to-avoid-conflict-with-guava"
+    },
+    "com.google.http-client:google-http-client": {
+      "shasums": {
+        "jar": "22447fde9f2e33e27a23a25953b1c622ead6c055c761fde6ca50573c9473457a",
+        "sources": "7fd1f5c075db280e2a878bf14c7da248e2984e6411a509bcad3e667dd3dd936d"
+      },
+      "version": "1.47.1"
+    },
+    "com.google.http-client:google-http-client-gson": {
+      "shasums": {
+        "jar": "64ac2b1313dca6b6fc9bd14128ab186528fe992b094f5371fa7f828eed8903bd",
+        "sources": "f1cd1055487dd85133f3665645c9a06a892664a500b6fe7a3e4eef4c2cefdbaf"
+      },
+      "version": "1.47.1"
+    },
+    "com.google.protobuf:protobuf-java-util": {
+      "shasums": {
+        "jar": "e7196eaefc3f81afa1571821635c7aefe2933f4be4a9df9e601fb59214b50269",
+        "sources": "ddfec53ff55c50f9017c3f4b9fba20253df6f59b1e5439a8cc74efff0f2366d2"
+      },
+      "version": "3.25.8"
+    },
+    "com.google.re2j:re2j": {
+      "shasums": {
+        "jar": "7b52c72156dd7f98b3237a5b35c1d34fba381b21048c89208913ad80a45dfbd7",
+        "sources": "4378db846db2d91c8028fe32efcb183a1f1b4157339d0a3b4c46b2c6b0655d7c"
+      },
+      "version": "1.8"
+    },
+    "io.grpc:grpc-alts": {
+      "shasums": {
+        "jar": "c0671401abc87cf6bd6cdb58a0bacf29aff0eb720d4d0f32373c128208cbe8fd",
+        "sources": "f6629f36e3b795d338f8f89273aff4e2f5f92da8fa435c1258c83a89ee9a249d"
+      },
+      "version": "1.71.0"
+    },
+    "io.grpc:grpc-api": {
+      "shasums": {
+        "jar": "7c2c39b76fd50ea4f58a7f4c12e82640e355fc92ca025be48072be1bcc2b177d",
+        "sources": "595571ec768ccbc3984fd080d184cfc4e0a57c36ae3ca7eed83a31b7293e8c33"
+      },
+      "version": "1.74.0"
+    },
+    "io.grpc:grpc-auth": {
+      "shasums": {
+        "jar": "1968885f6034075fcc49ef66ae6cd24d7ee6a44243779b1582dd715a3e84f1c4",
+        "sources": "99e071b6f01d657d462c55925d1c1502487ac0e74d1b570ccb017b48eede0141"
+      },
+      "version": "1.71.0"
+    },
+    "io.grpc:grpc-context": {
+      "shasums": {
+        "jar": "fec0773020404a1f9b9610168692c8ed47fb77181c2ba37cc842ba7664bfc6b0",
+        "sources": "0a45ca2dc3452783be72412c0e75e0a57c276e76d29d778a22e331279f08d5c0"
+      },
+      "version": "1.74.0"
+    },
+    "io.grpc:grpc-core": {
+      "shasums": {
+        "jar": "5175e6223ee1f0a63cf93049aabe7bd5b675cc739b286fe3b320a88fa90c320e",
+        "sources": "2c7a8d053a2bfa7c423ca01f1b6462bf402ff710aea8c49a73c2d0d73ba8e358"
+      },
+      "version": "1.74.0"
+    },
+    "io.grpc:grpc-googleapis": {
+      "shasums": {
+        "jar": "44277ccc6c9d3bc9cd3f1e17689c83168a72b132c00f56f93a7d6e1b9984ee5d",
+        "sources": "7909fa9cd191ba52e23d6442e369a068fa8bc8fed35b56bf884decbf179d2838"
+      },
+      "version": "1.71.0"
+    },
+    "io.grpc:grpc-grpclb": {
+      "shasums": {
+        "jar": "57867a44967669ea2a024ec97f3c497a9cdc7c73d06cee0f0e19185ef88a38fe",
+        "sources": "50f6cb31a4462bd8c6c00ef2b7e458fbf2b2d132c156989899191d67b73bad55"
+      },
+      "version": "1.71.0"
+    },
+    "io.grpc:grpc-inprocess": {
+      "shasums": {
+        "jar": "86c7de6e38cd3ea40b3720a037f58069c65f1a5d291aea370261eeebfe0557af",
+        "sources": "2d1dcf37332d23340411197c675a6831414eec1450a9829d58b84a4ce03cfc99"
+      },
+      "version": "1.71.0"
+    },
+    "io.grpc:grpc-netty-shaded": {
+      "shasums": {
+        "jar": "aecca383751c69d3fde1bdfc0d74e845a76f83887041440fc9e194a5f07e6c8f",
+        "sources": "0a45ca2dc3452783be72412c0e75e0a57c276e76d29d778a22e331279f08d5c0"
+      },
+      "version": "1.74.0"
+    },
+    "io.grpc:grpc-protobuf": {
+      "shasums": {
+        "jar": "7eba0625fba8e176c0fc1b80610fc2c52c036d95805ffdd3f6985a51c968de94",
+        "sources": "f56c4b166cfb083a2108857cb80eabb6a79a7856c0674490e4fa9849b29a7ff8"
+      },
+      "version": "1.71.0"
+    },
+    "io.grpc:grpc-protobuf-lite": {
+      "shasums": {
+        "jar": "ffad977400b49649fa2d5404abaadfca66c7e5956974f192411e8946a12e5f53",
+        "sources": "5cbc25c897d26e0cdeca771a79ad2d990b558957319cdc3856f13b8753f2a69a"
+      },
+      "version": "1.71.0"
+    },
+    "io.grpc:grpc-services": {
+      "shasums": {
+        "jar": "e966ba062b6793083ab163c38103d4fcbc71ed9f91db4bd81b30fcb975a0a845",
+        "sources": "2bd30bcc48fb39755743f7241add8ba17188f14a3b84ddfa052c1e6455e98065"
+      },
+      "version": "1.71.0"
+    },
+    "io.grpc:grpc-stub": {
+      "shasums": {
+        "jar": "51912e612b28db65eec0332a8be494b3c54d347ecd2c5f6a7fdd2cb994fab360",
+        "sources": "0cb35ee903b8bf016a719bcd00362c7dc8f25600bd0732192c7c95f1cec16e20"
+      },
+      "version": "1.71.0"
+    },
+    "io.grpc:grpc-util": {
+      "shasums": {
+        "jar": "296dc07438c43454afc1a7f2736086550feb1fc783f9547337ad2ae721e24350",
+        "sources": "ba7b02ef3f3a7ed93881c1c37db8b7372458215c7e2e7d57d1a7fc91725d94e1"
+      },
+      "version": "1.74.0"
+    },
+    "io.grpc:grpc-xds": {
+      "shasums": {
+        "jar": "b570b80809531410f965cc04e29466da1c338da434db5fcaa29b822d40fe80b5",
+        "sources": "011f131d452ebfbc749cff6780e2d1a812caea2deb75b8d8ce21cf778b66b66a"
+      },
+      "version": "1.71.0"
+    },
+    "io.opencensus:opencensus-api": {
+      "shasums": {
+        "jar": "f1474d47f4b6b001558ad27b952e35eda5cc7146788877fc52938c6eba24b382",
+        "sources": "6748d57aaae81995514ad3e2fb11a95aa88e158b3f93450288018eaccf31e86b"
+      },
+      "version": "0.31.1"
+    },
+    "io.opencensus:opencensus-contrib-http-util": {
+      "shasums": {
+        "jar": "3ea995b55a4068be22989b70cc29a4d788c2d328d1d50613a7a9afd13fdd2d0a",
+        "sources": "d55afd5f96dc724bd903a77a38b0a344d0e59f02a64b9ab2f32618bc582ea924"
+      },
+      "version": "0.31.1"
+    },
+    "io.opentelemetry:opentelemetry-api": {
+      "shasums": {
+        "jar": "6566f1f1133d611ff4e8b8fdb8eb18577b970425620315363ee9be43843b14bf",
+        "sources": "1e7bac30757e9504ea737c4be341130d8f013a3290059022156bce1a06d64546"
+      },
+      "version": "1.47.0"
+    },
+    "io.opentelemetry:opentelemetry-context": {
+      "shasums": {
+        "jar": "15b4fc4234e6dca6d54800d572694ecbd07ba52c15fc5b221b4da5517ce8d90d",
+        "sources": "7c959f00d13fc1698a7d37878715e6c42b5ee89b2076a324ffaf8de5b9348b2b"
+      },
+      "version": "1.47.0"
+    },
+    "io.opentelemetry:opentelemetry-semconv": {
+      "shasums": {
+        "jar": "b57721963af36e72f10d7f882bb247b775f8d182777cfe8b1907b40282ff47eb",
+        "sources": "4e9077a1e6167f8bfe114cb99a8c706bfa85048000364ea3897864f3fa0b5e01"
+      },
+      "version": "1.26.0-alpha"
+    },
+    "io.perfmark:perfmark-api": {
+      "shasums": {
+        "jar": "c7b478503ec524e55df19b424d46d27c8a68aeb801664fadd4f069b71f52d0f6",
+        "sources": "311551ab29cf51e5a8abee6a019e88dee47d1ea71deb9fcd3649db9c51b237bc"
+      },
+      "version": "0.27.0"
+    },
+    "javax.annotation:javax.annotation-api": {
+      "shasums": {
+        "jar": "e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b",
+        "sources": "128971e52e0d84a66e3b6e049dab8ad7b2c58b7e1ad37fa2debd3d40c2947b95"
+      },
+      "version": "1.3.2"
+    },
+    "junit:junit": {
+      "shasums": {
+        "jar": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3",
+        "sources": "34181df6482d40ea4c046b063cb53c7ffae94bdf1b1d62695bdf3adf9dea7e3a"
+      },
+      "version": "4.13.2"
+    },
+    "net.java.dev.jna:jna": {
+      "shasums": {
+        "jar": "66d4f819a062a51a1d5627bffc23fac55d1677f0e0a1feba144aabdd670a64bb",
+        "sources": "a4c45843e8f60df141c4f37602365a421bb278ca1ef30ba0a043d6a871dd29f4"
+      },
+      "version": "5.13.0"
+    },
+    "org.apache.commons:commons-compress": {
+      "shasums": {
+        "jar": "fbf2c5d275e513c08f4e4fb539726dbf3ac2142af956ba93efb983244f0c36bd",
+        "sources": "b79da77c5bb6e9b528d60c7336a59dc08cab29baac52f3d35441455c5fec4db1"
+      },
+      "version": "1.24.0"
+    },
+    "org.checkerframework:checker-qual": {
+      "shasums": {
+        "jar": "8b9d9a36eaaf7c0fc26503c83cd97d8c9c0f9e2913cc2a6e92ac26c735d4dcbe",
+        "sources": "546424b9b019b3d5b16716ec280cfc4e23b25feebecc2b60f9721d1fab6635d5"
+      },
+      "version": "3.49.0"
+    },
+    "org.codehaus.mojo:animal-sniffer-annotations": {
+      "shasums": {
+        "jar": "c720e6e5bcbe6b2f48ded75a47bccdb763eede79d14330102e0d352e3d89ed92",
+        "sources": "4270ce5531ed0f12e4234e08f240ef3b45ee3ceeb16e28d44abc61c12cf522ca"
+      },
+      "version": "1.24"
+    },
+    "org.conscrypt:conscrypt-openjdk-uber": {
+      "shasums": {
+        "jar": "eaf537d98e033d0f0451cd1b8cc74e02d7b55ec882da63c88060d806ba89c348",
+        "sources": "aa1d02e65351e202e83ece0614bce1022aa1da6e77313ef7c7663ab45fa9e3a5"
+      },
+      "version": "2.5.2"
+    },
+    "org.hamcrest:hamcrest-core": {
+      "shasums": {
+        "jar": "66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9",
+        "sources": "e223d2d8fbafd66057a8848cc94222d63c3cedd652cc48eddc0ab5c39c0f84df"
+      },
+      "version": "1.3"
+    },
+    "org.jetbrains:annotations": {
+      "shasums": {
+        "jar": "195fb0da046d55bb042e91543484cf1da68b02bb7afbfe031f229e45ac84b3f2",
+        "sources": "b2c0d02e0a32c56d359e99634e7d769f9b1a8cd6e25061995abad1c1baf86f56"
+      },
+      "version": "17.0.0"
+    },
+    "org.rnorth.duct-tape:duct-tape": {
+      "shasums": {
+        "jar": "31cef12ddec979d1f86d7cf708c41a17da523d05c685fd6642e9d0b2addb7240",
+        "sources": "b385fd2c2b435c313b3f02988d351503230c9631bfb432261cbd8ce9765d2a26"
+      },
+      "version": "1.0.8"
+    },
+    "org.testcontainers:gcloud": {
+      "shasums": {
+        "jar": "795970b08782e997deff916f25b9aadb05285b4a273bbc70ad5040bfff127ddd",
+        "sources": "116e67a7f29601ee942206a4430c207f66707950aa598c533dfa27394fa51229"
+      },
+      "version": "1.21.3"
+    },
+    "org.testcontainers:testcontainers": {
+      "shasums": {
+        "jar": "ef934ddac6f42759d71c303a2844544a56cf381b33437149955d3d8c992bec5d",
+        "sources": "e594cd4f3f95dfec1dff67e3ba4eabb2f1ef818238e35d1aea611939d3a0bb69"
+      },
+      "version": "1.21.3"
+    },
+    "org.threeten:threetenbp": {
+      "shasums": {
+        "jar": "857917d2319a4e92dc1c5e3aeb75a0dac84445ed315e7ac3d82bb8d2b298977f",
+        "sources": "b4d3602a948a10ea275991d4144c3cbbd9b9000bd3b58dfc7b74b240688ca4a9"
+      },
+      "version": "1.7.0"
+    }
+  },
+  "dependencies": {
+    "com.github.docker-java:docker-java-api": [
+      "com.fasterxml.jackson.core:jackson-annotations"
+    ],
+    "com.github.docker-java:docker-java-transport-zerodep": [
+      "com.github.docker-java:docker-java-transport",
+      "net.java.dev.jna:jna"
+    ],
+    "com.google.api.grpc:proto-google-cloud-pubsub-v1": [
+      "com.google.auto.value:auto-value-annotations",
+      "com.google.errorprone:error_prone_annotations",
+      "com.google.guava:listenablefuture",
+      "javax.annotation:javax.annotation-api",
+      "org.checkerframework:checker-qual"
+    ],
+    "com.google.cloud:google-cloud-pubsub": [
+      "com.google.android:annotations",
+      "com.google.api.grpc:proto-google-cloud-pubsub-v1",
+      "com.google.api.grpc:proto-google-common-protos",
+      "com.google.api.grpc:proto-google-iam-v1",
+      "com.google.api:api-common",
+      "com.google.api:gax",
+      "com.google.api:gax-grpc",
+      "com.google.api:gax-httpjson",
+      "com.google.auth:google-auth-library-credentials",
+      "com.google.auth:google-auth-library-oauth2-http",
+      "com.google.auto.value:auto-value-annotations",
+      "com.google.errorprone:error_prone_annotations",
+      "com.google.guava:guava",
+      "com.google.guava:listenablefuture",
+      "com.google.http-client:google-http-client",
+      "com.google.http-client:google-http-client-gson",
+      "com.google.protobuf:protobuf-java-util",
+      "com.google.re2j:re2j",
+      "io.grpc:grpc-alts",
+      "io.grpc:grpc-api",
+      "io.grpc:grpc-auth",
+      "io.grpc:grpc-context",
+      "io.grpc:grpc-core",
+      "io.grpc:grpc-googleapis",
+      "io.grpc:grpc-grpclb",
+      "io.grpc:grpc-inprocess",
+      "io.grpc:grpc-netty-shaded",
+      "io.grpc:grpc-protobuf",
+      "io.grpc:grpc-protobuf-lite",
+      "io.grpc:grpc-services",
+      "io.grpc:grpc-stub",
+      "io.grpc:grpc-util",
+      "io.grpc:grpc-xds",
+      "io.opencensus:opencensus-api",
+      "io.opencensus:opencensus-contrib-http-util",
+      "io.opentelemetry:opentelemetry-api",
+      "io.opentelemetry:opentelemetry-context",
+      "io.opentelemetry:opentelemetry-semconv",
+      "io.perfmark:perfmark-api",
+      "javax.annotation:javax.annotation-api",
+      "org.checkerframework:checker-qual",
+      "org.codehaus.mojo:animal-sniffer-annotations",
+      "org.conscrypt:conscrypt-openjdk-uber",
+      "org.threeten:threetenbp"
+    ],
+    "com.google.guava:guava": [
+      "com.google.errorprone:error_prone_annotations",
+      "com.google.guava:listenablefuture",
+      "org.checkerframework:checker-qual"
+    ],
+    "io.grpc:grpc-api": [
+      "com.google.errorprone:error_prone_annotations",
+      "com.google.guava:guava"
+    ],
+    "io.grpc:grpc-context": [
+      "io.grpc:grpc-api"
+    ],
+    "io.grpc:grpc-core": [
+      "com.google.android:annotations",
+      "com.google.errorprone:error_prone_annotations",
+      "com.google.guava:guava",
+      "io.grpc:grpc-api",
+      "io.grpc:grpc-context",
+      "io.perfmark:perfmark-api",
+      "org.codehaus.mojo:animal-sniffer-annotations"
+    ],
+    "io.grpc:grpc-netty-shaded": [
+      "com.google.errorprone:error_prone_annotations",
+      "com.google.guava:guava",
+      "io.grpc:grpc-api",
+      "io.grpc:grpc-core",
+      "io.grpc:grpc-util",
+      "io.perfmark:perfmark-api",
+      "org.codehaus.mojo:animal-sniffer-annotations"
+    ],
+    "io.grpc:grpc-util": [
+      "com.google.guava:guava",
+      "io.grpc:grpc-api",
+      "io.grpc:grpc-core",
+      "org.codehaus.mojo:animal-sniffer-annotations"
+    ],
+    "junit:junit": [
+      "org.hamcrest:hamcrest-core"
+    ],
+    "org.rnorth.duct-tape:duct-tape": [
+      "org.jetbrains:annotations"
+    ],
+    "org.testcontainers:gcloud": [
+      "org.testcontainers:testcontainers"
+    ],
+    "org.testcontainers:testcontainers": [
+      "com.github.docker-java:docker-java-api",
+      "com.github.docker-java:docker-java-transport-zerodep",
+      "junit:junit",
+      "org.apache.commons:commons-compress",
+      "org.rnorth.duct-tape:duct-tape"
+    ]
+  },
+  "packages": {
+    "com.fasterxml.jackson.core:jackson-annotations": [
+      "com.fasterxml.jackson.annotation"
+    ],
+    "com.github.docker-java:docker-java-api": [
+      "com.github.dockerjava.api",
+      "com.github.dockerjava.api.async",
+      "com.github.dockerjava.api.command",
+      "com.github.dockerjava.api.exception",
+      "com.github.dockerjava.api.model"
+    ],
+    "com.github.docker-java:docker-java-transport": [
+      "com.github.dockerjava.transport"
+    ],
+    "com.github.docker-java:docker-java-transport-zerodep": [
+      "com.github.dockerjava.zerodep",
+      "com.github.dockerjava.zerodep.shaded.org.apache.commons.codec",
+      "com.github.dockerjava.zerodep.shaded.org.apache.commons.codec.binary",
+      "com.github.dockerjava.zerodep.shaded.org.apache.commons.codec.cli",
+      "com.github.dockerjava.zerodep.shaded.org.apache.commons.codec.digest",
+      "com.github.dockerjava.zerodep.shaded.org.apache.commons.codec.language",
+      "com.github.dockerjava.zerodep.shaded.org.apache.commons.codec.language.bm",
+      "com.github.dockerjava.zerodep.shaded.org.apache.commons.codec.net",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.async",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.async.methods",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.auth",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.classic",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.classic.methods",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.config",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.cookie",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.entity",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.entity.mime",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.impl",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.impl.async",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.impl.auth",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.impl.classic",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.impl.cookie",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.impl.io",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.impl.nio",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.impl.routing",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.io",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.nio",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.protocol",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.psl",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.routing",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.socket",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.ssl",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.utils",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.annotation",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.concurrent",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.function",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.config",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.impl",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.impl.bootstrap",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.impl.io",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.impl.nio",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.io",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.io.entity",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.io.ssl",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.io.support",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.message",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.nio",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.nio.command",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.nio.entity",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.nio.ssl",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.nio.support",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.nio.support.classic",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.protocol",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.ssl",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2.config",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2.frame",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2.hpack",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2.impl",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2.impl.io",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2.impl.nio",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2.impl.nio.bootstrap",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2.nio",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2.nio.command",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2.nio.pool",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2.nio.support",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2.protocol",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http2.ssl",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.io",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.net",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.pool",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.reactor",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.reactor.ssl",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.ssl",
+      "com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.util"
+    ],
+    "com.google.android:annotations": [
+      "android.annotation"
+    ],
+    "com.google.api.grpc:proto-google-cloud-pubsub-v1": [
+      "com.google.pubsub.v1"
+    ],
+    "com.google.api.grpc:proto-google-common-protos": [
+      "com.google.api",
+      "com.google.apps.card.v1",
+      "com.google.cloud",
+      "com.google.cloud.audit",
+      "com.google.cloud.location",
+      "com.google.geo.type",
+      "com.google.logging.type",
+      "com.google.longrunning",
+      "com.google.rpc",
+      "com.google.rpc.context",
+      "com.google.shopping.type",
+      "com.google.type"
+    ],
+    "com.google.api.grpc:proto-google-iam-v1": [
+      "com.google.iam.v1",
+      "com.google.iam.v1.logging"
+    ],
+    "com.google.api:api-common": [
+      "com.google.api.core",
+      "com.google.api.pathtemplate",
+      "com.google.api.resourcenames"
+    ],
+    "com.google.api:gax": [
+      "com.google.api.gax.batching",
+      "com.google.api.gax.core",
+      "com.google.api.gax.logging",
+      "com.google.api.gax.longrunning",
+      "com.google.api.gax.nativeimage",
+      "com.google.api.gax.paging",
+      "com.google.api.gax.retrying",
+      "com.google.api.gax.rpc",
+      "com.google.api.gax.rpc.internal",
+      "com.google.api.gax.rpc.mtls",
+      "com.google.api.gax.tracing",
+      "com.google.api.gax.util"
+    ],
+    "com.google.api:gax-grpc": [
+      "com.google.api.gax.grpc",
+      "com.google.api.gax.grpc.nativeimage",
+      "com.google.longrunning",
+      "com.google.longrunning.stub"
+    ],
+    "com.google.api:gax-httpjson": [
+      "com.google.api.gax.httpjson",
+      "com.google.api.gax.httpjson.longrunning",
+      "com.google.api.gax.httpjson.longrunning.stub"
+    ],
+    "com.google.auth:google-auth-library-credentials": [
+      "com.google.auth"
+    ],
+    "com.google.auth:google-auth-library-oauth2-http": [
+      "com.google.auth.http",
+      "com.google.auth.mtls",
+      "com.google.auth.oauth2"
+    ],
+    "com.google.auto.value:auto-value-annotations": [
+      "com.google.auto.value",
+      "com.google.auto.value.extension.memoized",
+      "com.google.auto.value.extension.serializable",
+      "com.google.auto.value.extension.toprettystring"
+    ],
+    "com.google.cloud:google-cloud-pubsub": [
+      "com.google.cloud.pubsub.v1",
+      "com.google.cloud.pubsub.v1.stub"
+    ],
+    "com.google.errorprone:error_prone_annotations": [
+      "com.google.errorprone.annotations",
+      "com.google.errorprone.annotations.concurrent"
+    ],
+    "com.google.guava:guava": [
+      "com.google.common.annotations",
+      "com.google.common.base",
+      "com.google.common.base.internal",
+      "com.google.common.cache",
+      "com.google.common.collect",
+      "com.google.common.escape",
+      "com.google.common.eventbus",
+      "com.google.common.graph",
+      "com.google.common.hash",
+      "com.google.common.html",
+      "com.google.common.io",
+      "com.google.common.math",
+      "com.google.common.net",
+      "com.google.common.primitives",
+      "com.google.common.reflect",
+      "com.google.common.util.concurrent",
+      "com.google.common.xml",
+      "com.google.thirdparty.publicsuffix"
+    ],
+    "com.google.http-client:google-http-client": [
+      "com.google.api.client.http",
+      "com.google.api.client.http.apache",
+      "com.google.api.client.http.javanet",
+      "com.google.api.client.http.json",
+      "com.google.api.client.json",
+      "com.google.api.client.json.rpc2",
+      "com.google.api.client.json.webtoken",
+      "com.google.api.client.testing.http",
+      "com.google.api.client.testing.http.apache",
+      "com.google.api.client.testing.http.javanet",
+      "com.google.api.client.testing.json",
+      "com.google.api.client.testing.json.webtoken",
+      "com.google.api.client.testing.util",
+      "com.google.api.client.util",
+      "com.google.api.client.util.escape",
+      "com.google.api.client.util.store"
+    ],
+    "com.google.http-client:google-http-client-gson": [
+      "com.google.api.client.json.gson"
+    ],
+    "com.google.protobuf:protobuf-java-util": [
+      "com.google.protobuf.util"
+    ],
+    "com.google.re2j:re2j": [
+      "com.google.re2j"
+    ],
+    "io.grpc:grpc-alts": [
+      "io.grpc.alts",
+      "io.grpc.alts.internal"
+    ],
+    "io.grpc:grpc-api": [
+      "io.grpc"
+    ],
+    "io.grpc:grpc-auth": [
+      "io.grpc.auth"
+    ],
+    "io.grpc:grpc-core": [
+      "io.grpc.internal"
+    ],
+    "io.grpc:grpc-googleapis": [
+      "io.grpc.googleapis"
+    ],
+    "io.grpc:grpc-grpclb": [
+      "io.grpc.grpclb",
+      "io.grpc.lb.v1"
+    ],
+    "io.grpc:grpc-inprocess": [
+      "io.grpc.inprocess"
+    ],
+    "io.grpc:grpc-netty-shaded": [
+      "io.grpc.netty.shaded.io.grpc.netty",
+      "io.grpc.netty.shaded.io.netty.bootstrap",
+      "io.grpc.netty.shaded.io.netty.buffer",
+      "io.grpc.netty.shaded.io.netty.buffer.search",
+      "io.grpc.netty.shaded.io.netty.channel",
+      "io.grpc.netty.shaded.io.netty.channel.embedded",
+      "io.grpc.netty.shaded.io.netty.channel.epoll",
+      "io.grpc.netty.shaded.io.netty.channel.group",
+      "io.grpc.netty.shaded.io.netty.channel.internal",
+      "io.grpc.netty.shaded.io.netty.channel.local",
+      "io.grpc.netty.shaded.io.netty.channel.nio",
+      "io.grpc.netty.shaded.io.netty.channel.oio",
+      "io.grpc.netty.shaded.io.netty.channel.pool",
+      "io.grpc.netty.shaded.io.netty.channel.socket",
+      "io.grpc.netty.shaded.io.netty.channel.socket.nio",
+      "io.grpc.netty.shaded.io.netty.channel.socket.oio",
+      "io.grpc.netty.shaded.io.netty.channel.unix",
+      "io.grpc.netty.shaded.io.netty.handler.address",
+      "io.grpc.netty.shaded.io.netty.handler.codec",
+      "io.grpc.netty.shaded.io.netty.handler.codec.base64",
+      "io.grpc.netty.shaded.io.netty.handler.codec.bytes",
+      "io.grpc.netty.shaded.io.netty.handler.codec.compression",
+      "io.grpc.netty.shaded.io.netty.handler.codec.http",
+      "io.grpc.netty.shaded.io.netty.handler.codec.http.cookie",
+      "io.grpc.netty.shaded.io.netty.handler.codec.http.cors",
+      "io.grpc.netty.shaded.io.netty.handler.codec.http.multipart",
+      "io.grpc.netty.shaded.io.netty.handler.codec.http.websocketx",
+      "io.grpc.netty.shaded.io.netty.handler.codec.http.websocketx.extensions",
+      "io.grpc.netty.shaded.io.netty.handler.codec.http.websocketx.extensions.compression",
+      "io.grpc.netty.shaded.io.netty.handler.codec.http2",
+      "io.grpc.netty.shaded.io.netty.handler.codec.json",
+      "io.grpc.netty.shaded.io.netty.handler.codec.marshalling",
+      "io.grpc.netty.shaded.io.netty.handler.codec.protobuf",
+      "io.grpc.netty.shaded.io.netty.handler.codec.rtsp",
+      "io.grpc.netty.shaded.io.netty.handler.codec.serialization",
+      "io.grpc.netty.shaded.io.netty.handler.codec.socks",
+      "io.grpc.netty.shaded.io.netty.handler.codec.socksx",
+      "io.grpc.netty.shaded.io.netty.handler.codec.socksx.v4",
+      "io.grpc.netty.shaded.io.netty.handler.codec.socksx.v5",
+      "io.grpc.netty.shaded.io.netty.handler.codec.spdy",
+      "io.grpc.netty.shaded.io.netty.handler.codec.string",
+      "io.grpc.netty.shaded.io.netty.handler.codec.xml",
+      "io.grpc.netty.shaded.io.netty.handler.flow",
+      "io.grpc.netty.shaded.io.netty.handler.flush",
+      "io.grpc.netty.shaded.io.netty.handler.ipfilter",
+      "io.grpc.netty.shaded.io.netty.handler.logging",
+      "io.grpc.netty.shaded.io.netty.handler.pcap",
+      "io.grpc.netty.shaded.io.netty.handler.proxy",
+      "io.grpc.netty.shaded.io.netty.handler.ssl",
+      "io.grpc.netty.shaded.io.netty.handler.ssl.ocsp",
+      "io.grpc.netty.shaded.io.netty.handler.ssl.util",
+      "io.grpc.netty.shaded.io.netty.handler.stream",
+      "io.grpc.netty.shaded.io.netty.handler.timeout",
+      "io.grpc.netty.shaded.io.netty.handler.traffic",
+      "io.grpc.netty.shaded.io.netty.internal.tcnative",
+      "io.grpc.netty.shaded.io.netty.resolver",
+      "io.grpc.netty.shaded.io.netty.util",
+      "io.grpc.netty.shaded.io.netty.util.collection",
+      "io.grpc.netty.shaded.io.netty.util.concurrent",
+      "io.grpc.netty.shaded.io.netty.util.internal",
+      "io.grpc.netty.shaded.io.netty.util.internal.logging",
+      "io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues",
+      "io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.atomic",
+      "io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.util",
+      "io.grpc.netty.shaded.io.netty.util.internal.svm"
+    ],
+    "io.grpc:grpc-protobuf": [
+      "io.grpc.protobuf"
+    ],
+    "io.grpc:grpc-protobuf-lite": [
+      "io.grpc.protobuf.lite"
+    ],
+    "io.grpc:grpc-services": [
+      "io.grpc.binarylog.v1",
+      "io.grpc.channelz.v1",
+      "io.grpc.health.v1",
+      "io.grpc.protobuf.services",
+      "io.grpc.protobuf.services.internal",
+      "io.grpc.reflection.v1",
+      "io.grpc.reflection.v1alpha",
+      "io.grpc.services"
+    ],
+    "io.grpc:grpc-stub": [
+      "io.grpc.stub",
+      "io.grpc.stub.annotations"
+    ],
+    "io.grpc:grpc-util": [
+      "io.grpc.util"
+    ],
+    "io.grpc:grpc-xds": [
+      "io.grpc.xds",
+      "io.grpc.xds.client",
+      "io.grpc.xds.internal",
+      "io.grpc.xds.internal.rbac.engine",
+      "io.grpc.xds.internal.security",
+      "io.grpc.xds.internal.security.certprovider",
+      "io.grpc.xds.internal.security.trust",
+      "io.grpc.xds.orca",
+      "io.grpc.xds.shaded.com.github.udpa.udpa.type.v1",
+      "io.grpc.xds.shaded.com.github.xds.core.v3",
+      "io.grpc.xds.shaded.com.github.xds.data.orca.v3",
+      "io.grpc.xds.shaded.com.github.xds.service.orca.v3",
+      "io.grpc.xds.shaded.com.github.xds.type.matcher.v3",
+      "io.grpc.xds.shaded.com.github.xds.type.v3",
+      "io.grpc.xds.shaded.com.google.api.expr.v1alpha1",
+      "io.grpc.xds.shaded.dev.cel.expr",
+      "io.grpc.xds.shaded.envoy.annotations",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.admin.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.config.accesslog.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.config.bootstrap.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.config.cluster.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.config.core.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.config.endpoint.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.config.listener.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.config.metrics.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.config.overload.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.config.rbac.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.config.route.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.config.trace.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.data.accesslog.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.clusters.aggregate.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.common.fault.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.fault.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.rate_limit_quota.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.rbac.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.http.router.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.client_side_weighted_round_robin.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.common.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.least_request.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.pick_first.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.ring_hash.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.round_robin.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.load_balancing_policies.wrr_locality.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.extensions.transport_sockets.tls.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.service.discovery.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.service.load_stats.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.service.rate_limit_quota.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.service.status.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.type.http.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.type.matcher.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.type.metadata.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.type.tracing.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.envoy.type.v3",
+      "io.grpc.xds.shaded.io.envoyproxy.pgv.validate",
+      "io.grpc.xds.shaded.udpa.annotations",
+      "io.grpc.xds.shaded.xds.annotations.v3"
+    ],
+    "io.opencensus:opencensus-api": [
+      "io.opencensus.common",
+      "io.opencensus.internal",
+      "io.opencensus.metrics",
+      "io.opencensus.metrics.data",
+      "io.opencensus.metrics.export",
+      "io.opencensus.resource",
+      "io.opencensus.stats",
+      "io.opencensus.tags",
+      "io.opencensus.tags.propagation",
+      "io.opencensus.tags.unsafe",
+      "io.opencensus.trace",
+      "io.opencensus.trace.config",
+      "io.opencensus.trace.export",
+      "io.opencensus.trace.internal",
+      "io.opencensus.trace.propagation",
+      "io.opencensus.trace.samplers",
+      "io.opencensus.trace.unsafe"
+    ],
+    "io.opencensus:opencensus-contrib-http-util": [
+      "io.opencensus.contrib.http",
+      "io.opencensus.contrib.http.util"
+    ],
+    "io.opentelemetry:opentelemetry-api": [
+      "io.opentelemetry.api",
+      "io.opentelemetry.api.baggage",
+      "io.opentelemetry.api.baggage.propagation",
+      "io.opentelemetry.api.common",
+      "io.opentelemetry.api.internal",
+      "io.opentelemetry.api.logs",
+      "io.opentelemetry.api.metrics",
+      "io.opentelemetry.api.trace",
+      "io.opentelemetry.api.trace.propagation",
+      "io.opentelemetry.api.trace.propagation.internal"
+    ],
+    "io.opentelemetry:opentelemetry-context": [
+      "io.opentelemetry.context",
+      "io.opentelemetry.context.internal.shaded",
+      "io.opentelemetry.context.propagation",
+      "io.opentelemetry.context.propagation.internal"
+    ],
+    "io.opentelemetry:opentelemetry-semconv": [
+      "io.opentelemetry.semconv.resource.attributes",
+      "io.opentelemetry.semconv.trace.attributes"
+    ],
+    "io.perfmark:perfmark-api": [
+      "io.perfmark"
+    ],
+    "javax.annotation:javax.annotation-api": [
+      "javax.annotation",
+      "javax.annotation.security",
+      "javax.annotation.sql"
+    ],
+    "junit:junit": [
+      "junit.extensions",
+      "junit.framework",
+      "junit.runner",
+      "junit.textui",
+      "org.junit",
+      "org.junit.experimental",
+      "org.junit.experimental.categories",
+      "org.junit.experimental.max",
+      "org.junit.experimental.results",
+      "org.junit.experimental.runners",
+      "org.junit.experimental.theories",
+      "org.junit.experimental.theories.internal",
+      "org.junit.experimental.theories.suppliers",
+      "org.junit.function",
+      "org.junit.internal",
+      "org.junit.internal.builders",
+      "org.junit.internal.management",
+      "org.junit.internal.matchers",
+      "org.junit.internal.requests",
+      "org.junit.internal.runners",
+      "org.junit.internal.runners.model",
+      "org.junit.internal.runners.rules",
+      "org.junit.internal.runners.statements",
+      "org.junit.matchers",
+      "org.junit.rules",
+      "org.junit.runner",
+      "org.junit.runner.manipulation",
+      "org.junit.runner.notification",
+      "org.junit.runners",
+      "org.junit.runners.model",
+      "org.junit.runners.parameterized",
+      "org.junit.validator"
+    ],
+    "net.java.dev.jna:jna": [
+      "com.sun.jna",
+      "com.sun.jna.internal",
+      "com.sun.jna.ptr",
+      "com.sun.jna.win32"
+    ],
+    "org.apache.commons:commons-compress": [
+      "org.apache.commons.compress",
+      "org.apache.commons.compress.archivers",
+      "org.apache.commons.compress.archivers.ar",
+      "org.apache.commons.compress.archivers.arj",
+      "org.apache.commons.compress.archivers.cpio",
+      "org.apache.commons.compress.archivers.dump",
+      "org.apache.commons.compress.archivers.examples",
+      "org.apache.commons.compress.archivers.jar",
+      "org.apache.commons.compress.archivers.sevenz",
+      "org.apache.commons.compress.archivers.tar",
+      "org.apache.commons.compress.archivers.zip",
+      "org.apache.commons.compress.changes",
+      "org.apache.commons.compress.compressors",
+      "org.apache.commons.compress.compressors.brotli",
+      "org.apache.commons.compress.compressors.bzip2",
+      "org.apache.commons.compress.compressors.deflate",
+      "org.apache.commons.compress.compressors.deflate64",
+      "org.apache.commons.compress.compressors.gzip",
+      "org.apache.commons.compress.compressors.lz4",
+      "org.apache.commons.compress.compressors.lz77support",
+      "org.apache.commons.compress.compressors.lzma",
+      "org.apache.commons.compress.compressors.lzw",
+      "org.apache.commons.compress.compressors.pack200",
+      "org.apache.commons.compress.compressors.snappy",
+      "org.apache.commons.compress.compressors.xz",
+      "org.apache.commons.compress.compressors.z",
+      "org.apache.commons.compress.compressors.zstandard",
+      "org.apache.commons.compress.harmony",
+      "org.apache.commons.compress.harmony.archive.internal.nls",
+      "org.apache.commons.compress.harmony.pack200",
+      "org.apache.commons.compress.harmony.unpack200",
+      "org.apache.commons.compress.harmony.unpack200.bytecode",
+      "org.apache.commons.compress.harmony.unpack200.bytecode.forms",
+      "org.apache.commons.compress.java.util.jar",
+      "org.apache.commons.compress.parallel",
+      "org.apache.commons.compress.utils"
+    ],
+    "org.checkerframework:checker-qual": [
+      "org.checkerframework.checker.builder.qual",
+      "org.checkerframework.checker.calledmethods.qual",
+      "org.checkerframework.checker.compilermsgs.qual",
+      "org.checkerframework.checker.fenum.qual",
+      "org.checkerframework.checker.formatter.qual",
+      "org.checkerframework.checker.guieffect.qual",
+      "org.checkerframework.checker.i18n.qual",
+      "org.checkerframework.checker.i18nformatter.qual",
+      "org.checkerframework.checker.index.qual",
+      "org.checkerframework.checker.initialization.qual",
+      "org.checkerframework.checker.interning.qual",
+      "org.checkerframework.checker.lock.qual",
+      "org.checkerframework.checker.mustcall.qual",
+      "org.checkerframework.checker.nonempty.qual",
+      "org.checkerframework.checker.nullness.qual",
+      "org.checkerframework.checker.optional.qual",
+      "org.checkerframework.checker.propkey.qual",
+      "org.checkerframework.checker.regex.qual",
+      "org.checkerframework.checker.signature.qual",
+      "org.checkerframework.checker.signedness.qual",
+      "org.checkerframework.checker.sqlquotes.qual",
+      "org.checkerframework.checker.tainting.qual",
+      "org.checkerframework.checker.units.qual",
+      "org.checkerframework.common.aliasing.qual",
+      "org.checkerframework.common.initializedfields.qual",
+      "org.checkerframework.common.reflection.qual",
+      "org.checkerframework.common.returnsreceiver.qual",
+      "org.checkerframework.common.subtyping.qual",
+      "org.checkerframework.common.util.count.report.qual",
+      "org.checkerframework.common.value.qual",
+      "org.checkerframework.dataflow.qual",
+      "org.checkerframework.framework.qual"
+    ],
+    "org.codehaus.mojo:animal-sniffer-annotations": [
+      "org.codehaus.mojo.animal_sniffer"
+    ],
+    "org.conscrypt:conscrypt-openjdk-uber": [
+      "org.conscrypt",
+      "org.conscrypt.ct",
+      "org.conscrypt.io"
+    ],
+    "org.hamcrest:hamcrest-core": [
+      "org.hamcrest",
+      "org.hamcrest.core",
+      "org.hamcrest.internal"
+    ],
+    "org.jetbrains:annotations": [
+      "org.intellij.lang.annotations",
+      "org.jetbrains.annotations"
+    ],
+    "org.rnorth.duct-tape:duct-tape": [
+      "org.rnorth.ducttape",
+      "org.rnorth.ducttape.circuitbreakers",
+      "org.rnorth.ducttape.inconsistents",
+      "org.rnorth.ducttape.ratelimits",
+      "org.rnorth.ducttape.timeouts",
+      "org.rnorth.ducttape.unreliables"
+    ],
+    "org.testcontainers:gcloud": [
+      "org.testcontainers.containers"
+    ],
+    "org.testcontainers:testcontainers": [
+      "org.testcontainers",
+      "org.testcontainers.containers",
+      "org.testcontainers.containers.output",
+      "org.testcontainers.containers.startupcheck",
+      "org.testcontainers.containers.traits",
+      "org.testcontainers.containers.wait.internal",
+      "org.testcontainers.containers.wait.strategy",
+      "org.testcontainers.core",
+      "org.testcontainers.dockerclient",
+      "org.testcontainers.images",
+      "org.testcontainers.images.builder",
+      "org.testcontainers.images.builder.dockerfile",
+      "org.testcontainers.images.builder.dockerfile.statement",
+      "org.testcontainers.images.builder.dockerfile.traits",
+      "org.testcontainers.images.builder.traits",
+      "org.testcontainers.jib",
+      "org.testcontainers.lifecycle",
+      "org.testcontainers.shaded.com.fasterxml.jackson.core",
+      "org.testcontainers.shaded.com.fasterxml.jackson.core.base",
+      "org.testcontainers.shaded.com.fasterxml.jackson.core.filter",
+      "org.testcontainers.shaded.com.fasterxml.jackson.core.format",
+      "org.testcontainers.shaded.com.fasterxml.jackson.core.io",
+      "org.testcontainers.shaded.com.fasterxml.jackson.core.json",
+      "org.testcontainers.shaded.com.fasterxml.jackson.core.sym",
+      "org.testcontainers.shaded.com.fasterxml.jackson.core.type",
+      "org.testcontainers.shaded.com.fasterxml.jackson.core.util",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.annotation",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.cfg",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.deser",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.deser.impl",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.deser.std",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.exc",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.ext",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.introspect",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.jsonFormatVisitors",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.jsonschema",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.jsontype",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.jsontype.impl",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.module",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.node",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.ser",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.ser.impl",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.ser.std",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.type",
+      "org.testcontainers.shaded.com.fasterxml.jackson.databind.util",
+      "org.testcontainers.shaded.com.github.dockerjava.core",
+      "org.testcontainers.shaded.com.github.dockerjava.core.async",
+      "org.testcontainers.shaded.com.github.dockerjava.core.command",
+      "org.testcontainers.shaded.com.github.dockerjava.core.dockerfile",
+      "org.testcontainers.shaded.com.github.dockerjava.core.exception",
+      "org.testcontainers.shaded.com.github.dockerjava.core.exec",
+      "org.testcontainers.shaded.com.github.dockerjava.core.util",
+      "org.testcontainers.shaded.com.google.common.annotations",
+      "org.testcontainers.shaded.com.google.common.base",
+      "org.testcontainers.shaded.com.google.common.base.internal",
+      "org.testcontainers.shaded.com.google.common.cache",
+      "org.testcontainers.shaded.com.google.common.collect",
+      "org.testcontainers.shaded.com.google.common.escape",
+      "org.testcontainers.shaded.com.google.common.eventbus",
+      "org.testcontainers.shaded.com.google.common.graph",
+      "org.testcontainers.shaded.com.google.common.hash",
+      "org.testcontainers.shaded.com.google.common.html",
+      "org.testcontainers.shaded.com.google.common.io",
+      "org.testcontainers.shaded.com.google.common.math",
+      "org.testcontainers.shaded.com.google.common.net",
+      "org.testcontainers.shaded.com.google.common.primitives",
+      "org.testcontainers.shaded.com.google.common.reflect",
+      "org.testcontainers.shaded.com.google.common.util.concurrent",
+      "org.testcontainers.shaded.com.google.common.util.concurrent.internal",
+      "org.testcontainers.shaded.com.google.common.xml",
+      "org.testcontainers.shaded.com.google.errorprone.annotations",
+      "org.testcontainers.shaded.com.google.errorprone.annotations.concurrent",
+      "org.testcontainers.shaded.com.google.thirdparty.publicsuffix",
+      "org.testcontainers.shaded.com.trilead.ssh2",
+      "org.testcontainers.shaded.com.trilead.ssh2.auth",
+      "org.testcontainers.shaded.com.trilead.ssh2.channel",
+      "org.testcontainers.shaded.com.trilead.ssh2.crypto",
+      "org.testcontainers.shaded.com.trilead.ssh2.crypto.cipher",
+      "org.testcontainers.shaded.com.trilead.ssh2.crypto.dh",
+      "org.testcontainers.shaded.com.trilead.ssh2.crypto.digest",
+      "org.testcontainers.shaded.com.trilead.ssh2.log",
+      "org.testcontainers.shaded.com.trilead.ssh2.packets",
+      "org.testcontainers.shaded.com.trilead.ssh2.sftp",
+      "org.testcontainers.shaded.com.trilead.ssh2.signature",
+      "org.testcontainers.shaded.com.trilead.ssh2.transport",
+      "org.testcontainers.shaded.com.trilead.ssh2.util",
+      "org.testcontainers.shaded.org.apache.commons.io",
+      "org.testcontainers.shaded.org.apache.commons.io.build",
+      "org.testcontainers.shaded.org.apache.commons.io.charset",
+      "org.testcontainers.shaded.org.apache.commons.io.comparator",
+      "org.testcontainers.shaded.org.apache.commons.io.file",
+      "org.testcontainers.shaded.org.apache.commons.io.file.attribute",
+      "org.testcontainers.shaded.org.apache.commons.io.file.spi",
+      "org.testcontainers.shaded.org.apache.commons.io.filefilter",
+      "org.testcontainers.shaded.org.apache.commons.io.function",
+      "org.testcontainers.shaded.org.apache.commons.io.input",
+      "org.testcontainers.shaded.org.apache.commons.io.input.buffer",
+      "org.testcontainers.shaded.org.apache.commons.io.monitor",
+      "org.testcontainers.shaded.org.apache.commons.io.output",
+      "org.testcontainers.shaded.org.apache.commons.io.serialization",
+      "org.testcontainers.shaded.org.apache.commons.lang3",
+      "org.testcontainers.shaded.org.apache.commons.lang3.arch",
+      "org.testcontainers.shaded.org.apache.commons.lang3.builder",
+      "org.testcontainers.shaded.org.apache.commons.lang3.compare",
+      "org.testcontainers.shaded.org.apache.commons.lang3.concurrent",
+      "org.testcontainers.shaded.org.apache.commons.lang3.concurrent.locks",
+      "org.testcontainers.shaded.org.apache.commons.lang3.event",
+      "org.testcontainers.shaded.org.apache.commons.lang3.exception",
+      "org.testcontainers.shaded.org.apache.commons.lang3.function",
+      "org.testcontainers.shaded.org.apache.commons.lang3.math",
+      "org.testcontainers.shaded.org.apache.commons.lang3.mutable",
+      "org.testcontainers.shaded.org.apache.commons.lang3.reflect",
+      "org.testcontainers.shaded.org.apache.commons.lang3.stream",
+      "org.testcontainers.shaded.org.apache.commons.lang3.text",
+      "org.testcontainers.shaded.org.apache.commons.lang3.text.translate",
+      "org.testcontainers.shaded.org.apache.commons.lang3.time",
+      "org.testcontainers.shaded.org.apache.commons.lang3.tuple",
+      "org.testcontainers.shaded.org.awaitility",
+      "org.testcontainers.shaded.org.awaitility.classpath",
+      "org.testcontainers.shaded.org.awaitility.constraint",
+      "org.testcontainers.shaded.org.awaitility.core",
+      "org.testcontainers.shaded.org.awaitility.pollinterval",
+      "org.testcontainers.shaded.org.awaitility.reflect",
+      "org.testcontainers.shaded.org.awaitility.reflect.exception",
+      "org.testcontainers.shaded.org.awaitility.spi",
+      "org.testcontainers.shaded.org.bouncycastle",
+      "org.testcontainers.shaded.org.bouncycastle.asn1",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.anssi",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.bc",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.bsi",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.cmc",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.cmp",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.cms",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.cms.ecc",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.crmf",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.cryptlib",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.cryptopro",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.dvcs",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.eac",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.edec",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.esf",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.ess",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.est",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.gm",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.gnu",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.iana",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.icao",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.isara",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.isismtt",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.isismtt.ocsp",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.isismtt.x509",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.iso",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.kisa",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.microsoft",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.misc",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.mozilla",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.nist",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.nsri",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.ntt",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.ocsp",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.oiw",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.pkcs",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.rosstandart",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.sec",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.smime",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.teletrust",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.tsp",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.ua",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.util",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.x500",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.x500.style",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.x509",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.x509.qualified",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.x509.sigi",
+      "org.testcontainers.shaded.org.bouncycastle.asn1.x9",
+      "org.testcontainers.shaded.org.bouncycastle.cert",
+      "org.testcontainers.shaded.org.bouncycastle.cert.bc",
+      "org.testcontainers.shaded.org.bouncycastle.cert.cmp",
+      "org.testcontainers.shaded.org.bouncycastle.cert.crmf",
+      "org.testcontainers.shaded.org.bouncycastle.cert.crmf.bc",
+      "org.testcontainers.shaded.org.bouncycastle.cert.crmf.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.cert.dane",
+      "org.testcontainers.shaded.org.bouncycastle.cert.dane.fetcher",
+      "org.testcontainers.shaded.org.bouncycastle.cert.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.cert.ocsp",
+      "org.testcontainers.shaded.org.bouncycastle.cert.ocsp.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.cert.path",
+      "org.testcontainers.shaded.org.bouncycastle.cert.path.validations",
+      "org.testcontainers.shaded.org.bouncycastle.cert.selector",
+      "org.testcontainers.shaded.org.bouncycastle.cert.selector.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.cmc",
+      "org.testcontainers.shaded.org.bouncycastle.cms",
+      "org.testcontainers.shaded.org.bouncycastle.cms.bc",
+      "org.testcontainers.shaded.org.bouncycastle.cms.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.crypto",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.agreement",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.agreement.jpake",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.agreement.kdf",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.agreement.srp",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.commitments",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.constraints",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.digests",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.ec",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.encodings",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.engines",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.examples",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.fpe",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.generators",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.hpke",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.io",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.kems",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.macs",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.modes",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.modes.gcm",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.modes.kgcm",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.paddings",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.params",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.parsers",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.prng",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.prng.drbg",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.signers",
+      "org.testcontainers.shaded.org.bouncycastle.crypto.util",
+      "org.testcontainers.shaded.org.bouncycastle.dvcs",
+      "org.testcontainers.shaded.org.bouncycastle.eac",
+      "org.testcontainers.shaded.org.bouncycastle.eac.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.eac.operator",
+      "org.testcontainers.shaded.org.bouncycastle.eac.operator.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.est",
+      "org.testcontainers.shaded.org.bouncycastle.est.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.i18n",
+      "org.testcontainers.shaded.org.bouncycastle.i18n.filter",
+      "org.testcontainers.shaded.org.bouncycastle.iana",
+      "org.testcontainers.shaded.org.bouncycastle.internal.asn1.bsi",
+      "org.testcontainers.shaded.org.bouncycastle.internal.asn1.cms",
+      "org.testcontainers.shaded.org.bouncycastle.internal.asn1.eac",
+      "org.testcontainers.shaded.org.bouncycastle.internal.asn1.isismtt",
+      "org.testcontainers.shaded.org.bouncycastle.its",
+      "org.testcontainers.shaded.org.bouncycastle.its.bc",
+      "org.testcontainers.shaded.org.bouncycastle.its.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.its.operator",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.interfaces",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.io",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric.dh",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric.dsa",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric.dstu",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric.ec",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric.ecgost",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric.ecgost12",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric.edec",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric.elgamal",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric.gost",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric.ies",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric.rsa",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric.util",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.asymmetric.x509",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.config",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.digest",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.drbg",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.keystore",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.keystore.bc",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.keystore.bcfks",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.keystore.pkcs12",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.keystore.util",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.symmetric",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.symmetric.util",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.provider.util",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.spec",
+      "org.testcontainers.shaded.org.bouncycastle.jcajce.util",
+      "org.testcontainers.shaded.org.bouncycastle.jce",
+      "org.testcontainers.shaded.org.bouncycastle.jce.exception",
+      "org.testcontainers.shaded.org.bouncycastle.jce.interfaces",
+      "org.testcontainers.shaded.org.bouncycastle.jce.netscape",
+      "org.testcontainers.shaded.org.bouncycastle.jce.provider",
+      "org.testcontainers.shaded.org.bouncycastle.jce.spec",
+      "org.testcontainers.shaded.org.bouncycastle.math",
+      "org.testcontainers.shaded.org.bouncycastle.math.ec",
+      "org.testcontainers.shaded.org.bouncycastle.math.ec.custom.djb",
+      "org.testcontainers.shaded.org.bouncycastle.math.ec.custom.gm",
+      "org.testcontainers.shaded.org.bouncycastle.math.ec.custom.sec",
+      "org.testcontainers.shaded.org.bouncycastle.math.ec.endo",
+      "org.testcontainers.shaded.org.bouncycastle.math.ec.rfc7748",
+      "org.testcontainers.shaded.org.bouncycastle.math.ec.rfc8032",
+      "org.testcontainers.shaded.org.bouncycastle.math.ec.tools",
+      "org.testcontainers.shaded.org.bouncycastle.math.field",
+      "org.testcontainers.shaded.org.bouncycastle.math.raw",
+      "org.testcontainers.shaded.org.bouncycastle.mime",
+      "org.testcontainers.shaded.org.bouncycastle.mime.encoding",
+      "org.testcontainers.shaded.org.bouncycastle.mime.smime",
+      "org.testcontainers.shaded.org.bouncycastle.mozilla",
+      "org.testcontainers.shaded.org.bouncycastle.mozilla.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.oer",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.etsi102941",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.etsi102941.basetypes",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.etsi103097",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.etsi103097.extension",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.ieee1609dot2",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.ieee1609dot2.basetypes",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.ieee1609dot2dot1",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.template.etsi102941",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.template.etsi102941.basetypes",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.template.etsi103097",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.template.etsi103097.extension",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.template.ieee1609dot2",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.template.ieee1609dot2.basetypes",
+      "org.testcontainers.shaded.org.bouncycastle.oer.its.template.ieee1609dot2dot1",
+      "org.testcontainers.shaded.org.bouncycastle.openssl",
+      "org.testcontainers.shaded.org.bouncycastle.openssl.bc",
+      "org.testcontainers.shaded.org.bouncycastle.openssl.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.operator",
+      "org.testcontainers.shaded.org.bouncycastle.operator.bc",
+      "org.testcontainers.shaded.org.bouncycastle.operator.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.pkcs",
+      "org.testcontainers.shaded.org.bouncycastle.pkcs.bc",
+      "org.testcontainers.shaded.org.bouncycastle.pkcs.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.pkix",
+      "org.testcontainers.shaded.org.bouncycastle.pkix.jcajce",
+      "org.testcontainers.shaded.org.bouncycastle.pkix.util",
+      "org.testcontainers.shaded.org.bouncycastle.pkix.util.filter",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.asn1",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.bike",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.cmce",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.crystals.dilithium",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.crystals.kyber",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.falcon",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.frodo",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.gemss",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.hqc",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.lms",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.newhope",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.ntru",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.ntruprime",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.picnic",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.rainbow",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.saber",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.sphincs",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.sphincsplus",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.util",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.crypto.xmss",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.interfaces",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.bike",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.cmce",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.dilithium",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.falcon",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.frodo",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.gmss",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.hqc",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.kyber",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.lms",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.mceliece",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.newhope",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.ntru",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.ntruprime",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.picnic",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.rainbow",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.saber",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.sphincs",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.sphincsplus",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.util",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.provider.xmss",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.jcajce.spec",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.legacy.crypto.gmss",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.legacy.crypto.gmss.util",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.legacy.crypto.mceliece",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.legacy.crypto.qtesla",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.legacy.crypto.rainbow",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.legacy.crypto.rainbow.util",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.legacy.math.linearalgebra",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.math.ntru",
+      "org.testcontainers.shaded.org.bouncycastle.pqc.math.ntru.parameters",
+      "org.testcontainers.shaded.org.bouncycastle.tsp",
+      "org.testcontainers.shaded.org.bouncycastle.tsp.cms",
+      "org.testcontainers.shaded.org.bouncycastle.tsp.ers",
+      "org.testcontainers.shaded.org.bouncycastle.util",
+      "org.testcontainers.shaded.org.bouncycastle.util.encoders",
+      "org.testcontainers.shaded.org.bouncycastle.util.io",
+      "org.testcontainers.shaded.org.bouncycastle.util.io.pem",
+      "org.testcontainers.shaded.org.bouncycastle.util.test",
+      "org.testcontainers.shaded.org.bouncycastle.voms",
+      "org.testcontainers.shaded.org.bouncycastle.x509",
+      "org.testcontainers.shaded.org.bouncycastle.x509.extension",
+      "org.testcontainers.shaded.org.bouncycastle.x509.util",
+      "org.testcontainers.shaded.org.checkerframework.checker.builder.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.calledmethods.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.compilermsgs.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.fenum.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.formatter.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.guieffect.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.i18n.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.i18nformatter.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.index.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.initialization.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.interning.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.lock.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.mustcall.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.nullness.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.optional.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.propkey.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.regex.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.signature.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.signedness.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.tainting.qual",
+      "org.testcontainers.shaded.org.checkerframework.checker.units.qual",
+      "org.testcontainers.shaded.org.checkerframework.common.aliasing.qual",
+      "org.testcontainers.shaded.org.checkerframework.common.initializedfields.qual",
+      "org.testcontainers.shaded.org.checkerframework.common.reflection.qual",
+      "org.testcontainers.shaded.org.checkerframework.common.returnsreceiver.qual",
+      "org.testcontainers.shaded.org.checkerframework.common.subtyping.qual",
+      "org.testcontainers.shaded.org.checkerframework.common.util.count.report.qual",
+      "org.testcontainers.shaded.org.checkerframework.common.value.qual",
+      "org.testcontainers.shaded.org.checkerframework.dataflow.qual",
+      "org.testcontainers.shaded.org.checkerframework.framework.qual",
+      "org.testcontainers.shaded.org.hamcrest",
+      "org.testcontainers.shaded.org.hamcrest.beans",
+      "org.testcontainers.shaded.org.hamcrest.collection",
+      "org.testcontainers.shaded.org.hamcrest.comparator",
+      "org.testcontainers.shaded.org.hamcrest.core",
+      "org.testcontainers.shaded.org.hamcrest.internal",
+      "org.testcontainers.shaded.org.hamcrest.io",
+      "org.testcontainers.shaded.org.hamcrest.number",
+      "org.testcontainers.shaded.org.hamcrest.object",
+      "org.testcontainers.shaded.org.hamcrest.text",
+      "org.testcontainers.shaded.org.hamcrest.xml",
+      "org.testcontainers.shaded.org.yaml.snakeyaml",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.comments",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.composer",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.constructor",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.emitter",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.env",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.error",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.events",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.extensions.compactnotation",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.external.biz.base64Coder",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.external.com.google.gdata.util.common.base",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.introspector",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.nodes",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.parser",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.reader",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.representer",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.resolver",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.scanner",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.serializer",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.tokens",
+      "org.testcontainers.shaded.org.yaml.snakeyaml.util",
+      "org.testcontainers.shaded.org.zeroturnaround.exec",
+      "org.testcontainers.shaded.org.zeroturnaround.exec.close",
+      "org.testcontainers.shaded.org.zeroturnaround.exec.listener",
+      "org.testcontainers.shaded.org.zeroturnaround.exec.stop",
+      "org.testcontainers.shaded.org.zeroturnaround.exec.stream",
+      "org.testcontainers.shaded.org.zeroturnaround.exec.stream.slf4j",
+      "org.testcontainers.utility"
+    ],
+    "org.threeten:threetenbp": [
+      "org.threeten.bp",
+      "org.threeten.bp.chrono",
+      "org.threeten.bp.format",
+      "org.threeten.bp.jdk8",
+      "org.threeten.bp.temporal",
+      "org.threeten.bp.zone"
+    ]
+  },
+  "repositories": {
+    "https://repo1.maven.org/maven2/": [
+      "com.fasterxml.jackson.core:jackson-annotations",
+      "com.fasterxml.jackson.core:jackson-annotations:jar:sources",
+      "com.github.docker-java:docker-java-api",
+      "com.github.docker-java:docker-java-api:jar:sources",
+      "com.github.docker-java:docker-java-transport",
+      "com.github.docker-java:docker-java-transport-zerodep",
+      "com.github.docker-java:docker-java-transport-zerodep:jar:sources",
+      "com.github.docker-java:docker-java-transport:jar:sources",
+      "com.google.android:annotations",
+      "com.google.android:annotations:jar:sources",
+      "com.google.api.grpc:proto-google-cloud-pubsub-v1",
+      "com.google.api.grpc:proto-google-cloud-pubsub-v1:jar:sources",
+      "com.google.api.grpc:proto-google-common-protos",
+      "com.google.api.grpc:proto-google-common-protos:jar:sources",
+      "com.google.api.grpc:proto-google-iam-v1",
+      "com.google.api.grpc:proto-google-iam-v1:jar:sources",
+      "com.google.api:api-common",
+      "com.google.api:api-common:jar:sources",
+      "com.google.api:gax",
+      "com.google.api:gax-grpc",
+      "com.google.api:gax-grpc:jar:sources",
+      "com.google.api:gax-httpjson",
+      "com.google.api:gax-httpjson:jar:sources",
+      "com.google.api:gax:jar:sources",
+      "com.google.auth:google-auth-library-credentials",
+      "com.google.auth:google-auth-library-credentials:jar:sources",
+      "com.google.auth:google-auth-library-oauth2-http",
+      "com.google.auth:google-auth-library-oauth2-http:jar:sources",
+      "com.google.auto.value:auto-value-annotations",
+      "com.google.auto.value:auto-value-annotations:jar:sources",
+      "com.google.cloud:google-cloud-pubsub",
+      "com.google.cloud:google-cloud-pubsub:jar:sources",
+      "com.google.errorprone:error_prone_annotations",
+      "com.google.errorprone:error_prone_annotations:jar:sources",
+      "com.google.guava:guava",
+      "com.google.guava:guava:jar:sources",
+      "com.google.guava:listenablefuture",
+      "com.google.http-client:google-http-client",
+      "com.google.http-client:google-http-client-gson",
+      "com.google.http-client:google-http-client-gson:jar:sources",
+      "com.google.http-client:google-http-client:jar:sources",
+      "com.google.protobuf:protobuf-java-util",
+      "com.google.protobuf:protobuf-java-util:jar:sources",
+      "com.google.re2j:re2j",
+      "com.google.re2j:re2j:jar:sources",
+      "io.grpc:grpc-alts",
+      "io.grpc:grpc-alts:jar:sources",
+      "io.grpc:grpc-api",
+      "io.grpc:grpc-api:jar:sources",
+      "io.grpc:grpc-auth",
+      "io.grpc:grpc-auth:jar:sources",
+      "io.grpc:grpc-context",
+      "io.grpc:grpc-context:jar:sources",
+      "io.grpc:grpc-core",
+      "io.grpc:grpc-core:jar:sources",
+      "io.grpc:grpc-googleapis",
+      "io.grpc:grpc-googleapis:jar:sources",
+      "io.grpc:grpc-grpclb",
+      "io.grpc:grpc-grpclb:jar:sources",
+      "io.grpc:grpc-inprocess",
+      "io.grpc:grpc-inprocess:jar:sources",
+      "io.grpc:grpc-netty-shaded",
+      "io.grpc:grpc-netty-shaded:jar:sources",
+      "io.grpc:grpc-protobuf",
+      "io.grpc:grpc-protobuf-lite",
+      "io.grpc:grpc-protobuf-lite:jar:sources",
+      "io.grpc:grpc-protobuf:jar:sources",
+      "io.grpc:grpc-services",
+      "io.grpc:grpc-services:jar:sources",
+      "io.grpc:grpc-stub",
+      "io.grpc:grpc-stub:jar:sources",
+      "io.grpc:grpc-util",
+      "io.grpc:grpc-util:jar:sources",
+      "io.grpc:grpc-xds",
+      "io.grpc:grpc-xds:jar:sources",
+      "io.opencensus:opencensus-api",
+      "io.opencensus:opencensus-api:jar:sources",
+      "io.opencensus:opencensus-contrib-http-util",
+      "io.opencensus:opencensus-contrib-http-util:jar:sources",
+      "io.opentelemetry:opentelemetry-api",
+      "io.opentelemetry:opentelemetry-api:jar:sources",
+      "io.opentelemetry:opentelemetry-context",
+      "io.opentelemetry:opentelemetry-context:jar:sources",
+      "io.opentelemetry:opentelemetry-semconv",
+      "io.opentelemetry:opentelemetry-semconv:jar:sources",
+      "io.perfmark:perfmark-api",
+      "io.perfmark:perfmark-api:jar:sources",
+      "javax.annotation:javax.annotation-api",
+      "javax.annotation:javax.annotation-api:jar:sources",
+      "junit:junit",
+      "junit:junit:jar:sources",
+      "net.java.dev.jna:jna",
+      "net.java.dev.jna:jna:jar:sources",
+      "org.apache.commons:commons-compress",
+      "org.apache.commons:commons-compress:jar:sources",
+      "org.checkerframework:checker-qual",
+      "org.checkerframework:checker-qual:jar:sources",
+      "org.codehaus.mojo:animal-sniffer-annotations",
+      "org.codehaus.mojo:animal-sniffer-annotations:jar:sources",
+      "org.conscrypt:conscrypt-openjdk-uber",
+      "org.conscrypt:conscrypt-openjdk-uber:jar:sources",
+      "org.hamcrest:hamcrest-core",
+      "org.hamcrest:hamcrest-core:jar:sources",
+      "org.jetbrains:annotations",
+      "org.jetbrains:annotations:jar:sources",
+      "org.rnorth.duct-tape:duct-tape",
+      "org.rnorth.duct-tape:duct-tape:jar:sources",
+      "org.testcontainers:gcloud",
+      "org.testcontainers:gcloud:jar:sources",
+      "org.testcontainers:testcontainers",
+      "org.testcontainers:testcontainers:jar:sources",
+      "org.threeten:threetenbp",
+      "org.threeten:threetenbp:jar:sources"
+    ]
+  },
+  "services": {
+    "io.grpc:grpc-core": {
+      "io.grpc.LoadBalancerProvider": [
+        "io.grpc.internal.PickFirstLoadBalancerProvider"
+      ],
+      "io.grpc.NameResolverProvider": [
+        "io.grpc.internal.DnsNameResolverProvider"
+      ]
+    },
+    "io.grpc:grpc-core:jar:sources": {
+      "io.grpc.LoadBalancerProvider": [
+        "io.grpc.internal.PickFirstLoadBalancerProvider"
+      ],
+      "io.grpc.NameResolverProvider": [
+        "io.grpc.internal.DnsNameResolverProvider"
+      ]
+    },
+    "io.grpc:grpc-googleapis": {
+      "io.grpc.NameResolverProvider": [
+        "io.grpc.googleapis.GoogleCloudToProdExperimentalNameResolverProvider",
+        "io.grpc.googleapis.GoogleCloudToProdNameResolverProvider"
+      ]
+    },
+    "io.grpc:grpc-googleapis:jar:sources": {
+      "io.grpc.NameResolverProvider": [
+        "io.grpc.googleapis.GoogleCloudToProdExperimentalNameResolverProvider",
+        "io.grpc.googleapis.GoogleCloudToProdNameResolverProvider"
+      ]
+    },
+    "io.grpc:grpc-grpclb": {
+      "io.grpc.LoadBalancerProvider": [
+        "io.grpc.grpclb.GrpclbLoadBalancerProvider"
+      ],
+      "io.grpc.NameResolverProvider": [
+        "io.grpc.grpclb.SecretGrpclbNameResolverProvider$Provider"
+      ]
+    },
+    "io.grpc:grpc-grpclb:jar:sources": {
+      "io.grpc.LoadBalancerProvider": [
+        "io.grpc.grpclb.GrpclbLoadBalancerProvider"
+      ],
+      "io.grpc.NameResolverProvider": [
+        "io.grpc.grpclb.SecretGrpclbNameResolverProvider$Provider"
+      ]
+    },
+    "io.grpc:grpc-netty-shaded": {
+      "io.grpc.ManagedChannelProvider": [
+        "io.grpc.netty.shaded.io.grpc.netty.NettyChannelProvider",
+        "io.grpc.netty.shaded.io.grpc.netty.UdsNettyChannelProvider"
+      ],
+      "io.grpc.NameResolverProvider": [
+        "io.grpc.netty.shaded.io.grpc.netty.UdsNameResolverProvider"
+      ],
+      "io.grpc.ServerProvider": [
+        "io.grpc.netty.shaded.io.grpc.netty.NettyServerProvider"
+      ],
+      "reactor.blockhound.integration.BlockHoundIntegration": [
+        "io.grpc.netty.shaded.io.netty.util.internal.Hidden$NettyBlockHoundIntegration"
+      ]
+    },
+    "io.grpc:grpc-services": {
+      "io.grpc.LoadBalancerProvider": [
+        "io.grpc.protobuf.services.internal.HealthCheckingRoundRobinLoadBalancerProvider"
+      ]
+    },
+    "io.grpc:grpc-services:jar:sources": {
+      "io.grpc.LoadBalancerProvider": [
+        "io.grpc.protobuf.services.internal.HealthCheckingRoundRobinLoadBalancerProvider"
+      ]
+    },
+    "io.grpc:grpc-util": {
+      "io.grpc.LoadBalancerProvider": [
+        "io.grpc.util.OutlierDetectionLoadBalancerProvider",
+        "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"
+      ]
+    },
+    "io.grpc:grpc-util:jar:sources": {
+      "io.grpc.LoadBalancerProvider": [
+        "io.grpc.util.OutlierDetectionLoadBalancerProvider",
+        "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"
+      ]
+    },
+    "io.grpc:grpc-xds": {
+      "io.grpc.LoadBalancerProvider": [
+        "io.grpc.xds.CdsLoadBalancerProvider",
+        "io.grpc.xds.ClusterImplLoadBalancerProvider",
+        "io.grpc.xds.ClusterManagerLoadBalancerProvider",
+        "io.grpc.xds.ClusterResolverLoadBalancerProvider",
+        "io.grpc.xds.LeastRequestLoadBalancerProvider",
+        "io.grpc.xds.PriorityLoadBalancerProvider",
+        "io.grpc.xds.RingHashLoadBalancerProvider",
+        "io.grpc.xds.WeightedRoundRobinLoadBalancerProvider",
+        "io.grpc.xds.WeightedTargetLoadBalancerProvider",
+        "io.grpc.xds.WrrLocalityLoadBalancerProvider"
+      ],
+      "io.grpc.NameResolverProvider": [
+        "io.grpc.xds.XdsNameResolverProvider"
+      ],
+      "io.grpc.xds.XdsCredentialsProvider": [
+        "io.grpc.xds.internal.GoogleDefaultXdsCredentialsProvider",
+        "io.grpc.xds.internal.InsecureXdsCredentialsProvider",
+        "io.grpc.xds.internal.TlsXdsCredentialsProvider"
+      ]
+    },
+    "io.grpc:grpc-xds:jar:sources": {
+      "io.grpc.LoadBalancerProvider": [
+        "io.grpc.xds.CdsLoadBalancerProvider",
+        "io.grpc.xds.ClusterImplLoadBalancerProvider",
+        "io.grpc.xds.ClusterManagerLoadBalancerProvider",
+        "io.grpc.xds.ClusterResolverLoadBalancerProvider",
+        "io.grpc.xds.LeastRequestLoadBalancerProvider",
+        "io.grpc.xds.PriorityLoadBalancerProvider",
+        "io.grpc.xds.RingHashLoadBalancerProvider",
+        "io.grpc.xds.WeightedRoundRobinLoadBalancerProvider",
+        "io.grpc.xds.WeightedTargetLoadBalancerProvider",
+        "io.grpc.xds.WrrLocalityLoadBalancerProvider"
+      ],
+      "io.grpc.NameResolverProvider": [
+        "io.grpc.xds.XdsNameResolverProvider"
+      ],
+      "io.grpc.xds.XdsCredentialsProvider": [
+        "io.grpc.xds.internal.GoogleDefaultXdsCredentialsProvider",
+        "io.grpc.xds.internal.InsecureXdsCredentialsProvider",
+        "io.grpc.xds.internal.TlsXdsCredentialsProvider"
+      ]
+    },
+    "org.testcontainers:testcontainers": {
+      "org.testcontainers.dockerclient.DockerClientProviderStrategy": [
+        "org.testcontainers.dockerclient.DockerDesktopClientProviderStrategy",
+        "org.testcontainers.dockerclient.DockerMachineClientProviderStrategy",
+        "org.testcontainers.dockerclient.EnvironmentAndSystemPropertyClientProviderStrategy",
+        "org.testcontainers.dockerclient.NpipeSocketClientProviderStrategy",
+        "org.testcontainers.dockerclient.RootlessDockerClientProviderStrategy",
+        "org.testcontainers.dockerclient.TestcontainersHostPropertyClientProviderStrategy",
+        "org.testcontainers.dockerclient.UnixSocketClientProviderStrategy"
+      ],
+      "org.testcontainers.shaded.com.fasterxml.jackson.core.JsonFactory": [
+        "org.testcontainers.shaded.com.fasterxml.jackson.core.JsonFactory"
+      ],
+      "org.testcontainers.shaded.com.fasterxml.jackson.core.ObjectCodec": [
+        "org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper"
+      ]
+    },
+    "org.threeten:threetenbp": {
+      "org.threeten.bp.zone.ZoneRulesProvider": [
+        "org.threeten.bp.zone.TzdbZoneRulesProvider"
+      ]
+    }
+  },
+  "version": "3"
+}
diff --git a/high-availability-gcp_third_party_runtime_jars.allowlist.txt b/high-availability-gcp_third_party_runtime_jars.allowlist.txt
new file mode 100644
index 0000000..eadbd99
--- /dev/null
+++ b/high-availability-gcp_third_party_runtime_jars.allowlist.txt
@@ -0,0 +1,45 @@
+animal-sniffer-annotations
+annotations
+api-common
+auto-value-annotations
+checker-qual
+conscrypt-openjdk-uber
+error_prone_annotations
+gax
+gax-grpc
+gax-httpjson
+google-auth-library-credentials
+google-auth-library-oauth2-http
+google-cloud-pubsub
+google-http-client
+google-http-client-gson
+grpc-alts
+grpc-api
+grpc-auth
+grpc-context
+grpc-core
+grpc-googleapis
+grpc-grpclb
+grpc-inprocess
+grpc-netty-shaded
+grpc-protobuf
+grpc-protobuf-lite
+grpc-services
+grpc-stub
+grpc-util
+grpc-xds
+guava-33.4.0-jre
+javax.annotation-api
+listenablefuture-9999.0-empty-to-avoid-conflict-with-guava
+opencensus-api
+opencensus-contrib-http-util
+opentelemetry-api
+opentelemetry-context
+opentelemetry-semconv-1.26.0-alpha
+perfmark-api
+proto-google-cloud-pubsub-v1
+proto-google-common-protos
+proto-google-iam-v1
+protobuf-java-util
+re2j
+threetenbp
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/Configuration.java
new file mode 100644
index 0000000..0a0fe9a
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/Configuration.java
@@ -0,0 +1,80 @@
+// 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import com.google.gerrit.server.config.ConfigUtil;
+import java.time.Duration;
+import org.eclipse.jgit.lib.Config;
+
+public record Configuration(
+    Identity identity, Topics topics, Limits limits, Retention retention, Retry retry) {
+
+  public record Identity(String project, String keyLocation) {}
+
+  public record Topics(String main, String streamEvents) {}
+
+  public record Limits(
+      Duration subscriptionTimeout,
+      Duration shutdownTimeout,
+      int publisherThreads,
+      int subscriberThreads) {}
+
+  public record Retention(Duration ackDeadline, Duration duration, boolean retainAcked) {}
+
+  public record Retry(int maxDeliveryAttempts, Duration minimumBackoff, Duration maximumBackoff) {}
+
+  private static final String PUBSUB = "pubsub";
+  private static final String TOPICS = "topics";
+  private static final String LIMITS = "limits";
+  private static final String RETENTION = "retention";
+  private static final String RETRY = "retry";
+
+  public Configuration(Config cfg) {
+    this(
+        new Identity(
+            cfg.getString(PUBSUB, null, "projectId"),
+            cfg.getString(PUBSUB, null, "privateKeyLocation")),
+        new Topics(
+            getString(cfg, PUBSUB, TOPICS, "main", "default"),
+            getString(cfg, PUBSUB, TOPICS, "streamEvents", "stream-events")),
+        new Limits(
+            duration(cfg, PUBSUB, LIMITS, "subscriptionTimeout", Duration.ofSeconds(10)),
+            duration(cfg, PUBSUB, LIMITS, "shutdownTimeout", Duration.ofSeconds(10)),
+            cfg.getInt(PUBSUB, LIMITS, "publisherThreadPoolSize", 4),
+            cfg.getInt(PUBSUB, LIMITS, "subscriberThreadPoolSize", 4)),
+        new Retention(
+            duration(cfg, PUBSUB, RETENTION, "ackDeadline", Duration.ofSeconds(10)),
+            duration(cfg, PUBSUB, RETENTION, "messageRetentionDuration", Duration.ofDays(7)),
+            cfg.getBoolean(PUBSUB, RETENTION, "retainAckedMessages", false)),
+        new Retry(
+            cfg.getInt(PUBSUB, RETRY, "maxDeliveryAttempts", 5),
+            duration(cfg, PUBSUB, RETRY, "minimumBackoff", Duration.ofSeconds(10)),
+            duration(cfg, PUBSUB, RETRY, "maximumBackoff", Duration.ofMinutes(10))));
+  }
+
+  private static Duration duration(
+      Config c, String section, String subsection, String key, Duration defaultValue) {
+    return Duration.ofMillis(
+        ConfigUtil.getTimeUnit(c, section, subsection, key, defaultValue.toMillis(), MILLISECONDS));
+  }
+
+  private static String getString(
+      Config cfg, String section, String subsection, String field, String def) {
+    String value = cfg.getString(section, subsection, field);
+    return value == null ? def : value;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/DefaultTopic.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/DefaultTopic.java
new file mode 100644
index 0000000..0fa3ea1
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/DefaultTopic.java
@@ -0,0 +1,24 @@
+// 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+
+@BindingAnnotation
+@Retention(RUNTIME)
+public @interface DefaultTopic {}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/GCPPublisherFactory.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/GCPPublisherFactory.java
new file mode 100644
index 0000000..ad16215
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/GCPPublisherFactory.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.api.gax.core.CredentialsProvider;
+import com.google.api.gax.core.ExecutorProvider;
+import com.google.cloud.pubsub.v1.Publisher;
+import com.google.gerrit.server.StartupException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.pubsub.v1.TopicName;
+import java.io.IOException;
+
+@Singleton
+public class GCPPublisherFactory implements PublisherFactory {
+  private final CredentialsProvider credentials;
+  private final ExecutorProvider executor;
+
+  @Inject
+  public GCPPublisherFactory(
+      CredentialsProvider credentials, @PublisherExecutorProvider ExecutorProvider executor) {
+    this.credentials = credentials;
+    this.executor = executor;
+  }
+
+  @Override
+  public Publisher create(TopicName topic) {
+    try {
+      return Publisher.newBuilder(topic)
+          .setExecutorProvider(executor)
+          .setCredentialsProvider(credentials)
+          .build();
+    } catch (IOException e) {
+      throw new StartupException("Failed to create publisher for PubSub.", e);
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/GCPSubscriberFactory.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/GCPSubscriberFactory.java
new file mode 100644
index 0000000..56f1b4b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/GCPSubscriberFactory.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.api.gax.core.CredentialsProvider;
+import com.google.api.gax.core.ExecutorProvider;
+import com.google.cloud.pubsub.v1.MessageReceiver;
+import com.google.cloud.pubsub.v1.Subscriber;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.pubsub.v1.ProjectSubscriptionName;
+
+@Singleton
+public class GCPSubscriberFactory implements SubscriberFactory {
+  private final CredentialsProvider credentials;
+  private final MessageReceiver messageReceiver;
+  private final ExecutorProvider executor;
+
+  @Inject
+  public GCPSubscriberFactory(
+      CredentialsProvider credentials,
+      MessageReceiver messageReceiver,
+      @SubscriberExecutorProvider ExecutorProvider executor) {
+    this.credentials = credentials;
+    this.messageReceiver = messageReceiver;
+    this.executor = executor;
+  }
+
+  @Override
+  public Subscriber create(ProjectSubscriptionName subscriptionName) {
+    return Subscriber.newBuilder(subscriptionName.toString(), messageReceiver)
+        .setExecutorProvider(executor)
+        .setCredentialsProvider(credentials)
+        .build();
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/GcpProjectId.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/GcpProjectId.java
new file mode 100644
index 0000000..3a2bb32
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/GcpProjectId.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2025 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+
+@BindingAnnotation
+@Retention(RUNTIME)
+public @interface GcpProjectId {}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/GcpPubSubForwarderModule.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/GcpPubSubForwarderModule.java
new file mode 100644
index 0000000..36f9dd2
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/GcpPubSubForwarderModule.java
@@ -0,0 +1,223 @@
+// 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+import com.google.api.gax.core.CredentialsProvider;
+import com.google.api.gax.core.ExecutorProvider;
+import com.google.api.gax.core.FixedCredentialsProvider;
+import com.google.api.gax.core.InstantiatingExecutorProvider;
+import com.google.api.gax.core.NoCredentialsProvider;
+import com.google.api.gax.grpc.GrpcTransportChannel;
+import com.google.api.gax.rpc.FixedTransportChannelProvider;
+import com.google.api.gax.rpc.TransportChannelProvider;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+import com.google.cloud.pubsub.v1.MessageReceiver;
+import com.google.cloud.pubsub.v1.Publisher;
+import com.google.cloud.pubsub.v1.Subscriber;
+import com.google.cloud.pubsub.v1.SubscriptionAdminClient;
+import com.google.cloud.pubsub.v1.SubscriptionAdminSettings;
+import com.google.cloud.pubsub.v1.TopicAdminClient;
+import com.google.cloud.pubsub.v1.TopicAdminSettings;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.inject.AbstractModule;
+import com.google.inject.Module;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import com.google.inject.util.Modules;
+import com.google.pubsub.v1.TopicName;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+public class GcpPubSubForwarderModule extends LifecycleModule {
+  public static final String PUBSUB_EMULATOR_HOST = "PUBSUB_EMULATOR_HOST";
+
+  public static Module create() {
+    String hostPort = getEmulatorHost();
+    if (Strings.isNullOrEmpty(hostPort)) {
+      return createForRealPubSub();
+    }
+    return createForEmulator(hostPort);
+  }
+
+  public static Module createForEmulator(String hostPort) {
+    return Modules.override(new GcpPubSubForwarderModule()).with(new EmulatorModule(hostPort));
+  }
+
+  public static GcpPubSubForwarderModule createForRealPubSub() {
+    return new GcpPubSubForwarderModule();
+  }
+
+  @VisibleForTesting
+  GcpPubSubForwarderModule() {}
+
+  @Override
+  protected void configure() {
+    bind(PubSubNamingFactory.class);
+    bind(PubSubInitializer.class);
+    bind(MessageReceiver.class).toProvider(MessageReceiverProvider.class);
+    bind(PublisherFactory.class).to(GCPPublisherFactory.class);
+    bind(SubscriberFactory.class).to(GCPSubscriberFactory.class);
+    bind(PubSubMessageProcessor.class);
+    bind(Forwarder.class).to(PubSubForwarder.class);
+    listener().to(OnStartStop.class);
+  }
+
+  @Provides
+  @Singleton
+  @GcpProjectId
+  public String getGcpProjectId(Configuration config) {
+    return config.identity().project();
+  }
+
+  @Provides
+  @Singleton
+  @PublisherExecutorProvider
+  @VisibleForTesting
+  public static ExecutorProvider buildPublisherExecutorProvider(Configuration config) {
+    return InstantiatingExecutorProvider.newBuilder()
+        .setExecutorThreadCount(config.limits().publisherThreads())
+        .build();
+  }
+
+  @Provides
+  @Singleton
+  @SubscriberExecutorProvider
+  @VisibleForTesting
+  public static ExecutorProvider buildSubscriberExecutorProvider(Configuration config) {
+    return InstantiatingExecutorProvider.newBuilder()
+        .setExecutorThreadCount(config.limits().subscriberThreads())
+        .build();
+  }
+
+  @Provides
+  @Singleton
+  @DefaultTopic
+  Publisher createDefaultTopicPublisher(
+      PublisherFactory factory, @GcpProjectId String projectId, TopicNames topicNames) {
+    return factory.create(TopicName.of(projectId, topicNames.defaultTopic()));
+  }
+
+  @Provides
+  @Singleton
+  @StreamEventsTopic
+  Publisher createStreamEventsTopicPublisher(
+      PublisherFactory factory, @GcpProjectId String projectId, TopicNames topicNames) {
+    return factory.create(TopicName.of(projectId, topicNames.streamEventsTopic()));
+  }
+
+  @Provides
+  @Singleton
+  Collection<Publisher> allPublishers(
+      @DefaultTopic Publisher defaultPublisher,
+      @StreamEventsTopic Publisher streamEventsPublisher) {
+    return List.of(defaultPublisher, streamEventsPublisher);
+  }
+
+  @Provides
+  @Singleton
+  Collection<Subscriber> allSubscribers(
+      TopicNames topicNames,
+      PubSubNamingFactory namingFactory,
+      SubscriberFactory subscriberFactory) {
+    return topicNames.all().stream()
+        .map(name -> namingFactory.createFor(name))
+        .map(names -> subscriberFactory.create(names.subscription()))
+        .toList();
+  }
+
+  private static String getEmulatorHost() {
+    return Optional.ofNullable(System.getenv(PUBSUB_EMULATOR_HOST))
+        .orElse(System.getProperty(PUBSUB_EMULATOR_HOST));
+  }
+
+  @Provides
+  @Singleton
+  CredentialsProvider getCredentialsProvider(Configuration config)
+      throws FileNotFoundException, IOException {
+    return FixedCredentialsProvider.create(
+        ServiceAccountCredentials.fromStream(new FileInputStream(config.identity().keyLocation())));
+  }
+
+  @Provides
+  @Singleton
+  SubscriptionAdminClient createSubscriptionAdminClient(CredentialsProvider credentials)
+      throws IOException {
+    return SubscriptionAdminClient.create(
+        SubscriptionAdminSettings.newBuilder().setCredentialsProvider(credentials).build());
+  }
+
+  @Provides
+  @Singleton
+  TopicAdminClient createTopicAdminClient(CredentialsProvider credentials) throws IOException {
+    TopicAdminSettings settings =
+        TopicAdminSettings.newBuilder().setCredentialsProvider(credentials).build();
+    return TopicAdminClient.create(settings);
+  }
+
+  static class EmulatorModule extends AbstractModule {
+    private final String hostPort;
+
+    EmulatorModule(String hostPort) {
+      this.hostPort = hostPort;
+    }
+
+    @Override
+    protected void configure() {
+      bind(CredentialsProvider.class).toInstance(NoCredentialsProvider.create());
+      bind(PublisherFactory.class).to(LocalPublisherFactory.class);
+      bind(SubscriberFactory.class).to(LocalSubscriberFactory.class);
+    }
+
+    @Provides
+    @Singleton
+    TransportChannelProvider createTransportChannelProvider() {
+      ManagedChannel channel = ManagedChannelBuilder.forTarget(hostPort).usePlaintext().build();
+      return FixedTransportChannelProvider.create(GrpcTransportChannel.create(channel));
+    }
+
+    @Provides
+    @Singleton
+    SubscriptionAdminClient createSubscriptionAdminClient(TransportChannelProvider channelProvider)
+        throws IOException {
+      SubscriptionAdminSettings settings =
+          SubscriptionAdminSettings.newBuilder()
+              .setTransportChannelProvider(channelProvider)
+              .setCredentialsProvider(NoCredentialsProvider.create())
+              .build();
+      return SubscriptionAdminClient.create(settings);
+    }
+
+    @Provides
+    @Singleton
+    TopicAdminClient createTopicAdminClient(TransportChannelProvider channelProvider)
+        throws IOException {
+      TopicAdminSettings settings =
+          TopicAdminSettings.newBuilder()
+              .setTransportChannelProvider(channelProvider)
+              .setCredentialsProvider(NoCredentialsProvider.create())
+              .build();
+      return TopicAdminClient.create(settings);
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/LocalPublisherFactory.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/LocalPublisherFactory.java
new file mode 100644
index 0000000..e6b6ece
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/LocalPublisherFactory.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.api.gax.core.CredentialsProvider;
+import com.google.api.gax.core.ExecutorProvider;
+import com.google.api.gax.rpc.TransportChannelProvider;
+import com.google.cloud.pubsub.v1.Publisher;
+import com.google.gerrit.server.StartupException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.pubsub.v1.TopicName;
+import java.io.IOException;
+
+@Singleton
+public class LocalPublisherFactory implements PublisherFactory {
+  private final CredentialsProvider credentials;
+  private final TransportChannelProvider transportChannelProvider;
+  private final ExecutorProvider executor;
+
+  @Inject
+  public LocalPublisherFactory(
+      CredentialsProvider credentials,
+      TransportChannelProvider transportChannelProvider,
+      @PublisherExecutorProvider ExecutorProvider executor) {
+    this.credentials = credentials;
+    this.transportChannelProvider = transportChannelProvider;
+    this.executor = executor;
+  }
+
+  @Override
+  public Publisher create(TopicName topic) {
+    try {
+      return Publisher.newBuilder(topic)
+          .setExecutorProvider(executor)
+          .setChannelProvider(transportChannelProvider)
+          .setCredentialsProvider(credentials)
+          .build();
+    } catch (IOException e) {
+      throw new StartupException("Failed to create publisher for PubSub.", e);
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/LocalSubscriberFactory.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/LocalSubscriberFactory.java
new file mode 100644
index 0000000..7e88acf
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/LocalSubscriberFactory.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.api.gax.core.CredentialsProvider;
+import com.google.api.gax.core.ExecutorProvider;
+import com.google.api.gax.rpc.TransportChannelProvider;
+import com.google.cloud.pubsub.v1.MessageReceiver;
+import com.google.cloud.pubsub.v1.Subscriber;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.pubsub.v1.ProjectSubscriptionName;
+
+@Singleton
+public class LocalSubscriberFactory implements SubscriberFactory {
+  private final CredentialsProvider credentials;
+  private TransportChannelProvider transportChannelProvider;
+  private final MessageReceiver messageReceiver;
+  private final ExecutorProvider executor;
+
+  @Inject
+  public LocalSubscriberFactory(
+      CredentialsProvider credentials,
+      TransportChannelProvider transportChannelProvider,
+      MessageReceiver messageReceiver,
+      @SubscriberExecutorProvider ExecutorProvider executor) {
+    this.credentials = credentials;
+    this.transportChannelProvider = transportChannelProvider;
+    this.messageReceiver = messageReceiver;
+    this.executor = executor;
+  }
+
+  @Override
+  public Subscriber create(ProjectSubscriptionName subscriptionName) {
+    return Subscriber.newBuilder(subscriptionName.toString(), messageReceiver)
+        .setExecutorProvider(executor)
+        .setChannelProvider(transportChannelProvider)
+        .setCredentialsProvider(credentials)
+        .build();
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/MessageReceiverProvider.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/MessageReceiverProvider.java
new file mode 100644
index 0000000..22ed9f8
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/MessageReceiverProvider.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.cloud.pubsub.v1.AckReplyConsumer;
+import com.google.cloud.pubsub.v1.MessageReceiver;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.server.config.GerritInstanceId;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.pubsub.v1.PubsubMessage;
+
+@Singleton
+public class MessageReceiverProvider implements Provider<MessageReceiver> {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  private final PubSubMessageProcessor processor;
+  private final String instanceId;
+
+  @Inject
+  public MessageReceiverProvider(
+      PubSubMessageProcessor processor, @GerritInstanceId String instanceId) {
+    this.processor = processor;
+    this.instanceId = instanceId;
+  }
+
+  @Override
+  public MessageReceiver get() {
+    return (PubsubMessage message, AckReplyConsumer consumer) -> {
+      try {
+        logger.atFine().log("Received message: %s", message);
+        if (instanceId.equals(message.getAttributesMap().get("instanceId"))) {
+          logger.atWarning().log(
+              "Skipping message %s since it was sent by this instance %s",
+              message.getMessageId(), instanceId);
+          consumer.ack();
+          return;
+        }
+        if (processor.handle(message)) {
+          consumer.ack();
+        } else {
+          consumer.nack();
+        }
+      } catch (Exception e) {
+        consumer.nack();
+        logger.atSevere().withCause(e).log(
+            "Exception when consuming message %s [message: %s]",
+            message.getMessageId(), message.getData().toStringUtf8());
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/Module.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/Module.java
new file mode 100644
index 0000000..c61d073
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/Module.java
@@ -0,0 +1,35 @@
+// 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+
+public class Module extends AbstractModule {
+  @Override
+  protected void configure() {
+    DynamicItem.bind(binder(), Forwarder.class).to(PubSubForwarder.class);
+    install(GcpPubSubForwarderModule.create());
+  }
+
+  @Provides
+  Configuration createPubSub(PluginConfigFactory cfg, @PluginName String pluginName) {
+    return new Configuration(cfg.getGlobalPluginConfig(pluginName));
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/OnStartStop.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/OnStartStop.java
new file mode 100644
index 0000000..663f66b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/OnStartStop.java
@@ -0,0 +1,97 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.cloud.pubsub.v1.Publisher;
+import com.google.cloud.pubsub.v1.Subscriber;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.util.Collection;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@Singleton
+public class OnStartStop implements LifecycleListener {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  private final PubSubInitializer initializer;
+  private final Configuration config;
+
+  private final Provider<Collection<Publisher>> publishers;
+  private final Provider<Collection<Subscriber>> subscribers;
+
+  @Inject
+  public OnStartStop(
+      PubSubInitializer initializer,
+      Configuration config,
+      Provider<Collection<Publisher>> publishers,
+      Provider<Collection<Subscriber>> subscribers) {
+    this.initializer = initializer;
+    this.config = config;
+    this.publishers = publishers;
+    this.subscribers = subscribers;
+  }
+
+  @Override
+  public void start() {
+    initializer.initialize();
+    startSubscribers();
+  }
+
+  private void startSubscribers() {
+    for (Subscriber subscriber : subscribers.get()) {
+      try {
+        subscriber
+            .startAsync()
+            .awaitRunning(config.limits().subscriptionTimeout().getSeconds(), TimeUnit.SECONDS);
+      } catch (TimeoutException e) {
+        throw new IllegalStateException("Timeout while subscribing to PubSub topic", e);
+      }
+    }
+  }
+
+  @Override
+  public void stop() {
+    shutdownPublishers();
+    stopSubscribers();
+  }
+
+  private void shutdownPublishers() {
+    for (Publisher publisher : publishers.get()) {
+      try {
+        publisher.shutdown();
+        publisher.awaitTermination(
+            config.limits().shutdownTimeout().getSeconds(), TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        logger.atSevere().withCause(e).log("Could not close the MessageDispatcher");
+      }
+    }
+  }
+
+  private void stopSubscribers() {
+    for (Subscriber subscriber : subscribers.get()) {
+      try {
+        subscriber
+            .stopAsync()
+            .awaitTerminated(config.limits().shutdownTimeout().getSeconds(), TimeUnit.SECONDS);
+      } catch (TimeoutException e) {
+        logger.atSevere().withCause(e).log("Timeout during stopping of subscription.");
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubForwarder.java
new file mode 100644
index 0000000..87947b9
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubForwarder.java
@@ -0,0 +1,161 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+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.api.core.ApiFutureCallback;
+import com.google.api.core.ApiFutures;
+import com.google.cloud.pubsub.v1.Publisher;
+import com.google.common.flogger.FluentLogger;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.config.GerritInstanceId;
+import com.google.gerrit.server.events.Event;
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.protobuf.ByteString;
+import com.google.pubsub.v1.PubsubMessage;
+import java.time.Instant;
+import java.util.concurrent.CompletableFuture;
+
+@Singleton
+public class PubSubForwarder implements Forwarder {
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
+
+  private final Publisher defaultPublisher;
+  private final Publisher streamEventsPublisher;
+  private final Gson gson;
+  private final String instanceId;
+
+  @Inject
+  PubSubForwarder(
+      @DefaultTopic Publisher defaultPublisher,
+      @StreamEventsTopic Publisher streamEventsPublisher,
+      @CommandsGson Gson gson,
+      @GerritInstanceId String instanceId) {
+    this.defaultPublisher = defaultPublisher;
+    this.streamEventsPublisher = streamEventsPublisher;
+    this.gson = gson;
+    this.instanceId = instanceId;
+  }
+
+  @Override
+  public CompletableFuture<Result> indexAccount(int accountId, IndexEvent indexEvent) {
+    return execute(new IndexAccount(accountId, indexEvent.eventCreatedOn), defaultPublisher);
+  }
+
+  @Override
+  public CompletableFuture<Result> indexChange(
+      String projectName, int changeId, IndexEvent indexEvent) {
+    return execute(
+        new IndexChange.Update(projectName, changeId, indexEvent), defaultPublisher);
+  }
+
+  @Override
+  public CompletableFuture<Result> batchIndexChange(
+      String projectName, int changeId, IndexEvent indexEvent) {
+    return execute(
+        new IndexChange.BatchUpdate(projectName, changeId, indexEvent),
+        defaultPublisher);
+  }
+
+  @Override
+  public CompletableFuture<Result> deleteChangeFromIndex(
+      String projectName, int changeId, IndexEvent indexEvent) {
+    return execute(
+        new IndexChange.Delete(projectName, changeId, indexEvent), defaultPublisher);
+  }
+
+  @Override
+  public CompletableFuture<Result> indexGroup(String uuid, IndexEvent indexEvent) {
+    return execute(new IndexGroup(uuid, indexEvent.eventCreatedOn), defaultPublisher);
+  }
+
+  @Override
+  public CompletableFuture<Result> indexProject(String projectName, IndexEvent indexEvent) {
+    return execute(new IndexProject(projectName, indexEvent.eventCreatedOn), defaultPublisher);
+  }
+
+  @Override
+  public CompletableFuture<Result> send(Event event) {
+    return execute(
+        new PostEvent(event, Instant.ofEpochSecond(event.eventCreatedOn)), streamEventsPublisher);
+  }
+
+  @Override
+  public CompletableFuture<Result> evict(String cacheName, Object key) {
+    return execute(new EvictCache(cacheName, gson.toJson(key), Instant.now()), defaultPublisher);
+  }
+
+  @Override
+  public CompletableFuture<Result> addToProjectList(String projectName) {
+    return execute(new AddToProjectList(projectName, Instant.now()), defaultPublisher);
+  }
+
+  @Override
+  public CompletableFuture<Result> removeFromProjectList(String projectName) {
+    return execute(new RemoveFromProjectList(projectName, Instant.now()), defaultPublisher);
+  }
+
+  @Override
+  public CompletableFuture<Result> deleteAllChangesForProject(Project.NameKey projectName) {
+    return execute(
+        new DeleteAllProjectChangesFromIndex(projectName, Instant.now()), defaultPublisher);
+  }
+
+  private PubsubMessage buildMessage(Command cmd) {
+    return PubsubMessage.newBuilder()
+        .putAttributes("instanceId", instanceId)
+        .setData(ByteString.copyFromUtf8(gson.toJson(cmd)))
+        .build();
+  }
+
+  private CompletableFuture<Result> execute(Command cmd, Publisher publisher) {
+    CompletableFuture<Result> future = new CompletableFuture<>();
+    String msg = gson.toJson(cmd);
+    log.atFine().log("Publishing message: %s", msg);
+    ApiFutures.addCallback(
+        publisher.publish(buildMessage(cmd)),
+        new ApiFutureCallback<String>() {
+
+          @Override
+          public void onFailure(Throwable t) {
+            log.atSevere().withCause(t).log("Failed to publish message: %s", msg);
+            future.complete(new Result(cmd.type, false));
+          }
+
+          @Override
+          public void onSuccess(String result) {
+            log.atFine().log("Published message (id: %s): %s", result, msg);
+            future.complete(new Result(cmd.type, true));
+          }
+        },
+        MoreExecutors.directExecutor());
+    return future;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubInitializer.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubInitializer.java
new file mode 100644
index 0000000..990829f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubInitializer.java
@@ -0,0 +1,165 @@
+// Copyright (C) 2025 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.api.gax.rpc.AlreadyExistsException;
+import com.google.api.gax.rpc.NotFoundException;
+import com.google.cloud.pubsub.v1.SubscriptionAdminClient;
+import com.google.cloud.pubsub.v1.TopicAdminClient;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.server.config.GerritInstanceId;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.protobuf.FieldMask;
+import com.google.pubsub.v1.DeadLetterPolicy;
+import com.google.pubsub.v1.RetryPolicy;
+import com.google.pubsub.v1.Subscription;
+import com.google.pubsub.v1.TopicName;
+import com.google.pubsub.v1.UpdateSubscriptionRequest;
+
+/** Initialize topic(s) and subscription(s) for Pub/Sub if they do not already exist. */
+@Singleton
+public class PubSubInitializer {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  private final TopicAdminClient topicAdminClient;
+  private final SubscriptionAdminClient subscriptionAdminClient;
+  private final Configuration config;
+  private final String instanceId;
+  private final TopicNames topicNames;
+  private final PubSubNamingFactory namingFactory;
+
+  @Inject
+  PubSubInitializer(
+      TopicAdminClient topicAdminClient,
+      SubscriptionAdminClient subscriptionAdminClient,
+      Configuration config,
+      @GerritInstanceId String instanceId,
+      TopicNames topicNames,
+      PubSubNamingFactory namingFactory) {
+    this.topicAdminClient = topicAdminClient;
+    this.subscriptionAdminClient = subscriptionAdminClient;
+    this.config = config;
+    this.instanceId = instanceId;
+    this.topicNames = topicNames;
+    this.namingFactory = namingFactory;
+  }
+
+  public void initialize() {
+    for (String name : topicNames.all()) {
+      PubSubNames names = namingFactory.createFor(name);
+      initializeTopic(names.topic());
+      initializeSubscription(names);
+    }
+  }
+
+  private void initializeTopic(TopicName topic) {
+    try {
+      topicAdminClient.createTopic(topic);
+      logger.atInfo().log("Created topic %s", topic.getTopic());
+    } catch (AlreadyExistsException e) {
+      logger.atInfo().log("Topic %s already exists", topic.getTopic());
+    }
+  }
+
+  private void initializeSubscription(PubSubNames names) {
+    // Initialize the DLT topic and its default subscription
+    initializeTopic(names.deadLetterTopic());
+    initializeDltSubscription(names);
+
+    // Initialize the main subscription
+    Subscription subscription;
+    try {
+      subscription = subscriptionAdminClient.getSubscription(names.subscription());
+      logger.atInfo().log("Subscription for topic %s already exists", names.topic());
+    } catch (NotFoundException e) {
+      logger.atInfo().log("Creating subscription for topic %s", names.topic());
+      String filter = String.format("attributes.instanceId!=\"%s\"", instanceId);
+      subscription =
+          Subscription.newBuilder()
+              .setName(names.subscription().toString())
+              .setTopic(names.topic().toString())
+              .setFilter(filter)
+              .build();
+      subscriptionAdminClient.createSubscription(subscription);
+    }
+
+    updateSubscriptionSettings(subscription, names.deadLetterTopic());
+  }
+
+  private void initializeDltSubscription(PubSubNames names) {
+    try {
+      subscriptionAdminClient.getSubscription(names.deadLetterSubscription());
+      logger.atInfo().log("DLT Subscription for topic %s already exists", names.deadLetterTopic());
+    } catch (NotFoundException e) {
+      logger.atInfo().log("Creating DLT subscription for topic %s", names.deadLetterTopic());
+      Subscription subscription =
+          Subscription.newBuilder()
+              .setName(names.deadLetterSubscription().toString())
+              .setTopic(names.deadLetterTopic().toString())
+              .build();
+      subscriptionAdminClient.createSubscription(subscription);
+    }
+  }
+
+  private void updateSubscriptionSettings(Subscription subscription, TopicName dltTopicName) {
+    DeadLetterPolicy deadLetterPolicy =
+        DeadLetterPolicy.newBuilder()
+            .setDeadLetterTopic(dltTopicName.toString())
+            .setMaxDeliveryAttempts(config.retry().maxDeliveryAttempts())
+            .build();
+
+    RetryPolicy retryPolicy =
+        RetryPolicy.newBuilder()
+            .setMinimumBackoff(
+                com.google.protobuf.Duration.newBuilder()
+                    .setSeconds(config.retry().minimumBackoff().getSeconds())
+                    .build())
+            .setMaximumBackoff(
+                com.google.protobuf.Duration.newBuilder()
+                    .setSeconds(config.retry().maximumBackoff().getSeconds())
+                    .build())
+            .build();
+
+    Subscription desired =
+        subscription.toBuilder()
+            .setAckDeadlineSeconds((int) config.retention().ackDeadline().getSeconds())
+            .setMessageRetentionDuration(
+                com.google.protobuf.Duration.newBuilder()
+                    .setSeconds(config.retention().duration().getSeconds())
+                    .build())
+            .setRetainAckedMessages(config.retention().retainAcked())
+            .setDeadLetterPolicy(deadLetterPolicy)
+            .setRetryPolicy(retryPolicy)
+            .build();
+
+    FieldMask fieldMask =
+        FieldMask.newBuilder()
+            .addPaths("ack_deadline_seconds")
+            .addPaths("message_retention_duration")
+            .addPaths("retain_acked_messages")
+            .addPaths("dead_letter_policy")
+            .addPaths("retry_policy")
+            .build();
+
+    UpdateSubscriptionRequest updateRequest =
+        UpdateSubscriptionRequest.newBuilder()
+            .setSubscription(desired)
+            .setUpdateMask(fieldMask)
+            .build();
+
+    subscriptionAdminClient.updateSubscription(updateRequest);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubMessageProcessor.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubMessageProcessor.java
new file mode 100644
index 0000000..c3fcada
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubMessageProcessor.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+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.gerrit.extensions.registration.DynamicItem;
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.pubsub.v1.PubsubMessage;
+
+@Singleton
+public class PubSubMessageProcessor {
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
+
+  private final Gson gson;
+  private final DynamicItem<CommandProcessor> processor;
+
+  @Inject
+  PubSubMessageProcessor(@CommandsGson Gson gson, DynamicItem<CommandProcessor> processor) {
+    this.gson = gson;
+    this.processor = processor;
+  }
+
+  public boolean handle(PubsubMessage msg) {
+    log.atFine().log("Processing message: %s", msg);
+    return processor.get().handle(getCommand(msg));
+  }
+
+  private Command getCommand(PubsubMessage msg) {
+    try {
+      String s = msg.getData().toStringUtf8();
+      return gson.fromJson(s, Command.class);
+    } catch (RuntimeException e) {
+      log.atSevere().withCause(e).log("Error parsing message %s", msg);
+      throw e;
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubNames.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubNames.java
new file mode 100644
index 0000000..510ca31
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubNames.java
@@ -0,0 +1,24 @@
+// 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.pubsub.v1.ProjectSubscriptionName;
+import com.google.pubsub.v1.TopicName;
+
+public record PubSubNames(
+    TopicName topic,
+    ProjectSubscriptionName subscription,
+    TopicName deadLetterTopic,
+    ProjectSubscriptionName deadLetterSubscription) {}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubNamingFactory.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubNamingFactory.java
new file mode 100644
index 0000000..36dd418
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubNamingFactory.java
@@ -0,0 +1,49 @@
+// 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.gerrit.server.config.GerritInstanceId;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.pubsub.v1.ProjectSubscriptionName;
+import com.google.pubsub.v1.TopicName;
+
+@Singleton
+public class PubSubNamingFactory {
+
+  private final String projectId;
+  private final String instanceId;
+
+  @Inject
+  public PubSubNamingFactory(@GcpProjectId String projectId, @GerritInstanceId String instanceId) {
+    this.projectId = projectId;
+    this.instanceId = instanceId;
+  }
+
+  public PubSubNames createFor(String topicId) {
+    TopicName topic = TopicName.of(projectId, topicId);
+
+    String subId = String.format("%s.%s", topicId, instanceId);
+    ProjectSubscriptionName sub = ProjectSubscriptionName.of(projectId, subId);
+
+    String dltTopicId = String.format("%s.dlt", subId);
+    TopicName dltTopic = TopicName.of(projectId, dltTopicId);
+
+    String dltSubId = String.format("%s.monitoring", dltTopicId);
+    ProjectSubscriptionName dltSub = ProjectSubscriptionName.of(projectId, dltSubId);
+
+    return new PubSubNames(topic, sub, dltTopic, dltSub);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PublisherExecutorProvider.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PublisherExecutorProvider.java
new file mode 100644
index 0000000..d5e76e6
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PublisherExecutorProvider.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+
+@BindingAnnotation
+@Retention(RUNTIME)
+public @interface PublisherExecutorProvider {}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PublisherFactory.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PublisherFactory.java
new file mode 100644
index 0000000..2a5e381
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/PublisherFactory.java
@@ -0,0 +1,22 @@
+// Copyright (C) 2025 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.cloud.pubsub.v1.Publisher;
+import com.google.pubsub.v1.TopicName;
+
+public interface PublisherFactory {
+  Publisher create(TopicName topic);
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/StreamEventsTopic.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/StreamEventsTopic.java
new file mode 100644
index 0000000..7304b45
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/StreamEventsTopic.java
@@ -0,0 +1,24 @@
+// 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+
+@BindingAnnotation
+@Retention(RUNTIME)
+public @interface StreamEventsTopic {}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/SubscriberExecutorProvider.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/SubscriberExecutorProvider.java
new file mode 100644
index 0000000..a047912
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/SubscriberExecutorProvider.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+
+@BindingAnnotation
+@Retention(RUNTIME)
+public @interface SubscriberExecutorProvider {}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/SubscriberFactory.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/SubscriberFactory.java
new file mode 100644
index 0000000..2e85f9e
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/SubscriberFactory.java
@@ -0,0 +1,22 @@
+// Copyright (C) 2025 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.cloud.pubsub.v1.Subscriber;
+import com.google.pubsub.v1.ProjectSubscriptionName;
+
+public interface SubscriberFactory {
+  Subscriber create(ProjectSubscriptionName subscription);
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/SubscriptionNames.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/SubscriptionNames.java
new file mode 100644
index 0000000..6d8f12b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/SubscriptionNames.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2025 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.gerrit.server.config.GerritInstanceId;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.pubsub.v1.TopicName;
+import java.util.List;
+
+@Singleton
+public class SubscriptionNames {
+
+  private final List<String> names;
+  private final String projectId;
+  private final String instanceId;
+
+  public static String getSubscriptionName(String instanceId, String topic) {
+    return instanceId + "-" + topic;
+  }
+
+  @Inject
+  SubscriptionNames(
+      @GerritInstanceId String instanceId, @GcpProjectId String projectId, TopicNames topicNames) {
+    this.instanceId = instanceId;
+    this.projectId = projectId;
+    this.names = initializeNames(topicNames);
+  }
+
+  public String nameFor(TopicName topic) {
+    return instanceId + "-" + topic.getTopic();
+  }
+
+  public List<String> all() {
+    return names;
+  }
+
+  private List<String> initializeNames(TopicNames topicNames) {
+    return topicNames.all().stream()
+        .map(name -> TopicName.of(projectId, name))
+        .map(topic -> instanceId + "-" + topic.getTopic())
+        .toList();
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/TopicNames.java b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/TopicNames.java
new file mode 100644
index 0000000..b7a4578
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/highavailability/gcp/TopicNames.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2025 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.List;
+
+@Singleton
+public class TopicNames {
+  private final Configuration.Topics topics;
+
+  @Inject
+  public TopicNames(Configuration config) {
+    this.topics = config.topics();
+  }
+
+  public String defaultTopic() {
+    return topics.main();
+  }
+
+  public String streamEventsTopic() {
+    return topics.streamEvents();
+  }
+
+  public List<String> all() {
+    return List.of(topics.main(), topics.streamEvents());
+  }
+}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
new file mode 100644
index 0000000..4488f5e
--- /dev/null
+++ b/src/main/resources/Documentation/about.md
@@ -0,0 +1,45 @@
+# High Availability for Google Cloud Pub/Sub
+
+This plugin extends the Gerrit High Availability plugin by using Google Cloud Pub/Sub 
+as the message broker for forwarding events between Gerrit instances instead of HTTP-based 
+direct communication.
+
+## Google Cloud Pub/Sub as Message Broker
+
+Google Cloud Pub/Sub provides:
+
+* **Decoupling**: Instances do not need to know about each other directly or maintain HTTP connections
+* **Scalability**: Automatically scales to handle high message volume
+* **Reliability**: Messages are persisted and retried automatically
+* **Ordering**: Preserves message ordering per topic
+* **Dead-letter queues**: Messages that exceed max receive count are moved to a dead-letter topic for debugging
+
+## Prerequisites
+
+* Google Cloud Project with Pub/Sub API enabled
+* Google Cloud service account with the following roles:
+  - `roles/pubsub.editor` - To create and manage topics and subscriptions
+  - `roles/pubsub.publisher` - To publish messages
+  - `roles/pubsub.subscriber` - To consume messages
+* Service account credentials (JSON key file or Application Default Credentials)
+
+## How It Works
+
+Instead of the base plugin's HTTP-based peer communication model, this GCP extension:
+
+1. Publishes all HA events (cache evictions, index updates, stream events) to a Google Cloud Pub/Sub topic
+2. Each Gerrit instance subscribes to the topic via its own subscription
+3. Messages are processed asynchronously by each instance's subscription handler
+4. Failed messages are automatically retried per Pub/Sub's retry policy
+5. Messages exceeding the max receive count are moved to a dead-letter topic
+
+## Monitoring and Debugging
+
+Failed messages can be investigated via the dead-letter topic. Use Google Cloud Console or 
+`gcloud pubsub` CLI commands to:
+
+* View subscription lag and processing rates
+* Inspect dead-lettered messages
+* Monitor message throughput and latency
+
+For troubleshooting steps, refer to [config.md](config.md) and Google Cloud Pub/Sub documentation.
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
new file mode 100644
index 0000000..424ea8d
--- /dev/null
+++ b/src/main/resources/Documentation/build.md
@@ -0,0 +1,42 @@
+Build
+=====
+
+This plugin can be built with Bazel in the Gerrit tree.
+
+Clone or symlink this plugin repository to the `plugins/` directory
+of Gerrit's source tree. Edit the external dependency Bazel build file
+`gerrit/plugins/external_plugin_deps.MODULE.bazel` and add a line
+to import the plugin's external dependencies.
+
+If you also want to build the high-availability plugin (which
+the @PLUGIN@ depends on) from source code also clone or symlink
+its repository to the `plugins/` directory and add another line
+to the `external_plugin_deps.MODULE.bazel`:
+
+```
+include("//plugins/high-availability:external_plugin_deps.MODULE.bazel")
+include("//plugins/high-availability-aws:external_plugin_deps.MODULE.bazel")
+```
+
+From Gerrit source tree issue the command:
+
+```
+  bazel build plugins/@PLUGIN@
+```
+
+The output is created in
+
+```
+  bazel-bin/plugins/@PLUGIN@/@PLUGIN@.jar
+```
+
+To execute the tests run either one of:
+
+```
+  bazel test --test_tag_filters=@PLUGIN@ //...
+  bazel test plugins/@PLUGIN@:@PLUGIN@_tests
+```
+
+[Back to @PLUGIN@ documentation index][index]
+
+[index]: index.html
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
new file mode 100644
index 0000000..fa165c9
--- /dev/null
+++ b/src/main/resources/Documentation/config.md
@@ -0,0 +1,83 @@
+	@PLUGIN@ Configuration
+=========================
+
+Overview of all available configuration options:
+```
+[pubsub]
+  projectId = my-gcp-project
+  privateKeyLocation = /path/to/private/key/my-gcp-project-6f0d80a3f655.json
+[pubsub "topics"]
+  main = default
+  streamEvents = stream-events
+[pubsub "limits"]
+  subscriptionTimeout = 10s
+  shutdownTimeout = 10s
+  publisherThreadPoolSize = 4
+  subscriberThreadPoolSize = 4
+[pubsub "retention"]
+  ackDeadline = 10s
+  messageRetentionDuration = 7d
+  retainAckedMessages = false
+[pubsub "retry"]
+  maxDeliveryAttempts = 5
+  minimumBackoff = 10s
+  maximumBackoff = 10m
+```
+
+```pubsub.projectId```
+:   The name of the GCP project containing the PubSub topic to be used.
+
+```pubsub.privateKeyLocation```
+:   The location of the file containing the service account key of the service
+    account that Gerrit can use to create subscriptions for the topics configured in
+    pubsub.topics and to subscribe to it.
+
+```pubsub.topics.main```
+:   The name of the main PubSub topic used for publishing events.
+    Default: `default`
+
+```pubsub.topics.streamEvents```
+:   The name of the PubSub topic used for publishing stream events.
+    Default: `stream-events`
+
+```pubsub.limits.subscriptionTimeout```
+:   The timeout duration for subscription operations.
+    Default: `10s`
+
+```pubsub.limits.shutdownTimeout```
+:   The timeout duration for graceful shutdown of the plugin.
+    Default: `10s`
+
+```pubsub.limits.publisherThreadPoolSize```
+:   The number of threads used by the publisher thread pool for publishing messages.
+    Default: `4`
+
+```pubsub.limits.subscriberThreadPoolSize```
+:   The number of threads used by the subscriber thread pool for consuming messages.
+    Default: `4`
+
+```pubsub.retention.ackDeadline```
+:   The amount of time the Pub/Sub service will wait for an acknowledgement before
+    redelivering the message.
+    Default: `10s`
+
+```pubsub.retention.messageRetentionDuration```
+:   The length of time that messages in the topic should be retained.
+    Default: `7d`
+
+```pubsub.retention.retainAckedMessages```
+:   Whether to retain messages that have already been acknowledged.
+    Default: `false`
+
+```pubsub.retry.maxDeliveryAttempts```
+:   The maximum number of delivery attempts for a message before it is sent to
+    the dead letter topic.
+    Default: `5`
+
+```pubsub.retry.minimumBackoff```
+:   The minimum backoff duration for retrying failed message deliveries.
+    Default: `10s`
+
+```pubsub.retry.maximumBackoff```
+:   The maximum backoff duration for retrying failed message deliveries.
+    Default: `10m`
diff --git a/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/EmulatedPubSub.java b/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/EmulatedPubSub.java
new file mode 100644
index 0000000..7ec900f
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/EmulatedPubSub.java
@@ -0,0 +1,92 @@
+// 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.api.gax.core.CredentialsProvider;
+import com.google.api.gax.core.NoCredentialsProvider;
+import com.google.api.gax.grpc.GrpcTransportChannel;
+import com.google.api.gax.rpc.FixedTransportChannelProvider;
+import com.google.pubsub.v1.TopicName;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import org.junit.Ignore;
+import org.testcontainers.containers.PubSubEmulatorContainer;
+import org.testcontainers.utility.DockerImageName;
+
+@Ignore
+public class EmulatedPubSub extends PubSubTestSystem {
+  public static final String PROJECT_ID = "test";
+  public static final String DEFAULT_TOPIC = "gerrit";
+  public static final String STREAM_EVENTS_TOPIC = "stream-events";
+  public static final TopicName TOPIC_NAME = TopicName.of(PROJECT_ID, DEFAULT_TOPIC);
+  public static final TopicName STREAM_EVENTS_TOPIC_NAME =
+      TopicName.of(PROJECT_ID, STREAM_EVENTS_TOPIC);
+
+  private final PubSubEmulatorContainer container;
+  private final FixedTransportChannelProvider channelProvider;
+  private final String hostPort;
+
+  public EmulatedPubSub() throws Exception {
+    container =
+        new PubSubEmulatorContainer(
+            DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk:550.0.0-emulators"));
+    container.start();
+    hostPort = container.getEmulatorEndpoint();
+    ManagedChannel channel = ManagedChannelBuilder.forTarget(hostPort).usePlaintext().build();
+    channelProvider = FixedTransportChannelProvider.create(GrpcTransportChannel.create(channel));
+    System.out.println(
+        String.format(
+            "PubSub emulator container started and is listening on %s",
+            container.getEmulatorEndpoint()));
+  }
+
+  @Override
+  public void reset() throws Exception {}
+
+  @Override
+  public void cleanup() throws Exception {
+    container.stop();
+    container.close();
+  }
+
+  public String getHostPort() {
+    return hostPort;
+  }
+
+  @Override
+  String getProjectId() {
+    return PROJECT_ID;
+  }
+
+  @Override
+  String getTopicName() {
+    return DEFAULT_TOPIC;
+  }
+
+  @Override
+  String getStreamEventsTopicName() {
+    return STREAM_EVENTS_TOPIC;
+  }
+
+  @Override
+  String getPrivateKeyFilePath() {
+    return null;
+  }
+
+  @Override
+  CredentialsProvider getCredentials() throws Exception {
+    return NoCredentialsProvider.create();
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubForwarderTest.java b/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubForwarderTest.java
new file mode 100644
index 0000000..de2d894
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubForwarderTest.java
@@ -0,0 +1,96 @@
+// 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
+import com.google.pubsub.v1.ReceivedMessage;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class PubSubForwarderTest {
+  private PubSubTestSystem testSystem;
+
+  TestApp app1;
+  TestApp app2;
+
+  @BeforeClass
+  public static void startTestSystem() throws Exception {}
+
+  @Before
+  public void setUp() throws Exception {
+    testSystem = PubSubTestSystem.create();
+    app1 = new TestApp("inst-1", testSystem);
+    app1.start();
+
+    app2 = new TestApp("inst-2", testSystem);
+    app2.start();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    app1.stop();
+    app2.stop();
+    testSystem.cleanup();
+  }
+
+  @AfterClass
+  public static void stopTestSystem() throws Exception {}
+
+  @Test
+  public void indexAccount_publishSubscribe_OK() throws Exception {
+    when(app2.cmdProcessor.handle(any())).thenReturn(true);
+    CompletableFuture<Result> result = app1.forwarder.indexAccount(100, new IndexEvent());
+    assertThat(result.get().result()).isTrue();
+    verify(app2.cmdProcessor, timeout(10000).times(1)).handle(any());
+  }
+
+  @Test
+  public void indexAccount_publishSubscribe_Retries() throws Exception {
+    when(app2.cmdProcessor.handle(any())).thenReturn(false, true);
+    CompletableFuture<Result> result = app1.forwarder.indexAccount(100, new IndexEvent());
+    assertThat(result.get().result()).isTrue();
+    verify(app2.cmdProcessor, timeout(10000).atLeast(2)).handle(any());
+  }
+
+  @Test
+  public void maxReceiveCount_exceeded_movedToDlt() throws Exception {
+    app2.drainDltMessages();
+
+    when(app2.cmdProcessor.handle(any())).thenReturn(false);
+    CompletableFuture<Result> result = app1.forwarder.indexAccount(100, new IndexEvent());
+    assertThat(result.get().result()).isTrue();
+
+    // wait long enough so that max delivery attempts is exhausted and message moved to DLT
+    verify(app2.cmdProcessor, timeout(20000).atLeast(5)).handle(any());
+
+    List<ReceivedMessage> messages = app2.receiveMessagesFromDlt();
+    assertThat(messages).hasSize(1);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubTestSystem.java b/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubTestSystem.java
new file mode 100644
index 0000000..eb56c4f
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/PubSubTestSystem.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.api.gax.core.CredentialsProvider;
+import org.junit.Ignore;
+
+@Ignore
+public abstract class PubSubTestSystem {
+
+  public static PubSubTestSystem create() throws Exception {
+    PubSubTestSystem testSystem;
+    if (System.getenv("TEST_REAL_PUBSUB") == null) {
+      testSystem = new EmulatedPubSub();
+    } else {
+      testSystem = RealPubSub.create();
+    }
+    testSystem.reset();
+    return testSystem;
+  }
+
+  abstract String getProjectId();
+
+  abstract String getTopicName();
+
+  abstract String getStreamEventsTopicName();
+
+  abstract String getPrivateKeyFilePath();
+
+  abstract CredentialsProvider getCredentials() throws Exception;
+
+  abstract void reset() throws Exception;
+
+  abstract void cleanup() throws Exception;
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/RealPubSub.java b/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/RealPubSub.java
new file mode 100644
index 0000000..d4748b5
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/RealPubSub.java
@@ -0,0 +1,101 @@
+// Copyright (C) 2024 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.googlesource.gerrit.plugins.highavailability.gcp;
+
+import com.google.api.client.util.Strings;
+import com.google.api.gax.core.CredentialsProvider;
+import com.google.api.gax.core.FixedCredentialsProvider;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+import com.google.pubsub.v1.TopicName;
+import java.io.FileInputStream;
+import org.junit.Ignore;
+
+@Ignore
+public class RealPubSub extends PubSubTestSystem {
+  private static RealPubSub INSTANCE;
+
+  public static RealPubSub create() {
+    if (INSTANCE == null) {
+      String keyPath = getEnv("GOOGLE_APPLICATION_CREDENTIALS");
+      String project = getEnv("GCP_PROJECT");
+      String topic = System.getenv("PUBSUB_TOPIC");
+      String streamEventsTopic = System.getenv("PUBSUB_STREAM_EVENTS_TOPIC");
+      if (Strings.isNullOrEmpty(topic)) {
+        INSTANCE = new RealPubSub(keyPath, project);
+      } else {
+        INSTANCE = new RealPubSub(keyPath, project, topic, streamEventsTopic);
+      }
+    }
+    return INSTANCE;
+  }
+
+  private static String getEnv(String name) {
+    String value = System.getenv(name);
+    if (Strings.isNullOrEmpty(value)) {
+      throw new IllegalStateException("Environment variable " + name + " is not set");
+    }
+    return value;
+  }
+
+  private final String keyPath;
+  private final TopicName topicName;
+  private final TopicName streamEventsTopicName;
+
+  private RealPubSub(String keyPath, String project) {
+    this(
+        keyPath,
+        project,
+        "gerrit-pubsub-" + System.currentTimeMillis(),
+        "gerrit-stream-events-" + System.currentTimeMillis());
+  }
+
+  private RealPubSub(String keyPath, String project, String topic, String streamEventsTopic) {
+    this.keyPath = keyPath;
+    this.topicName = TopicName.of(project, topic);
+    this.streamEventsTopicName = TopicName.of(project, streamEventsTopic);
+  }
+
+  @Override
+  public void reset() throws Exception {}
+
+  @Override
+  void cleanup() throws Exception {}
+
+  @Override
+  String getProjectId() {
+    return topicName.getProject();
+  }
+
+  @Override
+  String getTopicName() {
+    return topicName.getTopic();
+  }
+
+  @Override
+  String getStreamEventsTopicName() {
+    return streamEventsTopicName.getTopic();
+  }
+
+  @Override
+  String getPrivateKeyFilePath() {
+    return keyPath;
+  }
+
+  @Override
+  CredentialsProvider getCredentials() throws Exception {
+    return FixedCredentialsProvider.create(
+        ServiceAccountCredentials.fromStream(new FileInputStream(keyPath)));
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/TestApp.java b/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/TestApp.java
new file mode 100644
index 0000000..c765375
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/highavailability/gcp/TestApp.java
@@ -0,0 +1,130 @@
+package com.googlesource.gerrit.plugins.highavailability.gcp;
+
+import static org.mockito.Mockito.mock;
+
+import com.ericsson.gerrit.plugins.highavailability.ApiModule;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.commands.CommandProcessor;
+import com.google.cloud.pubsub.v1.SubscriptionAdminClient;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.server.config.GerritInstanceId;
+import com.google.gerrit.server.events.EventGson;
+import com.google.gerrit.server.events.EventGsonProvider;
+import com.google.gson.Gson;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.util.Modules;
+import com.google.pubsub.v1.AcknowledgeRequest;
+import com.google.pubsub.v1.PullRequest;
+import com.google.pubsub.v1.PullResponse;
+import com.google.pubsub.v1.ReceivedMessage;
+import com.googlesource.gerrit.plugins.highavailability.gcp.Configuration.Identity;
+import com.googlesource.gerrit.plugins.highavailability.gcp.Configuration.Limits;
+import com.googlesource.gerrit.plugins.highavailability.gcp.Configuration.Retention;
+import com.googlesource.gerrit.plugins.highavailability.gcp.Configuration.Retry;
+import com.googlesource.gerrit.plugins.highavailability.gcp.Configuration.Topics;
+import java.time.Duration;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Ignore;
+
+@Ignore
+public class TestApp {
+  private final PubSubTestSystem testSystem;
+  private final String instanceId;
+  private PubSubNames names;
+
+  CommandProcessor cmdProcessor = mock(CommandProcessor.class);
+
+  @Inject PubSubForwarder forwarder;
+  @Inject private OnStartStop onStartStop;
+  @Inject private SubscriptionAdminClient subscriptionAdminClient;
+  @Inject private @GcpProjectId String projectId;
+  @Inject private PubSubNamingFactory namingFactory;
+
+  TestApp(String instanceId, PubSubTestSystem testSystem) {
+    this.instanceId = instanceId;
+    this.testSystem = testSystem;
+  }
+
+  public void start() throws Exception {
+    Configuration cfg =
+        new Configuration(
+            new Identity(testSystem.getProjectId(), testSystem.getPrivateKeyFilePath()),
+            new Topics(testSystem.getTopicName(), testSystem.getStreamEventsTopicName()),
+            new Limits(Duration.ofSeconds(30), Duration.ofSeconds(30), 4, 4),
+            new Retention(Duration.ofSeconds(10), Duration.ofMinutes(10), false),
+            new Retry(5, Duration.ofSeconds(1), Duration.ofSeconds(10)));
+
+    Module testSupportModule =
+        new AbstractModule() {
+          @Override
+          protected void configure() {
+            bind(Configuration.class).toInstance(cfg);
+            bind(Gson.class)
+                .annotatedWith(EventGson.class)
+                .toInstance(new EventGsonProvider().get());
+            bind(String.class).annotatedWith(GerritInstanceId.class).toInstance(instanceId);
+            install(new ApiModule());
+            DynamicItem.bind(binder(), CommandProcessor.class).toInstance(cmdProcessor);
+          }
+        };
+
+    Module gcpModule;
+    if (testSystem instanceof EmulatedPubSub) {
+      gcpModule =
+          GcpPubSubForwarderModule.createForEmulator(((EmulatedPubSub) testSystem).getHostPort());
+    } else {
+      gcpModule = GcpPubSubForwarderModule.createForRealPubSub();
+    }
+
+    Module testModule = Modules.combine(testSupportModule, gcpModule);
+    Injector injector = Guice.createInjector(testModule);
+    injector.injectMembers(this);
+
+    names = namingFactory.createFor(testSystem.getTopicName());
+    onStartStop.start();
+  }
+
+  public List<ReceivedMessage> receiveMessagesFromDlt() {
+    PullRequest pullRequest =
+        PullRequest.newBuilder()
+            .setSubscription(names.deadLetterSubscription().toString())
+            .setMaxMessages(10)
+            .build();
+
+    PullResponse pullResponse = subscriptionAdminClient.pullCallable().call(pullRequest);
+    List<ReceivedMessage> messages = pullResponse.getReceivedMessagesList();
+    ack(messages);
+    return messages;
+  }
+
+  private void ack(List<ReceivedMessage> messages) {
+    List<String> ackIds =
+        messages.stream().map(ReceivedMessage::getAckId).collect(Collectors.toList());
+    if (ackIds.isEmpty()) {
+      return;
+    }
+    AcknowledgeRequest acknowledgeRequest =
+        AcknowledgeRequest.newBuilder()
+            .setSubscription(names.deadLetterSubscription().toString())
+            .addAllAckIds(ackIds)
+            .build();
+    subscriptionAdminClient.acknowledge(acknowledgeRequest);
+  }
+
+  public void drainDltMessages() {
+    List<ReceivedMessage> messages;
+    do {
+      messages = receiveMessagesFromDlt();
+      ack(messages);
+    } while (!messages.isEmpty());
+  }
+
+  public void stop() throws Exception {
+    onStartStop.stop();
+    testSystem.cleanup();
+  }
+}