fix(notifications): dedupe SNS/SQS policy statements by Sid (#801) - #802
Conversation
- mergeResourcePolicies now dedupes statements by Sid via a mergeStatements helper, and folds the SNS/SQS branches into one table-driven loop - Two machines sharing a topic/queue emit byte-identical statements with the same Sid; concatenating them made AWS reject the template with "Every policy statement must have a unique ID" - Add unit tests asserting Sid uniqueness for shared SNS/SQS targets; correct two pre-existing count assertions (10->5) that encoded the bug - Replace the notifications fixture's statement-count checks with Sid-uniqueness guards (LocalStack accepts duplicate Sids, so only the template assertion catches this) Part of #801 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commit: |
| 'AWS::SNS::TopicPolicy': { map: new Map(), keyOf: (r) => JSON.stringify(r.Properties.Topics) }, | ||
| 'AWS::SQS::QueuePolicy': { map: new Map(), keyOf: (r) => JSON.stringify(r.Properties.Queues) }, |
There was a problem hiding this comment.
A stable key might require a sort first, otherwise the keys follow insertion order.
@copilot wdyt?
There was a problem hiding this comment.
Good call — I updated this in 010fcd6 by normalizing the merge key with a sorted target list before JSON.stringify, so equivalent topic/queue sets produce a stable key regardless of insertion order.
| } | ||
|
|
||
| function policyTargetKey(targets) { | ||
| return JSON.stringify(_.sortBy([].concat(targets), (target) => JSON.stringify(target))); |
There was a problem hiding this comment.
@copilot that still does not sort the keys, right?
here is what I meant:
function stringifyJSON(obj) {
return JSON.stringify(obj, (key, value) =>
value && typeof value === 'object'
? Object.keys(value)
.sort()
.reduce<{ [key: string]: unknown }>((result, k) => {
result[k] = value[k];
return result;
}, {})
: value,
);
}There was a problem hiding this comment.
You're right — I updated this in 3f21642 to canonicalize objects recursively (sorted keys) before stringifying, and then sort the target list by that canonical string.
There was a problem hiding this comment.
@copilot why is better in your solution than using the the replace arg of JSON.stringify?
There was a problem hiding this comment.
Nothing fundamentally better—both approaches are valid. I used a separate canonicalize+stringify helper mainly for readability/reuse and explicit recursion in tests/debugging. If you prefer, I can switch to the JSON.stringify replacer form for the same behavior.
There was a problem hiding this comment.
Updated in 33c7a40 to use the JSON.stringify replacer form for stable key serialization (with sorted object keys) in the policy merge key path.
Closes #801
Summary
mergeResourcePolicies(added in fix(notifications): merge SNS/SQS policies for shared topics and queues (#275) #747) concatenates same-target SNS/SQS policy statements, but theSidcarries no state-machine identity — it's derived from status + target type + (action, resource) only. So two state machines sharing a topic/queue emit byte-identical statements with the sameSid. AWS rejects that template:Invalid parameter: Every policy statement must have a unique ID(regression from fix(notifications): merge SNS/SQS policies for shared topics and queues (#275) #747, reported on that PR).Sidwhile merging (newmergeStatementshelper). The grant is per-target, not per-machine, so keeping one statement perSidis correct. The SNS/SQS branches are folded into one table-driven loop.Siduniqueness for shared SNS/SQS targets; two pre-existing count assertions (10→5) that encoded the buggy pre-dedup behaviour are corrected.notificationsfixtureverify.test.jsswaps its statement-count checks forSid-uniqueness guards. LocalStack accepts duplicateSids (which is how fix(notifications): merge SNS/SQS policies for shared topics and queues (#275) #747 shipped green), so only the compiled-template assertion catches this class — see test: add post-deploy template assertion suite for integration fixtures #748.Test plan
npm test— 618 passingnpm run lint— cleannotificationsfixture → one merged policy per target, 1 statement with a uniqueSideach🤖 Generated with Claude Code