@aifabrix/builder 2.58.0 → 2.59.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 (84) hide show
  1. package/lib/agent-kit/codex-app-server.js +44 -0
  2. package/lib/agent-kit/init.js +4 -17
  3. package/lib/agent-kit/session-report.js +15 -10
  4. package/lib/agent-kit/setup-auth.js +131 -0
  5. package/lib/agent-kit/setup-context.js +118 -0
  6. package/lib/agent-kit/setup-github.js +70 -0
  7. package/lib/agent-kit/setup-managed.js +79 -0
  8. package/lib/agent-kit/setup-process.js +42 -0
  9. package/lib/agent-kit/setup-session.js +53 -0
  10. package/lib/agent-kit/setup-state.js +52 -0
  11. package/lib/agent-kit/setup-workspace.js +113 -0
  12. package/lib/agent-kit/setup.js +84 -0
  13. package/lib/agent-kit/start.js +4 -3
  14. package/lib/agent-kit/tmux-sessions.js +20 -3
  15. package/lib/api/dev.api.js +1 -1
  16. package/lib/api/miso-health.api.js +26 -0
  17. package/lib/api/types/dev.types.js +3 -3
  18. package/lib/app/deploy.js +0 -102
  19. package/lib/cli/index.js +4 -0
  20. package/lib/cli/setup-dev-path-commands.js +4 -0
  21. package/lib/cli/setup-dev.js +7 -4
  22. package/lib/cli/setup-help.js +86 -0
  23. package/lib/cli/setup-onboarding.js +37 -0
  24. package/lib/cli/setup-utility-repair.js +7 -5
  25. package/lib/commands/agent-kit-help.js +21 -7
  26. package/lib/commands/agent-kit-sessions.js +126 -0
  27. package/lib/commands/agent-kit-setup.js +44 -0
  28. package/lib/commands/agent-kit.js +5 -79
  29. package/lib/commands/dev-init.js +21 -23
  30. package/lib/commands/dev-refresh.js +2 -2
  31. package/lib/commands/governance-verify-external.js +12 -5
  32. package/lib/commands/repair-datasource.js +9 -9
  33. package/lib/commands/repair.js +45 -8
  34. package/lib/commands/upload-qualification-client.js +56 -11
  35. package/lib/commands/upload-qualification-poll.js +51 -22
  36. package/lib/commands/wizard-dataplane.js +16 -52
  37. package/lib/datasource/datasource-validate-summary.js +8 -5
  38. package/lib/datasource/sync-defaults.js +53 -0
  39. package/lib/deployment/installation/azure-infra-stage.js +10 -0
  40. package/lib/deployment/installation/azure-preflight.js +13 -2
  41. package/lib/deployment/installation/azure-readiness.js +24 -2
  42. package/lib/deployment/installation/environment-auth.js +60 -20
  43. package/lib/deployment/installation/environment-stage.js +9 -5
  44. package/lib/deployment/installation/infra-catalog.js +8 -2
  45. package/lib/deployment/installation/infra-manifest.js +5 -0
  46. package/lib/deployment/installation/registry-auth-mode.js +33 -10
  47. package/lib/deployment/marketplace-environment.js +26 -1
  48. package/lib/generator/index.js +23 -12
  49. package/lib/integration-definition/apply.js +2 -2
  50. package/lib/internal/fs-real-sync.js +4 -1
  51. package/lib/programmatic/bearer-auth.js +3 -62
  52. package/lib/programmatic/builder-api-startup.js +51 -0
  53. package/lib/programmatic/builder-miso-runtime.js +167 -0
  54. package/lib/programmatic/openapi-descriptions.js +3 -3
  55. package/lib/programmatic/openapi-schema-descriptions-core.js +3 -3
  56. package/lib/programmatic/openapi-spec.js +2 -1
  57. package/lib/programmatic/route-handler-map.js +3 -2
  58. package/lib/schema/environment-deploy-request.schema.json +1 -1
  59. package/lib/schema/external-datasource.schema.json +20 -5
  60. package/lib/schema/infra-parameter.schema.json +1 -1
  61. package/lib/schema/infra.parameter.yaml +2 -1
  62. package/lib/schema/infrastructure-schema.json +145 -1
  63. package/lib/schema/installation-environment.schema.json +1 -1
  64. package/lib/schema/wizard-config.schema.json +1 -1
  65. package/lib/utils/config-paths.js +24 -29
  66. package/lib/utils/config-registry-preference.js +13 -6
  67. package/lib/utils/dev-init-ssh-merge.js +5 -4
  68. package/lib/utils/dev-user-groups.js +2 -1
  69. package/lib/utils/docker-daemon-tls-ca.js +2 -2
  70. package/lib/utils/error-formatters/validation-errors.js +25 -5
  71. package/lib/utils/help-builder.js +4 -3
  72. package/lib/utils/token-manager.js +29 -36
  73. package/package.json +3 -2
  74. package/templates/agent-kit/instructions/AGENTKIT.md +39 -2
  75. package/templates/agent-kit/instructions/root.AGENTS.md +40 -1
  76. package/templates/agent-kit/skills/shared/hosts.md +23 -5
  77. package/templates/agent-kit/workspace/README.md +14 -1
  78. package/templates/applications/builder-api/application.yaml +1 -1
  79. package/templates/applications/dataplane/application.yaml +1 -1
  80. package/templates/applications/dataplane/env.template +1 -1
  81. package/templates/applications/miso-controller/application.yaml +1 -1
  82. package/templates/applications/miso-controller/env.template +1 -1
  83. package/templates/external-system/external-datasource.yaml.hbs +7 -6
  84. package/templates/marketplace/main.json +438 -80
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Builder's process-owned SDK runtime and application-token health check.
3
+ * @fileoverview SDK owns providers, secrets, renewal and connections.
4
+ */
5
+ 'use strict';
6
+
7
+ const { validateMisoApplicationToken } = require('../api/miso-health.api');
8
+ let activeRuntime;
9
+
10
+ /** @param {string} code Safe category @returns {Error} Service-unavailable error */
11
+ function runtimeError(code) {
12
+ const messages = {
13
+ miso_authentication_failed: 'Miso application authentication failed',
14
+ miso_timeout: 'Miso application token check timed out',
15
+ miso_unreachable: 'Miso Controller is unavailable',
16
+ miso_runtime_unavailable: 'Miso application runtime is unavailable'
17
+ };
18
+ return Object.assign(new Error(messages[code]), {
19
+ statusCode: 503, code, errorType: `/Errors/${code}`
20
+ });
21
+ }
22
+
23
+ /** @param {unknown} error SDK error @returns {string} Safe category */
24
+ function failureCode(error) {
25
+ const status = error?.statusCode || error?.errorResponse?.status || error?.response?.status;
26
+ if ([401, 403].includes(status) || error?.code === 'authorization-denied') {
27
+ return 'miso_authentication_failed';
28
+ }
29
+ if (['ECONNABORTED', 'ETIMEDOUT', 'ERR_CANCELED'].includes(error?.code)) {
30
+ return 'miso_timeout';
31
+ }
32
+ return 'miso_unreachable';
33
+ }
34
+
35
+ /** SDK resource ownership and fail-closed application state. */
36
+ class RuntimeController {
37
+ /** @param {Object} runtime SDK runtime */
38
+ constructor(runtime) {
39
+ this.runtime = runtime;
40
+ this.unsubscribe = runtime.onInvalidated(reason => {
41
+ this.unavailable = reason === 'authorization-denied'
42
+ ? 'miso_authentication_failed' : 'miso_runtime_unavailable';
43
+ this.abort?.abort();
44
+ });
45
+ }
46
+
47
+ /** @returns {void} Throws when the SDK snapshot is unavailable */
48
+ assertAvailable() {
49
+ if (this.unavailable) throw runtimeError(this.unavailable);
50
+ try {
51
+ this.runtime.secrets.get('');
52
+ } catch {
53
+ throw runtimeError(this.unavailable || 'miso_runtime_unavailable');
54
+ }
55
+ }
56
+
57
+ /** @returns {Object} Initialized client */
58
+ getClient() {
59
+ this.assertAvailable();
60
+ return this.runtime.client;
61
+ }
62
+
63
+ /** @returns {Promise<void>} Idempotent cleanup */
64
+ close() {
65
+ if (!this.closing) {
66
+ this.unavailable = this.unavailable || 'miso_runtime_unavailable';
67
+ this.abort?.abort();
68
+ this.unsubscribe();
69
+ this.closing = Promise.resolve().then(() => this.runtime.close()).catch(() => {
70
+ throw runtimeError('miso_runtime_unavailable');
71
+ });
72
+ }
73
+ return this.closing;
74
+ }
75
+
76
+ /** @returns {Promise<void>} Fresh, coalesced token proof; never caches success */
77
+ health() {
78
+ this.assertAvailable();
79
+ if (!this.pending) {
80
+ this.abort = new AbortController();
81
+ this.pending = runProbe(this.runtime.client, this.abort,
82
+ () => this.assertAvailable(), code => {
83
+ this.unavailable = code;
84
+ void this.close().catch(() => {});
85
+ }, () => {
86
+ this.pending = undefined;
87
+ });
88
+ }
89
+ return this.pending;
90
+ }
91
+ }
92
+
93
+ /** @param {Object} runtime SDK runtime @returns {RuntimeController} Consumer controls */
94
+ function createRuntimeController(runtime) {
95
+ return new RuntimeController(runtime);
96
+ }
97
+
98
+ /**
99
+ * Coalesce until transport settles, including SDK-owned token acquisition after a deadline.
100
+ * @param {Object} client SDK client
101
+ * @param {AbortController} abort Cancellation
102
+ * @param {Function} assertAvailable Runtime guard
103
+ * @param {Function} invalidate Terminal rejection
104
+ * @param {Function} settled Release in-flight work
105
+ * @returns {Promise<void>} Bounded health result
106
+ */
107
+ function runProbe(client, abort, assertAvailable, invalidate, settled) {
108
+ let timer;
109
+ const deadline = new Promise((_, reject) => {
110
+ timer = setTimeout(() => {
111
+ abort.abort(); reject(runtimeError('miso_timeout'));
112
+ }, 3000);
113
+ });
114
+ const request = validateMisoApplicationToken(client, abort.signal).then(() => {
115
+ assertAvailable();
116
+ }).catch(error => {
117
+ let code = failureCode(error);
118
+ try {
119
+ assertAvailable();
120
+ } catch (unavailable) {
121
+ code = unavailable.code;
122
+ }
123
+ if (code === 'miso_authentication_failed') invalidate(code);
124
+ throw runtimeError(code);
125
+ }).finally(() => {
126
+ clearTimeout(timer); settled();
127
+ });
128
+ return Promise.race([request, deadline]);
129
+ }
130
+
131
+ /** @param {Function} [init] SDK factory for tests @returns {Promise<Object>} Runtime controls */
132
+ async function initializeBuilderMisoRuntime(init) {
133
+ if (activeRuntime) throw runtimeError('miso_runtime_unavailable');
134
+ const factory = init || require('@aifabrix/miso-client/bootstrap').initSecrets;
135
+ try {
136
+ activeRuntime = createRuntimeController(await factory());
137
+ return activeRuntime;
138
+ } catch {
139
+ throw runtimeError('miso_runtime_unavailable');
140
+ }
141
+ }
142
+
143
+ /** @returns {Object} Active guarded runtime */
144
+ function getBuilderMisoRuntime() {
145
+ if (!activeRuntime) throw runtimeError('miso_runtime_unavailable');
146
+ return activeRuntime;
147
+ }
148
+
149
+ /** @returns {Promise<void>} Close and release process-owned runtime */
150
+ async function closeBuilderMisoRuntime() {
151
+ const current = activeRuntime;
152
+ activeRuntime = undefined;
153
+ await current?.close();
154
+ }
155
+
156
+ /** @param {boolean} [ready] Readiness response @returns {Promise<Object>} Healthy payload */
157
+ async function checkBuilderMisoHealth(ready = false) {
158
+ await getBuilderMisoRuntime().health();
159
+ return ready
160
+ ? { status: 'ready', dataplaneUrl: process.env.DATAPLANE_URL || null }
161
+ : { status: 'ok' };
162
+ }
163
+
164
+ module.exports = {
165
+ initializeBuilderMisoRuntime, getBuilderMisoRuntime, closeBuilderMisoRuntime,
166
+ checkBuilderMisoHealth, createRuntimeController
167
+ };
@@ -213,14 +213,14 @@ const OPERATION_DESCRIPTIONS = Object.freeze({
213
213
  tags: ['repository']
214
214
  },
215
215
  health: {
216
- summary: 'Liveness probe',
216
+ summary: 'Application-token health probe',
217
217
  description:
218
- 'Returns ok when the builder-api process is running. Portal availability probe. Empty channel catalog is not a down signal. No authentication.',
218
+ 'Returns ok only after Miso accepts the application client token. Returns 503 when authentication, runtime validity or connectivity fails. No caller authentication; user credentials cannot substitute for the application token.',
219
219
  tags: ['infra']
220
220
  },
221
221
  ready: {
222
222
  summary: 'Readiness probe',
223
- description: 'Returns ready with configured dataplaneUrl. Does not verify Dataplane connectivity in V1.',
223
+ description: 'Verifies the application client token with Miso and returns ready with configured dataplaneUrl. Returns 503 for an unusable token or unavailable Miso runtime. Does not verify Dataplane connectivity.',
224
224
  tags: ['infra']
225
225
  },
226
226
  getBuilderHelp: {
@@ -113,11 +113,11 @@ const CORE_SCHEMA_DESCRIPTIONS = {
113
113
  deleted: 'Always true when the workspace folder was removed.'
114
114
  },
115
115
  HealthData: {
116
- _schema: 'Liveness probe payload when the builder-api process is running.',
117
- status: 'Always ok for successful liveness checks.'
116
+ _schema: 'Health payload after Miso accepts the application client token.',
117
+ status: 'Always ok after successful application-token validation.'
118
118
  },
119
119
  ReadyData: {
120
- _schema: 'Readiness probe payload; does not verify Dataplane connectivity in V1.',
120
+ _schema: 'Readiness after Miso application-token validation; does not verify Dataplane connectivity.',
121
121
  status: 'Always ready when the service accepts traffic.',
122
122
  dataplaneUrl: 'Configured dev Dataplane base URL, or null when unset.'
123
123
  },
@@ -68,7 +68,8 @@ function buildStandardErrorResponses() {
68
68
  408: problemResponseContent('Request exceeded capability timeout.'),
69
69
  409: problemResponseContent('Workspace lock held by another in-flight operation.'),
70
70
  413: problemResponseContent('Request body exceeds MAX_BODY_BYTES.'),
71
- 500: problemResponseContent('Unexpected server error.')
71
+ 500: problemResponseContent('Unexpected server error.'),
72
+ 503: problemResponseContent('Miso application authentication failed, token check timed out, or runtime/Controller unavailable. Safe failure category appears in type.')
72
73
  };
73
74
  }
74
75
 
@@ -8,6 +8,7 @@
8
8
 
9
9
  'use strict';
10
10
 
11
+ const { checkBuilderMisoHealth } = require('./builder-miso-runtime');
11
12
  const { buildContext } = require('./route-handlers-context');
12
13
  const { listSystems, createSystem, getSystemManifest, deleteSystem } = require('./workspace-ops');
13
14
  const { persistSuppliedOpenApi } = require('./persist-supplied-openapi');
@@ -230,8 +231,8 @@ const HANDLER_MAP = {
230
231
  getRepositoryStatus: (params, auth) => getRepositoryStatus(buildContext(auth, params.systemKey)),
231
232
  runRepositoryDispatchPipeline: (params, auth, body) =>
232
233
  runRepositoryDispatchPipeline(buildContext(auth, params.systemKey), body),
233
- health: () => ({ status: 'ok' }),
234
- ready: () => ({ status: 'ready', dataplaneUrl: process.env.DATAPLANE_URL || null }),
234
+ health: () => checkBuilderMisoHealth(),
235
+ ready: () => checkBuilderMisoHealth(true),
235
236
  getBuilderHelp: (_params, _auth, _body, query) => handleGetBuilderHelp(query || {}),
236
237
  getEvidenceMetadata: (params, auth) =>
237
238
  getEvidenceMetadata(buildContext(auth, '_evidence'), params.roleKey),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/environment-deploy-request.schema.json",
3
+ "$id": "aifabrix://schema/environment-deploy-request.schema.json",
4
4
  "title": "Environment Deploy Request",
5
5
  "description": "Request body for POST /api/v1/environments/{envKey}/deploy",
6
6
  "type": "object",
@@ -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.11",
10
+ "version":"2.9.12",
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-08-30T00:00:00Z",
15
+ "updatedAt":"2026-09-17T00:00:00Z",
16
16
  "compatibility":{
17
17
  "minVersion":"1.0.0",
18
18
  "maxVersion":"3.0.0",
@@ -31,6 +31,15 @@
31
31
  "external-system.schema.json"
32
32
  ],
33
33
  "changelog":[
34
+ {
35
+ "version":"2.9.12",
36
+ "date":"2026-09-17T00:00:00Z",
37
+ "changes":[
38
+ "Added cipOperation.shape enum value 'upsert' (manifest-declared only; composes the datasource's standard create + update via fetch.source datasource; existence-resolved branch with fail-closed per-action permissions). COM writeShape and published x-ai-intent gain the same value. 543 O-SHAPE updated.",
39
+ "Added optional sync.enabled (default true) to disable scheduled, manual, webhook, and syncBridge synchronization without disabling datasource reads. Added @monthly as the authoring default for new full-set schedules; existing schedule-less manifests remain valid and are not migrated. CIP $defs unchanged (543 visual N/A)."
40
+ ],
41
+ "breaking":false
42
+ },
34
43
  {
35
44
  "version":"2.9.11",
36
45
  "date":"2026-08-30T00:00:00Z",
@@ -862,6 +871,11 @@
862
871
  "type":"object",
863
872
  "description":"Record synchronization rules.",
864
873
  "properties":{
874
+ "enabled":{
875
+ "type":"boolean",
876
+ "description":"Whether synchronization is enabled for this datasource. False blocks scheduled, manual, webhook, and syncBridge synchronization while preserving normal datasource access.",
877
+ "default":true
878
+ },
865
879
  "mode":{
866
880
  "type":"string",
867
881
  "enum":[
@@ -874,6 +888,7 @@
874
888
  "schedule":{
875
889
  "type":"string",
876
890
  "description":"Cron for full-set (sync all) jobs. Supports 5-field cron syntax or @hourly/@daily/@weekly/@monthly.",
891
+ "default":"@monthly",
877
892
  "pattern":"^(@hourly|@daily|@weekly|@monthly|([0-9*/,-]+\\s+){4}[0-9*/,-]+)$"
878
893
  },
879
894
  "batchSize":{
@@ -2547,9 +2562,10 @@
2547
2562
  "enum":[
2548
2563
  "create",
2549
2564
  "update",
2550
- "delete"
2565
+ "delete",
2566
+ "upsert"
2551
2567
  ],
2552
- "description":"Manifest classification for custom operations when fetch step inference is insufficient. Resolved by resolve_cip_write_shape (precedence over fetch inference) and drives x-ai-intent, x-ai-sideEffects, and x-ai-contract.riskLevel on published OpenAPI. shape delete marks the operation as dangerous for agent policy (with core capability delete)."
2568
+ "description":"Manifest classification for custom operations when fetch step inference is insufficient. Resolved by resolve_cip_write_shape (precedence over fetch inference) and drives x-ai-intent, x-ai-sideEffects, and x-ai-contract.riskLevel on published OpenAPI. shape delete marks the operation as dangerous for agent policy (with core capability delete). shape upsert is never inferred: the operation must compose exactly this datasource's standard create and update operations as two fetch.source 'datasource' steps; at execution the generic CIP branch resolves record existence from validation.repeatingValues reject fields and runs exactly one half, enforcing that half's permissions fail-closed (no cross-action fallback)."
2553
2569
  },
2554
2570
  "steps":{
2555
2571
  "type":"array",
@@ -3467,4 +3483,3 @@
3467
3483
  },
3468
3484
  "additionalProperties":false
3469
3485
  }
3470
-
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://aifabrix.dev/schemas/infra-parameter.schema.json",
3
+ "$id": "aifabrix://schema/infra-parameter.schema.json",
4
4
  "title": "Infra parameter catalog",
5
5
  "type": "object",
6
6
  "required": ["version", "parameters"],
@@ -98,7 +98,8 @@ parameters:
98
98
  category: Redis
99
99
  sensitive: true
100
100
  valueSource: providerOutput
101
- requiredOnSystemOnboarding: true
101
+ # Not needed at first-time install: Redis is provisioned later (env deploy / TA), not at marketplace bootstrap.
102
+ requiredOnSystemOnboarding: false
102
103
  scope: system
103
104
  generator:
104
105
  type: literal
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/infrastructure-schema.json",
3
+ "$id": "aifabrix://schema/infrastructure-schema.json",
4
4
  "title": "AI Fabrix Installation Infrastructure Manifest",
5
5
  "description": "Committed, non-secret environment-infra.json for an installation workspace under infrastructure/<resource-group>/. Secrets appear only as valueFromEnv references.",
6
6
  "metadata": {
@@ -507,6 +507,7 @@
507
507
  "entraIdClientSecret",
508
508
  "entraIdTenantId",
509
509
  "licenseEdition",
510
+ "capacityProfile",
510
511
  "onboardingMode",
511
512
  "misoImageTag",
512
513
  "keycloakImageTag"
@@ -981,6 +982,51 @@
981
982
  }
982
983
  }
983
984
  },
985
+ "deployRegistryRoleAssignment": {
986
+ "type": "object",
987
+ "required": [
988
+ "value",
989
+ "source",
990
+ "description"
991
+ ],
992
+ "additionalProperties": false,
993
+ "properties": {
994
+ "value": {
995
+ "type": [
996
+ "string",
997
+ "boolean",
998
+ "number",
999
+ "object",
1000
+ "array"
1001
+ ]
1002
+ },
1003
+ "source": {
1004
+ "type": "string",
1005
+ "minLength": 1
1006
+ },
1007
+ "description": {
1008
+ "type": "string",
1009
+ "minLength": 1
1010
+ },
1011
+ "required": {
1012
+ "type": "boolean"
1013
+ },
1014
+ "restoreValue": {
1015
+ "type": [
1016
+ "string",
1017
+ "boolean",
1018
+ "number",
1019
+ "object",
1020
+ "array"
1021
+ ],
1022
+ "description": "Value to restore when credential mode no longer applies."
1023
+ },
1024
+ "restoreSource": {
1025
+ "type": "string",
1026
+ "minLength": 1
1027
+ }
1028
+ }
1029
+ },
984
1030
  "moriBaseUrl": {
985
1031
  "type": "object",
986
1032
  "required": [
@@ -1242,6 +1288,98 @@
1242
1288
  "type": "boolean"
1243
1289
  }
1244
1290
  }
1291
+ },
1292
+ "moriLicensePublicKey": {
1293
+ "type": "object",
1294
+ "required": [
1295
+ "value",
1296
+ "source",
1297
+ "description"
1298
+ ],
1299
+ "additionalProperties": false,
1300
+ "properties": {
1301
+ "value": {
1302
+ "type": [
1303
+ "string",
1304
+ "boolean",
1305
+ "number",
1306
+ "object",
1307
+ "array"
1308
+ ]
1309
+ },
1310
+ "source": {
1311
+ "type": "string",
1312
+ "minLength": 1
1313
+ },
1314
+ "description": {
1315
+ "type": "string",
1316
+ "minLength": 1
1317
+ },
1318
+ "required": {
1319
+ "type": "boolean"
1320
+ }
1321
+ }
1322
+ },
1323
+ "moriLicenseIssuer": {
1324
+ "type": "object",
1325
+ "required": [
1326
+ "value",
1327
+ "source",
1328
+ "description"
1329
+ ],
1330
+ "additionalProperties": false,
1331
+ "properties": {
1332
+ "value": {
1333
+ "type": [
1334
+ "string",
1335
+ "boolean",
1336
+ "number",
1337
+ "object",
1338
+ "array"
1339
+ ]
1340
+ },
1341
+ "source": {
1342
+ "type": "string",
1343
+ "minLength": 1
1344
+ },
1345
+ "description": {
1346
+ "type": "string",
1347
+ "minLength": 1
1348
+ },
1349
+ "required": {
1350
+ "type": "boolean"
1351
+ }
1352
+ }
1353
+ },
1354
+ "capacityProfile": {
1355
+ "type": "object",
1356
+ "required": [
1357
+ "value",
1358
+ "source",
1359
+ "description"
1360
+ ],
1361
+ "additionalProperties": false,
1362
+ "properties": {
1363
+ "value": {
1364
+ "enum": [
1365
+ "s",
1366
+ "m",
1367
+ "l",
1368
+ "xl"
1369
+ ]
1370
+ },
1371
+ "source": {
1372
+ "type": "string",
1373
+ "minLength": 1
1374
+ },
1375
+ "description": {
1376
+ "type": "string",
1377
+ "minLength": 1
1378
+ },
1379
+ "required": {
1380
+ "type": "boolean"
1381
+ }
1382
+ }
1245
1383
  }
1246
1384
  }
1247
1385
  },
@@ -1326,6 +1464,12 @@
1326
1464
  },
