@aifabrix/builder 2.51.0 → 2.52.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 (198) hide show
  1. package/jest.isolated-projects.js +77 -1
  2. package/jest.projects.js +5 -426
  3. package/lib/api/assistants.api.js +67 -0
  4. package/lib/api/datasources-extended.api.js +6 -6
  5. package/lib/api/evidence-authoring-helpers.js +267 -0
  6. package/lib/api/evidence-authoring.api.js +337 -0
  7. package/lib/api/evidence.api.js +57 -1
  8. package/lib/api/index.js +24 -7
  9. package/lib/api/pipeline.api.js +43 -3
  10. package/lib/api/records-bulk.api.js +28 -3
  11. package/lib/api/types/evidence.types.js +156 -8
  12. package/lib/api/work.api.js +283 -0
  13. package/lib/app/run-container-start.js +4 -0
  14. package/lib/app/run-deploy-job.js +216 -0
  15. package/lib/app/run-env-compose.js +32 -0
  16. package/lib/app/run-env-recovery.js +35 -0
  17. package/lib/app/run-helpers.js +15 -9
  18. package/lib/app/run.js +18 -2
  19. package/lib/channels/add-channel.js +54 -23
  20. package/lib/channels/approval-guides/chatgpt.js +13 -9
  21. package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
  22. package/lib/channels/channel-artifact-generator.js +14 -2
  23. package/lib/channels/channel-credential.js +3 -1
  24. package/lib/channels/channel-probe-evidence.js +79 -0
  25. package/lib/channels/channel-probe.js +265 -13
  26. package/lib/channels/channel-runtime-resolver.js +120 -29
  27. package/lib/channels/channel-status-display.js +44 -0
  28. package/lib/channels/channel-targets.js +25 -1
  29. package/lib/channels/chatgpt-runtime-contract.js +29 -0
  30. package/lib/channels/microsoft-copilot-package.js +280 -0
  31. package/lib/channels/runtime-tool-catalog.js +165 -0
  32. package/lib/cli/index.js +2 -0
  33. package/lib/cli/infra-guided-footers.js +1 -1
  34. package/lib/cli/infra-guided-public-url-warn.js +55 -0
  35. package/lib/cli/infra-guided.js +65 -56
  36. package/lib/cli/setup-app.help.js +4 -0
  37. package/lib/cli/setup-app.js +4 -0
  38. package/lib/cli/setup-app.test-commands.js +59 -28
  39. package/lib/cli/setup-external-system-help.js +8 -2
  40. package/lib/cli/setup-external-system.js +62 -12
  41. package/lib/commands/app-test.js +2 -14
  42. package/lib/commands/channel-add-setup.js +8 -2
  43. package/lib/commands/channel.js +16 -5
  44. package/lib/commands/datasource-load-export-cli.js +6 -1
  45. package/lib/commands/deploy-gate-failure-details.js +19 -17
  46. package/lib/commands/deploy-gate-orchestrator.js +3 -2
  47. package/lib/commands/dev-down.js +2 -6
  48. package/lib/commands/evidence-kind.js +65 -15
  49. package/lib/commands/governance-generate-external.js +56 -21
  50. package/lib/commands/governance-verify-external.js +2 -5
  51. package/lib/commands/lifecycle-external.js +21 -8
  52. package/lib/commands/repair-datasource-apply.js +10 -1
  53. package/lib/commands/repair-datasource-run.js +4 -2
  54. package/lib/commands/repair-datasource-test-payload.js +80 -19
  55. package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
  56. package/lib/commands/repair-internal.js +9 -1
  57. package/lib/commands/repair-openapi-sync.js +34 -17
  58. package/lib/commands/role-assistant.js +371 -0
  59. package/lib/commands/test-governance-external.js +2 -5
  60. package/lib/commands/up-miso.js +61 -24
  61. package/lib/commands/upload-data-sync-finalize.js +25 -7
  62. package/lib/commands/upload-sidecar-followups.js +144 -47
  63. package/lib/commands/upload.js +43 -6
  64. package/lib/commands/verify-governance-combined-external.js +73 -9
  65. package/lib/commands/verify-governance-db.js +80 -25
  66. package/lib/commands/verify-governance-external.js +31 -10
  67. package/lib/commands/verify-operations-external.js +2 -5
  68. package/lib/commands/verify-operations-step-plan.js +31 -0
  69. package/lib/commands/verify-trust-command-action.js +2 -5
  70. package/lib/commands/wizard-core-helpers.js +7 -3
  71. package/lib/commands/wizard-core.js +78 -9
  72. package/lib/datasource/bulk-loader-run-batches.js +4 -2
  73. package/lib/datasource/bulk-loader-service.js +5 -1
  74. package/lib/datasource/datasource-identifier.js +4 -3
  75. package/lib/datasource/datasource-validate-summary.js +2 -2
  76. package/lib/datasource/integration-context.js +3 -3
  77. package/lib/datasource/resolve-app.js +90 -8
  78. package/lib/datasource/test-e2e.js +2 -2
  79. package/lib/datasource/unified-validation-run-body.js +15 -1
  80. package/lib/datasource/unified-validation-run-resolve.js +2 -2
  81. package/lib/datasource/validate.js +13 -6
  82. package/lib/external-system/deploy-helpers.js +39 -1
  83. package/lib/external-system/deploy.js +25 -11
  84. package/lib/external-system/download-helpers.js +111 -2
  85. package/lib/external-system/download-role-assistant.js +55 -0
  86. package/lib/external-system/download.js +45 -26
  87. package/lib/external-system/test-helpers.js +1 -1
  88. package/lib/external-system/test-system-level.js +21 -3
  89. package/lib/external-system/test.js +43 -7
  90. package/lib/generator/builders.js +12 -15
  91. package/lib/generator/wizard.js +51 -4
  92. package/lib/lifecycle/access-control-abac.js +121 -0
  93. package/lib/lifecycle/lifecycle-governance-options.js +4 -1
  94. package/lib/lifecycle/product-model.js +3 -2
  95. package/lib/lifecycle/report-display.js +14 -2
  96. package/lib/programmatic/capability-registry.js +20 -1
  97. package/lib/programmatic/command-options.js +1 -0
  98. package/lib/programmatic/index.js +4 -0
  99. package/lib/programmatic/manifest-patch-section.js +48 -9
  100. package/lib/programmatic/manifest-section-registry-data.js +3 -2
  101. package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
  102. package/lib/programmatic/openapi-component-schemas.js +16 -1
  103. package/lib/programmatic/openapi-description-enricher.js +3 -1
  104. package/lib/programmatic/openapi-descriptions.js +55 -5
  105. package/lib/programmatic/openapi-evidence-schemas.js +161 -0
  106. package/lib/programmatic/openapi-manifest-schemas.js +6 -2
  107. package/lib/programmatic/openapi-operation-bindings.js +99 -0
  108. package/lib/programmatic/openapi-request-schemas.js +36 -1
  109. package/lib/programmatic/openapi-result-schemas.js +1 -1
  110. package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
  111. package/lib/programmatic/openapi-schema-descriptions-core.js +1 -1
  112. package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
  113. package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
  114. package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
  115. package/lib/programmatic/openapi-spec.js +47 -28
  116. package/lib/programmatic/orchestration-openapi-meta.js +5 -0
  117. package/lib/programmatic/request-timeout.js +3 -0
  118. package/lib/programmatic/route-handler-map.js +89 -2
  119. package/lib/programmatic/run-certify.js +15 -2
  120. package/lib/programmatic/run-deploy.js +74 -0
  121. package/lib/programmatic/run-evidence-authoring.js +80 -0
  122. package/lib/programmatic/run-protection.js +124 -0
  123. package/lib/programmatic/run-template-workspace.js +28 -1
  124. package/lib/programmatic/workspace-context.js +6 -0
  125. package/lib/protection/auth-context.js +48 -6
  126. package/lib/protection/paths.js +22 -0
  127. package/lib/protection/run-protection-create-helpers.js +22 -1
  128. package/lib/protection/run-protection-create.js +1 -1
  129. package/lib/protection/sync-after-upload.js +1 -1
  130. package/lib/role-assistant/evidence-sync.js +450 -0
  131. package/lib/role-assistant/github-workflow.js +96 -0
  132. package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
  133. package/lib/role-assistant/knowledge-sync.js +485 -0
  134. package/lib/role-assistant/settings.js +126 -0
  135. package/lib/role-assistant/test-cases-evaluate.js +170 -0
  136. package/lib/role-assistant/test-cases-honesty.js +184 -0
  137. package/lib/role-assistant/test-cases.js +364 -0
  138. package/lib/role-assistant/test-runner-workhub-answers.js +221 -0
  139. package/lib/role-assistant/test-runner-workhub.js +399 -0
  140. package/lib/role-assistant/test-runner.js +500 -0
  141. package/lib/schema/application-schema.json +24 -28
  142. package/lib/schema/deployment-rules.yaml +1 -0
  143. package/lib/schema/external-datasource.schema.json +38 -4
  144. package/lib/schema/external-system.schema.json +4 -4
  145. package/lib/schema/role-assistant-settings.schema.json +108 -0
  146. package/lib/utils/api.js +52 -27
  147. package/lib/utils/app-path-resolve.js +72 -24
  148. package/lib/utils/authentication-shape.js +3 -1
  149. package/lib/utils/certify-external-workspace.js +40 -0
  150. package/lib/utils/deployment-validation.js +28 -3
  151. package/lib/utils/docker-run-ephemeral.js +115 -0
  152. package/lib/utils/enterprise-knowledge-paths.js +29 -12
  153. package/lib/utils/env-copy-keycloak.js +196 -0
  154. package/lib/utils/env-copy.js +34 -1
  155. package/lib/utils/external-system-readiness-compact-display.js +3 -0
  156. package/lib/utils/health-check-public-warn.js +31 -13
  157. package/lib/utils/health-check.js +11 -1
  158. package/lib/utils/help-builder.js +1 -0
  159. package/lib/utils/paths.js +80 -38
  160. package/lib/utils/pipeline-upload-auth-retry.js +15 -3
  161. package/lib/utils/role-assistant-paths.js +160 -0
  162. package/lib/utils/schema-loader.js +41 -0
  163. package/lib/utils/schema-resolver.js +36 -4
  164. package/lib/utils/token-manager.js +38 -29
  165. package/lib/utils/upload-sync-options.js +7 -4
  166. package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
  167. package/lib/validation/external-manifest-validator.js +31 -8
  168. package/lib/validation/reserved-system-key-prefixes.js +56 -0
  169. package/lib/validation/validate-external-file.js +8 -1
  170. package/lib/validation/validate.js +54 -19
  171. package/package.json +2 -1
  172. package/scripts/export-builder-api-evidence.js +1 -1
  173. package/templates/applications/builder-api/application.yaml +3 -3
  174. package/templates/applications/dataplane/application.yaml +24 -17
  175. package/templates/applications/dataplane/env.template +2 -2
  176. package/templates/applications/dataplane/rbac.yaml +24 -10
  177. package/templates/applications/keycloak/application.yaml +2 -2
  178. package/templates/applications/keycloak/env.template +5 -2
  179. package/templates/applications/miso-controller/application.yaml +3 -3
  180. package/templates/applications/miso-controller/env.template +17 -2
  181. package/templates/channels/chatgpt/install.template.md.hbs +82 -26
  182. package/templates/channels/chatgpt/integration/README.md +11 -7
  183. package/templates/channels/chatgpt/integration/application.json +2 -2
  184. package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
  185. package/templates/channels/chatgpt/integration/env.template +1 -1
  186. package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
  187. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
  188. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
  189. package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
  190. package/templates/channels/microsoft-copilot/integration/README.md +9 -7
  191. package/templates/channels/microsoft-copilot/integration/application.json +2 -2
  192. package/templates/channels/microsoft-copilot/integration/env.template +1 -1
  193. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
  194. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
  195. package/templates/channels/slack/install.template.md.hbs +6 -1
  196. package/templates/python/docker-compose.hbs +10 -0
  197. package/templates/typescript/docker-compose.hbs +10 -0
  198. /package/.cursor/plans/{157.1-security-quality-violations-by-type.csv → Done/157.1-security-quality-violations-by-type.csv} +0 -0
