@aifabrix/builder 2.9.0 → 2.10.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/lib/app-config.js CHANGED
@@ -46,23 +46,54 @@ async function generateVariablesYamlFile(appPath, appName, config) {
46
46
  }
47
47
  }
48
48
 
49
+ /**
50
+ * Generates env.template content for external systems based on authentication type
51
+ * @param {Object} config - Application configuration with authType and systemKey
52
+ * @param {string} appName - Application name (used as fallback for systemKey)
53
+ * @returns {string} Environment template content
54
+ */
55
+ function generateExternalSystemEnvTemplate(config, appName) {
56
+ const systemKey = config.systemKey || appName;
57
+ const authType = config.authType || 'apikey';
58
+ const lines = [
59
+ `# ${systemKey} ${authType.toUpperCase()} Configuration`,
60
+ '# These values are set via the Miso Controller interface or Dataplane portal',
61
+ '# Values are stored in Key Vault automatically by the platform',
62
+ ''
63
+ ];
64
+
65
+ if (authType === 'oauth2') {
66
+ lines.push('CLIENTID=kv://' + systemKey + '-clientidKeyVault');
67
+ lines.push('CLIENTSECRET=kv://' + systemKey + '-clientsecretKeyVault');
68
+ lines.push('TOKENURL=https://api.example.com/oauth/token');
69
+ lines.push('REDIRECT_URI=kv://' + systemKey + '-redirect-uriKeyVault');
70
+ } else if (authType === 'apikey') {
71
+ lines.push('API_KEY=kv://' + systemKey + '-api-keyKeyVault');
72
+ } else if (authType === 'basic') {
73
+ lines.push('USERNAME=kv://' + systemKey + '-usernameKeyVault');
74
+ lines.push('PASSWORD=kv://' + systemKey + '-passwordKeyVault');
75
+ }
76
+
77
+ return lines.join('\n');
78
+ }
79
+
49
80
  /**
50
81
  * Generates env.template file if it doesn't exist
51
82
  * @async
52
83
  * @param {string} appPath - Path to application directory
84
+ * @param {string} appName - Application name
53
85
  * @param {Object} config - Application configuration
54
86
  * @param {Object} existingEnv - Existing environment variables
55
87
  */
