@openhi/constructs 0.0.105 → 0.0.106

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 (73) hide show
  1. package/lib/chunk-AGF3RAAZ.mjs +20 -0
  2. package/lib/chunk-AGF3RAAZ.mjs.map +1 -0
  3. package/lib/{chunk-BXEG7IOZ.mjs → chunk-AO3E22CS.mjs} +2 -2
  4. package/lib/{chunk-WNUH2WDZ.mjs → chunk-CHPEQRXU.mjs} +2 -2
  5. package/lib/chunk-JUNL76HF.mjs +428 -0
  6. package/lib/chunk-JUNL76HF.mjs.map +1 -0
  7. package/lib/chunk-L6UAP4KP.mjs +27 -0
  8. package/lib/chunk-L6UAP4KP.mjs.map +1 -0
  9. package/lib/{chunk-3QS3WKRC.mjs → chunk-LZOMFHX3.mjs} +9 -2
  10. package/lib/chunk-QMIOLLAS.mjs +531 -0
  11. package/lib/chunk-QMIOLLAS.mjs.map +1 -0
  12. package/lib/chunk-SYBADQXI.mjs +607 -0
  13. package/lib/chunk-SYBADQXI.mjs.map +1 -0
  14. package/lib/chunk-VXX4I3EF.mjs +19 -0
  15. package/lib/chunk-VXX4I3EF.mjs.map +1 -0
  16. package/lib/{chunk-36YCDLLA.mjs → chunk-VYDIGFIX.mjs} +75 -481
  17. package/lib/chunk-VYDIGFIX.mjs.map +1 -0
  18. package/lib/chunk-YU2HRNUP.mjs +33 -0
  19. package/lib/chunk-YU2HRNUP.mjs.map +1 -0
  20. package/lib/chunk-YZZDUJHI.mjs +37 -0
  21. package/lib/chunk-YZZDUJHI.mjs.map +1 -0
  22. package/lib/cors-options-lambda.handler.mjs +1 -1
  23. package/lib/data-store-postgres-replication.handler.mjs +1 -1
  24. package/lib/events-BfrkMoBD.d.mts +44 -0
  25. package/lib/events-BfrkMoBD.d.ts +44 -0
  26. package/lib/events-DGep6C7w.d.mts +207 -0
  27. package/lib/events-DGep6C7w.d.ts +207 -0
  28. package/lib/firehose-archive-transform.handler.mjs +1 -1
  29. package/lib/index.d.mts +417 -9
  30. package/lib/index.d.ts +663 -10
  31. package/lib/index.js +2400 -111
  32. package/lib/index.js.map +1 -1
  33. package/lib/index.mjs +781 -104
  34. package/lib/index.mjs.map +1 -1
  35. package/lib/openhi-context-CaBH8SFo.d.mts +39 -0
  36. package/lib/openhi-context-CaBH8SFo.d.ts +39 -0
  37. package/lib/platform-deploy-bridge.handler.d.mts +14 -0
  38. package/lib/platform-deploy-bridge.handler.d.ts +14 -0
  39. package/lib/platform-deploy-bridge.handler.js +762 -0
  40. package/lib/platform-deploy-bridge.handler.js.map +1 -0
  41. package/lib/platform-deploy-bridge.handler.mjs +134 -0
  42. package/lib/platform-deploy-bridge.handler.mjs.map +1 -0
  43. package/lib/post-authentication.handler.mjs +1 -1
  44. package/lib/post-confirmation.handler.mjs +1 -1
  45. package/lib/pre-token-generation.handler.js +76 -31
  46. package/lib/pre-token-generation.handler.js.map +1 -1
  47. package/lib/pre-token-generation.handler.mjs +5 -3
  48. package/lib/pre-token-generation.handler.mjs.map +1 -1
  49. package/lib/provision-default-workspace.handler.js +86 -41
  50. package/lib/provision-default-workspace.handler.js.map +1 -1
  51. package/lib/provision-default-workspace.handler.mjs +6 -4
  52. package/lib/provision-default-workspace.handler.mjs.map +1 -1
  53. package/lib/rest-api-lambda.handler.js +114 -59
  54. package/lib/rest-api-lambda.handler.js.map +1 -1
  55. package/lib/rest-api-lambda.handler.mjs +40 -61
  56. package/lib/rest-api-lambda.handler.mjs.map +1 -1
  57. package/lib/seed-demo-data.handler.d.mts +107 -0
  58. package/lib/seed-demo-data.handler.d.ts +107 -0
  59. package/lib/seed-demo-data.handler.js +2037 -0
  60. package/lib/seed-demo-data.handler.js.map +1 -0
  61. package/lib/seed-demo-data.handler.mjs +23 -0
  62. package/lib/seed-demo-data.handler.mjs.map +1 -0
  63. package/lib/seed-system-data.handler.d.mts +64 -0
  64. package/lib/seed-system-data.handler.d.ts +64 -0
  65. package/lib/seed-system-data.handler.js +1631 -0
  66. package/lib/seed-system-data.handler.js.map +1 -0
  67. package/lib/seed-system-data.handler.mjs +135 -0
  68. package/lib/seed-system-data.handler.mjs.map +1 -0
  69. package/package.json +4 -2
  70. package/lib/chunk-36YCDLLA.mjs.map +0 -1
  71. /package/lib/{chunk-BXEG7IOZ.mjs.map → chunk-AO3E22CS.mjs.map} +0 -0
  72. /package/lib/{chunk-WNUH2WDZ.mjs.map → chunk-CHPEQRXU.mjs.map} +0 -0
  73. /package/lib/{chunk-3QS3WKRC.mjs.map → chunk-LZOMFHX3.mjs.map} +0 -0
package/lib/index.js CHANGED
@@ -90,9 +90,611 @@ var require_lib = __commonJS({
90
90
  }
91
91
  });
92
92
 
93
+ // ../workflows/lib/envelope-version.js
94
+ var require_envelope_version = __commonJS({
95
+ "../workflows/lib/envelope-version.js"(exports2) {
96
+ "use strict";
97
+ Object.defineProperty(exports2, "__esModule", { value: true });
98
+ exports2.ENVELOPE_VERSION = void 0;
99
+ exports2.isSupportedEnvelopeVersion = isSupportedEnvelopeVersion;
100
+ exports2.ENVELOPE_VERSION = "1.0";
101
+ var ENVELOPE_VERSION_PATTERN = /^\d+\.\d+$/;
102
+ var MIN_SUPPORTED_MAJOR = 1;
103
+ var MAX_SUPPORTED_MAJOR = 1;
104
+ function isSupportedEnvelopeVersion(version) {
105
+ if (!ENVELOPE_VERSION_PATTERN.test(version)) {
106
+ return false;
107
+ }
108
+ const major = Number.parseInt(version.split(".")[0], 10);
109
+ return major >= MIN_SUPPORTED_MAJOR && major <= MAX_SUPPORTED_MAJOR;
110
+ }
111
+ }
112
+ });
113
+
114
+ // ../workflows/lib/envelope.js
115
+ var require_envelope = __commonJS({
116
+ "../workflows/lib/envelope.js"(exports2) {
117
+ "use strict";
118
+ Object.defineProperty(exports2, "__esModule", { value: true });
119
+ exports2.MissingActorContextError = void 0;
120
+ exports2.isWorkflowUserActor = isWorkflowUserActor;
121
+ exports2.isWorkflowSystemActor = isWorkflowSystemActor;
122
+ exports2.workflowUserActorFromClaims = workflowUserActorFromClaims;
123
+ function isWorkflowUserActor(actor) {
124
+ return actor.ohi_uid !== void 0;
125
+ }
126
+ function isWorkflowSystemActor(actor) {
127
+ return actor.system !== void 0;
128
+ }
129
+ function workflowUserActorFromClaims(claims) {
130
+ if (claims.ohi_tid === void 0 || claims.ohi_wid === void 0) {
131
+ throw new MissingActorContextError("workflowUserActorFromClaims: ohi_tid and ohi_wid are required on the workflow user-actor; the caller's JWT is missing one or both. Use a system-actor for pre-provisioning bootstrap workflows.");
132
+ }
133
+ return {
134
+ ohi_tid: claims.ohi_tid,
135
+ ohi_wid: claims.ohi_wid,
136
+ ohi_uid: claims.ohi_uid,
137
+ ohi_uname: claims.ohi_uname
138
+ };
139
+ }
140
+ var MissingActorContextError = class extends Error {
141
+ /** @param message - human-readable description of the missing claim. */
142
+ constructor(message) {
143
+ super(message);
144
+ this.name = "MissingActorContextError";
145
+ }
146
+ };
147
+ exports2.MissingActorContextError = MissingActorContextError;
148
+ }
149
+ });
150
+
151
+ // ../workflows/lib/sources.js
152
+ var require_sources = __commonJS({
153
+ "../workflows/lib/sources.js"(exports2) {
154
+ "use strict";
155
+ Object.defineProperty(exports2, "__esModule", { value: true });
156
+ exports2.DEFAULT_BUS_NAME_BY_SOURCE = exports2.OPENHI_OPS_SOURCE = exports2.OPENHI_DATA_SOURCE = exports2.OPENHI_CONTROL_SOURCE = void 0;
157
+ exports2.OPENHI_CONTROL_SOURCE = "openhi.control";
158
+ exports2.OPENHI_DATA_SOURCE = "openhi.data";
159
+ exports2.OPENHI_OPS_SOURCE = "openhi.ops";
160
+ exports2.DEFAULT_BUS_NAME_BY_SOURCE = {
161
+ [exports2.OPENHI_CONTROL_SOURCE]: "openhi-control-event-bus",
162
+ [exports2.OPENHI_DATA_SOURCE]: "openhi-data-event-bus",
163
+ [exports2.OPENHI_OPS_SOURCE]: "openhi-ops-event-bus"
164
+ };
165
+ }
166
+ });
167
+
168
+ // ../workflows/lib/detail-types/registry.js
169
+ var require_registry = __commonJS({
170
+ "../workflows/lib/detail-types/registry.js"(exports2) {
171
+ "use strict";
172
+ Object.defineProperty(exports2, "__esModule", { value: true });
173
+ exports2.InvalidDetailTypeRegistrationError = void 0;
174
+ exports2.defineDetailType = defineDetailType;
175
+ exports2.isWellFormedDetailType = isWellFormedDetailType;
176
+ function defineDetailType(entry) {
177
+ if (!isWellFormedDetailType(entry.detailType)) {
178
+ throw new InvalidDetailTypeRegistrationError(`Detail-type "${entry.detailType}" does not match the platform-wide format <area>.<event>.v<integer>. See TR-016 \xA7Open Items #2.`);
179
+ }
180
+ return entry;
181
+ }
182
+ var DETAIL_TYPE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*\.[a-z0-9]+(?:-[a-z0-9]+)*\.v\d+$/;
183
+ function isWellFormedDetailType(detailType) {
184
+ return DETAIL_TYPE_PATTERN.test(detailType);
185
+ }
186
+ var InvalidDetailTypeRegistrationError = class extends Error {
187
+ /** @param message - human-readable description of the violation. */
188
+ constructor(message) {
189
+ super(message);
190
+ this.name = "InvalidDetailTypeRegistrationError";
191
+ }
192
+ };
193
+ exports2.InvalidDetailTypeRegistrationError = InvalidDetailTypeRegistrationError;
194
+ }
195
+ });
196
+
197
+ // ../workflows/lib/detail-types/platform.js
198
+ var require_platform = __commonJS({
199
+ "../workflows/lib/detail-types/platform.js"(exports2) {
200
+ "use strict";
201
+ Object.defineProperty(exports2, "__esModule", { value: true });
202
+ exports2.PlatformSystemDataSeededV1 = exports2.PlatformDeploymentCompletedV1 = void 0;
203
+ var sources_1 = require_sources();
204
+ var registry_1 = require_registry();
205
+ exports2.PlatformDeploymentCompletedV1 = (0, registry_1.defineDetailType)({
206
+ detailType: "platform.deployment-completed.v1",
207
+ source: sources_1.OPENHI_CONTROL_SOURCE,
208
+ dedupRequired: true
209
+ });
210
+ exports2.PlatformSystemDataSeededV1 = (0, registry_1.defineDetailType)({
211
+ detailType: "platform.system-data-seeded.v1",
212
+ source: sources_1.OPENHI_CONTROL_SOURCE,
213
+ dedupRequired: true
214
+ });
215
+ }
216
+ });
217
+
218
+ // ../workflows/lib/detail-types/index.js
219
+ var require_detail_types = __commonJS({
220
+ "../workflows/lib/detail-types/index.js"(exports2) {
221
+ "use strict";
222
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
223
+ if (k2 === void 0) k2 = k;
224
+ var desc = Object.getOwnPropertyDescriptor(m, k);
225
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
226
+ desc = { enumerable: true, get: function() {
227
+ return m[k];
228
+ } };
229
+ }
230
+ Object.defineProperty(o, k2, desc);
231
+ }) : (function(o, m, k, k2) {
232
+ if (k2 === void 0) k2 = k;
233
+ o[k2] = m[k];
234
+ }));
235
+ var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
236
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
237
+ };
238
+ Object.defineProperty(exports2, "__esModule", { value: true });
239
+ __exportStar(require_platform(), exports2);
240
+ __exportStar(require_registry(), exports2);
241
+ }
242
+ });
243
+
244
+ // ../workflows/lib/publisher.js
245
+ var require_publisher = __commonJS({
246
+ "../workflows/lib/publisher.js"(exports2) {
247
+ "use strict";
248
+ Object.defineProperty(exports2, "__esModule", { value: true });
249
+ exports2.WorkflowPublishError = void 0;
250
+ exports2.workflowsClient = workflowsClient;
251
+ exports2.publishWorkflowEvent = publishWorkflowEvent;
252
+ var node_crypto_1 = require("crypto");
253
+ var client_eventbridge_1 = require("@aws-sdk/client-eventbridge");
254
+ var envelope_version_1 = require_envelope_version();
255
+ var sources_1 = require_sources();
256
+ function workflowsClient(bridge, options = {}) {
257
+ return {
258
+ publish: (entry, payload, ctx) => publishWorkflowEvent(bridge, entry, payload, ctx, options)
259
+ };
260
+ }
261
+ async function publishWorkflowEvent(bridge, entry, payload, ctx, options = {}) {
262
+ const eventIdGenerator = options.eventIdGenerator ?? (() => (0, node_crypto_1.randomUUID)());
263
+ const correlationIdGenerator = options.correlationIdGenerator ?? (() => (0, node_crypto_1.randomUUID)());
264
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
265
+ const envelope = {
266
+ eventId: eventIdGenerator(),
267
+ attempt: 1,
268
+ correlationId: ctx.correlationId ?? correlationIdGenerator(),
269
+ causationId: ctx.causationId ?? null,
270
+ actor: ctx.actor,
271
+ occurredAt: now().toISOString(),
272
+ envelopeVersion: envelope_version_1.ENVELOPE_VERSION,
273
+ payload
274
+ };
275
+ const busName = options.busNameByPlane?.[entry.source] ?? sources_1.DEFAULT_BUS_NAME_BY_SOURCE[entry.source];
276
+ const result = await bridge.send(new client_eventbridge_1.PutEventsCommand({
277
+ Entries: [
278
+ {
279
+ EventBusName: busName,
280
+ Source: entry.source,
281
+ DetailType: entry.detailType,
282
+ Detail: JSON.stringify(envelope)
283
+ }
284
+ ]
285
+ }));
286
+ if ((result.FailedEntryCount ?? 0) > 0) {
287
+ const first = result.Entries?.[0];
288
+ throw new WorkflowPublishError(`EventBridge rejected ${entry.detailType} publish on bus ${busName}: ${first?.ErrorCode ?? "unknown"} \u2014 ${first?.ErrorMessage ?? "no error message"}`);
289
+ }
290
+ return { eventId: envelope.eventId };
291
+ }
292
+ var WorkflowPublishError = class extends Error {
293
+ /** @param message - human-readable description of the failed publish. */
294
+ constructor(message) {
295
+ super(message);
296
+ this.name = "WorkflowPublishError";
297
+ }
298
+ };
299
+ exports2.WorkflowPublishError = WorkflowPublishError;
300
+ }
301
+ });
302
+
303
+ // ../workflows/lib/consumer.js
304
+ var require_consumer = __commonJS({
305
+ "../workflows/lib/consumer.js"(exports2) {
306
+ "use strict";
307
+ Object.defineProperty(exports2, "__esModule", { value: true });
308
+ exports2.UnsupportedEnvelopeVersionError = exports2.InvalidWorkflowEventError = void 0;
309
+ exports2.parseWorkflowEvent = parseWorkflowEvent2;
310
+ var envelope_version_1 = require_envelope_version();
311
+ function parseWorkflowEvent2(event, expected) {
312
+ if (event.source !== expected.source) {
313
+ throw new InvalidWorkflowEventError(`EventBridge source "${event.source}" does not match expected detail-type's source "${expected.source}".`);
314
+ }
315
+ if (event["detail-type"] !== expected.detailType) {
316
+ throw new InvalidWorkflowEventError(`EventBridge detail-type "${event["detail-type"]}" does not match expected "${expected.detailType}".`);
317
+ }
318
+ const candidate = asEnvelopeCandidate(event.detail);
319
+ if (!(0, envelope_version_1.isSupportedEnvelopeVersion)(candidate.envelopeVersion)) {
320
+ throw new UnsupportedEnvelopeVersionError(`Envelope version "${candidate.envelopeVersion}" is outside the SDK's supported range.`);
321
+ }
322
+ const envelope = {
323
+ eventId: candidate.eventId,
324
+ attempt: candidate.attempt,
325
+ correlationId: candidate.correlationId,
326
+ causationId: candidate.causationId,
327
+ actor: candidate.actor,
328
+ occurredAt: candidate.occurredAt,
329
+ envelopeVersion: candidate.envelopeVersion,
330
+ payload: candidate.payload
331
+ };
332
+ return {
333
+ envelope,
334
+ dedupKey: { eventId: envelope.eventId, attempt: envelope.attempt }
335
+ };
336
+ }
337
+ function asEnvelopeCandidate(detail) {
338
+ if (detail === null || typeof detail !== "object") {
339
+ throw new InvalidWorkflowEventError("EventBridge detail is not a non-null object.");
340
+ }
341
+ const obj = detail;
342
+ assertString(obj, "eventId");
343
+ assertPositiveInteger(obj, "attempt");
344
+ assertString(obj, "correlationId");
345
+ assertCausationId(obj);
346
+ assertActor(obj);
347
+ assertString(obj, "occurredAt");
348
+ assertString(obj, "envelopeVersion");
349
+ if (!("payload" in obj)) {
350
+ throw new InvalidWorkflowEventError("Envelope is missing required field: payload.");
351
+ }
352
+ return obj;
353
+ }
354
+ function assertString(obj, field) {
355
+ const value = obj[field];
356
+ if (typeof value !== "string" || value.length === 0) {
357
+ throw new InvalidWorkflowEventError(`Envelope field "${field}" must be a non-empty string.`);
358
+ }
359
+ }
360
+ function assertPositiveInteger(obj, field) {
361
+ const value = obj[field];
362
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
363
+ throw new InvalidWorkflowEventError(`Envelope field "${field}" must be a 1-indexed integer.`);
364
+ }
365
+ }
366
+ function assertCausationId(obj) {
367
+ if (!("causationId" in obj)) {
368
+ throw new InvalidWorkflowEventError("Envelope is missing required field: causationId.");
369
+ }
370
+ const value = obj.causationId;
371
+ if (value !== null && (typeof value !== "string" || value.length === 0)) {
372
+ throw new InvalidWorkflowEventError('Envelope field "causationId" must be a non-empty string or null.');
373
+ }
374
+ }
375
+ function assertActor(obj) {
376
+ const actor = obj.actor;
377
+ if (actor === null || typeof actor !== "object") {
378
+ throw new InvalidWorkflowEventError('Envelope field "actor" must be an object.');
379
+ }
380
+ const actorObj = actor;
381
+ const isUserActor = typeof actorObj.ohi_uid === "string" && typeof actorObj.ohi_uname === "string" && typeof actorObj.ohi_tid === "string" && typeof actorObj.ohi_wid === "string";
382
+ const isSystemActor = typeof actorObj.system === "string";
383
+ if (!isUserActor && !isSystemActor) {
384
+ throw new InvalidWorkflowEventError('Envelope field "actor" must be either a user-actor (ohi_tid, ohi_wid, ohi_uid, ohi_uname) or a system-actor ({ system: string }).');
385
+ }
386
+ }
387
+ var InvalidWorkflowEventError = class extends Error {
388
+ /** @param message - human-readable description of the validation failure. */
389
+ constructor(message) {
390
+ super(message);
391
+ this.name = "InvalidWorkflowEventError";
392
+ }
393
+ };
394
+ exports2.InvalidWorkflowEventError = InvalidWorkflowEventError;
395
+ var UnsupportedEnvelopeVersionError = class extends Error {
396
+ /** @param message - human-readable description of the unsupported version. */
397
+ constructor(message) {
398
+ super(message);
399
+ this.name = "UnsupportedEnvelopeVersionError";
400
+ }
401
+ };
402
+ exports2.UnsupportedEnvelopeVersionError = UnsupportedEnvelopeVersionError;
403
+ }
404
+ });
405
+
406
+ // ../workflows/lib/dedup/env.js
407
+ var require_env = __commonJS({
408
+ "../workflows/lib/dedup/env.js"(exports2) {
409
+ "use strict";
410
+ Object.defineProperty(exports2, "__esModule", { value: true });
411
+ exports2.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = exports2.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = exports2.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR = void 0;
412
+ exports2.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR = "OPENHI_WORKFLOW_DEDUP_TABLE_NAME";
413
+ exports2.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = 14 * 24 * 60 * 60;
414
+ exports2.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = 64;
415
+ }
416
+ });
417
+
418
+ // ../workflows/lib/dedup/workflow-dedup-client.js
419
+ var require_workflow_dedup_client = __commonJS({
420
+ "../workflows/lib/dedup/workflow-dedup-client.js"(exports2) {
421
+ "use strict";
422
+ Object.defineProperty(exports2, "__esModule", { value: true });
423
+ exports2.WorkflowDedupInvalidInputError = exports2.WorkflowDedupTableNameMissingError = void 0;
424
+ exports2.workflowDedupClient = workflowDedupClient2;
425
+ exports2.recordIfAbsent = recordIfAbsent;
426
+ exports2.markFailed = markFailed;
427
+ exports2.encodeSortKey = encodeSortKey;
428
+ var client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
429
+ var env_1 = require_env();
430
+ function workflowDedupClient2(dynamodb, options = {}) {
431
+ return {
432
+ recordIfAbsent: (input) => recordIfAbsent(dynamodb, input, options),
433
+ markFailed: (input) => markFailed(dynamodb, input, options)
434
+ };
435
+ }
436
+ async function recordIfAbsent(dynamodb, input, options = {}) {
437
+ assertConsumerName(input.consumerName);
438
+ assertPositiveInteger(input.attempt, "attempt");
439
+ const ttlSeconds = input.ttlSeconds ?? options.defaultTtlSeconds ?? env_1.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS;
440
+ if (!Number.isInteger(ttlSeconds) || ttlSeconds <= 0) {
441
+ throw new WorkflowDedupInvalidInputError(`ttlSeconds must be a positive integer; got ${ttlSeconds}.`);
442
+ }
443
+ const tableName = resolveTableName(options.tableName);
444
+ const now = (options.now ?? defaultNow)();
445
+ const sk = encodeSortKey(input.eventId, input.attempt);
446
+ const expiresAt = Math.floor(now.getTime() / 1e3) + ttlSeconds;
447
+ try {
448
+ await dynamodb.send(new client_dynamodb_1.PutItemCommand({
449
+ TableName: tableName,
450
+ Item: {
451
+ consumerName: { S: input.consumerName },
452
+ sk: { S: sk },
453
+ eventId: { S: input.eventId },
454
+ attempt: { N: String(input.attempt) },
455
+ recordedAt: { S: now.toISOString() },
456
+ expiresAt: { N: String(expiresAt) }
457
+ },
458
+ ConditionExpression: "attribute_not_exists(consumerName) AND attribute_not_exists(sk)"
459
+ }));
460
+ return { recorded: true };
461
+ } catch (err) {
462
+ if (err instanceof client_dynamodb_1.ConditionalCheckFailedException) {
463
+ return { recorded: false, alreadyProcessed: true };
464
+ }
465
+ throw err;
466
+ }
467
+ }
468
+ async function markFailed(dynamodb, input, options = {}) {
469
+ assertConsumerName(input.consumerName);
470
+ assertPositiveInteger(input.attempt, "attempt");
471
+ if (input.reason.length === 0) {
472
+ throw new WorkflowDedupInvalidInputError("reason must be non-empty.");
473
+ }
474
+ const tableName = resolveTableName(options.tableName);
475
+ const now = (options.now ?? defaultNow)();
476
+ const sk = encodeSortKey(input.eventId, input.attempt);
477
+ await dynamodb.send(new client_dynamodb_1.UpdateItemCommand({
478
+ TableName: tableName,
479
+ Key: {
480
+ consumerName: { S: input.consumerName },
481
+ sk: { S: sk }
482
+ },
483
+ UpdateExpression: "SET #failed = :failed, #failureReason = :reason, #failedAt = :failedAt",
484
+ ExpressionAttributeNames: {
485
+ "#failed": "failed",
486
+ "#failureReason": "failureReason",
487
+ "#failedAt": "failedAt"
488
+ },
489
+ ExpressionAttributeValues: {
490
+ ":failed": { BOOL: true },
491
+ ":reason": { S: input.reason },
492
+ ":failedAt": { S: now.toISOString() }
493
+ }
494
+ }));
495
+ }
496
+ function encodeSortKey(eventId, attempt) {
497
+ if (eventId.length === 0) {
498
+ throw new WorkflowDedupInvalidInputError("eventId must be non-empty.");
499
+ }
500
+ return `${eventId}#${attempt}`;
501
+ }
502
+ function resolveTableName(explicit) {
503
+ const name = explicit ?? process.env[env_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR];
504
+ if (!name) {
505
+ throw new WorkflowDedupTableNameMissingError(`Workflow dedup table name not set. Pass options.tableName or set ${env_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR}.`);
506
+ }
507
+ return name;
508
+ }
509
+ function assertConsumerName(consumerName) {
510
+ if (consumerName.length === 0) {
511
+ throw new WorkflowDedupInvalidInputError("consumerName must be non-empty.");
512
+ }
513
+ if (consumerName.length > env_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH) {
514
+ throw new WorkflowDedupInvalidInputError(`consumerName must be \u2264${env_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH} chars; got ${consumerName.length}.`);
515
+ }
516
+ if (/\s/.test(consumerName)) {
517
+ throw new WorkflowDedupInvalidInputError("consumerName must not contain whitespace.");
518
+ }
519
+ }
520
+ function assertPositiveInteger(value, field) {
521
+ if (!Number.isInteger(value) || value < 1) {
522
+ throw new WorkflowDedupInvalidInputError(`${field} must be a 1-indexed integer; got ${value}.`);
523
+ }
524
+ }
525
+ function defaultNow() {
526
+ return /* @__PURE__ */ new Date();
527
+ }
528
+ var WorkflowDedupTableNameMissingError = class extends Error {
529
+ /** @param message - human-readable description. */
530
+ constructor(message) {
531
+ super(message);
532
+ this.name = "WorkflowDedupTableNameMissingError";
533
+ }
534
+ };
535
+ exports2.WorkflowDedupTableNameMissingError = WorkflowDedupTableNameMissingError;
536
+ var WorkflowDedupInvalidInputError = class extends Error {
537
+ /** @param message - human-readable description. */
538
+ constructor(message) {
539
+ super(message);
540
+ this.name = "WorkflowDedupInvalidInputError";
541
+ }
542
+ };
543
+ exports2.WorkflowDedupInvalidInputError = WorkflowDedupInvalidInputError;
544
+ }
545
+ });
546
+
547
+ // ../workflows/lib/dedup/index.js
548
+ var require_dedup = __commonJS({
549
+ "../workflows/lib/dedup/index.js"(exports2) {
550
+ "use strict";
551
+ Object.defineProperty(exports2, "__esModule", { value: true });
552
+ exports2.workflowDedupClient = exports2.recordIfAbsent = exports2.markFailed = exports2.encodeSortKey = exports2.WorkflowDedupTableNameMissingError = exports2.WorkflowDedupInvalidInputError = exports2.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR = exports2.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = exports2.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = void 0;
553
+ var env_1 = require_env();
554
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS", { enumerable: true, get: function() {
555
+ return env_1.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS;
556
+ } });
557
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH", { enumerable: true, get: function() {
558
+ return env_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH;
559
+ } });
560
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR", { enumerable: true, get: function() {
561
+ return env_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR;
562
+ } });
563
+ var workflow_dedup_client_1 = require_workflow_dedup_client();
564
+ Object.defineProperty(exports2, "WorkflowDedupInvalidInputError", { enumerable: true, get: function() {
565
+ return workflow_dedup_client_1.WorkflowDedupInvalidInputError;
566
+ } });
567
+ Object.defineProperty(exports2, "WorkflowDedupTableNameMissingError", { enumerable: true, get: function() {
568
+ return workflow_dedup_client_1.WorkflowDedupTableNameMissingError;
569
+ } });
570
+ Object.defineProperty(exports2, "encodeSortKey", { enumerable: true, get: function() {
571
+ return workflow_dedup_client_1.encodeSortKey;
572
+ } });
573
+ Object.defineProperty(exports2, "markFailed", { enumerable: true, get: function() {
574
+ return workflow_dedup_client_1.markFailed;
575
+ } });
576
+ Object.defineProperty(exports2, "recordIfAbsent", { enumerable: true, get: function() {
577
+ return workflow_dedup_client_1.recordIfAbsent;
578
+ } });
579
+ Object.defineProperty(exports2, "workflowDedupClient", { enumerable: true, get: function() {
580
+ return workflow_dedup_client_1.workflowDedupClient;
581
+ } });
582
+ }
583
+ });
584
+
585
+ // ../workflows/lib/index.js
586
+ var require_lib2 = __commonJS({
587
+ "../workflows/lib/index.js"(exports2) {
588
+ "use strict";
589
+ Object.defineProperty(exports2, "__esModule", { value: true });
590
+ exports2.workflowDedupClient = exports2.recordIfAbsent = exports2.markFailed = exports2.encodeSortKey = exports2.WorkflowDedupTableNameMissingError = exports2.WorkflowDedupInvalidInputError = exports2.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR = exports2.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = exports2.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = exports2.parseWorkflowEvent = exports2.UnsupportedEnvelopeVersionError = exports2.InvalidWorkflowEventError = exports2.workflowsClient = exports2.publishWorkflowEvent = exports2.WorkflowPublishError = exports2.isWellFormedDetailType = exports2.defineDetailType = exports2.PlatformSystemDataSeededV1 = exports2.PlatformDeploymentCompletedV1 = exports2.InvalidDetailTypeRegistrationError = exports2.OPENHI_OPS_SOURCE = exports2.OPENHI_DATA_SOURCE = exports2.OPENHI_CONTROL_SOURCE = exports2.DEFAULT_BUS_NAME_BY_SOURCE = exports2.workflowUserActorFromClaims = exports2.isWorkflowUserActor = exports2.isWorkflowSystemActor = exports2.MissingActorContextError = exports2.isSupportedEnvelopeVersion = exports2.ENVELOPE_VERSION = void 0;
591
+ var envelope_version_1 = require_envelope_version();
592
+ Object.defineProperty(exports2, "ENVELOPE_VERSION", { enumerable: true, get: function() {
593
+ return envelope_version_1.ENVELOPE_VERSION;
594
+ } });
595
+ Object.defineProperty(exports2, "isSupportedEnvelopeVersion", { enumerable: true, get: function() {
596
+ return envelope_version_1.isSupportedEnvelopeVersion;
597
+ } });
598
+ var envelope_1 = require_envelope();
599
+ Object.defineProperty(exports2, "MissingActorContextError", { enumerable: true, get: function() {
600
+ return envelope_1.MissingActorContextError;
601
+ } });
602
+ Object.defineProperty(exports2, "isWorkflowSystemActor", { enumerable: true, get: function() {
603
+ return envelope_1.isWorkflowSystemActor;
604
+ } });
605
+ Object.defineProperty(exports2, "isWorkflowUserActor", { enumerable: true, get: function() {
606
+ return envelope_1.isWorkflowUserActor;
607
+ } });
608
+ Object.defineProperty(exports2, "workflowUserActorFromClaims", { enumerable: true, get: function() {
609
+ return envelope_1.workflowUserActorFromClaims;
610
+ } });
611
+ var sources_1 = require_sources();
612
+ Object.defineProperty(exports2, "DEFAULT_BUS_NAME_BY_SOURCE", { enumerable: true, get: function() {
613
+ return sources_1.DEFAULT_BUS_NAME_BY_SOURCE;
614
+ } });
615
+ Object.defineProperty(exports2, "OPENHI_CONTROL_SOURCE", { enumerable: true, get: function() {
616
+ return sources_1.OPENHI_CONTROL_SOURCE;
617
+ } });
618
+ Object.defineProperty(exports2, "OPENHI_DATA_SOURCE", { enumerable: true, get: function() {
619
+ return sources_1.OPENHI_DATA_SOURCE;
620
+ } });
621
+ Object.defineProperty(exports2, "OPENHI_OPS_SOURCE", { enumerable: true, get: function() {
622
+ return sources_1.OPENHI_OPS_SOURCE;
623
+ } });
624
+ var detail_types_1 = require_detail_types();
625
+ Object.defineProperty(exports2, "InvalidDetailTypeRegistrationError", { enumerable: true, get: function() {
626
+ return detail_types_1.InvalidDetailTypeRegistrationError;
627
+ } });
628
+ Object.defineProperty(exports2, "PlatformDeploymentCompletedV1", { enumerable: true, get: function() {
629
+ return detail_types_1.PlatformDeploymentCompletedV1;
630
+ } });
631
+ Object.defineProperty(exports2, "PlatformSystemDataSeededV1", { enumerable: true, get: function() {
632
+ return detail_types_1.PlatformSystemDataSeededV1;
633
+ } });
634
+ Object.defineProperty(exports2, "defineDetailType", { enumerable: true, get: function() {
635
+ return detail_types_1.defineDetailType;
636
+ } });
637
+ Object.defineProperty(exports2, "isWellFormedDetailType", { enumerable: true, get: function() {
638
+ return detail_types_1.isWellFormedDetailType;
639
+ } });
640
+ var publisher_1 = require_publisher();
641
+ Object.defineProperty(exports2, "WorkflowPublishError", { enumerable: true, get: function() {
642
+ return publisher_1.WorkflowPublishError;
643
+ } });
644
+ Object.defineProperty(exports2, "publishWorkflowEvent", { enumerable: true, get: function() {
645
+ return publisher_1.publishWorkflowEvent;
646
+ } });
647
+ Object.defineProperty(exports2, "workflowsClient", { enumerable: true, get: function() {
648
+ return publisher_1.workflowsClient;
649
+ } });
650
+ var consumer_1 = require_consumer();
651
+ Object.defineProperty(exports2, "InvalidWorkflowEventError", { enumerable: true, get: function() {
652
+ return consumer_1.InvalidWorkflowEventError;
653
+ } });
654
+ Object.defineProperty(exports2, "UnsupportedEnvelopeVersionError", { enumerable: true, get: function() {
655
+ return consumer_1.UnsupportedEnvelopeVersionError;
656
+ } });
657
+ Object.defineProperty(exports2, "parseWorkflowEvent", { enumerable: true, get: function() {
658
+ return consumer_1.parseWorkflowEvent;
659
+ } });
660
+ var dedup_1 = require_dedup();
661
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS", { enumerable: true, get: function() {
662
+ return dedup_1.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS;
663
+ } });
664
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH", { enumerable: true, get: function() {
665
+ return dedup_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH;
666
+ } });
667
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR", { enumerable: true, get: function() {
668
+ return dedup_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR;
669
+ } });
670
+ Object.defineProperty(exports2, "WorkflowDedupInvalidInputError", { enumerable: true, get: function() {
671
+ return dedup_1.WorkflowDedupInvalidInputError;
672
+ } });
673
+ Object.defineProperty(exports2, "WorkflowDedupTableNameMissingError", { enumerable: true, get: function() {
674
+ return dedup_1.WorkflowDedupTableNameMissingError;
675
+ } });
676
+ Object.defineProperty(exports2, "encodeSortKey", { enumerable: true, get: function() {
677
+ return dedup_1.encodeSortKey;
678
+ } });
679
+ Object.defineProperty(exports2, "markFailed", { enumerable: true, get: function() {
680
+ return dedup_1.markFailed;
681
+ } });
682
+ Object.defineProperty(exports2, "recordIfAbsent", { enumerable: true, get: function() {
683
+ return dedup_1.recordIfAbsent;
684
+ } });
685
+ Object.defineProperty(exports2, "workflowDedupClient", { enumerable: true, get: function() {
686
+ return dedup_1.workflowDedupClient;
687
+ } });
688
+ }
689
+ });
690
+
93
691
  // src/index.ts
94
692
  var src_exports = {};
