Repository: kubernetes-sigs/gateway-api-inference-extension

Issue:
[bug] with same Model Name, reqs to the same pod
https://github.com/kubernetes-sigs/gateway-api-inference-extension/blame/fdea49d87eac966798c381aec0e5a8e665414891/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go#L235C3-L235C32


Hints from issue comments:
@LLMHao: func hashPrompt(ctx context.Context, request *types.LLMRequest, cacheBlockSize int, maxPrefixBlocks int) []BlockHash {
    prompt := []byte(request.Prompt)
    if len(prompt) < cacheBlockSize {
        return nil
    }
    if len(prompt) > cacheBlockSize*maxPrefixBlocks {
        prompt = prompt[:maxPrefixBlocks*cacheBlockSize]
    }
    
    res := make([]BlockHash, 0, len(prompt)/cacheBlockSize)
    if len(prompt) >= cacheBlockSize {
        firstBlock := prompt[0:cacheBlockSize]
        combined := append([]byte(request.TargetModel), firstBlock...)
        res = append(res, BlockHash(xxhash.Sum64(combined)))
    }
    
    for i := cacheBlockSize; i+cacheBlockSize <= len(prompt); i += cacheBlockSize {
        block := prompt[i : i+cacheBlockSize]
        prevBlockHash := res[len(res)-1]
        blockWithPrev := append(block, toBytes(prevBlockHash)...)
        res = append(res, BlockHash(xxhash.Sum64(blockWithPrev)))
    }
    
    return res
}@nirrozenbaum: cc: @vMaroon @kfirtoledo @liu-cong 

Solution patch:
diff --git a/.custom-gcl.yml b/.custom-gcl.yml
new file mode 100644
index 0000000..fc27c17
--- /dev/null
+++ b/.custom-gcl.yml
@@ -0,0 +1,6 @@
+version: v2.3.1
+name: golangci-kube-api-linter
+destination: ./bin
+plugins:
+- module: 'sigs.k8s.io/kube-api-linter'
+  version: 'v0.0.0-20250808120943-48643eb2563d' # Pin to a commit while there's no tag
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..d21c25e
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,56 @@
+<!--  Thanks for sending a pull request! Here are some tips for you:
+
+1. If this is your first time contributing to Gateway API Inference Extension, please read our
+   developer guide (https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/docs/dev.md)
+   and our community page (https://gateway-api-inference-extension.sigs.k8s.io/contributing/).
+2. If this is your first time contributing to a Kubernetes project, please read
+   our contributor guidelines:
+   https://git.k8s.io/community/contributors/guide/first-contribution.md#your-first-contribution
+3. Please label this pull request according to what type of issue you are
+   addressing, especially if this is a release targeted pull request. For
+   reference on required PR/issue labels, read here:
+   https://git.k8s.io/community/contributors/devel/sig-release/release.md#issuepr-kind-label
+4. If you want *faster* PR reviews, read how:
+   https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
+5. If the PR is unfinished, see how to mark it:
+   https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
+6. If this PR is a new design proposal, please review existing design docs for guidance:
+   https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/docs/proposals
+-->
+
+**What type of PR is this?**
+<!--
+Add one of the following kinds:
+/kind bug
+/kind cleanup
+/kind documentation
+/kind feature
+/kind gep
+/kind test
+
+Optionally add one or more of the following kinds if applicable:
+/kind deprecation
+/kind failing-test
+/kind flake
+/kind regression
+/area conformance-test
+/area conformance-machinery
+-->
+
+**What this PR does / why we need it**:
+
+**Which issue(s) this PR fixes**:
+<!--
+*Automatically closes linked issue when PR is merged.
+Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
+-->
+Fixes #
+
+**Does this PR introduce a user-facing change?**:
+<!--
+If no, just write "NONE" in the release-note block below.
+If yes, please enter a release note below:
+-->
+```release-note
+
+```
diff --git a/.github/workflows/kal.yml b/.github/workflows/kal.yml
new file mode 100644
index 0000000..618256e
--- /dev/null
+++ b/.github/workflows/kal.yml
@@ -0,0 +1,28 @@
+name: PR golangci-lint
+
+on:
+  pull_request:
+    types: [opened, edited, synchronize, reopened]
+
+# Remove all permissions from GITHUB_TOKEN except metadata.
+permissions: {}
+
+jobs:
+  golangci:
+    name: kube-api-lint
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+    steps:
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.2.2
+        name: Checkout code
+        with:
+          persist-credentials: false
+      - name: Set up Go
+        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # tag=v5.5.0
+      - name: Install Golang CI Lint
+        run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.2.1
+      - name: Build KAL
+        run: golangci-lint custom
+      - name: run api linter
+        run: ./bin/golangci-kube-api-linter run -c ./.golangci-kal.yml ./...
diff --git a/.gitignore b/.gitignore
index 4442b65..82afc2e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,6 @@ go.work.sum
 
 # generated docs
 site
+
+# MacOS generated files
+**/.DS_Store
diff --git a/.golangci-kal.yml b/.golangci-kal.yml
new file mode 100644
index 0000000..099d81d
--- /dev/null
+++ b/.golangci-kal.yml
@@ -0,0 +1,48 @@
+version: "2"
+linters:
+  default: none
+  enable:
+    - kubeapilinter
+  settings:
+    custom:
+      kubeapilinter:
+        type: module
+        description: Kube API LInter lints Kube like APIs based on API conventions and best practices.
+        settings:
+          linters:
+            enable:
+              - "*"
+            disable:
+              - "commentstart"
+          lintersConfig:
+            conflictingmarkers:
+              conflicts:
+              - name: "default_vs_required"
+                sets:
+                  - ["default", "kubebuilder:default"]
+                  - ["required", "kubebuilder:validation:Required", "k8s:required"]
+                description: "A field with a default value cannot be required"
+            conditions:
+              isFirstField: Warn # Require conditions to be the first field in the status struct.
+              usePatchStrategy: Forbid # Forbid patchStrategy markers on the Conditions field.
+              useProtobuf: Forbid # We don't use protobuf, so protobuf tags are not required.
+            optionalfields:
+              pointers:
+                preference: WhenRequired # Always | WhenRequired # Whether to always require pointers, or only when required. Defaults to `Always`.
+                policy: SuggestFix # SuggestFix | Warn # The policy for pointers in optional fields. Defaults to `SuggestFix`. 
+              omitempty:
+                policy: SuggestFix # SuggestFix | Warn | Ignore # The policy for omitempty in optional fields. Defaults to `SuggestFix`.
+              omitzero:
+                policy: Forbid # Enforce the `omitempty` route with a pointer for all optional structs.
+  exclusions:
+    generated: strict
+    paths:
+      - conformance/
+    paths-except:
+      - api/
+issues:
+  max-issues-per-linter: 0
+  max-same-issues: 0
+run:
+  timeout: 5m
+  tests: false
diff --git a/Makefile b/Makefile
index ba66ef4..023ed0e 100644
--- a/Makefile
+++ b/Makefile
@@ -298,16 +298,16 @@ live-docs:
 	docker run --rm -it -p 3000:3000 -v ${PWD}:/docs gaie/mkdocs
 
 .PHONY: apix-ref-docs
-apix-ref-docs:
-	crd-ref-docs \
+apix-ref-docs: crd-ref-docs
+	${CRD_REF_DOCS} \
 		--source-path=${PWD}/apix/v1alpha2 \
 		--config=crd-ref-docs.yaml \
 		--renderer=markdown \
 		--output-path=${PWD}/site-src/reference/x-spec.md
 
 .PHONY: api-ref-docs
-api-ref-docs:
-	crd-ref-docs \
+api-ref-docs: crd-ref-docs
+	${CRD_REF_DOCS} \
 		--source-path=${PWD}/api \
 		--config=crd-ref-docs.yaml \
 		--renderer=markdown \
@@ -328,11 +328,11 @@ uninstall: generate kustomize ## Uninstall CRDs from the K8s cluster specified i
 	$(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f -
 
 ##@ Helm
-PHONY: inferencepool-helm-chart-push
+.PHONY: inferencepool-helm-chart-push
 inferencepool-helm-chart-push: yq helm
 	CHART=inferencepool EXTRA_TAG="$(EXTRA_TAG)" IMAGE_REGISTRY="$(IMAGE_REGISTRY)" YQ="$(YQ)" HELM="$(HELM)" ./hack/push-chart.sh
 
-PHONY: bbr-helm-chart-push
+.PHONY: bbr-helm-chart-push
 bbr-helm-chart-push: yq helm
 	CHART=body-based-routing EXTRA_TAG="$(EXTRA_TAG)" IMAGE_REGISTRY="$(IMAGE_REGISTRY)" YQ="$(YQ)" HELM="$(HELM)" ./hack/push-chart.sh
 
@@ -364,6 +364,7 @@ KUBECTL ?= kubectl
 KUSTOMIZE ?= $(LOCALBIN)/kustomize
 CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
 ENVTEST ?= $(LOCALBIN)/setup-envtest
+CRD_REF_DOCS ?= $(LOCALBIN)/crd-ref-docs
 GOLANGCI_LINT = $(LOCALBIN)/golangci-lint
 HELM = $(PROJECT_DIR)/bin/helm
 YQ = $(PROJECT_DIR)/bin/yq
@@ -374,10 +375,12 @@ GCI = $(LOCALBIN)/gci
 KUSTOMIZE_VERSION ?= v5.4.3
 CONTROLLER_TOOLS_VERSION ?= v0.16.1
 ENVTEST_VERSION ?= release-0.19
+CRD_REF_DOCS_VERSION ?= v0.2.0
 GOLANGCI_LINT_VERSION ?= v2.3.0
 HELM_VERSION ?= v3.17.1
 KUBECTL_VALIDATE_VERSION ?= v0.0.4
 GCI_VERSION ?= v0.13.6
+YQ_VERSION ?= v4.45.1
 
 .PHONY: kustomize
 kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
@@ -389,6 +392,11 @@ controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessar
 $(CONTROLLER_GEN): $(LOCALBIN)
 	$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))
 
+.PHONY: crd-ref-docs
+crd-ref-docs: $(CRD_REF_DOCS) ## Download crd-ref-docs locally if necessary.
+$(CRD_REF_DOCS): $(LOCALBIN)
+	$(call go-install-tool,$(CRD_REF_DOCS),github.com/elastic/crd-ref-docs,$(CRD_REF_DOCS_VERSION))
+
 .PHONY: envtest
 envtest: $(ENVTEST) ## Download setup-envtest locally if necessary.
 $(ENVTEST): $(LOCALBIN)
@@ -401,7 +409,7 @@ $(GOLANGCI_LINT): $(LOCALBIN)
 
 .PHONY: yq
 yq: ## Download yq locally if necessary.
-	GOBIN=$(PROJECT_DIR)/bin GO111MODULE=on go install github.com/mikefarah/yq/v4@v4.45.1
+	GOBIN=$(PROJECT_DIR)/bin GO111MODULE=on go install github.com/mikefarah/yq/v4@$(YQ_VERSION)
 
 .PHONY: helm
 helm: ## Download helm locally if necessary.
diff --git a/OWNERS b/OWNERS
index ecb73d5..596ad1b 100644
--- a/OWNERS
+++ b/OWNERS
@@ -2,7 +2,6 @@
 
 approvers:
 - gateway-api-inference-extension-maintainers
-- wg-serving-leads
 
 reviewers:
 - gateway-api-inference-extension-reviewers
diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES
index 32a0493..6273d0e 100644
--- a/OWNERS_ALIASES
+++ b/OWNERS_ALIASES
@@ -12,10 +12,3 @@ aliases:
   - liu-cong
   - robscott
   - shaneutt
-
-
-  wg-serving-leads:
-  - ArangoGutierrez
-  - Jeffwan
-  - SergeyKanzhelev
-  - terrytangyuan
diff --git a/README.md b/README.md
index 8212c73..51aaf28 100644
--- a/README.md
+++ b/README.md
@@ -60,7 +60,7 @@ For deeper insights and more advanced concepts, refer to our [proposals](/docs/p
 
 ## Technical Overview
 
-This extension upgrades an [ext-proc](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_proc_filter) capable proxy or gateway - such as Envoy Gateway, kGateway, or the GKE Gateway - to become an **[inference gateway]** - supporting inference platform teams self-hosting Generative Models (with a current focus on large language models) on Kubernetes. This integration makes it easy to expose and control access to your local [OpenAI-compatible chat completion endpoints](https://platform.openai.com/docs/api-reference/chat) to other workloads on or off cluster, or to integrate your self-hosted models alongside model-as-a-service providers in a higher level **AI Gateway** like LiteLLM, Solo AI Gateway, or Apigee.
+This extension upgrades an [ext-proc](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_proc_filter) capable proxy or gateway - such as Envoy Gateway, kgateway, or the GKE Gateway - to become an **[inference gateway]** - supporting inference platform teams self-hosting Generative Models (with a current focus on large language models) on Kubernetes. This integration makes it easy to expose and control access to your local [OpenAI-compatible chat completion endpoints](https://platform.openai.com/docs/api-reference/chat) to other workloads on or off cluster, or to integrate your self-hosted models alongside model-as-a-service providers in a higher level **AI Gateway** like LiteLLM, Solo AI Gateway, or Apigee.
 
 The Inference Gateway:
 
diff --git a/api/v1/inferencepool_types.go b/api/v1/inferencepool_types.go
index 98f45d5..21f4e77 100644
--- a/api/v1/inferencepool_types.go
+++ b/api/v1/inferencepool_types.go
@@ -24,23 +24,30 @@ import (
 //
 // +kubebuilder:object:root=true
 // TODO: change the annotation once it gets officially approved
-// +kubebuilder:metadata:annotations="api-approved.kubernetes.io=unapproved, experimental-only"
+// +kubebuilder:metadata:annotations="api-approved.kubernetes.io=https://github.com/kubernetes-sigs/gateway-api-inference-extension/pull/1173"
+// +kubebuilder:resource:shortName=infpool
 // +kubebuilder:subresource:status
 // +kubebuilder:storageversion
 // +genclient
 type InferencePool struct {
-	metav1.TypeMeta   `json:",inline"`
+	metav1.TypeMeta `json:",inline"`
+
+	// +optional
 	metav1.ObjectMeta `json:"metadata,omitempty"`
 
-	Spec InferencePoolSpec `json:"spec,omitempty"`
+	// Spec defines the desired state of the InferencePool.
+	//
+	// +required
+	Spec InferencePoolSpec `json:"spec,omitzero"`
 
-	// Status defines the observed state of InferencePool.
+	// Status defines the observed state of the InferencePool.
 	//
-	// +kubebuilder:default={parent: {{parentRef: {kind: "Status", name: "default"}, conditions: {{type: "Accepted", status: "Unknown", reason: "Pending", message: "Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}}}}}
+	// +optional
+	//nolint:kubeapilinter // status should not be a pointer.
 	Status InferencePoolStatus `json:"status,omitempty"`
 }
 
-// InferencePoolList contains a list of InferencePool.
+// InferencePoolList contains a list of InferencePools.
 //
 // +kubebuilder:object:root=true
 type InferencePoolList struct {
@@ -49,59 +56,49 @@ type InferencePoolList struct {
 	Items           []InferencePool `json:"items"`
 }
 
-// InferencePoolSpec defines the desired state of InferencePool
+// InferencePoolSpec defines the desired state of the InferencePool.
 type InferencePoolSpec struct {
-	// Selector defines a map of labels to watch model server Pods
-	// that should be included in the InferencePool.
-	// In some cases, implementations may translate this field to a Service selector, so this matches the simple
-	// map used for Service selectors instead of the full Kubernetes LabelSelector type.
-	// If specified, it will be applied to match the model server pods in the same namespace as the InferencePool.
-	// Cross namesoace selector is not supported.
+	// Selector determines which Pods are members of this inference pool.
+	// It matches Pods by their labels only within the same namespace; cross-namespace
+	// selection is not supported.
 	//
-	// +kubebuilder:validation:Required
-	Selector map[LabelKey]LabelValue `json:"selector"`
-
-	// TargetPortNumber defines the port number to access the selected model server Pods.
-	// The number must be in the range 1 to 65535.
+	// The structure of this LabelSelector is intentionally simple to be compatible
+	// with Kubernetes Service selectors, as some implementations may translate
+	// this configuration into a Service resource.
 	//
-	// +kubebuilder:validation:Minimum=1
-	// +kubebuilder:validation:Maximum=65535
-	// +kubebuilder:validation:Required
-	TargetPortNumber int32 `json:"targetPortNumber"`
+	// +required
+	Selector LabelSelector `json:"selector,omitzero"`
 
-	// EndpointPickerConfig specifies the configuration needed by the proxy to discover and connect to the endpoint
-	// picker service that picks endpoints for the requests routed to this pool.
-	EndpointPickerConfig `json:",inline"`
-}
+	// TargetPorts defines a list of ports that are exposed by this InferencePool.
+	// Currently, the list may only include a single port definition.
+	//
+	// +kubebuilder:validation:MinItems=1
+	// +kubebuilder:validation:MaxItems=1
+	// +listType=atomic
+	// +required
+	TargetPorts []Port `json:"targetPorts,omitempty"`
 
-// EndpointPickerConfig specifies the configuration needed by the proxy to discover and connect to the endpoint picker extension.
-// This type is intended to be a union of mutually exclusive configuration options that we may add in the future.
-type EndpointPickerConfig struct {
-	// Extension configures an endpoint picker as an extension service.
+	// EndpointPickerRef is a reference to the Endpoint Picker extension and its
+	// associated configuration.
 	//
-	// +kubebuilder:validation:Required
-	ExtensionRef *Extension `json:"extensionRef,omitempty"`
+	// +required
+	EndpointPickerRef EndpointPickerRef `json:"endpointPickerRef,omitzero"`
 }
 
-// Extension specifies how to configure an extension that runs the endpoint picker.
-type Extension struct {
-	// Reference is a reference to a service extension. When ExtensionReference is invalid,
-	// a 5XX status code MUST be returned for the request that would have otherwise been routed
-	// to the invalid backend.
-	ExtensionReference `json:",inline"`
-
-	// ExtensionConnection configures the connection between the Gateway and the extension.
-	ExtensionConnection `json:",inline"`
+// Port defines the network port that will be exposed by this InferencePool.
+type Port struct {
+	// Number defines the port number to access the selected model server Pods.
+	// The number must be in the range 1 to 65535.
+	//
+	// +required
+	Number PortNumber `json:"number,omitempty"`
 }
 
-// ExtensionReference is a reference to the extension.
-//
-// If a reference is invalid, the implementation MUST update the `ResolvedRefs`
-// Condition on the InferencePool's status to `status: False`. A 5XX status code MUST be returned
-// for the request that would have otherwise been routed to the invalid backend.
-type ExtensionReference struct {
-	// Group is the group of the referent.
-	// The default value is "", representing the Core API group.
+// EndpointPickerRef specifies a reference to an Endpoint Picker extension and its
+// associated configuration.
+type EndpointPickerRef struct {
+	// Group is the group of the referent API object. When unspecified, the default value
+	// is "", representing the Core API group.
 	//
 	// +optional
 	// +kubebuilder:default=""
@@ -109,6 +106,8 @@ type ExtensionReference struct {
 
 	// Kind is the Kubernetes resource kind of the referent.
 	//
+	// Required if the referent is ambiguous, e.g. service with multiple ports.
+	//
 	// Defaults to "Service" when not specified.
 	//
 	// ExternalName services can refer to CNAME DNS records that may live
@@ -119,69 +118,71 @@ type ExtensionReference struct {
 	//
 	// +optional
 	// +kubebuilder:default=Service
+	//nolint:kubeapilinter // ignore kubeapilinter here as we want to use pointer for optional struct.
 	Kind *Kind `json:"kind,omitempty"`
 
-	// Name is the name of the referent.
+	// Name is the name of the referent API object.
 	//
-	// +kubebuilder:validation:Required
-	Name ObjectName `json:"name"`
+	// +required
+	Name ObjectName `json:"name,omitempty"`
 
-	// The port number on the service running the extension. When unspecified,
-	// implementations SHOULD infer a default value of 9002 when the Kind is
-	// Service.
+	// PortNumber is the port number of the Endpoint Picker extension service. When unspecified,
+	// implementations SHOULD infer a default value of 9002 when the kind field is "Service" or
+	// unspecified (defaults to "Service").
 	//
 	// +optional
+	//nolint:kubeapilinter // ignore kubeapilinter here as we want to use pointer for optional struct.
 	PortNumber *PortNumber `json:"portNumber,omitempty"`
-}
 
-// ExtensionConnection encapsulates options that configures the connection to the extension.
-type ExtensionConnection struct {
-	// Configures how the gateway handles the case when the extension is not responsive.
-	// Defaults to failClose.
+	// FailureMode configures how the parent handles the case when the Endpoint Picker extension
+	// is non-responsive. When unspecified, defaults to "FailClose".
 	//
 	// +optional
 	// +kubebuilder:default="FailClose"
-	FailureMode *ExtensionFailureMode `json:"failureMode"`
+	//nolint:kubeapilinter // ignore kubeapilinter here as we want to use pointer for optional struct.
+	FailureMode *EndpointPickerFailureMode `json:"failureMode,omitempty"`
 }
 
-// ExtensionFailureMode defines the options for how the gateway handles the case when the extension is not
-// responsive.
+// EndpointPickerFailureMode defines the options for how the parent handles the case when the
+// Endpoint Picker extension is non-responsive.
+//
 // +kubebuilder:validation:Enum=FailOpen;FailClose
-type ExtensionFailureMode string
+type EndpointPickerFailureMode string
 
 const (
-	// FailOpen specifies that the proxy should forward the request to an endpoint of its picking when the Endpoint Picker fails.
-	FailOpen ExtensionFailureMode = "FailOpen"
-	// FailClose specifies that the proxy should drop the request when the Endpoint Picker fails.
-	FailClose ExtensionFailureMode = "FailClose"
+	// EndpointPickerFailOpen specifies that the parent should forward the request to an endpoint
+	// of its picking when the Endpoint Picker extension fails.
+	EndpointPickerFailOpen EndpointPickerFailureMode = "FailOpen"
+	// EndpointPickerFailClose specifies that the parent should drop the request when the Endpoint
+	// Picker extension fails.
+	EndpointPickerFailClose EndpointPickerFailureMode = "FailClose"
 )
 
-// InferencePoolStatus defines the observed state of InferencePool.
+// InferencePoolStatus defines the observed state of the InferencePool.
 type InferencePoolStatus struct {
-	// Parents is a list of parent resources (usually Gateways) that are
-	// associated with the InferencePool, and the status of the InferencePool with respect to
-	// each parent.
+	// Parents is a list of parent resources, typically Gateways, that are associated with
+	// the InferencePool, and the status of the InferencePool with respect to each parent.
 	//
-	// A maximum of 32 Gateways will be represented in this list. When the list contains
-	// `kind: Status, name: default`, it indicates that the InferencePool is not
-	// associated with any Gateway and a controller must perform the following:
+	// A controller that manages the InferencePool, must add an entry for each parent it manages
+	// and remove the parent entry when the controller no longer considers the InferencePool to
+	// be associated with that parent.
 	//
-	//  - Remove the parent when setting the "Accepted" condition.
-	//  - Add the parent when the controller will no longer manage the InferencePool
-	//    and no other parents exist.
+	// A maximum of 32 parents will be represented in this list. When the list is empty,
+	// it indicates that the InferencePool is not associated with any parents.
 	//
 	// +kubebuilder:validation:MaxItems=32
-	Parents []PoolStatus `json:"parent,omitempty"`
+	// +optional
+	// +listType=atomic
+	Parents []ParentStatus `json:"parents,omitempty"`
 }
 
-// PoolStatus defines the observed state of InferencePool from a Gateway.
-type PoolStatus struct {
-	// GatewayRef indicates the gateway that observed state of InferencePool.
-	GatewayRef ParentGatewayReference `json:"parentRef"`
-
-	// Conditions track the state of the InferencePool.
+// ParentStatus defines the observed state of InferencePool from a Parent, i.e. Gateway.
+type ParentStatus struct {
+	// Conditions is a list of status conditions that provide information about the observed
+	// state of the InferencePool. This field is required to be set by the controller that
+	// manages the InferencePool.
 	//
-	// Known condition types are:
+	// Supported condition types are:
 	//
 	// * "Accepted"
 	// * "ResolvedRefs"
@@ -190,27 +191,33 @@ type PoolStatus struct {
 	// +listType=map
 	// +listMapKey=type
 	// +kubebuilder:validation:MaxItems=8
-	// +kubebuilder:default={{type: "Accepted", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}}
 	Conditions []metav1.Condition `json:"conditions,omitempty"`
+
+	// ParentRef is used to identify the parent resource that this status
+	// is associated with. It is used to match the InferencePool with the parent
+	// resource, such as a Gateway.
+	//
+	// +required
+	ParentRef ParentReference `json:"parentRef,omitzero"`
 }
 
-// InferencePoolConditionType is a type of condition for the InferencePool
+// InferencePoolConditionType is a type of status condition for the InferencePool.
 type InferencePoolConditionType string
 
-// InferencePoolReason is the reason for a given InferencePoolConditionType
+// InferencePoolReason is the reason for a type of InferencePool status condition.
 type InferencePoolReason string
 
 const (
-	// This condition indicates whether the InferencePool has been accepted or rejected
-	// by a Gateway, and why.
+	// InferencePoolConditionAccepted is a type of condition that indicates whether
+	// the InferencePool has been accepted or rejected by a Parent, and why.
 	//
 	// Possible reasons for this condition to be True are:
 	//
-	// * "Accepted"
+	// * "SupportedByParent"
 	//
 	// Possible reasons for this condition to be False are:
 	//
-	// * "NotSupportedByGateway"
+	// * "Accepted"
 	// * "HTTPRouteNotAccepted"
 	//
 	// Possible reasons for this condition to be Unknown are:
@@ -221,28 +228,26 @@ const (
 	// prefer to use the reasons listed above to improve interoperability.
 	InferencePoolConditionAccepted InferencePoolConditionType = "Accepted"
 
-	// This reason is used with the "Accepted" condition when the InferencePool has been
-	// accepted by the Gateway.
+	// InferencePoolReasonAccepted is a reason used with the "Accepted" condition
+	// when the InferencePool is accepted by a Parent because the Parent supports
+	// InferencePool as a backend.
 	InferencePoolReasonAccepted InferencePoolReason = "Accepted"
 
-	// This reason is used with the "Accepted" condition when the InferencePool
-	// has not been accepted by a Gateway because the Gateway does not support
-	// InferencePool as a backend.
-	InferencePoolReasonNotSupportedByGateway InferencePoolReason = "NotSupportedByGateway"
+	// InferencePoolReasonNotSupportedByParent is a reason used with the "Accepted"
+	// condition when the InferencePool has not been accepted by a Parent because
+	// the Parent does not support InferencePool as a backend.
+	InferencePoolReasonNotSupportedByParent InferencePoolReason = "NotSupportedByParent"
 
-	// This reason is used with the "Accepted" condition when the InferencePool is
-	// referenced by an HTTPRoute that has been rejected by the Gateway. The user
-	// should inspect the status of the referring HTTPRoute for the specific reason.
+	// InferencePoolReasonHTTPRouteNotAccepted is an optional reason used with the
+	// "Accepted" condition when the InferencePool is referenced by an HTTPRoute that
+	// has been rejected by the Parent. The user should inspect the status of the
+	// referring HTTPRoute for the specific reason.
 	InferencePoolReasonHTTPRouteNotAccepted InferencePoolReason = "HTTPRouteNotAccepted"
-
-	// This reason is used with the "Accepted" when a controller has not yet
-	// reconciled the InferencePool.
-	InferencePoolReasonPending InferencePoolReason = "Pending"
 )
 
 const (
-	// This condition indicates whether the controller was able to resolve all
-	// the object references for the InferencePool.
+	// InferencePoolConditionResolvedRefs is a type of condition that indicates whether
+	// the controller was able to resolve all the object references for the InferencePool.
 	//
 	// Possible reasons for this condition to be True are:
 	//
@@ -256,38 +261,48 @@ const (
 	// prefer to use the reasons listed above to improve interoperability.
 	InferencePoolConditionResolvedRefs InferencePoolConditionType = "ResolvedRefs"
 
-	// This reason is used with the "ResolvedRefs" condition when the condition
-	// is true.
+	// InferencePoolReasonResolvedRefs is a reason used with the "ResolvedRefs"
+	// condition when the condition is true.
 	InferencePoolReasonResolvedRefs InferencePoolReason = "ResolvedRefs"
 
-	// This reason is used with the "ResolvedRefs" condition when the
-	// ExtensionRef is invalid in some way. This can include an unsupported kind
-	// or API group, or a reference to a resource that can not be found.
+	// InferencePoolReasonInvalidExtensionRef is a reason used with the "ResolvedRefs"
+	// condition when the Extension is invalid in some way. This can include an
+	// unsupported kind or API group, or a reference to a resource that cannot be found.
 	InferencePoolReasonInvalidExtensionRef InferencePoolReason = "InvalidExtensionRef"
 )
 
-// ParentGatewayReference identifies an API object including its namespace,
-// defaulting to Gateway.
-type ParentGatewayReference struct {
-	// Group is the group of the referent.
+// ParentReference identifies an API object. It is used to associate the InferencePool with a
+// parent resource, such as a Gateway.
+type ParentReference struct {
+	// Group is the group of the referent API object. When unspecified, the referent is assumed
+	// to be in the "gateway.networking.k8s.io" API group.
 	//
 	// +optional
 	// +kubebuilder:default="gateway.networking.k8s.io"
-	Group *Group `json:"group"`
+	Group *Group `json:"group,omitempty"`
 
-	// Kind is kind of the referent. For example "Gateway".
+	// Kind is the kind of the referent API object. When unspecified, the referent is assumed
+	// to be a "Gateway" kind.
 	//
 	// +optional
 	// +kubebuilder:default=Gateway
-	Kind *Kind `json:"kind"`
+	//nolint:kubeapilinter // ignore kubeapilinter here as we want to use pointer for optional struct.
+	Kind *Kind `json:"kind,omitempty"`
 
-	// Name is the name of the referent.
-	Name ObjectName `json:"name"`
+	// Name is the name of the referent API object.
+	//
+	// +required
+	Name ObjectName `json:"name,omitempty"`
 
-	// Namespace is the namespace of the referent.  If not present,
-	// the namespace of the referent is assumed to be the same as
-	// the namespace of the referring object.
+	// Namespace is the namespace of the referenced object. When unspecified, the local
+	// namespace is inferred.
+	//
+	// Note that when a namespace different than the local namespace is specified,
+	// a ReferenceGrant object is required in the referent namespace to allow that
+	// namespace's owner to accept the reference. See the ReferenceGrant
+	// documentation for details: https://gateway-api.sigs.k8s.io/api-types/referencegrant/
 	//
 	// +optional
+	//nolint:kubeapilinter // ignore kubeapilinter here as we want to use pointer for optional struct.
 	Namespace *Namespace `json:"namespace,omitempty"`
 }
diff --git a/api/v1/shared_types.go b/api/v1/shared_types.go
index 800291b..bc315fd 100644
--- a/api/v1/shared_types.go
+++ b/api/v1/shared_types.go
@@ -32,6 +32,7 @@ package v1
 //
 // * "example.com/bar" - "/" is an invalid character
 //
+// +kubebuilder:validation:MinLength=0
 // +kubebuilder:validation:MaxLength=253
 // +kubebuilder:validation:Pattern=`^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
 type Group string
@@ -127,3 +128,16 @@ type LabelKey string
 // +kubebuilder:validation:MaxLength=63
 // +kubebuilder:validation:Pattern=`^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$`
 type LabelValue string
+
+// LabelSelector defines a query for resources based on their labels.
+// This simplified version uses only the matchLabels field.
+type LabelSelector struct {
+	// MatchLabels contains a set of required {key,value} pairs.
+	// An object must match every label in this map to be selected.
+	// The matching logic is an AND operation on all entries.
+	//
+	// +required
+	// +kubebuilder:validation:MinItems=1
+	// +kubebuilder:validation:MaxItems=64
+	MatchLabels map[LabelKey]LabelValue `json:"matchLabels,omitempty"`
+}
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
index adf19ac..791bc96 100644
--- a/api/v1/zz_generated.deepcopy.go
+++ b/api/v1/zz_generated.deepcopy.go
@@ -26,64 +26,7 @@ import (
 )
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *EndpointPickerConfig) DeepCopyInto(out *EndpointPickerConfig) {
-	*out = *in
-	if in.ExtensionRef != nil {
-		in, out := &in.ExtensionRef, &out.ExtensionRef
-		*out = new(Extension)
-		(*in).DeepCopyInto(*out)
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointPickerConfig.
-func (in *EndpointPickerConfig) DeepCopy() *EndpointPickerConfig {
-	if in == nil {
-		return nil
-	}
-	out := new(EndpointPickerConfig)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *Extension) DeepCopyInto(out *Extension) {
-	*out = *in
-	in.ExtensionReference.DeepCopyInto(&out.ExtensionReference)
-	in.ExtensionConnection.DeepCopyInto(&out.ExtensionConnection)
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Extension.
-func (in *Extension) DeepCopy() *Extension {
-	if in == nil {
-		return nil
-	}
-	out := new(Extension)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ExtensionConnection) DeepCopyInto(out *ExtensionConnection) {
-	*out = *in
-	if in.FailureMode != nil {
-		in, out := &in.FailureMode, &out.FailureMode
-		*out = new(ExtensionFailureMode)
-		**out = **in
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionConnection.
-func (in *ExtensionConnection) DeepCopy() *ExtensionConnection {
-	if in == nil {
-		return nil
-	}
-	out := new(ExtensionConnection)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ExtensionReference) DeepCopyInto(out *ExtensionReference) {
+func (in *EndpointPickerRef) DeepCopyInto(out *EndpointPickerRef) {
 	*out = *in
 	if in.Group != nil {
 		in, out := &in.Group, &out.Group
@@ -100,14 +43,19 @@ func (in *ExtensionReference) DeepCopyInto(out *ExtensionReference) {
 		*out = new(PortNumber)
 		**out = **in
 	}
+	if in.FailureMode != nil {
+		in, out := &in.FailureMode, &out.FailureMode
+		*out = new(EndpointPickerFailureMode)
+		**out = **in
+	}
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionReference.
-func (in *ExtensionReference) DeepCopy() *ExtensionReference {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointPickerRef.
+func (in *EndpointPickerRef) DeepCopy() *EndpointPickerRef {
 	if in == nil {
 		return nil
 	}
-	out := new(ExtensionReference)
+	out := new(EndpointPickerRef)
 	in.DeepCopyInto(out)
 	return out
 }
@@ -174,14 +122,13 @@ func (in *InferencePoolList) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *InferencePoolSpec) DeepCopyInto(out *InferencePoolSpec) {
 	*out = *in
-	if in.Selector != nil {
-		in, out := &in.Selector, &out.Selector
-		*out = make(map[LabelKey]LabelValue, len(*in))
-		for key, val := range *in {
-			(*out)[key] = val
-		}
+	in.Selector.DeepCopyInto(&out.Selector)
+	if in.TargetPorts != nil {
+		in, out := &in.TargetPorts, &out.TargetPorts
+		*out = make([]Port, len(*in))
+		copy(*out, *in)
 	}
-	in.EndpointPickerConfig.DeepCopyInto(&out.EndpointPickerConfig)
+	in.EndpointPickerRef.DeepCopyInto(&out.EndpointPickerRef)
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InferencePoolSpec.
@@ -199,7 +146,7 @@ func (in *InferencePoolStatus) DeepCopyInto(out *InferencePoolStatus) {
 	*out = *in
 	if in.Parents != nil {
 		in, out := &in.Parents, &out.Parents
-		*out = make([]PoolStatus, len(*in))
+		*out = make([]ParentStatus, len(*in))
 		for i := range *in {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
@@ -217,7 +164,29 @@ func (in *InferencePoolStatus) DeepCopy() *InferencePoolStatus {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ParentGatewayReference) DeepCopyInto(out *ParentGatewayReference) {
+func (in *LabelSelector) DeepCopyInto(out *LabelSelector) {
+	*out = *in
+	if in.MatchLabels != nil {
+		in, out := &in.MatchLabels, &out.MatchLabels
+		*out = make(map[LabelKey]LabelValue, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelSelector.
+func (in *LabelSelector) DeepCopy() *LabelSelector {
+	if in == nil {
+		return nil
+	}
+	out := new(LabelSelector)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ParentReference) DeepCopyInto(out *ParentReference) {
 	*out = *in
 	if in.Group != nil {
 		in, out := &in.Group, &out.Group
@@ -236,20 +205,19 @@ func (in *ParentGatewayReference) DeepCopyInto(out *ParentGatewayReference) {
 	}
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParentGatewayReference.
-func (in *ParentGatewayReference) DeepCopy() *ParentGatewayReference {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParentReference.
+func (in *ParentReference) DeepCopy() *ParentReference {
 	if in == nil {
 		return nil
 	}
-	out := new(ParentGatewayReference)
+	out := new(ParentReference)
 	in.DeepCopyInto(out)
 	return out
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *PoolStatus) DeepCopyInto(out *PoolStatus) {
+func (in *ParentStatus) DeepCopyInto(out *ParentStatus) {
 	*out = *in
-	in.GatewayRef.DeepCopyInto(&out.GatewayRef)
 	if in.Conditions != nil {
 		in, out := &in.Conditions, &out.Conditions
 		*out = make([]metav1.Condition, len(*in))
@@ -257,14 +225,30 @@ func (in *PoolStatus) DeepCopyInto(out *PoolStatus) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	in.ParentRef.DeepCopyInto(&out.ParentRef)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParentStatus.
+func (in *ParentStatus) DeepCopy() *ParentStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(ParentStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Port) DeepCopyInto(out *Port) {
+	*out = *in
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PoolStatus.
-func (in *PoolStatus) DeepCopy() *PoolStatus {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Port.
+func (in *Port) DeepCopy() *Port {
 	if in == nil {
 		return nil
 	}
-	out := new(PoolStatus)
+	out := new(Port)
 	in.DeepCopyInto(out)
 	return out
 }
diff --git a/apix/v1alpha2/inferenceobjective_types.go b/apix/v1alpha2/inferenceobjective_types.go
index 4af3e27..53869d9 100644
--- a/apix/v1alpha2/inferenceobjective_types.go
+++ b/apix/v1alpha2/inferenceobjective_types.go
@@ -27,7 +27,7 @@ import (
 // +kubebuilder:storageversion
 // +kubebuilder:printcolumn:name="Model Name",type=string,JSONPath=`.spec.modelName`
 // +kubebuilder:printcolumn:name="Inference Pool",type=string,JSONPath=`.spec.poolRef.name`
-// +kubebuilder:printcolumn:name="Criticality",type=string,JSONPath=`.spec.criticality`
+// +kubebuilder:printcolumn:name="Priority",type=string,JSONPath=`.spec.priority`
 // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
 // +genclient
 type InferenceObjective struct {
@@ -64,25 +64,20 @@ type InferenceObjectiveList struct {
 // condition, one will be selected at random.
 type InferenceObjectiveSpec struct {
 
-	// Criticality defines how important it is to serve the model compared to other models referencing the same pool.
-	// Criticality impacts how traffic is handled in resource constrained situations. It handles this by
-	// queuing or rejecting requests of lower criticality. InferenceObjectives of an equivalent Criticality will
-	// fairly share resources over throughput of tokens. In the future, the metric used to calculate fairness,
-	// and the proportionality of fairness will be configurable.
-	//
-	// Default values for this field will not be set, to allow for future additions of new field that may 'one of' with this field.
-	// Any implementations that may consume this field may treat an unset value as the 'Standard' range.
+	// Priority defines how important it is to serve the request compared to other requests in the same pool.
+	// Priority is an integer value that defines the priority of the request.
+	// The higher the value, the more critical the request is; negative values _are_ allowed.
+	// No default value is set for this field, allowing for future additions of new fields that may 'one of' with this field.
+	// However, implementations that consume this field (such as the Endpoint Picker) will treat an unset value as '0'.
+	// Priority is used in flow control, primarily in the event of resource scarcity(requests need to be queued).
+	// All requests will be queued, and flow control will _always_ allow requests of higher priority to be served first.
+	// Fairness is only enforced and tracked between requests of the same priority.
+	//
+	// Example: requests with Priority 10 will always be served before
+	// requests with Priority of 0 (the value used if Priority is unset or no InfereneceObjective is specified).
+	// Similarly requests with a Priority of -10 will always be served after requests with Priority of 0.
 	// +optional
-	Criticality *Criticality `json:"criticality,omitempty"`
-
-	// TargetModels allow multiple versions of a model for traffic splitting.
-	// If not specified, the target model name is defaulted to the modelName parameter.
-	// modelName is often in reference to a LoRA adapter.
-	//
-	// +optional
-	// +kubebuilder:validation:MaxItems=10
-	// +kubebuilder:validation:XValidation:message="Weights should be set for all models, or none of the models.",rule="self.all(model, has(model.weight)) || self.all(model, !has(model.weight))"
-	TargetModels []TargetModel `json:"targetModels,omitempty"`
+	Priority *int `json:"priority,omitempty"`
 
 	// PoolRef is a reference to the inference pool, the pool must exist in the same namespace.
 	//
@@ -96,7 +91,7 @@ type PoolObjectReference struct {
 	// Group is the group of the referent.
 	//
 	// +optional
-	// +kubebuilder:default="inference.networking.x-k8s.io"
+	// +kubebuilder:default="inference.networking.k8s.io"
 	Group Group `json:"group,omitempty"`
 
 	// Kind is kind of the referent. For example "InferencePool".
@@ -111,59 +106,6 @@ type PoolObjectReference struct {
 	Name ObjectName `json:"name"`
 }
 
-// Criticality defines how important it is to serve the model compared to other models.
-// Criticality is intentionally a bounded enum to contain the possibilities that need to be supported by the load balancing algorithm. Any reference to the Criticality field must be optional (use a pointer), and set no default.
-// This allows us to union this with a oneOf field in the future should we wish to adjust/extend this behavior.
-// +kubebuilder:validation:Enum=Critical;Standard;Sheddable
-type Criticality string
-
-const (
-	// Critical defines the highest level of criticality. Requests to this band will be shed last.
-	Critical Criticality = "Critical"
-
-	// Standard defines the base criticality level and is more important than Sheddable but less
-	// important than Critical. Requests in this band will be shed before critical traffic.
-	// Most models are expected to fall within this band.
-	Standard Criticality = "Standard"
-
-	// Sheddable defines the lowest level of criticality. Requests to this band will be shed before
-	// all other bands.
-	Sheddable Criticality = "Sheddable"
-)
-
-// TargetModel represents a deployed model or a LoRA adapter. The
-// Name field is expected to match the name of the LoRA adapter
-// (or base model) as it is registered within the model server. Inference
-// Gateway assumes that the model exists on the model server and it's the
-// responsibility of the user to validate a correct match. Should a model fail
-// to exist at request time, the error is processed by the Inference Gateway
-// and emitted on the appropriate InferenceObjective object.
-type TargetModel struct {
-	// Name is the name of the adapter or base model, as expected by the ModelServer.
-	//
-	// +kubebuilder:validation:MaxLength=253
-	// +kubebuilder:validation:Required
-	Name string `json:"name"`
-
-	// Weight is used to determine the proportion of traffic that should be
-	// sent to this model when multiple target models are specified.
-	//
-	// Weight defines the proportion of requests forwarded to the specified
-	// model. This is computed as weight/(sum of all weights in this
-	// TargetModels list). For non-zero values, there may be some epsilon from
-	// the exact proportion defined here depending on the precision an
-	// implementation supports. Weight is not a percentage and the sum of
-	// weights does not need to equal 100.
-	//
-	// If a weight is set for any targetModel, it must be set for all targetModels.
-	// Conversely weights are optional, so long as ALL targetModels do not specify a weight.
-	//
-	// +optional
-	// +kubebuilder:validation:Minimum=1
-	// +kubebuilder:validation:Maximum=1000000
-	Weight *int32 `json:"weight,omitempty"`
-}
-
 // InferenceObjectiveStatus defines the observed state of InferenceObjective
 type InferenceObjectiveStatus struct {
 	// Conditions track the state of the InferenceObjective.
diff --git a/apix/v1alpha2/inferencepool_conversion.go b/apix/v1alpha2/inferencepool_conversion.go
new file mode 100644
index 0000000..c470e62
--- /dev/null
+++ b/apix/v1alpha2/inferencepool_conversion.go
@@ -0,0 +1,231 @@
+/*
+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.
+*/
+
+package v1alpha2
+
+import (
+	"errors"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/ptr"
+
+	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
+)
+
+// ConvertTo converts this InferencePool (v1alpha2) to the v1 version.
+func (src *InferencePool) ConvertTo(dst *v1.InferencePool) error {
+	if dst == nil {
+		return errors.New("dst cannot be nil")
+	}
+	endpointPickRef, err := convertExtensionRefToV1(&src.Spec.ExtensionRef)
+	if err != nil {
+		return err
+	}
+	v1Status, err := convertStatusToV1(&src.Status)
+	if err != nil {
+		return err
+	}
+
+	meta := metav1.TypeMeta{
+		Kind:       src.Kind,
+		APIVersion: v1.GroupVersion.String(), // Ensure the API version is set correctly.
+	}
+	dst.TypeMeta = meta
+	dst.ObjectMeta = src.ObjectMeta
+	dst.Spec.TargetPorts = []v1.Port{{Number: v1.PortNumber(src.Spec.TargetPortNumber)}}
+	dst.Spec.EndpointPickerRef = endpointPickRef
+	dst.Status = *v1Status
+
+	if src.Spec.Selector != nil {
+		dst.Spec.Selector.MatchLabels = make(map[v1.LabelKey]v1.LabelValue, len(src.Spec.Selector))
+		for k, v := range src.Spec.Selector {
+			dst.Spec.Selector.MatchLabels[v1.LabelKey(k)] = v1.LabelValue(v)
+		}
+	}
+	return nil
+}
+
+// ConvertFrom converts from the v1 version to this version (v1alpha2).
+func (dst *InferencePool) ConvertFrom(src *v1.InferencePool) error {
+	if src == nil {
+		return errors.New("src cannot be nil")
+	}
+	extensionRef, err := convertEndpointPickerRefFromV1(&src.Spec.EndpointPickerRef)
+	if err != nil {
+		return err
+	}
+	status, err := convertStatusFromV1(&src.Status)
+	if err != nil {
+		return err
+	}
+
+	meta := metav1.TypeMeta{
+		Kind:       src.Kind,
+		APIVersion: GroupVersion.String(), // Ensure the API version is set correctly.
+	}
+	dst.TypeMeta = meta
+	dst.ObjectMeta = src.ObjectMeta
+	dst.Spec.TargetPortNumber = int32(src.Spec.TargetPorts[0].Number)
+	dst.Spec.ExtensionRef = extensionRef
+	dst.Status = *status
+
+	if src.Spec.Selector.MatchLabels != nil {
+		dst.Spec.Selector = make(map[LabelKey]LabelValue, len(src.Spec.Selector.MatchLabels))
+		for k, v := range src.Spec.Selector.MatchLabels {
+			dst.Spec.Selector[LabelKey(k)] = LabelValue(v)
+		}
+	}
+	return nil
+}
+
+func convertStatusToV1(src *InferencePoolStatus) (*v1.InferencePoolStatus, error) {
+	if src == nil {
+		return nil, errors.New("src cannot be nil")
+	}
+	if src.Parents == nil {
+		return &v1.InferencePoolStatus{}, nil
+	}
+	out := &v1.InferencePoolStatus{
+		Parents: make([]v1.ParentStatus, 0, len(src.Parents)),
+	}
+	for _, p := range src.Parents {
+		ps := v1.ParentStatus{
+			ParentRef:  toV1ParentRef(p.GatewayRef),
+			Conditions: make([]metav1.Condition, 0, len(p.Conditions)),
+		}
+		for _, c := range p.Conditions {
+			cc := c
+			// v1alpha2: "Accepted" -> v1: "SupportedByParent"
+			if cc.Type == string(v1.InferencePoolConditionAccepted) &&
+				cc.Reason == string(InferencePoolReasonAccepted) {
+				cc.Reason = string(v1.InferencePoolReasonAccepted)
+			}
+			ps.Conditions = append(ps.Conditions, cc)
+		}
+		out.Parents = append(out.Parents, ps)
+	}
+	return out, nil
+}
+
+func convertStatusFromV1(src *v1.InferencePoolStatus) (*InferencePoolStatus, error) {
+	if src == nil {
+		return nil, errors.New("src cannot be nil")
+	}
+	if src.Parents == nil {
+		return &InferencePoolStatus{}, nil
+	}
+	out := &InferencePoolStatus{
+		Parents: make([]PoolStatus, 0, len(src.Parents)),
+	}
+	for _, p := range src.Parents {
+		ps := PoolStatus{
+			GatewayRef: fromV1ParentRef(p.ParentRef),
+			Conditions: make([]metav1.Condition, 0, len(p.Conditions)),
+		}
+		for _, c := range p.Conditions {
+			cc := c
+			// v1: "SupportedByParent" -> v1alpha2: "Accepted"
+			if cc.Type == string(v1.InferencePoolConditionAccepted) &&
+				cc.Reason == string(v1.InferencePoolReasonAccepted) {
+				cc.Reason = string(InferencePoolReasonAccepted)
+			}
+			ps.Conditions = append(ps.Conditions, cc)
+		}
+		out.Parents = append(out.Parents, ps)
+	}
+	return out, nil
+}
+
+func toV1ParentRef(in ParentGatewayReference) v1.ParentReference {
+	out := v1.ParentReference{
+		Name: v1.ObjectName(in.Name),
+	}
+	if in.Group != nil {
+		g := v1.Group(*in.Group)
+		out.Group = &g
+	}
+	if in.Kind != nil {
+		k := v1.Kind(*in.Kind)
+		out.Kind = &k
+	}
+	if in.Namespace != nil {
+		ns := v1.Namespace(*in.Namespace)
+		out.Namespace = &ns
+	}
+	return out
+}
+
+func fromV1ParentRef(in v1.ParentReference) ParentGatewayReference {
+	out := ParentGatewayReference{
+		Name: ObjectName(in.Name),
+	}
+	if in.Group != nil {
+		g := Group(*in.Group)
+		out.Group = &g
+	}
+	if in.Kind != nil {
+		k := Kind(*in.Kind)
+		out.Kind = &k
+	}
+	if in.Namespace != nil {
+		ns := Namespace(*in.Namespace)
+		out.Namespace = &ns
+	}
+	return out
+}
+
+func convertExtensionRefToV1(src *Extension) (v1.EndpointPickerRef, error) {
+	endpointPickerRef := v1.EndpointPickerRef{}
+	if src == nil {
+		return endpointPickerRef, errors.New("src cannot be nil")
+	}
+	if src.Group != nil {
+		endpointPickerRef.Group = ptr.To(v1.Group(*src.Group))
+	}
+	if src.Kind != nil {
+		endpointPickerRef.Kind = ptr.To(v1.Kind(*src.Kind))
+	}
+	endpointPickerRef.Name = v1.ObjectName(src.Name)
+	if src.PortNumber != nil {
+		endpointPickerRef.PortNumber = ptr.To(v1.PortNumber(*src.PortNumber))
+	}
+	if src.FailureMode != nil {
+		endpointPickerRef.FailureMode = ptr.To(v1.EndpointPickerFailureMode(*src.FailureMode))
+	}
+
+	return endpointPickerRef, nil
+}
+
+func convertEndpointPickerRefFromV1(src *v1.EndpointPickerRef) (Extension, error) {
+	extension := Extension{}
+	if src == nil {
+		return extension, errors.New("src cannot be nil")
+	}
+	if src.Group != nil {
+		extension.Group = ptr.To(Group(*src.Group))
+	}
+	if src.Kind != nil {
+		extension.Kind = ptr.To(Kind(*src.Kind))
+	}
+	extension.Name = ObjectName(src.Name)
+	if src.PortNumber != nil {
+		extension.PortNumber = ptr.To(PortNumber(*src.PortNumber))
+	}
+	if src.FailureMode != nil {
+		extension.FailureMode = ptr.To(ExtensionFailureMode(*src.FailureMode))
+	}
+	return extension, nil
+}
diff --git a/apix/v1alpha2/inferencepool_types.go b/apix/v1alpha2/inferencepool_types.go
index 0213d86..7ccf215 100644
--- a/apix/v1alpha2/inferencepool_types.go
+++ b/apix/v1alpha2/inferencepool_types.go
@@ -21,10 +21,9 @@ import (
 )
 
 // InferencePool is the Schema for the InferencePools API.
-//
 // +kubebuilder:object:root=true
-// TODO: change the annotation once it gets officially approved
 // +kubebuilder:metadata:annotations="api-approved.kubernetes.io=unapproved, experimental-only"
+// +kubebuilder:resource:shortName=xinfpool
 // +kubebuilder:subresource:status
 // +kubebuilder:storageversion
 // +genclient
@@ -69,42 +68,13 @@ type InferencePoolSpec struct {
 	// +kubebuilder:validation:Required
 	TargetPortNumber int32 `json:"targetPortNumber"`
 
-	// EndpointPickerConfig specifies the configuration needed by the proxy to discover and connect to the endpoint
-	// picker service that picks endpoints for the requests routed to this pool.
-	EndpointPickerConfig `json:",inline"`
-}
-
-// EndpointPickerConfig specifies the configuration needed by the proxy to discover and connect to the endpoint picker extension.
-// This type is intended to be a union of mutually exclusive configuration options that we may add in the future.
-type EndpointPickerConfig struct {
 	// Extension configures an endpoint picker as an extension service.
-	//
-	// +kubebuilder:validation:Required
-	ExtensionRef *Extension `json:"extensionRef,omitempty"`
+	// +required
+	ExtensionRef Extension `json:"extensionRef,omitempty"`
 }
 
 // Extension specifies how to configure an extension that runs the endpoint picker.
 type Extension struct {
-	// Reference is a reference to a service extension. When ExtensionReference is invalid,
-	// a 5XX status code MUST be returned for the request that would have otherwise been routed
-	// to the invalid backend.
-	ExtensionReference `json:",inline"`
-
-	// ExtensionConnection configures the connection between the Gateway and the extension.
-	ExtensionConnection `json:",inline"`
-}
-
-// ExtensionReference is a reference to the extension.
-//
-// Connections to this extension MUST use TLS by default. Implementations MAY
-// provide a way to customize this connection to use cleartext, a different
-// protocol, or custom TLS configuration.
-//
-// If a reference is invalid, the implementation MUST update the `ResolvedRefs`
-// Condition on the InferencePool's status to `status: False`. A 5XX status code
-// MUST be returned for the request that would have otherwise been routed to the
-// invalid backend.
-type ExtensionReference struct {
 	// Group is the group of the referent.
 	// The default value is "", representing the Core API group.
 	//
@@ -137,10 +107,7 @@ type ExtensionReference struct {
 	//
 	// +optional
 	PortNumber *PortNumber `json:"portNumber,omitempty"`
-}
 
-// ExtensionConnection encapsulates options that configures the connection to the extension.
-type ExtensionConnection struct {
 	// Configures how the gateway handles the case when the extension is not responsive.
 	// Defaults to failClose.
 	//
@@ -266,7 +233,7 @@ const (
 	InferencePoolReasonResolvedRefs InferencePoolReason = "ResolvedRefs"
 
 	// This reason is used with the "ResolvedRefs" condition when the
-	// ExtensionRef is invalid in some way. This can include an unsupported kind
+	// Extension is invalid in some way. This can include an unsupported kind
 	// or API group, or a reference to a resource that can not be found.
 	InferencePoolReasonInvalidExtensionRef InferencePoolReason = "InvalidExtensionRef"
 )
diff --git a/apix/v1alpha2/zz_generated.deepcopy.go b/apix/v1alpha2/zz_generated.deepcopy.go
index 697c443..0249d44 100644
--- a/apix/v1alpha2/zz_generated.deepcopy.go
+++ b/apix/v1alpha2/zz_generated.deepcopy.go
@@ -25,65 +25,8 @@ import (
 	runtime "k8s.io/apimachinery/pkg/runtime"
 )
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *EndpointPickerConfig) DeepCopyInto(out *EndpointPickerConfig) {
-	*out = *in
-	if in.ExtensionRef != nil {
-		in, out := &in.ExtensionRef, &out.ExtensionRef
-		*out = new(Extension)
-		(*in).DeepCopyInto(*out)
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointPickerConfig.
-func (in *EndpointPickerConfig) DeepCopy() *EndpointPickerConfig {
-	if in == nil {
-		return nil
-	}
-	out := new(EndpointPickerConfig)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Extension) DeepCopyInto(out *Extension) {
-	*out = *in
-	in.ExtensionReference.DeepCopyInto(&out.ExtensionReference)
-	in.ExtensionConnection.DeepCopyInto(&out.ExtensionConnection)
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Extension.
-func (in *Extension) DeepCopy() *Extension {
-	if in == nil {
-		return nil
-	}
-	out := new(Extension)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ExtensionConnection) DeepCopyInto(out *ExtensionConnection) {
-	*out = *in
-	if in.FailureMode != nil {
-		in, out := &in.FailureMode, &out.FailureMode
-		*out = new(ExtensionFailureMode)
-		**out = **in
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionConnection.
-func (in *ExtensionConnection) DeepCopy() *ExtensionConnection {
-	if in == nil {
-		return nil
-	}
-	out := new(ExtensionConnection)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ExtensionReference) DeepCopyInto(out *ExtensionReference) {
 	*out = *in
 	if in.Group != nil {
 		in, out := &in.Group, &out.Group
@@ -100,14 +43,19 @@ func (in *ExtensionReference) DeepCopyInto(out *ExtensionReference) {
 		*out = new(PortNumber)
 		**out = **in
 	}
+	if in.FailureMode != nil {
+		in, out := &in.FailureMode, &out.FailureMode
+		*out = new(ExtensionFailureMode)
+		**out = **in
+	}
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionReference.
-func (in *ExtensionReference) DeepCopy() *ExtensionReference {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Extension.
+func (in *Extension) DeepCopy() *Extension {
 	if in == nil {
 		return nil
 	}
-	out := new(ExtensionReference)
+	out := new(Extension)
 	in.DeepCopyInto(out)
 	return out
 }
@@ -174,18 +122,11 @@ func (in *InferenceObjectiveList) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *InferenceObjectiveSpec) DeepCopyInto(out *InferenceObjectiveSpec) {
 	*out = *in
-	if in.Criticality != nil {
-		in, out := &in.Criticality, &out.Criticality
-		*out = new(Criticality)
+	if in.Priority != nil {
+		in, out := &in.Priority, &out.Priority
+		*out = new(int)
 		**out = **in
 	}
-	if in.TargetModels != nil {
-		in, out := &in.TargetModels, &out.TargetModels
-		*out = make([]TargetModel, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
 	out.PoolRef = in.PoolRef
 }
 
@@ -290,7 +231,7 @@ func (in *InferencePoolSpec) DeepCopyInto(out *InferencePoolSpec) {
 			(*out)[key] = val
 		}
 	}
-	in.EndpointPickerConfig.DeepCopyInto(&out.EndpointPickerConfig)
+	in.ExtensionRef.DeepCopyInto(&out.ExtensionRef)
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InferencePoolSpec.
@@ -392,23 +333,3 @@ func (in *PoolStatus) DeepCopy() *PoolStatus {
 	in.DeepCopyInto(out)
 	return out
 }
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *TargetModel) DeepCopyInto(out *TargetModel) {
-	*out = *in
-	if in.Weight != nil {
-		in, out := &in.Weight, &out.Weight
-		*out = new(int32)
-		**out = **in
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetModel.
-func (in *TargetModel) DeepCopy() *TargetModel {
-	if in == nil {
-		return nil
-	}
-	out := new(TargetModel)
-	in.DeepCopyInto(out)
-	return out
-}
diff --git a/bbr.Dockerfile b/bbr.Dockerfile
index 8f21eea..36ae378 100644
--- a/bbr.Dockerfile
+++ b/bbr.Dockerfile
@@ -1,6 +1,6 @@
 # Dockerfile has specific requirement to put this ARG at the beginning:
 # https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact
-ARG BUILDER_IMAGE=golang:1.23
+ARG BUILDER_IMAGE=golang:1.24
 ARG BASE_IMAGE=gcr.io/distroless/static:nonroot
 
 ## Multistage build
diff --git a/client-go/applyconfiguration/api/v1/endpointpickerconfig.go b/client-go/applyconfiguration/api/v1/endpointpickerconfig.go
deleted file mode 100644
index 9877d40..0000000
--- a/client-go/applyconfiguration/api/v1/endpointpickerconfig.go
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
-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.
-*/
-
-// Code generated by applyconfiguration-gen. DO NOT EDIT.
-
-package v1
-
-// EndpointPickerConfigApplyConfiguration represents a declarative configuration of the EndpointPickerConfig type for use
-// with apply.
-type EndpointPickerConfigApplyConfiguration struct {
-	ExtensionRef *ExtensionApplyConfiguration `json:"extensionRef,omitempty"`
-}
-
-// EndpointPickerConfigApplyConfiguration constructs a declarative configuration of the EndpointPickerConfig type for use with
-// apply.
-func EndpointPickerConfig() *EndpointPickerConfigApplyConfiguration {
-	return &EndpointPickerConfigApplyConfiguration{}
-}
-
-// WithExtensionRef sets the ExtensionRef field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the ExtensionRef field is set to the value of the last call.
-func (b *EndpointPickerConfigApplyConfiguration) WithExtensionRef(value *ExtensionApplyConfiguration) *EndpointPickerConfigApplyConfiguration {
-	b.ExtensionRef = value
-	return b
-}
diff --git a/client-go/applyconfiguration/api/v1/extension.go b/client-go/applyconfiguration/api/v1/endpointpickerref.go
similarity index 59%
rename from client-go/applyconfiguration/api/v1/extension.go
rename to client-go/applyconfiguration/api/v1/endpointpickerref.go
index b14ec83..8a9991b 100644
--- a/client-go/applyconfiguration/api/v1/extension.go
+++ b/client-go/applyconfiguration/api/v1/endpointpickerref.go
@@ -22,55 +22,58 @@ import (
 	apiv1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 )
 
-// ExtensionApplyConfiguration represents a declarative configuration of the Extension type for use
+// EndpointPickerRefApplyConfiguration represents a declarative configuration of the EndpointPickerRef type for use
 // with apply.
-type ExtensionApplyConfiguration struct {
-	ExtensionReferenceApplyConfiguration  `json:",inline"`
-	ExtensionConnectionApplyConfiguration `json:",inline"`
+type EndpointPickerRefApplyConfiguration struct {
+	Group       *apiv1.Group                     `json:"group,omitempty"`
+	Kind        *apiv1.Kind                      `json:"kind,omitempty"`
+	Name        *apiv1.ObjectName                `json:"name,omitempty"`
+	PortNumber  *apiv1.PortNumber                `json:"portNumber,omitempty"`
+	FailureMode *apiv1.EndpointPickerFailureMode `json:"failureMode,omitempty"`
 }
 
-// ExtensionApplyConfiguration constructs a declarative configuration of the Extension type for use with
+// EndpointPickerRefApplyConfiguration constructs a declarative configuration of the EndpointPickerRef type for use with
 // apply.
-func Extension() *ExtensionApplyConfiguration {
-	return &ExtensionApplyConfiguration{}
+func EndpointPickerRef() *EndpointPickerRefApplyConfiguration {
+	return &EndpointPickerRefApplyConfiguration{}
 }
 
 // WithGroup sets the Group field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the Group field is set to the value of the last call.
-func (b *ExtensionApplyConfiguration) WithGroup(value apiv1.Group) *ExtensionApplyConfiguration {
-	b.ExtensionReferenceApplyConfiguration.Group = &value
+func (b *EndpointPickerRefApplyConfiguration) WithGroup(value apiv1.Group) *EndpointPickerRefApplyConfiguration {
+	b.Group = &value
 	return b
 }
 
 // WithKind sets the Kind field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the Kind field is set to the value of the last call.
-func (b *ExtensionApplyConfiguration) WithKind(value apiv1.Kind) *ExtensionApplyConfiguration {
-	b.ExtensionReferenceApplyConfiguration.Kind = &value
+func (b *EndpointPickerRefApplyConfiguration) WithKind(value apiv1.Kind) *EndpointPickerRefApplyConfiguration {
+	b.Kind = &value
 	return b
 }
 
 // WithName sets the Name field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the Name field is set to the value of the last call.
-func (b *ExtensionApplyConfiguration) WithName(value apiv1.ObjectName) *ExtensionApplyConfiguration {
-	b.ExtensionReferenceApplyConfiguration.Name = &value
+func (b *EndpointPickerRefApplyConfiguration) WithName(value apiv1.ObjectName) *EndpointPickerRefApplyConfiguration {
+	b.Name = &value
 	return b
 }
 
 // WithPortNumber sets the PortNumber field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the PortNumber field is set to the value of the last call.
-func (b *ExtensionApplyConfiguration) WithPortNumber(value apiv1.PortNumber) *ExtensionApplyConfiguration {
-	b.ExtensionReferenceApplyConfiguration.PortNumber = &value
+func (b *EndpointPickerRefApplyConfiguration) WithPortNumber(value apiv1.PortNumber) *EndpointPickerRefApplyConfiguration {
+	b.PortNumber = &value
 	return b
 }
 
 // WithFailureMode sets the FailureMode field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the FailureMode field is set to the value of the last call.
-func (b *ExtensionApplyConfiguration) WithFailureMode(value apiv1.ExtensionFailureMode) *ExtensionApplyConfiguration {
-	b.ExtensionConnectionApplyConfiguration.FailureMode = &value
+func (b *EndpointPickerRefApplyConfiguration) WithFailureMode(value apiv1.EndpointPickerFailureMode) *EndpointPickerRefApplyConfiguration {
+	b.FailureMode = &value
 	return b
 }
diff --git a/client-go/applyconfiguration/api/v1/extensionreference.go b/client-go/applyconfiguration/api/v1/extensionreference.go
deleted file mode 100644
index 21402d1..0000000
--- a/client-go/applyconfiguration/api/v1/extensionreference.go
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
-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.
-*/
-
-// Code generated by applyconfiguration-gen. DO NOT EDIT.
-
-package v1
-
-import (
-	apiv1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
-)
-
-// ExtensionReferenceApplyConfiguration represents a declarative configuration of the ExtensionReference type for use
-// with apply.
-type ExtensionReferenceApplyConfiguration struct {
-	Group      *apiv1.Group      `json:"group,omitempty"`
-	Kind       *apiv1.Kind       `json:"kind,omitempty"`
-	Name       *apiv1.ObjectName `json:"name,omitempty"`
-	PortNumber *apiv1.PortNumber `json:"portNumber,omitempty"`
-}
-
-// ExtensionReferenceApplyConfiguration constructs a declarative configuration of the ExtensionReference type for use with
-// apply.
-func ExtensionReference() *ExtensionReferenceApplyConfiguration {
-	return &ExtensionReferenceApplyConfiguration{}
-}
-
-// WithGroup sets the Group field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the Group field is set to the value of the last call.
-func (b *ExtensionReferenceApplyConfiguration) WithGroup(value apiv1.Group) *ExtensionReferenceApplyConfiguration {
-	b.Group = &value
-	return b
-}
-
-// WithKind sets the Kind field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the Kind field is set to the value of the last call.
-func (b *ExtensionReferenceApplyConfiguration) WithKind(value apiv1.Kind) *ExtensionReferenceApplyConfiguration {
-	b.Kind = &value
-	return b
-}
-
-// WithName sets the Name field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the Name field is set to the value of the last call.
-func (b *ExtensionReferenceApplyConfiguration) WithName(value apiv1.ObjectName) *ExtensionReferenceApplyConfiguration {
-	b.Name = &value
-	return b
-}
-
-// WithPortNumber sets the PortNumber field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the PortNumber field is set to the value of the last call.
-func (b *ExtensionReferenceApplyConfiguration) WithPortNumber(value apiv1.PortNumber) *ExtensionReferenceApplyConfiguration {
-	b.PortNumber = &value
-	return b
-}
diff --git a/client-go/applyconfiguration/api/v1/inferencepoolspec.go b/client-go/applyconfiguration/api/v1/inferencepoolspec.go
index d9c08b2..ca44987 100644
--- a/client-go/applyconfiguration/api/v1/inferencepoolspec.go
+++ b/client-go/applyconfiguration/api/v1/inferencepoolspec.go
@@ -18,16 +18,12 @@ limitations under the License.
 
 package v1
 
-import (
-	apiv1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
-)
-
 // InferencePoolSpecApplyConfiguration represents a declarative configuration of the InferencePoolSpec type for use
 // with apply.
 type InferencePoolSpecApplyConfiguration struct {
-	Selector                               map[apiv1.LabelKey]apiv1.LabelValue `json:"selector,omitempty"`
-	TargetPortNumber                       *int32                              `json:"targetPortNumber,omitempty"`
-	EndpointPickerConfigApplyConfiguration `json:",inline"`
+	Selector          *LabelSelectorApplyConfiguration     `json:"selector,omitempty"`
+	TargetPorts       []PortApplyConfiguration             `json:"targetPorts,omitempty"`
+	EndpointPickerRef *EndpointPickerRefApplyConfiguration `json:"endpointPickerRef,omitempty"`
 }
 
 // InferencePoolSpecApplyConfiguration constructs a declarative configuration of the InferencePoolSpec type for use with
@@ -36,32 +32,31 @@ func InferencePoolSpec() *InferencePoolSpecApplyConfiguration {
 	return &InferencePoolSpecApplyConfiguration{}
 }
 
-// WithSelector puts the entries into the Selector field in the declarative configuration
-// and returns the receiver, so that objects can be build by chaining "With" function invocations.
-// If called multiple times, the entries provided by each call will be put on the Selector field,
-// overwriting an existing map entries in Selector field with the same key.
-func (b *InferencePoolSpecApplyConfiguration) WithSelector(entries map[apiv1.LabelKey]apiv1.LabelValue) *InferencePoolSpecApplyConfiguration {
-	if b.Selector == nil && len(entries) > 0 {
-		b.Selector = make(map[apiv1.LabelKey]apiv1.LabelValue, len(entries))
-	}
-	for k, v := range entries {
-		b.Selector[k] = v
-	}
+// WithSelector sets the Selector field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Selector field is set to the value of the last call.
+func (b *InferencePoolSpecApplyConfiguration) WithSelector(value *LabelSelectorApplyConfiguration) *InferencePoolSpecApplyConfiguration {
+	b.Selector = value
 	return b
 }
 
-// WithTargetPortNumber sets the TargetPortNumber field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the TargetPortNumber field is set to the value of the last call.
-func (b *InferencePoolSpecApplyConfiguration) WithTargetPortNumber(value int32) *InferencePoolSpecApplyConfiguration {
-	b.TargetPortNumber = &value
+// WithTargetPorts adds the given value to the TargetPorts field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the TargetPorts field.
+func (b *InferencePoolSpecApplyConfiguration) WithTargetPorts(values ...*PortApplyConfiguration) *InferencePoolSpecApplyConfiguration {
+	for i := range values {
+		if values[i] == nil {
+			panic("nil value passed to WithTargetPorts")
+		}
+		b.TargetPorts = append(b.TargetPorts, *values[i])
+	}
 	return b
 }
 
-// WithExtensionRef sets the ExtensionRef field in the declarative configuration to the given value
+// WithEndpointPickerRef sets the EndpointPickerRef field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the ExtensionRef field is set to the value of the last call.
-func (b *InferencePoolSpecApplyConfiguration) WithExtensionRef(value *ExtensionApplyConfiguration) *InferencePoolSpecApplyConfiguration {
-	b.EndpointPickerConfigApplyConfiguration.ExtensionRef = value
+// If called multiple times, the EndpointPickerRef field is set to the value of the last call.
+func (b *InferencePoolSpecApplyConfiguration) WithEndpointPickerRef(value *EndpointPickerRefApplyConfiguration) *InferencePoolSpecApplyConfiguration {
+	b.EndpointPickerRef = value
 	return b
 }
diff --git a/client-go/applyconfiguration/api/v1/inferencepoolstatus.go b/client-go/applyconfiguration/api/v1/inferencepoolstatus.go
index 5c66e34..499b771 100644
--- a/client-go/applyconfiguration/api/v1/inferencepoolstatus.go
+++ b/client-go/applyconfiguration/api/v1/inferencepoolstatus.go
@@ -21,7 +21,7 @@ package v1
 // InferencePoolStatusApplyConfiguration represents a declarative configuration of the InferencePoolStatus type for use
 // with apply.
 type InferencePoolStatusApplyConfiguration struct {
-	Parents []PoolStatusApplyConfiguration `json:"parent,omitempty"`
+	Parents []ParentStatusApplyConfiguration `json:"parents,omitempty"`
 }
 
 // InferencePoolStatusApplyConfiguration constructs a declarative configuration of the InferencePoolStatus type for use with
@@ -33,7 +33,7 @@ func InferencePoolStatus() *InferencePoolStatusApplyConfiguration {
 // WithParents adds the given value to the Parents field in the declarative configuration
 // and returns the receiver, so that objects can be build by chaining "With" function invocations.
 // If called multiple times, values provided by each call will be appended to the Parents field.
-func (b *InferencePoolStatusApplyConfiguration) WithParents(values ...*PoolStatusApplyConfiguration) *InferencePoolStatusApplyConfiguration {
+func (b *InferencePoolStatusApplyConfiguration) WithParents(values ...*ParentStatusApplyConfiguration) *InferencePoolStatusApplyConfiguration {
 	for i := range values {
 		if values[i] == nil {
 			panic("nil value passed to WithParents")
diff --git a/client-go/applyconfiguration/api/v1/labelselector.go b/client-go/applyconfiguration/api/v1/labelselector.go
new file mode 100644
index 0000000..70953db
--- /dev/null
+++ b/client-go/applyconfiguration/api/v1/labelselector.go
@@ -0,0 +1,49 @@
+/*
+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.
+*/
+
+// Code generated by applyconfiguration-gen. DO NOT EDIT.
+
+package v1
+
+import (
+	apiv1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
+)
+
+// LabelSelectorApplyConfiguration represents a declarative configuration of the LabelSelector type for use
+// with apply.
+type LabelSelectorApplyConfiguration struct {
+	MatchLabels map[apiv1.LabelKey]apiv1.LabelValue `json:"matchLabels,omitempty"`
+}
+
+// LabelSelectorApplyConfiguration constructs a declarative configuration of the LabelSelector type for use with
+// apply.
+func LabelSelector() *LabelSelectorApplyConfiguration {
+	return &LabelSelectorApplyConfiguration{}
+}
+
+// WithMatchLabels puts the entries into the MatchLabels field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, the entries provided by each call will be put on the MatchLabels field,
+// overwriting an existing map entries in MatchLabels field with the same key.
+func (b *LabelSelectorApplyConfiguration) WithMatchLabels(entries map[apiv1.LabelKey]apiv1.LabelValue) *LabelSelectorApplyConfiguration {
+	if b.MatchLabels == nil && len(entries) > 0 {
+		b.MatchLabels = make(map[apiv1.LabelKey]apiv1.LabelValue, len(entries))
+	}
+	for k, v := range entries {
+		b.MatchLabels[k] = v
+	}
+	return b
+}
diff --git a/client-go/applyconfiguration/api/v1/parentgatewayreference.go b/client-go/applyconfiguration/api/v1/parentreference.go
similarity index 69%
rename from client-go/applyconfiguration/api/v1/parentgatewayreference.go
rename to client-go/applyconfiguration/api/v1/parentreference.go
index 5e23aeb..2798f02 100644
--- a/client-go/applyconfiguration/api/v1/parentgatewayreference.go
+++ b/client-go/applyconfiguration/api/v1/parentreference.go
@@ -22,25 +22,25 @@ import (
 	apiv1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 )
 
-// ParentGatewayReferenceApplyConfiguration represents a declarative configuration of the ParentGatewayReference type for use
+// ParentReferenceApplyConfiguration represents a declarative configuration of the ParentReference type for use
 // with apply.
-type ParentGatewayReferenceApplyConfiguration struct {
+type ParentReferenceApplyConfiguration struct {
 	Group     *apiv1.Group      `json:"group,omitempty"`
 	Kind      *apiv1.Kind       `json:"kind,omitempty"`
 	Name      *apiv1.ObjectName `json:"name,omitempty"`
 	Namespace *apiv1.Namespace  `json:"namespace,omitempty"`
 }
 
-// ParentGatewayReferenceApplyConfiguration constructs a declarative configuration of the ParentGatewayReference type for use with
+// ParentReferenceApplyConfiguration constructs a declarative configuration of the ParentReference type for use with
 // apply.
-func ParentGatewayReference() *ParentGatewayReferenceApplyConfiguration {
-	return &ParentGatewayReferenceApplyConfiguration{}
+func ParentReference() *ParentReferenceApplyConfiguration {
+	return &ParentReferenceApplyConfiguration{}
 }
 
 // WithGroup sets the Group field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the Group field is set to the value of the last call.
-func (b *ParentGatewayReferenceApplyConfiguration) WithGroup(value apiv1.Group) *ParentGatewayReferenceApplyConfiguration {
+func (b *ParentReferenceApplyConfiguration) WithGroup(value apiv1.Group) *ParentReferenceApplyConfiguration {
 	b.Group = &value
 	return b
 }
@@ -48,7 +48,7 @@ func (b *ParentGatewayReferenceApplyConfiguration) WithGroup(value apiv1.Group)
 // WithKind sets the Kind field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the Kind field is set to the value of the last call.
-func (b *ParentGatewayReferenceApplyConfiguration) WithKind(value apiv1.Kind) *ParentGatewayReferenceApplyConfiguration {
+func (b *ParentReferenceApplyConfiguration) WithKind(value apiv1.Kind) *ParentReferenceApplyConfiguration {
 	b.Kind = &value
 	return b
 }
@@ -56,7 +56,7 @@ func (b *ParentGatewayReferenceApplyConfiguration) WithKind(value apiv1.Kind) *P
 // WithName sets the Name field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the Name field is set to the value of the last call.
-func (b *ParentGatewayReferenceApplyConfiguration) WithName(value apiv1.ObjectName) *ParentGatewayReferenceApplyConfiguration {
+func (b *ParentReferenceApplyConfiguration) WithName(value apiv1.ObjectName) *ParentReferenceApplyConfiguration {
 	b.Name = &value
 	return b
 }
@@ -64,7 +64,7 @@ func (b *ParentGatewayReferenceApplyConfiguration) WithName(value apiv1.ObjectNa
 // WithNamespace sets the Namespace field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the Namespace field is set to the value of the last call.
-func (b *ParentGatewayReferenceApplyConfiguration) WithNamespace(value apiv1.Namespace) *ParentGatewayReferenceApplyConfiguration {
+func (b *ParentReferenceApplyConfiguration) WithNamespace(value apiv1.Namespace) *ParentReferenceApplyConfiguration {
 	b.Namespace = &value
 	return b
 }
diff --git a/client-go/applyconfiguration/api/v1/poolstatus.go b/client-go/applyconfiguration/api/v1/parentstatus.go
similarity index 57%
rename from client-go/applyconfiguration/api/v1/poolstatus.go
rename to client-go/applyconfiguration/api/v1/parentstatus.go
index 64fe2dc..5accb0f 100644
--- a/client-go/applyconfiguration/api/v1/poolstatus.go
+++ b/client-go/applyconfiguration/api/v1/parentstatus.go
@@ -22,31 +22,23 @@ import (
 	metav1 "k8s.io/client-go/applyconfigurations/meta/v1"
 )
 
-// PoolStatusApplyConfiguration represents a declarative configuration of the PoolStatus type for use
+// ParentStatusApplyConfiguration represents a declarative configuration of the ParentStatus type for use
 // with apply.
-type PoolStatusApplyConfiguration struct {
-	GatewayRef *ParentGatewayReferenceApplyConfiguration `json:"parentRef,omitempty"`
-	Conditions []metav1.ConditionApplyConfiguration      `json:"conditions,omitempty"`
+type ParentStatusApplyConfiguration struct {
+	Conditions []metav1.ConditionApplyConfiguration `json:"conditions,omitempty"`
+	ParentRef  *ParentReferenceApplyConfiguration   `json:"parentRef,omitempty"`
 }
 
-// PoolStatusApplyConfiguration constructs a declarative configuration of the PoolStatus type for use with
+// ParentStatusApplyConfiguration constructs a declarative configuration of the ParentStatus type for use with
 // apply.
-func PoolStatus() *PoolStatusApplyConfiguration {
-	return &PoolStatusApplyConfiguration{}
-}
-
-// WithGatewayRef sets the GatewayRef field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the GatewayRef field is set to the value of the last call.
-func (b *PoolStatusApplyConfiguration) WithGatewayRef(value *ParentGatewayReferenceApplyConfiguration) *PoolStatusApplyConfiguration {
-	b.GatewayRef = value
-	return b
+func ParentStatus() *ParentStatusApplyConfiguration {
+	return &ParentStatusApplyConfiguration{}
 }
 
 // WithConditions adds the given value to the Conditions field in the declarative configuration
 // and returns the receiver, so that objects can be build by chaining "With" function invocations.
 // If called multiple times, values provided by each call will be appended to the Conditions field.
-func (b *PoolStatusApplyConfiguration) WithConditions(values ...*metav1.ConditionApplyConfiguration) *PoolStatusApplyConfiguration {
+func (b *ParentStatusApplyConfiguration) WithConditions(values ...*metav1.ConditionApplyConfiguration) *ParentStatusApplyConfiguration {
 	for i := range values {
 		if values[i] == nil {
 			panic("nil value passed to WithConditions")
@@ -55,3 +47,11 @@ func (b *PoolStatusApplyConfiguration) WithConditions(values ...*metav1.Conditio
 	}
 	return b
 }
+
+// WithParentRef sets the ParentRef field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ParentRef field is set to the value of the last call.
+func (b *ParentStatusApplyConfiguration) WithParentRef(value *ParentReferenceApplyConfiguration) *ParentStatusApplyConfiguration {
+	b.ParentRef = value
+	return b
+}
diff --git a/client-go/applyconfiguration/api/v1/extensionconnection.go b/client-go/applyconfiguration/api/v1/port.go
similarity index 51%
rename from client-go/applyconfiguration/api/v1/extensionconnection.go
rename to client-go/applyconfiguration/api/v1/port.go
index cc07827..6067a5d 100644
--- a/client-go/applyconfiguration/api/v1/extensionconnection.go
+++ b/client-go/applyconfiguration/api/v1/port.go
@@ -22,22 +22,22 @@ import (
 	apiv1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 )
 
-// ExtensionConnectionApplyConfiguration represents a declarative configuration of the ExtensionConnection type for use
+// PortApplyConfiguration represents a declarative configuration of the Port type for use
 // with apply.
-type ExtensionConnectionApplyConfiguration struct {
-	FailureMode *apiv1.ExtensionFailureMode `json:"failureMode,omitempty"`
+type PortApplyConfiguration struct {
+	Number *apiv1.PortNumber `json:"number,omitempty"`
 }
 
-// ExtensionConnectionApplyConfiguration constructs a declarative configuration of the ExtensionConnection type for use with
+// PortApplyConfiguration constructs a declarative configuration of the Port type for use with
 // apply.
-func ExtensionConnection() *ExtensionConnectionApplyConfiguration {
-	return &ExtensionConnectionApplyConfiguration{}
+func Port() *PortApplyConfiguration {
+	return &PortApplyConfiguration{}
 }
 
-// WithFailureMode sets the FailureMode field in the declarative configuration to the given value
+// WithNumber sets the Number field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the FailureMode field is set to the value of the last call.
-func (b *ExtensionConnectionApplyConfiguration) WithFailureMode(value apiv1.ExtensionFailureMode) *ExtensionConnectionApplyConfiguration {
-	b.FailureMode = &value
+// If called multiple times, the Number field is set to the value of the last call.
+func (b *PortApplyConfiguration) WithNumber(value apiv1.PortNumber) *PortApplyConfiguration {
+	b.Number = &value
 	return b
 }
diff --git a/client-go/applyconfiguration/apix/v1alpha2/endpointpickerconfig.go b/client-go/applyconfiguration/apix/v1alpha2/endpointpickerconfig.go
deleted file mode 100644
index 39d4d8d..0000000
--- a/client-go/applyconfiguration/apix/v1alpha2/endpointpickerconfig.go
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
-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.
-*/
-
-// Code generated by applyconfiguration-gen. DO NOT EDIT.
-
-package v1alpha2
-
-// EndpointPickerConfigApplyConfiguration represents a declarative configuration of the EndpointPickerConfig type for use
-// with apply.
-type EndpointPickerConfigApplyConfiguration struct {
-	ExtensionRef *ExtensionApplyConfiguration `json:"extensionRef,omitempty"`
-}
-
-// EndpointPickerConfigApplyConfiguration constructs a declarative configuration of the EndpointPickerConfig type for use with
-// apply.
-func EndpointPickerConfig() *EndpointPickerConfigApplyConfiguration {
-	return &EndpointPickerConfigApplyConfiguration{}
-}
-
-// WithExtensionRef sets the ExtensionRef field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the ExtensionRef field is set to the value of the last call.
-func (b *EndpointPickerConfigApplyConfiguration) WithExtensionRef(value *ExtensionApplyConfiguration) *EndpointPickerConfigApplyConfiguration {
-	b.ExtensionRef = value
-	return b
-}
diff --git a/client-go/applyconfiguration/apix/v1alpha2/extension.go b/client-go/applyconfiguration/apix/v1alpha2/extension.go
index e3690f9..2f40a61 100644
--- a/client-go/applyconfiguration/apix/v1alpha2/extension.go
+++ b/client-go/applyconfiguration/apix/v1alpha2/extension.go
@@ -25,8 +25,11 @@ import (
 // ExtensionApplyConfiguration represents a declarative configuration of the Extension type for use
 // with apply.
 type ExtensionApplyConfiguration struct {
-	ExtensionReferenceApplyConfiguration  `json:",inline"`
-	ExtensionConnectionApplyConfiguration `json:",inline"`
+	Group       *apixv1alpha2.Group                `json:"group,omitempty"`
+	Kind        *apixv1alpha2.Kind                 `json:"kind,omitempty"`
+	Name        *apixv1alpha2.ObjectName           `json:"name,omitempty"`
+	PortNumber  *apixv1alpha2.PortNumber           `json:"portNumber,omitempty"`
+	FailureMode *apixv1alpha2.ExtensionFailureMode `json:"failureMode,omitempty"`
 }
 
 // ExtensionApplyConfiguration constructs a declarative configuration of the Extension type for use with
@@ -39,7 +42,7 @@ func Extension() *ExtensionApplyConfiguration {
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the Group field is set to the value of the last call.
 func (b *ExtensionApplyConfiguration) WithGroup(value apixv1alpha2.Group) *ExtensionApplyConfiguration {
-	b.ExtensionReferenceApplyConfiguration.Group = &value
+	b.Group = &value
 	return b
 }
 
@@ -47,7 +50,7 @@ func (b *ExtensionApplyConfiguration) WithGroup(value apixv1alpha2.Group) *Exten
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the Kind field is set to the value of the last call.
 func (b *ExtensionApplyConfiguration) WithKind(value apixv1alpha2.Kind) *ExtensionApplyConfiguration {
-	b.ExtensionReferenceApplyConfiguration.Kind = &value
+	b.Kind = &value
 	return b
 }
 
@@ -55,7 +58,7 @@ func (b *ExtensionApplyConfiguration) WithKind(value apixv1alpha2.Kind) *Extensi
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the Name field is set to the value of the last call.
 func (b *ExtensionApplyConfiguration) WithName(value apixv1alpha2.ObjectName) *ExtensionApplyConfiguration {
-	b.ExtensionReferenceApplyConfiguration.Name = &value
+	b.Name = &value
 	return b
 }
 
@@ -63,7 +66,7 @@ func (b *ExtensionApplyConfiguration) WithName(value apixv1alpha2.ObjectName) *E
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the PortNumber field is set to the value of the last call.
 func (b *ExtensionApplyConfiguration) WithPortNumber(value apixv1alpha2.PortNumber) *ExtensionApplyConfiguration {
-	b.ExtensionReferenceApplyConfiguration.PortNumber = &value
+	b.PortNumber = &value
 	return b
 }
 
@@ -71,6 +74,6 @@ func (b *ExtensionApplyConfiguration) WithPortNumber(value apixv1alpha2.PortNumb
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the FailureMode field is set to the value of the last call.
 func (b *ExtensionApplyConfiguration) WithFailureMode(value apixv1alpha2.ExtensionFailureMode) *ExtensionApplyConfiguration {
-	b.ExtensionConnectionApplyConfiguration.FailureMode = &value
+	b.FailureMode = &value
 	return b
 }
diff --git a/client-go/applyconfiguration/apix/v1alpha2/extensionconnection.go b/client-go/applyconfiguration/apix/v1alpha2/extensionconnection.go
deleted file mode 100644
index 17c1c6f..0000000
--- a/client-go/applyconfiguration/apix/v1alpha2/extensionconnection.go
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-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.
-*/
-
-// Code generated by applyconfiguration-gen. DO NOT EDIT.
-
-package v1alpha2
-
-import (
-	apixv1alpha2 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
-)
-
-// ExtensionConnectionApplyConfiguration represents a declarative configuration of the ExtensionConnection type for use
-// with apply.
-type ExtensionConnectionApplyConfiguration struct {
-	FailureMode *apixv1alpha2.ExtensionFailureMode `json:"failureMode,omitempty"`
-}
-
-// ExtensionConnectionApplyConfiguration constructs a declarative configuration of the ExtensionConnection type for use with
-// apply.
-func ExtensionConnection() *ExtensionConnectionApplyConfiguration {
-	return &ExtensionConnectionApplyConfiguration{}
-}
-
-// WithFailureMode sets the FailureMode field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the FailureMode field is set to the value of the last call.
-func (b *ExtensionConnectionApplyConfiguration) WithFailureMode(value apixv1alpha2.ExtensionFailureMode) *ExtensionConnectionApplyConfiguration {
-	b.FailureMode = &value
-	return b
-}
diff --git a/client-go/applyconfiguration/apix/v1alpha2/extensionreference.go b/client-go/applyconfiguration/apix/v1alpha2/extensionreference.go
deleted file mode 100644
index 1142cf2..0000000
--- a/client-go/applyconfiguration/apix/v1alpha2/extensionreference.go
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
-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.
-*/
-
-// Code generated by applyconfiguration-gen. DO NOT EDIT.
-
-package v1alpha2
-
-import (
-	apixv1alpha2 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
-)
-
-// ExtensionReferenceApplyConfiguration represents a declarative configuration of the ExtensionReference type for use
-// with apply.
-type ExtensionReferenceApplyConfiguration struct {
-	Group      *apixv1alpha2.Group      `json:"group,omitempty"`
-	Kind       *apixv1alpha2.Kind       `json:"kind,omitempty"`
-	Name       *apixv1alpha2.ObjectName `json:"name,omitempty"`
-	PortNumber *apixv1alpha2.PortNumber `json:"portNumber,omitempty"`
-}
-
-// ExtensionReferenceApplyConfiguration constructs a declarative configuration of the ExtensionReference type for use with
-// apply.
-func ExtensionReference() *ExtensionReferenceApplyConfiguration {
-	return &ExtensionReferenceApplyConfiguration{}
-}
-
-// WithGroup sets the Group field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the Group field is set to the value of the last call.
-func (b *ExtensionReferenceApplyConfiguration) WithGroup(value apixv1alpha2.Group) *ExtensionReferenceApplyConfiguration {
-	b.Group = &value
-	return b
-}
-
-// WithKind sets the Kind field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the Kind field is set to the value of the last call.
-func (b *ExtensionReferenceApplyConfiguration) WithKind(value apixv1alpha2.Kind) *ExtensionReferenceApplyConfiguration {
-	b.Kind = &value
-	return b
-}
-
-// WithName sets the Name field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the Name field is set to the value of the last call.
-func (b *ExtensionReferenceApplyConfiguration) WithName(value apixv1alpha2.ObjectName) *ExtensionReferenceApplyConfiguration {
-	b.Name = &value
-	return b
-}
-
-// WithPortNumber sets the PortNumber field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the PortNumber field is set to the value of the last call.
-func (b *ExtensionReferenceApplyConfiguration) WithPortNumber(value apixv1alpha2.PortNumber) *ExtensionReferenceApplyConfiguration {
-	b.PortNumber = &value
-	return b
-}
diff --git a/client-go/applyconfiguration/apix/v1alpha2/inferenceobjectivespec.go b/client-go/applyconfiguration/apix/v1alpha2/inferenceobjectivespec.go
index a8554a7..6fbcf33 100644
--- a/client-go/applyconfiguration/apix/v1alpha2/inferenceobjectivespec.go
+++ b/client-go/applyconfiguration/apix/v1alpha2/inferenceobjectivespec.go
@@ -18,16 +18,11 @@ limitations under the License.
 
 package v1alpha2
 
-import (
-	apixv1alpha2 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
-)
-
 // InferenceObjectiveSpecApplyConfiguration represents a declarative configuration of the InferenceObjectiveSpec type for use
 // with apply.
 type InferenceObjectiveSpecApplyConfiguration struct {
-	Criticality  *apixv1alpha2.Criticality              `json:"criticality,omitempty"`
-	TargetModels []TargetModelApplyConfiguration        `json:"targetModels,omitempty"`
-	PoolRef      *PoolObjectReferenceApplyConfiguration `json:"poolRef,omitempty"`
+	Priority *int                                   `json:"priority,omitempty"`
+	PoolRef  *PoolObjectReferenceApplyConfiguration `json:"poolRef,omitempty"`
 }
 
 // InferenceObjectiveSpecApplyConfiguration constructs a declarative configuration of the InferenceObjectiveSpec type for use with
@@ -36,24 +31,11 @@ func InferenceObjectiveSpec() *InferenceObjectiveSpecApplyConfiguration {
 	return &InferenceObjectiveSpecApplyConfiguration{}
 }
 
-// WithCriticality sets the Criticality field in the declarative configuration to the given value
+// WithPriority sets the Priority field in the declarative configuration to the given value
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the Criticality field is set to the value of the last call.
-func (b *InferenceObjectiveSpecApplyConfiguration) WithCriticality(value apixv1alpha2.Criticality) *InferenceObjectiveSpecApplyConfiguration {
-	b.Criticality = &value
-	return b
-}
-
-// WithTargetModels adds the given value to the TargetModels field in the declarative configuration
-// and returns the receiver, so that objects can be build by chaining "With" function invocations.
-// If called multiple times, values provided by each call will be appended to the TargetModels field.
-func (b *InferenceObjectiveSpecApplyConfiguration) WithTargetModels(values ...*TargetModelApplyConfiguration) *InferenceObjectiveSpecApplyConfiguration {
-	for i := range values {
-		if values[i] == nil {
-			panic("nil value passed to WithTargetModels")
-		}
-		b.TargetModels = append(b.TargetModels, *values[i])
-	}
+// If called multiple times, the Priority field is set to the value of the last call.
+func (b *InferenceObjectiveSpecApplyConfiguration) WithPriority(value int) *InferenceObjectiveSpecApplyConfiguration {
+	b.Priority = &value
 	return b
 }
 
diff --git a/client-go/applyconfiguration/apix/v1alpha2/inferencepoolspec.go b/client-go/applyconfiguration/apix/v1alpha2/inferencepoolspec.go
index 8773d9c..1f59e88 100644
--- a/client-go/applyconfiguration/apix/v1alpha2/inferencepoolspec.go
+++ b/client-go/applyconfiguration/apix/v1alpha2/inferencepoolspec.go
@@ -25,9 +25,9 @@ import (
 // InferencePoolSpecApplyConfiguration represents a declarative configuration of the InferencePoolSpec type for use
 // with apply.
 type InferencePoolSpecApplyConfiguration struct {
-	Selector                               map[apixv1alpha2.LabelKey]apixv1alpha2.LabelValue `json:"selector,omitempty"`
-	TargetPortNumber                       *int32                                            `json:"targetPortNumber,omitempty"`
-	EndpointPickerConfigApplyConfiguration `json:",inline"`
+	Selector         map[apixv1alpha2.LabelKey]apixv1alpha2.LabelValue `json:"selector,omitempty"`
+	TargetPortNumber *int32                                            `json:"targetPortNumber,omitempty"`
+	ExtensionRef     *ExtensionApplyConfiguration                      `json:"extensionRef,omitempty"`
 }
 
 // InferencePoolSpecApplyConfiguration constructs a declarative configuration of the InferencePoolSpec type for use with
@@ -62,6 +62,6 @@ func (b *InferencePoolSpecApplyConfiguration) WithTargetPortNumber(value int32)
 // and returns the receiver, so that objects can be built by chaining "With" function invocations.
 // If called multiple times, the ExtensionRef field is set to the value of the last call.
 func (b *InferencePoolSpecApplyConfiguration) WithExtensionRef(value *ExtensionApplyConfiguration) *InferencePoolSpecApplyConfiguration {
-	b.EndpointPickerConfigApplyConfiguration.ExtensionRef = value
+	b.ExtensionRef = value
 	return b
 }
diff --git a/client-go/applyconfiguration/apix/v1alpha2/targetmodel.go b/client-go/applyconfiguration/apix/v1alpha2/targetmodel.go
deleted file mode 100644
index 643587d..0000000
--- a/client-go/applyconfiguration/apix/v1alpha2/targetmodel.go
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-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.
-*/
-
-// Code generated by applyconfiguration-gen. DO NOT EDIT.
-
-package v1alpha2
-
-// TargetModelApplyConfiguration represents a declarative configuration of the TargetModel type for use
-// with apply.
-type TargetModelApplyConfiguration struct {
-	Name   *string `json:"name,omitempty"`
-	Weight *int32  `json:"weight,omitempty"`
-}
-
-// TargetModelApplyConfiguration constructs a declarative configuration of the TargetModel type for use with
-// apply.
-func TargetModel() *TargetModelApplyConfiguration {
-	return &TargetModelApplyConfiguration{}
-}
-
-// WithName sets the Name field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the Name field is set to the value of the last call.
-func (b *TargetModelApplyConfiguration) WithName(value string) *TargetModelApplyConfiguration {
-	b.Name = &value
-	return b
-}
-
-// WithWeight sets the Weight field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the Weight field is set to the value of the last call.
-func (b *TargetModelApplyConfiguration) WithWeight(value int32) *TargetModelApplyConfiguration {
-	b.Weight = &value
-	return b
-}
diff --git a/client-go/applyconfiguration/utils.go b/client-go/applyconfiguration/utils.go
index 46895d8..0662699 100644
--- a/client-go/applyconfiguration/utils.go
+++ b/client-go/applyconfiguration/utils.go
@@ -34,34 +34,26 @@ import (
 func ForKind(kind schema.GroupVersionKind) interface{} {
 	switch kind {
 	// Group=inference.networking.k8s.io, Version=v1
-	case v1.SchemeGroupVersion.WithKind("EndpointPickerConfig"):
-		return &apiv1.EndpointPickerConfigApplyConfiguration{}
-	case v1.SchemeGroupVersion.WithKind("Extension"):
-		return &apiv1.ExtensionApplyConfiguration{}
-	case v1.SchemeGroupVersion.WithKind("ExtensionConnection"):
-		return &apiv1.ExtensionConnectionApplyConfiguration{}
-	case v1.SchemeGroupVersion.WithKind("ExtensionReference"):
-		return &apiv1.ExtensionReferenceApplyConfiguration{}
+	case v1.SchemeGroupVersion.WithKind("EndpointPickerRef"):
+		return &apiv1.EndpointPickerRefApplyConfiguration{}
 	case v1.SchemeGroupVersion.WithKind("InferencePool"):
 		return &apiv1.InferencePoolApplyConfiguration{}
 	case v1.SchemeGroupVersion.WithKind("InferencePoolSpec"):
 		return &apiv1.InferencePoolSpecApplyConfiguration{}
 	case v1.SchemeGroupVersion.WithKind("InferencePoolStatus"):
 		return &apiv1.InferencePoolStatusApplyConfiguration{}
-	case v1.SchemeGroupVersion.WithKind("ParentGatewayReference"):
-		return &apiv1.ParentGatewayReferenceApplyConfiguration{}
-	case v1.SchemeGroupVersion.WithKind("PoolStatus"):
-		return &apiv1.PoolStatusApplyConfiguration{}
+	case v1.SchemeGroupVersion.WithKind("LabelSelector"):
+		return &apiv1.LabelSelectorApplyConfiguration{}
+	case v1.SchemeGroupVersion.WithKind("ParentReference"):
+		return &apiv1.ParentReferenceApplyConfiguration{}
+	case v1.SchemeGroupVersion.WithKind("ParentStatus"):
+		return &apiv1.ParentStatusApplyConfiguration{}
+	case v1.SchemeGroupVersion.WithKind("Port"):
+		return &apiv1.PortApplyConfiguration{}
 
 		// Group=inference.networking.x-k8s.io, Version=v1alpha2
-	case v1alpha2.SchemeGroupVersion.WithKind("EndpointPickerConfig"):
-		return &apixv1alpha2.EndpointPickerConfigApplyConfiguration{}
 	case v1alpha2.SchemeGroupVersion.WithKind("Extension"):
 		return &apixv1alpha2.ExtensionApplyConfiguration{}
-	case v1alpha2.SchemeGroupVersion.WithKind("ExtensionConnection"):
-		return &apixv1alpha2.ExtensionConnectionApplyConfiguration{}
-	case v1alpha2.SchemeGroupVersion.WithKind("ExtensionReference"):
-		return &apixv1alpha2.ExtensionReferenceApplyConfiguration{}
 	case v1alpha2.SchemeGroupVersion.WithKind("InferenceObjective"):
 		return &apixv1alpha2.InferenceObjectiveApplyConfiguration{}
 	case v1alpha2.SchemeGroupVersion.WithKind("InferenceObjectiveSpec"):
@@ -80,8 +72,6 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
 		return &apixv1alpha2.PoolObjectReferenceApplyConfiguration{}
 	case v1alpha2.SchemeGroupVersion.WithKind("PoolStatus"):
 		return &apixv1alpha2.PoolStatusApplyConfiguration{}
-	case v1alpha2.SchemeGroupVersion.WithKind("TargetModel"):
-		return &apixv1alpha2.TargetModelApplyConfiguration{}
 
 	}
 	return nil
diff --git a/cmd/epp/runner/health.go b/cmd/epp/runner/health.go
index cc5da8d..1edbcff 100644
--- a/cmd/epp/runner/health.go
+++ b/cmd/epp/runner/health.go
@@ -18,6 +18,8 @@ package runner
 
 import (
 	"context"
+	"fmt"
+	"sync/atomic"
 
 	extProcPb "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
 	"github.com/go-logr/logr"
@@ -30,37 +32,88 @@ import (
 )
 
 type healthServer struct {
-	logger    logr.Logger
-	datastore datastore.Datastore
+	logger                logr.Logger
+	datastore             datastore.Datastore
+	isLeader              *atomic.Bool
+	leaderElectionEnabled bool
 }
 
+const (
+	LivenessCheckService  = "liveness"
+	ReadinessCheckService = "readiness"
+)
+
 func (s *healthServer) Check(ctx context.Context, in *healthPb.HealthCheckRequest) (*healthPb.HealthCheckResponse, error) {
-	// TODO: we're accepting ANY service name for now as a temporary hack in alignment with
-	// upstream issues. See https://github.com/kubernetes-sigs/gateway-api-inference-extension/pull/788
-	// if in.Service != extProcPb.ExternalProcessor_ServiceDesc.ServiceName {
-	// 	s.logger.V(logutil.DEFAULT).Info("gRPC health check requested unknown service", "available-services", []string{extProcPb.ExternalProcessor_ServiceDesc.ServiceName}, "requested-service", in.Service)
-	// 	return &healthPb.HealthCheckResponse{Status: healthPb.HealthCheckResponse_SERVICE_UNKNOWN}, nil
-	// }
-
-	if !s.datastore.PoolHasSynced() {
-		s.logger.V(logutil.DEFAULT).Info("gRPC health check not serving", "service", in.Service)
+	isLive := s.datastore.PoolHasSynced()
+
+	// If leader election is disabled, use current logic: all checks are based on whether the pool has synced.
+	if !s.leaderElectionEnabled {
+		if !isLive {
+			s.logger.V(logutil.DEFAULT).Info("gRPC health check not serving (leader election disabled)", "service", in.Service)
+			return &healthPb.HealthCheckResponse{Status: healthPb.HealthCheckResponse_NOT_SERVING}, nil
+		}
+		s.logger.V(logutil.TRACE).Info("gRPC health check serving (leader election disabled)", "service", in.Service)
+		return &healthPb.HealthCheckResponse{Status: healthPb.HealthCheckResponse_SERVING}, nil
+	}
+
+	// When leader election is enabled, differentiate between liveness and readiness.
+	// The service name in the request determines which check to perform.
+	var checkName string
+	var isPassing bool
+
+	switch in.Service {
+	case ReadinessCheckService:
+		checkName = "readiness"
+		isPassing = isLive && s.isLeader.Load()
+	case "": // Handle overall server health for load balancers that use an empty service name.
+		checkName = "empty service name (considered as overall health)"
+		// The overall health for a load balancer should reflect readiness to accept traffic,
+		// which is true only for the leader pod that has synced its data.
+		isPassing = isLive && s.isLeader.Load()
+	case LivenessCheckService:
+		checkName = "liveness"
+		// Any pod that is running and can respond to this gRPC check is considered "live".
+		// The datastore sync status should not affect liveness, only readiness.
+		// This is to prevent the non-leader node from continuous restarts
+		isPassing = true
+	case extProcPb.ExternalProcessor_ServiceDesc.ServiceName:
+		// The main service is considered ready only on the leader.
+		checkName = "ext_proc"
+		isPassing = isLive && s.isLeader.Load()
+	default:
+		s.logger.V(logutil.DEFAULT).Info("gRPC health check requested unknown service", "available-services", []string{LivenessCheckService, ReadinessCheckService, extProcPb.ExternalProcessor_ServiceDesc.ServiceName}, "requested-service", in.Service)
+		return &healthPb.HealthCheckResponse{Status: healthPb.HealthCheckResponse_SERVICE_UNKNOWN}, nil
+	}
+
+	if !isPassing {
+		s.logger.V(logutil.DEFAULT).Info(fmt.Sprintf("gRPC %s check not serving", checkName), "service", in.Service, "isLive", isLive, "isLeader", s.isLeader.Load())
 		return &healthPb.HealthCheckResponse{Status: healthPb.HealthCheckResponse_NOT_SERVING}, nil
 	}
-	s.logger.V(logutil.TRACE).Info("gRPC health check serving", "service", in.Service)
+
+	s.logger.V(logutil.TRACE).Info(fmt.Sprintf("gRPC %s check serving", checkName), "service", in.Service)
 	return &healthPb.HealthCheckResponse{Status: healthPb.HealthCheckResponse_SERVING}, nil
 }
 
 func (s *healthServer) List(ctx context.Context, _ *healthPb.HealthListRequest) (*healthPb.HealthListResponse, error) {
-	// currently only the ext_proc service is provided
-	serviceHealthResponse, err := s.Check(ctx, &healthPb.HealthCheckRequest{Service: extProcPb.ExternalProcessor_ServiceDesc.ServiceName})
-	if err != nil {
-		return nil, err
+	statuses := make(map[string]*healthPb.HealthCheckResponse)
+
+	services := []string{extProcPb.ExternalProcessor_ServiceDesc.ServiceName}
+	if s.leaderElectionEnabled {
+		services = append(services, LivenessCheckService, ReadinessCheckService)
+	}
+
+	for _, service := range services {
+		resp, err := s.Check(ctx, &healthPb.HealthCheckRequest{Service: service})
+		if err != nil {
+			// Check can return an error for unknown services, but here we are iterating known services.
+			// If another error occurs, we should probably return it.
+			return nil, err
+		}
+		statuses[service] = resp
 	}
 
 	return &healthPb.HealthListResponse{
-		Statuses: map[string]*healthPb.HealthCheckResponse{
-			extProcPb.ExternalProcessor_ServiceDesc.ServiceName: serviceHealthResponse,
-		},
+		Statuses: statuses,
 	}, nil
 }
 
diff --git a/cmd/epp/runner/runner.go b/cmd/epp/runner/runner.go
index d8e21c4..ea0fe56 100644
--- a/cmd/epp/runner/runner.go
+++ b/cmd/epp/runner/runner.go
@@ -25,6 +25,7 @@ import (
 	"net/http"
 	"net/http/pprof"
 	"os"
+	"sync/atomic"
 
 	"github.com/go-logr/logr"
 	"github.com/prometheus/client_golang/prometheus"
@@ -45,6 +46,8 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/common"
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/config/loader"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
+	dlmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics/collectors"
@@ -58,99 +61,50 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/scorer"
 	testfilter "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/test/filter"
 	runserver "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/server"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/env"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
 	"sigs.k8s.io/gateway-api-inference-extension/version"
 )
 
+const (
+	// enableExperimentalDatalayerV2 defines the environment variable
+	// used as feature flag for the pluggable data layer.
+	enableExperimentalDatalayerV2 = "ENABLE_EXPERIMENTAL_DATALAYER_V2"
+)
+
 var (
-	grpcPort = flag.Int(
-		"grpc-port",
-		runserver.DefaultGrpcPort,
-		"The gRPC port used for communicating with Envoy proxy")
-	grpcHealthPort = flag.Int(
-		"grpc-health-port",
-		runserver.DefaultGrpcHealthPort,
-		"The port used for gRPC liveness and readiness probes")
-	metricsPort = flag.Int(
-		"metrics-port",
-		runserver.DefaultMetricsPort,
-		"The metrics port")
-	enablePprof = flag.Bool(
-		"enable-pprof",
-		runserver.DefaultEnablePprof,
-		"Enables pprof handlers. Defaults to true. Set to false to disable pprof handlers.")
-	poolName = flag.String(
-		"pool-name",
-		runserver.DefaultPoolName,
-		"Name of the InferencePool this Endpoint Picker is associated with.")
-	poolGroup = flag.String(
-		"pool-group",
-		runserver.DefaultPoolGroup,
-		"group of the InferencePool this Endpoint Picker is associated with.")
-	poolNamespace = flag.String(
-		"pool-namespace",
-		runserver.DefaultPoolNamespace,
-		"Namespace of the InferencePool this Endpoint Picker is associated with.")
-	logVerbosity = flag.Int(
-		"v",
-		logging.DEFAULT,
-		"number for the log level verbosity")
-	secureServing = flag.Bool(
-		"secure-serving",
-		runserver.DefaultSecureServing,
-		"Enables secure serving. Defaults to true.")
-	healthChecking = flag.Bool(
-		"health-checking",
-		runserver.DefaultHealthChecking,
-		"Enables health checking")
-	certPath = flag.String(
-		"cert-path",
-		runserver.DefaultCertPath,
-		"The path to the certificate for secure serving. The certificate and private key files "+
-			"are assumed to be named tls.crt and tls.key, respectively. If not set, and secureServing is enabled, "+
-			"then a self-signed certificate is used.")
+	grpcPort       = flag.Int("grpc-port", runserver.DefaultGrpcPort, "The gRPC port used for communicating with Envoy proxy")
+	grpcHealthPort = flag.Int("grpc-health-port", runserver.DefaultGrpcHealthPort, "The port used for gRPC liveness and readiness probes")
+	metricsPort    = flag.Int("metrics-port", runserver.DefaultMetricsPort, "The metrics port")
+	enablePprof    = flag.Bool("enable-pprof", runserver.DefaultEnablePprof, "Enables pprof handlers. Defaults to true. Set to false to disable pprof handlers.")
+	poolName       = flag.String("pool-name", runserver.DefaultPoolName, "Name of the InferencePool this Endpoint Picker is associated with.")
+	poolGroup      = flag.String("pool-group", runserver.DefaultPoolGroup, "group of the InferencePool this Endpoint Picker is associated with.")
+	poolNamespace  = flag.String("pool-namespace", runserver.DefaultPoolNamespace, "Namespace of the InferencePool this Endpoint Picker is associated with.")
+	logVerbosity   = flag.Int("v", logging.DEFAULT, "number for the log level verbosity")
+	secureServing  = flag.Bool("secure-serving", runserver.DefaultSecureServing, "Enables secure serving. Defaults to true.")
+	healthChecking = flag.Bool("health-checking", runserver.DefaultHealthChecking, "Enables health checking")
+	certPath       = flag.String("cert-path", runserver.DefaultCertPath, "The path to the certificate for secure serving. The certificate and private key files "+
+		"are assumed to be named tls.crt and tls.key, respectively. If not set, and secureServing is enabled, "+
+		"then a self-signed certificate is used.")
 	// metric flags
-	totalQueuedRequestsMetric = flag.String(
-		"total-queued-requests-metric",
-		runserver.DefaultTotalQueuedRequestsMetric,
-		"Prometheus metric for the number of queued requests.")
-	kvCacheUsagePercentageMetric = flag.String(
-		"kv-cache-usage-percentage-metric",
-		runserver.DefaultKvCacheUsagePercentageMetric,
-		"Prometheus metric for the fraction of KV-cache blocks currently in use (from 0 to 1).")
+	totalQueuedRequestsMetric    = flag.String("total-queued-requests-metric", runserver.DefaultTotalQueuedRequestsMetric, "Prometheus metric for the number of queued requests.")
+	kvCacheUsagePercentageMetric = flag.String("kv-cache-usage-percentage-metric", runserver.DefaultKvCacheUsagePercentageMetric, "Prometheus metric for the fraction of KV-cache blocks currently in use (from 0 to 1).")
 	// LoRA metrics
-	loraInfoMetric = flag.String(
-		"lora-info-metric",
-		runserver.DefaultLoraInfoMetric,
-		"Prometheus metric for the LoRA info metrics (must be in vLLM label format).")
-
+	loraInfoMetric = flag.String("lora-info-metric", runserver.DefaultLoraInfoMetric, "Prometheus metric for the LoRA info metrics (must be in vLLM label format).")
 	// metrics related flags
-	refreshMetricsInterval = flag.Duration(
-		"refresh-metrics-interval",
-		runserver.DefaultRefreshMetricsInterval,
-		"interval to refresh metrics")
-	refreshPrometheusMetricsInterval = flag.Duration(
-		"refresh-prometheus-metrics-interval",
-		runserver.DefaultRefreshPrometheusMetricsInterval,
-		"interval to flush prometheus metrics")
-	metricsStalenessThreshold = flag.Duration("metrics-staleness-threshold",
-		runserver.DefaultMetricsStalenessThreshold,
-		"Duration after which metrics are considered stale. This is used to determine if a pod's metrics are fresh enough.")
+	refreshMetricsInterval           = flag.Duration("refresh-metrics-interval", runserver.DefaultRefreshMetricsInterval, "interval to refresh metrics")
+	refreshPrometheusMetricsInterval = flag.Duration("refresh-prometheus-metrics-interval", runserver.DefaultRefreshPrometheusMetricsInterval, "interval to flush prometheus metrics")
+	metricsStalenessThreshold        = flag.Duration("metrics-staleness-threshold", runserver.DefaultMetricsStalenessThreshold, "Duration after which metrics are considered stale. This is used to determine if a pod's metrics are fresh enough.")
 	// configuration flags
-	configFile = flag.String(
-		"config-file",
-		runserver.DefaultConfigFile,
-		"The path to the configuration file")
-	configText = flag.String(
-		"config-text",
-		runserver.DefaultConfigText,
-		"The configuration specified as text, in lieu of a file")
+	configFile = flag.String("config-file", runserver.DefaultConfigFile, "The path to the configuration file")
+	configText = flag.String("config-text", runserver.DefaultConfigText, "The configuration specified as text, in lieu of a file")
 
 	modelServerMetricsPort = flag.Int("model-server-metrics-port", 0, "Port to scrape metrics from pods. "+
-		"Default value will be set to InferencePool.Spec.TargetPortNumber if not set.")
+		"Default value will be set to the InferencePool.Spec.TargetPorts[0].Number if not set.")
 	modelServerMetricsPath                    = flag.String("model-server-metrics-path", "/metrics", "Path to scrape metrics from pods")
 	modelServerMetricsScheme                  = flag.String("model-server-metrics-scheme", "http", "Scheme to scrape metrics from pods")
 	modelServerMetricsHttpsInsecureSkipVerify = flag.Bool("model-server-metrics-https-insecure-skip-verify", true, "When using 'https' scheme for 'model-server-metrics-scheme', configure 'InsecureSkipVerify' (default to true)")
+	haEnableLeaderElection                    = flag.Bool("ha-enable-leader-election", false, "Enables leader election for high availability. When enabled, readiness probes will only pass on the leader.")
 
 	setupLog = ctrl.Log.WithName("setup")
 )
@@ -178,33 +132,7 @@ func (r *Runner) WithSchedulerConfig(schedulerConfig *scheduling.SchedulerConfig
 	return r
 }
 
-func bindEnvToFlags() {
-	// map[ENV_VAR]flagName   – add more as needed
-	for env, flg := range map[string]string{
-		"GRPC_PORT":                                       "grpc-port",
-		"GRPC_HEALTH_PORT":                                "grpc-health-port",
-		"MODEL_SERVER_METRICS_PORT":                       "model-server-metrics-port",
-		"MODEL_SERVER_METRICS_PATH":                       "model-server-metrics-path",
-		"MODEL_SERVER_METRICS_SCHEME":                     "model-server-metrics-scheme",
-		"MODEL_SERVER_METRICS_HTTPS_INSECURE_SKIP_VERIFY": "model-server-metrics-https-insecure-skip-verify",
-		"POOL_NAME":                                       "pool-name",
-		"POOL_NAMESPACE":                                  "pool-namespace",
-		// durations & bools work too; flag.Set expects the *string* form
-		"REFRESH_METRICS_INTERVAL": "refresh-metrics-interval",
-		"SECURE_SERVING":           "secure-serving",
-	} {
-		if v := os.Getenv(env); v != "" {
-			// ignore error; Parse() will catch invalid values later
-			_ = flag.Set(flg, v)
-		}
-	}
-}
-
 func (r *Runner) Run(ctx context.Context) error {
-	// Defaults already baked into flag declarations
-	// Load env vars as "soft" overrides
-	bindEnvToFlags()
-
 	opts := zap.Options{
 		Development: true,
 	}
@@ -238,40 +166,12 @@ func (r *Runner) Run(ctx context.Context) error {
 	}
 
 	// --- Setup Datastore ---
-	mapping, err := backendmetrics.NewMetricMapping(
-		*totalQueuedRequestsMetric,
-		*kvCacheUsagePercentageMetric,
-		*loraInfoMetric,
-	)
+	useDatalayerV2 := env.GetEnvBool(enableExperimentalDatalayerV2, false, setupLog)
+	epf, err := r.setupMetricsCollection(setupLog, useDatalayerV2)
 	if err != nil {
-		setupLog.Error(err, "Failed to create metric mapping from flags.")
 		return err
 	}
-	verifyMetricMapping(*mapping, setupLog)
-
-	var metricsHttpClient *http.Client
-	if *modelServerMetricsScheme == "https" {
-		metricsHttpClient = &http.Client{
-			Transport: &http.Transport{
-				TLSClientConfig: &tls.Config{
-					InsecureSkipVerify: *modelServerMetricsHttpsInsecureSkipVerify,
-				},
-			},
-		}
-	} else {
-		metricsHttpClient = http.DefaultClient
-	}
-
-	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.PodMetricsClientImpl{
-		MetricMapping:            mapping,
-		ModelServerMetricsPort:   int32(*modelServerMetricsPort),
-		ModelServerMetricsPath:   *modelServerMetricsPath,
-		ModelServerMetricsScheme: *modelServerMetricsScheme,
-		Client:                   metricsHttpClient,
-	},
-		*refreshMetricsInterval, *metricsStalenessThreshold)
-
-	datastore := datastore.NewDatastore(ctx, pmf)
+	datastore := datastore.NewDatastore(ctx, epf)
 
 	// --- Setup Metrics Server ---
 	customCollectors := []prometheus.Collector{collectors.NewInferencePoolMetricsCollector(datastore)}
@@ -299,12 +199,28 @@ func (r *Runner) Run(ctx context.Context) error {
 		NamespacedName: poolNamespacedName,
 		GroupKind:      poolGroupKind,
 	}
-	mgr, err := runserver.NewDefaultManager(poolGKNN, cfg, metricsServerOptions)
+
+	isLeader := &atomic.Bool{}
+	isLeader.Store(false)
+
+	mgr, err := runserver.NewDefaultManager(poolGKNN, cfg, metricsServerOptions, *haEnableLeaderElection)
 	if err != nil {
 		setupLog.Error(err, "Failed to create controller manager")
 		return err
 	}
 
+	if *haEnableLeaderElection {
+		setupLog.Info("Leader election enabled")
+		go func() {
+			<-mgr.Elected()
+			isLeader.Store(true)
+			setupLog.Info("This instance is now the leader!")
+		}()
+	} else {
+		// If leader election is disabled, all instances are "leaders" for readiness purposes.
+		isLeader.Store(true)
+	}
+
 	if *enablePprof {
 		setupLog.Info("Enabling pprof handlers")
 		err = setupPprofHandlers(mgr)
@@ -348,6 +264,7 @@ func (r *Runner) Run(ctx context.Context) error {
 		MetricsStalenessThreshold:        *metricsStalenessThreshold,
 		Director:                         director,
 		SaturationDetector:               saturationDetector,
+		UseExperimentalDatalayerV2:       useDatalayerV2, // pluggable data layer feature flag
 	}
 	if err := serverRunner.SetupWithManager(ctx, mgr); err != nil {
 		setupLog.Error(err, "Failed to setup EPP controllers")
@@ -356,7 +273,7 @@ func (r *Runner) Run(ctx context.Context) error {
 
 	// --- Add Runnables to Manager ---
 	// Register health server.
-	if err := registerHealthServer(mgr, ctrl.Log.WithName("health"), datastore, *grpcHealthPort); err != nil {
+	if err := registerHealthServer(mgr, ctrl.Log.WithName("health"), datastore, *grpcHealthPort, isLeader, *haEnableLeaderElection); err != nil {
 		return err
 	}
 
@@ -423,6 +340,81 @@ func (r *Runner) parsePluginsConfiguration(ctx context.Context) error {
 	return nil
 }
 
+func (r *Runner) setupMetricsCollection(setupLog logr.Logger, useExperimentalDatalayer bool) (datalayer.EndpointFactory, error) {
+	if useExperimentalDatalayer {
+		return setupDatalayer()
+	}
+
+	if len(datalayer.GetSources()) != 0 {
+		setupLog.Info("data sources registered but pluggable datalayer is disabled")
+	}
+	return setupMetricsV1(setupLog)
+}
+
+func setupMetricsV1(setupLog logr.Logger) (datalayer.EndpointFactory, error) {
+	mapping, err := backendmetrics.NewMetricMapping(
+		*totalQueuedRequestsMetric,
+		*kvCacheUsagePercentageMetric,
+		*loraInfoMetric,
+	)
+	if err != nil {
+		setupLog.Error(err, "Failed to create metric mapping from flags.")
+		return nil, err
+	}
+	verifyMetricMapping(*mapping, setupLog)
+
+	var metricsHttpClient *http.Client
+	if *modelServerMetricsScheme == "https" {
+		metricsHttpClient = &http.Client{
+			Transport: &http.Transport{
+				TLSClientConfig: &tls.Config{
+					InsecureSkipVerify: *modelServerMetricsHttpsInsecureSkipVerify,
+				},
+			},
+		}
+	} else {
+		metricsHttpClient = http.DefaultClient
+	}
+
+	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.PodMetricsClientImpl{
+		MetricMapping:            mapping,
+		ModelServerMetricsPort:   int32(*modelServerMetricsPort),
+		ModelServerMetricsPath:   *modelServerMetricsPath,
+		ModelServerMetricsScheme: *modelServerMetricsScheme,
+		Client:                   metricsHttpClient,
+	},
+		*refreshMetricsInterval)
+	return pmf, nil
+}
+
+func setupDatalayer() (datalayer.EndpointFactory, error) {
+	// create and register a metrics data source and extractor. In the future,
+	// data sources and extractors might be configured via a file. Once done,
+	// this (and registering the sources with the endpoint factory) should
+	// be moved accordingly.
+	source := dlmetrics.NewDataSource(*modelServerMetricsScheme,
+		int32(*modelServerMetricsPort), // start with (optional) command line port value
+		*modelServerMetricsPath,
+		*modelServerMetricsHttpsInsecureSkipVerify,
+		nil)
+	extractor, err := dlmetrics.NewExtractor(*totalQueuedRequestsMetric,
+		*kvCacheUsagePercentageMetric,
+		*loraInfoMetric)
+
+	if err != nil {
+		return nil, err
+	}
+	if err := source.AddExtractor(extractor); err != nil {
+		return nil, err
+	}
+	if err := datalayer.RegisterSource(source); err != nil {
+		return nil, err
+	}
+
+	factory := datalayer.NewEndpointFactory(datalayer.GetSources(), *refreshMetricsInterval)
+	return factory, nil
+}
+
 func initLogging(opts *zap.Options) {
 	// Unless -zap-log-level is explicitly set, use -v
 	useV := true
@@ -452,11 +444,13 @@ func registerExtProcServer(mgr manager.Manager, runner *runserver.ExtProcServerR
 }
 
 // registerHealthServer adds the Health gRPC server as a Runnable to the given manager.
-func registerHealthServer(mgr manager.Manager, logger logr.Logger, ds datastore.Datastore, port int) error {
+func registerHealthServer(mgr manager.Manager, logger logr.Logger, ds datastore.Datastore, port int, isLeader *atomic.Bool, leaderElectionEnabled bool) error {
 	srv := grpc.NewServer()
 	healthPb.RegisterHealthServer(srv, &healthServer{
-		logger:    logger,
-		datastore: ds,
+		logger:                logger,
+		datastore:             ds,
+		isLeader:              isLeader,
+		leaderElectionEnabled: leaderElectionEnabled,
 	})
 	if err := mgr.Add(
 		runnable.NoLeaderElection(runnable.GRPCServer("health", srv, port))); err != nil {
diff --git a/config/charts/inferencepool/README.md b/config/charts/inferencepool/README.md
index b8a8d0a..5bbbac7 100644
--- a/config/charts/inferencepool/README.md
+++ b/config/charts/inferencepool/README.md
@@ -79,6 +79,24 @@ $ helm install triton-llama3-8b-instruct \
   oci://us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/charts/inferencepool --version v0
 ```
 
+### Install with High Availability (HA)
+
+To deploy the EndpointPicker in a high-availability (HA) active-passive configuration, you can enable leader election. When enabled, the EPP deployment will have multiple replicas, but only one "leader" replica will be active and ready to process traffic at any given time. If the leader pod fails, another pod will be elected as the new leader, ensuring service continuity.
+
+To enable HA, set `inferenceExtension.flags.has-enable-leader-election` to `true` and increase the number of replicas in your `values.yaml` file:
+
+```yaml
+inferenceExtension:
+  replicas: 3
+  has-enable-leader-election: true
+```
+
+Then apply it with:
+
+```txt
+helm install vllm-llama3-8b-instruct ./config/charts/inferencepool -f values.yaml
+```
+
 ## Uninstall
 
 Run the following command to uninstall the chart:
@@ -101,11 +119,11 @@ The following table list the configurable parameters of the chart.
 | `inferenceExtension.image.hub`              | Registry URL where the endpoint picker image is hosted.                                                                |
 | `inferenceExtension.image.tag`              | Image tag of the endpoint picker.                                                                                      |
 | `inferenceExtension.image.pullPolicy`       | Image pull policy for the container. Possible values: `Always`, `IfNotPresent`, or `Never`. Defaults to `Always`.      |
-| `inferenceExtension.extProcPort`            | Port where the endpoint picker service is served for external processing. Defaults to `9002`.                          |
 | `inferenceExtension.env`                    | List of environment variables to set in the endpoint picker container as free-form YAML. Defaults to `[]`.             |
-| `inferenceExtension.extraContainerPorts`    | List of additional container ports to expose. Defaults to `[]`.                                                       |
-| `inferenceExtension.extraServicePorts`      | List of additional service ports to expose. Defaults to `[]`.                                                         |
-| `inferenceExtension.logVerbosity`           | Logging verbosity level for the endpoint picker. Defaults to `"3"`.                                                   |
+| `inferenceExtension.extraContainerPorts`    | List of additional container ports to expose. Defaults to `[]`.                                                        |
+| `inferenceExtension.extraServicePorts`      | List of additional service ports to expose. Defaults to `[]`.                                                          |
+| `inferenceExtension.flags`                  | List of flags which are passed through to endpoint picker. Example flags, enable-pprof, grpc-port etc. Refer [runner.go](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/cmd/epp/runner/runner.go) for complete list.                                                            |
+| `inferenceExtension.flags.has-enable-leader-election` | Enable leader election for high availability. When enabled, only one EPP pod (the leader) will be ready to serve traffic.       |
 | `provider.name`                             | Name of the Inference Gateway implementation being used. Possible values: `gke`. Defaults to `none`.                   |
 
 ## Notes
diff --git a/config/charts/inferencepool/templates/epp-deployment.yaml b/config/charts/inferencepool/templates/epp-deployment.yaml
index 23957a3..3d210fc 100644
--- a/config/charts/inferencepool/templates/epp-deployment.yaml
+++ b/config/charts/inferencepool/templates/epp-deployment.yaml
@@ -27,23 +27,13 @@ spec:
         - {{ .Release.Name }}
         - --pool-namespace
         - {{ .Release.Namespace }}
-        - --v
-        - "{{ .Values.inferenceExtension.logVerbosity | default "3" }}"
-        - --grpc-port
-        - "9002"
-        - --grpc-health-port
-        - "9003"
         - --zap-encoder
         - "json"
-        - --metrics-port
-        - "9090"
         - --config-file
-        - "config/{{ .Values.inferenceExtension.pluginsConfigFile }}"
-        # https://pkg.go.dev/flag#hdr-Command_line_flag_syntax; space is only for non-bool flags
-        - "--enable-pprof={{ .Values.inferenceExtension.enablePprof }}"
-        - "--model-server-metrics-path={{ .Values.inferenceExtension.modelServerMetricsPath }}"
-        - "--model-server-metrics-scheme={{ .Values.inferenceExtension.modelServerMetricsScheme }}"
-        - "--model-server-metrics-https-insecure-skip-verify={{ .Values.inferenceExtension.modelServerMetricsHttpsInsecureSkipVerify }}"
+        - "/config/default-plugins.yaml"
+        {{- range .Values.inferenceExtension.flags }}
+        - "--{{ .name }}={{ .value }}"
+        {{- end }}
         {{- if eq (.Values.inferencePool.modelServerType | default "vllm") "triton-tensorrt-llm" }}
         - --total-queued-requests-metric
         - "nv_trt_llm_request_metrics{request_type=waiting}"
@@ -63,15 +53,27 @@ spec:
         {{- toYaml . | nindent 8 }}
         {{- end }}
         livenessProbe:
+          {{- if .Values.inferenceExtension.enableLeaderElection }}
+          grpc:
+            port: 9003
+            service: liveness
+          {{- else }}
           grpc:
             port: 9003
             service: inference-extension
+          {{- end }}
           initialDelaySeconds: 5
           periodSeconds: 10
         readinessProbe:
+          {{- if .Values.inferenceExtension.enableLeaderElection }}
+          grpc:
+            port: 9003
+            service: readiness
+          {{- else }}
           grpc:
             port: 9003
             service: inference-extension
+          {{- end }}
           initialDelaySeconds: 5
           periodSeconds: 10
         {{- with .Values.inferenceExtension.env }}
diff --git a/config/charts/inferencepool/templates/inferencepool.yaml b/config/charts/inferencepool/templates/inferencepool.yaml
index f9d04df..27e6f72 100644
--- a/config/charts/inferencepool/templates/inferencepool.yaml
+++ b/config/charts/inferencepool/templates/inferencepool.yaml
@@ -7,12 +7,16 @@ metadata:
   labels:
     {{- include "gateway-api-inference-extension.labels" . | nindent 4 }}
 spec:
-  targetPortNumber: {{ .Values.inferencePool.targetPortNumber }}
-  selector:
-    {{- if .Values.inferencePool.modelServers.matchLabels }}
-    {{- range $key, $value := .Values.inferencePool.modelServers.matchLabels }}
-    {{ $key }}: {{ quote $value }}
-    {{- end }}
+  targetPorts:
+    {{- range .Values.inferencePool.targetPorts }}
+      - number: {{ .number }}
     {{- end }}
-  extensionRef:
+  selector:
+    matchLabels:
+      {{- if .Values.inferencePool.modelServers.matchLabels }}
+      {{- range $key, $value := .Values.inferencePool.modelServers.matchLabels }}
+      {{ $key }}: {{ quote $value }}
+      {{- end }}
+      {{- end }}
+  endpointPickerRef:
     name: {{ include "gateway-api-inference-extension.name" . }}
diff --git a/config/charts/inferencepool/templates/leader-election-rbac.yaml b/config/charts/inferencepool/templates/leader-election-rbac.yaml
new file mode 100644
index 0000000..923bdd6
--- /dev/null
+++ b/config/charts/inferencepool/templates/leader-election-rbac.yaml
@@ -0,0 +1,30 @@
+{{- if .Values.inferenceExtension.enableLeaderElection }}
+---
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: {{ include "gateway-api-inference-extension.name" . }}-leader-election
+  namespace: {{ .Release.Namespace }}
+  labels:
+    {{- include "gateway-api-inference-extension.labels" . | nindent 4 }}
+rules:
+- apiGroups: [ "coordination.k8s.io" ]
+  resources: [ "leases" ]
+  verbs: [ "get", "list", "watch", "create", "update", "patch", "delete" ]
+- apiGroups: [ "" ]
+  resources: [ "events" ]
+  verbs: [ "create", "patch" ]
+---
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: {{ include "gateway-api-inference-extension.name" . }}-leader-election-binding
+  namespace: {{ .Release.Namespace }}
+subjects:
+- kind: ServiceAccount
+  name: {{ include "gateway-api-inference-extension.name" . }}
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: {{ include "gateway-api-inference-extension.name" . }}-leader-election
+{{- end }}
diff --git a/config/charts/inferencepool/values.yaml b/config/charts/inferencepool/values.yaml
index 2c295c1..443085e 100644
--- a/config/charts/inferencepool/values.yaml
+++ b/config/charts/inferencepool/values.yaml
@@ -7,12 +7,13 @@ inferenceExtension:
     pullPolicy: Always
   extProcPort: 9002
   env: []
-  enablePprof: true # Enable pprof handlers for profiling and debugging
-  modelServerMetricsPath: "/metrics"
-  modelServerMetricsScheme: "http"
-  modelServerMetricsHttpsInsecureSkipVerify: true
-  # This is the plugins configuration file. 
   pluginsConfigFile: "default-plugins.yaml"
+  # Define additional container ports
+  extraContainerPorts: []
+  # Define additional service ports
+  extraServicePorts: []
+
+  # This is the plugins configuration file.
   # pluginsCustomConfig:
   #   custom-plugins.yaml: |
   #     apiVersion: inference.networking.x-k8s.io/v1alpha1
@@ -28,19 +29,20 @@ inferenceExtension:
 
   # Example environment variables:
   # env:
-  #   KV_CACHE_SCORE_WEIGHT: "1"
+  #   ENABLE_EXPERIMENTAL_FEATURE: "true"
 
-  # Define additional container ports
-  extraContainerPorts: []
-  # Define additional service ports
-  extraServicePorts: []
+  flags:
+    # Log verbosity
+    - name: v
+      value: 1
 
 inferencePool:
-  targetPortNumber: 8000
+  targetPorts:
+    - number: 8000
   modelServerType: vllm # vllm, triton-tensorrt-llm
-  # modelServers: # REQUIRED
-    # matchLabels: 
-    #   app: vllm-llama3-8b-instruct
+  modelServers:
+    matchLabels:
+      app: vllm-llama3-8b-instruct
 
 provider:
   name: none
diff --git a/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml b/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml
index 78e3a9c..66cbcec 100644
--- a/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml
+++ b/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml
@@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    api-approved.kubernetes.io: unapproved, experimental-only
+    api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api-inference-extension/pull/1173
     inference.networking.k8s.io/bundle-version: main-dev
   creationTimestamp: null
   name: inferencepools.inference.networking.k8s.io
@@ -12,6 +12,8 @@ spec:
     kind: InferencePool
     listKind: InferencePoolList
     plural: inferencepools
+    shortNames:
+    - infpool
     singular: inferencepool
   scope: Namespaced
   versions:
@@ -39,17 +41,18 @@ spec:
           metadata:
             type: object
           spec:
-            description: InferencePoolSpec defines the desired state of InferencePool
+            description: Spec defines the desired state of the InferencePool.
             properties:
-              extensionRef:
-                description: Extension configures an endpoint picker as an extension
-                  service.
+              endpointPickerRef:
+                description: |-
+                  EndpointPickerRef is a reference to the Endpoint Picker extension and its
+                  associated configuration.
                 properties:
                   failureMode:
                     default: FailClose
                     description: |-
-                      Configures how the gateway handles the case when the extension is not responsive.
-                      Defaults to failClose.
+                      FailureMode configures how the parent handles the case when the Endpoint Picker extension
+                      is non-responsive. When unspecified, defaults to "FailClose".
                     enum:
                     - FailOpen
                     - FailClose
@@ -57,9 +60,10 @@ spec:
                   group:
                     default: ""
                     description: |-
-                      Group is the group of the referent.
-                      The default value is "", representing the Core API group.
+                      Group is the group of the referent API object. When unspecified, the default value
+                      is "", representing the Core API group.
                     maxLength: 253
+                    minLength: 0
                     pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
                     type: string
                   kind:
@@ -67,6 +71,8 @@ spec:
                     description: |-
                       Kind is the Kubernetes resource kind of the referent.
 
+                      Required if the referent is ambiguous, e.g. service with multiple ports.
+
                       Defaults to "Service" when not specified.
 
                       ExternalName services can refer to CNAME DNS records that may live
@@ -79,15 +85,15 @@ spec:
                     pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
                     type: string
                   name:
-                    description: Name is the name of the referent.
+                    description: Name is the name of the referent API object.
                     maxLength: 253
                     minLength: 1
                     type: string
                   portNumber:
                     description: |-
-                      The port number on the service running the extension. When unspecified,
-                      implementations SHOULD infer a default value of 9002 when the Kind is
-                      Service.
+                      PortNumber is the port number of the Endpoint Picker extension service. When unspecified,
+                      implementations SHOULD infer a default value of 9002 when the kind field is "Service" or
+                      unspecified (defaults to "Service").
                     format: int32
                     maximum: 65535
                     minimum: 1
@@ -96,86 +102,94 @@ spec:
                 - name
                 type: object
               selector:
-                additionalProperties:
-                  description: |-
-                    LabelValue is the value of a label. This is used for validation
-                    of maps. This matches the Kubernetes label validation rules:
-                    * must be 63 characters or less (can be empty),
-                    * unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]),
-                    * could contain dashes (-), underscores (_), dots (.), and alphanumerics between.
+                description: |-
+                  Selector determines which Pods are members of this inference pool.
+                  It matches Pods by their labels only within the same namespace; cross-namespace
+                  selection is not supported.
+
+                  The structure of this LabelSelector is intentionally simple to be compatible
+                  with Kubernetes Service selectors, as some implementations may translate
+                  this configuration into a Service resource.
+                properties:
+                  matchLabels:
+                    additionalProperties:
+                      description: |-
+                        LabelValue is the value of a label. This is used for validation
+                        of maps. This matches the Kubernetes label validation rules:
+                        * must be 63 characters or less (can be empty),
+                        * unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]),
+                        * could contain dashes (-), underscores (_), dots (.), and alphanumerics between.
 
-                    Valid values include:
+                        Valid values include:
 
-                    * MyValue
-                    * my.name
-                    * 123-my-value
-                  maxLength: 63
-                  minLength: 0
-                  pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$
-                  type: string
-                description: |-
-                  Selector defines a map of labels to watch model server Pods
-                  that should be included in the InferencePool.
-                  In some cases, implementations may translate this field to a Service selector, so this matches the simple
-                  map used for Service selectors instead of the full Kubernetes LabelSelector type.
-                  If specified, it will be applied to match the model server pods in the same namespace as the InferencePool.
-                  Cross namesoace selector is not supported.
+                        * MyValue
+                        * my.name
+                        * 123-my-value
+                      maxLength: 63
+                      minLength: 0
+                      pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$
+                      type: string
+                    description: |-
+                      MatchLabels contains a set of required {key,value} pairs.
+                      An object must match every label in this map to be selected.
+                      The matching logic is an AND operation on all entries.
+                    type: object
+                required:
+                - matchLabels
                 type: object
-              targetPortNumber:
+              targetPorts:
                 description: |-
-                  TargetPortNumber defines the port number to access the selected model server Pods.
-                  The number must be in the range 1 to 65535.
-                format: int32
-                maximum: 65535
-                minimum: 1
-                type: integer
+                  TargetPorts defines a list of ports that are exposed by this InferencePool.
+                  Currently, the list may only include a single port definition.
+                items:
+                  description: Port defines the network port that will be exposed
+                    by this InferencePool.
+                  properties:
+                    number:
+                      description: |-
+                        Number defines the port number to access the selected model server Pods.
+                        The number must be in the range 1 to 65535.
+                      format: int32
+                      maximum: 65535
+                      minimum: 1
+                      type: integer
+                  required:
+                  - number
+                  type: object
+                maxItems: 1
+                minItems: 1
+                type: array
+                x-kubernetes-list-type: atomic
             required:
-            - extensionRef
+            - endpointPickerRef
             - selector
-            - targetPortNumber
+            - targetPorts
             type: object
           status:
-            default:
-              parent:
-              - conditions:
-                - lastTransitionTime: "1970-01-01T00:00:00Z"
-                  message: Waiting for controller
-                  reason: Pending
-                  status: Unknown
-                  type: Accepted
-                parentRef:
-                  kind: Status
-                  name: default
-            description: Status defines the observed state of InferencePool.
+            description: Status defines the observed state of the InferencePool.
             properties:
-              parent:
+              parents:
                 description: |-
-                  Parents is a list of parent resources (usually Gateways) that are
-                  associated with the InferencePool, and the status of the InferencePool with respect to
-                  each parent.
+                  Parents is a list of parent resources, typically Gateways, that are associated with
+                  the InferencePool, and the status of the InferencePool with respect to each parent.
 
-                  A maximum of 32 Gateways will be represented in this list. When the list contains
-                  `kind: Status, name: default`, it indicates that the InferencePool is not
-                  associated with any Gateway and a controller must perform the following:
+                  A controller that manages the InferencePool, must add an entry for each parent it manages
+                  and remove the parent entry when the controller no longer considers the InferencePool to
+                  be associated with that parent.
 
-                   - Remove the parent when setting the "Accepted" condition.
-                   - Add the parent when the controller will no longer manage the InferencePool
-                     and no other parents exist.
+                  A maximum of 32 parents will be represented in this list. When the list is empty,
+                  it indicates that the InferencePool is not associated with any parents.
                 items:
-                  description: PoolStatus defines the observed state of InferencePool
-                    from a Gateway.
+                  description: ParentStatus defines the observed state of InferencePool
+                    from a Parent, i.e. Gateway.
                   properties:
                     conditions:
-                      default:
-                      - lastTransitionTime: "1970-01-01T00:00:00Z"
-                        message: Waiting for controller
-                        reason: Pending
-                        status: Unknown
-                        type: Accepted
                       description: |-
-                        Conditions track the state of the InferencePool.
+                        Conditions is a list of status conditions that provide information about the observed
+                        state of the InferencePool. This field is required to be set by the controller that
+                        manages the InferencePool.
 
-                        Known condition types are:
+                        Supported condition types are:
 
                         * "Accepted"
                         * "ResolvedRefs"
@@ -240,32 +254,43 @@ spec:
                       - type
                       x-kubernetes-list-type: map
                     parentRef:
-                      description: GatewayRef indicates the gateway that observed
-                        state of InferencePool.
+                      description: |-
+                        ParentRef is used to identify the parent resource that this status
+                        is associated with. It is used to match the InferencePool with the parent
+                        resource, such as a Gateway.
                       properties:
                         group:
                           default: gateway.networking.k8s.io
-                          description: Group is the group of the referent.
+                          description: |-
+                            Group is the group of the referent API object. When unspecified, the referent is assumed
+                            to be in the "gateway.networking.k8s.io" API group.
                           maxLength: 253
+                          minLength: 0
                           pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
                           type: string
                         kind:
                           default: Gateway
-                          description: Kind is kind of the referent. For example "Gateway".
+                          description: |-
+                            Kind is the kind of the referent API object. When unspecified, the referent is assumed
+                            to be a "Gateway" kind.
                           maxLength: 63
                           minLength: 1
                           pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
                           type: string
                         name:
-                          description: Name is the name of the referent.
+                          description: Name is the name of the referent API object.
                           maxLength: 253
                           minLength: 1
                           type: string
                         namespace:
                           description: |-
-                            Namespace is the namespace of the referent.  If not present,
-                            the namespace of the referent is assumed to be the same as
-                            the namespace of the referring object.
+                            Namespace is the namespace of the referenced object. When unspecified, the local
+                            namespace is inferred.
+
+                            Note that when a namespace different than the local namespace is specified,
+                            a ReferenceGrant object is required in the referent namespace to allow that
+                            namespace's owner to accept the reference. See the ReferenceGrant
+                            documentation for details: https://gateway-api.sigs.k8s.io/api-types/referencegrant/
                           maxLength: 63
                           minLength: 1
                           pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
@@ -278,7 +303,10 @@ spec:
                   type: object
                 maxItems: 32
                 type: array
+                x-kubernetes-list-type: atomic
             type: object
+        required:
+        - spec
         type: object
     served: true
     storage: true
diff --git a/config/crd/bases/inference.networking.x-k8s.io_inferenceobjectives.yaml b/config/crd/bases/inference.networking.x-k8s.io_inferenceobjectives.yaml
index 1c86d0a..27ad4e5 100644
--- a/config/crd/bases/inference.networking.x-k8s.io_inferenceobjectives.yaml
+++ b/config/crd/bases/inference.networking.x-k8s.io_inferenceobjectives.yaml
@@ -21,8 +21,8 @@ spec:
     - jsonPath: .spec.poolRef.name
       name: Inference Pool
       type: string
-    - jsonPath: .spec.criticality
-      name: Criticality
+    - jsonPath: .spec.priority
+      name: Priority
       type: string
     - jsonPath: .metadata.creationTimestamp
       name: Age
@@ -68,27 +68,12 @@ spec:
               creation timestamp, will be selected to remain valid. In the event of a race
               condition, one will be selected at random.
             properties:
-              criticality:
-                description: |-
-                  Criticality defines how important it is to serve the model compared to other models referencing the same pool.
-                  Criticality impacts how traffic is handled in resource constrained situations. It handles this by
-                  queuing or rejecting requests of lower criticality. InferenceObjectives of an equivalent Criticality will
-                  fairly share resources over throughput of tokens. In the future, the metric used to calculate fairness,
-                  and the proportionality of fairness will be configurable.
-
-                  Default values for this field will not be set, to allow for future additions of new field that may 'one of' with this field.
-                  Any implementations that may consume this field may treat an unset value as the 'Standard' range.
-                enum:
-                - Critical
-                - Standard
-                - Sheddable
-                type: string
               poolRef:
                 description: PoolRef is a reference to the inference pool, the pool
                   must exist in the same namespace.
                 properties:
                   group:
-                    default: inference.networking.x-k8s.io
+                    default: inference.networking.k8s.io
                     description: Group is the group of the referent.
                     maxLength: 253
                     pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
@@ -108,52 +93,21 @@ spec:
                 required:
                 - name
                 type: object
-              targetModels:
+              priority:
                 description: |-
-                  TargetModels allow multiple versions of a model for traffic splitting.
-                  If not specified, the target model name is defaulted to the modelName parameter.
-                  modelName is often in reference to a LoRA adapter.
-                items:
-                  description: |-
-                    TargetModel represents a deployed model or a LoRA adapter. The
-                    Name field is expected to match the name of the LoRA adapter
-                    (or base model) as it is registered within the model server. Inference
-                    Gateway assumes that the model exists on the model server and it's the
-                    responsibility of the user to validate a correct match. Should a model fail
-                    to exist at request time, the error is processed by the Inference Gateway
-                    and emitted on the appropriate InferenceObjective object.
-                  properties:
-                    name:
-                      description: Name is the name of the adapter or base model,
-                        as expected by the ModelServer.
-                      maxLength: 253
-                      type: string
-                    weight:
-                      description: |-
-                        Weight is used to determine the proportion of traffic that should be
-                        sent to this model when multiple target models are specified.
+                  Priority defines how important it is to serve the request compared to other requests in the same pool.
+                  Priority is an integer value that defines the priority of the request.
+                  The higher the value, the more critical the request is; negative values _are_ allowed.
+                  No default value is set for this field, allowing for future additions of new fields that may 'one of' with this field.
+                  However, implementations that consume this field (such as the Endpoint Picker) will treat an unset value as '0'.
+                  Priority is used in flow control, primarily in the event of resource scarcity(requests need to be queued).
+                  All requests will be queued, and flow control will _always_ allow requests of higher priority to be served first.
+                  Fairness is only enforced and tracked between requests of the same priority.
 
-                        Weight defines the proportion of requests forwarded to the specified
-                        model. This is computed as weight/(sum of all weights in this
-                        TargetModels list). For non-zero values, there may be some epsilon from
-                        the exact proportion defined here depending on the precision an
-                        implementation supports. Weight is not a percentage and the sum of
-                        weights does not need to equal 100.
-
-                        If a weight is set for any targetModel, it must be set for all targetModels.
-                        Conversely weights are optional, so long as ALL targetModels do not specify a weight.
-                      format: int32
-                      maximum: 1000000
-                      minimum: 1
-                      type: integer
-                  required:
-                  - name
-                  type: object
-                maxItems: 10
-                type: array
-                x-kubernetes-validations:
-                - message: Weights should be set for all models, or none of the models.
-                  rule: self.all(model, has(model.weight)) || self.all(model, !has(model.weight))
+                  Example: requests with Priority 10 will always be served before
+                  requests with Priority of 0 (the value used if Priority is unset or no InfereneceObjective is specified).
+                  Similarly requests with a Priority of -10 will always be served after requests with Priority of 0.
+                type: integer
             required:
             - poolRef
             type: object
diff --git a/config/crd/bases/inference.networking.x-k8s.io_inferencepools.yaml b/config/crd/bases/inference.networking.x-k8s.io_inferencepools.yaml
index 138734e..ef73c4a 100644
--- a/config/crd/bases/inference.networking.x-k8s.io_inferencepools.yaml
+++ b/config/crd/bases/inference.networking.x-k8s.io_inferencepools.yaml
@@ -12,14 +12,15 @@ spec:
     kind: InferencePool
     listKind: InferencePoolList
     plural: inferencepools
+    shortNames:
+    - xinfpool
     singular: inferencepool
   scope: Namespaced
   versions:
   - name: v1alpha2
     schema:
       openAPIV3Schema:
-        description: |
-          InferencePool is the Schema for the InferencePools API.
+        description: InferencePool is the Schema for the InferencePools API.
         properties:
           apiVersion:
             description: |-
diff --git a/config/manifests/gateway/agentgateway/gateway.yaml b/config/manifests/gateway/agentgateway/gateway.yaml
new file mode 100644
index 0000000..9407cc7
--- /dev/null
+++ b/config/manifests/gateway/agentgateway/gateway.yaml
@@ -0,0 +1,10 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: inference-gateway
+spec:
+  gatewayClassName: agentgateway
+  listeners:
+  - name: http
+    port: 80
+    protocol: HTTP
diff --git a/config/manifests/gateway/agentgateway/httproute.yaml b/config/manifests/gateway/agentgateway/httproute.yaml
new file mode 100644
index 0000000..18e90ce
--- /dev/null
+++ b/config/manifests/gateway/agentgateway/httproute.yaml
@@ -0,0 +1,20 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: llm-route
+spec:
+  parentRefs:
+  - group: gateway.networking.k8s.io
+    kind: Gateway
+    name: inference-gateway
+  rules:
+  - backendRefs:
+    - group: inference.networking.x-k8s.io
+      kind: InferencePool
+      name: vllm-llama3-8b-instruct
+    matches:
+    - path:
+        type: PathPrefix
+        value: /
+    timeouts:
+      request: 300s
diff --git a/config/manifests/inferenceobjective.yaml b/config/manifests/inferenceobjective.yaml
index f4a5de1..3b7ba91 100644
--- a/config/manifests/inferenceobjective.yaml
+++ b/config/manifests/inferenceobjective.yaml
@@ -3,20 +3,19 @@ kind: InferenceObjective
 metadata:
   name: food-review
 spec:
-  criticality: Standard
+  priority: 1
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: food-review-1
-    weight: 100
 ---
 apiVersion: inference.networking.x-k8s.io/v1alpha2
 kind: InferenceObjective
 metadata:
   name: base-model
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
 ---
 apiVersion: inference.networking.x-k8s.io/v1alpha2
@@ -24,6 +23,7 @@ kind: InferenceObjective
 metadata:
   name: base-model-cpu
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
diff --git a/config/manifests/inferencepool-resources.yaml b/config/manifests/inferencepool-resources.yaml
index c8a7fc7..7c31da9 100644
--- a/config/manifests/inferencepool-resources.yaml
+++ b/config/manifests/inferencepool-resources.yaml
@@ -8,10 +8,12 @@ kind: InferencePool
 metadata:
   name: vllm-llama3-8b-instruct
 spec:
-  targetPortNumber: 8000
+  targetPorts:
+    - number: 8000
   selector:
-    app: vllm-llama3-8b-instruct
-  extensionRef:
+    matchLabels:
+      app: vllm-llama3-8b-instruct
+  endpointPickerRef:
     name: vllm-llama3-8b-instruct-epp
 ---
 apiVersion: v1
diff --git a/config/manifests/regression-testing/inferenceobjective.yaml b/config/manifests/regression-testing/inferenceobjective.yaml
index 182383c..eede557 100644
--- a/config/manifests/regression-testing/inferenceobjective.yaml
+++ b/config/manifests/regression-testing/inferenceobjective.yaml
@@ -3,12 +3,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-0
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-0
-    weight: 100
 
 ---
 
@@ -17,12 +15,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-1
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-1
-    weight: 100
 
 ---
 
@@ -31,12 +27,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-2
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-2
-    weight: 100
 
 ---
 
@@ -45,12 +39,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-3
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-3
-    weight: 100
 
 ---
 
@@ -59,12 +51,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-4
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-4
-    weight: 100
 
 ---
 
@@ -73,12 +63,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-5
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-5
-    weight: 100
 
 ---
 
@@ -87,12 +75,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-6
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-6
-    weight: 100
 
 ---
 
@@ -101,12 +87,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-7
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-7
-    weight: 100
 
 ---
 
@@ -115,12 +99,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-8
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-8
-    weight: 100
 
 ---
 
@@ -129,12 +111,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-9
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-9
-    weight: 100
 
 ---
 
@@ -143,12 +123,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-10
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-10
-    weight: 100
 
 ---
 
@@ -157,12 +135,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-11
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-11
-    weight: 100
 
 ---
 
@@ -171,12 +147,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-12
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-12
-    weight: 100
 
 
 ---
@@ -186,12 +160,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-13
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-13
-    weight: 100
 
 
 ---
@@ -201,12 +173,10 @@ kind: InferenceObjective
 metadata:
   name: adapter-14
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
+    group: inference.networking.k8s.io
     name: vllm-llama3-8b-instruct
-  targetModels:
-  - name: adapter-14
-    weight: 100
 
 ---
 
@@ -216,6 +186,7 @@ kind: InferenceObjective
 metadata:
   name: base-model
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
-    name: vllm-llama3-8b-instruct
\ No newline at end of file
+    group: inference.networking.k8s.io
+    name: vllm-llama3-8b-instruct
diff --git a/config/observability/prometheus/rbac.yaml b/config/observability/prometheus/rbac.yaml
new file mode 100644
index 0000000..9fad217
--- /dev/null
+++ b/config/observability/prometheus/rbac.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: inference-gateway-sa-metrics-reader
+  namespace: monitoring
diff --git a/config/observability/prometheus/values.yaml b/config/observability/prometheus/values.yaml
new file mode 100644
index 0000000..780b889
--- /dev/null
+++ b/config/observability/prometheus/values.yaml
@@ -0,0 +1,27 @@
+serviceAccounts:
+  server:
+    create: false
+    name: inference-gateway-sa-metrics-reader
+
+extraScrapeConfigs: |
+  - job_name: 'inference-extension-epp'
+    authorization:
+        credentials_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+    scrape_interval: 5s   
+    kubernetes_sd_configs:
+      - role: endpoints
+    relabel_configs:
+      - source_labels: [__meta_kubernetes_service_name]
+        action: keep
+        regex: .*-epp$
+      - source_labels: [__meta_kubernetes_pod_container_port_number]
+        action: keep
+        regex: "9090"
+  - job_name: vllm
+    scrape_interval: 5s   
+    kubernetes_sd_configs:
+      - role: pod
+    relabel_configs:
+      - source_labels: [__meta_kubernetes_pod_label_app]
+        action: keep
+        regex: vllm-llama3-8b-instruct
diff --git a/conformance/reports/v0.5.1/gateway/agentgateway/README.md b/conformance/reports/v0.5.1/gateway/agentgateway/README.md
new file mode 100644
index 0000000..13bb2a2
--- /dev/null
+++ b/conformance/reports/v0.5.1/gateway/agentgateway/README.md
@@ -0,0 +1,11 @@
+# Agentgateway (with kgateway)
+
+## Table of Contents
+
+| Extension Version Tested | Profile Tested | Implementation Version | Mode    | Report                                                                     |
+|--------------------------|----------------|------------------------|---------|----------------------------------------------------------------------------|
+| v0.5.1                   | Gateway        | v0.7.2                 | default | [v0.7.2 report](./inference-v0.7.2-report.yaml)   |
+
+## Reproduce
+
+From the [kgateway repository](https://github.com/kgateway-dev/kgateway/): `CONFORMANCE_GATEWAY_CLASS=agentgateway make gie-conformance`.
diff --git a/conformance/reports/v0.5.1/gateway/agentgateway/inference-v0.7.2-report.yaml b/conformance/reports/v0.5.1/gateway/agentgateway/inference-v0.7.2-report.yaml
new file mode 100644
index 0000000..0f1078e
--- /dev/null
+++ b/conformance/reports/v0.5.1/gateway/agentgateway/inference-v0.7.2-report.yaml
@@ -0,0 +1,23 @@
+GatewayAPIInferenceExtensionVersion: v0.5.1
+apiVersion: gateway.networking.k8s.io/v1
+date: "2025-08-06T17:50:20-07:00"
+gatewayAPIChannel: experimental
+gatewayAPIVersion: v1.3.0
+implementation:
+  contact:
+  - github.com/agentgateway/agentgateway/issues/new/choose
+  organization: agentgateway
+  project: agentgateway
+  url: http://agentgateway.dev/
+  version: v0.7.2
+kind: ConformanceReport
+mode: default
+profiles:
+- core:
+    result: success
+    statistics:
+      Failed: 0
+      Passed: 9
+      Skipped: 0
+  name: Gateway
+  summary: Core tests succeeded.
diff --git a/conformance/resources/base.yaml b/conformance/resources/base.yaml
index fac7ead..4fd3b38 100644
--- a/conformance/resources/base.yaml
+++ b/conformance/resources/base.yaml
@@ -153,9 +153,11 @@ metadata:
   namespace: gateway-conformance-app-backend
 spec:
   selector:
-    app: primary-inference-model-server
-  targetPortNumber: 3000
-  extensionRef:
+    matchLabels:
+      app: primary-inference-model-server
+  targetPorts:
+    - number: 3000
+  endpointPickerRef:
     name: primary-endpoint-picker-svc
 ---
 # --- Primary Conformance EPP service Definition ---
@@ -246,9 +248,11 @@ metadata:
   namespace: gateway-conformance-app-backend
 spec:
   selector:
-    app: secondary-inference-model-server
-  targetPortNumber: 3000
-  extensionRef:
+    matchLabels:
+      app: secondary-inference-model-server
+  targetPorts:
+    - number: 3000
+  endpointPickerRef:
     name: secondary-endpoint-picker-svc
     failureMode: FailOpen
 ---
diff --git a/conformance/resources/resourcename.go b/conformance/resources/resourcename.go
index 4c0c7b4..6f23840 100644
--- a/conformance/resources/resourcename.go
+++ b/conformance/resources/resourcename.go
@@ -34,6 +34,8 @@ const (
 
 	ModelServerPodReplicas    = 3
 	EndPointPickerPodReplicas = 1
+
+	InferenceObjName = "conformance-fake-model-server"
 )
 
 var (
diff --git a/conformance/utils/kubernetes/helpers.go b/conformance/utils/kubernetes/helpers.go
index 54d0260..fe0168a 100644
--- a/conformance/utils/kubernetes/helpers.go
+++ b/conformance/utils/kubernetes/helpers.go
@@ -32,6 +32,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
+	"k8s.io/utils/ptr"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
 	gatewayapiconfig "sigs.k8s.io/gateway-api/conformance/utils/config"
@@ -105,7 +106,9 @@ func InferencePoolMustHaveCondition(t *testing.T, c client.Reader, poolNN types.
 			}
 
 			for _, parentStatus := range pool.Status.Parents {
-				if parentStatus.GatewayRef.Namespace != nil && string(*parentStatus.GatewayRef.Namespace) == gateway.Namespace && string(parentStatus.GatewayRef.Name) == gateway.Name {
+				if parentStatus.ParentRef.Namespace != nil &&
+					string(*parentStatus.ParentRef.Namespace) == gateway.Namespace &&
+					string(parentStatus.ParentRef.Name) == gateway.Name {
 					if checkCondition(t, parentStatus.Conditions, expectedCondition) {
 						conditionFound = true
 						return true, nil
@@ -130,8 +133,11 @@ func InferencePoolMustHaveCondition(t *testing.T, c client.Reader, poolNN types.
 				debugMsg += " (No parent statuses reported)"
 			}
 			for i, parentStatus := range lastObservedPool.Status.Parents {
-				namespace := parentStatus.GatewayRef.Namespace
-				debugMsg += fmt.Sprintf("\n  Parent %d (Gateway: %s/%s):", i, *namespace, parentStatus.GatewayRef.Name)
+				namespace := parentStatus.ParentRef.Namespace
+				if namespace == nil {
+					namespace = ptr.To(inferenceapi.Namespace(poolNN.Namespace)) // Fallback to the pool's namespace
+				}
+				debugMsg += fmt.Sprintf("\n  Parent %d (Gateway: %s/%s):", i, *namespace, parentStatus.ParentRef.Name)
 				if len(parentStatus.Conditions) == 0 {
 					debugMsg += " (No conditions reported for this parent)"
 				}
diff --git a/docs/proposals/003-model-server-protocol/README.md b/docs/proposals/003-model-server-protocol/README.md
index e6c7cf3..4b27743 100644
--- a/docs/proposals/003-model-server-protocol/README.md
+++ b/docs/proposals/003-model-server-protocol/README.md
@@ -21,10 +21,10 @@ effort.
 The corresponding metrics in vLLM are also shown in the table below, as vLLM is already integrated
 into the reference endpoint picker implementation.
 
-| Metric | Type | Description | vLLM metric | Triton TensorRT-LLM|
-| ----- | ---- | ---- | ---- | ---- |
-| TotalQueuedRequests         | Gauge     | The current total number of requests in the queue.| `vllm:num_requests_waiting`| `nv_trt_llm_request_metrics{request_type=waiting}`|
-| KVCacheUtilization| Gauge     | The current KV cache utilization in percentage.| `vllm:gpu_cache_usage_perc`| `nv_trt_llm_kv_cache_block_metrics{kv_cache_block_type=fraction}`|
+| Metric | Type | Description | vLLM metric | Triton TensorRT-LLM| SGLang |
+| ----- | ---- | ---- | ---- | ---- | ---- |
+| TotalQueuedRequests         | Gauge     | The current total number of requests in the queue.| `vllm:num_requests_waiting`| `nv_trt_llm_request_metrics{request_type=waiting}`| `sglang:num_queue_reqs`
+| KVCacheUtilization| Gauge     | The current KV cache utilization in percentage.| `vllm:gpu_cache_usage_perc`| `nv_trt_llm_kv_cache_block_metrics{kv_cache_block_type=fraction}`| `sglang:token_usage`
 
 
 ### LoRA Adapter Serving
diff --git a/docs/proposals/004-endpoint-picker-protocol/README.md b/docs/proposals/004-endpoint-picker-protocol/README.md
index c12af20..18cb10d 100644
--- a/docs/proposals/004-endpoint-picker-protocol/README.md
+++ b/docs/proposals/004-endpoint-picker-protocol/README.md
@@ -82,6 +82,3 @@ This metadata is required because the EPP provides a list of endpoints to the da
 
 ### Why envoy.lb namespace as a default?
 The `envoy.lb` namespace is a predefined namespace. One common way to use the selected endpoint returned from the server, is [envoy subsets](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/subsets)  where host metadata for subset load balancing must be placed under `envoy.lb`. Note that this is not related to the subsetting feature discussed above, this is an enovy implementation detail.
-
-## Matching An InferenceModel
-The model name of a request MUST match the `Spec.ModelName` parameter of one of the `InferenceModels` referencing the `InferencePool` managed by the EPP. Otherwise, the EPP MUST return a 404 status code.
diff --git a/go.mod b/go.mod
index 181e11b..f43559a 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
 	github.com/google/go-cmp v0.7.0
 	github.com/google/uuid v1.6.0
 	github.com/hashicorp/golang-lru/v2 v2.0.7
-	github.com/onsi/ginkgo/v2 v2.23.4
+	github.com/onsi/ginkgo/v2 v2.24.0
 	github.com/onsi/gomega v1.38.0
 	github.com/prometheus/client_golang v1.23.0
 	github.com/prometheus/client_model v0.6.2
@@ -21,13 +21,13 @@ require (
 	go.uber.org/zap v1.27.0
 	golang.org/x/sync v0.16.0
 	google.golang.org/grpc v1.74.2
-	google.golang.org/protobuf v1.36.6
-	k8s.io/api v0.33.3
-	k8s.io/apiextensions-apiserver v0.33.3
-	k8s.io/apimachinery v0.33.3
-	k8s.io/client-go v0.33.3
-	k8s.io/code-generator v0.33.3
-	k8s.io/component-base v0.33.3
+	google.golang.org/protobuf v1.36.7
+	k8s.io/api v0.33.4
+	k8s.io/apiextensions-apiserver v0.33.4
+	k8s.io/apimachinery v0.33.4
+	k8s.io/client-go v0.33.4
+	k8s.io/code-generator v0.33.4
+	k8s.io/component-base v0.33.4
 	k8s.io/utils v0.0.0-20241210054802-24370beab758
 	sigs.k8s.io/controller-runtime v0.21.0
 	sigs.k8s.io/controller-tools v0.18.0
@@ -40,6 +40,7 @@ require (
 	cel.dev/expr v0.24.0 // indirect
 	github.com/Masterminds/goutils v1.1.1 // indirect
 	github.com/Masterminds/semver v1.5.0 // indirect
+	github.com/Masterminds/semver/v3 v3.3.1 // indirect
 	github.com/Masterminds/sprig v2.22.0+incompatible // indirect
 	github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
@@ -104,16 +105,16 @@ require (
 	go.uber.org/atomic v1.11.0 // indirect
 	go.uber.org/automaxprocs v1.6.0 // indirect
 	go.yaml.in/yaml/v2 v2.4.2 // indirect
-	golang.org/x/crypto v0.40.0 // indirect
+	golang.org/x/crypto v0.41.0 // indirect
 	golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
-	golang.org/x/mod v0.26.0 // indirect
-	golang.org/x/net v0.42.0 // indirect
+	golang.org/x/mod v0.27.0 // indirect
+	golang.org/x/net v0.43.0 // indirect
 	golang.org/x/oauth2 v0.30.0 // indirect
-	golang.org/x/sys v0.34.0 // indirect
-	golang.org/x/term v0.33.0 // indirect
-	golang.org/x/text v0.27.0 // indirect
+	golang.org/x/sys v0.35.0 // indirect
+	golang.org/x/term v0.34.0 // indirect
+	golang.org/x/text v0.28.0 // indirect
 	golang.org/x/time v0.12.0 // indirect
-	golang.org/x/tools v0.35.0 // indirect
+	golang.org/x/tools v0.36.0 // indirect
 	golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
@@ -122,7 +123,7 @@ require (
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	k8s.io/apiserver v0.33.3 // indirect
+	k8s.io/apiserver v0.33.4 // indirect
 	k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 // indirect
 	k8s.io/klog/v2 v2.130.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
diff --git a/go.sum b/go.sum
index 0a323e0..5a2d94c 100644
--- a/go.sum
+++ b/go.sum
@@ -18,6 +18,8 @@ github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJ
 github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
 github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
 github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
+github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
+github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
 github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
 github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
 github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=
@@ -194,8 +196,8 @@ github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
 github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
-github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
+github.com/onsi/ginkgo/v2 v2.24.0 h1:obZz8LAnHicNdbBqvG3ytAFx8fgza+i1IDpBVcHT2YE=
+github.com/onsi/ginkgo/v2 v2.24.0/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk=
 github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY=
 github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o=
 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
@@ -282,20 +284,20 @@ go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
-golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
+golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
+golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
-golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
+golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
+golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
-golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
 golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
 golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -306,22 +308,22 @@ golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
-golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
+golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
 golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
-golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
+golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
+golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
 golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
 golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
 golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
@@ -340,8 +342,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
 google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
+google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -356,20 +358,20 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8=
-k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE=
-k8s.io/apiextensions-apiserver v0.33.3 h1:qmOcAHN6DjfD0v9kxL5udB27SRP6SG/MTopmge3MwEs=
-k8s.io/apiextensions-apiserver v0.33.3/go.mod h1:oROuctgo27mUsyp9+Obahos6CWcMISSAPzQ77CAQGz8=
-k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
-k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
-k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4=
-k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E=
-k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA=
-k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg=
-k8s.io/code-generator v0.33.3 h1:6+34LhYkIuQ/yn/E3qlpVqjQaP8smzCu4NE1A8b0LWs=
-k8s.io/code-generator v0.33.3/go.mod h1:6Y02+HQJYgNphv9z3wJB5w+sjYDIEBQW7sh62PkufvA=
-k8s.io/component-base v0.33.3 h1:mlAuyJqyPlKZM7FyaoM/LcunZaaY353RXiOd2+B5tGA=
-k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4=
+k8s.io/api v0.33.4 h1:oTzrFVNPXBjMu0IlpA2eDDIU49jsuEorGHB4cvKupkk=
+k8s.io/api v0.33.4/go.mod h1:VHQZ4cuxQ9sCUMESJV5+Fe8bGnqAARZ08tSTdHWfeAc=
+k8s.io/apiextensions-apiserver v0.33.4 h1:rtq5SeXiDbXmSwxsF0MLe2Mtv3SwprA6wp+5qh/CrOU=
+k8s.io/apiextensions-apiserver v0.33.4/go.mod h1:mWXcZQkQV1GQyxeIjYApuqsn/081hhXPZwZ2URuJeSs=
+k8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s=
+k8s.io/apimachinery v0.33.4/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
+k8s.io/apiserver v0.33.4 h1:6N0TEVA6kASUS3owYDIFJjUH6lgN8ogQmzZvaFFj1/Y=
+k8s.io/apiserver v0.33.4/go.mod h1:8ODgXMnOoSPLMUg1aAzMFx+7wTJM+URil+INjbTZCok=
+k8s.io/client-go v0.33.4 h1:TNH+CSu8EmXfitntjUPwaKVPN0AYMbc9F1bBS8/ABpw=
+k8s.io/client-go v0.33.4/go.mod h1:LsA0+hBG2DPwovjd931L/AoaezMPX9CmBgyVyBZmbCY=
+k8s.io/code-generator v0.33.4 h1:DiA801QxqApRIBh3OWULasVAUA237XnYvFNMh+E34Y8=
+k8s.io/code-generator v0.33.4/go.mod h1:ifWxKWhEl/Z1K7WmWAyOBEf3ex/i546ingCzLC8YVIY=
+k8s.io/component-base v0.33.4 h1:Jvb/aw/tl3pfgnJ0E0qPuYLT0NwdYs1VXXYQmSuxJGY=
+k8s.io/component-base v0.33.4/go.mod h1:567TeSdixWW2Xb1yYUQ7qk5Docp2kNznKL87eygY8Rc=
 k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 h1:2OX19X59HxDprNCVrWi6jb7LW1PoqTlYqEq5H2oetog=
 k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
 k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
diff --git a/hack/release-quickstart.sh b/hack/release-quickstart.sh
index 8f25cbd..22c7051 100755
--- a/hack/release-quickstart.sh
+++ b/hack/release-quickstart.sh
@@ -30,12 +30,12 @@ else
 fi
 
 # The vLLM image versions
-# The GPU image is from https://hub.docker.com/layers/vllm/vllm-openai
-VLLM_GPU="${VLLM_GPU:-0.9.1}"
+# The GPU image is from https://hub.docker.com/r/vllm/vllm-openai/tags
+VLLM_GPU="${VLLM_GPU:-0.10.0}"
 # The CPU image is from https://gallery.ecr.aws/q9t5s3a7/vllm-cpu-release-repo
-VLLM_CPU="${VLLM_CPU:-0.9.1}"
+VLLM_CPU="${VLLM_CPU:-0.10.0}"
 # The sim image is from https://github.com/llm-d/llm-d-inference-sim/pkgs/container/llm-d-inference-sim
-VLLM_SIM="${VLLM_SIM:-0.3.0}"
+VLLM_SIM="${VLLM_SIM:-0.3.2-fix}"
 
 echo "Using release tag: ${RELEASE_TAG}"
 echo "Using vLLM GPU image version: ${VLLM_GPU}"
@@ -78,7 +78,7 @@ EPP="config/manifests/inferencepool-resources.yaml"
 #TODO: Put all helm values files into an array to loop over
 EPP_HELM="config/charts/inferencepool/values.yaml"
 BBR_HELM="config/charts/body-based-routing/values.yaml"
-CONFORMANCE_MANIFESTS="conformance/resources/manifests/manifests.yaml"
+CONFORMANCE_MANIFESTS="conformance/resources/base.yaml"
 echo "Updating ${EPP}, ${EPP_HELM}, ${BBR_HELM}, and ${CONFORMANCE_MANIFESTS} ..."
 
 # Update the container tag.
@@ -127,6 +127,15 @@ sed -i.bak -E "s|(llm-d/llm-d-inference-sim:)[^\"[:space:]]+|\1v${VLLM_SIM}|g" "
 # Also change the imagePullPolicy from Always to IfNotPresent on lines containing the vLLM image.
 sed -i.bak '/llm-d\/llm-d-inference-sim/{n;s/Always/IfNotPresent/;}' "$VLLM_SIM_DEPLOY"
 
+# Update the container tag for lora-syncer in vLLM CPU and GPU deployment manifests.
+sed -i.bak -E "s|(us-central1-docker\.pkg\.dev/k8s-staging-images/gateway-api-inference-extension/lora-syncer:)[^\"[:space:]]+|\1${RELEASE_TAG}|g" "$VLLM_GPU_DEPLOY" "$VLLM_CPU_DEPLOY"
+
+# Update the container image pull policy for lora-syncer in vLLM CPU and GPU deployment manifests.
+sed -i.bak '/us-central1-docker.pkg.dev\/k8s-staging-images\/gateway-api-inference-extension\/lora-syncer/{n;s/Always/IfNotPresent/;}' "$VLLM_GPU_DEPLOY" "$VLLM_CPU_DEPLOY"
+
+# Update the container registry for lora-syncer in vLLM CPU and GPU deployment manifests.
+sed -i.bak -E "s|us-central1-docker\.pkg\.dev/k8s-staging-images|registry.k8s.io|g" "$VLLM_GPU_DEPLOY" "$VLLM_CPU_DEPLOY"
+
 # -----------------------------------------------------------------------------
 # Stage the changes
 # -----------------------------------------------------------------------------
diff --git a/pkg/bbr/handlers/request.go b/pkg/bbr/handlers/request.go
index 98cd245..e7baec6 100644
--- a/pkg/bbr/handlers/request.go
+++ b/pkg/bbr/handlers/request.go
@@ -19,7 +19,6 @@ package handlers
 import (
 	"context"
 	"encoding/json"
-	"fmt"
 
 	basepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
 	eppb "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
@@ -31,18 +30,22 @@ import (
 
 const modelHeader = "X-Gateway-Model-Name"
 
+type RequestBody struct {
+	Model string `json:"model"`
+}
+
 // HandleRequestBody handles request bodies.
 func (s *Server) HandleRequestBody(ctx context.Context, requestBodyBytes []byte) ([]*eppb.ProcessingResponse, error) {
 	logger := log.FromContext(ctx)
 	var ret []*eppb.ProcessingResponse
 
-	var requestBody map[string]any
+	var requestBody RequestBody
 	if err := json.Unmarshal(requestBodyBytes, &requestBody); err != nil {
+		metrics.RecordModelNotParsedCounter()
 		return nil, err
 	}
 
-	modelVal, ok := requestBody["model"]
-	if !ok {
+	if requestBody.Model == "" {
 		metrics.RecordModelNotInBodyCounter()
 		logger.V(logutil.DEFAULT).Info("Request body does not contain model parameter")
 		if s.streaming {
@@ -63,13 +66,6 @@ func (s *Server) HandleRequestBody(ctx context.Context, requestBodyBytes []byte)
 		return ret, nil
 	}
 
-	modelStr, ok := modelVal.(string)
-	if !ok {
-		metrics.RecordModelNotParsedCounter()
-		logger.V(logutil.DEFAULT).Info("Model parameter value is not a string")
-		return nil, fmt.Errorf("the model parameter value %v is not a string", modelVal)
-	}
-
 	metrics.RecordSuccessCounter()
 
 	if s.streaming {
@@ -83,7 +79,7 @@ func (s *Server) HandleRequestBody(ctx context.Context, requestBodyBytes []byte)
 								{
 									Header: &basepb.HeaderValue{
 										Key:      modelHeader,
-										RawValue: []byte(modelStr),
+										RawValue: []byte(requestBody.Model),
 									},
 								},
 							},
@@ -108,7 +104,7 @@ func (s *Server) HandleRequestBody(ctx context.Context, requestBodyBytes []byte)
 								{
 									Header: &basepb.HeaderValue{
 										Key:      modelHeader,
-										RawValue: []byte(modelStr),
+										RawValue: []byte(requestBody.Model),
 									},
 								},
 							},
diff --git a/pkg/common/convert.go b/pkg/common/convert.go
deleted file mode 100644
index bb67155..0000000
--- a/pkg/common/convert.go
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-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.
-*/
-
-// Package common defines structs for referring to fully qualified k8s resources.
-package common
-
-import (
-	"fmt"
-
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime"
-
-	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
-	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
-)
-
-func ToUnstructured(obj any) (*unstructured.Unstructured, error) {
-	u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
-	if err != nil {
-		return nil, err
-	}
-	return &unstructured.Unstructured{Object: u}, nil
-}
-
-var ToInferencePool = convert[v1.InferencePool]
-
-var ToXInferencePool = convert[v1alpha2.InferencePool]
-
-func convert[T any](u *unstructured.Unstructured) (*T, error) {
-	var res T
-	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &res); err != nil {
-		return nil, fmt.Errorf("error converting unstructured to T: %v", err)
-	}
-	return &res, nil
-}
diff --git a/pkg/epp/backend/metrics/fake.go b/pkg/epp/backend/metrics/fake.go
index 284a264..4e0687f 100644
--- a/pkg/epp/backend/metrics/fake.go
+++ b/pkg/epp/backend/metrics/fake.go
@@ -20,12 +20,14 @@ import (
 	"context"
 	"fmt"
 	"sync"
+	"time"
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
 	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
 )
 
@@ -42,13 +44,23 @@ func (fpm *FakePodMetrics) String() string {
 func (fpm *FakePodMetrics) GetPod() *backend.Pod {
 	return fpm.Pod
 }
+
 func (fpm *FakePodMetrics) GetMetrics() *MetricsState {
 	return fpm.Metrics
 }
+
 func (fpm *FakePodMetrics) UpdatePod(pod *corev1.Pod) {
 	fpm.Pod = toInternalPod(pod)
 }
-func (fpm *FakePodMetrics) StopRefreshLoop() {} // noop
+
+func (*FakePodMetrics) Put(string, datalayer.Cloneable)        {}
+func (*FakePodMetrics) Get(string) (datalayer.Cloneable, bool) { return nil, false }
+func (*FakePodMetrics) Keys() []string                         { return nil }
+
+func (fpm *FakePodMetrics) UpdateMetrics(updated *MetricsState) {
+	updated.UpdateTime = time.Now()
+	fpm.Metrics = updated
+}
 
 type FakePodMetricsClient struct {
 	errMu sync.RWMutex
diff --git a/pkg/epp/backend/metrics/logger.go b/pkg/epp/backend/metrics/logger.go
index bc34546..69fc404 100644
--- a/pkg/epp/backend/metrics/logger.go
+++ b/pkg/epp/backend/metrics/logger.go
@@ -24,7 +24,7 @@ import (
 	"github.com/go-logr/logr"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 
-	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics"
 	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
 )
@@ -33,15 +33,9 @@ const (
 	debugPrintInterval = 5 * time.Second
 )
 
-type Datastore interface {
-	PoolGet() (*v1.InferencePool, error)
-	// PodMetrics operations
-	PodList(func(PodMetrics) bool) []PodMetrics
-}
-
 // StartMetricsLogger starts goroutines to 1) Print metrics debug logs if the DEBUG log level is
 // enabled; 2) flushes Prometheus metrics about the backend servers.
-func StartMetricsLogger(ctx context.Context, datastore Datastore, refreshPrometheusMetricsInterval, metricsStalenessThreshold time.Duration) {
+func StartMetricsLogger(ctx context.Context, datastore datalayer.PoolInfo, refreshPrometheusMetricsInterval, metricsStalenessThreshold time.Duration) {
 	logger := log.FromContext(ctx)
 	ticker := time.NewTicker(refreshPrometheusMetricsInterval)
 	go func() {
@@ -68,9 +62,7 @@ func StartMetricsLogger(ctx context.Context, datastore Datastore, refreshPrometh
 					logger.V(logutil.DEFAULT).Info("Shutting down metrics logger thread")
 					return
 				case <-ticker.C:
-					podsWithFreshMetrics := datastore.PodList(func(pm PodMetrics) bool {
-						return time.Since(pm.GetMetrics().UpdateTime) <= metricsStalenessThreshold
-					})
+					podsWithFreshMetrics := datastore.PodList(PodsWithFreshMetrics(metricsStalenessThreshold))
 					podsWithStaleMetrics := datastore.PodList(func(pm PodMetrics) bool {
 						return time.Since(pm.GetMetrics().UpdateTime) > metricsStalenessThreshold
 					})
@@ -82,7 +74,7 @@ func StartMetricsLogger(ctx context.Context, datastore Datastore, refreshPrometh
 	}
 }
 
-func refreshPrometheusMetrics(logger logr.Logger, datastore Datastore, metricsStalenessThreshold time.Duration) {
+func refreshPrometheusMetrics(logger logr.Logger, datastore datalayer.PoolInfo, metricsStalenessThreshold time.Duration) {
 	pool, err := datastore.PoolGet()
 	if err != nil {
 		// No inference pool or not initialize.
@@ -93,9 +85,7 @@ func refreshPrometheusMetrics(logger logr.Logger, datastore Datastore, metricsSt
 	var kvCacheTotal float64
 	var queueTotal int
 
-	podMetrics := datastore.PodList(func(pm PodMetrics) bool {
-		return time.Since(pm.GetMetrics().UpdateTime) <= metricsStalenessThreshold
-	})
+	podMetrics := datastore.PodList(PodsWithFreshMetrics(metricsStalenessThreshold))
 	logger.V(logutil.TRACE).Info("Refreshing Prometheus Metrics", "ReadyPods", len(podMetrics))
 	if len(podMetrics) == 0 {
 		return
diff --git a/pkg/epp/backend/metrics/metrics_state.go b/pkg/epp/backend/metrics/metrics_state.go
index 0215ac0..50da1cc 100644
--- a/pkg/epp/backend/metrics/metrics_state.go
+++ b/pkg/epp/backend/metrics/metrics_state.go
@@ -17,64 +17,13 @@ limitations under the License.
 package metrics
 
 import (
-	"fmt"
-	"time"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
 )
 
 // NewMetricsState initializes a new MetricsState and returns its pointer.
 func NewMetricsState() *MetricsState {
-	return &MetricsState{
-		ActiveModels:  make(map[string]int),
-		WaitingModels: make(map[string]int),
-	}
+	return datalayer.NewMetrics()
 }
 
 // MetricsState holds the latest state of the metrics that were scraped from a pod.
-type MetricsState struct {
-	// ActiveModels is a set of models(including LoRA adapters) that are currently cached to GPU.
-	ActiveModels  map[string]int
-	WaitingModels map[string]int
-	// MaxActiveModels is the maximum number of models that can be loaded to GPU.
-	MaxActiveModels         int
-	RunningQueueSize        int
-	WaitingQueueSize        int
-	KVCacheUsagePercent     float64
-	KvCacheMaxTokenCapacity int
-
-	// UpdateTime record the last time when the metrics were updated.
-	UpdateTime time.Time
-}
-
-// String returns a string with all MetricState information
-func (s *MetricsState) String() string {
-	if s == nil {
-		return ""
-	}
-	return fmt.Sprintf("%+v", *s)
-}
-
-// Clone creates a copy of MetricsState and returns its pointer.
-// Clone returns nil if the object being cloned is nil.
-func (s *MetricsState) Clone() *MetricsState {
-	if s == nil {
-		return nil
-	}
-	activeModels := make(map[string]int, len(s.ActiveModels))
-	for key, value := range s.ActiveModels {
-		activeModels[key] = value
-	}
-	waitingModels := make(map[string]int, len(s.WaitingModels))
-	for key, value := range s.WaitingModels {
-		waitingModels[key] = value
-	}
-	return &MetricsState{
-		ActiveModels:            activeModels,
-		WaitingModels:           waitingModels,
-		MaxActiveModels:         s.MaxActiveModels,
-		RunningQueueSize:        s.RunningQueueSize,
-		WaitingQueueSize:        s.WaitingQueueSize,
-		KVCacheUsagePercent:     s.KVCacheUsagePercent,
-		KvCacheMaxTokenCapacity: s.KvCacheMaxTokenCapacity,
-		UpdateTime:              s.UpdateTime,
-	}
-}
+type MetricsState = datalayer.Metrics
diff --git a/pkg/epp/backend/metrics/pod_metrics.go b/pkg/epp/backend/metrics/pod_metrics.go
index 3471ddf..da66a97 100644
--- a/pkg/epp/backend/metrics/pod_metrics.go
+++ b/pkg/epp/backend/metrics/pod_metrics.go
@@ -28,6 +28,7 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
 	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
 )
 
@@ -39,7 +40,7 @@ type podMetrics struct {
 	pod      atomic.Pointer[backend.Pod]
 	metrics  atomic.Pointer[MetricsState]
 	pmc      PodMetricsClient
-	ds       Datastore
+	ds       datalayer.PoolInfo
 	interval time.Duration
 
 	startOnce sync.Once // ensures the refresh loop goroutine is started only once
@@ -116,7 +117,10 @@ func (pm *podMetrics) refreshMetrics() error {
 	}
 	ctx, cancel := context.WithTimeout(context.Background(), fetchMetricsTimeout)
 	defer cancel()
-	updated, err := pm.pmc.FetchMetrics(ctx, pm.GetPod(), pm.GetMetrics(), pool.Spec.TargetPortNumber)
+	if len(pool.Spec.TargetPorts) != 1 {
+		return fmt.Errorf("expected 1 target port, got %d", len(pool.Spec.TargetPorts))
+	}
+	updated, err := pm.pmc.FetchMetrics(ctx, pm.GetPod(), pm.GetMetrics(), int32(pool.Spec.TargetPorts[0].Number))
 	if err != nil {
 		pm.logger.V(logutil.TRACE).Info("Failed to refreshed metrics:", "err", err)
 	}
@@ -129,17 +133,27 @@ func (pm *podMetrics) refreshMetrics() error {
 	// this case, the updated metrics object will have partial updates. A partial update is
 	// considered better than no updates.
 	if updated != nil {
-		updated.UpdateTime = time.Now()
-		pm.logger.V(logutil.TRACE).Info("Refreshed metrics", "updated", updated)
-		pm.metrics.Store(updated)
+		pm.UpdateMetrics(updated)
 	}
 
 	return nil
 }
 
-func (pm *podMetrics) StopRefreshLoop() {
+func (pm *podMetrics) stopRefreshLoop() {
 	pm.logger.V(logutil.DEFAULT).Info("Stopping refresher", "pod", pm.GetPod())
 	pm.stopOnce.Do(func() {
 		close(pm.done)
 	})
 }
+
+// Allowing forward compatibility between PodMetrics and datalayer.Endpoint, by
+// implementing missing functions (e.g., extended attributes support) as no-op.
+func (*podMetrics) Put(string, datalayer.Cloneable)        {}
+func (*podMetrics) Get(string) (datalayer.Cloneable, bool) { return nil, false }
+func (*podMetrics) Keys() []string                         { return nil }
+
+func (pm *podMetrics) UpdateMetrics(updated *MetricsState) {
+	updated.UpdateTime = time.Now()
+	pm.logger.V(logutil.TRACE).Info("Refreshed metrics", "updated", updated)
+	pm.metrics.Store(updated)
+}
diff --git a/pkg/epp/backend/metrics/types.go b/pkg/epp/backend/metrics/types.go
index 975910f..aadeb85 100644
--- a/pkg/epp/backend/metrics/types.go
+++ b/pkg/epp/backend/metrics/types.go
@@ -25,28 +25,35 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
 )
 
 var (
-	AllPodPredicate = func(PodMetrics) bool { return true }
+	AllPodsPredicate = func(PodMetrics) bool { return true }
 )
 
-func NewPodMetricsFactory(pmc PodMetricsClient, refreshMetricsInterval, metricsStalenessThreshold time.Duration) *PodMetricsFactory {
+func PodsWithFreshMetrics(stalenessThreshold time.Duration) func(PodMetrics) bool {
+	return func(pm PodMetrics) bool {
+		if pm == nil {
+			return false // Skip nil pods
+		}
+		return time.Since(pm.GetMetrics().UpdateTime) <= stalenessThreshold
+	}
+}
+
+func NewPodMetricsFactory(pmc PodMetricsClient, refreshMetricsInterval time.Duration) *PodMetricsFactory {
 	return &PodMetricsFactory{
-		pmc:                       pmc,
-		refreshMetricsInterval:    refreshMetricsInterval,
-		metricsStalenessThreshold: metricsStalenessThreshold,
+		pmc:                    pmc,
+		refreshMetricsInterval: refreshMetricsInterval,
 	}
 }
 
 type PodMetricsFactory struct {
-	pmc                       PodMetricsClient
-	refreshMetricsInterval    time.Duration
-	metricsStalenessThreshold time.Duration
+	pmc                    PodMetricsClient
+	refreshMetricsInterval time.Duration
 }
 
-func (f *PodMetricsFactory) NewPodMetrics(parentCtx context.Context, in *corev1.Pod, ds Datastore) PodMetrics {
+func (f *PodMetricsFactory) NewEndpoint(parentCtx context.Context, in *corev1.Pod, ds datalayer.PoolInfo) PodMetrics {
 	pod := toInternalPod(in)
 	pm := &podMetrics{
 		pmc:       f.pmc,
@@ -64,10 +71,10 @@ func (f *PodMetricsFactory) NewPodMetrics(parentCtx context.Context, in *corev1.
 	return pm
 }
 
-type PodMetrics interface {
-	GetPod() *backend.Pod
-	GetMetrics() *MetricsState
-	UpdatePod(*corev1.Pod)
-	StopRefreshLoop()
-	String() string
+func (f *PodMetricsFactory) ReleaseEndpoint(ep PodMetrics) {
+	if pm, ok := ep.(*podMetrics); ok {
+		pm.stopRefreshLoop()
+	}
 }
+
+type PodMetrics = datalayer.Endpoint
diff --git a/pkg/epp/backend/pod.go b/pkg/epp/backend/pod.go
index 3340a3d..324a747 100644
--- a/pkg/epp/backend/pod.go
+++ b/pkg/epp/backend/pod.go
@@ -17,38 +17,7 @@ limitations under the License.
 package backend
 
 import (
-	"fmt"
-
-	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
 )
 
-type Pod struct {
-	NamespacedName types.NamespacedName
-	Address        string
-	Labels         map[string]string
-}
-
-func (p *Pod) String() string {
-	if p == nil {
-		return ""
-	}
-	return fmt.Sprintf("%+v", *p)
-}
-
-func (p *Pod) Clone() *Pod {
-	if p == nil {
-		return nil
-	}
-	clonedLabels := make(map[string]string, len(p.Labels))
-	for key, value := range p.Labels {
-		clonedLabels[key] = value
-	}
-	return &Pod{
-		NamespacedName: types.NamespacedName{
-			Name:      p.NamespacedName.Name,
-			Namespace: p.NamespacedName.Namespace,
-		},
-		Address: p.Address,
-		Labels:  clonedLabels,
-	}
-}
+type Pod = datalayer.PodInfo
diff --git a/pkg/epp/config/loader/defaults.go b/pkg/epp/config/loader/defaults.go
index 7be0d98..2a8236e 100644
--- a/pkg/epp/config/loader/defaults.go
+++ b/pkg/epp/config/loader/defaults.go
@@ -69,7 +69,7 @@ func setDefaultsPhaseTwo(cfg *configapi.EndpointPickerConfig, handle plugins.Han
 		thePlugins := []configapi.SchedulingPlugin{}
 		for pluginName, plugin := range allPlugins {
 			switch plugin.(type) {
-			case framework.Filter, framework.Picker, framework.PostCycle, framework.Scorer:
+			case framework.Filter, framework.Scorer, framework.Picker:
 				thePlugins = append(thePlugins, configapi.SchedulingPlugin{PluginRef: pluginName})
 			}
 		}
diff --git a/pkg/epp/controller/inferenceobjective_reconciler.go b/pkg/epp/controller/inferenceobjective_reconciler.go
index 7fc50f1..53bce86 100644
--- a/pkg/epp/controller/inferenceobjective_reconciler.go
+++ b/pkg/epp/controller/inferenceobjective_reconciler.go
@@ -39,7 +39,7 @@ type InferenceObjectiveReconciler struct {
 }
 
 func (c *InferenceObjectiveReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
-	logger := log.FromContext(ctx).V(logutil.DEFAULT).WithValues("InferenceObjective", req.NamespacedName)
+	logger := log.FromContext(ctx).V(logutil.DEFAULT)
 	ctx = ctrl.LoggerInto(ctx, logger)
 
 	logger.Info("Reconciling InferenceObjective")
diff --git a/pkg/epp/controller/inferencepool_reconciler.go b/pkg/epp/controller/inferencepool_reconciler.go
index 1d3c0aa..d8b7668 100644
--- a/pkg/epp/controller/inferencepool_reconciler.go
+++ b/pkg/epp/controller/inferencepool_reconciler.go
@@ -21,7 +21,6 @@ import (
 	"fmt"
 
 	"k8s.io/apimachinery/pkg/api/errors"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/log"
@@ -43,10 +42,10 @@ type InferencePoolReconciler struct {
 }
 
 func (c *InferencePoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
-	logger := log.FromContext(ctx).WithValues("group", c.PoolGKNN.Group, "inferencePool", req.NamespacedName).V(logutil.DEFAULT)
+	logger := log.FromContext(ctx).WithValues("group", c.PoolGKNN.Group).V(logutil.DEFAULT)
 	ctx = ctrl.LoggerInto(ctx, logger)
 
-	logger.Info("Reconciling InferencePool", "group", c.PoolGKNN.Group, "inferencePool", req.NamespacedName)
+	logger.Info("Reconciling InferencePool")
 
 	// 1. Initialize a generic client.Object based on the group.
 	var obj client.Object
@@ -81,23 +80,17 @@ func (c *InferencePoolReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 	}
 
 	// 4. Convert the fetched object to the canonical v1.InferencePool.
-	var v1infPool *v1.InferencePool
+	v1infPool := &v1.InferencePool{}
 
 	switch pool := obj.(type) {
 	case *v1.InferencePool:
 		// If it's already a v1 object, just use it.
 		v1infPool = pool
 	case *v1alpha2.InferencePool:
-		// If it's a v1alpha2 object, convert it to v1.
-		var uns *unstructured.Unstructured
-		uns, err := common.ToUnstructured(pool)
+		var err error
+		err = pool.ConvertTo(v1infPool)
 		if err != nil {
-			logger.Error(err, "Failed to convert inferencePool to unstructured")
-			return ctrl.Result{}, err
-		}
-		v1infPool, err = common.ToInferencePool(uns)
-		if err != nil {
-			logger.Error(err, "Failed to convert unstructured to inferencePool")
+			logger.Error(err, "Failed to convert XInferencePool to InferencePool")
 			return ctrl.Result{}, err
 		}
 	default:
diff --git a/pkg/epp/controller/pod_reconciler.go b/pkg/epp/controller/pod_reconciler.go
index 99f34c1..3cd7c25 100644
--- a/pkg/epp/controller/pod_reconciler.go
+++ b/pkg/epp/controller/pod_reconciler.go
@@ -47,7 +47,7 @@ func (c *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
 		return ctrl.Result{}, nil
 	}
 
-	logger.V(logutil.VERBOSE).Info("Pod being reconciled", "name", req.NamespacedName)
+	logger.V(logutil.VERBOSE).Info("Pod being reconciled")
 
 	pod := &corev1.Pod{}
 	if err := c.Get(ctx, req.NamespacedName, pod); err != nil {
@@ -55,7 +55,7 @@ func (c *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
 			c.Datastore.PodDelete(req.NamespacedName)
 			return ctrl.Result{}, nil
 		}
-		logger.V(logutil.DEFAULT).Error(err, "Unable to get pod", "name", req.NamespacedName)
+		logger.V(logutil.DEFAULT).Error(err, "Unable to get pod")
 		return ctrl.Result{}, err
 	}
 
@@ -92,13 +92,13 @@ func (c *PodReconciler) SetupWithManager(mgr ctrl.Manager) error {
 func (c *PodReconciler) updateDatastore(logger logr.Logger, pod *corev1.Pod) {
 	namespacedName := types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}
 	if !podutil.IsPodReady(pod) || !c.Datastore.PoolLabelsMatch(pod.Labels) {
-		logger.V(logutil.DEBUG).Info("Pod removed or not added", "name", namespacedName)
+		logger.V(logutil.DEBUG).Info("Pod removed or not added")
 		c.Datastore.PodDelete(namespacedName)
 	} else {
 		if c.Datastore.PodUpdateOrAddIfNotExist(pod) {
-			logger.V(logutil.DEFAULT).Info("Pod added", "name", namespacedName)
+			logger.V(logutil.DEFAULT).Info("Pod added")
 		} else {
-			logger.V(logutil.DEFAULT).Info("Pod already exists", "name", namespacedName)
+			logger.V(logutil.DEFAULT).Info("Pod already exists")
 		}
 	}
 }
diff --git a/pkg/epp/datalayer/collector.go b/pkg/epp/datalayer/collector.go
index 94aa9b6..86a8f7b 100644
--- a/pkg/epp/datalayer/collector.go
+++ b/pkg/epp/datalayer/collector.go
@@ -82,14 +82,18 @@ func NewCollector() *Collector {
 }
 
 // Start initiates data source collection for the endpoint.
+// TODO: pass PoolInfo for backward compatibility
 func (c *Collector) Start(ctx context.Context, ticker Ticker, ep Endpoint, sources []DataSource) error {
+	var ready chan struct{}
 	started := false
+
 	c.startOnce.Do(func() {
+		logger := log.FromContext(ctx).WithValues("endpoint", ep.GetPod().GetIPAddress())
 		c.ctx, c.cancel = context.WithCancel(ctx)
 		started = true
+		ready = make(chan struct{})
 
 		go func(endpoint Endpoint, sources []DataSource) {
-			logger := log.FromContext(ctx).WithValues("endpoint", ep.GetPod().GetIPAddress())
 			logger.V(logging.DEFAULT).Info("starting collection")
 
 			defer func() {
@@ -97,11 +101,14 @@ func (c *Collector) Start(ctx context.Context, ticker Ticker, ep Endpoint, sourc
 				ticker.Stop()
 			}()
 
+			close(ready) // signal ready to accept ticks
+
 			for {
 				select {
 				case <-c.ctx.Done(): // per endpoint context cancelled
 					return
 				case <-ticker.Channel():
+					// TODO: do not collect if there's no pool specified?
 					for _, src := range sources {
 						ctx, cancel := context.WithTimeout(c.ctx, defaultCollectionTimeout)
 						_ = src.Collect(ctx, endpoint) // TODO: track errors per collector?
@@ -115,7 +122,21 @@ func (c *Collector) Start(ctx context.Context, ticker Ticker, ep Endpoint, sourc
 	if !started {
 		return errors.New("collector start called multiple times")
 	}
-	return nil
+
+	// Wait for goroutine to signal readiness.
+	// The use of ready channel is mostly to make the function testable, by ensuring
+	// synchronous order of events. Ignoring test requirements, one could let the
+	// go routine start at some arbitrary point in the future, possibly after this
+	// function has returned.
+	select {
+	case <-ready:
+		return nil
+	case <-ctx.Done():
+		if c.cancel != nil {
+			c.cancel() // ensure clean up
+		}
+		return ctx.Err()
+	}
 }
 
 // Stop terminates the collector.
diff --git a/pkg/epp/datalayer/datasource.go b/pkg/epp/datalayer/datasource.go
index 6ad7b29..737e2c5 100644
--- a/pkg/epp/datalayer/datasource.go
+++ b/pkg/epp/datalayer/datasource.go
@@ -92,14 +92,27 @@ func (dsr *DataSourceRegistry) GetSources() []DataSource {
 
 // --- default registry accessors ---
 
+// RegisterSource adds a new data source to the default registry.
 func RegisterSource(src DataSource) error {
 	return defaultDataSources.Register(src)
 }
 
-func GetNamedSource(name string) (DataSource, bool) {
-	return defaultDataSources.GetNamedSource(name)
+// GetNamedSource returns a typed data source from the default registry.
+func GetNamedSource[T DataSource](name string) (T, bool) {
+	v, ok := defaultDataSources.GetNamedSource(name)
+	if !ok {
+		var zero T
+		return zero, false
+	}
+	src, ok := v.(T)
+	if !ok {
+		var zero T
+		return zero, false
+	}
+	return src, true
 }
 
+// GetSources returns the list of data sources registered in the default registry.
 func GetSources() []DataSource {
 	return defaultDataSources.GetSources()
 }
diff --git a/pkg/epp/datalayer/endpoint.go b/pkg/epp/datalayer/endpoint.go
index 31f24c2..2a72886 100644
--- a/pkg/epp/datalayer/endpoint.go
+++ b/pkg/epp/datalayer/endpoint.go
@@ -37,6 +37,7 @@ type EndpointMetricsState interface {
 
 // Endpoint represents an inference serving endpoint and its related attributes.
 type Endpoint interface {
+	fmt.Stringer
 	EndpointPodState
 	EndpointMetricsState
 	AttributeMap
@@ -57,7 +58,7 @@ func NewEndpoint() *ModelServer {
 }
 
 // String returns a representation of the ModelServer. For brevity, only names of
-// extended attributes are returned and not the values.
+// extended attributes are returned and not their values.
 func (srv *ModelServer) String() string {
 	return fmt.Sprintf("Pod: %v; Metrics: %v; Attributes: %v", srv.GetPod(), srv.GetMetrics(), srv.Keys())
 }
diff --git a/pkg/epp/datalayer/factory.go b/pkg/epp/datalayer/factory.go
new file mode 100644
index 0000000..eca7697
--- /dev/null
+++ b/pkg/epp/datalayer/factory.go
@@ -0,0 +1,121 @@
+/*
+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.
+*/
+
+package datalayer
+
+import (
+	"context"
+	"sync"
+	"time"
+
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/log"
+
+	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
+)
+
+// PoolInfo represents the DataStore information needed for endpoints.
+// TODO:
+// Consider if to remove/simplify in follow-ups. This is mostly for backward
+// compatibility with backend.metrics' expectations and allowing a shared
+// implementation during the transition.
+//   - Endpoint metric scraping uses PoolGet to access the pool's Port and Name.
+//   - Global metrics logging uses PoolGet solely for error return and PodList to enumerate
+//     all endpoints for metrics summarization.
+type PoolInfo interface {
+	PoolGet() (*v1.InferencePool, error)
+	PodList(func(Endpoint) bool) []Endpoint
+}
+
+// EndpointFactory defines an interface for managing Endpoint lifecycle. Specifically,
+// providing methods to allocate and retire endpoints. This can potentially be used for
+// pooled memory or other management chores in the implementation.
+type EndpointFactory interface {
+	NewEndpoint(parent context.Context, inpod *corev1.Pod, poolinfo PoolInfo) Endpoint
+	ReleaseEndpoint(ep Endpoint)
+}
+
+// EndpointLifecycle manages the life cycle (creation and termination) of
+// endpoints.
+type EndpointLifecycle struct {
+	sources         []DataSource  // data sources for collectors
+	collectors      sync.Map      // collectors map. key: Pod namespaced name, value: *Collector
+	refreshInterval time.Duration // metrics refresh interval
+}
+
+// NewEndpointFactory returns a new endpoint for factory, managing collectors for
+// its endpoints. This function assumes that sources are not modified afterwards.
+func NewEndpointFactory(sources []DataSource, refreshMetricsInterval time.Duration) *EndpointLifecycle {
+	return &EndpointLifecycle{
+		sources:         sources,
+		collectors:      sync.Map{},
+		refreshInterval: refreshMetricsInterval,
+	}
+}
+
+// NewEndpoint implements EndpointFactory.NewEndpoint.
+// Creates a new endpoint and starts its associated collector with its own ticker.
+// Guards against multiple concurrent calls for the same endpoint.
+func (lc *EndpointLifecycle) NewEndpoint(parent context.Context, inpod *corev1.Pod, _ PoolInfo) Endpoint {
+	key := types.NamespacedName{Namespace: inpod.Namespace, Name: inpod.Name}
+	logger := log.FromContext(parent).WithValues("pod", key)
+
+	if _, ok := lc.collectors.Load(key); ok {
+		logger.Info("collector already running for endpoint", "endpoint", key)
+		return nil
+	}
+
+	endpoint := NewEndpoint()
+	endpoint.UpdatePod(inpod)
+	collector := NewCollector() // for full backward compatibility, set the logger and poolinfo
+
+	if _, loaded := lc.collectors.LoadOrStore(key, collector); loaded {
+		// another goroutine already created and stored a collector for this endpoint.
+		// No need to start the new collector.
+		logger.Info("collector already running for endpoint", "endpoint", key)
+		return nil
+	}
+
+	ticker := NewTimeTicker(lc.refreshInterval)
+	if err := collector.Start(parent, ticker, endpoint, lc.sources); err != nil {
+		logger.Error(err, "failed to start collector for endpoint", "endpoint", key)
+		lc.collectors.Delete(key)
+	}
+
+	return endpoint
+}
+
+// ReleaseEndpoint implements EndpointFactory.ReleaseEndpoint
+// Stops the collector and cleans up resources for the endpoint
+func (lc *EndpointLifecycle) ReleaseEndpoint(ep Endpoint) {
+	key := ep.GetPod().GetNamespacedName()
+
+	if value, ok := lc.collectors.LoadAndDelete(key); ok {
+		collector := value.(*Collector)
+		_ = collector.Stop()
+	}
+}
+
+// Shutdown gracefully stops all collectors and cleans up all resources.
+func (lc *EndpointLifecycle) Shutdown() {
+	lc.collectors.Range(func(key, value any) bool {
+		collector := value.(*Collector)
+		_ = collector.Stop()
+		lc.collectors.Delete(key)
+		return true
+	})
+}
diff --git a/pkg/epp/datalayer/metrics/client.go b/pkg/epp/datalayer/metrics/client.go
index 7961b92..962a2a5 100644
--- a/pkg/epp/datalayer/metrics/client.go
+++ b/pkg/epp/datalayer/metrics/client.go
@@ -33,22 +33,31 @@ type Client interface {
 	Get(ctx context.Context, target *url.URL, ep datalayer.Addressable) (PrometheusMetricMap, error)
 }
 
-// -- package implementations --
 const (
+	// the maximum idle connection count is shared by all endpoints. The value is
+	// set high to ensure the number of idle connection is above the expected
+	// endpoint count. Setting it too low would cause thrashing of the idle connection
+	// pool and incur higher overheads for every GET (e.g., socket initiation, certificate
+	// exchange, connections in timed wait state, etc.).
 	maxIdleConnections = 5000
-	maxIdleTime        = 10 * time.Second
-	timeout            = 10 * time.Second
+	maxIdleTime        = 10 * time.Second // once a endpoint goes down, allow closing.
+	timeout            = 10 * time.Second // mostly guard against unresponsive endpoints.
+	// allow some grace when connections are not made idle immediately (e.g., parsing
+	// and updating might take some time). This allows maintaining up to two idle connections
+	// per endpoint (defined as scheme://host:port).
+	maxIdleConnsPerHost = 2
 )
 
 var (
+	baseTransport = &http.Transport{
+		MaxIdleConns:        maxIdleConnections,
+		MaxIdleConnsPerHost: maxIdleConnsPerHost,
+		// TODO: set additional timeouts, transport options, etc.
+	}
 	defaultClient = &client{
 		Client: http.Client{
-			Timeout: timeout,
-			Transport: &http.Transport{
-				MaxIdleConns:        maxIdleConnections,
-				MaxIdleConnsPerHost: 4, // host is defined as scheme://host:port
-			},
-			// TODO: set additional timeouts, transport options, etc.
+			Timeout:   timeout,
+			Transport: baseTransport,
 		},
 	}
 )
diff --git a/pkg/epp/datalayer/metrics/datasource.go b/pkg/epp/datalayer/metrics/datasource.go
index 5ff7ef9..7dcdc97 100644
--- a/pkg/epp/datalayer/metrics/datasource.go
+++ b/pkg/epp/datalayer/metrics/datasource.go
@@ -18,6 +18,7 @@ package metrics
 
 import (
 	"context"
+	"crypto/tls"
 	"errors"
 	"fmt"
 	"net"
@@ -30,7 +31,7 @@ import (
 )
 
 const (
-	dataSourceName = "metrics-data-source"
+	DataSourceName = "metrics-data-source"
 )
 
 // DataSource is a Model Server Protocol (MSP) compliant metrics data source,
@@ -44,9 +45,19 @@ type DataSource struct {
 	extractors sync.Map // key: name, value: extractor
 }
 
-// NewDataSource returns a new MSP compliant metrics data source, configured with the provided
-// client factory. If ClientFactory is nil, a default factory is used.
-func NewDataSource(metricsScheme string, metricsPort int32, metricsPath string, cl Client) *DataSource {
+// NewDataSource returns a new MSP compliant metrics data source, configured with
+// the provided client factory. If ClientFactory is nil, a default factory is used.
+// The Scheme, port and path are command line options. It should be noted that
+// a port value of zero is set if the command line is unspecified.
+func NewDataSource(metricsScheme string, metricsPort int32, metricsPath string, skipCertVerification bool, cl Client) *DataSource {
+	if metricsScheme == "https" {
+		httpsTransport := baseTransport.Clone()
+		httpsTransport.TLSClientConfig = &tls.Config{
+			InsecureSkipVerify: skipCertVerification,
+		}
+		defaultClient.Transport = httpsTransport
+	}
+
 	if cl == nil {
 		cl = defaultClient
 	}
@@ -61,14 +72,23 @@ func NewDataSource(metricsScheme string, metricsPort int32, metricsPath string,
 }
 
 // SetPort updates the port used for metrics scraping.
+// The port value can only be set once (i.e., if set by command line,
+// do not overwrite from Pool.Spec). A port value of 0 (i.e., unspecified
+// command line value) is ignored.
+// TODO: https://github.com/kubernetes-sigs/gateway-api-inference-extension/issues/1398
 func (dataSrc *DataSource) SetPort(metricsPort int32) {
-	port := strconv.Itoa(int(metricsPort))
-	dataSrc.metricsPort.Store(&port)
+	if dataSrc.metricsPort.Load() != nil { // do not overwrite
+		return
+	}
+	if metricsPort != 0 { // ignore zero value for port
+		port := strconv.Itoa(int(metricsPort))
+		dataSrc.metricsPort.Store(&port)
+	}
 }
 
 // Name returns the metrics data source name.
 func (dataSrc *DataSource) Name() string {
-	return dataSourceName
+	return DataSourceName
 }
 
 // AddExtractor adds an extractor to the data source, validating it can process
diff --git a/pkg/epp/datalayer/metrics/logger.go b/pkg/epp/datalayer/metrics/logger.go
new file mode 100644
index 0000000..fac757d
--- /dev/null
+++ b/pkg/epp/datalayer/metrics/logger.go
@@ -0,0 +1,138 @@
+/*
+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.
+*/
+
+package metrics
+
+import (
+	"context"
+	"time"
+
+	"github.com/go-logr/logr"
+	"sigs.k8s.io/controller-runtime/pkg/log"
+
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics"
+	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
+)
+
+const debugPrintInterval = 5 * time.Second
+
+// StartMetricsLogger starts background goroutines for:
+// 1. Refreshing Prometheus metrics periodically
+// 2. Debug logging (if DEBUG level enabled)
+func StartMetricsLogger(ctx context.Context, datastore datalayer.PoolInfo, refreshInterval, stalenessThreshold time.Duration) {
+	logger := log.FromContext(ctx)
+
+	go runPrometheusRefresher(ctx, logger, datastore, refreshInterval, stalenessThreshold)
+
+	if logger.V(logutil.DEBUG).Enabled() {
+		go runDebugLogger(ctx, logger, datastore, stalenessThreshold)
+	}
+}
+
+func runPrometheusRefresher(ctx context.Context, logger logr.Logger, datastore datalayer.PoolInfo, interval, stalenessThreshold time.Duration) {
+	ticker := time.NewTicker(interval)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-ctx.Done():
+			logger.V(logutil.DEFAULT).Info("Shutting down prometheus metrics thread")
+			return
+		case <-ticker.C:
+			refreshPrometheusMetrics(logger, datastore, stalenessThreshold)
+		}
+	}
+}
+
+func runDebugLogger(ctx context.Context, logger logr.Logger, datastore datalayer.PoolInfo, stalenessThreshold time.Duration) {
+	ticker := time.NewTicker(debugPrintInterval)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-ctx.Done():
+			logger.V(logutil.DEFAULT).Info("Shutting down metrics logger thread")
+			return
+		case <-ticker.C:
+			printDebugMetrics(logger, datastore, stalenessThreshold)
+		}
+	}
+}
+
+func podsWithFreshMetrics(stalenessThreshold time.Duration) func(datalayer.Endpoint) bool {
+	return func(ep datalayer.Endpoint) bool {
+		if ep == nil {
+			return false // Skip nil pods
+		}
+		return time.Since(ep.GetMetrics().UpdateTime) <= stalenessThreshold
+	}
+}
+
+func podsWithStaleMetrics(stalenessThreshold time.Duration) func(datalayer.Endpoint) bool {
+	return func(ep datalayer.Endpoint) bool {
+		if ep == nil {
+			return false // Skip nil pods
+		}
+		return time.Since(ep.GetMetrics().UpdateTime) > stalenessThreshold
+	}
+}
+
+func printDebugMetrics(logger logr.Logger, datastore datalayer.PoolInfo, stalenessThreshold time.Duration) {
+	freshPods := datastore.PodList(podsWithFreshMetrics(stalenessThreshold))
+	stalePods := datastore.PodList(podsWithStaleMetrics(stalenessThreshold))
+
+	logger.V(logutil.VERBOSE).Info("Current Pods and metrics gathered",
+		"Fresh metrics", freshPods, "Stale metrics", stalePods)
+}
+
+func refreshPrometheusMetrics(logger logr.Logger, datastore datalayer.PoolInfo, stalenessThreshold time.Duration) {
+	pool, err := datastore.PoolGet()
+	if err != nil {
+		logger.V(logutil.DEFAULT).Info("Pool is not initialized, skipping refreshing metrics")
+		return
+	}
+
+	podMetrics := datastore.PodList(podsWithFreshMetrics(stalenessThreshold))
+	logger.V(logutil.TRACE).Info("Refreshing Prometheus Metrics", "ReadyPods", len(podMetrics))
+
+	if len(podMetrics) == 0 {
+		return
+	}
+
+	totals := calculateTotals(podMetrics)
+	podCount := len(podMetrics)
+
+	metrics.RecordInferencePoolAvgKVCache(pool.Name, totals.kvCache/float64(podCount))
+	metrics.RecordInferencePoolAvgQueueSize(pool.Name, float64(totals.queueSize/podCount))
+	metrics.RecordInferencePoolReadyPods(pool.Name, float64(podCount))
+}
+
+// totals holds aggregated metric values
+type totals struct {
+	kvCache   float64
+	queueSize int
+}
+
+func calculateTotals(endpoints []datalayer.Endpoint) totals {
+	var result totals
+	for _, pod := range endpoints {
+		metrics := pod.GetMetrics()
+		result.kvCache += metrics.KVCacheUsagePercent
+		result.queueSize += metrics.WaitingQueueSize
+	}
+	return result
+}
diff --git a/pkg/epp/datastore/datastore.go b/pkg/epp/datastore/datastore.go
index b1dee7f..38127f8 100644
--- a/pkg/epp/datastore/datastore.go
+++ b/pkg/epp/datastore/datastore.go
@@ -32,6 +32,8 @@ import (
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
+	dlmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer/metrics"
 	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
 	podutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/pod"
 )
@@ -66,13 +68,13 @@ type Datastore interface {
 	Clear()
 }
 
-func NewDatastore(parentCtx context.Context, pmf *backendmetrics.PodMetricsFactory) Datastore {
+func NewDatastore(parentCtx context.Context, epFactory datalayer.EndpointFactory) Datastore {
 	store := &datastore{
 		parentCtx:           parentCtx,
 		poolAndObjectivesMu: sync.RWMutex{},
 		objectives:          make(map[string]*v1alpha2.InferenceObjective),
 		pods:                &sync.Map{},
-		pmf:                 pmf,
+		epf:                 epFactory,
 	}
 	return store
 }
@@ -87,7 +89,7 @@ type datastore struct {
 	objectives map[string]*v1alpha2.InferenceObjective
 	// key: types.NamespacedName, value: backendmetrics.PodMetrics
 	pods *sync.Map
-	pmf  *backendmetrics.PodMetricsFactory
+	epf  datalayer.EndpointFactory
 }
 
 func (ds *datastore) Clear() {
@@ -97,7 +99,7 @@ func (ds *datastore) Clear() {
 	ds.objectives = make(map[string]*v1alpha2.InferenceObjective)
 	// stop all pods go routines before clearing the pods map.
 	ds.pods.Range(func(_, v any) bool {
-		v.(backendmetrics.PodMetrics).StopRefreshLoop()
+		ds.epf.ReleaseEndpoint(v.(backendmetrics.PodMetrics))
 		return true
 	})
 	ds.pods.Clear()
@@ -115,6 +117,11 @@ func (ds *datastore) PoolSet(ctx context.Context, reader client.Reader, pool *v1
 
 	oldPool := ds.pool
 	ds.pool = pool
+	if oldPool == nil || pool.Spec.TargetPorts[0] != oldPool.Spec.TargetPorts[0] {
+		if source, found := datalayer.GetNamedSource[*dlmetrics.DataSource](dlmetrics.DataSourceName); found {
+			source.SetPort(int32(pool.Spec.TargetPorts[0].Number))
+		}
+	}
 	if oldPool == nil || !reflect.DeepEqual(pool.Spec.Selector, oldPool.Spec.Selector) {
 		logger.V(logutil.DEFAULT).Info("Updating inference pool endpoints", "selector", pool.Spec.Selector)
 		// A full resync is required to address two cases:
@@ -152,7 +159,7 @@ func (ds *datastore) PoolLabelsMatch(podLabels map[string]string) bool {
 	if ds.pool == nil {
 		return false
 	}
-	poolSelector := selectorFromInferencePoolSelector(ds.pool.Spec.Selector)
+	poolSelector := selectorFromInferencePoolSelector(ds.pool.Spec.Selector.MatchLabels)
 	podSet := labels.Set(podLabels)
 	return poolSelector.Matches(podSet)
 }
@@ -215,7 +222,7 @@ func (ds *datastore) PodUpdateOrAddIfNotExist(pod *corev1.Pod) bool {
 	var pm backendmetrics.PodMetrics
 	existing, ok := ds.pods.Load(namespacedName)
 	if !ok {
-		pm = ds.pmf.NewPodMetrics(ds.parentCtx, pod, ds)
+		pm = ds.epf.NewEndpoint(ds.parentCtx, pod, ds)
 		ds.pods.Store(namespacedName, pm)
 	} else {
 		pm = existing.(backendmetrics.PodMetrics)
@@ -228,8 +235,7 @@ func (ds *datastore) PodUpdateOrAddIfNotExist(pod *corev1.Pod) bool {
 func (ds *datastore) PodDelete(namespacedName types.NamespacedName) {
 	v, ok := ds.pods.LoadAndDelete(namespacedName)
 	if ok {
-		pmr := v.(backendmetrics.PodMetrics)
-		pmr.StopRefreshLoop()
+		ds.epf.ReleaseEndpoint(v.(backendmetrics.PodMetrics))
 	}
 }
 
@@ -237,7 +243,7 @@ func (ds *datastore) podResyncAll(ctx context.Context, reader client.Reader) err
 	logger := log.FromContext(ctx)
 	podList := &corev1.PodList{}
 	if err := reader.List(ctx, podList, &client.ListOptions{
-		LabelSelector: selectorFromInferencePoolSelector(ds.pool.Spec.Selector),
+		LabelSelector: selectorFromInferencePoolSelector(ds.pool.Spec.Selector.MatchLabels),
 		Namespace:     ds.pool.Namespace,
 	}); err != nil {
 		return fmt.Errorf("failed to list pods - %w", err)
diff --git a/pkg/epp/flowcontrol/contracts/doc.go b/pkg/epp/flowcontrol/contracts/doc.go
new file mode 100644
index 0000000..554f54e
--- /dev/null
+++ b/pkg/epp/flowcontrol/contracts/doc.go
@@ -0,0 +1,29 @@
+/*
+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.
+*/
+
+// Package contracts defines the boundaries and service interfaces for the Flow Control system.
+//
+// Adhering to a "Ports and Adapters" (Hexagonal) architectural style, these interfaces decouple the core
+// `controller.FlowController` engine from its dependencies. They establish the required behaviors and system invariants
+// that concrete implementations must uphold.
+//
+// The primary contracts are:
+//
+//   - `FlowRegistry`: The interface for the stateful control plane that manages the lifecycle of flows, queues, and
+//     policies.
+//
+//   - `SaturationDetector`: The interface for a component that provides real-time load signals.
+package contracts
diff --git a/pkg/epp/flowcontrol/contracts/errors.go b/pkg/epp/flowcontrol/contracts/errors.go
index fd46ec7..38af370 100644
--- a/pkg/epp/flowcontrol/contracts/errors.go
+++ b/pkg/epp/flowcontrol/contracts/errors.go
@@ -18,18 +18,19 @@ package contracts
 
 import "errors"
 
-// Registry Errors
 var (
-	// ErrFlowInstanceNotFound indicates that a requested flow instance (a `ManagedQueue`) does not exist in the registry
-	// shard, either because the flow is not registered or the specific instance (e.g., a draining queue at a particular
-	// priority) is not present.
+	// ErrFlowInstanceNotFound indicates that a requested flow instance (a `ManagedQueue`) does not exist.
 	ErrFlowInstanceNotFound = errors.New("flow instance not found")
 
-	// ErrPriorityBandNotFound indicates that a requested priority band does not exist in the registry because it was not
-	// part of the initial configuration.
+	// ErrFlowIDEmpty indicates that a flow specification was provided with an empty flow ID.
+	ErrFlowIDEmpty = errors.New("flow ID cannot be empty")
+
+	// ErrPriorityBandNotFound indicates that a requested priority band does not exist in the registry configuration.
 	ErrPriorityBandNotFound = errors.New("priority band not found")
 
-	// ErrPolicyQueueIncompatible indicates that a selected policy is not compatible with the capabilities of the queue it
-	// is intended to operate on. For example, a policy requiring priority-based peeking is used with a simple FIFO queue.
+	// ErrPolicyQueueIncompatible indicates that a selected policy is not compatible with the capabilities of the queue.
 	ErrPolicyQueueIncompatible = errors.New("policy is not compatible with queue capabilities")
+
+	// ErrInvalidShardCount indicates that an invalid shard count was provided (e.g., zero or negative).
+	ErrInvalidShardCount = errors.New("invalid shard count")
 )
diff --git a/pkg/epp/flowcontrol/contracts/mocks/mocks.go b/pkg/epp/flowcontrol/contracts/mocks/mocks.go
index 5431e2c..c5c8d2e 100644
--- a/pkg/epp/flowcontrol/contracts/mocks/mocks.go
+++ b/pkg/epp/flowcontrol/contracts/mocks/mocks.go
@@ -46,9 +46,8 @@ import (
 type MockRegistryShard struct {
 	IDFunc                       func() string
 	IsActiveFunc                 func() bool
-	ActiveManagedQueueFunc       func(flowID string) (contracts.ManagedQueue, error)
-	ManagedQueueFunc             func(flowID string, priority uint) (contracts.ManagedQueue, error)
-	IntraFlowDispatchPolicyFunc  func(flowID string, priority uint) (framework.IntraFlowDispatchPolicy, error)
+	ManagedQueueFunc             func(key types.FlowKey) (contracts.ManagedQueue, error)
+	IntraFlowDispatchPolicyFunc  func(key types.FlowKey) (framework.IntraFlowDispatchPolicy, error)
 	InterFlowDispatchPolicyFunc  func(priority uint) (framework.InterFlowDispatchPolicy, error)
 	PriorityBandAccessorFunc     func(priority uint) (framework.PriorityBandAccessor, error)
 	AllOrderedPriorityLevelsFunc func() []uint
@@ -69,23 +68,16 @@ func (m *MockRegistryShard) IsActive() bool {
 	return false
 }
 
-func (m *MockRegistryShard) ActiveManagedQueue(flowID string) (contracts.ManagedQueue, error) {
-	if m.ActiveManagedQueueFunc != nil {
-		return m.ActiveManagedQueueFunc(flowID)
-	}
-	return nil, nil
-}
-
-func (m *MockRegistryShard) ManagedQueue(flowID string, priority uint) (contracts.ManagedQueue, error) {
+func (m *MockRegistryShard) ManagedQueue(key types.FlowKey) (contracts.ManagedQueue, error) {
 	if m.ManagedQueueFunc != nil {
-		return m.ManagedQueueFunc(flowID, priority)
+		return m.ManagedQueueFunc(key)
 	}
 	return nil, nil
 }
 
-func (m *MockRegistryShard) IntraFlowDispatchPolicy(flowID string, priority uint) (framework.IntraFlowDispatchPolicy, error) {
+func (m *MockRegistryShard) IntraFlowDispatchPolicy(key types.FlowKey) (framework.IntraFlowDispatchPolicy, error) {
 	if m.IntraFlowDispatchPolicyFunc != nil {
-		return m.IntraFlowDispatchPolicyFunc(flowID, priority)
+		return m.IntraFlowDispatchPolicyFunc(key)
 	}
 	return nil, nil
 }
@@ -148,8 +140,8 @@ func (m *MockSaturationDetector) IsSaturated(ctx context.Context) bool {
 //  3. **Self-Wiring**: The `FlowQueueAccessor()` method returns the mock itself, ensuring the accessor is always
 //     correctly connected to the queue's state without manual wiring in tests.
 type MockManagedQueue struct {
-	// FlowSpecV defines the flow specification for this mock queue. It should be set by the test.
-	FlowSpecV types.FlowSpecification
+	// FlowKeyV defines the flow specification for this mock queue. It should be set by the test.
+	FlowKeyV types.FlowKey
 
 	// AddFunc allows a test to completely override the default Add behavior.
 	AddFunc func(item types.QueueItemAccessor) error
@@ -249,7 +241,7 @@ func (m *MockManagedQueue) Drain() ([]types.QueueItemAccessor, error) {
 	return drained, nil
 }
 
-func (m *MockManagedQueue) FlowSpec() types.FlowSpecification         { return m.FlowSpecV }
+func (m *MockManagedQueue) FlowKey() types.FlowKey                    { return m.FlowKeyV }
 func (m *MockManagedQueue) Name() string                              { return "" }
 func (m *MockManagedQueue) Capabilities() []framework.QueueCapability { return nil }
 func (m *MockManagedQueue) Comparator() framework.ItemComparator      { return nil }
diff --git a/pkg/epp/flowcontrol/contracts/registry.go b/pkg/epp/flowcontrol/contracts/registry.go
index 843f501..1b788d9 100644
--- a/pkg/epp/flowcontrol/contracts/registry.go
+++ b/pkg/epp/flowcontrol/contracts/registry.go
@@ -14,21 +14,107 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-// Package contracts defines the service interfaces that decouple the core `controller.FlowController` engine from its
-// primary dependencies. In alignment with a "Ports and Adapters" (or "Hexagonal") architectural style, these
-// interfaces represent the "ports" through which the engine communicates.
-//
-// This package contains the primary service contracts for the Flow Registry, which acts as the control plane for all
-// flow state and configuration.
 package contracts
 
 import (
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types"
 )
 
-// RegistryShard defines the read-oriented interface that a `controller.FlowController` worker uses to access its
-// specific slice (shard) of the `FlowRegistry`'s state. It provides the necessary methods for a worker to perform its
-// dispatch operations by accessing queues and policies in a concurrent-safe manner.
+// FlowRegistry is the complete interface for the global control plane. An implementation of this interface is the single
+// source of truth for all flow control state and configuration.
+//
+// # Conformance
+//
+// All methods MUST be goroutine-safe. Implementations are expected to perform complex updates (e.g.,
+// `RegisterOrUpdateFlow`) atomically.
+//
+// # System Invariants
+//
+// Concrete implementations MUST uphold the following invariants:
+//  1. Shard Consistency: All configured priority bands and registered flow instances must exist on every Active shard.
+//     Plugin instance types must be consistent for a given flow across all shards.
+//  2. Flow Instance Uniqueness: Each unique `types.FlowKey` (`ID` + `Priority`) corresponds to exactly one managed flow
+//     instance.
+//  3. Capacity Partitioning: Global and per-band capacity limits must be uniformly partitioned across all Active
+//     shards.
+//
+// # Flow Lifecycle
+//
+// A flow instance (identified by its immutable `FlowKey`) has a simple lifecycle:
+//
+//   - Registered: Known to the `FlowRegistry` via `RegisterOrUpdateFlow`.
+//   - Idle: Queues are empty across all Active and Draining shards.
+//   - Garbage Collected (Unregistered): The registry automatically garbage collects flows after they have remained Idle
+//     for a configurable duration.
+//
+// # Shard Lifecycle
+//
+// When a shard is decommissioned, it is marked inactive (`IsActive() == false`) to prevent new enqueues. The shard
+// continues to drain and is deleted only after it is empty.
+type FlowRegistry interface {
+	FlowRegistryAdmin
+	ShardProvider
+}
+
+// FlowRegistryAdmin defines the administrative interface for the global control plane.
+//
+// # Dynamic Update Strategies
+//
+// The contract specifies behaviors for handling dynamic updates, prioritizing stability and correctness:
+//
+//   - Immutable Flow Identity (`types.FlowKey`): The system treats the `FlowKey` (`ID` + `Priority`) as the immutable
+//     identifier. Changing the priority of traffic requires registering a new `FlowKey`. The old flow instance is
+//     automatically garbage collected when Idle. This design eliminates complex priority migration logic.
+//
+//   - Graceful Draining (Shard Scale-Down): Decommissioned shards enter a Draining state. They stop accepting new
+//     requests but continue to be processed for dispatch until empty.
+//
+//   - Self-Balancing (Shard Scale-Up): When new shards are added, the `controller.FlowController`'s distribution logic
+//     naturally utilizes them, funneling new requests to the less-loaded shards. Existing queued items are not
+//     migrated.
+type FlowRegistryAdmin interface {
+	// RegisterOrUpdateFlow handles the registration of a new flow instance or the update of an existing instance's
+	// specification (for the same `types.FlowKey`). The operation is atomic across all shards.
+	//
+	// Since the `FlowKey` (including `Priority`) is immutable, this method cannot change a flow's priority.
+	// To change priority, the caller should simply register the new `FlowKey`; the old flow instance will be
+	// automatically garbage collected when it becomes Idle.
+	//
+	// Returns errors wrapping `ErrFlowIDEmpty`, `ErrPriorityBandNotFound`, or internal errors if plugin instantiation
+	// fails.
+	RegisterOrUpdateFlow(spec types.FlowSpecification) error
+
+	// UpdateShardCount dynamically adjusts the number of internal state shards.
+	//
+	// The implementation MUST atomically re-partition capacity allocations across all active shards.
+	// Returns an error wrapping `ErrInvalidShardCount` if `n` is not positive.
+	UpdateShardCount(n int) error
+
+	// Stats returns globally aggregated statistics for the entire `FlowRegistry`.
+	Stats() AggregateStats
+
+	// ShardStats returns a slice of statistics, one for each internal shard. This provides visibility for debugging and
+	// monitoring per-shard behavior (e.g., identifying hot or stuck shards).
+	ShardStats() []ShardStats
+}
+
+// ShardProvider defines the interface for discovering available shards.
+//
+// A "shard" is an internal, parallel execution unit that allows the `controller.FlowController`'s core dispatch logic
+// to be parallelized, preventing a CPU bottleneck at high request rates. The `FlowRegistry`'s state is sharded to
+// support this parallelism by reducing lock contention.
+//
+// Consumers MUST check `RegistryShard.IsActive()` before routing new work to a shard to avoid sending requests to a
+// Draining shard.
+type ShardProvider interface {
+	// Shards returns a slice of accessors, one for each internal state shard (Active and Draining).
+	// Callers should not modify the returned slice.
+	Shards() []RegistryShard
+}
+
+// RegistryShard defines the interface for accessing a specific slice (shard) of the `FlowRegistry's` state.
+// It provides a concurrent-safe view for `controller.FlowController` workers.
 //
 // # Conformance
 //
@@ -41,21 +127,19 @@ type RegistryShard interface {
 	// being gracefully drained and should not be given new work.
 	IsActive() bool
 
-	// ActiveManagedQueue returns the currently active `ManagedQueue` for a given flow on this shard. This is the queue to
-	// which new requests for the flow should be enqueued.
-	// Returns an error wrapping `ErrFlowInstanceNotFound` if no active instance exists for the given `flowID`.
-	ActiveManagedQueue(flowID string) (ManagedQueue, error)
-
-	// ManagedQueue retrieves a specific (potentially draining) `ManagedQueue` instance from this shard. This allows a
-	// worker to continue dispatching items from queues that are draining as part of a flow update.
-	// Returns an error wrapping `ErrFlowInstanceNotFound` if no instance for the given flowID and priority exists.
-	ManagedQueue(flowID string, priority uint) (ManagedQueue, error)
+	// ManagedQueue retrieves the managed queue for the given, unique `types.FlowKey`. This is the primary method for
+	// accessing a specific flow's queue for either enqueueing or dispatching requests.
+	//
+	// Returns an error wrapping `ErrPriorityBandNotFound` if the priority specified in the `key` is not configured, or
+	// `ErrFlowInstanceNotFound` if no instance exists for the given `key`.
+	ManagedQueue(key types.FlowKey) (ManagedQueue, error)
 
-	// IntraFlowDispatchPolicy retrieves a flow's configured `framework.IntraFlowDispatchPolicy` for this shard.
+	// IntraFlowDispatchPolicy retrieves a flow's configured `framework.IntraFlowDispatchPolicy` for this shard,
+	// identified by its unique `types.FlowKey`.
 	// The registry guarantees that a non-nil default policy (as configured at the priority-band level) is returned if
-	// none is specified on the flow itself.
+	// none is specified for the flow.
 	// Returns an error wrapping `ErrFlowInstanceNotFound` if the flow instance does not exist.
-	IntraFlowDispatchPolicy(flowID string, priority uint) (framework.IntraFlowDispatchPolicy, error)
+	IntraFlowDispatchPolicy(key types.FlowKey) (framework.IntraFlowDispatchPolicy, error)
 
 	// InterFlowDispatchPolicy retrieves a priority band's configured `framework.InterFlowDispatchPolicy` for this shard.
 	// The registry guarantees that a non-nil default policy is returned if none is configured for the band.
@@ -63,8 +147,8 @@ type RegistryShard interface {
 	InterFlowDispatchPolicy(priority uint) (framework.InterFlowDispatchPolicy, error)
 
 	// PriorityBandAccessor retrieves a read-only accessor for a given priority level, providing a view of the band's
-	// state as seen by this specific shard. This is the primary entry point for inter-flow dispatch policies that
-	// need to inspect and compare multiple flow queues within the same priority band.
+	// state as seen by this specific shard. This is the primary entry point for inter-flow dispatch policies that need to
+	// inspect and compare multiple flow queues within the same priority band.
 	// Returns an error wrapping `ErrPriorityBandNotFound` if the priority level is not configured.
 	PriorityBandAccessor(priority uint) (framework.PriorityBandAccessor, error)
 
@@ -80,26 +164,33 @@ type RegistryShard interface {
 }
 
 // ManagedQueue defines the interface for a flow's queue instance on a specific shard.
-// It wraps an underlying `framework.SafeQueue`, augmenting it with lifecycle validation against the `FlowRegistry` and
-// integrating atomic statistics updates.
+// It acts as a stateful decorator around an underlying `framework.SafeQueue`.
 //
 // # Conformance
 //
-//   - All methods (including those embedded from `framework.SafeQueue`) MUST be goroutine-safe.
-//   - The `Add()` method MUST reject new items if the queue has been marked as "draining" by the `FlowRegistry`,
-//     ensuring that lifecycle changes are respected even by consumers holding a stale pointer to the queue.
-//   - All mutating methods (`Add()`, `Remove()`, `Cleanup()`, `Drain()`) MUST atomically update relevant statistics
-//     (e.g., queue length, byte size).
+//   - All methods MUST be goroutine-safe.
+//   - All mutating methods (`Add()`, `Remove()`, etc.) MUST ensure that the underlying queue state and the statistics
+//     (`Len`, `ByteSize`) are updated atomically relative to each other.
 type ManagedQueue interface {
 	framework.SafeQueue
 
-	// FlowQueueAccessor returns a read-only, flow-aware accessor for this queue.
-	// This accessor is primarily used by policy plugins to inspect the queue's state in a structured way.
-	//
+	// FlowQueueAccessor returns a read-only, flow-aware accessor for this queue, used by policy plugins.
 	// Conformance: This method MUST NOT return nil.
 	FlowQueueAccessor() framework.FlowQueueAccessor
 }
 
+// AggregateStats holds globally aggregated statistics for the entire `FlowRegistry`.
+type AggregateStats struct {
+	// TotalCapacityBytes is the globally configured maximum total byte size limit across all priority bands and shards.
+	TotalCapacityBytes uint64
+	// TotalByteSize is the total byte size of all items currently queued across the entire system.
+	TotalByteSize uint64
+	// TotalLen is the total number of items currently queued across the entire system.
+	TotalLen uint64
+	// PerPriorityBandStats maps each configured priority level to its globally aggregated statistics.
+	PerPriorityBandStats map[uint]PriorityBandStats
+}
+
 // ShardStats holds statistics for a single internal shard within the `FlowRegistry`.
 type ShardStats struct {
 	// TotalCapacityBytes is the optional, maximum total byte size limit aggregated across all priority bands within this
@@ -112,35 +203,22 @@ type ShardStats struct {
 	// TotalLen is the total number of items currently queued across all priority bands within this shard.
 	TotalLen uint64
 	// PerPriorityBandStats maps each configured priority level to its statistics within this shard.
+	// The capacity values within represent this shard's partition of the global band capacity.
 	// The key is the numerical priority level.
 	// All configured priority levels are guaranteed to be represented.
 	PerPriorityBandStats map[uint]PriorityBandStats
 }
 
-// DeepCopy returns a deep copy of the `ShardStats`.
-func (s *ShardStats) DeepCopy() ShardStats {
-	if s == nil {
-		return ShardStats{}
-	}
-	newStats := *s
-	if s.PerPriorityBandStats != nil {
-		newStats.PerPriorityBandStats = make(map[uint]PriorityBandStats, len(s.PerPriorityBandStats))
-		for k, v := range s.PerPriorityBandStats {
-			newStats.PerPriorityBandStats[k] = v.DeepCopy()
-		}
-	}
-	return newStats
-}
-
 // PriorityBandStats holds aggregated statistics for a single priority band.
 type PriorityBandStats struct {
 	// Priority is the numerical priority level this struct describes.
 	Priority uint
-	// PriorityName is an optional, human-readable name for the priority level (e.g., "Critical", "Sheddable").
+	// PriorityName is a human-readable name for the priority band (e.g., "Critical", "Sheddable").
+	// The registry configuration requires this field, so it is guaranteed to be non-empty.
 	PriorityName string
-	// CapacityBytes is the configured maximum total byte size for this priority band, aggregated across all items in
-	// all flow queues within this band. If scoped to a shard, its value represents the configured band limit for the
-	// `FlowRegistry` partitioned for this shard.
+	// CapacityBytes is the configured maximum total byte size for this priority band.
+	// When viewed via `AggregateStats`, this is the global limit. When viewed via `ShardStats`, this is the partitioned
+	// value for that specific shard.
 	// The `controller.FlowController` enforces this limit.
 	// A default non-zero value is guaranteed if not configured.
 	CapacityBytes uint64
@@ -149,11 +227,3 @@ type PriorityBandStats struct {
 	// Len is the total number of items currently queued in this priority band.
 	Len uint64
 }
-
-// DeepCopy returns a deep copy of the `PriorityBandStats`.
-func (s *PriorityBandStats) DeepCopy() PriorityBandStats {
-	if s == nil {
-		return PriorityBandStats{}
-	}
-	return *s
-}
diff --git a/pkg/epp/flowcontrol/controller/internal/filter.go b/pkg/epp/flowcontrol/controller/internal/filter.go
index f7e7313..0a06692 100644
--- a/pkg/epp/flowcontrol/controller/internal/filter.go
+++ b/pkg/epp/flowcontrol/controller/internal/filter.go
@@ -23,12 +23,13 @@ import (
 
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types"
 	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
 )
 
 // BandFilter is a function that acts as a pre-policy gate. It takes a complete view of a priority band and returns a
-// subset of flow IDs that are considered viable candidates for a subsequent policy decision. It can also return a
-// boolean signal to pause the entire operation for the band.
+// potentially filtered `framework.PriorityBandAccessor` containing only the flows that are viable candidates for a
+// subsequent policy decision. It can also return a boolean signal to pause the entire operation for the band.
 //
 // This abstraction decouples the logic of determining *viability* (e.g., is a flow subject to backpressure?) from the
 // logic of *selection* (e.g., which of the viable flows is the fairest to pick next?). This separation simplifies the
@@ -39,13 +40,13 @@ import (
 // can be chained to apply different viability criteria. For example, a future filter could be developed to temporarily
 // exclude a "misbehaving" flow that is causing repeated errors, quarantining it from policy consideration.
 //
-// A nil `allowedFlows` map indicates that no filtering is necessary and all flows in the band are visible.
-// This provides a zero-allocation fast path for the common case where no flows are being filtered.
+// A nil returned `PriorityBandAccessor` indicates that no filtering was necessary and the original accessor should be
+// used. This provides a zero-allocation fast path for the common case where no flows are being filtered.
 type BandFilter func(
 	ctx context.Context,
 	band framework.PriorityBandAccessor,
 	logger logr.Logger,
-) (allowedFlows map[string]struct{}, shouldPause bool)
+) (filteredBand framework.PriorityBandAccessor, shouldPause bool)
 
 // NewSaturationFilter creates a `BandFilter` that uses the provided `contracts.SaturationDetector` to determine which
 // flows are dispatchable. This is the standard filter used in the production `FlowController` for the dispatch
@@ -55,7 +56,7 @@ func NewSaturationFilter(sd contracts.SaturationDetector) BandFilter {
 		ctx context.Context,
 		band framework.PriorityBandAccessor,
 		logger logr.Logger,
-	) (map[string]struct{}, bool) {
+	) (framework.PriorityBandAccessor, bool) {
 		// Phase 1: Implement the current global saturation check.
 		if sd.IsSaturated(ctx) {
 			logger.V(logutil.VERBOSE).Info("System saturated, pausing dispatch for this shard.")
@@ -63,7 +64,8 @@ func NewSaturationFilter(sd contracts.SaturationDetector) BandFilter {
 		}
 
 		// Phase 2 (Future): This is where per-flow saturation logic would go.
-		// It would iterate `band`, call `IsSaturated(ctx, flowID)`, and build a filtered map of allowed flows.
+		// It would iterate `band`, call `IsSaturated(ctx, flowID)`, and build a filtered map of allowed flows,
+		// then return `newSubsetPriorityBandAccessor(band, allowedFlows)`.
 		// For now, no per-flow filtering is done. Return nil to signal the fast path.
 		return nil, false // Do not pause, and do not filter any flows.
 	}
@@ -73,31 +75,28 @@ func NewSaturationFilter(sd contracts.SaturationDetector) BandFilter {
 // It implements the `framework.PriorityBandAccessor` interface, ensuring that any policy operating on it will only
 // see the allowed flows, regardless of which accessor method is used. This provides correctness by construction.
 //
-// For performance, it pre-computes a slice of the allowed flow IDs at creation time, making subsequent calls to
-// `FlowIDs()` an O(1) operation with zero allocations.
+// For performance, it pre-computes a slice of the allowed flows at creation time, making subsequent calls to
+// `FlowKeys()` an O(1) operation with zero allocations.
 type subsetPriorityBandAccessor struct {
 	originalAccessor  framework.PriorityBandAccessor
-	allowedFlows      map[string]struct{}
-	allowedFlowsSlice []string
+	allowedFlows      map[types.FlowKey]struct{}
+	allowedFlowsSlice []types.FlowKey
 }
 
 var _ framework.PriorityBandAccessor = &subsetPriorityBandAccessor{}
 
 // newSubsetPriorityBandAccessor creates a new filtered view of a priority band.
-func newSubsetPriorityBandAccessor(
-	original framework.PriorityBandAccessor,
-	allowed map[string]struct{},
-) *subsetPriorityBandAccessor {
-	// Pre-compute the slice of flow IDs for performance.
-	ids := make([]string, 0, len(allowed))
-	for id := range allowed {
-		ids = append(ids, id)
+func newSubsetPriorityBandAccessor(original framework.PriorityBandAccessor, allowed []types.FlowKey) *subsetPriorityBandAccessor {
+	// Pre-compute the map for efficient lookups in `Queue()` and `IterateQueues()`.
+	allowedMap := make(map[types.FlowKey]struct{}, len(allowed))
+	for _, k := range allowed {
+		allowedMap[k] = struct{}{}
 	}
 
 	return &subsetPriorityBandAccessor{
 		originalAccessor:  original,
-		allowedFlows:      allowed,
-		allowedFlowsSlice: ids,
+		allowedFlows:      allowedMap,
+		allowedFlowsSlice: allowed,
 	}
 }
 
@@ -111,19 +110,21 @@ func (s *subsetPriorityBandAccessor) PriorityName() string {
 	return s.originalAccessor.PriorityName()
 }
 
-// FlowIDs returns a slice of all flow IDs within this priority band that are in the allowed subset.
+// FlowKeys returns a slice of the composite `types.FlowKey`s for every flow instance currently active within this
+// priority band that are in the allowed subset.
 // This is an O(1) operation because the slice is pre-computed at creation.
-func (s *subsetPriorityBandAccessor) FlowIDs() []string {
+func (s *subsetPriorityBandAccessor) FlowKeys() []types.FlowKey {
 	return s.allowedFlowsSlice
 }
 
-// Queue returns a `framework.FlowQueueAccessor` for the specified `flowID` within this priority band, but only if it is
+// Queue returns a `framework.FlowQueueAccessor` for the specified `ID` within this priority band, but only if it is
 // in the allowed subset. This is an O(1) map lookup. If the flow is not in the allowed subset, it returns nil.
-func (s *subsetPriorityBandAccessor) Queue(flowID string) framework.FlowQueueAccessor {
-	if _, ok := s.allowedFlows[flowID]; !ok {
+func (s *subsetPriorityBandAccessor) Queue(id string) framework.FlowQueueAccessor {
+	key := types.FlowKey{ID: id, Priority: s.Priority()}
+	if _, ok := s.allowedFlows[key]; !ok {
 		return nil
 	}
-	return s.originalAccessor.Queue(flowID)
+	return s.originalAccessor.Queue(id)
 }
 
 // IterateQueues executes the given `callback` for each `framework.FlowQueueAccessor` in the allowed subset of this
@@ -132,7 +133,7 @@ func (s *subsetPriorityBandAccessor) Queue(flowID string) framework.FlowQueueAcc
 // efficient than iterating over a pre-computed slice of IDs.
 func (s *subsetPriorityBandAccessor) IterateQueues(callback func(queue framework.FlowQueueAccessor) bool) {
 	s.originalAccessor.IterateQueues(func(queue framework.FlowQueueAccessor) bool {
-		if _, ok := s.allowedFlows[queue.FlowSpec().ID]; ok {
+		if _, ok := s.allowedFlows[queue.FlowKey()]; ok {
 			// This queue is in the allowed set, so execute the callback.
 			if !callback(queue) {
 				return false // The callback requested to stop, so we stop the outer iteration too.
diff --git a/pkg/epp/flowcontrol/controller/internal/processor.go b/pkg/epp/flowcontrol/controller/internal/processor.go
index cef5b35..7f9c8ee 100644
--- a/pkg/epp/flowcontrol/controller/internal/processor.go
+++ b/pkg/epp/flowcontrol/controller/internal/processor.go
@@ -199,36 +199,37 @@ func (sp *ShardProcessor) Enqueue(item *flowItem) {
 // main `Run` goroutine, making its "check-then-act" logic for capacity safe.
 func (sp *ShardProcessor) enqueue(item *flowItem) {
 	req := item.OriginalRequest()
+	key := req.FlowKey()
+
 	logger := log.FromContext(req.Context()).WithName("enqueue").WithValues(
-		"flowID", req.FlowID(),
+		"flowKey", key,
+		"flowID", key.ID,
+		"priority", key.Priority,
 		"reqID", req.ID(),
 		"reqByteSize", req.ByteSize(),
 	)
 
-	managedQ, err := sp.shard.ActiveManagedQueue(req.FlowID())
+	managedQ, err := sp.shard.ManagedQueue(key)
 	if err != nil {
-		// This is a significant configuration error; an active queue should exist for a valid flow.
-		finalErr := fmt.Errorf("configuration error: failed to get active queue for flow %q: %w", req.FlowID(), err)
+		finalErr := fmt.Errorf("configuration error: failed to get queue for flow key %s: %w", key, err)
 		logger.Error(finalErr, "Rejecting item.")
 		item.finalize(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr))
 		return
 	}
-	priority := managedQ.FlowQueueAccessor().FlowSpec().Priority
-	logger = logger.WithValues("priority", priority)
 
-	band, err := sp.shard.PriorityBandAccessor(priority)
+	band, err := sp.shard.PriorityBandAccessor(key.Priority)
 	if err != nil {
-		finalErr := fmt.Errorf("configuration error: failed to get priority band for priority %d: %w", priority, err)
+		finalErr := fmt.Errorf("configuration error: failed to get priority band for priority %d: %w", key.Priority, err)
 		logger.Error(finalErr, "Rejecting item.")
 		item.finalize(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr))
 		return
 	}
 	logger = logger.WithValues("priorityName", band.PriorityName())
 
-	if !sp.hasCapacity(priority, req.ByteSize()) {
+	if !sp.hasCapacity(key.Priority, req.ByteSize()) {
 		// This is an expected outcome, not a system error. Log at the default level with rich context.
 		stats := sp.shard.Stats()
-		bandStats := stats.PerPriorityBandStats[priority]
+		bandStats := stats.PerPriorityBandStats[key.Priority]
 		logger.V(logutil.DEFAULT).Info("Rejecting request, queue at capacity",
 			"outcome", types.QueueOutcomeRejectedCapacity,
 			"shardTotalBytes", stats.TotalByteSize,
@@ -250,15 +251,14 @@ func (sp *ShardProcessor) enqueue(item *flowItem) {
 	//     eventually find and evict any finalized item that slips through this check and is added to a queue.
 	if item.isFinalized() {
 		outcome, err := item.FinalState()
-		logger.V(logutil.VERBOSE).Info("Item finalized before adding to queue, ignoring.",
-			"outcome", outcome, "err", err)
+		logger.V(logutil.VERBOSE).Info("Item finalized before adding to queue, ignoring.", "outcome", outcome, "err", err)
 		return
 	}
 
 	// This is the point of commitment. After this call, the item is officially in the queue and is the responsibility of
 	// the dispatch or cleanup loops to finalize.
 	if err := managedQ.Add(item); err != nil {
-		finalErr := fmt.Errorf("failed to add item to queue for flow %q: %w", req.FlowID(), err)
+		finalErr := fmt.Errorf("failed to add item to queue for flow key %s: %w", key, err)
 		logger.Error(finalErr, "Rejecting item.")
 		item.finalize(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr))
 		return
@@ -309,27 +309,25 @@ func (sp *ShardProcessor) dispatchCycle(ctx context.Context) bool {
 	// This could be abstracted into a third policy tier (e.g., an `InterBandDispatchPolicy`) if more complex scheduling
 	// between bands, such as Weighted Fair Queuing (WFQ), is ever required. For now, strict priority is sufficient.
 	for _, priority := range sp.shard.AllOrderedPriorityLevels() {
-		band, err := sp.shard.PriorityBandAccessor(priority)
+		originalBand, err := sp.shard.PriorityBandAccessor(priority)
 		if err != nil {
 			baseLogger.Error(err, "Failed to get PriorityBandAccessor, skipping band", "priority", priority)
 			continue
 		}
-		logger := baseLogger.WithValues("priority", priority, "priorityName", band.PriorityName())
+		logger := baseLogger.WithValues("priority", priority, "priorityName", originalBand.PriorityName())
 
 		// Apply the configured filter to get a view of only the dispatchable flows.
-		allowedFlows, shouldPause := sp.dispatchFilter(ctx, band, logger)
+		dispatchableBand, shouldPause := sp.dispatchFilter(ctx, originalBand, logger)
 		if shouldPause {
 			return false // A global gate told us to stop the entire cycle.
 		}
-
-		dispatchableBand := band
-		if allowedFlows != nil {
-			// An explicit subset of flows is allowed; create a filtered view.
-			dispatchableBand = newSubsetPriorityBandAccessor(band, allowedFlows)
+		if dispatchableBand == nil {
+			// A nil return from the filter indicates the fast path: no filtering was needed.
+			dispatchableBand = originalBand
 		}
 
 		// Pass the (potentially filtered) band to the policies.
-		item, dispatchPriority, err := sp.selectItem(dispatchableBand, logger)
+		item, err := sp.selectItem(dispatchableBand, logger)
 		if err != nil {
 			// The error handling strategy depends on the type of failure (inter- vs. intra-flow).
 			if errors.Is(err, errIntraFlow) {
@@ -344,9 +342,14 @@ func (sp *ShardProcessor) dispatchCycle(ctx context.Context) bool {
 			logger.V(logutil.TRACE).Info("No item selected by dispatch policies, skipping band")
 			continue
 		}
-		logger = logger.WithValues("flowID", item.OriginalRequest().FlowID(), "reqID", item.OriginalRequest().ID())
-
-		if err := sp.dispatchItem(item, dispatchPriority, logger); err != nil {
+		logger = logger.WithValues(
+			"flowKey", item.OriginalRequest().FlowKey(),
+			"flowID", item.OriginalRequest().FlowKey().ID,
+			"flowPriority", item.OriginalRequest().FlowKey().Priority,
+			"reqID", item.OriginalRequest().ID(),
+			"reqByteSize", item.OriginalRequest().ByteSize())
+
+		if err := sp.dispatchItem(item, logger); err != nil {
 			// All errors from dispatchItem are considered intra-flow and unrecoverable for this band in this cycle.
 			logger.Error(err, "Failed to dispatch item, skipping priority band for this cycle")
 			continue
@@ -363,52 +366,52 @@ func (sp *ShardProcessor) dispatchCycle(ctx context.Context) bool {
 func (sp *ShardProcessor) selectItem(
 	band framework.PriorityBandAccessor,
 	logger logr.Logger,
-) (types.QueueItemAccessor, uint, error) {
+) (types.QueueItemAccessor, error) {
 	interP, err := sp.shard.InterFlowDispatchPolicy(band.Priority())
 	if err != nil {
-		return nil, 0, fmt.Errorf("%w: could not get InterFlowDispatchPolicy: %w", errInterFlow, err)
+		return nil, fmt.Errorf("%w: could not get InterFlowDispatchPolicy: %w", errInterFlow, err)
 	}
 	queue, err := interP.SelectQueue(band)
 	if err != nil {
-		return nil, 0, fmt.Errorf("%w: InterFlowDispatchPolicy %q failed to select queue: %w",
+		return nil, fmt.Errorf("%w: InterFlowDispatchPolicy %q failed to select queue: %w",
 			errInterFlow, interP.Name(), err)
 	}
 	if queue == nil {
 		logger.V(logutil.TRACE).Info("No queue selected by InterFlowDispatchPolicy")
-		return nil, 0, nil
-	}
-	logger = logger.WithValues("selectedFlowID", queue.FlowSpec().ID)
-
-	priority := queue.FlowSpec().Priority
-	intraP, err := sp.shard.IntraFlowDispatchPolicy(queue.FlowSpec().ID, priority)
+		return nil, nil
+	}
+	key := queue.FlowKey()
+	logger = logger.WithValues(
+		"selectedFlowKey", key,
+		"selectedFlowID", key.ID,
+		"selectedFlowPriority", key.Priority)
+	intraP, err := sp.shard.IntraFlowDispatchPolicy(key)
 	if err != nil {
 		// This is an intra-flow failure because we have already successfully selected a queue.
-		return nil, 0, fmt.Errorf("%w: could not get IntraFlowDispatchPolicy for flow %q: %w",
-			errIntraFlow, queue.FlowSpec().ID, err)
+		return nil, fmt.Errorf("%w: could not get IntraFlowDispatchPolicy for flow %q: %w", errIntraFlow, key, err)
 	}
 	item, err := intraP.SelectItem(queue)
 	if err != nil {
-		return nil, 0, fmt.Errorf("%w: IntraFlowDispatchPolicy %q failed to select item for flow %q: %w",
-			errIntraFlow, intraP.Name(), queue.FlowSpec().ID, err)
+		return nil, fmt.Errorf("%w: IntraFlowDispatchPolicy %q failed to select item for flow %q: %w",
+			errIntraFlow, intraP.Name(), key, err)
 	}
 	if item == nil {
 		logger.V(logutil.TRACE).Info("No item selected by IntraFlowDispatchPolicy")
-		return nil, 0, nil
+		return nil, nil
 	}
-	return item, priority, nil
+	return item, nil
 }
 
 // dispatchItem handles the final steps of dispatching an item after it has been selected by policies. This includes
 // removing it from its queue, checking for last-minute expiry, and finalizing its outcome.
-func (sp *ShardProcessor) dispatchItem(itemAcc types.QueueItemAccessor, priority uint, logger logr.Logger) error {
+func (sp *ShardProcessor) dispatchItem(itemAcc types.QueueItemAccessor, logger logr.Logger) error {
 	logger = logger.WithName("dispatchItem")
 
 	req := itemAcc.OriginalRequest()
 	// We must look up the queue by its specific priority, as a flow might have draining queues at other levels.
-	managedQ, err := sp.shard.ManagedQueue(req.FlowID(), priority)
+	managedQ, err := sp.shard.ManagedQueue(req.FlowKey())
 	if err != nil {
-		return fmt.Errorf("%w: failed to get ManagedQueue for flow %q at priority %d: %w",
-			errIntraFlow, req.FlowID(), priority, err)
+		return fmt.Errorf("%w: failed to get ManagedQueue for flow %q: %w", errIntraFlow, req.FlowKey(), err)
 	}
 
 	// The core mutation: remove the item from the queue.
@@ -418,7 +421,7 @@ func (sp *ShardProcessor) dispatchItem(itemAcc types.QueueItemAccessor, priority
 		// selected by the policy and the time this function is called.
 		logger.V(logutil.VERBOSE).Info("Item already removed from queue, likely by expiry cleanup", "err", err)
 		return fmt.Errorf("%w: failed to remove item %q from queue for flow %q: %w",
-			errIntraFlow, req.ID(), req.FlowID(), err)
+			errIntraFlow, req.ID(), req.FlowKey(), err)
 	}
 
 	removedItem, ok := removedItemAcc.(*flowItem)
@@ -625,8 +628,12 @@ func (sp *ShardProcessor) processAllQueuesConcurrently(
 		go func() {
 			defer wg.Done()
 			for q := range tasks {
-				queueLogger := logger.WithValues("flowID", q.FlowSpec().ID, "priority", q.FlowSpec().Priority)
-				managedQ, err := sp.shard.ManagedQueue(q.FlowSpec().ID, q.FlowSpec().Priority)
+				key := q.FlowKey()
+				queueLogger := logger.WithValues(
+					"flowKey", key,
+					"flowID", key.ID,
+					"flowPriority", key.Priority)
+				managedQ, err := sp.shard.ManagedQueue(key)
 				if err != nil {
 					queueLogger.Error(err, "Failed to get ManagedQueue")
 					continue
diff --git a/pkg/epp/flowcontrol/framework/mocks/mocks.go b/pkg/epp/flowcontrol/framework/mocks/mocks.go
index 804ba71..b8715b7 100644
--- a/pkg/epp/flowcontrol/framework/mocks/mocks.go
+++ b/pkg/epp/flowcontrol/framework/mocks/mocks.go
@@ -41,7 +41,7 @@ type MockFlowQueueAccessor struct {
 	PeekHeadErrV  error
 	PeekTailV     types.QueueItemAccessor
 	PeekTailErrV  error
-	FlowSpecV     types.FlowSpecification
+	FlowKeyV      types.FlowKey
 	ComparatorV   framework.ItemComparator
 	CapabilitiesV []framework.QueueCapability
 }
@@ -50,7 +50,7 @@ func (m *MockFlowQueueAccessor) Name() string                              { ret
 func (m *MockFlowQueueAccessor) Len() int                                  { return m.LenV }
 func (m *MockFlowQueueAccessor) ByteSize() uint64                          { return m.ByteSizeV }
 func (m *MockFlowQueueAccessor) Comparator() framework.ItemComparator      { return m.ComparatorV }
-func (m *MockFlowQueueAccessor) FlowSpec() types.FlowSpecification         { return m.FlowSpecV }
+func (m *MockFlowQueueAccessor) FlowKey() types.FlowKey                    { return m.FlowKeyV }
 func (m *MockFlowQueueAccessor) Capabilities() []framework.QueueCapability { return m.CapabilitiesV }
 
 func (m *MockFlowQueueAccessor) PeekHead() (types.QueueItemAccessor, error) {
@@ -69,7 +69,7 @@ var _ framework.FlowQueueAccessor = &MockFlowQueueAccessor{}
 type MockPriorityBandAccessor struct {
 	PriorityV         uint
 	PriorityNameV     string
-	FlowIDsFunc       func() []string
+	FlowKeysFunc      func() []types.FlowKey
 	QueueFunc         func(flowID string) framework.FlowQueueAccessor
 	IterateQueuesFunc func(callback func(queue framework.FlowQueueAccessor) (keepIterating bool))
 }
@@ -77,16 +77,16 @@ type MockPriorityBandAccessor struct {
 func (m *MockPriorityBandAccessor) Priority() uint       { return m.PriorityV }
 func (m *MockPriorityBandAccessor) PriorityName() string { return m.PriorityNameV }
 
-func (m *MockPriorityBandAccessor) FlowIDs() []string {
-	if m.FlowIDsFunc != nil {
-		return m.FlowIDsFunc()
+func (m *MockPriorityBandAccessor) FlowKeys() []types.FlowKey {
+	if m.FlowKeysFunc != nil {
+		return m.FlowKeysFunc()
 	}
 	return nil
 }
 
-func (m *MockPriorityBandAccessor) Queue(flowID string) framework.FlowQueueAccessor {
+func (m *MockPriorityBandAccessor) Queue(id string) framework.FlowQueueAccessor {
 	if m.QueueFunc != nil {
-		return m.QueueFunc(flowID)
+		return m.QueueFunc(id)
 	}
 	return nil
 }
diff --git a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/roundrobin/roundrobin.go b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/roundrobin/roundrobin.go
index 9906dc1..c71b801 100644
--- a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/roundrobin/roundrobin.go
+++ b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/roundrobin/roundrobin.go
@@ -24,6 +24,7 @@ import (
 
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types"
 )
 
 // RoundRobinPolicyName is the name of the Round Robin policy implementation.
@@ -70,7 +71,7 @@ func (p *roundRobin) SelectQueue(band framework.PriorityBandAccessor) (framework
 // "RoundRobin" displacement policy is introduced, while keeping the dispatch policy's public API stable.
 type iterator struct {
 	mu           sync.Mutex
-	lastSelected string
+	lastSelected *types.FlowKey
 }
 
 // newIterator creates a new round-robin Iterator.
@@ -85,34 +86,35 @@ func (r *iterator) selectNextQueue(band framework.PriorityBandAccessor) framewor
 	r.mu.Lock()
 	defer r.mu.Unlock()
 
-	flowIDs := band.FlowIDs()
-	if len(flowIDs) == 0 {
-		r.lastSelected = "" // Reset state if no flows are present
+	keys := band.FlowKeys()
+	if len(keys) == 0 {
+		r.lastSelected = nil // Reset state if no flows are present
 		return nil
 	}
-	slices.Sort(flowIDs)
+	// Sort for deterministic ordering.
+	slices.SortFunc(keys, func(a, b types.FlowKey) int { return a.Compare(b) })
 
 	startIndex := 0
-	if r.lastSelected != "" {
+	if r.lastSelected != nil {
 		// Find the index of the last selected flow.
 		// If it's not found (e.g., the flow was removed), we'll start from the beginning.
-		if idx := slices.Index(flowIDs, r.lastSelected); idx != -1 {
-			startIndex = (idx + 1) % len(flowIDs)
+		if idx := slices.Index(keys, *r.lastSelected); idx != -1 {
+			startIndex = (idx + 1) % len(keys)
 		}
 	}
 
-	numFlows := len(flowIDs)
+	numFlows := len(keys)
 	for i := 0; i < numFlows; i++ {
 		currentIdx := (startIndex + i) % numFlows
-		currentFlowID := flowIDs[currentIdx]
-		queue := band.Queue(currentFlowID)
+		currentKey := keys[currentIdx]
+		queue := band.Queue(currentKey.ID)
 		if queue != nil && queue.Len() > 0 {
-			r.lastSelected = currentFlowID
+			r.lastSelected = &currentKey
 			return queue
 		}
 	}
 
 	// No non-empty queue was found.
-	r.lastSelected = ""
+	r.lastSelected = nil
 	return nil
 }
diff --git a/pkg/epp/flowcontrol/framework/policies.go b/pkg/epp/flowcontrol/framework/policies.go
index da6d5c4..5fc3e64 100644
--- a/pkg/epp/flowcontrol/framework/policies.go
+++ b/pkg/epp/flowcontrol/framework/policies.go
@@ -144,8 +144,8 @@ type InterFlowDispatchPolicy interface {
 // FlowQueueAccessor provides a policy-facing, read-only view of a single flow's queue.
 // It combines general queue inspection methods (embedded via `QueueInspectionMethods`) with flow-specific metadata.
 //
-// Instances of `FlowQueueAccessor` are vended by a `contracts.ManagedQueue` and are the primary means by which policies
-// inspect individual queue state.
+// Instances of `FlowQueueAccessor` are typically obtained via a `PriorityBandAccessor` and are the primary means by
+// which policies inspect individual queue state.
 //
 // Conformance: Implementations MUST ensure all methods (including those embedded from `QueueInspectionMethods`) are
 // goroutine-safe for concurrent access.
@@ -156,12 +156,12 @@ type FlowQueueAccessor interface {
 	// This is determined by the `IntraFlowDispatchPolicy` associated with this queue's flow.
 	Comparator() ItemComparator
 
-	// FlowSpec returns the `types.FlowSpecification` of the flow this queue accessor is associated with.
-	// This provides essential context (like `FlowID`) to policies.
-	FlowSpec() types.FlowSpecification
+	// FlowKey returns the unique, immutable `types.FlowKey` of the flow instance this queue accessor is associated with.
+	// This provides essential context (like the logical grouping `ID` and `Priority`) to policies.
+	FlowKey() types.FlowKey
 }
 
-// PriorityBandAccessor provides a read-only view into a specific priority band within the `ports.FlowRegistry`.
+// PriorityBandAccessor provides a read-only view into a specific priority band within the `contracts.FlowRegistry`.
 // It allows the `controller.FlowController` and inter-flow policies to inspect the state of all flow queues within that
 // band.
 //
@@ -173,15 +173,20 @@ type PriorityBandAccessor interface {
 	// PriorityName returns the human-readable name of this priority band.
 	PriorityName() string
 
-	// FlowIDs returns a slice of all flow IDs within this priority band.
-	// The order of items in the slice is not guaranteed unless specified by the implementations (e.g., for deterministic
-	// testing scenarios).
-	FlowIDs() []string
+	// FlowKeys returns a slice of the composite `types.FlowKey`s for every flow instance currently active within this
+	// priority band.
+	// This method provides the complete, canonical identifiers for all flows in the band. The caller can use the `ID`
+	// field from each key to look up a specific queue via the `Queue(id string)` method.
+	// The order of keys in the returned slice is not guaranteed unless specified by the implementations (e.g., for
+	// deterministic testing scenarios).
+	FlowKeys() []types.FlowKey
 
-	// Queue returns a `FlowQueueAccessor` for the specified `flowID` within this priority band.
+	// Queue returns a `FlowQueueAccessor` for the specified logical grouping `ID` within this priority band.
+	// Note: This uses the logical `ID` string (`types.FlowKey.ID`), not the full `types.FlowKey`, as the priority is
+	// already defined by the accessor's scope.
 	//
-	// Conformance: Implementations MUST return nil if the `flowID` is not found in this band.
-	Queue(flowID string) FlowQueueAccessor
+	// Conformance: Implementations MUST return nil if the `ID` is not found in this band.
+	Queue(id string) FlowQueueAccessor
 
 	// IterateQueues executes the given `callback` for each `FlowQueueAccessor` in this priority band.
 	// Iteration stops if the `callback` returns false. The order of iteration is not guaranteed unless specified by the
diff --git a/pkg/epp/flowcontrol/registry/config.go b/pkg/epp/flowcontrol/registry/config.go
index ed98ab8..d404e78 100644
--- a/pkg/epp/flowcontrol/registry/config.go
+++ b/pkg/epp/flowcontrol/registry/config.go
@@ -19,6 +19,7 @@ package registry
 import (
 	"errors"
 	"fmt"
+	"time"
 
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework"
@@ -30,67 +31,213 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/queue/listqueue"
 )
 
-// Config holds the master configuration for the entire `FlowRegistry`. It serves as the top-level blueprint, defining
-// global capacity limits and the structure of its priority bands.
+// =============================================================================
+// Default Values
+// =============================================================================
+
+const (
+	// defaultPriorityBandMaxBytes is the default global capacity for a priority band if not explicitly configured.
+	// It is set to 1 GB.
+	defaultPriorityBandMaxBytes uint64 = 1_000_000_000
+	// defaultIntraFlowDispatchPolicy is the default policy for selecting items within a single flow's queue.
+	defaultIntraFlowDispatchPolicy intra.RegisteredPolicyName = fcfs.FCFSPolicyName
+	// defaultInterFlowDispatchPolicy is the default policy for selecting which flow's queue to service next.
+	defaultInterFlowDispatchPolicy inter.RegisteredPolicyName = besthead.BestHeadPolicyName
+	// defaultQueue is the default queue implementation for flows.
+	defaultQueue queue.RegisteredQueueName = listqueue.ListQueueName
+	// defaultInitialShardCount is the default number of parallel shards to create when the registry is initialized.
+	defaultInitialShardCount int = 1
+	// defaultFlowGCTimeout is the default duration of inactivity after which an idle flow is garbage collected.
+	// This also serves as the interval for the periodic garbage collection scan.
+	defaultFlowGCTimeout time.Duration = 5 * time.Minute
+	// defaultEventChannelBufferSize is the default size of the buffered channel for control plane events.
+	defaultEventChannelBufferSize int = 4096
+)
+
+// =============================================================================
+// Global Configuration (FlowRegistry)
+// =============================================================================
+
+// Config holds the master configuration for the entire `FlowRegistry`.
+// It serves as the top-level blueprint, defining global settings and the templates for its priority bands.
 //
-// This master configuration is validated and defaulted once at startup. It is then partitioned and distributed to each
-// internal `registryShard`, ensuring a consistent and predictable state across the system.
+// This configuration is validated and defaulted once at startup. It is then used to generate partitioned `ShardConfig`
+// instances for each internal `registryShard`.
 type Config struct {
 	// MaxBytes defines an optional, global maximum total byte size limit aggregated across all priority bands and shards.
 	// The `controller.FlowController` enforces this limit in addition to per-band capacity limits.
-	//
-	// Optional: Defaults to 0, which signifies that the global limit is ignored.
+	// A value of 0 signifies that this global limit is ignored, and only per-band limits apply.
+	// Optional: Defaults to 0.
 	MaxBytes uint64
 
-	// PriorityBands defines the set of priority bands managed by the `FlowRegistry`. The configuration for each band,
-	// including its default policies and queue types, is specified here.
-	//
+	// PriorityBands defines the set of priority band templates managed by the `FlowRegistry`.
+	// The configuration for each band, including its default policies and queue types, is specified here.
+	// These templates are used to generate partitioned `ShardPriorityBandConfig`s.
 	// Required: At least one `PriorityBandConfig` must be provided for a functional registry.
 	PriorityBands []PriorityBandConfig
+
+	// InitialShardCount specifies the number of parallel shards to create when the registry is initialized.
+	// This value must be greater than zero.
+	// Optional: Defaults to `defaultInitialShardCount` (1).
+	InitialShardCount int
+
+	// FlowGCTimeout defines the interval at which the registry scans for and garbage collects idle flows. A flow is
+	// collected if it has been observed to be Idle for at least one full scan interval.
+	// Optional: Defaults to `defaultFlowGCTimeout` (5 minutes).
+	FlowGCTimeout time.Duration
+
+	// EventChannelBufferSize defines the size of the buffered channel used for internal control plane events.
+	// A larger buffer can absorb larger bursts of events (e.g., from many queues becoming non-empty simultaneously)
+	// without blocking the data path, but consumes more memory.
+	// This value must be greater than zero.
+	// Optional: Defaults to `defaultEventChannelBufferSize` (4096).
+	EventChannelBufferSize int
+
+	// priorityBandMap provides O(1) lookups of `PriorityBandConfig` by priority level.
+	// Crucially, this map serves as a correctness mechanism. It ensures that accessors return a safe, stable pointer to
+	// the correct element within this specific configuration instance, preventing common "pointer-to-loop-variable"
+	// errors, especially across deep copies or partitioning.
+	// It is populated during validation and when the config is copied or partitioned.
+	priorityBandMap map[uint]*PriorityBandConfig
+
+	// Factory functions used for plugin instantiation during configuration validation.
+	// These enable dependency injection for unit testing the validation logic.
+	interFlowDispatchPolicyFactory interFlowDispatchPolicyFactory
+	intraFlowDispatchPolicyFactory intraFlowDispatchPolicyFactory
+	queueFactory                   queueFactory
 }
 
-// partition calculates and returns a new `Config` with capacity values partitioned for a specific shard.
-// This method ensures that the total capacity is distributed as evenly as possible across all shards.
-func (c *Config) partition(shardIndex, totalShards int) (*Config, error) {
-	if totalShards <= 0 || shardIndex < 0 || shardIndex >= totalShards {
-		return nil, fmt.Errorf("invalid shard partitioning arguments: shardIndex=%d, totalShards=%d",
-			shardIndex, totalShards)
-	}
+// PriorityBandConfig defines the configuration template for a single priority band.
+// It establishes the default behaviors (such as queueing and dispatch policies) and total capacity limits for all flows
+// that operate at this priority level.
+type PriorityBandConfig struct {
+	// Priority is the unique numerical priority level for this band.
+	// Convention: Lower numerical values indicate higher priority (e.g., 0 is highest).
+	// Required.
+	Priority uint
 
-	partitionValue := func(total uint64) uint64 {
-		if total == 0 {
-			return 0
-		}
-		base := total / uint64(totalShards)
-		remainder := total % uint64(totalShards)
-		if uint64(shardIndex) < remainder {
-			return base + 1
-		}
-		return base
-	}
+	// PriorityName is a human-readable name for this priority band (e.g., "Critical", "Standard").
+	// It must be unique across all priority bands in the configuration.
+	// Required.
+	PriorityName string
 
-	newCfg := &Config{
-		MaxBytes:      partitionValue(c.MaxBytes),
-		PriorityBands: make([]PriorityBandConfig, len(c.PriorityBands)),
-	}
+	// IntraFlowDispatchPolicy specifies the default name of the policy used to select a request from within a single
+	// flow's queue in this band.
+	// Optional: Defaults to `defaultIntraFlowDispatchPolicy` ("FCFS").
+	IntraFlowDispatchPolicy intra.RegisteredPolicyName
 
-	for i, band := range c.PriorityBands {
-		newBand := band                                  // Copy the original config
-		newBand.MaxBytes = partitionValue(band.MaxBytes) // Overwrite with the partitioned value
-		newCfg.PriorityBands[i] = newBand
-	}
+	// InterFlowDispatchPolicy specifies the name of the policy used to select which flow's queue to service next from
+	// this band.
+	// Optional: Defaults to `defaultInterFlowDispatchPolicy` ("BestHead").
+	InterFlowDispatchPolicy inter.RegisteredPolicyName
+
+	// Queue specifies the default name of the `framework.SafeQueue` implementation for flow queues in this band.
+	// Optional: Defaults to `defaultQueue` ("ListQueue").
+	Queue queue.RegisteredQueueName
 
+	// MaxBytes defines the maximum total byte size for this priority band, aggregated across all shards.
+	// Optional: Defaults to `defaultPriorityBandMaxBytes` (1 GB).
+	MaxBytes uint64
+}
+
+// NewConfig performs validation and initialization, returning a guaranteed-valid `Config` object.
+// This is the required constructor for creating a new configuration. It applies provided functional options (primarily
+// for testing) and does not mutate the input `cfg`.
+func NewConfig(cfg Config, opts ...configOption) (*Config, error) {
+	newCfg := cfg.deepCopy()
+	for _, opt := range opts {
+		opt(newCfg)
+	}
+	if err := newCfg.validateAndApplyDefaults(); err != nil {
+		return nil, err
+	}
 	return newCfg, nil
 }
 
-// validateAndApplyDefaults checks the configuration for validity and populates any empty fields with system defaults.
-// This method should be called once by the registry before it initializes any shards.
+// =============================================================================
+// Shard-Level Configuration
+// =============================================================================
+
+// ShardConfig holds the partitioned configuration for a single `registryShard`.
+// It contains only the data relevant to that shard and is derived from the global `Config`.
+type ShardConfig struct {
+	// MaxBytes is this shard's partitioned portion of the global `Config.MaxBytes` limit.
+	MaxBytes uint64
+
+	// PriorityBands holds the partitioned configuration for each priority band managed by this shard.
+	PriorityBands []ShardPriorityBandConfig
+
+	// priorityBandMap provides O(1) lookups of `ShardPriorityBandConfig` by priority level.
+	// It serves as a correctness mechanism, ensuring that accessors return a safe, stable pointer to the correct element
+	// within this specific shard configuration instance.
+	priorityBandMap map[uint]*ShardPriorityBandConfig
+}
+
+// ShardPriorityBandConfig holds the partitioned configuration for a single priority band within a single shard.
+type ShardPriorityBandConfig struct {
+	// Priority is the unique numerical priority level for this band.
+	Priority uint
+	// PriorityName is a unique human-readable name for this priority band.
+	PriorityName string
+	// IntraFlowDispatchPolicy is the name of the policy for dispatch within a flow's queue.
+	IntraFlowDispatchPolicy intra.RegisteredPolicyName
+	// InterFlowDispatchPolicy is the name of the policy for dispatch between flow queues.
+	InterFlowDispatchPolicy inter.RegisteredPolicyName
+	// Queue is the name of the queue implementation to use.
+	Queue queue.RegisteredQueueName
+	// MaxBytes is this shard's partitioned portion of this band's global capacity limit.
+	// The `controller.FlowController` enforces this limit for this specific shard.
+	MaxBytes uint64
+}
+
+// getBandConfig finds and returns the shard-level configuration for a specific priority level.
+// Returns an error wrapping `contracts.ErrPriorityBandNotFound` if the priority is not configured.
+func (sc *ShardConfig) getBandConfig(priority uint) (*ShardPriorityBandConfig, error) {
+	if band, ok := sc.priorityBandMap[priority]; ok {
+		return band, nil
+	}
+	return nil, fmt.Errorf("shard config for priority %d not found: %w", priority, contracts.ErrPriorityBandNotFound)
+}
+
+// =============================================================================
+// Internal Methods and Helpers
+// =============================================================================
+
+// --- Validation and Defaulting ---
+
+// validateAndApplyDefaults checks the global configuration for validity (including plugin compatibility) and mutates
+// the receiver to populate any empty fields with system defaults. It also initializes internal lookup maps.
 func (c *Config) validateAndApplyDefaults() error {
+	// Apply defaults to top-level fields.
+	if c.InitialShardCount <= 0 {
+		c.InitialShardCount = defaultInitialShardCount
+	}
+	if c.FlowGCTimeout <= 0 {
+		c.FlowGCTimeout = defaultFlowGCTimeout
+	}
+	if c.EventChannelBufferSize <= 0 {
+		c.EventChannelBufferSize = defaultEventChannelBufferSize
+	}
+
+	// Ensure the DI factories are initialized for production use if `NewConfig` was called without options.
+	if c.interFlowDispatchPolicyFactory == nil {
+		c.interFlowDispatchPolicyFactory = inter.NewPolicyFromName
+	}
+	if c.intraFlowDispatchPolicyFactory == nil {
+		c.intraFlowDispatchPolicyFactory = intra.NewPolicyFromName
+	}
+	if c.queueFactory == nil {
+		c.queueFactory = queue.NewQueueFromName
+	}
+
 	if len(c.PriorityBands) == 0 {
 		return errors.New("config validation failed: at least one priority band must be defined")
 	}
 
-	priorities := make(map[uint]struct{}) // Keep track of seen priorities
+	// Validate and default each priority band.
+	priorities := make(map[uint]struct{})
+	priorityNames := make(map[string]struct{})
+	c.priorityBandMap = make(map[uint]*PriorityBandConfig, len(c.PriorityBands))
 
 	for i := range c.PriorityBands {
 		band := &c.PriorityBands[i]
@@ -100,29 +247,37 @@ func (c *Config) validateAndApplyDefaults() error {
 		priorities[band.Priority] = struct{}{}
 
 		if band.PriorityName == "" {
-			return errors.New("config validation failed: PriorityName is required for all priority bands")
+			return fmt.Errorf("config validation failed: PriorityName is required for priority band %d", band.Priority)
+		}
+		if _, exists := priorityNames[band.PriorityName]; exists {
+			return fmt.Errorf("config validation failed: duplicate priority name %q found", band.PriorityName)
 		}
+		priorityNames[band.PriorityName] = struct{}{}
+
 		if band.IntraFlowDispatchPolicy == "" {
-			band.IntraFlowDispatchPolicy = fcfs.FCFSPolicyName
+			band.IntraFlowDispatchPolicy = defaultIntraFlowDispatchPolicy
 		}
 		if band.InterFlowDispatchPolicy == "" {
-			band.InterFlowDispatchPolicy = besthead.BestHeadPolicyName
+			band.InterFlowDispatchPolicy = defaultInterFlowDispatchPolicy
 		}
 		if band.Queue == "" {
-			band.Queue = listqueue.ListQueueName
+			band.Queue = defaultQueue
+		}
+		if band.MaxBytes == 0 {
+			band.MaxBytes = defaultPriorityBandMaxBytes
 		}
 
-		// After defaulting, validate that the chosen plugins are compatible.
-		if err := validateBandCompatibility(*band); err != nil {
+		if err := c.validateBandCompatibility(*band); err != nil {
 			return err
 		}
+		c.priorityBandMap[band.Priority] = band
 	}
 	return nil
 }
 
-// validateBandCompatibility verifies that a band's default policy is compatible with its default queue type.
-func validateBandCompatibility(band PriorityBandConfig) error {
-	policy, err := intra.NewPolicyFromName(band.IntraFlowDispatchPolicy)
+// validateBandCompatibility verifies that a band's configured queue type has the necessary capabilities.
+func (c *Config) validateBandCompatibility(band PriorityBandConfig) error {
+	policy, err := c.intraFlowDispatchPolicyFactory(band.IntraFlowDispatchPolicy)
 	if err != nil {
 		return fmt.Errorf("failed to validate policy %q for priority band %d: %w",
 			band.IntraFlowDispatchPolicy, band.Priority, err)
@@ -130,23 +285,22 @@ func validateBandCompatibility(band PriorityBandConfig) error {
 
 	requiredCapabilities := policy.RequiredQueueCapabilities()
 	if len(requiredCapabilities) == 0 {
-		return nil // Policy has no specific requirements.
+		return nil // The policy is compatible with any queue type.
 	}
 
 	// Create a temporary queue instance to inspect its capabilities.
-	tempQueue, err := queue.NewQueueFromName(band.Queue, nil)
+	tempQueue, err := c.queueFactory(band.Queue, nil)
 	if err != nil {
-		return fmt.Errorf("failed to inspect queue type %q for priority band %d: %w", band.Queue, band.Priority, err)
+		return fmt.Errorf("failed to instantiate queue type %q for capability inspection (priority band %d): %w",
+			band.Queue, band.Priority, err)
 	}
 	queueCapabilities := tempQueue.Capabilities()
 
-	// Build a set of the queue's capabilities for efficient lookup.
 	capabilitySet := make(map[framework.QueueCapability]struct{}, len(queueCapabilities))
 	for _, cap := range queueCapabilities {
 		capabilitySet[cap] = struct{}{}
 	}
 
-	// Check if all required capabilities are present.
 	for _, req := range requiredCapabilities {
 		if _, ok := capabilitySet[req]; !ok {
 			return fmt.Errorf(
@@ -160,45 +314,151 @@ func validateBandCompatibility(band PriorityBandConfig) error {
 			)
 		}
 	}
-
 	return nil
 }
 
-// PriorityBandConfig defines the configuration for a single priority band within the `FlowRegistry`. It establishes the
-// default behaviors (such as queueing and dispatch policies) and capacity limits for all flows that operate at this
-// priority level.
-type PriorityBandConfig struct {
-	// Priority is the numerical priority level for this band.
-	// Convention: Lower numerical values indicate higher priority (e.g., 0 is highest).
-	//
-	// Required.
-	Priority uint
+// --- Partitioning Logic ---
 
-	// PriorityName is a human-readable name for this priority band (e.g., "Critical", "Standard", "Sheddable").
-	//
-	// Required.
-	PriorityName string
+// partition derives a `ShardConfig` from the master `Config` for a specific shard index.
+// It calculates the capacity distribution, ensuring that the total global capacity is distributed completely and
+// evenly.
+func (c *Config) partition(shardIndex, totalShards int) *ShardConfig {
+	shardCfg := &ShardConfig{
+		MaxBytes:        partitionUint64(c.MaxBytes, shardIndex, totalShards),
+		PriorityBands:   make([]ShardPriorityBandConfig, len(c.PriorityBands)),
+		priorityBandMap: make(map[uint]*ShardPriorityBandConfig, len(c.PriorityBands)),
+	}
 
-	// IntraFlowDispatchPolicy specifies the default name of the registered policy used to select a specific request to
-	// dispatch next from within a single flow's queue in this band. This default can be overridden on a per-flow basis.
-	//
-	// Optional: If empty, a system default (e.g., "FCFS") is used.
-	IntraFlowDispatchPolicy intra.RegisteredPolicyName
+	for i, template := range c.PriorityBands {
+		shardBandCfg := ShardPriorityBandConfig{
+			Priority:                template.Priority,
+			PriorityName:            template.PriorityName,
+			IntraFlowDispatchPolicy: template.IntraFlowDispatchPolicy,
+			InterFlowDispatchPolicy: template.InterFlowDispatchPolicy,
+			Queue:                   template.Queue,
+			MaxBytes:                partitionUint64(template.MaxBytes, shardIndex, totalShards),
+		}
+		shardCfg.PriorityBands[i] = shardBandCfg
 
-	// InterFlowDispatchPolicy specifies the name of the registered policy used to select which flow's queue to service
-	// next from this band.
-	//
-	// Optional: If empty, a system default (e.g., "BestHead") is used.
-	InterFlowDispatchPolicy inter.RegisteredPolicyName
+		// Crucial: We must take the address of the element within the new slice (`shardCfg.PriorityBands`) to ensure the
+		// map pointers are correct for the newly created ShardConfig instance.
+		shardCfg.priorityBandMap[shardBandCfg.Priority] = &shardCfg.PriorityBands[i]
+	}
+	return shardCfg
+}
 
-	// Queue specifies the default name of the registered SafeQueue implementation to be used for flow queues within this
-	// band.
-	//
-	// Optional: If empty, a system default (e.g., "ListQueue") is used.
-	Queue queue.RegisteredQueueName
+// partitionUint64 distributes a total uint64 value across a number of partitions.
+// It distributes the remainder of the division one by one to the first few partitions.
+func partitionUint64(total uint64, partitionIndex, totalPartitions int) uint64 {
+	if totalPartitions <= 0 {
+		panic(fmt.Sprintf("invariant violation: cannot partition into %d partitions", totalPartitions))
+	}
+	if total == 0 {
+		return 0
+	}
+	base := total / uint64(totalPartitions)
+	remainder := total % uint64(totalPartitions)
+	// Distribute the remainder. The first `remainder` partitions get one extra from the total.
+	// For example, if total=10 and partitions=3, base=3, remainder=1. Partition 0 gets 3+1=4, partitions 1 and 2 get 3.
+	if uint64(partitionIndex) < remainder {
+		base++
+	}
+	return base
+}
 
-	// MaxBytes defines the maximum total byte size for this specific priority band, aggregated across all shards.
-	//
-	// Optional: If not set, a system default (e.g., 1 GB) is applied.
-	MaxBytes uint64
+// --- Test-Only Dependency Injection ---
+
+// configOption is a function that applies a configuration change to a `Config` object.
+// This is used exclusively for dependency injection in tests and should not be used in production code.
+type configOption func(*Config)
+
+// interFlowDispatchPolicyFactory defines the signature for a function that creates an
+// `framework.InterFlowDispatchPolicy` instance from its registered name.
+// It serves as an abstraction over the concrete `inter.NewPolicyFromName` factory, enabling dependency injection for
+// testing validation logic.
+type interFlowDispatchPolicyFactory func(name inter.RegisteredPolicyName) (framework.InterFlowDispatchPolicy, error)
+
+// intraFlowDispatchPolicyFactory defines the signature for a function that creates an
+// `framework.IntraFlowDispatchPolicy` instance from its registered name.
+// It serves as an abstraction over the concrete `intra.NewPolicyFromName` factory, enabling dependency injection for
+// testing validation logic.
+type intraFlowDispatchPolicyFactory func(name intra.RegisteredPolicyName) (framework.IntraFlowDispatchPolicy, error)
+
+// queueFactory defines the signature for a function that creates a `framework.SafeQueue` instance from its registered
+// name and a given `framework.ItemComparator`.
+// It serves as an abstraction over the concrete `queue.NewQueueFromName` factory, enabling dependency injection for
+// testing validation logic.
+type queueFactory func(
+	name queue.RegisteredQueueName,
+	comparator framework.ItemComparator,
+) (framework.SafeQueue, error)
+
+// withInterFlowDispatchPolicyFactory returns a test-only `configOption` that overrides the factory function for
+// creating `framework.InterFlowDispatchPolicy` instances.
+// This is used exclusively for testing validation logic.
+// test-only
+func withInterFlowDispatchPolicyFactory(factory interFlowDispatchPolicyFactory) configOption {
+	return func(c *Config) {
+		c.interFlowDispatchPolicyFactory = factory
+	}
+}
+
+// withIntraFlowDispatchPolicyFactory returns a test-only `configOption` that overrides the factory function for
+// creating `framework.IntraFlowDispatchPolicy` instances.
+// This is used exclusively for testing validation logic.
+// test-only
+func withIntraFlowDispatchPolicyFactory(factory intraFlowDispatchPolicyFactory) configOption {
+	return func(c *Config) {
+		c.intraFlowDispatchPolicyFactory = factory
+	}
+}
+
+// withQueueFactory returns a test-only `configOption` that overrides the factory function for creating
+// `framework.SafeQueue` instances.
+// This is used exclusively for testing validation logic.
+// test-only
+func withQueueFactory(factory queueFactory) configOption {
+	return func(c *Config) {
+		c.queueFactory = factory
+	}
+}
+
+// --- Internal Utilities ---
+
+// deepCopy creates a deep copy of the `Config` object.
+func (c *Config) deepCopy() *Config {
+	if c == nil {
+		return nil
+	}
+	newCfg := &Config{
+		MaxBytes:                       c.MaxBytes,
+		InitialShardCount:              c.InitialShardCount,
+		FlowGCTimeout:                  c.FlowGCTimeout,
+		EventChannelBufferSize:         c.EventChannelBufferSize,
+		PriorityBands:                  make([]PriorityBandConfig, len(c.PriorityBands)),
+		priorityBandMap:                make(map[uint]*PriorityBandConfig, len(c.PriorityBands)),
+		interFlowDispatchPolicyFactory: c.interFlowDispatchPolicyFactory,
+		intraFlowDispatchPolicyFactory: c.intraFlowDispatchPolicyFactory,
+		queueFactory:                   c.queueFactory,
+	}
+
+	// PriorityBandConfig contains only value types, so a slice copy is sufficient for a deep copy.
+	copy(newCfg.PriorityBands, c.PriorityBands)
+
+	// Crucial: We must rebuild the map and take the address of the elements within the new slice (`newCfg.PriorityBands`)
+	// to ensure the map pointers are correct for the newly created `Config` instance.
+	for i := range newCfg.PriorityBands {
+		band := &newCfg.PriorityBands[i]
+		newCfg.priorityBandMap[band.Priority] = band
+	}
+	return newCfg
+}
+
+// getBandConfig finds and returns the global configuration template for a specific priority level.
+// Returns an error wrapping `contracts.ErrPriorityBandNotFound` if the priority is not configured.
+func (c *Config) getBandConfig(priority uint) (*PriorityBandConfig, error) {
+	if band, ok := c.priorityBandMap[priority]; ok {
+		return band, nil
+	}
+	return nil, fmt.Errorf("global config for priority %d not found: %w", priority, contracts.ErrPriorityBandNotFound)
 }
diff --git a/pkg/epp/flowcontrol/registry/doc.go b/pkg/epp/flowcontrol/registry/doc.go
index 521ad6e..3355c77 100644
--- a/pkg/epp/flowcontrol/registry/doc.go
+++ b/pkg/epp/flowcontrol/registry/doc.go
@@ -14,27 +14,116 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-// Package registry provides the concrete implementation of the Flow Registry.
+// Package registry provides the concrete implementation of the `contracts.FlowRegistry`.
 //
-// As the stateful control plane for the entire Flow Control system, this package is responsible for managing the
-// lifecycle of all flows, queues, and policies. It serves as the "adapter" that implements the service "ports"
-// (interfaces) defined in the `contracts` package. It provides a sharded, concurrent-safe view of its state to the
-// `controller.FlowController` workers, enabling scalable, parallel request processing.
+// As the stateful control plane, this package manages the lifecycle of all flows, queues, and policies. It provides a
+// sharded, concurrent-safe view of its state to the `controller.FlowController` workers, enabling scalable, parallel
+// request processing.
 //
-// # Key Components
+// # Architecture: Composite, Sharded, and Separated Concerns
 //
-//   - `FlowRegistry`: The future top-level administrative object that will manage the entire system, including shard
-//     lifecycles and flow registration. (Not yet implemented).
+// The registry separates the control plane (orchestration) from the data plane (request processing state).
 //
-//   - `registryShard`: A concrete implementation of the `contracts.RegistryShard` interface. It represents a single,
-//     concurrent-safe slice of the registry's state, containing a set of priority bands and the flow queues within
-//     them. This is the primary object a `controller.FlowController` worker interacts with.
+//   - `FlowRegistry`: The Control Plane. The top-level orchestrator and single source of truth. It centralizes complex
+//     operations: flow registration, garbage collection (GC) coordination, and shard scaling.
 //
-//   - `managedQueue`: A concrete implementation of the `contracts.ManagedQueue` interface. It acts as a stateful
-//     decorator around a `framework.SafeQueue`, adding critical registry-level functionality such as atomic statistics
-//     tracking and lifecycle state enforcement (active vs. draining).
+//   - `registryShard`: The Data Plane Slice. A concurrent-safe "slice" of the registry's total state. It provides a
+//     read-optimized view for `FlowController` workers.
 //
-//   - `Config`: The top-level configuration object that defines the structure and default behaviors of the registry,
-//     including the definition of priority bands and default policy selections. This configuration is partitioned and
-//     distributed to each `registryShard`.
+//   - `managedQueue`: The Stateful Decorator. A wrapper around a `framework.SafeQueue`. It augments the queue with
+//     atomic statistics tracking and signaling state transitions to the control plane.
+//
+// # Data Flow and Interaction Model
+//
+// The data path (Enqueue/Dispatch) is optimized for minimal latency and maximum concurrency.
+//
+// Enqueue Path:
+//  1. The `FlowController`'s distributor selects an active `registryShard`.
+//  2. The distributor calls `shard.ManagedQueue(flowKey)` (acquires `RLock`).
+//  3. The distributor calls `managedQueue.Add(item)`.
+//  4. `managedQueue` atomically updates the queue and its stats and signals the control plane.
+//
+// Dispatch Path:
+//  1. A `FlowController` worker iterates over its assigned `registryShard`.
+//  2. The worker uses policies and accessors to select the next item (acquires `RLock`).
+//  3. The worker calls `managedQueue.Remove(handle)`.
+//  4. `managedQueue` atomically updates the queue and its stats and signals the control plane.
+//
+// # Concurrency Strategy: Multi-Tiered and Optimized
+//
+// The registry maximizes performance on the hot path while ensuring strict correctness for complex state transitions:
+//
+//   - Serialized Control Plane (Actor Model): The `FlowRegistry` uses a single background goroutine to process all
+//     state change events serially, eliminating race conditions in the control plane.
+//
+//   - Sharding (Data Plane Parallelism): State is partitioned across multiple `registryShard` instances, allowing the
+//     data path to scale linearly.
+//
+//   - Lock-Free Data Path (Atomics): Statistics aggregation (Shard/Registry level) uses lock-free atomics.
+//
+//   - Strict Consistency (Hybrid Locking): `managedQueue` uses a hybrid locking model (Mutex for writes, Atomics for
+//     reads) to guarantee strict consistency between queue contents and statistics, which is required for GC
+//     correctness.
+//
+// (See the `FlowRegistry` struct documentation for detailed locking rules).
+//
+// # Garbage Collection: The "Trust but Verify" Pattern
+//
+// The registry handles the race condition between asynchronous data path activity and synchronous GC. The control plane
+// maintains an eventually consistent cache (`flowState`).
+//
+// The registry uses a periodic, generational "Trust but Verify" pattern. It identifies candidate flows using the cache.
+// Before deletion, it performs a "Verify" step: it synchronously acquires write locks on ALL shards and queries the
+// ground truth (live queue counters). This provides strong consistency when needed.
+//
+// (See the `garbageCollectFlowLocked` function documentation for detailed steps).
+//
+// # Scalability Characteristics and Trade-offs
+//
+// The architecture prioritizes data path throughput and correctness, introducing specific trade-offs:
+//
+//   - Data Path Throughput (Excellent): Scales linearly with the number of shards and benefits from lock-free
+//     statistics updates.
+//
+//   - GC Latency Impact (Trade-off): The GC "Verify" step requires locking all shards (O(N)). This briefly pauses the
+//     data path. As the shard count (N) increases, this may impact P99 latency. This trade-off guarantees correctness.
+//
+//   - Control Plane Responsiveness during Scale-Up (Trade-off): Scaling up requires synchronizing all existing flows
+//     (M) onto the new shards (K). This O(M*K) operation occurs under the main control plane lock. If M is large, this
+//     operation may block the control plane.
+//
+// # Event-Driven State Machine and Lifecycle Scenarios
+//
+// The system relies on atomic state transitions to generate reliable, edge-triggered signals. These signals are sent
+// reliably; if the event channel is full, the sender blocks, applying necessary backpressure to ensure no events are
+// lost, preventing state divergence.
+//
+// The following scenarios detail how the registry handles lifecycle events:
+//
+// New Flow Registration: A new flow instance `F1` (`FlowKey{ID: "A", Priority: 10}`) is registered.
+//
+//  1. `managedQueue` instances are created for `F1` on all shards.
+//  2. The `flowState` cache marks `F1` as Idle. If it remains Idle, it will eventually be garbage collected.
+//
+// Flow Activity/Inactivity:
+//
+//  1. When the first request for `F1` is enqueued, the queue signals `BecameNonEmpty`. The control plane marks `F1`
+//     as Active, protecting it from GC.
+//  2. When the last request is dispatched globally, the queues signal `BecameEmpty`. The control plane updates the
+//     cache, and `F1` is now considered Idle by the GC scanner.
+//
+// "Changing" Flow Priority: Traffic for `ID: "A"` needs to shift from `Priority: 10` to `Priority: 20`.
+//
+//  1. The caller registers a new flow instance, `F2` (`FlowKey{ID: "A", Priority: 20}`).
+//  2. The system treats `F1` and `F2` as independent entities (Immutable `FlowKey` design).
+//  3. As `F1` becomes Idle, it is automatically garbage collected. This achieves the outcome gracefully without complex
+//     state migration logic.
+//
+// Shard Scaling:
+//
+//   - Scale-Up: New shards are created and marked Active. Existing flows are synchronized onto the new shards.
+//     Configuration is re-partitioned.
+//   - Scale-Down: Targeted shards transition to Draining (stop accepting new work). Configuration is re-partitioned
+//     across remaining active shards. When a Draining shard is empty, it signals `BecameDrained` and is removed by the
+//     control plane.
 package registry
diff --git a/pkg/epp/flowcontrol/registry/events.go b/pkg/epp/flowcontrol/registry/events.go
new file mode 100644
index 0000000..c76a38e
--- /dev/null
+++ b/pkg/epp/flowcontrol/registry/events.go
@@ -0,0 +1,127 @@
+/*
+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.
+*/
+
+package registry
+
+import (
+	"fmt"
+
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types"
+)
+
+// =============================================================================
+// Control Plane Events (Transport)
+// =============================================================================
+
+// event is a marker interface for internal state machine events processed by the `FlowRegistry`'s event loop.
+type event interface {
+	isEvent()
+}
+
+// queueStateChangedEvent is sent when a `managedQueue`'s state changes, carrying a `queueStateSignal`.
+type queueStateChangedEvent struct {
+	shardID string
+	key     types.FlowKey
+	signal  queueStateSignal
+}
+
+func (queueStateChangedEvent) isEvent() {}
+
+// shardStateChangedEvent is sent when a `registryShard`'s state changes, carrying a `shardStateSignal`.
+type shardStateChangedEvent struct {
+	shardID string
+	signal  shardStateSignal
+}
+
+func (shardStateChangedEvent) isEvent() {}
+
+// syncEvent is used exclusively for testing to synchronize test execution with the `FlowRegistry`'s event loop.
+// It allows tests to wait until all preceding events have been processed, eliminating the need for polling.
+// test-only
+type syncEvent struct {
+	// doneCh is used by the event loop to signal back to the sender that the sync event has been processed.
+	doneCh chan struct{}
+}
+
+func (syncEvent) isEvent() {}
+
+// =============================================================================
+// Control Plane Signals and Callbacks
+// =============================================================================
+
+// --- Queue Signals ---
+
+// queueStateSignal is an enum describing the edge-triggered state change events emitted by a `managedQueue`.
+type queueStateSignal int
+
+const (
+	// queueStateSignalBecameEmpty is sent when a queue transitions from non-empty to empty.
+	// Trigger: Len > 0 -> Len == 0. Used for inactivity GC tracking.
+	queueStateSignalBecameEmpty queueStateSignal = iota
+
+	// queueStateSignalBecameNonEmpty is sent when a queue transitions from empty to non-empty.
+	// Trigger: Len == 0 -> Len > 0. Used for inactivity GC tracking.
+	queueStateSignalBecameNonEmpty
+)
+
+func (s queueStateSignal) String() string {
+	switch s {
+	case queueStateSignalBecameEmpty:
+		return "QueueBecameEmpty"
+	case queueStateSignalBecameNonEmpty:
+		return "QueueBecameNonEmpty"
+	default:
+		return fmt.Sprintf("Unknown(%d)", s)
+	}
+}
+
+// signalQueueStateFunc defines the callback function that a `managedQueue` uses to signal lifecycle events.
+// Implementations should avoid blocking on internal locks or I/O. However, they are expected to block if the
+// `FlowRegistry`'s event channel is full; this behavior is required to apply backpressure and ensure reliable event
+// delivery for the GC system.
+type signalQueueStateFunc func(key types.FlowKey, signal queueStateSignal)
+
+// --- Shard Signals ---
+
+// shardStateSignal is an enum describing the edge-triggered state change events emitted by a `registryShard`.
+type shardStateSignal int
+
+const (
+	// shardStateSignalBecameDrained is sent when a Draining shard transitions to empty.
+	// Trigger: Transition from `componentStatusDraining` -> `componentStatusDrained`. Used for final GC of the shard.
+	shardStateSignalBecameDrained shardStateSignal = iota
+)
+
+func (s shardStateSignal) String() string {
+	switch s {
+	case shardStateSignalBecameDrained:
+		return "ShardBecameDrained"
+	default:
+		return fmt.Sprintf("Unknown(%d)", s)
+	}
+}
+
+// signalShardStateFunc defines the callback function that a `registryShard` uses to signal its own state changes.
+// Implementations should avoid blocking on internal locks or I/O. However, they are expected to block if the
+// `FlowRegistry`'s event channel is full (see `signalQueueStateFunc` for rationale).
+type signalShardStateFunc func(shardID string, signal shardStateSignal)
+
+// --- Statistics Propagation ---
+
+// propagateStatsDeltaFunc defines the callback function used to propagate statistics changes (deltas) up the hierarchy
+// (Queue -> Shard -> Registry).
+// Implementations MUST be non-blocking (relying on atomics).
+type propagateStatsDeltaFunc func(priority uint, lenDelta, byteSizeDelta int64)
diff --git a/pkg/epp/flowcontrol/registry/flowstate.go b/pkg/epp/flowcontrol/registry/flowstate.go
new file mode 100644
index 0000000..b515d8f
--- /dev/null
+++ b/pkg/epp/flowcontrol/registry/flowstate.go
@@ -0,0 +1,105 @@
+/*
+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.
+*/
+
+package registry
+
+import (
+	"fmt"
+
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types"
+)
+
+// flowState holds all tracking state for a single flow instance within the registry.
+//
+// # Role: The Eventually Consistent Cache for GC
+//
+// This structure is central to the GC logic. It acts as an eventually consistent, cached view of a flow's emptiness
+// status across all shards. It is updated asynchronously via events from the data path.
+//
+// # Concurrency and Consistency Model
+//
+// `flowState` is a passive, non-thread-safe data structure. Access to `flowState` is serialized by `FlowRegistry.mu`.
+//
+// Invariant: Because this state is eventually consistent, it must not be the sole source of truth for destructive
+// operations (like garbage collection). All destructive actions must first verify the live state of the system by
+// consulting the atomic counters on the `managedQueue` instances directly (the "Trust but Verify" pattern).
+type flowState struct {
+	// spec is the desired state of the flow instance, uniquely identified by its immutable `spec.Key`.
+	spec types.FlowSpecification
+	// emptyOnShards tracks the empty status of the flow's queue across all shards.
+	// The key is the shard ID.
+	emptyOnShards map[string]bool
+	// lastActiveGeneration tracks the GC generation number in which this flow was last observed to be Active.
+	// This is used by the periodic GC scanner to identify flows that have been Idle for at least one GC cycle.
+	lastActiveGeneration uint64
+}
+
+// newFlowState creates the initial state for a newly registered flow instance.
+// It initializes the state based on the current set of all shards.
+func newFlowState(spec types.FlowSpecification, allShards []*registryShard) *flowState {
+	s := &flowState{
+		spec:          spec,
+		emptyOnShards: make(map[string]bool, len(allShards)),
+		// A new flow starts at generation 0. This sentinel value indicates to the GC "Mark" phase that the flow is brand
+		// new and should be granted a grace period of one cycle.
+		lastActiveGeneration: gcGenerationNewFlow,
+	}
+	// A new flow instance starts with an empty queue on all shards.
+	for _, shard := range allShards {
+		s.emptyOnShards[shard.id] = true
+	}
+	return s
+}
+
+// update synchronizes the flow's specification. This is a forward-looking method for when non-key fields on the spec
+// become mutable (e.g., a per-flow policy override). It enforces the invariant that the `FlowKey` cannot change.
+func (s *flowState) update(newSpec types.FlowSpecification) {
+	if s.spec.Key != newSpec.Key {
+		// This should be impossible if the FlowRegistry's logic is correct, as flowStates are keyed by FlowKey.
+		panic(fmt.Sprintf("invariant violation: attempted to update flowState for key %v with a new key %v",
+			s.spec.Key, newSpec.Key))
+	}
+	s.spec = newSpec
+}
+
+// handleQueueSignal updates the flow's internal emptiness state based on a signal from one of its queues.
+func (s *flowState) handleQueueSignal(shardID string, signal queueStateSignal) {
+	switch signal {
+	case queueStateSignalBecameEmpty:
+		s.emptyOnShards[shardID] = true
+	case queueStateSignalBecameNonEmpty:
+		s.emptyOnShards[shardID] = false
+	}
+}
+
+// isIdle checks if the flow's queues are empty across the provided set of shards (active and draining).
+//
+// The `shards` parameter defines the scope of the check (the current ground truth). We only verify the state against
+// this list because the internal `emptyOnShards` map might contain stale entries for shards that have been garbage
+// collected by the registry but not yet purged from this specific `flowState`.
+func (s *flowState) isIdle(shards []*registryShard) bool {
+	for _, shard := range shards {
+		if !s.emptyOnShards[shard.id] {
+			return false
+		}
+	}
+	return true
+}
+
+// purgeShard removes a decommissioned shard's ID from the tracking map to prevent memory leaks.
+func (s *flowState) purgeShard(shardID string) {
+	delete(s.emptyOnShards, shardID)
+}
diff --git a/pkg/epp/flowcontrol/registry/lifecycle.go b/pkg/epp/flowcontrol/registry/lifecycle.go
new file mode 100644
index 0000000..8b5ba6e
--- /dev/null
+++ b/pkg/epp/flowcontrol/registry/lifecycle.go
@@ -0,0 +1,56 @@
+/*
+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.
+*/
+
+package registry
+
+import (
+	"fmt"
+)
+
+// =============================================================================
+// Component Lifecycle State Machine
+// =============================================================================
+
+// componentStatus represents the lifecycle state of a `registryShard`.
+// It is manipulated using atomic operations (e.g., `atomic.Int32`) to ensure robust, atomic state transitions.
+type componentStatus int32
+
+const (
+	// componentStatusActive indicates the component is fully operational and accepting new work.
+	componentStatusActive componentStatus = iota
+
+	// componentStatusDraining indicates the component is shutting down. It is not accepting new work, but is still
+	// processing existing work.
+	componentStatusDraining
+
+	// componentStatusDrained indicates the component has finished draining and is empty.
+	// The transition into this state (from `Draining`) occurs exactly once via `CompareAndSwap` and triggers the
+	// `BecameDrained` signal. This acts as an atomic latch for GC.
+	componentStatusDrained
+)
+
+func (s componentStatus) String() string {
+	switch s {
+	case componentStatusActive:
+		return "Active"
+	case componentStatusDraining:
+		return "Draining"
+	case componentStatusDrained:
+		return "Drained"
+	default:
+		return fmt.Sprintf("Unknown(%d)", s)
+	}
+}
diff --git a/pkg/epp/flowcontrol/registry/managedqueue.go b/pkg/epp/flowcontrol/registry/managedqueue.go
index bea7e7e..5d3a670 100644
--- a/pkg/epp/flowcontrol/registry/managedqueue.go
+++ b/pkg/epp/flowcontrol/registry/managedqueue.go
@@ -18,6 +18,7 @@ package registry
 
 import (
 	"fmt"
+	"sync"
 	"sync/atomic"
 
 	"github.com/go-logr/logr"
@@ -28,96 +29,115 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
 )
 
-// parentStatsReconciler defines the callback function that a `managedQueue` uses to propagate its statistics changes up
-// to its parent `registryShard`.
-type parentStatsReconciler func(lenDelta, byteSizeDelta int64)
-
-// managedQueue implements `contracts.ManagedQueue`. It is a stateful decorator that wraps a `framework.SafeQueue`,
-// augmenting it with two critical, registry-level responsibilities:
-//  1. Atomic Statistics: It maintains its own `len` and `byteSize` counters, which are updated atomically. This allows
-//     the parent `registryShard` to aggregate statistics across many queues without locks.
-//  2. Lifecycle Enforcement: It tracks the queue's lifecycle state (active vs. draining) via an `isActive` flag. This
-//     is crucial for graceful flow updates, as it allows the registry to stop new requests from being enqueued while
-//     allowing existing items to be drained.
+// managedQueueCallbacks groups the callback functions that a `managedQueue` uses to communicate with its parent shard.
+type managedQueueCallbacks struct {
+	// propagateStatsDelta is called to propagate statistics changes (e.g., queue length, byte size) up to the parent.
+	propagateStatsDelta propagateStatsDeltaFunc
+	// signalQueueState is called to signal important lifecycle events, such as becoming empty, which are used by the
+	// garbage collector.
+	signalQueueState signalQueueStateFunc
+}
+
+// managedQueue implements `contracts.ManagedQueue`. It acts as a stateful decorator around a `framework.SafeQueue`.
+//
+// # Role: The Stateful Decorator
+//
+// Its primary responsibility is to augment a generic queue implementation with critical registry features: strictly
+// consistent statistics tracking and exactly-once, edge-triggered signaling of state changes (e.g., becoming empty) to
+// the control plane for garbage collection.
+//
+// # Concurrency Model: Hybrid Locking (Mutex + Atomics + `SafeQueue`)
+//
+// The `managedQueue` employs a hybrid locking strategy to guarantee strict consistency between the queue contents and
+// its statistics, while maintaining high performance.
+//
+//  1. Mutex-Protected Writes (`sync.Mutex`): A single mutex protects all mutating operations (Add, Remove, etc.).
+//     This ensures that the update to the underlying queue and the update to the internal counters occur as a single,
+//     atomic transaction. This strict consistency is required for GC correctness.
+//
+//  2. Synchronous Propagation and Ordering: The propagation of statistics deltas (both `Len` and `ByteSize`) occurs
+//     synchronously within this critical section. This strict ordering (`Add` propagation before `Remove` propagation
+//     for the same item) guarantees the non-negative invariant across the entire system (Shard/Registry aggregates).
+//
+//  3. Lock-Free Statistics Reads (Atomics): The counters use `atomic.Int64`. This allows high-frequency accessors
+//     (`Len()`, `ByteSize()`) to read the statistics without acquiring the Mutex.
 //
-// # Statistical Integrity
+//  4. Concurrent Reads (`SafeQueue`): Read operations (e.g., `PeekHead`/`PeekTail` used by policies) rely on the
+//     underlying `framework.SafeQueue`'s concurrency control and do not acquire the `managedQueue`'s write lock.
 //
-// For performance, `managedQueue` maintains its own `len` and `byteSize` fields using atomic operations. This provides
-// O(1) access for the parent `registryShard`'s aggregated statistics without needing to lock the underlying queue.
+// # Statistical Integrity and Invariant Protection
 //
-// This design is predicated on two critical assumptions:
-//  1. Exclusive Access: All mutating operations on the underlying `framework.SafeQueue` MUST be performed exclusively
-//     through this `managedQueue` wrapper. Direct access to the underlying queue will cause statistical drift.
-//  2. In-Process Queue: The `framework.SafeQueue` implementation is an in-process data structure (e.g., a list or
-//     heap). Its state MUST NOT change through external mechanisms. For example, a queue implementation backed by a
-//     distributed cache like Redis with its own TTL-based eviction policy would violate this assumption and lead to
-//     state inconsistency, as items could be removed without notifying the `managedQueue`.
+//  1. Exclusive Access: All mutations on the underlying `SafeQueue` MUST be performed exclusively through this wrapper.
 //
-// This approach avoids the need for the `framework.SafeQueue` interface to return state deltas on each operation,
-// keeping its contract simpler.
+//  2. Non-Autonomous State: The underlying queue implementation must not change state autonomously (e.g., no internal
+//     TTL eviction).
+//
+//  3. Read-Only Proxy: To prevent policy plugins from bypassing statistics tracking, the read-only view is provided via
+//     the `flowQueueAccessor` wrapper. This enforces the read-only contract at the type system level.
 type managedQueue struct {
-	// Note: There is no mutex here. Concurrency control is delegated to the underlying `framework.SafeQueue` and the
-	// atomic operations on the stats fields.
-	queue               framework.SafeQueue
-	dispatchPolicy      framework.IntraFlowDispatchPolicy
-	flowSpec            types.FlowSpecification
-	byteSize            atomic.Uint64
-	len                 atomic.Uint64
-	isActive            atomic.Bool
-	reconcileShardStats parentStatsReconciler
-	logger              logr.Logger
+	// mu protects all mutating operations (writes) on the queue. It ensures that the underlying queue's state and the
+	// atomic counters are updated atomically. Read operations (like `Peek`) do not acquire this lock.
+	mu sync.Mutex
+
+	// queue is the underlying, concurrency-safe queue implementation that this `managedQueue` decorates.
+	queue framework.SafeQueue
+
+	// dispatchPolicy is the intra-flow policy used to select items from this specific queue.
+	dispatchPolicy framework.IntraFlowDispatchPolicy
+
+	// key uniquely identifies the flow instance this queue belongs to.
+	key types.FlowKey
+
+	// Queue-level statistics. Updated under the protection of the `mu` lock, but read lock-free.
+	// Guaranteed to be non-negative.
+	byteSize atomic.Int64
+	len      atomic.Int64
+
+	// parentCallbacks provides the communication channels back to the parent shard.
+	parentCallbacks managedQueueCallbacks
+	logger          logr.Logger
 }
 
+var _ contracts.ManagedQueue = &managedQueue{}
+
 // newManagedQueue creates a new instance of a `managedQueue`.
 func newManagedQueue(
 	queue framework.SafeQueue,
 	dispatchPolicy framework.IntraFlowDispatchPolicy,
-	flowSpec types.FlowSpecification,
+	key types.FlowKey,
 	logger logr.Logger,
-	reconcileShardStats parentStatsReconciler,
+	parentCallbacks managedQueueCallbacks,
 ) *managedQueue {
 	mqLogger := logger.WithName("managed-queue").WithValues(
-		"flowID", flowSpec.ID,
-		"priority", flowSpec.Priority,
+		"flowKey", key,
 		"queueType", queue.Name(),
 	)
 	mq := &managedQueue{
-		queue:               queue,
-		dispatchPolicy:      dispatchPolicy,
-		flowSpec:            flowSpec,
-		reconcileShardStats: reconcileShardStats,
-		logger:              mqLogger,
+		queue:           queue,
+		dispatchPolicy:  dispatchPolicy,
+		key:             key,
+		parentCallbacks: parentCallbacks,
+		logger:          mqLogger,
 	}
-	mq.isActive.Store(true)
 	return mq
 }
 
-// markAsDraining is an internal method called by the parent shard to transition this queue to a draining state.
-// Once a queue is marked as draining, it will no longer accept new items via `Add`.
-func (mq *managedQueue) markAsDraining() {
-	// Use CompareAndSwap to ensure we only log the transition once.
-	if mq.isActive.CompareAndSwap(true, false) {
-		mq.logger.V(logging.DEFAULT).Info("Queue marked as draining")
-	}
-}
-
-// FlowQueueAccessor returns a new `flowQueueAccessor` instance, which provides a read-only, policy-facing view of the
-// queue.
+// FlowQueueAccessor returns a read-only, flow-aware view of this queue.
+// This accessor is primarily used by policy plugins to inspect the queue's state in a structured way.
 func (mq *managedQueue) FlowQueueAccessor() framework.FlowQueueAccessor {
 	return &flowQueueAccessor{mq: mq}
 }
 
-// Add first checks if the queue is active. If it is, it wraps the underlying `framework.SafeQueue.Add` call and
-// atomically updates the queue's and the parent shard's statistics.
+// Add wraps the underlying `framework.SafeQueue.Add` call and atomically updates the queue's and the parent shard's
+// statistics.
 func (mq *managedQueue) Add(item types.QueueItemAccessor) error {
-	if !mq.isActive.Load() {
-		return fmt.Errorf("flow instance %q is not active and cannot accept new requests: %w",
-			mq.flowSpec.ID, contracts.ErrFlowInstanceNotFound)
-	}
+	mq.mu.Lock()
+	defer mq.mu.Unlock()
+
 	if err := mq.queue.Add(item); err != nil {
 		return err
 	}
-	mq.reconcileStats(1, int64(item.OriginalRequest().ByteSize()))
+	mq.propagateStatsDelta(1, int64(item.OriginalRequest().ByteSize()))
 	mq.logger.V(logging.TRACE).Info("Request added to queue", "requestID", item.OriginalRequest().ID())
 	return nil
 }
@@ -125,11 +145,14 @@ func (mq *managedQueue) Add(item types.QueueItemAccessor) error {
 // Remove wraps the underlying `framework.SafeQueue.Remove` call and atomically updates statistics upon successful
 // removal.
 func (mq *managedQueue) Remove(handle types.QueueItemHandle) (types.QueueItemAccessor, error) {
+	mq.mu.Lock()
+	defer mq.mu.Unlock()
+
 	removedItem, err := mq.queue.Remove(handle)
 	if err != nil {
 		return nil, err
 	}
-	mq.reconcileStats(-1, -int64(removedItem.OriginalRequest().ByteSize()))
+	mq.propagateStatsDelta(-1, -int64(removedItem.OriginalRequest().ByteSize()))
 	mq.logger.V(logging.TRACE).Info("Request removed from queue", "requestID", removedItem.OriginalRequest().ID())
 	return removedItem, nil
 }
@@ -137,110 +160,114 @@ func (mq *managedQueue) Remove(handle types.QueueItemHandle) (types.QueueItemAcc
 // Cleanup wraps the underlying `framework.SafeQueue.Cleanup` call and atomically updates statistics for all removed
 // items.
 func (mq *managedQueue) Cleanup(predicate framework.PredicateFunc) (cleanedItems []types.QueueItemAccessor, err error) {
+	mq.mu.Lock()
+	defer mq.mu.Unlock()
+
 	cleanedItems, err = mq.queue.Cleanup(predicate)
-	if err != nil || len(cleanedItems) == 0 {
-		return cleanedItems, err
+	if err != nil {
+		return nil, err
 	}
-
-	var lenDelta int64
-	var byteSizeDelta int64
-	for _, item := range cleanedItems {
-		lenDelta--
-		byteSizeDelta -= int64(item.OriginalRequest().ByteSize())
+	if len(cleanedItems) == 0 {
+		return cleanedItems, nil
 	}
-	mq.reconcileStats(lenDelta, byteSizeDelta)
-	mq.logger.V(logging.DEBUG).Info("Cleaned up queue", "removedItemCount", len(cleanedItems),
-		"lenDelta", lenDelta, "byteSizeDelta", byteSizeDelta)
+	mq.propagateStatsDeltaForRemovedItems(cleanedItems)
+	mq.logger.V(logging.DEBUG).Info("Cleaned up queue", "removedItemCount", len(cleanedItems))
 	return cleanedItems, nil
 }
 
 // Drain wraps the underlying `framework.SafeQueue.Drain` call and atomically updates statistics for all removed items.
 func (mq *managedQueue) Drain() ([]types.QueueItemAccessor, error) {
+	mq.mu.Lock()
+	defer mq.mu.Unlock()
+
 	drainedItems, err := mq.queue.Drain()
-	if err != nil || len(drainedItems) == 0 {
-		return drainedItems, err
+	if err != nil {
+		return nil, err
 	}
-
-	var lenDelta int64
-	var byteSizeDelta int64
-	for _, item := range drainedItems {
-		lenDelta--
-		byteSizeDelta -= int64(item.OriginalRequest().ByteSize())
+	if len(drainedItems) == 0 {
+		return drainedItems, nil
 	}
-	mq.reconcileStats(lenDelta, byteSizeDelta)
-	mq.logger.V(logging.DEBUG).Info("Drained queue", "itemCount", len(drainedItems),
-		"lenDelta", lenDelta, "byteSizeDelta", byteSizeDelta)
+	mq.propagateStatsDeltaForRemovedItems(drainedItems)
+	mq.logger.V(logging.DEBUG).Info("Drained queue", "itemCount", len(drainedItems))
 	return drainedItems, nil
 }
 
-// reconcileStats atomically updates the queue's own statistics and calls the parent shard's reconciler to ensure
-// aggregated stats remain consistent.
-func (mq *managedQueue) reconcileStats(lenDelta, byteSizeDelta int64) {
-	// The use of Add with a negative number on a Uint64 is the standard Go atomic way to perform subtraction, leveraging
-	// two's complement arithmetic.
-	mq.len.Add(uint64(lenDelta))
-	mq.byteSize.Add(uint64(byteSizeDelta))
-	if mq.reconcileShardStats != nil {
-		mq.reconcileShardStats(lenDelta, byteSizeDelta)
-	}
-}
-
-// --- Pass-through and accessor methods ---
-
-// Name returns the name of the underlying queue implementation.
-func (mq *managedQueue) Name() string { return mq.queue.Name() }
-
-// Capabilities returns the capabilities of the underlying queue implementation.
-func (mq *managedQueue) Capabilities() []framework.QueueCapability { return mq.queue.Capabilities() }
-
-// Len returns the number of items in the queue.
-func (mq *managedQueue) Len() int { return int(mq.len.Load()) }
+// --- Pass-through and Accessor Methods ---
 
-// ByteSize returns the total byte size of all items in the queue.
-func (mq *managedQueue) ByteSize() uint64 { return mq.byteSize.Load() }
-
-// PeekHead returns the item at the front of the queue without removing it.
+func (mq *managedQueue) Name() string                               { return mq.queue.Name() }
+func (mq *managedQueue) Capabilities() []framework.QueueCapability  { return mq.queue.Capabilities() }
+func (mq *managedQueue) Len() int                                   { return int(mq.len.Load()) }
+func (mq *managedQueue) ByteSize() uint64                           { return uint64(mq.byteSize.Load()) }
 func (mq *managedQueue) PeekHead() (types.QueueItemAccessor, error) { return mq.queue.PeekHead() }
-
-// PeekTail returns the item at the back of the queue without removing it.
 func (mq *managedQueue) PeekTail() (types.QueueItemAccessor, error) { return mq.queue.PeekTail() }
+func (mq *managedQueue) Comparator() framework.ItemComparator       { return mq.dispatchPolicy.Comparator() }
 
-// Comparator returns the `framework.ItemComparator` that defines this queue's item ordering logic, as dictated by its
-// configured dispatch policy.
-func (mq *managedQueue) Comparator() framework.ItemComparator { return mq.dispatchPolicy.Comparator() }
+// --- Internal Methods ---
 
-var _ contracts.ManagedQueue = &managedQueue{}
-
-// --- flowQueueAccessor ---
+// propagateStatsDelta updates the queue's statistics, signals emptiness events, and propagates the delta.
+//
+// Invariant Check: This function panics if a statistic becomes negative. Because all updates are protected by the
+// `managedQueue.mu` lock, a negative value indicates a critical logic error (e.g., double-counting a removal).
+// This check enforces the non-negative invariant locally, which mathematically guarantees that the aggregated
+// statistics (Shard/Registry level) also remain non-negative.
+func (mq *managedQueue) propagateStatsDelta(lenDelta, byteSizeDelta int64) {
+	// Note: We rely on the caller (Add/Remove/etc.) to hold the `managedQueue.mu` lock.
+
+	newLen := mq.len.Add(lenDelta)
+	if newLen < 0 {
+		panic(fmt.Sprintf("invariant violation: managedQueue length for flow %s became negative (%d)", mq.key, newLen))
+	}
+	mq.byteSize.Add(byteSizeDelta)
+
+	// Evaluate and signal our own state change *before* propagating the statistics to the parent shard. This ensures a
+	// strict, bottom-up event ordering, preventing race conditions where the parent might process its own state change
+	// before the child's signal is handled.
+	// 1. Evaluate GC signals based on the strictly consistent local state.
+	// 2. Propagate the delta up to the Shard/Registry. This propagation is lock-free and eventually consistent.
+	mq.evaluateEmptinessState(newLen-lenDelta, newLen)
+	mq.parentCallbacks.propagateStatsDelta(mq.key.Priority, lenDelta, byteSizeDelta)
+}
 
-// flowQueueAccessor implements `framework.FlowQueueAccessor`. It provides a read-only, policy-facing view of a
-// `managedQueue`.
-type flowQueueAccessor struct {
-	mq *managedQueue
+// evaluateEmptinessState checks if the queue has transitioned between non-empty <-> empty and signals the parent if so.
+func (mq *managedQueue) evaluateEmptinessState(oldLen, newLen int64) {
+	if oldLen > 0 && newLen == 0 {
+		mq.parentCallbacks.signalQueueState(mq.key, queueStateSignalBecameEmpty)
+	} else if oldLen == 0 && newLen > 0 {
+		mq.parentCallbacks.signalQueueState(mq.key, queueStateSignalBecameNonEmpty)
+	}
 }
 
-// Name returns the name of the queue.
-func (a *flowQueueAccessor) Name() string { return a.mq.Name() }
+// propagateStatsDeltaForRemovedItems calculates the total stat changes for a slice of removed items and applies them.
+func (mq *managedQueue) propagateStatsDeltaForRemovedItems(items []types.QueueItemAccessor) {
+	var lenDelta int64
+	var byteSizeDelta int64
+	for _, item := range items {
+		lenDelta--
+		byteSizeDelta -= int64(item.OriginalRequest().ByteSize())
+	}
+	mq.propagateStatsDelta(lenDelta, byteSizeDelta)
+}
 
-// Capabilities returns the capabilities of the queue.
-func (a *flowQueueAccessor) Capabilities() []framework.QueueCapability { return a.mq.Capabilities() }
+// --- `flowQueueAccessor` ---
 
-// Len returns the number of items in the queue.
-func (a *flowQueueAccessor) Len() int { return a.mq.Len() }
+// flowQueueAccessor implements `framework.FlowQueueAccessor`. It provides a read-only, policy-facing view.
+//
+// # Role: The Read-Only Proxy
+//
+// This wrapper protects system invariants. It acts as a proxy that exposes only the read-only methods.
+// This prevents policy plugins from using type assertions to access the concrete `*managedQueue` and calling mutation
+// methods, which would bypass statistics tracking and signaling.
+type flowQueueAccessor struct {
+	mq *managedQueue
+}
 
-// ByteSize returns the total byte size of all items in the queue.
-func (a *flowQueueAccessor) ByteSize() uint64 { return a.mq.ByteSize() }
+var _ framework.FlowQueueAccessor = &flowQueueAccessor{}
 
-// PeekHead returns the item at the front of the queue without removing it.
+func (a *flowQueueAccessor) Name() string                               { return a.mq.Name() }
+func (a *flowQueueAccessor) Capabilities() []framework.QueueCapability  { return a.mq.Capabilities() }
+func (a *flowQueueAccessor) Len() int                                   { return a.mq.Len() }
+func (a *flowQueueAccessor) ByteSize() uint64                           { return a.mq.ByteSize() }
 func (a *flowQueueAccessor) PeekHead() (types.QueueItemAccessor, error) { return a.mq.PeekHead() }
-
-// PeekTail returns the item at the back of the queue without removing it.
 func (a *flowQueueAccessor) PeekTail() (types.QueueItemAccessor, error) { return a.mq.PeekTail() }
-
-// Comparator returns the `framework.ItemComparator` that defines this queue's item ordering logic.
-func (a *flowQueueAccessor) Comparator() framework.ItemComparator { return a.mq.Comparator() }
-
-// FlowSpec returns the `types.FlowSpecification` of the flow this queue accessor is associated with.
-func (a *flowQueueAccessor) FlowSpec() types.FlowSpecification { return a.mq.flowSpec }
-
-var _ framework.FlowQueueAccessor = &flowQueueAccessor{}
+func (a *flowQueueAccessor) Comparator() framework.ItemComparator       { return a.mq.Comparator() }
+func (a *flowQueueAccessor) FlowKey() types.FlowKey                     { return a.mq.key }
diff --git a/pkg/epp/flowcontrol/registry/registry.go b/pkg/epp/flowcontrol/registry/registry.go
new file mode 100644
index 0000000..34ed8fc
--- /dev/null
+++ b/pkg/epp/flowcontrol/registry/registry.go
@@ -0,0 +1,746 @@
+/*
+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.
+*/
+
+package registry
+
+import (
+	"context"
+	"fmt"
+	"sync"
+	"sync/atomic"
+
+	"github.com/go-logr/logr"
+	"k8s.io/utils/clock"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
+)
+
+// bandStats holds the aggregated atomic statistics for a single priority band across all shards.
+type bandStats struct {
+	byteSize atomic.Int64
+	len      atomic.Int64
+}
+
+// FlowRegistry is the concrete implementation of the `contracts.FlowRegistry` interface.
+//
+// # Role: The Central Orchestrator
+//
+// The `FlowRegistry` is the single source of truth for all configuration and the lifecycle manager for all shards and
+// flow instances (identified by `types.FlowKey`). It is responsible for complex, multi-step operations such as flow
+// registration, dynamic shard scaling, and coordinating garbage collection across all shards.
+//
+// # Concurrency Model: The Serialized Control Plane (Actor Model)
+//
+// To ensure correctness during complex state transitions, the `FlowRegistry` employs an Actor-like pattern. All
+// administrative operations and internal state change events are serialized.
+//
+// A single background goroutine (the `Run` loop) processes events from the `events` channel and a periodic `gcTicker`.
+// This event loop acquires the main `mu` lock before processing any event. External administrative methods (like
+// `RegisterOrUpdateFlow`) also acquire this lock. This strict serialization eliminates race conditions in the control
+// plane, simplifying the complex logic of the distributed state machine.
+//
+// # Detailed Concurrency Strategy and Locking Rules
+//
+// The registry employs a multi-tiered concurrency strategy:
+//
+//  1. Serialized Control Plane (Actor Model): Implemented by the `Run` loop and the `mu` lock.
+//
+//  2. Coarse-Grained Admin Lock (`FlowRegistry.mu`): Protects the core control plane state.
+//
+//  3. Shard-Level R/W Lock (`registryShard.mu`): Protects a single shard's metadata.
+//
+//  4. Queue-Level Write Lock (`managedQueue.mu`): Each `managedQueue` uses a `sync.Mutex` to protect writes, ensuring
+//     strict consistency between queue contents and statistics (required for GC correctness).
+//
+//  5. Lock-Free Data Path (Atomics): Statistics aggregation (Shard/Registry level) uses lock-free atomics. Statistics
+//     reads at the queue level are also lock-free.
+//
+//  6. Strict Lock Hierarchy: To prevent deadlocks, a strict acquisition hierarchy is enforced:
+//     `FlowRegistry.mu` -> `registryShard.mu` -> `managedQueue.mu`
+//     If multiple locks are required, they MUST be acquired in this order.
+//
+//  7. Blocking Rules: Goroutines MUST NOT hold the `FlowRegistry.mu` lock while attempting to send to the `events`
+//     channel. Sends block if the channel is full. Blocking while holding the lock would deadlock the event loop.
+//     (e.g., use the `deferredActions` pattern).
+type FlowRegistry struct {
+	logger logr.Logger
+
+	// config holds the master configuration for the entire system. It is validated and defaulted at startup and used as
+	// the template for creating partitioned `ShardConfig`s.
+	config *Config
+
+	// clock provides the time abstraction for the registry and its components (like `gcTracker`).
+	clock clock.WithTickerAndDelayedExecution
+
+	// mu protects administrative operations and the internal state (shard lists, `flowState`s, etc.).
+	// Acquired by both external administrative methods and the internal event loop (`Run`), ensuring serialization.
+	mu sync.Mutex
+
+	// activeShards contains shards that are operational (Active).
+	// A slice is used to maintain a deterministic order, which is crucial for consistent configuration partitioning
+	// during scaling events.
+	activeShards []*registryShard
+
+	// drainingShards contains shards that are being gracefully shut down.
+	// A map is used for efficient O(1) removal of a shard by its ID when its draining process completes.
+	drainingShards map[string]*registryShard
+
+	// allShards is a cached, combined slice of active and draining shards.
+	// This cache is updated only when the set of shards changes, optimizing read-heavy operations that need to iterate
+	// over all shards.
+	allShards []*registryShard
+
+	// nextShardID is a monotonically increasing counter used to generate unique, stable IDs for shards throughout the
+	// lifetime of the process.
+	nextShardID uint64
+
+	// flowStates tracks the desired state and GC state of all flow instances, keyed by the immutable `types.FlowKey`.
+	flowStates map[types.FlowKey]*flowState
+
+	// gcTicker drives the periodic garbage collection cycle.
+	gcTicker clock.Ticker
+
+	// gcGeneration is a monotonically increasing counter for GC cycles, used for the "mark-and-sweep" algorithm.
+	gcGeneration uint64
+
+	// events is a channel for all internal state change events from shards and queues.
+	//
+	// Note: The GC system relies on exactly-once delivery of edge-triggered events; therefore, sends to this channel must
+	// not be dropped. If the buffer fills, the data path will block, applying necessary backpressure to the control
+	// plane.
+	events chan event
+
+	// Globally aggregated statistics. Updated atomically via lock-free propagation.
+	totalByteSize atomic.Int64
+	totalLen      atomic.Int64
+
+	// perPriorityBandStats stores *bandStats, keyed by priority (`uint`).
+	// The map structure is immutable after initialization; values are updated atomically.
+	perPriorityBandStats map[uint]*bandStats
+}
+
+var _ contracts.FlowRegistry = &FlowRegistry{}
+
+// RegistryOption allows configuring the `FlowRegistry` during initialization using functional options.
+type RegistryOption func(*FlowRegistry)
+
+// WithClock sets the clock abstraction used by the registry (primarily for GC timers).
+// This is essential for deterministic testing. If `clk` is nil, the option is ignored.
+func WithClock(clk clock.WithTickerAndDelayedExecution) RegistryOption {
+	return func(fr *FlowRegistry) {
+		if clk != nil {
+			fr.clock = clk
+		}
+	}
+}
+
+// NewFlowRegistry creates and initializes a new `FlowRegistry` instance.
+func NewFlowRegistry(config Config, logger logr.Logger, opts ...RegistryOption) (*FlowRegistry, error) {
+	validatedConfig, err := NewConfig(config)
+	if err != nil {
+		return nil, fmt.Errorf("master configuration is invalid: %w", err)
+	}
+
+	// Buffered channel to absorb bursts of events. See comment on the struct field for concurrency notes.
+	events := make(chan event, config.EventChannelBufferSize)
+
+	fr := &FlowRegistry{
+		config:               validatedConfig,
+		logger:               logger.WithName("flow-registry"),
+		flowStates:           make(map[types.FlowKey]*flowState),
+		events:               events,
+		activeShards:         []*registryShard{},
+		drainingShards:       make(map[string]*registryShard),
+		perPriorityBandStats: make(map[uint]*bandStats, len(validatedConfig.PriorityBands)),
+		// Initialize `generation` to 1. A generation value of 0 in `flowState` is reserved as a sentinel value to indicate a
+		// brand new flow that has not yet been processed by the GC loop.
+		gcGeneration: 1,
+	}
+
+	for _, opt := range opts {
+		opt(fr)
+	}
+
+	if fr.clock == nil {
+		fr.clock = &clock.RealClock{}
+	}
+
+	fr.gcTicker = fr.clock.NewTicker(fr.config.FlowGCTimeout)
+
+	for i := range config.PriorityBands {
+		band := &config.PriorityBands[i]
+		fr.perPriorityBandStats[band.Priority] = &bandStats{}
+	}
+
+	// `UpdateShardCount` handles the initial creation and populates `activeShards`.
+	if err := fr.UpdateShardCount(validatedConfig.InitialShardCount); err != nil {
+		fr.gcTicker.Stop()
+		return nil, fmt.Errorf("failed to initialize shards: %w", err)
+	}
+
+	fr.logger.V(logging.DEFAULT).Info("FlowRegistry initialized successfully")
+	return fr, nil
+}
+
+// Run starts the registry's background event processing loop. It blocks until the provided context is cancelled.
+// This loop implements the serialized control plane (Actor model), handling both asynchronous signals from data plane
+// components and periodic ticks for garbage collection.
+func (fr *FlowRegistry) Run(ctx context.Context) {
+	fr.logger.Info("Starting FlowRegistry event loop")
+	defer fr.logger.Info("FlowRegistry event loop stopped")
+	defer fr.gcTicker.Stop()
+
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-fr.gcTicker.C():
+			fr.mu.Lock()
+			fr.onGCTick()
+			fr.mu.Unlock()
+		case evt := <-fr.events:
+			fr.mu.Lock()
+			switch e := evt.(type) {
+			case *queueStateChangedEvent:
+				fr.onQueueStateChanged(e)
+			case *shardStateChangedEvent:
+				fr.onShardStateChanged(e)
+			case *syncEvent:
+				close(e.doneCh) // Signal synchronization point reached.
+			}
+			fr.mu.Unlock()
+		}
+	}
+}
+
+// RegisterOrUpdateFlow handles the registration of a new flow instance or the update of an existing instance's
+// specification (for the same `types.FlowKey`). It orchestrates the creation or update atomically across all managed
+// shards.
+func (fr *FlowRegistry) RegisterOrUpdateFlow(spec types.FlowSpecification) error {
+	if spec.Key.ID == "" {
+		return fmt.Errorf("invalid flow specification: %w", contracts.ErrFlowIDEmpty)
+	}
+
+	fr.mu.Lock()
+	defer fr.mu.Unlock()
+
+	totalShardCount := len(fr.activeShards) + len(fr.drainingShards)
+	components, err := fr.buildFlowComponents(spec, totalShardCount)
+	if err != nil {
+		return err
+	}
+
+	fr.applyFlowSynchronizationLocked(spec, components)
+	return nil
+}
+
+// UpdateShardCount dynamically adjusts the number of internal state shards.
+func (fr *FlowRegistry) UpdateShardCount(n int) error {
+	if n <= 0 {
+		return fmt.Errorf("%w: shard count must be a positive integer, but got %d", contracts.ErrInvalidShardCount, n)
+	}
+
+	fr.mu.Lock()
+	currentActiveShards := len(fr.activeShards)
+	if n == currentActiveShards {
+		fr.mu.Unlock()
+		return nil
+	}
+
+	// deferredActions holds functions to be executed after the lock is released. This pattern is used to cleanly separate
+	// state mutations (under lock) from side effects that might block (like sending to the `events` channel).
+	var deferredActions []func()
+
+	if n > currentActiveShards {
+		if err := fr.executeScaleUpLocked(n); err != nil {
+			fr.mu.Unlock()
+			return err
+		}
+	} else {
+		fr.executeScaleDownLocked(n, &deferredActions)
+	}
+	fr.mu.Unlock()
+
+	// Execute all deferred side effects outside the lock.
+	for _, action := range deferredActions {
+		action()
+	}
+	return nil
+}
+
+// Stats returns globally aggregated statistics for the entire `FlowRegistry`.
+//
+// Note on Concurrency and Consistency: Statistics are aggregated using high-performance, lock-free atomic updates.
+// The returned stats represent a near-consistent snapshot of the system's state. It is not perfectly atomic because the
+// various counters are loaded independently without a global lock.
+func (fr *FlowRegistry) Stats() contracts.AggregateStats {
+	// Casts from `int64` to `uint64` are safe because the non-negative invariant is strictly enforced at the
+	// `managedQueue` level.
+	stats := contracts.AggregateStats{
+		TotalCapacityBytes:   fr.config.MaxBytes,
+		TotalByteSize:        uint64(fr.totalByteSize.Load()),
+		TotalLen:             uint64(fr.totalLen.Load()),
+		PerPriorityBandStats: make(map[uint]contracts.PriorityBandStats, len(fr.config.PriorityBands)),
+	}
+
+	for p, s := range fr.perPriorityBandStats {
+		bandCfg, err := fr.config.getBandConfig(p)
+		if err != nil {
+			// The stats map was populated from the config, so the config must exist for this priority.
+			// This indicates severe state corruption.
+			panic(fmt.Sprintf("invariant violation: priority band config (%d) missing during stats aggregation: %v", p, err))
+		}
+		stats.PerPriorityBandStats[p] = contracts.PriorityBandStats{
+			Priority:      p,
+			PriorityName:  bandCfg.PriorityName,
+			CapacityBytes: bandCfg.MaxBytes,
+			ByteSize:      uint64(s.byteSize.Load()),
+			Len:           uint64(s.len.Load()),
+		}
+	}
+	return stats
+}
+
+// updateAllShardsCacheLocked recalculates and updates the cached `allShards` slice.
+// It must be called any time the `activeShards` or `drainingShards` collections are modified.
+// It expects the registry's lock to be held.
+func (fr *FlowRegistry) updateAllShardsCacheLocked() {
+	allShards := make([]*registryShard, 0, len(fr.activeShards)+len(fr.drainingShards))
+	allShards = append(allShards, fr.activeShards...)
+
+	// Note: Iteration over a map is non-deterministic. However, the order of Draining shards in the combined slice does
+	// not impact correctness. Active shards are always first and in a stable order.
+	for _, shard := range fr.drainingShards {
+		allShards = append(allShards, shard)
+	}
+	fr.allShards = allShards
+}
+
+// ShardStats returns a slice of statistics, one for each internal shard (Active and Draining).
+func (fr *FlowRegistry) ShardStats() []contracts.ShardStats {
+	// To minimize lock contention, we acquire the lock just long enough to get the combined list of shards.
+	// Iterating and gathering stats (which involves reading shard-level atomics/locks) is done outside the critical
+	// section.
+	fr.mu.Lock()
+	allShards := fr.allShards
+	fr.mu.Unlock()
+
+	shardStats := make([]contracts.ShardStats, len(allShards))
+	for i, s := range allShards {
+		shardStats[i] = s.Stats()
+	}
+	return shardStats
+}
+
+// Shards returns a slice of accessors for all internal shards (active and draining).
+// Active shards always precede draining shards in the returned slice.
+func (fr *FlowRegistry) Shards() []contracts.RegistryShard {
+	// Similar to `ShardStats`, minimize lock contention by getting the list under lock.
+	fr.mu.Lock()
+	allShards := fr.allShards
+	fr.mu.Unlock()
+
+	shardContracts := make([]contracts.RegistryShard, len(allShards))
+	for i, s := range allShards {
+		shardContracts[i] = s
+	}
+	return shardContracts
+}
+
+// --- Internal Methods ---
+
+// executeScaleUpLocked handles adding new shards using a "prepare-commit" pattern.
+//
+// First, in a "prepare" phase, all new shards are fully created and initialized in a temporary slice. This includes
+// all fallible work. If any part of this phase fails, the operation is aborted without modifying the `FlowRegistry`'s
+// state. If preparation succeeds, a "commit" phase atomically applies the changes to the registry.
+//
+// # Scalability Considerations
+//
+// The preparation phase synchronizes all existing flows onto the new shards. This requires O(M*K) operations
+// (M=flows, K=new shards) performed while holding the main control plane lock. If M is large, this operation will block
+// the control plane (including event processing) for a significant duration.
+//
+// Expects the registry's write lock to be held.
+func (fr *FlowRegistry) executeScaleUpLocked(newTotalActive int) error {
+	currentActive := len(fr.activeShards)
+	numToAdd := newTotalActive - currentActive
+
+	fr.logger.Info("Scaling up shards", "currentActive", currentActive, "newTotalActive", newTotalActive)
+
+	// --- Prepare Phase ---
+	// Create all new shards in a temporary slice. This phase is fallible and performs no mutations on the
+	// `FlowRegistry`'s state.
+	preparedShards := make([]*registryShard, numToAdd)
+	for i := range numToAdd {
+		shardID := fmt.Sprintf("shard-%d", fr.nextShardID+uint64(i))
+		shardIndex := currentActive + i
+		partitionedConfig := fr.config.partition(shardIndex, newTotalActive)
+
+		callbacks := shardCallbacks{
+			propagateStatsDelta: fr.propagateStatsDelta,
+			signalQueueState:    fr.handleQueueStateSignal,
+			signalShardState:    fr.handleShardStateSignal,
+		}
+		shard, err := newShard(shardID, partitionedConfig, fr.logger, callbacks, fr.config.interFlowDispatchPolicyFactory)
+		if err != nil {
+			return fmt.Errorf("failed to create new shard %s: %w", shardID, err)
+		}
+
+		// Synchronize all existing flows onto this newly created shard.
+		for _, state := range fr.flowStates {
+			components, err := fr.buildFlowComponents(state.spec, 1)
+			if err != nil {
+				// This is unlikely as the flow was already validated, but we handle it defensively.
+				return fmt.Errorf("failed to prepare synchronization for flow %s on new shard %s: %w",
+					state.spec.Key, shardID, err)
+			}
+			shard.synchronizeFlow(state.spec, components[0].policy, components[0].queue)
+		}
+		preparedShards[i] = shard
+	}
+
+	// --- Commit Phase ---
+	// Preparation succeeded. Atomically apply all changes to the registry's state.
+	// This phase must be infallible.
+	fr.activeShards = append(fr.activeShards, preparedShards...)
+
+	for _, shard := range preparedShards {
+		for _, state := range fr.flowStates {
+			state.emptyOnShards[shard.id] = true
+		}
+	}
+
+	fr.nextShardID += uint64(numToAdd)
+	fr.repartitionShardConfigsLocked()
+	fr.updateAllShardsCacheLocked()
+	return nil
+}
+
+// executeScaleDownLocked handles marking shards for graceful draining and re-partitioning.
+// It appends the necessary draining actions to the `deferredActions` slice. These actions MUST be executed by the
+// caller after the registry lock is released to prevent deadlocks.
+// It expects the registry's write lock to be held.
+func (fr *FlowRegistry) executeScaleDownLocked(newTotalActive int, deferredActions *[]func()) {
+	currentActive := len(fr.activeShards)
+	fr.logger.Info("Scaling down shards", "currentActive", currentActive, "newTotalActive", newTotalActive)
+
+	// Identify the shards to drain. These are the ones at the end of the Active list.
+	shardsToDrain := fr.activeShards[newTotalActive:]
+	fr.activeShards = fr.activeShards[:newTotalActive]
+	for _, shard := range shardsToDrain {
+		fr.drainingShards[shard.id] = shard
+	}
+
+	// Defer the `markAsDraining` calls, which may block if the `events` channel is full.
+	for _, shard := range shardsToDrain {
+		s := shard
+		*deferredActions = append(*deferredActions, func() {
+			s.markAsDraining()
+		})
+	}
+
+	fr.repartitionShardConfigsLocked()
+	fr.updateAllShardsCacheLocked()
+}
+
+// applyFlowSynchronizationLocked is the "commit" step of `RegisterOrUpdateFlow`.
+// It updates the central `flowState` and propagates the changes to all shards.
+// It expects the registry's write lock to be held.
+func (fr *FlowRegistry) applyFlowSynchronizationLocked(spec types.FlowSpecification, components []flowComponents) {
+	key := spec.Key
+	state, exists := fr.flowStates[key]
+	allShards := fr.allShards
+	if !exists {
+		// This is a new flow instance.
+		state = newFlowState(spec, allShards)
+		fr.flowStates[key] = state
+	} else {
+		// This is an update to an existing flow instance (e.g., policy change, when supported).
+		state.update(spec)
+	}
+
+	if len(allShards) != len(components) {
+		// This indicates a severe logic error during the prepare/commit phase synchronization (a race in
+		// `RegisterOrUpdateFlow`).
+		panic(fmt.Sprintf("invariant violation: shard/queue/policy count mismatch during commit for flow %s", spec.Key))
+	}
+
+	// Propagate the update to all shards (Active and Draining), giving each its own dedicated policy and queue instance.
+	for i, shard := range allShards {
+		shard.synchronizeFlow(spec, components[i].policy, components[i].queue)
+	}
+	fr.logger.Info("Successfully registered or updated flow instance", "flowKey", key)
+}
+
+// repartitionShardConfigsLocked updates the partitioned configuration for all active shards.
+// It expects the registry's write lock to be held.
+func (fr *FlowRegistry) repartitionShardConfigsLocked() {
+	numActive := len(fr.activeShards)
+	for i, shard := range fr.activeShards {
+		newPartitionedConfig := fr.config.partition(i, numActive)
+		shard.updateConfig(newPartitionedConfig)
+	}
+}
+
+// flowComponents holds the set of plugin instances created for a single shard.
+type flowComponents struct {
+	policy framework.IntraFlowDispatchPolicy
+	queue  framework.SafeQueue
+}
+
+// buildFlowComponents instantiates the necessary plugin components for a new flow instance.
+// It creates a distinct instance of each component for each shard to ensure state isolation.
+func (fr *FlowRegistry) buildFlowComponents(spec types.FlowSpecification, numInstances int) ([]flowComponents, error) {
+	priority := spec.Key.Priority
+	bandConfig, err := fr.config.getBandConfig(priority)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get configuration for priority %d: %w", priority, err)
+	}
+
+	// TODO: When flow-level queue/policy overrides are implemented, check `spec` first.
+	policyName := bandConfig.IntraFlowDispatchPolicy
+	queueName := bandConfig.Queue
+	allComponents := make([]flowComponents, numInstances)
+
+	for i := range numInstances {
+		policy, err := fr.config.intraFlowDispatchPolicyFactory(policyName)
+		if err != nil {
+			return nil, fmt.Errorf("failed to instantiate intra-flow policy %s for flow %s: %w", policyName, spec.Key, err)
+		}
+
+		q, err := fr.config.queueFactory(queueName, policy.Comparator())
+		if err != nil {
+			return nil, fmt.Errorf("failed to instantiate queue %s for flow %s: %w", queueName, spec.Key, err)
+		}
+		allComponents[i] = flowComponents{policy: policy, queue: q}
+	}
+
+	return allComponents, nil
+}
+
+// garbageCollectFlowLocked orchestrates the "Trust but Verify" garbage collection of a
+// single flow instance. It implements the three steps of the pattern:
+// 1. Trust the eventually consistent cache.
+// 2. Verify the ground truth with a "stop-the-world" pause.
+// 3. Act to delete the flow if confirmed Idle.
+//
+// It acquires no locks itself but expects the caller to hold the main registry lock.
+func (fr *FlowRegistry) garbageCollectFlowLocked(key types.FlowKey) bool {
+	state, exists := fr.flowStates[key]
+	if !exists {
+		return false // Already deleted. Benign race.
+	}
+
+	if !state.isIdle(fr.allShards) {
+		return false
+	}
+
+	logger := fr.logger.WithValues("flowKey", key, "flowID", key.ID, "priority", key.Priority)
+	if !fr.verifyFlowIsTrulyIdleLocked(key, logger) {
+		return false
+	}
+
+	fr.deleteFlowLocked(key, logger)
+	return true
+}
+
+// verifyFlowIsTrulyIdleLocked performs the "stop-the-world" verification step of GC.
+// It acquires a write lock on ALL shards, briefly pausing the data path to get a strongly consistent view of all queue
+// lengths for a given flow.
+//
+// # Scalability Considerations
+//
+// This "Verify" step requires acquiring write locks on all shards (O(N)). As the shard count (N) increases, this pause
+// duration may grow, potentially impacting P99 latency. This trade-off is made explicitly to guarantee correctness and
+// prevent data loss.
+//
+// Returns true if the flow is confirmed to be empty everywhere, false otherwise.
+func (fr *FlowRegistry) verifyFlowIsTrulyIdleLocked(key types.FlowKey, logger logr.Logger) bool {
+	allShards := fr.allShards
+	for _, shard := range allShards {
+		shard.mu.Lock()
+	}
+	defer func() {
+		for _, shard := range allShards {
+			shard.mu.Unlock()
+		}
+	}()
+
+	for _, shard := range allShards {
+		mq, err := shard.managedQueueLocked(key)
+		if err != nil {
+			panic(fmt.Sprintf("invariant violation: managed queue for flow %s not found on shard %s during GC: %v",
+				key, shard.ID(), err))
+		}
+		if mq.Len() > 0 {
+			logger.V(logging.DEBUG).Info("GC aborted: Live check revealed flow instance is active", "shardID", shard.ID)
+			return false
+		}
+	}
+	return true
+}
+
+// deleteFlowLocked performs the destructive step of GC. It removes the flow's queues from all shards and deletes its
+// state from the central registry map.
+func (fr *FlowRegistry) deleteFlowLocked(key types.FlowKey, logger logr.Logger) {
+	logger.V(logging.VERBOSE).Info("Garbage collecting inactive flow instance")
+	for _, shard := range fr.allShards {
+		shard.garbageCollectLocked(key)
+	}
+	delete(fr.flowStates, key)
+	logger.V(logging.VERBOSE).Info("Successfully garbage collected flow instance")
+}
+
+// --- Event Handling (The Control Plane Loop) ---
+
+const gcGenerationNewFlow uint64 = 0
+
+// onGCTick implements the periodic "mark and sweep" garbage collection cycle.
+//
+// # Algorithm: Generational Mark-and-Sweep
+//
+// The GC uses a generational algorithm to ensure any idle flow survives for at least one full GC cycle before being
+// collected.
+//
+//  1. Mark: Any flow that is either actively serving requests OR is brand new (identified by a sentinel
+//     `lastActiveGeneration` value of `gcGenerationNewFlow`) is "marked" by updating its generation timestamp. This
+//     design choice keeps the `flowState` object decoupled from the GC engine's internal clock.
+//
+//  2. Sweep: Any flow that is Idle AND was last marked before the *previous* GC cycle is eligible for collection.
+//
+// It expects the registry's write lock to be held.
+func (fr *FlowRegistry) onGCTick() {
+	previousGeneration := fr.gcGeneration
+	currentGeneration := previousGeneration + 1
+	logger := fr.logger.WithValues("gcGeneration", currentGeneration)
+	logger.V(logging.DEBUG).Info("Starting periodic GC cycle")
+
+	// --- Mark Phase ---
+	// A flow is "marked" for survival by updating its `lastActiveGeneration`.
+	for _, state := range fr.flowStates {
+		if !state.isIdle(fr.allShards) {
+			// If a flow is currently Active, mark it with the current generation.
+			// This protects it from collection in the next cycle.
+			state.lastActiveGeneration = currentGeneration
+		} else if state.lastActiveGeneration == gcGenerationNewFlow {
+			// If a flow is new and Idle, mark it with the *previous* generation.
+			// This grants it a grace period for the *current* sweep, but correctly identifies it as a candidate for the
+			// *next* sweep if it remains Idle.
+			state.lastActiveGeneration = previousGeneration
+		}
+	}
+
+	// --- Sweep Phase ---
+	// A flow is a candidate for collection if it is Idle AND its `lastActiveGeneration` is from before the previous
+	// cycle.
+	var candidates []types.FlowKey
+	for key, state := range fr.flowStates {
+		if state.isIdle(fr.allShards) && state.lastActiveGeneration < previousGeneration {
+			candidates = append(candidates, key)
+		}
+	}
+
+	if len(candidates) > 0 {
+		logger.V(logging.DEBUG).Info("Found Idle flow instances for GC verification", "count", len(candidates))
+		for _, key := range candidates {
+			fr.garbageCollectFlowLocked(key)
+		}
+	}
+
+	// Finalize the cycle by updating the registry's generation.
+	fr.gcGeneration = currentGeneration
+}
+
+// onQueueStateChanged handles a state change signal from a `managedQueue`. It updates the control plane's cached view
+// of the flow's Idle state.
+func (fr *FlowRegistry) onQueueStateChanged(e *queueStateChangedEvent) {
+	fr.logger.V(logging.DEBUG).Info("Processing queue state signal",
+		"shardID", e.shardID,
+		"flowKey", e.key,
+		"flowID", e.key.ID,
+		"priority", e.key.Priority,
+		"signal", e.signal,
+	)
+
+	state, ok := fr.flowStates[e.key]
+	if !ok {
+		return // Flow was likely already garbage collected.
+	}
+	state.handleQueueSignal(e.shardID, e.signal)
+}
+
+// onShardStateChanged handles a state change signal from a `registryShard`.
+func (fr *FlowRegistry) onShardStateChanged(e *shardStateChangedEvent) {
+	if e.signal == shardStateSignalBecameDrained {
+		logger := fr.logger.WithValues("shardID", e.shardID, "signal", e.signal)
+		logger.Info("Draining shard is now empty, finalizing garbage collection")
+
+		if _, ok := fr.drainingShards[e.shardID]; !ok {
+			// A shard signaled Drained but wasn't in the Draining list (e.g., it was Active, or the signal was somehow
+			// processed twice despite the atomic latch).
+			// The system state is potentially corrupted.
+			panic(fmt.Sprintf("invariant violation: shard %s not found in draining map during GC", e.shardID))
+		}
+		delete(fr.drainingShards, e.shardID)
+
+		for _, flowState := range fr.flowStates {
+			flowState.purgeShard(e.shardID)
+		}
+		fr.updateAllShardsCacheLocked()
+	}
+}
+
+// --- Callbacks (Data Plane -> Control Plane Communication) ---
+
+// handleQueueStateSignal is the callback passed to shards to allow them to signal queue state changes.
+// It sends an event to the event channel for serialized processing by the control plane.
+func (fr *FlowRegistry) handleQueueStateSignal(shardID string, key types.FlowKey, signal queueStateSignal) {
+	// This must block if the channel is full. Dropping events would cause state divergence and memory leaks, as the GC
+	// system is edge-triggered. Blocking provides necessary backpressure from the data plane to the control plane.
+	fr.events <- &queueStateChangedEvent{
+		shardID: shardID,
+		key:     key,
+		signal:  signal,
+	}
+}
+
+// handleShardStateSignal is the callback passed to shards to allow them to signal their own state changes.
+func (fr *FlowRegistry) handleShardStateSignal(shardID string, signal shardStateSignal) {
+	// This must also block (see `handleQueueStateSignal`).
+	fr.events <- &shardStateChangedEvent{
+		shardID: shardID,
+		signal:  signal,
+	}
+}
+
+// propagateStatsDelta is the top-level, lock-free aggregator for all statistics changes from all shards.
+// It uses atomic operations to maintain high performance under concurrent updates from multiple shards.
+// As a result, its counters are eventually consistent and may be transiently inaccurate during high-contention races.
+func (fr *FlowRegistry) propagateStatsDelta(priority uint, lenDelta, byteSizeDelta int64) {
+	stats, ok := fr.perPriorityBandStats[priority]
+	if !ok {
+		// Stats are being propagated for a priority that wasn't initialized.
+		panic(fmt.Sprintf("invariant violation: priority band (%d) stats missing during propagation", priority))
+	}
+
+	stats.len.Add(lenDelta)
+	stats.byteSize.Add(byteSizeDelta)
+	fr.totalLen.Add(lenDelta)
+	fr.totalByteSize.Add(byteSizeDelta)
+}
diff --git a/pkg/epp/flowcontrol/registry/shard.go b/pkg/epp/flowcontrol/registry/shard.go
index 1e894f0..fd98462 100644
--- a/pkg/epp/flowcontrol/registry/shard.go
+++ b/pkg/epp/flowcontrol/registry/shard.go
@@ -26,206 +26,159 @@ import (
 
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework"
-	inter "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch"
-	intra "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
 )
 
-// registryShard implements the `contracts.RegistryShard` interface. It represents a single, concurrent-safe slice of
-// the `FlowRegistry`'s state, providing an operational view for a single `controller.FlowController` worker.
+// shardCallbacks groups the callback functions that a `registryShard` uses to communicate with its parent registry.
+type shardCallbacks struct {
+	propagateStatsDelta propagateStatsDeltaFunc
+	signalQueueState    func(shardID string, key types.FlowKey, signal queueStateSignal)
+	signalShardState    signalShardStateFunc
+}
+
+// priorityBand holds all the `managedQueues` and configuration for a single priority level within a shard.
+type priorityBand struct {
+	// config holds the partitioned config for this specific band within this shard.
+	config ShardPriorityBandConfig
+
+	// queues holds all `managedQueue` instances within this band, keyed by their logical `ID` string.
+	// The priority is implicit from the parent `priorityBand`.
+	queues map[string]*managedQueue
+
+	// Band-level statistics. Updated atomically via lock-free propagation.
+	byteSize atomic.Int64
+	len      atomic.Int64
+
+	// Cached policy instance for this band, created at initialization.
+	interFlowDispatchPolicy framework.InterFlowDispatchPolicy
+}
+
+// registryShard implements the `contracts.RegistryShard` interface.
+//
+// # Role: The Data Plane Slice
+//
+// It represents a single, concurrent-safe slice of the registry's total state. It provides a read-optimized view for a
+// `controller.FlowController` worker.
 //
-// # Responsibilities
+// # Concurrency: `RWMutex` and Atomics
 //
-//   - Holding the partitioned configuration and state (queues, policies) for its assigned shard.
-//   - Providing read-only access to its state for the `controller.FlowController`'s dispatch loop.
-//   - Aggregating statistics from its `managedQueue` instances.
+// The `registryShard` balances read performance with write safety:
 //
-// # Concurrency
+//   - `sync.RWMutex` (mu): Protects the shard's internal maps (`priorityBands`) during administrative operations.
 //
-// The `registryShard` uses a combination of an `RWMutex` and atomic operations to manage concurrency.
-//   - The `mu` RWMutex protects the shard's internal maps (`priorityBands`, `activeFlows`) during administrative
-//     operations like flow registration or updates. This ensures that the set of active or draining queues appears
-//     atomic to a `controller.FlowController` worker. All read-oriented methods on the shard take a read lock.
-//   - All statistics (`totalByteSize`, `totalLen`, etc.) are implemented as `atomic.Uint64` to allow for lock-free,
-//     high-performance updates from many concurrent queue operations.
+//   - Atomics (Stats): Aggregated statistics (`totalByteSize`, `totalLen`) use atomics for lock-free updates during
+//     delta propagation.
+//
+//   - Atomic Lifecycle (Status): The lifecycle state is managed via an atomic `status` enum.
 type registryShard struct {
-	id           string
-	logger       logr.Logger
-	config       *Config // Holds the *partitioned* config for this shard.
-	isActive     bool
-	reconcileFun parentStatsReconciler
+	id     string
+	logger logr.Logger
+
+	// config holds the partitioned configuration for this shard, derived from the `FlowRegistry`'s global `Config`.
+	// It contains only the settings and capacity limits relevant to this specific shard.
+	config *ShardConfig
+
+	// status tracks the lifecycle state of the shard (Active, Draining, Drained).
+	// It is stored as an `int32` for atomic operations.
+	status atomic.Int32 // `componentStatus`
 
-	// mu protects the shard's internal maps (`priorityBands` and `activeFlows`).
+	// parentCallbacks provides the communication channels back to the parent registry.
+	parentCallbacks shardCallbacks
+
+	// mu protects the shard's internal maps (`priorityBands`).
 	mu sync.RWMutex
 
-	// priorityBands is the primary lookup table for all managed queues on this shard, organized by `priority`, then by
-	// `flowID`. This map contains BOTH active and draining queues.
+	// priorityBands is the primary lookup table for all managed queues on this shard, organized by `priority`.
 	priorityBands map[uint]*priorityBand
 
-	// activeFlows is a flattened map for O(1) access to the SINGLE active queue for a given logical flow ID.
-	// This is the critical lookup for the `Enqueue` path. If a `flowID` is not in this map, it has no active queue on
-	// this shard.
-	activeFlows map[string]*managedQueue
-
 	// orderedPriorityLevels is a cached, sorted list of `priority` levels.
 	// It is populated at initialization to avoid repeated map key iteration and sorting during the dispatch loop,
 	// ensuring a deterministic, ordered traversal from highest to lowest priority.
 	orderedPriorityLevels []uint
 
-	// Shard-level statistics, which are updated atomically to ensure they are safe for concurrent access without locks.
-	totalByteSize atomic.Uint64
-	totalLen      atomic.Uint64
+	// Shard-level statistics. Updated atomically via lock-free propagation.
+	totalByteSize atomic.Int64
+	totalLen      atomic.Int64
 }
 
-// priorityBand holds all the `managedQueues` and configuration for a single priority level within a shard.
-type priorityBand struct {
-	// config holds the partitioned config for this specific band.
-	config PriorityBandConfig
-
-	// queues holds all `managedQueue` instances within this band, keyed by `flowID`. This includes both active and
-	// draining queues.
-	queues map[string]*managedQueue
-
-	// Band-level statistics, which are updated atomically.
-	byteSize atomic.Uint64
-	len      atomic.Uint64
-
-	// Cached policy instances for this band, created at initialization.
-	interFlowDispatchPolicy        framework.InterFlowDispatchPolicy
-	defaultIntraFlowDispatchPolicy framework.IntraFlowDispatchPolicy
-}
+var _ contracts.RegistryShard = &registryShard{}
 
 // newShard creates a new `registryShard` instance from a partitioned configuration.
 func newShard(
 	id string,
-	partitionedConfig *Config,
+	config *ShardConfig,
 	logger logr.Logger,
-	reconcileFunc parentStatsReconciler,
+	parentCallbacks shardCallbacks,
+	interFlowFactory interFlowDispatchPolicyFactory,
 ) (*registryShard, error) {
 	shardLogger := logger.WithName("registry-shard").WithValues("shardID", id)
 	s := &registryShard{
-		id:            id,
-		logger:        shardLogger,
-		config:        partitionedConfig,
-		isActive:      true,
-		reconcileFun:  reconcileFunc,
-		priorityBands: make(map[uint]*priorityBand, len(partitionedConfig.PriorityBands)),
-		activeFlows:   make(map[string]*managedQueue),
+		id:              id,
+		logger:          shardLogger,
+		config:          config,
+		parentCallbacks: parentCallbacks,
+		priorityBands:   make(map[uint]*priorityBand, len(config.PriorityBands)),
 	}
+	s.status.Store(int32(componentStatusActive))
 
-	for _, bandConfig := range partitionedConfig.PriorityBands {
-		interPolicy, err := inter.NewPolicyFromName(bandConfig.InterFlowDispatchPolicy)
+	for _, bandConfig := range config.PriorityBands {
+		interPolicy, err := interFlowFactory(bandConfig.InterFlowDispatchPolicy)
 		if err != nil {
 			return nil, fmt.Errorf("failed to create inter-flow policy %q for priority band %d: %w",
 				bandConfig.InterFlowDispatchPolicy, bandConfig.Priority, err)
 		}
 
-		intraPolicy, err := intra.NewPolicyFromName(bandConfig.IntraFlowDispatchPolicy)
-		if err != nil {
-			return nil, fmt.Errorf("failed to create intra-flow policy %q for priority band %d: %w",
-				bandConfig.IntraFlowDispatchPolicy, bandConfig.Priority, err)
-		}
-
 		s.priorityBands[bandConfig.Priority] = &priorityBand{
-			config:                         bandConfig,
-			queues:                         make(map[string]*managedQueue),
-			interFlowDispatchPolicy:        interPolicy,
-			defaultIntraFlowDispatchPolicy: intraPolicy,
+			config:                  bandConfig,
+			queues:                  make(map[string]*managedQueue),
+			interFlowDispatchPolicy: interPolicy,
 		}
 		s.orderedPriorityLevels = append(s.orderedPriorityLevels, bandConfig.Priority)
 	}
 
-	// Sort the priority levels to ensure deterministic iteration order.
 	slices.Sort(s.orderedPriorityLevels)
 	s.logger.V(logging.DEFAULT).Info("Registry shard initialized successfully",
 		"priorityBandCount", len(s.priorityBands), "orderedPriorities", s.orderedPriorityLevels)
 	return s, nil
 }
 
-// reconcileStats is the single point of entry for all statistics changes within the shard. It updates the relevant
-// band's stats, the shard's total stats, and propagates the delta to the parent registry.
-func (s *registryShard) reconcileStats(priority uint, lenDelta, byteSizeDelta int64) {
-	s.totalLen.Add(uint64(lenDelta))
-	s.totalByteSize.Add(uint64(byteSizeDelta))
-
-	if band, ok := s.priorityBands[priority]; ok {
-		band.len.Add(uint64(lenDelta))
-		band.byteSize.Add(uint64(byteSizeDelta))
-	}
-
-	s.logger.V(logging.TRACE).Info("Reconciled shard stats", "priority", priority,
-		"lenDelta", lenDelta, "byteSizeDelta", byteSizeDelta)
-
-	if s.reconcileFun != nil {
-		s.reconcileFun(lenDelta, byteSizeDelta)
-	}
-}
-
 // ID returns the unique identifier for this shard.
 func (s *registryShard) ID() string { return s.id }
 
 // IsActive returns true if the shard is active and accepting new requests.
+// This is used by the `controller.FlowController` to determine if it should use this shard for new enqueue operations.
 func (s *registryShard) IsActive() bool {
-	s.mu.RLock()
-	defer s.mu.RUnlock()
-	return s.isActive
-}
-
-// ActiveManagedQueue returns the currently active `ManagedQueue` for a given flow.
-func (s *registryShard) ActiveManagedQueue(flowID string) (contracts.ManagedQueue, error) {
-	s.mu.RLock()
-	defer s.mu.RUnlock()
-
-	mq, ok := s.activeFlows[flowID]
-	if !ok {
-		return nil, fmt.Errorf("failed to get active queue for flow %q: %w", flowID, contracts.ErrFlowInstanceNotFound)
-	}
-	return mq, nil
+	return componentStatus(s.status.Load()) == componentStatusActive
 }
 
-// ManagedQueue retrieves a specific (potentially draining) `ManagedQueue` instance from this shard.
-func (s *registryShard) ManagedQueue(flowID string, priority uint) (contracts.ManagedQueue, error) {
+// ManagedQueue retrieves a specific `contracts.ManagedQueue` instance from this shard.
+func (s *registryShard) ManagedQueue(key types.FlowKey) (contracts.ManagedQueue, error) {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
-
-	band, ok := s.priorityBands[priority]
-	if !ok {
-		return nil, fmt.Errorf("failed to get managed queue for flow %q: %w", flowID, contracts.ErrPriorityBandNotFound)
-	}
-	mq, ok := band.queues[flowID]
-	if !ok {
-		return nil, fmt.Errorf("failed to get managed queue for flow %q at priority %d: %w",
-			flowID, priority, contracts.ErrFlowInstanceNotFound)
-	}
-	return mq, nil
+	return s.managedQueueLocked(key)
 }
 
 // IntraFlowDispatchPolicy retrieves a flow's configured `framework.IntraFlowDispatchPolicy`.
-func (s *registryShard) IntraFlowDispatchPolicy(
-	flowID string,
-	priority uint,
-) (framework.IntraFlowDispatchPolicy, error) {
+func (s *registryShard) IntraFlowDispatchPolicy(key types.FlowKey) (framework.IntraFlowDispatchPolicy, error) {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
 
-	band, ok := s.priorityBands[priority]
+	band, ok := s.priorityBands[key.Priority]
 	if !ok {
-		return nil, fmt.Errorf("failed to get intra-flow policy for flow %q: %w", flowID, contracts.ErrPriorityBandNotFound)
+		return nil, fmt.Errorf("failed to get intra-flow policy for flow %q: %w", key, contracts.ErrPriorityBandNotFound)
 	}
-	mq, ok := band.queues[flowID]
+	mq, ok := band.queues[key.ID]
 	if !ok {
-		return nil, fmt.Errorf("failed to get intra-flow policy for flow %q at priority %d: %w",
-			flowID, priority, contracts.ErrFlowInstanceNotFound)
+		return nil, fmt.Errorf("failed to get intra-flow policy for flow %q: %w", key, contracts.ErrFlowInstanceNotFound)
 	}
-	// The policy is stored on the managed queue.
+	// The policy is stored on the `managedQueue`.
 	return mq.dispatchPolicy, nil
 }
 
 // InterFlowDispatchPolicy retrieves a priority band's configured `framework.InterFlowDispatchPolicy`.
+// This read is lock-free as the policy instance is immutable after the shard is initialized.
 func (s *registryShard) InterFlowDispatchPolicy(priority uint) (framework.InterFlowDispatchPolicy, error) {
-	s.mu.RLock()
-	defer s.mu.RUnlock()
-
 	band, ok := s.priorityBands[priority]
 	if !ok {
 		return nil, fmt.Errorf("failed to get inter-flow policy for priority %d: %w",
@@ -234,7 +187,7 @@ func (s *registryShard) InterFlowDispatchPolicy(priority uint) (framework.InterF
 	return band.interFlowDispatchPolicy, nil
 }
 
-// PriorityBandAccessor retrieves a read-only accessor for a given priority level.
+// PriorityBandAccessor retrieves a read-only view for a given priority level.
 func (s *registryShard) PriorityBandAccessor(priority uint) (framework.PriorityBandAccessor, error) {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
@@ -247,22 +200,29 @@ func (s *registryShard) PriorityBandAccessor(priority uint) (framework.PriorityB
 	return &priorityBandAccessor{shard: s, band: band}, nil
 }
 
-// AllOrderedPriorityLevels returns all configured priority levels for this shard, sorted from highest to lowest
-// priority (ascending numerical order).
+// AllOrderedPriorityLevels returns a cached, sorted slice of all configured priority levels for this shard.
+// The slice is sorted from highest to lowest priority (ascending numerical order).
 func (s *registryShard) AllOrderedPriorityLevels() []uint {
-	// This is cached and read-only, so no lock is needed.
 	return s.orderedPriorityLevels
 }
 
-// Stats returns a snapshot of the statistics for this specific shard.
+// Stats returns a snapshot of the aggregated statistics for this specific shard.
+//
+// Note on Concurrency and Consistency: Statistics are aggregated using high-performance, lock-free atomic updates
+// propagated from the underlying `managedQueue` instances. The returned stats represent a near-consistent snapshot of
+// the shard's state. It is not perfectly atomic because the various counters are loaded independently without a global
+// lock.
 func (s *registryShard) Stats() contracts.ShardStats {
+	// Acquire `RLock` only to ensure the config/map structure isn't changing during iteration.
 	s.mu.RLock()
 	defer s.mu.RUnlock()
 
+	// Casts from `int64` to `uint64` are safe because the non-negative invariant is strictly enforced at the
+	// `managedQueue` level.
 	stats := contracts.ShardStats{
 		TotalCapacityBytes:   s.config.MaxBytes,
-		TotalByteSize:        s.totalByteSize.Load(),
-		TotalLen:             s.totalLen.Load(),
+		TotalByteSize:        uint64(s.totalByteSize.Load()),
+		TotalLen:             uint64(s.totalLen.Load()),
 		PerPriorityBandStats: make(map[uint]contracts.PriorityBandStats, len(s.priorityBands)),
 	}
 
@@ -270,17 +230,156 @@ func (s *registryShard) Stats() contracts.ShardStats {
 		stats.PerPriorityBandStats[priority] = contracts.PriorityBandStats{
 			Priority:      priority,
 			PriorityName:  band.config.PriorityName,
-			CapacityBytes: band.config.MaxBytes, // This is the partitioned capacity
-			ByteSize:      band.byteSize.Load(),
-			Len:           band.len.Load(),
+			CapacityBytes: band.config.MaxBytes, // This is the partitioned capacity.
+			ByteSize:      uint64(band.byteSize.Load()),
+			Len:           uint64(band.len.Load()),
 		}
 	}
 	return stats
 }
 
-var _ contracts.RegistryShard = &registryShard{}
+//  --- Internal Administrative/Lifecycle Methods (called by `FlowRegistry`) ---
+
+// synchronizeFlow is the internal administrative method for creating a flow instance on this shard.
+// Since a flow instance (identified by its immutable `FlowKey`) cannot be updated yet, this function is a simple
+// "create if not exists" operation. It is idempotent.
+func (s *registryShard) synchronizeFlow(
+	spec types.FlowSpecification,
+	policy framework.IntraFlowDispatchPolicy,
+	q framework.SafeQueue,
+) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	key := spec.Key
 
-// --- priorityBandAccessor ---
+	// Find the correct priority band. A missing band is an invariant violation, as the `FlowRegistry` should have already
+	// validated this.
+	band, ok := s.priorityBands[key.Priority]
+	if !ok {
+		panic(fmt.Sprintf("invariant violation: attempt to synchronize flow on non-existent priority band %d", key.Priority))
+	}
+
+	if _, ok := band.queues[key.ID]; ok {
+		return
+	}
+
+	s.logger.V(logging.TRACE).Info("Creating new queue for flow instance.",
+		"flowKey", key, "queueType", q.Name())
+
+	callbacks := managedQueueCallbacks{
+		propagateStatsDelta: s.propagateStatsDelta,
+		signalQueueState: func(key types.FlowKey, signal queueStateSignal) {
+			s.parentCallbacks.signalQueueState(s.id, key, signal)
+		},
+	}
+	mq := newManagedQueue(q, policy, spec.Key, s.logger, callbacks)
+	band.queues[key.ID] = mq
+}
+
+// garbageCollectLocked removes a queue instance from the shard.
+// This must be called under the shard's write lock.
+func (s *registryShard) garbageCollectLocked(key types.FlowKey) {
+	s.logger.Info("Garbage collecting queue instance.", "flowKey", key, "flowID", key.ID, "priority", key.Priority)
+	if band, ok := s.priorityBands[key.Priority]; ok {
+		delete(band.queues, key.ID)
+	}
+}
+
+// markAsDraining transitions the shard to a Draining state. This method is lock-free.
+func (s *registryShard) markAsDraining() {
+	// Attempt to transition from Active to Draining atomically.
+	if s.status.CompareAndSwap(int32(componentStatusActive), int32(componentStatusDraining)) {
+		s.logger.V(logging.DEBUG).Info("Shard status changed",
+			"from", componentStatusActive,
+			"to", componentStatusDraining,
+		)
+	}
+
+	// Check if the shard is *already* empty when marked as draining. If so, immediately attempt the transition to
+	// Drained to ensure timely GC. This handles the race where the shard becomes empty just before or during being
+	// marked Draining.
+	if s.totalLen.Load() == 0 {
+		// Attempt to transition from Draining to Drained atomically.
+		if s.status.CompareAndSwap(int32(componentStatusDraining), int32(componentStatusDrained)) {
+			s.parentCallbacks.signalShardState(s.id, shardStateSignalBecameDrained)
+		}
+	}
+}
+
+// managedQueueLocked retrieves a specific `contracts.ManagedQueue` instance from this shard.
+// This must be called under the shard's read lock.
+func (s *registryShard) managedQueueLocked(key types.FlowKey) (*managedQueue, error) {
+	band, ok := s.priorityBands[key.Priority]
+	if !ok {
+		return nil, fmt.Errorf("failed to get managed queue for flow %q: %w", key, contracts.ErrPriorityBandNotFound)
+	}
+	mq, ok := band.queues[key.ID]
+	if !ok {
+		return nil, fmt.Errorf("failed to get managed queue for flow %q: %w", key, contracts.ErrFlowInstanceNotFound)
+	}
+	return mq, nil
+}
+
+// updateConfig atomically replaces the shard's configuration. This is used during scaling events to re-partition
+// capacity allocations.
+func (s *registryShard) updateConfig(newConfig *ShardConfig) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	s.config = newConfig
+	// Update the partitioned config for each priority band as well.
+	for priority, band := range s.priorityBands {
+		newBandConfig, err := newConfig.getBandConfig(priority)
+		if err != nil {
+			// An invariant was violated: a priority exists in the shard but not in the new config.
+			// This should be impossible if the registry's logic is correct.
+			panic(fmt.Errorf("invariant violation: priority band (%d) missing in new shard configuration during update: %w",
+				priority, err))
+		}
+		band.config = *newBandConfig
+	}
+	s.logger.Info("Shard configuration updated")
+}
+
+// --- Internal Callback Methods ---
+
+// propagateStatsDelta is the single point of entry for all statistics changes within the shard.
+// It updates the relevant band's stats, the shard's total stats, and handles the shard's lifecycle signaling before
+// propagating the delta to the parent registry.
+// It uses atomic operations to maintain high performance under concurrent updates from multiple shards.
+// As a result, its counters are eventually consistent and may be transiently inaccurate during high-contention races.
+func (s *registryShard) propagateStatsDelta(priority uint, lenDelta, byteSizeDelta int64) {
+	band, ok := s.priorityBands[priority]
+	if !ok {
+		// This should be impossible if the `managedQueue` calling this is correctly registered.
+		panic(fmt.Sprintf("invariant violation on shard %s: received stats propagation for unknown priority band (%d)",
+			s.id, priority))
+	}
+
+	band.len.Add(lenDelta)
+	band.byteSize.Add(byteSizeDelta)
+	newTotalLen := s.totalLen.Add(lenDelta)
+	s.totalByteSize.Add(byteSizeDelta)
+
+	// Following the strict bottom-up signaling pattern, we evaluate and signal our own state change *before* propagating
+	// the statistics to the parent registry.
+	s.evaluateDrainingState(newTotalLen)
+	s.parentCallbacks.propagateStatsDelta(priority, lenDelta, byteSizeDelta)
+}
+
+// evaluateDrainingState checks if the shard has transitioned to the Drained state and signals the parent.
+func (s *registryShard) evaluateDrainingState(currentLen int64) {
+	if currentLen == 0 {
+		// Attempt transition from Draining to Drained atomically.
+		// This acts as the exactly-once latch. If it succeeds, this goroutine is solely responsible for signaling.
+		if s.status.CompareAndSwap(int32(componentStatusDraining), int32(componentStatusDrained)) {
+			s.parentCallbacks.signalShardState(s.id, shardStateSignalBecameDrained)
+		}
+	}
+}
+
+// --- `priorityBandAccessor` ---
 
 // priorityBandAccessor implements `framework.PriorityBandAccessor`. It provides a read-only, concurrent-safe view of a
 // single priority band within a shard.
@@ -289,34 +388,39 @@ type priorityBandAccessor struct {
 	band  *priorityBand
 }
 
+var _ framework.PriorityBandAccessor = &priorityBandAccessor{}
+
 // Priority returns the numerical priority level of this band.
-func (a *priorityBandAccessor) Priority() uint {
-	return a.band.config.Priority
-}
+func (a *priorityBandAccessor) Priority() uint { return a.band.config.Priority }
 
 // PriorityName returns the human-readable name of this priority band.
-func (a *priorityBandAccessor) PriorityName() string {
-	return a.band.config.PriorityName
-}
+func (a *priorityBandAccessor) PriorityName() string { return a.band.config.PriorityName }
 
-// FlowIDs returns a slice of all flow IDs within this priority band.
-func (a *priorityBandAccessor) FlowIDs() []string {
+// FlowKeys returns a slice of all flow keys within this priority band.
+//
+// To minimize lock contention, this implementation first snapshots the flow IDs under a read lock and then constructs
+// the final slice of `types.FlowKey` structs outside of the lock.
+func (a *priorityBandAccessor) FlowKeys() []types.FlowKey {
 	a.shard.mu.RLock()
-	defer a.shard.mu.RUnlock()
-
-	flowIDs := make([]string, 0, len(a.band.queues))
+	ids := make([]string, 0, len(a.band.queues))
 	for id := range a.band.queues {
-		flowIDs = append(flowIDs, id)
+		ids = append(ids, id)
+	}
+	a.shard.mu.RUnlock()
+
+	flowKeys := make([]types.FlowKey, len(ids))
+	for i, id := range ids {
+		flowKeys[i] = types.FlowKey{ID: id, Priority: a.Priority()}
 	}
-	return flowIDs
+	return flowKeys
 }
 
-// Queue returns a `framework.FlowQueueAccessor` for the specified `flowID` within this priority band.
-func (a *priorityBandAccessor) Queue(flowID string) framework.FlowQueueAccessor {
+// Queue returns a `framework.FlowQueueAccessor` for the specified logical `ID` within this priority band.
+func (a *priorityBandAccessor) Queue(id string) framework.FlowQueueAccessor {
 	a.shard.mu.RLock()
 	defer a.shard.mu.RUnlock()
 
-	mq, ok := a.band.queues[flowID]
+	mq, ok := a.band.queues[id]
 	if !ok {
 		return nil
 	}
@@ -324,17 +428,21 @@ func (a *priorityBandAccessor) Queue(flowID string) framework.FlowQueueAccessor
 }
 
 // IterateQueues executes the given `callback` for each `framework.FlowQueueAccessor` in this priority band.
-// The callback is executed under the shard's read lock, so it should be efficient and non-blocking.
-// If the callback returns false, iteration stops.
+//
+// To minimize lock contention, this implementation snapshots the queue accessors under a read lock and then executes
+// the callback on the snapshot, outside of the lock. This ensures that a potentially slow policy (the callback) does
+// not block other operations on the shard.
 func (a *priorityBandAccessor) IterateQueues(callback func(queue framework.FlowQueueAccessor) bool) {
 	a.shard.mu.RLock()
-	defer a.shard.mu.RUnlock()
-
+	accessors := make([]framework.FlowQueueAccessor, 0, len(a.band.queues))
 	for _, mq := range a.band.queues {
-		if !callback(mq.FlowQueueAccessor()) {
+		accessors = append(accessors, mq.FlowQueueAccessor())
+	}
+	a.shard.mu.RUnlock()
+
+	for _, accessor := range accessors {
+		if !callback(accessor) {
 			return
 		}
 	}
 }
-
-var _ framework.PriorityBandAccessor = &priorityBandAccessor{}
diff --git a/pkg/epp/flowcontrol/types/errors.go b/pkg/epp/flowcontrol/types/errors.go
index 4da47c1..f7dffbd 100644
--- a/pkg/epp/flowcontrol/types/errors.go
+++ b/pkg/epp/flowcontrol/types/errors.go
@@ -46,9 +46,6 @@ var (
 	// ErrNilRequest indicates that a nil `types.FlowControlRequest` was provided.
 	ErrNilRequest = errors.New("FlowControlRequest cannot be nil")
 
-	// ErrFlowIDEmpty indicates that a flow ID was empty when one was required.
-	ErrFlowIDEmpty = errors.New("flow ID cannot be empty")
-
 	// ErrQueueAtCapacity indicates that a request could not be enqueued because queue capacity limits were met.
 	ErrQueueAtCapacity = errors.New("queue at capacity and displacement failed to make space")
 )
diff --git a/pkg/epp/flowcontrol/types/flow.go b/pkg/epp/flowcontrol/types/flow.go
index 4bdd508..71017c1 100644
--- a/pkg/epp/flowcontrol/types/flow.go
+++ b/pkg/epp/flowcontrol/types/flow.go
@@ -16,13 +16,63 @@ limitations under the License.
 
 package types
 
-// FlowSpecification defines the complete configuration for a single logical flow.
-// It is the data contract used by the `contracts.FlowRegistry` to create and manage the lifecycle of queues and
-// policies.
-type FlowSpecification struct {
-	// ID is the unique identifier for this flow (e.g., model name, tenant ID).
+import (
+	"strconv"
+	"strings"
+)
+
+// FlowKey is the unique, immutable identifier for a single, independently managed flow instance within the
+// `contracts.FlowRegistry`.
+// It combines a logical grouping `ID` with a specific `Priority` level to form a composite primary key.
+//
+// The core architectural principle of this model is that each unique, immutable `FlowKey` represents a completely
+// separate stream of traffic with its own queue, lifecycle and statistics.
+type FlowKey struct {
+	// ID is the logical grouping identifier for a flow, such as a tenant ID or a model name. This ID can be shared across
+	// multiple `FlowKey`s to represent different classes of traffic for the same logical entity. It provides a way to
+	// group related traffic but does not uniquely identify a manageable flow instance on its own.
 	ID string
 
-	// Priority is the numerical priority level for this flow. Lower values indicate higher priority.
+	// Priority is the numerical priority level that defines the priority band for this specific flow instance.
+	//
+	// A different Priority value for the same ID creates a distinct flow instance. For example, the keys
+	// `{ID: "TenantA", Priority: 10}` and `{ID: "TenantA", Priority: 100}` are treated as two completely separate,
+	// concurrently active flows. This allows a single tenant to serve traffic at multiple priority levels simultaneously.
+	//
+	// Because the `FlowKey` is immutable, changing the priority of traffic requires using a new `FlowKey`; the old flow
+	// instance will be automatically garbage collected by the registry when it becomes idle.
 	Priority uint
 }
+
+func (k FlowKey) String() string {
+	return k.ID + ":" + strconv.FormatUint(uint64(k.Priority), 10)
+}
+
+// Compare provides a stable comparison function for two FlowKey instances, suitable for use with sorting algorithms.
+// It returns -1 if the key is less than the other, 0 if they are equal, and 1 if the key is greater than the other.
+// The comparison is performed first by `Priority` (ascending, higher priority first) and then by `ID` (ascending).
+func (k FlowKey) Compare(other FlowKey) int {
+	if k.Priority < other.Priority { // Lower number means higher priority
+		return -1
+	}
+	if k.Priority > other.Priority {
+		return 1
+	}
+	return strings.Compare(k.ID, other.ID)
+}
+
+// FlowSpecification defines the complete configuration for a single logical flow instance, which is uniquely identified
+// by its immutable `Key`.
+//
+// It is the primary data contract used by the `contracts.FlowRegistry` to register or update a flow instance. The
+// registry manages one flow instance for each unique `FlowKey`. While the `Key` itself is immutable, the specification
+// can be updated via `RegisterOrUpdateFlow` to modify configurable parameters (e.g., policies, capacity limits) for the
+// existing flow instance (when these update stories are supported).
+type FlowSpecification struct {
+	// Key is the unique, immutable identifier for the flow instance this specification describes.
+	Key FlowKey
+
+	// TODO: Add other flow-scoped configuration fields here, such as:
+	// - IntraFlowDispatchPolicy intra.RegisteredPolicyName
+	// - CapacityBytes uint64
+}
diff --git a/pkg/epp/flowcontrol/types/mocks/mocks.go b/pkg/epp/flowcontrol/types/mocks/mocks.go
index 7eb6f37..dbef031 100644
--- a/pkg/epp/flowcontrol/types/mocks/mocks.go
+++ b/pkg/epp/flowcontrol/types/mocks/mocks.go
@@ -28,27 +28,32 @@ import (
 // MockFlowControlRequest provides a mock implementation of the `types.FlowControlRequest` interface.
 type MockFlowControlRequest struct {
 	Ctx                  context.Context
-	FlowIDV              string
+	FlowKeyV             types.FlowKey
 	ByteSizeV            uint64
 	InitialEffectiveTTLV time.Duration
 	IDV                  string
 }
 
 // NewMockFlowControlRequest creates a new `MockFlowControlRequest` instance.
-func NewMockFlowControlRequest(byteSize uint64, id, flowID string, ctx context.Context) *MockFlowControlRequest {
+func NewMockFlowControlRequest(
+	byteSize uint64,
+	id string,
+	key types.FlowKey,
+	ctx context.Context,
+) *MockFlowControlRequest {
 	if ctx == nil {
 		ctx = context.Background()
 	}
 	return &MockFlowControlRequest{
 		ByteSizeV: byteSize,
 		IDV:       id,
-		FlowIDV:   flowID,
+		FlowKeyV:  key,
 		Ctx:       ctx,
 	}
 }
 
 func (m *MockFlowControlRequest) Context() context.Context           { return m.Ctx }
-func (m *MockFlowControlRequest) FlowID() string                     { return m.FlowIDV }
+func (m *MockFlowControlRequest) FlowKey() types.FlowKey             { return m.FlowKeyV }
 func (m *MockFlowControlRequest) ByteSize() uint64                   { return m.ByteSizeV }
 func (m *MockFlowControlRequest) InitialEffectiveTTL() time.Duration { return m.InitialEffectiveTTLV }
 func (m *MockFlowControlRequest) ID() string                         { return m.IDV }
@@ -92,13 +97,13 @@ var _ types.QueueItemAccessor = &MockQueueItemAccessor{}
 
 // NewMockQueueItemAccessor is a constructor for `MockQueueItemAccessor` that initializes the mock with a default
 // `MockFlowControlRequest` and `MockQueueItemHandle` to prevent nil pointer dereferences in tests.
-func NewMockQueueItemAccessor(byteSize uint64, reqID, flowID string) *MockQueueItemAccessor {
+func NewMockQueueItemAccessor(byteSize uint64, reqID string, key types.FlowKey) *MockQueueItemAccessor {
 	return &MockQueueItemAccessor{
 		EnqueueTimeV: time.Now(),
 		OriginalRequestV: NewMockFlowControlRequest(
 			byteSize,
 			reqID,
-			flowID,
+			key,
 			context.Background(),
 		),
 		HandleV: &MockQueueItemHandle{},
diff --git a/pkg/epp/flowcontrol/types/request.go b/pkg/epp/flowcontrol/types/request.go
index 8b50236..7569407 100644
--- a/pkg/epp/flowcontrol/types/request.go
+++ b/pkg/epp/flowcontrol/types/request.go
@@ -33,10 +33,11 @@ type FlowControlRequest interface {
 	// queue.
 	Context() context.Context
 
-	// FlowID returns the unique identifier for the flow this request belongs to (e.g., model name, tenant ID). The
-	// `controller.FlowController` uses this ID to look up the active `contracts.ManagedQueue` and configured
-	// `framework.IntraFlowDispatchPolicy` from a `contracts.RegistryShard`.
-	FlowID() string
+	// FlowKey returns the composite key that uniquely identifies the flow instance this request belongs to.
+	// The `controller.FlowController` uses this key as the primary identifier to look up the correct
+	// `contracts.ManagedQueue` and configured `framework.IntraFlowDispatchPolicy` from a `contracts.RegistryShard`.
+	// The returned key is treated as an immutable value.
+	FlowKey() FlowKey
 
 	// ByteSize returns the request's size in bytes (e.g., prompt size). This is used by the `controller.FlowController`
 	// for managing byte-based capacity limits and for `contracts.FlowRegistry` statistics.
diff --git a/pkg/epp/handlers/request.go b/pkg/epp/handlers/request.go
index 096f0c8..7f81221 100644
--- a/pkg/epp/handlers/request.go
+++ b/pkg/epp/handlers/request.go
@@ -17,6 +17,7 @@ limitations under the License.
 package handlers
 
 import (
+	"fmt"
 	"strconv"
 	"time"
 
@@ -45,7 +46,10 @@ func (s *StreamingServer) HandleRequestHeaders(reqCtx *RequestContext, req *extP
 		if err != nil {
 			return err
 		}
-		reqCtx.TargetEndpoint = pod.Address + ":" + strconv.Itoa(int(pool.Spec.TargetPortNumber))
+		if len(pool.Spec.TargetPorts) != 1 {
+			return fmt.Errorf("expected 1 target port, got %d", len(pool.Spec.TargetPorts))
+		}
+		reqCtx.TargetEndpoint = pod.Address + ":" + strconv.Itoa(int(pool.Spec.TargetPorts[0].Number))
 		reqCtx.RequestSize = 0
 		reqCtx.reqHeaderResp = s.generateRequestHeaderResponse(reqCtx)
 		return nil
@@ -69,6 +73,11 @@ func (s *StreamingServer) HandleRequestHeaders(reqCtx *RequestContext, req *extP
 			// remove the objective header from the request headers,
 			// this is not data that should be manipulated or sent to the backend.
 			delete(reqCtx.Request.Headers, header.Key)
+		case metadata.ModelNameRewriteKey:
+			reqCtx.TargetModelName = reqCtx.Request.Headers[header.Key]
+			// remove the rewrite header from the request headers,
+			// this is not data that should be manipulated or sent to the backend.
+			delete(reqCtx.Request.Headers, header.Key)
 		}
 	}
 	return nil
diff --git a/pkg/epp/metadata/consts.go b/pkg/epp/metadata/consts.go
index faf9015..be6c482 100644
--- a/pkg/epp/metadata/consts.go
+++ b/pkg/epp/metadata/consts.go
@@ -30,4 +30,6 @@ const (
 	FlowFairnessIDKey = "x-gateway-inference-fairness-id"
 	// ObjectiveKey is the header key used to specify the objective of an incoming request.
 	ObjectiveKey = "x-gateway-inference-objective"
+	// ModelNameRewriteKey is the header key used to specify the model name to be used when the request is forwarded to the model server.
+	ModelNameRewriteKey = "x-gateway-model-name-rewrite"
 )
diff --git a/pkg/epp/metrics/collectors/inference_pool.go b/pkg/epp/metrics/collectors/inference_pool.go
index 41a3d6d..ec3def1 100644
--- a/pkg/epp/metrics/collectors/inference_pool.go
+++ b/pkg/epp/metrics/collectors/inference_pool.go
@@ -63,7 +63,7 @@ func (c *inferencePoolMetricsCollector) Collect(ch chan<- prometheus.Metric) {
 		return
 	}
 
-	podMetrics := c.ds.PodList(backendmetrics.AllPodPredicate)
+	podMetrics := c.ds.PodList(backendmetrics.AllPodsPredicate)
 	if len(podMetrics) == 0 {
 		return
 	}
diff --git a/pkg/epp/plugins/plugin_state.go b/pkg/epp/plugins/plugin_state.go
index afd0f87..2da3557 100644
--- a/pkg/epp/plugins/plugin_state.go
+++ b/pkg/epp/plugins/plugin_state.go
@@ -109,17 +109,23 @@ func (s *PluginState) cleanup(ctx context.Context) {
 			log.FromContext(ctx).V(logutil.DEFAULT).Info("Shutting down plugin state cleanup")
 			return
 		case <-ticker.C:
-			s.requestToLastAccessTime.Range(func(k, v any) bool {
-				requestID := k.(string)
-				lastAccessTime := v.(time.Time)
-				if time.Since(lastAccessTime) > stalenessThreshold {
-					s.Delete(requestID) // cleanup stale requests (this is safe in sync.Map)
-				}
-				return true
-			})
+			s.cleanStaleRequests()
 		}
 	}
+}
 
+// cleanStaleRequests iterates through all requests and removes those that haven't been
+// accessed for longer than stalenessThreshold. This operation is safe to run concurrently
+// with other operations on the PluginState.
+func (s *PluginState) cleanStaleRequests() {
+	s.requestToLastAccessTime.Range(func(k, v any) bool {
+		requestID := k.(string)
+		lastAccessTime := v.(time.Time)
+		if time.Since(lastAccessTime) > stalenessThreshold {
+			s.Delete(requestID) // cleanup stale requests (this is safe in sync.Map)
+		}
+		return true
+	})
 }
 
 // ReadPluginStateKey retrieves data with the given key from PluginState and asserts it to type T.
diff --git a/pkg/epp/requestcontrol/director.go b/pkg/epp/requestcontrol/director.go
index 3ba2818..1435c01 100644
--- a/pkg/epp/requestcontrol/director.go
+++ b/pkg/epp/requestcontrol/director.go
@@ -27,7 +27,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/go-logr/logr"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
@@ -71,6 +70,10 @@ type Director struct {
 	saturationDetector  SaturationDetector
 	preRequestPlugins   []PreRequest
 	postResponsePlugins []PostResponse
+	// we just need a pointer to an int variable since priority is a pointer in InferenceObjective
+	// no need to set this in the constructor, since the value we want is the default int val
+	// and value types cannot be nil
+	defaultPriority int
 }
 
 // HandleRequest orchestrates the request lifecycle:
@@ -91,7 +94,12 @@ func (d *Director) HandleRequest(ctx context.Context, reqCtx *handlers.RequestCo
 	if !ok {
 		return reqCtx, errutil.Error{Code: errutil.BadRequest, Msg: "model not found in request body"}
 	}
-	reqCtx.TargetModelName = reqCtx.IncomingModelName // Default to incoming model name
+	if reqCtx.TargetModelName == "" {
+		// Default to incoming model name
+		reqCtx.TargetModelName = reqCtx.IncomingModelName
+	}
+	reqCtx.Request.Body["model"] = reqCtx.TargetModelName
+
 	prompt, err := requtil.ExtractPromptFromRequestBody(requestBodyMap)
 	if err != nil {
 		return reqCtx, err
@@ -99,26 +107,14 @@ func (d *Director) HandleRequest(ctx context.Context, reqCtx *handlers.RequestCo
 	infObjective := d.datastore.ObjectiveGet(reqCtx.ObjectiveKey)
 	if infObjective == nil {
 		logger.V(logutil.VERBOSE).Info("No associated InferenceObjective found, using default", "objectiveKey", reqCtx.ObjectiveKey)
-		standard := v1alpha2.Standard
 		infObjective = &v1alpha2.InferenceObjective{
 			Spec: v1alpha2.InferenceObjectiveSpec{
-				Criticality: &standard,
+				Priority: &d.defaultPriority,
 			},
 		}
-	}
-
-	if infObjective.Spec.Criticality == nil {
-		// Default to Standard if not specified.
-		standard := v1alpha2.Standard
-		infObjective.Spec.Criticality = &standard
-	}
-
-	if len(infObjective.Spec.TargetModels) > 0 {
-		reqCtx.TargetModelName = RandomWeightedDraw(logger, infObjective, 0)
-		if reqCtx.TargetModelName == "" {
-			return reqCtx, errutil.Error{Code: errutil.BadConfiguration, Msg: fmt.Sprintf("error getting target model name for model %v", infObjective.Name)}
-		}
-		reqCtx.Request.Body["model"] = reqCtx.TargetModelName // Update target model in the body.
+	} else if infObjective.Spec.Priority == nil {
+		// Default to 0 if not specified.
+		infObjective.Spec.Priority = &d.defaultPriority
 	}
 
 	// Prepare LLMRequest (needed for both saturation detection and Scheduler)
@@ -129,13 +125,13 @@ func (d *Director) HandleRequest(ctx context.Context, reqCtx *handlers.RequestCo
 		Headers:     reqCtx.Request.Headers,
 	}
 
-	logger = logger.WithValues("objectiveKey", reqCtx.ObjectiveKey, "incomingModelName", reqCtx.IncomingModelName, "targetModelName", reqCtx.TargetModelName, "criticality", infObjective.Spec.Criticality)
+	logger = logger.WithValues("objectiveKey", reqCtx.ObjectiveKey, "incomingModelName", reqCtx.IncomingModelName, "targetModelName", reqCtx.TargetModelName, "priority", infObjective.Spec.Priority)
 
 	ctx = log.IntoContext(ctx, logger)
 	logger.V(logutil.DEBUG).Info("LLM request assembled")
 
 	// --- 2. Admission Control check --
-	if err := d.admitRequest(ctx, *infObjective.Spec.Criticality, reqCtx.FairnessID); err != nil {
+	if err := d.admitRequest(ctx, *infObjective.Spec.Priority, reqCtx.FairnessID); err != nil {
 		return reqCtx, err
 	}
 
@@ -161,22 +157,24 @@ func (d *Director) HandleRequest(ctx context.Context, reqCtx *handlers.RequestCo
 }
 
 // admitRequest handles admission control to decide whether or not to accept the request
-// based on the request criticality and system saturation state.
-func (d *Director) admitRequest(ctx context.Context, requestCriticality v1alpha2.Criticality, fairnessID string) error {
+// based on the request priority and system saturation state.
+func (d *Director) admitRequest(ctx context.Context, requestPriority int, fairnessID string) error {
 	logger := log.FromContext(ctx)
 
-	logger.V(logutil.TRACE).Info("Entering Flow Control", "criticality", requestCriticality, "fairnessID", fairnessID)
+	logger.V(logutil.TRACE).Info("Entering Flow Control", "priority", requestPriority, "fairnessID", fairnessID)
 
-	if requestCriticality == v1alpha2.Critical {
-		logger.V(logutil.DEBUG).Info("Critical request bypassing saturation check.")
+	// This will be removed in favor of a more robust implementation (Flow Control) in the very near future.
+	// TODO: Make this a configurable value.
+	// Tracking issue https://github.com/kubernetes-sigs/gateway-api-inference-extension/issues/1347
+	if requestPriority >= 0 {
+		logger.V(logutil.TRACE).Info("Non-sheddable request bypassing saturation check.")
 		return nil
 	}
 
-	logger.V(logutil.DEBUG).Info("Performing saturation check for non-critical request.")
 	if d.saturationDetector.IsSaturated(ctx) { // Assuming non-nil Saturation Detector
 		return errutil.Error{
 			Code: errutil.InferencePoolResourceExhausted,
-			Msg:  "system saturated, non-critical request dropped",
+			Msg:  "system saturated, sheddable request dropped",
 		}
 	}
 
@@ -194,13 +192,13 @@ func (d *Director) getCandidatePodsForScheduling(ctx context.Context, requestMet
 
 	subsetMap, found := requestMetadata[metadata.SubsetFilterNamespace].(map[string]any)
 	if !found {
-		return d.toSchedulerPodMetrics(d.datastore.PodList(backendmetrics.AllPodPredicate))
+		return d.toSchedulerPodMetrics(d.datastore.PodList(backendmetrics.AllPodsPredicate))
 	}
 
 	// Check if endpoint key is present in the subset map and ensure there is at least one value
 	endpointSubsetList, found := subsetMap[metadata.SubsetFilterKey].([]any)
 	if !found {
-		return d.toSchedulerPodMetrics(d.datastore.PodList(backendmetrics.AllPodPredicate))
+		return d.toSchedulerPodMetrics(d.datastore.PodList(backendmetrics.AllPodsPredicate))
 	} else if len(endpointSubsetList) == 0 {
 		loggerTrace.Info("found empty subset filter in request metadata, filtering all pods")
 		return []schedulingtypes.Pod{}
@@ -242,7 +240,10 @@ func (d *Director) prepareRequest(ctx context.Context, reqCtx *handlers.RequestC
 		return reqCtx, err
 	}
 	targetPods := []*backend.Pod{}
-	targetPort := int(pool.Spec.TargetPortNumber)
+	if len(pool.Spec.TargetPorts) != 1 {
+		return reqCtx, errutil.Error{Code: errutil.BadRequest, Msg: "targetPorts should have length 1"}
+	}
+	targetPort := int(pool.Spec.TargetPorts[0].Number)
 	targetEndpoints := []string{}
 
 	for _, pod := range result.ProfileResults[result.PrimaryProfileName].TargetPods {
@@ -286,7 +287,7 @@ func (d *Director) HandleResponse(ctx context.Context, reqCtx *handlers.RequestC
 }
 
 func (d *Director) GetRandomPod() *backend.Pod {
-	pods := d.datastore.PodList(backendmetrics.AllPodPredicate)
+	pods := d.datastore.PodList(backendmetrics.AllPodsPredicate)
 	if len(pods) == 0 {
 		return nil
 	}
@@ -295,37 +296,6 @@ func (d *Director) GetRandomPod() *backend.Pod {
 	return pod.GetPod()
 }
 
-func RandomWeightedDraw(logger logr.Logger, model *v1alpha2.InferenceObjective, seed int64) string {
-	// TODO: after we are down to 1 server implementation, make these methods a part of the struct
-	// and handle random seeding on the struct.
-	source := rand.NewSource(rand.Int63())
-	if seed > 0 {
-		source = rand.NewSource(seed)
-	}
-	r := rand.New(source)
-
-	// all the weight values are nil, then we should return random model name
-	if model.Spec.TargetModels[0].Weight == nil {
-		index := r.Int31n(int32(len(model.Spec.TargetModels)))
-		return model.Spec.TargetModels[index].Name
-	}
-
-	var weights int32
-	for _, model := range model.Spec.TargetModels {
-		weights += *model.Weight
-	}
-	logger.V(logutil.TRACE).Info("Weights for model computed", "model", model.Name, "weights", weights)
-	randomVal := r.Int31n(weights)
-	// TODO: optimize this without using loop
-	for _, model := range model.Spec.TargetModels {
-		if randomVal < *model.Weight {
-			return model.Name
-		}
-		randomVal -= *model.Weight
-	}
-	return ""
-}
-
 func (d *Director) runPreRequestPlugins(ctx context.Context, request *schedulingtypes.LLMRequest,
 	schedulingResult *schedulingtypes.SchedulingResult, targetPort int) {
 	loggerDebug := log.FromContext(ctx).V(logutil.DEBUG)
diff --git a/pkg/epp/saturationdetector/saturationdetector.go b/pkg/epp/saturationdetector/saturationdetector.go
index 57be1af..bc47ecd 100644
--- a/pkg/epp/saturationdetector/saturationdetector.go
+++ b/pkg/epp/saturationdetector/saturationdetector.go
@@ -109,7 +109,7 @@ func NewDetector(config *Config, datastore Datastore, logger logr.Logger) *Detec
 func (d *Detector) IsSaturated(ctx context.Context) bool {
 	logger := log.FromContext(ctx).WithName(loggerName)
 	// TODO: filter out stale metrics here if needed.
-	allPodsMetrics := d.datastore.PodList(backendmetrics.AllPodPredicate)
+	allPodsMetrics := d.datastore.PodList(backendmetrics.AllPodsPredicate)
 	if len(allPodsMetrics) == 0 {
 		logger.V(logutil.VERBOSE).Info("No pods found in datastore; system is considered SATURATED (no capacity).")
 		// If there are no pods, there is no capacity to serve requests.
diff --git a/pkg/epp/scheduling/framework/plugins.go b/pkg/epp/scheduling/framework/plugins.go
index 2c22843..99397a4 100644
--- a/pkg/epp/scheduling/framework/plugins.go
+++ b/pkg/epp/scheduling/framework/plugins.go
@@ -28,7 +28,6 @@ const (
 	FilterExtensionPoint                 = "Filter"
 	ScorerExtensionPoint                 = "Scorer"
 	PickerExtensionPoint                 = "Picker"
-	PostCycleExtensionPoint              = "PostCycle"
 	ProcessProfilesResultsExtensionPoint = "ProcessProfilesResults"
 )
 
@@ -69,10 +68,3 @@ type Picker interface {
 	plugins.Plugin
 	Pick(ctx context.Context, cycleState *types.CycleState, scoredPods []*types.ScoredPod) *types.ProfileRunResult
 }
-
-// PostCycle is called by the scheduler after it selects a targetPod for the request in the SchedulerProfile cycle.
-// DEPRECATED - do not use PostCycle. this is in the process of deprecation.
-type PostCycle interface {
-	plugins.Plugin
-	PostCycle(ctx context.Context, cycleState *types.CycleState, res *types.ProfileRunResult)
-}
diff --git a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go
index 9c1a8d8..8c7b52c 100644
--- a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go
+++ b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go
@@ -28,6 +28,7 @@ import (
 
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/requestcontrol"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types"
 	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
@@ -73,9 +74,10 @@ type Config struct {
 }
 
 type Plugin struct {
-	Config
-	typedName plugins.TypedName
-	indexer   Indexer
+	typedName   plugins.TypedName
+	config      Config
+	pluginState *plugins.PluginState
+	indexer     Indexer
 }
 
 // podSet holds an pods servers that may have a specific prefix hash.
@@ -122,10 +124,10 @@ func (s *SchedulingContextState) Clone() plugins.StateData {
 
 // compile-time type assertion
 var _ framework.Scorer = &Plugin{}
-var _ framework.PostCycle = &Plugin{}
+var _ requestcontrol.PreRequest = &Plugin{}
 
 // PrefixCachePluginFactory defines the factory function for Prefix plugin.
-func PrefixCachePluginFactory(name string, rawParameters json.RawMessage, _ plugins.Handle) (plugins.Plugin, error) {
+func PrefixCachePluginFactory(name string, rawParameters json.RawMessage, handle plugins.Handle) (plugins.Plugin, error) {
 	parameters := Config{
 		HashBlockSize:          DefaultHashBlockSize,
 		MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks,
@@ -138,11 +140,11 @@ func PrefixCachePluginFactory(name string, rawParameters json.RawMessage, _ plug
 		}
 	}
 
-	return New(parameters).WithName(name), nil
+	return New(handle.Context(), parameters).WithName(name), nil
 }
 
 // New initializes a new prefix Plugin and returns its pointer.
-func New(config Config) *Plugin {
+func New(ctx context.Context, config Config) *Plugin {
 	capacity := config.LRUCapacityPerServer
 	if capacity <= 0 {
 		capacity = DefaultLRUCapacityPerServer
@@ -153,34 +155,35 @@ func New(config Config) *Plugin {
 	}
 
 	return &Plugin{
-		typedName: plugins.TypedName{Type: PrefixCachePluginType, Name: PrefixCachePluginType},
-		Config:    config,
-		indexer:   newIndexer(capacity),
+		typedName:   plugins.TypedName{Type: PrefixCachePluginType, Name: PrefixCachePluginType},
+		config:      config,
+		pluginState: plugins.NewPluginState(ctx),
+		indexer:     newIndexer(capacity),
 	}
 }
 
 // TypedName returns the type and name tuple of this plugin instance.
-func (m *Plugin) TypedName() plugins.TypedName {
-	return m.typedName
+func (p *Plugin) TypedName() plugins.TypedName {
+	return p.typedName
 }
 
 // WithName sets the name of the plugin.
-func (m *Plugin) WithName(name string) *Plugin {
-	m.typedName.Name = name
-	return m
+func (p *Plugin) WithName(name string) *Plugin {
+	p.typedName.Name = name
+	return p
 }
 
 // Score returns the scoring result for the given list of pods based on context.
-func (m *Plugin) Score(ctx context.Context, cycleState *types.CycleState, request *types.LLMRequest, pods []types.Pod) map[types.Pod]float64 {
+func (p *Plugin) Score(ctx context.Context, _ *types.CycleState, request *types.LLMRequest, pods []types.Pod) map[types.Pod]float64 {
 	loggerTrace := log.FromContext(ctx).V(logutil.TRACE)
 	// pre score step, hashing prompt and find longest prefix match.
-	hashes := hashPrompt(ctx, request, m.HashBlockSize, m.MaxPrefixBlocksToMatch)
+	hashes := hashPrompt(ctx, request, p.config.HashBlockSize, p.config.MaxPrefixBlocksToMatch)
 	state := &SchedulingContextState{
 		PrefixHashes:       hashes,
-		PrefixCacheServers: m.matchLongestPrefix(ctx, hashes),
+		PrefixCacheServers: p.matchLongestPrefix(ctx, hashes),
 	}
 
-	cycleState.Write(plugins.StateKey(m.TypedName().Type), state)
+	p.pluginState.Write(request.RequestId, plugins.StateKey(p.TypedName().Type), state)
 	loggerTrace.Info(fmt.Sprintf("cached servers: %+v", state.PrefixCacheServers), "hashes", state.PrefixHashes)
 	// calculate the scores of pods
 	scores := make(map[types.Pod]float64, len(pods))
@@ -200,31 +203,34 @@ func (m *Plugin) Score(ctx context.Context, cycleState *types.CycleState, reques
 	return scores
 }
 
-// PostCycle records in the plugin cache the result of the scheduling selection.
-func (m *Plugin) PostCycle(ctx context.Context, cycleState *types.CycleState, res *types.ProfileRunResult) {
-	targetPod := res.TargetPods[0].GetPod()
-	state, err := types.ReadCycleStateKey[*SchedulingContextState](cycleState, PrefixCachePluginType)
+// PreRequest records in the plugin cache the result of the scheduling selection.
+func (p *Plugin) PreRequest(ctx context.Context, request *types.LLMRequest, schedulingResult *types.SchedulingResult, _ int) {
+	primaryProfileResult := schedulingResult.ProfileResults[schedulingResult.PrimaryProfileName]
+	targetPod := primaryProfileResult.TargetPods[0].GetPod() // get the first pod of the primary profile
+
+	state, err := plugins.ReadPluginStateKey[*SchedulingContextState](p.pluginState, request.RequestId, PrefixCachePluginType)
+	p.pluginState.Delete(request.RequestId) // delete the state explicitly after completing using it
 	if err != nil {
-		log.FromContext(ctx).Error(err, "failed to read prefix plugin cycle state")
+		log.FromContext(ctx).Error(err, "failed to read prefix plugin state", "requestID", request.RequestId)
 		return
 	}
 
-	m.indexer.Add(state.PrefixHashes, ServerID(targetPod.NamespacedName))
+	p.indexer.Add(state.PrefixHashes, ServerID(targetPod.NamespacedName))
 
 	total := len(state.PrefixHashes)
 	matchLen := state.PrefixCacheServers[ServerID(targetPod.NamespacedName)]
-	metrics.RecordPrefixCacheMatch(matchLen*m.HashBlockSize, total*m.HashBlockSize)
+	metrics.RecordPrefixCacheMatch(matchLen*p.config.HashBlockSize, total*p.config.HashBlockSize)
 }
 
 // matchLongestPrefix returns a map of servers and length of prefix that each server caches.
-func (m *Plugin) matchLongestPrefix(ctx context.Context, hashes []BlockHash) map[ServerID]int {
+func (p *Plugin) matchLongestPrefix(ctx context.Context, hashes []BlockHash) map[ServerID]int {
 	loggerTrace := log.FromContext(ctx).V(logutil.TRACE)
 	res := make(map[ServerID]int)
 	// Use a greedy strategy to search from the longest prefix.
 	// NOTE: It's possible to further optimize this with a binary search.
 	for i := 0; i < len(hashes); i++ {
 		hash := hashes[i]
-		cachedServers := m.indexer.Get(hash)
+		cachedServers := p.indexer.Get(hash)
 		if len(cachedServers) == 0 {
 			break
 		} else {
@@ -257,8 +263,16 @@ func hashPrompt(ctx context.Context, request *types.LLMRequest, cacheBlockSize i
 	// If the last block is smaller than cacheBlockSize, it will be ignored.
 	res := make([]BlockHash, 0, 1+len(prompt)/cacheBlockSize)
 	// Add the model to the first block hash so that different models have different hashes even with the same body.
-	res = append(res, BlockHash(xxhash.Sum64String(request.TargetModel)))
-	for i := 0; i+cacheBlockSize <= len(prompt); i += cacheBlockSize {
+
+	firstBlockSize := cacheBlockSize
+	if len(prompt) < cacheBlockSize {
+		firstBlockSize = len(prompt)
+	}
+	firstBlock := prompt[0:firstBlockSize]
+	firstBlockWithModel := append([]byte(request.TargetModel), firstBlock...)
+	res = append(res, BlockHash(xxhash.Sum64(firstBlockWithModel)))
+
+	for i := cacheBlockSize; i+cacheBlockSize <= len(prompt); i += cacheBlockSize {
 		block := prompt[i : i+cacheBlockSize]
 		prevBlockHash := res[len(res)-1]
 		block = append(block, toBytes(prevBlockHash)...)
diff --git a/pkg/epp/scheduling/framework/scheduler_profile.go b/pkg/epp/scheduling/framework/scheduler_profile.go
index b3b24d5..9790b98 100644
--- a/pkg/epp/scheduling/framework/scheduler_profile.go
+++ b/pkg/epp/scheduling/framework/scheduler_profile.go
@@ -34,19 +34,17 @@ import (
 // NewSchedulerProfile creates a new SchedulerProfile object and returns its pointer.
 func NewSchedulerProfile() *SchedulerProfile {
 	return &SchedulerProfile{
-		filters:          []Filter{},
-		scorers:          []*WeightedScorer{},
-		postCyclePlugins: []PostCycle{},
+		filters: []Filter{},
+		scorers: []*WeightedScorer{},
 		// picker remains nil since profile doesn't support multiple pickers
 	}
 }
 
 // SchedulerProfile provides a profile configuration for the scheduler which influence routing decisions.
 type SchedulerProfile struct {
-	filters          []Filter
-	scorers          []*WeightedScorer
-	picker           Picker
-	postCyclePlugins []PostCycle
+	filters []Filter
+	scorers []*WeightedScorer
+	picker  Picker
 }
 
 // WithFilters sets the given filter plugins as the Filter plugins.
@@ -70,13 +68,6 @@ func (p *SchedulerProfile) WithPicker(picker Picker) *SchedulerProfile {
 	return p
 }
 
-// WithPostCyclePlugins sets the given plugins as the PostCycle plugins.
-// If the SchedulerProfile has PostCycle plugins, this call replaces the existing plugins with the given ones.
-func (p *SchedulerProfile) WithPostCyclePlugins(plugins ...PostCycle) *SchedulerProfile {
-	p.postCyclePlugins = plugins
-	return p
-}
-
 // AddPlugins adds the given plugins to all scheduler plugins according to the interfaces each plugin implements.
 // A plugin may implement more than one scheduler plugin interface.
 // Special Case: In order to add a scorer, one must use the scorer.NewWeightedScorer function in order to provide a weight.
@@ -99,9 +90,6 @@ func (p *SchedulerProfile) AddPlugins(pluginObjects ...plugins.Plugin) error {
 			}
 			p.picker = picker
 		}
-		if postCyclePlugin, ok := plugin.(PostCycle); ok {
-			p.postCyclePlugins = append(p.postCyclePlugins, postCyclePlugin)
-		}
 	}
 	return nil
 }
@@ -115,21 +103,17 @@ func (p *SchedulerProfile) String() string {
 	for i, scorer := range p.scorers {
 		scorerNames[i] = fmt.Sprintf("%s: %d", scorer.TypedName(), scorer.Weight())
 	}
-	postCyclePluginNames := make([]string, len(p.postCyclePlugins))
-	for i, postCyclePlugin := range p.postCyclePlugins {
-		postCyclePluginNames[i] = postCyclePlugin.TypedName().String()
-	}
+
 	return fmt.Sprintf(
-		"{Filters: [%s], Scorers: [%s], Picker: %s, PostCyclePlugins: [%s]}",
+		"{Filters: [%s], Scorers: [%s], Picker: %s}",
 		strings.Join(filterNames, ", "),
 		strings.Join(scorerNames, ", "),
 		p.picker.TypedName(),
-		strings.Join(postCyclePluginNames, ", "),
 	)
 }
 
-// RunCycle runs a SchedulerProfile cycle. In other words, it invokes all the SchedulerProfile plugins in this
-// order - Filters, Scorers, Picker, PostCyclePlugins. After completing all, it returns the result.
+// Run runs a SchedulerProfile. It invokes all the SchedulerProfile plugins for the given request in this
+// order - Filters, Scorers, Picker. After completing all, it returns the result.
 func (p *SchedulerProfile) Run(ctx context.Context, request *types.LLMRequest, cycleState *types.CycleState, candidatePods []types.Pod) (*types.ProfileRunResult, error) {
 	pods := p.runFilterPlugins(ctx, request, cycleState, candidatePods)
 	if len(pods) == 0 {
@@ -140,8 +124,6 @@ func (p *SchedulerProfile) Run(ctx context.Context, request *types.LLMRequest, c
 
 	result := p.runPickerPlugin(ctx, cycleState, weightedScorePerPod)
 
-	p.runPostCyclePlugins(ctx, cycleState, result)
-
 	return result, nil
 }
 
@@ -166,8 +148,8 @@ func (p *SchedulerProfile) runFilterPlugins(ctx context.Context, request *types.
 }
 
 func (p *SchedulerProfile) runScorerPlugins(ctx context.Context, request *types.LLMRequest, cycleState *types.CycleState, pods []types.Pod) map[types.Pod]float64 {
-	loggerDebug := log.FromContext(ctx).V(logutil.DEBUG)
-	loggerDebug.Info("Before running scorer plugins", "pods", pods)
+	logger := log.FromContext(ctx)
+	logger.V(logutil.DEBUG).Info("Before running scorer plugins", "pods", pods)
 
 	weightedScorePerPod := make(map[types.Pod]float64, len(pods))
 	for _, pod := range pods {
@@ -175,16 +157,17 @@ func (p *SchedulerProfile) runScorerPlugins(ctx context.Context, request *types.
 	}
 	// Iterate through each scorer in the chain and accumulate the weighted scores.
 	for _, scorer := range p.scorers {
-		loggerDebug.Info("Running scorer plugin", "plugin", scorer.TypedName())
+		logger.V(logutil.DEBUG).Info("Running scorer plugin", "plugin", scorer.TypedName())
 		before := time.Now()
 		scores := scorer.Score(ctx, cycleState, request, pods)
 		metrics.RecordPluginProcessingLatency(ScorerExtensionPoint, scorer.TypedName().Type, scorer.TypedName().Name, time.Since(before))
 		for pod, score := range scores { // weight is relative to the sum of weights
+			logger.V(logutil.DEBUG).Info("Calculated score", "plugin", scorer.TypedName(), "endpoint", pod.GetPod().NamespacedName, "score", score)
 			weightedScorePerPod[pod] += enforceScoreRange(score) * float64(scorer.Weight())
 		}
-		loggerDebug.Info("Completed running scorer plugin successfully", "plugin", scorer.TypedName())
+		logger.V(logutil.DEBUG).Info("Completed running scorer plugin successfully", "plugin", scorer.TypedName())
 	}
-	loggerDebug.Info("Completed running scorer plugins successfully")
+	logger.V(logutil.DEBUG).Info("Completed running scorer plugins successfully")
 
 	return weightedScorePerPod
 }
@@ -207,17 +190,6 @@ func (p *SchedulerProfile) runPickerPlugin(ctx context.Context, cycleState *type
 	return result
 }
 
-func (p *SchedulerProfile) runPostCyclePlugins(ctx context.Context, cycleState *types.CycleState, result *types.ProfileRunResult) {
-	loggerDebug := log.FromContext(ctx).V(logutil.DEBUG)
-	for _, plugin := range p.postCyclePlugins {
-		loggerDebug.Info("Running post-cycle plugin", "plugin", plugin.TypedName())
-		before := time.Now()
-		plugin.PostCycle(ctx, cycleState, result)
-		metrics.RecordPluginProcessingLatency(PostCycleExtensionPoint, plugin.TypedName().Type, plugin.TypedName().Name, time.Since(before))
-		loggerDebug.Info("Completed running post-cycle plugin successfully", "plugin", plugin.TypedName())
-	}
-}
-
 func enforceScoreRange(score float64) float64 {
 	if score < 0 {
 		return 0
diff --git a/pkg/epp/server/controller_manager.go b/pkg/epp/server/controller_manager.go
index 05038f7..47e4f12 100644
--- a/pkg/epp/server/controller_manager.go
+++ b/pkg/epp/server/controller_manager.go
@@ -85,12 +85,23 @@ func defaultManagerOptions(gknn common.GKNN, metricsServerOptions metricsserver.
 }
 
 // NewDefaultManager creates a new controller manager with default configuration.
-func NewDefaultManager(gknn common.GKNN, restConfig *rest.Config, metricsServerOptions metricsserver.Options) (ctrl.Manager, error) {
+func NewDefaultManager(gknn common.GKNN, restConfig *rest.Config, metricsServerOptions metricsserver.Options, leaderElectionEnabled bool) (ctrl.Manager, error) {
 	opt, err := defaultManagerOptions(gknn, metricsServerOptions)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create controller manager options: %v", err)
 	}
+
+	if leaderElectionEnabled {
+		opt.LeaderElection = true
+		opt.LeaderElectionResourceLock = "leases"
+		// The lease name needs to be unique per EPP deployment.
+		opt.LeaderElectionID = fmt.Sprintf("epp-%s-%s.gateway-api-inference-extension.sigs.k8s.io", gknn.Namespace, gknn.Name)
+		opt.LeaderElectionNamespace = gknn.Namespace
+		opt.LeaderElectionReleaseOnCancel = true
+	}
+
 	manager, err := ctrl.NewManager(restConfig, opt)
+
 	if err != nil {
 		return nil, fmt.Errorf("failed to create controller manager: %v", err)
 	}
diff --git a/pkg/epp/server/runserver.go b/pkg/epp/server/runserver.go
index 1b84bd7..69a928e 100644
--- a/pkg/epp/server/runserver.go
+++ b/pkg/epp/server/runserver.go
@@ -38,6 +38,7 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/common"
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/controller"
+	dlmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/handlers"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/requestcontrol"
@@ -56,6 +57,7 @@ type ExtProcServerRunner struct {
 	MetricsStalenessThreshold        time.Duration
 	Director                         *requestcontrol.Director
 	SaturationDetector               requestcontrol.SaturationDetector
+	UseExperimentalDatalayerV2       bool // Pluggable data layer feature flag
 
 	// This should only be used in tests. We won't need this once we do not inject metrics in the tests.
 	// TODO:(https://github.com/kubernetes-sigs/gateway-api-inference-extension/issues/432) Cleanup
@@ -138,7 +140,12 @@ func (r *ExtProcServerRunner) SetupWithManager(ctx context.Context, mgr ctrl.Man
 // The runnable implements LeaderElectionRunnable with leader election disabled.
 func (r *ExtProcServerRunner) AsRunnable(logger logr.Logger) manager.Runnable {
 	return runnable.NoLeaderElection(manager.RunnableFunc(func(ctx context.Context) error {
-		backendmetrics.StartMetricsLogger(ctx, r.Datastore, r.RefreshPrometheusMetricsInterval, r.MetricsStalenessThreshold)
+		if r.UseExperimentalDatalayerV2 {
+			dlmetrics.StartMetricsLogger(ctx, r.Datastore, r.RefreshPrometheusMetricsInterval, r.MetricsStalenessThreshold)
+		} else {
+			backendmetrics.StartMetricsLogger(ctx, r.Datastore, r.RefreshPrometheusMetricsInterval, r.MetricsStalenessThreshold)
+		}
+
 		var srv *grpc.Server
 		if r.SecureServing {
 			var cert tls.Certificate
diff --git a/site-src/api-types/inferencepool.md b/site-src/api-types/inferencepool.md
index d279447..c4481b1 100644
--- a/site-src/api-types/inferencepool.md
+++ b/site-src/api-types/inferencepool.md
@@ -33,7 +33,8 @@ kind: InferencePool
 metadata:
   name: vllm-llama3-8b-instruct
 spec:
-  targetPortNumber: 8000
+  targetPorts:
+    - number: 8000
   selector:
     app: vllm-llama3-8b-instruct
   extensionRef:
diff --git a/site-src/contributing/devguide.md b/site-src/contributing/devguide.md
new file mode 100644
index 0000000..9e45a0d
--- /dev/null
+++ b/site-src/contributing/devguide.md
@@ -0,0 +1,51 @@
+# Developer Guide
+
+## Integration Tests Debug Guide
+This document provides detailed instructions on how to run and debug integration tests locally in debug mode.
+
+### Prerequisites
+
+#### 1. Install Required Tools
+Ensure the envtest tool is installed:
+```bash
+$ make envtest
+```
+
+#### 2. Verify Kubernetes Test Environment
+Run the following command to set up and verify the test environment:
+```bash
+$ ./bin/setup-envtest use 1.31.0 --bin-dir ./bin -p path
+bin/k8s/1.31.0-darwin-arm64
+```
+### Run test in shell
+```shell
+export KUBEBUILDER_ASSETS=<project-root-path>/bin/k8s/1.31.0-<platform-architecture> 
+go test sigs.k8s.io/gateway-api-inference-extension/test/integration/epp -run <test-function-name>
+```
+
+### Configure and Run in GoLand
+
+#### 1. Create Test Configuration
+Select the test case you want to debug:
+![](../images/modify-run-configuration.png)
+
+#### 2. Configure Environment Variables
+Set environment variables in the Run/Debug Configuration:
+
+![](../images/edit-environment-variables.png)
+
+**Required environment variable:**
+
+- **Name:** `KUBEBUILDER_ASSETS`
+- **Value:** `<project-root-path>/bin/k8s/1.31.0-<platform-architecture>`
+
+**Example path:**
+```
+/go/src/kubernetes.io/gateway-api-inference-extension/bin/k8s/1.31.0-darwin-arm64
+```
+
+#### 3. Set Breakpoints and Run
+
+Example Output:
+
+![](../images/running-example.png)
diff --git a/site-src/guides/adapter-rollout.md b/site-src/guides/adapter-rollout.md
index 0325ce2..0936d29 100644
--- a/site-src/guides/adapter-rollout.md
+++ b/site-src/guides/adapter-rollout.md
@@ -68,7 +68,7 @@ kind: InferenceModel
 metadata:
   name: food-review
 spec:
-  criticality: Standard
+  criticality: 1
   poolRef:
     name: vllm-llama3-8b-instruct
   targetModels:
@@ -106,7 +106,7 @@ kind: InferenceModel
 metadata:
   name: food-review
 spec:
-  criticality: Standard
+  criticality: 1
   poolRef:
     name: vllm-llama3-8b-instruct
   targetModels:
diff --git a/site-src/guides/implementers.md b/site-src/guides/implementers.md
index 4fb6ee7..6fce016 100644
--- a/site-src/guides/implementers.md
+++ b/site-src/guides/implementers.md
@@ -47,7 +47,8 @@ kind: InferencePool
 metadata:
   name: vllm-llama3-8b-instruct
 spec:
-  targetPortNumber: 8000
+  targetPorts:
+    - number: 8000
   selector:
     app: vllm-llama3-8b-instruct
   extensionRef:
@@ -141,7 +142,7 @@ Supporting this broad range of extension capabilities (including for inference,
 Several implementations can be used as references:
 
 - A fully featured [reference implementation](https://github.com/envoyproxy/envoy/tree/main/source/extensions/filters/http/ext_proc) (C++) can be found in the Envoy GitHub repository.
-- A second implementation (Rust, non-Envoy) is available in [Agent Gateway](https://github.com/agentgateway/agentgateway/blob/v0.5.2/crates/proxy/src/ext_proc.rs).
+- A second implementation (Rust, non-Envoy) is available in [agentgateway](https://github.com/agentgateway/agentgateway/blob/v0.7.2/crates/agentgateway/src/http/ext_proc.rs).
 
 #### Portable Implementation
 
diff --git a/site-src/guides/index.md b/site-src/guides/index.md
index 0cf6437..dbb104c 100644
--- a/site-src/guides/index.md
+++ b/site-src/guides/index.md
@@ -8,7 +8,7 @@ This quickstart guide is intended for engineers familiar with k8s and model serv
 
 ## **Prerequisites**
 
-- A cluster with:
+A cluster with:
   - Support for services of type `LoadBalancer`. For kind clusters, follow [this guide](https://kind.sigs.k8s.io/docs/user/loadbalancer)
   to get services of type LoadBalancer working.
   - Support for [sidecar containers](https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/) (enabled by default since Kubernetes v1.29)
@@ -71,30 +71,8 @@ This quickstart guide is intended for engineers familiar with k8s and model serv
 
 ### Install the Inference Extension CRDs
 
-=== "Latest Release"
-
-      ```bash
-      kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/releases/latest/download/manifests.yaml
-      ```
-
-=== "Dev Version"
-
-      ```bash
-      kubectl apply -k https://github.com/kubernetes-sigs/gateway-api-inference-extension/config/crd
-      ```
-
-### Deploy InferenceModel
-
-   Deploy the sample InferenceModel which is configured to forward traffic to the `food-review-1` [LoRA adapter](https://docs.vllm.ai/en/latest/features/lora.html) of the sample model server.
-
    ```bash
-   kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/inferenceobjective.yaml
-   ```
-
-### Deploy the InferencePool and Endpoint Picker Extension
-
-   ```bash
-   kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/inferencepool-resources.yaml
+   kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/releases/latest/download/manifests.yaml
    ```
 
 ### Deploy an Inference Gateway
@@ -106,20 +84,19 @@ This quickstart guide is intended for engineers familiar with k8s and model serv
       1. Enable the Gateway API and configure proxy-only subnets when necessary. See [Deploy Gateways](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways)
       for detailed instructions.
 
-      1. Deploy Gateway and HealthCheckPolicy resources
+      2. Deploy Inference Gateway:
 
          ```bash
          kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/gke/gateway.yaml
-         kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/gke/healthcheck.yaml
          ```
 
          Confirm that the Gateway was assigned an IP address and reports a `Programmed=True` status:
+
          ```bash
          $ kubectl get gateway inference-gateway
          NAME                CLASS               ADDRESS         PROGRAMMED   AGE
          inference-gateway   inference-gateway   <MY_ADDRESS>    True         22s
          ```
-
       3. Deploy the HTTPRoute
 
          ```bash
@@ -131,13 +108,7 @@ This quickstart guide is intended for engineers familiar with k8s and model serv
          ```bash
          kubectl get httproute llm-route -o yaml
          ```
-
-      5. Given that the default connection timeout may be insufficient for most inference workloads, it is recommended to configure a timeout appropriate for your intended use case.
-
-         ```bash
-         kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/gke/gcp-backend-policy.yaml
-         ```
-
+   
 === "Istio"
 
       Please note that this feature is currently in an experimental phase and is not intended for production use.
@@ -161,7 +132,7 @@ This quickstart guide is intended for engineers familiar with k8s and model serv
          wget https://storage.googleapis.com/istio-build/dev/$TAG/istioctl-$TAG-win.zip
          unzip istioctl-$TAG-win.zip
 
-         ./istioctl install --set tag=$TAG --set hub=gcr.io/istio-testing
+         ./istioctl install --set tag=$TAG --set hub=gcr.io/istio-testing --set values.pilot.env.ENABLE_GATEWAY_API_INFERENCE_EXTENSION=true
          ```
 
       3. If you run the Endpoint Picker (EPP) with the `--secure-serving` flag set to `true` (the default mode), it is currently using a self-signed certificate. As a security measure, Istio does not trust self-signed certificates by default. As a temporary workaround, you can apply the destination rule to bypass TLS verification for EPP. A more secure TLS implementation in EPP is being discussed in [Issue 582](https://github.com/kubernetes-sigs/gateway-api-inference-extension/issues/582).
@@ -244,6 +215,78 @@ This quickstart guide is intended for engineers familiar with k8s and model serv
          kubectl get httproute llm-route -o yaml
          ```
 
+=== "Agentgateway"
+
+      [Agentgateway](https://agentgateway.dev/) is a purpose-built proxy designed for AI workloads, and comes with native support for inference routing. Agentgateway integrates with [Kgateway](https://kgateway.dev/) as it's control plane.
+
+      1. Requirements
+
+         - [Helm](https://helm.sh/docs/intro/install/) installed.
+         - Gateway API [CRDs](https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api) installed.
+
+      2. Set the Kgateway version and install the Kgateway CRDs.
+
+         ```bash
+         KGTW_VERSION=v2.0.4
+         helm upgrade -i --create-namespace --namespace kgateway-system --version $KGTW_VERSION kgateway-crds oci://cr.kgateway.dev/kgateway-dev/charts/kgateway-crds
+         ```
+
+      3. Install Kgateway
+
+         ```bash
+         helm upgrade -i --namespace kgateway-system --version $KGTW_VERSION kgateway oci://cr.kgateway.dev/kgateway-dev/charts/kgateway --set inferenceExtension.enabled=true --set agentGateway.enabled=true
+         ```
+
+      4. Deploy the Gateway
+
+         ```bash
+         kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/agentgateway/gateway.yaml
+         ```
+
+         Confirm that the Gateway was assigned an IP address and reports a `Programmed=True` status:
+         ```bash
+         $ kubectl get gateway inference-gateway
+         NAME                CLASS               ADDRESS         PROGRAMMED   AGE
+         inference-gateway   agentgateway        <MY_ADDRESS>    True         22s
+         ```
+
+      5. Deploy the HTTPRoute
+
+         ```bash
+         kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/agentgateway/httproute.yaml
+         ```
+
+      6. Confirm that the HTTPRoute status conditions include `Accepted=True` and `ResolvedRefs=True`:
+
+         ```bash
+         kubectl get httproute llm-route -o yaml
+         ```
+
+
+### Deploy the InferencePool and Endpoint Picker Extension
+
+   Install an InferencePool named `vllm-llama3-8b-instruct` that selects from endpoints with label app: vllm-llama3-8b-instruct and listening on port 8000, you can run the following command:
+
+   ```bash
+   export GATEWAY_PROVIDER=none #  See [README](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/config/charts/inferencepool/README.md#configuration) for valid configurations
+   helm install vllm-llama3-8b-instruct \
+   --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \
+   --set provider.name=$GATEWAY_PROVIDER \
+   --version v0.3.0 \
+   oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool
+   ```
+
+   The Helm install automatically installs the endpoint-picker, inferencepool along with provider specific resources.
+
+### Deploy InferenceObjective (Optional)
+
+   Deploy the sample InferenceObjective which allows you to specify priority of requests.
+
+   ```bash
+   kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/inferenceobjective.yaml
+   ```
+
+
 ### Try it out
 
    Wait until the gateway is ready.
@@ -339,3 +382,25 @@ This quickstart guide is intended for engineers familiar with k8s and model serv
          ```bash
          kubectl delete ns kgateway-system
          ```
+
+=== "Agentgateway"
+
+      The following instructions assume you would like to cleanup ALL Kgateway resources that were created in this quickstart guide.
+
+      1. Uninstall Kgateway
+
+         ```bash
+         helm uninstall kgateway -n kgateway-system
+         ```
+
+      1. Uninstall the Kgateway CRDs.
+
+         ```bash
+         helm uninstall kgateway-crds -n kgateway-system
+         ```
+
+      1. Remove the Kgateway namespace.
+
+         ```bash
+         kubectl delete ns kgateway-system
+         ```
diff --git a/site-src/guides/inferencepool-rollout.md b/site-src/guides/inferencepool-rollout.md
index f5bef65..98b3cc4 100644
--- a/site-src/guides/inferencepool-rollout.md
+++ b/site-src/guides/inferencepool-rollout.md
@@ -56,7 +56,7 @@ kind: InferenceModel
 metadata:
   name: food-review-new
 spec:
-  criticality: Standard
+  criticality: 1
   poolRef:
     name: vllm-llama3-8b-instruct-new
   targetModels:
@@ -208,7 +208,8 @@ kind: InferencePool
 metadata:
   name: vllm-llama3-8b-instruct-new
 spec:
-  targetPortNumber: 8000
+  targetPorts:
+    - number: 8000
   selector:
     app: vllm-llama3-8b-instruct-new
   extensionRef:
diff --git a/site-src/guides/metrics-and-observability.md b/site-src/guides/metrics-and-observability.md
index 07b86a0..34b0db1 100644
--- a/site-src/guides/metrics-and-observability.md
+++ b/site-src/guides/metrics-and-observability.md
@@ -126,6 +126,81 @@ PROFILE_NAME=heap
 curl -H "Authorization: Bearer $TOKEN" localhost:9090/debug/pprof/$PROFILE_NAME -o profile.out
 go tool pprof -png profile.out
 ```
+## Setting Up Grafana + Prometheus
+
+### Grafana
+
+A simple grafana deployment can be done with the following commands:
+
+```bash
+helm repo add grafana https://grafana.github.io/helm-charts
+helm install grafana grafana/grafana --namespace monitoring --create-namespace
+```
+
+Get the Grafana URL to visit by running these commands in the same shell:
+
+```bash
+kubectl -n monitoring port-forward deploy/grafana 3000:3000
+```
+
+Get the generated password for the `admin` user:
+
+```bash
+kubectl -n monitoring get secret grafana \
+  -o go-template='{% raw %}{{ index .data "admin-password" | base64decode }}{% endraw %}'
+```
+
+You can now access the Grafana UI from [http://127.0.0.1:3000](http://127.0.0.1:3000)
+
+### Prometheus
+
+We currently have 2 types of prometheus deployments documented:
+
+1. Self Hosted using the prometheus helm chart
+2. Using Google Managed Prometheus
+
+=== "Self-Hosted"
+
+    Create necessary ServiceAccount and RBAC resources:
+
+     ```bash
+        kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/observability/prometheus/rbac.yaml
+     ```
+
+    Patch the metrics reader ClusterRoleBinding to reference the new ServiceAccount:
+    ```bash
+       kubectl patch clusterrolebinding inference-gateway-sa-metrics-reader-role-binding \
+         --type='json' \
+         -p='[{"op": "replace", "path": "/subjects/0/namespace", "value": "monitoring"}]'
+    ```
+
+    Add the prometheus-community helm repository:
+
+     ```bash
+        helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
+     ```
+
+    Deploy the prometheus helm chart using this command:
+     ```bash
+        helm install prometheus prometheus-community/prometheus \
+         --namespace monitoring \
+         -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/observability/prometheus/values.yaml
+     ```
+
+    You can add the prometheus data source to grafana following [This Guide](https://grafana.com/docs/grafana/latest/administration/data-source-management/).
+    The prometheus server host is by default `http://prometheus-server`
+
+    Notice that the given values file is very simple and will work directly after following the [Getting Started Guide](https://gateway-api-inference-extension.sigs.k8s.io/guides/), you might need to modify it
+
+=== "Google Managed"
+
+    If you run the inference gateway with [Google Managed Prometheus](https://cloud.google.com/stackdriver/docs/managed-prometheus), please follow the [instructions](https://cloud.google.com/stackdriver/docs/managed-prometheus/query)
+    to configure Google Managed Prometheus as data source for the grafana dashboard.
+
+## Load Inference Extension dashboard into Grafana
+
+Please follow [grafana instructions](https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/import-dashboards/) to load the dashboard json.
+The dashboard can be found here [Grafana Dashboard](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/tools/dashboards/inference_gateway.json)
 
 ## Prometheus Alerts
 
diff --git a/site-src/guides/serve-multiple-lora-adapters.md b/site-src/guides/serve-multiple-lora-adapters.md
index af78d26..59cfe72 100644
--- a/site-src/guides/serve-multiple-lora-adapters.md
+++ b/site-src/guides/serve-multiple-lora-adapters.md
@@ -27,7 +27,7 @@ kind: InferenceModel
 metadata:
   name: english-bot
 spec:
-  criticality: Standard
+  criticality: 1
   poolRef:
     name: gemma3
         
@@ -37,7 +37,7 @@ kind: InferenceModel
 metadata:
   name: spanish-bot
 spec:
-  criticality: Critical
+  criticality: 2
   poolRef:
     name: gemma3
     
diff --git a/site-src/images/edit-environment-variables.png b/site-src/images/edit-environment-variables.png
new file mode 100644
index 0000000..bf2030c
Binary files /dev/null and b/site-src/images/edit-environment-variables.png differ
diff --git a/site-src/images/modify-run-configuration.png b/site-src/images/modify-run-configuration.png
new file mode 100644
index 0000000..d72b81c
Binary files /dev/null and b/site-src/images/modify-run-configuration.png differ
diff --git a/site-src/images/running-example.png b/site-src/images/running-example.png
new file mode 100644
index 0000000..f909ee2
Binary files /dev/null and b/site-src/images/running-example.png differ
diff --git a/site-src/implementations/gateways.md b/site-src/implementations/gateways.md
index 53e6f92..5ab1e09 100644
--- a/site-src/implementations/gateways.md
+++ b/site-src/implementations/gateways.md
@@ -2,17 +2,45 @@
 
 This project has several implementations that are planned or in progress:
 
-* [Envoy AI Gateway][1]
-* [Kgateway][2]
-* [Google Kubernetes Engine][3]
-* [Istio][4]
-* [Alibaba Cloud Container Service for Kubernetes][5]
-
-[1]:#envoy-gateway
-[2]:#kgateway
-[3]:#google-kubernetes-engine
-[4]:#istio
-[5]:#alibaba-cloud-container-service-for-kubernetes
+* [Agentgateway][1]
+* [Alibaba Cloud Container Service for Kubernetes][2]
+* [Envoy AI Gateway][3]
+* [Google Kubernetes Engine][4]
+* [Istio][5]
+* [Kgateway][6]
+
+[1]:#agentgateway
+[2]:#alibaba-cloud-container-service-for-kubernetes
+[3]:#envoy-ai-gateway
+[4]:#google-kubernetes-engine
+[5]:#istio
+[6]:#kgateway
+
+## Agentgateway
+
+[Agentgateway](https://agentgateway.dev/) is an open source Gateway API implementation focusing on AI use cases, including LLM consumption, LLM serving, agent-to-agent ([A2A](https://a2aproject.github.io/A2A/latest/)), and agent-to-tool ([MCP](https://modelcontextprotocol.io/introduction)). It is the first and only proxy designed specifically for the Kubernetes Gateway API, powered by a high performance and scalable Rust dataplane implementation.
+
+Agentgateway comes with native support for Gateway API Inference Extension, powered by the [Kgateway](https://kgateway.dev/) control plane.
+
+## Alibaba Cloud Container Service for Kubernetes
+
+[Alibaba Cloud Container Service for Kubernetes (ACK)][ack] is a managed Kubernetes platform 
+offered by Alibaba Cloud. The implementation of the Gateway API in ACK is through the 
+[ACK Gateway with Inference Extension][ack-gie] component, which introduces model-aware, 
+GPU-efficient load balancing for AI workloads beyond basic HTTP routing.
+
+The ACK Gateway with Inference Extension implements the Gateway API Inference Extension 
+and provides optimized routing for serving generative AI workloads, 
+including weighted traffic splitting, mirroring, advanced routing, etc. 
+See the docs for the [usage][ack-gie-usage].
+
+Progress towards supporting Gateway API Inference Extension is being tracked 
+by [this Issue](https://github.com/AliyunContainerService/ack-gateway-api/issues/1).
+
+[ack]:https://www.alibabacloud.com/help/en/ack
+[ack-gie]:https://www.alibabacloud.com/help/en/ack/product-overview/ack-gateway-with-inference-extension
+[ack-gie-usage]:https://www.alibabacloud.com/help/en/ack/ack-managed-and-ack-dedicated/user-guide/intelligent-routing-and-traffic-management-with-ack-gateway-inference-extension
+
 
 ## Envoy AI Gateway
 
@@ -29,14 +57,6 @@ Issue](https://github.com/envoyproxy/ai-gateway/issues/423).
 [aigw-capabilities]:https://aigateway.envoyproxy.io/docs/capabilities/
 [aigw-quickstart]:https://aigateway.envoyproxy.io/docs/capabilities/gateway-api-inference-extension
 
-## Kgateway
-
-[Kgateway](https://kgateway.dev/) is a Gateway API Inference Extension
-[conformant](https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/conformance/reports/v0.5.1/gateway/kgateway)
-gateway that can run [independently](https://gateway-api-inference-extension.sigs.k8s.io/guides/#__tabbed_3_3), as an [Istio waypoint](https://kgateway.dev/blog/extend-istio-ambient-kgateway-waypoint/),
-or within your [llm-d infrastructure](https://github.com/llm-d-incubation/llm-d-infra) to improve accelerator (GPU)
-utilization for AI inference workloads.
-
 ## Google Kubernetes Engine
 
 [Google Kubernetes Engine (GKE)][gke] is a managed Kubernetes platform offered
@@ -66,21 +86,10 @@ For service mesh users, Istio also fully supports east-west (including [GAMMA](h
 Gateway API Inference Extension support is being tracked by this [GitHub
 Issue](https://github.com/istio/istio/issues/55768).
 
-## Alibaba Cloud Container Service for Kubernetes
-
-[Alibaba Cloud Container Service for Kubernetes (ACK)][ack] is a managed Kubernetes platform 
-offered by Alibaba Cloud. The implementation of the Gateway API in ACK is through the 
-[ACK Gateway with Inference Extension][ack-gie] component, which introduces model-aware, 
-GPU-efficient load balancing for AI workloads beyond basic HTTP routing.
-
-The ACK Gateway with Inference Extension implements the Gateway API Inference Extension 
-and provides optimized routing for serving generative AI workloads, 
-including weighted traffic splitting, mirroring, advanced routing, etc. 
-See the docs for the [usage][ack-gie-usage].
-
-Progress towards supporting Gateway API Inference Extension is being tracked 
-by [this Issue](https://github.com/AliyunContainerService/ack-gateway-api/issues/1).
+## Kgateway
 
-[ack]:https://www.alibabacloud.com/help/en/ack
-[ack-gie]:https://www.alibabacloud.com/help/en/ack/product-overview/ack-gateway-with-inference-extension
-[ack-gie-usage]:https://www.alibabacloud.com/help/en/ack/ack-managed-and-ack-dedicated/user-guide/intelligent-routing-and-traffic-management-with-ack-gateway-inference-extension
\ No newline at end of file
+[Kgateway](https://kgateway.dev/) is a Gateway API Inference Extension
+[conformant](https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/conformance/reports/v0.5.1/gateway/kgateway)
+gateway that can run [independently](https://gateway-api-inference-extension.sigs.k8s.io/guides/#__tabbed_3_3), as an [Istio waypoint](https://kgateway.dev/blog/extend-istio-ambient-kgateway-waypoint/),
+or within your [llm-d infrastructure](https://github.com/llm-d-incubation/llm-d-infra) to improve accelerator (GPU)
+utilization for AI inference workloads.
diff --git a/site-src/implementations/model-servers.md b/site-src/implementations/model-servers.md
index 72f9931..da9968f 100644
--- a/site-src/implementations/model-servers.md
+++ b/site-src/implementations/model-servers.md
@@ -1,5 +1,3 @@
-
-
 # Supported Model Servers
 
 Any model server that conform to the [model server protocol](https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/docs/proposals/003-model-server-protocol) are supported by the inference extension.
@@ -11,6 +9,7 @@ Any model server that conform to the [model server protocol](https://github.com/
 | vLLM V0              | v0.6.4 and above                                                                                                       | [commit 0ad216f](https://github.com/vllm-project/vllm/commit/0ad216f5750742115c686723bf38698372d483fd)                            |                                                                                                             |
 | vLLM V1              | v0.8.0 and above                                                                                                       | [commit bc32bc7](https://github.com/vllm-project/vllm/commit/bc32bc73aad076849ac88565cff745b01b17d89c)                            |                                                                                                             |
 | Triton(TensorRT-LLM) | [25.03](https://docs.nvidia.com/deeplearning/triton-inference-server/release-notes/rel-25-03.html#rel-25-03) and above | [commit 15cb989](https://github.com/triton-inference-server/tensorrtllm_backend/commit/15cb989b00523d8e92dce5165b9b9846c047a70d). | LoRA affinity feature is not available as the required LoRA metrics haven't been implemented in Triton yet. [Feature request](https://github.com/triton-inference-server/server/issues/8181) |
+| SGLang               | v0.4.0 and above | [commit 1929c06](https://github.com/sgl-project/sglang/commit/1929c067625089c9c3c04321578f450275f24041) | Set `--enable-metrics` on the model server. LoRA affinity feature is not available as the required LoRA metrics haven't been implemented in SGLang yet.
 
 ## vLLM
 
@@ -36,3 +35,18 @@ Use `--set inferencePool.modelServerType=triton-tensorrt-llm` to install the [`i
 - --lora-info-metric
 - "" # Set an empty metric to disable LoRA metric scraping as they are not supported by Triton yet.
 ```
+
+## SGLang
+
+### Edit EPP deployment yaml
+
+ Add the following to the `args` of the [EPP deployment](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/42eb5ff1c5af1275df43ac384df0ddf20da95134/config/manifests/inferencepool-resources.yaml#L32)
+
+```
+- --totalQueuedRequestsMetric
+- "sglang:num_queue_reqs"
+- --kvCacheUsagePercentageMetric
+- "sglang:token_usage"
+- --lora-info-metric
+- "" # Set an empty metric to disable LoRA metric scraping as they are not supported by SGLang yet.
+```
diff --git a/site-src/reference/spec.md b/site-src/reference/spec.md
index 4260597..a06968b 100644
--- a/site-src/reference/spec.md
+++ b/site-src/reference/spec.md
@@ -15,101 +15,44 @@ inference.networking.k8s.io API group.
 
 
 
-#### EndpointPickerConfig
-
-
-
-EndpointPickerConfig specifies the configuration needed by the proxy to discover and connect to the endpoint picker extension.
-This type is intended to be a union of mutually exclusive configuration options that we may add in the future.
-
-
-
-_Appears in:_
-- [InferencePoolSpec](#inferencepoolspec)
-
-| Field | Description | Default | Validation |
-| --- | --- | --- | --- |
-| `extensionRef` _[Extension](#extension)_ | Extension configures an endpoint picker as an extension service. |  | Required: \{\} <br /> |
-
-
-#### Extension
-
-
-
-Extension specifies how to configure an extension that runs the endpoint picker.
-
-
-
-_Appears in:_
-- [EndpointPickerConfig](#endpointpickerconfig)
-- [InferencePoolSpec](#inferencepoolspec)
-
-| Field | Description | Default | Validation |
-| --- | --- | --- | --- |
-| `group` _[Group](#group)_ | Group is the group of the referent.<br />The default value is "", representing the Core API group. |  | MaxLength: 253 <br />Pattern: `^$\|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` <br /> |
-| `kind` _[Kind](#kind)_ | Kind is the Kubernetes resource kind of the referent.<br />Defaults to "Service" when not specified.<br />ExternalName services can refer to CNAME DNS records that may live<br />outside of the cluster and as such are difficult to reason about in<br />terms of conformance. They also may not be safe to forward to (see<br />CVE-2021-25740 for more information). Implementations MUST NOT<br />support ExternalName Services. | Service | MaxLength: 63 <br />MinLength: 1 <br />Pattern: `^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$` <br /> |
-| `name` _[ObjectName](#objectname)_ | Name is the name of the referent. |  | MaxLength: 253 <br />MinLength: 1 <br />Required: \{\} <br /> |
-| `portNumber` _[PortNumber](#portnumber)_ | The port number on the service running the extension. When unspecified,<br />implementations SHOULD infer a default value of 9002 when the Kind is<br />Service. |  | Maximum: 65535 <br />Minimum: 1 <br /> |
-| `failureMode` _[ExtensionFailureMode](#extensionfailuremode)_ | Configures how the gateway handles the case when the extension is not responsive.<br />Defaults to failClose. | FailClose | Enum: [FailOpen FailClose] <br /> |
-
-
-#### ExtensionConnection
-
-
-
-ExtensionConnection encapsulates options that configures the connection to the extension.
-
-
-
-_Appears in:_
-- [Extension](#extension)
-
-| Field | Description | Default | Validation |
-| --- | --- | --- | --- |
-| `failureMode` _[ExtensionFailureMode](#extensionfailuremode)_ | Configures how the gateway handles the case when the extension is not responsive.<br />Defaults to failClose. | FailClose | Enum: [FailOpen FailClose] <br /> |
-
-
-#### ExtensionFailureMode
+#### EndpointPickerFailureMode
 
 _Underlying type:_ _string_
 
-ExtensionFailureMode defines the options for how the gateway handles the case when the extension is not
-responsive.
+EndpointPickerFailureMode defines the options for how the parent handles the case when the
+Endpoint Picker extension is non-responsive.
 
 _Validation:_
 - Enum: [FailOpen FailClose]
 
 _Appears in:_
-- [Extension](#extension)
-- [ExtensionConnection](#extensionconnection)
+- [EndpointPickerRef](#endpointpickerref)
 
 | Field | Description |
 | --- | --- |
-| `FailOpen` | FailOpen specifies that the proxy should forward the request to an endpoint of its picking when the Endpoint Picker fails.<br /> |
-| `FailClose` | FailClose specifies that the proxy should drop the request when the Endpoint Picker fails.<br /> |
-
+| `FailOpen` | EndpointPickerFailOpen specifies that the parent should forward the request to an endpoint<br />of its picking when the Endpoint Picker extension fails.<br /> |
+| `FailClose` | EndpointPickerFailClose specifies that the parent should drop the request when the Endpoint<br />Picker extension fails.<br /> |
 
-#### ExtensionReference
 
+#### EndpointPickerRef
 
 
-ExtensionReference is a reference to the extension.
 
-If a reference is invalid, the implementation MUST update the `ResolvedRefs`
-Condition on the InferencePool's status to `status: False`. A 5XX status code MUST be returned
-for the request that would have otherwise been routed to the invalid backend.
+EndpointPickerRef specifies a reference to an Endpoint Picker extension and its
+associated configuration.
 
 
 
 _Appears in:_
-- [Extension](#extension)
+- [InferencePoolSpec](#inferencepoolspec)
 
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
-| `group` _[Group](#group)_ | Group is the group of the referent.<br />The default value is "", representing the Core API group. |  | MaxLength: 253 <br />Pattern: `^$\|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` <br /> |
-| `kind` _[Kind](#kind)_ | Kind is the Kubernetes resource kind of the referent.<br />Defaults to "Service" when not specified.<br />ExternalName services can refer to CNAME DNS records that may live<br />outside of the cluster and as such are difficult to reason about in<br />terms of conformance. They also may not be safe to forward to (see<br />CVE-2021-25740 for more information). Implementations MUST NOT<br />support ExternalName Services. | Service | MaxLength: 63 <br />MinLength: 1 <br />Pattern: `^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$` <br /> |
-| `name` _[ObjectName](#objectname)_ | Name is the name of the referent. |  | MaxLength: 253 <br />MinLength: 1 <br />Required: \{\} <br /> |
-| `portNumber` _[PortNumber](#portnumber)_ | The port number on the service running the extension. When unspecified,<br />implementations SHOULD infer a default value of 9002 when the Kind is<br />Service. |  | Maximum: 65535 <br />Minimum: 1 <br /> |
+| `group` _[Group](#group)_ | Group is the group of the referent API object. When unspecified, the default value<br />is "", representing the Core API group. |  | MaxLength: 253 <br />MinLength: 0 <br />Pattern: `^$\|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` <br /> |
+| `kind` _[Kind](#kind)_ | Kind is the Kubernetes resource kind of the referent API object. When unspecified,<br />the referent is assumed to be a "Service" kind.<br />ExternalName services can refer to CNAME DNS records that may live<br />outside of the cluster and as such are difficult to reason about in<br />terms of conformance. They also may not be safe to forward to (see<br />CVE-2021-25740 for more information). Implementations MUST NOT<br />support ExternalName Services. | Service | MaxLength: 63 <br />MinLength: 1 <br />Pattern: `^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$` <br /> |
+| `name` _[ObjectName](#objectname)_ | Name is the name of the referent API object. |  | MaxLength: 253 <br />MinLength: 1 <br /> |
+| `portNumber` _[PortNumber](#portnumber)_ | PortNumber is the port number of the Endpoint Picker extension service. When unspecified,<br />implementations SHOULD infer a default value of 9002 when the kind field is "Service" or<br />unspecified (defaults to "Service"). |  | Maximum: 65535 <br />Minimum: 1 <br /> |
+| `failureMode` _[EndpointPickerFailureMode](#endpointpickerfailuremode)_ | FailureMode configures how the parent handles the case when the Endpoint Picker extension<br />is non-responsive. When unspecified, defaults to "FailClose". | FailClose | Enum: [FailOpen FailClose] <br /> |
 
 
 #### Group
@@ -134,12 +77,12 @@ Invalid values include:
 
 _Validation:_
 - MaxLength: 253
+- MinLength: 0
 - Pattern: `^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
 
 _Appears in:_
-- [Extension](#extension)
-- [ExtensionReference](#extensionreference)
-- [ParentGatewayReference](#parentgatewayreference)
+- [EndpointPickerRef](#endpointpickerref)
+- [ParentReference](#parentreference)
 
 
 
@@ -159,8 +102,8 @@ InferencePool is the Schema for the InferencePools API.
 | `apiVersion` _string_ | `inference.networking.k8s.io/v1` | | |
 | `kind` _string_ | `InferencePool` | | |
 | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. |  |  |
-| `spec` _[InferencePoolSpec](#inferencepoolspec)_ |  |  |  |
-| `status` _[InferencePoolStatus](#inferencepoolstatus)_ | Status defines the observed state of InferencePool. | \{ parent:[map[conditions:[map[lastTransitionTime:1970-01-01T00:00:00Z message:Waiting for controller reason:Pending status:Unknown type:Accepted]] parentRef:map[kind:Status name:default]]] \} |  |
+| `spec` _[InferencePoolSpec](#inferencepoolspec)_ | Spec defines the desired state of the InferencePool. |  |  |
+| `status` _[InferencePoolStatus](#inferencepoolstatus)_ | Status defines the observed state of the InferencePool. |  |  |
 
 
 
@@ -171,7 +114,7 @@ InferencePool is the Schema for the InferencePools API.
 
 
 
-InferencePoolSpec defines the desired state of InferencePool
+InferencePoolSpec defines the desired state of the InferencePool.
 
 
 
@@ -180,16 +123,16 @@ _Appears in:_
 
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
-| `selector` _object (keys:[LabelKey](#labelkey), values:[LabelValue](#labelvalue))_ | Selector defines a map of labels to watch model server Pods<br />that should be included in the InferencePool.<br />In some cases, implementations may translate this field to a Service selector, so this matches the simple<br />map used for Service selectors instead of the full Kubernetes LabelSelector type.<br />If specified, it will be applied to match the model server pods in the same namespace as the InferencePool.<br />Cross namesoace selector is not supported. |  | Required: \{\} <br /> |
-| `targetPortNumber` _integer_ | TargetPortNumber defines the port number to access the selected model server Pods.<br />The number must be in the range 1 to 65535. |  | Maximum: 65535 <br />Minimum: 1 <br />Required: \{\} <br /> |
-| `extensionRef` _[Extension](#extension)_ | Extension configures an endpoint picker as an extension service. |  | Required: \{\} <br /> |
+| `selector` _[LabelSelector](#labelselector)_ | Selector determines which Pods are members of this inference pool.<br />It matches Pods by their labels only within the same namespace; cross-namespace<br />selection is not supported.<br />The structure of this LabelSelector is intentionally simple to be compatible<br />with Kubernetes Service selectors, as some implementations may translate<br />this configuration into a Service resource. |  |  |
+| `targetPorts` _[Port](#port) array_ | TargetPorts defines a list of ports that are exposed by this InferencePool.<br />Currently, the list may only include a single port definition. |  | MaxItems: 1 <br />MinItems: 1 <br /> |
+| `endpointPickerRef` _[EndpointPickerRef](#endpointpickerref)_ | EndpointPickerRef is a reference to the Endpoint Picker extension and its<br />associated configuration. |  |  |
 
 
 #### InferencePoolStatus
 
 
 
-InferencePoolStatus defines the observed state of InferencePool.
+InferencePoolStatus defines the observed state of the InferencePool.
 
 
 
@@ -198,7 +141,7 @@ _Appears in:_
 
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
-| `parent` _[PoolStatus](#poolstatus) array_ | Parents is a list of parent resources (usually Gateways) that are<br />associated with the InferencePool, and the status of the InferencePool with respect to<br />each parent.<br />A maximum of 32 Gateways will be represented in this list. When the list contains<br />`kind: Status, name: default`, it indicates that the InferencePool is not<br />associated with any Gateway and a controller must perform the following:<br /> - Remove the parent when setting the "Accepted" condition.<br /> - Add the parent when the controller will no longer manage the InferencePool<br />   and no other parents exist. |  | MaxItems: 32 <br /> |
+| `parents` _[ParentStatus](#parentstatus) array_ | Parents is a list of parent resources, typically Gateways, that are associated with<br />the InferencePool, and the status of the InferencePool with respect to each parent.<br />A controller that manages the InferencePool, must add an entry for each parent it manages<br />and remove the parent entry when the controller no longer considers the InferencePool to<br />be associated with that parent.<br />A maximum of 32 parents will be represented in this list. When the list is empty,<br />it indicates that the InferencePool is not associated with any parents. |  | MaxItems: 32 <br /> |
 
 
 #### Kind
@@ -222,9 +165,8 @@ _Validation:_
 - Pattern: `^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$`
 
 _Appears in:_
-- [Extension](#extension)
-- [ExtensionReference](#extensionreference)
-- [ParentGatewayReference](#parentgatewayreference)
+- [EndpointPickerRef](#endpointpickerref)
+- [ParentReference](#parentreference)
 
 
 
@@ -256,9 +198,26 @@ _Validation:_
 - MinLength: 1
 - Pattern: `^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$`
 
+_Appears in:_
+- [LabelSelector](#labelselector)
+
+
+
+#### LabelSelector
+
+
+
+LabelSelector defines a query for resources based on their labels.
+This simplified version uses only the matchLabels field.
+
+
+
 _Appears in:_
 - [InferencePoolSpec](#inferencepoolspec)
 
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `matchLabels` _object (keys:[LabelKey](#labelkey), values:[LabelValue](#labelvalue))_ | MatchLabels contains a set of required \{key,value\} pairs.<br />An object must match every label in this map to be selected.<br />The matching logic is an AND operation on all entries. |  | MaxItems: 64 <br />MinItems: 1 <br /> |
 
 
 #### LabelValue
@@ -283,7 +242,7 @@ _Validation:_
 - Pattern: `^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$`
 
 _Appears in:_
-- [InferencePoolSpec](#inferencepoolspec)
+- [LabelSelector](#labelselector)
 
 
 
@@ -313,7 +272,7 @@ _Validation:_
 - Pattern: `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`
 
 _Appears in:_
-- [ParentGatewayReference](#parentgatewayreference)
+- [ParentReference](#parentreference)
 
 
 
@@ -330,37 +289,36 @@ _Validation:_
 - MinLength: 1
 
 _Appears in:_
-- [Extension](#extension)
-- [ExtensionReference](#extensionreference)
-- [ParentGatewayReference](#parentgatewayreference)
+- [EndpointPickerRef](#endpointpickerref)
+- [ParentReference](#parentreference)
 
 
 
-#### ParentGatewayReference
+#### ParentReference
 
 
 
-ParentGatewayReference identifies an API object including its namespace,
-defaulting to Gateway.
+ParentReference identifies an API object. It is used to associate the InferencePool with a
+parent resource, such as a Gateway.
 
 
 
 _Appears in:_
-- [PoolStatus](#poolstatus)
+- [ParentStatus](#parentstatus)
 
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
-| `group` _[Group](#group)_ | Group is the group of the referent. | gateway.networking.k8s.io | MaxLength: 253 <br />Pattern: `^$\|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` <br /> |
-| `kind` _[Kind](#kind)_ | Kind is kind of the referent. For example "Gateway". | Gateway | MaxLength: 63 <br />MinLength: 1 <br />Pattern: `^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$` <br /> |
-| `name` _[ObjectName](#objectname)_ | Name is the name of the referent. |  | MaxLength: 253 <br />MinLength: 1 <br /> |
-| `namespace` _[Namespace](#namespace)_ | Namespace is the namespace of the referent.  If not present,<br />the namespace of the referent is assumed to be the same as<br />the namespace of the referring object. |  | MaxLength: 63 <br />MinLength: 1 <br />Pattern: `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$` <br /> |
+| `group` _[Group](#group)_ | Group is the group of the referent API object. When unspecified, the referent is assumed<br />to be in the "gateway.networking.k8s.io" API group. | gateway.networking.k8s.io | MaxLength: 253 <br />MinLength: 0 <br />Pattern: `^$\|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` <br /> |
+| `kind` _[Kind](#kind)_ | Kind is the kind of the referent API object. When unspecified, the referent is assumed<br />to be a "Gateway" kind. | Gateway | MaxLength: 63 <br />MinLength: 1 <br />Pattern: `^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$` <br /> |
+| `name` _[ObjectName](#objectname)_ | Name is the name of the referent API object. |  | MaxLength: 253 <br />MinLength: 1 <br /> |
+| `namespace` _[Namespace](#namespace)_ | Namespace is the namespace of the referenced object. When unspecified, the local<br />namespace is inferred.<br />Note that when a namespace different than the local namespace is specified,<br />a ReferenceGrant object is required in the referent namespace to allow that<br />namespace's owner to accept the reference. See the ReferenceGrant<br />documentation for details: https://gateway-api.sigs.k8s.io/api-types/referencegrant/ |  | MaxLength: 63 <br />MinLength: 1 <br />Pattern: `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$` <br /> |
 
 
-#### PoolStatus
+#### ParentStatus
 
 
 
-PoolStatus defines the observed state of InferencePool from a Gateway.
+ParentStatus defines the observed state of InferencePool from a Parent, i.e. Gateway.
 
 
 
@@ -369,8 +327,24 @@ _Appears in:_
 
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
-| `parentRef` _[ParentGatewayReference](#parentgatewayreference)_ | GatewayRef indicates the gateway that observed state of InferencePool. |  |  |
-| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | Conditions track the state of the InferencePool.<br />Known condition types are:<br />* "Accepted"<br />* "ResolvedRefs" | [map[lastTransitionTime:1970-01-01T00:00:00Z message:Waiting for controller reason:Pending status:Unknown type:Accepted]] | MaxItems: 8 <br /> |
+| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | Conditions is a list of status conditions that provide information about the observed<br />state of the InferencePool. This field is required to be set by the controller that<br />manages the InferencePool.<br />Supported condition types are:<br />* "Accepted"<br />* "ResolvedRefs" |  | MaxItems: 8 <br /> |
+| `parentRef` _[ParentReference](#parentreference)_ | ParentRef is used to identify the parent resource that this status<br />is associated with. It is used to match the InferencePool with the parent<br />resource, such as a Gateway. |  |  |
+
+
+#### Port
+
+
+
+Port defines the network port that will be exposed by this InferencePool.
+
+
+
+_Appears in:_
+- [InferencePoolSpec](#inferencepoolspec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `number` _[PortNumber](#portnumber)_ | Number defines the port number to access the selected model server Pods.<br />The number must be in the range 1 to 65535. |  | Maximum: 65535 <br />Minimum: 1 <br /> |
 
 
 #### PortNumber
@@ -384,8 +358,8 @@ _Validation:_
 - Minimum: 1
 
 _Appears in:_
-- [Extension](#extension)
-- [ExtensionReference](#extensionreference)
+- [EndpointPickerRef](#endpointpickerref)
+- [Port](#port)
 
 
 
diff --git a/site-src/reference/x-spec.md b/site-src/reference/x-spec.md
index 5791e8d..9151dcd 100644
--- a/site-src/reference/x-spec.md
+++ b/site-src/reference/x-spec.md
@@ -11,49 +11,11 @@ inference.networking.x-k8s.io API group.
 
 
 ### Resource Types
-- [InferenceModel](#inferencemodel)
+- [InferenceObjective](#inferenceobjective)
 - [InferencePool](#inferencepool)
 
 
 
-#### Criticality
-
-_Underlying type:_ _string_
-
-Criticality defines how important it is to serve the model compared to other models.
-Criticality is intentionally a bounded enum to contain the possibilities that need to be supported by the load balancing algorithm. Any reference to the Criticality field must be optional (use a pointer), and set no default.
-This allows us to union this with a oneOf field in the future should we wish to adjust/extend this behavior.
-
-_Validation:_
-- Enum: [Critical Standard Sheddable]
-
-_Appears in:_
-- [InferenceModelSpec](#inferencemodelspec)
-
-| Field | Description |
-| --- | --- |
-| `Critical` | Critical defines the highest level of criticality. Requests to this band will be shed last.<br /> |
-| `Standard` | Standard defines the base criticality level and is more important than Sheddable but less<br />important than Critical. Requests in this band will be shed before critical traffic.<br />Most models are expected to fall within this band.<br /> |
-| `Sheddable` | Sheddable defines the lowest level of criticality. Requests to this band will be shed before<br />all other bands.<br /> |
-
-
-#### EndpointPickerConfig
-
-
-
-EndpointPickerConfig specifies the configuration needed by the proxy to discover and connect to the endpoint picker extension.
-This type is intended to be a union of mutually exclusive configuration options that we may add in the future.
-
-
-
-_Appears in:_
-- [InferencePoolSpec](#inferencepoolspec)
-
-| Field | Description | Default | Validation |
-| --- | --- | --- | --- |
-| `extensionRef` _[Extension](#extension)_ | Extension configures an endpoint picker as an extension service. |  | Required: \{\} <br /> |
-
-
 #### Extension
 
 
@@ -63,7 +25,6 @@ Extension specifies how to configure an extension that runs the endpoint picker.
 
 
 _Appears in:_
-- [EndpointPickerConfig](#endpointpickerconfig)
 - [InferencePoolSpec](#inferencepoolspec)
 
 | Field | Description | Default | Validation |
@@ -75,22 +36,6 @@ _Appears in:_
 | `failureMode` _[ExtensionFailureMode](#extensionfailuremode)_ | Configures how the gateway handles the case when the extension is not responsive.<br />Defaults to failClose. | FailClose | Enum: [FailOpen FailClose] <br /> |
 
 
-#### ExtensionConnection
-
-
-
-ExtensionConnection encapsulates options that configures the connection to the extension.
-
-
-
-_Appears in:_
-- [Extension](#extension)
-
-| Field | Description | Default | Validation |
-| --- | --- | --- | --- |
-| `failureMode` _[ExtensionFailureMode](#extensionfailuremode)_ | Configures how the gateway handles the case when the extension is not responsive.<br />Defaults to failClose. | FailClose | Enum: [FailOpen FailClose] <br /> |
-
-
 #### ExtensionFailureMode
 
 _Underlying type:_ _string_
@@ -103,7 +48,6 @@ _Validation:_
 
 _Appears in:_
 - [Extension](#extension)
-- [ExtensionConnection](#extensionconnection)
 
 | Field | Description |
 | --- | --- |
@@ -111,34 +55,6 @@ _Appears in:_
 | `FailClose` | FailClose specifies that the proxy should drop the request when the Endpoint Picker fails.<br /> |
 
 
-#### ExtensionReference
-
-
-
-ExtensionReference is a reference to the extension.
-
-Connections to this extension MUST use TLS by default. Implementations MAY
-provide a way to customize this connection to use cleartext, a different
-protocol, or custom TLS configuration.
-
-If a reference is invalid, the implementation MUST update the `ResolvedRefs`
-Condition on the InferencePool's status to `status: False`. A 5XX status code
-MUST be returned for the request that would have otherwise been routed to the
-invalid backend.
-
-
-
-_Appears in:_
-- [Extension](#extension)
-
-| Field | Description | Default | Validation |
-| --- | --- | --- | --- |
-| `group` _[Group](#group)_ | Group is the group of the referent.<br />The default value is "", representing the Core API group. |  | MaxLength: 253 <br />Pattern: `^$\|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` <br /> |
-| `kind` _[Kind](#kind)_ | Kind is the Kubernetes resource kind of the referent.<br />Defaults to "Service" when not specified.<br />ExternalName services can refer to CNAME DNS records that may live<br />outside of the cluster and as such are difficult to reason about in<br />terms of conformance. They also may not be safe to forward to (see<br />CVE-2021-25740 for more information). Implementations MUST NOT<br />support ExternalName Services. | Service | MaxLength: 63 <br />MinLength: 1 <br />Pattern: `^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$` <br /> |
-| `name` _[ObjectName](#objectname)_ | Name is the name of the referent. |  | MaxLength: 253 <br />MinLength: 1 <br />Required: \{\} <br /> |
-| `portNumber` _[PortNumber](#portnumber)_ | The port number on the service running the extension. When unspecified,<br />implementations SHOULD infer a default value of 9002 when the Kind is<br />Service. |  | Maximum: 65535 <br />Minimum: 1 <br /> |
-
-
 #### Group
 
 _Underlying type:_ _string_
@@ -165,17 +81,16 @@ _Validation:_
 
 _Appears in:_
 - [Extension](#extension)
-- [ExtensionReference](#extensionreference)
 - [ParentGatewayReference](#parentgatewayreference)
 - [PoolObjectReference](#poolobjectreference)
 
 
 
-#### InferenceModel
+#### InferenceObjective
 
 
 
-InferenceModel is the Schema for the InferenceModels API.
+InferenceObjective is the Schema for the InferenceObjectives API.
 
 
 
@@ -184,21 +99,21 @@ InferenceModel is the Schema for the InferenceModels API.
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
 | `apiVersion` _string_ | `inference.networking.x-k8s.io/v1alpha2` | | |
-| `kind` _string_ | `InferenceModel` | | |
+| `kind` _string_ | `InferenceObjective` | | |
 | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. |  |  |
-| `spec` _[InferenceModelSpec](#inferencemodelspec)_ |  |  |  |
-| `status` _[InferenceModelStatus](#inferencemodelstatus)_ |  |  |  |
+| `spec` _[InferenceObjectiveSpec](#inferenceobjectivespec)_ |  |  |  |
+| `status` _[InferenceObjectiveStatus](#inferenceobjectivestatus)_ |  |  |  |
 
 
 
 
 
 
-#### InferenceModelSpec
+#### InferenceObjectiveSpec
 
 
 
-InferenceModelSpec represents the desired state of a specific model use case. This resource is
+InferenceObjectiveSpec represents the desired state of a specific model use case. This resource is
 managed by the "Inference Workload Owner" persona.
 
 The Inference Workload Owner persona is someone that trains, verifies, and
@@ -206,41 +121,39 @@ leverages a large language model from a model frontend, drives the lifecycle
 and rollout of new versions of those models, and defines the specific
 performance and latency goals for the model. These workloads are
 expected to operate within an InferencePool sharing compute capacity with other
-InferenceModels, defined by the Inference Platform Admin.
+InferenceObjectives, defined by the Inference Platform Admin.
 
-InferenceModel's modelName (not the ObjectMeta name) is unique for a given InferencePool,
+InferenceObjective's modelName (not the ObjectMeta name) is unique for a given InferencePool,
 if the name is reused, an error will be shown on the status of a
-InferenceModel that attempted to reuse. The oldest InferenceModel, based on
+InferenceObjective that attempted to reuse. The oldest InferenceObjective, based on
 creation timestamp, will be selected to remain valid. In the event of a race
 condition, one will be selected at random.
 
 
 
 _Appears in:_
-- [InferenceModel](#inferencemodel)
+- [InferenceObjective](#inferenceobjective)
 
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
-| `modelName` _string_ | ModelName is the name of the model as it will be set in the "model" parameter for an incoming request.<br />ModelNames must be unique for a referencing InferencePool<br />(names can be reused for a different pool in the same cluster).<br />The modelName with the oldest creation timestamp is retained, and the incoming<br />InferenceModel's Ready status is set to false with a corresponding reason.<br />In the rare case of a race condition, one Model will be selected randomly to be considered valid, and the other rejected.<br />Names can be reserved without an underlying model configured in the pool.<br />This can be done by specifying a target model and setting the weight to zero,<br />an error will be returned specifying that no valid target model is found. |  | MaxLength: 256 <br />Required: \{\} <br /> |
-| `criticality` _[Criticality](#criticality)_ | Criticality defines how important it is to serve the model compared to other models referencing the same pool.<br />Criticality impacts how traffic is handled in resource constrained situations. It handles this by<br />queuing or rejecting requests of lower criticality. InferenceModels of an equivalent Criticality will<br />fairly share resources over throughput of tokens. In the future, the metric used to calculate fairness,<br />and the proportionality of fairness will be configurable.<br />Default values for this field will not be set, to allow for future additions of new field that may 'one of' with this field.<br />Any implementations that may consume this field may treat an unset value as the 'Standard' range. |  | Enum: [Critical Standard Sheddable] <br /> |
-| `targetModels` _[TargetModel](#targetmodel) array_ | TargetModels allow multiple versions of a model for traffic splitting.<br />If not specified, the target model name is defaulted to the modelName parameter.<br />modelName is often in reference to a LoRA adapter. |  | MaxItems: 10 <br /> |
+| `priority` _integer_ | Priority defines how important it is to serve the request compared to other requests in the same pool.<br />Priority is an integer value that defines the priority of the request.<br />The higher the value, the more critical the request is; negative values _are_ allowed.<br />No default value is set for this field, allowing for future additions of new fields that may 'one of' with this field.<br />However, implementations that consume this field (such as the Endpoint Picker) will treat an unset value as '0'.<br />Priority is used in flow control, primarily in the event of resource scarcity(requests need to be queued).<br />All requests will be queued, and flow control will _always_ allow requests of higher priority to be served first.<br />Fairness is only enforced and tracked between requests of the same priority.<br />Example: requests with Priority 10 will always be served before<br />requests with Priority of 0 (the value used if Priority is unset or no InfereneceObjective is specified).<br />Similarly requests with a Priority of -10 will always be served after requests with Priority of 0. |  |  |
 | `poolRef` _[PoolObjectReference](#poolobjectreference)_ | PoolRef is a reference to the inference pool, the pool must exist in the same namespace. |  | Required: \{\} <br /> |
 
 
-#### InferenceModelStatus
+#### InferenceObjectiveStatus
 
 
 
-InferenceModelStatus defines the observed state of InferenceModel
+InferenceObjectiveStatus defines the observed state of InferenceObjective
 
 
 
 _Appears in:_
-- [InferenceModel](#inferencemodel)
+- [InferenceObjective](#inferenceobjective)
 
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
-| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | Conditions track the state of the InferenceModel.<br />Known condition types are:<br />* "Accepted" | [map[lastTransitionTime:1970-01-01T00:00:00Z message:Waiting for controller reason:Pending status:Unknown type:Ready]] | MaxItems: 8 <br /> |
+| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | Conditions track the state of the InferenceObjective.<br />Known condition types are:<br />* "Accepted" | [map[lastTransitionTime:1970-01-01T00:00:00Z message:Waiting for controller reason:Pending status:Unknown type:Ready]] | MaxItems: 8 <br /> |
 
 
 #### InferencePool
@@ -253,7 +166,6 @@ InferencePool is the Schema for the InferencePools API.
 
 
 
-
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
 | `apiVersion` _string_ | `inference.networking.x-k8s.io/v1alpha2` | | |
@@ -282,7 +194,7 @@ _Appears in:_
 | --- | --- | --- | --- |
 | `selector` _object (keys:[LabelKey](#labelkey), values:[LabelValue](#labelvalue))_ | Selector defines a map of labels to watch model server Pods<br />that should be included in the InferencePool.<br />In some cases, implementations may translate this field to a Service selector, so this matches the simple<br />map used for Service selectors instead of the full Kubernetes LabelSelector type.<br />If specified, it will be applied to match the model server pods in the same namespace as the InferencePool.<br />Cross namesoace selector is not supported. |  | Required: \{\} <br /> |
 | `targetPortNumber` _integer_ | TargetPortNumber defines the port number to access the selected model server Pods.<br />The number must be in the range 1 to 65535. |  | Maximum: 65535 <br />Minimum: 1 <br />Required: \{\} <br /> |
-| `extensionRef` _[Extension](#extension)_ | Extension configures an endpoint picker as an extension service. |  | Required: \{\} <br /> |
+| `extensionRef` _[Extension](#extension)_ | Extension configures an endpoint picker as an extension service. |  |  |
 
 
 #### InferencePoolStatus
@@ -323,7 +235,6 @@ _Validation:_
 
 _Appears in:_
 - [Extension](#extension)
-- [ExtensionReference](#extensionreference)
 - [ParentGatewayReference](#parentgatewayreference)
 - [PoolObjectReference](#poolobjectreference)
 
@@ -432,7 +343,6 @@ _Validation:_
 
 _Appears in:_
 - [Extension](#extension)
-- [ExtensionReference](#extensionreference)
 - [ParentGatewayReference](#parentgatewayreference)
 - [PoolObjectReference](#poolobjectreference)
 
@@ -468,11 +378,11 @@ referrer.
 
 
 _Appears in:_
-- [InferenceModelSpec](#inferencemodelspec)
+- [InferenceObjectiveSpec](#inferenceobjectivespec)
 
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
-| `group` _[Group](#group)_ | Group is the group of the referent. | inference.networking.x-k8s.io | MaxLength: 253 <br />Pattern: `^$\|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` <br /> |
+| `group` _[Group](#group)_ | Group is the group of the referent. | inference.networking.k8s.io | MaxLength: 253 <br />Pattern: `^$\|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` <br /> |
 | `kind` _[Kind](#kind)_ | Kind is kind of the referent. For example "InferencePool". | InferencePool | MaxLength: 63 <br />MinLength: 1 <br />Pattern: `^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$` <br /> |
 | `name` _[ObjectName](#objectname)_ | Name is the name of the referent. |  | MaxLength: 253 <br />MinLength: 1 <br />Required: \{\} <br /> |
 
@@ -506,30 +416,6 @@ _Validation:_
 
 _Appears in:_
 - [Extension](#extension)
-- [ExtensionReference](#extensionreference)
-
-
-
-#### TargetModel
-
 
 
-TargetModel represents a deployed model or a LoRA adapter. The
-Name field is expected to match the name of the LoRA adapter
-(or base model) as it is registered within the model server. Inference
-Gateway assumes that the model exists on the model server and it's the
-responsibility of the user to validate a correct match. Should a model fail
-to exist at request time, the error is processed by the Inference Gateway
-and emitted on the appropriate InferenceModel object.
-
-
-
-_Appears in:_
-- [InferenceModelSpec](#inferencemodelspec)
-
-| Field | Description | Default | Validation |
-| --- | --- | --- | --- |
-| `name` _string_ | Name is the name of the adapter or base model, as expected by the ModelServer. |  | MaxLength: 253 <br />Required: \{\} <br /> |
-| `weight` _integer_ | Weight is used to determine the proportion of traffic that should be<br />sent to this model when multiple target models are specified.<br />Weight defines the proportion of requests forwarded to the specified<br />model. This is computed as weight/(sum of all weights in this<br />TargetModels list). For non-zero values, there may be some epsilon from<br />the exact proportion defined here depending on the precision an<br />implementation supports. Weight is not a percentage and the sum of<br />weights does not need to equal 100.<br />If a weight is set for any targetModel, it must be set for all targetModels.<br />Conversely weights are optional, so long as ALL targetModels do not specify a weight. |  | Maximum: 1e+06 <br />Minimum: 1 <br /> |
-
 



Test patch:
diff --git a/apix/v1alpha2/inferencepool_conversion_test.go b/apix/v1alpha2/inferencepool_conversion_test.go
new file mode 100644
index 0000000..5a828dc
--- /dev/null
+++ b/apix/v1alpha2/inferencepool_conversion_test.go
@@ -0,0 +1,390 @@
+/*
+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.
+*/
+
+package v1alpha2
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
+)
+
+var (
+	group       = Group("my-group")
+	kind        = Kind("MyKind")
+	failureMode = ExtensionFailureMode("Deny")
+	portNumber  = PortNumber(9000)
+	timestamp   = metav1.Unix(0, 0)
+
+	v1Group       = v1.Group("my-group")
+	v1Kind        = v1.Kind("MyKind")
+	v1FailureMode = v1.EndpointPickerFailureMode("Deny")
+	v1PortNumber  = v1.PortNumber(9000)
+)
+
+func TestInferencePoolConvertTo(t *testing.T) {
+	tests := []struct {
+		name    string
+		src     *InferencePool
+		want    *v1.InferencePool
+		wantErr bool
+	}{
+		{
+			name: "full conversion from v1alpha2 to v1 including status",
+			src: &InferencePool{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "InferencePool",
+					APIVersion: GroupVersion.String(),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-pool",
+					Namespace: "test-ns",
+				},
+				Spec: InferencePoolSpec{
+					Selector: map[LabelKey]LabelValue{
+						"app": "my-model-server",
+					},
+					TargetPortNumber: 8080,
+					ExtensionRef: Extension{
+						Group:       &group,
+						Kind:        &kind,
+						Name:        "my-epp-service",
+						PortNumber:  &portNumber,
+						FailureMode: &failureMode,
+					},
+				},
+				Status: InferencePoolStatus{
+					Parents: []PoolStatus{
+						{
+							GatewayRef: ParentGatewayReference{Name: "my-gateway"},
+							Conditions: []metav1.Condition{
+								{
+									Type:               string(InferencePoolConditionAccepted),
+									Status:             metav1.ConditionTrue,
+									Reason:             string(InferencePoolReasonAccepted),
+									LastTransitionTime: timestamp,
+								},
+							},
+						},
+					},
+				},
+			},
+			want: &v1.InferencePool{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "InferencePool",
+					APIVersion: v1.GroupVersion.String(),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-pool",
+					Namespace: "test-ns",
+				},
+				Spec: v1.InferencePoolSpec{
+					Selector: v1.LabelSelector{
+						MatchLabels: map[v1.LabelKey]v1.LabelValue{
+							"app": "my-model-server",
+						},
+					},
+					TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8080))}},
+					EndpointPickerRef: v1.EndpointPickerRef{
+						Group:       &v1Group,
+						Kind:        &v1Kind,
+						Name:        "my-epp-service",
+						PortNumber:  &v1PortNumber,
+						FailureMode: &v1FailureMode,
+					},
+				},
+				Status: v1.InferencePoolStatus{
+					Parents: []v1.ParentStatus{
+						{
+							ParentRef: v1.ParentReference{Name: "my-gateway"},
+							Conditions: []metav1.Condition{
+								{
+									Type:               string(v1.InferencePoolConditionAccepted),
+									Status:             metav1.ConditionTrue,
+									Reason:             string(v1.InferencePoolReasonAccepted),
+									LastTransitionTime: timestamp,
+								},
+							},
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "conversion from v1alpha2 to v1 with empty extensionRef",
+			src: &InferencePool{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "InferencePool",
+					APIVersion: GroupVersion.String(),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-pool",
+					Namespace: "test-ns",
+				},
+				Spec: InferencePoolSpec{
+					Selector: map[LabelKey]LabelValue{
+						"app": "my-model-server",
+					},
+					TargetPortNumber: 8080,
+				},
+				Status: InferencePoolStatus{
+					Parents: []PoolStatus{
+						{
+							GatewayRef: ParentGatewayReference{Name: "my-gateway"},
+							Conditions: []metav1.Condition{
+								{
+									Type:               string(InferencePoolConditionAccepted),
+									Status:             metav1.ConditionTrue,
+									Reason:             string(InferencePoolReasonAccepted),
+									LastTransitionTime: timestamp,
+								},
+							},
+						},
+					},
+				},
+			},
+			want: &v1.InferencePool{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "InferencePool",
+					APIVersion: v1.GroupVersion.String(),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-pool",
+					Namespace: "test-ns",
+				},
+				Spec: v1.InferencePoolSpec{
+					Selector: v1.LabelSelector{
+						MatchLabels: map[v1.LabelKey]v1.LabelValue{
+							"app": "my-model-server",
+						},
+					},
+					TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8080))}},
+				},
+				Status: v1.InferencePoolStatus{
+					Parents: []v1.ParentStatus{
+						{
+							ParentRef: v1.ParentReference{Name: "my-gateway"},
+							Conditions: []metav1.Condition{
+								{
+									Type:               string(v1.InferencePoolConditionAccepted),
+									Status:             metav1.ConditionTrue,
+									Reason:             string(v1.InferencePoolReasonAccepted),
+									LastTransitionTime: timestamp,
+								},
+							},
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := &v1.InferencePool{}
+			err := tt.src.ConvertTo(got)
+			if (err != nil) != tt.wantErr {
+				t.Fatalf("ConvertTo() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			if diff := cmp.Diff(tt.want, got); diff != "" {
+				t.Errorf("ConvertTo() mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestInferencePoolConvertFrom(t *testing.T) {
+	tests := []struct {
+		name    string
+		src     *v1.InferencePool
+		want    *InferencePool
+		wantErr bool
+	}{
+		{
+			name: "full conversion from v1 to v1alpha2 including status",
+			src: &v1.InferencePool{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "InferencePool",
+					APIVersion: v1.GroupVersion.String(),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-pool",
+					Namespace: "test-ns",
+				},
+				Spec: v1.InferencePoolSpec{
+					Selector: v1.LabelSelector{
+						MatchLabels: map[v1.LabelKey]v1.LabelValue{
+							"app": "my-model-server",
+						},
+					},
+					TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8080))}},
+					EndpointPickerRef: v1.EndpointPickerRef{
+						Group:       &v1Group,
+						Kind:        &v1Kind,
+						Name:        "my-epp-service",
+						PortNumber:  &v1PortNumber,
+						FailureMode: &v1FailureMode,
+					},
+				},
+				Status: v1.InferencePoolStatus{
+					Parents: []v1.ParentStatus{
+						{
+							ParentRef: v1.ParentReference{Name: "my-gateway"},
+							Conditions: []metav1.Condition{
+								{
+									Type:               string(v1.InferencePoolConditionAccepted),
+									Status:             metav1.ConditionTrue,
+									Reason:             string(v1.InferencePoolReasonAccepted),
+									LastTransitionTime: timestamp,
+								},
+							},
+						},
+					},
+				},
+			},
+			want: &InferencePool{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "InferencePool",
+					APIVersion: GroupVersion.String(),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-pool",
+					Namespace: "test-ns",
+				},
+				Spec: InferencePoolSpec{
+					Selector: map[LabelKey]LabelValue{
+						"app": "my-model-server",
+					},
+					TargetPortNumber: 8080,
+					ExtensionRef: Extension{
+						Group:       &group,
+						Kind:        &kind,
+						Name:        "my-epp-service",
+						PortNumber:  &portNumber,
+						FailureMode: &failureMode,
+					},
+				},
+				Status: InferencePoolStatus{
+					Parents: []PoolStatus{
+						{
+							GatewayRef: ParentGatewayReference{Name: "my-gateway"},
+							Conditions: []metav1.Condition{
+								{
+									Type:               string(InferencePoolConditionAccepted),
+									Status:             metav1.ConditionTrue,
+									Reason:             string(InferencePoolReasonAccepted),
+									LastTransitionTime: timestamp,
+								},
+							},
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "conversion from v1 to v1alpha2 with empty extensionRef",
+			src: &v1.InferencePool{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "InferencePool",
+					APIVersion: v1.GroupVersion.String(),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-pool",
+					Namespace: "test-ns",
+				},
+				Spec: v1.InferencePoolSpec{
+					Selector: v1.LabelSelector{
+						MatchLabels: map[v1.LabelKey]v1.LabelValue{
+							"app": "my-model-server",
+						},
+					},
+					TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8080))}},
+				},
+				Status: v1.InferencePoolStatus{
+					Parents: []v1.ParentStatus{
+						{
+							ParentRef: v1.ParentReference{Name: "my-gateway"},
+							Conditions: []metav1.Condition{
+								{
+									Type:               string(v1.InferencePoolConditionAccepted),
+									Status:             metav1.ConditionTrue,
+									Reason:             string(v1.InferencePoolReasonAccepted),
+									LastTransitionTime: timestamp,
+								},
+							},
+						},
+					},
+				},
+			},
+			want: &InferencePool{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "InferencePool",
+					APIVersion: GroupVersion.String(),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-pool",
+					Namespace: "test-ns",
+				},
+				Spec: InferencePoolSpec{
+					Selector: map[LabelKey]LabelValue{
+						"app": "my-model-server",
+					},
+					TargetPortNumber: 8080,
+				},
+				Status: InferencePoolStatus{
+					Parents: []PoolStatus{
+						{
+							GatewayRef: ParentGatewayReference{Name: "my-gateway"},
+							Conditions: []metav1.Condition{
+								{
+									Type:               string(InferencePoolConditionAccepted),
+									Status:             metav1.ConditionTrue,
+									Reason:             string(InferencePoolReasonAccepted),
+									LastTransitionTime: timestamp,
+								},
+							},
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name:    "nil source",
+			src:     nil,
+			want:    &InferencePool{},
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := &InferencePool{}
+			err := got.ConvertFrom(tt.src)
+			if (err != nil) != tt.wantErr {
+				t.Fatalf("ConvertFrom() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			if diff := cmp.Diff(tt.want, got); diff != "" {
+				t.Errorf("ConvertFrom() mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
diff --git a/conformance/tests/epp_unavailable_fail_open.go b/conformance/tests/epp_unavailable_fail_open.go
index 36e0fa2..8af5972 100644
--- a/conformance/tests/epp_unavailable_fail_open.go
+++ b/conformance/tests/epp_unavailable_fail_open.go
@@ -29,7 +29,8 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/conformance/utils/config"
 	k8sutils "sigs.k8s.io/gateway-api-inference-extension/conformance/utils/kubernetes"
 	trafficutils "sigs.k8s.io/gateway-api-inference-extension/conformance/utils/traffic"
-	testfilter "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/test/filter"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metadata"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/test"
 )
 
 func init() {
@@ -75,9 +76,12 @@ var EppUnAvailableFailOpen = suite.ConformanceTest{
 				s.TimeoutConfig,
 				gwAddr,
 				trafficutils.Request{
-					Host:      hostname,
-					Path:      path,
-					Headers:   map[string]string{testfilter.HeaderTestEppEndPointSelectionKey: targetPodIP},
+					Host: hostname,
+					Path: path,
+					Headers: map[string]string{
+						test.HeaderTestEppEndPointSelectionKey: targetPodIP,
+						metadata.ObjectiveKey:                  resources.InferenceObjName,
+					},
 					Method:    http.MethodPost,
 					Body:      requestBody,
 					Backend:   pods[0].Name, // Make sure the request is from the targetPod when the EPP is alive.
@@ -100,9 +104,12 @@ var EppUnAvailableFailOpen = suite.ConformanceTest{
 				s.TimeoutConfig,
 				gwAddr,
 				trafficutils.Request{
-					Host:      hostname,
-					Path:      path,
-					Headers:   map[string]string{testfilter.HeaderTestEppEndPointSelectionKey: targetPodIP},
+					Host: hostname,
+					Path: path,
+					Headers: map[string]string{
+						test.HeaderTestEppEndPointSelectionKey: targetPodIP,
+						metadata.ObjectiveKey:                  resources.InferenceObjName,
+					},
 					Method:    http.MethodPost,
 					Body:      requestBody,
 					Backend:   appPodBackendPrefix, // Only checks the prefix since the EPP is not alive and the response can return from any Pod.
diff --git a/conformance/tests/epp_unavailable_fail_open.yaml b/conformance/tests/epp_unavailable_fail_open.yaml
index 225dbba..93f3e86 100644
--- a/conformance/tests/epp_unavailable_fail_open.yaml
+++ b/conformance/tests/epp_unavailable_fail_open.yaml
@@ -6,7 +6,7 @@ metadata:
   name: conformance-fake-model-server
   namespace: gateway-conformance-app-backend
 spec:
-  criticality: Critical # Mark it as critical to bypass the saturation check since the model server is fake and don't have such metrics. 
+  criticality: 2 # Mark criticality high enough to bypass the saturation check since the model server is fake and don't have such metrics. 
   poolRef:
     name: secondary-inference-pool
 ---
diff --git a/conformance/tests/gateway_following_epp_routing.go b/conformance/tests/gateway_following_epp_routing.go
index 95b894c..30a6dde 100644
--- a/conformance/tests/gateway_following_epp_routing.go
+++ b/conformance/tests/gateway_following_epp_routing.go
@@ -33,7 +33,8 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/conformance/resources"
 	k8sutils "sigs.k8s.io/gateway-api-inference-extension/conformance/utils/kubernetes"
 	"sigs.k8s.io/gateway-api-inference-extension/conformance/utils/traffic"
-	testfilter "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/test/filter"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metadata"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/test"
 )
 
 func init() {
@@ -92,9 +93,12 @@ var GatewayFollowingEPPRouting = suite.ConformanceTest{
 				s.TimeoutConfig,
 				gwAddr,
 				traffic.Request{
-					Host:      hostname,
-					Path:      path,
-					Headers:   map[string]string{testfilter.HeaderTestEppEndPointSelectionKey: podIPs[i]},
+					Host: hostname,
+					Path: path,
+					Headers: map[string]string{
+						test.HeaderTestEppEndPointSelectionKey: podIPs[i],
+						metadata.ObjectiveKey:                  resources.InferenceObjName,
+					},
 					Method:    http.MethodPost,
 					Body:      requestBody,
 					Backend:   podNames[i],
@@ -128,9 +132,12 @@ var GatewayFollowingEPPRouting = suite.ConformanceTest{
 		for _, tc := range testCases {
 			t.Run(tc.name, func(t *testing.T) {
 				eppHeaderValue := strings.Join(tc.podIPsToBeReturnedByEPP, ",")
-				headers := map[string]string{testfilter.HeaderTestEppEndPointSelectionKey: eppHeaderValue}
+				headers := map[string]string{
+					test.HeaderTestEppEndPointSelectionKey: eppHeaderValue,
+					metadata.ObjectiveKey:                  resources.InferenceObjName,
+				}
 
-				t.Logf("Sending request to %s with EPP header '%s: %s'", gwAddr, testfilter.HeaderTestEppEndPointSelectionKey, eppHeaderValue)
+				t.Logf("Sending request to %s with EPP header '%s: %s'", gwAddr, test.HeaderTestEppEndPointSelectionKey, eppHeaderValue)
 				t.Logf("Expecting traffic to be routed to pod: %v", tc.expectAllRequestsRoutedWithinPodNames)
 
 				assertTrafficOnlyReachesToExpectedPods(t, s, gwAddr, gwhttp.ExpectedResponse{
diff --git a/conformance/tests/gateway_following_epp_routing.yaml b/conformance/tests/gateway_following_epp_routing.yaml
index 9e3a43e..37b00e7 100644
--- a/conformance/tests/gateway_following_epp_routing.yaml
+++ b/conformance/tests/gateway_following_epp_routing.yaml
@@ -6,7 +6,7 @@ metadata:
   name: conformance-fake-model-server
   namespace: gateway-conformance-app-backend
 spec:
-  criticality: Critical # Mark it as critical to bypass the saturation check since the model server is fake and don't have such metrics. 
+  criticality: 2 # Mark criticality high enough to bypass the saturation check since the model server is fake and don't have such metrics.
   poolRef:
     name: primary-inference-pool
 ---
diff --git a/conformance/tests/inferencepool_invalid_epp_service.yaml b/conformance/tests/inferencepool_invalid_epp_service.yaml
index 740e23a..cdc2048 100644
--- a/conformance/tests/inferencepool_invalid_epp_service.yaml
+++ b/conformance/tests/inferencepool_invalid_epp_service.yaml
@@ -5,9 +5,11 @@ metadata:
   namespace: gateway-conformance-app-backend
 spec:
   selector:
-    app: primary-inference-model-server
-  targetPortNumber: 3000
-  extensionRef:
+    matchLabels:
+      app: primary-inference-model-server
+  targetPorts:
+  - number: 3000
+  endpointPickerRef:
     name: non-existent-epp-svc
 ---
 apiVersion: gateway.networking.k8s.io/v1
diff --git a/pkg/epp/backend/metrics/pod_metrics_test.go b/pkg/epp/backend/metrics/pod_metrics_test.go
index 1c4a339..9a0e1a6 100644
--- a/pkg/epp/backend/metrics/pod_metrics_test.go
+++ b/pkg/epp/backend/metrics/pod_metrics_test.go
@@ -62,10 +62,10 @@ var (
 func TestMetricsRefresh(t *testing.T) {
 	ctx := context.Background()
 	pmc := &FakePodMetricsClient{}
-	pmf := NewPodMetricsFactory(pmc, time.Millisecond, time.Second*2)
+	pmf := NewPodMetricsFactory(pmc, time.Millisecond)
 
 	// The refresher is initialized with empty metrics.
-	pm := pmf.NewPodMetrics(ctx, pod1, &fakeDataStore{})
+	pm := pmf.NewEndpoint(ctx, pod1, &fakeDataStore{})
 
 	namespacedName := types.NamespacedName{Name: pod1.Name, Namespace: pod1.Namespace}
 	// Use SetRes to simulate an update of metrics from the pod.
@@ -78,7 +78,7 @@ func TestMetricsRefresh(t *testing.T) {
 
 	// Stop the loop, and simulate metric update again, this time the PodMetrics won't get the
 	// new update.
-	pm.StopRefreshLoop()
+	pmf.ReleaseEndpoint(pm)
 	time.Sleep(pmf.refreshMetricsInterval * 2 /* small buffer for robustness */)
 	pmc.SetRes(map[types.NamespacedName]*MetricsState{namespacedName: updated})
 	// Still expect the same condition (no metrics update).
@@ -88,7 +88,7 @@ func TestMetricsRefresh(t *testing.T) {
 type fakeDataStore struct{}
 
 func (f *fakeDataStore) PoolGet() (*v1.InferencePool, error) {
-	return &v1.InferencePool{Spec: v1.InferencePoolSpec{TargetPortNumber: 8000}}, nil
+	return &v1.InferencePool{Spec: v1.InferencePoolSpec{TargetPorts: []v1.Port{{Number: 8000}}}}, nil
 }
 
 func (f *fakeDataStore) PodList(func(PodMetrics) bool) []PodMetrics {
diff --git a/pkg/epp/config/loader/configloader_test.go b/pkg/epp/config/loader/configloader_test.go
index 0701f96..15a97f2 100644
--- a/pkg/epp/config/loader/configloader_test.go
+++ b/pkg/epp/config/loader/configloader_test.go
@@ -663,7 +663,6 @@ func (f *test1) Filter(_ context.Context, _ *types.CycleState, _ *types.LLMReque
 
 // compile-time type validation
 var _ framework.Scorer = &test2{}
-var _ framework.PostCycle = &test2{}
 
 type test2 struct {
 	typedName plugins.TypedName
@@ -683,8 +682,6 @@ func (m *test2) Score(_ context.Context, _ *types.CycleState, _ *types.LLMReques
 	return map[types.Pod]float64{}
 }
 
-func (m *test2) PostCycle(_ context.Context, _ *types.CycleState, _ *types.ProfileRunResult) {}
-
 // compile-time type validation
 var _ framework.Picker = &testPicker{}
 
diff --git a/pkg/epp/controller/inferenceobjective_reconciler_test.go b/pkg/epp/controller/inferenceobjective_reconciler_test.go
index 6ab8cea..de43d6e 100644
--- a/pkg/epp/controller/inferenceobjective_reconciler_test.go
+++ b/pkg/epp/controller/inferenceobjective_reconciler_test.go
@@ -43,19 +43,19 @@ var (
 	pool          = utiltest.MakeInferencePool("test-pool1").Namespace("ns1").ObjRef()
 	infObjective1 = utiltest.MakeInferenceObjective("model1").
 			Namespace(pool.Namespace).
-			Criticality(v1alpha2.Standard).
+			Priority(1).
 			CreationTimestamp(metav1.Unix(1000, 0)).
 			PoolName(pool.Name).
 			PoolGroup("inference.networking.k8s.io").ObjRef()
 	infObjective1Pool2 = utiltest.MakeInferenceObjective(infObjective1.Name).
 				Namespace(infObjective1.Namespace).
-				Criticality(*infObjective1.Spec.Criticality).
+				Priority(*infObjective1.Spec.Priority).
 				CreationTimestamp(metav1.Unix(1001, 0)).
 				PoolName("test-pool2").
 				PoolGroup("inference.networking.k8s.io").ObjRef()
 	infObjective1Critical = utiltest.MakeInferenceObjective(infObjective1.Name).
 				Namespace(infObjective1.Namespace).
-				Criticality(v1alpha2.Critical).
+				Priority(2).
 				CreationTimestamp(metav1.Unix(1003, 0)).
 				PoolName(pool.Name).
 				PoolGroup("inference.networking.k8s.io").ObjRef()
@@ -67,7 +67,7 @@ var (
 				PoolGroup("inference.networking.k8s.io").ObjRef()
 	infObjective1DiffGroup = utiltest.MakeInferenceObjective(infObjective1.Name).
 				Namespace(pool.Namespace).
-				Criticality(v1alpha2.Standard).
+				Priority(1).
 				CreationTimestamp(metav1.Unix(1005, 0)).
 				PoolName(pool.Name).
 				PoolGroup("inference.networking.x-k8s.io").ObjRef()
@@ -112,7 +112,7 @@ func TestInferenceObjectiveReconciler(t *testing.T) {
 			wantObjectives:     []*v1alpha2.InferenceObjective{},
 		},
 		{
-			name:               "Objective changed criticality",
+			name:               "Objective changed priority",
 			objectivessInStore: []*v1alpha2.InferenceObjective{infObjective1},
 			objective:          infObjective1Critical,
 			wantObjectives:     []*v1alpha2.InferenceObjective{infObjective1Critical},
@@ -159,7 +159,7 @@ func TestInferenceObjectiveReconciler(t *testing.T) {
 				WithScheme(scheme).
 				WithObjects(initObjs...).
 				Build()
-			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second, time.Second*2)
+			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
 			ds := datastore.NewDatastore(t.Context(), pmf)
 			for _, m := range test.objectivessInStore {
 				ds.ObjectiveSet(m)
diff --git a/pkg/epp/controller/inferencepool_reconciler_test.go b/pkg/epp/controller/inferencepool_reconciler_test.go
index a29d531..7f69385 100644
--- a/pkg/epp/controller/inferencepool_reconciler_test.go
+++ b/pkg/epp/controller/inferencepool_reconciler_test.go
@@ -80,9 +80,10 @@ func TestInferencePoolReconciler(t *testing.T) {
 	pool1 := utiltest.MakeInferencePool("pool1").
 		Namespace("pool1-ns").
 		Selector(selector_v1).
-		TargetPortNumber(8080).ObjRef()
+		TargetPorts(8080).
+		EndpointPickerRef("epp-service").ObjRef()
 	pool1.SetGroupVersionKind(gvk)
-	pool2 := utiltest.MakeInferencePool("pool2").Namespace("pool2-ns").ObjRef()
+	pool2 := utiltest.MakeInferencePool("pool2").Namespace("pool2-ns").EndpointPickerRef("epp-service").ObjRef()
 	pool2.SetGroupVersionKind(gvk)
 
 	// Set up the scheme.
@@ -111,7 +112,7 @@ func TestInferencePoolReconciler(t *testing.T) {
 	req := ctrl.Request{NamespacedName: namespacedName}
 	ctx := context.Background()
 
-	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second, time.Second*2)
+	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
 	datastore := datastore.NewDatastore(ctx, pmf)
 	inferencePoolReconciler := &InferencePoolReconciler{Reader: fakeClient, Datastore: datastore, PoolGKNN: gknn}
 
@@ -127,7 +128,9 @@ func TestInferencePoolReconciler(t *testing.T) {
 	if err := fakeClient.Get(ctx, req.NamespacedName, newPool1); err != nil {
 		t.Errorf("Unexpected pool get error: %v", err)
 	}
-	newPool1.Spec.Selector = map[v1.LabelKey]v1.LabelValue{"app": "vllm_v2"}
+	newPool1.Spec.Selector = v1.LabelSelector{
+		MatchLabels: map[v1.LabelKey]v1.LabelValue{"app": "vllm_v2"},
+	}
 	if err := fakeClient.Update(ctx, newPool1, &client.UpdateOptions{}); err != nil {
 		t.Errorf("Unexpected pool update error: %v", err)
 	}
@@ -143,7 +146,7 @@ func TestInferencePoolReconciler(t *testing.T) {
 	if err := fakeClient.Get(ctx, req.NamespacedName, newPool1); err != nil {
 		t.Errorf("Unexpected pool get error: %v", err)
 	}
-	newPool1.Spec.TargetPortNumber = 9090
+	newPool1.Spec.TargetPorts = []v1.Port{{Number: 9090}}
 	if err := fakeClient.Update(ctx, newPool1, &client.UpdateOptions{}); err != nil {
 		t.Errorf("Unexpected pool update error: %v", err)
 	}
@@ -186,7 +189,7 @@ func diffStore(datastore datastore.Datastore, params diffStoreParams) string {
 		params.wantPods = []string{}
 	}
 	gotPods := []string{}
-	for _, pm := range datastore.PodList(backendmetrics.AllPodPredicate) {
+	for _, pm := range datastore.PodList(backendmetrics.AllPodsPredicate) {
 		gotPods = append(gotPods, pm.GetPod().NamespacedName.Name)
 	}
 	if diff := cmp.Diff(params.wantPods, gotPods, cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" {
@@ -216,11 +219,15 @@ func TestXInferencePoolReconciler(t *testing.T) {
 		Version: v1alpha2.GroupVersion.Version,
 		Kind:    "InferencePool",
 	}
-	pool1 := utiltest.MakeXInferencePool("pool1").
+	pool1 := utiltest.MakeAlphaInferencePool("pool1").
 		Namespace("pool1-ns").
 		Selector(selector_v1).
+		ExtensionRef("epp-service").
+		TargetPortNumber(8080).ObjRef()
+	pool2 := utiltest.MakeAlphaInferencePool("pool2").
+		Namespace("pool2-ns").
+		ExtensionRef("epp-service").
 		TargetPortNumber(8080).ObjRef()
-	pool2 := utiltest.MakeXInferencePool("pool2").Namespace("pool2-ns").ObjRef()
 	pool1.SetGroupVersionKind(gvk)
 	pool2.SetGroupVersionKind(gvk)
 
@@ -250,7 +257,7 @@ func TestXInferencePoolReconciler(t *testing.T) {
 	req := ctrl.Request{NamespacedName: namespacedName}
 	ctx := context.Background()
 
-	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second, time.Second*2)
+	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
 	datastore := datastore.NewDatastore(ctx, pmf)
 	inferencePoolReconciler := &InferencePoolReconciler{Reader: fakeClient, Datastore: datastore, PoolGKNN: gknn}
 
@@ -319,13 +326,12 @@ func xDiffStore(t *testing.T, datastore datastore.Datastore, params xDiffStorePa
 	if gotPool == nil && params.wantPool == nil {
 		return ""
 	}
-	uns, err := common.ToUnstructured(gotPool)
-	if err != nil {
-		t.Fatalf("failed to convert XInferencePool to Unstructured: %v", err)
-	}
-	gotXPool, err := common.ToXInferencePool(uns)
+
+	gotXPool := &v1alpha2.InferencePool{}
+
+	err := gotXPool.ConvertFrom(gotPool)
 	if err != nil {
-		t.Fatalf("failed to convert unstructured to InferencePool: %v", err)
+		t.Fatalf("failed to convert InferencePool to XInferencePool: %v", err)
 	}
 	if diff := cmp.Diff(params.wantPool, gotXPool); diff != "" {
 		return "pool:" + diff
@@ -336,7 +342,7 @@ func xDiffStore(t *testing.T, datastore datastore.Datastore, params xDiffStorePa
 		params.wantPods = []string{}
 	}
 	gotPods := []string{}
-	for _, pm := range datastore.PodList(backendmetrics.AllPodPredicate) {
+	for _, pm := range datastore.PodList(backendmetrics.AllPodsPredicate) {
 		gotPods = append(gotPods, pm.GetPod().NamespacedName.Name)
 	}
 	if diff := cmp.Diff(params.wantPods, gotPods, cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" {
diff --git a/pkg/epp/controller/pod_reconciler_test.go b/pkg/epp/controller/pod_reconciler_test.go
index 0f62021..5ceb3ef 100644
--- a/pkg/epp/controller/pod_reconciler_test.go
+++ b/pkg/epp/controller/pod_reconciler_test.go
@@ -44,7 +44,7 @@ var (
 	basePod3  = &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod3"}, Status: corev1.PodStatus{PodIP: "address-3"}}
 	basePod11 = &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}, Status: corev1.PodStatus{PodIP: "address-11"}}
 	pmc       = &backendmetrics.FakePodMetricsClient{}
-	pmf       = backendmetrics.NewPodMetricsFactory(pmc, time.Second, time.Second*2)
+	pmf       = backendmetrics.NewPodMetricsFactory(pmc, time.Second)
 )
 
 func TestPodReconciler(t *testing.T) {
@@ -61,9 +61,11 @@ func TestPodReconciler(t *testing.T) {
 			existingPods: []*corev1.Pod{basePod1, basePod2},
 			pool: &v1.InferencePool{
 				Spec: v1.InferencePoolSpec{
-					TargetPortNumber: int32(8000),
-					Selector: map[v1.LabelKey]v1.LabelValue{
-						"some-key": "some-val",
+					TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8000))}},
+					Selector: v1.LabelSelector{
+						MatchLabels: map[v1.LabelKey]v1.LabelValue{
+							"some-key": "some-val",
+						},
 					},
 				},
 			},
@@ -77,9 +79,11 @@ func TestPodReconciler(t *testing.T) {
 			existingPods: []*corev1.Pod{basePod1, basePod2},
 			pool: &v1.InferencePool{
 				Spec: v1.InferencePoolSpec{
-					TargetPortNumber: int32(8000),
-					Selector: map[v1.LabelKey]v1.LabelValue{
-						"some-key": "some-val",
+					TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8000))}},
+					Selector: v1.LabelSelector{
+						MatchLabels: map[v1.LabelKey]v1.LabelValue{
+							"some-key": "some-val",
+						},
 					},
 				},
 			},
@@ -93,9 +97,11 @@ func TestPodReconciler(t *testing.T) {
 			existingPods: []*corev1.Pod{basePod1, basePod2},
 			pool: &v1.InferencePool{
 				Spec: v1.InferencePoolSpec{
-					TargetPortNumber: int32(8000),
-					Selector: map[v1.LabelKey]v1.LabelValue{
-						"some-key": "some-val",
+					TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8000))}},
+					Selector: v1.LabelSelector{
+						MatchLabels: map[v1.LabelKey]v1.LabelValue{
+							"some-key": "some-val",
+						},
 					},
 				},
 			},
@@ -110,9 +116,11 @@ func TestPodReconciler(t *testing.T) {
 			existingPods: []*corev1.Pod{basePod1, basePod2},
 			pool: &v1.InferencePool{
 				Spec: v1.InferencePoolSpec{
-					TargetPortNumber: int32(8000),
-					Selector: map[v1.LabelKey]v1.LabelValue{
-						"some-key": "some-val",
+					TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8000))}},
+					Selector: v1.LabelSelector{
+						MatchLabels: map[v1.LabelKey]v1.LabelValue{
+							"some-key": "some-val",
+						},
 					},
 				},
 			},
@@ -124,9 +132,11 @@ func TestPodReconciler(t *testing.T) {
 			existingPods: []*corev1.Pod{basePod1, basePod2},
 			pool: &v1.InferencePool{
 				Spec: v1.InferencePoolSpec{
-					TargetPortNumber: int32(8000),
-					Selector: map[v1.LabelKey]v1.LabelValue{
-						"some-key": "some-val",
+					TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8000))}},
+					Selector: v1.LabelSelector{
+						MatchLabels: map[v1.LabelKey]v1.LabelValue{
+							"some-key": "some-val",
+						},
 					},
 				},
 			},
@@ -139,9 +149,11 @@ func TestPodReconciler(t *testing.T) {
 			existingPods: []*corev1.Pod{basePod1, basePod2},
 			pool: &v1.InferencePool{
 				Spec: v1.InferencePoolSpec{
-					TargetPortNumber: int32(8000),
-					Selector: map[v1.LabelKey]v1.LabelValue{
-						"some-key": "some-val",
+					TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8000))}},
+					Selector: v1.LabelSelector{
+						MatchLabels: map[v1.LabelKey]v1.LabelValue{
+							"some-key": "some-val",
+						},
 					},
 				},
 			},
@@ -155,9 +167,11 @@ func TestPodReconciler(t *testing.T) {
 			existingPods: []*corev1.Pod{basePod1, basePod2},
 			pool: &v1.InferencePool{
 				Spec: v1.InferencePoolSpec{
-					TargetPortNumber: int32(8000),
-					Selector: map[v1.LabelKey]v1.LabelValue{
-						"some-key": "some-val",
+					TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8000))}},
+					Selector: v1.LabelSelector{
+						MatchLabels: map[v1.LabelKey]v1.LabelValue{
+							"some-key": "some-val",
+						},
 					},
 				},
 			},
@@ -198,7 +212,7 @@ func TestPodReconciler(t *testing.T) {
 			}
 
 			var gotPods []*corev1.Pod
-			for _, pm := range store.PodList(backendmetrics.AllPodPredicate) {
+			for _, pm := range store.PodList(backendmetrics.AllPodsPredicate) {
 				pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: pm.GetPod().NamespacedName.Name, Namespace: pm.GetPod().NamespacedName.Namespace}, Status: corev1.PodStatus{PodIP: pm.GetPod().Address}}
 				gotPods = append(gotPods, pod)
 			}
diff --git a/pkg/epp/datalayer/collector_test.go b/pkg/epp/datalayer/collector_test.go
index b5f348d..2d47de3 100644
--- a/pkg/epp/datalayer/collector_test.go
+++ b/pkg/epp/datalayer/collector_test.go
@@ -98,15 +98,16 @@ func TestCollectorCollectsOnTicks(t *testing.T) {
 	c := NewCollector()
 	ticker := mocks.NewTicker()
 	ctx := context.Background()
-	require.NoError(t, c.Start(ctx, ticker, endpoint, []DataSource{source}))
 
+	require.NoError(t, c.Start(ctx, ticker, endpoint, []DataSource{source}))
 	ticker.Tick()
 	ticker.Tick()
-	time.Sleep(20 * time.Millisecond) // let collector process the ticks
 
-	got := atomic.LoadInt64(&source.callCount)
-	want := int64(2)
-	assert.Equal(t, want, got, "call count mismatch")
+	// use Eventually for async processing
+	require.Eventually(t, func() bool {
+		return atomic.LoadInt64(&source.callCount) == 2
+	}, 1*time.Second, 2*time.Millisecond, "expected 2 collections")
+
 	require.NoError(t, c.Stop())
 }
 
diff --git a/pkg/epp/datastore/datastore_test.go b/pkg/epp/datastore/datastore_test.go
index e402fc4..271c31e 100644
--- a/pkg/epp/datastore/datastore_test.go
+++ b/pkg/epp/datastore/datastore_test.go
@@ -82,7 +82,7 @@ func TestPool(t *testing.T) {
 			fakeClient := fake.NewClientBuilder().
 				WithScheme(scheme).
 				Build()
-			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second, time.Second*2)
+			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
 			datastore := NewDatastore(context.Background(), pmf)
 			_ = datastore.PoolSet(context.Background(), fakeClient, tt.inferencePool)
 			gotPool, gotErr := datastore.PoolGet()
@@ -114,7 +114,7 @@ func TestObjective(t *testing.T) {
 	model2ts := testutil.MakeInferenceObjective("model2").ObjRef()
 	// Same model name as model1ts, newer timestamp
 	model1tsCritical := testutil.MakeInferenceObjective("model1").
-		Criticality(v1alpha2.Critical).ObjRef()
+		Priority(2).ObjRef()
 	// Same object name as model2ts, different model name.
 	model2chat := testutil.MakeInferenceObjective(model2ts.Name).ObjRef()
 
@@ -135,7 +135,7 @@ func TestObjective(t *testing.T) {
 			wantOpResult: true,
 		},
 		{
-			name:           "Set model1 with the same modelName, but with diff criticality, should update.",
+			name:           "Set model1 with the same modelName, but with diff priority, should update.",
 			existingModels: []*v1alpha2.InferenceObjective{model1ts},
 			op: func(ds Datastore) bool {
 				ds.ObjectiveSet(model1tsCritical)
@@ -189,7 +189,7 @@ func TestObjective(t *testing.T) {
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second, time.Second*2)
+			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
 			ds := NewDatastore(t.Context(), pmf)
 			for _, m := range test.existingModels {
 				ds.ObjectiveSet(m)
@@ -245,7 +245,7 @@ var (
 	pod2NamespacedName = types.NamespacedName{Name: pod2.Name, Namespace: pod2.Namespace}
 	inferencePool      = &v1.InferencePool{
 		Spec: v1.InferencePoolSpec{
-			TargetPortNumber: 8000,
+			TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8000))}},
 		},
 	}
 )
@@ -256,6 +256,7 @@ func TestMetrics(t *testing.T) {
 		pmc       backendmetrics.PodMetricsClient
 		storePods []*corev1.Pod
 		want      []*backendmetrics.MetricsState
+		predict   func(backendmetrics.PodMetrics) bool
 	}{
 		{
 			name: "Probing metrics success",
@@ -313,15 +314,18 @@ func TestMetrics(t *testing.T) {
 			fakeClient := fake.NewClientBuilder().
 				WithScheme(scheme).
 				Build()
-			pmf := backendmetrics.NewPodMetricsFactory(test.pmc, time.Millisecond, time.Second*2)
+			pmf := backendmetrics.NewPodMetricsFactory(test.pmc, time.Millisecond)
 			ds := NewDatastore(ctx, pmf)
 			_ = ds.PoolSet(ctx, fakeClient, inferencePool)
 			for _, pod := range test.storePods {
 				ds.PodUpdateOrAddIfNotExist(pod)
 			}
 			time.Sleep(1 * time.Second) // Give some time for the metrics to be fetched.
+			if test.predict == nil {
+				test.predict = backendmetrics.AllPodsPredicate
+			}
 			assert.EventuallyWithT(t, func(t *assert.CollectT) {
-				got := ds.PodList(backendmetrics.AllPodPredicate)
+				got := ds.PodList(test.predict)
 				metrics := []*backendmetrics.MetricsState{}
 				for _, one := range got {
 					metrics = append(metrics, one.GetMetrics())
@@ -407,7 +411,7 @@ func TestPods(t *testing.T) {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			ctx := context.Background()
-			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second, time.Second*2)
+			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
 			ds := NewDatastore(t.Context(), pmf)
 			for _, pod := range test.existingPods {
 				ds.PodUpdateOrAddIfNotExist(pod)
@@ -415,7 +419,7 @@ func TestPods(t *testing.T) {
 
 			test.op(ctx, ds)
 			var gotPods []*corev1.Pod
-			for _, pm := range ds.PodList(backendmetrics.AllPodPredicate) {
+			for _, pm := range ds.PodList(backendmetrics.AllPodsPredicate) {
 				pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: pm.GetPod().NamespacedName.Name, Namespace: pm.GetPod().NamespacedName.Namespace}, Status: corev1.PodStatus{PodIP: pm.GetPod().Address}}
 				gotPods = append(gotPods, pod)
 			}
diff --git a/pkg/epp/flowcontrol/controller/internal/filter_test.go b/pkg/epp/flowcontrol/controller/internal/filter_test.go
index f51581e..ceff9e8 100644
--- a/pkg/epp/flowcontrol/controller/internal/filter_test.go
+++ b/pkg/epp/flowcontrol/controller/internal/filter_test.go
@@ -35,22 +35,22 @@ func TestNewSaturationFilter(t *testing.T) {
 	t.Parallel()
 
 	testCases := []struct {
-		name              string
-		isSaturated       bool
-		expectShouldPause bool
-		expectAllowed     map[string]struct{}
+		name                  string
+		isSaturated           bool
+		expectShouldPause     bool
+		expectFilteredBandNil bool
 	}{
 		{
-			name:              "should not pause or filter when system is not saturated",
-			isSaturated:       false,
-			expectShouldPause: false,
-			expectAllowed:     nil, // nil map signals the fast path
+			name:                  "should not pause or filter when system is not saturated",
+			isSaturated:           false,
+			expectShouldPause:     false,
+			expectFilteredBandNil: true, // nil band signals the fast path
 		},
 		{
-			name:              "should pause when system is saturated",
-			isSaturated:       true,
-			expectShouldPause: true,
-			expectAllowed:     nil,
+			name:                  "should pause when system is saturated",
+			isSaturated:           true,
+			expectShouldPause:     true,
+			expectFilteredBandNil: true,
 		},
 	}
 
@@ -66,15 +66,15 @@ func TestNewSaturationFilter(t *testing.T) {
 			mockBand := &frameworkmocks.MockPriorityBandAccessor{}
 
 			// --- ACT ---
-			allowed, shouldPause := filter(context.Background(), mockBand, logr.Discard())
+			filteredBand, shouldPause := filter(context.Background(), mockBand, logr.Discard())
 
 			// --- ASSERT ---
 			assert.Equal(t, tc.expectShouldPause, shouldPause, "The filter's pause signal should match the expected value")
 
-			if tc.expectAllowed == nil {
-				assert.Nil(t, allowed, "Expected allowed map to be nil for the fast path")
+			if tc.expectFilteredBandNil {
+				assert.Nil(t, filteredBand, "Expected filtered band to be nil")
 			} else {
-				assert.Equal(t, tc.expectAllowed, allowed, "The set of allowed flows should match the expected value")
+				assert.NotNil(t, filteredBand, "Expected a non-nil filtered band")
 			}
 		})
 	}
@@ -85,15 +85,19 @@ func TestSubsetPriorityBandAccessor(t *testing.T) {
 
 	// --- ARRANGE ---
 	// Setup a mock original accessor that knows about three flows.
-	mockQueueA := &frameworkmocks.MockFlowQueueAccessor{FlowSpecV: types.FlowSpecification{ID: "flow-a"}}
-	mockQueueB := &frameworkmocks.MockFlowQueueAccessor{FlowSpecV: types.FlowSpecification{ID: "flow-b"}}
-	mockQueueC := &frameworkmocks.MockFlowQueueAccessor{FlowSpecV: types.FlowSpecification{ID: "flow-c"}}
+	flowAKey := types.FlowKey{ID: "flow-a", Priority: 10}
+	flowBKey := types.FlowKey{ID: "flow-b", Priority: 10}
+	flowCKey := types.FlowKey{ID: "flow-c", Priority: 10}
+
+	mockQueueA := &frameworkmocks.MockFlowQueueAccessor{FlowKeyV: flowAKey}
+	mockQueueB := &frameworkmocks.MockFlowQueueAccessor{FlowKeyV: flowBKey}
+	mockQueueC := &frameworkmocks.MockFlowQueueAccessor{FlowKeyV: flowCKey}
 
 	originalAccessor := &frameworkmocks.MockPriorityBandAccessor{
 		PriorityV:     10,
 		PriorityNameV: "High",
-		FlowIDsFunc: func() []string {
-			return []string{"flow-a", "flow-b", "flow-c"}
+		FlowKeysFunc: func() []types.FlowKey {
+			return []types.FlowKey{flowAKey, flowBKey, flowCKey}
 		},
 		QueueFunc: func(id string) framework.FlowQueueAccessor {
 			switch id {
@@ -118,10 +122,7 @@ func TestSubsetPriorityBandAccessor(t *testing.T) {
 	}
 
 	// Create a subset view that only allows two of the flows.
-	allowedFlows := map[string]struct{}{
-		"flow-a": {},
-		"flow-c": {},
-	}
+	allowedFlows := []types.FlowKey{flowAKey, flowCKey}
 	subsetAccessor := newSubsetPriorityBandAccessor(originalAccessor, allowedFlows)
 	require.NotNil(t, subsetAccessor, "newSubsetPriorityBandAccessor should not return nil")
 
@@ -132,12 +133,14 @@ func TestSubsetPriorityBandAccessor(t *testing.T) {
 			"PriorityName() should pass through from the original accessor")
 	})
 
-	t.Run("should only return allowed flow IDs", func(t *testing.T) {
+	t.Run("should only return allowed flow keys", func(t *testing.T) {
 		t.Parallel()
-		flowIDs := subsetAccessor.FlowIDs()
+		flowKeys := subsetAccessor.FlowKeys()
 		// Sort for consistent comparison, as the pre-computed slice order is not guaranteed.
-		sort.Strings(flowIDs)
-		assert.Equal(t, []string{"flow-a", "flow-c"}, flowIDs, "FlowIDs() should only return the allowed subset")
+		sort.Slice(flowKeys, func(i, j int) bool {
+			return flowKeys[i].ID < flowKeys[j].ID
+		})
+		assert.Equal(t, []types.FlowKey{flowAKey, flowCKey}, flowKeys, "FlowKeys() should only return the allowed subset")
 	})
 
 	t.Run("should only return queues for allowed flows", func(t *testing.T) {
@@ -151,7 +154,7 @@ func TestSubsetPriorityBandAccessor(t *testing.T) {
 		t.Parallel()
 		var iterated []string
 		subsetAccessor.IterateQueues(func(queue framework.FlowQueueAccessor) bool {
-			iterated = append(iterated, queue.FlowSpec().ID)
+			iterated = append(iterated, queue.FlowKey().ID)
 			return true
 		})
 		// Sort for consistent comparison, as iteration order is not guaranteed.
@@ -163,7 +166,7 @@ func TestSubsetPriorityBandAccessor(t *testing.T) {
 		t.Parallel()
 		var iterated []string
 		subsetAccessor.IterateQueues(func(queue framework.FlowQueueAccessor) bool {
-			iterated = append(iterated, queue.FlowSpec().ID)
+			iterated = append(iterated, queue.FlowKey().ID)
 			return false // Exit after the first item.
 		})
 		assert.Len(t, iterated, 1, "Iteration should have stopped after one item")
diff --git a/pkg/epp/flowcontrol/controller/internal/item_test.go b/pkg/epp/flowcontrol/controller/internal/item_test.go
index 11e08ae..d50aaed 100644
--- a/pkg/epp/flowcontrol/controller/internal/item_test.go
+++ b/pkg/epp/flowcontrol/controller/internal/item_test.go
@@ -41,7 +41,8 @@ func TestItem(t *testing.T) {
 
 	t.Run("should have a non-finalized state upon creation", func(t *testing.T) {
 		t.Parallel()
-		req := typesmocks.NewMockFlowControlRequest(100, "req-1", "flow-a", context.Background())
+		key := types.FlowKey{ID: "flow-a", Priority: 10}
+		req := typesmocks.NewMockFlowControlRequest(100, "req-1", key, context.Background())
 		item := NewItem(req, time.Minute, time.Now())
 		require.NotNil(t, item, "NewItem should not return nil")
 		outcome, err := item.FinalState()
diff --git a/pkg/epp/flowcontrol/controller/internal/processor_test.go b/pkg/epp/flowcontrol/controller/internal/processor_test.go
index 09f1206..67657a9 100644
--- a/pkg/epp/flowcontrol/controller/internal/processor_test.go
+++ b/pkg/epp/flowcontrol/controller/internal/processor_test.go
@@ -33,7 +33,7 @@ limitations under the License.
 //     ensures that tests are verifying the processor's orchestration logic (i.e., that it calls its dependencies
 //     correctly) and are not affected by confounding bugs in those dependencies.
 //
-// In summary, this strategy is a prerequisite for reliably testing a concurrent engine, not just a simple data
+// In summary, this is a prerequisite for reliably testing a concurrent engine, not just a simple data
 // structure.
 //
 
@@ -71,7 +71,7 @@ const (
 	testWaitTimeout = 1 * time.Second
 )
 
-var testFlow = types.FlowSpecification{ID: "flow-a", Priority: 10}
+var testFlow = types.FlowKey{ID: "flow-a", Priority: 10}
 
 // TestMain sets up the logger for all tests in the package.
 func TestMain(m *testing.M) {
@@ -121,8 +121,8 @@ type testHarness struct {
 	// --- Centralized Mock State ---
 	// The harness's mutex protects the single source of truth for all mock state.
 	mu            sync.Mutex
-	queues        map[string]*mocks.MockManagedQueue // Key: `flowID`
-	priorityFlows map[uint][]string                  // Key: `priority`, Val: slice of `flowIDs`
+	queues        map[types.FlowKey]*mocks.MockManagedQueue
+	priorityFlows map[uint][]types.FlowKey // Key: `priority`
 
 	// Customizable policy logic for tests to override.
 	interFlowPolicySelectQueue func(band framework.PriorityBandAccessor) (framework.FlowQueueAccessor, error)
@@ -138,12 +138,11 @@ func newTestHarness(t *testing.T, expiryCleanupInterval time.Duration) *testHarn
 		mockClock:         newMockClock(),
 		logger:            logr.Discard(),
 		startSignal:       make(chan struct{}),
-		queues:            make(map[string]*mocks.MockManagedQueue),
-		priorityFlows:     make(map[uint][]string),
+		queues:            make(map[types.FlowKey]*mocks.MockManagedQueue),
+		priorityFlows:     make(map[uint][]types.FlowKey),
 	}
 
 	// Wire up the harness to provide the mock implementations for the shard's dependencies.
-	h.ActiveManagedQueueFunc = h.activeManagedQueue
 	h.ManagedQueueFunc = h.managedQueue
 	h.AllOrderedPriorityLevelsFunc = h.allOrderedPriorityLevels
 	h.PriorityBandAccessorFunc = h.priorityBandAccessor
@@ -165,7 +164,7 @@ func newTestHarness(t *testing.T, expiryCleanupInterval time.Duration) *testHarn
 		ctx context.Context,
 		band framework.PriorityBandAccessor,
 		logger logr.Logger,
-	) (map[string]struct{}, bool) {
+	) (framework.PriorityBandAccessor, bool) {
 		return nil, false
 	}
 	h.processor = NewShardProcessor(h, filter, h.mockClock, expiryCleanupInterval, h.logger)
@@ -215,48 +214,38 @@ func (h *testHarness) waitForFinalization(item *flowItem) (types.QueueOutcome, e
 }
 
 // newTestItem creates a new flowItem for testing purposes.
-func (h *testHarness) newTestItem(id, flowID string, ttl time.Duration) *flowItem {
+func (h *testHarness) newTestItem(id string, key types.FlowKey, ttl time.Duration) *flowItem {
 	h.t.Helper()
 	ctx := log.IntoContext(context.Background(), h.logger)
-	req := typesmocks.NewMockFlowControlRequest(100, id, flowID, ctx)
+	req := typesmocks.NewMockFlowControlRequest(100, id, key, ctx)
 	return NewItem(req, ttl, h.mockClock.Now())
 }
 
 // addQueue centrally registers a new mock queue for a given flow, ensuring all harness components are aware of it.
-func (h *testHarness) addQueue(spec types.FlowSpecification) *mocks.MockManagedQueue {
+func (h *testHarness) addQueue(key types.FlowKey) *mocks.MockManagedQueue {
 	h.t.Helper()
 	h.mu.Lock()
 	defer h.mu.Unlock()
 
-	mockQueue := &mocks.MockManagedQueue{FlowSpecV: spec}
-	h.queues[spec.ID] = mockQueue
+	mockQueue := &mocks.MockManagedQueue{FlowKeyV: key}
+	h.queues[key] = mockQueue
 
-	// Add the `flowID` to the correct priority band, creating the band if needed.
-	h.priorityFlows[spec.Priority] = append(h.priorityFlows[spec.Priority], spec.ID)
+	// Add the key to the correct priority band, creating the band if needed.
+	h.priorityFlows[key.Priority] = append(h.priorityFlows[key.Priority], key)
 
 	return mockQueue
 }
 
 // --- Mock Interface Implementations ---
 
-// activeManagedQueue provides the mock implementation for the `RegistryShard` interface.
-func (h *testHarness) activeManagedQueue(flowID string) (contracts.ManagedQueue, error) {
-	h.mu.Lock()
-	defer h.mu.Unlock()
-	if q, ok := h.queues[flowID]; ok {
-		return q, nil
-	}
-	return nil, fmt.Errorf("test setup error: no active queue for flow %q", flowID)
-}
-
 // managedQueue provides the mock implementation for the `RegistryShard` interface.
-func (h *testHarness) managedQueue(flowID string, priority uint) (contracts.ManagedQueue, error) {
+func (h *testHarness) managedQueue(key types.FlowKey) (contracts.ManagedQueue, error) {
 	h.mu.Lock()
 	defer h.mu.Unlock()
-	if q, ok := h.queues[flowID]; ok && q.FlowSpec().Priority == priority {
+	if q, ok := h.queues[key]; ok {
 		return q, nil
 	}
-	return nil, fmt.Errorf("test setup error: no queue for %q at priority %d", flowID, priority)
+	return nil, fmt.Errorf("test setup error: no queue for %q", key)
 }
 
 // allOrderedPriorityLevels provides the mock implementation for the `RegistryShard` interface.
@@ -278,15 +267,15 @@ func (h *testHarness) priorityBandAccessor(p uint) (framework.PriorityBandAccess
 
 	// Safely get a snapshot of the flow IDs under a lock.
 	h.mu.Lock()
-	flowIDsForPriority := h.priorityFlows[p]
+	flowKeysForPriority := h.priorityFlows[p]
 	h.mu.Unlock()
 
 	// Configure the mock's behavior with a closure that reads from the harness's centralized, thread-safe state.
 	band.IterateQueuesFunc = func(cb func(fqa framework.FlowQueueAccessor) bool) {
 		// This closure safely iterates over the snapshot of flow IDs.
-		for _, id := range flowIDsForPriority {
+		for _, key := range flowKeysForPriority {
 			// Get the queue using the thread-safe `managedQueue` method.
-			q, err := h.managedQueue(id, p)
+			q, err := h.managedQueue(key)
 			if err == nil && q != nil {
 				mq := q.(*mocks.MockManagedQueue)
 				if !cb(mq.FlowQueueAccessor()) {
@@ -323,7 +312,7 @@ func (h *testHarness) interFlowDispatchPolicy(p uint) (framework.InterFlowDispat
 }
 
 // intraFlowDispatchPolicy provides the mock implementation for the `contracts.RegistryShard` interface.
-func (h *testHarness) intraFlowDispatchPolicy(flowID string, priority uint) (framework.IntraFlowDispatchPolicy, error) {
+func (h *testHarness) intraFlowDispatchPolicy(types.FlowKey) (framework.IntraFlowDispatchPolicy, error) {
 	policy := &frameworkmocks.MockIntraFlowDispatchPolicy{}
 	// If the test provided a custom implementation, use it.
 	if h.intraFlowPolicySelectItem != nil {
@@ -351,8 +340,8 @@ func TestShardProcessor(t *testing.T) {
 			t.Parallel()
 			// --- ARRANGE ---
 			h := newTestHarness(t, testCleanupTick)
-			item := h.newTestItem("req-dispatch-success", testFlow.ID, testTTL)
-			h.addQueue(types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority})
+			item := h.newTestItem("req-dispatch-success", testFlow, testTTL)
+			h.addQueue(types.FlowKey{ID: testFlow.ID, Priority: testFlow.Priority})
 
 			// --- ACT ---
 			h.Start()
@@ -370,8 +359,8 @@ func TestShardProcessor(t *testing.T) {
 			t.Parallel()
 			// --- ARRANGE ---
 			h := newTestHarness(t, testCleanupTick)
-			item := h.newTestItem("req-capacity-reject", testFlow.ID, testTTL)
-			h.addQueue(types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority})
+			item := h.newTestItem("req-capacity-reject", testFlow, testTTL)
+			h.addQueue(testFlow)
 			h.StatsFunc = func() contracts.ShardStats {
 				return contracts.ShardStats{PerPriorityBandStats: map[uint]contracts.PriorityBandStats{
 					testFlow.Priority: {CapacityBytes: 50}, // 50 is less than item size of 100
@@ -395,9 +384,9 @@ func TestShardProcessor(t *testing.T) {
 			t.Parallel()
 			// --- ARRANGE ---
 			h := newTestHarness(t, testCleanupTick)
-			item := h.newTestItem("req-lookup-fail-reject", testFlow.ID, testTTL)
+			item := h.newTestItem("req-lookup-fail-reject", testFlow, testTTL)
 			registryErr := errors.New("test registry lookup error")
-			h.ActiveManagedQueueFunc = func(flowID string) (contracts.ManagedQueue, error) {
+			h.ManagedQueueFunc = func(types.FlowKey) (contracts.ManagedQueue, error) {
 				return nil, registryErr
 			}
 
@@ -418,8 +407,8 @@ func TestShardProcessor(t *testing.T) {
 			t.Parallel()
 			// --- ARRANGE ---
 			h := newTestHarness(t, testCleanupTick)
-			item := h.newTestItem("req-shutdown-reject", testFlow.ID, testTTL)
-			h.addQueue(types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority})
+			item := h.newTestItem("req-shutdown-reject", testFlow, testTTL)
+			h.addQueue(testFlow)
 
 			// --- ACT ---
 			h.Start()
@@ -439,8 +428,8 @@ func TestShardProcessor(t *testing.T) {
 			t.Parallel()
 			// --- ARRANGE ---
 			h := newTestHarness(t, testCleanupTick)
-			item := h.newTestItem("req-expired-evict", testFlow.ID, testShortTTL)
-			h.addQueue(types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority})
+			item := h.newTestItem("req-expired-evict", testFlow, testShortTTL)
+			h.addQueue(testFlow)
 
 			// --- ACT ---
 			h.Start()
@@ -463,8 +452,8 @@ func TestShardProcessor(t *testing.T) {
 			t.Parallel()
 			// --- ARRANGE ---
 			h := newTestHarness(t, 1*time.Hour) // Disable background cleanup to isolate dispatch logic.
-			item := h.newTestItem("req-expired-dispatch-evict", testFlow.ID, testShortTTL)
-			mockQueue := h.addQueue(types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority})
+			item := h.newTestItem("req-expired-dispatch-evict", testFlow, testShortTTL)
+			mockQueue := h.addQueue(testFlow)
 			require.NoError(t, mockQueue.Add(item), "Adding item to mock queue should not fail")
 
 			// Have the policy select the item, but then advance time so it's expired by the time dispatchItem actually runs.
@@ -494,9 +483,9 @@ func TestShardProcessor(t *testing.T) {
 			// --- ARRANGE ---
 			h := newTestHarness(t, testCleanupTick)
 			ctx, cancel := context.WithCancel(context.Background())
-			req := typesmocks.NewMockFlowControlRequest(100, "req-ctx-cancel", testFlow.ID, ctx)
+			req := typesmocks.NewMockFlowControlRequest(100, "req-ctx-cancel", testFlow, ctx)
 			item := NewItem(req, testTTL, h.mockClock.Now())
-			h.addQueue(types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority})
+			h.addQueue(testFlow)
 
 			// --- ACT ---
 			h.Start()
@@ -518,8 +507,8 @@ func TestShardProcessor(t *testing.T) {
 			t.Parallel()
 			// --- ARRANGE ---
 			h := newTestHarness(t, testCleanupTick)
-			item := h.newTestItem("req-shutdown-evict", testFlow.ID, testTTL)
-			mockQueue := h.addQueue(types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority})
+			item := h.newTestItem("req-shutdown-evict", testFlow, testTTL)
+			mockQueue := h.addQueue(testFlow)
 			require.NoError(t, mockQueue.Add(item), "Adding item to mock queue should not fail")
 
 			// Prevent dispatch to ensure we test shutdown eviction, not a successful dispatch.
@@ -544,10 +533,10 @@ func TestShardProcessor(t *testing.T) {
 			// --- ARRANGE ---
 			h := newTestHarness(t, testCleanupTick)
 			const numConcurrentItems = 20
-			q := h.addQueue(types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority})
+			q := h.addQueue(testFlow)
 			itemsToTest := make([]*flowItem, 0, numConcurrentItems)
 			for i := 0; i < numConcurrentItems; i++ {
-				item := h.newTestItem(fmt.Sprintf("req-concurrent-%d", i), testFlow.ID, testTTL)
+				item := h.newTestItem(fmt.Sprintf("req-concurrent-%d", i), testFlow, testTTL)
 				itemsToTest = append(itemsToTest, item)
 			}
 
@@ -581,8 +570,8 @@ func TestShardProcessor(t *testing.T) {
 
 			// --- ARRANGE ---
 			h := newTestHarness(t, 1*time.Hour) // Disable background cleanup to isolate the race.
-			item := h.newTestItem("req-race", testFlow.ID, testShortTTL)
-			q := h.addQueue(types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority})
+			item := h.newTestItem("req-race", testFlow, testShortTTL)
+			q := h.addQueue(testFlow)
 
 			// Use channels to pause the dispatch cycle right before it would remove the item.
 			policyCanProceed := make(chan struct{})
@@ -683,7 +672,7 @@ func TestShardProcessor(t *testing.T) {
 				{
 					name: "should reject item on registry queue lookup failure",
 					setupHarness: func(h *testHarness) {
-						h.ActiveManagedQueueFunc = func(string) (contracts.ManagedQueue, error) { return nil, testErr }
+						h.ManagedQueueFunc = func(types.FlowKey) (contracts.ManagedQueue, error) { return nil, testErr }
 					},
 					assert: func(t *testing.T, h *testHarness, item *flowItem) {
 						outcome, err := item.FinalState()
@@ -695,7 +684,7 @@ func TestShardProcessor(t *testing.T) {
 				{
 					name: "should reject item on registry priority band lookup failure",
 					setupHarness: func(h *testHarness) {
-						h.addQueue(types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority})
+						h.addQueue(testFlow)
 						h.PriorityBandAccessorFunc = func(uint) (framework.PriorityBandAccessor, error) { return nil, testErr }
 					},
 					assert: func(t *testing.T, h *testHarness, item *flowItem) {
@@ -708,7 +697,7 @@ func TestShardProcessor(t *testing.T) {
 				{
 					name: "should reject item on queue add failure",
 					setupHarness: func(h *testHarness) {
-						mockQueue := h.addQueue(types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority})
+						mockQueue := h.addQueue(testFlow)
 						mockQueue.AddFunc = func(types.QueueItemAccessor) error { return testErr }
 					},
 					assert: func(t *testing.T, h *testHarness, item *flowItem) {
@@ -721,7 +710,7 @@ func TestShardProcessor(t *testing.T) {
 				{
 					name: "should ignore an already-finalized item",
 					setupHarness: func(h *testHarness) {
-						mockQueue := h.addQueue(types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority})
+						mockQueue := h.addQueue(testFlow)
 						var addCallCount int
 						mockQueue.AddFunc = func(item types.QueueItemAccessor) error {
 							addCallCount++
@@ -734,7 +723,7 @@ func TestShardProcessor(t *testing.T) {
 					},
 					item: func() *flowItem {
 						// Create a pre-finalized item.
-						item := newTestHarness(t, 0).newTestItem("req-finalized", testFlow.ID, testTTL)
+						item := newTestHarness(t, 0).newTestItem("req-finalized", testFlow, testTTL)
 						item.finalize(types.QueueOutcomeDispatched, nil)
 						return item
 					}(),
@@ -754,7 +743,7 @@ func TestShardProcessor(t *testing.T) {
 					tc.setupHarness(h)
 					item := tc.item
 					if item == nil {
-						item = h.newTestItem("req-enqueue-test", testFlow.ID, testTTL)
+						item = h.newTestItem("req-enqueue-test", testFlow, testTTL)
 					}
 					h.processor.enqueue(item)
 					tc.assert(t, h, item)
@@ -833,7 +822,6 @@ func TestShardProcessor(t *testing.T) {
 				t.Parallel()
 				policyErr := errors.New("policy failure")
 				registryErr := errors.New("registry error")
-				specA := types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority}
 
 				testCases := []struct {
 					name              string
@@ -843,7 +831,7 @@ func TestShardProcessor(t *testing.T) {
 					{
 						name: "should do nothing if no items are queued",
 						setupHarness: func(h *testHarness) {
-							h.addQueue(specA) // Add a queue, but no items.
+							h.addQueue(testFlow) // Add a queue, but no items.
 						},
 						expectDidDispatch: false,
 					},
@@ -851,13 +839,13 @@ func TestShardProcessor(t *testing.T) {
 						name: "should stop dispatching when filter signals pause",
 						setupHarness: func(h *testHarness) {
 							// Add an item that *could* be dispatched to prove the pause is effective.
-							q := h.addQueue(types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority})
-							require.NoError(t, q.Add(h.newTestItem("item", testFlow.ID, testTTL)))
+							q := h.addQueue(testFlow)
+							require.NoError(t, q.Add(h.newTestItem("item", testFlow, testTTL)))
 							h.processor.dispatchFilter = func(
 								_ context.Context,
 								_ framework.PriorityBandAccessor,
 								_ logr.Logger,
-							) (map[string]struct{}, bool) {
+							) (framework.PriorityBandAccessor, bool) {
 								return nil, true // Signal pause.
 							}
 						},
@@ -875,7 +863,7 @@ func TestShardProcessor(t *testing.T) {
 					{
 						name: "should skip band on inter-flow policy error",
 						setupHarness: func(h *testHarness) {
-							h.addQueue(specA)
+							h.addQueue(testFlow)
 							h.interFlowPolicySelectQueue = func(
 								_ framework.PriorityBandAccessor,
 							) (framework.FlowQueueAccessor, error) {
@@ -887,8 +875,8 @@ func TestShardProcessor(t *testing.T) {
 					{
 						name: "should skip band if inter-flow policy returns no queue",
 						setupHarness: func(h *testHarness) {
-							q := h.addQueue(specA)
-							require.NoError(t, q.Add(h.newTestItem("item", testFlow.ID, testTTL)))
+							q := h.addQueue(testFlow)
+							require.NoError(t, q.Add(h.newTestItem("item", testFlow, testTTL)))
 							h.interFlowPolicySelectQueue = func(
 								_ framework.PriorityBandAccessor,
 							) (framework.FlowQueueAccessor, error) {
@@ -900,8 +888,8 @@ func TestShardProcessor(t *testing.T) {
 					{
 						name: "should skip band on intra-flow policy error",
 						setupHarness: func(h *testHarness) {
-							q := h.addQueue(specA)
-							require.NoError(t, q.Add(h.newTestItem("item", testFlow.ID, testTTL)))
+							q := h.addQueue(testFlow)
+							require.NoError(t, q.Add(h.newTestItem("item", testFlow, testTTL)))
 							h.interFlowPolicySelectQueue = func(
 								_ framework.PriorityBandAccessor,
 							) (framework.FlowQueueAccessor, error) {
@@ -916,8 +904,8 @@ func TestShardProcessor(t *testing.T) {
 					{
 						name: "should skip band if intra-flow policy returns no item",
 						setupHarness: func(h *testHarness) {
-							q := h.addQueue(specA)
-							require.NoError(t, q.Add(h.newTestItem("item", testFlow.ID, testTTL)))
+							q := h.addQueue(testFlow)
+							require.NoError(t, q.Add(h.newTestItem("item", testFlow, testTTL)))
 							h.interFlowPolicySelectQueue = func(
 								_ framework.PriorityBandAccessor,
 							) (framework.FlowQueueAccessor, error) {
@@ -933,12 +921,12 @@ func TestShardProcessor(t *testing.T) {
 						name: "should continue to lower priority band on inter-flow policy error",
 						setupHarness: func(h *testHarness) {
 							// Create a failing high-priority queue and a working low-priority queue.
-							specHigh := types.FlowSpecification{ID: "flow-high", Priority: testFlow.Priority}
-							specLow := types.FlowSpecification{ID: "flow-low", Priority: 20}
-							h.addQueue(specHigh)
-							qLow := h.addQueue(specLow)
+							keyHigh := types.FlowKey{ID: "flow-high", Priority: testFlow.Priority}
+							keyLow := types.FlowKey{ID: "flow-low", Priority: 20}
+							h.addQueue(keyHigh)
+							qLow := h.addQueue(keyLow)
 
-							itemLow := h.newTestItem("item-low", specLow.ID, testTTL)
+							itemLow := h.newTestItem("item-low", keyLow, testTTL)
 							require.NoError(t, qLow.Add(itemLow))
 
 							h.interFlowPolicySelectQueue = func(
@@ -948,7 +936,7 @@ func TestShardProcessor(t *testing.T) {
 									return nil, errors.New("policy failure") // Fail high-priority.
 								}
 								// Succeed for low-priority.
-								q, _ := h.managedQueue(specLow.ID, specLow.Priority)
+								q, _ := h.managedQueue(keyLow)
 								return q.FlowQueueAccessor(), nil
 							}
 						},
@@ -971,34 +959,33 @@ func TestShardProcessor(t *testing.T) {
 				t.Parallel()
 				// --- ARRANGE ---
 				h := newTestHarness(t, testCleanupTick)
-				specA := types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority}
-				specB := types.FlowSpecification{ID: "flow-b", Priority: testFlow.Priority}
-				h.addQueue(specA)
-				qB := h.addQueue(specB)
-				itemB := h.newTestItem("item-b", specB.ID, testTTL)
+				flowB := types.FlowKey{ID: "flow-b", Priority: testFlow.Priority}
+				h.addQueue(testFlow)
+				qB := h.addQueue(flowB)
+				itemB := h.newTestItem("item-b", flowB, testTTL)
 				require.NoError(t, qB.Add(itemB))
 
 				// This filter only allows flow-b.
 				h.processor.dispatchFilter = func(
 					_ context.Context,
-					_ framework.PriorityBandAccessor,
+					originalBand framework.PriorityBandAccessor,
 					_ logr.Logger,
-				) (map[string]struct{}, bool) {
-					return map[string]struct{}{specB.ID: {}}, false
+				) (framework.PriorityBandAccessor, bool) {
+					return newSubsetPriorityBandAccessor(originalBand, []types.FlowKey{flowB}), false
 				}
 
 				// This policy will be given the filtered view, so it should only see flow-b.
 				h.interFlowPolicySelectQueue = func(band framework.PriorityBandAccessor) (framework.FlowQueueAccessor, error) {
 					var flowIDs []string
 					band.IterateQueues(func(fqa framework.FlowQueueAccessor) bool {
-						flowIDs = append(flowIDs, fqa.FlowSpec().ID)
+						flowIDs = append(flowIDs, fqa.FlowKey().ID)
 						return true
 					})
 					// This is the core assertion of the test.
-					require.ElementsMatch(t, []string{specB.ID}, flowIDs, "Policy should only see the filtered flow")
+					require.ElementsMatch(t, []string{flowB.ID}, flowIDs, "Policy should only see the filtered flow")
 
 					// Select flow-b to prove the chain works.
-					q, _ := h.managedQueue(specB.ID, specB.Priority)
+					q, _ := h.managedQueue(flowB)
 					return q.FlowQueueAccessor(), nil
 				}
 
@@ -1016,22 +1003,22 @@ func TestShardProcessor(t *testing.T) {
 				t.Parallel()
 				// --- ARRANGE ---
 				h := newTestHarness(t, testCleanupTick)
-				specHigh := types.FlowSpecification{ID: "flow-high", Priority: 10}
-				specLow := types.FlowSpecification{ID: "flow-low", Priority: 20}
-				qHigh := h.addQueue(specHigh)
-				qLow := h.addQueue(specLow)
+				keyHigh := types.FlowKey{ID: "flow-high", Priority: 10}
+				keyLow := types.FlowKey{ID: "flow-low", Priority: 20}
+				qHigh := h.addQueue(keyHigh)
+				qLow := h.addQueue(keyLow)
 
 				const numItems = 3
 				highPrioItems := make([]*flowItem, numItems)
 				lowPrioItems := make([]*flowItem, numItems)
 				for i := range numItems {
 					// Add high priority items.
-					itemH := h.newTestItem(fmt.Sprintf("req-high-%d", i), specHigh.ID, testTTL)
+					itemH := h.newTestItem(fmt.Sprintf("req-high-%d", i), keyHigh, testTTL)
 					require.NoError(t, qHigh.Add(itemH))
 					highPrioItems[i] = itemH
 
 					// Add low priority items.
-					itemL := h.newTestItem(fmt.Sprintf("req-low-%d", i), specLow.ID, testTTL)
+					itemL := h.newTestItem(fmt.Sprintf("req-low-%d", i), keyLow, testTTL)
 					require.NoError(t, qLow.Add(itemL))
 					lowPrioItems[i] = itemL
 				}
@@ -1075,14 +1062,14 @@ func TestShardProcessor(t *testing.T) {
 					{
 						name: "on ManagedQueue lookup failure",
 						setupMocks: func(h *testHarness) {
-							h.ManagedQueueFunc = func(string, uint) (contracts.ManagedQueue, error) { return nil, registryErr }
+							h.ManagedQueueFunc = func(types.FlowKey) (contracts.ManagedQueue, error) { return nil, registryErr }
 						},
 						expectedErr: registryErr,
 					},
 					{
 						name: "on queue remove failure",
 						setupMocks: func(h *testHarness) {
-							h.ManagedQueueFunc = func(string, uint) (contracts.ManagedQueue, error) {
+							h.ManagedQueueFunc = func(types.FlowKey) (contracts.ManagedQueue, error) {
 								return &mocks.MockManagedQueue{
 									RemoveFunc: func(types.QueueItemHandle) (types.QueueItemAccessor, error) {
 										return nil, registryErr
@@ -1099,8 +1086,8 @@ func TestShardProcessor(t *testing.T) {
 						t.Parallel()
 						h := newTestHarness(t, testCleanupTick)
 						tc.setupMocks(h)
-						item := h.newTestItem("req-dispatch-fail", testFlow.ID, testTTL)
-						err := h.processor.dispatchItem(item, testFlow.Priority, h.logger)
+						item := h.newTestItem("req-dispatch-fail", testFlow, testTTL)
+						err := h.processor.dispatchItem(item, h.logger)
 						require.Error(t, err, "dispatchItem should return an error")
 						assert.ErrorIs(t, err, tc.expectedErr, "The underlying registry error should be preserved")
 					})
@@ -1111,9 +1098,9 @@ func TestShardProcessor(t *testing.T) {
 				t.Parallel()
 				// --- ARRANGE ---
 				h := newTestHarness(t, testCleanupTick)
-				item := h.newTestItem("req-expired-dispatch", testFlow.ID, testShortTTL)
+				item := h.newTestItem("req-expired-dispatch", testFlow, testShortTTL)
 
-				h.ManagedQueueFunc = func(string, uint) (contracts.ManagedQueue, error) {
+				h.ManagedQueueFunc = func(types.FlowKey) (contracts.ManagedQueue, error) {
 					return &mocks.MockManagedQueue{
 						RemoveFunc: func(types.QueueItemHandle) (types.QueueItemAccessor, error) {
 							return item, nil
@@ -1123,7 +1110,7 @@ func TestShardProcessor(t *testing.T) {
 
 				// --- ACT ---
 				h.mockClock.Advance(testShortTTL * 2) // Make the item expire.
-				err := h.processor.dispatchItem(item, testFlow.Priority, h.logger)
+				err := h.processor.dispatchItem(item, h.logger)
 
 				// --- ASSERT ---
 				// First, check the error returned by `dispatchItem`.
@@ -1142,10 +1129,10 @@ func TestShardProcessor(t *testing.T) {
 				// --- ARRANGE ---
 				h := newTestHarness(t, testCleanupTick)
 				badItem := &typesmocks.MockQueueItemAccessor{
-					OriginalRequestV: typesmocks.NewMockFlowControlRequest(0, "bad-item", "", context.Background()),
+					OriginalRequestV: typesmocks.NewMockFlowControlRequest(0, "bad-item", testFlow, context.Background()),
 				}
 
-				h.ManagedQueueFunc = func(string, uint) (contracts.ManagedQueue, error) {
+				h.ManagedQueueFunc = func(types.FlowKey) (contracts.ManagedQueue, error) {
 					return &mocks.MockManagedQueue{
 						RemoveFunc: func(types.QueueItemHandle) (types.QueueItemAccessor, error) {
 							return badItem, nil
@@ -1153,13 +1140,13 @@ func TestShardProcessor(t *testing.T) {
 					}, nil
 				}
 
-				itemToDispatch := h.newTestItem("req-dispatch-panic", testFlow.ID, testTTL)
+				itemToDispatch := h.newTestItem("req-dispatch-panic", testFlow, testTTL)
 				expectedPanicMsg := fmt.Sprintf("%s: internal error: item %q of type %T is not a *flowItem",
 					errIntraFlow, "bad-item", badItem)
 
 				// --- ACT & ASSERT ---
 				assert.PanicsWithError(t, expectedPanicMsg, func() {
-					_ = h.processor.dispatchItem(itemToDispatch, testFlow.Priority, h.logger)
+					_ = h.processor.dispatchItem(itemToDispatch, h.logger)
 				}, "A type mismatch from a queue should cause a panic")
 			})
 		})
@@ -1171,10 +1158,9 @@ func TestShardProcessor(t *testing.T) {
 				t.Parallel()
 				// --- ARRANGE ---
 				h := newTestHarness(t, testCleanupTick)
-				spec := types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority}
 				// Create an item that is already expired relative to the cleanup time.
-				item := h.newTestItem("req-expired", testFlow.ID, 1*time.Millisecond)
-				q := h.addQueue(spec)
+				item := h.newTestItem("req-expired", testFlow, 1*time.Millisecond)
+				q := h.addQueue(testFlow)
 				require.NoError(t, q.Add(item))
 				cleanupTime := h.mockClock.Now().Add(10 * time.Millisecond)
 
@@ -1192,9 +1178,8 @@ func TestShardProcessor(t *testing.T) {
 				t.Parallel()
 				// --- ARRANGE ---
 				h := newTestHarness(t, testCleanupTick)
-				spec := types.FlowSpecification{ID: testFlow.ID, Priority: testFlow.Priority}
-				item := h.newTestItem("req-pending", testFlow.ID, testTTL)
-				q := h.addQueue(spec)
+				item := h.newTestItem("req-pending", testFlow, testTTL)
+				q := h.addQueue(testFlow)
 				require.NoError(t, q.Add(item))
 
 				// --- ACT ---
@@ -1228,7 +1213,7 @@ func TestShardProcessor(t *testing.T) {
 				// --- ARRANGE ---
 				h := newTestHarness(t, testCleanupTick)
 				item := &typesmocks.MockQueueItemAccessor{
-					OriginalRequestV: typesmocks.NewMockFlowControlRequest(0, "bad-item", testFlow.ID, context.Background()),
+					OriginalRequestV: typesmocks.NewMockFlowControlRequest(0, "bad-item", testFlow, context.Background()),
 				}
 				items := []types.QueueItemAccessor{item}
 
@@ -1252,11 +1237,11 @@ func TestShardProcessor(t *testing.T) {
 				var processedCount atomic.Int32
 
 				for i := range numQueues {
-					spec := types.FlowSpecification{
+					key := types.FlowKey{
 						ID:       fmt.Sprintf("flow-%d", i),
 						Priority: testFlow.Priority,
 					}
-					h.addQueue(spec)
+					h.addQueue(key)
 				}
 
 				processFn := func(mq contracts.ManagedQueue, logger logr.Logger) {
@@ -1293,7 +1278,7 @@ func TestCheckItemExpiry(t *testing.T) {
 		{
 			name: "should not be expired if TTL is not reached and context is active",
 			item: NewItem(
-				typesmocks.NewMockFlowControlRequest(100, "req-not-expired", "", context.Background()),
+				typesmocks.NewMockFlowControlRequest(100, "req-not-expired", testFlow, context.Background()),
 				testTTL,
 				now),
 			now:           now.Add(30 * time.Second),
@@ -1304,7 +1289,7 @@ func TestCheckItemExpiry(t *testing.T) {
 		{
 			name: "should not be expired if TTL is disabled (0)",
 			item: NewItem(
-				typesmocks.NewMockFlowControlRequest(100, "req-not-expired-no-ttl", "", context.Background()),
+				typesmocks.NewMockFlowControlRequest(100, "req-not-expired-no-ttl", testFlow, context.Background()),
 				0,
 				now),
 			now:           now.Add(30 * time.Second),
@@ -1315,7 +1300,7 @@ func TestCheckItemExpiry(t *testing.T) {
 		{
 			name: "should be expired if TTL is exceeded",
 			item: NewItem(
-				typesmocks.NewMockFlowControlRequest(100, "req-ttl-expired", "", context.Background()),
+				typesmocks.NewMockFlowControlRequest(100, "req-ttl-expired", testFlow, context.Background()),
 				time.Second,
 				now),
 			now:           now.Add(2 * time.Second),
@@ -1326,7 +1311,7 @@ func TestCheckItemExpiry(t *testing.T) {
 		{
 			name: "should be expired if context is cancelled",
 			item: NewItem(
-				typesmocks.NewMockFlowControlRequest(100, "req-ctx-cancelled", "", ctxCancelled),
+				typesmocks.NewMockFlowControlRequest(100, "req-ctx-cancelled", testFlow, ctxCancelled),
 				testTTL,
 				now),
 			now:           now,
@@ -1337,7 +1322,10 @@ func TestCheckItemExpiry(t *testing.T) {
 		{
 			name: "should be expired if already finalized",
 			item: func() types.QueueItemAccessor {
-				i := NewItem(typesmocks.NewMockFlowControlRequest(100, "req-finalized", "", context.Background()), testTTL, now)
+				i := NewItem(
+					typesmocks.NewMockFlowControlRequest(100, "req-finalized", testFlow, context.Background()),
+					testTTL,
+					now)
 				i.finalize(types.QueueOutcomeDispatched, nil)
 				return i
 			}(),
@@ -1376,7 +1364,7 @@ func TestCheckItemExpiry(t *testing.T) {
 		t.Parallel()
 		// --- ARRANGE ---
 		badItem := &typesmocks.MockQueueItemAccessor{
-			OriginalRequestV: typesmocks.NewMockFlowControlRequest(0, "item-bad-type", "", context.Background()),
+			OriginalRequestV: typesmocks.NewMockFlowControlRequest(0, "item-bad-type", testFlow, context.Background()),
 		}
 
 		expectedPanicMsg := fmt.Sprintf("internal error: item %q of type %T is not a *flowItem",
diff --git a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/besthead/besthead_test.go b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/besthead/besthead_test.go
index 4905a21..5971d3f 100644
--- a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/besthead/besthead_test.go
+++ b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/besthead/besthead_test.go
@@ -31,11 +31,16 @@ import (
 )
 
 const (
-	flow1           = "flow1"
-	flow2           = "flow2"
+	flow1ID         = "flow1"
+	flow2ID         = "flow2"
 	commonScoreType = "enqueue_time_ns_asc"
 )
 
+var (
+	flow1Key = types.FlowKey{ID: flow1ID, Priority: 0}
+	flow2Key = types.FlowKey{ID: flow2ID, Priority: 0}
+)
+
 // enqueueTimeComparatorFunc is a test utility. Lower enqueue time is better.
 func enqueueTimeComparatorFunc(a, b types.QueueItemAccessor) bool {
 	return a.EnqueueTime().Before(b.EnqueueTime())
@@ -49,20 +54,21 @@ func newTestComparator() *frameworkmocks.MockItemComparator {
 }
 
 func newTestBand(queues ...framework.FlowQueueAccessor) *frameworkmocks.MockPriorityBandAccessor {
-	flowIDs := make([]string, 0, len(queues))
+	flowKeys := make([]types.FlowKey, 0, len(queues))
 	queuesByID := make(map[string]framework.FlowQueueAccessor, len(queues))
 	for _, q := range queues {
-		flowIDs = append(flowIDs, q.FlowSpec().ID)
-		queuesByID[q.FlowSpec().ID] = q
+		key := q.FlowKey()
+		flowKeys = append(flowKeys, key)
+		queuesByID[key.ID] = q
 	}
 	return &frameworkmocks.MockPriorityBandAccessor{
-		FlowIDsFunc: func() []string { return flowIDs },
+		FlowKeysFunc: func() []types.FlowKey { return flowKeys },
 		QueueFunc: func(id string) framework.FlowQueueAccessor {
 			return queuesByID[id]
 		},
 		IterateQueuesFunc: func(iterator func(queue framework.FlowQueueAccessor) bool) {
-			for _, id := range flowIDs {
-				if !iterator(queuesByID[id]) {
+			for _, key := range flowKeys {
+				if !iterator(queuesByID[key.ID]) {
 					break
 				}
 			}
@@ -81,27 +87,27 @@ func TestBestHead_SelectQueue(t *testing.T) {
 	policy := newBestHead()
 	now := time.Now()
 
-	itemBetter := typesmocks.NewMockQueueItemAccessor(10, "itemBetter", flow1)
+	itemBetter := typesmocks.NewMockQueueItemAccessor(10, "itemBetter", flow1Key)
 	itemBetter.EnqueueTimeV = now.Add(-10 * time.Second)
-	itemWorse := typesmocks.NewMockQueueItemAccessor(20, "itemWorse", flow2)
+	itemWorse := typesmocks.NewMockQueueItemAccessor(20, "itemWorse", flow2Key)
 	itemWorse.EnqueueTimeV = now.Add(-5 * time.Second)
 
 	queue1 := &frameworkmocks.MockFlowQueueAccessor{
 		LenV:        1,
 		PeekHeadV:   itemBetter,
-		FlowSpecV:   types.FlowSpecification{ID: flow1},
+		FlowKeyV:    flow1Key,
 		ComparatorV: newTestComparator(),
 	}
 	queue2 := &frameworkmocks.MockFlowQueueAccessor{
 		LenV:        1,
 		PeekHeadV:   itemWorse,
-		FlowSpecV:   types.FlowSpecification{ID: flow2},
+		FlowKeyV:    flow2Key,
 		ComparatorV: newTestComparator(),
 	}
 	queueEmpty := &frameworkmocks.MockFlowQueueAccessor{
 		LenV:         0,
 		PeekHeadErrV: framework.ErrQueueEmpty,
-		FlowSpecV:    types.FlowSpecification{ID: "flowEmpty"},
+		FlowKeyV:     types.FlowKey{ID: "flowEmpty"},
 		ComparatorV:  newTestComparator(),
 	}
 
@@ -115,17 +121,17 @@ func TestBestHead_SelectQueue(t *testing.T) {
 		{
 			name:            "BasicSelection_TwoQueues",
 			band:            newTestBand(queue1, queue2),
-			expectedQueueID: flow1,
+			expectedQueueID: flow1ID,
 		},
 		{
 			name:            "IgnoresEmptyQueues",
 			band:            newTestBand(queue1, queueEmpty, queue2),
-			expectedQueueID: flow1,
+			expectedQueueID: flow1ID,
 		},
 		{
 			name:            "SingleNonEmptyQueue",
 			band:            newTestBand(queue1),
-			expectedQueueID: flow1,
+			expectedQueueID: flow1ID,
 		},
 		{
 			name: "ComparatorCompatibility",
@@ -133,13 +139,13 @@ func TestBestHead_SelectQueue(t *testing.T) {
 				&frameworkmocks.MockFlowQueueAccessor{
 					LenV:        1,
 					PeekHeadV:   itemBetter,
-					FlowSpecV:   types.FlowSpecification{ID: flow1},
+					FlowKeyV:    flow1Key,
 					ComparatorV: &frameworkmocks.MockItemComparator{ScoreTypeV: "typeA", FuncV: enqueueTimeComparatorFunc},
 				},
 				&frameworkmocks.MockFlowQueueAccessor{
 					LenV:        1,
 					PeekHeadV:   itemWorse,
-					FlowSpecV:   types.FlowSpecification{ID: flow2},
+					FlowKeyV:    flow2Key,
 					ComparatorV: &frameworkmocks.MockItemComparator{ScoreTypeV: "typeB", FuncV: enqueueTimeComparatorFunc},
 				},
 			),
@@ -151,12 +157,12 @@ func TestBestHead_SelectQueue(t *testing.T) {
 				&frameworkmocks.MockFlowQueueAccessor{
 					LenV:         1,
 					PeekHeadErrV: errors.New("peek error"),
-					FlowSpecV:    types.FlowSpecification{ID: flow1},
+					FlowKeyV:     flow1Key,
 					ComparatorV:  newTestComparator(),
 				},
 				queue2,
 			),
-			expectedQueueID: flow2,
+			expectedQueueID: flow2ID,
 		},
 		{
 			name: "QueueComparatorIsNil",
@@ -164,7 +170,7 @@ func TestBestHead_SelectQueue(t *testing.T) {
 				&frameworkmocks.MockFlowQueueAccessor{
 					LenV:        1,
 					PeekHeadV:   itemBetter,
-					FlowSpecV:   types.FlowSpecification{ID: flow1},
+					FlowKeyV:    flow1Key,
 					ComparatorV: nil,
 				},
 				queue2,
@@ -177,7 +183,7 @@ func TestBestHead_SelectQueue(t *testing.T) {
 				&frameworkmocks.MockFlowQueueAccessor{
 					LenV:        1,
 					PeekHeadV:   itemBetter,
-					FlowSpecV:   types.FlowSpecification{ID: flow1},
+					FlowKeyV:    flow1Key,
 					ComparatorV: &frameworkmocks.MockItemComparator{ScoreTypeV: commonScoreType, FuncV: nil},
 				},
 				queue2,
@@ -191,7 +197,7 @@ func TestBestHead_SelectQueue(t *testing.T) {
 				&frameworkmocks.MockFlowQueueAccessor{
 					LenV:         0,
 					PeekHeadErrV: framework.ErrQueueEmpty,
-					FlowSpecV:    types.FlowSpecification{ID: "flowEmpty2"},
+					FlowKeyV:     types.FlowKey{ID: "flowEmpty2"},
 					ComparatorV:  newTestComparator(),
 				},
 			),
@@ -223,7 +229,7 @@ func TestBestHead_SelectQueue(t *testing.T) {
 					assert.Nil(t, selected, "No queue should be selected")
 				} else {
 					require.NotNil(t, selected, "A queue should have been selected")
-					assert.Equal(t, tc.expectedQueueID, selected.FlowSpec().ID, "The selected queue should have the expected ID")
+					assert.Equal(t, tc.expectedQueueID, selected.FlowKey().ID, "The selected queue should have the expected ID")
 				}
 			}
 		})
diff --git a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/functional_test.go b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/functional_test.go
index 9dacc2a..7649b32 100644
--- a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/functional_test.go
+++ b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/functional_test.go
@@ -65,7 +65,7 @@ func runSelectQueueConformanceTests(t *testing.T, policy framework.InterFlowDisp
 	mockQueueEmpty := &frameworkmocks.MockFlowQueueAccessor{
 		LenV:         0,
 		PeekHeadErrV: framework.ErrQueueEmpty,
-		FlowSpecV:    types.FlowSpecification{ID: flowIDEmpty},
+		FlowKeyV:     types.FlowKey{ID: flowIDEmpty},
 	}
 
 	testCases := []struct {
@@ -84,7 +84,7 @@ func runSelectQueueConformanceTests(t *testing.T, policy framework.InterFlowDisp
 		{
 			name: "With an empty priority band accessor",
 			band: &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsFunc:       func() []string { return []string{} },
+				FlowKeysFunc:      func() []types.FlowKey { return []types.FlowKey{} },
 				IterateQueuesFunc: func(callback func(queue framework.FlowQueueAccessor) bool) { /* no-op */ },
 			},
 			expectErr: false,
@@ -93,7 +93,7 @@ func runSelectQueueConformanceTests(t *testing.T, policy framework.InterFlowDisp
 		{
 			name: "With a band that has one empty queue",
 			band: &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsFunc: func() []string { return []string{flowIDEmpty} },
+				FlowKeysFunc: func() []types.FlowKey { return []types.FlowKey{{ID: flowIDEmpty}} },
 				QueueFunc: func(fID string) framework.FlowQueueAccessor {
 					if fID == flowIDEmpty {
 						return mockQueueEmpty
@@ -107,7 +107,7 @@ func runSelectQueueConformanceTests(t *testing.T, policy framework.InterFlowDisp
 		{
 			name: "With a band that has multiple empty queues",
 			band: &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsFunc: func() []string { return []string{flowIDEmpty, "flow-empty-2"} },
+				FlowKeysFunc: func() []types.FlowKey { return []types.FlowKey{{ID: flowIDEmpty}, {ID: "flow-empty-2"}} },
 				QueueFunc: func(fID string) framework.FlowQueueAccessor {
 					return mockQueueEmpty
 				},
@@ -136,7 +136,7 @@ func runSelectQueueConformanceTests(t *testing.T, policy framework.InterFlowDisp
 			}
 
 			if tc.expectedQueue != nil {
-				assert.Equal(t, tc.expectedQueue.FlowSpec().ID, selectedQueue.FlowSpec().ID,
+				assert.Equal(t, tc.expectedQueue.FlowKey(), selectedQueue.FlowKey(),
 					"SelectQueue for policy %s returned an unexpected queue", policy.Name())
 			}
 		})
diff --git a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/roundrobin/roundrobin_test.go b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/roundrobin/roundrobin_test.go
index f2255f0..dd4a026 100644
--- a/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/roundrobin/roundrobin_test.go
+++ b/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/roundrobin/roundrobin_test.go
@@ -30,6 +30,12 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types"
 )
 
+var (
+	flow1Key = types.FlowKey{ID: "flow1", Priority: 0}
+	flow2Key = types.FlowKey{ID: "flow2", Priority: 0}
+	flow3Key = types.FlowKey{ID: "flow3", Priority: 0}
+)
+
 func TestRoundRobin_Name(t *testing.T) {
 	t.Parallel()
 	policy := newRoundRobin()
@@ -41,12 +47,12 @@ func TestRoundRobin_SelectQueue_Logic(t *testing.T) {
 	policy := newRoundRobin()
 
 	// Setup: Three non-empty queues
-	queue1 := &frameworkmocks.MockFlowQueueAccessor{LenV: 1, FlowSpecV: types.FlowSpecification{ID: "flow1"}}
-	queue2 := &frameworkmocks.MockFlowQueueAccessor{LenV: 2, FlowSpecV: types.FlowSpecification{ID: "flow2"}}
-	queue3 := &frameworkmocks.MockFlowQueueAccessor{LenV: 3, FlowSpecV: types.FlowSpecification{ID: "flow3"}}
+	queue1 := &frameworkmocks.MockFlowQueueAccessor{LenV: 1, FlowKeyV: flow1Key}
+	queue2 := &frameworkmocks.MockFlowQueueAccessor{LenV: 2, FlowKeyV: flow2Key}
+	queue3 := &frameworkmocks.MockFlowQueueAccessor{LenV: 3, FlowKeyV: flow3Key}
 
 	mockBand := &frameworkmocks.MockPriorityBandAccessor{
-		FlowIDsFunc: func() []string { return []string{"flow3", "flow1", "flow2"} }, // Unsorted to test sorting
+		FlowKeysFunc: func() []types.FlowKey { return []types.FlowKey{flow3Key, flow1Key, flow2Key} }, // Unsorted to test sorting
 		QueueFunc: func(id string) framework.FlowQueueAccessor {
 			switch id {
 			case "flow1":
@@ -60,7 +66,7 @@ func TestRoundRobin_SelectQueue_Logic(t *testing.T) {
 		},
 	}
 
-	// Expected order is based on sorted FlowIDs: flow1, flow2, flow3
+	// Expected order is based on sorted FlowKeys: flow1, flow2, flow3
 	expectedOrder := []string{"flow1", "flow2", "flow3"}
 
 	// First cycle
@@ -68,7 +74,7 @@ func TestRoundRobin_SelectQueue_Logic(t *testing.T) {
 		selected, err := policy.SelectQueue(mockBand)
 		require.NoError(t, err, "SelectQueue should not error on a valid band")
 		require.NotNil(t, selected, "SelectQueue should have selected a queue")
-		assert.Equal(t, expectedOrder[i], selected.FlowSpec().ID,
+		assert.Equal(t, expectedOrder[i], selected.FlowKey().ID,
 			"Cycle 1, selection %d should be %s", i+1, expectedOrder[i])
 	}
 
@@ -77,7 +83,7 @@ func TestRoundRobin_SelectQueue_Logic(t *testing.T) {
 		selected, err := policy.SelectQueue(mockBand)
 		require.NoError(t, err, "SelectQueue should not error on a valid band")
 		require.NotNil(t, selected, "SelectQueue should have selected a queue")
-		assert.Equal(t, expectedOrder[i], selected.FlowSpec().ID,
+		assert.Equal(t, expectedOrder[i], selected.FlowKey().ID,
 			"Cycle 2, selection %d should be %s", i+1, expectedOrder[i])
 	}
 }
@@ -87,12 +93,13 @@ func TestRoundRobin_SelectQueue_SkipsEmptyQueues(t *testing.T) {
 	policy := newRoundRobin()
 
 	// Setup: Two non-empty queues and one empty queue
-	queue1 := &frameworkmocks.MockFlowQueueAccessor{LenV: 1, FlowSpecV: types.FlowSpecification{ID: "flow1"}}
-	queueEmpty := &frameworkmocks.MockFlowQueueAccessor{LenV: 0, FlowSpecV: types.FlowSpecification{ID: "flowEmpty"}}
-	queue3 := &frameworkmocks.MockFlowQueueAccessor{LenV: 3, FlowSpecV: types.FlowSpecification{ID: "flow3"}}
+	flowEmptyKey := types.FlowKey{ID: "flowEmpty", Priority: 0}
+	queue1 := &frameworkmocks.MockFlowQueueAccessor{LenV: 1, FlowKeyV: flow1Key}
+	queueEmpty := &frameworkmocks.MockFlowQueueAccessor{LenV: 0, FlowKeyV: flowEmptyKey}
+	queue3 := &frameworkmocks.MockFlowQueueAccessor{LenV: 3, FlowKeyV: flow3Key}
 
 	mockBand := &frameworkmocks.MockPriorityBandAccessor{
-		FlowIDsFunc: func() []string { return []string{"flow1", "flowEmpty", "flow3"} },
+		FlowKeysFunc: func() []types.FlowKey { return []types.FlowKey{flow1Key, flowEmptyKey, flow3Key} },
 		QueueFunc: func(id string) framework.FlowQueueAccessor {
 			switch id {
 			case "flow1":
@@ -110,17 +117,17 @@ func TestRoundRobin_SelectQueue_SkipsEmptyQueues(t *testing.T) {
 	selected, err := policy.SelectQueue(mockBand)
 	require.NoError(t, err, "SelectQueue should not error when skipping queues")
 	require.NotNil(t, selected, "SelectQueue should select the first non-empty queue")
-	assert.Equal(t, "flow1", selected.FlowSpec().ID, "First selection should be flow1")
+	assert.Equal(t, "flow1", selected.FlowKey().ID, "First selection should be flow1")
 
 	selected, err = policy.SelectQueue(mockBand)
 	require.NoError(t, err, "SelectQueue should not error when skipping queues")
 	require.NotNil(t, selected, "SelectQueue should select the second non-empty queue")
-	assert.Equal(t, "flow3", selected.FlowSpec().ID, "Second selection should be flow3, skipping flowEmpty")
+	assert.Equal(t, "flow3", selected.FlowKey().ID, "Second selection should be flow3, skipping flowEmpty")
 
 	selected, err = policy.SelectQueue(mockBand)
 	require.NoError(t, err, "SelectQueue should not error when wrapping around")
 	require.NotNil(t, selected, "SelectQueue should wrap around and select a queue")
-	assert.Equal(t, "flow1", selected.FlowSpec().ID, "Should wrap around and select flow1 again")
+	assert.Equal(t, "flow1", selected.FlowKey().ID, "Should wrap around and select flow1 again")
 }
 
 func TestRoundRobin_SelectQueue_HandlesDynamicFlows(t *testing.T) {
@@ -128,10 +135,10 @@ func TestRoundRobin_SelectQueue_HandlesDynamicFlows(t *testing.T) {
 	policy := newRoundRobin()
 
 	// Initial setup
-	queue1 := &frameworkmocks.MockFlowQueueAccessor{LenV: 1, FlowSpecV: types.FlowSpecification{ID: "flow1"}}
-	queue2 := &frameworkmocks.MockFlowQueueAccessor{LenV: 1, FlowSpecV: types.FlowSpecification{ID: "flow2"}}
+	queue1 := &frameworkmocks.MockFlowQueueAccessor{LenV: 1, FlowKeyV: flow1Key}
+	queue2 := &frameworkmocks.MockFlowQueueAccessor{LenV: 1, FlowKeyV: flow2Key}
 	mockBand := &frameworkmocks.MockPriorityBandAccessor{
-		FlowIDsFunc: func() []string { return []string{"flow1", "flow2"} },
+		FlowKeysFunc: func() []types.FlowKey { return []types.FlowKey{flow1Key, flow2Key} },
 		QueueFunc: func(id string) framework.FlowQueueAccessor {
 			if id == "flow1" {
 				return queue1
@@ -144,11 +151,11 @@ func TestRoundRobin_SelectQueue_HandlesDynamicFlows(t *testing.T) {
 	selected, err := policy.SelectQueue(mockBand)
 	require.NoError(t, err, "SelectQueue should not error on initial selection")
 	require.NotNil(t, selected, "SelectQueue should select a queue initially")
-	assert.Equal(t, "flow1", selected.FlowSpec().ID, "First selection should be flow1")
+	assert.Equal(t, "flow1", selected.FlowKey().ID, "First selection should be flow1")
 
 	// --- Simulate adding a flow ---
-	queue3 := &frameworkmocks.MockFlowQueueAccessor{LenV: 1, FlowSpecV: types.FlowSpecification{ID: "flow3"}}
-	mockBand.FlowIDsFunc = func() []string { return []string{"flow1", "flow2", "flow3"} }
+	queue3 := &frameworkmocks.MockFlowQueueAccessor{LenV: 1, FlowKeyV: flow3Key}
+	mockBand.FlowKeysFunc = func() []types.FlowKey { return []types.FlowKey{flow1Key, flow2Key, flow3Key} }
 	mockBand.QueueFunc = func(id string) framework.FlowQueueAccessor {
 		switch id {
 		case "flow1":
@@ -165,22 +172,22 @@ func TestRoundRobin_SelectQueue_HandlesDynamicFlows(t *testing.T) {
 	selected, err = policy.SelectQueue(mockBand)
 	require.NoError(t, err, "SelectQueue should not error after adding a flow")
 	require.NotNil(t, selected, "SelectQueue should select a queue after adding a flow")
-	assert.Equal(t, "flow2", selected.FlowSpec().ID, "Next selection should be flow2")
+	assert.Equal(t, "flow2", selected.FlowKey().ID, "Next selection should be flow2")
 
 	// Next selection should be flow3
 	selected, err = policy.SelectQueue(mockBand)
 	require.NoError(t, err, "SelectQueue should not error on the third selection")
 	require.NotNil(t, selected, "SelectQueue should select the new flow")
-	assert.Equal(t, "flow3", selected.FlowSpec().ID, "Next selection should be the new flow3")
+	assert.Equal(t, "flow3", selected.FlowKey().ID, "Next selection should be the new flow3")
 
 	// --- Simulate removing a flow ---
-	mockBand.FlowIDsFunc = func() []string { return []string{"flow1", "flow3"} } // flow2 is removed
+	mockBand.FlowKeysFunc = func() []types.FlowKey { return []types.FlowKey{flow1Key, flow3Key} } // flow2 is removed
 
 	// Next selection should wrap around and pick flow1
 	selected, err = policy.SelectQueue(mockBand)
 	require.NoError(t, err, "SelectQueue should not error after removing a flow")
 	require.NotNil(t, selected, "SelectQueue should select a queue after removing a flow")
-	assert.Equal(t, "flow1", selected.FlowSpec().ID, "Next selection should wrap around to flow1 after a removal")
+	assert.Equal(t, "flow1", selected.FlowKey().ID, "Next selection should wrap around to flow1 after a removal")
 }
 
 func TestRoundRobin_SelectQueue_Concurrency(t *testing.T) {
@@ -193,17 +200,17 @@ func TestRoundRobin_SelectQueue_Concurrency(t *testing.T) {
 
 			// Setup: Three non-empty queues
 			queues := []*frameworkmocks.MockFlowQueueAccessor{
-				{LenV: 1, FlowSpecV: types.FlowSpecification{ID: "flow1"}},
-				{LenV: 2, FlowSpecV: types.FlowSpecification{ID: "flow2"}},
-				{LenV: 3, FlowSpecV: types.FlowSpecification{ID: "flow3"}},
+				{LenV: 1, FlowKeyV: flow1Key},
+				{LenV: 2, FlowKeyV: flow2Key},
+				{LenV: 3, FlowKeyV: flow3Key},
 			}
 			numQueues := int64(len(queues))
 
 			mockBand := &frameworkmocks.MockPriorityBandAccessor{
-				FlowIDsFunc: func() []string { return []string{"flow1", "flow2", "flow3"} },
+				FlowKeysFunc: func() []types.FlowKey { return []types.FlowKey{flow1Key, flow2Key, flow3Key} },
 				QueueFunc: func(id string) framework.FlowQueueAccessor {
 					for _, q := range queues {
-						if q.FlowSpec().ID == id {
+						if q.FlowKey().ID == id {
 							return q
 						}
 					}
@@ -225,7 +232,7 @@ func TestRoundRobin_SelectQueue_Concurrency(t *testing.T) {
 					for range selectionsPerGoroutine {
 						selected, err := policy.SelectQueue(mockBand)
 						if err == nil && selected != nil {
-							val, _ := selectionCounts.LoadOrStore(selected.FlowSpec().ID, new(atomic.Int64))
+							val, _ := selectionCounts.LoadOrStore(selected.FlowKey().ID, new(atomic.Int64))
 							val.(*atomic.Int64).Add(1)
 						}
 					}
diff --git a/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs/fcfs_test.go b/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs/fcfs_test.go
index 8a13c6c..45c1442 100644
--- a/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs/fcfs_test.go
+++ b/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs/fcfs_test.go
@@ -29,6 +29,8 @@ import (
 	typesmocks "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types/mocks"
 )
 
+var testFlowKey = types.FlowKey{ID: "test-flow", Priority: 0}
+
 func TestFCFS_Name(t *testing.T) {
 	t.Parallel()
 	policy := newFCFS()
@@ -49,7 +51,7 @@ func TestFCFS_SelectItem(t *testing.T) {
 	// This unit test focuses on the policy-specific success path.
 	policy := newFCFS()
 
-	mockItem := typesmocks.NewMockQueueItemAccessor(1, "item1", "flow1")
+	mockItem := typesmocks.NewMockQueueItemAccessor(1, "item1", testFlowKey)
 	mockQueue := &frameworkmocks.MockFlowQueueAccessor{
 		PeekHeadV: mockItem,
 		LenV:      1,
@@ -67,13 +69,13 @@ func TestEnqueueTimeComparator_Func(t *testing.T) {
 	require.NotNil(t, compareFunc)
 
 	now := time.Now()
-	itemA := typesmocks.NewMockQueueItemAccessor(10, "itemA", "test-flow")
+	itemA := typesmocks.NewMockQueueItemAccessor(10, "itemA", testFlowKey)
 	itemA.EnqueueTimeV = now
 
-	itemB := typesmocks.NewMockQueueItemAccessor(20, "itemB", "test-flow")
+	itemB := typesmocks.NewMockQueueItemAccessor(20, "itemB", testFlowKey)
 	itemB.EnqueueTimeV = now.Add(time.Second) // B is later than A
 
-	itemC := typesmocks.NewMockQueueItemAccessor(30, "itemC", "test-flow")
+	itemC := typesmocks.NewMockQueueItemAccessor(30, "itemC", testFlowKey)
 	itemC.EnqueueTimeV = now // C is same time as A
 
 	testCases := []struct {
diff --git a/pkg/epp/flowcontrol/framework/plugins/queue/benchmark_test.go b/pkg/epp/flowcontrol/framework/plugins/queue/benchmark_test.go
index 916cacd..7d20270 100644
--- a/pkg/epp/flowcontrol/framework/plugins/queue/benchmark_test.go
+++ b/pkg/epp/flowcontrol/framework/plugins/queue/benchmark_test.go
@@ -27,6 +27,8 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types/mocks"
 )
 
+var benchmarkFlowKey = types.FlowKey{ID: "benchmark-flow"}
+
 // BenchmarkQueues runs a series of benchmarks against all registered queue implementations.
 func BenchmarkQueues(b *testing.B) {
 	for queueName, constructor := range queue.RegisteredQueues {
@@ -68,7 +70,7 @@ func benchmarkAddRemove(b *testing.B, q framework.SafeQueue) {
 
 	b.RunParallel(func(pb *testing.PB) {
 		for pb.Next() {
-			item := mocks.NewMockQueueItemAccessor(1, "item", "benchmark-flow")
+			item := mocks.NewMockQueueItemAccessor(1, "item", benchmarkFlowKey)
 			err := q.Add(item)
 			if err != nil {
 				b.Fatalf("Add failed: %v", err)
@@ -85,7 +87,7 @@ func benchmarkAddRemove(b *testing.B, q framework.SafeQueue) {
 // common consumer pattern where a single worker peeks at an item before deciding to process and remove it.
 func benchmarkAddPeekRemove(b *testing.B, q framework.SafeQueue) {
 	// Pre-add one item so PeekHead doesn't fail on the first iteration.
-	initialItem := mocks.NewMockQueueItemAccessor(1, "initial", "benchmark-flow")
+	initialItem := mocks.NewMockQueueItemAccessor(1, "initial", benchmarkFlowKey)
 	if err := q.Add(initialItem); err != nil {
 		b.Fatalf("Failed to add initial item: %v", err)
 	}
@@ -93,7 +95,7 @@ func benchmarkAddPeekRemove(b *testing.B, q framework.SafeQueue) {
 	b.ReportAllocs()
 
 	for i := 0; i < b.N; i++ {
-		item := mocks.NewMockQueueItemAccessor(1, "item", "benchmark-flow")
+		item := mocks.NewMockQueueItemAccessor(1, "item", benchmarkFlowKey)
 		err := q.Add(item)
 		if err != nil {
 			b.Fatalf("Add failed: %v", err)
@@ -122,7 +124,7 @@ func benchmarkBulkAddThenBulkRemove(b *testing.B, q framework.SafeQueue) {
 		// Add a batch of items
 		items := make([]types.QueueItemAccessor, 100)
 		for j := range items {
-			item := mocks.NewMockQueueItemAccessor(1, fmt.Sprintf("bulk-%d-%d", i, j), "benchmark-flow")
+			item := mocks.NewMockQueueItemAccessor(1, fmt.Sprintf("bulk-%d-%d", i, j), benchmarkFlowKey)
 			items[j] = item
 			if err := q.Add(item); err != nil {
 				b.Fatalf("Add failed: %v", err)
@@ -146,7 +148,7 @@ func benchmarkBulkAddThenBulkRemove(b *testing.B, q framework.SafeQueue) {
 // understanding the performance of accessing the lowest-priority item.
 func benchmarkAddPeekTailRemove(b *testing.B, q framework.SafeQueue) {
 	// Pre-add one item so PeekTail doesn't fail on the first iteration.
-	initialItem := mocks.NewMockQueueItemAccessor(1, "initial", "benchmark-flow")
+	initialItem := mocks.NewMockQueueItemAccessor(1, "initial", benchmarkFlowKey)
 	if err := q.Add(initialItem); err != nil {
 		b.Fatalf("Failed to add initial item: %v", err)
 	}
@@ -154,7 +156,7 @@ func benchmarkAddPeekTailRemove(b *testing.B, q framework.SafeQueue) {
 	b.ReportAllocs()
 
 	for i := 0; i < b.N; i++ {
-		item := mocks.NewMockQueueItemAccessor(1, "item", "benchmark-flow")
+		item := mocks.NewMockQueueItemAccessor(1, "item", benchmarkFlowKey)
 		err := q.Add(item)
 		if err != nil {
 			b.Fatalf("Add failed: %v", err)
@@ -177,7 +179,7 @@ func benchmarkAddPeekTailRemove(b *testing.B, q framework.SafeQueue) {
 func benchmarkHighContention(b *testing.B, q framework.SafeQueue) {
 	// Pre-fill the queue to ensure consumers have work to do immediately.
 	for i := range 1000 {
-		item := mocks.NewMockQueueItemAccessor(1, fmt.Sprintf("prefill-%d", i), "benchmark-flow")
+		item := mocks.NewMockQueueItemAccessor(1, fmt.Sprintf("prefill-%d", i), benchmarkFlowKey)
 		if err := q.Add(item); err != nil {
 			b.Fatalf("Failed to pre-fill queue: %v", err)
 		}
@@ -196,7 +198,7 @@ func benchmarkHighContention(b *testing.B, q framework.SafeQueue) {
 				case <-stopCh:
 					return
 				default:
-					item := mocks.NewMockQueueItemAccessor(1, "item", "benchmark-flow")
+					item := mocks.NewMockQueueItemAccessor(1, "item", benchmarkFlowKey)
 					_ = q.Add(item)
 				}
 			}
diff --git a/pkg/epp/flowcontrol/framework/plugins/queue/functional_test.go b/pkg/epp/flowcontrol/framework/plugins/queue/functional_test.go
index fe01053..d6ff8a2 100644
--- a/pkg/epp/flowcontrol/framework/plugins/queue/functional_test.go
+++ b/pkg/epp/flowcontrol/framework/plugins/queue/functional_test.go
@@ -184,7 +184,7 @@ func TestQueueConformance(t *testing.T) {
 	for queueName, constructor := range queue.RegisteredQueues {
 		t.Run(string(queueName), func(t *testing.T) {
 			t.Parallel()
-			flowSpec := &types.FlowSpecification{ID: "test-flow-1", Priority: 0}
+			flowKey := types.FlowKey{ID: "test-flow-1", Priority: 0}
 
 			t.Run("Initialization", func(t *testing.T) {
 				t.Parallel()
@@ -206,11 +206,11 @@ func TestQueueConformance(t *testing.T) {
 
 				now := time.Now()
 
-				item1 := typesmocks.NewMockQueueItemAccessor(100, "item1_fifo", flowSpec.ID)
+				item1 := typesmocks.NewMockQueueItemAccessor(100, "item1_fifo", flowKey)
 				item1.EnqueueTimeV = now.Add(-2 * time.Second) // Earliest
-				item2 := typesmocks.NewMockQueueItemAccessor(50, "item2_fifo", flowSpec.ID)
+				item2 := typesmocks.NewMockQueueItemAccessor(50, "item2_fifo", flowKey)
 				item2.EnqueueTimeV = now.Add(-1 * time.Second) // Middle
-				item3 := typesmocks.NewMockQueueItemAccessor(20, "item3_fifo", flowSpec.ID)
+				item3 := typesmocks.NewMockQueueItemAccessor(20, "item3_fifo", flowKey)
 				item3.EnqueueTimeV = now // Latest
 
 				itemsInFIFOOrder := []*typesmocks.MockQueueItemAccessor{item1, item2, item3}
@@ -224,9 +224,9 @@ func TestQueueConformance(t *testing.T) {
 					q, err := constructor(byteSizeComparator)
 					require.NoError(t, err, "Setup: creating queue with byteSizeComparator should not fail")
 
-					itemLarge := typesmocks.NewMockQueueItemAccessor(100, "itemLarge_prio", flowSpec.ID)
-					itemSmall := typesmocks.NewMockQueueItemAccessor(20, "itemSmall_prio", flowSpec.ID)
-					itemMedium := typesmocks.NewMockQueueItemAccessor(50, "itemMedium_prio", flowSpec.ID)
+					itemLarge := typesmocks.NewMockQueueItemAccessor(100, "itemLarge_prio", flowKey)
+					itemSmall := typesmocks.NewMockQueueItemAccessor(20, "itemSmall_prio", flowKey)
+					itemMedium := typesmocks.NewMockQueueItemAccessor(50, "itemMedium_prio", flowKey)
 
 					itemsInByteSizeOrder := []*typesmocks.MockQueueItemAccessor{itemSmall, itemMedium, itemLarge}
 					testLifecycleAndOrdering(t, q, itemsInByteSizeOrder, "PriorityByteSize")
@@ -238,11 +238,11 @@ func TestQueueConformance(t *testing.T) {
 					require.NoError(t, err, "Setup: creating queue with reverseEnqueueTimeComparator should not fail")
 
 					now := time.Now()
-					item1 := typesmocks.NewMockQueueItemAccessor(100, "item1_lifo", flowSpec.ID)
+					item1 := typesmocks.NewMockQueueItemAccessor(100, "item1_lifo", flowKey)
 					item1.EnqueueTimeV = now.Add(-2 * time.Second) // Earliest
-					item2 := typesmocks.NewMockQueueItemAccessor(50, "item2_lifo", flowSpec.ID)
+					item2 := typesmocks.NewMockQueueItemAccessor(50, "item2_lifo", flowKey)
 					item2.EnqueueTimeV = now.Add(-1 * time.Second) // Middle
-					item3 := typesmocks.NewMockQueueItemAccessor(20, "item3_lifo", flowSpec.ID)
+					item3 := typesmocks.NewMockQueueItemAccessor(20, "item3_lifo", flowKey)
 					item3.EnqueueTimeV = now // Latest
 
 					itemsInLIFOOrder := []*typesmocks.MockQueueItemAccessor{item3, item2, item1}
@@ -268,13 +268,13 @@ func TestQueueConformance(t *testing.T) {
 				q, err := constructor(enqueueTimeComparator)
 				require.NoError(t, err, "Setup: creating queue for test should not fail")
 
-				item := typesmocks.NewMockQueueItemAccessor(100, "item", flowSpec.ID)
+				item := typesmocks.NewMockQueueItemAccessor(100, "item", flowKey)
 				err = q.Add(item)
 				require.NoError(t, err, "Setup: adding an item should succeed")
 
 				otherQ, err := constructor(enqueueTimeComparator) // A different queue instance
 				require.NoError(t, err, "Setup: creating otherQ should succeed")
-				otherItem := typesmocks.NewMockQueueItemAccessor(10, "other_item", "other_flow")
+				otherItem := typesmocks.NewMockQueueItemAccessor(10, "other_item", types.FlowKey{ID: "other-flow"})
 				err = otherQ.Add(otherItem)
 				require.NoError(t, err, "Setup: adding item to otherQ should succeed")
 				alienHandle := otherItem.Handle()
@@ -318,11 +318,11 @@ func TestQueueConformance(t *testing.T) {
 				require.NoError(t, err, "Setup: creating queue for test should not fail")
 
 				now := time.Now()
-				item1 := typesmocks.NewMockQueueItemAccessor(10, "item1_nonhead", flowSpec.ID)
+				item1 := typesmocks.NewMockQueueItemAccessor(10, "item1_nonhead", flowKey)
 				item1.EnqueueTimeV = now.Add(-3 * time.Second)
-				item2 := typesmocks.NewMockQueueItemAccessor(20, "item2_nonhead_TARGET", flowSpec.ID)
+				item2 := typesmocks.NewMockQueueItemAccessor(20, "item2_nonhead_TARGET", flowKey)
 				item2.EnqueueTimeV = now.Add(-2 * time.Second)
-				item3 := typesmocks.NewMockQueueItemAccessor(30, "item3_nonhead", flowSpec.ID)
+				item3 := typesmocks.NewMockQueueItemAccessor(30, "item3_nonhead", flowKey)
 				item3.EnqueueTimeV = now.Add(-1 * time.Second)
 
 				_ = q.Add(item1)
@@ -364,8 +364,8 @@ func TestQueueConformance(t *testing.T) {
 			t.Run("Cleanup_PredicateMatchesNone", func(t *testing.T) {
 				t.Parallel()
 				q, _ := constructor(enqueueTimeComparator)
-				itemK1 := typesmocks.NewMockQueueItemAccessor(10, "k1_matchNone", flowSpec.ID)
-				itemK2 := typesmocks.NewMockQueueItemAccessor(12, "k2_matchNone", flowSpec.ID)
+				itemK1 := typesmocks.NewMockQueueItemAccessor(10, "k1_matchNone", flowKey)
+				itemK2 := typesmocks.NewMockQueueItemAccessor(12, "k2_matchNone", flowKey)
 				_ = q.Add(itemK1)
 				_ = q.Add(itemK2)
 				initialLen := q.Len()
@@ -384,8 +384,8 @@ func TestQueueConformance(t *testing.T) {
 			t.Run("Cleanup_PredicateMatchesAll", func(t *testing.T) {
 				t.Parallel()
 				q, _ := constructor(enqueueTimeComparator)
-				itemR1 := typesmocks.NewMockQueueItemAccessor(11, "r1_matchAll", flowSpec.ID)
-				itemR2 := typesmocks.NewMockQueueItemAccessor(13, "r2_matchAll", flowSpec.ID)
+				itemR1 := typesmocks.NewMockQueueItemAccessor(11, "r1_matchAll", flowKey)
+				itemR2 := typesmocks.NewMockQueueItemAccessor(13, "r2_matchAll", flowKey)
 				_ = q.Add(itemR1)
 				_ = q.Add(itemR2)
 
@@ -401,10 +401,10 @@ func TestQueueConformance(t *testing.T) {
 			t.Run("Cleanup_PredicateMatchesSubset_VerifyHandles", func(t *testing.T) {
 				t.Parallel()
 				q, _ := constructor(enqueueTimeComparator)
-				iK1 := typesmocks.NewMockQueueItemAccessor(20, "k1_subset", flowSpec.ID)
-				iR1 := typesmocks.NewMockQueueItemAccessor(11, "r1_subset", flowSpec.ID)
-				iK2 := typesmocks.NewMockQueueItemAccessor(22, "k2_subset", flowSpec.ID)
-				iR2 := typesmocks.NewMockQueueItemAccessor(33, "r2_subset", flowSpec.ID)
+				iK1 := typesmocks.NewMockQueueItemAccessor(20, "k1_subset", flowKey)
+				iR1 := typesmocks.NewMockQueueItemAccessor(11, "r1_subset", flowKey)
+				iK2 := typesmocks.NewMockQueueItemAccessor(22, "k2_subset", flowKey)
+				iR2 := typesmocks.NewMockQueueItemAccessor(33, "r2_subset", flowKey)
 				_ = q.Add(iK1)
 				_ = q.Add(iR1)
 				_ = q.Add(iK2)
@@ -453,8 +453,8 @@ func TestQueueConformance(t *testing.T) {
 				q, err := constructor(enqueueTimeComparator)
 				require.NoError(t, err, "Setup: creating queue for drain test should not fail")
 
-				itemD1 := typesmocks.NewMockQueueItemAccessor(10, "ditem1", flowSpec.ID)
-				itemD2 := typesmocks.NewMockQueueItemAccessor(20, "ditem2", flowSpec.ID)
+				itemD1 := typesmocks.NewMockQueueItemAccessor(10, "ditem1", flowKey)
+				itemD2 := typesmocks.NewMockQueueItemAccessor(20, "ditem2", flowKey)
 				_ = q.Add(itemD1)
 				_ = q.Add(itemD2)
 
@@ -512,7 +512,7 @@ func TestQueueConformance(t *testing.T) {
 
 				// Pre-populate the queue with an initial set of items.
 				for i := 0; i < initialItems; i++ {
-					item := typesmocks.NewMockQueueItemAccessor(1, fmt.Sprintf("%s_conc_init_%d", flowSpec.ID, i), flowSpec.ID)
+					item := typesmocks.NewMockQueueItemAccessor(1, fmt.Sprintf("%s_conc_init_%d", flowKey, i), flowKey)
 					err := q.Add(item)
 					require.NoError(t, err, "Setup: pre-populating the queue should not fail")
 					handleChan <- item.Handle()
@@ -531,7 +531,7 @@ func TestQueueConformance(t *testing.T) {
 							switch opType {
 							case 0: // Add
 								item := typesmocks.NewMockQueueItemAccessor(1,
-									fmt.Sprintf("%s_conc_init_%d_%d", flowSpec.ID, routineID, j), flowSpec.ID)
+									fmt.Sprintf("%s_conc_init_%d_%d", flowKey, routineID, j), flowKey)
 								err := q.Add(item)
 								if assert.NoError(t, err, "Add must be goroutine-safe") {
 									successfulAdds.Add(1)
diff --git a/pkg/epp/flowcontrol/framework/plugins/queue/maxminheap/maxminheap_test.go b/pkg/epp/flowcontrol/framework/plugins/queue/maxminheap/maxminheap_test.go
index 39e82fb..22a6290 100644
--- a/pkg/epp/flowcontrol/framework/plugins/queue/maxminheap/maxminheap_test.go
+++ b/pkg/epp/flowcontrol/framework/plugins/queue/maxminheap/maxminheap_test.go
@@ -46,7 +46,7 @@ func TestMaxMinHeap_InternalProperty(t *testing.T) {
 	now := time.Now()
 	for i := range items {
 		// Add items in a somewhat random order of enqueue times
-		items[i] = typesmocks.NewMockQueueItemAccessor(10, "item", "flow")
+		items[i] = typesmocks.NewMockQueueItemAccessor(10, "item", types.FlowKey{ID: "flow"})
 		items[i].EnqueueTimeV = now.Add(time.Duration((i%5-2)*10) * time.Second)
 		err := q.Add(items[i])
 		require.NoError(t, err, "Add should not fail")
diff --git a/pkg/epp/flowcontrol/registry/config_test.go b/pkg/epp/flowcontrol/registry/config_test.go
index 0cf9f93..376282f 100644
--- a/pkg/epp/flowcontrol/registry/config_test.go
+++ b/pkg/epp/flowcontrol/registry/config_test.go
@@ -18,7 +18,9 @@ package registry
 
 import (
 	"errors"
+	"fmt"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -27,120 +29,147 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/mocks"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/besthead"
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/roundrobin"
 	intra "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/queue"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/queue/listqueue"
 )
 
-func TestConfig_ValidateAndApplyDefaults(t *testing.T) {
+func TestConfig_NewConfig(t *testing.T) {
 	t.Parallel()
 
-	// Setup for failure injection tests
-	failingPolicyName := intra.RegisteredPolicyName("failing-policy-for-config-test")
-	intra.MustRegisterPolicy(failingPolicyName, func() (framework.IntraFlowDispatchPolicy, error) {
-		return nil, errors.New("policy instantiation failed")
-	})
-	failingQueueName := queue.RegisteredQueueName("failing-queue-for-config-test")
-	queue.MustRegisterQueue(failingQueueName, func(_ framework.ItemComparator) (framework.SafeQueue, error) {
-		return nil, errors.New("queue instantiation failed")
-	})
-
-	// Setup a mock policy with a specific capability requirement to test the compatibility check.
-	const mockCapability = framework.QueueCapability("TEST_CAPABILITY_FOR_CONFIG")
-	policyWithReqName := intra.RegisteredPolicyName("policy-with-req-for-config-test")
-	intra.MustRegisterPolicy(policyWithReqName, func() (framework.IntraFlowDispatchPolicy, error) {
-		return &mocks.MockIntraFlowDispatchPolicy{
-			NameV: string(policyWithReqName),
-			RequiredQueueCapabilitiesV: []framework.QueueCapability{
-				mockCapability,
-			},
-		}, nil
-	})
-
 	testCases := []struct {
 		name          string
-		input         *Config
+		input         Config
+		opts          []configOption
 		expectErr     bool
 		expectedErrIs error
-		expectedCfg   *Config
+		assertion     func(*testing.T, Config, *Config)
 	}{
+		// --- Success Paths ---
 		{
-			name: "Valid config with missing defaults",
-			input: &Config{
+			name: "ShouldApplyDefaults_WhenFieldsAreUnspecified",
+			input: Config{
 				PriorityBands: []PriorityBandConfig{
 					{Priority: 1, PriorityName: "High"},
-					{Priority: 2, PriorityName: "Low", InterFlowDispatchPolicy: roundrobin.RoundRobinPolicyName},
+					{Priority: 2, PriorityName: "Low", MaxBytes: 500},
 				},
 			},
-			expectErr: false,
-			expectedCfg: &Config{
-				PriorityBands: []PriorityBandConfig{
-					{
-						Priority:                1,
-						PriorityName:            "High",
-						IntraFlowDispatchPolicy: fcfs.FCFSPolicyName,
-						InterFlowDispatchPolicy: besthead.BestHeadPolicyName,
-						Queue:                   listqueue.ListQueueName,
-					},
-					{
-						Priority:                2,
-						PriorityName:            "Low",
-						IntraFlowDispatchPolicy: fcfs.FCFSPolicyName,
-						InterFlowDispatchPolicy: roundrobin.RoundRobinPolicyName,
-						Queue:                   listqueue.ListQueueName,
-					},
-				},
+			assertion: func(t *testing.T, _ Config, newCfg *Config) {
+				assert.Equal(t, defaultInitialShardCount, newCfg.InitialShardCount, "InitialShardCount should be defaulted")
+				assert.Equal(t, defaultFlowGCTimeout, newCfg.FlowGCTimeout, "FlowGCTimeout should be defaulted")
+				assert.Equal(t, defaultEventChannelBufferSize, newCfg.EventChannelBufferSize,
+					"EventChannelBufferSize should be defaulted")
+				require.Len(t, newCfg.PriorityBands, 2, "Should have two priority bands")
+				band1 := newCfg.PriorityBands[0]
+				assert.Equal(t, defaultIntraFlowDispatchPolicy, band1.IntraFlowDispatchPolicy,
+					"Band 1 IntraFlowDispatchPolicy should be defaulted")
+				assert.Equal(t, defaultInterFlowDispatchPolicy, band1.InterFlowDispatchPolicy,
+					"Band 1 InterFlowDispatchPolicy should be defaulted")
+				assert.Equal(t, defaultQueue, band1.Queue, "Band 1 Queue should be defaulted")
+				assert.Equal(t, defaultPriorityBandMaxBytes, band1.MaxBytes, "Band 1 MaxBytes should be defaulted")
+				band2 := newCfg.PriorityBands[1]
+				assert.Equal(t, uint64(500), band2.MaxBytes, "Band 2 MaxBytes should retain its specified value")
 			},
 		},
 		{
-			name: "Config with all fields specified and compatible",
-			input: &Config{
-				MaxBytes: 1000,
-				PriorityBands: []PriorityBandConfig{
-					{
-						Priority:                1,
-						PriorityName:            "High",
-						IntraFlowDispatchPolicy: fcfs.FCFSPolicyName, // Compatible with ListQueue
-						InterFlowDispatchPolicy: besthead.BestHeadPolicyName,
-						Queue:                   listqueue.ListQueueName,
-						MaxBytes:                500,
-					},
-				},
+			name: "ShouldRemainUnchanged_WhenConfigIsFullySpecifiedAndValid",
+			input: Config{
+				MaxBytes:               1000,
+				InitialShardCount:      10,
+				FlowGCTimeout:          10 * time.Minute,
+				EventChannelBufferSize: 5000,
+				PriorityBands: []PriorityBandConfig{{
+					Priority:                1,
+					PriorityName:            "Critical",
+					IntraFlowDispatchPolicy: fcfs.FCFSPolicyName,
+					InterFlowDispatchPolicy: besthead.BestHeadPolicyName,
+					Queue:                   listqueue.ListQueueName,
+					MaxBytes:                500,
+				}},
+			},
+			assertion: func(t *testing.T, originalCfg Config, newCfg *Config) {
+				assert.Equal(t, originalCfg.MaxBytes, newCfg.MaxBytes, "MaxBytes should remain unchanged")
+				assert.Equal(t, originalCfg.InitialShardCount, newCfg.InitialShardCount,
+					"InitialShardCount should remain unchanged")
+				assert.Equal(t, originalCfg.FlowGCTimeout, newCfg.FlowGCTimeout, "FlowGCTimeout should remain unchanged")
+				assert.Equal(t, originalCfg.EventChannelBufferSize, newCfg.EventChannelBufferSize,
+					"EventChannelBufferSize should remain unchanged")
+				assert.Equal(t, originalCfg.PriorityBands, newCfg.PriorityBands, "PriorityBands should remain unchanged")
+			},
+		},
+		{
+			name: "ShouldSucceed_WhenPolicyHasNoRequirements",
+			input: Config{
+				PriorityBands: []PriorityBandConfig{{
+					Priority:                1,
+					PriorityName:            "High",
+					IntraFlowDispatchPolicy: intra.RegisteredPolicyName("policy-without-req"),
+				}},
+			},
+			opts: []configOption{
+				withIntraFlowDispatchPolicyFactory(
+					func(_ intra.RegisteredPolicyName) (framework.IntraFlowDispatchPolicy, error) {
+						return &mocks.MockIntraFlowDispatchPolicy{
+							NameV:                      "policy-without-req",
+							RequiredQueueCapabilitiesV: []framework.QueueCapability{},
+						}, nil
+					}),
 			},
 			expectErr: false,
-			expectedCfg: &Config{ // Should be unchanged
-				MaxBytes: 1000,
-				PriorityBands: []PriorityBandConfig{
-					{
-						Priority:                1,
-						PriorityName:            "High",
-						IntraFlowDispatchPolicy: fcfs.FCFSPolicyName,
-						InterFlowDispatchPolicy: besthead.BestHeadPolicyName,
-						Queue:                   listqueue.ListQueueName,
-						MaxBytes:                500,
-					},
-				},
+		},
+		{
+			name: "ShouldSucceed_WhenPolicyAndQueueAreCompatible",
+			input: Config{
+				PriorityBands: []PriorityBandConfig{{
+					Priority:                1,
+					PriorityName:            "High",
+					IntraFlowDispatchPolicy: intra.RegisteredPolicyName("policy-with-reqs"),
+					Queue:                   queue.RegisteredQueueName("queue-with-reqs-capability"),
+				}},
+			},
+			opts: []configOption{
+				withIntraFlowDispatchPolicyFactory(
+					func(_ intra.RegisteredPolicyName) (framework.IntraFlowDispatchPolicy, error) {
+						return &mocks.MockIntraFlowDispatchPolicy{
+							RequiredQueueCapabilitiesV: []framework.QueueCapability{"capability-A", "capability-B"},
+						}, nil
+					}),
+				withQueueFactory(
+					func(_ queue.RegisteredQueueName, _ framework.ItemComparator) (framework.SafeQueue, error) {
+						return &mocks.MockSafeQueue{
+							CapabilitiesV: []framework.QueueCapability{"capability-A", "capability-B", "capability-C"},
+						}, nil
+					}),
+			},
+			expectErr: false,
+		},
+		// --- Core Behavioral Contracts ---
+		{
+			name: "ShouldReturnDeepCopy_AndNotMutateInput",
+			input: Config{
+				PriorityBands: []PriorityBandConfig{{Priority: 1, PriorityName: "High"}},
+			},
+			assertion: func(t *testing.T, originalCfg Config, newCfg *Config) {
+				newCfg.PriorityBands[0].PriorityName = "changed"
+				assert.NotEqual(t, originalCfg.PriorityBands[0].PriorityName, newCfg.PriorityBands[0].PriorityName,
+					"Original config should not be mutated by changes to the new config")
 			},
 		},
+		// --- Input Validation Errors ---
 		{
-			name:      "Error: No priority bands",
-			input:     &Config{PriorityBands: []PriorityBandConfig{}},
+			name:      "ShouldError_WhenNoPriorityBandsAreDefined",
+			input:     Config{PriorityBands: []PriorityBandConfig{}},
 			expectErr: true,
 		},
 		{
-			name: "Error: Missing PriorityName",
-			input: &Config{
-				PriorityBands: []PriorityBandConfig{
-					{Priority: 1},
-				},
-			},
+			name:      "ShouldError_WhenPriorityNameIsMissing",
+			input:     Config{PriorityBands: []PriorityBandConfig{{Priority: 1}}},
 			expectErr: true,
 		},
 		{
-			name: "Error: Duplicate priority level",
-			input: &Config{
+			name: "ShouldError_WhenPriorityLevelsAreDuplicated",
+			input: Config{
 				PriorityBands: []PriorityBandConfig{
 					{Priority: 1, PriorityName: "High"},
 					{Priority: 1, PriorityName: "Also High"},
@@ -149,62 +178,118 @@ func TestConfig_ValidateAndApplyDefaults(t *testing.T) {
 			expectErr: true,
 		},
 		{
-			name: "Error: Incompatible policy and queue",
-			input: &Config{
+			name: "ShouldError_WhenPriorityNamesAreDuplicated",
+			input: Config{
 				PriorityBands: []PriorityBandConfig{
-					{
-						Priority:                1,
-						PriorityName:            "High",
-						IntraFlowDispatchPolicy: policyWithReqName,       // Requires mock capability
-						Queue:                   listqueue.ListQueueName, // Does not provide it
-					},
+					{Priority: 1, PriorityName: "High"},
+					{Priority: 2, PriorityName: "High"},
 				},
 			},
+			expectErr: true,
+		},
+		// --- Plugin and Compatibility Errors ---
+		{
+			name: "ShouldError_WhenPolicyAndQueueAreIncompatible",
+			input: Config{
+				PriorityBands: []PriorityBandConfig{{
+					Priority:                1,
+					PriorityName:            "High",
+					IntraFlowDispatchPolicy: intra.RegisteredPolicyName("policy-with-req"),
+					Queue:                   listqueue.ListQueueName,
+				}},
+			},
+			opts: []configOption{withIntraFlowDispatchPolicyFactory(
+				func(_ intra.RegisteredPolicyName) (framework.IntraFlowDispatchPolicy, error) {
+					return &mocks.MockIntraFlowDispatchPolicy{
+						RequiredQueueCapabilitiesV: []framework.QueueCapability{framework.QueueCapability("required-capability")},
+					}, nil
+				})},
 			expectErr:     true,
 			expectedErrIs: contracts.ErrPolicyQueueIncompatible,
 		},
 		{
-			name: "Error: Failing policy instantiation",
-			input: &Config{
-				PriorityBands: []PriorityBandConfig{
-					{
-						Priority:                1,
-						PriorityName:            "High",
-						IntraFlowDispatchPolicy: failingPolicyName,
-						Queue:                   listqueue.ListQueueName,
-					},
-				},
+			name: "ShouldError_WhenQueueFactoryFails",
+			input: Config{
+				PriorityBands: []PriorityBandConfig{{
+					Priority:     1,
+					PriorityName: "High",
+					Queue:        queue.RegisteredQueueName("failing-queue"),
+				}},
 			},
 			expectErr: true,
+			opts: []configOption{withQueueFactory(
+				func(_ queue.RegisteredQueueName, _ framework.ItemComparator) (framework.SafeQueue, error) {
+					return nil, errors.New("queue creation failed")
+				})},
 		},
 		{
-			name: "Error: Failing queue instantiation",
-			input: &Config{
-				PriorityBands: []PriorityBandConfig{
-					{
-						Priority:                1,
-						PriorityName:            "High",
-						IntraFlowDispatchPolicy: fcfs.FCFSPolicyName,
-						Queue:                   failingQueueName,
-					},
-				},
+			name: "ShouldError_WhenPolicyFactoryFails",
+			input: Config{
+				PriorityBands: []PriorityBandConfig{{
+					Priority:                1,
+					PriorityName:            "High",
+					IntraFlowDispatchPolicy: intra.RegisteredPolicyName("failing-policy"),
+				}},
 			},
+			opts: []configOption{withIntraFlowDispatchPolicyFactory(
+				func(_ intra.RegisteredPolicyName) (framework.IntraFlowDispatchPolicy, error) {
+					return nil, errors.New("policy creation failed")
+				})},
 			expectErr: true,
 		},
+		// --- Defaulting of Invalid Values ---
+		{
+			name: "ShouldApplyDefault_WhenInitialShardCountIsInvalid",
+			input: Config{
+				InitialShardCount: -1,
+				PriorityBands:     []PriorityBandConfig{{Priority: 1, PriorityName: "High"}},
+			},
+			assertion: func(t *testing.T, _ Config, newCfg *Config) {
+				assert.Equal(t, defaultInitialShardCount, newCfg.InitialShardCount,
+					"Invalid InitialShardCount should be corrected to the default")
+			},
+		},
+		{
+			name: "ShouldApplyDefault_WhenFlowGCTimeoutIsInvalid",
+			input: Config{
+				FlowGCTimeout: 0,
+				PriorityBands: []PriorityBandConfig{{Priority: 1, PriorityName: "High"}},
+			},
+			assertion: func(t *testing.T, _ Config, newCfg *Config) {
+				assert.Equal(t, defaultFlowGCTimeout, newCfg.FlowGCTimeout,
+					"Invalid FlowGCTimeout should be corrected to the default")
+			},
+		},
+		{
+			name: "ShouldApplyDefault_WhenEventChannelBufferSizeIsInvalid",
+			input: Config{
+				EventChannelBufferSize: -1,
+				PriorityBands:          []PriorityBandConfig{{Priority: 1, PriorityName: "High"}},
+			},
+			assertion: func(t *testing.T, _ Config, newCfg *Config) {
+				assert.Equal(t, defaultEventChannelBufferSize, newCfg.EventChannelBufferSize,
+					"Invalid EventChannelBufferSize should be corrected to the default")
+			},
+		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
-			err := tc.input.validateAndApplyDefaults()
+			originalInputCopy := tc.input.deepCopy()
+			newCfg, err := NewConfig(tc.input, tc.opts...)
 			if tc.expectErr {
-				require.Error(t, err, "Expected an error for this test case")
+				require.Error(t, err, "NewConfig should have returned an error")
 				if tc.expectedErrIs != nil {
-					assert.ErrorIs(t, err, tc.expectedErrIs, "Error should be of the expected type")
+					assert.ErrorIs(t, err, tc.expectedErrIs, "Error should wrap the expected error type")
 				}
+				assert.Nil(t, newCfg, "On error, the returned config should be nil")
 			} else {
-				require.NoError(t, err, "Did not expect an error for this test case")
-				assert.Equal(t, tc.expectedCfg, tc.input, "Config after applying defaults does not match expected config")
+				require.NoError(t, err, "NewConfig should not have returned an error")
+				require.NotNil(t, newCfg, "On success, the returned config should not be nil")
+				if tc.assertion != nil {
+					tc.assertion(t, *originalInputCopy, newCfg)
+				}
 			}
 		})
 	}
@@ -212,81 +297,201 @@ func TestConfig_ValidateAndApplyDefaults(t *testing.T) {
 
 func TestConfig_Partition(t *testing.T) {
 	t.Parallel()
-
-	baseConfig := &Config{
-		MaxBytes: 103,
+	baseCfg, err := NewConfig(Config{
+		MaxBytes: 103, // Will not distribute evenly
 		PriorityBands: []PriorityBandConfig{
-			{Priority: 1, PriorityName: "High", MaxBytes: 55},
-			{Priority: 2, PriorityName: "Low", MaxBytes: 0}, // Should remain 0
+			{Priority: 1, PriorityName: "High", MaxBytes: 55}, // Will not distribute evenly
+			{Priority: 2, PriorityName: "Low", MaxBytes: 0},   // Will be defaulted to 1,000,000,000
+			{Priority: 3, PriorityName: "Mid", MaxBytes: 20},  // Will distribute evenly
 		},
-	}
+	})
+	require.NoError(t, err, "Test setup: creating the base config should not fail")
 
-	t.Run("EvenDistributionWithRemainder", func(t *testing.T) {
+	t.Run("ShouldDistributeWithRemainderCorrectly", func(t *testing.T) {
 		t.Parallel()
 		totalShards := 10
-		// Global: 103 / 10 = 10 remainder 3. First 3 shards get 11, rest get 10.
-		// Band 1: 55 / 10 = 5 remainder 5. First 5 shards get 6, rest get 5.
+		// Global: 103 / 10 = 10 rem 3. First 3 shards get 11, rest get 10.
+		// Band 1: 55 / 10 = 5 rem 5. First 5 shards get 6, rest get 5.
+		// Band 2 (defaulted): 1,000,000,000 / 10 = 100,000,000 for all shards.
+		// Band 3: 20 / 10 = 2 rem 0. All shards get 2.
 		expectedGlobalBytes := []uint64{11, 11, 11, 10, 10, 10, 10, 10, 10, 10}
 		expectedBand1Bytes := []uint64{6, 6, 6, 6, 6, 5, 5, 5, 5, 5}
 
-		var totalGlobal, totalBand1 uint64
+		var totalGlobal, totalBand1, totalBand2, totalBand3 uint64
 		for i := range totalShards {
-			partitioned, err := baseConfig.partition(i, totalShards)
-			require.NoError(t, err, "Partitioning should not fail for shard %d", i)
-			assert.Equal(t, expectedGlobalBytes[i], partitioned.MaxBytes, "Global MaxBytes for shard %d is incorrect", i)
-			require.Len(t, partitioned.PriorityBands, 2, "Partitioned config should have the same number of bands")
-			assert.Equal(t, expectedBand1Bytes[i], partitioned.PriorityBands[0].MaxBytes,
+			shardCfg := baseCfg.partition(i, totalShards)
+			require.NotNil(t, shardCfg, "Partitioned config for shard %d should not be nil", i)
+			assert.Equal(t, expectedGlobalBytes[i], shardCfg.MaxBytes, "Global MaxBytes for shard %d is incorrect", i)
+			require.Len(t, shardCfg.PriorityBands, 3, "Partitioned config should have 3 bands")
+			assert.Equal(t, expectedBand1Bytes[i], shardCfg.PriorityBands[0].MaxBytes,
 				"Band 1 MaxBytes for shard %d is incorrect", i)
-			assert.Zero(t, partitioned.PriorityBands[1].MaxBytes, "Band 2 MaxBytes should remain zero for shard %d", i)
-			totalGlobal += partitioned.MaxBytes
-			totalBand1 += partitioned.PriorityBands[0].MaxBytes
+
+			expectedBand2Bytes := defaultPriorityBandMaxBytes / uint64(totalShards)
+			assert.Equal(t, expectedBand2Bytes, shardCfg.PriorityBands[1].MaxBytes,
+				"Band 2 MaxBytes for shard %d is incorrect", i)
+			assert.Equal(t, uint64(2), shardCfg.PriorityBands[2].MaxBytes,
+				"Band 3 MaxBytes for shard %d is incorrect", i)
+
+			totalGlobal += shardCfg.MaxBytes
+			totalBand1 += shardCfg.PriorityBands[0].MaxBytes
+			totalBand2 += shardCfg.PriorityBands[1].MaxBytes
+			totalBand3 += shardCfg.PriorityBands[2].MaxBytes
 		}
-		assert.Equal(t, baseConfig.MaxBytes, totalGlobal, "Sum of partitioned global MaxBytes should equal original")
-		assert.Equal(t, baseConfig.PriorityBands[0].MaxBytes, totalBand1,
+		assert.Equal(t, baseCfg.MaxBytes, totalGlobal, "Sum of partitioned global MaxBytes should equal original")
+		assert.Equal(t, baseCfg.PriorityBands[0].MaxBytes, totalBand1,
 			"Sum of partitioned band 1 MaxBytes should equal original")
+		assert.Equal(t, defaultPriorityBandMaxBytes, totalBand2,
+			"Sum of partitioned band 2 MaxBytes should equal the default value")
+		assert.Equal(t, baseCfg.PriorityBands[2].MaxBytes, totalBand3,
+			"Sum of partitioned band 3 MaxBytes should equal original")
 	})
 
-	t.Run("SingleShard", func(t *testing.T) {
+	t.Run("ShouldHandleSingleShard", func(t *testing.T) {
 		t.Parallel()
-		partitioned, err := baseConfig.partition(0, 1)
-		require.NoError(t, err, "Partitioning for a single shard should not fail")
-		assert.Equal(t, baseConfig.MaxBytes, partitioned.MaxBytes, "Global MaxBytes should be unchanged for a single shard")
-		require.Len(t, partitioned.PriorityBands, 2, "Partitioned config should have the same number of bands")
-		assert.Equal(t, baseConfig.PriorityBands[0].MaxBytes, partitioned.PriorityBands[0].MaxBytes,
-			"Band 1 MaxBytes should be unchanged for a single shard")
-	})
-
-	t.Run("EmptyPriorityBands", func(t *testing.T) {
-		t.Parallel()
-		config := &Config{
-			MaxBytes:      100,
-			PriorityBands: []PriorityBandConfig{},
-		}
-		partitioned, err := config.partition(1, 3)
-		require.NoError(t, err, "Partitioning should not fail for empty priority bands")
-		assert.Equal(t, uint64(33), partitioned.MaxBytes, "Global MaxBytes should be partitioned correctly")
-		assert.Empty(t, partitioned.PriorityBands, "PriorityBands slice should be empty")
-		assert.NotNil(t, partitioned.PriorityBands, "PriorityBands slice should not be nil")
+		shardCfg := baseCfg.partition(0, 1)
+		require.NotNil(t, shardCfg, "Partitioned config for single shard should not be nil")
+		assert.Equal(t, baseCfg.MaxBytes, shardCfg.MaxBytes, "Global MaxBytes should be unchanged for a single shard")
+		require.Len(t, shardCfg.PriorityBands, 3, "Partitioned config should have 3 bands")
+		assert.Equal(t, baseCfg.PriorityBands[0].MaxBytes, shardCfg.PriorityBands[0].MaxBytes,
+			"Band 1 MaxBytes should be unchanged")
 	})
 
-	t.Run("ErrorHandling", func(t *testing.T) {
+	t.Run("ShouldPanic_WhenTotalShardsIsInvalid", func(t *testing.T) {
 		t.Parallel()
 		testCases := []struct {
 			name        string
-			shardIndex  int
 			totalShards int
 		}{
-			{"NegativeShardIndex", -1, 5},
-			{"ShardIndexOutOfBounds", 5, 5},
-			{"ZeroTotalShards", 0, 0},
-			{"NegativeTotalShards", 0, -1},
+			{"ZeroShards", 0},
+			{"NegativeShards", -1},
 		}
+
 		for _, tc := range testCases {
 			t.Run(tc.name, func(t *testing.T) {
 				t.Parallel()
-				_, err := baseConfig.partition(tc.shardIndex, tc.totalShards)
-				assert.Error(t, err, "Expected an error for invalid partitioning arguments")
+				assert.PanicsWithValue(t,
+					fmt.Sprintf("invariant violation: cannot partition into %d partitions", tc.totalShards),
+					func() { baseCfg.partition(0, tc.totalShards) },
+					"Should panic with a specific message when totalShards is not positive",
+				)
 			})
 		}
 	})
 }
+
+func TestConfig_GetBandConfig(t *testing.T) {
+	t.Parallel()
+	cfg, err := NewConfig(Config{
+		PriorityBands: []PriorityBandConfig{
+			{Priority: 10, PriorityName: "High"},
+		},
+	})
+	require.NoError(t, err, "Test setup: creating the config should not fail")
+
+	t.Run("ShouldReturnCorrectConfig_ForExistingPriority", func(t *testing.T) {
+		t.Parallel()
+		bandCfg, err := cfg.getBandConfig(10)
+		require.NoError(t, err, "Should not error for an existing priority")
+		require.NotNil(t, bandCfg, "Returned band config should not be nil")
+		assert.Equal(t, "High", bandCfg.PriorityName, "Should return the correct band config")
+	})
+
+	t.Run("ShouldReturnError_ForNonExistentPriority", func(t *testing.T) {
+		t.Parallel()
+		_, err := cfg.getBandConfig(99)
+		assert.ErrorIs(t, err, contracts.ErrPriorityBandNotFound, "Error should wrap ErrPriorityBandNotFound")
+	})
+}
+
+func TestConfig_DeepCopy(t *testing.T) {
+	t.Parallel()
+
+	baseCfg := Config{
+		MaxBytes:               1000,
+		InitialShardCount:      5,
+		FlowGCTimeout:          10 * time.Minute,
+		EventChannelBufferSize: 2048,
+		PriorityBands: []PriorityBandConfig{
+			{Priority: 1, PriorityName: "A"},
+			{Priority: 2, PriorityName: "B"},
+		},
+	}
+	// Create a fully initialized "original" config to be the source of the copy.
+	original, err := NewConfig(baseCfg)
+	require.NoError(t, err, "Setup for deep copy should not fail")
+
+	t.Run("ShouldReturnNil_ForNilReceiver", func(t *testing.T) {
+		t.Parallel()
+		var nilConfig *Config
+		assert.Nil(t, nilConfig.deepCopy(), "Deep copy of a nil config should be nil")
+	})
+
+	t.Run("ShouldCreateIdenticalButSeparateObject", func(t *testing.T) {
+		t.Parallel()
+		clone := original.deepCopy()
+
+		// Assert that the clone is not the same object in memory.
+		require.NotSame(t, original, clone, "Clone should be a new object in memory")
+		require.NotSame(t, &original.PriorityBands, &clone.PriorityBands, "PriorityBands slice should be a new object")
+		require.NotSame(t, &original.priorityBandMap, &clone.priorityBandMap, "priorityBandMap should be a new object")
+		assert.Equal(t, original.MaxBytes, clone.MaxBytes, "MaxBytes should be copied")
+		assert.Equal(t, original.InitialShardCount, clone.InitialShardCount, "InitialShardCount should be copied")
+		assert.Equal(t, original.FlowGCTimeout, clone.FlowGCTimeout, "FlowGCTimeout should be copied")
+		assert.Equal(t, original.EventChannelBufferSize, clone.EventChannelBufferSize,
+			"EventChannelBufferSize should be copied")
+		assert.Equal(t, original.PriorityBands, clone.PriorityBands, "PriorityBands should be deeply copied")
+	})
+
+	t.Run("ShouldBeIndependent_AfterCopying", func(t *testing.T) {
+		t.Parallel()
+		clone := original.deepCopy()
+
+		// Modify the clone
+		clone.MaxBytes = 2000
+		clone.PriorityBands[0].PriorityName = "modified"
+		// This modification through the map should also modify the slice element, as the map holds pointers.
+		clone.priorityBandMap[clone.PriorityBands[0].Priority].PriorityName = "modified-in-map"
+		clone.queueFactory = func(_ queue.RegisteredQueueName, _ framework.ItemComparator) (framework.SafeQueue, error) {
+			return nil, nil
+		}
+
+		// Assert that the original remains unchanged by comparing field by field for clarity.
+		assert.Equal(t, uint64(1000), original.MaxBytes, "Original MaxBytes should not be changed")
+		assert.Equal(t, 5, original.InitialShardCount, "Original InitialShardCount should not be changed")
+		assert.Equal(t, "A", original.PriorityBands[0].PriorityName, "Original PriorityBands slice should not be changed")
+		assert.Equal(t, "A", original.priorityBandMap[1].PriorityName, "Original priorityBandMap should not be changed")
+		assert.NotNil(t, original.queueFactory, "Original queueFactory should not be nil")
+
+		// Verify the change was contained to the clone. The final value should be the one set via the map.
+		assert.Equal(t, "modified-in-map", clone.PriorityBands[0].PriorityName,
+			"Clone's slice should reflect the change from the map")
+		assert.Equal(t, "modified-in-map", clone.priorityBandMap[1].PriorityName, "Clone's map should reflect the change")
+	})
+}
+
+func TestShardConfig_GetBandConfig(t *testing.T) {
+	t.Parallel()
+	baseCfg, err := NewConfig(Config{
+		PriorityBands: []PriorityBandConfig{
+			{Priority: 10, PriorityName: "High"},
+			{Priority: 20, PriorityName: "Low"},
+		},
+	})
+	require.NoError(t, err, "Test setup: creating the base config should not fail")
+	shardCfg := baseCfg.partition(0, 2)
+	require.NotNil(t, shardCfg, "Test setup: partitioned config should not be nil")
+
+	t.Run("ShouldReturnCorrectConfig_ForExistingPriority", func(t *testing.T) {
+		t.Parallel()
+		bandCfg, err := shardCfg.getBandConfig(20)
+		require.NoError(t, err, "Should not error for an existing priority")
+		require.NotNil(t, bandCfg, "Returned band config should not be nil")
+		assert.Equal(t, "Low", bandCfg.PriorityName, "Should return the correct partitioned band config")
+	})
+
+	t.Run("ShouldReturnError_ForNonExistentPriority", func(t *testing.T) {
+		t.Parallel()
+		_, err := shardCfg.getBandConfig(99)
+		assert.ErrorIs(t, err, contracts.ErrPriorityBandNotFound, "Error should wrap ErrPriorityBandNotFound")
+	})
+}
diff --git a/pkg/epp/flowcontrol/registry/flowstate_test.go b/pkg/epp/flowcontrol/registry/flowstate_test.go
new file mode 100644
index 0000000..ceedbd5
--- /dev/null
+++ b/pkg/epp/flowcontrol/registry/flowstate_test.go
@@ -0,0 +1,216 @@
+/*
+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.
+*/
+
+package registry
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types"
+)
+
+// fsTestHarness holds all the common components for a `flowState` test.
+type fsTestHarness struct {
+	t         *testing.T
+	fs        *flowState
+	allShards []*registryShard
+}
+
+// newFsTestHarness creates a new test harness, initializing a `flowState` with a default spec and shard layout.
+func newFsTestHarness(t *testing.T) *fsTestHarness {
+	t.Helper()
+
+	spec := types.FlowSpecification{
+		Key: types.FlowKey{ID: "f1", Priority: 10},
+	}
+	// Note: The shards are simple structs for this test, as we only need their IDs.
+	allShards := []*registryShard{{id: "s1"}, {id: "s2"}, {id: "s3-draining"}}
+
+	fs := newFlowState(spec, allShards)
+
+	return &fsTestHarness{
+		t:         t,
+		fs:        fs,
+		allShards: allShards,
+	}
+}
+
+func TestFlowState_New(t *testing.T) {
+	t.Parallel()
+	h := newFsTestHarness(t)
+
+	assert.Equal(t, uint64(0), h.fs.lastActiveGeneration, "Initial lastActiveGeneration should be 0")
+	require.Len(t, h.fs.emptyOnShards, 3, "Should track emptiness status for all initial shards")
+
+	// A new flow instance should be considered empty on all shards by default.
+	assert.True(t, h.fs.emptyOnShards["s1"], "Queue on s1 should start as empty")
+	assert.True(t, h.fs.emptyOnShards["s2"], "Queue on s2 should start as empty")
+	assert.True(t, h.fs.emptyOnShards["s3-draining"], "Queue on s3-draining should start as empty")
+}
+
+func TestFlowState_Update(t *testing.T) {
+	t.Parallel()
+
+	t.Run("ShouldUpdateSpec_WhenKeyIsUnchanged", func(t *testing.T) {
+		t.Parallel()
+		h := newFsTestHarness(t)
+
+		// Create a new spec with the same key but different (hypothetical) content.
+		updatedSpec := types.FlowSpecification{
+			Key: h.fs.spec.Key,
+			// Imagine other fields like per-flow `framework.IntraFlowDispatchPolicy` overrides being added here in the
+			// future.
+		}
+
+		h.fs.update(updatedSpec)
+		assert.Equal(t, updatedSpec, h.fs.spec, "Spec should be updated with new content")
+	})
+
+	t.Run("ShouldPanic_WhenKeyIsChanged", func(t *testing.T) {
+		t.Parallel()
+		h := newFsTestHarness(t)
+
+		invalidSpec := types.FlowSpecification{
+			Key: types.FlowKey{ID: "f1", Priority: 99}, // Different priority
+		}
+
+		assert.PanicsWithValue(t,
+			fmt.Sprintf("invariant violation: attempted to update flowState for key %s with a new key %s",
+				h.fs.spec.Key, invalidSpec.Key),
+			func() { h.fs.update(invalidSpec) },
+			"Should panic when attempting to change the immutable FlowKey",
+		)
+	})
+}
+
+func TestFlowState_HandleQueueSignal(t *testing.T) {
+	t.Parallel()
+
+	testCases := []struct {
+		name          string
+		signal        queueStateSignal
+		initialState  bool
+		expectedState bool
+		message       string
+	}{
+		{
+			name:          "BecameNonEmpty_WhenPreviouslyEmpty",
+			signal:        queueStateSignalBecameNonEmpty,
+			initialState:  true,
+			expectedState: false,
+			message:       "Should mark queue as non-empty (false)",
+		},
+		{
+			name:          "BecameEmpty_WhenPreviouslyNonEmpty",
+			signal:        queueStateSignalBecameEmpty,
+			initialState:  false,
+			expectedState: true,
+			message:       "Should mark queue as empty (true)",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			h := newFsTestHarness(t)
+			shardID := "s1"
+
+			h.fs.emptyOnShards[shardID] = tc.initialState
+			h.fs.handleQueueSignal(shardID, tc.signal)
+			assert.Equal(t, tc.expectedState, h.fs.emptyOnShards[shardID], tc.message)
+		})
+	}
+}
+
+func TestFlowState_IsIdle(t *testing.T) {
+	t.Parallel()
+
+	testCases := []struct {
+		name       string
+		setup      func(h *fsTestHarness)
+		shards     []*registryShard // The set of shards to check for idleness.
+		expectIdle bool
+		message    string
+	}{
+		{
+			name:       "WhenAllShardsAreEmpty",
+			setup:      nil, // Default state is all empty.
+			expectIdle: true,
+			message:    "Should be idle when all tracked queues are empty",
+		},
+		{
+			name: "WhenOneShardIsNonEmpty",
+			setup: func(h *fsTestHarness) {
+				h.fs.handleQueueSignal("s2", queueStateSignalBecameNonEmpty)
+			},
+			expectIdle: false,
+			message:    "Should not be idle if any queue is non-empty",
+		},
+		{
+			name: "WhenADrainingShardIsNonEmpty",
+			setup: func(h *fsTestHarness) {
+				h.fs.handleQueueSignal("s3-draining", queueStateSignalBecameNonEmpty)
+			},
+			expectIdle: false,
+			message:    "Should not be idle if a draining shard's queue is non-empty",
+		},
+		{
+			name: "WhenNonEmptyShardIsNotInCheckedSet",
+			setup: func(h *fsTestHarness) {
+				// The flow is active on a shard that is Draining.
+				h.fs.handleQueueSignal("s3-draining", queueStateSignalBecameNonEmpty)
+			},
+			// But we are only checking the set of Active shards.
+			shards:     []*registryShard{{id: "s1"}, {id: "s2"}},
+			expectIdle: true,
+			message:    "Should be considered idle if the only non-empty queue is on a shard that is not being checked",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			h := newFsTestHarness(t)
+			if tc.setup != nil {
+				tc.setup(h)
+			}
+
+			shardsToCheck := tc.shards
+			if shardsToCheck == nil {
+				shardsToCheck = h.allShards
+			}
+
+			assert.Equal(t, tc.expectIdle, h.fs.isIdle(shardsToCheck), tc.message)
+		})
+	}
+}
+
+func TestFlowState_PurgeShard(t *testing.T) {
+	t.Parallel()
+	h := newFsTestHarness(t)
+	shardToPurge := "s2"
+
+	require.Contains(t, h.fs.emptyOnShards, shardToPurge, "Test setup: shard must exist before purging")
+	h.fs.purgeShard(shardToPurge)
+
+	assert.NotContains(t, h.fs.emptyOnShards, shardToPurge, "Shard should be purged from the tracking map")
+	assert.Contains(t, h.fs.emptyOnShards, "s1", "Other shards should remain in the tracking map")
+	assert.Len(t, h.fs.emptyOnShards, 2, "Map length should be reduced by one")
+}
diff --git a/pkg/epp/flowcontrol/registry/managedqueue_test.go b/pkg/epp/flowcontrol/registry/managedqueue_test.go
index 6fc172f..0b3d3da 100644
--- a/pkg/epp/flowcontrol/registry/managedqueue_test.go
+++ b/pkg/epp/flowcontrol/registry/managedqueue_test.go
@@ -18,6 +18,7 @@ package registry
 
 import (
 	"errors"
+	"fmt"
 	"sync"
 	"sync/atomic"
 	"testing"
@@ -26,7 +27,6 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework"
 	frameworkmocks "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/mocks"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/queue"
@@ -35,454 +35,551 @@ import (
 	typesmocks "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types/mocks"
 )
 
-// testStatsReconciler is a mock implementation of the `parentStatsReconciler` function.
-// It captures the deltas it's called with, allowing tests to assert on them.
-type testStatsReconciler struct {
-	mu              sync.Mutex
-	lenDelta        int64
-	byteSizeDelta   int64
-	invocationCount int
-}
+// --- Test Harness and Mocks ---
 
-func (r *testStatsReconciler) reconcile(lenDelta, byteSizeDelta int64) {
-	r.mu.Lock()
-	defer r.mu.Unlock()
-	r.lenDelta += lenDelta
-	r.byteSizeDelta += byteSizeDelta
-	r.invocationCount++
+// mqTestHarness holds all components for testing a `managedQueue`.
+type mqTestHarness struct {
+	t              *testing.T
+	mq             *managedQueue
+	propagator     *mockStatsPropagator
+	signalRecorder *mockQueueStateSignalRecorder
+	mockPolicy     *frameworkmocks.MockIntraFlowDispatchPolicy
 }
 
-func (r *testStatsReconciler) getStats() (lenDelta, byteSizeDelta int64, count int) {
-	r.mu.Lock()
-	defer r.mu.Unlock()
-	return r.lenDelta, r.byteSizeDelta, r.invocationCount
+// newMockedMqHarness creates a harness that uses a mocked underlying queue.
+// This is ideal for isolating and unit testing the decorator logic of `managedQueue`.
+func newMockedMqHarness(t *testing.T, queue *frameworkmocks.MockSafeQueue, key types.FlowKey) *mqTestHarness {
+	t.Helper()
+	return newMqHarness(t, queue, key)
 }
 
-// testFixture holds the components needed for a `managedQueue` test.
-type testFixture struct {
-	mq             *managedQueue
-	mockQueue      *frameworkmocks.MockSafeQueue
-	mockPolicy     *frameworkmocks.MockIntraFlowDispatchPolicy
-	reconciler     *testStatsReconciler
-	flowSpec       types.FlowSpecification
-	mockComparator *frameworkmocks.MockItemComparator
+// newRealMqHarness creates a harness that uses a real "ListQueue" implementation.
+// This is essential for integration and concurrency tests.
+func newRealMqHarness(t *testing.T, key types.FlowKey) *mqTestHarness {
+	t.Helper()
+	q, err := queue.NewQueueFromName(listqueue.ListQueueName, nil)
+	require.NoError(t, err, "Test setup: creating a real ListQueue should not fail")
+	return newMqHarness(t, q, key)
 }
 
-// setupTestManagedQueue creates a new test fixture for testing the `managedQueue`.
-func setupTestManagedQueue(t *testing.T) *testFixture {
+func newMqHarness(t *testing.T, queue framework.SafeQueue, key types.FlowKey) *mqTestHarness {
 	t.Helper()
 
-	mockQueue := &frameworkmocks.MockSafeQueue{}
-	reconciler := &testStatsReconciler{}
-	flowSpec := types.FlowSpecification{ID: "test-flow", Priority: 1}
-	mockComparator := &frameworkmocks.MockItemComparator{}
+	propagator := &mockStatsPropagator{}
+	signalRec := newMockQueueStateSignalRecorder()
 	mockPolicy := &frameworkmocks.MockIntraFlowDispatchPolicy{
-		ComparatorV: mockComparator,
+		ComparatorV: &frameworkmocks.MockItemComparator{},
 	}
 
-	mq := newManagedQueue(
-		mockQueue,
-		mockPolicy,
-		flowSpec,
-		logr.Discard(),
-		reconciler.reconcile,
-	)
-	require.NotNil(t, mq, "newManagedQueue should not return nil")
+	callbacks := managedQueueCallbacks{
+		propagateStatsDelta: propagator.propagate,
+		signalQueueState:    signalRec.signal,
+	}
+	mq := newManagedQueue(queue, mockPolicy, key, logr.Discard(), callbacks)
+	require.NotNil(t, mq, "Test setup: newManagedQueue should not return nil")
 
-	return &testFixture{
+	return &mqTestHarness{
+		t:              t,
 		mq:             mq,
-		mockQueue:      mockQueue,
+		propagator:     propagator,
+		signalRecorder: signalRec,
 		mockPolicy:     mockPolicy,
-		reconciler:     reconciler,
-		flowSpec:       flowSpec,
-		mockComparator: mockComparator,
 	}
 }
 
-func TestManagedQueue_New(t *testing.T) {
-	t.Parallel()
-	f := setupTestManagedQueue(t)
+func (h *mqTestHarness) setupWithItems(items ...types.QueueItemAccessor) {
+	h.t.Helper()
+	for _, item := range items {
+		err := h.mq.Add(item)
+		require.NoError(h.t, err, "Harness setup: failed to add item")
+	}
+	// Reset counters after the setup phase is complete.
+	h.propagator.reset()
+}
+
+// mockStatsPropagator captures stat changes from the system under test.
+type mockStatsPropagator struct {
+	lenDelta      atomic.Int64
+	byteSizeDelta atomic.Int64
+}
+
+func (p *mockStatsPropagator) propagate(_ uint, lenDelta, byteSizeDelta int64) {
+	p.lenDelta.Add(lenDelta)
+	p.byteSizeDelta.Add(byteSizeDelta)
+}
 
-	assert.Zero(t, f.mq.Len(), "A new managedQueue should have a length of 0")
-	assert.Zero(t, f.mq.ByteSize(), "A new managedQueue should have a byte size of 0")
-	assert.True(t, f.mq.isActive.Load(), "A new managedQueue should be active")
+func (p *mockStatsPropagator) reset() {
+	p.lenDelta.Store(0)
+	p.byteSizeDelta.Store(0)
+}
+
+// mockQueueStateSignalRecorder captures queue state signals from the system under test.
+type mockQueueStateSignalRecorder struct {
+	mu      sync.Mutex
+	signals []queueStateSignal
+}
+
+func newMockQueueStateSignalRecorder() *mockQueueStateSignalRecorder {
+	return &mockQueueStateSignalRecorder{signals: make([]queueStateSignal, 0)}
+}
+
+func (r *mockQueueStateSignalRecorder) signal(_ types.FlowKey, signal queueStateSignal) {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	r.signals = append(r.signals, signal)
+}
+
+func (r *mockQueueStateSignalRecorder) getSignals() []queueStateSignal {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	signalsCopy := make([]queueStateSignal, len(r.signals))
+	copy(signalsCopy, r.signals)
+	return signalsCopy
+}
+
+// --- Unit Tests ---
+
+func TestManagedQueue_InitialState(t *testing.T) {
+	t.Parallel()
+	h := newMockedMqHarness(t, &frameworkmocks.MockSafeQueue{}, types.FlowKey{ID: "flow", Priority: 1})
+	assert.Zero(t, h.mq.Len(), "A new queue should have a length of 0")
+	assert.Zero(t, h.mq.ByteSize(), "A new queue should have a byte size of 0")
 }
 
 func TestManagedQueue_Add(t *testing.T) {
 	t.Parallel()
+	flowKey := types.FlowKey{ID: "flow", Priority: 1}
 
 	testCases := []struct {
 		name                  string
-		itemByteSize          uint64
-		mockAddError          error
-		markAsDraining        bool
-		expectError           bool
-		expectedErrorIs       error
-		expectedLen           int
-		expectedByteSize      uint64
+		setupMock             func(q *frameworkmocks.MockSafeQueue)
+		expectErr             bool
 		expectedLenDelta      int64
 		expectedByteSizeDelta int64
-		expectedReconcile     bool
 	}{
 		{
-			name:                  "Success",
-			itemByteSize:          100,
-			expectError:           false,
-			expectedLen:           1,
-			expectedByteSize:      100,
+			name: "ShouldSucceed_AndIncrementStats",
+			setupMock: func(q *frameworkmocks.MockSafeQueue) {
+				q.AddFunc = func(types.QueueItemAccessor) error { return nil }
+			},
+			expectErr:             false,
 			expectedLenDelta:      1,
 			expectedByteSizeDelta: 100,
-			expectedReconcile:     true,
-		},
-		{
-			name:                  "Error from underlying queue",
-			itemByteSize:          100,
-			mockAddError:          errors.New("queue full"),
-			expectError:           true,
-			expectedLen:           0,
-			expectedByteSize:      0,
-			expectedLenDelta:      0,
-			expectedByteSizeDelta: 0,
-			expectedReconcile:     false,
 		},
 		{
-			name:                  "Error on inactive queue",
-			itemByteSize:          100,
-			markAsDraining:        true,
-			expectError:           true,
-			expectedErrorIs:       contracts.ErrFlowInstanceNotFound,
-			expectedLen:           0,
-			expectedByteSize:      0,
+			name: "ShouldFail_AndNotChangeStats_WhenUnderlyingQueueFails",
+			setupMock: func(q *frameworkmocks.MockSafeQueue) {
+				q.AddFunc = func(types.QueueItemAccessor) error { return errors.New("add failed") }
+			},
+			expectErr:             true,
 			expectedLenDelta:      0,
 			expectedByteSizeDelta: 0,
-			expectedReconcile:     false,
 		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
-			f := setupTestManagedQueue(t)
+			item := typesmocks.NewMockQueueItemAccessor(100, "req", flowKey)
+			q := &frameworkmocks.MockSafeQueue{}
+			tc.setupMock(q)
+			h := newMockedMqHarness(t, q, flowKey)
 
-			// Configure mock
-			f.mockQueue.AddFunc = func(item types.QueueItemAccessor) error {
-				return tc.mockAddError
-			}
-
-			if tc.markAsDraining {
-				f.mq.markAsDraining()
-				assert.False(t, f.mq.isActive.Load(), "Setup: queue should be marked as inactive")
-			}
+			err := h.mq.Add(item)
 
-			item := typesmocks.NewMockQueueItemAccessor(tc.itemByteSize, "req-1", "test-flow")
-			err := f.mq.Add(item)
-
-			if tc.expectError {
+			if tc.expectErr {
 				require.Error(t, err, "Add should have returned an error")
-				if tc.expectedErrorIs != nil {
-					assert.ErrorIs(t, err, tc.expectedErrorIs, "Error should wrap the expected sentinel error")
-				}
 			} else {
 				require.NoError(t, err, "Add should not have returned an error")
 			}
-
-			// Assert final state
-			assert.Equal(t, tc.expectedLen, f.mq.Len(), "Final length should be as expected")
-			assert.Equal(t, tc.expectedByteSize, f.mq.ByteSize(), "Final byte size should be as expected")
-
-			// Assert reconciler state
-			lenDelta, byteSizeDelta, count := f.reconciler.getStats()
-			assert.Equal(t, tc.expectedLenDelta, lenDelta, "Reconciler length delta should be as expected")
-			assert.Equal(t, tc.expectedByteSizeDelta, byteSizeDelta, "Reconciler byte size delta should be as expected")
-			if tc.expectedReconcile {
-				assert.Equal(t, 1, count, "Reconciler should have been called once")
-			} else {
-				assert.Zero(t, count, "Reconciler should not have been called")
-			}
+			assert.Equal(t, tc.expectedLenDelta, h.propagator.lenDelta.Load(),
+				"Propagated length delta should be correct")
+			assert.Equal(t, tc.expectedByteSizeDelta, h.propagator.byteSizeDelta.Load(),
+				"Propagated byte size delta should be correct")
 		})
 	}
 }
 
 func TestManagedQueue_Remove(t *testing.T) {
 	t.Parallel()
-	f := setupTestManagedQueue(t)
-
-	// Setup initial state
-	initialItem := typesmocks.NewMockQueueItemAccessor(100, "req-1", "test-flow")
-	f.mockQueue.AddFunc = func(item types.QueueItemAccessor) error { return nil }
-	err := f.mq.Add(initialItem)
-	require.NoError(t, err, "Setup: Adding an item should not fail")
-	require.Equal(t, 1, f.mq.Len(), "Setup: Length should be 1 after adding an item")
-
-	// --- Test Success ---
-	t.Run("Success", func(t *testing.T) {
-		// Configure mock for Remove
-		f.mockQueue.RemoveFunc = func(handle types.QueueItemHandle) (types.QueueItemAccessor, error) {
-			return initialItem, nil
-		}
+	flowKey := types.FlowKey{ID: "flow", Priority: 1}
 
-		// Perform Remove
-		removedItem, err := f.mq.Remove(initialItem.Handle())
-		require.NoError(t, err, "Remove should not return an error")
-		assert.Equal(t, initialItem, removedItem, "Remove should return the correct item")
-
-		// Assert final state
-		assert.Zero(t, f.mq.Len(), "Length should be 0 after removing the only item")
-		assert.Zero(t, f.mq.ByteSize(), "ByteSize should be 0 after removing the only item")
-
-		// Assert reconciler state
-		lenDelta, byteSizeDelta, count := f.reconciler.getStats()
-		assert.Equal(t, int64(0), lenDelta, "Net length delta should be 0 after add and remove")
-		assert.Equal(t, int64(0), byteSizeDelta, "Net byte size delta should be 0 after add and remove")
-		assert.Equal(t, 2, count, "Reconciler should have been called for both Add and Remove")
-	})
-
-	// --- Test Error ---
-	t.Run("Error", func(t *testing.T) {
-		f := setupTestManagedQueue(t)
-		require.NoError(t, f.mq.Add(initialItem), "Setup: Adding an item should not fail")
-
-		// Configure mock to return an error
-		expectedErr := errors.New("item not found")
-		f.mockQueue.RemoveFunc = func(handle types.QueueItemHandle) (types.QueueItemAccessor, error) {
-			return nil, expectedErr
-		}
+	testCases := []struct {
+		name                  string
+		setupMock             func(q *frameworkmocks.MockSafeQueue, item types.QueueItemAccessor)
+		expectErr             bool
+		expectedLenDelta      int64
+		expectedByteSizeDelta int64
+	}{
+		{
+			name: "ShouldSucceed_AndDecrementStats",
+			setupMock: func(q *frameworkmocks.MockSafeQueue, item types.QueueItemAccessor) {
+				q.RemoveFunc = func(_ types.QueueItemHandle) (types.QueueItemAccessor, error) {
+					return item, nil
+				}
+			},
+			expectErr:             false,
+			expectedLenDelta:      -1,
+			expectedByteSizeDelta: -100,
+		},
+		{
+			name: "ShouldFail_AndNotChangeStats_WhenUnderlyingQueueFails",
+			setupMock: func(q *frameworkmocks.MockSafeQueue, item types.QueueItemAccessor) {
+				q.RemoveFunc = func(_ types.QueueItemHandle) (types.QueueItemAccessor, error) {
+					return nil, errors.New("remove failed")
+				}
+			},
+			expectErr:             true,
+			expectedLenDelta:      0,
+			expectedByteSizeDelta: 0,
+		},
+	}
 
-		_, err := f.mq.Remove(initialItem.Handle())
-		require.ErrorIs(t, err, expectedErr, "Remove should propagate the error")
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			q := &frameworkmocks.MockSafeQueue{}
+			h := newMockedMqHarness(t, q, flowKey)
+			item := typesmocks.NewMockQueueItemAccessor(100, "req", flowKey)
+			h.setupWithItems(item)
+			tc.setupMock(q, item)
 
-		// Assert state did not change
-		assert.Equal(t, 1, f.mq.Len(), "Length should not change on a failed remove")
-		assert.Equal(t, uint64(100), f.mq.ByteSize(), "ByteSize should not change on a failed remove")
+			_, err := h.mq.Remove(item.Handle())
 
-		// Assert reconciler was not called for the remove
-		_, _, count := f.reconciler.getStats()
-		assert.Equal(t, 1, count, "Reconciler should only have been called for the initial Add")
-	})
+			if tc.expectErr {
+				require.Error(t, err, "Remove should have returned an error")
+			} else {
+				require.NoError(t, err, "Remove should not have returned an error")
+			}
+			assert.Equal(t, tc.expectedLenDelta, h.propagator.lenDelta.Load(), "Propagated length delta should be correct")
+			assert.Equal(t, tc.expectedByteSizeDelta, h.propagator.byteSizeDelta.Load(),
+				"Propagated byte size delta should be correct")
+		})
+	}
 }
 
-func TestManagedQueue_CleanupAndDrain(t *testing.T) {
+func TestManagedQueue_Cleanup(t *testing.T) {
 	t.Parallel()
-	item1 := typesmocks.NewMockQueueItemAccessor(10, "req-1", "test-flow")
-	item2 := typesmocks.NewMockQueueItemAccessor(20, "req-2", "test-flow")
-	item3 := typesmocks.NewMockQueueItemAccessor(30, "req-3", "test-flow")
-
-	// --- Test Cleanup ---
-	t.Run("Cleanup", func(t *testing.T) {
-		t.Parallel()
-		f := setupTestManagedQueue(t)
-		// Add initial items
-		require.NoError(t, f.mq.Add(item1), "Setup: Add item1 should not fail")
-		require.NoError(t, f.mq.Add(item2), "Setup: Add item2 should not fail")
-		require.NoError(t, f.mq.Add(item3), "Setup: Add item3 should not fail")
-		require.Equal(t, 3, f.mq.Len(), "Setup: Initial length should be 3")
-		require.Equal(t, uint64(60), f.mq.ByteSize(), "Setup: Initial byte size should be 60")
-
-		// Configure mock to clean up item2
-		f.mockQueue.CleanupFunc = func(p framework.PredicateFunc) ([]types.QueueItemAccessor, error) {
-			return []types.QueueItemAccessor{item2}, nil
-		}
+	flowKey := types.FlowKey{ID: "flow", Priority: 1}
 
-		cleaned, err := f.mq.Cleanup(func(i types.QueueItemAccessor) bool { return true })
-		require.NoError(t, err, "Cleanup should not return an error")
-		require.Len(t, cleaned, 1, "Cleanup should return one item")
-
-		// Assert final state
-		assert.Equal(t, 2, f.mq.Len(), "Length should be 2 after cleanup")
-		assert.Equal(t, uint64(40), f.mq.ByteSize(), "ByteSize should be 40 after cleanup")
-
-		// Assert reconciler state (3 adds, 1 cleanup)
-		lenDelta, byteSizeDelta, count := f.reconciler.getStats()
-		assert.Equal(t, int64(2), lenDelta, "Net length delta should be 2")
-		assert.Equal(t, int64(40), byteSizeDelta, "Net byte size delta should be 40")
-		assert.Equal(t, 4, count, "Reconciler should have been called 4 times")
-	})
-
-	// --- Test Drain ---
-	t.Run("Drain", func(t *testing.T) {
-		t.Parallel()
-		f := setupTestManagedQueue(t)
-		// Add initial items
-		require.NoError(t, f.mq.Add(item1), "Setup: Add item1 should not fail")
-		require.NoError(t, f.mq.Add(item2), "Setup: Add item2 should not fail")
-		require.Equal(t, 2, f.mq.Len(), "Setup: Initial length should be 2")
-		require.Equal(t, uint64(30), f.mq.ByteSize(), "Setup: Initial byte size should be 30")
-
-		// Configure mock to drain both items
-		f.mockQueue.DrainFunc = func() ([]types.QueueItemAccessor, error) {
-			return []types.QueueItemAccessor{item1, item2}, nil
-		}
+	testCases := []struct {
+		name                  string
+		setupMock             func(q *frameworkmocks.MockSafeQueue, items []types.QueueItemAccessor)
+		expectErr             bool
+		expectedLenDelta      int64
+		expectedByteSizeDelta int64
+	}{
+		{
+			name: "ShouldSucceed_AndDecrementStats",
+			setupMock: func(q *frameworkmocks.MockSafeQueue, items []types.QueueItemAccessor) {
+				q.CleanupFunc = func(_ framework.PredicateFunc) ([]types.QueueItemAccessor, error) {
+					return items, nil
+				}
+			},
+			expectErr:             false,
+			expectedLenDelta:      -2,
+			expectedByteSizeDelta: -125,
+		},
+		{
+			name: "ShouldSucceed_AndNotChangeStats_WhenNoItemsRemoved",
+			setupMock: func(q *frameworkmocks.MockSafeQueue, items []types.QueueItemAccessor) {
+				q.CleanupFunc = func(_ framework.PredicateFunc) ([]types.QueueItemAccessor, error) {
+					return nil, nil // Simulate no items matching predicate.
+				}
+			},
+			expectErr:             false,
+			expectedLenDelta:      0,
+			expectedByteSizeDelta: 0,
+		},
+		{
+			name: "ShouldFail_AndNotChangeStats_WhenUnderlyingQueueFails",
+			setupMock: func(q *frameworkmocks.MockSafeQueue, items []types.QueueItemAccessor) {
+				q.CleanupFunc = func(_ framework.PredicateFunc) ([]types.QueueItemAccessor, error) {
+					return nil, errors.New("cleanup failed")
+				}
+			},
+			expectErr:             true,
+			expectedLenDelta:      0,
+			expectedByteSizeDelta: 0,
+		},
+	}
 
-		drained, err := f.mq.Drain()
-		require.NoError(t, err, "Drain should not return an error")
-		require.Len(t, drained, 2, "Drain should return two items")
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			q := &frameworkmocks.MockSafeQueue{}
+			h := newMockedMqHarness(t, q, flowKey)
+			items := []types.QueueItemAccessor{
+				typesmocks.NewMockQueueItemAccessor(50, "req", flowKey),
+				typesmocks.NewMockQueueItemAccessor(75, "req", flowKey),
+			}
+			h.setupWithItems(items...)
+			tc.setupMock(q, items)
 
-		// Assert final state
-		assert.Zero(t, f.mq.Len(), "Length should be 0 after drain")
-		assert.Zero(t, f.mq.ByteSize(), "ByteSize should be 0 after drain")
+			_, err := h.mq.Cleanup(func(_ types.QueueItemAccessor) bool { return true })
 
-		// Assert reconciler state (2 adds, 1 drain)
-		lenDelta, byteSizeDelta, count := f.reconciler.getStats()
-		assert.Equal(t, int64(0), lenDelta, "Net length delta should be 0")
-		assert.Equal(t, int64(0), byteSizeDelta, "Net byte size delta should be 0")
-		assert.Equal(t, 3, count, "Reconciler should have been called 3 times")
-	})
+			if tc.expectErr {
+				require.Error(t, err, "Cleanup should have returned an error")
+			} else {
+				require.NoError(t, err, "Cleanup should not have returned an error")
+			}
+			assert.Equal(t, tc.expectedLenDelta, h.propagator.lenDelta.Load(), "Propagated length delta should be correct")
+			assert.Equal(t, tc.expectedByteSizeDelta, h.propagator.byteSizeDelta.Load(),
+				"Propagated byte size delta should be correct")
+		})
+	}
+}
 
-	// --- Test Error Paths ---
-	t.Run("ErrorPaths", func(t *testing.T) {
-		f := setupTestManagedQueue(t)
-		require.NoError(t, f.mq.Add(item1), "Setup: Adding an item should not fail")
-		initialLen, initialByteSize := f.mq.Len(), f.mq.ByteSize()
+func TestManagedQueue_Drain(t *testing.T) {
+	t.Parallel()
+	flowKey := types.FlowKey{ID: "flow", Priority: 1}
 
-		expectedErr := errors.New("internal error")
+	testCases := []struct {
+		name                  string
+		setupMock             func(q *frameworkmocks.MockSafeQueue, items []types.QueueItemAccessor)
+		expectErr             bool
+		expectedLenDelta      int64
+		expectedByteSizeDelta int64
+	}{
+		{
+			name: "ShouldSucceed_AndDecrementStats",
+			setupMock: func(q *frameworkmocks.MockSafeQueue, items []types.QueueItemAccessor) {
+				q.DrainFunc = func() ([]types.QueueItemAccessor, error) {
+					return items, nil
+				}
+			},
+			expectErr:             false,
+			expectedLenDelta:      -2,
+			expectedByteSizeDelta: -125,
+		},
+		{
+			name: "ShouldFail_AndNotChangeStats_WhenUnderlyingQueueFails",
+			setupMock: func(q *frameworkmocks.MockSafeQueue, items []types.QueueItemAccessor) {
+				q.DrainFunc = func() ([]types.QueueItemAccessor, error) {
+					return nil, errors.New("drain failed")
+				}
+			},
+			expectErr:             true,
+			expectedLenDelta:      0,
+			expectedByteSizeDelta: 0,
+		},
+	}
 
-		// Cleanup error
-		f.mockQueue.CleanupFunc = func(p framework.PredicateFunc) ([]types.QueueItemAccessor, error) {
-			return nil, expectedErr
-		}
-		_, err := f.mq.Cleanup(func(i types.QueueItemAccessor) bool { return true })
-		require.ErrorIs(t, err, expectedErr, "Cleanup should propagate error")
-		assert.Equal(t, initialLen, f.mq.Len(), "Len should not change on Cleanup error")
-		assert.Equal(t, initialByteSize, f.mq.ByteSize(), "ByteSize should not change on Cleanup error")
-
-		// Drain error
-		f.mockQueue.DrainFunc = func() ([]types.QueueItemAccessor, error) {
-			return nil, expectedErr
-		}
-		_, err = f.mq.Drain()
-		require.ErrorIs(t, err, expectedErr, "Drain should propagate error")
-		assert.Equal(t, initialLen, f.mq.Len(), "Len should not change on Drain error")
-		assert.Equal(t, initialByteSize, f.mq.ByteSize(), "ByteSize should not change on Drain error")
-	})
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			q := &frameworkmocks.MockSafeQueue{}
+			h := newMockedMqHarness(t, q, flowKey)
+			items := []types.QueueItemAccessor{
+				typesmocks.NewMockQueueItemAccessor(50, "req", flowKey),
+				typesmocks.NewMockQueueItemAccessor(75, "req", flowKey),
+			}
+			h.setupWithItems(items...)
+			tc.setupMock(q, items)
+
+			_, err := h.mq.Drain()
+
+			if tc.expectErr {
+				require.Error(t, err, "Drain should have returned an error")
+			} else {
+				require.NoError(t, err, "Drain should not have returned an error")
+			}
+			assert.Equal(t, tc.expectedLenDelta, h.propagator.lenDelta.Load(), "Propagated length delta should be correct")
+			assert.Equal(t, tc.expectedByteSizeDelta, h.propagator.byteSizeDelta.Load(),
+				"Propagated byte size delta should be correct")
+		})
+	}
 }
 
-func TestManagedQueue_FlowQueueAccessor(t *testing.T) {
+func TestManagedQueue_PanicOnUnderflow(t *testing.T) {
 	t.Parallel()
-	f := setupTestManagedQueue(t)
-	item := typesmocks.NewMockQueueItemAccessor(100, "req-1", "test-flow")
-
-	// Setup underlying queue state
-	f.mockQueue.PeekHeadV = item
-	f.mockQueue.PeekTailV = item
-	f.mockQueue.NameV = "MockQueue"
-	f.mockQueue.CapabilitiesV = []framework.QueueCapability{framework.CapabilityFIFO}
+	flowKey := types.FlowKey{ID: "flow", Priority: 1}
+	item := typesmocks.NewMockQueueItemAccessor(100, "req", flowKey)
+	q := &frameworkmocks.MockSafeQueue{}
+	q.AddFunc = func(types.QueueItemAccessor) error { return nil }
+	q.RemoveFunc = func(types.QueueItemHandle) (types.QueueItemAccessor, error) {
+		return item, nil
+	}
+	h := newMockedMqHarness(t, q, flowKey)
+
+	// Add and then successfully remove the item.
+	require.NoError(t, h.mq.Add(item), "Test setup: Add should succeed")
+	_, err := h.mq.Remove(item.Handle())
+	require.NoError(t, err, "Test setup: First Remove should succeed")
+	require.Zero(t, h.mq.Len(), "Test setup: Queue should be empty")
+
+	// Attempting to remove the same item again should cause a panic.
+	assert.PanicsWithValue(t,
+		fmt.Sprintf("invariant violation: managedQueue length for flow %s became negative (-1)", flowKey),
+		func() { _, _ = h.mq.Remove(item.Handle()) },
+		"A second removal of the same item should trigger a panic on length underflow",
+	)
+}
 
-	// Add an item to populate the managed queue's stats
-	require.NoError(t, f.mq.Add(item), "Setup: Adding an item should not fail")
+func TestManagedQueue_Signaling(t *testing.T) {
+	t.Parallel()
+	flowKey := types.FlowKey{ID: "flow", Priority: 1}
+	h := newRealMqHarness(t, flowKey)
+	item1 := typesmocks.NewMockQueueItemAccessor(100, "r1", flowKey)
+	item2 := typesmocks.NewMockQueueItemAccessor(50, "r2", flowKey)
+
+	// 1. Initial state: Empty
+	assert.Empty(t, h.signalRecorder.getSignals(), "No signals should be present on a new queue")
+
+	// 2. Transition: Empty -> NonEmpty
+	require.NoError(t, h.mq.Add(item1), "Adding an item should not fail")
+	assert.Equal(t, []queueStateSignal{queueStateSignalBecameNonEmpty}, h.signalRecorder.getSignals(),
+		"Should signal BecameNonEmpty on first add")
+
+	// 3. Steady state: NonEmpty -> NonEmpty
+	require.NoError(t, h.mq.Add(item2), "Adding a second item should not fail")
+	assert.Equal(t, []queueStateSignal{queueStateSignalBecameNonEmpty}, h.signalRecorder.getSignals(),
+		"No new signal should be sent when adding to a non-empty queue")
+
+	// 4. Steady state: NonEmpty -> NonEmpty
+	_, err := h.mq.Remove(item1.Handle())
+	require.NoError(t, err, "Removing an item should not fail")
+	assert.Equal(t, []queueStateSignal{queueStateSignalBecameNonEmpty}, h.signalRecorder.getSignals(),
+		"No new signal should be sent when removing from a multi-item queue")
+
+	// 5. Transition: NonEmpty -> Empty
+	_, err = h.mq.Remove(item2.Handle())
+	require.NoError(t, err, "Removing the last item should not fail")
+	expectedSignalSequence := []queueStateSignal{queueStateSignalBecameNonEmpty, queueStateSignalBecameEmpty}
+	assert.Equal(t, expectedSignalSequence, h.signalRecorder.getSignals(),
+		"Should signal BecameEmpty on removal of the last item")
+}
 
-	// Get the accessor
-	accessor := f.mq.FlowQueueAccessor()
+func TestManagedQueue_FlowQueueAccessor_ProxiesCalls(t *testing.T) {
+	t.Parallel()
+	flowKey := types.FlowKey{ID: "flow", Priority: 1}
+	q := &frameworkmocks.MockSafeQueue{}
+	harness := newMockedMqHarness(t, q, flowKey)
+	item := typesmocks.NewMockQueueItemAccessor(100, "req-1", flowKey)
+	q.PeekHeadV = item
+	q.PeekTailV = item
+	q.NameV = "MockQueue"
+	q.CapabilitiesV = []framework.QueueCapability{framework.CapabilityFIFO}
+	require.NoError(t, harness.mq.Add(item), "Test setup: Adding an item should not fail")
+
+	accessor := harness.mq.FlowQueueAccessor()
 	require.NotNil(t, accessor, "FlowQueueAccessor should not be nil")
 
-	// Assert that the accessor methods reflect the underlying state
-	assert.Equal(t, f.mq.Name(), accessor.Name(), "Accessor Name() should match managed queue")
-	assert.Equal(t, f.mq.Capabilities(), accessor.Capabilities(), "Accessor Capabilities() should match managed queue")
-	assert.Equal(t, f.mq.Len(), accessor.Len(), "Accessor Len() should match managed queue")
-	assert.Equal(t, f.mq.ByteSize(), accessor.ByteSize(), "Accessor ByteSize() should match managed queue")
-	assert.Equal(t, f.flowSpec, accessor.FlowSpec(), "Accessor FlowSpec() should match managed queue")
-	assert.Equal(t, f.mockComparator, accessor.Comparator(), "Accessor Comparator() should match the one from the policy")
-	assert.Equal(t, f.mockComparator, f.mq.Comparator(), "ManagedQueue Comparator() should match the one from the policy")
+	assert.Equal(t, harness.mq.Name(), accessor.Name(), "Accessor Name() should match managed queue")
+	assert.Equal(t, harness.mq.Capabilities(), accessor.Capabilities(),
+		"Accessor Capabilities() should match managed queue")
+	assert.Equal(t, harness.mq.Len(), accessor.Len(), "Accessor Len() should match managed queue")
+	assert.Equal(t, harness.mq.ByteSize(), accessor.ByteSize(), "Accessor ByteSize() should match managed queue")
+	assert.Equal(t, flowKey, accessor.FlowKey(), "Accessor FlowKey() should match managed queue")
+	assert.Equal(t, harness.mockPolicy.Comparator(), accessor.Comparator(),
+		"Accessor Comparator() should match the one from the policy")
+	assert.Equal(t, harness.mockPolicy.Comparator(), harness.mq.Comparator(),
+		"ManagedQueue Comparator() should also match the one from the policy")
 
 	peekedHead, err := accessor.PeekHead()
 	require.NoError(t, err, "Accessor PeekHead() should not return an error")
-	assert.Equal(t, item, peekedHead, "Accessor PeekHead() should return the correct item")
+	assert.Same(t, item, peekedHead, "Accessor PeekHead() should return the correct item instance")
 
 	peekedTail, err := accessor.PeekTail()
 	require.NoError(t, err, "Accessor PeekTail() should not return an error")
-	assert.Equal(t, item, peekedTail, "Accessor PeekTail() should return the correct item")
+	assert.Same(t, item, peekedTail, "Accessor PeekTail() should return the correct item instance")
 }
 
-func TestManagedQueue_Concurrency(t *testing.T) {
+// --- Concurrency Tests ---
+
+// TestManagedQueue_Concurrency_SignalingRace targets the race condition of a queue flapping between empty and non-empty
+// states.
+// It ensures the `BecameEmpty` and `BecameNonEmpty` signals are sent correctly in strict alternation, without
+// duplicates or missed signals.
+func TestManagedQueue_Concurrency_SignalingRace(t *testing.T) {
 	t.Parallel()
 
-	// Use a real `listqueue` since it's concurrent-safe.
-	lq, err := queue.NewQueueFromName(listqueue.ListQueueName, nil)
-	require.NoError(t, err, "Setup: creating a real listqueue should not fail")
+	const ops = 1000
+	flowKey := types.FlowKey{ID: "flow", Priority: 1}
+	h := newRealMqHarness(t, flowKey)
 
-	reconciler := &testStatsReconciler{}
-	flowSpec := types.FlowSpecification{ID: "conc-test-flow", Priority: 1}
-	mq := newManagedQueue(lq, nil, flowSpec, logr.Discard(), reconciler.reconcile)
+	var wg sync.WaitGroup
+	wg.Add(2)
 
-	const (
-		numGoroutines   = 20
-		opsPerGoroutine = 200
-		itemByteSize    = 10
-		initialItems    = 500
-	)
+	// Goroutine 1: Adder - continuously adds items.
+	go func() {
+		defer wg.Done()
+		for range ops {
+			_ = h.mq.Add(typesmocks.NewMockQueueItemAccessor(10, "req", flowKey))
+		}
+	}()
+
+	// Goroutine 2: Remover - continuously drains the queue.
+	go func() {
+		defer wg.Done()
+		for range ops {
+			// Drain is used to remove all items present in a single atomic operation.
+			_, _ = h.mq.Drain()
+		}
+	}()
 
-	var wg sync.WaitGroup
-	var successfulAdds, successfulRemoves atomic.Int64
+	wg.Wait()
 
-	// Use a channel as a concurrent-safe pool of handles for removal.
-	handles := make(chan types.QueueItemHandle, initialItems+(numGoroutines*opsPerGoroutine))
+	// Verification: The critical part of this test is to analyze the sequence of signals.
+	signals := h.signalRecorder.getSignals()
+	require.NotEmpty(t, signals, "At least some signals should have been generated")
 
-	// Pre-fill the queue to give removers something to do immediately.
-	for range initialItems {
-		item := typesmocks.NewMockQueueItemAccessor(uint64(itemByteSize), "initial", "flow")
-		require.NoError(t, mq.Add(item), "Setup: pre-filling queue should not fail")
-		handles <- item.Handle()
+	// The sequence must be a strict alternation of `BecameNonEmpty` and `BecameEmpty` signals.
+	// There should never be two of the same signal in a row.
+	for i := 0; i < len(signals)-1; i++ {
+		assert.NotEqual(t, signals[i], signals[i+1], "Signals at index %d and %d must not be duplicates", i, i+1)
 	}
-	// Reset reconciler stats after setup so we only measure the concurrent phase.
-	*reconciler = testStatsReconciler{}
 
-	// Launch goroutines to perform a mix of concurrent operations.
-	wg.Add(numGoroutines)
-	for i := range numGoroutines {
-		go func(routineID int) {
-			defer wg.Done()
-			for j := range opsPerGoroutine {
-				// Mix up operations between adding and removing.
-				if (routineID+j)%2 == 0 {
-					// Add operation
-					item := typesmocks.NewMockQueueItemAccessor(uint64(itemByteSize), "req", "flow")
-					if err := mq.Add(item); err == nil {
-						successfulAdds.Add(1)
-						handles <- item.Handle()
-					}
-				} else {
-					// Remove operation
-					select {
-					case handle := <-handles:
-						if _, err := mq.Remove(handle); err == nil {
-							successfulRemoves.Add(1)
-						}
-					default:
-						// No handles available, do nothing. This can happen if removers are faster than adders.
-					}
-				}
-			}
-		}(i)
-	}
-	wg.Wait()
+	// The first signal must be `BecameNonEmpty`.
+	assert.Equal(t, queueStateSignalBecameNonEmpty, signals[0], "The first signal must be BecameNonEmpty")
+}
 
-	// All concurrent operations are complete. Drain any remaining items to get the final count.
-	drainedItems, err := mq.Drain()
-	require.NoError(t, err, "Draining the queue at the end should not fail")
+// TestManagedQueue_Concurrency_ItemIntegrity validates that under high concurrency, the queue does not lose or
+// duplicate items and that the final propagated statistics are consistent with the operations performed.
+func TestManagedQueue_Concurrency_ItemIntegrity(t *testing.T) {
+	t.Parallel()
 
-	// Final consistency checks.
-	finalItemCount := len(drainedItems)
-	finalByteSize := mq.ByteSize()
+	const numGoRoutines = 10
+	const opsPerGoRoutine = 500
+	const itemByteSize = 10
 
-	// The number of items left in the queue should match our tracking.
-	expectedFinalItemCount := initialItems + int(successfulAdds.Load()) - int(successfulRemoves.Load())
-	assert.Equal(t, expectedFinalItemCount, finalItemCount, "Final item count should match initial + adds - removes")
+	flowKey := types.FlowKey{ID: "flow", Priority: 1}
+	h := newRealMqHarness(t, flowKey)
+	var wg sync.WaitGroup
+	wg.Add(numGoRoutines)
+
+	// Each goroutine will attempt to perform a mix of `Add` and `Remove` operations.
+	for range numGoRoutines {
+		go func() {
+			defer wg.Done()
+			for range opsPerGoRoutine {
+				// Add an item.
+				item := typesmocks.NewMockQueueItemAccessor(uint64(itemByteSize), "req", flowKey)
+				require.NoError(t, h.mq.Add(item), "Concurrent Add should not fail")
 
-	// The queue's internal stats must be zero after draining.
-	assert.Zero(t, mq.Len(), "Managed queue length must be zero after drain")
-	assert.Zero(t, finalByteSize, "Managed queue byte size must be zero after drain")
+				// Immediately try to remove it. This creates high contention on the queue's internal state.
+				_, _ = h.mq.Remove(item.Handle())
+			}
+		}()
+	}
+
+	wg.Wait()
 
-	// The net change reported to the reconciler must match the net change from the concurrent phase,
-	// plus the final drain.
-	netLenChangeDuringConcurrentPhase := successfulAdds.Load() - successfulRemoves.Load()
-	netByteSizeChangeDuringConcurrentPhase := netLenChangeDuringConcurrentPhase * itemByteSize
+	// After all operations, the queue should ideally be empty, but some removals might have failed if another goroutine
+	// got to it first. We drain any remaining items to get a final count.
+	_, err := h.mq.Drain()
+	require.NoError(t, err, "Final drain should not fail")
 
-	lenDelta, byteSizeDelta, _ := reconciler.getStats()
+	// Final State Verification
+	assert.Zero(t, h.mq.Len(), "Queue length must be zero after final drain")
+	assert.Zero(t, h.mq.ByteSize(), "Queue byte size must be zero after final drain")
 
-	expectedLenDelta := netLenChangeDuringConcurrentPhase - int64(finalItemCount)
-	expectedByteSizeDelta := netByteSizeChangeDuringConcurrentPhase - int64(uint64(finalItemCount)*itemByteSize)
+	// Statistical Integrity Verification
+	// The total number of propagated additions must equal the number of propagated removals.
+	lenDelta := h.propagator.lenDelta.Load()
+	byteSizeDelta := h.propagator.byteSizeDelta.Load()
 
-	assert.Equal(t, expectedLenDelta, lenDelta,
-		"Net length delta in reconciler should match the net change from all operations")
-	assert.Equal(t, expectedByteSizeDelta, byteSizeDelta,
-		"Net byte size delta in reconciler should match the net change from all operations")
+	assert.Equal(t, int64(0), lenDelta,
+		"Net length delta propagated must be zero, proving every add was matched by a remove delta")
+	assert.Equal(t, int64(0), byteSizeDelta, "Net byte size delta propagated must be zero")
 }
diff --git a/pkg/epp/flowcontrol/registry/registry_test.go b/pkg/epp/flowcontrol/registry/registry_test.go
new file mode 100644
index 0000000..fcefd86
--- /dev/null
+++ b/pkg/epp/flowcontrol/registry/registry_test.go
@@ -0,0 +1,809 @@
+/*
+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.
+*/
+
+package registry
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/go-logr/logr"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"k8s.io/utils/clock"
+	testclock "k8s.io/utils/clock/testing"
+
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework"
+	inter "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch"
+	intra "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/queue"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types/mocks"
+)
+
+const syncTimeout = 2 * time.Second
+
+// --- Test Harness and Mocks ---
+
+// registryTestHarness provides a fully initialized test harness for the `FlowRegistry`.
+type registryTestHarness struct {
+	t           *testing.T
+	ctx         context.Context
+	cancel      context.CancelFunc
+	fr          *FlowRegistry
+	config      Config
+	fakeClock   *testclock.FakeClock
+	activeItems map[types.FlowKey]types.QueueItemAccessor
+}
+
+type harnessOptions struct {
+	config            *Config
+	useFakeClock      bool
+	initialShardCount int
+}
+
+// newRegistryTestHarness creates and starts a new `FlowRegistry` for testing.
+func newRegistryTestHarness(t *testing.T, opts harnessOptions) *registryTestHarness {
+	t.Helper()
+
+	var config Config
+	if opts.config != nil {
+		config = *opts.config
+	} else {
+		config = Config{
+			FlowGCTimeout: 1 * time.Minute,
+			PriorityBands: []PriorityBandConfig{
+				{Priority: 10, PriorityName: "High"},
+				{Priority: 20, PriorityName: "Low"},
+			},
+		}
+	}
+	if opts.initialShardCount > 0 {
+		config.InitialShardCount = opts.initialShardCount
+	}
+
+	var clk clock.WithTickerAndDelayedExecution
+	var fakeClock *testclock.FakeClock
+	if opts.useFakeClock {
+		fakeClock = testclock.NewFakeClock(time.Now())
+		clk = fakeClock
+	}
+
+	fr, err := NewFlowRegistry(config, logr.Discard(), WithClock(clk))
+	require.NoError(t, err, "Test setup: NewFlowRegistry should not fail")
+
+	ctx, cancel := context.WithCancel(context.Background())
+	var wg sync.WaitGroup
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		fr.Run(ctx)
+	}()
+
+	t.Cleanup(func() {
+		cancel()
+		wg.Wait()
+	})
+
+	return &registryTestHarness{
+		t:           t,
+		ctx:         ctx,
+		cancel:      cancel,
+		fr:          fr,
+		config:      config,
+		fakeClock:   fakeClock,
+		activeItems: make(map[types.FlowKey]types.QueueItemAccessor),
+	}
+}
+
+// synchronize blocks until the `FlowRegistry`'s event loop has processed all preceding events.
+func (h *registryTestHarness) synchronize() {
+	h.t.Helper()
+	doneCh := make(chan struct{})
+	select {
+	case h.fr.events <- &syncEvent{doneCh: doneCh}:
+		select {
+		case <-doneCh:
+		case <-time.After(syncTimeout):
+			h.t.Fatalf("Timeout waiting for FlowRegistry synchronization ack")
+		}
+	case <-time.After(syncTimeout):
+		h.t.Fatalf("Timeout sending sync event to FlowRegistry (channel may be full)")
+	}
+}
+
+// waitForGCTick advances the fake clock to trigger a GC cycle and then blocks until that cycle is complete.
+// This works by sandwiching the clock step between two synchronization barriers.
+func (h *registryTestHarness) waitForGCTick() {
+	h.t.Helper()
+	require.NotNil(h.t, h.fakeClock, "waitForGCTick requires the fake clock to be enabled")
+	h.synchronize()
+	h.fakeClock.Step(h.config.FlowGCTimeout + time.Millisecond)
+	h.synchronize()
+}
+
+// setFlowActive makes a flow Active (by adding an item) or Idle (by removing it) and synchronizes the state change.
+func (h *registryTestHarness) setFlowActive(key types.FlowKey, active bool) {
+	h.t.Helper()
+	shard := h.getFirstActiveShard()
+	mq, err := shard.ManagedQueue(key)
+	require.NoError(h.t, err, "Failed to get managed queue to set flow activity for flow %s", key)
+
+	if active {
+		require.NotContains(h.t, h.activeItems, key, "Flow %s is already marked as Active in the test harness", key)
+		item := mocks.NewMockQueueItemAccessor(100, "req", key)
+		require.NoError(h.t, mq.Add(item), "Failed to add item to make flow %s Active", key)
+		h.activeItems[key] = item
+	} else {
+		item, ok := h.activeItems[key]
+		require.True(h.t, ok, "Flow %s was not Active in the test harness, cannot make it Idle", key)
+		_, err := mq.Remove(item.Handle())
+		require.NoError(h.t, err, "Failed to remove item to make flow %s Idle", key)
+		delete(h.activeItems, key)
+	}
+	h.synchronize()
+}
+
+func (h *registryTestHarness) getFirstActiveShard() contracts.RegistryShard {
+	h.t.Helper()
+	allShards := h.fr.Shards()
+	for _, s := range allShards {
+		if s.IsActive() {
+			return s
+		}
+	}
+	h.t.Fatalf("Failed to find any active shard in list of %d shards", len(allShards))
+	return nil
+}
+
+func (h *registryTestHarness) assertFlowExists(key types.FlowKey, msgAndArgs ...any) {
+	h.t.Helper()
+	allShards := h.fr.Shards()
+	require.NotEmpty(h.t, allShards, "Cannot assert flow existence without shards")
+
+	// It's sufficient to check one shard, as the registry guarantees consistency.
+	// A more robust check could iterate all, but this is a good balance.
+	_, err := allShards[0].ManagedQueue(key)
+	assert.NoError(h.t, err, msgAndArgs...)
+}
+
+func (h *registryTestHarness) assertFlowDoesNotExist(key types.FlowKey, msgAndArgs ...any) {
+	h.t.Helper()
+	allShards := h.fr.Shards()
+	// If there are no shards, the flow cannot exist.
+	if len(allShards) == 0 {
+		return
+	}
+	_, err := allShards[0].ManagedQueue(key)
+	assert.ErrorIs(h.t, err, contracts.ErrFlowInstanceNotFound, msgAndArgs...)
+}
+
+// --- Basic Tests ---
+
+func TestFlowRegistry_New_ErrorPaths(t *testing.T) {
+	t.Parallel()
+
+	t.Run("ShouldFail_WhenConfigIsInvalid", func(t *testing.T) {
+		t.Parallel()
+		_, err := NewFlowRegistry(Config{}, logr.Discard()) // No priority bands is invalid.
+		require.Error(t, err, "NewFlowRegistry should fail with an invalid config")
+	})
+
+	t.Run("ShouldFail_WhenInitialShardCreationFails", func(t *testing.T) {
+		t.Parallel()
+		failingInterFlowFactory := func(_ inter.RegisteredPolicyName) (framework.InterFlowDispatchPolicy, error) {
+			return nil, errors.New("injected shard creation failure")
+		}
+		configWithFailingFactory := Config{
+			PriorityBands: []PriorityBandConfig{{Priority: 10, PriorityName: "High"}},
+		}
+		cfg, err := NewConfig(configWithFailingFactory, withInterFlowDispatchPolicyFactory(failingInterFlowFactory))
+		require.NoError(t, err, "Test setup: creating the config object itself should not fail")
+
+		_, err = NewFlowRegistry(*cfg, logr.Discard())
+
+		require.Error(t, err, "NewFlowRegistry should fail when initial shard setup fails")
+		assert.Contains(t, err.Error(), "injected shard creation failure", "Error message should reflect the root cause")
+	})
+}
+
+// --- Administrative Method Tests ---
+
+func TestFlowRegistry_RegisterOrUpdateFlow(t *testing.T) {
+	t.Parallel()
+	h := newRegistryTestHarness(t, harnessOptions{})
+	key := types.FlowKey{ID: "test-flow", Priority: 10}
+	spec := types.FlowSpecification{Key: key}
+
+	err := h.fr.RegisterOrUpdateFlow(spec)
+	require.NoError(t, err, "Registering a new flow should succeed")
+	h.assertFlowExists(key, "Flow should exist in registry after initial registration")
+
+	err = h.fr.RegisterOrUpdateFlow(spec)
+	require.NoError(t, err, "Re-registering the same flow should be idempotent")
+	h.assertFlowExists(key, "Flow should still exist after idempotent re-registration")
+}
+
+func TestFlowRegistry_RegisterOrUpdateFlow_ErrorPaths(t *testing.T) {
+	t.Parallel()
+
+	// Define mock factories that can be injected to force specific failures.
+	failingPolicyFactory := func(name intra.RegisteredPolicyName) (framework.IntraFlowDispatchPolicy, error) {
+		return nil, errors.New("injected policy factory failure")
+	}
+	failingQueueFactory := func(name queue.RegisteredQueueName, c framework.ItemComparator) (framework.SafeQueue, error) {
+		return nil, errors.New("injected queue factory failure")
+	}
+
+	baseConfig := Config{
+		PriorityBands: []PriorityBandConfig{
+			{Priority: 10, PriorityName: "High"},
+		},
+	}
+
+	testCases := []struct {
+		name   string
+		spec   types.FlowSpecification
+		setup  func(h *registryTestHarness) // Surgical setup to induce failure.
+		errStr string                       // Expected substring in the error message.
+		errIs  error                        // Optional: Expected wrapped error.
+	}{
+		{
+			name:   "ShouldFail_WhenSpecIDIsEmpty",
+			spec:   types.FlowSpecification{Key: types.FlowKey{ID: "", Priority: 10}},
+			errStr: "flow ID cannot be empty",
+			errIs:  contracts.ErrFlowIDEmpty,
+		},
+		{
+			name:   "ShouldFail_WhenPriorityBandIsNotFound",
+			spec:   types.FlowSpecification{Key: types.FlowKey{ID: "flow", Priority: 99}},
+			errStr: "failed to get configuration for priority 99",
+			errIs:  contracts.ErrPriorityBandNotFound,
+		},
+		{
+			name: "ShouldFail_WhenPolicyFactoryFails",
+			spec: types.FlowSpecification{Key: types.FlowKey{ID: "flow", Priority: 10}},
+			setup: func(h *registryTestHarness) {
+				// After the registry is created with a valid config, we surgically replace the policy factory with one that is
+				// guaranteed to fail.
+				h.fr.config.intraFlowDispatchPolicyFactory = failingPolicyFactory
+			},
+			errStr: "failed to instantiate intra-flow policy",
+		},
+		{
+			name: "ShouldFail_WhenQueueFactoryFails",
+			spec: types.FlowSpecification{Key: types.FlowKey{ID: "flow", Priority: 10}},
+			setup: func(h *registryTestHarness) {
+				// Similarly, we inject a failing queue factory.
+				h.fr.config.queueFactory = failingQueueFactory
+			},
+			errStr: "failed to instantiate queue",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			h := newRegistryTestHarness(t, harnessOptions{config: &baseConfig})
+			if tc.setup != nil {
+				tc.setup(h)
+			}
+
+			err := h.fr.RegisterOrUpdateFlow(tc.spec)
+
+			require.Error(t, err, "RegisterOrUpdateFlow should have returned an error")
+			assert.Contains(t, err.Error(), tc.errStr, "Error message should contain expected text")
+			if tc.errIs != nil {
+				assert.ErrorIs(t, err, tc.errIs, "Error should match expected wrapped error")
+			}
+		})
+	}
+}
+
+func TestFlowRegistry_UpdateShardCount(t *testing.T) {
+	t.Parallel()
+
+	// stateForDrainingTest holds the specific item that will be placed on a shard that is about to be Drained.
+	// We need to pass it from the setup phase to the assertion phase.
+	type stateForDrainingTest struct {
+		drainingShardID string
+		item            types.QueueItemAccessor
+		flowKey         types.FlowKey
+	}
+
+	testCases := []struct {
+		name              string
+		initialShardCount int
+		targetShardCount  int
+		setup             func(h *registryTestHarness) any // Returns optional state for assertions
+		expectErrIs       error
+		expectErrStr      string
+		assertions        func(t *testing.T, h *registryTestHarness, state any)
+	}{
+		{
+			name:              "ShouldSucceed_OnScaleUp",
+			initialShardCount: 2,
+			targetShardCount:  4,
+			assertions: func(t *testing.T, h *registryTestHarness, _ any) {
+				assert.Len(t, h.fr.Shards(), 4, "Registry should have 4 shards after scale-up")
+				assert.Len(t, h.fr.activeShards, 4, "Registry should have 4 active shards")
+				assert.Empty(t, h.fr.drainingShards, "Registry should have 0 draining shards")
+			},
+		},
+		{
+			name:              "ShouldBeNoOp_WhenCountIsUnchanged",
+			initialShardCount: 2,
+			targetShardCount:  2,
+			assertions: func(t *testing.T, h *registryTestHarness, _ any) {
+				assert.Len(t, h.fr.Shards(), 2, "Shard count should remain unchanged")
+			},
+		},
+		{
+			name:              "ShouldSucceed_OnScaleUp_WithExistingFlows",
+			initialShardCount: 1,
+			targetShardCount:  2,
+			setup: func(h *registryTestHarness) any {
+				key := types.FlowKey{ID: "flow", Priority: 10}
+				require.NoError(t, h.fr.RegisterOrUpdateFlow(types.FlowSpecification{Key: key}),
+					"Test setup: failed to register flow")
+				return key // Pass the key to assertions
+			},
+			assertions: func(t *testing.T, h *registryTestHarness, state any) {
+				key := state.(types.FlowKey)
+				assert.Len(t, h.fr.Shards(), 2, "Registry should now have 2 shards")
+				h.assertFlowExists(key, "Flow must exist on all shards after scaling up")
+			},
+		},
+		{
+			name:              "ShouldFail_WhenShardCountIsInvalid_Zero",
+			initialShardCount: 1,
+			targetShardCount:  0,
+			expectErrIs:       contracts.ErrInvalidShardCount,
+		},
+		{
+			name:              "ShouldFail_WhenShardCountIsInvalid_Negative",
+			initialShardCount: 1,
+			targetShardCount:  -1,
+			expectErrIs:       contracts.ErrInvalidShardCount,
+		},
+		{
+			name:              "ShouldFailAndRollback_WhenFlowSyncFailsDuringScaleUp",
+			initialShardCount: 1,
+			targetShardCount:  2,
+			setup: func(h *registryTestHarness) any {
+				// Create a valid, existing flow in the registry.
+				key := types.FlowKey{ID: "flow", Priority: 10}
+				require.NoError(t, h.fr.RegisterOrUpdateFlow(types.FlowSpecification{Key: key}),
+					"Test setup: failed to register existing flow")
+
+				// Sabotage the system: Inject a policy factory that is guaranteed to fail.
+				// This will be called when the registry tries to create components for the existing flow on the *new* shard.
+				failingPolicyFactory := func(_ intra.RegisteredPolicyName) (framework.IntraFlowDispatchPolicy, error) {
+					return nil, errors.New("injected flow sync failure")
+				}
+				h.fr.mu.Lock()
+				h.fr.config.intraFlowDispatchPolicyFactory = failingPolicyFactory
+				h.fr.mu.Unlock()
+
+				return nil
+			},
+			expectErrStr: "injected flow sync failure",
+			assertions: func(t *testing.T, h *registryTestHarness, _ any) {
+				// The scale-up must have been aborted, leaving the registry in its original state.
+				assert.Len(t, h.fr.Shards(), 1, "Shard count should not have changed after a failed scale-up")
+			},
+		},
+		{
+			name:              "ShouldGracefullyDrain_OnScaleDown",
+			initialShardCount: 3,
+			targetShardCount:  2,
+			setup: func(h *registryTestHarness) any {
+				key := types.FlowKey{ID: "test-flow", Priority: 10}
+				require.NoError(t, h.fr.RegisterOrUpdateFlow(types.FlowSpecification{Key: key}),
+					"Test setup: failed to register flow")
+
+				allShards := h.fr.Shards()
+				require.Len(t, allShards, 3, "Test setup: Should start with 3 shards")
+				drainingShard := allShards[2] // The last shard will be chosen for draining.
+
+				mq, err := drainingShard.ManagedQueue(key)
+				require.NoError(t, err, "Test setup: failed to get queue on Draining shard")
+				item := mocks.NewMockQueueItemAccessor(100, "req", key)
+				require.NoError(t, mq.Add(item), "Test setup: failed to add item")
+				h.synchronize()
+
+				return &stateForDrainingTest{
+					drainingShardID: drainingShard.ID(),
+					item:            item,
+					flowKey:         key,
+				}
+			},
+			assertions: func(t *testing.T, h *registryTestHarness, state any) {
+				testState := state.(*stateForDrainingTest)
+				h.synchronize() // Ensure the Draining state has been processed.
+
+				// Assert the intermediate Draining state is correct.
+				shards := h.fr.Shards()
+				require.Len(t, shards, 3, "Registry should still track the Draining shard until it is empty")
+				var drainingShard contracts.RegistryShard
+				for _, s := range shards {
+					if s.ID() == testState.drainingShardID {
+						drainingShard = s
+					}
+				}
+				require.NotNil(t, drainingShard, "Draining shard should still be present in the list")
+				assert.False(t, drainingShard.IsActive(), "Target shard should be marked as not active")
+
+				// Assert that the item on the draining shard is still accessible.
+				mq, err := drainingShard.ManagedQueue(testState.flowKey)
+				require.NoError(t, err, "Should still be able to get queue from draining shard")
+				removedItem, err := mq.Remove(testState.item.Handle())
+				require.NoError(t, err, "Should be able to remove the item from the draining shard")
+				assert.Equal(t, testState.item.OriginalRequest().ID(), removedItem.OriginalRequest().ID(),
+					"Correct item was removed")
+
+				// After the item is removed, the shard becomes empty and should be fully garbage collected.
+				h.synchronize() // Process the `BecameDrained` event.
+				assert.Len(t, h.fr.Shards(), 2,
+					"Drained shard was not garbage collected from the registry after becoming empty")
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			h := newRegistryTestHarness(t, harnessOptions{initialShardCount: tc.initialShardCount})
+			var state any
+			if tc.setup != nil {
+				state = tc.setup(h)
+			}
+
+			err := h.fr.UpdateShardCount(tc.targetShardCount)
+
+			if tc.expectErrIs != nil || tc.expectErrStr != "" {
+				require.Error(t, err, "UpdateShardCount should have returned an error")
+				if tc.expectErrIs != nil {
+					assert.ErrorIs(t, err, tc.expectErrIs, "Error should be the expected type")
+				}
+				if tc.expectErrStr != "" {
+					assert.Contains(t, err.Error(), tc.expectErrStr, "Error message should contain expected substring")
+				}
+			} else {
+				require.NoError(t, err, "UpdateShardCount should not have returned an error")
+			}
+
+			if tc.assertions != nil {
+				tc.assertions(t, h, state)
+			}
+		})
+	}
+}
+
+// --- Stats and Observability Tests ---
+
+func TestFlowRegistry_Stats_And_ShardStats(t *testing.T) {
+	t.Parallel()
+	h := newRegistryTestHarness(t, harnessOptions{initialShardCount: 2})
+	keyHigh := types.FlowKey{ID: "flow-high", Priority: 10}
+	keyLow := types.FlowKey{ID: "flow-low", Priority: 20}
+	require.NoError(t, h.fr.RegisterOrUpdateFlow(types.FlowSpecification{Key: keyHigh}))
+	require.NoError(t, h.fr.RegisterOrUpdateFlow(types.FlowSpecification{Key: keyLow}))
+
+	// Add items to the queues. Note that these are distributed across 2 shards.
+	h.setFlowActive(keyHigh, true) // 100 bytes
+	h.setFlowActive(keyLow, true)  // 100 bytes
+	h.synchronize()
+
+	// Test global stats.
+	stats := h.fr.Stats()
+	assert.Equal(t, uint64(2), stats.TotalLen, "Global TotalLen should be correct")
+	assert.Equal(t, uint64(200), stats.TotalByteSize, "Global TotalByteSize should be correct")
+	assert.Equal(t, uint64(1), stats.PerPriorityBandStats[10].Len, "Global stats for priority 10 Len is incorrect")
+	assert.Equal(t, uint64(100), stats.PerPriorityBandStats[10].ByteSize,
+		"Global stats for priority 10 ByteSize is incorrect")
+
+	// Test shard stats.
+	shardStats := h.fr.ShardStats()
+	require.Len(t, shardStats, 2, "Should be stats for 2 shards")
+
+	var totalShardLen, totalShardBytes uint64
+	for _, ss := range shardStats {
+		totalShardLen += ss.TotalLen
+		totalShardBytes += ss.TotalByteSize
+	}
+	assert.Equal(t, stats.TotalLen, totalShardLen, "Sum of shard lengths should equal global length")
+	assert.Equal(t, stats.TotalByteSize, totalShardBytes, "Sum of shard byte sizes should equal global byte size")
+}
+
+// --- Garbage Collection Tests ---
+
+func TestFlowRegistry_GarbageCollection_IdleFlows(t *testing.T) {
+	t.Parallel()
+	key := types.FlowKey{ID: "gc-flow", Priority: 10}
+	spec := types.FlowSpecification{Key: key}
+
+	t.Run("ShouldCollectIdleFlow_AfterGracePeriod", func(t *testing.T) {
+		t.Parallel()
+		h := newRegistryTestHarness(t, harnessOptions{useFakeClock: true})
+		require.NoError(t, h.fr.RegisterOrUpdateFlow(spec), "Test setup: failed to register flow")
+		h.synchronize()
+
+		// A new flow is granted a one-cycle grace period. The Mark phase of the first GC cycle will see
+		// 1lastActiveGeneration==gcGenerationNewFlow` and "timestamp" it for survival.
+		h.waitForGCTick()
+		h.assertFlowExists(key, "A new Idle flow must survive its first GC cycle")
+
+		// In the second cycle, the flow is Idle and was not marked in the preceding cycle, so it is collected.
+		h.waitForGCTick()
+		h.assertFlowDoesNotExist(key, "An Idle flow must be collected after its grace period expires")
+	})
+
+	t.Run("ShouldNotCollectFlow_ThatWasRecentlyActive", func(t *testing.T) {
+		t.Parallel()
+		h := newRegistryTestHarness(t, harnessOptions{useFakeClock: true})
+		require.NoError(t, h.fr.RegisterOrUpdateFlow(spec), "Test setup: failed to register flow")
+		h.synchronize()
+
+		h.setFlowActive(key, true)
+		h.waitForGCTick()
+		h.assertFlowExists(key, "An Active flow should not be collected")
+		h.setFlowActive(key, false)
+
+		// The flow is now Idle, but it was marked as Active in the immediately preceding cycle, so it must survive this
+		// sweep.
+		h.waitForGCTick()
+		h.assertFlowExists(key, "A flow must not be collected in the cycle immediately after it becomes Idle")
+
+		// Having been Idle for a full cycle, it will now be collected.
+		h.waitForGCTick()
+		h.assertFlowDoesNotExist(key, "A flow must be collected after being Idle for a full cycle")
+	})
+
+	t.Run("ShouldNotCollectFlow_WhenActivityRacesWithGC", func(t *testing.T) {
+		t.Parallel()
+		h := newRegistryTestHarness(t, harnessOptions{useFakeClock: true})
+		require.NoError(t, h.fr.RegisterOrUpdateFlow(spec), "Test setup: failed to register flow")
+		h.synchronize()
+
+		// Establish a state where the flow is a candidate for GC on the *next* tick.
+		// This requires only getting it past its initial grace period.
+		h.waitForGCTick()
+		h.assertFlowExists(key, "Test setup failed: flow should exist after its grace period")
+
+		// Trigger the race: send a GC tick event but DO NOT wait for it to be processed.
+		h.fakeClock.Step(h.config.FlowGCTimeout + time.Millisecond)
+
+		// Immediately send a competing activity signal.
+		shard := h.getFirstActiveShard()
+		mq, err := shard.ManagedQueue(key)
+		require.NoError(t, err, "Test setup: failed to get managed queue for race")
+		require.NoError(t, mq.Add(mocks.NewMockQueueItemAccessor(100, "req", key)), "Test setup: failed to add item")
+
+		// Synchronize. The `onGCTick` runs first, but its "Verify" step will see the live item and abort.
+		h.synchronize()
+
+		// The flow must survive due to the "Trust but Verify" live check.
+		h.assertFlowExists(key, "Flow should survive GC due to the 'Trust but Verify' live check")
+	})
+
+	t.Run("ShouldBeBenign_WhenGCingAlreadyDeletedFlow", func(t *testing.T) {
+		t.Parallel()
+		h := newRegistryTestHarness(t, harnessOptions{useFakeClock: true})
+		require.NoError(t, h.fr.RegisterOrUpdateFlow(spec), "Test setup: failed to register flow")
+
+		h.waitForGCTick()
+		h.waitForGCTick() // The second tick collects the flow.
+		h.assertFlowDoesNotExist(key, "Test setup failed: flow was not collected")
+
+		// Manually (white-box) call the GC function again for the same key.
+		// It should return false and not panic.
+		var collected bool
+		assert.NotPanics(t, func() {
+			h.fr.mu.Lock()
+			collected = h.fr.garbageCollectFlowLocked(key)
+			h.fr.mu.Unlock()
+		}, "GCing a deleted flow should not panic")
+		assert.False(t, collected, "GCing a deleted flow should return false")
+	})
+
+	t.Run("ShouldAbortGC_WhenCacheIsActive", func(t *testing.T) {
+		t.Parallel()
+		h := newRegistryTestHarness(t, harnessOptions{useFakeClock: true})
+		require.NoError(t, h.fr.RegisterOrUpdateFlow(spec), "Test setup: failed to register flow")
+		h.setFlowActive(key, true) // Make the flow Active.
+
+		// Manually call the GC function. It should check the cache (`isIdle`) first and abort immediately without
+		// performing the "Verify" step.
+		var collected bool
+		assert.NotPanics(t, func() {
+			h.fr.mu.Lock()
+			collected = h.fr.garbageCollectFlowLocked(key)
+			h.fr.mu.Unlock()
+		}, "GCing an active flow should not panic")
+
+		assert.False(t, collected, "GCing an active flow should return false")
+		h.assertFlowExists(key, "Flow should still exist after aborted GC attempt")
+	})
+}
+
+func TestFlowRegistry_GarbageCollection_DrainedShards(t *testing.T) {
+	t.Parallel()
+	h := newRegistryTestHarness(t, harnessOptions{initialShardCount: 2})
+	key := types.FlowKey{ID: "test-flow", Priority: 10}
+	require.NoError(t, h.fr.RegisterOrUpdateFlow(types.FlowSpecification{Key: key}),
+		"Test setup: failed to register flow")
+
+	allShards := h.fr.Shards()
+	require.Len(t, allShards, 2, "Test setup: Should start with 2 shards")
+	drainingShard := allShards[1]
+
+	// Add an item to the shard that will be drained to prevent its immediate GC upon scale-down.
+	mq, err := drainingShard.ManagedQueue(key)
+	require.NoError(t, err, "Test setup: failed to get queue on Draining shard")
+	item := mocks.NewMockQueueItemAccessor(100, "req", key)
+	require.NoError(t, mq.Add(item), "Test setup: failed to add item")
+	h.synchronize()
+
+	// Scale down, which marks the shard as Draining.
+	require.NoError(t, h.fr.UpdateShardCount(1), "Scaling down should succeed")
+	h.synchronize()
+
+	require.Len(t, h.fr.Shards(), 2, "Registry should still track the Draining shard")
+	assert.False(t, drainingShard.IsActive(), "Target shard should be marked as Draining")
+
+	// Remove the last item, which triggers the `ShardBecameDrained` signal.
+	_, err = mq.Remove(item.Handle())
+	require.NoError(t, err, "Failed to remove last item from Draining shard")
+	h.synchronize() // Process the `BecameDrained` signal.
+
+	assert.Len(t, h.fr.Shards(), 1, "Drained shard was not garbage collected from the registry")
+
+	// Crucial memory leak check: ensure the shard's ID was purged from all flow states.
+	h.fr.mu.Lock()
+	defer h.fr.mu.Unlock()
+	flowState, exists := h.fr.flowStates[key]
+	require.True(t, exists, "Flow state should still exist for the Active flow")
+	assert.NotContains(t, flowState.emptyOnShards, drainingShard.ID(),
+		"Drained shard ID must be purged from flow state map to prevent memory leaks")
+}
+
+// --- Event Handling and State Machine Edge Cases ---
+
+func TestFlowRegistry_EventHandling_StaleSignals(t *testing.T) {
+	t.Parallel()
+
+	t.Run("ShouldIgnoreQueueSignal_ForGarbageCollectedFlow", func(t *testing.T) {
+		t.Parallel()
+		h := newRegistryTestHarness(t, harnessOptions{useFakeClock: true})
+		key := types.FlowKey{ID: "flow", Priority: 10}
+		require.NoError(t, h.fr.RegisterOrUpdateFlow(types.FlowSpecification{Key: key}),
+			"Test setup: failed to register flow")
+
+		h.setFlowActive(key, true)
+		h.synchronize()
+
+		// Manually (white-box) delete the flow from the registry's state map.
+		// This simulates a race where a signal is in-flight while the flow is being GC'd.
+		h.fr.mu.Lock()
+		delete(h.fr.flowStates, key)
+		h.fr.mu.Unlock()
+
+		// Now, trigger a `BecameEmpty` signal by making the flow Idle.
+		// The `onQueueStateChanged` handler should receive this, find no `flowState`, and return gracefully.
+		assert.NotPanics(t, func() {
+			h.setFlowActive(key, false)
+		}, "Registry should not panic when receiving a signal for a deleted flow")
+	})
+}
+
+// --- Invariant Panic Tests ---
+
+func TestFlowRegistry_InvariantPanics(t *testing.T) {
+	t.Parallel()
+
+	t.Run("ShouldPanic_WhenQueueIsMissingDuringGC", func(t *testing.T) {
+		t.Parallel()
+		h := newRegistryTestHarness(t, harnessOptions{})
+		key := types.FlowKey{ID: "flow", Priority: 10}
+		require.NoError(t, h.fr.RegisterOrUpdateFlow(types.FlowSpecification{Key: key}),
+			"Test setup: failed to register flow")
+
+		// Manually corrupt the state by removing the queue from a shard, creating an invariant violation.
+		// This is white-box testing to validate a critical failure path.
+		h.fr.mu.Lock()
+		shard := h.fr.activeShards[0]
+		shard.mu.Lock()
+		delete(shard.priorityBands[key.Priority].queues, key.ID)
+		shard.mu.Unlock()
+		h.fr.mu.Unlock()
+
+		// Manually prepare the flow for GC by setting its generation. This bypasses the need for the async `Run` loop,
+		// allowing us to catch the panic in this goroutine.
+		h.fr.mu.Lock()
+		h.fr.flowStates[key].lastActiveGeneration = h.fr.gcGeneration - 1 // Make it a candidate
+		h.fr.mu.Unlock()
+
+		assert.Panics(t, func() { // Value assertion is too brittle as it wraps an error.
+			h.fr.mu.Lock()
+			defer h.fr.mu.Unlock()
+			_ = h.fr.garbageCollectFlowLocked(key)
+		}, "GC must panic when a queue is missing for a tracked flow state")
+	})
+
+	t.Run("ShouldPanic_OnSignalFromUntrackedDrainingShard", func(t *testing.T) {
+		t.Parallel()
+		h := newRegistryTestHarness(t, harnessOptions{})
+		fakeEvent := &shardStateChangedEvent{
+			shardID: "shard-does-not-exist",
+			signal:  shardStateSignalBecameDrained,
+		}
+
+		assert.PanicsWithValue(t,
+			"invariant violation: shard shard-does-not-exist not found in draining map during GC",
+			func() {
+				h.fr.mu.Lock()
+				defer h.fr.mu.Unlock()
+				h.fr.onShardStateChanged(fakeEvent)
+			}, "Panic should occur when a non-existent shard signals it has drained")
+	})
+
+	t.Run("ShouldPanic_OnStatsForUnknownPriority", func(t *testing.T) {
+		t.Parallel()
+		h := newRegistryTestHarness(t, harnessOptions{})
+		assert.PanicsWithValue(t,
+			"invariant violation: priority band (999) stats missing during propagation",
+			func() {
+				h.fr.propagateStatsDelta(999, 1, 1) // 999 is not a configured priority.
+			},
+			"Panic should occur when stats are propagated for an unknown priority")
+	})
+
+	t.Run("ShouldPanic_OnComponentMismatchDuringSync", func(t *testing.T) {
+		t.Parallel()
+		h := newRegistryTestHarness(t, harnessOptions{})
+		spec := types.FlowSpecification{Key: types.FlowKey{ID: "flow", Priority: 10}}
+
+		assert.PanicsWithValue(t,
+			fmt.Sprintf("invariant violation: shard/queue/policy count mismatch during commit for flow %s", spec.Key),
+			func() {
+				h.fr.mu.Lock()
+				defer h.fr.mu.Unlock()
+				// Call with a mismatched number of components (0) vs. shards (1).
+				h.fr.applyFlowSynchronizationLocked(spec, []flowComponents{})
+			},
+			"Panic should occur when component count mismatches shard count")
+	})
+
+	t.Run("ShouldPanic_OnStatsAggregation_WithStateMismatch", func(t *testing.T) {
+		t.Parallel()
+		h := newRegistryTestHarness(t, harnessOptions{})
+
+		// Manually corrupt the state by adding a stats entry for a priority that does not exist in the configuration.
+		h.fr.mu.Lock()
+		h.fr.perPriorityBandStats[999] = &bandStats{}
+		h.fr.mu.Unlock()
+
+		assert.Panics(t, // Value assertion is too brittle as it wraps an error.
+			func() { h.fr.Stats() },
+			"Stats() must panic when perPriorityBandStats contains a key not in the config")
+	})
+}
diff --git a/pkg/epp/flowcontrol/registry/shard_test.go b/pkg/epp/flowcontrol/registry/shard_test.go
index fe7f96e..aa1f06d 100644
--- a/pkg/epp/flowcontrol/registry/shard_test.go
+++ b/pkg/epp/flowcontrol/registry/shard_test.go
@@ -17,8 +17,7 @@ limitations under the License.
 package registry
 
 import (
-	"errors"
-	"sort"
+	"sync"
 	"testing"
 
 	"github.com/go-logr/logr"
@@ -27,329 +26,489 @@ import (
 
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework"
+	frameworkmocks "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/mocks"
 	inter "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch"
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/policies/interflow/dispatch/besthead"
 	intra "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch"
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/queue"
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/queue/listqueue"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types/mocks"
 )
 
-// shardTestFixture holds the components needed for a `registryShard` test.
-type shardTestFixture struct {
-	config *Config
-	shard  *registryShard
+// --- Test Harness and Mocks ---
+
+// shardTestHarness holds the components needed for a `registryShard` test.
+type shardTestHarness struct {
+	t               *testing.T
+	globalConfig    *Config
+	shard           *registryShard
+	shardSignaler   *mockShardSignalRecorder
+	statsPropagator *mockStatsPropagator
+	callbacks       shardCallbacks // Callbacks to be passed to `newShard`
 }
 
-// setupTestShard creates a new test fixture for testing the `registryShard`.
-func setupTestShard(t *testing.T) *shardTestFixture {
+// newShardTestHarness creates a new test harness for testing the `registryShard`.
+// It correctly simulates the parent registry's behavior by creating a global `Config`, partitioning it, and then
+// creating a shard from the partitioned `ShardConfig`.
+func newShardTestHarness(t *testing.T) *shardTestHarness {
 	t.Helper()
 
-	config := &Config{
+	globalConfig, err := NewConfig(Config{
 		PriorityBands: []PriorityBandConfig{
-			{
-				Priority:     10,
-				PriorityName: "High",
-			},
-			{
-				Priority:     20,
-				PriorityName: "Low",
-			},
+			{Priority: 10, PriorityName: "High"},
+			{Priority: 20, PriorityName: "Low"},
 		},
+	})
+	require.NoError(t, err, "Test setup: validating and defaulting config should not fail")
+
+	shardSignaler := &mockShardSignalRecorder{}
+	statsPropagator := &mockStatsPropagator{}
+	callbacks := shardCallbacks{
+		propagateStatsDelta: statsPropagator.propagate,
+		// For most tests, we don't care about queue signals, so this is a no-op.
+		signalQueueState: func(string, types.FlowKey, queueStateSignal) {},
+		signalShardState: shardSignaler.signal,
 	}
-	// Apply defaults to the master config first, as the parent registry would.
-	err := config.validateAndApplyDefaults()
-	require.NoError(t, err, "Setup: validating and defaulting config should not fail")
-
-	// The parent registry would partition the config. For a single shard test, we can use the defaulted one directly.
-	shard, err := newShard("test-shard-1", config, logr.Discard(), nil)
-	require.NoError(t, err, "Setup: newShard should not return an error")
-	require.NotNil(t, shard, "Setup: newShard should return a non-nil shard")
-
-	return &shardTestFixture{
-		config: config,
-		shard:  shard,
+
+	// Partition the global config to create a shard-specific config.
+	shardConfig := globalConfig.partition(0, 1)
+
+	shard, err := newShard("test-shard-1", shardConfig, logr.Discard(), callbacks, inter.NewPolicyFromName)
+	require.NoError(t, err, "Test setup: newShard should not return an error")
+
+	return &shardTestHarness{
+		t:               t,
+		globalConfig:    globalConfig,
+		shard:           shard,
+		shardSignaler:   shardSignaler,
+		statsPropagator: statsPropagator,
+		callbacks:       callbacks,
 	}
 }
 
-// _reconcileFlow_testOnly is a test helper that simulates the future admin logic for adding or updating a flow.
-// It creates a `managedQueue` and correctly populates the `priorityBands` and `activeFlows` maps.
-// This helper is intended to be replaced by the real `reconcileFlow` method in a future PR.
-func (s *registryShard) _reconcileFlow_testOnly(
-	t *testing.T,
-	flowSpec types.FlowSpecification,
-	isActive bool,
-) *managedQueue {
-	t.Helper()
-
-	band, ok := s.priorityBands[flowSpec.Priority]
-	require.True(t, ok, "Setup: priority band %d should exist", flowSpec.Priority)
+// synchronizeFlowWithMocks is a test helper that simulates the parent registry's logic for calling `synchronizeFlow` on
+// the shard, but with mock plugins to ensure test isolation.
+func (h *shardTestHarness) synchronizeFlowWithMocks(key types.FlowKey, q framework.SafeQueue) {
+	h.t.Helper()
+	spec := types.FlowSpecification{Key: key}
+	mockPolicy := &frameworkmocks.MockIntraFlowDispatchPolicy{}
+	h.shard.synchronizeFlow(spec, mockPolicy, q)
+}
 
-	lq, err := queue.NewQueueFromName(listqueue.ListQueueName, nil)
-	require.NoError(t, err, "Setup: creating a real listqueue should not fail")
+// synchronizeFlowWithRealQueue is a test helper that uses a real queue implementation.
+// This is essential for integration and concurrency tests where the interaction between the shard and a real queue is
+// being validated.
+func (h *shardTestHarness) synchronizeFlowWithRealQueue(key types.FlowKey) {
+	h.t.Helper()
+	spec := types.FlowSpecification{Key: key}
+
+	// This logic correctly mimics the parent registry's instantiation process.
+	policy, err := intra.NewPolicyFromName(defaultIntraFlowDispatchPolicy)
+	require.NoError(h.t, err, "Test setup: failed to create real intra-flow policy")
+	q, err := queue.NewQueueFromName(defaultQueue, policy.Comparator())
+	require.NoError(h.t, err, "Test setup: failed to create real queue")
+	h.shard.synchronizeFlow(spec, policy, q)
+}
 
-	mq := newManagedQueue(
-		lq,
-		band.defaultIntraFlowDispatchPolicy,
-		flowSpec,
-		logr.Discard(),
-		func(lenDelta, byteSizeDelta int64) { s.reconcileStats(flowSpec.Priority, lenDelta, byteSizeDelta) },
-	)
-	require.NotNil(t, mq, "Setup: newManagedQueue should not return nil")
+// addItem adds an item to a specific flow on the shard, failing the test on any error.
+func (h *shardTestHarness) addItem(key types.FlowKey, size uint64) types.QueueItemAccessor {
+	h.t.Helper()
+	mq, err := h.shard.ManagedQueue(key)
+	require.NoError(h.t, err, "Helper addItem: failed to get queue for flow %v", key)
+	item := mocks.NewMockQueueItemAccessor(size, "req", key)
+	require.NoError(h.t, mq.Add(item), "Helper addItem: failed to add item to queue")
+	return item
+}
 
-	band.queues[flowSpec.ID] = mq
-	if isActive {
-		s.activeFlows[flowSpec.ID] = mq
-	}
+// removeItem removes an item from a specific flow, failing the test on any error.
+func (h *shardTestHarness) removeItem(key types.FlowKey, item types.QueueItemAccessor) {
+	h.t.Helper()
+	mq, err := h.shard.ManagedQueue(key)
+	require.NoError(h.t, err, "Helper removeItem: failed to get queue for flow %v", key)
+	_, err = mq.Remove(item.Handle())
+	require.NoError(h.t, err, "Helper removeItem: failed to remove item from queue")
+}
 
-	return mq
+// mockShardSignalRecorder is a thread-safe helper for recording shard state signals.
+type mockShardSignalRecorder struct {
+	mu      sync.Mutex
+	signals []shardStateSignal
 }
 
-func TestNewShard(t *testing.T) {
-	t.Parallel()
-	f := setupTestShard(t)
-
-	assert.Equal(t, "test-shard-1", f.shard.ID(), "ID should be set correctly")
-	assert.True(t, f.shard.IsActive(), "A new shard should be active")
-	require.Len(t, f.shard.priorityBands, 2, "Should have 2 priority bands")
-
-	// Check that priority levels are sorted correctly
-	assert.Equal(t, []uint{10, 20}, f.shard.AllOrderedPriorityLevels(), "Priority levels should be ordered")
-
-	// Check band 10
-	band10, ok := f.shard.priorityBands[10]
-	require.True(t, ok, "Priority band 10 should exist")
-	assert.Equal(t, uint(10), band10.config.Priority, "Band 10 should have correct priority")
-	assert.Equal(t, "High", band10.config.PriorityName, "Band 10 should have correct name")
-	assert.NotNil(t, band10.interFlowDispatchPolicy, "Inter-flow policy for band 10 should be instantiated")
-	assert.NotNil(t, band10.defaultIntraFlowDispatchPolicy,
-		"Default intra-flow policy for band 10 should be instantiated")
-	assert.Equal(t, besthead.BestHeadPolicyName, band10.interFlowDispatchPolicy.Name(),
-		"Correct default inter-flow policy should be used")
-	assert.Equal(t, fcfs.FCFSPolicyName, band10.defaultIntraFlowDispatchPolicy.Name(),
-		"Correct default intra-flow policy should be used")
-
-	// Check band 20
-	band20, ok := f.shard.priorityBands[20]
-	require.True(t, ok, "Priority band 20 should exist")
-	assert.Equal(t, uint(20), band20.config.Priority, "Band 20 should have correct priority")
-	assert.Equal(t, "Low", band20.config.PriorityName, "Band 20 should have correct name")
-	assert.NotNil(t, band20.interFlowDispatchPolicy, "Inter-flow policy for band 20 should be instantiated")
-	assert.NotNil(t, band20.defaultIntraFlowDispatchPolicy,
-		"Default intra-flow policy for band 20 should be instantiated")
+func (r *mockShardSignalRecorder) signal(_ string, signal shardStateSignal) {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	r.signals = append(r.signals, signal)
 }
 
-// TestNewShard_ErrorPaths modifies global plugin registries, so it cannot be run in parallel with other tests.
-func TestNewShard_ErrorPaths(t *testing.T) {
-	baseConfig := &Config{
-		PriorityBands: []PriorityBandConfig{{
-			Priority:                10,
-			PriorityName:            "High",
-			IntraFlowDispatchPolicy: fcfs.FCFSPolicyName,
-			InterFlowDispatchPolicy: besthead.BestHeadPolicyName,
-			Queue:                   listqueue.ListQueueName,
-		}},
-	}
-	require.NoError(t, baseConfig.validateAndApplyDefaults(), "Setup: base config should be valid")
+func (r *mockShardSignalRecorder) getSignals() []shardStateSignal {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	signalsCopy := make([]shardStateSignal, len(r.signals))
+	copy(signalsCopy, r.signals)
+	return signalsCopy
+}
 
-	t.Run("Invalid InterFlow Policy", func(t *testing.T) {
-		// Register a mock policy that always fails to instantiate
-		failingPolicyName := inter.RegisteredPolicyName("failing-inter-policy")
-		inter.MustRegisterPolicy(failingPolicyName, func() (framework.InterFlowDispatchPolicy, error) {
-			return nil, errors.New("inter-flow instantiation failed")
-		})
+// --- Basic Tests ---
 
-		badConfig := *baseConfig
-		badConfig.PriorityBands[0].InterFlowDispatchPolicy = failingPolicyName
+func TestShard_New(t *testing.T) {
+	t.Parallel()
+	h := newShardTestHarness(t)
 
-		_, err := newShard("test", &badConfig, logr.Discard(), nil)
-		require.Error(t, err, "newShard should fail with an invalid inter-flow policy")
-	})
+	assert.Equal(t, "test-shard-1", h.shard.ID(), "ID should be set correctly")
+	assert.True(t, h.shard.IsActive(), "A new shard should be active by default")
+	assert.Equal(t, []uint{10, 20}, h.shard.AllOrderedPriorityLevels(), "Priority levels should be correctly ordered")
 
-	t.Run("Invalid IntraFlow Policy", func(t *testing.T) {
-		// Register a mock policy that always fails to instantiate
-		failingPolicyName := intra.RegisteredPolicyName("failing-intra-policy")
-		intra.MustRegisterPolicy(failingPolicyName, func() (framework.IntraFlowDispatchPolicy, error) {
-			return nil, errors.New("intra-flow instantiation failed")
-		})
+	bandHigh, ok := h.shard.priorityBands[10]
+	require.True(t, ok, "High priority band should exist")
+	assert.Equal(t, "High", bandHigh.config.PriorityName, "High priority band should have correct name")
+	assert.NotNil(t, bandHigh.interFlowDispatchPolicy, "Inter-flow policy should be instantiated")
+	assert.Equal(t, string(defaultInterFlowDispatchPolicy), bandHigh.interFlowDispatchPolicy.Name(),
+		"Correct default inter-flow policy should be used")
+}
 
-		badConfig := *baseConfig
-		badConfig.PriorityBands[0].IntraFlowDispatchPolicy = failingPolicyName
+func TestShard_New_ErrorPaths(t *testing.T) {
+	t.Parallel()
 
-		_, err := newShard("test", &badConfig, logr.Discard(), nil)
-		require.Error(t, err, "newShard should fail with an invalid intra-flow policy")
+	t.Run("ShouldFail_WhenInterFlowPolicyIsMissing", func(t *testing.T) {
+		t.Parallel()
+		shardConfig := &ShardConfig{
+			PriorityBands: []ShardPriorityBandConfig{{Priority: 10, PriorityName: "High"}},
+		}
+		_, err := newShard("test-shard-1", shardConfig, logr.Discard(), shardCallbacks{}, inter.NewPolicyFromName)
+		assert.Error(t, err, "newShard should fail when missing a configured inter-flow policy")
 	})
 }
 
 func TestShard_Stats(t *testing.T) {
 	t.Parallel()
-	f := setupTestShard(t)
-
-	// Add a queue and some items to test stats aggregation
-	mq := f.shard._reconcileFlow_testOnly(t, types.FlowSpecification{ID: "flow1", Priority: 10}, true)
+	h := newShardTestHarness(t)
+	flowKeyHigh := types.FlowKey{ID: "flow1", Priority: 10}
+	h.synchronizeFlowWithMocks(flowKeyHigh, &frameworkmocks.MockSafeQueue{})
+	h.addItem(flowKeyHigh, 100)
+	h.addItem(flowKeyHigh, 50)
 
-	// Add items
-	require.NoError(t, mq.Add(mocks.NewMockQueueItemAccessor(100, "req1", "flow1")), "Adding item should not fail")
-	require.NoError(t, mq.Add(mocks.NewMockQueueItemAccessor(50, "req2", "flow1")), "Adding item should not fail")
+	stats := h.shard.Stats()
 
-	stats := f.shard.Stats()
-
-	// Check shard-level stats
 	assert.Equal(t, uint64(2), stats.TotalLen, "Total length should be 2")
 	assert.Equal(t, uint64(150), stats.TotalByteSize, "Total byte size should be 150")
 
-	// Check per-band stats
-	require.Len(t, stats.PerPriorityBandStats, 2, "Should have stats for 2 bands")
-	band10Stats := stats.PerPriorityBandStats[10]
-	assert.Equal(t, uint(10), band10Stats.Priority, "Band 10 stats should have correct priority")
-	assert.Equal(t, uint64(2), band10Stats.Len, "Band 10 length should be 2")
-	assert.Equal(t, uint64(150), band10Stats.ByteSize, "Band 10 byte size should be 150")
-
-	band20Stats := stats.PerPriorityBandStats[20]
-	assert.Equal(t, uint(20), band20Stats.Priority, "Band 20 stats should have correct priority")
-	assert.Zero(t, band20Stats.Len, "Band 20 length should be 0")
-	assert.Zero(t, band20Stats.ByteSize, "Band 20 byte size should be 0")
+	bandHighStats := stats.PerPriorityBandStats[10]
+	assert.Equal(t, uint64(2), bandHighStats.Len, "High priority band length should be 2")
+	assert.Equal(t, uint64(150), bandHighStats.ByteSize, "High priority band byte size should be 150")
+
+	bandLowStats := stats.PerPriorityBandStats[20]
+	assert.Zero(t, bandLowStats.Len, "Low priority band length should be 0")
 }
 
-func TestShard_Accessors(t *testing.T) {
+func TestShard_Accessors_SuccessPaths(t *testing.T) {
 	t.Parallel()
-	f := setupTestShard(t)
-
-	flowID := "test-flow"
-	activePriority := uint(10)
-	drainingPriority := uint(20)
-
-	// Setup state with one active and one draining queue for the same flow
-	activeQueue := f.shard._reconcileFlow_testOnly(t, types.FlowSpecification{
-		ID:       flowID,
-		Priority: activePriority,
-	}, true)
-	drainingQueue := f.shard._reconcileFlow_testOnly(t, types.FlowSpecification{
-		ID:       flowID,
-		Priority: drainingPriority,
-	}, false)
-
-	t.Run("ActiveManagedQueue", func(t *testing.T) {
+	h := newShardTestHarness(t)
+	flowKey := types.FlowKey{ID: "test-flow", Priority: 10}
+	h.synchronizeFlowWithRealQueue(flowKey)
+
+	t.Run("ManagedQueue_ShouldReturnCorrectInstance", func(t *testing.T) {
 		t.Parallel()
-		retrievedActiveQueue, err := f.shard.ActiveManagedQueue(flowID)
-		require.NoError(t, err, "ActiveManagedQueue should not error for an existing flow")
-		assert.Same(t, activeQueue, retrievedActiveQueue, "Should return the correct active queue")
+		mq, err := h.shard.ManagedQueue(flowKey)
 
-		_, err = f.shard.ActiveManagedQueue("non-existent-flow")
-		require.Error(t, err, "ActiveManagedQueue should error for a non-existent flow")
-		assert.ErrorIs(t, err, contracts.ErrFlowInstanceNotFound, "Error should be ErrFlowInstanceNotFound")
+		require.NoError(t, err, "ManagedQueue should not error for an existing flow")
+		require.NotNil(t, mq, "Returned ManagedQueue should not be nil")
+		accessor := mq.FlowQueueAccessor()
+		assert.Equal(t, flowKey, accessor.FlowKey(), "Returned queue should have the correct flow key")
 	})
 
-	t.Run("ManagedQueue", func(t *testing.T) {
+	t.Run("IntraFlowDispatchPolicy_ShouldReturnCorrectInstance", func(t *testing.T) {
 		t.Parallel()
-		retrievedDrainingQueue, err := f.shard.ManagedQueue(flowID, drainingPriority)
-		require.NoError(t, err, "ManagedQueue should not error for a draining queue")
-		assert.Same(t, drainingQueue, retrievedDrainingQueue, "Should return the correct draining queue")
+		policy, err := h.shard.IntraFlowDispatchPolicy(flowKey)
 
-		_, err = f.shard.ManagedQueue(flowID, 99) // Non-existent priority
-		require.Error(t, err, "ManagedQueue should error for a non-existent priority")
-		assert.ErrorIs(t, err, contracts.ErrPriorityBandNotFound, "Error should be ErrPriorityBandNotFound")
-
-		_, err = f.shard.ManagedQueue("non-existent-flow", activePriority)
-		require.Error(t, err, "ManagedQueue should error for a non-existent flow in an existing priority")
-		assert.ErrorIs(t, err, contracts.ErrFlowInstanceNotFound, "Error should be ErrFlowInstanceNotFound")
+		require.NoError(t, err, "IntraFlowDispatchPolicy should not error for an existing flow")
+		require.NotNil(t, policy, "Returned policy should not be nil")
+		assert.Equal(t, string(defaultIntraFlowDispatchPolicy), policy.Name(),
+			"Should return the correct default policy for the band")
 	})
 
-	t.Run("IntraFlowDispatchPolicy", func(t *testing.T) {
+	t.Run("InterFlowDispatchPolicy_ShouldReturnCorrectInstance", func(t *testing.T) {
 		t.Parallel()
-		retrievedActivePolicy, err := f.shard.IntraFlowDispatchPolicy(flowID, activePriority)
-		require.NoError(t, err, "IntraFlowDispatchPolicy should not error for an active instance")
-		assert.Same(t, activeQueue.dispatchPolicy, retrievedActivePolicy,
-			"Should return the policy from the active instance")
-
-		_, err = f.shard.IntraFlowDispatchPolicy("non-existent-flow", activePriority)
-		require.Error(t, err, "IntraFlowDispatchPolicy should error for a non-existent flow")
-		assert.ErrorIs(t, err, contracts.ErrFlowInstanceNotFound, "Error should be ErrFlowInstanceNotFound")
+		policy, err := h.shard.InterFlowDispatchPolicy(uint(10))
 
-		_, err = f.shard.IntraFlowDispatchPolicy(flowID, 99) // Non-existent priority
-		require.Error(t, err, "IntraFlowDispatchPolicy should error for a non-existent priority")
-		assert.ErrorIs(t, err, contracts.ErrPriorityBandNotFound, "Error should be ErrPriorityBandNotFound")
+		require.NoError(t, err, "InterFlowDispatchPolicy should not error for an existing priority")
+		require.NotNil(t, policy, "Returned policy should not be nil")
+		assert.Equal(t, string(defaultInterFlowDispatchPolicy), policy.Name(),
+			"Should return the correct default policy for the band")
 	})
+}
 
-	t.Run("InterFlowDispatchPolicy", func(t *testing.T) {
-		t.Parallel()
-		retrievedInterPolicy, err := f.shard.InterFlowDispatchPolicy(activePriority)
-		require.NoError(t, err, "InterFlowDispatchPolicy should not error for an existing priority")
-		assert.Same(t, f.shard.priorityBands[activePriority].interFlowDispatchPolicy, retrievedInterPolicy,
-			"Should return the correct inter-flow policy")
+func TestShard_Accessors_ErrorPaths(t *testing.T) {
+	t.Parallel()
+	h := newShardTestHarness(t)
+	flowKey := types.FlowKey{ID: "flow-a", Priority: 10}
+	h.synchronizeFlowWithMocks(flowKey, &frameworkmocks.MockSafeQueue{})
+
+	testCases := []struct {
+		name      string
+		action    func() error
+		expectErr error
+	}{
+		{
+			name:      "ManagedQueue_PriorityNotFound",
+			action:    func() error { _, err := h.shard.ManagedQueue(types.FlowKey{ID: "flow-a", Priority: 99}); return err },
+			expectErr: contracts.ErrPriorityBandNotFound,
+		},
+		{
+			name:      "ManagedQueue_FlowNotFound",
+			action:    func() error { _, err := h.shard.ManagedQueue(types.FlowKey{ID: "missing", Priority: 10}); return err },
+			expectErr: contracts.ErrFlowInstanceNotFound,
+		},
+		{
+			name:      "InterFlowDispatchPolicy_PriorityNotFound",
+			action:    func() error { _, err := h.shard.InterFlowDispatchPolicy(99); return err },
+			expectErr: contracts.ErrPriorityBandNotFound,
+		},
+		{
+			name: "IntraFlowDispatchPolicy_PriorityNotFound",
+			action: func() error {
+				_, err := h.shard.IntraFlowDispatchPolicy(types.FlowKey{ID: "flow-a", Priority: 99})
+				return err
+			},
+			expectErr: contracts.ErrPriorityBandNotFound,
+		},
+		{
+			name: "IntraFlowDispatchPolicy_FlowNotFound",
+			action: func() error {
+				_, err := h.shard.IntraFlowDispatchPolicy(types.FlowKey{ID: "missing", Priority: 10})
+				return err
+			},
+			expectErr: contracts.ErrFlowInstanceNotFound,
+		},
+	}
 
-		_, err = f.shard.InterFlowDispatchPolicy(99) // Non-existent priority
-		require.Error(t, err, "InterFlowDispatchPolicy should error for a non-existent priority")
-		assert.ErrorIs(t, err, contracts.ErrPriorityBandNotFound, "Error should be ErrPriorityBandNotFound")
-	})
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			err := tc.action()
+			require.Error(t, err, "Action should have returned an error")
+			assert.ErrorIs(t, err, tc.expectErr, "Error should wrap the expected sentinel error")
+		})
+	}
 }
 
 func TestShard_PriorityBandAccessor(t *testing.T) {
 	t.Parallel()
-	f := setupTestShard(t)
 
-	// Setup shard state for the tests
-	p1, p2 := uint(10), uint(20)
-	f.shard._reconcileFlow_testOnly(t, types.FlowSpecification{ID: "flow1", Priority: p1}, true)
-	f.shard._reconcileFlow_testOnly(t, types.FlowSpecification{ID: "flow1", Priority: p2}, false)
-	f.shard._reconcileFlow_testOnly(t, types.FlowSpecification{ID: "flow2", Priority: p1}, true)
+	// The shard will have two flows in the "High" priority band and one in the "Low" band.
+	h := newShardTestHarness(t)
+	highPriorityKey1 := types.FlowKey{ID: "flow1", Priority: 10}
+	highPriorityKey2 := types.FlowKey{ID: "flow2", Priority: 10}
+	lowPriorityKey := types.FlowKey{ID: "flow3", Priority: 20}
+
+	h.synchronizeFlowWithMocks(highPriorityKey1, &frameworkmocks.MockSafeQueue{})
+	h.synchronizeFlowWithMocks(highPriorityKey2, &frameworkmocks.MockSafeQueue{})
+	h.synchronizeFlowWithMocks(lowPriorityKey, &frameworkmocks.MockSafeQueue{})
 
-	t.Run("Accessor for existing priority", func(t *testing.T) {
+	t.Run("ShouldFail_WhenPriorityDoesNotExist", func(t *testing.T) {
 		t.Parallel()
-		accessor, err := f.shard.PriorityBandAccessor(p1)
-		require.NoError(t, err, "PriorityBandAccessor should not fail for existing priority")
+		_, err := h.shard.PriorityBandAccessor(99)
+		require.Error(t, err, "PriorityBandAccessor should fail for a non-existent priority")
+		assert.ErrorIs(t, err, contracts.ErrPriorityBandNotFound, "Error should be ErrPriorityBandNotFound")
+	})
+
+	t.Run("ShouldSucceed_WhenPriorityExists", func(t *testing.T) {
+		t.Parallel()
+		accessor, err := h.shard.PriorityBandAccessor(10)
+		require.NoError(t, err, "PriorityBandAccessor should not fail for an existing priority")
 		require.NotNil(t, accessor, "Accessor should not be nil")
 
-		t.Run("Properties", func(t *testing.T) {
+		t.Run("Properties_ShouldReturnCorrectValues", func(t *testing.T) {
 			t.Parallel()
-			assert.Equal(t, p1, accessor.Priority(), "Accessor should have correct priority")
-			assert.Equal(t, "High", accessor.PriorityName(), "Accessor should have correct priority name")
+			assert.Equal(t, uint(10), accessor.Priority(), "Accessor should have the correct priority")
+			assert.Equal(t, "High", accessor.PriorityName(), "Accessor should have the correct priority name")
 		})
 
-		t.Run("FlowIDs", func(t *testing.T) {
+		t.Run("FlowKeys_ShouldReturnAllKeysInBand", func(t *testing.T) {
 			t.Parallel()
-			flowIDs := accessor.FlowIDs()
-			sort.Strings(flowIDs)
-			assert.Equal(t, []string{"flow1", "flow2"}, flowIDs,
-				"Accessor should return correct flow IDs for the priority band")
+			keys := accessor.FlowKeys()
+			expectedKeys := []types.FlowKey{highPriorityKey1, highPriorityKey2}
+			assert.ElementsMatch(t, expectedKeys, keys,
+				"Accessor should return all flow keys for the priority band")
 		})
 
-		t.Run("Queue", func(t *testing.T) {
+		t.Run("Queue_ShouldReturnCorrectAccessor", func(t *testing.T) {
 			t.Parallel()
 			q := accessor.Queue("flow1")
-			require.NotNil(t, q, "Accessor should return queue for flow1")
-			assert.Equal(t, p1, q.FlowSpec().Priority, "Queue should have the correct priority")
-			assert.Nil(t, accessor.Queue("non-existent"), "Accessor should return nil for non-existent flow")
+			require.NotNil(t, q, "Accessor should return a non-nil accessor for an existing flow")
+			assert.Equal(t, highPriorityKey1, q.FlowKey(), "Queue accessor should have the correct flow key")
+			assert.Nil(t, accessor.Queue("non-existent"), "Accessor should return nil for a non-existent flow")
 		})
 
-		t.Run("IterateQueues", func(t *testing.T) {
+		t.Run("IterateQueues_ShouldVisitAllQueuesInBand", func(t *testing.T) {
 			t.Parallel()
-			var iteratedFlows []string
+			var iteratedKeys []types.FlowKey
 			accessor.IterateQueues(func(queue framework.FlowQueueAccessor) bool {
-				iteratedFlows = append(iteratedFlows, queue.FlowSpec().ID)
+				iteratedKeys = append(iteratedKeys, queue.FlowKey())
 				return true
 			})
-			sort.Strings(iteratedFlows)
-			assert.Equal(t, []string{"flow1", "flow2"}, iteratedFlows, "IterateQueues should visit all flows in the band")
+			expectedKeys := []types.FlowKey{highPriorityKey1, highPriorityKey2}
+			assert.ElementsMatch(t, expectedKeys, iteratedKeys, "IterateQueues should visit all flows in the band")
 		})
 
-		t.Run("IterateQueues with early exit", func(t *testing.T) {
+		t.Run("IterateQueues_ShouldExitEarly_WhenCallbackReturnsFalse", func(t *testing.T) {
 			t.Parallel()
-			var iteratedFlows []string
+			var iterationCount int
 			accessor.IterateQueues(func(queue framework.FlowQueueAccessor) bool {
-				iteratedFlows = append(iteratedFlows, queue.FlowSpec().ID)
-				return false // Exit after first item
+				iterationCount++
+				return false // Signal to stop iteration immediately.
 			})
-			assert.Len(t, iteratedFlows, 1, "IterateQueues should exit early if callback returns false")
+			assert.Equal(t, 1, iterationCount, "IterateQueues should exit after the first item")
 		})
 	})
+}
+
+// --- Lifecycle and State Management Tests ---
 
-	t.Run("Error on non-existent priority", func(t *testing.T) {
+func TestShard_SynchronizeFlow(t *testing.T) {
+	t.Parallel()
+	h := newShardTestHarness(t)
+	flowKey := types.FlowKey{ID: "flow1", Priority: 10}
+
+	// Synchronization should create the queue.
+	h.synchronizeFlowWithMocks(flowKey, &frameworkmocks.MockSafeQueue{})
+	mq1, err := h.shard.ManagedQueue(flowKey)
+	require.NoError(t, err, "Queue should exist after first synchronization")
+	assert.Contains(t, h.shard.priorityBands[10].queues, flowKey.ID,
+		"Queue should be in the correct priority band map")
+
+	// Synchronization should be idempotent.
+	h.synchronizeFlowWithMocks(flowKey, &frameworkmocks.MockSafeQueue{})
+	mq2, err := h.shard.ManagedQueue(flowKey)
+	require.NoError(t, err, "Queue should still exist after second synchronization")
+	assert.Same(t, mq1, mq2, "Queue instance should not have been replaced on second sync")
+}
+
+func TestShard_GarbageCollect(t *testing.T) {
+	t.Parallel()
+	h := newShardTestHarness(t)
+	flowKey := types.FlowKey{ID: "flow1", Priority: 10}
+	h.synchronizeFlowWithMocks(flowKey, &frameworkmocks.MockSafeQueue{})
+	require.Contains(t, h.shard.priorityBands[10].queues, flowKey.ID, "Test setup: queue must exist before GC")
+
+	h.shard.garbageCollectLocked(flowKey)
+	assert.NotContains(t, h.shard.priorityBands[10].queues, flowKey.ID,
+		"Queue should have been removed from the priority band")
+}
+
+// --- Draining Lifecycle and Concurrency Tests ---
+
+func TestShard_Lifecycle_Draining(t *testing.T) {
+	t.Parallel()
+
+	t.Run("ShouldTransitionToDraining_WhenMarkedWhileNonEmpty", func(t *testing.T) {
 		t.Parallel()
-		_, err := f.shard.PriorityBandAccessor(99)
-		require.Error(t, err, "PriorityBandAccessor should fail for non-existent priority")
-		assert.ErrorIs(t, err, contracts.ErrPriorityBandNotFound, "Error should be ErrPriorityBandNotFound")
+		h := newShardTestHarness(t)
+		flowKey := types.FlowKey{ID: "flow1", Priority: 10}
+		h.synchronizeFlowWithMocks(flowKey, &frameworkmocks.MockSafeQueue{})
+		h.addItem(flowKey, 100)
+
+		h.shard.markAsDraining()
+		assert.False(t, h.shard.IsActive(), "Shard should no longer be active")
+		assert.Equal(t, componentStatusDraining, componentStatus(h.shard.status.Load()), "Shard status should be Draining")
+	})
+
+	t.Run("ShouldTransitionToDrainedAndSignal_WhenMarkedWhileEmpty", func(t *testing.T) {
+		t.Parallel()
+		h := newShardTestHarness(t)
+		h.shard.markAsDraining()
+		assert.Equal(t, componentStatusDrained, componentStatus(h.shard.status.Load()), "Shard status should be Drained")
+		assert.Equal(t, []shardStateSignal{shardStateSignalBecameDrained}, h.shardSignaler.getSignals(),
+			"Should have sent BecameDrained signal")
+	})
+
+	t.Run("ShouldTransitionToDrainedAndSignal_WhenLastItemIsRemoved", func(t *testing.T) {
+		t.Parallel()
+		h := newShardTestHarness(t)
+		flowKey := types.FlowKey{ID: "flow1", Priority: 10}
+		h.synchronizeFlowWithRealQueue(flowKey)
+		item := h.addItem(flowKey, 100)
+
+		h.shard.markAsDraining()
+		require.Equal(t, componentStatusDraining, componentStatus(h.shard.status.Load()),
+			"Shard should be Draining while it contains items")
+
+		h.removeItem(flowKey, item)
+		assert.Equal(t, componentStatusDrained, componentStatus(h.shard.status.Load()),
+			"Shard should become Drained after last item is removed")
+		assert.Len(t, h.shardSignaler.getSignals(), 1, "A signal should have been sent")
+	})
+}
+
+// TestShard_Concurrency_DrainingRace targets the race between `markAsDraining()` and the shard becoming empty via
+// concurrent item removals. It proves the atomic CAS correctly arbitrates the race and signals exactly once.
+func TestShard_Concurrency_DrainingRace(t *testing.T) {
+	t.Parallel()
+	h := newShardTestHarness(t)
+	flowKey := types.FlowKey{ID: "flow1", Priority: 10}
+	h.synchronizeFlowWithRealQueue(flowKey)
+	item := h.addItem(flowKey, 1)
+
+	var wg sync.WaitGroup
+	wg.Add(2)
+
+	// Goroutine 1: Attempts to mark the shard as draining.
+	go func() {
+		defer wg.Done()
+		h.shard.markAsDraining()
+	}()
+
+	// Goroutine 2: Concurrently removes the single item.
+	go func() {
+		defer wg.Done()
+		h.removeItem(flowKey, item)
+	}()
+
+	wg.Wait()
+
+	// Verification: No matter which operation "won" the race, the final state must be Drained, and the signal must have
+	// been sent exactly once.
+	assert.Equal(t, componentStatusDrained, componentStatus(h.shard.status.Load()), "Final state must be Drained")
+	assert.Len(t, h.shardSignaler.getSignals(), 1, "BecameDrained signal must be sent exactly once")
+}
+
+// --- Invariant Panic Tests ---
+
+func TestShard_PanicOnCorruption(t *testing.T) {
+	t.Parallel()
+
+	t.Run("ShouldPanic_WhenStatsPropagatedForUnknownPriority", func(t *testing.T) {
+		t.Parallel()
+		h := newShardTestHarness(t)
+		invalidPriority := uint(99)
+		assert.Panics(t, func() {
+			// This simulates a corrupted callback from a rogue `managedQueue`.
+			h.shard.propagateStatsDelta(invalidPriority, 1, 1)
+		}, "propagateStatsDelta must panic for an unknown priority")
+	})
+
+	t.Run("ShouldPanic_WhenUpdatingConfigWithMissingPriority", func(t *testing.T) {
+		t.Parallel()
+		h := newShardTestHarness(t)
+
+		// Create a new config that is missing one of the shard's existing bands (priority 20).
+		newGlobalConfig, _ := NewConfig(Config{
+			PriorityBands: []PriorityBandConfig{{Priority: 10, PriorityName: "High-Updated"}},
+		})
+		newShardConfig := newGlobalConfig.partition(0, 1)
+
+		assert.Panics(t, func() {
+			h.shard.updateConfig(newShardConfig)
+		}, "updateConfig must panic when an existing priority is missing from the new config")
+	})
+
+	t.Run("ShouldPanic_WhenSynchronizingFlowWithMissingPriority", func(t *testing.T) {
+		t.Parallel()
+		h := newShardTestHarness(t)
+		assert.Panics(t, func() {
+			h.shard.synchronizeFlow(
+				types.FlowSpecification{Key: types.FlowKey{ID: "flow", Priority: 99}},
+				&frameworkmocks.MockIntraFlowDispatchPolicy{},
+				&frameworkmocks.MockSafeQueue{})
+		}, "synchronizeFlow must panic for an unknown priority")
 	})
 }
diff --git a/pkg/epp/metrics/collectors/inference_pool_test.go b/pkg/epp/metrics/collectors/inference_pool_test.go
index 533d7f3..dcac3b3 100644
--- a/pkg/epp/metrics/collectors/inference_pool_test.go
+++ b/pkg/epp/metrics/collectors/inference_pool_test.go
@@ -49,7 +49,7 @@ var (
 )
 
 func TestNoMetricsCollected(t *testing.T) {
-	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second, time.Second*2)
+	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
 	datastore := datastore.NewDatastore(context.Background(), pmf)
 
 	collector := &inferencePoolMetricsCollector{
@@ -67,7 +67,7 @@ func TestMetricsCollected(t *testing.T) {
 			pod1NamespacedName: pod1Metrics,
 		},
 	}
-	pmf := backendmetrics.NewPodMetricsFactory(pmc, time.Millisecond, time.Second*2)
+	pmf := backendmetrics.NewPodMetricsFactory(pmc, time.Millisecond)
 	ds := datastore.NewDatastore(context.Background(), pmf)
 
 	scheme := runtime.NewScheme()
@@ -80,7 +80,7 @@ func TestMetricsCollected(t *testing.T) {
 			Name: "test-pool",
 		},
 		Spec: v1.InferencePoolSpec{
-			TargetPortNumber: 8000,
+			TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8000))}},
 		},
 	}
 	_ = ds.PoolSet(context.Background(), fakeClient, inferencePool)
diff --git a/pkg/epp/plugins/plugin_state_test.go b/pkg/epp/plugins/plugin_state_test.go
new file mode 100644
index 0000000..0de65c2
--- /dev/null
+++ b/pkg/epp/plugins/plugin_state_test.go
@@ -0,0 +1,134 @@
+/*
+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.
+*/
+
+package plugins
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+
+	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
+)
+
+// pluginTestData implements the StateData interface for testing purposes.
+// It provides a simple string value that can be stored and retrieved.
+type pluginTestData struct {
+	value string
+}
+
+// Clone implements the StateData interface, creating a deep copy of the data.
+func (d *pluginTestData) Clone() StateData {
+	if d == nil {
+		return nil
+	}
+	return &pluginTestData{value: d.value}
+}
+
+// TestPluginState_ReadWrite verifies the basic operations of PluginState:
+// - Writing data for a request
+// - Reading the data back
+// - Deleting the data and confirming it's removed
+func TestPluginState_ReadWrite(t *testing.T) {
+	ctx := logutil.NewTestLoggerIntoContext(context.Background())
+
+	state := NewPluginState(ctx)
+
+	req1 := "req1"
+	key := StateKey("key")
+	data1 := "bar1"
+	req2 := "req2"
+	data2 := "bar2"
+
+	// Write data to the state storage
+	state.Write(req1, key, &pluginTestData{value: data1})
+	state.Write(req2, key, &pluginTestData{value: data2})
+
+	// Read back the req1 data and verify its content
+	readData, err := state.Read(req1, key)
+	assert.NoError(t, err)
+	td, ok := readData.(*pluginTestData)
+	assert.True(t, ok, "should be able to cast to pluginTestData")
+	assert.Equal(t, data1, td.value)
+
+	// Delete the req2 data and verify content that was read before is still valid
+	readData, err = state.Read(req2, key)
+	assert.NoError(t, err)
+	state.Delete(req2)
+	td, ok = readData.(*pluginTestData)
+	assert.True(t, ok, "should be able to cast to pluginTestData")
+	assert.Equal(t, data2, td.value)
+	// try to read again aftet deletion, verify error
+	readData, err = state.Read(req2, key)
+	assert.Equal(t, ErrNotFound, err)
+	assert.Nil(t, readData, "expected no data after delete")
+
+	// Read back the req1 data and verify its content after the req2 deleted
+	readData, err = state.Read(req1, key)
+	assert.NoError(t, err)
+	td, ok = readData.(*pluginTestData)
+	assert.True(t, ok, "should be able to cast to pluginTestData")
+	assert.Equal(t, data1, td.value)
+}
+
+// TestReadPluginStateKey tests the generic helper function ReadPluginStateKey which provides
+// type-safe access to stored data. It verifies:
+// - Successful type assertion and data retrieval
+// - Error handling for non-existent keys
+func TestReadPluginStateKey(t *testing.T) {
+	ctx := logutil.NewTestLoggerIntoContext(context.Background())
+	state := NewPluginState(ctx)
+
+	requestID := "req-1"
+	key := StateKey("foo")
+	data := &pluginTestData{value: "bar"}
+
+	state.Write(requestID, key, data)
+
+	// Read
+	val, err := ReadPluginStateKey[*pluginTestData](state, requestID, key)
+	assert.NoError(t, err)
+	assert.Equal(t, "bar", val.value)
+
+	// Not Found
+	_, err = ReadPluginStateKey[*pluginTestData](state, "not-exist", key)
+	assert.Equal(t, ErrNotFound, err)
+}
+
+// TestPluginState_Cleanup verifies the automatic cleanup of stale data.
+// It tests that data which hasn't been accessed for longer than stalenessThreshold
+// is properly removed from the storage.
+func TestPluginState_Cleanup(t *testing.T) {
+	ctx := logutil.NewTestLoggerIntoContext(context.Background())
+
+	state := NewPluginState(ctx)
+
+	requestID := "req-stale"
+	key := StateKey("foo")
+	data := &pluginTestData{value: "bar"}
+
+	state.Write(requestID, key, data)
+
+	// Manually set last access time to far in the past
+	state.requestToLastAccessTime.Store(requestID, time.Now().Add(-2*stalenessThreshold))
+	// Manually CleanUp
+	state.cleanStaleRequests()
+
+	_, err := state.Read(requestID, key)
+	assert.Equal(t, ErrNotFound, err)
+}
diff --git a/pkg/epp/requestcontrol/director_test.go b/pkg/epp/requestcontrol/director_test.go
index 3a98b28..e7968d4 100644
--- a/pkg/epp/requestcontrol/director_test.go
+++ b/pkg/epp/requestcontrol/director_test.go
@@ -34,7 +34,6 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
-	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend"
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
@@ -81,20 +80,19 @@ func TestDirector_HandleRequest(t *testing.T) {
 	// InferenceObjective definitions
 	ioFoodReview := testutil.MakeInferenceObjective("ioFoodReview").
 		CreationTimestamp(metav1.Unix(1000, 0)).
-		Criticality(v1alpha2.Critical).
+		Priority(2).
 		ObjRef()
 	ioFoodReviewSheddable := testutil.MakeInferenceObjective("imFoodReviewSheddable").
 		CreationTimestamp(metav1.Unix(1000, 0)).
-		Criticality(v1alpha2.Sheddable).
+		Priority(-1).
 		ObjRef()
 	ioFoodReviewResolve := testutil.MakeInferenceObjective("imFoodReviewResolve").
 		CreationTimestamp(metav1.Unix(1000, 0)).
-		Criticality(v1alpha2.Standard).
-		TargetModel("resolved-target-model-A").
+		Priority(1).
 		ObjRef()
 
 	// Datastore setup
-	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second, time.Second*2)
+	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
 	ds := datastore.NewDatastore(t.Context(), pmf)
 	ds.ObjectiveSet(ioFoodReview)
 	ds.ObjectiveSet(ioFoodReviewResolve)
@@ -103,9 +101,11 @@ func TestDirector_HandleRequest(t *testing.T) {
 	pool := &v1.InferencePool{
 		ObjectMeta: metav1.ObjectMeta{Name: "test-pool", Namespace: "default"},
 		Spec: v1.InferencePoolSpec{
-			TargetPortNumber: int32(8000),
-			Selector: map[v1.LabelKey]v1.LabelValue{
-				"app": "inference",
+			TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8000))}},
+			Selector: v1.LabelSelector{
+				MatchLabels: map[v1.LabelKey]v1.LabelValue{
+					"app": "inference",
+				},
 			},
 		},
 	}
@@ -177,6 +177,7 @@ func TestDirector_HandleRequest(t *testing.T) {
 		wantErrCode            string                   // Expected errutil code string
 		wantReqCtx             *handlers.RequestContext // Fields to check in the returned RequestContext
 		wantMutatedBodyModel   string                   // Expected model in reqCtx.Request.Body after PostDispatch
+		targetModelName        string                   // Expected model name after target model resolution
 	}{
 		{
 			name: "successful completions request (critical, saturation ignored)",
@@ -199,9 +200,10 @@ func TestDirector_HandleRequest(t *testing.T) {
 			},
 			wantMutatedBodyModel:   model,
 			inferenceObjectiveName: objectiveName,
+			targetModelName:        model,
 		},
 		{
-			name: "successful chat completions request (critical, saturation ignored)",
+			name: "successful chat completions request (default critical, saturation ignored)",
 			reqBodyMap: map[string]any{
 				"model": model,
 				"messages": []any{
@@ -211,11 +213,11 @@ func TestDirector_HandleRequest(t *testing.T) {
 					},
 				},
 			},
+			mockSaturationDetector: &mockSaturationDetector{isSaturated: true},
 			schedulerMockSetup: func(m *mockScheduler) {
 				m.scheduleResults = defaultSuccessfulScheduleResults
 			},
 			wantReqCtx: &handlers.RequestContext{
-				ObjectiveKey:    objectiveName,
 				TargetModelName: model,
 				TargetPod: &backend.Pod{
 					NamespacedName: types.NamespacedName{Namespace: "default", Name: "pod1"},
@@ -223,8 +225,8 @@ func TestDirector_HandleRequest(t *testing.T) {
 				},
 				TargetEndpoint: "192.168.1.100:8000,192.168.2.100:8000,192.168.4.100:8000",
 			},
-			wantMutatedBodyModel:   model,
-			inferenceObjectiveName: objectiveName,
+			wantMutatedBodyModel: model,
+			targetModelName:      model,
 		},
 		{
 			name: "successful chat completions request with multiple messages (critical, saturation ignored)",
@@ -255,6 +257,7 @@ func TestDirector_HandleRequest(t *testing.T) {
 			},
 			wantMutatedBodyModel:   model,
 			inferenceObjectiveName: objectiveName,
+			targetModelName:        model,
 		},
 		{
 			name: "successful completions request (sheddable, not saturated)",
@@ -277,6 +280,7 @@ func TestDirector_HandleRequest(t *testing.T) {
 			},
 			wantMutatedBodyModel:   modelSheddable,
 			inferenceObjectiveName: objectiveNameSheddable,
+			targetModelName:        modelSheddable,
 		},
 		{
 			name: "successful request with target model resolution",
@@ -299,6 +303,7 @@ func TestDirector_HandleRequest(t *testing.T) {
 			},
 			wantMutatedBodyModel:   "resolved-target-model-A",
 			inferenceObjectiveName: objectiveNameResolve,
+			targetModelName:        "resolved-target-model-A",
 		},
 		{
 			name: "nonexistent target defined, use default inference model",
@@ -321,6 +326,7 @@ func TestDirector_HandleRequest(t *testing.T) {
 			},
 			mockSaturationDetector: &mockSaturationDetector{isSaturated: false},
 			inferenceObjectiveName: "food-review-1",
+			targetModelName:        "food-review-1",
 		},
 		{
 
@@ -329,6 +335,7 @@ func TestDirector_HandleRequest(t *testing.T) {
 				"model":  modelSheddable,
 				"prompt": "sheddable prompt",
 			},
+			inferenceObjectiveName: objectiveNameSheddable,
 			mockSaturationDetector: &mockSaturationDetector{isSaturated: true},
 			wantErrCode:            errutil.InferencePoolResourceExhausted,
 		},
@@ -395,7 +402,8 @@ func TestDirector_HandleRequest(t *testing.T) {
 						requtil.RequestIdHeaderKey: "test-req-id-" + test.name, // Ensure a default request ID
 					},
 				},
-				ObjectiveKey: test.inferenceObjectiveName,
+				ObjectiveKey:    test.inferenceObjectiveName,
+				TargetModelName: test.targetModelName,
 			}
 			// Deep copy the body map.
 			for k, v := range test.reqBodyMap {
@@ -542,7 +550,7 @@ func TestGetCandidatePodsForScheduling(t *testing.T) {
 		},
 	}
 
-	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second, time.Second*2)
+	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
 	ds := datastore.NewDatastore(t.Context(), pmf)
 	for _, testPod := range testInput {
 		ds.PodUpdateOrAddIfNotExist(testPod)
@@ -564,76 +572,6 @@ func TestGetCandidatePodsForScheduling(t *testing.T) {
 	}
 }
 
-func TestRandomWeightedDraw(t *testing.T) {
-	logger := logutil.NewTestLogger()
-	// Note: These tests verify deterministic outcomes for a fixed seed (420).
-	// They do not test the statistical properties of the random draw.
-	tests := []struct {
-		name  string
-		model *v1alpha2.InferenceObjective
-		want  string
-	}{
-		{
-			name: "deterministic draw: 50/50 weights, seed 420",
-			model: &v1alpha2.InferenceObjective{
-				Spec: v1alpha2.InferenceObjectiveSpec{
-					TargetModels: []v1alpha2.TargetModel{
-						{Name: "canary", Weight: pointer(50)},
-						{Name: "v1", Weight: pointer(50)},
-					},
-				},
-			},
-			want: "canary",
-		},
-		{
-			name: "deterministic draw: 25/55/50 weights, seed 420",
-			model: &v1alpha2.InferenceObjective{
-				Spec: v1alpha2.InferenceObjectiveSpec{
-					TargetModels: []v1alpha2.TargetModel{
-						{Name: "canary", Weight: pointer(25)},
-						{Name: "v1.1", Weight: pointer(55)},
-						{Name: "v1", Weight: pointer(50)},
-					},
-				},
-			},
-			want: "v1",
-		},
-		{
-			name: "deterministic draw: 20/20/10 weights, seed 420",
-			model: &v1alpha2.InferenceObjective{
-				Spec: v1alpha2.InferenceObjectiveSpec{
-					TargetModels: []v1alpha2.TargetModel{
-						{Name: "canary", Weight: pointer(20)},
-						{Name: "v1.1", Weight: pointer(20)},
-						{Name: "v1", Weight: pointer(10)},
-					},
-				},
-			},
-			want: "v1.1",
-		},
-		{
-			name: "deterministic draw: nil weights (uniform), seed 420",
-			model: &v1alpha2.InferenceObjective{
-				Spec: v1alpha2.InferenceObjectiveSpec{
-					TargetModels: []v1alpha2.TargetModel{
-						{Name: "canary"},
-						{Name: "v1.1"},
-						{Name: "v1"},
-					},
-				},
-			},
-			want: "canary",
-		},
-	}
-	var seedVal int64 = 420
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			model := RandomWeightedDraw(logger, test.model, seedVal)
-			assert.Equal(t, test.want, model, "RandomWeightedDraw() with seed %d should produce expected model", seedVal)
-		})
-	}
-}
-
 func TestGetRandomPod(t *testing.T) {
 	tests := []struct {
 		name      string
@@ -665,7 +603,7 @@ func TestGetRandomPod(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Millisecond, time.Second*2)
+			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Millisecond)
 			ds := datastore.NewDatastore(t.Context(), pmf)
 			for _, pod := range test.storePods {
 				ds.PodUpdateOrAddIfNotExist(pod)
@@ -683,10 +621,6 @@ func TestGetRandomPod(t *testing.T) {
 	}
 }
 
-func pointer(v int32) *int32 {
-	return &v
-}
-
 func TestDirector_HandleResponse(t *testing.T) {
 	pr1 := newTestPostResponse("pr1")
 
diff --git a/pkg/epp/saturationdetector/saturationdetector_test.go b/pkg/epp/saturationdetector/saturationdetector_test.go
index c0ed3d2..f2530bd 100644
--- a/pkg/epp/saturationdetector/saturationdetector_test.go
+++ b/pkg/epp/saturationdetector/saturationdetector_test.go
@@ -114,6 +114,7 @@ func TestNewDetector(t *testing.T) {
 			detector := NewDetector(LoadConfigFromEnv(), test.datastore, logr.Discard())
 			if detector == nil {
 				t.Fatalf("NewDetector() returned nil detector for valid config")
+				return // explicit return so staticcheck does not warn on potential nil detector below.
 			}
 			if detector.config.QueueDepthThreshold != test.expectedQueueDepthThreshold {
 				t.Errorf("NewDetector() QueueDepthThreshold = %d, want %d", detector.config.QueueDepthThreshold, test.expectedQueueDepthThreshold)
diff --git a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go
index aaf68f0..eee8f63 100644
--- a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go
+++ b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go
@@ -24,10 +24,12 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/google/uuid"
 	"github.com/stretchr/testify/assert"
 	k8stypes "k8s.io/apimachinery/pkg/types"
 
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types"
 )
 
@@ -38,7 +40,7 @@ func TestPrefixPlugin(t *testing.T) {
 		MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks,
 		LRUCapacityPerServer:   DefaultLRUCapacityPerServer,
 	}
-	plugin := New(config)
+	plugin := New(context.Background(), config)
 
 	pod1 := &types.PodMetrics{Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: "pod1"}}}
 	pod2 := &types.PodMetrics{Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: "pod2"}}}
@@ -46,101 +48,131 @@ func TestPrefixPlugin(t *testing.T) {
 
 	// First request.
 	req1 := &types.LLMRequest{
+		RequestId:   uuid.NewString(),
 		TargetModel: "test-model1",
 		Prompt:      "aaaaaa",
 	}
-	cycleState1 := types.NewCycleState()
-	scores := plugin.Score(context.Background(), cycleState1, req1, pods)
-	state, err := types.ReadCycleStateKey[*SchedulingContextState](cycleState1, PrefixCachePluginType)
+	scores := plugin.Score(context.Background(), nil, req1, pods)
+	state, err := plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req1.RequestId, PrefixCachePluginType)
 	assert.NoError(t, err)
 	t.Logf("Hashes %+v, cached servers: %+v", state.PrefixHashes, state.PrefixCacheServers)
 	// Input size is 6, hash block size is 4, the last 2 characters are ignored.
-	// Total hashes = 2 (the first one is for the model)
-	assert.Equal(t, 2, len(state.PrefixHashes), "number of hashes is incorrect")
+	// Total hashes = 1 (the first one is for the prefix with model)
+	assert.Equal(t, 1, len(state.PrefixHashes), "number of hashes is incorrect")
 	assert.Equal(t, 0, len(state.PrefixCacheServers), "there shouldn't be any cached servers")
 	assert.Equal(t, float64(0), scores[pod1], "score for pod1")
 	assert.Equal(t, float64(0), scores[pod2], "score for pod2")
 
 	// Simulate pod1 was picked.
-	plugin.PostCycle(context.Background(), cycleState1, &types.ProfileRunResult{TargetPods: []types.Pod{pod1}})
+	schedulingResult := &types.SchedulingResult{
+		PrimaryProfileName: "default",
+		ProfileResults: map[string]*types.ProfileRunResult{
+			"default": {TargetPods: []types.Pod{pod1}},
+		},
+	}
+	plugin.PreRequest(context.Background(), req1, schedulingResult, 0)
 
 	// Second request doesn't share any prefix with first one. It should be added to the cache but
 	// the pod score should be 0.
 	req2 := &types.LLMRequest{
+		RequestId:   uuid.NewString(),
 		TargetModel: "test-model2",
 		Prompt:      "bbbbbb",
 	}
-	cycleState2 := types.NewCycleState()
-	scores = plugin.Score(context.Background(), cycleState2, req2, pods)
-	state, err = types.ReadCycleStateKey[*SchedulingContextState](cycleState2, PrefixCachePluginType)
+	scores = plugin.Score(context.Background(), nil, req2, pods)
+	state, err = plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req2.RequestId, PrefixCachePluginType)
 	assert.NoError(t, err)
 	t.Logf("Hashes %+v, cached servers: %+v", state.PrefixHashes, state.PrefixCacheServers)
 	// Input size is 6, hash block size is 4, the last 2 characters are ignored.
-	// Total hashes = 2 (the first one is for the model)
-	assert.Equal(t, 2, len(state.PrefixHashes), "number of hashes is incorrect")
+	// Total hashes = 1 (the first one is for the prefix with model)
+	assert.Equal(t, 1, len(state.PrefixHashes), "number of hashes is incorrect")
 	assert.Equal(t, 0, len(state.PrefixCacheServers), "there shouldn't be any cached servers")
 	assert.Equal(t, float64(0), scores[pod1], "score for pod1")
 	assert.Equal(t, float64(0), scores[pod2], "score for pod2")
 
 	// Simulate pod2 was picked.
-	plugin.PostCycle(context.Background(), cycleState2, &types.ProfileRunResult{TargetPods: []types.Pod{pod2}})
+	schedulingResult = &types.SchedulingResult{
+		PrimaryProfileName: "default",
+		ProfileResults: map[string]*types.ProfileRunResult{
+			"default": {TargetPods: []types.Pod{pod2}},
+		},
+	}
+	plugin.PreRequest(context.Background(), req2, schedulingResult, 0)
 
 	// Third request shares partial prefix with first one.
 	req3 := &types.LLMRequest{
+		RequestId:   uuid.NewString(),
 		TargetModel: "test-model1",
 		Prompt:      "aaaabbbb",
 	}
-	cycleState3 := types.NewCycleState()
-	scores = plugin.Score(context.Background(), cycleState3, req3, pods)
-	state, err = types.ReadCycleStateKey[*SchedulingContextState](cycleState3, PrefixCachePluginType)
+	scores = plugin.Score(context.Background(), nil, req3, pods)
+	state, err = plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req3.RequestId, PrefixCachePluginType)
 	assert.NoError(t, err)
 	t.Logf("Hashes %+v, cached servers: %+v", state.PrefixHashes, state.PrefixCacheServers)
 	// Input size is 8, hash block size is 4, so 2 hashes will be calculated.
-	// Total hashes = 3 (the first one is for the model)
-	assert.Equal(t, 3, len(state.PrefixHashes), "number of hashes is incorrect")
+	// Total hashes = 2 (the first one is for the prefix with model)
+	assert.Equal(t, 2, len(state.PrefixHashes), "number of hashes is incorrect")
 	assert.Equal(t, 1, len(state.PrefixCacheServers), "pod1 should have cached the aaaa prefix")
-	assert.Equal(t, float64(2)/float64(3), scores[pod1], "score should be 2/3 - the model and the first prefix block match")
+	assert.Equal(t, 0.5, scores[pod1], "score should be 0.5 - the model and the first prefix block match")
 	assert.Equal(t, float64(0), scores[pod2], "score for pod2")
 
-	plugin.PostCycle(context.Background(), cycleState3, &types.ProfileRunResult{TargetPods: []types.Pod{pod1}})
+	schedulingResult = &types.SchedulingResult{
+		PrimaryProfileName: "default",
+		ProfileResults: map[string]*types.ProfileRunResult{
+			"default": {TargetPods: []types.Pod{pod1}},
+		},
+	}
+	plugin.PreRequest(context.Background(), req3, schedulingResult, 0)
 
 	// 4th request is same as req3 except the model is different, still no match.
 	req4 := &types.LLMRequest{
+		RequestId:   uuid.NewString(),
 		TargetModel: "test-model-new",
 		Prompt:      "aaaabbbb",
 	}
-	cycleState4 := types.NewCycleState()
-	scores = plugin.Score(context.Background(), cycleState4, req4, pods)
-	state, err = types.ReadCycleStateKey[*SchedulingContextState](cycleState4, PrefixCachePluginType)
+	scores = plugin.Score(context.Background(), nil, req4, pods)
+	state, err = plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req4.RequestId, PrefixCachePluginType)
 	assert.NoError(t, err)
 	t.Logf("Hashes %+v, cached servers: %+v", state.PrefixHashes, state.PrefixCacheServers)
 	// Input size is 8, hash block size is 4, so 2 hashes will be calculated.
-	// Total hashes = 3 (the first one is for the model)
-	assert.Equal(t, 3, len(state.PrefixHashes), "number of hashes is incorrect")
+	// Total hashes = 2 (the first one is for the prefix with model)
+	assert.Equal(t, 2, len(state.PrefixHashes), "number of hashes is incorrect")
 	assert.Equal(t, 0, len(state.PrefixCacheServers), "pod1 should have cached the aaaa prefix")
 	assert.Equal(t, float64(0), scores[pod1], "score for pod1")
 	assert.Equal(t, float64(0), scores[pod2], "score for pod2")
 
-	plugin.PostCycle(context.Background(), cycleState4, &types.ProfileRunResult{TargetPods: []types.Pod{pod1}})
+	schedulingResult = &types.SchedulingResult{
+		PrimaryProfileName: "default",
+		ProfileResults: map[string]*types.ProfileRunResult{
+			"default": {TargetPods: []types.Pod{pod1}},
+		},
+	}
+	plugin.PreRequest(context.Background(), req4, schedulingResult, 0)
 
 	// 5th request shares partial prefix with 3rd one.
 	req5 := &types.LLMRequest{
+		RequestId:   uuid.NewString(),
 		TargetModel: "test-model1",
 		Prompt:      "aaaabbbbcccc",
 	}
-	cycleState5 := types.NewCycleState()
-	scores = plugin.Score(context.Background(), cycleState5, req5, pods)
-	state, err = types.ReadCycleStateKey[*SchedulingContextState](cycleState5, PrefixCachePluginType)
+	scores = plugin.Score(context.Background(), nil, req5, pods)
+	state, err = plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req5.RequestId, PrefixCachePluginType)
 	assert.NoError(t, err)
 	t.Logf("Hashes %+v, cached servers: %+v", state.PrefixHashes, state.PrefixCacheServers)
 	// Input size is 12, hash block size is 4, so 3 hashes will be calculated.
-	// Total hashes = 4 (the first one is for the model)
-	assert.Equal(t, 4, len(state.PrefixHashes), "number of hashes is incorrect")
+	// Total hashes = 3 (the first one is for the prefix with model)
+	assert.Equal(t, 3, len(state.PrefixHashes), "number of hashes is incorrect")
 	assert.Equal(t, 1, len(state.PrefixCacheServers), "pod1 should have cached the aaaa prefix")
-	assert.Equal(t, 0.75, scores[pod1], "score should be 0.75 - the model and the first 2 prefix blocks match")
+	assert.Equal(t, 2./3, scores[pod1], "score should be 2./3 - the model and the first 2 prefix blocks match")
 	assert.Equal(t, float64(0), scores[pod2], "score for pod2")
 
-	plugin.PostCycle(context.Background(), cycleState5, &types.ProfileRunResult{TargetPods: []types.Pod{pod1}})
+	schedulingResult = &types.SchedulingResult{
+		PrimaryProfileName: "default",
+		ProfileResults: map[string]*types.ProfileRunResult{
+			"default": {TargetPods: []types.Pod{pod1}},
+		},
+	}
+	plugin.PreRequest(context.Background(), req5, schedulingResult, 0)
 }
 
 // TestPrefixPluginStress is a stress test for the prefix scoring plugin, using prompts of increasing length.
@@ -153,7 +185,7 @@ func BenchmarkPrefixPluginStress(b *testing.B) {
 		LRUCapacityPerServer:   DefaultLRUCapacityPerServer,
 	}
 
-	plugin := New(config)
+	plugin := New(context.Background(), config)
 	types.NewCycleState()
 	var promptLen []int
 	for i := 1; i <= 1024; i++ {
@@ -174,19 +206,25 @@ func BenchmarkPrefixPluginStress(b *testing.B) {
 
 		pods := []types.Pod{pod}
 		req := &types.LLMRequest{
+			RequestId:   uuid.NewString(),
 			TargetModel: "model-stress",
 			Prompt:      prompt,
 		}
 
 		// First cycle: simulate scheduling and insert prefix info into the cache
-		cycleState := types.NewCycleState()
-		plugin.Score(context.Background(), cycleState, req, pods)
-		plugin.PostCycle(context.Background(), cycleState, &types.ProfileRunResult{TargetPods: []types.Pod{pod}})
+		plugin.Score(context.Background(), nil, req, pods)
+		schedulingResult := &types.SchedulingResult{
+			PrimaryProfileName: "default",
+			ProfileResults: map[string]*types.ProfileRunResult{
+				"default": {TargetPods: []types.Pod{pod}},
+			},
+		}
+		plugin.PreRequest(context.Background(), req, schedulingResult, 0)
 
 		// Second cycle: validate internal state
-		state, err := types.ReadCycleStateKey[*SchedulingContextState](cycleState, PrefixCachePluginType)
+		state, err := plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req.RequestId, PrefixCachePluginType)
 		assert.NoError(b, err)
-		expectedHashes := int(math.Min(float64(maxPrefixBlocks+1), float64(len(req.Prompt)/blockSize+1))) // the extra one is for the model.
+		expectedHashes := int(math.Min(float64(maxPrefixBlocks), float64(len(req.Prompt)/blockSize)))
 		assert.Equal(b, expectedHashes, len(state.PrefixHashes), "number of hashes is incorrect")
 	}
 }
diff --git a/pkg/epp/scheduling/framework/plugins/test/consts.go b/pkg/epp/scheduling/framework/plugins/test/consts.go
new file mode 100644
index 0000000..81c0579
--- /dev/null
+++ b/pkg/epp/scheduling/framework/plugins/test/consts.go
@@ -0,0 +1,25 @@
+/*
+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.
+*/
+
+package test
+
+const (
+	// HeaderTestEppEndPointSelectionKey is the header used for testing purposes to make EPP behavior controllable.
+	// The header value should be a comma-separated list of endpoint IP addresses.
+	// E.g., "test-epp-endpoint-selection": "10.0.0.7,10.0.0.8"
+	// The returned order is the same as the order provided in the header.
+	HeaderTestEppEndPointSelectionKey = "test-epp-endpoint-selection"
+)
diff --git a/pkg/epp/scheduling/framework/plugins/test/filter/filter_test.go b/pkg/epp/scheduling/framework/plugins/test/filter/filter_test.go
index b7434be..d5615bc 100644
--- a/pkg/epp/scheduling/framework/plugins/test/filter/filter_test.go
+++ b/pkg/epp/scheduling/framework/plugins/test/filter/filter_test.go
@@ -23,6 +23,7 @@ import (
 	"github.com/google/go-cmp/cmp"
 
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/test"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types"
 )
 
@@ -47,7 +48,7 @@ func TestFilter(t *testing.T) {
 		},
 		{
 			name: "TestHeaderBasedFilter, header endpoint set in request but no match",
-			req:  &types.LLMRequest{Headers: map[string]string{HeaderTestEppEndPointSelectionKey: "test-endpoint"}},
+			req:  &types.LLMRequest{Headers: map[string]string{test.HeaderTestEppEndPointSelectionKey: "test-endpoint"}},
 			input: []types.Pod{
 				&types.PodMetrics{
 					Pod: &backend.Pod{
@@ -59,7 +60,7 @@ func TestFilter(t *testing.T) {
 		},
 		{
 			name: "TestHeaderBasedFilter, header endpoint set",
-			req:  &types.LLMRequest{Headers: map[string]string{HeaderTestEppEndPointSelectionKey: "test-endpoint"}},
+			req:  &types.LLMRequest{Headers: map[string]string{test.HeaderTestEppEndPointSelectionKey: "test-endpoint"}},
 			input: []types.Pod{
 				&types.PodMetrics{
 					Pod: &backend.Pod{
@@ -77,7 +78,7 @@ func TestFilter(t *testing.T) {
 		},
 		{
 			name: "TestHeaderBasedFilter, multiple header endpoints set and multiple matches",
-			req:  &types.LLMRequest{Headers: map[string]string{HeaderTestEppEndPointSelectionKey: "test-endpoint3,test-endpoint2"}},
+			req:  &types.LLMRequest{Headers: map[string]string{test.HeaderTestEppEndPointSelectionKey: "test-endpoint3,test-endpoint2"}},
 			input: []types.Pod{
 				&types.PodMetrics{
 					Pod: &backend.Pod{
diff --git a/pkg/epp/scheduling/framework/plugins/test/filter/request_header_based_filter.go b/pkg/epp/scheduling/framework/plugins/test/filter/request_header_based_filter.go
index 113ec7b..bf36d77 100644
--- a/pkg/epp/scheduling/framework/plugins/test/filter/request_header_based_filter.go
+++ b/pkg/epp/scheduling/framework/plugins/test/filter/request_header_based_filter.go
@@ -23,15 +23,11 @@ import (
 
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/test"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types"
 )
 
 const (
-	// HeaderTestEppEndPointSelectionKey is the header used for testing purposes to make EPP behavior controllable.
-	// The header value should be a comma-separated list of endpoint IP addresses.
-	// E.g., "test-epp-endpoint-selection": "10.0.0.7,10.0.0.8"
-	// The returned order is the same as the order provided in the header.
-	HeaderTestEppEndPointSelectionKey = "test-epp-endpoint-selection"
 	// HeaderBasedTestingFilterType is the filter type that is used in plugins registry.
 	HeaderBasedTestingFilterType = "header-based-testing-filter"
 )
@@ -70,7 +66,7 @@ func (f *HeaderBasedTestingFilter) WithName(name string) *HeaderBasedTestingFilt
 
 // Filter selects pods that match the IP addresses specified in the request header.
 func (f *HeaderBasedTestingFilter) Filter(_ context.Context, _ *types.CycleState, request *types.LLMRequest, pods []types.Pod) []types.Pod {
-	headerValue, ok := request.Headers[HeaderTestEppEndPointSelectionKey]
+	headerValue, ok := request.Headers[test.HeaderTestEppEndPointSelectionKey]
 	if !ok || headerValue == "" {
 		return []types.Pod{}
 	}
diff --git a/pkg/epp/scheduling/framework/scheduler_profile_test.go b/pkg/epp/scheduling/framework/scheduler_profile_test.go
index 020223c..f79b48d 100644
--- a/pkg/epp/scheduling/framework/scheduler_profile_test.go
+++ b/pkg/epp/scheduling/framework/scheduler_profile_test.go
@@ -64,8 +64,7 @@ func TestSchedulePlugins(t *testing.T) {
 			profile: NewSchedulerProfile().
 				WithFilters(tp1, tp2).
 				WithScorers(NewWeightedScorer(tp1, 1), NewWeightedScorer(tp2, 1)).
-				WithPicker(pickerPlugin).
-				WithPostCyclePlugins(tp1, tp2),
+				WithPicker(pickerPlugin),
 			input: []types.Pod{
 				&types.PodMetrics{Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: "pod1"}}},
 				&types.PodMetrics{Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: "pod2"}}},
@@ -81,8 +80,7 @@ func TestSchedulePlugins(t *testing.T) {
 			profile: NewSchedulerProfile().
 				WithFilters(tp1, tp2).
 				WithScorers(NewWeightedScorer(tp1, 60), NewWeightedScorer(tp2, 40)).
-				WithPicker(pickerPlugin).
-				WithPostCyclePlugins(tp1, tp2),
+				WithPicker(pickerPlugin),
 			input: []types.Pod{
 				&types.PodMetrics{Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: "pod1"}}},
 				&types.PodMetrics{Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: "pod2"}}},
@@ -98,8 +96,7 @@ func TestSchedulePlugins(t *testing.T) {
 			profile: NewSchedulerProfile().
 				WithFilters(tp1, tp_filterAll).
 				WithScorers(NewWeightedScorer(tp1, 1), NewWeightedScorer(tp2, 1)).
-				WithPicker(pickerPlugin).
-				WithPostCyclePlugins(tp1, tp2),
+				WithPicker(pickerPlugin),
 			input: []types.Pod{
 				&types.PodMetrics{Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: "pod1"}}},
 				&types.PodMetrics{Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: "pod2"}}},
@@ -120,9 +117,6 @@ func TestSchedulePlugins(t *testing.T) {
 				plugin.Scorer.(*testPlugin).reset()
 			}
 			test.profile.picker.(*testPlugin).reset()
-			for _, plugin := range test.profile.postCyclePlugins {
-				plugin.(*testPlugin).reset()
-			}
 
 			// Initialize the scheduling context
 			request := &types.LLMRequest{
@@ -179,12 +173,6 @@ func TestSchedulePlugins(t *testing.T) {
 			if tp.WinnerPodScore != test.targetPodScore {
 				t.Errorf("winner pod score %v, expected %v", tp.WinnerPodScore, test.targetPodScore)
 			}
-			for _, plugin := range test.profile.postCyclePlugins {
-				tp, _ := plugin.(*testPlugin)
-				if tp.PostCycleCallCount != 1 {
-					t.Errorf("Plugin '%s' PostCycle() called %d times, expected 1", plugin.TypedName(), tp.PostCycleCallCount)
-				}
-			}
 		})
 	}
 }
@@ -193,7 +181,6 @@ func TestSchedulePlugins(t *testing.T) {
 var _ Filter = &testPlugin{}
 var _ Scorer = &testPlugin{}
 var _ Picker = &testPlugin{}
-var _ PostCycle = &testPlugin{}
 
 // testPlugin is an implementation useful in unit tests.
 type testPlugin struct {
@@ -204,7 +191,6 @@ type testPlugin struct {
 	ScoreRes              float64
 	FilterCallCount       int
 	FilterRes             []k8stypes.NamespacedName
-	PostCycleCallCount    int
 	PickCallCount         int
 	NumOfPickerCandidates int
 	PickRes               k8stypes.NamespacedName
@@ -246,15 +232,10 @@ func (tp *testPlugin) Pick(_ context.Context, _ *types.CycleState, scoredPods []
 	return &types.ProfileRunResult{TargetPods: winnerPods}
 }
 
-func (tp *testPlugin) PostCycle(_ context.Context, _ *types.CycleState, res *types.ProfileRunResult) {
-	tp.PostCycleCallCount++
-}
-
 func (tp *testPlugin) reset() {
 	tp.FilterCallCount = 0
 	tp.ScoreCallCount = 0
 	tp.NumOfScoredPods = 0
-	tp.PostCycleCallCount = 0
 	tp.PickCallCount = 0
 	tp.NumOfPickerCandidates = 0
 }
diff --git a/pkg/epp/scheduling/scheduler_test.go b/pkg/epp/scheduling/scheduler_test.go
index aea9c57..c197096 100644
--- a/pkg/epp/scheduling/scheduler_test.go
+++ b/pkg/epp/scheduling/scheduler_test.go
@@ -38,7 +38,7 @@ import (
 func TestSchedule(t *testing.T) {
 	kvCacheUtilizationScorer := scorer.NewKVCacheUtilizationScorer()
 	queueingScorer := scorer.NewQueueScorer()
-	prefixCacheScorer := prefix.New(prefix.DefaultConfig)
+	prefixCacheScorer := prefix.New(context.Background(), prefix.DefaultConfig)
 	loraAffinityScorer := scorer.NewLoraAffinityScorer()
 
 	defaultProfile := framework.NewSchedulerProfile().
diff --git a/pkg/epp/util/testing/wrappers.go b/pkg/epp/util/testing/wrappers.go
index cab70a4..9e7f4a1 100644
--- a/pkg/epp/util/testing/wrappers.go
+++ b/pkg/epp/util/testing/wrappers.go
@@ -136,11 +136,6 @@ func (m *InferenceObjectiveWrapper) ObjRef() *v1alpha2.InferenceObjective {
 	return &m.InferenceObjective
 }
 
-func (m *InferenceObjectiveWrapper) TargetModel(modelName string) *InferenceObjectiveWrapper {
-	m.Spec.TargetModels = append(m.Spec.TargetModels, v1alpha2.TargetModel{Name: modelName})
-	return m
-}
-
 func (m *InferenceObjectiveWrapper) PoolName(poolName string) *InferenceObjectiveWrapper {
 	m.Spec.PoolRef.Name = v1alpha2.ObjectName(poolName)
 	return m
@@ -151,8 +146,8 @@ func (m *InferenceObjectiveWrapper) PoolGroup(poolGroup string) *InferenceObject
 	return m
 }
 
-func (m *InferenceObjectiveWrapper) Criticality(criticality v1alpha2.Criticality) *InferenceObjectiveWrapper {
-	m.Spec.Criticality = &criticality
+func (m *InferenceObjectiveWrapper) Priority(priority int) *InferenceObjectiveWrapper {
+	m.Spec.Priority = &priority
 	return m
 }
 
@@ -199,17 +194,19 @@ func (m *InferencePoolWrapper) Selector(selector map[string]string) *InferencePo
 	for k, v := range selector {
 		s[v1.LabelKey(k)] = v1.LabelValue(v)
 	}
-	m.Spec.Selector = s
+	m.Spec.Selector = v1.LabelSelector{
+		MatchLabels: s,
+	}
 	return m
 }
 
-func (m *InferencePoolWrapper) TargetPortNumber(p int32) *InferencePoolWrapper {
-	m.Spec.TargetPortNumber = p
+func (m *InferencePoolWrapper) TargetPorts(p int32) *InferencePoolWrapper {
+	m.Spec.TargetPorts = []v1.Port{{Number: v1.PortNumber(p)}}
 	return m
 }
 
-func (m *InferencePoolWrapper) ExtensionRef(name string) *InferencePoolWrapper {
-	m.Spec.ExtensionRef = &v1.Extension{ExtensionReference: v1.ExtensionReference{Name: v1.ObjectName(name)}}
+func (m *InferencePoolWrapper) EndpointPickerRef(name string) *InferencePoolWrapper {
+	m.Spec.EndpointPickerRef = v1.EndpointPickerRef{Name: v1.ObjectName(name)}
 	return m
 }
 
@@ -218,14 +215,14 @@ func (m *InferencePoolWrapper) ObjRef() *v1.InferencePool {
 	return &m.InferencePool
 }
 
-// XInferencePoolWrapper wraps an group "inference.networking.x-k8s.io" InferencePool.
-type XInferencePoolWrapper struct {
+// AlphaInferencePoolWrapper wraps an group "inference.networking.x-k8s.io" InferencePool.
+type AlphaInferencePoolWrapper struct {
 	v1alpha2.InferencePool
 }
 
-// MakeXInferencePool creates a wrapper for a InferencePool.
-func MakeXInferencePool(name string) *XInferencePoolWrapper {
-	return &XInferencePoolWrapper{
+// MakeAlphaInferencePool creates a wrapper for a InferencePool.
+func MakeAlphaInferencePool(name string) *AlphaInferencePoolWrapper {
+	return &AlphaInferencePoolWrapper{
 		v1alpha2.InferencePool{
 			ObjectMeta: metav1.ObjectMeta{
 				Name: name,
@@ -235,12 +232,12 @@ func MakeXInferencePool(name string) *XInferencePoolWrapper {
 	}
 }
 
-func (m *XInferencePoolWrapper) Namespace(ns string) *XInferencePoolWrapper {
+func (m *AlphaInferencePoolWrapper) Namespace(ns string) *AlphaInferencePoolWrapper {
 	m.ObjectMeta.Namespace = ns
 	return m
 }
 
-func (m *XInferencePoolWrapper) Selector(selector map[string]string) *XInferencePoolWrapper {
+func (m *AlphaInferencePoolWrapper) Selector(selector map[string]string) *AlphaInferencePoolWrapper {
 	s := make(map[v1alpha2.LabelKey]v1alpha2.LabelValue)
 	for k, v := range selector {
 		s[v1alpha2.LabelKey(k)] = v1alpha2.LabelValue(v)
@@ -249,17 +246,17 @@ func (m *XInferencePoolWrapper) Selector(selector map[string]string) *XInference
 	return m
 }
 
-func (m *XInferencePoolWrapper) TargetPortNumber(p int32) *XInferencePoolWrapper {
+func (m *AlphaInferencePoolWrapper) TargetPortNumber(p int32) *AlphaInferencePoolWrapper {
 	m.Spec.TargetPortNumber = p
 	return m
 }
 
-func (m *XInferencePoolWrapper) ExtensionRef(name string) *XInferencePoolWrapper {
-	m.Spec.ExtensionRef = &v1alpha2.Extension{ExtensionReference: v1alpha2.ExtensionReference{Name: v1alpha2.ObjectName(name)}}
+func (m *AlphaInferencePoolWrapper) ExtensionRef(name string) *AlphaInferencePoolWrapper {
+	m.Spec.ExtensionRef = v1alpha2.Extension{Name: v1alpha2.ObjectName(name)}
 	return m
 }
 
 // Obj returns the wrapped InferencePool.
-func (m *XInferencePoolWrapper) ObjRef() *v1alpha2.InferencePool {
+func (m *AlphaInferencePoolWrapper) ObjRef() *v1alpha2.InferencePool {
 	return &m.InferencePool
 }
diff --git a/test/e2e/epp/README.md b/test/e2e/epp/README.md
index c3e4aa1..5a83f06 100644
--- a/test/e2e/epp/README.md
+++ b/test/e2e/epp/README.md
@@ -45,6 +45,19 @@ Follow these steps to run the end-to-end tests:
      export E2E_MANIFEST_PATH=[config/manifests/vllm/gpu-deployment.yaml|config/manifests/vllm/cpu-deployment.yaml]
      ```
 
+   - **Enable leader election tests**: By default, the e2e test runs the EPP server as a single replica.
+     To test the high-availability (HA) mode with leader election (3 replicas), set the following environment variable:
+
+     ```sh
+     export E2E_LEADER_ELECTION_ENABLED=true
+     ```
+
+   - **Pause before cleanup**: To pause the test run before cleaning up resources, set the `E2E_PAUSE_ON_EXIT` environment variable.
+     This is useful for debugging the state of the cluster after the test has run.
+
+     - To pause indefinitely, set it to `true`: `export E2E_PAUSE_ON_EXIT=true`
+     - To pause for a specific duration, provide a duration string: `export E2E_PAUSE_ON_EXIT=10m`
+
 1. **Run the Tests**: Run the `test-e2e` target:
 
    ```sh
diff --git a/test/e2e/epp/e2e_suite_test.go b/test/e2e/epp/e2e_suite_test.go
index a40d586..e6e5b83 100644
--- a/test/e2e/epp/e2e_suite_test.go
+++ b/test/e2e/epp/e2e_suite_test.go
@@ -85,8 +85,10 @@ const (
 	xInferObjectiveManifest = "../../../config/crd/bases/inference.networking.x-k8s.io_inferenceobjectives.yaml"
 	// inferPoolManifest is the manifest for the inference pool CRD with 'inference.networking.k8s.io' group.
 	inferPoolManifest = "../../../config/crd/bases/inference.networking.k8s.io_inferencepools.yaml"
-	// inferExtManifest is the manifest for the inference extension test resources.
-	inferExtManifest = "../../testdata/inferencepool-e2e.yaml"
+	// inferExtManifestDefault is the manifest for the default inference extension test resources (single replica).
+	inferExtManifestDefault = "../../testdata/inferencepool-e2e.yaml"
+	// inferExtManifestLeaderElection is the manifest for the inference extension test resources with leader election enabled (3 replicas).
+	inferExtManifestLeaderElection = "../../testdata/inferencepool-leader-election-e2e.yaml"
 	// envoyManifest is the manifest for the envoy proxy test resources.
 	envoyManifest = "../../testdata/envoy.yaml"
 	// metricsRbacManifest is the manifest for the rbac resources for testing metrics.
@@ -95,15 +97,18 @@ const (
 	modelServerManifestFilepathEnvVar = "MANIFEST_PATH"
 )
 
+const e2eLeaderElectionEnabledEnvVar = "E2E_LEADER_ELECTION_ENABLED"
+
 var (
 	ctx = context.Background()
 	cli client.Client
 	// Required for exec'ing in curl pod
-	kubeCli  *kubernetes.Clientset
-	scheme   = runtime.NewScheme()
-	cfg      = config.GetConfigOrDie()
-	nsName   string
-	e2eImage string
+	kubeCli               *kubernetes.Clientset
+	scheme                = runtime.NewScheme()
+	cfg                   = config.GetConfigOrDie()
+	nsName                string
+	e2eImage              string
+	leaderElectionEnabled bool
 )
 
 func TestAPIs(t *testing.T) {
@@ -121,6 +126,11 @@ var _ = ginkgo.BeforeSuite(func() {
 	e2eImage = os.Getenv("E2E_IMAGE")
 	gomega.Expect(e2eImage).NotTo(gomega.BeEmpty(), "E2E_IMAGE environment variable is not set")
 
+	if os.Getenv(e2eLeaderElectionEnabledEnvVar) == "true" {
+		leaderElectionEnabled = true
+		ginkgo.By("Leader election test mode enabled via " + e2eLeaderElectionEnabledEnvVar)
+	}
+
 	ginkgo.By("Setting up the test suite")
 	setupSuite()
 
@@ -146,7 +156,12 @@ func setupInfra() {
 	}
 
 	createCRDs(cli, crds)
-	createInferExt(cli, inferExtManifest)
+
+	inferExtManifestPath := inferExtManifestDefault
+	if leaderElectionEnabled {
+		inferExtManifestPath = inferExtManifestLeaderElection
+	}
+	createInferExt(cli, inferExtManifestPath)
 	createClient(cli, clientManifest)
 	createEnvoy(cli, envoyManifest)
 	createMetricsRbac(cli, metricsRbacManifest)
@@ -156,6 +171,20 @@ func setupInfra() {
 }
 
 var _ = ginkgo.AfterSuite(func() {
+	// If E2E_PAUSE_ON_EXIT is set, pause the test run before cleanup.
+	// This is useful for debugging the state of the cluster after the test has run.
+	if pauseStr := os.Getenv("E2E_PAUSE_ON_EXIT"); pauseStr != "" {
+		ginkgo.By("Pausing before cleanup as requested by E2E_PAUSE_ON_EXIT=" + pauseStr)
+		pauseDuration, err := time.ParseDuration(pauseStr)
+		if err != nil {
+			// If it's not a valid duration (e.g., "true"), just wait indefinitely.
+			ginkgo.By("Invalid duration, pausing indefinitely. Press Ctrl+C to stop the test runner when you are done.")
+			select {} // Block forever
+		}
+		ginkgo.By(fmt.Sprintf("Pausing for %v...", pauseDuration))
+		time.Sleep(pauseDuration)
+	}
+
 	ginkgo.By("Performing global cleanup")
 	cleanupResources()
 })
@@ -423,8 +452,12 @@ func createInferExt(k8sClient client.Client, filePath string) {
 		return k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: inferExtName}, deploy)
 	}, existsTimeout, interval)
 
-	// Wait for the deployment to be available.
-	testutils.DeploymentAvailable(ctx, k8sClient, deploy, modelReadyTimeout, interval)
+	if leaderElectionEnabled {
+		// With leader election enabled, only 1 replica will be "Ready" at any given time (the leader).
+		testutils.DeploymentReadyReplicas(ctx, k8sClient, deploy, 1, modelReadyTimeout, interval)
+	} else {
+		testutils.DeploymentAvailable(ctx, k8sClient, deploy, modelReadyTimeout, interval)
+	}
 
 	// Wait for the service to exist.
 	testutils.EventuallyExists(ctx, func() error {
diff --git a/test/e2e/epp/e2e_test.go b/test/e2e/epp/e2e_test.go
index 5e9b408..e01240d 100644
--- a/test/e2e/epp/e2e_test.go
+++ b/test/e2e/epp/e2e_test.go
@@ -28,10 +28,10 @@ import (
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/onsi/ginkgo/v2"
 	"github.com/onsi/gomega"
+	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/utils/ptr"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
@@ -72,181 +72,246 @@ var _ = ginkgo.Describe("InferencePool", func() {
 
 	ginkgo.When("The Inference Extension is running", func() {
 		ginkgo.It("Should route traffic to target model servers", func() {
-			for _, t := range []struct {
-				api              string
-				promptOrMessages any
-			}{
-				{
-					api:              "/completions",
-					promptOrMessages: "Write as if you were a critic: San Francisco",
-				},
-				{
-					api: "/chat/completions",
-					promptOrMessages: []map[string]any{
-						{
-							"role":    "user",
-							"content": "Write as if you were a critic: San Francisco",
-						},
-					},
-				},
-				{
-					api: "/chat/completions",
-					promptOrMessages: []map[string]any{
-						{
-							"role":    "user",
-							"content": "Write as if you were a critic: San Francisco",
-						},
-						{"role": "assistant", "content": "Okay, let's see..."},
-						{"role": "user", "content": "Now summarize your thoughts."},
-					},
-				},
-			} {
-				ginkgo.By(fmt.Sprintf("Verifying connectivity through the inference extension with %s api and prompt/messages: %v", t.api, t.promptOrMessages))
-
-				// Ensure the expected responses include the InferenceObjective target model names.
-				var expected []string
-				for _, m := range infObjective.Spec.TargetModels {
-					expected = append(expected, m.Name)
-				}
-				curlCmd := getCurlCommand(envoyName, nsName, envoyPort, modelName, curlTimeout, t.api, t.promptOrMessages, false)
-
-				actual := make(map[string]int)
-				gomega.Eventually(func() error {
-					resp, err := testutils.ExecCommandInPod(ctx, cfg, scheme, kubeCli, nsName, "curl", "curl", curlCmd)
-					if err != nil {
-						return err
-					}
-					if !strings.Contains(resp, "200 OK") {
-						return fmt.Errorf("did not get 200 OK: %s", resp)
-					}
-					for _, m := range expected {
-						if strings.Contains(resp, m) {
-							actual[m] = 0
-						}
-					}
-					var got []string
-					for m := range actual {
-						got = append(got, m)
-					}
-					// Compare ignoring order
-					if !cmp.Equal(got, expected, cmpopts.SortSlices(func(a, b string) bool { return a < b })) {
-						return fmt.Errorf("actual (%v) != expected (%v); resp=%q", got, expected, resp)
-					}
-					return nil
-				}, readyTimeout, curlInterval).Should(gomega.Succeed())
-			}
+			verifyTrafficRouting()
 		})
 
 		ginkgo.It("Should expose EPP metrics after generating traffic", func() {
-			// Define the metrics we expect to see
-			expectedMetrics := []string{
-				"inference_model_request_total",
-				"inference_model_request_error_total",
-				"inference_model_request_duration_seconds",
-				// TODO: normalized_time_per_output_token_seconds is not actually recorded yet
-				// "normalized_time_per_output_token_seconds",
-				"inference_model_request_sizes",
-				"inference_model_response_sizes",
-				"inference_model_input_tokens",
-				"inference_model_output_tokens",
-				"inference_pool_average_kv_cache_utilization",
-				"inference_pool_average_queue_size",
-				"inference_pool_per_pod_queue_size",
-				"inference_model_running_requests",
-				"inference_pool_ready_pods",
-				"inference_extension_info",
+			verifyMetrics()
+		})
+	})
+
+	ginkgo.When("Leader election is enabled", func() {
+		ginkgo.It("Should elect one leader and have other pods as not ready", func() {
+			if !leaderElectionEnabled {
+				ginkgo.Skip("Leader election is not enabled for this test run, skipping.")
 			}
 
-			// Generate traffic by sending requests through the inference extension
-			ginkgo.By("Generating traffic through the inference extension")
-			curlCmd := getCurlCommand(envoyName, nsName, envoyPort, modelName, curlTimeout, "/completions", "Write as if you were a critic: San Francisco", true)
+			ginkgo.By("Verifying that exactly one EPP pod is ready")
+			gomega.Eventually(func(g gomega.Gomega) {
+				podList := &corev1.PodList{}
+				err := cli.List(ctx, podList, client.InNamespace(nsName), client.MatchingLabels{"app": inferExtName})
+				g.Expect(err).NotTo(gomega.HaveOccurred())
 
-			// Run the curl command multiple times to generate some metrics data
-			for i := 0; i < 5; i++ {
-				_, err := testutils.ExecCommandInPod(ctx, cfg, scheme, kubeCli, nsName, "curl", "curl", curlCmd)
-				gomega.Expect(err).NotTo(gomega.HaveOccurred())
-			}
+				// The deployment should have 3 replicas for leader election.
+				g.Expect(podList.Items).To(gomega.HaveLen(3))
 
-			// modify the curl command to generate some error metrics
-			curlCmd[len(curlCmd)-1] = "invalid input"
-			for i := 0; i < 5; i++ {
-				_, err := testutils.ExecCommandInPod(ctx, cfg, scheme, kubeCli, nsName, "curl", "curl", curlCmd)
-				gomega.Expect(err).NotTo(gomega.HaveOccurred())
+				readyPods := 0
+				for _, pod := range podList.Items {
+					for _, cond := range pod.Status.Conditions {
+						if cond.Type == corev1.PodReady && cond.Status == corev1.ConditionTrue {
+							readyPods++
+						}
+					}
+				}
+				g.Expect(readyPods).To(gomega.Equal(1), "Expected exactly one pod to be ready")
+			}, readyTimeout, interval).Should(gomega.Succeed())
+		})
+
+		ginkgo.It("Should successfully failover and serve traffic after the leader pod is deleted", func() {
+			if !leaderElectionEnabled {
+				ginkgo.Skip("Leader election is not enabled for this test run, skipping.")
 			}
 
-			// Now scrape metrics from the EPP endpoint via the curl pod
-			ginkgo.By("Scraping metrics from the EPP endpoint")
+			ginkgo.By("STEP 1: Verifying initial leader is working correctly before failover")
+			verifyTrafficRouting()
+			verifyMetrics()
+
+			ginkgo.By("STEP 2: Finding and deleting the current leader pod")
+			oldLeaderPod := findReadyPod()
+			ginkgo.By("Found initial leader pod: " + oldLeaderPod.Name)
 
-			// Get Pod IP instead of Service
-			podList := &corev1.PodList{}
-			err := cli.List(ctx, podList, client.InNamespace(nsName), client.MatchingLabels{"app": inferExtName})
-			gomega.Expect(err).NotTo(gomega.HaveOccurred())
-			gomega.Expect(podList.Items).NotTo(gomega.BeEmpty())
-			podIP := podList.Items[0].Status.PodIP
-			gomega.Expect(podIP).NotTo(gomega.BeEmpty())
+			ginkgo.By(fmt.Sprintf("Deleting leader pod %s to trigger failover", oldLeaderPod.Name))
+			gomega.Expect(cli.Delete(ctx, oldLeaderPod)).To(gomega.Succeed())
 
-			// Get the authorization token for reading metrics
-			token := ""
+			ginkgo.By("STEP 3: Waiting for a new leader to be elected")
+			// The deployment controller will create a new pod. We need to wait for the total number of pods
+			// to be back to 3, and for one of the other pods to become the new leader.
+			deploy := &appsv1.Deployment{}
 			gomega.Eventually(func() error {
-				token, err = getMetricsReaderToken(cli)
-				if err != nil {
-					return err
-				}
-				if token == "" {
-					return errors.New("token not found")
-				}
-				return nil
+				return cli.Get(ctx, types.NamespacedName{Namespace: nsName, Name: inferExtName}, deploy)
 			}, existsTimeout, interval).Should(gomega.Succeed())
 
-			// Construct the metric scraping curl command using Pod IP
-			metricScrapeCmd := []string{
-				"curl",
-				"-i",
-				"--max-time",
-				strconv.Itoa((int)(curlTimeout.Seconds())),
-				"-H",
-				"Authorization: Bearer " + token,
-				fmt.Sprintf("http://%s:%d/metrics", podIP, 9090),
-			}
+			// Wait for one replica to become ready again.
+			testutils.DeploymentReadyReplicas(ctx, cli, deploy, 1, readyTimeout, interval)
 
-			ginkgo.By("Verifying that all expected metrics are present.")
-			gomega.Eventually(func() error {
-				// Execute the metrics scrape command inside the curl pod
-				resp, err := testutils.ExecCommandInPod(ctx, cfg, scheme, kubeCli, nsName, "curl", "curl", metricScrapeCmd)
-				if err != nil {
-					return err
-				}
-				// Verify that we got a 200 OK responsecurl
-				if !strings.Contains(resp, "200 OK") {
-					return fmt.Errorf("did not get 200 OK: %s", resp)
-				}
-				// Check if all expected metrics are present in the metrics output
-				for _, metric := range expectedMetrics {
-					if !strings.Contains(resp, metric) {
-						return fmt.Errorf("expected metric %s not found in metrics output", metric)
-					}
-				}
-				return nil
-			}, readyTimeout, curlInterval).Should(gomega.Succeed())
+			// Also wait for the total number of replicas to be back to 3.
+			gomega.Eventually(func(g gomega.Gomega) {
+				d := &appsv1.Deployment{}
+				err := cli.Get(ctx, types.NamespacedName{Namespace: nsName, Name: inferExtName}, d)
+				g.Expect(err).NotTo(gomega.HaveOccurred())
+				g.Expect(d.Status.Replicas).To(gomega.Equal(int32(3)), "Deployment should have 3 replicas")
+			}, readyTimeout, interval).Should(gomega.Succeed())
+
+			ginkgo.By("STEP 4: Verifying a new, different leader is elected")
+			var newLeaderPod *corev1.Pod
+			gomega.Eventually(func(g gomega.Gomega) {
+				// Find the current ready pod.
+				newLeaderPod = findReadyPod()
+
+				// Ensure the new leader is not the same as the one we just deleted.
+				// This guards against a race condition where we might find the old leader
+				// before its status is updated to NotReady.
+				g.Expect(newLeaderPod.Name).NotTo(gomega.Equal(oldLeaderPod.Name), "The new leader should not be the same as the old deleted leader")
+			}, readyTimeout, interval).Should(gomega.Succeed())
+			ginkgo.By("Found new leader pod: " + newLeaderPod.Name)
+
+			ginkgo.By("STEP 5: Verifying the new leader is working correctly after failover")
+			verifyTrafficRouting()
+			verifyMetrics()
 		})
 	})
 })
 
 // newInferenceObjective creates an InferenceObjective in the given namespace for testutils.
 func newInferenceObjective(ns string) *v1alpha2.InferenceObjective {
-	targets := []v1alpha2.TargetModel{
-		{
-			Name:   targetModelName,
-			Weight: ptr.To(int32(100)),
-		},
-	}
 	return testutils.MakeModelWrapper(types.NamespacedName{Name: "inferenceobjective-sample", Namespace: ns}).
-		SetCriticality(v1alpha2.Critical).
+		SetPriority(2).
 		SetPoolRef(modelServerName).
-		SetTargetModels(targets).
 		Obj()
 }
 
+// verifyTrafficRouting contains the logic for the "Should route traffic to target model servers" test.
+func verifyTrafficRouting() {
+	ginkgo.By("Verifying traffic routing")
+	for _, t := range []struct {
+		api              string
+		promptOrMessages any
+	}{
+		{
+			api:              "/completions",
+			promptOrMessages: "Write as if you were a critic: San Francisco",
+		},
+		{
+			api: "/chat/completions",
+			promptOrMessages: []map[string]any{
+				{
+					"role":    "user",
+					"content": "Write as if you were a critic: San Francisco",
+				},
+			},
+		},
+		{
+			api: "/chat/completions",
+			promptOrMessages: []map[string]any{
+				{
+					"role":    "user",
+					"content": "Write as if you were a critic: San Francisco",
+				},
+				{"role": "assistant", "content": "Okay, let's see..."},
+				{"role": "user", "content": "Now summarize your thoughts."},
+			},
+		},
+	} {
+		ginkgo.By(fmt.Sprintf("Verifying connectivity through the inference extension with %s api and prompt/messages: %v", t.api, t.promptOrMessages))
+
+		// Ensure the expected responses include the InferenceObjective target model names.
+		var expected []string
+		expected = append(expected, targetModelName)
+		curlCmd := getCurlCommand(envoyName, nsName, envoyPort, modelName, curlTimeout, t.api, t.promptOrMessages, false)
+
+		actual := make(map[string]int)
+		gomega.Eventually(func() error {
+			resp, err := testutils.ExecCommandInPod(ctx, cfg, scheme, kubeCli, nsName, "curl", "curl", curlCmd)
+			if err != nil {
+				return err
+			}
+			if !strings.Contains(resp, "200 OK") {
+				return fmt.Errorf("did not get 200 OK: %s", resp)
+			}
+			for _, m := range expected {
+				if strings.Contains(resp, m) {
+					actual[m] = 0
+				}
+			}
+			var got []string
+			for m := range actual {
+				got = append(got, m)
+			}
+			// Compare ignoring order
+			if !cmp.Equal(got, expected, cmpopts.SortSlices(func(a, b string) bool { return a < b })) {
+				return fmt.Errorf("actual (%v) != expected (%v); resp=%q", got, expected, resp)
+			}
+			return nil
+		}, readyTimeout, curlInterval).Should(gomega.Succeed())
+	}
+}
+
+// verifyMetrics contains the logic for the "Should expose EPP metrics after generating traffic" test.
+func verifyMetrics() {
+	ginkgo.By("Verifying metrics exposure")
+	// Define the metrics we expect to see
+	expectedMetrics := []string{
+		"inference_model_request_total",
+		"inference_model_request_error_total",
+		"inference_model_request_duration_seconds",
+		// TODO: normalized_time_per_output_token_seconds is not actually recorded yet
+		// "normalized_time_per_output_token_seconds",
+		"inference_model_request_sizes",
+		"inference_model_response_sizes",
+		"inference_model_input_tokens",
+		"inference_model_output_tokens",
+		"inference_pool_average_kv_cache_utilization",
+		"inference_pool_average_queue_size",
+		"inference_pool_per_pod_queue_size",
+		"inference_model_running_requests",
+		"inference_pool_ready_pods",
+		"inference_extension_info",
+	}
+
+	// Generate traffic by sending requests through the inference extension
+	ginkgo.By("Generating traffic through the inference extension")
+	curlCmd := getCurlCommand(envoyName, nsName, envoyPort, modelName, curlTimeout, "/completions", "Write as if you were a critic: San Francisco", true)
+
+	// Run the curl command multiple times to generate some metrics data
+	for i := 0; i < 5; i++ {
+		_, err := testutils.ExecCommandInPod(ctx, cfg, scheme, kubeCli, nsName, "curl", "curl", curlCmd)
+		gomega.Expect(err).NotTo(gomega.HaveOccurred())
+	}
+
+	// modify the curl command to generate some error metrics
+	curlCmd[len(curlCmd)-1] = "invalid input"
+	for i := 0; i < 5; i++ {
+		_, err := testutils.ExecCommandInPod(ctx, cfg, scheme, kubeCli, nsName, "curl", "curl", curlCmd)
+		gomega.Expect(err).NotTo(gomega.HaveOccurred())
+	}
+
+	// Now scrape metrics from the EPP endpoint via the curl pod
+	ginkgo.By("Scraping metrics from the EPP endpoint")
+	podIP := findReadyPod().Status.PodIP
+
+	// Get the authorization token for reading metrics
+	token := ""
+	gomega.Eventually(func(g gomega.Gomega) {
+		t, err := getMetricsReaderToken(cli)
+		g.Expect(err).NotTo(gomega.HaveOccurred())
+		g.Expect(t).NotTo(gomega.BeEmpty())
+		token = t
+	}, existsTimeout, interval).Should(gomega.Succeed())
+
+	// Construct the metric scraping curl command using Pod IP
+	metricScrapeCmd := getMetricsScrapeCommand(podIP, token)
+
+	ginkgo.By("Verifying that all expected metrics are present.")
+	gomega.Eventually(func() error {
+		// Execute the metrics scrape command inside the curl pod
+		resp, err := testutils.ExecCommandInPod(ctx, cfg, scheme, kubeCli, nsName, "curl", "curl", metricScrapeCmd)
+		if err != nil {
+			return err
+		}
+		// Verify that we got a 200 OK responsecurl
+		if !strings.Contains(resp, "200 OK") {
+			return fmt.Errorf("did not get 200 OK: %s", resp)
+		}
+		// Check if all expected metrics are present in the metrics output
+		for _, metric := range expectedMetrics {
+			if !strings.Contains(resp, metric) {
+				return fmt.Errorf("expected metric %s not found in metrics output", metric)
+			}
+		}
+		return nil
+	}, readyTimeout, curlInterval).Should(gomega.Succeed())
+}
+
 func getMetricsReaderToken(k8sClient client.Client) (string, error) {
 	secret := &corev1.Secret{}
 	err := k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: metricsReaderSecretName}, secret)
@@ -256,6 +321,43 @@ func getMetricsReaderToken(k8sClient client.Client) (string, error) {
 	return string(secret.Data["token"]), nil
 }
 
+// findReadyPod finds the first EPP pod that has a "Ready" status condition.
+// It's used to target the leader pod in an HA setup.
+func findReadyPod() *corev1.Pod {
+	var readyPod *corev1.Pod
+	gomega.Eventually(func(g gomega.Gomega) {
+		podList := &corev1.PodList{}
+		err := cli.List(ctx, podList, client.InNamespace(nsName), client.MatchingLabels{"app": inferExtName})
+		g.Expect(err).NotTo(gomega.HaveOccurred())
+
+		foundReadyPod := false
+		for i := range podList.Items {
+			pod := &podList.Items[i]
+			for _, cond := range pod.Status.Conditions {
+				if cond.Type == corev1.PodReady && cond.Status == corev1.ConditionTrue {
+					g.Expect(pod.Status.PodIP).NotTo(gomega.BeEmpty(), "Ready pod must have an IP")
+					readyPod = pod
+					foundReadyPod = true
+					break // break inner loop
+				}
+			}
+			if foundReadyPod {
+				break // break outer loop
+			}
+		}
+		g.Expect(foundReadyPod).To(gomega.BeTrue(), "No ready EPP pod found")
+	}, readyTimeout, interval).Should(gomega.Succeed())
+	return readyPod
+}
+
+// getMetricsScrapeCommand returns the command to scrape the /metrics endpoint.
+func getMetricsScrapeCommand(podIP, token string) []string {
+	return []string{
+		"curl", "-i", "--max-time", strconv.Itoa((int)(curlTimeout.Seconds())),
+		"-H", "Authorization: Bearer " + token, fmt.Sprintf("http://%s:%d/metrics", podIP, 9090),
+	}
+}
+
 // getCurlCommand returns the command, as a slice of strings, for curl'ing
 // the test model server at the given name, namespace, port, and model name.
 func getCurlCommand(name, ns, port, model string, timeout time.Duration, api string, promptOrMessages any, streaming bool) []string {
@@ -289,6 +391,8 @@ func getCurlCommand(name, ns, port, model string, timeout time.Duration, api str
 		"Content-Type: application/json",
 		"-H",
 		fmt.Sprintf("%v: inferenceobjective-sample", metadata.ObjectiveKey),
+		"-H",
+		fmt.Sprintf("%v: %s", metadata.ModelNameRewriteKey, targetModelName),
 		"-d",
 		string(b),
 	}
diff --git a/test/integration/bbr/hermetic_test.go b/test/integration/bbr/hermetic_test.go
index 69654be..e1c25a7 100644
--- a/test/integration/bbr/hermetic_test.go
+++ b/test/integration/bbr/hermetic_test.go
@@ -108,7 +108,7 @@ func TestFullDuplexStreamed_BodyBasedRouting(t *testing.T) {
 	}{
 		{
 			name: "success adding model parameter to header",
-			reqs: integrationutils.GenerateStreamedRequestSet(logger, "test", "foo", nil),
+			reqs: integrationutils.GenerateStreamedRequestSet(logger, "test", "foo", "foo", nil),
 			wantResponses: []*extProcPb.ProcessingResponse{
 				{
 					Response: &extProcPb.ProcessingResponse_RequestHeaders{
@@ -213,7 +213,7 @@ func TestFullDuplexStreamed_BodyBasedRouting(t *testing.T) {
 		},
 		{
 			name: "no model parameter",
-			reqs: integrationutils.GenerateStreamedRequestSet(logger, "test", "", nil),
+			reqs: integrationutils.GenerateStreamedRequestSet(logger, "test", "", "", nil),
 			wantResponses: []*extProcPb.ProcessingResponse{
 				{
 					Response: &extProcPb.ProcessingResponse_RequestHeaders{
diff --git a/test/integration/epp/hermetic_test.go b/test/integration/epp/hermetic_test.go
index c82a702..b421f7f 100644
--- a/test/integration/epp/hermetic_test.go
+++ b/test/integration/epp/hermetic_test.go
@@ -161,7 +161,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) {
 		// Request flow tests
 		{
 			name:     "select lower queue and kv cache, no active lora",
-			requests: integrationutils.GenerateStreamedRequestSet(logger, "test1", modelMyModel, nil),
+			requests: integrationutils.GenerateStreamedRequestSet(logger, "test1", modelMyModel, modelMyModelTarget, nil),
 			// Pod 1 will be picked because it has relatively low queue size and low KV cache.
 			pods: newPodStates(
 				podState{index: 0, queueSize: 3, kvCacheUsage: 0.2},
@@ -226,7 +226,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) {
 		},
 		{
 			name:     "select active lora, low queue",
-			requests: integrationutils.GenerateStreamedRequestSet(logger, "test2", modelSQLLora, nil),
+			requests: integrationutils.GenerateStreamedRequestSet(logger, "test2", modelSQLLora, modelSQLLoraTarget, nil),
 			// Pod 1 will be picked because it has relatively low queue size, the requested model active, and low KV cache.
 			pods: newPodStates(
 				podState{index: 0, queueSize: 0, kvCacheUsage: 0.2, activeModels: []string{"foo", "bar"}},
@@ -254,7 +254,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) {
 		},
 		{
 			name:     "select lora despite higher kv cache usage",
-			requests: integrationutils.GenerateStreamedRequestSet(logger, "test3", modelSQLLora, nil),
+			requests: integrationutils.GenerateStreamedRequestSet(logger, "test3", modelSQLLora, modelSQLLoraTarget, nil),
 			// Pod 2 will be picked despite NOT having the requested model active as it is above the affinity for queue size.
 			// Also it is critical, so we should still admit the request despite all queue sizes being greater than the queue
 			// size threshold.
@@ -282,42 +282,26 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) {
 			),
 		},
 		{
-			name:     "noncritical and all models past threshold, shed request",
-			requests: integrationutils.GenerateStreamedRequestSet(logger, "test4", modelSheddable, nil),
+			name:     "don't shed requests by default",
+			requests: integrationutils.GenerateStreamedRequestSet(logger, "test4", modelSQLLora, modelSQLLoraTarget, nil),
 			// pod 0: excluded; above queue size threshold
 			// pod 1: excluded; above KV cache threshold
 			// pod 2: excluded; above queue size threshold
 			pods: newPodStates(
-				podState{index: 0, queueSize: 6, kvCacheUsage: 0.2, activeModels: []string{"foo", "bar", modelSheddableTarget}},
-				podState{index: 1, queueSize: 0, kvCacheUsage: 0.85, activeModels: []string{"foo", modelSheddableTarget}},
-				podState{index: 2, queueSize: 10, kvCacheUsage: 0.9, activeModels: []string{"foo", modelSheddableTarget}},
-			),
-			wantErr:     false,
-			wantMetrics: map[string]string{},
-			wantResponses: integrationutils.NewImmediateErrorResponse(
-				envoyTypePb.StatusCode_TooManyRequests,
-				"inference gateway: InferencePoolResourceExhausted - system saturated, non-critical request dropped",
-			),
-		},
-		{
-			name:     "noncritical, but one server has capacity, do not shed",
-			requests: integrationutils.GenerateStreamedRequestSet(logger, "test5", modelSheddable, nil),
-			// Pod 1 will be picked because it has relatively low queue size and low KV cache.
-			pods: newPodStates(
-				podState{index: 0, queueSize: 4, kvCacheUsage: 0.2, activeModels: []string{"foo", "bar", modelSheddableTarget}},
-				podState{index: 1, queueSize: 4, kvCacheUsage: 0.85, activeModels: []string{"foo", modelSheddableTarget}},
-				podState{index: 2, queueSize: 10, kvCacheUsage: 0.9, activeModels: []string{"foo", modelSheddableTarget}},
+				podState{index: 0, queueSize: 6, kvCacheUsage: 0.2, activeModels: []string{"foo", "bar", modelSQLLoraTarget}},
+				podState{index: 1, queueSize: 0, kvCacheUsage: 0.85, activeModels: []string{"foo"}},
+				podState{index: 2, queueSize: 10, kvCacheUsage: 0.9, activeModels: []string{"foo"}},
 			),
 			wantMetrics: map[string]string{
 				"inference_model_request_total": inferenceObjectiveRequestTotal([]label{
-					{"model_name", modelSheddable},
-					{"target_model_name", modelSheddableTarget},
+					{"model_name", modelSQLLora},
+					{"target_model_name", modelSQLLoraTarget},
 				}),
 			},
 			wantErr: false,
 			wantResponses: integrationutils.NewRequestBufferedResponse(
 				"192.168.1.1:8000",
-				fmt.Sprintf(`{"max_tokens":100,"model":%q,"prompt":"test5","temperature":0}`, modelSheddableTarget),
+				fmt.Sprintf(`{"max_tokens":100,"model":%q,"prompt":"test4","temperature":0}`, modelSQLLoraTarget),
 				&configPb.HeaderValueOption{
 					Header: &configPb.HeaderValue{
 						Key:      "hi",
@@ -340,7 +324,11 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) {
 									},
 									{
 										Key:   metadata.ObjectiveKey,
-										Value: "sql-lora-sheddable",
+										Value: modelSheddable,
+									},
+									{
+										Key:   metadata.ModelNameRewriteKey,
+										Value: modelSheddableTarget,
 									},
 								},
 							},
@@ -398,6 +386,14 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) {
 										Key:   metadata.ObjectiveKey,
 										Value: modelDirect,
 									},
+									{
+										Key:   metadata.ModelNameRewriteKey,
+										Value: modelDirect,
+									},
+									{
+										Key:   metadata.ModelNameRewriteKey,
+										Value: modelDirect,
+									},
 								},
 							},
 						},
@@ -757,6 +753,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) {
 				logger,
 				"test2",
 				modelSQLLora,
+				modelSQLLoraTarget,
 				[]string{"192.168.1.1:8000", "192.168.1.2:8000", "192.168.1.3:8000"}),
 			// Pod 1 will be picked because it has relatively low queue size, the requested model active, low KV cache, and within subset.
 			pods: newPodStates(
@@ -789,6 +786,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) {
 				logger,
 				"test2",
 				modelSQLLora,
+				modelSQLLoraTarget,
 				[]string{"192.168.1.3:8000"}),
 			// Pod 3 has high queue and kv cache utilization, but it will still be picked because it is the only one matching subsetting target.
 			pods: newPodStates(
@@ -821,6 +819,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) {
 				logger,
 				"test2",
 				modelSQLLora,
+				modelSQLLoraTarget,
 				[]string{"192.168.1.4:8000", "192.168.1.5:8000", "192.168.1.6:8000"}),
 			// No pods will be picked as none are within the subset.
 			pods: newPodStates(
@@ -986,7 +985,7 @@ func setUpHermeticServer(t *testing.T, podAndMetrics map[*backend.Pod]*backendme
 
 	// check if all pods are synced to datastore
 	assert.EventuallyWithT(t, func(t *assert.CollectT) {
-		assert.Len(t, serverRunner.Datastore.PodList(backendmetrics.AllPodPredicate), len(podAndMetrics), "Datastore not synced")
+		assert.Len(t, serverRunner.Datastore.PodList(backendmetrics.AllPodsPredicate), len(podAndMetrics), "Datastore not synced")
 	}, 10*time.Second, time.Second)
 
 	// Create a grpc connection
@@ -1095,7 +1094,7 @@ func BeforeSuite() func() {
 
 	serverRunner = server.NewDefaultExtProcServerRunner()
 	serverRunner.TestPodMetricsClient = &backendmetrics.FakePodMetricsClient{}
-	pmf := backendmetrics.NewPodMetricsFactory(serverRunner.TestPodMetricsClient, 10*time.Millisecond, time.Second*2)
+	pmf := backendmetrics.NewPodMetricsFactory(serverRunner.TestPodMetricsClient, 10*time.Millisecond)
 	// Adjust from defaults
 	serverRunner.PoolGKNN = common.GKNN{
 		NamespacedName: types.NamespacedName{Namespace: testNamespace, Name: testPoolName},
@@ -1105,7 +1104,7 @@ func BeforeSuite() func() {
 
 	kvCacheUtilizationScorer := scorer.NewKVCacheUtilizationScorer()
 	queueingScorer := scorer.NewQueueScorer()
-	prefixCacheScorer := prefix.New(prefix.DefaultConfig)
+	prefixCacheScorer := prefix.New(context.Background(), prefix.DefaultConfig)
 	loraAffinityScorer := scorer.NewLoraAffinityScorer()
 
 	defaultProfile := framework.NewSchedulerProfile().
diff --git a/test/integration/util.go b/test/integration/util.go
index 38ef0a1..0052449 100644
--- a/test/integration/util.go
+++ b/test/integration/util.go
@@ -111,7 +111,7 @@ func GenerateRequest(logger logr.Logger, prompt, model string, filterMetadata []
 	return req
 }
 
-func GenerateStreamedRequestSet(logger logr.Logger, prompt, model string, filterMetadata []string) []*extProcPb.ProcessingRequest {
+func GenerateStreamedRequestSet(logger logr.Logger, prompt, model, targetModel string, filterMetadata []string) []*extProcPb.ProcessingRequest {
 	requests := []*extProcPb.ProcessingRequest{}
 	headerReq := &extProcPb.ProcessingRequest{
 		Request: &extProcPb.ProcessingRequest_RequestHeaders{
@@ -126,6 +126,10 @@ func GenerateStreamedRequestSet(logger logr.Logger, prompt, model string, filter
 							Key:   metadata.ObjectiveKey,
 							Value: model,
 						},
+						{
+							Key:   metadata.ModelNameRewriteKey,
+							Value: targetModel,
+						},
 					},
 				},
 			},
diff --git a/test/testdata/inferencepool-e2e.yaml b/test/testdata/inferencepool-e2e.yaml
index 2a3c744..9fa5059 100644
--- a/test/testdata/inferencepool-e2e.yaml
+++ b/test/testdata/inferencepool-e2e.yaml
@@ -4,10 +4,12 @@ metadata:
   labels:
   name: vllm-llama3-8b-instruct
 spec:
-  targetPortNumber: 8000
+  targetPorts:
+    - number: 8000
   selector:
-    app: vllm-llama3-8b-instruct
-  extensionRef:
+    matchLabels:
+      app: vllm-llama3-8b-instruct
+  endpointPickerRef:
     name: vllm-llama3-8b-instruct-epp
     namespace: $E2E_NS
 ---
diff --git a/test/testdata/inferencepool-leader-election-e2e.yaml b/test/testdata/inferencepool-leader-election-e2e.yaml
new file mode 100644
index 0000000..8c24347
--- /dev/null
+++ b/test/testdata/inferencepool-leader-election-e2e.yaml
@@ -0,0 +1,206 @@
+apiVersion: inference.networking.k8s.io/v1
+kind: InferencePool
+metadata:
+  labels:
+  name: vllm-llama3-8b-instruct
+spec:
+  targetPortNumber: 8000
+  selector:
+    app: vllm-llama3-8b-instruct
+  endpointPickerRef:
+    name: vllm-llama3-8b-instruct-epp
+    namespace: $E2E_NS
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: vllm-llama3-8b-instruct-epp
+  namespace: $E2E_NS
+spec:
+  selector:
+    app: vllm-llama3-8b-instruct-epp
+  ports:
+    - protocol: TCP
+      port: 9002
+      targetPort: 9002
+      appProtocol: http2
+  type: ClusterIP
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: vllm-llama3-8b-instruct-epp
+  namespace: $E2E_NS
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: vllm-llama3-8b-instruct-epp
+  namespace: $E2E_NS
+  labels:
+    app: vllm-llama3-8b-instruct-epp
+spec:
+  replicas: 3
+  selector:
+    matchLabels:
+      app: vllm-llama3-8b-instruct-epp
+  template:
+    metadata:
+      labels:
+        app: vllm-llama3-8b-instruct-epp
+    spec:
+      serviceAccountName: vllm-llama3-8b-instruct-epp
+      # Conservatively, this timeout should mirror the longest grace period of the pods within the pool
+      terminationGracePeriodSeconds: 130
+      containers:
+      - name: epp
+        image: $E2E_IMAGE
+        imagePullPolicy: IfNotPresent
+        args:
+        - --pool-name
+        - "vllm-llama3-8b-instruct"
+        - --pool-namespace
+        - "$E2E_NS"
+        - --v
+        - "4"
+        - --zap-encoder
+        - "json"
+        - --grpc-port
+        - "9002"
+        - --grpc-health-port
+        - "9003"
+        - --ha-enable-leader-election
+        - "--config-file"
+        - "/config/default-plugins.yaml"
+        ports:
+        - containerPort: 9002
+        - containerPort: 9003
+        - name: metrics
+          containerPort: 9090
+        livenessProbe:
+          grpc:
+            port: 9003
+            service: liveness
+          initialDelaySeconds: 5
+          periodSeconds: 10
+        readinessProbe:
+          grpc:
+            port: 9003
+            service: readiness
+          initialDelaySeconds: 5
+          periodSeconds: 10
+        volumeMounts:
+        - name: plugins-config-volume
+          mountPath: "/config"
+      volumes:
+      - name: plugins-config-volume
+        configMap:
+          name: plugins-config
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: plugins-config
+  namespace: $E2E_NS
+data:
+  default-plugins.yaml: |
+    apiVersion: inference.networking.x-k8s.io/v1alpha1
+    kind: EndpointPickerConfig
+    plugins:
+    - type: queue-scorer
+    - type: kv-cache-utilization-scorer
+    - type: prefix-cache-scorer
+    schedulingProfiles:
+    - name: default
+      plugins:
+      - pluginRef: queue-scorer
+      - pluginRef: kv-cache-utilization-scorer
+      - pluginRef: prefix-cache-scorer
+---
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: pod-read
+  namespace: $E2E_NS
+rules:
+- apiGroups: [ "inference.networking.x-k8s.io" ]
+  resources: [ "inferenceobjectives", "inferencepools" ]
+  verbs: [ "get", "watch", "list" ]
+- apiGroups: [ "inference.networking.k8s.io" ]
+  resources: [ "inferencepools" ]
+  verbs: [ "get", "watch", "list" ]
+- apiGroups: [ "" ]
+  resources: [ "pods" ]
+  verbs: [ "get", "watch", "list" ]
+---
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: epp-leader-election
+  namespace: $E2E_NS
+rules:
+- apiGroups: [ "coordination.k8s.io" ]
+  resources: [ "leases" ]
+  verbs: [ "get", "list", "watch", "create", "update", "patch", "delete" ]
+- apiGroups: [ "" ]
+  resources: [ "events" ]
+  verbs: [ "create", "patch" ]
+---
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: epp-leader-election-binding
+  namespace: $E2E_NS
+subjects:
+- kind: ServiceAccount
+  name: vllm-llama3-8b-instruct-epp
+  namespace: $E2E_NS
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: epp-leader-election
+---
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: pod-read-binding
+  namespace: $E2E_NS
+subjects:
+- kind: ServiceAccount
+  name: vllm-llama3-8b-instruct-epp
+  namespace: $E2E_NS
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: pod-read
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: auth-reviewer
+rules:
+- apiGroups:
+  - authentication.k8s.io
+  resources:
+  - tokenreviews
+  verbs:
+  - create
+- apiGroups:
+  - authorization.k8s.io
+  resources:
+  - subjectaccessreviews
+  verbs:
+  - create
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: auth-reviewer-binding
+subjects:
+- kind: ServiceAccount
+  name: vllm-llama3-8b-instruct-epp
+  namespace: $E2E_NS
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: auth-reviewer
diff --git a/test/testdata/inferencepool-with-model-hermetic.yaml b/test/testdata/inferencepool-with-model-hermetic.yaml
index 81798e6..794b7fc 100644
--- a/test/testdata/inferencepool-with-model-hermetic.yaml
+++ b/test/testdata/inferencepool-with-model-hermetic.yaml
@@ -4,10 +4,12 @@ metadata:
   name: vllm-llama3-8b-instruct-pool
   namespace: default
 spec:
-  targetPortNumber: 8000
+  targetPorts:
+    - number: 8000
   selector:
-    app: vllm-llama3-8b-instruct-pool
-  extensionRef:
+    matchLabels:
+      app: vllm-llama3-8b-instruct-pool
+  endpointPickerRef:
     name: epp
 ---
 apiVersion: inference.networking.x-k8s.io/v1alpha2
@@ -16,10 +18,9 @@ metadata:
   name: sql-lora
   namespace: default
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
     name: vllm-llama3-8b-instruct-pool
-    group: inference.networking.k8s.io
   targetModels:
   - name: sql-lora-1fdg2
     weight: 100
@@ -32,7 +33,6 @@ metadata:
 spec:
   poolRef:
     name: vllm-llama3-8b-instruct-pool
-    group: inference.networking.k8s.io
   targetModels:
   - name: sql-lora-1fdg3
     weight: 100
@@ -43,10 +43,9 @@ metadata:
   name: my-model
   namespace: default
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
     name: vllm-llama3-8b-instruct-pool
-    group: inference.networking.k8s.io
   targetModels:
   - name: my-model-12345
     weight: 100    
@@ -57,7 +56,6 @@ metadata:
   name: direct-model
   namespace: default
 spec:
-  criticality: Critical
+  priority: 2
   poolRef:
     name: vllm-llama3-8b-instruct-pool
-    group: inference.networking.k8s.io
\ No newline at end of file
diff --git a/test/utils/server.go b/test/utils/server.go
index fd16580..51eb33f 100644
--- a/test/utils/server.go
+++ b/test/utils/server.go
@@ -49,7 +49,7 @@ func PrepareForTestStreamingServer(objectives []*v1alpha2.InferenceObjective, po
 	ctx, cancel := context.WithCancel(context.Background())
 
 	pmc := &metrics.FakePodMetricsClient{}
-	pmf := metrics.NewPodMetricsFactory(pmc, time.Second, time.Second*2)
+	pmf := metrics.NewPodMetricsFactory(pmc, time.Second)
 	ds := datastore.NewDatastore(ctx, pmf)
 
 	initObjs := []client.Object{}
@@ -71,7 +71,7 @@ func PrepareForTestStreamingServer(objectives []*v1alpha2.InferenceObjective, po
 		WithObjects(initObjs...).
 		Build()
 	pool := testutil.MakeInferencePool(poolName).Namespace(namespace).ObjRef()
-	pool.Spec.TargetPortNumber = poolPort
+	pool.Spec.TargetPorts = []v1.Port{{Number: v1.PortNumber(poolPort)}}
 	_ = ds.PoolSet(context.Background(), fakeClient, pool)
 
 	return ctx, cancel, ds, pmc
diff --git a/test/utils/utils.go b/test/utils/utils.go
index c3e2759..8cc19b7 100644
--- a/test/utils/utils.go
+++ b/test/utils/utils.go
@@ -204,6 +204,18 @@ func DeploymentAvailable(ctx context.Context, cli client.Client, deploy *appsv1.
 	gomega.Eventually(checkDeploymentStatus, timeout, interval).WithArguments(ctx, cli, deploy, conditions).Should(gomega.BeTrue())
 }
 
+// DeploymentReadyReplicas checks if the given Deployment has at least `count` ready replicas before the given timeout.
+func DeploymentReadyReplicas(ctx context.Context, cli client.Client, deploy *appsv1.Deployment, count int, timeout, interval time.Duration) {
+	ginkgo.By(fmt.Sprintf("Checking if deployment %s/%s has at least %d ready replica(s)", deploy.Namespace, deploy.Name, count))
+	gomega.Eventually(func(g gomega.Gomega) {
+		var fetchedDeploy appsv1.Deployment
+		err := cli.Get(ctx, types.NamespacedName{Namespace: deploy.Namespace, Name: deploy.Name}, &fetchedDeploy)
+		g.Expect(err).NotTo(gomega.HaveOccurred())
+		g.Expect(fetchedDeploy.Status.ReadyReplicas).To(gomega.BeNumerically(">=", count),
+			fmt.Sprintf("Deployment only has %d ready replicas, want at least %d", fetchedDeploy.Status.ReadyReplicas, count))
+	}, timeout, interval).Should(gomega.Succeed())
+}
+
 // checkDeploymentStatus checks if the given Deployment status matches the expected conditions.
 func checkDeploymentStatus(ctx context.Context, cli client.Client, deploy *appsv1.Deployment, conditions []appsv1.DeploymentCondition) (bool, error) {
 	var fetchedDeploy appsv1.Deployment
diff --git a/test/utils/wrappers.go b/test/utils/wrappers.go
index 1af90b0..f1c0098 100644
--- a/test/utils/wrappers.go
+++ b/test/utils/wrappers.go
@@ -44,9 +44,9 @@ func MakeModelWrapper(namespacedName types.NamespacedName) *InferenceObjectiveWr
 	}
 }
 
-// SetCriticality sets the value of the InferenceObjective.spec.criticality.
-func (m *InferenceObjectiveWrapper) SetCriticality(level v1alpha2.Criticality) *InferenceObjectiveWrapper {
-	m.Spec.Criticality = &level
+// SetPriority sets the value of the InferenceObjective.spec.priority.
+func (m *InferenceObjectiveWrapper) SetPriority(level int) *InferenceObjectiveWrapper {
+	m.Spec.Priority = &level
 	return m
 }
 
@@ -62,12 +62,6 @@ func (m *InferenceObjectiveWrapper) SetPoolRef(name string) *InferenceObjectiveW
 	return m
 }
 
-// SetTargetModels sets the value of the InferenceObjective.spec.targetModels.
-func (m *InferenceObjectiveWrapper) SetTargetModels(models []v1alpha2.TargetModel) *InferenceObjectiveWrapper {
-	m.Spec.TargetModels = models
-	return m
-}
-
 // Obj returns the inner InferenceObjective.
 func (m *InferenceObjectiveWrapper) Obj() *v1alpha2.InferenceObjective {
 	return &m.InferenceObjective