@aifabrix/builder 2.50.0 → 2.51.1

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 (236) hide show
  1. package/.cursor/plans/Done/157.1-security-quality-violations-by-type.csv +374 -0
  2. package/.cursor/rules/assistant-driven-integration-build.mdc +40 -0
  3. package/integration/hubspot-test/create-hubspot.js +1 -16
  4. package/jest.isolated-projects.js +77 -1
  5. package/jest.projects.js +5 -426
  6. package/lib/api/assistants.api.js +67 -0
  7. package/lib/api/datasources-extended.api.js +6 -6
  8. package/lib/api/evidence-authoring-helpers.js +267 -0
  9. package/lib/api/evidence-authoring.api.js +337 -0
  10. package/lib/api/evidence-kinds.api.js +128 -0
  11. package/lib/api/evidence.api.js +57 -1
  12. package/lib/api/index.js +24 -7
  13. package/lib/api/pipeline.api.js +47 -3
  14. package/lib/api/records-bulk.api.js +28 -3
  15. package/lib/api/resource-types.api.js +4 -4
  16. package/lib/api/roles.api.js +121 -0
  17. package/lib/api/types/evidence-kinds.types.js +42 -0
  18. package/lib/api/types/evidence.types.js +156 -8
  19. package/lib/api/types/roles.types.js +36 -0
  20. package/lib/api/work.api.js +283 -0
  21. package/lib/app/certification-verify-rows.js +1 -1
  22. package/lib/app/run-deploy-job.js +108 -0
  23. package/lib/app/run-helpers.js +10 -3
  24. package/lib/app/run.js +8 -0
  25. package/lib/app/show.js +2 -2
  26. package/lib/certification/merge-certification-from-artifact.js +1 -1
  27. package/lib/channels/add-channel.js +54 -23
  28. package/lib/channels/approval-guides/chatgpt.js +8 -7
  29. package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
  30. package/lib/channels/channel-artifact-generator.js +16 -3
  31. package/lib/channels/channel-credential.js +3 -1
  32. package/lib/channels/channel-probe-evidence.js +79 -0
  33. package/lib/channels/channel-probe.js +265 -13
  34. package/lib/channels/channel-runtime-resolver.js +120 -29
  35. package/lib/channels/channel-status-display.js +44 -0
  36. package/lib/channels/channel-targets.js +7 -0
  37. package/lib/channels/chatgpt-runtime-contract.js +29 -0
  38. package/lib/channels/microsoft-copilot-package.js +280 -0
  39. package/lib/channels/runtime-tool-catalog.js +165 -0
  40. package/lib/cli/index.js +6 -0
  41. package/lib/cli/setup-app.js +26 -5
  42. package/lib/cli/setup-dev.js +23 -7
  43. package/lib/cli/setup-external-system-help.js +8 -2
  44. package/lib/cli/setup-external-system.js +12 -0
  45. package/lib/cli/setup-utility-help-after.js +12 -0
  46. package/lib/cli/setup-utility-validate.js +41 -2
  47. package/lib/commands/app-test.js +2 -14
  48. package/lib/commands/datasource-load-export-cli.js +6 -1
  49. package/lib/commands/deploy-gate-failure-details.js +23 -0
  50. package/lib/commands/deploy-gate-orchestrator.js +64 -18
  51. package/lib/commands/dev-cli-handlers.js +6 -3
  52. package/lib/commands/dev-down.js +2 -6
  53. package/lib/commands/dev-init.js +7 -109
  54. package/lib/commands/dev-refresh.js +197 -0
  55. package/lib/commands/evidence-kind.js +478 -0
  56. package/lib/commands/governance-command-action.js +4 -2
  57. package/lib/commands/governance-generate-external.js +56 -21
  58. package/lib/commands/lifecycle-external.js +10 -4
  59. package/lib/commands/repair-datasource-apply.js +10 -1
  60. package/lib/commands/repair-datasource-run.js +4 -2
  61. package/lib/commands/repair-datasource-test-payload.js +80 -19
  62. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
  63. package/lib/commands/repair-internal.js +9 -1
  64. package/lib/commands/repair-openapi-export.js +1 -1
  65. package/lib/commands/repair-openapi-sync.js +43 -18
  66. package/lib/commands/resource-type.js +101 -18
  67. package/lib/commands/role-assistant.js +313 -0
  68. package/lib/commands/role.js +452 -0
  69. package/lib/commands/setup.js +1 -1
  70. package/lib/commands/upload-data-sync-finalize.js +5 -4
  71. package/lib/commands/upload-sidecar-followups.js +144 -47
  72. package/lib/commands/upload.js +43 -6
  73. package/lib/commands/validate-json.js +230 -0
  74. package/lib/commands/verify-governance-combined-external.js +73 -9
  75. package/lib/commands/verify-governance-db.js +80 -25
  76. package/lib/commands/verify-governance-external.js +29 -5
  77. package/lib/commands/verify-operations-step-plan.js +78 -40
  78. package/lib/commands/wizard-core-helpers.js +7 -3
  79. package/lib/commands/wizard-core.js +78 -9
  80. package/lib/core/audit-logger.js +1 -1
  81. package/lib/core/secrets-load.js +1 -3
  82. package/lib/datasource/bulk-loader-run-batches.js +4 -2
  83. package/lib/datasource/bulk-loader-service.js +5 -1
  84. package/lib/datasource/capability/relate-operations.js +1 -1
  85. package/lib/datasource/datasource-validate-summary.js +2 -2
  86. package/lib/datasource/validate.js +13 -6
  87. package/lib/external-system/deploy-helpers.js +35 -1
  88. package/lib/external-system/deploy.js +23 -10
  89. package/lib/external-system/download-helpers.js +111 -2
  90. package/lib/external-system/download-role-assistant.js +55 -0
  91. package/lib/external-system/download.js +45 -26
  92. package/lib/external-system/test-helpers.js +1 -1
  93. package/lib/external-system/test.js +39 -3
  94. package/lib/generator/builders.js +3 -0
  95. package/lib/generator/helpers.js +13 -5
  96. package/lib/generator/split-variables.js +9 -3
  97. package/lib/generator/wizard.js +51 -4
  98. package/lib/infrastructure/helpers-docker-check.js +6 -0
  99. package/lib/infrastructure/helpers.js +1 -1
  100. package/lib/lifecycle/access-control-abac.js +121 -0
  101. package/lib/lifecycle/lifecycle-governance-options.js +4 -1
  102. package/lib/lifecycle/product-model.js +3 -2
  103. package/lib/lifecycle/report-display.js +14 -2
  104. package/lib/programmatic/auth-config.js +3 -2
  105. package/lib/programmatic/bearer-auth.js +1 -1
  106. package/lib/programmatic/capability-registry.js +20 -1
  107. package/lib/programmatic/command-options.js +1 -0
  108. package/lib/programmatic/enterprise-mcp-client.js +18 -2
  109. package/lib/programmatic/index.js +4 -0
  110. package/lib/programmatic/manifest-patch-section.js +48 -9
  111. package/lib/programmatic/manifest-section-registry-data.js +3 -2
  112. package/lib/programmatic/manifest-section-registry.js +18 -1
  113. package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
  114. package/lib/programmatic/openapi-component-schemas.js +16 -1
  115. package/lib/programmatic/openapi-description-enricher.js +3 -1
  116. package/lib/programmatic/openapi-descriptions.js +55 -5
  117. package/lib/programmatic/openapi-evidence-schemas.js +161 -0
  118. package/lib/programmatic/openapi-manifest-schemas.js +6 -2
  119. package/lib/programmatic/openapi-operation-bindings.js +99 -0
  120. package/lib/programmatic/openapi-request-schemas.js +36 -1
  121. package/lib/programmatic/openapi-result-schemas.js +29 -12
  122. package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
  123. package/lib/programmatic/openapi-schema-descriptions-core.js +6 -3
  124. package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
  125. package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
  126. package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
  127. package/lib/programmatic/openapi-schema-helpers.js +16 -1
  128. package/lib/programmatic/openapi-spec.js +48 -29
  129. package/lib/programmatic/orchestration-openapi-meta.js +5 -0
  130. package/lib/programmatic/request-timeout.js +3 -0
  131. package/lib/programmatic/route-handler-map.js +89 -2
  132. package/lib/programmatic/run-certify.js +15 -2
  133. package/lib/programmatic/run-deploy.js +74 -0
  134. package/lib/programmatic/run-evidence-authoring.js +80 -0
  135. package/lib/programmatic/run-protection.js +124 -0
  136. package/lib/programmatic/run-repository-emcp.js +11 -3
  137. package/lib/programmatic/run-template-workspace.js +42 -2
  138. package/lib/programmatic/workspace-context.js +6 -0
  139. package/lib/protection/auth-context.js +48 -6
  140. package/lib/protection/paths.js +22 -0
  141. package/lib/protection/run-protection-create-helpers.js +22 -1
  142. package/lib/protection/run-protection-create.js +1 -1
  143. package/lib/protection/sync-after-upload.js +1 -1
  144. package/lib/resolvers/evidence-kind-file.js +230 -0
  145. package/lib/resolvers/resource-type-file.js +36 -1
  146. package/lib/resolvers/role-file.js +227 -0
  147. package/lib/role-assistant/evidence-sync.js +450 -0
  148. package/lib/role-assistant/github-workflow.js +96 -0
  149. package/lib/role-assistant/knowledge-sync.js +465 -0
  150. package/lib/role-assistant/settings.js +126 -0
  151. package/lib/role-assistant/test-cases-evaluate.js +170 -0
  152. package/lib/role-assistant/test-cases-honesty.js +184 -0
  153. package/lib/role-assistant/test-cases.js +364 -0
  154. package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
  155. package/lib/role-assistant/test-runner-workhub.js +385 -0
  156. package/lib/role-assistant/test-runner.js +500 -0
  157. package/lib/schema/application-schema.json +36 -2
  158. package/lib/schema/deployment-rules.yaml +1 -0
  159. package/lib/schema/external-datasource.schema.json +121 -4
  160. package/lib/schema/external-system.schema.json +20 -6
  161. package/lib/schema/infra.parameter.yaml +45 -1
  162. package/lib/schema/role-assistant-settings.schema.json +108 -0
  163. package/lib/schema/type/credential-field-registry.json +19 -3
  164. package/lib/utils/api.js +52 -21
  165. package/lib/utils/app-path-resolve.js +72 -24
  166. package/lib/utils/app-register-config.js +4 -3
  167. package/lib/utils/authentication-shape.js +3 -1
  168. package/lib/utils/cli-sync-options.js +59 -3
  169. package/lib/utils/cli-utils.js +11 -3
  170. package/lib/utils/deployment-validation.js +28 -3
  171. package/lib/utils/dev-ca-install.js +5 -6
  172. package/lib/utils/dev-config.js +2 -2
  173. package/lib/utils/dev-list-sort.js +145 -0
  174. package/lib/utils/dev-platform-tls.js +0 -63
  175. package/lib/utils/docker-build.js +46 -27
  176. package/lib/utils/docker-daemon-tls-ca.js +1 -1
  177. package/lib/utils/docker-exec.js +91 -6
  178. package/lib/utils/docker-run-ephemeral.js +55 -0
  179. package/lib/utils/enterprise-knowledge-paths.js +29 -12
  180. package/lib/utils/env-copy-keycloak.js +196 -0
  181. package/lib/utils/env-copy.js +35 -4
  182. package/lib/utils/error-formatters/error-parser.js +0 -3
  183. package/lib/utils/external-env-template.js +1 -1
  184. package/lib/utils/external-system-readiness-compact-blocks.js +28 -1
  185. package/lib/utils/external-system-readiness-compact-display.js +3 -0
  186. package/lib/utils/health-check-request.js +2 -3
  187. package/lib/utils/help-builder.js +4 -0
  188. package/lib/utils/image-name.js +22 -4
  189. package/lib/utils/logger.js +37 -4
  190. package/lib/utils/manifest-source-emit.js +1 -1
  191. package/lib/utils/paths.js +98 -26
  192. package/lib/utils/pipeline-upload-auth-retry.js +19 -3
  193. package/lib/utils/postgres-platform-bootstrap.js +24 -17
  194. package/lib/utils/postgres-wipe.js +5 -2
  195. package/lib/utils/remote-dev-auth.js +39 -4
  196. package/lib/utils/resolve-docker-image-ref.js +36 -6
  197. package/lib/utils/role-assistant-paths.js +98 -0
  198. package/lib/utils/schema-loader.js +41 -0
  199. package/lib/utils/schema-resolver.js +36 -4
  200. package/lib/utils/token-manager.js +47 -22
  201. package/lib/utils/upload-sync-options.js +17 -3
  202. package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
  203. package/lib/validation/external-manifest-validator.js +31 -8
  204. package/lib/validation/openapi-contract-surface-validator.js +4 -0
  205. package/lib/validation/validate-external-file.js +8 -1
  206. package/lib/validation/validator.js +6 -5
  207. package/package.json +35 -12
  208. package/scripts/export-builder-api-evidence.js +3 -3
  209. package/scripts/lib/plan-actor.js +2 -2
  210. package/scripts/lib/plan-meta.js +1 -1
  211. package/scripts/lib/plan-parse.js +1 -1
  212. package/scripts/lib/plan-prevalidate-gates.js +12 -1
  213. package/scripts/lib/plan-run-report.js +13 -2
  214. package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +10 -10
  215. package/templates/applications/dataplane/application.yaml +47 -17
  216. package/templates/applications/dataplane/env.template +28 -9
  217. package/templates/applications/dataplane/rbac.yaml +78 -2
  218. package/templates/applications/miso-controller/application.yaml +10 -1
  219. package/templates/applications/miso-controller/env.template +13 -8
  220. package/templates/applications/miso-controller/rbac.yaml +72 -0
  221. package/templates/channels/chatgpt/install.template.md.hbs +42 -24
  222. package/templates/channels/chatgpt/integration/README.md +10 -7
  223. package/templates/channels/chatgpt/integration/application.json +2 -2
  224. package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
  225. package/templates/channels/chatgpt/integration/env.template +1 -1
  226. package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
  227. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
  228. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
  229. package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
  230. package/templates/channels/microsoft-copilot/integration/README.md +9 -7
  231. package/templates/channels/microsoft-copilot/integration/application.json +2 -2
  232. package/templates/channels/microsoft-copilot/integration/env.template +1 -1
  233. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
  234. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
  235. package/templates/channels/slack/integration/slack-system.json +60 -2
  236. package/templates/python/requirements.txt +1 -1
@@ -0,0 +1,452 @@
1
+ /**
2
+ * Business role commands (Controller).
3
+ *
4
+ * Commands:
5
+ * - aifabrix role create|import|export|get|list
6
+ *
7
+ * Distinct from `aifabrix identity role` (environment RBAC mappings).
8
+ *
9
+ * @fileoverview Business role CLI commands
10
+ * @author AI Fabrix Team
11
+ * @version 1.0.0
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const chalk = require('chalk');
17
+ const logger = require('../utils/logger');
18
+ const { resolveControllerUrl } = require('../utils/controller-url');
19
+ const { normalizeControllerUrl, resolveEnvironment } = require('../core/config');
20
+ const { getOrRefreshDeviceToken } = require('../utils/token-manager');
21
+ const {
22
+ readRoleCreateFile,
23
+ readRoleCatalogFile,
24
+ writeRoleCatalogFile,
25
+ mapRoleFileToApiPayload,
26
+ mapRoleApiToFileRow
27
+ } = require('../resolvers/role-file');
28
+ const {
29
+ formatBlockingError,
30
+ formatSuccessLine,
31
+ infoLine,
32
+ headerKeyValue
33
+ } = require('../utils/cli-layout-chalk');
34
+ const { listRoles, getRole, createRoleIdempotent } = require('../api/roles.api');
35
+
36
+ const ROLE_KEY_PATTERN = /^[a-z][a-z0-9-]*$/;
37
+
38
+ const ROLE_HELP_AFTER = `
39
+
40
+ Examples:
41
+ $ aifabrix role create --key sales-manager --display-name "Sales Manager" --domain sales
42
+ $ aifabrix role get sales-manager
43
+ $ aifabrix role list --search sales
44
+
45
+ Per-command help: aifabrix role <command> --help
46
+ `;
47
+
48
+ const ROLE_IMPORT_HELP_AFTER = `
49
+
50
+ Examples:
51
+ $ aifabrix role import --file ./baseline-roles.json
52
+ `;
53
+
54
+ const ROLE_EXPORT_HELP_AFTER = `
55
+
56
+ Examples:
57
+ $ aifabrix role export --file ./roles-export.json
58
+ `;
59
+
60
+ const ROLE_CREATE_HELP_AFTER = `
61
+
62
+ Examples:
63
+ $ aifabrix role create --key sales-manager --display-name "Sales Manager" --domain sales
64
+ $ aifabrix role create --key cfo --display-name "CFO" --domain finance --purpose "..." --business-outcome "..."
65
+ $ aifabrix role create --file ./sales-manager.json
66
+ `;
67
+
68
+ const ROLE_GET_HELP_AFTER = `
69
+
70
+ Examples:
71
+ $ aifabrix role get sales-manager
72
+ $ aifabrix role get clx1234567890abcdef
73
+ `;
74
+
75
+ const ROLE_LIST_HELP_AFTER = `
76
+
77
+ Examples:
78
+ $ aifabrix role list
79
+ $ aifabrix role list --page 1 --page-size 50
80
+ $ aifabrix role list --search sales
81
+ `;
82
+
83
+ /**
84
+ * @param {string} raw
85
+ * @returns {string}
86
+ */
87
+ function requireRoleKey(raw) {
88
+ const key = String(raw || '').trim();
89
+ if (!key) {
90
+ throw new Error('Role key is required (--key <key> or --file <path>).');
91
+ }
92
+ if (!ROLE_KEY_PATTERN.test(key)) {
93
+ throw new Error(
94
+ 'Role key must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.'
95
+ );
96
+ }
97
+ return key;
98
+ }
99
+
100
+ /**
101
+ * @param {Object} _options
102
+ * @returns {Promise<{ controllerUrl: string, authConfig: Object }>}
103
+ */
104
+ async function resolveControllerAndAuth(_options) {
105
+ const controllerUrl = await resolveControllerUrl();
106
+ if (!controllerUrl) {
107
+ throw new Error('Controller URL is required. Run "aifabrix login" first.');
108
+ }
109
+ const normalized = normalizeControllerUrl(controllerUrl);
110
+ const deviceToken = await getOrRefreshDeviceToken(normalized);
111
+ if (!deviceToken || !deviceToken.token) {
112
+ throw new Error(
113
+ `Not authenticated for controller: ${controllerUrl}. ` +
114
+ 'Run "aifabrix login" and try again.'
115
+ );
116
+ }
117
+ return {
118
+ controllerUrl: deviceToken.controller || normalized,
119
+ authConfig: { type: 'bearer', token: deviceToken.token }
120
+ };
121
+ }
122
+
123
+ async function resolveHeaderContext() {
124
+ const env = await resolveEnvironment();
125
+ const controllerUrl = await resolveControllerUrl();
126
+ const normalized = normalizeControllerUrl(controllerUrl);
127
+ return { environment: env || 'dev', controllerUrl: normalized };
128
+ }
129
+
130
+ /**
131
+ * @param {Object} response
132
+ * @returns {any}
133
+ */
134
+ function unwrapControllerData(response) {
135
+ return response?.data?.data ?? response?.data ?? response;
136
+ }
137
+
138
+ /**
139
+ * @param {Object|null|undefined} response
140
+ * @param {string} label
141
+ * @returns {void}
142
+ */
143
+ function assertControllerListSuccess(response, label) {
144
+ if (response && response.success !== false) {
145
+ return;
146
+ }
147
+ const message =
148
+ response?.formattedError || response?.error || `Failed to list ${label}`;
149
+ throw new Error(message);
150
+ }
151
+
152
+ /**
153
+ * Extract items from a Controller paginated list response.
154
+ * @param {Object} response
155
+ * @returns {Object[]}
156
+ */
157
+ function extractControllerListItems(response) {
158
+ if (response?.data && Array.isArray(response.data.data)) {
159
+ return response.data.data;
160
+ }
161
+ const unwrapped = unwrapControllerData(response);
162
+ if (Array.isArray(unwrapped)) {
163
+ return unwrapped;
164
+ }
165
+ if (unwrapped && Array.isArray(unwrapped.data)) {
166
+ return unwrapped.data;
167
+ }
168
+ throw new Error('Unexpected list response from controller');
169
+ }
170
+
171
+ /**
172
+ * Build create payload from flags and optional --file (maps domain → domainKey).
173
+ * @param {Object} options
174
+ * @returns {Object}
175
+ */
176
+ function buildCreatePayload(options) {
177
+ let base = {};
178
+ if (options.file) {
179
+ base = readRoleCreateFile(options.file);
180
+ }
181
+ const merged = { ...base };
182
+ if (options.key) merged.key = options.key;
183
+ if (options.displayName) merged.displayName = options.displayName;
184
+ if (options.description !== undefined) merged.description = options.description;
185
+ if (options.purpose !== undefined) merged.purpose = options.purpose;
186
+ if (options.businessOutcome !== undefined) merged.businessOutcome = options.businessOutcome;
187
+ if (options.domain) {
188
+ merged.domain = options.domain;
189
+ delete merged.domainKey;
190
+ }
191
+ if (options.file && !options.domain && base.domainKey) {
192
+ return { ...base, key: requireRoleKey(merged.key || base.key) };
193
+ }
194
+ const payload = mapRoleFileToApiPayload(
195
+ {
196
+ ...merged,
197
+ domain: merged.domain || merged.domainKey
198
+ },
199
+ 0
200
+ );
201
+ payload.key = requireRoleKey(payload.key);
202
+ return payload;
203
+ }
204
+
205
+ /**
206
+ * @param {Object[]} rows
207
+ * @param {string} controllerUrl
208
+ * @param {Object} authConfig
209
+ * @returns {Promise<{ created: number, existed: number, total: number }>}
210
+ */
211
+ async function importRoleRows(rows, controllerUrl, authConfig) {
212
+ let created = 0;
213
+ let existed = 0;
214
+ for (const payload of rows) {
215
+ payload.key = requireRoleKey(payload.key);
216
+ const out = await createRoleIdempotent(controllerUrl, authConfig, payload);
217
+ if (out.created) {
218
+ created += 1;
219
+ continue;
220
+ }
221
+ existed += 1;
222
+ }
223
+ return { created, existed, total: rows.length };
224
+ }
225
+
226
+ /**
227
+ * @param {string} controllerUrl
228
+ * @param {Object} authConfig
229
+ * @returns {Promise<Object[]>}
230
+ */
231
+ async function fetchAllRoles(controllerUrl, authConfig) {
232
+ const all = [];
233
+ let page = 1;
234
+ const pageSize = 100;
235
+ for (;;) {
236
+ const res = await listRoles(controllerUrl, authConfig, { page, pageSize });
237
+ assertControllerListSuccess(res, 'roles');
238
+ const items = extractControllerListItems(res);
239
+ if (items.length === 0) {
240
+ break;
241
+ }
242
+ all.push(...items);
243
+ if (items.length < pageSize) {
244
+ break;
245
+ }
246
+ page += 1;
247
+ }
248
+ return all;
249
+ }
250
+
251
+ function setupRoleImportCommand(role) {
252
+ role
253
+ .command('import')
254
+ .description('Import business roles from baseline JSON (idempotent)')
255
+ .addHelpText('after', ROLE_IMPORT_HELP_AFTER)
256
+ .requiredOption('--file <path>', 'Baseline catalog (roles[]) or JSON array')
257
+ .action(async(options) => {
258
+ try {
259
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
260
+ const rows = readRoleCatalogFile(options.file);
261
+ const summary = await importRoleRows(rows, controllerUrl, authConfig);
262
+ logger.log(
263
+ formatSuccessLine(
264
+ `Roles imported: ${summary.total} total ` +
265
+ `(${summary.created} created, ${summary.existed} already existed)`
266
+ )
267
+ );
268
+ } catch (e) {
269
+ logger.error(formatBlockingError(e.message));
270
+ process.exit(1);
271
+ }
272
+ });
273
+ }
274
+
275
+ function setupRoleExportCommand(role) {
276
+ role
277
+ .command('export')
278
+ .description('Export business roles to baseline-shaped JSON')
279
+ .addHelpText('after', ROLE_EXPORT_HELP_AFTER)
280
+ .requiredOption('--file <path>', 'Output path for baseline catalog JSON')
281
+ .action(async(options) => {
282
+ try {
283
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
284
+ const records = await fetchAllRoles(controllerUrl, authConfig);
285
+ const rows = records.map(mapRoleApiToFileRow);
286
+ writeRoleCatalogFile(options.file, rows);
287
+ if (rows.length === 0) {
288
+ logger.log(
289
+ infoLine(
290
+ 'No business roles on the controller. Create or import roles, then re-run export.'
291
+ )
292
+ );
293
+ }
294
+ logger.log(formatSuccessLine(`Roles exported: ${rows.length} written to ${options.file}`));
295
+ } catch (e) {
296
+ logger.error(formatBlockingError(e.message));
297
+ process.exit(1);
298
+ }
299
+ });
300
+ }
301
+
302
+ function setupRoleCreateCommand(role) {
303
+ role
304
+ .command('create')
305
+ .description('Create business role (idempotent; success if it already exists)')
306
+ .addHelpText('after', ROLE_CREATE_HELP_AFTER)
307
+ .option('--file <path>', 'Read role create payload from JSON file')
308
+ .option('--key <key>', 'Catalog key (lowercase letters, digits, hyphens)')
309
+ .option('--display-name <name>', 'Display name')
310
+ .option('--domain <domain>', 'Enterprise domain key (e.g. sales, finance, platform)')
311
+ .option('--purpose <text>', 'Role purpose')
312
+ .option('--business-outcome <text>', 'Business outcome')
313
+ .option('--description <text>', 'Description')
314
+ .action(async(options) => {
315
+ try {
316
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
317
+ const payload = buildCreatePayload(options);
318
+ const out = await createRoleIdempotent(controllerUrl, authConfig, payload);
319
+ const row = unwrapControllerData(out.response);
320
+ const domain = row.domainKey || payload.domainKey || '—';
321
+ if (out.created) {
322
+ logger.log(formatSuccessLine(`Role created: ${row.key} (domain: ${domain})`));
323
+ return;
324
+ }
325
+ logger.log(formatSuccessLine(`Role exists: ${row.key} (domain: ${domain})`));
326
+ logger.log(infoLine('No changes were needed.'));
327
+ } catch (e) {
328
+ logger.error(formatBlockingError(e.message));
329
+ process.exit(1);
330
+ }
331
+ });
332
+ }
333
+
334
+ function setupRoleGetCommand(role) {
335
+ role
336
+ .command('get <roleIdOrKey>')
337
+ .description('Get business role by id or key')
338
+ .addHelpText('after', ROLE_GET_HELP_AFTER)
339
+ .action(async(roleIdOrKey, options) => {
340
+ try {
341
+ const { environment, controllerUrl: headerControllerUrl } = await resolveHeaderContext();
342
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
343
+ const res = await getRole(controllerUrl, authConfig, String(roleIdOrKey).trim());
344
+ const row = unwrapControllerData(res);
345
+ logger.log(
346
+ chalk.bold(`\nšŸ‘¤ Business role in ${environment} environment (${headerControllerUrl}):\n`)
347
+ );
348
+ logger.log(headerKeyValue('Key:', row.key || '—'));
349
+ logger.log(headerKeyValue('Display:', row.displayName || '—'));
350
+ logger.log(headerKeyValue('Domain:', row.domainKey || '—'));
351
+ logger.log(headerKeyValue('Active:', row.isActive === false ? 'false' : 'true'));
352
+ if (row.purpose) logger.log(headerKeyValue('Purpose:', row.purpose));
353
+ if (row.businessOutcome) logger.log(headerKeyValue('Business outcome:', row.businessOutcome));
354
+ if (row.description) logger.log(headerKeyValue('Description:', row.description));
355
+ logger.log('');
356
+ } catch (e) {
357
+ logger.error(formatBlockingError(e.message));
358
+ process.exit(1);
359
+ }
360
+ });
361
+ }
362
+
363
+ const ROLE_LIST_COLUMNS = { key: 28, display: 28, domain: 14, active: 8 };
364
+
365
+ function logRoleListHeader(columns) {
366
+ const keyCol = 'Key'.padEnd(columns.key);
367
+ const displayCol = 'Display'.padEnd(columns.display);
368
+ const domainCol = 'Domain'.padEnd(columns.domain);
369
+ const activeCol = 'Active'.padEnd(columns.active);
370
+ logger.log(chalk.gray(`${keyCol}${displayCol}${domainCol}${activeCol}`));
371
+ logger.log(
372
+ chalk.gray('-'.repeat(columns.key + columns.display + columns.domain + columns.active))
373
+ );
374
+ }
375
+
376
+ function logRoleListRows(items, columns) {
377
+ items.forEach((d) => {
378
+ const key = (d?.key ?? '—').toString().padEnd(columns.key);
379
+ const displayName = (d?.displayName ?? '—').toString().padEnd(columns.display);
380
+ const domain = (d?.domainKey ?? '—').toString().padEnd(columns.domain);
381
+ const active = (d?.isActive === false ? 'false' : 'true').padEnd(columns.active);
382
+ logger.log(`${key}${displayName}${domain}${active}`);
383
+ });
384
+ }
385
+
386
+ function displayRoleList(items, environment, controllerUrl) {
387
+ logger.log(chalk.bold(`\nšŸ‘¤ Business roles in ${environment} environment (${controllerUrl}):\n`));
388
+ if (!Array.isArray(items) || items.length === 0) {
389
+ logger.log(chalk.gray(' No business roles found.\n'));
390
+ return;
391
+ }
392
+ logRoleListHeader(ROLE_LIST_COLUMNS);
393
+ logRoleListRows(items, ROLE_LIST_COLUMNS);
394
+ logger.log('');
395
+ }
396
+
397
+ function setupRoleListCommand(role) {
398
+ role
399
+ .command('list')
400
+ .description('List business roles')
401
+ .addHelpText('after', ROLE_LIST_HELP_AFTER)
402
+ .option('--page <n>', 'Page', (v) => parseInt(v, 10))
403
+ .option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
404
+ .option('--search <text>', 'Search by key/displayName/description')
405
+ .option('--domain-key <domain>', 'Filter by domain key')
406
+ .action(async(options) => {
407
+ try {
408
+ const { environment, controllerUrl: headerControllerUrl } = await resolveHeaderContext();
409
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
410
+ const res = await listRoles(controllerUrl, authConfig, {
411
+ page: options.page,
412
+ pageSize: options.pageSize,
413
+ search: options.search,
414
+ domainKey: options.domainKey
415
+ });
416
+ assertControllerListSuccess(res, 'roles');
417
+ const items = extractControllerListItems(res);
418
+ displayRoleList(items, environment, headerControllerUrl);
419
+ } catch (e) {
420
+ logger.error(formatBlockingError(e.message));
421
+ process.exit(1);
422
+ }
423
+ });
424
+ }
425
+
426
+ /**
427
+ * @param {import('commander').Command} program
428
+ */
429
+ function setupRoleCommands(program) {
430
+ const role = program
431
+ .command('role')
432
+ .description('Manage the Controller Role Catalog (COM)')
433
+ .addHelpText('after', ROLE_HELP_AFTER);
434
+ setupRoleCreateCommand(role);
435
+ setupRoleImportCommand(role);
436
+ setupRoleExportCommand(role);
437
+ setupRoleGetCommand(role);
438
+ setupRoleListCommand(role);
439
+ }
440
+
441
+ module.exports = {
442
+ setupRoleCommands,
443
+ requireRoleKey,
444
+ buildCreatePayload,
445
+ unwrapControllerData,
446
+ assertControllerListSuccess,
447
+ extractControllerListItems,
448
+ importRoleRows,
449
+ fetchAllRoles,
450
+ mapRoleFileToApiPayload,
451
+ mapRoleApiToFileRow
452
+ };
@@ -291,7 +291,7 @@ async function appendSetupInstallationRecord(payload) {
291
291
  async function handleSetup(options = {}) {
292
292
  const assumeYes = options.yes === true || options.assumeYes === true;
293
293
  const startedAt = new Date();
294
- let outcome = 'success';
294
+ let outcome;
295
295
  let setupMode = null;
296
296
  let err = null;
297
297
 
@@ -27,7 +27,7 @@ async function finalizeUploadPublishResult(ctx, payload, options) {
27
27
  options,
28
28
  label: 'Upload data sync'
29
29
  });
30
- return { ...ctx, dataSync };
30
+ return { ...ctx, dataSync, sidecar: ctx.sidecar || null };
31
31
  }
32
32
 
33
33
  /**
@@ -40,15 +40,16 @@ async function finishUnchangedUploadPublication(params) {
40
40
  if (!isQuietMinimalSync(options)) {
41
41
  logger.log(formatSuccessLine('Config unchanged on dataplane — upload skipped.'));
42
42
  }
43
- await runUploadSidecarFollowups({
43
+ const sidecar = await runUploadSidecarFollowups({
44
44
  systemKey,
45
45
  options,
46
46
  payload,
47
47
  dataplaneUrl,
48
- authConfig
48
+ authConfig,
49
+ environment
49
50
  });
50
51
  return finalizeUploadPublishResult(
51
- { dataplaneUrl, authConfig, environment, uploadSkipped: true },
52
+ { dataplaneUrl, authConfig, environment, uploadSkipped: true, sidecar },
52
53
  payload,
53
54
  options
54
55
  );