Skip to content

Add Swift and Kotlin FFI wrappers with packaging and publishing#1432

Merged
kixelated merged 10 commits into
mainfrom
claude/ffi-bindings-ci-vGD5y
May 22, 2026
Merged

Add Swift and Kotlin FFI wrappers with packaging and publishing#1432
kixelated merged 10 commits into
mainfrom
claude/ffi-bindings-ci-vGD5y

Conversation

@kixelated
Copy link
Copy Markdown
Collaborator

This PR adds ergonomic Swift and Kotlin wrappers around the moq-ffi UniFFI bindings, along with complete packaging and publishing infrastructure for both platforms.

Summary

Introduces two new language bindings for Media over QUIC:

  • Swift Package for iOS and macOS applications
  • Kotlin libraries for Android and JVM applications

Both wrappers provide idiomatic async/await APIs that integrate with their respective platform's concurrency models (Swift's AsyncSequence and Kotlin's Flow), with automatic cleanup and cancellation propagation.

Key Changes

Swift wrapper (swift/):

  • Sources/Moq/ - Ergonomic shim layer with AsyncSequence extensions for streaming APIs (updates, frames, groups, announcements)
  • Package.swift - SPM manifest with binary XCFramework target (URL/checksum rewritten at release time)
  • scripts/check.sh - Local development script that builds moq-ffi, generates bindings, and runs swift test
  • scripts/package.sh - Release packaging script that creates XCFramework from per-target static libs and produces tarball
  • scripts/publish.sh - Stub for publishing to moq-dev/moq-swift mirror repo
  • Smoke tests verifying native lib loading and cancellation behavior

Kotlin wrapper (kt/):

  • common/src/dev/moq/ - Shared wrapper sources with Flow extensions for streaming APIs
  • moq-jvm/ - Kotlin/JVM library for desktop/server with JNA resource layout
  • moq-android/ - Android library with JNI .so files per ABI
  • scripts/check.sh - Local development script for host target
  • scripts/package.sh - Release packaging that stages native libs and generates Gradle artifacts
  • scripts/publish.sh - Maven Central publishing via Sonatype API
  • Gradle configuration with Maven publishing and GPG signing support
  • Smoke tests for client construction and cancellation

CI/CD integration (.github/workflows/moq-ffi.yml):

  • Added x86_64-apple-ios target for iOS simulator x86_64 support
  • New package-swift job that assembles XCFramework and stages Swift package
  • New package-kotlin job that stages Maven-local artifacts for both JVM and Android
  • New publish-maven job (gated by PUBLISH_MAVEN variable) for Maven Central publishing
  • New publish-spm job (gated by PUBLISH_SPM variable) for Swift Package mirror publishing
  • Updated release job to depend on packaging jobs

Development tooling (justfile):

  • Added check-ffi target that runs Swift and Kotlin smoke tests (skips gracefully if toolchain unavailable)
  • Integrated into main ci target

Implementation Details

  • Cancellation propagation: Both wrappers use onCompletion hooks (Swift) and onCompletion callbacks (Kotlin) to forward coroutine/scope cancellation to native cancel() calls, enabling structured concurrency
  • Native lib distribution: Swift uses XCFramework with per-platform slices (iOS device, iOS simulator arm64+x86_64, macOS universal); Kotlin uses JNA resource layout for JVM and JNI for Android
  • Bindings generation: UniFFI-generated code is populated at build time and gitignored; local dev scripts regenerate from moq-ffi cdylib
  • Publishing gates: Maven and SPM publishing are disabled by default and require explicit repo variables + secrets to activate, preventing accidental releases
  • Version management: Build version flows from git tags through environment variables into Package.swift and Gradle manifests

Both wrappers follow platform conventions: Swift uses async/await with AsyncSequence, Kotlin uses coroutines with Flow, and both provide ergonomic entry points (Moq.connect()) that hide the underlying UniFFI complexity.

https://claude.ai/code/session_01NDx1JbhDV6TQApDw6kynWC

