tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.Inspect working directory and tool versions
ls -la /app && echo "---" && aws --version && python3 --version
total 8 drwxr-xr-x 2 root root 4096 Apr 21 20:05 . drwxr-xr-x 1 root root 4096 Apr 23 05:52 .. --- aws-cli/2.34.33 Python/3.14.4 Linux/6.10.14-linuxkit exe/aarch64.ubuntu.24 Python 3.12.3 [stdout] total 8 drwxr-xr-x 2 root root 4096 Apr 21 20:05 . drwxr-xr-x 1 root root 4096 Apr 23 05:52 .. --- aws-cli/2.34.33 Python/3.14.4 Linux/6.10.14-linuxkit exe/aarch64.ubuntu.24 Python 3.12.3
Check LocalStack health status
curl -s http://localstack:4566/_localstack/health | jq . | head -50
{
"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"
}Verify environment and tooling
echo "AK=$AWS_ACCESS_KEY_ID SK=$AWS_SECRET_ACCESS_KEY RG=$AWS_DEFAULT_REGION"; which zip; python3 -c "import boto3; print(boto3.__version__)"
AK=test SK=test RG=us-east-1 /usr/bin/zip 1.35.99 [stdout] AK=test SK=test RG=us-east-1 /usr/bin/zip 1.35.99
Scaffold build directories
mkdir -p /app/build/rotator /app/build/notifier /app/iam /app/sm && ls /app
build iam sm [stdout] build iam sm
import os
import secrets
import string
import uuid
import boto3
ENDPOINT = os.environ.get("AWS_ENDPOINT_URL", "http://localstack:4566")
SECRET_ID = os.environ["SECRET_ID"]
_sm = boto3.client("secretsmanager", endpoint_url=ENDPOINT)
def _generate_password(length: int = 32) -> str:
alphabet = string.ascii_letters + string.digits + "-_"
return "".join(secrets.choice(alphabet) for _ in range(length))
def lambda_handler(event, context):
new_password = _generate_password()
response = _sm.put_secret_value(
SecretId=SECRET_ID,
ClientRequestToken=str(uuid.uuid4()),
SecretString=new_password,
)
return {
"secretId": SECRET_ID,
"versionId": response["VersionId"],
"stages": response.get("VersionStages", []),
}
import json
def lambda_handler(event, context):
detail = event.get("detail", {}) if isinstance(event, dict) else {}
detail_type = event.get("detail-type") if isinstance(event, dict) else None
print(f"notified: detail-type={detail_type} detail={json.dumps(detail, sort_keys=True)}")
return {"ok": True}
Package both lambda zip files
cd /app/build/rotator && zip -q rotator.zip index.py && cd /app/build/notifier && zip -q notifier.zip index.py && ls -la /app/build/rotator /app/build/notifier
/app/build/notifier: total 16 drwxr-xr-x 2 root root 4096 Apr 23 05:54 . drwxr-xr-x 4 root root 4096 Apr 23 05:54 .. -rw-r--r-- 1 root root 320 Apr 23 05:54 index.py -rw-r--r-- 1 root root 349 Apr 23 05:54 notifier.zip /app/build/rotator: total 16 drwxr-xr-x 2 root root 4096 Apr 23 05:54 . drwxr-xr-x 4 root root 4096 Apr 23 05:54 .. -rw-r--r-- 1 root root 790 Apr 23 05:54 index.py -rw-r--r-- 1 root root 590 Apr 23 05:54 rotator.zip [stdout] /app/build/notifier: total 16 drwxr-xr-x 2 root root 4096 Apr 23 05:54 . drwxr-xr-x 4 root root 4096 Apr 23 05:54 .. -rw-r--r-- 1 root root 320 Apr 23 05:54 index.py -rw-r--r-- 1 root root 349 Apr 23 05:54 notifier.zip /app/build/rotator: total 16 drwxr-xr-x 2 root root 4096 Apr 23 05:54 . drwxr-xr-x 4 root root 4096 Apr 23 05:54 .. -rw-r--r-- 1 root root 790 Apr 23 05:54 index.py -rw-r--r-- 1 root root 590 Apr 23 05:54 rotator.zip
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}
]
}
{
"query": "select:TodoWrite",
"max_results": 1
}
, output,
{"type": "tool_reference", "tool_name": "TodoWrite"}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "states.amazonaws.com"},
"Action": "sts:AssumeRole"
}
]
}
Create CMK with key policy and alias
export AWS_ENDPOINT_URL=http://localstack:4566
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
echo "ACCOUNT_ID=$ACCOUNT_ID"
cat > /app/iam/kms-key-policy.json <<EOF
{
"Version": "2012-10-17",
"Id": "app-rotation-key-policy",
"Statement": [
{
"Sid": "EnableRootAccountAdmin",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::${ACCOUNT_ID}:root"},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "AllowSecretsManagerUseOfKey",
"Effect": "Allow",
"Principal": {"Service": "secretsmanager.amazonaws.com"},
"Action": [
"kms:Decrypt",
"kms:Encrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey",
"kms:CreateGrant"
],
"Resource": "*"
}
]
}
EOF
KEY_ID=$(aws kms create-key \
--description "CMK for secrets rotation pipeline" \
--key-usage ENCRYPT_DECRYPT \
--key-spec SYMMETRIC_DEFAULT \
--policy file:///app/iam/kms-key-policy.json \
--query 'KeyMetadata.KeyId' --output text)
echo "KEY_ID=$KEY_ID"
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"
# persist for next steps
echo "$ACCOUNT_ID" > /tmp/acct
echo "$KEY_ARN" > /tmp/keyarn
echo "$KEY_ID" > /tmp/keyidACCOUNT_ID=000000000000 KEY_ID=d7b06a24-40e6-49dd-82cf-45c7ffafa707 KEY_ARN=arn:aws:kms:us-east-1:000000000000:key/d7b06a24-40e6-49dd-82cf-45c7ffafa707 [stdout] ACCOUNT_ID=000000000000 KEY_ID=d7b06a24-40e6-49dd-82cf-45c7ffafa707 KEY_ARN=arn:aws:kms:us-east-1:000000000000:key/d7b06a24-40e6-49dd-82cf-45c7ffafa707
Create secret encrypted with CMK
export AWS_ENDPOINT_URL=http://localstack:4566
KEY_ID=$(cat /tmp/keyid)
SECRET_ARN=$(aws secretsmanager create-secret \
--name app/db/password \
--description "App DB password, rotated via step functions" \
--kms-key-id "$KEY_ID" \
--secret-string "initial-placeholder-$(date +%s)" \
--query ARN --output text)
echo "SECRET_ARN=$SECRET_ARN"
echo "$SECRET_ARN" > /tmp/secretarn
aws secretsmanager describe-secret --secret-id app/db/password --query '{Name:Name,KmsKeyId:KmsKeyId}' SECRET_ARN=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-nniOzG
{
"Name": "app/db/password",
"KmsKeyId": "d7b06a24-40e6-49dd-82cf-45c7ffafa707"
}
[stdout]
SECRET_ARN=arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-nniOzG
{
"Name": "app/db/password",
"KmsKeyId": "d7b06a24-40e6-49dd-82cf-45c7ffafa707"
}Create rotator role + lambda
export AWS_ENDPOINT_URL=http://localstack:4566
ACCOUNT_ID=$(cat /tmp/acct)
KEY_ARN=$(cat /tmp/keyarn)
SECRET_ARN=$(cat /tmp/secretarn)
cat > /app/iam/rotator-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Logs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:us-east-1:${ACCOUNT_ID}:log-group:/aws/lambda/secrets-rotator:*"
},
{
"Sid": "RotateSecret",
"Effect": "Allow",
"Action": [
"secretsmanager:DescribeSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:PutSecretValue",
"secretsmanager:UpdateSecretVersionStage"
],
"Resource": "${SECRET_ARN}"
},
{
"Sid": "UseCMK",
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:GenerateDataKey"
],
"Resource": "${KEY_ARN}"
}
]
}
EOF
aws iam create-role \
--role-name secrets-rotator-role \
--assume-role-policy-document file:///app/iam/lambda-trust.json \
--query 'Role.Arn' --output text
aws iam put-role-policy \
--role-name secrets-rotator-role \
--policy-name secrets-rotator-policy \
--policy-document file:///app/iam/rotator-policy.json
ROTATOR_ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/secrets-rotator-role"
echo "ROTATOR_ROLE_ARN=$ROTATOR_ROLE_ARN"
# create log group explicitly so scoped policy matches cleanly
aws logs create-log-group --log-group-name /aws/lambda/secrets-rotator 2>/dev/null || true
ROTATOR_ARN=$(aws lambda create-function \
--function-name secrets-rotator \
--runtime python3.11 \
--role "$ROTATOR_ROLE_ARN" \
--handler index.lambda_handler \
--zip-file fileb:///app/build/rotator/rotator.zip \
--timeout 30 \
--environment "Variables={SECRET_ID=app/db/password,AWS_ENDPOINT_URL=http://localstack:4566}" \
--query 'FunctionArn' --output text)
echo "ROTATOR_ARN=$ROTATOR_ARN"
echo "$ROTATOR_ARN" > /tmp/rotator_arn
# Wait for active
for i in 1 2 3 4 5 6 7 8 9 10; do
STATE=$(aws lambda get-function --function-name secrets-rotator --query 'Configuration.State' --output text 2>/dev/null || echo "Pending")
echo "state=$STATE"
[ "$STATE" = "Active" ] && break
sleep 2
donearn:aws:iam::000000000000:role/secrets-rotator-role ROTATOR_ROLE_ARN=arn:aws:iam::000000000000:role/secrets-rotator-role ROTATOR_ARN=arn:aws:lambda:us-east-1:000000000000:function:secrets-rotator state=Pending state=Active [stdout] arn:aws:iam::000000000000:role/secrets-rotator-role ROTATOR_ROLE_ARN=arn:aws:iam::000000000000:role/secrets-rotator-role ROTATOR_ARN=arn:aws:lambda:us-east-1:000000000000:function:secrets-rotator state=Pending state=Active
Smoke-test rotator lambda
export AWS_ENDPOINT_URL=http://localstack:4566
# smoke test the rotator
aws lambda invoke --function-name secrets-rotator --payload '{}' --cli-binary-format raw-in-base64-out /tmp/rotator-out.json >/dev/null
cat /tmp/rotator-out.json; echo
aws secretsmanager get-secret-value --secret-id app/db/password --query '{VersionId:VersionId,Stages:VersionStages}'{"secretId": "app/db/password", "versionId": "90a890c9-c160-483f-befb-e2daa4ac498c", "stages": ["AWSCURRENT"]}
{
"VersionId": "90a890c9-c160-483f-befb-e2daa4ac498c",
"Stages": [
"AWSCURRENT"
]
}
[stdout]
{"secretId": "app/db/password", "versionId": "90a890c9-c160-483f-befb-e2daa4ac498c", "stages": ["AWSCURRENT"]}
{
"VersionId": "90a890c9-c160-483f-befb-e2daa4ac498c",
"Stages": [
"AWSCURRENT"
]
}Create notification lambda + role
export AWS_ENDPOINT_URL=http://localstack:4566
ACCOUNT_ID=$(cat /tmp/acct)
cat > /app/iam/notifier-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Logs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:us-east-1:${ACCOUNT_ID}:log-group:/aws/lambda/notification-lambda:*"
}
]
}
EOF
aws iam create-role \
--role-name notification-lambda-role \
--assume-role-policy-document file:///app/iam/lambda-trust.json \
--query 'Role.Arn' --output text
aws iam put-role-policy \
--role-name notification-lambda-role \
--policy-name notification-lambda-policy \
--policy-document file:///app/iam/notifier-policy.json
NOTIF_ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/notification-lambda-role"
aws logs create-log-group --log-group-name /aws/lambda/notification-lambda 2>/dev/null || true
NOTIF_ARN=$(aws lambda create-function \
--function-name notification-lambda \
--runtime python3.11 \
--role "$NOTIF_ROLE_ARN" \
--handler index.lambda_handler \
--zip-file fileb:///app/build/notifier/notifier.zip \
--timeout 10 \
--query 'FunctionArn' --output text)
echo "NOTIF_ARN=$NOTIF_ARN"
echo "$NOTIF_ARN" > /tmp/notif_arn
for i in 1 2 3 4 5 6 7 8 9 10; do
STATE=$(aws lambda get-function --function-name notification-lambda --query 'Configuration.State' --output text 2>/dev/null || echo "Pending")
echo "state=$STATE"
[ "$STATE" = "Active" ] && break
sleep 2
donearn:aws:iam::000000000000:role/notification-lambda-role NOTIF_ARN=arn:aws:lambda:us-east-1:000000000000:function:notification-lambda state=Active [stdout] arn:aws:iam::000000000000:role/notification-lambda-role NOTIF_ARN=arn:aws:lambda:us-east-1:000000000000:function:notification-lambda state=Active
Create EB bus, rule, target, lambda invoke permission
export AWS_ENDPOINT_URL=http://localstack:4566
ACCOUNT_ID=$(cat /tmp/acct)
NOTIF_ARN=$(cat /tmp/notif_arn)
BUS_ARN=$(aws events create-event-bus --name rotation-events --query 'EventBusArn' --output text)
echo "BUS_ARN=$BUS_ARN"
echo "$BUS_ARN" > /tmp/bus_arn
cat > /app/sm/rule-pattern.json <<'EOF'
{
"source": ["rotation.pipeline"],
"detail-type": ["RotationComplete"]
}
EOF
RULE_ARN=$(aws events put-rule \
--name on-rotation-success \
--event-bus-name rotation-events \
--event-pattern file:///app/sm/rule-pattern.json \
--state ENABLED \
--query 'RuleArn' --output text)
echo "RULE_ARN=$RULE_ARN"
# lambda resource-based policy: let events on this rule invoke notification-lambda
aws lambda add-permission \
--function-name notification-lambda \
--statement-id allow-eb-on-rotation-success \
--action lambda:InvokeFunction \
--principal events.amazonaws.com \
--source-arn "$RULE_ARN" \
--query 'Statement' --output text >/dev/null
aws events put-targets \
--rule on-rotation-success \
--event-bus-name rotation-events \
--targets "Id=notifier,Arn=${NOTIF_ARN}"
aws events list-targets-by-rule --rule on-rotation-success --event-bus-name rotation-eventsBUS_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": []
}
{
"Targets": [
{
"Id": "notifier",
"Arn": "arn:aws:lambda:us-east-1:000000000000:function:notification-lambda"
}
]
}
[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": []
}
{
"Targets": [
{
"Id": "notifier",
"Arn": "arn:aws:lambda:us-east-1:000000000000:function:notification-lambda"
}
]
}Create child SM role and state machine
export AWS_ENDPOINT_URL=http://localstack:4566
ACCOUNT_ID=$(cat /tmp/acct)
ROTATOR_ARN=$(cat /tmp/rotator_arn)
BUS_ARN=$(cat /tmp/bus_arn)
cat > /app/iam/child-sm-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InvokeRotator",
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": "${ROTATOR_ARN}"
},
{
"Sid": "PutRotationEvent",
"Effect": "Allow",
"Action": "events:PutEvents",
"Resource": "${BUS_ARN}"
}
]
}
EOF
aws iam create-role \
--role-name rotation-child-role \
--assume-role-policy-document file:///app/iam/states-trust.json \
--query 'Role.Arn' --output text
aws iam put-role-policy \
--role-name rotation-child-role \
--policy-name rotation-child-policy \
--policy-document file:///app/iam/child-sm-policy.json
CHILD_ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/rotation-child-role"
cat > /app/sm/child.json <<EOF
{
"Comment": "Rotate secret then publish RotationComplete event",
"StartAt": "Rotate",
"States": {
"Rotate": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "${ROTATOR_ARN}",
"Payload": {}
},
"ResultPath": "\$.rotation",
"Next": "Notify"
},
"Notify": {
"Type": "Task",
"Resource": "arn:aws:states:::events:putEvents",
"Parameters": {
"Entries": [
{
"Source": "rotation.pipeline",
"DetailType": "RotationComplete",
"EventBusName": "rotation-events",
"Detail": {
"secretId": "app/db/password",
"versionId.\$": "\$.rotation.Payload.versionId"
}
}
]
},
"End": true
}
}
}
EOF
CHILD_ARN=$(aws stepfunctions create-state-machine \
--name rotation-child \
--definition file:///app/sm/child.json \
--role-arn "$CHILD_ROLE_ARN" \
--type STANDARD \
--query 'stateMachineArn' --output text)
echo "CHILD_ARN=$CHILD_ARN"
echo "$CHILD_ARN" > /tmp/child_arnarn:aws:iam::000000000000:role/rotation-child-role CHILD_ARN=arn:aws:states:us-east-1:000000000000:stateMachine:rotation-child [stdout] arn:aws:iam::000000000000:role/rotation-child-role CHILD_ARN=arn:aws:states:us-east-1:000000000000:stateMachine:rotation-child
Create parent SM role and state machine
export AWS_ENDPOINT_URL=http://localstack:4566
ACCOUNT_ID=$(cat /tmp/acct)
CHILD_ARN=$(cat /tmp/child_arn)
cat > /app/iam/parent-sm-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "StartChild",
"Effect": "Allow",
"Action": "states:StartExecution",
"Resource": "${CHILD_ARN}"
},
{
"Sid": "ControlChildExecutions",
"Effect": "Allow",
"Action": [
"states:DescribeExecution",
"states:StopExecution"
],
"Resource": "arn:aws:states:us-east-1:${ACCOUNT_ID}:execution:rotation-child:*"
},
{
"Sid": "ManagedRuleForSyncHandoff",
"Effect": "Allow",
"Action": [
"events:PutRule",
"events:PutTargets",
"events:DescribeRule"
],
"Resource": "arn:aws:events:us-east-1:${ACCOUNT_ID}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule*"
}
]
}
EOF
aws iam create-role \
--role-name rotation-orchestrator-role \
--assume-role-policy-document file:///app/iam/states-trust.json \
--query 'Role.Arn' --output text
aws iam put-role-policy \
--role-name rotation-orchestrator-role \
--policy-name rotation-orchestrator-policy \
--policy-document file:///app/iam/parent-sm-policy.json
PARENT_ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/rotation-orchestrator-role"
cat > /app/sm/parent.json <<EOF
{
"Comment": "Parent orchestrator -- hands off to rotation-child and waits",
"StartAt": "InvokeChild",
"States": {
"InvokeChild": {
"Type": "Task",
"Resource": "arn:aws:states:::states:startExecution.sync:2",
"Parameters": {
"StateMachineArn": "${CHILD_ARN}"
},
"End": true
}
}
}
EOF
PARENT_ARN=$(aws stepfunctions create-state-machine \
--name rotation-orchestrator \
--definition file:///app/sm/parent.json \
--role-arn "$PARENT_ROLE_ARN" \
--type STANDARD \
--query 'stateMachineArn' --output text)
echo "PARENT_ARN=$PARENT_ARN"
echo "$PARENT_ARN" > /tmp/parent_arnarn:aws:iam::000000000000:role/rotation-orchestrator-role PARENT_ARN=arn:aws:states:us-east-1:000000000000:stateMachine:rotation-orchestrator [stdout] arn:aws:iam::000000000000:role/rotation-orchestrator-role PARENT_ARN=arn:aws:states:us-east-1:000000000000:stateMachine:rotation-orchestrator
Start parent execution and poll for completion
export AWS_ENDPOINT_URL=http://localstack:4566
PARENT_ARN=$(cat /tmp/parent_arn)
# capture pre-state
BEFORE=$(aws secretsmanager get-secret-value --secret-id app/db/password --query '{v:VersionId,s:SecretString}')
echo "BEFORE: $BEFORE"
START_TS=$(date +%s)
EXEC_ARN=$(aws stepfunctions start-execution --state-machine-arn "$PARENT_ARN" --query 'executionArn' --output text)
echo "EXEC_ARN=$EXEC_ARN"
STATUS=RUNNING
while [ $(( $(date +%s) - START_TS )) -lt 90 ]; do
STATUS=$(aws stepfunctions describe-execution --execution-arn "$EXEC_ARN" --query 'status' --output text)
echo "t=$(( $(date +%s) - START_TS ))s status=$STATUS"
[ "$STATUS" != "RUNNING" ] && break
sleep 2
done
echo "FINAL_STATUS=$STATUS"
ELAPSED=$(( $(date +%s) - START_TS ))
echo "ELAPSED=${ELAPSED}s"BEFORE: {
"v": "90a890c9-c160-483f-befb-e2daa4ac498c",
"s": "vQx758b7jX0-bn2_xdMYxw1r0I4QCsCN"
}
EXEC_ARN=arn:aws:states:us-east-1:000000000000:execution:rotation-orchestrator:5e834881-bb96-4c41-b9ff-e9d337b8e9b8
t=2s status=RUNNING
t=4s status=SUCCEEDED
FINAL_STATUS=SUCCEEDED
ELAPSED=4s
[stdout]
BEFORE: {
"v": "90a890c9-c160-483f-befb-e2daa4ac498c",
"s": "vQx758b7jX0-bn2_xdMYxw1r0I4QCsCN"
}
EXEC_ARN=arn:aws:states:us-east-1:000000000000:execution:rotation-orchestrator:5e834881-bb96-4c41-b9ff-e9d337b8e9b8
t=2s status=RUNNING
t=4s status=SUCCEEDED
FINAL_STATUS=SUCCEEDED
ELAPSED=4sVerify AWSCURRENT moved and AWSPREVIOUS exists
export AWS_ENDPOINT_URL=http://localstack:4566
echo "=== secret state ==="
aws secretsmanager get-secret-value --secret-id app/db/password --query '{v:VersionId,s:SecretString}'
echo
echo "=== full version map ==="
aws secretsmanager list-secret-version-ids --secret-id app/db/password --include-deprecated=== secret state ===
{
"v": "a0d58baf-f481-481b-8993-07aa4c0102b2",
"s": "5d1IweaKIEQh3hgk9uuZ9XZl6WZUJTT7"
}
=== full version map ===
{
"Versions": [
{
"VersionId": "a0d58baf-f481-481b-8993-07aa4c0102b2",
"VersionStages": [
"AWSCURRENT"
],
"LastAccessedDate": "2026-04-23T00:00:00+00:00",
"CreatedDate": "2026-04-23T05:57:38+00:00"
},
{
"VersionId": "90a890c9-c160-483f-befb-e2daa4ac498c",
"VersionStages": [
"AWSPREVIOUS"
],
"LastAccessedDate": "2026-04-23T00:00:00+00:00",
"CreatedDate": "2026-04-23T05:56:03+00:00"
},
{
"VersionId": "c22106b1-2d8d-48c4-a635-c1b8a7245e43",
"CreatedDate": "2026-04-23T05:55:34+00:00"
}
],
"ARN": "arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-nniOzG",
"Name": "app/db/password"
}
[stdout]
=== secret state ===
{
"v": "a0d58baf-f481-481b-8993-07aa4c0102b2",
"s": "5d1IweaKIEQh3hgk9uuZ9XZl6WZUJTT7"
}
=== full version map ===
{
"Versions": [
{
"VersionId": "a0d58baf-f481-481b-8993-07aa4c0102b2",
"VersionStages": [
"AWSCURRENT"
],
"LastAccessedDate": "2026-04-23T00:00:00+00:00",
"CreatedDate": "2026-04-23T05:57:38+00:00"
},
{
"VersionId": "90a890c9-c160-483f-befb-e2daa4ac498c",
"VersionStages": [
"AWSPREVIOUS"
],
"LastAccessedDate": "2026-04-23T00:00:00+00:00",
"CreatedDate": "2026-04-23T05:56:03+00:00"
},
{
"VersionId": "c22106b1-2d8d-48c4-a635-c1b8a7245e43",
"CreatedDate": "2026-04-23T05:55:34+00:00"
}
],
"ARN": "arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-nniOzG",
"Name": "app/db/password"
}Tail notifier log group for notified: line
export AWS_ENDPOINT_URL=http://localstack:4566
# give EB a moment to fan out to the lambda
for i in 1 2 3 4 5 6 7 8 9 10; do
HIT=$(aws logs filter-log-events \
--log-group-name /aws/lambda/notification-lambda \
--filter-pattern '"notified:"' \
--query 'events[].message' --output text 2>/dev/null)
if [ -n "$HIT" ]; then
echo "=== notifier log match ==="
echo "$HIT"
break
fi
echo "waiting for notifier log... (${i})"
sleep 1
done=== notifier log match ===
START RequestId: 5d3ea8c0-6403-46c7-90bd-1ab10d83a4d3 Version: $LATEST
notified: detail-type=RotationComplete detail={"secretId": "app/db/password", "versionId": "a0d58baf-f481-481b-8993-07aa4c0102b2"}
END RequestId: 5d3ea8c0-6403-46c7-90bd-1ab10d83a4d3
REPORT RequestId: 5d3ea8c0-6403-46c7-90bd-1ab10d83a4d3 Duration: 9.27 ms Billed Duration: 10 ms Memory Size: 128 MB Max Memory Used: 128 MB
[stdout]
=== notifier log match ===
START RequestId: 5d3ea8c0-6403-46c7-90bd-1ab10d83a4d3 Version: $LATEST
notified: detail-type=RotationComplete detail={"secretId": "app/db/password", "versionId": "a0d58baf-f481-481b-8993-07aa4c0102b2"}
END RequestId: 5d3ea8c0-6403-46c7-90bd-1ab10d83a4d3
REPORT RequestId: 5d3ea8c0-6403-46c7-90bd-1ab10d83a4d3 Duration: 9.27 ms Billed Duration: 10 ms Memory Size: 128 MB Max Memory Used: 128 MBConfirm two consecutive rotations produce different passwords
export AWS_ENDPOINT_URL=http://localstack:4566 PARENT_ARN=$(cat /tmp/parent_arn) S1=$(aws secretsmanager get-secret-value --secret-id app/db/password --query SecretString --output text) EXEC=$(aws stepfunctions start-execution --state-machine-arn "$PARENT_ARN" --query executionArn --output text) for i in 1 2 3 4 5 6 7 8 9 10; do ST=$(aws stepfunctions describe-execution --execution-arn "$EXEC" --query status --output text) [ "$ST" != "RUNNING" ] && break sleep 1 done echo "second status=$ST" S2=$(aws secretsmanager get-secret-value --secret-id app/db/password --query SecretString --output text) echo "S1=$S1" echo "S2=$S2" [ "$S1" != "$S2" ] && echo "DIFFERENT: ok" || echo "SAME: FAIL"
second status=SUCCEEDED S1=5d1IweaKIEQh3hgk9uuZ9XZl6WZUJTT7 S2=TmCWd3kyR3K1LC6UAAisXo_d5dsD4rgz DIFFERENT: ok [stdout] second status=SUCCEEDED S1=5d1IweaKIEQh3hgk9uuZ9XZl6WZUJTT7 S2=TmCWd3kyR3K1LC6UAAisXo_d5dsD4rgz DIFFERENT: ok
Dump all four role policy docs
export AWS_ENDPOINT_URL=http://localstack:4566 echo "=== secrets-rotator-role ===" aws iam get-role-policy --role-name secrets-rotator-role --policy-name secrets-rotator-policy --query PolicyDocument echo echo "=== notification-lambda-role ===" aws iam get-role-policy --role-name notification-lambda-role --policy-name notification-lambda-policy --query PolicyDocument echo echo "=== rotation-child-role ===" aws iam get-role-policy --role-name rotation-child-role --policy-name rotation-child-policy --query PolicyDocument echo echo "=== rotation-orchestrator-role ===" aws iam get-role-policy --role-name rotation-orchestrator-role --policy-name rotation-orchestrator-policy --query PolicyDocument
=== secrets-rotator-role ===
{
"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": "RotateSecret",
"Effect": "Allow",
"Action": [
"secretsmanager:DescribeSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:PutSecretValue",
"secretsmanager:UpdateSecretVersionStage"
],
"Resource": "arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-nniOzG"
},
{
"Sid": "UseCMK",
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:GenerateDataKey"
],
"Resource": "arn:aws:kms:us-east-1:000000000000:key/d7b06a24-40e6-49dd-82cf-45c7ffafa707"
}
]
}
=== notification-lambda-role ===
{
"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:*"
}
]
}
=== rotation-child-role ===
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InvokeRotator",
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:us-east-1:000000000000:function:secrets-rotator"
},
{
"Sid": "PutRotationEvent",
"Effect": "Allow",
"Action": "events:PutEvents",
"Resource": "arn:aws:events:us-east-1:000000000000:event-bus/rotation-events"
}
]
}
=== rotation-orchestrator-role ===
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "StartChild",
"Effect": "Allow",
"Action": "states:StartExecution",
"Resource": "arn:aws:states:us-east-1:000000000000:stateMachine:rotation-child"
},
{
"Sid": "ControlChildExecutions",
"Effect": "Allow",
"Action": [
"states:DescribeExecution",
"states:StopExecution"
],
"Resource": "arn:aws:states:us-east-1:000000000000:execution:rotation-child:*"
},
{
"Sid": "ManagedRuleForSyncHandoff",
"Effect": "Allow",
"Action": [
"events:PutRule",
"events:PutTargets",
"events:DescribeRule"
],
"Resource": "arn:aws:events:us-east-1:000000000000:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule*"
}
]
}
[stdout]
=== secrets-rotator-role ===
{
"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": "RotateSecret",
"Effect": "Allow",
"Action": [
"secretsmanager:DescribeSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:PutSecretValue",
"secretsmanager:UpdateSecretVersionStage"
],
"Resource": "arn:aws:secretsmanager:us-east-1:000000000000:secret:app/db/password-nniOzG"
},
{
"Sid": "UseCMK",
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:GenerateDataKey"
],
"Resource": "arn:aws:kms:us-east-1:000000000000:key/d7b06a24-40e6-49dd-82cf-45c7ffafa707"
}
]
}
=== notification-lambda-role ===
{
"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:*"
}
]
}
=== rotation-child-role ===
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InvokeRotator",
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:us-east-1:000000000000:function:secrets-rotator"
},
{
"Sid": "PutRotationEvent",
"Effect": "Allow",
"Action": "events:PutEvents",
"Resource": "arn:aws:events:us-east-1:000000000000:event-bus/rotation-events"
}
]
}
=== rotation-orchestrator-role ===
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "StartChild",
"Effect": "Allow",
"Action": "states:StartExecution",
"Resource": "arn:aws:states:us-east-1:000000000000:stateMachine:rotation-child"
},
{
"Sid": "ControlChildExecutions",
"Effect": "Allow",
"Action": [
"states:DescribeExecution",
"states:StopExecution"
],
"Resource": "arn:aws:states:us-east-1:000000000000:execution:rotation-child:*"
},
{
"Sid": "ManagedRuleForSyncHandoff",
"Effect": "Allow",
"Action": [
"events:PutRule",
"events:PutTargets",
"events:DescribeRule"
],
"Resource": "arn:aws:events:us-east-1:000000000000:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule*"
}
]
}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/eyq_QE1p03vVO-bDIYY24/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 FAILED [ 66%]
../tests/test_state.py::test_rotator_lambda_allows_secretsmanager_invoke FAILED [ 73%]
../tests/test_state.py::test_parent_role_grants_sync2_managed_rule_perms FAILED [ 80%]
../tests/test_state.py::test_child_role_can_rotate_and_publish FAILED [ 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%]
=================================== FAILURES ===================================
___________________ test_kms_key_policy_grants_rotator_role ____________________
iam = <botocore.client.IAM object at 0xffffb45fdbe0>
kms = <botocore.client.KMS object at 0xffffb4b48320>
def test_kms_key_policy_grants_rotator_role(iam, kms):
role_arn = iam.get_role(RoleName=ROTATOR_ROLE)["Role"]["Arn"]
policy_str = kms.get_key_policy(KeyId=KEY_ALIAS, PolicyName="default")[
"Policy"
]
policy = json.loads(policy_str)
match = False
for st in policy.get("Statement", []):
if st.get("Effect") != "Allow":
continue
principal = st.get("Principal") or {}
if not isinstance(principal, dict):
continue
arns = set(_normalise_list(principal.get("AWS")))
if role_arn not in arns:
continue
actions = set(_normalise_list(st.get("Action")))
if _actions_cover(actions, REQUIRED_KMS_ACTIONS):
match = True
break
> assert match, (
f"KMS key policy on {KEY_ALIAS} has no Allow statement whose "
f"Principal.AWS includes {role_arn} and whose Action covers "
f"{sorted(REQUIRED_KMS_ACTIONS)}. Key policy: {policy_str}"
)
E AssertionError: KMS key policy on alias/app-rotation-key has no Allow statement whose Principal.AWS includes arn:aws:iam::000000000000:role/secrets-rotator-role and whose Action covers ['kms:Decrypt', 'kms:GenerateDataKey']. Key policy: {
E "Version": "2012-10-17",
E "Id": "app-rotation-key-policy",
E "Statement": [
E {
E "Sid": "EnableRootAccountAdmin",
E "Effect": "Allow",
E "Principal": {"AWS": "arn:aws:iam::000000000000:root"},
E "Action": "kms:*",
E "Resource": "*"
E },
E {
E "Sid": "AllowSecretsManagerUseOfKey",
E "Effect": "Allow",
E "Principal": {"Service": "secretsmanager.amazonaws.com"},
E "Action": [
E "kms:Decrypt",
E "kms:Encrypt",
E "kms:ReEncrypt*",
E "kms:GenerateDataKey*",
E "kms:DescribeKey",
E "kms:CreateGrant"
E ],
E "Resource": "*"
E }
E ]
E }
E
E assert False
/tests/test_state.py:322: AssertionError
_______________ test_rotator_lambda_allows_secretsmanager_invoke _______________
lmb = <botocore.client.Lambda object at 0xffffb4b49ee0>
def test_rotator_lambda_allows_secretsmanager_invoke(lmb):
try:
policy_doc = json.loads(
> lmb.get_policy(FunctionName=ROTATOR_FUNCTION)["Policy"]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
/tests/test_state.py:332:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/root/.cache/uv/archive-v0/eyq_QE1p03vVO-bDIYY24/lib/python3.12/site-packages/botocore/client.py:569: in _api_call
return self._make_api_call(operation_name, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <botocore.client.Lambda object at 0xffffb4b49ee0>
operation_name = 'GetPolicy', api_params = {'FunctionName': 'secrets-rotator'}
def _make_api_call(self, operation_name, api_params):
operation_model = self._service_model.operation_model(operation_name)
service_name = self._service_model.service_name
history_recorder.record(
'API_CALL',
{
'service': service_name,
'operation': operation_name,
'params': api_params,
},
)
if operation_model.deprecated:
logger.debug(
'Warning: %s.%s() is deprecated', service_name, operation_name
)
request_context = {
'client_region': self.meta.region_name,
'client_config': self.meta.config,
'has_streaming_input': operation_model.has_streaming_input,
'auth_type': operation_model.resolved_auth_type,
'unsigned_payload': operation_model.unsigned_payload,
}
api_params = self._emit_api_params(
api_params=api_params,
operation_model=operation_model,
context=request_context,
)
(
endpoint_url,
additional_headers,
properties,
) = self._resolve_endpoint_ruleset(
operation_model, api_params, request_context
)
if properties:
# Pass arbitrary endpoint info with the Request
# for use during construction.
request_context['endpoint_properties'] = properties
request_dict = self._convert_to_request_dict(
api_params=api_params,
operation_model=operation_model,
endpoint_url=endpoint_url,
context=request_context,
headers=additional_headers,
)
resolve_checksum_context(request_dict, operation_model, api_params)
service_id = self._service_model.service_id.hyphenize()
handler, event_response = self.meta.events.emit_until_response(
f'before-call.{service_id}.{operation_name}',
model=operation_model,
params=request_dict,
request_signer=self._request_signer,
context=request_context,
)
if event_response is not None:
http, parsed_response = event_response
else:
maybe_compress_request(
self.meta.config, request_dict, operation_model
)
apply_request_checksum(request_dict)
http, parsed_response = self._make_request(
operation_model, request_dict, request_context
)
self.meta.events.emit(
f'after-call.{service_id}.{operation_name}',
http_response=http,
parsed=parsed_response,
model=operation_model,
context=request_context,
)
if http.status_code >= 300:
error_info = parsed_response.get("Error", {})
error_code = error_info.get("QueryErrorCode") or error_info.get(
"Code"
)
error_class = self.exceptions.from_code(error_code)
> raise error_class(parsed_response, operation_name)
E botocore.errorfactory.ResourceNotFoundException: An error occurred (ResourceNotFoundException) when calling the GetPolicy operation: The resource you requested does not exist.
/root/.cache/uv/archive-v0/eyq_QE1p03vVO-bDIYY24/lib/python3.12/site-packages/botocore/client.py:1023: ResourceNotFoundException
During handling of the above exception, another exception occurred:
lmb = <botocore.client.Lambda object at 0xffffb4b49ee0>
def test_rotator_lambda_allows_secretsmanager_invoke(lmb):
try:
policy_doc = json.loads(
lmb.get_policy(FunctionName=ROTATOR_FUNCTION)["Policy"]
)
except ClientError as e:
> pytest.fail(
f"Lambda {ROTATOR_FUNCTION} has no resource-based policy , "
f"secretsmanager.amazonaws.com cannot invoke it: {e}"
)
E Failed: Lambda secrets-rotator has no resource-based policy , secretsmanager.amazonaws.com cannot invoke it: An error occurred (ResourceNotFoundException) when calling the GetPolicy operation: The resource you requested does not exist.
/tests/test_state.py:335: Failed
_______________ test_parent_role_grants_sync2_managed_rule_perms _______________
iam = <botocore.client.IAM object at 0xffffb45fdbe0>
def test_parent_role_grants_sync2_managed_rule_perms(iam):
"""The .sync:2 integration registers an EventBridge-managed callback
rule. Parent role needs events:PutRule/PutTargets/DescribeRule plus
full states:* on the child."""
found_events = False
found_states = False
for _pname, st in _inline_statements(iam, PARENT_ROLE):
actions = set(_normalise_list(st.get("Action")))
if _actions_cover(actions, REQUIRED_PARENT_EVENTS_ACTIONS):
found_events = True
if _actions_cover(actions, REQUIRED_PARENT_STATES_ACTIONS):
found_states = True
assert found_events, (
f"{PARENT_ROLE} missing events:PutRule/PutTargets/DescribeRule. "
f".sync:2 uses an EventBridge-managed callback rule; without "
f"these the parent cannot set up the callback."
)
> assert found_states, (
f"{PARENT_ROLE} missing states:StartExecution/DescribeExecution/"
f"StopExecution. .sync:2 requires all three on the child SM."
)
E AssertionError: rotation-orchestrator-role missing states:StartExecution/DescribeExecution/StopExecution. .sync:2 requires all three on the child SM.
E assert False
/tests/test_state.py:377: AssertionError
____________________ test_child_role_can_rotate_and_publish ____________________
iam = <botocore.client.IAM object at 0xffffb45fdbe0>
def test_child_role_can_rotate_and_publish(iam):
found_rotate = False
found_putevents = False
for _pname, st in _inline_statements(iam, CHILD_ROLE):
actions = set(_normalise_list(st.get("Action")))
if _actions_cover(actions, {"secretsmanager:RotateSecret"}):
found_rotate = True
if _actions_cover(actions, {"events:PutEvents"}):
found_putevents = True
> assert found_rotate, (
f"{CHILD_ROLE} lacks secretsmanager:RotateSecret , the child "
f"state machine cannot trigger rotation."
)
E AssertionError: rotation-child-role lacks secretsmanager:RotateSecret , the child state machine cannot trigger rotation.
E assert False
/tests/test_state.py:392: AssertionError
=============================== warnings summary ===============================
test_state.py: 34 warnings
/root/.cache/uv/archive-v0/eyq_QE1p03vVO-bDIYY24/lib/python3.12/site-packages/botocore/auth.py:424: DeprecationWa
… (truncated at 12,000 chars, full verifier log is in the trial artifacts)Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_7a21c6c66a3b4fe5. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_7a21c6c66a3b4fe5 · verifier authoritative; classifier explanatory.