Skip to content

WF-IMPL-077: DaprActivityRuntimeClient.cancel_activity adapter#500

Merged
toddysm merged 2 commits into
mainfrom
wf-impl-077-dapr-cancel-activity-adapter
Jun 1, 2026
Merged

WF-IMPL-077: DaprActivityRuntimeClient.cancel_activity adapter#500
toddysm merged 2 commits into
mainfrom
wf-impl-077-dapr-cancel-activity-adapter

Conversation

@toddysm
Copy link
Copy Markdown
Owner

@toddysm toddysm commented Jun 1, 2026

Summary

Closes #488. Implements the production DaprActivityRuntimeClient.cancel_activity HTTP adapter against the local Dapr sidecar's Service Invocation API. Cancellation is idempotent end-to-end: 404 (ARM has no record) and 409 (already terminated) collapse to a silent no-op with an INFO-level breadcrumb so operators can trace the cancel without it looking like an error.

Changes

Source — src/services/workflow-service/src/custos_workflow/clients/activity_runtime.py

  • New constant CANCEL_ACTIVITY_DAPR_METHOD = "CancelActivity" (exported via __all__).
  • Module-level _LOGGER = logging.getLogger(__name__) for the cancel-path INFO breadcrumbs.
  • DaprActivityRuntimeClient.cancel_activity replaces the WF-IMPL-076 NotImplementedError placeholder with an async POST that:
    • 200 / 204 → return.
    • 404 → INFO log + return (ARM has no record of the step).
    • 409 → INFO log + return (ARM reports step already terminated).
    • Any other 4xx or 5xx → raise OutboundRpcStatusError with the observed status_code so RunController can surface it via RunControllerError.
    • httpx.HTTPError → raise OutboundRpcTransportError, with the underlying httpx exception preserved on __cause__.
  • No envelope mapping: cancel_activity returns None per the Protocol surface; callers distinguish success from failure by whether an exception escapes.
  • No Idempotency-Key header: cancellation is idempotent on ARM via 404/409, so retries are byte-identical without keying. This is asserted in the test suite.
  • Updated DaprActivityRuntimeClient docstring to reflect that both methods are now async.

Tests — tests/clients/test_dapr_activity_runtime.py

17 new tests covering the full status-code matrix:

  • 200 / 204 success returns None.
  • 404 idempotent no-op + caplog-asserted INFO breadcrumb with run_id / step_id extras.
  • 409 idempotent no-op + caplog-asserted INFO breadcrumb.
  • 400 / 401 / 403 / 422 raise OutboundRpcStatusError with status_code echoed.
  • 500 / 502 / 503 / 504 raise OutboundRpcStatusError with status_code echoed.
  • httpx.ConnectTimeout and httpx.ConnectError raise OutboundRpcTransportError; original exception preserved on __cause__.
  • URL targets /v1.0/invoke/<arm-app-id>/method/CancelActivity (not ScheduleActivity).
  • Body uses camelCase: {"runId": ..., "stepId": ...}.
  • No Idempotency-Key header is attached (asserted via case-insensitive header set diff).
  • Per-call timeout propagates to req.extensions["timeout"].

Quality

  • ruff check / ruff format --check clean.
  • mypy --strict clean (158 source files).
  • 1802 passed, 1 pre-existing flake (tests/test_observability.py::test_module_imports_under_noop_providers).
  • activity_runtime.py: 100 % coverage; full-suite 98.97 %.

Design references

  • Workflow service: design/components/workflow-service/design.md § Internal RPC outbound — CancelActivity.
  • ARM: design/components/activity-runtime-manager/design.md § Internal RPCs.
  • Protocol contract: ActivityRuntimeClient.cancel_activity (idempotency contract).

Tracker

Implement the production CancelActivity HTTP adapter against the
local Dapr sidecar's Service Invocation API. Cancellation is
idempotent end-to-end: 404 (ARM has no record) and 409 (already
terminated) collapse to a silent no-op with an INFO-level
breadcrumb so operators can trace the cancel without it looking
like an error.

