@openhi/constructs 0.0.159 → 0.0.161

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 (129) hide show
  1. package/lib/{chunk-HQ67J7BP.mjs → chunk-5S6VFBLT.mjs} +12 -70
  2. package/lib/chunk-5S6VFBLT.mjs.map +1 -0
  3. package/lib/{chunk-MVQWAIMC.mjs → chunk-6BB4CRSS.mjs} +3 -312
  4. package/lib/chunk-6BB4CRSS.mjs.map +1 -0
  5. package/lib/{chunk-WPCBVDFZ.mjs → chunk-76UM2LQ5.mjs} +2 -2
  6. package/lib/chunk-7TRO2STL.mjs +4616 -0
  7. package/lib/chunk-7TRO2STL.mjs.map +1 -0
  8. package/lib/chunk-BUAYVN3C.mjs +87 -0
  9. package/lib/chunk-BUAYVN3C.mjs.map +1 -0
  10. package/lib/{chunk-23PUSHBV.mjs → chunk-D2Y6DDOC.mjs} +2 -2
  11. package/lib/chunk-DWSWCUZR.mjs +123 -0
  12. package/lib/chunk-DWSWCUZR.mjs.map +1 -0
  13. package/lib/{chunk-VZCPGQXA.mjs → chunk-EUIP2U5F.mjs} +69 -1
  14. package/lib/{chunk-VZCPGQXA.mjs.map → chunk-EUIP2U5F.mjs.map} +1 -1
  15. package/lib/chunk-GJTPXJKD.mjs +46 -0
  16. package/lib/chunk-GJTPXJKD.mjs.map +1 -0
  17. package/lib/chunk-I6LUPJUY.mjs +61 -0
  18. package/lib/chunk-I6LUPJUY.mjs.map +1 -0
  19. package/lib/{chunk-KR2Y2CVQ.mjs → chunk-KA3OMP3X.mjs} +2 -2
  20. package/lib/{chunk-ZM4GDHHC.mjs → chunk-KMEWULMX.mjs} +51 -3
  21. package/lib/chunk-KMEWULMX.mjs.map +1 -0
  22. package/lib/chunk-LKKLO66E.mjs +25 -0
  23. package/lib/chunk-LKKLO66E.mjs.map +1 -0
  24. package/lib/{chunk-CFJDATDK.mjs → chunk-MLFMW5IF.mjs} +43 -9
  25. package/lib/chunk-MLFMW5IF.mjs.map +1 -0
  26. package/lib/chunk-O5VQWB6U.mjs +315 -0
  27. package/lib/chunk-O5VQWB6U.mjs.map +1 -0
  28. package/lib/{chunk-7BQHLC7U.mjs → chunk-P3CTZWC2.mjs} +8 -40
  29. package/lib/chunk-P3CTZWC2.mjs.map +1 -0
  30. package/lib/chunk-P3NFCKTZ.mjs +502 -0
  31. package/lib/chunk-P3NFCKTZ.mjs.map +1 -0
  32. package/lib/{chunk-M7Y3BOQW.mjs → chunk-Q3MKITPY.mjs} +5 -5
  33. package/lib/chunk-Q64MOYJ7.mjs +218 -0
  34. package/lib/chunk-Q64MOYJ7.mjs.map +1 -0
  35. package/lib/chunk-RQKJNMX5.mjs +89 -0
  36. package/lib/chunk-RQKJNMX5.mjs.map +1 -0
  37. package/lib/{chunk-ZWSGM6PZ.mjs → chunk-SD7J3N3C.mjs} +2 -2
  38. package/lib/{chunk-7RZHFI77.mjs → chunk-VESULYQQ.mjs} +2 -2
  39. package/lib/{chunk-AOSEKL7U.mjs → chunk-WOTU36P3.mjs} +6 -103
  40. package/lib/chunk-WOTU36P3.mjs.map +1 -0
  41. package/lib/{chunk-X5E4YJGZ.mjs → chunk-YPTJJ35S.mjs} +2 -2
  42. package/lib/counter-apply-operation-DZM3MIDm.d.mts +63 -0
  43. package/lib/counter-apply-operation-DZM3MIDm.d.ts +63 -0
  44. package/lib/counter-maintenance.handler.d.mts +38 -0
  45. package/lib/counter-maintenance.handler.d.ts +38 -0
  46. package/lib/counter-maintenance.handler.js +2885 -0
  47. package/lib/counter-maintenance.handler.js.map +1 -0
  48. package/lib/counter-maintenance.handler.mjs +180 -0
  49. package/lib/counter-maintenance.handler.mjs.map +1 -0
  50. package/lib/counter-reconciliation.handler.d.mts +116 -0
  51. package/lib/counter-reconciliation.handler.d.ts +116 -0
  52. package/lib/counter-reconciliation.handler.js +3324 -0
  53. package/lib/counter-reconciliation.handler.js.map +1 -0
  54. package/lib/counter-reconciliation.handler.mjs +295 -0
  55. package/lib/counter-reconciliation.handler.mjs.map +1 -0
  56. package/lib/data-store-postgres-replication.handler.js +50 -2
  57. package/lib/data-store-postgres-replication.handler.js.map +1 -1
  58. package/lib/data-store-postgres-replication.handler.mjs +2 -2
  59. package/lib/delete-chunk.handler.js +118 -2
  60. package/lib/delete-chunk.handler.js.map +1 -1
  61. package/lib/delete-chunk.handler.mjs +3 -3
  62. package/lib/{events-DTgo2dcW.d.mts → events-TG654e7L.d.mts} +68 -19
  63. package/lib/{events-DTgo2dcW.d.ts → events-TG654e7L.d.ts} +68 -19
  64. package/lib/finalize.handler.js +50 -2
  65. package/lib/finalize.handler.js.map +1 -1
  66. package/lib/finalize.handler.mjs +4 -4
  67. package/lib/firehose-archive-transform.handler.js +50 -2
  68. package/lib/firehose-archive-transform.handler.js.map +1 -1
  69. package/lib/firehose-archive-transform.handler.mjs +2 -2
  70. package/lib/index.d.mts +1283 -4
  71. package/lib/index.d.ts +1389 -24
  72. package/lib/index.js +4113 -320
  73. package/lib/index.js.map +1 -1
  74. package/lib/index.mjs +602 -195
  75. package/lib/index.mjs.map +1 -1
  76. package/lib/list-chunks.handler.js +118 -2
  77. package/lib/list-chunks.handler.js.map +1 -1
  78. package/lib/list-chunks.handler.mjs +3 -3
  79. package/lib/platform-deploy-bridge.handler.js +50 -2
  80. package/lib/platform-deploy-bridge.handler.js.map +1 -1
  81. package/lib/platform-deploy-bridge.handler.mjs +1 -1
  82. package/lib/pre-token-generation.handler.js +68 -0
  83. package/lib/pre-token-generation.handler.js.map +1 -1
  84. package/lib/pre-token-generation.handler.mjs +9 -5
  85. package/lib/pre-token-generation.handler.mjs.map +1 -1
  86. package/lib/provision-default-workspace.handler.js +887 -4
  87. package/lib/provision-default-workspace.handler.js.map +1 -1
  88. package/lib/provision-default-workspace.handler.mjs +14 -9
  89. package/lib/provision-default-workspace.handler.mjs.map +1 -1
  90. package/lib/rename-finalize.handler.js +50 -2
  91. package/lib/rename-finalize.handler.js.map +1 -1
  92. package/lib/rename-finalize.handler.mjs +2 -2
  93. package/lib/rename-list-targets.handler.js +118 -2
  94. package/lib/rename-list-targets.handler.js.map +1 -1
  95. package/lib/rename-list-targets.handler.mjs +11 -9
  96. package/lib/rename-list-targets.handler.mjs.map +1 -1
  97. package/lib/rename-rewrite-chunk.handler.js +68 -0
  98. package/lib/rename-rewrite-chunk.handler.js.map +1 -1
  99. package/lib/rename-rewrite-chunk.handler.mjs +2 -2
  100. package/lib/rest-api-lambda.handler.js +1454 -251
  101. package/lib/rest-api-lambda.handler.js.map +1 -1
  102. package/lib/rest-api-lambda.handler.mjs +673 -821
  103. package/lib/rest-api-lambda.handler.mjs.map +1 -1
  104. package/lib/seed-demo-data.handler.d.mts +1 -1
  105. package/lib/seed-demo-data.handler.d.ts +1 -1
  106. package/lib/seed-demo-data.handler.js +4004 -201
  107. package/lib/seed-demo-data.handler.js.map +1 -1
  108. package/lib/seed-demo-data.handler.mjs +10 -7
  109. package/lib/seed-system-data.handler.js +118 -2
  110. package/lib/seed-system-data.handler.js.map +1 -1
  111. package/lib/seed-system-data.handler.mjs +5 -5
  112. package/package.json +1 -1
  113. package/lib/chunk-7BQHLC7U.mjs.map +0 -1
  114. package/lib/chunk-AOSEKL7U.mjs.map +0 -1
  115. package/lib/chunk-BQMJSDOD.mjs +0 -1136
  116. package/lib/chunk-BQMJSDOD.mjs.map +0 -1
  117. package/lib/chunk-CFJDATDK.mjs.map +0 -1
  118. package/lib/chunk-E6MCKJVS.mjs +0 -212
  119. package/lib/chunk-E6MCKJVS.mjs.map +0 -1
  120. package/lib/chunk-HQ67J7BP.mjs.map +0 -1
  121. package/lib/chunk-MVQWAIMC.mjs.map +0 -1
  122. package/lib/chunk-ZM4GDHHC.mjs.map +0 -1
  123. /package/lib/{chunk-WPCBVDFZ.mjs.map → chunk-76UM2LQ5.mjs.map} +0 -0
  124. /package/lib/{chunk-23PUSHBV.mjs.map → chunk-D2Y6DDOC.mjs.map} +0 -0
  125. /package/lib/{chunk-KR2Y2CVQ.mjs.map → chunk-KA3OMP3X.mjs.map} +0 -0
  126. /package/lib/{chunk-M7Y3BOQW.mjs.map → chunk-Q3MKITPY.mjs.map} +0 -0
  127. /package/lib/{chunk-ZWSGM6PZ.mjs.map → chunk-SD7J3N3C.mjs.map} +0 -0
  128. /package/lib/{chunk-7RZHFI77.mjs.map → chunk-VESULYQQ.mjs.map} +0 -0
  129. /package/lib/{chunk-X5E4YJGZ.mjs.map → chunk-YPTJJ35S.mjs.map} +0 -0
