@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.
- package/LICENSE +198 -0
- package/dist/auth/device-code-login.d.ts +40 -0
- package/dist/auth/device-code-login.d.ts.map +1 -0
- package/dist/auth/device-code-login.js +59 -0
- package/dist/auth/device-code-login.js.map +1 -0
- package/dist/auth/gdap-client.d.ts +81 -0
- package/dist/auth/gdap-client.d.ts.map +1 -0
- package/dist/auth/gdap-client.js +128 -0
- package/dist/auth/gdap-client.js.map +1 -0
- package/dist/auth/index.d.ts +19 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +19 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/token-manager.d.ts +54 -0
- package/dist/auth/token-manager.d.ts.map +1 -0
- package/dist/auth/token-manager.js +150 -0
- package/dist/auth/token-manager.js.map +1 -0
- package/dist/client.d.ts +27 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +27 -0
- package/dist/client.js.map +1 -0
- package/dist/config/client.d.ts +24 -0
- package/dist/config/client.d.ts.map +1 -0
- package/dist/config/client.js +18 -0
- package/dist/config/client.js.map +1 -0
- package/dist/config/index.d.ts +18 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +18 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +81 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +271 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +751 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +556 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/constants.d.ts +116 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +170 -0
- package/dist/constants.js.map +1 -0
- package/dist/dataverse/agent-resolver.d.ts +98 -0
- package/dist/dataverse/agent-resolver.d.ts.map +1 -0
- package/dist/dataverse/agent-resolver.js +185 -0
- package/dist/dataverse/agent-resolver.js.map +1 -0
- package/dist/dataverse/client.d.ts +104 -0
- package/dist/dataverse/client.d.ts.map +1 -0
- package/dist/dataverse/client.js +272 -0
- package/dist/dataverse/client.js.map +1 -0
- package/dist/dataverse/connection-refs.d.ts +115 -0
- package/dist/dataverse/connection-refs.d.ts.map +1 -0
- package/dist/dataverse/connection-refs.js +203 -0
- package/dist/dataverse/connection-refs.js.map +1 -0
- package/dist/dataverse/index.d.ts +20 -0
- package/dist/dataverse/index.d.ts.map +1 -0
- package/dist/dataverse/index.js +20 -0
- package/dist/dataverse/index.js.map +1 -0
- package/dist/dataverse/solution-ops.d.ts +100 -0
- package/dist/dataverse/solution-ops.d.ts.map +1 -0
- package/dist/dataverse/solution-ops.js +288 -0
- package/dist/dataverse/solution-ops.js.map +1 -0
- package/dist/errors.d.ts +171 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +178 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/mock/demo-data.d.ts +213 -0
- package/dist/mock/demo-data.d.ts.map +1 -0
- package/dist/mock/demo-data.js +1096 -0
- package/dist/mock/demo-data.js.map +1 -0
- package/dist/mock/demo-deployment-store.d.ts +77 -0
- package/dist/mock/demo-deployment-store.d.ts.map +1 -0
- package/dist/mock/demo-deployment-store.js +85 -0
- package/dist/mock/demo-deployment-store.js.map +1 -0
- package/dist/powerplatform/admin-client.d.ts +226 -0
- package/dist/powerplatform/admin-client.d.ts.map +1 -0
- package/dist/powerplatform/admin-client.js +315 -0
- package/dist/powerplatform/admin-client.js.map +1 -0
- package/dist/powerplatform/index.d.ts +18 -0
- package/dist/powerplatform/index.d.ts.map +1 -0
- package/dist/powerplatform/index.js +18 -0
- package/dist/powerplatform/index.js.map +1 -0
- package/dist/powerplatform/tenant-discovery.d.ts +100 -0
- package/dist/powerplatform/tenant-discovery.d.ts.map +1 -0
- package/dist/powerplatform/tenant-discovery.js +205 -0
- package/dist/powerplatform/tenant-discovery.js.map +1 -0
- package/dist/preconditions/check.d.ts +41 -0
- package/dist/preconditions/check.d.ts.map +1 -0
- package/dist/preconditions/check.js +173 -0
- package/dist/preconditions/check.js.map +1 -0
- package/dist/preconditions/index.d.ts +20 -0
- package/dist/preconditions/index.d.ts.map +1 -0
- package/dist/preconditions/index.js +20 -0
- package/dist/preconditions/index.js.map +1 -0
- package/dist/preconditions/loader.d.ts +33 -0
- package/dist/preconditions/loader.d.ts.map +1 -0
- package/dist/preconditions/loader.js +65 -0
- package/dist/preconditions/loader.js.map +1 -0
- package/dist/preconditions/schema.d.ts +103 -0
- package/dist/preconditions/schema.d.ts.map +1 -0
- package/dist/preconditions/schema.js +93 -0
- package/dist/preconditions/schema.js.map +1 -0
- package/dist/preconditions/types.d.ts +118 -0
- package/dist/preconditions/types.d.ts.map +1 -0
- package/dist/preconditions/types.js +17 -0
- package/dist/preconditions/types.js.map +1 -0
- package/dist/queue/index.d.ts +17 -0
- package/dist/queue/index.d.ts.map +1 -0
- package/dist/queue/index.js +17 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/memory-queue.d.ts +86 -0
- package/dist/queue/memory-queue.d.ts.map +1 -0
- package/dist/queue/memory-queue.js +221 -0
- package/dist/queue/memory-queue.js.map +1 -0
- package/dist/services/audit-log.d.ts +59 -0
- package/dist/services/audit-log.d.ts.map +1 -0
- package/dist/services/audit-log.js +193 -0
- package/dist/services/audit-log.js.map +1 -0
- package/dist/services/auth-error-parser.d.ts +36 -0
- package/dist/services/auth-error-parser.d.ts.map +1 -0
- package/dist/services/auth-error-parser.js +90 -0
- package/dist/services/auth-error-parser.js.map +1 -0
- package/dist/services/deployment-doctor.d.ts +109 -0
- package/dist/services/deployment-doctor.d.ts.map +1 -0
- package/dist/services/deployment-doctor.js +476 -0
- package/dist/services/deployment-doctor.js.map +1 -0
- package/dist/services/deployment-notifications.d.ts +41 -0
- package/dist/services/deployment-notifications.d.ts.map +1 -0
- package/dist/services/deployment-notifications.js +161 -0
- package/dist/services/deployment-notifications.js.map +1 -0
- package/dist/services/deployment-progress.d.ts +89 -0
- package/dist/services/deployment-progress.d.ts.map +1 -0
- package/dist/services/deployment-progress.js +244 -0
- package/dist/services/deployment-progress.js.map +1 -0
- package/dist/services/deployment-service.d.ts +97 -0
- package/dist/services/deployment-service.d.ts.map +1 -0
- package/dist/services/deployment-service.js +375 -0
- package/dist/services/deployment-service.js.map +1 -0
- package/dist/services/drift-analyzer.d.ts +86 -0
- package/dist/services/drift-analyzer.d.ts.map +1 -0
- package/dist/services/drift-analyzer.js +273 -0
- package/dist/services/drift-analyzer.js.map +1 -0
- package/dist/services/environment-setup.d.ts +97 -0
- package/dist/services/environment-setup.d.ts.map +1 -0
- package/dist/services/environment-setup.js +250 -0
- package/dist/services/environment-setup.js.map +1 -0
- package/dist/services/health-check.d.ts +168 -0
- package/dist/services/health-check.d.ts.map +1 -0
- package/dist/services/health-check.js +705 -0
- package/dist/services/health-check.js.map +1 -0
- package/dist/services/index.d.ts +39 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +39 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/logger.d.ts +139 -0
- package/dist/services/logger.d.ts.map +1 -0
- package/dist/services/logger.js +268 -0
- package/dist/services/logger.js.map +1 -0
- package/dist/services/notification-service.d.ts +55 -0
- package/dist/services/notification-service.d.ts.map +1 -0
- package/dist/services/notification-service.js +184 -0
- package/dist/services/notification-service.js.map +1 -0
- package/dist/services/risk-analyzer.d.ts +252 -0
- package/dist/services/risk-analyzer.d.ts.map +1 -0
- package/dist/services/risk-analyzer.js +866 -0
- package/dist/services/risk-analyzer.js.map +1 -0
- package/dist/services/rollback.d.ts +57 -0
- package/dist/services/rollback.d.ts.map +1 -0
- package/dist/services/rollback.js +270 -0
- package/dist/services/rollback.js.map +1 -0
- package/dist/services/scheduler.d.ts +80 -0
- package/dist/services/scheduler.d.ts.map +1 -0
- package/dist/services/scheduler.js +350 -0
- package/dist/services/scheduler.js.map +1 -0
- package/dist/services/secrets.d.ts +31 -0
- package/dist/services/secrets.d.ts.map +1 -0
- package/dist/services/secrets.js +206 -0
- package/dist/services/secrets.js.map +1 -0
- package/dist/services/settings-service.d.ts +132 -0
- package/dist/services/settings-service.d.ts.map +1 -0
- package/dist/services/settings-service.js +378 -0
- package/dist/services/settings-service.js.map +1 -0
- package/dist/services/solution-diff.d.ts +127 -0
- package/dist/services/solution-diff.d.ts.map +1 -0
- package/dist/services/solution-diff.js +260 -0
- package/dist/services/solution-diff.js.map +1 -0
- package/dist/services/solution-mode-detector.d.ts +35 -0
- package/dist/services/solution-mode-detector.d.ts.map +1 -0
- package/dist/services/solution-mode-detector.js +84 -0
- package/dist/services/solution-mode-detector.js.map +1 -0
- package/dist/services/tenant-resolver.d.ts +55 -0
- package/dist/services/tenant-resolver.d.ts.map +1 -0
- package/dist/services/tenant-resolver.js +126 -0
- package/dist/services/tenant-resolver.js.map +1 -0
- package/dist/services/unmanaged-customizations.d.ts +104 -0
- package/dist/services/unmanaged-customizations.d.ts.map +1 -0
- package/dist/services/unmanaged-customizations.js +521 -0
- package/dist/services/unmanaged-customizations.js.map +1 -0
- package/dist/services/url-templater.d.ts +184 -0
- package/dist/services/url-templater.d.ts.map +1 -0
- package/dist/services/url-templater.js +327 -0
- package/dist/services/url-templater.js.map +1 -0
- package/dist/services/version-checker.d.ts +108 -0
- package/dist/services/version-checker.d.ts.map +1 -0
- package/dist/services/version-checker.js +403 -0
- package/dist/services/version-checker.js.map +1 -0
- package/dist/services/waves.d.ts +90 -0
- package/dist/services/waves.d.ts.map +1 -0
- package/dist/services/waves.js +222 -0
- package/dist/services/waves.js.map +1 -0
- package/dist/services/webhook.d.ts +95 -0
- package/dist/services/webhook.d.ts.map +1 -0
- package/dist/services/webhook.js +244 -0
- package/dist/services/webhook.js.map +1 -0
- package/dist/utils/deployment-tools.d.ts +110 -0
- package/dist/utils/deployment-tools.d.ts.map +1 -0
- package/dist/utils/deployment-tools.js +121 -0
- package/dist/utils/deployment-tools.js.map +1 -0
- package/dist/utils/index.d.ts +17 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/index.js.map +1 -0
- 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
|