@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
@@ -57,30 +57,51 @@ function getSchemaNodeAtPath(rootSchema, pathParts) {
57
57
  return node;
58
58
  }
59
59
 
60
+ /**
61
+ * Format / pattern samples for string-like schema nodes.
62
+ * @param {Object} node - JSON Schema node
63
+ * @returns {*|undefined}
64
+ */
65
+ function placeholderFromFormatOrPattern(node) {
66
+ const samples = {
67
+ 'date-time': '2025-01-15T10:30:00Z',
68
+ date: '2025-01-15',
69
+ uuid: '00000000-0000-4000-8000-000000000001',
70
+ uri: 'https://example.com/objects/1',
71
+ 'uri-reference': 'https://example.com/objects/1',
72
+ email: 'sample@example.com'
73
+ };
74
+ if (node.format && Object.prototype.hasOwnProperty.call(samples, node.format)) {
75
+ return samples[node.format];
76
+ }
77
+ if (typeof node.pattern === 'string' && /\\d/.test(node.pattern)) {
78
+ return '+1 555 0100';
79
+ }
80
+ return undefined;
81
+ }
82
+
60
83
  /**
61
84
  * @param {Object|null|undefined} node - JSON Schema node
62
85
  * @returns {*}
63
86
  */
64
87
  function placeholderFromSchemaNode(node) {
65
- if (!node || typeof node !== 'object') return '';
88
+ if (!node || typeof node !== 'object') return 'sample';
89
+ const fromFormat = placeholderFromFormatOrPattern(node);
90
+ if (fromFormat !== undefined) return fromFormat;
66
91
  const type = primarySchemaType(node.type);
67
- if (node.format === 'date-time') return '2025-01-15T10:30:00Z';
68
- if (node.format === 'date') return '2025-01-15';
69
- if (node.format === 'uuid') return '00000000-0000-4000-8000-000000000001';
70
92
  if (type === 'boolean') return false;
71
93
  if (type === 'integer' || type === 'number') return 0;
72
94
  if (type === 'array') return [];
73
95
  if (type === 'object') {
74
- if (node.properties && typeof node.properties === 'object') {
75
- const out = {};
76
- for (const [k, sub] of Object.entries(node.properties)) {
77
- out[k] = placeholderFromSchemaNode(sub);
78
- }
79
- return out;
96
+ const props = node.properties;
97
+ if (!props || typeof props !== 'object') return {};
98
+ const out = {};
99
+ for (const [k, sub] of Object.entries(props)) {
100
+ out[k] = placeholderFromSchemaNode(sub);
80
101
  }
81
- return {};
102
+ return out;
82
103
  }
83
- return '';
104
+ return 'sample';
84
105
  }
85
106
 
86
107
  /**
@@ -92,18 +113,35 @@ function placeholderForType(type) {
92
113
  if (type === 'boolean') return false;
93
114
  if (type === 'array') return [];
94
115
  if (type === 'object') return {};
95
- return '';
116
+ return 'sample';
96
117
  }
97
118
 
98
119
  /**
99
- * @param {Object} parsed - Datasource config
100
- * @param {string[]} pathParts - Path under raw payload
101
- * @param {Object} [attrConfig] - Attribute config (optional type fallback)
102
- * @returns {*}
120
+ * Prefer schema nodes that carry format/pattern over bare type-only nodes.
121
+ * @param {Object|null|undefined} a
122
+ * @param {Object|null|undefined} b
123
+ * @returns {Object|null}
103
124
  */
125
+ function pickRicherSchemaNode(a, b) {
126
+ const score = node => {
127
+ if (!node || typeof node !== 'object') return -1;
128
+ let s = 0;
129
+ if (node.format) s += 2;
130
+ if (node.pattern) s += 2;
131
+ if (node.type) s += 1;
132
+ return s;
133
+ };
134
+ if (score(a) < 0 && score(b) < 0) return null;
135
+ return score(a) >= score(b) ? a : b;
136
+ }
137
+
104
138
  function resolvePlaceholderForPath(parsed, pathParts, attrConfig) {
105
139
  const node = getSchemaNodeAtPath(parsed?.metadataSchema, pathParts);
106
- if (node) return placeholderFromSchemaNode(node);
140
+ // HubSpot-style: materialized root prop (phone/email/url) may be richer than nested.
141
+ const leaf = pathParts[pathParts.length - 1];
142
+ const rootNode = parsed?.metadataSchema?.properties?.[leaf];
143
+ const picked = pickRicherSchemaNode(node, rootNode);
144
+ if (picked) return placeholderFromSchemaNode(picked);
107
145
  return placeholderForType(attrConfig?.type || 'string');
108
146
  }
109
147
 
@@ -154,7 +192,10 @@ function enrichObjectLeavesFromNestedPaths(parsed, pathEntries, payloadTemplate)
154
192
  payloadTemplate.properties = placeholderFromSchemaNode(propertiesNode);
155
193
  }
