@aifabrix/builder 2.56.0 → 2.57.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 (54) hide show
  1. package/lib/agent-kit/session-report.js +76 -0
  2. package/lib/agent-kit/start.js +124 -57
  3. package/lib/agent-kit/tmux-sessions.js +163 -0
  4. package/lib/api/environments.api.js +38 -0
  5. package/lib/api/marketplace-bootstrap.api.js +31 -0
  6. package/lib/cli/setup-environment.js +76 -19
  7. package/lib/commands/agent-kit-help.js +32 -15
  8. package/lib/commands/agent-kit.js +90 -8
  9. package/lib/deployment/installation/azure-infra-stage.js +160 -0
  10. package/lib/deployment/installation/azure-preflight.js +102 -0
  11. package/lib/deployment/installation/azure-readiness.js +71 -0
  12. package/lib/deployment/installation/docker-register.js +112 -0
  13. package/lib/deployment/installation/environment-auth.js +76 -0
  14. package/lib/deployment/installation/environment-manifest.js +165 -0
  15. package/lib/deployment/installation/environment-stage.js +191 -0
  16. package/lib/deployment/installation/index.js +39 -0
  17. package/lib/deployment/installation/infra-catalog.js +165 -0
  18. package/lib/deployment/installation/infra-manifest.js +161 -0
  19. package/lib/deployment/installation/infra-projection.js +86 -0
  20. package/lib/deployment/installation/public-ip.js +65 -0
  21. package/lib/deployment/installation/stage-input.js +169 -0
  22. package/lib/deployment/installation/stage-output.js +53 -0
  23. package/lib/deployment/installation/workspace-env.js +76 -0
  24. package/lib/deployment/installation/workspace-files.js +178 -0
  25. package/lib/deployment/installation/workspace-schema.js +40 -0
  26. package/lib/deployment/installation/workspace-secrets.js +66 -0
  27. package/lib/deployment/installation/workspace.js +128 -0
  28. package/lib/deployment/marketplace-environment.js +305 -0
  29. package/lib/generator/builders.js +4 -0
  30. package/lib/generator/helpers.js +55 -56
  31. package/lib/generator/split-variables.js +11 -5
  32. package/lib/internal/fs-real-sync.js +29 -0
  33. package/lib/role-assistant/package-validate.js +4 -0
  34. package/lib/role-assistant/test-runner-clarification-resubmit.js +149 -0
  35. package/lib/role-assistant/test-runner.js +14 -17
  36. package/lib/schema/application-schema.json +20 -2
  37. package/lib/schema/infra-parameter.schema.json +7 -0
  38. package/lib/schema/infra.parameter.yaml +26 -36
  39. package/lib/schema/infrastructure-schema.json +1480 -399
  40. package/lib/schema/installation-environment.schema.json +601 -0
  41. package/lib/utils/variable-transformer.js +3 -0
  42. package/lib/validation/required-variables.js +41 -0
  43. package/lib/validation/validator.js +8 -5
  44. package/package.json +5 -1
  45. package/templates/README.md +4 -0
  46. package/templates/agent-kit/instructions/AGENTKIT.md +6 -6
  47. package/templates/agent-kit/skills/shared/hosts.md +10 -10
  48. package/templates/applications/dataplane/application.yaml +1 -1
  49. package/templates/applications/dataplane/env.template +1 -1
  50. package/templates/applications/miso-controller/application.yaml +3 -2
  51. package/templates/applications/miso-controller/env.template +15 -6
  52. package/templates/marketplace/createUiDefinition.json +77 -0
  53. package/templates/marketplace/install.parameters.example.json +9 -0
  54. package/templates/marketplace/main.json +4087 -0
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Resubmit work.start with a businessRoleRef when an omitted-RA case's
3
+ * free-text message gets a synchronous clarificationRequired rejection.
4
+ * @fileoverview Extracted from test-runner.js (file-size)
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ function normalizeToken(value) {
10
+ if (typeof value !== 'string') {
11
+ return '';
12
+ }
13
+ return value
14
+ .trim()
15
+ .toLowerCase()
16
+ .replace(/[^a-z0-9]+/g, '');
17
+ }
18
+
19
+ function isRoleAssistantClarificationRequired(startResponse) {
20
+ return normalizeToken(startResponse?.status) === 'clarificationrequired';
21
+ }
22
+
23
+ function extractProcessExecutionId(startResponse) {
24
+ const candidates = [
25
+ startResponse?.work?.processExecutionId,
26
+ startResponse?.intent?.processExecutionId,
27
+ startResponse?.processExecutionId,
28
+ startResponse?.status?.processExecutionId
29
+ ];
30
+ for (const row of candidates) {
31
+ if (typeof row === 'string' && row.trim()) {
32
+ return row.trim();
33
+ }
34
+ }
35
+ return '';
36
+ }
37
+
38
+ function findRuntimeRoleAssistantAnswerValue(testCase) {
39
+ const answers = Array.isArray(testCase?.answers) ? testCase.answers : [];
40
+ for (const candidate of answers) {
41
+ const when = candidate && typeof candidate === 'object' ? candidate.when : null;
42
+ const questionKey = when && typeof when === 'object' ? when.questionKey : null;
43
+ if (normalizeToken(questionKey) !== 'runtimeroleassistant') {
44
+ continue;
45
+ }
46
+ const value = candidate.value;
47
+ if (typeof value === 'string' && value.trim()) {
48
+ return value.trim();
49
+ }
50
+ }
51
+ return '';
52
+ }
53
+
54
+ /**
55
+ * work.start with an omitted businessRoleRef and an ambiguous free-text
56
+ * message can return a *synchronous* `clarificationRequired` rejection with
57
+ * no task/processExecutionId at all -- there is nothing to `respond`/
58
+ * `continue` against yet. A case authored for this "omitted RA" scenario
59
+ * (input.businessRoleRef omitted, answers[].when.questionKey:
60
+ * runtimeRoleAssistant) is asserting the *next* work.start call should carry
61
+ * that answer's businessRoleRef, not that this one can be answered in place.
62
+ * Resubmit once with it before giving up.
63
+ *
64
+ * @param {object} params
65
+ * @param {(dataplaneUrl: string, authConfig: object, body: object) => Promise<object>} params.startWorkIntent
66
+ * @param {string} params.dataplaneUrl
67
+ * @param {object} params.authConfig
68
+ * @param {object} params.startPayload
69
+ * @param {object} params.testCase
70
+ * @param {string} params.roleKey
71
+ * @param {(prefix: string) => string} params.buildRuntimeId
72
+ * @returns {Promise<object|null>} the resubmitted start response, or null when no answer matched
73
+ */
74
+ async function resubmitStartWithRoleAssistantAnswer({
75
+ startWorkIntent,
76
+ dataplaneUrl,
77
+ authConfig,
78
+ startPayload,
79
+ testCase,
80
+ roleKey,
81
+ buildRuntimeId
82
+ }) {
83
+ const roleAssistantAnswer = findRuntimeRoleAssistantAnswerValue(testCase);
84
+ if (!roleAssistantAnswer) {
85
+ return null;
86
+ }
87
+ return startWorkIntent(dataplaneUrl, authConfig, {
88
+ ...startPayload,
89
+ // The rejected first call already recorded its submissionId as an
90
+ // idempotency key server-side (live-reproduced: reusing it here 409s
91
+ // with "submissionId was already used for a different request") --
92
+ // this resubmission is a genuinely new request, not a retry of the
93
+ // same one.
94
+ submissionId: buildRuntimeId(`ra-${roleKey}`),
95
+ businessRoleRef: roleAssistantAnswer
96
+ });
97
+ }
98
+
99
+ /**
100
+ * Start a free-text role-assistant case, resubmitting once with a matching
101
+ * `runtimeRoleAssistant` answer if the first call comes back as a
102
+ * synchronous clarification rejection with no processExecutionId.
103
+ *
104
+ * @param {object} params
105
+ * @param {(dataplaneUrl: string, authConfig: object, body: object) => Promise<object>} params.startWorkIntent
106
+ * @param {string} params.dataplaneUrl
107
+ * @param {object} params.authConfig
108
+ * @param {object} params.startPayload
109
+ * @param {object} params.testCase
110
+ * @param {string} params.roleKey
111
+ * @param {(prefix: string) => string} params.buildRuntimeId
112
+ * @returns {Promise<{startResponse: object, processExecutionId: string}>}
113
+ */
114
+ async function startRoleAssistantCaseWithClarificationResubmit({
115
+ startWorkIntent,
116
+ dataplaneUrl,
117
+ authConfig,
118
+ startPayload,
119
+ testCase,
120
+ roleKey,
121
+ buildRuntimeId
122
+ }) {
123
+ let startResponse = await startWorkIntent(dataplaneUrl, authConfig, startPayload);
124
+ let processExecutionId = extractProcessExecutionId(startResponse);
125
+ if (!processExecutionId && isRoleAssistantClarificationRequired(startResponse)) {
126
+ const resubmitted = await resubmitStartWithRoleAssistantAnswer({
127
+ startWorkIntent,
128
+ dataplaneUrl,
129
+ authConfig,
130
+ startPayload,
131
+ testCase,
132
+ roleKey,
133
+ buildRuntimeId
134
+ });
135
+ if (resubmitted) {
136
+ startResponse = resubmitted;
137
+ processExecutionId = extractProcessExecutionId(startResponse);
138
+ }
139
+ }
140
+ return { startResponse, processExecutionId };
141
+ }
142
+
143
+ module.exports = {
144
+ isRoleAssistantClarificationRequired,
145
+ findRuntimeRoleAssistantAnswerValue,
146
+ resubmitStartWithRoleAssistantAnswer,
147
+ startRoleAssistantCaseWithClarificationResubmit,
148
+ extractProcessExecutionId
149
+ };
@@ -25,6 +25,10 @@ const {
25
25
  resolveInputMessage
26
26
  } = require('./test-runner-start-payload');
