@aifabrix/builder 2.50.0 → 2.51.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 (236) hide show
  1. package/.cursor/plans/Done/157.1-security-quality-violations-by-type.csv +374 -0
  2. package/.cursor/rules/assistant-driven-integration-build.mdc +40 -0
  3. package/integration/hubspot-test/create-hubspot.js +1 -16
  4. package/jest.isolated-projects.js +77 -1
  5. package/jest.projects.js +5 -426
  6. package/lib/api/assistants.api.js +67 -0
  7. package/lib/api/datasources-extended.api.js +6 -6
  8. package/lib/api/evidence-authoring-helpers.js +267 -0
  9. package/lib/api/evidence-authoring.api.js +337 -0
  10. package/lib/api/evidence-kinds.api.js +128 -0
  11. package/lib/api/evidence.api.js +57 -1
  12. package/lib/api/index.js +24 -7
  13. package/lib/api/pipeline.api.js +47 -3
  14. package/lib/api/records-bulk.api.js +28 -3
  15. package/lib/api/resource-types.api.js +4 -4
  16. package/lib/api/roles.api.js +121 -0
  17. package/lib/api/types/evidence-kinds.types.js +42 -0
  18. package/lib/api/types/evidence.types.js +156 -8
  19. package/lib/api/types/roles.types.js +36 -0
  20. package/lib/api/work.api.js +283 -0
  21. package/lib/app/certification-verify-rows.js +1 -1
  22. package/lib/app/run-deploy-job.js +108 -0
  23. package/lib/app/run-helpers.js +10 -3
  24. package/lib/app/run.js +8 -0
  25. package/lib/app/show.js +2 -2
  26. package/lib/certification/merge-certification-from-artifact.js +1 -1
  27. package/lib/channels/add-channel.js +54 -23
  28. package/lib/channels/approval-guides/chatgpt.js +8 -7
  29. package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
  30. package/lib/channels/channel-artifact-generator.js +16 -3
  31. package/lib/channels/channel-credential.js +3 -1
  32. package/lib/channels/channel-probe-evidence.js +79 -0
  33. package/lib/channels/channel-probe.js +265 -13
  34. package/lib/channels/channel-runtime-resolver.js +120 -29
  35. package/lib/channels/channel-status-display.js +44 -0
  36. package/lib/channels/channel-targets.js +7 -0
  37. package/lib/channels/chatgpt-runtime-contract.js +29 -0
  38. package/lib/channels/microsoft-copilot-package.js +280 -0
  39. package/lib/channels/runtime-tool-catalog.js +165 -0
  40. package/lib/cli/index.js +6 -0
  41. package/lib/cli/setup-app.js +26 -5
  42. package/lib/cli/setup-dev.js +23 -7
  43. package/lib/cli/setup-external-system-help.js +8 -2
  44. package/lib/cli/setup-external-system.js +12 -0
  45. package/lib/cli/setup-utility-help-after.js +12 -0
  46. package/lib/cli/setup-utility-validate.js +41 -2
  47. package/lib/commands/app-test.js +2 -14
  48. package/lib/commands/datasource-load-export-cli.js +6 -1
  49. package/lib/commands/deploy-gate-failure-details.js +23 -0
  50. package/lib/commands/deploy-gate-orchestrator.js +64 -18
  51. package/lib/commands/dev-cli-handlers.js +6 -3
  52. package/lib/commands/dev-down.js +2 -6
  53. package/lib/commands/dev-init.js +7 -109
  54. package/lib/commands/dev-refresh.js +197 -0
  55. package/lib/commands/evidence-kind.js +478 -0
  56. package/lib/commands/governance-command-action.js +4 -2
  57. package/lib/commands/governance-generate-external.js +56 -21
  58. package/lib/commands/lifecycle-external.js +10 -4
  59. package/lib/commands/repair-datasource-apply.js +10 -1
  60. package/lib/commands/repair-datasource-run.js +4 -2
  61. package/lib/commands/repair-datasource-test-payload.js +80 -19
  62. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
  63. package/lib/commands/repair-internal.js +9 -1
  64. package/lib/commands/repair-openapi-export.js +1 -1
  65. package/lib/commands/repair-openapi-sync.js +43 -18
  66. package/lib/commands/resource-type.js +101 -18
  67. package/lib/commands/role-assistant.js +313 -0
  68. package/lib/commands/role.js +452 -0
  69. package/lib/commands/setup.js +1 -1
  70. package/lib/commands/upload-data-sync-finalize.js +5 -4
  71. package/lib/commands/upload-sidecar-followups.js +144 -47
  72. package/lib/commands/upload.js +43 -6
  73. package/lib/commands/validate-json.js +230 -0
  74. package/lib/commands/verify-governance-combined-external.js +73 -9
  75. package/lib/commands/verify-governance-db.js +80 -25
  76. package/lib/commands/verify-governance-external.js +29 -5
  77. package/lib/commands/verify-operations-step-plan.js +78 -40
  78. package/lib/commands/wizard-core-helpers.js +7 -3
  79. package/lib/commands/wizard-core.js +78 -9
  80. package/lib/core/audit-logger.js +1 -1
  81. package/lib/core/secrets-load.js +1 -3
  82. package/lib/datasource/bulk-loader-run-batches.js +4 -2
  83. package/lib/datasource/bulk-loader-service.js +5 -1
  84. package/lib/datasource/capability/relate-operations.js +1 -1
  85. package/lib/datasource/datasource-validate-summary.js +2 -2
  86. package/lib/datasource/validate.js +13 -6
  87. package/lib/external-system/deploy-helpers.js +35 -1
  88. package/lib/external-system/deploy.js +23 -10
  89. package/lib/external-system/download-helpers.js +111 -2
  90. package/lib/external-system/download-role-assistant.js +55 -0
  91. package/lib/external-system/download.js +45 -26
  92. package/lib/external-system/test-helpers.js +1 -1
  93. package/lib/external-system/test.js +39 -3
  94. package/lib/generator/builders.js +3 -0
  95. package/lib/generator/helpers.js +13 -5
  96. package/lib/generator/split-variables.js +9 -3
  97. package/lib/generator/wizard.js +51 -4
  98. package/lib/infrastructure/helpers-docker-check.js +6 -0
  99. package/lib/infrastructure/helpers.js +1 -1
  100. package/lib/lifecycle/access-control-abac.js +121 -0
  101. package/lib/lifecycle/lifecycle-governance-options.js +4 -1
  102. package/lib/lifecycle/product-model.js +3 -2
  103. package/lib/lifecycle/report-display.js +14 -2
  104. package/lib/programmatic/auth-config.js +3 -2
  105. package/lib/programmatic/bearer-auth.js +1 -1
  106. package/lib/programmatic/capability-registry.js +20 -1
  107. package/lib/programmatic/command-options.js +1 -0
  108. package/lib/programmatic/enterprise-mcp-client.js +18 -2
  109. package/lib/programmatic/index.js +4 -0
  110. package/lib/programmatic/manifest-patch-section.js +48 -9
  111. package/lib/programmatic/manifest-section-registry-data.js +3 -2
  112. package/lib/programmatic/manifest-section-registry.js +18 -1
  113. package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
  114. package/lib/programmatic/openapi-component-schemas.js +16 -1
  115. package/lib/programmatic/openapi-description-enricher.js +3 -1
  116. package/lib/programmatic/openapi-descriptions.js +55 -5
  117. package/lib/programmatic/openapi-evidence-schemas.js +161 -0
  118. package/lib/programmatic/openapi-manifest-schemas.js +6 -2
  119. package/lib/programmatic/openapi-operation-bindings.js +99 -0
  120. package/lib/programmatic/openapi-request-schemas.js +36 -1
  121. package/lib/programmatic/openapi-result-schemas.js +29 -12
  122. package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
  123. package/lib/programmatic/openapi-schema-descriptions-core.js +6 -3
  124. package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
  125. package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
  126. package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
  127. package/lib/programmatic/openapi-schema-helpers.js +16 -1
  128. package/lib/programmatic/openapi-spec.js +48 -29
  129. package/lib/programmatic/orchestration-openapi-meta.js +5 -0
  130. package/lib/programmatic/request-timeout.js +3 -0
  131. package/lib/programmatic/route-handler-map.js +89 -2
  132. package/lib/programmatic/run-certify.js +15 -2
  133. package/lib/programmatic/run-deploy.js +74 -0
  134. package/lib/programmatic/run-evidence-authoring.js +80 -0
  135. package/lib/programmatic/run-protection.js +124 -0
  136. package/lib/programmatic/run-repository-emcp.js +11 -3
  137. package/lib/programmatic/run-template-workspace.js +42 -2
  138. package/lib/programmatic/workspace-context.js +6 -0
  139. package/lib/protection/auth-context.js +48 -6
  140. package/lib/protection/paths.js +22 -0
  141. package/lib/protection/run-protection-create-helpers.js +22 -1
  142. package/lib/protection/run-protection-create.js +1 -1
  143. package/lib/protection/sync-after-upload.js +1 -1
  144. package/lib/resolvers/evidence-kind-file.js +230 -0
  145. package/lib/resolvers/resource-type-file.js +36 -1
  146. package/lib/resolvers/role-file.js +227 -0
  147. package/lib/role-assistant/evidence-sync.js +450 -0
  148. package/lib/role-assistant/github-workflow.js +96 -0
  149. package/lib/role-assistant/knowledge-sync.js +465 -0
  150. package/lib/role-assistant/settings.js +126 -0
  151. package/lib/role-assistant/test-cases-evaluate.js +170 -0
  152. package/lib/role-assistant/test-cases-honesty.js +184 -0
  153. package/lib/role-assistant/test-cases.js +364 -0
  154. package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
  155. package/lib/role-assistant/test-runner-workhub.js +385 -0
  156. package/lib/role-assistant/test-runner.js +500 -0
  157. package/lib/schema/application-schema.json +36 -2
  158. package/lib/schema/deployment-rules.yaml +1 -0
  159. package/lib/schema/external-datasource.schema.json +121 -4
  160. package/lib/schema/external-system.schema.json +20 -6
  161. package/lib/schema/infra.parameter.yaml +45 -1
  162. package/lib/schema/role-assistant-settings.schema.json +108 -0
  163. package/lib/schema/type/credential-field-registry.json +19 -3
  164. package/lib/utils/api.js +52 -21
  165. package/lib/utils/app-path-resolve.js +72 -24
  166. package/lib/utils/app-register-config.js +4 -3
  167. package/lib/utils/authentication-shape.js +3 -1
  168. package/lib/utils/cli-sync-options.js +59 -3
  169. package/lib/utils/cli-utils.js +11 -3
  170. package/lib/utils/deployment-validation.js +28 -3
  171. package/lib/utils/dev-ca-install.js +5 -6
  172. package/lib/utils/dev-config.js +2 -2
  173. package/lib/utils/dev-list-sort.js +145 -0
  174. package/lib/utils/dev-platform-tls.js +0 -63
  175. package/lib/utils/docker-build.js +46 -27
  176. package/lib/utils/docker-daemon-tls-ca.js +1 -1
  177. package/lib/utils/docker-exec.js +91 -6
  178. package/lib/utils/docker-run-ephemeral.js +55 -0
  179. package/lib/utils/enterprise-knowledge-paths.js +29 -12
  180. package/lib/utils/env-copy-keycloak.js +196 -0
  181. package/lib/utils/env-copy.js +35 -4
  182. package/lib/utils/error-formatters/error-parser.js +0 -3
  183. package/lib/utils/external-env-template.js +1 -1
  184. package/lib/utils/external-system-readiness-compact-blocks.js +28 -1
  185. package/lib/utils/external-system-readiness-compact-display.js +3 -0
  186. package/lib/utils/health-check-request.js +2 -3
  187. package/lib/utils/help-builder.js +4 -0
  188. package/lib/utils/image-name.js +22 -4
  189. package/lib/utils/logger.js +37 -4
  190. package/lib/utils/manifest-source-emit.js +1 -1
  191. package/lib/utils/paths.js +98 -26
  192. package/lib/utils/pipeline-upload-auth-retry.js +19 -3
  193. package/lib/utils/postgres-platform-bootstrap.js +24 -17
  194. package/lib/utils/postgres-wipe.js +5 -2
  195. package/lib/utils/remote-dev-auth.js +39 -4
  196. package/lib/utils/resolve-docker-image-ref.js +36 -6
  197. package/lib/utils/role-assistant-paths.js +98 -0
  198. package/lib/utils/schema-loader.js +41 -0
  199. package/lib/utils/schema-resolver.js +36 -4
  200. package/lib/utils/token-manager.js +47 -22
  201. package/lib/utils/upload-sync-options.js +17 -3
  202. package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
  203. package/lib/validation/external-manifest-validator.js +31 -8
  204. package/lib/validation/openapi-contract-surface-validator.js +4 -0
  205. package/lib/validation/validate-external-file.js +8 -1
  206. package/lib/validation/validator.js +6 -5
  207. package/package.json +35 -12
  208. package/scripts/export-builder-api-evidence.js +3 -3
  209. package/scripts/lib/plan-actor.js +2 -2
  210. package/scripts/lib/plan-meta.js +1 -1
  211. package/scripts/lib/plan-parse.js +1 -1
  212. package/scripts/lib/plan-prevalidate-gates.js +12 -1
  213. package/scripts/lib/plan-run-report.js +13 -2
  214. package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +10 -10
  215. package/templates/applications/dataplane/application.yaml +47 -17
  216. package/templates/applications/dataplane/env.template +28 -9
  217. package/templates/applications/dataplane/rbac.yaml +78 -2
  218. package/templates/applications/miso-controller/application.yaml +10 -1
  219. package/templates/applications/miso-controller/env.template +13 -8
  220. package/templates/applications/miso-controller/rbac.yaml +72 -0
  221. package/templates/channels/chatgpt/install.template.md.hbs +42 -24
  222. package/templates/channels/chatgpt/integration/README.md +10 -7
  223. package/templates/channels/chatgpt/integration/application.json +2 -2
  224. package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
  225. package/templates/channels/chatgpt/integration/env.template +1 -1
  226. package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
  227. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
  228. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
  229. package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
  230. package/templates/channels/microsoft-copilot/integration/README.md +9 -7
  231. package/templates/channels/microsoft-copilot/integration/application.json +2 -2
  232. package/templates/channels/microsoft-copilot/integration/env.template +1 -1
  233. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
  234. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
  235. package/templates/channels/slack/integration/slack-system.json +60 -2
  236. package/templates/python/requirements.txt +1 -1
