@aifabrix/builder 2.52.1 → 2.53.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.
Files changed (225) hide show
  1. package/lib/app/deploy-config.js +3 -2
  2. package/lib/channels/add-channel.js +11 -41
  3. package/lib/channels/approval-guides/index.js +3 -0
  4. package/lib/channels/approval-guides/microsoft-copilot.js +26 -17
  5. package/lib/channels/channel-add-existing.js +72 -0
  6. package/lib/channels/channel-runtime-resolver.js +5 -1
  7. package/lib/channels/channel-status-display.js +30 -3
  8. package/lib/channels/microsoft-copilot-entra-sso-form.js +73 -2
  9. package/lib/channels/microsoft-copilot-entra-sso.js +26 -5
  10. package/lib/channels/microsoft-copilot-limits.js +22 -3
  11. package/lib/channels/microsoft-copilot-package.js +59 -14
  12. package/lib/channels/microsoft-copilot-validate.js +40 -10
  13. package/lib/cli/attach-default-help-examples.js +102 -0
  14. package/lib/cli/index.js +2 -0
  15. package/lib/cli/setup-credential-deployment.js +7 -5
  16. package/lib/cli/setup-utility-validate.js +1 -0
  17. package/lib/commands/credential-push.js +37 -5
  18. package/lib/commands/package-score.js +72 -0
  19. package/lib/commands/role-assistant.js +46 -8
  20. package/lib/deployment/deployer-poll-run.js +2 -2
  21. package/lib/deployment/deployer.js +1 -1
  22. package/lib/deployment/environment.js +2 -2
  23. package/lib/external-system/deploy.js +11 -21
  24. package/lib/integration-definition/apply.js +12 -14
  25. package/lib/integration-definition/enrich-openapi-operations.js +9 -10
  26. package/lib/lab/package-parity-score.js +417 -0
  27. package/lib/programmatic/capability-registry.js +1 -0
  28. package/lib/programmatic/help-content/channel-onboarding.md +35 -0
  29. package/lib/programmatic/help-content/cip-overview.md +27 -0
  30. package/lib/programmatic/help-content/connected-system-ui.md +70 -0
  31. package/lib/programmatic/help-content/dimensions-guide.md +23 -0
  32. package/lib/programmatic/help-content/enterprise-sync-fabrix.md +123 -0
  33. package/lib/programmatic/help-content/overview.md +25 -0
  34. package/lib/programmatic/help-content/subscription-guide.md +27 -0
  35. package/lib/programmatic/help-content/workflow.md +50 -0
  36. package/lib/programmatic/openapi-component-schemas.js +1 -0
  37. package/lib/programmatic/openapi-descriptions.js +6 -0
  38. package/lib/programmatic/openapi-envelope-examples.js +11 -0
  39. package/lib/programmatic/openapi-operation-bindings.js +5 -0
  40. package/lib/programmatic/openapi-request-schemas.js +39 -13
  41. package/lib/programmatic/openapi-result-schemas.js +30 -0
  42. package/lib/programmatic/openapi-schema-descriptions-core.js +18 -0
  43. package/lib/programmatic/openapi-schema-descriptions-operations.js +19 -0
  44. package/lib/programmatic/persist-supplied-openapi.js +180 -0
  45. package/lib/programmatic/route-handler-map.js +6 -0
  46. package/lib/programmatic/run-integration-definition.js +13 -5
  47. package/lib/role-assistant/evidence-sync.js +55 -7
  48. package/lib/role-assistant/test-runner-attachments.js +118 -0
  49. package/lib/role-assistant/test-runner-bid-apply.js +100 -21
  50. package/lib/role-assistant/test-runner-bid-document.js +1 -1
  51. package/lib/role-assistant/test-runner-start-payload.js +96 -0
  52. package/lib/role-assistant/test-runner-workhub-answers.js +11 -7
  53. package/lib/role-assistant/test-runner-workhub-helpers.js +100 -0
  54. package/lib/role-assistant/test-runner-workhub-soft.js +86 -0
  55. package/lib/role-assistant/test-runner-workhub.js +68 -105
  56. package/lib/role-assistant/test-runner.js +5 -63
  57. package/lib/role-assistant/test-suite.js +33 -40
  58. package/lib/schema/external-datasource.schema.json +13 -4
  59. package/lib/utils/compose-generator.js +10 -1
  60. package/lib/utils/compose-traefik-extra-routes.js +107 -0
  61. package/lib/utils/docker-build.js +2 -2
  62. package/lib/utils/help-builder.js +2 -1
  63. package/lib/utils/promote-dev-docker-image.js +15 -10
  64. package/package.json +14 -16
  65. package/templates/README.md +12 -1
  66. package/templates/applications/dataplane/application.yaml +8 -1
  67. package/templates/applications/dataplane/env.template +1 -1
  68. package/templates/applications/miso-controller/application.yaml +2 -1
  69. package/templates/applications/miso-controller/rbac.yaml +1 -0
  70. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +6 -2
  71. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +8 -5
  72. package/templates/channels/microsoft-copilot/fi-fi.template.json.hbs +7 -0
  73. package/templates/channels/microsoft-copilot/install.template.md.hbs +74 -22
  74. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +29 -14
  75. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +6 -2
  76. package/templates/python/docker-compose.hbs +36 -0
  77. package/templates/typescript/docker-compose.hbs +36 -0
  78. package/.cursor/plans/Archive/157.1-security-quality-violations-by-type.csv +0 -374
  79. package/.cursor/process/README.md +0 -9
  80. package/.cursor/rules/anchor-docs.mdc +0 -15
  81. package/.cursor/rules/assistant-driven-integration-build.mdc +0 -40
  82. package/.cursor/rules/cli-layout.mdc +0 -81
  83. package/.cursor/rules/cli-product-neutral.mdc +0 -43
  84. package/.cursor/rules/deploy-rules/docker-image-ip-policy.mdc +0 -49
  85. package/.cursor/rules/docs-rules.mdc +0 -32
  86. package/.cursor/rules/plan-rules/application-lifecycle.mdc +0 -30
  87. package/.cursor/rules/plan-rules/plan-frontmatter.mdc +0 -35
  88. package/.cursor/rules/plan-rules/plan-lifecycle.mdc +0 -31
  89. package/.cursor/rules/plan-rules/plan-validation-gate.mdc +0 -34
  90. package/.cursor/rules/project-rules.mdc +0 -913
  91. package/.cursor/skills/README.md +0 -15
  92. package/.dockerignore +0 -52
  93. package/.eslintignore +0 -22
  94. package/.markdownlint.json +0 -14
  95. package/anchor-docs/README.md +0 -10
  96. package/anchor-docs/_TEMPLATE +0 -24
  97. package/babel.config.js +0 -6
  98. package/bin/builder-api-cleanup.js +0 -26
  99. package/bin/builder-api-nest.js +0 -10
  100. package/bin/builder-api-spike.js +0 -19
  101. package/enterprise-knowledge/README.md +0 -50
  102. package/enterprise-knowledge/enterprise-integration-lifecycle/README.md +0 -22
  103. package/enterprise-knowledge/enterprise-integration-lifecycle/application.json +0 -15
  104. package/enterprise-knowledge/enterprise-integration-lifecycle/deploy.js +0 -69
  105. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-deploy.json +0 -52
  106. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-system.json +0 -29
  107. package/enterprise-knowledge/enterprise-integration-lifecycle/env.template +0 -2
  108. package/integration/README.md +0 -81
  109. package/integration/github/README.md +0 -53
  110. package/integration/github/application.json +0 -21
  111. package/integration/github/deploy.js +0 -72
  112. package/integration/github/env.template +0 -11
  113. package/integration/github/github-datasource-issue-comments.json +0 -239
  114. package/integration/github/github-datasource-issues.json +0 -399
  115. package/integration/github/github-datasource-repository.json +0 -354
  116. package/integration/github/github-deploy.json +0 -1202
  117. package/integration/github/github-system.json +0 -82
  118. package/integration/hubspot-test/README.md +0 -159
  119. package/integration/hubspot-test/application.json +0 -54
  120. package/integration/hubspot-test/companies.json +0 -2048
  121. package/integration/hubspot-test/create-hubspot.js +0 -523
  122. package/integration/hubspot-test/env.template +0 -4
  123. package/integration/hubspot-test/hubspot-test-datasource-company.json +0 -138
  124. package/integration/hubspot-test/hubspot-test-datasource-contact.json +0 -146
  125. package/integration/hubspot-test/hubspot-test-datasource-deal.json +0 -146
  126. package/integration/hubspot-test/hubspot-test-datasource-users.json +0 -76
  127. package/integration/hubspot-test/hubspot-test-deploy.json +0 -2160
  128. package/integration/hubspot-test/hubspot-test-system.json +0 -74
  129. package/integration/hubspot-test/rbac.json +0 -166
  130. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +0 -20
  131. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +0 -9
  132. package/integration/hubspot-test/test-artifacts/wizard-invalid-add-datasource.yaml +0 -5
  133. package/integration/hubspot-test/test-artifacts/wizard-invalid-app-name.yaml +0 -5
  134. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-create.yaml +0 -7
  135. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-select.yaml +0 -7
  136. package/integration/hubspot-test/test-artifacts/wizard-invalid-known-platform.yaml +0 -4
  137. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-app.yaml +0 -4
  138. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
  139. package/integration/hubspot-test/test-artifacts/wizard-invalid-mode.yaml +0 -5
  140. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-file.yaml +0 -5
  141. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-url.yaml +0 -4
  142. package/integration/hubspot-test/test-artifacts/wizard-invalid-source.yaml +0 -4
  143. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-array-test.yaml +0 -5
  144. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
  145. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
  146. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-test.yaml +0 -5
  147. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-test.yaml +0 -5
  148. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +0 -5
  149. package/integration/hubspot-test/test-dataplane-down-helpers.js +0 -249
  150. package/integration/hubspot-test/test-dataplane-down-tests.js +0 -386
  151. package/integration/hubspot-test/test-dataplane-down.js +0 -209
  152. package/integration/hubspot-test/test.js +0 -1588
  153. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +0 -16
  154. package/integration/hubspot-test/wizard-hubspot-platform.yaml +0 -8
  155. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +0 -23
  156. package/integration/roundtrip-test-local/README.md +0 -143
  157. package/integration/roundtrip-test-local/application.yaml +0 -13
  158. package/integration/roundtrip-test-local/env.template +0 -15
  159. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +0 -14
  160. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +0 -61
  161. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +0 -25
  162. package/integration/roundtrip-test-local2/README.md +0 -143
  163. package/integration/roundtrip-test-local2/application.yaml +0 -13
  164. package/integration/roundtrip-test-local2/env.template +0 -15
  165. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +0 -31
  166. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +0 -86
  167. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +0 -25
  168. package/integration/test/wizard.yaml +0 -8
  169. package/jest.config.coverage.js +0 -37
  170. package/jest.config.default.js +0 -11
  171. package/jest.config.integration.fixtures.js +0 -22
  172. package/jest.config.integration.js +0 -33
  173. package/jest.config.isolated.js +0 -11
  174. package/jest.config.manual.js +0 -30
  175. package/jest.global-live-fabrix-hooks.js +0 -36
  176. package/jest.isolated-projects.js +0 -283
  177. package/jest.projects.js +0 -79
  178. package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +0 -205
  179. package/packages/builder-api/README.md +0 -120
  180. package/packages/builder-api/package.json +0 -30
  181. package/packages/builder-api/src/app.module.ts +0 -7
  182. package/packages/builder-api/src/auth/auth.module.ts +0 -5
  183. package/packages/builder-api/src/external-system/external-system.module.ts +0 -5
  184. package/packages/builder-api/src/main.ts +0 -16
  185. package/packages/builder-api/tsconfig.json +0 -15
  186. package/scripts/check-builder-api-architecture-grep.js +0 -79
  187. package/scripts/check-datasource-test-run-schema-sync.js +0 -34
  188. package/scripts/ci-fix.sh +0 -19
  189. package/scripts/ci-simulate.sh +0 -19
  190. package/scripts/diagnose-cli.js +0 -150
  191. package/scripts/docker_image_content_gate.js +0 -47
  192. package/scripts/docker_image_content_gate.sh +0 -5
  193. package/scripts/export-builder-api-evidence.js +0 -262
  194. package/scripts/install-local.js +0 -490
  195. package/scripts/lib/builder-api-docker-staging-copy.js +0 -60
  196. package/scripts/lib/builder-api-docker-staging-core.js +0 -183
  197. package/scripts/lib/docker-image-content-gate-core.js +0 -166
  198. package/scripts/lib/plan-actor.js +0 -109
  199. package/scripts/lib/plan-config.js +0 -20
  200. package/scripts/lib/plan-gates.js +0 -154
  201. package/scripts/lib/plan-meta.js +0 -104
  202. package/scripts/lib/plan-parse.js +0 -162
  203. package/scripts/lib/plan-prevalidate-gates.js +0 -337
  204. package/scripts/lib/plan-run-core.js +0 -125
  205. package/scripts/lib/plan-run-report.js +0 -119
  206. package/scripts/plan-approve.js +0 -24
  207. package/scripts/plan-archive.js +0 -49
  208. package/scripts/plan-run.js +0 -13
  209. package/scripts/plan-set-actor.js +0 -21
  210. package/scripts/plan-validate.js +0 -80
  211. package/scripts/pnpm/af.mjs +0 -125
  212. package/scripts/pnpm/check-quiet.mjs +0 -32
  213. package/scripts/pnpm/dev-install.mjs +0 -29
  214. package/scripts/pnpm/dev-reload.mjs +0 -18
  215. package/scripts/pnpm/help.mjs +0 -55
  216. package/scripts/pnpm/lib/dev-reload-core.mjs +0 -189
  217. package/scripts/pnpm/lib/dev-reload-log-capture.mjs +0 -67
  218. package/scripts/pnpm/lib/dev-reload-log-utils.mjs +0 -32
  219. package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +0 -124
  220. package/scripts/pnpm/lib/resolve-app-url.mjs +0 -183
  221. package/scripts/pnpm/lib/spinner.mjs +0 -42
  222. package/scripts/pnpm-global-remove.js +0 -48
  223. package/scripts/stage_builder_api_for_docker.js +0 -65
  224. package/scripts/sync-builder-api-template.js +0 -44
  225. package/scripts/test-dataplane-bootstrap.js +0 -331
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Persist validated OpenAPI documents into workspace openapi/ (517.2).
3
+ * @fileoverview Runtime-authorized documents only — Builder never browses task store
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const crypto = require('crypto');
9
+ const path = require('path');
10
+ const fs = require('../internal/fs-real-sync');
11
+ const { assertValidSystemKey } = require('./system-key');
12
+ const { ensureIntegrationDirectory } = require('./workspace-context');
13
+
14
+ function sha256Hex(buffer) {
15
+ return crypto.createHash('sha256').update(buffer).digest('hex');
16
+ }
17
+
18
+ function safeOpenApiBasename(filename) {
19
+ const raw = String(filename || '').trim();
20
+ const base = path.basename(raw);
21
+ if (!base || base === '.' || base === '..') {
22
+ throw new Error('openapi.filename.invalid');
23
+ }
24
+ if (raw.includes('..') || raw.includes('/') || raw.includes('\\') || raw.includes('\0')) {
25
+ throw new Error('openapi.filename.pathTraversal');
26
+ }
27
+ return base.endsWith('.json') || base.endsWith('.yaml') || base.endsWith('.yml')
28
+ ? base
29
+ : `${base}.json`;
30
+ }
31
+
32
+ function parseOpenApiDocument(body) {
33
+ if (body && typeof body === 'object' && !Array.isArray(body)) {
34
+ return body;
35
+ }
36
+ if (typeof body === 'string') {
37
+ const trimmed = body.trim();
38
+ if (!trimmed) {
39
+ throw new Error('openapi.empty');
40
+ }
41
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
42
+ return JSON.parse(trimmed);
43
+ }
44
+ throw new Error('openapi.yamlUnsupportedInSliceB');
45
+ }
46
+ throw new Error('openapi.body.required');
47
+ }
48
+
49
+ function validateOpenApiDocument(doc) {
50
+ if (!doc || typeof doc !== 'object' || Array.isArray(doc)) {
51
+ return { ok: false, reason: 'openapi.invalid', detail: 'Document must be an object' };
52
+ }
53
+ const version = typeof doc.openapi === 'string' ? doc.openapi.trim() : '';
54
+ if (!/^3\./.test(version)) {
55
+ return {
56
+ ok: false,
57
+ reason: 'openapi.invalid',
58
+ detail: 'Top-level openapi must be a 3.x string'
59
+ };
60
+ }
61
+ if (!doc.paths || typeof doc.paths !== 'object' || Array.isArray(doc.paths)) {
62
+ return {
63
+ ok: false,
64
+ reason: 'openapi.invalid',
65
+ detail: 'paths object is required'
66
+ };
67
+ }
68
+ if (Object.keys(doc.paths).length === 0) {
69
+ return {
70
+ ok: false,
71
+ reason: 'openapi.invalid',
72
+ detail: 'paths must not be empty for persist'
73
+ };
74
+ }
75
+ return { ok: true };
76
+ }
77
+
78
+ function documentBytes(doc) {
79
+ return Buffer.from(`${JSON.stringify(doc, null, 2)}\n`, 'utf8');
80
+ }
81
+
82
+ function openapiDirForContext(ctx) {
83
+ ensureIntegrationDirectory(ctx);
84
+ return path.join(ctx.getIntegrationRoot(), ctx.systemKey, 'openapi');
85
+ }
86
+
87
+ function relativeWorkspacePath(ctx, absolutePath) {
88
+ const root = ctx.getIntegrationRoot();
89
+ const rel = path.relative(root, absolutePath);
90
+ // root is .../integration; rel already includes <systemKey>/openapi/<file>
91
+ return path.join('integration', rel).split(path.sep).join('/');
92
+ }
93
+
94
+ function persistOneDocument(ctx, row) {
95
+ let filename = 'unknown.json';
96
+ try {
97
+ filename = safeOpenApiBasename(row && (row.filename || row.name));
98
+ } catch (error) {
99
+ return {
100
+ rejected: {
101
+ filename: String((row && (row.filename || row.name)) || 'unknown'),
102
+ reason: 'openapi.filename.invalid',
103
+ detail: error.message || String(error)
104
+ }
105
+ };
106
+ }
107
+ let doc;
108
+ try {
109
+ doc = parseOpenApiDocument(row && row.body);
110
+ } catch (error) {
111
+ return {
112
+ rejected: {
113
+ filename,
114
+ reason: 'openapi.invalid',
115
+ detail: error.message || String(error)
116
+ }
117
+ };
118
+ }
119
+ const check = validateOpenApiDocument(doc);
120
+ if (!check.ok) {
121
+ return { rejected: { filename, reason: check.reason, detail: check.detail } };
122
+ }
123
+ const bytes = documentBytes(doc);
124
+ const digest = `sha256:${sha256Hex(bytes)}`;
125
+ if (row.digest && String(row.digest).trim() && String(row.digest).trim() !== digest) {
126
+ return {
127
+ rejected: {
128
+ filename,
129
+ reason: 'openapi.digestMismatch',
130
+ detail: 'Provided digest does not match serialized body'
131
+ }
132
+ };
133
+ }
134
+ const dir = openapiDirForContext(ctx);
135
+ fs.mkdirSync(dir, { recursive: true });
136
+ const absolutePath = path.join(dir, filename);
137
+ fs.writeFileSync(absolutePath, bytes);
138
+ return {
139
+ accepted: {
140
+ filename,
141
+ path: relativeWorkspacePath(ctx, absolutePath),
142
+ digest
143
+ }
144
+ };
145
+ }
146
+
147
+ /**
148
+ * @param {import('./workspace-context').WorkspaceContext} ctx
149
+ * @param {Object} body
150
+ * @returns {{ accepted: Object[], rejected: Object[], workspaceRoot: string }}
151
+ */
152
+ function persistSuppliedOpenApi(ctx, body) {
153
+ assertValidSystemKey(ctx.systemKey);
154
+ const documents = body && Array.isArray(body.documents) ? body.documents : null;
155
+ if (!documents || documents.length === 0) {
156
+ throw new Error('documents array is required');
157
+ }
158
+ const accepted = [];
159
+ const rejected = [];
160
+ for (const row of documents) {
161
+ const outcome = persistOneDocument(ctx, row);
162
+ if (outcome.accepted) {
163
+ accepted.push(outcome.accepted);
164
+ } else if (outcome.rejected) {
165
+ rejected.push(outcome.rejected);
166
+ }
167
+ }
168
+ return {
169
+ accepted,
170
+ rejected,
171
+ workspaceRoot: ctx.getIntegrationRoot()
172
+ };
173
+ }
174
+
175
+ module.exports = {
176
+ persistSuppliedOpenApi,
177
+ validateOpenApiDocument,
178
+ safeOpenApiBasename,
179
+ parseOpenApiDocument
180
+ };
@@ -10,6 +10,7 @@
10
10
 