Source
------
- New constant CANCEL_ACTIVITY_DAPR_METHOD = 'CancelActivity'.
- Module-level _LOGGER (logging.getLogger(__name__)).
- DaprActivityRuntimeClient.cancel_activity replaces the
  NotImplementedError placeholder with an async POST that:
  * 200 / 204 -> return.
  * 404 / 409 -> INFO log + return (idempotent no-op).
  * Other 4xx + 5xx -> raise OutboundRpcStatusError with the
    observed status_code so RunController can surface it via
    RunControllerError.
  * httpx.HTTPError -> raise OutboundRpcTransportError, with the
    underlying httpx exception preserved on __cause__.
- No envelope mapping: cancel_activity returns None per the
  Protocol surface; callers distinguish success from failure by
  whether an exception escapes.
- No Idempotency-Key header: cancellation is idempotent on ARM
  via 404/409, so retries are byte-identical without keying.
- Updated DaprActivityRuntimeClient docstring (both methods async).

Tests
-----
17 new tests covering the full status-code matrix:
- 200 / 204 success returns None.
- 404 idempotent no-op + INFO breadcrumb with run_id/step_id extras.
- 409 idempotent no-op + INFO breadcrumb.
- 400/401/403/422 raise OutboundRpcStatusError with status_code.
- 500/502/503/504 raise OutboundRpcStatusError with status_code.
- httpx.ConnectTimeout and ConnectError raise
  OutboundRpcTransportError; original exception preserved on
  __cause__.
- URL targets /v1.0/invoke/<arm-app-id>/method/CancelActivity.
- Body uses camelCase: {runId, stepId}.
- No Idempotency-Key header is attached.
- Per-call timeout propagates to req.extensions.

Quality
-------
- ruff check / format clean.
- mypy --strict clean (158 source files).
- 1802 passed, 1 pre-existing flake
  (tests/test_observability.py::test_module_imports_under_noop_providers).
- activity_runtime.py: 100 % coverage; full-suite 98.97 %.

Closes #488.
Copilot AI review requested due to automatic review settings June 1, 2026 05:15
@toddysm toddysm added type:implementation Implementation work item phase:implementation Implementation phase component:workflow-service Workflow Service component labels Jun 1, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements the production DaprActivityRuntimeClient.cancel_activity adapter in workflow-service, calling ARM via the local Dapr sidecar’s Service Invocation API and enforcing the idempotent cancellation contract (404/409 treated as no-ops).

Changes:

  • Added CANCEL_ACTIVITY_DAPR_METHOD and implemented DaprActivityRuntimeClient.cancel_activity as an async HTTP POST with the specified status/transport error taxonomy and INFO breadcrumbs for idempotent no-ops.
  • Expanded the test_dapr_activity_runtime suite to cover the cancel status-code matrix, wire shape (URL + camelCase body), timeout propagation, and header invariants (no Idempotency-Key for cancel).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/services/workflow-service/src/custos_workflow/clients/activity_runtime.py Adds CancelActivity method constant + logger and implements the cancel adapter with idempotent 404/409 handling and error mapping.
src/services/workflow-service/tests/clients/test_dapr_activity_runtime.py Adds comprehensive unit tests for cancel_activity behavior, including logging breadcrumbs, wire contract, and error taxonomy.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/services/workflow-service/src/custos_workflow/clients/activity_runtime.py Outdated
Rewrite the misleading 'Any other 2xx is unexpected' inline
comment in DaprActivityRuntimeClient.cancel_activity to
accurately describe the fallthrough behaviour: every status
outside the contracted set (200/204 success + 404/409
idempotent no-op) — including 4xx, 5xx, redirects, and any
non-200/204 2xx — is surfaced as OutboundRpcStatusError.
@toddysm toddysm merged commit a41882c into main Jun 1, 2026
23 checks passed
@toddysm toddysm deleted the wf-impl-077-dapr-cancel-activity-adapter branch June 1, 2026 05:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component:workflow-service Workflow Service component phase:implementation Implementation phase type:implementation Implementation work item

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WF-IMPL-077: DaprActivityRuntimeClient adapter — CancelActivity

2 participants