Repository navigation
Package the native server as an opt-in release-candidate image #2751
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: PHPUnit Feature | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| merge_group: | |
| workflow_dispatch: | |
| inputs: | |
| corpus_base_ref: | |
| description: Full Git commit SHA to classify regression-corpus changes against | |
| required: true | |
| type: string | |
| prepared_local_workflow_commit: | |
| description: Optional full Native commit for candidate prepared-local source qualification. Does not replace locked-package CI. | |
| required: false | |
| type: string | |
| permissions: | |
| contents: read | |
| # A new PR cycle supersedes its previous qualification. Main and manual runs | |
| # keep distinct run IDs so each requested qualification can finish. | |
| concurrency: | |
| group: phpunit-feature-${{ github.event_name }}-${{ github.event.pull_request.number || github.run_id }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| action-policy: | |
| name: Central action policy preflight | |
| if: ${{ github.server_url == 'https://github.com' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Check out candidate source | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| persist-credentials: false | |
| - name: Check out central action policy | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| repository: durable-workflow/.github | |
| ref: main | |
| path: .central-action-policy | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 | |
| with: | |
| python-version: "3.13" | |
| - name: Bootstrap patched pip trust | |
| uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@75587fc33ff40c34f32ef777293dad56bb6f48c7 # clean-trust | |
| - name: Install policy dependencies | |
| run: python -m pip install PyYAML==6.0.2 | |
| - name: Require centrally approved immutable action commits | |
| run: >- | |
| python .central-action-policy/scripts/qualification_policy.py validate | |
| --policy .central-action-policy/qualification/policy.json | |
| --target server | |
| --workflow-directory .github/workflows | |
| preflight: | |
| name: Repository contracts | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout server | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Validate release metadata and CI scripts | |
| run: | | |
| node --check scripts/ci/sync-source-release.mjs | |
| node scripts/ci/sync-source-release.mjs --check | |
| node --test scripts/ci/test-source-release.mjs | |
| - name: Require safe PHP image build context | |
| run: node --test tests/Unit/PhpImageContextTest.mjs | |
| - name: Restore PHP's SQLite maintenance loop on every path | |
| run: node --test tests/Unit/PhpMaintenanceWindowTest.mjs | |
| - name: Restore the patch cohort's maintenance loop on every path | |
| run: node --test tests/Unit/PatchCohortMaintenanceTest.mjs | |
| - name: Run the required checks for merge queue groups | |
| run: node --test tests/Unit/MergeGroupTriggersTest.mjs tests/Unit/MergeQueueSonarTest.mjs | |
| - name: Report one Shared Server fixtures result on every pull request | |
| run: node --test tests/Unit/ServerParityAggregateTest.mjs tests/Unit/ServerParityPartsTest.mjs | |
| - name: Validate embedded Workflow package authority | |
| run: php scripts/ci/test-workflow-package-authority.php | |
| - name: Install worker OpenAPI validation dependencies | |
| # In the pinned Composer image, whose CA store is checked, not with the | |
| # runner's own store (GHSA-6q48-6c7w-9gw2). | |
| run: >- | |
| docker run --rm --user "$(id -u):$(id -g)" -e COMPOSER_HOME=/tmp/composer | |
| -v "$PWD:/app" -w /app composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee | |
| install | |
| --no-dev | |
| --no-scripts | |
| --no-interaction | |
| --no-progress | |
| --prefer-dist | |
| --ignore-platform-reqs | |
| - name: Require versioned worker OpenAPI evolution | |
| env: | |
| OPENAPI_BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'merge_group' && github.event.merge_group.base_sha || github.event_name == 'push' && github.event.before || inputs.corpus_base_ref }} | |
| run: php scripts/ci/check-worker-openapi-evolution.php "$OPENAPI_BASE_REF" | |
| - name: Audit locked PHP dependencies | |
| # In the pinned Composer image, whose CA store is checked (GHSA-6q48-6c7w-9gw2). | |
| run: >- | |
| docker run --rm --user "$(id -u):$(id -g)" -e COMPOSER_HOME=/tmp/composer -v "$PWD:/app:ro" -w /app composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee audit --locked --no-interaction | |
| - name: Require durable codec evidence | |
| env: | |
| CORPUS_BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'merge_group' && github.event.merge_group.base_sha || github.event_name == 'push' && github.event.before || inputs.corpus_base_ref }} | |
| run: | | |
| set -euo pipefail | |
| if [[ ! "$CORPUS_BASE_REF" =~ ^[0-9a-f]{40}$ ]] || [[ "$CORPUS_BASE_REF" =~ ^0+$ ]]; then | |
| echo "Invalid regression corpus base revision: $CORPUS_BASE_REF" >&2 | |
| exit 1 | |
| fi | |
| git rev-parse --verify "$CORPUS_BASE_REF^{commit}" | |
| python scripts/ci/validate-regression-corpus.py --base-ref "$CORPUS_BASE_REF" | |
| - name: Prove corpus policy guards cannot be weakened | |
| run: python scripts/ci/test-regression-corpus-policy.py | |
| - name: Prove pip starts from the patched, root-checked bootstrap | |
| run: python3 -m unittest tests/Unit/Support/patched_pip_test.py | |
| - name: Prove cargo trusts only a checked CA file and an empty directory | |
| env: | |
| DW_REQUIRE_CARGO_TRUST_TEST: '1' | |
| run: python3 -m unittest tests/Unit/Support/cargo_trust_test.py | |
| - name: Prove every active Cargo.lock resolves the fixed TLS trust crates | |
| run: python3 -m unittest tests/Unit/Support/cargo_lock_trust_test.py | |
| - name: Prove Composer runs pinned or confined to a checked CA file | |
| env: | |
| DW_REQUIRE_COMPOSER_TRUST_TEST: '1' | |
| run: python3 -m unittest tests/Unit/Support/composer_trust_test.py tests/Unit/Support/release_composer_audit_test.py | |
| - name: Prove the image installs and checks the fixed Brotli decoder | |
| run: python3 -m unittest tests/Unit/Support/brotli_trust_test.py | |
| feature: | |
| name: PHPUnit feature suite | |
| needs: action-policy | |
| # Report policy failures while allowing cancelled revisions to stop. | |
| if: ${{ always() && !cancelled() }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| steps: | |
| - name: Require central action policy preflight | |
| if: ${{ github.server_url == 'https://github.com' }} | |
| env: | |
| ACTION_POLICY_RESULT: ${{ needs.action-policy.result }} | |
| run: test "$ACTION_POLICY_RESULT" = success | |
| - name: Checkout server | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Cache public mirrored build inputs under their canonical image names | |
| run: | | |
| docker pull mirror.gcr.io/library/php:8.3-apache | |
| docker tag mirror.gcr.io/library/php:8.3-apache php:8.3-apache | |
| - name: Resolve locked workflow package authority | |
| id: workflow | |
| run: php scripts/ci/resolve-workflow-package-authority.php | |
| - name: Checkout workflow package from GitHub | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| repository: durable-workflow/workflow | |
| ref: ${{ steps.workflow.outputs.commit }} | |
| path: workflow-package | |
| persist-credentials: false | |
| - name: Verify workflow package source provenance | |
| shell: bash | |
| env: | |
| WORKFLOW_PACKAGE_SOURCE: ${{ steps.workflow.outputs.source }} | |
| WORKFLOW_PACKAGE_REF: ${{ steps.workflow.outputs.ref }} | |
| WORKFLOW_PACKAGE_COMMIT: ${{ steps.workflow.outputs.commit }} | |
| run: | | |
| set -euo pipefail | |
| resolved_commit="$(git -C workflow-package rev-parse HEAD)" | |
| if [[ "$resolved_commit" != "$WORKFLOW_PACKAGE_COMMIT" ]]; then | |
| echo "Resolved Workflow commit $resolved_commit does not match $WORKFLOW_PACKAGE_COMMIT" >&2 | |
| exit 1 | |
| fi | |
| printf '%s\n' \ | |
| "$WORKFLOW_PACKAGE_SOURCE" \ | |
| "$WORKFLOW_PACKAGE_REF" \ | |
| "$resolved_commit" \ | |
| > workflow-package/.package-provenance | |
| rm -rf workflow-package/.git | |
| - name: Run feature, Nexus, and regression corpus suites | |
| shell: bash | |
| env: | |
| CORPUS_BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'merge_group' && github.event.merge_group.base_sha || github.event_name == 'push' && github.event.before || inputs.corpus_base_ref }} | |
| run: | | |
| set -euo pipefail | |
| if [[ ! "$CORPUS_BASE_REF" =~ ^[0-9a-f]{40}$ ]] || [[ "$CORPUS_BASE_REF" =~ ^0+$ ]]; then | |
| echo "Invalid regression corpus base revision: $CORPUS_BASE_REF" >&2 | |
| exit 1 | |
| fi | |
| docker build --target base -t durable-workflow-server-phpunit-base . | |
| docker run --rm --entrypoint sh durable-workflow-server-phpunit-base \ | |
| -c 'node --version >/dev/null && python3 -m venv /tmp/dw-conformance-venv && test -x /tmp/dw-conformance-venv/bin/python3' | |
| docker run --rm --network none --entrypoint server-verify-trust durable-workflow-server-phpunit-base | |
| docker build -t durable-workflow-server-corpus-validator - <<'DOCKERFILE' | |
| FROM durable-workflow-server-phpunit-base | |
| RUN apt-get update \ | |
| && apt-get install -y --no-install-recommends git \ | |
| && rm -rf /var/lib/apt/lists/* \ | |
| && server-verify-trust final | |
| DOCKERFILE | |
| docker network create \ | |
| "dw-server-feature-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" | |
| docker run --detach \ | |
| --name "dw-server-s3-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| --network "dw-server-feature-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| --env AWS_ACCESS_KEY_ID=dw-s3-access \ | |
| --env AWS_SECRET_ACCESS_KEY=dw-s3-secret-key \ | |
| ghcr.io/chrislusf/seaweedfs@sha256:e1ee85a3089d96c4f3df699a0944468557813f5df306819c9d368c4039070ddf | |
| docker run --detach \ | |
| --name "dw-server-backup-mysql-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| --network "dw-server-feature-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| --memory 1g --memory-swap 1g \ | |
| --env MYSQL_ALLOW_EMPTY_PASSWORD=yes --env MYSQL_ROOT_HOST=% \ | |
| --health-cmd='mysqladmin --silent --host=127.0.0.1 ping' \ | |
| --health-interval=1s --health-start-period=20s --health-retries=30 \ | |
| mirror.gcr.io/library/mysql@sha256:679e7e924f38a3cbb62a3d7df32924b83f7321a602d3f9f967c01b3df18495d6 \ | |
| --disable-log-bin --performance-schema=ON \ | |
| --innodb-buffer-pool-size=64M --innodb-redo-log-capacity=64M \ | |
| --innodb-log-buffer-size=8M --table-open-cache=256 --max-connections=40 | |
| docker run --detach \ | |
| --name "dw-server-backup-pgsql-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| --network "dw-server-feature-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| --memory 256m --memory-swap 256m \ | |
| --env POSTGRES_HOST_AUTH_METHOD=trust \ | |
| --health-cmd='pg_isready --host=127.0.0.1 --username=postgres' \ | |
| --health-interval=1s --health-start-period=5s --health-retries=30 \ | |
| mirror.gcr.io/library/postgres@sha256:f1c3376c26f2609ab9f29f71f824103fe2fcd8ee0346485cb6122a4f93df6f94 | |
| # MariaDB for the real-database contention proofs that must cover | |
| # every supported engine (Server #774). | |
| docker run --detach \ | |
| --name "dw-server-backup-mariadb-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| --network "dw-server-feature-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| --memory 384m --memory-swap 384m \ | |
| --env MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=yes --env MARIADB_ROOT_HOST=% \ | |
| --health-cmd='mariadb-admin --silent --host=127.0.0.1 ping' \ | |
| --health-interval=1s --health-start-period=20s --health-retries=30 \ | |
| mirror.gcr.io/library/mariadb@sha256:7db29378d4fdab73f8123bbc2b48905c90d1a4b00cf848b028f1e81e623257f2 \ | |
| --innodb-buffer-pool-size=64M --max-connections=40 | |
| for database in mysql pgsql mariadb; do | |
| for attempt in $(seq 1 60); do | |
| health="$(docker inspect --format '{{.State.Health.Status}}' "dw-server-backup-${database}-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}")" | |
| [ "$health" = healthy ] && break | |
| [ "$health" != unhealthy ] || exit 1 | |
| sleep 1 | |
| done | |
| [ "$health" = healthy ] | |
| done | |
| for mode in session transaction0 transaction200; do | |
| pool_mode=transaction | |
| prepared_statements=0 | |
| [ "$mode" != session ] || pool_mode=session | |
| [ "$mode" != transaction200 ] || prepared_statements=200 | |
| docker run --detach \ | |
| --name "dw-server-pooler-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}-$mode" \ | |
| --network "dw-server-feature-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| --memory 128m --memory-swap 128m \ | |
| -e DB_HOST="dw-server-backup-pgsql-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| -e DB_NAME=postgres -e DB_USER=postgres -e DB_PASSWORD=pooler-disposable-password \ | |
| -e AUTH_TYPE=trust -e "POOL_MODE=$pool_mode" -e "MAX_PREPARED_STATEMENTS=$prepared_statements" \ | |
| -e DEFAULT_POOL_SIZE=2 -e MIN_POOL_SIZE=2 -e SERVER_ROUND_ROBIN=1 \ | |
| edoburu/pgbouncer@sha256:b17551c776ef7e5769ef80b956d20f85e2fd25dd8912d31d58f782aad495b711 | |
| done | |
| tar \ | |
| --exclude=vendor \ | |
| --exclude='*/vendor' \ | |
| --exclude=build \ | |
| --exclude='*/build' \ | |
| -cf - . \ | |
| | docker run --rm -i \ | |
| --network "dw-server-feature-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| -e CORPUS_BASE_REF \ | |
| -e DW_TEST_S3_ENDPOINT="http://dw-server-s3-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}:8333" \ | |
| -e DW_TEST_S3_ACCESS_KEY_ID=dw-s3-access \ | |
| -e DW_TEST_S3_SECRET_ACCESS_KEY=dw-s3-secret-key \ | |
| -e DW_TEST_BACKUP_MYSQL_HOST="dw-server-backup-mysql-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| -e DW_TEST_COMPLETION_MYSQL_HOST="dw-server-backup-mysql-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| -e DW_TEST_BACKUP_PGSQL_HOST="dw-server-backup-pgsql-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| -e DW_TEST_COMPLETION_PGSQL_HOST="dw-server-backup-pgsql-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| -e DW_TEST_COMPLETION_MARIADB_HOST="dw-server-backup-mariadb-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| -e DW_POOLER_TEST_HOST="dw-server-backup-pgsql-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| -e DW_POOLER_TEST_PORT=5432 -e DW_POOLER_TEST_DATABASE=postgres -e DW_POOLER_TEST_USER=postgres \ | |
| -e DW_POOLER_TEST_PREFIX="dw-server-pooler-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| -w /app \ | |
| --entrypoint sh \ | |
| durable-workflow-server-corpus-validator \ | |
| -lc 'set -eu; tar --no-same-owner -xf - -C /app; cp -a /app/workflow-package /workflow; php scripts/ci/prepare-release-workflow-composer-metadata.php; composer update durable-workflow/workflow --with-dependencies --minimal-changes --no-install --no-scripts --no-autoloader --no-interaction --no-progress --prefer-dist; composer install --no-interaction --no-progress --prefer-dist; git --version; vendor/bin/phpunit --version; git rev-parse --verify "$CORPUS_BASE_REF^{commit}"; php scripts/ci/check-worker-openapi-evolution.php "$CORPUS_BASE_REF"; vendor/bin/phpunit tests/Feature tests/Unit/NexusContractTest.php tests/Unit/CodecRegressionCorpusTest.php tests/Unit/PhpunitFeatureWorkflowContractTest.php tests/Unit/OpenApiDocumentEvolutionTest.php tests/Unit/NamespaceConformanceRunnerContractTest.php tests/Unit/NamespaceRuntimeContractTest.php tests/Unit/SchedulesConformanceRunnerContractTest.php tests/Unit/SchedulesRuntimeContractTest.php tests/Unit/ServerParitySdkDirectionResponseSummaryTest.php tests/Unit/ServerParitySdkQueryTransportTest.php tests/Unit/ServerParitySdkUpdateTransportTest.php tests/Unit/ServerParityLockPressureRetryTest.php --no-progress --colors=never; vendor/bin/phpunit tests/Unit/ReleaseImageImmutabilityGuardTest.php --no-progress --colors=never; vendor/bin/phpunit tests/Unit/ReleaseImagePublishWorkflowContractTest.php --filter test_php_security_audits_fail_before_release_publication --no-progress --colors=never; python3 scripts/ci/validate-regression-corpus.py --base-ref "$CORPUS_BASE_REF" --verify-counterfactual' | |
| - name: Select production image qualification for changed build inputs | |
| id: image_inputs | |
| run: | | |
| if git diff --quiet '${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before || inputs.corpus_base_ref }}' HEAD -- Dockerfile Dockerfile.dockerignore .dockerignore composer.json composer.lock ':(glob)docker/**'; then | |
| echo 'required=false' >> "$GITHUB_OUTPUT" | |
| else | |
| echo 'required=true' >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Build the candidate PHP production image | |
| if: ${{ steps.image_inputs.outputs.required == 'true' }} | |
| env: | |
| DOCKER_BUILDKIT: "1" | |
| WORKFLOW_PACKAGE_SOURCE: ${{ steps.workflow.outputs.source }} | |
| WORKFLOW_PACKAGE_REF: ${{ steps.workflow.outputs.ref }} | |
| WORKFLOW_PACKAGE_COMMIT: ${{ steps.workflow.outputs.commit }} | |
| run: | | |
| docker build --target production --tag durable-workflow-server-context-candidate \ | |
| --build-arg WORKFLOW_PACKAGE_SOURCE \ | |
| --build-arg WORKFLOW_PACKAGE_REF \ | |
| --build-arg WORKFLOW_PACKAGE_COMMIT . | |
| - name: Verify candidate PHP image first-run readiness | |
| if: ${{ always() && success() && steps.image_inputs.outputs.required == 'true' }} | |
| env: | |
| DW_SERVER_IMAGE: durable-workflow-server-context-candidate | |
| DW_FIRST_RUN_SCOPE: source-context-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} | |
| DW_FIRST_RUN_SKIP_PULL: "1" | |
| run: scripts/smoke-published-first-run.sh | |
| - name: Clean up candidate image readiness resources | |
| if: always() | |
| env: | |
| DW_FIRST_RUN_SCOPE: source-context-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} | |
| run: | | |
| docker rm -f "${DW_FIRST_RUN_SCOPE}-server" "${DW_FIRST_RUN_SCOPE}-bootstrap" >/dev/null 2>&1 || true | |
| docker volume rm -f "${DW_FIRST_RUN_SCOPE}-database" >/dev/null 2>&1 || true | |
| docker image rm durable-workflow-server-context-candidate >/dev/null 2>&1 || true | |
| # The service containers stay until the cleanup below, so a failed | |
| # suite records whether one was OOM-killed or exited, and its last log. | |
| - name: Show test service state after a failure | |
| if: failure() | |
| run: | | |
| for service in s3 backup-mysql backup-pgsql; do | |
| name="dw-server-${service}-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" | |
| docker inspect --format "${name}: status={{.State.Status}} oom_killed={{.State.OOMKilled}} exit={{.State.ExitCode}} finished={{.State.FinishedAt}} error={{.State.Error}}" "$name" || true | |
| docker logs --tail 40 "$name" 2>&1 || true | |
| done | |
| - name: Clean up test services | |
| if: always() | |
| run: | | |
| for mode in session transaction0 transaction200; do | |
| docker rm -f "dw-server-pooler-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}-$mode" >/dev/null 2>&1 || true | |
| done | |
| docker rm -f \ | |
| "dw-server-s3-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| "dw-server-backup-mysql-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| "dw-server-backup-pgsql-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| "dw-server-backup-mariadb-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| >/dev/null 2>&1 || true | |
| docker network rm \ | |
| "dw-server-feature-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}" \ | |
| >/dev/null 2>&1 || true | |
| - name: Verify required database TLS and certificate refusals | |
| timeout-minutes: 10 | |
| env: | |
| TLS_TEST_IMAGE: durable-workflow-server-phpunit-base | |
| TLS_TEST_PROJECT: dw-server-tls-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} | |
| TLS_TEST_ROOT: ${{ runner.temp }}/dw-server-tls-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} | |
| run: | | |
| python3 scripts/ci/test_database_tls_cleanup.py | |
| bash scripts/ci/qualify-database-tls.sh | |
| - name: Clean up database TLS fixtures | |
| if: ${{ always() }} | |
| env: | |
| TLS_TEST_PROJECT: dw-server-tls-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} | |
| TLS_TEST_ROOT: ${{ runner.temp }}/dw-server-tls-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} | |
| run: | | |
| docker rm -f "$TLS_TEST_PROJECT-certificates" >/dev/null 2>&1 || true | |
| docker ps -aq --filter "label=com.docker.compose.project=$TLS_TEST_PROJECT" | xargs -r docker rm -f >/dev/null 2>&1 || true | |
| docker network ls -q --filter "label=com.docker.compose.project=$TLS_TEST_PROJECT" | xargs -r docker network rm >/dev/null 2>&1 || true | |
| docker volume ls -q --filter "label=com.docker.compose.project=$TLS_TEST_PROJECT" | xargs -r docker volume rm -f >/dev/null 2>&1 || true | |
| bash scripts/ci/cleanup-database-tls.sh | |
| qualification: | |
| name: Feature source qualification | |
| needs: [preflight, feature] | |
| if: ${{ always() && !cancelled() }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 2 | |
| steps: | |
| - name: Require repository contracts and feature tests | |
| env: | |
| PREFLIGHT_RESULT: ${{ needs.preflight.result }} | |
| FEATURE_RESULT: ${{ needs.feature.result }} | |
| run: | | |
| set -euo pipefail | |
| test "$PREFLIGHT_RESULT" = success | |
| test "$FEATURE_RESULT" = success | |
| echo "Server repository contracts and feature tests succeeded." | |
| prepared-local-source: | |
| name: Candidate cancellation and prepared-local source qualification | |
| needs: [action-policy, preflight] | |
| if: ${{ github.event_name == 'workflow_dispatch' && inputs.prepared_local_workflow_commit != '' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| services: | |
| mysql: | |
| image: mysql@sha256:679e7e924f38a3cbb62a3d7df32924b83f7321a602d3f9f967c01b3df18495d6 | |
| env: | |
| MYSQL_ALLOW_EMPTY_PASSWORD: "yes" | |
| MYSQL_DATABASE: prepared_local_test | |
| MYSQL_ROOT_HOST: "%" | |
| ports: ["3317:3306"] | |
| options: >- | |
| --health-cmd="mysqladmin --silent --host=127.0.0.1 ping" | |
| --health-interval=2s --health-timeout=5s --health-retries=30 | |
| postgres: | |
| image: postgres@sha256:f1c3376c26f2609ab9f29f71f824103fe2fcd8ee0346485cb6122a4f93df6f94 | |
| env: | |
| POSTGRES_HOST_AUTH_METHOD: trust | |
| POSTGRES_DB: prepared_local_test | |
| ports: ["5439:5432"] | |
| options: >- | |
| --health-cmd="pg_isready --host=127.0.0.1 --username=postgres" | |
| --health-interval=2s --health-timeout=5s --health-retries=30 | |
| env: | |
| DW_PREPARED_WORKFLOW_COMMIT: ${{ inputs.prepared_local_workflow_commit }} | |
| steps: | |
| - name: Require immutable candidate source | |
| run: | | |
| set -euo pipefail | |
| [[ "$DW_PREPARED_WORKFLOW_COMMIT" =~ ^[0-9a-f]{40}$ ]] | |
| [[ ! "$DW_PREPARED_WORKFLOW_COMMIT" =~ ^0+$ ]] | |
| - name: Check out Server candidate | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| persist-credentials: false | |
| - name: Check out exact Native candidate | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| repository: durable-workflow/workflow | |
| ref: ${{ inputs.prepared_local_workflow_commit }} | |
| path: prepared-workflow-source | |
| persist-credentials: false | |
| - name: Record source binding and qualify all supported databases | |
| run: | | |
| set -euo pipefail | |
| test "$(git -C prepared-workflow-source rev-parse HEAD)" = "$DW_PREPARED_WORKFLOW_COMMIT" | |
| mkdir -p prepared-local-evidence | |
| chmod 0777 prepared-local-evidence | |
| { | |
| echo "Candidate source qualification only. No image, release or SDK conformance claim." | |
| echo "Server commit: $(git rev-parse HEAD)" | |
| echo "Native source overlay: $DW_PREPARED_WORKFLOW_COMMIT" | |
| echo "Locked package baseline retained by ordinary feature CI." | |
| } > prepared-local-evidence/provenance.txt | |
| docker build --target base -t dw-server-prepared-local-base . | |
| tar --exclude=vendor --exclude='*/vendor' --exclude=build --exclude='*/build' \ | |
| --exclude=prepared-local-evidence -cf ../prepared-local-source.tar . | |
| qualification_status=0 | |
| for database in sqlite mysql pgsql; do | |
| database_name=prepared_local_test | |
| database_user=root | |
| database_port=3317 | |
| if [ "$database" = sqlite ]; then database_name=:memory:; fi | |
| if [ "$database" = pgsql ]; then database_user=postgres; database_port=5439; fi | |
| if ! docker run --rm --init --network host \ | |
| --user 1000:1000 \ | |
| --tmpfs /app:rw,uid=1000,gid=1000,mode=0755 \ | |
| --volume "$PWD/../prepared-local-source.tar:/source.tar:ro" \ | |
| --volume "$PWD/prepared-local-evidence:/evidence" \ | |
| --env COMPOSER_HOME=/tmp/dw-prepared-composer \ | |
| --env DB_CONNECTION="$database" --env DB_DATABASE="$database_name" \ | |
| --env DB_HOST=127.0.0.1 --env DB_PORT="$database_port" \ | |
| --env DB_USERNAME="$database_user" --env DB_PASSWORD= \ | |
| --env DW_PREPARED_DATABASE="$database" \ | |
| --workdir /app --entrypoint sh dw-server-prepared-local-base -ec ' | |
| tar --no-same-owner -xf /source.tar -C /app | |
| composer install --no-interaction --no-progress --prefer-dist | |
| mv vendor/durable-workflow/workflow /tmp/locked-workflow-package | |
| cp -a prepared-workflow-source vendor/durable-workflow/workflow | |
| php vendor/bin/phpunit tests/Feature/PreparedLocalActivityProtocolTest.php tests/Feature/ActivityAttemptStatusTest.php tests/Feature/HistoryRetentionTest.php tests/Unit/WorkerPollFenceTest.php tests/Source/CancellationCascadeDiagnosticsTest.php tests/Source/PreparedLocalCancellationPolicyTest.php tests/Source/CancellationScopeAdmissionTest.php tests/Source/ScopedActivityCancellationReceiptTest.php tests/Source/CancellationScopeDeliveryTest.php tests/Source/NexusTargetCancellationAdmissionTest.php tests/Feature/NexusTargetCancellationPolicyTest.php tests/Feature/WorkflowStreamsTest.php tests/Feature/WorkflowDebugTest.php --fail-on-skipped --log-junit "/evidence/$DW_PREPARED_DATABASE.xml" --colors=never | |
| if [ "$DW_PREPARED_DATABASE" = sqlite ]; then | |
| php vendor/bin/phpunit tests/Feature/CooperativeCancellationProtocolTest.php tests/Feature/WorkflowWorkerProtocolTest.php tests/Feature/ActivityWorkerProtocolTest.php tests/Feature/SearchAttributeValueValidationTest.php tests/Feature/NamespaceDurableStateQuotaTest.php tests/Feature/RuntimeExternalPayloadTransportTest.php tests/Feature/RuntimePayloadCompletionUploadsTest.php tests/Unit/RuntimePayloadCompletionContextTest.php tests/Unit/WorkerProtocolOpenApiContractTest.php --log-junit /evidence/affected-regression.xml --colors=never | |
| fi | |
| ' > "prepared-local-evidence/$database.log" 2>&1; then | |
| qualification_status=1 | |
| fi | |
| done | |
| test "$qualification_status" = 0 | |
| - name: Retain exact source qualification evidence | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: candidate-prepared-local-source | |
| path: prepared-local-evidence | |
| if-no-files-found: error | |
| retention-days: 90 |