@aifabrix/builder 2.46.0 → 2.47.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 (175) hide show
  1. package/.npmrc.token +1 -0
  2. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
  3. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
  4. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
  5. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
  6. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
  7. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
  8. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
  9. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
  10. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
  11. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
  12. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
  13. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
  14. package/.temp/validation/summary.txt +5 -0
  15. package/jest.isolated-projects.js +18 -2
  16. package/jest.projects.js +24 -0
  17. package/lib/api/dataplane-health.api.js +19 -7
  18. package/lib/api/lifecycle.api.js +22 -1
  19. package/lib/api/resource-types.api.js +107 -0
  20. package/lib/api/runtime-configuration.api.js +72 -0
  21. package/lib/api/types/resource-types.types.js +45 -0
  22. package/lib/api/user-groups.api.js +9 -9
  23. package/lib/api/users.api.js +8 -8
  24. package/lib/api/wizard.api.js +10 -2
  25. package/lib/app/down.js +58 -26
  26. package/lib/app/index.js +2 -2
  27. package/lib/app/run-resolve-image.js +2 -1
  28. package/lib/app/run-stop-containers.js +113 -0
  29. package/lib/app/run.js +1 -31
  30. package/lib/app/show-display.js +35 -0
  31. package/lib/app/show.js +32 -1
  32. package/lib/build/index.js +17 -5
  33. package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
  34. package/lib/channels/add-channel.js +348 -0
  35. package/lib/channels/channel-artifact-generator.js +141 -0
  36. package/lib/channels/channel-audit.js +101 -0
  37. package/lib/channels/channel-auth-context.js +37 -0
  38. package/lib/channels/channel-credential.js +200 -0
  39. package/lib/channels/channel-display-name.js +64 -0
  40. package/lib/channels/channel-external-system.js +106 -0
  41. package/lib/channels/channel-integration-generator.js +274 -0
  42. package/lib/channels/channel-probe.js +89 -0
  43. package/lib/channels/channel-runtime-resolver.js +152 -0
  44. package/lib/channels/channel-status-display.js +141 -0
  45. package/lib/channels/channel-targets.js +115 -0
  46. package/lib/cli/index.js +16 -4
  47. package/lib/cli/setup-app.js +54 -3
  48. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  49. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  50. package/lib/cli/setup-platform.js +1 -1
  51. package/lib/cli/setup-utility-help-after.js +4 -1
  52. package/lib/cli/setup-utility-resolve.js +8 -5
  53. package/lib/commands/app-logs.js +54 -26
  54. package/lib/commands/app.js +41 -0
  55. package/lib/commands/auth-status-dataplane-version.js +97 -61
  56. package/lib/commands/auth-status.js +2 -7
  57. package/lib/commands/channel.js +58 -0
  58. package/lib/commands/datasource.js +1 -1
  59. package/lib/commands/identity-apply-sync.js +49 -8
  60. package/lib/commands/identity-group-cli.js +66 -26
  61. package/lib/commands/identity-membership-role-cache.js +58 -37
  62. package/lib/commands/identity-shared.js +22 -1
  63. package/lib/commands/identity-user-cli.js +80 -34
  64. package/lib/commands/repair-auth-apply.js +30 -4
  65. package/lib/commands/repair-auth-config.js +8 -13
  66. package/lib/commands/repair-datasource-com-v2.js +15 -0
  67. package/lib/commands/repair-datasource-service-contract.js +206 -0
  68. package/lib/commands/repair-datasource.js +53 -18
  69. package/lib/commands/repair-env-template.js +12 -12
  70. package/lib/commands/repair-openapi-sync.js +26 -4
  71. package/lib/commands/repair-steps.js +28 -13
  72. package/lib/commands/repair-system-alignment.js +7 -4
  73. package/lib/commands/resource-type.js +371 -0
  74. package/lib/commands/setup-modes.js +1 -1
  75. package/lib/commands/setup-prompts-platform-mode.js +24 -21
  76. package/lib/commands/test-trust-external.js +39 -90
  77. package/lib/commands/up-common.js +33 -1
  78. package/lib/commands/up-dataplane.js +8 -4
  79. package/lib/commands/upload.js +24 -3
  80. package/lib/commands/verify-trust-command-action.js +4 -1
  81. package/lib/commands/verify-trust-external.js +12 -20
  82. package/lib/core/config-setup-platform-mode.js +3 -3
  83. package/lib/core/setup-platform-mode.js +41 -6
  84. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  85. package/lib/datasource/capability/capability-manifest.js +254 -0
  86. package/lib/datasource/capability/capability-resolve.js +3 -4
  87. package/lib/datasource/capability/copy-operations.js +5 -3
  88. package/lib/datasource/capability/remove-operations.js +2 -1
  89. package/lib/datasource/capability/run-capability-copy.js +32 -4
  90. package/lib/datasource/capability/run-capability-diff.js +3 -3
  91. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  92. package/lib/datasource/capability/run-capability-edit.js +4 -7
  93. package/lib/datasource/capability/run-capability-relate.js +2 -4
  94. package/lib/datasource/capability/run-capability-remove.js +2 -4
  95. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  96. package/lib/datasource/datasource-identifier.js +2 -1
  97. package/lib/datasource/datasource-validate-display.js +1 -1
  98. package/lib/datasource/field-reference-validator.js +24 -3
  99. package/lib/datasource/orphan-profiles-validator.js +98 -0
  100. package/lib/datasource/validate.js +41 -23
  101. package/lib/external-system/download-helpers.js +87 -0
  102. package/lib/external-system/download.js +61 -122
  103. package/lib/external-system/generator.js +1 -59
  104. package/lib/external-system/test.js +10 -15
  105. package/lib/generator/builders.js +11 -1
  106. package/lib/generator/external-controller-manifest.js +5 -2
  107. package/lib/generator/split.js +3 -1
  108. package/lib/generator/wizard.js +1 -28
  109. package/lib/identity/fk-reference.js +65 -0
  110. package/lib/identity/identity-apply-core.js +13 -58
  111. package/lib/identity/identity-apply-phases.js +5 -1
  112. package/lib/identity/identity-display.js +243 -0
  113. package/lib/identity/identity-response-helpers.js +52 -0
  114. package/lib/identity/identity-sync-errors.js +62 -0
  115. package/lib/identity/resolve-user-id.js +129 -0
  116. package/lib/identity/username-default.js +31 -0
  117. package/lib/lifecycle/certification-progress.js +2 -2
  118. package/lib/lifecycle/report-display-trust.js +70 -0
  119. package/lib/lifecycle/report-display.js +14 -13
  120. package/lib/resolvers/datasource-resolver.js +2 -3
  121. package/lib/resolvers/resource-type-file.js +159 -0
  122. package/lib/schema/application-schema.json +16 -0
  123. package/lib/schema/deployment-rules.yaml +2 -1
  124. package/lib/schema/external-datasource.schema.json +372 -58
  125. package/lib/schema/external-system.schema.json +331 -2
  126. package/lib/schema/type/document-storage.json +9 -5
  127. package/lib/utils/app-run-containers.js +25 -2
  128. package/lib/utils/authentication-shape.js +224 -0
  129. package/lib/utils/build-copy.js +36 -5
  130. package/lib/utils/build-resolve-image.js +66 -12
  131. package/lib/utils/celery-companion-apps.js +53 -0
  132. package/lib/utils/cli-utils.js +4 -0
  133. package/lib/utils/compose-celery-worker.js +23 -0
  134. package/lib/utils/compose-generate-docker-compose.js +9 -1
  135. package/lib/utils/configuration-env-resolver.js +36 -0
  136. package/lib/utils/credential-secrets-env-kv.js +216 -0
  137. package/lib/utils/credential-secrets-env.js +52 -262
  138. package/lib/utils/dataplane-resolver.js +4 -115
  139. package/lib/utils/declarative-url-ports.js +24 -1
  140. package/lib/utils/docker-build.js +1 -4
  141. package/lib/utils/external-env-template.js +10 -3
  142. package/lib/utils/external-system-validators.js +8 -2
  143. package/lib/utils/help-builder.js +2 -0
  144. package/lib/utils/paths.js +25 -0
  145. package/lib/utils/platform-controller-url.js +2 -1
  146. package/lib/utils/platform-kv-ref.js +57 -0
  147. package/lib/utils/runtime-configuration-download.js +189 -0
  148. package/lib/utils/runtime-configuration-values.js +119 -0
  149. package/lib/utils/schema-loader.js +19 -4
  150. package/lib/utils/worker-sidecars.js +142 -0
  151. package/lib/validation/external-manifest-validator.js +2 -0
  152. package/lib/validation/runtime-configuration-rules.js +98 -0
  153. package/lib/validation/validate-display-dimensions.js +154 -0
  154. package/lib/validation/validate-display-sections.js +276 -0
  155. package/lib/validation/validate-display-steps.js +67 -0
  156. package/lib/validation/validate-display.js +24 -402
  157. package/package.json +1 -1
  158. package/templates/applications/dataplane/application.yaml +5 -0
  159. package/templates/applications/dataplane/env.template +26 -13
  160. package/templates/applications/dataplane/rbac.yaml +108 -1
  161. package/templates/applications/miso-controller/application.yaml +2 -2
  162. package/templates/applications/miso-controller/env.template +37 -122
  163. package/templates/applications/miso-controller/rbac.yaml +28 -13
  164. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  165. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  166. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  167. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  168. package/templates/channels/slack/install.template.md.hbs +49 -0
  169. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  170. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  171. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  172. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  173. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  174. package/templates/python/docker-compose.hbs +56 -0
  175. package/templates/typescript/docker-compose.hbs +52 -0
@@ -0,0 +1,243 @@
1
+ /**
2
+ * @fileoverview TTY layout helpers for aifabrix identity commands (plan 151)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const chalk = require('chalk');
10
+ const logger = require('../utils/logger');
11
+ const {
12
+ formatBlockingError,
13
+ formatNextActions,
14
+ headerKeyValue,
15
+ sectionTitle
16
+ } = require('../utils/cli-layout-chalk');
17
+ const { readFkId, readFkKey, readFkName, formatFkLabel } = require('./fk-reference');
18
+ const { unwrapControllerData } = require('./identity-response-helpers');
19
+
20
+ /**
21
+ * @param {Object|null|undefined} response
22
+ * @returns {{ items: Object[], meta: Object|null }}
23
+ */
24
+ function extractPaginatedList(response) {
25
+ if (!response || response.success === false) {
26
+ return { items: [], meta: null };
27
+ }
28
+ const body = response.data;
29
+ if (body && Array.isArray(body.data)) {
30
+ return { items: body.data, meta: body.meta ?? null };
31
+ }
32
+ const unwrapped = unwrapControllerData(response);
33
+ if (Array.isArray(unwrapped)) {
34
+ return { items: unwrapped, meta: null };
35
+ }
36
+ if (unwrapped && Array.isArray(unwrapped.data)) {
37
+ return { items: unwrapped.data, meta: unwrapped.meta ?? null };
38
+ }
39
+ return { items: [], meta: null };
40
+ }
41
+
42
+ /**
43
+ * @param {Object} user
44
+ * @returns {string}
45
+ */
46
+ function formatUserDisplayName(user) {
47
+ const display = String(user?.displayName || '').trim();
48
+ if (display) {
49
+ return display;
50
+ }
51
+ const parts = [user?.firstName, user?.lastName].filter(Boolean).map(String);
52
+ const joined = parts.join(' ').trim();
53
+ return joined || '—';
54
+ }
55
+
56
+ /**
57
+ * @param {string} value
58
+ * @param {number} width
59
+ * @returns {string}
60
+ */
61
+ function padCell(value, width) {
62
+ const text = String(value ?? '—');
63
+ if (text.length <= width) {
64
+ return text.padEnd(width);
65
+ }
66
+ if (width <= 1) {
67
+ return text.slice(0, width);
68
+ }
69
+ return `${text.slice(0, width - 1)}…`;
70
+ }
71
+
72
+ /**
73
+ * @param {{ emoji?: string, title: string, context?: string, shown?: number, total?: number|null }} opts
74
+ */
75
+ function printIdentityListHeader(opts) {
76
+ const emoji = opts.emoji ? `${opts.emoji} ` : '';
77
+ const context = opts.context ? ` (${opts.context})` : '';
78
+ const shown = Number.isFinite(opts.shown) ? opts.shown : 0;
79
+ const total = Number.isFinite(opts.total) ? opts.total : null;
80
+ const count =
81
+ total !== null && total >= shown ? `${shown} shown of ${total}` : `${shown} shown`;
82
+ logger.log(chalk.bold(`\n${emoji}${opts.title}${context} — ${count}:\n`));
83
+ }
84
+
85
+ /**
86
+ * @param {Array<{ label: string, width: number, value: (row: Object) => string }>} columns
87
+ * @param {Object[]} rows
88
+ */
89
+ function printIdentityTable(columns, rows) {
90
+ const header = columns.map((col) => padCell(col.label, col.width)).join('');
91
+ logger.log(chalk.gray(header));
92
+ logger.log(chalk.gray('-'.repeat(columns.reduce((sum, col) => sum + col.width, 0))));
93
+ rows.forEach((row) => {
94
+ const line = columns.map((col) => padCell(col.value(row), col.width)).join('');
95
+ logger.log(line);
96
+ });
97
+ logger.log('');
98
+ }
99
+
100
+ /**
101
+ * @param {Object|null|undefined} meta
102
+ */
103
+ function printListPaginationFooter(meta) {
104
+ if (!meta || !Number.isFinite(meta.totalItems)) {
105
+ return;
106
+ }
107
+ const page = meta.currentPage ?? meta.page ?? 1;
108
+ const pageSize = meta.pageSize ?? meta.page_size;
109
+ const totalPages = meta.totalPages ?? meta.total_pages;
110
+ const parts = [`total ${meta.totalItems}`];
111
+ if (pageSize) {
112
+ parts.unshift(`page ${page}`);
113
+ }
114
+ if (totalPages) {
115
+ parts.push(`${totalPages} pages`);
116
+ }
117
+ logger.log(chalk.gray(` ${parts.join(' · ')}\n`));
118
+ }
119
+
120
+ /**
121
+ * @param {string} title
122
+ * @param {Array<{ label: string, value: string }>} fields
123
+ */
124
+ function printIdentityRecord(title, fields) {
125
+ logger.log(sectionTitle(title));
126
+ fields.forEach(({ label, value }) => {
127
+ logger.log(` ${headerKeyValue(`${label}:`, value || '—')}`);
128
+ });
129
+ logger.log('');
130
+ }
131
+
132
+ /**
133
+ * @param {string} message
134
+ * @param {string[]} [hints]
135
+ */
136
+ function printIdentityEmpty(message, hints = []) {
137
+ logger.log(chalk.gray(` ${message}\n`));
138
+ if (hints.length > 0) {
139
+ logger.log(formatNextActions(hints));
140
+ logger.log('');
141
+ }
142
+ }
143
+
144
+ /**
145
+ * @param {string} entity
146
+ * @param {string} query
147
+ * @param {string[]} [hints]
148
+ */
149
+ function printIdentityNotFound(entity, query, hints = []) {
150
+ logger.error(formatBlockingError(`${entity} not found: ${query}`));
151
+ if (hints.length > 0) {
152
+ logger.log(formatNextActions(hints));
153
+ }
154
+ }
155
+
156
+ const USER_LIST_COLUMNS = [
157
+ { label: 'Id', width: 38, value: (u) => u.id || '—' },
158
+ { label: 'Username', width: 16, value: (u) => u.username || '—' },
159
+ { label: 'Name', width: 22, value: (u) => formatUserDisplayName(u) },
160
+ { label: 'Email', width: 34, value: (u) => u.email || '—' },
161
+ { label: 'Status', width: 10, value: (u) => u.status || '—' }
162
+ ];
163
+
164
+ const GROUP_LIST_COLUMNS = [
165
+ { label: 'Id', width: 38, value: (g) => g.id || '—' },
166
+ { label: 'Name', width: 28, value: (g) => g.name || '—' },
167
+ { label: 'Display name', width: 28, value: (g) => g.displayName || '—' }
168
+ ];
169
+
170
+ /**
171
+ * @param {Object[]} memberships
172
+ */
173
+ function printUserGroupsTable(memberships) {
174
+ printIdentityTable(
175
+ [
176
+ { label: 'Group key', width: 28, value: (m) => readFkKey(m.groupId) || '—' },
177
+ { label: 'Group name', width: 28, value: (m) => formatFkLabel(m.groupId) },
178
+ { label: 'Group id', width: 38, value: (m) => readFkId(m.groupId) || '—' },
179
+ { label: 'Role hint', width: 12, value: (m) => m.roleHint || '—' }
180
+ ],
181
+ memberships
182
+ );
183
+ }
184
+
185
+ /**
186
+ * @param {Object[]} memberships
187
+ */
188
+ function printGroupMembersTable(memberships) {
189
+ printIdentityTable(
190
+ [
191
+ { label: 'User key', width: 22, value: (m) => readFkKey(m.userId) || '—' },
192
+ { label: 'User name', width: 24, value: (m) => formatFkLabel(m.userId) },
193
+ { label: 'User id', width: 38, value: (m) => readFkId(m.userId) || '—' },
194
+ { label: 'Role hint', width: 12, value: (m) => m.roleHint || '—' }
195
+ ],
196
+ memberships
197
+ );
198
+ }
199
+
200
+ /**
201
+ * @param {Object} user
202
+ */
203
+ function printUserRecord(user) {
204
+ printIdentityRecord('User', [
205
+ { label: 'Id', value: user.id },
206
+ { label: 'Email', value: user.email },
207
+ { label: 'Username', value: user.username },
208
+ { label: 'Name', value: formatUserDisplayName(user) },
209
+ { label: 'Display name', value: user.displayName },
210
+ { label: 'Status', value: user.status },
211
+ { label: 'Verified', value: user.emailVerified ? 'yes' : 'no' }
212
+ ]);
213
+ }
214
+
215
+ /**
216
+ * @param {Object} group
217
+ */
218
+ function printGroupRecord(group) {
219
+ printIdentityRecord('Group', [
220
+ { label: 'Id', value: group.id },
221
+ { label: 'Name', value: group.name },
222
+ { label: 'Display name', value: group.displayName },
223
+ { label: 'Description', value: group.description }
224
+ ]);
225
+ }
226
+
227
+ module.exports = {
228
+ extractPaginatedList,
229
+ formatUserDisplayName,
230
+ printIdentityListHeader,
231
+ printIdentityTable,
232
+ printListPaginationFooter,
233
+ printIdentityRecord,
234
+ printIdentityEmpty,
235
+ printIdentityNotFound,
236
+ USER_LIST_COLUMNS,
237
+ GROUP_LIST_COLUMNS,
238
+ printUserGroupsTable,
239
+ printGroupMembersTable,
240
+ printUserRecord,
241
+ printGroupRecord,
242
+ readFkName
243
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @fileoverview Shared controller response helpers for identity modules
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * @param {Object} response
11
+ * @returns {any}
12
+ */
13
+ function unwrapControllerData(response) {
14
+ if (!response || response.success === false) {
15
+ return null;
16
+ }
17
+ return response?.data?.data ?? response?.data ?? response;
18
+ }
19
+
20
+ /**
21
+ * @param {Object} response
22
+ * @returns {string}
23
+ */
24
+ function apiErrorMessage(response) {
25
+ if (!response) {
26
+ return 'Request failed';
27
+ }
28
+ const err = response.error;
29
+ if (typeof err === 'string') {
30
+ return err;
31
+ }
32
+ if (err && typeof err === 'object') {
33
+ return err.formattedError || err.message || err.error || 'Request failed';
34
+ }
35
+ return response.formattedError || response.message || 'Request failed';
36
+ }
37
+
38
+ /**
39
+ * @param {Object} response
40
+ * @throws {Error}
41
+ */
42
+ function throwIfApiFailed(response) {
43
+ if (!response || response.success === false) {
44
+ throw new Error(apiErrorMessage(response));
45
+ }
46
+ }
47
+
48
+ module.exports = {
49
+ unwrapControllerData,
50
+ apiErrorMessage,
51
+ throwIfApiFailed
52
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @fileoverview User-facing identity sync failure messages
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * @param {Object|null|undefined} stats
11
+ * @returns {boolean}
12
+ */
13
+ function isZeroProgressSyncFailure(stats) {
14
+ const usersProcessed = Number(stats?.usersProcessed ?? 0);
15
+ const groupsProcessed = Number(stats?.groupsProcessed ?? 0);
16
+ return usersProcessed === 0 && groupsProcessed === 0;
17
+ }
18
+
19
+ /**
20
+ * Build a multi-line CLI message when controller sync stats report errors.
21
+ *
22
+ * @param {Object|null|undefined} stats
23
+ * @returns {string}
24
+ */
25
+ function buildSyncStatsFailureMessage(stats) {
26
+ const syncErrors = Number(stats?.errors ?? 0);
27
+ const usersProcessed = Number(stats?.usersProcessed ?? 0);
28
+ const groupsProcessed = Number(stats?.groupsProcessed ?? 0);
29
+
30
+ const lines = [
31
+ `Dataplane full sync reported ${syncErrors} error(s).`,
32
+ `usersProcessed=${usersProcessed} groupsProcessed=${groupsProcessed}.`
33
+ ];
34
+
35
+ if (isZeroProgressSyncFailure(stats)) {
36
+ lines.push(
37
+ '',
38
+ 'The controller accepted the sync request, but no identity data reached the dataplane.',
39
+ 'Common causes:',
40
+ ' • Expired or invalid dataplane client token (controller → dataplane auth)',
41
+ ' • Dataplane unreachable or unhealthy',
42
+ ' • Dataplane MISO_CLIENTID / MISO_CLIENTSECRET out of sync with the controller',
43
+ '',
44
+ 'Try:',
45
+ ' aifabrix auth status',
46
+ ' aifabrix run dataplane --reload',
47
+ 'Then retry:',
48
+ ' aifabrix identity sync -e dev --purge-cache',
49
+ '',
50
+ 'If it still fails, check miso-controller logs for the sync request.'
51
+ );
52
+ } else {
53
+ lines.push('Check miso-controller logs for the sync request.');
54
+ }
55
+
56
+ return lines.join('\n');
57
+ }
58
+
59
+ module.exports = {
60
+ isZeroProgressSyncFailure,
61
+ buildSyncStatsFailureMessage
62
+ };
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @fileoverview Resolve controller user id from id, email, or username
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { getUser, listUsers } = require('../api/users.api');
10
+ const { throwIfApiFailed, unwrapControllerData } = require('./identity-response-helpers');
11
+
12
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13
+
14
+ /**
15
+ * @param {string} value
16
+ * @returns {boolean}
17
+ */
18
+ function isLikelyUuid(value) {
19
+ return UUID_RE.test(String(value || '').trim());
20
+ }
21
+
22
+ /**
23
+ * @param {Object} response
24
+ * @returns {Object[]}
25
+ */
26
+ function extractUserListItems(response) {
27
+ const data = unwrapControllerData(response);
28
+ if (Array.isArray(data)) {
29
+ return data;
30
+ }
31
+ if (Array.isArray(data?.data)) {
32
+ return data.data;
33
+ }
34
+ return [];
35
+ }
36
+
37
+ /**
38
+ * @param {Object[]} items
39
+ * @param {string} value
40
+ * @returns {Object|null}
41
+ */
42
+ function pickUserMatch(items, value) {
43
+ const trimmed = String(value || '').trim();
44
+ if (!trimmed) {
45
+ return null;
46
+ }
47
+ const lower = trimmed.toLowerCase();
48
+ return (
49
+ items.find(
50
+ (user) =>
51
+ String(user.email || '').toLowerCase() === lower || String(user.username || '') === trimmed
52
+ ) || null
53
+ );
54
+ }
55
+
56
+ /**
57
+ * @async
58
+ * @param {string} controllerUrl
59
+ * @param {Object} authConfig
60
+ * @param {string} idOrEmail
61
+ * @returns {Promise<Object|null>}
62
+ */
63
+ async function lookupUserByIdOrEmail(controllerUrl, authConfig, idOrEmail) {
64
+ const value = String(idOrEmail || '').trim();
65
+ if (!value) {
66
+ return null;
67
+ }
68
+
69
+ if (isLikelyUuid(value)) {
70
+ const byId = await getUser(controllerUrl, authConfig, value);
71
+ if (byId && byId.success !== false) {
72
+ const row = unwrapControllerData(byId);
73
+ if (row?.id) {
74
+ return row;
75
+ }
76
+ }
77
+ return null;
78
+ }
79
+
80
+ if (value.includes('@')) {
81
+ const byEmailFilter = await listUsers(controllerUrl, authConfig, {
82
+ filter: `email:eq:${value}`,
83
+ pageSize: 1,
84
+ page: 1
85
+ });
86
+ throwIfApiFailed(byEmailFilter);
87
+ const filterMatch = pickUserMatch(extractUserListItems(byEmailFilter), value);
88
+ if (filterMatch?.id) {
89
+ return filterMatch;
90
+ }
91
+ }
92
+
93
+ const listed = await listUsers(controllerUrl, authConfig, {
94
+ search: value,
95
+ pageSize: 50,
96
+ page: 1
97
+ });
98
+ throwIfApiFailed(listed);
99
+ return pickUserMatch(extractUserListItems(listed), value);
100
+ }
101
+
102
+ /**
103
+ * @async
104
+ * @param {string} controllerUrl
105
+ * @param {Object} authConfig
106
+ * @param {string} idOrEmail
107
+ * @returns {Promise<string>} Controller user id
108
+ * @throws {Error} When value is empty or no matching user exists
109
+ */
110
+ async function resolveUserIdOrEmail(controllerUrl, authConfig, idOrEmail) {
111
+ const value = String(idOrEmail || '').trim();
112
+ if (!value) {
113
+ throw new Error('idOrEmail is required.');
114
+ }
115
+
116
+ const user = await lookupUserByIdOrEmail(controllerUrl, authConfig, value);
117
+ if (user?.id) {
118
+ return user.id;
119
+ }
120
+
121
+ throw new Error(`User not found: ${value}`);
122
+ }
123
+
124
+ module.exports = {
125
+ isLikelyUuid,
126
+ extractUserListItems,
127
+ lookupUserByIdOrEmail,
128
+ resolveUserIdOrEmail
129
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Default controller username when callers omit --username.
3
+ * Matches users.csv UserPrincipalName (email) convention.
4
+ *
5
+ * @fileoverview Identity username resolution
6
+ * @author AI Fabrix Team
7
+ * @version 1.0.0
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ /**
13
+ * @param {{ username?: string, email?: string }} input
14
+ * @returns {string}
15
+ * @throws {Error} When neither username nor email is non-empty
16
+ */
17
+ function resolveIdentityUsername(input) {
18
+ const username = String(input?.username || '').trim();
19
+ if (username) {
20
+ return username;
21
+ }
22
+ const email = String(input?.email || '').trim();
23
+ if (email) {
24
+ return email;
25
+ }
26
+ throw new Error('username or email is required');
27
+ }
28
+
29
+ module.exports = {
30
+ resolveIdentityUsername
31
+ };
@@ -49,7 +49,7 @@ const SILVER_TO_GOLD_CRITERIA = [
49
49
  {
50
50
  id: 'aiTrust',
51
51
  label: 'AI Trust ≥ 95%',
52
- evaluate: ctx => (ctx.report.trust?.businessContextConfidencePercent ?? 0) >= 95
52
+ evaluate: ctx => (ctx.report.trust?.aiTrustPercent ?? 0) >= 95
53
53
  },
54
54
  {
55
55
  id: 'activeWarnings',
@@ -164,7 +164,7 @@ function buildCertificationProgress(report, options = {}) {
164
164
  */
165
165
  function formatCriterionCurrentHint(id, report, openApiPublished) {
166
166
  if (id === 'aiTrust') {
167
- const p = report.trust?.businessContextConfidencePercent;
167
+ const p = report.trust?.aiTrustPercent;
168
168
  return typeof p === 'number' ? `(current: ${p}%)` : null;
169
169
  }
170
170
  if (id === 'operationalReadiness') {
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @fileoverview Trust pillar TTY display helpers for verify-trust (433.0).
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ const chalk = require('chalk');
8
+ const logger = require('../utils/logger');
9
+ const { successGlyph, failureGlyph } = require('../utils/cli-test-layout-chalk');
10
+ const { VERDICT } = require('./product-model');
11
+
12
+ const SEP = '────────────────────────';
13
+
14
+ /**
15
+ * @param {string} label
16
+ * @param {number|undefined} percent
17
+ */
18
+ function displaySubMetricLine(label, percent) {
19
+ logger.log(chalk.white(label));
20
+ logger.log(
21
+ typeof percent === 'number'
22
+ ? chalk.cyan.bold(`${percent}%`)
23
+ : chalk.gray('—')
24
+ );
25
+ logger.log('');
26
+ }
27
+
28
+ /**
29
+ * @param {Array<{ severity?: string, code?: string, message?: string }>|undefined} findings
30
+ */
31
+ function displayTrustFindings(findings) {
32
+ if (!Array.isArray(findings) || findings.length === 0) {
33
+ return;
34
+ }
35
+ logger.log('');
36
+ logger.log(chalk.white('Findings'));
37
+ logger.log(chalk.gray(SEP));
38
+ for (const finding of findings) {
39
+ const glyph =
40
+ finding.severity === 'error'
41
+ ? failureGlyph()
42
+ : finding.severity === 'warning'
43
+ ? chalk.yellow('⚠')
44
+ : successGlyph();
45
+ const msg = finding.message || finding.code || '';
46
+ logger.log(` ${glyph} ${msg}`);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * @param {Array<{ datasourceKey?: string, verdict?: string, trustDecision?: string }>|undefined} datasourceRows
52
+ */
53
+ function displayTrustDatasourceRows(datasourceRows) {
54
+ if (!Array.isArray(datasourceRows) || datasourceRows.length === 0) {
55
+ return;
56
+ }
57
+ logger.log('');
58
+ logger.log(chalk.white('Datasources'));
59
+ logger.log(chalk.gray(SEP));
60
+ for (const row of datasourceRows) {
61
+ const glyph = row.verdict === VERDICT.VERIFIED ? successGlyph() : failureGlyph();
62
+ logger.log(` ${row.datasourceKey}`.padEnd(36) + `${glyph} ${row.trustDecision || '—'}`);
63
+ }
64
+ }
65
+
66
+ module.exports = {
67
+ displaySubMetricLine,
68
+ displayTrustFindings,
69
+ displayTrustDatasourceRows
70
+ };
@@ -34,6 +34,11 @@ const {
34
34
  resolveNextStepCommand,
35
35
  formatProgressBar
36
36
  } = require('./certification-progress');
37
+ const {
38
+ displaySubMetricLine,
39
+ displayTrustFindings,
40
+ displayTrustDatasourceRows
41
+ } = require('./report-display-trust');
37
42
 
38
43
  const SEP = '────────────────────────';
39
44
 
@@ -158,19 +163,15 @@ function displayVerifyOperationsTTY(systemKey, result, opts = {}) {
158
163
  * @param {{ details?: boolean }} opts
159
164
  */
160
165
  function displayVerifyTrustTTY(systemKey, result, opts = {}) {
161
- displayPillarHeader(systemKey, 'AI TRUST');
162
- displayHeroMetric('AI Business Context Confidence', result.businessContextConfidencePercent);
166
+ displayPillarHeader(systemKey, 'AI TRUST REPORT');
167
+ displayHeroMetric('Overall', result.aiTrustPercent);
168
+ logger.log('');
169
+ displaySubMetricLine('Access Confidence', result.accessConfidencePercent);
170
+ displaySubMetricLine('Business Confidence', result.businessConfidencePercent);
163
171
  displayVerdictLine(result.verdict, 'AI TRUST VERIFIED', 'AI TRUST FAILED');
164
- if (opts.details === true && Array.isArray(result.datasourceRows)) {
165
- logger.log('');
166
- logger.log(chalk.white('Datasources'));
167
- logger.log(chalk.gray(SEP));
168
- for (const row of result.datasourceRows) {
169
- const conf =
170
- typeof row.confidencePercent === 'number' ? `${row.confidencePercent}%` : '—';
171
- const glyph = row.verdict === VERDICT.VERIFIED ? successGlyph() : failureGlyph();
172
- logger.log(` ${row.datasourceKey}`.padEnd(36) + `${glyph} ${conf}`);
173
- }
172
+ displayTrustFindings(result.findings);
173
+ if (opts.details === true) {
174
+ displayTrustDatasourceRows(result.datasourceRows);
174
175
  }
175
176
  }
176
177
 
@@ -267,7 +268,7 @@ function displayPillarScores(report) {
267
268
  logger.log(
268
269
  formatPillarScoreLine(
269
270
  'AI Trust',
270
- report.trust?.businessContextConfidencePercent,
271
+ report.trust?.aiTrustPercent,
271
272
  report.trust?.verdict
272
273
  )
273
274
  );
@@ -10,7 +10,7 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- const fs = require('fs');
13
+ const { loadConfigFile } = require('../utils/config-format');
14
14
  const { resolveValidateInputPath, resolvePathFromIntegrationDatasourceKey } = require('../datasource/validate');
15
15
 
16
16
  /**
@@ -41,8 +41,7 @@ function tryResolveDatasourceKeyToLocalPath(datasourceKey) {
41
41
  * @returns {any}
42
42
  */
43
43
  function readJsonFile(jsonPath) {
44
- const raw = fs.readFileSync(jsonPath, 'utf8');
45
- return JSON.parse(raw);
44
+ return loadConfigFile(jsonPath);
46
45
  }
47
46
 
48
47
  module.exports = {