@aifabrix/builder 2.54.1 → 2.55.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 (210) hide show
  1. package/README.md +8 -0
  2. package/lib/agent-kit/apply-ops.js +82 -0
  3. package/lib/agent-kit/checksums.js +28 -0
  4. package/lib/agent-kit/classify.js +113 -0
  5. package/lib/agent-kit/claude-shim.js +61 -0
  6. package/lib/agent-kit/constants.js +55 -0
  7. package/lib/agent-kit/file-list.js +103 -0
  8. package/lib/agent-kit/host-approve.js +130 -0
  9. package/lib/agent-kit/hosting/github.js +63 -0
  10. package/lib/agent-kit/hosts.js +71 -0
  11. package/lib/agent-kit/init.js +319 -0
  12. package/lib/agent-kit/kit-root.js +17 -0
  13. package/lib/agent-kit/managed-section.js +79 -0
  14. package/lib/agent-kit/package-load.js +45 -0
  15. package/lib/agent-kit/plan-ops.js +69 -0
  16. package/lib/agent-kit/remote-prompts.js +239 -0
  17. package/lib/agent-kit/render-host-markdown.js +40 -0
  18. package/lib/agent-kit/repo-guard.js +47 -0
  19. package/lib/agent-kit/report.js +107 -0
  20. package/lib/agent-kit/run.js +82 -0
  21. package/lib/agent-kit/scm/git.js +122 -0
  22. package/lib/agent-kit/state-file.js +83 -0
  23. package/lib/agent-kit/status.js +50 -0
  24. package/lib/agent-kit/v1-detect.js +81 -0
  25. package/lib/agent-kit/validate-kit.js +87 -0
  26. package/lib/agent-kit/workspace-name.js +40 -0
  27. package/lib/agent-kit/workspace-skeleton.js +103 -0
  28. package/lib/api/role-assistant-test-pack.api.js +86 -0
  29. package/lib/api/work-search.api.js +31 -4
  30. package/lib/api/work.api.js +19 -12
  31. package/lib/app/list-external-systems.js +2 -1
  32. package/lib/app/show-display.js +33 -6
  33. package/lib/app/show.js +25 -2
  34. package/lib/channels/add-channel.js +1 -0
  35. package/lib/channels/approval-guides/claude.js +2 -2
  36. package/lib/channels/channel-add-failure.js +1 -0
  37. package/lib/channels/channel-add-remaining.js +15 -9
  38. package/lib/channels/channel-definition-status.js +115 -0
  39. package/lib/channels/channel-description.js +2 -1
  40. package/lib/channels/channel-install-envelope.js +4 -2
  41. package/lib/channels/channel-integration-generator.js +2 -0
  42. package/lib/channels/channel-runtime-resolver.js +14 -0
  43. package/lib/channels/channel-targets.js +31 -5
  44. package/lib/channels/claude-setup-values.js +4 -1
  45. package/lib/channels/slack-channel-add.js +59 -6
  46. package/lib/channels/slack-mcp-callback.js +51 -0
  47. package/lib/channels/slack-mcp-runtime.js +156 -0
  48. package/lib/channels/slack-provisioning/install-cli.js +14 -2
  49. package/lib/channels/slack-provisioning/with-curl-on-path.js +6 -1
  50. package/lib/cli/index.js +4 -0
  51. package/lib/cli/setup-certification-commands.js +2 -2
  52. package/lib/cli/setup-certification.help.js +3 -1
  53. package/lib/commands/agent-kit-help.js +79 -0
  54. package/lib/commands/agent-kit.js +346 -0
  55. package/lib/commands/channel-add-help.js +5 -5
  56. package/lib/commands/datasource-capability-relate-cli.js +5 -4
  57. package/lib/commands/repair-auth-apply.js +1 -1
  58. package/lib/commands/repair-datasource-key-skip.js +39 -0
  59. package/lib/commands/repair-datasource-keys.js +24 -12
  60. package/lib/commands/repair-rbac.js +16 -10
  61. package/lib/commands/role-assistant-copy-output.js +61 -0
  62. package/lib/commands/role-assistant-copy.js +155 -0
  63. package/lib/commands/role-assistant-test-output.js +8 -9
  64. package/lib/commands/role-assistant-test-result-rows.js +42 -0
  65. package/lib/commands/role-assistant.js +117 -48
  66. package/lib/commands/search.js +143 -0
  67. package/lib/commands/test-trust-external.js +7 -5
  68. package/lib/commands/upload-sidecar-followups.js +4 -1
  69. package/lib/commands/verify-operations-skip-e2e.js +131 -0
  70. package/lib/commands/verify-operations-step-plan.js +6 -33
  71. package/lib/commands/verify-operations-steps.js +3 -1
  72. package/lib/datasource/capability/relate-operations.js +28 -10
  73. package/lib/datasource/capability/run-capability-relate.js +48 -25
  74. package/lib/datasource/datasource-identifier.js +13 -5
  75. package/lib/deployment/deployer-poll-run.js +117 -35
  76. package/lib/external-system/deploy-dataplane-published.js +70 -0
  77. package/lib/external-system/deploy.js +12 -2
  78. package/lib/external-system/download-role-assistant.js +41 -15
  79. package/lib/external-system/download.js +7 -1
  80. package/lib/generator/wizard-datasource-key.js +54 -0
  81. package/lib/generator/wizard.js +55 -54
  82. package/lib/integration-definition/apply-datasource-keys.js +75 -0
  83. package/lib/integration-definition/apply-relationships.js +195 -0
  84. package/lib/integration-definition/apply-static-enterprise.js +229 -0
  85. package/lib/integration-definition/apply-system-file.js +71 -0
  86. package/lib/integration-definition/apply-test-endpoint.js +89 -0
  87. package/lib/integration-definition/apply-viewpoint-defaults.js +157 -0
  88. package/lib/integration-definition/apply.js +78 -59
  89. package/lib/integration-definition/enrich-openapi-operations.js +36 -10
  90. package/lib/integration-definition/enrich-openapi-pick.js +87 -0
  91. package/lib/lifecycle/role-assistant-governance.js +56 -21
  92. package/lib/programmatic/capability-registry.js +1 -0
  93. package/lib/programmatic/index.js +2 -0
  94. package/lib/programmatic/openapi-apply-result-schemas.js +8 -0
  95. package/lib/programmatic/openapi-channel-install-schemas.js +4 -1
  96. package/lib/programmatic/openapi-component-schemas.js +1 -0
  97. package/lib/programmatic/openapi-descriptions.js +9 -3
  98. package/lib/programmatic/openapi-envelope-examples.js +7 -0
  99. package/lib/programmatic/openapi-operation-bindings.js +5 -0
  100. package/lib/programmatic/openapi-request-schemas.js +5 -0
  101. package/lib/programmatic/openapi-schema-descriptions-channel.js +8 -5
  102. package/lib/programmatic/openapi-schema-descriptions-operations.js +19 -0
  103. package/lib/programmatic/openapi-spec.js +1 -0
  104. package/lib/programmatic/request-timeout.js +3 -0
  105. package/lib/programmatic/route-handler-map.js +7 -0
  106. package/lib/programmatic/run-channel-catalog.js +50 -8
  107. package/lib/programmatic/run-channel-install-options.js +5 -1
  108. package/lib/programmatic/run-download.js +41 -0
  109. package/lib/protection/auth-context.js +3 -1
  110. package/lib/protection/protection-display.js +3 -2
  111. package/lib/protection/report-exit.js +50 -0
  112. package/lib/protection/run-commands.js +2 -0
  113. package/lib/role-assistant/package-validate.js +175 -0
  114. package/lib/role-assistant/test-case-assertion-row.js +36 -0
  115. package/lib/role-assistant/test-case-copy-bind.js +77 -0
  116. package/lib/role-assistant/test-case-copy-expected.js +127 -0
  117. package/lib/role-assistant/test-case-copy-from-work.js +90 -0
  118. package/lib/role-assistant/test-case-copy-interactions.js +67 -0
  119. package/lib/role-assistant/test-case-copy.js +119 -0
  120. package/lib/role-assistant/test-case-expected-catalog.js +135 -0
  121. package/lib/role-assistant/test-case-validate.js +88 -0
  122. package/lib/role-assistant/test-cases-bid.js +117 -4
  123. package/lib/role-assistant/test-cases-evaluate.js +137 -30
  124. package/lib/role-assistant/test-cases-honesty.js +155 -6
  125. package/lib/role-assistant/test-cases-lifecycle.js +291 -0
  126. package/lib/role-assistant/test-cases-search-graph.js +122 -0
  127. package/lib/role-assistant/test-cases-search.js +20 -2
  128. package/lib/role-assistant/test-cases.js +34 -168
  129. package/lib/role-assistant/test-pack-map.js +180 -0
  130. package/lib/role-assistant/test-pack-sync.js +187 -0
  131. package/lib/role-assistant/test-runner-debug.js +54 -0
  132. package/lib/role-assistant/test-runner-respond-body.js +36 -0
  133. package/lib/role-assistant/test-runner-search.js +20 -5
  134. package/lib/role-assistant/test-runner-start-payload.js +63 -10
  135. package/lib/role-assistant/test-runner-workhub-answers.js +45 -16
  136. package/lib/role-assistant/test-runner-workhub.js +45 -21
  137. package/lib/role-assistant/test-runner.js +50 -10
  138. package/lib/schema/external-datasource.schema.json +36 -5
  139. package/lib/schema/external-system.schema.json +20 -2
  140. package/lib/schema/role-assistant-test-case.schema.json +408 -0
  141. package/lib/search/dataplane-context.js +23 -0
  142. package/lib/search/search-args.js +211 -0
  143. package/lib/search/search-display.js +238 -0
  144. package/lib/search/search-errors.js +50 -0
  145. package/lib/search/search-prompts.js +119 -0
  146. package/lib/search/search-roles.js +85 -0
  147. package/lib/search/search-run.js +241 -0
  148. package/lib/search/search-skills.js +198 -0
  149. package/lib/utils/authentication-slot-show.js +153 -0
  150. package/lib/utils/credential-display.js +43 -1
  151. package/lib/utils/external-system-readiness-compact-blocks.js +5 -1
  152. package/lib/utils/external-system-readiness-compact-display.js +30 -1
  153. package/lib/utils/external-system-validators.js +2 -1
  154. package/lib/utils/help-builder.js +3 -1
  155. package/lib/utils/schema-loader.js +37 -0
  156. package/lib/validation/validate-external-file.js +5 -1
  157. package/lib/validation/validate.js +3 -2
  158. package/package.json +1 -1
  159. package/templates/agent-kit/adapters/claude/nested.CLAUDE.md +1 -0
  160. package/templates/agent-kit/adapters/claude/root.CLAUDE.md +5 -0
  161. package/templates/agent-kit/adapters/cursor/commands/cust-0-plan.md +5 -0
  162. package/templates/agent-kit/adapters/cursor/commands/cust-1-validate.md +5 -0
  163. package/templates/agent-kit/adapters/cursor/commands/cust-2-build.md +5 -0
  164. package/templates/agent-kit/adapters/cursor/commands/cust-3-push.md +5 -0
  165. package/templates/agent-kit/adapters/cursor/commands/cust-biz-1-validate.md +5 -0
  166. package/templates/agent-kit/adapters/cursor/commands/cust-biz-2-build.md +5 -0
  167. package/templates/agent-kit/adapters/cursor/commands/cust-biz-3-prove.md +5 -0
  168. package/templates/agent-kit/agent-kit.yaml +45 -0
  169. package/templates/agent-kit/instructions/AGENTKIT.md +83 -0
  170. package/templates/agent-kit/instructions/integration.AGENTS.md +25 -0
  171. package/templates/agent-kit/instructions/role-assistant.AGENTS.md +22 -0
  172. package/templates/agent-kit/instructions/root.AGENTS.md +37 -0
  173. package/templates/agent-kit/parity-inventory.yaml +188 -0
  174. package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +58 -0
  175. package/templates/agent-kit/skills/aifabrix-connected-system/references/contract-checklist.md +40 -0
  176. package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +54 -0
  177. package/templates/agent-kit/skills/aifabrix-connected-system/references/production-safety.md +35 -0
  178. package/templates/agent-kit/skills/aifabrix-connected-system/scripts/reserved-system-key.js +19 -0
  179. package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +51 -0
  180. package/templates/agent-kit/skills/aifabrix-plan/references/challenge-checklist.md +108 -0
  181. package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +34 -0
  182. package/templates/agent-kit/skills/aifabrix-plan/references/plan-template.md +182 -0
  183. package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +50 -0
  184. package/templates/agent-kit/skills/aifabrix-prove/references/demo-template.md +65 -0
  185. package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +68 -0
  186. package/templates/agent-kit/skills/aifabrix-prove/references/learning-template.md +66 -0
  187. package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +42 -0
  188. package/templates/agent-kit/skills/aifabrix-role-assistant/references/package-boundary.md +21 -0
  189. package/templates/agent-kit/skills/shared/hosts.md +78 -0
  190. package/templates/agent-kit/workspace/.cursor/plans/README.md +22 -0
  191. package/templates/agent-kit/workspace/LICENSE.mit +21 -0
  192. package/templates/agent-kit/workspace/README.md +89 -0
  193. package/templates/applications/builder-api/Dockerfile +3 -1
  194. package/templates/applications/dataplane/application.yaml +2 -2
  195. package/templates/applications/dataplane/env.template +1 -1
  196. package/templates/applications/dataplane/rbac.yaml +28 -0
  197. package/templates/applications/keycloak/env.template +3 -3
  198. package/templates/applications/miso-controller/application.yaml +1 -1
  199. package/templates/applications/miso-controller/env.template +8 -9
  200. package/templates/channels/claude/TROUBLESHOOTING.md +3 -0
  201. package/templates/channels/claude/channel.json +1 -1
  202. package/templates/channels/claude/install.template.md.hbs +1 -0
  203. package/templates/channels/claude/integration/claude-system.json +1 -1
  204. package/templates/channels/slack/channel.json +11 -0
  205. package/templates/channels/slack/install.template.md.hbs +9 -0
  206. package/templates/channels/slack/integration/slack-system.json +11 -0
  207. package/templates/channels/slack/manifest.template.json.hbs +6 -3
  208. package/templates/channels/teams-bot/channel.json +1 -0
  209. package/templates/python/docker-compose.hbs +14 -0
  210. package/templates/typescript/docker-compose.hbs +14 -0
