@reventlessdev/reventless-aws 3.0.0-alpha.223 → 3.0.0-alpha.225

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +6 -6
  3. package/src/adapter/AWS_Tags.res +36 -0
  4. package/src/adapter/Adapter_Helpers.res +6 -0
  5. package/src/adapter/Adapter_Helpers.res.mjs +6 -0
  6. package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res +77 -48
  7. package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res.mjs +55 -40
  8. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res +6 -0
  9. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +3 -1
  10. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +29 -6
  11. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs +13 -1
  12. package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res +83 -60
  13. package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res.mjs +20 -18
  14. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +44 -30
  15. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +22 -23
  16. package/src/adapter/Postgres/PgProjectionFeed.res +20 -1
  17. package/src/adapter/Postgres/PgProjectionFeed.res.mjs +3 -1
  18. package/src/adapter/Runtime/EventCollectorEntryPoint.mjs +6 -2
  19. package/src/adapter/Runtime/PluginExtensionPointEntryPoint.mjs +9 -2
  20. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +10 -4
  21. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +1 -1
  22. package/src/adapter/StateTopic/StateTopic_AppSync.res +8 -1
  23. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +4 -2
  24. package/src/plugin/cloner/ClonerRunner_Fargate.res +6 -0
  25. package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +2 -1
  26. package/src/util/Util_DeadLetterQueue.res +39 -4
  27. package/src/util/Util_DeadLetterQueue.res.mjs +16 -4
  28. package/src/util/Util_EventSourceMapping.res +8 -0
  29. package/src/util/Util_EventSourceMapping.res.mjs +6 -4
  30. package/tests/CommandTopicQueuePolicyTest.res +65 -0
  31. package/tests/CommandTopicQueuePolicyTest.res.mjs +53 -0
  32. package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +50 -0
  33. package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res.mjs +47 -0
  34. package/tests/integration/DcbEventLogStorage_DynamoDb_IntegrationTest.res +58 -0
  35. package/tests/integration/DcbEventLogStorage_DynamoDb_IntegrationTest.res.mjs +78 -0
@@ -10,64 +10,62 @@ let toResources = (eventTopics: ReventlessCore.EventTopic.allOutputs) =>
10
10
  ->Array.flatMap(outputs => outputs.resources)
11
11
  ->ReventlessCore.Adapter.resourcesToResolvedOutput
12
12
 