claude added 5 commits May 20, 2026 16:04
Adds top-level `swift/` (SPM) and `kt/` (Gradle) packages that wrap
`rs/moq-ffi`'s UniFFI bindings in idiomatic shims: AsyncSequence/Flow
adapters that propagate structured-concurrency cancellation, a
`Moq.connect(url:)` convenience entry point, and small error helpers.
The directory layout mirrors how the future `moq-mux-ffi`/`moq-net-ffi`
split will land (sibling SPM targets + Gradle modules per ffi crate).

Build complexity lives in checked-in scripts (`{swift,kt}/scripts/
{check,package,publish}.sh`) so the workflow YAML only orchestrates
artifact flow. The `moq-ffi-v*` release pipeline gains `package-swift`
(macOS, produces XCFramework.zip + tarball with Package.swift) and
`package-kotlin` (Ubuntu, produces a Maven-local layout with the JVM
JAR + Android AAR + JNI libs at JNA's classpath paths). Both publish
jobs ship today as gated stubs: `vars.PUBLISH_MAVEN` and
`vars.PUBLISH_SPM` flip them on, secret setup is documented in the
package READMEs.

`just ci` now runs `just check-ffi`, which builds moq-ffi for the host,
regenerates the bindings, and runs the wrapper smoke tests. Each leg
skips cleanly if its toolchain is missing (no Swift on Linux, no JDK)
so `just ci` remains runnable on bare-bones machines.

Verified locally: `bash kt/scripts/check.sh` on Linux/x86_64 builds
moq-ffi, populates the JNA-resource layout, and `gradle :moq-jvm:test`
exercises both smoke tests against the real native library; the
end-to-end `kt/scripts/package.sh` flow produces `dev/moq/moq-jvm/
0.0.0-dev/moq-jvm-0.0.0-dev.jar` containing the host slice at
`linux-x86-64/libmoq_ffi.so`.
Replaces the monolithic moq-ffi.yml workflow with three independent
workflows (swift.yml, kotlin.yml, python.yml). Each triggers on the
shared moq-ffi-v* tag, builds only the targets it needs, and publishes
its own artifact. No more moq-ffi GitHub Release: those native-lib
tarballs were a stepping stone for the language packages and have no
remaining consumers now that the wrappers ship directly. Each language
package version echoes moq-ffi's Cargo.toml version via the existing
parse-version helper.

The build.sh tar/zip step is now opt-in via --archive; the default
leaves the staging directories alone so upload-artifact can ingest them
without the tar -> upload -> download -> untar round-trip. Same for
generate_bindings: bindings stay as plain directories.

Kotlin wrapper switches from a two-module (-jvm + -android) layout to a
single Kotlin Multiplatform module that publishes `dev.moq:moq` with
JVM and Android variants under one coordinate. Source set hierarchy:
commonMain -> jvmAndAndroidMain (UniFFI-generated + wrappers) -> jvmMain
/ androidMain (platform-specific native lib packaging). The Android
target is opt-in via -Pandroid.enabled=true so contributors without the
Android SDK (or Google maven access) can still build the JVM variant.

Verified locally on Linux/x86_64: `bash kt/scripts/check.sh` builds
moq-ffi, regenerates bindings, runs :moq:jvmTest (both smoke tests
pass). `kt/scripts/package.sh` end-to-end produces the expected KMP
publication (dev.moq:moq + dev.moq:moq-jvm) with the host slice at
linux-x86-64/libmoq_ffi.so inside the JAR.
Replaces the stub kt/scripts/publish.sh with the com.vanniktech.maven.publish
gradle plugin, which handles the Sonatype Central Portal upload protocol
and GPG signing in a single task (`:moq:publishAndReleaseToMavenCentral`).

The kotlin.yml workflow now merges the package + publish steps into one
job that stages native libs + bindings into the gradle module, runs the
plugin's publish task with credentials supplied as
ORG_GRADLE_PROJECT_{mavenCentralUsername,mavenCentralPassword,signingInMemoryKey,signingInMemoryKeyPassword}
env vars, and uploads the maven-local layout as an inspectable artifact.
The vars.PUBLISH_MAVEN gate is dropped now that the publish path is
real; CI publishes on every moq-ffi-v* tag.
Splits the monolithic root justfile so each language owns its tool
invocations. Adds js/justfile, rs/justfile, py/justfile, kt/justfile,
swift/justfile, each set to run from its own directory via `set
working-directory := '.'` (or `'..'` for rs/, since cargo runs at the
workspace root). The root justfile imports them via `mod` so commands
like `just kt check`, `just rs ci`, `just py test` work as expected.
Cross-cutting recipes (check, ci, fix, test, build, update) now
delegate to the per-language modules instead of inlining each language's
toolchain.

Renames the new release workflows to match the existing release-*.yml
convention (release-js, release-py, release-rs already in place):
swift.yml -> release-swift.yml, kotlin.yml -> release-kt.yml. python.yml
left alone for now pending the moq-ffi/moq-lite consolidation decision.

Verified locally: `just --list`, `just --list kt`, `just --list rs`, etc.
all show the right recipes; `just kt check` runs from kt/, builds
moq-ffi, regenerates bindings, and passes :moq:jvmTest.
@kixelated kixelated marked this pull request as ready for review May 21, 2026 23:40
Comment thread .github/workflows/python.yml Outdated
with:
python-version: "3.12"

- name: Set Cargo.toml version
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? The moq-ffi version should take priority?

Comment thread .github/workflows/python.yml Outdated
if: vars.PUBLISH_PYTHON == 'true'
environment:
name: pypi
url: https://pypi.org/p/moq-ffi
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid publishing this moq-ffi package? I'd like to publish just py/moq-net or whatever it's called instead of an intermediate.

claude added 2 commits May 21, 2026 23:44
Calling `just rs check` from inside rs/justfile would recurse into a
nonexistent rs/rs module. The bare `just check` already resolves to the
sibling recipe in the same module.
semver/bump/release are pure Rust release-plz operations, so they belong
in the per-language module alongside check/ci/fix/test. The root justfile
keeps only cross-language orchestration. release-rs.yml is updated to
call `just rs release`.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Review Change Stack

Warning

Rate limit exceeded

@kixelated has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 52 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d88f6153-40f1-4c7c-b438-7cbdb07998d4

📥 Commits

Reviewing files that changed from the base of the PR and between da54fdf and f9bf402.

📒 Files selected for processing (7)
  • .github/workflows/release-kt.yml
  • .github/workflows/release-py.yml
  • .github/workflows/release-swift.yml
  • kt/README.md
  • kt/scripts/package.sh
  • swift/README.md
  • swift/scripts/package.sh

Walkthrough

This PR consolidates release automation, introduces native language wrappers for Kotlin and Swift, and restructures the build orchestration. The old unified moq-ffi.yml workflow is removed and replaced with separate tag-driven pipelines for Python (wheels via maturin), Kotlin (Maven Central), and Swift (SPM mirror). The root justfile delegates to per-language justfiles that isolate build logic. Python imports are refactored to use local ._uniffi modules, and maturin is adopted as the py/moq-net build backend. A new Rust rs/moq-ffi/build.sh flag allows optional archiving of bindings and libraries. The Kotlin wrapper adds Flow-based coroutine adapters, Gradle Multiplatform configuration, and Maven publishing setup. The Swift wrapper provides AsyncThrowingStream adapters, an SPM-ready manifest, XCFramework packaging, and local development support. Both language wrappers include smoke tests and documentation.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main addition: Swift and Kotlin FFI wrappers with their packaging and publishing infrastructure.
Description check ✅ Passed The description comprehensively details the changes across Swift, Kotlin, CI/CD, and development tooling, clearly relating to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/ffi-bindings-ci-vGD5y
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/ffi-bindings-ci-vGD5y

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (2)
kt/README.md (1)

51-51: ⚡ Quick win

Add language identifiers to fenced code blocks.

Lines 51 and 79 use unlabeled fenced blocks, which commonly triggers markdown lint warnings and hurts editor rendering.

Suggested doc fix
-```
+```text
 kt/
   build.gradle.kts          Root config (group, version)
   settings.gradle.kts       include(":moq"), pins AGP version
   gradle.properties         Defaults: version, android.useAndroidX, etc.
@@
-```
+```bash
    gpg --full-generate-key                       # RSA 4096, 4y expiry
    gpg --list-secret-keys --keyid-format=long    # find <KEYID>
    gpg --armor --export-secret-keys <KEYID> > signing-key.asc
    gpg --keyserver keys.openpgp.org --send-keys <KEYID>
    gpg --keyserver keyserver.ubuntu.com --send-keys <KEYID>

Also applies to: 79-79

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kt/README.md` at line 51, Add explicit language identifiers to the unlabeled
fenced code blocks in README.md: change the first unlabeled block (the directory
listing block around the kt/ example) from ``` to ```text and change the GPG
command block later from ``` to ```bash so Markdown linters and editors
correctly syntax-highlight those blocks; update the fenced markers around the
blocks referenced in the diff accordingly.
swift/scripts/check.sh (1)

40-41: ⚡ Quick win

Restore Package.swift on exit after local rewrite.

The script overwrites Package.swift but never restores it, which can wipe local edits and leave a dirty tree after smoke checks.

Suggested patch
 BINDGEN_OUT=$(mktemp -d)
-trap 'rm -rf "$BINDGEN_OUT"' EXIT
+PKG_FILE="$SWIFT_DIR/Package.swift"
+PKG_BACKUP=$(mktemp)
+cp "$PKG_FILE" "$PKG_BACKUP"
+cleanup() {
+    cp "$PKG_BACKUP" "$PKG_FILE"
+    rm -f "$PKG_BACKUP"
+    rm -rf "$BINDGEN_OUT"
+}
+trap cleanup EXIT
@@
-cat > "$SWIFT_DIR/Package.swift" <<EOF
+cat > "$PKG_FILE" <<EOF
 // swift-tools-version:5.9

Also applies to: 63-80

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@swift/scripts/check.sh` around lines 40 - 41, The script creates a temporary
dir via BINDGEN_OUT=$(mktemp -d) and registers a trap to clean it on EXIT but
never restores an overwritten Package.swift; update the EXIT trap (or add a new
trap handler) to save the original Package.swift before the local rewrite and
restore it on exit/failure, ensuring any temporary rewrite steps that modify
Package.swift (the rewrite section around lines 63-80) revert the file back;
reference the BINDGEN_OUT handling and the existing trap to add logic that
preserves the original content (e.g., copy Package.swift to a backup like
Package.swift.bak before rewriting and move it back in the trap) and remove the
backup after successful restore.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/python.yml:
- Around line 11-13: The workflow's concurrency block currently uses "group:
moq-ffi-python-release" with "cancel-in-progress: true", which can abort an
ongoing tag-based release; change "cancel-in-progress: true" to
"cancel-in-progress: false" (or remove the cancel-in-progress key) in the
concurrency block so in-flight tag releases are not canceled; update the
".github/workflows/python.yml" concurrency section that contains the "group:
moq-ffi-python-release" and "cancel-in-progress: true" entries to reflect this
change.
- Line 23: Update the workflow to pin third-party actions to immutable commit
SHAs and disable credential persistence for checkout: replace the tag-pinned
uses of actions/checkout@v6 with the full commit SHA for that release and add
the input persist-credentials: false under the actions/checkout step (the
checkout step symbolized by uses: actions/checkout and its surrounding block),
and likewise replace actions/setup-python@v6 (and any other uses: entries) with
their corresponding full commit SHAs to ensure deterministic, hardened runs.

In @.github/workflows/release-kt.yml:
- Around line 11-13: The workflow's concurrency block uses "group: release-kt"
with "cancel-in-progress: true", which will cancel an in-progress tag release
when a new tag is pushed; remove that risk by deleting the "cancel-in-progress"
key or set it to false so active tag-release runs are not canceled (locate the
concurrency block with the symbols "concurrency:", "group: release-kt" and
"cancel-in-progress" to update).
- Line 23: Replace all tag-pinned GitHub Actions (e.g., the occurrences of
"uses: actions/checkout@v6" and other "uses: ...@v#" entries) with immutable
commit SHAs and add persist-credentials: false to every checkout step;
specifically update each "uses: actions/checkout@..." invocation to use the full
commit SHA and add a with: persist-credentials: false block so the checkout
steps (the occurrences shown as "uses: actions/checkout@v6" and other checkout
lines) do not expose Git credentials.

In @.github/workflows/release-swift.yml:
- Around line 11-13: The concurrency block (concurrency, group,
cancel-in-progress) currently causes a new tag push to cancel an in-progress
release; update the concurrency configuration so releases for different tags
don't cancel each other — either set cancel-in-progress to false or make the
group unique per ref (e.g., include the tag/ref variable like github.ref in the
group name) so each tag release runs independently.
- Line 23: Update every uses: actions/checkout@v6 invocation to include
persist-credentials: false (i.e., set persist-credentials: false under each
checkout step) and replace the tag reference with the immutable commit SHA for
the checkout action; likewise replace tag references for
dtolnay/rust-toolchain@stable, Swatinem/rust-cache@v2,
actions/upload-artifact@v7, and actions/download-artifact@v8 with their exact
commit SHAs so all third‑party actions are pinned to immutable commits. Ensure
you update all occurrences of actions/checkout@v6 in the workflow and that each
checkout step includes the persist-credentials: false key.

In `@kt/moq/src/jvmAndAndroidMain/kotlin/dev/moq/Flows.kt`:
- Around line 25-32: The flow builders (e.g., MoqCatalogConsumer.updates()) must
register native cancellation immediately instead of relying on onCompletion;
before entering the loop, get the current coroutine Job via
currentCoroutineContext().job and call job.invokeOnCancellation { cancel() } so
native resources are cancelled promptly if the coroutine is requested to cancel
while a blocking native call (next()/nextGroup()/recvGroup()/readFrame()) is in
progress; add this invokeOnCancellation registration at the top of each flow
builder (before the while(true) loop) and you may keep or remove the existing
onCompletion handler once immediate cancellation is wired up.

In `@kt/README.md`:
- Line 74: Update the stale workflow filename referenced in the docs: replace
the mention of "kotlin.yml" with the current workflow name "release-kt.yml" in
the README sentence that describes the release workflow using
com.vanniktech.maven.publish, so the docs point maintainers to the correct file
for releases; ensure the phrasing still indicates it triggers on moq-ffi-v* tags
and uploads to Sonatype Central.

In `@kt/scripts/package.sh`:
- Around line 57-61: The script uses Bash associative arrays (declare -A) for
ANDROID_ABIS and JVM_LIBS which fail on Bash 3.2 (macOS); add a Bash version
guard at the top (check BASH_VERSINFO[0] >= 4) and emit a clear error message
and exit if too old, or alternatively replace the associative arrays
(ANDROID_ABIS and JVM_LIBS) with POSIX-safe logic (case statements or functions
that map keys to values) so the script works under /usr/bin/env bash on macOS;
update the check near the shebang and touch only the parts that reference
declare -A, ANDROID_ABIS, and JVM_LIBS.

In `@swift/README.md`:
- Line 44: The fenced code block that currently uses only triple backticks
should include a language identifier to satisfy MD040; edit the opening ``` in
the README's example block and append an appropriate language token (e.g., text,
swift, or json) so the block reads like ```text (or ```swift) to ensure the
markdown linter recognizes the code fence language.
- Line 40: Update the README reference that names the workflow file: replace the
outdated mention of "swift.yml" with the actual workflow filename
"release-swift.yml" where the README says the workflow triggers on the
`moq-ffi-v*` tag so the docs correctly point to the Swift release workflow;
ensure the text still mentions the `moq-ffi-v*` tag if intended.

In `@swift/scripts/package.sh`:
- Line 57: Normalize OUTPUT_DIR to an absolute path before any
mkdir/zip/checksum operations: resolve OUTPUT_DIR (e.g. via OUTPUT_DIR="$(cd
"$OUTPUT_DIR" && pwd)" or realpath) immediately before the mkdir -p
"$OUTPUT_DIR" call and use that absolute path for subsequent zip and checksum
steps (the packaging/zip and checksum blocks later in the script). Ensure all
references to OUTPUT_DIR in the zip and checksum commands use the normalized
variable so packaging run from a different CWD cannot read/write the wrong
location.

---

Nitpick comments:
In `@kt/README.md`:
- Line 51: Add explicit language identifiers to the unlabeled fenced code blocks
in README.md: change the first unlabeled block (the directory listing block
around the kt/ example) from ``` to ```text and change the GPG command block
later from ``` to ```bash so Markdown linters and editors correctly
syntax-highlight those blocks; update the fenced markers around the blocks
referenced in the diff accordingly.

In `@swift/scripts/check.sh`:
- Around line 40-41: The script creates a temporary dir via BINDGEN_OUT=$(mktemp
-d) and registers a trap to clean it on EXIT but never restores an overwritten
Package.swift; update the EXIT trap (or add a new trap handler) to save the
original Package.swift before the local rewrite and restore it on exit/failure,
ensuring any temporary rewrite steps that modify Package.swift (the rewrite
section around lines 63-80) revert the file back; reference the BINDGEN_OUT
handling and the existing trap to add logic that preserves the original content
(e.g., copy Package.swift to a backup like Package.swift.bak before rewriting
and move it back in the trap) and remove the backup after successful restore.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 521f1b93-bc5d-4885-9f95-bbdaf03ba079

📥 Commits

Reviewing files that changed from the base of the PR and between 0fcd1b1 and b974a4b.

📒 Files selected for processing (35)
  • .github/workflows/moq-ffi.yml
  • .github/workflows/python.yml
  • .github/workflows/release-kt.yml
  • .github/workflows/release-swift.yml
  • js/justfile
  • justfile
  • kt/.gitignore
  • kt/README.md
  • kt/build.gradle.kts
  • kt/gradle.properties
  • kt/justfile
  • kt/moq/android.gradle.kts
  • kt/moq/build.gradle.kts
  • kt/moq/src/jvmAndAndroidMain/kotlin/dev/moq/Errors.kt
  • kt/moq/src/jvmAndAndroidMain/kotlin/dev/moq/Flows.kt
  • kt/moq/src/jvmAndAndroidMain/kotlin/dev/moq/Moq.kt
  • kt/moq/src/jvmAndAndroidTest/kotlin/dev/moq/SmokeTest.kt
  • kt/scripts/check.sh
  • kt/scripts/package.sh
  • kt/settings.gradle.kts
  • py/justfile
  • rs/justfile
  • rs/moq-ffi/build.sh
  • swift/.gitignore
  • swift/Package.swift
  • swift/README.md
  • swift/Sources/Moq/AsyncSequences.swift
  • swift/Sources/Moq/Errors.swift
  • swift/Sources/Moq/Moq.swift
  • swift/Sources/Moq/Session.swift
  • swift/Tests/MoqTests/SmokeTests.swift
  • swift/justfile
  • swift/scripts/check.sh
  • swift/scripts/package.sh
  • swift/scripts/publish.sh
💤 Files with no reviewable changes (1)
  • .github/workflows/moq-ffi.yml

Comment thread .github/workflows/python.yml Outdated
Comment thread .github/workflows/python.yml Outdated
version: ${{ steps.parse.outputs.version }}

steps:
- uses: actions/checkout@v6
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
# Verify non-SHA-pinned actions and checkout credential persistence.
rg -n '^\s*-\s+uses:\s+\S+@v[0-9]+' .github/workflows/python.yml
rg -n -C2 'actions/checkout@' .github/workflows/python.yml

Repository: moq-dev/moq

Length of output: 372


🏁 Script executed:

#!/usr/bin/env bash
# List all uses: entries with line numbers
rg -n '^\s*-\s+uses:\s+\S+' .github/workflows/python.yml

# Show each actions/checkout occurrence with nearby context (for with/persist-credentials)
rg -n -C4 'actions/checkout@' .github/workflows/python.yml

# Check whether persist-credentials is set anywhere in this workflow
rg -n 'persist-credentials' .github/workflows/python.yml

Repository: moq-dev/moq

Length of output: 660


Fix action pinning and disable checkout credential persistence.
In .github/workflows/python.yml, the only uses: entries are actions/checkout@v6 (lines 23 and 55) and actions/setup-python@v6 (line 57). actions/checkout is not configured with with: persist-credentials: false, and none of the uses: entries are pinned to a commit SHA (they’re all tag-pinned like @v6). Pin actions/checkout to the full commit SHA and add:

Suggested hardening pattern
-      - uses: actions/checkout@v6
+      - uses: actions/checkout@<full_commit_sha>
+        with:
+          persist-credentials: false

Also apply SHA pinning to every other uses: entry (e.g., actions/setup-python).

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 23-23: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 23-23: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/python.yml at line 23, Update the workflow to pin
third-party actions to immutable commit SHAs and disable credential persistence
for checkout: replace the tag-pinned uses of actions/checkout@v6 with the full
commit SHA for that release and add the input persist-credentials: false under
the actions/checkout step (the checkout step symbolized by uses:
actions/checkout and its surrounding block), and likewise replace
actions/setup-python@v6 (and any other uses: entries) with their corresponding
full commit SHAs to ensure deterministic, hardened runs.

Comment thread .github/workflows/release-kt.yml Outdated
version: ${{ steps.parse.outputs.version }}

steps:
- uses: actions/checkout@v6
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
rg -n '^\s*-\s+uses:\s+\S+@v[0-9]+' .github/workflows/release-kt.yml
rg -n -C2 'actions/checkout@' .github/workflows/release-kt.yml

Repository: moq-dev/moq

Length of output: 686


🏁 Script executed:

cat -n .github/workflows/release-kt.yml | head -200

Repository: moq-dev/moq

Length of output: 6746


🏁 Script executed:

rg -n 'persist-credentials' .github/workflows/release-kt.yml

Repository: moq-dev/moq

Length of output: 37


Pin all workflow actions to immutable SHAs and add persist-credentials: false to checkout steps.

This release workflow uses mutable tag-pinned actions (e.g., @v6, @v4, @v7, @v8, @v3, @v2) which can be retagged or compromised. Additionally, all actions/checkout steps (lines 23, 56, 100, 136) lack persist-credentials: false, unnecessarily exposing Git credentials in the release environment.

Replace all @v# references with immutable commit SHAs, and add persist-credentials: false to each checkout step:

- uses: actions/checkout@<full-commit-sha>
  with:
    persist-credentials: false

Affected tag-pinned actions: lines 23, 56, 64, 88, 100, 106, 118, 136, 138, 144, 146, 149, 164, 187

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 23-23: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 23-23: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-kt.yml at line 23, Replace all tag-pinned GitHub
Actions (e.g., the occurrences of "uses: actions/checkout@v6" and other "uses:
...@v#" entries) with immutable commit SHAs and add persist-credentials: false
to every checkout step; specifically update each "uses: actions/checkout@..."
invocation to use the full commit SHA and add a with: persist-credentials: false
block so the checkout steps (the occurrences shown as "uses:
actions/checkout@v6" and other checkout lines) do not expose Git credentials.

Comment thread .github/workflows/release-swift.yml Outdated
Comment thread kt/README.md Outdated
Comment thread kt/scripts/package.sh
Comment thread swift/README.md Outdated
Comment thread swift/README.md Outdated
Comment thread swift/scripts/package.sh
Drop the intermediate moq-ffi PyPI package and the separate python.yml
workflow. py/moq-net is now a maturin uniffi project that builds
rs/moq-ffi as a cdylib + uniffi bindings, exposed inside the package as
the private moq_net._uniffi submodule. Users `pip install moq-net` and
import only from moq_net.

release-py.yml absorbs python.yml's cross-platform wheel matrix and
triggers on moq-ffi-v* tags (the same tag release-plz pushes when it
bumps rs/moq-ffi/Cargo.toml). Maturin's dynamic version reads from that
Cargo.toml, so the wheel version tracks the Rust crate without a
separate Python bump and without release-plz needing to touch
pyproject.toml.

Other cleanup:
- Drop rs/moq-ffi/pyproject.toml (no longer published as moq-ffi)
- Drop py/common/release.sh and the py just release recipe (CI-driven now)
- Move pytest deps from py/moq-net dev group to root dev group so
  `just py check/test` only needs one uv sync
- Add /py/moq-net/moq_net/_uniffi/ to .gitignore (maturin develop output)
- Add exclude rules so ruff and pyright skip the generated uniffi file
- Update CLAUDE.md to document the new layout

The Rust moq-ffi crate stays on crates.io unchanged. Swift/Kotlin
wrappers are untouched (uniffi scaffolding namespace remains "moq"
so MoqFFI.xcframework and uniffi.moq.* keep working).
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
.github/workflows/release-py.yml (2)

60-60: 💤 Low value

Consider adding persist-credentials: false for hardening.

Same as the parse-version job, this checkout doesn't need to persist credentials.

🛡️ Suggested hardening
       - uses: actions/checkout@v6
+        with:
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-py.yml at line 60, The checkout step currently
uses actions/checkout@v6 without restricting GitHub token exposure; update the
checkout step that invokes actions/checkout@v6 to include the input
persist-credentials: false so credentials are not persisted to the checked-out
repository (same change as the parse-version job). Locate the step that
references actions/checkout@v6 and add the persist-credentials: false key under
that step's uses to harden the workflow.

28-28: 💤 Low value

Consider adding persist-credentials: false for hardening.

The checkout action defaults to persisting Git credentials. Since this job only parses the tag and doesn't push, disabling credential persistence reduces attack surface.

🛡️ Suggested hardening
       - uses: actions/checkout@v6
+        with:
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-py.yml at line 28, The checkout step currently
uses "uses: actions/checkout@v6" which leaves Git credentials persisted; update
the checkout action invocation to include the option persist-credentials: false
so credentials are not retained (i.e., modify the step referencing uses:
actions/checkout@v6 to add persist-credentials: false under its with: block) to
harden the workflow for a read-only tag-parsing job.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.github/workflows/release-py.yml:
- Line 60: The checkout step currently uses actions/checkout@v6 without
restricting GitHub token exposure; update the checkout step that invokes
actions/checkout@v6 to include the input persist-credentials: false so
credentials are not persisted to the checked-out repository (same change as the
parse-version job). Locate the step that references actions/checkout@v6 and add
the persist-credentials: false key under that step's uses to harden the
workflow.
- Line 28: The checkout step currently uses "uses: actions/checkout@v6" which
leaves Git credentials persisted; update the checkout action invocation to
include the option persist-credentials: false so credentials are not retained
(i.e., modify the step referencing uses: actions/checkout@v6 to add
persist-credentials: false under its with: block) to harden the workflow for a
read-only tag-parsing job.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a90e9dc8-6015-4896-88e6-6c7a20c97ca1

📥 Commits

Reviewing files that changed from the base of the PR and between ecf1db6 and da54fdf.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • .github/workflows/release-py.yml
  • .gitignore
  • CLAUDE.md
  • py/common/release.sh
  • py/justfile
  • py/moq-net/moq_net/client.py
  • py/moq-net/moq_net/origin.py
  • py/moq-net/moq_net/publish.py
  • py/moq-net/moq_net/subscribe.py
  • py/moq-net/moq_net/types.py
  • py/moq-net/pyproject.toml
  • pyproject.toml
  • rs/moq-ffi/pyproject.toml
💤 Files with no reviewable changes (2)
  • py/common/release.sh
  • rs/moq-ffi/pyproject.toml
✅ Files skipped from review due to trivial changes (3)
  • py/moq-net/moq_net/subscribe.py
  • CLAUDE.md
  • py/moq-net/moq_net/publish.py

The parse-version and build jobs only need source code, not git push
access. Setting persist-credentials: false avoids leaving the
GITHUB_TOKEN in .git/config where downstream steps (or the maturin-action
container) could read it.
@kixelated kixelated enabled auto-merge (squash) May 22, 2026 00:22
- release-kt.yml, release-swift.yml: switch concurrency cancel-in-progress
  from true to false so a second moq-ffi-v* tag doesn't abort an
  in-flight Maven Central / GitHub Release upload mid-publish.
- kt/README.md: rename stale kotlin.yml reference to release-kt.yml.
- swift/README.md: rename stale swift.yml reference to release-swift.yml;
  add text language to the Layout fenced block for MD040.
- swift/scripts/package.sh: normalize OUTPUT_DIR to an absolute path
  after mkdir, since the zip and 'swift package compute-checksum' steps
  later run from cd'd subshells and would otherwise resolve a relative
  path against the wrong cwd.
- kt/scripts/package.sh: fail fast with a clear error on Bash < 4
  (macOS default /usr/bin/bash is 3.2), since the script uses
  associative arrays via declare -A.
@kixelated kixelated merged commit ececd8e into main May 22, 2026
1 check passed
@kixelated kixelated deleted the claude/ffi-bindings-ci-vGD5y branch May 22, 2026 00:38
@moq-bot moq-bot Bot mentioned this pull request May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants