@aifabrix/builder 2.55.2 → 2.57.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 (88) hide show
  1. package/lib/agent-kit/session-report.js +76 -0
  2. package/lib/agent-kit/start.js +124 -57
  3. package/lib/agent-kit/tmux-sessions.js +163 -0
  4. package/lib/api/environments.api.js +38 -0
  5. package/lib/api/marketplace-bootstrap.api.js +31 -0
  6. package/lib/api/pipeline.api.js +32 -3
  7. package/lib/app/deploy.js +6 -5
  8. package/lib/cli/setup-app.js +7 -6
  9. package/lib/cli/setup-environment.js +76 -19
  10. package/lib/cli/setup-external-system-help.js +15 -3
  11. package/lib/cli/setup-external-system.js +21 -3
  12. package/lib/commands/agent-kit-help.js +32 -15
  13. package/lib/commands/agent-kit.js +90 -8
  14. package/lib/commands/repair-datasource-auto-rbac.js +4 -9
  15. package/lib/commands/upload-openapi-sync-followup.js +59 -0
  16. package/lib/commands/upload-qualification-client.js +131 -0
  17. package/lib/commands/upload-qualification-followups.js +104 -0
  18. package/lib/commands/upload-qualification-poll.js +143 -0
  19. package/lib/commands/upload.js +37 -156
  20. package/lib/deployment/deploy-poll-ui.js +14 -10
  21. package/lib/deployment/deployer-status.js +4 -4
  22. package/lib/deployment/installation/azure-infra-stage.js +160 -0
  23. package/lib/deployment/installation/azure-preflight.js +102 -0
  24. package/lib/deployment/installation/azure-readiness.js +71 -0
  25. package/lib/deployment/installation/docker-register.js +112 -0
  26. package/lib/deployment/installation/environment-auth.js +76 -0
  27. package/lib/deployment/installation/environment-manifest.js +165 -0
  28. package/lib/deployment/installation/environment-stage.js +191 -0
  29. package/lib/deployment/installation/index.js +39 -0
  30. package/lib/deployment/installation/infra-catalog.js +165 -0
  31. package/lib/deployment/installation/infra-manifest.js +161 -0
  32. package/lib/deployment/installation/infra-projection.js +86 -0
  33. package/lib/deployment/installation/public-ip.js +65 -0
  34. package/lib/deployment/installation/stage-input.js +169 -0
  35. package/lib/deployment/installation/stage-output.js +53 -0
  36. package/lib/deployment/installation/workspace-env.js +76 -0
  37. package/lib/deployment/installation/workspace-files.js +178 -0
  38. package/lib/deployment/installation/workspace-schema.js +40 -0
  39. package/lib/deployment/installation/workspace-secrets.js +66 -0
  40. package/lib/deployment/installation/workspace.js +128 -0
  41. package/lib/deployment/marketplace-environment.js +305 -0
  42. package/lib/external-system/deploy.js +46 -51
  43. package/lib/generator/builders.js +4 -0
  44. package/lib/generator/helpers.js +55 -99
  45. package/lib/generator/index.js +0 -4
  46. package/lib/generator/split-variables.js +11 -5
  47. package/lib/internal/fs-real-sync.js +29 -0
  48. package/lib/programmatic/builder-api-request.js +8 -4
  49. package/lib/programmatic/capability-registry.js +2 -0
  50. package/lib/programmatic/get-deploy-status.js +45 -0
  51. package/lib/programmatic/get-upload-status.js +45 -0
  52. package/lib/programmatic/http-accepted-response.js +31 -0
  53. package/lib/programmatic/openapi-descriptions.js +14 -4
  54. package/lib/programmatic/request-timeout.js +21 -15
  55. package/lib/programmatic/route-handler-map.js +6 -0
  56. package/lib/programmatic/run-deploy.js +7 -3
  57. package/lib/programmatic/run-upload.js +11 -7
  58. package/lib/role-assistant/package-validate.js +4 -0
  59. package/lib/role-assistant/test-runner-clarification-resubmit.js +149 -0
  60. package/lib/role-assistant/test-runner.js +14 -17
  61. package/lib/schema/application-schema.json +20 -2
  62. package/lib/schema/infra-parameter.schema.json +7 -0
  63. package/lib/schema/infra.parameter.yaml +32 -43
  64. package/lib/schema/infrastructure-schema.json +1480 -399
  65. package/lib/schema/installation-environment.schema.json +601 -0
  66. package/lib/utils/cli-utils.js +3 -2
  67. package/lib/utils/qualification-poll-ui.js +136 -0
  68. package/lib/utils/variable-transformer.js +3 -0
  69. package/lib/validation/required-variables.js +41 -0
  70. package/lib/validation/validator.js +8 -5
  71. package/package.json +7 -2
  72. package/templates/README.md +4 -0
  73. package/templates/agent-kit/instructions/AGENTKIT.md +6 -6
  74. package/templates/agent-kit/skills/shared/hosts.md +10 -10
  75. package/templates/applications/builder-api/env.template +4 -0
  76. package/templates/applications/dataplane/application.yaml +1 -1
  77. package/templates/applications/dataplane/env.template +1 -1
  78. package/templates/applications/keycloak/Dockerfile +12 -5
  79. package/templates/applications/keycloak/application.yaml +2 -2
  80. package/templates/applications/keycloak/cimd-lenient-parse/AbstractClientIdMetadataDocumentExecutor.java +3 -3
  81. package/templates/applications/keycloak/cimd-lenient-parse/SOURCE.md +6 -6
  82. package/templates/applications/keycloak/env.template +13 -1
  83. package/templates/applications/miso-controller/application.yaml +3 -2
  84. package/templates/applications/miso-controller/env.template +28 -22
  85. package/templates/marketplace/createUiDefinition.json +77 -0
  86. package/templates/marketplace/install.parameters.example.json +9 -0
  87. package/templates/marketplace/main.json +4087 -0
  88. package/lib/generator/deploy-manifest-azure-kv.js +0 -81
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @fileoverview Resumable Dataplane qualification upload: accept, --wait/--no-wait, poll, resume.
3
+ * Mirrors dev-host-ops.js's job id + wait/no-wait contract (plan 191.0 task 1). Never invents
4
+ * qualification truth — accept/poll surface exactly what Dataplane returns.
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const logger = require('../utils/logger');
12
+ const { formatSuccessLine, formatWarningLine } = require('../utils/cli-test-layout-chalk');
13
+ const { formatApiError } = require('../utils/api-error-handler');
14
+ const { uploadApplicationViaPipelineWithAuthRetry } = require('../utils/pipeline-upload-auth-retry');
15
+ const { pollQualificationJobUntilTerminal } = require('./upload-qualification-poll');
16
+ const { createQualificationPollHandlers } = require('../utils/qualification-poll-ui');
17
+
18
+ /**
19
+ * Ctrl-C / disconnect during polling must detach without corrupting truth (plan 191.0 task 4):
20
+ * the qualification job keeps running on Dataplane regardless of whether this CLI process exits.
21
+ * This only makes that safe fact visible, printing the resume command before exiting 130.
22
+ * @param {string} jobId
23
+ * @param {string} systemKey
24
+ * @returns {() => void} Call to remove the listeners once polling finishes normally.
25
+ */
26
+ function registerUploadInterruptHint(jobId, systemKey) {
27
+ const onSignal = () => {
28
+ logger.log(
29
+ formatWarningLine(
30
+ `Interrupted — upload job ${jobId} is still running, unaffected. Resume with: aifabrix upload ${systemKey} --resume ${jobId}`
31
+ )
32
+ );
33
+ process.exit(130);
34
+ };
35
+ process.once('SIGINT', onSignal);
36
+ process.once('SIGTERM', onSignal);
37
+ return () => {
38
+ process.removeListener('SIGINT', onSignal);
39
+ process.removeListener('SIGTERM', onSignal);
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Submit the candidate for durable Dataplane qualification (async). Always returns quickly —
45
+ * a 202 accept, or the current state when an equivalent job is already in flight — never blocks
46
+ * for the ladder to finish.
47
+ * @param {string} dataplaneUrl
48
+ * @param {Object} authConfig
49
+ * @param {Object} payload
50
+ * @param {string} systemKey
51
+ * @param {Object} [options] - Forwarded to uploadApplicationViaPipeline (e.g. timeoutMs, ignored when async)
52
+ * @returns {Promise<{jobId: string, state: string, resumed: boolean, candidateFingerprint: string}>}
53
+ */
54
+ async function acceptQualificationUpload(dataplaneUrl, authConfig, payload, systemKey, options = {}) {
55
+ const res = await uploadApplicationViaPipelineWithAuthRetry(dataplaneUrl, authConfig, payload, systemKey, {
56
+ ...options,
57
+ async: true
58
+ });
59
+ if (res?.success === false) {
60
+ const err = new Error(formatApiError(res, dataplaneUrl));
61
+ const status = Number(res.status || res.statusCode);
62
+ err.statusCode = status >= 400 && status < 600 ? status : 400;
63
+ throw err;
64
+ }
65
+ return res.data;
66
+ }
67
+
68
+ /**
69
+ * Poll an existing qualification job id to terminal state (resume without re-uploading).
70
+ * @param {string} dataplaneUrl
71
+ * @param {Object} authConfig
72
+ * @param {string} jobId
73
+ * @param {string} systemKey - For the printed resume hint if interrupted mid-poll.
74
+ * @param {{ json?: boolean, silent?: boolean, timeoutMs?: number|string }} [options] - `timeoutMs`
75
+ * bounds only how long this CLI process waits (CLI `--timeout`); Dataplane keeps running the
76
+ * job regardless, so timing out returns a resumable jobId, never a fabricated failure.
77
+ * @returns {Promise<Object>} Terminal job status
78
+ */
79
+ async function resumeQualificationUpload(dataplaneUrl, authConfig, jobId, systemKey, options = {}) {
80
+ const pollUi = options.json || options.silent
81
+ ? createQualificationPollHandlers(jobId, { silent: true })
82
+ : createQualificationPollHandlers(jobId, { verbose: options.verbose === true });
83
+ const unregisterInterruptHint = registerUploadInterruptHint(jobId, systemKey);
84
+ try {
85
+ return await pollQualificationJobUntilTerminal(
86
+ dataplaneUrl,
87
+ authConfig,
88
+ jobId,
89
+ pollUi,
90
+ options.timeoutMs !== undefined ? Number(options.timeoutMs) : undefined
91
+ );
92
+ } finally {
93
+ pollUi.finish();
94
+ unregisterInterruptHint();
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Accept then, unless `--no-wait`, poll to terminal. On `--no-wait`, prints/returns the
100
+ * resumable job id immediately and never blocks.
101
+ * @param {string} dataplaneUrl
102
+ * @param {Object} authConfig
103
+ * @param {Object} payload
104
+ * @param {string} systemKey
105
+ * @param {{ wait?: boolean, json?: boolean, timeoutMs?: number|string }} [options]
106
+ * @returns {Promise<{jobId: string, candidateFingerprint: string, terminal: Object|null}>}
107
+ */
108
+ async function runQualificationUpload(dataplaneUrl, authConfig, payload, systemKey, options = {}) {
109
+ const accepted = await acceptQualificationUpload(dataplaneUrl, authConfig, payload, systemKey, options);
110
+ const wait = options.wait !== false;
111
+ if (!wait) {
112
+ if (options.json) {
113
+ logger.log(JSON.stringify({ ...accepted, terminal: null }, null, 2));
114
+ } else {
115
+ logger.log(
116
+ formatSuccessLine(
117
+ `Upload queued (job ${accepted.jobId}). Resume with: aifabrix upload ${systemKey} --resume ${accepted.jobId}`
118
+ )
119
+ );
120
+ }
121
+ return { ...accepted, terminal: null };
122
+ }
123
+ const terminal = await resumeQualificationUpload(dataplaneUrl, authConfig, accepted.jobId, systemKey, options);
124
+ return { ...accepted, terminal };
125
+ }
126
+
127
+ module.exports = {
128
+ acceptQualificationUpload,
129
+ resumeQualificationUpload,
130
+ runQualificationUpload
131
+ };
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @fileoverview Post-qualification follow-ups for the async upload path: credential push,
3
+ * local OpenAPI sync, sidecar governance/evidence sync, probe and a qualification-specific
4
+ * summary (plan 191.0 task 1).
5
+ *
6
+ * Note: this intentionally does not reconstruct the legacy tiered datasource-readiness summary
7
+ * (`logUploadReadinessSummary`) that the synchronous path shows — that summary classifies real
8
+ * server-assigned datasource state (status/isActive/mcpContract) which requires a fresh
9
+ * Dataplane fetch+remap not yet ported to this path. The qualification summary below reports
10
+ * what Dataplane actually returned instead of approximating server state from local data.
11
+ * @author AI Fabrix Team
12
+ * @version 1.0.0
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ const logger = require('../utils/logger');
18
+ const { formatSuccessLine, metadata, sectionTitle } = require('../utils/cli-test-layout-chalk');
19
+ const { pushAndLogCredentialSecrets } = require('./upload-credential-push-log');
20
+ const { finalizeUploadPublishResult } = require('./upload-data-sync-finalize');
21
+ const { runUploadSidecarFollowups } = require('./upload-sidecar-followups');
22
+ const { logAndSyncLocalOpenApiForUpload } = require('./upload-openapi-sync-followup');
23
+ const { maybeRunUploadProbe } = require('./upload-probe');
24
+
25
+ /**
26
+ * @param {string} systemKey
27
+ * @param {Object} _accepted - Machine-readable qualification identity (not shown in CLI output)
28
+ * @param {Object} job - Terminal PublishJobStatusResponse data
29
+ * @param {Object} options
30
+ */
31
+ function logQualifiedUploadSummary(systemKey, _accepted, job, options) {
32
+ if (options.suppressSummary === true || options.json === true) {
33
+ return;
34
+ }
35
+ logger.log('');
36
+ logger.log(sectionTitle('Upload complete'));
37
+ logger.log(metadata(`System: ${systemKey}`));
38
+ if (options.verbose === true) {
39
+ logger.log(metadata(`Job: ${job.jobId}`));
40
+ const certificateIds = job.result && Array.isArray(job.result.certificateIds) ? job.result.certificateIds : [];
41
+ if (certificateIds.length > 0) {
42
+ logger.log(metadata(`Certificates: ${certificateIds.join(', ')}`));
43
+ }
44
+ }
45
+ logger.log(formatSuccessLine(`${systemKey} uploaded successfully.`));
46
+ }
47
+
48
+ /**
49
+ * Runs the same downstream follow-ups as the synchronous publish path (credential push, local
50
+ * OpenAPI sync, sidecar governance/evidence sync, optional probe) once a qualification job has
51
+ * reached `completed`, then finalizes with the same result-envelope shape as the sync path.
52
+ * @param {string} systemKey
53
+ * @param {Object} options
54
+ * @param {Object} manifest
55
+ * @param {Object} payload
56
+ * @param {{ dataplaneUrl: string, authConfig: Object, environment: string, accepted: Object, job: Object }} ctx
57
+ * @returns {Promise<Object>}
58
+ */
59
+ async function completeQualifiedUpload(systemKey, options, manifest, payload, ctx) {
60
+ const quietCompact = options.minimal === true && options.verbose !== true;
61
+ const pushMeta = await pushAndLogCredentialSecrets(ctx.dataplaneUrl, ctx.authConfig, systemKey, payload, {
62
+ json: options.json === true,
63
+ pathOptions: options.pathOptions,
64
+ suppressCredentialLog: quietCompact,
65
+ excludeSecretKeys: options.excludeSecretKeys
66
+ });
67
+ await logAndSyncLocalOpenApiForUpload(systemKey, manifest, {
68
+ dataplaneUrl: ctx.dataplaneUrl,
69
+ authConfig: ctx.authConfig,
70
+ pathOptions: options.pathOptions,
71
+ quiet: quietCompact
72
+ });
73
+ const sidecar = await runUploadSidecarFollowups({
74
+ systemKey,
75
+ options,
76
+ payload,
77
+ dataplaneUrl: ctx.dataplaneUrl,
78
+ authConfig: ctx.authConfig,
79
+ environment: ctx.environment
80
+ });
81
+ logQualifiedUploadSummary(systemKey, ctx.accepted, ctx.job, options);
82
+ if (options.probe === true) {
83
+ await maybeRunUploadProbe(ctx.dataplaneUrl, systemKey, ctx.authConfig, options.probeTimeout, {
84
+ compact: quietCompact
85
+ });
86
+ }
87
+ return finalizeUploadPublishResult(
88
+ {
89
+ dataplaneUrl: ctx.dataplaneUrl,
90
+ authConfig: ctx.authConfig,
91
+ environment: ctx.environment,
92
+ uploadSkipped: false,
93
+ sidecar,
94
+ credentialPush: pushMeta.credentialPush,
95
+ configPush: pushMeta.configPush,
96
+ jobId: ctx.job.jobId,
97
+ candidateFingerprint: ctx.accepted.candidateFingerprint
98
+ },
99
+ payload,
100
+ options
101
+ );
102
+ }
103
+
104
+ module.exports = { completeQualifiedUpload, logQualifiedUploadSummary };
@@ -0,0 +1,143 @@
1
+ /**
2
+ * @fileoverview Poll a Dataplane qualification (publish) job until terminal (plan 191.0 task 1).
3
+ * Never invents success and never marks the candidate failed just because the CLI stopped polling —
4
+ * timing out returns a resumable jobId instead.
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { getPublishJobStatus } = require('../api/pipeline.api');
12
+ const { formatApiError } = require('../utils/api-error-handler');
13
+
14
+ const POLL_INTERVAL_MS = 3000;
15
+ const POLL_TIMEOUT_MS = 30 * 60 * 1000;
16
+ const TRANSIENT_HTTP_STATUSES = new Set([502, 503, 504]);
17
+ const MAX_TRANSIENT_POLL_ERRORS = 20;
18
+
19
+ const GOVERNANCE_SCENARIOS_MISSING = 'GOVERNANCE_SCENARIOS_MISSING';
20
+
21
+ function sleep(ms) {
22
+ return new Promise((resolve) => setTimeout(resolve, ms));
23
+ }
24
+
25
+ /**
26
+ * Convert repeated server error codes into one user-facing qualification failure.
27
+ * The raw job remains attached to the thrown error for JSON/programmatic callers.
28
+ * @param {Object} job
29
+ * @returns {{ message: string, userFacing: boolean }}
30
+ */
31
+ function formatQualificationJobFailure(job) {
32
+ const raw = String(job && job.error ? job.error : '').trim();
33
+ const parts = raw.split(';').map((part) => part.trim()).filter(Boolean);
34
+ const missingCount = parts.filter((part) => part === GOVERNANCE_SCENARIOS_MISSING).length;
35
+ if (missingCount > 0 && missingCount === parts.length) {
36
+ const scope = `${missingCount} datasource${missingCount === 1 ? '' : 's'}`;
37
+ return {
38
+ message: `Governance checks could not run because scenario packs are missing for ${scope}.`,
39
+ userFacing: true
40
+ };
41
+ }
42
+ const unique = [...new Set(parts)];
43
+ return {
44
+ message: unique.join('; ') || `Upload checks failed for job ${job && job.jobId ? job.jobId : ''}`.trim(),
45
+ userFacing: false
46
+ };
47
+ }
48
+
49
+ /**
50
+ * @param {number|string} status
51
+ * @returns {boolean}
52
+ */
53
+ function isTransientQualificationPollError(status) {
54
+ return TRANSIENT_HTTP_STATUSES.has(Number(status));
55
+ }
56
+
57
+ /**
58
+ * @param {string} dataplaneUrl
59
+ * @param {Object} authConfig
60
+ * @param {string} jobId
61
+ * @param {Object} [pollUi]
62
+ * @returns {Promise<Object>} Raw job status data
63
+ */
64
+ async function fetchQualificationJobWithRetry(dataplaneUrl, authConfig, jobId, pollUi = {}) {
65
+ const started = Date.now();
66
+ for (let attempt = 0; attempt <= MAX_TRANSIENT_POLL_ERRORS; attempt += 1) {
67
+ const res = await getPublishJobStatus(dataplaneUrl, authConfig, jobId);
68
+ if (res?.success !== false) {
69
+ return res.data;
70
+ }
71
+ if (!isTransientQualificationPollError(res.status)) {
72
+ const err = new Error(formatApiError(res, dataplaneUrl));
73
+ const status = Number(res.status);
74
+ err.statusCode = status >= 400 && status < 600 ? status : 400;
75
+ throw err;
76
+ }
77
+ if (attempt >= MAX_TRANSIENT_POLL_ERRORS) {
78
+ throw new Error(
79
+ `The upload service returned HTTP ${res.status} repeatedly while waiting for job ${jobId}.`
80
+ );
81
+ }
82
+ if (typeof pollUi.onTransientError === 'function') {
83
+ pollUi.onTransientError(res.status, Date.now() - started);
84
+ }
85
+ await sleep(POLL_INTERVAL_MS);
86
+ }
87
+ throw new Error(`Failed to check upload job ${jobId} after transient retries`);
88
+ }
89
+
90
+ /**
91
+ * Poll a Dataplane qualification job to terminal state (completed|failed). The timeout only
92
+ * bounds how long *this CLI process* waits — Dataplane keeps running the job regardless; timing
93
+ * out returns a resumable jobId, never a fabricated failure (plan 191.0 task 4).
94
+ * @param {string} dataplaneUrl
95
+ * @param {Object} authConfig
96
+ * @param {string} jobId
97
+ * @param {Object} [pollUi] - { onPollProgress(job), onTransientError(status, elapsedMs) }
98
+ * @param {number} [timeoutMs] - Defaults to POLL_TIMEOUT_MS (30 min); CLI `--timeout` overrides it.
99
+ * @returns {Promise<Object>} Terminal job status data (state: 'completed')
100
+ * @throws {Error} With `.jobId` set, when the job fails or the CLI-side wait times out (resumable).
101
+ */
102
+ async function pollQualificationJobUntilTerminal(
103
+ dataplaneUrl,
104
+ authConfig,
105
+ jobId,
106
+ pollUi = {},
107
+ timeoutMs = POLL_TIMEOUT_MS
108
+ ) {
109
+ const started = Date.now();
110
+ while (Date.now() - started < timeoutMs) {
111
+ const job = await fetchQualificationJobWithRetry(dataplaneUrl, authConfig, jobId, pollUi);
112
+ if (typeof pollUi.onPollProgress === 'function') {
113
+ pollUi.onPollProgress(job);
114
+ }
115
+ if (job.state === 'failed') {
116
+ const failure = formatQualificationJobFailure(job);
117
+ const err = new Error(failure.message);
118
+ err.jobId = jobId;
119
+ err.job = job;
120
+ err.skipDoctor = failure.userFacing;
121
+ throw err;
122
+ }
123
+ if (job.state === 'completed') {
124
+ return job;
125
+ }
126
+ await sleep(POLL_INTERVAL_MS);
127
+ }
128
+ const timeoutErr = new Error(
129
+ `Timed out waiting for upload job ${jobId}. It is still running — resume with: aifabrix upload <systemKey> --resume ${jobId}`
130
+ );
131
+ timeoutErr.jobId = jobId;
132
+ timeoutErr.resumable = true;
133
+ throw timeoutErr;
134
+ }
135
+
136
+ module.exports = {
137
+ POLL_INTERVAL_MS,
138
+ POLL_TIMEOUT_MS,
139
+ isTransientQualificationPollError,
140
+ fetchQualificationJobWithRetry,
141
+ formatQualificationJobFailure,
142
+ pollQualificationJobUntilTerminal
143
+ };
@@ -1,6 +1,5 @@
1
1
  const {
2
2
  formatSuccessLine,
3
- formatWarningLine,
4
3
  sectionTitle,
5
4
  headerKeyValue,
6
5
  metadata
@@ -21,8 +20,6 @@ const SEP = chalk.gray('──────────────────
21
20
  const { resolveControllerUrl } = require('../utils/controller-url');
22
21
  const { getDeploymentAuth } = require('../utils/token-manager');
23
22
  const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
24
- const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
25
- const { runWithUploadPipelineSpinner } = require('../utils/upload-pipeline-ui');
26
23
  const {
27
24
  buildResolvedEnvMapForIntegration,
28
25
  resolveConfigurationValues
@@ -33,21 +30,16 @@ const { generateControllerManifest } = require('../generator/external-controller
33
30
  const {
34
31
  validatePipelineConfig
35
32
  } = require('../api/pipeline.api');
36
- const { uploadApplicationViaPipelineWithAuthRetry } = require('../utils/pipeline-upload-auth-retry');
37
33
  const { formatApiError } = require('../utils/api-error-handler');
38
- const { unwrapPublicationResult, unwrapApiData, isPublicationUnchanged } = require('../utils/external-system-readiness-core');
34
+ const { unwrapApiData } = require('../utils/external-system-readiness-core');
39
35
  const {
40
36
  logServerValidationWarnings
41
37
  } = require('../utils/external-system-readiness-display');
42
- const { maybeRunUploadProbe } = require('./upload-probe');
43
- const { maybeSyncOpenApiFilesForMcp } = require('./repair-openapi-sync');
44
- const { exportProgrammaticOpenApiForRepair } = require('./repair-openapi-export');
45
38
  const { resolveUploadPublishContext } = require('./upload-publish-context');
39
+ const { runQualificationUpload, resumeQualificationUpload } = require('./upload-qualification-client');
40
+ const { completeQualifiedUpload } = require('./upload-qualification-followups');
46
41
  const { syncDeployJsonFromSources } = require('../external-system/sync-deploy-manifest');
47
42
  const { assertUploadPackageIdentity } = require('../external-system/upload-identity');
48
- const { handlePublicationAndFollowups } = require('./upload-sidecar-followups');
49
- const { finalizeUploadPublishResult, finishUnchangedUploadPublication } = require('./upload-data-sync-finalize');
50
- const { pushAndLogCredentialSecrets } = require('./upload-credential-push-log');
51
43
  const {
52
44
  SYSTEM_KIND_DIGITAL_WORKER,
53
45
  readRoleAssistantSettings,
@@ -129,39 +121,6 @@ async function resolveDataplaneAndAuth(systemKey, opts = {}) {
129
121
  return { dataplaneUrl, authConfig, environment };
130
122
  }
131
123
 
132
- /**
133
- * Runs single pipeline upload (upload → validate → publish) on the dataplane.
134
- * @param {string} dataplaneUrl - Dataplane base URL
135
- * @param {Object} authConfig - Auth config
136
- * @param {Object} payload - { version, application, dataSources, status: "draft" }
137
- * @param {string} systemKey
138
- * @param {Object} [options]
139
- * @param {number|string} [options.timeoutMs]
140
- * @returns {Promise<Object>} Raw API response envelope
141
- */
142
- async function runUploadValidatePublish(
143
- dataplaneUrl,
144
- authConfig,
145
- payload,
146
- systemKey,
147
- options = {}
148
- ) {
149
- const res = await uploadApplicationViaPipelineWithAuthRetry(
150
- dataplaneUrl,
151
- authConfig,
152
- payload,
153
- systemKey,
154
- { timeoutMs: options.timeoutMs }
155
- );
156
- if (res?.success === false) {
157
- const err = new Error(formatApiError(res, dataplaneUrl));
158
- const status = Number(res.status || res.statusCode);
159
- err.statusCode = status >= 400 && status < 600 ? status : 400;
160
- throw err;
161
- }
162
- return res;
163
- }
164
-
165
124
  /**
166
125
  * @param {Object} validationResult - Result from validateExternalSystemComplete
167
126
  * @returns {string}
@@ -269,97 +228,30 @@ async function buildValidatedUploadManifestPayload(systemKey, opts = {}) {
269
228
  }
270
229
 
271
230
  /**
272
- * Upload local `integration/<systemKey>/openapi/*.json` specs when present so dataplane can store
273
- * vendor OpenAPI keyed by each datasource `openapi.documentKey` (same behavior as repair --api OpenAPI sync).
274
- * Call this **after** a successful pipeline publish: list/upload endpoints require the external system row.
275
- * First publish still succeeds because pipeline materializes internal specs from `openapi.operations` when needed.
276
- *
231
+ * Accept (unless --resume) then poll a Dataplane qualification job to terminal state.
232
+ * @param {string} dataplaneUrl
233
+ * @param {Object} authConfig
234
+ * @param {Object} payload
277
235
  * @param {string} systemKey
278
- * @param {Object} manifest
279
- * @returns {Promise<void>}
236
+ * @param {Object} options - { resume?: string, wait?: boolean, json?: boolean, timeoutMs? }
237
+ * @returns {Promise<{ accepted: Object, terminal: Object|null }>}
280
238
  */
281
- async function logAndSyncLocalOpenApiForUpload(systemKey, manifest, uploadContext = {}) {
282
- const pathOptions = uploadContext.pathOptions || {};
283
- const appPath = resolveExternalSystemPath(systemKey, pathOptions);
284
- const ei = manifest && manifest.externalIntegration;
285
- const datasourceFiles = ei && Array.isArray(ei.dataSources) ? ei.dataSources : [];
286
- await exportProgrammaticOpenApiForRepair({
287
- appPath,
288
- systemKey,
289
- datasourceFiles,
290
- dryRun: false,
291
- backupCtx: { dryRun: false, changedFiles: [] }
292
- });
293
- const openapiSyncLines = await maybeSyncOpenApiFilesForMcp({
294
- enabled: true,
295
- dryRun: false,
296
- appPath,
297
- systemKey,
298
- datasourceFiles,
299
- dataplaneUrl: uploadContext.dataplaneUrl,
300
- authConfig: uploadContext.authConfig,
301
- // Always replace SpecFile rows keyed by openapi.documentKey so vendor/full
302
- // specs are not left missing while only thin internal:* slices remain.
303
- force: true
304
- });
305
- if (!uploadContext.quiet) {
306
- for (const line of openapiSyncLines) {
307
- logger.log(line.startsWith('Skipped') ? formatWarningLine(line) : formatSuccessLine(line));
308
- }
239
+ async function acceptOrResumeQualification(dataplaneUrl, authConfig, payload, systemKey, options) {
240
+ if (options.resume) {
241
+ const terminal = await resumeQualificationUpload(dataplaneUrl, authConfig, options.resume, systemKey, options);
242
+ return {
243
+ accepted: { jobId: options.resume, candidateFingerprint: terminal.result?.candidateFingerprint },
244
+ terminal
245
+ };
309
246
  }
247
+ const result = await runQualificationUpload(dataplaneUrl, authConfig, payload, systemKey, options);
248
+ return { accepted: result, terminal: result.terminal };
310
249
  }
311
250
 
312
251
  /**
313
- * @param {string} systemKey
314
- * @param {Object} options
315
- * @param {Object} manifest
316
- * @param {Object} payload
317
- * @param {{ dataplaneUrl: string, authConfig: Object, environment: string, rawRes: Object }} ctx
318
- */
319
- async function completeUploadAfterPublish(systemKey, options, manifest, payload, ctx) {
320
- const quietCompact = options.minimal === true && options.verbose !== true;
321
- const pushMeta = await pushAndLogCredentialSecrets(ctx.dataplaneUrl, ctx.authConfig, systemKey, payload, {
322
- json: options.json === true,
323
- pathOptions: options.pathOptions,
324
- suppressCredentialLog: quietCompact,
325
- excludeSecretKeys: options.excludeSecretKeys
326
- });
327
- await logAndSyncLocalOpenApiForUpload(systemKey, manifest, {
328
- dataplaneUrl: ctx.dataplaneUrl,
329
- authConfig: ctx.authConfig,
330
- pathOptions: options.pathOptions,
331
- quiet: quietCompact
332
- });
333
- const followupResult = await handlePublicationAndFollowups(
334
- {
335
- systemKey,
336
- options,
337
- manifest,
338
- payload,
339
- environment: ctx.environment,
340
- dataplaneUrl: ctx.dataplaneUrl,
341
- authConfig: ctx.authConfig,
342
- rawRes: ctx.rawRes,
343
- credentialPush: pushMeta.credentialPush,
344
- configPush: pushMeta.configPush
345
- },
346
- maybeRunUploadProbe
347
- );
348
- return finalizeUploadPublishResult(
349
- {
350
- dataplaneUrl: ctx.dataplaneUrl,
351
- authConfig: ctx.authConfig,
352
- environment: ctx.environment,
353
- uploadSkipped: false,
354
- sidecar: followupResult?.sidecar || null
355
- },
356
- payload,
357
- options
358
- );
359
- }
360
-
361
- /**
362
- * Upload path after dry-run check.
252
+ * Upload path after dry-run check: submit for durable Dataplane qualification (async, no
253
+ * request-lifetime failure), then — unless `--no-wait` — poll to terminal and run the same
254
+ * downstream follow-ups (credential push, OpenAPI sync, sidecar, probe) as before.
363
255
  * @param {string} systemKey
364
256
  * @param {Object} options
365
257
  * @param {Object} manifest
@@ -373,40 +265,28 @@ async function runUploadPublishAndSummary(systemKey, options, manifest, payload)
373
265
  options,
374
266
  payload
375
267
  );
376
- const rawRes = await runWithUploadPipelineSpinner(
377
- async() =>
378
- runUploadValidatePublish(dataplaneUrl, authConfig, payload, systemKey, {
379
- timeoutMs: options.timeoutMs
380
- }),
381
- {
382
- systemKey,
383
- json: options.json === true,
384
- syncMode: options.syncMode === true,
385
- suppressPipelineSpinner: options.suppressPipelineSpinner === true
386
- }
268
+ const { accepted, terminal } = await acceptOrResumeQualification(
269
+ dataplaneUrl,
270
+ authConfig,
271
+ payload,
272
+ systemKey,
273
+ options
387
274
  );
388
- const publication = unwrapPublicationResult(rawRes);
389
- if (isPublicationUnchanged(publication)) {
390
- return finishUnchangedUploadPublication({
391
- systemKey,
392
- options,
393
- payload,
394
- dataplaneUrl,
395
- authConfig,
396
- environment
397
- });
275
+ if (!terminal) {
276
+ return accepted;
398
277
  }
399
-
400
- return completeUploadAfterPublish(systemKey, options, manifest, payload, {
278
+ return completeQualifiedUpload(systemKey, options, manifest, payload, {
401
279
  dataplaneUrl,
402
280
  authConfig,
403
281
  environment,
404
- rawRes
282
+ accepted,
283
+ job: terminal
405
284
  });
406
285
  }
407
286
 
408
287
  /**
409
- * Uploads external system: publishes to dataplane and registers with controller (draft).
288
+ * Uploads external system: durably qualifies on dataplane (async ladder, no request-lifetime
289
+ * failure) then registers with controller (draft).
410
290
  * @param {string} systemKey - External system key (integration/<systemKey>/)
411
291
  * @param {Object} [options] - Options
412
292
  * @param {boolean} [options.dryRun] - Validate and build payload only; no API calls
@@ -415,7 +295,9 @@ async function runUploadPublishAndSummary(systemKey, options, manifest, payload)
415
295
  * @param {number} [options.probeTimeout] - Probe timeout ms (default 120000)
416
296
  * @param {number} [options.timeoutMs] - Pipeline publish timeout ms (HTTP + dataplane; default 300000)
417
297
  * @param {boolean} [options.minimal] - Short summary (default: compact unless --verbose)
418
- * @returns {Promise<{ dataplaneUrl: string, authConfig: Object, environment: string }|void>}
298
+ * @param {boolean} [options.wait=true] - Poll the qualification job to terminal state; --no-wait returns the job id immediately
299
+ * @param {string} [options.resume] - Poll an existing qualification job id instead of submitting a new candidate
300
+ * @returns {Promise<Object|void>} Terminal result when waited, `{jobId, candidateFingerprint}` when `--no-wait`, or `undefined` on dry-run
419
301
  * @throws {Error} If validation or API calls fail
420
302
  */
421
303
  async function uploadExternalSystem(systemKey, options = {}) {
@@ -461,6 +343,5 @@ module.exports = {
461
343
  buildValidatedUploadManifestPayload,
462
344
  buildUploadPayload,
463
345
  resolveDataplaneAndAuth,
464
- runUploadValidatePublish,
465
346
  validateSystemKeyFormat
466
347
  };