floci-io/floci · error · AwsException
ValidationException
ValidationException
Error message
EventBusPolicy StatementId is required.
What it means
Provisioning an AWS::Events::EventBusPolicy resource whose StatementId property resolves to null or blank. StatementId (the policy Sid) is mandatory in both the Statement-form and individual-form code paths, so the provisioner validates it up front before touching the bus policy.
Source
Thrown at src/main/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationResourceProvisioner.java:3296
// A missing attribute means ownership was never tracked, not that it changed: stacks
// provisioned before this attribute existed are restored from cloudformation-stacks.json
// without it. Refusing there would leave every such stack permanently in DELETE_FAILED, so
// fall back to the pre-tracking behaviour of deleting what the stack recorded it created.
String expectedCreatedTime = resource.getAttributes().get(EVENT_BUS_CREATED_TIME_ATTR);
if (expectedCreatedTime != null && !expectedCreatedTime.equals(eventBusCreatedTime(bus))) {
throw new AwsException("ValidationError",
"EventBus ownership changed; refusing to delete: " + busName, 400);
}
deleteEventBusSafe(busName, region);
}
private void provisionEventBusPolicy(StackResource r, JsonNode props, CloudFormationTemplateEngine engine,
String region) {
String busName = resolveOrDefault(props, "EventBusName", engine, "default");
String statementId = resolveOptional(props, "StatementId", engine);
if (statementId == null || statementId.isBlank()) {
throw new AwsException("ValidationException", "EventBusPolicy StatementId is required.", 400);
}
if (props != null && props.has("Statement") && props.get("Statement").isObject()) {
// Statement form: merge the full statement into the bus policy, keyed by Sid,
// so multiple EventBusPolicy resources on the same bus coexist.
try {
ObjectNode statement = (ObjectNode) engine.resolveNode(props.get("Statement")).deepCopy();
statement.put("Sid", statementId);
EventBus bus = eventBridgeService.describeEventBus(busName, region);
ObjectNode policy;
String current = bus.getPolicy();
if (current != null && !current.isBlank()) {
policy = (ObjectNode) objectMapper.readTree(current);
} else {
policy = objectMapper.createObjectNode();
policy.put("Version", "2012-10-17");
policy.putArray("Statement");View on GitHub (pinned to 62ff490619)
Solutions
- Add a non-blank StatementId to the resource properties in the template
- If StatementId comes from a parameter/Ref, verify the parameter has a real non-empty value at deploy time
- Ensure intrinsic expressions (Fn::Sub, Ref) actually resolve to a string rather than null — test with the emulator's template engine or aws cloudformation validate-template first
Example fix
# before
Resources:
BusPolicy:
Type: AWS::Events::EventBusPolicy
Properties:
EventBusName: !Ref Bus
Action: events:PutEvents
Principal: "123456789012"
# after
Resources:
BusPolicy:
Type: AWS::Events::EventBusPolicy
Properties:
EventBusName: !Ref Bus
StatementId: AllowCrossAccountPut
Action: events:PutEvents
Principal: "123456789012" Defensive patterns
Strategy: validation
Validate before calling
// Validate the template before deploy
const ok = resource.Properties?.StatementId != null
&& String(resource.Properties.StatementId).trim().length > 0;
if (!ok) throw new Error('EventBusPolicy requires a non-blank StatementId'); Type guard
function hasStatementId(p: unknown): p is { StatementId: string } {
return typeof (p as any)?.StatementId === 'string'
&& (p as any).StatementId.trim().length > 0;
} Prevention
- Run cfn-lint / aws cloudformation validate-template before CreateStack
- Copy from a known-good EventBusPolicy example that includes StatementId
- Treat StatementId as the policy's primary key — one unique Sid per policy resource per bus
When it happens
Trigger: CreateStack/UpdateStack with an AWS::Events::EventBusPolicy resource where StatementId is omitted, set to empty string, or resolves via intrinsic functions to blank (e.g. a Fn::Sub or Ref that yields "").
Common situations: Template authored with only Action+Principal (copying putPermission muscle memory where Sid is optional); StatementId supplied via a parameter defaulted to ''; short-form intrinsic shorthand miswritten so it resolves to null.
Related errors
- InternalFailure
- ValidationError
- ValidationError
- ValidationError
- TLS enabled but no certificate provided and self-signed gene
AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14).
Data as JSON: /api/errors/6699447678b33f74.
Report an issue: GitHub.