11
11
  const { buildContext } = require('./route-handlers-context');
12
12
  const { listSystems, createSystem, getSystemManifest, deleteSystem } = require('./workspace-ops');
13
+ const { persistSuppliedOpenApi } = require('./persist-supplied-openapi');
13
14
  const { runWizard } = require('./run-wizard');
14
15
  const {
15
16
  applyIntegrationDefinitionInWorkspace
@@ -63,6 +64,11 @@ const HANDLER_MAP = {
63
64
  }
64
65
  return createSystem(buildContext(auth, key), key);
65
66
  },
67
+ persistSuppliedOpenApi: (params, auth, body) =>
68
+ persistSuppliedOpenApi(
69
+ buildContext(auth, params.systemKey),
70
+ body && typeof body === 'object' ? body : {}
71
+ ),
66
72
  getSystemManifest: (params, auth) => getSystemManifest(buildContext(auth, params.systemKey)),
67
73
  deleteSystem: (params, auth) => deleteSystem(buildContext(auth, params.systemKey)),
68
74
  runWizard: (params, auth, body, query) =>
@@ -9,6 +9,7 @@
9
9
  'use strict';
10
10
 
11
11
  const path = require('path');
12
+ const fs = require('../internal/fs-real-sync');
12
13
  const {
13
14
  applyIntegrationDefinition,
14
15
  withDefinitionHash
@@ -20,10 +21,17 @@ const {
20
21
  } = require('./workspace-context');
21
22
  const { touchWorkspaceMeta } = require('./workspace-meta');
22
23
 
23
- const OPENAPI_ENRICHMENT_DIR = path.join(
24
- __dirname,
25
- '../integration-definition/openapi-enrichment'
26
- );
24
+ /**
25
+ * Workspace openapi/ is the only product resolve root for BID openapiSourceRef
26
+ * (517.2 persist layout). Do not bind apply to ship-local enrichment fixtures.
27
+ * @param {import('./workspace-context').WorkspaceContext} ctx
28
+ * @returns {string}
29
+ */
30
+ function workspaceOpenApiBaseDir(ctx) {
31
+ const dir = path.join(ctx.getIntegrationPath(), 'openapi');
32
+ fs.mkdirSync(dir, { recursive: true });
33
+ return dir;
34
+ }
27
35
 
28
36
  /**
29
37
  * @param {import('./workspace-context').WorkspaceContext} ctx
@@ -54,7 +62,7 @@ async function applyIntegrationDefinitionInWorkspace(ctx, body = {}) {
54
62
  outputDir: ctx.getIntegrationPath(),
55
63
  force,
56
64
  dryRun,
57
- openapiBaseDir: OPENAPI_ENRICHMENT_DIR
65
+ openapiBaseDir: workspaceOpenApiBaseDir(ctx)
58
66
  });
59
67
  touchWorkspaceMeta(ctx, {
60
68
  lastOperation: 'integration-definition-apply',
@@ -27,6 +27,10 @@ const EVIDENCE_DIR_NAME = 'evidence';
27
27
  const EVIDENCE_EXPORT_FILE = 'evidence.json';
28
28
  const VALIDATION_FAILED = 'failed';
29
29
  const VALIDATION_PASSED = 'passed';
30
+ /** Builder-keyed Category B catalogue — never import as Evidence (517.3 / 517.4.1). */
31
+ const SKIP_EVIDENCE_BASENAMES = new Set(['capability-business-catalog.json']);
32
+ /** PoC draft folders under evidence/ — never import as governed Evidence. */
33
+ const SKIP_EVIDENCE_DIR_NAMES = new Set(['candidates', 'candidate']);
30
34
 
31
35
  const EVIDENCE_MUTABLE_KEYS = [
32
36
  'key',
@@ -79,6 +83,23 @@ function isEvidenceFileName(fileName) {
79
83
  return lower.endsWith('.json') || lower.endsWith('.yaml') || lower.endsWith('.yml');
80
84
  }
81
85
 
86
+ /**
87
+ * Skip retired / forbidden evidence payloads (517.4.1 scrub).
88
+ * @param {string} filePath
89
+ * @param {unknown} parsed
90
+ * @returns {boolean}
91
+ */
92
+ function shouldSkipEvidencePayload(filePath, parsed) {
93
+ const base = path.basename(String(filePath || '')).toLowerCase();
94
+ if (SKIP_EVIDENCE_BASENAMES.has(base)) {
95
+ return true;
96
+ }
97
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && parsed._retired === true) {
98
+ return true;
99
+ }
100
+ return false;
101
+ }
102
+
82
103
  function collectEvidenceFiles(dirPath) {
83
104
  if (!fs.existsSync(dirPath)) {
84
105
  return [];
@@ -88,6 +109,10 @@ function collectEvidenceFiles(dirPath) {
88
109
  for (const entry of entries) {
89
110
  const fullPath = path.join(dirPath, entry.name);
90
111
  if (entry.isDirectory()) {
112
+ const dirName = String(entry.name || '').trim().toLowerCase();
113
+ if (SKIP_EVIDENCE_DIR_NAMES.has(dirName)) {
114
+ continue;
115
+ }
91
116
  files.push(...collectEvidenceFiles(fullPath));
92
117
  continue;
93
118
  }
@@ -152,8 +177,13 @@ function sanitizeEvidenceRow(row, systemKey, options = {}) {
152
177
  function loadEvidenceRowsFromFolder(evidencePath, systemKey) {
153
178
  const files = collectEvidenceFiles(evidencePath);
154
179
  const rows = [];
180
+ const skippedFiles = [];
155
181
  for (const filePath of files) {
156
182
  const parsed = parseEvidenceFile(filePath);
183
+ if (shouldSkipEvidencePayload(filePath, parsed)) {
184
+ skippedFiles.push(path.relative(evidencePath, filePath));
185
+ continue;
186
+ }
157
187
  const extractedRows = extractEvidenceRows(parsed);
158
188
  for (const row of extractedRows) {
159
189
  if (!row || typeof row !== 'object' || Array.isArray(row)) {
@@ -169,7 +199,7 @@ function loadEvidenceRowsFromFolder(evidencePath, systemKey) {
169
199
  rows.push(normalized);
170
200
  }
171
201
  }
172
- return { files, rows };
202
+ return { files, rows, skippedFiles };
173
203
  }
174
204
 
175
205
  function resolveEvidenceDirectory(systemKey, pathOptions = {}) {
@@ -277,21 +307,25 @@ function buildFailureSummary(failures) {
277
307
  return `${preview}${more}`;
278
308
  }
279
309
 
280
- function buildEvidenceDryRunSummary(evidencePath, files, rows) {
310
+ function buildEvidenceDryRunSummary(evidencePath, files, rows, skippedFiles = []) {
281
311
  return {
282
312
  skipped: false,
283
313
  dryRun: true,
284
314
  evidencePath,
285
315
  filesDiscovered: files.length,
316
+ filesSkipped: skippedFiles.length,
317
+ skippedFiles: [...skippedFiles],
286
318
  discovered: rows.length
287
319
  };
288
320
  }
289
321
 
290
- function buildEvidenceImportSummary(evidencePath, files, rows, loadResult) {
322
+ function buildEvidenceImportSummary(evidencePath, files, rows, loadResult, skippedFiles = []) {
291
323
  return {
292
324
  skipped: false,
293
325
  evidencePath,
294
326
  filesDiscovered: files.length,
327
+ filesSkipped: skippedFiles.length,
328
+ skippedFiles: [...skippedFiles],
295
329
  discovered: rows.length,
296
330
  loaded: Number(loadResult.inserted || 0) + Number(loadResult.updated || 0),
297
331
  validated: 0,
@@ -347,12 +381,18 @@ async function importRoleAssistantEvidence(params) {
347
381
  return { skipped: true, reason: 'not-role-assistant' };
348
382
  }
349
383
  const evidencePath = resolveEvidenceDirectory(systemKey, pathOptions);
350
- const { files, rows } = loadEvidenceRowsFromFolder(evidencePath, systemKey);
384
+ const { files, rows, skippedFiles } = loadEvidenceRowsFromFolder(evidencePath, systemKey);
351
385
  if (rows.length === 0) {
352
- return { skipped: true, reason: 'no-evidence-files', evidencePath };
386
+ return {
387
+ skipped: true,
388
+ reason: 'no-evidence-files',
389
+ evidencePath,
390
+ filesSkipped: (skippedFiles || []).length,
391
+ skippedFiles: [...(skippedFiles || [])]
392
+ };
353
393
  }
354
394
  if (dryRun) {
355
- return buildEvidenceDryRunSummary(evidencePath, files, rows);
395
+ return buildEvidenceDryRunSummary(evidencePath, files, rows, skippedFiles);
356
396
  }
357
397
  const { loadResult, refs } = await loadEvidenceAndResolveRefs(
358
398
  dataplaneUrl,
@@ -360,7 +400,13 @@ async function importRoleAssistantEvidence(params) {
360
400
  systemKey,
361
401
  rows
362
402
  );
363
- const summary = buildEvidenceImportSummary(evidencePath, files, rows, loadResult);
403
+ const summary = buildEvidenceImportSummary(
404
+ evidencePath,
405
+ files,
406
+ rows,
407
+ loadResult,
408
+ skippedFiles
409
+ );
364
410
  const failures = await runEvidenceLifecycleBatch(dataplaneUrl, authConfig, refs, summary);
365
411
  appendUnresolvedEvidenceFailures(summary, failures, refs.length, rows.length);
366
412
  throwWhenEvidenceLifecycleFailed(summary, failures);
@@ -443,6 +489,8 @@ function logRoleAssistantEvidenceExportSummary(systemKey, result, options = {})
443
489
 
444
490
  module.exports = {
445
491
  isDigitalWorkerModel,
492
+ shouldSkipEvidencePayload,
493
+ loadEvidenceRowsFromFolder,
446
494
  importRoleAssistantEvidence,
447
495
  exportRoleAssistantEvidence,
448
496
  logRoleAssistantEvidenceImportSummary,
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Build work.start attachments from case YAML (paths / openapiDir).
3
+ * @fileoverview 517.1 — map suite OpenAPI files to ATT attachment shape
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const path = require('path');
9
+ const fs = require('../internal/fs-real-sync');
10
+
11
+ function resolveCaseDirectory(testCase) {
12
+ const sourceFile =
13
+ testCase && typeof testCase.sourceFile === 'string' ? testCase.sourceFile.trim() : '';
14
+ if (!sourceFile) {
15
+ return process.cwd();
16
+ }
17
+ return path.dirname(path.resolve(sourceFile));
18
+ }
19
+
20
+ function readJsonAttachmentBody(absolutePath) {
21
+ if (!fs.existsSync(absolutePath)) {
22
+ throw new Error(`Role assistant attachment file not found: ${absolutePath}`);
23
+ }
24
+ const text = fs.readFileSync(absolutePath, 'utf8');
25
+ try {
26
+ return JSON.parse(text);
27
+ } catch (error) {
28
+ throw new Error(`Role assistant attachment is not valid JSON: ${absolutePath}`);
29
+ }
30
+ }
31
+
32
+ function attachmentFromFile(absolutePath, meta = {}) {
33
+ const basename = path.basename(absolutePath);
34
+ const role = typeof meta.role === 'string' && meta.role.trim() ? meta.role.trim() : 'openapi';
35
+ return {
36
+ name: basename,
37
+ filename: basename,
38
+ mediaType: 'application/json',
39
+ role,
40
+ contributionPurpose: 'task',
41
+ body: readJsonAttachmentBody(absolutePath)
42
+ };
43
+ }
44
+
45
+ function resolveRelativeAttachmentPath(caseDir, relativePath) {
46
+ if (typeof relativePath !== 'string' || !relativePath.trim()) {
47
+ throw new Error('Role assistant attachment path must be a non-empty string');
48
+ }
49
+ const absolutePath = path.resolve(caseDir, relativePath.trim());
50
+ return absolutePath;
51
+ }
52
+
53
+ function collectExplicitAttachments(caseDir, attachments) {
54
+ if (!Array.isArray(attachments) || attachments.length === 0) {
55
+ return [];
56
+ }
57
+ return attachments.map((row) => {
58
+ if (!row || typeof row !== 'object' || Array.isArray(row)) {
59
+ throw new Error('Role assistant input.attachments entries must be objects');
60
+ }
61
+ const rel = row.path || row.file || row.filename;
62
+ const absolutePath = resolveRelativeAttachmentPath(caseDir, rel);
63
+ return attachmentFromFile(absolutePath, row);
64
+ });
65
+ }
66
+
67
+ function listOpenapiJsonFiles(absoluteDir, allowList) {
68
+ if (!fs.existsSync(absoluteDir)) {
69
+ throw new Error(`Role assistant openapiDir not found: ${absoluteDir}`);
70
+ }
71
+ const names = fs
72
+ .readdirSync(absoluteDir)
73
+ .filter((entry) => /\.json$/i.test(entry))
74
+ .sort();
75
+ if (Array.isArray(allowList) && allowList.length > 0) {
76
+ const wanted = new Set(allowList.map((row) => String(row).trim()).filter(Boolean));
77
+ return names.filter((name) => wanted.has(name));
78
+ }
79
+ return names;
80
+ }
81
+
82
+ function collectOpenapiDirAttachments(caseDir, input) {
83
+ if (typeof input.openapiDir !== 'string' || !input.openapiDir.trim()) {
84
+ return [];
85
+ }
86
+ const absoluteDir = path.resolve(caseDir, input.openapiDir.trim());
87
+ const files = listOpenapiJsonFiles(absoluteDir, input.openapiFiles);
88
+ if (files.length === 0) {
89
+ throw new Error(`Role assistant openapiDir has no matching *.json files: ${absoluteDir}`);
90
+ }
91
+ return files.map((name) => attachmentFromFile(path.join(absoluteDir, name), { role: 'openapi' }));
92
+ }
93
+
94
+ /**
95
+ * Build ATT-shaped attachments for work.start from case input.
96
+ * @param {Object} testCase - Normalized case (may include sourceFile)
97
+ * @returns {Object[]|undefined}
98
+ */
99
+ function buildStartAttachments(testCase) {
100
+ const input =
101
+ testCase && typeof testCase.input === 'object' && testCase.input && !Array.isArray(testCase.input)
102
+ ? testCase.input
103
+ : {};
104
+ const caseDir = resolveCaseDirectory(testCase);
105
+ const fromExplicit = collectExplicitAttachments(caseDir, input.attachments);
106
+ const fromDir = collectOpenapiDirAttachments(caseDir, input);
107
+ const merged = [...fromExplicit, ...fromDir];
108
+ if (merged.length === 0) {
109
+ return undefined;
110
+ }
111
+ return merged;
112
+ }
113
+
114
+ module.exports = {
115
+ buildStartAttachments,
116
+ resolveCaseDirectory,
117
+ attachmentFromFile
118
+ };
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * Soft-loop BID apply after workspace.createsystem (suite 002 / plan 512.4).
3
- * @fileoverview Apply input.bidPath into builder-api workspace when Runtime COM
4
- * cannot yet invoke template.applyintegrationdefinition (RA facade not refreshed).
3
+ * @fileoverview LAB-ONLY debt helper: POST Builder-api HTTP apply when the RA
4
+ * soft loop needs a package materialize. Product path is Decision Engine →
5
+ * capability.execute(template.applyintegrationdefinition) → COM/CIP.
6
+ * Do not treat this module as the product apply path once Runtime COM invoke
7
+ * works for the suite (plan 517.5). Non-product — lab helper only.
5
8
  */
6
9
 
7
10
  'use strict';
@@ -12,6 +15,7 @@ const {
12
15
  loadCaseBidDocument,
13
16
  resolveTargetSystemKey
14
17
  } = require('./test-runner-bid-document');
18
+ const { buildStartAttachments } = require('./test-runner-attachments');
15
19
 
16
20
  /**
17
21
  * @returns {string}
@@ -59,6 +63,7 @@ function extractBearer(authConfig) {
59
63
  }
60
64
 
61
65
  /**
66
+ * True when wait is for template.applyintegrationdefinition and a case BID exists.
62
67
  * @param {Object} testCase
63
68
  * @param {Object} wait
64
69
  * @returns {boolean}
@@ -71,10 +76,95 @@ function shouldApplyBidAfterWait(testCase, wait) {
71
76
  const capability = String(
72
77
  wait.questionKey || wait.objectRef?.capabilityKey || wait.prompt || ''
73
78
  ).toLowerCase();
74
- return (
75
- capability.includes('createsystem') ||
76
- capability.includes('applyintegrationdefinition')
77
- );
79
+ // Product path: DE → capability.execute(template.applyintegrationdefinition).
80
+ return capability.includes('applyintegrationdefinition');
81
+ }
82
+
83
+ /**
84
+ * Lab-only soft HTTP BID apply. Hard cases (expected.soft !== true) must rely on
85
+ * Runtime COM/CIP apply after respond+continue — not Builder-api HTTP (517.5 / 517.4.7).
86
+ * @param {Object} testCase
87
+ * @param {Object} wait
88
+ * @returns {boolean}
89
+ */
90
+ function shouldSoftHttpBidApply(testCase, wait) {
91
+ if (!shouldApplyBidAfterWait(testCase, wait)) {
92
+ return false;
93
+ }
94
+ return testCase?.expected?.soft === true;
95
+ }
96
+
97
+ /**
98
+ * @param {Object} testCase
99
+ * @returns {Object[]|null}
100
+ */
101
+ function collectOpenApiPersistDocuments(testCase) {
102
+ let attachments = [];
103
+ try {
104
+ attachments = buildStartAttachments(testCase) || [];
105
+ } catch (_err) {
106
+ return null;
107
+ }
108
+ const documents = attachments
109
+ .filter((row) => row && (row.role === 'openapi' || !row.role) && row.body)
110
+ .map((row) => ({
111
+ filename: row.filename || row.name,
112
+ body: row.body
113
+ }))
114
+ .filter((row) => row.filename);
115
+ return documents.length > 0 ? documents : null;
116
+ }
117
+
118
+ /**
119
+ * @param {Response} res
120
+ * @param {string} label
121
+ * @returns {Promise<Object|null>}
122
+ */
123
+ async function readBuilderApiJsonOrThrow(res, label) {
124
+ const text = await res.text();
125
+ let body = null;
126
+ try {
127
+ body = text ? JSON.parse(text) : null;
128
+ } catch (_err) {
129
+ body = { raw: text };
130
+ }
131
+ if (!res.ok) {
132
+ const err = new Error(
133
+ `${label} HTTP ${res.status}: ${typeof body === 'object' ? JSON.stringify(body).slice(0, 300) : text}`
134
+ );
135
+ err.status = res.status;
136
+ throw err;
137
+ }
138
+ return body && body.data ? body.data : body;
139
+ }
140
+
141
+ /**
142
+ * Lab debt: stage suite OpenAPI into workspace via 517.2 persist API before apply.
143
+ * Product path is DE → capability.execute(persistsuppliedopenapi).
144
+ * @param {Object} authConfig
145
+ * @param {Object} testCase
146
+ * @param {string} systemKey
147
+ * @returns {Promise<Object|null>}
148
+ */
149
+ async function persistCaseOpenApiViaBuilderApi(authConfig, testCase, systemKey) {
150
+ const token = extractBearer(authConfig);
151
+ if (!token || !systemKey) {
152
+ return null;
153
+ }
154
+ const documents = collectOpenApiPersistDocuments(testCase);
155
+ if (!documents) {
156
+ return null;
157
+ }
158
+ const url = `${resolveBuilderApiBaseUrl()}/api/v1/builder/systems/${encodeURIComponent(systemKey)}/openapi/persist-supplied`;
159
+ const res = await fetch(url, {
160
+ method: 'POST',
161
+ headers: {
162
+ Authorization: `Bearer ${token}`,
163
+ 'Content-Type': 'application/json'
164
+ },
165
+ body: JSON.stringify({ documents })
166
+ });
167
+ return readBuilderApiJsonOrThrow(res, 'OpenAPI persist failed');
78
168
  }
79
169
 
80
170
  /**
@@ -89,6 +179,7 @@ async function applyCaseBidViaBuilderApi(authConfig, testCase) {
89
179
  if (!bid || !systemKey || !token) {
90
180
  return null;
91
181
  }
182
+ await persistCaseOpenApiViaBuilderApi(authConfig, testCase, systemKey);
92
183
  const url = `${resolveBuilderApiBaseUrl()}/api/v1/builder/systems/${encodeURIComponent(systemKey)}/integration-definition/apply`;
93
184
  const res = await fetch(url, {
94
185
  method: 'POST',
@@ -98,25 +189,13 @@ async function applyCaseBidViaBuilderApi(authConfig, testCase) {
98
189
  },
99
190
  body: JSON.stringify({ integrationDefinition: bid, force: true })
100
191
  });
101
- const text = await res.text();
102
- let body = null;
103
- try {
104
- body = text ? JSON.parse(text) : null;
105
- } catch (_err) {
106
- body = { raw: text };
107
- }
108
- if (!res.ok) {
109
- const err = new Error(
110
- `BID apply failed HTTP ${res.status}: ${typeof body === 'object' ? JSON.stringify(body).slice(0, 300) : text}`
111
- );
112
- err.status = res.status;
113
- throw err;
114
- }
115
- return body && body.data ? body.data : body;
192
+ return readBuilderApiJsonOrThrow(res, 'BID apply failed');
116
193
  }
117
194
 
118
195
  module.exports = {
119
196
  resolveBuilderApiBaseUrl,
120
197
  shouldApplyBidAfterWait,
198
+ shouldSoftHttpBidApply,
199
+ persistCaseOpenApiViaBuilderApi,
121
200
  applyCaseBidViaBuilderApi
122
201
  };
@@ -5,8 +5,8 @@
5
5
 
6
6
  'use strict';
7
7
 
8
- const fs = require('../internal/fs-real-sync');
9
8
  const path = require('path');
9
+ const fs = require('../internal/fs-real-sync');
10
10
 
11
11
  /**
12
12
  * @param {Object} testCase