1327
1465
  "portalUrl": {
1328
1466
  "$ref": "#/definitions/output"
1467
+ },
1468
+ "azureOpenAiCreated": {
1469
+ "$ref": "#/definitions/output"
1470
+ },
1471
+ "azureOpenAiSkipReason": {
1472
+ "$ref": "#/definitions/output"
1329
1473
  }
1330
1474
  }
1331
1475
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/installation-environment.schema.json",
3
+ "$id": "aifabrix://schema/installation-environment.schema.json",
4
4
  "title": "AI Fabrix Installation Environment File",
5
5
  "description": "Committed, non-secret infrastructure/<resource-group>/environment-dev|tst|pro.json. Inherited shared values name environment-infra.json as their source; request is the exact non-secret Miso deploy projection.",
6
6
  "metadata": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/wizard-config.schema.json",
3
+ "$id": "aifabrix://schema/wizard-config.schema.json",
4
4
  "title": "AI Fabrix Wizard Configuration Schema",
5
5
  "description": "Schema for validating wizard.yaml configuration files for headless external system creation",
6
6
  "type": "object",
@@ -101,13 +101,8 @@ function createHomeAndSecretsPathFunctions(getConfigFn, saveConfigFn) {
101
101
  return;
102
102
  }
103
103
  const resolved = path.resolve(trimmed);
104
- await setPathConfig(
105
- getConfigFn,
106
- saveConfigFn,
107
- 'aifabrix-work',
108
- resolved,
109
- 'Work path must be a non-empty string'
110
- );
104
+ await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-work', resolved,
105
+ 'Work path must be a non-empty string');
111
106
  },
112
107
  async getAifabrixSecretsPath() {
113
108
  return getPathConfig(getConfigFn, 'aifabrix-secrets');
@@ -168,6 +163,17 @@ async function resolveEnvConfigPathToAbsolute(raw, getConfigFn) {
168
163
  return path.normalize(path.resolve(base, trimmed));
169
164
  }
170
165
 
166
+ function findBuilderDirectory(base) {
167
+ if (!base) return null;
168
+ const candidate = path.join(base, 'builder');
169
+ try {
170
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) return candidate;
171
+ } catch {
172
+ /* Missing or inaccessible builder directory. */
173
+ }
174
+ return null;
175
+ }
176
+
171
177
  function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
172
178
  return {
173
179
  /**
@@ -199,30 +205,16 @@ function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
199
205
  return path.dirname(absolute);
200
206
  }
201
207
  const pathsMod = require('./paths');
202
- const tryDir = (base) => {
203
- if (!base) {
204
- return null;
205
- }
206
- const b = path.join(base, 'builder');
207
- try {
208
- if (fs.existsSync(b) && fs.statSync(b).isDirectory()) {
209
- return b;
210
- }
211
- } catch {
212
- /* ignore */
213
- }
214
- return null;
215
- };
216
- const fromProject = tryDir(pathsMod.getProjectRoot());
208
+ const fromProject = findBuilderDirectory(pathsMod.getProjectRoot());
217
209
  if (fromProject) {
218
210
  return fromProject;
219
211
  }