27
27
  const { pickHumanRespondBody } = require('./test-runner-respond-body');
28
+ const {
29
+ startRoleAssistantCaseWithClarificationResubmit,
30
+ extractProcessExecutionId
31
+ } = require('./test-runner-clarification-resubmit');
28
32
  const TERMINAL_STATUSES = new Set(['completed', 'stopped']);
29
33
  const WAITING_STATUS = 'waiting';
30
34
 
@@ -73,21 +77,6 @@ function isExecutableRoleAssistantCase(testCase) {
73
77
  return false;
74
78
  }
75
79
 
76
- function extractProcessExecutionId(startResponse) {
77
- const candidates = [
78
- startResponse?.work?.processExecutionId,
79
- startResponse?.intent?.processExecutionId,
80
- startResponse?.processExecutionId,
81
- startResponse?.status?.processExecutionId
82
- ];
83
- for (const row of candidates) {
84
- if (typeof row === 'string' && row.trim()) {
85
- return row.trim();
86
- }
87
- }
88
- return '';
89
- }
90
-
91
80
  function isTerminalStatus(statusValue) {
92
81
  return TERMINAL_STATUSES.has(normalizeToken(statusValue));
93
82
  }
@@ -420,8 +409,16 @@ async function runRoleAssistantCaseLifecycle(roleKey, testCase, runtimeContext,
420
409
  return runWorkHubCaseLifecycle(roleKey, testCase, runtimeContext, runnerOptions);
421
410
  }
