@aifabrix/builder 2.52.0 → 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 (134) 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/channels/add-channel.js +126 -19
  10. package/lib/channels/approval-guides/chatgpt.js +1 -1
  11. package/lib/channels/approval-guides/microsoft-copilot.js +16 -12
  12. package/lib/channels/channel-add-ensure.js +128 -0
  13. package/lib/channels/channel-artifact-generator.js +11 -1
  14. package/lib/channels/channel-branding.js +94 -0
  15. package/lib/channels/channel-credential.js +8 -0
  16. package/lib/channels/channel-discovery.js +330 -0
  17. package/lib/channels/channel-external-system.js +1 -1
  18. package/lib/channels/channel-runtime-resolver.js +103 -14
  19. package/lib/channels/channel-status-display.js +7 -1
  20. package/lib/channels/microsoft-copilot-channel-add.js +138 -0
  21. package/lib/channels/microsoft-copilot-entra-sso-form.js +180 -0
  22. package/lib/channels/microsoft-copilot-entra-sso.js +264 -0
  23. package/lib/channels/microsoft-copilot-icons.js +74 -0
  24. package/lib/channels/microsoft-copilot-limits.js +84 -0
  25. package/lib/channels/microsoft-copilot-package.js +130 -49
  26. package/lib/channels/microsoft-copilot-validate.js +197 -0
  27. package/lib/cli/index.js +2 -0
  28. package/lib/cli/infra-guided.js +13 -6
  29. package/lib/cli/setup-infra.js +31 -4
  30. package/lib/commands/channel-add-setup.js +28 -9
  31. package/lib/commands/channel.js +14 -10
  32. package/lib/commands/integration-definition.js +158 -0
  33. package/lib/commands/repair-auth-apply.js +19 -5
  34. package/lib/commands/repair-datasource-sync-operations.js +4 -1
  35. package/lib/commands/role-assistant-test-output.js +200 -0
  36. package/lib/commands/role-assistant.js +125 -166
  37. package/lib/datasource/capability/capability-resolve.js +15 -5
  38. package/lib/datasource/capability/copy-operations.js +14 -4
  39. package/lib/infrastructure/index.js +19 -4
  40. package/lib/integration-definition/apply.js +478 -0
  41. package/lib/integration-definition/canonical-json.js +51 -0
  42. package/lib/integration-definition/emit-suite-bid.js +94 -0
  43. package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
  44. package/lib/integration-definition/enrich-openapi-operations.js +434 -0
  45. package/lib/integration-definition/from-discovery.js +339 -0
  46. package/lib/integration-definition/hash.js +37 -0
  47. package/lib/integration-definition/index.js +37 -0
  48. package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +205 -0
  49. package/lib/integration-definition/resource-type-catalog.js +59 -0
  50. package/lib/integration-definition/system-kind-enum.js +40 -0
  51. package/lib/integration-definition/validate.js +148 -0
  52. package/lib/programmatic/builder-api-request.js +5 -1
  53. package/lib/programmatic/builder-help-governance.js +3 -2
  54. package/lib/programmatic/capability-registry.js +19 -0
  55. package/lib/programmatic/openapi-component-schemas.js +6 -1
  56. package/lib/programmatic/openapi-descriptions.js +40 -0
  57. package/lib/programmatic/openapi-envelope-examples.js +269 -0
  58. package/lib/programmatic/openapi-operation-bindings.js +45 -0
  59. package/lib/programmatic/openapi-request-schemas.js +14 -0
  60. package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
  61. package/lib/programmatic/openapi-result-schemas.js +46 -99
  62. package/lib/programmatic/openapi-schema-descriptions-core.js +7 -4
  63. package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
  64. package/lib/programmatic/openapi-schema-descriptions-operations.js +24 -0
  65. package/lib/programmatic/openapi-schema-helpers.js +9 -9
  66. package/lib/programmatic/openapi-spec.js +11 -1
  67. package/lib/programmatic/orchestration-openapi-meta.js +10 -0
  68. package/lib/programmatic/route-handler-map.js +29 -1
  69. package/lib/programmatic/run-channel-package.js +236 -0
  70. package/lib/programmatic/run-integration-definition.js +69 -0
  71. package/lib/role-assistant/knowledge-sync-logging.js +58 -0
  72. package/lib/role-assistant/knowledge-sync.js +88 -93
  73. package/lib/role-assistant/test-cases-bid.js +155 -0
  74. package/lib/role-assistant/test-cases-evaluate.js +9 -1
  75. package/lib/role-assistant/test-cases-honesty.js +24 -10
  76. package/lib/role-assistant/test-cases.js +28 -0
  77. package/lib/role-assistant/test-runner-bid-apply.js +122 -0
  78. package/lib/role-assistant/test-runner-bid-document.js +278 -0
  79. package/lib/role-assistant/test-runner-workhub-answers.js +245 -20
  80. package/lib/role-assistant/test-runner-workhub.js +100 -18
  81. package/lib/role-assistant/test-suite.js +119 -0
  82. package/lib/schema/application-schema.json +56 -6
  83. package/lib/schema/external-datasource.schema.json +103 -17
  84. package/lib/schema/external-system.schema.json +54 -3
  85. package/lib/schema/integration-definition.schema.json +195 -0
  86. package/lib/utils/authentication-shape.js +6 -0
  87. package/lib/utils/docker-build.js +3 -3
  88. package/lib/utils/help-builder.js +1 -0
  89. package/lib/utils/promote-dev-docker-image.js +97 -18
  90. package/lib/utils/transient-developer-id.js +28 -0
  91. package/package.json +1 -1
  92. package/scripts/pnpm/af.mjs +24 -9
  93. package/scripts/pnpm/help.mjs +1 -1
  94. package/templates/applications/dataplane/application.yaml +3 -2
  95. package/templates/applications/dataplane/env.template +7 -6
  96. package/templates/applications/miso-controller/application.yaml +23 -9
  97. package/templates/applications/miso-controller/env.template +8 -2
  98. package/templates/channels/README.md +11 -1
  99. package/templates/channels/brand/color-192.png +0 -0
  100. package/templates/channels/brand/dark/128.png +0 -0
  101. package/templates/channels/brand/dark/192.png +0 -0
  102. package/templates/channels/brand/dark/256.png +0 -0
  103. package/templates/channels/brand/dark/32.png +0 -0
  104. package/templates/channels/brand/dark/512.png +0 -0
  105. package/templates/channels/brand/dark/64.png +0 -0
  106. package/templates/channels/brand/light/128.png +0 -0
  107. package/templates/channels/brand/light/192.png +0 -0
  108. package/templates/channels/brand/light/256.png +0 -0
  109. package/templates/channels/brand/light/32.png +0 -0
  110. package/templates/channels/brand/light/512.png +0 -0
  111. package/templates/channels/brand/light/64.png +0 -0
  112. package/templates/channels/brand/mono-white/128.png +0 -0
  113. package/templates/channels/brand/mono-white/192.png +0 -0
  114. package/templates/channels/brand/mono-white/256.png +0 -0
  115. package/templates/channels/brand/mono-white/32.png +0 -0
  116. package/templates/channels/brand/mono-white/512.png +0 -0
  117. package/templates/channels/brand/mono-white/64.png +0 -0
  118. package/templates/channels/brand/outline-32.png +0 -0
  119. package/templates/channels/chatgpt/install.template.md.hbs +6 -4
  120. package/templates/channels/chatgpt/integration/README.md +4 -5
  121. package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
  122. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +10 -16
  123. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
  124. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +27 -0
  125. package/templates/channels/microsoft-copilot/install.template.md.hbs +39 -20
  126. package/templates/channels/microsoft-copilot/integration/README.md +2 -3
  127. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
  128. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +7 -7
  129. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +11 -0
  130. package/templates/channels/slack/integration/README.md +2 -4
  131. package/templates/channels/teams-bot/install.template.md.hbs +4 -0
  132. package/templates/channels/teams-bot/integration/README.md +3 -5
  133. package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
  134. /package/.cursor/plans/{Done → Archive}/157.1-security-quality-violations-by-type.csv +0 -0
