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

  1. Add a non-blank StatementId to the resource properties in the template
  2. If StatementId comes from a parameter/Ref, verify the parameter has a real non-empty value at deploy time
  3. 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

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


AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14). Data as JSON: /api/errors/6699447678b33f74. Report an issue: GitHub.