422
411
  const startPayload = buildStartIntentPayload(roleKey, testCase);
423
- const startResponse = await startWorkIntent(dataplaneUrl, authConfig, startPayload);
424
- const processExecutionId = extractProcessExecutionId(startResponse);
412
+ const { startResponse, processExecutionId } = await startRoleAssistantCaseWithClarificationResubmit({
413
+ startWorkIntent,
414
+ extractProcessExecutionId,
415
+ dataplaneUrl,
416
+ authConfig,
417
+ startPayload,
418
+ testCase,
419
+ roleKey,
420
+ buildRuntimeId
421
+ });
425
422
  if (!processExecutionId) {
426
423
  throw new Error(
427
424
  'work.start missing processExecutionId; use input.executionPath: workHub for free-text RA cases.'
@@ -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.6.2",
10
+ "version": "1.7.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-08-06T00:00:00Z",
15
+ "updatedAt": "2026-09-16T00: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.7.0",
35
+ "date": "2026-09-16T00:00:00Z",
36
+ "changes": [
37
+ "Added optional top-level required: unique environment-variable names the application needs in every deployment target. Absent or empty keeps existing behavior."
38
+ ],
39
+ "breaking": false
40
+ },
33
41
  {
34
42
  "version": "1.6.2",
35
43
  "date": "2026-08-06T00:00:00Z",
@@ -937,6 +945,16 @@
937
945
  "$ref": "aifabrix://schema/external-datasource.schema.json"
938
946
  }
939
947
  },
