@openhi/constructs 0.0.111 → 0.0.112

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 (111) hide show
  1. package/lib/chunk-23PUSHBV.mjs +24 -0
  2. package/lib/chunk-23PUSHBV.mjs.map +1 -0
  3. package/lib/{chunk-7FUAMZOF.mjs → chunk-53OHXLIL.mjs} +3 -3
  4. package/lib/chunk-6NBGYGFL.mjs +1803 -0
  5. package/lib/chunk-6NBGYGFL.mjs.map +1 -0
  6. package/lib/chunk-7RZHFI77.mjs +22 -0
  7. package/lib/chunk-7RZHFI77.mjs.map +1 -0
  8. package/lib/{chunk-7Q2IJ2J5.mjs → chunk-CUUKXDB2.mjs} +6 -6
  9. package/lib/chunk-FYHBHHWK.mjs +47 -0
  10. package/lib/chunk-FYHBHHWK.mjs.map +1 -0
  11. package/lib/{chunk-MULKGFIJ.mjs → chunk-GBDIGTNV.mjs} +165 -10
  12. package/lib/chunk-GBDIGTNV.mjs.map +1 -0
  13. package/lib/chunk-HQ67J7BP.mjs +199 -0
  14. package/lib/chunk-HQ67J7BP.mjs.map +1 -0
  15. package/lib/{chunk-AJ3G3THO.mjs → chunk-KO64HPWQ.mjs} +2 -2
  16. package/lib/{chunk-BB5MK4L3.mjs → chunk-KSFC72TT.mjs} +3 -3
  17. package/lib/{chunk-2TPJ6HOF.mjs → chunk-NZRW7ROK.mjs} +72 -54
  18. package/lib/chunk-NZRW7ROK.mjs.map +1 -0
  19. package/lib/chunk-QJDHVMKT.mjs +117 -0
  20. package/lib/chunk-QJDHVMKT.mjs.map +1 -0
  21. package/lib/{chunk-IS4VQRI4.mjs → chunk-QMBJ4VHC.mjs} +12 -47
  22. package/lib/chunk-QMBJ4VHC.mjs.map +1 -0
  23. package/lib/chunk-TRY7JGWO.mjs +16 -0
  24. package/lib/chunk-TRY7JGWO.mjs.map +1 -0
  25. package/lib/chunk-W4KR4CSL.mjs +236 -0
  26. package/lib/chunk-W4KR4CSL.mjs.map +1 -0
  27. package/lib/{chunk-AGF3RAAZ.mjs → chunk-WPCBVDFZ.mjs} +2 -2
  28. package/lib/chunk-WQWFVEVX.mjs +66 -0
  29. package/lib/chunk-WQWFVEVX.mjs.map +1 -0
  30. package/lib/{chunk-SYBADQXI.mjs → chunk-ZM4GDHHC.mjs} +77 -2
  31. package/lib/chunk-ZM4GDHHC.mjs.map +1 -0
  32. package/lib/delete-chunk.handler.d.mts +29 -0
  33. package/lib/delete-chunk.handler.d.ts +29 -0
  34. package/lib/delete-chunk.handler.js +2716 -0
  35. package/lib/delete-chunk.handler.js.map +1 -0
  36. package/lib/delete-chunk.handler.mjs +47 -0
  37. package/lib/delete-chunk.handler.mjs.map +1 -0
  38. package/lib/events-CjS-sm0W.d.mts +107 -0
  39. package/lib/events-CjS-sm0W.d.ts +107 -0
  40. package/lib/events-Da_cFgtc.d.mts +208 -0
  41. package/lib/events-Da_cFgtc.d.ts +208 -0
  42. package/lib/finalize.handler.d.mts +35 -0
  43. package/lib/finalize.handler.d.ts +35 -0
  44. package/lib/finalize.handler.js +875 -0
  45. package/lib/finalize.handler.js.map +1 -0
  46. package/lib/finalize.handler.mjs +166 -0
  47. package/lib/finalize.handler.mjs.map +1 -0
  48. package/lib/index.d.mts +189 -2
  49. package/lib/index.d.ts +500 -3
  50. package/lib/index.js +1753 -174
  51. package/lib/index.js.map +1 -1
  52. package/lib/index.mjs +571 -17
  53. package/lib/index.mjs.map +1 -1
  54. package/lib/list-chunks.handler.d.mts +28 -0
  55. package/lib/list-chunks.handler.d.ts +28 -0
  56. package/lib/list-chunks.handler.js +2746 -0
  57. package/lib/list-chunks.handler.js.map +1 -0
  58. package/lib/list-chunks.handler.mjs +54 -0
  59. package/lib/list-chunks.handler.mjs.map +1 -0
  60. package/lib/platform-deploy-bridge.handler.js +76 -1
  61. package/lib/platform-deploy-bridge.handler.js.map +1 -1
  62. package/lib/platform-deploy-bridge.handler.mjs +1 -1
  63. package/lib/pre-token-generation.handler.js +1106 -155
  64. package/lib/pre-token-generation.handler.js.map +1 -1
  65. package/lib/pre-token-generation.handler.mjs +6 -4
  66. package/lib/pre-token-generation.handler.mjs.map +1 -1
  67. package/lib/provision-default-workspace.handler.js +1529 -142
  68. package/lib/provision-default-workspace.handler.js.map +1 -1
  69. package/lib/provision-default-workspace.handler.mjs +8 -4
  70. package/lib/provision-default-workspace.handler.mjs.map +1 -1
  71. package/lib/rename-finalize.handler.d.mts +30 -0
  72. package/lib/rename-finalize.handler.d.ts +30 -0
  73. package/lib/rename-finalize.handler.js +795 -0
  74. package/lib/rename-finalize.handler.js.map +1 -0
  75. package/lib/rename-finalize.handler.mjs +90 -0
  76. package/lib/rename-finalize.handler.mjs.map +1 -0
  77. package/lib/rename-list-targets.handler.d.mts +26 -0
  78. package/lib/rename-list-targets.handler.d.ts +26 -0
  79. package/lib/rename-list-targets.handler.js +2985 -0
  80. package/lib/rename-list-targets.handler.js.map +1 -0
  81. package/lib/rename-list-targets.handler.mjs +431 -0
  82. package/lib/rename-list-targets.handler.mjs.map +1 -0
  83. package/lib/rename-rewrite-chunk.handler.d.mts +35 -0
  84. package/lib/rename-rewrite-chunk.handler.d.ts +35 -0
  85. package/lib/rename-rewrite-chunk.handler.js +2021 -0
  86. package/lib/rename-rewrite-chunk.handler.js.map +1 -0
  87. package/lib/rename-rewrite-chunk.handler.mjs +27 -0
  88. package/lib/rename-rewrite-chunk.handler.mjs.map +1 -0
  89. package/lib/rest-api-lambda.handler.js +4021 -932
  90. package/lib/rest-api-lambda.handler.js.map +1 -1
  91. package/lib/rest-api-lambda.handler.mjs +1786 -80
  92. package/lib/rest-api-lambda.handler.mjs.map +1 -1
  93. package/lib/seed-demo-data.handler.js +1588 -124
  94. package/lib/seed-demo-data.handler.js.map +1 -1
  95. package/lib/seed-demo-data.handler.mjs +10 -6
  96. package/lib/seed-system-data.handler.js +1179 -155
  97. package/lib/seed-system-data.handler.js.map +1 -1
  98. package/lib/seed-system-data.handler.mjs +5 -4
  99. package/lib/seed-system-data.handler.mjs.map +1 -1
  100. package/package.json +3 -3
  101. package/lib/chunk-2TPJ6HOF.mjs.map +0 -1
  102. package/lib/chunk-IS4VQRI4.mjs.map +0 -1
  103. package/lib/chunk-MULKGFIJ.mjs.map +0 -1
  104. package/lib/chunk-QR5JVSCF.mjs +0 -862
  105. package/lib/chunk-QR5JVSCF.mjs.map +0 -1
  106. package/lib/chunk-SYBADQXI.mjs.map +0 -1
  107. /package/lib/{chunk-7FUAMZOF.mjs.map → chunk-53OHXLIL.mjs.map} +0 -0
  108. /package/lib/{chunk-7Q2IJ2J5.mjs.map → chunk-CUUKXDB2.mjs.map} +0 -0
  109. /package/lib/{chunk-AJ3G3THO.mjs.map → chunk-KO64HPWQ.mjs.map} +0 -0
  110. /package/lib/{chunk-BB5MK4L3.mjs.map → chunk-KSFC72TT.mjs.map} +0 -0
  111. /package/lib/{chunk-AGF3RAAZ.mjs.map → chunk-WPCBVDFZ.mjs.map} +0 -0