@@ -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.5.0",
10
+ "version": "1.6.1",
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-07-21T00:00:00Z",
15
+ "updatedAt": "2026-08-03T00: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.1",
35
+ "date": "2026-08-03T00:00:00Z",
36
+ "changes": [
37
+ "registryMode external no longer restricts configuration item names to DOCKER_REGISTRY_SERVER_*; external is image pull source only and full env/portal configuration remains valid."
38
+ ],
39
+ "breaking": false
40
+ },
41
+ {
42
+ "version": "1.6.0",
43
+ "date": "2026-07-28T00:00:00Z",
44
+ "changes": [
45
+ "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."
46
+ ],
47
+ "breaking": false
48
+ },
33
49
  {
34
50
  "version": "1.5.0",
35
51
  "date": "2026-07-21T00:00:00Z",
@@ -837,6 +853,12 @@
837
853
  "type": "string",
838
854
  "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."
839
855
  },
856
+ "deployJob": {
857
+ "type": "string",
858
+ "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).",
859
+ "minLength": 1,
860
+ "maxLength": 500
861
+ },
840
862
  "scripts": {
841
863
  "type": "object",
842
864
  "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).",
@@ -960,32 +982,6 @@
960
982
  "databases"
961
983
  ]
962
984
  }
963
- },
964
- {
965
- "if": {
966
- "properties": {
967
- "registryMode": {
968
- "const": "external"
969
- }
970
- }
971
- },
972
- "then": {
973
- "properties": {
974
- "configuration": {
975
- "items": {
976
- "properties": {
977
- "name": {
978
- "enum": [
979
- "DOCKER_REGISTRY_SERVER_URL",
980
- "DOCKER_REGISTRY_SERVER_USERNAME",
981
- "DOCKER_REGISTRY_SERVER_PASSWORD"
982
- ]
983
- }
984
- }
985
- }
986
- }
987
- }
988
- }
989
985
  }