@@ -9,6 +9,15 @@ const path = require('path');
9
9
  const fsReal = require('../internal/fs-real-sync');
10
10
  const { getIntegrationPath } = require('../utils/paths');
11
11
  const { validateIntegrationDefinition } = require('./validate');
12
+ const {
13
+ canonicalDatasourceKeyMap,
14
+ datasourceFileName
15
+ } = require('./apply-datasource-keys');
16
+ const { applyBidRelationships } = require('./apply-relationships');
17
+ const { applyStaticAndEnterprise } = require('./apply-static-enterprise');
18
+ const { applyViewpointDefaults } = require('./apply-viewpoint-defaults');
19
+ const { stampCredentialTestEndpoint } = require('./apply-test-endpoint');
20
+ const { buildSystemFile } = require('./apply-system-file');
12
21
 
13
22
  const STAMP_FILE = '.aifabrix-integration-definition.json';
14
23
 
@@ -96,6 +105,21 @@ function buildDatasourceSchema(labelField) {
96
105
  };
97
106
  }
98
107
 
108
+ function mergeBidSchemaProperties(body, ds) {
109
+ const extra = ds.metadataSchema && ds.metadataSchema.properties;
110
+ if (!extra || typeof extra !== 'object') {
111
+ return;
112
+ }
113
+ if (!body.metadataSchema || typeof body.metadataSchema !== 'object') {
114
+ body.metadataSchema = { type: 'object', properties: {} };
115
+ }
116
+ const schema = /** @type {Record<string, unknown>} */ (body.metadataSchema);
117
+ if (!schema.properties || typeof schema.properties !== 'object') {
118
+ schema.properties = {};
119
+ }
120
+ Object.assign(schema.properties, extra);
121
+ }
122
+
99
123
  function enrichDatasourceFile(body, ds, enrichOptions) {
100
124
  const { enrichDatasourceFromOpenApi } = require('./enrich-openapi-operations');
101
125
  const openapiSourceRef =
@@ -133,6 +157,8 @@ function buildDatasourceFile(ds, systemKey, capabilities, sync, enrichOptions =
133
157
  entityType: ds.entityType || 'recordStorage',
134
158
  resourceType: ds.resourceType,
135
159
  version: '1.0.0',
160
+ ...(ds.storageType ? { storageType: ds.storageType } : {}),
161
+ ...(ds.dimensions && typeof ds.dimensions === 'object' ? { dimensions: ds.dimensions } : {}),
136
162
  capabilities: buildDatasourceCapabilities(caps, key),
137
163
  // Schema sync is mode/schedule/batchSize only; BID sync.mode=incremental is operational intent.
138
164
  sync: { mode: 'pull', schedule: /** @type {any} */ (sync).schedule || '0 */6 * * *', batchSize: 100 },
@@ -143,6 +169,7 @@ function buildDatasourceFile(ds, systemKey, capabilities, sync, enrichOptions =
143
169
  // Mirror capabilities → openapi.operations + execution.cip.operations stubs (154.7).
144
170
  repairSyncOperationsFromCapabilities(body, []);
145
171
  seedPropertyKeysOnDatasource(body, ds.propertyKeys);
172
+ mergeBidSchemaProperties(body, ds);
146
173
  enrichDatasourceFile(body, ds, enrichOptions);
147
174
  return body;
148
175
  }
@@ -192,42 +219,6 @@ function collectAssociationObjectTypes(datasources, systemOpenapiSourceRef, enri
192
219
  return [...types];
193
220
  }
194
221
 
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
222
  /**
232
223
  * @param {Record<string, unknown>} bid
233
224
  * @param {string[]} datasourceFiles
@@ -290,45 +281,73 @@ function buildEnvTemplate(bid) {
290
281
  * nextStamp: Record<string, unknown>
291
282
  * }}
292
283
  */
284
+ function remappedCapabilities(capabilities, keyMap) {
285
+ const out = {};
286
+ for (const [key, value] of Object.entries(capabilities || {})) {
287
+ out[keyMap.get(key) || key] = value;
288
+ }
289
+ return out;
290
+ }
291
+
292
+ function buildApplyStamp(bid, systemKey, identity) {
293
+ return {
294
+ schemaName: bid.schemaName,
295
+ schemaVersion: bid.schemaVersion,
296
+ definitionId: bid.definitionId,
297
+ definitionHash: bid.definitionHash,
298
+ suiteRunId: /** @type {any} */ (bid.meta).suiteRunId,
299
+ systemKey,
300
+ packageVersion: identity.packageVersion,
301
+ appliedAt: new Date().toISOString()
302
+ };
303
+ }
304
+
293
305
  function buildPackageArtifacts(bid, systemKey, enrichOptions = {}) {
294
306
  const identity = /** @type {Record<string, unknown>} */ (bid.packageIdentity);
295
307
  const datasources = /** @type {Record<string, unknown>[]} */ (bid.datasources || []);
296
- const capabilities = /** @type {Record<string, string[]>} */ (bid.capabilities || {});
308
+ const capabilities = remappedCapabilities(
309
+ /** @type {Record<string, string[]>} */ (bid.capabilities || {}),
310
+ canonicalDatasourceKeyMap(datasources, systemKey)
311
+ );
297
312
  const sync = /** @type {Record<string, unknown>} */ (bid.sync || {});
298
- const system = /** @type {Record<string, unknown>} */ (bid.system || {});
313
+ const systemBlock = /** @type {Record<string, unknown>} */ (bid.system || {});
299
314
  const systemOpenapiSourceRef =
300
- typeof system.openapiSourceRef === 'string' ? system.openapiSourceRef : '';
315
+ typeof systemBlock.openapiSourceRef === 'string' ? systemBlock.openapiSourceRef : '';
301
316
  const associationTargets = collectAssociationObjectTypes(
302
317
  datasources,
303
318
  systemOpenapiSourceRef,
304
319
  enrichOptions
305
320
  );
321
+ const keyMap = canonicalDatasourceKeyMap(datasources, systemKey);
306
322
  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, {
323
+ const dsFiles = datasources.map((ds) => {
324
+ const canonicalKey = keyMap.get(String(ds.key)) || String(ds.key);
325
+ return {
326
+ name: datasourceFileName(systemKey, canonicalKey),
327
+ body: buildDatasourceFile({ ...ds, key: canonicalKey }, systemKey, capabilities, sync, {
316
328
  ...enrichOptions,
317
329
  systemOpenapiSourceRef,
318
330
  associationTargets
319
331
  })
320
- })),
332
+ };
333
+ });
334
+ applyBidRelationships(dsFiles, /** @type {any[]} */ (bid.relationships) || [], keyMap);
335
+ applyStaticAndEnterprise(dsFiles, bid);
336
+ applyViewpointDefaults(dsFiles, /** @type {any[]} */ (bid.relationships) || [], keyMap);
337
+ const system = buildSystemFile(bid);
338
+ stampCredentialTestEndpoint(system, dsFiles);
339
+ return {
340
+ systemFile,
341
+ datasourceFiles: dsFiles.map((file) => file.name),
342
+ application: buildApplicationFile(
343
+ bid,
344
+ dsFiles.map((file) => file.name),
345
+ systemFile
346
+ ),
347
+ system,
348
+ dsFiles,
321
349
  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
- }
350
+ nextStamp: buildApplyStamp(bid, systemKey, identity)
332
351
  };