@@ -0,0 +1,2985 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // ../workflows/lib/envelope-version.js
34
+ var require_envelope_version = __commonJS({
35
+ "../workflows/lib/envelope-version.js"(exports2) {
36
+ "use strict";
37
+ Object.defineProperty(exports2, "__esModule", { value: true });
38
+ exports2.ENVELOPE_VERSION = void 0;
39
+ exports2.isSupportedEnvelopeVersion = isSupportedEnvelopeVersion;
40
+ exports2.ENVELOPE_VERSION = "1.0";
41
+ var ENVELOPE_VERSION_PATTERN = /^\d+\.\d+$/;
42
+ var MIN_SUPPORTED_MAJOR = 1;
43
+ var MAX_SUPPORTED_MAJOR = 1;
44
+ function isSupportedEnvelopeVersion(version) {
45
+ if (!ENVELOPE_VERSION_PATTERN.test(version)) {
46
+ return false;
47
+ }
48
+ const major = Number.parseInt(version.split(".")[0], 10);
49
+ return major >= MIN_SUPPORTED_MAJOR && major <= MAX_SUPPORTED_MAJOR;
50
+ }
51
+ }
52
+ });
53
+
54
+ // ../workflows/lib/envelope.js
55
+ var require_envelope = __commonJS({
56
+ "../workflows/lib/envelope.js"(exports2) {
57
+ "use strict";
58
+ Object.defineProperty(exports2, "__esModule", { value: true });
59
+ exports2.MissingActorContextError = void 0;
60
+ exports2.isWorkflowUserActor = isWorkflowUserActor;
61
+ exports2.isWorkflowSystemActor = isWorkflowSystemActor;
62
+ exports2.workflowUserActorFromClaims = workflowUserActorFromClaims;
63
+ function isWorkflowUserActor(actor) {
64
+ return actor.ohi_uid !== void 0;
65
+ }
66
+ function isWorkflowSystemActor(actor) {
67
+ return actor.system !== void 0;
68
+ }
69
+ function workflowUserActorFromClaims(claims) {
70
+ if (claims.ohi_tid === void 0 || claims.ohi_wid === void 0) {
71
+ 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.");
72
+ }
73
+ return {
74
+ ohi_tid: claims.ohi_tid,
75
+ ohi_wid: claims.ohi_wid,
76
+ ohi_uid: claims.ohi_uid,
77
+ ohi_uname: claims.ohi_uname
78
+ };
79
+ }
80
+ var MissingActorContextError = class extends Error {
81
+ /** @param message - human-readable description of the missing claim. */
82
+ constructor(message) {
83
+ super(message);
84
+ this.name = "MissingActorContextError";
85
+ }
86
+ };
87
+ exports2.MissingActorContextError = MissingActorContextError;
88
+ }
89
+ });
90
+
91
+ // ../workflows/lib/sources.js
92
+ var require_sources = __commonJS({
93
+ "../workflows/lib/sources.js"(exports2) {
94
+ "use strict";
95
+ Object.defineProperty(exports2, "__esModule", { value: true });
96
+ exports2.DEFAULT_BUS_NAME_BY_SOURCE = exports2.OPENHI_OPS_SOURCE = exports2.OPENHI_DATA_SOURCE = exports2.OPENHI_CONTROL_SOURCE = void 0;
97
+ exports2.OPENHI_CONTROL_SOURCE = "openhi.control";
98
+ exports2.OPENHI_DATA_SOURCE = "openhi.data";
99
+ exports2.OPENHI_OPS_SOURCE = "openhi.ops";
100
+ exports2.DEFAULT_BUS_NAME_BY_SOURCE = {
101
+ [exports2.OPENHI_CONTROL_SOURCE]: "openhi-control-event-bus",
102
+ [exports2.OPENHI_DATA_SOURCE]: "openhi-data-event-bus",
103
+ [exports2.OPENHI_OPS_SOURCE]: "openhi-ops-event-bus"
104
+ };
105
+ }
106
+ });
107
+
108
+ // ../workflows/lib/detail-types/registry.js
109
+ var require_registry = __commonJS({
110
+ "../workflows/lib/detail-types/registry.js"(exports2) {
111
+ "use strict";
112
+ Object.defineProperty(exports2, "__esModule", { value: true });
113
+ exports2.InvalidDetailTypeRegistrationError = void 0;
114
+ exports2.defineDetailType = defineDetailType;
115
+ exports2.isWellFormedDetailType = isWellFormedDetailType;
116
+ function defineDetailType(entry) {
117
+ if (!isWellFormedDetailType(entry.detailType)) {
118
+ throw new InvalidDetailTypeRegistrationError(`Detail-type "${entry.detailType}" does not match the platform-wide format <area>.<event>.v<integer>. See TR-016 \xA7Open Items #2.`);
119
+ }
120
+ return entry;
121
+ }
122
+ var DETAIL_TYPE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*\.[a-z0-9]+(?:-[a-z0-9]+)*\.v\d+$/;
123
+ function isWellFormedDetailType(detailType) {
124
+ return DETAIL_TYPE_PATTERN.test(detailType);
125
+ }
126
+ var InvalidDetailTypeRegistrationError = class extends Error {
127
+ /** @param message - human-readable description of the violation. */
128
+ constructor(message) {
129
+ super(message);
130
+ this.name = "InvalidDetailTypeRegistrationError";
131
+ }
132
+ };
133
+ exports2.InvalidDetailTypeRegistrationError = InvalidDetailTypeRegistrationError;
134
+ }
135
+ });
136
+
137
+ // ../workflows/lib/detail-types/control-plane.js
138
+ var require_control_plane = __commonJS({
139
+ "../workflows/lib/detail-types/control-plane.js"(exports2) {
140
+ "use strict";
141
+ Object.defineProperty(exports2, "__esModule", { value: true });
142
+ exports2.ControlPlaneRenameFailedV1 = exports2.ControlPlaneRenameCompleteV1 = exports2.ControlPlaneRenameV1 = exports2.RENAMABLE_ENTITY_TYPE = exports2.ControlPlaneOwningDeleteFailedV1 = exports2.ControlPlaneOwningDeleteCompleteV1 = exports2.ControlPlaneOwningDeleteV1 = exports2.OWNING_ENTITY_TYPE = void 0;
143
+ var sources_1 = require_sources();
144
+ var registry_1 = require_registry();
145
+ exports2.OWNING_ENTITY_TYPE = {
146
+ Workspace: "Workspace",
147
+ User: "User"
148
+ };
149
+ exports2.ControlPlaneOwningDeleteV1 = (0, registry_1.defineDetailType)({
150
+ detailType: "control-plane.owning-delete.v1",
151
+ source: sources_1.OPENHI_DATA_SOURCE,
152
+ dedupRequired: true
153
+ });
154
+ exports2.ControlPlaneOwningDeleteCompleteV1 = (0, registry_1.defineDetailType)({
155
+ detailType: "control-plane.owning-delete-complete.v1",
156
+ source: sources_1.OPENHI_OPS_SOURCE,
157
+ dedupRequired: true
158
+ });
159
+ exports2.ControlPlaneOwningDeleteFailedV1 = (0, registry_1.defineDetailType)({
160
+ detailType: "control-plane.owning-delete-failed.v1",
161
+ source: sources_1.OPENHI_OPS_SOURCE,
162
+ dedupRequired: true
163
+ });
164
+ exports2.RENAMABLE_ENTITY_TYPE = {
165
+ Tenant: "Tenant",
166
+ User: "User",
167
+ Role: "Role"
168
+ };
169
+ exports2.ControlPlaneRenameV1 = (0, registry_1.defineDetailType)({
170
+ detailType: "control-plane.rename.v1",
171
+ source: sources_1.OPENHI_DATA_SOURCE,
172
+ dedupRequired: true
173
+ });
174
+ exports2.ControlPlaneRenameCompleteV1 = (0, registry_1.defineDetailType)({
175
+ detailType: "control-plane.rename-complete.v1",
176
+ source: sources_1.OPENHI_OPS_SOURCE,
177
+ dedupRequired: true
178
+ });
179
+ exports2.ControlPlaneRenameFailedV1 = (0, registry_1.defineDetailType)({
180
+ detailType: "control-plane.rename-failed.v1",
181
+ source: sources_1.OPENHI_OPS_SOURCE,
182
+ dedupRequired: true
183
+ });
184
+ }
185
+ });
186
+
187
+ // ../workflows/lib/detail-types/platform.js
188
+ var require_platform = __commonJS({
189
+ "../workflows/lib/detail-types/platform.js"(exports2) {
190
+ "use strict";
191
+ Object.defineProperty(exports2, "__esModule", { value: true });
192
+ exports2.PlatformSystemDataSeededV1 = exports2.PlatformDeploymentCompletedV1 = void 0;
193
+ var sources_1 = require_sources();
194
+ var registry_1 = require_registry();
195
+ exports2.PlatformDeploymentCompletedV1 = (0, registry_1.defineDetailType)({
196
+ detailType: "platform.deployment-completed.v1",
197
+ source: sources_1.OPENHI_CONTROL_SOURCE,
198
+ dedupRequired: true
199
+ });
200
+ exports2.PlatformSystemDataSeededV1 = (0, registry_1.defineDetailType)({
201
+ detailType: "platform.system-data-seeded.v1",
202
+ source: sources_1.OPENHI_CONTROL_SOURCE,
203
+ dedupRequired: true
204
+ });
205
+ }
206
+ });
207
+
208
+ // ../workflows/lib/detail-types/index.js
209
+ var require_detail_types = __commonJS({
210
+ "../workflows/lib/detail-types/index.js"(exports2) {
211
+ "use strict";
212
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
213
+ if (k2 === void 0) k2 = k;
214
+ var desc = Object.getOwnPropertyDescriptor(m, k);
215
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
216
+ desc = { enumerable: true, get: function() {
217
+ return m[k];
218
+ } };
219
+ }
220
+ Object.defineProperty(o, k2, desc);
221
+ }) : (function(o, m, k, k2) {
222
+ if (k2 === void 0) k2 = k;
223
+ o[k2] = m[k];
224
+ }));
225
+ var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
226
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
227
+ };
228
+ Object.defineProperty(exports2, "__esModule", { value: true });
229
+ __exportStar(require_control_plane(), exports2);
230
+ __exportStar(require_platform(), exports2);
231
+ __exportStar(require_registry(), exports2);
232
+ }
233
+ });
234
+
235
+ // ../workflows/lib/publisher.js
236
+ var require_publisher = __commonJS({
237
+ "../workflows/lib/publisher.js"(exports2) {
238
+ "use strict";
239
+ Object.defineProperty(exports2, "__esModule", { value: true });
240
+ exports2.WorkflowPublishError = void 0;
241
+ exports2.workflowsClient = workflowsClient;
242
+ exports2.publishWorkflowEvent = publishWorkflowEvent;
243
+ var node_crypto_1 = require("crypto");
244
+ var client_eventbridge_1 = require("@aws-sdk/client-eventbridge");
245
+ var envelope_version_1 = require_envelope_version();
246
+ var sources_1 = require_sources();
247
+ function workflowsClient(bridge, options = {}) {
248
+ return {
249
+ publish: (entry, payload, ctx) => publishWorkflowEvent(bridge, entry, payload, ctx, options)
250
+ };
251
+ }
252
+ async function publishWorkflowEvent(bridge, entry, payload, ctx, options = {}) {
253
+ const eventIdGenerator = options.eventIdGenerator ?? (() => (0, node_crypto_1.randomUUID)());
254
+ const correlationIdGenerator = options.correlationIdGenerator ?? (() => (0, node_crypto_1.randomUUID)());
255
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
256
+ const envelope = {
257
+ eventId: eventIdGenerator(),
258
+ attempt: 1,
259
+ correlationId: ctx.correlationId ?? correlationIdGenerator(),
260
+ causationId: ctx.causationId ?? null,
261
+ actor: ctx.actor,
262
+ occurredAt: now().toISOString(),
263
+ envelopeVersion: envelope_version_1.ENVELOPE_VERSION,
264
+ payload
265
+ };
266
+ const busName = options.busNameByPlane?.[entry.source] ?? sources_1.DEFAULT_BUS_NAME_BY_SOURCE[entry.source];
267
+ const result = await bridge.send(new client_eventbridge_1.PutEventsCommand({
268
+ Entries: [
269
+ {
270
+ EventBusName: busName,
271
+ Source: entry.source,
272
+ DetailType: entry.detailType,
273
+ Detail: JSON.stringify(envelope)
274
+ }
275
+ ]
276
+ }));
277
+ if ((result.FailedEntryCount ?? 0) > 0) {
278
+ const first = result.Entries?.[0];
279
+ throw new WorkflowPublishError(`EventBridge rejected ${entry.detailType} publish on bus ${busName}: ${first?.ErrorCode ?? "unknown"} \u2014 ${first?.ErrorMessage ?? "no error message"}`);
280
+ }
281
+ return { eventId: envelope.eventId };
282
+ }
283
+ var WorkflowPublishError = class extends Error {
284
+ /** @param message - human-readable description of the failed publish. */
285
+ constructor(message) {
286
+ super(message);
287
+ this.name = "WorkflowPublishError";
288
+ }
289
+ };
290
+ exports2.WorkflowPublishError = WorkflowPublishError;
291
+ }
292
+ });
293
+
294
+ // ../workflows/lib/consumer.js
295
+ var require_consumer = __commonJS({
296
+ "../workflows/lib/consumer.js"(exports2) {
297
+ "use strict";
298
+ Object.defineProperty(exports2, "__esModule", { value: true });
299
+ exports2.UnsupportedEnvelopeVersionError = exports2.InvalidWorkflowEventError = void 0;
300
+ exports2.parseWorkflowEvent = parseWorkflowEvent;
301
+ var envelope_version_1 = require_envelope_version();
302
+ function parseWorkflowEvent(event, expected) {
303
+ if (event.source !== expected.source) {
304
+ throw new InvalidWorkflowEventError(`EventBridge source "${event.source}" does not match expected detail-type's source "${expected.source}".`);
305
+ }
306
+ if (event["detail-type"] !== expected.detailType) {
307
+ throw new InvalidWorkflowEventError(`EventBridge detail-type "${event["detail-type"]}" does not match expected "${expected.detailType}".`);
308
+ }
309
+ const candidate = asEnvelopeCandidate(event.detail);
310
+ if (!(0, envelope_version_1.isSupportedEnvelopeVersion)(candidate.envelopeVersion)) {
311
+ throw new UnsupportedEnvelopeVersionError(`Envelope version "${candidate.envelopeVersion}" is outside the SDK's supported range.`);
312
+ }
313
+ const envelope = {
314
+ eventId: candidate.eventId,
315
+ attempt: candidate.attempt,
316
+ correlationId: candidate.correlationId,
317
+ causationId: candidate.causationId,
318
+ actor: candidate.actor,
319
+ occurredAt: candidate.occurredAt,
320
+ envelopeVersion: candidate.envelopeVersion,
321
+ payload: candidate.payload
322
+ };
323
+ return {
324
+ envelope,
325
+ dedupKey: { eventId: envelope.eventId, attempt: envelope.attempt }
326
+ };
327
+ }
328
+ function asEnvelopeCandidate(detail) {
329
+ if (detail === null || typeof detail !== "object") {
330
+ throw new InvalidWorkflowEventError("EventBridge detail is not a non-null object.");
331
+ }
332
+ const obj = detail;
333
+ assertString(obj, "eventId");
334
+ assertPositiveInteger(obj, "attempt");
335
+ assertString(obj, "correlationId");
336
+ assertCausationId(obj);
337
+ assertActor(obj);
338
+ assertString(obj, "occurredAt");
339
+ assertString(obj, "envelopeVersion");
340
+ if (!("payload" in obj)) {
341
+ throw new InvalidWorkflowEventError("Envelope is missing required field: payload.");
342
+ }
343
+ return obj;
344
+ }
345
+ function assertString(obj, field) {
346
+ const value = obj[field];
347
+ if (typeof value !== "string" || value.length === 0) {
348
+ throw new InvalidWorkflowEventError(`Envelope field "${field}" must be a non-empty string.`);
349
+ }
350
+ }
351
+ function assertPositiveInteger(obj, field) {
352
+ const value = obj[field];
353
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
354
+ throw new InvalidWorkflowEventError(`Envelope field "${field}" must be a 1-indexed integer.`);
355
+ }
356
+ }
357
+ function assertCausationId(obj) {
358
+ if (!("causationId" in obj)) {
359
+ throw new InvalidWorkflowEventError("Envelope is missing required field: causationId.");
360
+ }
361
+ const value = obj.causationId;
362
+ if (value !== null && (typeof value !== "string" || value.length === 0)) {
363
+ throw new InvalidWorkflowEventError('Envelope field "causationId" must be a non-empty string or null.');
364
+ }
365
+ }
366
+ function assertActor(obj) {
367
+ const actor = obj.actor;
368
+ if (actor === null || typeof actor !== "object") {
369
+ throw new InvalidWorkflowEventError('Envelope field "actor" must be an object.');
370
+ }
371
+ const actorObj = actor;
372
+ const isUserActor = typeof actorObj.ohi_uid === "string" && typeof actorObj.ohi_uname === "string" && typeof actorObj.ohi_tid === "string" && typeof actorObj.ohi_wid === "string";
373
+ const isSystemActor = typeof actorObj.system === "string";
374
+ if (!isUserActor && !isSystemActor) {
375
+ 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 }).');
376
+ }
377
+ }
378
+ var InvalidWorkflowEventError = class extends Error {
379
+ /** @param message - human-readable description of the validation failure. */
380
+ constructor(message) {
381
+ super(message);
382
+ this.name = "InvalidWorkflowEventError";
383
+ }
384
+ };
385
+ exports2.InvalidWorkflowEventError = InvalidWorkflowEventError;
386
+ var UnsupportedEnvelopeVersionError = class extends Error {
387
+ /** @param message - human-readable description of the unsupported version. */
388
+ constructor(message) {
389
+ super(message);
390
+ this.name = "UnsupportedEnvelopeVersionError";
391
+ }
392
+ };
393
+ exports2.UnsupportedEnvelopeVersionError = UnsupportedEnvelopeVersionError;
394
+ }
395
+ });
396
+
397
+ // ../workflows/lib/dedup/env.js
398
+ var require_env = __commonJS({
399
+ "../workflows/lib/dedup/env.js"(exports2) {
400
+ "use strict";
401
+ Object.defineProperty(exports2, "__esModule", { value: true });
402
+ exports2.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = exports2.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = exports2.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR = void 0;
403
+ exports2.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR = "OPENHI_WORKFLOW_DEDUP_TABLE_NAME";
404
+ exports2.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = 14 * 24 * 60 * 60;
405
+ exports2.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = 64;
406
+ }
407
+ });
408
+
409
+ // ../workflows/lib/dedup/workflow-dedup-client.js
410
+ var require_workflow_dedup_client = __commonJS({
411
+ "../workflows/lib/dedup/workflow-dedup-client.js"(exports2) {
412
+ "use strict";
413
+ Object.defineProperty(exports2, "__esModule", { value: true });
414
+ exports2.WorkflowDedupInvalidInputError = exports2.WorkflowDedupTableNameMissingError = void 0;
415
+ exports2.workflowDedupClient = workflowDedupClient;
416
+ exports2.recordIfAbsent = recordIfAbsent;
417
+ exports2.markFailed = markFailed;
418
+ exports2.encodeSortKey = encodeSortKey;
419
+ var client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
420
+ var env_1 = require_env();
421
+ function workflowDedupClient(dynamodb, options = {}) {
422
+ return {
423
+ recordIfAbsent: (input) => recordIfAbsent(dynamodb, input, options),
424
+ markFailed: (input) => markFailed(dynamodb, input, options)
425
+ };
426
+ }
427
+ async function recordIfAbsent(dynamodb, input, options = {}) {
428
+ assertConsumerName(input.consumerName);
429
+ assertPositiveInteger(input.attempt, "attempt");
430
+ const ttlSeconds = input.ttlSeconds ?? options.defaultTtlSeconds ?? env_1.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS;
431
+ if (!Number.isInteger(ttlSeconds) || ttlSeconds <= 0) {
432
+ throw new WorkflowDedupInvalidInputError(`ttlSeconds must be a positive integer; got ${ttlSeconds}.`);
433
+ }
434
+ const tableName = resolveTableName(options.tableName);
435
+ const now = (options.now ?? defaultNow)();
436
+ const sk = encodeSortKey(input.eventId, input.attempt);
437
+ const expiresAt = Math.floor(now.getTime() / 1e3) + ttlSeconds;
438
+ try {
439
+ await dynamodb.send(new client_dynamodb_1.PutItemCommand({
440
+ TableName: tableName,
441
+ Item: {
442
+ consumerName: { S: input.consumerName },
443
+ sk: { S: sk },
444
+ eventId: { S: input.eventId },
445
+ attempt: { N: String(input.attempt) },
446
+ recordedAt: { S: now.toISOString() },
447
+ expiresAt: { N: String(expiresAt) }
448
+ },
449
+ ConditionExpression: "attribute_not_exists(consumerName) AND attribute_not_exists(sk)"
450
+ }));
451
+ return { recorded: true };
452
+ } catch (err) {
453
+ if (err instanceof client_dynamodb_1.ConditionalCheckFailedException) {
454
+ return { recorded: false, alreadyProcessed: true };
455
+ }
456
+ throw err;
457
+ }
458
+ }
459
+ async function markFailed(dynamodb, input, options = {}) {
460
+ assertConsumerName(input.consumerName);
461
+ assertPositiveInteger(input.attempt, "attempt");
462
+ if (input.reason.length === 0) {
463
+ throw new WorkflowDedupInvalidInputError("reason must be non-empty.");
464
+ }
465
+ const tableName = resolveTableName(options.tableName);
466
+ const now = (options.now ?? defaultNow)();
467
+ const sk = encodeSortKey(input.eventId, input.attempt);
468
+ await dynamodb.send(new client_dynamodb_1.UpdateItemCommand({
469
+ TableName: tableName,
470
+ Key: {
471
+ consumerName: { S: input.consumerName },
472
+ sk: { S: sk }
473
+ },
474
+ UpdateExpression: "SET #failed = :failed, #failureReason = :reason, #failedAt = :failedAt",
475
+ ExpressionAttributeNames: {
476
+ "#failed": "failed",
477
+ "#failureReason": "failureReason",
478
+ "#failedAt": "failedAt"
479
+ },
480
+ ExpressionAttributeValues: {
481
+ ":failed": { BOOL: true },
482
+ ":reason": { S: input.reason },
483
+ ":failedAt": { S: now.toISOString() }
484
+ }
485
+ }));
486
+ }
487
+ function encodeSortKey(eventId, attempt) {
488
+ if (eventId.length === 0) {
489
+ throw new WorkflowDedupInvalidInputError("eventId must be non-empty.");
490
+ }
491
+ return `${eventId}#${attempt}`;
492
+ }
493
+ function resolveTableName(explicit) {
494
+ const name = explicit ?? process.env[env_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR];
495
+ if (!name) {
496
+ throw new WorkflowDedupTableNameMissingError(`Workflow dedup table name not set. Pass options.tableName or set ${env_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR}.`);
497
+ }
498
+ return name;
499
+ }
500
+ function assertConsumerName(consumerName) {
501
+ if (consumerName.length === 0) {
502
+ throw new WorkflowDedupInvalidInputError("consumerName must be non-empty.");
503
+ }
504
+ if (consumerName.length > env_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH) {
505
+ throw new WorkflowDedupInvalidInputError(`consumerName must be \u2264${env_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH} chars; got ${consumerName.length}.`);
506
+ }
507
+ if (/\s/.test(consumerName)) {
508
+ throw new WorkflowDedupInvalidInputError("consumerName must not contain whitespace.");
509
+ }
510
+ }
511
+ function assertPositiveInteger(value, field) {
512
+ if (!Number.isInteger(value) || value < 1) {
513
+ throw new WorkflowDedupInvalidInputError(`${field} must be a 1-indexed integer; got ${value}.`);
514
+ }
515
+ }
516
+ function defaultNow() {
517
+ return /* @__PURE__ */ new Date();
518
+ }
519
+ var WorkflowDedupTableNameMissingError = class extends Error {
520
+ /** @param message - human-readable description. */
521
+ constructor(message) {
522
+ super(message);
523
+ this.name = "WorkflowDedupTableNameMissingError";
524
+ }
525
+ };
526
+ exports2.WorkflowDedupTableNameMissingError = WorkflowDedupTableNameMissingError;
527
+ var WorkflowDedupInvalidInputError = class extends Error {
528
+ /** @param message - human-readable description. */
529
+ constructor(message) {
530
+ super(message);
531
+ this.name = "WorkflowDedupInvalidInputError";
532
+ }
533
+ };
534
+ exports2.WorkflowDedupInvalidInputError = WorkflowDedupInvalidInputError;
535
+ }
536
+ });
537
+
538
+ // ../workflows/lib/dedup/index.js
539
+ var require_dedup = __commonJS({
540
+ "../workflows/lib/dedup/index.js"(exports2) {
541
+ "use strict";
542
+ Object.defineProperty(exports2, "__esModule", { value: true });
543
+ 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;
544
+ var env_1 = require_env();
545
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS", { enumerable: true, get: function() {
546
+ return env_1.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS;
547
+ } });
548
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH", { enumerable: true, get: function() {
549
+ return env_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH;
550
+ } });
551
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR", { enumerable: true, get: function() {
552
+ return env_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR;
553
+ } });
554
+ var workflow_dedup_client_1 = require_workflow_dedup_client();
555
+ Object.defineProperty(exports2, "WorkflowDedupInvalidInputError", { enumerable: true, get: function() {
556
+ return workflow_dedup_client_1.WorkflowDedupInvalidInputError;
557
+ } });
558
+ Object.defineProperty(exports2, "WorkflowDedupTableNameMissingError", { enumerable: true, get: function() {
559
+ return workflow_dedup_client_1.WorkflowDedupTableNameMissingError;
560
+ } });
561
+ Object.defineProperty(exports2, "encodeSortKey", { enumerable: true, get: function() {
562
+ return workflow_dedup_client_1.encodeSortKey;
563
+ } });
564
+ Object.defineProperty(exports2, "markFailed", { enumerable: true, get: function() {
565
+ return workflow_dedup_client_1.markFailed;
566
+ } });
567
+ Object.defineProperty(exports2, "recordIfAbsent", { enumerable: true, get: function() {
568
+ return workflow_dedup_client_1.recordIfAbsent;
569
+ } });
570
+ Object.defineProperty(exports2, "workflowDedupClient", { enumerable: true, get: function() {
571
+ return workflow_dedup_client_1.workflowDedupClient;
572
+ } });
573
+ }
574
+ });
575
+
576
+ // ../workflows/lib/index.js
577
+ var require_lib = __commonJS({
578
+ "../workflows/lib/index.js"(exports2) {
579
+ "use strict";
580
+ Object.defineProperty(exports2, "__esModule", { value: true });
581
+ 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.RENAMABLE_ENTITY_TYPE = exports2.PlatformSystemDataSeededV1 = exports2.PlatformDeploymentCompletedV1 = exports2.OWNING_ENTITY_TYPE = exports2.InvalidDetailTypeRegistrationError = exports2.ControlPlaneRenameV1 = exports2.ControlPlaneRenameFailedV1 = exports2.ControlPlaneRenameCompleteV1 = exports2.ControlPlaneOwningDeleteV1 = exports2.ControlPlaneOwningDeleteFailedV1 = exports2.ControlPlaneOwningDeleteCompleteV1 = 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;
582
+ var envelope_version_1 = require_envelope_version();
583
+ Object.defineProperty(exports2, "ENVELOPE_VERSION", { enumerable: true, get: function() {
584
+ return envelope_version_1.ENVELOPE_VERSION;
585
+ } });
586
+ Object.defineProperty(exports2, "isSupportedEnvelopeVersion", { enumerable: true, get: function() {
587
+ return envelope_version_1.isSupportedEnvelopeVersion;
588
+ } });
589
+ var envelope_1 = require_envelope();
590
+ Object.defineProperty(exports2, "MissingActorContextError", { enumerable: true, get: function() {
591
+ return envelope_1.MissingActorContextError;
592
+ } });
593
+ Object.defineProperty(exports2, "isWorkflowSystemActor", { enumerable: true, get: function() {
594
+ return envelope_1.isWorkflowSystemActor;
595
+ } });
596
+ Object.defineProperty(exports2, "isWorkflowUserActor", { enumerable: true, get: function() {
597
+ return envelope_1.isWorkflowUserActor;
598
+ } });
599
+ Object.defineProperty(exports2, "workflowUserActorFromClaims", { enumerable: true, get: function() {
600
+ return envelope_1.workflowUserActorFromClaims;
601
+ } });
602
+ var sources_1 = require_sources();
603
+ Object.defineProperty(exports2, "DEFAULT_BUS_NAME_BY_SOURCE", { enumerable: true, get: function() {
604
+ return sources_1.DEFAULT_BUS_NAME_BY_SOURCE;
605
+ } });
606
+ Object.defineProperty(exports2, "OPENHI_CONTROL_SOURCE", { enumerable: true, get: function() {
607
+ return sources_1.OPENHI_CONTROL_SOURCE;
608
+ } });
609
+ Object.defineProperty(exports2, "OPENHI_DATA_SOURCE", { enumerable: true, get: function() {
610
+ return sources_1.OPENHI_DATA_SOURCE;
611
+ } });
612
+ Object.defineProperty(exports2, "OPENHI_OPS_SOURCE", { enumerable: true, get: function() {
613
+ return sources_1.OPENHI_OPS_SOURCE;
614
+ } });
615
+ var detail_types_1 = require_detail_types();
616
+ Object.defineProperty(exports2, "ControlPlaneOwningDeleteCompleteV1", { enumerable: true, get: function() {
617
+ return detail_types_1.ControlPlaneOwningDeleteCompleteV1;
618
+ } });
619
+ Object.defineProperty(exports2, "ControlPlaneOwningDeleteFailedV1", { enumerable: true, get: function() {
620
+ return detail_types_1.ControlPlaneOwningDeleteFailedV1;
621
+ } });
622
+ Object.defineProperty(exports2, "ControlPlaneOwningDeleteV1", { enumerable: true, get: function() {
623
+ return detail_types_1.ControlPlaneOwningDeleteV1;
624
+ } });
625
+ Object.defineProperty(exports2, "ControlPlaneRenameCompleteV1", { enumerable: true, get: function() {
626
+ return detail_types_1.ControlPlaneRenameCompleteV1;
627
+ } });
628
+ Object.defineProperty(exports2, "ControlPlaneRenameFailedV1", { enumerable: true, get: function() {
629
+ return detail_types_1.ControlPlaneRenameFailedV1;
630
+ } });
631
+ Object.defineProperty(exports2, "ControlPlaneRenameV1", { enumerable: true, get: function() {
632
+ return detail_types_1.ControlPlaneRenameV1;
633
+ } });
634
+ Object.defineProperty(exports2, "InvalidDetailTypeRegistrationError", { enumerable: true, get: function() {
635
+ return detail_types_1.InvalidDetailTypeRegistrationError;
636
+ } });
637
+ Object.defineProperty(exports2, "OWNING_ENTITY_TYPE", { enumerable: true, get: function() {
638
+ return detail_types_1.OWNING_ENTITY_TYPE;
639
+ } });
640
+ Object.defineProperty(exports2, "PlatformDeploymentCompletedV1", { enumerable: true, get: function() {
641
+ return detail_types_1.PlatformDeploymentCompletedV1;
642
+ } });
643
+ Object.defineProperty(exports2, "PlatformSystemDataSeededV1", { enumerable: true, get: function() {
644
+ return detail_types_1.PlatformSystemDataSeededV1;
645
+ } });
646
+ Object.defineProperty(exports2, "RENAMABLE_ENTITY_TYPE", { enumerable: true, get: function() {
647
+ return detail_types_1.RENAMABLE_ENTITY_TYPE;
648
+ } });
649
+ Object.defineProperty(exports2, "defineDetailType", { enumerable: true, get: function() {
650
+ return detail_types_1.defineDetailType;
651
+ } });
652
+ Object.defineProperty(exports2, "isWellFormedDetailType", { enumerable: true, get: function() {
653
+ return detail_types_1.isWellFormedDetailType;
654
+ } });
655
+ var publisher_1 = require_publisher();
656
+ Object.defineProperty(exports2, "WorkflowPublishError", { enumerable: true, get: function() {
657
+ return publisher_1.WorkflowPublishError;
658
+ } });
659
+ Object.defineProperty(exports2, "publishWorkflowEvent", { enumerable: true, get: function() {
660
+ return publisher_1.publishWorkflowEvent;
661
+ } });
662
+ Object.defineProperty(exports2, "workflowsClient", { enumerable: true, get: function() {
663
+ return publisher_1.workflowsClient;
664
+ } });
665
+ var consumer_1 = require_consumer();
666
+ Object.defineProperty(exports2, "InvalidWorkflowEventError", { enumerable: true, get: function() {
667
+ return consumer_1.InvalidWorkflowEventError;
668
+ } });
669
+ Object.defineProperty(exports2, "UnsupportedEnvelopeVersionError", { enumerable: true, get: function() {
670
+ return consumer_1.UnsupportedEnvelopeVersionError;
671
+ } });
672
+ Object.defineProperty(exports2, "parseWorkflowEvent", { enumerable: true, get: function() {
673
+ return consumer_1.parseWorkflowEvent;
674
+ } });
675
+ var dedup_1 = require_dedup();
676
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS", { enumerable: true, get: function() {
677
+ return dedup_1.WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS;
678
+ } });
679
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH", { enumerable: true, get: function() {
680
+ return dedup_1.WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH;
681
+ } });
682
+ Object.defineProperty(exports2, "WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR", { enumerable: true, get: function() {
683
+ return dedup_1.WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR;
684
+ } });
685
+ Object.defineProperty(exports2, "WorkflowDedupInvalidInputError", { enumerable: true, get: function() {
686
+ return dedup_1.WorkflowDedupInvalidInputError;
687
+ } });
688
+ Object.defineProperty(exports2, "WorkflowDedupTableNameMissingError", { enumerable: true, get: function() {
689
+ return dedup_1.WorkflowDedupTableNameMissingError;
690
+ } });
691
+ Object.defineProperty(exports2, "encodeSortKey", { enumerable: true, get: function() {
692
+ return dedup_1.encodeSortKey;
693
+ } });
694
+ Object.defineProperty(exports2, "markFailed", { enumerable: true, get: function() {
695
+ return dedup_1.markFailed;
696
+ } });
697
+ Object.defineProperty(exports2, "recordIfAbsent", { enumerable: true, get: function() {
698
+ return dedup_1.recordIfAbsent;
699
+ } });
700
+ Object.defineProperty(exports2, "workflowDedupClient", { enumerable: true, get: function() {
701
+ return dedup_1.workflowDedupClient;
702
+ } });
703
+ }
704
+ });
705
+
706
+ // src/workflows/control-plane/rename-cascade/rename-list-targets.handler.ts
707
+ var rename_list_targets_handler_exports = {};
708
+ __export(rename_list_targets_handler_exports, {
709
+ handler: () => handler
710
+ });
711
+ module.exports = __toCommonJS(rename_list_targets_handler_exports);
712
+ var import_node_crypto = require("crypto");
713
+
714
+ // src/data/operations/control/rename-cascade/rename-cascade-list-targets-operation.ts
715
+ var import_workflows = __toESM(require_lib());
716
+
717
+ // src/data/dynamo/dynamo-control-service.ts
718
+ var import_electrodb14 = require("electrodb");
719
+
720
+ // src/data/dynamo/dynamo-client.ts
721
+ var import_client_dynamodb = require("@aws-sdk/client-dynamodb");
722
+ var defaultTableName = process.env.DYNAMO_TABLE_NAME ?? "jesttesttable";
723
+ var dynamoClient = new import_client_dynamodb.DynamoDBClient({
724
+ ...process.env.MOCK_DYNAMODB_ENDPOINT && {
725
+ endpoint: process.env.MOCK_DYNAMODB_ENDPOINT,
726
+ sslEnabled: false,
727
+ region: "local"
728
+ }
729
+ });
730
+
731
+ // src/data/dynamo/entities/control/configuration-entity.ts
732
+ var import_electrodb = require("electrodb");
733
+
734
+ // src/data/dynamo/entities/control/control-entity-common.ts
735
+ var import_types = require("@openhi/types");
736
+
737
+ // src/data/dynamo/shard.ts
738
+ var SHARD_COUNT = 4;
739
+ function computeShard(id) {
740
+ let hash = 2166136261;
741
+ for (let i = 0; i < id.length; i++) {
742
+ hash ^= id.charCodeAt(i);
743
+ hash = Math.imul(hash, 16777619);
744
+ }
745
+ return (hash >>> 0) % SHARD_COUNT;
746
+ }
747
+
748
+ // src/data/dynamo/entities/control/control-entity-common.ts
749
+ var gsi1ShardAttribute = {
750
+ type: "string",
751
+ watch: ["id"],
752
+ set: (_val, item) => {
753
+ if (typeof item?.id !== "string" || item.id.length === 0) {
754
+ return void 0;
755
+ }
756
+ return String(computeShard(item.id));
757
+ }
758
+ };
759
+ var gsi1skAttribute = {
760
+ type: "string",
761
+ watch: ["resource", "lastUpdated", "id"],
762
+ set: (_val, item) => {
763
+ const id = typeof item?.id === "string" ? item.id : "";
764
+ const lastUpdated = typeof item?.lastUpdated === "string" ? item.lastUpdated : "";
765
+ const fallback = `${lastUpdated}#${id}`;
766
+ if (typeof item?.resource !== "string" || item.resource.length === 0) {
767
+ return fallback;
768
+ }
769
+ let parsed;
770
+ try {
771
+ parsed = JSON.parse(item.resource);
772
+ } catch {
773
+ return fallback;
774
+ }
775
+ if (!parsed || typeof parsed !== "object") return fallback;
776
+ const resourceType = parsed.resourceType;
777
+ if (typeof resourceType !== "string") return fallback;
778
+ const label = (0, import_types.extractLabel)(parsed);
779
+ return label !== void 0 ? `${label}#${id}` : fallback;
780
+ }
781
+ };
782
+ function extractRoleId(resource) {
783
+ const flat = resource.roleId;
784
+ if (typeof flat === "string" && flat.length > 0) return flat;
785
+ const role = resource.role;
786
+ if (role && typeof role === "object") {
787
+ const reference = role.reference;
788
+ if (typeof reference === "string" && reference.length > 0) {
789
+ const slash = reference.lastIndexOf("/");
790
+ const tail = slash >= 0 ? reference.slice(slash + 1) : reference;
791
+ if (tail.length > 0) return tail;
792
+ }
793
+ }
794
+ return void 0;
795
+ }
796
+ var roleAssignmentGsi1skAttribute = {
797
+ type: "string",
798
+ watch: ["resource", "denormalizedUserName", "lastUpdated", "id"],
799
+ set: (_val, item) => {
800
+ const id = typeof item?.id === "string" ? item.id : "";
801
+ const lastUpdated = typeof item?.lastUpdated === "string" ? item.lastUpdated : "";
802
+ const fallback = `${lastUpdated}#${id}`;
803
+ if (typeof item?.resource !== "string" || item.resource.length === 0) {
804
+ return fallback;
805
+ }
806
+ let parsed;
807
+ try {
808
+ parsed = JSON.parse(item.resource);
809
+ } catch {
810
+ return fallback;
811
+ }
812
+ if (!parsed || typeof parsed !== "object") return fallback;
813
+ const roleId = extractRoleId(parsed);
814
+ if (roleId === void 0) return fallback;
815
+ const denormalizedUserName = typeof item.denormalizedUserName === "string" ? item.denormalizedUserName : "";
816
+ const normalizedUserName = denormalizedUserName.length > 0 ? (0, import_types.normalizeLabel)(denormalizedUserName) : "";
817
+ if (normalizedUserName.length === 0) return fallback;
818
+ return `${roleId}#${normalizedUserName}#${id}`;
819
+ }
820
+ };
821
+ var membershipGsi1skAttribute = {
822
+ type: "string",
823
+ watch: ["denormalizedUserName", "lastUpdated", "id"],
824
+ set: (_val, item) => {
825
+ const id = typeof item?.id === "string" ? item.id : "";
826
+ const lastUpdated = typeof item?.lastUpdated === "string" ? item.lastUpdated : "";
827
+ const fallback = `${lastUpdated}#${id}`;
828
+ const denormalizedUserName = typeof item?.denormalizedUserName === "string" ? item.denormalizedUserName : "";
829
+ const normalizedUserName = denormalizedUserName.length > 0 ? (0, import_types.normalizeLabel)(denormalizedUserName) : "";
830
+ if (normalizedUserName.length === 0) {
831
+ return fallback;
832
+ }
833
+ return `${normalizedUserName}#${id}`;
834
+ }
835
+ };
836
+
837
+ // src/data/dynamo/entities/control/configuration-entity.ts
838
+ var ConfigurationEntity = new import_electrodb.Entity({
839
+ model: {
840
+ entity: "configuration",
841
+ service: "control",
842
+ version: "01"
843
+ },
844
+ attributes: {
845
+ /** Sort key. "CURRENT" for current version; version history in S3. */
846
+ sk: {
847
+ type: "string",
848
+ required: true,
849
+ default: "CURRENT"
850
+ },
851
+ /** Tenant scope. Use "BASELINE" when the config is baseline default (no tenant). */
852
+ tenantId: {
853
+ type: "string",
854
+ required: true,
855
+ default: "BASELINE"
856
+ },
857
+ /** Workspace scope. Use "-" when absent. */
858
+ workspaceId: {
859
+ type: "string",
860
+ required: true,
861
+ default: "-"
862
+ },
863
+ /** User scope. Use "-" when absent. */
864
+ userId: {
865
+ type: "string",
866
+ required: true,
867
+ default: "-"
868
+ },
869
+ /** Role scope. Use "-" when absent. */
870
+ roleId: {
871
+ type: "string",
872
+ required: true,
873
+ default: "-"
874
+ },
875
+ /** Config type (category), e.g. endpoints, branding, display. */
876
+ key: {
877
+ type: "string",
878
+ required: true
879
+ },
880
+ /** FHIR Resource.id; logical id in URL and for the Configuration resource. */
881
+ id: {
882
+ type: "string",
883
+ required: true
884
+ },
885
+ /** Payload as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */
886
+ resource: {
887
+ type: "string",
888
+ required: true
889
+ },
890
+ /**
891
+ * Summary projection (key display fields as JSON string: id, key, status).
892
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
893
+ */
894
+ summary: {
895
+ type: "string",
896
+ required: true
897
+ },
898
+ /** Version id (e.g. ULID). Tracks current version; S3 history key. */
899
+ vid: {
900
+ type: "string",
901
+ required: true
902
+ },
903
+ lastUpdated: {
904
+ type: "string",
905
+ required: true
906
+ },
907
+ gsi1Shard: gsi1ShardAttribute,
908
+ deleted: {
909
+ type: "boolean",
910
+ required: false
911
+ },
912
+ bundleId: {
913
+ type: "string",
914
+ required: false
915
+ },
916
+ msgId: {
917
+ type: "string",
918
+ required: false
919
+ }
920
+ },
921
+ indexes: {
922
+ /** 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. */
923
+ record: {
924
+ pk: {
925
+ field: "PK",
926
+ composite: ["tenantId", "workspaceId", "userId", "roleId"],
927
+ template: "CONFIG#TID#${tenantId}#WID#${workspaceId}#UID#${userId}#RID#${roleId}"
928
+ },
929
+ sk: {
930
+ field: "SK",
931
+ composite: ["key", "sk"],
932
+ template: "KEY#${key}#SK#${sk}"
933
+ }
934
+ },
935
+ /**
936
+ * GSI1 — Unified Sharded List per ADR-011: list all Configuration entries for a
937
+ * (tenant, workspace) across the four shards. Use for "list configs scoped to this tenant"
938
+ * (workspaceId = "-") or "list configs scoped to this workspace". Does not support
939
+ * hierarchical resolution in one query; use base table GetItem in fallback order
940
+ * (user → workspace → tenant → baseline) for that.
941
+ * SK is `<key>#<id>` — Configuration's `key` is a required entity attribute (the
942
+ * config category: endpoints, branding, display, …) and the natural sort/lookup
943
+ * dimension. `casing: "none"` preserves the literal key value.
944
+ */
945
+ gsi1: {
946
+ index: "GSI1",
947
+ pk: {
948
+ field: "GSI1PK",
949
+ composite: ["tenantId", "workspaceId", "gsi1Shard"],
950
+ template: "TID#${tenantId}#WID#${workspaceId}#RT#Configuration#SHARD#${gsi1Shard}"
951
+ },
952
+ sk: {
953
+ field: "GSI1SK",
954
+ casing: "none",
955
+ composite: ["key", "id"],
956
+ template: "${key}#${id}"
957
+ }
958
+ }
959
+ }
960
+ });
961
+
962
+ // src/data/dynamo/entities/control/configuration-user-projection-entity.ts
963
+ var import_electrodb2 = require("electrodb");
964
+ var ConfigurationUserProjectionEntity = new import_electrodb2.Entity({
965
+ model: {
966
+ entity: "configurationUserProjection",
967
+ service: "control",
968
+ version: "01"
969
+ },
970
+ attributes: {
971
+ /**
972
+ * User partition discriminator. Renders as `USER#ID#<userId>` on the
973
+ * base-table PK. Always required — the projection has no meaning
974
+ * outside a user partition.
975
+ */
976
+ userId: {
977
+ type: "string",
978
+ required: true
979
+ },
980
+ /**
981
+ * Pre-composed sort key — built by the operations-layer projection
982
+ * writer via `buildConfigurationUserProjectionSk`. The entity stores
983
+ * the value verbatim so the SK grammar (pattern #10 user-scope) is
984
+ * owned by the operations layer, not duplicated here.
985
+ */
986
+ sk: {
987
+ type: "string",
988
+ required: true
989
+ },
990
+ /**
991
+ * Configuration canonical-record id. Stored as a discriminating
992
+ * field so consumers can hydrate the canonical row via the
993
+ * Configuration get-by-id operation when the projection's `summary`
994
+ * is insufficient.
995
+ */
996
+ configurationId: {
997
+ type: "string",
998
+ required: true
999
+ },
1000
+ /**
1001
+ * Tenant the Configuration is associated with. The canonical row
1002
+ * keys off `(tenantId, workspaceId, userId, roleId)`; the projection
1003
+ * carries `tenantId` so consumers reconstructing the canonical PK
1004
+ * have the tenant segment without a hop.
1005
+ */
1006
+ tenantId: {
1007
+ type: "string",
1008
+ required: true
1009
+ },
1010
+ /**
1011
+ * Scope marker. Always `"user"` on this projection — recorded
1012
+ * explicitly so future scope-bearing projections (workspace,
1013
+ * tenant, role) can share filter semantics in a unified
1014
+ * cross-projection list query if one ever lands.
1015
+ */
1016
+ scope: {
1017
+ type: "string",
1018
+ required: true,
1019
+ default: "user"
1020
+ },
1021
+ /**
1022
+ * Configuration's `key` attribute (config category, e.g. endpoints,
1023
+ * branding, display). Mirrored from the canonical row so consumers
1024
+ * reading the projection get the natural display label without a
1025
+ * BatchGet hop. Doubles as the source of `<normalizedConfigName>` in
1026
+ * the SK.
1027
+ */
1028
+ displayName: {
1029
+ type: "string",
1030
+ required: false
1031
+ },
1032
+ /**
1033
+ * Summary projection (key display fields as JSON string) — mirrored
1034
+ * from the canonical Configuration row so user-partition queries do
1035
+ * not need a BatchGet hop.
1036
+ */
1037
+ summary: {
1038
+ type: "string",
1039
+ required: true
1040
+ },
1041
+ /** Version id mirrored from the canonical Configuration row. */
1042
+ vid: {
1043
+ type: "string",
1044
+ required: true
1045
+ },
1046
+ /** Last-updated timestamp mirrored from the canonical Configuration row. */
1047
+ lastUpdated: {
1048
+ type: "string",
1049
+ required: true
1050
+ }
1051
+ },
1052
+ indexes: {
1053
+ /**
1054
+ * Base table: PK = USER#ID#<userId>, SK = operation-supplied. A
1055
+ * single `Query(PK = USER#ID#<userId>, SK begins_with
1056
+ * 'CONFIGURATION#')` returns the user's user-scoped Configurations
1057
+ * sorted by `<normalizedConfigName>` (then `<configurationId>` as
1058
+ * the tiebreaker).
1059
+ */
1060
+ record: {
1061
+ pk: {
1062
+ field: "PK",
1063
+ composite: ["userId"],
1064
+ template: "USER#ID#${userId}"
1065
+ },
1066
+ sk: {
1067
+ field: "SK",
1068
+ casing: "none",
1069
+ composite: ["sk"],
1070
+ template: "${sk}"
1071
+ }
1072
+ }
1073
+ }
1074
+ });
1075
+
1076
+ // src/data/dynamo/entities/control/configuration-workspace-projection-entity.ts
1077
+ var import_electrodb3 = require("electrodb");
1078
+ var ConfigurationWorkspaceProjectionEntity = new import_electrodb3.Entity({
1079
+ model: {
1080
+ entity: "configurationWorkspaceProjection",
1081
+ service: "control",
1082
+ version: "01"
1083
+ },
1084
+ attributes: {
1085
+ /**
1086
+ * Tenant the workspace belongs to. Renders as the leading segment
1087
+ * of the base-table PK. Always required — the workspace partition
1088
+ * is tenant-scoped per ADR-011.
1089
+ */
1090
+ tenantId: {
1091
+ type: "string",
1092
+ required: true
1093
+ },
1094
+ /**
1095
+ * Workspace partition discriminator. Renders as the trailing
1096
+ * segment of the base-table PK
1097
+ * (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —
1098
+ * the projection has no meaning outside a workspace partition.
1099
+ */
1100
+ workspaceId: {
1101
+ type: "string",
1102
+ required: true
1103
+ },
1104
+ /**
1105
+ * Pre-composed sort key — built by the operations-layer projection
1106
+ * writer via `buildConfigurationWorkspaceProjectionSk`. The entity
1107
+ * stores the value verbatim so the SK grammar (pattern #10
1108
+ * workspace-scope) is owned by the operations layer, not
1109
+ * duplicated here.
1110
+ */
1111
+ sk: {
1112
+ type: "string",
1113
+ required: true
1114
+ },
1115
+ /**
1116
+ * Configuration canonical-record id. Stored as a discriminating
1117
+ * field so consumers can hydrate the canonical row via the
1118
+ * Configuration get-by-id operation when the projection's `summary`
1119
+ * is insufficient.
1120
+ */
1121
+ configurationId: {
1122
+ type: "string",
1123
+ required: true
1124
+ },
1125
+ /**
1126
+ * Scope marker. Always `"workspace"` on this projection — recorded
1127
+ * explicitly so future scope-bearing projections (user, tenant,
1128
+ * role) can share filter semantics in a unified cross-projection
1129
+ * list query if one ever lands.
1130
+ */
1131
+ scope: {
1132
+ type: "string",
1133
+ required: true,
1134
+ default: "workspace"
1135
+ },
1136
+ /**
1137
+ * Configuration's `key` attribute (config category, e.g. endpoints,
1138
+ * branding, display). Mirrored from the canonical row so consumers
1139
+ * reading the projection get the natural display label without a
1140
+ * BatchGet hop. Doubles as the source of `<normalizedConfigName>`
1141
+ * in the SK.
1142
+ */
1143
+ displayName: {
1144
+ type: "string",
1145
+ required: false
1146
+ },
1147
+ /**
1148
+ * Summary projection (key display fields as JSON string) — mirrored
1149
+ * from the canonical Configuration row so workspace-partition
1150
+ * queries do not need a BatchGet hop.
1151
+ */
1152
+ summary: {
1153
+ type: "string",
1154
+ required: true
1155
+ },
1156
+ /** Version id mirrored from the canonical Configuration row. */
1157
+ vid: {
1158
+ type: "string",
1159
+ required: true
1160
+ },
1161
+ /** Last-updated timestamp mirrored from the canonical Configuration row. */
1162
+ lastUpdated: {
1163
+ type: "string",
1164
+ required: true
1165
+ }
1166
+ },
1167
+ indexes: {
1168
+ /**
1169
+ * Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,
1170
+ * SK = operation-supplied. A single
1171
+ * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'CONFIGURATION#')`
1172
+ * returns the workspace's workspace-scoped Configurations sorted by
1173
+ * `<normalizedConfigName>` (then `<configurationId>` as the
1174
+ * tiebreaker).
1175
+ */
1176
+ record: {
1177
+ pk: {
1178
+ field: "PK",
1179
+ composite: ["tenantId", "workspaceId"],
1180
+ template: "TID#${tenantId}#WORKSPACE#ID#${workspaceId}"
1181
+ },
1182
+ sk: {
1183
+ field: "SK",
1184
+ casing: "none",
1185
+ composite: ["sk"],
1186
+ template: "${sk}"
1187
+ }
1188
+ }
1189
+ }
1190
+ });
1191
+
1192
+ // src/data/dynamo/entities/control/membership-entity.ts
1193
+ var import_electrodb4 = require("electrodb");
1194
+ var MembershipEntity = new import_electrodb4.Entity({
1195
+ model: {
1196
+ entity: "membership",
1197
+ service: "control",
1198
+ version: "01"
1199
+ },
1200
+ attributes: {
1201
+ /** Sort key sentinel. Always "CURRENT". */
1202
+ sk: {
1203
+ type: "string",
1204
+ required: true,
1205
+ default: "CURRENT"
1206
+ },
1207
+ /** Tenant in which the user has membership (required). */
1208
+ tenantId: {
1209
+ type: "string",
1210
+ required: true
1211
+ },
1212
+ /** FHIR Resource.id; membership id. */
1213
+ id: {
1214
+ type: "string",
1215
+ required: true
1216
+ },
1217
+ /** Full Membership resource serialized as JSON string. */
1218
+ resource: {
1219
+ type: "string",
1220
+ required: true
1221
+ },
1222
+ /**
1223
+ * Summary projection (key display fields as JSON string: id, displayName, status).
1224
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
1225
+ */
1226
+ summary: {
1227
+ type: "string",
1228
+ required: true
1229
+ },
1230
+ /** Version id (e.g. ULID). */
1231
+ vid: {
1232
+ type: "string",
1233
+ required: true
1234
+ },
1235
+ lastUpdated: {
1236
+ type: "string",
1237
+ required: true
1238
+ },
1239
+ gsi1Shard: gsi1ShardAttribute,
1240
+ /**
1241
+ * Derived GSI1 sort key — `<normalizedUserName>#<id>` per ADR-018
1242
+ * pattern #1 so a GSI1 query partitioned on the tenant range-scans
1243
+ * by user-name prefix and returns memberships sorted by user name.
1244
+ * Falls back to `<lastUpdated>#<id>` when `denormalizedUserName`
1245
+ * is missing.
1246
+ */
1247
+ gsi1sk: membershipGsi1skAttribute,
1248
+ deleted: {
1249
+ type: "boolean",
1250
+ required: false
1251
+ },
1252
+ bundleId: {
1253
+ type: "string",
1254
+ required: false
1255
+ },
1256
+ msgId: {
1257
+ type: "string",
1258
+ required: false
1259
+ },
1260
+ /**
1261
+ * Denormalized `linked-data-identity` Reference (e.g. `Practitioner/abc`).
1262
+ * Populated from the FHIR extension on the Membership resource at write
1263
+ * time so future GSIs can index data-plane identity lookups without
1264
+ * deserializing the full resource JSON. See ADR 2026-03-13-02 §6.
1265
+ */
1266
+ linkedDataIdentityRef: {
1267
+ type: "string",
1268
+ required: false
1269
+ },
1270
+ /**
1271
+ * Denormalized display name of the linked Tenant, captured at row
1272
+ * last-write time. Promoted to a top-level attribute so the ADR-018
1273
+ * adjacency-list projection SKs (pattern #3 — `MEMBERSHIP#TENANT#<normalizedTenantName>#…`)
1274
+ * can be composed from a top-level field instead of digging into the
1275
+ * `resource` JSON. Optional on the schema so pre-TR-024 rows do not
1276
+ * break; the operations-layer multi-write helper (#1010) makes the
1277
+ * field load-bearing at write time per TR-024 rule 2 (write-time
1278
+ * source = canonical Tenant.displayName).
1279
+ * @see TR-024 — Denormalized display-name attributes
1280
+ */
1281
+ denormalizedTenantName: {
1282
+ type: "string",
1283
+ required: false
1284
+ },
1285
+ /**
1286
+ * Denormalized display name of the linked User, captured at row
1287
+ * last-write time. Promoted to a top-level attribute so the ADR-018
1288
+ * adjacency-list canonical-record GSI1SK (pattern #1 —
1289
+ * `<normalizedUserName>#<id>`) and workspace-projection SK (pattern #2)
1290
+ * can be composed from a top-level field. Optional on the schema so
1291
+ * pre-TR-024 rows do not break; the operations-layer multi-write helper
1292
+ * (#1010) makes the field load-bearing at write time per TR-024 rule 2
1293
+ * (write-time source = canonical User.displayName).
1294
+ * @see TR-024 — Denormalized display-name attributes
1295
+ */
1296
+ denormalizedUserName: {
1297
+ type: "string",
1298
+ required: false
1299
+ }
1300
+ },
1301
+ indexes: {
1302
+ /** Base table: PK = TID#<tenantId>#MEMBERSHIP#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
1303
+ record: {
1304
+ pk: {
1305
+ field: "PK",
1306
+ composite: ["tenantId", "id"],
1307
+ template: "TID#${tenantId}#MEMBERSHIP#ID#${id}"
1308
+ },
1309
+ sk: {
1310
+ field: "SK",
1311
+ composite: ["sk"],
1312
+ template: "${sk}"
1313
+ }
1314
+ },
1315
+ /**
1316
+ * GSI1 — Unified Sharded List per ADR-011: list all Memberships for a tenant across the
1317
+ * four shards. Membership is tenant-scoped only, so `WID#-` is a sentinel.
1318
+ * SK is derived via `membershipGsi1skAttribute` — composes
1319
+ * `<normalizedUserName>#<id>` per ADR-018 pattern #1 (users in a
1320
+ * tenant, sorted by user name); falls back to `<lastUpdated>#<id>`
1321
+ * when `denormalizedUserName` is missing. `casing: "none"` preserves
1322
+ * the normalized label and ISO-8601 `T`/`Z`.
1323
+ */
1324
+ gsi1: {
1325
+ index: "GSI1",
1326
+ pk: {
1327
+ field: "GSI1PK",
1328
+ composite: ["tenantId", "gsi1Shard"],
1329
+ template: "TID#${tenantId}#WID#-#RT#Membership#SHARD#${gsi1Shard}"
1330
+ },
1331
+ sk: {
1332
+ field: "GSI1SK",
1333
+ casing: "none",
1334
+ composite: ["gsi1sk"],
1335
+ template: "${gsi1sk}"
1336
+ }
1337
+ }
1338
+ }
1339
+ });
1340
+
1341
+ // src/data/dynamo/entities/control/membership-user-projection-entity.ts
1342
+ var import_electrodb5 = require("electrodb");
1343
+ var MembershipUserProjectionEntity = new import_electrodb5.Entity({
1344
+ model: {
1345
+ entity: "membershipUserProjection",
1346
+ service: "control",
1347
+ version: "01"
1348
+ },
1349
+ attributes: {
1350
+ /**
1351
+ * User partition discriminator. Renders as `USER#ID#<userId>` on the
1352
+ * base-table PK. Always required — the projection has no meaning
1353
+ * outside a user partition.
1354
+ */
1355
+ userId: {
1356
+ type: "string",
1357
+ required: true
1358
+ },
1359
+ /**
1360
+ * Pre-composed sort key — built by the operations-layer projection
1361
+ * writer via `buildMembershipUserProjectionSk*` helpers. The entity
1362
+ * stores the value verbatim so the SK grammar (patterns #3 and #4)
1363
+ * is owned by the operations layer, not duplicated here.
1364
+ */
1365
+ sk: {
1366
+ type: "string",
1367
+ required: true
1368
+ },
1369
+ /** Tenant in which the membership applies. Always required. */
1370
+ tenantId: {
1371
+ type: "string",
1372
+ required: true
1373
+ },
1374
+ /**
1375
+ * Workspace the membership scopes to. Present iff the projection
1376
+ * row is a pattern-#4 workspace sub-lane row; absent for pattern-#3
1377
+ * tenant sub-lane rows.
1378
+ */
1379
+ workspaceId: {
1380
+ type: "string",
1381
+ required: false
1382
+ },
1383
+ /**
1384
+ * Membership canonical-record id. Stored as a discriminating field
1385
+ * so consumers can hydrate the canonical row via
1386
+ * `MembershipEntity.get({ tenantId, id: membershipId })` when the
1387
+ * projection's `summary` is insufficient.
1388
+ */
1389
+ membershipId: {
1390
+ type: "string",
1391
+ required: true
1392
+ },
1393
+ /**
1394
+ * Summary projection (key display fields as JSON string: id,
1395
+ * displayName, status) — mirrored from the canonical Membership row
1396
+ * so user-partition queries do not need a BatchGet hop.
1397
+ */
1398
+ summary: {
1399
+ type: "string",
1400
+ required: true
1401
+ },
1402
+ /** Version id mirrored from the canonical Membership row. */
1403
+ vid: {
1404
+ type: "string",
1405
+ required: true
1406
+ },
1407
+ /** Last-updated timestamp mirrored from the canonical Membership row. */
1408
+ lastUpdated: {
1409
+ type: "string",
1410
+ required: true
1411
+ },
1412
+ /**
1413
+ * Denormalized Tenant display name — required to compose pattern-#3
1414
+ * SK (`MEMBERSHIP#TENANT#<normalizedTenantName>#…`). Optional on the
1415
+ * schema because pre-TR-024 rows may not carry a display name; the
1416
+ * operations layer falls back gracefully when missing.
1417
+ */
1418
+ denormalizedTenantName: {
1419
+ type: "string",
1420
+ required: false
1421
+ },
1422
+ /**
1423
+ * Denormalized User display name — mirrored from the canonical
1424
+ * Membership row per TR-024 rule 3 (canonical-record symmetry).
1425
+ * Carried on the projection so consumers can render the user's
1426
+ * display name without a hop to the User record.
1427
+ */
1428
+ denormalizedUserName: {
1429
+ type: "string",
1430
+ required: false
1431
+ },
1432
+ /**
1433
+ * Denormalized Workspace display name — required to compose
1434
+ * pattern-#4 SK (`MEMBERSHIP#WORKSPACE#TID#<tenantId>#<normalizedWorkspaceName>#…`).
1435
+ * Optional on the schema (TR-024 § Open Item #4 defers a formal
1436
+ * Workspace-rename cascade); the operations layer falls back to a
1437
+ * sentinel when missing so the SK still has a valid shape.
1438
+ */
1439
+ denormalizedWorkspaceName: {
1440
+ type: "string",
1441
+ required: false
1442
+ }
1443
+ },
1444
+ indexes: {
1445
+ /**
1446
+ * Base table: PK = USER#ID#<userId>, SK = operation-supplied.
1447
+ * Both pattern #3 and pattern #4 use this same index — the SK string
1448
+ * encodes the lane discriminator (`MEMBERSHIP#TENANT#…` vs
1449
+ * `MEMBERSHIP#WORKSPACE#…`) so a single `Query(PK = USER#ID#<userId>,
1450
+ * SK begins_with 'MEMBERSHIP#')` returns both lanes interleaved.
1451
+ */
1452
+ record: {
1453
+ pk: {
1454
+ field: "PK",
1455
+ composite: ["userId"],
1456
+ template: "USER#ID#${userId}"
1457
+ },
1458
+ sk: {
1459
+ field: "SK",
1460
+ casing: "none",
1461
+ composite: ["sk"],
1462
+ template: "${sk}"
1463
+ }
1464
+ }
1465
+ }
1466
+ });
1467
+
1468
+ // src/data/dynamo/entities/control/membership-workspace-projection-entity.ts
1469
+ var import_electrodb6 = require("electrodb");
1470
+ var MembershipWorkspaceProjectionEntity = new import_electrodb6.Entity({
1471
+ model: {
1472
+ entity: "membershipWorkspaceProjection",
1473
+ service: "control",
1474
+ version: "01"
1475
+ },
1476
+ attributes: {
1477
+ /**
1478
+ * Tenant the workspace belongs to. Renders as the leading segment
1479
+ * of the base-table PK. Always required — the workspace partition
1480
+ * is tenant-scoped per ADR-011.
1481
+ */
1482
+ tenantId: {
1483
+ type: "string",
1484
+ required: true
1485
+ },
1486
+ /**
1487
+ * Workspace partition discriminator. Renders as the trailing
1488
+ * segment of the base-table PK
1489
+ * (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —
1490
+ * the projection has no meaning outside a workspace partition.
1491
+ */
1492
+ workspaceId: {
1493
+ type: "string",
1494
+ required: true
1495
+ },
1496
+ /**
1497
+ * Pre-composed sort key — built by the operations-layer projection
1498
+ * writer via `buildMembershipWorkspaceProjectionSk`. The entity
1499
+ * stores the value verbatim so the SK grammar (pattern #2) is
1500
+ * owned by the operations layer, not duplicated here.
1501
+ */
1502
+ sk: {
1503
+ type: "string",
1504
+ required: true
1505
+ },
1506
+ /**
1507
+ * User the membership links. Stored as a discriminating field so
1508
+ * consumers can hydrate the canonical User row via
1509
+ * `UserEntity.get({ id: userId, sk: "CURRENT" })` when the
1510
+ * projection's `summary` is insufficient.
1511
+ */
1512
+ userId: {
1513
+ type: "string",
1514
+ required: true
1515
+ },
1516
+ /**
1517
+ * Membership canonical-record id. Stored as a discriminating field
1518
+ * so consumers can hydrate the canonical row via
1519
+ * `MembershipEntity.get({ tenantId, id: membershipId })` when the
1520
+ * projection's `summary` is insufficient.
1521
+ */
1522
+ membershipId: {
1523
+ type: "string",
1524
+ required: true
1525
+ },
1526
+ /**
1527
+ * Summary projection (key display fields as JSON string: id,
1528
+ * displayName, status) — mirrored from the canonical Membership row
1529
+ * so workspace-partition queries do not need a BatchGet hop.
1530
+ */
1531
+ summary: {
1532
+ type: "string",
1533
+ required: true
1534
+ },
1535
+ /** Version id mirrored from the canonical Membership row. */
1536
+ vid: {
1537
+ type: "string",
1538
+ required: true
1539
+ },
1540
+ /** Last-updated timestamp mirrored from the canonical Membership row. */
1541
+ lastUpdated: {
1542
+ type: "string",
1543
+ required: true
1544
+ },
1545
+ /**
1546
+ * Denormalized User display name — required to compose the
1547
+ * pattern-#2 SK (`MEMBERSHIP#<normalizedUserName>#…`). Optional on
1548
+ * the schema because pre-TR-024 rows may not carry a display name;
1549
+ * the operations layer falls back to a sentinel when missing so
1550
+ * the SK still has a valid shape. The TR-023 rename-cascade
1551
+ * pipeline rewrites the SK on a User rename.
1552
+ */
1553
+ denormalizedUserName: {
1554
+ type: "string",
1555
+ required: false
1556
+ }
1557
+ },
1558
+ indexes: {
1559
+ /**
1560
+ * Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,
1561
+ * SK = operation-supplied. Pattern #2 uses this index — the SK
1562
+ * encodes the entity-type prefix (`MEMBERSHIP#…`) so a
1563
+ * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'MEMBERSHIP#')`
1564
+ * returns every member projection for the workspace in normalized-
1565
+ * user-name sort order.
1566
+ */
1567
+ record: {
1568
+ pk: {
1569
+ field: "PK",
1570
+ composite: ["tenantId", "workspaceId"],
1571
+ template: "TID#${tenantId}#WORKSPACE#ID#${workspaceId}"
1572
+ },
1573
+ sk: {
1574
+ field: "SK",
1575
+ casing: "none",
1576
+ composite: ["sk"],
1577
+ template: "${sk}"
1578
+ }
1579
+ }
1580
+ }
1581
+ });
1582
+
1583
+ // src/data/dynamo/entities/control/role-entity.ts
1584
+ var import_electrodb7 = require("electrodb");
1585
+ var RoleEntity = new import_electrodb7.Entity({
1586
+ model: {
1587
+ entity: "role",
1588
+ service: "control",
1589
+ version: "01"
1590
+ },
1591
+ attributes: {
1592
+ /** Sort key sentinel. Always "CURRENT". */
1593
+ sk: {
1594
+ type: "string",
1595
+ required: true,
1596
+ default: "CURRENT"
1597
+ },
1598
+ /** FHIR Resource.id; role id. */
1599
+ id: {
1600
+ type: "string",
1601
+ required: true
1602
+ },
1603
+ /** Full Role resource serialized as JSON string. */
1604
+ resource: {
1605
+ type: "string",
1606
+ required: true
1607
+ },
1608
+ /**
1609
+ * Summary projection (key display fields as JSON string: id, displayName, status).
1610
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
1611
+ */
1612
+ summary: {
1613
+ type: "string",
1614
+ required: true
1615
+ },
1616
+ /** Version id (e.g. ULID). */
1617
+ vid: {
1618
+ type: "string",
1619
+ required: true
1620
+ },
1621
+ lastUpdated: {
1622
+ type: "string",
1623
+ required: true
1624
+ },
1625
+ gsi1Shard: gsi1ShardAttribute,
1626
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
1627
+ gsi1sk: gsi1skAttribute,
1628
+ deleted: {
1629
+ type: "boolean",
1630
+ required: false
1631
+ },
1632
+ bundleId: {
1633
+ type: "string",
1634
+ required: false
1635
+ },
1636
+ msgId: {
1637
+ type: "string",
1638
+ required: false
1639
+ }
1640
+ },
1641
+ indexes: {
1642
+ /** Base table: PK = ROLE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
1643
+ record: {
1644
+ pk: {
1645
+ field: "PK",
1646
+ composite: ["id"],
1647
+ template: "ROLE#ID#${id}"
1648
+ },
1649
+ sk: {
1650
+ field: "SK",
1651
+ composite: ["sk"],
1652
+ template: "${sk}"
1653
+ }
1654
+ },
1655
+ /**
1656
+ * GSI1 — Unified Sharded List per ADR-011: list all Roles across the four shards.
1657
+ * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#Role#SHARD#<n>`.
1658
+ * SK is derived via `gsi1skAttribute` — uses the resource's natural label when
1659
+ * extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
1660
+ * normalized label and ISO-8601 `T`/`Z`.
1661
+ */
1662
+ gsi1: {
1663
+ index: "GSI1",
1664
+ pk: {
1665
+ field: "GSI1PK",
1666
+ composite: ["gsi1Shard"],
1667
+ template: "TID#-#WID#-#RT#Role#SHARD#${gsi1Shard}"
1668
+ },
1669
+ sk: {
1670
+ field: "GSI1SK",
1671
+ casing: "none",
1672
+ composite: ["gsi1sk"],
1673
+ template: "${gsi1sk}"
1674
+ }
1675
+ }
1676
+ }
1677
+ });
1678
+
1679
+ // src/data/dynamo/entities/control/roleassignment-entity.ts
1680
+ var import_electrodb8 = require("electrodb");
1681
+ var RoleAssignmentEntity = new import_electrodb8.Entity({
1682
+ model: {
1683
+ entity: "roleassignment",
1684
+ service: "control",
1685
+ version: "01"
1686
+ },
1687
+ attributes: {
1688
+ /** Sort key sentinel. Always "CURRENT". */
1689
+ sk: {
1690
+ type: "string",
1691
+ required: true,
1692
+ default: "CURRENT"
1693
+ },
1694
+ /** Tenant in which the role assignment applies (required). */
1695
+ tenantId: {
1696
+ type: "string",
1697
+ required: true
1698
+ },
1699
+ /** FHIR Resource.id; role assignment id. */
1700
+ id: {
1701
+ type: "string",
1702
+ required: true
1703
+ },
1704
+ /** Full RoleAssignment resource serialized as JSON string. */
1705
+ resource: {
1706
+ type: "string",
1707
+ required: true
1708
+ },
1709
+ /**
1710
+ * Summary projection (key display fields as JSON string: id, displayName, status).
1711
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
1712
+ */
1713
+ summary: {
1714
+ type: "string",
1715
+ required: true
1716
+ },
1717
+ /** Version id (e.g. ULID). */
1718
+ vid: {
1719
+ type: "string",
1720
+ required: true
1721
+ },
1722
+ lastUpdated: {
1723
+ type: "string",
1724
+ required: true
1725
+ },
1726
+ gsi1Shard: gsi1ShardAttribute,
1727
+ /**
1728
+ * Derived GSI1 sort key — discriminator-first
1729
+ * `<roleId>#<normalizedUserName>#<id>` per ADR-018 pattern #8 so a
1730
+ * GSI1 query partitioned on the tenant can `begins_with('<roleId>#')`
1731
+ * to enumerate every user assigned to a given role, sorted by user
1732
+ * name. Falls back to `<lastUpdated>#<id>` when either component is
1733
+ * missing.
1734
+ */
1735
+ gsi1sk: roleAssignmentGsi1skAttribute,
1736
+ deleted: {
1737
+ type: "boolean",
1738
+ required: false
1739
+ },
1740
+ bundleId: {
1741
+ type: "string",
1742
+ required: false
1743
+ },
1744
+ msgId: {
1745
+ type: "string",
1746
+ required: false
1747
+ },
1748
+ /**
1749
+ * Denormalized display name of the linked Tenant, captured at row
1750
+ * last-write time. Promoted to a top-level attribute so the ADR-018
1751
+ * adjacency-list user-projection SK (pattern #5 —
1752
+ * `ROLEASSIGNMENT#TENANT#<normalizedRoleName>#<roleId>#TID#<tenantId>#<id>`)
1753
+ * can be composed from a top-level field instead of digging into the
1754
+ * `resource` JSON. Optional on the schema so pre-TR-024 rows do not
1755
+ * break; the operations-layer multi-write helper (#1010) makes the
1756
+ * field load-bearing at write time per TR-024 rule 2 (write-time
1757
+ * source = canonical Tenant.displayName).
1758
+ * @see TR-024 — Denormalized display-name attributes
1759
+ */
1760
+ denormalizedTenantName: {
1761
+ type: "string",
1762
+ required: false
1763
+ },
1764
+ /**
1765
+ * Denormalized display name of the linked User, captured at row
1766
+ * last-write time. Promoted to a top-level attribute so the ADR-018
1767
+ * adjacency-list canonical-record GSI1SK (pattern #8 —
1768
+ * `<roleId>#<normalizedUserName>#<id>`) and workspace-projection SK
1769
+ * (pattern #9) can be composed from a top-level field. Optional on
1770
+ * the schema so pre-TR-024 rows do not break; the operations-layer
1771
+ * multi-write helper (#1010) makes the field load-bearing at write
1772
+ * time per TR-024 rule 2 (write-time source = canonical
1773
+ * User.displayName).
1774
+ * @see TR-024 — Denormalized display-name attributes
1775
+ */
1776
+ denormalizedUserName: {
1777
+ type: "string",
1778
+ required: false
1779
+ },
1780
+ /**
1781
+ * Denormalized display name of the linked Role, captured at row
1782
+ * last-write time. Promoted to a top-level attribute so the ADR-018
1783
+ * adjacency-list user-projection SK (pattern #5 —
1784
+ * `ROLEASSIGNMENT#TENANT#<normalizedRoleName>#…`) can be composed from
1785
+ * a top-level field. Optional on the schema so pre-TR-024 rows do not
1786
+ * break; the operations-layer multi-write helper (#1010) makes the
1787
+ * field load-bearing at write time per TR-024 rule 2 (write-time
1788
+ * source = canonical Role.displayName).
1789
+ * @see TR-024 — Denormalized display-name attributes
1790
+ */
1791
+ denormalizedRoleName: {
1792
+ type: "string",
1793
+ required: false
1794
+ }
1795
+ },
1796
+ indexes: {
1797
+ /** Base table: PK = TID#<tenantId>#ROLEASSIGNMENT#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
1798
+ record: {
1799
+ pk: {
1800
+ field: "PK",
1801
+ composite: ["tenantId", "id"],
1802
+ template: "TID#${tenantId}#ROLEASSIGNMENT#ID#${id}"
1803
+ },
1804
+ sk: {
1805
+ field: "SK",
1806
+ composite: ["sk"],
1807
+ template: "${sk}"
1808
+ }
1809
+ },
1810
+ /**
1811
+ * GSI1 — Unified Sharded List per ADR-011: list all RoleAssignments for a tenant across the
1812
+ * four shards. Tenant-scoped only, so `WID#-` is a sentinel.
1813
+ * SK is derived via `roleAssignmentGsi1skAttribute` — composes the
1814
+ * discriminator-first `<roleId>#<normalizedUserName>#<id>` shape per
1815
+ * ADR-018 pattern #8 (users with a specific role in a tenant, sorted
1816
+ * by user name); falls back to `<lastUpdated>#<id>` when either
1817
+ * component is missing. `casing: "none"` preserves the normalized
1818
+ * label and ISO-8601 `T`/`Z`.
1819
+ */
1820
+ gsi1: {
1821
+ index: "GSI1",
1822
+ pk: {
1823
+ field: "GSI1PK",
1824
+ composite: ["tenantId", "gsi1Shard"],
1825
+ template: "TID#${tenantId}#WID#-#RT#RoleAssignment#SHARD#${gsi1Shard}"
1826
+ },
1827
+ sk: {
1828
+ field: "GSI1SK",
1829
+ casing: "none",
1830
+ composite: ["gsi1sk"],
1831
+ template: "${gsi1sk}"
1832
+ }
1833
+ }
1834
+ }
1835
+ });
1836
+
1837
+ // src/data/dynamo/entities/control/roleassignment-user-projection-entity.ts
1838
+ var import_electrodb9 = require("electrodb");
1839
+ var RoleAssignmentUserProjectionEntity = new import_electrodb9.Entity({
1840
+ model: {
1841
+ entity: "roleAssignmentUserProjection",
1842
+ service: "control",
1843
+ version: "01"
1844
+ },
1845
+ attributes: {
1846
+ /**
1847
+ * User partition discriminator. Renders as `USER#ID#<userId>` on the
1848
+ * base-table PK. Always required — the projection has no meaning
1849
+ * outside a user partition.
1850
+ */
1851
+ userId: {
1852
+ type: "string",
1853
+ required: true
1854
+ },
1855
+ /**
1856
+ * Pre-composed sort key — built by the operations-layer projection
1857
+ * writer via `buildRoleAssignmentUserProjectionSk*` helpers. The
1858
+ * entity stores the value verbatim so the SK grammar (tenant-lane
1859
+ * vs workspace-lane) is owned by the operations layer, not
1860
+ * duplicated here.
1861
+ */
1862
+ sk: {
1863
+ type: "string",
1864
+ required: true
1865
+ },
1866
+ /** Tenant in which the role assignment applies. Always required. */
1867
+ tenantId: {
1868
+ type: "string",
1869
+ required: true
1870
+ },
1871
+ /**
1872
+ * Workspace the role assignment scopes to. Present iff the
1873
+ * projection row is the workspace-level sub-lane; absent for
1874
+ * tenant-level sub-lane rows.
1875
+ */
1876
+ workspaceId: {
1877
+ type: "string",
1878
+ required: false
1879
+ },
1880
+ /**
1881
+ * Role the assignment grants. Stored as a discriminating field so
1882
+ * `Query(PK = USER#ID#<userId>, SK begins_with 'ROLEASSIGNMENT#…')`
1883
+ * results carry the role id without a hop to the canonical row.
1884
+ */
1885
+ roleId: {
1886
+ type: "string",
1887
+ required: true
1888
+ },
1889
+ /**
1890
+ * RoleAssignment canonical-record id. Stored as a discriminating
1891
+ * field so consumers can hydrate the canonical row via
1892
+ * `RoleAssignmentEntity.get({ tenantId, id: roleAssignmentId })`
1893
+ * when the projection's `summary` is insufficient.
1894
+ */
1895
+ roleAssignmentId: {
1896
+ type: "string",
1897
+ required: true
1898
+ },
1899
+ /**
1900
+ * Summary projection (key display fields as JSON string: id,
1901
+ * displayName, status) — mirrored from the canonical RoleAssignment
1902
+ * row so user-partition queries do not need a BatchGet hop.
1903
+ */
1904
+ summary: {
1905
+ type: "string",
1906
+ required: true
1907
+ },
1908
+ /** Version id mirrored from the canonical RoleAssignment row. */
1909
+ vid: {
1910
+ type: "string",
1911
+ required: true
1912
+ },
1913
+ /** Last-updated timestamp mirrored from the canonical RoleAssignment row. */
1914
+ lastUpdated: {
1915
+ type: "string",
1916
+ required: true
1917
+ },
1918
+ /**
1919
+ * Denormalized Tenant display name — mirrored from the canonical
1920
+ * RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).
1921
+ * Optional on the schema because pre-TR-024 rows may not carry a
1922
+ * display name; the operations layer falls back gracefully when
1923
+ * missing.
1924
+ */
1925
+ denormalizedTenantName: {
1926
+ type: "string",
1927
+ required: false
1928
+ },
1929
+ /**
1930
+ * Denormalized User display name — mirrored from the canonical
1931
+ * RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).
1932
+ * Carried on the projection so consumers can render the user's
1933
+ * display name without a hop to the User record.
1934
+ */
1935
+ denormalizedUserName: {
1936
+ type: "string",
1937
+ required: false
1938
+ },
1939
+ /**
1940
+ * Denormalized Role display name — required to compose the SK's
1941
+ * `<normalizedRoleName>` segment. Optional on the schema (pre-TR-024
1942
+ * rows fall back to a sentinel) but expected to be present at write
1943
+ * time per TR-024 rule 2 (write-time source =
1944
+ * canonical Role.displayName).
1945
+ */
1946
+ denormalizedRoleName: {
1947
+ type: "string",
1948
+ required: false
1949
+ }
1950
+ },
1951
+ indexes: {
1952
+ /**
1953
+ * Base table: PK = USER#ID#<userId>, SK = operation-supplied. Both
1954
+ * sub-lanes (tenant-level and workspace-level) use this same index —
1955
+ * the SK string encodes the lane discriminator
1956
+ * (`ROLEASSIGNMENT#TENANT#…` vs `ROLEASSIGNMENT#WORKSPACE#…`) so a
1957
+ * single `Query(PK = USER#ID#<userId>, SK begins_with
1958
+ * 'ROLEASSIGNMENT#')` returns both lanes interleaved.
1959
+ */
1960
+ record: {
1961
+ pk: {
1962
+ field: "PK",
1963
+ composite: ["userId"],
1964
+ template: "USER#ID#${userId}"
1965
+ },
1966
+ sk: {
1967
+ field: "SK",
1968
+ casing: "none",
1969
+ composite: ["sk"],
1970
+ template: "${sk}"
1971
+ }
1972
+ }
1973
+ }
1974
+ });
1975
+
1976
+ // src/data/dynamo/entities/control/roleassignment-workspace-projection-entity.ts
1977
+ var import_electrodb10 = require("electrodb");
1978
+ var RoleAssignmentWorkspaceProjectionEntity = new import_electrodb10.Entity({
1979
+ model: {
1980
+ entity: "roleAssignmentWorkspaceProjection",
1981
+ service: "control",
1982
+ version: "01"
1983
+ },
1984
+ attributes: {
1985
+ /**
1986
+ * Tenant the workspace belongs to. Renders as the leading segment
1987
+ * of the base-table PK. Always required — the workspace partition
1988
+ * is tenant-scoped per ADR-011.
1989
+ */
1990
+ tenantId: {
1991
+ type: "string",
1992
+ required: true
1993
+ },
1994
+ /**
1995
+ * Workspace partition discriminator. Renders as the trailing
1996
+ * segment of the base-table PK
1997
+ * (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —
1998
+ * the projection has no meaning outside a workspace partition.
1999
+ */
2000
+ workspaceId: {
2001
+ type: "string",
2002
+ required: true
2003
+ },
2004
+ /**
2005
+ * Pre-composed sort key — built by the operations-layer projection
2006
+ * writer via `buildRoleAssignmentWorkspaceProjectionSk`. The entity
2007
+ * stores the value verbatim so the SK grammar (pattern #9) is
2008
+ * owned by the operations layer, not duplicated here.
2009
+ */
2010
+ sk: {
2011
+ type: "string",
2012
+ required: true
2013
+ },
2014
+ /**
2015
+ * User the role assignment grants the role to. Stored as a
2016
+ * discriminating field so consumers can hydrate the canonical User
2017
+ * row via `UserEntity.get({ id: userId, sk: "CURRENT" })` when the
2018
+ * projection's `summary` is insufficient.
2019
+ */
2020
+ userId: {
2021
+ type: "string",
2022
+ required: true
2023
+ },
2024
+ /**
2025
+ * Role the assignment grants. Stored as a discriminating field —
2026
+ * also rendered into the SK as the discriminator-first segment so
2027
+ * `begins_with('ROLEASSIGNMENT#<roleId>#')` filters one role.
2028
+ */
2029
+ roleId: {
2030
+ type: "string",
2031
+ required: true
2032
+ },
2033
+ /**
2034
+ * RoleAssignment canonical-record id. Stored as a discriminating
2035
+ * field so consumers can hydrate the canonical row via
2036
+ * `RoleAssignmentEntity.get({ tenantId, id: roleAssignmentId })`
2037
+ * when the projection's `summary` is insufficient.
2038
+ */
2039
+ roleAssignmentId: {
2040
+ type: "string",
2041
+ required: true
2042
+ },
2043
+ /**
2044
+ * Summary projection (key display fields as JSON string: id,
2045
+ * displayName, status) — mirrored from the canonical RoleAssignment
2046
+ * row so workspace-partition queries do not need a BatchGet hop.
2047
+ */
2048
+ summary: {
2049
+ type: "string",
2050
+ required: true
2051
+ },
2052
+ /** Version id mirrored from the canonical RoleAssignment row. */
2053
+ vid: {
2054
+ type: "string",
2055
+ required: true
2056
+ },
2057
+ /** Last-updated timestamp mirrored from the canonical RoleAssignment row. */
2058
+ lastUpdated: {
2059
+ type: "string",
2060
+ required: true
2061
+ },
2062
+ /**
2063
+ * Denormalized User display name — required to compose the
2064
+ * pattern-#9 SK (`ROLEASSIGNMENT#<roleId>#<normalizedUserName>#…`).
2065
+ * Optional on the schema because pre-TR-024 rows may not carry a
2066
+ * display name; the operations layer falls back to a sentinel when
2067
+ * missing so the SK still has a valid shape. The TR-023 rename-
2068
+ * cascade pipeline rewrites the SK on a User rename.
2069
+ */
2070
+ denormalizedUserName: {
2071
+ type: "string",
2072
+ required: false
2073
+ },
2074
+ /**
2075
+ * Denormalized Role display name — mirrored from the canonical
2076
+ * RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).
2077
+ * Carried on the projection so consumers can render the role's
2078
+ * display name without a hop to the Role record. Not part of the
2079
+ * SK (pattern #9 sorts on `<normalizedUserName>`, not role name) —
2080
+ * a Role rename does NOT rewrite this SK.
2081
+ */
2082
+ denormalizedRoleName: {
2083
+ type: "string",
2084
+ required: false
2085
+ }
2086
+ },
2087
+ indexes: {
2088
+ /**
2089
+ * Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,
2090
+ * SK = operation-supplied. Pattern #9 uses this index — the SK
2091
+ * encodes the entity-type prefix and discriminator-first roleId
2092
+ * (`ROLEASSIGNMENT#<roleId>#…`) so
2093
+ * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'ROLEASSIGNMENT#<roleId>#')`
2094
+ * returns every user-assignment for that role in the workspace, sorted
2095
+ * by normalized user name.
2096
+ */
2097
+ record: {
2098
+ pk: {
2099
+ field: "PK",
2100
+ composite: ["tenantId", "workspaceId"],
2101
+ template: "TID#${tenantId}#WORKSPACE#ID#${workspaceId}"
2102
+ },
2103
+ sk: {
2104
+ field: "SK",
2105
+ casing: "none",
2106
+ composite: ["sk"],
2107
+ template: "${sk}"
2108
+ }
2109
+ }
2110
+ }
2111
+ });
2112
+
2113
+ // src/data/dynamo/entities/control/tenant-entity.ts
2114
+ var import_electrodb11 = require("electrodb");
2115
+ var TenantEntity = new import_electrodb11.Entity({
2116
+ model: {
2117
+ entity: "tenant",
2118
+ service: "control",
2119
+ version: "01"
2120
+ },
2121
+ attributes: {
2122
+ /** Sort key sentinel. Always "CURRENT". */
2123
+ sk: {
2124
+ type: "string",
2125
+ required: true,
2126
+ default: "CURRENT"
2127
+ },
2128
+ /** The tenant's own id (= resource id). Drives the partition key. */
2129
+ tenantId: {
2130
+ type: "string",
2131
+ required: true
2132
+ },
2133
+ /** FHIR Resource.id; logical id in URL. Equals tenantId. */
2134
+ id: {
2135
+ type: "string",
2136
+ required: true
2137
+ },
2138
+ /** Full Tenant resource serialized as JSON string. */
2139
+ resource: {
2140
+ type: "string",
2141
+ required: true
2142
+ },
2143
+ /**
2144
+ * Summary projection (key display fields as JSON string: id, displayName, status).
2145
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
2146
+ */
2147
+ summary: {
2148
+ type: "string",
2149
+ required: true
2150
+ },
2151
+ /** Version id (e.g. ULID). */
2152
+ vid: {
2153
+ type: "string",
2154
+ required: true
2155
+ },
2156
+ lastUpdated: {
2157
+ type: "string",
2158
+ required: true
2159
+ },
2160
+ gsi1Shard: gsi1ShardAttribute,
2161
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
2162
+ gsi1sk: gsi1skAttribute,
2163
+ deleted: {
2164
+ type: "boolean",
2165
+ required: false
2166
+ },
2167
+ bundleId: {
2168
+ type: "string",
2169
+ required: false
2170
+ },
2171
+ msgId: {
2172
+ type: "string",
2173
+ required: false
2174
+ }
2175
+ },
2176
+ indexes: {
2177
+ /** Base table: PK = TENANT#ID#<tenantId>, SK = CURRENT. Do not supply PK or SK from outside. */
2178
+ record: {
2179
+ pk: {
2180
+ field: "PK",
2181
+ composite: ["tenantId"],
2182
+ template: "TENANT#ID#${tenantId}"
2183
+ },
2184
+ sk: {
2185
+ field: "SK",
2186
+ composite: ["sk"],
2187
+ template: "${sk}"
2188
+ }
2189
+ },
2190
+ /**
2191
+ * GSI1 — Unified Sharded List per ADR-011: list all Tenants across the four shards.
2192
+ * Tenant lives at the platform tier (no parent tenant or workspace), so `TID#-#WID#-`
2193
+ * sentinels precede `RT#Tenant#SHARD#<n>`. SK is derived via `gsi1skAttribute` —
2194
+ * `<normalizedName>#<id>` when the resource carries a `name`, else `<lastUpdated>#<id>`
2195
+ * (DR-004). `casing: "none"` preserves the normalized label and ISO-8601 `T`/`Z`.
2196
+ */
2197
+ gsi1: {
2198
+ index: "GSI1",
2199
+ pk: {
2200
+ field: "GSI1PK",
2201
+ composite: ["gsi1Shard"],
2202
+ template: "TID#-#WID#-#RT#Tenant#SHARD#${gsi1Shard}"
2203
+ },
2204
+ sk: {
2205
+ field: "GSI1SK",
2206
+ casing: "none",
2207
+ composite: ["gsi1sk"],
2208
+ template: "${gsi1sk}"
2209
+ }
2210
+ }
2211
+ }
2212
+ });
2213
+
2214
+ // src/data/dynamo/entities/control/user-entity.ts
2215
+ var import_electrodb12 = require("electrodb");
2216
+ var UserEntity = new import_electrodb12.Entity({
2217
+ model: {
2218
+ entity: "user",
2219
+ service: "control",
2220
+ version: "01"
2221
+ },
2222
+ attributes: {
2223
+ /** Sort key sentinel. Always "CURRENT". */
2224
+ sk: {
2225
+ type: "string",
2226
+ required: true,
2227
+ default: "CURRENT"
2228
+ },
2229
+ /** FHIR Resource.id; platform user id (ohi_uid). */
2230
+ id: {
2231
+ type: "string",
2232
+ required: true
2233
+ },
2234
+ /** Full User resource serialized as JSON string. */
2235
+ resource: {
2236
+ type: "string",
2237
+ required: true
2238
+ },
2239
+ /**
2240
+ * Summary projection (key display fields as JSON string: id, displayName, status).
2241
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
2242
+ */
2243
+ summary: {
2244
+ type: "string",
2245
+ required: true
2246
+ },
2247
+ /**
2248
+ * Immutable Cognito-issued `sub` claim. Drives GSI2 (sub-lookup). Optional until the
2249
+ * Post Confirmation Lambda (#770) lands; required thereafter.
2250
+ */
2251
+ cognitoSub: {
2252
+ type: "string",
2253
+ required: false
2254
+ },
2255
+ /** Version id (e.g. ULID). */
2256
+ vid: {
2257
+ type: "string",
2258
+ required: true
2259
+ },
2260
+ lastUpdated: {
2261
+ type: "string",
2262
+ required: true
2263
+ },
2264
+ gsi1Shard: gsi1ShardAttribute,
2265
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
2266
+ gsi1sk: gsi1skAttribute,
2267
+ deleted: {
2268
+ type: "boolean",
2269
+ required: false
2270
+ },
2271
+ /**
2272
+ * TR-022 / ADR-018 lifecycle state for the cascade pipeline.
2273
+ *
2274
+ * - `active` (or undefined) — normal, readable state.
2275
+ * - `deleting` — intermediate state set synchronously by the
2276
+ * hard-delete API entry point. The owning-delete cascade state
2277
+ * machine fans out from this transition (DynamoDB stream →
2278
+ * `control-plane.owning-delete.v1` → Step Functions). Readers MUST
2279
+ * short-circuit on `deleting` so partial cascades stay invisible.
2280
+ * - `deleted-failed` — terminal failure state set by the cascade
2281
+ * finalize Lambda when the cascade run fails irrecoverably.
2282
+ * Operators recover by re-running the cascade or by direct
2283
+ * intervention.
2284
+ *
2285
+ * The cascade finalize step deletes the canonical record conditional
2286
+ * on `lifecycleState = "deleting"`; on replay the conditional check
2287
+ * fails and the finalize step treats that as a no-op success.
2288
+ */
2289
+ lifecycleState: {
2290
+ type: ["active", "deleting", "deleted-failed"],
2291
+ required: false
2292
+ },
2293
+ bundleId: {
2294
+ type: "string",
2295
+ required: false
2296
+ },
2297
+ msgId: {
2298
+ type: "string",
2299
+ required: false
2300
+ }
2301
+ },
2302
+ indexes: {
2303
+ /** Base table: PK = USER#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
2304
+ record: {
2305
+ pk: {
2306
+ field: "PK",
2307
+ composite: ["id"],
2308
+ template: "USER#ID#${id}"
2309
+ },
2310
+ sk: {
2311
+ field: "SK",
2312
+ composite: ["sk"],
2313
+ template: "${sk}"
2314
+ }
2315
+ },
2316
+ /**
2317
+ * GSI1 — Unified Sharded List per ADR-011: list all Users across the four shards.
2318
+ * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#User#SHARD#<n>`.
2319
+ * SK is derived via `gsi1skAttribute` — uses the resource's natural label when
2320
+ * extractable (string `name`/`title` via introspection), else `<lastUpdated>#<id>`
2321
+ * (DR-004). `casing: "none"` preserves the normalized label and ISO-8601 `T`/`Z`.
2322
+ */
2323
+ gsi1: {
2324
+ index: "GSI1",
2325
+ pk: {
2326
+ field: "GSI1PK",
2327
+ composite: ["gsi1Shard"],
2328
+ template: "TID#-#WID#-#RT#User#SHARD#${gsi1Shard}"
2329
+ },
2330
+ sk: {
2331
+ field: "GSI1SK",
2332
+ casing: "none",
2333
+ composite: ["gsi1sk"],
2334
+ template: "${gsi1sk}"
2335
+ }
2336
+ },
2337
+ /**
2338
+ * GSI2 — Cognito sub-lookup per ADR-011: resolves the UserEntity from a Cognito `sub` claim.
2339
+ * `condition` skips the index when `cognitoSub` is missing so legacy items without a sub are
2340
+ * not indexed.
2341
+ */
2342
+ gsi2: {
2343
+ index: "GSI2",
2344
+ condition: (attrs) => typeof attrs.cognitoSub === "string" && attrs.cognitoSub.length > 0,
2345
+ pk: {
2346
+ field: "GSI2PK",
2347
+ casing: "none",
2348
+ composite: ["cognitoSub"],
2349
+ template: "USER#SUB#${cognitoSub}"
2350
+ },
2351
+ sk: {
2352
+ field: "GSI2SK",
2353
+ casing: "none",
2354
+ composite: [],
2355
+ template: "CURRENT"
2356
+ }
2357
+ }
2358
+ }
2359
+ });
2360
+
2361
+ // src/data/dynamo/entities/control/workspace-entity.ts
2362
+ var import_electrodb13 = require("electrodb");
2363
+ var WorkspaceEntity = new import_electrodb13.Entity({
2364
+ model: {
2365
+ entity: "workspace",
2366
+ service: "control",
2367
+ version: "01"
2368
+ },
2369
+ attributes: {
2370
+ /** Sort key sentinel. Always "CURRENT". */
2371
+ sk: {
2372
+ type: "string",
2373
+ required: true,
2374
+ default: "CURRENT"
2375
+ },
2376
+ /** Tenant that contains this workspace (required). */
2377
+ tenantId: {
2378
+ type: "string",
2379
+ required: true
2380
+ },
2381
+ /** FHIR Resource.id; logical id in URL. */
2382
+ id: {
2383
+ type: "string",
2384
+ required: true
2385
+ },
2386
+ /** Full Workspace resource serialized as JSON string. */
2387
+ resource: {
2388
+ type: "string",
2389
+ required: true
2390
+ },
2391
+ /**
2392
+ * Summary projection (key display fields as JSON string: id, displayName, status).
2393
+ * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.
2394
+ */
2395
+ summary: {
2396
+ type: "string",
2397
+ required: true
2398
+ },
2399
+ /** Version id (e.g. ULID). */
2400
+ vid: {
2401
+ type: "string",
2402
+ required: true
2403
+ },
2404
+ lastUpdated: {
2405
+ type: "string",
2406
+ required: true
2407
+ },
2408
+ gsi1Shard: gsi1ShardAttribute,
2409
+ /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
2410
+ gsi1sk: gsi1skAttribute,
2411
+ deleted: {
2412
+ type: "boolean",
2413
+ required: false
2414
+ },
2415
+ /**
2416
+ * TR-022 / ADR-018 lifecycle state for the cascade pipeline.
2417
+ *
2418
+ * - `active` (or undefined) — normal, readable state.
2419
+ * - `deleting` — intermediate state set synchronously by the
2420
+ * hard-delete API entry point. The owning-delete cascade state
2421
+ * machine fans out from this transition (DynamoDB stream →
2422
+ * `control-plane.owning-delete.v1` → Step Functions). Readers MUST
2423
+ * short-circuit on `deleting` so partial cascades stay invisible.
2424
+ * - `deleted-failed` — terminal failure state set by the cascade
2425
+ * finalize Lambda when the cascade run fails irrecoverably.
2426
+ * Operators recover by re-running the cascade or by direct
2427
+ * intervention.
2428
+ *
2429
+ * The cascade finalize step deletes the canonical record conditional
2430
+ * on `lifecycleState = "deleting"`; on replay the conditional check
2431
+ * fails and the finalize step treats that as a no-op success.
2432
+ */
2433
+ lifecycleState: {
2434
+ type: ["active", "deleting", "deleted-failed"],
2435
+ required: false
2436
+ },
2437
+ bundleId: {
2438
+ type: "string",
2439
+ required: false
2440
+ },
2441
+ msgId: {
2442
+ type: "string",
2443
+ required: false
2444
+ }
2445
+ },
2446
+ indexes: {
2447
+ /** Base table: PK = TID#<tenantId>#WORKSPACE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */
2448
+ record: {
2449
+ pk: {
2450
+ field: "PK",
2451
+ composite: ["tenantId", "id"],
2452
+ template: "TID#${tenantId}#WORKSPACE#ID#${id}"
2453
+ },
2454
+ sk: {
2455
+ field: "SK",
2456
+ composite: ["sk"],
2457
+ template: "${sk}"
2458
+ }
2459
+ },
2460
+ /**
2461
+ * GSI1 — Unified Sharded List per ADR-011: list all Workspaces for a tenant across the
2462
+ * four shards. Workspace is itself the workspace identity, so `WID#-` is a sentinel.
2463
+ * SK is derived via `gsi1skAttribute` — `<normalizedName>#<id>` when the resource
2464
+ * carries a `name`, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves
2465
+ * the normalized label and ISO-8601 `T`/`Z`.
2466
+ */
2467
+ gsi1: {
2468
+ index: "GSI1",
2469
+ pk: {
2470
+ field: "GSI1PK",
2471
+ composite: ["tenantId", "gsi1Shard"],
2472
+ template: "TID#${tenantId}#WID#-#RT#Workspace#SHARD#${gsi1Shard}"
2473
+ },
2474
+ sk: {
2475
+ field: "GSI1SK",
2476
+ casing: "none",
2477
+ composite: ["gsi1sk"],
2478
+ template: "${gsi1sk}"
2479
+ }
2480
+ }
2481
+ }
2482
+ });
2483
+
2484
+ // src/data/dynamo/dynamo-control-service.ts
2485
+ var controlPlaneEntities = {
2486
+ configuration: ConfigurationEntity,
2487
+ configurationUserProjection: ConfigurationUserProjectionEntity,
2488
+ configurationWorkspaceProjection: ConfigurationWorkspaceProjectionEntity,
2489
+ membership: MembershipEntity,
2490
+ membershipUserProjection: MembershipUserProjectionEntity,
2491
+ membershipWorkspaceProjection: MembershipWorkspaceProjectionEntity,
2492
+ role: RoleEntity,
2493
+ roleAssignment: RoleAssignmentEntity,
2494
+ roleAssignmentUserProjection: RoleAssignmentUserProjectionEntity,
2495
+ roleAssignmentWorkspaceProjection: RoleAssignmentWorkspaceProjectionEntity,
2496
+ tenant: TenantEntity,
2497
+ user: UserEntity,
2498
+ workspace: WorkspaceEntity
2499
+ };
2500
+ var controlPlaneService = new import_electrodb14.Service(controlPlaneEntities, {
2501
+ table: defaultTableName,
2502
+ client: dynamoClient
2503
+ });
2504
+ var DynamoControlService = {
2505
+ entities: controlPlaneService.entities,
2506
+ transaction: controlPlaneService.transaction
2507
+ };
2508
+ function getDynamoControlService(tableName) {
2509
+ const resolved = tableName ?? defaultTableName;
2510
+ const service = new import_electrodb14.Service(controlPlaneEntities, {
2511
+ table: resolved,
2512
+ client: dynamoClient
2513
+ });
2514
+ return {
2515
+ entities: service.entities,
2516
+ transaction: service.transaction
2517
+ };
2518
+ }
2519
+
2520
+ // src/data/operations/control/membership/membership-user-projection.ts
2521
+ var import_types2 = require("@openhi/types");
2522
+ var MISSING_NAME_SENTINEL = "-";
2523
+ function buildMembershipUserProjectionSkTenantLane(params) {
2524
+ const normalizedTenantName = typeof params.denormalizedTenantName === "string" && params.denormalizedTenantName.length > 0 ? (0, import_types2.normalizeLabel)(params.denormalizedTenantName) : MISSING_NAME_SENTINEL;
2525
+ return `MEMBERSHIP#TENANT#${normalizedTenantName}#TID#${params.tenantId}#${params.membershipId}`;
2526
+ }
2527
+ function buildMembershipUserProjectionSkWorkspaceLane(params) {
2528
+ const normalizedWorkspaceName = typeof params.denormalizedWorkspaceName === "string" && params.denormalizedWorkspaceName.length > 0 ? (0, import_types2.normalizeLabel)(params.denormalizedWorkspaceName) : MISSING_NAME_SENTINEL;
2529
+ return `MEMBERSHIP#WORKSPACE#TID#${params.tenantId}#${normalizedWorkspaceName}#WID#${params.workspaceId}#${params.membershipId}`;
2530
+ }
2531
+ function extractReferenceSlug(resource, fieldName) {
2532
+ const field = resource[fieldName];
2533
+ if (!field || typeof field !== "object") {
2534
+ return void 0;
2535
+ }
2536
+ const reference = field.reference;
2537
+ if (typeof reference !== "string" || reference.length === 0) {
2538
+ return void 0;
2539
+ }
2540
+ const slash = reference.lastIndexOf("/");
2541
+ const tail = slash >= 0 ? reference.slice(slash + 1) : reference;
2542
+ return tail.length > 0 ? tail : void 0;
2543
+ }
2544
+
2545
+ // src/data/operations/control/membership/membership-workspace-projection.ts
2546
+ var import_types3 = require("@openhi/types");
2547
+ var MISSING_NAME_SENTINEL2 = "-";
2548
+ function buildMembershipWorkspaceProjectionSk(params) {
2549
+ const normalizedUserName = typeof params.denormalizedUserName === "string" && params.denormalizedUserName.length > 0 ? (0, import_types3.normalizeLabel)(params.denormalizedUserName) : MISSING_NAME_SENTINEL2;
2550
+ return `MEMBERSHIP#${normalizedUserName}#USER#${params.userId}#${params.membershipId}`;
2551
+ }
2552
+
2553
+ // src/data/operations/control/roleassignment/roleassignment-user-projection.ts
2554
+ var import_types4 = require("@openhi/types");
2555
+ var MISSING_NAME_SENTINEL3 = "-";
2556
+ function buildRoleAssignmentUserProjectionSkTenantLane(params) {
2557
+ const normalizedRoleName = typeof params.denormalizedRoleName === "string" && params.denormalizedRoleName.length > 0 ? (0, import_types4.normalizeLabel)(params.denormalizedRoleName) : MISSING_NAME_SENTINEL3;
2558
+ return `ROLEASSIGNMENT#TENANT#${normalizedRoleName}#${params.roleId}#TID#${params.tenantId}#${params.roleAssignmentId}`;
2559
+ }
2560
+ function buildRoleAssignmentUserProjectionSkWorkspaceLane(params) {
2561
+ const normalizedRoleName = typeof params.denormalizedRoleName === "string" && params.denormalizedRoleName.length > 0 ? (0, import_types4.normalizeLabel)(params.denormalizedRoleName) : MISSING_NAME_SENTINEL3;
2562
+ return `ROLEASSIGNMENT#WORKSPACE#${normalizedRoleName}#${params.roleId}#TID#${params.tenantId}#WID#${params.workspaceId}#${params.roleAssignmentId}`;
2563
+ }
2564
+
2565
+ // src/data/operations/control/roleassignment/roleassignment-workspace-projection.ts
2566
+ var import_types5 = require("@openhi/types");
2567
+ var MISSING_NAME_SENTINEL4 = "-";
2568
+ function buildRoleAssignmentWorkspaceProjectionSk(params) {
2569
+ const normalizedUserName = typeof params.denormalizedUserName === "string" && params.denormalizedUserName.length > 0 ? (0, import_types5.normalizeLabel)(params.denormalizedUserName) : MISSING_NAME_SENTINEL4;
2570
+ return `ROLEASSIGNMENT#${params.roleId}#${normalizedUserName}#USER#${params.userId}#${params.roleAssignmentId}`;
2571
+ }
2572
+
2573
+ // src/data/operations/control/rename-cascade/rename-cascade-list-targets-operation.ts
2574
+ var DEFAULT_PAGE_SIZE = 100;
2575
+ var STREAMS_FOR_ENTITY_TYPE = {
2576
+ Tenant: ["membershipUserProjection", "roleAssignmentUserProjection"],
2577
+ User: [
2578
+ "membershipUserProjection",
2579
+ "roleAssignmentUserProjection",
2580
+ "membershipWorkspaceProjection",
2581
+ "roleAssignmentWorkspaceProjection"
2582
+ ],
2583
+ Role: ["roleAssignmentUserProjection", "roleAssignmentWorkspaceProjection"]
2584
+ };
2585
+ async function listRenameCascadeTargetsOperation(params) {
2586
+ const {
2587
+ entityType,
2588
+ entityId,
2589
+ tenantId,
2590
+ oldName,
2591
+ newName,
2592
+ oldNormalizedName,
2593
+ newNormalizedName,
2594
+ cursors = {},
2595
+ limit = DEFAULT_PAGE_SIZE,
2596
+ tableName
2597
+ } = params;
2598
+ if (!entityId || entityId.length === 0) {
2599
+ throw new Error("listRenameCascadeTargetsOperation: entityId is required");
2600
+ }
2601
+ switch (entityType) {
2602
+ case import_workflows.RENAMABLE_ENTITY_TYPE.User:
2603
+ return pageUserRename({
2604
+ userId: entityId,
2605
+ oldNormalizedName,
2606
+ newNormalizedName,
2607
+ newName,
2608
+ cursors,
2609
+ limit,
2610
+ tableName
2611
+ });
2612
+ case import_workflows.RENAMABLE_ENTITY_TYPE.Role:
2613
+ return pageRoleRename({
2614
+ roleId: entityId,
2615
+ tenantId,
2616
+ newName,
2617
+ cursors,
2618
+ limit,
2619
+ tableName
2620
+ });
2621
+ case import_workflows.RENAMABLE_ENTITY_TYPE.Tenant:
2622
+ return pageTenantRename({
2623
+ tenantId: entityId,
2624
+ oldName,
2625
+ newName,
2626
+ cursors,
2627
+ limit,
2628
+ tableName
2629
+ });
2630
+ default: {
2631
+ const exhaustive = entityType;
2632
+ throw new Error(
2633
+ `listRenameCascadeTargetsOperation: unsupported entityType '${String(
2634
+ exhaustive
2635
+ )}'`
2636
+ );
2637
+ }
2638
+ }
2639
+ }
2640
+ async function pageUserRename(params) {
2641
+ const { userId, newName, cursors, limit, tableName } = params;
2642
+ const service = getDynamoControlService(tableName);
2643
+ const nextCursors = {};
2644
+ const targets = [];
2645
+ const muStream = cursors.membershipUserProjection;
2646
+ if (muStream !== null) {
2647
+ const page = await service.entities.membershipUserProjection.query.record({ userId }).begins({ sk: "MEMBERSHIP#" }).go({ cursor: muStream ?? null, limit });
2648
+ for (const row of page.data ?? []) {
2649
+ const oldKey = { userId: row.userId, sk: row.sk };
2650
+ const newSk = row.sk;
2651
+ const newKey = { userId: row.userId, sk: newSk };
2652
+ targets.push({
2653
+ entity: "membershipUserProjection",
2654
+ oldKey,
2655
+ newKey,
2656
+ newItem: {
2657
+ ...row,
2658
+ sk: newSk,
2659
+ denormalizedUserName: newName
2660
+ },
2661
+ skRewriteRequired: false
2662
+ });
2663
+ }
2664
+ nextCursors.membershipUserProjection = page.cursor ?? null;
2665
+ } else {
2666
+ nextCursors.membershipUserProjection = null;
2667
+ }
2668
+ const raUStream = cursors.roleAssignmentUserProjection;
2669
+ if (raUStream !== null) {
2670
+ const page = await service.entities.roleAssignmentUserProjection.query.record({ userId }).begins({ sk: "ROLEASSIGNMENT#" }).go({ cursor: raUStream ?? null, limit });
2671
+ for (const row of page.data ?? []) {
2672
+ const oldKey = { userId: row.userId, sk: row.sk };
2673
+ const newKey = { userId: row.userId, sk: row.sk };
2674
+ targets.push({
2675
+ entity: "roleAssignmentUserProjection",
2676
+ oldKey,
2677
+ newKey,
2678
+ newItem: {
2679
+ ...row,
2680
+ denormalizedUserName: newName
2681
+ },
2682
+ skRewriteRequired: false
2683
+ });
2684
+ }
2685
+ nextCursors.roleAssignmentUserProjection = page.cursor ?? null;
2686
+ } else {
2687
+ nextCursors.roleAssignmentUserProjection = null;
2688
+ }
2689
+ const discoveryCursor = cursors.workspaceDiscovery;
2690
+ if (discoveryCursor !== null) {
2691
+ const discovery = await service.entities.membershipUserProjection.query.record({ userId }).begins({ sk: "MEMBERSHIP#WORKSPACE#" }).go({ cursor: discoveryCursor ?? null, limit });
2692
+ for (const member of discovery.data ?? []) {
2693
+ if (!member.workspaceId || !member.tenantId) {
2694
+ continue;
2695
+ }
2696
+ await collectWorkspaceUserRenameTargets({
2697
+ service,
2698
+ tenantId: member.tenantId,
2699
+ workspaceId: member.workspaceId,
2700
+ userId,
2701
+ oldNormalizedName: params.oldNormalizedName,
2702
+ newNormalizedName: params.newNormalizedName,
2703
+ newName,
2704
+ targets
2705
+ });
2706
+ }
2707
+ nextCursors.workspaceDiscovery = discovery.cursor ?? null;
2708
+ } else {
2709
+ nextCursors.workspaceDiscovery = null;
2710
+ }
2711
+ nextCursors.membershipWorkspaceProjection = null;
2712
+ nextCursors.roleAssignmentWorkspaceProjection = null;
2713
+ const exhausted = STREAMS_FOR_ENTITY_TYPE.User.every((s) => nextCursors[s] === null) && nextCursors.workspaceDiscovery === null;
2714
+ return { targets, cursors: nextCursors, exhausted };
2715
+ }
2716
+ async function collectWorkspaceUserRenameTargets(params) {
2717
+ const {
2718
+ service,
2719
+ tenantId,
2720
+ workspaceId,
2721
+ userId,
2722
+ oldNormalizedName,
2723
+ newName,
2724
+ targets
2725
+ } = params;
2726
+ const mwPage = await service.entities.membershipWorkspaceProjection.query.record({ tenantId, workspaceId }).begins({ sk: `MEMBERSHIP#${oldNormalizedName}#USER#${userId}#` }).go({});
2727
+ for (const row of mwPage.data ?? []) {
2728
+ const newSk = buildMembershipWorkspaceProjectionSk({
2729
+ userId: row.userId,
2730
+ membershipId: row.membershipId,
2731
+ denormalizedUserName: newName
2732
+ });
2733
+ targets.push({
2734
+ entity: "membershipWorkspaceProjection",
2735
+ oldKey: {
2736
+ tenantId: row.tenantId,
2737
+ workspaceId: row.workspaceId,
2738
+ sk: row.sk
2739
+ },
2740
+ newKey: {
2741
+ tenantId: row.tenantId,
2742
+ workspaceId: row.workspaceId,
2743
+ sk: newSk
2744
+ },
2745
+ newItem: {
2746
+ ...row,
2747
+ sk: newSk,
2748
+ denormalizedUserName: newName
2749
+ },
2750
+ skRewriteRequired: row.sk !== newSk
2751
+ });
2752
+ }
2753
+ const raPage = await service.entities.roleAssignmentWorkspaceProjection.query.record({ tenantId, workspaceId }).begins({ sk: "ROLEASSIGNMENT#" }).where((attr, op) => op.eq(attr.userId, userId)).go({});
2754
+ for (const row of raPage.data ?? []) {
2755
+ const newSk = buildRoleAssignmentWorkspaceProjectionSk({
2756
+ roleId: row.roleId,
2757
+ userId: row.userId,
2758
+ roleAssignmentId: row.roleAssignmentId,
2759
+ denormalizedUserName: newName
2760
+ });
2761
+ targets.push({
2762
+ entity: "roleAssignmentWorkspaceProjection",
2763
+ oldKey: {
2764
+ tenantId: row.tenantId,
2765
+ workspaceId: row.workspaceId,
2766
+ sk: row.sk
2767
+ },
2768
+ newKey: {
2769
+ tenantId: row.tenantId,
2770
+ workspaceId: row.workspaceId,
2771
+ sk: newSk
2772
+ },
2773
+ newItem: {
2774
+ ...row,
2775
+ sk: newSk,
2776
+ denormalizedUserName: newName
2777
+ },
2778
+ skRewriteRequired: row.sk !== newSk
2779
+ });
2780
+ }
2781
+ }
2782
+ async function pageRoleRename(params) {
2783
+ const { roleId, tenantId, newName, cursors, limit, tableName } = params;
2784
+ if (!tenantId) {
2785
+ throw new Error(
2786
+ "listRenameCascadeTargetsOperation: tenantId is required for Role rename"
2787
+ );
2788
+ }
2789
+ const service = getDynamoControlService(tableName);
2790
+ const nextCursors = {};
2791
+ const targets = [];
2792
+ const discoveryCursor = cursors.roleDiscovery;
2793
+ if (discoveryCursor !== null) {
2794
+ const page = await service.entities.roleAssignment.query.gsi1({ tenantId, gsi1Shard: "0" }).begins({ gsi1sk: `${roleId}#` }).go({ cursor: discoveryCursor ?? null, limit });
2795
+ for (const row of page.data ?? []) {
2796
+ const userId = extractUserIdFromResource(row.resource);
2797
+ if (userId === void 0) {
2798
+ continue;
2799
+ }
2800
+ await collectUserRoleRenameTargets({
2801
+ service,
2802
+ userId,
2803
+ roleId,
2804
+ newName,
2805
+ targets
2806
+ });
2807
+ }
2808
+ nextCursors.roleDiscovery = page.cursor ?? null;
2809
+ } else {
2810
+ nextCursors.roleDiscovery = null;
2811
+ }
2812
+ nextCursors.roleAssignmentUserProjection = null;
2813
+ nextCursors.roleAssignmentWorkspaceProjection = null;
2814
+ const exhausted = nextCursors.roleDiscovery === null;
2815
+ return { targets, cursors: nextCursors, exhausted };
2816
+ }
2817
+ async function collectUserRoleRenameTargets(params) {
2818
+ const { service, userId, roleId, newName, targets } = params;
2819
+ const userProjPage = await service.entities.roleAssignmentUserProjection.query.record({ userId }).begins({ sk: "ROLEASSIGNMENT#" }).where((attr, op) => op.eq(attr.roleId, roleId)).go({});
2820
+ for (const row of userProjPage.data ?? []) {
2821
+ const isWorkspaceLane = typeof row.workspaceId === "string" && row.workspaceId.length > 0;
2822
+ const newSk = isWorkspaceLane ? buildRoleAssignmentUserProjectionSkWorkspaceLane({
2823
+ tenantId: row.tenantId,
2824
+ workspaceId: row.workspaceId,
2825
+ roleId: row.roleId,
2826
+ roleAssignmentId: row.roleAssignmentId,
2827
+ denormalizedRoleName: newName
2828
+ }) : buildRoleAssignmentUserProjectionSkTenantLane({
2829
+ tenantId: row.tenantId,
2830
+ roleId: row.roleId,
2831
+ roleAssignmentId: row.roleAssignmentId,
2832
+ denormalizedRoleName: newName
2833
+ });
2834
+ targets.push({
2835
+ entity: "roleAssignmentUserProjection",
2836
+ oldKey: { userId: row.userId, sk: row.sk },
2837
+ newKey: { userId: row.userId, sk: newSk },
2838
+ newItem: {
2839
+ ...row,
2840
+ sk: newSk,
2841
+ denormalizedRoleName: newName
2842
+ },
2843
+ skRewriteRequired: row.sk !== newSk
2844
+ });
2845
+ }
2846
+ }
2847
+ async function pageTenantRename(params) {
2848
+ const { tenantId, newName, cursors, limit, tableName } = params;
2849
+ const service = getDynamoControlService(tableName);
2850
+ const nextCursors = {};
2851
+ const targets = [];
2852
+ const discoveryCursor = cursors.tenantDiscovery;
2853
+ if (discoveryCursor !== null) {
2854
+ const page = await service.entities.membership.query.gsi1({ tenantId, gsi1Shard: "0" }).go({ cursor: discoveryCursor ?? null, limit });
2855
+ for (const row of page.data ?? []) {
2856
+ const userId = extractUserIdFromResource(row.resource);
2857
+ if (userId === void 0) {
2858
+ continue;
2859
+ }
2860
+ const userPage = await service.entities.membershipUserProjection.query.record({ userId }).begins({ sk: `MEMBERSHIP#TENANT#` }).where((attr, op) => op.eq(attr.tenantId, tenantId)).go({});
2861
+ for (const userRow of userPage.data ?? []) {
2862
+ const newSk = buildMembershipUserProjectionSkTenantLane({
2863
+ tenantId: userRow.tenantId,
2864
+ membershipId: userRow.membershipId,
2865
+ denormalizedTenantName: newName
2866
+ });
2867
+ targets.push({
2868
+ entity: "membershipUserProjection",
2869
+ oldKey: { userId: userRow.userId, sk: userRow.sk },
2870
+ newKey: { userId: userRow.userId, sk: newSk },
2871
+ newItem: {
2872
+ ...userRow,
2873
+ sk: newSk,
2874
+ denormalizedTenantName: newName
2875
+ },
2876
+ skRewriteRequired: userRow.sk !== newSk
2877
+ });
2878
+ }
2879
+ const wsPage = await service.entities.membershipUserProjection.query.record({ userId }).begins({ sk: `MEMBERSHIP#WORKSPACE#TID#${tenantId}#` }).go({});
2880
+ for (const wsRow of wsPage.data ?? []) {
2881
+ const newSk = buildMembershipUserProjectionSkWorkspaceLane({
2882
+ tenantId: wsRow.tenantId,
2883
+ workspaceId: wsRow.workspaceId,
2884
+ membershipId: wsRow.membershipId,
2885
+ denormalizedWorkspaceName: wsRow.denormalizedWorkspaceName
2886
+ });
2887
+ targets.push({
2888
+ entity: "membershipUserProjection",
2889
+ oldKey: { userId: wsRow.userId, sk: wsRow.sk },
2890
+ newKey: { userId: wsRow.userId, sk: newSk },
2891
+ newItem: {
2892
+ ...wsRow,
2893
+ sk: newSk,
2894
+ denormalizedTenantName: newName
2895
+ },
2896
+ skRewriteRequired: wsRow.sk !== newSk
2897
+ });
2898
+ }
2899
+ }
2900
+ nextCursors.tenantDiscovery = page.cursor ?? null;
2901
+ } else {
2902
+ nextCursors.tenantDiscovery = null;
2903
+ }
2904
+ nextCursors.membershipUserProjection = null;
2905
+ nextCursors.roleAssignmentUserProjection = null;
2906
+ const exhausted = nextCursors.tenantDiscovery === null;
2907
+ return { targets, cursors: nextCursors, exhausted };
2908
+ }
2909
+ function extractUserIdFromResource(resource) {
2910
+ if (typeof resource !== "string" || resource.length === 0) {
2911
+ return void 0;
2912
+ }
2913
+ let parsed;
2914
+ try {
2915
+ parsed = JSON.parse(resource);
2916
+ } catch {
2917
+ return void 0;
2918
+ }
2919
+ if (!parsed || typeof parsed !== "object") {
2920
+ return void 0;
2921
+ }
2922
+ return extractReferenceSlug(parsed, "user");
2923
+ }
2924
+
2925
+ // src/data/operations/control/rename-cascade/rename-cascade-rewrite-chunk-operation.ts
2926
+ var RENAME_CASCADE_MAX_TARGETS_PER_CHUNK = 50;
2927
+ function chunkRenameCascadeTargets(targets) {
2928
+ const chunks = [];
2929
+ for (let i = 0; i < targets.length; i += RENAME_CASCADE_MAX_TARGETS_PER_CHUNK) {
2930
+ chunks.push(targets.slice(i, i + RENAME_CASCADE_MAX_TARGETS_PER_CHUNK));
2931
+ }
2932
+ return chunks;
2933
+ }
2934
+
2935
+ // src/workflows/control-plane/rename-cascade/rename-list-targets.handler.ts
2936
+ var handler = async (input) => {
2937
+ const cursors = {};
2938
+ if (input.cursors) {
2939
+ for (const [key, value] of Object.entries(input.cursors)) {
2940
+ cursors[key] = value;
2941
+ }
2942
+ }
2943
+ const page = await listRenameCascadeTargetsOperation({
2944
+ entityType: input.entityType,
2945
+ entityId: input.entityId,
2946
+ tenantId: input.tenantId,
2947
+ oldName: input.oldName,
2948
+ newName: input.newName,
2949
+ oldNormalizedName: input.oldNormalizedName,
2950
+ newNormalizedName: input.newNormalizedName,
2951
+ cursors
2952
+ });
2953
+ const chunks = chunkRenameCascadeTargets(
2954
+ page.targets
2955
+ ).map((targets) => ({
2956
+ entityType: input.entityType,
2957
+ entityId: input.entityId,
2958
+ tenantId: input.tenantId,
2959
+ targets,
2960
+ chunkToken: (0, import_node_crypto.randomUUID)()
2961
+ }));
2962
+ const priorRewritten = input.itemsRewritten ?? 0;
2963
+ const priorChunks = input.chunkCount ?? 0;
2964
+ const itemsRewritten = priorRewritten + page.targets.length;
2965
+ const chunkCount = priorChunks + chunks.length;
2966
+ return {
2967
+ entityType: input.entityType,
2968
+ entityId: input.entityId,
2969
+ tenantId: input.tenantId,
2970
+ oldName: input.oldName,
2971
+ newName: input.newName,
2972
+ oldNormalizedName: input.oldNormalizedName,
2973
+ newNormalizedName: input.newNormalizedName,
2974
+ cursors: page.cursors,
2975
+ chunks,
2976
+ exhausted: page.exhausted,
2977
+ itemsRewritten,
2978
+ chunkCount
2979
+ };
2980
+ };
2981
+ // Annotate the CommonJS export names for ESM import in node:
2982
+ 0 && (module.exports = {
2983
+ handler
2984
+ });
2985
+ //# sourceMappingURL=rename-list-targets.handler.js.map