This is an automated email from the ASF dual-hosted git repository.

kaxil pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 71bdce5fa51 Run lang-SDK serialization conformance checks in CI 
instead of prek (#75872)
71bdce5fa51 is described below

commit 71bdce5fa513b35071b9a01ffe94b81611ae4644
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Wed Oct 28 22:10:43 2026 +0100

    Run lang-SDK serialization conformance checks in CI instead of prek (#75872)
    
    These checks build and run each SDK's serializer, so as prek hooks they 
needed a full
    Go, Java or Node toolchain and took minutes on every commit touching 
Airflow's
    serializer. They are tests, not static checks, so they now run as a CI test 
job that
    sets up only the toolchain of the SDK it checks, for the SDKs whose inputs 
changed.
---
 .github/workflows/ci-amd.yml                       | 58 ++++++++++++++
 .github/workflows/ci-arm.yml                       | 58 ++++++++++++++
 .pre-commit-config.yaml                            | 56 -------------
 dev/breeze/doc/ci/04_selective_checks.md           | 28 ++++---
 .../src/airflow_breeze/utils/selective_checks.py   | 52 ++++++++++---
 dev/breeze/tests/test_selective_checks.py          | 91 +++++++++++++++-------
 go-sdk/DEVELOPMENT.md                              | 10 +--
 go-sdk/airflow/conformance_test.go                 |  4 +-
 java-sdk/sdk/build.gradle.kts                      |  2 +-
 .../check_go_sdk.py}                               |  2 +-
 .../ci/lang_sdk_serialization/check_java_sdk.py    |  4 +-
 .../ci/lang_sdk_serialization/check_ts_sdk.py      |  7 +-
 scripts/ci/lang_sdk_serialization/compare.py       |  4 +-
 .../test_check_go_sdk.py}                          |  4 +-
 ts-sdk/.pre-commit-config.yaml                     | 16 ----
 ts-sdk/tests/conformance/serialize_typescript.ts   |  2 +-
 16 files changed, 254 insertions(+), 144 deletions(-)

diff --git a/.github/workflows/ci-amd.yml b/.github/workflows/ci-amd.yml
index 75c3ff8edc4..98dc1726b8f 100644
--- a/.github/workflows/ci-amd.yml
+++ b/.github/workflows/ci-amd.yml
@@ -303,6 +303,7 @@ jobs:
       kubernetes-versions-list-as-string: >-
         ${{ steps.selective-checks.outputs.kubernetes-versions-list-as-string 
}}
       kustomize-overlay-names: ${{ 
steps.selective-checks.outputs.kustomize-overlay-names }}
+      lang-sdk-conformance-sdks: ${{ 
steps.selective-checks.outputs.lang-sdk-conformance-sdks }}
       run-kustomize-overlays-tests: ${{ 
steps.selective-checks.outputs.run-kustomize-overlays-tests }}
       latest-versions-only: ${{ 
steps.selective-checks.outputs.latest-versions-only }}
       mysql-exclude: ${{ steps.selective-checks.outputs.mysql-exclude }}
@@ -1356,6 +1357,60 @@ jobs:
         working-directory: ./java-sdk
         run: ./gradlew test
 
+  tests-lang-sdk-conformance:
+    name: "Lang SDK serialization conformance (${{ matrix.sdk }})"
+    needs: [build-info]
+    runs-on: ${{ format(fromJSON(needs.build-info.outputs.runner-type)[0], 
'8+16', '32+64') }}
+    timeout-minutes: 20
+    permissions:
+      contents: read
+    strategy:
+      fail-fast: false
+      matrix:
+        sdk: ${{ fromJSON(needs.build-info.outputs.lang-sdk-conformance-sdks) 
}}
+    if: needs.build-info.outputs.lang-sdk-conformance-sdks != '[]'
+    steps:
+      - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1  # 
v7.0.1
+        with:
+          persist-credentials: false
+      - name: "RunsOn cost summary"
+        uses: ./.github/actions/runs-on-cost-summary
+      # Serializes the same Dags with Airflow, from airflow-core's sources, to 
compare them.
+      - name: "Install uv"
+        uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884  # 
v10.3.0
+        with:
+          python-version: "${{ needs.build-info.outputs.default-python-version 
}}"
+      - name: Setup Go
+        if: matrix.sdk == 'go'
+        uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e  # 
v7.0.0
+        with:
+          go-version-file: go-sdk/go.mod
+          cache-dependency-path: go-sdk/go.sum
+      - name: Setup Java
+        if: matrix.sdk == 'java'
+        uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad  # 
v6.1.0
+        with:
+          distribution: 'temurin'
+          java-version: ${{ env.JAVA_VERSION }}
+      - name: Setup pnpm
+        if: matrix.sdk == 'ts'
+        uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413  # 
v6.1.0
+        with:
+          package_json_file: ts-sdk/package.json
+          run_install: false
+      - name: Setup node
+        if: matrix.sdk == 'ts'
+        uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1  # 
v7.1.0
+        with:
+          node-version: "24.19.0"
+          cache: 'pnpm'
+          cache-dependency-path: ts-sdk/pnpm-lock.yaml
+      - name: "Check the ${{ matrix.sdk }} SDK serializes Dags the way Airflow 
does"
+        env:
+          SDK: ${{ matrix.sdk }}
+        run: python3 "scripts/ci/lang_sdk_serialization/check_${SDK}_sdk.py"
+
   build-java-sdk-docs:
     name: "Java SDK docs"
     needs: [build-info]
@@ -1495,6 +1550,7 @@ jobs:
       - tests-go-sdk
       - tests-java-sdk
       - tests-ts-sdk
+      - tests-lang-sdk-conformance
       - build-java-sdk-docs
       - build-ts-sdk-docs
       - tests-with-lowest-direct-resolution-core
@@ -1548,6 +1604,7 @@ jobs:
       - tests-java-sdk
       - tests-kubernetes
       - tests-kustomize-overlays
+      - tests-lang-sdk-conformance
       - tests-mysql-core
       - tests-mysql-providers
       - tests-non-db-core
@@ -1615,6 +1672,7 @@ jobs:
       - tests-java-sdk
       - tests-kubernetes
       - tests-kustomize-overlays
+      - tests-lang-sdk-conformance
       - tests-mysql-core
       - tests-mysql-providers
       - tests-non-db-core
