@aifabrix/builder 2.49.0 → 2.49.2

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 (35) hide show
  1. package/.cursor/process/README.md +9 -0
  2. package/.cursor/skills/README.md +14 -0
  3. package/lib/api/dev.api.js +1 -0
  4. package/lib/api/host-ops.api.js +69 -0
  5. package/lib/cli/infra-guided-auth.js +8 -6
  6. package/lib/cli/infra-guided.js +11 -2
  7. package/lib/cli/setup-dev-path-commands.js +24 -0
  8. package/lib/cli/setup-dev.js +108 -14
  9. package/lib/commands/dev-cli-handlers.js +64 -3
  10. package/lib/commands/dev-host-ops.js +239 -0
  11. package/lib/commands/teardown.js +3 -5
  12. package/lib/commands/up-common.js +8 -11
  13. package/lib/commands/up-dataplane.js +81 -13
  14. package/lib/commands/up-miso.js +6 -2
  15. package/lib/schema/external-system.schema.json +114 -2
  16. package/lib/utils/health-check-poll-ui.js +63 -28
  17. package/lib/utils/health-check.js +50 -21
  18. package/lib/utils/paths.js +26 -36
  19. package/package.json +1 -1
  20. package/templates/applications/dataplane/application.yaml +1 -1
  21. package/templates/applications/dataplane/env.template +2 -1
  22. package/templates/applications/miso-controller/env.template +2 -0
  23. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +0 -1
  24. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +0 -17
  25. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +0 -1
  26. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +0 -1
  27. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +0 -1
  28. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +0 -17
  29. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +0 -17
  30. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +0 -17
  31. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +0 -17
  32. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +0 -17
  33. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +0 -17
  34. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +0 -17
  35. package/.temp/validation/summary.txt +0 -5
@@ -26,36 +26,50 @@ function buildHealthCheckPollText(attemptIndex, maxAttempts) {
26
26
  );
27
27
  }
28
28
 
