diff --git a/.cspell.json b/.cspell.json
index 757f0c48..bb8aef81 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -6,6 +6,7 @@
"**/*.{java,md,xml,yml,yaml,json,txt,properties}"
],
"words": [
+ "japicmp",
"Skyflow",
"skyflow",
"skyflowapi",
diff --git a/.github/workflows/contract-tests.yml b/.github/workflows/contract-tests.yml
new file mode 100644
index 00000000..f50424b6
--- /dev/null
+++ b/.github/workflows/contract-tests.yml
@@ -0,0 +1,118 @@
+name: Contract Tests
+
+on:
+ pull_request:
+ branches:
+ - main
+ - release/*
+
+jobs:
+ contract-tests:
+ name: Contract Tests
+ runs-on: ubuntu-latest
+
+ permissions:
+ contents: read
+ pull-requests: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '11'
+ cache: 'maven'
+
+ - name: Verify API surface snapshot
+ run: mvn -B install -pl common,v2 -am -DskipTests -Dmaven.javadoc.skip=true -Dgpg.skip=true
+
+ - name: Show API surface diff
+ if: failure()
+ run: |
+ echo "### API surface changes detected ###"
+ echo "See v2/target/japicmp/default-cli.diff for the full comparison against v2/api-report/skyflow-java.baseline.jar."
+ echo "If this change is intentional, run scripts/contract-snapshot-update.sh and commit the updated baseline jar."
+ echo ""
+ cat v2/target/japicmp/default-cli.diff || true
+
+ - name: Upload API surface diff on failure
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: api-surface-diff
+ path: v2/target/japicmp/**
+ retention-days: 7
+
+ # The step above only shows a diff when the CURRENT build differs from the
+ # committed baseline - once someone runs contract-snapshot-update.sh and
+ # commits the refreshed baseline jar, that check goes green and shows nothing.
+ # A reviewer looking at a green PR that touches api-report/skyflow-java.baseline.jar
+ # (a binary file) would otherwise have no way to see WHAT was just approved as
+ # the new contract. These steps explicitly diff the OLD committed baseline
+ # (from the PR's base branch) against the NEW committed baseline (from this PR)
+ # and post it as a PR comment, regardless of whether the check above passed.
+ - name: Check if contract baseline was updated in this PR
+ id: baseline-diff-check
+ if: always() && github.event.pull_request
+ run: |
+ git fetch origin "${{ github.event.pull_request.base.ref }}" --depth=1
+ if git diff --name-only "origin/${{ github.event.pull_request.base.ref }}" HEAD -- v2/api-report/skyflow-java.baseline.jar | grep -q .; then
+ echo "changed=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "changed=false" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Diff old vs new contract baseline
+ if: always() && steps.baseline-diff-check.outputs.changed == 'true'
+ run: |
+ curl -sL -o /tmp/japicmp-cli.jar "https://repo.maven.apache.org/maven2/com/github/siom79/japicmp/japicmp/0.26.0/japicmp-0.26.0-jar-with-dependencies.jar"
+ mvn -q -B dependency:build-classpath -pl v2 -Dmdep.outputFile=/tmp/v2-classpath.txt -Dmaven.javadoc.skip=true -Dgpg.skip=true
+ git show "origin/${{ github.event.pull_request.base.ref }}:v2/api-report/skyflow-java.baseline.jar" > /tmp/old-baseline.jar
+
+ java -jar /tmp/japicmp-cli.jar \
+ -o /tmp/old-baseline.jar \
+ -n v2/api-report/skyflow-java.baseline.jar \
+ -a protected \
+ -e "com.skyflow.generated.*;com.skyflow.utils.*" \
+ --old-classpath "$(cat /tmp/v2-classpath.txt)" \
+ --new-classpath "$(cat /tmp/v2-classpath.txt)" \
+ -m \
+ --markdown > /tmp/contract-baseline-diff.md || true
+
+ cat /tmp/contract-baseline-diff.md
+
+ - name: Comment contract baseline change on PR
+ if: always() && steps.baseline-diff-check.outputs.changed == 'true'
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const fs = require('fs');
+ const summary = fs.readFileSync('/tmp/contract-baseline-diff.md', 'utf8');
+ const marker = '';
+ const body = `${marker}\n## Contract baseline change detected\n\nThis PR updates \`v2/api-report/skyflow-java.baseline.jar\` (the approved public API contract). Here is exactly what it changes, comparing the baseline on \`${{ github.event.pull_request.base.ref }}\` against the baseline committed in this PR:\n\n${summary}`;
+ const { data: comments } = await github.rest.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ });
+ const existing = comments.find(c => c.body && c.body.includes(marker));
+ if (existing) {
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: existing.id,
+ body,
+ });
+ } else {
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body,
+ });
+ }
diff --git a/scripts/contract-snapshot-update.sh b/scripts/contract-snapshot-update.sh
new file mode 100755
index 00000000..95201b9a
--- /dev/null
+++ b/scripts/contract-snapshot-update.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# Regenerates the v2 contract-testing baseline (api-report/skyflow-java.baseline.jar)
+# from the CURRENT working tree and overwrites the committed snapshot.
+#
+# Run this after an intentional public API change to v2, review the resulting
+# git diff on the jar (a new binary blob) alongside your code change, and commit
+# both together. This is the only way the committed baseline should ever change -
+# japicmp never pulls a published version for this comparison.
+set -euo pipefail
+
+cd "$(dirname "$0")/.."
+
+mvn -B package -pl common,v2 -am -DskipTests -Dmaven.javadoc.skip=true -Dgpg.skip=true
+
+SHADED_JAR=$(ls v2/target/skyflow-java-*-with-common.jar | head -n1)
+
+if [ -z "$SHADED_JAR" ]; then
+ echo "Error: could not find a built v2/target/skyflow-java-*-with-common.jar. Did the build succeed?"
+ exit 1
+fi
+
+mkdir -p v2/api-report
+cp "$SHADED_JAR" v2/api-report/skyflow-java.baseline.jar
+
+echo "--------------------------"
+echo "Updated v2/api-report/skyflow-java.baseline.jar from $SHADED_JAR"
+echo "Review the diff and commit this file alongside your API change."
diff --git a/v2/api-report/skyflow-java.baseline.jar b/v2/api-report/skyflow-java.baseline.jar
new file mode 100644
index 00000000..c35c4ef7
Binary files /dev/null and b/v2/api-report/skyflow-java.baseline.jar differ
diff --git a/v2/pom.xml b/v2/pom.xml
index b35a9552..1ed591fe 100644
--- a/v2/pom.xml
+++ b/v2/pom.xml
@@ -46,6 +46,84 @@
9
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+
+ shade-for-japicmp
+ package
+
+ shade
+
+
+ true
+ with-common
+
+
+ com.skyflow:common
+
+
+
+
+
+
+
+ com.github.siom79.japicmp
+ japicmp-maven-plugin
+ 0.26.0
+
+
+
+
+ ${project.basedir}/api-report/skyflow-java.baseline.jar
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}-with-common.jar
+
+
+
+ protected
+ true
+ false
+ true
+ true
+
+ com.skyflow.generated.*
+ com.skyflow.utils.*
+
+ false
+ false
+
+
+
+
+ default-cli
+ verify
+
+ cmp
+
+
+
+
\ No newline at end of file