@@ -0,0 +1,180 @@
1
+ import {
2
+ COUNTER_TARGET,
3
+ applyCounterDeltaOperation,
4
+ isAdminRoleAssignment
5
+ } from "./chunk-RQKJNMX5.mjs";
6
+ import {
7
+ COUNTER_MAINTENANCE_CONSUMER_NAME,
8
+ import_workflows
9
+ } from "./chunk-LKKLO66E.mjs";
10
+ import "./chunk-EUIP2U5F.mjs";
11
+ import "./chunk-TRY7JGWO.mjs";
12
+ import {
13
+ require_lib
14
+ } from "./chunk-KMEWULMX.mjs";
15
+ import {
16
+ __toESM
17
+ } from "./chunk-LZOMFHX3.mjs";
18
+
19
+ // src/workflows/control-plane/counter-maintenance/counter-maintenance.handler.ts
20
+ var import_workflows2 = __toESM(require_lib());
21
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
22
+
23
+ // src/data/operations/control/counters/counter-event-router.ts
24
+ function membershipMutations(payload, delta) {
25
+ const mutations = [];
26
+ if (payload.workspaceId !== void 0) {
27
+ mutations.push({
28
+ target: COUNTER_TARGET.Workspace,
29
+ tenantId: payload.tenantId,
30
+ workspaceId: payload.workspaceId,
31
+ attribute: "usersInWorkspace",
32
+ delta
33
+ });
34
+ if (payload.userId !== void 0) {
35
+ mutations.push({
36
+ target: COUNTER_TARGET.User,
37
+ userId: payload.userId,
38
+ attribute: "workspacesForUser",
39
+ delta
40
+ });
41
+ }
42
+ return mutations;
43
+ }
44
+ mutations.push({
45
+ target: COUNTER_TARGET.Tenant,
46
+ tenantId: payload.tenantId,
47
+ attribute: "usersInTenant",
48
+ delta
49
+ });
50
+ if (payload.userId !== void 0) {
51
+ mutations.push({
52
+ target: COUNTER_TARGET.User,
53
+ userId: payload.userId,
54
+ attribute: "tenantsForUser",
55
+ delta
56
+ });
57
+ }
58
+ return mutations;
59
+ }
60
+ function roleAssignmentMutations(payload, delta) {
61
+ if (payload.workspaceId === void 0) {
62
+ return [];
63
+ }
64
+ const attribute = isAdminRoleAssignment({
65
+ roleLevel: payload.roleLevel,
66
+ roleId: payload.roleId
67
+ }) ? "adminUsersInWorkspace" : "normalUsersInWorkspace";
68
+ return [
69
+ {
70
+ target: COUNTER_TARGET.Workspace,
71
+ tenantId: payload.tenantId,
72
+ workspaceId: payload.workspaceId,
73
+ attribute,
74
+ delta
75
+ }
76
+ ];
77
+ }
78
+ function workspaceMutations(payload, delta) {
79
+ return [
80
+ {
81
+ target: COUNTER_TARGET.Tenant,
82
+ tenantId: payload.tenantId,
83
+ attribute: "workspacesInTenant",
84
+ delta
85
+ }
86
+ ];
87
+ }
88
+
89
+ // src/workflows/control-plane/counter-maintenance/counter-maintenance.handler.ts
90
+ var errorMessage = (err) => err instanceof Error ? err.message : String(err);
91
+ function resolveEvent(event) {
92
+ const detailType = event["detail-type"];
93
+ switch (detailType) {
94
+ case import_workflows.ControlPlaneMembershipCreatedV1.detailType:
95
+ return resolveMembership(event, import_workflows.ControlPlaneMembershipCreatedV1, 1);
96
+ case import_workflows.ControlPlaneMembershipDeletedV1.detailType:
97
+ return resolveMembership(event, import_workflows.ControlPlaneMembershipDeletedV1, -1);
98
+ case import_workflows.ControlPlaneRoleAssignmentCreatedV1.detailType:
99
+ return resolveRoleAssignment(
100
+ event,
101
+ import_workflows.ControlPlaneRoleAssignmentCreatedV1,
102
+ 1
103
+ );
104
+ case import_workflows.ControlPlaneRoleAssignmentDeletedV1.detailType:
105
+ return resolveRoleAssignment(
106
+ event,
107
+ import_workflows.ControlPlaneRoleAssignmentDeletedV1,
108
+ -1
109
+ );
110
+ case import_workflows.ControlPlaneWorkspaceCreatedV1.detailType:
111
+ return resolveWorkspace(event, import_workflows.ControlPlaneWorkspaceCreatedV1, 1);
112
+ case import_workflows.ControlPlaneWorkspaceDeletedV1.detailType:
113
+ return resolveWorkspace(event, import_workflows.ControlPlaneWorkspaceDeletedV1, -1);
114
+ default:
115
+ return null;
116
+ }
117
+ }
118
+ function resolveMembership(event, entry, delta) {
119
+ const parsed = (0, import_workflows2.parseWorkflowEvent)(event, entry);
120
+ return {
121
+ mutations: membershipMutations(parsed.envelope.payload, delta),
122
+ dedupKey: parsed.dedupKey
123
+ };
124
+ }
125
+ function resolveRoleAssignment(event, entry, delta) {
126
+ const parsed = (0, import_workflows2.parseWorkflowEvent)(event, entry);
127
+ return {
128
+ mutations: roleAssignmentMutations(parsed.envelope.payload, delta),
129
+ dedupKey: parsed.dedupKey
130
+ };
131
+ }
132
+ function resolveWorkspace(event, entry, delta) {
133
+ const parsed = (0, import_workflows2.parseWorkflowEvent)(event, entry);
134
+ return {
135
+ mutations: workspaceMutations(parsed.envelope.payload, delta),
136
+ dedupKey: parsed.dedupKey
137
+ };
138
+ }
139
+ var runCounterMaintenance = async (event, deps) => {
140
+ const resolved = resolveEvent(event);
141
+ if (resolved === null) {
142
+ return;
143
+ }
144
+ const recordResult = await deps.dedupClient.recordIfAbsent({
145
+ consumerName: COUNTER_MAINTENANCE_CONSUMER_NAME,
146
+ eventId: resolved.dedupKey.eventId,
147
+ attempt: resolved.dedupKey.attempt
148
+ });
149
+ if (!recordResult.recorded) {
150
+ return;
151
+ }
152
+ try {
153
+ for (const mutation of resolved.mutations) {
154
+ await deps.applyMutation(mutation);
155
+ }
156
+ } catch (err) {
157
+ await deps.dedupClient.markFailed({
158
+ consumerName: COUNTER_MAINTENANCE_CONSUMER_NAME,
159
+ eventId: resolved.dedupKey.eventId,
160
+ attempt: resolved.dedupKey.attempt,
161
+ reason: errorMessage(err)
162
+ });
163
+ throw err;
164
+ }
165
+ };
166
+ var productionDependencies = () => {
167
+ const dynamodb = new DynamoDBClient({});
168
+ return {
169
+ dedupClient: (0, import_workflows2.workflowDedupClient)(dynamodb),
170
+ applyMutation: async (mutation) => {
171
+ await applyCounterDeltaOperation({ mutation });
172
+ }
173
+ };
174
+ };
175
+ var handler = async (event) => runCounterMaintenance(event, productionDependencies());
176
+ export {
177
+ handler,
178
+ runCounterMaintenance
179
+ };
180
+ //# sourceMappingURL=counter-maintenance.handler.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/workflows/control-plane/counter-maintenance/counter-maintenance.handler.ts","../src/data/operations/control/counters/counter-event-router.ts"],"sourcesContent":["import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport {\n parseWorkflowEvent,\n workflowDedupClient,\n type ControlPlaneMembershipChangedV1Detail,\n type ControlPlaneRoleAssignmentChangedV1Detail,\n type ControlPlaneWorkspaceChangedV1Detail,\n type WorkflowDedupClient,\n type WorkflowDetailTypeEntry,\n} from \"@openhi/workflows\";\nimport type { EventBridgeEvent } from \"aws-lambda\";\nimport {\n COUNTER_MAINTENANCE_CONSUMER_NAME,\n ControlPlaneMembershipCreatedV1,\n ControlPlaneMembershipDeletedV1,\n ControlPlaneRoleAssignmentCreatedV1,\n ControlPlaneRoleAssignmentDeletedV1,\n ControlPlaneWorkspaceCreatedV1,\n ControlPlaneWorkspaceDeletedV1,\n} from \"./events\";\nimport { applyCounterDeltaOperation } from \"../../../data/operations/control/counters/counter-apply-operation\";\nimport {\n type CounterMutation,\n membershipMutations,\n roleAssignmentMutations,\n workspaceMutations,\n} from \"../../../data/operations/control/counters/counter-event-router\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/counter-maintenance/counter-maintenance-handler.md\n *\n * ADR-028 counter-maintenance consumer. Invoked once per control-plane\n * domain event on the control event bus. Dedupes the event on\n * `(consumerName, eventId, attempt)` via the TR-015 `WorkflowDedupTable`,\n * then applies the resolved atomic-ADD counter deltas to the affected\n * canonical Tenant / Workspace / User records.\n *\n * Idempotency: an atomic ADD is not idempotent, so a replayed event must\n * be a no-op. `recordIfAbsent` is the circuit-breaker — when it returns\n * `recorded: false` a prior delivery of this exact `(eventId, attempt)`\n * already applied the deltas, so the handler exits without re-applying.\n * The dedup write happens-before the counter mutations, exactly as\n * ADR-028 § Consume requires.\n */\n\ntype CounterMaintenanceEvent = EventBridgeEvent<string, unknown>;\n\nconst errorMessage = (err: unknown): string =>\n err instanceof Error ? err.message : String(err);\n\n/**\n * Resolve the counter mutations for an event, selecting the matching\n * detail-type registry entry so `parseWorkflowEvent` validates the\n * envelope against the right source + detail-type and surfaces the\n * `(eventId, attempt)` dedup tuple.\n *\n * Returns `null` for a detail-type this consumer does not handle (the\n * EventBridge rule should never deliver one, but the guard keeps a\n * mis-targeted rule from throwing on an unknown shape).\n */\nfunction resolveEvent(event: CounterMaintenanceEvent): {\n readonly mutations: Array<CounterMutation>;\n readonly dedupKey: { readonly eventId: string; readonly attempt: number };\n} | null {\n const detailType = event[\"detail-type\"];\n\n switch (detailType) {\n case ControlPlaneMembershipCreatedV1.detailType:\n return resolveMembership(event, ControlPlaneMembershipCreatedV1, 1);\n case ControlPlaneMembershipDeletedV1.detailType:\n return resolveMembership(event, ControlPlaneMembershipDeletedV1, -1);\n case ControlPlaneRoleAssignmentCreatedV1.detailType:\n return resolveRoleAssignment(\n event,\n ControlPlaneRoleAssignmentCreatedV1,\n 1,\n );\n case ControlPlaneRoleAssignmentDeletedV1.detailType:\n return resolveRoleAssignment(\n event,\n ControlPlaneRoleAssignmentDeletedV1,\n -1,\n );\n case ControlPlaneWorkspaceCreatedV1.detailType:\n return resolveWorkspace(event, ControlPlaneWorkspaceCreatedV1, 1);\n case ControlPlaneWorkspaceDeletedV1.detailType:\n return resolveWorkspace(event, ControlPlaneWorkspaceDeletedV1, -1);\n default:\n return null;\n }\n}\n\nfunction resolveMembership(\n event: CounterMaintenanceEvent,\n entry: WorkflowDetailTypeEntry<ControlPlaneMembershipChangedV1Detail>,\n delta: 1 | -1,\n): {\n mutations: Array<CounterMutation>;\n dedupKey: { eventId: string; attempt: number };\n} {\n const parsed = parseWorkflowEvent(event, entry);\n return {\n mutations: membershipMutations(parsed.envelope.payload, delta),\n dedupKey: parsed.dedupKey,\n };\n}\n\nfunction resolveRoleAssignment(\n event: CounterMaintenanceEvent,\n entry: WorkflowDetailTypeEntry<ControlPlaneRoleAssignmentChangedV1Detail>,\n delta: 1 | -1,\n): {\n mutations: Array<CounterMutation>;\n dedupKey: { eventId: string; attempt: number };\n} {\n const parsed = parseWorkflowEvent(event, entry);\n return {\n mutations: roleAssignmentMutations(parsed.envelope.payload, delta),\n dedupKey: parsed.dedupKey,\n };\n}\n\nfunction resolveWorkspace(\n event: CounterMaintenanceEvent,\n entry: WorkflowDetailTypeEntry<ControlPlaneWorkspaceChangedV1Detail>,\n delta: 1 | -1,\n): {\n mutations: Array<CounterMutation>;\n dedupKey: { eventId: string; attempt: number };\n} {\n const parsed = parseWorkflowEvent(event, entry);\n return {\n mutations: workspaceMutations(parsed.envelope.payload, delta),\n dedupKey: parsed.dedupKey,\n };\n}\n\n/** Dependency seam for tests; production wires the real dedup client. */\nexport interface CounterMaintenanceDependencies {\n readonly dedupClient: WorkflowDedupClient;\n /**\n * Apply one resolved counter mutation. Defaults to\n * {@link applyCounterDeltaOperation}; tests inject a spy.\n */\n readonly applyMutation: (mutation: CounterMutation) => Promise<void>;\n}\n\n/**\n * Test-visible orchestrator. The production `handler` calls this with\n * real dependencies; unit tests inject fakes.\n */\nexport const runCounterMaintenance = async (\n event: CounterMaintenanceEvent,\n deps: CounterMaintenanceDependencies,\n): Promise<void> => {\n const resolved = resolveEvent(event);\n if (resolved === null) {\n // Detail-type this consumer does not handle — the EventBridge rule\n // should never deliver one. Treat as a no-op rather than throwing.\n return;\n }\n\n // Dedup is the single circuit-breaker between EventBridge's\n // at-least-once redelivery and the non-idempotent atomic ADDs below.\n const recordResult = await deps.dedupClient.recordIfAbsent({\n consumerName: COUNTER_MAINTENANCE_CONSUMER_NAME,\n eventId: resolved.dedupKey.eventId,\n attempt: resolved.dedupKey.attempt,\n });\n if (!recordResult.recorded) {\n return;\n }\n\n try {\n // Apply mutations sequentially. Each is an independent atomic ADD on\n // a distinct canonical record; ordering does not matter for\n // correctness, and sequential keeps the failure surface simple.\n for (const mutation of resolved.mutations) {\n await deps.applyMutation(mutation);\n }\n } catch (err) {\n // Mark the dedup row failed so the replay tooling (TR-016 follow-up)\n // can re-publish with a fresh attempt, then re-throw so EventBridge's\n // failure-detection path (retry + DLQ + alarm) stays intact.\n await deps.dedupClient.markFailed({\n consumerName: COUNTER_MAINTENANCE_CONSUMER_NAME,\n eventId: resolved.dedupKey.eventId,\n attempt: resolved.dedupKey.attempt,\n reason: errorMessage(err),\n });\n throw err;\n }\n};\n\nconst productionDependencies = (): CounterMaintenanceDependencies => {\n const dynamodb = new DynamoDBClient({});\n return {\n dedupClient: workflowDedupClient(dynamodb),\n applyMutation: async (mutation) => {\n await applyCounterDeltaOperation({ mutation });\n },\n };\n};\n\nexport const handler = async (event: CounterMaintenanceEvent): Promise<void> =>\n runCounterMaintenance(event, productionDependencies());\n","import {\n COUNTER_TARGET,\n type CounterDelta,\n type CounterMutation,\n type WorkspaceCounter,\n} from \"./counter-apply-operation\";\nimport { isAdminRoleAssignment } from \"./role-admin-classification\";\n\n/**\n * ADR-028 event → counter-delta routing. Pure functions that translate\n * one decoded control-plane domain event into the set of atomic counter\n * deltas the counter-maintenance consumer must apply. Kept side-effect\n * free so the routing rules are unit-testable without a DynamoDB client.\n *\n * The membership scope discriminator is `workspaceId` set/unset (the\n * same ADR-018 sub-lane distinction the publisher carries):\n * - membership, `workspaceId` ABSENT (tenant-scoped) →\n * `Tenant.usersInTenant` ±1 AND `User.tenantsForUser` ±1\n * - membership, `workspaceId` PRESENT (workspace-scoped) →\n * `Workspace.usersInWorkspace` ±1 AND `User.workspacesForUser` ±1\n * - role-assignment, `workspaceId` PRESENT → admin/normal classified →\n * `Workspace.adminUsersInWorkspace` ±1 OR `Workspace.normalUsersInWorkspace` ±1\n * - role-assignment, `workspaceId` ABSENT (tenant-level) → no counter\n * - workspace created/deleted → `Tenant.workspacesInTenant` ±1\n *\n * @see ADR-028 — Denormalized Control-Plane Membership Counters\n */\n\nexport type { CounterMutation };\n\n/** Membership / role-assignment / workspace event payload shapes the router reads. */\nexport interface MembershipChangedPayload {\n readonly tenantId: string;\n readonly userId?: string;\n readonly workspaceId?: string;\n}\n\nexport interface RoleAssignmentChangedPayload {\n readonly tenantId: string;\n readonly userId?: string;\n readonly workspaceId?: string;\n readonly roleId?: string;\n readonly roleLevel?: string;\n}\n\nexport interface WorkspaceChangedPayload {\n readonly tenantId: string;\n readonly workspaceId: string;\n}\n\n/**\n * Resolve the counter mutations for a membership create / delete.\n * `delta` is `+1` for created, `-1` for deleted.\n */\nexport function membershipMutations(\n payload: MembershipChangedPayload,\n delta: CounterDelta,\n): Array<CounterMutation> {\n const mutations: Array<CounterMutation> = [];\n\n if (payload.workspaceId !== undefined) {\n // Workspace-scoped membership: users-in-workspace + workspaces-for-user.\n mutations.push({\n target: COUNTER_TARGET.Workspace,\n tenantId: payload.tenantId,\n workspaceId: payload.workspaceId,\n attribute: \"usersInWorkspace\",\n delta,\n });\n if (payload.userId !== undefined) {\n mutations.push({\n target: COUNTER_TARGET.User,\n userId: payload.userId,\n attribute: \"workspacesForUser\",\n delta,\n });\n }\n return mutations;\n }\n\n // Tenant-scoped membership: users-in-tenant + tenants-for-user.\n mutations.push({\n target: COUNTER_TARGET.Tenant,\n tenantId: payload.tenantId,\n attribute: \"usersInTenant\",\n delta,\n });\n if (payload.userId !== undefined) {\n mutations.push({\n target: COUNTER_TARGET.User,\n userId: payload.userId,\n attribute: \"tenantsForUser\",\n delta,\n });\n }\n return mutations;\n}\n\n/**\n * Resolve the counter mutations for a role-assignment create / delete.\n * Only workspace-scoped assignments drive a counter; tenant-level\n * assignments return no mutations. The admin/normal bucket is chosen by\n * the centralized {@link isAdminRoleAssignment} predicate.\n */\nexport function roleAssignmentMutations(\n payload: RoleAssignmentChangedPayload,\n delta: CounterDelta,\n): Array<CounterMutation> {\n if (payload.workspaceId === undefined) {\n return [];\n }\n\n const attribute: WorkspaceCounter = isAdminRoleAssignment({\n roleLevel: payload.roleLevel,\n roleId: payload.roleId,\n })\n ? \"adminUsersInWorkspace\"\n : \"normalUsersInWorkspace\";\n\n return [\n {\n target: COUNTER_TARGET.Workspace,\n tenantId: payload.tenantId,\n workspaceId: payload.workspaceId,\n attribute,\n delta,\n },\n ];\n}\n\n/**\n * Resolve the counter mutation for a workspace create / delete:\n * `Tenant.workspacesInTenant` ±1.\n */\nexport function workspaceMutations(\n payload: WorkspaceChangedPayload,\n delta: CounterDelta,\n): Array<CounterMutation> {\n return [\n {\n target: COUNTER_TARGET.Tenant,\n tenantId: payload.tenantId,\n attribute: \"workspacesInTenant\",\n delta,\n },\n ];\n}\n"],"mappings":"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","names":["import_workflows"]}
@@ -0,0 +1,116 @@
1
+ import { a as CounterTarget } from './counter-apply-operation-DZM3MIDm.mjs';
2
+
3
+ /**
4
+ * ADR-028 counter reconciliation — recompute the denormalized
5
+ * control-plane counters from canonical data and repair drift.
6
+ *
7
+ * The atomic-ADD path ({@link applyCounterDeltaOperation}) maintains the
8
+ * counters incrementally off domain events, but events can be missed,
9
+ * replayed, or arrive after a record was created without one (rows that
10
+ * predate the counter work). This operation is the correctness backstop
11
+ * ADR-028 names: it ignores the current counter value, recomputes the
12
+ * true value from canonical records, and writes the absolute recomputed
13
+ * value back with a DynamoDB `SET` (not `ADD`). A `SET` repairs both
14
+ * directions of drift and backfills an absent / `0` attribute to its
15
+ * correct value in one write.
16
+ *
17
+ * Counter semantics recomputed here MUST match {@link counterEventRouter}:
18
+ *
19
+ * - `Tenant.usersInTenant` = # tenant-scoped Memberships in the tenant
20
+ * (membership with NO workspace reference).
21
+ * - `Tenant.workspacesInTenant` = # Workspaces in the tenant.
22
+ * - `Workspace.usersInWorkspace` = # workspace-scoped Memberships for the workspace.
23
+ * - `Workspace.adminUsersInWorkspace` / `normalUsersInWorkspace` =
24
+ * # workspace-scoped RoleAssignments for the workspace, bucketed by
25
+ * {@link isAdminRoleAssignment} on the assignment's role level / role id.
26
+ * - `User.tenantsForUser` = # tenant-scoped Memberships for the user.
27
+ * - `User.workspacesForUser` = # workspace-scoped Memberships for the user.
28
+ *
29
+ * @see counter-apply-operation.ts — the incremental ADD path this reconciles against.
30
+ * @see counter-event-router.ts — the event → counter semantics this mirrors.
31
+ */
32
+ /** One counter's old → new transition, recorded only when `old !== new`. */
33
+ interface CounterDriftEntry {
34
+ /** Which canonical entity the counter lives on. */
35
+ readonly target: CounterTarget;
36
+ /** Identity of the canonical record (tenantId for Tenant, workspaceId for Workspace, userId for User). */
37
+ readonly id: string;
38
+ /** Tenant the record belongs to (Workspace only; omitted for Tenant / User). */
39
+ readonly tenantId?: string;
40
+ /** The counter attribute name. */
41
+ readonly counter: string;
42
+ /** The value found on the record before reconciliation (0 when the attribute was absent). */
43
+ readonly old: number;
44
+ /** The recomputed-from-canonical value written back. */
45
+ readonly new: number;
46
+ }
47
+
48
+ /**
49
+ * ADR-028 counter-reconciliation driver — walks every canonical Tenant,
50
+ * Workspace, and User, reconciles each record's denormalized counters
51
+ * against canonical data, and accumulates a single drift report.
52
+ *
53
+ * Enumeration reuses the existing GSI1-sharded list operations
54
+ * (`summary` mode — ids only, no per-record BatchGet hydration):
55
+ *
56
+ * - All Tenants via {@link listTenantsOperation}.
57
+ * - Per tenant, all Workspaces in that tenant via
58
+ * {@link listWorkspacesOperation} (the workspace GSI1 partition is
59
+ * tenant-scoped).
60
+ * - All Users via {@link listUsersOperation}.
61
+ *
62
+ * Each record is then handed to the matching per-target recompute
63
+ * ({@link reconcileTenantCountersOperation},
64
+ * {@link reconcileWorkspaceCountersOperation},
65
+ * {@link reconcileUserCountersOperation}), which owns the SET-back repair
66
+ * and returns the per-counter old → new drift it corrected.
67
+ *
68
+ * @see counter-reconcile-operation.ts — the per-target recompute + repair.
69
+ */
70
+ /** Totals summarizing one reconciliation run, alongside the per-counter drift list. */
71
+ interface CounterReconcileReport {
72
+ /** Every counter that changed across every record, in walk order. */
73
+ readonly drift: Array<CounterDriftEntry>;
74
+ /** How many canonical records of each kind were scanned. */
75
+ readonly scanned: {
76
+ readonly tenants: number;
77
+ readonly workspaces: number;
78
+ readonly users: number;
79
+ };
80
+ /** Total number of individual counters corrected (== `drift.length`). */
81
+ readonly countersCorrected: number;
82
+ }
83
+
84
+ /**
85
+ * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/counter-reconciliation/counter-reconciliation-handler.md
86
+ *
87
+ * ADR-028 counter-reconciliation job handler. Invoked on demand (manual
88
+ * `aws lambda invoke`, an operator runbook, or a scheduled trigger) — not
89
+ * an EventBridge consumer. Walks every canonical Tenant / Workspace /
90
+ * User, recomputes the denormalized counters from canonical data, repairs
91
+ * any drift with a `SET` write, and logs the per-counter old → new drift
92
+ * report.
93
+ *
94
+ * Idempotent by construction: each per-target recompute writes the
95
+ * absolute recomputed value, so a second run over unchanged data corrects
96
+ * nothing and reports zero drift. That is why the job needs no dedup
97
+ * circuit-breaker (unlike the event-driven counter-maintenance consumer,
98
+ * whose atomic ADDs are not idempotent).
99
+ */
100
+ /** Dependency seam for tests; production wires the real driver. */
101
+ interface CounterReconciliationDependencies {
102
+ /**
103
+ * Run the full reconciliation sweep. Defaults to
104
+ * {@link reconcileAllCountersOperation}; tests inject a fake.
105
+ */
106
+ readonly reconcileAll: () => Promise<CounterReconcileReport>;
107
+ }
108
+ /**
109
+ * Test-visible orchestrator. The production `handler` calls this with the
110
+ * real driver; unit tests inject a fake. Returns the drift report so an
111
+ * invoker (or test) can assert on what was corrected.
112
+ */
113
+ declare const runCounterReconciliation: (deps: CounterReconciliationDependencies) => Promise<CounterReconcileReport>;
114
+ declare const handler: () => Promise<CounterReconcileReport>;
115
+
116
+ export { type CounterReconciliationDependencies, handler, runCounterReconciliation };
@@ -0,0 +1,116 @@
1
+ import { a as CounterTarget } from './counter-apply-operation-DZM3MIDm.js';
2
+
3
+ /**
4
+ * ADR-028 counter reconciliation — recompute the denormalized
5
+ * control-plane counters from canonical data and repair drift.
6
+ *
7
+ * The atomic-ADD path ({@link applyCounterDeltaOperation}) maintains the
8
+ * counters incrementally off domain events, but events can be missed,
9
+ * replayed, or arrive after a record was created without one (rows that
10
+ * predate the counter work). This operation is the correctness backstop
11
+ * ADR-028 names: it ignores the current counter value, recomputes the
12
+ * true value from canonical records, and writes the absolute recomputed
13
+ * value back with a DynamoDB `SET` (not `ADD`). A `SET` repairs both
14
+ * directions of drift and backfills an absent / `0` attribute to its
15
+ * correct value in one write.
16
+ *
17
+ * Counter semantics recomputed here MUST match {@link counterEventRouter}:
18
+ *
19
+ * - `Tenant.usersInTenant` = # tenant-scoped Memberships in the tenant
20
+ * (membership with NO workspace reference).
21
+ * - `Tenant.workspacesInTenant` = # Workspaces in the tenant.
22
+ * - `Workspace.usersInWorkspace` = # workspace-scoped Memberships for the workspace.
23
+ * - `Workspace.adminUsersInWorkspace` / `normalUsersInWorkspace` =
24
+ * # workspace-scoped RoleAssignments for the workspace, bucketed by
25
+ * {@link isAdminRoleAssignment} on the assignment's role level / role id.
26
+ * - `User.tenantsForUser` = # tenant-scoped Memberships for the user.
27
+ * - `User.workspacesForUser` = # workspace-scoped Memberships for the user.
28
+ *
29
+ * @see counter-apply-operation.ts — the incremental ADD path this reconciles against.
30
+ * @see counter-event-router.ts — the event → counter semantics this mirrors.
31
+ */
32
+ /** One counter's old → new transition, recorded only when `old !== new`. */
33
+ interface CounterDriftEntry {
34
+ /** Which canonical entity the counter lives on. */
35
+ readonly target: CounterTarget;
36
+ /** Identity of the canonical record (tenantId for Tenant, workspaceId for Workspace, userId for User). */
37
+ readonly id: string;
38
+ /** Tenant the record belongs to (Workspace only; omitted for Tenant / User). */
39
+ readonly tenantId?: string;
40
+ /** The counter attribute name. */
41
+ readonly counter: string;
42
+ /** The value found on the record before reconciliation (0 when the attribute was absent). */
43
+ readonly old: number;
44
+ /** The recomputed-from-canonical value written back. */
45
+ readonly new: number;
46
+ }
47
+
48
+ /**
49
+ * ADR-028 counter-reconciliation driver — walks every canonical Tenant,
50
+ * Workspace, and User, reconciles each record's denormalized counters
51
+ * against canonical data, and accumulates a single drift report.
52
+ *
53
+ * Enumeration reuses the existing GSI1-sharded list operations
54
+ * (`summary` mode — ids only, no per-record BatchGet hydration):
55
+ *
56
+ * - All Tenants via {@link listTenantsOperation}.
57
+ * - Per tenant, all Workspaces in that tenant via
58
+ * {@link listWorkspacesOperation} (the workspace GSI1 partition is
59
+ * tenant-scoped).
60
+ * - All Users via {@link listUsersOperation}.
61
+ *
62
+ * Each record is then handed to the matching per-target recompute
63
+ * ({@link reconcileTenantCountersOperation},
64
+ * {@link reconcileWorkspaceCountersOperation},
65
+ * {@link reconcileUserCountersOperation}), which owns the SET-back repair
66
+ * and returns the per-counter old → new drift it corrected.
67
+ *
68
+ * @see counter-reconcile-operation.ts — the per-target recompute + repair.
69
+ */
70
+ /** Totals summarizing one reconciliation run, alongside the per-counter drift list. */
71
+ interface CounterReconcileReport {
72
+ /** Every counter that changed across every record, in walk order. */
73
+ readonly drift: Array<CounterDriftEntry>;
74
+ /** How many canonical records of each kind were scanned. */
75
+ readonly scanned: {
76
+ readonly tenants: number;
77
+ readonly workspaces: number;
78
+ readonly users: number;
79
+ };
80
+ /** Total number of individual counters corrected (== `drift.length`). */
81
+ readonly countersCorrected: number;
82
+ }
83
+
84
+ /**
85
+ * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/counter-reconciliation/counter-reconciliation-handler.md
86
+ *
87
+ * ADR-028 counter-reconciliation job handler. Invoked on demand (manual
88
+ * `aws lambda invoke`, an operator runbook, or a scheduled trigger) — not
89
+ * an EventBridge consumer. Walks every canonical Tenant / Workspace /
90
+ * User, recomputes the denormalized counters from canonical data, repairs
91
+ * any drift with a `SET` write, and logs the per-counter old → new drift
92
+ * report.
93
+ *
94
+ * Idempotent by construction: each per-target recompute writes the
95
+ * absolute recomputed value, so a second run over unchanged data corrects
96
+ * nothing and reports zero drift. That is why the job needs no dedup
97
+ * circuit-breaker (unlike the event-driven counter-maintenance consumer,
98
+ * whose atomic ADDs are not idempotent).
99
+ */
100
+ /** Dependency seam for tests; production wires the real driver. */
101
+ interface CounterReconciliationDependencies {
102
+ /**
103
+ * Run the full reconciliation sweep. Defaults to
104
+ * {@link reconcileAllCountersOperation}; tests inject a fake.
105
+ */
106
+ readonly reconcileAll: () => Promise<CounterReconcileReport>;
107
+ }
108
+ /**
109
+ * Test-visible orchestrator. The production `handler` calls this with the
110
+ * real driver; unit tests inject a fake. Returns the drift report so an
111
+ * invoker (or test) can assert on what was corrected.
112
+ */
113
+ declare const runCounterReconciliation: (deps: CounterReconciliationDependencies) => Promise<CounterReconcileReport>;
114
+ declare const handler: () => Promise<CounterReconcileReport>;
115
+
116
+ export { type CounterReconciliationDependencies, handler, runCounterReconciliation };