29
- /**
30
- * @param {number} maxAttempts
31
- * @returns {{ usesSpinner: boolean, onWaiting: (attemptIndex: number) => void, finish: () => void }}
32
- */
33
- function createHealthCheckPollHandlers(maxAttempts) {
34
- if (shouldUseHealthCheckPollSpinner()) {
35
- const ora = require('ora');
36
- const spinner = ora({
37
- text: buildHealthCheckPollText(0, maxAttempts),
38
- spinner: 'dots'
39
- });
40
- let started = false;
41
- return {
42
- usesSpinner: true,
43
- onWaiting: (attemptIndex) => {
44
- if (!started) {
45
- spinner.start();
46
- started = true;
47
- }
48
- spinner.text = buildHealthCheckPollText(attemptIndex, maxAttempts);
49
- },
50
- finish: () => {
51
- if (started) {
52
- spinner.stop();
53
- process.stdout.write('\n');
54
- }
29
+ function createSilentHealthCheckPollHandlers() {
30
+ return {
31
+ usesSpinner: false,
32
+ onWaiting: () => {},
33
+ finish: () => {}
34
+ };
35
+ }
36
+
37
+ function createDelegatedHealthCheckPollHandlers(maxAttempts, onPollText) {
38
+ return {
39
+ usesSpinner: false,
40
+ onWaiting: (attemptIndex) => {
41
+ onPollText(buildHealthCheckPollText(attemptIndex, maxAttempts));
42
+ },
43
+ finish: () => {}
44
+ };
45
+ }
46
+
47
+ function createOraHealthCheckPollHandlers(maxAttempts) {
48
+ const ora = require('ora');
49
+ const spinner = ora({
50
+ text: buildHealthCheckPollText(0, maxAttempts),
51
+ spinner: 'dots'
52
+ });
53
+ let started = false;
54
+ return {
55
+ usesSpinner: true,
56
+ onWaiting: (attemptIndex) => {
57
+ if (!started) {
58
+ spinner.start();
59
+ started = true;
55
60
  }
56
- };
57
- }
61
+ spinner.text = buildHealthCheckPollText(attemptIndex, maxAttempts);
62
+ },
63
+ finish: () => {
64
+ if (started) {
65
+ spinner.stop();
66
+ process.stdout.write('\n');
67
+ }
68
+ }
69
+ };
70
+ }
58
71
 
72
+ function createCarriageReturnHealthCheckPollHandlers(maxAttempts) {
59
73
  let lastLine = '';
60
74
  return {
61
75
  usesSpinner: false,
@@ -77,6 +91,27 @@ function createHealthCheckPollHandlers(maxAttempts) {
77
91
  };
78
92
  }
79
93
 
94
+ /**
95
+ * @param {number} maxAttempts
96
+ * @param {{ silent?: boolean, onPollText?: (text: string) => void }} [options]
97
+ * @returns {{ usesSpinner: boolean, onWaiting: (attemptIndex: number) => void, finish: () => void }}
98
+ */
99
+ function createHealthCheckPollHandlers(maxAttempts, options = {}) {
100
+ const silent = options && options.silent === true;
101
+ const onPollText = options && typeof options.onPollText === 'function' ? options.onPollText : null;
102
+
103
+ if (silent) {
104
+ return createSilentHealthCheckPollHandlers();
105
+ }
106
+ if (onPollText) {
107
+ return createDelegatedHealthCheckPollHandlers(maxAttempts, onPollText);
108
+ }
109
+ if (shouldUseHealthCheckPollSpinner()) {
110
+ return createOraHealthCheckPollHandlers(maxAttempts);
111
+ }
112
+ return createCarriageReturnHealthCheckPollHandlers(maxAttempts);
113
+ }
114
+
80
115
  module.exports = {
81
116
  buildHealthCheckPollText,
82
117
  createHealthCheckPollHandlers,
@@ -331,29 +331,26 @@ async function performHealthCheckAttemptForUrls(
331
331
  return { ok: false, resolvedIndex: -1 };
332
332
  }
333
333
 
334
- async function waitForHealthCheck(appName, timeout = 90, port = null, config = null, debug = false, runOptions = {}) {
335
- await waitForDbInit(appName);
336
-
337
- const healthCheckPort = await determineHealthCheckPort(port, appName, debug);
338
- const { maxAttempts } = buildHealthCheckConfig(healthCheckPort, config, timeout, debug);
339
- const { urlsToTry, skippedPublicHealthUrl } = await computePreferredHealthCheckUrls(
340
- appName,
341
- healthCheckPort,
342
- config,
343
- runOptions,
344
- debug
345
- );
346
-
347
- if (skippedPublicHealthUrl && urlsToTry.length === 1 && debug) {
348
- logger.log(
349
- chalk.gray(
350
- `ℹ Health check: public URL not used (DNS): ${skippedPublicHealthUrl}. ` +
351
- `Probing ${urlsToTry[0]} only until the app responds.`
352
- )
353
- );
334
+ function resolveHealthCheckPollOptions(runOptions = {}) {
335
+ const pollOptions = {};
336
+ if (runOptions.silentPoll === true) {
337
+ pollOptions.silent = true;
338
+ }
339
+ if (typeof runOptions.onPollText === 'function') {
340
+ pollOptions.onPollText = runOptions.onPollText;
354
341
  }
342
+ return pollOptions;
343
+ }
355
344
 
356
- const poll = createHealthCheckPollHandlers(maxAttempts);
345
+ async function pollUntilHealthCheckPasses({
346
+ urlsToTry,
347
+ maxAttempts,
348
+ timeout,
349
+ debug,
350
+ skippedPublicHealthUrl,
351
+ pollOptions = {}
352
+ }) {
353
+ const poll = createHealthCheckPollHandlers(maxAttempts, pollOptions);
357
354
  const pollAttemptOpts = { suppressSuccessLog: true };
358
355
 
359
356
  for (let attempts = 0; attempts < maxAttempts; attempts++) {
@@ -389,6 +386,38 @@ async function waitForHealthCheck(appName, timeout = 90, port = null, config = n
389
386
  throw new Error(`Health check timeout after ${timeout} seconds`);
390
387
  }
391
388
 
389
+ async function waitForHealthCheck(appName, timeout = 90, port = null, config = null, debug = false, runOptions = {}) {
390
+ await waitForDbInit(appName);
391
+
392
+ const healthCheckPort = await determineHealthCheckPort(port, appName, debug);
393
+ const { maxAttempts } = buildHealthCheckConfig(healthCheckPort, config, timeout, debug);
394
+ const { urlsToTry, skippedPublicHealthUrl } = await computePreferredHealthCheckUrls(
395
+ appName,
396
+ healthCheckPort,
397
+ config,
398
+ runOptions,
399
+ debug
400
+ );
401
+
402
+ if (skippedPublicHealthUrl && urlsToTry.length === 1 && debug) {
403
+ logger.log(
404
+ chalk.gray(
405
+ `ℹ Health check: public URL not used (DNS): ${skippedPublicHealthUrl}. ` +
406
+ `Probing ${urlsToTry[0]} only until the app responds.`
407
+ )
408
+ );
409
+ }
410
+
411
+ await pollUntilHealthCheckPasses({
412
+ urlsToTry,
413
+ maxAttempts,
414
+ timeout,
415
+ debug,
416
+ skippedPublicHealthUrl,
417
+ pollOptions: resolveHealthCheckPollOptions(runOptions)
418
+ });
419
+ }
420
+
392
421
  /**
393
422
  * Checks if port is available
394
423
  * @param {number} port - Port number to check
@@ -459,13 +459,23 @@ function isSystemBuilderAppName(appName) {
459
459
  }
460
460
 
461
461
  /**
462
- * `builder/<appName>` under {@link getAppsMaterializationParent} (same tree as {@link getSystemBuilderRoot}).
462
+ * Home / work materialization path: `(aifabrix-work | aifabrix-home)/builder/<appName>`.
463
+ * Used by setup, teardown, and template copy — never the product-repo `cwd/builder` tree.
463
464
  *
464
465
  * @param {string} appName
465
466
  * @returns {string}
466
467
  */
468
+ function getHomeBuilderAppPath(appName) {
469
+ return path.join(getAppsMaterializationParent(), 'builder', appName);
470
+ }
471
+
472
+ /**
473
+ * @deprecated Prefer {@link getHomeBuilderAppPath}; same path.
474
+ * @param {string} appName
475
+ * @returns {string}
476
+ */
467
477
  function getProjectBuilderAppPath(appName) {
468
- return path.join(getIntegrationBuilderBaseDir(), 'builder', appName);
478
+ return getHomeBuilderAppPath(appName);
469
479
  }
470
480
 
471
481
  /**
@@ -630,38 +640,25 @@ function resolveBuildContext(configDir, buildContext) {
630
640
  }
631
641
 
632
642
  /**
633
- * `cwd/builder/<appName>` when present as a directory and allowed (platform empty stubs skipped).
643
+ * `cwd/builder/<appName>` when the running folder has a resolvable application manifest.
634
644
  *
635
645
  * @param {string} appName
636
646
  * @returns {string|null}
637
647
  */
638
- function tryCwdBuilderPathOrNull(appName) {
639
- const cwdApp = path.join(path.resolve(process.cwd()), 'builder', appName);
640
- try {
641
- if (!nodeFs().existsSync(cwdApp)) {
642
- return null;
643
- }
644
- const st = nodeFs().statSync(cwdApp);
645
- if (!st || typeof st.isDirectory !== 'function' || !st.isDirectory()) {
646
- return null;
647
- }
648
- if (isSystemBuilderAppName(appName) && !isProjectBuilderAppDirectory(cwdApp)) {
649
- return null;
650
- }
651
- return cwdApp;
652
- } catch {
648
+ function getCwdBuilderAppPath(appName) {
649
+ if (!appName || typeof appName !== 'string') {
653
650
  return null;
654
651
  }
652
+ const cwdApp = path.join(path.resolve(process.cwd()), 'builder', appName);
653
+ return isProjectBuilderAppDirectory(cwdApp) ? cwdApp : null;
655
654
  }
656
655
 
657
656
  /**
658
- * Gets the builder folder path:
659
- * 1. Plan 141 Tier‑1: `cwd/builder/<app>` or `cwd/integration/<app>` when a resolvable application manifest exists.
660
- * 2. Else **`cwd/builder/<app>`** when that directory exists. For **platform** apps (`keycloak`,
661
- * `miso-controller`, `dataplane`), an **empty** cwd stub is ignored so materialization under
662
- * `(work|home)/builder` still wins.
663
- * 3. Else **`(aifabrix-work or aifabrix-home)/builder/<appName>`** — used by setup / `up-platform` /
664
- * `up-miso` / `up-dataplane` for template materialization.
657
+ * Gets the builder folder path for run/deploy/register:
658
+ * 1. **`cwd/builder/<app>`** when a resolvable application manifest exists in the running folder.
659
+ * 2. Else **`(aifabrix-work | aifabrix-home)/builder/<app>`** (setup materializes templates there).
660
+ *
661
+ * Teardown and setup `--force` clean use {@link getHomeBuilderAppPath} only (never product-repo trees).
665
662
  *
666
663
  * @param {string} appName - Application name
667
664
  * @returns {string} Absolute path to builder directory
@@ -670,20 +667,11 @@ function getBuilderPath(appName) {
670
667
  if (!appName || typeof appName !== 'string') {
671
668
  throw new Error('App name is required and must be a string');
672
669
  }
673
- const { resolveApplicationManifestPathSync } = require('./manifest-location');
674
- const manifestHit = resolveApplicationManifestPathSync({
675
- targetKey: appName,
676
- mode: 'auto',
677
- cwd: process.cwd()
678
- });
679
- if (manifestHit && (manifestHit.tier === 'cwd-builder' || manifestHit.tier === 'cwd-integration')) {
680
- return manifestHit.absolutePath;
681
- }
682
- const cwdHit = tryCwdBuilderPathOrNull(appName);
670
+ const cwdHit = getCwdBuilderAppPath(appName);
683
671
  if (cwdHit) {
684
672
  return cwdHit;
685
673
  }
686
- return path.join(getAppsMaterializationParent(), 'builder', appName);
674
+ return getHomeBuilderAppPath(appName);
687
675
  }
688
676
 
689
677
  /**
@@ -918,6 +906,8 @@ module.exports = {
918
906
  getIntegrationBuilderBaseDir,
919
907
  SYSTEM_BUILDER_APP_KEYS,
920
908
  isSystemBuilderAppName,
909
+ getHomeBuilderAppPath,
910
+ getCwdBuilderAppPath,
921
911
  getProjectBuilderAppPath,
922
912
  getSystemPlatformMaterializationParent,
923
913
  getSystemBuilderRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.49.0",
3
+ "version": "2.49.2",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -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: 1.9.5
8
+ version: 2.0.0
9
9
 
10
10
  # Image Configuration
11
11
  # Set tag to match your build (e.g. aifabrix build dataplane -t 1.0.0 then tag: 1.0.0)
@@ -26,6 +26,7 @@ LOCAL_MODE=false
26
26
  # Celery worker process tuning (dataplane-worker container; optional overrides)
27
27
  # CELERY_WORKER_QUEUES=external_data,document_sync,document_processing,audit
28
28
  # CELERY_WORKER_CONCURRENCY=2
29
+ # CELERY_WORKER_RUNTIME is set automatically by python -m app.core.celery_worker_main (NullPool per task).
29
30
  # CELERY_LOG_LEVEL=info
30
31
 
31
32
  # When API_KEY is set, a matching Bearer token bypasses OAuth2 validation
@@ -42,7 +43,7 @@ API_KEY_BYPASS_ROLES=full-access
42
43
 
43
44
  # API Configuration
44
45
  API_V1_STR=/api/v1
45
- VERSION=1.9.5
46
+ VERSION=2.0.0
46
47
  # Minimum supported aifabrix-builder CLI semver (advertised on /health and /api/v1/health; empty = no gate)
47
48
  # MIN_BUILDER_CLI_VERSION=2.45.0
48
49
  MIN_BUILDER_CLI_VERSION=2.45.5
@@ -157,6 +157,8 @@ WAIT_FOR_KEYCLOAK=true
157
157
  # For production: use public domain (e.g., https://miso.example.com)
158
158
  # url://public includes front-door path from application.yaml (e.g. /controller).
159
159
  MISO_WEB_SERVER_URL=url://public
160
+ # Provider-grant OAuth redirect_uri base (Azure/start-dev also set this explicitly; code falls back to MISO_WEB_SERVER_URL)
161
+ CONTROLLER_BASE_URL=url://public
160
162
  MISO_CONTROLLER_URL=url://internal
161
163
  MISO_RELATIVE_PATH=url://vdir-public
162
164
  ALLOWED_ORIGINS=http://localhost:*,url://host-public,url://host-private,url://dataplane-host-public,url://dataplane-host-private
@@ -1 +0,0 @@
1
- {"apiVersion":"dataplane.aifabrix.ai/v1","kind":"Protection","metadata":{"key":"hubspot-country-sales","displayName":"HubSpot Country Sales Access"},"spec":{"enabled":true,"datasourceKey":"hubspot-companies","mode":"replaceForSource","rules":[{"key":"sales-country-users","principal":{"type":"group","expression":"Sales {{fk.country.metadata.iso3}} Users"},"grants":[{"dimensionKey":"country","valueExpression":"{{fk.country.metadata.iso2}}"}]}]}}
@@ -1,17 +0,0 @@
1
- apiVersion: dataplane.aifabrix.ai/v1
2
- kind: Protection
3
- metadata:
4
- key: hubspot-country-sales
5
- displayName: HubSpot Country Sales Access
6
- spec:
7
- enabled: true
8
- datasourceKey: hubspot-companies
9
- mode: replaceForSource
10
- rules:
11
- - key: sales-country-users
12
- principal:
13
- type: group
14
- expression: "Sales {{fk.country.metadata.iso3}} Users"
15
- grants:
16
- - dimensionKey: country
17
- valueExpression: "{{fk.country.metadata.iso2}}"
@@ -1 +0,0 @@
1
- {"apiVersion":"dataplane.aifabrix.ai/v1","kind":"Protection","metadata":{"key":"hubspot-country-sales","displayName":"HubSpot Country Sales Access"},"spec":{"enabled":true,"datasourceKey":"hubspot-companies","mode":"replaceForSource","rules":[{"key":"sales-country-users","principal":{"type":"group","expression":"Sales {{fk.country.metadata.iso3}} Users"},"grants":[{"dimensionKey":"country","valueExpression":"{{fk.country.metadata.iso2}}"}]}]}}
@@ -1,17 +0,0 @@
1
- apiVersion: dataplane.aifabrix.ai/v1
2
- kind: Protection
3
- metadata:
4
- key: hubspot-country-sales
5
- displayName: HubSpot Country Sales Access
6
- spec:
7
- enabled: true
8
- datasourceKey: hubspot-companies
9
- mode: replaceForSource
10
- rules:
11
- - key: sales-country-users
12
- principal:
13
- type: group
14
- expression: "Sales {{fk.country.metadata.iso3}} Users"
15
- grants:
16
- - dimensionKey: country
17
- valueExpression: "{{fk.country.metadata.iso2}}"
@@ -1,17 +0,0 @@
1
- apiVersion: dataplane.aifabrix.ai/v1
2
- kind: Protection
3
- metadata:
4
- key: hubspot-country-sales
5
- displayName: HubSpot Country Sales Access
6
- spec:
7
- enabled: true
8
- datasourceKey: hubspot-companies
9
- mode: replaceForSource
10
- rules:
11
- - key: sales-country-users
12
- principal:
13
- type: group
14
- expression: "Sales {{fk.country.metadata.iso3}} Users"
15
- grants:
16
- - dimensionKey: country
17
- valueExpression: "{{fk.country.metadata.iso2}}"
@@ -1,17 +0,0 @@
1
- apiVersion: dataplane.aifabrix.ai/v1
2
- kind: Protection
3
- metadata:
4
- key: hubspot-country-sales
5
- displayName: HubSpot Country Sales Access
6
- spec:
7
- enabled: true
8
- datasourceKey: hubspot-companies
9
- mode: replaceForSource
10
- rules:
11
- - key: sales-country-users
12
- principal:
13
- type: group
14
- expression: "Sales {{fk.country.metadata.iso3}} Users"
15
- grants:
16
- - dimensionKey: country
17
- valueExpression: "{{fk.country.metadata.iso2}}"
@@ -1,17 +0,0 @@
1
- apiVersion: dataplane.aifabrix.ai/v1
2
- kind: Protection
3
- metadata:
4
- key: hubspot-country-sales
5
- displayName: HubSpot Country Sales Access
6
- spec:
7
- enabled: true
8
- datasourceKey: hubspot-companies
9
- mode: replaceForSource
10
- rules:
11
- - key: sales-country-users
12
- principal:
13
- type: group
14
- expression: "Sales {{fk.country.metadata.iso3}} Users"
15
- grants:
16
- - dimensionKey: country
17
- valueExpression: "{{fk.country.metadata.iso2}}"
@@ -1,17 +0,0 @@
1
- apiVersion: dataplane.aifabrix.ai/v1
2
- kind: Protection
3
- metadata:
4
- key: hubspot-country-sales
5
- displayName: HubSpot Country Sales Access
6
- spec:
7
- enabled: true
8
- datasourceKey: hubspot-companies
9
- mode: replaceForSource
10
- rules:
11
- - key: sales-country-users
12
- principal:
13
- type: group
14
- expression: "Sales {{fk.country.metadata.iso3}} Users"
15
- grants:
16
- - dimensionKey: country
17
- valueExpression: "{{fk.country.metadata.iso2}}"
@@ -1,17 +0,0 @@
1
- apiVersion: dataplane.aifabrix.ai/v1
2
- kind: Protection
3
- metadata:
4
- key: hubspot-country-sales
5
- displayName: HubSpot Country Sales Access
6
- spec:
7
- enabled: true
8
- datasourceKey: hubspot-companies
9
- mode: replaceForSource
10
- rules:
11
- - key: sales-country-users
12
- principal:
13
- type: group
14
- expression: "Sales {{fk.country.metadata.iso3}} Users"
15
- grants:
16
- - dimensionKey: country
17
- valueExpression: "{{fk.country.metadata.iso2}}"
@@ -1,17 +0,0 @@
1
- apiVersion: dataplane.aifabrix.ai/v1
2
- kind: Protection
3
- metadata:
4
- key: hubspot-country-sales
5
- displayName: HubSpot Country Sales Access
6
- spec:
7
- enabled: true
8
- datasourceKey: hubspot-companies
9
- mode: replaceForSource
10
- rules:
11
- - key: sales-country-users
12
- principal:
13
- type: group
14
- expression: "Sales {{fk.country.metadata.iso3}} Users"
15
- grants:
16
- - dimensionKey: country
17
- valueExpression: "{{fk.country.metadata.iso2}}"
@@ -1,5 +0,0 @@
1
- lint:fix exit=0
2
- lint exit=0
3
- test exit=0
4
- miso typecheck exit=0
5
- miso build exit=0