@@ -0,0 +1,195 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "aifabrix://schema/integration-definition.schema.json",
4
+ "title": "AI Fabrix Builder Integration Definition (BID)",
5
+ "description": "Canonical BT → Builder integration definition. Schema name aifabrix.builder.integrationDefinition v1.0.0.",
6
+ "metadata": {
7
+ "key": "integration-definition-schema",
8
+ "name": "Builder Integration Definition",
9
+ "version": "1.0.0",
10
+ "schemaName": "aifabrix.builder.integrationDefinition",
11
+ "type": "schema",
12
+ "category": "integration",
13
+ "author": "AI Fabrix Team",
14
+ "createdAt": "2026-08-04T00:00:00Z",
15
+ "updatedAt": "2026-08-04T00:00:00Z"
16
+ },
17
+ "type": "object",
18
+ "additionalProperties": false,
19
+ "required": [
20
+ "schemaName",
21
+ "schemaVersion",
22
+ "definitionId",
23
+ "definitionHash",
24
+ "meta",
25
+ "packageIdentity",
26
+ "system",
27
+ "datasources",
28
+ "relationships",
29
+ "sync",
30
+ "capabilities",
31
+ "authority",
32
+ "acceptance"
33
+ ],
34
+ "properties": {
35
+ "schemaName": {
36
+ "type": "string",
37
+ "const": "aifabrix.builder.integrationDefinition"
38
+ },
39
+ "schemaVersion": {
40
+ "type": "string",
41
+ "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
42
+ "description": "BID schema semver; implementers must accept 1.0.0."
43
+ },
44
+ "definitionId": {
45
+ "type": "string",
46
+ "format": "uuid"
47
+ },
48
+ "definitionHash": {
49
+ "type": "string",
50
+ "pattern": "^sha256:[a-f0-9]{64}$",
51
+ "description": "SHA-256 of canonicalized BID body excluding this field."
52
+ },
53
+ "meta": {
54
+ "type": "object",
55
+ "additionalProperties": false,
56
+ "required": ["suiteRunId", "producedAt", "btAssistantKey"],
57
+ "properties": {
58
+ "suiteRunId": { "type": "string", "minLength": 1 },
59
+ "producedAt": { "type": "string", "format": "date-time" },
60
+ "btAssistantKey": { "type": "string", "minLength": 1 },
61
+ "answerFingerprint": { "type": "string" },
62
+ "knowledgePackRefs": {
63
+ "type": "array",
64
+ "items": { "type": "string", "minLength": 1 }
65
+ },
66
+ "humanAnswerRefs": {
67
+ "type": "array",
68
+ "items": { "type": "string", "minLength": 1 }
69
+ },
70
+ "replaceSystemKey": { "type": "boolean", "default": false }
71
+ }
72
+ },
73
+ "packageIdentity": {
74
+ "type": "object",
75
+ "additionalProperties": false,
76
+ "required": ["systemKey", "packageVersion", "displayName"],
77
+ "properties": {
78
+ "systemKey": { "type": "string", "pattern": "^[a-z0-9-]+$", "minLength": 3 },
79
+ "packageVersion": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
80
+ "displayName": { "type": "string", "minLength": 1 }
81
+ }
82
+ },
83
+ "system": {
84
+ "type": "object",
85
+ "additionalProperties": true,
86
+ "required": ["key", "systemKind", "connectionRefs"],
87
+ "properties": {
88
+ "key": { "type": "string", "pattern": "^[a-z0-9-]+$" },
89
+ "displayName": { "type": "string" },
90
+ "description": { "type": "string" },
91
+ "systemKind": { "type": "string", "minLength": 1 },
92
+ "type": { "type": "string", "enum": ["openapi", "mcp", "custom"] },
93
+ "connectionRefs": {
94
+ "type": "object",
95
+ "description": "kv:// or credential key refs only — no plaintext secrets.",
96
+ "additionalProperties": { "type": "string", "minLength": 1 }
97
+ },
98
+ "openapiSourceRef": { "type": "string" }
99
+ }
100
+ },
101
+ "datasources": {
102
+ "type": "array",
103
+ "minItems": 1,
104
+ "items": {
105
+ "type": "object",
106
+ "additionalProperties": true,
107
+ "required": ["key", "displayName", "entityType", "resourceType"],
108
+ "properties": {
109
+ "key": { "type": "string", "pattern": "^[a-z0-9-]+$", "minLength": 3 },
110
+ "displayName": { "type": "string", "minLength": 1 },
111
+ "entityType": {
112
+ "type": "string",
113
+ "enum": [
114
+ "documentStorage",
115
+ "vectorStore",
116
+ "recordStorage",
117
+ "messageService",
118
+ "serviceContract",
119
+ "none"
120
+ ]
121
+ },
122
+ "resourceType": { "type": "string", "pattern": "^[a-z0-9-]+$" },
123
+ "capabilityKeys": {
124
+ "type": "array",
125
+ "items": { "type": "string", "pattern": "^[a-z][a-zA-Z0-9]*$" }
126
+ },
127
+ "propertyKeys": {
128
+ "type": "array",
129
+ "description": "Vendor property names to sync/index (CRM objects → CIP query.properties + fieldMappings).",
130
+ "items": { "type": "string", "pattern": "^[a-zA-Z][a-zA-Z0-9_]*$", "minLength": 1 }
131
+ },
132
+ "openapiSourceRef": { "type": "string" }
133
+ }
134
+ }
135
+ },
136
+ "relationships": {
137
+ "type": "array",
138
+ "items": {
139
+ "type": "object",
140
+ "additionalProperties": true,
141
+ "required": ["from", "to", "kind"],
142
+ "properties": {
143
+ "from": { "type": "string", "minLength": 1 },
144
+ "to": { "type": "string", "minLength": 1 },
145
+ "kind": { "type": "string", "minLength": 1 }
146
+ }
147
+ }
148
+ },
149
+ "sync": {
150
+ "type": "object",
151
+ "additionalProperties": true,
152
+ "required": ["mode"],
153
+ "properties": {
154
+ "mode": {
155
+ "type": "string",
156
+ "enum": ["incremental"],
157
+ "description": "Incremental sync is mandatory for CRM profile datasources."
158
+ },
159
+ "datasources": {
160
+ "type": "array",
161
+ "items": { "type": "string", "pattern": "^[a-z0-9-]+$" }
162
+ }
163
+ }
164
+ },
165
+ "capabilities": {
166
+ "type": "object",
167
+ "additionalProperties": {
168
+ "type": "array",
169
+ "items": { "type": "string", "pattern": "^[a-z][a-zA-Z0-9]*$" }
170
+ },
171
+ "description": "Map of datasource key → capability keys (must include named write for company DS)."
172
+ },
173
+ "authority": {
174
+ "type": "object",
175
+ "additionalProperties": true,
176
+ "required": ["writeRequiresUserAuthority"],
177
+ "properties": {
178
+ "writeRequiresUserAuthority": { "type": "boolean" },
179
+ "notes": { "type": "string" }
180
+ }
181
+ },
182
+ "acceptance": {
183
+ "type": "object",
184
+ "additionalProperties": true,
185
+ "required": ["gates"],
186
+ "properties": {
187
+ "gates": {
188
+ "type": "array",
189
+ "minItems": 1,
190
+ "items": { "type": "string", "minLength": 1 }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
@@ -110,6 +110,9 @@ function buildAuthenticationFromMethod(systemKey, authType) {
110
110
  if (method === 'none') {
111
111
  return { authType: 'none' };
112
112
  }
113
+ if (method === 'system') {
114
+ return { authType: 'system' };
115
+ }
113
116
  const flat = buildFlatCredentialTemplate(systemKey, method);
114
117
  return {
115
118
  authType: 'credential',
@@ -174,6 +177,9 @@ function migrateAuthenticationToNestedSchema(authentication, systemKey) {
174
177
  if (authentication.authType === 'none') {
175
178
  return { authentication: { authType: 'none' }, changed: false };
176
179
  }
180
+ if (authentication.authType === 'system') {
181
+ return { authentication: { authType: 'system' }, changed: false };
182
+ }
177
183
  if (authentication.authType === 'apiProvider' && authentication.apiProvider) {
178
184
  return { authentication, changed: false };
179
185
  }
@@ -253,12 +253,12 @@ async function executeBuild(imageName, dockerfilePath, contextPath, tag, options
253
253
  );
254
254
  await executeDockerBuild(imageName, dockerfilePath, contextPath, tag, buildArgs, noCache);
255
255
 
256
- // Tag image if additional tag provided
257
- if (options && options.tag && options.tag !== 'latest') {
256
+ // Also apply floating `main` when building a version tag (matches dev-promote dual tags).
257
+ if (options && options.tag && options.tag !== 'latest' && options.tag !== 'main') {
258
258
  const { execFileWithDockerEnv } = require('./docker-exec');
259
259
  const { getDockerExecEnv } = require('./remote-docker-env');
260
260
  const env = await getDockerExecEnv();
261
- await execFileWithDockerEnv('docker', ['tag', `${imageName}:${tag}`, `${imageName}:latest`], { env });
261
+ await execFileWithDockerEnv('docker', ['tag', `${imageName}:${tag}`, `${imageName}:main`], { env });
262
262
  }
263
263
  }
264
264
 
@@ -111,6 +111,7 @@ const CATEGORIES = [
111
111
  { name: 'delete', term: 'delete <systemKey>' },
112
112
  { name: 'repair', term: 'repair <systemKey>' },
113
113
  { name: 'manifest', term: 'manifest get-section <systemKey> <sectionKey>' },
114
+ { name: 'integration-definition', term: 'integration-definition apply <file>' },
114
115
  { name: 'channel', term: 'channel scaffold <channelTarget>' },
115
116
  { name: 'datasource' },
116
117
  { name: 'datasource load', term: 'datasource load <datasourceKey>' },
@@ -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
@@ -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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.52.0",
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',
@@ -5,7 +5,7 @@ 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.20
8
+ version: 2.0.26
9
9
  # Image Configuration
10
10
  # Set tag to match your build (example: aifabrix build dataplane -t 1.0.0).
11
11
  # Registry is required so the controller can pull the image (avoids docker-not-found on the controller host).
@@ -88,7 +88,8 @@ build:
88
88
  # - MISO_*, KEYCLOAK_* → auto-generated during Azure install
89
89
  # - AI params / provider secrets → LLM external systems + secret store
90
90
  # (never product OPENAI_* / AZURE_OPENAI_*). ≥2 providers →
91
- # ENTERPRISE_LLM_DEFAULT_SELECTOR (portal LLM category).
91
+ # ENTERPRISE_LLM_DEFAULT_SELECTOR (portal/env deploy default); Environment
92
+ # Settings ai.llmProvider overrides without redeploy.
92
93
  # - ENVIRONMENT → set by deployment target
93
94
 
94
95
  configuration:
@@ -44,7 +44,7 @@ API_KEY_BYPASS_ROLES=full-access
44
44
 
45
45
  # API Configuration
46
46
  API_V1_STR=/api/v1
47
- VERSION=2.0.20
47
+ VERSION=2.0.26
48
48
  # Minimum supported aifabrix-builder CLI semver (advertised on /health and /api/v1/health; empty = no gate)
49
49
  # MIN_BUILDER_CLI_VERSION=2.45.0
50
50
  MIN_BUILDER_CLI_VERSION=2.45.5
@@ -155,11 +155,10 @@ MISO_PIPELINE_ENV_KEY=
155
155
 
156
156
  # LLM: chat/embed route through governed enterprise-llm only. Provider credentials
157
157
  # live on LLM provider external systems + dataplane secret store (not product .env).
158
- # When ≥2 LLM providers are published under enterprise-llm, set selectorKey
159
- # (system key), e.g. openai | azure-openai | ollama. Ignored when only one.
160
- # Prefer portal Configuration category LLM after deploy; this env is the resolve target.
161
- # Default LLM provider systemKey when ≥2 LLM providers are published under
162
- # enterprise-llm (fail-closed if empty). Portal category LLM can override.
158
+ # Deploy-time default provider (systemKey) when Environment Settings ai.llmProvider
159
+ # is empty. Operators can override in Environment Settings without redeploying.
160
+ # When ≥2 LLM providers are published and both settings and this env are empty,
161
+ # resolve fails closed. Ignored when only one provider is published.
163
162
  # Lab default: openai (catalog also ships azure-openai).
164
163
  ENTERPRISE_LLM_DEFAULT_SELECTOR=openai
165
164
 
@@ -180,6 +179,8 @@ ABAC_EXPLAIN_MODE_ENABLED=false
180
179
  ABAC_PERFORMANCE_THRESHOLD_MS=1000
181
180
 
182
181
  # RBAC Audit Configuration
182
+ # When true, audit denials (rbac.permission.check.failure) and decision traces.
183
+ # Successful allow checks are not audited (no forensic value; floods every request).
183
184
  RBAC_AUDIT_ENABLED=true
184
185
  # RBAC audit detail: summary | detailed | explain
185
186
  RBAC_AUDIT_DETAIL_LEVEL=summary