948
+ "required": {
949
+ "type": "array",
950
+ "description": "Environment-variable names this application requires in every deployment target. Each name must be declared by the application configuration (env.template) and resolve before deployment. Absent or empty means no extra requirement.",
951
+ "uniqueItems": true,
952
+ "items": {
953
+ "type": "string",
954
+ "pattern": "^[A-Z_][A-Z0-9_]*$",
955
+ "maxLength": 255
956
+ }
957
+ },
940
958
  "environmentScopedResources": {
941
959
  "type": "boolean",
942
960
  "description": "When true, this application supports environment-scoped resource names (Key Vault key prefixes, Redis DB index, local Docker container and Traefik paths) when useEnvironmentScopedResources is activated in ~/.aifabrix/config.yaml and the target environment is dev or tst. Does not apply to pro. Used to share Postgres/Docker across dev/tst without collisions."
@@ -122,6 +122,13 @@
122
122
  },
123
123
  "requiredOnSystemOnboarding": { "type": "boolean" },
124
124
  "ownerAppKey": { "type": "string", "minLength": 1 },
125
+ "sharedWithAppKeys": {
126
+ "type": "array",
127
+ "items": { "type": "string", "minLength": 1 },
128
+ "minItems": 1,
129
+ "uniqueItems": true,
130
+ "description": "Named applications, other than ownerAppKey, that may read this value."
131
+ },
125
132
  "scope": { "$ref": "#/definitions/scopeEnum" },
126
133
  "generator": { "$ref": "#/definitions/generator" },
