feat(lambda): ESM FilterCriteria + partial-batch failure + StartingPosition#740
Conversation
…lure + StartingPosition
Closes the long-standing ESM correctness gaps for SQS / Kinesis / DDB Streams pollers.
- New `fakecloud-lambda::filter::FilterSet` implements the EventBridge-style
JSON pattern subset documented for Lambda ESM FilterCriteria: exact
equality, array-of-scalars (or), `exists` / `prefix` / `suffix` /
`equals-ignore-case` / `anything-but` / `numeric` operators, and the
SQS body-decode special case (patterns rooted at `body` are matched
against the JSON-parsed message body).
- EventSourceMapping struct gained `filter_patterns`,
`function_response_types`, `starting_position`,
`starting_position_timestamp`, `parallelization_factor`, and
`maximum_batching_window_in_seconds`. Both CreateEventSourceMapping
and UpdateEventSourceMapping read/write them, and the JSON serializer
reflects them back on Get/List.
- SQS poller honors FilterCriteria (drops + acks non-matching),
MaximumBatchingWindowInSeconds (holds partial batches), and
FunctionResponseTypes=[ReportBatchItemFailures] (parses
`{"batchItemFailures":[{"itemIdentifier":...}]}` and only retries
the failed messages instead of the entire batch).
- Kinesis poller honors FilterCriteria (advances checkpoint past
dropped records — AWS treats them as consumed) and StartingPosition
on first poll (TRIM_HORIZON / LATEST / AT_TIMESTAMP).
- DDB Streams poller honors FilterCriteria + StartingPosition
(TRIM_HORIZON / LATEST — AWS doesn't accept AT_TIMESTAMP for DDB).
Tests: 7 unit tests on FilterSet (operator coverage), 3 unit tests
on `batchItemFailures` parsing, 1 e2e test on SQS FilterCriteria
end-to-end (matching message routed, non-matching acked off the queue).
Docs/website/README updated to advertise the new ESM behavior.
There was a problem hiding this comment.
7 issues found across 12 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="crates/fakecloud-server/src/dynamodb_streams_lambda_poller.rs">
<violation number="1" location="crates/fakecloud-server/src/dynamodb_streams_lambda_poller.rs:215">
P1: Checkpoint is advanced before confirming Lambda invocation success, so failed batches are dropped instead of retried.</violation>
</file>
<file name="crates/fakecloud-lambda/src/extras.rs">
<violation number="1" location="crates/fakecloud-lambda/src/extras.rs:1690">
P2: UpdateEventSourceMapping now mutates new ESM fields but does not return them in the response, so clients cannot round-trip updated `FilterCriteria`/`FunctionResponseTypes`/batching settings from the update call.</violation>
</file>
<file name="crates/fakecloud-server/src/sqs_lambda_poller.rs">
<violation number="1" location="crates/fakecloud-server/src/sqs_lambda_poller.rs:341">
P2: `split_batch_failures` returns failure IDs verbatim from the Lambda response without intersecting them with `batch_ids`. A bogus or stale `itemIdentifier` in the response could match an unrelated message in the queue, incorrectly bumping its `receive_count` and resetting its visibility. Filter the failures to only include IDs present in the current batch.</violation>
</file>
<file name="crates/fakecloud-server/src/kinesis_lambda_poller.rs">
<violation number="1" location="crates/fakecloud-server/src/kinesis_lambda_poller.rs:133">
P1: Checkpoint is advanced before delivery, so failed invocations permanently lose records. The old code only advanced the checkpoint after a successful delivery. Move the unconditional checkpoint advance into the `matched.is_empty()` branch, and keep the post-delivery advance for the success path.</violation>
</file>
<file name="crates/fakecloud-lambda/src/filter.rs">
<violation number="1" location="crates/fakecloud-lambda/src/filter.rs:37">
P1: Invalid JSON filter patterns are silently discarded, which can turn malformed `FilterCriteria` into pass-through delivery for all records.</violation>
<violation number="2" location="crates/fakecloud-lambda/src/filter.rs:110">
P1: `exists` operator is implemented as null-check instead of field-presence check, so present `null` fields are matched incorrectly.</violation>
<violation number="3" location="crates/fakecloud-lambda/src/filter.rs:143">
P2: Malformed `numeric` arrays with odd length currently evaluate to `true`, causing false-positive matches.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
7 findings from Cubic, all addressed:
- filter.rs: `exists` operator now tests field *presence* (matching
AWS), not value-is-null. `{"foo": null}` matches `{"exists": true}`
because the key is present; `{}` matches `{"exists": false}`. Internal
evaluator passes `Option<&Value>` so presence and absence are
distinguishable end-to-end.
- filter.rs: numeric arrays must be even-length and non-empty —
malformed `["<", 5, "<"]` now returns no-match instead of true.
- filter.rs: invalid JSON patterns are logged at runtime, AND
`FilterSet::validate` rejects them at `CreateEventSourceMapping`
with `InvalidParameterValueException` (matches AWS behavior).
- sqs_lambda_poller.rs: `split_batch_failures` intersects the failure
list with the actual batch ids — stale or made-up `itemIdentifier`
no longer holds an unrelated message visible.
- kinesis_lambda_poller.rs / dynamodb_streams_lambda_poller.rs:
checkpoint advances *after* a successful invoke (or after a filter
drops every record). A failed invoke leaves the records pending so
the next poll retries them — matches AWS at-least-once.
- extras.rs: UpdateEventSourceMapping returns `FilterCriteria`,
`FunctionResponseTypes`, `MaximumBatchingWindowInSeconds`, and
`ParallelizationFactor` in the response so clients can round-trip
updated values.
3 new unit tests cover the corrected behavior:
- exists treats null-valued field as present, missing field as absent
- numeric odd-length arrays do not silently match
- validate rejects invalid JSON patterns
There was a problem hiding this comment.
1 issue found across 6 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="crates/fakecloud-lambda/src/service.rs">
<violation number="1" location="crates/fakecloud-lambda/src/service.rs:1114">
P2: Validation is applied after lossy parsing, so non-string/malformed `FilterCriteria.Filters[].Pattern` values can be silently ignored instead of rejected.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Cubic flagged that the previous extractor used `as_str` + filter_map, which silently dropped entries where Pattern was a number, object, or boolean. Validation then ran against the cleaned list and accepted it. Strict extraction now rejects any non-string Pattern with InvalidParameterValueException before validation runs.
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="crates/fakecloud-lambda/src/service.rs">
<violation number="1" location="crates/fakecloud-lambda/src/service.rs:1108">
P2: `FilterCriteria` type is not validated: non-object values are treated as absent and silently accepted.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
The post-Cubic correctness fix only deletes SQS messages on a successful Lambda invocation; the prior "fake-code" blob caused the runtime to error and the poller to leave the message visible for retry. Use the same runnable Python handler the Kinesis ESM test uses so the assertion that the message is consumed reflects real Lambda success, not poller leniency.
…artup The fixed 2-second sleep before asserting the SQS->Lambda invocation isn't long enough in CI when Docker pulls the Python runtime image: the poller's "aws:sqs" record only lands after invoke completes, and the test was reading the in-flight "aws:lambda:delivery" record from the LambdaDelivery adapter as the last entry. Replace the sleep + last-entry lookup with a polling loop that waits for the "aws:sqs" record explicitly, and use `.iter().rev().find()` to pick it out of the invocation list (matching the kinesis test).
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="crates/fakecloud-e2e/tests/cross_service.rs">
<violation number="1" location="crates/fakecloud-e2e/tests/cross_service.rs:553">
P2: Add a timeout to this polling loop so the test fails instead of hanging forever when the invocation never arrives.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Summary
Closes the long-standing event source mapping correctness gaps for SQS / Kinesis / DDB Streams pollers.
Test plan
Docs / website / README updated to advertise the new ESM behavior.
Summary by cubic
Adds FilterCriteria matching, SQS partial-batch failures, and StartingPosition to Lambda event source mappings. Pollers now drop/ack non-matching records, honor SQS batching windows, and keep checkpoints aligned with AWS semantics.
New Features
fakecloud-lambda::filter::FilterSetadds EventBridge-style JSON pattern matching (exists/prefix/suffix/equals-ignore-case/anything-but/numeric) with SQS body JSON-decode; used by SQS, Kinesis, and DDB Streams. Non-matching records are discarded and progress advances.FunctionResponseTypes=["ReportBatchItemFailures"]; parses{"batchItemFailures":[{"itemIdentifier":"<id>"}]}and retries only failed IDs. HonorsMaximumBatchingWindowInSecondsfor partial batches.StartingPositionon first poll — Kinesis (TRIM_HORIZON/LATEST/AT_TIMESTAMP) and DDB Streams (TRIM_HORIZON/LATEST).FilterCriteria,FunctionResponseTypes,StartingPosition,StartingPositionTimestamp,MaximumBatchingWindowInSeconds, andParallelizationFactor.Bug Fixes
existsnow checks field presence (null counts as present);numericrequires even, non-empty operator arrays; invalid JSON patterns are rejected on create; non-stringFilters[].Patternis rejected withInvalidParameterValueException; non-objectFilterCriteriais rejected at create.itemIdentifiervalues with the actual batch to avoid holding unrelated messages.aws:sqsrecord, and bounds the wait with a 30s timeout to avoid CI hangs.Written for commit 293c0fc. Summary will update on new commits.