chore(release): sync v9.7.0 from source #749
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: SDK Smoke Tests | |
| # Validates that SDK examples and README curl commands work against the stack. | |
| # Catches broken documentation before merge. | |
| # | |
| # Runtime: ~4-5 minutes | |
| # Trigger: Changes to agent, orchestrator, demo examples, docker-compose, or enterprise code | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| - develop | |
| merge_group: | |
| branches: | |
| - main | |
| - develop | |
| push: | |
| branches: | |
| - main | |
| - develop | |
| paths: | |
| - 'platform/agent/**' | |
| - 'platform/orchestrator/**' | |
| - 'platform/connectors/**' | |
| - 'ee/platform/agent/**' | |
| - 'ee/platform/orchestrator/**' | |
| - 'ee/platform/connectors/**' | |
| - 'examples/demo/**' | |
| - 'docker-compose.yml' | |
| - 'docker-compose.enterprise.yml' | |
| - 'migrations/**' | |
| - 'scripts/e2e/**' | |
| - 'scripts/setup-e2e-testing.sh' | |
| - 'scripts/operators/provision-app-role.sh' | |
| - '.github/workflows/sdk-smoke-tests.yml' | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| # Cancel orphaned PR runs when a new commit is pushed. Pushes to main / merge_group | |
| # runs use the SHA so they never share a group and never cancel each other. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| AXONFLOW_TELEMETRY: 'off' | |
| # Timeouts and retry configuration | |
| HEALTH_CHECK_RETRIES: 30 | |
| HEALTH_CHECK_INTERVAL: 2 | |
| DOCKER_WAIT_TIMEOUT: 180 | |
| jobs: | |
| # ============================================================================= | |
| # Change Detection (PR and merge_group only) | |
| # ============================================================================= | |
| detect-changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' || github.event_name == 'merge_group' | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| sdk-relevant: ${{ steps.filter.outputs.sdk-relevant }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Detect SDK-relevant changes | |
| uses: dorny/paths-filter@v4 | |
| id: filter | |
| with: | |
| filters: | | |
| sdk-relevant: | |
| - 'platform/agent/**' | |
| - 'platform/orchestrator/**' | |
| - 'platform/connectors/**' | |
| - 'ee/platform/agent/**' | |
| - 'ee/platform/orchestrator/**' | |
| - 'ee/platform/connectors/**' | |
| - 'examples/demo/**' | |
| - 'docker-compose.yml' | |
| - 'docker-compose.enterprise.yml' | |
| - 'migrations/**' | |
| - 'scripts/e2e/**' | |
| - 'scripts/setup-e2e-testing.sh' | |
| - 'scripts/operators/provision-app-role.sh' | |
| - '.github/workflows/sdk-smoke-tests.yml' | |
| smoke-tests: | |
| name: SDK Smoke Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: [detect-changes] | |
| # Run if: push/workflow_dispatch OR (PR/merge_group with SDK-relevant changes) | |
| if: | | |
| always() && | |
| (github.event_name == 'push' || | |
| github.event_name == 'workflow_dispatch' || | |
| (needs.detect-changes.result == 'success' && needs.detect-changes.outputs.sdk-relevant == 'true')) | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.11' | |
| - name: Install dependencies | |
| run: | | |
| pip install requests | |
| # jq is pre-installed on GitHub runners, but verify | |
| jq --version | |
| - name: Start AxonFlow stack | |
| id: start-stack | |
| run: | | |
| echo "🚀 Starting AxonFlow stack..." | |
| docker compose up -d --wait --wait-timeout ${{ env.DOCKER_WAIT_TIMEOUT }} || { | |
| echo "❌ Docker compose failed to start" | |
| echo "📋 Docker compose logs:" | |
| docker compose logs | |
| exit 1 | |
| } | |
| echo "✅ Docker compose started" | |
| - name: Wait for services to be healthy | |
| shell: bash | |
| run: | | |
| echo "⏳ Waiting for services to be healthy..." | |
| # Function to check health with retries | |
| wait_for_health() { | |
| local url=$1 | |
| local name=$2 | |
| local retries=${{ env.HEALTH_CHECK_RETRIES }} | |
| local interval=${{ env.HEALTH_CHECK_INTERVAL }} | |
| for i in $(seq 1 $retries); do | |
| if curl -sf "$url" > /dev/null 2>&1; then | |
| echo "✅ $name is healthy" | |
| return 0 | |
| fi | |
| echo "⏳ Waiting for $name... (attempt $i/$retries)" | |
| sleep $interval | |
| done | |
| echo "❌ $name failed to become healthy after $retries attempts" | |
| return 1 | |
| } | |
| # Check both services | |
| wait_for_health "http://localhost:8080/health" "Agent" || exit 1 | |
| wait_for_health "http://localhost:8081/health" "Orchestrator" || exit 1 | |
| echo "" | |
| echo "✅ All services are healthy!" | |
| - name: Test PII Detection (SSN - Redact Mode) | |
| shell: bash | |
| run: | | |
| echo "🧪 Testing PII detection (SSN - redact mode)..." | |
| response=$(curl -s -X POST http://localhost:8080/api/policy/pre-check \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"user_token": "test-user", "client_id": "smoke-test", "query": "Look up customer with SSN 123-45-6789"}') | |
| echo "Response: $response" | |
| # SSN detection defaults to redact mode (approved=true, requires_redaction=true) | |
| # Issue #891: PII now defaults to redact instead of block | |
| if echo "$response" | jq -e '.requires_redaction == true' > /dev/null 2>&1; then | |
| policy=$(echo "$response" | jq -r '.policies[0]') | |
| echo "✅ SSN detected, flagged for redaction (policy: $policy)" | |
| else | |
| echo "❌ SSN not flagged for redaction" | |
| echo "Expected: requires_redaction=true" | |
| exit 1 | |
| fi | |
| - name: Test PII Detection (Credit Card - Redact Mode) | |
| shell: bash | |
| run: | | |
| echo "🧪 Testing PII detection (Credit Card - redact mode)..." | |
| response=$(curl -s -X POST http://localhost:8080/api/policy/pre-check \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"user_token": "test-user", "client_id": "smoke-test", "query": "Process payment for card 4111-1111-1111-1111"}') | |
| echo "Response: $response" | |
| # Credit card detection defaults to redact mode (approved=true, requires_redaction=true) | |
| # Issue #891: PII now defaults to redact instead of block | |
| if echo "$response" | jq -e '.requires_redaction == true' > /dev/null 2>&1; then | |
| echo "✅ Credit card detected, flagged for redaction" | |
| else | |
| echo "❌ Credit card not flagged for redaction" | |
| echo "Expected: requires_redaction=true" | |
| exit 1 | |
| fi | |
| - name: Test SQL Injection Blocking | |
| shell: bash | |
| run: | | |
| echo "🧪 Testing SQL injection blocking..." | |
| response=$(curl -s -X POST http://localhost:8080/api/policy/pre-check \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"user_token": "test-user", "client_id": "smoke-test", "query": "SELECT * FROM users UNION SELECT * FROM passwords"}') | |
| echo "Response: $response" | |
| if echo "$response" | jq -e '.approved == false or .policyApproved == false or .blocked == true' > /dev/null 2>&1; then | |
| echo "✅ SQL injection correctly blocked" | |
| else | |
| echo "❌ SQL injection not blocked" | |
| echo "Expected: approved=false or blocked=true" | |
| exit 1 | |
| fi | |
| - name: Test Safe Query (Baseline) | |
| shell: bash | |
| run: | | |
| echo "🧪 Testing safe query passes..." | |
| response=$(curl -s -X POST http://localhost:8080/api/policy/pre-check \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"user_token": "test-user", "client_id": "smoke-test", "query": "What is the weather today?"}') | |
| echo "Response: $response" | |
| # Safe query should be approved | |
| if echo "$response" | jq -e '.approved == true or .policyApproved == true or .blocked == false' > /dev/null 2>&1; then | |
| echo "✅ Safe query correctly approved" | |
| else | |
| echo "❌ Safe query incorrectly blocked" | |
| echo "Expected: approved=true or blocked=false" | |
| exit 1 | |
| fi | |
| - name: Collect logs on failure | |
| if: failure() | |
| run: | | |
| echo "📋 Collecting diagnostic logs..." | |
| echo "" | |
| echo "=== Docker Compose Status ===" | |
| docker compose ps | |
| echo "" | |
| echo "=== Agent Logs (last 100 lines) ===" | |
| docker compose logs --tail=100 agent 2>/dev/null || echo "(no agent logs)" | |
| echo "" | |
| echo "=== Orchestrator Logs (last 100 lines) ===" | |
| docker compose logs --tail=100 orchestrator 2>/dev/null || echo "(no orchestrator logs)" | |
| echo "" | |
| echo "=== PostgreSQL Logs (last 50 lines) ===" | |
| docker compose logs --tail=50 postgres 2>/dev/null || echo "(no postgres logs)" | |
| - name: Cleanup | |
| if: always() | |
| run: | | |
| docker compose down -v --remove-orphans 2>/dev/null || true | |
| # ============================================================================= | |
| # e2e-production-posture (issue #2391 / PR-E1) | |
| # ============================================================================= | |
| # Mandatory production-posture E2E. Boots the enterprise stack under | |
| # AXONFLOW_DB_USE_APP_ROLE=true with provisioned app-role + platform-admin | |
| # DSNs, asserts every main pool actually connected AS axonflow_app_role, | |
| # then runs the production-posture example suite from | |
| # scripts/e2e/production_posture_registry.txt. Entries marked | |
| # E2E-EXPECT-FAIL-UNTIL-PR-C1 are expected to fail with an RLS policy | |
| # violation until PR-C1 lands the WithOrgScope wraps on the agent-side | |
| # INSERT surface; the runner gates each entry against its expected outcome. | |
| # | |
| # Gating: This job is MANDATORY (the smoke-test-summary job below has it in | |
| # `needs` and asserts result == 'success' or 'skipped'). v8.0.0 tag-cut is | |
| # blocked on this job being green. | |
| e2e-production-posture: | |
| name: E2E Production-Posture (axonflow_app_role) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| # The production-posture job builds the enterprise compose stack via | |
| # `docker compose -f docker-compose.yml -f docker-compose.enterprise.yml`, | |
| # which requires docker-compose.enterprise.yml to be present in the | |
| # working tree. That file is enterprise-only and is excluded from the | |
| # community sync filter, so on community-mirror checkouts this job has | |
| # no buildable target and must skip. `hashFiles()` is not allowed in a | |
| # job-level `if:` expression (per GH Actions docs — `hashFiles` is only | |
| # available inside `jobs.<job_id>.steps.*` contexts), so the gate uses | |
| # `github.repository` instead: run only on the enterprise repo where | |
| # the file ships; skip on the community mirror. | |
| if: github.repository == 'getaxonflow/axonflow-enterprise' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Install postgresql-client (psql) | |
| run: | | |
| # provision-app-role.sh needs psql on the host (not in the postgres | |
| # container) — it connects to postgres via the docker-published port | |
| # 5432 from the GH runner shell. | |
| sudo apt-get update -qq | |
| sudo apt-get install -y --no-install-recommends postgresql-client | |
| - name: Start AxonFlow stack with USE_APP_ROLE=false (so migrations run as master) | |
| run: | | |
| echo "Phase 1: boot stack under master role so mig 098 can CREATE ROLE" | |
| # Pre-build images (the enterprise binary build is the long pole | |
| # ~6-9 min cold; folding it into `up --wait` would race the | |
| # wait-timeout). Splitting build from up gives the wait phase a | |
| # clean budget. The dedicated portal-e2e workflow uses the same | |
| # pattern for the same reason. | |
| echo "Phase 1a: build images (no wait timeout — build phase is bounded by the job timeout)" | |
| docker compose -f docker-compose.yml -f docker-compose.enterprise.yml build axonflow-agent axonflow-orchestrator axonflow-customer-portal || { | |
| echo "::error::Docker compose build failed" | |
| exit 1 | |
| } | |
| echo "Phase 1b: up -d (no --wait — we'll poll /health manually with a long budget)" | |
| docker compose -f docker-compose.yml -f docker-compose.enterprise.yml up -d || { | |
| echo "::error::Docker compose up failed" | |
| docker compose -f docker-compose.yml -f docker-compose.enterprise.yml logs | |
| exit 1 | |
| } | |
| echo "Phase 1c: wait for agent /health (migrations applied) — up to 240s" | |
| healthy=0 | |
| for i in $(seq 1 120); do | |
| if curl -sf http://localhost:8080/health >/dev/null 2>&1; then | |
| echo "agent /health OK on attempt $i" | |
| healthy=1 | |
| break | |
| fi | |
| sleep 2 | |
| done | |
| if [ "$healthy" -ne 1 ]; then | |
| echo "::error::agent never became healthy after 240s" | |
| docker compose -f docker-compose.yml -f docker-compose.enterprise.yml ps | |
| docker logs --tail=300 axonflow-agent 2>&1 || true | |
| exit 1 | |
| fi | |
| - name: Phase 2 — Provision axonflow_app_role + axonflow_platform_admin | |
| env: | |
| APP_ROLE_PASSWORD: ci-pp-app-role-pw | |
| PLATFORM_ADMIN_PASSWORD: ci-pp-admin-pw | |
| DATABASE_URL: postgres://axonflow:localdev123@127.0.0.1:5432/axonflow?sslmode=disable | |
| run: | | |
| set -euo pipefail | |
| echo "Phase 2: ALTER ROLE both roles via scripts/operators/provision-app-role.sh" | |
| bash scripts/operators/provision-app-role.sh | |
| - name: Phase 3 — Restart agent/orchestrator/customer-portal with USE_APP_ROLE=true | |
| run: | | |
| set -euo pipefail | |
| echo "Phase 3: append app-role env vars to .env and recreate the 3 main services" | |
| touch .env | |
| # Strip any prior entries to keep .env clean across re-runs. | |
| sed -i '/^AXONFLOW_DB_USE_APP_ROLE=/d' .env | |
| sed -i '/^AXONFLOW_DB_APP_ROLE_URL=/d' .env | |
| sed -i '/^AXONFLOW_DB_PLATFORM_ADMIN_URL=/d' .env | |
| echo "AXONFLOW_DB_USE_APP_ROLE=true" >> .env | |
| echo "AXONFLOW_DB_APP_ROLE_URL=postgres://axonflow_app_role:ci-pp-app-role-pw@postgres:5432/axonflow?sslmode=disable" >> .env | |
| echo "AXONFLOW_DB_PLATFORM_ADMIN_URL=postgres://axonflow_platform_admin:ci-pp-admin-pw@postgres:5432/axonflow?sslmode=disable" >> .env | |
| docker compose -f docker-compose.yml -f docker-compose.enterprise.yml up -d \ | |
| --no-deps --force-recreate \ | |
| axonflow-agent axonflow-orchestrator axonflow-customer-portal | |
| echo "Waiting for /health on all 3 services post-restart..." | |
| # Each service gets up to 60×2s = 120s. If any fails to come up, | |
| # exit 1 — falling through silently would make the Phase-4 probe | |
| # produce a misleading "service emitted zero log output" error | |
| # instead of the real "container failed to restart" error. | |
| for svc_port in "agent:8080" "orchestrator:8081" "customer-portal:8082"; do | |
| port="${svc_port##*:}" | |
| name="${svc_port%%:*}" | |
| healthy=0 | |
| for i in $(seq 1 60); do | |
| if curl -sf "http://localhost:${port}/health" >/dev/null 2>&1; then | |
| echo "$name /health OK on attempt $i" | |
| healthy=1 | |
| break | |
| fi | |
| sleep 2 | |
| done | |
| if [ "$healthy" -ne 1 ]; then | |
| echo "::error::$name never became healthy after 60 attempts (~120s) post-restart." | |
| echo "=== docker compose ps ===" | |
| docker compose -f docker-compose.yml -f docker-compose.enterprise.yml ps || true | |
| echo "=== last 200 lines of axonflow-$name logs ===" | |
| docker logs --tail=200 "axonflow-$name" 2>&1 || true | |
| exit 1 | |
| fi | |
| done | |
| - name: Phase 4 — Boot-log probe (MANDATORY) | |
| run: | | |
| set -euo pipefail | |
| echo "Phase 4: assert every main pool is on axonflow_app_role" | |
| bash scripts/e2e/probe-boot-log.sh | |
| - name: Phase 5 — Run production-posture suite | |
| env: | |
| # SUITE_EXPECT_BOOT_RLS_EVIDENCE=0 (clean-boot regression-guard mode). | |
| # Issue #2400 closed by wrapping ee/.../heartbeat.go::sendHeartbeat | |
| # (the EE mirror copied over the community variant by the | |
| # enterprise Dockerfile). With that wrap in place the production- | |
| # posture boot is RLS-violation-free. Any new un-wrapped INSERT | |
| # into an RLS-gated table surfaces here. | |
| SUITE_EXPECT_BOOT_RLS_EVIDENCE: '0' | |
| run: | | |
| set -euo pipefail | |
| echo "Phase 5: run scripts/e2e/run-production-posture-suite.sh" | |
| echo " SUITE_EXPECT_BOOT_RLS_EVIDENCE=$SUITE_EXPECT_BOOT_RLS_EVIDENCE (clean-boot regression guard)" | |
| bash scripts/e2e/run-production-posture-suite.sh | |
| - name: Dump diagnostic logs on failure | |
| if: failure() | |
| run: | | |
| set +e | |
| echo "=== docker compose ps ===" | |
| docker compose -f docker-compose.yml -f docker-compose.enterprise.yml ps | |
| echo "" | |
| for svc in axonflow-agent axonflow-orchestrator axonflow-customer-portal axonflow-postgres; do | |
| echo "=== logs: $svc (last 300 lines) ===" | |
| docker logs --tail=300 "$svc" 2>&1 || true | |
| echo "" | |
| echo "=== current_user= boot-log lines from $svc ===" | |
| docker logs "$svc" 2>&1 | grep -E 'current_user=' || true | |
| echo "" | |
| done | |
| - name: Cleanup | |
| if: always() | |
| run: | | |
| docker compose -f docker-compose.yml -f docker-compose.enterprise.yml down -v --remove-orphans 2>/dev/null || true | |
| smoke-test-summary: | |
| name: Smoke Test Summary | |
| runs-on: ubuntu-latest | |
| needs: [detect-changes, smoke-tests, e2e-production-posture] | |
| if: always() | |
| steps: | |
| - name: Check results | |
| run: | | |
| echo "🧪 SDK Smoke Tests Complete" | |
| echo "" | |
| echo "Results:" | |
| echo " Detect Changes: ${{ needs.detect-changes.result }}" | |
| echo " Smoke Tests: ${{ needs.smoke-tests.result }}" | |
| echo " E2E Production-Posture: ${{ needs.e2e-production-posture.result }}" | |
| - name: Fail if tests failed | |
| # smoke-tests: success or skipped (skipped = no SDK-relevant changes). | |
| # e2e-production-posture: success on enterprise checkouts where the | |
| # enterprise compose file is present + drives the build. On | |
| # community-mirror checkouts the job's job-level `if:` (hashFiles | |
| # on docker-compose.enterprise.yml) skips it because the enterprise | |
| # compose file is excluded from the sync filter and there's no | |
| # buildable target. Accept `skipped` here for that case; `failure` | |
| # still fails the summary. | |
| if: | | |
| needs.detect-changes.result == 'failure' || | |
| (needs.smoke-tests.result != 'success' && needs.smoke-tests.result != 'skipped') || | |
| (needs.e2e-production-posture.result != 'success' && needs.e2e-production-posture.result != 'skipped') | |
| run: | | |
| echo "❌ SDK smoke tests failed" | |
| echo " smoke-tests: ${{ needs.smoke-tests.result }}" | |
| echo " e2e-production-posture: ${{ needs.e2e-production-posture.result }}" | |
| exit 1 | |
| - name: Success message | |
| if: success() | |
| run: | | |
| echo "✅ SDK smoke tests passed!" | |
| echo "" | |
| echo "Validated:" | |
| echo " - PII Detection (SSN - redact mode, requires_redaction=true)" | |
| echo " - PII Detection (Credit Card - redact mode, requires_redaction=true)" | |
| echo " - SQL Injection Blocking (approved=false)" | |
| echo " - Safe Query Approval (approved=true)" | |
| echo " - Production-Posture E2E: every main pool on axonflow_app_role (#2391)" |