The generic EC2 fallback iterated over Reservations dicts and called
item.get("InstanceId", "") on each Reservation, which always returned ""
because Reservations have "ReservationId"/"Instances" — not "InstanceId".
Every result had id="" and a malformed ARN.
Add a dedicated handler before the generic fallback that unpacks
response["Reservations"] -> reservation["Instances"] -> InstanceId,
mirroring the pattern used for s3/ecs/eks/iam/lambda/rds.
Also add BDD scenario, step definitions, and mock helper support for
describe_instances returning N instances with non-empty IDs.
ISSUES CLOSED: #1021
Implements real AWS SDK integration for CloudResourceHandler using boto3
as an optional dependency. Key changes:
- Add boto3/botocore as optional [aws] dependency in pyproject.toml
- Implement CloudResourceHandler.resolve() for AWS: builds boto3 session,
verifies credentials via STS get_caller_identity for account-level types,
and returns a BoundResource with the resource ARN as sandbox_path
- Implement discover_aws_resources() to enumerate VPCs, subnets, instances,
S3 buckets, IAM roles, RDS instances, ECS clusters, Lambda functions,
and EKS clusters via the AWS API
- Implement CloudResourceHandler.discover_children() for AWS resource types
using the new discovery function
- Implement CloudSandboxStrategy.create/commit/rollback for AWS using a
tag-based isolation strategy (CleverAgents:PlanId tag)
- GCP and Azure providers still raise NotImplementedError (pending)
- boto3 is optional: handler raises ImportError with helpful install message
when boto3 is not installed
- Credentials are never logged (existing redaction infrastructure preserved)
- Update cloud_resources.feature to reflect new AWS behavior
- Add comprehensive cloud_aws_sdk.feature with 47 BDD scenarios covering
all new code paths with mocked boto3
Closes#1021