This is an automated email from the ASF dual-hosted git repository.
jason810496 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 f881a99629c Add Go SDK capability manifest and compatibility matrix
(#71753)
f881a99629c is described below
commit f881a99629cd0676876924202b821537a9e4d493
Author: Jason(Zhe-You) Liu <[email protected]>
AuthorDate: Fri Sep 18 09:05:18 2026 +0800
Add Go SDK capability manifest and compatibility matrix (#71753)
* Add Go SDK capability manifest and compatibility matrix
* Keep Go SDK compatibility claims accurate
Airflow 3.3 does not recognize the Go SDK's current supervisor schema, and
the drift check should remain reliable across idiomatic Go constant
declarations.
* Keep Go SDK compatibility validation reliable
Go permits equivalent constant declaration forms, and source-file failures
should not turn the matrix check into an opaque traceback. Historical
capability dates also need context when a newer wire schema raises the current
runtime floor.
* Distinguish a missing/renamed schema version constant from a real mismatch
read_go_schema_version() returns None both when the SupervisorSchemaVersion
constant is gone and when its declaration no longer matches
SCHEMA_VERSION_PATTERN. Previously that fell through to the mismatch branch,
reporting "SupervisorSchemaVersion is None" and telling users to update
capabilities.yaml, which cannot fix a missing or renamed Go constant.
---
.pre-commit-config.yaml | 14 +++
go-sdk/README.md | 54 ++++++++++
go-sdk/capabilities.yaml | 119 +++++++++++++++++++++
scripts/ci/prek/lang_sdk_compat_matrix.py | 10 +-
scripts/ci/prek/update_go_sdk_readme_matrix.py | 110 +++++++++++++++++++
.../ci/prek/test_update_go_sdk_readme_matrix.py | 102 ++++++++++++++++++
6 files changed, 404 insertions(+), 5 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 23cadebddb9..ecccc745ae7 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -323,6 +323,20 @@ repos:
^\.pre-commit-config\.yaml$
pass_filenames: false
require_serial: true
+ - id: update-go-sdk-readme-matrix
+ name: Update the Go SDK compatibility matrix in go-sdk/README.md
+ entry: ./scripts/ci/prek/update_go_sdk_readme_matrix.py
+ language: python
+ files: >
+ (?x)
+ ^go-sdk/capabilities\.yaml$|
+ ^go-sdk/pkg/execution/messages\.go$|
+ ^go-sdk/README\.md$|
+ ^scripts/ci/prek/lang_sdk_compat_matrix\.py$|
+ ^scripts/ci/prek/update_go_sdk_readme_matrix\.py$
+ additional_dependencies: ['PyYAML>=6.0', 'rich>=13.6.0']
+ pass_filenames: false
+ require_serial: true
- id: check-go-example-mod-tidy
name: Check lang-SDK Go example module is tidy against the Go SDK
entry: ./scripts/ci/prek/check_go_example_mod_tidy.py
diff --git a/go-sdk/README.md b/go-sdk/README.md
index 14e44f3c531..a5d619025f6 100644
--- a/go-sdk/README.md
+++ b/go-sdk/README.md
@@ -275,6 +275,60 @@ the full range of task states, and alternate XCom backends
without implementing
- Deploy the matching Python stub Dag (above) into Airflow. There is no
separate Go worker to run: the
Airflow worker forks the bundle binary once per task instance.
+## Compatibility matrix
+
+Which Airflow TaskInstance states and capabilities this SDK supports. This
table is generated from
+[`capabilities.yaml`](capabilities.yaml); the conformance dimensions are
defined in the
+[Language SDK conformance
spec](https://github.com/apache/airflow/blob/main/contributing-docs/30_new_language_sdk.rst).
+The minimum version applies to the current SDK source, while "Since" records
when each capability
+first became available and can therefore be earlier than the current minimum.
+Do not edit the table by hand — edit `capabilities.yaml` and let the
`update-go-sdk-readme-matrix`
+prek hook regenerate it.
+
+<!-- BEGIN AUTO-GENERATED LANG-SDK COMPAT MATRIX -->
+
+*Min. Airflow version: 3.4 · supervisor schema: 2026-10-30*
+
+| Dimension | Tier | Supported | Since | Notes |
+|---|---|---|---|---|
+| **TaskInstance states** | | | | |
+| state: `success` | MUST | ✓ | 3.3 | |
+| state: `failed` | MUST | ✓ | 3.3 | |
+| state: `up_for_retry` | MUST | ✓ | 3.3 | RetryTask |
+| state: `skipped` | SHOULD | ✗ | – | runtime does not emit TaskState skipped
yet |
+| state: `deferred` | MAY | ✗ | – | runtime does not emit DeferTask yet |
+| state: `up_for_reschedule` | MAY | ✗ | – | runtime does not emit
RescheduleTask yet |
+| state: `awaiting_input` | MAY | ✗ | – | runtime does not emit AwaitInputTask
yet |
+| state: `removed` | MAY | ✓ | 3.3 | |
+| **Runtime capabilities** | | | | |
+| capability: `mixed-lang-stub-target` | MUST | ✓ | 3.3 | @task.stub |
+| capability: `task-logging` | MUST | ✓ | 3.3 | slog records streamed over the
logs socket |
+| capability: `xcom-read-write` | MUST | ✓ | 3.3 | PushXCom / GetXCom |
+| capability: `connection-read` | MUST | ✓ | 3.3 | GetConnection |
+| capability: `variable-read-write` | MUST | ✗ | – | GetVariable only; no
write over the comm socket yet |
+| capability: `self-contained-bundle` | MUST | ✓ | 3.3 | AFBNDL01 native
binary via airflow-go-pack |
+| capability: `retry-policy` | MAY | ✗ | – | no task-facing retry-policy API
yet |
+| capability: `task-state-store` | MAY | ✗ | – | no task-facing state-store
API yet |
+| capability: `asset-state-store` | MAY | ✗ | – | no task-facing state-store
API yet |
+| capability: `asset-event-emit` | MAY | ✗ | – | runtime does not emit asset
events yet |
+| capability: `asset-event-read` | MAY | ✗ | – | no task-facing asset-event
API yet |
+| **Native-Dag authoring** | | | | |
+| capability: `native-dag-authoring` | SHOULD | ✗ | – | native Dag authoring
not implemented yet |
+| capability: `task-args` | MUST † | n/a | – | |
+| capability: `dag-params` | MUST † | n/a | – | |
+| capability: `taskflow-dependencies` | MUST † | n/a | – | |
+| capability: `branching` | SHOULD † | n/a | – | |
+| capability: `dag-test` | SHOULD † | n/a | – | |
+| capability: `task-group` | MAY † | n/a | – | |
+| capability: `dynamic-task-mapping` | MAY † | n/a | – | |
+| capability: `asset-inlets-outlets` | MAY † | n/a | – | |
+| capability: `asset-scheduling` | MAY † | n/a | – | |
+| capability: `object-store` | MAY † | n/a | – | no object-storage API yet |
+
+*Marks: ✓ supported · ✗ not supported · n/a not applicable. A tier marked †
applies only when `native-dag-authoring` is supported.*
+
+<!-- END AUTO-GENERATED LANG-SDK COMPAT MATRIX -->
+
## How it works
The bundle binary speaks the coordinator protocol when Airflow launches it
with `--comm` and `--logs`.
diff --git a/go-sdk/capabilities.yaml b/go-sdk/capabilities.yaml
new file mode 100644
index 00000000000..94be96e9639
--- /dev/null
+++ b/go-sdk/capabilities.yaml
@@ -0,0 +1,119 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+---
+sdk: go
+
+min_airflow_version: "3.4"
+
+# Keep in sync with SupervisorSchemaVersion in pkg/execution/messages.go,
which is embedded in
+# bundle metadata. The render hook fails if the two disagree.
+supervisor_schema_version: "2026-10-30"
+
+# The task runner terminates a task with SucceedTask, RetryTask, or TaskState
(failed/removed); it
+# does not yet emit skipped, DeferTask, RescheduleTask, or AwaitInputTask.
+states:
+ success:
+ supported: true
+ since: "3.3"
+ failed:
+ supported: true
+ since: "3.3"
+ up_for_retry:
+ supported: true
+ since: "3.3"
+ note: "RetryTask"
+ skipped:
+ supported: false
+ note: "runtime does not emit TaskState skipped yet"
+ deferred:
+ supported: false
+ note: "runtime does not emit DeferTask yet"
+ up_for_reschedule:
+ supported: false
+ note: "runtime does not emit RescheduleTask yet"
+ awaiting_input:
+ supported: false
+ note: "runtime does not emit AwaitInputTask yet"
+ removed:
+ supported: true
+ since: "3.3"
+
+# Runtime capabilities reflect the task-facing CoordinatorClient surface;
native-Dag authoring is
+# not implemented yet, so every native capability is unsupported.
+capabilities:
+ mixed-lang-stub-target:
+ supported: true
+ since: "3.3"
+ note: "@task.stub"
+ task-logging:
+ supported: true
+ since: "3.3"
+ note: "slog records streamed over the logs socket"
+ xcom-read-write:
+ supported: true
+ since: "3.3"
+ note: "PushXCom / GetXCom"
+ connection-read:
+ supported: true
+ since: "3.3"
+ note: "GetConnection"
+ variable-read-write:
+ supported: false
+ note: "GetVariable only; no write over the comm socket yet"
+ self-contained-bundle:
+ supported: true
+ since: "3.3"
+ note: "AFBNDL01 native binary via airflow-go-pack"
+ retry-policy:
+ supported: false
+ note: "no task-facing retry-policy API yet"
+ task-state-store:
+ supported: false
+ note: "no task-facing state-store API yet"
+ asset-state-store:
+ supported: false
+ note: "no task-facing state-store API yet"
+ asset-event-emit:
+ supported: false
+ note: "runtime does not emit asset events yet"
+ asset-event-read:
+ supported: false
+ note: "no task-facing asset-event API yet"
+ native-dag-authoring:
+ supported: false
+ note: "native Dag authoring not implemented yet"
+ task-args:
+ supported: false
+ dag-params:
+ supported: false
+ taskflow-dependencies:
+ supported: false
+ branching:
+ supported: false
+ dag-test:
+ supported: false
+ task-group:
+ supported: false
+ dynamic-task-mapping:
+ supported: false
+ asset-inlets-outlets:
+ supported: false
+ asset-scheduling:
+ supported: false
+ object-store:
+ supported: false
+ note: "no object-storage API yet"
diff --git a/scripts/ci/prek/lang_sdk_compat_matrix.py
b/scripts/ci/prek/lang_sdk_compat_matrix.py
index 7de1b3a30a9..9c8dd3ca92a 100644
--- a/scripts/ci/prek/lang_sdk_compat_matrix.py
+++ b/scripts/ci/prek/lang_sdk_compat_matrix.py
@@ -117,11 +117,11 @@ class LangSdk(TypedDict):
readme: Path
-# The registry of Language SDKs and where each one's manifest and README live.
Only the Java SDK
-# declares one so far; the Go and TypeScript entries record where theirs go
when those runtimes
-# declare their capabilities. Because of that, `capabilities_yaml` is a
declared location and not a
-# promise the file exists — a consumer walking the whole registry must check
`.exists()` before
-# calling load_capabilities().
+# The registry of Language SDKs and where each one's manifest and README live.
The Go and Java SDKs
+# declare manifests; the TypeScript entry records where its manifest goes when
that runtime declares
+# its capabilities. Because of that, `capabilities_yaml` is a declared
location and not a promise the
+# file exists — a consumer walking the whole registry must check `.exists()`
before calling
+# load_capabilities().
LANG_SDKS: list[LangSdk] = [
{
"id": "go",
diff --git a/scripts/ci/prek/update_go_sdk_readme_matrix.py
b/scripts/ci/prek/update_go_sdk_readme_matrix.py
new file mode 100755
index 00000000000..b44c5b9e3f4
--- /dev/null
+++ b/scripts/ci/prek/update_go_sdk_readme_matrix.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+# /// script
+# requires-python = ">=3.10,<3.11"
+# dependencies = ["PyYAML>=6.0", "rich>=13.6.0"]
+# ///
+"""Regenerate the Go SDK compatibility table in ``go-sdk/README.md``.
+
+Renders the Markdown matrix from ``go-sdk/capabilities.yaml`` between the
AUTO-GENERATED markers.
+Exits non-zero when the file changed so the contributor re-stages it. The hook
also verifies that
+the manifest's supervisor schema version matches the Go runtime constant
embedded in bundle metadata.
+"""
+
+from __future__ import annotations
+
+import re
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).resolve().parent))
+
+from common_prek_utils import console, insert_documentation
+from lang_sdk_compat_matrix import (
+ AIRFLOW_ROOT_PATH,
+ LANG_SDKS,
+ README_MATRIX_FOOTER,
+ README_MATRIX_HEADER,
+ CapabilitiesDoc,
+ load_capabilities,
+ render_markdown_table,
+)
+
+SDK_ID = "go"
+GO_MESSAGES = AIRFLOW_ROOT_PATH / "go-sdk" / "pkg" / "execution" /
"messages.go"
+SCHEMA_VERSION_PATTERN = re.compile(
+
r'^\s*(?:const\s+)?SupervisorSchemaVersion(?:\s+[A-Za-z_]\w*(?:\.\w+)*)?\s*=\s*"(?P<version>[^"]+)"'
+)
+
+
+def read_go_schema_version() -> str | None:
+ """Read ``SupervisorSchemaVersion`` from the Go runtime source."""
+ for line in GO_MESSAGES.read_text().splitlines():
+ if match := SCHEMA_VERSION_PATTERN.match(line):
+ return match.group("version")
+ return None
+
+
+def check_schema_version(doc: CapabilitiesDoc) -> bool:
+ """Whether the manifest agrees with the schema version embedded in Go
bundle metadata."""
+ try:
+ go_version = read_go_schema_version()
+ except OSError as error:
+ console.print(f"[red]Could not read {GO_MESSAGES}: {error}[/]")
+ return False
+ if go_version is None:
+ console.print(
+ "[red]Could not read SupervisorSchemaVersion from
go-sdk/pkg/execution/messages.go: "
+ "the constant is missing, or its declaration no longer matches
this hook's pattern. "
+ "Restore the constant, or update SCHEMA_VERSION_PATTERN if the
declaration changed. "
+ "Editing go-sdk/capabilities.yaml cannot fix this.[/]"
+ )
+ return False
+ declared = doc["supervisor_schema_version"]
+ if go_version == declared:
+ return True
+ console.print(
+ f"[red]go-sdk/capabilities.yaml declares supervisor_schema_version
{declared!r} but "
+ f"pkg/execution/messages.go SupervisorSchemaVersion is {go_version!r}.
"
+ "Update capabilities.yaml to match.[/]"
+ )
+ return False
+
+
+def main() -> int:
+ sdk = next(entry for entry in LANG_SDKS if entry["id"] == SDK_ID)
+ doc = load_capabilities(sdk["capabilities_yaml"], expected_sdk=SDK_ID)
+ if not check_schema_version(doc):
+ return 1
+ changed = insert_documentation(
+ sdk["readme"],
+ render_markdown_table(doc),
+ README_MATRIX_HEADER,
+ README_MATRIX_FOOTER,
+ extra_information="the Go SDK compatibility matrix",
+ )
+ if changed:
+ console.print(
+ "[yellow]Regenerated the Go SDK compatibility matrix in
go-sdk/README.md; re-stage it.[/]"
+ )
+ return 1
+ return 0
+
+
+if __name__ in ("__main__", "__mp_main__"):
+ raise SystemExit(main())
diff --git a/scripts/tests/ci/prek/test_update_go_sdk_readme_matrix.py
b/scripts/tests/ci/prek/test_update_go_sdk_readme_matrix.py
new file mode 100644
index 00000000000..4bfdde594c8
--- /dev/null
+++ b/scripts/tests/ci/prek/test_update_go_sdk_readme_matrix.py
@@ -0,0 +1,102 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+from __future__ import annotations
+
+import pytest
+import yaml
+from ci.prek import lang_sdk_compat_matrix as matrix,
update_go_sdk_readme_matrix as hook
+
+SCHEMA_VERSION = "2026-06-16"
+
+
+def _doc() -> dict:
+ def entry(supported: bool) -> dict:
+ return {"supported": supported, "since": "3.3" if supported else None,
"note": ""}
+
+ return {
+ "sdk": "go",
+ "supervisor_schema_version": SCHEMA_VERSION,
+ "min_airflow_version": "3.3",
+ "states": {state: entry(True) for state, _ in matrix.STATE_DIMENSIONS},
+ "capabilities": {cap.name: entry(True) for cap in
matrix.CAPABILITY_DIMENSIONS},
+ }
+
+
+class TestMain:
+ @pytest.fixture
+ def wired(self, tmp_path, monkeypatch):
+ capabilities_yaml = tmp_path / "capabilities.yaml"
+ capabilities_yaml.write_text(yaml.safe_dump(_doc()))
+ readme = tmp_path / "README.md"
+
readme.write_text(f"intro\n\n{matrix.README_MATRIX_HEADER}\n{matrix.README_MATRIX_FOOTER}\n\noutro\n")
+ go_messages = tmp_path / "messages.go"
+ go_messages.write_text(f'package execution\n\nconst
SupervisorSchemaVersion = "{SCHEMA_VERSION}"\n')
+ monkeypatch.setattr(
+ hook,
+ "LANG_SDKS",
+ [{"id": "go", "capabilities_yaml": capabilities_yaml, "readme":
readme}],
+ )
+ monkeypatch.setattr(hook, "GO_MESSAGES", go_messages)
+ return readme
+
+ def test_generates_target_then_is_idempotent(self, wired):
+ assert hook.main() == 1
+ content = wired.read_text()
+ assert "| Dimension | Tier | Supported | Since | Notes |" in content
+ assert matrix.SUPPORTED_MARK in content
+ assert content.startswith("intro\n") and content.endswith("outro\n")
+
+ assert hook.main() == 0
+
+ def
test_schema_version_disagreeing_with_go_source_fails_without_writing(self,
wired):
+ hook.GO_MESSAGES.write_text('package execution\n\nconst
SupervisorSchemaVersion = "2020-01-01"\n')
+ assert hook.main() == 1
+ assert matrix.README_MATRIX_HEADER + "\n" +
matrix.README_MATRIX_FOOTER in wired.read_text()
+
+ def test_renamed_schema_version_constant_fails_without_writing(self,
wired):
+ hook.GO_MESSAGES.write_text(
+ 'package execution\n\nconst SupervisorSchemaVersionRenamed =
"2020-01-01"\n'
+ )
+ assert hook.main() == 1
+ assert matrix.README_MATRIX_HEADER + "\n" +
matrix.README_MATRIX_FOOTER in wired.read_text()
+
+ def test_schema_version_matches_go_source(self):
+ sdk = next(entry for entry in matrix.LANG_SDKS if entry["id"] ==
hook.SDK_ID)
+ doc = matrix.load_capabilities(sdk["capabilities_yaml"],
expected_sdk=hook.SDK_ID)
+ assert doc["supervisor_schema_version"] ==
hook.read_go_schema_version()
+
+ @pytest.mark.parametrize(
+ "declaration",
+ [
+ f'const (\n\tSupervisorSchemaVersion = "{SCHEMA_VERSION}"\n)',
+ f'const SupervisorSchemaVersion string = "{SCHEMA_VERSION}"',
+ f'const SupervisorSchemaVersion = "{SCHEMA_VERSION}" // current
schema',
+ f'const SupervisorSchemaVersion="{SCHEMA_VERSION}"',
+ ],
+ )
+ def test_reads_supported_schema_version_declarations(self, declaration,
tmp_path, monkeypatch):
+ go_messages = tmp_path / "messages.go"
+ go_messages.write_text(f"package execution\n\n{declaration}\n")
+ monkeypatch.setattr(hook, "GO_MESSAGES", go_messages)
+
+ assert hook.read_go_schema_version() == SCHEMA_VERSION
+
+ def test_missing_go_source_fails_without_writing(self, wired):
+ hook.GO_MESSAGES.unlink()
+
+ assert hook.main() == 1
+ assert matrix.README_MATRIX_HEADER + "\n" +
matrix.README_MATRIX_FOOTER in wired.read_text()