@aifabrix/builder 2.52.0 → 2.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/lib/api/datasources-extended.api.js +8 -8
  2. package/lib/api/keycloak.api.js +92 -0
  3. package/lib/api/token-providers.api.js +77 -0
  4. package/lib/api/types/keycloak.types.js +23 -0
  5. package/lib/api/types/token-providers.types.js +65 -0
  6. package/lib/app/deploy-config.js +3 -2
  7. package/lib/app/down.js +16 -1
  8. package/lib/channels/add-channel.js +134 -57
  9. package/lib/channels/approval-guides/chatgpt.js +1 -1
  10. package/lib/channels/approval-guides/index.js +3 -0
  11. package/lib/channels/approval-guides/microsoft-copilot.js +27 -14
  12. package/lib/channels/channel-add-ensure.js +128 -0
  13. package/lib/channels/channel-add-existing.js +72 -0
  14. package/lib/channels/channel-artifact-generator.js +11 -1
  15. package/lib/channels/channel-branding.js +94 -0
  16. package/lib/channels/channel-credential.js +8 -0
  17. package/lib/channels/channel-discovery.js +330 -0
  18. package/lib/channels/channel-external-system.js +1 -1
  19. package/lib/channels/channel-runtime-resolver.js +107 -14
  20. package/lib/channels/channel-status-display.js +36 -3
  21. package/lib/channels/microsoft-copilot-channel-add.js +138 -0
  22. package/lib/channels/microsoft-copilot-entra-sso-form.js +251 -0
  23. package/lib/channels/microsoft-copilot-entra-sso.js +285 -0
  24. package/lib/channels/microsoft-copilot-icons.js +74 -0
  25. package/lib/channels/microsoft-copilot-limits.js +103 -0
  26. package/lib/channels/microsoft-copilot-package.js +178 -52
  27. package/lib/channels/microsoft-copilot-validate.js +227 -0
  28. package/lib/cli/attach-default-help-examples.js +102 -0
  29. package/lib/cli/index.js +4 -0
  30. package/lib/cli/infra-guided.js +13 -6
  31. package/lib/cli/setup-credential-deployment.js +7 -5
  32. package/lib/cli/setup-infra.js +31 -4
  33. package/lib/cli/setup-utility-validate.js +1 -0
  34. package/lib/commands/channel-add-setup.js +28 -9
  35. package/lib/commands/channel.js +14 -10
  36. package/lib/commands/credential-push.js +37 -5
  37. package/lib/commands/integration-definition.js +158 -0
  38. package/lib/commands/package-score.js +72 -0
  39. package/lib/commands/repair-auth-apply.js +19 -5
  40. package/lib/commands/repair-datasource-sync-operations.js +4 -1
  41. package/lib/commands/role-assistant-test-output.js +200 -0
  42. package/lib/commands/role-assistant.js +163 -166
  43. package/lib/datasource/capability/capability-resolve.js +15 -5
  44. package/lib/datasource/capability/copy-operations.js +14 -4
  45. package/lib/deployment/deployer-poll-run.js +2 -2
  46. package/lib/deployment/deployer.js +1 -1
  47. package/lib/deployment/environment.js +2 -2
  48. package/lib/external-system/deploy.js +11 -21
  49. package/lib/infrastructure/index.js +19 -4
  50. package/lib/integration-definition/apply.js +476 -0
  51. package/lib/integration-definition/canonical-json.js +51 -0
  52. package/lib/integration-definition/emit-suite-bid.js +94 -0
  53. package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
  54. package/lib/integration-definition/enrich-openapi-operations.js +433 -0
  55. package/lib/integration-definition/from-discovery.js +339 -0
  56. package/lib/integration-definition/hash.js +37 -0
  57. package/lib/integration-definition/index.js +37 -0
  58. package/lib/integration-definition/resource-type-catalog.js +59 -0
  59. package/lib/integration-definition/system-kind-enum.js +40 -0
  60. package/lib/integration-definition/validate.js +148 -0
  61. package/lib/lab/package-parity-score.js +417 -0
  62. package/lib/programmatic/builder-api-request.js +5 -1
  63. package/lib/programmatic/builder-help-governance.js +3 -2
  64. package/lib/programmatic/capability-registry.js +20 -0
  65. package/lib/programmatic/help-content/channel-onboarding.md +35 -0
  66. package/lib/programmatic/help-content/cip-overview.md +27 -0
  67. package/lib/programmatic/help-content/connected-system-ui.md +70 -0
  68. package/lib/programmatic/help-content/dimensions-guide.md +23 -0
  69. package/lib/programmatic/help-content/enterprise-sync-fabrix.md +123 -0
  70. package/lib/programmatic/help-content/overview.md +25 -0
  71. package/lib/programmatic/help-content/subscription-guide.md +27 -0
  72. package/lib/programmatic/help-content/workflow.md +50 -0
  73. package/lib/programmatic/openapi-component-schemas.js +7 -1
  74. package/lib/programmatic/openapi-descriptions.js +46 -0
  75. package/lib/programmatic/openapi-envelope-examples.js +280 -0
  76. package/lib/programmatic/openapi-operation-bindings.js +50 -0
  77. package/lib/programmatic/openapi-request-schemas.js +40 -0
  78. package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
  79. package/lib/programmatic/openapi-result-schemas.js +76 -99
  80. package/lib/programmatic/openapi-schema-descriptions-core.js +25 -4
  81. package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
  82. package/lib/programmatic/openapi-schema-descriptions-operations.js +43 -0
  83. package/lib/programmatic/openapi-schema-helpers.js +9 -9
  84. package/lib/programmatic/openapi-spec.js +11 -1
  85. package/lib/programmatic/orchestration-openapi-meta.js +10 -0
  86. package/lib/programmatic/persist-supplied-openapi.js +180 -0
  87. package/lib/programmatic/route-handler-map.js +35 -1
  88. package/lib/programmatic/run-channel-package.js +236 -0
  89. package/lib/programmatic/run-integration-definition.js +77 -0
  90. package/lib/role-assistant/evidence-sync.js +55 -7
  91. package/lib/role-assistant/knowledge-sync-logging.js +58 -0
  92. package/lib/role-assistant/knowledge-sync.js +88 -93
  93. package/lib/role-assistant/test-cases-bid.js +155 -0
  94. package/lib/role-assistant/test-cases-evaluate.js +9 -1
  95. package/lib/role-assistant/test-cases-honesty.js +24 -10
  96. package/lib/role-assistant/test-cases.js +28 -0
  97. package/lib/role-assistant/test-runner-attachments.js +118 -0
  98. package/lib/role-assistant/test-runner-bid-apply.js +201 -0
  99. package/lib/role-assistant/test-runner-bid-document.js +278 -0
  100. package/lib/role-assistant/test-runner-start-payload.js +96 -0
  101. package/lib/role-assistant/test-runner-workhub-answers.js +249 -20
  102. package/lib/role-assistant/test-runner-workhub-helpers.js +100 -0
  103. package/lib/role-assistant/test-runner-workhub-soft.js +86 -0
  104. package/lib/role-assistant/test-runner-workhub.js +152 -107
  105. package/lib/role-assistant/test-runner.js +5 -63
  106. package/lib/role-assistant/test-suite.js +112 -0
  107. package/lib/schema/application-schema.json +56 -6
  108. package/lib/schema/external-datasource.schema.json +103 -17
  109. package/lib/schema/external-system.schema.json +54 -3
  110. package/lib/schema/integration-definition.schema.json +195 -0
  111. package/lib/utils/authentication-shape.js +6 -0
  112. package/lib/utils/compose-generator.js +10 -1
  113. package/lib/utils/compose-traefik-extra-routes.js +107 -0
  114. package/lib/utils/docker-build.js +2 -2
  115. package/lib/utils/help-builder.js +3 -1
  116. package/lib/utils/promote-dev-docker-image.js +102 -18
  117. package/lib/utils/transient-developer-id.js +28 -0
  118. package/package.json +14 -16
  119. package/templates/README.md +12 -1
  120. package/templates/applications/dataplane/application.yaml +10 -2
  121. package/templates/applications/dataplane/env.template +7 -6
  122. package/templates/applications/miso-controller/application.yaml +23 -8
  123. package/templates/applications/miso-controller/env.template +8 -2
  124. package/templates/applications/miso-controller/rbac.yaml +1 -0
  125. package/templates/channels/README.md +11 -1
  126. package/templates/channels/brand/color-192.png +0 -0
  127. package/templates/channels/brand/dark/128.png +0 -0
  128. package/templates/channels/brand/dark/192.png +0 -0
  129. package/templates/channels/brand/dark/256.png +0 -0
  130. package/templates/channels/brand/dark/32.png +0 -0
  131. package/templates/channels/brand/dark/512.png +0 -0
  132. package/templates/channels/brand/dark/64.png +0 -0
  133. package/templates/channels/brand/light/128.png +0 -0
  134. package/templates/channels/brand/light/192.png +0 -0
  135. package/templates/channels/brand/light/256.png +0 -0
  136. package/templates/channels/brand/light/32.png +0 -0
  137. package/templates/channels/brand/light/512.png +0 -0
  138. package/templates/channels/brand/light/64.png +0 -0
  139. package/templates/channels/brand/mono-white/128.png +0 -0
  140. package/templates/channels/brand/mono-white/192.png +0 -0
  141. package/templates/channels/brand/mono-white/256.png +0 -0
  142. package/templates/channels/brand/mono-white/32.png +0 -0
  143. package/templates/channels/brand/mono-white/512.png +0 -0
  144. package/templates/channels/brand/mono-white/64.png +0 -0
  145. package/templates/channels/brand/outline-32.png +0 -0
  146. package/templates/channels/chatgpt/install.template.md.hbs +6 -4
  147. package/templates/channels/chatgpt/integration/README.md +4 -5
  148. package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
  149. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +14 -16
  150. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
  151. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +30 -0
  152. package/templates/channels/microsoft-copilot/fi-fi.template.json.hbs +7 -0
  153. package/templates/channels/microsoft-copilot/install.template.md.hbs +93 -22
  154. package/templates/channels/microsoft-copilot/integration/README.md +2 -3
  155. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
  156. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -19
  157. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +15 -0
  158. package/templates/channels/slack/integration/README.md +2 -4
  159. package/templates/channels/teams-bot/install.template.md.hbs +4 -0
  160. package/templates/channels/teams-bot/integration/README.md +3 -5
  161. package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
  162. package/templates/python/docker-compose.hbs +36 -0
  163. package/templates/typescript/docker-compose.hbs +36 -0
  164. package/.cursor/plans/Done/157.1-security-quality-violations-by-type.csv +0 -374
  165. package/.cursor/process/README.md +0 -9
  166. package/.cursor/rules/anchor-docs.mdc +0 -15
  167. package/.cursor/rules/assistant-driven-integration-build.mdc +0 -40
  168. package/.cursor/rules/cli-layout.mdc +0 -81
  169. package/.cursor/rules/cli-product-neutral.mdc +0 -43
  170. package/.cursor/rules/deploy-rules/docker-image-ip-policy.mdc +0 -49
  171. package/.cursor/rules/docs-rules.mdc +0 -32
  172. package/.cursor/rules/plan-rules/application-lifecycle.mdc +0 -30
  173. package/.cursor/rules/plan-rules/plan-frontmatter.mdc +0 -35
  174. package/.cursor/rules/plan-rules/plan-lifecycle.mdc +0 -31
  175. package/.cursor/rules/plan-rules/plan-validation-gate.mdc +0 -34
  176. package/.cursor/rules/project-rules.mdc +0 -913
  177. package/.cursor/skills/README.md +0 -15
  178. package/.dockerignore +0 -52
  179. package/.eslintignore +0 -22
  180. package/.markdownlint.json +0 -14
  181. package/anchor-docs/README.md +0 -10
  182. package/anchor-docs/_TEMPLATE +0 -24
  183. package/babel.config.js +0 -6
  184. package/bin/builder-api-cleanup.js +0 -26
  185. package/bin/builder-api-nest.js +0 -10
  186. package/bin/builder-api-spike.js +0 -19
  187. package/enterprise-knowledge/README.md +0 -50
  188. package/enterprise-knowledge/enterprise-integration-lifecycle/README.md +0 -22
  189. package/enterprise-knowledge/enterprise-integration-lifecycle/application.json +0 -15
  190. package/enterprise-knowledge/enterprise-integration-lifecycle/deploy.js +0 -69
  191. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-deploy.json +0 -52
  192. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-system.json +0 -29
  193. package/enterprise-knowledge/enterprise-integration-lifecycle/env.template +0 -2
  194. package/integration/README.md +0 -81
  195. package/integration/github/README.md +0 -53
  196. package/integration/github/application.json +0 -21
  197. package/integration/github/deploy.js +0 -72
  198. package/integration/github/env.template +0 -11
  199. package/integration/github/github-datasource-issue-comments.json +0 -239
  200. package/integration/github/github-datasource-issues.json +0 -399
  201. package/integration/github/github-datasource-repository.json +0 -354
  202. package/integration/github/github-deploy.json +0 -1202
  203. package/integration/github/github-system.json +0 -82
  204. package/integration/hubspot-test/README.md +0 -159
  205. package/integration/hubspot-test/application.json +0 -54
  206. package/integration/hubspot-test/companies.json +0 -2048
  207. package/integration/hubspot-test/create-hubspot.js +0 -523
  208. package/integration/hubspot-test/env.template +0 -4
  209. package/integration/hubspot-test/hubspot-test-datasource-company.json +0 -138
  210. package/integration/hubspot-test/hubspot-test-datasource-contact.json +0 -146
  211. package/integration/hubspot-test/hubspot-test-datasource-deal.json +0 -146
  212. package/integration/hubspot-test/hubspot-test-datasource-users.json +0 -76
  213. package/integration/hubspot-test/hubspot-test-deploy.json +0 -2160
  214. package/integration/hubspot-test/hubspot-test-system.json +0 -74
  215. package/integration/hubspot-test/rbac.json +0 -166
  216. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +0 -20
  217. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +0 -9
  218. package/integration/hubspot-test/test-artifacts/wizard-invalid-add-datasource.yaml +0 -5
  219. package/integration/hubspot-test/test-artifacts/wizard-invalid-app-name.yaml +0 -5
  220. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-create.yaml +0 -7
  221. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-select.yaml +0 -7
  222. package/integration/hubspot-test/test-artifacts/wizard-invalid-known-platform.yaml +0 -4
  223. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-app.yaml +0 -4
  224. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
  225. package/integration/hubspot-test/test-artifacts/wizard-invalid-mode.yaml +0 -5
  226. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-file.yaml +0 -5
  227. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-url.yaml +0 -4
  228. package/integration/hubspot-test/test-artifacts/wizard-invalid-source.yaml +0 -4
  229. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-array-test.yaml +0 -5
  230. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
  231. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
  232. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-test.yaml +0 -5
  233. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-test.yaml +0 -5
  234. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +0 -5
  235. package/integration/hubspot-test/test-dataplane-down-helpers.js +0 -249
  236. package/integration/hubspot-test/test-dataplane-down-tests.js +0 -386
  237. package/integration/hubspot-test/test-dataplane-down.js +0 -209
  238. package/integration/hubspot-test/test.js +0 -1588
  239. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +0 -16
  240. package/integration/hubspot-test/wizard-hubspot-platform.yaml +0 -8
  241. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +0 -23
  242. package/integration/roundtrip-test-local/README.md +0 -143
  243. package/integration/roundtrip-test-local/application.yaml +0 -13
  244. package/integration/roundtrip-test-local/env.template +0 -15
  245. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +0 -14
  246. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +0 -61
  247. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +0 -25
  248. package/integration/roundtrip-test-local2/README.md +0 -143
  249. package/integration/roundtrip-test-local2/application.yaml +0 -13
  250. package/integration/roundtrip-test-local2/env.template +0 -15
  251. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +0 -31
  252. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +0 -86
  253. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +0 -25
  254. package/integration/test/wizard.yaml +0 -8
  255. package/jest.config.coverage.js +0 -37
  256. package/jest.config.default.js +0 -11
  257. package/jest.config.integration.fixtures.js +0 -22
  258. package/jest.config.integration.js +0 -33
  259. package/jest.config.isolated.js +0 -11
  260. package/jest.config.manual.js +0 -30
  261. package/jest.global-live-fabrix-hooks.js +0 -36
  262. package/jest.isolated-projects.js +0 -260
  263. package/jest.projects.js +0 -76
  264. package/packages/builder-api/README.md +0 -120
  265. package/packages/builder-api/package.json +0 -30
  266. package/packages/builder-api/src/app.module.ts +0 -7
  267. package/packages/builder-api/src/auth/auth.module.ts +0 -5
  268. package/packages/builder-api/src/external-system/external-system.module.ts +0 -5
  269. package/packages/builder-api/src/main.ts +0 -16
  270. package/packages/builder-api/tsconfig.json +0 -15
  271. package/scripts/check-builder-api-architecture-grep.js +0 -79
  272. package/scripts/check-datasource-test-run-schema-sync.js +0 -34
  273. package/scripts/ci-fix.sh +0 -19
  274. package/scripts/ci-simulate.sh +0 -19
  275. package/scripts/diagnose-cli.js +0 -150
  276. package/scripts/docker_image_content_gate.js +0 -47
  277. package/scripts/docker_image_content_gate.sh +0 -5
  278. package/scripts/export-builder-api-evidence.js +0 -262
  279. package/scripts/install-local.js +0 -490
  280. package/scripts/lib/builder-api-docker-staging-copy.js +0 -60
  281. package/scripts/lib/builder-api-docker-staging-core.js +0 -183
  282. package/scripts/lib/docker-image-content-gate-core.js +0 -166
  283. package/scripts/lib/plan-actor.js +0 -109
  284. package/scripts/lib/plan-config.js +0 -20
  285. package/scripts/lib/plan-gates.js +0 -154
  286. package/scripts/lib/plan-meta.js +0 -104
  287. package/scripts/lib/plan-parse.js +0 -162
  288. package/scripts/lib/plan-prevalidate-gates.js +0 -337
  289. package/scripts/lib/plan-run-core.js +0 -125
  290. package/scripts/lib/plan-run-report.js +0 -119
  291. package/scripts/plan-approve.js +0 -24
  292. package/scripts/plan-archive.js +0 -49
  293. package/scripts/plan-run.js +0 -13
  294. package/scripts/plan-set-actor.js +0 -21
  295. package/scripts/plan-validate.js +0 -80
  296. package/scripts/pnpm/af.mjs +0 -110
  297. package/scripts/pnpm/check-quiet.mjs +0 -32
  298. package/scripts/pnpm/dev-install.mjs +0 -29
  299. package/scripts/pnpm/dev-reload.mjs +0 -18
  300. package/scripts/pnpm/help.mjs +0 -55
  301. package/scripts/pnpm/lib/dev-reload-core.mjs +0 -189
  302. package/scripts/pnpm/lib/dev-reload-log-capture.mjs +0 -67
  303. package/scripts/pnpm/lib/dev-reload-log-utils.mjs +0 -32
  304. package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +0 -124
  305. package/scripts/pnpm/lib/resolve-app-url.mjs +0 -183
  306. package/scripts/pnpm/lib/spinner.mjs +0 -42
  307. package/scripts/pnpm-global-remove.js +0 -48
  308. package/scripts/stage_builder_api_for_docker.js +0 -65
  309. package/scripts/sync-builder-api-template.js +0 -44
  310. package/scripts/test-dataplane-bootstrap.js +0 -331