95
693
  __export(src_exports, {
694
+ BRIDGED_STATUSES: () => BRIDGED_STATUSES,
695
+ CLOUDFORMATION_EVENT_SOURCE: () => CLOUDFORMATION_EVENT_SOURCE,
696
+ CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE: () => CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE,
697
+ CONTROL_EVENT_BUS_NAME_ENV_VAR: () => CONTROL_EVENT_BUS_NAME_ENV_VAR,
96
698
  ChildHostedZone: () => ChildHostedZone,
97
699
  CognitoFixtureSeederClient: () => CognitoFixtureSeederClient,
98
700
  CognitoUserPool: () => CognitoUserPool,
@@ -103,11 +705,22 @@ __export(src_exports, {
103
705
  DATA_STORE_CHANGE_DETAIL_MAX_UTF8_BYTES: () => DATA_STORE_CHANGE_DETAIL_MAX_UTF8_BYTES,
104
706
  DATA_STORE_CHANGE_DETAIL_TYPE: () => DATA_STORE_CHANGE_DETAIL_TYPE,
105
707
  DATA_STORE_CHANGE_EVENT_SOURCE: () => DATA_STORE_CHANGE_EVENT_SOURCE,
708
+ DEMO_PERIOD: () => DEMO_PERIOD,
709
+ DEMO_TENANT_SPECS: () => DEMO_TENANT_SPECS,
710
+ DEMO_URN_SYSTEM: () => DEMO_URN_SYSTEM,
711
+ DEV_USERS: () => DEV_USERS,
106
712
  DataEventBus: () => DataEventBus,
107
713
  DataStoreHistoricalArchive: () => DataStoreHistoricalArchive,
108
714
  DataStorePostgresReplica: () => DataStorePostgresReplica,
109
715
  DiscoverableStringParameter: () => DiscoverableStringParameter,
110
716
  DynamoDbDataStore: () => DynamoDbDataStore,
717
+ OPENHI_REPO_TAG_KEY_ENV_VAR: () => OPENHI_REPO_TAG_KEY_ENV_VAR,
718
+ OPENHI_RESOURCE_URN_SYSTEM: () => OPENHI_RESOURCE_URN_SYSTEM,
719
+ OPENHI_TAG_KEY_PREFIX_ENV_VAR: () => OPENHI_TAG_KEY_PREFIX_ENV_VAR,
720
+ OPENHI_TAG_SUFFIX_BRANCH_NAME: () => OPENHI_TAG_SUFFIX_BRANCH_NAME,
721
+ OPENHI_TAG_SUFFIX_REPO_NAME: () => OPENHI_TAG_SUFFIX_REPO_NAME,
722
+ OPENHI_TAG_SUFFIX_SERVICE_TYPE: () => OPENHI_TAG_SUFFIX_SERVICE_TYPE,
723
+ OPENHI_TAG_SUFFIX_STAGE_TYPE: () => OPENHI_TAG_SUFFIX_STAGE_TYPE,
111
724
  OpenHiApp: () => OpenHiApp,
112
725
  OpenHiAuthService: () => OpenHiAuthService,
113
726
  OpenHiDataService: () => OpenHiDataService,
@@ -118,10 +731,17 @@ __export(src_exports, {
118
731
  OpenHiService: () => OpenHiService,
119
732
  OpenHiStage: () => OpenHiStage,
120
733
  OpsEventBus: () => OpsEventBus,
734
+ PLACEHOLDER_TENANT_ID: () => PLACEHOLDER_TENANT_ID,
735
+ PLACEHOLDER_WORKSPACE_ID: () => PLACEHOLDER_WORKSPACE_ID,
736
+ PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM: () => PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM,
737
+ PLATFORM_SCOPE_TENANT_ID: () => PLATFORM_SCOPE_TENANT_ID,
121
738
  POSTGRES_REPLICA_CLUSTER_ARN_SSM_NAME: () => POSTGRES_REPLICA_CLUSTER_ARN_SSM_NAME,
122
739
  POSTGRES_REPLICA_DATABASE_NAME_SSM_NAME: () => POSTGRES_REPLICA_DATABASE_NAME_SSM_NAME,
123
740
  POSTGRES_REPLICA_SECRET_ARN_SSM_NAME: () => POSTGRES_REPLICA_SECRET_ARN_SSM_NAME,
124
741
  PROVISION_DEFAULT_WORKSPACE_DETAIL_TYPE: () => PROVISION_DEFAULT_WORKSPACE_DETAIL_TYPE,
742
+ PlatformDeployBridge: () => PlatformDeployBridge,
743
+ PlatformDeployBridgeLambda: () => PlatformDeployBridgeLambda,
744
+ PlatformDeploymentCompletedV1: () => import_workflows4.PlatformDeploymentCompletedV1,
125
745
  PostAuthenticationLambda: () => PostAuthenticationLambda,
126
746
  PostConfirmationLambda: () => PostConfirmationLambda,
127
747
  PreTokenGenerationLambda: () => PreTokenGenerationLambda,
@@ -131,14 +751,40 @@ __export(src_exports, {
131
751
  RootHostedZone: () => RootHostedZone,
132
752
  RootHttpApi: () => RootHttpApi,
133
753
  RootWildcardCertificate: () => RootWildcardCertificate,
754
+ SEED_DEMO_DATA_CONSUMER_NAME: () => SEED_DEMO_DATA_CONSUMER_NAME,
755
+ SEED_SYSTEM_DATA_ACTOR_SYSTEM: () => SEED_SYSTEM_DATA_ACTOR_SYSTEM,
756
+ SEED_SYSTEM_DATA_CONSUMER_NAME: () => SEED_SYSTEM_DATA_CONSUMER_NAME,
757
+ SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR: () => SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR,
134
758
  STATIC_HOSTING_SERVICE_TYPE: () => STATIC_HOSTING_SERVICE_TYPE,
759
+ SeedDemoDataLambda: () => SeedDemoDataLambda,
760
+ SeedDemoDataWorkflow: () => SeedDemoDataWorkflow,
761
+ SeedSystemDataLambda: () => SeedSystemDataLambda,
762
+ SeedSystemDataWorkflow: () => SeedSystemDataWorkflow,
135
763
  StaticHosting: () => StaticHosting,
136
764
  USER_ONBOARDING_EVENT_SOURCE: () => USER_ONBOARDING_EVENT_SOURCE,
137
765
  UserOnboardingWorkflow: () => UserOnboardingWorkflow,
766
+ WorkflowDedupConsumerNameInvalidError: () => WorkflowDedupConsumerNameInvalidError,
767
+ WorkflowDedupTable: () => WorkflowDedupTable,
768
+ WorkflowDedupTableDuplicateError: () => WorkflowDedupTableDuplicateError,
138
769
  buildFhirCurrentResourceChangeDetail: () => buildFhirCurrentResourceChangeDetail,
139
770
  buildProvisionDefaultWorkspaceRequestedDetail: () => buildProvisionDefaultWorkspaceRequestedDetail,
771
+ demoBasePartitionKeys: () => demoBasePartitionKeys,
772
+ demoDevUserPartitionKeys: () => demoDevUserPartitionKeys,
773
+ demoMembershipId: () => demoMembershipId,
774
+ demoMembershipPartitionKey: () => demoMembershipPartitionKey,
775
+ demoRoleAssignmentId: () => demoRoleAssignmentId,
776
+ demoRoleAssignmentPartitionKey: () => demoRoleAssignmentPartitionKey,
777
+ demoRolesForUserInTenant: () => demoRolesForUserInTenant,
778
+ demoScenarioIdentifier: () => demoScenarioIdentifier,
779
+ demoTenantPartitionKey: () => demoTenantPartitionKey,
780
+ demoUserPartitionKey: () => demoUserPartitionKey,
781
+ demoWorkspacePartitionKey: () => demoWorkspacePartitionKey,
140
782
  getDynamoDbDataStoreTableName: () => getDynamoDbDataStoreTableName,
141
- getPostgresReplicaSchemaName: () => getPostgresReplicaSchemaName
783
+ getPostgresReplicaSchemaName: () => getPostgresReplicaSchemaName,
784
+ getWorkflowDedupTableName: () => getWorkflowDedupTableName,
785
+ openHiTagKey: () => openHiTagKey,
786
+ openhiResourceIdentifier: () => openhiResourceIdentifier,
787
+ rolePartitionKey: () => rolePartitionKey
142
788
  });
143
789
  module.exports = __toCommonJS(src_exports);
144
790
 
@@ -353,6 +999,11 @@ var import_utils = require("@codedrifters/utils");
353
999
  var import_config3 = __toESM(require_lib());
354
1000
  var import_aws_cdk_lib4 = require("aws-cdk-lib");
355
1001
  var import_change_case = require("change-case");
1002
+ var OPENHI_TAG_SUFFIX_REPO_NAME = "repo-name";
1003
+ var OPENHI_TAG_SUFFIX_BRANCH_NAME = "branch-name";
1004
+ var OPENHI_TAG_SUFFIX_SERVICE_TYPE = "service-type";
1005
+ var OPENHI_TAG_SUFFIX_STAGE_TYPE = "stage-type";
1006
+ var openHiTagKey = (appName, suffix) => `${appName}:${suffix}`;
356
1007
  var OpenHiService = class extends import_aws_cdk_lib4.Stack {
357
1008
  /**
358
1009
  * Creates a new OpenHI service stack.
@@ -420,11 +1071,20 @@ var OpenHiService = class extends import_aws_cdk_lib4.Stack {
420
1071
  `availability-zones:account=${account}:region=${region}`,
421
1072
  [`${region}a`, `${region}b`, `${region}c`]
422
1073
  );
423
- import_aws_cdk_lib4.Tags.of(this).add(`${appName}:repo-name`, repoName.slice(0, 255));
424
- import_aws_cdk_lib4.Tags.of(this).add(`${appName}:branch-name`, branchName.slice(0, 255));
425
- import_aws_cdk_lib4.Tags.of(this).add(`${appName}:service-type`, id.slice(0, 255));
426
1074
  import_aws_cdk_lib4.Tags.of(this).add(
427
- `${appName}:stage-type`,
1075
+ openHiTagKey(appName, OPENHI_TAG_SUFFIX_REPO_NAME),
1076
+ repoName.slice(0, 255)
1077
+ );
1078
+ import_aws_cdk_lib4.Tags.of(this).add(
1079
+ openHiTagKey(appName, OPENHI_TAG_SUFFIX_BRANCH_NAME),
1080
+ branchName.slice(0, 255)
1081
+ );
1082
+ import_aws_cdk_lib4.Tags.of(this).add(
1083
+ openHiTagKey(appName, OPENHI_TAG_SUFFIX_SERVICE_TYPE),
1084
+ id.slice(0, 255)
1085
+ );
1086
+ import_aws_cdk_lib4.Tags.of(this).add(
1087
+ openHiTagKey(appName, OPENHI_TAG_SUFFIX_STAGE_TYPE),
428
1088
  ohEnv.ohStage.stageType.slice(0, 255)
429
1089
  );
430
1090
  }
@@ -1110,6 +1770,204 @@ var DynamoDbDataStore = class extends import_aws_dynamodb.Table {
1110
1770
  }
1111
1771
  };
1112
1772
 
1773
+ // src/components/dynamodb/workflow-dedup-table.ts
1774
+ var import_workflows = __toESM(require_lib2());
1775
+ var import_aws_cdk_lib8 = require("aws-cdk-lib");
1776
+ var import_aws_dynamodb2 = require("aws-cdk-lib/aws-dynamodb");
1777
+ var import_aws_iam = require("aws-cdk-lib/aws-iam");
1778
+ var import_constructs5 = require("constructs");
1779
+ function getWorkflowDedupTableName(scope) {
1780
+ const stack = OpenHiService.of(scope);
1781
+ return `workflow-dedup-${stack.branchHash}`;
1782
+ }
1783
+ var _WorkflowDedupTable = class _WorkflowDedupTable extends import_constructs5.Construct {
1784
+ constructor(scope, id, props = {}) {
1785
+ super(scope, id);
1786
+ this.registeredConsumers = /* @__PURE__ */ new Set();
1787
+ const service = OpenHiService.of(scope);
1788
+ const others = service.node.findAll().filter(
1789
+ (c) => c instanceof _WorkflowDedupTable && c !== this
1790
+ );
1791
+ if (others.length > 0) {
1792
+ throw new WorkflowDedupTableDuplicateError(
1793
+ `WorkflowDedupTable already exists at ${others[0].node.path}; only one shared dedup table is allowed per service stack (TR-015).`
1794
+ );
1795
+ }
1796
+ this.table = new import_aws_dynamodb2.Table(this, "Table", {
1797
+ tableName: getWorkflowDedupTableName(scope),
1798
+ partitionKey: {
1799
+ name: "consumerName",
1800
+ type: import_aws_dynamodb2.AttributeType.STRING
1801
+ },
1802
+ sortKey: {
1803
+ name: "sk",
1804
+ type: import_aws_dynamodb2.AttributeType.STRING
1805
+ },
1806
+ billingMode: import_aws_dynamodb2.BillingMode.PAY_PER_REQUEST,
1807
+ timeToLiveAttribute: "expiresAt",
1808
+ removalPolicy: props.removalPolicy ?? service.removalPolicy
1809
+ });
1810
+ new DiscoverableStringParameter(this, "table-name-param", {
1811
+ ssmParamName: _WorkflowDedupTable.TABLE_NAME_SSM_PARAM_NAME,
1812
+ stringValue: this.table.tableName
1813
+ });
1814
+ new DiscoverableStringParameter(this, "table-arn-param", {
1815
+ ssmParamName: _WorkflowDedupTable.TABLE_ARN_SSM_PARAM_NAME,
1816
+ stringValue: this.table.tableArn
1817
+ });
1818
+ }
1819
+ /** Cross-stack lookup for the table name. */
1820
+ static tableNameFromLookup(scope) {
1821
+ return DiscoverableStringParameter.valueForLookupName(scope, {
1822
+ ssmParamName: _WorkflowDedupTable.TABLE_NAME_SSM_PARAM_NAME,
1823
+ serviceType: _WorkflowDedupTable.PUBLISHER_SERVICE_TYPE
1824
+ });
1825
+ }
1826
+ /** Cross-stack lookup for the table ARN. */
1827
+ static tableArnFromLookup(scope) {
1828
+ return DiscoverableStringParameter.valueForLookupName(scope, {
1829
+ ssmParamName: _WorkflowDedupTable.TABLE_ARN_SSM_PARAM_NAME,
1830
+ serviceType: _WorkflowDedupTable.PUBLISHER_SERVICE_TYPE
1831
+ });
1832
+ }
1833
+ /**
1834
+ * Cross-stack equivalent of {@link grantConsumer}. Use when the dedup
1835
+ * table is on a different stack than the consumer Lambda — the
1836
+ * grant resolves the table name + ARN via SSM at synth time, so the
1837
+ * consumer stack does not pick up a CloudFormation export dependency
1838
+ * on the global stack.
1839
+ *
1840
+ * Inverts the singleton-guard semantics of `grantConsumer`: there is
1841
+ * no synth-time check that the same `consumerName` was registered
1842
+ * twice across stacks. Consumer names are agreed by convention
1843
+ * (see TR-015); double-registration is operator error caught at
1844
+ * design time, not synth time.
1845
+ */
1846
+ static grantConsumerFromLookup(scope, fn, consumerName, options = {}) {
1847
+ _WorkflowDedupTable.assertConsumerNameStatic(consumerName);
1848
+ const tableName = _WorkflowDedupTable.tableNameFromLookup(scope);
1849
+ const tableArn = _WorkflowDedupTable.tableArnFromLookup(scope);
1850
+ fn.addEnvironment(import_workflows.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR, tableName);
1851
+ if (options.defaultTtlSeconds !== void 0) {
1852
+ fn.addEnvironment(
1853
+ "OPENHI_WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS",
1854
+ String(options.defaultTtlSeconds)
1855
+ );
1856
+ }
1857
+ fn.addToRolePolicy(
1858
+ new import_aws_iam.PolicyStatement({
1859
+ effect: import_aws_iam.Effect.ALLOW,
1860
+ actions: [
1861
+ "dynamodb:PutItem",
1862
+ "dynamodb:UpdateItem",
1863
+ "dynamodb:GetItem",
1864
+ "dynamodb:Query"
1865
+ ],
1866
+ resources: [tableArn],
1867
+ conditions: {
1868
+ "ForAllValues:StringEquals": {
1869
+ "dynamodb:LeadingKeys": [consumerName]
1870
+ }
1871
+ }
1872
+ })
1873
+ );
1874
+ }
1875
+ /**
1876
+ * Standalone consumer-name validator shared by the instance method
1877
+ * and `grantConsumerFromLookup` so the two grants enforce identical
1878
+ * invariants.
1879
+ */
1880
+ static assertConsumerNameStatic(consumerName) {
1881
+ if (consumerName.length === 0) {
1882
+ throw new WorkflowDedupConsumerNameInvalidError(
1883
+ "consumerName must be non-empty."
1884
+ );
1885
+ }
1886
+ if (consumerName.length > import_workflows.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH) {
1887
+ throw new WorkflowDedupConsumerNameInvalidError(
1888
+ `consumerName must be at most ${import_workflows.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH} chars; got ${consumerName.length}.`
1889
+ );
1890
+ }
1891
+ if (/\s/.test(consumerName)) {
1892
+ throw new WorkflowDedupConsumerNameInvalidError(
1893
+ "consumerName must not contain whitespace."
1894
+ );
1895
+ }
1896
+ }
1897
+ /**
1898
+ * Wire a Lambda consumer to this table. Injects the table-name env var
1899
+ * so the runtime `WorkflowDedupClient` can resolve it, then attaches a
1900
+ * per-consumer IAM grant scoped by `dynamodb:LeadingKeys` so the
1901
+ * consumer can only read/write its own partition.
1902
+ */
1903
+ grantConsumer(fn, consumerName, options = {}) {
1904
+ this.assertConsumerName(consumerName);
1905
+ if (this.registeredConsumers.has(consumerName)) {
1906
+ import_aws_cdk_lib8.Annotations.of(this).addWarning(
1907
+ `WorkflowDedupTable: consumerName "${consumerName}" registered more than once; subsequent grantConsumer calls add policy statements but do not re-inject the env var.`
1908
+ );
1909
+ }
1910
+ this.registeredConsumers.add(consumerName);
1911
+ fn.addEnvironment(import_workflows.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR, this.table.tableName);
1912
+ if (options.defaultTtlSeconds !== void 0) {
1913
+ fn.addEnvironment(
1914
+ "OPENHI_WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS",
1915
+ String(options.defaultTtlSeconds)
1916
+ );
1917
+ }
1918
+ fn.addToRolePolicy(
1919
+ new import_aws_iam.PolicyStatement({
1920
+ effect: import_aws_iam.Effect.ALLOW,
1921
+ actions: [
1922
+ "dynamodb:PutItem",
1923
+ "dynamodb:UpdateItem",
1924
+ "dynamodb:GetItem",
1925
+ "dynamodb:Query"
1926
+ ],
1927
+ resources: [this.table.tableArn],
1928
+ conditions: {
1929
+ "ForAllValues:StringEquals": {
1930
+ "dynamodb:LeadingKeys": [consumerName]
1931
+ }
1932
+ }
1933
+ })
1934
+ );
1935
+ }
1936
+ assertConsumerName(consumerName) {
1937
+ _WorkflowDedupTable.assertConsumerNameStatic(consumerName);
1938
+ }
1939
+ };
1940
+ /** SSM param name (short) used by `DiscoverableStringParameter` for the table name lookup. */
1941
+ _WorkflowDedupTable.TABLE_NAME_SSM_PARAM_NAME = "workflow-dedup-table-name";
1942
+ /** SSM param name (short) used by `DiscoverableStringParameter` for the table ARN lookup. */
1943
+ _WorkflowDedupTable.TABLE_ARN_SSM_PARAM_NAME = "workflow-dedup-table-arn";
1944
+ /**
1945
+ * Service-type the publishing stack runs under. The cross-stack lookups
1946
+ * pin to this value so consumer stacks on a different service-type
1947
+ * (e.g. `data`, `auth`) resolve the parameter at the publisher's SSM
1948
+ * path instead of their own. Typed against `OpenHiServiceType` so a
1949
+ * future rename of the literal triggers a compile error; not pulled
1950
+ * from `OpenHiGlobalService.SERVICE_TYPE` because
1951
+ * `OpenHiGlobalService` already imports `WorkflowDedupTable` — a
1952
+ * back-import would create a circular dependency.
1953
+ */
1954
+ _WorkflowDedupTable.PUBLISHER_SERVICE_TYPE = "global";
1955
+ var WorkflowDedupTable = _WorkflowDedupTable;
1956
+ var WorkflowDedupTableDuplicateError = class extends Error {
1957
+ /** @param message - human-readable description of the duplicate. */
1958
+ constructor(message) {
1959
+ super(message);
1960
+ this.name = "WorkflowDedupTableDuplicateError";
1961
+ }
1962
+ };
1963
+ var WorkflowDedupConsumerNameInvalidError = class extends Error {
1964
+ /** @param message - human-readable description of the invariant violation. */
1965
+ constructor(message) {
1966
+ super(message);
1967
+ this.name = "WorkflowDedupConsumerNameInvalidError";
1968
+ }
1969
+ };
1970
+
1113
1971
  // src/components/event-bridge/data-event-bus.ts
1114
1972
  var import_aws_events = require("aws-cdk-lib/aws-events");
1115
1973
  var DataEventBus = class _DataEventBus extends import_aws_events.EventBus {
@@ -1179,13 +2037,13 @@ var ControlEventBus = class _ControlEventBus extends import_aws_events3.EventBus
1179
2037
  // src/components/postgres/data-store-postgres-replica.ts
1180
2038
  var import_node_fs5 = __toESM(require("fs"));
1181
2039
  var import_node_path5 = __toESM(require("path"));
1182
- var import_aws_cdk_lib8 = require("aws-cdk-lib");
2040
+ var import_aws_cdk_lib9 = require("aws-cdk-lib");
1183
2041
  var ec2 = __toESM(require("aws-cdk-lib/aws-ec2"));
1184
2042
  var import_aws_lambda5 = require("aws-cdk-lib/aws-lambda");
1185
2043
  var import_aws_lambda_event_sources = require("aws-cdk-lib/aws-lambda-event-sources");
1186
2044
  var import_aws_lambda_nodejs5 = require("aws-cdk-lib/aws-lambda-nodejs");
1187
2045
  var rds = __toESM(require("aws-cdk-lib/aws-rds"));
1188
- var import_constructs5 = require("constructs");
2046
+ var import_constructs6 = require("constructs");
1189
2047
  var HANDLER_NAME5 = "data-store-postgres-replication.handler.js";
1190
2048
  var DEFAULT_DATABASE_NAME = "openhi";
1191
2049
  var SCHEMA_NAME_PATTERN = /^[a-z_][a-z0-9_]{0,62}$/;
@@ -1208,7 +2066,7 @@ function getPostgresReplicaSchemaName(branchHash) {
1208
2066
  }
1209
2067
  return candidate;
1210
2068
  }
1211
- var DataStorePostgresReplica = class extends import_constructs5.Construct {
2069
+ var DataStorePostgresReplica = class extends import_constructs6.Construct {
1212
2070
  /**
1213
2071
  * Resolve the cluster ARN published by an upstream {@link DataStorePostgresReplica}.
1214
2072
  * Use from any stack that needs to grant `rds-data:ExecuteStatement` against
@@ -1245,7 +2103,7 @@ var DataStorePostgresReplica = class extends import_constructs5.Construct {
1245
2103
  super(scope, id);
1246
2104
  this.databaseName = props.databaseName ?? DEFAULT_DATABASE_NAME;
1247
2105
  this.schemaName = getPostgresReplicaSchemaName(props.branchHash);
1248
- const region = import_aws_cdk_lib8.Stack.of(this).region;
2106
+ const region = import_aws_cdk_lib9.Stack.of(this).region;
1249
2107
  this.vpc = props.vpc ?? new ec2.Vpc(this, "Vpc", {
1250
2108
  availabilityZones: [`${region}a`, `${region}b`],
1251
2109
  natGateways: 0,
@@ -1281,7 +2139,7 @@ var DataStorePostgresReplica = class extends import_constructs5.Construct {
1281
2139
  entry: resolveHandlerEntry5(__dirname),
1282
2140
  runtime: import_aws_lambda5.Runtime.NODEJS_LATEST,
1283
2141
  memorySize: 512,
1284
- timeout: import_aws_cdk_lib8.Duration.minutes(1),
2142
+ timeout: import_aws_cdk_lib9.Duration.minutes(1),
1285
2143
  vpc: this.vpc,
1286
2144
  vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
1287
2145
  description: "Replicates DynamoDB current-resource changes into the Postgres `resources` JSONB table (ADR 2026-04-17-01).",
@@ -1308,7 +2166,7 @@ var DataStorePostgresReplica = class extends import_constructs5.Construct {
1308
2166
  new import_aws_lambda_event_sources.KinesisEventSource(props.kinesisStream, {
1309
2167
  startingPosition: import_aws_lambda5.StartingPosition.LATEST,
1310
2168
  batchSize: 100,
1311
- maxBatchingWindow: import_aws_cdk_lib8.Duration.seconds(5),
2169
+ maxBatchingWindow: import_aws_cdk_lib9.Duration.seconds(5),
1312
2170
  retryAttempts: 10,
1313
2171
  bisectBatchOnError: true,
1314
2172
  parallelizationFactor: 2,
@@ -1341,7 +2199,7 @@ var DataStorePostgresReplica = class extends import_constructs5.Construct {
1341
2199
  };
1342
2200
 
1343
2201
  // src/components/route-53/child-hosted-zone.ts
1344
- var import_aws_cdk_lib9 = require("aws-cdk-lib");
2202
+ var import_aws_cdk_lib10 = require("aws-cdk-lib");
1345
2203
  var import_aws_route53 = require("aws-cdk-lib/aws-route53");
1346
2204
  var ChildHostedZone = class extends import_aws_route53.HostedZone {
1347
2205
  constructor(scope, id, props) {
@@ -1350,7 +2208,7 @@ var ChildHostedZone = class extends import_aws_route53.HostedZone {
1350
2208
  zone: props.parentHostedZone,
1351
2209
  recordName: this.zoneName,
1352
2210
  values: this.hostedZoneNameServers || [],
1353
- ttl: import_aws_cdk_lib9.Duration.minutes(5)
2211
+ ttl: import_aws_cdk_lib10.Duration.minutes(5)
1354
2212
  });
1355
2213
  }
1356
2214
  };
@@ -1360,8 +2218,8 @@ var ChildHostedZone = class extends import_aws_route53.HostedZone {
1360
2218
  ChildHostedZone.SSM_PARAM_NAME = "CHILDHOSTEDZONE";
1361
2219
 
1362
2220
  // src/components/route-53/root-hosted-zone.ts
1363
- var import_constructs6 = require("constructs");
1364
- var RootHostedZone = class extends import_constructs6.Construct {
2221
+ var import_constructs7 = require("constructs");
2222
+ var RootHostedZone = class extends import_constructs7.Construct {
1365
2223
  };
1366
2224
 
1367
2225
  // src/components/static-hosting/static-hosting.ts
@@ -1369,9 +2227,9 @@ var import_aws_cloudfront = require("aws-cdk-lib/aws-cloudfront");
1369
2227
  var import_aws_cloudfront_origins = require("aws-cdk-lib/aws-cloudfront-origins");
1370
2228
  var import_aws_s3 = require("aws-cdk-lib/aws-s3");
1371
2229
  var import_core = require("aws-cdk-lib/core");
1372
- var import_constructs7 = require("constructs");
2230
+ var import_constructs8 = require("constructs");
1373
2231
  var STATIC_HOSTING_SERVICE_TYPE = "website";
1374
- var _StaticHosting = class _StaticHosting extends import_constructs7.Construct {
2232
+ var _StaticHosting = class _StaticHosting extends import_constructs8.Construct {
1375
2233
  constructor(scope, id, props = {}) {
1376
2234
  super(scope, id);
1377
2235
  const stack = OpenHiService.of(scope);
@@ -1423,29 +2281,127 @@ _StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_ARN = "STATIC_HOSTING_DISTRIBUTION_AR
1423
2281
  var StaticHosting = _StaticHosting;
1424
2282
 
1425
2283
  // src/services/open-hi-auth-service.ts
1426
- var import_config4 = __toESM(require_lib());
2284
+ var import_config5 = __toESM(require_lib());
1427
2285
  var import_aws_cognito5 = require("aws-cdk-lib/aws-cognito");
1428
- var import_aws_iam2 = require("aws-cdk-lib/aws-iam");
2286
+ var import_aws_iam6 = require("aws-cdk-lib/aws-iam");
1429
2287
  var import_aws_kms2 = require("aws-cdk-lib/aws-kms");
1430
2288
  var import_core2 = require("aws-cdk-lib/core");
1431
2289
 
1432
2290
  // src/services/open-hi-data-service.ts
1433
- var import_aws_dynamodb2 = require("aws-cdk-lib/aws-dynamodb");
2291
+ var import_config4 = __toESM(require_lib());
2292
+ var import_aws_dynamodb3 = require("aws-cdk-lib/aws-dynamodb");
1434
2293
  var kinesis = __toESM(require("aws-cdk-lib/aws-kinesis"));
1435
2294
 
1436
2295
  // src/services/open-hi-global-service.ts
1437
2296
  var import_aws_certificatemanager2 = require("aws-cdk-lib/aws-certificatemanager");
1438
- var import_aws_events4 = require("aws-cdk-lib/aws-events");
2297
+ var import_aws_events5 = require("aws-cdk-lib/aws-events");
1439
2298
  var import_aws_route532 = require("aws-cdk-lib/aws-route53");
1440
2299
  var import_aws_ssm3 = require("aws-cdk-lib/aws-ssm");
1441
- var _OpenHiGlobalService = class _OpenHiGlobalService extends OpenHiService {
1442
- /**
1443
- * Returns an IHostedZone from the given attributes (no SSM). Use when the zone is imported from config.
1444
- */
1445
- static rootHostedZoneFromConstruct(scope, props) {
1446
- return import_aws_route532.HostedZone.fromHostedZoneAttributes(scope, "root-zone", props);
1447
- }
1448
- /**
2300
+
2301
+ // src/workflows/control-plane/platform-deploy-bridge/events.ts
2302
+ var CLOUDFORMATION_EVENT_SOURCE = "aws.cloudformation";
2303
+ var CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE = "CloudFormation Stack Status Change";
2304
+ var BRIDGED_STATUSES = ["CREATE_COMPLETE", "UPDATE_COMPLETE"];
2305
+ var CONTROL_EVENT_BUS_NAME_ENV_VAR = "CONTROL_EVENT_BUS_NAME";
2306
+ var OPENHI_REPO_TAG_KEY_ENV_VAR = "OPENHI_REPO_TAG_KEY";
2307
+ var OPENHI_TAG_KEY_PREFIX_ENV_VAR = "OPENHI_TAG_KEY_PREFIX";
2308
+ var PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM = "platform-deploy-bridge";
2309
+
2310
+ // src/workflows/control-plane/platform-deploy-bridge/platform-deploy-bridge.ts
2311
+ var import_constructs10 = require("constructs");
2312
+
2313
+ // src/workflows/control-plane/platform-deploy-bridge/platform-deploy-bridge-lambda.ts
2314
+ var import_node_fs6 = __toESM(require("fs"));
2315
+ var import_node_path6 = __toESM(require("path"));
2316
+ var import_aws_cdk_lib11 = require("aws-cdk-lib");
2317
+ var import_aws_events4 = require("aws-cdk-lib/aws-events");
2318
+ var import_aws_events_targets = require("aws-cdk-lib/aws-events-targets");
2319
+ var import_aws_iam2 = require("aws-cdk-lib/aws-iam");
2320
+ var import_aws_lambda6 = require("aws-cdk-lib/aws-lambda");
2321
+ var import_aws_lambda_nodejs6 = require("aws-cdk-lib/aws-lambda-nodejs");
2322
+ var import_constructs9 = require("constructs");
2323
+ var HANDLER_NAME6 = "platform-deploy-bridge.handler.js";
2324
+ function resolveHandlerEntry6(dirname) {
2325
+ const sameDir = import_node_path6.default.join(dirname, HANDLER_NAME6);
2326
+ if (import_node_fs6.default.existsSync(sameDir)) {
2327
+ return sameDir;
2328
+ }
2329
+ return import_node_path6.default.join(dirname, "..", "..", "..", "..", "lib", HANDLER_NAME6);
2330
+ }
2331
+ var PlatformDeployBridgeLambda = class extends import_constructs9.Construct {
2332
+ constructor(scope, props) {
2333
+ super(scope, "platform-deploy-bridge-lambda");
2334
+ const service = OpenHiService.of(this);
2335
+ const repoTagKey = openHiTagKey(
2336
+ service.appName,
2337
+ OPENHI_TAG_SUFFIX_REPO_NAME
2338
+ );
2339
+ const tagKeyPrefix = `${service.appName}:`;
2340
+ const ownStackName = import_aws_cdk_lib11.Stack.of(this).stackName;
2341
+ const ownSuffix = `-${service.serviceId}-${import_aws_cdk_lib11.Stack.of(this).account}-${import_aws_cdk_lib11.Stack.of(this).region}`;
2342
+ const sharedPrefix = ownStackName.endsWith(ownSuffix) ? ownStackName.slice(0, -ownSuffix.length) : service.branchHash;
2343
+ const stackIdPrefix = `arn:aws:cloudformation:${import_aws_cdk_lib11.Stack.of(this).region}:${import_aws_cdk_lib11.Stack.of(this).account}:stack/${sharedPrefix}-`;
2344
+ this.lambda = new import_aws_lambda_nodejs6.NodejsFunction(this, "handler", {
2345
+ entry: resolveHandlerEntry6(__dirname),
2346
+ runtime: import_aws_lambda6.Runtime.NODEJS_LATEST,
2347
+ memorySize: 256,
2348
+ timeout: import_aws_cdk_lib11.Duration.seconds(30),
2349
+ environment: {
2350
+ [CONTROL_EVENT_BUS_NAME_ENV_VAR]: props.controlEventBus.eventBusName,
2351
+ [OPENHI_REPO_TAG_KEY_ENV_VAR]: repoTagKey,
2352
+ [OPENHI_TAG_KEY_PREFIX_ENV_VAR]: tagKeyPrefix
2353
+ }
2354
+ });
2355
+ this.lambda.addToRolePolicy(
2356
+ new import_aws_iam2.PolicyStatement({
2357
+ effect: import_aws_iam2.Effect.ALLOW,
2358
+ actions: ["cloudformation:DescribeStacks"],
2359
+ resources: [
2360
+ `arn:aws:cloudformation:${import_aws_cdk_lib11.Stack.of(this).region}:${import_aws_cdk_lib11.Stack.of(this).account}:stack/*`
2361
+ ]
2362
+ })
2363
+ );
2364
+ props.controlEventBus.grantPutEventsTo(this.lambda);
2365
+ this.rule = new import_aws_events4.Rule(this, "rule", {
2366
+ eventPattern: {
2367
+ source: [CLOUDFORMATION_EVENT_SOURCE],
2368
+ detailType: [CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE],
2369
+ detail: {
2370
+ "stack-id": [{ prefix: stackIdPrefix }],
2371
+ "status-details": {
2372
+ status: [...BRIDGED_STATUSES]
2373
+ }
2374
+ }
2375
+ },
2376
+ targets: [
2377
+ new import_aws_events_targets.LambdaFunction(this.lambda, {
2378
+ retryAttempts: 2,
2379
+ maxEventAge: import_aws_cdk_lib11.Duration.hours(2)
2380
+ })
2381
+ ]
2382
+ });
2383
+ }
2384
+ };
2385
+
2386
+ // src/workflows/control-plane/platform-deploy-bridge/platform-deploy-bridge.ts
2387
+ var PlatformDeployBridge = class extends import_constructs10.Construct {
2388
+ constructor(scope, props) {
2389
+ super(scope, "platform-deploy-bridge");
2390
+ this.bridgeLambda = new PlatformDeployBridgeLambda(this, {
2391
+ controlEventBus: props.controlEventBus
2392
+ });
2393
+ }
2394
+ };
2395
+
2396
+ // src/services/open-hi-global-service.ts
2397
+ var _OpenHiGlobalService = class _OpenHiGlobalService extends OpenHiService {
2398
+ /**
2399
+ * Returns an IHostedZone from the given attributes (no SSM). Use when the zone is imported from config.
2400
+ */
2401
+ static rootHostedZoneFromConstruct(scope, props) {
2402
+ return import_aws_route532.HostedZone.fromHostedZoneAttributes(scope, "root-zone", props);
2403
+ }
2404
+ /**
1449
2405
  * Returns an ICertificate by looking up the Global stack's wildcard cert ARN from SSM.
1450
2406
  */
1451
2407
  static rootWildcardCertificateFromConstruct(scope) {
@@ -1476,7 +2432,7 @@ var _OpenHiGlobalService = class _OpenHiGlobalService extends OpenHiService {
1476
2432
  * Returns the data event bus by name (deterministic per branch). Use from other stacks to obtain an IEventBus reference.
1477
2433
  */
1478
2434
  static dataEventBusFromConstruct(scope) {
1479
- return import_aws_events4.EventBus.fromEventBusName(
2435
+ return import_aws_events5.EventBus.fromEventBusName(
1480
2436
  scope,
1481
2437
  "data-event-bus",
1482
2438
  DataEventBus.getEventBusName(scope)
@@ -1486,7 +2442,7 @@ var _OpenHiGlobalService = class _OpenHiGlobalService extends OpenHiService {
1486
2442
  * Returns the ops event bus by name (deterministic per branch). Use from other stacks to obtain an IEventBus reference.
1487
2443
  */
1488
2444
  static opsEventBusFromConstruct(scope) {
1489
- return import_aws_events4.EventBus.fromEventBusName(
2445
+ return import_aws_events5.EventBus.fromEventBusName(
1490
2446
  scope,
1491
2447
  "ops-event-bus",
1492
2448
  OpsEventBus.getEventBusName(scope)
@@ -1496,12 +2452,23 @@ var _OpenHiGlobalService = class _OpenHiGlobalService extends OpenHiService {
1496
2452
  * Returns the control-plane event bus by name (deterministic per branch). Use from other stacks to obtain an IEventBus reference.
1497
2453
  */
1498
2454
  static controlEventBusFromConstruct(scope) {
1499
- return import_aws_events4.EventBus.fromEventBusName(
2455
+ return import_aws_events5.EventBus.fromEventBusName(
1500
2456
  scope,
1501
2457
  "control-event-bus",
1502
2458
  ControlEventBus.getEventBusName(scope)
1503
2459
  );
1504
2460
  }
2461
+ /**
2462
+ * Returns the workflow dedup table by name (deterministic per branch).
2463
+ * Use from other stacks to obtain an ITable reference. Consumer Lambdas
2464
+ * are typically wired via `WorkflowDedupTable.grantConsumer(fn, name)`
2465
+ * on the owning service's `workflowDedupTable` reference; the
2466
+ * `tableNameFromLookup` / `tableArnFromLookup` SSM helpers on the
2467
+ * construct cover cross-stack consumers that need only the name/ARN.
2468
+ */
2469
+ static workflowDedupTableNameFromLookup(scope) {
2470
+ return WorkflowDedupTable.tableNameFromLookup(scope);
2471
+ }
1505
2472
  get serviceType() {
1506
2473
  return _OpenHiGlobalService.SERVICE_TYPE;
1507
2474
  }
@@ -1515,6 +2482,8 @@ var _OpenHiGlobalService = class _OpenHiGlobalService extends OpenHiService {
1515
2482
  this.dataEventBus = this.createDataEventBus();
1516
2483
  this.opsEventBus = this.createOpsEventBus();
1517
2484
  this.controlEventBus = this.createControlEventBus();
2485
+ this.workflowDedupTable = this.createWorkflowDedupTable();
2486
+ this.platformDeployBridge = this.createPlatformDeployBridge();
1518
2487
  }
1519
2488
  /**
1520
2489
  * Validates that config required for the Global stack is present.
@@ -1586,23 +2555,1248 @@ var _OpenHiGlobalService = class _OpenHiGlobalService extends OpenHiService {
1586
2555
  createControlEventBus() {
1587
2556
  return new ControlEventBus(this);
1588
2557
  }
2558
+ /**
2559
+ * Creates the platform deploy bridge that republishes CloudFormation
2560
+ * Stack Status Change events onto the control event bus.
2561
+ * Override to customize.
2562
+ */
2563
+ createPlatformDeployBridge() {
2564
+ return new PlatformDeployBridge(this, {
2565
+ controlEventBus: this.controlEventBus
2566
+ });
2567
+ }
2568
+ /**
2569
+ * Creates the shared workflow dedup table (TR-015 singleton).
2570
+ * Override to customize.
2571
+ */
2572
+ createWorkflowDedupTable() {
2573
+ return new WorkflowDedupTable(this, "workflow-dedup-table");
2574
+ }
1589
2575
  };
1590
2576
  _OpenHiGlobalService.SERVICE_TYPE = "global";
1591
2577
  var OpenHiGlobalService = _OpenHiGlobalService;
1592
2578
 
1593
- // src/services/open-hi-data-service.ts
1594
- var _OpenHiDataService = class _OpenHiDataService extends OpenHiService {
1595
- /**
1596
- * Returns the data store table by name. Use from other stacks (e.g. REST API Lambda) to obtain an ITable reference.
1597
- */
1598
- static dynamoDbDataStoreFromConstruct(scope, id = "dynamo-db-data-store") {
1599
- return import_aws_dynamodb2.Table.fromTableName(scope, id, getDynamoDbDataStoreTableName(scope));
2579
+ // src/workflows/control-plane/seed-demo-data/events.ts
2580
+ var import_types = require("@openhi/types");
2581
+ var import_workflows2 = __toESM(require_lib2());
2582
+ var SEED_DEMO_DATA_CONSUMER_NAME = "seed-demo-data";
2583
+ var DEMO_URN_SYSTEM = "urn:openhi:demo";
2584
+ var OPENHI_RESOURCE_URN_SYSTEM = "http://openhi.org/";
2585
+ var DEMO_PERIOD = { start: "2026-01-01T00:00:00Z" };
2586
+ var PLATFORM_SCOPE_TENANT_ID = "platform";
2587
+ var PLACEHOLDER_TENANT_ID = "placeholder-tenant-id";
2588
+ var PLACEHOLDER_WORKSPACE_ID = "placeholder-workspace-id";
2589
+ var DEV_USERS = [
2590
+ { id: "dev-russell", email: "russell@codedrifters.com" },
2591
+ { id: "dev-cameron", email: "cameron@codedrifters.com" },
2592
+ { id: "dev-neelima", email: "neelima@codedrifters.com" },
2593
+ { id: "dev-garon", email: "garon@codedrifters.com" },
2594
+ { id: "dev-dave", email: "dave@codedrifters.com" },
2595
+ { id: "dev-drew", email: "drew@codedrifters.com" },
2596
+ { id: "dev-jessica", email: "jessica@codedrifters.com" },
2597
+ { id: "dev-jared", email: "jared@codedrifters.com" },
2598
+ { id: "dev-goddess", email: "goddess@codedrifters.com" }
2599
+ ];
2600
+ var DEMO_TENANT_SPECS = [
2601
+ {
2602
+ scenario: "placeholder",
2603
+ tenantId: PLACEHOLDER_TENANT_ID,
2604
+ tenantName: "OpenHI Placeholder Tenant",
2605
+ workspaces: [
2606
+ {
2607
+ id: PLACEHOLDER_WORKSPACE_ID,
2608
+ name: "OpenHI Placeholder Workspace",
2609
+ roleSuffix: "workspace"
2610
+ }
2611
+ ]
2612
+ },
2613
+ {
2614
+ scenario: "demo-wound-care",
2615
+ tenantId: "demo-wound-care-tenant",
2616
+ tenantName: "Cedarbrook Wound Healing Institute",
2617
+ workspaces: [
2618
+ {
2619
+ id: "demo-wound-care-workspace",
2620
+ name: "Cedarbrook Outpatient Wound Clinic",
2621
+ roleSuffix: "workspace"
2622
+ }
2623
+ ]
2624
+ },
2625
+ {
2626
+ scenario: "demo-primary-care",
2627
+ tenantId: "demo-primary-care-tenant",
2628
+ tenantName: "Maple Ridge Family Medicine",
2629
+ workspaces: [
2630
+ {
2631
+ id: "demo-primary-care-workspace",
2632
+ name: "Maple Ridge Main Street Office",
2633
+ roleSuffix: "workspace"
2634
+ }
2635
+ ]
2636
+ },
2637
+ {
2638
+ scenario: "demo-mixed",
2639
+ tenantId: "demo-mixed-tenant",
2640
+ tenantName: "Northbridge Health Network",
2641
+ workspaces: [
2642
+ {
2643
+ id: "demo-mixed-workspace-wound-care",
2644
+ name: "Northbridge Wound Care Center",
2645
+ roleSuffix: "workspace-wound-care"
2646
+ },
2647
+ {
2648
+ id: "demo-mixed-workspace-primary-care",
2649
+ name: "Northbridge Family Practice",
2650
+ roleSuffix: "workspace-primary-care"
2651
+ }
2652
+ ]
2653
+ }
2654
+ ];
2655
+ var demoMembershipId = (devUserId, tenantId) => `demo-membership-${devUserId}-${tenantId}`;
2656
+ var demoRoleAssignmentId = (devUserId, tenantId, roleCode) => `demo-roleassignment-${devUserId}-${tenantId}-${roleCode}`;
2657
+ var demoScenarioIdentifier = (scenario, roleSuffix) => ({
2658
+ system: DEMO_URN_SYSTEM,
2659
+ value: `${scenario}:${roleSuffix}`
2660
+ });
2661
+ var openhiResourceIdentifier = (params) => ({
2662
+ use: "unversioned",
2663
+ system: OPENHI_RESOURCE_URN_SYSTEM,
2664
+ value: `urn:ohi:${params.tenantId}:${params.workspaceId}:${params.resourceType}:${params.id}`
2665
+ });
2666
+ var demoRolesForUserInTenant = (_user, _tenantId) => {
2667
+ void _user;
2668
+ void _tenantId;
2669
+ return [import_types.CONTROL_PLANE_ROLE_CODE.TENANT_ADMIN];
2670
+ };
2671
+ var rolePartitionKey = (roleId) => `role#id#${roleId}`;
2672
+ var demoTenantPartitionKey = (tenantId) => `tenant#id#${tenantId}`;
2673
+ var demoWorkspacePartitionKey = (tenantId, workspaceId) => `tid#${tenantId}#workspace#id#${workspaceId}`;
2674
+ var demoMembershipPartitionKey = (tenantId, membershipId) => `tid#${tenantId}#membership#id#${membershipId}`;
2675
+ var demoRoleAssignmentPartitionKey = (tenantId, roleAssignmentId) => `tid#${tenantId}#roleassignment#id#${roleAssignmentId}`;
2676
+ var demoUserPartitionKey = (userId) => `user#id#${userId}`;
2677
+ var demoBasePartitionKeys = () => {
2678
+ const keys = [];
2679
+ for (const spec of DEMO_TENANT_SPECS) {
2680
+ keys.push(demoTenantPartitionKey(spec.tenantId));
2681
+ for (const workspace of spec.workspaces) {
2682
+ keys.push(demoWorkspacePartitionKey(spec.tenantId, workspace.id));
2683
+ }
1600
2684
  }
1601
- get serviceType() {
1602
- return _OpenHiDataService.SERVICE_TYPE;
2685
+ return keys;
2686
+ };
2687
+ var demoDevUserPartitionKeys = (devUsers) => {
2688
+ const keys = [];
2689
+ for (const user of devUsers) {
2690
+ keys.push(demoUserPartitionKey(user.id));
2691
+ for (const spec of DEMO_TENANT_SPECS) {
2692
+ keys.push(
2693
+ demoMembershipPartitionKey(
2694
+ spec.tenantId,
2695
+ demoMembershipId(user.id, spec.tenantId)
2696
+ )
2697
+ );
2698
+ for (const roleCode of demoRolesForUserInTenant(user, spec.tenantId)) {
2699
+ keys.push(
2700
+ demoRoleAssignmentPartitionKey(
2701
+ spec.tenantId,
2702
+ demoRoleAssignmentId(user.id, spec.tenantId, roleCode)
2703
+ )
2704
+ );
2705
+ }
2706
+ }
2707
+ keys.push(
2708
+ demoRoleAssignmentPartitionKey(
2709
+ PLATFORM_SCOPE_TENANT_ID,
2710
+ demoRoleAssignmentId(
2711
+ user.id,
2712
+ PLATFORM_SCOPE_TENANT_ID,
2713
+ import_types.CONTROL_PLANE_ROLE_CODE.SYSTEM_ADMIN
2714
+ )
2715
+ )
2716
+ );
2717
+ }
2718
+ return keys;
2719
+ };
2720
+
2721
+ // src/workflows/control-plane/seed-demo-data/seed-demo-data-lambda.ts
2722
+ var import_node_fs7 = __toESM(require("fs"));
2723
+ var import_node_path7 = __toESM(require("path"));
2724
+ var import_types8 = require("@openhi/types");
2725
+ var import_aws_cdk_lib12 = require("aws-cdk-lib");
2726
+ var import_aws_events6 = require("aws-cdk-lib/aws-events");
2727
+ var import_aws_events_targets2 = require("aws-cdk-lib/aws-events-targets");
2728
+ var import_aws_iam3 = require("aws-cdk-lib/aws-iam");
2729
+ var import_aws_lambda7 = require("aws-cdk-lib/aws-lambda");
2730
+ var import_aws_lambda_nodejs7 = require("aws-cdk-lib/aws-lambda-nodejs");
2731
+ var import_constructs11 = require("constructs");
2732
+
2733
+ // src/workflows/control-plane/seed-demo-data/seed-demo-data.handler.ts
2734
+ var import_node_crypto = require("crypto");
2735
+ var import_client_cognito_identity_provider = require("@aws-sdk/client-cognito-identity-provider");
2736
+ var import_client_dynamodb2 = require("@aws-sdk/client-dynamodb");
2737
+ var import_types7 = require("@openhi/types");
2738
+ var import_workflows3 = __toESM(require_lib2());
2739
+
2740
+ // src/data/dynamo/dynamo-control-service.ts
2741
+ var import_electrodb8 = require("electrodb");
2742
+
2743
+ // src/data/dynamo/dynamo-client.ts
2744
+ var import_client_dynamodb = require("@aws-sdk/client-dynamodb");
2745
+ var defaultTableName = process.env.DYNAMO_TABLE_NAME ?? "jesttesttable";
2746
+ var dynamoClient = new import_client_dynamodb.DynamoDBClient({
2747
+ ...process.env.MOCK_DYNAMODB_ENDPOINT && {
2748
+ endpoint: process.env.MOCK_DYNAMODB_ENDPOINT,
2749
+ sslEnabled: false,
2750
+ region: "local"
2751
+ }
2752
+ });
2753
+
2754
+ // src/data/dynamo/entities/control/configuration-entity.ts
2755
+ var import_electrodb = require("electrodb");
2756
+
2757
+ // src/data/dynamo/entities/control/control-entity-common.ts
2758
+ var import_types2 = require("@openhi/types");
2759
+
2760
+ // src/data/dynamo/shard.ts
2761
+ var SHARD_COUNT = 4;
2762
+ function computeShard(id) {
2763
+ let hash = 2166136261;
2764
+ for (let i = 0; i < id.length; i++) {
2765
+ hash ^= id.charCodeAt(i);
2766
+ hash = Math.imul(hash, 16777619);
2767
+ }
2768
+ return (hash >>> 0) % SHARD_COUNT;
2769
+ }
2770
+
2771
+ // src/data/dynamo/entities/control/control-entity-common.ts
2772
+ var gsi1ShardAttribute = {
2773
+ type: "string",
2774
+ watch: ["id"],
2775
+ set: (_val, item) => {
2776
+ if (typeof item?.id !== "string" || item.id.length === 0) {
2777
+ return void 0;
2778
+ }
2779
+ return String(computeShard(item.id));
2780
+ }
2781
+ };
2782
+ var gsi1skAttribute = {
2783
+ type: "string",
2784
+ watch: ["resource", "lastUpdated", "id"],
2785
+ set: (_val, item) => {
2786
+ const id = typeof item?.id === "string" ? item.id : "";
2787
+ const lastUpdated = typeof item?.lastUpdated === "string" ? item.lastUpdated : "";
2788
+ const fallback = `${lastUpdated}#${id}`;
2789
+ if (typeof item?.resource !== "string" || item.resource.length === 0) {
2790
+ return fallback;
2791
+ }
2792
+ let parsed;
2793
+ try {
2794
+ parsed = JSON.parse(item.resource);
2795
+ } catch {
2796
+ return fallback;
2797
+ }
2798
+ if (!parsed || typeof parsed !== "object") return fallback;
2799
+ const resourceType = parsed.resourceType;
2800
+ if (typeof resourceType !== "string") return fallback;
2801
+ const label = (0, import_types2.extractLabel)(parsed);
2802
+ return label !== void 0 ? `${label}#${id}` : fallback;
2803
+ }
2804
+ };
2805
+
2806
+ // src/data/dynamo/entities/control/configuration-entity.ts
2807
+ var ConfigurationEntity = new import_electrodb.Entity({
2808
+ model: {
2809
+ entity: "configuration",
2810
+ service: "control",
2811
+ version: "01"
2812
+ },
2813
+ attributes: {
2814
+ /** Sort key. "CURRENT" for current version; version history in S3. */
2815
+ sk: {
2816
+ type: "string",
2817
+ required: true,
2818
+ default: "CURRENT"
2819
+ },
2820
+ /** Tenant scope. Use "BASELINE" when the config is baseline default (no tenant). */
2821
+ tenantId: {
2822
+ type: "string",
2823
+ required: true,
2824
+ default: "BASELINE"
2825
+ },
2826
+ /** Workspace scope. Use "-" when absent. */
2827
+ workspaceId: {
2828
+ type: "string",
2829
+ required: true,
2830
+ default: "-"
2831
+ },
2832
+ /** User scope. Use "-" when absent. */
2833
+ userId: {
2834
+ type: "string",
2835
+ required: true,
2836
+ default: "-"
2837
+ },
2838
+ /** Role scope. Use "-" when absent. */
2839
+ roleId: {
2840
+ type: "string",
2841
+ required: true,
2842
+ default: "-"
2843
+ },
2844
+ /** Config type (category), e.g. endpoints, branding, display. */
2845
+ key: {
2846
+ type: "string",
2847
+ required: true
2848
+ },
2849
+ /** FHIR Resource.id; logical id in URL and for the Configuration resource. */
2850
+ id: {
2851
+ type: "string",
2852
+ required: true
2853
+ },
2854
+ /** Payload as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */
2855
+ resource: {
2856
+ type: "string",
2857
+ required: true
2858
+ },
2859
+ /**
2860
+ * Summary projection (key display fields as JSON string: id, key, status).
2861
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
2862
+ */
2863
+ summary: {
2864
+ type: "string",
2865
+ required: true
2866
+ },
2867
+ /** Version id (e.g. ULID). Tracks current version; S3 history key. */
2868
+ vid: {
2869
+ type: "string",
2870
+ required: true
2871
+ },
2872
+ lastUpdated: {
2873
+ type: "string",
2874
+ required: true
2875
+ },
2876
+ gsi1Shard: gsi1ShardAttribute,
2877
+ deleted: {
2878
+ type: "boolean",
2879
+ required: false
2880
+ },
2881
+ bundleId: {
2882
+ type: "string",
2883
+ required: false
2884
+ },
2885
+ msgId: {
2886
+ type: "string",
2887
+ required: false
2888
+ }
2889
+ },
2890
+ indexes: {
2891
+ /** Base table: PK, SK (data store key names). PK is built from tenantId, workspaceId, userId, roleId; SK is built from key and sk. Do not supply PK or SK from outside. */
2892
+ record: {
2893
+ pk: {
2894
+ field: "PK",
2895
+ composite: ["tenantId", "workspaceId", "userId", "roleId"],
2896
+ template: "CONFIG#TID#${tenantId}#WID#${workspaceId}#UID#${userId}#RID#${roleId}"
2897
+ },
2898
+ sk: {
2899
+ field: "SK",
2900
+ composite: ["key", "sk"],
2901
+ template: "KEY#${key}#SK#${sk}"
2902
+ }
2903
+ },
2904
+ /**
2905
+ * GSI1 — Unified Sharded List per ADR-011: list all Configuration entries for a
2906
+ * (tenant, workspace) across the four shards. Use for "list configs scoped to this tenant"
2907
+ * (workspaceId = "-") or "list configs scoped to this workspace". Does not support
2908
+ * hierarchical resolution in one query; use base table GetItem in fallback order
2909
+ * (user → workspace → tenant → baseline) for that.
2910
+ * SK is `<key>#<id>` — Configuration's `key` is a required entity attribute (the
2911
+ * config category: endpoints, branding, display, …) and the natural sort/lookup
2912
+ * dimension. `casing: "none"` preserves the literal key value.
2913
+ */
2914
+ gsi1: {
2915
+ index: "GSI1",
2916
+ pk: {
2917
+ field: "GSI1PK",
2918
+ composite: ["tenantId", "workspaceId", "gsi1Shard"],
2919
+ template: "TID#${tenantId}#WID#${workspaceId}#RT#Configuration#SHARD#${gsi1Shard}"
2920
+ },
2921
+ sk: {
2922
+ field: "GSI1SK",
2923
+ casing: "none",
2924
+ composite: ["key", "id"],
2925
+ template: "${key}#${id}"
2926
+ }
2927
+ }
2928
+ }
2929
+ });
2930
+
2931
+ // src/data/dynamo/entities/control/membership-entity.ts
2932
+ var import_electrodb2 = require("electrodb");
2933
+ var MembershipEntity = new import_electrodb2.Entity({
2934
+ model: {
2935
+ entity: "membership",
2936
+ service: "control",
2937
+ version: "01"
2938
+ },
2939
+ attributes: {
2940
+ /** Sort key sentinel. Always "CURRENT". */
2941
+ sk: {
2942
+ type: "string",
2943
+ required: true,
2944
+ default: "CURRENT"
2945
+ },
2946
+ /** Tenant in which the user has membership (required). */
2947
+ tenantId: {
2948
+ type: "string",
2949
+ required: true
2950
+ },
2951
+ /** FHIR Resource.id; membership id. */
2952
+ id: {
2953
+ type: "string",
2954
+ required: true
2955
+ },
2956
+ /** Full Membership resource serialized as JSON string. */
2957
+ resource: {
2958
+ type: "string",
2959
+ required: true
2960
+ },
2961
+ /**
2962
+ * Summary projection (key display fields as JSON string: id, displayName, status).
2963
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
2964
+ */
2965
+ summary: {
2966
+ type: "string",
2967
+ required: true
2968
+ },
2969
+ /** Version id (e.g. ULID). */
2970
+ vid: {
2971
+ type: "string",
2972
+ required: true
2973
+ },
2974
+ lastUpdated: {
2975
+ type: "string",
2976
+ required: true
2977
+ },
2978
+ gsi1Shard: gsi1ShardAttribute,
2979
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
2980
+ gsi1sk: gsi1skAttribute,
2981
+ deleted: {
2982
+ type: "boolean",
2983
+ required: false
2984
+ },
2985
+ bundleId: {
2986
+ type: "string",
2987
+ required: false
2988
+ },
2989
+ msgId: {
2990
+ type: "string",
2991
+ required: false
2992
+ }
2993
+ },
2994
+ indexes: {
2995
+ /** Base table: PK = TID#<tenantId>#MEMBERSHIP#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
2996
+ record: {
2997
+ pk: {
2998
+ field: "PK",
2999
+ composite: ["tenantId", "id"],
3000
+ template: "TID#${tenantId}#MEMBERSHIP#ID#${id}"
3001
+ },
3002
+ sk: {
3003
+ field: "SK",
3004
+ composite: ["sk"],
3005
+ template: "${sk}"
3006
+ }
3007
+ },
3008
+ /**
3009
+ * GSI1 — Unified Sharded List per ADR-011: list all Memberships for a tenant across the
3010
+ * four shards. Membership is tenant-scoped only, so `WID#-` is a sentinel.
3011
+ * SK is derived via `gsi1skAttribute` — uses the resource's natural label when
3012
+ * extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
3013
+ * normalized label and ISO-8601 `T`/`Z`.
3014
+ */
3015
+ gsi1: {
3016
+ index: "GSI1",
3017
+ pk: {
3018
+ field: "GSI1PK",
3019
+ composite: ["tenantId", "gsi1Shard"],
3020
+ template: "TID#${tenantId}#WID#-#RT#Membership#SHARD#${gsi1Shard}"
3021
+ },
3022
+ sk: {
3023
+ field: "GSI1SK",
3024
+ casing: "none",
3025
+ composite: ["gsi1sk"],
3026
+ template: "${gsi1sk}"
3027
+ }
3028
+ }
3029
+ }
3030
+ });
3031
+
3032
+ // src/data/dynamo/entities/control/role-entity.ts
3033
+ var import_electrodb3 = require("electrodb");
3034
+ var RoleEntity = new import_electrodb3.Entity({
3035
+ model: {
3036
+ entity: "role",
3037
+ service: "control",
3038
+ version: "01"
3039
+ },
3040
+ attributes: {
3041
+ /** Sort key sentinel. Always "CURRENT". */
3042
+ sk: {
3043
+ type: "string",
3044
+ required: true,
3045
+ default: "CURRENT"
3046
+ },
3047
+ /** FHIR Resource.id; role id. */
3048
+ id: {
3049
+ type: "string",
3050
+ required: true
3051
+ },
3052
+ /** Full Role resource serialized as JSON string. */
3053
+ resource: {
3054
+ type: "string",
3055
+ required: true
3056
+ },
3057
+ /**
3058
+ * Summary projection (key display fields as JSON string: id, displayName, status).
3059
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
3060
+ */
3061
+ summary: {
3062
+ type: "string",
3063
+ required: true
3064
+ },
3065
+ /** Version id (e.g. ULID). */
3066
+ vid: {
3067
+ type: "string",
3068
+ required: true
3069
+ },
3070
+ lastUpdated: {
3071
+ type: "string",
3072
+ required: true
3073
+ },
3074
+ gsi1Shard: gsi1ShardAttribute,
3075
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
3076
+ gsi1sk: gsi1skAttribute,
3077
+ deleted: {
3078
+ type: "boolean",
3079
+ required: false
3080
+ },
3081
+ bundleId: {
3082
+ type: "string",
3083
+ required: false
3084
+ },
3085
+ msgId: {
3086
+ type: "string",
3087
+ required: false
3088
+ }
3089
+ },
3090
+ indexes: {
3091
+ /** Base table: PK = ROLE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
3092
+ record: {
3093
+ pk: {
3094
+ field: "PK",
3095
+ composite: ["id"],
3096
+ template: "ROLE#ID#${id}"
3097
+ },
3098
+ sk: {
3099
+ field: "SK",
3100
+ composite: ["sk"],
3101
+ template: "${sk}"
3102
+ }
3103
+ },
3104
+ /**
3105
+ * GSI1 — Unified Sharded List per ADR-011: list all Roles across the four shards.
3106
+ * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#Role#SHARD#<n>`.
3107
+ * SK is derived via `gsi1skAttribute` — uses the resource's natural label when
3108
+ * extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
3109
+ * normalized label and ISO-8601 `T`/`Z`.
3110
+ */
3111
+ gsi1: {
3112
+ index: "GSI1",
3113
+ pk: {
3114
+ field: "GSI1PK",
3115
+ composite: ["gsi1Shard"],
3116
+ template: "TID#-#WID#-#RT#Role#SHARD#${gsi1Shard}"
3117
+ },
3118
+ sk: {
3119
+ field: "GSI1SK",
3120
+ casing: "none",
3121
+ composite: ["gsi1sk"],
3122
+ template: "${gsi1sk}"
3123
+ }
3124
+ }
3125
+ }
3126
+ });
3127
+
3128
+ // src/data/dynamo/entities/control/roleassignment-entity.ts
3129
+ var import_electrodb4 = require("electrodb");
3130
+ var RoleAssignmentEntity = new import_electrodb4.Entity({
3131
+ model: {
3132
+ entity: "roleassignment",
3133
+ service: "control",
3134
+ version: "01"
3135
+ },
3136
+ attributes: {
3137
+ /** Sort key sentinel. Always "CURRENT". */
3138
+ sk: {
3139
+ type: "string",
3140
+ required: true,
3141
+ default: "CURRENT"
3142
+ },
3143
+ /** Tenant in which the role assignment applies (required). */
3144
+ tenantId: {
3145
+ type: "string",
3146
+ required: true
3147
+ },
3148
+ /** FHIR Resource.id; role assignment id. */
3149
+ id: {
3150
+ type: "string",
3151
+ required: true
3152
+ },
3153
+ /** Full RoleAssignment resource serialized as JSON string. */
3154
+ resource: {
3155
+ type: "string",
3156
+ required: true
3157
+ },
3158
+ /**
3159
+ * Summary projection (key display fields as JSON string: id, displayName, status).
3160
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
3161
+ */
3162
+ summary: {
3163
+ type: "string",
3164
+ required: true
3165
+ },
3166
+ /** Version id (e.g. ULID). */
3167
+ vid: {
3168
+ type: "string",
3169
+ required: true
3170
+ },
3171
+ lastUpdated: {
3172
+ type: "string",
3173
+ required: true
3174
+ },
3175
+ gsi1Shard: gsi1ShardAttribute,
3176
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
3177
+ gsi1sk: gsi1skAttribute,
3178
+ deleted: {
3179
+ type: "boolean",
3180
+ required: false
3181
+ },
3182
+ bundleId: {
3183
+ type: "string",
3184
+ required: false
3185
+ },
3186
+ msgId: {
3187
+ type: "string",
3188
+ required: false
3189
+ }
3190
+ },
3191
+ indexes: {
3192
+ /** Base table: PK = TID#<tenantId>#ROLEASSIGNMENT#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
3193
+ record: {
3194
+ pk: {
3195
+ field: "PK",
3196
+ composite: ["tenantId", "id"],
3197
+ template: "TID#${tenantId}#ROLEASSIGNMENT#ID#${id}"
3198
+ },
3199
+ sk: {
3200
+ field: "SK",
3201
+ composite: ["sk"],
3202
+ template: "${sk}"
3203
+ }
3204
+ },
3205
+ /**
3206
+ * GSI1 — Unified Sharded List per ADR-011: list all RoleAssignments for a tenant across the
3207
+ * four shards. Tenant-scoped only, so `WID#-` is a sentinel.
3208
+ * SK is derived via `gsi1skAttribute` — uses the resource's natural label when
3209
+ * extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
3210
+ * normalized label and ISO-8601 `T`/`Z`.
3211
+ */
3212
+ gsi1: {
3213
+ index: "GSI1",
3214
+ pk: {
3215
+ field: "GSI1PK",
3216
+ composite: ["tenantId", "gsi1Shard"],
3217
+ template: "TID#${tenantId}#WID#-#RT#RoleAssignment#SHARD#${gsi1Shard}"
3218
+ },
3219
+ sk: {
3220
+ field: "GSI1SK",
3221
+ casing: "none",
3222
+ composite: ["gsi1sk"],
3223
+ template: "${gsi1sk}"
3224
+ }
3225
+ }
3226
+ }
3227
+ });
3228
+
3229
+ // src/data/dynamo/entities/control/tenant-entity.ts
3230
+ var import_electrodb5 = require("electrodb");
3231
+ var TenantEntity = new import_electrodb5.Entity({
3232
+ model: {
3233
+ entity: "tenant",
3234
+ service: "control",
3235
+ version: "01"
3236
+ },
3237
+ attributes: {
3238
+ /** Sort key sentinel. Always "CURRENT". */
3239
+ sk: {
3240
+ type: "string",
3241
+ required: true,
3242
+ default: "CURRENT"
3243
+ },
3244
+ /** The tenant's own id (= resource id). Drives the partition key. */
3245
+ tenantId: {
3246
+ type: "string",
3247
+ required: true
3248
+ },
3249
+ /** FHIR Resource.id; logical id in URL. Equals tenantId. */
3250
+ id: {
3251
+ type: "string",
3252
+ required: true
3253
+ },
3254
+ /** Full Tenant resource serialized as JSON string. */
3255
+ resource: {
3256
+ type: "string",
3257
+ required: true
3258
+ },
3259
+ /**
3260
+ * Summary projection (key display fields as JSON string: id, displayName, status).
3261
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
3262
+ */
3263
+ summary: {
3264
+ type: "string",
3265
+ required: true
3266
+ },
3267
+ /** Version id (e.g. ULID). */
3268
+ vid: {
3269
+ type: "string",
3270
+ required: true
3271
+ },
3272
+ lastUpdated: {
3273
+ type: "string",
3274
+ required: true
3275
+ },
3276
+ gsi1Shard: gsi1ShardAttribute,
3277
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
3278
+ gsi1sk: gsi1skAttribute,
3279
+ deleted: {
3280
+ type: "boolean",
3281
+ required: false
3282
+ },
3283
+ bundleId: {
3284
+ type: "string",
3285
+ required: false
3286
+ },
3287
+ msgId: {
3288
+ type: "string",
3289
+ required: false
3290
+ }
3291
+ },
3292
+ indexes: {
3293
+ /** Base table: PK = TENANT#ID#<tenantId>, SK = CURRENT. Do not supply PK or SK from outside. */
3294
+ record: {
3295
+ pk: {
3296
+ field: "PK",
3297
+ composite: ["tenantId"],
3298
+ template: "TENANT#ID#${tenantId}"
3299
+ },
3300
+ sk: {
3301
+ field: "SK",
3302
+ composite: ["sk"],
3303
+ template: "${sk}"
3304
+ }
3305
+ },
3306
+ /**
3307
+ * GSI1 — Unified Sharded List per ADR-011: list all Tenants across the four shards.
3308
+ * Tenant lives at the platform tier (no parent tenant or workspace), so `TID#-#WID#-`
3309
+ * sentinels precede `RT#Tenant#SHARD#<n>`. SK is derived via `gsi1skAttribute` —
3310
+ * `<normalizedName>#<id>` when the resource carries a `name`, else `<lastUpdated>#<id>`
3311
+ * (DR-004). `casing: "none"` preserves the normalized label and ISO-8601 `T`/`Z`.
3312
+ */
3313
+ gsi1: {
3314
+ index: "GSI1",
3315
+ pk: {
3316
+ field: "GSI1PK",
3317
+ composite: ["gsi1Shard"],
3318
+ template: "TID#-#WID#-#RT#Tenant#SHARD#${gsi1Shard}"
3319
+ },
3320
+ sk: {
3321
+ field: "GSI1SK",
3322
+ casing: "none",
3323
+ composite: ["gsi1sk"],
3324
+ template: "${gsi1sk}"
3325
+ }
3326
+ }
3327
+ }
3328
+ });
3329
+
3330
+ // src/data/dynamo/entities/control/user-entity.ts
3331
+ var import_electrodb6 = require("electrodb");
3332
+ var UserEntity = new import_electrodb6.Entity({
3333
+ model: {
3334
+ entity: "user",
3335
+ service: "control",
3336
+ version: "01"
3337
+ },
3338
+ attributes: {
3339
+ /** Sort key sentinel. Always "CURRENT". */
3340
+ sk: {
3341
+ type: "string",
3342
+ required: true,
3343
+ default: "CURRENT"
3344
+ },
3345
+ /** FHIR Resource.id; platform user id (ohi_uid). */
3346
+ id: {
3347
+ type: "string",
3348
+ required: true
3349
+ },
3350
+ /** Full User resource serialized as JSON string. */
3351
+ resource: {
3352
+ type: "string",
3353
+ required: true
3354
+ },
3355
+ /**
3356
+ * Summary projection (key display fields as JSON string: id, displayName, status).
3357
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
3358
+ */
3359
+ summary: {
3360
+ type: "string",
3361
+ required: true
3362
+ },
3363
+ /**
3364
+ * Immutable Cognito-issued `sub` claim. Drives GSI2 (sub-lookup). Optional until the
3365
+ * Post Confirmation Lambda (#770) lands; required thereafter.
3366
+ */
3367
+ cognitoSub: {
3368
+ type: "string",
3369
+ required: false
3370
+ },
3371
+ /** Version id (e.g. ULID). */
3372
+ vid: {
3373
+ type: "string",
3374
+ required: true
3375
+ },
3376
+ lastUpdated: {
3377
+ type: "string",
3378
+ required: true
3379
+ },
3380
+ gsi1Shard: gsi1ShardAttribute,
3381
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
3382
+ gsi1sk: gsi1skAttribute,
3383
+ deleted: {
3384
+ type: "boolean",
3385
+ required: false
3386
+ },
3387
+ bundleId: {
3388
+ type: "string",
3389
+ required: false
3390
+ },
3391
+ msgId: {
3392
+ type: "string",
3393
+ required: false
3394
+ }
3395
+ },
3396
+ indexes: {
3397
+ /** Base table: PK = USER#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
3398
+ record: {
3399
+ pk: {
3400
+ field: "PK",
3401
+ composite: ["id"],
3402
+ template: "USER#ID#${id}"
3403
+ },
3404
+ sk: {
3405
+ field: "SK",
3406
+ composite: ["sk"],
3407
+ template: "${sk}"
3408
+ }
3409
+ },
3410
+ /**
3411
+ * GSI1 — Unified Sharded List per ADR-011: list all Users across the four shards.
3412
+ * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#User#SHARD#<n>`.
3413
+ * SK is derived via `gsi1skAttribute` — uses the resource's natural label when
3414
+ * extractable (string `name`/`title` via introspection), else `<lastUpdated>#<id>`
3415
+ * (DR-004). `casing: "none"` preserves the normalized label and ISO-8601 `T`/`Z`.
3416
+ */
3417
+ gsi1: {
3418
+ index: "GSI1",
3419
+ pk: {
3420
+ field: "GSI1PK",
3421
+ composite: ["gsi1Shard"],
3422
+ template: "TID#-#WID#-#RT#User#SHARD#${gsi1Shard}"
3423
+ },
3424
+ sk: {
3425
+ field: "GSI1SK",
3426
+ casing: "none",
3427
+ composite: ["gsi1sk"],
3428
+ template: "${gsi1sk}"
3429
+ }
3430
+ },
3431
+ /**
3432
+ * GSI2 — Cognito sub-lookup per ADR-011: resolves the UserEntity from a Cognito `sub` claim.
3433
+ * `condition` skips the index when `cognitoSub` is missing so legacy items without a sub are
3434
+ * not indexed.
3435
+ */
3436
+ gsi2: {
3437
+ index: "GSI2",
3438
+ condition: (attrs) => typeof attrs.cognitoSub === "string" && attrs.cognitoSub.length > 0,
3439
+ pk: {
3440
+ field: "GSI2PK",
3441
+ casing: "none",
3442
+ composite: ["cognitoSub"],
3443
+ template: "USER#SUB#${cognitoSub}"
3444
+ },
3445
+ sk: {
3446
+ field: "GSI2SK",
3447
+ casing: "none",
3448
+ composite: [],
3449
+ template: "CURRENT"
3450
+ }
3451
+ }
3452
+ }
3453
+ });
3454
+
3455
+ // src/data/dynamo/entities/control/workspace-entity.ts
3456
+ var import_electrodb7 = require("electrodb");
3457
+ var WorkspaceEntity = new import_electrodb7.Entity({
3458
+ model: {
3459
+ entity: "workspace",
3460
+ service: "control",
3461
+ version: "01"
3462
+ },
3463
+ attributes: {
3464
+ /** Sort key sentinel. Always "CURRENT". */
3465
+ sk: {
3466
+ type: "string",
3467
+ required: true,
3468
+ default: "CURRENT"
3469
+ },
3470
+ /** Tenant that contains this workspace (required). */
3471
+ tenantId: {
3472
+ type: "string",
3473
+ required: true
3474
+ },
3475
+ /** FHIR Resource.id; logical id in URL. */
3476
+ id: {
3477
+ type: "string",
3478
+ required: true
3479
+ },
3480
+ /** Full Workspace resource serialized as JSON string. */
3481
+ resource: {
3482
+ type: "string",
3483
+ required: true
3484
+ },
3485
+ /**
3486
+ * Summary projection (key display fields as JSON string: id, displayName, status).
3487
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
3488
+ */
3489
+ summary: {
3490
+ type: "string",
3491
+ required: true
3492
+ },
3493
+ /** Version id (e.g. ULID). */
3494
+ vid: {
3495
+ type: "string",
3496
+ required: true
3497
+ },
3498
+ lastUpdated: {
3499
+ type: "string",
3500
+ required: true
3501
+ },
3502
+ gsi1Shard: gsi1ShardAttribute,
3503
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
3504
+ gsi1sk: gsi1skAttribute,
3505
+ deleted: {
3506
+ type: "boolean",
3507
+ required: false
3508
+ },
3509
+ bundleId: {
3510
+ type: "string",
3511
+ required: false
3512
+ },
3513
+ msgId: {
3514
+ type: "string",
3515
+ required: false
3516
+ }
3517
+ },
3518
+ indexes: {
3519
+ /** Base table: PK = TID#<tenantId>#WORKSPACE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
3520
+ record: {
3521
+ pk: {
3522
+ field: "PK",
3523
+ composite: ["tenantId", "id"],
3524
+ template: "TID#${tenantId}#WORKSPACE#ID#${id}"
3525
+ },
3526
+ sk: {
3527
+ field: "SK",
3528
+ composite: ["sk"],
3529
+ template: "${sk}"
3530
+ }
3531
+ },
3532
+ /**
3533
+ * GSI1 — Unified Sharded List per ADR-011: list all Workspaces for a tenant across the
3534
+ * four shards. Workspace is itself the workspace identity, so `WID#-` is a sentinel.
3535
+ * SK is derived via `gsi1skAttribute` — `<normalizedName>#<id>` when the resource
3536
+ * carries a `name`, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves
3537
+ * the normalized label and ISO-8601 `T`/`Z`.
3538
+ */
3539
+ gsi1: {
3540
+ index: "GSI1",
3541
+ pk: {
3542
+ field: "GSI1PK",
3543
+ composite: ["tenantId", "gsi1Shard"],
3544
+ template: "TID#${tenantId}#WID#-#RT#Workspace#SHARD#${gsi1Shard}"
3545
+ },
3546
+ sk: {
3547
+ field: "GSI1SK",
3548
+ casing: "none",
3549
+ composite: ["gsi1sk"],
3550
+ template: "${gsi1sk}"
3551
+ }
3552
+ }
3553
+ }
3554
+ });
3555
+
3556
+ // src/data/dynamo/dynamo-control-service.ts
3557
+ var controlPlaneEntities = {
3558
+ configuration: ConfigurationEntity,
3559
+ membership: MembershipEntity,
3560
+ role: RoleEntity,
3561
+ roleAssignment: RoleAssignmentEntity,
3562
+ tenant: TenantEntity,
3563
+ user: UserEntity,
3564
+ workspace: WorkspaceEntity
3565
+ };
3566
+ var controlPlaneService = new import_electrodb8.Service(controlPlaneEntities, {
3567
+ table: defaultTableName,
3568
+ client: dynamoClient
3569
+ });
3570
+ var DynamoControlService = {
3571
+ entities: controlPlaneService.entities
3572
+ };
3573
+
3574
+ // src/data/operations/control/membership/membership-create-operation.ts
3575
+ var import_types3 = require("@openhi/types");
3576
+
3577
+ // src/data/operations/control/roleassignment/roleassignment-create-operation.ts
3578
+ var import_types4 = require("@openhi/types");
3579
+
3580
+ // src/data/operations/control/tenant/tenant-create-operation.ts
3581
+ var import_types5 = require("@openhi/types");
3582
+
3583
+ // src/data/operations/control/workspace/workspace-create-operation.ts
3584
+ var import_types6 = require("@openhi/types");
3585
+
3586
+ // src/workflows/control-plane/seed-demo-data/seed-demo-data.handler.ts
3587
+ var SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR = "SEED_DEMO_DATA_USER_POOL_ID";
3588
+
3589
+ // src/workflows/control-plane/seed-demo-data/seed-demo-data-lambda.ts
3590
+ var HANDLER_NAME7 = "seed-demo-data.handler.js";
3591
+ function resolveHandlerEntry7(dirname) {
3592
+ const sameDir = import_node_path7.default.join(dirname, HANDLER_NAME7);
3593
+ if (import_node_fs7.default.existsSync(sameDir)) {
3594
+ return sameDir;
1603
3595
  }
3596
+ return import_node_path7.default.join(dirname, "..", "..", "..", "..", "lib", HANDLER_NAME7);
3597
+ }
3598
+ var SeedDemoDataLambda = class extends import_constructs11.Construct {
3599
+ constructor(scope, props) {
3600
+ super(scope, "seed-demo-data-lambda");
3601
+ this.lambda = new import_aws_lambda_nodejs7.NodejsFunction(this, "handler", {
3602
+ entry: resolveHandlerEntry7(__dirname),
3603
+ runtime: import_aws_lambda7.Runtime.NODEJS_LATEST,
3604
+ memorySize: 512,
3605
+ timeout: import_aws_cdk_lib12.Duration.minutes(2),
3606
+ environment: {
3607
+ DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,
3608
+ [SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR]: props.userPool.userPoolId
3609
+ }
3610
+ });
3611
+ const roleReadKeys = Object.values(import_types8.CONTROL_PLANE_ROLE_IDS).map(
3612
+ rolePartitionKey
3613
+ );
3614
+ this.lambda.addToRolePolicy(
3615
+ new import_aws_iam3.PolicyStatement({
3616
+ effect: import_aws_iam3.Effect.ALLOW,
3617
+ actions: ["dynamodb:GetItem"],
3618
+ resources: [props.dataStoreTable.tableArn],
3619
+ conditions: {
3620
+ "ForAllValues:StringEquals": {
3621
+ "dynamodb:LeadingKeys": roleReadKeys
3622
+ }
3623
+ }
3624
+ })
3625
+ );
3626
+ const writeKeys = [
3627
+ ...demoBasePartitionKeys(),
3628
+ ...demoDevUserPartitionKeys(DEV_USERS)
3629
+ ];
3630
+ this.lambda.addToRolePolicy(
3631
+ new import_aws_iam3.PolicyStatement({
3632
+ effect: import_aws_iam3.Effect.ALLOW,
3633
+ actions: ["dynamodb:PutItem", "dynamodb:UpdateItem"],
3634
+ resources: [props.dataStoreTable.tableArn],
3635
+ conditions: {
3636
+ "ForAllValues:StringEquals": {
3637
+ "dynamodb:LeadingKeys": writeKeys
3638
+ }
3639
+ }
3640
+ })
3641
+ );
3642
+ this.lambda.addToRolePolicy(
3643
+ new import_aws_iam3.PolicyStatement({
3644
+ effect: import_aws_iam3.Effect.ALLOW,
3645
+ actions: [
3646
+ "cognito-idp:AdminCreateUser",
3647
+ "cognito-idp:AdminGetUser",
3648
+ "cognito-idp:AdminSetUserPassword"
3649
+ ],
3650
+ resources: [
3651
+ import_aws_cdk_lib12.Stack.of(this).formatArn({
3652
+ service: "cognito-idp",
3653
+ resource: "userpool",
3654
+ resourceName: props.userPool.userPoolId
3655
+ })
3656
+ ]
3657
+ })
3658
+ );
3659
+ this.rule = new import_aws_events6.Rule(this, "rule", {
3660
+ eventBus: props.controlEventBus,
3661
+ eventPattern: {
3662
+ source: [import_workflows2.PlatformSystemDataSeededV1.source],
3663
+ detailType: [import_workflows2.PlatformSystemDataSeededV1.detailType]
3664
+ },
3665
+ targets: [
3666
+ new import_aws_events_targets2.LambdaFunction(this.lambda, {
3667
+ retryAttempts: 2,
3668
+ maxEventAge: import_aws_cdk_lib12.Duration.hours(2)
3669
+ })
3670
+ ]
3671
+ });
3672
+ }
3673
+ };
3674
+
3675
+ // src/workflows/control-plane/seed-demo-data/seed-demo-data-workflow.ts
3676
+ var import_constructs12 = require("constructs");
3677
+ var SeedDemoDataWorkflow = class extends import_constructs12.Construct {
3678
+ constructor(scope, props) {
3679
+ super(scope, "seed-demo-data-workflow");
3680
+ this.seedDemoData = new SeedDemoDataLambda(this, {
3681
+ controlEventBus: props.controlEventBus,
3682
+ dataStoreTable: props.dataStoreTable,
3683
+ userPool: props.userPool
3684
+ });
3685
+ WorkflowDedupTable.grantConsumerFromLookup(
3686
+ this,
3687
+ this.seedDemoData.lambda,
3688
+ SEED_DEMO_DATA_CONSUMER_NAME
3689
+ );
3690
+ }
3691
+ };
3692
+
3693
+ // src/workflows/control-plane/seed-system-data/events.ts
3694
+ var import_workflows4 = __toESM(require_lib2());
3695
+ var SEED_SYSTEM_DATA_CONSUMER_NAME = "seed-system-data";
3696
+ var SEED_SYSTEM_DATA_ACTOR_SYSTEM = "seed-system-data";
3697
+ var SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR = "CONTROL_EVENT_BUS_NAME";
3698
+
3699
+ // src/workflows/control-plane/seed-system-data/seed-system-data-lambda.ts
3700
+ var import_node_fs8 = __toESM(require("fs"));
3701
+ var import_node_path8 = __toESM(require("path"));
3702
+ var import_types9 = require("@openhi/types");
3703
+ var import_aws_cdk_lib13 = require("aws-cdk-lib");
3704
+ var import_aws_events7 = require("aws-cdk-lib/aws-events");
3705
+ var import_aws_events_targets3 = require("aws-cdk-lib/aws-events-targets");
3706
+ var import_aws_iam4 = require("aws-cdk-lib/aws-iam");
3707
+ var import_aws_lambda8 = require("aws-cdk-lib/aws-lambda");
3708
+ var import_aws_lambda_nodejs8 = require("aws-cdk-lib/aws-lambda-nodejs");
3709
+ var import_constructs13 = require("constructs");
3710
+ var HANDLER_NAME8 = "seed-system-data.handler.js";
3711
+ function resolveHandlerEntry8(dirname) {
3712
+ const sameDir = import_node_path8.default.join(dirname, HANDLER_NAME8);
3713
+ if (import_node_fs8.default.existsSync(sameDir)) {
3714
+ return sameDir;
3715
+ }
3716
+ return import_node_path8.default.join(dirname, "..", "..", "..", "..", "lib", HANDLER_NAME8);
3717
+ }
3718
+ var SeedSystemDataLambda = class extends import_constructs13.Construct {
3719
+ constructor(scope, props) {
3720
+ super(scope, "seed-system-data-lambda");
3721
+ this.lambda = new import_aws_lambda_nodejs8.NodejsFunction(this, "handler", {
3722
+ entry: resolveHandlerEntry8(__dirname),
3723
+ runtime: import_aws_lambda8.Runtime.NODEJS_LATEST,
3724
+ memorySize: 512,
3725
+ timeout: import_aws_cdk_lib13.Duration.minutes(1),
3726
+ environment: {
3727
+ DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,
3728
+ [SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR]: props.controlEventBus.eventBusName
3729
+ }
3730
+ });
3731
+ const roleArns = Object.values(import_types9.CONTROL_PLANE_ROLE_IDS).map(
3732
+ (id) => `role#id#${id}`
3733
+ );
3734
+ this.lambda.addToRolePolicy(
3735
+ new import_aws_iam4.PolicyStatement({
3736
+ effect: import_aws_iam4.Effect.ALLOW,
3737
+ actions: ["dynamodb:PutItem", "dynamodb:UpdateItem"],
3738
+ resources: [props.dataStoreTable.tableArn],
3739
+ conditions: {
3740
+ "ForAllValues:StringEquals": {
3741
+ "dynamodb:LeadingKeys": roleArns
3742
+ }
3743
+ }
3744
+ })
3745
+ );
3746
+ props.controlEventBus.grantPutEventsTo(this.lambda);
3747
+ const hostStackName = import_aws_cdk_lib13.Stack.of(this).stackName;
3748
+ this.rule = new import_aws_events7.Rule(this, "rule", {
3749
+ eventBus: props.controlEventBus,
3750
+ eventPattern: {
3751
+ source: [import_workflows4.PlatformDeploymentCompletedV1.source],
3752
+ detailType: [import_workflows4.PlatformDeploymentCompletedV1.detailType],
3753
+ detail: {
3754
+ payload: {
3755
+ stackName: [hostStackName]
3756
+ }
3757
+ }
3758
+ },
3759
+ targets: [
3760
+ new import_aws_events_targets3.LambdaFunction(this.lambda, {
3761
+ retryAttempts: 2,
3762
+ maxEventAge: import_aws_cdk_lib13.Duration.hours(2)
3763
+ })
3764
+ ]
3765
+ });
3766
+ }
3767
+ };
3768
+
3769
+ // src/workflows/control-plane/seed-system-data/seed-system-data-workflow.ts
3770
+ var import_constructs14 = require("constructs");
3771
+ var SeedSystemDataWorkflow = class extends import_constructs14.Construct {
3772
+ constructor(scope, props) {
3773
+ super(scope, "seed-system-data-workflow");
3774
+ this.seedSystemData = new SeedSystemDataLambda(this, {
3775
+ controlEventBus: props.controlEventBus,
3776
+ dataStoreTable: props.dataStoreTable
3777
+ });
3778
+ WorkflowDedupTable.grantConsumerFromLookup(
3779
+ this,
3780
+ this.seedSystemData.lambda,
3781
+ SEED_SYSTEM_DATA_CONSUMER_NAME
3782
+ );
3783
+ }
3784
+ };
3785
+
3786
+ // src/services/open-hi-data-service.ts
3787
+ var _OpenHiDataService = class _OpenHiDataService extends OpenHiService {
1604
3788
  constructor(ohEnv, props = {}) {
1605
3789
  super(ohEnv, _OpenHiDataService.SERVICE_TYPE, props);
3790
+ /**
3791
+ * Cached control-event-bus lookup. `OpenHiGlobalService.controlEventBusFromConstruct`
3792
+ * registers a child `EventBus.fromEventBusName` construct with a
3793
+ * fixed id under the scope it is passed, so calling it twice on the
3794
+ * same `OpenHiDataService` instance collides. The cache mirrors the
3795
+ * `private controlEventBus()` pattern already used in
3796
+ * `OpenHiAuthService`. Use {@link controlEventBus} from this class
3797
+ * — never call the static lookup from inside `OpenHiDataService`.
3798
+ */
3799
+ this._controlEventBus = null;
1606
3800
  this.props = props;
1607
3801
  this.dataStoreChangeStream = new kinesis.Stream(
1608
3802
  this,
@@ -1637,6 +3831,53 @@ var _OpenHiDataService = class _OpenHiDataService extends OpenHiService {
1637
3831
  branchHash: this.branchHash
1638
3832
  }
1639
3833
  );
3834
+ this.seedSystemDataWorkflow = this.createSeedSystemDataWorkflow();
3835
+ this.seedDemoDataWorkflow = this.createSeedDemoDataWorkflow();
3836
+ }
3837
+ /**
3838
+ * Returns the data store table by name. Use from other stacks (e.g. REST API Lambda) to obtain an ITable reference.
3839
+ */
3840
+ static dynamoDbDataStoreFromConstruct(scope, id = "dynamo-db-data-store") {
3841
+ return import_aws_dynamodb3.Table.fromTableName(scope, id, getDynamoDbDataStoreTableName(scope));
3842
+ }
3843
+ get serviceType() {
3844
+ return _OpenHiDataService.SERVICE_TYPE;
3845
+ }
3846
+ /**
3847
+ * Lazily looks up the control event bus exactly once per
3848
+ * `OpenHiDataService` instance and caches the reference. Every
3849
+ * workflow that consumes the bus must read it through this method
3850
+ * — see {@link _controlEventBus} for the underlying collision risk.
3851
+ */
3852
+ controlEventBus() {
3853
+ if (this._controlEventBus === null) {
3854
+ this._controlEventBus = OpenHiGlobalService.controlEventBusFromConstruct(this);
3855
+ }
3856
+ return this._controlEventBus;
3857
+ }
3858
+ /**
3859
+ * Creates the seed-system-data workflow. Override to customize.
3860
+ */
3861
+ createSeedSystemDataWorkflow() {
3862
+ return new SeedSystemDataWorkflow(this, {
3863
+ controlEventBus: this.controlEventBus(),
3864
+ dataStoreTable: this.dataStore
3865
+ });
3866
+ }
3867
+ /**
3868
+ * Creates the seed-demo-data workflow — but only on non-prod
3869
+ * stages. Returns `undefined` on prod so the workflow literally
3870
+ * does not exist in prod stacks. Override to customize.
3871
+ */
3872
+ createSeedDemoDataWorkflow() {
3873
+ if (this.ohEnv.ohStage.stageType === import_config4.OPEN_HI_STAGE.PROD) {
3874
+ return void 0;
3875
+ }
3876
+ return new SeedDemoDataWorkflow(this, {
3877
+ controlEventBus: this.controlEventBus(),
3878
+ dataStoreTable: this.dataStore,
3879
+ userPool: OpenHiAuthService.userPoolFromConstruct(this)
3880
+ });
1640
3881
  }
1641
3882
  /**
1642
3883
  * Creates the single-table DynamoDB data store.
@@ -1645,7 +3886,7 @@ var _OpenHiDataService = class _OpenHiDataService extends OpenHiService {
1645
3886
  createDataStore() {
1646
3887
  return new DynamoDbDataStore(this, "dynamo-db-data-store", {
1647
3888
  kinesisStream: this.dataStoreChangeStream,
1648
- stream: import_aws_dynamodb2.StreamViewType.NEW_AND_OLD_IMAGES
3889
+ stream: import_aws_dynamodb3.StreamViewType.NEW_AND_OLD_IMAGES
1649
3890
  });
1650
3891
  }
1651
3892
  };
@@ -1678,29 +3919,29 @@ var buildProvisionDefaultWorkspaceRequestedDetail = (event) => {
1678
3919
  };
1679
3920
 
1680
3921
  // src/workflows/control-plane/user-onboarding/provision-default-workspace-lambda.ts
1681
- var import_node_fs6 = __toESM(require("fs"));
1682
- var import_node_path6 = __toESM(require("path"));
1683
- var import_aws_cdk_lib10 = require("aws-cdk-lib");
1684
- var import_aws_events5 = require("aws-cdk-lib/aws-events");
1685
- var import_aws_events_targets = require("aws-cdk-lib/aws-events-targets");
1686
- var import_aws_iam = require("aws-cdk-lib/aws-iam");
1687
- var import_aws_lambda6 = require("aws-cdk-lib/aws-lambda");
1688
- var import_aws_lambda_nodejs6 = require("aws-cdk-lib/aws-lambda-nodejs");
1689
- var import_constructs8 = require("constructs");
1690
- var HANDLER_NAME6 = "provision-default-workspace.handler.js";
1691
- function resolveHandlerEntry6(dirname) {
1692
- const sameDir = import_node_path6.default.join(dirname, HANDLER_NAME6);
1693
- if (import_node_fs6.default.existsSync(sameDir)) {
3922
+ var import_node_fs9 = __toESM(require("fs"));
3923
+ var import_node_path9 = __toESM(require("path"));
3924
+ var import_aws_cdk_lib14 = require("aws-cdk-lib");
3925
+ var import_aws_events8 = require("aws-cdk-lib/aws-events");
3926
+ var import_aws_events_targets4 = require("aws-cdk-lib/aws-events-targets");
3927
+ var import_aws_iam5 = require("aws-cdk-lib/aws-iam");
3928
+ var import_aws_lambda9 = require("aws-cdk-lib/aws-lambda");
3929
+ var import_aws_lambda_nodejs9 = require("aws-cdk-lib/aws-lambda-nodejs");
3930
+ var import_constructs15 = require("constructs");
3931
+ var HANDLER_NAME9 = "provision-default-workspace.handler.js";
3932
+ function resolveHandlerEntry9(dirname) {
3933
+ const sameDir = import_node_path9.default.join(dirname, HANDLER_NAME9);
3934
+ if (import_node_fs9.default.existsSync(sameDir)) {
1694
3935
  return sameDir;
1695
3936
  }
1696
- return import_node_path6.default.join(dirname, "..", "..", "..", "..", "lib", HANDLER_NAME6);
3937
+ return import_node_path9.default.join(dirname, "..", "..", "..", "..", "lib", HANDLER_NAME9);
1697
3938
  }
1698
- var ProvisionDefaultWorkspaceLambda = class extends import_constructs8.Construct {
3939
+ var ProvisionDefaultWorkspaceLambda = class extends import_constructs15.Construct {
1699
3940
  constructor(scope, props) {
1700
3941
  super(scope, "provision-default-workspace-lambda");
1701
- this.lambda = new import_aws_lambda_nodejs6.NodejsFunction(this, "handler", {
1702
- entry: resolveHandlerEntry6(__dirname),
1703
- runtime: import_aws_lambda6.Runtime.NODEJS_LATEST,
3942
+ this.lambda = new import_aws_lambda_nodejs9.NodejsFunction(this, "handler", {
3943
+ entry: resolveHandlerEntry9(__dirname),
3944
+ runtime: import_aws_lambda9.Runtime.NODEJS_LATEST,
1704
3945
  memorySize: 1024,
1705
3946
  environment: {
1706
3947
  DYNAMO_TABLE_NAME: props.dataStoreTable.tableName
@@ -1712,22 +3953,22 @@ var ProvisionDefaultWorkspaceLambda = class extends import_constructs8.Construct
1712
3953
  "dynamodb:UpdateItem"
1713
3954
  );
1714
3955
  this.lambda.addToRolePolicy(
1715
- new import_aws_iam.PolicyStatement({
1716
- effect: import_aws_iam.Effect.ALLOW,
3956
+ new import_aws_iam5.PolicyStatement({
3957
+ effect: import_aws_iam5.Effect.ALLOW,
1717
3958
  actions: ["dynamodb:Query"],
1718
3959
  resources: [`${props.dataStoreTable.tableArn}/index/*`]
1719
3960
  })
1720
3961
  );
1721
- this.rule = new import_aws_events5.Rule(this, "rule", {
3962
+ this.rule = new import_aws_events8.Rule(this, "rule", {
1722
3963
  eventBus: props.controlEventBus,
1723
3964
  eventPattern: {
1724
3965
  source: [USER_ONBOARDING_EVENT_SOURCE],
1725
3966
  detailType: [PROVISION_DEFAULT_WORKSPACE_DETAIL_TYPE]
1726
3967
  },
1727
3968
  targets: [
1728
- new import_aws_events_targets.LambdaFunction(this.lambda, {
3969
+ new import_aws_events_targets4.LambdaFunction(this.lambda, {
1729
3970
  retryAttempts: 2,
1730
- maxEventAge: import_aws_cdk_lib10.Duration.hours(2)
3971
+ maxEventAge: import_aws_cdk_lib14.Duration.hours(2)
1731
3972
  })
1732
3973
  ]
1733
3974
  });
@@ -1735,8 +3976,8 @@ var ProvisionDefaultWorkspaceLambda = class extends import_constructs8.Construct
1735
3976
  };
1736
3977
 
1737
3978
  // src/workflows/control-plane/user-onboarding/user-onboarding-workflow.ts
1738
- var import_constructs9 = require("constructs");
1739
- var UserOnboardingWorkflow = class extends import_constructs9.Construct {
3979
+ var import_constructs16 = require("constructs");
3980
+ var UserOnboardingWorkflow = class extends import_constructs16.Construct {
1740
3981
  constructor(scope, props) {
1741
3982
  super(scope, "user-onboarding-workflow");
1742
3983
  this.provisionDefaultWorkspace = new ProvisionDefaultWorkspaceLambda(this, {
@@ -1951,8 +4192,8 @@ var _OpenHiAuthService = class _OpenHiAuthService extends OpenHiService {
1951
4192
  const dynamoActions = ["dynamodb:GetItem", "dynamodb:Query"];
1952
4193
  dataStoreTable.grant(this.preTokenGenerationLambda, ...dynamoActions);
1953
4194
  this.preTokenGenerationLambda.addToRolePolicy(
1954
- new import_aws_iam2.PolicyStatement({
1955
- effect: import_aws_iam2.Effect.ALLOW,
4195
+ new import_aws_iam6.PolicyStatement({
4196
+ effect: import_aws_iam6.Effect.ALLOW,
1956
4197
  actions: [...dynamoActions],
1957
4198
  resources: [`${dataStoreTable.tableArn}/index/*`]
1958
4199
  })
@@ -1973,7 +4214,7 @@ var _OpenHiAuthService = class _OpenHiAuthService extends OpenHiService {
1973
4214
  */
1974
4215
  grantPostAuthenticationPermissions() {
1975
4216
  this.postAuthenticationLambda.addToRolePolicy(
1976
- new import_aws_iam2.PolicyStatement({
4217
+ new import_aws_iam6.PolicyStatement({
1977
4218
  actions: ["cognito-idp:AdminUserGlobalSignOut"],
1978
4219
  resources: [
1979
4220
  import_core2.Stack.of(this).formatArn({
@@ -2020,7 +4261,7 @@ var _OpenHiAuthService = class _OpenHiAuthService extends OpenHiService {
2020
4261
  * via env vars to drive `InitiateAuth`.
2021
4262
  */
2022
4263
  createFixtureSeederClient() {
2023
- if (this.ohEnv.ohStage.stageType === import_config4.OPEN_HI_STAGE.PROD) {
4264
+ if (this.ohEnv.ohStage.stageType === import_config5.OPEN_HI_STAGE.PROD) {
2024
4265
  return void 0;
2025
4266
  }
2026
4267
  const client = new CognitoFixtureSeederClient(this, {
@@ -2057,62 +4298,62 @@ _OpenHiAuthService.SERVICE_TYPE = "auth";
2057
4298
  var OpenHiAuthService = _OpenHiAuthService;
2058
4299
 
2059
4300
  // src/services/open-hi-rest-api-service.ts
2060
- var import_config5 = __toESM(require_lib());
4301
+ var import_config6 = __toESM(require_lib());
2061
4302
  var import_aws_apigatewayv22 = require("aws-cdk-lib/aws-apigatewayv2");
2062
4303
  var import_aws_apigatewayv2_authorizers = require("aws-cdk-lib/aws-apigatewayv2-authorizers");
2063
4304
  var import_aws_apigatewayv2_integrations = require("aws-cdk-lib/aws-apigatewayv2-integrations");
2064
- var import_aws_iam3 = require("aws-cdk-lib/aws-iam");
4305
+ var import_aws_iam7 = require("aws-cdk-lib/aws-iam");
2065
4306
  var import_aws_route533 = require("aws-cdk-lib/aws-route53");
2066
4307
  var import_aws_route53_targets = require("aws-cdk-lib/aws-route53-targets");
2067
4308
  var import_core3 = require("aws-cdk-lib/core");
2068
4309
 
2069
4310
  // src/data/lambda/cors-options-lambda.ts
2070
- var import_node_fs7 = __toESM(require("fs"));
2071
- var import_node_path7 = __toESM(require("path"));
2072
- var import_aws_lambda7 = require("aws-cdk-lib/aws-lambda");
2073
- var import_aws_lambda_nodejs7 = require("aws-cdk-lib/aws-lambda-nodejs");
2074
- var import_constructs10 = require("constructs");
2075
- var HANDLER_NAME7 = "cors-options-lambda.handler.js";
2076
- function resolveHandlerEntry7(dirname) {
2077
- const sameDir = import_node_path7.default.join(dirname, HANDLER_NAME7);
2078
- if (import_node_fs7.default.existsSync(sameDir)) {
4311
+ var import_node_fs10 = __toESM(require("fs"));
4312
+ var import_node_path10 = __toESM(require("path"));
4313
+ var import_aws_lambda10 = require("aws-cdk-lib/aws-lambda");
4314
+ var import_aws_lambda_nodejs10 = require("aws-cdk-lib/aws-lambda-nodejs");
4315
+ var import_constructs17 = require("constructs");
4316
+ var HANDLER_NAME10 = "cors-options-lambda.handler.js";
4317
+ function resolveHandlerEntry10(dirname) {
4318
+ const sameDir = import_node_path10.default.join(dirname, HANDLER_NAME10);
4319
+ if (import_node_fs10.default.existsSync(sameDir)) {
2079
4320
  return sameDir;
2080
4321
  }
2081
- const fromLib = import_node_path7.default.join(dirname, "..", "..", "..", "lib", HANDLER_NAME7);
4322
+ const fromLib = import_node_path10.default.join(dirname, "..", "..", "..", "lib", HANDLER_NAME10);
2082
4323
  return fromLib;
2083
4324
  }
2084
- var CorsOptionsLambda = class extends import_constructs10.Construct {
4325
+ var CorsOptionsLambda = class extends import_constructs17.Construct {
2085
4326
  constructor(scope, id = "cors-options-lambda") {
2086
4327
  super(scope, id);
2087
- this.lambda = new import_aws_lambda_nodejs7.NodejsFunction(this, "handler", {
2088
- entry: resolveHandlerEntry7(__dirname),
2089
- runtime: import_aws_lambda7.Runtime.NODEJS_LATEST,
4328
+ this.lambda = new import_aws_lambda_nodejs10.NodejsFunction(this, "handler", {
4329
+ entry: resolveHandlerEntry10(__dirname),
4330
+ runtime: import_aws_lambda10.Runtime.NODEJS_LATEST,
2090
4331
  memorySize: 128
2091
4332
  });
2092
4333
  }
2093
4334
  };
2094
4335
 
2095
4336
  // src/data/lambda/rest-api-lambda.ts
2096
- var import_node_fs8 = __toESM(require("fs"));
2097
- var import_node_path8 = __toESM(require("path"));
2098
- var import_aws_lambda8 = require("aws-cdk-lib/aws-lambda");
2099
- var import_aws_lambda_nodejs8 = require("aws-cdk-lib/aws-lambda-nodejs");
2100
- var import_constructs11 = require("constructs");
2101
- var HANDLER_NAME8 = "rest-api-lambda.handler.js";
2102
- function resolveHandlerEntry8(dirname) {
2103
- const sameDir = import_node_path8.default.join(dirname, HANDLER_NAME8);
2104
- if (import_node_fs8.default.existsSync(sameDir)) {
4337
+ var import_node_fs11 = __toESM(require("fs"));
4338
+ var import_node_path11 = __toESM(require("path"));
4339
+ var import_aws_lambda11 = require("aws-cdk-lib/aws-lambda");
4340
+ var import_aws_lambda_nodejs11 = require("aws-cdk-lib/aws-lambda-nodejs");
4341
+ var import_constructs18 = require("constructs");
4342
+ var HANDLER_NAME11 = "rest-api-lambda.handler.js";
4343
+ function resolveHandlerEntry11(dirname) {
4344
+ const sameDir = import_node_path11.default.join(dirname, HANDLER_NAME11);
4345
+ if (import_node_fs11.default.existsSync(sameDir)) {
2105
4346
  return sameDir;
2106
4347
  }
2107
- const fromLib = import_node_path8.default.join(dirname, "..", "..", "..", "lib", HANDLER_NAME8);
4348
+ const fromLib = import_node_path11.default.join(dirname, "..", "..", "..", "lib", HANDLER_NAME11);
2108
4349
  return fromLib;
2109
4350
  }
2110
- var RestApiLambda = class extends import_constructs11.Construct {
4351
+ var RestApiLambda = class extends import_constructs18.Construct {
2111
4352
  constructor(scope, props) {
2112
4353
  super(scope, "rest-api-lambda");
2113
- this.lambda = new import_aws_lambda_nodejs8.NodejsFunction(this, "handler", {
2114
- entry: resolveHandlerEntry8(__dirname),
2115
- runtime: import_aws_lambda8.Runtime.NODEJS_LATEST,
4354
+ this.lambda = new import_aws_lambda_nodejs11.NodejsFunction(this, "handler", {
4355
+ entry: resolveHandlerEntry11(__dirname),
4356
+ runtime: import_aws_lambda11.Runtime.NODEJS_LATEST,
2116
4357
  memorySize: 1024,
2117
4358
  environment: {
2118
4359
  DYNAMO_TABLE_NAME: props.dynamoTableName,
@@ -2254,8 +4495,8 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
2254
4495
  postgresSchema
2255
4496
  });
2256
4497
  lambda.addToRolePolicy(
2257
- new import_aws_iam3.PolicyStatement({
2258
- effect: import_aws_iam3.Effect.ALLOW,
4498
+ new import_aws_iam7.PolicyStatement({
4499
+ effect: import_aws_iam7.Effect.ALLOW,
2259
4500
  actions: [
2260
4501
  "rds-data:ExecuteStatement",
2261
4502
  "rds-data:BatchExecuteStatement"
@@ -2264,8 +4505,8 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
2264
4505
  })
2265
4506
  );
2266
4507
  lambda.addToRolePolicy(
2267
- new import_aws_iam3.PolicyStatement({
2268
- effect: import_aws_iam3.Effect.ALLOW,
4508
+ new import_aws_iam7.PolicyStatement({
4509
+ effect: import_aws_iam7.Effect.ALLOW,
2269
4510
  actions: ["secretsmanager:GetSecretValue"],
2270
4511
  resources: [postgresSecretArn]
2271
4512
  })
@@ -2283,15 +4524,15 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
2283
4524
  ];
2284
4525
  dataStoreTable.grant(lambda, ...dynamoActions);
2285
4526
  lambda.addToRolePolicy(
2286
- new import_aws_iam3.PolicyStatement({
2287
- effect: import_aws_iam3.Effect.ALLOW,
4527
+ new import_aws_iam7.PolicyStatement({
4528
+ effect: import_aws_iam7.Effect.ALLOW,
2288
4529
  actions: [...dynamoActions],
2289
4530
  resources: [`${dataStoreTable.tableArn}/index/*`]
2290
4531
  })
2291
4532
  );
2292
4533
  lambda.addToRolePolicy(
2293
- new import_aws_iam3.PolicyStatement({
2294
- effect: import_aws_iam3.Effect.ALLOW,
4534
+ new import_aws_iam7.PolicyStatement({
4535
+ effect: import_aws_iam7.Effect.ALLOW,
2295
4536
  actions: [
2296
4537
  "ssm:GetParameter",
2297
4538
  "ssm:GetParameters",
@@ -2350,7 +4591,7 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
2350
4591
  const userPool = OpenHiAuthService.userPoolFromConstruct(this);
2351
4592
  const userPoolClient = OpenHiAuthService.userPoolClientFromConstruct(this);
2352
4593
  const userPoolClients = [userPoolClient];
2353
- if (this.ohEnv.ohStage.stageType !== import_config5.OPEN_HI_STAGE.PROD) {
4594
+ if (this.ohEnv.ohStage.stageType !== import_config6.OPEN_HI_STAGE.PROD) {
2354
4595
  userPoolClients.push(
2355
4596
  OpenHiAuthService.fixtureSeederClientFromConstruct(this)
2356
4597
  );
@@ -2440,6 +4681,10 @@ _OpenHiGraphqlService.SERVICE_TYPE = "graphql-api";
2440
4681
  var OpenHiGraphqlService = _OpenHiGraphqlService;
2441
4682
  // Annotate the CommonJS export names for ESM import in node:
2442
4683
  0 && (module.exports = {
4684
+ BRIDGED_STATUSES,
4685
+ CLOUDFORMATION_EVENT_SOURCE,
4686
+ CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE,
4687
+ CONTROL_EVENT_BUS_NAME_ENV_VAR,
2443
4688
  ChildHostedZone,
2444
4689
  CognitoFixtureSeederClient,
2445
4690
  CognitoUserPool,
@@ -2450,11 +4695,22 @@ var OpenHiGraphqlService = _OpenHiGraphqlService;
2450
4695
  DATA_STORE_CHANGE_DETAIL_MAX_UTF8_BYTES,
2451
4696
  DATA_STORE_CHANGE_DETAIL_TYPE,
2452
4697
  DATA_STORE_CHANGE_EVENT_SOURCE,
4698
+ DEMO_PERIOD,
4699
+ DEMO_TENANT_SPECS,
4700
+ DEMO_URN_SYSTEM,
4701
+ DEV_USERS,
2453
4702
  DataEventBus,
2454
4703
  DataStoreHistoricalArchive,
2455
4704
  DataStorePostgresReplica,
2456
4705
  DiscoverableStringParameter,
2457
4706
  DynamoDbDataStore,
4707
+ OPENHI_REPO_TAG_KEY_ENV_VAR,
4708
+ OPENHI_RESOURCE_URN_SYSTEM,
4709
+ OPENHI_TAG_KEY_PREFIX_ENV_VAR,
4710
+ OPENHI_TAG_SUFFIX_BRANCH_NAME,
4711
+ OPENHI_TAG_SUFFIX_REPO_NAME,
4712
+ OPENHI_TAG_SUFFIX_SERVICE_TYPE,
4713
+ OPENHI_TAG_SUFFIX_STAGE_TYPE,
2458
4714
  OpenHiApp,
2459
4715
  OpenHiAuthService,
2460
4716
  OpenHiDataService,
@@ -2465,10 +4721,17 @@ var OpenHiGraphqlService = _OpenHiGraphqlService;
2465
4721
  OpenHiService,
2466
4722
  OpenHiStage,
2467
4723
  OpsEventBus,
4724
+ PLACEHOLDER_TENANT_ID,
4725
+ PLACEHOLDER_WORKSPACE_ID,
4726
+ PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM,
4727
+ PLATFORM_SCOPE_TENANT_ID,
2468
4728
  POSTGRES_REPLICA_CLUSTER_ARN_SSM_NAME,
2469
4729
  POSTGRES_REPLICA_DATABASE_NAME_SSM_NAME,
2470
4730
  POSTGRES_REPLICA_SECRET_ARN_SSM_NAME,
2471
4731
  PROVISION_DEFAULT_WORKSPACE_DETAIL_TYPE,
4732
+ PlatformDeployBridge,
4733
+ PlatformDeployBridgeLambda,
4734
+ PlatformDeploymentCompletedV1,
2472
4735
  PostAuthenticationLambda,
2473
4736
  PostConfirmationLambda,
2474
4737
  PreTokenGenerationLambda,
@@ -2478,13 +4741,39 @@ var OpenHiGraphqlService = _OpenHiGraphqlService;
2478
4741
  RootHostedZone,
2479
4742
  RootHttpApi,
2480
4743
  RootWildcardCertificate,
4744
+ SEED_DEMO_DATA_CONSUMER_NAME,
4745
+ SEED_SYSTEM_DATA_ACTOR_SYSTEM,
4746
+ SEED_SYSTEM_DATA_CONSUMER_NAME,
4747
+ SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR,
2481
4748
  STATIC_HOSTING_SERVICE_TYPE,
4749
+ SeedDemoDataLambda,
4750
+ SeedDemoDataWorkflow,
4751
+ SeedSystemDataLambda,
4752
+ SeedSystemDataWorkflow,
2482
4753
  StaticHosting,
2483
4754
  USER_ONBOARDING_EVENT_SOURCE,
2484
4755
  UserOnboardingWorkflow,
4756
+ WorkflowDedupConsumerNameInvalidError,
4757
+ WorkflowDedupTable,
4758
+ WorkflowDedupTableDuplicateError,
2485
4759
  buildFhirCurrentResourceChangeDetail,
2486
4760
  buildProvisionDefaultWorkspaceRequestedDetail,
4761
+ demoBasePartitionKeys,
4762
+ demoDevUserPartitionKeys,
4763
+ demoMembershipId,
4764
+ demoMembershipPartitionKey,
4765
+ demoRoleAssignmentId,
4766
+ demoRoleAssignmentPartitionKey,
4767
+ demoRolesForUserInTenant,
4768
+ demoScenarioIdentifier,
4769
+ demoTenantPartitionKey,
4770
+ demoUserPartitionKey,
4771
+ demoWorkspacePartitionKey,
2487
4772
  getDynamoDbDataStoreTableName,
2488
- getPostgresReplicaSchemaName
4773
+ getPostgresReplicaSchemaName,
4774
+ getWorkflowDedupTableName,
4775
+ openHiTagKey,
4776
+ openhiResourceIdentifier,
4777
+ rolePartitionKey
2489
4778
  });
2490
4779
  //# sourceMappingURL=index.js.map