Skip to content

Describe durable service-call snapshots #293

Describe durable service-call snapshots

Describe durable service-call snapshots #293

name: Version Validation
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
version-validation:
name: Test CLI and SDK version validation
runs-on: ubuntu-latest
steps:
- name: Checkout server
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Generate APP_KEY
id: app-key
run: |
# Generate a 32-character base64 key for Laravel
KEY=$(openssl rand -base64 32)
echo "key=base64:$KEY" >> $GITHUB_OUTPUT
# Test 1: Compatible version (2.0.0) - CLI success
- name: Start server (version 2.0.0)
env:
APP_KEY: ${{ steps.app-key.outputs.key }}
DW_AUTH_TOKEN: test-token-123
APP_VERSION: 2.0.0
run: |
docker compose up -d --wait
sleep 5
# Verify server is healthy
curl -f http://localhost:8080/api/health || exit 1
# Verify version endpoint returns 2.0.0
VERSION=$(curl -fsS -H "Authorization: Bearer ${DW_AUTH_TOKEN}" http://localhost:8080/api/cluster/info | jq -r '.version')
echo "Server version: $VERSION"
if [ "$VERSION" != "2.0.0" ]; then
echo "ERROR: Expected version 2.0.0, got $VERSION"
exit 1
fi
- name: Install CLI
run: |
cd /tmp
git clone https://github.com/durable-workflow/cli.git
cd cli
composer install --no-dev --optimize-autoloader
chmod +x bin/dw
echo "/tmp/cli/bin" >> $GITHUB_PATH
- name: Test CLI with compatible server (should succeed)
env:
DURABLE_WORKFLOW_SERVER_URL: http://localhost:8080
DURABLE_WORKFLOW_AUTH_TOKEN: test-token-123
DURABLE_WORKFLOW_NAMESPACE: default
run: |
# CLI should connect successfully to version 2.0.0
dw server:info || exit 1
echo "✅ CLI successfully connected to compatible server"
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.10'
- name: Install Python SDK
run: |
# Version-manifest validation tracks the source contract shared by
# server main and sdk-python main. Published PyPI artifacts may lag
# this compatibility behavior.
pip install "durable-workflow @ git+https://github.com/durable-workflow/sdk-python.git@main"
- name: Test Python SDK with compatible server (should succeed)
run: |
cat > /tmp/test_worker.py << 'EOF'
import asyncio
from durable_workflow import Client, Worker, workflow, activity
@activity.defn(name="test_activity")
def test_activity() -> str:
return "ok"
@workflow.defn(name="test_workflow")
class TestWorkflow:
def run(self, ctx):
return "ok"
async def main():
client = Client("http://localhost:8080", token="test-token-123", namespace="default")
# This should succeed because server is 2.0.0
worker = Worker(
client,
task_queue="test-queue",
workflows=[TestWorkflow],
activities=[test_activity],
worker_id="test-worker-compat",
)
# Worker registration validates version in _register()
# We'll just call _register() directly to test validation
try:
await worker._register()
print("✅ Python SDK successfully connected to compatible server")
finally:
await client.aclose()
asyncio.run(main())
EOF
python /tmp/test_worker.py
- name: Stop server
run: docker compose down -v
# Test 2: Top-level APP_VERSION is build identity only. Protocol
# manifests remain authoritative for client compatibility.
- name: Create docker-compose override for version 3.0.0
run: |
cat > docker-compose.override.yml << 'EOF'
services:
bootstrap:
environment:
APP_VERSION: "3.0.0"
server:
environment:
APP_VERSION: "3.0.0"
worker:
environment:
APP_VERSION: "3.0.0"
scheduler:
environment:
APP_VERSION: "3.0.0"
EOF
- name: Start server (version 3.0.0)
env:
APP_KEY: ${{ steps.app-key.outputs.key }}
DW_AUTH_TOKEN: test-token-123
run: |
docker compose up -d --wait
sleep 5
# Verify server reports version 3.0.0
VERSION=$(curl -fsS -H "Authorization: Bearer ${DW_AUTH_TOKEN}" http://localhost:8080/api/cluster/info | jq -r '.version')
echo "Server version: $VERSION"
if [ "$VERSION" != "3.0.0" ]; then
echo "ERROR: Expected version 3.0.0, got $VERSION"
exit 1
fi
- name: Test CLI with informational top-level version (should succeed)
env:
DURABLE_WORKFLOW_SERVER_URL: http://localhost:8080
DURABLE_WORKFLOW_AUTH_TOKEN: test-token-123
DURABLE_WORKFLOW_NAMESPACE: default
run: |
dw server:info || exit 1
echo "✅ CLI accepted compatible protocol manifests despite APP_VERSION=3.0.0"
- name: Test Python SDK with informational top-level version (should succeed)
run: |
cat > /tmp/test_worker_app_version.py << 'EOF'
import asyncio
from durable_workflow import Client, Worker, workflow, activity
@activity.defn(name="test_activity")
def test_activity() -> str:
return "ok"
@workflow.defn(name="test_workflow")
class TestWorkflow:
def run(self, ctx):
return "ok"
async def main():
client = Client("http://localhost:8080", token="test-token-123", namespace="default")
worker = Worker(
client,
task_queue="test-queue",
workflows=[TestWorkflow],
activities=[test_activity],
worker_id="test-worker-incompat",
)
try:
await worker._register()
print("✅ Python SDK accepted compatible protocol manifests despite APP_VERSION=3.0.0")
finally:
await client.aclose()
asyncio.run(main())
EOF
python /tmp/test_worker_app_version.py
- name: Stop server
run: docker compose down -v
# Test 3: Incompatible worker protocol manifest. The CLI only uses the
# control plane and should still succeed; the Python worker validates the
# worker protocol and must fail closed.
- name: Create docker-compose override with incompatible worker protocol
run: |
cat > docker-compose.override.yml << 'EOF'
services:
bootstrap:
environment:
DW_WORKER_PROTOCOL_VERSION: "2.0"
server:
environment:
DW_WORKER_PROTOCOL_VERSION: "2.0"
worker:
environment:
DW_WORKER_PROTOCOL_VERSION: "2.0"
scheduler:
environment:
DW_WORKER_PROTOCOL_VERSION: "2.0"
EOF
- name: Start server (worker protocol 2.0)
env:
APP_KEY: ${{ steps.app-key.outputs.key }}
DW_AUTH_TOKEN: test-token-123
run: |
docker compose up -d --wait
sleep 5
VERSION=$(curl -fsS -H "Authorization: Bearer ${DW_AUTH_TOKEN}" http://localhost:8080/api/cluster/info | jq -r '.worker_protocol.version')
echo "Worker protocol version: $VERSION"
if [ "$VERSION" != "2.0" ]; then
echo "ERROR: Expected worker protocol 2.0, got $VERSION"
exit 1
fi
- name: Test CLI with incompatible worker protocol (should succeed)
env:
DURABLE_WORKFLOW_SERVER_URL: http://localhost:8080
DURABLE_WORKFLOW_AUTH_TOKEN: test-token-123
DURABLE_WORKFLOW_NAMESPACE: default
run: |
dw server:info || exit 1
echo "✅ CLI ignored worker-protocol-only incompatibility"
- name: Test Python SDK with incompatible worker protocol (should fail with clear error)
run: |
cat > /tmp/test_worker_incompatible.py << 'EOF'
import asyncio
from durable_workflow import Client, Worker, workflow, activity
@activity.defn(name="test_activity")
def test_activity() -> str:
return "ok"
@workflow.defn(name="test_workflow")
class TestWorkflow:
def run(self, ctx):
return "ok"
async def main():
client = Client("http://localhost:8080", token="test-token-123", namespace="default")
worker = Worker(
client,
task_queue="test-queue",
workflows=[TestWorkflow],
activities=[test_activity],
worker_id="test-worker-incompat",
)
try:
await worker._register()
print("❌ ERROR: Worker registration should have failed with incompatible worker protocol")
await client.aclose()
exit(1)
except RuntimeError as e:
error_msg = str(e)
print(f"Worker registration error: {error_msg}")
if "unsupported worker_protocol.version" in error_msg and "sdk-python 0.2.x" in error_msg:
print("✅ Python SDK failed with correct worker protocol error message")
else:
print("❌ ERROR: Python SDK error message doesn't match expected format")
print("Expected: 'unsupported worker_protocol.version' and 'sdk-python 0.2.x'")
print(f"Got: {error_msg}")
await client.aclose()
exit(1)
finally:
await client.aclose()
asyncio.run(main())
EOF
python /tmp/test_worker_incompatible.py
- name: Stop server
run: docker compose down -v
# Test 4: Unknown top-level version remains informational.
- name: Create docker-compose override with no version
run: |
cat > docker-compose.override.yml << 'EOF'
services:
bootstrap:
environment:
APP_VERSION: "unknown"
server:
environment:
APP_VERSION: "unknown"
worker:
environment:
APP_VERSION: "unknown"
scheduler:
environment:
APP_VERSION: "unknown"
EOF
- name: Start server (version unknown)
env:
APP_KEY: ${{ steps.app-key.outputs.key }}
DW_AUTH_TOKEN: test-token-123
run: |
docker compose up -d --wait
sleep 5
- name: Test CLI with unknown top-level version (should succeed)
env:
DURABLE_WORKFLOW_SERVER_URL: http://localhost:8080
DURABLE_WORKFLOW_AUTH_TOKEN: test-token-123
DURABLE_WORKFLOW_NAMESPACE: default
run: |
dw server:info || exit 1
echo "✅ CLI accepted compatible protocol manifests despite APP_VERSION=unknown"
- name: Test Python SDK with unknown top-level version (should succeed)
run: |
cat > /tmp/test_worker_unknown.py << 'EOF'
import asyncio
from durable_workflow import Client, Worker, workflow, activity
@activity.defn(name="test_activity")
def test_activity() -> str:
return "ok"
@workflow.defn(name="test_workflow")
class TestWorkflow:
def run(self, ctx):
return "ok"
async def main():
client = Client("http://localhost:8080", token="test-token-123", namespace="default")
worker = Worker(
client,
task_queue="test-queue",
workflows=[TestWorkflow],
activities=[test_activity],
worker_id="test-worker-unknown",
)
# Should succeed because protocol manifests are valid.
try:
await worker._register()
print("✅ Python SDK accepted compatible protocol manifests despite APP_VERSION=unknown")
finally:
await client.aclose()
asyncio.run(main())
EOF
python /tmp/test_worker_unknown.py
- name: Stop server
if: always()
run: docker compose down -v