127
134
  "ensureOn": {
@@ -435,6 +435,13 @@ parameters:
435
435
 
436
436
  - key: miso-controller-secrets-encryptionKeyVault
437
437
  ownerAppKey: miso-controller
438
+ # Plan 227.4: the data plane must hold the same key. It hands this value to MisoClient so
439
+ # the controller's encrypt/decrypt service interoperates, and a credential encrypted by one
440
+ # side is decrypted by the other. The read was already happening in the shipped template
441
+ # and the policy denied it. Declared rather than silently permitted, and kept to the one
442
+ # application that needs it instead of moving the key to a scope that grants everyone.
443
+ sharedWithAppKeys:
444
+ - dataplane
438
445
  scope: app
439
446
  generator:
440
447
  type: randomBytes32
@@ -619,21 +626,6 @@ parameters:
619
626
  notes: Per-app OAuth client secret for dataplane; generated on first ensure.
620
627
 
621
628
  # Mori / telemetry is optional for an unlicensed Community installation.
622
- # MORI_AUTH_METHOD=kv://mori-controller-auth-methodKeyVault — local default apiKey.
623
- - key: mori-controller-auth-methodKeyVault
624
- category: Mori
625
- sensitive: false
626
- valueSource: optionalInput
627
- requiredOnSystemOnboarding: false
628
- scope: system
629
- generator:
630
- type: literal
631
- value: 'apiKey'
632
- ensureOn: [upInfra, resolveApp]
633
- azure:
634
- notes: >-
635
- Local/dev default apiKey. Override when using Mori basic auth or other methods.
636
-
637
629
  - key: mori-controller-api-keyKeyVault
638
630
  category: Mori
639
631
  sensitive: true
@@ -646,19 +638,8 @@ parameters:
646
638
  azure:
647
639
  notes: Empty locally; set when calling Mori with API key auth.
648
640
 
649
- - key: mori-controller-basic-usernameKeyVault
650
- category: Mori
651
- sensitive: false
652
- valueSource: optionalInput
653
- requiredOnSystemOnboarding: false
654
- scope: system
655
- generator:
656
- type: emptyString
657
- ensureOn: [upInfra, resolveApp]
658
- azure:
659
- notes: Empty locally; Mori basic auth optional.
660
-
661
- - key: mori-controller-basic-passwordKeyVault
641
+ # Public, but a security-critical trust anchor. Never derive it from a pasted JWT.
642
+ - key: mori-license-public-keyKeyVault
662
643
  category: Mori
663
644
  sensitive: true
664
645
  valueSource: optionalInput
@@ -668,7 +649,8 @@ parameters:
668
649
  type: emptyString
669
650
  ensureOn: [upInfra, resolveApp]
670
651
  azure:
671
- notes: Empty locally; Mori basic auth optional.
652
+ vaultSecretName: mori-license-public-keyKeyVault
653
+ notes: Trusted Mori SPKI PEM public key, provisioned by the installer or issuer release identity.
672
654
 
673
655
  - key: appinsights-connectionStringKeyVault
674
656
  category: Observability
@@ -842,8 +824,9 @@ parameters:
842
824
  type: databaseUrl
843
825
  ensureOn: [upInfra, resolveApp]
844
826
  azure:
845
- vaultSecretName: databases-miso-controller-0-urlKeyVault
827
+ vaultSecretNamePattern: '{appKey}-databases-{index}-urlKeyVault'
846
828
  legacyVaultSecretNames:
829
+ - '{key}'
847
830
  - '{serviceName}-databases-0-urlKeyVault'
848
831
 
849
832
  - key: databases-miso-controller-0-passwordKeyVault
@@ -853,7 +836,9 @@ parameters:
853
836
  type: databasePassword
854
837
  ensureOn: [upInfra, resolveApp]
855
838
  azure:
856
- vaultSecretName: databases-miso-controller-0-passwordKeyVault
839
+ vaultSecretNamePattern: '{appKey}-databases-{index}-passwordKeyVault'
840
+ legacyVaultSecretNames:
841
+ - '{key}'
857
842
 
858
843
  - key: databases-miso-controller-1-urlKeyVault
859
844
  ownerAppKey: miso-controller
@@ -862,8 +847,9 @@ parameters:
862
847
  type: databaseUrl
863
848
  ensureOn: [upInfra, resolveApp]
864
849
  azure:
865
- vaultSecretName: databases-miso-controller-1-urlKeyVault
850
+ vaultSecretNamePattern: '{appKey}-databases-{index}-urlKeyVault'
866
851
  legacyVaultSecretNames:
852
+ - '{key}'
867
853
  - '{serviceName}-databases-1-urlKeyVault'
868
854
 
869
855
  - key: databases-miso-controller-1-passwordKeyVault
@@ -873,7 +859,9 @@ parameters:
873
859
  type: databasePassword
874
860
  ensureOn: [upInfra, resolveApp]
875
861
  azure:
876
- vaultSecretName: databases-miso-controller-1-passwordKeyVault
862
+ vaultSecretNamePattern: '{appKey}-databases-{index}-passwordKeyVault'
863
+ legacyVaultSecretNames:
864
+ - '{key}'
877
865
 
878
866
  # Per-index database secrets. Index must match requires.databases order in application.yaml.
879
867
  - keyPattern: '^databases-[a-z0-9-]+-\d+-urlKeyVault$'
@@ -882,9 +870,10 @@ parameters:
882
870
  type: databaseUrl
883
871
  ensureOn: [upInfra, resolveApp]
884
872
  azure:
873
+ vaultSecretNamePattern: '{appKey}-databases-{index}-urlKeyVault'
885
874
  legacyVaultSecretNames:
886
- - '{appKey}-databases-{index}-urlKeyVault'
887
- notes: The direct manifest key is canonical; the app-prefixed form is migration-only.
875
+ - '{key}'
876
+ notes: The manifest key stays logical; the app-prefixed name is the physical vault key.
888
877
 
889
878
  - keyPattern: '^databases-[a-z0-9-]+-\d+-passwordKeyVault$'
890
879
  scope: app
@@ -892,8 +881,9 @@ parameters:
892
881
  type: databasePassword
893
882
  ensureOn: [upInfra, resolveApp]
894
883
  azure:
884
+ vaultSecretNamePattern: '{appKey}-databases-{index}-passwordKeyVault'
895
885
  legacyVaultSecretNames:
896
- - '{appKey}-databases-{index}-passwordKeyVault'
886
+ - '{key}'
897
887
 
898
888
  # Keycloak and other *-url keys (non-database).
899
889
  - keyPattern: '^keycloak-.+-url$'