@aifabrix/builder 2.59.0 → 2.61.2
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/README.md +14 -11
- package/docs/README.md +80 -0
- package/docs/builder-help/evidence-patterns.json +155 -0
- package/docs/builder-help/golden-examples/crm-company.json +30 -0
- package/docs/builder-help/golden-examples/crm-deal.json +29 -0
- package/docs/builder-help/golden-examples/document-storage-keyed-get.json +85 -0
- package/docs/builder-help/golden-examples/document-storage.json +30 -0
- package/docs/builder-help/golden-examples/meeting-transcript.json +29 -0
- package/docs/builder-help/golden-examples/repository-template.json +29 -0
- package/docs/builder-help/golden-examples/service-ticket.json +29 -0
- package/docs/builder-help/platform-roles.json +98 -0
- package/docs/builder-help/resource-type-catalog.json +402 -0
- package/lib/agent-kit/git-identity.js +180 -0
- package/lib/agent-kit/setup.js +3 -0
- package/lib/agent-kit/start.js +45 -1
- package/lib/api/configuration.api.js +131 -0
- package/lib/api/role-assistant-test-job.api.js +60 -0
- package/lib/api/system-secrets.api.js +72 -0
- package/lib/api/work-search.api.js +13 -6
- package/lib/app/deploy.js +8 -1
- package/lib/app/run-docker-fallback.js +6 -1
- package/lib/app/run-helpers.js +2 -1
- package/lib/app/run-parameter-sync.js +142 -0
- package/lib/app/show-display.js +1 -0
- package/lib/app/show-online.js +15 -0
- package/lib/build/docker-build-args.js +5 -2
- package/lib/build/index.js +3 -2
- package/lib/build/standard-docker-build.js +6 -3
- package/lib/cli/setup-app.js +19 -1
- package/lib/cli/setup-environment.js +156 -0
- package/lib/cli/setup-utility.js +24 -1
- package/lib/commands/datasource-capability-upsert-cli.js +112 -0
- package/lib/commands/datasource-capability.js +4 -2
- package/lib/commands/env-secret-context.js +113 -0
- package/lib/commands/env-secret-list.js +200 -0
- package/lib/commands/env-secret-push-confirm.js +54 -0
- package/lib/commands/env-secret-push-run.js +227 -0
- package/lib/commands/env-secret-push.js +168 -0
- package/lib/commands/repair-datasource-apply.js +2 -0
- package/lib/commands/repair-datasource-keyed-document.js +122 -0
- package/lib/commands/repair-datasource-run.js +1 -0
- package/lib/commands/role-assistant.js +7 -0
- package/lib/commands/setup-modes.js +1 -8
- package/lib/commands/setup-prompts.js +2 -180
- package/lib/commands/verify-operations-skip-e2e.js +25 -1
- package/lib/commands/verify-operations-steps.js +13 -1
- package/lib/commands/wizard-config-normalizer.js +7 -4
- package/lib/commands/wizard-core.js +5 -157
- package/lib/commands/wizard-file-saving.js +163 -0
- package/lib/core/env-platform-expand.js +97 -0
- package/lib/core/secrets-env-content.js +42 -4
- package/lib/core/secrets-env-write.js +10 -3
- package/lib/core/secrets-load.js +4 -2
- package/lib/datasource/binary-documents-validator.js +190 -0
- package/lib/datasource/capability/run-capability-upsert.js +202 -0
- package/lib/datasource/capability/upsert-ingredients.js +291 -0
- package/lib/datasource/capability/upsert-operations.js +138 -0
- package/lib/datasource/capability/upsert-test-scaffold.js +189 -0
- package/lib/datasource/validate.js +12 -5
- package/lib/deployment/installation/azure-infra-stage.js +3 -1
- package/lib/deployment/installation/infra-catalog.js +2 -5
- package/lib/generator/builders.js +17 -0
- package/lib/generator/helpers.js +23 -2
- package/lib/generator/index.js +13 -6
- package/lib/lifecycle/product-model.js +4 -3
- package/lib/lifecycle/report-display.js +3 -2
- package/lib/parameters/infra-parameter-catalog.js +1 -1
- package/lib/programmatic/builder-help-enterprise-sync-fabrix.js +1 -1
- package/lib/programmatic/builder-help-governance.js +1 -1
- package/lib/programmatic/builder-help.js +1 -1
- package/lib/role-assistant/test-cases-search.js +3 -0
- package/lib/role-assistant/test-job-runner.js +125 -0
- package/lib/role-assistant/test-runner-search.js +44 -2
- package/lib/role-assistant/test-runner-workhub-answers.js +4 -1
- package/lib/role-assistant/test-runner-workhub-missing-fields.js +42 -0
- package/lib/role-assistant/test-runner-workhub-wait-stop.js +94 -0
- package/lib/role-assistant/test-runner-workhub.js +28 -19
- package/lib/schema/application-schema.json +205 -2
- package/lib/schema/external-datasource.schema.json +23 -3
- package/lib/schema/infra-parameter.schema.json +139 -33
- package/lib/schema/infra.parameter.yaml +416 -66
- package/lib/schema/infrastructure-schema.json +10 -35
- package/lib/utils/compose-generate-docker-compose.js +16 -9
- package/lib/utils/datasource-binary-evidence.js +92 -0
- package/lib/utils/datasource-test-run-capability-scope.js +44 -1
- package/lib/utils/datasource-test-run-debug-display.js +2 -0
- package/lib/utils/datasource-test-run-display.js +8 -2
- package/lib/utils/datasource-test-run-issue-guidance.js +176 -0
- package/lib/utils/datasource-test-run-tty-log.js +2 -0
- package/lib/utils/docker-build.js +29 -8
- package/lib/utils/docker-manifest-public-port.js +36 -0
- package/lib/utils/env-copy.js +11 -10
- package/lib/utils/external-system-system-test-tty.js +3 -2
- package/lib/utils/image-tags.js +2 -2
- package/lib/utils/platform-kv-ref.js +1 -1
- package/lib/utils/platform-resolution.js +226 -0
- package/lib/utils/prepare-local-data-mount.js +58 -0
- package/lib/utils/resolve-docker-image-ref.js +8 -3
- package/lib/utils/secrets-helpers.js +0 -1
- package/lib/utils/system-secret-mapping.js +125 -0
- package/lib/utils/test-log-writer.js +2 -1
- package/lib/validation/external-manifest-validator.js +5 -0
- package/lib/validation/openapi-contract-surface-validator.js +3 -1
- package/lib/validation/validate-external-file.js +5 -1
- package/package.json +5 -4
- package/templates/README.md +2 -1
- package/templates/agent-kit/agent-kit.yaml +4 -1
- package/templates/agent-kit/instructions/AGENTKIT.md +1 -1
- package/templates/agent-kit/instructions/root.AGENTS.md +2 -0
- package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +35 -2
- package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +15 -0
- package/templates/agent-kit/skills/aifabrix-connected-system/scripts/delivery-verdict.js +176 -0
- package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +6 -2
- package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +28 -2
- package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +22 -0
- package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +28 -1
- package/templates/agent-kit/skills/aifabrix-role-assistant/references/testing-playbook.md +117 -0
- package/templates/agent-kit/skills/shared/feedback.md +31 -0
- package/templates/agent-kit/skills/shared/hosts.md +13 -3
- package/templates/agent-kit/skills/shared/interaction.md +83 -0
- package/templates/agent-kit/skills/shared/status.md +76 -0
- package/templates/agent-kit/workspace/BUILDER_IMPROVEMENT_FINDINGS.md +27 -0
- package/templates/applications/builder-api/application.yaml +1 -1
- package/templates/applications/builder-api/env.template +5 -1
- package/templates/applications/dataplane/application.yaml +30 -2
- package/templates/applications/dataplane/env.template +36 -5
- package/templates/applications/keycloak/application.yaml +6 -1
- package/templates/applications/miso-controller/application.yaml +96 -1
- package/templates/applications/miso-controller/env.template +63 -37
- package/templates/applications/miso-controller/rbac.yaml +17 -0
- package/templates/external-system/external-datasource.yaml.hbs +7 -1
- package/templates/marketplace/main.json +73 -225
- package/templates/python/Dockerfile.hbs +2 -0
- package/templates/python/docker-compose.hbs +1 -1
- package/templates/typescript/Dockerfile.hbs +2 -0
- package/templates/typescript/docker-compose.hbs +12 -0
- package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +0 -34
- /package/{lib/programmatic/help-content → docs/builder-help/content}/channel-onboarding.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/cip-overview.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/connected-system-ui.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/dimensions-guide.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/enterprise-sync-fabrix.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/overview.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/subscription-guide.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/workflow.md +0 -0
|
@@ -17,7 +17,7 @@ const {
|
|
|
17
17
|
wantsSkillsOnlyPath
|
|
18
18
|
} = require('./test-runner-workhub-helpers');
|
|
19
19
|
|
|
20
|
-
function
|
|
20
|
+
function directOperationKeys(spec) {
|
|
21
21
|
const raw = Array.isArray(spec && spec.operations) ? spec.operations : [];
|
|
22
22
|
const keys = [];
|
|
23
23
|
for (const item of raw) {
|
|
@@ -33,6 +33,42 @@ function operationKeys(spec) {
|
|
|
33
33
|
return keys;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function operationClassKeys(spec) {
|
|
37
|
+
const classes = spec && typeof spec.operationClasses === 'object'
|
|
38
|
+
? spec.operationClasses
|
|
39
|
+
: {};
|
|
40
|
+
const keys = [];
|
|
41
|
+
for (const rows of Object.values(classes)) {
|
|
42
|
+
if (!Array.isArray(rows)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
for (const row of rows) {
|
|
46
|
+
const key = String(row || '').trim();
|
|
47
|
+
if (key && !keys.includes(key)) {
|
|
48
|
+
keys.push(key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return keys;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function operationKeys(spec) {
|
|
56
|
+
return [...new Set([...directOperationKeys(spec), ...operationClassKeys(spec)])];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function skillsRequestOptions(testCase) {
|
|
60
|
+
if (!wantsSkillsOnlyPath(testCase)) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const input = resolveInput(testCase);
|
|
64
|
+
const resourceType = String(input.resourceType || '').trim();
|
|
65
|
+
const operation = String(input.operation || '').trim();
|
|
66
|
+
if (operation) {
|
|
67
|
+
return { detail: 'operation', resourceType, operation };
|
|
68
|
+
}
|
|
69
|
+
return resourceType ? { detail: 'resource', resourceType } : undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
36
72
|
function pickResourceType(skillsPayload, operation) {
|
|
37
73
|
const rts = resourceTypesMap(skillsPayload);
|
|
38
74
|
const keys = Object.keys(rts);
|
|
@@ -139,7 +175,12 @@ async function runSearchCaseLifecycle(roleKey, testCase, runtimeContext) {
|
|
|
139
175
|
throw new Error('Role assistant search case requires dataplane auth context.');
|
|
140
176
|
}
|
|
141
177
|
const roleRef = resolveRoleRef(roleKey, testCase);
|
|
142
|
-
const skillsRes = await getWorkSkills(
|
|
178
|
+
const skillsRes = await getWorkSkills(
|
|
179
|
+
dataplaneUrl,
|
|
180
|
+
authConfig,
|
|
181
|
+
roleRef,
|
|
182
|
+
skillsRequestOptions(testCase)
|
|
183
|
+
);
|
|
143
184
|
if (wantsSkillsOnlyPath(testCase)) {
|
|
144
185
|
return searchLifecycleResult(roleKey, testCase, roleRef, skillsRes, null, null);
|
|
145
186
|
}
|
|
@@ -157,6 +198,7 @@ module.exports = {
|
|
|
157
198
|
wantsSearchPath,
|
|
158
199
|
wantsSkillsOnlyPath,
|
|
159
200
|
operationKeys,
|
|
201
|
+
skillsRequestOptions,
|
|
160
202
|
pickResourceType,
|
|
161
203
|
resolveRoleRef,
|
|
162
204
|
buildSearchArguments,
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
+
const { coversMissingFields } = require('./test-runner-workhub-missing-fields');
|
|
9
|
+
|
|
8
10
|
const {
|
|
9
11
|
resolveTargetSystemKey,
|
|
10
12
|
shouldStampSuiteBid,
|
|
@@ -409,7 +411,8 @@ function resolveRespondValue(testCase, wait, statusRow, objective) {
|
|
|
409
411
|
if (fromCase !== undefined) {
|
|
410
412
|
return fromCase;
|
|
411
413
|
}
|
|
412
|
-
|
|
414
|
+
const synthesized = synthesizeWaitAnswer(wait, statusRow, objective, testCase);
|
|
415
|
+
return coversMissingFields(wait, synthesized) ? synthesized : undefined;
|
|
413
416
|
}
|
|
414
417
|
|
|
415
418
|
function waitRespondFingerprint(wait) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Missing-field coverage for synthesized Work Hub answers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
function asPlainObject(value) {
|
|
8
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Fields the runtime named as missing (missingRequestBody waits). A synthesized
|
|
16
|
+
* answer that lacks any of them only makes the runtime ask the same question
|
|
17
|
+
* again, so it must not be sent.
|
|
18
|
+
* @param {Object} wait
|
|
19
|
+
* @returns {string[]}
|
|
20
|
+
*/
|
|
21
|
+
function waitMissingFields(wait) {
|
|
22
|
+
const raw = wait?.objectRef && typeof wait.objectRef === 'object' ? wait.objectRef.missingFields : null;
|
|
23
|
+
const list = Array.isArray(raw) ? raw : String(raw || '').split(',');
|
|
24
|
+
return list.map((row) => String(row || '').trim()).filter(Boolean);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function coversMissingFields(wait, value) {
|
|
28
|
+
if (value === undefined) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const missing = waitMissingFields(wait);
|
|
32
|
+
if (missing.length === 0) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
const obj = asPlainObject(value);
|
|
36
|
+
if (!obj) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return missing.every((field) => obj[field] !== undefined && obj[field] !== null && obj[field] !== '');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { coversMissingFields, waitMissingFields };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Work Hub poll stop rules for waits the runner cannot resolve.
|
|
3
|
+
*
|
|
4
|
+
* A wait with no matching case answer, a question the runtime re-issues
|
|
5
|
+
* unchanged after our answer, or a failed respond call never resolves by
|
|
6
|
+
* polling. Stopping at once keeps a stalled case at seconds, not the 180 s
|
|
7
|
+
* poll timeout (dev01 sales ladder, 2026-09-26: six cases, 18 min waiting).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const UNANSWERED_WAIT_TYPES = new Set(['respond-skipped', 'respond-repeated', 'respond-error']);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The runtime asked the same question again after our answer: the answer did
|
|
16
|
+
* not resolve it, and repeating it cannot. Record once so the poll can stop.
|
|
17
|
+
*/
|
|
18
|
+
function recordRepeatedResponse(interactions, peId, fingerprint, wait) {
|
|
19
|
+
const already = interactions.some(
|
|
20
|
+
(row) => row && row.type === 'respond-repeated' && row.fingerprint === fingerprint
|
|
21
|
+
);
|
|
22
|
+
if (already) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
interactions.push({
|
|
26
|
+
type: 'respond-repeated',
|
|
27
|
+
processExecutionId: peId,
|
|
28
|
+
fingerprint,
|
|
29
|
+
questionKey: wait.questionKey || wait.requestKey || null,
|
|
30
|
+
objectRefKind: wait.objectRef?.kind || null,
|
|
31
|
+
reason: 'same-question-after-answer'
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function recordRespondError(interactions, peId, error) {
|
|
36
|
+
interactions.push({
|
|
37
|
+
type: 'respond-error',
|
|
38
|
+
processExecutionId: peId,
|
|
39
|
+
error: (error && error.message) || String(error)
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A wait we could not answer (no matching case answer, the runtime re-asked
|
|
45
|
+
* after our answer, or respond failed) will not change by polling: stop now
|
|
46
|
+
* instead of waiting for the poll timeout.
|
|
47
|
+
* @param {Array<Object>} interactions
|
|
48
|
+
* @returns {string|null} stop reason or null to keep polling
|
|
49
|
+
*/
|
|
50
|
+
function unansweredWaitStopReason(interactions) {
|
|
51
|
+
const last = interactions.length > 0 ? interactions[interactions.length - 1] : null;
|
|
52
|
+
if (!last || !UNANSWERED_WAIT_TYPES.has(last.type)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return last.type === 'respond-error' ? 'respondFailed' : 'unansweredQuestion';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function recordSkippedResponse(interactions, peId, fingerprint, wait) {
|
|
59
|
+
interactions.push({
|
|
60
|
+
type: 'respond-skipped',
|
|
61
|
+
processExecutionId: peId,
|
|
62
|
+
fingerprint,
|
|
63
|
+
questionKey: wait.questionKey || wait.requestKey || null,
|
|
64
|
+
objectRefKind: wait.objectRef?.kind || null,
|
|
65
|
+
reason: 'no-matching-answer-or-synthesis'
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const ALREADY_TERMINAL_RE = /already terminal/i;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* work.respond resumes the Decision Engine inline; by the time work.continue
|
|
73
|
+
* lands the execution may already be terminal. That is a finished run, not a
|
|
74
|
+
* failed answer.
|
|
75
|
+
*/
|
|
76
|
+
async function continueAfterRespond(dataplaneUrl, authConfig, peId) {
|
|
77
|
+
const { continueWorkExecution } = require('../api/work.api');
|
|
78
|
+
try {
|
|
79
|
+
await continueWorkExecution(dataplaneUrl, authConfig, peId, {});
|
|
80
|
+
} catch (err) {
|
|
81
|
+
if (!ALREADY_TERMINAL_RE.test((err && err.message) || String(err))) {
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
continueAfterRespond,
|
|
89
|
+
UNANSWERED_WAIT_TYPES,
|
|
90
|
+
recordSkippedResponse,
|
|
91
|
+
recordRepeatedResponse,
|
|
92
|
+
recordRespondError,
|
|
93
|
+
unansweredWaitStopReason
|
|
94
|
+
};
|
|
@@ -13,8 +13,7 @@ const {
|
|
|
13
13
|
getWorkTask,
|
|
14
14
|
approveWorkTask,
|
|
15
15
|
getWorkExecutionStatus,
|
|
16
|
-
respondWorkExecution
|
|
17
|
-
continueWorkExecution
|
|
16
|
+
respondWorkExecution
|
|
18
17
|
} = require('../api/work.api');
|
|
19
18
|
const {
|
|
20
19
|
synthesizeWaitAnswer,
|
|
@@ -29,6 +28,13 @@ const {
|
|
|
29
28
|
persistCaseOpenApiViaBuilderApi
|
|
30
29
|
} = require('./test-runner-bid-apply');
|
|
31
30
|
const { resolveTargetSystemKey } = require('./test-runner-bid-document');
|
|
31
|
+
const {
|
|
32
|
+
recordSkippedResponse,
|
|
33
|
+
recordRepeatedResponse,
|
|
34
|
+
recordRespondError,
|
|
35
|
+
unansweredWaitStopReason,
|
|
36
|
+
continueAfterRespond
|
|
37
|
+
} = require('./test-runner-workhub-wait-stop');
|
|
32
38
|
const {
|
|
33
39
|
TERMINAL_TASK,
|
|
34
40
|
delayMs,
|
|
@@ -178,17 +184,6 @@ async function maybeApproveTask(dataplaneUrl, authConfig, taskId, ctx) {
|
|
|
178
184
|
await _attemptApproveTask(dataplaneUrl, authConfig, taskId, ctx);
|
|
179
185
|
}
|
|
180
186
|
|
|
181
|
-
function recordSkippedResponse(interactions, peId, fingerprint, wait) {
|
|
182
|
-
interactions.push({
|
|
183
|
-
type: 'respond-skipped',
|
|
184
|
-
processExecutionId: peId,
|
|
185
|
-
fingerprint,
|
|
186
|
-
questionKey: wait.questionKey || wait.requestKey || null,
|
|
187
|
-
objectRefKind: wait.objectRef?.kind || null,
|
|
188
|
-
reason: 'no-matching-answer-or-synthesis'
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
187
|
function recordBidEmit(testCase, interactions, peId) {
|
|
193
188
|
const { loadCaseBidDocument } = require('./test-runner-workhub-answers');
|
|
194
189
|
const emittedBid = loadCaseBidDocument(testCase);
|
|
@@ -237,6 +232,7 @@ async function respondToWaitingExecution(
|
|
|
237
232
|
const wait = statusRow.wait && typeof statusRow.wait === 'object' ? statusRow.wait : {};
|
|
238
233
|
const fingerprint = waitRespondFingerprint(wait);
|
|
239
234
|
if (alreadyResponded(interactions, fingerprint)) {
|
|
235
|
+
recordRepeatedResponse(interactions, peId, fingerprint, wait);
|
|
240
236
|
return;
|
|
241
237
|
}
|
|
242
238
|
const payload = buildRespondPayload(
|
|
@@ -260,7 +256,7 @@ async function respondToWaitingExecution(
|
|
|
260
256
|
}
|
|
261
257
|
}
|
|
262
258
|
await respondWorkExecution(dataplaneUrl, authConfig, peId, payload);
|
|
263
|
-
await
|
|
259
|
+
await continueAfterRespond(dataplaneUrl, authConfig, peId);
|
|
264
260
|
interactions.push({ type: 'respond', processExecutionId: peId, fingerprint });
|
|
265
261
|
if (shouldApplyBidAfterWait(testCase, wait)) {
|
|
266
262
|
recordBidEmit(testCase, interactions, peId);
|
|
@@ -291,8 +287,9 @@ async function maybeRespondWaiting(dataplaneUrl, authConfig, taskRow, testCase,
|
|
|
291
287
|
interactions,
|
|
292
288
|
statusRow
|
|
293
289
|
);
|
|
294
|
-
} catch (
|
|
295
|
-
//
|
|
290
|
+
} catch (err) {
|
|
291
|
+
// A failed respond/continue never resolves itself; record it so the poll stops.
|
|
292
|
+
recordRespondError(interactions, peId, err);
|
|
296
293
|
}
|
|
297
294
|
}
|
|
298
295
|
|
|
@@ -301,7 +298,13 @@ function assertTerminalWorkHubTask(last, taskId, timeoutMs, options = {}) {
|
|
|
301
298
|
throw new Error(`Work Hub task ${taskId} produced no status rows.`);
|
|
302
299
|
}
|
|
303
300
|
const finalStatusToken = String(last?.status || last?.taskStatus || '').trim().toLowerCase();
|
|
304
|
-
if (
|
|
301
|
+
if (TERMINAL_TASK.has(finalStatusToken)) {
|
|
302
|
+
return last;
|
|
303
|
+
}
|
|
304
|
+
if (options.stoppedReason) {
|
|
305
|
+
return { ...last, pollStopped: options.stoppedReason, lastStatus: finalStatusToken || 'unknown' };
|
|
306
|
+
}
|
|
307
|
+
if (options.recordTimeout === true) {
|
|
305
308
|
last = {
|
|
306
309
|
...last,
|
|
307
310
|
pollTimedOut: true,
|
|
@@ -353,14 +356,18 @@ async function pollWorkHubTask(dataplaneUrl, authConfig, taskId, testCase, optio
|
|
|
353
356
|
applyWorkHubPollStatus(ctx, last, status);
|
|
354
357
|
await maybeApproveTask(dataplaneUrl, authConfig, taskId, ctx);
|
|
355
358
|
await maybeRespondWaiting(dataplaneUrl, authConfig, last, testCase, ctx.interactions);
|
|
356
|
-
|
|
359
|
+
ctx.stoppedReason = unansweredWaitStopReason(ctx.interactions);
|
|
360
|
+
if (ctx.stoppedReason || shouldStopPendingApprovalPoll(status, ctx)) {
|
|
357
361
|
break;
|
|
358
362
|
}
|
|
359
363
|
await delayMs(intervalMs);
|
|
360
364
|
}
|
|
361
365
|
|
|
362
366
|
return {
|
|
363
|
-
finalTask: assertTerminalWorkHubTask(last, taskId, timeoutMs, {
|
|
367
|
+
finalTask: assertTerminalWorkHubTask(last, taskId, timeoutMs, {
|
|
368
|
+
recordTimeout: !ctx.stoppedReason,
|
|
369
|
+
stoppedReason: ctx.stoppedReason || null
|
|
370
|
+
}),
|
|
364
371
|
timeline,
|
|
365
372
|
interactions: ctx.interactions
|
|
366
373
|
};
|
|
@@ -478,6 +485,8 @@ async function runWorkHubCaseLifecycle(roleKey, testCase, runtimeContext, option
|
|
|
478
485
|
module.exports = {
|
|
479
486
|
wantsWorkHubPath,
|
|
480
487
|
runWorkHubCaseLifecycle,
|
|
488
|
+
pollWorkHubTask,
|
|
489
|
+
unansweredWaitStopReason,
|
|
481
490
|
planAlreadyActivated,
|
|
482
491
|
isPlanLostError,
|
|
483
492
|
extractPeFromTask,
|
|
@@ -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.10.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-09-
|
|
15
|
+
"updatedAt": "2026-09-25T00:00:00Z",
|
|
16
16
|
"compatibility": {
|
|
17
17
|
"minVersion": "1.0.0",
|
|
18
18
|
"maxVersion": "2.0.0",
|
|
@@ -30,6 +30,35 @@
|
|
|
30
30
|
"external-datasource.schema.json"
|
|
31
31
|
],
|
|
32
32
|
"changelog": [
|
|
33
|
+
{
|
|
34
|
+
"version": "1.10.0",
|
|
35
|
+
"date": "2026-09-25T00:00:00Z",
|
|
36
|
+
"changes": [
|
|
37
|
+
"Added optional top-level webSocketsEnabled: request Azure App Service WebSocket support for applications whose manifest opts in. Absent or false preserves existing deployment behavior."
|
|
38
|
+
],
|
|
39
|
+
"breaking": false
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"version": "1.9.0",
|
|
43
|
+
"date": "2026-09-23T00:00:00Z",
|
|
44
|
+
"breaking": false,
|
|
45
|
+
"changes": [
|
|
46
|
+
"Added optional platforms: the target Azure resource group is matched against resource patterns to yield {platform} and {platform.name}, and that entry's configuration resolves from them.",
|
|
47
|
+
"Added optional environments: per-environment configuration layered over the platform match, overriding by name.",
|
|
48
|
+
"Added optional portalInput.visible (default true) so a resolved or derived value stays out of the portal form.",
|
|
49
|
+
"Both sections are optional and independent; manifests without them are unchanged."
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"version": "1.8.0",
|
|
54
|
+
"date": "2026-09-21T00:00:00Z",
|
|
55
|
+
"changes": [
|
|
56
|
+
"Added optional build.target: the multi-stage Dockerfile stage to build (docker build --target). Omitted, Docker builds the final stage as before, so existing manifests are unaffected. Lets one Dockerfile and one source tree produce more than one image.",
|
|
57
|
+
"Added optional top-level runAsRoot: run the container as the image user (root) rather than the invoking host user. Absent or false keeps the existing behavior. Only for an application that administers its own operating system, such as creating Linux accounts or running sshd.",
|
|
58
|
+
"Added optional top-level additionalPorts: extra container ports to publish beside the main port, for an application serving a second protocol such as SSH from the same container. Each host port is derived as hostPort + developerId * 100, the same formula as the main port. A base below 1024 is permitted so a well-known port keeps its number. Absent keeps the existing single-port behavior."
|
|
59
|
+
],
|
|
60
|
+
"breaking": false
|
|
61
|
+
},
|
|
33
62
|
{
|
|
34
63
|
"version": "1.7.0",
|
|
35
64
|
"date": "2026-09-16T00:00:00Z",
|
|
@@ -210,6 +239,128 @@
|
|
|
210
239
|
"type": "boolean",
|
|
211
240
|
"description": "Whether application requires storage.Physical storage is mapped to /mnt/data/. Blob storage is mapped to SMB File share /data/{app-key}/."
|
|
212
241
|
},
|
|
242
|
+
"webSocketsEnabled": {
|
|
243
|
+
"type": "boolean",
|
|
244
|
+
"description": "Whether the hosting provider must enable WebSocket upgrades for this application. Optional and false by default."
|
|
245
|
+
},
|
|
246
|
+
"platforms": {
|
|
247
|
+
"type": "array",
|
|
248
|
+
"description": "Platform resolution: the target Azure resource group is matched against these entries, yielding {platform} and {platform.name}. Optional and independent of environments.",
|
|
249
|
+
"items": {
|
|
250
|
+
"type": "object",
|
|
251
|
+
"required": ["resource", "name", "configuration"],
|
|
252
|
+
"additionalProperties": false,
|
|
253
|
+
"properties": {
|
|
254
|
+
"resource": {
|
|
255
|
+
"type": "string",
|
|
256
|
+
"description": "Resource-group pattern; {platform} marks the platform segment and * captures a name segment",
|
|
257
|
+
"minLength": 1
|
|
258
|
+
},
|
|
259
|
+
"name": {
|
|
260
|
+
"type": "string",
|
|
261
|
+
"description": "Platform name: a literal, or * to take the captured segment",
|
|
262
|
+
"minLength": 1
|
|
263
|
+
},
|
|
264
|
+
"options": {
|
|
265
|
+
"type": "array",
|
|
266
|
+
"description": "Allowed platform values; required when resource contains {platform}",
|
|
267
|
+
"minItems": 1,
|
|
268
|
+
"items": { "type": "string", "minLength": 1 }
|
|
269
|
+
},
|
|
270
|
+
"platform": {
|
|
271
|
+
"type": "string",
|
|
272
|
+
"description": "Declared platform for a resource pattern with no {platform} token; mutually exclusive with options",
|
|
273
|
+
"minLength": 1
|
|
274
|
+
},
|
|
275
|
+
"configuration": {
|
|
276
|
+
"type": "array",
|
|
277
|
+
"description": "Values resolved when this platform matches",
|
|
278
|
+
"items": {
|
|
279
|
+
"type": "object",
|
|
280
|
+
"required": ["name", "value"],
|
|
281
|
+
"additionalProperties": false,
|
|
282
|
+
"properties": {
|
|
283
|
+
"name": {
|
|
284
|
+
"type": "string",
|
|
285
|
+
"description": "Configuration variable name",
|
|
286
|
+
"pattern": "^[A-Z_][A-Z0-9_]*$"
|
|
287
|
+
},
|
|
288
|
+
"value": {
|
|
289
|
+
"type": "string",
|
|
290
|
+
"description": "Literal, or a value containing {platform}, {platform.name} or {resourceGroup} tokens"
|
|
291
|
+
},
|
|
292
|
+
"location": {
|
|
293
|
+
"type": "string",
|
|
294
|
+
"description": "Where the value is stored",
|
|
295
|
+
"enum": ["variable", "keyvault"]
|
|
296
|
+
},
|
|
297
|
+
"required": {
|
|
298
|
+
"type": "boolean",
|
|
299
|
+
"description": "Whether this configuration is required"
|
|
300
|
+
},
|
|
301
|
+
"category": {
|
|
302
|
+
"type": "string",
|
|
303
|
+
"description": "Optional UI grouping label for portal/deployment wizard Configuration tab",
|
|
304
|
+
"minLength": 1,
|
|
305
|
+
"maxLength": 100
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
"environments": {
|
|
314
|
+
"type": "array",
|
|
315
|
+
"description": "Per-environment values layered over the platform match, overriding by name. Optional and independent of platforms.",
|
|
316
|
+
"items": {
|
|
317
|
+
"type": "object",
|
|
318
|
+
"required": ["environment", "configuration"],
|
|
319
|
+
"additionalProperties": false,
|
|
320
|
+
"properties": {
|
|
321
|
+
"environment": {
|
|
322
|
+
"type": "string",
|
|
323
|
+
"description": "Deployment environment key (for example miso, dev, tst, pro)",
|
|
324
|
+
"minLength": 1
|
|
325
|
+
},
|
|
326
|
+
"configuration": {
|
|
327
|
+
"type": "array",
|
|
328
|
+
"description": "Values resolved for this environment",
|
|
329
|
+
"items": {
|
|
330
|
+
"type": "object",
|
|
331
|
+
"required": ["name", "value"],
|
|
332
|
+
"additionalProperties": false,
|
|
333
|
+
"properties": {
|
|
334
|
+
"name": {
|
|
335
|
+
"type": "string",
|
|
336
|
+
"description": "Configuration variable name",
|
|
337
|
+
"pattern": "^[A-Z_][A-Z0-9_]*$"
|
|
338
|
+
},
|
|
339
|
+
"value": {
|
|
340
|
+
"type": "string",
|
|
341
|
+
"description": "Literal, or a value containing {platform}, {platform.name} or {resourceGroup} tokens"
|
|
342
|
+
},
|
|
343
|
+
"location": {
|
|
344
|
+
"type": "string",
|
|
345
|
+
"description": "Where the value is stored",
|
|
346
|
+
"enum": ["variable", "keyvault"]
|
|
347
|
+
},
|
|
348
|
+
"required": {
|
|
349
|
+
"type": "boolean",
|
|
350
|
+
"description": "Whether this configuration is required"
|
|
351
|
+
},
|
|
352
|
+
"category": {
|
|
353
|
+
"type": "string",
|
|
354
|
+
"description": "Optional UI grouping label for portal/deployment wizard Configuration tab",
|
|
355
|
+
"minLength": 1,
|
|
356
|
+
"maxLength": 100
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
},
|
|
213
364
|
"configuration": {
|
|
214
365
|
"type": "array",
|
|
215
366
|
"description": "Core application configuration",
|
|
@@ -274,6 +425,10 @@
|
|
|
274
425
|
"type": "boolean",
|
|
275
426
|
"description": "Whether to mask the input (for passwords)"
|
|
276
427
|
},
|
|
428
|
+
"visible": {
|
|
429
|
+
"type": "boolean",
|
|
430
|
+
"description": "Whether the portal shows this input. Default true; false keeps a resolved or derived value out of the form."
|
|
431
|
+
},
|
|
277
432
|
"validation": {
|
|
278
433
|
"type": "object",
|
|
279
434
|
"properties": {
|
|
@@ -413,6 +568,10 @@
|
|
|
413
568
|
"type": "boolean",
|
|
414
569
|
"description": "Whether to mask the input (for passwords)"
|
|
415
570
|
},
|
|
571
|
+
"visible": {
|
|
572
|
+
"type": "boolean",
|
|
573
|
+
"description": "Whether the portal shows this input. Default true; false keeps a resolved or derived value out of the form."
|
|
574
|
+
},
|
|
416
575
|
"validation": {
|
|
417
576
|
"type": "object",
|
|
418
577
|
"properties": {
|
|
@@ -806,6 +965,43 @@
|
|
|
806
965
|
"minLength": 1,
|
|
807
966
|
"maxLength": 200
|
|
808
967
|
},
|
|
968
|
+
"additionalPorts": {
|
|
969
|
+
"type": "array",
|
|
970
|
+
"description": "Extra container ports to publish beside the main port. For an application that serves a second protocol from the same container, such as SSH alongside HTTP. Each host port is derived the same way as the main one: hostPort + developerId * 100 (developer id 0 keeps hostPort).",
|
|
971
|
+
"maxItems": 5,
|
|
972
|
+
"items": {
|
|
973
|
+
"type": "object",
|
|
974
|
+
"required": ["name", "containerPort", "hostPort"],
|
|
975
|
+
"additionalProperties": false,
|
|
976
|
+
"properties": {
|
|
977
|
+
"name": {
|
|
978
|
+
"type": "string",
|
|
979
|
+
"description": "What the port is for, e.g. 'ssh'. Used in generated compose comments and diagnostics.",
|
|
980
|
+
"pattern": "^[a-z][a-z0-9-]*$",
|
|
981
|
+
"minLength": 1,
|
|
982
|
+
"maxLength": 30
|
|
983
|
+
},
|
|
984
|
+
"containerPort": {
|
|
985
|
+
"type": "integer",
|
|
986
|
+
"description": "Port the process listens on inside the container.",
|
|
987
|
+
"minimum": 1,
|
|
988
|
+
"maximum": 65535
|
|
989
|
+
},
|
|
990
|
+
"hostPort": {
|
|
991
|
+
"type": "integer",
|
|
992
|
+
"description": "Base host port. The published port is hostPort + developerId * 100, so developers on one host do not collide. A base below 1024 is allowed so a well-known port can keep its number (SSH as 22, giving 122 for developer 1); the container engine binds it, not the application.",
|
|
993
|
+
"minimum": 1,
|
|
994
|
+
"maximum": 65535
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
},
|
|
999
|
+
"runAsRoot": {
|
|
1000
|
+
"type": "boolean",
|
|
1001
|
+
"default": false,
|
|
1002
|
+
"description": "Run the container as the image user (root) instead of the invoking host user. Only for an application that administers its own operating system, such as creating Linux accounts or running sshd; files it writes to a bind mount will be root-owned. Absent or false keeps the existing behavior.",
|
|
1003
|
+
"examples": [false]
|
|
1004
|
+
},
|
|
809
1005
|
"runtimeVersion": {
|
|
810
1006
|
"type": "object",
|
|
811
1007
|
"description": "Runtime version configuration",
|
|
@@ -886,6 +1082,13 @@
|
|
|
886
1082
|
"description": "Dockerfile name (empty or missing = use auto-generated template)",
|
|
887
1083
|
"pattern": "^[^/].*"
|
|
888
1084
|
},
|
|
1085
|
+
"target": {
|
|
1086
|
+
"type": "string",
|
|
1087
|
+
"description": "Multi-stage Dockerfile stage to build (docker build --target). Omit to build the final stage, which is the default. Lets one Dockerfile and one source tree produce more than one image, e.g. an application shipped in two installation variants.",
|
|
1088
|
+
"minLength": 1,
|
|
1089
|
+
"maxLength": 100,
|
|
1090
|
+
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$"
|
|
1091
|
+
},
|
|
889
1092
|
"remoteSyncPath": {
|
|
890
1093
|
"type": "string",
|
|
891
1094
|
"description": "Relative path under user-mutagen-folder for remote sync and Docker -v; when unset, defaults to dev/<appKey>. No leading slash.",
|
|
@@ -7,7 +7,7 @@
|
|
|
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.13",
|
|
11
11
|
"type":"schema",
|
|
12
12
|
"category":"integration",
|
|
13
13
|
"author":"AI Fabrix Team",
|
|
@@ -31,6 +31,15 @@
|
|
|
31
31
|
"external-system.schema.json"
|
|
32
32
|
],
|
|
33
33
|
"changelog":[
|
|
34
|
+
{
|
|
35
|
+
"version":"2.9.13",
|
|
36
|
+
"date":"2026-09-24T00:00:00Z",
|
|
37
|
+
"changes":[
|
|
38
|
+
"Added fetch.responseType ('json' | 'binary', default json): a file body becomes one binary record with headers and request arguments exposed to fieldMappings while bytes stay out of traces.",
|
|
39
|
+
"Added output.mode 'documents' for entityType documentStorage: binary records are persisted through the document pipeline and emitted as document references; documentStorage.binaryOperationRef may name the datasource's own get for keyed retrieval."
|
|
40
|
+
],
|
|
41
|
+
"breaking":false
|
|
42
|
+
},
|
|
34
43
|
{
|
|
35
44
|
"version":"2.9.12",
|
|
36
45
|
"date":"2026-09-17T00:00:00Z",
|
|
@@ -2739,6 +2748,15 @@
|
|
|
2739
2748
|
},
|
|
2740
2749
|
"httpResponseNormalization":{
|
|
2741
2750
|
"$ref":"#/$defs/httpResponseNormalization"
|
|
2751
|
+
},
|
|
2752
|
+
"responseType":{
|
|
2753
|
+
"type":"string",
|
|
2754
|
+
"enum":[
|
|
2755
|
+
"json",
|
|
2756
|
+
"binary"
|
|
2757
|
+
],
|
|
2758
|
+
"default":"json",
|
|
2759
|
+
"description":"Body contract of the vendor response. 'binary' turns a file body (application/octet-stream, PDF, image ...) into one binary record whose bytes stay out of traces: {_binary:{contentType,size,filename,sha256}, headers:{...}, params:{...}} for map expressions such as raw.headers.AttachmentName or raw.params.PurchaseOrderNumber. A binary Content-Type is promoted automatically when responseType is omitted; declaring 'binary' fails closed when the body is JSON."
|
|
2742
2760
|
}
|
|
2743
2761
|
},
|
|
2744
2762
|
"allOf":[
|
|
@@ -3174,9 +3192,11 @@
|
|
|
3174
3192
|
"mode":{
|
|
3175
3193
|
"type":"string",
|
|
3176
3194
|
"enum":[
|
|
3177
|
-
"records"
|
|
3195
|
+
"records",
|
|
3196
|
+
"documents"
|
|
3178
3197
|
],
|
|
3179
|
-
"default":"records"
|
|
3198
|
+
"default":"records",
|
|
3199
|
+
"description":"'records' emits normalized records. 'documents' (entityType documentStorage only) stores each binary record through the document pipeline (document store, DocumentRecord/registry rows, FK dimensions, ingestion per documentStorage.ingestAfterSync) and emits document references {key, filename, contentType, size, sha256, downloadRef}; IDE simulation never persists."
|
|
3180
3200
|
},
|
|
3181
3201
|
"limit":{
|
|
3182
3202
|
"type":"integer",
|