@pax8-cta/core 0.1.0

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 (226) hide show
  1. package/LICENSE +198 -0
  2. package/dist/auth/device-code-login.d.ts +40 -0
  3. package/dist/auth/device-code-login.d.ts.map +1 -0
  4. package/dist/auth/device-code-login.js +59 -0
  5. package/dist/auth/device-code-login.js.map +1 -0
  6. package/dist/auth/gdap-client.d.ts +81 -0
  7. package/dist/auth/gdap-client.d.ts.map +1 -0
  8. package/dist/auth/gdap-client.js +128 -0
  9. package/dist/auth/gdap-client.js.map +1 -0
  10. package/dist/auth/index.d.ts +19 -0
  11. package/dist/auth/index.d.ts.map +1 -0
  12. package/dist/auth/index.js +19 -0
  13. package/dist/auth/index.js.map +1 -0
  14. package/dist/auth/token-manager.d.ts +54 -0
  15. package/dist/auth/token-manager.d.ts.map +1 -0
  16. package/dist/auth/token-manager.js +150 -0
  17. package/dist/auth/token-manager.js.map +1 -0
  18. package/dist/client.d.ts +27 -0
  19. package/dist/client.d.ts.map +1 -0
  20. package/dist/client.js +27 -0
  21. package/dist/client.js.map +1 -0
  22. package/dist/config/client.d.ts +24 -0
  23. package/dist/config/client.d.ts.map +1 -0
  24. package/dist/config/client.js +18 -0
  25. package/dist/config/client.js.map +1 -0
  26. package/dist/config/index.d.ts +18 -0
  27. package/dist/config/index.d.ts.map +1 -0
  28. package/dist/config/index.js +18 -0
  29. package/dist/config/index.js.map +1 -0
  30. package/dist/config/loader.d.ts +81 -0
  31. package/dist/config/loader.d.ts.map +1 -0
  32. package/dist/config/loader.js +271 -0
  33. package/dist/config/loader.js.map +1 -0
  34. package/dist/config/schema.d.ts +751 -0
  35. package/dist/config/schema.d.ts.map +1 -0
  36. package/dist/config/schema.js +556 -0
  37. package/dist/config/schema.js.map +1 -0
  38. package/dist/constants.d.ts +116 -0
  39. package/dist/constants.d.ts.map +1 -0
  40. package/dist/constants.js +170 -0
  41. package/dist/constants.js.map +1 -0
  42. package/dist/dataverse/agent-resolver.d.ts +98 -0
  43. package/dist/dataverse/agent-resolver.d.ts.map +1 -0
  44. package/dist/dataverse/agent-resolver.js +185 -0
  45. package/dist/dataverse/agent-resolver.js.map +1 -0
  46. package/dist/dataverse/client.d.ts +104 -0
  47. package/dist/dataverse/client.d.ts.map +1 -0
  48. package/dist/dataverse/client.js +272 -0
  49. package/dist/dataverse/client.js.map +1 -0
  50. package/dist/dataverse/connection-refs.d.ts +115 -0
  51. package/dist/dataverse/connection-refs.d.ts.map +1 -0
  52. package/dist/dataverse/connection-refs.js +203 -0
  53. package/dist/dataverse/connection-refs.js.map +1 -0
  54. package/dist/dataverse/index.d.ts +20 -0
  55. package/dist/dataverse/index.d.ts.map +1 -0
  56. package/dist/dataverse/index.js +20 -0
  57. package/dist/dataverse/index.js.map +1 -0
  58. package/dist/dataverse/solution-ops.d.ts +100 -0
  59. package/dist/dataverse/solution-ops.d.ts.map +1 -0
  60. package/dist/dataverse/solution-ops.js +288 -0
  61. package/dist/dataverse/solution-ops.js.map +1 -0
  62. package/dist/errors.d.ts +171 -0
  63. package/dist/errors.d.ts.map +1 -0
  64. package/dist/errors.js +178 -0
  65. package/dist/errors.js.map +1 -0
  66. package/dist/index.d.ts +27 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +40 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/mock/demo-data.d.ts +213 -0
  71. package/dist/mock/demo-data.d.ts.map +1 -0
  72. package/dist/mock/demo-data.js +1096 -0
  73. package/dist/mock/demo-data.js.map +1 -0
  74. package/dist/mock/demo-deployment-store.d.ts +77 -0
  75. package/dist/mock/demo-deployment-store.d.ts.map +1 -0
  76. package/dist/mock/demo-deployment-store.js +85 -0
  77. package/dist/mock/demo-deployment-store.js.map +1 -0
  78. package/dist/powerplatform/admin-client.d.ts +226 -0
  79. package/dist/powerplatform/admin-client.d.ts.map +1 -0
  80. package/dist/powerplatform/admin-client.js +315 -0
  81. package/dist/powerplatform/admin-client.js.map +1 -0
  82. package/dist/powerplatform/index.d.ts +18 -0
  83. package/dist/powerplatform/index.d.ts.map +1 -0
  84. package/dist/powerplatform/index.js +18 -0
  85. package/dist/powerplatform/index.js.map +1 -0
  86. package/dist/powerplatform/tenant-discovery.d.ts +100 -0
  87. package/dist/powerplatform/tenant-discovery.d.ts.map +1 -0
  88. package/dist/powerplatform/tenant-discovery.js +205 -0
  89. package/dist/powerplatform/tenant-discovery.js.map +1 -0
  90. package/dist/preconditions/check.d.ts +41 -0
  91. package/dist/preconditions/check.d.ts.map +1 -0
  92. package/dist/preconditions/check.js +173 -0
  93. package/dist/preconditions/check.js.map +1 -0
  94. package/dist/preconditions/index.d.ts +20 -0
  95. package/dist/preconditions/index.d.ts.map +1 -0
  96. package/dist/preconditions/index.js +20 -0
  97. package/dist/preconditions/index.js.map +1 -0
  98. package/dist/preconditions/loader.d.ts +33 -0
  99. package/dist/preconditions/loader.d.ts.map +1 -0
  100. package/dist/preconditions/loader.js +65 -0
  101. package/dist/preconditions/loader.js.map +1 -0
  102. package/dist/preconditions/schema.d.ts +103 -0
  103. package/dist/preconditions/schema.d.ts.map +1 -0
  104. package/dist/preconditions/schema.js +93 -0
  105. package/dist/preconditions/schema.js.map +1 -0
  106. package/dist/preconditions/types.d.ts +118 -0
  107. package/dist/preconditions/types.d.ts.map +1 -0
  108. package/dist/preconditions/types.js +17 -0
  109. package/dist/preconditions/types.js.map +1 -0
  110. package/dist/queue/index.d.ts +17 -0
  111. package/dist/queue/index.d.ts.map +1 -0
  112. package/dist/queue/index.js +17 -0
  113. package/dist/queue/index.js.map +1 -0
  114. package/dist/queue/memory-queue.d.ts +86 -0
  115. package/dist/queue/memory-queue.d.ts.map +1 -0
  116. package/dist/queue/memory-queue.js +221 -0
  117. package/dist/queue/memory-queue.js.map +1 -0
  118. package/dist/services/audit-log.d.ts +59 -0
  119. package/dist/services/audit-log.d.ts.map +1 -0
  120. package/dist/services/audit-log.js +193 -0
  121. package/dist/services/audit-log.js.map +1 -0
  122. package/dist/services/auth-error-parser.d.ts +36 -0
  123. package/dist/services/auth-error-parser.d.ts.map +1 -0
  124. package/dist/services/auth-error-parser.js +90 -0
  125. package/dist/services/auth-error-parser.js.map +1 -0
  126. package/dist/services/deployment-doctor.d.ts +109 -0
  127. package/dist/services/deployment-doctor.d.ts.map +1 -0
  128. package/dist/services/deployment-doctor.js +476 -0
  129. package/dist/services/deployment-doctor.js.map +1 -0
  130. package/dist/services/deployment-notifications.d.ts +41 -0
  131. package/dist/services/deployment-notifications.d.ts.map +1 -0
  132. package/dist/services/deployment-notifications.js +161 -0
  133. package/dist/services/deployment-notifications.js.map +1 -0
  134. package/dist/services/deployment-progress.d.ts +89 -0
  135. package/dist/services/deployment-progress.d.ts.map +1 -0
  136. package/dist/services/deployment-progress.js +244 -0
  137. package/dist/services/deployment-progress.js.map +1 -0
  138. package/dist/services/deployment-service.d.ts +97 -0
  139. package/dist/services/deployment-service.d.ts.map +1 -0
  140. package/dist/services/deployment-service.js +375 -0
  141. package/dist/services/deployment-service.js.map +1 -0
  142. package/dist/services/drift-analyzer.d.ts +86 -0
  143. package/dist/services/drift-analyzer.d.ts.map +1 -0
  144. package/dist/services/drift-analyzer.js +273 -0
  145. package/dist/services/drift-analyzer.js.map +1 -0
  146. package/dist/services/environment-setup.d.ts +97 -0
  147. package/dist/services/environment-setup.d.ts.map +1 -0
  148. package/dist/services/environment-setup.js +250 -0
  149. package/dist/services/environment-setup.js.map +1 -0
  150. package/dist/services/health-check.d.ts +168 -0
  151. package/dist/services/health-check.d.ts.map +1 -0
  152. package/dist/services/health-check.js +705 -0
  153. package/dist/services/health-check.js.map +1 -0
  154. package/dist/services/index.d.ts +39 -0
  155. package/dist/services/index.d.ts.map +1 -0
  156. package/dist/services/index.js +39 -0
  157. package/dist/services/index.js.map +1 -0
  158. package/dist/services/logger.d.ts +139 -0
  159. package/dist/services/logger.d.ts.map +1 -0
  160. package/dist/services/logger.js +268 -0
  161. package/dist/services/logger.js.map +1 -0
  162. package/dist/services/notification-service.d.ts +55 -0
  163. package/dist/services/notification-service.d.ts.map +1 -0
  164. package/dist/services/notification-service.js +184 -0
  165. package/dist/services/notification-service.js.map +1 -0
  166. package/dist/services/risk-analyzer.d.ts +252 -0
  167. package/dist/services/risk-analyzer.d.ts.map +1 -0
  168. package/dist/services/risk-analyzer.js +866 -0
  169. package/dist/services/risk-analyzer.js.map +1 -0
  170. package/dist/services/rollback.d.ts +57 -0
  171. package/dist/services/rollback.d.ts.map +1 -0
  172. package/dist/services/rollback.js +270 -0
  173. package/dist/services/rollback.js.map +1 -0
  174. package/dist/services/scheduler.d.ts +80 -0
  175. package/dist/services/scheduler.d.ts.map +1 -0
  176. package/dist/services/scheduler.js +350 -0
  177. package/dist/services/scheduler.js.map +1 -0
  178. package/dist/services/secrets.d.ts +31 -0
  179. package/dist/services/secrets.d.ts.map +1 -0
  180. package/dist/services/secrets.js +206 -0
  181. package/dist/services/secrets.js.map +1 -0
  182. package/dist/services/settings-service.d.ts +132 -0
  183. package/dist/services/settings-service.d.ts.map +1 -0
  184. package/dist/services/settings-service.js +378 -0
  185. package/dist/services/settings-service.js.map +1 -0
  186. package/dist/services/solution-diff.d.ts +127 -0
  187. package/dist/services/solution-diff.d.ts.map +1 -0
  188. package/dist/services/solution-diff.js +260 -0
  189. package/dist/services/solution-diff.js.map +1 -0
  190. package/dist/services/solution-mode-detector.d.ts +35 -0
  191. package/dist/services/solution-mode-detector.d.ts.map +1 -0
  192. package/dist/services/solution-mode-detector.js +84 -0
  193. package/dist/services/solution-mode-detector.js.map +1 -0
  194. package/dist/services/tenant-resolver.d.ts +55 -0
  195. package/dist/services/tenant-resolver.d.ts.map +1 -0
  196. package/dist/services/tenant-resolver.js +126 -0
  197. package/dist/services/tenant-resolver.js.map +1 -0
  198. package/dist/services/unmanaged-customizations.d.ts +104 -0
  199. package/dist/services/unmanaged-customizations.d.ts.map +1 -0
  200. package/dist/services/unmanaged-customizations.js +521 -0
  201. package/dist/services/unmanaged-customizations.js.map +1 -0
  202. package/dist/services/url-templater.d.ts +184 -0
  203. package/dist/services/url-templater.d.ts.map +1 -0
  204. package/dist/services/url-templater.js +327 -0
  205. package/dist/services/url-templater.js.map +1 -0
  206. package/dist/services/version-checker.d.ts +108 -0
  207. package/dist/services/version-checker.d.ts.map +1 -0
  208. package/dist/services/version-checker.js +403 -0
  209. package/dist/services/version-checker.js.map +1 -0
  210. package/dist/services/waves.d.ts +90 -0
  211. package/dist/services/waves.d.ts.map +1 -0
  212. package/dist/services/waves.js +222 -0
  213. package/dist/services/waves.js.map +1 -0
  214. package/dist/services/webhook.d.ts +95 -0
  215. package/dist/services/webhook.d.ts.map +1 -0
  216. package/dist/services/webhook.js +244 -0
  217. package/dist/services/webhook.js.map +1 -0
  218. package/dist/utils/deployment-tools.d.ts +110 -0
  219. package/dist/utils/deployment-tools.d.ts.map +1 -0
  220. package/dist/utils/deployment-tools.js +121 -0
  221. package/dist/utils/deployment-tools.js.map +1 -0
  222. package/dist/utils/index.d.ts +17 -0
  223. package/dist/utils/index.d.ts.map +1 -0
  224. package/dist/utils/index.js +18 -0
  225. package/dist/utils/index.js.map +1 -0
  226. package/package.json +49 -0