13
- let createQueuePolicy = (queue: PulumiAws.SQS.Queue.t, name, _resources, opts) => {
14
- let _ =
15
- (queue.arn, queue.id)
16
- ->Pulumi.Output.all2
17
- ->Pulumi.Output.apply(((queueArn, queueId)) => {
18
- // SQS queue ARN is `arn:aws:sqs:<region>:<accountId>:<name>` — segment
19
- // index 4 (0-based). Used to scope cross-plugin SNS sources to this
20
- // account (aws:SourceAccount) and to topic names following the
21
- // Reventless EventTopic naming convention (aws:SourceArn arnLike).
22
- let accountId = queueArn->String.split(":")->Array.get(4)->Option.getOr("")
23
- let queuePolicyDocument =
24
- PulumiAws.PolicyDocument.make(
25
- ~id=name ++ "QueuePolicy",
26
- ~statements=[
27
- // Single statement allows SendMessage from any SNS topic owned by
28
- // this AWS account whose name matches the Reventless EventTopic
29
- // naming convention. Replaces the previous per-topic arnEquals
30
- // list so cross-plugin SNS subscriptions created at runtime by the
31
- // admin's manageSubscriptions hook (Phase 3 Step 1) are accepted
32
- // without requiring a redeploy of the receiving plugin. Security
33
- // boundary: SourceAccount condition keeps third-party-account
34
- // topics out; ArnLike + SNS service principal further narrows
35
- // accepted senders to in-account EventTopic resources.
36
- {
37
- sid: "AllowReceiveSnsEvents",
38
- principal: Principals({
39
- service: PrincipalIds([AWS.SNS.principal]),
40
- }),
41
- effect: Allow,
42
- actions: Actions(["sqs:SendMessage"]),
43
- resources: Resource(queueArn),
44
- conditions: {
45
- stringEquals: [
46
- ("aws:SourceAccount", ConditionValue(accountId)),
47
- ]->Dict.fromArray,
48
- arnLike: [
49
- ("aws:SourceArn", ConditionValue(`arn:aws:sns:*:${accountId}:*EventTopic-*`)),
50
- ]->Dict.fromArray,
51
- },
52
- },
53
- ],
54
- )
55
- ->toJsonString
56
- ->Pulumi.Input.make
57
- let _queuePolicy = {
58
- PulumiAws.SQS.QueuePolicy.make(
59
- ~name,
60
- ~args={
61
- queueUrl: queueId->Pulumi.Input.make,
62
- policy: queuePolicyDocument,
13
+ let createQueuePolicy = (queue: PulumiAws.SQS.Queue.t, name, opts) => {
14
+ // The document is a *value* derived from the resolved ARN, so it is built in
15
+ // an apply. The QueuePolicy itself stays outside: passing `queue.id` as an
16
+ // Output is what registers the policy -> queue dependency, without which
17
+ // Pulumi has no ordering constraint and can delete the queue first on a
18
+ // replacement, leaving the policy's delete polling a queue that is gone.
19
+ let queuePolicyDocument = queue.arn->Pulumi.Output.apply(queueArn => {
20
+ // Scopes cross-plugin SNS sources to this account (aws:SourceAccount) and
21
+ // to topic names following the Reventless EventTopic naming convention
22
+ // (aws:SourceArn arnLike).
23
+ let accountId = queueArn->accountIdOfQueueArn
24
+ PulumiAws.PolicyDocument.make(
25
+ ~id=name ++ "QueuePolicy",
26
+ ~statements=[
27
+ // Single statement allows SendMessage from any SNS topic owned by
28
+ // this AWS account whose name matches the Reventless EventTopic
29
+ // naming convention. A per-topic arnEquals list would require a
30
+ // redeploy of the receiving plugin whenever the platform's
31
+ // manageSubscriptions hook creates a cross-plugin SNS subscription at
32
+ // runtime. Security boundary: SourceAccount keeps third-party-account
33
+ // topics out; ArnLike + SNS service principal further narrows accepted
34
+ // senders to in-account EventTopic resources.
35
+ {
36
+ sid: "AllowReceiveSnsEvents",
37
+ principal: Principals({
38
+ service: PrincipalIds([AWS.SNS.principal]),
39
+ }),
40
+ effect: Allow,
41
+ actions: Actions(["sqs:SendMessage"]),
42
+ resources: Resource(queueArn),
43
+ conditions: {
44
+ stringEquals: [("aws:SourceAccount", ConditionValue(accountId))]->Dict.fromArray,
45
+ arnLike: [
46
+ ("aws:SourceArn", ConditionValue(`arn:aws:sns:*:${accountId}:*EventTopic-*`)),
47
+ ]->Dict.fromArray,
63
48
  },
64
- ~opts=Some(opts),
65
- )
66
- }
67
- })
49
+ },
50
+ ],
51
+ )->toJsonString
52
+ })
53
+ let _queuePolicy = PulumiAws.SQS.QueuePolicy.make(
54
+ ~name,
55
+ ~args={
56
+ queueUrl: queue.id->Pulumi.Output.asInput,
57
+ policy: queuePolicyDocument->Pulumi.Output.asInput,
58
+ },
59
+ ~opts=Some(opts),
60
+ )
68
61
  }
69
62
 
70
- let subscribeQueue2SnsTopic = (queue, name, resources: array<ReventlessInfra.Adapter.resolvedResource>, opts) => {
63
+ let subscribeQueue2SnsTopic = (
64
+ queue,
65
+ name,
66
+ resources: array<ReventlessInfra.Adapter.resolvedResource>,
67
+ opts,
68
+ ) => {
71
69
  let _snsTopicSubscriptions = resources->Array.map(resource => {
72
70
  log.debug(~comp="EventCollector", `subscribeToSnsTopic: ${name} -> ${resource.name}`)
73
71
  let subscription = Util_SQS.subscribeToSnsTopic(
@@ -82,20 +80,35 @@ let subscribeQueue2SnsTopic = (queue, name, resources: array<ReventlessInfra.Ada
82
80
  }
83
81
 
84
82
  let connectSqsQueue2SnsTopics = (queue: PulumiAws.SQS.Queue.t, name, eventTopics, opts) => {
83
+ // The policy grants SendMessage to any in-account EventTopic by naming
84
+ // convention, so it does not depend on the resolved topic resources and is
85
+ // created outside the apply. Only the SNS subscriptions need them.
86
+ queue->createQueuePolicy(name, opts)
85
87
  let _ =
86
88
  (eventTopics->toResources, queue.id)
87
89
  ->Pulumi.Output.all2
88
90
  ->Pulumi.Output.apply(((eventTopicResources, queueId)) => {
89
91
  log.debug(
90
92
  ~comp="EventCollector",
91
- `connectSqsQueue2SnsTopics ${queueId}: ${eventTopicResources->Array.length->Int.toString} topic resource(s)`,
93
+ `connectSqsQueue2SnsTopics ${queueId}: ${eventTopicResources
94
+ ->Array.length
95
+ ->Int.toString} topic resource(s)`,
92
96
  )
93
- let snsResources = eventTopicResources->snsResources
94
- queue->createQueuePolicy(name, snsResources, opts)
95
- queue->subscribeQueue2SnsTopic(name, snsResources, opts)
97
+ queue->subscribeQueue2SnsTopic(name, eventTopicResources->snsResources, opts)
96
98
  })
97
99
  }
98
100
 
101
+ // The EventCollector's transport wiring: one mapping per stream source plus one
102
+ // per buffer queue. `~component=name` keeps them attributed to the collector they
103
+ // feed, not to the mapping's own generated name.
104
+ let esmTags = (name, esmName) =>
105
+ AWS.Tags.make(
106
+ ~name=esmName,
107
+ ~kind=ReventlessCore.EventCollector.componentType,
108
+ ~role=EventSourceMapping,
109
+ ~component=name,
110
+ )
111
+
99
112
  let connectLambda = (
100
113
  lambda: Pulumi.Output.t<PulumiAws.Lambda.Function.t>,
101
114
  name: string,
@@ -252,6 +265,10 @@ let connectLambda = (
252
265
  ~targetName=name,
253
266
  ~sourceName=dynamoDbStreamResource.name,
254
267
  ~source=dynamoDbStreamResource->ReventlessCore.AdapterDeploytime.resolvedToResource,
268
+ ~tags=esmTags(
269
+ name,
270
+ dynamoDbStreamResource.name->ReventlessCore.Util.baseName ++ ("2" ++ name),
271
+ ),
255
272
  ~opts,
256
273
  )
257
274
  )
@@ -259,7 +276,13 @@ let connectLambda = (
259
276
 
260
277
  let subscriptionResources = queues->Array.mapWithIndex((queue, idx) => {
261
278
  let esmName = queues->Array.length > 1 ? `${name}Sqs${Int.toString(idx)}` : name
262
- Util_EventSourceMapping.subscribeSqs(~lambda, ~name=esmName, ~queue, ~opts)
279
+ Util_EventSourceMapping.subscribeSqs(
280
+ ~lambda,
281
+ ~name=esmName,
282
+ ~queue,
283
+ ~tags=esmTags(name, esmName),
284
+ ~opts,
285
+ )
263
286
  })
264
287
 
265
288
  subscriptionResources
@@ -2,15 +2,17 @@
2
2
 
3
3
  import * as Aws from "@pulumi/aws";
4
4
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
5
- import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
5
  import * as Pulumi from "@pulumi/pulumi";
7
6
  import * as Lambda$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Lambda/Lambda.res.mjs";
8
7
  import * as AWS$ReventlessAws from "../AWS.res.mjs";
8
+ import * as Util$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util.res.mjs";
9
9
  import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
10
+ import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
10
11
  import * as Adapter$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Adapter.res.mjs";
11
12
  import * as Util_SQS$ReventlessAws from "../../util/Util_SQS.res.mjs";
12
13
  import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
13
14
  import * as Adapter_Helpers$ReventlessAws from "../Adapter_Helpers.res.mjs";
15
+ import * as EventCollector$ReventlessCore from "@reventlessdev/reventless-core/src/components/EventCollector/EventCollector.res.mjs";
14
16
  import * as AdapterDeploytime$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/AdapterDeploytime.res.mjs";
15
17
  import * as Util_EventSourceMapping$ReventlessAws from "../../util/Util_EventSourceMapping.res.mjs";
16
18
 
@@ -20,14 +22,10 @@ function toResources(eventTopics) {
20
22
  return Adapter$ReventlessCore.resourcesToResolvedOutput(Object.values(eventTopics).flatMap(outputs => outputs.resources));
21
23
  }
22
24
 
23
- function createQueuePolicy(queue, name, _resources, opts) {
24
- Pulumi.all([
25
- queue.arn,
26
- queue.id
27
- ]).apply(param => {
28
- let queueArn = param[0];
29
- let accountId = Stdlib_Option.getOr(queueArn.split(":")[4], "");
30
- let queuePolicyDocument = PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "QueuePolicy", [{
25
+ function createQueuePolicy(queue, name, opts) {
26
+ let queuePolicyDocument = queue.arn.apply(queueArn => {
27
+ let accountId = Adapter_Helpers$ReventlessAws.accountIdOfQueueArn(queueArn);
28
+ return PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "QueuePolicy", [{
31
29
  Sid: "AllowReceiveSnsEvents",
32
30
  Principal: {
33
31
  Service: [AWS$ReventlessAws.SNS.principal]
@@ -46,11 +44,11 @@ function createQueuePolicy(queue, name, _resources, opts) {
46
44
  ]])
47
45
  }
48
46
  }]));
49
- new (Aws.sqs.QueuePolicy)(name, {
50
- policy: queuePolicyDocument,
51
- queueUrl: param[1]
52
- }, opts);
53
47
  });
48
+ new (Aws.sqs.QueuePolicy)(name, {
49
+ policy: queuePolicyDocument,
50
+ queueUrl: queue.id
51
+ }, opts);
54
52
  }
55
53
 
56
54
  function subscribeQueue2SnsTopic(queue, name, resources, opts) {
@@ -62,18 +60,21 @@ function subscribeQueue2SnsTopic(queue, name, resources, opts) {
62
60
  }
63
61
 
64
62
  function connectSqsQueue2SnsTopics(queue, name, eventTopics, opts) {
63
+ createQueuePolicy(queue, name, opts);
65
64
  Pulumi.all([
66
65
  toResources(eventTopics),
67
66
  queue.id
68
67
  ]).apply(param => {
69
68
  let eventTopicResources = param[0];
70
69
  log.debug("EventCollector", undefined, `connectSqsQueue2SnsTopics ` + param[1] + `: ` + eventTopicResources.length.toString() + ` topic resource(s)`);
71
- let snsResources = Adapter_Helpers$ReventlessAws.snsResources(eventTopicResources);
72
- createQueuePolicy(queue, name, snsResources, opts);
73
- subscribeQueue2SnsTopic(queue, name, snsResources, opts);
70
+ subscribeQueue2SnsTopic(queue, name, Adapter_Helpers$ReventlessAws.snsResources(eventTopicResources), opts);
74
71
  });
75
72
  }
76
73
 
74
+ function esmTags(name, esmName) {
75
+ return AWS_Tags$ReventlessAws.make(esmName, EventCollector$ReventlessCore.componentType, "EventSourceMapping", undefined, name, undefined, undefined, undefined);
76
+ }
77
+
77
78
  function connectLambda(lambda, name, lambdaRole, queues, eventTopics, resources, opts) {
78
79
  Pulumi.all([
79
80
  toResources(eventTopics),
@@ -147,11 +148,11 @@ function connectLambda(lambda, name, lambdaRole, queues, eventTopics, resources,
147
148
  ])),
148
149
  role: lambdaRole.id
149
150
  }, opts);
150
- dynamoDbStreamResources.map(dynamoDbStreamResource => Util_EventSourceMapping$ReventlessAws.subscribe(undefined, lambda, name, dynamoDbStreamResource.name, AdapterDeploytime$ReventlessCore.resolvedToResource(dynamoDbStreamResource), opts));
151
+ dynamoDbStreamResources.map(dynamoDbStreamResource => Util_EventSourceMapping$ReventlessAws.subscribe(undefined, lambda, name, dynamoDbStreamResource.name, AdapterDeploytime$ReventlessCore.resolvedToResource(dynamoDbStreamResource), esmTags(name, Util$ReventlessCore.baseName(dynamoDbStreamResource.name) + ("2" + name)), opts));
151
152
  });
152
153
  return queues.map((queue, idx) => {
153
154
  let esmName = queues.length > 1 ? name + `Sqs` + idx.toString() : name;
154
- return Util_EventSourceMapping$ReventlessAws.subscribeSqs(lambda, esmName, queue, undefined, opts);
155
+ return Util_EventSourceMapping$ReventlessAws.subscribeSqs(lambda, esmName, queue, undefined, esmTags(name, esmName), opts);
155
156
  });
156
157
  }
157
158
 
@@ -161,6 +162,7 @@ export {
161
162
  createQueuePolicy,
162
163
  subscribeQueue2SnsTopic,
163
164
  connectSqsQueue2SnsTopics,
165
+ esmTags,
164
166
  connectLambda,
165
167
  }
166
168
  /* log Not a pure module */
@@ -108,44 +108,52 @@ let make = (
108
108
  )
109
109
  ->Pulumi.Output.asInput,
110
110
  sqsManagedSseEnabled: false->Pulumi.Input.make,
111
+ tags: AWS.Tags.make(
112
+ ~name=name ++ "EventLogSubQueue",
113
+ ~kind=ReventlessCore.EventTopic.componentType,
114
+ ~role=EventLogSubscription,
115
+ ~component=name,
116
+ ),
111
117
  },
112
118
  ~opts,
113
119
  )
114
120
 
115
121
  // SQS queue policy — allow SNS to send messages
116
122
  let snsResource = eventTopicOutputs.resources->Array.getUnsafe(0)
117
- let _ =
118
- (queue.arn, queue.id, snsResource.urn)
119
- ->Pulumi.Output.all3
120
- ->Pulumi.Output.apply(((queueArn, queueId, snsUrn)) => {
123
+ // The document is a *value* derived from resolved ARNs, so it is built in an
124
+ // apply. The QueuePolicy itself stays outside: passing `queue.id` as an Output
125
+ // is what registers the policy -> queue dependency, without which Pulumi has
126
+ // no ordering constraint and can delete the queue first on a replacement,
127
+ // leaving the policy's delete polling a queue that no longer exists.
128
+ let queuePolicyDocument =
129
+ (queue.arn, snsResource.urn)
130
+ ->Pulumi.Output.all2
131
+ ->Pulumi.Output.apply(((queueArn, snsUrn)) => {
121
132
  open PolicyDocument
122
- let _queuePolicy = SQS.QueuePolicy.make(
123
- ~name=name ++ "EventLogSubQueuePolicy",
124
- ~args={
125
- queueUrl: queueId->Pulumi.Input.make,
126
- policy: PolicyDocument.make(
127
- ~id=name ++ "EventLogSubQueuePolicy",
128
- ~statements=[
129
- {
130
- sid: "AllowSNSSend",
131
- principal: Principals({service: PrincipalIds([AWS.SNS.principal])}),
132
- effect: Allow,
133
- actions: Actions(["sqs:SendMessage"]),
134
- resources: Resource(queueArn),
135
- conditions: {
136
- arnEquals: [
137
- ("aws:SourceArn", ConditionValues([snsUrn])),
138
- ]->Dict.fromArray,
139
- },
140
- },
141
- ],
142
- )
143
- ->PolicyDocument.toJsonString
144
- ->Pulumi.Input.make,
145
- },
146
- ~opts=Some(opts),
147
- )
133
+ PolicyDocument.make(
134
+ ~id=name ++ "EventLogSubQueuePolicy",
135
+ ~statements=[
136
+ {
137
+ sid: "AllowSNSSend",
138
+ principal: Principals({service: PrincipalIds([AWS.SNS.principal])}),
139
+ effect: Allow,
140
+ actions: Actions(["sqs:SendMessage"]),
141
+ resources: Resource(queueArn),
142
+ conditions: {
143
+ arnEquals: [("aws:SourceArn", ConditionValues([snsUrn]))]->Dict.fromArray,
144
+ },
145
+ },
146
+ ],
147
+ )->PolicyDocument.toJsonString
148
148
  })
149
+ let _queuePolicy = SQS.QueuePolicy.make(
150
+ ~name=name ++ "EventLogSubQueuePolicy",
151
+ ~args={
152
+ queueUrl: queue.id->Pulumi.Output.asInput,
153
+ policy: queuePolicyDocument->Pulumi.Output.asInput,
154
+ },
155
+ ~opts=Some(opts),
156
+ )
149
157
 
150
158
  // SNS → SQS subscription (raw delivery so body IS the event JSON)
151
159
  let _subscription = Util_SQS.subscribeToSnsTopic(
@@ -264,6 +272,12 @@ let make = (
264
272
  ~lambda=lambdaOutput,
265
273
  ~name=name ++ "EventLogSubEventSourceMapping",
266
274
  ~queue,
275
+ ~tags=AWS.Tags.make(
276
+ ~name=name ++ "EventLogSubEventSourceMapping",
277
+ ~kind=ReventlessCore.EventTopic.componentType,
278
+ ~role=EventSourceMapping,
279
+ ~component=name,
280
+ ),
267
281
  ~opts,
268
282
  )
269
283
  }
@@ -81,34 +81,33 @@ export async function handler(event) {
81
81
  function make(name, topicName, eventTopicOutputs, eventsApi, opts) {
82
82
  let queue = new (Aws.sqs.Queue)(name + "EventLogSubQueue", {
83
83
  redrivePolicy: Util_DeadLetterQueue$ReventlessAws.queue.arn.apply(dlqArn => SQS_Queue$PulumiAws.RedrivePolicy.make(dlqArn, 5)),
84
+ tags: AWS_Tags$ReventlessAws.make(name + "EventLogSubQueue", EventTopic$ReventlessCore.componentType, "EventLogSubscription", undefined, name, undefined, undefined, undefined),
84
85
  visibilityTimeoutSeconds: 60,
85
86
  sqsManagedSseEnabled: false
86
87
  }, opts);
87
88
  let snsResource = eventTopicOutputs.resources[0];
88
- Pulumi.all([
89
+ let queuePolicyDocument = Pulumi.all([
89
90
  queue.arn,
90
- queue.id,
91
91
  snsResource.urn
92
- ]).apply(param => {
93
- new (Aws.sqs.QueuePolicy)(name + "EventLogSubQueuePolicy", {
94
- policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "EventLogSubQueuePolicy", [{
95
- Sid: "AllowSNSSend",
96
- Principal: {
97
- Service: [AWS$ReventlessAws.SNS.principal]
98
- },
99
- Effect: "Allow",
100
- Action: ["sqs:SendMessage"],
101
- Resource: param[0],
102
- Condition: {
103
- ArnEquals: Object.fromEntries([[
104
- "aws:SourceArn",
105
- [param[2]]
106
- ]])
107
- }
108
- }])),
109
- queueUrl: param[1]
110
- }, opts);
111
- });
92
+ ]).apply(param => PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "EventLogSubQueuePolicy", [{
93
+ Sid: "AllowSNSSend",
94
+ Principal: {
95
+ Service: [AWS$ReventlessAws.SNS.principal]
96
+ },
97
+ Effect: "Allow",
98
+ Action: ["sqs:SendMessage"],
99
+ Resource: param[0],
100
+ Condition: {
101
+ ArnEquals: Object.fromEntries([[
102
+ "aws:SourceArn",
103
+ [param[1]]
104
+ ]])
105
+ }
106
+ }])));
107
+ new (Aws.sqs.QueuePolicy)(name + "EventLogSubQueuePolicy", {
108
+ policy: queuePolicyDocument,
109
+ queueUrl: queue.id
110
+ }, opts);
112
111
  Util_SQS$ReventlessAws.subscribeToSnsTopic(queue, name + "EventLogSub", name, snsResource, opts);
113
112
  let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "EventLogSubRole", Pulumi.output(AWS$ReventlessAws.Lambda.principal), AWS_Tags$ReventlessAws.make(name + "EventLogSubRole", EventTopic$ReventlessCore.componentType, "Identity", undefined, name, undefined, undefined, undefined), opts);
114
113
  Pulumi.all([
@@ -174,7 +173,7 @@ function make(name, topicName, eventTopicOutputs, eventsApi, opts) {
174
173
  sourceCodeHash: sourceCodeHash
175
174
  }, opts);
176
175
  let lambdaOutput = Pulumi.output(lambda);
177
- Util_EventSourceMapping$ReventlessAws.subscribeSqs(lambdaOutput, name + "EventLogSubEventSourceMapping", queue, undefined, opts);
176
+ Util_EventSourceMapping$ReventlessAws.subscribeSqs(lambdaOutput, name + "EventLogSubEventSourceMapping", queue, undefined, AWS_Tags$ReventlessAws.make(name + "EventLogSubEventSourceMapping", EventTopic$ReventlessCore.componentType, "EventSourceMapping", undefined, name, undefined, undefined, undefined), opts);
178
177
  }
179
178
 
180
179
  export {
@@ -51,6 +51,14 @@ let makeQueue = (
51
51
  )
52
52
  ->Pulumi.Output.asInput,
53
53
  sqsManagedSseEnabled: false->Pulumi.Input.make,
54
+ // Plugin substrate: one shared feed per plugin ("AllReadModelsFeed"), not a
55
+ // resource any single component owns.
56
+ tags: AWS.Tags.make(
57
+ ~name,
58
+ ~kind=ReventlessCore.ComponentType.Plugin,
59
+ ~role=EventCollector,
60
+ ~scope=Plugin,
61
+ ),
54
62
  },
55
63
  ~opts,
56
64
  )
@@ -69,7 +77,18 @@ let connect = (
69
77
  ~lambdaRole: PulumiAws.IAM.Role.t,
70
78
  ~opts: Pulumi.CustomResourceOptions.t,
71
79
  ) => {
72
- let _esm = Util_EventSourceMapping.subscribeSqs(~lambda, ~name, ~queue, ~opts)
80
+ let _esm = Util_EventSourceMapping.subscribeSqs(
81
+ ~lambda,
82
+ ~name,
83
+ ~queue,
84
+ ~tags=AWS.Tags.make(
85
+ ~name,
86
+ ~kind=ReventlessCore.ComponentType.Plugin,
87
+ ~role=EventSourceMapping,
88
+ ~scope=Plugin,
89
+ ),
90
+ ~opts,
91
+ )
73
92
  let _policy = queue.arn->Pulumi.Output.apply(queueArn => {
74
93
  open PulumiAws.PolicyDocument
75
94
  PulumiAws.IAM.RolePolicy.make(
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as Aws from "@pulumi/aws";
4
4
  import * as SQS_Queue$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/SQS/SQS_Queue.res.mjs";
5
+ import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
5
6
  import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
6
7
  import * as Util_DeadLetterQueue$ReventlessAws from "../../util/Util_DeadLetterQueue.res.mjs";
7
8
  import * as Util_EventSourceMapping$ReventlessAws from "../../util/Util_EventSourceMapping.res.mjs";
@@ -15,6 +16,7 @@ function getFeedQueues() {
15
16
  function makeQueue(name, scope, includeClassic, includeDcb, opts) {
16
17
  let queue = new (Aws.sqs.Queue)(name, {
17
18
  redrivePolicy: Util_DeadLetterQueue$ReventlessAws.queue.arn.apply(dlqArn => SQS_Queue$PulumiAws.RedrivePolicy.make(dlqArn, 5)),
19
+ tags: AWS_Tags$ReventlessAws.make(name, "Plugin", "EventCollector", "Plugin", undefined, undefined, undefined, undefined),
18
20
  visibilityTimeoutSeconds: 120,
19
21
  sqsManagedSseEnabled: false
20
22
  }, opts);
@@ -29,7 +31,7 @@ function makeQueue(name, scope, includeClassic, includeDcb, opts) {
29
31
  }
30
32
 
31
33
  function connect(name, queue, lambda, lambdaRole, opts) {
32
- Util_EventSourceMapping$ReventlessAws.subscribeSqs(lambda, name, queue, undefined, opts);
34
+ Util_EventSourceMapping$ReventlessAws.subscribeSqs(lambda, name, queue, undefined, AWS_Tags$ReventlessAws.make(name, "Plugin", "EventSourceMapping", "Plugin", undefined, undefined, undefined, undefined), opts);
33
35
  queue.arn.apply(queueArn => new (Aws.iam.RolePolicy)(name + `Receive`, {
34
36
  policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + `ReceivePolicy`, [{
35
37
  Sid: "AllowReceiveFeedQueue",
@@ -430,7 +430,11 @@ const importFromAsset = (() => {
430
430
  async function buildHandler() {
431
431
  const config = parseHandlerConfig(process.env["HANDLER_CONFIG"]);
432
432
  const pluginDefinition = loadPluginDefinition();
433
- const lambdaFunctionName = process.env["AWS_LAMBDA_FUNCTION_NAME"] || "unknown";
433
+ // Stack name, not the function name — see PluginExtensionPointEntryPoint for
434
+ // why the disconnect schedule's rule prefix must not carry a content hash.
435
+ // Both entry points instantiate the same admin Plugin EP module, so they have
436
+ // to derive this identically.
437
+ const environment = process.env["Environment"] || "unknown";
434
438
 
435
439
  // runtimeOps carries only what the admin EP mapping's callHandler actually
436
440
  // needs: sendMessageToChannel for ForwardCommand. Cross-plugin SNS
@@ -496,7 +500,7 @@ async function buildHandler() {
496
500
  // Admin's Plugin EP: invoke the Make functor with runtime ops.
497
501
  const epModule = mappingsMod.Make({
498
502
  runtimeOps,
499
- environment: lambdaFunctionName,
503
+ environment,
500
504
  // Connect-driven schema self-heal retired (Phase 4b) — the ApiSchemaPush
501
505
  // SideEffect on ApiFragmentRegistry events is the single schema writer.
502
506
  updateApiSchema: undefined,
@@ -37,10 +37,17 @@ function buildHandler() {
37
37
  // Instantiate Plugin EP mapping. updateApiSchema and manageSubscriptions are
38
38
  // admin-only hooks; this Lambda only handles incoming commands (Heartbeat,
39
39
  // ForwardCommand) so they stay undefined here.
40
- const lambdaFunctionName = process.env["AWS_LAMBDA_FUNCTION_NAME"] || "unknown";
40
+ //
41
+ // `environment` prefixes the disconnect schedule's EventBridge rule name, so it
42
+ // must be stable across deploys and unique per stack. The stack name is both;
43
+ // AWS_LAMBDA_FUNCTION_NAME is neither — it carries a content hash, so replacing
44
+ // this Lambda would orphan every outstanding rule the previous generation
45
+ // created. EventCollectorEntryPoint instantiates the same EP module and must
46
+ // agree, or one Lambda creates rules the other cannot delete.
47
+ const environment = process.env["Environment"] || "unknown";
41
48
  const pluginModule = pluginEPPluginMake({
42
49
  runtimeOps,
43
- environment: lambdaFunctionName,
50
+ environment,
44
51
  updateApiSchema: undefined,
45
52
  manageSubscriptions: undefined,
46
53
  });
@@ -27,15 +27,21 @@ let make: ReventlessCore.Runtime.environmentMaker<'event, context, 'result, part
27
27
  let opts =
28
28
  opts->Option.map(ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions)
29
29
 
30
+ // Legacy path (see above): no component kind reaches here because nothing calls
31
+ // it, so it is attributed to the Platform rather than to an invented domain kind.
32
+ let tags = AWS.Tags.make(~name, ~kind=ReventlessCore.ComponentType.Platform, ~role=Runtime, ~scope=Platform)
33
+
30
34
  let lambdaRole = IAM.Role.makeWithDefaultPolicy(
31
35
  ~name,
32
36
  ~servicePrincipal=AWS.Lambda.principal->Pulumi.Output.make,
37
+ ~tags=AWS.Tags.make(
38
+ ~name,
39
+ ~kind=ReventlessCore.ComponentType.Platform,
40
+ ~role=Identity,
41
+ ~scope=Platform,
42
+ ),
33
43
  ~opts?,
34
44
  )
35
-
36
- // Legacy path (see above): no component kind reaches here because nothing calls
37
- // it, so it is attributed to the Platform rather than to an invented domain kind.
38
- let tags = AWS.Tags.make(~name, ~kind=ReventlessCore.ComponentType.Platform, ~role=Runtime, ~scope=Platform)
39
45
  let lambda =
40
46
  handler->Pulumi.Output.apply(handler =>
41
47
  Lambda.CallbackFunction.make(
@@ -29,8 +29,8 @@ function make(name, handler, memorySizeOpt, timeoutOpt, opts) {
29
29
  let memorySize = memorySizeOpt !== undefined ? memorySizeOpt : 1024;
30
30
  let timeout = timeoutOpt !== undefined ? timeoutOpt : 30;
31
31
  let opts$1 = Stdlib_Option.map(opts, Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions);
32
- let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name, Pulumi.output(AWS$ReventlessAws.Lambda.principal), undefined, opts$1);
33
32
  let tags = AWS_Tags$ReventlessAws.make(name, "Platform", "Runtime", "Platform", undefined, undefined, undefined, undefined);
33
+ let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name, Pulumi.output(AWS$ReventlessAws.Lambda.principal), AWS_Tags$ReventlessAws.make(name, "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
34
34
  let lambda = handler.apply(handler => new (Aws.lambda.CallbackFunction)(name, Lambda$PulumiAws.CallbackFunction.Args.make(handler, lambdaRole, undefined, undefined, undefined, memorySize, timeout, undefined, undefined, undefined, tags, undefined), opts$1 !== undefined ? Primitive_option.valFromOption(opts$1) : undefined));
35
35
  return {
36
36
  parts: {
@@ -410,14 +410,21 @@ let finish = (
410
410
  // isolates the bad record across those retries instead of redelivering
411
411
  // the whole batch each time.
412
412
  entries->Array.forEach(entry => {
413
+ let esmName = entry.topicName ++ "Stream2" ++ name ++ "StateTopic"
413
414
  let _esm = EventSourceMapping.make(
414
- ~name=entry.topicName ++ "Stream2" ++ name ++ "StateTopic",
415
+ ~name=esmName,
415
416
  ~args={
416
417
  EventSourceMapping.functionName: lambda.arn->Pulumi.Output.asInput,
417
418
  eventSourceArn: entry.streamArn->Pulumi.Output.asInput,
418
419
  startingPosition: LATEST,
419
420
  maximumRetryAttempts: 3,
420
421
  bisectBatchOnFunctionError: true,
422
+ tags: AWS.Tags.make(
423
+ ~name=esmName,
424
+ ~kind=ReventlessCore.QueryDb.componentType,
425
+ ~role=EventSourceMapping,
426
+ ~component=name,
427
+ ),
421
428
  },
422
429
  ~opts=Some(opts),
423
430
  )
@@ -284,12 +284,14 @@ function finish(eventsApi, opts) {
284
284
  sourceCodeHash: sourceCodeHash
285
285
  }, opts);
286
286
  entries.forEach(entry => {
287
- new (Aws.lambda.EventSourceMapping)(entry.topicName + "Stream2" + name + "StateTopic", {
287
+ let esmName = entry.topicName + "Stream2" + name + "StateTopic";
288
+ new (Aws.lambda.EventSourceMapping)(esmName, {
288
289
  functionName: lambda.arn,
289
290
  bisectBatchOnFunctionError: true,
290
291
  eventSourceArn: entry.streamArn,
291
292
  maximumRetryAttempts: 3,
292
- startingPosition: "LATEST"
293
+ startingPosition: "LATEST",
294
+ tags: AWS_Tags$ReventlessAws.make(esmName, QueryDb$ReventlessCore.componentType, "EventSourceMapping", undefined, name, undefined, undefined, undefined)
293
295
  }, opts);
294
296
  });
295
297
  registry[key] = [];
@@ -209,6 +209,12 @@ export const handler = async (event) => {
209
209
  cpu: "1024"->Pulumi.Input.make,
210
210
  requiresCompatibilities: ["FARGATE"],
211
211
  networkMode: #awsvpc,
212
+ tags: AWS.Tags.make(
213
+ ~name,
214
+ ~kind=ReventlessCore.Cloner.componentType,
215
+ ~role=DataTransfer,
216
+ ~component=name,
217
+ ),
212
218
  },
213
219
  ~opts?,
214
220
  )