156
194
  if (payloadTemplate.properties[leaf] === undefined || payloadTemplate.properties[leaf] === '') {
157
- const leafNode = getSchemaNodeAtPath(parsed?.metadataSchema, ['properties', leaf]);
195
+ const leafNode = pickRicherSchemaNode(
196
+ getSchemaNodeAtPath(parsed?.metadataSchema, ['properties', leaf]),
197
+ parsed?.metadataSchema?.properties?.[leaf]
198
+ );
158
199
  payloadTemplate.properties[leaf] = leafNode
159
200
  ? placeholderFromSchemaNode(leafNode)
160
201
  : 'sample';
@@ -209,6 +250,24 @@ function buildPathEntriesFromAttributes(parsed, attrs) {
209
250
  * @param {Array} pathEntries - From buildPathEntriesFromAttributes
210
251
  * @returns {{ payloadTemplate: Object, expectedResult: Object }}
211
252
  */
253
+ /**
254
+ * Ensure metadataSchema.required root keys exist on payloadTemplate.
255
+ * @param {Object} parsed
256
+ * @param {Object} payloadTemplate
257
+ */
258
+ function ensureRequiredRootPlaceholders(parsed, payloadTemplate) {
259
+ const required = parsed?.metadataSchema?.required;
260
+ const props = parsed?.metadataSchema?.properties;
261
+ if (!Array.isArray(required) || !props || typeof props !== 'object') return;
262
+ for (const key of required) {
263
+ if (!key || typeof key !== 'string') continue;
264
+ const existing = payloadTemplate[key];
265
+ if (existing !== undefined && existing !== '') continue;
266
+ const node = props[key];
267
+ payloadTemplate[key] = node ? placeholderFromSchemaNode(node) : 'sample';
268
+ }
269
+ }
270
+
212
271
  function buildTemplatesFromPathEntries(parsed, pathEntries) {
213
272
  const payloadTemplate = {};
214
273
  const expectedResult = {};
@@ -223,6 +282,7 @@ function buildTemplatesFromPathEntries(parsed, pathEntries) {
223
282
  setNestedValue(payloadTemplate, parts, resolvePlaceholderForPath(parsed, parts, config));
224
283
  }
225
284
  enrichObjectLeavesFromNestedPaths(parsed, pathEntries, payloadTemplate);
285
+ ensureRequiredRootPlaceholders(parsed, payloadTemplate);
226
286
  return { payloadTemplate, expectedResult };
227
287
  }
228
288
 
@@ -255,5 +315,6 @@ module.exports = {
255
315
  placeholderFromSchemaNode,
256
316
  placeholderForType,
257
317
  resolvePlaceholderForPath,
318
+ ensureRequiredRootPlaceholders,
258
319
  repairTestPayload
259
320
  };
@@ -88,21 +88,59 @@ function ensureSyncBridgeExternalIdMetadata(parsed, changes) {
88
88
  return true;
89
89
  }
90
90
 
91
+ /**
92
+ * Default syncBridge when --sync repair runs and the field is absent.
93
+ * Explicit values (including disabled) are never overwritten.
94
+ *
95
+ * @param {object} parsed
96
+ * @param {Object} [options]
97
+ * @returns {'master' | 'disabled' | null}
98
+ */
99
+ function defaultSyncBridgeForSyncRepair(parsed, options = {}) {
100
+ if (options.sync !== true) {
101
+ return null;
102
+ }
103
+ if (Object.prototype.hasOwnProperty.call(parsed || {}, 'syncBridge')) {
104
+ return null;
105
+ }
106
+ const entityType = String(parsed?.entityType || '').trim();
107
+ if (entityType === 'none') {
108
+ return null;
109
+ }
110
+ const resourceType = String(parsed?.resourceType || '').trim();
111
+ // Owner/employee catalogs typically do not own sync propagation.
112
+ if (resourceType === 'employee') {
113
+ return 'disabled';
114
+ }
115
+ if (entityType === 'recordStorage' || entityType === '') {
116
+ return 'master';
117
+ }
118
+ return null;
119
+ }
120
+
91
121
  /**
92
122
  * @param {object} parsed
93
123
  * @param {string[]} changes
94
124
  * @param {string[]} warnings
125
+ * @param {Object} [options]
95
126
  * @returns {boolean}
96
127
  */
97
- function repairSyncBridgeSection(parsed, changes, warnings) {
128
+ function repairSyncBridgeSection(parsed, changes, warnings, options = {}) {
129
+ let updated = false;
130
+ const seeded = defaultSyncBridgeForSyncRepair(parsed, options);
131
+ if (seeded) {
132
+ parsed.syncBridge = seeded;
133
+ changes.push(`Set syncBridge default ${seeded} (--sync repair; field was absent)`);
134
+ updated = true;
135
+ }
98
136
  const mode = parsed?.syncBridge || 'disabled';
99
137
  if (mode === 'disabled') {
100
- return false;
138
+ return updated;
101
139
  }
102
140
  if (mode === 'master' && !parsed.resourceType) {
103
141
  warnings.push('syncBridge=master requires resourceType for propagation grouping.');
104
142
  }
105
- return ensureSyncBridgeExternalIdMetadata(parsed, changes);
143
+ return ensureSyncBridgeExternalIdMetadata(parsed, changes) || updated;
106
144
  }
107
145
 
108
146
  /**
@@ -128,6 +166,7 @@ function collectWebhookSyncBridgeSystemWarnings(parsed, systemParsed, warnings)
128
166
  module.exports = {
129
167
  repairWebhookSection,
130
168
  repairSyncBridgeSection,
169
+ defaultSyncBridgeForSyncRepair,
131
170
  collectWebhookSyncBridgeSystemWarnings,
132
171
  systemHasWebhookInboundAuth
133
172
  };
@@ -28,7 +28,15 @@ function discoverIntegrationFiles(appPath) {
28
28
  if (!fs.existsSync(appPath)) {
29
29
  return { systemFiles: [], datasourceFiles: [] };
30
30
  }
31
- const entries = fs.readdirSync(appPath);
31
+ let entries = [];
32
+ try {
33
+ entries = fs.readdirSync(appPath);
34
+ } catch (error) {
35
+ if (error && error.code === 'ENOENT') {
36
+ return { systemFiles: [], datasourceFiles: [] };
37
+ }
38
+ throw error;
39
+ }
32
40
  const systemFiles = [];
33
41
  const datasourceFiles = [];
34
42
  for (const name of entries) {
@@ -30,7 +30,7 @@ function documentKeyToLocalOpenApiPath(appPath, systemKey, documentKey) {
30
30
  }
31
31
 
32
32
  /** @type {Set<string>} */
33
- const PROGRAMMATIC_ORCHESTRATION_SYSTEM_KEYS = new Set(['aifabrix-builder-api']);
33
+ const PROGRAMMATIC_ORCHESTRATION_SYSTEM_KEYS = new Set(['builder-api']);
34
34
 
35
35
  /**
36
36
  * @param {string} systemKey
@@ -97,7 +97,7 @@ function extractOpenApiFileListItems(res) {
97
97
  const data = res && res.data;
98
98
  if (data && Array.isArray(data.data)) return data.data;
99
99
  if (data && Array.isArray(data.items)) return data.items;
100
- if (res && Array.isArray(res.items)) return res.items;
100
+ if (Array.isArray(res.items)) return res.items;
101
101
  return [];
102
102
  }
103
103
 
@@ -123,6 +123,28 @@ function readDatasourceDocumentKey(appPath, datasourceFileName) {
123
123
  return openapi && typeof openapi.documentKey === 'string' ? openapi.documentKey : null;
124
124
  }
125
125
 
126
+ /**
127
+ * @returns {Promise<'uploaded'|'skipped'|'skip'>}
128
+ */
129
+ async function syncOneDatasourceOpenApiFile(ctx, fileName) {
130
+ const documentKey = readDatasourceDocumentKey(ctx.appPath, fileName);
131
+ if (!documentKey || ctx.uploadedThisRun.has(documentKey)) return 'skip';
132
+ if (!ctx.forceUpload && ctx.existingKeys.has(documentKey)) return 'skip';
133
+
134
+ const localPath = documentKeyToLocalOpenApiPath(ctx.appPath, ctx.systemKey, documentKey);
135
+ if (!(await fileExistsAsync(localPath))) return 'skipped';
136
+
137
+ await uploadOneOpenApiFile(
138
+ ctx.dataplaneUrl,
139
+ ctx.authConfig,
140
+ ctx.systemKey,
141
+ localPath,
142
+ documentKey
143
+ );
144
+ ctx.uploadedThisRun.add(documentKey);
145
+ return 'uploaded';
146
+ }
147
+
126
148
  /**
127
149
  * @param {Object} opts
128
150
  * @param {string} opts.appPath
@@ -139,24 +161,21 @@ async function syncOpenApiFilesForMcp(opts) {
139
161
  dataplaneUrl: opts.dataplaneUrl,
140
162
  authConfig: opts.authConfig
141
163
  });
142
- const existingKeys = await getExistingOpenApiKeys(dataplaneUrl, systemKey, authConfig);
143
- const uploadedKeys = new Set(existingKeys);
164
+ const ctx = {
165
+ appPath,
166
+ systemKey,
167
+ dataplaneUrl,
168
+ authConfig,
169
+ existingKeys: await getExistingOpenApiKeys(dataplaneUrl, systemKey, authConfig),
170
+ uploadedThisRun: new Set(),
171
+ forceUpload: opts.force === true
172
+ };
144
173
  let uploaded = 0;
145
174
  let skipped = 0;
146
-
147
175
  for (const fileName of datasourceFiles || []) {
148
- const documentKey = readDatasourceDocumentKey(appPath, fileName);
149
- if (!documentKey || uploadedKeys.has(documentKey)) continue;
150
-
151
- const localPath = documentKeyToLocalOpenApiPath(appPath, systemKey, documentKey);
152
- if (!(await fileExistsAsync(localPath))) {
153
- skipped += 1;
154
- continue;
155
- }
156
-
157
- await uploadOneOpenApiFile(dataplaneUrl, authConfig, systemKey, localPath, documentKey);
158
- uploadedKeys.add(documentKey);
159
- uploaded += 1;
176
+ const outcome = await syncOneDatasourceOpenApiFile(ctx, fileName);
177
+ if (outcome === 'uploaded') uploaded += 1;
178
+ if (outcome === 'skipped') skipped += 1;
160
179
  }
161
180
 
162
181
  return { uploaded, skipped };
@@ -177,7 +196,11 @@ async function maybeSyncOpenApiFilesForMcp(opts) {
177
196
  const r = await syncOpenApiFilesForMcp(opts);
178
197
  const lines = [];
179
198
  if (r.uploaded > 0) {
180
- lines.push(`Uploaded ${r.uploaded} OpenAPI file(s) for MCP (keyed by openapi.documentKey)`);
199
+ lines.push(
200
+ opts.force
201
+ ? `Uploaded ${r.uploaded} OpenAPI file(s) for MCP (force replace by openapi.documentKey)`
202
+ : `Uploaded ${r.uploaded} OpenAPI file(s) for MCP (keyed by openapi.documentKey)`
203
+ );
181
204
  }
182
205
  if (r.skipped > 0) {
183
206
  lines.push(
@@ -215,7 +238,9 @@ async function maybeRunOpenApiSyncForMcp({
215
238
  dryRun,
216
239
  appPath,
217
240
  systemKey,
218
- datasourceFiles
241
+ datasourceFiles,
242
+ // --api re-exports local OpenAPI; always replace SpecFile rows that already exist.
243
+ force: true
219
244
  });
220
245
  openapiLines.forEach((l) => changes.push(l));
221
246
  return exportLines.length > 0 || openapiLines.length > 0;
@@ -2,10 +2,7 @@
2
2
  * Resource type catalog commands (Controller).
3
3
  *
4
4
  * Commands:
5
- * - aifabrix resource-type create
6
- * - aifabrix resource-type import
7
- * - aifabrix resource-type get
8
- * - aifabrix resource-type list
5
+ * - aifabrix resource-type create|import|export|get|list
9
6
  *
10
7
  * @fileoverview Resource type CLI commands
11
8
  * @author AI Fabrix Team
@@ -21,7 +18,9 @@ const { normalizeControllerUrl, resolveEnvironment } = require('../core/config')
21
18
  const { getOrRefreshDeviceToken } = require('../utils/token-manager');
22
19
  const {
23
20
  readResourceTypeCreateFile,
24
- readResourceTypeCatalogFile
21
+ readResourceTypeCatalogFile,
22
+ mapResourceTypeApiToFileRow,
23
+ writeResourceTypeCatalogFile
25
24
  } = require('../resolvers/resource-type-file');
26
25
  const {
27
26
  formatBlockingError,
@@ -41,45 +40,43 @@ const RESOURCE_TYPE_KEY_PATTERN = /^[a-z][a-z0-9-]*$/;
41
40
  const RESOURCE_TYPE_HELP_AFTER = `
42
41
 
43
42
  Examples:
44
- $ aifabrix resource-type create --key customer --display-name "Customer"
43
+ $ aifabrix resource-type create --key customer --display-name "Customer" --domain sales
45
44
  $ aifabrix resource-type get customer
46
45
  $ aifabrix resource-type list --search customer
46
+ $ aifabrix resource-type export --file ./resource-types-export.json
47
47
 
48
48
  Per-command help: aifabrix resource-type <command> --help
49
49
  `;
50
50
 
51
51
  const RESOURCE_TYPE_IMPORT_HELP_AFTER = `
52
-
53
52
  Examples:
54
53
  $ aifabrix resource-type import --file ./baseline-resourcetypes.json
55
54
  $ aifabrix resource-type import --file /path/to/catalog.json
55
+ `;
56
56
 
57
+ const RESOURCE_TYPE_EXPORT_HELP_AFTER = `
58
+ Examples:
59
+ $ aifabrix resource-type export --file ./resource-types-export.json
57
60
  `;
58
61
 
59
62
  const RESOURCE_TYPE_CREATE_HELP_AFTER = `
60
-
61
63
  Examples:
62
- $ aifabrix resource-type create --key customer --display-name "Customer"
64
+ $ aifabrix resource-type create --key customer --display-name "Customer" --domain sales
63
65
  $ aifabrix resource-type create --key deal --display-name "Deal" --category business --default-risk-level medium --domain sales
64
66
  $ aifabrix resource-type create --file ./customer.json
65
-
66
67
  `;
67
68
 
68
69
  const RESOURCE_TYPE_GET_HELP_AFTER = `
69
-
70
70
  Examples:
71
71
  $ aifabrix resource-type get customer
72
72
  $ aifabrix resource-type get clx1234567890abcdef
73
-
74
73
  `;
75
74
 
76
75
  const RESOURCE_TYPE_LIST_HELP_AFTER = `
77
-
78
76
  Examples:
79
77
  $ aifabrix resource-type list
80
78
  $ aifabrix resource-type list --page 1 --page-size 50
81
79
  $ aifabrix resource-type list --search customer
82
-
83
80
  `;
84
81
 
85
82
  /**
@@ -165,8 +162,62 @@ function unwrapControllerData(response) {
165
162
  }
166
163
 
167
164
  /**
168
- * @param {import('commander').Command} rt
165
+ * @param {Object|null|undefined} response
166
+ * @param {string} label
167
+ * @returns {void}
168
+ */
169
+ function assertControllerListSuccess(response, label) {
170
+ if (response && response.success !== false) {
171
+ return;
172
+ }
173
+ const message =
174
+ response?.formattedError || response?.error || `Failed to list ${label}`;
175
+ throw new Error(message);
176
+ }
177
+
178
+ /**
179
+ * @param {Object} response
180
+ * @returns {Object[]}
169
181
  */
182
+ function extractControllerListItems(response) {
183
+ if (response?.data && Array.isArray(response.data.data)) {
184
+ return response.data.data;
185
+ }
186
+ const unwrapped = unwrapControllerData(response);
187
+ if (Array.isArray(unwrapped)) {
188
+ return unwrapped;
189
+ }
190
+ if (unwrapped && Array.isArray(unwrapped.data)) {
191
+ return unwrapped.data;
192
+ }
193
+ throw new Error('Unexpected list response from controller');
194
+ }
195
+
196
+ /**
197
+ * @param {string} controllerUrl
198
+ * @param {Object} authConfig
199
+ * @returns {Promise<Object[]>}
200
+ */
201
+ async function fetchAllResourceTypes(controllerUrl, authConfig) {
202
+ const all = [];
203
+ let page = 1;
204
+ const pageSize = 100;
205
+ for (;;) {
206
+ const res = await listResourceTypes(controllerUrl, authConfig, { page, pageSize });
207
+ assertControllerListSuccess(res, 'resource types');
208
+ const items = extractControllerListItems(res);
209
+ if (items.length === 0) {
210
+ break;
211
+ }
212
+ all.push(...items);
213
+ if (items.length < pageSize) {
214
+ break;
215
+ }
216
+ page += 1;
217
+ }
218
+ return all;
219
+ }
220
+
170
221
  /**
171
222
  * @param {Object[]} rows
172
223
  * @param {string} controllerUrl
@@ -213,6 +264,35 @@ function setupResourceTypeImportCommand(rt) {
213
264
  });
214
265
  }
215
266
 
267
+ function setupResourceTypeExportCommand(rt) {
268
+ rt
269
+ .command('export')
270
+ .description('Export resource types to baseline-shaped JSON')
271
+ .addHelpText('after', RESOURCE_TYPE_EXPORT_HELP_AFTER)
272
+ .requiredOption('--file <path>', 'Output path for baseline catalog JSON')
273
+ .action(async(options) => {
274
+ try {
275
+ const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
276
+ const records = await fetchAllResourceTypes(controllerUrl, authConfig);
277
+ const rows = records.map(mapResourceTypeApiToFileRow);
278
+ writeResourceTypeCatalogFile(options.file, rows);
279
+ if (rows.length === 0) {
280
+ logger.log(
281
+ infoLine(
282
+ 'No resource types on the controller. Create or import types, then re-run export.'
283
+ )
284
+ );
285
+ }
286
+ logger.log(
287
+ formatSuccessLine(`Resource types exported: ${rows.length} written to ${options.file}`)
288
+ );
289
+ } catch (e) {
290
+ logger.error(formatBlockingError(e.message));
291
+ process.exit(1);
292
+ }
293
+ });
294
+ }
295
+
216
296
  function setupResourceTypeCreateCommand(rt) {
217
297
  rt
218
298
  .command('create')
@@ -377,8 +457,8 @@ function setupResourceTypeListCommand(rt) {
377
457
  category: options.category,
378
458
  status: options.status
379
459
  });
380
- const payload = unwrapControllerData(res);
381
- const items = payload?.data ?? payload ?? [];
460
+ assertControllerListSuccess(res, 'resource types');
461
+ const items = extractControllerListItems(res);
382
462
  displayResourceTypeList(items, environment, headerControllerUrl);
383
463
  } catch (e) {
384
464
  logger.error(formatBlockingError(e.message));
@@ -397,6 +477,7 @@ function setupResourceTypeCommands(program) {
397
477
  .addHelpText('after', RESOURCE_TYPE_HELP_AFTER);
398
478
  setupResourceTypeCreateCommand(rt);
399
479
  setupResourceTypeImportCommand(rt);
480
+ setupResourceTypeExportCommand(rt);
400
481
  setupResourceTypeGetCommand(rt);
401
482
  setupResourceTypeListCommand(rt);
402
483
  }
@@ -406,5 +487,7 @@ module.exports = {
406
487
  requireResourceTypeKey,
407
488
  buildCreatePayload,
408
489
  unwrapControllerData,
409
- importResourceTypeRows
490
+ importResourceTypeRows,
491
+ fetchAllResourceTypes,
492
+ mapResourceTypeApiToFileRow
410
493
  };