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

uranusjr 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 a527de7aba6 Agent skills for Java SDK and new language SDK (#68603)
a527de7aba6 is described below

commit a527de7aba64c0b8f99c6ed988d3d7d2f82e43c7
Author: Tzu-ping Chung <[email protected]>
AuthorDate: Mon Jun 22 15:08:20 2026 +0800

    Agent skills for Java SDK and new language SDK (#68603)
---
 .agents/skills/airflow-java-sdk/SKILL.md  | 134 ++++++++++++++++++++++++
 .agents/skills/airflow-new-sdk/SKILL.md   | 160 +++++++++++++++++++++++++++++
 contributing-docs/30_new_language_sdk.rst |  53 +++++++++-
 java-sdk/README.md                        | 165 +++++++++++++++++++++++++-----
 4 files changed, 485 insertions(+), 27 deletions(-)

diff --git a/.agents/skills/airflow-java-sdk/SKILL.md 
b/.agents/skills/airflow-java-sdk/SKILL.md
new file mode 100644
index 00000000000..8edb7426795
--- /dev/null
+++ b/.agents/skills/airflow-java-sdk/SKILL.md
@@ -0,0 +1,134 @@
+---
+name: airflow-java-sdk
+description: >
+  Guide for contributing to the Airflow Java SDK (AIP-108). Use this skill
+  whenever a contributor is working in the `java-sdk/` directory or on the Java
+  coordinator in `task-sdk/src/airflow/sdk/coordinators/java/` — whether they
+  want to add a feature, write tests, fix a bug, understand the architecture, 
or
+  prepare a PR. Trigger on phrases like "Java SDK", "JavaCoordinator",
+  "java-sdk", "annotation processor", "Builder.Task", "BundleBuilder", or
+  anything about running JVM tasks in Airflow.
+---
+
+<!-- SPDX-License-Identifier: Apache-2.0
+     https://www.apache.org/licenses/LICENSE-2.0 -->
+
+# Airflow Java SDK contributor guide
+
+The Java SDK lets Airflow tasks execute JVM code (Java, Kotlin, or any JVM 
language). You are helping
+a contributor work in one or both of these locations:
+
+- **`java-sdk/`** — the JVM-side library (Kotlin source, published to Maven)
+- **`task-sdk/src/airflow/sdk/coordinators/java/`** — the Python coordinator 
that launches the JVM subprocess
+
+Read these two documents early in every session — they contain the 
authoritative reference material:
+
+- `airflow-core/docs/authoring-and-scheduling/language-sdks/java.rst` — 
user-facing guide:
+  annotation vs. interface API, XCom type mapping, Gradle/Maven steps, 
coordinator config.
+- `java-sdk/README.md` — contributor guide: repository layout, detailed 
execution walkthrough,
+  Gradle + Breeze test commands, coding conventions, common tasks, and PR 
checklist.
+
+---
+
+## SDK package architecture
+
+The JVM-side library is split into two packages with distinct visibility rules:
+
+- **`org.apache.airflow.sdk`** — public, user-facing API. Classes here (e.g. 
`Client`, `Bundle`,
+  `BundleBuilder`, `Server`) are stable contracts that DAG authors and task 
implementers import
+  directly. Changes to this package are breaking changes.
+- **`org.apache.airflow.sdk.execution`** — internal implementation detail. 
Everything in this
+  package (`CoordinatorComm`, `LogSender`, `Log`, `Client` in `execution/`, 
generated schema
+  models, etc.) is not intended to be imported by users. It may change between 
releases without
+  notice.
+
+When reviewing or writing code, enforce this boundary: user task code and 
`BundleBuilder`
+subclasses must only import from `org.apache.airflow.sdk`; any import of
+`org.apache.airflow.sdk.execution.*` in user-facing API surface is a red flag.
+
+---
+
+## Bundle composition and coordinator discovery
+
+A **bundle** is a directory of JAR files (typically `build/bundle/`) placed on 
the coordinator's
+`jars_root`. The coordinator scans the directory at task-dispatch time to find:
+
+1. **`Main-Class`** (standard JAR manifest attribute) — the fully-qualified 
class name of the
+   entry point that the coordinator invokes with `java -classpath … 
<Main-Class> --comm … --logs …`.
+   This must be a class with a `public static void main(String[] args)` 
method; the Gradle plugin
+   `org.apache.airflow.sdk` writes it automatically from `airflowBundle { 
mainClass = "…" }` and
+   validates that the class exists and has the right signature at build time.
+
+2. **`Airflow-Supervisor-Schema-Version`** (Airflow-specific manifest 
attribute) — the wire
+   protocol version the JVM side expects when talking to the Python 
supervisor. In fat-JAR mode
+   (the default), the Gradle plugin reads this value from the `airflow-sdk` 
JAR in
+   `runtimeClasspath` and copies it into the shadow JAR manifest. In thin-JAR 
mode (`fatJar =
+   false`), the value stays in the `airflow-sdk` JAR deployed alongside the 
bundle JAR.
+
+The Python coordinator (`JavaCoordinator`) scans every JAR under `jars_root` 
with
+`_JarInfo.find()`, reads `META-INF/MANIFEST.MF` out of each ZIP, and collects 
`Main-Class` and
+`Airflow-Supervisor-Schema-Version` from whichever JARs carry them. The 
resolved schema version
+is then passed as the `schema_version` return value from 
`_build_execute_task_command`, which
+the base `SubprocessCoordinator` uses to negotiate the supervisor wire 
protocol.
+
+If `main_class` is set explicitly on the `JavaCoordinator` instance (via 
`[sdk] coordinators`
+kwargs), the scan uses it as a filter; otherwise the first JAR with a 
`Main-Class` attribute
+wins. Either way, `Airflow-Supervisor-Schema-Version` must be present in at 
least one JAR in
+`jars_root` or startup fails.
+
+---
+
+## Key files to know
+
+| File | Purpose |
+|---|---|
+| `java-sdk/sdk/.../Client.kt` | Public API (Variables, Connections, XCom) |
+| `java-sdk/sdk/.../execution/Client.kt` | Supervisor wire calls |
+| `java-sdk/sdk/.../execution/Comm.kt` | 4-byte-prefix MessagePack framing |
+| `java-sdk/sdk/.../Server.kt` | Entry-point; drives the execution loop |
+| `java-sdk/processor/.../BuilderProcessor.kt` | Kapt annotation processor |
+| `java-sdk/plugin/.../AirflowSdkPlugin.kt` | Gradle bundle plugin |
+| `task-sdk/.../coordinators/java/coordinator.py` | Python side — spawns the 
JVM |
+| `task-sdk/.../schema/schema.json` | Wire protocol definition (both sides) |
+
+---
+
+## Running tests
+
+Always use `./gradlew` from inside `java-sdk/`; never run Gradle via apt's 
`gradle`.
+See `java-sdk/README.md#testing` for the full list of Gradle commands.
+
+For the Python coordinator, use Breeze (never `pytest` directly on the host):
+
+```bash
+breeze testing task-sdk-tests -- task_sdk/coordinators/java
+```
+
+End-to-end test suite:
+
+```bash
+E2E_TEST_MODE=java_sdk uv run --project airflow-e2e-tests pytest \
+    tests/airflow_e2e_tests/java_sdk_tests/ -xvs
+```
+
+---
+
+## Updating the Python coordinator
+
+`coordinator.py` extends `SubprocessCoordinator`. The only method subclasses 
must implement is
+`_build_execute_task_command`, which returns `(argv, schema_version)`. Look at 
the existing
+implementation for how `jars_root`, `java_executable`, `jvm_args`, and 
`main_class` are
+assembled into the command. Do not reach into the JVM process from Python 
beyond what this
+method provides.
+
+---
+
+## Upgrading Supervisor Schema client
+
+When upgrading to a newer Supervisor Schema version:
+
+- Regenerate models with `./gradlew generateJsonSchema2Pojo`
+- Modify `execution/Client.kt` to handle changes
+
+The `java-sdk/README.md#contributing` section walks through the full "adding a 
new Client
+method" sequence step by step.
diff --git a/.agents/skills/airflow-new-sdk/SKILL.md 
b/.agents/skills/airflow-new-sdk/SKILL.md
new file mode 100644
index 00000000000..6534be502d5
--- /dev/null
+++ b/.agents/skills/airflow-new-sdk/SKILL.md
@@ -0,0 +1,160 @@
+---
+name: airflow-new-sdk
+description: >
+  Guide for implementing a brand-new language SDK for Airflow (AIP-108). Use
+  this skill when a contributor wants to add support for a new programming
+  language — designing the Python coordinator, implementing the wire protocol 
in
+  the target language, writing the bundle format, and structuring the PR. 
Trigger
+  on phrases like "new language SDK", "new SDK", "add support for [language]",
+  "implement coordinator for", "SubprocessCoordinator", "BaseCoordinator",
+  "new runtime", "AFBNDL01", "supervisor schema", or anything about bringing a
+  new language into the Airflow executor ecosystem.
+---
+
+<!-- SPDX-License-Identifier: Apache-2.0
+     https://www.apache.org/licenses/LICENSE-2.0 -->
+
+# Implementing a new language SDK for Airflow
+
+## Start here
+
+**Read `contributing-docs/30_new_language_sdk.rst` first.** It is the
+authoritative contributor guide for this topic — coordinator base class 
choices,
+wire protocol spec, bundle footer format, and testing requirements. Everything
+in this skill builds on top of it, not alongside it.
+
+---
+
+## Repository layout
+
+Every new SDK needs two things. The coordinator (Python) goes here:
+
+```
+task-sdk/src/airflow/sdk/coordinators/<language>/
+    __init__.py        # re-export + module docstring
+    coordinator.py     # subclass of SubprocessCoordinator or BaseCoordinator
+task-sdk/tests/coordinators/<language>/
+    test_coordinator.py
+task-sdk/tests/integration/coordinators/<language>/
+    test_integration.py   # requires Breeze
+```
+
+The language SDK itself lives in a top-level `<language>-sdk/` directory (like
+`java-sdk/` and `go-sdk/`). For native-executable languages using
+`ExecutableCoordinator`, no coordinator code is needed at all.
+
+---
+
+## Choosing the right base class — quick guide
+
+```
+Does the runtime compile to a self-contained native executable?
+  YES → Use ExecutableCoordinator (zero Python to write).
+        Append an AFBNDL01 footer with a packer tool (see go-sdk reference).
+  NO  →
+    Does it start via a single shell command (node, ruby, dotnet, …)?
+      YES → Subclass SubprocessCoordinator.
+            Implement _build_execute_task_command only (see 
30_new_language_sdk.rst).
+      NO  →
+        Subclass BaseCoordinator and implement execute_task from scratch.
+        (Rare: gRPC daemons, shared memory, persistent processes.)
+```
+
+The full rationale, method signature, and socket lifecycle for each path are in
+`30_new_language_sdk.rst`. Read that section before writing any code.
+
+---
+
+## Reference implementations to study
+
+| What to study | Where |
+|---|---|
+| SubprocessCoordinator base class | 
`task-sdk/src/airflow/sdk/coordinators/_subprocess.py` |
+| Java coordinator (SubprocessCoordinator subclass) | 
`task-sdk/src/airflow/sdk/coordinators/java/coordinator.py` |
+| ExecutableCoordinator (native bundles) | 
`task-sdk/src/airflow/sdk/coordinators/executable/coordinator.py` |
+| Wire protocol in Kotlin | 
`java-sdk/sdk/src/main/kotlin/org/apache/airflow/sdk/execution/` |
+| Wire protocol in Go | `go-sdk/pkg/execution/` |
+| AFBNDL01 footer (Go reference) | `go-sdk/internal/bundlefooter/`, 
`task-sdk/docs/executable-bundle-spec.rst` |
+| All message types and field specs | 
`task-sdk/src/airflow/sdk/execution_time/schema/schema.json` |
+
+The Java and Go implementations are the two production reference points. When
+implementing a new SDK, read whichever matches the target language's runtime
+model (JVM/interpreted → Java; native/compiled → Go).
+
+---
+
+## Logging
+
+The **Logging** section of `30_new_language_sdk.rst` is the spec: the `--logs`
+JSON record format, the level names, and the `AIRFLOW__LOGGING__*` environment
+variables. Read it first. A few language-neutral details it leaves out:
+
+- **Level values.** Levels follow Python's `logging` scale, so thresholds line
+  up with the rest of Airflow: `CRITICAL=50`, `ERROR=40`, `WARNING=30`,
+  `INFO=20`, `DEBUG=10`, `NOTSET=0`.
+- **Parsing `NAMESPACE_LEVELS`.** Split the value on `[\s,]+`, then split each
+  item on `=` into `(logger_name, level_name)`. Emit a record only when its
+  level is `>=` the matching `logger_name` threshold, or the global threshold
+  when no per-logger entry matches.
+- **Don't drop late logs.** Connect the `--logs` socket early and keep it open
+  until the `--comm` channel has finished; otherwise records emitted during
+  teardown can be lost.
+- **Extra config keys.** The runtime can't read Airflow's config, so if your 
SDK
+  needs `[logging]` settings beyond the two above, propagate them as 
environment
+  variables from your coordinator's `start`, the same way.
+
+For how a given language wires its native logging frameworks into this channel,
+read that SDK's source (e.g. `java-sdk/`) rather than reproducing it here.
+
+### E2E test suite
+
+Create two files mirroring `java_sdk_tests/` or `go_sdk_tests/`:
+
+```
+airflow-e2e-tests/tests/airflow_e2e_tests/<language>_sdk_tests/
+    __init__.py
+    test_<language>_sdk_dag.py
+```
+
+The test file should:
+
+- Trigger the SDK's example Dag (the one added to `<language>-sdk/dags/` or
+  equivalent) via `AirflowClient.trigger_dag`.
+- Wait for the run to finish with `AirflowClient.wait_for_dag_run`.
+- Assert that each SDK task instance reached `"success"`.
+- Assert at least one XCom value — confirms the full round-trip from task
+  return value through the supervisor to the XCom store.
+- Assert structured logs where the SDK emits them (see the Go suite for an
+  example of log-content assertions).
+
+Run locally with:
+
+```bash
+E2E_TEST_MODE=<language>_sdk uv run --project airflow-e2e-tests pytest \
+    tests/airflow_e2e_tests/<language>_sdk_tests/ -xvs
+```
+
+The Java (`java_sdk_tests/test_java_sdk_dag.py`) and Go
+(`go_sdk_tests/test_go_sdk_dag.py`) suites are the reference implementations.
+
+
+---
+
+## PR checklist (items not covered by 30_new_language_sdk.rst)
+
+The `30_new_language_sdk.rst` guide covers coordinator placement, wire protocol
+implementation, and testing. These additional items belong in the same PR:
+
+1. `task-sdk/src/airflow/sdk/coordinators/<language>/__init__.py` — short
+   module docstring and `__all__` re-export of the coordinator class.
+2. `airflow-core/docs/authoring-and-scheduling/language-sdks/<language>.rst` —
+   user-facing doc following the structure of `java.rst` or `go.rst`.
+3. `airflow-core/docs/authoring-and-scheduling/language-sdks/index.rst` — add
+   the new doc to the toctree.
+4. `airflow-core/newsfragments/<PR>.feature.rst` — a new language is always
+   user-visible; add a newsfragment.
+5. CI wiring — check `dev/breeze/src/airflow_breeze/utils/selective_checks.py`
+   to confirm `<language>-sdk/` changes trigger the right test group. Add if
+   missing.
+6. E2E tests — add a `<language>_sdk_tests/` suite under
+   `airflow-e2e-tests/tests/airflow_e2e_tests/`. See below.
diff --git a/contributing-docs/30_new_language_sdk.rst 
b/contributing-docs/30_new_language_sdk.rst
index e83194b6e45..f1fc2245909 100644
--- a/contributing-docs/30_new_language_sdk.rst
+++ b/contributing-docs/30_new_language_sdk.rst
@@ -128,7 +128,7 @@ string that identifies the schema revision.
 The schema evolves over time, so the SDK must tell the supervisor what API
 version it was built against for this bridging to work. How the target-language
 SDK uses ``schema.json`` to generate or validate its message types is covered 
in
-`Language SDK`_ below.
+`Language SDK (target language)`_ below.
 
 Adding a new coordinator class
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -237,6 +237,57 @@ match responses to requests. The SDK MUST correlate by 
``id`` whenever multiple
 requests can be outstanding simultaneously (e.g. when tasks are executed
 concurrently, or when a single task issues multiple requests).
 
+Logging
+~~~~~~~
+
+The SDK should wire the native logging mechanism (the equivalent of Python's
+``logging``, and/or whatever is popular in the ecosystem) to send logs emitted
+during execution to Airflow's task log store, so they appear in the UI with the
+rest of the output. Records travel over the ``--logs`` socket introduced in
+`Startup`_.
+
+Log messages should be **newline-delimited JSON**. Each log is a UTF-8 encoded
+JSON object in one line, terminated by a newline character (``\n``). Each 
object
+is a structlog-style event:
+
+.. code-block:: text
+
+    {"event": "Starting extraction", "level": "info", "logger": 
"com.example.SalesPipeline", "timestamp": "2026-06-22T12:00:00", "rows": 42}
+
+* ``event`` is the log message.
+* ``level`` is the level **name** in lower case. The following levels are
+  supported: ``critical``, ``error``, ``warning``, ``info``, ``debug``, and
+  ``notset``. A line carrying any other level is dropped.
+* ``timestamp`` is an ISO-8601 timestamp.
+* Any remaining keys are forwarded as structured fields on the log record.
+
+See Python documentation on logging for details on log levels. Since log level
+definitions on logging tools differ, the SDK should implement appropriate
+translation logic to convert the levels.
+
+An SDK SHOULD integrate with the language's established logging frameworks
+rather than invent a new API, so task authors keep using the tools they already
+know. The Java SDK, for example, ships adapters for many popular logging APIs,
+including ``System.Logger`` and SLF4J.
+
+The supervisor should not perform filtering on this socket; it should record
+every line it receives. Instead, the supervisor should provide necessary
+information for the SDK to perform filtering. ``SubprocessCoordinator`` (and
+``ExecutableCoordinator`` since its a subclass) does this by setting the
+following environment variables when launching the foreign runtime subprocess:
+
+* ``AIRFLOW__LOGGING__LOGGING_LEVEL`` is the global threshold read configured 
in
+  ``[logging] logging_level``, e.g. ``INFO``.
+* ``AIRFLOW__LOGGING__NAMESPACE_LEVELS`` contains per-logger overrides from
+  ``[logging] namespace_levels`` e.g. ``sqlalchemy=INFO, botocore=WARNING``.
+
+See configuration documentation on the format and meaning of these
+configurations.
+
+The SDK MUST read these at startup and drop any record below the applicable
+threshold *before* sending it, so that filtering matches what a Python task on
+the same deployment would produce.
+
 Error handling
 ~~~~~~~~~~~~~~
 
diff --git a/java-sdk/README.md b/java-sdk/README.md
index 809c1bef9dd..788cbeb4805 100644
--- a/java-sdk/README.md
+++ b/java-sdk/README.md
@@ -53,11 +53,6 @@ After the build is successful, you should be able to see 
directories in `~/.m2/r
 
 Now `cd example` into the example project, and
 
-* Put the [DAG with stub tasks](./example/src/resources/dags) to somewhere 
Airflow can find.
-
-* Ensure the `java` command is available in the same environment the Airflow
-  task worker is in.
-
 * Package the example to `./example/build/bundle`
 
   ```bash
@@ -65,6 +60,11 @@ Now `cd example` into the example project, and
   ../gradlew bundle
   ```
 
+* Put the [DAG with stub tasks](./example/src/resources/dags) to somewhere 
Airflow can find.
+
+* Ensure the `java` command is available in the same environment the Airflow
+  task worker is in.
+
 * Configure Airflow to route tasks in the *java* queue to be run with Java:
 
   ```bash
@@ -193,31 +193,144 @@ Verify all artifacts have been released correctly to the
 Check *Updated by* (should be your ID), *Uploaded Date*, and *Last Modified*.
 
 
-## Technical Details
+## Contributing
 
-The user uses the SDK to implement a Java application that implements task
-methods, and metadata on which DAG and task each method should be used
-for.
+The user implements a Java application containing task methods annotated (or
+registered) with the SDK. The application is packaged as a bundle and placed
+where Airflow can find it.
 
-When the Airflow Supervisor identifies a task should be run with Java, it
-launches the Java application as a subprocess. The Java application accepts
-flags `--comm` and `--logs` from the command line to identify TCP sockets it
-should connect to, and communicates with the Supervisor through these channels
-during execution.
+When the Airflow supervisor identifies that a task should run with Java, it
+launches the JVM application as a subprocess. The flow is:
 
-1. On connection, the Supervisor immediately sends a StartupDetails message
-   through the comm socket.
-2. The Java application finds and executes the relevant method.
-3. During execution, the Java application uses the comm socket to retrieve
-   information (e.g. Variable) from, and send data (e.g. XCom) to Airflow.
-4. The Java application informs the comm socket to tell the Supervisor the
-   task's terminal state.
-5. The Java application exits.
+1. `JavaCoordinator.execute_task()` (Python) scans `jars_root`, builds the
+   classpath, and spawns `java -cp <jars> <MainClass> --comm=<host>:<port>
+   --logs=<host>:<port>`.
+2. `Server.kt` connects to both sockets immediately on startup.
+3. The supervisor sends a `StartupDetails` MessagePack message; the JVM reads
+   it, looks up the matching task by `dag_id` + `task_id`, and calls the
+   user's task method.
+4. During execution the JVM sends requests to the supervisor (GetVariable,
+   GetConnection, GetXCom, SetXCom, etc.) and the supervisor responds. All
+   frames are a 4-byte big-endian length prefix followed by a MessagePack
+   payload.
+5. On completion (or exception) the JVM sends a `TaskState` message and closes
+   the socket. The JVM process then exits.
 
-During the Java application's lifetime, it also sends log messages generated by
-the SDK (not user code) through the logs socket, so the Supervisor can append
-them to Airflow logs.
+Log messages produced by the SDK (not by user code) are forwarded over the
+`--logs` socket so the supervisor can append them to Airflow's log store.
 
-Communication uses the same formats as the Python-based processes.
+The wire protocol is defined in
+`task-sdk/src/airflow/sdk/execution_time/schema/schema.json`.
+`execution/Comm.kt` implements the framing layer. Adding a new message type
+requires changes in **both** `schema.json` (Python side) and
+`execution/Comm.kt` + `execution/Client.kt` (JVM side).
 
 See [Architectural Design Records](./adr) in the `adr` directory to learn more.
+
+### Repository layout
+
+```
+java-sdk/
+├── sdk/          # Core library: public API (org.apache.airflow.sdk) and 
internal
+│                 #   execution layer (org.apache.airflow.sdk.execution)
+├── processor/    # Annotation processor that generates *Builder classes
+├── plugin/       # Gradle plugin (org.apache.airflow.sdk) — bundle task, 
manifest
+│                 #   attribute injection, and verifyBundleMainClass
+├── bom/          # Bill of Materials POM so consumers can import all SDK 
artifacts
+│                 #   at a consistent version
+├── slf4j/        # SLF4J logging provider; routes SLF4J calls to the Airflow 
log store
+├── jul/          # java.util.logging handler; routes JUL records to the 
Airflow log store
+├── jpl/          # Java Platform Logging provider (System.Logger, JEP 264); 
routes JPL
+│                 #   calls to the Airflow log store
+├── log4j2/       # Log4j 2 appender; routes Log4j 2 events to the Airflow log 
store
+├── example/      # End-to-end example bundle (annotation + interface APIs, 
Java source)
+├── adr/          # Architectural Decision Records for the Java SDK
+└── buildSrc/     # Shared Gradle convention plugins (Java version, lint, etc.)
+```
+
+The Python coordinator that launches the JVM subprocess lives outside this 
directory:
+
+```
+task-sdk/src/airflow/sdk/coordinators/java/   # JavaCoordinator 
(SubprocessCoordinator subclass)
+task-sdk/tests/coordinators/java/             # Python-side unit and 
integration tests
+```
+
+### Testing
+
+```bash
+# Run all JVM tests
+./gradlew test
+
+# Run a specific test class
+./gradlew :sdk:test --tests "org.apache.airflow.sdk.execution.CommTest"
+
+# Smoke-check the example bundle
+./gradlew :example:bundle
+```
+
+For the Python coordinator, use Breeze (never run pytest on the host directly):
+
+```bash
+breeze testing task-sdk-tests -- task_sdk/coordinators/java
+```
+
+End-to-end tests that exercise a real Airflow environment:
+
+```bash
+E2E_TEST_MODE=java_sdk uv run --project airflow-e2e-tests pytest \
+    tests/airflow_e2e_tests/java_sdk_tests/ -xvs
+```
+
+### Coding conventions
+
+- All SDK and processor source is **Kotlin**; Java is the *public API target*,
+  not the implementation language.
+- Keep `sdk/src/main/kotlin/` (the public API surface) free of internal
+  implementation details; those belong in the `execution/` sub-package.
+- The annotation processor (`BuilderProcessor.kt`) uses `kapt`. When adding a
+  new annotation, define it in `Builder.kt`, handle it in
+  `BuilderProcessor.kt`, and add a golden-output test in
+  `processor/src/test/kotlin/`.
+- The Python coordinator subclasses `SubprocessCoordinator`. Do not reach into
+  the JVM process from Python beyond what `_build_execute_task_command`
+  provides.
+- Run `./gradlew ktLintCheck spotlessCheck` (or `ktLintFormat spotlessApply`)
+  before submitting — the project enforces Kotlin and Java formatting.
+- All new files need the Apache License header.
+
+### Common tasks
+
+**Adding a new `Client` method** (e.g. a new Airflow API call):
+
+1. Regenerate POJO classes from `schema.json` if the message type is new.
+2. Add Kotlin request/response data classes in `execution/Comm.kt` or a new 
file.
+3. Add the public-facing method to `Client.kt` which delegates to
+   `execution/Client.kt` for the supervisor wire call.
+4. Write a unit test in `sdk/src/test/kotlin/.../ClientTest.kt` mocking the
+   socket layer.
+5. Update `airflow-core/docs/authoring-and-scheduling/language-sdks/java.rst`
+   if the change is user-visible.
+
+**Adding a new annotation**:
+
+1. Define the annotation interface in `Builder.kt`.
+2. Handle it in `BuilderProcessor.kt` — generate the appropriate code in the
+   `*Builder` class.
+3. Add a test in `BuilderTest.kt` with expected generated output.
+4. Update the annotation table in `java.rst`.
+
+**Fixing a framing or protocol bug**: focus on `execution/Comm.kt` and
+`execution/Frame.kt`. `CommTest.kt` covers encode/decode round-trips; add a
+regression test reproducing the bug before fixing it.
+
+### PR checklist
+
+- Run `./gradlew build test` (JVM) and the relevant pytest suite (Python
+  coordinator).
+- Confirm the example bundle still compiles (see "Running the example" above up
+  to the bundling step)
+- If `schema.json` changed, verify both sides (JVM + Python) handle the
+  new/changed fields.
+- Add or update tests for every changed behaviour.
+- For user-visible changes to `task-sdk/`, add a newsfragment under
+  `airflow-core/newsfragments/`.

Reply via email to