Repository: kubernetes-sigs/kueue

Issue:
Refactor updating workload via PatchAdmissionStatus to make sure update is not saved on error
<!-- Please only use this template for submitting clean up requests -->

**What would you like to be cleaned**:

Calling PatchAdmissionStatus can currently lead to subtle bugs, when the function fails on error, because the object passed to the function is modified.

To achieve that we could have a new helper dedicated to Workloads which, say `workload.PatchStatus`:
1. takes an internal DeepCopy of the workload
2. calls PatchAdmissionStatus or another helper 
3. updates the workload pointer pass to invoke the function on success
4. discards the changes in case of failure

Alternatively, return the new version of the object on succeessful request (but this is more code changes). 


**Why is this needed**:

To avoid modifying the cached valued of the Workload in case the update fails. This inconsistency may result in subtle issues.

Hints from issue comments:
@mimowo: /assign @mbobrovskyi 
cc @mszadkow 
tentatively as a continuation of https://github.com/kubernetes-sigs/kueue/pull/7709

Solution patch:
diff --git a/.github/ISSUE_TEMPLATE/NEW_RELEASE.md b/.github/ISSUE_TEMPLATE/NEW_RELEASE.md
index 85b801522..317ad2aae 100644
--- a/.github/ISSUE_TEMPLATE/NEW_RELEASE.md
+++ b/.github/ISSUE_TEMPLATE/NEW_RELEASE.md
@@ -42,7 +42,7 @@ Please do not remove items from the checklist
       via UI or `gh release --repo kubernetes-sigs/kueue upload $VERSION artifacts/*`.
 - [ ] Submit a PR against [k8s.io](https://github.com/kubernetes/k8s.io) to
       [promote the container images and Helm Chart](https://github.com/kubernetes/k8s.io/tree/main/registry.k8s.io#image-promoter)
-      to production: <!-- example kubernetes/k8s.io#7899 -->
+      to production: <!-- K8S_IO_PULL --> <!-- example kubernetes/k8s.io#7899 -->
   - [ ] Update `registry.k8s.io/images/k8s-staging-kueue/images.yaml`.
 - [ ] Wait for the PR to be merged and verify that the image `registry.k8s.io/kueue/kueue:$VERSION` is available.
 - [ ] Publish the draft release prepared at the [GitHub releases page](https://github.com/kubernetes-sigs/kueue/releases).
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 7819005ff..8bd9d1086 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -83,22 +83,7 @@ updates:
       - dependency-name: k8s.io/*
         update-types: ["version-update:semver-major", "version-update:semver-minor"]
 
-  # Maintain npm dependencies for KueueViz frontend
-  - package-ecosystem: "npm"
-    directory: "/cmd/kueueviz/frontend"
-    schedule:
-      interval: "weekly"
-    labels:
-      - "ok-to-test"
-      - "release-note-none"
-    groups:
-      mui-packages:
-        patterns:
-          - "@mui/*"
-      all:
-        update-types:
-          - "minor"
-          - "patch"
+  # Frontend npm dependencies managed by Renovate (see renovate.json)
 
   # Maintain npm dependencies for KueueViz frontend cypress e2e tests
   - package-ecosystem: "npm"
diff --git a/.github/workflows/update-versions-with-renovate.yaml b/.github/workflows/update-versions-with-renovate.yaml
new file mode 100644
index 000000000..966de6f73
--- /dev/null
+++ b/.github/workflows/update-versions-with-renovate.yaml
@@ -0,0 +1,26 @@
+name: update-versions-with-renovate
+
+on:
+  schedule:
+    # Once a week
+    - cron: '0 0 * * 0'
+  workflow_dispatch:
+
+jobs:
+  renovate:
+    runs-on: ubuntu-latest
+    if: github.repository == 'kubernetes-sigs/kueue'
+    permissions:
+      contents: write
+      pull-requests: write
+    steps:
+      - name: checkout
+        uses: actions/checkout@v6
+      - name: run renovate (dry-run on PRs)
+        uses: renovatebot/github-action@v44.0.4
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          configurationFile: renovate.json
+        env:
+          LOG_LEVEL: info
+          RENOVATE_REPOSITORIES: ${{ github.repository }}
diff --git a/Makefile b/Makefile
index 5fc722e80..d59c79f8d 100644
--- a/Makefile
+++ b/Makefile
@@ -141,7 +141,7 @@ update-helm: manifests yq yaml-processor
 	$(BIN_DIR)/yaml-processor -zap-log-level=$(YAML_PROCESSOR_LOG_LEVEL) hack/processing-plan.yaml
 
 .PHONY: generate
-generate: gomod-download generate-mocks generate-apiref generate-code generate-kueuectl-docs generate-helm-docs
+generate: gomod-download generate-mocks generate-apiref generate-code generate-kueuectl-docs generate-helm-docs generate-metrics-tables
 
 .PHONY: generate-code
 generate-code: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations and client-go libraries.
@@ -334,6 +334,11 @@ yaml-processor:
 	cd $(TOOLS_DIR)/yaml-processor && \
 	$(GO_BUILD_ENV) $(GO_CMD) build -ldflags="$(LD_FLAGS)" -o $(BIN_DIR)/yaml-processor
 
+.PHONY: metricsdoc
+metricsdoc:
+	cd $(TOOLS_DIR)/metricsdoc && \
+	$(GO_BUILD_ENV) $(GO_CMD) build -ldflags="$(LD_FLAGS)" -o $(BIN_DIR)/metricsdoc
+
 ##@ Deployment
 
 ifndef ignore-not-found
@@ -549,6 +554,10 @@ generate-kueuectl-docs: kueuectl-docs
 generate-helm-docs: helm-docs
 	$(HELM_DOCS) -c $(PROJECT_DIR)/charts/kueue
 
+.PHONY: generate-metrics-tables
+generate-metrics-tables: metricsdoc
+	$(BIN_DIR)/metricsdoc --metrics-package=pkg/metrics --out=site/content/en/docs/reference/metrics.md
+
 # Build the ray-project-mini image
 .PHONY: ray-project-mini-image-build
 ray-project-mini-image-build:
diff --git a/cmd/experimental/kueue-populator/Makefile b/cmd/experimental/kueue-populator/Makefile
index dc78ffdfa..fc899d948 100644
--- a/cmd/experimental/kueue-populator/Makefile
+++ b/cmd/experimental/kueue-populator/Makefile
@@ -8,6 +8,20 @@ endif
 GO_CMD ?= go
 GO_FMT ?= gofmt
 
+ROOT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))/../../..
+BIN_DIR := $(ROOT_DIR)/bin
+
+HELM := $(BIN_DIR)/helm
+ENVTEST := $(BIN_DIR)/setup-envtest
+GINKGO := $(BIN_DIR)/ginkgo
+GOTESTSUM := $(BIN_DIR)/gotestsum
+ARTIFACTS ?= $(ROOT_DIR)/artifacts
+ENVTEST_K8S_VERSION ?= 1.34
+INTEGRATION_NPROCS ?= 4
+CONTROLLER_GEN := $(BIN_DIR)/controller-gen
+YAML_PROCESSOR = $(BIN_DIR)/yaml-processor
+YAML_PROCESSOR_LOG_LEVEL ?= info
+
 GIT_TAG ?= $(shell git describe --tags --dirty --always)
 GIT_COMMIT ?= $(shell git rev-parse HEAD)
 HOST_IMAGE_PLATFORM ?= linux/$(shell go env GOARCH)
@@ -26,7 +40,20 @@ BASE_BUILDER_IMAGE ?= golang:1.25.0
 BUILDER_IMAGE ?= $(BASE_BUILDER_IMAGE)
 CGO_ENABLED ?= 0
 
-HELM ?= helm
+$(YAML_PROCESSOR):
+	cd $(ROOT_DIR) && $(MAKE) yaml-processor
+
+$(GOTESTSUM):
+	cd $(ROOT_DIR) && $(MAKE) gotestsum
+
+$(ENVTEST):
+	cd $(ROOT_DIR) && $(MAKE) envtest
+
+$(GINKGO):
+	cd $(ROOT_DIR) && $(MAKE) ginkgo
+
+$(HELM):
+	cd $(ROOT_DIR) && $(MAKE) helm
 
 .PHONY: all
 all: fmt vet build
@@ -64,26 +91,12 @@ kind-image-build: PLATFORMS=linux/amd64
 kind-image-build: PUSH=--load
 kind-image-build: image-build
 
-# Integration tests
-ROOT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))/../../..
-BIN_DIR := $(ROOT_DIR)/bin
-ENVTEST := $(BIN_DIR)/setup-envtest
-GINKGO := $(BIN_DIR)/ginkgo
-GOTESTSUM := $(BIN_DIR)/gotestsum
-ARTIFACTS ?= $(ROOT_DIR)/artifacts
-ENVTEST_K8S_VERSION ?= 1.34
-INTEGRATION_NPROCS ?= 4
-CONTROLLER_GEN := $(BIN_DIR)/controller-gen
-
-YAML_PROCESSOR = $(BIN_DIR)/yaml-processor
-YAML_PROCESSOR_LOG_LEVEL ?= info
-
 .PHONY: manifests
 manifests:
 	$(CONTROLLER_GEN) rbac:roleName=kueue-populator-role output:rbac:artifacts:config=config/rbac paths="./pkg/controller/..."
 
 .PHONY: test-integration
-test-integration:
+test-integration: $(ENVTEST) $(GINKGO)
 	KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" \
 	PROJECT_DIR=$(ROOT_DIR)/ \
 	GOWORK=off $(GINKGO) -procs=$(INTEGRATION_NPROCS) -v ./test/integration/...
@@ -93,33 +106,34 @@ test-e2e:
 	./run-e2e.sh
 
 .PHONY: test
-test: ## Run unit tests.
+test: $(GOTESTSUM) ## Run unit tests.
+	mkdir -p $(ARTIFACTS)
 	GOWORK=off $(GOTESTSUM) --junitfile $(ARTIFACTS)/junit-unit.xml -- \
 		-race -coverpkg=./... -coverprofile $(ARTIFACTS)/cover-unit.out ./pkg/controller/...
 
-
 .PHONY: update-helm
 update-helm: manifests $(YAML_PROCESSOR)
 	$(YAML_PROCESSOR) -zap-log-level=$(YAML_PROCESSOR_LOG_LEVEL) hack/processing-plan.yaml
 
-$(YAML_PROCESSOR):
-	cd $(ROOT_DIR) && $(MAKE) yaml-processor
+.PHONY: helm-dep-build
+helm-dep-build: $(HELM)
+	$(HELM) dependency build charts/kueue-populator
 
 .PHONY: helm-lint
-helm-lint: ## Run Helm chart lint test.
+helm-lint: $(HELM) helm-dep-build ## Run Helm chart lint test.
 	$(HELM) lint charts/kueue-populator
 
 .PHONY: helm-verify
-helm-verify: helm-lint ## run helm template and detect any rendering failures
+helm-verify: $(HELM) helm-dep-build ## Run Helm chart lint test.
 	$(HELM) template charts/kueue-populator > /dev/null
 	$(HELM) template charts/kueue-populator --set image.repository=my-repo --set image.tag=v0.1.0 > /dev/null
 
 .PHONY: helm-unit-test
-helm-unit-test:
+helm-unit-test: $(HELM) helm-dep-build ## Run Helm chart unit tests.
 	$(HELM) unittest charts/kueue-populator --strict --debug
 
 .PHONY: helm-test
-helm-test: ## Run Helm chart integration tests (requires active cluster).
+helm-test: helm-lint helm-verify helm-unit-test ## Run Helm chart integration tests (requires active cluster).
 	$(HELM) test kueue-populator
 
 .PHONY: verify
diff --git a/hack/internal/tools/go.mod b/hack/internal/tools/go.mod
index 5297c4443..b50f20fc8 100644
--- a/hack/internal/tools/go.mod
+++ b/hack/internal/tools/go.mod
@@ -22,10 +22,10 @@ tool (
 
 require (
 	github.com/gohugoio/hugo v0.151.2
-	github.com/golangci/golangci-lint/v2 v2.6.1
+	github.com/golangci/golangci-lint/v2 v2.6.2
 	github.com/google/go-cmp v0.7.0
 	github.com/kubernetes-sigs/reference-docs/genref v0.28.0
-	github.com/mikefarah/yq/v4 v4.48.1
+	github.com/mikefarah/yq/v4 v4.49.2
 	github.com/norwoodj/helm-docs v1.14.2
 	github.com/onsi/ginkgo/v2 v2.27.2
 	go.uber.org/mock v0.6.0
@@ -33,12 +33,12 @@ require (
 	gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
 	gopkg.in/yaml.v3 v3.0.1
 	gotest.tools/gotestsum v1.13.0
-	helm.sh/helm/v4 v4.0.0
+	helm.sh/helm/v4 v4.0.1
 	k8s.io/code-generator v0.34.2
 	sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250407161039-6ad5c1dd4418
 	sigs.k8s.io/controller-tools v0.19.0
 	sigs.k8s.io/kind v0.30.0
-	sigs.k8s.io/kustomize/kustomize/v5 v5.7.1
+	sigs.k8s.io/kustomize/kustomize/v5 v5.8.0
 	sigs.k8s.io/mdtoc v1.4.0
 	sigs.k8s.io/yaml v1.6.0
 )
@@ -339,8 +339,8 @@ require (
 	github.com/makeworld-the-better-one/dither/v2 v2.4.0 // indirect
 	github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect
 	github.com/manuelarte/funcorder v0.5.0 // indirect
-	github.com/maratori/testableexamples v1.0.0 // indirect
-	github.com/maratori/testpackage v1.1.1 // indirect
+	github.com/maratori/testableexamples v1.0.1 // indirect
+	github.com/maratori/testpackage v1.1.2 // indirect
 	github.com/marekm4/color-extractor v1.2.1 // indirect
 	github.com/matoous/godox v1.1.0 // indirect
 	github.com/mattn/go-colorable v0.1.14 // indirect
@@ -473,6 +473,7 @@ require (
 	go.uber.org/multierr v1.11.0 // indirect
 	go.yaml.in/yaml/v2 v2.4.2 // indirect
 	go.yaml.in/yaml/v3 v3.0.4 // indirect
+	go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
 	gocloud.dev v0.43.0 // indirect
 	golang.org/x/crypto v0.45.0 // indirect
 	golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
@@ -527,9 +528,9 @@ require (
 	rsc.io/qr v0.2.0 // indirect
 	sigs.k8s.io/controller-runtime v0.22.3 // indirect
 	sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
-	sigs.k8s.io/kustomize/api v0.20.1 // indirect
-	sigs.k8s.io/kustomize/cmd/config v0.20.1 // indirect
-	sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
+	sigs.k8s.io/kustomize/api v0.21.0 // indirect
+	sigs.k8s.io/kustomize/cmd/config v0.21.0 // indirect
+	sigs.k8s.io/kustomize/kyaml v0.21.0 // indirect
 	sigs.k8s.io/randfill v1.0.0 // indirect
 	sigs.k8s.io/release-utils v0.12.1 // indirect
 	sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
diff --git a/hack/internal/tools/go.sum b/hack/internal/tools/go.sum
index aaa3e6e4a..cb2234afb 100644
--- a/hack/internal/tools/go.sum
+++ b/hack/internal/tools/go.sum
@@ -632,8 +632,8 @@ github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarog
 github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss=
 github.com/golangci/gofmt v0.0.0-20250704145412-3e58ba0443c6 h1:jlKy3uQkETB3zMBK8utduvojT+If2nDAM1pWpEzXjaY=
 github.com/golangci/gofmt v0.0.0-20250704145412-3e58ba0443c6/go.mod h1:OyaRySOXorMn8zJqFku8YsKptIhPkANyKKTMC+rqMCs=
-github.com/golangci/golangci-lint/v2 v2.6.1 h1:yTYVG6BJ4eG9QE815BAQDrC2xBbTB8lW11jg+YQ0b1c=
-github.com/golangci/golangci-lint/v2 v2.6.1/go.mod h1:zlg/a5aw46LvRBHZ3OOwwsCtkjWTrn3wm5qowoteCRg=
+github.com/golangci/golangci-lint/v2 v2.6.2 h1:jkMSVv36JmyTENcEertckvimvjPcD5qxNM7W7qhECvI=
+github.com/golangci/golangci-lint/v2 v2.6.2/go.mod h1:fSIMDiBt9kzdpnvvV7GO6iWzyv5uaeZ+iPor+2uRczE=
 github.com/golangci/golines v0.0.0-20250217232252-b35a6149b587 h1:RXtAfHDBWAv49/t94l3j9Iqvy6eXL/nm56EejqrZuQc=
 github.com/golangci/golines v0.0.0-20250217232252-b35a6149b587/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ=
 github.com/golangci/misspell v0.7.0 h1:4GOHr/T1lTW0hhR4tgaaV1WS/lJ+ncvYCoFKmqJsj0c=
@@ -867,10 +867,10 @@ github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLt
 github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM=
 github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8=
 github.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA=
-github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI=
-github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE=
-github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04=
-github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc=
+github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8=
+github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ=
+github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs=
+github.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc=
 github.com/marekm4/color-extractor v1.2.1 h1:3Zb2tQsn6bITZ8MBVhc33Qn1k5/SEuZ18mrXGUqIwn0=
 github.com/marekm4/color-extractor v1.2.1/go.mod h1:90VjmiHI6M8ez9eYUaXLdcKnS+BAOp7w+NpwBdkJmpA=
 github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
@@ -899,8 +899,8 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX
 github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
 github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
 github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
-github.com/mikefarah/yq/v4 v4.48.1 h1:xfw9oDxur6z+friOtMAuzSvQ9LPLb0LRDsl1e36DGCo=
-github.com/mikefarah/yq/v4 v4.48.1/go.mod h1:NbCTmYAY+KjwCZemVDqLxSAeZKjXFK04lgIiSx13tD0=
+github.com/mikefarah/yq/v4 v4.49.2 h1:GdMcAvlogQqV1IwW7ivoXjZBMVGA44L9X1yytzAkK2Y=
+github.com/mikefarah/yq/v4 v4.49.2/go.mod h1:iHgYxHyqA6VqQfre+TocMnqnk7e2+pNzAHusH8y95Wk=
 github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -1290,6 +1290,8 @@ go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
 go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
 go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
 go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
+go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
 gocloud.dev v0.43.0 h1:aW3eq4RMyehbJ54PMsh4hsp7iX8cO/98ZRzJJOzN/5M=
 gocloud.dev v0.43.0/go.mod h1:eD8rkg7LhKUHrzkEdLTZ+Ty/vgPHPCd+yMQdfelQVu4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -1705,8 +1707,8 @@ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
 gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
 helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k=
 helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc=
-helm.sh/helm/v4 v4.0.0 h1:Ppai7cygdmyxSR+JR9djUoVrRmyMI/yY5P5TBd25oHs=
-helm.sh/helm/v4 v4.0.0/go.mod h1:G1Y5AE+lJPQSAjh7nbXnhZrtGtxo+I6POSu9DruYiGI=
+helm.sh/helm/v4 v4.0.1 h1:WAfnyQEaEnTgaIPJssN+sPx0k1pkKldro8tE4q3c51A=
+helm.sh/helm/v4 v4.0.1/go.mod h1:G1Y5AE+lJPQSAjh7nbXnhZrtGtxo+I6POSu9DruYiGI=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1786,14 +1788,14 @@ sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7np
 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
 sigs.k8s.io/kind v0.30.0 h1:2Xi1KFEfSMm0XDcvKnUt15ZfgRPCT0OnCBbpgh8DztY=
 sigs.k8s.io/kind v0.30.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8=
-sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
-sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM=
-sigs.k8s.io/kustomize/cmd/config v0.20.1 h1:4APUORmZe2BYrsqgGfEKdd/r7gM6i43egLrUzilpiFo=
-sigs.k8s.io/kustomize/cmd/config v0.20.1/go.mod h1:R7rQ8kxknVlXWVUIbxWtMgu8DCCNVtl8V0KrmeVd/KE=
-sigs.k8s.io/kustomize/kustomize/v5 v5.7.1 h1:sYJsarwy/SDJfjjLMUqwFDGPwzUtMOQ1i1Ed49+XSbw=
-sigs.k8s.io/kustomize/kustomize/v5 v5.7.1/go.mod h1:+5/SrBcJ4agx1SJknGuR/c9thwRSKLxnKoI5BzXFaLU=
-sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78=
-sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po=
+sigs.k8s.io/kustomize/api v0.21.0 h1:I7nry5p8iDJbuRdYS7ez8MUvw7XVNPcIP5GkzzuXIIQ=
+sigs.k8s.io/kustomize/api v0.21.0/go.mod h1:XGVQuR5n2pXKWbzXHweZU683pALGw/AMVO4zU4iS8SE=
+sigs.k8s.io/kustomize/cmd/config v0.21.0 h1:ikLtzcNK9isBqSaXXhAg7LRCTNKdp70z5v/c4Y55DOw=
+sigs.k8s.io/kustomize/cmd/config v0.21.0/go.mod h1:oxa6eRzeLWUcE7M3Rmio29Sfc4KpqGspHur3GjOYqNA=
+sigs.k8s.io/kustomize/kustomize/v5 v5.8.0 h1:CCIJK7z/xJOlkXOaDOcL2jprV53a/eloiL02wg7oJJs=
+sigs.k8s.io/kustomize/kustomize/v5 v5.8.0/go.mod h1:qewGAExYZK9LbPPbnJMPK5HQ8nsdxRzpclIg0qslzDo=
+sigs.k8s.io/kustomize/kyaml v0.21.0 h1:7mQAf3dUwf0wBerWJd8rXhVcnkk5Tvn/q91cGkaP6HQ=
+sigs.k8s.io/kustomize/kyaml v0.21.0/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
 sigs.k8s.io/mdtoc v1.4.0 h1:2pDEwJSjoVrGr5BPkG+LoLkYLKvgtGYurrBY8ul3SxQ=
 sigs.k8s.io/mdtoc v1.4.0/go.mod h1:KVnRRtK1rX9aQ95qF0rt3x2ytTxf3r7W7N41H+0KF0k=
 sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
diff --git a/hack/internal/tools/metricsdoc/main.go b/hack/internal/tools/metricsdoc/main.go
new file mode 100644
index 000000000..1e4786278
--- /dev/null
+++ b/hack/internal/tools/metricsdoc/main.go
@@ -0,0 +1,381 @@
+/*
+Copyright The Kubernetes 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 main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"go/ast"
+	"go/parser"
+	"go/token"
+	"maps"
+	"os"
+	"regexp"
+	"slices"
+	"strings"
+)
+
+type Metric struct {
+	FullName  string
+	Type      string
+	Help      string
+	Labels    []string
+	Group     string
+	LabelDocs map[string]string
+}
+
+var (
+	inPackagePath string
+	outFile       string
+)
+
+func main() {
+	flag.StringVar(&inPackagePath, "metrics-package", "pkg/metrics", "path to the metrics package directory (preferred)")
+	flag.StringVar(&outFile, "out", "site/content/en/docs/reference/metrics.md", "output markdown file to update in-place")
+	flag.Parse()
+
+	metrics, err := extractMetricsFromPackage(inPackagePath)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "extract: %v\n", err)
+		os.Exit(1)
+	}
+
+	content, err := os.ReadFile(outFile)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "read out: %v\n", err)
+		os.Exit(1)
+	}
+	rendered := renderTables(metrics)
+
+	out := string(content)
+	// Replace only content between markers per group key
+	for key, table := range rendered {
+		pat := regexp.MustCompile(fmt.Sprintf(`(?s)<!--\s*BEGIN GENERATED TABLE:\s*%s\s*-->.*?<!--\s*END GENERATED TABLE:\s*%s\s*-->`, regexp.QuoteMeta(key), regexp.QuoteMeta(key)))
+		block := fmt.Sprintf("<!-- BEGIN GENERATED TABLE: %s -->\n%s<!-- END GENERATED TABLE: %s -->", key, table, key)
+		if pat.MatchString(out) {
+			out = pat.ReplaceAllString(out, block)
+		}
+	}
+
+	if err := os.WriteFile(outFile, []byte(out), 0644); err != nil {
+		fmt.Fprintf(os.Stderr, "write: %v\n", err)
+		os.Exit(1)
+	}
+}
+
+func extractMetricsFromPackage(dir string) ([]Metric, error) {
+	fset := token.NewFileSet()
+	pkgs, err := parser.ParseDir(fset, dir, nil, parser.ParseComments)
+	if err != nil {
+		return nil, err
+	}
+	var all []Metric
+	var missingGroups []string
+	for _, pkg := range pkgs {
+		for _, f := range pkg.Files {
+			ast.Inspect(f, func(n ast.Node) bool {
+				vs, ok := n.(*ast.ValueSpec)
+				if !ok || len(vs.Values) != 1 {
+					return true
+				}
+				groupFromComment, labelDocs := parseMarkers(vs)
+				call, ok := vs.Values[0].(*ast.CallExpr)
+				if !ok {
+					return true
+				}
+				sel, ok := call.Fun.(*ast.SelectorExpr)
+				if !ok {
+					return true
+				}
+				pkgIdent, ok := sel.X.(*ast.Ident)
+				if !ok || pkgIdent.Name != "prometheus" {
+					return true
+				}
+				fun := sel.Sel.Name
+				if fun != "NewCounterVec" && fun != "NewGaugeVec" && fun != "NewHistogramVec" {
+					return true
+				}
+				if len(call.Args) < 2 {
+					return true
+				}
+				opts, ok := call.Args[0].(*ast.CompositeLit)
+				if !ok {
+					return true
+				}
+				var name, help string
+				for _, elt := range opts.Elts {
+					kv, ok := elt.(*ast.KeyValueExpr)
+					if !ok {
+						continue
+					}
+					var keyName string
+					switch k := kv.Key.(type) {
+					case *ast.Ident:
+						keyName = k.Name
+					case *ast.SelectorExpr:
+						keyName = k.Sel.Name
+					default:
+						keyName = exprToString(kv.Key)
+					}
+					switch keyName {
+					case "Name":
+						name = stringLiteral(kv.Value)
+					case "Help":
+						help = stringLiteral(kv.Value)
+					}
+				}
+				labels := parseLabels(call.Args[1])
+				m := Metric{
+					FullName:  "kueue_" + name,
+					Type:      map[string]string{"NewCounterVec": "Counter", "NewGaugeVec": "Gauge", "NewHistogramVec": "Histogram"}[fun],
+					Help:      normalizeHelp(help),
+					Labels:    labels,
+					LabelDocs: labelDocs,
+				}
+				if groupFromComment == "" {
+					varName := ""
+					if len(vs.Names) > 0 && vs.Names[0] != nil {
+						varName = vs.Names[0].Name
+					}
+					missingGroups = append(missingGroups, varName)
+					return true
+				}
+				m.Group = groupFromComment
+				all = append(all, m)
+				return true
+			})
+		}
+	}
+	if len(missingGroups) > 0 {
+		return nil, fmt.Errorf("missing metricsdoc:group marker on metrics: %s", strings.Join(missingGroups, ", "))
+	}
+	// stable sort within package groups is applied later in render
+	return all, nil
+}
+
+func parseLabels(arg ast.Expr) []string {
+	var labels []string
+	cl, ok := arg.(*ast.CompositeLit)
+	if !ok {
+		return labels
+	}
+	for _, elt := range cl.Elts {
+		labels = append(labels, stringLiteral(elt))
+	}
+	return labels
+}
+
+func stringLiteral(e ast.Expr) string {
+	switch v := e.(type) {
+	case *ast.BasicLit:
+		s := v.Value
+		s = strings.Trim(s, "`")
+		s = strings.Trim(s, "\"")
+		return s
+	case *ast.BinaryExpr:
+		// Handle concatenation with +
+		return stringLiteral(v.X) + stringLiteral(v.Y)
+	default:
+		return exprToString(e)
+	}
+}
+
+func exprToString(e ast.Expr) string {
+	var buf bytes.Buffer
+	ast.Fprint(&buf, token.NewFileSet(), e, nil)
+	return buf.String()
+}
+
+func renderTables(ms []Metric) map[string]string {
+	by := map[string][]Metric{}
+	for _, m := range ms {
+		by[m.Group] = append(by[m.Group], m)
+	}
+	for k := range by {
+		slices.SortFunc(by[k], func(a, b Metric) int {
+			if a.FullName < b.FullName {
+				return -1
+			} else if a.FullName > b.FullName {
+				return 1
+			}
+			return 0
+		})
+	}
+	out := map[string]string{}
+	for key, list := range by {
+		var b strings.Builder
+		writeHeader(&b)
+		for _, m := range list {
+			desc := sanitizeForTable(m.Help)
+			labels := formatLabels(m.Labels, m.LabelDocs)
+			fmt.Fprintf(&b, "| `%s` | %s | %s | %s |\n", m.FullName, m.Type, desc, labels)
+		}
+		out[key] = b.String()
+	}
+	return out
+}
+
+func writeHeader(b *strings.Builder) {
+	b.WriteString("| Metric name | Type | Description | Labels |\n")
+	b.WriteString("| --- | --- | --- | --- |\n")
+}
+
+func sanitizeForTable(s string) string {
+	s = strings.ReplaceAll(s, "\r\n", "\n")
+	s = strings.TrimSpace(s)
+	s = strings.ReplaceAll(s, "\n", "<br>")
+	return s
+}
+
+func normalizeHelp(s string) string { return strings.TrimSpace(s) }
+
+func formatLabels(labels []string, docs map[string]string) string {
+	if len(labels) == 0 {
+		return ""
+	}
+	var parts []string
+	for _, l := range labels {
+		if docs != nil {
+			if desc, ok := docs[l]; ok && desc != "" {
+				parts = append(parts, fmt.Sprintf("`%s`: %s", l, desc))
+				continue
+			}
+		}
+		parts = append(parts, fmt.Sprintf("`%s`", l))
+	}
+	return strings.Join(parts, "<br> ")
+}
+
+var (
+	groupMarker = regexp.MustCompile(`(?i)\+\s*metricsdoc:group\s*=\s*([a-z_]+)`)
+	labelMarker = regexp.MustCompile(`(?i)\+\s*metricsdoc:labels\s*=\s*([^\n\r]*)`)
+)
+
+func parseMarkers(vs *ast.ValueSpec) (string, map[string]string) {
+	var group string
+	labelDocs := map[string]string{}
+	for _, text := range commentTexts(vs) {
+		if group == "" {
+			if m := groupMarker.FindStringSubmatch(text); len(m) == 2 {
+				group = strings.ToLower(m[1])
+			}
+		}
+		matches := labelMarker.FindAllStringSubmatch(text, -1)
+		for _, match := range matches {
+			if len(match) != 2 {
+				continue
+			}
+			maps.Copy(labelDocs, parseLabelAnnotations(match[1]))
+		}
+	}
+	if len(labelDocs) == 0 {
+		return group, nil
+	}
+	return group, labelDocs
+}
+
+func commentTexts(vs *ast.ValueSpec) []string {
+	var out []string
+	if vs.Doc != nil {
+		for _, c := range vs.Doc.List {
+			if c != nil {
+				out = append(out, c.Text)
+			}
+		}
+	}
+	if vs.Comment != nil {
+		for _, c := range vs.Comment.List {
+			if c != nil {
+				out = append(out, c.Text)
+			}
+		}
+	}
+	return out
+}
+
+func parseLabelAnnotations(body string) map[string]string {
+	body = strings.TrimSpace(body)
+	body = strings.TrimSuffix(body, "*/")
+	body = strings.TrimSpace(body)
+	out := map[string]string{}
+	for _, pair := range splitLabelPairs(body) {
+		if pair == "" {
+			continue
+		}
+		kv := strings.SplitN(pair, "=", 2)
+		if len(kv) != 2 {
+			continue
+		}
+		key := stripQuotes(strings.TrimSpace(kv[0]))
+		val := stripQuotes(strings.TrimSpace(kv[1]))
+		if key == "" || val == "" {
+			continue
+		}
+		out[key] = val
+	}
+	return out
+}
+
+func splitLabelPairs(body string) []string {
+	var parts []string
+	var current strings.Builder
+	var quote rune
+	for _, r := range body {
+		switch r {
+		case '"', '\'', '`':
+			switch quote {
+			case 0:
+				quote = r
+			case r:
+				quote = 0
+			}
+			current.WriteRune(r)
+		case ',':
+			if quote != 0 {
+				current.WriteRune(r)
+				continue
+			}
+			part := strings.TrimSpace(current.String())
+			if part != "" {
+				parts = append(parts, part)
+			}
+			current.Reset()
+		default:
+			current.WriteRune(r)
+		}
+	}
+	if current.Len() > 0 {
+		part := strings.TrimSpace(current.String())
+		if part != "" {
+			parts = append(parts, part)
+		}
+	}
+	return parts
+}
+
+func stripQuotes(s string) string {
+	if len(s) < 2 {
+		return s
+	}
+	first := s[0]
+	last := s[len(s)-1]
+	if (first == last) && (first == '"' || first == '\'' || first == '`') {
+		return s[1 : len(s)-1]
+	}
+	return s
+}
diff --git a/hack/releasing/promote_pull.sh b/hack/releasing/promote_pull.sh
new file mode 100755
index 000000000..967d6cb01
--- /dev/null
+++ b/hack/releasing/promote_pull.sh
@@ -0,0 +1,393 @@
+#!/usr/bin/env bash
+
+# Copyright 2025 The Kubernetes 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.
+
+set -o errexit
+set -o nounset
+set -o pipefail
+
+KUBERNETES_SIGS_KUEUE_PATH="$(git rev-parse --show-toplevel)"
+declare -r KUBERNETES_SIGS_KUEUE_PATH
+
+cd "$KUBERNETES_SIGS_KUEUE_PATH"
+
+KUBERNETES_SIGS_KUEUE_UPSTREAM_REMOTE=${KUBERNETES_SIGS_KUEUE_UPSTREAM_REMOTE:-upstream}
+KUBERNETES_SIGS_KUEUE_FORK_REMOTE=${KUBERNETES_SIGS_KUEUE_FORK_REMOTE:-origin}
+KUBERNETES_SIGS_KUEUE_MAIN_REPO_ORG=${KUBERNETES_SIGS_KUEUE_MAIN_REPO_ORG:-$(git remote get-url "$KUBERNETES_SIGS_KUEUE_UPSTREAM_REMOTE" | awk '{gsub(/http[s]:\/\/|git@/,"")}1' | awk -F'[@:./]' 'NR==1{print $3}')}
+KUBERNETES_SIGS_KUEUE_MAIN_REPO_NAME=${KUBERNETES_SIGS_KUEUE_MAIN_REPO_NAME:-$(git remote get-url "$KUBERNETES_SIGS_KUEUE_UPSTREAM_REMOTE" | awk '{gsub(/http[s]:\/\/|git@/,"")}1' | awk -F'[@:./]' 'NR==1{print $4}')}
+
+# shellcheck source=hack/releasing/common.sh
+source "${KUBERNETES_SIGS_KUEUE_PATH}/hack/releasing/common.sh"
+
+if [[ -v KUBERNETES_REPOS_PATH ]]; then
+  KUBERNETES_REPOS_PATH=$(resolve_path "${KUBERNETES_REPOS_PATH}")
+  if [[ ! -d "${KUBERNETES_REPOS_PATH}" ]]; then
+    echo "!!! Invalid value for KUBERNETES_REPOS_PATH: the path \"${KUBERNETES_REPOS_PATH}\" does not exist."
+    exit 1
+  fi
+else
+  KUBERNETES_REPOS_PATH="$(resolve_path "${KUBERNETES_SIGS_KUEUE_PATH}/../../kubernetes")"
+fi
+declare -r KUBERNETES_REPOS_PATH
+
+if [[ -v KUBERNETES_K8S_IO_PATH ]]; then
+  KUBERNETES_K8S_IO_PATH=$(resolve_path "${KUBERNETES_K8S_IO_PATH}")
+  if [[ ! -d "${KUBERNETES_K8S_IO_PATH}" ]]; then
+    echo "!!! Invalid value for KUBERNETES_K8S_IO_PATH: the path \"${KUBERNETES_K8S_IO_PATH}\" does not exist."
+    exit 1
+  fi
+else
+  KUBERNETES_K8S_IO_PATH="${KUBERNETES_REPOS_PATH}/k8s.io"
+fi
+declare -r KUBERNETES_K8S_IO_PATH
+
+K8S_STAGING_KUEUE_PATH="${KUBERNETES_K8S_IO_PATH}/registry.k8s.io/images/k8s-staging-kueue"
+if [[ ! -d "${K8S_STAGING_KUEUE_PATH}" ]]; then
+  echo "!!! The path \"${K8S_STAGING_KUEUE_PATH}\" does not exist."
+  exit 1
+fi
+declare -r K8S_STAGING_KUEUE_PATH
+
+cd "${K8S_STAGING_KUEUE_PATH}"
+
+STARTING_BRANCH=$(git symbolic-ref --short HEAD)
+declare -r STARTING_BRANCH
+declare -r REBASE_MAGIC=".git/rebase-apply"
+DRY_RUN=${DRY_RUN:-""}
+KUBERNETES_K8S_IO_UPSTREAM_REMOTE=${KUBERNETES_K8S_IO_UPSTREAM_REMOTE:-upstream}
+KUBERNETES_K8S_IO_FORK_REMOTE=${KUBERNETES_K8S_IO_FORK_REMOTE:-origin}
+KUBERNETES_K8S_IO_MAIN_REPO_ORG=$(git remote get-url "$KUBERNETES_K8S_IO_UPSTREAM_REMOTE" | cut -d: -f2- | cut -d/ -f1)
+KUBERNETES_K8S_IO_MAIN_REPO_NAME=$(git remote get-url "$KUBERNETES_K8S_IO_UPSTREAM_REMOTE" | cut -d: -f2- | cut -d/ -f2- | sed 's/\.git$//')
+
+if [[ -z ${GITHUB_USER:-} ]]; then
+  echo "Please export GITHUB_USER=<your-user> (or GH organization, if that's where your fork lives)"
+  exit 1
+fi
+
+if ! command -v gh > /dev/null; then
+  echo "Can't find 'gh' tool in PATH, please install from https://github.com/cli/cli"
+  exit 1
+fi
+
+if [[ "$#" -ne 1 ]]; then
+  echo "${0} <version>"
+  echo
+  echo "  Create promote PR"
+  echo
+  echo "  Example:"
+  echo "    $0 v0.13.2"
+  echo
+  echo "  Set the DRY_RUN environment var to skip git push and creating PR."
+  echo "  When DRY_RUN is set the script will leave you in a branch containing the commits."
+  echo
+  echo "  Set KUBERNETES_K8S_IO_UPSTREAM_REMOTE (default: upstream) and KUBERNETES_K8S_IO_FORK_REMOTE (default: origin)"
+  echo "  to override the default remote names to what you have locally."
+  echo
+  echo "  Set KUBERNETES_REPOS_PATH (default: ../../kubernetes) and KUBERNETES_K8S_IO_PATH (default: ../../kubernetes/k8s.io)"
+  echo "  to override the default kubernetes paths to what you have locally."
+  exit 2
+fi
+
+# Checks if you are logged in. Will error/bail if you are not.
+gh auth status
+
+if git_status=$(git status --porcelain --untracked=no 2>/dev/null) && [[ -n "${git_status}" ]]; then
+  echo "!!! Dirty tree. Clean up and try again."
+  exit 1
+fi
+
+if [[ -e "${REBASE_MAGIC}" ]]; then
+  echo "!!! 'git rebase' or 'git am' in progress. Clean up and try again."
+  exit 1
+fi
+
+declare -r RELEASE_VERSION="$1"
+
+if [[ ! "$RELEASE_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+  echo "!!! Invalid release version. It should be semantic version like v0.13.2"
+  exit 1
+fi
+
+IFS='.' read -r MAJOR MINOR _ <<< "${RELEASE_VERSION#v}"
+
+declare -r MAJOR_MINOR="$MAJOR.$MINOR"
+
+LAST_SUPPORT_MINOR=$((MINOR - 1))
+if [ "$LAST_SUPPORT_MINOR" -lt 0 ]; then
+  LAST_SUPPORT_MINOR=0  # Prevent negative minors
+fi
+
+RELEASE_ISSUE_NAME="Release ${RELEASE_VERSION}"
+
+RELEASE_ISSUE_NUMBER=$(gh issue list --repo="${KUBERNETES_SIGS_KUEUE_MAIN_REPO_ORG}/${KUBERNETES_SIGS_KUEUE_MAIN_REPO_NAME}" --search "in:title ${RELEASE_ISSUE_NAME}" | awk '{print $1}' || true)
+if [ -z "$RELEASE_ISSUE_NUMBER" ]; then
+  echo "!!! No release issue found for version ${RELEASE_VERSION}. Please create 'Release ${RELEASE_VERSION}' issue first."
+  exit 1
+fi
+
+RELEASE_ISSUE=$(gh issue view "${RELEASE_ISSUE_NUMBER}" --repo="${KUBERNETES_SIGS_KUEUE_MAIN_REPO_ORG}/${KUBERNETES_SIGS_KUEUE_MAIN_REPO_NAME}" --json body || true)
+if [ -z "$RELEASE_ISSUE" ]; then
+  echo "!!! No release issue found for version ${RELEASE_VERSION}. Please create 'Release ${RELEASE_VERSION}' issue first."
+  exit 1
+fi
+
+RELEASE_ISSUE_BODY=$(echo "${RELEASE_ISSUE}" | jq -r '.body')
+
+clean_branches=()
+function cleanup {
+  # Return to the starting branch and delete specified branches
+  echo
+  echo "+++ Returning to the ${STARTING_BRANCH} branch."
+  git checkout -f "${STARTING_BRANCH}" >/dev/null 2>&1 || {
+    echo "!!! Failed to return to ${STARTING_BRANCH}. Please check your git status."
+    return 1
+  }
+
+  if [[ ${#clean_branches[@]} -eq 0 ]]; then
+    echo "!!! No branches specified for cleanup."
+    return 0
+  fi
+
+  for branch in "${clean_branches[@]}"; do
+    if [[ -n "${branch}" && -z "${DRY_RUN}" ]]; then
+      echo "!!! Deleting branch ${branch}."
+      git branch -D "${branch}" >/dev/null 2>&1 || {
+        echo "!!! Failed to delete branch ${branch}. It may not exist or is currently checked out."
+      }
+    else
+      echo "!!! Skipping deletion of branch ${branch} because DRY_RUN is set."
+      echo "To delete this branch manually:"
+      echo "  git branch -D ${branch}"
+    fi
+  done
+}
+trap cleanup EXIT
+
+# $1 - base branch
+# $2 - remote branch
+# $3 - pr name
+function make_pr() {
+  local rel
+  rel="$(basename "$1")"
+
+  echo
+  echo "+++ Creating a pull request on GitHub at ${GITHUB_USER}:$2 for ${rel}"
+
+  pr_text=$(cat <<EOF
+#### What this PR does / why we need it:
+$3.
+
+#### Which issue(s) this PR fixes:
+Part of ${KUBERNETES_SIGS_KUEUE_MAIN_REPO_ORG}/${KUBERNETES_SIGS_KUEUE_MAIN_REPO_NAME}#${RELEASE_ISSUE_NUMBER}
+EOF
+)
+
+   gh pr create --title="$3" --body="${pr_text}" --head "${GITHUB_USER}:$2" --base "${rel}" --repo="${KUBERNETES_K8S_IO_MAIN_REPO_ORG}/${KUBERNETES_K8S_IO_MAIN_REPO_NAME}"
+}
+
+# $1 - images file
+# $2 - release version
+# $3 - digest
+# $4 - component
+function insert_image() {
+  local images_file="$1"
+  local release_version="$2"
+  local digest="$3"
+  local component="$4"
+
+  echo "+++ Insert image $component@$release_version:$digest"
+
+  tmp_file=$(mktemp)
+
+  awk -v component="${component}" -v release_version="${release_version}" -v digest="${digest}" '
+    BEGIN {
+      new_line = sprintf("    \"%s\": [\"%s\"]", digest, release_version);
+      found_component = 0;
+      in_component = 0;
+      inserted = 0;
+
+      # Remove "v" prefix
+      core_release_version = gsub(/^v/, "", version);
+
+      # Split release_version into major, minor, patch
+      split(release_version, vref, ".");
+
+      ref_major = vref[1] + 0;  # Convert to number
+      ref_minor = vref[2] + 0;
+      ref_patch = vref[3] + 0;
+    }
+
+    # Skip empty lines
+    /^[[:space:]]*$/ {
+       next;
+    }
+
+    # Match lines starting with "- name: " followed by the component name
+    /^[[:space:]]*- name: / {
+      if (in_component && !inserted) {
+        print new_line;
+      }
+
+      found_component = 1;
+      in_component = ($3 == component) ? 1 : 0;
+      inserted = 0;
+
+      print;
+      next;
+    }
+
+    # Insert the new entry before the line containing the version reference
+    in_component && !inserted && $0 ~ /v?[0-9]+\.[0-9]+\.[0-9]+/ {
+      line = $0;
+
+      # Remove everything before the array
+      sub(/.*:[[:space:]]*\[/, "", line);
+      # Remove trailing ] and anything after
+      sub(/\][[:space:]]*.*$/, "", line);
+
+      # Split into versions (e.g., "v0.1.0", "v0.1.12")
+      n = split(line, versions, /,[[:space:]]*/);
+
+      for (i = 1; i <= n; i++) {
+        version = versions[i];
+
+        gsub(/^"/, "", version);
+        gsub(/"$/, "", version);
+
+        # Skip empty versions
+        if (version == "") {
+          continue;
+        }
+
+        # Check if version matches vMAJOR.MINOR.PATCH
+        if (version ~ /^v?[0-9]+\.[0-9]+\.[0-9]+$/) {
+          # Remove "v" prefix
+          gsub(/^v/, "", version);
+
+          # Split version into components
+          split(version, v, "\.");
+
+          major = v[1] + 0;
+          minor = v[2] + 0;
+          patch = v[3] + 0;
+
+          # Compare with release_version
+          if (major < ref_major ||
+              (major == ref_major && minor < ref_minor) ||
+              (major == ref_major && minor == ref_minor && patch < ref_patch)) {
+              print new_line;
+              inserted = 1;
+              break;
+          }
+        }
+      }
+      print;
+      next;
+    }
+    # Print all other lines unchanged
+    { print; }
+  ' "${images_file}" > "$tmp_file"
+
+  mv "$tmp_file" "$IMAGES_FILE"
+}
+
+declare -r IMAGES_FILE="images.yaml"
+declare -r STAGING_IMAGE_REGISTRY="us-central1-docker.pkg.dev/k8s-staging-images/kueue"
+
+# $1 - image name
+# $1 - version
+function get_image_full_name() {
+  local image_name="$1"
+  local version="$2"
+  local full_image_name="${STAGING_IMAGE_REGISTRY}/${image_name}:${version}"
+  echo "${full_image_name}"
+}
+
+# $1 - full image name
+function get_image_details() {
+  local full_image_name="$1"
+  gcloud container images describe "${full_image_name}" --verbosity error --format json || true
+}
+
+# $1 - base branch
+# $2 - local branch
+# $3 - commit message
+function prepare_local_branch() {
+  echo "+++ Creating local branch $2"
+  git checkout -b "$2" "${KUBERNETES_K8S_IO_UPSTREAM_REMOTE}/$1"
+  clean_branches+=("$2")
+
+  while IFS= read -r name; do
+    version="$RELEASE_VERSION"
+    if [ "${name}" == "charts/kueue" ]; then
+      version="${version#v}"
+    fi
+
+    full_image_name=$(get_image_full_name "$name" "$version")
+    image_details=$(get_image_details "$full_image_name")
+    if [ -z "$image_details" ]; then
+      echo " !!! Image \"${full_image_name}\" is not found."
+      exit 1
+    fi
+    digest=$(echo "$image_details" | jq -r '.image_summary.digest')
+    insert_image "$IMAGES_FILE" "$version" "$digest" "$name"
+  done < <(yq e '.[] | .name' "${IMAGES_FILE}")
+
+  git add .
+  git commit -m "$3"
+}
+
+# $1 - base branch
+# $2 - remote branch
+# $3 - local branch
+# $4 - pr name
+function push_and_create_pr() {
+  if [[ -n "${DRY_RUN}" ]]; then
+    echo "!!! Skipping git push and PR creation because you set DRY_RUN."
+    return
+  fi
+
+  echo
+  echo "+++ I'm about to do the following to push to GitHub (and I'm assuming ${KUBERNETES_K8S_IO_FORK_REMOTE} is your personal fork):"
+  echo
+  echo "  git push ${KUBERNETES_K8S_IO_FORK_REMOTE} ${3}:${2}"
+  echo
+
+  read -p "+++ Proceed (anything other than 'y' aborts it)? [y/N] " -r
+  if ! [[ "${REPLY}" =~ ^[yY]$ ]]; then
+    echo "Aborting." >&2
+  else
+    git push "${KUBERNETES_K8S_IO_FORK_REMOTE}" -f "${3}:${2}"
+    make_pr "$1" "$2" "$4"
+  fi
+}
+
+K8S_IO_BRANCH="kueue-promote-${MAJOR_MINOR}"
+declare -r K8S_IO_BRANCH
+K8S_IO_BRANCH_UNIQUE="${K8S_IO_BRANCH}-$(date +%s)"
+declare -r K8S_IO_BRANCH_UNIQUE
+K8S_IO_PR_NAME="Kueue: Promote ${MAJOR_MINOR}"
+declare -r K8S_IO_PR_NAME
+
+prepare_local_branch main "${K8S_IO_BRANCH_UNIQUE}" "${K8S_IO_PR_NAME}"
+push_and_create_pr main "${K8S_IO_BRANCH}" "${K8S_IO_BRANCH_UNIQUE}" "${K8S_IO_PR_NAME}"
+
+K8S_IO_PR_NUMBER=$(gh pr list --repo="${KUBERNETES_K8S_IO_MAIN_REPO_ORG}/${KUBERNETES_K8S_IO_MAIN_REPO_NAME}" | grep "${K8S_IO_PR_NAME}" | awk '{print $1}' || true)
+if [ -n "$K8S_IO_PR_NUMBER" ]; then
+  NEW_RELEASE_ISSUE_BODY=${RELEASE_ISSUE_BODY//<!-- K8S_IO_PULL -->/${KUBERNETES_K8S_IO_MAIN_REPO_ORG}/${KUBERNETES_K8S_IO_MAIN_REPO_NAME}#${K8S_IO_PR_NUMBER}}
+  gh issue edit "${RELEASE_ISSUE_NUMBER}" --body "${NEW_RELEASE_ISSUE_BODY}" --repo="${KUBERNETES_SIGS_KUEUE_MAIN_REPO_ORG}/${KUBERNETES_SIGS_KUEUE_MAIN_REPO_NAME}" || {
+    echo "!!! Failed to edit release issue \"${RELEASE_ISSUE_NAME}\": gh issue edit command failed."
+  }
+fi
\ No newline at end of file
diff --git a/pkg/controller/admissionchecks/multikueue/workload.go b/pkg/controller/admissionchecks/multikueue/workload.go
index 5223a9e94..3cd595ae6 100644
--- a/pkg/controller/admissionchecks/multikueue/workload.go
+++ b/pkg/controller/admissionchecks/multikueue/workload.go
@@ -443,8 +443,8 @@ func (w *wlReconciler) nominateAndSynchronizeWorkers(ctx context.Context, group
 			nominatedWorkers = append(nominatedWorkers, workerName)
 		}
 		if group.local.Status.ClusterName == nil && !equality.Semantic.DeepEqual(group.local.Status.NominatedClusterNames, nominatedWorkers) {
-			if err := workload.PatchAdmissionStatus(ctx, w.client, group.local, w.clock, func() (bool, error) {
-				group.local.Status.NominatedClusterNames = nominatedWorkers
+			if err := workload.PatchAdmissionStatus(ctx, w.client, group.local, w.clock, func(wl *kueue.Workload) (bool, error) {
+				wl.Status.NominatedClusterNames = nominatedWorkers
 				return true, nil
 			}); err != nil {
 				log.V(2).Error(err, "Failed to patch nominated clusters", "workload", klog.KObj(group.local))
@@ -646,9 +646,9 @@ func (w *wlReconciler) syncReservingRemoteState(ctx context.Context, group *wlGr
 		return nil
 	}
 
-	if err := workload.PatchAdmissionStatus(ctx, w.client, group.local, w.clock, func() (bool, error) {
+	if err := workload.PatchAdmissionStatus(ctx, w.client, group.local, w.clock, func(wl *kueue.Workload) (bool, error) {
 		if needsTopologyUpdate {
-			updateDelayedTopologyRequest(group.local, group.remotes[reservingRemote])
+			updateDelayedTopologyRequest(wl, group.remotes[reservingRemote])
 		}
 
 		if needsACUpdate {
@@ -662,10 +662,10 @@ func (w *wlReconciler) syncReservingRemoteState(ctx context.Context, group *wlGr
 			// update the transition time since is used to detect the lost worker state.
 			acs.LastTransitionTime = metav1.NewTime(w.clock.Now())
 
-			workload.SetAdmissionCheckState(&group.local.Status.AdmissionChecks, *acs, w.clock)
+			workload.SetAdmissionCheckState(&wl.Status.AdmissionChecks, *acs, w.clock)
 			// Set the cluster name to the reserving remote and clear the nominated clusters.
-			group.local.Status.ClusterName = &reservingRemote
-			group.local.Status.NominatedClusterNames = nil
+			wl.Status.ClusterName = &reservingRemote
+			wl.Status.NominatedClusterNames = nil
 		}
 
 		return true, nil
diff --git a/pkg/controller/core/workload_controller.go b/pkg/controller/core/workload_controller.go
index a8d81e039..f4fc1de1d 100644
--- a/pkg/controller/core/workload_controller.go
+++ b/pkg/controller/core/workload_controller.go
@@ -196,12 +196,12 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 	}
 
 	if requeueAt := workload.NeedsRequeueAtUpdate(&wl, r.clock); requeueAt != nil {
-		err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func() (bool, error) {
+		err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 			if wl.Status.RequeueState == nil {
 				wl.Status.RequeueState = &kueue.RequeueState{}
 			}
 			log.V(2).Info("At least one admission check set a retry time", "requeueAt", requeueAt, "current", wl.Status.RequeueState.RequeueAt)
-			workload.SetRequeueState(&wl, *requeueAt, false)
+			workload.SetRequeueState(wl, *requeueAt, false)
 			return true, nil
 		})
 		return reconcile.Result{}, client.IgnoreNotFound(err)
@@ -287,7 +287,7 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 				log.V(3).Info("Pending workload requeued after backoff")
 
 				// Clear RequeueAt since backoff has elapsed
-				err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func() (bool, error) {
+				err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 					if wl.Status.RequeueState != nil {
 						wl.Status.RequeueState.RequeueAt = nil
 					}
@@ -309,11 +309,11 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 
 		var updated bool
 		var requeueAfter time.Duration
-		err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func() (bool, error) {
+		err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 			if cond := apimeta.FindStatusCondition(wl.Status.Conditions, kueue.WorkloadRequeued); cond != nil && cond.Status == metav1.ConditionFalse {
 				switch cond.Reason {
 				case kueue.WorkloadDeactivated:
-					workload.SetRequeuedCondition(&wl, kueue.WorkloadReactivated, "The workload was reactivated", true)
+					workload.SetRequeuedCondition(wl, kueue.WorkloadReactivated, "The workload was reactivated", true)
 					updated = true
 				case kueue.WorkloadEvictedByPodsReadyTimeout, kueue.WorkloadEvictedByAdmissionCheck:
 
@@ -326,7 +326,7 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 					if wl.Status.RequeueState != nil {
 						wl.Status.RequeueState.RequeueAt = nil
 					}
-					workload.SetRequeuedCondition(&wl, kueue.WorkloadBackoffFinished, "The workload backoff was finished", true)
+					workload.SetRequeuedCondition(wl, kueue.WorkloadBackoffFinished, "The workload backoff was finished", true)
 					updated = true
 				}
 			}
@@ -356,7 +356,7 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 		if wl.Status.RequeueState != nil {
 			updated = true
 		}
-		prepare := func() {
+		prepare := func(wl *kueue.Workload) {
 			if dtCond != nil {
 				apimeta.RemoveStatusCondition(&wl.Status.Conditions, kueue.WorkloadDeactivationTarget)
 			}
@@ -376,7 +376,7 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 				}
 			} else {
 				if err := clientutil.PatchStatus(ctx, r.client, &wl, func() (bool, error) {
-					prepare()
+					prepare(&wl)
 					return true, nil
 				}); err != nil {
 					if !apierrors.IsNotFound(err) {
@@ -397,8 +397,8 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 	lqExists := err == nil
 	lqActive := ptr.Deref(lq.Spec.StopPolicy, kueue.None) == kueue.None
 	if lqExists && lqActive && isDisabledRequeuedByLocalQueueStopped(&wl) {
-		return ctrl.Result{}, client.IgnoreNotFound(workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func() (bool, error) {
-			return workload.SetRequeuedCondition(&wl, kueue.WorkloadLocalQueueRestarted, "The LocalQueue was restarted after being stopped", true), nil
+		return ctrl.Result{}, client.IgnoreNotFound(workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func(wl *kueue.Workload) (bool, error) {
+			return workload.SetRequeuedCondition(wl, kueue.WorkloadLocalQueueRestarted, "The LocalQueue was restarted after being stopped", true), nil
 		}))
 	}
 
@@ -411,8 +411,8 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 		}
 		// If stopped cluster queue is started we need to set the WorkloadRequeued condition to true.
 		if isDisabledRequeuedByClusterQueueStopped(&wl) && ptr.Deref(cq.Spec.StopPolicy, kueue.None) == kueue.None {
-			return ctrl.Result{}, client.IgnoreNotFound(workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func() (bool, error) {
-				return workload.SetRequeuedCondition(&wl, kueue.WorkloadClusterQueueRestarted, "The ClusterQueue was restarted after being stopped", true), nil
+			return ctrl.Result{}, client.IgnoreNotFound(workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func(wl *kueue.Workload) (bool, error) {
+				return workload.SetRequeuedCondition(wl, kueue.WorkloadClusterQueueRestarted, "The ClusterQueue was restarted after being stopped", true), nil
 			}))
 		}
 		if updated, err := r.reconcileSyncAdmissionChecks(ctx, &wl, &cq); updated || err != nil {
@@ -424,8 +424,8 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 	// false before the workloads eviction.
 	if !workload.IsAdmitted(&wl) {
 		var updated bool
-		if err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func() (bool, error) {
-			updated = workload.SyncAdmittedCondition(&wl, r.clock.Now())
+		if err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func(wl *kueue.Workload) (bool, error) {
+			updated = workload.SyncAdmittedCondition(wl, r.clock.Now())
 			return updated, nil
 		}); err != nil {
 			return ctrl.Result{}, client.IgnoreNotFound(err)
@@ -489,29 +489,29 @@ func (r *WorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
 	switch {
 	case !lqExists:
 		log.V(3).Info("Workload is inadmissible because of missing LocalQueue", "localQueue", klog.KRef(wl.Namespace, string(wl.Spec.QueueName)))
-		if err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func() (bool, error) {
-			return workload.UnsetQuotaReservationWithCondition(&wl, kueue.WorkloadInadmissible, fmt.Sprintf("LocalQueue %s doesn't exist", wl.Spec.QueueName), r.clock.Now()), nil
+		if err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func(wl *kueue.Workload) (bool, error) {
+			return workload.UnsetQuotaReservationWithCondition(wl, kueue.WorkloadInadmissible, fmt.Sprintf("LocalQueue %s doesn't exist", wl.Spec.QueueName), r.clock.Now()), nil
 		}); err != nil {
 			return ctrl.Result{}, client.IgnoreNotFound(err)
 		}
 	case !lqActive:
 		log.V(3).Info("Workload is inadmissible because of stopped LocalQueue", "localQueue", klog.KRef(wl.Namespace, string(wl.Spec.QueueName)))
-		if err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func() (bool, error) {
-			return workload.UnsetQuotaReservationWithCondition(&wl, kueue.WorkloadInadmissible, fmt.Sprintf("LocalQueue %s is inactive", wl.Spec.QueueName), r.clock.Now()), nil
+		if err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func(wl *kueue.Workload) (bool, error) {
+			return workload.UnsetQuotaReservationWithCondition(wl, kueue.WorkloadInadmissible, fmt.Sprintf("LocalQueue %s is inactive", wl.Spec.QueueName), r.clock.Now()), nil
 		}); err != nil {
 			return ctrl.Result{}, client.IgnoreNotFound(err)
 		}
 	case !cqOk:
 		log.V(3).Info("Workload is inadmissible because of missing ClusterQueue", "clusterQueue", klog.KRef("", string(cqName)))
-		if err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func() (bool, error) {
-			return workload.UnsetQuotaReservationWithCondition(&wl, kueue.WorkloadInadmissible, fmt.Sprintf("ClusterQueue %s doesn't exist", cqName), r.clock.Now()), nil
+		if err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func(wl *kueue.Workload) (bool, error) {
+			return workload.UnsetQuotaReservationWithCondition(wl, kueue.WorkloadInadmissible, fmt.Sprintf("ClusterQueue %s doesn't exist", cqName), r.clock.Now()), nil
 		}); err != nil {
 			return ctrl.Result{}, client.IgnoreNotFound(err)
 		}
 	case !r.cache.ClusterQueueActive(cqName):
 		log.V(3).Info("Workload is inadmissible because ClusterQueue is inactive", "clusterQueue", klog.KRef("", string(cqName)))
-		if err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func() (bool, error) {
-			return workload.UnsetQuotaReservationWithCondition(&wl, kueue.WorkloadInadmissible, fmt.Sprintf("ClusterQueue %s is inactive", cqName), r.clock.Now()), nil
+		if err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func(wl *kueue.Workload) (bool, error) {
+			return workload.UnsetQuotaReservationWithCondition(wl, kueue.WorkloadInadmissible, fmt.Sprintf("ClusterQueue %s is inactive", cqName), r.clock.Now()), nil
 		}); err != nil {
 			return ctrl.Result{}, client.IgnoreNotFound(err)
 		}
@@ -549,7 +549,7 @@ func (r *WorkloadReconciler) reconcileMaxExecutionTime(ctx context.Context, wl *
 	}
 
 	if !apimeta.IsStatusConditionTrue(wl.Status.Conditions, kueue.WorkloadDeactivationTarget) {
-		err := workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func() (bool, error) {
+		err := workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 			updated := workload.SetDeactivationTarget(wl, kueue.WorkloadMaximumExecutionTimeExceeded, "exceeding the maximum execution time")
 			if wl.Status.AccumulatedPastExecutionTimeSeconds != nil {
 				wl.Status.AccumulatedPastExecutionTimeSeconds = nil
@@ -578,7 +578,7 @@ func (r *WorkloadReconciler) reconcileCheckBasedEviction(ctx context.Context, wl
 		for _, check := range workload.RejectedChecks(wl) {
 			rejectedCheckNames = append(rejectedCheckNames, check.Name)
 		}
-		err := workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func() (bool, error) {
+		err := workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 			return workload.SetDeactivationTarget(wl, kueue.WorkloadEvictedByAdmissionCheck, fmt.Sprintf("Admission check(s): %v, were rejected", stringsutils.Join(rejectedCheckNames, ","))), nil
 		})
 		if err != nil {
@@ -630,14 +630,14 @@ func (r *WorkloadReconciler) reconcileOnLocalQueueActiveState(ctx context.Contex
 
 	if !lqExists || !lq.DeletionTimestamp.IsZero() {
 		log.V(3).Info("Workload is inadmissible because the LocalQueue is terminating or missing", "localQueue", klog.KRef("", string(wl.Spec.QueueName)))
-		return true, workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func() (bool, error) {
+		return true, workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 			return workload.UnsetQuotaReservationWithCondition(wl, kueue.WorkloadInadmissible, fmt.Sprintf("LocalQueue %s is terminating or missing", wl.Spec.QueueName), r.clock.Now()), nil
 		})
 	}
 
 	if queueStopPolicy != kueue.None {
 		log.V(3).Info("Workload is inadmissible because the LocalQueue is stopped", "localQueue", klog.KRef("", string(wl.Spec.QueueName)))
-		return true, workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func() (bool, error) {
+		return true, workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 			return workload.UnsetQuotaReservationWithCondition(wl, kueue.WorkloadInadmissible, fmt.Sprintf("LocalQueue %s is stopped", wl.Spec.QueueName), r.clock.Now()), nil
 		})
 	}
@@ -672,14 +672,14 @@ func (r *WorkloadReconciler) reconcileOnClusterQueueActiveState(ctx context.Cont
 
 	if !cqExists || !cq.DeletionTimestamp.IsZero() {
 		log.V(3).Info("Workload is inadmissible because the ClusterQueue is terminating or missing", "clusterQueue", klog.KRef("", string(cqName)))
-		return true, workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func() (bool, error) {
+		return true, workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 			return workload.UnsetQuotaReservationWithCondition(wl, kueue.WorkloadInadmissible, fmt.Sprintf("ClusterQueue %s is terminating or missing", cqName), r.clock.Now()), nil
 		})
 	}
 
 	if queueStopPolicy != kueue.None {
 		log.V(3).Info("Workload is inadmissible because the ClusterQueue is stopped", "clusterQueue", klog.KRef("", string(cqName)))
-		return true, workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func() (bool, error) {
+		return true, workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 			return workload.UnsetQuotaReservationWithCondition(wl, kueue.WorkloadInadmissible, fmt.Sprintf("ClusterQueue %s is stopped", cqName), r.clock.Now()), nil
 		})
 	}
@@ -747,7 +747,7 @@ func (r *WorkloadReconciler) reconcileNotReadyTimeout(ctx context.Context, req c
 	// Increments a re-queueing count and update a time to be re-queued.
 	log.V(2).Info("Start the eviction of the workload due to exceeding the PodsReady timeout")
 	message := fmt.Sprintf("Exceeded the PodsReady timeout %s", req.String())
-	err = workload.Evict(ctx, r.client, r.recorder, wl, kueue.WorkloadEvictedByPodsReadyTimeout, message, underlyingCause, r.clock, workload.WithCustomPrepare(func() {
+	err = workload.Evict(ctx, r.client, r.recorder, wl, kueue.WorkloadEvictedByPodsReadyTimeout, message, underlyingCause, r.clock, workload.WithCustomPrepare(func(wl *kueue.Workload) {
 		workload.UpdateRequeueState(wl, r.waitForPodsReady.requeuingBackoffBaseSeconds, int32(r.waitForPodsReady.requeuingBackoffMaxDuration.Seconds()), r.clock)
 	}))
 
@@ -761,7 +761,7 @@ func (r *WorkloadReconciler) triggerDeactivation(ctx context.Context, wl *kueue.
 	requeueState := ptr.Deref(wl.Status.RequeueState, kueue.RequeueState{})
 	// If requeuingBackoffLimitCount equals to null, the workloads is repeatedly and endless re-queued.
 	if r.waitForPodsReady.requeuingBackoffLimitCount != nil && ptr.Deref(requeueState.Count, 0)+1 > *r.waitForPodsReady.requeuingBackoffLimitCount {
-		if err := workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func() (bool, error) {
+		if err := workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 			return workload.SetDeactivationTarget(wl, kueue.WorkloadRequeuingLimitExceeded, "exceeding the maximum number of re-queuing retries"), nil
 		}); err != nil {
 			return false, err
diff --git a/pkg/controller/jobframework/reconciler.go b/pkg/controller/jobframework/reconciler.go
index 15aca6b34..ad7b6a877 100644
--- a/pkg/controller/jobframework/reconciler.go
+++ b/pkg/controller/jobframework/reconciler.go
@@ -551,7 +551,7 @@ func (r *JobReconciler) ReconcileGenericJob(ctx context.Context, req ctrl.Reques
 		if workload.HasQuotaReservation(wl) {
 			if !job.IsActive() {
 				log.V(6).Info("The job is no longer active, clear the workloads admission")
-				err := workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func() (bool, error) {
+				err := workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 					// The requeued condition status set to true only on EvictedByPreemption
 					setRequeued := (evCond.Reason == kueue.WorkloadEvictedByPreemption) || (evCond.Reason == kueue.WorkloadEvictedDueToNodeFailures)
 					updated := workload.SetRequeuedCondition(wl, evCond.Reason, evCond.Message, setRequeued)
diff --git a/pkg/controller/tas/node_failure_controller.go b/pkg/controller/tas/node_failure_controller.go
index 165b0e972..bb4ed6e96 100644
--- a/pkg/controller/tas/node_failure_controller.go
+++ b/pkg/controller/tas/node_failure_controller.go
@@ -342,7 +342,7 @@ func (r *nodeFailureReconciler) handleHealthyNode(ctx context.Context, nodeName
 
 func (r *nodeFailureReconciler) removeUnhealthyNodes(ctx context.Context, wl *kueue.Workload, nodeName string) error {
 	if workload.HasUnhealthyNode(wl, nodeName) {
-		return workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func() (bool, error) {
+		return workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 			wl.Status.UnhealthyNodes = slices.DeleteFunc(wl.Status.UnhealthyNodes, func(n kueue.UnhealthyNode) bool {
 				return n.Name == nodeName
 			})
@@ -354,7 +354,7 @@ func (r *nodeFailureReconciler) removeUnhealthyNodes(ctx context.Context, wl *ku
 
 func (r *nodeFailureReconciler) addUnhealthyNode(ctx context.Context, wl *kueue.Workload, nodeName string) error {
 	if !workload.HasUnhealthyNode(wl, nodeName) {
-		return workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func() (bool, error) {
+		return workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 			wl.Status.UnhealthyNodes = append(wl.Status.UnhealthyNodes, kueue.UnhealthyNode{Name: nodeName})
 			return true, nil
 		})
diff --git a/pkg/controller/workloaddispatcher/incrementaldispatcher.go b/pkg/controller/workloaddispatcher/incrementaldispatcher.go
index 7e8d1ed52..c0dd4a39d 100644
--- a/pkg/controller/workloaddispatcher/incrementaldispatcher.go
+++ b/pkg/controller/workloaddispatcher/incrementaldispatcher.go
@@ -140,7 +140,7 @@ func (r *IncrementalDispatcherReconciler) nominateWorkers(ctx context.Context, w
 	}
 
 	nominatedWorkers := append(wl.Status.NominatedClusterNames, nextNominatedWorkers...)
-	if err = workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func() (bool, error) {
+	if err = workload.PatchAdmissionStatus(ctx, r.client, wl, r.clock, func(wl *kueue.Workload) (bool, error) {
 		wl.Status.NominatedClusterNames = nominatedWorkers
 		return true, nil
 	}); err != nil {
diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go
index 1d7f916d4..c34bbac9d 100644
--- a/pkg/metrics/metrics.go
+++ b/pkg/metrics/metrics.go
@@ -64,6 +64,8 @@ var (
 
 	// Metrics tied to the scheduler
 
+	// +metricsdoc:group=health
+	// +metricsdoc:labels=result="possible values are `success` or `inadmissible`"
 	AdmissionAttemptsTotal = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Subsystem: constants.KueueName,
@@ -76,6 +78,8 @@ The label 'result' can have the following values:
 		}, []string{"result"},
 	)
 
+	// +metricsdoc:group=health
+	// +metricsdoc:labels=result="possible values are `success` or `inadmissible`"
 	admissionAttemptDuration = prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Subsystem: constants.KueueName,
@@ -87,6 +91,8 @@ The label 'result' can have the following values:
 		}, []string{"result"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue"
 	AdmissionCyclePreemptionSkips = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -98,6 +104,8 @@ The label 'result' can have the following values:
 
 	// Metrics tied to the queue system.
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=git_version="git version",git_commit="git commit",build_date="build date",go_version="go version",compiler="compiler",platform="platform"
 	buildInfo = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -107,6 +115,8 @@ The label 'result' can have the following values:
 		[]string{"git_version", "git_commit", "build_date", "go_version", "compiler", "platform"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue",status="status label (varies by metric)"
 	PendingWorkloads = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -118,6 +128,8 @@ The label 'result' can have the following values:
 		}, []string{"cluster_queue", "status"},
 	)
 
+	// +metricsdoc:group=localqueue
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue",status="status label (varies by metric)"
 	LocalQueuePendingWorkloads = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -129,6 +141,8 @@ The label 'result' can have the following values:
 		}, []string{"name", "namespace", "status"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue",priority_class="the priority class name"
 	QuotaReservedWorkloadsTotal = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Subsystem: constants.KueueName,
@@ -137,6 +151,8 @@ The label 'result' can have the following values:
 		}, []string{"cluster_queue", "priority_class"},
 	)
 
+	// +metricsdoc:group=localqueue
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue",priority_class="the priority class name"
 	LocalQueueQuotaReservedWorkloadsTotal = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Subsystem: constants.KueueName,
@@ -145,6 +161,8 @@ The label 'result' can have the following values:
 		}, []string{"name", "namespace", "priority_class"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue",priority_class="the priority class name"
 	QuotaReservedWaitTime = prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Subsystem: constants.KueueName,
@@ -154,6 +172,8 @@ The label 'result' can have the following values:
 		}, []string{"cluster_queue", "priority_class"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue",reason="eviction or preemption reason",underlying_cause="root cause for eviction"
 	PodsReadyToEvictedTimeSeconds = prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Subsystem: constants.KueueName,
@@ -176,6 +196,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"cluster_queue", "reason", "underlying_cause"},
 	)
 
+	// +metricsdoc:group=localqueue
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue",priority_class="the priority class name"
 	LocalQueueQuotaReservedWaitTime = prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Subsystem: constants.KueueName,
@@ -185,6 +207,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"name", "namespace", "priority_class"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue",priority_class="the priority class name"
 	AdmittedWorkloadsTotal = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Subsystem: constants.KueueName,
@@ -193,6 +217,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"cluster_queue", "priority_class"},
 	)
 
+	// +metricsdoc:group=localqueue
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue",priority_class="the priority class name"
 	LocalQueueAdmittedWorkloadsTotal = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Subsystem: constants.KueueName,
@@ -201,6 +227,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"name", "namespace", "priority_class"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue",priority_class="the priority class name"
 	AdmissionWaitTime = prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Subsystem: constants.KueueName,
@@ -210,6 +238,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"cluster_queue", "priority_class"},
 	)
 
+	// +metricsdoc:group=optional_wait_for_pods_ready
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue",priority_class="the priority class name"
 	QueuedUntilReadyWaitTime = prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Subsystem: constants.KueueName,
@@ -219,6 +249,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"cluster_queue", "priority_class"},
 	)
 
+	// +metricsdoc:group=optional_wait_for_pods_ready
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue",priority_class="the priority class name"
 	AdmittedUntilReadyWaitTime = prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Subsystem: constants.KueueName,
@@ -228,6 +260,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"cluster_queue", "priority_class"},
 	)
 
+	// +metricsdoc:group=localqueue
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue",priority_class="the priority class name"
 	LocalQueueAdmissionWaitTime = prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Subsystem: constants.KueueName,
@@ -237,6 +271,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"name", "namespace", "priority_class"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue",priority_class="the priority class name"
 	AdmissionChecksWaitTime = prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Subsystem: constants.KueueName,
@@ -246,6 +282,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"cluster_queue", "priority_class"},
 	)
 
+	// +metricsdoc:group=localqueue
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue",priority_class="the priority class name"
 	LocalQueueAdmissionChecksWaitTime = prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Subsystem: constants.KueueName,
@@ -255,6 +293,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"name", "namespace", "priority_class"},
 	)
 
+	// +metricsdoc:group=optional_wait_for_pods_ready
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue",priority_class="the priority class name"
 	LocalQueueQueuedUntilReadyWaitTime = prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Subsystem: constants.KueueName,
@@ -264,6 +304,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"name", "namespace", "priority_class"},
 	)
 
+	// +metricsdoc:group=optional_wait_for_pods_ready
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue",priority_class="the priority class name"
 	LocalQueueAdmittedUntilReadyWaitTime = prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Subsystem: constants.KueueName,
@@ -273,6 +315,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"name", "namespace", "priority_class"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue",reason="eviction or preemption reason",underlying_cause="root cause for eviction",priority_class="the priority class name"
 	EvictedWorkloadsTotal = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Subsystem: constants.KueueName,
@@ -294,6 +338,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"cluster_queue", "reason", "underlying_cause", "priority_class"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue"
 	ReplacedWorkloadSlicesTotal = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Subsystem: constants.KueueName,
@@ -302,6 +348,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"cluster_queue"},
 	)
 
+	// +metricsdoc:group=localqueue
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue",reason="eviction or preemption reason",underlying_cause="root cause for eviction",priority_class="the priority class name"
 	LocalQueueEvictedWorkloadsTotal = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Subsystem: constants.KueueName,
@@ -323,6 +371,8 @@ The label 'underlying_cause' can have the following values:
 		}, []string{"name", "namespace", "reason", "underlying_cause", "priority_class"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue",reason="eviction or preemption reason",detailed_reason="finer-grained eviction cause",priority_class="the priority class name"
 	EvictedWorkloadsOnceTotal = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Subsystem: constants.KueueName,
@@ -346,6 +396,8 @@ The label 'detailed_reason' can have the following values:
 		}, []string{"cluster_queue", "reason", "detailed_reason", "priority_class"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=preempting_cluster_queue="the ClusterQueue executing preemption",reason="eviction or preemption reason"
 	PreemptedWorkloadsTotal = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Subsystem: constants.KueueName,
@@ -361,6 +413,8 @@ The label 'reason' can have the following values:
 
 	// Metrics tied to the cache.
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue"
 	ReservingActiveWorkloads = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -369,6 +423,8 @@ The label 'reason' can have the following values:
 		}, []string{"cluster_queue"},
 	)
 
+	// +metricsdoc:group=localqueue
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue"
 	LocalQueueReservingActiveWorkloads = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -377,6 +433,8 @@ The label 'reason' can have the following values:
 		}, []string{"name", "namespace"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue"
 	AdmittedActiveWorkloads = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -385,6 +443,8 @@ The label 'reason' can have the following values:
 		}, []string{"cluster_queue"},
 	)
 
+	// +metricsdoc:group=localqueue
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue"
 	LocalQueueAdmittedActiveWorkloads = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -393,6 +453,8 @@ The label 'reason' can have the following values:
 		}, []string{"name", "namespace"},
 	)
 
+	// +metricsdoc:group=clusterqueue
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue",status="one of `pending`, `active`, or `terminated`"
 	ClusterQueueByStatus = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -402,6 +464,8 @@ For a ClusterQueue, the metric only reports a value of 1 for one of the statuses
 		}, []string{"cluster_queue", "status"},
 	)
 
+	// +metricsdoc:group=localqueue
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue",active="one of `True`, `False`, or `Unknown`"
 	LocalQueueByStatus = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -413,6 +477,8 @@ For a LocalQueue, the metric only reports a value of 1 for one of the statuses.`
 
 	// Optional cluster queue metrics
 
+	// +metricsdoc:group=optional_clusterqueue_resources
+	// +metricsdoc:labels=cohort="the name of the Cohort",cluster_queue="the name of the ClusterQueue",flavor="the resource flavor name",resource="the resource name"
 	ClusterQueueResourceReservations = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -421,6 +487,8 @@ For a LocalQueue, the metric only reports a value of 1 for one of the statuses.`
 		}, []string{"cohort", "cluster_queue", "flavor", "resource"},
 	)
 
+	// +metricsdoc:group=optional_clusterqueue_resources
+	// +metricsdoc:labels=cohort="the name of the Cohort",cluster_queue="the name of the ClusterQueue",flavor="the resource flavor name",resource="the resource name"
 	ClusterQueueResourceUsage = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -429,6 +497,8 @@ For a LocalQueue, the metric only reports a value of 1 for one of the statuses.`
 		}, []string{"cohort", "cluster_queue", "flavor", "resource"},
 	)
 
+	// +metricsdoc:group=localqueue
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue",flavor="the resource flavor name",resource="the resource name"
 	LocalQueueResourceReservations = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -437,6 +507,8 @@ For a LocalQueue, the metric only reports a value of 1 for one of the statuses.`
 		}, []string{"name", "namespace", "flavor", "resource"},
 	)
 
+	// +metricsdoc:group=localqueue
+	// +metricsdoc:labels=name="the name of the LocalQueue",namespace="the namespace of the LocalQueue",flavor="the resource flavor name",resource="the resource name"
 	LocalQueueResourceUsage = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -445,6 +517,8 @@ For a LocalQueue, the metric only reports a value of 1 for one of the statuses.`
 		}, []string{"name", "namespace", "flavor", "resource"},
 	)
 
+	// +metricsdoc:group=optional_clusterqueue_resources
+	// +metricsdoc:labels=cohort="the name of the Cohort",cluster_queue="the name of the ClusterQueue",flavor="the resource flavor name",resource="the resource name"
 	ClusterQueueResourceNominalQuota = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -453,6 +527,8 @@ For a LocalQueue, the metric only reports a value of 1 for one of the statuses.`
 		}, []string{"cohort", "cluster_queue", "flavor", "resource"},
 	)
 
+	// +metricsdoc:group=optional_clusterqueue_resources
+	// +metricsdoc:labels=cohort="the name of the Cohort",cluster_queue="the name of the ClusterQueue",flavor="the resource flavor name",resource="the resource name"
 	ClusterQueueResourceBorrowingLimit = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -461,6 +537,8 @@ For a LocalQueue, the metric only reports a value of 1 for one of the statuses.`
 		}, []string{"cohort", "cluster_queue", "flavor", "resource"},
 	)
 
+	// +metricsdoc:group=optional_clusterqueue_resources
+	// +metricsdoc:labels=cohort="the name of the Cohort",cluster_queue="the name of the ClusterQueue",flavor="the resource flavor name",resource="the resource name"
 	ClusterQueueResourceLendingLimit = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -469,6 +547,8 @@ For a LocalQueue, the metric only reports a value of 1 for one of the statuses.`
 		}, []string{"cohort", "cluster_queue", "flavor", "resource"},
 	)
 
+	// +metricsdoc:group=optional_clusterqueue_resources
+	// +metricsdoc:labels=cluster_queue="the name of the ClusterQueue",cohort="the name of the Cohort"
 	ClusterQueueWeightedShare = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
@@ -481,6 +561,8 @@ If the ClusterQueue has a weight of zero and is borrowing, this will return NaN.
 		}, []string{"cluster_queue", "cohort"},
 	)
 
+	// +metricsdoc:group=cohort
+	// +metricsdoc:labels=cohort="the name of the Cohort"
 	CohortWeightedShare = prometheus.NewGaugeVec(
 		prometheus.GaugeOpts{
 			Subsystem: constants.KueueName,
diff --git a/pkg/scheduler/preemption/preemption.go b/pkg/scheduler/preemption/preemption.go
index b5fc19f34..1c79923b5 100644
--- a/pkg/scheduler/preemption/preemption.go
+++ b/pkg/scheduler/preemption/preemption.go
@@ -169,8 +169,8 @@ func (p *Preemptor) IssuePreemptions(ctx context.Context, preemptor *workload.In
 
 			message := preemptionMessage(preemptor.Obj, target.Reason, preemptorPath, preempteePath)
 			wlCopy := target.WorkloadInfo.Obj.DeepCopy()
-			err := workload.Evict(ctx, p.client, p.recorder, wlCopy, kueue.WorkloadEvictedByPreemption, message, "", p.clock, workload.WithCustomPrepare(func() {
-				workload.SetPreemptedCondition(wlCopy, p.clock.Now(), target.Reason, message)
+			err := workload.Evict(ctx, p.client, p.recorder, wlCopy, kueue.WorkloadEvictedByPreemption, message, "", p.clock, workload.WithCustomPrepare(func(wl *kueue.Workload) {
+				workload.SetPreemptedCondition(wl, p.clock.Now(), target.Reason, message)
 			}))
 			if err != nil {
 				errCh.SendErrorWithCancel(err, cancel)
diff --git a/pkg/scheduler/scheduler.go b/pkg/scheduler/scheduler.go
index ffb3cd9d7..fe537c4e6 100644
--- a/pkg/scheduler/scheduler.go
+++ b/pkg/scheduler/scheduler.go
@@ -238,7 +238,7 @@ func (s *Scheduler) schedule(ctx context.Context) wait.SpeedSignal {
 
 		if features.Enabled(features.TASFailedNodeReplacementFailFast) && workload.HasTopologyAssignmentWithUnhealthyNode(e.Obj) && mode != flavorassigner.Fit {
 			// evict workload we couldn't find the replacement for
-			if err := s.evictWorkloadAfterFailedTASReplacement(ctx, log, e.Obj); err != nil {
+			if err := s.evictWorkloadAfterFailedTASReplacement(ctx, log, e.Obj.DeepCopy()); err != nil {
 				log.V(2).Error(err, "Failed to evict workload after failed try to find a node replacement")
 				continue
 			}
@@ -316,7 +316,7 @@ func (s *Scheduler) schedule(ctx context.Context) wait.SpeedSignal {
 			// Block admission until all currently admitted workloads are in
 			// PodsReady condition if the waitForPodsReady is enabled
 			wl := e.Obj.DeepCopy()
-			if err := workload.PatchAdmissionStatus(ctx, s.client, wl, s.clock, func() (bool, error) {
+			if err := workload.PatchAdmissionStatus(ctx, s.client, wl, s.clock, func(wl *kueue.Workload) (bool, error) {
 				return workload.UnsetQuotaReservationWithCondition(wl, "Waiting", "waiting for all admitted workloads to be in PodsReady condition", s.clock.Now()), nil
 			}, workload.WithLooseOnApply()); err != nil {
 				log.Error(err, "Could not update Workload status")
@@ -620,17 +620,18 @@ func (s *Scheduler) admit(ctx context.Context, e *entry, cq *schdcache.ClusterQu
 		PodSetAssignments: e.assignment.ToAPI(),
 	}
 
-	if err := s.assumeWorkload(log, e, cq, admission); err != nil {
+	cacheWl, err := s.assumeWorkload(log, e, cq, admission)
+	if err != nil {
 		return err
 	}
 
 	newWorkload := e.Obj.DeepCopy()
 	s.admissionRoutineWrapper.Run(func() {
-		err := workload.PatchAdmissionStatus(ctx, s.client, newWorkload, s.clock, func() (bool, error) {
-			s.prepareWorkload(log, newWorkload, cq, admission)
+		err := workload.PatchAdmissionStatus(ctx, s.client, newWorkload, s.clock, func(wl *kueue.Workload) (bool, error) {
+			s.prepareWorkload(log, wl, cq, admission)
 			if features.Enabled(features.TopologyAwareScheduling) && workload.HasUnhealthyNodes(e.Obj) {
 				log.V(5).Info("Clearing the topology assignment recovery field from the workload status after successful recovery")
-				newWorkload.Status.UnhealthyNodes = nil
+				wl.Status.UnhealthyNodes = nil
 			}
 			return true, nil
 		}, workload.WithLooseOnApply())
@@ -642,7 +643,7 @@ func (s *Scheduler) admit(ctx context.Context, e *entry, cq *schdcache.ClusterQu
 		}
 		// Ignore errors because the workload or clusterQueue could have been deleted
 		// by an event.
-		_ = s.cache.ForgetWorkload(log, newWorkload)
+		_ = s.cache.ForgetWorkload(log, cacheWl)
 		if afs.Enabled(s.admissionFairSharing) {
 			s.updateEntryPenalty(log, e, subtract)
 		}
@@ -666,11 +667,11 @@ func (s *Scheduler) prepareWorkload(log logr.Logger, wl *kueue.Workload, cq *sch
 	}
 }
 
-func (s *Scheduler) assumeWorkload(log logr.Logger, e *entry, cq *schdcache.ClusterQueueSnapshot, admission *kueue.Admission) error {
+func (s *Scheduler) assumeWorkload(log logr.Logger, e *entry, cq *schdcache.ClusterQueueSnapshot, admission *kueue.Admission) (*kueue.Workload, error) {
 	cacheWl := e.Obj.DeepCopy()
 	s.prepareWorkload(log, cacheWl, cq, admission)
 	if err := s.cache.AssumeWorkload(log, cacheWl); err != nil {
-		return err
+		return nil, err
 	}
 
 	e.status = assumed
@@ -681,7 +682,7 @@ func (s *Scheduler) assumeWorkload(log logr.Logger, e *entry, cq *schdcache.Clus
 		// Trigger LocalQueue reconciler to apply any pending penalties
 		s.queues.NotifyWorkloadUpdateWatchers(e.Obj, cacheWl)
 	}
-	return nil
+	return cacheWl, nil
 }
 
 type entryOrdering struct {
@@ -792,7 +793,7 @@ func (s *Scheduler) requeueAndUpdate(ctx context.Context, e entry) {
 	log.V(2).Info("Workload re-queued", "workload", klog.KObj(e.Obj), "clusterQueue", klog.KRef("", string(e.ClusterQueue)), "queue", klog.KRef(e.Obj.Namespace, string(e.Obj.Spec.QueueName)), "requeueReason", e.requeueReason, "added", added, "status", e.status)
 	if e.status == notNominated || e.status == skipped {
 		wl := e.Obj.DeepCopy()
-		if err := workload.PatchAdmissionStatus(ctx, s.client, wl, s.clock, func() (bool, error) {
+		if err := workload.PatchAdmissionStatus(ctx, s.client, wl, s.clock, func(wl *kueue.Workload) (bool, error) {
 			updated := workload.UnsetQuotaReservationWithCondition(wl, "Pending", e.inadmissibleMsg, s.clock.Now())
 			if workload.PropagateResourceRequests(wl, &e.Info) {
 				updated = true
diff --git a/pkg/workload/workload.go b/pkg/workload/workload.go
index 8123b9519..125826b53 100644
--- a/pkg/workload/workload.go
+++ b/pkg/workload/workload.go
@@ -1023,25 +1023,31 @@ func WithLooseOnApply() PatchAdmissionStatusOption {
 // PatchAdmissionStatus updates the admission status of a workload.
 // If the WorkloadRequestUseMergePatch feature is enabled, it uses a Merge Patch with update function.
 // Otherwise, it runs the update function and, if updated, applies the SSA Patch status.
-func PatchAdmissionStatus(ctx context.Context, c client.Client, w *kueue.Workload, clk clock.Clock, update func() (bool, error), options ...PatchAdmissionStatusOption) error {
+func PatchAdmissionStatus(ctx context.Context, c client.Client, w *kueue.Workload, clk clock.Clock, update func(*kueue.Workload) (bool, error), options ...PatchAdmissionStatusOption) error {
 	opts := DefaultPatchAdmissionStatusOptions()
 	for _, opt := range options {
 		opt(opts)
 	}
-
+	var err error
+	wlCopy := w.DeepCopy()
 	if features.Enabled(features.WorkloadRequestUseMergePatch) {
 		var patchOptions []clientutil.PatchOption
 		if !opts.StrictPatch {
 			patchOptions = append(patchOptions, clientutil.WithLoose())
 		}
-		return clientutil.PatchStatus(ctx, c, w, update, patchOptions...)
+		err = clientutil.PatchStatus(ctx, c, wlCopy, func() (bool, error) {
+			return update(wlCopy)
+		}, patchOptions...)
+	} else {
+		if updated, err := update(wlCopy); err != nil || !updated {
+			return err
+		}
+		err = ApplyAdmissionStatus(ctx, c, wlCopy, opts.StrictApply, clk)
 	}
-	updated, err := update()
-	if err != nil || !updated {
-		return err
+	if err == nil {
+		wlCopy.DeepCopyInto(w)
 	}
-
-	return ApplyAdmissionStatus(ctx, c, w, opts.StrictApply, clk)
+	return err
 }
 
 type Ordering struct {
@@ -1279,7 +1285,7 @@ func AdmissionChecksForWorkload(log logr.Logger, wl *kueue.Workload, admissionCh
 type EvictOption func(*EvictOptions)
 
 type EvictOptions struct {
-	CustomPrepare func()
+	CustomPrepare func(wl *kueue.Workload)
 }
 
 func DefaultEvictOptions() *EvictOptions {
@@ -1288,7 +1294,7 @@ func DefaultEvictOptions() *EvictOptions {
 	}
 }
 
-func WithCustomPrepare(customPrepare func()) EvictOption {
+func WithCustomPrepare(customPrepare func(wl *kueue.Workload)) EvictOption {
 	return func(o *EvictOptions) {
 		if customPrepare != nil {
 			o.CustomPrepare = customPrepare
@@ -1307,9 +1313,9 @@ func Evict(ctx context.Context, c client.Client, recorder record.EventRecorder,
 		reportWorkloadEvictedOnce bool
 	)
 
-	if err := PatchAdmissionStatus(ctx, c, wl, clock, func() (bool, error) {
+	if err := PatchAdmissionStatus(ctx, c, wl, clock, func(wl *kueue.Workload) (bool, error) {
 		if opts.CustomPrepare != nil {
-			opts.CustomPrepare()
+			opts.CustomPrepare(wl)
 		}
 
 		evictionReason := reason
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 000000000..18d223e7e
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,23 @@
+{
+  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+  "extends": ["config:recommended", "helpers:pinGitHubActionDigests"],
+  "labels": ["ok-to-test", "release-note-none"],
+  "enabledManagers": ["npm"],
+  "ignorePaths": ["site/**", "test/e2e/kueueviz/**"],
+  "packageRules": [
+    {
+      "description": "Group MUI packages in KueueViz frontend",
+      "matchManagers": ["npm"],
+      "matchFileNames": ["cmd/kueueviz/frontend/**"],
+      "groupName": "mui-packages",
+      "matchPackageNames": ["@mui/{/,}**"]
+    },
+    {
+      "description": "Group minor/patch updates for KueueViz frontend",
+      "matchManagers": ["npm"],
+      "matchFileNames": ["cmd/kueueviz/frontend/**"],
+      "matchUpdateTypes": ["minor", "patch"],
+      "groupName": "frontend minor and patch"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/site/content/en/docs/reference/metrics.md b/site/content/en/docs/reference/metrics.md
index 0bfd19016..abda88a58 100644
--- a/site/content/en/docs/reference/metrics.md
+++ b/site/content/en/docs/reference/metrics.md
@@ -9,84 +9,97 @@ Kueue exposes [prometheus](https://prometheus.io) metrics to monitor the health
 of the system and the status of [ClusterQueues](/docs/concepts/cluster_queue)
 and [LocalQueues](/docs/concepts/local_queue).
 
+<!-- NOTE: Sections below contain tables generated from code. Do not edit content between BEGIN/END GENERATED TABLE markers. Use `make generate-metrics-tables` to update. -->
+
 ## Kueue health
 
 Use the following metrics to monitor the health of the kueue controllers:
 
-
-| Metric name                                | Type      | Description                                                                                                                                   | Labels                                                    |
-| -------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
-| `kueue_admission_attempts_total`           | Counter   | The total number of attempts to [admit](/docs/concepts#admission) workloads. Each admission attempt might try to admit more than one workload. | `result`: possible values are `success` or `inadmissible` |
-| `kueue_admission_attempt_duration_seconds` | Histogram | The latency of an admission attempt.                                                                                                          | `result`: possible values are `success` or `inadmissible` |
+<!-- BEGIN GENERATED TABLE: health -->
+| Metric name | Type | Description | Labels |
+| --- | --- | --- | --- |
+| `kueue_admission_attempt_duration_seconds` | Histogram | The latency of an admission attempt.<br>The label 'result' can have the following values:<br>- 'success' means that at least one workload was admitted.,<br>- 'inadmissible' means that no workload was admitted. | `result`: possible values are `success` or `inadmissible` |
+| `kueue_admission_attempts_total` | Counter | The total number of attempts to admit workloads.<br>Each admission attempt might try to admit more than one workload.<br>The label 'result' can have the following values:<br>- 'success' means that at least one workload was admitted.,<br>- 'inadmissible' means that no workload was admitted. | `result`: possible values are `success` or `inadmissible` |
+<!-- END GENERATED TABLE: health -->
 
 ## ClusterQueue status
 
 Use the following metrics to monitor the status of your ClusterQueues:
 
-
-| Metric name                                | Type      | Description                                                                         | Labels                                                                                                                                                                                            |
-| -------------------------------------------- | ----------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `kueue_pending_workloads`                  | Gauge     | The number of pending workloads.                                                    | `cluster_queue`: the name of the ClusterQueue<br> `status`: possible values are `active` or `inadmissible`                                                                                        |
-| `kueue_quota_reserved_workloads_total`     | Counter   | The total number of quota reserved workloads.                                       | `cluster_queue`: the name of the ClusterQueue<br> priority_class: the priority class name                                                                                         |
-| `kueue_quota_reserved_wait_time_seconds`   | Histogram | The time between a workload was created or requeued until it got quota reservation. | `cluster_queue`: the name of the ClusterQueue<br> priority_class: the priority class name                                                                                         |
-| `kueue_admitted_workloads_total`           | Counter   | The total number of admitted workloads.                                             | `cluster_queue`: the name of the ClusterQueue<br> priority_class: the priority class name                                                                                           |
-| `kueue_evicted_workloads_total`            | Counter   | The total number of evicted workloads.                                              | `cluster_queue`: the name of the ClusterQueue<br> `reason`: Possible values are `Preempted`, `PodsReadyTimeout`, `AdmissionCheck`, `ClusterQueueStopped` or `Deactivated`<br> priority_class: the priority class name |
-| `kueue_evicted_workloads_once_total`       | Counter   | The number of unique workload evictions per 'cluster_queue'                         | `cluster_queue`: the name of the ClusterQueue<br> `reason`: Possible values are `Preempted`, `PodsReadyTimeout`, `AdmissionCheck`, `ClusterQueueStopped` or `Deactivated`<br> `detailedReason`: specifies a finer-grained explanation that complements the eviction cause<br> priority_class: the priority class name |
-| `kueue_admission_wait_time_seconds`        | Histogram | The time between a workload was created or requeued until admission.                | `cluster_queue`: the name of the ClusterQueue<br> priority_class: the priority class name                                                                                         |
-| `kueue_admission_checks_wait_time_seconds` | Histogram | The time from when a workload got the quota reservation until admission.            | `cluster_queue`: the name of the ClusterQueue<br> priority_class: the priority class name                                                                                         |
-| `kueue_admitted_active_workloads`          | Gauge     | The number of admitted Workloads that are active (unsuspended and not finished)     | `cluster_queue`: the name of the ClusterQueue                                                                                                                                                     |
-| `kueue_cluster_queue_status`               | Gauge     | Reports the status of the ClusterQueue                                              | `cluster_queue`: The name of the ClusterQueue<br> `status`: Possible values are `pending`, `active` or `terminated`. For a ClusterQueue, the metric only reports a value of 1 for one of the statuses. |
-| `kueue_reserving_active_workloads`         | Gauge     | The number of Workloads that are reserving quota, per `cluster_queue`.              | `cluster_queue`: the name of the ClusterQueue                                                                                                                                                     |
-| `kueue_admission_cycle_preemption_skips`   | Gauge     | The number of Workloads in the ClusterQueue that got preemption candidates but had to be skipped because other ClusterQueues needed the same resources in the same cycle | `cluster_queue`: the name of the ClusterQueue                                                                     |
-| `kueue_preempted_workloads_total`          | Counter   | The number of preempted workloads per `preempting_cluster_queue`                    | `preempting_cluster_queue`: the name of the ClusterQueue<br> `reason`: possible values are `InClusterQueue` means that the workload was preempted by a workload in the same ClusterQueue; `InCohortReclamation` means that the workload was preempted by a workload in the same cohort due to reclamation of nominal quota; `InCohortFairSharing` means that the workload was preempted by a workload in the same cohort due to Fair Sharing; `InCohortReclaimWhileBorrowing` means that the workload was preempted by a workload in the same cohort due to reclamation of nominal quota while borrowing |
+<!-- BEGIN GENERATED TABLE: clusterqueue -->
+| Metric name | Type | Description | Labels |
+| --- | --- | --- | --- |
+| `kueue_admission_checks_wait_time_seconds` | Histogram | The time from when a workload got the quota reservation until admission, per 'cluster_queue' | `cluster_queue`: the name of the ClusterQueue<br> `priority_class`: the priority class name |
+| `kueue_admission_cycle_preemption_skips` | Gauge | The number of Workloads in the ClusterQueue that got preemption candidates but had to be skipped because other ClusterQueues needed the same resources in the same cycle | `cluster_queue`: the name of the ClusterQueue |
+| `kueue_admission_wait_time_seconds` | Histogram | The time between a workload was created or requeued until admission, per 'cluster_queue' | `cluster_queue`: the name of the ClusterQueue<br> `priority_class`: the priority class name |
+| `kueue_admitted_active_workloads` | Gauge | The number of admitted Workloads that are active (unsuspended and not finished), per 'cluster_queue' | `cluster_queue`: the name of the ClusterQueue |
+| `kueue_admitted_workloads_total` | Counter | The total number of admitted workloads per 'cluster_queue' | `cluster_queue`: the name of the ClusterQueue<br> `priority_class`: the priority class name |
+| `kueue_build_info` | Gauge | Kueue build information. 1 labeled by git version, git commit, build date, go version, compiler, platform | `git_version`: git version<br> `git_commit`: git commit<br> `build_date`: build date<br> `go_version`: go version<br> `compiler`: compiler<br> `platform`: platform |
+| `kueue_cluster_queue_status` | Gauge | Reports 'cluster_queue' with its 'status' (with possible values 'pending', 'active' or 'terminated').<br>For a ClusterQueue, the metric only reports a value of 1 for one of the statuses. | `cluster_queue`: the name of the ClusterQueue<br> `status`: one of `pending`, `active`, or `terminated` |
+| `kueue_evicted_workloads_once_total` | Counter | The number of unique workload evictions per 'cluster_queue',<br>The label 'reason' can have the following values:<br>- "Preempted" means that the workload was evicted in order to free resources for a workload with a higher priority or reclamation of nominal quota.<br>- "PodsReadyTimeout" means that the eviction took place due to a PodsReady timeout.<br>- "AdmissionCheck" means that the workload was evicted because at least one admission check transitioned to False.<br>- "ClusterQueueStopped" means that the workload was evicted because the ClusterQueue is stopped.<br>- "LocalQueueStopped" means that the workload was evicted because the LocalQueue is stopped.<br>- "NodeFailures" means that the workload was evicted due to node failures when using TopologyAwareScheduling.<br>- "Deactivated" means that the workload was evicted because spec.active is set to false.<br>The label 'detailed_reason' can have the following values:<br>- "" means that the value in 'reason' label is the root cause for eviction.<br>- "WaitForStart" means that the pods have not been ready since admission, or the workload is not admitted.<br>- "WaitForRecovery" means that the Pods were ready since the workload admission, but some pod has failed.<br>- "AdmissionCheck" means that the workload was evicted by Kueue due to a rejected admission check.<br>- "MaximumExecutionTimeExceeded" means that the workload was evicted by Kueue due to maximum execution time exceeded.<br>- "RequeuingLimitExceeded" means that the workload was evicted by Kueue due to requeuing limit exceeded. | `cluster_queue`: the name of the ClusterQueue<br> `reason`: eviction or preemption reason<br> `detailed_reason`: finer-grained eviction cause<br> `priority_class`: the priority class name |
+| `kueue_evicted_workloads_total` | Counter | The number of evicted workloads per 'cluster_queue',<br>The label 'reason' can have the following values:<br>- "Preempted" means that the workload was evicted in order to free resources for a workload with a higher priority or reclamation of nominal quota.<br>- "PodsReadyTimeout" means that the eviction took place due to a PodsReady timeout.<br>- "AdmissionCheck" means that the workload was evicted because at least one admission check transitioned to False.<br>- "ClusterQueueStopped" means that the workload was evicted because the ClusterQueue is stopped.<br>- "LocalQueueStopped" means that the workload was evicted because the LocalQueue is stopped.<br>- "NodeFailures" means that the workload was evicted due to node failures when using TopologyAwareScheduling.<br>- "Deactivated" means that the workload was evicted because spec.active is set to false.<br>The label 'underlying_cause' can have the following values:<br>- "" means that the value in 'reason' label is the root cause for eviction.<br>- "AdmissionCheck" means that the workload was evicted by Kueue due to a rejected admission check.<br>- "MaximumExecutionTimeExceeded" means that the workload was evicted by Kueue due to maximum execution time exceeded.<br>- "RequeuingLimitExceeded" means that the workload was evicted by Kueue due to requeuing limit exceeded. | `cluster_queue`: the name of the ClusterQueue<br> `reason`: eviction or preemption reason<br> `underlying_cause`: root cause for eviction<br> `priority_class`: the priority class name |
+| `kueue_pending_workloads` | Gauge | The number of pending workloads, per 'cluster_queue' and 'status'.<br>'status' can have the following values:<br>- "active" means that the workloads are in the admission queue.<br>- "inadmissible" means there was a failed admission attempt for these workloads and they won't be retried until cluster conditions, which could make this workload admissible, change | `cluster_queue`: the name of the ClusterQueue<br> `status`: status label (varies by metric) |
+| `kueue_pods_ready_to_evicted_time_seconds` | Histogram | The number of seconds between a workload's pods being ready and eviction workloads per 'cluster_queue',<br>The label 'reason' can have the following values:<br>- "Preempted" means that the workload was evicted in order to free resources for a workload with a higher priority or reclamation of nominal quota.<br>- "PodsReadyTimeout" means that the eviction took place due to a PodsReady timeout.<br>- "AdmissionCheck" means that the workload was evicted because at least one admission check transitioned to False.<br>- "ClusterQueueStopped" means that the workload was evicted because the ClusterQueue is stopped.<br>- "LocalQueueStopped" means that the workload was evicted because the LocalQueue is stopped.<br>- "NodeFailures" means that the workload was evicted due to node failures when using TopologyAwareScheduling.<br>- "Deactivated" means that the workload was evicted because spec.active is set to false.<br>The label 'underlying_cause' can have the following values:<br>- "" means that the value in 'reason' label is the root cause for eviction.<br>- "AdmissionCheck" means that the workload was evicted by Kueue due to a rejected admission check.<br>- "MaximumExecutionTimeExceeded" means that the workload was evicted by Kueue due to maximum execution time exceeded.<br>- "RequeuingLimitExceeded" means that the workload was evicted by Kueue due to requeuing limit exceeded. | `cluster_queue`: the name of the ClusterQueue<br> `reason`: eviction or preemption reason<br> `underlying_cause`: root cause for eviction |
+| `kueue_preempted_workloads_total` | Counter | The number of preempted workloads per 'preempting_cluster_queue',<br>The label 'reason' can have the following values:<br>- "InClusterQueue" means that the workload was preempted by a workload in the same ClusterQueue.<br>- "InCohortReclamation" means that the workload was preempted by a workload in the same cohort due to reclamation of nominal quota.<br>- "InCohortFairSharing" means that the workload was preempted by a workload in the same cohort Fair Sharing.<br>- "InCohortReclaimWhileBorrowing" means that the workload was preempted by a workload in the same cohort due to reclamation of nominal quota while borrowing. | `preempting_cluster_queue`: the ClusterQueue executing preemption<br> `reason`: eviction or preemption reason |
+| `kueue_quota_reserved_wait_time_seconds` | Histogram | The time between a workload was created or requeued until it got quota reservation, per 'cluster_queue' | `cluster_queue`: the name of the ClusterQueue<br> `priority_class`: the priority class name |
+| `kueue_quota_reserved_workloads_total` | Counter | The total number of quota reserved workloads per 'cluster_queue' | `cluster_queue`: the name of the ClusterQueue<br> `priority_class`: the priority class name |
+| `kueue_replaced_workload_slices_total` | Counter | The number of replaced workload slices per 'cluster_queue' | `cluster_queue`: the name of the ClusterQueue |
+| `kueue_reserving_active_workloads` | Gauge | The number of Workloads that are reserving quota, per 'cluster_queue' | `cluster_queue`: the name of the ClusterQueue |
+<!-- END GENERATED TABLE: clusterqueue -->
 
 ## LocalQueue Status (alpha)
 
 The following metrics are available only if `LocalQueueMetrics` feature gate is enabled. Check the [Change the feature gates configuration](/docs/installation/#change-the-feature-gates-configuration) section of the [Installation](/docs/installation/) for details.
 
-| Metric Name                                            | Type      | Description                                                                                           | Labels                                                                                                                                                                                                                                                                                                                                                   |
-|--------------------------------------------------------|-----------|-------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `kueue_local_queue_pending_workloads`                  | Gauge     | The number of pending workloads, per `local_queue` and `status`.                                      | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in<br />`status`: can be either `active` for the number of active pending workloads or `inadmissible`                                                                                                                                                     |
-| `kueue_local_queue_quota_reserved_workloads_total`     | Counter   | The number of workloads with quota reserved in a LocalQueue                                           | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in<br />`priority_class`: the name of the priority class, if applicable                                                                                                                                                                                   |
-| `kueue_local_queue_quota_reserved_wait_time_seconds`   | Histogram | The time between a workload was created or requeued until it got quota reservation, per `local_queue` | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in<br />`priority_class`: the name of the priority class, if applicable                                                                                                                                                                                   |
-| `kueue_local_queue_admitted_workloads_total`           | Counter   | The total number of admitted workloads per `local_queue`                                              | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in<br />`priority_class`: the name of the priority class, if applicable                                                                                                                                                                                   |
-| `kueue_local_queue_admission_checks_wait_time_seconds` | Histogram | The time from when a workload got the quota reservation until admission, per `local_queue`            | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in<br />`priority_class`: the name of the priority class, if applicable                                                                                                                                                                                   |
-| `kueue_local_queue_admission_wait_time_seconds`        | Histogram | The time between a workload was created or requeued until admission, per `local_queue`                | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in<br />`priority_class`: the name of the priority class, if applicable                                                                                                                                                                                   |
-| `kueue_local_queue_evicted_workloads_total`            | Counter   | The number of evicted workloads per `local_queue`                                                     | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in<br />`priority_class`: the name of the priority class, if applicable<br />`reason`: the reason the workload was pre-empted. It can have the following values ["Preempted", "PodsReadyTimeout", "AdmissionCheck", "ClusterQueueStopped", "Deactivated"] |
-| `kueue_local_queue_reserving_active_workloads`         | Gauge     | The number of Workloads that are reserving quota, per `localQueue`                                    | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in                                                                                                                                                                                                                                                        |
-| `kueue_local_queue_admitted_active_workloads`          | Gauge     | The number of admitted Workloads that are active (unsuspended and not finished), per `localQueue`     | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in                                                                                                                                                                                                                                                        |
-| `kueue_local_queue_status`                             | Gauge     | Reports a LocalQueue's `active` status (ability to schedule workloads)                                | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in<br />`active`: one of [`True`, `False`, `Unknown`] and exclusively one is positive at any given time                                                                                                                                                   |
-| `kueue_local_queue_resource_reservation`               | Gauge     | Reports the LocalQueue's total resource usage within all the`flavors`                                 | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in<br />`flavor`: the name of the flavor which resources are being consumed from<br />`resource`: the resource which is being consumed                                                                                                                    |
-| `kueue_local_queue_resource_usage`                     | Gauge     | Reports the localQueue's total resource reservation within all the `flavors`                          | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in<br />`flavor`: the name of the flavor which resources are being consumed from<br />`resource`: the resource which is being consumed                                                                                                                    |
+<!-- BEGIN GENERATED TABLE: localqueue -->
+| Metric name | Type | Description | Labels |
+| --- | --- | --- | --- |
+| `kueue_local_queue_admission_checks_wait_time_seconds` | Histogram | The time from when a workload got the quota reservation until admission, per 'local_queue' | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue<br> `priority_class`: the priority class name |
+| `kueue_local_queue_admission_wait_time_seconds` | Histogram | The time between a workload was created or requeued until admission, per 'local_queue' | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue<br> `priority_class`: the priority class name |
+| `kueue_local_queue_admitted_active_workloads` | Gauge | The number of admitted Workloads that are active (unsuspended and not finished), per 'localQueue' | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue |
+| `kueue_local_queue_admitted_workloads_total` | Counter | The total number of admitted workloads per 'local_queue' | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue<br> `priority_class`: the priority class name |
+| `kueue_local_queue_evicted_workloads_total` | Counter | The number of evicted workloads per 'local_queue',<br>The label 'reason' can have the following values:<br>- "Preempted" means that the workload was evicted in order to free resources for a workload with a higher priority or reclamation of nominal quota.<br>- "PodsReadyTimeout" means that the eviction took place due to a PodsReady timeout.<br>- "AdmissionCheck" means that the workload was evicted because at least one admission check transitioned to False.<br>- "ClusterQueueStopped" means that the workload was evicted because the ClusterQueue is stopped.<br>- "LocalQueueStopped" means that the workload was evicted because the LocalQueue is stopped.<br>- "NodeFailures" means that the workload was evicted due to node failures when using TopologyAwareScheduling.<br>- "Deactivated" means that the workload was evicted because spec.active is set to false.<br>The label 'underlying_cause' can have the following values:<br>- "" means that the value in 'reason' label is the root cause for eviction.<br>- "AdmissionCheck" means that the workload was evicted by Kueue due to a rejected admission check.<br>- "MaximumExecutionTimeExceeded" means that the workload was evicted by Kueue due to maximum execution time exceeded.<br>- "RequeuingLimitExceeded" means that the workload was evicted by Kueue due to requeuing limit exceeded. | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue<br> `reason`: eviction or preemption reason<br> `underlying_cause`: root cause for eviction<br> `priority_class`: the priority class name |
+| `kueue_local_queue_pending_workloads` | Gauge | The number of pending workloads, per 'local_queue' and 'status'.<br>'status' can have the following values:<br>- "active" means that the workloads are in the admission queue.<br>- "inadmissible" means there was a failed admission attempt for these workloads and they won't be retried until cluster conditions, which could make this workload admissible, change | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue<br> `status`: status label (varies by metric) |
+| `kueue_local_queue_quota_reserved_wait_time_seconds` | Histogram | The time between a workload was created or requeued until it got quota reservation, per 'local_queue' | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue<br> `priority_class`: the priority class name |
+| `kueue_local_queue_quota_reserved_workloads_total` | Counter | The total number of quota reserved workloads per 'local_queue' | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue<br> `priority_class`: the priority class name |
+| `kueue_local_queue_reserving_active_workloads` | Gauge | The number of Workloads that are reserving quota, per 'localQueue' | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue |
+| `kueue_local_queue_resource_reservation` | Gauge | Reports the localQueue's total resource reservation within all the flavors | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue<br> `flavor`: the resource flavor name<br> `resource`: the resource name |
+| `kueue_local_queue_resource_usage` | Gauge | Reports the localQueue's total resource usage within all the flavors | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue<br> `flavor`: the resource flavor name<br> `resource`: the resource name |
+| `kueue_local_queue_status` | Gauge | Reports 'localQueue' with its 'active' status (with possible values 'True', 'False', or 'Unknown').<br>For a LocalQueue, the metric only reports a value of 1 for one of the statuses. | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue<br> `active`: one of `True`, `False`, or `Unknown` |
+<!-- END GENERATED TABLE: localqueue -->
 
 ## Cohort Status
 
-| Metric name                   | Type  | Description                                                                                                                                                                                                                                                                                                                                                                                            | Labels                           |
-|-------------------------------|-------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------|
-| `kueue_cohort_weighted_share` | Gauge | Reports a value that representing the maximum of the ratios of usage above nominal quota to the lendable resources in the Cohort, among all the resources provided by the Cohort, and divided by the weight. If zero, it means that the usage of the Cohort is below the nominal quota. If the Cohort has a weight of zero, this will return 9223372036854775807, the maximum possible share value.    | `cohort`: The name of the Cohort |
+<!-- BEGIN GENERATED TABLE: cohort -->
+| Metric name | Type | Description | Labels |
+| --- | --- | --- | --- |
+| `kueue_cohort_weighted_share` | Gauge | Reports a value that representing the maximum of the ratios of usage above nominal<br>quota to the lendable resources in the Cohort, among all the resources provided by<br>the Cohort, and divided by the weight.<br>If zero, it means that the usage of the Cohort is below the nominal quota.<br>If the Cohort has a weight of zero and is borrowing, this will return NaN. | `cohort`: the name of the Cohort |
+<!-- END GENERATED TABLE: cohort -->
 
 ### Optional metrics
 
 The following metrics are available only if `metrics.enableClusterQueueResources` is enabled in the [manager's configuration](/docs/installation/#install-a-custom-configured-released-version).
 
-
-| Metric name                           | Type  | Description                                                                                                                                                                             | Labels                                                                                                                                                              |
-| --------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `kueue_cluster_queue_resource_reservation` | Gauge | Reports the cluster_queue's total resource reservation within all the flavors                                                                                                      | `cohort`: The cohort in which the queue belongs<br> `cluster_queue`: The name of the ClusterQueue<br> `flavor`: referenced flavor<br> `resource`: The resource name |
-| `kueue_cluster_queue_resource_usage`  | Gauge | Reports the ClusterQueue's total resource usage                                                                                                                                         | `cohort`: The cohort in which the queue belongs<br> `cluster_queue`: The name of the ClusterQueue<br> `flavor`: referenced flavor<br> `resource`: The resource name |
-| `kueue_cluster_queue_nominal_quota`   | Gauge | Reports the ClusterQueue's resource quota                                                                                                                                               | `cohort`: The cohort in which the queue belongs<br> `cluster_queue`: The name of the ClusterQueue<br> `flavor`: referenced flavor<br> `resource`: The resource name |
-| `kueue_cluster_queue_borrowing_limit` | Gauge | Reports the ClusterQueue's resource borrowing limit                                                                                                                                     | `cohort`: The cohort in which the queue belongs<br> `cluster_queue`: The name of the ClusterQueue<br> `flavor`: referenced flavor<br> `resource`: The resource name |
-| `kueue_cluster_queue_lending_limit`   | Gauge | Reports the cluster_queue's resource lending limit within all the flavors                                                                                                               | `cohort`: The cohort in which the queue belongs<br> `cluster_queue`: The name of the ClusterQueue<br> `flavor`: referenced flavor<br> `resource`: The resource name |
-| `kueue_cluster_queue_weighted_share`  | Gauge | Reports a value that representing the maximum of the ratios of usage above nominal quota to the lendable resources in the cohort, among all the resources provided by the ClusterQueue. | `cluster_queue`: The name of the ClusterQueue 
+<!-- BEGIN GENERATED TABLE: optional_clusterqueue_resources -->
+| Metric name | Type | Description | Labels |
+| --- | --- | --- | --- |
+| `kueue_cluster_queue_borrowing_limit` | Gauge | Reports the cluster_queue's resource borrowing limit within all the flavors | `cohort`: the name of the Cohort<br> `cluster_queue`: the name of the ClusterQueue<br> `flavor`: the resource flavor name<br> `resource`: the resource name |
+| `kueue_cluster_queue_lending_limit` | Gauge | Reports the cluster_queue's resource lending limit within all the flavors | `cohort`: the name of the Cohort<br> `cluster_queue`: the name of the ClusterQueue<br> `flavor`: the resource flavor name<br> `resource`: the resource name |
+| `kueue_cluster_queue_nominal_quota` | Gauge | Reports the cluster_queue's resource nominal quota within all the flavors | `cohort`: the name of the Cohort<br> `cluster_queue`: the name of the ClusterQueue<br> `flavor`: the resource flavor name<br> `resource`: the resource name |
+| `kueue_cluster_queue_resource_reservation` | Gauge | Reports the cluster_queue's total resource reservation within all the flavors | `cohort`: the name of the Cohort<br> `cluster_queue`: the name of the ClusterQueue<br> `flavor`: the resource flavor name<br> `resource`: the resource name |
+| `kueue_cluster_queue_resource_usage` | Gauge | Reports the cluster_queue's total resource usage within all the flavors | `cohort`: the name of the Cohort<br> `cluster_queue`: the name of the ClusterQueue<br> `flavor`: the resource flavor name<br> `resource`: the resource name |
+| `kueue_cluster_queue_weighted_share` | Gauge | Reports a value that representing the maximum of the ratios of usage above nominal<br>quota to the lendable resources in the cohort, among all the resources provided by<br>the ClusterQueue, and divided by the weight.<br>If zero, it means that the usage of the ClusterQueue is below the nominal quota.<br>If the ClusterQueue has a weight of zero and is borrowing, this will return NaN. | `cluster_queue`: the name of the ClusterQueue<br> `cohort`: the name of the Cohort |
+<!-- END GENERATED TABLE: optional_clusterqueue_resources -->
 
 
 The following metrics are available only if `waitForPodsReady` is enabled in the [manager's configuration](/docs/installation/#install-a-custom-configured-released-version).
-For more details [see](/docs/tasks/manage/setup_wait_for_pods_ready).                                                                                                                      
-
-
-| Metric name                                                | Type      | Description                                                                        | Labels                                                                                            |
-| ---------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
-| `kueue_ready_wait_time_seconds`                            | Histogram | The time between a workload was created or requeued until ready.                   | `cluster_queue`: the name of the ClusterQueue<br> `priority_class`: the priority class name |
-| `kueue_admitted_until_ready_wait_time_seconds`             | Histogram | The time between a workload was admitted until ready.                              | `cluster_queue`: the name of the ClusterQueue<br> `priority_class`: the priority class name |
-| `kueue_local_queue_ready_wait_time_seconds`                | Histogram | The time between a workload was created or requeued until ready, per `local_queue` | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in<br />`priority_class`: the priority class name |
-| `kueue_local_queue_admitted_until_ready_wait_time_seconds` | Histogram | The time between a workload was admitted until ready, per `local_queue`            | `name`: the name of the LocalQueue<br />`namespace`: the namespace that the LocalQueue resides in<br />`priority_class`: the priority class name |
\ No newline at end of file
+For more details [see](/docs/tasks/manage/setup_wait_for_pods_ready).
+
+<!-- BEGIN GENERATED TABLE: optional_wait_for_pods_ready -->
+| Metric name | Type | Description | Labels |
+| --- | --- | --- | --- |
+| `kueue_admitted_until_ready_wait_time_seconds` | Histogram | The time between a workload was admitted until ready, per 'cluster_queue' | `cluster_queue`: the name of the ClusterQueue<br> `priority_class`: the priority class name |
+| `kueue_local_queue_admitted_until_ready_wait_time_seconds` | Histogram | The time between a workload was admitted until ready, per 'local_queue' | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue<br> `priority_class`: the priority class name |
+| `kueue_local_queue_ready_wait_time_seconds` | Histogram | The time between a workload was created or requeued until ready, per 'local_queue' | `name`: the name of the LocalQueue<br> `namespace`: the namespace of the LocalQueue<br> `priority_class`: the priority class name |
+| `kueue_ready_wait_time_seconds` | Histogram | The time between a workload was created or requeued until ready, per 'cluster_queue' | `cluster_queue`: the name of the ClusterQueue<br> `priority_class`: the priority class name |
+<!-- END GENERATED TABLE: optional_wait_for_pods_ready -->



Test patch:
diff --git a/pkg/workload/workload_test.go b/pkg/workload/workload_test.go
index 7bc5d1af6..fd399df4b 100644
--- a/pkg/workload/workload_test.go
+++ b/pkg/workload/workload_test.go
@@ -1652,7 +1652,7 @@ func TestPatchAdmissionStatus(t *testing.T) {
 					cl = utiltesting.NewFakeClient(wl)
 				}
 				called := false
-				gotErr := PatchAdmissionStatus(ctx, cl, wl, fakeClock, func() (bool, error) {
+				gotErr := PatchAdmissionStatus(ctx, cl, wl, fakeClock, func(wl *kueue.Workload) (bool, error) {
 					called = true
 					return tc.patchCall.updated, tc.patchCall.err
 				})
diff --git a/test/e2e/singlecluster/tas_test.go b/test/e2e/singlecluster/tas_test.go
index edcbbcad7..9cf8df6c0 100644
--- a/test/e2e/singlecluster/tas_test.go
+++ b/test/e2e/singlecluster/tas_test.go
@@ -105,19 +105,22 @@ var _ = ginkgo.Describe("TopologyAwareScheduling", func() {
 			util.MustCreate(ctx, k8sClient, sampleJob)
 
 			createdWorkload := &kueue.Workload{}
+			wlLookupKey := types.NamespacedName{Name: workloadjob.GetWorkloadNameForJob(sampleJob.Name, sampleJob.UID), Namespace: ns.Name}
+
+			ginkgo.By(fmt.Sprintf("await for admission of workload %q", wlLookupKey), func() {
+				gomega.Eventually(func(g gomega.Gomega) {
+					g.Expect(k8sClient.Get(ctx, wlLookupKey, createdWorkload)).Should(gomega.Succeed())
+					g.Expect(createdWorkload.Status.Admission).ShouldNot(gomega.BeNil())
+				}, util.LongTimeout, util.Interval).Should(gomega.Succeed())
+			})
 
 			// The job might have finished at this point. That shouldn't be a problem for the purpose of this test
 			jobKey := client.ObjectKeyFromObject(sampleJob)
 			util.ExpectJobUnsuspendedWithNodeSelectors(ctx, k8sClient, jobKey, map[string]string{
 				"instance-type": "on-demand",
 			})
-			wlLookupKey := types.NamespacedName{Name: workloadjob.GetWorkloadNameForJob(sampleJob.Name, sampleJob.UID), Namespace: ns.Name}
 
-			ginkgo.By(fmt.Sprintf("await for admission of workload %q and verify TopologyAssignment", wlLookupKey), func() {
-				gomega.Eventually(func(g gomega.Gomega) {
-					g.Expect(k8sClient.Get(ctx, wlLookupKey, createdWorkload)).Should(gomega.Succeed())
-					g.Expect(createdWorkload.Status.Admission).ShouldNot(gomega.BeNil())
-				}, util.LongTimeout, util.Interval).Should(gomega.Succeed())
+			ginkgo.By("verify TopologyAssignment", func() {
 				gomega.Expect(createdWorkload.Status.Admission.PodSetAssignments).Should(gomega.HaveLen(1))
 				gomega.Expect(createdWorkload.Status.Admission.PodSetAssignments[0].TopologyAssignment).Should(gomega.BeComparableTo(
 					tas.V1Beta2From(&tas.TopologyAssignment{
diff --git a/test/integration/multikueue/dispatcher_test.go b/test/integration/multikueue/dispatcher_test.go
index 79c19c532..49482d91a 100644
--- a/test/integration/multikueue/dispatcher_test.go
+++ b/test/integration/multikueue/dispatcher_test.go
@@ -215,13 +215,13 @@ var _ = ginkgo.Describe("MultiKueueDispatcherIncremental", ginkgo.Ordered, ginkg
 		ginkgo.By("setting the check conditions for eviction", func() {
 			gomega.Eventually(func(g gomega.Gomega) {
 				g.Expect(managerTestCluster.client.Get(managerTestCluster.ctx, wlLookupKey, createdWorkload)).To(gomega.Succeed())
-				g.Expect(workload.PatchAdmissionStatus(managerTestCluster.ctx, managerTestCluster.client, createdWorkload, util.RealClock, func() (bool, error) {
+				g.Expect(workload.PatchAdmissionStatus(managerTestCluster.ctx, managerTestCluster.client, createdWorkload, util.RealClock, func(wl *kueue.Workload) (bool, error) {
 					acs := kueue.AdmissionCheckState{
 						Name:    kueue.AdmissionCheckReference(multiKueueAC.Name),
 						State:   kueue.CheckStateRejected,
 						Message: "check rejected",
 					}
-					return workload.SetAdmissionCheckState(&createdWorkload.Status.AdmissionChecks, acs, util.RealClock), nil
+					return workload.SetAdmissionCheckState(&wl.Status.AdmissionChecks, acs, util.RealClock), nil
 				})).To(gomega.Succeed())
 			}, util.Timeout, util.Interval).Should(gomega.Succeed())
 		})
@@ -388,8 +388,8 @@ var _ = ginkgo.Describe("MultiKueueDispatcherExternal", ginkgo.Ordered, ginkgo.C
 			gomega.Eventually(func(g gomega.Gomega) {
 				managerWl := &kueue.Workload{}
 				g.Expect(managerTestCluster.client.Get(managerTestCluster.ctx, wlLookupKey, managerWl)).To(gomega.Succeed())
-				g.Expect(workload.PatchAdmissionStatus(managerTestCluster.ctx, managerTestCluster.client, managerWl, util.RealClock, func() (bool, error) {
-					managerWl.Status.NominatedClusterNames = []string{workerCluster2.Name}
+				g.Expect(workload.PatchAdmissionStatus(managerTestCluster.ctx, managerTestCluster.client, managerWl, util.RealClock, func(wl *kueue.Workload) (bool, error) {
+					wl.Status.NominatedClusterNames = []string{workerCluster2.Name}
 					return true, nil
 				})).To(gomega.Succeed())
 			}, util.Timeout, util.Interval).Should(gomega.Succeed())
@@ -418,8 +418,8 @@ var _ = ginkgo.Describe("MultiKueueDispatcherExternal", ginkgo.Ordered, ginkgo.C
 			managerWl := &kueue.Workload{}
 			gomega.Eventually(func(g gomega.Gomega) {
 				g.Expect(managerTestCluster.client.Get(managerTestCluster.ctx, wlLookupKey, managerWl)).To(gomega.Succeed())
-				g.Expect(workload.PatchAdmissionStatus(managerTestCluster.ctx, managerTestCluster.client, managerWl, util.RealClock, func() (bool, error) {
-					managerWl.Status.NominatedClusterNames = []string{workerCluster1.Name, workerCluster2.Name}
+				g.Expect(workload.PatchAdmissionStatus(managerTestCluster.ctx, managerTestCluster.client, managerWl, util.RealClock, func(wl *kueue.Workload) (bool, error) {
+					wl.Status.NominatedClusterNames = []string{workerCluster1.Name, workerCluster2.Name}
 					return true, nil
 				})).To(gomega.Succeed())
 			}, util.Timeout, util.Interval).Should(gomega.Succeed())
@@ -460,13 +460,13 @@ var _ = ginkgo.Describe("MultiKueueDispatcherExternal", ginkgo.Ordered, ginkgo.C
 		ginkgo.By("setting the check conditions for eviction", func() {
 			gomega.Eventually(func(g gomega.Gomega) {
 				g.Expect(managerTestCluster.client.Get(managerTestCluster.ctx, wlLookupKey, createdWorkload)).To(gomega.Succeed())
-				g.Expect(workload.PatchAdmissionStatus(managerTestCluster.ctx, managerTestCluster.client, createdWorkload, util.RealClock, func() (bool, error) {
+				g.Expect(workload.PatchAdmissionStatus(managerTestCluster.ctx, managerTestCluster.client, createdWorkload, util.RealClock, func(wl *kueue.Workload) (bool, error) {
 					acs := kueue.AdmissionCheckState{
 						Name:    kueue.AdmissionCheckReference(multiKueueAC.Name),
 						State:   kueue.CheckStateRejected,
 						Message: "check rejected",
 					}
-					return workload.SetAdmissionCheckState(&createdWorkload.Status.AdmissionChecks, acs, util.RealClock), nil
+					return workload.SetAdmissionCheckState(&wl.Status.AdmissionChecks, acs, util.RealClock), nil
 				})).To(gomega.Succeed())
 			}, util.Timeout, util.Interval).Should(gomega.Succeed())
 		})
@@ -677,8 +677,8 @@ var _ = ginkgo.Describe("MultiKueueDispatcherAllAtOnce", ginkgo.Ordered, ginkgo.
 		ginkgo.By("Resetting ClusterName field in the manager", func() {
 			gomega.Eventually(func(g gomega.Gomega) {
 				g.Expect(managerTestCluster.client.Get(managerTestCluster.ctx, wlLookupKey, createdWorkload)).To(gomega.Succeed())
-				g.Expect(workload.PatchAdmissionStatus(managerTestCluster.ctx, managerTestCluster.client, createdWorkload, util.RealClock, func() (bool, error) {
-					createdWorkload.Status.ClusterName = nil
+				g.Expect(workload.PatchAdmissionStatus(managerTestCluster.ctx, managerTestCluster.client, createdWorkload, util.RealClock, func(wl *kueue.Workload) (bool, error) {
+					wl.Status.ClusterName = nil
 					return true, nil
 				})).To(gomega.Succeed())
 			}, util.Timeout, util.Interval).Should(gomega.Succeed())
diff --git a/test/integration/singlecluster/controller/core/workload_controller_test.go b/test/integration/singlecluster/controller/core/workload_controller_test.go
index 53fc05ac7..9ecc3525b 100644
--- a/test/integration/singlecluster/controller/core/workload_controller_test.go
+++ b/test/integration/singlecluster/controller/core/workload_controller_test.go
@@ -575,7 +575,7 @@ var _ = ginkgo.Describe("Workload controller", ginkgo.Ordered, ginkgo.ContinueOn
 			ginkgo.By("evicting the workload, the accumulated admission time is updated", func() {
 				gomega.Eventually(func(g gomega.Gomega) {
 					g.Expect(k8sClient.Get(ctx, key, wl)).To(gomega.Succeed())
-					g.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, wl, util.RealClock, func() (bool, error) {
+					g.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, wl, util.RealClock, func(wl *kueue.Workload) (bool, error) {
 						return workload.SetEvictedCondition(wl, util.RealClock.Now(), "ByTest", "by test"), nil
 					})).Should(gomega.Succeed())
 				}, util.Timeout, util.Interval).Should(gomega.Succeed())
diff --git a/test/integration/singlecluster/controller/jobs/pod/pod_controller_test.go b/test/integration/singlecluster/controller/jobs/pod/pod_controller_test.go
index e4f2d694c..8ea6d8cd9 100644
--- a/test/integration/singlecluster/controller/jobs/pod/pod_controller_test.go
+++ b/test/integration/singlecluster/controller/jobs/pod/pod_controller_test.go
@@ -833,8 +833,8 @@ var _ = ginkgo.Describe("Pod controller", ginkgo.Ordered, ginkgo.ContinueOnFailu
 					createdWorkload := &kueue.Workload{}
 					gomega.Eventually(func(g gomega.Gomega) {
 						g.Expect(k8sClient.Get(ctx, wlLookupKey, createdWorkload)).Should(gomega.Succeed())
-						g.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, createdWorkload, util.RealClock, func() (bool, error) {
-							return workload.SetEvictedCondition(createdWorkload, util.RealClock.Now(), "ByTest", "by test"), nil
+						g.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, createdWorkload, util.RealClock, func(wl *kueue.Workload) (bool, error) {
+							return workload.SetEvictedCondition(wl, util.RealClock.Now(), "ByTest", "by test"), nil
 						})).Should(gomega.Succeed())
 					}, util.Timeout, util.Interval).Should(gomega.Succeed())
 
@@ -1426,7 +1426,7 @@ var _ = ginkgo.Describe("Pod controller", ginkgo.Ordered, ginkgo.ContinueOnFailu
 				ginkgo.By("setting evicted condition to true", func() {
 					gomega.Eventually(func(g gomega.Gomega) {
 						g.Expect(k8sClient.Get(ctx, wlKey, wl)).Should(gomega.Succeed())
-						g.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, wl, util.RealClock, func() (bool, error) {
+						g.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, wl, util.RealClock, func(wl *kueue.Workload) (bool, error) {
 							return workload.SetEvictedCondition(wl, util.RealClock.Now(), kueue.WorkloadEvictedByPreemption, "By test"), nil
 						})).Should(gomega.Succeed())
 					}, util.Timeout, util.Interval).Should(gomega.Succeed())
diff --git a/test/integration/singlecluster/scheduler/podsready/scheduler_test.go b/test/integration/singlecluster/scheduler/podsready/scheduler_test.go
index b916ae44f..3743a66fb 100644
--- a/test/integration/singlecluster/scheduler/podsready/scheduler_test.go
+++ b/test/integration/singlecluster/scheduler/podsready/scheduler_test.go
@@ -328,8 +328,8 @@ var _ = ginkgo.Describe("SchedulerWithWaitForPodsReady", func() {
 				g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(prodWl), prodWl)).Should(gomega.Succeed())
 				g.Expect(workload.IsActive(prodWl)).Should(gomega.BeFalse())
 				g.Expect(prodWl.Status.RequeueState).Should(gomega.BeNil())
-				g.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, prodWl, util.RealClock, func() (bool, error) {
-					return workload.SetRequeuedCondition(prodWl, kueue.WorkloadDeactivated, "by test", false), nil
+				g.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, prodWl, util.RealClock, func(wl *kueue.Workload) (bool, error) {
+					return workload.SetRequeuedCondition(wl, kueue.WorkloadDeactivated, "by test", false), nil
 				})).Should(gomega.Succeed())
 			}, util.Timeout, util.Interval).Should(gomega.Succeed())
 			util.FinishEvictionForWorkloads(ctx, k8sClient, prodWl)
diff --git a/test/integration/singlecluster/webhook/core/workload_test.go b/test/integration/singlecluster/webhook/core/workload_test.go
index 16c2af40c..6b4b35407 100644
--- a/test/integration/singlecluster/webhook/core/workload_test.go
+++ b/test/integration/singlecluster/webhook/core/workload_test.go
@@ -359,7 +359,7 @@ var _ = ginkgo.Describe("Workload validating webhook", ginkgo.Ordered, func() {
 
 			gomega.Eventually(func(g gomega.Gomega) {
 				g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), wl)).To(gomega.Succeed())
-				err := workload.PatchAdmissionStatus(ctx, k8sClient, wl, util.RealClock, func() (bool, error) {
+				err := workload.PatchAdmissionStatus(ctx, k8sClient, wl, util.RealClock, func(wl *kueue.Workload) (bool, error) {
 					return workload.SetQuotaReservation(wl, a, util.RealClock), nil
 				})
 				g.Expect(err).Should(matcher)
diff --git a/test/performance/scheduler/runner/controller/controller.go b/test/performance/scheduler/runner/controller/controller.go
index b4d91d545..beaeb231f 100644
--- a/test/performance/scheduler/runner/controller/controller.go
+++ b/test/performance/scheduler/runner/controller/controller.go
@@ -117,8 +117,8 @@ func (r *reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
 	// this should only:
 	// 1. finish the workloads eviction
 	if apimeta.IsStatusConditionTrue(wl.Status.Conditions, kueue.WorkloadEvicted) {
-		err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func() (bool, error) {
-			return workload.UnsetQuotaReservationWithCondition(&wl, "Pending", "Evicted by the test runner", time.Now()), nil
+		err := workload.PatchAdmissionStatus(ctx, r.client, &wl, r.clock, func(wl *kueue.Workload) (bool, error) {
+			return workload.UnsetQuotaReservationWithCondition(wl, "Pending", "Evicted by the test runner", time.Now()), nil
 		})
 		if err == nil {
 			log.V(5).Info("Finish eviction")
diff --git a/test/util/util.go b/test/util/util.go
index f00edc217..814928750 100644
--- a/test/util/util.go
+++ b/test/util/util.go
@@ -438,8 +438,8 @@ func SetRequeuedConditionWithPodsReadyTimeout(ctx context.Context, k8sClient cli
 	gomega.EventuallyWithOffset(1, func(g gomega.Gomega) {
 		var wl kueue.Workload
 		g.Expect(k8sClient.Get(ctx, wlKey, &wl)).Should(gomega.Succeed())
-		g.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, &wl, RealClock, func() (bool, error) {
-			return workload.SetRequeuedCondition(&wl, kueue.WorkloadEvictedByPodsReadyTimeout, fmt.Sprintf("Exceeded the PodsReady timeout %s", klog.KObj(&wl).String()), false), nil
+		g.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, &wl, RealClock, func(wl *kueue.Workload) (bool, error) {
+			return workload.SetRequeuedCondition(wl, kueue.WorkloadEvictedByPodsReadyTimeout, fmt.Sprintf("Exceeded the PodsReady timeout %s", klog.KObj(wl).String()), false), nil
 		})).Should(gomega.Succeed())
 	}, Timeout, Interval).Should(gomega.Succeed())
 }
@@ -818,12 +818,12 @@ func SetQuotaReservation(ctx context.Context, k8sClient client.Client, wlKey cli
 	gomega.EventuallyWithOffset(1, func(g gomega.Gomega) {
 		updatedWl := &kueue.Workload{}
 		g.ExpectWithOffset(1, k8sClient.Get(ctx, wlKey, updatedWl)).To(gomega.Succeed())
-		g.ExpectWithOffset(1, workload.PatchAdmissionStatus(ctx, k8sClient, updatedWl, clk, func() (bool, error) {
+		g.ExpectWithOffset(1, workload.PatchAdmissionStatus(ctx, k8sClient, updatedWl, clk, func(wl *kueue.Workload) (bool, error) {
 			var updated bool
 			if admission == nil {
-				updated = workload.UnsetQuotaReservationWithCondition(updatedWl, "EvictedByTest", "Evicted By Test", clk.Now())
+				updated = workload.UnsetQuotaReservationWithCondition(wl, "EvictedByTest", "Evicted By Test", clk.Now())
 			} else {
-				updated = workload.SetQuotaReservation(updatedWl, admission, clk)
+				updated = workload.SetQuotaReservation(wl, admission, clk)
 			}
 			return updated, nil
 		})).To(gomega.Succeed())
@@ -838,8 +838,8 @@ func SyncAdmittedConditionForWorkloads(ctx context.Context, k8sClient client.Cli
 	for _, wl := range wls {
 		gomega.EventuallyWithOffset(1, func(g gomega.Gomega) {
 			g.ExpectWithOffset(1, k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWorkload)).To(gomega.Succeed())
-			g.ExpectWithOffset(1, workload.PatchAdmissionStatus(ctx, k8sClient, &updatedWorkload, RealClock, func() (bool, error) {
-				return workload.SyncAdmittedCondition(&updatedWorkload, time.Now()), nil
+			g.ExpectWithOffset(1, workload.PatchAdmissionStatus(ctx, k8sClient, &updatedWorkload, RealClock, func(wl *kueue.Workload) (bool, error) {
+				return workload.SyncAdmittedCondition(wl, time.Now()), nil
 			})).To(gomega.Succeed())
 		}, Timeout, Interval).Should(gomega.Succeed())
 	}
@@ -864,8 +864,8 @@ func FinishEvictionForWorkloads(ctx context.Context, k8sClient client.Client, wl
 			var updatedWorkload kueue.Workload
 			g.Expect(k8sClient.Get(ctx, key, &updatedWorkload)).Should(gomega.Succeed())
 			if apimeta.IsStatusConditionTrue(updatedWorkload.Status.Conditions, kueue.WorkloadQuotaReserved) {
-				g.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, &updatedWorkload, RealClock, func() (bool, error) {
-					return workload.UnsetQuotaReservationWithCondition(&updatedWorkload, "Pending", "By test", time.Now()), nil
+				g.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, &updatedWorkload, RealClock, func(wl *kueue.Workload) (bool, error) {
+					return workload.UnsetQuotaReservationWithCondition(wl, "Pending", "By test", time.Now()), nil
 				}),
 				).Should(gomega.Succeed(), fmt.Sprintf("Unable to unset quota reservation for %q", key))
 			}
diff --git a/test/util/util_scheduling.go b/test/util/util_scheduling.go
index 5c2cb58a5..dc652dff3 100644
--- a/test/util/util_scheduling.go
+++ b/test/util/util_scheduling.go
@@ -55,8 +55,8 @@ func finishEvictionsOfAnyWorkloadsInCq(ctx context.Context, k8sClient client.Cli
 		evicted := meta.IsStatusConditionTrue(wl.Status.Conditions, kueue.WorkloadEvicted)
 		quotaReserved := meta.IsStatusConditionTrue(wl.Status.Conditions, kueue.WorkloadQuotaReserved)
 		if evicted && quotaReserved {
-			gomega.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, &wl, RealClock, func() (bool, error) {
-				return workload.UnsetQuotaReservationWithCondition(&wl, "Pending", "Eviction finished by test", time.Now()), nil
+			gomega.Expect(workload.PatchAdmissionStatus(ctx, k8sClient, &wl, RealClock, func(wl *kueue.Workload) (bool, error) {
+				return workload.UnsetQuotaReservationWithCondition(wl, "Pending", "Eviction finished by test", time.Now()), nil
 			}),
 			).To(gomega.Succeed())
 			finished.Insert(wl.UID)