Repository: etcd-io/etcd

Issue:
[Robustness Tests] Broken failpoint - labelRaftDropHeartbeat
### Bug report criteria

- [x] This bug report is not security related, security issues should be disclosed privately via security@etcd.io.
- [x] This is not a support request or question, support requests or questions should be raised in the etcd [discussion forums](https://github.com/etcd-io/etcd/discussions).
- [x] You have read the etcd [bug reporting guidelines](https://github.com/etcd-io/etcd/blob/main/Documentation/contributor-guide/reporting_bugs.md).
- [x] Existing open issues along with etcd [frequently asked questions](https://etcd.io/docs/latest/faq) have been checked and this is not a duplicate.

### What happened?

When executing `make gofail-enable`, `labelRaftDropHeartbeat` doesn't work as expected.  Need to investigate why that is the case.

### What did you expect to happen?

`make gofail-enable` and then `git diff` should show that the failpoint `labelRaftDropHeartbeat` has been altered in a way that the marker comment becomes a failpoint.

### How can we reproduce it (as minimally and precisely as possible)?

```
make gofail-enable
git diff
```
Then search for `labelRaftDropHeartbeat`

### Anything else we need to know?

_No response_

### Etcd version (please run commands below)

N/A

### Etcd configuration (command line flags or environment variables)

N/A

### Etcd debug information (please run commands below, feel free to obfuscate the IP address or FQDN in the output)

N/A

### Relevant log output

```Shell

```

Discussion:
@nwnt: /assign

Solution patch:
diff --git a/.github/workflows/antithesis-test.yml b/.github/workflows/antithesis-test.yml
index 9c64672cf..ef2088b93 100644
--- a/.github/workflows/antithesis-test.yml
+++ b/.github/workflows/antithesis-test.yml
@@ -48,7 +48,7 @@ jobs:
       CFG_NODE_COUNT: ${{ inputs.cfg_node_count || 3 }}
     steps:
       - name: Checkout the code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 
       - name: Login to Antithesis Docker Registry
         uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
diff --git a/.github/workflows/antithesis-verify.yml b/.github/workflows/antithesis-verify.yml
index 7db1fd88f..2a09971b5 100644
--- a/.github/workflows/antithesis-verify.yml
+++ b/.github/workflows/antithesis-verify.yml
@@ -22,7 +22,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
 
       - name: Build etcd-server and etcd-client images
         run: |
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 9ce6bb03e..5e6604303 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -37,10 +37,10 @@ jobs:
         language: ['go']
     steps:
       - name: Checkout repository
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
       # Initializes the CodeQL tools for scanning.
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
+        uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5
         with:
           # If you wish to specify custom queries, you can do so here or in a config file.
           # By default, queries listed here will override any specified in a config file.
@@ -50,6 +50,6 @@ jobs:
       # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
       # If this step fails, then you should remove it and run the build manually (see below)
       - name: Autobuild
-        uses: github/codeql-action/autobuild@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
+        uses: github/codeql-action/autobuild@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5
       - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
+        uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5
diff --git a/.github/workflows/measure-testgrid-flakiness.yaml b/.github/workflows/measure-testgrid-flakiness.yaml
index 8c5a8fd73..84b3a69d4 100644
--- a/.github/workflows/measure-testgrid-flakiness.yaml
+++ b/.github/workflows/measure-testgrid-flakiness.yaml
@@ -12,7 +12,7 @@ jobs:
     name: Measure TestGrid Flakiness
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
       - id: goversion
         run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT"
       - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index 83cd3f889..9dad28de1 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -23,7 +23,7 @@ jobs:
 
     steps:
       - name: "Checkout code"
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
 
@@ -50,6 +50,6 @@ jobs:
 
       # Upload the results to GitHub's code scanning dashboard.
       - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
+        uses: github/codeql-action/upload-sarif@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5
         with:
           sarif_file: results.sarif
diff --git a/.go-version b/.go-version
index 6521720b4..7a429d68a 100644
--- a/.go-version
+++ b/.go-version
@@ -1 +1 @@
-1.24.5
+1.24.6
diff --git a/CHANGELOG/CHANGELOG-3.4.md b/CHANGELOG/CHANGELOG-3.4.md
index 54ceb9d91..83bfc849b 100644
--- a/CHANGELOG/CHANGELOG-3.4.md
+++ b/CHANGELOG/CHANGELOG-3.4.md
@@ -14,7 +14,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 
 ### Dependencies
 
-- Compile binaries using [go 1.23.11](https://github.com/etcd-io/etcd/pull/20322).
+- Compile binaries using [go 1.23.12](https://github.com/etcd-io/etcd/pull/20463).
 
 ---
 
diff --git a/CHANGELOG/CHANGELOG-3.5.md b/CHANGELOG/CHANGELOG-3.5.md
index 9588f3868..df1eb351a 100644
--- a/CHANGELOG/CHANGELOG-3.5.md
+++ b/CHANGELOG/CHANGELOG-3.5.md
@@ -6,6 +6,10 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 
 ## v3.5.23 (TBA)
 
+### Dependencies
+
+- Compile binaries using [go 1.23.12](https://github.com/etcd-io/etcd/pull/20462)
+
 ---
 
 ## v3.5.22 (2025-07-22)
diff --git a/CHANGELOG/CHANGELOG-3.6.md b/CHANGELOG/CHANGELOG-3.6.md
index 1745ad84c..af90e7bfc 100644
--- a/CHANGELOG/CHANGELOG-3.6.md
+++ b/CHANGELOG/CHANGELOG-3.6.md
@@ -9,6 +9,11 @@ Previous change logs can be found at [CHANGELOG-3.5](https://github.com/etcd-io/
 ### etcd server
 
 - [Remove the flag `--experimental-snapshot-catch-up-entries` from `etcd --help` output](https://github.com/etcd-io/etcd/pull/20422)
+- Fix [etcd repeatedly log the error "cannot detect storage schema version: missing confstate information"](https://github.com/etcd-io/etcd/pull/20496)
+
+### Dependencies
+
+- Compile binaries using [go 1.23.12](https://github.com/etcd-io/etcd/pull/20461).
 
 ---
 
diff --git a/CHANGELOG/CHANGELOG-3.7.md b/CHANGELOG/CHANGELOG-3.7.md
index 4bcb77399..b75ae5b2e 100644
--- a/CHANGELOG/CHANGELOG-3.7.md
+++ b/CHANGELOG/CHANGELOG-3.7.md
@@ -15,6 +15,7 @@ Previous change logs can be found at [CHANGELOG-3.6](https://github.com/etcd-io/
 
 - [Update go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc to v0.61.0 and replaced the deprecated `UnaryServerInterceptor` and `StreamServerInterceptor` with `NewServerHandler`](https://github.com/etcd-io/etcd/pull/20017)
 - [Add Support for Unix Socket endpoints](https://github.com/etcd-io/etcd/pull/19760)
+- [Improves performance of lease and user/role operations (up to 2x) by updating `(*readView) Rev()` to use `SharedBufReadTxMode`](https://github.com/etcd-io/etcd/pull/20411)
 
 ### Package `pkg`
 
@@ -23,9 +24,13 @@ Previous change logs can be found at [CHANGELOG-3.6](https://github.com/etcd-io/
 
 ### Dependencies
 
-- Compile binaries using [go 1.24.5](https://github.com/etcd-io/etcd/pull/20320)
+- Compile binaries using [go 1.24.6](https://github.com/etcd-io/etcd/pull/20460)
 - [Migrate the deprecated go-grpc-middleware v1 logging and tags libraries to v2 interceptors](https://github.com/etcd-io/etcd/pull/20420)
 
 ### Deprecations
 
 - Deprecated [UsageFunc in pkg/cobrautl](https://github.com/etcd-io/etcd/pull/18356).
+
+### etcdctl
+
+- [Organize etcdctl commands](https://github.com/etcd-io/etcd/pull/20162) to make them more concise and easier to understand.
diff --git a/Makefile b/Makefile
index 4b44b672e..a81b65f7a 100644
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,20 @@ include $(REPOSITORY_ROOT)/tests/robustness/Makefile
 build:
 	GO_BUILD_FLAGS="${GO_BUILD_FLAGS} -v -mod=readonly" ./scripts/build.sh
 
+.PHONY: install-benchmark
+install-benchmark: build
+ifeq (, $(shell which benchmark))
+	@echo "Installing etcd benchmark tool..."
+	go install -v ./tools/benchmark
+else
+	@echo "benchmark tool already installed..."
+endif
+
+.PHONY: bench-put
+bench-put: build install-benchmark
+	@echo "Running benchmark: put $(ARGS)"
+	./scripts/benchmark_test.sh put $(ARGS)
+
 PLATFORMS=linux-amd64 linux-386 linux-arm linux-arm64 linux-ppc64le linux-s390x darwin-amd64 darwin-arm64 windows-amd64 windows-arm64
 
 .PHONY: build-all
diff --git a/api/go.mod b/api/go.mod
index e208debc9..c3a0e59e2 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -2,7 +2,7 @@ module go.etcd.io/etcd/api/v3
 
 go 1.24
 
-toolchain go1.24.5
+toolchain go1.24.6
 
 require (
 	github.com/coreos/go-semver v0.3.1
@@ -10,9 +10,9 @@ require (
 	github.com/golang/protobuf v1.5.4
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1
 	github.com/stretchr/testify v1.10.0
-	google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822
+	google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0
 	google.golang.org/grpc v1.74.2
-	google.golang.org/protobuf v1.36.6
+	google.golang.org/protobuf v1.36.7
 )
 
 require (
@@ -21,10 +21,10 @@ require (
 	github.com/rogpeppe/go-internal v1.14.1 // indirect
 	go.opentelemetry.io/otel v1.37.0 // indirect
 	go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
-	golang.org/x/net v0.42.0 // indirect
-	golang.org/x/sys v0.34.0 // indirect
-	golang.org/x/text v0.27.0 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
+	golang.org/x/net v0.43.0 // indirect
+	golang.org/x/sys v0.35.0 // indirect
+	golang.org/x/text v0.28.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
diff --git a/api/go.sum b/api/go.sum
index 8f36ccae9..8dfd91805 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -51,20 +51,20 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
-golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -73,14 +73,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
 google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
+google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/cache/cache.go b/cache/cache.go
index 8b9abd44a..75d7acc69 100644
--- a/cache/cache.go
+++ b/cache/cache.go
@@ -27,7 +27,6 @@ import (
 )
 
 var (
-	// TODO: add gap-free replay for arbitrary startRevs and drop this guard.
 	// Returned when an option combination isn’t yet handled by the cache (e.g. WithPrevKV, WithProgressNotify for Watch(), WithCountOnly for Get()).
 	ErrUnsupportedRequest = errors.New("cache: unsupported request parameters")
 	// Returned when the requested key or key‑range is invalid (empty or reversed) or lies outside c.prefix.
@@ -149,8 +148,10 @@ func (c *Cache) Watch(ctx context.Context, key string, opts ...clientv3.OpOption
 }
 
 func (c *Cache) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) {
-	if err := c.WaitReady(ctx); err != nil {
-		return nil, err
+	if c.store.LatestRev() == 0 {
+		if err := c.WaitReady(ctx); err != nil {
+			return nil, err
+		}
 	}
 	op := clientv3.OpGet(key, opts...)
 
@@ -253,14 +254,12 @@ func (c *Cache) watch(ctx context.Context, rev int64) error {
 			if err := resp.Err(); err != nil {
 				c.ready.Reset()
 				c.demux.Purge()
-				c.store.Reset()
 				return err
 			}
 
 			if err := c.store.Apply(resp.Events); err != nil {
 				c.ready.Reset()
 				c.demux.Purge()
-				c.store.Reset()
 				return err
 			}
 			c.demux.Broadcast(resp.Events)
diff --git a/cache/demux.go b/cache/demux.go
index adbc495b6..a8aebe359 100644
--- a/cache/demux.go
+++ b/cache/demux.go
@@ -27,7 +27,7 @@ type demux struct {
 	// activeWatchers & laggingWatchers hold the first revision the watcher still needs (nextRev).
 	activeWatchers  map[*watcher]int64
 	laggingWatchers map[*watcher]int64
-	history         *ringBuffer
+	history         ringBuffer[*clientv3.Event]
 	resyncInterval  time.Duration
 }
 
@@ -45,7 +45,7 @@ func newDemux(historyWindowSize int, resyncInterval time.Duration) *demux {
 	return &demux{
 		activeWatchers:  make(map[*watcher]int64),
 		laggingWatchers: make(map[*watcher]int64),
-		history:         newRingBuffer(historyWindowSize),
+		history:         *newRingBuffer(historyWindowSize, func(ev *clientv3.Event) int64 { return ev.Kv.ModRevision }),
 		resyncInterval:  resyncInterval,
 	}
 }
@@ -71,13 +71,17 @@ func (d *demux) Register(w *watcher, startingRev int64) {
 	defer d.mu.Unlock()
 
 	latestRev := d.history.PeekLatest()
+	if latestRev == 0 {
+		if startingRev == 0 {
+			d.activeWatchers[w] = 0
+		} else {
+			d.laggingWatchers[w] = startingRev
+		}
+		return
+	}
 
 	// Special case: 0 means “newest”.
 	if startingRev == 0 {
-		if latestRev == 0 {
-			d.activeWatchers[w] = 0
-			return
-		}
 		startingRev = latestRev + 1
 	}
 
@@ -107,9 +111,14 @@ func (d *demux) Broadcast(events []*clientv3.Event) {
 	defer d.mu.Unlock()
 
 	d.history.Append(events)
-
+	firstRev := events[0].Kv.ModRevision
 	lastRev := events[len(events)-1].Kv.ModRevision
 	for w, nextRev := range d.activeWatchers {
+		if nextRev != 0 && firstRev > nextRev {
+			d.laggingWatchers[w] = nextRev
+			delete(d.activeWatchers, w)
+			continue
+		}
 		start := len(events)
 		for i, ev := range events {
 			if ev.Kv.ModRevision >= nextRev {
@@ -151,23 +160,26 @@ func (d *demux) resyncLaggingWatchers() {
 	defer d.mu.Unlock()
 
 	oldestRev := d.history.PeekOldest()
+	if oldestRev == 0 {
+		return
+	}
+
 	for w, nextRev := range d.laggingWatchers {
-		if oldestRev != 0 && nextRev < oldestRev {
+		if nextRev < oldestRev {
 			w.Stop()
 			delete(d.laggingWatchers, w)
 			continue
 		}
 		// TODO: re-enable key‐predicate in Filter when non‐zero startRev or performance tuning is needed
-		missed := d.history.Filter(nextRev)
-
 		enqueueFailed := false
-		for _, eventBatch := range missed {
+		d.history.AscendGreaterOrEqual(nextRev, func(rev int64, eventBatch []*clientv3.Event) bool {
 			if !w.enqueueEvent(eventBatch) { // buffer overflow: watcher still lagging
 				enqueueFailed = true
-				break
+				return false
 			}
-			nextRev = eventBatch[0].Kv.ModRevision + 1
-		}
+			nextRev = rev + 1
+			return true
+		})
 
 		if !enqueueFailed {
 			delete(d.laggingWatchers, w)
diff --git a/cache/go.mod b/cache/go.mod
index 28b9af25c..2c63bd726 100644
--- a/cache/go.mod
+++ b/cache/go.mod
@@ -2,7 +2,7 @@ module go.etcd.io/etcd/cache/v3
 
 go 1.24
 
-toolchain go1.24.5
+toolchain go1.24.6
 
 require (
 	github.com/google/go-cmp v0.7.0
@@ -19,13 +19,13 @@ require (
 	go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/zap v1.27.0 // indirect
-	golang.org/x/net v0.42.0 // indirect
-	golang.org/x/sys v0.34.0 // indirect
-	golang.org/x/text v0.27.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
+	golang.org/x/net v0.43.0 // indirect
+	golang.org/x/sys v0.35.0 // indirect
+	golang.org/x/text v0.28.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
 	google.golang.org/grpc v1.74.2 // indirect
-	google.golang.org/protobuf v1.36.6 // indirect
+	google.golang.org/protobuf v1.36.7 // indirect
 )
 
 replace (
diff --git a/cache/go.sum b/cache/go.sum
index 817d24bb3..53b6764c2 100644
--- a/cache/go.sum
+++ b/cache/go.sum
@@ -33,14 +33,14 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
-github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
+github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
 github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
 github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
-github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
-github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -72,20 +72,20 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
-golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -94,14 +94,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
 google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
+google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/cache/ready.go b/cache/ready.go
index 96a1c6883..820d7fff3 100644
--- a/cache/ready.go
+++ b/cache/ready.go
@@ -19,16 +19,15 @@ import (
 	"sync"
 )
 
-// ready tracks the transition from “not ready” → “ready” and allows waiters to block
-// until the first transition has happened, and to be reset on error.
+// ready tracks readiness state changes and allows callers to wait for a target state
 type ready struct {
-	mu        sync.Mutex
-	isReady   bool
-	readyChan chan struct{} // closed exactly once per ready transition
+	mu      sync.Mutex
+	isReady bool
+	stateCh chan struct{} // closed on any state transition, then replaced immediately
 }
 
 func newReady() *ready {
-	return &ready{readyChan: make(chan struct{})}
+	return &ready{stateCh: make(chan struct{})}
 }
 
 func (r *ready) Ready() bool {
@@ -38,20 +37,11 @@ func (r *ready) Ready() bool {
 }
 
 func (r *ready) WaitReady(ctx context.Context) error {
-	r.mu.Lock()
-	ch := r.readyChan
-	ready := r.isReady
-	r.mu.Unlock()
+	return r.waitForState(ctx, func() bool { return r.isReady })
+}
 
-	if ready {
-		return nil
-	}
-	select {
-	case <-ch:
-		return ctx.Err()
-	case <-ctx.Done():
-		return ctx.Err()
-	}
+func (r *ready) WaitNotReady(ctx context.Context) error {
+	return r.waitForState(ctx, func() bool { return !r.isReady })
 }
 
 func (r *ready) Set() {
@@ -59,7 +49,8 @@ func (r *ready) Set() {
 	defer r.mu.Unlock()
 	if !r.isReady {
 		r.isReady = true
-		close(r.readyChan)
+		close(r.stateCh)
+		r.stateCh = make(chan struct{})
 	}
 }
 
@@ -68,6 +59,25 @@ func (r *ready) Reset() {
 	defer r.mu.Unlock()
 	if r.isReady {
 		r.isReady = false
-		r.readyChan = make(chan struct{})
+		close(r.stateCh)
+		r.stateCh = make(chan struct{})
+	}
+}
+
+func (r *ready) waitForState(ctx context.Context, pred func() bool) error {
+	for {
+		r.mu.Lock()
+		if pred() {
+			r.mu.Unlock()
+			return ctx.Err()
+		}
+		stateChCopy := r.stateCh
+		r.mu.Unlock()
+
+		select {
+		case <-stateChCopy:
+		case <-ctx.Done():
+			return ctx.Err()
+		}
 	}
 }
diff --git a/cache/ringbuffer.go b/cache/ringbuffer.go
index b9d1681cb..258758bed 100644
--- a/cache/ringbuffer.go
+++ b/cache/ringbuffer.go
@@ -14,54 +14,55 @@
 
 package cache
 
-import (
-	"fmt"
+import "fmt"
 
-	clientv3 "go.etcd.io/etcd/client/v3"
-)
-
-type ringBuffer struct {
-	buffer []batch
+type ringBuffer[T any] struct {
+	buffer []entry[T]
 	// head is the index immediately after the last non-empty entry in the buffer (i.e., the next write position).
 	head, tail, size int
+	revisionOf       RevisionOf[T]
 }
 
-// batch groups all events that share one ModRevision.
-type batch struct {
-	rev    int64
-	events []*clientv3.Event
+type entry[T any] struct {
+	revision int64
+	items    []T
 }
 
-type KeyPredicate = func([]byte) bool
+type (
+	KeyPredicate      = func([]byte) bool
+	RevisionOf[T any] func(T) int64
+	IterFunc[T any]   func(rev int64, items []T) bool
+)
 
-func newRingBuffer(capacity int) *ringBuffer {
+func newRingBuffer[T any](capacity int, revisionOf RevisionOf[T]) *ringBuffer[T] {
 	// assume capacity > 0 – validated by Cache
-	return &ringBuffer{
-		buffer: make([]batch, capacity),
+	return &ringBuffer[T]{
+		buffer:     make([]entry[T], capacity),
+		revisionOf: revisionOf,
 	}
 }
 
-func (r *ringBuffer) Append(events []*clientv3.Event) {
+func (r *ringBuffer[T]) Append(items []T) {
 	start := 0
-	for end := 1; end < len(events); end++ {
-		if events[end].Kv.ModRevision != events[start].Kv.ModRevision {
-			r.append(batch{
-				rev:    events[start].Kv.ModRevision,
-				events: events[start:end],
+	for end := 1; end < len(items); end++ {
+		if r.revisionOf(items[end]) != r.revisionOf(items[start]) {
+			r.append(entry[T]{
+				revision: r.revisionOf(items[start]),
+				items:    items[start:end],
 			})
 			start = end
 		}
 	}
-	if start < len(events) {
-		r.append(batch{
-			rev:    events[start].Kv.ModRevision,
-			events: events[start:],
+	if start < len(items) {
+		r.append(entry[T]{
+			revision: r.revisionOf(items[start]),
+			items:    items[start:],
 		})
 	}
 }
 
-func (r *ringBuffer) append(b batch) {
-	if len(b.events) == 0 {
+func (r *ringBuffer[T]) append(entry entry[T]) {
+	if len(entry.items) == 0 {
 		return
 	}
 	if r.size == len(r.buffer) {
@@ -69,47 +70,127 @@ func (r *ringBuffer) append(b batch) {
 	} else {
 		r.size++
 	}
-	r.buffer[r.head] = b
+	r.buffer[r.head] = entry
 	r.head = (r.head + 1) % len(r.buffer)
 }
 
-// Filter returns all events in the buffer whose ModRevision is >= minRev.
+// AscendGreaterOrEqual iterates through entries in ascending order starting from the first entry with revision >= pivot.
 // TODO: use binary search on the ring buffer to locate the first entry >= nextRev instead of a full scan
-func (r *ringBuffer) Filter(minRev int64) (eventBatches [][]*clientv3.Event) {
-	eventBatches = make([][]*clientv3.Event, 0, r.size)
+func (r *ringBuffer[T]) AscendGreaterOrEqual(pivot int64, iter IterFunc[T]) {
+	if r.size == 0 {
+		return
+	}
+
+	for n, i := 0, r.tail; n < r.size; n, i = n+1, (i+1)%len(r.buffer) {
+		entry := r.buffer[i]
+
+		if entry.items == nil {
+			panic(fmt.Sprintf("ringBuffer.AscendGreaterOrEqual: unexpected nil at %d", i))
+		}
+
+		if entry.revision < pivot {
+			continue
+		}
+
+		if !iter(entry.revision, entry.items) {
+			return
+		}
+	}
+}
+
+// AscendLessThan iterates in ascending order over entries with revision < pivot.
+func (r *ringBuffer[T]) AscendLessThan(pivot int64, iter IterFunc[T]) {
+	if r.size == 0 {
+		return
+	}
 
 	for n, i := 0, r.tail; n < r.size; n, i = n+1, (i+1)%len(r.buffer) {
-		eventBatch := r.buffer[i]
-		if eventBatch.events == nil {
-			panic(fmt.Sprintf("ringBuffer.Filter: unexpected nil eventBatch at index %d", i))
+		entry := r.buffer[i]
+
+		if entry.items == nil {
+			panic(fmt.Sprintf("ringBuffer.AscendLessThan: unexpected nil at %d", i))
+		}
+
+		if entry.revision >= pivot {
+			return
+		}
+
+		if !iter(entry.revision, entry.items) {
+			return
+		}
+	}
+}
+
+// DescendGreaterThan iterates in descending order over entries with revision > pivot.
+func (r *ringBuffer[T]) DescendGreaterThan(pivot int64, iter IterFunc[T]) {
+	if r.size == 0 {
+		return
+	}
+
+	for n, i := 0, r.moduloIndex(r.head-1); n < r.size; n, i = n+1, r.moduloIndex(i-1) {
+		entry := r.buffer[i]
+
+		if entry.items == nil {
+			panic(fmt.Sprintf("ringBuffer.DescendGreaterThan: unexpected nil at %d", i))
+		}
+
+		if entry.revision <= pivot {
+			return
+		}
+
+		if !iter(entry.revision, entry.items) {
+			return
+		}
+	}
+}
+
+// DescendLessOrEqual iterates in descending order over entries with revision <= pivot.
+func (r *ringBuffer[T]) DescendLessOrEqual(pivot int64, iter IterFunc[T]) {
+	if r.size == 0 {
+		return
+	}
+
+	for n, i := 0, r.moduloIndex(r.head-1); n < r.size; n, i = n+1, r.moduloIndex(i-1) {
+		entry := r.buffer[i]
+
+		if entry.items == nil {
+			panic(fmt.Sprintf("ringBuffer.DescendLessOrEqual: unexpected nil at %d", i))
+		}
+
+		if entry.revision > pivot {
+			continue
 		}
-		if eventBatch.rev >= minRev {
-			eventBatches = append(eventBatches, eventBatch.events)
+
+		if !iter(entry.revision, entry.items) {
+			return
 		}
 	}
-	return eventBatches
 }
 
-// PeekLatest returns the most recently-appended event (or nil if empty).
-func (r *ringBuffer) PeekLatest() int64 {
+// PeekLatest returns the most recently-appended revision (or 0 if empty).
+func (r *ringBuffer[T]) PeekLatest() int64 {
 	if r.size == 0 {
 		return 0
 	}
 	idx := (r.head - 1 + len(r.buffer)) % len(r.buffer)
-	return r.buffer[idx].rev
+	return r.buffer[idx].revision
 }
 
-// PeekOldest returns the oldest event currently stored (or nil if empty).
-func (r *ringBuffer) PeekOldest() int64 {
+// PeekOldest returns the oldest revision currently stored (or 0 if empty).
+func (r *ringBuffer[T]) PeekOldest() int64 {
 	if r.size == 0 {
 		return 0
 	}
-	return r.buffer[r.tail].rev
+	return r.buffer[r.tail].revision
 }
 
-func (r *ringBuffer) RebaseHistory() {
+func (r *ringBuffer[T]) RebaseHistory() {
 	r.head, r.tail, r.size = 0, 0, 0
 	for i := range r.buffer {
-		r.buffer[i] = batch{}
+		r.buffer[i] = entry[T]{}
 	}
 }
+
+func (r *ringBuffer[T]) moduloIndex(index int) int {
+	return (index + len(r.buffer)) % len(r.buffer)
+}
diff --git a/cache/store.go b/cache/store.go
index 1bea4a1f1..e45706a2d 100644
--- a/cache/store.go
+++ b/cache/store.go
@@ -71,14 +71,6 @@ func (s *store) Restore(kvs []*mvccpb.KeyValue, rev int64) {
 	s.latestRev = rev
 }
 
-// Reset purges all in-memory state when the upstream watch stream reports compaction.
-func (s *store) Reset() {
-	s.mu.Lock()
-	defer s.mu.Unlock()
-	s.kvs = make(map[string]*mvccpb.KeyValue)
-	s.latestRev = 0
-}
-
 func (s *store) Apply(events []*clientv3.Event) error {
 	s.mu.Lock()
 	defer s.mu.Unlock()
diff --git a/client/pkg/go.mod b/client/pkg/go.mod
index 144c44c9c..701629a6a 100644
--- a/client/pkg/go.mod
+++ b/client/pkg/go.mod
@@ -2,13 +2,13 @@ module go.etcd.io/etcd/client/pkg/v3
 
 go 1.24
 
-toolchain go1.24.5
+toolchain go1.24.6
 
 require (
 	github.com/coreos/go-systemd/v22 v22.5.0
 	github.com/stretchr/testify v1.10.0
 	go.uber.org/zap v1.27.0
-	golang.org/x/sys v0.34.0
+	golang.org/x/sys v0.35.0
 )
 
 require (
diff --git a/client/pkg/go.sum b/client/pkg/go.sum
index 462fa64c9..f0b353686 100644
--- a/client/pkg/go.sum
+++ b/client/pkg/go.sum
@@ -25,8 +25,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
 go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/client/v3/go.mod b/client/v3/go.mod
index a3b82ba89..f3b2e4fc0 100644
--- a/client/v3/go.mod
+++ b/client/v3/go.mod
@@ -2,19 +2,19 @@ module go.etcd.io/etcd/client/v3
 
 go 1.24
 
-toolchain go1.24.5
+toolchain go1.24.6
 
 require (
 	github.com/coreos/go-semver v0.3.1
 	github.com/dustin/go-humanize v1.0.1
 	github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0
-	github.com/prometheus/client_golang v1.22.0
+	github.com/prometheus/client_golang v1.23.0
 	github.com/stretchr/testify v1.10.0
 	go.etcd.io/etcd/api/v3 v3.6.0-alpha.0
 	go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0
 	go.uber.org/zap v1.27.0
 	google.golang.org/grpc v1.74.2
-	sigs.k8s.io/yaml v1.5.0
+	sigs.k8s.io/yaml v1.6.0
 )
 
 require (
@@ -30,18 +30,18 @@ require (
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/prometheus/client_model v0.6.2 // indirect
 	github.com/prometheus/common v0.65.0 // indirect
-	github.com/prometheus/procfs v0.15.1 // indirect
+	github.com/prometheus/procfs v0.16.1 // indirect
 	go.opentelemetry.io/otel/metric v1.37.0 // indirect
 	go.opentelemetry.io/otel/sdk v1.37.0 // indirect
 	go.opentelemetry.io/otel/trace v1.37.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.yaml.in/yaml/v2 v2.4.2 // indirect
-	golang.org/x/net v0.42.0 // indirect
-	golang.org/x/sys v0.34.0 // indirect
-	golang.org/x/text v0.27.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
-	google.golang.org/protobuf v1.36.6 // indirect
+	golang.org/x/net v0.43.0 // indirect
+	golang.org/x/sys v0.35.0 // indirect
+	golang.org/x/text v0.28.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
+	google.golang.org/protobuf v1.36.7 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
diff --git a/client/v3/go.sum b/client/v3/go.sum
index a7805d1a4..c5b0ac6d5 100644
--- a/client/v3/go.sum
+++ b/client/v3/go.sum
@@ -43,14 +43,14 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
-github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
+github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
 github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
 github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
-github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
-github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
 github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
@@ -88,20 +88,20 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
-golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -110,18 +110,18 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
 google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
+google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
-sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
+sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
+sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
diff --git a/etcdctl/ctlv3/command/alarm_command.go b/etcdctl/ctlv3/command/alarm_command.go
index 679f9d98f..9bf6ffbda 100644
--- a/etcdctl/ctlv3/command/alarm_command.go
+++ b/etcdctl/ctlv3/command/alarm_command.go
@@ -26,8 +26,10 @@ import (
 // NewAlarmCommand returns the cobra command for "alarm".
 func NewAlarmCommand() *cobra.Command {
 	ac := &cobra.Command{
-		Use:   "alarm <subcommand>",
-		Short: "Alarm related commands",
+		Use:     "alarm <subcommand>",
+		Short:   "Alarm related commands. Use `etcdctl alarm --help` to see subcommands",
+		Long:    "Alarm related commands",
+		GroupID: groupClusterMaintenanceID,
 	}
 
 	ac.AddCommand(NewAlarmDisarmCommand())
diff --git a/etcdctl/ctlv3/command/auth_command.go b/etcdctl/ctlv3/command/auth_command.go
index 13ccda44d..6a414f7aa 100644
--- a/etcdctl/ctlv3/command/auth_command.go
+++ b/etcdctl/ctlv3/command/auth_command.go
@@ -27,8 +27,10 @@ import (
 // NewAuthCommand returns the cobra command for "auth".
 func NewAuthCommand() *cobra.Command {
 	ac := &cobra.Command{
-		Use:   "auth <enable or disable>",
-		Short: "Enable or disable authentication",
+		Use:     "auth <enable or disable>",
+		Short:   "Enable or disable authentication. Use `etcdctl auth --help` to see subcommands",
+		Long:    "Enable or disable authentication",
+		GroupID: groupAuthenticationID,
 	}
 
 	ac.AddCommand(newAuthEnableCommand())
diff --git a/etcdctl/ctlv3/command/check.go b/etcdctl/ctlv3/command/check.go
index c88015400..904120982 100644
--- a/etcdctl/ctlv3/command/check.go
+++ b/etcdctl/ctlv3/command/check.go
@@ -107,8 +107,10 @@ var checkDatascaleCfgMap = map[string]checkDatascaleCfg{
 // NewCheckCommand returns the cobra command for "check".
 func NewCheckCommand() *cobra.Command {
 	cc := &cobra.Command{
-		Use:   "check <subcommand>",
-		Short: "commands for checking properties of the etcd cluster",
+		Use:     "check <subcommand>",
+		Short:   "commands for checking properties of the etcd cluster. Use `etcdctl check --help` to see subcommands",
+		Long:    "commands for checking properties of the etcd cluster",
+		GroupID: groupUtilityID,
 	}
 
 	cc.AddCommand(NewCheckPerfCommand())
diff --git a/etcdctl/ctlv3/command/compaction_command.go b/etcdctl/ctlv3/command/compaction_command.go
index 5c0bb1019..457538a59 100644
--- a/etcdctl/ctlv3/command/compaction_command.go
+++ b/etcdctl/ctlv3/command/compaction_command.go
@@ -29,9 +29,10 @@ var compactPhysical bool
 // NewCompactionCommand returns the cobra command for "compaction".
 func NewCompactionCommand() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "compaction [options] <revision>",
-		Short: "Compacts the event history in etcd",
-		Run:   compactionCommandFunc,
+		Use:     "compaction [options] <revision>",
+		Short:   "Compacts the event history in etcd",
+		Run:     compactionCommandFunc,
+		GroupID: groupKVID,
 	}
 	cmd.Flags().BoolVar(&compactPhysical, "physical", false, "'true' to wait for compaction to physically remove all old revisions")
 	return cmd
diff --git a/etcdctl/ctlv3/command/completion_command.go b/etcdctl/ctlv3/command/completion_command.go
index cb98d4a7d..45f284426 100644
--- a/etcdctl/ctlv3/command/completion_command.go
+++ b/etcdctl/ctlv3/command/completion_command.go
@@ -78,6 +78,7 @@ PowerShell:
 				cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
 			}
 		},
+		GroupID: groupUtilityID,
 	}
 
 	return cmd
diff --git a/etcdctl/ctlv3/command/defrag_command.go b/etcdctl/ctlv3/command/defrag_command.go
index 36116d488..c68de9cd1 100644
--- a/etcdctl/ctlv3/command/defrag_command.go
+++ b/etcdctl/ctlv3/command/defrag_command.go
@@ -27,9 +27,10 @@ import (
 // NewDefragCommand returns the cobra command for "Defrag".
 func NewDefragCommand() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "defrag",
-		Short: "Defragments the storage of the etcd members with given endpoints",
-		Run:   defragCommandFunc,
+		Use:     "defrag",
+		Short:   "Defragments the storage of the etcd members with given endpoints",
+		Run:     defragCommandFunc,
+		GroupID: groupClusterMaintenanceID,
 	}
 	cmd.PersistentFlags().BoolVar(&epClusterEndpoints, "cluster", false, "use all endpoints from the cluster member list")
 	return cmd
diff --git a/etcdctl/ctlv3/command/del_command.go b/etcdctl/ctlv3/command/del_command.go
index 51b7abb3e..5fa39f807 100644
--- a/etcdctl/ctlv3/command/del_command.go
+++ b/etcdctl/ctlv3/command/del_command.go
@@ -35,9 +35,10 @@ var (
 // NewDelCommand returns the cobra command for "del".
 func NewDelCommand() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "del [options] <key> [range_end]",
-		Short: "Removes the specified key or range of keys [key, range_end)",
-		Run:   delCommandFunc,
+		Use:     "del [options] <key> [range_end]",
+		Short:   "Removes the specified key or range of keys [key, range_end)",
+		Run:     delCommandFunc,
+		GroupID: groupKVID,
 	}
 
 	cmd.Flags().BoolVar(&delPrefix, "prefix", false, "delete keys with matching prefix")
diff --git a/etcdctl/ctlv3/command/downgrade_command.go b/etcdctl/ctlv3/command/downgrade_command.go
index 8b6ab9cd1..c56b7749c 100644
--- a/etcdctl/ctlv3/command/downgrade_command.go
+++ b/etcdctl/ctlv3/command/downgrade_command.go
@@ -26,8 +26,10 @@ import (
 // NewDowngradeCommand returns the cobra command for "downgrade".
 func NewDowngradeCommand() *cobra.Command {
 	dc := &cobra.Command{
-		Use:   "downgrade <TARGET_VERSION>",
-		Short: "Downgrade related commands",
+		Use:     "downgrade <TARGET_VERSION>",
+		Short:   "Downgrade related commands. Use `etcdctl downgrade --help` to see subcommands",
+		Long:    "Downgrade related commands",
+		GroupID: groupClusterMaintenanceID,
 	}
 
 	dc.AddCommand(NewDowngradeValidateCommand())
diff --git a/etcdctl/ctlv3/command/elect_command.go b/etcdctl/ctlv3/command/elect_command.go
index eee4289e7..6ff828843 100644
--- a/etcdctl/ctlv3/command/elect_command.go
+++ b/etcdctl/ctlv3/command/elect_command.go
@@ -33,9 +33,10 @@ var electListen bool
 // NewElectCommand returns the cobra command for "elect".
 func NewElectCommand() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "elect <election-name> [proposal]",
-		Short: "Observes and participates in leader election",
-		Run:   electCommandFunc,
+		Use:     "elect <election-name> [proposal]",
+		Short:   "Observes and participates in leader election",
+		Run:     electCommandFunc,
+		GroupID: groupConcurrencyID,
 	}
 	cmd.Flags().BoolVarP(&electListen, "listen", "l", false, "observation mode")
 	return cmd
diff --git a/etcdctl/ctlv3/command/ep_command.go b/etcdctl/ctlv3/command/ep_command.go
index d82194d32..e074a6218 100644
--- a/etcdctl/ctlv3/command/ep_command.go
+++ b/etcdctl/ctlv3/command/ep_command.go
@@ -39,8 +39,10 @@ var (
 // NewEndpointCommand returns the cobra command for "endpoint".
 func NewEndpointCommand() *cobra.Command {
 	ec := &cobra.Command{
-		Use:   "endpoint <subcommand>",
-		Short: "Endpoint related commands",
+		Use:     "endpoint <subcommand>",
+		Short:   "Endpoint related commands. Use `etcdctl endpoint --help` to see subcommands",
+		Long:    "Endpoint related commands",
+		GroupID: groupClusterMaintenanceID,
 	}
 
 	ec.PersistentFlags().BoolVar(&epClusterEndpoints, "cluster", false, "use all endpoints from the cluster member list")
diff --git a/etcdctl/ctlv3/command/get_command.go b/etcdctl/ctlv3/command/get_command.go
index 7d687aa5c..94d34dac0 100644
--- a/etcdctl/ctlv3/command/get_command.go
+++ b/etcdctl/ctlv3/command/get_command.go
@@ -44,9 +44,10 @@ var (
 // NewGetCommand returns the cobra command for "get".
 func NewGetCommand() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "get [options] <key> [range_end]",
-		Short: "Gets the key or a range of keys",
-		Run:   getCommandFunc,
+		Use:     "get [options] <key> [range_end]",
+		Short:   "Gets the key or a range of keys",
+		Run:     getCommandFunc,
+		GroupID: groupKVID,
 	}
 
 	cmd.Flags().StringVar(&getConsistency, "consistency", "l", "Linearizable(l) or Serializable(s)")
diff --git a/etcdctl/ctlv3/command/groups.go b/etcdctl/ctlv3/command/groups.go
new file mode 100644
index 000000000..ca0229d3c
--- /dev/null
+++ b/etcdctl/ctlv3/command/groups.go
@@ -0,0 +1,60 @@
+// Copyright 2025 The etcd Authors
+//
+// 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 command
+
+import "github.com/spf13/cobra"
+
+const (
+	groupKVID                 = "kv"
+	groupClusterMaintenanceID = "cluster maintenance"
+	groupConcurrencyID        = "concurrency"
+	groupAuthenticationID     = "authentication"
+	groupUtilityID            = "utility"
+)
+
+func NewKVGroup() *cobra.Group {
+	return &cobra.Group{
+		ID:    groupKVID,
+		Title: "Key-value commands",
+	}
+}
+
+func NewClusterMaintenanceGroup() *cobra.Group {
+	return &cobra.Group{
+		ID:    groupClusterMaintenanceID,
+		Title: "Cluster maintenance commands",
+	}
+}
+
+func NewConcurrencyGroup() *cobra.Group {
+	return &cobra.Group{
+		ID:    groupConcurrencyID,
+		Title: "Concurrency commands",
+	}
+}
+
+func NewAuthenticationGroup() *cobra.Group {
+	return &cobra.Group{
+		ID:    groupAuthenticationID,
+		Title: "Authentication commands",
+	}
+}
+
+func NewUtilityGroup() *cobra.Group {
+	return &cobra.Group{
+		ID:    groupUtilityID,
+		Title: "Utility commands",
+	}
+}
diff --git a/etcdctl/ctlv3/command/help_command.go b/etcdctl/ctlv3/command/help_command.go
new file mode 100644
index 000000000..259069791
--- /dev/null
+++ b/etcdctl/ctlv3/command/help_command.go
@@ -0,0 +1,21 @@
+// Copyright 2025 The etcd Authors
+//
+// 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 command
+
+import "github.com/spf13/cobra"
+
+func SetHelpCmdGroup(rootCmd *cobra.Command) {
+	rootCmd.SetHelpCommandGroupID(groupUtilityID)
+}
diff --git a/etcdctl/ctlv3/command/lease_command.go b/etcdctl/ctlv3/command/lease_command.go
index 8e70670b6..0fccc5f64 100644
--- a/etcdctl/ctlv3/command/lease_command.go
+++ b/etcdctl/ctlv3/command/lease_command.go
@@ -28,8 +28,10 @@ import (
 // NewLeaseCommand returns the cobra command for "lease".
 func NewLeaseCommand() *cobra.Command {
 	lc := &cobra.Command{
-		Use:   "lease <subcommand>",
-		Short: "Lease related commands",
+		Use:     "lease <subcommand>",
+		Short:   "Lease related commands. Use `etcdctl lease --help` to see subcommands",
+		Long:    "Lease related commands",
+		GroupID: groupKVID,
 	}
 
 	lc.AddCommand(NewLeaseGrantCommand())
diff --git a/etcdctl/ctlv3/command/lock_command.go b/etcdctl/ctlv3/command/lock_command.go
index 64fb0ee81..0faa76fe6 100644
--- a/etcdctl/ctlv3/command/lock_command.go
+++ b/etcdctl/ctlv3/command/lock_command.go
@@ -35,9 +35,10 @@ var lockTTL = 10
 // NewLockCommand returns the cobra command for "lock".
 func NewLockCommand() *cobra.Command {
 	c := &cobra.Command{
-		Use:   "lock <lockname> [exec-command arg1 arg2 ...]",
-		Short: "Acquires a named lock",
-		Run:   lockCommandFunc,
+		Use:     "lock <lockname> [exec-command arg1 arg2 ...]",
+		Short:   "Acquires a named lock",
+		Run:     lockCommandFunc,
+		GroupID: groupConcurrencyID,
 	}
 	c.Flags().IntVarP(&lockTTL, "ttl", "", lockTTL, "timeout for session")
 	return c
diff --git a/etcdctl/ctlv3/command/make_mirror_command.go b/etcdctl/ctlv3/command/make_mirror_command.go
index c8b6220d5..eea3d293e 100644
--- a/etcdctl/ctlv3/command/make_mirror_command.go
+++ b/etcdctl/ctlv3/command/make_mirror_command.go
@@ -53,9 +53,10 @@ var (
 // NewMakeMirrorCommand returns the cobra command for "makeMirror".
 func NewMakeMirrorCommand() *cobra.Command {
 	c := &cobra.Command{
-		Use:   "make-mirror [options] <destination>",
-		Short: "Makes a mirror at the destination etcd cluster",
-		Run:   makeMirrorCommandFunc,
+		Use:     "make-mirror [options] <destination>",
+		Short:   "Makes a mirror at the destination etcd cluster",
+		Run:     makeMirrorCommandFunc,
+		GroupID: groupUtilityID,
 	}
 
 	c.Flags().StringVar(&mmprefix, "prefix", "", "Key-value prefix to mirror")
diff --git a/etcdctl/ctlv3/command/member_command.go b/etcdctl/ctlv3/command/member_command.go
index 256bf2b9c..0ead08f43 100644
--- a/etcdctl/ctlv3/command/member_command.go
+++ b/etcdctl/ctlv3/command/member_command.go
@@ -35,8 +35,10 @@ var (
 // NewMemberCommand returns the cobra command for "member".
 func NewMemberCommand() *cobra.Command {
 	mc := &cobra.Command{
-		Use:   "member <subcommand>",
-		Short: "Membership related commands",
+		Use:     "member <subcommand>",
+		Short:   "Membership related commands. Use `etcdctl member --help` to see subcommands",
+		Long:    "Membership related commands",
+		GroupID: groupClusterMaintenanceID,
 	}
 
 	mc.AddCommand(NewMemberAddCommand())
diff --git a/etcdctl/ctlv3/command/move_leader_command.go b/etcdctl/ctlv3/command/move_leader_command.go
index a7b4f397b..320d9ff39 100644
--- a/etcdctl/ctlv3/command/move_leader_command.go
+++ b/etcdctl/ctlv3/command/move_leader_command.go
@@ -27,9 +27,10 @@ import (
 // NewMoveLeaderCommand returns the cobra command for "move-leader".
 func NewMoveLeaderCommand() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "move-leader <transferee-member-id>",
-		Short: "Transfers leadership to another etcd cluster member.",
-		Run:   transferLeadershipCommandFunc,
+		Use:     "move-leader <transferee-member-id>",
+		Short:   "Transfers leadership to another etcd cluster member.",
+		Run:     transferLeadershipCommandFunc,
+		GroupID: groupClusterMaintenanceID,
 	}
 	return cmd
 }
diff --git a/etcdctl/ctlv3/command/put_command.go b/etcdctl/ctlv3/command/put_command.go
index 4814db559..084815013 100644
--- a/etcdctl/ctlv3/command/put_command.go
+++ b/etcdctl/ctlv3/command/put_command.go
@@ -56,7 +56,8 @@ For example,
 $ cat file | put <key>
 will store the content of the file to <key>.
 `,
-		Run: putCommandFunc,
+		Run:     putCommandFunc,
+		GroupID: groupKVID,
 	}
 	cmd.Flags().StringVar(&leaseStr, "lease", "0", "lease ID (in hexadecimal) to attach to the key")
 	cmd.Flags().BoolVar(&putPrevKV, "prev-kv", false, "return the previous key-value pair before modification")
diff --git a/etcdctl/ctlv3/command/role_command.go b/etcdctl/ctlv3/command/role_command.go
index 705d88d88..5fd96ca1c 100644
--- a/etcdctl/ctlv3/command/role_command.go
+++ b/etcdctl/ctlv3/command/role_command.go
@@ -32,8 +32,10 @@ var (
 // NewRoleCommand returns the cobra command for "role".
 func NewRoleCommand() *cobra.Command {
 	ac := &cobra.Command{
-		Use:   "role <subcommand>",
-		Short: "Role related commands",
+		Use:     "role <subcommand>",
+		Short:   "Role related commands. Use `etcdctl role --help` to see subcommands",
+		Long:    "Role related commands",
+		GroupID: groupAuthenticationID,
 	}
 
 	ac.AddCommand(newRoleAddCommand())
diff --git a/etcdctl/ctlv3/command/snapshot_command.go b/etcdctl/ctlv3/command/snapshot_command.go
index 66934bede..d98c93b0b 100644
--- a/etcdctl/ctlv3/command/snapshot_command.go
+++ b/etcdctl/ctlv3/command/snapshot_command.go
@@ -52,6 +52,7 @@ func NewSnapshotCommand() *cobra.Command {
 		Use:     "snapshot <subcommand>",
 		Short:   "Manages etcd node snapshots",
 		Example: snapshotExample,
+		GroupID: groupClusterMaintenanceID,
 	}
 	cmd.AddCommand(NewSnapshotSaveCommand())
 	return cmd
diff --git a/etcdctl/ctlv3/command/txn_command.go b/etcdctl/ctlv3/command/txn_command.go
index 5433f82bd..df746a228 100644
--- a/etcdctl/ctlv3/command/txn_command.go
+++ b/etcdctl/ctlv3/command/txn_command.go
@@ -59,7 +59,8 @@ put key2 "some extra key"
 ---
 
 Refer to https://github.com/etcd-io/etcd/blob/main/etcdctl/README.md#txn-options.`,
-		Run: txnCommandFunc,
+		Run:     txnCommandFunc,
+		GroupID: groupKVID,
 	}
 	cmd.Flags().BoolVarP(&txnInteractive, "interactive", "i", false, "Input transaction in interactive mode")
 	return cmd
@@ -150,16 +151,19 @@ func parseRequestUnion(line string) (*clientv3.Op, error) {
 	opc := make(chan clientv3.Op, 1)
 
 	put := NewPutCommand()
+	put.GroupID = ""
 	put.Run = func(cmd *cobra.Command, args []string) {
 		key, value, opts := getPutOp(args)
 		opc <- clientv3.OpPut(key, value, opts...)
 	}
 	get := NewGetCommand()
+	get.GroupID = ""
 	get.Run = func(cmd *cobra.Command, args []string) {
 		key, opts := getGetOp(args)
 		opc <- clientv3.OpGet(key, opts...)
 	}
 	del := NewDelCommand()
+	del.GroupID = ""
 	del.Run = func(cmd *cobra.Command, args []string) {
 		key, opts := getDelOp(args)
 		opc <- clientv3.OpDelete(key, opts...)
diff --git a/etcdctl/ctlv3/command/user_command.go b/etcdctl/ctlv3/command/user_command.go
index f3e59a704..20e4d3e8c 100644
--- a/etcdctl/ctlv3/command/user_command.go
+++ b/etcdctl/ctlv3/command/user_command.go
@@ -31,8 +31,10 @@ var userShowDetail bool
 // NewUserCommand returns the cobra command for "user".
 func NewUserCommand() *cobra.Command {
 	ac := &cobra.Command{
-		Use:   "user <subcommand>",
-		Short: "User related commands",
+		Use:     "user <subcommand>",
+		Short:   "User related commands. Use `etcdctl user --help` to see subcommands",
+		Long:    "User related commands",
+		GroupID: groupAuthenticationID,
 	}
 
 	ac.AddCommand(newUserAddCommand())
diff --git a/etcdctl/ctlv3/command/version_command.go b/etcdctl/ctlv3/command/version_command.go
index e57567d6a..d5be27adb 100644
--- a/etcdctl/ctlv3/command/version_command.go
+++ b/etcdctl/ctlv3/command/version_command.go
@@ -25,9 +25,10 @@ import (
 // NewVersionCommand prints out the version of etcd.
 func NewVersionCommand() *cobra.Command {
 	return &cobra.Command{
-		Use:   "version",
-		Short: "Prints the version of etcdctl",
-		Run:   versionCommandFunc,
+		Use:     "version",
+		Short:   "Prints the version of etcdctl",
+		Run:     versionCommandFunc,
+		GroupID: groupUtilityID,
 	}
 }
 
diff --git a/etcdctl/ctlv3/command/watch_command.go b/etcdctl/ctlv3/command/watch_command.go
index fa8fc72d8..b8c8bdf13 100644
--- a/etcdctl/ctlv3/command/watch_command.go
+++ b/etcdctl/ctlv3/command/watch_command.go
@@ -47,9 +47,10 @@ var (
 // NewWatchCommand returns the cobra command for "watch".
 func NewWatchCommand() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "watch [options] [key or prefix] [range_end] [--] [exec-command arg1 arg2 ...]",
-		Short: "Watches events stream on keys or prefixes",
-		Run:   watchCommandFunc,
+		Use:     "watch [options] [key or prefix] [range_end] [--] [exec-command arg1 arg2 ...]",
+		Short:   "Watches events stream on keys or prefixes",
+		Run:     watchCommandFunc,
+		GroupID: groupKVID,
 	}
 
 	cmd.Flags().BoolVarP(&watchInteractive, "interactive", "i", false, "Interactive mode")
diff --git a/etcdctl/ctlv3/ctl.go b/etcdctl/ctlv3/ctl.go
index e992fa4fa..0fc657071 100644
--- a/etcdctl/ctlv3/ctl.go
+++ b/etcdctl/ctlv3/ctl.go
@@ -21,9 +21,7 @@ import (
 
 	"github.com/spf13/cobra"
 
-	"go.etcd.io/etcd/api/v3/version"
 	"go.etcd.io/etcd/etcdctl/v3/ctlv3/command"
-	"go.etcd.io/etcd/etcdctl/v3/util"
 	"go.etcd.io/etcd/pkg/v3/cobrautl"
 )
 
@@ -76,6 +74,14 @@ func init() {
 	rootCmd.PersistentFlags().StringVarP(&globalFlags.TLS.ServerName, "discovery-srv", "d", "", "domain name to query for SRV records describing cluster endpoints")
 	rootCmd.PersistentFlags().StringVarP(&globalFlags.DNSClusterServiceName, "discovery-srv-name", "", "", "service name to query when using DNS discovery")
 
+	rootCmd.AddGroup(
+		command.NewKVGroup(),
+		command.NewClusterMaintenanceGroup(),
+		command.NewConcurrencyGroup(),
+		command.NewAuthenticationGroup(),
+		command.NewUtilityGroup(),
+	)
+
 	rootCmd.AddCommand(
 		command.NewGetCommand(),
 		command.NewPutCommand(),
@@ -101,16 +107,11 @@ func init() {
 		command.NewCompletionCommand(),
 		command.NewDowngradeCommand(),
 	)
-}
 
-func usageFunc(c *cobra.Command) error {
-	return util.UsageFunc(c, version.Version, version.APIVersion)
+	command.SetHelpCmdGroup(rootCmd)
 }
 
 func Start() error {
-	rootCmd.SetUsageFunc(usageFunc)
-	// Make help just show the usage
-	rootCmd.SetHelpTemplate(`{{.UsageString}}`)
 	return rootCmd.Execute()
 }
 
diff --git a/etcdctl/go.mod b/etcdctl/go.mod
index f42c7bde4..9c2b00d55 100644
--- a/etcdctl/go.mod
+++ b/etcdctl/go.mod
@@ -2,7 +2,7 @@ module go.etcd.io/etcd/etcdctl/v3
 
 go 1.24
 
-toolchain go1.24.5
+toolchain go1.24.6
 
 require (
 	github.com/bgentry/speakeasy v0.2.0
@@ -39,12 +39,12 @@ require (
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/net v0.42.0 // indirect
-	golang.org/x/sys v0.34.0 // indirect
-	golang.org/x/text v0.27.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
-	google.golang.org/protobuf v1.36.6 // indirect
+	golang.org/x/net v0.43.0 // indirect
+	golang.org/x/sys v0.35.0 // indirect
+	golang.org/x/text v0.28.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
+	google.golang.org/protobuf v1.36.7 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
diff --git a/etcdctl/go.sum b/etcdctl/go.sum
index cf08b9fd2..6cba9dbe2 100644
--- a/etcdctl/go.sum
+++ b/etcdctl/go.sum
@@ -62,14 +62,14 @@ github.com/olekukonko/tablewriter v1.0.9 h1:XGwRsYLC2bY7bNd93Dk51bcPZksWZmLYuaTH
 github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
-github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
+github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
 github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
 github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
-github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
-github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -112,8 +112,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
-golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -121,12 +121,12 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
 golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -137,14 +137,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
 google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
+google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/etcdctl/util/help.go b/etcdctl/util/help.go
deleted file mode 100644
index 0e8035313..000000000
--- a/etcdctl/util/help.go
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2025 The etcd Authors
-//
-// 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.
-
-// copied from https://github.com/rkt/rkt/blob/master/rkt/help.go
-
-package util
-
-import (
-	"fmt"
-	"io"
-	"os"
-	"strings"
-	"text/tabwriter"
-	"text/template"
-
-	"github.com/spf13/cobra"
-	"github.com/spf13/pflag"
-)
-
-var (
-	commandUsageTemplate *template.Template
-	templFuncs           = template.FuncMap{
-		"descToLines": func(s string) []string {
-			// trim leading/trailing whitespace and split into slice of lines
-			return strings.Split(strings.Trim(s, "\n\t "), "\n")
-		},
-		"cmdName": func(cmd *cobra.Command, startCmd *cobra.Command) string {
-			parts := []string{cmd.Name()}
-			for cmd.HasParent() && cmd.Parent().Name() != startCmd.Name() {
-				cmd = cmd.Parent()
-				parts = append([]string{cmd.Name()}, parts...)
-			}
-			return strings.Join(parts, " ")
-		},
-		"indent": func(s string) string {
-			pad := strings.Repeat(" ", 2)
-			return pad + strings.Replace(s, "\n", "\n"+pad, -1)
-		},
-	}
-)
-
-func init() {
-	commandUsage := `
-{{ $cmd := .Cmd }}\
-{{ $cmdname := cmdName .Cmd .Cmd.Root }}\
-NAME:
-{{if not .Cmd.HasParent}}\
-{{printf "%s - %s" .Cmd.Name .Cmd.Short | indent}}
-{{else}}\
-{{printf "%s - %s" $cmdname .Cmd.Short | indent}}
-{{end}}\
-
-USAGE:
-{{printf "%s" .Cmd.UseLine | indent}}
-{{ if not .Cmd.HasParent }}\
-
-VERSION:
-{{printf "%s" .Version | indent}}
-{{end}}\
-{{if .Cmd.HasSubCommands}}\
-
-API VERSION:
-{{.APIVersion | indent}}
-{{end}}\
-{{if .Cmd.HasExample}}\
-
-Examples:
-{{.Cmd.Example}}
-{{end}}\
-{{if .Cmd.HasSubCommands}}\
-
-COMMANDS:
-{{range .SubCommands}}\
-{{ $cmdname := cmdName . $cmd }}\
-{{ if .Runnable }}\
-{{printf "%s\t%s" $cmdname .Short | indent}}
-{{end}}\
-{{end}}\
-{{end}}\
-{{ if .Cmd.Long }}\
-
-DESCRIPTION:
-{{range $line := descToLines .Cmd.Long}}{{printf "%s" $line | indent}}
-{{end}}\
-{{end}}\
-{{if .Cmd.HasLocalFlags}}\
-
-OPTIONS:
-{{.LocalFlags}}\
-{{end}}\
-{{if .Cmd.HasInheritedFlags}}\
-
-GLOBAL OPTIONS:
-{{.GlobalFlags}}\
-{{end}}
-`[1:]
-
-	commandUsageTemplate = template.Must(template.New("command_usage").Funcs(templFuncs).Parse(strings.ReplaceAll(commandUsage, "\\\n", "")))
-}
-
-func etcdFlagUsages(flagSet *pflag.FlagSet) string {
-	x := new(strings.Builder)
-
-	flagSet.VisitAll(func(flag *pflag.Flag) {
-		if len(flag.Deprecated) > 0 {
-			return
-		}
-		var format string
-		if len(flag.Shorthand) > 0 {
-			format = "  -%s, --%s"
-		} else {
-			format = "   %s   --%s"
-		}
-		if len(flag.NoOptDefVal) > 0 {
-			format = format + "["
-		}
-		if flag.Value.Type() == "string" {
-			// put quotes on the value
-			format = format + "=%q"
-		} else {
-			format = format + "=%s"
-		}
-		if len(flag.NoOptDefVal) > 0 {
-			format = format + "]"
-		}
-		format = format + "\t%s\n"
-		shorthand := flag.Shorthand
-		fmt.Fprintf(x, format, shorthand, flag.Name, flag.DefValue, flag.Usage)
-	})
-
-	return x.String()
-}
-
-func getSubCommands(cmd *cobra.Command) []*cobra.Command {
-	var subCommands []*cobra.Command
-	for _, subCmd := range cmd.Commands() {
-		subCommands = append(subCommands, subCmd)
-		subCommands = append(subCommands, getSubCommands(subCmd)...)
-	}
-	return subCommands
-}
-
-func UsageFunc(cmd *cobra.Command, version, APIVersion string) error {
-	subCommands := getSubCommands(cmd)
-	tabOut := getTabOutWithWriter(os.Stdout)
-	commandUsageTemplate.Execute(tabOut, struct {
-		Cmd         *cobra.Command
-		LocalFlags  string
-		GlobalFlags string
-		SubCommands []*cobra.Command
-		Version     string
-		APIVersion  string
-	}{
-		cmd,
-		etcdFlagUsages(cmd.LocalFlags()),
-		etcdFlagUsages(cmd.InheritedFlags()),
-		subCommands,
-		version,
-		APIVersion,
-	})
-	tabOut.Flush()
-	return nil
-}
-
-func getTabOutWithWriter(writer io.Writer) *tabwriter.Writer {
-	aTabOut := new(tabwriter.Writer)
-	aTabOut.Init(writer, 0, 8, 1, '\t', 0)
-	return aTabOut
-}
diff --git a/etcdutl/go.mod b/etcdutl/go.mod
index 2497d53c2..bfd2ce8d0 100644
--- a/etcdutl/go.mod
+++ b/etcdutl/go.mod
@@ -2,7 +2,7 @@ module go.etcd.io/etcd/etcdutl/v3
 
 go 1.24
 
-toolchain go1.24.5
+toolchain go1.24.6
 
 replace (
 	go.etcd.io/etcd/api/v3 => ../api
@@ -46,7 +46,7 @@ require (
 	github.com/go-logr/logr v1.4.3 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
-	github.com/golang-jwt/jwt/v5 v5.2.3 // indirect
+	github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
 	github.com/golang/protobuf v1.5.4 // indirect
 	github.com/google/btree v1.1.3 // indirect
 	github.com/google/go-cmp v0.7.0 // indirect
@@ -64,10 +64,10 @@ require (
 	github.com/olekukonko/errors v1.1.0 // indirect
 	github.com/olekukonko/ll v0.0.9 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
-	github.com/prometheus/client_golang v1.22.0 // indirect
+	github.com/prometheus/client_golang v1.23.0 // indirect
 	github.com/prometheus/client_model v0.6.2 // indirect
 	github.com/prometheus/common v0.65.0 // indirect
-	github.com/prometheus/procfs v0.15.1 // indirect
+	github.com/prometheus/procfs v0.16.1 // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/soheilhy/cmux v0.1.5 // indirect
@@ -82,19 +82,19 @@ require (
 	go.opentelemetry.io/otel/metric v1.37.0 // indirect
 	go.opentelemetry.io/otel/sdk v1.37.0 // indirect
 	go.opentelemetry.io/otel/trace v1.37.0 // indirect
-	go.opentelemetry.io/proto/otlp v1.7.0 // indirect
+	go.opentelemetry.io/proto/otlp v1.7.1 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.yaml.in/yaml/v2 v2.4.2 // indirect
-	golang.org/x/crypto v0.40.0 // indirect
-	golang.org/x/net v0.42.0 // indirect
-	golang.org/x/sys v0.34.0 // indirect
-	golang.org/x/text v0.27.0 // indirect
+	golang.org/x/crypto v0.41.0 // indirect
+	golang.org/x/net v0.43.0 // indirect
+	golang.org/x/sys v0.35.0 // indirect
+	golang.org/x/text v0.28.0 // indirect
 	golang.org/x/time v0.12.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
 	google.golang.org/grpc v1.74.2 // indirect
-	google.golang.org/protobuf v1.36.6 // indirect
+	google.golang.org/protobuf v1.36.7 // indirect
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	sigs.k8s.io/yaml v1.5.0 // indirect
+	sigs.k8s.io/yaml v1.6.0 // indirect
 )
diff --git a/etcdutl/go.sum b/etcdutl/go.sum
index 07a5bf9f5..6d117239b 100644
--- a/etcdutl/go.sum
+++ b/etcdutl/go.sum
@@ -27,8 +27,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
-github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
+github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
@@ -76,14 +76,14 @@ github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfB
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
-github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
+github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
 github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
 github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
-github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
-github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -131,8 +131,8 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh
 go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
 go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
 go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
-go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
-go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -146,8 +146,8 @@ go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
-golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
+golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
+golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -155,8 +155,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
-golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -167,12 +167,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
 golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -183,14 +183,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
 google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
+google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -199,5 +199,5 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
-sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
+sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
+sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
diff --git a/go.mod b/go.mod
index e6a907a55..f25a2c59e 100644
--- a/go.mod
+++ b/go.mod
@@ -2,7 +2,7 @@ module go.etcd.io/etcd/v3
 
 go 1.24
 
-toolchain go1.24.5
+toolchain go1.24.6
 
 replace (
 	go.etcd.io/etcd/api/v3 => ./api
@@ -36,7 +36,7 @@ require (
 	go.uber.org/zap v1.27.0
 	golang.org/x/time v0.12.0
 	google.golang.org/grpc v1.74.2
-	google.golang.org/protobuf v1.36.6
+	google.golang.org/protobuf v1.36.7
 )
 
 require (
@@ -50,7 +50,7 @@ require (
 	github.com/go-logr/logr v1.4.3 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
-	github.com/golang-jwt/jwt/v5 v5.2.3 // indirect
+	github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
 	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
 	github.com/golang/protobuf v1.5.4 // indirect
 	github.com/google/btree v1.1.3 // indirect
@@ -71,10 +71,10 @@ require (
 	github.com/olekukonko/ll v0.0.9 // indirect
 	github.com/olekukonko/tablewriter v1.0.9 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
-	github.com/prometheus/client_golang v1.22.0 // indirect
+	github.com/prometheus/client_golang v1.23.0 // indirect
 	github.com/prometheus/client_model v0.6.2 // indirect
 	github.com/prometheus/common v0.65.0 // indirect
-	github.com/prometheus/procfs v0.15.1 // indirect
+	github.com/prometheus/procfs v0.16.1 // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/soheilhy/cmux v0.1.5 // indirect
@@ -90,16 +90,16 @@ require (
 	go.opentelemetry.io/otel/metric v1.37.0 // indirect
 	go.opentelemetry.io/otel/sdk v1.37.0 // indirect
 	go.opentelemetry.io/otel/trace v1.37.0 // indirect
-	go.opentelemetry.io/proto/otlp v1.7.0 // indirect
+	go.opentelemetry.io/proto/otlp v1.7.1 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.yaml.in/yaml/v2 v2.4.2 // indirect
-	golang.org/x/crypto v0.40.0 // indirect
-	golang.org/x/net v0.42.0 // indirect
-	golang.org/x/sys v0.34.0 // indirect
-	golang.org/x/text v0.27.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
+	golang.org/x/crypto v0.41.0 // indirect
+	golang.org/x/net v0.43.0 // indirect
+	golang.org/x/sys v0.35.0 // indirect
+	golang.org/x/text v0.28.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	sigs.k8s.io/yaml v1.5.0 // indirect
+	sigs.k8s.io/yaml v1.6.0 // indirect
 )
diff --git a/go.sum b/go.sum
index de2e370b5..d580c2bf2 100644
--- a/go.sum
+++ b/go.sum
@@ -45,8 +45,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
-github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
+github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
@@ -108,15 +108,15 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
-github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
+github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
 github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
 github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
-github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
-github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -170,8 +170,8 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh
 go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
 go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
 go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
-go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
-go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
@@ -188,8 +188,8 @@ go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
-golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
+golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
+golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -205,8 +205,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
-golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -222,12 +222,12 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
 golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -247,10 +247,10 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@@ -258,8 +258,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
 google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
 google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
+google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -271,5 +271,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
-sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
+sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
+sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
diff --git a/pkg/go.mod b/pkg/go.mod
index ed9ed5516..eefa80e29 100644
--- a/pkg/go.mod
+++ b/pkg/go.mod
@@ -2,7 +2,7 @@ module go.etcd.io/etcd/pkg/v3
 
 go 1.24
 
-toolchain go1.24.5
+toolchain go1.24.6
 
 require (
 	github.com/creack/pty v1.1.18
@@ -24,11 +24,11 @@ require (
 	go.opentelemetry.io/otel v1.37.0 // indirect
 	go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/net v0.42.0 // indirect
-	golang.org/x/sys v0.34.0 // indirect
-	golang.org/x/text v0.27.0 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
-	google.golang.org/protobuf v1.36.6 // indirect
+	golang.org/x/net v0.43.0 // indirect
+	golang.org/x/sys v0.35.0 // indirect
+	golang.org/x/text v0.28.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
+	google.golang.org/protobuf v1.36.7 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
diff --git a/pkg/go.sum b/pkg/go.sum
index b780cceca..d8a0d3c54 100644
--- a/pkg/go.sum
+++ b/pkg/go.sum
@@ -54,18 +54,18 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
 go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
-golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
-golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
 google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
+google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/scripts/benchmark_test.sh b/scripts/benchmark_test.sh
new file mode 100755
index 000000000..95da4141f
--- /dev/null
+++ b/scripts/benchmark_test.sh
@@ -0,0 +1,62 @@
+#!/usr/bin/env bash
+# Copyright 2025 The etcd Authors
+#
+# 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.
+
+# This script runs a benchmark on a locally started etcd server
+
+set -euo pipefail
+
+source ./scripts/test_lib.sh
+
+COMMON_BENCHMARK_FLAGS="--report-perfdash"
+
+if [[ $# -lt 1 ]]; then
+  echo "Usage: $0 <benchmark-name> [tester args...]"
+  exit 1
+fi
+
+BENCHMARK_NAME="$1"
+ARGS="${*:2}"
+
+echo "Starting the etcd server..."
+DATA_DIR=$(mktemp -d /tmp/etcd/data-XXXXXX)
+./bin/etcd --data-dir="$DATA_DIR" > /tmp/etcd.log 2>&1 &
+etcd_pid=$!
+
+trap 'log_warning -e "Stopping etcd server - PID $etcd_pid";
+      kill $etcd_pid 2>/dev/null;
+      rm -rf $DATA_DIR;
+      log_success "Deleted the contents from $DATA_DIR related to benchmark test"' EXIT
+
+# Wait until etcd becomes healthy
+for retry in {1..10}; do
+  if ./bin/etcdctl endpoint health --cluster> /dev/null 2>&1; then
+    log_success -e "\\netcd is healthy"
+    break
+  fi
+  log_warning -e "\\nWaiting for etcd to be healthy..."
+  sleep 1
+  if [[ $retry -eq 10 ]]; then
+    log_error -e "\\nFailed to confirm etcd health after $retry attempts. Check /tmp/etcd.log for more information"
+    exit 1
+  fi
+done
+
+log_success -e "etcd process is running with PID $etcd_pid"
+
+log_callout -e "\\nPerforming benchmark $BENCHMARK_NAME with arguments: $ARGS"
+read -r -a TESTER_OPTIONS <<< "$ARGS"
+log_callout "Running: benchmark $BENCHMARK_NAME ${TESTER_OPTIONS[*]} $COMMON_BENCHMARK_FLAGS"
+benchmark "$BENCHMARK_NAME" "${TESTER_OPTIONS[@]}" $COMMON_BENCHMARK_FLAGS
+log_callout "Completed: benchmark $BENCHMARK_NAME ${TESTER_OPTIONS[*]} $COMMON_BENCHMARK_FLAGS"
diff --git a/server/etcdserver/api/rafthttp/stream.go b/server/etcdserver/api/rafthttp/stream.go
index 23eb9c9a9..cd4e11e1f 100644
--- a/server/etcdserver/api/rafthttp/stream.go
+++ b/server/etcdserver/api/rafthttp/stream.go
@@ -498,7 +498,7 @@ func (cr *streamReader) decodeLoop(rc io.ReadCloser, t streamType) error {
 			return err
 		}
 
-		// gofail-go: var raftDropHeartbeat struct{}
+		// gofail: var raftDropHeartbeat struct{}
 		// continue labelRaftDropHeartbeat
 		receivedBytes.WithLabelValues(types.ID(m.From).String()).Add(float64(m.Size()))
 
diff --git a/server/etcdserver/api/v3rpc/watch.go b/server/etcdserver/api/v3rpc/watch.go
index d4a5bc351..a6c1e42cc 100644
--- a/server/etcdserver/api/v3rpc/watch.go
+++ b/server/etcdserver/api/v3rpc/watch.go
@@ -22,6 +22,8 @@ import (
 	"sync"
 	"time"
 
+	"go.opentelemetry.io/otel/attribute"
+	"go.opentelemetry.io/otel/trace"
 	"go.uber.org/zap"
 
 	pb "go.etcd.io/etcd/api/v3/etcdserverpb"
@@ -29,6 +31,7 @@ import (
 	"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
 	"go.etcd.io/etcd/client/pkg/v3/verify"
 	clientv3 "go.etcd.io/etcd/client/v3"
+	"go.etcd.io/etcd/pkg/v3/traceutil"
 	"go.etcd.io/etcd/server/v3/auth"
 	"go.etcd.io/etcd/server/v3/etcdserver"
 	"go.etcd.io/etcd/server/v3/etcdserver/apply"
@@ -304,8 +307,16 @@ func (sws *serverWatchStream) recvLoop() error {
 			}
 
 			filters := FiltersFromRequest(creq)
-
-			id, err := sws.watchStream.Watch(mvcc.WatchID(creq.WatchId), creq.Key, creq.RangeEnd, creq.StartRevision, filters...)
+			ctx, _ := traceutil.Tracer.Start(sws.gRPCStream.Context(), "watch", trace.WithAttributes(
+				attribute.String("key", string(creq.Key)),
+				attribute.String("range_end", string(creq.RangeEnd)),
+				attribute.Int64("start_rev", creq.StartRevision),
+				attribute.Bool("progress_notify", creq.ProgressNotify),
+				attribute.Bool("prev_kv", creq.PrevKv),
+				attribute.Bool("fragment", creq.Fragment),
+			))
+
+			id, err := sws.watchStream.Watch(ctx, mvcc.WatchID(creq.WatchId), creq.Key, creq.RangeEnd, creq.StartRevision, filters...)
 			if err == nil {
 				sws.mu.Lock()
 				if creq.ProgressNotify {
diff --git a/server/go.mod b/server/go.mod
index 39899f7d3..a13d56535 100644
--- a/server/go.mod
+++ b/server/go.mod
@@ -2,14 +2,14 @@ module go.etcd.io/etcd/server/v3
 
 go 1.24
 
-toolchain go1.24.5
+toolchain go1.24.6
 
 require (
 	github.com/coreos/go-semver v0.3.1
 	github.com/coreos/go-systemd/v22 v22.5.0
 	github.com/dustin/go-humanize v1.0.1
 	github.com/gogo/protobuf v1.3.2
-	github.com/golang-jwt/jwt/v5 v5.2.3
+	github.com/golang-jwt/jwt/v5 v5.3.0
 	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8
 	github.com/golang/protobuf v1.5.4
 	github.com/google/btree v1.1.3
@@ -18,7 +18,7 @@ require (
 	github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1
 	github.com/jonboulle/clockwork v0.5.0
-	github.com/prometheus/client_golang v1.22.0
+	github.com/prometheus/client_golang v1.23.0
 	github.com/prometheus/client_model v0.6.2
 	github.com/soheilhy/cmux v0.1.5
 	github.com/spf13/cobra v1.9.1
@@ -37,14 +37,14 @@ require (
 	go.opentelemetry.io/otel/sdk v1.37.0
 	go.opentelemetry.io/otel/trace v1.37.0
 	go.uber.org/zap v1.27.0
-	golang.org/x/crypto v0.40.0
-	golang.org/x/net v0.42.0
+	golang.org/x/crypto v0.41.0
+	golang.org/x/net v0.43.0
 	golang.org/x/time v0.12.0
-	google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822
+	google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0
 	google.golang.org/grpc v1.74.2
-	google.golang.org/protobuf v1.36.6
+	google.golang.org/protobuf v1.36.7
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1
-	sigs.k8s.io/yaml v1.5.0
+	sigs.k8s.io/yaml v1.6.0
 )
 
 require (
@@ -61,18 +61,18 @@ require (
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/prometheus/common v0.65.0 // indirect
-	github.com/prometheus/procfs v0.15.1 // indirect
+	github.com/prometheus/procfs v0.16.1 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/spf13/pflag v1.0.7 // indirect
 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
 	go.opentelemetry.io/otel/metric v1.37.0 // indirect
-	go.opentelemetry.io/proto/otlp v1.7.0 // indirect
+	go.opentelemetry.io/proto/otlp v1.7.1 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.yaml.in/yaml/v2 v2.4.2 // indirect
-	golang.org/x/sys v0.34.0 // indirect
-	golang.org/x/text v0.27.0 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
+	golang.org/x/sys v0.35.0 // indirect
+	golang.org/x/text v0.28.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
diff --git a/server/go.sum b/server/go.sum
index b4d526393..f4920f7fe 100644
--- a/server/go.sum
+++ b/server/go.sum
@@ -25,8 +25,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
-github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
+github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -64,14 +64,14 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
-github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
+github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
 github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
 github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
-github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
-github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
 github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -116,8 +116,8 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh
 go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
 go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
 go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
-go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
-go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -131,8 +131,8 @@ go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
-golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
+golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
+golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -140,8 +140,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
-golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -151,12 +151,12 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
 golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -167,14 +167,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
 google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
+google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -183,5 +183,5 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
-sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
+sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
+sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
diff --git a/server/storage/mvcc/kv_view.go b/server/storage/mvcc/kv_view.go
index 56260e759..b5e8bf909 100644
--- a/server/storage/mvcc/kv_view.go
+++ b/server/storage/mvcc/kv_view.go
@@ -24,13 +24,13 @@ import (
 type readView struct{ kv KV }
 
 func (rv *readView) FirstRev() int64 {
-	tr := rv.kv.Read(ConcurrentReadTxMode, traceutil.TODO())
+	tr := rv.kv.Read(SharedBufReadTxMode, traceutil.TODO())
 	defer tr.End()
 	return tr.FirstRev()
 }
 
 func (rv *readView) Rev() int64 {
-	tr := rv.kv.Read(ConcurrentReadTxMode, traceutil.TODO())
+	tr := rv.kv.Read(SharedBufReadTxMode, traceutil.TODO())
 	defer tr.End()
 	return tr.Rev()
 }
diff --git a/server/storage/mvcc/watcher.go b/server/storage/mvcc/watcher.go
index c67c21d61..87bcf9a40 100644
--- a/server/storage/mvcc/watcher.go
+++ b/server/storage/mvcc/watcher.go
@@ -16,9 +16,12 @@ package mvcc
 
 import (
 	"bytes"
+	"context"
 	"errors"
 	"sync"
 
+	"go.opentelemetry.io/otel/trace"
+
 	"go.etcd.io/etcd/api/v3/mvccpb"
 	clientv3 "go.etcd.io/etcd/client/v3"
 )
@@ -45,7 +48,7 @@ type WatchStream interface {
 	// in events that are sent to the created watcher through stream channel.
 	// The watch ID is used when it's not equal to AutoWatchID. Otherwise,
 	// an auto-generated watch ID is returned.
-	Watch(id WatchID, key, end []byte, startRev int64, fcs ...FilterFunc) (WatchID, error)
+	Watch(ctx context.Context, id WatchID, key, end []byte, startRev int64, fcs ...FilterFunc) (WatchID, error)
 
 	// Chan returns a chan. All watch response will be sent to the returned chan.
 	Chan() <-chan WatchResponse
@@ -109,7 +112,7 @@ type watchStream struct {
 }
 
 // Watch creates a new watcher in the stream and returns its WatchID.
-func (ws *watchStream) Watch(id WatchID, key, end []byte, startRev int64, fcs ...FilterFunc) (WatchID, error) {
+func (ws *watchStream) Watch(ctx context.Context, id WatchID, key, end []byte, startRev int64, fcs ...FilterFunc) (WatchID, error) {
 	// prevent wrong range where key >= end lexicographically
 	// watch request with 'WithFromKey' has empty-byte range end
 	if len(end) != 0 && bytes.Compare(key, end) != -1 {
@@ -134,7 +137,11 @@ func (ws *watchStream) Watch(id WatchID, key, end []byte, startRev int64, fcs ..
 
 	w, c := ws.watchable.watch(key, end, startRev, id, ws.ch, fcs...)
 
-	ws.cancels[id] = c
+	span := trace.SpanFromContext(ctx)
+	ws.cancels[id] = func() {
+		defer span.End()
+		c()
+	}
 	ws.watchers[id] = w
 	return id, nil
 }
diff --git a/server/storage/schema/schema.go b/server/storage/schema/schema.go
index 72f870a10..607e89e66 100644
--- a/server/storage/schema/schema.go
+++ b/server/storage/schema/schema.go
@@ -95,15 +95,11 @@ func UnsafeDetectSchemaVersion(lg *zap.Logger, tx backend.UnsafeReader) (v semve
 		return *vp, nil
 	}
 
-	// TODO: remove the operations of reading the fields `confState`
-	// and `term` in 3.7. We only need to be back-compatible
-	// with 3.6 when we are running 3.7, and the `storageVersion`
-	// already exists in all versions >= 3.6, so we don't need to
-	// use any other fields to identify the etcd's storage version.
-	confstate := UnsafeConfStateFromBackend(lg, tx)
-	if confstate == nil {
-		return v, fmt.Errorf("missing confstate information")
-	}
+	// TODO: remove the operations of reading the field `term`
+	// in 3.7. We only need to be back-compatible with 3.6 when
+	// we are running 3.7, and the `storageVersion` already exists
+	// in all versions >= 3.6, so we don't need to use any other
+	// fields to identify the etcd's storage version.
 	_, term := UnsafeReadConsistentIndex(tx)
 	if term == 0 {
 		return v, fmt.Errorf("missing term information")
diff --git a/tools/mod/go.mod b/tools/mod/go.mod
index 8beb0f5f8..e45a1219f 100644
--- a/tools/mod/go.mod
+++ b/tools/mod/go.mod
@@ -2,7 +2,7 @@ module go.etcd.io/etcd/tools/v3
 
 go 1.24
 
-toolchain go1.24.5
+toolchain go1.24.6
 
 require (
 	github.com/alexfalkowski/gocovmerge v1.11.0
@@ -152,10 +152,10 @@ require (
 	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/polyfloyd/go-errorlint v1.7.1 // indirect
-	github.com/prometheus/client_golang v1.22.0 // indirect
+	github.com/prometheus/client_golang v1.23.0 // indirect
 	github.com/prometheus/client_model v0.6.2 // indirect
 	github.com/prometheus/common v0.65.0 // indirect
-	github.com/prometheus/procfs v0.15.1 // indirect
+	github.com/prometheus/procfs v0.16.1 // indirect
 	github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect
 	github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
 	github.com/quasilyte/gogrep v0.5.0 // indirect
@@ -213,20 +213,22 @@ require (
 	go.uber.org/automaxprocs v1.6.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/zap v1.27.0 // indirect
-	golang.org/x/crypto v0.40.0 // indirect
+	golang.org/x/crypto v0.41.0 // indirect
 	golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
 	golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
-	golang.org/x/mod v0.25.0 // indirect
-	golang.org/x/net v0.42.0 // indirect
+	golang.org/x/mod v0.26.0 // indirect
+	golang.org/x/net v0.43.0 // indirect
 	golang.org/x/sync v0.16.0 // indirect
-	golang.org/x/sys v0.34.0 // indirect
-	golang.org/x/term v0.33.0 // indirect
-	golang.org/x/text v0.27.0 // indirect
-	golang.org/x/tools v0.34.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
+	golang.org/x/sys v0.35.0 // indirect
+	golang.org/x/term v0.34.0 // indirect
+	golang.org/x/text v0.28.0 // indirect
+	golang.org/x/tools v0.35.0 // indirect
+	golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
+	golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
 	google.golang.org/grpc v1.74.2 // indirect
-	google.golang.org/protobuf v1.36.6 // indirect
+	google.golang.org/protobuf v1.36.7 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	k8s.io/klog/v2 v2.130.1 // indirect
diff --git a/tools/mod/go.sum b/tools/mod/go.sum
index 4afb7e4c0..d81573fce 100644
--- a/tools/mod/go.sum
+++ b/tools/mod/go.sum
@@ -353,14 +353,14 @@ github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L
 github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8=
 github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
 github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
-github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
-github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
+github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
 github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
 github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
-github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
-github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
 github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo=
 github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI=
 github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE=
@@ -541,8 +541,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
-golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
-golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
+golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
+golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
 golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
@@ -560,8 +560,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
-golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
+golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
+golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -583,8 +583,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
 golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
-golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
-golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
 golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -626,8 +626,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -641,8 +641,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
-golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
-golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
+golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
+golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -657,8 +657,8 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
@@ -679,17 +679,21 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
 golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
-golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
-golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
+golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
+golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
+golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
+golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
+golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
+golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
 google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
@@ -697,8 +701,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
+google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
diff --git a/tools/rw-heatmaps/go.mod b/tools/rw-heatmaps/go.mod
index a6d1a139c..c1137f51e 100644
--- a/tools/rw-heatmaps/go.mod
+++ b/tools/rw-heatmaps/go.mod
@@ -2,7 +2,7 @@ module go.etcd.io/etcd/tools/rw-heatmaps/v3
 
 go 1.24
 
-toolchain go1.24.5
+toolchain go1.24.6
 
 require (
 	github.com/spf13/cobra v1.9.1
@@ -22,5 +22,5 @@ require (
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
 	golang.org/x/image v0.18.0 // indirect
-	golang.org/x/text v0.27.0 // indirect
+	golang.org/x/text v0.28.0 // indirect
 )
diff --git a/tools/rw-heatmaps/go.sum b/tools/rw-heatmaps/go.sum
index f2f146b46..ec10b648a 100644
--- a/tools/rw-heatmaps/go.sum
+++ b/tools/rw-heatmaps/go.sum
@@ -54,8 +54,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
diff --git a/tools/testgrid-analysis/go.mod b/tools/testgrid-analysis/go.mod
index b2c9a2988..ad8cf9dc5 100644
--- a/tools/testgrid-analysis/go.mod
+++ b/tools/testgrid-analysis/go.mod
@@ -2,13 +2,13 @@ module go.etcd.io/etcd/tools/testgrid-analysis/v3
 
 go 1.24
 
-toolchain go1.24.5
+toolchain go1.24.6
 
 require (
 	github.com/GoogleCloudPlatform/testgrid v0.0.173
 	github.com/google/go-github/v60 v60.0.0
 	github.com/spf13/cobra v1.9.1
-	google.golang.org/protobuf v1.36.6
+	google.golang.org/protobuf v1.36.7
 )
 
 require (
@@ -17,9 +17,9 @@ require (
 	github.com/spf13/pflag v1.0.7 // indirect
 	go.opentelemetry.io/otel v1.37.0 // indirect
 	go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
-	golang.org/x/net v0.42.0 // indirect
-	golang.org/x/sys v0.34.0 // indirect
-	golang.org/x/text v0.27.0 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
+	golang.org/x/net v0.43.0 // indirect
+	golang.org/x/sys v0.35.0 // indirect
+	golang.org/x/text v0.28.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
 	google.golang.org/grpc v1.74.2 // indirect
 )
diff --git a/tools/testgrid-analysis/go.sum b/tools/testgrid-analysis/go.sum
index 793995d68..1dc969658 100644
--- a/tools/testgrid-analysis/go.sum
+++ b/tools/testgrid-analysis/go.sum
@@ -1337,8 +1337,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
-golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
-golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1485,8 +1485,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1517,8 +1517,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1842,8 +1842,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.
 google.golang.org/genproto/googleapis/rpc v0.0.0-20230720185612-659f7aaaa771/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1910,8 +1910,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
+google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=



Test patch:
diff --git a/cache/cache_test.go b/cache/cache_test.go
index 3093d49fb..ec846b61d 100644
--- a/cache/cache_test.go
+++ b/cache/cache_test.go
@@ -16,6 +16,7 @@ package cache
 
 import (
 	"context"
+	"sync"
 	"testing"
 	"time"
 
@@ -191,7 +192,7 @@ func TestCacheWatchAtomicOrderedDelivery(t *testing.T) {
 			mw := newMockWatcher(16)
 			fakeClient := &clientv3.Client{
 				Watcher: mw,
-				KV:      &kvStub{},
+				KV:      newKVStub(),
 			}
 			cache, err := New(fakeClient, "")
 			if err != nil {
@@ -398,35 +399,115 @@ func TestValidateWatchRange(t *testing.T) {
 	}
 }
 
-type mockWatcher struct {
-	responses  chan clientv3.WatchResponse
-	registered chan struct{}
-}
+func TestCacheCompactionResync(t *testing.T) {
+	firstSnapshot := &clientv3.GetResponse{
+		Header: &pb.ResponseHeader{Revision: 5},
+		Kvs: []*mvccpb.KeyValue{
+			{Key: []byte("foo"), Value: []byte("old_value"), ModRevision: 5, CreateRevision: 5, Version: 1},
+			{Key: []byte("bar"), Value: []byte("old_bar"), ModRevision: 3, CreateRevision: 3, Version: 1},
+		},
+	}
+	secondSnapshot := &clientv3.GetResponse{
+		Header: &pb.ResponseHeader{Revision: 20},
+		Kvs: []*mvccpb.KeyValue{
+			{Key: []byte("foo"), Value: []byte("new_value"), ModRevision: 20, CreateRevision: 5, Version: 2},
+			{Key: []byte("baz"), Value: []byte("new_baz"), ModRevision: 18, CreateRevision: 18, Version: 1},
+		},
+	}
+	fakeClient := &clientv3.Client{
+		Watcher: newMockWatcher(16),
+		KV:      newKVStub(firstSnapshot, secondSnapshot),
+	}
+	cache, err := New(fakeClient, "")
+	if err != nil {
+		t.Fatalf("New cache: %v", err)
+	}
+	defer cache.Close()
+	mw := fakeClient.Watcher.(*mockWatcher)
 
-type kvStub struct{}
+	t.Log("Phase 1: initial getWatch bootstrap")
+	mw.triggerCreatedNotify()
+	<-mw.registered
+	if err = cache.WaitReady(t.Context()); err != nil {
+		t.Fatalf("initial WaitReady: %v", err)
+	}
+	verifySnapshot(t, cache, []*mvccpb.KeyValue{
+		{Key: []byte("bar"), Value: []byte("old_bar"), ModRevision: 3, CreateRevision: 3, Version: 1},
+		{Key: []byte("foo"), Value: []byte("old_value"), ModRevision: 5, CreateRevision: 5, Version: 1},
+	})
 
-func (kvStub) Get(ctx context.Context, key string, _ ...clientv3.OpOption) (*clientv3.GetResponse, error) {
-	return &clientv3.GetResponse{Header: &pb.ResponseHeader{Revision: 0}}, nil
-}
+	t.Log("Phase 2: simulate compaction")
+	mw.errorCompacted(10)
 
-func (kvStub) Put(ctx context.Context, key, val string, _ ...clientv3.OpOption) (*clientv3.PutResponse, error) {
-	return nil, nil
-}
+	waitUntil(t, time.Second, 10*time.Millisecond, func() bool { return !cache.Ready() })
+	start := time.Now()
 
-func (kvStub) Delete(ctx context.Context, key string, _ ...clientv3.OpOption) (*clientv3.DeleteResponse, error) {
-	return nil, nil
-}
+	ctxGet, cancelGet := context.WithTimeout(t.Context(), 100*time.Millisecond)
+	defer cancelGet()
+	snapshot, err := cache.Get(ctxGet, "foo", clientv3.WithSerializable())
+	if err != nil {
+		t.Fatalf("expected Get() to serve from cached snapshot after compaction, got %v", err)
+	}
+	if got := snapshot.Header.Revision; got != firstSnapshot.Header.Revision {
+		t.Fatalf("expected cached revision %d after compaction, got %d", firstSnapshot.Header.Revision, got)
+	}
+	if string(snapshot.Kvs[0].Value) != "old_value" {
+		t.Fatalf("expected cached value 'old_value' during compaction, got %q", string(snapshot.Kvs[0].Value))
+	}
 
-func (kvStub) Compact(ctx context.Context, rev int64, _ ...clientv3.CompactOption) (*clientv3.CompactResponse, error) {
-	return nil, nil
+	t.Log("Phase 3: resync after compaction")
+	mw.triggerCreatedNotify()
+	if err = cache.WaitReady(t.Context()); err != nil {
+		t.Fatalf("second WaitReady: %v", err)
+	}
+	elapsed := time.Since(start)
+	if elapsed > time.Second {
+		t.Fatalf("cache was unready for %v; want:  < 1 s", elapsed)
+	}
+
+	expectSnapshotRev := int64(20)
+	expectedWatchStart := secondSnapshot.Header.Revision + 1
+	if gotWatchStart := mw.lastStartRev; gotWatchStart != expectedWatchStart {
+		t.Errorf("Watch started at rev=%d; want %d", gotWatchStart, expectedWatchStart)
+	}
+
+	ctx, cancel := context.WithTimeout(t.Context(), time.Second)
+	defer cancel()
+	if err = cache.WaitForRevision(ctx, expectSnapshotRev); err != nil {
+		t.Fatalf("cache never reached rev=%d: %v", expectSnapshotRev, err)
+	}
+
+	gotSnapshot, err := cache.Get(t.Context(), "foo", clientv3.WithSerializable())
+	if err != nil {
+		t.Fatalf("Get after resync: %v", err)
+	}
+	if gotSnapshot.Header.Revision != expectSnapshotRev {
+		t.Errorf("unexpected Snapshot revision: got=%d, want=%d", gotSnapshot.Header.Revision, expectSnapshotRev)
+	}
+	verifySnapshot(t, cache, []*mvccpb.KeyValue{
+		{Key: []byte("baz"), Value: []byte("new_baz"), ModRevision: 18, CreateRevision: 18, Version: 1},
+		{Key: []byte("foo"), Value: []byte("new_value"), ModRevision: 20, CreateRevision: 5, Version: 2},
+	})
 }
 
-func (kvStub) Do(ctx context.Context, op clientv3.Op) (clientv3.OpResponse, error) {
-	return clientv3.OpResponse{}, nil
+func waitUntil(t *testing.T, timeout, poll time.Duration, cond func() bool) {
+	deadline := time.Now().Add(timeout)
+	for time.Now().Before(deadline) {
+		if cond() {
+			return
+		}
+		time.Sleep(poll)
+	}
+	t.Fatalf("condition not satisfied within %s", timeout)
 }
 
-func (kvStub) Txn(ctx context.Context) clientv3.Txn {
-	return nil
+type mockWatcher struct {
+	responses    chan clientv3.WatchResponse
+	registered   chan struct{}
+	closeOnce    sync.Once
+	wg           sync.WaitGroup
+	mu           sync.Mutex
+	lastStartRev int64
 }
 
 func newMockWatcher(buf int) *mockWatcher {
@@ -436,18 +517,120 @@ func newMockWatcher(buf int) *mockWatcher {
 	}
 }
 
-func (m *mockWatcher) Watch(_ context.Context, _ string, _ ...clientv3.OpOption) clientv3.WatchChan {
-	select { // prevent “close of closed channel” on re-watch
+func (m *mockWatcher) Watch(ctx context.Context, _ string, opts ...clientv3.OpOption) clientv3.WatchChan {
+	rev := m.extractRev(opts)
+	m.recordStartRev(rev)
+
+	m.signalRegistration()
+
+	out := make(chan clientv3.WatchResponse)
+	m.wg.Add(1)
+	go m.streamResponses(ctx, out)
+	return out
+}
+
+func (m *mockWatcher) RequestProgress(_ context.Context) error { return nil }
+
+func (m *mockWatcher) Close() error {
+	m.closeOnce.Do(func() { close(m.responses) })
+	m.wg.Wait()
+	return nil
+}
+
+func (m *mockWatcher) triggerCreatedNotify() { m.responses <- clientv3.WatchResponse{} }
+
+func (m *mockWatcher) errorCompacted(compRev int64) {
+	m.responses <- clientv3.WatchResponse{
+		Canceled:        true,
+		CompactRevision: compRev,
+	}
+}
+
+func (m *mockWatcher) extractRev(opts []clientv3.OpOption) int64 {
+	var op clientv3.Op
+	for _, o := range opts {
+		o(&op)
+	}
+	return op.Rev()
+}
+
+func (m *mockWatcher) recordStartRev(rev int64) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	m.lastStartRev = rev
+}
+
+func (m *mockWatcher) signalRegistration() {
+	select {
 	case <-m.registered:
 	default:
 		close(m.registered)
 	}
-	return m.responses
 }
 
-func (m *mockWatcher) RequestProgress(_ context.Context) error { return nil }
+func (m *mockWatcher) streamResponses(ctx context.Context, out chan<- clientv3.WatchResponse) {
+	defer func() {
+		close(out)
+		m.wg.Done()
+	}()
+
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case resp, ok := <-m.responses:
+			if !ok {
+				return
+			}
+			out <- resp
+			if resp.Canceled {
+				return
+			}
+		}
+	}
+}
+
+type kvStub struct {
+	queued      []*clientv3.GetResponse
+	defaultResp *clientv3.GetResponse
+}
+
+func newKVStub(resps ...*clientv3.GetResponse) *kvStub {
+	queue := append([]*clientv3.GetResponse(nil), resps...)
+	return &kvStub{
+		queued:      queue,
+		defaultResp: &clientv3.GetResponse{Header: &pb.ResponseHeader{Revision: 0}},
+	}
+}
 
-func (m *mockWatcher) Close() error { close(m.responses); return nil }
+func (s *kvStub) Get(ctx context.Context, key string, _ ...clientv3.OpOption) (*clientv3.GetResponse, error) {
+	if len(s.queued) > 0 {
+		next := s.queued[0]
+		s.queued = s.queued[1:]
+		return next, nil
+	}
+	return s.defaultResp, nil
+}
+
+func (s *kvStub) Put(ctx context.Context, key, val string, _ ...clientv3.OpOption) (*clientv3.PutResponse, error) {
+	return nil, nil
+}
+
+func (s *kvStub) Delete(ctx context.Context, key string, _ ...clientv3.OpOption) (*clientv3.DeleteResponse, error) {
+	return nil, nil
+}
+
+func (s *kvStub) Compact(ctx context.Context, rev int64, _ ...clientv3.CompactOption) (*clientv3.CompactResponse, error) {
+	return nil, nil
+}
+
+func (s *kvStub) Do(ctx context.Context, op clientv3.Op) (clientv3.OpResponse, error) {
+	return clientv3.OpResponse{}, nil
+}
+
+func (s *kvStub) Txn(ctx context.Context) clientv3.Txn {
+	return nil
+}
 
 func event(eventType mvccpb.Event_EventType, key string, rev int64) *clientv3.Event {
 	return &clientv3.Event{
@@ -491,3 +674,14 @@ func collectAndAssertAtomicEvents(ctx context.Context, t *testing.T, watchCh cli
 		}
 	}
 }
+
+func verifySnapshot(t *testing.T, cache *Cache, want []*mvccpb.KeyValue) {
+	resp, err := cache.Get(t.Context(), "", clientv3.WithPrefix(), clientv3.WithSerializable())
+	if err != nil {
+		t.Fatalf("Get all keys: %v", err)
+	}
+
+	if diff := cmp.Diff(want, resp.Kvs); diff != "" {
+		t.Fatalf("cache snapshot mismatch (-want +got):\n%s", diff)
+	}
+}
diff --git a/cache/ready_test.go b/cache/ready_test.go
new file mode 100644
index 000000000..ae59caab2
--- /dev/null
+++ b/cache/ready_test.go
@@ -0,0 +1,195 @@
+// Copyright 2025 The etcd Authors
+//
+// 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 cache
+
+import (
+	"context"
+	"errors"
+	"sync"
+	"testing"
+	"time"
+)
+
+func TestWaitMethods(t *testing.T) {
+	tests := []struct {
+		name         string
+		initialReady bool
+		expectReady  bool
+		waitMethod   string
+		expectBlock  bool
+	}{
+		{
+			name:         "not_ready_testing_WaitNotReady",
+			initialReady: false,
+			expectReady:  false,
+			waitMethod:   "not_ready",
+			expectBlock:  false,
+		},
+		{
+			name:         "not_ready_testing_WaitReady",
+			initialReady: false,
+			expectReady:  false,
+			waitMethod:   "ready",
+			expectBlock:  true,
+		},
+		{
+			name:         "ready_testing_WaitReady",
+			initialReady: true,
+			expectReady:  true,
+			waitMethod:   "ready",
+			expectBlock:  false,
+		},
+		{
+			name:         "ready_testing_WaitNotReady",
+			initialReady: true,
+			expectReady:  true,
+			waitMethod:   "not_ready",
+			expectBlock:  true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			r := newReady()
+
+			if tt.initialReady {
+				r.Set()
+			}
+
+			if got := r.Ready(); got != tt.expectReady {
+				t.Fatalf("Ready() = %t; want %t", got, tt.expectReady)
+			}
+
+			ctx, cancel := context.WithTimeout(t.Context(), 100*time.Millisecond)
+			defer cancel()
+
+			var err error
+			switch tt.waitMethod {
+			case "ready":
+				err = r.WaitReady(ctx)
+			case "not_ready":
+				err = r.WaitNotReady(ctx)
+			default:
+				t.Fatalf("invalid waitMethod: %s", tt.waitMethod)
+			}
+
+			if tt.expectBlock {
+				if !errors.Is(err, context.DeadlineExceeded) {
+					t.Fatalf("expected timeout but got: %v", err)
+				}
+			} else {
+				if err != nil {
+					t.Fatalf("expected immediate return but got error: %v", err)
+				}
+			}
+		})
+	}
+}
+
+func TestSetUnblocksWaiters(t *testing.T) {
+	testStateTransitionUnblocksWaiters(t, false, true, (*ready).Set, true, "WaitReady")
+}
+
+func TestResetUnblocksWaiters(t *testing.T) {
+	testStateTransitionUnblocksWaiters(t, true, false, (*ready).Reset, false, "WaitNotReady")
+}
+
+func testStateTransitionUnblocksWaiters(t *testing.T, initialSet bool, waitForReady bool, transition func(*ready), expectedReady bool, waitMethodName string) {
+	cases := []struct {
+		name string
+		n    int
+	}{
+		{"one_waiter", 1},
+		{"several_waiters", 16},
+		{"many_waiters", 128},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			r := newReady()
+			if initialSet {
+				r.Set()
+			}
+
+			var startWg sync.WaitGroup
+			var readyWg sync.WaitGroup
+
+			errs := make(chan error, tc.n)
+			for i := 0; i < tc.n; i++ {
+				startWg.Add(1)
+				readyWg.Add(1)
+				go func() {
+					defer readyWg.Done()
+					startWg.Done()
+
+					ctx, cancel := context.WithTimeout(t.Context(), time.Second)
+					defer cancel()
+
+					if waitForReady {
+						errs <- r.WaitReady(ctx)
+					} else {
+						errs <- r.WaitNotReady(ctx)
+					}
+				}()
+			}
+			startWg.Wait()
+			time.Sleep(50 * time.Millisecond)
+			transition(r)
+			readyWg.Wait()
+
+			for i := 0; i < tc.n; i++ {
+				if err := <-errs; err != nil {
+					t.Fatalf("waiter %d: %s = %v; want: nil", i, waitMethodName, err)
+				}
+			}
+
+			if r.Ready() != expectedReady {
+				t.Fatalf("Ready() = %t after transition; want %t", r.Ready(), expectedReady)
+			}
+
+			if waitForReady {
+				if err := r.WaitReady(t.Context()); err != nil {
+					t.Fatalf("immediate WaitReady() after transition = %v; want: nil", err)
+				}
+			} else {
+				if err := r.WaitNotReady(t.Context()); err != nil {
+					t.Fatalf("immediate WaitNotReady() after transition = %v; want: nil", err)
+				}
+			}
+		})
+	}
+}
+
+func TestIdempotentStateTransitions(t *testing.T) {
+	r := newReady()
+
+	r.Set()
+	r.Set()
+	if !r.Ready() {
+		t.Fatalf("Ready() = false after double Set(); want: true")
+	}
+	if err := r.WaitReady(t.Context()); err != nil {
+		t.Fatalf("WaitReady() after double Set() = %v; want: nil", err)
+	}
+
+	r.Reset()
+	r.Reset()
+	if r.Ready() {
+		t.Fatalf("Ready() = true after double Reset(); want: false")
+	}
+	if err := r.WaitNotReady(t.Context()); err != nil {
+		t.Fatalf("WaitNotReady() after double Reset() = %v; want <nil>", err)
+	}
+}
diff --git a/cache/ringbuffer_test.go b/cache/ringbuffer_test.go
index 0e7e3b010..f4ce80762 100644
--- a/cache/ringbuffer_test.go
+++ b/cache/ringbuffer_test.go
@@ -65,7 +65,7 @@ func TestPeekLatestAndOldest(t *testing.T) {
 	for _, tt := range tests {
 		tt := tt
 		t.Run(tt.name, func(t *testing.T) {
-			rb := newRingBuffer(tt.capacity)
+			rb := newRingBuffer(tt.capacity, func(ev *clientv3.Event) int64 { return ev.Kv.ModRevision })
 			for _, r := range tt.revs {
 				batch, err := makeEventBatch(r, "k", 1)
 				if err != nil {
@@ -90,82 +90,259 @@ func TestPeekLatestAndOldest(t *testing.T) {
 	}
 }
 
-func TestFilter(t *testing.T) {
+func TestIterationMethods(t *testing.T) {
+	type iterTestCase struct {
+		method            iterMethod
+		pivot             int64
+		wantIterRevisions []int64
+	}
 	tests := []struct {
-		name             string
-		capacity         int
-		revs             []int64
-		minRev           int64
-		wantFilteredRevs []int64
-		wantLatestRev    int64
+		name           string
+		capacity       int
+		setupRevisions []int64
+		cases          []iterTestCase
 	}{
 		{
-			name:             "no_filter",
-			capacity:         5,
-			revs:             []int64{1, 2, 3},
-			minRev:           0,
-			wantFilteredRevs: []int64{1, 2, 3},
-			wantLatestRev:    3,
+			name:           "empty_buffer",
+			capacity:       4,
+			setupRevisions: nil,
+			cases: []iterTestCase{
+				{ascendGTE, 0, []int64{}},
+				{ascendLT, 10, []int64{}},
+				{descendGT, 0, []int64{}},
+				{descendLTE, 10, []int64{}},
+			},
 		},
 		{
-			name:             "partial_match",
-			capacity:         5,
-			revs:             []int64{10, 11, 12, 13},
-			minRev:           12,
-			wantFilteredRevs: []int64{12, 13},
-			wantLatestRev:    13,
+			name:           "basic_filtering",
+			capacity:       5,
+			setupRevisions: []int64{1, 2, 3},
+			cases: []iterTestCase{
+				{ascendGTE, 0, []int64{1, 2, 3}},
+				{ascendGTE, 2, []int64{2, 3}},
+				{ascendGTE, 100, []int64{}},
+				{ascendLT, 3, []int64{1, 2}},
+				{ascendLT, 1, []int64{}},
+				{ascendLT, 100, []int64{1, 2, 3}},
+				{descendGT, 1, []int64{3, 2}},
+				{descendGT, 3, []int64{}},
+				{descendGT, 0, []int64{3, 2, 1}},
+				{descendLTE, 2, []int64{2, 1}},
+				{descendLTE, 3, []int64{3, 2, 1}},
+				{descendLTE, 0, []int64{}},
+			},
 		},
 		{
-			name:             "filter_when_full",
-			capacity:         3,
-			revs:             []int64{20, 21, 22, 23, 24},
-			minRev:           23,
-			wantFilteredRevs: []int64{23, 24},
-			wantLatestRev:    24,
+			name:           "overflowed stores only entries within capacity",
+			capacity:       3,
+			setupRevisions: []int64{20, 21, 22, 23, 24}, // stored: 22, 23, 24
+			cases: []iterTestCase{
+				{ascendGTE, 23, []int64{23, 24}},
+				{ascendGTE, 0, []int64{22, 23, 24}},
+				{ascendLT, 23, []int64{22}},
+				{ascendLT, 25, []int64{22, 23, 24}},
+				{descendGT, 22, []int64{24, 23}},
+				{descendGT, 25, []int64{}},
+				{descendLTE, 23, []int64{23, 22}},
+				{descendLTE, 24, []int64{24, 23, 22}},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			rb := setupRingBuffer(t, tt.capacity, tt.setupRevisions)
+
+			for _, tc := range tt.cases {
+				tc := tc
+				t.Run(fmt.Sprintf("%s_pivot_%d", tc.method, tc.pivot), func(t *testing.T) {
+					got := collectRevisions(rb, tc.method, tc.pivot)
+					if diff := cmp.Diff(tc.wantIterRevisions, got); diff != "" {
+						t.Fatalf("%s(%d) mismatch (-want +got):\n%s", tc.method, tc.pivot, diff)
+					}
+				})
+			}
+		})
+	}
+}
+
+func TestIterationWithBatching(t *testing.T) {
+	rb := newRingBuffer(6, func(ev *clientv3.Event) int64 { return ev.Kv.ModRevision })
+	events := []*clientv3.Event{
+		{Kv: &mvccpb.KeyValue{Key: []byte("key-a"), ModRevision: 5}},
+		{Kv: &mvccpb.KeyValue{Key: []byte("key-b-1"), ModRevision: 10}},
+		{Kv: &mvccpb.KeyValue{Key: []byte("key-b-2"), ModRevision: 10}},
+		{Kv: &mvccpb.KeyValue{Key: []byte("key-b-3"), ModRevision: 10}},
+		{Kv: &mvccpb.KeyValue{Key: []byte("key-c"), ModRevision: 12}},
+	}
+
+	rb.Append(events)
+
+	tests := []struct {
+		name   string
+		method iterMethod
+		pivot  int64
+		want   [][]*clientv3.Event
+	}{
+		{
+			name:   "ascending_gte_includes_batched_revision",
+			method: ascendGTE,
+			pivot:  10,
+			want: [][]*clientv3.Event{
+				{
+					{Kv: &mvccpb.KeyValue{Key: []byte("key-b-1"), ModRevision: 10}},
+					{Kv: &mvccpb.KeyValue{Key: []byte("key-b-2"), ModRevision: 10}},
+					{Kv: &mvccpb.KeyValue{Key: []byte("key-b-3"), ModRevision: 10}},
+				},
+				{
+					{Kv: &mvccpb.KeyValue{Key: []byte("key-c"), ModRevision: 12}},
+				},
+			},
 		},
 		{
-			name:             "none_match",
-			capacity:         4,
-			revs:             []int64{30, 31},
-			minRev:           100,
-			wantFilteredRevs: []int64{},
-			wantLatestRev:    31,
+			name:   "ascending_lt_stops_before_batched_revision",
+			method: ascendLT,
+			pivot:  10,
+			want: [][]*clientv3.Event{
+				{
+					{Kv: &mvccpb.KeyValue{Key: []byte("key-a"), ModRevision: 5}},
+				},
+			},
 		},
 		{
-			name:             "empty_buffer",
-			capacity:         3,
-			revs:             nil,
-			minRev:           0,
-			wantFilteredRevs: []int64{},
-			wantLatestRev:    0,
+			name:   "all_revisions_with_proper_batch_sizes",
+			method: ascendGTE,
+			pivot:  0,
+			want: [][]*clientv3.Event{
+				{
+					{Kv: &mvccpb.KeyValue{Key: []byte("key-a"), ModRevision: 5}},
+				},
+				{
+					{Kv: &mvccpb.KeyValue{Key: []byte("key-b-1"), ModRevision: 10}},
+					{Kv: &mvccpb.KeyValue{Key: []byte("key-b-2"), ModRevision: 10}},
+					{Kv: &mvccpb.KeyValue{Key: []byte("key-b-3"), ModRevision: 10}},
+				},
+				{
+					{Kv: &mvccpb.KeyValue{Key: []byte("key-c"), ModRevision: 12}},
+				},
+			},
 		},
 	}
 
 	for _, tt := range tests {
 		tt := tt
 		t.Run(tt.name, func(t *testing.T) {
-			rb := newRingBuffer(tt.capacity)
-			for _, r := range tt.revs {
-				batch, err := makeEventBatch(r, "k", 11)
-				if err != nil {
-					t.Fatalf("makeEventBatch(%d, k, 11) failed: %v", r, err)
-				}
-				rb.Append(batch)
+			var got [][]*clientv3.Event
+
+			rb.iterate(tt.method, tt.pivot, func(rev int64, events []*clientv3.Event) bool {
+				got = append(got, events)
+				return true
+			})
+
+			if diff := cmp.Diff(tt.want, got); diff != "" {
+				t.Fatalf("Events mismatch (-want +got):\n%s", diff)
 			}
+		})
+	}
+}
 
-			gotBatches := rb.Filter(tt.minRev)
-			gotRevs := make([]int64, len(gotBatches))
-			for i, b := range gotBatches {
-				gotRevs[i] = b[0].Kv.ModRevision
+func TestIterationEarlyStop(t *testing.T) {
+	rb := setupRingBuffer(t, 5, []int64{5, 10, 15, 20})
+	tests := []struct {
+		name      string
+		method    iterMethod
+		pivot     int64
+		stopAfter int
+		want      []int64
+	}{
+		{
+			name:      "find_first_match_ascending",
+			method:    ascendGTE,
+			pivot:     10,
+			stopAfter: 1,
+			want:      []int64{10},
+		},
+		{
+			name:      "find_first_two_ascending_lt",
+			method:    ascendLT,
+			pivot:     20,
+			stopAfter: 2,
+			want:      []int64{5, 10},
+		},
+		{
+			name:      "find_first_two_descending_gt",
+			method:    descendGT,
+			pivot:     5,
+			stopAfter: 2,
+			want:      []int64{20, 15},
+		},
+		{
+			name:      "find_first_match_descending_lte",
+			method:    descendLTE,
+			pivot:     15,
+			stopAfter: 1,
+			want:      []int64{15},
+		},
+	}
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			var collected []int64
+			callCount := 0
+
+			rb.iterate(tt.method, tt.pivot, func(rev int64, events []*clientv3.Event) bool {
+				collected = append(collected, rev)
+				callCount++
+
+				shouldContinue := callCount < tt.stopAfter
+
+				if !shouldContinue {
+					t.Logf("Stopping early after %d items (callback returned false)", callCount)
+				}
+
+				return shouldContinue
+			})
+
+			if diff := cmp.Diff(tt.want, collected); diff != "" {
+				t.Fatalf("Early stop failed.\nExpected: \nDiff (-want +got):\n%s", diff)
 			}
 
-			if diff := cmp.Diff(tt.wantFilteredRevs, gotRevs); diff != "" {
-				t.Fatalf("Filter() revisions mismatch (-want +got):\n%s", diff)
+			if callCount != tt.stopAfter {
+				t.Fatalf("Expected exactly %d callback calls, got %d", tt.stopAfter, callCount)
 			}
+
+			t.Logf("Successfully stopped early: collected %v after %d callbacks",
+				collected, callCount)
 		})
 	}
 }
 
+type iterMethod string
+
+const (
+	ascendGTE  iterMethod = "AscendGreaterOrEqual"
+	ascendLT   iterMethod = "AscendLessThan"
+	descendGT  iterMethod = "DescendGreaterThan"
+	descendLTE iterMethod = "DescendLessOrEqual"
+)
+
+func (r *ringBuffer[T]) iterate(method iterMethod, pivot int64, fn IterFunc[T]) {
+	switch method {
+	case ascendGTE:
+		r.AscendGreaterOrEqual(pivot, fn)
+	case ascendLT:
+		r.AscendLessThan(pivot, fn)
+	case descendGT:
+		r.DescendGreaterThan(pivot, fn)
+	case descendLTE:
+		r.DescendLessOrEqual(pivot, fn)
+	default:
+		panic(fmt.Sprintf("unknown iteration method: %s", method))
+	}
+}
+
 func TestAtomicOrdered(t *testing.T) {
 	tests := []struct {
 		name     string
@@ -217,7 +394,7 @@ func TestAtomicOrdered(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
 
-			rb := newRingBuffer(tt.capacity)
+			rb := newRingBuffer(tt.capacity, func(ev *clientv3.Event) int64 { return ev.Kv.ModRevision })
 			for _, in := range tt.inputs {
 				batch, err := makeEventBatch(in.rev, in.key, in.size)
 				if err != nil {
@@ -226,16 +403,23 @@ func TestAtomicOrdered(t *testing.T) {
 				rb.Append(batch)
 			}
 
-			gotBatches := rb.Filter(0)
-			if len(gotBatches) != len(tt.wantRev) {
-				t.Fatalf("len(got) = %d, want %d", len(gotBatches), len(tt.wantRev))
+			gotRevs := []int64{}
+			var gotSizes []int
+			rb.AscendGreaterOrEqual(0, func(rev int64, events []*clientv3.Event) bool {
+				gotRevs = append(gotRevs, rev)
+				gotSizes = append(gotSizes, len(events))
+				return true
+			})
+
+			if len(gotRevs) != len(tt.wantRev) {
+				t.Fatalf("len(got) = %d, want %d", len(gotRevs), len(tt.wantRev))
 			}
-			for i, b := range gotBatches {
-				if b[0].Kv.ModRevision != tt.wantRev[i] {
-					t.Errorf("at idx %d: rev = %d, want %d", i, b[0].Kv.ModRevision, tt.wantRev[i])
+			for i := range gotRevs {
+				if gotRevs[i] != tt.wantRev[i] {
+					t.Errorf("at idx %d: rev = %d, want %d", i, gotRevs[i], tt.wantRev[i])
 				}
-				if batchSize := len(b); batchSize != tt.wantSize[i] {
-					t.Errorf("at rev %d: events.len = %d, want %d", b[0].Kv.ModRevision, batchSize, tt.wantSize[i])
+				if gotSizes[i] != tt.wantSize[i] {
+					t.Errorf("at rev %d: events.len = %d, want %d", gotRevs[i], gotSizes[i], tt.wantSize[i])
 				}
 			}
 		})
@@ -261,7 +445,7 @@ func TestRebaseHistory(t *testing.T) {
 		tt := tt
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
-			rb := newRingBuffer(4)
+			rb := newRingBuffer(4, func(ev *clientv3.Event) int64 { return ev.Kv.ModRevision })
 			for _, r := range tt.revs {
 				batch, err := makeEventBatch(r, "k", 1)
 				if err != nil {
@@ -273,7 +457,6 @@ func TestRebaseHistory(t *testing.T) {
 			rb.RebaseHistory()
 
 			oldestRev := rb.PeekOldest()
-
 			latestRev := rb.PeekLatest()
 
 			if oldestRev != 0 {
@@ -283,14 +466,40 @@ func TestRebaseHistory(t *testing.T) {
 				t.Fatalf("PeekLatest()=%d, want=%d", latestRev, 0)
 			}
 
-			batches := rb.Filter(0)
-			if len(batches) != 0 {
-				t.Fatalf("Filter() len(events)=%d, want=%d", len(batches), 0)
+			gotRevs := []int64{}
+			rb.AscendGreaterOrEqual(0, func(rev int64, events []*clientv3.Event) bool {
+				gotRevs = append(gotRevs, rev)
+				return true
+			})
+
+			if len(gotRevs) != 0 {
+				t.Fatalf("AscendGreaterOrEqual() len(events)=%d, want=%d", len(gotRevs), 0)
 			}
 		})
 	}
 }
 
+func setupRingBuffer(t *testing.T, capacity int, revs []int64) *ringBuffer[*clientv3.Event] {
+	rb := newRingBuffer(capacity, func(ev *clientv3.Event) int64 { return ev.Kv.ModRevision })
+	for _, r := range revs {
+		batch, err := makeEventBatch(r, "key", 1)
+		if err != nil {
+			t.Fatalf("makeEventBatch(%d, %s, %d) failed: %v", r, "key", 1, err)
+		}
+		rb.Append(batch)
+	}
+	return rb
+}
+
+func collectRevisions(rb *ringBuffer[*clientv3.Event], method iterMethod, pivot int64) []int64 {
+	revs := []int64{}
+	rb.iterate(method, pivot, func(rev int64, events []*clientv3.Event) bool {
+		revs = append(revs, rev)
+		return true
+	})
+	return revs
+}
+
 func makeEventBatch(rev int64, key string, batchSize int) ([]*clientv3.Event, error) {
 	if batchSize < 0 {
 		return nil, fmt.Errorf("invalid batchSize %d", batchSize)
diff --git a/server/storage/mvcc/kv_test.go b/server/storage/mvcc/kv_test.go
index 1b2bf6ae5..734868a23 100644
--- a/server/storage/mvcc/kv_test.go
+++ b/server/storage/mvcc/kv_test.go
@@ -779,7 +779,7 @@ func TestWatchableKVWatch(t *testing.T) {
 	w := s.NewWatchStream()
 	defer w.Close()
 
-	wid, _ := w.Watch(0, []byte("foo"), []byte("fop"), 0)
+	wid, _ := w.Watch(t.Context(), 0, []byte("foo"), []byte("fop"), 0)
 
 	wev := []mvccpb.Event{
 		{
@@ -847,7 +847,7 @@ func TestWatchableKVWatch(t *testing.T) {
 	}
 
 	w = s.NewWatchStream()
-	wid, _ = w.Watch(0, []byte("foo1"), []byte("foo2"), 3)
+	wid, _ = w.Watch(t.Context(), 0, []byte("foo1"), []byte("foo2"), 3)
 
 	select {
 	case resp := <-w.Chan():
diff --git a/server/storage/mvcc/watchable_store_bench_test.go b/server/storage/mvcc/watchable_store_bench_test.go
index c8990576b..c1da715ee 100644
--- a/server/storage/mvcc/watchable_store_bench_test.go
+++ b/server/storage/mvcc/watchable_store_bench_test.go
@@ -94,7 +94,7 @@ func benchmarkWatchableStoreWatchPut(b *testing.B, synced bool) {
 	defer w.Close()
 	watchIDs := make([]WatchID, b.N)
 	for i := range watchIDs {
-		watchIDs[i], _ = w.Watch(0, k, nil, rev)
+		watchIDs[i], _ = w.Watch(b.Context(), 0, k, nil, rev)
 	}
 
 	b.ResetTimer()
@@ -144,7 +144,7 @@ func BenchmarkWatchableStoreUnsyncedCancel(b *testing.B) {
 	watchIDs := make([]WatchID, watcherN)
 	for i := 0; i < watcherN; i++ {
 		// non-0 value to keep watchers in unsynced
-		watchIDs[i], _ = w.Watch(0, testKey, nil, 1)
+		watchIDs[i], _ = w.Watch(b.Context(), 0, testKey, nil, 1)
 	}
 
 	// random-cancel N watchers to make it not biased towards
@@ -182,7 +182,7 @@ func BenchmarkWatchableStoreSyncedCancel(b *testing.B) {
 	watchIDs := make([]WatchID, watcherN)
 	for i := 0; i < watcherN; i++ {
 		// 0 for startRev to keep watchers in synced
-		watchIDs[i], _ = w.Watch(0, testKey, nil, 0)
+		watchIDs[i], _ = w.Watch(b.Context(), 0, testKey, nil, 0)
 	}
 
 	// randomly cancel watchers to make it not biased towards
diff --git a/server/storage/mvcc/watchable_store_test.go b/server/storage/mvcc/watchable_store_test.go
index d7b69adb9..8a3c6727c 100644
--- a/server/storage/mvcc/watchable_store_test.go
+++ b/server/storage/mvcc/watchable_store_test.go
@@ -46,7 +46,7 @@ func TestWatch(t *testing.T) {
 	w := s.NewWatchStream()
 	defer w.Close()
 
-	w.Watch(0, testKey, nil, 0)
+	w.Watch(t.Context(), 0, testKey, nil, 0)
 	if !s.(*watchableStore).synced.contains(string(testKey)) {
 		// the key must have had an entry in synced
 		t.Errorf("existence = false, want true")
@@ -65,7 +65,7 @@ func TestNewWatcherCancel(t *testing.T) {
 	w := s.NewWatchStream()
 	defer w.Close()
 
-	wt, _ := w.Watch(0, testKey, nil, 0)
+	wt, _ := w.Watch(t.Context(), 0, testKey, nil, 0)
 	if err := w.Cancel(wt); err != nil {
 		t.Error(err)
 	}
@@ -107,7 +107,7 @@ etcd_debugging_mvcc_watcher_total %d
 		w := s.NewWatchStream()
 		defer w.Close()
 
-		wt, _ := w.Watch(0, testKey, nil, 0)
+		wt, _ := w.Watch(t.Context(), 0, testKey, nil, 0)
 
 		// after creating watch, the gauge state should have increased
 		expectWatchGauge(initialGaugeState + 1)
@@ -152,7 +152,7 @@ etcd_debugging_mvcc_watcher_total %d
 		w := s.NewWatchStream()
 		defer w.Close()
 
-		wt, _ := w.Watch(0, testKey, nil, rev-1)
+		wt, _ := w.Watch(t.Context(), 0, testKey, nil, rev-1)
 
 		// wait for the watcher to be marked as compacted
 		select {
@@ -206,7 +206,7 @@ etcd_debugging_mvcc_watcher_total %d
 
 		w := s.NewWatchStream()
 
-		wt, _ := w.Watch(0, testKey, nil, rev-1)
+		wt, _ := w.Watch(t.Context(), 0, testKey, nil, rev-1)
 
 		// wait for the watcher to be marked as compacted
 		select {
@@ -274,7 +274,7 @@ func TestCancelUnsynced(t *testing.T) {
 	watchIDs := make([]WatchID, watcherN)
 	for i := 0; i < watcherN; i++ {
 		// use 1 to keep watchers in unsynced
-		watchIDs[i], _ = w.Watch(0, testKey, nil, 1)
+		watchIDs[i], _ = w.Watch(t.Context(), 0, testKey, nil, 1)
 	}
 
 	for _, idx := range watchIDs {
@@ -307,7 +307,7 @@ func TestSyncWatchers(t *testing.T) {
 	defer w.Close()
 	watcherN := 100
 	for i := 0; i < watcherN; i++ {
-		_, err := w.Watch(0, testKey, nil, 1)
+		_, err := w.Watch(t.Context(), 0, testKey, nil, 1)
 		require.NoError(t, err)
 	}
 
@@ -462,7 +462,7 @@ func TestWatchCompacted(t *testing.T) {
 	w := s.NewWatchStream()
 	defer w.Close()
 
-	wt, _ := w.Watch(0, testKey, nil, compactRev-1)
+	wt, _ := w.Watch(t.Context(), 0, testKey, nil, compactRev-1)
 	select {
 	case resp := <-w.Chan():
 		if resp.WatchID != wt {
@@ -508,7 +508,7 @@ func TestWatchNoEventLossOnCompact(t *testing.T) {
 		2: 6, // create unsyncd watchers with compactRev < startRev < currentRev
 	}
 	for id, startRev := range watchers {
-		_, err := w.Watch(id, testKey, nil, startRev)
+		_, err := w.Watch(t.Context(), id, testKey, nil, startRev)
 		require.NoError(t, err)
 	}
 	// fill up w.Chan() with 1 buf via 2 compacted watch response
@@ -547,7 +547,7 @@ func TestWatchFutureRev(t *testing.T) {
 	defer w.Close()
 
 	wrev := int64(10)
-	w.Watch(0, testKey, nil, wrev)
+	w.Watch(t.Context(), 0, testKey, nil, wrev)
 
 	for i := 0; i < 10; i++ {
 		rev := s.Put(testKey, testValue, lease.NoLease)
@@ -645,7 +645,7 @@ func testWatchRestore(t *testing.T, delayBeforeRestore, delayAfterRestore time.D
 		w := s.NewWatchStream()
 		defer w.Close()
 		watchers = append(watchers, w)
-		w.Watch(WatchID(i+1), testKey, nil, tc.startRevision)
+		w.Watch(t.Context(), WatchID(i+1), testKey, nil, tc.startRevision)
 	}
 
 	s.Put(testKey, testValue, lease.NoLease)
@@ -737,7 +737,7 @@ func TestWatchBatchUnsynced(t *testing.T) {
 			w := s.NewWatchStream()
 			defer w.Close()
 
-			w.Watch(0, v, nil, 1)
+			w.Watch(t.Context(), 0, v, nil, 1)
 			var revisionBatches [][]int64
 			eventCount := 0
 			for eventCount < tc.revisions*tc.eventsPerRevision {
@@ -882,7 +882,7 @@ func TestWatchVictims(t *testing.T) {
 	for i := 0; i < numWatches; i++ {
 		go func() {
 			w := s.NewWatchStream()
-			w.Watch(0, testKey, nil, 1)
+			w.Watch(t.Context(), 0, testKey, nil, 1)
 			defer func() {
 				w.Close()
 				wg.Done()
@@ -954,7 +954,7 @@ func TestStressWatchCancelClose(t *testing.T) {
 			w := s.NewWatchStream()
 			ids := make([]WatchID, 10)
 			for i := range ids {
-				ids[i], _ = w.Watch(0, testKey, nil, 0)
+				ids[i], _ = w.Watch(t.Context(), 0, testKey, nil, 0)
 			}
 			<-readyc
 			wg.Add(1 + len(ids)/2)
diff --git a/server/storage/mvcc/watcher_bench_test.go b/server/storage/mvcc/watcher_bench_test.go
index 3d0dccea3..be316fa83 100644
--- a/server/storage/mvcc/watcher_bench_test.go
+++ b/server/storage/mvcc/watcher_bench_test.go
@@ -36,6 +36,6 @@ func BenchmarkKVWatcherMemoryUsage(b *testing.B) {
 	b.ReportAllocs()
 	b.StartTimer()
 	for i := 0; i < b.N; i++ {
-		w.Watch(0, []byte(fmt.Sprint("foo", i)), nil, 0)
+		w.Watch(b.Context(), 0, []byte(fmt.Sprint("foo", i)), nil, 0)
 	}
 }
diff --git a/server/storage/mvcc/watcher_test.go b/server/storage/mvcc/watcher_test.go
index 9a9f4f13a..215bd53dc 100644
--- a/server/storage/mvcc/watcher_test.go
+++ b/server/storage/mvcc/watcher_test.go
@@ -44,7 +44,7 @@ func TestWatcherWatchID(t *testing.T) {
 	idm := make(map[WatchID]struct{})
 
 	for i := 0; i < 10; i++ {
-		id, _ := w.Watch(0, []byte("foo"), nil, 0)
+		id, _ := w.Watch(t.Context(), 0, []byte("foo"), nil, 0)
 		if _, ok := idm[id]; ok {
 			t.Errorf("#%d: id %d exists", i, id)
 		}
@@ -66,7 +66,7 @@ func TestWatcherWatchID(t *testing.T) {
 
 	// unsynced watchers
 	for i := 10; i < 20; i++ {
-		id, _ := w.Watch(0, []byte("foo2"), nil, 1)
+		id, _ := w.Watch(t.Context(), 0, []byte("foo2"), nil, 1)
 		if _, ok := idm[id]; ok {
 			t.Errorf("#%d: id %d exists", i, id)
 		}
@@ -107,7 +107,7 @@ func TestWatcherRequestsCustomID(t *testing.T) {
 	}
 
 	for i, tcase := range tt {
-		id, err := w.Watch(tcase.givenID, []byte("foo"), nil, 0)
+		id, err := w.Watch(t.Context(), tcase.givenID, []byte("foo"), nil, 0)
 		if tcase.expectedErr != nil || err != nil {
 			if !errors.Is(err, tcase.expectedErr) {
 				t.Errorf("expected get error %q in test case %q, got %q", tcase.expectedErr, i, err)
@@ -134,7 +134,7 @@ func TestWatcherWatchPrefix(t *testing.T) {
 	keyWatch, keyEnd, keyPut := []byte("foo"), []byte("fop"), []byte("foobar")
 
 	for i := 0; i < 10; i++ {
-		id, _ := w.Watch(0, keyWatch, keyEnd, 0)
+		id, _ := w.Watch(t.Context(), 0, keyWatch, keyEnd, 0)
 		if _, ok := idm[id]; ok {
 			t.Errorf("#%d: unexpected duplicated id %x", i, id)
 		}
@@ -166,7 +166,7 @@ func TestWatcherWatchPrefix(t *testing.T) {
 
 	// unsynced watchers
 	for i := 10; i < 15; i++ {
-		id, _ := w.Watch(0, keyWatch1, keyEnd1, 1)
+		id, _ := w.Watch(t.Context(), 0, keyWatch1, keyEnd1, 1)
 		if _, ok := idm[id]; ok {
 			t.Errorf("#%d: id %d exists", i, id)
 		}
@@ -202,14 +202,14 @@ func TestWatcherWatchWrongRange(t *testing.T) {
 	w := s.NewWatchStream()
 	defer w.Close()
 
-	if _, err := w.Watch(0, []byte("foa"), []byte("foa"), 1); !errors.Is(err, ErrEmptyWatcherRange) {
+	if _, err := w.Watch(t.Context(), 0, []byte("foa"), []byte("foa"), 1); !errors.Is(err, ErrEmptyWatcherRange) {
 		t.Fatalf("key == end range given; expected ErrEmptyWatcherRange, got %+v", err)
 	}
-	if _, err := w.Watch(0, []byte("fob"), []byte("foa"), 1); !errors.Is(err, ErrEmptyWatcherRange) {
+	if _, err := w.Watch(t.Context(), 0, []byte("fob"), []byte("foa"), 1); !errors.Is(err, ErrEmptyWatcherRange) {
 		t.Fatalf("key > end range given; expected ErrEmptyWatcherRange, got %+v", err)
 	}
 	// watch request with 'WithFromKey' has empty-byte range end
-	if id, _ := w.Watch(0, []byte("foo"), []byte{}, 1); id != 0 {
+	if id, _ := w.Watch(t.Context(), 0, []byte("foo"), []byte{}, 1); id != 0 {
 		t.Fatalf("\x00 is range given; id expected 0, got %d", id)
 	}
 }
@@ -232,7 +232,7 @@ func TestWatchDeleteRange(t *testing.T) {
 
 	w := s.NewWatchStream()
 	from, to := testKeyPrefix, []byte(fmt.Sprintf("%s_%d", testKeyPrefix, 99))
-	w.Watch(0, from, to, 0)
+	w.Watch(t.Context(), 0, from, to, 0)
 
 	s.DeleteRange(from, to)
 
@@ -262,7 +262,7 @@ func TestWatchStreamCancelWatcherByID(t *testing.T) {
 	w := s.NewWatchStream()
 	defer w.Close()
 
-	id, _ := w.Watch(0, []byte("foo"), nil, 0)
+	id, _ := w.Watch(t.Context(), 0, []byte("foo"), nil, 0)
 
 	tests := []struct {
 		cancelID WatchID
@@ -350,7 +350,7 @@ func TestWatcherRequestProgress(t *testing.T) {
 
 			w := s.NewWatchStream()
 
-			id, _ := w.Watch(0, notTestKey, nil, tc.startRev)
+			id, _ := w.Watch(t.Context(), 0, notTestKey, nil, tc.startRev)
 			w.RequestProgress(id)
 			asssertProgressSent(t, w, id, tc.expectProgressBeforeSync)
 			s.syncWatchers([]mvccpb.Event{})
@@ -394,7 +394,7 @@ func TestWatcherRequestProgressAll(t *testing.T) {
 	// at least one Watch for progress notifications to get
 	// generated.
 	w := s.NewWatchStream()
-	w.Watch(0, notTestKey, nil, 1)
+	w.Watch(t.Context(), 0, notTestKey, nil, 1)
 
 	w.RequestProgressAll()
 	select {
@@ -429,7 +429,7 @@ func TestWatcherWatchWithFilter(t *testing.T) {
 		return e.Type == mvccpb.PUT
 	}
 
-	w.Watch(0, []byte("foo"), nil, 0, filterPut)
+	w.Watch(t.Context(), 0, []byte("foo"), nil, 0, filterPut)
 	done := make(chan struct{}, 1)
 
 	go func() {
diff --git a/server/storage/schema/schema_test.go b/server/storage/schema/schema_test.go
index 9e46be0db..4f6f25c0b 100644
--- a/server/storage/schema/schema_test.go
+++ b/server/storage/schema/schema_test.go
@@ -115,16 +115,7 @@ func TestMigrate(t *testing.T) {
 		expectErrorMsg string
 	}{
 		// As storage version field was added in v3.6, for v3.5 we will not set it.
-		// For storage to be considered v3.5 it have both confstate and term key set.
-		{
-			name:           `Upgrading v3.5 to v3.6 should be rejected if confstate is not set`,
-			version:        version.V3_5,
-			overrideKeys:   func(tx backend.UnsafeReadWriter) {},
-			targetVersion:  version.V3_6,
-			expectVersion:  nil,
-			expectError:    true,
-			expectErrorMsg: `cannot detect storage schema version: missing confstate information`,
-		},
+		// For storage to be considered v3.5 it has term key set.
 		{
 			name:    `Upgrading v3.5 to v3.6 should be rejected if term is not set`,
 			version: version.V3_5,
diff --git a/tests/antithesis/config/Dockerfile b/tests/antithesis/config/Dockerfile
index 4ae172f66..b262cca0f 100644
--- a/tests/antithesis/config/Dockerfile
+++ b/tests/antithesis/config/Dockerfile
@@ -1,4 +1,4 @@
-ARG GO_VERSION=1.24.5
+ARG GO_VERSION=1.24.6
 
 FROM golang:$GO_VERSION AS build
 RUN go install github.com/a8m/envsubst/cmd/envsubst@v1.4.3
diff --git a/tests/antithesis/server/Dockerfile b/tests/antithesis/server/Dockerfile
index ed1f96bba..1c4142dc5 100644
--- a/tests/antithesis/server/Dockerfile
+++ b/tests/antithesis/server/Dockerfile
@@ -1,4 +1,4 @@
-ARG GO_VERSION=1.24.5
+ARG GO_VERSION=1.24.6
 ARG ARCH=amd64
 
 FROM golang:$GO_VERSION
diff --git a/tests/antithesis/test-template/Dockerfile b/tests/antithesis/test-template/Dockerfile
index 55d43feab..910439126 100644
--- a/tests/antithesis/test-template/Dockerfile
+++ b/tests/antithesis/test-template/Dockerfile
@@ -1,4 +1,4 @@
-ARG GO_VERSION=1.24.5
+ARG GO_VERSION=1.24.6
 ARG ARCH=amd64
 
 FROM golang:$GO_VERSION
diff --git a/tests/common/e2e_test.go b/tests/common/e2e_test.go
index e9e2f1921..37f534843 100644
--- a/tests/common/e2e_test.go
+++ b/tests/common/e2e_test.go
@@ -85,3 +85,39 @@ func WithAuthToken(token string) config.ClientOption {
 func WithEndpoints(endpoints []string) config.ClientOption {
 	return e2e.WithEndpoints(endpoints)
 }
+
+func WithHTTP2Debug() config.ClusterOption {
+	return func(c *config.ClusterConfig) {
+		ctx := ensureE2EClusterContext(c)
+		if ctx.EnvVars == nil {
+			ctx.EnvVars = map[string]string{}
+		}
+		// Enable debug mode to get logs with http2 headers (including authority)
+		ctx.EnvVars["GODEBUG"] = "http2debug=2"
+		c.ClusterContext = ctx
+	}
+}
+
+func WithUnixClient() config.ClusterOption {
+	return func(c *config.ClusterConfig) {
+		ctx := ensureE2EClusterContext(c)
+		ctx.UseUnix = true
+		c.ClusterContext = ctx
+	}
+}
+
+func WithTCPClient() config.ClusterOption {
+	return func(c *config.ClusterConfig) {
+		ctx := ensureE2EClusterContext(c)
+		ctx.UseUnix = false
+		c.ClusterContext = ctx
+	}
+}
+
+func ensureE2EClusterContext(c *config.ClusterConfig) *e2e.ClusterContext {
+	ctx, _ := c.ClusterContext.(*e2e.ClusterContext)
+	if ctx == nil {
+		ctx = &e2e.ClusterContext{}
+	}
+	return ctx
+}
diff --git a/tests/e2e/ctl_v3_grpc_test.go b/tests/common/grpc_test.go
similarity index 60%
rename from tests/e2e/ctl_v3_grpc_test.go
rename to tests/common/grpc_test.go
index 58f2791e2..77ea01c4e 100644
--- a/tests/e2e/ctl_v3_grpc_test.go
+++ b/tests/common/grpc_test.go
@@ -12,31 +12,33 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//go:build !cluster_proxy
-
-package e2e
+package common
 
 import (
 	"context"
 	"fmt"
-	"net/url"
-	"strings"
 	"testing"
 	"time"
 
-	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
-	"go.etcd.io/etcd/pkg/v3/expect"
 	"go.etcd.io/etcd/tests/v3/framework/config"
-	"go.etcd.io/etcd/tests/v3/framework/e2e"
+	intf "go.etcd.io/etcd/tests/v3/framework/interfaces"
 	"go.etcd.io/etcd/tests/v3/framework/testutils"
 )
 
+type clientSocket int
+
+const (
+	clientSocketTCP clientSocket = iota
+	clientSocketUnix
+)
+
 func TestAuthority(t *testing.T) {
+	testRunner.BeforeTest(t)
 	tcs := []struct {
 		name                   string
-		useUnix                bool
+		clientSocket           clientSocket
 		useTLS                 bool
 		useInsecureTLS         bool
 		clientURLPattern       string
@@ -44,43 +46,46 @@ func TestAuthority(t *testing.T) {
 	}{
 		{
 			name:                   "unix:path",
-			useUnix:                true,
+			clientSocket:           clientSocketUnix,
 			clientURLPattern:       "unix:localhost:${MEMBER_PORT}",
 			expectAuthorityPattern: "localhost:${MEMBER_PORT}",
 		},
 		{
 			name:                   "unix://absolute_path",
-			useUnix:                true,
+			clientSocket:           clientSocketUnix,
 			clientURLPattern:       "unix://localhost:${MEMBER_PORT}",
 			expectAuthorityPattern: "localhost:${MEMBER_PORT}",
 		},
 		// "unixs" is not standard schema supported by etcd
 		{
 			name:                   "unixs:absolute_path",
-			useUnix:                true,
+			clientSocket:           clientSocketUnix,
 			useTLS:                 true,
 			clientURLPattern:       "unixs:localhost:${MEMBER_PORT}",
 			expectAuthorityPattern: "localhost:${MEMBER_PORT}",
 		},
 		{
 			name:                   "unixs://absolute_path",
-			useUnix:                true,
+			clientSocket:           clientSocketUnix,
 			useTLS:                 true,
 			clientURLPattern:       "unixs://localhost:${MEMBER_PORT}",
 			expectAuthorityPattern: "localhost:${MEMBER_PORT}",
 		},
 		{
 			name:                   "http://domain[:port]",
+			clientSocket:           clientSocketTCP,
 			clientURLPattern:       "http://localhost:${MEMBER_PORT}",
 			expectAuthorityPattern: "localhost:${MEMBER_PORT}",
 		},
 		{
 			name:                   "http://address[:port]",
+			clientSocket:           clientSocketTCP,
 			clientURLPattern:       "http://127.0.0.1:${MEMBER_PORT}",
 			expectAuthorityPattern: "127.0.0.1:${MEMBER_PORT}",
 		},
 		{
 			name:                   "https://domain[:port] insecure",
+			clientSocket:           clientSocketTCP,
 			useTLS:                 true,
 			useInsecureTLS:         true,
 			clientURLPattern:       "https://localhost:${MEMBER_PORT}",
@@ -88,6 +93,7 @@ func TestAuthority(t *testing.T) {
 		},
 		{
 			name:                   "https://address[:port] insecure",
+			clientSocket:           clientSocketTCP,
 			useTLS:                 true,
 			useInsecureTLS:         true,
 			clientURLPattern:       "https://127.0.0.1:${MEMBER_PORT}",
@@ -95,12 +101,14 @@ func TestAuthority(t *testing.T) {
 		},
 		{
 			name:                   "https://domain[:port]",
+			clientSocket:           clientSocketTCP,
 			useTLS:                 true,
 			clientURLPattern:       "https://localhost:${MEMBER_PORT}",
 			expectAuthorityPattern: "localhost:${MEMBER_PORT}",
 		},
 		{
 			name:                   "https://address[:port]",
+			clientSocket:           clientSocketTCP,
 			useTLS:                 true,
 			clientURLPattern:       "https://127.0.0.1:${MEMBER_PORT}",
 			expectAuthorityPattern: "127.0.0.1:${MEMBER_PORT}",
@@ -109,64 +117,53 @@ func TestAuthority(t *testing.T) {
 	for _, tc := range tcs {
 		for _, clusterSize := range []int{1, 3} {
 			t.Run(fmt.Sprintf("Size: %d, Scenario: %q", clusterSize, tc.name), func(t *testing.T) {
-				e2e.BeforeTest(t)
 				ctx, cancel := context.WithCancel(t.Context())
 				defer cancel()
 
-				cfg := e2e.NewConfigNoTLS()
+				cfg := config.NewClusterConfig()
 				cfg.ClusterSize = clusterSize
-				if tc.useTLS {
-					cfg.Client.ConnectionType = e2e.ClientTLS
+
+				switch {
+				case tc.useInsecureTLS:
+					cfg.ClientTLS = config.AutoTLS
+				case tc.useTLS:
+					cfg.ClientTLS = config.ManualTLS
+				default:
+					cfg.ClientTLS = config.NoTLS
 				}
-				cfg.Client.AutoTLS = tc.useInsecureTLS
-				// Enable debug mode to get logs with http2 headers (including authority)
-				cfg.EnvVars = map[string]string{"GODEBUG": "http2debug=2"}
-				if tc.useUnix {
-					cfg.BaseClientScheme = "unix"
+
+				opts := []config.ClusterOption{
+					config.WithClusterConfig(cfg),
+					WithHTTP2Debug(), // enable http2 header logs only for e2e tests
 				}
 
-				epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(cfg))
-				if err != nil {
-					t.Fatalf("could not start etcd process cluster (%v)", err)
+				switch tc.clientSocket {
+				case clientSocketTCP:
+					opts = append(opts, WithTCPClient())
+				case clientSocketUnix:
+					opts = append(opts, WithUnixClient())
 				}
-				defer epc.Close()
 
-				endpoints := templateEndpoints(t, tc.clientURLPattern, epc)
-				client, err := e2e.NewEtcdctl(cfg.Client, endpoints)
-				require.NoError(t, err)
+				clus := testRunner.NewCluster(ctx, t, opts...)
+				defer clus.Close()
+
+				tmpEndpoints, ok := clus.(intf.TemplateEndpoints)
+				require.Truef(t, ok, "cluster does not implement TemplateEndpoints")
+
+				endpoints := tmpEndpoints.TemplateEndpoints(t, tc.clientURLPattern)
+
+				cc := testutils.MustClient(clus.Client(WithEndpoints(endpoints)))
+
 				for i := 0; i < 100; i++ {
-					require.NoError(t, client.Put(ctx, "foo", "bar", config.PutOptions{}))
+					require.NoError(t, cc.Put(ctx, "foo", "bar", config.PutOptions{}))
 				}
 
 				testutils.ExecuteWithTimeout(t, 5*time.Second, func() {
-					assertAuthority(t, tc.expectAuthorityPattern, epc)
+					asserter, ok := clus.(intf.AssertAuthority)
+					require.Truef(t, ok, "cluster does not implement AssertAuthority")
+					asserter.AssertAuthority(t, tc.expectAuthorityPattern)
 				})
 			})
 		}
 	}
 }
-
-func templateEndpoints(t *testing.T, pattern string, clus *e2e.EtcdProcessCluster) []string {
-	t.Helper()
-	var endpoints []string
-	for i := 0; i < clus.Cfg.ClusterSize; i++ {
-		ent := pattern
-		ent = strings.ReplaceAll(ent, "${MEMBER_PORT}", fmt.Sprintf("%d", e2e.EtcdProcessBasePort+i*5))
-		endpoints = append(endpoints, ent)
-	}
-	return endpoints
-}
-
-func assertAuthority(t *testing.T, expectAuthorityPattern string, clus *e2e.EtcdProcessCluster) {
-	for i := range clus.Procs {
-		line, _ := clus.Procs[i].Logs().ExpectWithContext(t.Context(), expect.ExpectedResponse{Value: `http2: decoded hpack field header field ":authority"`})
-		line = strings.TrimSuffix(line, "\n")
-		line = strings.TrimSuffix(line, "\r")
-
-		u, err := url.Parse(clus.Procs[i].EndpointsGRPC()[0])
-		require.NoError(t, err)
-		expectAuthority := strings.ReplaceAll(expectAuthorityPattern, "${MEMBER_PORT}", u.Port())
-		expectLine := fmt.Sprintf(`http2: decoded hpack field header field ":authority" = %q`, expectAuthority)
-		assert.Truef(t, strings.HasSuffix(line, expectLine), "Got %q expected suffix %q", line, expectLine)
-	}
-}
diff --git a/tests/common/integration_test.go b/tests/common/integration_test.go
index f41055c44..338b78b9b 100644
--- a/tests/common/integration_test.go
+++ b/tests/common/integration_test.go
@@ -63,3 +63,31 @@ func WithAuthToken(token string) config.ClientOption {
 func WithEndpoints(endpoints []string) config.ClientOption {
 	return integration.WithEndpoints(endpoints)
 }
+
+func WithHTTP2Debug() config.ClusterOption {
+	return func(c *config.ClusterConfig) {}
+}
+
+func WithUnixClient() config.ClusterOption {
+	return func(c *config.ClusterConfig) {
+		ctx := ensureIntegrationClusterContext(c)
+		ctx.UseUnix = true
+		c.ClusterContext = ctx
+	}
+}
+
+func WithTCPClient() config.ClusterOption {
+	return func(c *config.ClusterConfig) {
+		ctx := ensureIntegrationClusterContext(c)
+		ctx.UseUnix = false
+		c.ClusterContext = ctx
+	}
+}
+
+func ensureIntegrationClusterContext(c *config.ClusterConfig) *integration.ClusterContext {
+	ctx, _ := c.ClusterContext.(*integration.ClusterContext)
+	if ctx == nil {
+		ctx = &integration.ClusterContext{}
+	}
+	return ctx
+}
diff --git a/tests/common/unit_test.go b/tests/common/unit_test.go
index 48e3a51cd..52fe5d16b 100644
--- a/tests/common/unit_test.go
+++ b/tests/common/unit_test.go
@@ -44,3 +44,15 @@ func WithAuthToken(token string) config.ClientOption {
 func WithEndpoints(endpoints []string) config.ClientOption {
 	return func(any) {}
 }
+
+func WithHTTP2Debug() config.ClusterOption {
+	return func(c *config.ClusterConfig) {}
+}
+
+func WithTCPClient() config.ClusterOption {
+	return func(c *config.ClusterConfig) {}
+}
+
+func WithUnixClient() config.ClusterOption {
+	return func(c *config.ClusterConfig) {}
+}
diff --git a/tests/e2e/logging_test.go b/tests/e2e/logging_test.go
index 8e6b064ad..3cea7ee0c 100644
--- a/tests/e2e/logging_test.go
+++ b/tests/e2e/logging_test.go
@@ -40,7 +40,7 @@ func TestNoErrorLogsDuringNormalOperations(t *testing.T) {
 				"setting up serving from embedded etcd failed.": true,
 				// See https://github.com/etcd-io/etcd/pull/19040#issuecomment-2539173800
 				// TODO: Remove with etcd 3.7
-				"cannot detect storage schema version: missing confstate information": true,
+				"cannot detect storage schema version: missing term information": true,
 			},
 		},
 		{
@@ -53,7 +53,7 @@ func TestNoErrorLogsDuringNormalOperations(t *testing.T) {
 				"setting up serving from embedded etcd failed.": true,
 				// See https://github.com/etcd-io/etcd/pull/19040#issuecomment-2539173800
 				// TODO: Remove with etcd 3.7
-				"cannot detect storage schema version: missing confstate information": true,
+				"cannot detect storage schema version: missing term information": true,
 			},
 		},
 		{
@@ -70,7 +70,7 @@ func TestNoErrorLogsDuringNormalOperations(t *testing.T) {
 				"setting up serving from embedded etcd failed.": true,
 				// See https://github.com/etcd-io/etcd/pull/19040#issuecomment-2539173800
 				// TODO: Remove with etcd 3.7
-				"cannot detect storage schema version: missing confstate information": true,
+				"cannot detect storage schema version: missing term information": true,
 			},
 		},
 		{
@@ -85,7 +85,7 @@ func TestNoErrorLogsDuringNormalOperations(t *testing.T) {
 				"setting up serving from embedded etcd failed.": true,
 				// See https://github.com/etcd-io/etcd/pull/19040#issuecomment-2539173800
 				// TODO: Remove with etcd 3.7
-				"cannot detect storage schema version: missing confstate information": true,
+				"cannot detect storage schema version: missing term information": true,
 			},
 		},
 		{
@@ -100,7 +100,7 @@ func TestNoErrorLogsDuringNormalOperations(t *testing.T) {
 				"setting up serving from embedded etcd failed.": true,
 				// See https://github.com/etcd-io/etcd/pull/19040#issuecomment-2539173800
 				// TODO: Remove with etcd 3.7
-				"cannot detect storage schema version: missing confstate information": true,
+				"cannot detect storage schema version: missing term information": true,
 			},
 		},
 	}
diff --git a/tests/e2e/reproduce_18667_test.go b/tests/e2e/reproduce_18667_test.go
new file mode 100644
index 000000000..e7ac651cf
--- /dev/null
+++ b/tests/e2e/reproduce_18667_test.go
@@ -0,0 +1,85 @@
+// Copyright 2025 The etcd Authors
+//
+// 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 e2e
+
+import (
+	"fmt"
+	"math/rand"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
+	clientv3 "go.etcd.io/etcd/client/v3"
+	"go.etcd.io/etcd/tests/v3/framework/e2e"
+)
+
+// TestReproduce18667 reproduces the issue: https://github.com/etcd-io/etcd/issues/18667.
+func TestReproduce18667(t *testing.T) {
+	e2e.BeforeTest(t)
+
+	ctx := t.Context()
+	clus, cerr := e2e.NewEtcdProcessCluster(ctx, t,
+		e2e.WithClusterSize(3),
+		e2e.WithSnapshotCount(1000),
+	)
+	require.NoError(t, cerr)
+
+	t.Cleanup(func() { clus.Stop() })
+
+	targetIdx := rand.Intn(3)
+	cli := newClient(t, clus.Procs[targetIdx].EndpointsGRPC(), e2e.ClientConfig{})
+
+	t.Log("Put key-value [k1...k20]")
+	var revision int64
+	for i := 1; i <= 20; i++ {
+		resp, err := cli.Put(ctx, fmt.Sprintf("k%d", i), fmt.Sprintf("v%d", i))
+		require.NoError(t, err)
+		revision = resp.Header.Revision
+	}
+
+	t.Logf("Compact on the latest revision %d", revision)
+	_, err := cli.Compact(ctx, revision)
+	require.NoError(t, err)
+
+	t.Log("Send txn to make data inconsistent among etcdservers")
+	cmp := clientv3.Compare(clientv3.Value("k1"), "=", "v1")
+	then := []clientv3.Op{
+		clientv3.OpPut("k2", "foo"),
+		clientv3.OpGet("k1", clientv3.WithRev(revision-1)),
+	}
+	_, err = cli.Txn(ctx).If(cmp).Then(then...).Commit()
+	require.Error(t, err)
+	require.Contains(t, err.Error(), rpctypes.ErrCompacted.Error())
+
+	t.Log("Verify all members have consistent data on key 'k2'")
+	for i := 0; i < clus.Cfg.ClusterSize; i++ {
+		idx := (targetIdx + i) % clus.Cfg.ClusterSize
+		cli = newClient(t, clus.Procs[idx].EndpointsGRPC(), e2e.ClientConfig{})
+		resp, err := cli.Get(ctx, "k2")
+		require.NoError(t, err)
+		// TODO: All members are supposed to have the same key/value pair k2:v2.
+		// However, the issue https://github.com/etcd-io/etcd/issues/18667
+		// hasn't been resolved yet, so some members hold the wrong data
+		// "k2:foo". Once the issue is fixed, we should remove the if-else
+		// branch below, and all member should have consistent data k2:v2.
+		if i == 0 {
+			assert.Equal(t, "v2", string(resp.Kvs[0].Value))
+		} else {
+			assert.Equal(t, "foo", string(resp.Kvs[0].Value))
+		}
+	}
+}
diff --git a/tests/framework/e2e/config.go b/tests/framework/e2e/config.go
index 3cdc3f692..4e0405468 100644
--- a/tests/framework/e2e/config.go
+++ b/tests/framework/e2e/config.go
@@ -41,6 +41,8 @@ func (cv ClusterVersion) String() string {
 
 type ClusterContext struct {
 	Version ClusterVersion
+	EnvVars map[string]string
+	UseUnix bool
 }
 
 var experimentalFlags = map[string]struct{}{
diff --git a/tests/framework/e2e/e2e.go b/tests/framework/e2e/e2e.go
index 6f3954d5d..7ab52006b 100644
--- a/tests/framework/e2e/e2e.go
+++ b/tests/framework/e2e/e2e.go
@@ -16,10 +16,17 @@ package e2e
 
 import (
 	"context"
+	"fmt"
+	"net/url"
 	"os"
+	"strings"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
 	"go.etcd.io/etcd/client/pkg/v3/testutil"
+	"go.etcd.io/etcd/pkg/v3/expect"
 	"go.etcd.io/etcd/tests/v3/framework/config"
 	intf "go.etcd.io/etcd/tests/v3/framework/interfaces"
 )
@@ -53,9 +60,12 @@ func (e e2eRunner) NewCluster(ctx context.Context, tb testing.TB, opts ...config
 		WithSnapshotCount(cfg.SnapshotCount),
 	)
 
-	if cfg.ClusterContext != nil {
-		e2eClusterCtx := cfg.ClusterContext.(*ClusterContext)
-		e2eConfig.Version = e2eClusterCtx.Version
+	if ctx, ok := cfg.ClusterContext.(*ClusterContext); ok && ctx != nil {
+		e2eConfig.Version = ctx.Version
+		e2eConfig.EnvVars = ctx.EnvVars
+		if ctx.UseUnix {
+			e2eConfig.BaseClientScheme = "unix"
+		}
 	}
 
 	switch cfg.ClientTLS {
@@ -111,6 +121,32 @@ func (c *e2eCluster) Members() (ms []intf.Member) {
 	return ms
 }
 
+func (c *e2eCluster) TemplateEndpoints(tb testing.TB, pattern string) []string {
+	tb.Helper()
+	var endpoints []string
+	for i := 0; i < c.Cfg.ClusterSize; i++ {
+		ent := pattern
+		ent = strings.ReplaceAll(ent, "${MEMBER_PORT}", fmt.Sprintf("%d", EtcdProcessBasePort+i*5))
+		endpoints = append(endpoints, ent)
+	}
+	return endpoints
+}
+
+func (c *e2eCluster) AssertAuthority(tb testing.TB, expectAuthorityPattern string) {
+	for i := range c.Procs {
+		line, _ := c.Procs[i].Logs().ExpectWithContext(tb.Context(), expect.ExpectedResponse{
+			Value: `http2: decoded hpack field header field ":authority"`,
+		})
+		line = strings.TrimSuffix(strings.TrimSuffix(line, "\n"), "\r")
+
+		u, err := url.Parse(c.Procs[i].EndpointsGRPC()[0])
+		require.NoError(tb, err)
+		expectAuthority := strings.ReplaceAll(expectAuthorityPattern, "${MEMBER_PORT}", u.Port())
+		expectLine := fmt.Sprintf(`http2: decoded hpack field header field ":authority" = %q`, expectAuthority)
+		assert.Truef(tb, strings.HasSuffix(line, expectLine), "Got %q expected suffix %q", line, expectLine)
+	}
+}
+
 type e2eClient struct {
 	*EtcdctlV3
 }
diff --git a/tests/framework/integration/config.go b/tests/framework/integration/config.go
new file mode 100644
index 000000000..b1b246006
--- /dev/null
+++ b/tests/framework/integration/config.go
@@ -0,0 +1,19 @@
+// Copyright 2025 The etcd Authors
+//
+// 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 integration
+
+type ClusterContext struct {
+	UseUnix bool
+}
diff --git a/tests/framework/integration/integration.go b/tests/framework/integration/integration.go
index 6152ca705..9711f38e7 100644
--- a/tests/framework/integration/integration.go
+++ b/tests/framework/integration/integration.go
@@ -63,6 +63,14 @@ func (e integrationRunner) NewCluster(ctx context.Context, tb testing.TB, opts .
 	if err != nil {
 		tb.Fatalf("PeerTLS: %s", err)
 	}
+
+	if cfg.ClusterContext != nil {
+		if ctx, ok := cfg.ClusterContext.(*ClusterContext); ok && ctx != nil {
+			integrationCfg.UseTCP = !ctx.UseUnix
+			integrationCfg.UseIP = !ctx.UseUnix
+		}
+	}
+
 	return &integrationCluster{
 		Cluster: NewCluster(tb, &integrationCfg),
 		t:       tb,
@@ -100,6 +108,48 @@ func (c *integrationCluster) Members() (ms []intf.Member) {
 	return ms
 }
 
+func (c *integrationCluster) TemplateEndpoints(tb testing.TB, pattern string) []string {
+	tb.Helper()
+	var endpoints []string
+	for _, m := range c.Cluster.Members {
+		ent := pattern
+		ent = strings.ReplaceAll(ent, "${MEMBER_PORT}", m.GRPCPortNumber())
+		ent = strings.ReplaceAll(ent, "${MEMBER_NAME}", m.Name)
+		endpoints = append(endpoints, ent)
+	}
+	return endpoints
+}
+
+func templateAuthority(tb testing.TB, pattern string, m *Member) string {
+	tb.Helper()
+	authority := pattern
+	authority = strings.ReplaceAll(authority, "${MEMBER_PORT}", m.GRPCPortNumber())
+	authority = strings.ReplaceAll(authority, "${MEMBER_NAME}", m.Name)
+	return authority
+}
+
+func (c *integrationCluster) AssertAuthority(tb testing.TB, expectedAuthorityPattern string) {
+	tb.Helper()
+	const filterMethod = "/etcdserverpb.KV/Put"
+	for _, m := range c.Cluster.Members {
+		expectedAuthority := templateAuthority(tb, expectedAuthorityPattern, m)
+		requestsFound := 0
+		for _, r := range m.RecordedRequests() {
+			if r.FullMethod != filterMethod {
+				continue
+			}
+			if r.Authority == expectedAuthority {
+				requestsFound++
+			} else {
+				tb.Errorf("Got unexpected authority header, member %q, request %q, got %q, expected %q", m.Name, r.FullMethod, r.Authority, expectedAuthority)
+			}
+		}
+		if requestsFound == 0 {
+			tb.Errorf("Expect at least one request with matched authority header value was recorded by the server intercepter on member %s but got 0", m.Name)
+		}
+	}
+}
+
 type integrationMember struct {
 	*Member
 	t testing.TB
diff --git a/tests/framework/interfaces/interface.go b/tests/framework/interfaces/interface.go
index 0477ea5f0..0d334011b 100644
--- a/tests/framework/interfaces/interface.go
+++ b/tests/framework/interfaces/interface.go
@@ -85,3 +85,11 @@ type Client interface {
 
 	Watch(ctx context.Context, key string, opts config.WatchOptions) clientv3.WatchChan
 }
+
+type TemplateEndpoints interface {
+	TemplateEndpoints(tb testing.TB, pattern string) []string
+}
+
+type AssertAuthority interface {
+	AssertAuthority(tb testing.TB, expectedAuthorityPattern string)
+}
diff --git a/tests/go.mod b/tests/go.mod
index c9ca2d002..fa0d60aee 100644
--- a/tests/go.mod
+++ b/tests/go.mod
@@ -2,7 +2,7 @@ module go.etcd.io/etcd/tests/v3
 
 go 1.24
 
-toolchain go1.24.5
+toolchain go1.24.6
 
 replace (
 	go.etcd.io/etcd/api/v3 => ../api
@@ -19,14 +19,14 @@ require (
 	github.com/anishathalye/porcupine v1.0.2
 	github.com/antithesishq/antithesis-sdk-go v0.4.3
 	github.com/coreos/go-semver v0.3.1
-	github.com/golang-jwt/jwt/v5 v5.2.3
+	github.com/golang-jwt/jwt/v5 v5.3.0
 	github.com/golang/protobuf v1.5.4
 	github.com/google/go-cmp v0.7.0
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
 	github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1
 	github.com/olekukonko/tablewriter v1.0.9
-	github.com/prometheus/client_golang v1.22.0
+	github.com/prometheus/client_golang v1.23.0
 	github.com/prometheus/client_model v0.6.2
 	github.com/prometheus/common v0.65.0
 	github.com/soheilhy/cmux v0.1.5
@@ -45,13 +45,13 @@ require (
 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0
 	go.opentelemetry.io/otel v1.37.0
 	go.opentelemetry.io/otel/sdk v1.37.0
-	go.opentelemetry.io/proto/otlp v1.7.0
+	go.opentelemetry.io/proto/otlp v1.7.1
 	go.uber.org/zap v1.27.0
-	golang.org/x/crypto v0.40.0
+	golang.org/x/crypto v0.41.0
 	golang.org/x/sync v0.16.0
 	golang.org/x/time v0.12.0
 	google.golang.org/grpc v1.74.2
-	google.golang.org/protobuf v1.36.6
+	google.golang.org/protobuf v1.36.7
 )
 
 require (
@@ -83,7 +83,7 @@ require (
 	github.com/olekukonko/errors v1.1.0 // indirect
 	github.com/olekukonko/ll v0.0.9 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
-	github.com/prometheus/procfs v0.15.1 // indirect
+	github.com/prometheus/procfs v0.16.1 // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/spf13/cobra v1.9.1 // indirect
@@ -97,12 +97,12 @@ require (
 	go.opentelemetry.io/otel/trace v1.37.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.yaml.in/yaml/v2 v2.4.2 // indirect
-	golang.org/x/net v0.42.0 // indirect
-	golang.org/x/sys v0.34.0 // indirect
-	golang.org/x/text v0.27.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
+	golang.org/x/net v0.43.0 // indirect
+	golang.org/x/sys v0.35.0 // indirect
+	golang.org/x/text v0.28.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	sigs.k8s.io/yaml v1.5.0 // indirect
+	sigs.k8s.io/yaml v1.6.0 // indirect
 )
diff --git a/tests/go.sum b/tests/go.sum
index 2c7cb9d35..e72d089e8 100644
--- a/tests/go.sum
+++ b/tests/go.sum
@@ -51,8 +51,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
-github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
+github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
@@ -114,15 +114,15 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
-github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
+github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
 github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
 github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
-github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
-github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -176,8 +176,8 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh
 go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
 go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
 go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
-go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
-go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
@@ -194,8 +194,8 @@ go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
-golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
+golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
+golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -211,8 +211,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
-golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -228,12 +228,12 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
 golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -253,10 +253,10 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
-google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@@ -264,8 +264,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
 google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
 google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
+google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -277,5 +277,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
-sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
+sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
+sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
diff --git a/tests/integration/grpc_test.go b/tests/integration/grpc_test.go
deleted file mode 100644
index 606262942..000000000
--- a/tests/integration/grpc_test.go
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2021 The etcd Authors
-//
-// 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 integration
-
-import (
-	tls "crypto/tls"
-	"fmt"
-	"strings"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/require"
-	"google.golang.org/grpc"
-
-	clientv3 "go.etcd.io/etcd/client/v3"
-	"go.etcd.io/etcd/tests/v3/framework/integration"
-)
-
-func TestAuthority(t *testing.T) {
-	tcs := []struct {
-		name                   string
-		useTCP                 bool
-		useTLS                 bool
-		clientURLPattern       string
-		expectAuthorityPattern string
-	}{
-		{
-			name:                   "unix:path",
-			clientURLPattern:       "unix:localhost:${MEMBER_NAME}",
-			expectAuthorityPattern: "localhost:${MEMBER_NAME}",
-		},
-		{
-			name:                   "unix://absolute_path",
-			clientURLPattern:       "unix://localhost:${MEMBER_NAME}",
-			expectAuthorityPattern: "localhost:${MEMBER_NAME}",
-		},
-		// "unixs" is not standard schema supported by etcd
-		{
-			name:                   "unixs:absolute_path",
-			useTLS:                 true,
-			clientURLPattern:       "unixs:localhost:${MEMBER_NAME}",
-			expectAuthorityPattern: "localhost:${MEMBER_NAME}",
-		},
-		{
-			name:                   "unixs://absolute_path",
-			useTLS:                 true,
-			clientURLPattern:       "unixs://localhost:${MEMBER_NAME}",
-			expectAuthorityPattern: "localhost:${MEMBER_NAME}",
-		},
-		{
-			name:                   "http://domain[:port]",
-			useTCP:                 true,
-			clientURLPattern:       "http://localhost:${MEMBER_PORT}",
-			expectAuthorityPattern: "localhost:${MEMBER_PORT}",
-		},
-		{
-			name:                   "https://domain[:port]",
-			useTLS:                 true,
-			useTCP:                 true,
-			clientURLPattern:       "https://localhost:${MEMBER_PORT}",
-			expectAuthorityPattern: "localhost:${MEMBER_PORT}",
-		},
-		{
-			name:                   "http://address[:port]",
-			useTCP:                 true,
-			clientURLPattern:       "http://127.0.0.1:${MEMBER_PORT}",
-			expectAuthorityPattern: "127.0.0.1:${MEMBER_PORT}",
-		},
-		{
-			name:                   "https://address[:port]",
-			useTCP:                 true,
-			useTLS:                 true,
-			clientURLPattern:       "https://127.0.0.1:${MEMBER_PORT}",
-			expectAuthorityPattern: "127.0.0.1:${MEMBER_PORT}",
-		},
-	}
-	for _, tc := range tcs {
-		for _, clusterSize := range []int{1, 3} {
-			t.Run(fmt.Sprintf("Size: %d, Scenario: %q", clusterSize, tc.name), func(t *testing.T) {
-				integration.BeforeTest(t)
-				cfg := integration.ClusterConfig{
-					Size:   clusterSize,
-					UseTCP: tc.useTCP,
-					UseIP:  tc.useTCP,
-				}
-				cfg, tlsConfig := setupTLS(t, tc.useTLS, cfg)
-				clus := integration.NewCluster(t, &cfg)
-				defer clus.Terminate(t)
-
-				kv := setupClient(t, tc.clientURLPattern, clus, tlsConfig)
-				defer kv.Close()
-
-				putRequestMethod := "/etcdserverpb.KV/Put"
-				for i := 0; i < 100; i++ {
-					_, err := kv.Put(t.Context(), "foo", "bar")
-					require.NoError(t, err)
-				}
-
-				assertAuthority(t, tc.expectAuthorityPattern, clus, putRequestMethod)
-			})
-		}
-	}
-}
-
-func setupTLS(t *testing.T, useTLS bool, cfg integration.ClusterConfig) (integration.ClusterConfig, *tls.Config) {
-	t.Helper()
-	if useTLS {
-		cfg.ClientTLS = &integration.TestTLSInfo
-		tlsConfig, err := integration.TestTLSInfo.ClientConfig()
-		require.NoError(t, err)
-		return cfg, tlsConfig
-	}
-	return cfg, nil
-}
-
-func setupClient(t *testing.T, endpointPattern string, clus *integration.Cluster, tlsConfig *tls.Config) *clientv3.Client {
-	t.Helper()
-	endpoints := templateEndpoints(t, endpointPattern, clus)
-	kv, err := clientv3.New(clientv3.Config{
-		Endpoints:   endpoints,
-		DialTimeout: 5 * time.Second,
-		DialOptions: []grpc.DialOption{grpc.WithBlock()},
-		TLS:         tlsConfig,
-	})
-	require.NoError(t, err)
-	return kv
-}
-
-func templateEndpoints(t *testing.T, pattern string, clus *integration.Cluster) []string {
-	t.Helper()
-	var endpoints []string
-	for _, m := range clus.Members {
-		ent := pattern
-		ent = strings.ReplaceAll(ent, "${MEMBER_PORT}", m.GRPCPortNumber())
-		ent = strings.ReplaceAll(ent, "${MEMBER_NAME}", m.Name)
-		endpoints = append(endpoints, ent)
-	}
-	return endpoints
-}
-
-func templateAuthority(t *testing.T, pattern string, m *integration.Member) string {
-	t.Helper()
-	authority := pattern
-	authority = strings.ReplaceAll(authority, "${MEMBER_PORT}", m.GRPCPortNumber())
-	authority = strings.ReplaceAll(authority, "${MEMBER_NAME}", m.Name)
-	return authority
-}
-
-func assertAuthority(t *testing.T, expectedAuthorityPattern string, clus *integration.Cluster, filterMethod string) {
-	t.Helper()
-	for _, m := range clus.Members {
-		requestsFound := 0
-		expectedAuthority := templateAuthority(t, expectedAuthorityPattern, m)
-		for _, r := range m.RecordedRequests() {
-			if filterMethod != "" && r.FullMethod != filterMethod {
-				continue
-			}
-			if r.Authority == expectedAuthority {
-				requestsFound++
-			} else {
-				t.Errorf("Got unexpected authority header, member: %q, request: %q, got authority: %q, expected %q", m.Name, r.FullMethod, r.Authority, expectedAuthority)
-			}
-		}
-		if requestsFound == 0 {
-			t.Errorf("Expect at least one request with matched authority header value was recorded by the server intercepter on member %s but got 0", m.Name)
-		}
-	}
-}
diff --git a/tests/integration/tracing_test.go b/tests/integration/tracing_test.go
index 082ea3e84..899458f69 100644
--- a/tests/integration/tracing_test.go
+++ b/tests/integration/tracing_test.go
@@ -138,7 +138,7 @@ func TestTracing(t *testing.T) {
 				defer cancel()
 
 				// Create a watch channel
-				watchChan := cli.Watch(ctx, "watch-key")
+				watchChan := cli.Watch(ctx, "watch-key", clientv3.WithProgressNotify(), clientv3.WithRev(1))
 
 				// Put a value to trigger the watch
 				_, err := cli.Put(ctx, "watch-key", "watch-value")
@@ -155,8 +155,33 @@ func TestTracing(t *testing.T) {
 				}
 			},
 			wantSpan: &v1.Span{
-				Name: "etcdserverpb.Watch/Watch",
-				// Attributes are set outside Etcd in otelgrpc, so they are ignored here.
+				Name: "watch",
+				Attributes: []*commonv1.KeyValue{
+					{
+						Key:   "key",
+						Value: &commonv1.AnyValue{Value: &commonv1.AnyValue_StringValue{StringValue: "watch-key"}},
+					},
+					{
+						Key:   "range_end",
+						Value: &commonv1.AnyValue{Value: &commonv1.AnyValue_StringValue{StringValue: ""}},
+					},
+					{
+						Key:   "start_rev",
+						Value: &commonv1.AnyValue{Value: &commonv1.AnyValue_IntValue{IntValue: 1}},
+					},
+					{
+						Key:   "progress_notify",
+						Value: &commonv1.AnyValue{Value: &commonv1.AnyValue_BoolValue{BoolValue: true}},
+					},
+					{
+						Key:   "prev_kv",
+						Value: &commonv1.AnyValue{Value: &commonv1.AnyValue_BoolValue{BoolValue: false}},
+					},
+					{
+						Key:   "fragment",
+						Value: &commonv1.AnyValue{Value: &commonv1.AnyValue_BoolValue{BoolValue: false}},
+					},
+				},
 			},
 		},
 	} {
diff --git a/tests/robustness/Makefile b/tests/robustness/Makefile
index 68d77d188..fe7748e2a 100644
--- a/tests/robustness/Makefile
+++ b/tests/robustness/Makefile
@@ -86,7 +86,7 @@ install-gofail: $(GOPATH)/bin/gofail
 
 .PHONY: gofail-enable
 gofail-enable: $(GOPATH)/bin/gofail
-	$(GOPATH)/bin/gofail enable server/etcdserver/ server/lease/leasehttp server/storage/backend/ server/storage/mvcc/ server/storage/wal/ server/etcdserver/api/v3rpc/ server/etcdserver/api/membership/
+	$(GOPATH)/bin/gofail enable server/etcdserver/ server/lease/leasehttp server/storage/backend/ server/storage/mvcc/ server/storage/wal/ server/etcdserver/api/v3rpc/ server/etcdserver/api/membership/ server/etcdserver/api/rafthttp/
 	cd $(REPOSITORY_ROOT)/server && go get go.etcd.io/gofail@${GOFAIL_VERSION}
 	cd $(REPOSITORY_ROOT)/etcdutl && go get go.etcd.io/gofail@${GOFAIL_VERSION}
 	cd $(REPOSITORY_ROOT)/etcdctl && go get go.etcd.io/gofail@${GOFAIL_VERSION}
@@ -94,7 +94,7 @@ gofail-enable: $(GOPATH)/bin/gofail
 
 .PHONY: gofail-disable
 gofail-disable: $(GOPATH)/bin/gofail
-	$(GOPATH)/bin/gofail disable server/etcdserver/ server/lease/leasehttp server/storage/backend/ server/storage/mvcc/ server/storage/wal/ server/etcdserver/api/v3rpc/ server/etcdserver/api/membership/
+	$(GOPATH)/bin/gofail disable server/etcdserver/ server/lease/leasehttp server/storage/backend/ server/storage/mvcc/ server/storage/wal/ server/etcdserver/api/v3rpc/ server/etcdserver/api/membership/ server/etcdserver/api/rafthttp/
 	cd $(REPOSITORY_ROOT)/server && go mod tidy
 	cd $(REPOSITORY_ROOT)/etcdutl && go mod tidy
 	cd $(REPOSITORY_ROOT)/etcdctl && go mod tidy
diff --git a/tests/robustness/coverage/coverage_test.go b/tests/robustness/coverage/coverage_test.go
index d4ca35a8e..235b71347 100644
--- a/tests/robustness/coverage/coverage_test.go
+++ b/tests/robustness/coverage/coverage_test.go
@@ -134,9 +134,24 @@ var referenceUsageOfEtcdAPI = map[string]refOp{
 	"etcdserverpb.KV/Compact": {
 		// Compaction should move to using internal Etcd mechanism
 		// Discussed in https://github.com/kubernetes/kubernetes/issues/80513
+		args: []column{
+			{name: "rev", matcher: isRevisionSet},
+			{name: "physical", matcher: boolAttrSet("is_physical")},
+		},
 	},
 	"etcdserverpb.Watch/Watch": {
 		// Not part of the contract interface (yet)
+		args: []column{
+			{name: "range_end", matcher: isRangeEndSet},
+			{name: "start_rev", matcher: intAttrSet("start_rev")},
+			{name: "prev_kv", matcher: boolAttrSet("prev_kv")},
+			{name: "fragment", matcher: boolAttrSet("fragment")},
+			{name: "progress_notify", matcher: boolAttrSet("progress_notify")},
+		},
+		keyAttrName: "key",
+		methods: []method{
+			{name: "Watch", matcher: notMatcher(keyIsEqualStr("key", "compact_rev_key"))},
+		},
 	},
 	"etcdserverpb.Lease/LeaseGrant": {
 		// Used to manage masterleases and events
@@ -242,6 +257,9 @@ func testInterfaceUse(t *testing.T, filename string) {
 }
 
 func extractPattern(trace *tracev1.ResourceSpans, key string) (string, bool) {
+	if key == "" {
+		return "", true
+	}
 	k, found := strAttr(trace, key)
 	if !found {
 		return "", false
@@ -292,6 +310,9 @@ func argsToDescription(matched string, cols []column) string {
 }
 
 func extractMethod(methodToMatched []method, trace *tracev1.ResourceSpans) (string, bool) {
+	if len(methodToMatched) == 0 {
+		return getOperationName(trace), true
+	}
 	for _, mm := range methodToMatched {
 		if mm.matcher(trace) {
 			return mm.name, true
diff --git a/tests/robustness/coverage/matchers_test.go b/tests/robustness/coverage/matchers_test.go
index 62935edde..2db75e0d3 100644
--- a/tests/robustness/coverage/matchers_test.go
+++ b/tests/robustness/coverage/matchers_test.go
@@ -47,14 +47,16 @@ func keyIsEqualInt(key string, want int) Matcher {
 	}
 }
 
-func isLimitSet(trace *tracev1.ResourceSpans) bool {
-	limit, found := intAttr(trace, "limit")
-	return found && limit > 0
-}
+var (
+	isLimitSet    = intAttrSet("limit")
+	isRevisionSet = intAttrSet("rev")
+)
 
-func isRevisionSet(trace *tracev1.ResourceSpans) bool {
-	rev, found := intAttr(trace, "rev")
-	return found && rev > 0
+func intAttrSet(key string) Matcher {
+	return func(trace *tracev1.ResourceSpans) bool {
+		val, found := intAttr(trace, key)
+		return found && val > 0
+	}
 }
 
 func intAttr(trace *tracev1.ResourceSpans, key string) (int, bool) {
@@ -95,19 +97,17 @@ func strAttr(trace *tracev1.ResourceSpans, key string) (string, bool) {
 	return "", false
 }
 
-func isReadOnly(trace *tracev1.ResourceSpans) bool {
-	isRO, found := boolAttr(trace, "read_only")
-	return found && isRO
-}
-
-func isKeysOnly(trace *tracev1.ResourceSpans) bool {
-	isKeys, found := boolAttr(trace, "keys_only")
-	return found && isKeys
-}
+var (
+	isReadOnly  = boolAttrSet("read_only")
+	isKeysOnly  = boolAttrSet("keys_only")
+	isCountOnly = boolAttrSet("count_only")
+)
 
-func isCountOnly(trace *tracev1.ResourceSpans) bool {
-	isCount, found := boolAttr(trace, "count_only")
-	return found && isCount
+func boolAttrSet(key string) Matcher {
+	return func(trace *tracev1.ResourceSpans) bool {
+		val, found := boolAttr(trace, key)
+		return found && val
+	}
 }
 
 func boolAttr(trace *tracev1.ResourceSpans, key string) (bool, bool) {