@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
@@ -64,6 +64,7 @@ const CHANNEL_INSTALL_SCHEMA_DESCRIPTIONS = {
64
64
  provisioning: 'Provisioning mode for this definition.',
65
65
  runtimeTarget: 'Default runtime or MCP Connected System.',
66
66
  experimental: 'True when install requires allowExperimental.',
67
+ status: 'Catalog availability. Omit or visible: listed and installable. hide is omitted from this catalog. coming-soon: listed read-only; new install is refused.',
67
68
  maxInstances: 'Optional cap on Connected System instances for this definition. Omit for unbounded.',
68
69
  profiles: 'Optional generic install profiles (for example MCP client targets).'
69
70
  },
@@ -78,6 +79,7 @@ const CHANNEL_INSTALL_SCHEMA_DESCRIPTIONS = {
78
79
  displayLabel: 'Human-readable interface name.',
79
80
  description: 'Short catalog blurb for the portal table. Not remaining-step copy.',
80
81
  experimental: 'True when the target requires allowExperimental.',
82
+ status: 'visible: listed and installable. coming-soon: listed read-only. hide is omitted from the catalog.',
81
83
  provisioning: 'How Builder provisions this channel (automatic, guided, or cli).',
82
84
  runtimeTarget: 'Runtime or MCP Connected System. Not the channel instance key.',
83
85
  installationSummary: 'Count and status rollup of Connected System instances for this definition.',
@@ -88,12 +90,12 @@ const CHANNEL_INSTALL_SCHEMA_DESCRIPTIONS = {
88
90
  iconHref: 'Data URL for the definition icon.svg from the Builder channel template. Omitted when the template has no icon.',
89
91
  maxInstances: 'Optional instance cap from the Channel Definition. Omitted when unbounded.',
90
92
  installationCount: 'Number of Connected System instances currently projected for this definition.',
91
- canInstall: 'False when installationCount is already at maxInstances. Omitted meaning is canInstall true when unbounded.',
92
- blockedReason: 'Present when canInstall is false. Explains why a new install is refused.'
93
+ canInstall: 'False when coming-soon or when installationCount is already at maxInstances. Omitted meaning is canInstall true when unbounded.',
94
+ blockedReason: 'Present when canInstall is false. Explains why a new install is refused (coming soon or at cap).'
93
95
  },
94
96
  ListChannelTargetsData: {
95
97
  _schema: 'Catalog of live channel / interface targets.',
96
- channels: 'Live targets the caller may install. Unimplemented keys are omitted.'
98
+ channels: 'Live targets. hide is omitted. coming-soon is included read-only (canInstall false). Unimplemented keys are omitted.'
97
99
  },
98
100
  ListChannelTargetsResponse: {
99
101
  _schema: '200 response envelope for listChannelTargets.',
@@ -116,8 +118,9 @@ const CHANNEL_INSTALL_SCHEMA_DESCRIPTIONS = {
116
118
  installations: 'Connected System instances for this definition. Empty array when none exist.',
117
119
  maxInstances: 'Optional instance cap from the Channel Definition. Omitted when unbounded.',
118
120
  installationCount: 'Number of Connected System instances currently projected for this definition.',
119
- canInstall: 'False when installationCount is already at maxInstances.',
120
- blockedReason: 'Present when canInstall is false.'
121
+ canInstall: 'False when coming-soon or when installationCount is already at maxInstances.',
122
+ blockedReason: 'Present when canInstall is false.',
123
+ status: 'visible or coming-soon. hide definitions return 404 instead of this payload.'
121
124
  },
122
125
  ChannelDefinitionDetailResponse: {
123
126
  _schema: '200 response envelope for getChannelDefinitionDetail.',
@@ -124,6 +124,12 @@ const OPERATIONS_SCHEMA_DESCRIPTIONS = {
124
124
  sync: 'When true, force full datasource data sync after deploy.',
125
125
  verbose: 'When true, expand deploy summary output.'
126
126
  },
127
+ DownloadRequest: {
128
+ _schema: 'Optional flags for workspace download (CLI aifabrix download parity).',
129
+ force: 'When true, overwrite existing local package files.',
130
+ dryRun: 'When true, preview files that would be written without mutating the workspace.',
131
+ format: 'Package file format when downloading a Connected System (json or yaml).'
132
+ },
127
133
  ProtectionUploadRequest: {
128
134
  _schema: 'Optional flags for protection upload (CLI protection upload parity).',
129
135
  dryRun: 'When true, validate only; do not upload or sync.',
@@ -354,6 +360,19 @@ const OPERATIONS_SCHEMA_DESCRIPTIONS = {
354
360
  correlationId: 'UUID correlating logs for this request.',
355
361
  requestId: 'Optional per-request UUID when emitted by the server.'
356
362
  },
363
+ DownloadResultData: {
364
+ _schema: 'Workspace download outcome summary.',
365
+ downloaded: 'True when package files were written (false on dryRun).',
366
+ dryRun: 'True when validate-only dry run was requested.',
367
+ systemKey: 'Downloaded integration or Role Assistant system key.'
368
+ },
369
+ DownloadResultResponse: {
370
+ _schema: '200 response envelope for runDownload.',
371
+ success: 'Always true for successful builder-api JSON responses.',
372
+ data: 'Typed payload; see DownloadResultData.',
373
+ correlationId: 'UUID correlating logs for this request.',
374
+ requestId: 'Optional per-request UUID when emitted by the server.'
375
+ },
357
376
  ProtectionUploadData: {
358
377
  _schema: 'Protection upload outcome.',
359
378
  datasourceKey: 'Datasource key whose protection manifest was uploaded.',
@@ -88,6 +88,7 @@ function applyOperationDurationHints(capability, operation) {
88
88
  const durationByHandler = {
89
89
  runUpload: Number(process.env.MAX_UPLOAD_TIMEOUT_MS || 300000),
90
90
  runDeploySystem: Number(process.env.MAX_DEPLOY_TIMEOUT_MS || 300000),
91
+ runDownload: Number(process.env.MAX_DOWNLOAD_TIMEOUT_MS || 300000),
91
92
  runTest: Number(process.env.MAX_TEST_E2E_TIMEOUT_MS || 120000),
92
93
  runCertifyPillar: Number(process.env.MAX_CERTIFY_TIMEOUT_MS || 120000)
93
94
  };
@@ -19,6 +19,9 @@ function getTimeoutMsForHandler(handlerId) {
19
19
  if (handlerId === 'runDeploySystem') {
20
20
  return Number(process.env.MAX_DEPLOY_TIMEOUT_MS || 300000);
21
21
  }
22
+ if (handlerId === 'runDownload') {
23
+ return Number(process.env.MAX_DOWNLOAD_TIMEOUT_MS || 300000);
24
+ }
22
25
  if (handlerId === 'runTest') {
23
26
  return Number(process.env.MAX_TEST_E2E_TIMEOUT_MS || 120000);
24
27
  }
@@ -19,6 +19,7 @@ const { runRepair } = require('./run-repair');
19
19
  const { runValidate } = require('./run-validate');
20
20
  const { runPreviewUpload, runUpload } = require('./run-upload');
21
21
  const { runDeploySystem } = require('./run-deploy');
22
+ const { runDownload } = require('./run-download');
22
23
  const { runProtectionUpload, runProtectionCreate } = require('./run-protection');
23
24
  const { runTest } = require('./run-test');
24
25
  const { runCertifyPillar } = require('./run-certify');
@@ -164,6 +165,12 @@ const HANDLER_MAP = {
164
165
  sync: body.sync === true,
165
166
  verbose: body.verbose === true
166
167
  }),
168
+ runDownload: (params, auth, body = {}) =>
169
+ runDownload(buildContext(auth, params.systemKey), {
170
+ force: body.force === true,
171
+ dryRun: body.dryRun === true,
172
+ format: body.format
173
+ }),
167
174
  runProtectionUpload: (params, auth, body) =>
168
175
  runProtectionUpload(buildContext(auth, params.systemKey), params.datasourceKey, {
169
176
  dryRun: body.dryRun === true,
@@ -8,7 +8,7 @@
8
8
 
9
9
  'use strict';
10
10
 
11
- const { getChannelDefinition } = require('../channels/channel-definition-registry');
11
+ const registry = require('../channels/channel-definition-registry');
12
12
  const { listLiveChannelTargetKeys, getChannelTarget } = require('../channels/channel-targets');
13
13
  const { profilesForCatalogRow } = require('../channels/channel-install-profiles');
14
14
  const { toSafeConfiguration, toPortalFields } = require('../channels/channel-configuration-map');
@@ -23,7 +23,14 @@ const {
23
23
  } = require('../channels/channel-install-inventory');
24
24
  const { listConnectedSystemsForInventory } = require('../channels/channel-inventory-fetch');
25
25
  const { cardinalityProjection } = require('../channels/channel-cardinality');
26
+ const {
27
+ applyChannelStatusProjection,
28
+ normalizeChannelStatus
29
+ } = require('../channels/channel-definition-status');
26
30
  const { httpError } = require('./channel-install-session');
31
+ const { getExternalSystem } = require('../api/external-systems.api');
32
+ const { unwrapApiData } = require('../utils/external-system-readiness-core');
33
+ const { isNotFoundError } = require('../channels/channel-instance-collision');
27
34
 
28
35
  /**
29
36
  * @param {Object} target
@@ -32,6 +39,7 @@ const { httpError } = require('./channel-install-session');
32
39
  */
33
40
  function toChannelCatalogRow(target, installations = []) {
34
41
  const summary = summarizeInstallations(installations);
42
+ const definition = registry.getChannelDefinition(target.key);
35
43
  const row = {
36
44
  key: target.key,
37
45
  displayLabel: target.displayLabel,
@@ -49,7 +57,10 @@ function toChannelCatalogRow(target, installations = []) {
49
57
  }
50
58
  return {
51
59
  ...row,
52
- ...cardinalityProjection(getChannelDefinition(target.key), installations)
60
+ ...applyChannelStatusProjection(
61
+ normalizeChannelStatus(definition),
62
+ cardinalityProjection(definition, installations)
63
+ )
53
64
  };
54
65
  }
55
66
 
@@ -59,7 +70,7 @@ function toChannelCatalogRow(target, installations = []) {
59
70
  * @returns {Object}
60
71
  */
61
72
  function definitionListFields(definitionKey) {
62
- const definition = getChannelDefinition(definitionKey);
73
+ const definition = registry.getChannelDefinition(definitionKey);
63
74
  if (!definition) {
64
75
  return {};
65
76
  }
@@ -99,8 +110,8 @@ async function listChannelTargets(query = {}, ctx = null, deps = {}) {
99
110
  * @returns {Object}
100
111
  */
101
112
  function requireChannelDefinition(definitionKey) {
102
- const definition = getChannelDefinition(definitionKey);
103
- if (!definition) {
113
+ const definition = registry.getChannelDefinition(definitionKey);
114
+ if (!definition || normalizeChannelStatus(definition) === 'hide') {
104
115
  throw httpError(`Unknown channel definition "${definitionKey}".`, 404);
105
116
  }
106
117
  return definition;
@@ -125,6 +136,7 @@ function toSafeDefinition(definition) {
125
136
  provisioning: channel.provisioning,
126
137
  runtimeTarget: channel.runtimeTarget,
127
138
  experimental: channel.experimental === true,
139
+ status: normalizeChannelStatus(definition),
128
140
  profiles: Array.isArray(channel.profiles) ? channel.profiles : []
129
141
  }
130
142
  };
@@ -153,11 +165,39 @@ async function getChannelDefinitionDetail(ctx, definitionKey, deps = {}) {
153
165
  const installations = installationsForDefinition(systems, definition.key);
154
166
  return {
155
167
  ...toSafeDefinition(definition),
156
- ...cardinalityProjection(definition, installations),
168
+ ...applyChannelStatusProjection(
169
+ normalizeChannelStatus(definition),
170
+ cardinalityProjection(definition, installations)
171
+ ),
157
172
  installations
158
173
  };
159
174
  }
160
175
 
176
+ /**
177
+ * @param {Object} ctx
178
+ * @param {string} definitionKey
179
+ * @param {string} systemKey
180
+ * @param {Object} [deps]
181
+ * @returns {Promise<Object>}
182
+ */
183
+ async function installationFromDirectLookup(ctx, definitionKey, systemKey, deps) {
184
+ const getSystem = deps.getExternalSystem || getExternalSystem;
185
+ try {
186
+ const res = await getSystem(ctx.dataplaneUrl, systemKey, {
187
+ type: 'bearer',
188
+ token: ctx.bearerToken,
189
+ preserveBearer: true
190
+ });
191
+ const system = unwrapApiData(res);
192
+ return installationsForDefinition(system ? [system] : [], definitionKey)[0] || null;
193
+ } catch (err) {
194
+ if (isNotFoundError(err)) {
195
+ return null;
196
+ }
197
+ throw err;
198
+ }
199
+ }
200
+
161
201
  /**
162
202
  * @param {Object} ctx
163
203
  * @param {string} definitionKey
@@ -168,8 +208,10 @@ async function getChannelDefinitionDetail(ctx, definitionKey, deps = {}) {
168
208
  async function getChannelInstallation(ctx, definitionKey, systemKey, deps = {}) {
169
209
  requireChannelDefinition(definitionKey);
170
210
  const systems = await listConnectedSystemsForInventory(ctx, deps);
171
- const installations = installationsForDefinition(systems, definitionKey);
172
- const found = installations.find((row) => row.key === systemKey);
211
+ const fromList = installationsForDefinition(systems, definitionKey).find(
212
+ (row) => row.key === systemKey
213
+ );
214
+ const found = fromList || (await installationFromDirectLookup(ctx, definitionKey, systemKey, deps));
173
215
  if (!found) {
174
216
  throw httpError(
175
217
  `Channel installation "${systemKey}" was not found for "${definitionKey}".`,
@@ -56,9 +56,13 @@ function assertChannelInstallTarget(systemKey, body) {
56
56
  assertInstallProfile(systemKey, body || {});
57
57
  try {
58
58
  assertChannelTargetInstallable(systemKey, {
59
- allowExperimental: body && body.allowExperimental === true
59
+ allowExperimental: body && body.allowExperimental === true,
60
+ continueInstall: body && body.continueInstall === true
60
61
  });
61
62
  } catch (err) {
63
+ if (err && err.statusCode === 403) {
64
+ throw httpError(err.message, 403);
65
+ }
62
66
  const live = listLiveChannelTargetKeys({
63
67
  allowExperimental: body && body.allowExperimental === true
64
68
  }).join(', ');
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Programmatic download wrapper — CLI download parity for builder-api.
3
+ *
4
+ * @fileoverview runDownload for POST /systems/:systemKey/download
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { downloadExternalSystem } = require('../external-system/download');
12
+ const { runWithWorkspaceContext } = require('./workspace-context');
13
+ const { mergeProgrammaticOptions } = require('./command-options');
14
+ const { touchWorkspaceMeta } = require('./workspace-meta');
15
+
16
+ /**
17
+ * @param {import('./workspace-context').WorkspaceContext} ctx
18
+ * @param {Object} [options]
19
+ * @param {boolean} [options.force]
20
+ * @param {boolean} [options.dryRun]
21
+ * @param {string} [options.format]
22
+ * @returns {Promise<Object>}
23
+ */
24
+ async function runDownload(ctx, options = {}) {
25
+ const merged = mergeProgrammaticOptions(ctx, options);
26
+ return runWithWorkspaceContext(ctx, async() => {
27
+ await downloadExternalSystem(ctx.systemKey, merged);
28
+ if (!options.dryRun) {
29
+ touchWorkspaceMeta(ctx, { lastOperation: 'download' });
30
+ }
31
+ return {
32
+ downloaded: options.dryRun !== true,
33
+ dryRun: options.dryRun === true,
34
+ systemKey: ctx.systemKey
35
+ };
36
+ });
37
+ }
38
+
39
+ module.exports = {
40
+ runDownload
41
+ };
@@ -67,7 +67,9 @@ async function resolveCliProtectionDataplaneContext(options) {
67
67
  throw new Error('Authentication required. Run "aifabrix login" and try again.');
68
68
  }
69
69
 
70
- const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
70
+ const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig, {
71
+ silent: options.json === true || options.silent === true
72
+ });
71
73
  return { environment, controllerUrl, dataplaneUrl, authConfig };
72
74
  }
73
75
 
@@ -18,7 +18,7 @@ const {
18
18
  formatProgress,
19
19
  formatNextActions
20
20
  } = require('../utils/cli-test-layout-chalk');
21
- const { reportTaskRows } = require('./report-exit');
21
+ const { reportTaskRows, isProtectionProblemResponse } = require('./report-exit');
22
22
  const { describeProtectionRoot } = require('./paths');
23
23
  const {
24
24
  appendTaskRows,
@@ -46,7 +46,8 @@ function formatProtectionValidateTTY(report, opts = {}) {
46
46
  const meta = report?.metadata || {};
47
47
  appendProtectionReportHeader(lines, meta);
48
48
  const summary = report?.summary || {};
49
- const failCount = Number(summary.fail || 0);
49
+ const problem = isProtectionProblemResponse(report);
50
+ const failCount = problem ? Math.max(1, Number(summary.fail || 0)) : Number(summary.fail || 0);
50
51
  const warnCount = Number(summary.warn || 0);
51
52
  const aggregate = failCount > 0 ? 'fail' : warnCount > 0 ? 'warn' : 'ok';
52
53
  const glyph = aggregate === 'fail' ? '✖' : aggregate === 'warn' ? '⚠' : '✔';
@@ -23,6 +23,51 @@ function reportTaskRows(report) {
23
23
  return [];
24
24
  }
25
25
 
26
+ /**
27
+ * True when the payload is an RFC 7807 / API-client error, not a validate report.
28
+ * @param {*} report
29
+ * @returns {boolean}
30
+ */
31
+ function isProtectionProblemResponse(report) {
32
+ if (!report || typeof report !== 'object') {
33
+ return false;
34
+ }
35
+ if (report.success === false) {
36
+ return true;
37
+ }
38
+ const status = Number(report.statusCode || report.status);
39
+ if (Number.isFinite(status) && status >= 400) {
40
+ return true;
41
+ }
42
+ const type = typeof report.type === 'string' ? report.type : '';
43
+ return type.includes('/Errors/') || type.includes('application/problem');
44
+ }
45
+
46
+ /**
47
+ * @param {*} uploaded
48
+ * @returns {string}
49
+ */
50
+ function protectionUploadFailureMessage(uploaded) {
51
+ if (!uploaded || typeof uploaded !== 'object') {
52
+ return 'Protection upload returned empty response';
53
+ }
54
+ return (
55
+ uploaded.formattedError ||
56
+ uploaded.error ||
57
+ uploaded.detail ||
58
+ `Protection upload failed (HTTP ${uploaded.statusCode || uploaded.status || 0})`
59
+ );
60
+ }
61
+
62
+ /**
63
+ * @param {*} uploaded
64
+ */
65
+ function assertProtectionUploadSucceeded(uploaded) {
66
+ if (!uploaded || typeof uploaded !== 'object' || isProtectionProblemResponse(uploaded)) {
67
+ throw new Error(protectionUploadFailureMessage(uploaded));
68
+ }
69
+ }
70
+
26
71
  /**
27
72
  * @param {Object} report
28
73
  * @param {Object} [opts]
@@ -33,6 +78,9 @@ function exitCodeFromProtectionReport(report, opts = {}) {
33
78
  if (!report || typeof report !== 'object') {
34
79
  return 3;
35
80
  }
81
+ if (isProtectionProblemResponse(report)) {
82
+ return 1;
83
+ }
36
84
  const summary = report.summary || {};
37
85
  const failCount = Number(summary.fail || 0);
38
86
  const warnCount = Number(summary.warn || 0);
@@ -58,5 +106,7 @@ function exitCodeFromProtectionReport(report, opts = {}) {
58
106
 
59
107
  module.exports = {
60
108
  reportTaskRows,
109
+ isProtectionProblemResponse,
110
+ assertProtectionUploadSucceeded,
61
111
  exitCodeFromProtectionReport
62
112
  };
@@ -18,6 +18,7 @@ const {
18
18
  deleteProtection,
19
19
  findProtectionKeyByDatasource
20
20
  } = require('../api/protection.api');
21
+ const { assertProtectionUploadSucceeded } = require('./report-exit');
21
22
  const { preflightDatasourceReady } = require('./preflight-datasource-ready');
22
23
  const { runProtectionUploadDataSync } = require('./run-commands-upload-sync');
23
24
  const {
@@ -63,6 +64,7 @@ async function runProtectionUpload(datasourceKey, manifest, opts, logger) {
63
64
 
64
65
  logger.log(formatProgress('Uploading protection manifest...'));
65
66
  const uploaded = await uploadProtection(ctx.dataplaneUrl, ctx.authConfig, manifest);
67
+ assertProtectionUploadSucceeded(uploaded);
66
68
  logger.log(
67
69
  formatSuccessLine(
68
70
  `Protection for '${datasourceKey}' uploaded (deploymentId ${uploaded?.deploymentId || '—'}, revision ${uploaded?.revision ?? '—'})`
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Role Assistant package folder validation (settings, tests, evidence, knowledge).
3
+ * @fileoverview Walk ra-* governed files. Ignore leftover Connected System artifacts.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const path = require('path');
9
+ const fs = require('../internal/fs-real-sync');
10
+ const { validateExternalFile, parseJsonFileContent } = require('../validation/validate-external-file');
11
+ const { listTestCaseFiles } = require('./test-cases');
12
+ const { validateTestCaseFile } = require('./test-case-validate');
13
+
14
+ const SETTINGS_TYPE = 'role-assistant-settings';
15
+ const EMPTY_TESTS_SKIP = 'tests/: skipped (no test cases)';
16
+
17
+ function fileRow(filePath, type, result) {
18
+ return {
19
+ file: path.basename(filePath),
20
+ type,
21
+ valid: result.valid,
22
+ path: filePath,
23
+ errors: result.errors || [],
24
+ warnings: result.warnings || []
25
+ };
26
+ }
27
+
28
+ function listFilesByExt(dirPath, ext, acc) {
29
+ if (!fs.existsSync(dirPath)) {
30
+ return acc;
31
+ }
32
+ for (const name of fs.readdirSync(dirPath)) {
33
+ if (String(name).startsWith('.')) {
34
+ continue;
35
+ }
36
+ const full = path.join(dirPath, name);
37
+ const st = fs.statSync(full);
38
+ if (st.isDirectory()) {
39
+ listFilesByExt(full, ext, acc);
40
+ } else if (String(name).toLowerCase().endsWith(ext)) {
41
+ acc.push(full);
42
+ }
43
+ }
44
+ return acc;
45
+ }
46
+
47
+ function evidenceRows(parsed) {
48
+ if (Array.isArray(parsed)) {
49
+ return parsed;
50
+ }
51
+ if (parsed && typeof parsed === 'object') {
52
+ return [parsed];
53
+ }
54
+ return null;
55
+ }
56
+
57
+ function validateEvidenceJsonFile(filePath) {
58
+ const parseResult = parseJsonFileContent(filePath);
59
+ if (parseResult.error) {
60
+ return { ...parseResult.error, file: filePath, type: 'evidence' };
61
+ }
62
+ const rows = evidenceRows(parseResult.parsed);
63
+ const errors = [];
64
+ if (!rows) {
65
+ errors.push(`${filePath}: Evidence JSON must be an object or array of objects`);
66
+ } else {
67
+ rows.forEach((row, index) => {
68
+ const key = row && typeof row === 'object' && !Array.isArray(row) ? String(row.key || '').trim() : '';
69
+ if (!key) {
70
+ errors.push(`${filePath}: item ${index} is missing key`);
71
+ }
72
+ });
73
+ }
74
+ return { valid: errors.length === 0, errors, warnings: [], file: filePath, type: 'evidence' };
75
+ }
76
+
77
+ function validateKnowledgeMarkdown(filePath) {
78
+ const text = fs.readFileSync(filePath, 'utf8');
79
+ if (!String(text).trim()) {
80
+ return {
81
+ valid: false,
82
+ errors: [`${filePath}: knowledge markdown must not be empty`],
83
+ warnings: [],
84
+ file: filePath,
85
+ type: 'knowledge'
86
+ };
87
+ }
88
+ return { valid: true, errors: [], warnings: [], file: filePath, type: 'knowledge' };
89
+ }
90
+
91
+ async function validateSettingsFile(appPath) {
92
+ const yamlPath = path.join(appPath, 'role-assistant.yaml');
93
+ try {
94
+ const result = await validateExternalFile(yamlPath, SETTINGS_TYPE);
95
+ return fileRow(yamlPath, SETTINGS_TYPE, result);
96
+ } catch (error) {
97
+ return fileRow(yamlPath, SETTINGS_TYPE, {
98
+ valid: false,
99
+ errors: [error.message],
100
+ warnings: []
101
+ });
102
+ }
103
+ }
104
+
105
+ function collectDuplicateCaseIdErrors(caseFiles) {
106
+ const seen = new Map();
107
+ const errors = [];
108
+ for (const row of caseFiles) {
109
+ const parseResult = parseJsonFileContent(row.path);
110
+ const caseId = parseResult.parsed && String(parseResult.parsed.caseId || '').trim();
111
+ if (!caseId) {
112
+ continue;
113
+ }
114
+ if (seen.has(caseId)) {
115
+ errors.push(`${row.path}: duplicate caseId "${caseId}" (also ${seen.get(caseId)})`);
116
+ } else {
117
+ seen.set(caseId, row.path);
118
+ }
119
+ }
120
+ return errors;
121
+ }
122
+
123
+ function validateTestFiles(appPath) {
124
+ const testsDir = path.join(appPath, 'tests');
125
+ const yamlFiles = listTestCaseFiles(testsDir);
126
+ if (yamlFiles.length === 0) {
127
+ return { files: [], errors: [], warnings: [EMPTY_TESTS_SKIP] };
128
+ }
129
+ const files = yamlFiles.map((filePath) => fileRow(filePath, 'role-assistant-test-case', validateTestCaseFile(filePath)));
130
+ const errors = files.flatMap((row) => row.errors);
131
+ errors.push(...collectDuplicateCaseIdErrors(files));
132
+ return { files, errors, warnings: [] };
133
+ }
134
+
135
+ function validateEvidenceFiles(appPath) {
136
+ const jsonFiles = listFilesByExt(path.join(appPath, 'evidence'), '.json', []);
137
+ const files = jsonFiles.map((filePath) => fileRow(filePath, 'evidence', validateEvidenceJsonFile(filePath)));
138
+ return { files, errors: files.flatMap((row) => row.errors), warnings: [] };
139
+ }
140
+
141
+ function validateKnowledgeFiles(appPath) {
142
+ const mdFiles = listFilesByExt(path.join(appPath, 'knowledge'), '.md', []).filter((filePath) => {
143
+ return path.dirname(filePath) === path.join(appPath, 'knowledge');
144
+ });
145
+ const files = mdFiles.map((filePath) => fileRow(filePath, 'knowledge', validateKnowledgeMarkdown(filePath)));
146
+ return { files, errors: files.flatMap((row) => row.errors), warnings: [] };
147
+ }
148
+
149
+ async function validateRoleAssistantPackage(appPath) {
150
+ const settings = await validateSettingsFile(appPath);
151
+ const tests = validateTestFiles(appPath);
152
+ const evidence = validateEvidenceFiles(appPath);
153
+ const knowledge = validateKnowledgeFiles(appPath);
154
+ const files = [settings, ...tests.files, ...evidence.files, ...knowledge.files];
155
+ const errors = [
156
+ ...settings.errors,
157
+ ...tests.errors,
158
+ ...evidence.errors,
159
+ ...knowledge.errors
160
+ ];
161
+ const warnings = [...settings.warnings, ...tests.warnings, ...evidence.warnings, ...knowledge.warnings];
162
+ return {
163
+ valid: errors.length === 0,
164
+ errors,
165
+ warnings,
166
+ files
167
+ };
168
+ }
169
+
170
+ module.exports = {
171
+ EMPTY_TESTS_SKIP,
172
+ validateRoleAssistantPackage,
173
+ validateEvidenceJsonFile,
174
+ validateKnowledgeMarkdown
175
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Shared assertion row helpers (pass/fail/warn/notes/skip).
3
+ * @fileoverview Kind flags for Role Assistant test result rows
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ function skipRow(id, message) {
9
+ return { id, passed: true, skip: true, kind: 'skip', message };
10
+ }
11
+
12
+ function notesRow(id, message) {
13
+ return { id, passed: true, notes: true, kind: 'notes', message };
14
+ }
15
+
16
+ function withKind(row) {
17
+ if (!row || row.kind) {
18
+ return row;
19
+ }
20
+ if (row.notes === true) {
21
+ return { ...row, kind: 'notes' };
22
+ }
23
+ if (row.skip === true) {
24
+ return { ...row, kind: 'skip' };
25
+ }
26
+ if (row.warn === true) {
27
+ return { ...row, kind: 'warn' };
28
+ }
29
+ return { ...row, kind: row.passed ? 'pass' : 'fail' };
30
+ }
31
+
32
+ module.exports = {
33
+ skipRow,
34
+ notesRow,
35
+ withKind
36
+ };