@aifabrix/builder 2.51.1 → 2.52.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 (187) hide show
  1. package/jest.isolated-projects.js +25 -2
  2. package/jest.projects.js +4 -1
  3. package/lib/api/datasources-extended.api.js +8 -8
  4. package/lib/api/keycloak.api.js +92 -0
  5. package/lib/api/token-providers.api.js +77 -0
  6. package/lib/api/types/keycloak.types.js +23 -0
  7. package/lib/api/types/token-providers.types.js +65 -0
  8. package/lib/app/down.js +16 -1
  9. package/lib/app/run-container-start.js +4 -0
  10. package/lib/app/run-deploy-job.js +135 -27
  11. package/lib/app/run-env-compose.js +32 -0
  12. package/lib/app/run-env-recovery.js +35 -0
  13. package/lib/app/run-helpers.js +5 -6
  14. package/lib/app/run.js +11 -3
  15. package/lib/channels/add-channel.js +126 -19
  16. package/lib/channels/approval-guides/chatgpt.js +12 -9
  17. package/lib/channels/approval-guides/microsoft-copilot.js +16 -12
  18. package/lib/channels/channel-add-ensure.js +128 -0
  19. package/lib/channels/channel-artifact-generator.js +11 -1
  20. package/lib/channels/channel-branding.js +94 -0
  21. package/lib/channels/channel-credential.js +8 -0
  22. package/lib/channels/channel-discovery.js +330 -0
  23. package/lib/channels/channel-external-system.js +1 -1
  24. package/lib/channels/channel-runtime-resolver.js +103 -14
  25. package/lib/channels/channel-status-display.js +7 -1
  26. package/lib/channels/channel-targets.js +18 -1
  27. package/lib/channels/chatgpt-runtime-contract.js +1 -1
  28. package/lib/channels/microsoft-copilot-channel-add.js +138 -0
  29. package/lib/channels/microsoft-copilot-entra-sso-form.js +180 -0
  30. package/lib/channels/microsoft-copilot-entra-sso.js +264 -0
  31. package/lib/channels/microsoft-copilot-icons.js +74 -0
  32. package/lib/channels/microsoft-copilot-limits.js +84 -0
  33. package/lib/channels/microsoft-copilot-package.js +130 -49
  34. package/lib/channels/microsoft-copilot-validate.js +197 -0
  35. package/lib/cli/index.js +2 -0
  36. package/lib/cli/infra-guided-footers.js +1 -1
  37. package/lib/cli/infra-guided-public-url-warn.js +55 -0
  38. package/lib/cli/infra-guided.js +78 -62
  39. package/lib/cli/setup-app.help.js +4 -0
  40. package/lib/cli/setup-app.test-commands.js +59 -28
  41. package/lib/cli/setup-external-system.js +50 -12
  42. package/lib/cli/setup-infra.js +31 -4
  43. package/lib/commands/channel-add-setup.js +35 -10
  44. package/lib/commands/channel.js +27 -12
  45. package/lib/commands/deploy-gate-orchestrator.js +3 -2
  46. package/lib/commands/governance-verify-external.js +2 -5
  47. package/lib/commands/integration-definition.js +158 -0
  48. package/lib/commands/lifecycle-external.js +14 -7
  49. package/lib/commands/repair-auth-apply.js +19 -5
  50. package/lib/commands/repair-datasource-sync-operations.js +4 -1
  51. package/lib/commands/role-assistant-test-output.js +200 -0
  52. package/lib/commands/role-assistant.js +131 -114
  53. package/lib/commands/test-governance-external.js +2 -5
  54. package/lib/commands/up-miso.js +61 -24
  55. package/lib/commands/upload-data-sync-finalize.js +20 -3
  56. package/lib/commands/verify-governance-external.js +2 -5
  57. package/lib/commands/verify-operations-external.js +2 -5
  58. package/lib/commands/verify-operations-step-plan.js +31 -0
  59. package/lib/commands/verify-trust-command-action.js +2 -5
  60. package/lib/datasource/capability/capability-resolve.js +15 -5
  61. package/lib/datasource/capability/copy-operations.js +14 -4
  62. package/lib/datasource/datasource-identifier.js +4 -3
  63. package/lib/datasource/integration-context.js +3 -3
  64. package/lib/datasource/resolve-app.js +90 -8
  65. package/lib/datasource/test-e2e.js +2 -2
  66. package/lib/datasource/unified-validation-run-body.js +15 -1
  67. package/lib/datasource/unified-validation-run-resolve.js +2 -2
  68. package/lib/external-system/deploy-helpers.js +9 -5
  69. package/lib/external-system/deploy.js +2 -1
  70. package/lib/external-system/test-system-level.js +21 -3
  71. package/lib/external-system/test.js +4 -4
  72. package/lib/generator/builders.js +9 -15
  73. package/lib/infrastructure/index.js +19 -4
  74. package/lib/integration-definition/apply.js +478 -0
  75. package/lib/integration-definition/canonical-json.js +51 -0
  76. package/lib/integration-definition/emit-suite-bid.js +94 -0
  77. package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
  78. package/lib/integration-definition/enrich-openapi-operations.js +434 -0
  79. package/lib/integration-definition/from-discovery.js +339 -0
  80. package/lib/integration-definition/hash.js +37 -0
  81. package/lib/integration-definition/index.js +37 -0
  82. package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +205 -0
  83. package/lib/integration-definition/resource-type-catalog.js +59 -0
  84. package/lib/integration-definition/system-kind-enum.js +40 -0
  85. package/lib/integration-definition/validate.js +148 -0
  86. package/lib/programmatic/builder-api-request.js +5 -1
  87. package/lib/programmatic/builder-help-governance.js +3 -2
  88. package/lib/programmatic/capability-registry.js +19 -0
  89. package/lib/programmatic/openapi-component-schemas.js +6 -1
  90. package/lib/programmatic/openapi-descriptions.js +40 -0
  91. package/lib/programmatic/openapi-envelope-examples.js +269 -0
  92. package/lib/programmatic/openapi-operation-bindings.js +45 -0
  93. package/lib/programmatic/openapi-request-schemas.js +14 -0
  94. package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
  95. package/lib/programmatic/openapi-result-schemas.js +46 -99
  96. package/lib/programmatic/openapi-schema-descriptions-core.js +7 -4
  97. package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
  98. package/lib/programmatic/openapi-schema-descriptions-operations.js +24 -0
  99. package/lib/programmatic/openapi-schema-helpers.js +9 -9
  100. package/lib/programmatic/openapi-spec.js +11 -1
  101. package/lib/programmatic/orchestration-openapi-meta.js +10 -0
  102. package/lib/programmatic/route-handler-map.js +29 -1
  103. package/lib/programmatic/run-channel-package.js +236 -0
  104. package/lib/programmatic/run-integration-definition.js +69 -0
  105. package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
  106. package/lib/role-assistant/knowledge-sync-logging.js +58 -0
  107. package/lib/role-assistant/knowledge-sync.js +104 -89
  108. package/lib/role-assistant/test-cases-bid.js +155 -0
  109. package/lib/role-assistant/test-cases-evaluate.js +9 -1
  110. package/lib/role-assistant/test-cases-honesty.js +24 -10
  111. package/lib/role-assistant/test-cases.js +28 -0
  112. package/lib/role-assistant/test-runner-bid-apply.js +122 -0
  113. package/lib/role-assistant/test-runner-bid-document.js +278 -0
  114. package/lib/role-assistant/test-runner-workhub-answers.js +289 -46
  115. package/lib/role-assistant/test-runner-workhub.js +121 -25
  116. package/lib/role-assistant/test-suite.js +119 -0
  117. package/lib/schema/application-schema.json +64 -32
  118. package/lib/schema/external-datasource.schema.json +103 -17
  119. package/lib/schema/external-system.schema.json +54 -3
  120. package/lib/schema/infra.parameter.yaml +0 -20
  121. package/lib/schema/integration-definition.schema.json +195 -0
  122. package/lib/utils/authentication-shape.js +6 -0
  123. package/lib/utils/certify-external-workspace.js +40 -0
  124. package/lib/utils/docker-build.js +3 -3
  125. package/lib/utils/docker-run-ephemeral.js +77 -17
  126. package/lib/utils/health-check-public-warn.js +31 -13
  127. package/lib/utils/health-check.js +11 -1
  128. package/lib/utils/help-builder.js +1 -0
  129. package/lib/utils/paths.js +13 -15
  130. package/lib/utils/promote-dev-docker-image.js +97 -18
  131. package/lib/utils/role-assistant-paths.js +76 -14
  132. package/lib/utils/transient-developer-id.js +28 -0
  133. package/lib/utils/upload-sync-options.js +7 -4
  134. package/lib/validation/reserved-system-key-prefixes.js +56 -0
  135. package/lib/validation/validate.js +54 -19
  136. package/package.json +1 -1
  137. package/scripts/pnpm/af.mjs +24 -9
  138. package/scripts/pnpm/help.mjs +1 -1
  139. package/templates/applications/builder-api/application.yaml +3 -3
  140. package/templates/applications/dataplane/application.yaml +5 -5
  141. package/templates/applications/dataplane/env.template +8 -7
  142. package/templates/applications/dataplane/rbac.yaml +10 -10
  143. package/templates/applications/keycloak/application.yaml +2 -2
  144. package/templates/applications/keycloak/env.template +5 -2
  145. package/templates/applications/miso-controller/application.yaml +25 -11
  146. package/templates/applications/miso-controller/env.template +24 -5
  147. package/templates/channels/README.md +11 -1
  148. package/templates/channels/brand/color-192.png +0 -0
  149. package/templates/channels/brand/dark/128.png +0 -0
  150. package/templates/channels/brand/dark/192.png +0 -0
  151. package/templates/channels/brand/dark/256.png +0 -0
  152. package/templates/channels/brand/dark/32.png +0 -0
  153. package/templates/channels/brand/dark/512.png +0 -0
  154. package/templates/channels/brand/dark/64.png +0 -0
  155. package/templates/channels/brand/light/128.png +0 -0
  156. package/templates/channels/brand/light/192.png +0 -0
  157. package/templates/channels/brand/light/256.png +0 -0
  158. package/templates/channels/brand/light/32.png +0 -0
  159. package/templates/channels/brand/light/512.png +0 -0
  160. package/templates/channels/brand/light/64.png +0 -0
  161. package/templates/channels/brand/mono-white/128.png +0 -0
  162. package/templates/channels/brand/mono-white/192.png +0 -0
  163. package/templates/channels/brand/mono-white/256.png +0 -0
  164. package/templates/channels/brand/mono-white/32.png +0 -0
  165. package/templates/channels/brand/mono-white/512.png +0 -0
  166. package/templates/channels/brand/mono-white/64.png +0 -0
  167. package/templates/channels/brand/outline-32.png +0 -0
  168. package/templates/channels/chatgpt/install.template.md.hbs +76 -36
  169. package/templates/channels/chatgpt/integration/README.md +4 -4
  170. package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
  171. package/templates/channels/chatgpt/manifest.template.json.hbs +3 -3
  172. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +10 -16
  173. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
  174. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +27 -0
  175. package/templates/channels/microsoft-copilot/install.template.md.hbs +39 -20
  176. package/templates/channels/microsoft-copilot/integration/README.md +2 -3
  177. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
  178. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +7 -7
  179. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +11 -0
  180. package/templates/channels/slack/install.template.md.hbs +6 -1
  181. package/templates/channels/slack/integration/README.md +2 -4
  182. package/templates/channels/teams-bot/install.template.md.hbs +4 -0
  183. package/templates/channels/teams-bot/integration/README.md +3 -5
  184. package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
  185. package/templates/python/docker-compose.hbs +10 -0
  186. package/templates/typescript/docker-compose.hbs +10 -0
  187. /package/.cursor/plans/{Done → Archive}/157.1-security-quality-violations-by-type.csv +0 -0
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Promote a developer-scoped local Docker image to the manifest base repository (tag only).
3
3
  *