@@ -0,0 +1,500 @@
1
+ /**
2
+ * Role assistant case runtime runner using dataplane work APIs.
3
+ *
4
+ * @fileoverview Execute role-assistant test case input/answers/approvals over work.start/status/respond/continue
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const crypto = require('crypto');
12
+ const { VERDICT } = require('../lifecycle/product-model');
13
+ const {
14
+ startWorkIntent,
15
+ getWorkExecutionStatus,
16
+ respondWorkExecution,
17
+ continueWorkExecution,
18
+ getWorkExecutionResult
19
+ } = require('../api/work.api');
20
+ const { wantsWorkHubPath, runWorkHubCaseLifecycle } = require('./test-runner-workhub');
21
+ const TERMINAL_STATUSES = new Set(['completed', 'stopped']);
22
+ const WAITING_STATUS = 'waiting';
23
+
24
+ function delayMs(ms) {
25
+ const wait = Number(ms);
26
+ if (!Number.isFinite(wait) || wait <= 0) {
27
+ return Promise.resolve();
28
+ }
29
+ return new Promise((resolve) => setTimeout(resolve, wait));
30
+ }
31
+
32
+ function normalizeToken(value) {
33
+ if (typeof value !== 'string') {
34
+ return '';
35
+ }
36
+ return value
37
+ .trim()
38
+ .toLowerCase()
39
+ .replace(/[^a-z0-9]+/g, '');
40
+ }
41
+
42
+ function buildRuntimeId(prefix) {
43
+ if (typeof crypto.randomUUID === 'function') {
44
+ return `${prefix}-${crypto.randomUUID()}`;
45
+ }
46
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
47
+ }
48
+
49
+ function applyOptionalStringField(payload, value, key) {
50
+ if (typeof value === 'string' && value.trim()) {
51
+ payload[key] = value.trim();
52
+ }
53
+ }
54
+
55
+ function resolveInputObject(testCase) {
56
+ const input = testCase && typeof testCase.input === 'object' && testCase.input ? testCase.input : {};
57
+ return Array.isArray(input) ? {} : input;
58
+ }
59
+
60
+ function resolveInputMessage(testCase) {
61
+ const input = resolveInputObject(testCase);
62
+ const candidates = [input.message, input.text, input.prompt];
63
+ for (const row of candidates) {
64
+ if (typeof row === 'string' && row.trim()) {
65
+ return row.trim();
66
+ }
67
+ }
68
+ return '';
69
+ }
70
+
71
+ function isExecutableRoleAssistantCase(testCase) {
72
+ const input = resolveInputObject(testCase);
73
+ if (typeof input.content !== 'undefined') {
74
+ return true;
75
+ }
76
+ if (resolveInputMessage(testCase)) {
77
+ return true;
78
+ }
79
+ return false;
80
+ }
81
+
82
+ function buildStartPayloadSourceMetadata(roleKey, testCase, input) {
83
+ return {
84
+ roleKey,
85
+ caseId: testCase.caseId,
86
+ runner: 'aifabrix-role-assistant-test',
87
+ ...(input.sourceMetadata && typeof input.sourceMetadata === 'object' ? input.sourceMetadata : {})
88
+ };
89
+ }
90
+
91
+ function applyStartPayloadOptionalFields(payload, input) {
92
+ const mappedFields = [
93
+ ['activationId', 'activationId'],
94
+ ['workerTaskId', 'workerTaskId'],
95
+ ['principalRef', 'principalRef'],
96
+ ['businessRoleRef', 'businessRoleRef']
97
+ ];
98
+ for (const [inputKey, payloadKey] of mappedFields) {
99
+ applyOptionalStringField(payload, input[inputKey], payloadKey);
100
+ }
101
+ }
102
+
103
+ function buildStartIntentPayload(roleKey, testCase) {
104
+ const input = resolveInputObject(testCase);
105
+ const message = resolveInputMessage(testCase);
106
+ const hasContent = typeof input.content !== 'undefined';
107
+ if (!hasContent && !message) {
108
+ throw new Error(
109
+ `Role assistant test case "${testCase.caseId}" is missing input.message or input.content for runtime execution.`
110
+ );
111
+ }
112
+ const payload = {
113
+ submissionId: String(input.submissionId || buildRuntimeId(`ra-${roleKey}`)),
114
+ content: hasContent ? input.content : message,
115
+ comModelRef: String(input.comModelRef || 'aifabrix.com/intent/default'),
116
+ comModelVersion: String(input.comModelVersion || '1.0'),
117
+ comVocabulary: Array.isArray(input.comVocabulary) && input.comVocabulary.length > 0
118
+ ? input.comVocabulary.map((row) => String(row))
119
+ : ['objective', 'entity', 'action'],
120
+ sourceType: String(input.sourceType || 'api'),
121
+ sourceLanguage: String(input.sourceLanguage || 'en'),
122
+ responseLanguage: String(input.responseLanguage || input.sourceLanguage || 'en'),
123
+ sourceMetadata: buildStartPayloadSourceMetadata(roleKey, testCase, input)
124
+ };
125
+ applyStartPayloadOptionalFields(payload, input);
126
+ return payload;
127
+ }
128
+
129
+ function extractProcessExecutionId(startResponse) {
130
+ const candidates = [
131
+ startResponse?.intent?.processExecutionId,
132
+ startResponse?.processExecutionId,
133
+ startResponse?.status?.processExecutionId
134
+ ];
135
+ for (const row of candidates) {
136
+ if (typeof row === 'string' && row.trim()) {
137
+ return row.trim();
138
+ }
139
+ }
140
+ return '';
141
+ }
142
+
143
+ function isTerminalStatus(statusValue) {
144
+ return TERMINAL_STATUSES.has(normalizeToken(statusValue));
145
+ }
146
+
147
+ function getWaitDescriptor(statusRow) {
148
+ return statusRow && typeof statusRow.wait === 'object' && statusRow.wait ? statusRow.wait : {};
149
+ }
150
+
151
+ function collectWaitMatchTokens(wait) {
152
+ const out = [];
153
+ const add = (value) => {
154
+ if (typeof value === 'string' && value.trim()) {
155
+ out.push(value.trim());
156
+ }
157
+ };
158
+ add(wait.answerType);
159
+ add(wait.waitReason);
160
+ add(wait.interactionKind);
161
+ add(wait.questionKey);
162
+ add(wait.requestKey);
163
+ if (wait.objectRef && typeof wait.objectRef === 'object') {
164
+ add(wait.objectRef.key);
165
+ add(wait.objectRef.name);
166
+ add(wait.objectRef.type);
167
+ add(wait.objectRef.id);
168
+ }
169
+ if (wait.safeProjection && typeof wait.safeProjection === 'object') {
170
+ add(wait.safeProjection.capabilityKey);
171
+ add(wait.safeProjection.capability);
172
+ add(wait.safeProjection.key);
173
+ }
174
+ return out.map((row) => normalizeToken(row)).filter(Boolean);
175
+ }
176
+
177
+ function matchesWhenSpec(wait, when = {}) {
178
+ if (!when || typeof when !== 'object' || Array.isArray(when)) {
179
+ return true;
180
+ }
181
+ const tokens = collectWaitMatchTokens(wait);
182
+ const includesToken = (value) => {
183
+ const normalized = normalizeToken(value);
184
+ if (!normalized) {
185
+ return true;
186
+ }
187
+ return tokens.includes(normalized);
188
+ };
189
+ if (!includesToken(when.type)) {
190
+ return false;
191
+ }
192
+ if (!includesToken(when.key)) {
193
+ return false;
194
+ }
195
+ if (!includesToken(when.questionKey)) {
196
+ return false;
197
+ }
198
+ if (!includesToken(when.requestKey)) {
199
+ return false;
200
+ }
201
+ if (!includesToken(when.capability)) {
202
+ return false;
203
+ }
204
+ if (!includesToken(when.interactionKind)) {
205
+ return false;
206
+ }
207
+ return includesToken(when.waitReason);
208
+ }
209
+
210
+ function findMatchingEntry(entries, usedIndexes, wait) {
211
+ for (let index = 0; index < entries.length; index += 1) {
212
+ if (usedIndexes.has(index)) {
213
+ continue;
214
+ }
215
+ const candidate = entries[index];
216
+ const when = candidate && typeof candidate === 'object' ? candidate.when : null;
217
+ if (matchesWhenSpec(wait, when)) {
218
+ return { index, entry: candidate || {} };
219
+ }
220
+ }
221
+ return null;
222
+ }
223
+
224
+ function normalizeApprovalDecision(value) {
225
+ const token = normalizeToken(value);
226
+ if (token === 'approve' || token === 'approved' || token === 'accept') {
227
+ return 'approved';
228
+ }
229
+ if (token === 'reject' || token === 'rejected' || token === 'decline' || token === 'declined') {
230
+ return 'rejected';
231
+ }
232
+ return '';
233
+ }
234
+
235
+ function resolveAskRespondValue(respond) {
236
+ if (typeof respond.value !== 'undefined') {
237
+ return respond.value;
238
+ }
239
+ if (typeof respond.message !== 'undefined') {
240
+ return respond.message;
241
+ }
242
+ if (typeof respond.text !== 'undefined') {
243
+ return respond.text;
244
+ }
245
+ return respond;
246
+ }
247
+
248
+ function applyWaitVersioningFields(payload, wait) {
249
+ applyOptionalStringField(payload, wait.requestVersion, 'requestVersion');
250
+ applyOptionalStringField(payload, wait.stateVersionRef, 'stateVersionRef');
251
+ applyOptionalStringField(payload, wait.requestFingerprint, 'requestFingerprint');
252
+ }
253
+
254
+ function buildAskRespondPayload(answerEntry, wait) {
255
+ const respond = answerEntry && typeof answerEntry.respond === 'object' && answerEntry.respond
256
+ ? answerEntry.respond
257
+ : {};
258
+ const payload = {
259
+ respondIdempotencyKey: buildRuntimeId('ra-answer'),
260
+ answerType: 'ask',
261
+ value: resolveAskRespondValue(respond)
262
+ };
263
+ applyOptionalStringField(payload, respond.selectedOptionId, 'selectedOptionId');
264
+ applyOptionalStringField(payload, respond.evidenceRef, 'evidenceRef');
265
+ if (respond.declined === true) {
266
+ payload.declined = true;
267
+ }
268
+ applyWaitVersioningFields(payload, wait);
269
+ return payload;
270
+ }
271
+
272
+ function buildApprovalRespondPayload(approvalEntry, wait) {
273
+ const decision = normalizeApprovalDecision(
274
+ approvalEntry?.decision || approvalEntry?.respond?.decision || approvalEntry?.respond?.approvalDecision
275
+ );
276
+ if (!decision) {
277
+ throw new Error('Approval entry in case requires decision "approve" or "reject".');
278
+ }
279
+ const payload = {
280
+ respondIdempotencyKey: buildRuntimeId('ra-approval'),
281
+ answerType: 'approval',
282
+ approvalDecision: decision
283
+ };
284
+ applyWaitVersioningFields(payload, wait);
285
+ return payload;
286
+ }
287
+
288
+ function buildStatusSnapshot(statusRow) {
289
+ return {
290
+ processExecutionId: statusRow?.processExecutionId || null,
291
+ status: statusRow?.status || null,
292
+ message: statusRow?.message || null,
293
+ wait: statusRow?.wait || null,
294
+ correlationId: statusRow?.correlationId || null
295
+ };
296
+ }
297
+
298
+ function buildRunnerOptions(options = {}) {
299
+ const maxStatusPolls = Number(options.maxStatusPolls);
300
+ const statusPollIntervalMs = Number(options.statusPollIntervalMs);
301
+ return {
302
+ maxStatusPolls: Number.isFinite(maxStatusPolls) && maxStatusPolls > 0 ? maxStatusPolls : 20,
303
+ statusPollIntervalMs:
304
+ Number.isFinite(statusPollIntervalMs) && statusPollIntervalMs >= 0 ? statusPollIntervalMs : 750
305
+ };
306
+ }
307
+
308
+ async function fetchWorkResultSafe(dataplaneUrl, authConfig, processExecutionId, testCase) {
309
+ const input = resolveInputObject(testCase);
310
+ const requestedDepth = input.requestedDepth || 'details';
311
+ const format = input.format || 'json';
312
+ return getWorkExecutionResult(dataplaneUrl, authConfig, processExecutionId, {
313
+ requestedDepth,
314
+ format
315
+ });
316
+ }
317
+
318
+ function finalizeWaitingAdvanceStatus(respondResult, continueResult) {
319
+ const afterRespondStatus = respondResult && typeof respondResult.status === 'object'
320
+ ? respondResult.status
321
+ : null;
322
+ return continueResult && typeof continueResult === 'object'
323
+ ? continueResult
324
+ : afterRespondStatus;
325
+ }
326
+
327
+ async function advanceApprovalWaitingExecution(context, approvals, wait) {
328
+ const { dataplaneUrl, authConfig, processExecutionId, usedApprovalIndexes } = context;
329
+ const match = findMatchingEntry(approvals, usedApprovalIndexes, wait);
330
+ if (!match) {
331
+ return { status: null, matched: false, interactionType: 'approval' };
332
+ }
333
+ usedApprovalIndexes.add(match.index);
334
+ const payload = buildApprovalRespondPayload(match.entry, wait);
335
+ const respondResult = await respondWorkExecution(dataplaneUrl, authConfig, processExecutionId, payload);
336
+ const continueResult = await continueWorkExecution(dataplaneUrl, authConfig, processExecutionId, {});
337
+ return {
338
+ status: finalizeWaitingAdvanceStatus(respondResult, continueResult),
339
+ matched: true,
340
+ interactionType: 'approval',
341
+ respondPayload: payload
342
+ };
343
+ }
344
+
345
+ async function advanceAskWaitingExecution(context, answers, wait) {
346
+ const { dataplaneUrl, authConfig, processExecutionId, usedAnswerIndexes } = context;
347
+ const match = findMatchingEntry(answers, usedAnswerIndexes, wait);
348
+ if (!match) {
349
+ return { status: null, matched: false, interactionType: 'ask' };
350
+ }
351
+ usedAnswerIndexes.add(match.index);
352
+ const payload = buildAskRespondPayload(match.entry, wait);
353
+ const respondResult = await respondWorkExecution(dataplaneUrl, authConfig, processExecutionId, payload);
354
+ const continueResult = await continueWorkExecution(dataplaneUrl, authConfig, processExecutionId, {});
355
+ return {
356
+ status: finalizeWaitingAdvanceStatus(respondResult, continueResult),
357
+ matched: true,
358
+ interactionType: 'ask',
359
+ respondPayload: payload
360
+ };
361
+ }
362
+
363
+ async function advanceWaitingExecution(context) {
364
+ const { wait, testCase } = context;
365
+ const waitReason = normalizeToken(wait.waitReason || wait.answerType || wait.interactionKind);
366
+ const approvals = Array.isArray(testCase.approvals) ? testCase.approvals : [];
367
+ const answers = Array.isArray(testCase.answers) ? testCase.answers : [];
368
+ if (waitReason === 'approval') {
369
+ return advanceApprovalWaitingExecution(context, approvals, wait);
370
+ }
371
+ return advanceAskWaitingExecution(context, answers, wait);
372
+ }
373
+
374
+ async function pollCaseExecutionStatus(dataplaneUrl, authConfig, processExecutionId, testCase, options) {
375
+ const usedAnswerIndexes = new Set();
376
+ const usedApprovalIndexes = new Set();
377
+ const timeline = [];
378
+ const interactions = [];
379
+ let statusRow = await getWorkExecutionStatus(dataplaneUrl, authConfig, processExecutionId);
380
+ for (let pollIndex = 0; pollIndex < options.maxStatusPolls; pollIndex += 1) {
381
+ timeline.push(buildStatusSnapshot(statusRow));
382
+ if (isTerminalStatus(statusRow?.status)) {
383
+ break;
384
+ }
385
+ if (normalizeToken(statusRow?.status) === WAITING_STATUS) {
386
+ const wait = getWaitDescriptor(statusRow);
387
+ const advanced = await advanceWaitingExecution({
388
+ dataplaneUrl,
389
+ authConfig,
390
+ processExecutionId,
391
+ wait,
392
+ testCase,
393
+ usedAnswerIndexes,
394
+ usedApprovalIndexes
395
+ });
396
+ interactions.push({
397
+ pollIndex,
398
+ type: advanced.interactionType,
399
+ waitReason: wait.waitReason || wait.answerType || wait.interactionKind || null,
400
+ matched: advanced.matched === true,
401
+ questionKey: wait.questionKey || wait.requestKey || null
402
+ });
403
+ if (!advanced.matched || !advanced.status) {
404
+ break;
405
+ }
406
+ statusRow = advanced.status;
407
+ continue;
408
+ }
409
+ await delayMs(options.statusPollIntervalMs);
410
+ statusRow = await getWorkExecutionStatus(dataplaneUrl, authConfig, processExecutionId);
411
+ }
412
+ return { finalStatus: statusRow, timeline, interactions };
413
+ }
414
+
415
+ async function safeLoadWorkResult(dataplaneUrl, authConfig, processExecutionId, testCase) {
416
+ try {
417
+ const workResult = await fetchWorkResultSafe(dataplaneUrl, authConfig, processExecutionId, testCase);
418
+ return { workResult, workResultError: null };
419
+ } catch (error) {
420
+ return { workResult: null, workResultError: error.message || String(error) };
421
+ }
422
+ }
423
+
424
+ async function runRoleAssistantCaseLifecycle(roleKey, testCase, runtimeContext, options = {}) {
425
+ const dataplaneUrl = runtimeContext?.dataplaneUrl;
426
+ const authConfig = runtimeContext?.authConfig;
427
+ if (!dataplaneUrl || !authConfig) {
428
+ throw new Error('Role assistant case runtime requires dataplane auth context.');
429
+ }
430
+ const runnerOptions = buildRunnerOptions(options);
431
+ if (wantsWorkHubPath(testCase)) {
432
+ return runWorkHubCaseLifecycle(roleKey, testCase, runtimeContext, runnerOptions);
433
+ }
434
+ const startPayload = buildStartIntentPayload(roleKey, testCase);
435
+ const startResponse = await startWorkIntent(dataplaneUrl, authConfig, startPayload);
436
+ const processExecutionId = extractProcessExecutionId(startResponse);
437
+ if (!processExecutionId) {
438
+ throw new Error(
439
+ 'work.start missing processExecutionId; use input.executionPath: workHub for free-text RA cases.'
440
+ );
441
+ }
442
+ const pollState = await pollCaseExecutionStatus(
443
+ dataplaneUrl,
444
+ authConfig,
445
+ processExecutionId,
446
+ testCase,
447
+ runnerOptions
448
+ );
449
+ const { workResult, workResultError } = await safeLoadWorkResult(
450
+ dataplaneUrl,
451
+ authConfig,
452
+ processExecutionId,
453
+ testCase
454
+ );
455
+ return {
456
+ roleKey,
457
+ caseId: testCase.caseId,
458
+ processExecutionId,
459
+ startResponse,
460
+ finalStatus: pollState.finalStatus,
461
+ timeline: pollState.timeline,
462
+ interactions: pollState.interactions,
463
+ workResult,
464
+ workResultError
465
+ };
466
+ }
467
+
468
+ function mergeCaseExecutionIntoLifecycleResult(lifecycleResult, executionResult) {
469
+ const base = lifecycleResult && typeof lifecycleResult === 'object' ? lifecycleResult : {};
470
+ return {
471
+ ...base,
472
+ status: executionResult?.finalStatus?.status || base.status,
473
+ wait: executionResult?.finalStatus?.wait || base.wait,
474
+ processExecutionId: executionResult?.processExecutionId || base.processExecutionId,
475
+ workResult: executionResult?.workResult || null,
476
+ task: executionResult?.workResult?.task || null,
477
+ steps: Array.isArray(executionResult?.workResult?.steps) ? executionResult.workResult.steps : [],
478
+ workTimeline: executionResult?.timeline || [],
479
+ workInteractions: executionResult?.interactions || []
480
+ };
481
+ }
482
+
483
+ function lifecycleVerdictFromCaseExecution(executionResult, fallbackVerdict) {
484
+ const statusToken = normalizeToken(executionResult?.finalStatus?.status);
485
+ if (statusToken === 'completed') {
486
+ return VERDICT.VERIFIED;
487
+ }
488
+ if (statusToken === 'stopped' || statusToken === 'waiting' || statusToken === 'running') {
489
+ return VERDICT.NOT_VERIFIED;
490
+ }
491
+ return fallbackVerdict || VERDICT.FAILED;
492
+ }
493
+
494
+ module.exports = {
495
+ isExecutableRoleAssistantCase,
496
+ buildStartIntentPayload,
497
+ runRoleAssistantCaseLifecycle,
498
+ mergeCaseExecutionIntoLifecycleResult,
499
+ lifecycleVerdictFromCaseExecution
500
+ };
@@ -7,12 +7,12 @@
7
7
  "key": "application-schema",
8
8
  "name": "Application Configuration Schema",
9
9
  "description": "JSON schema for validating AI Fabrix application configuration files",
10
- "version": "1.4.0",
10
+ "version": "1.6.0",
11
11
  "type": "schema",
12
12
  "category": "infrastructure",
13
13
  "author": "AI Fabrix Team",
14
14
  "createdAt": "2024-01-01T00:00:00Z",
15
- "updatedAt": "2026-04-06T00:00:00Z",
15
+ "updatedAt": "2026-07-28T00:00:00Z",
16
16
  "compatibility": {
17
17
  "minVersion": "1.0.0",
18
18
  "maxVersion": "2.0.0",
@@ -30,6 +30,22 @@
30
30
  "external-datasource.schema.json"
31
31
  ],
32
32
  "changelog": [
33
+ {
34
+ "version": "1.6.0",
35
+ "date": "2026-07-28T00:00:00Z",
36
+ "changes": [
37
+ "Added optional build.deployJob (string): one-shot command run once before any long-running role start (api/worker/beat). Language-agnostic (python, node, pnpm, etc.). Not an array; no phase/once/id fields."
38
+ ],
39
+ "breaking": false
40
+ },
41
+ {
42
+ "version": "1.5.0",
43
+ "date": "2026-07-21T00:00:00Z",
44
+ "changes": [
45
+ "Added optional configuration[].category (and conditionalConfiguration item category) for portal UI grouping labels in Miso Configuration tab."
46
+ ],
47
+ "breaking": false
48
+ },
33
49
  {
34
50
  "version": "1.4.0",
35
51
  "date": "2026-04-06T00:00:00Z",
@@ -203,6 +219,12 @@
203
219
  "type": "boolean",
204
220
  "description": "Whether this configuration is required"
205
221
  },
222
+ "category": {
223
+ "type": "string",
224
+ "description": "Optional UI grouping label for portal/deployment wizard Configuration tab",
225
+ "minLength": 1,
226
+ "maxLength": 100
227
+ },
206
228
  "portalInput": {
207
229
  "type": "object",
208
230
  "description": "Portal input configuration for user-provided values",
@@ -315,6 +337,12 @@
315
337
  "type": "boolean",
316
338
  "description": "Whether this configuration is required"
317
339
  },
340
+ "category": {
341
+ "type": "string",
342
+ "description": "Optional UI grouping label for portal/deployment wizard Configuration tab",
343
+ "minLength": 1,
344
+ "maxLength": 100
345
+ },
318
346
  "portalInput": {
319
347
  "type": "object",
320
348
  "description": "Portal input configuration for user-provided values",
@@ -817,6 +845,12 @@
817
845
  "type": "string",
818
846
  "description": "Compose/Azure startup command for the beat sidecar (<appKey>-beat) when build.worker is true. Omit or set to empty string to skip the beat role."
819
847
  },
848
+ "deployJob": {
849
+ "type": "string",
850
+ "description": "Optional one-shot command run once before any long-running role (imageRun / imageRunWorker / imageRunBeat). Local aifabrix run and remote Deployment execute it once per start/deploy (app-scoped, never once-per-sidecar). Language-agnostic command string (e.g. python -m app.db.migrate upgrade, node dist/migrate.js).",
851
+ "minLength": 1,
852
+ "maxLength": 500
853
+ },
820
854
  "scripts": {
821
855
  "type": "object",
822
856
  "description": "Override CLI script commands (aifabrix test, install, lint, test-e2e, test-integration). When a key is missing, language default is used (TypeScript: pnpm test/install/lint/test:e2e/test:integration, Python: make test/install/lint/test:e2e/test-integration).",
@@ -124,6 +124,7 @@ externalDataSource:
124
124
  - capabilities
125
125
  - authority
126
126
  - foreignKeys
127
+ - dimensions
127
128
  - execution
128
129
  - config
129
130
  - openapi