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

spetz pushed a commit to branch swift-release-pipeline
in repository https://gitbox.apache.org/repos/asf/iggy.git

commit 875d4091f4c1eaf4affbd65fd5643b79e990e3a1
Author: spetz <[email protected]>
AuthorDate: Mon Nov 2 21:52:38 2026 +0100

    feat(swift): publish the SDK to SwiftPM from the repository root
---
 .github/actions/swift/post-merge/action.yml        | 107 +++++++++++
 .github/actions/swift/pre-merge/action.yml         |  31 ++++
 .github/config/components.yml                      |   2 +
 .github/config/publish.yml                         |   7 +
 .github/workflows/post-merge.yml                   |   5 +-
 .github/workflows/publish.yml                      |  27 ++-
 .gitignore                                         |   4 +
 .spi.yml                                           |  22 +++
 AGENTS.md                                          |   2 +-
 Package.swift                                      |  57 ++++++
 foreign/swift/LICENSE                              | 201 +++++++++++++++++++++
 foreign/swift/NOTICE                               |  12 ++
 foreign/swift/README.md                            |  22 ++-
 foreign/swift/Sources/Iggy/Iggy.swift              |   5 +-
 foreign/swift/Tests/IggyTests/Fixtures/golden.json |   8 +-
 scripts/bump-version.sh                            |  61 ++++++-
 scripts/prepare-release.sh                         |   1 +
 17 files changed, 548 insertions(+), 26 deletions(-)

diff --git a/.github/actions/swift/post-merge/action.yml 
b/.github/actions/swift/post-merge/action.yml
new file mode 100644
index 000000000..f849b277e
--- /dev/null
+++ b/.github/actions/swift/post-merge/action.yml
@@ -0,0 +1,107 @@
+# 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.
+
+name: swift-post-merge
+description: Swift post-merge tag preparation github iggy actions
+
+inputs:
+  version:
+    description: "Version for tagging (without the 'v' prefix)"
+    required: true
+  tag:
+    description: "Git tag the publish workflow creates for this version"
+    required: true
+  dry_run:
+    description: "Dry run mode"
+    required: false
+    default: "false"
+
+runs:
+  using: "composite"
+  steps:
+    # Build metadata cannot distinguish versions with the same SwiftPM 
precedence.
+    - name: Validate version and tag
+      shell: bash
+      env:
+        VERSION: ${{ inputs.version }}
+        TAG: ${{ inputs.tag }}
+      run: |
+        set -euo pipefail
+        PRERELEASE='(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)'
+        
SEMVER="^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-${PRERELEASE}(\.${PRERELEASE})*)?$"
+        if ! [[ "$VERSION" =~ $SEMVER ]]; then
+          echo "❌ Invalid Swift SDK version: $VERSION"
+          echo "Expected X.Y.Z or X.Y.Z-prerelease, without build metadata"
+          exit 1
+        fi
+        if [ "$TAG" != "v${VERSION}" ]; then
+          echo "❌ Tag $TAG does not match version $VERSION, expected 
v${VERSION}"
+          exit 1
+        fi
+        echo "✅ Version $VERSION, tag $TAG"
+
+    # SwiftPM consumers receive the root manifest and legal files with the 
repository.
+    - name: Check package and legal files
+      shell: bash
+      run: |
+        set -euo pipefail
+        for file in Package.swift LICENSE NOTICE foreign/swift/LICENSE 
foreign/swift/NOTICE; do
+          if [ ! -f "$file" ]; then
+            echo "❌ Missing $file"
+            exit 1
+          fi
+        done
+        echo "✅ Root manifest and legal files present"
+
+    - name: Setup Swift
+      uses: ./.github/actions/utils/setup-swift
+      with:
+        cache-key: "release"
+        package-path: "."
+
+    # An unpinned release build checks dependency resolution before the tag 
exists.
+    - name: Build the published package
+      shell: bash
+      run: swift build -c release --product Iggy
+
+    - name: Summary
+      shell: bash
+      env:
+        VERSION: ${{ inputs.version }}
+        TAG: ${{ inputs.tag }}
+        DRY_RUN: ${{ inputs.dry_run }}
+      run: |
+        {
+          echo "## 🏷️ Swift Package Tag"
+          echo ""
+          echo "| Property | Value |"
+          echo "|----------|-------|"
+          echo "| **Version** | \`${VERSION}\` |"
+          echo "| **Git Tag** | \`${TAG}\` |"
+          echo "| **Package URL** | \`https://github.com/${{ github.repository 
}}.git\` |"
+          echo ""
+          echo "\`\`\`swift"
+          echo ".package(url: \"https://github.com/${{ github.repository 
}}.git\", exact: \"${VERSION}\")"
+          echo ".product(name: \"Iggy\", package: \"iggy\")"
+          echo "\`\`\`"
+          echo ""
+          if [ "$DRY_RUN" = "true" ]; then
+            echo "**Note:** Dry run, no tag will be created"
+          else
+            echo "**Note:** The publish workflow creates the tag after this 
step"
+          fi
+        } >> "$GITHUB_STEP_SUMMARY"
diff --git a/.github/actions/swift/pre-merge/action.yml 
b/.github/actions/swift/pre-merge/action.yml
index 2043b5e1a..ef88b400f 100644
--- a/.github/actions/swift/pre-merge/action.yml
+++ b/.github/actions/swift/pre-merge/action.yml
@@ -45,6 +45,37 @@ runs:
         for package in examples/swift/getting-started bdd/swift/tests; do
           swift format lint --strict --recursive --configuration 
foreign/swift/.swift-format "$package/Sources" "$package/Package.swift"
         done
+        swift format lint --strict --configuration foreign/swift/.swift-format 
Package.swift
+
+    # Consumers and CI must build the same library from the two manifests.
+    - name: Check the root manifest matches the SDK manifest
+      shell: bash
+      if: inputs.task == 'lint'
+      run: |
+        set -euo pipefail
+        root_manifest=$(swift package --package-path . dump-package)
+        sdk_manifest=$(swift package --package-path foreign/swift dump-package)
+        if ! jq -e '.products | length == 1 and .[0].name == "Iggy"' <<< 
"$root_manifest" >/dev/null \
+          || ! jq -e '.targets | length == 1 and .[0].name == "Iggy"' <<< 
"$root_manifest" >/dev/null; then
+          echo "❌ Root Package.swift must declare only the Iggy library and 
target"
+          exit 1
+        fi
+        root_path=$(jq -r '.targets[] | select(.name == "Iggy") | .path' <<< 
"$root_manifest")
+        if [ "$root_path" != "foreign/swift/Sources/Iggy" ]; then
+          echo "❌ Root Package.swift builds Iggy from '$root_path', expected 
foreign/swift/Sources/Iggy"
+          exit 1
+        fi
+        sdk_path=$(jq -r '.targets[] | select(.name == "Iggy") | .path // 
"Sources/Iggy"' <<< "$sdk_manifest")
+        if [ "$sdk_path" != "Sources/Iggy" ]; then
+          echo "❌ SDK Package.swift builds Iggy from '$sdk_path', expected 
Sources/Iggy"
+          exit 1
+        fi
+        projection='del(.name, .packageKind) | .products |= map(select(.name 
== "Iggy")) | .targets |= map(select(.name == "Iggy") | del(.path))'
+        if ! diff <(jq -S "$projection" <<< "$root_manifest") <(jq -S 
"$projection" <<< "$sdk_manifest"); then
+          echo "❌ Package.swift and foreign/swift/Package.swift disagree, see 
the diff above"
+          exit 1
+        fi
+        echo "✅ Root Package.swift matches foreign/swift/Package.swift"
 
     # A warning-free build is the lint gate the Swift compiler provides: the
     # SDK builds in Swift 6 language mode with strict concurrency checking.
diff --git a/.github/config/components.yml b/.github/config/components.yml
index 7a217cb4a..619243efb 100644
--- a/.github/config/components.yml
+++ b/.github/config/components.yml
@@ -357,6 +357,8 @@ components:
       - "foreign/swift/**"
       - "bdd/swift/**"
       - "examples/swift/**"
+      - "Package.swift"
+      - ".spi.yml"
     tasks: ["lint", "test", "build", "build-macos", "golden", "e2e"]
 
   # Shared BDD infrastructure that affects all BDD tests
diff --git a/.github/config/publish.yml b/.github/config/publish.yml
index c54ce712d..a889834d7 100644
--- a/.github/config/publish.yml
+++ b/.github/config/publish.yml
@@ -136,3 +136,10 @@ components:
     registry: none
     version_file: "foreign/go/contracts/version.go"
     version_regex: 'const\s+Version\s*=\s*"([^"]+)"'
+
+  # ── Swift (tag only, SwiftPM resolves the root Package.swift) ──────────────
+  sdk-swift:
+    tag_pattern: "^v([0-9]+\\.[0-9]+\\.[0-9]+(?:-[0-9A-Za-z.-]+)?)$"
+    registry: swiftpm
+    version_file: "foreign/swift/Sources/Iggy/Iggy.swift"
+    version_regex: 'sdkVersion\s*=\s*"([^"]+)"'
diff --git a/.github/workflows/post-merge.yml b/.github/workflows/post-merge.yml
index 4cfe07c3b..fa1b610ca 100644
--- a/.github/workflows/post-merge.yml
+++ b/.github/workflows/post-merge.yml
@@ -16,7 +16,8 @@
 # under the License.
 
 # Auto-publish: detects pre-release crate/SDK versions without tags
-# and publishes them to crates.io, Docker Hub, PyPI, npm, Maven, NuGet.
+# and publishes them to crates.io, Docker Hub, PyPI, npm, Maven, NuGet,
+# or as git tags for Go and Swift.
 # Runs on every push to master.
 
 name: Post-merge
@@ -138,7 +139,7 @@ jobs:
 
           # Check SDKs for pre-release versions without tags
           SDKS_TO_PUBLISH=""
-          for sdk in sdk-python sdk-node sdk-java sdk-csharp sdk-go; do
+          for sdk in sdk-python sdk-node sdk-java sdk-csharp sdk-go sdk-swift; 
do
             VERSION=$(scripts/extract-version.sh "$sdk")
             TAG=$(scripts/extract-version.sh "$sdk" --tag)
 
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 9ba31d109..f2aa6521e 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -49,7 +49,7 @@ on:
         required: false
         default: ""
       publish_other:
-        description: "Other SDKs to publish (comma-separated: python, node, 
java, csharp, go)"
+        description: "Other SDKs to publish (comma-separated: python, node, 
java, csharp, go, swift)"
         type: string
         required: false
         default: ""
@@ -258,7 +258,7 @@ jobs:
 
             // Parse other SDKs
             ('${{ inputs.publish_other }}').split(',').map(s => 
s.trim()).filter(Boolean).forEach(sdk => {
-              if (['python','node','java','csharp','go'].includes(sdk)) {
+              if 
(['python','node','java','csharp','go','swift'].includes(sdk)) {
                 wants.push(`sdk-${sdk}`);
               } else {
                 core.warning(`Unknown SDK: ${sdk}`);
@@ -272,7 +272,8 @@ jobs:
               npm: 'node',
               maven: 'java',
               nuget: 'csharp',
-              none: 'go'
+              none: 'go',
+              swiftpm: 'swift'
             }[entry.registry] || 'unknown');
 
             const targets = [];
@@ -987,7 +988,7 @@ jobs:
             Commit: ${{ needs.validate.outputs.commit }}
             Released by: GitHub Actions (workflow ${{ github.run_id }})
 
-  # Non-Docker, non-Rust publishing (Python, Node, Java, C#, Go SDKs)
+  # Non-Docker, non-Rust publishing (Python, Node, Java, C#, Go, Swift SDKs)
   # Note: This job runs in parallel with Docker publishing - no dependency 
between them
   publish:
     name: ${{ matrix.name }}
@@ -1156,14 +1157,25 @@ jobs:
           version: ${{ steps.ver.outputs.version }}
           dry_run: ${{ inputs.dry_run }}
 
+      # ─────────────────────────────────────────
+      # Swift Package (Tag-only, root Package.swift)
+      # ─────────────────────────────────────────
+      - name: Prepare Swift tag
+        if: matrix.type == 'swift'
+        uses: ./.github/actions/swift/post-merge
+        with:
+          version: ${{ steps.ver.outputs.version }}
+          tag: ${{ steps.ver.outputs.tag }}
+          dry_run: ${{ inputs.dry_run }}
+
       # ─────────────────────────────────────────
       # Wait gates: do not push the git tag until the registry is actually
       # serving the new version. The Rust path already does this via
       # wait-for-crate; mirroring it here closes the "artifact published,
       # tag never created" rc1 failure class for foreign SDKs.
       #
-      # Go has no registry wait: the tag IS the release (the Go proxy
-      # fetches from git on demand).
+      # Go and Swift have no registry wait: the tag IS the release (the Go
+      # proxy and SwiftPM fetch from git on demand).
       # ─────────────────────────────────────────
       - name: Wait for PyPI availability
         if: |
@@ -1265,7 +1277,7 @@ jobs:
       # releases) and the "no tag_pattern" skip in one place — keeping the
       # rule symmetric with the docker-manifests job above. The wait gates
       # above ensure the tag is never pushed before the registry is serving
-      # the artifact (Go is exempt: the tag IS the release).
+      # the artifact (Go and Swift are exempt: the tag IS the release).
       - name: Tag SDK release (${{ matrix.key }})
         if: |
           success() &&
@@ -1389,6 +1401,7 @@ jobs:
                 maven)      REGISTRY_DISPLAY="Maven" ;;
                 nuget)      REGISTRY_DISPLAY="NuGet" ;;
                 none)       REGISTRY_DISPLAY="Tag only" ;;
+                swiftpm)    REGISTRY_DISPLAY="Tag only (SwiftPM)" ;;
                 *)          REGISTRY_DISPLAY="$REGISTRY" ;;
               esac
 
diff --git a/.gitignore b/.gitignore
index 944c044bc..5d46d1f38 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,6 +27,10 @@ foreign/java/**/out
 foreign/node/**/dist
 foreign/swift/.build
 foreign/swift/.swiftpm
+# The root manifest publishes the Swift SDK. Consumers resolve their own pins.
+/.build
+/.swiftpm
+/Package.resolved
 examples/swift/.build
 examples/swift/**/.build
 examples/swift/**/.swiftpm
diff --git a/.spi.yml b/.spi.yml
new file mode 100644
index 000000000..d6b1db253
--- /dev/null
+++ b/.spi.yml
@@ -0,0 +1,22 @@
+# 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.
+
+# Swift Package Index builds and hosts the DocC reference for the SDK.
+version: 1
+builder:
+  configs:
+    - documentation_targets: [Iggy]
diff --git a/AGENTS.md b/AGENTS.md
index 36f97a997..68ff45dba 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -3,7 +3,7 @@
 **Apache Iggy** is a persistent message streaming platform
 in Rust. Thread-per-core shared-nothing, `io_uring` + `compio`.
 Transports: QUIC, WebSocket, TCP (custom binary), HTTP (REST). SDKs:
-Rust, .NET, Java, Python, Go, C++, Node.js. A connectors subsystem
+Rust, .NET, Java, Python, Go, C++, Node.js, Swift. A connectors subsystem
 ingests from / egresses to external systems via dlopened plugins.
 
 > Skills live under `.claude/skills/`, with per-skill links under
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 000000000..0486d6b1f
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,57 @@
+// swift-tools-version: 6.0
+// 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.
+
+// SwiftPM resolves packages only from a repository root. CI keeps this in 
sync with foreign/swift/Package.swift.
+
+import PackageDescription
+
+let package = Package(
+    name: "iggy",
+    platforms: [
+        .macOS(.v13),
+        .iOS(.v16),
+        .tvOS(.v16),
+        .watchOS(.v9),
+        .visionOS(.v1),
+    ],
+    products: [
+        .library(name: "Iggy", targets: ["Iggy"])
+    ],
+    dependencies: [
+        .package(url: "https://github.com/apple/swift-nio.git";, from: 
"2.80.0"),
+        .package(url: "https://github.com/apple/swift-nio-ssl.git";, from: 
"2.29.0"),
+        .package(url: "https://github.com/apple/swift-log.git";, from: "1.6.0"),
+        .package(url: "https://github.com/apple/swift-crypto.git";, 
"3.0.0"..<"4.0.0"),
+    ],
+    targets: [
+        .target(
+            name: "Iggy",
+            dependencies: [
+                .product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
+                .product(name: "NIOCore", package: "swift-nio"),
+                .product(name: "NIOPosix", package: "swift-nio"),
+                .product(name: "NIOTLS", package: "swift-nio"),
+                .product(name: "NIOSSL", package: "swift-nio-ssl"),
+                .product(name: "Logging", package: "swift-log"),
+                .product(name: "Crypto", package: "swift-crypto"),
+            ],
+            path: "foreign/swift/Sources/Iggy"
+        )
+    ],
+    swiftLanguageModes: [.v6]
+)
diff --git a/foreign/swift/LICENSE b/foreign/swift/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/foreign/swift/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/foreign/swift/NOTICE b/foreign/swift/NOTICE
new file mode 100644
index 000000000..346bbd9c1
--- /dev/null
+++ b/foreign/swift/NOTICE
@@ -0,0 +1,12 @@
+Apache Iggy
+Copyright 2026 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+================================================================
+
+The Iggy project code was originally created, designed, developed by Piotr 
Gankiewicz in April 2023.
+It was released as an open-source project under MIT License, later converted 
to Apache 2.0 License,
+and donated by LaserData, Inc. to the Apache Software Foundation (ASF) in 
February 2025.
+Copyright April 2023 - February 2025 Piotr Gankiewicz, LaserData, Inc.
diff --git a/foreign/swift/README.md b/foreign/swift/README.md
index a9d604000..44888721d 100644
--- a/foreign/swift/README.md
+++ b/foreign/swift/README.md
@@ -36,20 +36,20 @@ The SDK depends on 
[SwiftNIO](https://github.com/apple/swift-nio),
 
 ## Installation
 
-Swift Package Manager fetches a package from the root of a git repository, so 
the SDK
-cannot be depended on straight from this monorepo's subdirectory. Add it 
through the
-package mirror, which carries every release as a `v<version>` tag:
+Swift Package Manager resolves a package from the root of a git repository, so 
the Iggy repository root carries a `Package.swift` that publishes this SDK. 
Depend on the Iggy repository itself, replacing `0.1.10` with a released 
version:
 
 ```swift
 // Package.swift
 dependencies: [
-    .package(url: "https://github.com/apache/iggy-swift.git";, from: "0.1.1")
+    .package(url: "https://github.com/apache/iggy.git";, from: "0.1.10")
 ],
 targets: [
-    .target(name: "MyApp", dependencies: [.product(name: "Iggy", package: 
"iggy-swift")])
+    .target(name: "MyApp", dependencies: [.product(name: "Iggy", package: 
"iggy")])
 ]
 ```
 
+Stable Swift releases use `vX.Y.Z` tags after PMC approval. An `X.Y.Z-edge.N` 
tag is a development snapshot of unreleased changes, not an Apache release. A 
version range such as `from: "0.1.10"` never selects a snapshot. The first 
resolve clones the whole Iggy repository, which SwiftPM then caches.
+
 From a checkout of this repository, or a vendored copy, depend on it by path:
 
 ```swift