4
- * @fileoverview Used by `aifabrix build --base --from-dev` and dev-promote workflows
4
+ * @fileoverview Used by `aifabrix build --base --from-dev` and dev-promote workflows.
5
+ * Promote always applies two base tags: `main` (floating) and the version tag (TAG / package version).
5
6
  * @author AI Fabrix Team
6
- * @version 1.0.0
7
+ * @version 1.1.0
7
8
  */
8
9
 
9
10
  'use strict';
@@ -15,6 +16,9 @@ const { execWithDockerEnv } = require('./docker-exec');
15
16
  const logger = require('./logger');
16
17
  const { formatSuccessLine } = require('./cli-test-layout-chalk');
17
18
 
19
+ /** Floating base tag applied on every promote alongside the version tag. */
20
+ const PROMOTE_MAIN_TAG = 'main';
21
+
18
22
  /**
19
23
  * @param {string} value
20
24
  * @returns {string}
@@ -61,6 +65,33 @@ async function dockerTagImageRef(sourceRef, targetRef) {
61
65
  await execWithDockerEnv(`docker tag ${shellQuote(sourceRef)} ${shellQuote(targetRef)}`);
62
66
  }
63
67
 
68
+ /**
69
+ * Version tag for promote (not floating aliases).
70
+ * @param {Object} options
71
+ * @returns {string}
72
+ */
73
+ function resolvePromoteVersionTag(options = {}) {
74
+ const raw = String(options.tag || '').trim();
75
+ if (!raw || raw === 'latest' || raw === PROMOTE_MAIN_TAG) {
76
+ return 'latest';
77
+ }
78
+ return raw;
79
+ }
80
+
81
+ /**
82
+ * Base tags to apply on promote: version first, then `main` when distinct.
83
+ * @param {Object} options
84
+ * @returns {string[]}
85
+ */
86
+ function resolvePromoteBaseTags(options = {}) {
87
+ const versionTag = resolvePromoteVersionTag(options);
88
+ const tags = [versionTag];
89
+ if (versionTag !== PROMOTE_MAIN_TAG) {
90
+ tags.push(PROMOTE_MAIN_TAG);
91
+ }
92
+ return tags;
93
+ }
94
+
64
95
  /**
65
96
  * @param {string} appName
66
97
  * @param {Object} variables
@@ -69,7 +100,7 @@ async function dockerTagImageRef(sourceRef, targetRef) {
69
100
  * @returns {Promise<{ devRef: string, baseRef: string, devTag: string, baseTag: string }>}
70
101
  */
