tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.Check workspace and tool versions
ls /app && echo "---" && aws --version && python3 --version && which jq zip curl
--- aws-cli/2.34.33 Python/3.14.4 Linux/6.10.14-linuxkit exe/aarch64.ubuntu.24 Python 3.12.3 /usr/bin/jq /usr/bin/zip /usr/bin/curl [stdout] --- aws-cli/2.34.33 Python/3.14.4 Linux/6.10.14-linuxkit exe/aarch64.ubuntu.24 Python 3.12.3 /usr/bin/jq /usr/bin/zip /usr/bin/curl
Check LocalStack health
curl -s http://localstack:4566/_localstack/health | jq .
{
"services": {
"acm": "disabled",
"apigateway": "disabled",
"cloudformation": "disabled",
"cloudwatch": "disabled",
"config": "disabled",
"dynamodb": "disabled",
"dynamodbstreams": "disabled",
"ec2": "disabled",
"es": "disabled",
"events": "available",
"firehose": "disabled",
"iam": "available",
"kinesis": "disabled",
"kms": "available",
"lambda": "available",
"logs": "available",
"opensearch": "disabled",
"redshift": "disabled",
"resource-groups": "disabled",
"resourcegroupstaggingapi": "disabled",
"route53": "disabled",
"route53resolver": "disabled",
"s3": "available",
"s3control": "disabled",
"scheduler": "disabled",
"secretsmanager": "available",
"ses": "disabled",
"sns": "disabled",
"sqs": "disabled",
"ssm": "disabled",
"stepfunctions": "available",
"sts": "available",
"support": "disabled",
"swf": "disabled",
"transcribe": "disabled"
},
"edition": "community",
"version": "4.13.1"
}
[stdout]
{
"services": {
"acm": "disabled",
"apigateway": "disabled",
"cloudformation": "disabled",
"cloudwatch": "disabled",
"config": "disabled",
"dynamodb": "disabled",
"dynamodbstreams": "disabled",
"ec2": "disabled",
"es": "disabled",
"events": "available",
"firehose": "disabled",
"iam": "available",
"kinesis": "disabled",
"kms": "available",
"lambda": "available",
"logs": "available",
"opensearch": "disabled",
"redshift": "disabled",
"resource-groups": "disabled",
"resourcegroupstaggingapi": "disabled",
"route53": "disabled",
"route53resolver": "disabled",
"s3": "available",
"s3control": "disabled",
"scheduler": "disabled",
"secretsmanager": "available",
"ses": "disabled",
"sns": "disabled",
"sqs": "disabled",
"ssm": "disabled",
"stepfunctions": "available",
"sts": "available",
"support": "disabled",
"swf": "disabled",
"transcribe": "disabled"
},
"edition": "community",
"version": "4.13.1"
}Create working directory structure
mkdir -p /app/work/lambdas/rotator /app/work/lambdas/notifier /app/work/policies /app/work/asl && ls /app/work
asl lambdas policies [stdout] asl lambdas policies
Verify AWS CLI against LocalStack
echo "endpoint=http://localstack:4566" && export AWS_ENDPOINT_URL=http://localstack:4566 && aws sts get-caller-identity
endpoint=http://localstack:4566
{
"UserId": "AKIAIOSFODNN7EXAMPLE",
"Account": "000000000000",
"Arn": "arn:aws:iam::000000000000:root"
}
[stdout]
endpoint=http://localstack:4566
{
"UserId": "AKIAIOSFODNN7EXAMPLE",
"Account": "000000000000",
"Arn": "arn:aws:iam::000000000000:root"
}Create env helper
cat > /app/work/env.sh <<'EOF' export AWS_ENDPOINT_URL=http://localstack:4566 export AWS_DEFAULT_REGION=us-east-1 export AWS_REGION=us-east-1 EOF echo done
done [stdout] done
{
"Version": "2012-10-17",
"Id": "app-rotation-key-policy",
"Statement": [
{
"Sid": "EnableRootAccountAdmin",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::000000000000:root" },
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "AllowSecretsManagerUseOfKey",
"Effect": "Allow",
"Principal": { "Service": "secretsmanager.amazonaws.com" },
"Action": [
"kms:Decrypt",
"kms:Encrypt",
"kms:GenerateDataKey",
"kms:DescribeKey",
"kms:ReEncrypt*"
],
"Resource": "*"
},
{
"Sid": "AllowRotatorLambdaRoleUseOfKey",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::000000000000:role/secrets-rotator-role" },
"Action": [
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:DescribeKey"
],
"Resource": "*"
}
]
}
Create customer-managed KMS key and alias
source /app/work/env.sh KEY_ID=$(aws kms create-key \ --description "CMK for app/db/password rotation" \ --key-usage ENCRYPT_DECRYPT \ --policy file:///app/work/policies/kms-key-policy.json \ --query 'KeyMetadata.KeyId' --output text) echo "KEY_ID=$KEY_ID" | tee /app/work/state.env aws kms create-alias --alias-name alias/app-rotation-key --target-key-id "$KEY_ID" KEY_ARN=$(aws kms describe-key --key-id alias/app-rotation-key --query 'KeyMetadata.Arn' --output text) echo "KEY_ARN=$KEY_ARN" | tee -a /app/work/state.env
KEY_ID=ec4bc77e-2c74-420c-babb-ab282582d1fe KEY_ARN=arn:aws:kms:us-east-1:000000000000:key/ec4bc77e-2c74-420c-babb-ab282582d1fe [stdout] KEY_ID=ec4bc77e-2c74-420c-babb-ab282582d1fe KEY_ARN=arn:aws:kms:us-east-1:000000000000:key/ec4bc77e-2c74-420c-babb-ab282582d1fe
Create secret encrypted with CMK
source /app/work/env.sh source /app/work/state.env SECRET_ARN=$(aws secretsmanager create-secret \ --name app/db/password \ --description "App DB password (rotated via Step Functions)" \ --kms-key-id alias/app-rotation-key \ --secret-string "initial-placeholder-$(date +%s)" \ --query 'ARN' --output text) echo "SECRET_ARN=$SECRET_ARN" | tee -a /app/work/state.env
SECRET_ARN=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR [stdout] SECRET_ARN=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR
{
"query": "select:TodoWrite",
"max_results": 1
}
, output,
{"type": "tool_reference", "tool_name": "TodoWrite"}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Logs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:us-east-1:000000000000:log-group:/aws/lambda/secrets-rotator:*"
},
{
"Sid": "SecretOps",
"Effect": "Allow",
"Action": [
"secretsmanager:DescribeSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:PutSecretValue",
"secretsmanager:UpdateSecretVersionStage"
],
"Resource": "arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-*"
},
{
"Sid": "KmsForSecret",
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:us-east-1:000000000000:key/*",
"Condition": {
"StringEquals": {
"kms:ViaService": "secretsmanager.us-east-1.amazonaws.com"
}
}
},
{
"Sid": "GetRandomPassword",
"Effect": "Allow",
"Action": "secretsmanager:GetRandomPassword",
"Resource": "*"
}
]
}
Create rotator lambda role with inline policy
source /app/work/env.sh ROTATOR_ROLE_ARN=$(aws iam create-role \ --role-name secrets-rotator-role \ --assume-role-policy-document file:///app/work/policies/lambda-trust.json \ --query 'Role.Arn' --output text) echo "ROTATOR_ROLE_ARN=$ROTATOR_ROLE_ARN" | tee -a /app/work/state.env aws iam put-role-policy \ --role-name secrets-rotator-role \ --policy-name secrets-rotator-inline \ --policy-document file:///app/work/policies/rotator-role-policy.json echo done
ROTATOR_ROLE_ARN=arn:aws:iam::000000000000:role/secrets-rotator-role done [stdout] ROTATOR_ROLE_ARN=arn:aws:iam::000000000000:role/secrets-rotator-role done
import logging
import os
import secrets
import string
import boto3
log = logging.getLogger()
log.setLevel(logging.INFO)
ENDPOINT = os.environ.get("AWS_ENDPOINT_URL_LOCAL", "http://localstack:4566")
sm = boto3.client("secretsmanager", endpoint_url=ENDPOINT, region_name="us-east-1")
ALPHABET = string.ascii_letters + string.digits + "-_"
def _generate_password(n: int = 32) -> str:
return "".join(secrets.choice(ALPHABET) for _ in range(n))
def lambda_handler(event, _context):
log.info("rotation event: %s", event)
step = event["Step"]
secret_id = event["SecretId"]
token = event["ClientRequestToken"]
meta = sm.describe_secret(SecretId=secret_id)
version_stages = meta.get("VersionIdsToStages", {})
if token not in version_stages:
raise ValueError(f"Token {token} has no stage for secret {secret_id}")
if "AWSCURRENT" in version_stages[token]:
log.info("Version %s already AWSCURRENT; nothing to do", token)
return
if "AWSPENDING" not in version_stages[token]:
raise ValueError(f"Token {token} not pending for secret {secret_id}")
if step == "createSecret":
return create_secret(secret_id, token)
if step == "setSecret":
return set_secret(secret_id, token)
if step == "testSecret":
return test_secret(secret_id, token)
if step == "finishSecret":
return finish_secret(secret_id, token)
raise ValueError(f"Unknown step {step}")
def create_secret(secret_id, token):
try:
sm.get_secret_value(SecretId=secret_id, VersionId=token, VersionStage="AWSPENDING")
log.info("AWSPENDING already exists for %s", token)
return
except sm.exceptions.ResourceNotFoundException:
pass
new_password = _generate_password()
sm.put_secret_value(
SecretId=secret_id,
ClientRequestToken=token,
SecretString=new_password,
VersionStages=["AWSPENDING"],
)
log.info("Created AWSPENDING version %s", token)
def set_secret(_secret_id, _token):
# No external database to update in this demo pipeline.
log.info("setSecret: no-op")
def test_secret(secret_id, token):
v = sm.get_secret_value(SecretId=secret_id, VersionId=token, VersionStage="AWSPENDING")
if not v.get("SecretString"):
raise ValueError("AWSPENDING has no SecretString")
log.info("testSecret: AWSPENDING readable")
def finish_secret(secret_id, token):
meta = sm.describe_secret(SecretId=secret_id)
current_version = None
for vid, stages in meta.get("VersionIdsToStages", {}).items():
if "AWSCURRENT" in stages:
current_version = vid
break
if current_version == token:
log.info("finishSecret: already current")
return
sm.update_secret_version_stage(
SecretId=secret_id,
VersionStage="AWSCURRENT",
MoveToVersionId=token,
RemoveFromVersionId=current_version,
)
log.info("finishSecret: moved AWSCURRENT from %s to %s", current_version, token)
Package and deploy rotator lambda
source /app/work/env.sh
source /app/work/state.env
cd /app/work/lambdas/rotator && zip -q rotator.zip handler.py
# retry a few times because IAM role propagation can be eventually-consistent even on LocalStack
for i in 1 2 3 4 5; do
out=$(aws lambda create-function \
--function-name secrets-rotator \
--runtime python3.11 \
--role "$ROTATOR_ROLE_ARN" \
--handler handler.lambda_handler \
--timeout 30 \
--environment "Variables={AWS_ENDPOINT_URL_LOCAL=http://localstack:4566}" \
--zip-file fileb:///app/work/lambdas/rotator/rotator.zip 2>&1) && break
echo "retry $i: $out"
sleep 2
done
ROTATOR_ARN=$(aws lambda get-function --function-name secrets-rotator --query 'Configuration.FunctionArn' --output text)
echo "ROTATOR_ARN=$ROTATOR_ARN" | tee -a /app/work/state.envROTATOR_ARN=arn:aws:lambda:us-east-1:000000000000:function:secrets-rotator [stdout] ROTATOR_ARN=arn:aws:lambda:us-east-1:000000000000:function:secrets-rotator
Attach rotator to secret
source /app/work/env.sh
source /app/work/state.env
# wait for lambda to be Active (LocalStack creates container lazily)
for i in $(seq 1 30); do
state=$(aws lambda get-function --function-name secrets-rotator --query 'Configuration.State' --output text)
[ "$state" = "Active" ] && { echo "rotator Active"; break; }
sleep 1
done
# Secrets Manager must be allowed to invoke the lambda; this is a Lambda resource policy, not an IAM role policy.
aws lambda add-permission \
--function-name secrets-rotator \
--statement-id AllowSecretsManagerInvoke \
--action lambda:InvokeFunction \
--principal secretsmanager.amazonaws.com \
--source-arn "$SECRET_ARN" \
--source-account 000000000000 \
> /dev/null
aws secretsmanager rotate-secret \
--secret-id app/db/password \
--rotation-lambda-arn "$ROTATOR_ARN" \
--rotation-rules AutomaticallyAfterDays=30 \
--no-rotate-immediately \
> /dev/null
aws secretsmanager describe-secret --secret-id app/db/password \
--query '{RotationEnabled:RotationEnabled,RotationLambda:RotationLambdaARN}'rotator Active
aws: [ERROR]: An error occurred (InternalError) when calling the RotateSecret operation (reached max retries: 2): exception while calling secretsmanager.RotateSecret: cannot access local variable 'new_version_id' where it is not associated with a value
{
"RotationEnabled": true,
"RotationLambda": "arn:aws:lambda:us-east-1:000000000000:function:secrets-rotator"
}
[stdout]
rotator Active
aws: [ERROR]: An error occurred (InternalError) when calling the RotateSecret operation (reached max retries: 2): exception while calling secretsmanager.RotateSecret: cannot access local variable 'new_version_id' where it is not associated with a value
{
"RotationEnabled": true,
"RotationLambda": "arn:aws:lambda:us-east-1:000000000000:function:secrets-rotator"
}Smoke test rotation
source /app/work/env.sh before=$(aws secretsmanager get-secret-value --secret-id app/db/password --query '[VersionId,SecretString]' --output json) echo "BEFORE: $before" aws secretsmanager rotate-secret --secret-id app/db/password --query '[VersionId,ARN]' --output json sleep 2 after=$(aws secretsmanager get-secret-value --secret-id app/db/password --query '[VersionId,SecretString]' --output json) echo "AFTER: $after" aws secretsmanager describe-secret --secret-id app/db/password --query 'VersionIdsToStages'
BEFORE: [
"7c4283f2-907d-4a8b-89bb-0b3bf9d2b637",
"initial-placeholder-1776839166"
]
[
"ad9c35e2-2587-470e-b0eb-ea91eba5748d",
"arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR"
]
AFTER: [
"ad9c35e2-2587-470e-b0eb-ea91eba5748d",
"TDtVOe897Tqt9LJtO7gjr8-AQifBEUFC"
]
{
"7c4283f2-907d-4a8b-89bb-0b3bf9d2b637": [
"AWSPREVIOUS"
],
"ad9c35e2-2587-470e-b0eb-ea91eba5748d": [
"AWSPENDING",
"AWSCURRENT"
]
}
[stdout]
BEFORE: [
"7c4283f2-907d-4a8b-89bb-0b3bf9d2b637",
"initial-placeholder-1776839166"
]
[
"ad9c35e2-2587-470e-b0eb-ea91eba5748d",
"arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR"
]
AFTER: [
"ad9c35e2-2587-470e-b0eb-ea91eba5748d",
"TDtVOe897Tqt9LJtO7gjr8-AQifBEUFC"
]
{
"7c4283f2-907d-4a8b-89bb-0b3bf9d2b637": [
"AWSPREVIOUS"
],
"ad9c35e2-2587-470e-b0eb-ea91eba5748d": [
"AWSPENDING",
"AWSCURRENT"
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Logs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:us-east-1:000000000000:log-group:/aws/lambda/notification-lambda:*"
}
]
}
import json
import logging
log = logging.getLogger()
log.setLevel(logging.INFO)
def lambda_handler(event, _context):
detail = event.get("detail", {}) if isinstance(event, dict) else {}
secret = detail.get("secretId", "<unknown>")
version = detail.get("versionId", "<unknown>")
log.info("notified: RotationComplete secret=%s version=%s payload=%s",
secret, version, json.dumps(event))
return {"ok": True}
Create notification-lambda role + lambda
source /app/work/env.sh
NOTIFIER_ROLE_ARN=$(aws iam create-role \
--role-name notification-lambda-role \
--assume-role-policy-document file:///app/work/policies/lambda-trust.json \
--query 'Role.Arn' --output text)
echo "NOTIFIER_ROLE_ARN=$NOTIFIER_ROLE_ARN" | tee -a /app/work/state.env
aws iam put-role-policy \
--role-name notification-lambda-role \
--policy-name notification-lambda-inline \
--policy-document file:///app/work/policies/notifier-role-policy.json
cd /app/work/lambdas/notifier && zip -q notifier.zip handler.py
for i in 1 2 3 4 5; do
aws lambda create-function \
--function-name notification-lambda \
--runtime python3.11 \
--role "$NOTIFIER_ROLE_ARN" \
--handler handler.lambda_handler \
--timeout 15 \
--zip-file fileb:///app/work/lambdas/notifier/notifier.zip > /dev/null 2>&1 && break
sleep 2
done
NOTIFIER_ARN=$(aws lambda get-function --function-name notification-lambda --query 'Configuration.FunctionArn' --output text)
echo "NOTIFIER_ARN=$NOTIFIER_ARN" | tee -a /app/work/state.env
for i in $(seq 1 30); do
state=$(aws lambda get-function --function-name notification-lambda --query 'Configuration.State' --output text)
[ "$state" = "Active" ] && { echo "notifier Active"; break; }
sleep 1
doneNOTIFIER_ROLE_ARN=arn:aws:iam::000000000000:role/notification-lambda-role NOTIFIER_ARN=arn:aws:lambda:us-east-1:000000000000:function:notification-lambda notifier Active [stdout] NOTIFIER_ROLE_ARN=arn:aws:iam::000000000000:role/notification-lambda-role NOTIFIER_ARN=arn:aws:lambda:us-east-1:000000000000:function:notification-lambda notifier Active
Create bus, rule, target, and lambda invoke permission
source /app/work/env.sh
source /app/work/state.env
BUS_ARN=$(aws events create-event-bus --name rotation-events --query 'EventBusArn' --output text)
echo "BUS_ARN=$BUS_ARN" | tee -a /app/work/state.env
RULE_ARN=$(aws events put-rule \
--name on-rotation-success \
--event-bus-name rotation-events \
--event-pattern '{"source":["rotation.pipeline"],"detail-type":["RotationComplete"]}' \
--state ENABLED \
--query 'RuleArn' --output text)
echo "RULE_ARN=$RULE_ARN" | tee -a /app/work/state.env
aws events put-targets \
--event-bus-name rotation-events \
--rule on-rotation-success \
--targets "Id=1,Arn=$NOTIFIER_ARN"
aws lambda add-permission \
--function-name notification-lambda \
--statement-id AllowEventBridgeInvoke \
--action lambda:InvokeFunction \
--principal events.amazonaws.com \
--source-arn "$RULE_ARN" > /dev/null
echo okBUS_ARN=arn:aws:events:us-east-1:000000000000:event-bus/rotation-events
RULE_ARN=arn:aws:events:us-east-1:000000000000:rule/rotation-events/on-rotation-success
{
"FailedEntryCount": 0,
"FailedEntries": []
}
ok
[stdout]
BUS_ARN=arn:aws:events:us-east-1:000000000000:event-bus/rotation-events
RULE_ARN=arn:aws:events:us-east-1:000000000000:rule/rotation-events/on-rotation-success
{
"FailedEntryCount": 0,
"FailedEntries": []
}
okProbe bus → rule → lambda path
source /app/work/env.sh
# quick end-to-end sanity check for the bus → rule → lambda path
aws events put-events --entries "[{\"Source\":\"rotation.pipeline\",\"DetailType\":\"RotationComplete\",\"EventBusName\":\"rotation-events\",\"Detail\":\"{\\\"probe\\\":true}\"}]"
sleep 3
# find the log group/stream for the notifier
LG=/aws/lambda/notification-lambda
aws logs describe-log-streams --log-group-name "$LG" --order-by LastEventTime --descending --max-items 1 --query 'logStreams[0].logStreamName' --output text 2>/dev/null | head -1{
"FailedEntryCount": 0,
"Entries": [
{
"EventId": "f9d68320-589b-4096-9df3-cab2a5b98790"
}
]
}
2026/04/22/[$LATEST]b3ace30e16f26bceb79d9d5168fbcf99
[stdout]
{
"FailedEntryCount": 0,
"Entries": [
{
"EventId": "f9d68320-589b-4096-9df3-cab2a5b98790"
}
]
}
2026/04/22/[$LATEST]b3ace30e16f26bceb79d9d5168fbcf99Confirm notifier logged "notified:"
source /app/work/env.sh aws logs filter-log-events --log-group-name /aws/lambda/notification-lambda --filter-pattern '"notified:"' --query 'events[*].message' --output text | head -5
START RequestId: ab23eb75-fbaf-46c9-a4d2-4715553e7950 Version: $LATEST
[INFO] 2026-04-22T06:28:43.524Z ab23eb75-fbaf-46c9-a4d2-4715553e7950 notified: RotationComplete secret=<unknown> version=<unknown> payload={"version": "0", "id": "f9d68320-589b-4096-9df3-cab2a5b98790", "detail-type": "RotationComplete", "source": "rotation.pipeline", "account": "000000000000", "time": "2026-04-22T06:28:42Z", "region": "us-east-1", "resources": [], "detail": {"probe": true}}
END RequestId: ab23eb75-fbaf-46c9-a4d2-4715553e7950
REPORT RequestId: ab23eb75-fbaf-46c9-a4d2-4715553e7950 Duration: 11.02 ms Billed Duration: 12 ms Memory Size: 128 MB Max Memory Used: 128 MB
[stdout]
START RequestId: ab23eb75-fbaf-46c9-a4d2-4715553e7950 Version: $LATEST
[INFO] 2026-04-22T06:28:43.524Z ab23eb75-fbaf-46c9-a4d2-4715553e7950 notified: RotationComplete secret=<unknown> version=<unknown> payload={"version": "0", "id": "f9d68320-589b-4096-9df3-cab2a5b98790", "detail-type": "RotationComplete", "source": "rotation.pipeline", "account": "000000000000", "time": "2026-04-22T06:28:42Z", "region": "us-east-1", "resources": [], "detail": {"probe": true}}
END RequestId: ab23eb75-fbaf-46c9-a4d2-4715553e7950
REPORT RequestId: ab23eb75-fbaf-46c9-a4d2-4715553e7950 Duration: 11.02 ms Billed Duration: 12 ms Memory Size: 128 MB Max Memory Used: 128 MB{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "states.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RotateTheSecret",
"Effect": "Allow",
"Action": [
"secretsmanager:RotateSecret",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-*"
},
{
"Sid": "PutEventOnRotationBus",
"Effect": "Allow",
"Action": "events:PutEvents",
"Resource": "arn:aws:events:us-east-1:000000000000:event-bus/rotation-events"
}
]
}
{
"Comment": "Rotates app/db/password then publishes RotationComplete to the rotation-events bus",
"StartAt": "RotateSecret",
"States": {
"RotateSecret": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:secretsmanager:rotateSecret",
"Parameters": {
"SecretId": "app/db/password"
},
"ResultPath": "$.rotation",
"Next": "WaitForRotation"
},
"WaitForRotation": {
"Type": "Wait",
"Seconds": 3,
"Next": "DescribeSecret"
},
"DescribeSecret": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:secretsmanager:describeSecret",
"Parameters": {
"SecretId": "app/db/password"
},
"ResultPath": "$.describe",
"Next": "PublishSuccess"
},
"PublishSuccess": {
"Type": "Task",
"Resource": "arn:aws:states:::events:putEvents",
"Parameters": {
"Entries": [
{
"EventBusName": "rotation-events",
"Source": "rotation.pipeline",
"DetailType": "RotationComplete",
"Detail": {
"secretId.$": "$.rotation.ARN",
"versionId.$": "$.rotation.VersionId"
}
}
]
},
"End": true
}
}
}
Create child SM role + state machine
source /app/work/env.sh
CHILD_ROLE_ARN=$(aws iam create-role \
--role-name rotation-child-role \
--assume-role-policy-document file:///app/work/policies/sfn-trust.json \
--query 'Role.Arn' --output text)
echo "CHILD_ROLE_ARN=$CHILD_ROLE_ARN" | tee -a /app/work/state.env
aws iam put-role-policy \
--role-name rotation-child-role \
--policy-name rotation-child-inline \
--policy-document file:///app/work/policies/rotation-child-role-policy.json
for i in 1 2 3 4 5; do
CHILD_SM_ARN=$(aws stepfunctions create-state-machine \
--name rotation-child \
--type STANDARD \
--role-arn "$CHILD_ROLE_ARN" \
--definition file:///app/work/asl/rotation-child.json \
--query 'stateMachineArn' --output text 2>/dev/null) && break
sleep 2
done
echo "CHILD_SM_ARN=$CHILD_SM_ARN" | tee -a /app/work/state.envCHILD_ROLE_ARN=arn:aws:iam::000000000000:role/rotation-child-role CHILD_SM_ARN=arn:aws:states:us-east-1:000000000000:stateMachine:rotation-child [stdout] CHILD_ROLE_ARN=arn:aws:iam::000000000000:role/rotation-child-role CHILD_SM_ARN=arn:aws:states:us-east-1:000000000000:stateMachine:rotation-child
Test child SM independently
source /app/work/env.sh
source /app/work/state.env
# smoke-test child SM directly
EX=$(aws stepfunctions start-execution --state-machine-arn "$CHILD_SM_ARN" --input '{}' --query 'executionArn' --output text)
for i in $(seq 1 30); do
st=$(aws stepfunctions describe-execution --execution-arn "$EX" --query 'status' --output text)
[ "$st" = "RUNNING" ] || break
sleep 1
done
echo "child status: $st"
aws stepfunctions describe-execution --execution-arn "$EX" --query '{status:status,output:output,error:error,cause:cause}'child status: FAILED
{
"status": "FAILED",
"output": null,
"error": "States.Runtime",
"cause": "An error occurred while executing the state 'PublishSuccess' (entered at the event id #14). The JSONPath '$.rotation.ARN' could not be found in the input '{\"rotation\":{\"Arn\":\"arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR\",\"Name\":\"app/db/password\",\"VersionId\":\"9c50713d-d8ba-4d00-96c6-4a8875bc641d\"},\"describe\":{\"Arn\":\"arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR\",\"Name\":\"app/db/password\",\"Description\":\"App DB password (rotated via Step Functions)\",\"KmsKeyId\":\"alias/app-rotation-key\",\"RotationEnabled\":true,\"RotationLambdaArn\":\"arn:aws:lambda:us-east-1:000000000000:function:secrets-rotator\",\"RotationRules\":{\"AutomaticallyAfterDays\":30},\"LastRotatedDate\":\"2026-04-22T06:29:33Z\",\"LastChangedDate\":\"2026-04-22T06:29:30Z\",\"LastAccessedDate\":\"2026-04-22T00:00:00Z\",\"NextRotationDate\":\"2026-05-22T06:29:29Z\",\"VersionIdsToStages\":{\"ad9c35e2-2587-470e-b0eb-ea91eba5748d\":[\"AWSPREVIOUS\"],\"9c50713d-d8ba-4d00-96c6-4a8875bc641d\":[\"AWSPENDING\",\"AWSCURRENT\"]},\"CreatedDate\":\"2026-04-22T06:26:07Z\"}}'"
}
[stdout]
child status: FAILED
{
"status": "FAILED",
"output": null,
"error": "States.Runtime",
"cause": "An error occurred while executing the state 'PublishSuccess' (entered at the event id #14). The JSONPath '$.rotation.ARN' could not be found in the input '{\"rotation\":{\"Arn\":\"arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR\",\"Name\":\"app/db/password\",\"VersionId\":\"9c50713d-d8ba-4d00-96c6-4a8875bc641d\"},\"describe\":{\"Arn\":\"arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR\",\"Name\":\"app/db/password\",\"Description\":\"App DB password (rotated via Step Functions)\",\"KmsKeyId\":\"alias/app-rotation-key\",\"RotationEnabled\":true,\"RotationLambdaArn\":\"arn:aws:lambda:us-east-1:000000000000:function:secrets-rotator\",\"RotationRules\":{\"AutomaticallyAfterDays\":30},\"LastRotatedDate\":\"2026-04-22T06:29:33Z\",\"LastChangedDate\":\"2026-04-22T06:29:30Z\",\"LastAccessedDate\":\"2026-04-22T00:00:00Z\",\"NextRotationDate\":\"2026-05-22T06:29:29Z\",\"VersionIdsToStages\":{\"ad9c35e2-2587-470e-b0eb-ea91eba5748d\":[\"AWSPREVIOUS\"],\"9c50713d-d8ba-4d00-96c6-4a8875bc641d\":[\"AWSPENDING\",\"AWSCURRENT\"]},\"CreatedDate\":\"2026-04-22T06:26:07Z\"}}'"
}/app/work/asl/rotation-child.json
"secretId.$": "$.rotation.ARN",
"versionId.$": "$.rotation.VersionId" "secretId.$": "$.rotation.Arn",
"versionId.$": "$.rotation.VersionId"Retest child SM after fix
source /app/work/env.sh
source /app/work/state.env
aws stepfunctions update-state-machine \
--state-machine-arn "$CHILD_SM_ARN" \
--definition file:///app/work/asl/rotation-child.json > /dev/null
EX=$(aws stepfunctions start-execution --state-machine-arn "$CHILD_SM_ARN" --input '{}' --query 'executionArn' --output text)
for i in $(seq 1 30); do
st=$(aws stepfunctions describe-execution --execution-arn "$EX" --query 'status' --output text)
[ "$st" = "RUNNING" ] || break
sleep 1
done
echo "child status: $st"
aws stepfunctions describe-execution --execution-arn "$EX" --query '{status:status,error:error}'child status: SUCCEEDED
{
"status": "SUCCEEDED",
"error": null
}
[stdout]
child status: SUCCEEDED
{
"status": "SUCCEEDED",
"error": null
}Confirm child SM triggered notifier
source /app/work/env.sh sleep 2 aws logs filter-log-events --log-group-name /aws/lambda/notification-lambda --filter-pattern '"notified:"' --query 'events[-1].message' --output text
REPORT RequestId: fc00cfb1-09bb-41bb-b416-7b257d2e8ce3 Duration: 12.21 ms Billed Duration: 13 ms Memory Size: 128 MB Max Memory Used: 128 MB [stdout] REPORT RequestId: fc00cfb1-09bb-41bb-b416-7b257d2e8ce3 Duration: 12.21 ms Billed Duration: 13 ms Memory Size: 128 MB Max Memory Used: 128 MB
Get all notified: log lines
source /app/work/env.sh aws logs filter-log-events --log-group-name /aws/lambda/notification-lambda --filter-pattern '"notified:"' --query 'events[*].message' --output text | tail -10
START RequestId: ab23eb75-fbaf-46c9-a4d2-4715553e7950 Version: $LATEST
[INFO] 2026-04-22T06:28:43.524Z ab23eb75-fbaf-46c9-a4d2-4715553e7950 notified: RotationComplete secret=<unknown> version=<unknown> payload={"version": "0", "id": "f9d68320-589b-4096-9df3-cab2a5b98790", "detail-type": "RotationComplete", "source": "rotation.pipeline", "account": "000000000000", "time": "2026-04-22T06:28:42Z", "region": "us-east-1", "resources": [], "detail": {"probe": true}}
END RequestId: ab23eb75-fbaf-46c9-a4d2-4715553e7950
REPORT RequestId: ab23eb75-fbaf-46c9-a4d2-4715553e7950 Duration: 11.02 ms Billed Duration: 12 ms Memory Size: 128 MB Max Memory Used: 128 MB
START RequestId: fc00cfb1-09bb-41bb-b416-7b257d2e8ce3 Version: $LATEST
[INFO] 2026-04-22T06:30:03.649Z fc00cfb1-09bb-41bb-b416-7b257d2e8ce3 notified: RotationComplete secret=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR version=2598e455-4ba0-49cc-a323-084ad28135ba payload={"version": "0", "id": "6597fe38-9fe0-4022-85fc-817afb15ab93", "detail-type": "RotationComplete", "source": "rotation.pipeline", "account": "000000000000", "time": "2026-04-22T06:30:03Z", "region": "us-east-1", "resources": ["arn:aws:states:us-east-1:000000000000:stateMachine:rotation-child", "arn:aws:states:us-east-1:000000000000:execution:rotation-child:87168ce6-e40b-43b2-8d87-728a6e5b1bbe"], "detail": {"secretId": "arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR", "versionId": "2598e455-4ba0-49cc-a323-084ad28135ba"}}
END RequestId: fc00cfb1-09bb-41bb-b416-7b257d2e8ce3
REPORT RequestId: fc00cfb1-09bb-41bb-b416-7b257d2e8ce3 Duration: 12.21 ms Billed Duration: 13 ms Memory Size: 128 MB Max Memory Used: 128 MB
[stdout]
START RequestId: ab23eb75-fbaf-46c9-a4d2-4715553e7950 Version: $LATEST
[INFO] 2026-04-22T06:28:43.524Z ab23eb75-fbaf-46c9-a4d2-4715553e7950 notified: RotationComplete secret=<unknown> version=<unknown> payload={"version": "0", "id": "f9d68320-589b-4096-9df3-cab2a5b98790", "detail-type": "RotationComplete", "source": "rotation.pipeline", "account": "000000000000", "time": "2026-04-22T06:28:42Z", "region": "us-east-1", "resources": [], "detail": {"probe": true}}
END RequestId: ab23eb75-fbaf-46c9-a4d2-4715553e7950
REPORT RequestId: ab23eb75-fbaf-46c9-a4d2-4715553e7950 Duration: 11.02 ms Billed Duration: 12 ms Memory Size: 128 MB Max Memory Used: 128 MB
START RequestId: fc00cfb1-09bb-41bb-b416-7b257d2e8ce3 Version: $LATEST
[INFO] 2026-04-22T06:30:03.649Z fc00cfb1-09bb-41bb-b416-7b257d2e8ce3 notified: RotationComplete secret=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR version=2598e455-4ba0-49cc-a323-084ad28135ba payload={"version": "0", "id": "6597fe38-9fe0-4022-85fc-817afb15ab93", "detail-type": "RotationComplete", "source": "rotation.pipeline", "account": "000000000000", "time": "2026-04-22T06:30:03Z", "region": "us-east-1", "resources": ["arn:aws:states:us-east-1:000000000000:stateMachine:rotation-child", "arn:aws:states:us-east-1:000000000000:execution:rotation-child:87168ce6-e40b-43b2-8d87-728a6e5b1bbe"], "detail": {"secretId": "arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR", "versionId": "2598e455-4ba0-49cc-a323-084ad28135ba"}}
END RequestId: fc00cfb1-09bb-41bb-b416-7b257d2e8ce3
REPORT RequestId: fc00cfb1-09bb-41bb-b416-7b257d2e8ce3 Duration: 12.21 ms Billed Duration: 13 ms Memory Size: 128 MB Max Memory Used: 128 MB{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InvokeChildSyncExecution",
"Effect": "Allow",
"Action": [
"states:StartExecution",
"states:DescribeExecution",
"states:StopExecution"
],
"Resource": [
"arn:aws:states:us-east-1:000000000000:stateMachine:rotation-child",
"arn:aws:states:us-east-1:000000000000:execution:rotation-child:*"
]
},
{
"Sid": "ManagedRuleForSyncIntegration",
"Effect": "Allow",
"Action": [
"events:PutRule",
"events:PutTargets",
"events:DescribeRule"
],
"Resource": "arn:aws:events:us-east-1:000000000000:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule"
}
]
}
{
"Comment": "Entry point for on-demand rotation. Synchronously invokes the child SM and waits for completion.",
"StartAt": "InvokeChild",
"States": {
"InvokeChild": {
"Type": "Task",
"Resource": "arn:aws:states:::states:startExecution.sync:2",
"Parameters": {
"StateMachineArn": "arn:aws:states:us-east-1:000000000000:stateMachine:rotation-child",
"Input": {
"AWS_STEP_FUNCTIONS_STARTED_BY_EXECUTION_ID.$": "$$.Execution.Id"
}
},
"End": true
}
}
}
Create parent orchestrator SM + role
source /app/work/env.sh
ORCH_ROLE_ARN=$(aws iam create-role \
--role-name rotation-orchestrator-role \
--assume-role-policy-document file:///app/work/policies/sfn-trust.json \
--query 'Role.Arn' --output text)
echo "ORCH_ROLE_ARN=$ORCH_ROLE_ARN" | tee -a /app/work/state.env
aws iam put-role-policy \
--role-name rotation-orchestrator-role \
--policy-name rotation-orchestrator-inline \
--policy-document file:///app/work/policies/rotation-orchestrator-role-policy.json
for i in 1 2 3 4 5; do
PARENT_SM_ARN=$(aws stepfunctions create-state-machine \
--name rotation-orchestrator \
--type STANDARD \
--role-arn "$ORCH_ROLE_ARN" \
--definition file:///app/work/asl/rotation-orchestrator.json \
--query 'stateMachineArn' --output text 2>/dev/null) && break
sleep 2
done
echo "PARENT_SM_ARN=$PARENT_SM_ARN" | tee -a /app/work/state.envORCH_ROLE_ARN=arn:aws:iam::000000000000:role/rotation-orchestrator-role PARENT_SM_ARN=arn:aws:states:us-east-1:000000000000:stateMachine:rotation-orchestrator [stdout] ORCH_ROLE_ARN=arn:aws:iam::000000000000:role/rotation-orchestrator-role PARENT_SM_ARN=arn:aws:states:us-east-1:000000000000:stateMachine:rotation-orchestrator
Start parent orchestrator; wait for SUCCEEDED
source /app/work/env.sh
source /app/work/state.env
CURRENT_BEFORE=$(aws secretsmanager get-secret-value --secret-id app/db/password --query '[VersionId,SecretString]' --output json)
echo "BEFORE: $CURRENT_BEFORE"
T0=$(date +%s)
EX=$(aws stepfunctions start-execution \
--state-machine-arn "$PARENT_SM_ARN" \
--query 'executionArn' --output text)
echo "started $EX"
for i in $(seq 1 90); do
st=$(aws stepfunctions describe-execution --execution-arn "$EX" --query 'status' --output text)
[ "$st" != "RUNNING" ] && break
sleep 1
done
T1=$(date +%s)
elapsed=$((T1-T0))
echo "parent status: $st (elapsed ${elapsed}s)"
aws stepfunctions describe-execution --execution-arn "$EX" --query '{status:status,error:error,cause:cause}'BEFORE: [
"2598e455-4ba0-49cc-a323-084ad28135ba",
"dx6G_ih959M3cIpHdRaJT1QONN1Zq7pm"
]
started arn:aws:states:us-east-1:000000000000:execution:rotation-orchestrator:5a4af4c8-4657-436c-a8fb-3720383f768c
parent status: SUCCEEDED (elapsed 9s)
{
"status": "SUCCEEDED",
"error": null,
"cause": null
}
[stdout]
BEFORE: [
"2598e455-4ba0-49cc-a323-084ad28135ba",
"dx6G_ih959M3cIpHdRaJT1QONN1Zq7pm"
]
started arn:aws:states:us-east-1:000000000000:execution:rotation-orchestrator:5a4af4c8-4657-436c-a8fb-3720383f768c
parent status: SUCCEEDED (elapsed 9s)
{
"status": "SUCCEEDED",
"error": null,
"cause": null
}Confirm secret rotated and notifier logged
source /app/work/env.sh echo "=== secret state after parent run ===" aws secretsmanager get-secret-value --secret-id app/db/password --query '[VersionId,SecretString]' --output json aws secretsmanager describe-secret --secret-id app/db/password --query 'VersionIdsToStages' echo "=== last notifier log entry ===" sleep 2 aws logs filter-log-events --log-group-name /aws/lambda/notification-lambda --filter-pattern '"notified:"' --query 'events[-1].message' --output text echo "=== all 'notified:' lines (most recent last) ===" aws logs filter-log-events --log-group-name /aws/lambda/notification-lambda --filter-pattern '"notified:"' --query 'events[*].message' --output text | grep -o 'notified:[^"]*' | tail -5
=== secret state after parent run ===
[
"a7816755-fbf9-4556-94f8-bfd87cde5547",
"yinGmfuNMeR1I7Y5fdXLk6-sxH-QYboQ"
]
{
"2598e455-4ba0-49cc-a323-084ad28135ba": [
"AWSPREVIOUS"
],
"a7816755-fbf9-4556-94f8-bfd87cde5547": [
"AWSPENDING",
"AWSCURRENT"
]
}
=== last notifier log entry ===
REPORT RequestId: 428e00e1-6148-4181-955c-2c04f72e3316 Duration: 12.30 ms Billed Duration: 13 ms Memory Size: 128 MB Max Memory Used: 128 MB
=== all 'notified:' lines (most recent last) ===
notified: RotationComplete secret=<unknown> version=<unknown> payload={
notified: RotationComplete secret=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR version=2598e455-4ba0-49cc-a323-084ad28135ba payload={
notified: RotationComplete secret=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR version=a7816755-fbf9-4556-94f8-bfd87cde5547 payload={
[stdout]
=== secret state after parent run ===
[
"a7816755-fbf9-4556-94f8-bfd87cde5547",
"yinGmfuNMeR1I7Y5fdXLk6-sxH-QYboQ"
]
{
"2598e455-4ba0-49cc-a323-084ad28135ba": [
"AWSPREVIOUS"
],
"a7816755-fbf9-4556-94f8-bfd87cde5547": [
"AWSPENDING",
"AWSCURRENT"
]
}
=== last notifier log entry ===
REPORT RequestId: 428e00e1-6148-4181-955c-2c04f72e3316 Duration: 12.30 ms Billed Duration: 13 ms Memory Size: 128 MB Max Memory Used: 128 MB
=== all 'notified:' lines (most recent last) ===
notified: RotationComplete secret=<unknown> version=<unknown> payload={
notified: RotationComplete secret=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR version=2598e455-4ba0-49cc-a323-084ad28135ba payload={
notified: RotationComplete secret=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR version=a7816755-fbf9-4556-94f8-bfd87cde5547 payload={/app/work/policies/rotator-role-policy.json
{
"Sid": "KmsForSecret",
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:us-east-1:000000000000:key/*",
"Condition": {
"StringEquals": {
"kms:ViaService": "secretsmanager.us-east-1.amazonaws.com"
}
}
},
{
"Sid": "GetRandomPassword",
"Effect": "Allow",
"Action": "secretsmanager:GetRandomPassword",
"Resource": "*"
} {
"Sid": "KmsForSecret",
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:us-east-1:000000000000:key/*",
"Condition": {
"StringEquals": {
"kms:ViaService": "secretsmanager.us-east-1.amazonaws.com"
}
}
}Reapply rotator policy and do one more full parent run
source /app/work/env.sh aws iam put-role-policy \ --role-name secrets-rotator-role \ --policy-name secrets-rotator-inline \ --policy-document file:///app/work/policies/rotator-role-policy.json echo "updated" # quick one-more rotation via parent to confirm nothing regressed source /app/work/state.env BEFORE=$(aws secretsmanager get-secret-value --secret-id app/db/password --query 'SecretString' --output text) EX=$(aws stepfunctions start-execution --state-machine-arn "$PARENT_SM_ARN" --query 'executionArn' --output text) T0=$(date +%s) for i in $(seq 1 90); do st=$(aws stepfunctions describe-execution --execution-arn "$EX" --query 'status' --output text) [ "$st" != "RUNNING" ] && break sleep 1 done T1=$(date +%s) AFTER=$(aws secretsmanager get-secret-value --secret-id app/db/password --query 'SecretString' --output text) echo "parent=$st elapsed=$((T1-T0))s string-changed=$([ "$BEFORE" != "$AFTER" ] && echo yes || echo no)" sleep 2 aws logs filter-log-events --log-group-name /aws/lambda/notification-lambda --filter-pattern '"notified:"' --query 'events[*].message' --output text | grep -o 'notified:[^"]*' | tail -3
updated
parent=SUCCEEDED elapsed=9s string-changed=yes
notified: RotationComplete secret=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR version=2598e455-4ba0-49cc-a323-084ad28135ba payload={
notified: RotationComplete secret=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR version=a7816755-fbf9-4556-94f8-bfd87cde5547 payload={
notified: RotationComplete secret=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR version=055846fd-7835-476e-9b29-b9ec8757ae31 payload={
[stdout]
updated
parent=SUCCEEDED elapsed=9s string-changed=yes
notified: RotationComplete secret=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR version=2598e455-4ba0-49cc-a323-084ad28135ba payload={
notified: RotationComplete secret=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR version=a7816755-fbf9-4556-94f8-bfd87cde5547 payload={
notified: RotationComplete secret=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-KcqMgR version=055846fd-7835-476e-9b29-b9ec8757ae31 payload={No file changes, the agent left the workspace untouched.
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-8.4.1, pluggy-1.6.0 -- /root/.cache/uv/archive-v0/PxMOhwi_rFCClX-0XaydZ/bin/python
cachedir: .pytest_cache
rootdir: /tests
plugins: json-ctrf-0.3.5
collecting ... collected 15 items
../tests/test_state.py::test_aws_cli_binary_is_real PASSED [ 6%]
../tests/test_state.py::test_localstack_reachable PASSED [ 13%]
../tests/test_state.py::test_secret_exists_and_uses_customer_cmk PASSED [ 20%]
../tests/test_state.py::test_rotation_lambda_exists PASSED [ 26%]
../tests/test_state.py::test_notification_lambda_exists PASSED [ 33%]
../tests/test_state.py::test_parent_and_child_state_machines_exist PASSED [ 40%]
../tests/test_state.py::test_event_bus_and_rule_exist PASSED [ 46%]
../tests/test_state.py::test_rule_target_points_at_notification_lambda PASSED [ 53%]
../tests/test_state.py::test_rotator_role_grants_kms_actions PASSED [ 60%]
../tests/test_state.py::test_kms_key_policy_grants_rotator_role PASSED [ 66%]
../tests/test_state.py::test_rotator_lambda_allows_secretsmanager_invoke PASSED [ 73%]
../tests/test_state.py::test_parent_role_grants_sync2_managed_rule_perms PASSED [ 80%]
../tests/test_state.py::test_child_role_can_rotate_and_publish PASSED [ 86%]
../tests/test_state.py::test_notification_lambda_allows_eventbridge_invoke PASSED [ 93%]
../tests/test_state.py::test_end_to_end_parent_execution_rotates_and_notifies PASSED [100%]
=============================== warnings summary ===============================
test_state.py: 37 warnings
/root/.cache/uv/archive-v0/PxMOhwi_rFCClX-0XaydZ/lib/python3.12/site-packages/botocore/auth.py:424: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
datetime_now = datetime.datetime.utcnow()
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
==================================== PASSES ====================================
=========================== short test summary info ============================
PASSED ../tests/test_state.py::test_aws_cli_binary_is_real
PASSED ../tests/test_state.py::test_localstack_reachable
PASSED ../tests/test_state.py::test_secret_exists_and_uses_customer_cmk
PASSED ../tests/test_state.py::test_rotation_lambda_exists
PASSED ../tests/test_state.py::test_notification_lambda_exists
PASSED ../tests/test_state.py::test_parent_and_child_state_machines_exist
PASSED ../tests/test_state.py::test_event_bus_and_rule_exist
PASSED ../tests/test_state.py::test_rule_target_points_at_notification_lambda
PASSED ../tests/test_state.py::test_rotator_role_grants_kms_actions
PASSED ../tests/test_state.py::test_kms_key_policy_grants_rotator_role
PASSED ../tests/test_state.py::test_rotator_lambda_allows_secretsmanager_invoke
PASSED ../tests/test_state.py::test_parent_role_grants_sync2_managed_rule_perms
PASSED ../tests/test_state.py::test_child_role_can_rotate_and_publish
PASSED ../tests/test_state.py::test_notification_lambda_allows_eventbridge_invoke
PASSED ../tests/test_state.py::test_end_to_end_parent_execution_rotates_and_notifies
======================= 15 passed, 37 warnings in 15.63s =======================Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_df59a96e93344185. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_df59a96e93344185 · verifier authoritative; classifier explanatory.