Repository: kubernetes-sigs/gateway-api

Issue:
Change conformance tests to read from supportedFeatures (GEP-2162)
/kind conformance

See title

cc: @youngnick @robscott 

Discussion:
@k8s-ci-robot: @LiorLieberman: The label(s) `kind/conformance` cannot be applied, because the repository doesn't have them.

<details>

In response to [this](https://github.com/kubernetes-sigs/gateway-api/issues/3759):

>/kind conformance
>
>See title
>
>cc: @youngnick @robscott 


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>

Solution patch:
diff --git a/apis/v1/gateway_types.go b/apis/v1/gateway_types.go
index 3e1ec4c7..f54e5d10 100644
--- a/apis/v1/gateway_types.go
+++ b/apis/v1/gateway_types.go
@@ -1062,6 +1062,37 @@ const (
 	GatewayReasonListenersNotReady GatewayConditionReason = "ListenersNotReady"
 )
 
+const (
+	// AttachedListenerSets is a condition that is true when the Gateway has
+	// at least one ListenerSet attached to it.
+	//
+	// Possible reasons for this condition to be True are:
+	//
+	// * "ListenerSetsAttached"
+	//
+	// Possible reasons for this condition to be False are:
+	//
+	// * "NoListenerSetsAttached"
+	// * "ListenerSetsNotAllowed"
+	//
+	// Controllers may raise this condition with other reasons,
+	// but should prefer to use the reasons listed above to improve
+	// interoperability.
+	GatewayConditionAttachedListenerSets GatewayConditionType = "AttachedListenerSets"
+
+	// This reason is used with the "AttachedListenerSets" condition when the
+	// Gateway has at least one ListenerSet attached to it.
+	GatewayReasonListenerSetsAttached GatewayConditionReason = "ListenerSetsAttached"
+
+	// This reason is used with the "AttachedListenerSets" condition when the
+	// Gateway has no ListenerSets attached to it.
+	GatewayReasonNoListenerSetsAttached GatewayConditionReason = "NoListenerSetsAttached"
+
+	// This reason is used with the "AttachedListenerSets" condition when the
+	// Gateway has ListenerSets attached to it, but the ListenerSets are not allowed.
+	GatewayReasonListenerSetsNotAllowed GatewayConditionReason = "ListenerSetsNotAllowed"
+)
+
 // ListenerStatus is the status associated with a Listener.
 type ListenerStatus struct {
 	// Name is the name of the Listener that this status corresponds to.
diff --git a/apisx/v1alpha1/xlistenerset_types.go b/apisx/v1alpha1/xlistenerset_types.go
index 529b7f35..92cca1b7 100644
--- a/apisx/v1alpha1/xlistenerset_types.go
+++ b/apisx/v1alpha1/xlistenerset_types.go
@@ -29,8 +29,33 @@ import (
 // +kubebuilder:printcolumn:name="Programmed",type=string,JSONPath=`.status.conditions[?(@.type=="Programmed")].status`
 // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
 
-// XListenerSet defines a set of additional listeners
-// to attach to an existing Gateway.
+// XListenerSet defines a set of additional listeners to attach to an existing Gateway.
+// This resource provides a mechanism to merge multiple listeners into a single Gateway.
+//
+// The parent Gateway must explicitly allow ListenerSet attachment through its
+// AllowedListeners configuration. By default, Gateways do not allow ListenerSet
+// attachment.
+//
+// Routes can attach to a ListenerSet by specifying it as a parentRef, and can
+// optionally target specific listeners using the sectionName field.
+//
+// Policy Attachment:
+// - Policies that attach to a ListenerSet apply to all listeners defined in that resource
+// - Policies do not impact listeners in the parent Gateway
+// - Different ListenerSets attached to the same Gateway can have different policies
+// - If an implementation cannot apply a policy to specific listeners, it should reject the policy
+//
+// ReferenceGrant Semantics:
+// - ReferenceGrants applied to a Gateway are not inherited by child ListenerSets
+// - ReferenceGrants applied to a ListenerSet do not grant permission to the parent Gateway's listeners
+// - A ListenerSet can reference secrets/backends in its own namespace without a ReferenceGrant
+//
+// Gateway Integration:
+// - The parent Gateway's status will include an "AttachedListenerSets" condition
+// - This condition will be:
+//   - True: when AllowedListeners is set and at least one child ListenerSet is attached
+//   - False: when AllowedListeners is set but no valid listeners are attached, or when AllowedListeners is not set or false
+//   - Unknown: when no AllowedListeners config is present
 type XListenerSet struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata,omitempty"`
@@ -63,10 +88,10 @@ type ListenerSetSpec struct {
 	//
 	// 1. "parent" Gateway
 	// 2. ListenerSet ordered by creation time (oldest first)
-	// 3. ListenerSet ordered alphabetically by “{namespace}/{name}”.
+	// 3. ListenerSet ordered alphabetically by "{namespace}/{name}".
 	//
 	// An implementation MAY reject listeners by setting the ListenerEntryStatus
-	// `Accepted`` condition to False with the Reason `TooManyListeners`
+	// `Accepted` condition to False with the Reason `TooManyListeners`
 	//
 	// If a listener has a conflict, this will be reported in the
 	// Status.ListenerEntryStatus setting the `Conflicted` condition to True.
diff --git a/config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml b/config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml
index 51763224..2454521c 100644
--- a/config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml
+++ b/config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml
@@ -34,8 +34,33 @@ spec:
     schema:
       openAPIV3Schema:
         description: |-
-          XListenerSet defines a set of additional listeners
-          to attach to an existing Gateway.
+          XListenerSet defines a set of additional listeners to attach to an existing Gateway.
+          This resource provides a mechanism to merge multiple listeners into a single Gateway.
+
+          The parent Gateway must explicitly allow ListenerSet attachment through its
+          AllowedListeners configuration. By default, Gateways do not allow ListenerSet
+          attachment.
+
+          Routes can attach to a ListenerSet by specifying it as a parentRef, and can
+          optionally target specific listeners using the sectionName field.
+
+          Policy Attachment:
+          - Policies that attach to a ListenerSet apply to all listeners defined in that resource
+          - Policies do not impact listeners in the parent Gateway
+          - Different ListenerSets attached to the same Gateway can have different policies
+          - If an implementation cannot apply a policy to specific listeners, it should reject the policy
+
+          ReferenceGrant Semantics:
+          - ReferenceGrants applied to a Gateway are not inherited by child ListenerSets
+          - ReferenceGrants applied to a ListenerSet do not grant permission to the parent Gateway's listeners
+          - A ListenerSet can reference secrets/backends in its own namespace without a ReferenceGrant
+
+          Gateway Integration:
+          - The parent Gateway's status will include an "AttachedListenerSets" condition
+          - This condition will be:
+            - True: when AllowedListeners is set and at least one child ListenerSet is attached
+            - False: when AllowedListeners is set but no valid listeners are attached, or when AllowedListeners is not set or false
+            - Unknown: when no AllowedListeners config is present
         properties:
           apiVersion:
             description: |-
@@ -73,10 +98,10 @@ spec:
 
                   1. "parent" Gateway
                   2. ListenerSet ordered by creation time (oldest first)
-                  3. ListenerSet ordered alphabetically by “{namespace}/{name}”.
+                  3. ListenerSet ordered alphabetically by "{namespace}/{name}".
 
                   An implementation MAY reject listeners by setting the ListenerEntryStatus
-                  `Accepted`` condition to False with the Reason `TooManyListeners`
+                  `Accepted` condition to False with the Reason `TooManyListeners`
 
                   If a listener has a conflict, this will be reported in the
                   Status.ListenerEntryStatus setting the `Conflicted` condition to True.
diff --git a/conformance/apis/v1/conformancereport.go b/conformance/apis/v1/conformancereport.go
index 7f4f4d53..dcea92df 100644
--- a/conformance/apis/v1/conformancereport.go
+++ b/conformance/apis/v1/conformancereport.go
@@ -50,6 +50,10 @@ type ConformanceReport struct {
 	// SucceededProvisionalTests is a list of the names of the provisional tests that
 	// have been successfully run.
 	SucceededProvisionalTests []string `json:"succeededProvisionalTests,omitempty"`
+
+	// InferredSupportedFeatures indicates whether the supported features were
+	// automatically detected by the conformance suite.
+	InferredSupportedFeatures bool `json:"inferredSupportedFeatures"`
 }
 
 // Implementation provides metadata information on the downstream
diff --git a/conformance/conformance.go b/conformance/conformance.go
index ce264a94..12e58849 100644
--- a/conformance/conformance.go
+++ b/conformance/conformance.go
@@ -29,11 +29,14 @@ import (
 	"sigs.k8s.io/gateway-api/conformance/tests"
 	conformanceconfig "sigs.k8s.io/gateway-api/conformance/utils/config"
 	"sigs.k8s.io/gateway-api/conformance/utils/flags"
+
 	"sigs.k8s.io/gateway-api/conformance/utils/suite"
 
 	"github.com/stretchr/testify/require"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+
 	clientset "k8s.io/client-go/kubernetes"
+
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/config"
 	"sigs.k8s.io/yaml"
@@ -63,6 +66,7 @@ func DefaultOptions(t *testing.T) suite.ConformanceOptions {
 
 	supportedFeatures := suite.ParseSupportedFeatures(*flags.SupportedFeatures)
 	exemptFeatures := suite.ParseSupportedFeatures(*flags.ExemptFeatures)
+
 	skipTests := suite.ParseSkipTests(*flags.SkipTests)
 	namespaceLabels := suite.ParseKeyValuePairs(*flags.NamespaceLabels)
 	namespaceAnnotations := suite.ParseKeyValuePairs(*flags.NamespaceAnnotations)
@@ -144,6 +148,7 @@ func logOptions(t *testing.T, opts suite.ConformanceOptions) {
 	t.Logf("  Enable All Features: %t", opts.EnableAllSupportedFeatures)
 	t.Logf("  Supported Features: %v", opts.SupportedFeatures.UnsortedList())
 	t.Logf("  ExemptFeatures: %v", opts.ExemptFeatures.UnsortedList())
+	t.Logf("  ConformanceProfiles: %v", opts.ConformanceProfiles.UnsortedList())
 }
 
 func writeReport(logf func(string, ...any), report confv1.ConformanceReport, output string) error {
diff --git a/conformance/reports/v1.3.0/buoyant-enterprise-for-linkerd/README.md b/conformance/reports/v1.3.0/buoyant-enterprise-for-linkerd/README.md
new file mode 100644
index 00000000..e598c55f
--- /dev/null
+++ b/conformance/reports/v1.3.0/buoyant-enterprise-for-linkerd/README.md
@@ -0,0 +1,32 @@
+# Buoyant Enterprise for Linkerd
+
+## Table of Contents
+
+| API channel  | Implementation version                    | Mode    | Report                                                 |
+|--------------|-------------------------------------------|---------|--------------------------------------------------------|
+| standard     | [enterprise-2.18](https://docs.buoyant.io/buoyant-enterprise-linkerd/latest/overview//) | default | [enterprise-2.18 report](./standard-2.18-default-report.yaml) |
+
+## Notes
+
+This report uses the v1.3.0 Gateway API CRDs, but was run using the tests on
+the `main` branch at commit `6cd1558a9e`, in order to take advantage more
+effective tests for the `MESH` conformance profile that landed after v1.3.0
+was cut.
+
+## Reproduce
+
+To reproduce a Buoyant Enterprise for Linkerd conformance test report:
+
+0. `cd` to the top level of this repository.
+
+1. Create an empty cluster.
+
+2. Run `bash conformance/reports/v1.3.0/buoyant-enterprise-for-linkerd/run-conformance.sh`.
+
+   You can set `LINKERD_VERSION`, `GATEWAY_API_CHANNEL`, and
+   `GATEWAY_API_VERSION` if you want to try different versions of things.
+   (Note that if you set `GATEWAY_API_VERSION`, you'll need to be on a
+   matching Gateway API branch.)
+
+3. The conformance report will be written to the
+   `conformance/reports/v1.3.0/buoyant-enterprise-for-linkerd/` directory.
diff --git a/conformance/reports/v1.3.0/buoyant-enterprise-for-linkerd/run-conformance.sh b/conformance/reports/v1.3.0/buoyant-enterprise-for-linkerd/run-conformance.sh
new file mode 100644
index 00000000..fc9efcd4
--- /dev/null
+++ b/conformance/reports/v1.3.0/buoyant-enterprise-for-linkerd/run-conformance.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+# Copyright 2025 The Kubernetes Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Set these as needed.
+LINKERD_VERSION=${LINKERD_VERSION:-enterprise-2.18}
+GATEWAY_API_CHANNEL=${GATEWAY_API_CHANNEL:-standard}
+GATEWAY_API_VERSION=${GATEWAY_API_VERSION:-v1.3.0}
+
+UNSUPPORTED_FEATURES="MeshHTTPRouteRedirectPath,MeshHTTPRouteRewritePath"
+
+CONFORMANCE_PRODUCT=buoyant-enterprise-for-linkerd
+CONFORMANCE_VERSION=$(echo $LINKERD_VERSION | cut -d- -f2-)
+GATEWAY_API_BASE_URL=https://github.com/kubernetes-sigs/gateway-api/releases/download
+
+echo "Using Buoyant Enterprise for Linkerd version $LINKERD_VERSION"
+echo "Using Gateway API $GATEWAY_API_VERSION $GATEWAY_API_CHANNEL"
+
+# Install the Linkerd CLI.
+curl --proto '=https' --tlsv1.2 -sSfL https://enterprise.buoyant.io/install \
+  | env LINKERD2_VERSION=${LINKERD_EDGE_VERSION} sh
+
+export PATH=$HOME/.linkerd2/bin:$PATH
+
+# Install the Gateway API CRDs.
+
+kubectl apply -f ${GATEWAY_API_BASE_URL}/${GATEWAY_API_VERSION}/${GATEWAY_API_CHANNEL}-install.yaml
+
+# Install the Linkerd control plane.
+linkerd install --crds | kubectl apply -f -
+linkerd install | kubectl apply -f -
+linkerd check
+
+# Run the conformance tests.
+
+REPORT_NAME=${GATEWAY_API_CHANNEL}-${CONFORMANCE_VERSION}-default-report.yaml
+REPORT_PATH=reports/${GATEWAY_API_VERSION}/${CONFORMANCE_PRODUCT}/${REPORT_NAME}
+
+go test \
+    -p 4 \
+    ./conformance \
+    -run TestConformance \
+    -args \
+        --organization Buoyant \
+        --project "Buoyant Enterprise for Linkerd" \
+        --url https://buoyant.io/ \
+        --version ${LINKERD_VERSION} \
+        --contact "gateway-api@buoyant.io" \
+        --report-output ${REPORT_PATH} \
+        --conformance-profiles=MESH-HTTP,MESH-GRPC \
+        --all-features \
+        --exempt-features=Gateway,ReferenceGrant,${UNSUPPORTED_FEATURES} \
+        --namespace-annotations=linkerd.io/inject=enabled
diff --git a/conformance/reports/v1.3.0/buoyant-enterprise-for-linkerd/standard-2.18-default-report.yaml b/conformance/reports/v1.3.0/buoyant-enterprise-for-linkerd/standard-2.18-default-report.yaml
new file mode 100644
index 00000000..90e4e77a
--- /dev/null
+++ b/conformance/reports/v1.3.0/buoyant-enterprise-for-linkerd/standard-2.18-default-report.yaml
@@ -0,0 +1,64 @@
+apiVersion: gateway.networking.k8s.io/v1
+date: "2025-06-11T14:31:02-04:00"
+gatewayAPIChannel: standard
+gatewayAPIVersion: v1.3.0
+implementation:
+  contact:
+  - gateway-api@buoyant.io
+  organization: Buoyant
+  project: Buoyant Enterprise for Linkerd
+  url: https://buoyant.io/
+  version: enterprise-2.18
+kind: ConformanceReport
+mode: default
+profiles:
+- core:
+    result: success
+    statistics:
+      Failed: 0
+      Passed: 1
+      Skipped: 0
+  name: MESH-GRPC
+  summary: Core tests succeeded.
+- core:
+    result: success
+    statistics:
+      Failed: 0
+      Passed: 7
+      Skipped: 0
+  extended:
+    result: success
+    statistics:
+      Failed: 0
+      Passed: 8
+      Skipped: 0
+    supportedFeatures:
+    - HTTPRouteBackendProtocolH2C
+    - HTTPRouteBackendProtocolWebSocket
+    - HTTPRouteBackendRequestHeaderModification
+    - HTTPRouteBackendTimeout
+    - HTTPRouteDestinationPortMatching
+    - HTTPRouteHostRewrite
+    - HTTPRouteMethodMatching
+    - HTTPRouteParentRefPort
+    - HTTPRoutePathRedirect
+    - HTTPRoutePathRewrite
+    - HTTPRoutePortRedirect
+    - HTTPRouteQueryParamMatching
+    - HTTPRouteRequestMirror
+    - HTTPRouteRequestMultipleMirrors
+    - HTTPRouteRequestPercentageMirror
+    - HTTPRouteRequestTimeout
+    - HTTPRouteResponseHeaderModification
+    - HTTPRouteSchemeRedirect
+    - MeshClusterIPMatching
+    - MeshConsumerRoute
+    - MeshHTTPRouteBackendRequestHeaderModification
+    - MeshHTTPRouteQueryParamMatching
+    - MeshHTTPRouteRedirectPort
+    - MeshHTTPRouteSchemeRedirect
+    unsupportedFeatures:
+    - MeshHTTPRouteRedirectPath
+    - MeshHTTPRouteRewritePath
+  name: MESH-HTTP
+  summary: Core tests succeeded. Extended tests succeeded.
diff --git a/conformance/reports/v1.3.0/linkerd-linkerd/README.md b/conformance/reports/v1.3.0/linkerd-linkerd/README.md
new file mode 100644
index 00000000..19a73bc7
--- /dev/null
+++ b/conformance/reports/v1.3.0/linkerd-linkerd/README.md
@@ -0,0 +1,65 @@
+# Linkerd
+
+## Table of Contents
+
+| API channel  | Implementation version                    | Mode    | Report                                                 |
+|--------------|-------------------------------------------|---------|--------------------------------------------------------|
+| standard     | [version-2.18](https://github.com/linkerd/linkerd2/releases/tag/version-2.18/) | default | [version-2.18 report](./standard-2.18-default-report.yaml) |
+
+## Notes
+
+This report uses the v1.3.0 Gateway API CRDs, but was run using the tests on
+the `main` branch at commit `6cd1558a9e`, in order to take advantage of more
+effective tests for the `MESH` conformance profile that landed after v1.3.0
+was cut.
+
+### Linkerd Versioning
+
+The Linkerd project publishes and announces _versions_ that correspond to
+specific project milestones and sets of new features. This report is for
+Linkerd 2.18.
+
+Linkerd versions are available in different types of _release artifacts_:
+
+- _Edge releases_ are published on a weekly or near-weekly basis by the
+  Linkerd open-source project. Their names are `edge-y.m.n`, where `y` is the
+  two-digit year, `m` is the numeric month, and `n` is the number of the edge
+  release in that month (e.g. `edge-25.5.1` is the first edge release in May
+  of 2025).
+
+  Each major version of Linkerd has a corresponding edge release, indicated by
+  a `version-2.X` tag. For example, Linkerd 2.18 corresponds to `edge-25.4.4`,
+  and therefore the `version-2.18` tag and the `edge-25.4.4` tag are on the
+  same commit.
+
+- _Stable releases_ of Linkerd follow semantic versioning, and are published
+  by the vendor community around Linkerd.
+
+For more information on Linkerd versioning, see the Linkerd [Releases and
+Versions] documentation.
+
+Since Gateway API conformance tests require semantic versioning for the
+implementation version, the Linkerd project reports conformance using the
+`version` tags. However, the `run_conformance.sh` script referenced below
+installs the corresponding `edge` tag, because the Linkerd CLI is actually
+published using the `edge` tag.
+
+[Releases and Versions]: https://linkerd.io/releases/
+
+## Reproduce
+
+To reproduce a Linkerd conformance test report:
+
+0. `cd` to the top level of this repository.
+
+1. Create an empty cluster.
+
+2. Run `bash conformance/reports/v1.3.0/linkerd-linkerd/run-conformance.sh`.
+
+   You can set `LINKERD_VERSION`, `LINKERD_EDGE_VERSION`,
+   `GATEWAY_API_CHANNEL`, and `GATEWAY_API_VERSION` if you want to try
+   different versions of things. (Note that if you set `GATEWAY_API_VERSION`,
+   you'll need to be on a matching Gateway API branch.)
+
+3. The conformance report will be written to the
+   `conformance/reports/v1.3.0/linkerd-linkerd/` directory.
diff --git a/conformance/reports/v1.3.0/linkerd-linkerd/run-conformance.sh b/conformance/reports/v1.3.0/linkerd-linkerd/run-conformance.sh
new file mode 100644
index 00000000..93d0b210
--- /dev/null
+++ b/conformance/reports/v1.3.0/linkerd-linkerd/run-conformance.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+# Copyright 2025 The Kubernetes Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Set these as needed.
+LINKERD_VERSION=${LINKERD_VERSION:-version-2.18}
+LINKERD_EDGE_VERSION=${LINKERD_EDGE_VERSION:-edge-25.4.4}
+GATEWAY_API_CHANNEL=${GATEWAY_API_CHANNEL:-standard}
+GATEWAY_API_VERSION=${GATEWAY_API_VERSION:-v1.3.0}
+
+UNSUPPORTED_FEATURES="MeshHTTPRouteRedirectPath,MeshHTTPRouteRewritePath"
+
+CONFORMANCE_PRODUCT=linkerd-linkerd
+CONFORMANCE_VERSION=$(echo $LINKERD_VERSION | cut -d- -f2-)
+GATEWAY_API_BASE_URL=https://github.com/kubernetes-sigs/gateway-api/releases/download
+
+echo "Using Linkerd version $LINKERD_VERSION (AKA $LINKERD_EDGE_VERSION)"
+echo "Using Gateway API $GATEWAY_API_VERSION $GATEWAY_API_CHANNEL"
+
+# Install the Linkerd CLI.
+curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install-edge \
+  | env LINKERD2_VERSION=${LINKERD_EDGE_VERSION} sh
+
+export PATH=$HOME/.linkerd2/bin:$PATH
+
+# Install the Gateway API CRDs.
+
+kubectl apply -f ${GATEWAY_API_BASE_URL}/${GATEWAY_API_VERSION}/${GATEWAY_API_CHANNEL}-install.yaml
+
+# Install the Linkerd control plane.
+linkerd install --crds | kubectl apply -f -
+linkerd install | kubectl apply -f -
+linkerd check
+
+# Run the conformance tests.
+
+REPORT_NAME=${GATEWAY_API_CHANNEL}-${CONFORMANCE_VERSION}-default-report.yaml
+REPORT_PATH=reports/${GATEWAY_API_VERSION}/${CONFORMANCE_PRODUCT}/${REPORT_NAME}
+
+go test \
+    -p 4 \
+    ./conformance \
+    -run TestConformance \
+    -args \
+        --organization Linkerd \
+        --project Linkerd \
+        --url https://github.com/linkerd/linkerd2 \
+        --version ${LINKERD_VERSION} \
+        --contact https://github.com/linkerd/linkerd2/blob/main/MAINTAINERS.md \
+        --report-output ${REPORT_PATH} \
+        --conformance-profiles=MESH-HTTP,MESH-GRPC \
+        --all-features \
+        --exempt-features=Gateway,ReferenceGrant,${UNSUPPORTED_FEATURES} \
+        --namespace-annotations=linkerd.io/inject=enabled
diff --git a/conformance/reports/v1.3.0/linkerd-linkerd/standard-2.18-default-report.yaml b/conformance/reports/v1.3.0/linkerd-linkerd/standard-2.18-default-report.yaml
new file mode 100644
index 00000000..54d05030
--- /dev/null
+++ b/conformance/reports/v1.3.0/linkerd-linkerd/standard-2.18-default-report.yaml
@@ -0,0 +1,64 @@
+apiVersion: gateway.networking.k8s.io/v1
+date: "2025-06-11T14:21:05-04:00"
+gatewayAPIChannel: standard
+gatewayAPIVersion: v1.3.0
+implementation:
+  contact:
+  - https://github.com/linkerd/linkerd2/blob/main/MAINTAINERS.md
+  organization: Linkerd
+  project: Linkerd
+  url: https://github.com/linkerd/linkerd2
+  version: version-2.18
+kind: ConformanceReport
+mode: default
+profiles:
+- core:
+    result: success
+    statistics:
+      Failed: 0
+      Passed: 1
+      Skipped: 0
+  name: MESH-GRPC
+  summary: Core tests succeeded.
+- core:
+    result: success
+    statistics:
+      Failed: 0
+      Passed: 7
+      Skipped: 0
+  extended:
+    result: success
+    statistics:
+      Failed: 0
+      Passed: 8
+      Skipped: 0
+    supportedFeatures:
+    - HTTPRouteBackendProtocolH2C
+    - HTTPRouteBackendProtocolWebSocket
+    - HTTPRouteBackendRequestHeaderModification
+    - HTTPRouteBackendTimeout
+    - HTTPRouteDestinationPortMatching
+    - HTTPRouteHostRewrite
+    - HTTPRouteMethodMatching
+    - HTTPRouteParentRefPort
+    - HTTPRoutePathRedirect
+    - HTTPRoutePathRewrite
+    - HTTPRoutePortRedirect
+    - HTTPRouteQueryParamMatching
+    - HTTPRouteRequestMirror
+    - HTTPRouteRequestMultipleMirrors
+    - HTTPRouteRequestPercentageMirror
+    - HTTPRouteRequestTimeout
+    - HTTPRouteResponseHeaderModification
+    - HTTPRouteSchemeRedirect
+    - MeshClusterIPMatching
+    - MeshConsumerRoute
+    - MeshHTTPRouteBackendRequestHeaderModification
+    - MeshHTTPRouteQueryParamMatching
+    - MeshHTTPRouteRedirectPort
+    - MeshHTTPRouteSchemeRedirect
+    unsupportedFeatures:
+    - MeshHTTPRouteRedirectPath
+    - MeshHTTPRouteRewritePath
+  name: MESH-HTTP
+  summary: Core tests succeeded. Extended tests succeeded.
diff --git a/conformance/utils/http/http.go b/conformance/utils/http/http.go
index 78667903..18b5e1fe 100644
--- a/conformance/utils/http/http.go
+++ b/conformance/utils/http/http.go
@@ -70,6 +70,7 @@ type Request struct {
 	Headers          map[string]string
 	UnfollowRedirect bool
 	Protocol         string
+	Body             string
 }
 
 // ExpectedRequest defines expected properties of a request that reaches a backend.
@@ -139,6 +140,7 @@ func MakeRequest(t *testing.T, expected *ExpectedResponse, gwAddr, protocol, sch
 		Protocol:         expected.Request.Protocol,
 		Headers:          map[string][]string{},
 		UnfollowRedirect: expected.Request.UnfollowRedirect,
+		Body:             expected.Request.Body,
 	}
 
 	if expected.Request.Headers != nil {
diff --git a/conformance/utils/roundtripper/roundtripper.go b/conformance/utils/roundtripper/roundtripper.go
index c37f4500..f7950878 100644
--- a/conformance/utils/roundtripper/roundtripper.go
+++ b/conformance/utils/roundtripper/roundtripper.go
@@ -29,6 +29,7 @@ import (
 	"net/http/httputil"
 	"net/url"
 	"regexp"
+	"strings"
 	"testing"
 
 	"golang.org/x/net/http2"
@@ -59,6 +60,7 @@ type Request struct {
 	CertPem          []byte
 	KeyPem           []byte
 	Server           string
+	Body             string
 }
 
 // String returns a printable version of Request for logging. Note that the
@@ -194,7 +196,12 @@ func (d *DefaultRoundTripper) defaultRoundTrip(request Request, transport http.R
 	ctx, cancel := context.WithTimeout(context.Background(), d.TimeoutConfig.RequestTimeout)
 	defer cancel()
 	ctx = withT(ctx, request.T)
-	req, err := http.NewRequestWithContext(ctx, method, request.URL.String(), nil)
+
+	var reqBody io.Reader
+	if request.Body != "" {
+		reqBody = strings.NewReader(request.Body)
+	}
+	req, err := http.NewRequestWithContext(ctx, method, request.URL.String(), reqBody)
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/conformance/utils/suite/conformance.go b/conformance/utils/suite/conformance.go
index 3bbbbc1c..12db14d6 100644
--- a/conformance/utils/suite/conformance.go
+++ b/conformance/utils/suite/conformance.go
@@ -21,8 +21,6 @@ import (
 	"strings"
 	"testing"
 
-	"k8s.io/apimachinery/pkg/util/sets"
-
 	"sigs.k8s.io/gateway-api/conformance/utils/tlog"
 	"sigs.k8s.io/gateway-api/pkg/features"
 )
@@ -81,11 +79,11 @@ func (test *ConformanceTest) Run(t *testing.T, suite *ConformanceTestSuite) {
 
 // ParseSupportedFeatures parses flag arguments and converts the string to
 // sets.Set[features.FeatureName]
-func ParseSupportedFeatures(f string) sets.Set[features.FeatureName] {
+func ParseSupportedFeatures(f string) FeaturesSet {
 	if f == "" {
 		return nil
 	}
-	res := sets.Set[features.FeatureName]{}
+	res := FeaturesSet{}
 	for _, value := range strings.Split(f, ",") {
 		res.Insert(features.FeatureName(value))
 	}
diff --git a/conformance/utils/suite/reports.go b/conformance/utils/suite/reports.go
index 4b03d677..4efcc4e7 100644
--- a/conformance/utils/suite/reports.go
+++ b/conformance/utils/suite/reports.go
@@ -22,7 +22,6 @@ import (
 	"k8s.io/apimachinery/pkg/util/sets"
 
 	confv1 "sigs.k8s.io/gateway-api/conformance/apis/v1"
-	"sigs.k8s.io/gateway-api/pkg/features"
 )
 
 // -----------------------------------------------------------------------------
@@ -107,7 +106,7 @@ func (p profileReportsMap) list() (profileReports []confv1.ProfileReport) {
 	return
 }
 
-func (p profileReportsMap) compileResults(supportedFeaturesMap map[ConformanceProfileName]sets.Set[features.FeatureName], unsupportedFeaturesMap map[ConformanceProfileName]sets.Set[features.FeatureName]) {
+func (p profileReportsMap) compileResults(supportedFeaturesMap map[ConformanceProfileName]FeaturesSet, unsupportedFeaturesMap map[ConformanceProfileName]FeaturesSet) {
 	for key, report := range p {
 		// report the overall result for core features
 		switch {
@@ -162,7 +161,8 @@ func (p profileReportsMap) compileResults(supportedFeaturesMap map[ConformancePr
 // isTestExtended determines if a provided test is considered to be supported
 // at an extended level of support given the provided conformance profile.
 //
-// TODO: right now the tests themselves don't indicate the conformance
+// TODO(#3759) Update this method to be based on Features inferred.
+// Right now the tests themselves don't indicate the conformance
 // support level associated with them. The only way we have right now
 // in this prototype to know whether a test belongs to any particular
 // conformance level is to compare the features needed for the test to
diff --git a/conformance/utils/suite/suite.go b/conformance/utils/suite/suite.go
index e6a5cb12..f1164fa2 100644
--- a/conformance/utils/suite/suite.go
+++ b/conformance/utils/suite/suite.go
@@ -31,11 +31,13 @@ import (
 	"github.com/stretchr/testify/require"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/sets"
 	clientset "k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
+	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
 	"sigs.k8s.io/gateway-api/apis/v1beta1"
 	confv1 "sigs.k8s.io/gateway-api/conformance/apis/v1"
 	"sigs.k8s.io/gateway-api/conformance/utils/config"
@@ -69,7 +71,7 @@ type ConformanceTestSuite struct {
 	BaseManifests            string
 	MeshManifests            string
 	Applier                  kubernetes.Applier
-	SupportedFeatures        sets.Set[features.FeatureName]
+	SupportedFeatures        FeaturesSet
 	TimeoutConfig            config.TimeoutConfig
 	SkipTests                sets.Set[string]
 	SkipProvisionalTests     bool
@@ -79,6 +81,11 @@ type ConformanceTestSuite struct {
 	UsableNetworkAddresses   []v1beta1.GatewaySpecAddress
 	UnusableNetworkAddresses []v1beta1.GatewaySpecAddress
 
+	// If SupportedFeatures are automatically determined from GWC Status.
+	// This will be required to report in future iterations as the passing
+	// will be determined based on this.
+	isInferredSupportedFeatures bool
+
 	// mode is the operating mode of the implementation.
 	// The default value for it is "default".
 	mode string
@@ -142,8 +149,8 @@ type ConformanceOptions struct {
 	// CleanupBaseResources indicates whether or not the base test
 	// resources such as Gateways should be cleaned up after the run.
 	CleanupBaseResources       bool
-	SupportedFeatures          sets.Set[features.FeatureName]
-	ExemptFeatures             sets.Set[features.FeatureName]
+	SupportedFeatures          FeaturesSet
+	ExemptFeatures             FeaturesSet
 	EnableAllSupportedFeatures bool
 	TimeoutConfig              config.TimeoutConfig
 	// SkipTests contains all the tests not to be run and can be used to opt out
@@ -172,6 +179,8 @@ type ConformanceOptions struct {
 	ConformanceProfiles sets.Set[ConformanceProfileName]
 }
 
+type FeaturesSet = sets.Set[features.FeatureName]
+
 const (
 	// undefinedKeyword is set in the ConformanceReport "GatewayAPIVersion" and
 	// "GatewayAPIChannel" fields in case it's not possible to figure out the actual
@@ -181,16 +190,23 @@ const (
 
 // NewConformanceTestSuite is a helper to use for creating a new ConformanceTestSuite.
 func NewConformanceTestSuite(options ConformanceOptions) (*ConformanceTestSuite, error) {
-	// test suite callers are required to provide either:
-	// - one conformance profile via the flag '-conformance-profiles'
-	// - a list of supported features via the flag '-supported-features'
-	// - an explicit test to run via the flag '-run-test'
-	// - all features are being tested via the flag '-all-features'
-	if options.SupportedFeatures.Len() == 0 &&
-		options.ConformanceProfiles.Len() == 0 &&
-		!options.EnableAllSupportedFeatures &&
-		options.RunTest == "" {
-		return nil, fmt.Errorf("no conformance profile, supported features, explicit tests were provided so no tests could be selected")
+	supportedFeatures := options.SupportedFeatures.Difference(options.ExemptFeatures)
+	isInferred := false
+	switch {
+	case options.EnableAllSupportedFeatures:
+		supportedFeatures = features.SetsToNamesSet(features.AllFeatures)
+	case shouldInferSupportedFeatures(&options):
+		var err error
+		supportedFeatures, err = fetchSupportedFeatures(options.Client, options.GatewayClassName)
+		if err != nil {
+			return nil, fmt.Errorf("Cannot infer supported features: %w", err)
+		}
+		isInferred = true
+	}
+
+	// If features were not inferred from Status, it's a GWC issue.
+	if isInferred && supportedFeatures.Len() == 0 {
+		return nil, fmt.Errorf("no supported features were determined for test suite")
 	}
 
 	config.SetupTimeoutConfig(&options.TimeoutConfig)
@@ -224,19 +240,6 @@ func NewConformanceTestSuite(options ConformanceOptions) (*ConformanceTestSuite,
 		mode = options.Mode
 	}
 
-	// test suite callers can potentially just run all tests by saying they
-	// cover all features, if they don't they'll need to have provided a
-	// conformance profile or at least some specific features they support.
-	if options.EnableAllSupportedFeatures {
-		options.SupportedFeatures = features.SetsToNamesSet(features.AllFeatures)
-	} else if options.SupportedFeatures == nil {
-		options.SupportedFeatures = sets.New[features.FeatureName]()
-	}
-
-	for feature := range options.ExemptFeatures {
-		options.SupportedFeatures.Delete(feature)
-	}
-
 	suite := &ConformanceTestSuite{
 		Client:           options.Client,
 		ClientOptions:    options.ClientOptions,
@@ -254,7 +257,7 @@ func NewConformanceTestSuite(options ConformanceOptions) (*ConformanceTestSuite,
 			NamespaceAnnotations: options.NamespaceAnnotations,
 			AddressType:          options.AddressType,
 		},
-		SupportedFeatures:           options.SupportedFeatures,
+		SupportedFeatures:           supportedFeatures,
 		TimeoutConfig:               options.TimeoutConfig,
 		SkipTests:                   sets.New(options.SkipTests...),
 		RunTest:                     options.RunTest,
@@ -270,6 +273,7 @@ func NewConformanceTestSuite(options ConformanceOptions) (*ConformanceTestSuite,
 		mode:                        mode,
 		apiVersion:                  apiVersion,
 		apiChannel:                  apiChannel,
+		isInferredSupportedFeatures: isInferred,
 		Hook:                        options.Hook,
 	}
 
@@ -288,12 +292,12 @@ func NewConformanceTestSuite(options ConformanceOptions) (*ConformanceTestSuite,
 		for _, f := range conformanceProfile.ExtendedFeatures.UnsortedList() {
 			if options.SupportedFeatures.Has(f) {
 				if suite.extendedSupportedFeatures[conformanceProfileName] == nil {
-					suite.extendedSupportedFeatures[conformanceProfileName] = sets.New[features.FeatureName]()
+					suite.extendedSupportedFeatures[conformanceProfileName] = FeaturesSet{}
 				}
 				suite.extendedSupportedFeatures[conformanceProfileName].Insert(f)
 			} else {
 				if suite.extendedUnsupportedFeatures[conformanceProfileName] == nil {
-					suite.extendedUnsupportedFeatures[conformanceProfileName] = sets.New[features.FeatureName]()
+					suite.extendedUnsupportedFeatures[conformanceProfileName] = FeaturesSet{}
 				}
 				suite.extendedUnsupportedFeatures[conformanceProfileName].Insert(f)
 			}
@@ -390,6 +394,10 @@ func (suite *ConformanceTestSuite) Setup(t *testing.T, tests []ConformanceTest)
 	}
 }
 
+func (suite *ConformanceTestSuite) IsInferredSupportedFeatures() bool {
+	return suite.isInferredSupportedFeatures
+}
+
 func (suite *ConformanceTestSuite) setClientsetForTest(test ConformanceTest) error {
 	featureNames := []string{}
 	for _, v := range test.Features {
@@ -544,6 +552,7 @@ func (suite *ConformanceTestSuite) Report() (*confv1.ConformanceReport, error) {
 		GatewayAPIChannel:         suite.apiChannel,
 		ProfileReports:            profileReports.list(),
 		SucceededProvisionalTests: succeededProvisionalTests,
+		InferredSupportedFeatures: suite.IsInferredSupportedFeatures(),
 	}, nil
 }
 
@@ -573,6 +582,39 @@ func ParseConformanceProfiles(p string) sets.Set[ConformanceProfileName] {
 	return res
 }
 
+func fetchSupportedFeatures(client client.Client, gatewayClassName string) (FeaturesSet, error) {
+	if gatewayClassName == "" {
+		return nil, fmt.Errorf("GatewayClass name must be provided to fetch supported features")
+	}
+	gwc := &gatewayv1.GatewayClass{}
+	err := client.Get(context.TODO(), types.NamespacedName{Name: gatewayClassName}, gwc)
+	if err != nil {
+		return nil, fmt.Errorf("fetchSupportedFeatures(): %w", err)
+	}
+
+	fs := FeaturesSet{}
+	for _, feature := range gwc.Status.SupportedFeatures {
+		fs.Insert(features.FeatureName(feature.Name))
+	}
+	fmt.Printf("Supported features for GatewayClass %s: %v\n", gatewayClassName, fs.UnsortedList())
+	return fs, nil
+}
+
+// shouldInferSupportedFeatures checks if any flags were supplied for manually
+// picking what to test. Inferred supported features are only used when no flags
+// are set.
+func shouldInferSupportedFeatures(opts *ConformanceOptions) bool {
+	if opts == nil {
+		return false
+	}
+	return !opts.EnableAllSupportedFeatures &&
+		opts.SupportedFeatures.Len() == 0 &&
+		opts.ExemptFeatures.Len() == 0 &&
+		opts.ConformanceProfiles.Len() == 0 &&
+		len(opts.SkipTests) == 0 &&
+		opts.RunTest == ""
+}
+
 // getAPIVersionAndChannel iterates over all the crds installed in the cluster and check the version and channel annotations.
 // In case the annotations are not found or there are crds with different versions or channels, an error is returned.
 func getAPIVersionAndChannel(crds []apiextensionsv1.CustomResourceDefinition) (version string, channel string, err error) {
diff --git a/geps/gep-1713/index.md b/geps/gep-1713/index.md
index f51f044d..4cad39fd 100644
--- a/geps/gep-1713/index.md
+++ b/geps/gep-1713/index.md
@@ -36,8 +36,8 @@ Thus updating a single `Gateway` resource with this many certificates is a conte
 
 More broadly, large scale gateway users often expose `O(1000)` domains, but are currently limited by the maximum of 64 `listeners`.
 
-The spec currently has language to indicate implementations `MAY` merge `Gateways` resources but does not define any specific requirements for how that should work.
-https://github.com/kubernetes-sigs/gateway-api/blob/541e9fc2b3c2f62915cb58dc0ee5e43e4096b3e2/apis/v1beta1/gateway_types.go#L76-L78
+The [spec currently has language](https://github.com/kubernetes-sigs/gateway-api/blob/541e9fc2b3c2f62915cb58dc0ee5e43e4096b3e2/apis/v1beta1/gateway_types.go#L76-L78) to indicate implementations `MAY` merge `Gateways` resources but does not define any specific requirements for how that should work.
+
 
 ## Feature Details
 
@@ -164,15 +164,8 @@ type ListenerEntry struct {
 	// Port is the network port. Multiple listeners may use the
 	// same port, subject to the Listener compatibility rules.
 	//
-	// If the port is specified as zero, the implementation will assign
-	// a unique port. If the implementation does not support dynamic port
-	// assignment, it MUST set `Accepted` condition to `False` with the
-	// `UnsupportedPort` reason.
-	//
 	// Support: Core
-	//
-	// +optional
-	Port *PortNumber `json:"port,omitempty"`
+	Port PortNumber `json:"port,omitempty"`
 
 	// Protocol specifies the network protocol this listener expects to receive.
 	//
@@ -385,8 +378,7 @@ spec:
 ```
 ### ListenerEntry
 
-`ListenerEntry` is currently a copy of the `Listener` struct with some changes
-1. `Port` is now a pointer to allow for dynamic port assignment.
+`ListenerEntry` is currently a copy of the `Listener` struct with some changes noted in the below sections
 
 ## Semantics
 
@@ -399,7 +391,7 @@ When there are no listeners the `Gateway`'s `status.listeners` should be empty o
 
 Implementations, when creating a `Gateway`, may provision underlying infrastructure when there are no listeners present. The status conditions `Accepted` and `Programmed` conditions should reflect state of this provisioning.
 
-### Gateway <> ListenerSet Handshake
+### Gateway & ListenerSet Handshake
 
 By default a `Gateway` MUST NOT allow `ListenerSets` to be attached. Users can enable this behaviour by configuring their `Gateway` to allow `ListenerSet` attachment:
 
@@ -413,7 +405,7 @@ spec:
   - from: Same
 ```
 
-### Route Attaching
+### Route Attachment
 
 Routes MUST be able to specify a `ListenerSet` as a `parentRef`. Routes can use `sectionName`/`port` fields in `ParentReference` to help target a specific listener. If no listener is targeted (`sectionName`/`port` are unset) then the Route attaches to all the listeners in the `ListenerSet`.
 
@@ -461,10 +453,36 @@ spec:
     sectionName: foo
 ```
 
+#### Optional Section Name
+
+If a `sectionName` in a Route's `parentRef` is not set then the Route MUST attach to only the listeners in the referenced parent. As an example given a `Gateway` and it's child `ListenerSets` a route attaching to the `Gateway` with an empty `sectionName` shall only attach to the listeners in the `Gateways` immediate `spec.listeners` list. In other words, the Route will not attach to any listeners in the `ListenerSets`. This is necessary because, for UX reasons, the `name` field does not have to be unique across all Listeners merged into a Gateway (see the section below for details).
+
+### Policy Attachment
+
+Policy attachment is [under discussion] in https://github.com/kubernetes-sigs/gateway-api/discussions/2927
+
+Similar to Routes, `ListenerSet` can inherit policy from a Gateway.
+Policies that attach to a `ListenerSet` apply to all listeners defined in that resource, but do not impact listeners in the parent `Gateway`. This allows `ListenerSets` attached to the same `Gateway` to have different policies.
+If the implementation cannot apply the policy to only specific listeners, it should reject the policy.
+
+### ReferenceGrant Semantics
+
+When a `ReferenceGrant` is applied to a `Gateway` it MUST NOT be inherited by child `ListenerSets`. Thus a `ListenerSet` listener MUST NOT access secrets granted to the `Gateway` listeners.
+
+When a `ReferenceGrant` is applied to a `ListenerSet` it MUST NOT grant permission to the parent `Gateway`'s listeners. Thus a `Gateway` listener MUST NOT access secrets granted to the `ListenerSet` listeners.
+
+A `ListenerSet` must be able to reference a secret/backend in the same namespace as itself without a `ReferenceGrant`.
+
+
 ### Listener Validation
 
-Implementations MUST treat the parent `Gateway`s as having the merged list of all listeners from itself and attached `ListenerSets`. See 'Listener Precedence' for more details on ordering.
-Validation of this list of listeners MUST behave the same as if the list were part of a single `Gateway`.
+Within a single resource such as a `Gateway` or `ListenerSet` the list of listeners MUST have unique names. Implementations MUST allow listeners from a child `ListenerSet` to be merged into a parent `Gateway` when listeners have the same name. Likewise implementations MUST allow sibling `ListenerSets` listeners with matching names to be merged into a parent `Gateway`. This allows for authors of Routes to simply attach to their desired parentRef and listener without having to worry about naming conflicts across resources.
+
+It is up to the implementations how unique names are generated internally. One example would be to hash the `ListenerSet` name+namespace and prepend it to the listener entry `name`.
+
+Implementations MUST treat the parent `Gateway`s as having the merged list of all listeners from itself and attached `ListenerSets` and validation of this list of listeners MUST behave the same as if the list were part of a single `Gateway` with the relaxed listener name constraints.
+
+Ordering will follow the semantics defined in [Listener Precedence](#listener-precedence).
 
 From the earlier example the above resources would be equivalent to a single `Gateway` where the listeners are collapsed into a single list.
 
@@ -514,7 +532,7 @@ Listeners should be merged using the following precedence:
 
 Conflicts are covered in the section 'ListenerConditions within a ListenerSet'
 
-###  Gateway Conditions
+### Gateway Conditions
 
 `Gateway`'s `Accepted` and `Programmed` top-level conditions remain unchanged and reflect the status of the local configuration.
 
@@ -555,14 +573,6 @@ If a listener has a conflict, this should be reported in the `ListenerEntryStatu
 
 Implementations SHOULD be cautious about what information from the parent or siblings are reported to avoid accidentally leaking sensitive information that the child would not otherwise have access to. This can include contents of secrets etc.
 
-### Policy Attachment
-
-Policy attachment is [under discussion] in https://github.com/kubernetes-sigs/gateway-api/discussions/2927
-
-Similar to Routes, `ListenerSet` can inherit policy from a Gateway.
-Policies that attach to a `ListenerSet` apply to all listeners defined in that resource, but do not impact listeners in the parent `Gateway`. This allows `ListenerSets` attached to the same `Gateway` to have different policies.
-If the implementation cannot apply the policy to only specific listeners, it should reject the policy.
-
 ## Alternatives
 
 ### Re-using Gateway Resource
diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go
index f4a7d0d4..96ac1646 100644
--- a/pkg/generated/openapi/zz_generated.openapi.go
+++ b/pkg/generated/openapi/zz_generated.openapi.go
@@ -7882,7 +7882,7 @@ func schema_sigsk8sio_gateway_api_apisx_v1alpha1_ListenerSetSpec(ref common.Refe
 							},
 						},
 						SchemaProps: spec.SchemaProps{
-							Description: "Listeners associated with this ListenerSet. Listeners define logical endpoints that are bound on this referenced parent Gateway's addresses.\n\nListeners in a `Gateway` and their attached `ListenerSets` are concatenated as a list when programming the underlying infrastructure. Each listener name does not need to be unique across the Gateway and ListenerSets. See ListenerEntry.Name for more details.\n\nImplementations MUST treat the parent Gateway as having the merged list of all listeners from itself and attached ListenerSets using the following precedence:\n\n1. \"parent\" Gateway 2. ListenerSet ordered by creation time (oldest first) 3. ListenerSet ordered alphabetically by “{namespace}/{name}”.\n\nAn implementation MAY reject listeners by setting the ListenerEntryStatus `Accepted`` condition to False with the Reason `TooManyListeners`\n\nIf a listener has a conflict, this will be reported in the Status.ListenerEntryStatus setting the `Conflicted` condition to True.\n\nImplementations SHOULD be cautious about what information from the parent or siblings are reported to avoid accidentally leaking sensitive information that the child would not otherwise have access to. This can include contents of secrets etc.",
+							Description: "Listeners associated with this ListenerSet. Listeners define logical endpoints that are bound on this referenced parent Gateway's addresses.\n\nListeners in a `Gateway` and their attached `ListenerSets` are concatenated as a list when programming the underlying infrastructure. Each listener name does not need to be unique across the Gateway and ListenerSets. See ListenerEntry.Name for more details.\n\nImplementations MUST treat the parent Gateway as having the merged list of all listeners from itself and attached ListenerSets using the following precedence:\n\n1. \"parent\" Gateway 2. ListenerSet ordered by creation time (oldest first) 3. ListenerSet ordered alphabetically by \"{namespace}/{name}\".\n\nAn implementation MAY reject listeners by setting the ListenerEntryStatus `Accepted` condition to False with the Reason `TooManyListeners`\n\nIf a listener has a conflict, this will be reported in the Status.ListenerEntryStatus setting the `Conflicted` condition to True.\n\nImplementations SHOULD be cautious about what information from the parent or siblings are reported to avoid accidentally leaking sensitive information that the child would not otherwise have access to. This can include contents of secrets etc.",
 							Type:        []string{"array"},
 							Items: &spec.SchemaOrArray{
 								Schema: &spec.Schema{
@@ -8161,7 +8161,7 @@ func schema_sigsk8sio_gateway_api_apisx_v1alpha1_XListenerSet(ref common.Referen
 	return common.OpenAPIDefinition{
 		Schema: spec.Schema{
 			SchemaProps: spec.SchemaProps{
-				Description: "XListenerSet defines a set of additional listeners to attach to an existing Gateway.",
+				Description: "XListenerSet defines a set of additional listeners to attach to an existing Gateway. This resource provides a mechanism to merge multiple listeners into a single Gateway.\n\nThe parent Gateway must explicitly allow ListenerSet attachment through its AllowedListeners configuration. By default, Gateways do not allow ListenerSet attachment.\n\nRoutes can attach to a ListenerSet by specifying it as a parentRef, and can optionally target specific listeners using the sectionName field.\n\nPolicy Attachment: - Policies that attach to a ListenerSet apply to all listeners defined in that resource - Policies do not impact listeners in the parent Gateway - Different ListenerSets attached to the same Gateway can have different policies - If an implementation cannot apply a policy to specific listeners, it should reject the policy\n\nReferenceGrant Semantics: - ReferenceGrants applied to a Gateway are not inherited by child ListenerSets - ReferenceGrants applied to a ListenerSet do not grant permission to the parent Gateway's listeners - A ListenerSet can reference secrets/backends in its own namespace without a ReferenceGrant\n\nGateway Integration: - The parent Gateway's status will include an \"AttachedListenerSets\" condition - This condition will be:\n  - True: when AllowedListeners is set and at least one child ListenerSet is attached\n  - False: when AllowedListeners is set but no valid listeners are attached, or when AllowedListeners is not set or false\n  - Unknown: when no AllowedListeners config is present",
 				Type:        []string{"object"},
 				Properties: map[string]spec.Schema{
 					"kind": {
diff --git a/site-src/implementations.md b/site-src/implementations.md
index e65f26aa..06b0e792 100644
--- a/site-src/implementations.md
+++ b/site-src/implementations.md
@@ -51,7 +51,7 @@ cover, and documentation to help users get started.
 - [Google Cloud Service Mesh][38] (GA)
 - [Istio][9] (GA)
 - [Kuma][11] (GA)
-- [Linkerd][28] (experimental)
+- [Linkerd][28] (GA)
 
 ## Integrations <a name="integrations"></a>
 



Test patch:
diff --git a/conformance/utils/suite/suite_test.go b/conformance/utils/suite/suite_test.go
index 951571f7..ccb259ad 100644
--- a/conformance/utils/suite/suite_test.go
+++ b/conformance/utils/suite/suite_test.go
@@ -22,9 +22,13 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/util/sets"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
+	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
 	confv1 "sigs.k8s.io/gateway-api/conformance/apis/v1"
 	"sigs.k8s.io/gateway-api/pkg/consts"
 	"sigs.k8s.io/gateway-api/pkg/features"
@@ -217,7 +221,7 @@ var (
 func TestSuiteReport(t *testing.T) {
 	testCases := []struct {
 		name                      string
-		features                  sets.Set[features.FeatureName]
+		features                  FeaturesSet
 		extendedSupportedFeatures map[ConformanceProfileName]sets.Set[features.FeatureName]
 		profiles                  sets.Set[ConformanceProfileName]
 		skipProvisionalTests      bool
@@ -276,6 +280,7 @@ func TestSuiteReport(t *testing.T) {
 					coreProvisionalTest.ShortName,
 					extendedProvisionalTest.ShortName,
 				},
+				InferredSupportedFeatures: true,
 			},
 		},
 		{
@@ -385,6 +390,7 @@ func TestSuiteReport(t *testing.T) {
 						},
 					},
 				},
+				InferredSupportedFeatures: true,
 			},
 		},
 	}
@@ -407,3 +413,128 @@ func TestSuiteReport(t *testing.T) {
 		})
 	}
 }
+
+var statusFeatureNames = []string{
+	"Gateway",
+	"GatewayPort8080",
+	"HTTPRoute",
+	"HTTPRouteHostRewrite",
+	"HTTPRouteMethodMatching",
+	"HTTPRoutePathRewrite",
+	"TTPRouteQueryParamMatching",
+	"HTTPRouteResponseHeaderModification",
+	"ReferenceGrant",
+}
+
+func TestInferSupportedFeatures(t *testing.T) {
+	testCases := []struct {
+		name               string
+		allowAllFeatures   bool
+		supportedFeatures  FeaturesSet
+		exemptFeatures     FeaturesSet
+		ConformanceProfile sets.Set[ConformanceProfileName]
+		expectedFeatures   FeaturesSet
+		expectedIsInferred bool
+	}{
+		{
+			name:               "properly infer supported features",
+			expectedFeatures:   namesToFeatureSet(statusFeatureNames),
+			expectedIsInferred: true,
+		},
+		{
+			name:              "no features",
+			supportedFeatures: sets.New[features.FeatureName]("Gateway"),
+			expectedFeatures:  sets.New[features.FeatureName]("Gateway"),
+		},
+		{
+			name:              "remove exempt features",
+			supportedFeatures: sets.New[features.FeatureName]("Gateway", "HTTPRoute"),
+			exemptFeatures:    sets.New[features.FeatureName]("HTTPRoute"),
+			expectedFeatures:  sets.New[features.FeatureName]("Gateway"),
+		},
+		{
+			name:             "allow all features",
+			allowAllFeatures: true,
+			expectedFeatures: features.SetsToNamesSet(features.AllFeatures),
+		},
+		{
+			name:               "supports conformance profile - core",
+			ConformanceProfile: sets.New(GatewayHTTPConformanceProfileName),
+			expectedFeatures:   namesToFeatureSet([]string{"Gateway", "HTTPRoute", "ReferenceGrant"}),
+		},
+	}
+
+	gwcName := "ochopintre"
+	gwc := &gatewayv1.GatewayClass{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: gwcName,
+		},
+		Spec: gatewayv1.GatewayClassSpec{
+			ControllerName: "example.com/gateway-controller",
+		},
+		Status: gatewayv1.GatewayClassStatus{
+			Conditions: []metav1.Condition{
+				{
+					Type:    string(gatewayv1.GatewayConditionAccepted),
+					Status:  metav1.ConditionTrue,
+					Reason:  "Accepted",
+					Message: "GatewayClass is accepted and ready for use",
+				},
+			},
+			SupportedFeatures: featureNamesToSet(statusFeatureNames),
+		},
+	}
+	scheme := runtime.NewScheme()
+	scheme.AddKnownTypes(gatewayv1.SchemeGroupVersion, &gatewayv1.GatewayClass{})
+	fakeClient := fake.NewClientBuilder().
+		WithScheme(scheme).
+		WithObjects(gwc).
+		WithLists(&apiextensionsv1.CustomResourceDefinitionList{}).
+		Build()
+
+	gatewayv1.Install(fakeClient.Scheme())
+	apiextensionsv1.AddToScheme(fakeClient.Scheme())
+
+	for _, tc := range testCases {
+		options := ConformanceOptions{
+			AllowCRDsMismatch:          true,
+			GatewayClassName:           gwcName,
+			EnableAllSupportedFeatures: tc.allowAllFeatures,
+			SupportedFeatures:          tc.supportedFeatures,
+			ExemptFeatures:             tc.exemptFeatures,
+			ConformanceProfiles:        tc.ConformanceProfile,
+			Client:                     fakeClient,
+		}
+
+		t.Run(tc.name, func(t *testing.T) {
+			cSuite, err := NewConformanceTestSuite(options)
+			if err != nil {
+				t.Fatalf("error initializing conformance suite: %v", err)
+			}
+
+			if cSuite.IsInferredSupportedFeatures() != tc.expectedIsInferred {
+				t.Errorf("InferredSupportedFeatures mismatch: got %v, want %v", cSuite.IsInferredSupportedFeatures(), tc.expectedIsInferred)
+			}
+
+			if equal := cSuite.SupportedFeatures.Equal(tc.expectedFeatures); !equal {
+				t.Errorf("SupportedFeatures mismatch: got %v, want %v", cSuite.SupportedFeatures.UnsortedList(), tc.expectedFeatures.UnsortedList())
+			}
+		})
+	}
+}
+
+func featureNamesToSet(set []string) []gatewayv1.SupportedFeature {
+	var features []gatewayv1.SupportedFeature
+	for _, feature := range set {
+		features = append(features, gatewayv1.SupportedFeature{Name: gatewayv1.FeatureName(feature)})
+	}
+	return features
+}
+
+func namesToFeatureSet(names []string) FeaturesSet {
+	featureSet := FeaturesSet{}
+	for _, name := range names {
+		featureSet.Insert(features.FeatureName(name))
+	}
+	return featureSet
+}