71
102
  async function resolveDevAndBaseImageRefs(appName, variables, developerId, options) {
72
- const baseTag = options.tag || 'latest';
103
+ const baseTag = resolvePromoteVersionTag(options);
73
104
  const devTag = options.fromDevTag || 'latest';
74
105
  const { buildImageName: baseName } = await resolveBuildDockerTagNames(appName, variables, developerId, {
75
106
  ...options,
@@ -93,31 +124,68 @@ async function resolveDevAndBaseImageRefs(appName, variables, developerId, optio
93
124
  * @param {Object} appConfig
94
125
  * @param {string|number} developerId
95
126
  * @param {Object} options
96
- * @returns {Promise<{ devRef: string, baseRef: string }|null>}
127
+ * @returns {Promise<{
128
+ * devRef: string,
129
+ * baseRef: string,
130
+ * baseName: string,
131
+ * baseTags: string[]
132
+ * }|null>}
97
133
  */
98
134
  async function resolvePromoteSourceAndTargetRefs(appName, appConfig, developerId, options) {
99
- const baseTag = options.tag || 'latest';
135
+ const baseTags = resolvePromoteBaseTags(options);
136
+ const versionTag = baseTags[0];
100
137
  const devTagCandidates = ['latest'];
101
- if (baseTag !== 'latest' && !devTagCandidates.includes(baseTag)) {
102
- devTagCandidates.push(baseTag);
138
+ if (versionTag !== 'latest' && !devTagCandidates.includes(versionTag)) {
139
+ devTagCandidates.push(versionTag);
103
140
  }
104
141
  for (const fromDevTag of devTagCandidates) {
105
142
  const refs = await resolveDevAndBaseImageRefs(appName, appConfig, developerId, {
106
143
  ...options,
144
+ tag: versionTag,
107
145
  fromDevTag
108
146
  });
109
147
  if (await dockerImageRefExists(refs.devRef)) {
110
- return { devRef: refs.devRef, baseRef: refs.baseRef };
148
+ const baseName = refs.baseRef.slice(0, refs.baseRef.lastIndexOf(':'));
149
+ return {
150
+ devRef: refs.devRef,
151
+ baseRef: refs.baseRef,
152
+ baseName,
153
+ baseTags
154
+ };
111
155
  }
112
156
  }
113
157
  return null;
114
158
  }
115
159
 
160
+ /**
161
+ * @param {string} baseName
162
+ * @param {string[]} baseTags
163
+ * @param {string|null} devId
164
+ * @returns {Promise<boolean>} true when every target already matches dev image id
165
+ */
166
+ async function allBaseTagsMatchDev(baseName, baseTags, devId) {
167
+ if (!devId) {
168
+ return false;
169
+ }
170
+ for (const tag of baseTags) {
171
+ const ref = `${baseName}:${tag}`;
172
+ if (!(await dockerImageRefExists(ref))) {
173
+ return false;
174
+ }
175
+ const baseId = await readDockerImageId(ref);
176
+ if (!baseId || baseId !== devId) {
177
+ return false;
178
+ }
179
+ }
180
+ return true;
181
+ }
182
+
116
183
  /**
117
184
  * Tag developer-scoped image as manifest base when present locally.
185
+ * Always applies the version tag and `main`.
118
186
  * @param {string} appName
119
187
  * @param {Object} options - build options (`base`, `tag`, `registry`, `fromDev`)
120
- * @returns {Promise<string|null>} base image ref when promoted/skipped; null when dev image missing
188
+ * @returns {Promise<string|null>} primary base image ref (version) when promoted; null when missing
121
189
  */
122
190
  async function tryPromoteDevImageToBase(appName, options = {}) {
123
191
  if (options.base !== true) {
@@ -135,19 +203,27 @@ async function tryPromoteDevImageToBase(appName, options = {}) {
135
203
  if (!resolved) {
136
204
  return null;
137
205
  }
138
- const { devRef, baseRef } = resolved;
206
+ const { devRef, baseRef, baseName, baseTags } = resolved;
139
207
 
140
208
  const devId = await readDockerImageId(devRef);
141
- if (await dockerImageRefExists(baseRef)) {
142
- const baseId = await readDockerImageId(baseRef);
143
- if (devId && baseId && devId === baseId) {
144
- logger.log(formatSuccessLine(`Base image already matches dev build: ${baseRef}`));
145
- return baseRef;
146
- }
209
+ if (await allBaseTagsMatchDev(baseName, baseTags, devId)) {
210
+ logger.log(
211
+ formatSuccessLine(
212
+ `Base image already matches dev build: ${baseTags.map((t) => `${baseName}:${t}`).join(', ')}`
213
+ )
214
+ );
215
+ return baseRef;
147
216
  }
148
217
 
149
- await dockerTagImageRef(devRef, baseRef);
150
- logger.log(formatSuccessLine(`Promoted dev image to base (tag only): ${baseRef}`));
218
+ for (const tag of baseTags) {
219
+ const targetRef = `${baseName}:${tag}`;
220
+ await dockerTagImageRef(devRef, targetRef);
221
+ }
222
+ logger.log(
223
+ formatSuccessLine(
224
+ `Promoted dev image to base (tag only): ${baseTags.map((t) => `${baseName}:${t}`).join(', ')}`
225
+ )
226
+ );
151
227
  return baseRef;
152
228
  }
153
229
 
@@ -160,10 +236,13 @@ function shouldAttemptDevPromote(options) {
160
236
  }
161
237
 
162
238
  module.exports = {
239
+ PROMOTE_MAIN_TAG,
163
240
  tryPromoteDevImageToBase,
164
241
  shouldAttemptDevPromote,
165
242
  resolveDevAndBaseImageRefs,
166
243
  resolvePromoteSourceAndTargetRefs,
244
+ resolvePromoteBaseTags,
245
+ resolvePromoteVersionTag,
167
246
  dockerImageRefExists,
168
247
  readDockerImageId,
169
248
  dockerTagImageRef
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @fileoverview Paths under role-assistant/ for Role Assistant packages
5
5
  * @author AI Fabrix Team
6
- * @version 1.0.0
6
+ * @version 1.1.0
7
7
  */
8
8
 
9
9
  'use strict';
@@ -14,6 +14,7 @@ const { nodeFs } = require('../internal/node-fs');
14
14
 
15
15
  const WORKSPACE_ROLE_ASSISTANT = 'role-assistant';
16
16
  const SYSTEM_KIND_DIGITAL_WORKER = 'digitalWorker';
17
+ const ROLE_ASSISTANT_SYSTEM_PREFIX = 'ra-';
17
18
 
18
19
  function getMaterializationParent() {
19
20
  const work = process.env.AIFABRIX_WORK;
@@ -63,7 +64,64 @@ function isExistingDirectory(disk, dirPath) {
63
64
  }
64
65
 
65
66
  /**
66
- * Resolves role-assistant/<roleKey> with cwd-first behavior.
67
+ * Bare business roleKey (strips leading ra- if present).
68
+ * @param {string} roleKeyOrSystemKey
69
+ * @returns {string}
70
+ */
71
+ function bareRoleKey(roleKeyOrSystemKey) {
72
+ const raw = String(roleKeyOrSystemKey || '').trim();
73
+ if (raw.toLowerCase().startsWith(ROLE_ASSISTANT_SYSTEM_PREFIX)) {
74
+ return raw.slice(ROLE_ASSISTANT_SYSTEM_PREFIX.length);
75
+ }
76
+ return raw;
77
+ }
78
+
79
+ /**
80
+ * Folder / ExternalSystem key for a Role Assistant: ra-{roleKey}.
81
+ * @param {string} roleKeyOrSystemKey
82
+ * @returns {string}
83
+ */
84
+ function roleAssistantSystemKey(roleKeyOrSystemKey) {
85
+ const bare = bareRoleKey(roleKeyOrSystemKey);
86
+ if (!bare) {
87
+ return '';
88
+ }
89
+ return `${ROLE_ASSISTANT_SYSTEM_PREFIX}${bare}`;
90
+ }
91
+
92
+ /**
93
+ * Candidate folder names under role-assistant/ (prefer ra-, then legacy bare).
94
+ * @param {string} roleKeyOrSystemKey
95
+ * @returns {string[]}
96
+ */
97
+ function roleAssistantFolderCandidates(roleKeyOrSystemKey) {
98
+ const withRa = roleAssistantSystemKey(roleKeyOrSystemKey);
99
+ const bare = bareRoleKey(roleKeyOrSystemKey);
100
+ const out = [];
101
+ if (withRa) {
102
+ out.push(withRa);
103
+ }
104
+ if (bare && bare !== withRa) {
105
+ out.push(bare);
106
+ }
107
+ return out;
108
+ }
109
+
110
+ function firstExistingUnderRoot(disk, root, folderNames) {
111
+ if (!root) {
112
+ return null;
113
+ }
114
+ for (const name of folderNames) {
115
+ const candidate = path.join(root, name);
116
+ if (isExistingDirectory(disk, candidate)) {
117
+ return candidate;
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+
123
+ /**
124
+ * Resolves role-assistant/ra-{roleKey} (preferred) or legacy bare folder.
67
125
  * @param {string} roleKey
68
126
  * @returns {string}
69
127
  */
@@ -72,27 +130,31 @@ function getRoleAssistantPath(roleKey) {
72
130
  throw new Error('Role key is required and must be a string');
73
131
  }
74
132
  const disk = nodeFs();
133
+ const folders = roleAssistantFolderCandidates(roleKey);
134
+ const preferred = roleAssistantSystemKey(roleKey);
75
135
  const cwdRoot = getCwdRoleAssistantRoot();
76
- const matPath = path.join(getMaterializationParent(), WORKSPACE_ROLE_ASSISTANT, roleKey);
77
- if (cwdRoot) {
78
- const cwdPath = path.join(cwdRoot, roleKey);
79
- if (isExistingDirectory(disk, cwdPath)) {
80
- return cwdPath;
81
- }
82
- }
83
- if (isExistingDirectory(disk, matPath)) {
84
- return matPath;
136
+ const matRoot = path.join(getMaterializationParent(), WORKSPACE_ROLE_ASSISTANT);
137
+
138
+ const existing =
139
+ firstExistingUnderRoot(disk, cwdRoot, folders) ||
140
+ firstExistingUnderRoot(disk, matRoot, folders);
141
+ if (existing) {
142
+ return existing;
85
143
  }
86
144
  if (cwdRoot) {
87
- return path.join(cwdRoot, roleKey);
145
+ return path.join(cwdRoot, preferred);
88
146
  }
89
- return matPath;
147
+ return path.join(matRoot, preferred);
90
148
  }
91
149
 
92
150
  module.exports = {
93
151
  WORKSPACE_ROLE_ASSISTANT,
94
152
  SYSTEM_KIND_DIGITAL_WORKER,
153
+ ROLE_ASSISTANT_SYSTEM_PREFIX,
95
154
  getCwdRoleAssistantRoot,
96
155
  getRoleAssistantRoot,
97
- getRoleAssistantPath
156
+ getRoleAssistantPath,
157
+ bareRoleKey,
158
+ roleAssistantSystemKey,
159
+ roleAssistantFolderCandidates
98
160
  };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Transient developer-id override for CLI commands (does not persist config).
3
+ *
4
+ * @fileoverview Resolve optional --developer-id / --developer without setDeveloperId
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { validateAndNormalizeDeveloperId } = require('../core/config-normalize');
10
+
11
+ /**
12
+ * Resolve optional CLI developer override (transient — does not persist).
13
+ * Prefers developerId (--developer-id) over developer (-d/--developer).
14
+ *
15
+ * @param {Object} [options] - Commander options
16
+ * @returns {string|undefined} Normalized digit string, or undefined to use config
17
+ */
18
+ function resolveTransientDeveloperIdFromOptions(options) {
19
+ const raw = options && (options.developerId ?? options.developer);
20
+ if (raw === undefined || raw === null || raw === '') {
21
+ return undefined;
22
+ }
23
+ return validateAndNormalizeDeveloperId(raw);
24
+ }
25
+
26
+ module.exports = {
27
+ resolveTransientDeveloperIdFromOptions
28
+ };
@@ -10,7 +10,7 @@
10
10
 
11
11
  /**
12
12
  * @param {Object} [options] - CLI options from test / sync callers
13
- * @returns {{ minimal: true, verbose: boolean, silentResolve: true, syncMode: true, force?: true, authConfig?: Object, dataplaneUrl?: string, environment?: string }}
13
+ * @returns {{ minimal: true, verbose: boolean, silentResolve: boolean, syncMode: boolean, force?: true, authConfig?: Object, dataplaneUrl?: string, environment?: string }}
14
14
  */
15
15
  function buildMinimalUploadSyncOptions(options = {}) {
16
16
  const deployPreflight = options.uploadContext === 'deploy-preflight';
@@ -18,11 +18,14 @@ function buildMinimalUploadSyncOptions(options = {}) {
18
18
  minimal: true,
19
19
  // CLI -v on verify-* / test-* is for run output, not preflight upload banners.
20
20
  verbose: false,
21
- silentResolve: true,
22
- syncMode: true,
21
+ // Deploy preflight should match `af upload` progress (URL + Publishing spinner).
22
+ // Verify/test --sync stays silent so parent command owns the progress line.
23
+ silentResolve: !deployPreflight,
24
+ syncMode: !deployPreflight,
23
25
  skipDataSync: true,
26
+ // Explicit suppress wins; deploy-preflight never suppresses the Publishing spinner.
24
27
  suppressPipelineSpinner:
25
- options.suppressPipelineSpinner === true || deployPreflight === true
28
+ options.suppressPipelineSpinner === true && !deployPreflight
26
29
  };
27
30
  if (options.force === true) {
28
31
  out.force = true;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Frozen ExternalSystem key prefixes (Role Assistant / Enterprise Knowledge).
3
+ *
4
+ * @fileoverview Reject hand-authored ra-/ek- keys under integration/
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const RESERVED_SYSTEM_KEY_PREFIXES = ['ra-', 'ek-'];
12
+
13
+ /**
14
+ * @param {string|null|undefined} systemKey
15
+ * @returns {boolean}
16
+ */
17
+ function hasReservedPlatformSystemKeyPrefix(systemKey) {
18
+ const normalized = String(systemKey || '')
19
+ .trim()
20
+ .toLowerCase();
21
+ if (!normalized) {
22
+ return false;
23
+ }
24
+ return RESERVED_SYSTEM_KEY_PREFIXES.some((prefix) => normalized.startsWith(prefix));
25
+ }
26
+
27
+ /**
28
+ * Connected Systems under integration/ must not use reserved prefixes.
29
+ * Role Assistant packages live under role-assistant/ra-*.
30
+ *
31
+ * @param {string|null|undefined} systemKey
32
+ * @param {string|null|undefined} workspaceBaseDir - integration | role-assistant | …
33
+ * @returns {string|null} Error message or null when allowed
34
+ */
35
+ function reservedSystemKeyError(systemKey, workspaceBaseDir) {
36
+ if (!hasReservedPlatformSystemKeyPrefix(systemKey)) {
37
+ return null;
38
+ }
39
+ const workspace = String(workspaceBaseDir || '')
40
+ .trim()
41
+ .toLowerCase();
42
+ if (workspace === 'role-assistant' || workspace === 'enterprise-knowledge') {
43
+ return null;
44
+ }
45
+ return (
46
+ `System key '${systemKey}' uses a reserved prefix (ra- or ek-). ` +
47
+ 'Role Assistant packages use role-assistant/ra-{roleKey}/; ' +
48
+ 'Enterprise Knowledge uses ek- keys from platform generators only.'
49
+ );
50
+ }
51
+
52
+ module.exports = {
53
+ RESERVED_SYSTEM_KEY_PREFIXES,
54
+ hasReservedPlatformSystemKeyPrefix,
55
+ reservedSystemKeyError
56
+ };
@@ -22,6 +22,7 @@ const { generateControllerManifest } = require('../generator/external-controller
22
22
  const { validateControllerManifest } = require('./external-manifest-validator');
23
23
  const { runExternalValidateWithOptionalCertSync } = require('./validate-external-cert-sync');
24
24
  const { validateExternalFile } = require('./validate-external-file');
25
+ const { reservedSystemKeyError } = require('./reserved-system-key-prefixes');
25
26
 
26
27
  /**
27
28
  * Validates a file path (detects type and validates)
@@ -276,6 +277,52 @@ async function validateManifestStep(appName, options = {}) {
276
277
  }
277
278
  }
278
279
 
280
+ /**
281
+ * Early-fail payload when system key uses a reserved prefix.
282
+ * @param {string} reservedError
283
+ * @param {string} appPath
284
+ * @returns {Object}
285
+ */
286
+ function reservedSystemKeyValidationResult(reservedError, appPath) {
287
+ return {
288
+ valid: false,
289
+ errors: [reservedError],
290
+ warnings: [],
291
+ steps: {
292
+ application: { valid: false, errors: [reservedError], warnings: [] },
293
+ components: { valid: true, errors: [], warnings: [], files: [], skipped: true },
294
+ manifest: { valid: true, errors: [], warnings: [], skipped: true }
295
+ },
296
+ appPath
297
+ };
298
+ }
299
+
300
+ /**
301
+ * Aggregate step results into the complete validation response.
302
+ * @param {Object} steps
303
+ * @param {string} appPath
304
+ * @returns {Object}
305
+ */
306
+ function aggregateExternalValidationSteps(steps, appPath) {
307
+ const allErrors = [
308
+ ...steps.application.errors,
309
+ ...steps.components.errors,
310
+ ...steps.manifest.errors
311
+ ];
312
+ const allWarnings = [
313
+ ...steps.application.warnings,
314
+ ...steps.components.warnings,
315
+ ...steps.manifest.warnings
316
+ ];
317
+ return {
318
+ valid: allErrors.length === 0,
319
+ errors: allErrors,
320
+ warnings: allWarnings,
321
+ steps,
322
+ appPath
323
+ };
324
+ }
325
+
279
326
  /**
280
327
  * Validates external system completely (components + full manifest)
281
328
  * Performs step-by-step validation: application config → components → full manifest
@@ -295,7 +342,12 @@ async function validateExternalSystemComplete(appName, options = {}) {
295
342
  throw new Error('App name is required and must be a string');
296
343
  }
297
344
 
298
- const { appPath } = await detectAppType(appName, options);
345
+ const { appPath, baseDir } = await detectAppType(appName, options);
346
+ const reservedError = reservedSystemKeyError(appName, baseDir);
347
+ if (reservedError) {
348
+ return reservedSystemKeyValidationResult(reservedError, appPath);
349
+ }
350
+
299
351
  const steps = {
300
352
  application: { valid: false, errors: [], warnings: [] },
301
353
  components: { valid: false, errors: [], warnings: [], files: [] },
@@ -303,14 +355,9 @@ async function validateExternalSystemComplete(appName, options = {}) {
303
355
  };
304
356
 
305
357
  const externalOptions = { ...options };
306
-
307
- // Step 1: Validate Application Config
308
358
  steps.application = await validateApplicationStep(appName, externalOptions);
309
-
310
- // Step 2: Validate Individual Components
311
359
  steps.components = await validateComponentsStep(appName, externalOptions);
312
360
 
313
- // If components have errors, return early (don't validate manifest)
314
361
  if (!steps.components.valid) {
315
362
  steps.manifest = { valid: true, errors: [], warnings: [], skipped: true };
316
363
  return {
@@ -322,20 +369,8 @@ async function validateExternalSystemComplete(appName, options = {}) {
322
369
  };
323
370
  }
324
371
 
325
- // Step 3 & 4: Generate and Validate Full Manifest (only if Step 2 passes)
326
372
  steps.manifest = await validateManifestStep(appName, { ...externalOptions, appPath });
327
-
328
- // Aggregate Results
329
- const allErrors = [...steps.application.errors, ...steps.components.errors, ...steps.manifest.errors];
330
- const allWarnings = [...steps.application.warnings, ...steps.components.warnings, ...steps.manifest.warnings];
331
-
332
- return {
333
- valid: allErrors.length === 0,
334
- errors: allErrors,
335
- warnings: allWarnings,
336
- steps,
337
- appPath
338
- };
373
+ return aggregateExternalValidationSteps(steps, appPath);
339
374
  }
340
375
 
341
376
  async function validateAppOrFile(appOrFile, options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.51.1",
3
+ "version": "2.52.1",
4
4
  "description": "AI Fabrix Builder — CLI and developer scripts (pnpm + af)",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -4,9 +4,13 @@
4
4
  * Usage: pnpm run af <subcommand> | npm run af -- <subcommand>
5
5
  */
6
6
  import { spawn } from 'node:child_process';
7
+ import { readFileSync } from 'node:fs';
8
+ import { dirname, join } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
7
10
  import { resolveAifabrixBin } from './lib/resolve-aifabrix-bin.mjs';
8
11
 
9
12
  const AIFABRIX_BIN = resolveAifabrixBin();
13
+ const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '../..');
10
14
 
11
15
  const USAGE = `Usage: pnpm run af <subcommand>
12
16
 
@@ -19,7 +23,7 @@ Subcommands:
19
23
  ba-build aifabrix build builder-api
20
24
  dev-build ba-build then ba-run (local Docker image)
21
25
  dev-reload Docker live reload (spinner + URL + .temp/dev-reload.log)
22
- dev-promote build --base --from-dev + run --base (global image; TAG=1.9.5 pnpm run dev-promote)
26
+ dev-promote build --base --from-dev + run --base (tags main + version; TAG=2.52.0 or package.json)
23
27
  `;
24
28
 
25
29
  const COMMANDS = {
@@ -36,10 +40,20 @@ async function runDevBuild() {
36
40
  await runCommand(COMMANDS['ba-run']);
37
41
  }
38
42
 
43
+ function readPackageVersion() {
44
+ try {
45
+ const pkg = JSON.parse(readFileSync(join(REPO_ROOT, 'package.json'), 'utf8'));
46
+ const version = String(pkg.version || '').trim();
47
+ return version || null;
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+
39
53
  function resolvePromoteTag() {
40
54
  const fromArg = process.argv[3];
41
- const tag = (process.env.TAG || fromArg || '').trim();
42
- if (!tag || tag === 'latest') {
55
+ const tag = (process.env.TAG || fromArg || readPackageVersion() || '').trim();
56
+ if (!tag || tag === 'latest' || tag === 'main') {
43
57
  return null;
44
58
  }
45
59
  return tag;
@@ -47,13 +61,14 @@ function resolvePromoteTag() {
47
61
 
48
62
  async function runDevPromote() {
49
63
  const tag = resolvePromoteTag();
50
- if (tag) {
51
- await runCommand([AIFABRIX_BIN, 'build', 'builder-api', '--base', '--from-dev', '--tag', tag]);
52
- await runCommand([AIFABRIX_BIN, 'run', 'builder-api', '--base', '--tag', tag]);
53
- return;
64
+ if (!tag) {
65
+ throw new Error(
66
+ 'dev-promote requires a version tag. Set TAG=x.y.z or ensure package.json has "version".'
67
+ );
54
68
  }
55
- await runCommand([AIFABRIX_BIN, 'build', 'builder-api', '--base', '--from-dev']);
56
- await runCommand([AIFABRIX_BIN, 'run', 'builder-api', '--base']);
69
+ // Promote applies both :main and :<version>; run uses the version tag.
70
+ await runCommand([AIFABRIX_BIN, 'build', 'builder-api', '--base', '--from-dev', '--tag', tag]);
71
+ await runCommand([AIFABRIX_BIN, 'run', 'builder-api', '--base', '--tag', tag]);
57
72
  }
58
73
 
59
74
  function runCommand(args) {
@@ -25,7 +25,7 @@ const publicSections = [
25
25
 
26
26
  const advancedLines = [
27
27
  '--- Advanced (help -- --all) ---',
28
- 'dev-build / dev-promote / af ba-*',
28
+ 'dev-build / dev-promote (tags main + version) / af ba-*',
29
29
  'build:ci → lint + schema-sync + flags + test:ci (same as GitHub)',
30
30
  'sync-json → publish builder/builder-api → templates/',
31
31
  'test:154 → builder-api scoped Jest + arch grep',
@@ -2,7 +2,7 @@
2
2
  app:
3
3
  key: builder-api
4
4
  displayName: 'AI Fabrix Builder API'
5
- description: 'Stateless HTTP orchestration for external-system workspace lifecycle. Provider-neutral integration flows via Dataplane Enterprise MCP and governance APIs (154.0 / 154.1).'
5
+ description: 'Business Transformation services — HTTP orchestration for connected-system workspace lifecycle via Dataplane Enterprise MCP and governance APIs.'
6
6
  type: webapp
7
7
  language: typescript
8
8
  version: 1.0.0
@@ -11,8 +11,8 @@ app:
11
11
  image:
12
12
  name: aifabrix/builder-api
13
13
  tag: latest
14
- registry: aifabrixdevacr.azurecr.io
15
- registryMode: acr
14
+ registry: aifabrixacr.azurecr.io
15
+ registryMode: external
16
16
 
17
17
  # Port Configuration (container port; host port = port + developer_id * 100)
18
18
  port: 3005
@@ -5,16 +5,15 @@ app:
5
5
  description: "AI Fabrix Dataplane is a secure, in-tenant integration and automation layer that supplies governed, normalized, and explainable enterprise data to AI agents. Using CIP as a declarative standard, it enforces RBAC and ABAC, executes integrations, and exposes trusted data via MCP and OpenAPI."
6
6
  type: webapp
7
7
  language: python # Explicitly specify Python language
8
- version: 2.0.15
9
-
8
+ version: 2.0.26
10
9
  # Image Configuration
11
10
  # Set tag to match your build (example: aifabrix build dataplane -t 1.0.0).
12
11
  # Registry is required so the controller can pull the image (avoids docker-not-found on the controller host).
13
12
  image:
14
13
  name: aifabrix/dataplane
15
14
  tag: latest
16
- registry: aifabrixdevacr.azurecr.io
17
- registryMode: acr
15
+ registry: aifabrixacr.azurecr.io
16
+ registryMode: external
18
17
 
19
18
  # Port Configuration
20
19
  port: 3001
@@ -89,7 +88,8 @@ build:
89
88
  # - MISO_*, KEYCLOAK_* → auto-generated during Azure install
90
89
  # - AI params / provider secrets → LLM external systems + secret store
91
90
  # (never product OPENAI_* / AZURE_OPENAI_*). ≥2 providers →
92
- # ENTERPRISE_LLM_DEFAULT_SELECTOR (portal LLM category).
91
+ # ENTERPRISE_LLM_DEFAULT_SELECTOR (portal/env deploy default); Environment
92
+ # Settings ai.llmProvider overrides without redeploy.
93
93
  # - ENVIRONMENT → set by deployment target
94
94
 
95
95
  configuration: