feat(trigger-service): add WorkflowServiceClient Dapr adapter (TS-IMPL-013)#666
Merged
Conversation
Add the outbound Dapr service-invocation client the dispatcher uses to drive
the Workflow Service Internal RPCs: start_run -> POST /internal/runs:start and
raise_external_event -> POST /internal/runs/{runId}/steps/{stepId}:raiseEvent.
Request bodies mirror the WF InternalStartRunRequest/RaiseExternalEventRequest
wire contracts; idempotencyKey is propagated in the body and Idempotency-Key
header. Transport failures and 408/429/5xx raise a retryable error; permanent
4xx and undecodable bodies raise non-retryable. Adds Noop and Fake doubles and
promotes httpx to a runtime dependency.
Closes #643
Contributor
There was a problem hiding this comment.
Pull request overview
Adds the Trigger Service’s outbound WorkflowServiceClient implementation for invoking Workflow Service internal RPCs via the local Dapr sidecar, plus test coverage and dependency updates to support runtime httpx usage.
Changes:
- Introduces
DaprWorkflowServiceClientwith wire models and retryable/permanent error taxonomy forstart_runandraise_external_event. - Adds unit tests covering endpoint/url helpers, success paths, error mapping, and doubles (
Noop/Fake). - Promotes
httpxto a runtime dependency and marks TS-IMPL-013 complete in the trigger-service design TODOs.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/services/trigger-service/src/custos_trigger/clients/workflow.py | Adds the Dapr service-invocation adapter, wire models, error taxonomy, and doubles for Workflow Service RPCs. |
| src/services/trigger-service/tests/test_workflow_client.py | Adds async unit tests for URL/env helpers, success/error behavior, and client doubles. |
| src/services/trigger-service/src/custos_trigger/clients/init.py | Introduces the clients package and re-exports the workflow client surface. |
| src/services/trigger-service/pyproject.toml | Moves httpx from dev-only to runtime dependency. |
| design/components/trigger-service/todos.md | Marks TS-IMPL-013 as completed. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Reject empty host/app_id and non-positive port in DaprEndpoint, and reject empty/slash-only method paths in build_invoke_url, mirroring the workflow- service _dapr_invoke precedent and failing fast on unusable endpoints.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements TS-IMPL-013 — the
WorkflowServiceClientDapr service-invocationadapter the dispatcher (TS-IMPL-014) uses to drive the Workflow Service's two
inbound Internal RPCs.
Closes #643What's included
custos_trigger/clients/workflow.py— outbound client over the local Daprsidecar (
http://{host}:{port}/v1.0/invoke/{appId}/method/{method}), mirroringthe Workflow Service's own
_dapr_invokeprecedent:start_run(...)→POST /internal/runs:start, returnsRunRef.raise_external_event(run_id, step_id, ...)→POST /internal/runs/{runId}/steps/{stepId}:raiseEvent(202, empty body).StartRunRequest,RaiseExternalEventRequest) match the WFInternalStartRunRequest/RaiseExternalEventRequestcamelCase wirecontracts;
idempotencyKeyis sent in the body and as anIdempotency-Keyheader when present.408/429/5xxraise a retryableWorkflowClientError; permanent4xxand undecodable 2xx bodies raise anon-retryable one — so the dispatcher can backoff/retry vs. dead-letter.
DaprEndpoint+build_invoke_url+read_dapr_endpoint(host/port fromDAPR_HTTP_HOST/DAPR_HTTP_PORT, app id supplied by the caller).NoopWorkflowServiceClientand recordingFakeWorkflowServiceClientdoubles.pyproject.toml— promoteshttpxfrom a dev-only to a runtime dependency(the client uses it at runtime).
tests/test_workflow_client.py— async coverage of URL/endpoint helpers,start/raise success, idempotency header on/off, retryable 5xx/429, permanent
4xx, transport + decode errors, and both doubles.
Acceptance
InternalStartRunRequest/RaiseExternalEventRequestschemas.5xxsurface a retryable error;Fakerecords calls; 100% adapter coverage.ruff/ruff format/mypyclean;pytest≥90% (99.9% total).