diff --git a/.github/workflows/ci-arm.yml b/.github/workflows/ci-arm.yml
index 341e1e02160..8aa56eab63a 100644
--- a/.github/workflows/ci-arm.yml
+++ b/.github/workflows/ci-arm.yml
@@ -184,6 +184,7 @@ jobs:
       kubernetes-versions-list-as-string: >-
         ${{ steps.selective-checks.outputs.kubernetes-versions-list-as-string 
}}
       kustomize-overlay-names: ${{ 
steps.selective-checks.outputs.kustomize-overlay-names }}
+      lang-sdk-conformance-sdks: ${{ 
steps.selective-checks.outputs.lang-sdk-conformance-sdks }}
       run-kustomize-overlays-tests: ${{ 
steps.selective-checks.outputs.run-kustomize-overlays-tests }}
       latest-versions-only: ${{ 
steps.selective-checks.outputs.latest-versions-only }}
       mysql-exclude: ${{ steps.selective-checks.outputs.mysql-exclude }}
@@ -1237,6 +1238,60 @@ jobs:
         working-directory: ./java-sdk
         run: ./gradlew test
 
+  tests-lang-sdk-conformance:
+    name: "Lang SDK serialization conformance (${{ matrix.sdk }})"
+    needs: [build-info]
+    runs-on: ${{ format(fromJSON(needs.build-info.outputs.runner-type)[0], 
'8+16', '32+64') }}
+    timeout-minutes: 20
+    permissions:
+      contents: read
+    strategy:
+      fail-fast: false
+      matrix:
+        sdk: ${{ fromJSON(needs.build-info.outputs.lang-sdk-conformance-sdks) 
}}
+    if: needs.build-info.outputs.lang-sdk-conformance-sdks != '[]'
+    steps:
+      - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1  # 
v7.0.1
+        with:
+          persist-credentials: false
+      - name: "RunsOn cost summary"
+        uses: ./.github/actions/runs-on-cost-summary
+      # Serializes the same Dags with Airflow, from airflow-core's sources, to 
compare them.
+      - name: "Install uv"
+        uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884  # 
v10.3.0
+        with:
+          python-version: "${{ needs.build-info.outputs.default-python-version 
}}"
+      - name: Setup Go
+        if: matrix.sdk == 'go'
+        uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e  # 
v7.0.0
+        with:
+          go-version-file: go-sdk/go.mod
+          cache-dependency-path: go-sdk/go.sum
+      - name: Setup Java
+        if: matrix.sdk == 'java'
+        uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad  # 
v6.1.0
+        with:
+          distribution: 'temurin'
+          java-version: ${{ env.JAVA_VERSION }}
+      - name: Setup pnpm
+        if: matrix.sdk == 'ts'
+        uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413  # 
v6.1.0
+        with:
+          package_json_file: ts-sdk/package.json
+          run_install: false
+      - name: Setup node
+        if: matrix.sdk == 'ts'
+        uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1  # 
v7.1.0
+        with:
+          node-version: "24.19.0"
+          cache: 'pnpm'
+          cache-dependency-path: ts-sdk/pnpm-lock.yaml
+      - name: "Check the ${{ matrix.sdk }} SDK serializes Dags the way Airflow 
does"
+        env:
+          SDK: ${{ matrix.sdk }}
+        run: python3 "scripts/ci/lang_sdk_serialization/check_${SDK}_sdk.py"
+
   build-java-sdk-docs:
     name: "Java SDK docs"
     needs: [build-info]
@@ -1376,6 +1431,7 @@ jobs:
       - tests-go-sdk
       - tests-java-sdk
       - tests-ts-sdk
+      - tests-lang-sdk-conformance
       - build-java-sdk-docs
       - build-ts-sdk-docs
       - tests-with-lowest-direct-resolution-core
@@ -1428,6 +1484,7 @@ jobs:
       - tests-java-sdk
       - tests-kubernetes
       - tests-kustomize-overlays
+      - tests-lang-sdk-conformance
       - tests-mysql-core
       - tests-mysql-providers
       - tests-non-db-core
@@ -1494,6 +1551,7 @@ jobs:
       - tests-java-sdk
       - tests-kubernetes
       - tests-kustomize-overlays
+      - tests-lang-sdk-conformance
       - tests-mysql-core
       - tests-mysql-providers
       - tests-non-db-core
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 104650a697d..77edd24df59 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -500,62 +500,6 @@ repos:
           ^scripts/ci/prek/check_go_sdk_generated_drift\.py$
         pass_filenames: false
         require_serial: true
-      # check-ts-sdk-serialization-conformance in ts-sdk/ covers the SDK's own 
files; this runs the same
-      # check when the shared harness or Airflow's serializer changes, which 
that project cannot see.
-      - id: check-ts-sdk-serialization-conformance-shared
-        name: Check the TS SDK serializes Dags the way Airflow does, after a 
shared change
-        env:
-          PYTHONPYCACHEPREFIX: .build/pycache
-        description: "Serialize the shared test Dags with the TS SDK and with 
Airflow, and compare the two"
-        entry: ./ts-sdk/scripts/ci/prek/check_serialization_conformance.py
-        language: node
-        additional_dependencies: ['[email protected]']
-        pass_filenames: false
-        require_serial: true
-        files: >
-          (?x)
-          ^airflow-core/src/airflow/serialization/serialized_objects\.py$|
-          ^airflow-core/src/airflow/serialization/schema\.json$|
-          ^scripts/ci/lang_sdk_serialization/.*$
-      - id: check-go-sdk-serialization-conformance
-        name: Check the Go SDK serializes Dags the way Airflow does
-        env:
-          PYTHONPYCACHEPREFIX: .build/pycache
-        description: "Serialize the shared test Dags with the Go SDK and with 
Airflow, and compare the two"
-        entry: ./scripts/ci/prek/check_go_sdk_serialization_conformance.py
-        # golang so prek provisions the toolchain that builds the Go 
serializer.
-        language: golang
-        pass_filenames: false
-        require_serial: true
-        files: >
-          (?x)
-          ^airflow-core/src/airflow/serialization/serialized_objects\.py$|
-          ^airflow-core/src/airflow/serialization/schema\.json$|
-          ^go-sdk/airflow/.*\.go$|
-          ^go-sdk/schema/dag-schema\.json$|
-          ^scripts/ci/lang_sdk_serialization/.*$|
-          ^scripts/ci/prek/check_go_sdk_serialization_conformance\.py$
-      - id: check-java-sdk-serialization-conformance
-        name: Check the Java SDK serializes Dags the way Airflow does
-        env:
-          PYTHONPYCACHEPREFIX: .build/pycache
-        description: "Serialize the shared test Dags with the Java SDK and 
with Airflow, and compare the two"
-        entry: ./java-sdk/scripts/ci/prek/check_serialization_conformance.py
-        language: system
-        pass_filenames: false
-        require_serial: true
-        files: >
-          (?x)
-          ^airflow-core/src/airflow/serialization/serialized_objects\.py$|
-          ^airflow-core/src/airflow/serialization/schema\.json$|
-          ^java-sdk/scripts/ci/prek/check_serialization_conformance\.py$|
-          ^java-sdk/sdk/schema/dag-schema\.json$|
-          ^java-sdk/sdk/build\.gradle\.kts$|
-          
^java-sdk/sdk/src/main/kotlin/org/apache/airflow/sdk/(Switch|Bundle|Condition|DagDef|Decider|Deps|Arg|TaskGroupRef|TriggerDagRun)\.kt$|
-          
^java-sdk/sdk/src/main/kotlin/org/apache/airflow/sdk/execution/Serde\.kt$|
-          
^java-sdk/sdk/src/main/kotlin/org/apache/airflow/sdk/internal/(Refs|Fields)\.kt$|
-          ^java-sdk/sdk/src/test/kotlin/org/apache/airflow/sdk/conformance/.*$|
-          ^scripts/ci/lang_sdk_serialization/.*$
       - id: check-go-version-in-sync
         name: Check Go toolchain version is consistent across build files
         env:
diff --git a/dev/breeze/doc/ci/04_selective_checks.md 
b/dev/breeze/doc/ci/04_selective_checks.md
index 4481d99b054..051fd3d5699 100644
--- a/dev/breeze/doc/ci/04_selective_checks.md
+++ b/dev/breeze/doc/ci/04_selective_checks.md
@@ -525,6 +525,15 @@ together using `pytest-xdist` (pytest-xdist distributes 
the tests among parallel
 * `TS SDK unit tests` (the vitest suite run by `pnpm test` in `ts-sdk/`, 
exposed as the
   `run-ts-sdk-tests` output) run when `TS SDK files` change: anything under 
`ts-sdk/` except `.md`
   files, `ts-sdk/api-docs/`, and `ts-sdk/docs/` (the docs toolchain's own 
package).
+* `Lang SDK serialization conformance` (the `tests-lang-sdk-conformance` job, 
which serializes the Dags
+  of `scripts/ci/lang_sdk_serialization/test_dags.yaml` with an SDK and with 
Airflow and compares the
+  two) runs for each SDK listed in the `lang-sdk-conformance-sdks` output: 
`go` when `Go SDK
+  conformance files` change, `java` when `Java SDK conformance files` change 
and `ts` when `TS SDK
+  conformance files` change. Each group is the SDK's own files (as in `Go SDK 
files`, `Java SDK files`
+  and `TS SDK files`) plus what the check compares the SDK against: Airflow's 
serializer
+  (`serialized_objects.py`), 
`airflow-core/src/airflow/serialization/schema.json` and the shared
+  harness under `scripts/ci/lang_sdk_serialization/`. None of those three 
forces `full_tests_needed`,
+  so a change to any of them runs the check for every SDK. The list is empty 
on a component branch.
 * `Java SDK E2E tests` (the `java_sdk` mode of the deployed-stack tests, 
exposed as the
   `run-java-sdk-e2e-tests` output) run when the Java SDK sources (`java-sdk/`, 
excluding `.md`), the
   Java test-fixture and native-Dag bundles 
(`airflow-e2e-tests/java-test-bundle/`,
@@ -614,18 +623,14 @@ when some files are not changed. Those are the rules 
implemented:
     `regenerate-java-sdk-verification-metadata` checks are skipped. All three 
run the java-sdk
     Gradle wrapper, and `regenerate-java-sdk-verification-metadata` also 
resolves the whole Java
     SDK dependency graph from Maven Central
-  * if no `Java SDK conformance files` changed - 
`check-java-sdk-serialization-conformance`
-    and `sync-java-sdk-dag-schema` are skipped 
(`check-java-sdk-serialization-conformance`
-    compiles the Java SDK with Gradle and resolves its dependencies from Maven 
Central, and
-    `sync-java-sdk-dag-schema` runs the `:sdk:syncDagSchema` Gradle task). The 
group is wider
-    than `Java SDK files`. It also covers Airflow's serializer 
(`serialized_objects.py`),
-    `airflow-core/src/airflow/serialization/schema.json` and the shared 
harness under
-    `scripts/ci/lang_sdk_serialization/`. These files are in the group because 
the conformance
-    check compares the SDK against them, and none of them forces 
`full_tests_needed`. Because
-    airflow-core's `schema.json` is in the group, `sync-java-sdk-dag-schema` 
still runs on a PR
-    that changes only that file. Such a change is what leaves the Java SDK's 
copy in
+  * if no `Java SDK conformance files` changed - `sync-java-sdk-dag-schema` is 
skipped (it runs the
+    `:sdk:syncDagSchema` Gradle task). The group is wider than `Java SDK 
files`: it also covers
+    Airflow's serializer, `airflow-core/src/airflow/serialization/schema.json` 
and the shared
+    harness under `scripts/ci/lang_sdk_serialization/` (see `Lang SDK 
serialization conformance`
+    above). Because airflow-core's `schema.json` is in the group, 
`sync-java-sdk-dag-schema` still
+    runs on a PR that changes only that file. Such a change is what leaves the 
Java SDK's copy in
     `java-sdk/sdk/schema/dag-schema.json` out of date
-  * whichever of the five Java SDK hooks above runs first in a job pays for 
Gradle's cold
+  * whichever of the four Java SDK hooks above runs first in a job pays for 
Gradle's cold
     start, which includes downloading the Gradle distribution. A PR skips the 
cold start only
     when it changes none of the `Java SDK conformance files`
   * neither Java SDK group includes `.pre-commit-config.yaml` or CI setup 
files. A PR that
@@ -698,6 +703,7 @@ GitHub Actions to pass the list of parameters to a command 
to execute
 | kubernetes-combos-list-as-string                        | All combinations 
of Python version and Kubernetes version to use for tests as space-separated 
string    | 3.11-v1.25.2 3.12-v1.28.13               | *    |
 | kubernetes-versions                                     | All Kubernetes 
versions to use for tests as JSON array                                         
         | \['v1.25.2'\]                            |      |
 | kubernetes-versions-list-as-string                      | All Kubernetes 
versions to use for tests as space-separated string                             
         | v1.25.2                                  | *    |
+| lang-sdk-conformance-sdks                               | Language SDKs 
whose serialization conformance check runs, as JSON array                       
          | \["go", "java", "ts"\]                   |      |
 | latest-versions-only                                    | If set, the number 
of Python, Kubernetes, DB versions will be limited to the latest ones.          
     | false                                    |      |
 | mypy-checks                                             | List of folders to 
be considered for mypy checks                                                   
     | \["airflow_aux", "airflow_core"\]        |      |
 | mysql-exclude                                           | Which versions of 
MySQL to exclude for tests as JSON array                                        
      | []                                       |      |
diff --git a/dev/breeze/src/airflow_breeze/utils/selective_checks.py 
b/dev/breeze/src/airflow_breeze/utils/selective_checks.py
index 614cc2672f8..868ed5bf4ff 100644
--- a/dev/breeze/src/airflow_breeze/utils/selective_checks.py
+++ b/dev/breeze/src/airflow_breeze/utils/selective_checks.py
@@ -147,9 +147,11 @@ class FileGroupForCi(Enum):
     TASK_SDK_INTEGRATION_TEST_FILES = auto()
     AGENT_FRAMEWORK_FILES = auto()
     GO_SDK_FILES = auto()
+    GO_SDK_CONFORMANCE_FILES = auto()
     JAVA_SDK_FILES = auto()
     JAVA_SDK_CONFORMANCE_FILES = auto()
     TS_SDK_FILES = auto()
+    TS_SDK_CONFORMANCE_FILES = auto()
     TS_SDK_DOCS_FILES = auto()
     AIRFLOW_CTL_FILES = auto()
     AIRFLOW_CTL_INTEGRATION_TEST_FILES = auto()
@@ -217,6 +219,15 @@ LANG_SDK_NATIVE_DAG_PARSING_FILES = (
     r"^task-sdk/src/airflow/sdk/importers/.*",
 )
 
+# What every language SDK's serialization conformance check compares the SDK 
against: Airflow's
+# serializer and schema, and the shared harness. They do not force 
full_tests_needed, so each SDK's
+# conformance group lists them next to the SDK's own files.
+LANG_SDK_CONFORMANCE_SHARED_FILES = (
+    r"^airflow-core/src/airflow/serialization/serialized_objects\.py$",
+    r"^airflow-core/src/airflow/serialization/schema\.json$",
+    r"^scripts/ci/lang_sdk_serialization/.*",
+)
+
 CI_FILE_GROUP_MATCHES: HashableDict[FileGroupForCi] = HashableDict(
     {
         FileGroupForCi.ENVIRONMENT_FILES: [
@@ -538,6 +549,12 @@ CI_FILE_GROUP_MATCHES: HashableDict[FileGroupForCi] = 
HashableDict(
             # everything else (go.mod, go.sum, build config) must trigger the 
unit tests.
             r"^go-sdk/(?!.*\.md$).*",
         ],
+        FileGroupForCi.GO_SDK_CONFORMANCE_FILES: [
+            # The Go SDK, plus what its serialization conformance check 
compares it against:
+            # Airflow's serializer and schema, and the shared harness.
+            r"^go-sdk/(?!.*\.md$).*",
+            *LANG_SDK_CONFORMANCE_SHARED_FILES,
+        ],
         FileGroupForCi.JAVA_SDK_FILES: [
             # `.md` excluded — doc-only edits do not affect the Gradle build.
             r"^java-sdk/(?!.*\.md$).*",
@@ -546,9 +563,7 @@ CI_FILE_GROUP_MATCHES: HashableDict[FileGroupForCi] = 
HashableDict(
             # The Java SDK, plus what its serialization conformance check 
compares it against:
             # Airflow's serializer and schema, and the shared harness.
             r"^java-sdk/(?!.*\.md$).*",
-            r"^airflow-core/src/airflow/serialization/serialized_objects\.py$",
-            r"^airflow-core/src/airflow/serialization/schema\.json$",
-            r"^scripts/ci/lang_sdk_serialization/.*",
+            *LANG_SDK_CONFORMANCE_SHARED_FILES,
         ],
         FileGroupForCi.TS_SDK_DOCS_FILES: [
             # TypeDoc renders the reference from the SDK sources and category 
entry points,
@@ -568,6 +583,12 @@ CI_FILE_GROUP_MATCHES: HashableDict[FileGroupForCi] = 
HashableDict(
             # docs toolchain's own package, with its own tests that vitest 
does not run.
             r"^ts-sdk/(?!api-docs/)(?!docs/)(?!.*\.md$).*",
         ],
+        FileGroupForCi.TS_SDK_CONFORMANCE_FILES: [
+            # The TS SDK, plus what its serialization conformance check 
compares it against:
+            # Airflow's serializer and schema, and the shared harness.
+            r"^ts-sdk/(?!api-docs/)(?!docs/)(?!.*\.md$).*",
+            *LANG_SDK_CONFORMANCE_SHARED_FILES,
+        ],
         FileGroupForCi.ASSET_FILES: [
             r"^airflow-core/src/airflow/assets/",
             r"^airflow-core/src/airflow/models/assets/",
@@ -1510,6 +1531,19 @@ class SelectiveChecks:
     def run_ts_sdk_docs(self) -> bool:
         return self._should_be_run(FileGroupForCi.TS_SDK_DOCS_FILES)
 
+    @cached_property
+    def lang_sdk_conformance_sdks(self) -> str:
+        """JSON list of the language SDKs whose serialization conformance 
check runs, as the matrix of its job."""
+        # No component branch runs it, as for the outputs scoped with 
_scoped_to_component_branch.
+        if self._component_branch is not None:
+            return json.dumps([])
+        groups = {
+            "go": FileGroupForCi.GO_SDK_CONFORMANCE_FILES,
+            "java": FileGroupForCi.JAVA_SDK_CONFORMANCE_FILES,
+            "ts": FileGroupForCi.TS_SDK_CONFORMANCE_FILES,
+        }
+        return json.dumps([sdk for sdk, group in groups.items() if 
self._should_be_run(group)])
+
     @cached_property
     @_scoped_to_component_branch
     def run_airflow_ctl_tests(self) -> bool:
@@ -2144,14 +2178,10 @@ class SelectiveChecks:
             # depend on that resolution.
             prek_hooks_to_skip.add("regenerate-java-sdk-verification-metadata")
         if not self._matching_files(FileGroupForCi.JAVA_SDK_CONFORMANCE_FILES, 
CI_FILE_GROUP_MATCHES):
-            # This hook compiles the Java SDK with Gradle and resolves its 
dependencies from Maven
-            # Central. Skip it unless a java-sdk file, Airflow's serializer or 
schema, or the shared
-            # harness changed. Those last do not force full_tests_needed, so 
they need their own group.
-            prek_hooks_to_skip.add("check-java-sdk-serialization-conformance")
-            # sync-java-sdk-dag-schema runs the Gradle wrapper too. This hook 
is skipped together
-            # with the conformance check, not with ktlint, because 
JAVA_SDK_CONFORMANCE_FILES
-            # includes airflow-core's schema.json. A PR that changes only 
schema.json can leave
-            # the Java SDK's copy of the schema out of date, and this hook 
detects the outdated copy.
+            # sync-java-sdk-dag-schema runs the Gradle wrapper too. This hook 
is skipped with
+            # JAVA_SDK_CONFORMANCE_FILES, not with ktlint, because that group 
includes airflow-core's
+            # schema.json. A PR that changes only schema.json can leave the 
Java SDK's copy of the
+            # schema out of date, and this hook detects the outdated copy.
             prek_hooks_to_skip.add("sync-java-sdk-dag-schema")
         if not self._matching_files(FileGroupForCi.TS_SDK_FILES, 
CI_FILE_GROUP_MATCHES):
             # This hook regenerates ts-sdk/src/generated/supervisor.ts from 
the vendored schema
diff --git a/dev/breeze/tests/test_selective_checks.py 
b/dev/breeze/tests/test_selective_checks.py
index 2aa1c6fc5ce..5cdac3e1e94 100644
--- a/dev/breeze/tests/test_selective_checks.py
+++ b/dev/breeze/tests/test_selective_checks.py
@@ -117,7 +117,7 @@ LIST_OF_ALL_PROVIDER_TESTS_AS_JSON = json.dumps(
 
 
 ALL_SKIPPED_COMMITS_ON_NO_CI_IMAGE = (
-    
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,flynt,identity,ktlint,lint-helm-chart,"
+    
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,flynt,identity,ktlint,lint-helm-chart,"
     
"mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
     "mypy-scripts,"
@@ -131,7 +131,7 @@ ALL_SKIPPED_COMMITS_ON_NO_CI_IMAGE = (
 ALL_SKIPPED_COMMITS_BY_DEFAULT_ON_ALL_TESTS_NEEDED = "identity"
 
 ALL_SKIPPED_COMMITS_IF_ONLY_UI_OPENAPI_CHANGED = (
-    
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,flynt,identity,ktlint,"
+    
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,flynt,identity,ktlint,"
     
"lint-helm-chart,mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,"
     
"mypy-airflow-e2e-tests,mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,"
     
"mypy-kubernetes-tests,mypy-scripts,mypy-shared-configuration,mypy-shared-dagnode,"
@@ -143,7 +143,7 @@ ALL_SKIPPED_COMMITS_IF_ONLY_UI_OPENAPI_CHANGED = (
 )
 
 ALL_SKIPPED_COMMITS_IF_NO_UI = (
-    
"check-java-sdk-serialization-conformance,check-ts-sdk-supervisor-schema,identity,ktlint,mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
+    
"check-ts-sdk-supervisor-schema,identity,ktlint,mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
     "mypy-scripts,"
     
"mypy-shared-configuration,mypy-shared-dagnode,mypy-shared-listeners,mypy-shared-logging,"
@@ -153,7 +153,7 @@ ALL_SKIPPED_COMMITS_IF_NO_UI = (
     
"regenerate-java-sdk-verification-metadata,spotless,sync-java-sdk-dag-schema,ts-compile-lint-simple-auth-manager-ui,ts-compile-lint-ui"
 )
 ALL_SKIPPED_COMMITS_IF_NO_HELM_TESTS = (
-    
"check-java-sdk-serialization-conformance,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+    "check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
     
"mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
     "mypy-scripts,"
@@ -164,7 +164,7 @@ ALL_SKIPPED_COMMITS_IF_NO_HELM_TESTS = (
 )
 
 ALL_SKIPPED_COMMITS_IF_NO_UI_AND_HELM_TESTS = (
-    
"check-java-sdk-serialization-conformance,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+    "check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
     
"mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
     "mypy-scripts,"
@@ -179,7 +179,7 @@ ALL_SKIPPED_COMMITS_IF_NO_UI_AND_HELM_TESTS = (
 # forced. airflow-core Python changed (so mypy-airflow-core + flynt run); no
 # provider.yaml, helm, or UI files changed, so those checks stay skipped.
 ALL_SKIPPED_COMMITS_IF_ONLY_API_SOURCE_CHANGED = (
-    
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+    
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
     "mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
     "mypy-scripts,"
@@ -192,7 +192,7 @@ ALL_SKIPPED_COMMITS_IF_ONLY_API_SOURCE_CHANGED = (
 )
 
 ALL_SKIPPED_COMMITS_IF_NO_PROVIDERS_AND_UI = (
-    
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,"
+    "check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,"
     
"mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
     "mypy-scripts,"
@@ -204,7 +204,7 @@ ALL_SKIPPED_COMMITS_IF_NO_PROVIDERS_AND_UI = (
 )
 
 ALL_SKIPPED_COMMITS_IF_NO_PROVIDERS = (
-    
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+    
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
     
"mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
     "mypy-scripts,"
@@ -217,7 +217,7 @@ ALL_SKIPPED_COMMITS_IF_NO_PROVIDERS = (
 
 
 ALL_SKIPPED_COMMITS_IF_NO_PROVIDERS_UI_AND_HELM_TESTS = (
-    
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+    
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
     
"mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
     "mypy-scripts,"
@@ -229,7 +229,7 @@ ALL_SKIPPED_COMMITS_IF_NO_PROVIDERS_UI_AND_HELM_TESTS = (
 )
 
 ALL_SKIPPED_COMMITS_IF_NO_CODE_PROVIDERS_AND_HELM_TESTS = (
-    
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,flynt,identity,ktlint,lint-helm-chart,"
+    
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,flynt,identity,ktlint,lint-helm-chart,"
     
"mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
     "mypy-scripts,"
@@ -240,7 +240,7 @@ ALL_SKIPPED_COMMITS_IF_NO_CODE_PROVIDERS_AND_HELM_TESTS = (
 )
 
 ALL_SKIPPED_COMMITS_IF_NOT_IMPORTANT_FILES_CHANGED = (
-    
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,flynt,identity,ktlint,lint-helm-chart,"
+    
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,flynt,identity,ktlint,lint-helm-chart,"
     
"mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
     "mypy-scripts,"
@@ -485,7 +485,7 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
                     "run-amazon-tests": "false",
                     "docs-build": "true",
                     "skip-prek-hooks": (
-                        
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+                        
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
                         
"mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
                         
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
                         "mypy-scripts,"
@@ -531,7 +531,7 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
                     "run-api-tests": "true",
                     "docs-build": "true",
                     "skip-prek-hooks": (
-                        
"check-java-sdk-serialization-conformance,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+                        
"check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
                         
"mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
                         
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
                         "mypy-scripts,"
@@ -786,7 +786,7 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
                     "docs-build": "true",
                     "full-tests-needed": "false",
                     "skip-prek-hooks": (
-                        
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+                        
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
                         
"mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
                         
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
                         "mypy-scripts,"
@@ -824,7 +824,7 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
                     "docs-build": "false",
                     "full-tests-needed": "false",
                     "skip-prek-hooks": (
-                        
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+                        
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
                         
"mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
                         
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
                         "mypy-scripts,"
@@ -887,7 +887,7 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
                     "docs-build": "true",
                     "full-tests-needed": "false",
                     "skip-prek-hooks": (
-                        
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+                        
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
                         
"mypy-airflow-core,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
                         
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
                         "mypy-scripts,"
@@ -923,7 +923,7 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
                     "docs-build": "false",
                     "full-tests-needed": "false",
                     "skip-prek-hooks": (
-                        
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+                        
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
                         
"mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-e2e-tests,"
                         
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
                         "mypy-scripts,"
@@ -1272,7 +1272,7 @@ def assert_outputs_are_printed(expected_outputs: 
dict[str, str], stderr: str):
                 "docs-build": "false",
                 "run-kubernetes-tests": "false",
                 "skip-prek-hooks": (
-                    
"check-java-sdk-serialization-conformance,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+                    
"check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
                     
"mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
                     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
                     "mypy-scripts,"
@@ -1479,7 +1479,7 @@ def assert_outputs_are_printed(expected_outputs: 
dict[str, str], stderr: str):
                 "run-amazon-tests": "false",
                 "docs-build": "true",
                 "skip-prek-hooks": (
-                    
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,flynt,identity,ktlint,"
+                    
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,flynt,identity,ktlint,"
                     
"mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
                     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
                     "mypy-scripts,"
@@ -1989,7 +1989,7 @@ def assert_outputs_are_printed(expected_outputs: 
dict[str, str], stderr: str):
                 ("shared/logging/src/airflow_shared/logging/remote.py",),
                 {
                     "skip-prek-hooks": (
-                        
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+                        
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
                         
"mypy-airflow-core,mypy-airflow-ctl,mypy-airflow-ctl-tests,"
                         
"mypy-airflow-e2e-tests,mypy-dev,mypy-devel-common,mypy-docker-tests,"
                         "mypy-helm-tests,mypy-kubernetes-tests,mypy-scripts,"
@@ -2027,7 +2027,6 @@ def test_expected_output_pull_request_main(
         "ktlint",
         "spotless",
         "regenerate-java-sdk-verification-metadata",
-        "check-java-sdk-serialization-conformance",
         "sync-java-sdk-dag-schema",
     ],
 )
@@ -2067,7 +2066,6 @@ def 
test_java_sdk_gradle_hooks_only_run_for_java_sdk_changes(
     assert (hook in skipped_hooks) is hook_skipped
 
 
[email protected]("hook", ["check-java-sdk-serialization-conformance", 
"sync-java-sdk-dag-schema"])
 @pytest.mark.parametrize(
     ("files", "hook_skipped"),
     [
@@ -2093,9 +2091,7 @@ def 
test_java_sdk_gradle_hooks_only_run_for_java_sdk_changes(
         ),
     ],
 )
-def test_java_sdk_conformance_hook_runs_for_serializer_changes(
-    files: tuple[str, ...], hook_skipped: bool, hook: str
-):
+def test_sync_java_sdk_dag_schema_runs_for_serializer_changes(files: 
tuple[str, ...], hook_skipped: bool):
     stderr = SelectiveChecks(
         files=files,
         commit_ref=NEUTRAL_COMMIT,
@@ -2104,7 +2100,43 @@ def 
test_java_sdk_conformance_hook_runs_for_serializer_changes(
         default_branch="main",
     )
     skipped_hooks = 
get_outputs_from_stderr(str(stderr))["skip-prek-hooks"].split(",")
-    assert (hook in skipped_hooks) is hook_skipped
+    assert ("sync-java-sdk-dag-schema" in skipped_hooks) is hook_skipped
+
+
[email protected](
+    ("files", "expected_sdks"),
+    [
+        pytest.param(("go-sdk/airflow/serialize.go",), ["go"], 
id="go-sdk-change"),
+        pytest.param(("java-sdk/sdk/build.gradle.kts",), ["java"], 
id="java-sdk-change"),
+        pytest.param(("ts-sdk/src/coordinator/serde.ts",), ["ts"], 
id="ts-sdk-change"),
+        pytest.param(
+            ("airflow-core/src/airflow/serialization/serialized_objects.py",),
+            ["go", "java", "ts"],
+            id="airflow-serializer-change",
+        ),
+        pytest.param(
+            ("airflow-core/src/airflow/serialization/schema.json",),
+            ["go", "java", "ts"],
+            id="airflow-dag-schema-change",
+        ),
+        pytest.param(
+            ("scripts/ci/lang_sdk_serialization/compare.py",),
+            ["go", "java", "ts"],
+            id="shared-harness-change",
+        ),
+        pytest.param(("go-sdk/README.md",), [], id="sdk-docs-only-change"),
+        pytest.param(("SECURITY.md",), [], id="unrelated-change"),
+    ],
+)
+def test_lang_sdk_conformance_sdks(files: tuple[str, ...], expected_sdks: 
list[str]):
+    stderr = SelectiveChecks(
+        files=files,
+        commit_ref=NEUTRAL_COMMIT,
+        github_event=GithubEvents.PULL_REQUEST,
+        pr_labels=tuple(),
+        default_branch="main",
+    )
+    assert 
json.loads(get_outputs_from_stderr(str(stderr))["lang-sdk-conformance-sdks"]) 
== expected_sdks
 
 
 def test_spotless_skipped_when_only_airflow_serializer_changes():
@@ -3076,7 +3108,7 @@ def test_expected_output_fork_push(
                 "docs-build": "true",
                 "docs-list-as-string": ALL_DOCS_SELECTED_FOR_BUILD,
                 "skip-prek-hooks": (
-                    
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+                    
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
                     
"mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
                     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
                     "mypy-scripts,"
@@ -3118,7 +3150,7 @@ def test_expected_output_fork_push(
                 "microsoft.mssql mongo mysql openlineage oracle postgres "
                 "presto salesforce samba sftp ssh standard trino",
                 "skip-prek-hooks": (
-                    
"check-java-sdk-serialization-conformance,check-ts-sdk-supervisor-schema,identity,ktlint,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
+                    
"check-ts-sdk-supervisor-schema,identity,ktlint,mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
                     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
                     "mypy-scripts,"
                     
"mypy-shared-configuration,mypy-shared-dagnode,mypy-shared-listeners,mypy-shared-logging,"
@@ -3163,7 +3195,7 @@ def test_expected_output_fork_push(
                 "docs-build": "true",
                 "docs-list-as-string": "apache-airflow",
                 "skip-prek-hooks": (
-                    
"check-java-sdk-serialization-conformance,check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
+                    
"check-provider-yaml-valid,check-ts-sdk-supervisor-schema,identity,ktlint,lint-helm-chart,"
                     
"mypy-airflow-ctl,mypy-airflow-ctl-tests,mypy-airflow-e2e-tests,"
                     
"mypy-dev,mypy-devel-common,mypy-docker-tests,mypy-helm-tests,mypy-kubernetes-tests,"
                     "mypy-scripts,"
@@ -5643,6 +5675,7 @@ def 
test_component_branch_runs_only_its_component(context, expected_on, expected
     assert checks.docs_list_as_string == expected_docs
     assert checks.docs_build is (expected_docs is not None)
     assert checks.basic_checks_only is ("ci_image_build" not in expected_on)
+    assert checks.lang_sdk_conformance_sdks == "[]"
 
 
 @pytest.mark.parametrize(
diff --git a/go-sdk/DEVELOPMENT.md b/go-sdk/DEVELOPMENT.md
index b6511e8ec99..cd40e52e5b4 100644
--- a/go-sdk/DEVELOPMENT.md
+++ b/go-sdk/DEVELOPMENT.md
@@ -159,17 +159,17 @@ dag.Task(load, airflow.Inputs(transformed, 
airflow.Literal("s3://bucket/out")))
 
 A literal must be JSON that decodes into its parameter type, and it adds no 
edge to the Dag.
 
-The `check-go-sdk-serialization-conformance` prek hook builds the Dags of
+The serialization conformance check builds the Dags of
 
[`scripts/ci/lang_sdk_serialization/test_dags.yaml`](../scripts/ci/lang_sdk_serialization/test_dags.yaml)
 twice: with this SDK, and with Python and Airflow's own serializer. It loads 
the Go output with
-Airflow's deserializer and compares the two serializations field by field. Run 
it from the
-repository root:
+Airflow's deserializer and compares the two serializations field by field. CI 
runs it in the
+`Lang SDK serialization conformance` job. Run it from the repository root (it 
needs `go` and `uv`):
 
 ```bash
-prek run check-go-sdk-serialization-conformance --all-files
+python3 scripts/ci/lang_sdk_serialization/check_go_sdk.py
 ```
 
-The hook runs every Dag of the file, including the ones that `requires` a 
feature that only some SDKs
+The check runs every Dag of the file, including the ones that `requires` a 
feature that only some SDKs
 have. 
[`go_native.json`](../airflow-core/tests/unit/dag_processing/lang_sdk_fixtures/go_native.json)
 holds the Go output for the same Dags, so that the Airflow core tests check 
that Airflow loads it.
 `TestSerializeConformanceDags` says how to rewrite it.
diff --git a/go-sdk/airflow/conformance_test.go 
b/go-sdk/airflow/conformance_test.go
index a86b9209531..64dc043f460 100644
--- a/go-sdk/airflow/conformance_test.go
+++ b/go-sdk/airflow/conformance_test.go
@@ -37,8 +37,8 @@ import (
 // TestSerializeConformanceDags builds the Dags of 
scripts/ci/lang_sdk_serialization/test_dags.yaml
 // with this SDK, serializes them, and writes them to a JSON file keyed by 
dag_id.
 // serialize_python.py in the same directory does the same with Airflow's own 
serializer. compare.py
-// there runs this test as the serializer of the Go SDK for the
-// check-go-sdk-serialization-conformance prek hook, and passes the two paths 
after -args:
+// there runs this test as the serializer of the Go SDK for check_go_sdk.py, 
and passes the two
+// paths after -args:
 //
 //     go -C go-sdk test ./airflow -count=1 -run 
'^TestSerializeConformanceDags$' -args <test_dags.yaml> <output.json>
 //
diff --git a/java-sdk/sdk/build.gradle.kts b/java-sdk/sdk/build.gradle.kts
index 825f481b950..09d626e68f6 100644
--- a/java-sdk/sdk/build.gradle.kts
+++ b/java-sdk/sdk/build.gradle.kts
@@ -959,7 +959,7 @@ publishing {
 }
 
 // Prints the classpath that runs the conformance serializer, for
-// java-sdk/scripts/ci/prek/check_serialization_conformance.py.
+// scripts/ci/lang_sdk_serialization/check_java_sdk.py.
 tasks.register("printConformanceClasspath") {
     dependsOn("testClasses")
     // Capture early to keep compatibility to the Gradle configuration cache.
diff --git a/scripts/ci/prek/check_go_sdk_serialization_conformance.py 
b/scripts/ci/lang_sdk_serialization/check_go_sdk.py
similarity index 96%
rename from scripts/ci/prek/check_go_sdk_serialization_conformance.py
rename to scripts/ci/lang_sdk_serialization/check_go_sdk.py
index 2a152cee655..6406548e18d 100755
--- a/scripts/ci/prek/check_go_sdk_serialization_conformance.py
+++ b/scripts/ci/lang_sdk_serialization/check_go_sdk.py
@@ -25,7 +25,7 @@ and compares the two serializations field by field.
 
 Run from the repo root:
 
-    python3 scripts/ci/prek/check_go_sdk_serialization_conformance.py
+    python3 scripts/ci/lang_sdk_serialization/check_go_sdk.py
 
 It needs ``go`` and ``uv``. Exits 0 when the two serializations agree, 1 
otherwise.
 """
diff --git a/java-sdk/scripts/ci/prek/check_serialization_conformance.py 
b/scripts/ci/lang_sdk_serialization/check_java_sdk.py
similarity index 94%
rename from java-sdk/scripts/ci/prek/check_serialization_conformance.py
rename to scripts/ci/lang_sdk_serialization/check_java_sdk.py
index d6f454ec3e7..a65cecc4432 100755
--- a/java-sdk/scripts/ci/prek/check_serialization_conformance.py
+++ b/scripts/ci/lang_sdk_serialization/check_java_sdk.py
@@ -23,9 +23,7 @@ import subprocess
 import sys
 from pathlib import Path
 
-sys.path.insert(0, str(Path(__file__).resolve().parents[4] / "scripts" / "ci" 
/ "prek"))
-
-from common_prek_utils import AIRFLOW_ROOT_PATH
+AIRFLOW_ROOT_PATH = Path(__file__).resolve().parents[3]
 
 if __name__ not in ("__main__", "__mp_main__"):
     raise SystemExit(
diff --git a/ts-sdk/scripts/ci/prek/check_serialization_conformance.py 
b/scripts/ci/lang_sdk_serialization/check_ts_sdk.py
similarity index 92%
rename from ts-sdk/scripts/ci/prek/check_serialization_conformance.py
rename to scripts/ci/lang_sdk_serialization/check_ts_sdk.py
index 95a11280f2f..027b6def534 100755
--- a/ts-sdk/scripts/ci/prek/check_serialization_conformance.py
+++ b/scripts/ci/lang_sdk_serialization/check_ts_sdk.py
@@ -23,9 +23,7 @@ import subprocess
 import sys
 from pathlib import Path
 
-sys.path.insert(0, str(Path(__file__).resolve().parents[4] / "scripts" / "ci" 
/ "prek"))
-
-from common_prek_utils import AIRFLOW_ROOT_PATH, run_command
+AIRFLOW_ROOT_PATH = Path(__file__).resolve().parents[3]
 
 # The features of test_dags.yaml that 
tests/conformance/serialize_typescript.ts can build. A Dag that
 # requires another feature is left out until the builder gets it.
@@ -38,9 +36,10 @@ if __name__ not in ("__main__", "__mp_main__"):
     )
 
 if __name__ == "__main__":
-    run_command(
+    subprocess.run(
         ["pnpm", "install", "--frozen-lockfile", 
"--config.confirmModulesPurge=false"],
         cwd=AIRFLOW_ROOT_PATH / "ts-sdk",
+        check=True,
     )
     compare = AIRFLOW_ROOT_PATH / "scripts" / "ci" / "lang_sdk_serialization" 
/ "compare.py"
     serializer = ["pnpm", "--dir", "ts-sdk", "exec", "tsx", 
"tests/conformance/serialize_typescript.ts"]
diff --git a/scripts/ci/lang_sdk_serialization/compare.py 
b/scripts/ci/lang_sdk_serialization/compare.py
index ecb27acb733..80ccc9324ae 100644
--- a/scripts/ci/lang_sdk_serialization/compare.py
+++ b/scripts/ci/lang_sdk_serialization/compare.py
@@ -35,8 +35,8 @@ features the SDK has, as a comma-separated list or ``all``, 
and the copy of test
 the Dags whose features are all among them. A Dag that requires nothing is 
always in it, and the SDK
 has to write exactly the Dags of the copy.
 
-Each SDK's own prek hook runs it this way, such as
-java-sdk/scripts/ci/prek/check_serialization_conformance.py for the Java SDK.
+Each SDK's check script next to this file runs it this way, such as 
check_java_sdk.py for the
+Java SDK.
 
 On a failure the files are kept, and their directory is printed.
 """
diff --git 
a/scripts/tests/ci/prek/test_check_go_sdk_serialization_conformance.py 
b/scripts/tests/ci/lang_sdk_serialization/test_check_go_sdk.py
similarity index 94%
rename from scripts/tests/ci/prek/test_check_go_sdk_serialization_conformance.py
rename to scripts/tests/ci/lang_sdk_serialization/test_check_go_sdk.py
index f3ce5fccc68..4ec45a64bb1 100644
--- a/scripts/tests/ci/prek/test_check_go_sdk_serialization_conformance.py
+++ b/scripts/tests/ci/lang_sdk_serialization/test_check_go_sdk.py
@@ -21,10 +21,10 @@ import subprocess
 import sys
 from unittest import mock
 
-import check_go_sdk_serialization_conformance as checker
+from ci.lang_sdk_serialization import check_go_sdk as checker
 
 
[email protected]("check_go_sdk_serialization_conformance.subprocess.run", 
autospec=True)
[email protected]("ci.lang_sdk_serialization.check_go_sdk.subprocess.run", 
autospec=True)
 def 
test_main_runs_compare_with_the_go_serializer_and_returns_its_exit_code(mock_run):
     mock_run.return_value = subprocess.CompletedProcess(args=[], returncode=1)
 
diff --git a/ts-sdk/.pre-commit-config.yaml b/ts-sdk/.pre-commit-config.yaml
index 0a927e4dd01..9e4cb2081b7 100644
--- a/ts-sdk/.pre-commit-config.yaml
+++ b/ts-sdk/.pre-commit-config.yaml
@@ -68,22 +68,6 @@ repos:
         additional_dependencies: ['[email protected]']
         pass_filenames: false
         require_serial: true
-      - id: check-ts-sdk-serialization-conformance
-        name: Check the TS SDK serializes Dags the way Airflow does
-        env:
-          PYTHONPYCACHEPREFIX: .build/pycache
-        description: "Serialize the shared test Dags with the TS SDK and with 
Airflow, and compare the two"
-        entry: ./scripts/ci/prek/check_serialization_conformance.py
-        language: node
-        files: >
-          (?x)
-          ^schema/dag-schema\.json$|
-          ^src/coordinator/serde\.ts$|
-          ^src/sdk/dag\.ts$|
-          ^tests/conformance/.*$
-        additional_dependencies: ['[email protected]']
-        pass_filenames: false
-        require_serial: true
       - id: check-ts-sdk-docs-package-version-in-sync
         name: Check ts-sdk/docs package.json versions match ts-sdk/package.json
         env:
diff --git a/ts-sdk/tests/conformance/serialize_typescript.ts 
b/ts-sdk/tests/conformance/serialize_typescript.ts
index 9dabc16a71c..4bc13e2a88a 100644
--- a/ts-sdk/tests/conformance/serialize_typescript.ts
+++ b/ts-sdk/tests/conformance/serialize_typescript.ts
@@ -19,7 +19,7 @@
 
 // Serializes the Dags of scripts/ci/lang_sdk_serialization/test_dags.yaml 
with this SDK, as
 // serialize_python.py there does with Airflow's serializer, and writes them 
keyed by Dag id. The
-// check-ts-sdk-serialization-conformance hook has that directory's compare.py 
run it as:
+// check_ts_sdk.py in that directory has compare.py run it as:
 //
 //   pnpm --dir ts-sdk exec tsx tests/conformance/serialize_typescript.ts 
<test_dags.yaml> <output.json>
 

Reply via email to