333
352
  }
334
353
 
@@ -10,6 +10,7 @@ const fsReal = require('../internal/fs-real-sync');
10
10
  const {
11
11
  maybeEnrichCrmStyleFieldMappings
12
12
  } = require('./enrich-crm-field-mappings');
13
+ const { pickBestMatchingOp, entityTokensFromDatasource } = require('./enrich-openapi-pick');
13
14
 
14
15
  const PATH_PARAM_RE = /\{(\w+)\}/g;
15
16
 
@@ -171,7 +172,8 @@ function normalizeOp(method, opPath, op) {
171
172
  method: String(method || '').toUpperCase(),
172
173
  path: String(opPath),
173
174
  operationId: typeof op.operationId === 'string' ? op.operationId : '',
174
- pathParamCount: params.length
175
+ pathParamCount: params.length,
176
+ tags: Array.isArray(op.tags) ? op.tags.map((row) => String(row || '')) : []
175
177
  };
176
178
  }
177
179
 
@@ -212,12 +214,29 @@ function isBatchOrSearch(text) {
212
214
  * @param {Array<{ method: string, path: string, operationId: string, pathParamCount: number }>} ops
213
215
  * @returns {{ method: string, path: string, operationId: string } | null}
214
216
  */
215
- function pickFirstMatchingOp(candidates, predicate, idPattern) {
216
- const hits = candidates.filter(predicate);
217
- return hits.find((op) => idPattern.test(op.operationId)) || hits[0] || null;
217
+ function requiredQueryDefaults(doc, method, opPath) {
218
+ const out = {};
219
+ for (const param of operationParameters(doc, method, opPath)) {
220
+ if (!param || param.in !== 'query' || param.required !== true) {
221
+ continue;
222
+ }
223
+ const name = String(param.name || '').trim();
224
+ if (!name) {
225
+ continue;
226
+ }
227
+ const schema = param.schema && typeof param.schema === 'object' ? param.schema : {};
228
+ if (schema.default !== undefined) {
229
+ out[name] = schema.default;
230
+ } else if (typeof schema.minimum === 'number') {
231
+ out[name] = schema.minimum;
232
+ } else {
233
+ out[name] = `{{${name}}}`;
234
+ }
235
+ }
236
+ return out;
218
237
  }
219
238
 
220
- function pickOperationForCapability(capabilityKey, ops) {
239
+ function pickOperationForCapability(capabilityKey, ops, entityTokens) {
221
240
  const key = String(capabilityKey || '').toLowerCase();
222
241
  const candidates = ops.filter((op) => !isBatchOrSearch(`${op.path} ${op.operationId}`));
223
242
  const rules = {
@@ -256,7 +275,7 @@ function pickOperationForCapability(capabilityKey, ops) {
256
275
  if (!rule) {
257
276
  return null;
258
277
  }
259
- return pickFirstMatchingOp(candidates, rule.predicate, rule.idPattern);
278
+ return pickBestMatchingOp(candidates, rule.predicate, rule.idPattern, entityTokens);
260
279
  }
261
280
 
262
281
  /**
@@ -293,7 +312,11 @@ function bindOpenApiOperation(body, binding, capabilityKey) {
293
312
 
294
313
  function createCipQuery(capabilityKey, bindOptions) {
295
314
  const cap = String(capabilityKey || '').toLowerCase();
296
- const query = {};
315
+ const query = {
316
+ ...(bindOptions.requiredQuery && typeof bindOptions.requiredQuery === 'object'
317
+ ? bindOptions.requiredQuery
318
+ : {})
319
+ };
297
320
  const targets = Array.isArray(bindOptions.associationTargets)
298
321
  ? bindOptions.associationTargets.map((row) => String(row || '').trim()).filter(Boolean)
299
322
  : [];
@@ -394,10 +417,11 @@ function enrichDatasourceFromOpenApi(body, openapiSourceRef, options = {}) {
394
417
  )
395
418
  .map((row) => String(row || '').trim())
396
419
  .filter((row) => row && row !== selfType);
420
+ const entityTokens = entityTokensFromDatasource(body);
397
421
  const bound = [];
398
422
  let samplePath = '';
399
423
  for (const capabilityKey of caps) {
400
- const picked = pickOperationForCapability(capabilityKey, indexed);
424
+ const picked = pickOperationForCapability(capabilityKey, indexed, entityTokens);
401
425
  if (!picked) {
402
426
  continue;
403
427
  }
@@ -408,7 +432,8 @@ function enrichDatasourceFromOpenApi(body, openapiSourceRef, options = {}) {
408
432
  picked.path
409
433
  ),
410
434
  associationTargets,
411
- cursorPagination: operationCursorPagination(doc, picked.method, picked.path)
435
+ cursorPagination: operationCursorPagination(doc, picked.method, picked.path),
436
+ requiredQuery: requiredQueryDefaults(doc, picked.method, picked.path)
412
437
  });
413
438
  bound.push(capabilityKey);
414
439
  if (!samplePath) {
@@ -429,5 +454,6 @@ module.exports = {
429
454
  enrichDatasourceFromOpenApi,
430
455
  objectTypeFromOpenApiPath,
431
456
  operationDeclaresAssociationsQuery,
432
- operationCursorPagination
457
+ operationCursorPagination,
458
+ requiredQueryDefaults
433
459
  };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Score OpenAPI operations by datasource entity tokens.
3
+ *
4
+ * @fileoverview Prefer path/tag overlap over first GET in a multi-entity spec
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * @param {Record<string, unknown>} body
11
+ * @returns {string[]}
12
+ */
13
+ function entityTokensFromDatasource(body) {
14
+ const tokens = new Set();
15
+ for (const raw of [body.key, body.resourceType, body.displayName]) {
16
+ const value = String(raw || '').trim().toLowerCase();
17
+ if (!value) {
18
+ continue;
19
+ }
20
+ tokens.add(value);
21
+ for (const part of value.split(/[-_]/g)) {
22
+ if (part.length > 1) {
23
+ tokens.add(part);
24
+ }
25
+ }
26
+ }
27
+ return [...tokens];
28
+ }
29
+
30
+ /**
31
+ * @param {{ path?: string, operationId?: string, tags?: string[] }} op
32
+ * @param {string[]} tokens
33
+ * @returns {number}
34
+ */
35
+ function scoreOpForEntity(op, tokens) {
36
+ if (!Array.isArray(tokens) || tokens.length === 0) {
37
+ return 0;
38
+ }
39
+ const path = String(op.path || '').toLowerCase();
40
+ const id = String(op.operationId || '').toLowerCase();
41
+ const tags = Array.isArray(op.tags)
42
+ ? op.tags.map((row) => String(row || '').toLowerCase()).join(' ')
43
+ : '';
44
+ const hay = `${path} ${id} ${tags}`;
45
+ const segments = path.split('/').filter(Boolean);
46
+ let score = 0;
47
+ for (const token of tokens) {
48
+ if (segments.includes(token)) {
49
+ score += 20;
50
+ } else if (hay.includes(token)) {
51
+ score += 10;
52
+ }
53
+ }
54
+ return score;
55
+ }
56
+
57
+ /**
58
+ * @param {Array<{ method: string, path: string, operationId: string }>} candidates
59
+ * @param {(op: object) => boolean} predicate
60
+ * @param {RegExp} idPattern
61
+ * @param {string[]} [entityTokens]
62
+ * @returns {object | null}
63
+ */
64
+ function pickBestMatchingOp(candidates, predicate, idPattern, entityTokens) {
65
+ const hits = candidates.filter(predicate);
66
+ if (hits.length === 0) {
67
+ return null;
68
+ }
69
+ const tokens = Array.isArray(entityTokens) ? entityTokens : [];
70
+ if (tokens.length > 0) {
71
+ const scored = hits.map((op) => ({
72
+ op,
73
+ score: scoreOpForEntity(op, tokens) + (idPattern.test(op.operationId) ? 5 : 0)
74
+ }));
75
+ scored.sort((left, right) => right.score - left.score);
76
+ if (scored[0].score > 0) {
77
+ return scored[0].op;
78
+ }
79
+ }
80
+ return hits.find((op) => idPattern.test(op.operationId)) || hits[0] || null;
81
+ }
82
+
83
+ module.exports = {
84
+ entityTokensFromDatasource,
85
+ scoreOpForEntity,
86
+ pickBestMatchingOp
87
+ };
@@ -11,7 +11,9 @@ const { VERDICT } = require('./product-model');
11
11
 
12
12
  const GOVERNANCE_SCENARIOS_MISSING = 'GOVERNANCE_SCENARIOS_MISSING';
13
13
  const GOVERNANCE_NOT_VERIFIED = 'GOVERNANCE_NOT_VERIFIED';
14
+ const OPERATIONS_NOT_VERIFIED = 'OPERATIONS_NOT_VERIFIED';
14
15
  const ROLE_ASSISTANT_GOVERNANCE_WARNING = 'ROLE_ASSISTANT_GOVERNANCE_PACKS';
16
+ const ROLE_ASSISTANT_OPERATIONS_WARNING = 'ROLE_ASSISTANT_CS_OPERATIONS';
15
17
 
16
18
  const VERDICT_RANK = Object.freeze({
17
19
  [VERDICT.FAILED]: 4,
@@ -70,37 +72,50 @@ function rollupGovernanceFromDatasources(datasources) {
70
72
  return worst;
71
73
  }
72
74
 
73
- function stripGovernanceNotVerified(recommendations) {
75
+ function stripCsNotVerified(recommendations) {
74
76
  if (!Array.isArray(recommendations)) {
75
77
  return [];
76
78
  }
77
- return recommendations.filter((rec) => rec && rec.code !== GOVERNANCE_NOT_VERIFIED);
79
+ return recommendations.filter(
80
+ (rec) =>
81
+ rec && rec.code !== GOVERNANCE_NOT_VERIFIED && rec.code !== OPERATIONS_NOT_VERIFIED
82
+ );
78
83
  }
79
84
 
80
- function appendRoleAssistantGovernanceWarning(warnings) {
85
+ function appendRoleAssistantCsWarning(warnings, code, message) {
81
86
  const next = Array.isArray(warnings) ? [...warnings] : [];
82
- const exists = next.some((row) => row && row.code === ROLE_ASSISTANT_GOVERNANCE_WARNING);
87
+ const exists = next.some((row) => row && row.code === code);
83
88
  if (!exists) {
84
- next.push({
85
- code: ROLE_ASSISTANT_GOVERNANCE_WARNING,
86
- message:
87
- 'Role Assistant datasources do not use Connected System governance packs. ' +
88
- 'Isolation is Work Hub cases and role-assistant.yaml.'
89
- });
89
+ next.push({ code, message });
90
90
  }
91
91
  return next;
92
92
  }
93
93
 
94
- /**
95
- * Treat CS-only missing packs as not applicable on a Role Assistant report.
96
- * @param {Object} report
97
- * @returns {Object}
98
- */
99
- function remapRoleAssistantGovernanceReport(report) {
100
- if (!report || typeof report !== 'object') {
101
- return report;
94
+ function notApplicableCsPillar(block, errorCode) {
95
+ const base = block && typeof block === 'object' ? { ...block } : {};
96
+ base.verdict = VERDICT.NOT_APPLICABLE;
97
+ if (errorCode) {
98
+ base.errorCode = errorCode;
102
99
  }
103
- const next = { ...report };
100
+ return base;
101
+ }
102
+
103
+ function remapCsPillars(next) {
104
+ let remapped = false;
105
+ const opsVerdict = String((next.operations && next.operations.verdict) || '');
106
+ if (opsVerdict === VERDICT.FAILED || opsVerdict === VERDICT.NOT_VERIFIED) {
107
+ next.operations = notApplicableCsPillar(next.operations, OPERATIONS_NOT_VERIFIED);
108
+ remapped = true;
109
+ }
110
+ const govVerdict = String((next.governance && next.governance.verdict) || '');
111
+ if (govVerdict === VERDICT.FAILED || govVerdict === VERDICT.NOT_VERIFIED) {
112
+ next.governance = notApplicableCsPillar(next.governance, GOVERNANCE_NOT_VERIFIED);
113
+ remapped = true;
114
+ }
115
+ return remapped;
116
+ }
117
+
118
+ function remapMissingPackRows(report, next) {
104
119
  let remapped = false;
105
120
  if (Array.isArray(report.datasources)) {
106
121
  next.datasources = report.datasources.map((row) => {
@@ -120,11 +135,30 @@ function remapRoleAssistantGovernanceReport(report) {
120
135
  next.governance = notApplicableMissingPack(next.governance);
121
136
  remapped = true;
122
137
  }
138
+ return remapped;
139
+ }
140
+
141
+ function remapRoleAssistantGovernanceReport(report) {
142
+ if (!report || typeof report !== 'object') {
143
+ return report;
144
+ }
145
+ const next = { ...report };
146
+ const remapped = remapMissingPackRows(report, next) || remapCsPillars(next);
123
147
  if (!remapped) {
124
148
  return report;
125
149
  }
126
- next.recommendations = stripGovernanceNotVerified(report.recommendations);
127
- next.warnings = appendRoleAssistantGovernanceWarning(report.warnings);
150
+ next.recommendations = stripCsNotVerified(report.recommendations);
151
+ next.warnings = appendRoleAssistantCsWarning(
152
+ report.warnings,
153
+ ROLE_ASSISTANT_GOVERNANCE_WARNING,
154
+ 'Role Assistant datasources do not use Connected System governance packs. ' +
155
+ 'Isolation is Work Hub cases and role-assistant.yaml.'
156
+ );
157
+ next.warnings = appendRoleAssistantCsWarning(
158
+ next.warnings,
159
+ ROLE_ASSISTANT_OPERATIONS_WARNING,
160
+ 'Role Assistant prove does not require Connected System operations verification.'
161
+ );
128
162
  return next;
129
163
  }
130
164
 
@@ -140,6 +174,7 @@ function lifecyclePillarSatisfied(verdict) {
140
174
  module.exports = {
141
175
  GOVERNANCE_SCENARIOS_MISSING,
142
176
  ROLE_ASSISTANT_GOVERNANCE_WARNING,
177
+ ROLE_ASSISTANT_OPERATIONS_WARNING,
143
178
  governanceIsMissingPack,
144
179
  remapRoleAssistantGovernanceReport,
145
180
  lifecyclePillarSatisfied
@@ -49,6 +49,7 @@ const CAPABILITIES = Object.freeze([
49
49
  { method: 'GET', route: '/systems/:systemKey/lifecycle', handlerId: 'getLifecycleReport', operationType: 'certify.read', mutating: false },
50
50
  { method: 'POST', route: '/systems/:systemKey/upload', handlerId: 'runUpload', operationType: 'apply.dev', mutating: true },
51
51
  { method: 'POST', route: '/systems/:systemKey/deploy', handlerId: 'runDeploySystem', operationType: 'apply.prod', mutating: true },
52
+ { method: 'POST', route: '/systems/:systemKey/download', handlerId: 'runDownload', operationType: 'workspace.write', mutating: true },
52
53
  {
53
54
  method: 'POST',
54
55
  route: '/systems/:systemKey/datasources/:datasourceKey/protection/upload',
@@ -7,6 +7,7 @@
7
7
  const workspaceContext = require('./workspace-context');
8
8
  const runUpload = require('./run-upload');
9
9
  const runDeploy = require('./run-deploy');
10
+ const runDownload = require('./run-download');
10
11
  const runProtection = require('./run-protection');
11
12
  const runWizard = require('./run-wizard');
12
13
  const runRepair = require('./run-repair');
@@ -32,6 +33,7 @@ module.exports = {
32
33
  ...workspaceContext,
33
34
  ...runUpload,
34
35
  ...runDeploy,
36
+ ...runDownload,
35
37
  ...runProtection,
36
38
  ...runWizard,
37
39
  ...runRepair,
@@ -21,6 +21,14 @@ const OPENAPI_APPLY_RESULT_SCHEMAS = {
21
21
  },
22
22
  ['deployed']
23
23
  ),
24
+ DownloadResultData: frozenObject(
25
+ {
26
+ downloaded: { type: 'boolean' },
27
+ dryRun: { type: 'boolean' },
28
+ systemKey: schemaRef('SystemKey')
29
+ },
30
+ ['downloaded', 'dryRun', 'systemKey']
31
+ ),
24
32
  ProtectionUploadData: frozenObject(
25
33
  {
26
34
  datasourceKey: schemaRef('DatasourceKey'),
@@ -90,6 +90,7 @@ const OPENAPI_CHANNEL_INSTALL_RESULT_SCHEMAS = {
90
90
  provisioning: schemaRef('ChannelProvisioning'),
91
91
  runtimeTarget: schemaRef('ChannelRuntimeTarget'),
92
92
  experimental: { type: 'boolean' },
93
+ status: { type: 'string', enum: ['visible', 'hide', 'coming-soon'] },
93
94
  maxInstances: { type: 'integer', minimum: 1 },
94
95
  profiles: { type: 'array', items: schemaRef('ChannelTargetProfile') }
95
96
  }),
@@ -108,7 +109,8 @@ const OPENAPI_CHANNEL_INSTALL_RESULT_SCHEMAS = {
108
109
  maxInstances: { type: 'integer', minimum: 1 },
109
110
  installationCount: { type: 'integer', minimum: 0 },
110
111
  canInstall: { type: 'boolean' },
111
- blockedReason: { type: 'string' }
112
+ blockedReason: { type: 'string' },
113
+ status: { type: 'string', enum: ['visible', 'hide', 'coming-soon'] }
112
114
  },
113
115
  ['key', 'displayName', 'channel', 'installations']
114
116
  ),
@@ -140,6 +142,7 @@ const OPENAPI_CHANNEL_INSTALL_RESULT_SCHEMAS = {
140
142
  displayLabel: { type: 'string' },
141
143
  description: { type: 'string' },
142
144
  experimental: { type: 'boolean' },
145
+ status: { type: 'string', enum: ['visible', 'hide', 'coming-soon'] },
143
146
  provisioning: schemaRef('ChannelProvisioning'),
144
147
  runtimeTarget: schemaRef('ChannelRuntimeTarget'),
145
148
  installationSummary: schemaRef('ChannelInstallationSummary'),
@@ -40,6 +40,7 @@ const RESPONSE_ENVELOPES = [
40
40
  ['PreviewUploadResponse', 'PreviewUploadData'],
41
41
  ['UploadResultResponse', 'UploadResultData'],
42
42
  ['DeployResultResponse', 'DeployResultData'],
43
+ ['DownloadResultResponse', 'DownloadResultData'],
43
44
  ['ProtectionUploadResponse', 'ProtectionUploadData'],
44
45
  ['ProtectionCreateResponse', 'ProtectionCreateData'],
45
46
  ['TestResultResponse', 'TestResultData'],
@@ -140,6 +140,12 @@ const OPERATION_DESCRIPTIONS = Object.freeze({
140
140
  'Controller deploy for external systems (CLI aifabrix deploy parity). Optional body.gate matches CLI --gate (operations,trust,governance,full). Certificate seal is a platform side-effect of published Deploy — not a separate certificates issue call. Mutating; use Idempotency-Key.',
141
141
  tags: ['apply']
142
142
  },
143
+ runDownload: {
144
+ summary: 'Download system package into the workspace',
145
+ description:
146
+ 'Fetch the published system from Dataplane into the workspace (CLI aifabrix download parity). Role Assistant packages also pull Evidence, Knowledge, and tests. Mutating; use Idempotency-Key.',
147
+ tags: ['workspace']
148
+ },
143
149
  runProtectionUpload: {
144
150
  summary: 'Upload protection manifest for a datasource',
145
151
  description:
@@ -269,13 +275,13 @@ const OPERATION_DESCRIPTIONS = Object.freeze({
269
275
  listChannelTargets: {
270
276
  summary: 'List live channel definitions',
271
277
  description:
272
- 'List live Channel Definitions. Each row has key, displayLabel, description, experimental, provisioning, runtimeTarget, installationSummary, installationCount, and canInstall. Optional maxInstances and blockedReason when capped. Catalog is information-only; publishing uses POST install. Read-only.',
278
+ 'List live Channel Definitions. Each row has key, displayLabel, description, experimental, status, provisioning, runtimeTarget, installationSummary, installationCount, and canInstall. Optional maxInstances and blockedReason when capped or coming-soon. hide definitions are omitted. Catalog is information-only; publishing uses POST install. Read-only.',
273
279
  tags: ['infra']
274
280
  },
275
281
  getChannelDefinitionDetail: {
276
282
  summary: 'Get a Channel Definition',
277
283
  description:
278
- 'Return a safe Channel Definition (identity, configuration, provisioning, runtimeTarget, profiles, optional maxInstances) plus installations projected from Dataplane Connected Systems. Includes canInstall. Unknown definition keys return 404. Known definitions with no instances return installations []. Read-only.',
284
+ 'Return a safe Channel Definition (identity, configuration, provisioning, runtimeTarget, profiles, optional maxInstances, status) plus installations projected from Dataplane Connected Systems. Includes canInstall. Unknown or hide definition keys return 404. Known definitions with no instances return installations []. Read-only.',
279
285
  tags: ['infra']
280
286
  },
281
287
  getChannelInstallation: {
@@ -287,7 +293,7 @@ const OPERATION_DESCRIPTIONS = Object.freeze({
287
293
  runChannelInstall: {
288
294
  summary: 'Install a channel',
289
295
  description:
290
- 'Scaffold, upload, and configure a channel from a Channel Definition. Path systemKey is the definition key. Body name is required; optional systemKey is the Connected System instance key; configuration is a generic propertyKey map. Returns help markdown plus phase, safe userActions, definitionKey, installationKey, and systemKey (the instance). Mutating.',
296
+ 'Scaffold, upload, and configure a channel from a Channel Definition. Path systemKey is the definition key. Body name is required; optional systemKey is the Connected System instance key; configuration is a generic propertyKey map. hide and coming-soon definitions return 403; --force does not override. Returns help markdown plus phase, safe userActions, definitionKey, installationKey, and systemKey (the instance). Mutating.',
291
297
  tags: ['infra']
292
298
  },
293
299
  getChannelInstall: {
@@ -141,6 +141,11 @@ const OPENAPI_ENVELOPE_DATA_EXAMPLES = {
141
141
  environment: 'dev',
142
142
  systemKey: SAMPLE_SYSTEM_KEY
143
143
  },
144
+ DownloadResultData: {
145
+ downloaded: true,
146
+ dryRun: false,
147
+ systemKey: SAMPLE_SYSTEM_KEY
148
+ },
144
149
  ProtectionUploadData: {
145
150
  datasourceKey: SAMPLE_DATASOURCE_KEY,
146
151
  uploaded: true,
@@ -282,6 +287,7 @@ const OPENAPI_ENVELOPE_DATA_EXAMPLES = {
282
287
  displayLabel: 'ChatGPT',
283
288
  description: 'ChatGPT channel',
284
289
  experimental: false,
290
+ status: 'visible',
285
291
  provisioning: { mode: 'guided' },
286
292
  runtimeTarget: { defaultSystemKey: 'runtime-orchestration', configurable: true },
287
293
  installationSummary: { count: 0, statusRollup: 'none' },
@@ -310,6 +316,7 @@ const OPENAPI_ENVELOPE_DATA_EXAMPLES = {
310
316
  provisioning: { mode: 'guided' },
311
317
  runtimeTarget: { defaultSystemKey: 'runtime-orchestration', configurable: true },
312
318
  experimental: false,
319
+ status: 'visible',
313
320
  profiles: []
314
321
  },
315
322
  installations: []
@@ -233,6 +233,11 @@ const OPERATION_BINDINGS = Object.freeze({
233
233
  requestBody: jsonRequestBody('DeployRequest'),
234
234
  successResponse: 'DeployResultResponse'
235
235
  },
236
+ runDownload: {
237
+ parameters: [SYSTEM_KEY_PARAM],
238
+ requestBody: jsonRequestBody('DownloadRequest'),
239
+ successResponse: 'DownloadResultResponse'
240
+ },
236
241
  runProtectionUpload: {
237
242
  parameters: [SYSTEM_KEY_PARAM, DATASOURCE_KEY_PARAM],
238
243
  requestBody: jsonRequestBody('ProtectionUploadRequest'),
@@ -147,6 +147,11 @@ const OPENAPI_REQUEST_SCHEMAS = {
147
147
  sync: { type: 'boolean' },
148
148
  verbose: { type: 'boolean' }
149
149
  }),
150
+ DownloadRequest: frozenObject({
151
+ force: { type: 'boolean' },
152
+ dryRun: { type: 'boolean' },
153
+ format: { type: 'string', enum: ['json', 'yaml'] }
154
+ }),
150
155
  ProtectionUploadRequest: frozenObject({
151
156
  dryRun: { type: 'boolean' },
152
157
  sync: { type: 'boolean' },