990
986
  ]
991
987
  }
@@ -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
@@ -7,12 +7,12 @@
7
7
  "key":"external-datasource-schema",
8
8
  "name":"External Data Source Configuration Schema",
9
9
  "description":"JSON schema for validating ExternalDataSource configuration files",
10
- "version":"2.9.4",
10
+ "version":"2.9.5",
11
11
  "type":"schema",
12
12
  "category":"integration",
13
13
  "author":"AI Fabrix Team",
14
14
  "createdAt":"2024-01-01T00:00:00Z",
15
- "updatedAt":"2026-07-18T00:00:00Z",
15
+ "updatedAt":"2026-07-31T00:00:00Z",
16
16
  "compatibility":{
17
17
  "minVersion":"1.0.0",
18
18
  "maxVersion":"3.0.0",
@@ -31,6 +31,14 @@
31
31
  "external-system.schema.json"
32
32
  ],
33
33
  "changelog":[
34
+ {
35
+ "version":"2.9.5",
36
+ "date":"2026-07-31T00:00:00Z",
37
+ "changes":[
38
+ "Added optional root accessControl.abac with applicability (required | notApplicable) and optional rationale for governance certification (scenario packs) — distinct from CIP filter.abac.mode."
39
+ ],
40
+ "breaking":false
41
+ },
34
42
  {
35
43
  "version":"2.9.4",
36
44
  "date":"2026-07-18T00:00:00Z",
@@ -86,7 +94,7 @@
86
94
  "version":"2.8.1",
87
95
  "date":"2026-06-15T00:00:00Z",
88
96
  "changes":[
89
- "Added composition.datasources for composed enterprise and Digital Worker datasources (438.6).",
97
+ "Added composition.datasources for composed enterprise and Role Assistant datasources (438.6).",
90
98
  "Added optional subscriptionFilter (roleKeys, includeSystemKinds, excludeSystemKinds, resourceTypes, systemKeys, datasourceKeys) on datasource manifests."
91
99
  ],
92
100
  "breaking":false
@@ -403,6 +411,32 @@
403
411
  "minimum":1,
404
412
  "description":"Optional sort order of this datasource within its external system (1 = first) for agent and integrator workflows when multiple record-storage datasources exist. Does not control CIP step execution order. Published to OpenAPI governance / optional runtime meta.business.businessOrder when epic 404.2 is enabled. foreignKeys alone do not define this order."
405
413
  },
414
+ "accessControl":{
415
+ "type":"object",
416
+ "additionalProperties":false,
417
+ "description":"Datasource-level access-control declarations for certification and product surfaces. Distinct from CIP filter.abac.mode (runtime filter step).",
418
+ "properties":{
419
+ "abac":{
420
+ "type":"object",
421
+ "additionalProperties":false,
422
+ "description":"Whether record-scoped ABAC governance scenario packs apply to this datasource.",
423
+ "required":["applicability"],
424
+ "properties":{
425
+ "applicability":{
426
+ "type":"string",
427
+ "enum":["required","notApplicable"],
428
+ "description":"required (default when accessControl.abac omitted): verify-governance / scenario packs expected. notApplicable: governance pillar is NOT_APPLICABLE for this datasource; packs and subject emails not required."
429
+ },
430
+ "rationale":{
431
+ "type":"string",
432
+ "minLength":1,
433
+ "maxLength":500,
434
+ "description":"Human-readable reason shown in Overview Governance Validation and CLI when applicability is notApplicable. Recommended when notApplicable."
435
+ }
436
+ }
437
+ }
438
+ }
439
+ },
406
440
  "composition":{
407
441
  "type":"object",
408
442
  "description":"Declares upstream datasources merged into this composed datasource. Generator owns merged metadata, exposedSchema, capabilities, and REST/MCP contract.",
@@ -423,7 +457,7 @@
423
457
  },
424
458
  "subscriptionFilter":{
425
459
  "$ref":"aifabrix://schema/external-system.schema.json#/$defs/subscriptionFilter",
426
- "description":"Optional role and systemKind filters for Digital Worker generation inputs on datasource manifests."
460
+ "description":"Optional role and systemKind filters for Role Assistant generation inputs on datasource manifests."
427
461
  },
428
462
  "agentExposure":{
429
463
  "type":"object",