@@ -544,3 +544,15 @@ for package in examples/swift/getting-started 
bdd/swift/tests; do
   swift format lint --strict --recursive --configuration 
foreign/swift/.swift-format "$package/Sources" "$package/Package.swift"
 done
 ```
+
+### Releasing
+
+`IggyVersion.sdkVersion` in `Sources/Iggy/Iggy.swift` is the SDK version. 
Change it only with `scripts/bump-version.sh sdk-swift` from the repository 
root, which also regenerates the golden vectors that embed it: `--edge` for the 
next development snapshot, `--strip-edge` for the version that goes to a 
release vote.
+
+After a merge to master, CI tags an untagged `X.Y.Z-edge.N` version as 
`vX.Y.Z-edge.N`. Developers who choose to test unreleased changes can select an 
existing snapshot with an exact requirement such as `exact: "0.1.10-edge.1"`.
+
+For a stable release, the PMC vote must cover the Swift version and source 
archive, including the root manifest. The release manager verifies the vote 
result and publishes the signed source release through Apache's release 
process. The workflow does not verify the vote automatically. Merging a plain 
`X.Y.Z` version to master does not publish a stable Swift tag.
+
+After that, run the Publish workflow on the voted commit with 
`publish_other=swift` and `create_edge_docker_tag=false`. Run with 
`dry_run=true` first, then `dry_run=false` and `skip_tag_creation=false` to 
publish the tag.
+
+The root `Package.swift` declares only the library. Its tools version, 
platforms, dependencies and `Iggy` target must match this package's manifest, 
and CI fails when they differ.
diff --git a/foreign/swift/Sources/Iggy/Iggy.swift 
b/foreign/swift/Sources/Iggy/Iggy.swift
index 3addcb8ad..eef3218f7 100644
--- a/foreign/swift/Sources/Iggy/Iggy.swift
+++ b/foreign/swift/Sources/Iggy/Iggy.swift
@@ -19,7 +19,6 @@
 public enum IggyVersion {
     /// Name of this SDK as reported to the server.
     public static let sdkName = "swift-sdk"
-    /// Version of this SDK as reported to the server. Provisional until the
-    /// release wiring lands; the release tooling reads this constant.
-    public static let sdkVersion = "0.1.9"
+    /// Version of this SDK as reported to the server and named by its 
`v<version>` release tag.
+    public static let sdkVersion = "0.1.10-edge.1"
 }
diff --git a/foreign/swift/Tests/IggyTests/Fixtures/golden.json 
b/foreign/swift/Tests/IggyTests/Fixtures/golden.json
index 579d35918..c27aefec0 100644
--- a/foreign/swift/Tests/IggyTests/Fixtures/golden.json
+++ b/foreign/swift/Tests/IggyTests/Fixtures/golden.json
@@ -935,7 +935,7 @@
     "polling.next": "050000000000000000",
     "polling.offset.100": "016400000000000000",
     "polling.timestamp.1700000000000": "020068e5cf8b010000",
-    "request.bind_session": 
"023000000973776966742d73646b05302e312e3901000000000000000000000000000000640000000000000078000000000000005a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a",
+    "request.bind_session": 
"023000000973776966742d73646b0d302e312e31302d656467652e3101000000000000000000000000000000640000000000000078000000000000005a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a",
     "request.change_password": 
"010401000000086f6c642d706173730c6e65772d706173732d313233",
     "request.create_consumer_group": "01040100000001040200000003677270",
     "request.create_partitions": "01040100000001040200000005000000",
@@ -976,8 +976,8 @@
     "request.get_users": "",
     "request.join_consumer_group": "010401000000010402000000010403000000",
     "request.leave_consumer_group": "010401000000010402000000020167",
-    "request.login_register": 
"023000000973776966742d73646b05302e312e395a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a0469676779046967677900000000",
-    "request.login_register_with_pat": 
"023000000973776966742d73646b05302e312e395a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a107061742d61626331323364656634353600000000",
+    "request.login_register": 
"023000000973776966742d73646b0d302e312e31302d656467652e315a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a0469676779046967677900000000",
+    "request.login_register_with_pat": 
"023000000973776966742d73646b0d302e312e31302d656467652e315a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a107061742d61626331323364656634353600000000",
     "request.logout_user": "",
     "request.ping": "",
     "request.poll_messages": 
"0101040100000001040a00000001041400000001050000000164000000000000003200000001",
@@ -1032,7 +1032,7 @@
     "response.user_details.no_permissions": 
"0100000000e090663c130600010561646d696e000000000000000000000000",
     "stream_key.fingerprint": "8441e38538cbfa57",
     "user_headers.sample": 
"0207000000617474656d70740b0400000003000000020800000074726163652d69640203000000616263",
-    "version_info": "023000000973776966742d73646b05302e312e39",
+    "version_info": "023000000973776966742d73646b0d302e312e31302d656467652e31",
     "vsr.eviction.incompatible_protocol": 
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001032547698badcfeefcdab896745230102300000023000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 [...]
     "vsr.eviction.stale_client": 
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001032547698badcfeefcdab896745230100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 [...]
     "vsr.reply.committed": 
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0100000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001032547698badcfeefcdab896745230100000000000000000c0000000000000000000000000000000700000000000000800000000000000000000000010000000000000000000000
 [...]
diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh
index 0ef47a5d9..fc755c2ea 100755
--- a/scripts/bump-version.sh
+++ b/scripts/bump-version.sh
@@ -26,6 +26,10 @@ YELLOW='\033[1;33m'
 CYAN='\033[0;36m'
 NC='\033[0m'
 
+SWIFT_VERSION_FILE="foreign/swift/Sources/Iggy/Iggy.swift"
+SWIFT_GOLDEN_GENERATOR="foreign/swift/Tools/golden-vectors/Cargo.toml"
+SWIFT_GOLDEN_FIXTURE="foreign/swift/Tests/IggyTests/Fixtures/golden.json"
+
 usage() {
     cat <<'EOF'
 Usage:
@@ -58,6 +62,7 @@ Components:
   sdk-go               foreign/go/contracts/version.go
   sdk-csharp           foreign/csharp/Iggy_SDK/Iggy_SDK.csproj
   sdk-java             foreign/java/gradle.properties
+  sdk-swift            foreign/swift/Sources/Iggy/Iggy.swift + regenerated 
golden vectors
   web-ui               web/package.json
 
 Version flags (exactly one required):
@@ -91,11 +96,11 @@ RUST_COMPONENTS="rust-sdk rust-common rust-binary-protocol 
rust-server rust-cli
 CONNECTOR_SINK_COMPONENTS="rust-connector-clickhouse-sink 
rust-connector-delta-sink rust-connector-doris-sink 
rust-connector-elasticsearch-sink rust-connector-http-sink 
rust-connector-iceberg-sink rust-connector-influxdb-sink 
rust-connector-meilisearch-sink rust-connector-mongodb-sink 
rust-connector-postgres-sink rust-connector-quickwit-sink 
rust-connector-rabbitmq-sink rust-connector-redshift-sink 
rust-connector-s3-sink rust-connector-stdout-sink rust-connector-surrealdb-sink"
 CONNECTOR_SOURCE_COMPONENTS="rust-connector-elasticsearch-source 
rust-connector-http-source rust-connector-influxdb-source 
rust-connector-postgres-source rust-connector-random-source"
 CONNECTOR_COMPONENTS="rust-connector-runtime ${CONNECTOR_SINK_COMPONENTS} 
${CONNECTOR_SOURCE_COMPONENTS}"
-SDK_COMPONENTS="sdk-python sdk-node sdk-go sdk-csharp sdk-java"
+SDK_COMPONENTS="sdk-python sdk-node sdk-go sdk-csharp sdk-java sdk-swift"
 ALL_COMPONENTS="${RUST_COMPONENTS} ${CONNECTOR_COMPONENTS} ${SDK_COMPONENTS} 
web-ui"
 
 # Returns "file:format" lines per component.
-# Format keys: cargo, cargo-ws-dep:PKG, cargo-dep:PKG, python-cargo, 
pyproject, json, csproj, gradle, go
+# Format keys: cargo, cargo-ws-dep:PKG, cargo-dep:PKG, python-cargo, 
pyproject, json, csproj, gradle, go, swift
 get_version_files() {
     local component="$1"
     case "$component" in
@@ -168,6 +173,9 @@ get_version_files() {
         sdk-java)
             echo "foreign/java/gradle.properties:gradle"
             ;;
+        sdk-swift)
+            echo "${SWIFT_VERSION_FILE}:swift"
+            ;;
         web-ui)
             echo "web/package.json:json"
             ;;
@@ -249,7 +257,7 @@ translate_version() {
     read -r base pre_type pre_num <<< "$parsed"
 
     case "$format" in
-        cargo|cargo-ws-dep:*|cargo-dep:*|json|csproj|go)
+        cargo|cargo-ws-dep:*|cargo-dep:*|json|csproj|go|swift)
             echo "$canonical" ;;
         python-cargo)
             case "$pre_type" in
@@ -276,7 +284,7 @@ translate_version() {
 canonicalize_version() {
     local raw="$1" format="$2"
     case "$format" in
-        cargo|cargo-ws-dep:*|cargo-dep:*|json|csproj|go)
+        cargo|cargo-ws-dep:*|cargo-dep:*|json|csproj|go|swift)
             echo "$raw" ;;
         python-cargo)
             # Handle both old (0.7.2-dev.1) and new (0.7.2-dev1) formats
@@ -331,6 +339,8 @@ read_current_version() {
             grep '^version=' "$abs_file" | head -1 | sed 's/version=//' ;;
         go)
             grep 'const Version' "$abs_file" | head -1 | sed 's/.*const 
Version = "\([^"]*\)".*/\1/' ;;
+        swift)
+            grep 'static let sdkVersion = ' "$abs_file" | head -1 | sed 
's/.*sdkVersion = "\([^"]*\)".*/\1/' ;;
         *)
             echo -e "${RED}Unknown format: ${format}${NC}" >&2
             return 1 ;;
@@ -375,6 +385,8 @@ write_version() {
             sedi "s/^version=.*/version=${translated}/" "$abs_file" ;;
         go)
             sedi "1,/const Version = \"[^\"]*\"/{s/const Version = 
\"[^\"]*\"/const Version = \"${translated}\"/;}" "$abs_file" ;;
+        swift)
+            sedi "s/\(static let sdkVersion = \"\)[^\"]*\"/\1${translated}\"/" 
"$abs_file" ;;
         *)
             echo -e "${RED}Unknown format: ${format}${NC}" >&2
             return 1 ;;
@@ -480,6 +492,20 @@ preflight_consistency_check() {
     echo "$first_canonical"
 }
 
+# Swift golden vectors embed the SDK version. A failed run leaves the fixture 
untouched.
+regenerate_swift_golden() {
+    local generated
+    generated=$(mktemp "${REPO_ROOT}/${SWIFT_GOLDEN_FIXTURE}.XXXXXX") || 
return 1
+    if ! cargo run --quiet --manifest-path 
"${REPO_ROOT}/${SWIFT_GOLDEN_GENERATOR}" -- "$generated"; then
+        rm -f "$generated"
+        return 1
+    fi
+    if ! chmod 644 "$generated" || ! mv -f "$generated" 
"${REPO_ROOT}/${SWIFT_GOLDEN_FIXTURE}"; then
+        rm -f "$generated"
+        return 1
+    fi
+}
+
 # Main bump logic.
 cmd_bump() {
     local component="$1"
@@ -563,6 +589,19 @@ cmd_bump() {
     echo -e "${CYAN}New:${NC}       ${new_ver}"
     echo ""
 
+    local swift_backup=""
+    if [[ "$component" == "sdk-swift" && $apply -eq 1 ]]; then
+        if ! command -v cargo > /dev/null 2>&1; then
+            echo -e "${RED}sdk-swift needs cargo to regenerate 
${SWIFT_GOLDEN_FIXTURE}${NC}" >&2
+            return 1
+        fi
+        swift_backup=$(mktemp) || return 1
+        if ! cp "${REPO_ROOT}/${SWIFT_VERSION_FILE}" "$swift_backup"; then
+            rm -f "$swift_backup"
+            return 1
+        fi
+    fi
+
     local changed_files=()
     while IFS= read -r entry; do
         local file="${entry%%:*}"
@@ -589,6 +628,20 @@ cmd_bump() {
         fi
     done < <(get_version_files "$component")
 
+    if [[ -n "$swift_backup" ]]; then
+        if [[ ${#changed_files[@]} -gt 0 ]]; then
+            echo "  Regenerating ${SWIFT_GOLDEN_FIXTURE}"
+            if ! regenerate_swift_golden; then
+                cp "$swift_backup" "${REPO_ROOT}/${SWIFT_VERSION_FILE}"
+                rm -f "$swift_backup"
+                echo -e "${RED}Golden vector generation failed, restored 
${SWIFT_VERSION_FILE}${NC}" >&2
+                return 1
+            fi
+            changed_files+=("$SWIFT_GOLDEN_FIXTURE")
+        fi
+        rm -f "$swift_backup"
+    fi
+
     echo ""
     if [[ $apply -eq 1 ]]; then
         echo -e "${GREEN}Applied.${NC}"
diff --git a/scripts/prepare-release.sh b/scripts/prepare-release.sh
index 835aaf962..7fd5f915f 100755
--- a/scripts/prepare-release.sh
+++ b/scripts/prepare-release.sh
@@ -52,6 +52,7 @@ RELEASE_PATHS=(
   "Dockerfile"
   "LICENSE"
   "NOTICE"
+  "Package.swift"
   "about.hbs"
   "about.toml"
   "docker-compose.yml"

Reply via email to