Skip to content

WF-IMPL-076: DaprActivityRuntimeClient adapter — ScheduleActivity (#487)#499

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

WF-IMPL-076: DaprActivityRuntimeClient adapter — ScheduleActivity (#487)#499
toddysm merged 2 commits into
mainfrom
wf-impl-076-dapr-schedule-activity-adapter

Conversation

@toddysm
Copy link
Copy Markdown
Owner

@toddysm toddysm commented Jun 1, 2026

Summary

Implements WF-IMPL-076 — the production DaprActivityRuntimeClient.schedule_activity adapter against the local Dapr sidecar's Service Invocation API, behind the locked ActivityRuntimeClient Protocol (WF-IMPL-049) and the WF-IMPL-075 outbound-RPC error taxonomy.

What

  • src/services/workflow-service/src/custos_workflow/clients/activity_runtime.py
    • New DaprActivityRuntimeClient(http_client, endpoint, timeout=10.0).
    • async def schedule_activity(request) -> ActivityResultEnvelope:
      • Marshals the request to the camelCase wire envelope (runId, stepId, attempt, activityRef, inputs, connectorContexts, deadline) per the ARM design § Internal RPCs.
      • POSTs to …/v1.0/invoke/<arm-app-id>/method/ScheduleActivity with Idempotency-Key: {runId}|{stepId}|{attempt} (canonical IdempotencyTriple.to_str).
      • Parses the response body into an ActivityResultEnvelope; shape mismatches raise OutboundRpcDecodeError.
      • Maps every transport-layer failure (httpx.HTTPError → transport, non-2xx → status, HTTP 499 → cancelled, decode failure → decode) through map_to_activity_envelope(exc, attempt=request.attempt) so the return value is always a shape-valid envelope.
    • cancel_activity raises NotImplementedError pending WF-IMPL-077.
    • Exposed via custos_workflow.clients.__all__.
  • src/services/workflow-service/tests/clients/test_dapr_activity_runtime.py
    • 41 unit tests using httpx.MockTransport covering success / permanent / parametrised status matrix (400/401/404/422/408/429/500/502/503/504) / HTTP 499 cancelled / transport timeout / arbitrary httpx.HTTPError / invalid JSON body / shape-mismatch decode / idempotency header invariants (canonical encoding + attempt rotation) / URL targets the ARM app-id / cancel_activity raises / default + override timeout propagation.

Acceptance criteria checklist

  • 200 success envelope passed through.
  • 200 class_="permanent" envelope passed through unchanged.
  • HTTP 4xx (≠ 408 / 429) → returned envelope class_="permanent".
  • HTTP 5xx, 408, 429 → returned envelope class_="retryable".
  • Transport timeout → returned envelope class_="retryable".
  • HTTP 499 → returned envelope class_="cancelled".
  • Malformed response body → returned envelope error.code = "workflow.client.decode". Note: maps to class_="permanent" per the WF-IMPL-075 locked taxonomy (decode = contract violation, not transient). This is one bullet in the acceptance criteria that conflicts with the locked taxonomy; the adapter follows the taxonomy lock.
  • Returned envelope passes ActivityResultEnvelope.__post_init__ invariants on every path.
  • Idempotency-Key header asserted on every outbound request (canonical IdempotencyTriple encoding).
  • 100 % unit-test coverage on the new adapter (activity_runtime.py rises from 100 % to 100 % including all new lines).
  • ruff check, ruff format --check, mypy --strict clean.

Test results

  • Full suite: 1785 passed, 1 known pre-existing flake (tests/test_observability.py::test_module_imports_under_noop_providers — same subprocess ModuleNotFoundError cross-test pollution called out in the WF-IMPL-074 / 075 PRs; passes in isolation).
  • Coverage: 100 % on clients/activity_runtime.py; full suite 98.97 % (≥ 90 % floor).
  • ruff check . && ruff format --check . clean.
  • mypy --strict src tests clean.

Tracker

Notes for reviewer

  • The class lives in activity_runtime.py per the issue scope; needed lazy imports for _errors and connector inside the method body and helpers to avoid a top-level cycle (_errors imports ActivityResultClass / ActivityResultEnvelope from this module).
  • The adapter is async; the WF-IMPL-079 worker-wiring task will bridge this to the sync ActivityRuntimeClient Protocol that the FakeDaprActivityDispatcher and orchestrator-side generator drive.

Adds the production ActivityRuntimeClient adapter that posts each
schedule_activity call to the Dapr sidecar's Service Invocation
API and reconstructs an ActivityResultEnvelope from the response.

Highlights:
- DaprActivityRuntimeClient(http_client, endpoint, timeout=10.0)
  with an async schedule_activity that POSTs to
  /v1.0/invoke/<arm-app-id>/method/ScheduleActivity with the
  canonical Idempotency-Key header built from IdempotencyTriple.
- camelCase wire envelope (runId, stepId, attempt, activityRef,
  inputs, connectorContexts, deadline) per the ARM design § Internal
  RPCs; ConnectorContext is serialised to the spec'd nested shape
  (slotName, handle, expiresAt, connectorKind).
- Transport / status / decode / cancelled failure modes are mapped
  through the WF-IMPL-075 OutboundRpcError taxonomy and rendered
  via map_to_activity_envelope, so the return value is always a
  shape-valid ActivityResultEnvelope (4xx -> permanent / 5xx +
  408/429 -> retryable / HTTP 499 -> cancelled / httpx.HTTPError
  -> retryable / shape-mismatch body -> permanent decode).
- cancel_activity raises NotImplementedError pending WF-IMPL-077.
- 100% coverage on the new adapter; full suite 1785 passed (1 known
  pre-existing flake), 98.97% line coverage.

Closes #487.
Copilot AI review requested due to automatic review settings June 1, 2026 04:59
@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.schedule_activity HTTP adapter for calling the Activity Runtime Manager via the local Dapr sidecar Service Invocation API, including full request/response wire-shape marshaling and WF-IMPL-075 outbound-RPC error-to-envelope mapping.

Changes:

  • Added DaprActivityRuntimeClient implementation in activity_runtime.py, including camelCase wire serialization, idempotency header, and status/transport/decode/cancelled error normalization into ActivityResultEnvelope.
  • Added a comprehensive httpx.MockTransport-based unit test suite covering success, status mapping matrix, transport/decode failures, idempotency header invariants, and timeout propagation.
  • Re-exported DaprActivityRuntimeClient from custos_workflow.clients.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
src/services/workflow-service/src/custos_workflow/clients/activity_runtime.py Adds the production Dapr Service Invocation adapter + wire (de)serialization helpers for ScheduleActivity.
src/services/workflow-service/tests/clients/test_dapr_activity_runtime.py Adds unit tests for the adapter’s wire contract and locked error taxonomy mapping.
src/services/workflow-service/src/custos_workflow/clients/init.py Re-exports DaprActivityRuntimeClient from the clients package.

💡 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
Comment thread src/services/workflow-service/src/custos_workflow/clients/activity_runtime.py Outdated
Comment thread src/services/workflow-service/tests/clients/test_dapr_activity_runtime.py Outdated
Comment thread src/services/workflow-service/tests/clients/test_dapr_activity_runtime.py Outdated
Comment thread src/services/workflow-service/tests/clients/test_dapr_activity_runtime.py Outdated
Comment thread src/services/workflow-service/tests/clients/test_dapr_activity_runtime.py Outdated
- Rewrite _iso_utc docstring to accurately describe that
  neither ScheduleActivityRequest nor IdempotencyTriple validate
  deadline tzinfo; this helper is the enforcement point.
- Update the naïve-tzinfo branch comment to match the actual
  behaviour (reject, not treat as UTC).
- Rename test_invalid_json_body_mapped_to_retryable_decode →
  test_invalid_json_body_mapped_to_permanent_decode so the
  name matches the asserted permanent class.
- Wrap the three remaining standalone tests
  (test_cancel_activity_raises_not_implemented,
  test_default_timeout_matches_constant,
  test_timeout_override_honoured) with async with
  httpx.AsyncClient(...) to avoid leaking the client.
@toddysm toddysm merged commit 9aab49a into main Jun 1, 2026
23 checks passed
@toddysm toddysm deleted the wf-impl-076-dapr-schedule-activity-adapter branch June 1, 2026 05:08
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-076: DaprActivityRuntimeClient adapter — ScheduleActivity

2 participants