@@ -0,0 +1,1096 @@
1
+ /**
2
+ * Copyright 2024 Pax8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * Simple seeded random number generator for consistent mock data
18
+ * Uses a mulberry32 algorithm
19
+ */
20
+ function seededRandom(seed) {
21
+ return function () {
22
+ let t = (seed += 0x6d2b79f5);
23
+ t = Math.imul(t ^ (t >>> 15), t | 1);
24
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
25
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
26
+ };
27
+ }
28
+ /**
29
+ * Generate a numeric hash from a string (for seeding)
30
+ */
31
+ function hashString(str) {
32
+ let hash = 0;
33
+ for (let i = 0; i < str.length; i++) {
34
+ const char = str.charCodeAt(i);
35
+ hash = (hash << 5) - hash + char;
36
+ hash = hash & hash; // Convert to 32bit integer
37
+ }
38
+ return Math.abs(hash);
39
+ }
40
+ /**
41
+ * Check if demo mode is enabled
42
+ */
43
+ export function isDemoMode() {
44
+ return process.env.DEMO_MODE === "true" || process.env.DEMO_MODE === "1";
45
+ }
46
+ /**
47
+ * Get demo metadata for a tenant by ID.
48
+ * Returns typed metadata or undefined for non-demo tenants.
49
+ */
50
+ export function getDemoTenantMetadata(tenantId) {
51
+ const tenant = DEMO_TENANTS.find((t) => t.tenantId === tenantId);
52
+ // Recover the typed view: DEMO_TENANTS is exposed as TenantConfig[] for
53
+ // downstream compatibility, but the underlying records are DemoTenantConfig.
54
+ return tenant?.metadata;
55
+ }
56
+ /**
57
+ * Latest published versions for each demo solution. Used as the baseline
58
+ * for `deployedSolutions` and the `tenantsBehind` math in drift summaries.
59
+ * Kept in sync with `DEMO_SOLUTIONS` below.
60
+ */
61
+ const LATEST_SOLUTION_VERSIONS = {
62
+ CustomerServiceAgent: "1.0.0.5",
63
+ SalesAssistant: "2.1.0",
64
+ HROnboarding: "1.2.3",
65
+ ITHelpdesk: "3.0.1",
66
+ };
67
+ /**
68
+ * Helper to express "all current" deployed-solution state without repeating
69
+ * the version literals across tenants.
70
+ */
71
+ function allCurrent(deployedAt) {
72
+ return [
73
+ {
74
+ uniqueName: "CustomerServiceAgent",
75
+ deployedVersion: LATEST_SOLUTION_VERSIONS.CustomerServiceAgent,
76
+ deployedAt,
77
+ },
78
+ {
79
+ uniqueName: "SalesAssistant",
80
+ deployedVersion: LATEST_SOLUTION_VERSIONS.SalesAssistant,
81
+ deployedAt,
82
+ },
83
+ {
84
+ uniqueName: "HROnboarding",
85
+ deployedVersion: LATEST_SOLUTION_VERSIONS.HROnboarding,
86
+ deployedAt,
87
+ },
88
+ {
89
+ uniqueName: "ITHelpdesk",
90
+ deployedVersion: LATEST_SOLUTION_VERSIONS.ITHelpdesk,
91
+ deployedAt,
92
+ },
93
+ ];
94
+ }
95
+ /**
96
+ * Sample tenant data representing fictional MSP customers.
97
+ * Each tenant has a distinct risk profile and scenario so the drift, show,
98
+ * and deployments commands all have meaningful spread to display.
99
+ *
100
+ * Healthy (current): Contoso, Northern Heights HVAC, Coho Vineyard
101
+ * Mildly drifted (LOW): Fabrikam, Litware, Tailspin Toys
102
+ * Drifted (MED): Apex Legal LLP
103
+ * High risk (HIGH): Apex Legal LLP escalation, Meridian Pediatrics, Woodgrove Bank
104
+ * Critical (HIGH+do not update): Proseware (chronic failures + 4 versions behind + stale)
105
+ * Disabled: Crown Auto Group
106
+ *
107
+ * The `deployedSolutions` field is the single source of truth for both
108
+ * `solutions drift` and `solutions show --tenants` so the two commands
109
+ * cannot disagree about which tenant is current vs outdated.
110
+ */
111
+ // Typed as `TenantConfig[]` to keep downstream consumers in CLI/core
112
+ // unchanged. The richer `metadata` shape is opt-in via `DemoTenantMetadata`
113
+ // casts at read time (see `getDemoTenantMetadata` and version-checker.ts).
114
+ // The cast through `unknown` is required because TenantConfig.metadata is
115
+ // constrained to primitive values for YAML safety, but DEMO_TENANTS in
116
+ // memory carries arrays/objects/null that enable richer demo scenarios.
117
+ const DEMO_TENANTS_AUTHORED = [
118
+ // ──────────────────────────────────────────────────────────────────────
119
+ // Healthy tenants - Valid permissions, current solutions, good history
120
+ // ──────────────────────────────────────────────────────────────────────
121
+ {
122
+ name: "Contoso Corporation",
123
+ tenantId: "11111111-1111-1111-1111-111111111111",
124
+ environmentUrl: "https://contoso-prod.crm.dynamics.com",
125
+ tags: ["enterprise", "priority", "east-coast"],
126
+ enabled: true,
127
+ autoSetup: true,
128
+ metadata: {
129
+ industry: "Manufacturing",
130
+ employees: 5000,
131
+ contractTier: "Enterprise",
132
+ riskProfile: "healthy",
133
+ gdapStatus: "valid",
134
+ connectionStatus: "valid",
135
+ recentFailures: 0,
136
+ lastSuccessfulDeployment: "2026-04-29T10:30:00Z",
137
+ deployedSolutions: allCurrent("2026-04-29T10:30:00Z"),
138
+ deploymentHistory: {
139
+ totalDeploys: 24,
140
+ successfulDeploys: 23,
141
+ lastDeployDaysAgo: 5,
142
+ lastDeployResult: "success",
143
+ },
144
+ },
145
+ },
146
+ {
147
+ name: "Fabrikam Inc",
148
+ tenantId: "22222222-2222-2222-2222-222222222222",
149
+ environmentUrl: "https://fabrikam-prod.crm.dynamics.com",
150
+ tags: ["enterprise", "west-coast"],
151
+ enabled: true,
152
+ autoSetup: true,
153
+ metadata: {
154
+ industry: "Retail",
155
+ employees: 2500,
156
+ contractTier: "Enterprise",
157
+ riskProfile: "healthy",
158
+ gdapStatus: "valid",
159
+ connectionStatus: "valid",
160
+ recentFailures: 0,
161
+ lastSuccessfulDeployment: "2026-04-27T14:15:00Z",
162
+ // 1 minor version behind on HROnboarding -> LOW risk
163
+ deployedSolutions: [
164
+ {
165
+ uniqueName: "CustomerServiceAgent",
166
+ deployedVersion: "1.0.0.5",
167
+ deployedAt: "2026-04-27T14:15:00Z",
168
+ },
169
+ {
170
+ uniqueName: "SalesAssistant",
171
+ deployedVersion: "2.1.0",
172
+ deployedAt: "2026-04-25T09:30:00Z",
173
+ },
174
+ {
175
+ uniqueName: "HROnboarding",
176
+ deployedVersion: "1.2.2",
177
+ deployedAt: "2026-03-12T11:00:00Z",
178
+ },
179
+ {
180
+ uniqueName: "ITHelpdesk",
181
+ deployedVersion: "3.0.1",
182
+ deployedAt: "2026-04-10T08:45:00Z",
183
+ },
184
+ ],
185
+ deploymentHistory: {
186
+ totalDeploys: 18,
187
+ successfulDeploys: 17,
188
+ lastDeployDaysAgo: 7,
189
+ lastDeployResult: "success",
190
+ },
191
+ },
192
+ },
193
+ {
194
+ name: "Northern Heights HVAC",
195
+ tenantId: "33333333-3333-3333-3333-333333333333",
196
+ environmentUrl: "https://northernheights.crm.dynamics.com",
197
+ tags: ["smb", "services", "midwest"],
198
+ enabled: true,
199
+ autoSetup: true,
200
+ metadata: {
201
+ industry: "HVAC Services",
202
+ employees: 120,
203
+ contractTier: "Professional",
204
+ riskProfile: "healthy",
205
+ gdapStatus: "valid",
206
+ connectionStatus: "valid",
207
+ recentFailures: 0,
208
+ lastSuccessfulDeployment: "2026-04-25T09:00:00Z",
209
+ deployedSolutions: allCurrent("2026-04-25T09:00:00Z"),
210
+ deploymentHistory: {
211
+ totalDeploys: 12,
212
+ successfulDeploys: 11,
213
+ lastDeployDaysAgo: 9,
214
+ lastDeployResult: "success",
215
+ },
216
+ },
217
+ },
218
+ {
219
+ name: "Litware Inc",
220
+ tenantId: "88888888-8888-8888-8888-888888888888",
221
+ environmentUrl: "https://litware.crm.dynamics.com",
222
+ tags: ["enterprise", "technology"],
223
+ enabled: true,
224
+ autoSetup: true,
225
+ metadata: {
226
+ industry: "Technology",
227
+ employees: 1200,
228
+ contractTier: "Enterprise",
229
+ riskProfile: "healthy",
230
+ gdapStatus: "valid",
231
+ connectionStatus: "valid",
232
+ recentFailures: 0,
233
+ lastSuccessfulDeployment: "2026-04-30T16:45:00Z",
234
+ // 1 minor version behind on SalesAssistant -> LOW risk
235
+ deployedSolutions: [
236
+ {
237
+ uniqueName: "CustomerServiceAgent",
238
+ deployedVersion: "1.0.0.5",
239
+ deployedAt: "2026-04-30T16:45:00Z",
240
+ },
241
+ {
242
+ uniqueName: "SalesAssistant",
243
+ deployedVersion: "2.0.0",
244
+ deployedAt: "2026-02-18T10:00:00Z",
245
+ },
246
+ {
247
+ uniqueName: "HROnboarding",
248
+ deployedVersion: "1.2.3",
249
+ deployedAt: "2026-04-12T13:15:00Z",
250
+ },
251
+ {
252
+ uniqueName: "ITHelpdesk",
253
+ deployedVersion: "3.0.1",
254
+ deployedAt: "2026-04-22T11:30:00Z",
255
+ },
256
+ ],
257
+ deploymentHistory: {
258
+ totalDeploys: 22,
259
+ successfulDeploys: 20,
260
+ lastDeployDaysAgo: 4,
261
+ lastDeployResult: "success",
262
+ },
263
+ },
264
+ },
265
+ // ──────────────────────────────────────────────────────────────────────
266
+ // Mid-risk and high-risk tenants
267
+ // ──────────────────────────────────────────────────────────────────────
268
+ {
269
+ name: "Apex Legal LLP",
270
+ tenantId: "44444444-4444-4444-4444-444444444444",
271
+ environmentUrl: "https://apexlegal.crm.dynamics.com",
272
+ tags: ["smb", "professional"],
273
+ enabled: true,
274
+ autoSetup: true,
275
+ metadata: {
276
+ industry: "Legal Services",
277
+ employees: 280,
278
+ contractTier: "Professional",
279
+ riskProfile: "problematic",
280
+ gdapStatus: "missing_role",
281
+ gdapIssue: "Missing Power Platform Administrator role",
282
+ connectionStatus: "expired",
283
+ connectionIssue: "Dataverse connection expired, needs reauthentication",
284
+ recentFailures: 1,
285
+ lastSuccessfulDeployment: "2026-03-30T08:00:00Z",
286
+ lastDeploymentError: "Connection timeout - environment unreachable",
287
+ // 2 versions behind on CustomerServiceAgent and HROnboarding -> MED risk
288
+ deployedSolutions: [
289
+ {
290
+ uniqueName: "CustomerServiceAgent",
291
+ deployedVersion: "1.0.0.3",
292
+ deployedAt: "2026-02-10T14:00:00Z",
293
+ },
294
+ {
295
+ uniqueName: "SalesAssistant",
296
+ deployedVersion: "2.1.0",
297
+ deployedAt: "2026-03-30T08:00:00Z",
298
+ },
299
+ {
300
+ uniqueName: "HROnboarding",
301
+ deployedVersion: "1.2.1",
302
+ deployedAt: "2026-01-22T09:30:00Z",
303
+ },
304
+ {
305
+ uniqueName: "ITHelpdesk",
306
+ deployedVersion: "3.0.1",
307
+ deployedAt: "2026-03-15T11:45:00Z",
308
+ },
309
+ ],
310
+ deploymentHistory: {
311
+ totalDeploys: 14,
312
+ successfulDeploys: 10, // 71% success rate -> moderate
313
+ lastDeployDaysAgo: 35, // -> aging_environment
314
+ lastDeployResult: "success",
315
+ },
316
+ // Phase 1 preflight: CA policy is enabled (equals), MFA required
317
+ // (at-least true), and grant controls are stricter than the manifest
318
+ // demands (manifest requires ["mfa"]; Apex has ["mfa","compliantDevice"]).
319
+ preconditionState: [
320
+ {
321
+ resourceType: "microsoft.entra.conditionalaccesspolicy",
322
+ resourceMatcher: { displayName: "Require MFA for Admins" },
323
+ resourceDisplayName: "Require MFA for Admins",
324
+ currentProperties: {
325
+ id: "ca-pol-apex-001",
326
+ state: "enabled",
327
+ requireMfa: true,
328
+ grantControls: ["mfa", "compliantDevice"],
329
+ },
330
+ },
331
+ {
332
+ resourceType: "microsoft.powerplatform.connectionreference",
333
+ resourceMatcher: { displayName: "Dataverse" },
334
+ resourceDisplayName: "Dataverse",
335
+ currentProperties: {
336
+ id: "conn-apex-dataverse",
337
+ expired: false,
338
+ },
339
+ },
340
+ {
341
+ resourceType: "microsoft.exchange.transportrule",
342
+ resourceMatcher: { displayName: "Block-External-AutoForward" },
343
+ resourceDisplayName: "Block-External-AutoForward",
344
+ currentProperties: {
345
+ id: "rule-apex-autoforward",
346
+ enabled: true,
347
+ },
348
+ },
349
+ ],
350
+ },
351
+ },
352
+ {
353
+ name: "Proseware",
354
+ tenantId: "99999999-9999-9999-9999-999999999999",
355
+ environmentUrl: "https://proseware.crm.dynamics.com",
356
+ tags: ["smb", "technology"],
357
+ enabled: true,
358
+ autoSetup: true,
359
+ metadata: {
360
+ industry: "Software",
361
+ employees: 200,
362
+ contractTier: "Professional",
363
+ riskProfile: "problematic",
364
+ gdapStatus: "expired",
365
+ gdapIssue: "GDAP relationship expired on 2026-04-15",
366
+ gdapRelationshipExpiry: "2026-04-15T00:00:00Z",
367
+ connectionStatus: "missing",
368
+ connectionIssue: "SharePoint connection never configured",
369
+ recentFailures: 5,
370
+ lastSuccessfulDeployment: "2026-01-15T11:00:00Z",
371
+ lastDeploymentError: "Solution import failed: missing required connection reference",
372
+ // 4 versions behind on CustomerServiceAgent + chronic failures -> HIGH (do_not_update)
373
+ deployedSolutions: [
374
+ {
375
+ uniqueName: "CustomerServiceAgent",
376
+ deployedVersion: "1.0.0.1",
377
+ deployedAt: "2025-11-08T11:00:00Z",
378
+ },
379
+ {
380
+ uniqueName: "SalesAssistant",
381
+ deployedVersion: "2.0.0",
382
+ deployedAt: "2025-12-20T13:00:00Z",
383
+ },
384
+ {
385
+ uniqueName: "HROnboarding",
386
+ deployedVersion: "1.2.0",
387
+ deployedAt: "2025-10-15T10:00:00Z",
388
+ },
389
+ {
390
+ uniqueName: "ITHelpdesk",
391
+ deployedVersion: null,
392
+ deployedAt: undefined,
393
+ },
394
+ ],
395
+ deploymentHistory: {
396
+ totalDeploys: 10,
397
+ successfulDeploys: 3, // 30% -> low_success_rate (high severity)
398
+ lastDeployDaysAgo: 105, // -> stale_environment (high severity)
399
+ lastDeployResult: "failure",
400
+ },
401
+ },
402
+ },
403
+ {
404
+ name: "Meridian Pediatrics",
405
+ tenantId: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
406
+ environmentUrl: "https://meridianpeds.crm.dynamics.com",
407
+ tags: ["smb", "healthcare"],
408
+ enabled: true,
409
+ autoSetup: true,
410
+ metadata: {
411
+ industry: "Healthcare",
412
+ employees: 95,
413
+ contractTier: "Professional",
414
+ riskProfile: "problematic",
415
+ gdapStatus: "propagating",
416
+ gdapIssue: "GDAP relationship created 12 hours ago, still propagating",
417
+ connectionStatus: "expiring_certificate",
418
+ connectionIssue: "OAuth certificate expires in 15 days",
419
+ recentFailures: 2,
420
+ lastSuccessfulDeployment: "2026-03-25T13:30:00Z",
421
+ lastDeploymentError: "Permission denied: insufficient privileges",
422
+ // CSA never deployed + 2 versions behind elsewhere + last failure -> HIGH
423
+ deployedSolutions: [
424
+ {
425
+ uniqueName: "CustomerServiceAgent",
426
+ deployedVersion: null,
427
+ deployedAt: undefined,
428
+ },
429
+ {
430
+ uniqueName: "SalesAssistant",
431
+ deployedVersion: "2.1.0",
432
+ deployedAt: "2026-03-25T13:30:00Z",
433
+ },
434
+ {
435
+ uniqueName: "HROnboarding",
436
+ deployedVersion: "1.2.1",
437
+ deployedAt: "2026-02-28T09:00:00Z",
438
+ },
439
+ {
440
+ uniqueName: "ITHelpdesk",
441
+ deployedVersion: "3.0.0",
442
+ deployedAt: "2026-03-10T14:20:00Z",
443
+ },
444
+ ],
445
+ deploymentHistory: {
446
+ totalDeploys: 9,
447
+ successfulDeploys: 4, // 44% -> low_success_rate (high severity)
448
+ lastDeployDaysAgo: 40, // -> aging_environment
449
+ lastDeployResult: "failure",
450
+ },
451
+ },
452
+ },
453
+ // ──────────────────────────────────────────────────────────────────────
454
+ // Production-critical tenant - High stakes; outdated state shows the
455
+ // production-tag multiplier escalating risk into HIGH territory.
456
+ // ──────────────────────────────────────────────────────────────────────
457
+ {
458
+ name: "Woodgrove Bank",
459
+ tenantId: "55555555-5555-5555-5555-555555555555",
460
+ environmentUrl: "https://woodgrove.crm.dynamics.com",
461
+ tags: ["enterprise", "finance", "priority", "production"],
462
+ enabled: true,
463
+ autoSetup: true,
464
+ metadata: {
465
+ industry: "Financial Services",
466
+ employees: 8000,
467
+ contractTier: "Enterprise",
468
+ riskProfile: "production-critical",
469
+ gdapStatus: "expiring_soon",
470
+ gdapIssue: "GDAP relationship expires in 5 days",
471
+ gdapRelationshipExpiry: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000).toISOString(),
472
+ connectionStatus: "valid",
473
+ recentFailures: 1,
474
+ lastSuccessfulDeployment: "2026-03-30T02:30:00Z",
475
+ lastDeploymentError: "Import timeout after 300s - large solution exceeded maximum import duration",
476
+ // 3 versions behind on CSA + production-tag escalation -> HIGH risk
477
+ deployedSolutions: [
478
+ {
479
+ uniqueName: "CustomerServiceAgent",
480
+ deployedVersion: "1.0.0.2",
481
+ deployedAt: "2026-01-10T02:30:00Z",
482
+ },
483
+ {
484
+ uniqueName: "SalesAssistant",
485
+ deployedVersion: "2.0.0",
486
+ deployedAt: "2025-12-18T03:00:00Z",
487
+ },
488
+ {
489
+ uniqueName: "HROnboarding",
490
+ deployedVersion: "1.2.3",
491
+ deployedAt: "2026-02-22T02:15:00Z",
492
+ },
493
+ {
494
+ uniqueName: "ITHelpdesk",
495
+ deployedVersion: "3.0.1",
496
+ deployedAt: "2026-04-05T02:45:00Z",
497
+ },
498
+ ],
499
+ deploymentHistory: {
500
+ totalDeploys: 20,
501
+ successfulDeploys: 16, // 80% -> moderate
502
+ lastDeployDaysAgo: 35, // -> aging_environment
503
+ lastDeployResult: "failure",
504
+ },
505
+ // Phase 1 preflight: production-critical, but the CA policy is still
506
+ // in reportOnly — a real failure that would block this deploy. The
507
+ // other resources are fine.
508
+ preconditionState: [
509
+ {
510
+ resourceType: "microsoft.entra.conditionalaccesspolicy",
511
+ resourceMatcher: { displayName: "Require MFA for Admins" },
512
+ resourceDisplayName: "Require MFA for Admins",
513
+ currentProperties: {
514
+ id: "ca-pol-woodgrove-001",
515
+ state: "reportOnly",
516
+ requireMfa: true,
517
+ },
518
+ },
519
+ {
520
+ resourceType: "microsoft.powerplatform.connectionreference",
521
+ resourceMatcher: { displayName: "Dataverse" },
522
+ resourceDisplayName: "Dataverse",
523
+ currentProperties: {
524
+ id: "conn-woodgrove-dataverse",
525
+ expired: false,
526
+ },
527
+ },
528
+ {
529
+ resourceType: "microsoft.exchange.transportrule",
530
+ resourceMatcher: { displayName: "Block-External-AutoForward" },
531
+ resourceDisplayName: "Block-External-AutoForward",
532
+ currentProperties: {
533
+ id: "rule-woodgrove-autoforward",
534
+ enabled: true,
535
+ },
536
+ },
537
+ ],
538
+ },
539
+ },
540
+ // ──────────────────────────────────────────────────────────────────────
541
+ // Test tenants - Lower blast radius even with minor drift
542
+ // ──────────────────────────────────────────────────────────────────────
543
+ {
544
+ name: "Tailspin Toys",
545
+ tenantId: "66666666-6666-6666-6666-666666666666",
546
+ environmentUrl: "https://tailspin.crm.dynamics.com",
547
+ tags: ["smb", "retail", "test"],
548
+ enabled: true,
549
+ autoSetup: true,
550
+ metadata: {
551
+ industry: "Retail",
552
+ employees: 75,
553
+ contractTier: "Starter",
554
+ riskProfile: "test",
555
+ gdapStatus: "valid",
556
+ connectionStatus: "valid",
557
+ recentFailures: 1,
558
+ lastSuccessfulDeployment: "2026-05-01T08:00:00Z",
559
+ lastDeploymentError: "Timeout after 120s - retried successfully",
560
+ // 1 patch version behind on CSA -> LOW
561
+ deployedSolutions: [
562
+ {
563
+ uniqueName: "CustomerServiceAgent",
564
+ deployedVersion: "1.0.0.4",
565
+ deployedAt: "2026-04-12T08:00:00Z",
566
+ },
567
+ {
568
+ uniqueName: "SalesAssistant",
569
+ deployedVersion: "2.1.0",
570
+ deployedAt: "2026-04-22T08:00:00Z",
571
+ },
572
+ {
573
+ uniqueName: "HROnboarding",
574
+ deployedVersion: "1.2.3",
575
+ deployedAt: "2026-04-30T08:00:00Z",
576
+ },
577
+ {
578
+ uniqueName: "ITHelpdesk",
579
+ deployedVersion: "3.0.1",
580
+ deployedAt: "2026-05-01T08:00:00Z",
581
+ },
582
+ ],
583
+ deploymentHistory: {
584
+ totalDeploys: 15,
585
+ successfulDeploys: 14,
586
+ lastDeployDaysAgo: 3,
587
+ lastDeployResult: "success",
588
+ },
589
+ // Phase 1 preflight: CA policy is still in reportOnly (will fail
590
+ // equals=enabled), and the Dataverse connection ref is expired.
591
+ // Transport rule is fine — only the first two surface as failures.
592
+ preconditionState: [
593
+ {
594
+ resourceType: "microsoft.entra.conditionalaccesspolicy",
595
+ resourceMatcher: { displayName: "Require MFA for Admins" },
596
+ resourceDisplayName: "Require MFA for Admins",
597
+ currentProperties: {
598
+ id: "ca-pol-tailspin-001",
599
+ state: "reportOnly",
600
+ requireMfa: true,
601
+ },
602
+ },
603
+ {
604
+ resourceType: "microsoft.powerplatform.connectionreference",
605
+ resourceMatcher: { displayName: "Dataverse" },
606
+ resourceDisplayName: "Dataverse",
607
+ currentProperties: {
608
+ id: "conn-tailspin-dataverse",
609
+ expired: true,
610
+ },
611
+ },
612
+ {
613
+ resourceType: "microsoft.exchange.transportrule",
614
+ resourceMatcher: { displayName: "Block-External-AutoForward" },
615
+ resourceDisplayName: "Block-External-AutoForward",
616
+ currentProperties: {
617
+ id: "rule-tailspin-autoforward",
618
+ enabled: true,
619
+ },
620
+ },
621
+ ],
622
+ },
623
+ },
624
+ {
625
+ name: "Coho Vineyard",
626
+ tenantId: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
627
+ environmentUrl: "https://coho.crm.dynamics.com",
628
+ tags: ["smb", "hospitality", "test"],
629
+ enabled: true,
630
+ autoSetup: true,
631
+ metadata: {
632
+ industry: "Hospitality",
633
+ employees: 45,
634
+ contractTier: "Starter",
635
+ riskProfile: "test",
636
+ gdapStatus: "valid",
637
+ connectionStatus: "valid",
638
+ recentFailures: 0,
639
+ lastSuccessfulDeployment: "2026-04-28T10:00:00Z",
640
+ deployedSolutions: allCurrent("2026-04-28T10:00:00Z"),
641
+ deploymentHistory: {
642
+ totalDeploys: 8,
643
+ successfulDeploys: 8,
644
+ lastDeployDaysAgo: 6,
645
+ lastDeployResult: "success",
646
+ },
647
+ // Phase 1 preflight: CA policy passes, but the Dataverse connection
648
+ // ref is expired and the auto-forward block transport rule is off.
649
+ preconditionState: [
650
+ {
651
+ resourceType: "microsoft.entra.conditionalaccesspolicy",
652
+ resourceMatcher: { displayName: "Require MFA for Admins" },
653
+ resourceDisplayName: "Require MFA for Admins",
654
+ currentProperties: {
655
+ id: "ca-pol-coho-001",
656
+ state: "enabled",
657
+ requireMfa: true,
658
+ },
659
+ },
660
+ {
661
+ resourceType: "microsoft.powerplatform.connectionreference",
662
+ resourceMatcher: { displayName: "Dataverse" },
663
+ resourceDisplayName: "Dataverse",
664
+ currentProperties: {
665
+ id: "conn-coho-dataverse",
666
+ expired: true,
667
+ },
668
+ },
669
+ {
670
+ resourceType: "microsoft.exchange.transportrule",
671
+ resourceMatcher: { displayName: "Block-External-AutoForward" },
672
+ resourceDisplayName: "Block-External-AutoForward",
673
+ currentProperties: {
674
+ id: "rule-coho-autoforward",
675
+ enabled: false,
676
+ },
677
+ },
678
+ ],
679
+ },
680
+ },
681
+ // ──────────────────────────────────────────────────────────────────────
682
+ // Disabled tenant - Contract renewal pending
683
+ // ──────────────────────────────────────────────────────────────────────
684
+ {
685
+ name: "Crown Auto Group",
686
+ tenantId: "77777777-7777-7777-7777-777777777777",
687
+ environmentUrl: "https://crownauto.crm.dynamics.com",
688
+ tags: ["smb", "retail"],
689
+ enabled: false,
690
+ autoSetup: true,
691
+ metadata: {
692
+ industry: "Auto Retail",
693
+ employees: 60,
694
+ contractTier: "Starter",
695
+ riskProfile: "problematic",
696
+ gdapStatus: "expired",
697
+ gdapIssue: "Contract renewal pending, GDAP relationship suspended",
698
+ connectionStatus: "expired",
699
+ connectionIssue: "All connections expired due to suspended relationship",
700
+ recentFailures: 0,
701
+ disabledReason: "Contract renewal pending",
702
+ // Phase 1 preflight: empty array — every manifest precondition will
703
+ // resolve to "missing-resource" for this tenant. Crown is disabled so
704
+ // it won't normally show up in `--all`, but the data is correct if a
705
+ // user explicitly opts in.
706
+ preconditionState: [],
707
+ },
708
+ },
709
+ ];
710
+ /**
711
+ * `DEMO_TENANTS` exposed as `TenantConfig[]` for compatibility with all
712
+ * downstream callers. The metadata still carries the rich `DemoTenantMetadata`
713
+ * shape — read it via `getDemoTenantMetadata()` to recover the typed view.
714
+ */
715
+ export const DEMO_TENANTS = DEMO_TENANTS_AUTHORED;
716
+ /**
717
+ * Demo configuration
718
+ */
719
+ export const DEMO_CONFIG = {
720
+ version: "2.0",
721
+ partner: {
722
+ tenantId: "00000000-0000-0000-0000-000000000000",
723
+ clientId: "demo-client-id-0000-0000-000000000000",
724
+ },
725
+ source: {
726
+ tenantId: "00000000-0000-0000-0000-000000000000",
727
+ environmentUrl: "https://partner-dev.crm.dynamics.com",
728
+ },
729
+ tenants: DEMO_TENANTS,
730
+ settings: {
731
+ schedule: {
732
+ cron: "0 2 * * 0", // Weekly on Sunday at 2 AM
733
+ timezone: "America/New_York",
734
+ maintenanceWindow: {
735
+ start: "02:00",
736
+ end: "06:00",
737
+ daysOfWeek: [0, 6], // Saturday and Sunday
738
+ },
739
+ },
740
+ approval: {
741
+ required: true,
742
+ minApprovals: 1,
743
+ timeout: "24h",
744
+ autoApproveForTags: ["smb"],
745
+ },
746
+ rateLimit: {
747
+ maxConcurrent: 5,
748
+ delayBetweenTenants: "2s",
749
+ maxRequestsPerMinute: 30,
750
+ },
751
+ },
752
+ };
753
+ /**
754
+ * Realistic error messages for failed deployments
755
+ */
756
+ const DEMO_ERROR_MESSAGES = [
757
+ "Connection timeout - environment unreachable",
758
+ "Permission denied: insufficient privileges for solution import",
759
+ "Solution import failed: missing required connection reference 'shared_commondataserviceforapps'",
760
+ "Validation error: solution version conflict with existing unmanaged customization",
761
+ "HTTP 503 Service Unavailable - Dataverse API temporarily down",
762
+ "OAuth token expired during import, reauthentication required",
763
+ "Solution dependency not met: Microsoft Dataverse base solution version mismatch",
764
+ "Import timeout after 300s - large solution exceeded maximum import duration",
765
+ ];
766
+ /**
767
+ * Generate a mock deployment with realistic-looking data
768
+ * Uses seeded randomness based on deployment ID for consistent results
769
+ */
770
+ export function generateMockDeployment(overrides) {
771
+ const deploymentId = overrides?.id || `demo-${Date.now().toString(36)}`;
772
+ // Create a seeded random generator based on deployment ID
773
+ const random = seededRandom(hashString(deploymentId));
774
+ const statuses = ["completed", "in_progress", "pending", "failed"];
775
+ const randomStatus = overrides?.status || statuses[Math.floor(random() * statuses.length)];
776
+ const triggers = ["manual", "scheduled", "webhook", "cli", "api"];
777
+ const triggeredBy = overrides?.triggeredBy || triggers[Math.floor(random() * triggers.length)];
778
+ // Base time for this deployment (seeded)
779
+ const baseCreatedAt = overrides?.createdAt
780
+ ? new Date(overrides.createdAt).getTime()
781
+ : Date.now() - 3600000;
782
+ // Pre-compute realistic failure counts so MSP demos don't show 50% failure rates.
783
+ // Most failed deployments only have 1-2 failed tenants — that's still a meaningful
784
+ // incident without making the operator look incompetent.
785
+ // Roughly 20% of failed deployments are "bad days" with 3 failures.
786
+ const enabledCount = DEMO_TENANTS.filter((t) => t.enabled).length;
787
+ const failureCountForFailedDeploy = (() => {
788
+ const r = random();
789
+ if (r < 0.55)
790
+ return 1;
791
+ if (r < 0.8)
792
+ return 2;
793
+ return Math.min(3, enabledCount); // bad day, capped
794
+ })();
795
+ const tenantResults = DEMO_TENANTS.filter((t) => t.enabled).map((tenant, index) => {
796
+ let status;
797
+ // Use seeded random for tenant-level decisions
798
+ const tenantRandom = random();
799
+ if (randomStatus === "completed") {
800
+ status = tenantRandom > 0.1 ? "completed" : "failed"; // 90% success rate
801
+ }
802
+ else if (randomStatus === "in_progress") {
803
+ if (index < 3)
804
+ status = "completed";
805
+ else if (index === 3)
806
+ status = "in_progress";
807
+ else
808
+ status = "pending";
809
+ }
810
+ else if (randomStatus === "failed") {
811
+ // Place failures at the end so they're visible in the table without
812
+ // dominating the deployment. e.g. 1 of 10 fails, not 5 of 10.
813
+ status = index < enabledCount - failureCountForFailedDeploy ? "completed" : "failed";
814
+ }
815
+ else {
816
+ status = "pending";
817
+ }
818
+ const startedAt = status !== "pending" ? new Date(baseCreatedAt + index * 60000).toISOString() : undefined;
819
+ const completedAt = status === "completed" || status === "failed"
820
+ ? new Date(baseCreatedAt + (index + 1) * 60000).toISOString()
821
+ : undefined;
822
+ // Use varied error messages based on tenant metadata
823
+ let error;
824
+ if (status === "failed") {
825
+ const meta = getDemoTenantMetadata(tenant.tenantId);
826
+ error =
827
+ meta?.lastDeploymentError || DEMO_ERROR_MESSAGES[index % DEMO_ERROR_MESSAGES.length];
828
+ }
829
+ return {
830
+ tenantId: tenant.tenantId,
831
+ tenantName: tenant.name,
832
+ status,
833
+ startedAt,
834
+ completedAt,
835
+ error,
836
+ solutionImportJobId: status !== "pending" ? `import-${tenant.tenantId.slice(0, 8)}` : undefined,
837
+ attemptNumber: 1,
838
+ };
839
+ });
840
+ const completedCount = tenantResults.filter((r) => r.status === "completed").length;
841
+ const failedCount = tenantResults.filter((r) => r.status === "failed").length;
842
+ // Calculate duration based on status
843
+ let durationMs;
844
+ let completedAt;
845
+ const startedAt = new Date(baseCreatedAt).toISOString();
846
+ if (randomStatus === "completed" || randomStatus === "failed") {
847
+ // Duration between 2-10 minutes for completed deployments
848
+ durationMs = Math.floor(120000 + random() * 480000);
849
+ completedAt = new Date(baseCreatedAt + durationMs).toISOString();
850
+ }
851
+ else if (randomStatus === "in_progress") {
852
+ // In progress - duration so far
853
+ durationMs = Math.floor(60000 + random() * 180000);
854
+ }
855
+ return {
856
+ id: deploymentId,
857
+ solutionPath: "./solutions/CustomerServiceAgent_1_0_0_5.zip",
858
+ solutionName: "CustomerServiceAgent",
859
+ solutionVersion: "1.0.0.5",
860
+ status: randomStatus,
861
+ createdAt: new Date(baseCreatedAt).toISOString(),
862
+ updatedAt: new Date().toISOString(),
863
+ startedAt,
864
+ completedAt,
865
+ tenantResults,
866
+ totalTenants: tenantResults.length,
867
+ completedTenants: completedCount,
868
+ failedTenants: failedCount,
869
+ triggeredBy,
870
+ durationMs,
871
+ canRollback: randomStatus === "completed" && random() > 0.3,
872
+ ...overrides,
873
+ };
874
+ }
875
+ /**
876
+ * Generate a list of mock deployments for history view.
877
+ * Uses deterministic IDs based on index for consistent data across refreshes.
878
+ *
879
+ * Deployment history is designed to exercise risk analysis scenarios:
880
+ * - 70% success, 20% failure, 10% partial (completed with tenant failures)
881
+ * - Varied time distribution: recent (last 7 days), medium (last 30 days), old (90+ days)
882
+ * - Varied durations: fast (2-5 min), normal (8-15 min), slow (20-45 min)
883
+ * - Different error types: permission, timeout, connection, validation
884
+ */
885
+ export function generateMockDeploymentHistory(count = 10) {
886
+ const solutions = [
887
+ { name: "CustomerServiceAgent", version: "1.0.0.5" },
888
+ { name: "SalesAssistant", version: "2.1.0" },
889
+ { name: "HROnboarding", version: "1.2.3" },
890
+ { name: "ITHelpdesk", version: "3.0.1" },
891
+ ];
892
+ const triggers = ["manual", "scheduled", "webhook", "cli", "api"];
893
+ const deployments = [];
894
+ // Anchor history to "now" so deployments feel recent regardless of when
895
+ // the demo is run. A fixed historical date worked when the demo data was
896
+ // first authored, but it gradually rotted into "463d ago" timestamps.
897
+ const baseTimestamp = Date.now();
898
+ for (let i = 0; i < count; i++) {
899
+ const solution = solutions[i % solutions.length];
900
+ // Varied time distribution: cluster recent deployments closer together
901
+ let hoursAgo;
902
+ if (i < 5) {
903
+ hoursAgo = i * 6; // Recent: every 6 hours (last ~30 hours)
904
+ }
905
+ else if (i < 15) {
906
+ hoursAgo = 30 + (i - 5) * 24; // Medium: daily (last ~10 days)
907
+ }
908
+ else {
909
+ hoursAgo = 270 + (i - 15) * 48; // Old: every 2 days (60+ days ago)
910
+ }
911
+ const createdAt = new Date(baseTimestamp - hoursAgo * 60 * 60 * 1000);
912
+ // Deterministic deployment ID based on index
913
+ const deploymentId = `demo-hist-${i.toString().padStart(3, "0")}`;
914
+ // Deterministic status pattern: ~70% success, ~20% failure, ~10% in_progress/partial
915
+ let status;
916
+ if (i === 0) {
917
+ status = "in_progress";
918
+ }
919
+ else if (i === 3 || i === 7 || i === 12 || i === 18) {
920
+ status = "failed"; // ~20% failure rate
921
+ }
922
+ else {
923
+ status = "completed";
924
+ }
925
+ // Deterministic trigger based on index
926
+ const triggeredBy = triggers[i % triggers.length];
927
+ deployments.push(generateMockDeployment({
928
+ id: deploymentId,
929
+ solutionName: solution.name,
930
+ solutionVersion: solution.version,
931
+ solutionPath: `./solutions/${solution.name}_${solution.version.replace(/\./g, "_")}.zip`,
932
+ status,
933
+ createdAt: createdAt.toISOString(),
934
+ updatedAt: new Date(createdAt.getTime() + 30 * 60000).toISOString(),
935
+ triggeredBy,
936
+ }));
937
+ }
938
+ return deployments;
939
+ }
940
+ /**
941
+ * Mock solution metadata with extended details
942
+ */
943
+ export const DEMO_SOLUTIONS = [
944
+ {
945
+ uniqueName: "CustomerServiceAgent",
946
+ friendlyName: "Customer Service Agent",
947
+ version: "1.0.0.5",
948
+ isManaged: true,
949
+ publisherName: "Contoso ISV",
950
+ description: "Handles customer inquiries, troubleshoots issues, and escalates complex cases. Integrates with CRM for ticket creation and customer history lookup.",
951
+ category: "Customer Support",
952
+ capabilities: ["Chat", "Email", "Ticket Creation", "Knowledge Base"],
953
+ tags: ["production", "priority"],
954
+ dependencies: ["Microsoft Dataverse", "Dynamics 365 Customer Service"],
955
+ connectionReferences: [
956
+ { name: "Dataverse", connectorId: "shared_commondataserviceforapps", required: true },
957
+ { name: "Office 365 Outlook", connectorId: "shared_office365", required: false },
958
+ ],
959
+ environmentVariables: [
960
+ {
961
+ name: "SupportEmailAddress",
962
+ type: "string",
963
+ required: true,
964
+ defaultValue: "support@contoso.com",
965
+ },
966
+ { name: "EscalationThresholdMinutes", type: "number", required: false, defaultValue: "30" },
967
+ { name: "EnableAutoResponse", type: "boolean", required: false, defaultValue: "true" },
968
+ ],
969
+ lastPublished: "2025-01-15T14:30:00Z",
970
+ sizeKb: 2450,
971
+ changelog: "v1.0.0.5 - Fixed escalation routing logic\nv1.0.0.4 - Added email channel support\nv1.0.0.3 - Knowledge base integration",
972
+ },
973
+ {
974
+ uniqueName: "SalesAssistant",
975
+ friendlyName: "Sales Assistant Copilot",
976
+ version: "2.1.0",
977
+ isManaged: true,
978
+ publisherName: "Contoso ISV",
979
+ description: "Assists sales teams with lead qualification, meeting prep, and opportunity insights. Pulls data from Dynamics 365 Sales to provide account summaries.",
980
+ category: "Sales",
981
+ capabilities: ["Lead Scoring", "Account Insights", "Pipeline Analysis"],
982
+ tags: ["sales", "enterprise"],
983
+ dependencies: ["Microsoft Dataverse", "Dynamics 365 Sales"],
984
+ connectionReferences: [
985
+ { name: "Dataverse", connectorId: "shared_commondataserviceforapps", required: true },
986
+ { name: "Microsoft Teams", connectorId: "shared_teams", required: true },
987
+ ],
988
+ environmentVariables: [
989
+ { name: "SalesApiEndpoint", type: "string", required: true },
990
+ { name: "LeadScoreThreshold", type: "number", required: false, defaultValue: "75" },
991
+ ],
992
+ lastPublished: "2025-01-20T09:15:00Z",
993
+ sizeKb: 1890,
994
+ changelog: "v2.1.0 - New pipeline analytics feature\nv2.0.0 - Major UI refresh",
995
+ },
996
+ {
997
+ uniqueName: "HROnboarding",
998
+ friendlyName: "HR Onboarding Bot",
999
+ version: "1.2.3",
1000
+ isManaged: true,
1001
+ publisherName: "Contoso ISV",
1002
+ description: "Guides new hires through onboarding tasks, answers policy questions, and helps schedule orientation sessions. Connects to HR systems for document collection.",
1003
+ category: "Human Resources",
1004
+ capabilities: ["Onboarding Workflow", "Policy Q&A", "Document Collection"],
1005
+ tags: ["hr", "internal"],
1006
+ dependencies: ["Microsoft Dataverse"],
1007
+ connectionReferences: [
1008
+ { name: "Dataverse", connectorId: "shared_commondataserviceforapps", required: true },
1009
+ { name: "SharePoint", connectorId: "shared_sharepointonline", required: true },
1010
+ { name: "Office 365 Users", connectorId: "shared_office365users", required: true },
1011
+ ],
1012
+ environmentVariables: [
1013
+ { name: "HRPortalUrl", type: "string", required: true },
1014
+ {
1015
+ name: "OnboardingFolderPath",
1016
+ type: "string",
1017
+ required: true,
1018
+ defaultValue: "/sites/hr/onboarding",
1019
+ },
1020
+ ],
1021
+ lastPublished: "2025-01-10T16:45:00Z",
1022
+ sizeKb: 1250,
1023
+ changelog: "v1.2.3 - Bug fixes for document upload\nv1.2.0 - Added policy Q&A module",
1024
+ },
1025
+ {
1026
+ uniqueName: "ITHelpdesk",
1027
+ friendlyName: "IT Helpdesk Agent",
1028
+ version: "3.0.1",
1029
+ isManaged: true,
1030
+ publisherName: "Contoso ISV",
1031
+ description: "Resolves common IT issues like password resets, software installation, and VPN troubleshooting. Creates ServiceNow tickets for complex problems.",
1032
+ category: "IT Support",
1033
+ capabilities: ["Password Reset", "Software Install", "Ticket Escalation"],
1034
+ tags: ["it", "production"],
1035
+ dependencies: ["Microsoft Dataverse", "Azure Active Directory"],
1036
+ connectionReferences: [
1037
+ { name: "Dataverse", connectorId: "shared_commondataserviceforapps", required: true },
1038
+ { name: "Azure AD", connectorId: "shared_azuread", required: true },
1039
+ { name: "ServiceNow", connectorId: "shared_servicenow", required: false },
1040
+ ],
1041
+ environmentVariables: [
1042
+ { name: "ServiceNowInstance", type: "string", required: false },
1043
+ { name: "ServiceNowApiKey", type: "secret", required: false },
1044
+ { name: "AutoResetEnabled", type: "boolean", required: false, defaultValue: "false" },
1045
+ ],
1046
+ lastPublished: "2025-01-22T11:00:00Z",
1047
+ sizeKb: 3100,
1048
+ changelog: "v3.0.1 - Hotfix for VPN troubleshooter\nv3.0.0 - ServiceNow integration added",
1049
+ },
1050
+ ];
1051
+ /**
1052
+ * Simulate async operation with random delay
1053
+ */
1054
+ export async function simulateDelay(minMs = 100, maxMs = 500) {
1055
+ const delay = Math.random() * (maxMs - minMs) + minMs;
1056
+ await new Promise((resolve) => setTimeout(resolve, delay));
1057
+ }
1058
+ /**
1059
+ * Mock tenant health check result
1060
+ */
1061
+ export function generateMockHealthCheck(_tenantId) {
1062
+ const isHealthy = Math.random() > 0.15; // 85% healthy
1063
+ return {
1064
+ healthy: isHealthy,
1065
+ checks: [
1066
+ { name: "Dataverse Connection", passed: true },
1067
+ { name: "Authentication", passed: true },
1068
+ {
1069
+ name: "API Availability",
1070
+ passed: isHealthy,
1071
+ message: isHealthy ? undefined : "Timeout after 30s",
1072
+ },
1073
+ { name: "License Check", passed: true },
1074
+ ],
1075
+ };
1076
+ }
1077
+ /**
1078
+ * Mock deployment preview/diff
1079
+ */
1080
+ export function generateMockDeploymentPreview(_solutionName, tenantName) {
1081
+ const willInstall = Math.random() > 0.7;
1082
+ const willUpgrade = !willInstall;
1083
+ return {
1084
+ willInstall,
1085
+ willUpgrade,
1086
+ sourceVersion: "1.0.0.5",
1087
+ targetVersion: willInstall ? null : "1.0.0.4",
1088
+ warnings: willUpgrade && Math.random() > 0.5
1089
+ ? [
1090
+ `Same version already installed on ${tenantName}. Import will overwrite existing customizations.`,
1091
+ ]
1092
+ : [],
1093
+ estimatedDurationMs: 30000 + Math.floor(Math.random() * 60000),
1094
+ };
1095
+ }
1096
+ //# sourceMappingURL=demo-data.js.map