@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
@@ -8,11 +8,14 @@ const path = require('path');
8
8
  const fs = require('../internal/fs-real-sync');
9
9
  const yaml = require('js-yaml');
10
10
  const { resolveExternalSystemPath } = require('../utils/app-path-resolve');
11
- const { VERDICT } = require('../lifecycle/product-model');
12
11
  const {
13
12
  buildHonestyObservation,
14
13
  evaluateHonestyExpectations
15
14
  } = require('./test-cases-honesty');
15
+ const {
16
+ SUPPORTED_LIFECYCLE_STATES,
17
+ deriveLifecycleState
18
+ } = require('./test-cases-lifecycle');
16
19
  function loadNodeFsSnapshot() {
17
20
  if (global.__AIFABRIX_NODE_FS_UNMOCKED__) {
18
21
  return global.__AIFABRIX_NODE_FS_UNMOCKED__;
@@ -22,32 +25,6 @@ function loadNodeFsSnapshot() {
22
25
  : require('node:fs');
23
26
  }
24
27
  const nodeFs = loadNodeFsSnapshot();
25
- const SUPPORTED_LIFECYCLE_STATES = new Set(['completed', 'question', 'approval-required', 'dependency', 'safe-stop', 'rejected', 'failed']);
26
- const LIFECYCLE_STATE_ALIASES = Object.freeze({
27
- completed: ['completed', 'complete', 'done', 'success', 'verified'],
28
- question: ['question', 'question_required', 'awaiting_question', 'pending_question'],
29
- 'approval-required': ['approval_required', 'awaiting_approval', 'pending_approval', 'requires_approval'],
30
- dependency: ['dependency', 'dependency_wait', 'dependency_blocked', 'awaiting_dependency'],
31
- 'safe-stop': ['safe_stop', 'safe_stopped', 'policy_blocked', 'halted', 'paused'],
32
- rejected: ['rejected', 'denied', 'declined', 'policy_rejected'],
33
- failed: ['failed', 'error', 'errored', 'timeout', 'cancelled', 'canceled']
34
- });
35
-
36
- const WORK_STATUS_TO_LIFECYCLE = Object.freeze({
37
- completed: 'completed', waiting: 'question', running: 'dependency', stopped: 'safe-stop'
38
- });
39
- const WAIT_REASON_TO_LIFECYCLE = Object.freeze({
40
- ask: 'question', approval: 'approval-required', ownerroute: 'dependency',
41
- realityconfirm: 'question', knowledgeconfirm: 'question'
42
- });
43
- const WORKER_TASK_STATUS_TO_LIFECYCLE = Object.freeze({
44
- draft: 'dependency', pendingapproval: 'approval-required', approved: 'dependency',
45
- running: 'dependency', completed: 'completed', failed: 'failed',
46
- cancelled: 'rejected', canceled: 'rejected'
47
- });
48
- const WORKER_APPROVAL_STATE_TO_LIFECYCLE = Object.freeze({
49
- pending: 'approval-required', approved: 'dependency', rejected: 'rejected'
50
- });
51
28
  function safeExistsSync(filePath) {
52
29
  // Prefer unmocked node:fs so Jest worker fs mocks cannot hide real temp files.
53
30
  return nodeFs.existsSync(filePath) || fs.existsSync(filePath);
@@ -105,6 +82,7 @@ function normalizeCaseFromFile(filePath, parsed) {
105
82
  answers: Array.isArray(parsed.answers) ? parsed.answers : [],
106
83
  approvals: Array.isArray(parsed.approvals) ? parsed.approvals : [],
107
84
  expected,
85
+ metadata: parsed.metadata && typeof parsed.metadata === 'object' ? parsed.metadata : {},
108
86
  preflight: parsed.preflight && typeof parsed.preflight === 'object' ? parsed.preflight : {},
109
87
  sourceFile: filePath
110
88
  };
@@ -202,150 +180,11 @@ function loadRoleAssistantTestCase(roleKey, selector, options = {}) {
202
180
  throw new Error(`Role assistant test case ${lookup} not found in ${testsDir}`);
203
181
  }
204
182
  const parsed = parseCaseYamlFile(filePath);
183
+ const { assertTestCaseDocument } = require('./test-case-validate');
184
+ assertTestCaseDocument(parsed, filePath);
205
185
  return normalizeCaseFromFile(filePath, parsed);
206
186
  }
207
187
 
208
- function normalizeLifecycleStateToken(value) {
209
- if (typeof value !== 'string') {
210
- return '';
211
- }
212
- return value
213
- .trim()
214
- .toLowerCase()
215
- .replace(/[^a-z0-9]+/g, '_')
216
- .replace(/^_+|_+$/g, '');
217
- }
218
-
219
- function lifecycleStateFromToken(token) {
220
- if (!token) {
221
- return null;
222
- }
223
- for (const [state, aliases] of Object.entries(LIFECYCLE_STATE_ALIASES)) {
224
- for (const alias of aliases) {
225
- if (token === alias || token.startsWith(`${alias}_`)) {
226
- return state;
227
- }
228
- }
229
- }
230
- return null;
231
- }
232
-
233
- function lifecycleStateFromSignalValue(value) {
234
- if (value === null || value === undefined) {
235
- return null;
236
- }
237
- if (typeof value === 'string') {
238
- return lifecycleStateFromToken(normalizeLifecycleStateToken(value));
239
- }
240
- if (Array.isArray(value)) {
241
- for (const row of value) {
242
- const state = lifecycleStateFromSignalValue(row);
243
- if (state) {
244
- return state;
245
- }
246
- }
247
- return null;
248
- }
249
- if (typeof value === 'object') {
250
- const fields = [
251
- 'lifecycleState',
252
- 'state',
253
- 'status',
254
- 'code',
255
- 'type',
256
- 'reason',
257
- 'kind',
258
- 'category',
259
- 'errorCode'
260
- ];
261
- for (const field of fields) {
262
- const state = lifecycleStateFromSignalValue(value[field]);
263
- if (state) {
264
- return state;
265
- }
266
- }
267
- }
268
- return null;
269
- }
270
-
271
- function mapWorkStatusFromResult(result) {
272
- const rawStatus = normalizeLifecycleStateToken(result?.status);
273
- if (rawStatus && WORK_STATUS_TO_LIFECYCLE[rawStatus]) {
274
- const waitReason = normalizeLifecycleStateToken(result?.wait?.waitReason || '');
275
- if (rawStatus === 'waiting' && waitReason && WAIT_REASON_TO_LIFECYCLE[waitReason]) {
276
- return WAIT_REASON_TO_LIFECYCLE[waitReason];
277
- }
278
- return WORK_STATUS_TO_LIFECYCLE[rawStatus];
279
- }
280
- return null;
281
- }
282
-
283
- function mapWorkerTaskStateFromResult(result) {
284
- const statusCandidates = [result?.task?.status, result?.status, result?.workerTask?.status];
285
- for (const row of statusCandidates) {
286
- const token = normalizeLifecycleStateToken(row);
287
- if (token && WORKER_TASK_STATUS_TO_LIFECYCLE[token]) {
288
- return WORKER_TASK_STATUS_TO_LIFECYCLE[token];
289
- }
290
- }
291
- const approvalCandidates = [result?.task?.approvalState, result?.approvalState, result?.workerTask?.approvalState];
292
- for (const row of approvalCandidates) {
293
- const token = normalizeLifecycleStateToken(row);
294
- if (token && WORKER_APPROVAL_STATE_TO_LIFECYCLE[token]) {
295
- return WORKER_APPROVAL_STATE_TO_LIFECYCLE[token];
296
- }
297
- }
298
- return null;
299
- }
300
-
301
- function resolveLifecycleStateFromResult(result) {
302
- const workState = mapWorkStatusFromResult(result);
303
- if (workState) {
304
- return workState;
305
- }
306
- const workerTaskState = mapWorkerTaskStateFromResult(result);
307
- if (workerTaskState) {
308
- return workerTaskState;
309
- }
310
- const topLevelCandidates = [
311
- result?.lifecycleState, result?.state, result?.status, result?.lifecycle?.state,
312
- result?.report?.lifecycleState, result?.report?.state, result?.report?.status,
313
- result?.report?.lifecycle?.state, result?.report?.runtime?.state, result?.report?.work?.state
314
- ];
315
- for (const candidate of topLevelCandidates) {
316
- const state = lifecycleStateFromSignalValue(candidate);
317
- if (state) {
318
- return state;
319
- }
320
- }
321
- const steps = Array.isArray(result?.stepsRun) ? result.stepsRun : [];
322
- for (const row of steps) {
323
- const stepCandidates = [
324
- row?.lifecycleState, row?.state, row?.status, row?.code, row?.errorCode, row?.reason,
325
- row?.kind, row?.type, row?.error, row?.failure, row?.details, row?.result
326
- ];
327
- for (const candidate of stepCandidates) {
328
- const state = lifecycleStateFromSignalValue(candidate);
329
- if (state) {
330
- return state;
331
- }
332
- }
333
- }
334
- return null;
335
- }
336
-
337
- function deriveLifecycleState(result, lifecycleVerdict) {
338
- if (lifecycleVerdict === VERDICT.VERIFIED) {
339
- return 'completed';
340
- }
341
- const structuredState = resolveLifecycleStateFromResult(result);
342
- if (structuredState) {
343
- return structuredState;
344
- }
345
- if (lifecycleVerdict === VERDICT.NOT_VERIFIED) return 'safe-stop';
346
- return 'failed';
347
- }
348
-
349
188
  function addCapabilityToken(set, value) {
350
189
  if (typeof value === 'string' && value.trim()) set.add(value.trim());
351
190
  }
@@ -361,9 +200,34 @@ function collectCapabilitiesFromWorkSteps(out, steps) {
361
200
  addCapabilityToken(out, row.executionOutcome.capabilityKey);
362
201
  addCapabilityToken(out, row.executionOutcome.capability);
363
202
  }
203
+ const execution = row.capabilityExecution;
204
+ if (execution && typeof execution === 'object') {
205
+ addCapabilityToken(out, execution.capabilityKey);
206
+ addCapabilityToken(out, execution.capability);
207
+ }
364
208
  }
365
209
  }