56
- async function generateEnvTemplateFile(appPath, config, existingEnv) {
57
- // Skip env.template for external type
58
- if (config.type === 'external') {
59
- return;
60
- }
61
-
88
+ async function generateEnvTemplateFile(appPath, appName, config, existingEnv) {
62
89
  const envTemplatePath = path.join(appPath, 'env.template');
63
90
  if (!(await fileExists(envTemplatePath))) {
64
91
  let envTemplate;
65
- if (existingEnv) {
92
+
93
+ if (config.type === 'external') {
94
+ // Generate env.template for external systems based on authType
95
+ envTemplate = generateExternalSystemEnvTemplate(config, appName);
96
+ } else if (existingEnv) {
66
97
  const envResult = await generateEnvTemplateFromReader(config, existingEnv);
67
98
  envTemplate = envResult.template;
68
99
 
@@ -155,7 +186,7 @@ async function generateDeployJsonFile(appPath, appName, config) {
155
186
  async function generateConfigFiles(appPath, appName, config, existingEnv) {
156
187
  try {
157
188
  await generateVariablesYamlFile(appPath, appName, config);
158
- await generateEnvTemplateFile(appPath, config, existingEnv);
189
+ await generateEnvTemplateFile(appPath, appName, config, existingEnv);
159
190
  await generateRbacYamlFile(appPath, appName, config);
160
191
  await generateDeployJsonFile(appPath, appName, config);
161
192
  await generateReadmeMdFile(appPath, appName, config);
@@ -21,6 +21,7 @@ const { updateEnvTemplate } = require('./utils/env-template');
21
21
  const { getOrRefreshDeviceToken } = require('./utils/token-manager');
22
22
  const { detectAppType } = require('./utils/paths');
23
23
  const { generateEnvFile } = require('./secrets');
24
+ const applicationSchema = require('./schema/application-schema.json');
24
25
 
25
26
  // Import createApp to auto-generate config if missing
26
27
  let createApp;
@@ -30,8 +31,17 @@ try {
30
31
  createApp = null;
31
32
  }
32
33
 
34
+ // Extract valid enum values from application schema
35
+ const validTypes = applicationSchema.properties.type.enum || [];
36
+ const validRegistryModes = applicationSchema.properties.registryMode.enum || [];
37
+ const portConstraints = {
38
+ minimum: applicationSchema.properties.port?.minimum || 1,
39
+ maximum: applicationSchema.properties.port?.maximum || 65535
40
+ };
41
+
33
42
  /**
34
43
  * Validation schema for application registration
44
+ * Validates according to application-schema.json
35
45
  */
36
46
  const registerApplicationSchema = {
37
47
  environmentId: (val) => {
@@ -44,10 +54,12 @@ const registerApplicationSchema = {
44
54
  if (!val || val.length < 1) {
45
55
  throw new Error('Application key is required');
46
56
  }
47
- if (val.length > 50) {
48
- throw new Error('Application key must be at most 50 characters');
57
+ const keyPattern = applicationSchema.properties.key.pattern;
58
+ const keyMaxLength = applicationSchema.properties.key.maxLength || 50;
59
+ if (val.length > keyMaxLength) {
60
+ throw new Error(`Application key must be at most ${keyMaxLength} characters`);
49
61
  }
50
- if (!/^[a-z0-9-]+$/.test(val)) {
62
+ if (keyPattern && !new RegExp(keyPattern).test(val)) {
51
63
  throw new Error('Application key must contain only lowercase letters, numbers, and hyphens');
52
64
  }
53
65
  return val;
@@ -56,25 +68,28 @@ const registerApplicationSchema = {
56
68
  if (!val || val.length < 1) {
57
69
  throw new Error('Display name is required');
58
70
  }
59
- if (val.length > 100) {
60
- throw new Error('Display name must be at most 100 characters');
71
+ const displayNameMaxLength = applicationSchema.properties.displayName.maxLength || 100;
72
+ if (val.length > displayNameMaxLength) {
73
+ throw new Error(`Display name must be at most ${displayNameMaxLength} characters`);
61
74
  }
62
75
  return val;
63
76
  },
64
77
  description: (val) => val || undefined,
65
78
  configuration: (val) => {
66
- const validTypes = ['webapp', 'api', 'service', 'functionapp'];
67
- const validRegistryModes = ['acr', 'external', 'public'];
68
-
69
79
  if (!val || !val.type || !validTypes.includes(val.type)) {
70
- throw new Error('Configuration type must be one of: webapp, api, service, functionapp');
80
+ throw new Error(`Configuration type must be one of: ${validTypes.join(', ')}`);
71
81
  }
72
82
  if (!val.registryMode || !validRegistryModes.includes(val.registryMode)) {
73
- throw new Error('Registry mode must be one of: acr, external, public');
83
+ throw new Error(`Registry mode must be one of: ${validRegistryModes.join(', ')}`);
74
84
  }
75
- if (val.port !== undefined) {
76
- if (!Number.isInteger(val.port) || val.port < 1 || val.port > 65535) {
77
- throw new Error('Port must be an integer between 1 and 65535');
85
+ // Port validation: skip for external type (external systems don't need ports)
86
+ // For other types, port is required and must be valid
87
+ if (val.type !== 'external') {
88
+ if (val.port === undefined || val.port === null) {
89
+ throw new Error('Port is required for non-external application types');
90
+ }
91
+ if (!Number.isInteger(val.port) || val.port < portConstraints.minimum || val.port > portConstraints.maximum) {
92
+ throw new Error(`Port must be an integer between ${portConstraints.minimum} and ${portConstraints.maximum}`);
78
93
  }
79
94
  }
80
95
  return val;
@@ -359,7 +359,13 @@ async function handleDeviceCodeLogin(controllerUrl, environment, offline, scope)
359
359
  );
360
360
 
361
361
  } catch (deviceError) {
362
- logger.error(chalk.red(`\n❌ Device code flow failed: ${deviceError.message}`));
362
+ // Display formatted error if available (includes detailed validation info)
363
+ if (deviceError.formattedError) {
364
+ logger.error(chalk.red('\n❌ Device code flow failed:'));
365
+ logger.log(deviceError.formattedError);
366
+ } else {
367
+ logger.error(chalk.red(`\n❌ Device code flow failed: ${deviceError.message}`));
368
+ }
363
369
  process.exit(1);
364
370
  }
365
371
  }
@@ -11,6 +11,10 @@ environments:
11
11
  MISO_PORT: 3000
12
12
  KEYCLOAK_HOST: keycloak
13
13
  KEYCLOAK_PORT: 8082
14
+ NODE_ENV: production
15
+ PYTHONUNBUFFERED: 1
16
+ PYTHONDONTWRITEBYTECODE: 1
17
+ PYTHONIOENCODING: utf-8
14
18
 
15
19
  local:
16
20
  DB_HOST: localhost
@@ -20,4 +24,8 @@ environments:
20
24
  MISO_HOST: localhost
21
25
  MISO_PORT: 3010
22
26
  KEYCLOAK_HOST: localhost
23
- KEYCLOAK_PORT: 8082
27
+ KEYCLOAK_PORT: 8082
28
+ NODE_ENV: development
29
+ PYTHONUNBUFFERED: 1
30
+ PYTHONDONTWRITEBYTECODE: 1
31
+ PYTHONIOENCODING: utf-8
package/lib/templates.js CHANGED
@@ -20,11 +20,16 @@ function generateVariablesYaml(appName, config) {
20
20
 
21
21
  // For external type, create minimal variables.yaml
22
22
  if (appType === 'external') {
23
+ // Use config values if provided, otherwise use defaults consistent with prompts
24
+ const systemKey = config.systemKey || appName;
25
+ const systemDisplayName = config.systemDisplayName || displayName;
26
+ const systemDescription = config.systemDescription || `External system integration for ${appName}`;
27
+
23
28
  const variables = {
24
29
  app: {
25
- key: appName,
26
- displayName: displayName,
27
- description: `${appName.replace(/-/g, ' ')} external system`,
30
+ key: systemKey,
31
+ displayName: systemDisplayName,
32
+ description: systemDescription,
28
33
  type: 'external'
29
34
  },
30
35
  deployment: {
@@ -134,13 +139,31 @@ function generateVariablesYaml(appName, config) {
134
139
  */
135
140
  function buildCoreEnv(config) {
136
141
  return {
137
- 'NODE_ENV': 'development',
142
+ 'NODE_ENV': '${NODE_ENV}',
138
143
  'PORT': config.port || 3000,
139
144
  'APP_NAME': config.appName || 'myapp',
140
145
  'LOG_LEVEL': 'info'
141
146
  };
142
147
  }
143
148
 
149
+ /**
150
+ * Builds Python-specific environment variables
151
+ * @param {Object} config - Configuration options
152
+ * @returns {Object} Python environment variables
153
+ */
154
+ function buildPythonEnv(config) {
155
+ const language = config.language || 'typescript';
156
+ if (language !== 'python') {
157
+ return {};
158
+ }
159
+
160
+ return {
161
+ 'PYTHONUNBUFFERED': '${PYTHONUNBUFFERED}',
162
+ 'PYTHONDONTWRITEBYTECODE': '${PYTHONDONTWRITEBYTECODE}',
163
+ 'PYTHONIOENCODING': '${PYTHONIOENCODING}'
164
+ };
165
+ }
166
+
144
167
  function buildDatabaseEnv(config) {
145
168
  if (!config.database) {
146
169
  return {};
@@ -207,8 +230,9 @@ function buildMonitoringEnv(config) {
207
230
  return {
208
231
  'MISO_CONTROLLER_URL': config.controllerUrl || 'https://controller.aifabrix.ai',
209
232
  'MISO_ENVIRONMENT': 'dev',
210
- 'MISO_CLIENTID': 'kv://miso-clientid',
211
- 'MISO_CLIENTSECRET': 'kv://miso-clientsecret'
233
+ 'MISO_CLIENTID': 'kv://miso-controller-client-idKeyVault',
234
+ 'MISO_CLIENTSECRET': 'kv://miso-controller-client-secretKeyVault',
235
+ 'MISO_WEB_SERVER_URL': 'kv://miso-controller-web-server-url'
212
236
  };
213
237
  }
214
238
 
@@ -220,10 +244,17 @@ function buildMonitoringEnv(config) {
220
244
  function addCoreVariables(lines, envVars) {
221
245
  Object.entries(envVars).forEach(([key, value]) => {
222
246
  if (key.startsWith('NODE_ENV') || key.startsWith('PORT') ||
223
- key.startsWith('APP_NAME') || key.startsWith('LOG_LEVEL')) {
247
+ key.startsWith('APP_NAME') || key.startsWith('LOG_LEVEL') ||
248
+ key.startsWith('PYTHON')) {
224
249
  lines.push(`${key}=${value}`);
225
250
  }
226
251
  });
252
+
253
+ // Add ALLOWED_ORIGINS and WEB_SERVER_URL after PORT variable
254
+ // ALLOWED_ORIGINS: My application public address
255
+ lines.push('ALLOWED_ORIGINS=http://localhost:*,');
256
+ // WEB_SERVER_URL: Miso public address (uses ${PORT} template variable)
257
+ lines.push('WEB_SERVER_URL=http://localhost:${PORT},');
227
258
  }
228
259
 
229
260
  function addMonitoringSection(lines, envVars) {
@@ -232,6 +263,10 @@ function addMonitoringSection(lines, envVars) {
232
263
  lines.push(`MISO_ENVIRONMENT=${envVars['MISO_ENVIRONMENT']}`);
233
264
  lines.push(`MISO_CLIENTID=${envVars['MISO_CLIENTID']}`);
234
265
  lines.push(`MISO_CLIENTSECRET=${envVars['MISO_CLIENTSECRET']}`);
266
+ // MISO_WEB_SERVER_URL: Miso public address
267
+ if (envVars['MISO_WEB_SERVER_URL']) {
268
+ lines.push(`MISO_WEB_SERVER_URL=${envVars['MISO_WEB_SERVER_URL']}`);
269
+ }
235
270
  }
236
271
 
237
272
  function addDatabaseSection(lines, envVars) {
@@ -316,6 +351,7 @@ function addAuthenticationSection(lines, envVars) {
316
351
  function generateEnvTemplate(config, existingEnv = {}) {
317
352
  const envVars = {
318
353
  ...buildCoreEnv(config),
354
+ ...buildPythonEnv(config),
319
355
  ...buildDatabaseEnv(config),
320
356
  ...buildRedisEnv(config),
321
357
  ...buildStorageEnv(config),
@@ -73,13 +73,11 @@ function formatPermissionError(errorData) {
73
73
 
74
74
  const requiredPerms = extractRequiredPermissions(errorData);
75
75
  addPermissionList(lines, requiredPerms, 'Required permissions');
76
-
77
76
  const requestUrl = errorData.instance || errorData.url;
78
77
  const method = errorData.method || 'POST';
79
78
  if (requestUrl) {
80
79
  lines.push(chalk.gray(`Request: ${method} ${requestUrl}`));
81
80
  }
82
-
83
81
  if (errorData.correlationId) {
84
82
  lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
85
83
  }
@@ -97,18 +95,27 @@ function formatValidationError(errorData) {
97
95
  lines.push(chalk.red('❌ Validation Error\n'));
98
96
 
99
97
  // Handle RFC 7807 Problem Details format
100
- // Priority: detail > title > message
98
+ // Priority: detail > title > errorDescription > message > error
101
99
  if (errorData.detail) {
102
100
  lines.push(chalk.yellow(errorData.detail));
103
101
  lines.push('');
104
102
  } else if (errorData.title) {
105
103
  lines.push(chalk.yellow(errorData.title));
106
104
  lines.push('');
105
+ } else if (errorData.errorDescription) {
106
+ // Handle Keycloak-style error format
107
+ lines.push(chalk.yellow(errorData.errorDescription));
108
+ if (errorData.error) {
109
+ lines.push(chalk.gray(`Error code: ${errorData.error}`));
110
+ }
111
+ lines.push('');
107
112
  } else if (errorData.message) {
108
113
  lines.push(chalk.yellow(errorData.message));
109
114
  lines.push('');
115
+ } else if (errorData.error) {
116
+ lines.push(chalk.yellow(errorData.error));
117
+ lines.push('');
110
118
  }
111
-
112
119
  // Handle errors array - this is the most important part
113
120
  if (errorData.errors && Array.isArray(errorData.errors) && errorData.errors.length > 0) {
114
121
  lines.push(chalk.yellow('Validation errors:'));
@@ -124,17 +131,14 @@ function formatValidationError(errorData) {
124
131
  });
125
132
  lines.push('');
126
133
  }
127
-
128
134
  // Show instance (endpoint) if available (RFC 7807)
129
135
  if (errorData.instance) {
130
136
  lines.push(chalk.gray(`Endpoint: ${errorData.instance}`));
131
137
  }
132
-
133
138
  // Show correlation ID if available
134
139
  if (errorData.correlationId) {
135
140
  lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
136
141
  }
137
-
138
142
  return lines.join('\n');
139
143
  }
140
144
  /**
@@ -145,7 +149,6 @@ function formatValidationError(errorData) {
145
149
  function formatAuthenticationError(errorData) {
146
150
  const lines = [];
147
151
  lines.push(chalk.red('❌ Authentication Failed\n'));
148
-
149
152
  if (errorData.message) {
150
153
  lines.push(chalk.yellow(errorData.message));
151
154
  } else {
@@ -153,11 +156,9 @@ function formatAuthenticationError(errorData) {
153
156
  }
154
157
  lines.push('');
155
158
  lines.push(chalk.gray('Run: aifabrix login'));
156
-
157
159
  if (errorData.correlationId) {
158
160
  lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
159
161
  }
160
-
161
162
  return lines.join('\n');
162
163
  }
163
164
  /**
@@ -379,7 +380,7 @@ function createErrorResult(type, message, formatted, data) {
379
380
  * @returns {string} Error message
380
381
  */
381
382
  function getErrorMessage(errorData, defaultMessage) {
382
- return errorData.detail || errorData.title || errorData.message || defaultMessage;
383
+ return errorData.detail || errorData.title || errorData.errorDescription || errorData.message || errorData.error || defaultMessage;
383
384
  }
384
385
 
385
386
  /**
@@ -486,7 +487,6 @@ function formatApiError(apiResponse) {
486
487
  const parsed = parseErrorResponse(errorResponse, statusCode, isNetworkError);
487
488
  return parsed.formatted;
488
489
  }
489
-
490
490
  module.exports = {
491
491
  parseErrorResponse,
492
492
  formatApiError,
@@ -140,15 +140,61 @@ function parseTokenResponse(response) {
140
140
  };
141
141
  }
142
142
 
143
+ /**
144
+ * Creates a validation error with detailed information
145
+ * @function createValidationError
146
+ * @param {Object} response - Full API response object
147
+ * @returns {Error} Validation error with formattedError and errorData attached
148
+ */
149
+ function createValidationError(response) {
150
+ const validationError = new Error('Token polling failed: Validation error');
151
+
152
+ // Attach formatted error if available (includes detailed validation info with ANSI colors)
153
+ if (response && response.formattedError) {
154
+ validationError.formattedError = response.formattedError;
155
+ validationError.message = `Token polling failed:\n${response.formattedError}`;
156
+ }
157
+
158
+ // Attach error data for programmatic access
159
+ if (response && response.errorData) {
160
+ validationError.errorData = response.errorData;
161
+ validationError.errorType = response.errorType || 'validation';
162
+
163
+ // Build detailed message if formattedError not available
164
+ if (!validationError.formattedError) {
165
+ const errorData = response.errorData;
166
+ const detail = errorData.detail || errorData.title || errorData.message || 'Validation error';
167
+ let errorMsg = `Token polling failed: ${detail}`;
168
+ // Add validation errors if available
169
+ if (errorData.errors && Array.isArray(errorData.errors) && errorData.errors.length > 0) {
170
+ errorMsg += '\n\nValidation errors:';
171
+ errorData.errors.forEach(err => {
172
+ const field = err.field || err.path || 'validation';
173
+ const message = err.message || 'Invalid value';
174
+ if (field === 'validation' || field === 'unknown') {
175
+ errorMsg += `\n • ${message}`;
176
+ } else {
177
+ errorMsg += `\n • ${field}: ${message}`;
178
+ }
179
+ });
180
+ }
181
+ validationError.message = errorMsg;
182
+ }
183
+ }
184
+
185
+ return validationError;
186
+ }
187
+
143
188
  /**
144
189
  * Handles polling errors
145
190
  * @function handlePollingErrors
146
191
  * @param {string} error - Error code
147
192
  * @param {number} status - HTTP status code
193
+ * @param {Object} response - Full API response object (for accessing formattedError and errorData)
148
194
  * @throws {Error} For fatal errors
149
195
  * @returns {boolean} True if should continue polling
150
196
  */
151
- function handlePollingErrors(error, status) {
197
+ function handlePollingErrors(error, status, response) {
152
198
  if (error === 'authorization_pending' || status === 202) {
153
199
  return true;
154
200
  }
@@ -166,6 +212,11 @@ function handlePollingErrors(error, status) {
166
212
  return true;
167
213
  }
168
214
 
215
+ // Handle validation errors with detailed message
216
+ if (error === 'validation_error' || status === 400) {
217
+ throw createValidationError(response);
218
+ }
219
+
169
220
  throw new Error(`Token polling failed: ${error}`);
170
221
  }
171
222
 
@@ -187,6 +238,18 @@ async function waitForNextPoll(interval, slowDown) {
187
238
  * @returns {string} Error code or 'Unknown error'
188
239
  */
189
240
  function extractPollingError(response) {
241
+ // Check for structured error data first (from api-error-handler)
242
+ if (response.errorData) {
243
+ const errorData = response.errorData;
244
+ // For validation errors, return the error type so we can handle it specially
245
+ if (response.errorType === 'validation') {
246
+ return 'validation_error';
247
+ }
248
+ // Return the error message from structured error
249
+ return errorData.detail || errorData.title || errorData.message || errorData.error || response.error || 'Unknown error';
250
+ }
251
+
252
+ // Fallback to original extraction logic
190
253
  const apiResponse = response.data || {};
191
254
  const errorData = typeof apiResponse === 'object' ? apiResponse : {};
192
255
  return errorData.error || response.error || 'Unknown error';
@@ -229,7 +292,7 @@ async function processPollingResponse(response, interval) {
229
292
  }
230
293
 
231
294
  const error = extractPollingError(response);
232
- const shouldContinue = handlePollingErrors(error, response.status);
295
+ const shouldContinue = handlePollingErrors(error, response.status, response);
233
296
 
234
297
  if (shouldContinue) {
235
298
  const slowDown = error === 'slow_down';
@@ -20,10 +20,11 @@ const logger = require('../utils/logger');
20
20
  * @param {string} appKey - Application key
21
21
  * @param {string} clientIdKey - Secret key for client ID (e.g., 'myapp-client-idKeyVault')
22
22
  * @param {string} clientSecretKey - Secret key for client secret (e.g., 'myapp-client-secretKeyVault')
23
- * @param {string} controllerUrl - Controller URL (e.g., 'http://localhost:3010' or 'https://controller.aifabrix.ai')
23
+ * @param {string} _controllerUrl - Controller URL (e.g., 'http://localhost:3010' or 'https://controller.aifabrix.ai')
24
+ * Note: This parameter is accepted for compatibility but the template format http://${MISO_HOST}:${MISO_PORT} is used instead
24
25
  * @returns {Promise<void>} Resolves when template is updated
25
26
  */
26
- async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey, controllerUrl) {
27
+ async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey, _controllerUrl) {
27
28
  const envTemplatePath = path.join(process.cwd(), 'builder', appKey, 'env.template');
28
29
 
29
30
  if (!fsSync.existsSync(envTemplatePath)) {
@@ -49,7 +50,7 @@ async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey, controlle
49
50
  }
50
51
 
51
52
  if (hasControllerUrl) {
52
- content = content.replace(/^MISO_CONTROLLER_URL\s*=.*$/m, `MISO_CONTROLLER_URL=${controllerUrl}`);
53
+ content = content.replace(/^MISO_CONTROLLER_URL\s*=.*$/m, 'MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}');
53
54
  }
54
55
 
55
56
  // Add missing entries
@@ -62,7 +63,7 @@ async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey, controlle
62
63
  missingEntries.push(`MISO_CLIENTSECRET=kv://${clientSecretKey}`);
63
64
  }
64
65
  if (!hasControllerUrl) {
65
- missingEntries.push(`MISO_CONTROLLER_URL=${controllerUrl}`);
66
+ missingEntries.push('MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}');
66
67
  }
67
68
 
68
69
  const misoSection = `# MISO Application Client Credentials (per application)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.9.0",
3
+ "version": "2.10.1",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -26,13 +26,19 @@ KC_HTTP_MANAGEMENT_HEALTH_ENABLED=false
26
26
  # =============================================================================
27
27
  # DATABASE CONFIGURATION
28
28
  # =============================================================================
29
+
30
+ # MISO Application Client Credentials (per application)
31
+ MISO_CLIENTID=kv://keycloak-client-idKeyVault
32
+ MISO_CLIENTSECRET=kv://keycloak-client-secretKeyVault
33
+ MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}
34
+ MISO_WEB_SERVER_URL=kv://miso-controller-web-server-url
35
+
29
36
  # Connects to postgres service in Docker network (postgres) or localhost (local)
30
37
 
31
38
  KC_DB=postgres
32
39
  KC_DB_URL_HOST=${DB_HOST}
33
- KC_DB_URL_PORT=5432
40
+ KC_DB_URL_PORT=${DB_PORT}
34
41
  KC_DB_URL_DATABASE=keycloak
35
42
  KC_DB_USERNAME=keycloak_user
36
43
  KC_DB_PASSWORD=kv://databases-keycloak-0-passwordKeyVault
37
44
  DB_0_PASSWORD=kv://databases-keycloak-0-passwordKeyVault
38
-
@@ -1,14 +1,14 @@
1
1
  # Application Metadata
2
2
  app:
3
3
  key: keycloak
4
- displayName: "AI Fabrix Keycloak"
5
- description: "Identity and Access Management"
4
+ displayName: 'AI Fabrix Keycloak'
5
+ description: 'Identity and Access Management'
6
6
  type: webapp
7
7
 
8
8
  # Image Configuration
9
9
  image:
10
10
  name: aifabrix/keycloak
11
- tag: "latest"
11
+ tag: 'latest'
12
12
  registry: devflowiseacr.azurecr.io
13
13
  registryMode: acr
14
14
 
@@ -28,13 +28,21 @@ ONBOARDING_ADMIN_EMAIL=kv://miso-controller-admin-emailKeyVault
28
28
  # APPLICATION ENVIRONMENT
29
29
  # =============================================================================
30
30
 
31
- NODE_ENV=development
32
- PORT=3000
31
+ # NODE_ENV: production for Docker (serves pre-built static files), development for local dev
32
+ # In Docker, this should be production to prevent Vite dev server initialization
33
+ NODE_ENV=${NODE_ENV}
34
+ PORT=${MISO_PORT}
33
35
  AUTO_CREATE_TABLES=true
34
36
  FAST_STARTUP=false
35
- ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
37
+ ALLOWED_ORIGINS=http://localhost:*
36
38
  ENABLE_API_DOCS=true
37
39
 
40
+ # Rate Limiting Configuration (for local development)
41
+ # Set DISABLE_RATE_LIMIT=true to disable rate limiting entirely (local development only)
42
+ DISABLE_RATE_LIMIT=true
43
+ # RATE_LIMIT_WINDOW_MS=900000 # 15 minutes in milliseconds (default: 900000)
44
+ # RATE_LIMIT_MAX=100 # Max requests per window (default: 100)
45
+
38
46
  # Package Version (auto-set by npm/pnpm, optional override)
39
47
  # npm_package_version=1.0.0
40
48
 
@@ -72,6 +80,7 @@ REDIS_PERMISSIONS_TTL=900
72
80
 
73
81
  KEYCLOAK_REALM=aifabrix
74
82
  KEYCLOAK_SERVER_URL=kv://keycloak-server-urlKeyVault
83
+ KEYCLOAK_PUBLIC_SERVER_URL=kv://keycloak-public-server-urlKeyVault
75
84
  KEYCLOAK_CLIENT_ID=miso-controller
76
85
  KEYCLOAK_CLIENT_SECRET=kv://keycloak-client-secretKeyVault
77
86
  KEYCLOAK_ADMIN_USERNAME=admin
@@ -96,8 +105,9 @@ AZURE_SERVICE_NAME=kv://azure-service-nameKeyVault
96
105
  AZURE_CLIENT_ID=kv://azure-client-idKeyVault
97
106
  AZURE_CLIENT_SECRET=kv://azure-client-secretKeyVault
98
107
 
99
- # Mock Mode (set to false for production)
100
- MOCK=true
108
+ # Mock Mode (defaults to false - set to true only for testing/development)
109
+ # Set MOCK=true to prevent actual Azure resource creation (for testing)
110
+ MOCK=false
101
111
 
102
112
  # =============================================================================
103
113
  # SECURITY & ENCRYPTION
@@ -117,11 +127,14 @@ API_KEY=kv://miso-controller-api-key-secretKeyVault
117
127
  # =============================================================================
118
128
 
119
129
  # MISO Controller URL
120
- MISO_CONTROLLER_URL=kv://miso-controller-url
121
-
122
- # Web Server URL (for OpenAPI documentation server URLs)
123
- # Used to generate correct server URLs in OpenAPI spec
124
- WEB_SERVER_URL=kv://miso-controller-web-server-url
130
+ MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}
131
+
132
+ # Web Server URL (for OpenAPI documentation server URLs and Keycloak callbacks)
133
+ # This is the PUBLIC-FACING URL that browsers/users access (e.g., http://localhost:3100)
134
+ # Used to generate correct server URLs in OpenAPI spec and Keycloak callback URLs
135
+ # For Docker: use localhost with mapped port (e.g., localhost:3100)
136
+ # For production: use public domain (e.g., https://miso.example.com)
137
+ MISO_WEB_SERVER_URL=kv://miso-controller-web-server-url
125
138
 
126
139
  # MISO Environment Configuration (miso, dev, tst, pro)
127
140
  MISO_ENVIRONMENT=miso
@@ -1,230 +1,280 @@
1
1
  roles:
2
- - name: "AI Fabrix Platform Admin"
3
- value: "aifabrix-platform-admin"
4
- description: "Full platform infrastructure management and enterprise controller access"
5
- Groups: ["AI-Fabrix-Platform-Admins"]
6
-
7
- - name: "AI Fabrix Security Admin"
8
- value: "aifabrix-security-admin"
9
- description: "Security and compliance management for enterprise controller"
10
- Groups: ["AI-Fabrix-Security-Admins"]
11
-
12
- - name: "AI Fabrix Infrastructure Admin"
13
- value: "aifabrix-infrastructure-admin"
14
- description: "Infrastructure deployment and management across environments"
15
- Groups: ["AI-Fabrix-Infrastructure-Admins"]
16
-
17
- - name: "AI Fabrix Deployment Admin"
18
- value: "aifabrix-deployment-admin"
19
- description: "Application deployment orchestration and environment management"
20
- Groups: ["AI-Fabrix-Deployment-Admins"]
21
-
22
- - name: "AI Fabrix Compliance Admin"
23
- value: "aifabrix-compliance-admin"
24
- description: "ISO 27001 compliance monitoring and audit management"
25
- Groups: ["AI-Fabrix-Compliance-Admins"]
26
-
27
- - name: "AI Fabrix Developer"
28
- value: "aifabrix-developer"
29
- description: "Developer access to deploy applications via GitHub Actions"
30
- Groups: ["AI-Fabrix-Developers"]
31
-
32
- - name: "AI Fabrix Observer"
33
- value: "aifabrix-observer"
34
- description: "Read-only access to monitoring, logs, and compliance reports"
35
- Groups: ["AI-Fabrix-Observers"]
2
+ - name: 'AI Fabrix Platform Admin'
3
+ value: 'aifabrix-platform-admin'
4
+ description: 'Full platform infrastructure management and enterprise controller access'
5
+ Groups: ['AI-Fabrix-Platform-Admins']
6
+
7
+ - name: 'AI Fabrix Security Admin'
8
+ value: 'aifabrix-security-admin'
9
+ description: 'Security and compliance management for enterprise controller'
10
+ Groups: ['AI-Fabrix-Security-Admins']
11
+
12
+ - name: 'AI Fabrix Infrastructure Admin'
13
+ value: 'aifabrix-infrastructure-admin'
14
+ description: 'Infrastructure deployment and management across environments'
15
+ Groups: ['AI-Fabrix-Infrastructure-Admins']
16
+
17
+ - name: 'AI Fabrix Deployment Admin'
18
+ value: 'aifabrix-deployment-admin'
19
+ description: 'Application deployment orchestration and environment management'
20
+ Groups: ['AI-Fabrix-Deployment-Admins']
21
+
22
+ - name: 'AI Fabrix Compliance Admin'
23
+ value: 'aifabrix-compliance-admin'
24
+ description: 'ISO 27001 compliance monitoring and audit management'
25
+ Groups: ['AI-Fabrix-Compliance-Admins']
26
+
27
+ - name: 'AI Fabrix Developer'
28
+ value: 'aifabrix-developer'
29
+ description: 'Developer access to deploy applications via GitHub Actions'
30
+ Groups: ['AI-Fabrix-Developers']
31
+
32
+ - name: 'AI Fabrix Observer'
33
+ value: 'aifabrix-observer'
34
+ description: 'Read-only access to monitoring, logs, and compliance reports'
35
+ Groups: ['AI-Fabrix-Observers']
36
36
 
37
37
  permissions:
38
38
  # Service User Management
39
- - name: "service-user:create"
40
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
41
- description: "Create service users and API clients"
42
-
43
- - name: "service-user:read"
44
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-observer"]
45
- description: "View service users and their configurations"
46
-
47
- - name: "service-user:update"
48
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
49
- description: "Update service user configurations and regenerate secrets"
50
-
51
- - name: "service-user:delete"
52
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
53
- description: "Deactivate service users"
54
-
39
+ - name: 'service-user:create'
40
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
41
+ description: 'Create service users and API clients'
42
+
43
+ - name: 'service-user:read'
44
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-observer']
45
+ description: 'View service users and their configurations'
46
+
47
+ - name: 'service-user:update'
48
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
49
+ description: 'Update service user configurations and regenerate secrets'
50
+
51
+ - name: 'service-user:delete'
52
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
53
+ description: 'Deactivate service users'
54
+
55
55
  # User Management
56
- - name: "users:create"
57
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
58
- description: "Create new users"
59
-
60
- - name: "users:read"
61
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-observer"]
62
- description: "View user information and profiles"
63
-
64
- - name: "users:update"
65
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
66
- description: "Update user information and manage group memberships"
67
-
68
- - name: "users:delete"
69
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
70
- description: "Delete users"
71
-
56
+ - name: 'users:create'
57
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
58
+ description: 'Create new users'
59
+
60
+ - name: 'users:read'
61
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-observer']
62
+ description: 'View user information and profiles'
63
+
64
+ - name: 'users:update'
65
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
66
+ description: 'Update user information and manage group memberships'
67
+
68
+ - name: 'users:delete'
69
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
70
+ description: 'Delete users'
71
+
72
72
  # Group Management
73
- - name: "groups:create"
74
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
75
- description: "Create new groups"
76
-
77
- - name: "groups:read"
78
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-observer"]
79
- description: "View group information and members"
80
-
81
- - name: "groups:update"
82
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
83
- description: "Update group information"
84
-
85
- - name: "groups:delete"
86
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
87
- description: "Delete groups"
88
-
73
+ - name: 'groups:create'
74
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
75
+ description: 'Create new groups'
76
+
77
+ - name: 'groups:read'
78
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-observer']
79
+ description: 'View group information and members'
80
+
81
+ - name: 'groups:update'
82
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
83
+ description: 'Update group information'
84
+
85
+ - name: 'groups:delete'
86
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
87
+ description: 'Delete groups'
88
+
89
89
  # Administrative Permissions
90
- - name: "admin:read"
91
- roles: ["aifabrix-platform-admin"]
92
- description: "Administrative read access to all resources"
93
-
94
- - name: "admin:write"
95
- roles: ["aifabrix-platform-admin"]
96
- description: "Administrative write access to all resources"
97
-
98
- - name: "admin:delete"
99
- roles: ["aifabrix-platform-admin"]
100
- description: "Administrative delete access to all resources"
101
-
90
+ - name: 'admin:read'
91
+ roles: ['aifabrix-platform-admin']
92
+ description: 'Administrative read access to all resources'
93
+
94
+ - name: 'admin:write'
95
+ roles: ['aifabrix-platform-admin']
96
+ description: 'Administrative write access to all resources'
97
+
98
+ - name: 'admin:delete'
99
+ roles: ['aifabrix-platform-admin']
100
+ description: 'Administrative delete access to all resources'
101
+
102
102
  # Template Applications (environment = null)
103
- - name: "applications:create"
104
- roles: ["aifabrix-platform-admin", "aifabrix-infrastructure-admin", "aifabrix-deployment-admin"]
105
- description: "Register new application templates"
106
-
107
- - name: "applications:read"
108
- roles: ["aifabrix-platform-admin", "aifabrix-infrastructure-admin", "aifabrix-deployment-admin", "aifabrix-developer", "aifabrix-observer"]
109
- description: "View application templates"
110
-
111
- - name: "applications:update"
112
- roles: ["aifabrix-platform-admin", "aifabrix-infrastructure-admin", "aifabrix-deployment-admin"]
113
- description: "Update application templates"
114
-
115
- - name: "applications:delete"
116
- roles: ["aifabrix-platform-admin", "aifabrix-infrastructure-admin"]
117
- description: "Remove application templates"
118
-
103
+ - name: 'applications:create'
104
+ roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin', 'aifabrix-deployment-admin']
105
+ description: 'Register new application templates'
106
+
107
+ - name: 'applications:read'
108
+ roles:
109
+ [
110
+ 'aifabrix-platform-admin',
111
+ 'aifabrix-infrastructure-admin',
112
+ 'aifabrix-deployment-admin',
113
+ 'aifabrix-developer',
114
+ 'aifabrix-observer'
115
+ ]
116
+ description: 'View application templates'
117
+
118
+ - name: 'applications:update'
119
+ roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin', 'aifabrix-deployment-admin']
120
+ description: 'Update application templates'
121
+
122
+ - name: 'applications:delete'
123
+ roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin']
124
+ description: 'Remove application templates'
125
+
119
126
  # Environments
120
- - name: "environments:create"
121
- roles: ["aifabrix-platform-admin", "aifabrix-infrastructure-admin"]
122
- description: "Create new environments (dev, tst, pro, miso)"
123
-
124
- - name: "environments:read"
125
- roles: ["aifabrix-platform-admin", "aifabrix-infrastructure-admin", "aifabrix-deployment-admin", "aifabrix-developer", "aifabrix-observer"]
126
- description: "View environments and their status"
127
-
128
- - name: "environments:update"
129
- roles: ["aifabrix-platform-admin", "aifabrix-infrastructure-admin"]
130
- description: "Update environment configuration"
131
-
132
- - name: "environments:delete"
133
- roles: ["aifabrix-platform-admin", "aifabrix-infrastructure-admin"]
134
- description: "Delete environments"
135
-
127
+ - name: 'environments:create'
128
+ roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin']
129
+ description: 'Create new environments (dev, tst, pro, miso)'
130
+
131
+ - name: 'environments:read'
132
+ roles:
133
+ [
134
+ 'aifabrix-platform-admin',
135
+ 'aifabrix-infrastructure-admin',
136
+ 'aifabrix-deployment-admin',
137
+ 'aifabrix-developer',
138
+ 'aifabrix-observer'
139
+ ]
140
+ description: 'View environments and their status'
141
+
142
+ - name: 'environments:update'
143
+ roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin']
144
+ description: 'Update environment configuration'
145
+
146
+ - name: 'environments:delete'
147
+ roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin']
148
+ description: 'Delete environments'
149
+
136
150
  # Environment Applications
137
- - name: "environments-applications:create"
138
- roles: ["aifabrix-platform-admin", "aifabrix-deployment-admin", "aifabrix-developer"]
139
- description: "Create applications within environments"
140
-
141
- - name: "environments-applications:read"
142
- roles: ["aifabrix-platform-admin", "aifabrix-deployment-admin", "aifabrix-developer", "aifabrix-observer"]
143
- description: "View applications within environments"
144
-
145
- - name: "environments-applications:update"
146
- roles: ["aifabrix-platform-admin", "aifabrix-deployment-admin", "aifabrix-developer"]
147
- description: "Update applications within environments"
148
-
149
- - name: "environments-applications:delete"
150
- roles: ["aifabrix-platform-admin", "aifabrix-deployment-admin"]
151
- description: "Remove applications from environments"
152
-
151
+ - name: 'environments-applications:create'
152
+ roles: ['aifabrix-platform-admin', 'aifabrix-deployment-admin', 'aifabrix-developer']
153
+ description: 'Create applications within environments'
154
+
155
+ - name: 'environments-applications:read'
156
+ roles:
157
+ [
158
+ 'aifabrix-platform-admin',
159
+ 'aifabrix-deployment-admin',
160
+ 'aifabrix-developer',
161
+ 'aifabrix-observer'
162
+ ]
163
+ description: 'View applications within environments'
164
+
165
+ - name: 'environments-applications:update'
166
+ roles: ['aifabrix-platform-admin', 'aifabrix-deployment-admin', 'aifabrix-developer']
167
+ description: 'Update applications within environments'
168
+
169
+ - name: 'environments-applications:delete'
170
+ roles: ['aifabrix-platform-admin', 'aifabrix-deployment-admin']
171
+ description: 'Remove applications from environments'
172
+
153
173
  # Pipeline & Deployment
154
- - name: "applications:deploy"
155
- roles: ["aifabrix-platform-admin", "aifabrix-deployment-admin", "aifabrix-developer"]
156
- description: "Deploy applications to environments"
157
-
158
- - name: "deployments:read"
159
- roles: ["aifabrix-platform-admin", "aifabrix-deployment-admin", "aifabrix-developer", "aifabrix-observer"]
160
- description: "View deployment history and status"
161
-
174
+ - name: 'applications:deploy'
175
+ roles: ['aifabrix-platform-admin', 'aifabrix-deployment-admin', 'aifabrix-developer']
176
+ description: 'Deploy applications to environments'
177
+
178
+ - name: 'deployments:read'
179
+ roles:
180
+ [
181
+ 'aifabrix-platform-admin',
182
+ 'aifabrix-deployment-admin',
183
+ 'aifabrix-developer',
184
+ 'aifabrix-observer'
185
+ ]
186
+ description: 'View deployment history and status'
187
+
162
188
  # Controller Operations
163
- - name: "controller:admin"
164
- roles: ["aifabrix-platform-admin"]
165
- description: "Full administrative access to controller operations"
166
-
167
- - name: "controller:deploy"
168
- roles: ["aifabrix-platform-admin", "aifabrix-infrastructure-admin", "aifabrix-deployment-admin"]
169
- description: "Deploy infrastructure and manage environments"
170
-
171
- - name: "controller:monitor"
172
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-observer"]
173
- description: "Monitor system health and view logs"
174
-
175
- - name: "controller:compliance"
176
- roles: ["aifabrix-platform-admin", "aifabrix-compliance-admin"]
177
- description: "Access compliance reports and audit logs"
178
-
189
+ - name: 'controller:admin'
190
+ roles: ['aifabrix-platform-admin']
191
+ description: 'Full administrative access to controller operations'
192
+
193
+ - name: 'controller:deploy'
194
+ roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin', 'aifabrix-deployment-admin']
195
+ description: 'Deploy infrastructure and manage environments'
196
+
197
+ - name: 'controller:monitor'
198
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-observer']
199
+ description: 'Monitor system health and view logs'
200
+
201
+ - name: 'controller:compliance'
202
+ roles: ['aifabrix-platform-admin', 'aifabrix-compliance-admin']
203
+ description: 'Access compliance reports and audit logs'
204
+
179
205
  # Authentication & Authorization
180
- - name: "auth:read"
181
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-developer", "aifabrix-observer"]
182
- description: "View user roles and permissions"
183
-
206
+ - name: 'auth:read'
207
+ roles:
208
+ [
209
+ 'aifabrix-platform-admin',
210
+ 'aifabrix-security-admin',
211
+ 'aifabrix-developer',
212
+ 'aifabrix-observer'
213
+ ]
214
+ description: 'View user roles and permissions'
215
+
184
216
  # Logs
185
- - name: "logs:read"
186
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-compliance-admin", "aifabrix-observer"]
187
- description: "View application and audit logs"
188
-
189
- - name: "logs:write"
190
- roles: ["aifabrix-platform-admin", "aifabrix-developer"]
191
- description: "Write audit and error logs"
192
-
193
- - name: "logs:export"
194
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-compliance-admin"]
195
- description: "Export logs for archival and compliance"
196
-
197
- - name: "audit:read"
198
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-compliance-admin"]
199
- description: "View audit trail logs"
200
-
201
- - name: "jobs:read"
202
- roles: ["aifabrix-platform-admin", "aifabrix-infrastructure-admin", "aifabrix-deployment-admin", "aifabrix-observer"]
203
- description: "View job and performance logs"
204
-
205
- - name: "admin:export"
206
- roles: ["aifabrix-platform-admin"]
207
- description: "Administrative export access to all data"
208
-
217
+ - name: 'logs:read'
218
+ roles:
219
+ [
220
+ 'aifabrix-platform-admin',
221
+ 'aifabrix-security-admin',
222
+ 'aifabrix-compliance-admin',
223
+ 'aifabrix-observer'
224
+ ]
225
+ description: 'View application and audit logs'
226
+
227
+ - name: 'logs:write'
228
+ roles: ['aifabrix-platform-admin', 'aifabrix-developer']
229
+ description: 'Write audit and error logs'
230
+
231
+ - name: 'logs:export'
232
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-compliance-admin']
233
+ description: 'Export logs for archival and compliance'
234
+
235
+ - name: 'audit:read'
236
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-compliance-admin']
237
+ description: 'View audit trail logs'
238
+
239
+ - name: 'jobs:read'
240
+ roles:
241
+ [
242
+ 'aifabrix-platform-admin',
243
+ 'aifabrix-infrastructure-admin',
244
+ 'aifabrix-deployment-admin',
245
+ 'aifabrix-observer'
246
+ ]
247
+ description: 'View job and performance logs'
248
+
249
+ - name: 'admin:export'
250
+ roles: ['aifabrix-platform-admin']
251
+ description: 'Administrative export access to all data'
252
+
209
253
  # Admin Operations
210
- - name: "admin:sync"
211
- roles: ["aifabrix-platform-admin", "aifabrix-infrastructure-admin"]
212
- description: "Full system synchronization operations"
213
-
214
- - name: "admin:keycloak"
215
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
216
- description: "Keycloak administration and configuration"
217
-
254
+ - name: 'admin:sync'
255
+ roles: ['aifabrix-platform-admin', 'aifabrix-infrastructure-admin']
256
+ description: 'Full system synchronization operations'
257
+
258
+ - name: 'admin:keycloak'
259
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
260
+ description: 'Keycloak administration and configuration'
261
+
218
262
  # Cache Management
219
- - name: "cache:read"
220
- roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-observer"]
221
- description: "View cache statistics and performance metrics"
222
-
223
- - name: "cache:admin"
224
- roles: ["aifabrix-platform-admin"]
225
- description: "Manage cache (clear, invalidate patterns)"
226
-
263
+ - name: 'cache:read'
264
+ roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-observer']
265
+ description: 'View cache statistics and performance metrics'
266
+
267
+ - name: 'cache:admin'
268
+ roles: ['aifabrix-platform-admin']
269
+ description: 'Manage cache (clear, invalidate patterns)'
270
+
227
271
  # Dashboard
228
- - name: "dashboard:read"
229
- roles: ["aifabrix-platform-admin", "aifabrix-deployment-admin", "aifabrix-developer", "aifabrix-observer"]
230
- description: "View dashboard summaries and aggregates"
272
+ - name: 'dashboard:read'
273
+ roles:
274
+ [
275
+ 'aifabrix-platform-admin',
276
+ 'aifabrix-deployment-admin',
277
+ 'aifabrix-developer',
278
+ 'aifabrix-observer'
279
+ ]
280
+ description: 'View dashboard summaries and aggregates'
@@ -1,8 +1,8 @@
1
1
  # Application Metadata
2
2
  app:
3
3
  key: miso-controller
4
- displayName: "Miso Controller"
5
- description: "AI Fabrix Miso Controller - Backend API and orchestration service"
4
+ displayName: 'Miso Controller'
5
+ description: 'AI Fabrix Miso Controller - Backend API and orchestration service'
6
6
  type: webapp
7
7
 
8
8
  # Image Configuration
@@ -34,7 +34,7 @@ healthCheck:
34
34
 
35
35
  # Authentication
36
36
  authentication:
37
- type: local
37
+ type: keycloak
38
38
  enableSSO: true
39
39
  requiredRoles:
40
40
  - aifabrix-user