Skip to content

feat(runner): per-SM execution roles + DockerLambdaStrategy (closes #5, #6) - #7

Merged
royassis merged 3 commits into
masterfrom
feat/multi-role-and-docker-lambda
Jul 13, 2026
Merged

royassis merged 3 commits into
masterfrom
feat/multi-role-and-docker-lambda

Conversation

@royassis

Copy link
Copy Markdown
Collaborator

Implements the two open enhancement issues. No external consumers, so the breaking API change in #5 is acceptable.

Summary

  • Support per-state-machine execution roles (multi-role fidelity) in WorkflowRunner #5 — Per-state-machine execution roles. WorkflowRunner no longer takes a single role_arn. Each asl_registry entry carries its own ROLE_ARN, validated at construction (no shared-role fallback). The active role is switched to a sub-machine's role at each nested startExecution boundary 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.
  • Add DockerLambdaStrategy — run a Lambda container image via the Runtime Interface Emulator #6 — DockerLambdaStrategy. Runs a lambda:invoke step's real Lambda container image locally via the AWS Runtime Interface Emulator (RIE), instead of a hand-written mock. Built on the pluggable ImageSource (like DockerBatchStrategy); resolves the event Payload via TestState TRACE, runs the image detached with the RIE port published, POSTs the event, and returns the {"Payload": <json-string>} result shape. Ships with a LocalLambdaImageStrategy alias and a get_lambda_payload() helper.

Details

#5

  • Drop role_arn ctor param; require ROLE_ARN per registry entry with a clear validation error.
  • Add active_role_arn + role_for(); switch/restore at nested startExecution.
  • Replace all orchestrator.role_arn reads with the active role.
  • AslDefinitionDict gains a NotRequired[str] ROLE_ARN.

#6

  • DockerLambdaStrategy / LocalLambdaImageStrategy taking an ImageSource.
  • Free-port selection, detached --rm container, AWS_* env forwarding, stdlib-urllib POST with retry-until-up.
  • Exported from the package root + __all__.

Docs / examples / tests

  • New examples/docker-lambda/ (echo Lambda image + ASL + run.py + README), wired into the integration harness + offline completeness guard.
  • All examples/docs updated to the per-SM ROLE_ARN shape; new strategy documented in docs/strategies.md.

Test plan

  • uv run pytest tests/unit -n auto — 33 pass (incl. nested-SM role switch, missing-ROLE_ARN validation, role_for(), Lambda payload/result encoding).
  • Offline examples completeness guard passes.
  • Docs build warning-clean (sphinx-build -W).
  • ruff + black clean; wheel includes the engine + py.typed.
  • RIE path verified with Docker end-to-end (built echo_lambda, real round-trip returned {"doubled": 42, "echo": {"number": 21}}).
  • Example integration tests (make test-examples) need ROLE_ARN + AWS creds — not runnable in this environment.

Closes #5. Closes #6.

🤖 Generated with Claude Code

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>

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment on lines +324 to +333
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
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 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.

Suggested change
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

royassis and others added 2 commits July 13, 2026 16:10
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>
@royassis
royassis merged commit 2a489a7 into master Jul 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant