@aifabrix/builder 2.51.1 → 2.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/jest.isolated-projects.js +25 -2
- package/jest.projects.js +4 -1
- package/lib/api/datasources-extended.api.js +8 -8
- package/lib/api/keycloak.api.js +92 -0
- package/lib/api/token-providers.api.js +77 -0
- package/lib/api/types/keycloak.types.js +23 -0
- package/lib/api/types/token-providers.types.js +65 -0
- package/lib/app/down.js +16 -1
- package/lib/app/run-container-start.js +4 -0
- package/lib/app/run-deploy-job.js +135 -27
- package/lib/app/run-env-compose.js +32 -0
- package/lib/app/run-env-recovery.js +35 -0
- package/lib/app/run-helpers.js +5 -6
- package/lib/app/run.js +11 -3
- package/lib/channels/add-channel.js +126 -19
- package/lib/channels/approval-guides/chatgpt.js +12 -9
- package/lib/channels/approval-guides/microsoft-copilot.js +16 -12
- package/lib/channels/channel-add-ensure.js +128 -0
- package/lib/channels/channel-artifact-generator.js +11 -1
- package/lib/channels/channel-branding.js +94 -0
- package/lib/channels/channel-credential.js +8 -0
- package/lib/channels/channel-discovery.js +330 -0
- package/lib/channels/channel-external-system.js +1 -1
- package/lib/channels/channel-runtime-resolver.js +103 -14
- package/lib/channels/channel-status-display.js +7 -1
- package/lib/channels/channel-targets.js +18 -1
- package/lib/channels/chatgpt-runtime-contract.js +1 -1
- package/lib/channels/microsoft-copilot-channel-add.js +138 -0
- package/lib/channels/microsoft-copilot-entra-sso-form.js +180 -0
- package/lib/channels/microsoft-copilot-entra-sso.js +264 -0
- package/lib/channels/microsoft-copilot-icons.js +74 -0
- package/lib/channels/microsoft-copilot-limits.js +84 -0
- package/lib/channels/microsoft-copilot-package.js +130 -49
- package/lib/channels/microsoft-copilot-validate.js +197 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/infra-guided-footers.js +1 -1
- package/lib/cli/infra-guided-public-url-warn.js +55 -0
- package/lib/cli/infra-guided.js +78 -62
- package/lib/cli/setup-app.help.js +4 -0
- package/lib/cli/setup-app.test-commands.js +59 -28
- package/lib/cli/setup-external-system.js +50 -12
- package/lib/cli/setup-infra.js +31 -4
- package/lib/commands/channel-add-setup.js +35 -10
- package/lib/commands/channel.js +27 -12
- package/lib/commands/deploy-gate-orchestrator.js +3 -2
- package/lib/commands/governance-verify-external.js +2 -5
- package/lib/commands/integration-definition.js +158 -0
- package/lib/commands/lifecycle-external.js +14 -7
- package/lib/commands/repair-auth-apply.js +19 -5
- package/lib/commands/repair-datasource-sync-operations.js +4 -1
- package/lib/commands/role-assistant-test-output.js +200 -0
- package/lib/commands/role-assistant.js +131 -114
- package/lib/commands/test-governance-external.js +2 -5
- package/lib/commands/up-miso.js +61 -24
- package/lib/commands/upload-data-sync-finalize.js +20 -3
- package/lib/commands/verify-governance-external.js +2 -5
- package/lib/commands/verify-operations-external.js +2 -5
- package/lib/commands/verify-operations-step-plan.js +31 -0
- package/lib/commands/verify-trust-command-action.js +2 -5
- package/lib/datasource/capability/capability-resolve.js +15 -5
- package/lib/datasource/capability/copy-operations.js +14 -4
- package/lib/datasource/datasource-identifier.js +4 -3
- package/lib/datasource/integration-context.js +3 -3
- package/lib/datasource/resolve-app.js +90 -8
- package/lib/datasource/test-e2e.js +2 -2
- package/lib/datasource/unified-validation-run-body.js +15 -1
- package/lib/datasource/unified-validation-run-resolve.js +2 -2
- package/lib/external-system/deploy-helpers.js +9 -5
- package/lib/external-system/deploy.js +2 -1
- package/lib/external-system/test-system-level.js +21 -3
- package/lib/external-system/test.js +4 -4
- package/lib/generator/builders.js +9 -15
- package/lib/infrastructure/index.js +19 -4
- package/lib/integration-definition/apply.js +478 -0
- package/lib/integration-definition/canonical-json.js +51 -0
- package/lib/integration-definition/emit-suite-bid.js +94 -0
- package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
- package/lib/integration-definition/enrich-openapi-operations.js +434 -0
- package/lib/integration-definition/from-discovery.js +339 -0
- package/lib/integration-definition/hash.js +37 -0
- package/lib/integration-definition/index.js +37 -0
- package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +205 -0
- package/lib/integration-definition/resource-type-catalog.js +59 -0
- package/lib/integration-definition/system-kind-enum.js +40 -0
- package/lib/integration-definition/validate.js +148 -0
- package/lib/programmatic/builder-api-request.js +5 -1
- package/lib/programmatic/builder-help-governance.js +3 -2
- package/lib/programmatic/capability-registry.js +19 -0
- package/lib/programmatic/openapi-component-schemas.js +6 -1
- package/lib/programmatic/openapi-descriptions.js +40 -0
- package/lib/programmatic/openapi-envelope-examples.js +269 -0
- package/lib/programmatic/openapi-operation-bindings.js +45 -0
- package/lib/programmatic/openapi-request-schemas.js +14 -0
- package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
- package/lib/programmatic/openapi-result-schemas.js +46 -99
- package/lib/programmatic/openapi-schema-descriptions-core.js +7 -4
- package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-operations.js +24 -0
- package/lib/programmatic/openapi-schema-helpers.js +9 -9
- package/lib/programmatic/openapi-spec.js +11 -1
- package/lib/programmatic/orchestration-openapi-meta.js +10 -0
- package/lib/programmatic/route-handler-map.js +29 -1
- package/lib/programmatic/run-channel-package.js +236 -0
- package/lib/programmatic/run-integration-definition.js +69 -0
- package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
- package/lib/role-assistant/knowledge-sync-logging.js +58 -0
- package/lib/role-assistant/knowledge-sync.js +104 -89
- package/lib/role-assistant/test-cases-bid.js +155 -0
- package/lib/role-assistant/test-cases-evaluate.js +9 -1
- package/lib/role-assistant/test-cases-honesty.js +24 -10
- package/lib/role-assistant/test-cases.js +28 -0
- package/lib/role-assistant/test-runner-bid-apply.js +122 -0
- package/lib/role-assistant/test-runner-bid-document.js +278 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +289 -46
- package/lib/role-assistant/test-runner-workhub.js +121 -25
- package/lib/role-assistant/test-suite.js +119 -0
- package/lib/schema/application-schema.json +64 -32
- package/lib/schema/external-datasource.schema.json +103 -17
- package/lib/schema/external-system.schema.json +54 -3
- package/lib/schema/infra.parameter.yaml +0 -20
- package/lib/schema/integration-definition.schema.json +195 -0
- package/lib/utils/authentication-shape.js +6 -0
- package/lib/utils/certify-external-workspace.js +40 -0
- package/lib/utils/docker-build.js +3 -3
- package/lib/utils/docker-run-ephemeral.js +77 -17
- package/lib/utils/health-check-public-warn.js +31 -13
- package/lib/utils/health-check.js +11 -1
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/paths.js +13 -15
- package/lib/utils/promote-dev-docker-image.js +97 -18
- package/lib/utils/role-assistant-paths.js +76 -14
- package/lib/utils/transient-developer-id.js +28 -0
- package/lib/utils/upload-sync-options.js +7 -4
- package/lib/validation/reserved-system-key-prefixes.js +56 -0
- package/lib/validation/validate.js +54 -19
- package/package.json +1 -1
- package/scripts/pnpm/af.mjs +24 -9
- package/scripts/pnpm/help.mjs +1 -1
- package/templates/applications/builder-api/application.yaml +3 -3
- package/templates/applications/dataplane/application.yaml +5 -5
- package/templates/applications/dataplane/env.template +8 -7
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/application.yaml +2 -2
- package/templates/applications/keycloak/env.template +5 -2
- package/templates/applications/miso-controller/application.yaml +25 -11
- package/templates/applications/miso-controller/env.template +24 -5
- package/templates/channels/README.md +11 -1
- package/templates/channels/brand/color-192.png +0 -0
- package/templates/channels/brand/dark/128.png +0 -0
- package/templates/channels/brand/dark/192.png +0 -0
- package/templates/channels/brand/dark/256.png +0 -0
- package/templates/channels/brand/dark/32.png +0 -0
- package/templates/channels/brand/dark/512.png +0 -0
- package/templates/channels/brand/dark/64.png +0 -0
- package/templates/channels/brand/light/128.png +0 -0
- package/templates/channels/brand/light/192.png +0 -0
- package/templates/channels/brand/light/256.png +0 -0
- package/templates/channels/brand/light/32.png +0 -0
- package/templates/channels/brand/light/512.png +0 -0
- package/templates/channels/brand/light/64.png +0 -0
- package/templates/channels/brand/mono-white/128.png +0 -0
- package/templates/channels/brand/mono-white/192.png +0 -0
- package/templates/channels/brand/mono-white/256.png +0 -0
- package/templates/channels/brand/mono-white/32.png +0 -0
- package/templates/channels/brand/mono-white/512.png +0 -0
- package/templates/channels/brand/mono-white/64.png +0 -0
- package/templates/channels/brand/outline-32.png +0 -0
- package/templates/channels/chatgpt/install.template.md.hbs +76 -36
- package/templates/channels/chatgpt/integration/README.md +4 -4
- package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
- package/templates/channels/chatgpt/manifest.template.json.hbs +3 -3
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +10 -16
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
- package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +27 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +39 -20
- package/templates/channels/microsoft-copilot/integration/README.md +2 -3
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +7 -7
- package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +11 -0
- package/templates/channels/slack/install.template.md.hbs +6 -1
- package/templates/channels/slack/integration/README.md +2 -4
- package/templates/channels/teams-bot/install.template.md.hbs +4 -0
- package/templates/channels/teams-bot/integration/README.md +3 -5
- package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
- package/templates/python/docker-compose.hbs +10 -0
- package/templates/typescript/docker-compose.hbs +10 -0
- /package/.cursor/plans/{Done → Archive}/157.1-security-quality-violations-by-type.csv +0 -0
|
@@ -0,0 +1,478 @@
|
|
|
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 fs = require('../internal/fs-real-sync');
|
|
9
|
+
const fsp = require('fs').promises;
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { getIntegrationPath } = require('../utils/paths');
|
|
12
|
+
const { validateIntegrationDefinition } = require('./validate');
|
|
13
|
+
|
|
14
|
+
const STAMP_FILE = '.aifabrix-integration-definition.json';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} dir
|
|
18
|
+
* @returns {Promise<Record<string, unknown> | null>}
|
|
19
|
+
*/
|
|
20
|
+
async function readStamp(dir) {
|
|
21
|
+
const stampPath = path.join(dir, STAMP_FILE);
|
|
22
|
+
try {
|
|
23
|
+
const raw = await fsp.readFile(stampPath, 'utf8');
|
|
24
|
+
return JSON.parse(raw);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
if (err && err.code === 'ENOENT') {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {Record<string, unknown>} bid
|
|
35
|
+
* @param {Record<string, unknown>} stamp
|
|
36
|
+
* @returns {void}
|
|
37
|
+
*/
|
|
38
|
+
function assertApplyAllowed(bid, stamp) {
|
|
39
|
+
if (!stamp) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const meta = /** @type {Record<string, unknown>} */ (bid.meta || {});
|
|
43
|
+
if (stamp.definitionId === bid.definitionId && stamp.definitionHash === bid.definitionHash) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (stamp.definitionId === bid.definitionId) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (stamp.systemKey === /** @type {any} */ (bid.packageIdentity).systemKey && !meta.replaceSystemKey) {
|
|
50
|
+
const err = new Error(
|
|
51
|
+
`systemKey ${stamp.systemKey} already bound to definitionId ${stamp.definitionId}; ` +
|
|
52
|
+
'set meta.replaceSystemKey true to replace'
|
|
53
|
+
);
|
|
54
|
+
err.code = 'BID_SYSTEM_KEY_CONFLICT';
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildDatasourceCapabilities(caps, key) {
|
|
60
|
+
return caps.map((capability) => ({
|
|
61
|
+
key: capability,
|
|
62
|
+
description: `${capability} ${key}`,
|
|
63
|
+
riskLevel:
|
|
64
|
+
capability === 'delete'
|
|
65
|
+
? 'high'
|
|
66
|
+
: capability.startsWith('create') || capability.startsWith('update')
|
|
67
|
+
? 'medium'
|
|
68
|
+
: 'low',
|
|
69
|
+
approvalRequired: false
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildDatasourceSchema(labelField) {
|
|
74
|
+
return {
|
|
75
|
+
metadataSchema: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
externalId: { type: 'string', index: true, description: 'Stable external join identity' },
|
|
79
|
+
id: { type: 'string', index: true, description: 'Vendor record id' },
|
|
80
|
+
[labelField]: { type: 'string', index: true, filter: true, description: 'Display label' }
|
|
81
|
+
},
|
|
82
|
+
required: ['externalId', 'id']
|
|
83
|
+
},
|
|
84
|
+
fieldMappings: {
|
|
85
|
+
attributes: {
|
|
86
|
+
id: { expression: '{{raw.id}}', description: 'Vendor record id' },
|
|
87
|
+
externalId: {
|
|
88
|
+
expression: '{{raw.id}}',
|
|
89
|
+
description: 'Stable join identity (defaults to vendor id until enrichment)'
|
|
90
|
+
},
|
|
91
|
+
[labelField]: {
|
|
92
|
+
expression: `{{raw.${labelField}}}`,
|
|
93
|
+
description: 'Display label (enrich from OpenAPI raw paths as needed)'
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function enrichDatasourceFile(body, ds, enrichOptions) {
|
|
101
|
+
const { enrichDatasourceFromOpenApi } = require('./enrich-openapi-operations');
|
|
102
|
+
const openapiSourceRef =
|
|
103
|
+
(typeof ds.openapiSourceRef === 'string' && ds.openapiSourceRef.trim()) ||
|
|
104
|
+
(typeof enrichOptions.systemOpenapiSourceRef === 'string' &&
|
|
105
|
+
enrichOptions.systemOpenapiSourceRef.trim()) ||
|
|
106
|
+
'';
|
|
107
|
+
if (openapiSourceRef) {
|
|
108
|
+
enrichDatasourceFromOpenApi(body, openapiSourceRef, {
|
|
109
|
+
baseDir: enrichOptions.baseDir,
|
|
110
|
+
associationTargets: enrichOptions.associationTargets
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {Record<string, unknown>} ds
|
|
117
|
+
* @param {string} systemKey
|
|
118
|
+
* @param {Record<string, string[]>} capabilities
|
|
119
|
+
* @param {Record<string, unknown>} sync
|
|
120
|
+
* @returns {Record<string, unknown>}
|
|
121
|
+
*/
|
|
122
|
+
function buildDatasourceFile(ds, systemKey, capabilities, sync, enrichOptions = {}) {
|
|
123
|
+
const key = String(ds.key);
|
|
124
|
+
const caps = capabilities[key] || ds.capabilityKeys || ['list', 'get'];
|
|
125
|
+
const labelField = ds.labelField || 'name';
|
|
126
|
+
const { repairSyncOperationsFromCapabilities } = require('../commands/repair-datasource-sync-operations');
|
|
127
|
+
const { seedPropertyKeysOnDatasource } = require('./enrich-crm-field-mappings');
|
|
128
|
+
const body = {
|
|
129
|
+
key,
|
|
130
|
+
displayName: ds.displayName || key,
|
|
131
|
+
description: ds.description || `${ds.displayName || key} datasource`,
|
|
132
|
+
enabled: true,
|
|
133
|
+
systemKey,
|
|
134
|
+
entityType: ds.entityType || 'recordStorage',
|
|
135
|
+
resourceType: ds.resourceType,
|
|
136
|
+
version: '1.0.0',
|
|
137
|
+
capabilities: buildDatasourceCapabilities(caps, key),
|
|
138
|
+
// Schema sync is mode/schedule/batchSize only; BID sync.mode=incremental is operational intent.
|
|
139
|
+
sync: { mode: 'pull', schedule: /** @type {any} */ (sync).schedule || '0 */6 * * *', batchSize: 100 },
|
|
140
|
+
primaryKey: ['id'],
|
|
141
|
+
labelKey: [labelField],
|
|
142
|
+
...buildDatasourceSchema(labelField)
|
|
143
|
+
};
|
|
144
|
+
// Mirror capabilities → openapi.operations + execution.cip.operations stubs (154.7).
|
|
145
|
+
repairSyncOperationsFromCapabilities(body, []);
|
|
146
|
+
seedPropertyKeysOnDatasource(body, ds.propertyKeys);
|
|
147
|
+
enrichDatasourceFile(body, ds, enrichOptions);
|
|
148
|
+
return body;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Collect vendor object-type segments from each datasource OpenAPI ref.
|
|
153
|
+
* Used so list/get can request associations to sibling collections.
|
|
154
|
+
* @param {any[]} datasources
|
|
155
|
+
* @param {string} systemOpenapiSourceRef
|
|
156
|
+
* @param {{ baseDir?: string }} enrichOptions
|
|
157
|
+
* @returns {string[]}
|
|
158
|
+
*/
|
|
159
|
+
function collectAssociationObjectTypes(datasources, systemOpenapiSourceRef, enrichOptions = {}) {
|
|
160
|
+
const {
|
|
161
|
+
loadOpenApiDocument,
|
|
162
|
+
indexOpenApiOperations,
|
|
163
|
+
objectTypeFromOpenApiPath
|
|
164
|
+
} = require('./enrich-openapi-operations');
|
|
165
|
+
const types = new Set();
|
|
166
|
+
for (const ds of datasources || []) {
|
|
167
|
+
// BID datasource keys are the named collection segments CRM association
|
|
168
|
+
// queries expect (opaque OpenAPI paths like /objects/0-3 omit these).
|
|
169
|
+
const dsKey = String(ds.key || '').trim();
|
|
170
|
+
if (dsKey) {
|
|
171
|
+
types.add(dsKey);
|
|
172
|
+
}
|
|
173
|
+
const ref =
|
|
174
|
+
(typeof ds.openapiSourceRef === 'string' && ds.openapiSourceRef.trim()) ||
|
|
175
|
+
systemOpenapiSourceRef ||
|
|
176
|
+
'';
|
|
177
|
+
if (!ref) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const doc = loadOpenApiDocument(ref, enrichOptions.baseDir);
|
|
182
|
+
const ops = indexOpenApiOperations(doc);
|
|
183
|
+
for (const op of ops) {
|
|
184
|
+
const objectType = objectTypeFromOpenApiPath(op.path);
|
|
185
|
+
if (objectType) {
|
|
186
|
+
types.add(objectType);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
// Skip unreadable refs; enrichment still binds per-datasource.
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return [...types];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @param {Record<string, unknown>} bid
|
|
198
|
+
* @returns {Record<string, unknown>}
|
|
199
|
+
*/
|
|
200
|
+
function buildSystemFile(bid) {
|
|
201
|
+
const system = /** @type {Record<string, unknown>} */ (bid.system);
|
|
202
|
+
const identity = /** @type {Record<string, unknown>} */ (bid.packageIdentity);
|
|
203
|
+
const refs = /** @type {Record<string, string>} */ (system.connectionRefs || {});
|
|
204
|
+
const tokenRef = refs.token || refs.apiKey || Object.values(refs)[0] || `kv://${identity.systemKey}/token`;
|
|
205
|
+
return {
|
|
206
|
+
key: system.key,
|
|
207
|
+
displayName: system.displayName || identity.displayName,
|
|
208
|
+
description: system.description || identity.displayName,
|
|
209
|
+
type: system.type || 'openapi',
|
|
210
|
+
enabled: true,
|
|
211
|
+
systemKind: system.systemKind || 'crm',
|
|
212
|
+
authentication: {
|
|
213
|
+
authType: 'credential',
|
|
214
|
+
credential: {
|
|
215
|
+
credentialKey: `${identity.systemKey}-cred`,
|
|
216
|
+
method: 'bearerToken',
|
|
217
|
+
variables: {
|
|
218
|
+
baseUrl: refs.baseUrl || 'https://example.invalid',
|
|
219
|
+
headerName: 'Authorization',
|
|
220
|
+
prefix: 'Bearer'
|
|
221
|
+
},
|
|
222
|
+
security: {
|
|
223
|
+
token: tokenRef
|
|
224
|
+
},
|
|
225
|
+
writeOnce: true
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
dataSources: (/** @type {any[]} */ (bid.datasources) || []).map((d) => d.key)
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @param {Record<string, unknown>} bid
|
|
234
|
+
* @param {string[]} datasourceFiles
|
|
235
|
+
* @param {string} systemFile
|
|
236
|
+
* @returns {Record<string, unknown>}
|
|
237
|
+
*/
|
|
238
|
+
function buildApplicationFile(bid, datasourceFiles, systemFile) {
|
|
239
|
+
const identity = /** @type {Record<string, unknown>} */ (bid.packageIdentity);
|
|
240
|
+
return {
|
|
241
|
+
app: {
|
|
242
|
+
key: identity.systemKey,
|
|
243
|
+
displayName: identity.displayName,
|
|
244
|
+
description: `${identity.displayName} (BT BID-applied)`,
|
|
245
|
+
type: 'external'
|
|
246
|
+
},
|
|
247
|
+
externalIntegration: {
|
|
248
|
+
schemaBasePath: './',
|
|
249
|
+
systems: [systemFile],
|
|
250
|
+
dataSources: datasourceFiles,
|
|
251
|
+
autopublish: true,
|
|
252
|
+
version: identity.packageVersion
|
|
253
|
+
},
|
|
254
|
+
integrationDefinition: {
|
|
255
|
+
schemaName: bid.schemaName,
|
|
256
|
+
schemaVersion: bid.schemaVersion,
|
|
257
|
+
definitionId: bid.definitionId,
|
|
258
|
+
definitionHash: bid.definitionHash,
|
|
259
|
+
suiteRunId: /** @type {any} */ (bid.meta).suiteRunId
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @param {Record<string, unknown>} bid
|
|
266
|
+
* @returns {string}
|
|
267
|
+
*/
|
|
268
|
+
function buildEnvTemplate(bid) {
|
|
269
|
+
const identity = /** @type {Record<string, unknown>} */ (bid.packageIdentity);
|
|
270
|
+
const systemKey = String(identity.systemKey);
|
|
271
|
+
const system = /** @type {Record<string, unknown>} */ (bid.system || {});
|
|
272
|
+
const refs = /** @type {Record<string, string>} */ (system.connectionRefs || {});
|
|
273
|
+
const tokenRef = refs.token || refs.apiKey || `kv://${systemKey}/token`;
|
|
274
|
+
return [
|
|
275
|
+
`# Environment for ${systemKey} (BID-applied). No plaintext secrets.`,
|
|
276
|
+
`# Resolve with aifabrix secret set ${systemKey}/token "<value>" --shared`,
|
|
277
|
+
`KV_TOKEN=${tokenRef}`,
|
|
278
|
+
''
|
|
279
|
+
].join('\n');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* @param {Record<string, unknown>} bid
|
|
284
|
+
* @param {string} systemKey
|
|
285
|
+
* @returns {{
|
|
286
|
+
* systemFile: string,
|
|
287
|
+
* datasourceFiles: string[],
|
|
288
|
+
* application: Record<string, unknown>,
|
|
289
|
+
* system: Record<string, unknown>,
|
|
290
|
+
* dsFiles: Array<{ name: string, body: Record<string, unknown> }>,
|
|
291
|
+
* nextStamp: Record<string, unknown>
|
|
292
|
+
* }}
|
|
293
|
+
*/
|
|
294
|
+
function buildPackageArtifacts(bid, systemKey, enrichOptions = {}) {
|
|
295
|
+
const identity = /** @type {Record<string, unknown>} */ (bid.packageIdentity);
|
|
296
|
+
const datasources = /** @type {Record<string, unknown>[]} */ (bid.datasources || []);
|
|
297
|
+
const capabilities = /** @type {Record<string, string[]>} */ (bid.capabilities || {});
|
|
298
|
+
const sync = /** @type {Record<string, unknown>} */ (bid.sync || {});
|
|
299
|
+
const system = /** @type {Record<string, unknown>} */ (bid.system || {});
|
|
300
|
+
const systemOpenapiSourceRef =
|
|
301
|
+
typeof system.openapiSourceRef === 'string' ? system.openapiSourceRef : '';
|
|
302
|
+
const associationTargets = collectAssociationObjectTypes(
|
|
303
|
+
datasources,
|
|
304
|
+
systemOpenapiSourceRef,
|
|
305
|
+
enrichOptions
|
|
306
|
+
);
|
|
307
|
+
const systemFile = `${systemKey}-system.json`;
|
|
308
|
+
const datasourceFiles = datasources.map((ds) => `${systemKey}-datasource-${ds.key}.json`);
|
|
309
|
+
return {
|
|
310
|
+
systemFile,
|
|
311
|
+
datasourceFiles,
|
|
312
|
+
application: buildApplicationFile(bid, datasourceFiles, systemFile),
|
|
313
|
+
system: buildSystemFile(bid),
|
|
314
|
+
dsFiles: datasources.map((ds) => ({
|
|
315
|
+
name: `${systemKey}-datasource-${ds.key}.json`,
|
|
316
|
+
body: buildDatasourceFile(ds, systemKey, capabilities, sync, {
|
|
317
|
+
...enrichOptions,
|
|
318
|
+
systemOpenapiSourceRef,
|
|
319
|
+
associationTargets
|
|
320
|
+
})
|
|
321
|
+
})),
|
|
322
|
+
envTemplate: buildEnvTemplate(bid),
|
|
323
|
+
nextStamp: {
|
|
324
|
+
schemaName: bid.schemaName,
|
|
325
|
+
schemaVersion: bid.schemaVersion,
|
|
326
|
+
definitionId: bid.definitionId,
|
|
327
|
+
definitionHash: bid.definitionHash,
|
|
328
|
+
suiteRunId: /** @type {any} */ (bid.meta).suiteRunId,
|
|
329
|
+
systemKey,
|
|
330
|
+
packageVersion: identity.packageVersion,
|
|
331
|
+
appliedAt: new Date().toISOString()
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* @param {string} outDir
|
|
338
|
+
* @param {ReturnType<typeof buildPackageArtifacts>} artifacts
|
|
339
|
+
* @returns {Promise<void>}
|
|
340
|
+
*/
|
|
341
|
+
async function writePackageArtifacts(outDir, artifacts) {
|
|
342
|
+
await fsp.mkdir(outDir, { recursive: true });
|
|
343
|
+
await fsp.writeFile(
|
|
344
|
+
path.join(outDir, 'application.json'),
|
|
345
|
+
JSON.stringify(artifacts.application, null, 2),
|
|
346
|
+
'utf8'
|
|
347
|
+
);
|
|
348
|
+
await fsp.writeFile(
|
|
349
|
+
path.join(outDir, artifacts.systemFile),
|
|
350
|
+
JSON.stringify(artifacts.system, null, 2),
|
|
351
|
+
'utf8'
|
|
352
|
+
);
|
|
353
|
+
for (const file of artifacts.dsFiles) {
|
|
354
|
+
await fsp.writeFile(path.join(outDir, file.name), JSON.stringify(file.body, null, 2), 'utf8');
|
|
355
|
+
}
|
|
356
|
+
await fsp.writeFile(path.join(outDir, 'env.template'), artifacts.envTemplate, 'utf8');
|
|
357
|
+
await fsp.writeFile(
|
|
358
|
+
path.join(outDir, STAMP_FILE),
|
|
359
|
+
JSON.stringify(artifacts.nextStamp, null, 2),
|
|
360
|
+
'utf8'
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* @param {unknown} bidInput
|
|
366
|
+
* @returns {Record<string, unknown>}
|
|
367
|
+
*/
|
|
368
|
+
function requireValidBid(bidInput) {
|
|
369
|
+
const result = validateIntegrationDefinition(bidInput);
|
|
370
|
+
if (!result.valid) {
|
|
371
|
+
const err = new Error(`Invalid BID: ${result.errors.join('; ')}`);
|
|
372
|
+
err.code = 'BID_INVALID';
|
|
373
|
+
err.errors = result.errors;
|
|
374
|
+
throw err;
|
|
375
|
+
}
|
|
376
|
+
return result.bid;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* @param {Record<string, unknown> | null} stamp
|
|
381
|
+
* @param {Record<string, unknown>} bid
|
|
382
|
+
* @param {string} outDir
|
|
383
|
+
* @param {string} systemKey
|
|
384
|
+
* @returns {Record<string, unknown> | null}
|
|
385
|
+
*/
|
|
386
|
+
function idempotentResult(stamp, bid, outDir, systemKey) {
|
|
387
|
+
const identity = /** @type {Record<string, unknown>} */ (bid.packageIdentity);
|
|
388
|
+
if (
|
|
389
|
+
!(
|
|
390
|
+
stamp &&
|
|
391
|
+
stamp.definitionId === bid.definitionId &&
|
|
392
|
+
stamp.definitionHash === bid.definitionHash &&
|
|
393
|
+
fs.existsSync(path.join(outDir, 'application.json'))
|
|
394
|
+
)
|
|
395
|
+
) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
status: 'idempotent',
|
|
400
|
+
systemKey,
|
|
401
|
+
outputDir: outDir,
|
|
402
|
+
definitionId: bid.definitionId,
|
|
403
|
+
definitionHash: bid.definitionHash,
|
|
404
|
+
packageVersion: identity.packageVersion
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Apply a validated BID to the integration workspace.
|
|
410
|
+
* @async
|
|
411
|
+
* @param {Record<string, unknown>} bidInput
|
|
412
|
+
* @param {{
|
|
413
|
+
* outputDir?: string,
|
|
414
|
+
* workspaceRoot?: string,
|
|
415
|
+
* dryRun?: boolean,
|
|
416
|
+
* force?: boolean,
|
|
417
|
+
* openapiBaseDir?: string,
|
|
418
|
+
* baseDir?: string
|
|
419
|
+
* }} [options]
|
|
420
|
+
* @returns {Promise<Record<string, unknown>>}
|
|
421
|
+
*/
|
|
422
|
+
async function applyIntegrationDefinition(bidInput, options = {}) {
|
|
423
|
+
const bid = requireValidBid(bidInput);
|
|
424
|
+
const identity = /** @type {Record<string, unknown>} */ (bid.packageIdentity);
|
|
425
|
+
const systemKey = String(identity.systemKey);
|
|
426
|
+
const outDir =
|
|
427
|
+
options.outputDir ||
|
|
428
|
+
(options.workspaceRoot
|
|
429
|
+
? path.join(options.workspaceRoot, 'integration', systemKey)
|
|
430
|
+
: getIntegrationPath(systemKey));
|
|
431
|
+
const stamp = await readStamp(outDir);
|
|
432
|
+
const early = options.force ? null : idempotentResult(stamp, bid, outDir, systemKey);
|
|
433
|
+
if (early) {
|
|
434
|
+
return early;
|
|
435
|
+
}
|
|
436
|
+
assertApplyAllowed(bid, stamp);
|
|
437
|
+
// Prefer explicit openapiBaseDir; else ship-local enrichment specs (not caller cwd).
|
|
438
|
+
// Suite/builder-api apply runs with workspace cwd and must still resolve openapiSourceRef.
|
|
439
|
+
const defaultOpenapiBaseDir = path.join(__dirname, 'openapi-enrichment');
|
|
440
|
+
const artifacts = buildPackageArtifacts(bid, systemKey, {
|
|
441
|
+
baseDir: options.openapiBaseDir || options.baseDir || defaultOpenapiBaseDir
|
|
442
|
+
});
|
|
443
|
+
const fileList = [
|
|
444
|
+
'application.json',
|
|
445
|
+
artifacts.systemFile,
|
|
446
|
+
...artifacts.datasourceFiles,
|
|
447
|
+
'env.template',
|
|
448
|
+
STAMP_FILE
|
|
449
|
+
];
|
|
450
|
+
if (options.dryRun) {
|
|
451
|
+
return {
|
|
452
|
+
status: 'dryRun',
|
|
453
|
+
systemKey,
|
|
454
|
+
outputDir: outDir,
|
|
455
|
+
files: fileList,
|
|
456
|
+
stamp: artifacts.nextStamp
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
await writePackageArtifacts(outDir, artifacts);
|
|
460
|
+
return {
|
|
461
|
+
status: stamp ? 'updated' : 'created',
|
|
462
|
+
systemKey,
|
|
463
|
+
outputDir: outDir,
|
|
464
|
+
definitionId: bid.definitionId,
|
|
465
|
+
definitionHash: bid.definitionHash,
|
|
466
|
+
packageVersion: identity.packageVersion,
|
|
467
|
+
files: fileList
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
module.exports = {
|
|
472
|
+
applyIntegrationDefinition,
|
|
473
|
+
STAMP_FILE,
|
|
474
|
+
buildApplicationFile,
|
|
475
|
+
buildSystemFile,
|
|
476
|
+
buildDatasourceFile,
|
|
477
|
+
buildEnvTemplate
|
|
478
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical JSON helpers for Builder Integration Definition hashing.
|
|
3
|
+
* @fileoverview Stable stringify for BID definitionHash
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Recursively sort object keys for canonical JSON.
|
|
10
|
+
* @param {unknown} value
|
|
11
|
+
* @returns {unknown}
|
|
12
|
+
*/
|
|
13
|
+
function sortValue(value) {
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return value.map(sortValue);
|
|
16
|
+
}
|
|
17
|
+
if (value && typeof value === 'object' && !(value instanceof Date)) {
|
|
18
|
+
const out = {};
|
|
19
|
+
for (const key of Object.keys(value).sort()) {
|
|
20
|
+
out[key] = sortValue(value[key]);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Canonical JSON string (sorted keys, no whitespace variance).
|
|
29
|
+
* @param {unknown} value
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
function canonicalJsonString(value) {
|
|
33
|
+
return JSON.stringify(sortValue(value));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Clone BID without definitionHash for hashing.
|
|
38
|
+
* @param {Record<string, unknown>} bid
|
|
39
|
+
* @returns {Record<string, unknown>}
|
|
40
|
+
*/
|
|
41
|
+
function bidBodyWithoutHash(bid) {
|
|
42
|
+
const clone = { ...bid };
|
|
43
|
+
delete clone.definitionHash;
|
|
44
|
+
return clone;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
sortValue,
|
|
49
|
+
canonicalJsonString,
|
|
50
|
+
bidBodyWithoutHash
|
|
51
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stamp suite provenance onto a BID template for hard GP acceptance.
|
|
3
|
+
* @fileoverview Suite-emitted BID: suiteRunId + answerFingerprint + definitionHash
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
const { withDefinitionHash } = require('./hash');
|
|
10
|
+
const { canonicalJsonString } = require('./canonical-json');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Fingerprint discovery / case answers for BID meta.answerFingerprint.
|
|
14
|
+
* @param {unknown} answers
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
function fingerprintAnswers(answers) {
|
|
18
|
+
const digest = crypto
|
|
19
|
+
.createHash('sha256')
|
|
20
|
+
.update(canonicalJsonString(answers === null || answers === undefined ? null : answers), 'utf8')
|
|
21
|
+
.digest('hex');
|
|
22
|
+
return `sha256:${digest}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolvePriorMeta(baseBid) {
|
|
26
|
+
return baseBid.meta && typeof baseBid.meta === 'object' && !Array.isArray(baseBid.meta)
|
|
27
|
+
? baseBid.meta
|
|
28
|
+
: {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveAnswerFingerprint(options) {
|
|
32
|
+
if (typeof options.answerFingerprint === 'string' && options.answerFingerprint.trim()) {
|
|
33
|
+
return options.answerFingerprint.trim();
|
|
34
|
+
}
|
|
35
|
+
return fingerprintAnswers(options.answers);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resolveProducedAt(options) {
|
|
39
|
+
if (typeof options.producedAt === 'string' && options.producedAt.trim()) {
|
|
40
|
+
return options.producedAt.trim();
|
|
41
|
+
}
|
|
42
|
+
return new Date().toISOString();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveBtAssistantKey(options, priorMeta) {
|
|
46
|
+
if (typeof options.btAssistantKey === 'string' && options.btAssistantKey.trim()) {
|
|
47
|
+
return options.btAssistantKey.trim();
|
|
48
|
+
}
|
|
49
|
+
if (typeof priorMeta.btAssistantKey === 'string' && priorMeta.btAssistantKey.trim()) {
|
|
50
|
+
return priorMeta.btAssistantKey.trim();
|
|
51
|
+
}
|
|
52
|
+
return 'ra-business-transformation';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Clone a BID template and stamp suite provenance (hard GP).
|
|
57
|
+
* Does not invent datasources/capabilities — only meta + hash refresh.
|
|
58
|
+
*
|
|
59
|
+
* @param {Record<string, unknown>} baseBid - Valid BID template (e.g. example-bid.json)
|
|
60
|
+
* @param {Object} options
|
|
61
|
+
* @param {string} options.suiteRunId - Suite / processExecution id (required for hard GP)
|
|
62
|
+
* @param {string} [options.producedAt] - ISO timestamp
|
|
63
|
+
* @param {string} [options.btAssistantKey]
|
|
64
|
+
* @param {unknown} [options.answers] - Case answers for answerFingerprint
|
|
65
|
+
* @param {string} [options.answerFingerprint] - Explicit fingerprint override
|
|
66
|
+
* @returns {Record<string, unknown>}
|
|
67
|
+
*/
|
|
68
|
+
function emitSuiteBid(baseBid, options = {}) {
|
|
69
|
+
if (!baseBid || typeof baseBid !== 'object' || Array.isArray(baseBid)) {
|
|
70
|
+
throw new Error('emitSuiteBid requires a BID object');
|
|
71
|
+
}
|
|
72
|
+
const suiteRunId = String(options.suiteRunId || '').trim();
|
|
73
|
+
if (!suiteRunId) {
|
|
74
|
+
throw new Error('emitSuiteBid requires options.suiteRunId');
|
|
75
|
+
}
|
|
76
|
+
const priorMeta = resolvePriorMeta(baseBid);
|
|
77
|
+
const next = {
|
|
78
|
+
...baseBid,
|
|
79
|
+
meta: {
|
|
80
|
+
...priorMeta,
|
|
81
|
+
suiteRunId,
|
|
82
|
+
producedAt: resolveProducedAt(options),
|
|
83
|
+
btAssistantKey: resolveBtAssistantKey(options, priorMeta),
|
|
84
|
+
answerFingerprint: resolveAnswerFingerprint(options)
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
delete next.definitionHash;
|
|
88
|
+
return withDefinitionHash(next);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
emitSuiteBid,
|
|
93
|
+
fingerprintAnswers
|
|
94
|
+
};
|