feat(runner): per-SM execution roles + DockerLambdaStrategy (closes #5, #6) - #7
Conversation
Implements two enhancements (no external consumers; breaking API is acceptable). Issue #5 — per-state-machine execution roles (multi-role fidelity): - Drop the single `role_arn` ctor param from WorkflowRunner. - Require a `ROLE_ARN` on every asl_registry entry; validate at construction (clear error, no shared-role fallback). - Add `active_role_arn` + `role_for()`; set it to main's role in start() and switch/restore it at each nested startExecution boundary so a sub-machine runs under its own role. Replace all orchestrator.role_arn reads with the active role. - AslDefinitionDict gains a NotRequired ROLE_ARN. Issue #6 — DockerLambdaStrategy (run a real Lambda container image via the RIE): - Add DockerLambdaStrategy (+ LocalLambdaImageStrategy alias) built on the pluggable ImageSource, matching DockerBatchStrategy. - Add get_lambda_payload() (TestState TRACE -> afterArguments.Payload). - Free-port selection, detached container with the RIE port published, AWS_* env forwarded, POST the event to /2015-03-31/functions/function/invocations, and return the {"Payload": <json-string>} lambda:invoke result shape. - Export both new symbols from the package root + __all__. Docs, examples, and tests: - New examples/docker-lambda/ (echo Lambda image + ASL + run.py + README), wired into the integration harness + completeness guard. - Update all examples/docs to the per-SM ROLE_ARN shape. - Unit tests: role-switch across a nested SM boundary, missing-ROLE_ARN validation, role_for(), and the Lambda strategy's payload/result encoding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
This PR successfully implements per-state-machine execution roles and the DockerLambdaStrategy feature. The implementation is well-structured with comprehensive test coverage and clear documentation.
The API change removing the single role_arn parameter in favor of per-SM roles is a breaking change but acceptable for the project context. The role-switching mechanism in nested state machines is correctly implemented with proper restoration.
Critical Issue Found:
- Resource leak risk in
DockerLambdaStrategy._invoke_via_rie()where container cleanup may not occur if certain exceptions are raised (see comment on strategies.py:324-333)
Strengths:
- Comprehensive test coverage including role-switching scenarios
- Clear validation errors for missing
ROLE_ARN - Proper resource cleanup patterns in most cases
- Well-documented new strategy
Please address the critical resource leak issue before merging.
You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.
| try: | ||
| url = f"http://localhost:{host_port}{RIE_INVOCATION_PATH}" | ||
| return self._post_event(url, payload) | ||
| finally: | ||
| try: | ||
| docker.stop(container) | ||
| except Exception: # container may already be gone (--rm) | ||
| logger.debug( | ||
| "failed to stop RIE container (already removed?)", exc_info=True | ||
| ) |
There was a problem hiding this comment.
🛑 Resource Leak Risk: If _post_event raises an exception other than the caught types (e.g., json.JSONDecodeError from line 305), the container will not be stopped, causing resource leaks. The finally block at line 328 only catches exceptions from docker.stop(), not from _post_event(). Wrap the entire try block to ensure cleanup happens in all failure scenarios.
| try: | |
| url = f"http://localhost:{host_port}{RIE_INVOCATION_PATH}" | |
| return self._post_event(url, payload) | |
| finally: | |
| try: | |
| docker.stop(container) | |
| except Exception: # container may already be gone (--rm) | |
| logger.debug( | |
| "failed to stop RIE container (already removed?)", exc_info=True | |
| ) | |
| try: | |
| url = f"" | |
| result = self._post_event(url, payload) | |
| except Exception: | |
| # Ensure container cleanup on any failure, then re-raise | |
| try: | |
| docker.stop(container) | |
| except Exception: | |
| logger.debug("failed to stop RIE container", exc_info=True) | |
| raise | |
| else: | |
| # Normal cleanup after successful execution | |
| try: | |
| docker.stop(container) | |
| except Exception: | |
| logger.debug("failed to stop RIE container (already removed?)", exc_info=True) | |
| return result |
Fixes the failing "Lint + offline tests" check on PR #7 (ruff format --check). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements the two open enhancement issues. No external consumers, so the breaking API change in #5 is acceptable.
Summary
WorkflowRunnerno longer takes a singlerole_arn. Eachasl_registryentry carries its ownROLE_ARN, validated at construction (no shared-role fallback). The active role is switched to a sub-machine's role at each nestedstartExecutionboundary and restored afterward — so a parent and its sub-machine run under their real, distinct roles, surfacing per-role IAM scoping bugs a single shared role would hide.DockerLambdaStrategy. Runs alambda:invokestep's real Lambda container image locally via the AWS Runtime Interface Emulator (RIE), instead of a hand-written mock. Built on the pluggableImageSource(likeDockerBatchStrategy); resolves the eventPayloadvia TestState TRACE, runs the image detached with the RIE port published, POSTs the event, and returns the{"Payload": <json-string>}result shape. Ships with aLocalLambdaImageStrategyalias and aget_lambda_payload()helper.Details
#5
role_arnctor param; requireROLE_ARNper registry entry with a clear validation error.active_role_arn+role_for(); switch/restore at nestedstartExecution.orchestrator.role_arnreads with the active role.AslDefinitionDictgains aNotRequired[str]ROLE_ARN.#6
DockerLambdaStrategy/LocalLambdaImageStrategytaking anImageSource.--rmcontainer,AWS_*env forwarding, stdlib-urllibPOST with retry-until-up.__all__.Docs / examples / tests
examples/docker-lambda/(echo Lambda image + ASL + run.py + README), wired into the integration harness + offline completeness guard.ROLE_ARNshape; new strategy documented indocs/strategies.md.Test plan
uv run pytest tests/unit -n auto— 33 pass (incl. nested-SM role switch, missing-ROLE_ARNvalidation,role_for(), Lambda payload/result encoding).sphinx-build -W).ruff+blackclean; wheel includes the engine +py.typed.echo_lambda, real round-trip returned{"doubled": 42, "echo": {"number": 21}}).make test-examples) needROLE_ARN+ AWS creds — not runnable in this environment.Closes #5. Closes #6.
🤖 Generated with Claude Code