220
- const fromEffective = tryDir(pathsMod.getBuilderRoot());
212
+ const fromEffective = findBuilderDirectory(pathsMod.getBuilderRoot());
221
213
  if (fromEffective) {
222
214
  return fromEffective;
223
215
  }
224
216
  const work = pathsMod.getAifabrixWork();
225
- return tryDir(work);
217
+ return findBuilderDirectory(work);
226
218
  }
227
219
  };
228
220
  }
@@ -368,11 +360,10 @@ function applySecretsUrlFromRemote(config) {
368
360
  config['aifabrix-secrets'] = `${base}/api/dev/secrets`;
369
361
  }
370
362
 
371
- function applySyncAndDockerFromHost(config) {
363
+ function applySyncHost(config) {
372
364
  const host = hostnameFromUrl(config['remote-server']);
373
365
  if (!host) return;
374
366
  if (!config['sync-ssh-host']) config['sync-ssh-host'] = host;
375
- if (!config['docker-endpoint']) config['docker-endpoint'] = `tcp://${host}:2376`;
376
367
  }
377
368
 
378
369
  async function mergeRemoteSettingsImpl(getConfigFn, saveConfigFn, settings) {
@@ -386,8 +377,13 @@ async function mergeRemoteSettingsImpl(getConfigFn, saveConfigFn, settings) {
386
377
  if (value === '') continue;
387
378
  config[key] = value;
388
379
  }
380
+ // Current settings are authoritative: never inherit Docker access from another identity.
381
+ if (typeof settings['docker-endpoint'] !== 'string' || !settings['docker-endpoint'].trim()) {
382
+ delete config['docker-endpoint'];
383
+ delete config['docker-tls-skip-verify'];
384
+ }
389
385
  applySecretsUrlFromRemote(config);
390
- applySyncAndDockerFromHost(config);
386
+ applySyncHost(config);
391
387
  await saveConfigFn(config);
392
388
  }
393
389
 
@@ -428,7 +424,6 @@ module.exports = {
428
424
  resolveEnvConfigPathToAbsolute,
429
425
  SETTINGS_RESPONSE_KEYS,
430
426
  applySecretsUrlFromRemote,
431
- applySyncAndDockerFromHost,
427
+ applySyncAndDockerFromHost: applySyncHost,
432
428
  hostnameFromUrl
433
429
  };
434
-
@@ -14,13 +14,15 @@ const { getRuntimeConfigFile } = require('../core/config-runtime-paths');
14
14
 
15
15
  const REGISTRY_CONFIG_KEY = 'registry';
16
16
  const HOST_LABEL = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?';
17
- const REGISTRY_HOST_RE = new RegExp(`^${HOST_LABEL}(?:\\.${HOST_LABEL})*(?::\\d{1,5})?$`);
17
+ const REGISTRY_HOST_RE = new RegExp(`^${HOST_LABEL}(?:\\.${HOST_LABEL})*$`);
18
18
 
19
19
  /**
20
- * Normalizes and validates a registry host (optionally with a port).
21
- * Rejects schemes, paths, embedded credentials, query/fragment text and whitespace.
20
+ * Normalizes and validates a registry host.
21
+ * Rejects schemes, paths, embedded credentials, query/fragment text and whitespace. A port is
22
+ * rejected because the deployment `image` contract (application-schema.json, shared with the
23
+ * Controller) accepts only one colon, which separates the tag.
22
24
  * @param {string} value - Raw registry value
23
- * @returns {string} Lower-case host[:port]
25
+ * @returns {string} Lower-case host
24
26
  * @throws {Error} When the value is empty or not a plain host
25
27
  */
26
28
  function normalizeRegistryHost(value) {
@@ -33,8 +35,13 @@ function normalizeRegistryHost(value) {
33
35
  throw new Error(`Invalid registry "${raw}": use a host name such as myregistry.azurecr.io (no scheme, path or credentials)`);
34
36
  }
35
37
  const host = withoutSlash.toLowerCase();
36
- const port = host.includes(':') ? Number(host.split(':')[1]) : null;
37
- if (!REGISTRY_HOST_RE.test(host) || (port !== null && (port < 1 || port > 65535))) {
38
+ if (host.includes(':')) {
39
+ throw new Error(
40
+ `Invalid registry "${raw}": a port is not supported because the deployed image reference keeps one colon for the tag. ` +
41
+ 'Use a host name such as myregistry.azurecr.io.'
42
+ );
43
+ }
44
+ if (!REGISTRY_HOST_RE.test(host)) {
38
45
  throw new Error(`Invalid registry "${raw}": use a host name such as myregistry.azurecr.io`);
39
46
  }
40
47
  return host;