@@ -7,7 +7,11 @@
7
7
  */
8
8
 
9
9
  const { rewriteCapabilityReferences } = require('./reference-rewrite');
10
- const { capabilityKeyFromEntry } = require('./capability-manifest');
10
+ const {
11
+ capabilityKeyFromEntry,
12
+ capabilitiesIncludes,
13
+ buildCapabilityObject
14
+ } = require('./capability-manifest');
11
15
  const { jsonPointerPath } = require('./json-pointer');
12
16
  const { buildBasicExposureProfileArray } = require('./basic-exposure');
13
17
  const {
@@ -256,14 +260,20 @@ function appendCapabilityList(doc, resolvedAs, patchOperations, updatedSections)
256
260
  if (!Array.isArray(doc.capabilities)) {
257
261
  doc.capabilities = [];
258
262
  }
259
- if (doc.capabilities.includes(resolvedAs)) {
263
+ if (capabilitiesIncludes(doc.capabilities, resolvedAs)) {
260
264
  return;
261
265
  }
262
- doc.capabilities.push(resolvedAs);
266
+ const usesObjects = doc.capabilities.some(
267
+ (entry) => entry && typeof entry === 'object' && !Array.isArray(entry)
268
+ );
269
+ const value = usesObjects
270
+ ? buildCapabilityObject(resolvedAs, doc.displayName || doc.key || 'records')
271
+ : resolvedAs;
272
+ doc.capabilities.push(value);
263
273
  patchOperations.push({
264
274
  op: 'add',
265
275
  path: '/capabilities/-',
266
- value: resolvedAs
276
+ value
267
277
  });
268
278
  updatedSections.push(`capabilities[]: ${resolvedAs}`);
269
279
  }
@@ -31,7 +31,7 @@ const { createDeployPollHandlers } = require('./deploy-poll-ui');
31
31
  */
32
32
  async function pollDeploymentStatus(deploymentId, controllerUrl, envKey, authConfig, options = {}) {
33
33
  const interval = options.interval || 5000;
34
- const maxAttempts = options.maxAttempts || 60;
34
+ const maxAttempts = options.maxAttempts || 300;
35
35
 
36
36
  const validatedEnvKey = validateEnvironmentKey(envKey);
37
37
  const pipelineAuthConfig = convertToPipelineAuthConfig(authConfig);
@@ -74,7 +74,7 @@ async function pollDeployment(result, url, validatedEnvKey, authConfig, options)
74
74
  }
75
75
 
76
76
  const resolvedInterval = await resolvePollIntervalFromController(url, options.pollInterval);
77
- const maxAttempts = options.pollMaxAttempts || 60;
77
+ const maxAttempts = options.pollMaxAttempts || 300;
78
78
  const ownsPollUi = !options.pollUi;
79
79
  const pollUi =
80
80
  options.pollUi ||
@@ -387,7 +387,7 @@ async function deployToController(manifest, controllerUrl, envKey, authConfig, o
387
387
 
388
388
  const usePollUi = options.poll !== false && options.silentPoll !== true;
389
389
  const pollUi = usePollUi
390
- ? createDeployPollHandlers(options.pollMaxAttempts || 60, { silent: false })
390
+ ? createDeployPollHandlers(options.pollMaxAttempts || 300, { silent: false })
391
391
  : null;
392
392
  const deployOpts = pollUi ? { ...options, pollUi } : options;
393
393
 
@@ -253,7 +253,7 @@ async function pollEnvironmentStatus(deploymentId, controllerUrl, envKey, authCo
253
253
  const validatedUrl = validateControllerUrl(controllerUrl);
254
254
  const validatedEnvKey = validateEnvironmentKey(envKey);
255
255
  const pollInterval = options.pollInterval || 5000;
256
- const maxAttempts = options.maxAttempts || 60;
256
+ const maxAttempts = options.maxAttempts || 300;
257
257
  const apiAuthConfig = { type: 'bearer', token: authConfig.token };
258
258
 
259
259
  logger.log(chalk.blue(`⏳ Polling environment status (${pollInterval}ms intervals)...`));
@@ -398,7 +398,7 @@ async function pollDeploymentStatusIfEnabled(result, validatedControllerUrl, env
398
398
  authConfig,
399
399
  {
400
400
  pollInterval,
401
- maxAttempts: options.pollMaxAttempts || 60
401
+ maxAttempts: options.pollMaxAttempts || 300
402
402
  }
403
403
  );
404
404
  result.status = pollResult.status;
@@ -131,19 +131,6 @@ async function fetchDataplaneDeployReadiness(controllerUrl, environment, authCon
131
131
  }
132
132
  }
133
133
 
134
- /**
135
- * Deploys via controller and prints readiness summary (config / deployment / runtime layers).
136
- * @async
137
- * @param {Object} manifest - Controller manifest
138
- * @param {string} controllerUrl - Controller base URL
139
- * @param {string} environment - Environment key
140
- * @param {Object} authConfig - Authentication configuration
141
- * @param {Object} options - Deployment options (poll, probe, probeTimeout)
142
- * @returns {Promise<Object>} Deployment result
143
- */
144
- /**
145
- * @param {Object} deploymentOutcome - from parseControllerDeploymentOutcome
146
- */
147
134
  /**
148
135
  * Optional dataplane validation/run after external deploy (--probe).
149
136
  * @async
@@ -202,12 +189,21 @@ function logImmediateControllerDeploymentOutcome(deploymentOutcome) {
202
189
  logger.log(chalk.gray(' Check the controller deployment job or logs for details.'));
203
190
  }
204
191
 
192
+ /** External deploy waits on controller → dataplane publish job (may run prove gates / tests). */
193
+ const EXTERNAL_DEPLOY_POLL_INTERVAL_MS = 2000;
194
+ /** 300 × 2s ≈ 10 minutes — must cover controller publish-job max wait with gates. */
195
+ const EXTERNAL_DEPLOY_POLL_MAX_ATTEMPTS = 300;
196
+
205
197
  function buildExternalDeployPollOpts(options) {
206
198
  return {
207
199
  ...options,
208
200
  poll: true,
209
- pollInterval: options.pollInterval !== undefined ? options.pollInterval : 500,
210
- pollMaxAttempts: options.pollMaxAttempts !== undefined ? options.pollMaxAttempts : 22,
201
+ pollInterval:
202
+ options.pollInterval !== undefined ? options.pollInterval : EXTERNAL_DEPLOY_POLL_INTERVAL_MS,
203
+ pollMaxAttempts:
204
+ options.pollMaxAttempts !== undefined
205
+ ? options.pollMaxAttempts
206
+ : EXTERNAL_DEPLOY_POLL_MAX_ATTEMPTS,
211
207
  silentPoll: options.silentPoll === true
212
208
  };
213
209
  }
@@ -381,12 +377,6 @@ function wrapDeployExternalSystemError(error, appName) {
381
377
  return new Error(message);
382
378
  }
383
379
 
384
- /**
385
- * @async
386
- * @param {string} appName
387
- * @param {Object} options
388
- * @returns {Promise<Object>}
389
- */
390
380
  /**
391
381
  * @param {string} appName
392
382
  * @param {Object} options
@@ -203,12 +203,26 @@ async function removeAppVolumes(appNames, devId) {
203
203
  }
204
204
  }
205
205
 
206
+ /**
207
+ * Resolve developer id for stop paths. Optional override is transient (not config).
208
+ * @param {string|number|null|undefined} developerId
209
+ * @returns {Promise<string>}
210
+ */
211
+ async function resolveStopDeveloperId(developerId) {
212
+ if (developerId === undefined || developerId === null || developerId === '') {
213
+ return config.getDeveloperId();
214
+ }
215
+ const { validateAndNormalizeDeveloperId } = require('../core/config-normalize');
216
+ return validateAndNormalizeDeveloperId(developerId);
217
+ }
218
+
206
219
  /**
207
220
  * Stops and removes local infrastructure services and all application containers
208
221
  * on the same network. Cleanly shuts down infra and app containers.
209
222
  *
210
223
  * @async
211
224
  * @function stopInfra
225
+ * @param {string|number|null|undefined} [developerId] - Optional target developer (does not change config)
212
226
  * @returns {Promise<void>} Resolves when infrastructure is stopped
213
227
  * @throws {Error} If Docker compose fails
214
228
  *
@@ -216,8 +230,8 @@ async function removeAppVolumes(appNames, devId) {
216
230
  * await stopInfra();
217
231
  * // All infrastructure and app containers on the same network are stopped and removed
218
232
  */
219
- async function stopInfra() {
220
- const devId = await config.getDeveloperId();
233
+ async function stopInfra(developerId) {
234
+ const devId = await resolveStopDeveloperId(developerId);
221
235
  const { infraDir, adminSecretsPath } = resolveInfraStatePaths(devId);
222
236
  const composePath = path.join(infraDir, 'compose.yaml');
223
237
  const hasCompose = fs.existsSync(composePath);
@@ -279,6 +293,7 @@ async function stopAllAppContainersAndVolumes(devId) {
279
293
  *
280
294
  * @async
281
295
  * @function stopInfraWithVolumes
296
+ * @param {string|number|null|undefined} [developerId] - Optional target developer (does not change config)
282
297
  * @returns {Promise<void>} Resolves when infrastructure is stopped and volumes removed
283
298
  * @throws {Error} If Docker compose fails
284
299
  *
@@ -286,8 +301,8 @@ async function stopAllAppContainersAndVolumes(devId) {
286
301
  * await stopInfraWithVolumes();
287
302
  * // All infrastructure and app containers and volumes are removed
288
303
  */
289
- async function stopInfraWithVolumes() {
290
- const devId = await config.getDeveloperId();
304
+ async function stopInfraWithVolumes(developerId) {
305
+ const devId = await resolveStopDeveloperId(developerId);
291
306
  const { infraDir, adminSecretsPath } = resolveInfraStatePaths(devId);
292
307
  const composePath = path.join(infraDir, 'compose.yaml');
293
308
  const hasCompose = fs.existsSync(composePath);
@@ -0,0 +1,476 @@
1
+ /**
2
+ * Apply Builder Integration Definition to integration/<systemKey>/.
3
+ * @fileoverview Idempotent BID → package materialization (no AI)
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const path = require('path');
9
+ const fsReal = require('../internal/fs-real-sync');
10
+ const { getIntegrationPath } = require('../utils/paths');
11
+ const { validateIntegrationDefinition } = require('./validate');
12
+
13
+ const STAMP_FILE = '.aifabrix-integration-definition.json';
14
+
15
+ /**
16
+ * @param {string} dir
17
+ * @returns {Promise<Record<string, unknown> | null>}
18
+ */
19
+ async function readStamp(dir) {
20
+ const stampPath = path.join(dir, STAMP_FILE);
21
+ try {
22
+ const raw = fsReal.readFileSync(stampPath, 'utf8');
23
+ return JSON.parse(raw);
24
+ } catch (err) {
25
+ if (err && err.code === 'ENOENT') {
26
+ return null;
27
+ }
28
+ throw err;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * @param {Record<string, unknown>} bid
34
+ * @param {Record<string, unknown>} stamp
35
+ * @returns {void}
36
+ */
37
+ function assertApplyAllowed(bid, stamp) {
38
+ if (!stamp) {
39
+ return;
40
+ }
41
+ const meta = /** @type {Record<string, unknown>} */ (bid.meta || {});
42
+ if (stamp.definitionId === bid.definitionId && stamp.definitionHash === bid.definitionHash) {
43
+ return;
44
+ }
45
+ if (stamp.definitionId === bid.definitionId) {
46
+ return;
47
+ }
48
+ if (stamp.systemKey === /** @type {any} */ (bid.packageIdentity).systemKey && !meta.replaceSystemKey) {
49
+ const err = new Error(
50
+ `systemKey ${stamp.systemKey} already bound to definitionId ${stamp.definitionId}; ` +
51
+ 'set meta.replaceSystemKey true to replace'
52
+ );
53
+ err.code = 'BID_SYSTEM_KEY_CONFLICT';
54
+ throw err;
55
+ }
56
+ }
57
+
58
+ function buildDatasourceCapabilities(caps, key) {
59
+ return caps.map((capability) => ({
60
+ key: capability,
61
+ description: `${capability} ${key}`,
62
+ riskLevel:
63
+ capability === 'delete'
64
+ ? 'high'
65
+ : capability.startsWith('create') || capability.startsWith('update')
66
+ ? 'medium'
67
+ : 'low',
68
+ approvalRequired: false
69
+ }));
70
+ }
71
+
72
+ function buildDatasourceSchema(labelField) {
73
+ return {
74
+ metadataSchema: {
75
+ type: 'object',
76
+ properties: {
77
+ externalId: { type: 'string', index: true, description: 'Stable external join identity' },
78
+ id: { type: 'string', index: true, description: 'Vendor record id' },
79
+ [labelField]: { type: 'string', index: true, filter: true, description: 'Display label' }
80
+ },
81
+ required: ['externalId', 'id']
82
+ },
83
+ fieldMappings: {
84
+ attributes: {
85
+ id: { expression: '{{raw.id}}', description: 'Vendor record id' },
86
+ externalId: {
87
+ expression: '{{raw.id}}',
88
+ description: 'Stable join identity (defaults to vendor id until enrichment)'
89
+ },
90
+ [labelField]: {
91
+ expression: `{{raw.${labelField}}}`,
92
+ description: 'Display label (enrich from OpenAPI raw paths as needed)'
93
+ }
94
+ }
95
+ }
96
+ };
97
+ }
98
+
99
+ function enrichDatasourceFile(body, ds, enrichOptions) {
100
+ const { enrichDatasourceFromOpenApi } = require('./enrich-openapi-operations');
101
+ const openapiSourceRef =
102
+ (typeof ds.openapiSourceRef === 'string' && ds.openapiSourceRef.trim()) ||
103
+ (typeof enrichOptions.systemOpenapiSourceRef === 'string' &&
104
+ enrichOptions.systemOpenapiSourceRef.trim()) ||
105
+ '';
106
+ if (openapiSourceRef) {
107
+ enrichDatasourceFromOpenApi(body, openapiSourceRef, {
108
+ baseDir: enrichOptions.baseDir,
109
+ associationTargets: enrichOptions.associationTargets
110
+ });
111
+ }
112
+ }
113
+
114
+ /**
115
+ * @param {Record<string, unknown>} ds
116
+ * @param {string} systemKey
117
+ * @param {Record<string, string[]>} capabilities
118
+ * @param {Record<string, unknown>} sync
119
+ * @returns {Record<string, unknown>}
120
+ */
121
+ function buildDatasourceFile(ds, systemKey, capabilities, sync, enrichOptions = {}) {
122
+ const key = String(ds.key);
123
+ const caps = capabilities[key] || ds.capabilityKeys || ['list', 'get'];
124
+ const labelField = ds.labelField || 'name';
125
+ const { repairSyncOperationsFromCapabilities } = require('../commands/repair-datasource-sync-operations');
126
+ const { seedPropertyKeysOnDatasource } = require('./enrich-crm-field-mappings');
127
+ const body = {
128
+ key,
129
+ displayName: ds.displayName || key,
130
+ description: ds.description || `${ds.displayName || key} datasource`,
131
+ enabled: true,
132
+ systemKey,
133
+ entityType: ds.entityType || 'recordStorage',
134
+ resourceType: ds.resourceType,
135
+ version: '1.0.0',
136
+ capabilities: buildDatasourceCapabilities(caps, key),
137
+ // Schema sync is mode/schedule/batchSize only; BID sync.mode=incremental is operational intent.
138
+ sync: { mode: 'pull', schedule: /** @type {any} */ (sync).schedule || '0 */6 * * *', batchSize: 100 },
139
+ primaryKey: ['id'],
140
+ labelKey: [labelField],
141
+ ...buildDatasourceSchema(labelField)
142
+ };
143
+ // Mirror capabilities → openapi.operations + execution.cip.operations stubs (154.7).
144
+ repairSyncOperationsFromCapabilities(body, []);
145
+ seedPropertyKeysOnDatasource(body, ds.propertyKeys);
146
+ enrichDatasourceFile(body, ds, enrichOptions);
147
+ return body;
148
+ }
149
+
150
+ /**
151
+ * Collect vendor object-type segments from each datasource OpenAPI ref.
152
+ * Used so list/get can request associations to sibling collections.
153
+ * @param {any[]} datasources
154
+ * @param {string} systemOpenapiSourceRef
155
+ * @param {{ baseDir?: string }} enrichOptions
156
+ * @returns {string[]}
157
+ */
158
+ function collectAssociationObjectTypes(datasources, systemOpenapiSourceRef, enrichOptions = {}) {
159
+ const {
160
+ loadOpenApiDocument,
161
+ indexOpenApiOperations,
162
+ objectTypeFromOpenApiPath
163
+ } = require('./enrich-openapi-operations');
164
+ const types = new Set();
165
+ for (const ds of datasources || []) {
166
+ // BID datasource keys are the named collection segments CRM association
167
+ // queries expect (opaque OpenAPI paths like /objects/0-3 omit these).
168
+ const dsKey = String(ds.key || '').trim();
169
+ if (dsKey) {
170
+ types.add(dsKey);
171
+ }
172
+ const ref =
173
+ (typeof ds.openapiSourceRef === 'string' && ds.openapiSourceRef.trim()) ||
174
+ systemOpenapiSourceRef ||
175
+ '';
176
+ if (!ref) {
177
+ continue;
178
+ }
179
+ try {
180
+ const doc = loadOpenApiDocument(ref, enrichOptions.baseDir);
181
+ const ops = indexOpenApiOperations(doc);
182
+ for (const op of ops) {
183
+ const objectType = objectTypeFromOpenApiPath(op.path);
184
+ if (objectType) {
185
+ types.add(objectType);
186
+ }
187
+ }
188
+ } catch {
189
+ // Skip unreadable refs; enrichment still binds per-datasource.
190
+ }
191
+ }
192
+ return [...types];
193
+ }
194
+
195
+ /**
196
+ * @param {Record<string, unknown>} bid
197
+ * @returns {Record<string, unknown>}
198
+ */
199
+ function buildSystemFile(bid) {
200
+ const system = /** @type {Record<string, unknown>} */ (bid.system);
201
+ const identity = /** @type {Record<string, unknown>} */ (bid.packageIdentity);
202
+ const refs = /** @type {Record<string, string>} */ (system.connectionRefs || {});
203
+ const tokenRef = refs.token || refs.apiKey || Object.values(refs)[0] || `kv://${identity.systemKey}/token`;
204
+ return {
205
+ key: system.key,
206
+ displayName: system.displayName || identity.displayName,
207
+ description: system.description || identity.displayName,
208
+ type: system.type || 'openapi',
209
+ enabled: true,
210
+ systemKind: system.systemKind || 'crm',
211
+ authentication: {
212
+ authType: 'credential',
213
+ credential: {
214
+ credentialKey: `${identity.systemKey}-cred`,
215
+ method: 'bearerToken',
216
+ variables: {
217
+ baseUrl: refs.baseUrl || 'https://example.invalid',
218
+ headerName: 'Authorization',
219
+ prefix: 'Bearer'
220
+ },
221
+ security: {
222
+ token: tokenRef
223
+ },
224
+ writeOnce: true
225
+ }
226
+ },
227
+ dataSources: (/** @type {any[]} */ (bid.datasources) || []).map((d) => d.key)
228
+ };
229
+ }
230
+
231
+ /**
232
+ * @param {Record<string, unknown>} bid
233
+ * @param {string[]} datasourceFiles
234
+ * @param {string} systemFile
235
+ * @returns {Record<string, unknown>}
236
+ */
237
+ function buildApplicationFile(bid, datasourceFiles, systemFile) {
238
+ const identity = /** @type {Record<string, unknown>} */ (bid.packageIdentity);
239
+ return {
240
+ app: {
241
+ key: identity.systemKey,
242
+ displayName: identity.displayName,
243
+ description: `${identity.displayName} (BT BID-applied)`,
244
+ type: 'external'
245
+ },
246
+ externalIntegration: {
247
+ schemaBasePath: './',
248
+ systems: [systemFile],
249
+ dataSources: datasourceFiles,
250
+ autopublish: true,
251
+ version: identity.packageVersion
252
+ },
253
+ integrationDefinition: {
254
+ schemaName: bid.schemaName,
255
+ schemaVersion: bid.schemaVersion,
256
+ definitionId: bid.definitionId,
257
+ definitionHash: bid.definitionHash,
258
+ suiteRunId: /** @type {any} */ (bid.meta).suiteRunId
259
+ }
260
+ };
261
+ }
262
+
263
+ /**
264
+ * @param {Record<string, unknown>} bid
265
+ * @returns {string}
266
+ */
267
+ function buildEnvTemplate(bid) {
268
+ const identity = /** @type {Record<string, unknown>} */ (bid.packageIdentity);
269
+ const systemKey = String(identity.systemKey);
270
+ const system = /** @type {Record<string, unknown>} */ (bid.system || {});
271
+ const refs = /** @type {Record<string, string>} */ (system.connectionRefs || {});
272
+ const tokenRef = refs.token || refs.apiKey || `kv://${systemKey}/token`;
273
+ return [
274
+ `# Environment for ${systemKey} (BID-applied). No plaintext secrets.`,
275
+ `# Resolve with aifabrix secret set ${systemKey}/token "<value>" --shared`,
276
+ `KV_TOKEN=${tokenRef}`,
277
+ ''
278
+ ].join('\n');
279
+ }
280
+
281
+ /**
282
+ * @param {Record<string, unknown>} bid
283
+ * @param {string} systemKey
284
+ * @returns {{
285
+ * systemFile: string,
286
+ * datasourceFiles: string[],
287
+ * application: Record<string, unknown>,
288
+ * system: Record<string, unknown>,
289
+ * dsFiles: Array<{ name: string, body: Record<string, unknown> }>,
290
+ * nextStamp: Record<string, unknown>
291
+ * }}
292
+ */
293
+ function buildPackageArtifacts(bid, systemKey, enrichOptions = {}) {
294
+ const identity = /** @type {Record<string, unknown>} */ (bid.packageIdentity);
295
+ const datasources = /** @type {Record<string, unknown>[]} */ (bid.datasources || []);
296
+ const capabilities = /** @type {Record<string, string[]>} */ (bid.capabilities || {});
297
+ const sync = /** @type {Record<string, unknown>} */ (bid.sync || {});
298
+ const system = /** @type {Record<string, unknown>} */ (bid.system || {});
299
+ const systemOpenapiSourceRef =
300
+ typeof system.openapiSourceRef === 'string' ? system.openapiSourceRef : '';
301
+ const associationTargets = collectAssociationObjectTypes(
302
+ datasources,
303
+ systemOpenapiSourceRef,
304
+ enrichOptions
305
+ );
306
+ const systemFile = `${systemKey}-system.json`;
307
+ const datasourceFiles = datasources.map((ds) => `${systemKey}-datasource-${ds.key}.json`);
308
+ return {
309
+ systemFile,
310
+ datasourceFiles,
311
+ application: buildApplicationFile(bid, datasourceFiles, systemFile),
312
+ system: buildSystemFile(bid),
313
+ dsFiles: datasources.map((ds) => ({
314
+ name: `${systemKey}-datasource-${ds.key}.json`,
315
+ body: buildDatasourceFile(ds, systemKey, capabilities, sync, {
316
+ ...enrichOptions,
317
+ systemOpenapiSourceRef,
318
+ associationTargets
319
+ })
320
+ })),
321
+ envTemplate: buildEnvTemplate(bid),
322
+ nextStamp: {
323
+ schemaName: bid.schemaName,
324
+ schemaVersion: bid.schemaVersion,
325
+ definitionId: bid.definitionId,
326
+ definitionHash: bid.definitionHash,
327
+ suiteRunId: /** @type {any} */ (bid.meta).suiteRunId,
328
+ systemKey,
329
+ packageVersion: identity.packageVersion,
330
+ appliedAt: new Date().toISOString()
331
+ }
332
+ };
333
+ }
334
+
335
+ /**
336
+ * @param {string} outDir
337
+ * @param {ReturnType<typeof buildPackageArtifacts>} artifacts
338
+ * @returns {Promise<void>}
339
+ */
340
+ async function writePackageArtifacts(outDir, artifacts) {
341
+ fsReal.mkdirSync(outDir, { recursive: true });
342
+ fsReal.writeFileSync(
343
+ path.join(outDir, 'application.json'),
344
+ JSON.stringify(artifacts.application, null, 2),
345
+ 'utf8'
346
+ );
347
+ fsReal.writeFileSync(
348
+ path.join(outDir, artifacts.systemFile),
349
+ JSON.stringify(artifacts.system, null, 2),
350
+ 'utf8'
351
+ );
352
+ for (const file of artifacts.dsFiles) {
353
+ fsReal.writeFileSync(path.join(outDir, file.name), JSON.stringify(file.body, null, 2), 'utf8');
354
+ }
355
+ fsReal.writeFileSync(path.join(outDir, 'env.template'), artifacts.envTemplate, 'utf8');
356
+ fsReal.writeFileSync(
357
+ path.join(outDir, STAMP_FILE),
358
+ JSON.stringify(artifacts.nextStamp, null, 2),
359
+ 'utf8'
360
+ );
361
+ }
362
+
363
+ /**
364
+ * @param {unknown} bidInput
365
+ * @returns {Record<string, unknown>}
366
+ */
367
+ function requireValidBid(bidInput) {
368
+ const result = validateIntegrationDefinition(bidInput);
369
+ if (!result.valid) {
370
+ const err = new Error(`Invalid BID: ${result.errors.join('; ')}`);
371
+ err.code = 'BID_INVALID';
372
+ err.errors = result.errors;
373
+ throw err;
374
+ }
375
+ return result.bid;
376
+ }
377
+
378
+ /**
379
+ * @param {Record<string, unknown> | null} stamp
380
+ * @param {Record<string, unknown>} bid
381
+ * @param {string} outDir
382
+ * @param {string} systemKey
383
+ * @returns {Record<string, unknown> | null}
384
+ */
385
+ function idempotentResult(stamp, bid, outDir, systemKey) {
386
+ const identity = /** @type {Record<string, unknown>} */ (bid.packageIdentity);
387
+ if (
388
+ !(
389
+ stamp &&
390
+ stamp.definitionId === bid.definitionId &&
391
+ stamp.definitionHash === bid.definitionHash &&
392
+ fsReal.existsSync(path.join(outDir, 'application.json'))
393
+ )
394
+ ) {
395
+ return null;
396
+ }
397
+ return {
398
+ status: 'idempotent',
399
+ systemKey,
400
+ outputDir: outDir,
401
+ definitionId: bid.definitionId,
402
+ definitionHash: bid.definitionHash,
403
+ packageVersion: identity.packageVersion
404
+ };
405
+ }
406
+
407
+ /**
408
+ * Apply a validated BID to the integration workspace.
409
+ * @async
410
+ * @param {Record<string, unknown>} bidInput
411
+ * @param {{
412
+ * outputDir?: string,
413
+ * workspaceRoot?: string,
414
+ * dryRun?: boolean,
415
+ * force?: boolean,
416
+ * openapiBaseDir?: string,
417
+ * baseDir?: string
418
+ * }} [options]
419
+ * @returns {Promise<Record<string, unknown>>}
420
+ */
421
+ async function applyIntegrationDefinition(bidInput, options = {}) {
422
+ const bid = requireValidBid(bidInput);
423
+ const identity = /** @type {Record<string, unknown>} */ (bid.packageIdentity);
424
+ const systemKey = String(identity.systemKey);
425
+ const outDir =
426
+ options.outputDir ||
427
+ (options.workspaceRoot
428
+ ? path.join(options.workspaceRoot, 'integration', systemKey)
429
+ : getIntegrationPath(systemKey));
430
+ const stamp = await readStamp(outDir);
431
+ const early = options.force ? null : idempotentResult(stamp, bid, outDir, systemKey);
432
+ if (early) {
433
+ return early;
434
+ }
435
+ assertApplyAllowed(bid, stamp);
436
+ // Prefer explicit openapiBaseDir (workspace integration/<systemKey>/openapi
437
+ // from 517.2 persist). Do not default product apply to ship-local enrichment.
438
+ const artifacts = buildPackageArtifacts(bid, systemKey, {
439
+ baseDir: options.openapiBaseDir || options.baseDir
440
+ });
441
+ const fileList = [
442
+ 'application.json',
443
+ artifacts.systemFile,
444
+ ...artifacts.datasourceFiles,
445
+ 'env.template',
446
+ STAMP_FILE
447
+ ];
448
+ if (options.dryRun) {
449
+ return {
450
+ status: 'dryRun',
451
+ systemKey,
452
+ outputDir: outDir,
453
+ files: fileList,
454
+ stamp: artifacts.nextStamp
455
+ };
456
+ }
457
+ await writePackageArtifacts(outDir, artifacts);
458
+ return {
459
+ status: stamp ? 'updated' : 'created',
460
+ systemKey,
461
+ outputDir: outDir,
462
+ definitionId: bid.definitionId,
463
+ definitionHash: bid.definitionHash,
464
+ packageVersion: identity.packageVersion,
465
+ files: fileList
466
+ };
467
+ }
468
+
469
+ module.exports = {
470
+ applyIntegrationDefinition,
471
+ STAMP_FILE,
472
+ buildApplicationFile,
473
+ buildSystemFile,
474
+ buildDatasourceFile,
475
+ buildEnvTemplate
476
+ };