366
210
 
211
+ /**
212
+ * Identity-preflight CIP fallback uses operationId "operation" (or omits it).
213
+ * That is a Reality probe log, not an observed write capability.
214
+ * @param {object} row
215
+ * @returns {boolean}
216
+ */
217
+ function cipArtifactIsUnspecifiedOperation(row) {
218
+ const fingerprint = typeof row.fingerprint === 'string' ? row.fingerprint : '';
219
+ const opMatch = fingerprint.match(/(?:^|[|,])operationId=([^|,]+)/);
220
+ if (opMatch) {
221
+ const operationId = opMatch[1].trim();
222
+ return !operationId || operationId === 'operation';
223
+ }
224
+ const artifactRef = typeof row.artifactRef === 'string' ? row.artifactRef : '';
225
+ if (/^cip-log:[^:]+:[^:]+:operation:/.test(artifactRef)) {
226
+ return true;
227
+ }
228
+ return /(?:^|[|,])capabilityKey=/.test(fingerprint);
229
+ }
230
+
367
231
  /**
368
232
  * Work-result artifactIndex often carries CIP fingerprints like
369
233
  * `capabilityKey=customer.createBasic|operationId=createbasic` when steps[] is empty.
@@ -374,6 +238,7 @@ function collectCapabilitiesFromArtifactIndex(out, artifacts) {
374
238
  if (!Array.isArray(artifacts)) return;
375
239
  for (const row of artifacts) {
376
240
  if (!row || typeof row !== 'object') continue;
241
+ if (cipArtifactIsUnspecifiedOperation(row)) continue;
377
242
  addCapabilityToken(out, row.capabilityKey);
378
243
  addCapabilityToken(out, row.capability);
379
244
  const fingerprint = typeof row.fingerprint === 'string' ? row.fingerprint : '';
@@ -414,6 +279,7 @@ function collectObservedCapabilities(result) {
414
279
  module.exports = {
415
280
  SUPPORTED_LIFECYCLE_STATES,
416
281
  roleAssistantTestsDir,
282
+ listTestCaseFiles,
417
283
  loadRoleAssistantTestCase,
418
284
  deriveLifecycleState,
419
285
  collectObservedCapabilities,
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Map local Role Assistant YAML cases to a published test pack (and back).
3
+ *
4
+ * @fileoverview YAML specification only — currentStatus stays on dataplane
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+ const yaml = require('js-yaml');
13
+ const fs = require('../internal/fs-real-sync');
14
+ const { roleAssistantTestsDir } = require('./test-cases');
15
+ const { discoverSuiteCaseFiles } = require('./test-suite');
16
+ const { roleAssistantSystemKey } = require('../utils/role-assistant-paths');
17
+
18
+ const PACK_API_VERSION = 'dataplane.aifabrix.ai/v1';
19
+ const PACK_KIND = 'RoleAssistantTestPack';
20
+ const STATUS_KEYS = new Set(['currentStatus', 'lastTestedAt', 'summary', 'sourceFile']);
21
+ const YAML_SPEC_KEYS = [
22
+ 'caseId',
23
+ 'input',
24
+ 'answers',
25
+ 'approvals',
26
+ 'expected',
27
+ 'preflight',
28
+ 'metadata'
29
+ ];
30
+ const PACK_INPUT_ALIASES = ['executionPath', 'roleRef', 'businessRoleRef'];
31
+
32
+ function isYamlName(name) {
33
+ return /\.(ya?ml)$/i.test(name);
34
+ }
35
+
36
+ function listRootYamlFiles(testsDir) {
37
+ if (!fs.existsSync(testsDir)) {
38
+ return [];
39
+ }
40
+ return fs.readdirSync(testsDir)
41
+ .filter((name) => isYamlName(name))
42
+ .map((name) => path.join(testsDir, name));
43
+ }
44
+
45
+ function copySpecFields(parsed, caseId) {
46
+ const out = { caseId };
47
+ for (const key of YAML_SPEC_KEYS) {
48
+ if (key === 'caseId' || parsed[key] === undefined) {
49
+ continue;
50
+ }
51
+ out[key] = parsed[key];
52
+ }
53
+ const input = out.input && typeof out.input === 'object' && !Array.isArray(out.input)
54
+ ? out.input
55
+ : {};
56
+ for (const key of PACK_INPUT_ALIASES) {
57
+ if (input[key] !== undefined) {
58
+ out[key] = input[key];
59
+ }
60
+ }
61
+ return out;
62
+ }
63
+
64
+ function specCaseFromFile(filePath) {
65
+ const parsed = yaml.load(fs.readFileSync(filePath, 'utf8'));
66
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
67
+ throw new Error(`Test case must be an object: ${filePath}`);
68
+ }
69
+ const fileBase = path.basename(filePath, path.extname(filePath));
70
+ const caseId = String(parsed.caseId || fileBase).trim();
71
+ if (!caseId) {
72
+ throw new Error(`Test case is missing caseId: ${filePath}`);
73
+ }
74
+ return copySpecFields(parsed, caseId);
75
+ }
76
+
77
+ function collectSuiteMap(testsDir) {
78
+ const suites = new Map();
79
+ const add = (suiteId, filePath) => {
80
+ const id = String(suiteId || 'default').trim() || 'default';
81
+ if (!suites.has(id)) {
82
+ suites.set(id, []);
83
+ }
84
+ suites.get(id).push(specCaseFromFile(filePath));
85
+ };
86
+ for (const filePath of listRootYamlFiles(testsDir)) {
87
+ add('default', filePath);
88
+ }
89
+ for (const row of discoverSuiteCaseFiles(testsDir)) {
90
+ add(row.suite, row.absPath);
91
+ }
92
+ return suites;
93
+ }
94
+
95
+ function buildRoleAssistantTestPack(systemKey, suiteMap) {
96
+ const key = roleAssistantSystemKey(systemKey);
97
+ const suites = [];
98
+ for (const [suiteId, cases] of suiteMap.entries()) {
99
+ if (cases.length > 0) {
100
+ suites.push({ suiteId, cases });
101
+ }
102
+ }
103
+ return {
104
+ apiVersion: PACK_API_VERSION,
105
+ kind: PACK_KIND,
106
+ metadata: { key },
107
+ spec: { suites }
108
+ };
109
+ }
110
+
111
+ function mapLocalTestsToPack(systemKey, options = {}) {
112
+ const testsDir = roleAssistantTestsDir(systemKey, options);
113
+ if (!fs.existsSync(testsDir)) {
114
+ return { skipped: true, reason: 'No tests folder' };
115
+ }
116
+ const suiteMap = collectSuiteMap(testsDir);
117
+ const pack = buildRoleAssistantTestPack(systemKey, suiteMap);
118
+ if (!pack.spec.suites.length) {
119
+ return { skipped: true, reason: 'No test cases in tests folder' };
120
+ }
121
+ return { skipped: false, pack, testsDir };
122
+ }
123
+
124
+ function mergePackAliasesIntoInput(packCase, input) {
125
+ const next = { ...input };
126
+ for (const key of PACK_INPUT_ALIASES) {
127
+ if (packCase[key] !== undefined && next[key] === undefined) {
128
+ next[key] = packCase[key];
129
+ }
130
+ }
131
+ return next;
132
+ }
133
+
134
+ function yamlFromPackCase(packCase) {
135
+ const body = {};
136
+ for (const key of YAML_SPEC_KEYS) {
137
+ if (packCase[key] === undefined || STATUS_KEYS.has(key)) {
138
+ continue;
139
+ }
140
+ body[key] = packCase[key];
141
+ }
142
+ const input = body.input && typeof body.input === 'object' && !Array.isArray(body.input)
143
+ ? body.input
144
+ : {};
145
+ const merged = mergePackAliasesIntoInput(packCase, input);
146
+ if (Object.keys(merged).length > 0) {
147
+ body.input = merged;
148
+ }
149
+ return yaml.dump(body, { lineWidth: 120, noRefs: true, sortKeys: false });
150
+ }
151
+
152
+ function writePackToTestsDir(pack, testsDir) {
153
+ const suites = pack && pack.spec && Array.isArray(pack.spec.suites) ? pack.spec.suites : [];
154
+ fs.mkdirSync(testsDir, { recursive: true });
155
+ let written = 0;
156
+ for (const suite of suites) {
157
+ const suiteId = String((suite && suite.suiteId) || 'default').trim() || 'default';
158
+ const suiteDir = path.join(testsDir, suiteId);
159
+ fs.mkdirSync(suiteDir, { recursive: true });
160
+ const cases = Array.isArray(suite.cases) ? suite.cases : [];
161
+ for (const row of cases) {
162
+ const caseId = String((row && row.caseId) || '').trim();
163
+ if (!caseId) {
164
+ continue;
165
+ }
166
+ fs.writeFileSync(path.join(suiteDir, `${caseId}.yaml`), yamlFromPackCase(row), 'utf8');
167
+ written += 1;
168
+ }
169
+ }
170
+ return written;
171
+ }
172
+
173
+ module.exports = {
174
+ PACK_API_VERSION,
175
+ PACK_KIND,
176
+ YAML_SPEC_KEYS,
177
+ mapLocalTestsToPack,
178
+ writePackToTestsDir,
179
+ specCaseFromFile
180
+ };
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Role Assistant test-pack sidecar: upload PUT, download GET, case status PATCH.
3
+ *
4
+ * @fileoverview Shared CLI and builder-api adapters — no Task/PE or /runs
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const os = require('os');
12
+ const path = require('path');
13
+ const logger = require('../utils/logger');
14
+ const { formatWarningLine } = require('../utils/cli-test-layout-chalk');
15
+ const { isDigitalWorkerKind } = require('../utils/platform-shell-package');
16
+ const { roleAssistantTestsDir } = require('./test-cases');
17
+ const { mapLocalTestsToPack, writePackToTestsDir } = require('./test-pack-map');
18
+ const {
19
+ upsertRoleAssistantTestPack,
20
+ getRoleAssistantTestPack,
21
+ patchRoleAssistantTestCaseStatus
22
+ } = require('../api/role-assistant-test-pack.api');
23
+ const fs = require('../internal/fs-real-sync');
24
+
25
+ function skipResult(reason) {
26
+ return { skipped: true, reason };
27
+ }
28
+
29
+ function isRoleAssistantPayload(payload) {
30
+ return isDigitalWorkerKind(payload && payload.application);
31
+ }
32
+
33
+ async function runTestsSidecar(ctx, sidecar, quietSidecar) {
34
+ if (!isRoleAssistantPayload(ctx.payload)) {
35
+ sidecar.tests = skipResult('Not a Role Assistant package');
36
+ return;
37
+ }
38
+ if (ctx.options && ctx.options.dryRun === true) {
39
+ sidecar.tests = skipResult('Dry run');
40
+ return;
41
+ }
42
+ const mapped = mapLocalTestsToPack(ctx.systemKey, {
43
+ pathOptions: (ctx.options && ctx.options.pathOptions) || {},
44
+ testsDir: ctx.options && ctx.options.testsDir
45
+ });
46
+ if (mapped.skipped) {
47
+ sidecar.tests = skipResult(mapped.reason);
48
+ return;
49
+ }
50
+ try {
51
+ const uploaded = await upsertRoleAssistantTestPack(
52
+ ctx.dataplaneUrl,
53
+ ctx.authConfig,
54
+ ctx.systemKey,
55
+ mapped.pack
56
+ );
57
+ sidecar.tests = {
58
+ skipped: false,
59
+ packVersion: uploaded && uploaded.packVersion,
60
+ packKey: uploaded && uploaded.packKey
61
+ };
62
+ } catch (err) {
63
+ sidecar.tests = skipResult(err.message || String(err));
64
+ if (!quietSidecar) {
65
+ logger.log(formatWarningLine(`Role assistant tests upload skipped: ${err.message}`));
66
+ }
67
+ }
68
+ }
69
+
70
+ async function exportRoleAssistantTests(params) {
71
+ const { systemKey, dataplaneUrl, authConfig, options = {} } = params;
72
+ const row = await getRoleAssistantTestPack(dataplaneUrl, authConfig, systemKey, {
73
+ allowMissing: true
74
+ });
75
+ if (!row || !row.pack) {
76
+ return skipResult('No published test pack');
77
+ }
78
+ const testsDir = roleAssistantTestsDir(systemKey, {
79
+ pathOptions: options.pathOptions || {},
80
+ testsDir: options.testsDir
81
+ });
82
+ const written = writePackToTestsDir(row.pack, testsDir);
83
+ return { skipped: false, written, testsDir };
84
+ }
85
+
86
+ function publishedTestsOwnerToken() {
87
+ if (typeof process.getuid === 'function') {
88
+ return String(process.getuid());
89
+ }
90
+ return 'u';
91
+ }
92
+
93
+ function publishedTestsTempDir(systemKey) {
94
+ const key = String(systemKey || 'pack').replace(/[^a-zA-Z0-9._-]+/g, '_');
95
+ return path.join(os.tmpdir(), `aifabrix-ra-tests-${publishedTestsOwnerToken()}`, key);
96
+ }
97
+
98
+ async function materializePublishedTests(systemKey, dataplaneUrl, authConfig, destRoot) {
99
+ const row = await getRoleAssistantTestPack(dataplaneUrl, authConfig, systemKey, {
100
+ allowMissing: true
101
+ });
102
+ if (!row || !row.pack) {
103
+ throw new Error(
104
+ `No published tests for ${systemKey}. Upload the Role Assistant package first.`
105
+ );
106
+ }
107
+ if (fs.existsSync(destRoot)) {
108
+ fs.rmSync(destRoot, { recursive: true, force: true });
109
+ }
110
+ const written = writePackToTestsDir(row.pack, destRoot);
111
+ return { testsDir: destRoot, written };
112
+ }
113
+
114
+ function caseStatusFromEvaluation(evaluation) {
115
+ if (!evaluation) {
116
+ return 'skipped';
117
+ }
118
+ if (evaluation.passed === true) {
119
+ return 'passed';
120
+ }
121
+ if (evaluation.passed === false) {
122
+ return 'failed';
123
+ }
124
+ return 'skipped';
125
+ }
126
+
127
+ function caseSummaryFromEvaluation(evaluation) {
128
+ if (!evaluation) {
129
+ return undefined;
130
+ }
131
+ const summary = evaluation.summary || evaluation.reason || evaluation.error;
132
+ return typeof summary === 'string' && summary.trim() ? summary.trim() : undefined;
133
+ }
134
+
135
+ async function loadPublishedTestsIntoOptions(systemKey, opts, authContext) {
136
+ if (!opts || opts.fromDataplane !== true) {
137
+ return opts;
138
+ }
139
+ const dest = publishedTestsTempDir(systemKey);
140
+ await materializePublishedTests(
141
+ systemKey,
142
+ authContext.dataplaneUrl,
143
+ authContext.authConfig,
144
+ dest
145
+ );
146
+ return { ...opts, casesRoot: dest, testsDir: dest };
147
+ }
148
+
149
+ async function patchCaseStatusAfterRun(params) {
150
+ const { systemKey, caseId, evaluation, dataplaneUrl, authConfig } = params;
151
+ if (!dataplaneUrl || !authConfig || !caseId) {
152
+ return { skipped: true, reason: 'Missing dataplane context' };
153
+ }
154
+ const body = {
155
+ status: caseStatusFromEvaluation(evaluation),
156
+ testedAt: new Date().toISOString()
157
+ };
158
+ const summary = caseSummaryFromEvaluation(evaluation);
159
+ if (summary) {
160
+ body.summary = summary;
161
+ }
162
+ try {
163
+ await patchRoleAssistantTestCaseStatus(
164
+ dataplaneUrl,
165
+ authConfig,
166
+ systemKey,
167
+ caseId,
168
+ body
169
+ );
170
+ return { skipped: false, status: body.status };
171
+ } catch (err) {
172
+ logger.log(
173
+ formatWarningLine(`Could not update published case status for ${caseId}: ${err.message}`)
174
+ );
175
+ return { skipped: true, reason: err.message || String(err) };
176
+ }
177
+ }
178
+
179
+ module.exports = {
180
+ runTestsSidecar,
181
+ exportRoleAssistantTests,
182
+ publishedTestsTempDir,
183
+ materializePublishedTests,
184
+ loadPublishedTestsIntoOptions,
185
+ caseStatusFromEvaluation,
186
+ patchCaseStatusAfterRun
187
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Role assistant --debug Work Result fetch and log persistence.
3
+ *
4
+ * @fileoverview Technical work-result depth and logs under role-assistant/<role>/logs/
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('../internal/fs-real-sync');
12
+ const path = require('path');
13
+ const { getWorkExecutionResult } = require('../api/work.api');
14
+ const { sanitizeForLog } = require('../utils/test-log-writer');
15
+ const {
16
+ getRoleAssistantPath,
17
+ roleAssistantSystemKey
18
+ } = require('../utils/role-assistant-paths');
19
+
20
+ function requestedWorkResultDepth(options) {
21
+ return options && options.debug === true ? 'technical' : 'business';
22
+ }
23
+
24
+ async function fetchWorkHubResult(dataplaneUrl, authConfig, processExecutionId, options = {}) {
25
+ try {
26
+ const workResult = await getWorkExecutionResult(dataplaneUrl, authConfig, processExecutionId, {
27
+ requestedDepth: requestedWorkResultDepth(options),
28
+ format: 'json'
29
+ });
30
+ return { workResult, workResultError: null };
31
+ } catch (error) {
32
+ return { workResult: null, workResultError: error.message || String(error) };
33
+ }
34
+ }
35
+
36
+ function persistRoleAssistantDebugLog(roleKey, payload, options = {}) {
37
+ if (!options || options.debug !== true) {
38
+ return null;
39
+ }
40
+ const systemKey = roleAssistantSystemKey(roleKey);
41
+ const logsDir = path.join(getRoleAssistantPath(systemKey), 'logs');
42
+ fs.mkdirSync(logsDir, { recursive: true });
43
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
44
+ const filePath = path.join(logsDir, `work-result-technical-${timestamp}.json`);
45
+ const sanitized = sanitizeForLog(payload);
46
+ fs.writeFileSync(filePath, `${JSON.stringify(sanitized, null, 2)}\n`, 'utf8');
47
+ return filePath;
48
+ }
49
+
50
+ module.exports = {
51
+ requestedWorkResultDepth,
52
+ fetchWorkHubResult,
53
+ persistRoleAssistantDebugLog
54
+ };