@aifabrix/builder 2.51.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.
- package/jest.isolated-projects.js +77 -1
- package/jest.projects.js +5 -426
- package/lib/api/assistants.api.js +67 -0
- package/lib/api/datasources-extended.api.js +6 -6
- package/lib/api/evidence-authoring-helpers.js +267 -0
- package/lib/api/evidence-authoring.api.js +337 -0
- package/lib/api/evidence.api.js +57 -1
- package/lib/api/index.js +24 -7
- package/lib/api/pipeline.api.js +43 -3
- package/lib/api/records-bulk.api.js +28 -3
- package/lib/api/types/evidence.types.js +156 -8
- package/lib/api/work.api.js +283 -0
- package/lib/app/run-deploy-job.js +108 -0
- package/lib/app/run-helpers.js +10 -3
- package/lib/app/run.js +8 -0
- package/lib/channels/add-channel.js +54 -23
- package/lib/channels/approval-guides/chatgpt.js +8 -7
- package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
- package/lib/channels/channel-artifact-generator.js +14 -2
- package/lib/channels/channel-credential.js +3 -1
- package/lib/channels/channel-probe-evidence.js +79 -0
- package/lib/channels/channel-probe.js +265 -13
- package/lib/channels/channel-runtime-resolver.js +120 -29
- package/lib/channels/channel-status-display.js +44 -0
- package/lib/channels/channel-targets.js +7 -0
- package/lib/channels/chatgpt-runtime-contract.js +29 -0
- package/lib/channels/microsoft-copilot-package.js +280 -0
- package/lib/channels/runtime-tool-catalog.js +165 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/setup-app.js +4 -0
- package/lib/cli/setup-external-system-help.js +8 -2
- package/lib/cli/setup-external-system.js +12 -0
- package/lib/commands/app-test.js +2 -14
- package/lib/commands/datasource-load-export-cli.js +6 -1
- package/lib/commands/deploy-gate-failure-details.js +19 -17
- package/lib/commands/dev-down.js +2 -6
- package/lib/commands/evidence-kind.js +65 -15
- package/lib/commands/governance-generate-external.js +56 -21
- package/lib/commands/lifecycle-external.js +10 -4
- package/lib/commands/repair-datasource-apply.js +10 -1
- package/lib/commands/repair-datasource-run.js +4 -2
- package/lib/commands/repair-datasource-test-payload.js +80 -19
- package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
- package/lib/commands/repair-internal.js +9 -1
- package/lib/commands/repair-openapi-sync.js +34 -17
- package/lib/commands/role-assistant.js +313 -0
- package/lib/commands/upload-data-sync-finalize.js +5 -4
- package/lib/commands/upload-sidecar-followups.js +144 -47
- package/lib/commands/upload.js +43 -6
- package/lib/commands/verify-governance-combined-external.js +73 -9
- package/lib/commands/verify-governance-db.js +80 -25
- package/lib/commands/verify-governance-external.js +29 -5
- package/lib/commands/wizard-core-helpers.js +7 -3
- package/lib/commands/wizard-core.js +78 -9
- package/lib/datasource/bulk-loader-run-batches.js +4 -2
- package/lib/datasource/bulk-loader-service.js +5 -1
- package/lib/datasource/datasource-validate-summary.js +2 -2
- package/lib/datasource/validate.js +13 -6
- package/lib/external-system/deploy-helpers.js +35 -1
- package/lib/external-system/deploy.js +23 -10
- package/lib/external-system/download-helpers.js +111 -2
- package/lib/external-system/download-role-assistant.js +55 -0
- package/lib/external-system/download.js +45 -26
- package/lib/external-system/test-helpers.js +1 -1
- package/lib/external-system/test.js +39 -3
- package/lib/generator/builders.js +3 -0
- package/lib/generator/wizard.js +51 -4
- package/lib/lifecycle/access-control-abac.js +121 -0
- package/lib/lifecycle/lifecycle-governance-options.js +4 -1
- package/lib/lifecycle/product-model.js +3 -2
- package/lib/lifecycle/report-display.js +14 -2
- package/lib/programmatic/capability-registry.js +20 -1
- package/lib/programmatic/command-options.js +1 -0
- package/lib/programmatic/index.js +4 -0
- package/lib/programmatic/manifest-patch-section.js +48 -9
- package/lib/programmatic/manifest-section-registry-data.js +3 -2
- package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
- package/lib/programmatic/openapi-component-schemas.js +16 -1
- package/lib/programmatic/openapi-description-enricher.js +3 -1
- package/lib/programmatic/openapi-descriptions.js +55 -5
- package/lib/programmatic/openapi-evidence-schemas.js +161 -0
- package/lib/programmatic/openapi-manifest-schemas.js +6 -2
- package/lib/programmatic/openapi-operation-bindings.js +99 -0
- package/lib/programmatic/openapi-request-schemas.js +36 -1
- package/lib/programmatic/openapi-result-schemas.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
- package/lib/programmatic/openapi-schema-descriptions-core.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
- package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
- package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
- package/lib/programmatic/openapi-spec.js +47 -28
- package/lib/programmatic/orchestration-openapi-meta.js +5 -0
- package/lib/programmatic/request-timeout.js +3 -0
- package/lib/programmatic/route-handler-map.js +89 -2
- package/lib/programmatic/run-certify.js +15 -2
- package/lib/programmatic/run-deploy.js +74 -0
- package/lib/programmatic/run-evidence-authoring.js +80 -0
- package/lib/programmatic/run-protection.js +124 -0
- package/lib/programmatic/run-template-workspace.js +28 -1
- package/lib/programmatic/workspace-context.js +6 -0
- package/lib/protection/auth-context.js +48 -6
- package/lib/protection/paths.js +22 -0
- package/lib/protection/run-protection-create-helpers.js +22 -1
- package/lib/protection/run-protection-create.js +1 -1
- package/lib/protection/sync-after-upload.js +1 -1
- package/lib/role-assistant/evidence-sync.js +450 -0
- package/lib/role-assistant/github-workflow.js +96 -0
- package/lib/role-assistant/knowledge-sync.js +465 -0
- package/lib/role-assistant/settings.js +126 -0
- package/lib/role-assistant/test-cases-evaluate.js +170 -0
- package/lib/role-assistant/test-cases-honesty.js +184 -0
- package/lib/role-assistant/test-cases.js +364 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
- package/lib/role-assistant/test-runner-workhub.js +385 -0
- package/lib/role-assistant/test-runner.js +500 -0
- package/lib/schema/application-schema.json +16 -2
- package/lib/schema/deployment-rules.yaml +1 -0
- package/lib/schema/external-datasource.schema.json +38 -4
- package/lib/schema/external-system.schema.json +4 -4
- package/lib/schema/infra.parameter.yaml +20 -0
- package/lib/schema/role-assistant-settings.schema.json +108 -0
- package/lib/utils/api.js +52 -27
- package/lib/utils/app-path-resolve.js +72 -24
- package/lib/utils/authentication-shape.js +3 -1
- package/lib/utils/deployment-validation.js +28 -3
- package/lib/utils/docker-run-ephemeral.js +55 -0
- package/lib/utils/enterprise-knowledge-paths.js +29 -12
- package/lib/utils/env-copy-keycloak.js +196 -0
- package/lib/utils/env-copy.js +34 -1
- package/lib/utils/external-system-readiness-compact-display.js +3 -0
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/paths.js +67 -23
- package/lib/utils/pipeline-upload-auth-retry.js +15 -3
- package/lib/utils/role-assistant-paths.js +98 -0
- package/lib/utils/schema-loader.js +41 -0
- package/lib/utils/schema-resolver.js +36 -4
- package/lib/utils/token-manager.js +38 -29
- package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
- package/lib/validation/external-manifest-validator.js +31 -8
- package/lib/validation/validate-external-file.js +8 -1
- package/package.json +2 -1
- package/scripts/export-builder-api-evidence.js +1 -1
- package/templates/applications/dataplane/application.yaml +23 -15
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/dataplane/rbac.yaml +14 -0
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +3 -1
- package/templates/channels/chatgpt/install.template.md.hbs +42 -24
- package/templates/channels/chatgpt/integration/README.md +10 -7
- package/templates/channels/chatgpt/integration/application.json +2 -2
- package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
- package/templates/channels/chatgpt/integration/env.template +1 -1
- package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
- package/templates/channels/microsoft-copilot/integration/README.md +9 -7
- package/templates/channels/microsoft-copilot/integration/application.json +2 -2
- package/templates/channels/microsoft-copilot/integration/env.template +1 -1
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
- /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.
|
|
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-07-
|
|
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,14 @@
|
|
|
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
|
+
},
|
|
33
41
|
{
|
|
34
42
|
"version": "1.5.0",
|
|
35
43
|
"date": "2026-07-21T00:00:00Z",
|
|
@@ -837,6 +845,12 @@
|
|
|
837
845
|
"type": "string",
|
|
838
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."
|
|
839
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
|
+
},
|
|
840
854
|
"scripts": {
|
|
841
855
|
"type": "object",
|
|
842
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).",
|
|
@@ -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.
|
|
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-
|
|
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
|
|
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
|
|
460
|
+
"description":"Optional role and systemKind filters for Role Assistant generation inputs on datasource manifests."
|
|
427
461
|
},
|
|
428
462
|
"agentExposure":{
|
|
429
463
|
"type":"object",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"date": "2026-06-15T00:00:00Z",
|
|
73
73
|
"changes": [
|
|
74
74
|
"Added systemKind digitalWorker to x-systemKindCatalog (enterpriseContext group).",
|
|
75
|
-
"Extended subscriptionFilter with roleKeys, includeSystemKinds, and excludeSystemKinds for
|
|
75
|
+
"Extended subscriptionFilter with roleKeys, includeSystemKinds, and excludeSystemKinds for Role Assistant generation inputs."
|
|
76
76
|
],
|
|
77
77
|
"breaking": false
|
|
78
78
|
},
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
"$defs": {
|
|
185
185
|
"subscriptionFilter": {
|
|
186
186
|
"type": "object",
|
|
187
|
-
"description": "Shared filter engine shape for subscription triggers and
|
|
187
|
+
"description": "Shared filter engine shape for subscription triggers and Role Assistant generation inputs.",
|
|
188
188
|
"properties": {
|
|
189
189
|
"resourceTypes": {
|
|
190
190
|
"type": "array",
|
|
@@ -217,7 +217,7 @@
|
|
|
217
217
|
"minLength": 1
|
|
218
218
|
},
|
|
219
219
|
"minItems": 1,
|
|
220
|
-
"description": "Role keys that scope
|
|
220
|
+
"description": "Role keys that scope Role Assistant or subscription generation."
|
|
221
221
|
},
|
|
222
222
|
"includeSystemKinds": {
|
|
223
223
|
"type": "array",
|
|
@@ -978,7 +978,7 @@
|
|
|
978
978
|
},
|
|
979
979
|
{
|
|
980
980
|
"key": "digitalWorker",
|
|
981
|
-
"displayName": "
|
|
981
|
+
"displayName": "Role Assistant",
|
|
982
982
|
"group": "enterpriseContext"
|
|
983
983
|
},
|
|
984
984
|
{
|
|
@@ -340,6 +340,26 @@ parameters:
|
|
|
340
340
|
vaultSecretName: dataplane-client-secretKeyVault
|
|
341
341
|
notes: Per-app OAuth client secret for dataplane; generated on first ensure.
|
|
342
342
|
|
|
343
|
+
- key: mori-controller-client-idKeyVault
|
|
344
|
+
scope: app
|
|
345
|
+
generator:
|
|
346
|
+
type: literal
|
|
347
|
+
value: 'miso-controller-dev-mori-controller'
|
|
348
|
+
ensureOn: [upInfra, resolveApp]
|
|
349
|
+
azure:
|
|
350
|
+
notes: >-
|
|
351
|
+
Per-app OAuth client id from controller registration; literal default matches
|
|
352
|
+
{controllerId}-{environmentKey}-{applicationKey} (override per environment).
|
|
353
|
+
|
|
354
|
+
- key: mori-controller-client-secretKeyVault
|
|
355
|
+
scope: app
|
|
356
|
+
generator:
|
|
357
|
+
type: randomBytes32
|
|
358
|
+
ensureOn: [upInfra, resolveApp]
|
|
359
|
+
azure:
|
|
360
|
+
vaultSecretName: mori-controller-client-secretKeyVault
|
|
361
|
+
notes: Per-app OAuth client secret for mori-controller; generated on first ensure.
|
|
362
|
+
|
|
343
363
|
# Mori / telemetry: optional locally (LICENSE_JWT via miso-controller-jwt-licenseKeyVault=DEVELOPMENT skips Mori).
|
|
344
364
|
# MORI_AUTH_METHOD=kv://mori-controller-auth-methodKeyVault — local default apiKey.
|
|
345
365
|
- key: mori-controller-auth-methodKeyVault
|