Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
69ca3ca
Decode canonical cooperative cancellation identity and ranges
durable-workflow-ops Sep 30, 2026
18ec381
Replay canonical cancellation at durable calls and recover cleanup
durable-workflow-ops Sep 30, 2026
a3c909c
Add fenced cooperative cancellation request and delivery client APIs
durable-workflow-ops Sep 30, 2026
febb590
Connect cooperative cancellation delivery to Worker replay and local …
durable-workflow-ops Oct 1, 2026
797e393
Drain cooperative local cleanup and fence it at shutdown deadline
durable-workflow-ops Oct 1, 2026
119747b
Qualify cooperative cancellation against a connected candidate Server
durable-workflow-ops Oct 1, 2026
8c6bd87
Supervise cooperative remote activity callbacks with bounded ownershi…
durable-workflow-ops Oct 1, 2026
9f36749
Use the SDK ServerError status in connected publication fence assertions
durable-workflow-ops Oct 1, 2026
88f0e31
Finish fenced remote claims without delaying worker shutdown on obser…
durable-workflow-ops Oct 1, 2026
ec794e2
Qualify real Python activity process death and attempt reclaim
durable-workflow-ops Oct 1, 2026
b3c45b6
Prove expired attempt remains unchanged after heartbeat
durable-workflow-ops Oct 1, 2026
e69cf85
Use Worker backpressure policy in connected claim fixture
durable-workflow-ops Oct 1, 2026
07e2030
test: align repair cadence and fixture imports
durable-workflow-ops Oct 1, 2026
2b28cca
fix: defer Python parent claims while child cleanup waits
durable-workflow-ops Oct 1, 2026
1763e72
feat: expose immutable cancellation context in Python replay
durable-workflow-ops Oct 1, 2026
aeb54ff
Preserve typed child cancellation policies in commands and cold replay
durable-workflow-ops Oct 1, 2026
c17ee56
Validate original remote cancellation stop receipts in the Python client
durable-workflow-ops Oct 2, 2026
07b2fb0
Own cooperative activity callbacks in independent spawn supervisors
durable-workflow-ops Oct 2, 2026
16ae1f0
Retain exact Native overlay provenance in Python source qualification
durable-workflow-ops Oct 2, 2026
19febbf
Stop and join cooperative Python remote callbacks before acknowledgin…
durable-workflow-ops Oct 2, 2026
edf890a
Prove connected Python callback stops and original cancellation receipts
durable-workflow-ops Oct 2, 2026
003ff07
Compare public cancellation history by immutable receipt payload
durable-workflow-ops Oct 2, 2026
8eabc65
Add source prepared local activity transport with original claim budgets
durable-workflow-ops Oct 2, 2026
5150044
feat: admit and supervise prepared local callbacks
durable-workflow-ops Oct 2, 2026
0a7c68b
Consume atomic prepared local activity groups with joined cancellation
durable-workflow-ops Oct 2, 2026
e256cbd
Retain prepared group refusals and mixed admission history
durable-workflow-ops Oct 2, 2026
f927f69
Preserve prepared application heartbeat progress
durable-workflow-ops Oct 2, 2026
a6b243e
Use the canonical prepared heartbeat details envelope
durable-workflow-ops Oct 2, 2026
f740388
Accept activity cancellation waits with explicit claim release
durable-workflow-ops Oct 2, 2026
bf93195
Format pending cancellation response cases
durable-workflow-ops Oct 2, 2026
de0c476
Preserve remote activity cancellation policies through Python replay
durable-workflow-ops Oct 2, 2026
8c1e937
Add deterministic cancellation remaining time during replay
durable-workflow-ops Oct 2, 2026
0e1c427
Qualify remaining budgets across prepared cleanup recovery
durable-workflow-ops Oct 2, 2026
afa7cfa
Support prepared local Activity cancellation policies
durable-workflow-ops Oct 3, 2026
fce32d5
Accept historical local policy omission in qualification
durable-workflow-ops Oct 3, 2026
d41e76d
Format historical policy qualification assertion
durable-workflow-ops Oct 3, 2026
928ded1
Keep configured poll windows separate from HTTP grace
durable-workflow-ops Oct 3, 2026
6b98cdf
Merge published Python 2.3.8 polling fix into cancellation candidate
durable-workflow-ops Oct 3, 2026
3cddb86
Refuse unqualified cancellation scope replay before Python applicatio…
durable-workflow-ops Oct 3, 2026
b086c44
Refuse unqualified scoped delivery replay
durable-workflow-ops Oct 3, 2026
363ade8
Format scoped delivery admission markers
durable-workflow-ops Oct 3, 2026
4a33482
Preserve scoped cancellation origins in child run contexts
durable-workflow-ops Oct 4, 2026
3c77a01
Merge remote-tracking branch 'origin/main' into feat/cooperative-canc…
durable-workflow-ops Oct 4, 2026
7490102
Prove candidate scope openings from original claim history
durable-workflow-ops Oct 5, 2026
e0491df
Qualify Python scope opening against real Native history
durable-workflow-ops Oct 5, 2026
7ae2f2e
feat: replay canonical cancellation scope openings
durable-workflow-ops Oct 5, 2026
a007c43
feat: replay prepared scalar scope cancellation on its original claim
durable-workflow-ops Oct 5, 2026
dfa49ff
fix: match Native scope descriptor hashing for ASCII DEL
durable-workflow-ops Oct 6, 2026
dedeae3
feat: preserve original authority on shielded scope cleanup timers
durable-workflow-ops Oct 6, 2026
f39ef17
feat: replay original scoped parallel cancellation and shielded cleanup
durable-workflow-ops Oct 6, 2026
9538ca7
test: publish callback PID markers atomically
durable-workflow-ops Oct 6, 2026
f3d97a8
fix: prove scoped group transport on the original claim
durable-workflow-ops Oct 6, 2026
d672ba8
Replay inherited scoped cancellation through included descendants
durable-workflow-ops Oct 6, 2026
6de40e7
Prepare ancestor scope cancellation before descendant requests exist
durable-workflow-ops Oct 6, 2026
9cbc16f
Reject missing and retrograde scoped cleanup timer proofs
durable-workflow-ops Oct 6, 2026
4ac4500
Replay scoped prepared cleanup before original root cancellation
durable-workflow-ops Oct 6, 2026
d7bf08c
Preserve ancestor cleanup proof separately from descendant membership
durable-workflow-ops Oct 6, 2026
35a32bd
Exercise scoped cleanup recovery without redundant metadata checkpoints
durable-workflow-ops Oct 6, 2026
fe16e0a
Qualify scoped async and sync callback stop without application heart…
durable-workflow-ops Oct 6, 2026
f5bfdcd
Coordinate scoped callback-stop receipts within the original budget
durable-workflow-ops Oct 6, 2026
be5a0cb
Accept asyncio budget expiry on supported Python 3.10
durable-workflow-ops Oct 6, 2026
27ff742
Prepare cooperative cancellation SDK release candidate
durable-workflow-ops Oct 6, 2026
ce256a9
Explain cancellation worker opt-in for release candidate
durable-workflow-ops Oct 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 73 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ on:
pull_request:
branches: [main]
workflow_dispatch:
inputs:
server_commit:
description: Exact public Server candidate SHA, or empty for its default branch
type: string
default: ""
cooperative_qualification:
description: Qualify the candidate cooperative protocol 1.20
type: boolean
default: false
native_commit:
description: Optional exact public Native source overlay SHA for candidate qualification
type: string
default: ""

permissions:
contents: read
Expand Down Expand Up @@ -145,6 +158,11 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 25
needs: [lint, test]
env:
DURABLE_WORKFLOW_WORKER_PROTOCOL_VERSION: ${{ inputs.cooperative_qualification && '1.20' || '1.19' }}
DURABLE_WORKFLOW_COOPERATIVE_QUALIFICATION: ${{ inputs.cooperative_qualification && '1' || '0' }}
DURABLE_WORKFLOW_NATIVE_SOURCE: ${{ github.workspace }}/native
COMPOSE_FILE: docker-compose.test.yml
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
Expand All @@ -153,7 +171,32 @@ jobs:
with:
python-version: "3.12"
- name: Check out public Server integration source
run: python sdk-python/scripts/ci/checkout-public-repository.py server server
env:
SERVER_COMMIT: ${{ inputs.server_commit }}
run: python sdk-python/scripts/ci/checkout-public-repository.py server server --commit "$SERVER_COMMIT"
- name: Require exact cooperative candidate identities
env:
COOPERATIVE_REQUESTED: ${{ inputs.cooperative_qualification }}
SERVER_COMMIT: ${{ inputs.server_commit }}
NATIVE_COMMIT: ${{ inputs.native_commit }}
run: |
if [ "$COOPERATIVE_REQUESTED" = true ]; then
[[ "$SERVER_COMMIT" =~ ^[0-9a-f]{40}$ ]]
fi
if [ -n "$NATIVE_COMMIT" ]; then
test "$COOPERATIVE_REQUESTED" = true
[[ "$NATIVE_COMMIT" =~ ^[0-9a-f]{40}$ ]]
fi
- name: Check out optional exact public Native source
if: ${{ inputs.native_commit != '' }}
env:
NATIVE_COMMIT: ${{ inputs.native_commit }}
run: python sdk-python/scripts/ci/checkout-public-repository.py workflow native --commit "$NATIVE_COMMIT"
- name: Select readonly Native source qualification
if: ${{ inputs.native_commit != '' }}
run: |
printf 'COMPOSE_FILE=docker-compose.test.yml:docker-compose.native-cancellation.yml\n' >> "$GITHUB_ENV"
printf 'DURABLE_WORKFLOW_NATIVE_SOURCE_QUALIFICATION=1\n' >> "$GITHUB_ENV"
- run: pip install -e '.[dev]'
working-directory: sdk-python
- name: Configure isolated Docker project
Expand All @@ -162,31 +205,55 @@ jobs:
- name: Start Server stack
working-directory: sdk-python
run: |
docker compose --project-name "$COMPOSE_PROJECT_NAME" -f docker-compose.test.yml \
docker compose --project-name "$COMPOSE_PROJECT_NAME" \
up -d --build --wait --timeout 300
- name: Retain exact source and published image authority
working-directory: sdk-python
env:
NATIVE_COMMIT: ${{ inputs.native_commit }}
run: |
docker compose exec -T server cat /app/.package-provenance > integration-package-provenance.txt
docker compose images --format json > integration-images.json
jq --null-input --arg sdk "$GITHUB_SHA" --arg server "$(git -C ../server rev-parse HEAD)" --arg native "$NATIVE_COMMIT" \
'{qualification:"source",sdk_commit:$sdk,server_commit:$server,native_source_overlay:($native | if length > 0 then . else null end)}' \
> integration-source-provenance.json
- name: Select and probe integration endpoint
working-directory: sdk-python
run: python scripts/ci/configure-integration-endpoint.py
- name: Run integration tests
shell: bash
working-directory: sdk-python
env:
DURABLE_WORKFLOW_AUTH_TOKEN: test-token
run: pytest tests/integration/ -v
run: pytest tests/integration/ -v --capture=tee-sys --junitxml=integration-results.xml 2>&1 | tee integration-scenarios.log
- name: Retain connected scenario results
if: ${{ always() && github.server_url == 'https://github.com' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: connected-integration-${{ github.sha }}
path: |
sdk-python/integration-results.xml
sdk-python/integration-scenarios.log
sdk-python/integration-source-provenance.json
sdk-python/integration-package-provenance.txt
sdk-python/integration-images.json
if-no-files-found: warn
retention-days: ${{ inputs.cooperative_qualification && 90 || 7 }}
- name: Emit integration diagnostics
if: failure()
working-directory: sdk-python
run: |
for service in bootstrap server worker; do
for service in bootstrap server worker repair; do
echo "::group::$service logs"
docker compose --project-name "$COMPOSE_PROJECT_NAME" -f docker-compose.test.yml \
docker compose --project-name "$COMPOSE_PROJECT_NAME" \
logs --no-color --tail 200 "$service" || true
echo "::endgroup::"
done
- name: Teardown
if: always()
working-directory: sdk-python
run: |
docker compose --project-name "$COMPOSE_PROJECT_NAME" -f docker-compose.test.yml \
docker compose --project-name "$COMPOSE_PROJECT_NAME" \
down -v --rmi local

target-branch-qualification:
Expand Down
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,21 @@ capabilities with Server at startup. Use stable `2.x` SDK and Server channels
for new applications. The [compatibility guide](https://durable-workflow.com/docs/2.0/compatibility/)
documents protocol and upgrade guarantees.

## Cooperative cancellation release candidate

Use `request_cancellation()` for bounded, replayable workflow cleanup. Opt in
against a Server that advertises protocol 1.20 and the required capabilities:
set `DURABLE_WORKFLOW_WORKER_PROTOCOL_VERSION=1.20` and include
`cooperative_cancellation` in the Worker's `capabilities`. Durable local callback
admission also needs `prepared_local_activities`, with
`prepared_local_activity_cancellation_policies` for explicit local policies.
Independently cancellable scopes remain disabled.

The cooperative worker supervises async and synchronous activity callbacks
independently of application heartbeats. Existing terminal cancellation remains
available. See the [cancellation guide](docs/cooperative-cancellation-design.md)
for immutable context, operation policies, shielded cleanup and recovery.

## Development

```bash
Expand All @@ -127,11 +142,26 @@ pytest tests/ -m "not integration"
Integration tests use Docker:

```bash
export COMPOSE_PROJECT_NAME=sdk-python-local
docker compose -f docker-compose.test.yml up -d --build --wait
pytest tests/integration/ -v
SERVER_PORT=$(docker compose -f docker-compose.test.yml port server 8080 | sed 's/.*://')
DURABLE_WORKFLOW_SERVER_URL="http://127.0.0.1:$SERVER_PORT" DURABLE_WORKFLOW_AUTH_TOKEN=test-token pytest tests/integration/ -v
docker compose -f docker-compose.test.yml down -v
```

Candidate cooperative cancellation qualification is explicit. In a manual CI
run, supply an exact public `server_commit` and set `cooperative_qualification`
to true. CI verifies that checkout, builds the candidate Server, enables protocol
1.20, runs the connected cases and retains JUnit, raw observations, image
authority and exact source provenance. An optional exact `native_commit` mounts
that public Native checkout read-only into the test stack. The image's published
Composer authority stays intact and the evidence identifies the source overlay.
These are source qualification runs. For a local
candidate, set `DURABLE_WORKFLOW_WORKER_PROTOCOL_VERSION=1.20` before starting
Compose and `DURABLE_WORKFLOW_COOPERATIVE_QUALIFICATION=1` for pytest. These cases
fail if the runtime does not discover the required capability. Ordinary CI
keeps protocol 1.19 and skips this unpublished feature's connected cases.

## License

[MIT](LICENSE)
15 changes: 15 additions & 0 deletions docker-compose.native-cancellation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Source qualification only. Published Composer authority stays in the image.
services:
bootstrap:
volumes:
- ${DURABLE_WORKFLOW_NATIVE_SOURCE:?exact Native checkout}:/app/vendor/durable-workflow/workflow:ro
server:
volumes:
- ${DURABLE_WORKFLOW_NATIVE_SOURCE:?exact Native checkout}:/app/vendor/durable-workflow/workflow:ro
- ./tests/integration/native-scope-request.php:/app/sdk-source-fixtures/native-scope-request.php:ro
worker:
volumes:
- ${DURABLE_WORKFLOW_NATIVE_SOURCE:?exact Native checkout}:/app/vendor/durable-workflow/workflow:ro
repair:
volumes:
- ${DURABLE_WORKFLOW_NATIVE_SOURCE:?exact Native checkout}:/app/vendor/durable-workflow/workflow:ro
12 changes: 12 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ services:
WORKFLOW_SERVER_AUTH_DRIVER: token
WORKFLOW_SERVER_AUTH_TOKEN: "test-token"
DW_WORKFLOW_TASK_TIMEOUT: 10
# Candidate cooperative qualification is explicit. Published defaults remain 1.19.
DW_WORKER_PROTOCOL_VERSION: "${DURABLE_WORKFLOW_WORKER_PROTOCOL_VERSION:-1.19}"
depends_on:
mysql:
condition: service_healthy
Expand Down Expand Up @@ -70,6 +72,16 @@ services:
redis:
condition: service_healthy

repair:
build:
context: ../server
# Match the production scheduler's unscoped, unthrottled repair cadence.
command: sh -c 'while true; do php artisan workflow:v2:repair-pass --json; sleep 10; done'
environment: *server-env
depends_on:
server:
condition: service_healthy

mysql:
image: mysql:8.0
environment:
Expand Down
Loading
Loading