@aifabrix/builder 2.55.2 → 2.56.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 (41) hide show
  1. package/lib/api/pipeline.api.js +32 -3
  2. package/lib/app/deploy.js +6 -5
  3. package/lib/cli/setup-app.js +7 -6
  4. package/lib/cli/setup-external-system-help.js +15 -3
  5. package/lib/cli/setup-external-system.js +21 -3
  6. package/lib/commands/repair-datasource-auto-rbac.js +4 -9
  7. package/lib/commands/upload-openapi-sync-followup.js +59 -0
  8. package/lib/commands/upload-qualification-client.js +131 -0
  9. package/lib/commands/upload-qualification-followups.js +104 -0
  10. package/lib/commands/upload-qualification-poll.js +143 -0
  11. package/lib/commands/upload.js +37 -156
  12. package/lib/deployment/deploy-poll-ui.js +14 -10
  13. package/lib/deployment/deployer-status.js +4 -4
  14. package/lib/external-system/deploy.js +46 -51
  15. package/lib/generator/helpers.js +4 -47
  16. package/lib/generator/index.js +0 -4
  17. package/lib/programmatic/builder-api-request.js +8 -4
  18. package/lib/programmatic/capability-registry.js +2 -0
  19. package/lib/programmatic/get-deploy-status.js +45 -0
  20. package/lib/programmatic/get-upload-status.js +45 -0
  21. package/lib/programmatic/http-accepted-response.js +31 -0
  22. package/lib/programmatic/openapi-descriptions.js +14 -4
  23. package/lib/programmatic/request-timeout.js +21 -15
  24. package/lib/programmatic/route-handler-map.js +6 -0
  25. package/lib/programmatic/run-deploy.js +7 -3
  26. package/lib/programmatic/run-upload.js +11 -7
  27. package/lib/schema/infra.parameter.yaml +6 -7
  28. package/lib/utils/cli-utils.js +3 -2
  29. package/lib/utils/qualification-poll-ui.js +136 -0
  30. package/package.json +3 -2
  31. package/templates/applications/builder-api/env.template +4 -0
  32. package/templates/applications/dataplane/application.yaml +1 -1
  33. package/templates/applications/dataplane/env.template +1 -1
  34. package/templates/applications/keycloak/Dockerfile +12 -5
  35. package/templates/applications/keycloak/application.yaml +2 -2
  36. package/templates/applications/keycloak/cimd-lenient-parse/AbstractClientIdMetadataDocumentExecutor.java +3 -3
  37. package/templates/applications/keycloak/cimd-lenient-parse/SOURCE.md +6 -6
  38. package/templates/applications/keycloak/env.template +13 -1
  39. package/templates/applications/miso-controller/application.yaml +1 -1
  40. package/templates/applications/miso-controller/env.template +13 -16
  41. package/lib/generator/deploy-manifest-azure-kv.js +0 -81
@@ -0,0 +1,136 @@
1
+ /**
2
+ * @fileoverview TTY ora spinner + throttled non-TTY lines for qualification job polling.
3
+ * Mirrors host-ops-poll-ui.js's presentation pattern (plan 191.0 task 1).
4
+ * @author AI Fabrix Team
5
+ * @version 1.0.0
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const chalk = require('chalk');
11
+ const logger = require('./logger');
12
+
13
+ const SILENT_POLL_HANDLERS = {
14
+ onPollProgress: () => {},
15
+ onTransientError: () => {},
16
+ finish: () => {}
17
+ };
18
+
19
+ const NON_TTY_THROTTLE_MS = 5000;
20
+
21
+ const HUMAN_STEP_LABELS = Object.freeze({
22
+ stageCandidate: 'Uploading configuration',
23
+ gateOperations: 'Checking operations',
24
+ gateTrust: 'Checking trust',
25
+ gateGovernance: 'Checking governance',
26
+ sealCertificate: 'Finalizing upload'
27
+ });
28
+
29
+ function shouldUseQualificationPollSpinner() {
30
+ return Boolean(process && process.stdout && process.stdout.isTTY);
31
+ }
32
+
33
+ /**
34
+ * @param {Object} job - PublishJobStatusResponse-shaped data ({ state, steps })
35
+ * @returns {string}
36
+ */
37
+ function currentQualificationStepLabel(job, options = {}) {
38
+ if (!job || !Array.isArray(job.steps) || job.steps.length === 0) {
39
+ return 'Preparing upload';
40
+ }
41
+ const running = job.steps.find((step) => step.status === 'running');
42
+ if (running) return qualificationStepLabel(running, 'running', options);
43
+ const failed = job.steps.find((step) => step.status === 'failed');
44
+ if (failed) return qualificationStepLabel(failed, 'failed', options);
45
+ const pending = job.steps.find((step) => step.status === 'pending');
46
+ if (pending) return qualificationStepLabel(pending, 'pending', options);
47
+ return 'Upload complete';
48
+ }
49
+
50
+ function qualificationStepLabel(step, _status, _options) {
51
+ return HUMAN_STEP_LABELS[step.key] || 'Processing upload';
52
+ }
53
+
54
+ /**
55
+ * @param {string} jobId
56
+ * @param {Object|null} job
57
+ * @param {number} elapsedMs
58
+ * @param {string} [transientNote]
59
+ * @returns {string}
60
+ */
61
+ function buildQualificationPollLine(jobId, job, elapsedMs, transientNote, options = {}) {
62
+ const seconds = Math.max(0, Math.round(elapsedMs / 1000));
63
+ const note = transientNote ? ` — ${transientNote}` : '';
64
+ if (options.verbose === true) {
65
+ return `Upload (job ${jobId}): ${currentQualificationStepLabel(job, options)} — ${seconds}s elapsed${note}`;
66
+ }
67
+ return `${currentQualificationStepLabel(job, options)} — ${seconds}s elapsed${note}`;
68
+ }
69
+
70
+ function createSpinnerQualificationPollHandlers(jobId, startedAt, options) {
71
+ const ora = require('ora');
72
+ const spinner = ora({
73
+ text: buildQualificationPollLine(jobId, null, 0, undefined, options),
74
+ spinner: 'dots'
75
+ }).start();
76
+ return {
77
+ onPollProgress: (job) => {
78
+ spinner.text = buildQualificationPollLine(jobId, job, Date.now() - startedAt, undefined, options);
79
+ },
80
+ onTransientError: (status, elapsedMs) => {
81
+ spinner.text = buildQualificationPollLine(
82
+ jobId,
83
+ null,
84
+ elapsedMs,
85
+ 'service temporarily unavailable, retrying',
86
+ options
87
+ );
88
+ },
89
+ finish: () => spinner.stop()
90
+ };
91
+ }
92
+
93
+ function createNonTtyQualificationPollHandlers(jobId, startedAt, options) {
94
+ let lastLogAt = 0;
95
+ return {
96
+ onPollProgress: (job) => {
97
+ const now = Date.now();
98
+ if (now - lastLogAt < NON_TTY_THROTTLE_MS) {
99
+ return;
100
+ }
101
+ lastLogAt = now;
102
+ logger.log(chalk.gray(` ${buildQualificationPollLine(jobId, job, now - startedAt, undefined, options)}`));
103
+ },
104
+ onTransientError: (status, elapsedMs) => {
105
+ logger.log(
106
+ chalk.gray(
107
+ ` ${buildQualificationPollLine(jobId, null, elapsedMs, 'service temporarily unavailable, retrying', options)}`
108
+ )
109
+ );
110
+ },
111
+ finish: () => {}
112
+ };
113
+ }
114
+
115
+ /**
116
+ * @param {string} jobId
117
+ * @param {{ startedAt?: number, silent?: boolean, verbose?: boolean }} [options]
118
+ * @returns {{ onPollProgress: Function, onTransientError: Function, finish: () => void }}
119
+ */
120
+ function createQualificationPollHandlers(jobId, options = {}) {
121
+ if (options.silent === true) {
122
+ return SILENT_POLL_HANDLERS;
123
+ }
124
+ const startedAt = options.startedAt || Date.now();
125
+ if (shouldUseQualificationPollSpinner()) {
126
+ return createSpinnerQualificationPollHandlers(jobId, startedAt, options);
127
+ }
128
+ return createNonTtyQualificationPollHandlers(jobId, startedAt, options);
129
+ }
130
+
131
+ module.exports = {
132
+ currentQualificationStepLabel,
133
+ buildQualificationPollLine,
134
+ createQualificationPollHandlers,
135
+ shouldUseQualificationPollSpinner
136
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.55.2",
3
+ "version": "2.56.0",
4
4
  "description": "AI Fabrix Builder — CLI and developer scripts (pnpm + af)",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -21,6 +21,7 @@
21
21
  "dev-reload": "node scripts/pnpm/dev-reload.mjs",
22
22
  "dev-build": "node scripts/pnpm/af.mjs dev-build",
23
23
  "dev-promote": "node scripts/pnpm/af.mjs dev-promote",
24
+ "dev-deploy": "node scripts/pnpm/dev-deploy.mjs",
24
25
  "af": "node scripts/pnpm/af.mjs",
25
26
  "check-quiet": "node scripts/pnpm/check-quiet.mjs",
26
27
  "check": "npm run lint && npm run test",
@@ -125,7 +126,7 @@
125
126
  }
126
127
  },
127
128
  "dependencies": {
128
- "@aifabrix/miso-client": "^4.19.0",
129
+ "@aifabrix/miso-client": "4.22.4",
129
130
  "adm-zip": "^0.6.0",
130
131
  "ajv": "^8.20.0",
131
132
  "ajv-formats": "^3.0.1",
@@ -37,6 +37,10 @@ DATAPLANE_URL=url://dataplane-internal
37
37
  # Public: Slack/Teams/ChatGPT callbacks, packages, and portal remaining-step URLs
38
38
  DATAPLANE_PUBLIC_URL=url://dataplane-public
39
39
 
40
+ # Self URLs: internal service calls and public links generated by Builder API
41
+ BUILDER_API_URL=url://internal
42
+ BUILDER_API_PUBLIC_URL=url://public
43
+
40
44
  DEFAULT_ENVIRONMENT=dev
41
45
 
42
46
  # Browser Origin allowlist for portal calls (CORS). url:// expands at resolve.
@@ -5,7 +5,7 @@ app:
5
5
  description: "AI Fabrix Dataplane is a secure, in-tenant integration and automation layer that supplies governed, normalized, and explainable enterprise data to AI agents. Using CIP as a declarative standard, it enforces RBAC and ABAC, executes integrations, and exposes trusted data via MCP and OpenAPI."
6
6
  type: webapp
7
7
  language: python # Explicitly specify Python language
8
- version: 2.0.65
8
+ version: 2.0.68
9
9
  # Image Configuration
10
10
  # Set tag to match your build (example: aifabrix build dataplane -t 1.0.0).
11
11
  # Registry is required so the controller can pull the image (avoids docker-not-found on the controller host).
@@ -44,7 +44,7 @@ API_KEY_BYPASS_ROLES=full-access
44
44
 
45
45
  # API Configuration
46
46
  API_V1_STR=/api/v1
47
- VERSION=2.0.65
47
+ VERSION=2.0.68
48
48
  # Minimum supported aifabrix-builder CLI semver (advertised on /health and /api/v1/health; empty = no gate)
49
49
  # MIN_BUILDER_CLI_VERSION=2.45.0
50
50
  MIN_BUILDER_CLI_VERSION=2.45.5
@@ -1,29 +1,34 @@
1
1
  # Keycloak Identity and Access Management with Custom Themes
2
- # Official 26.7.2 distro + experimental CIMD. Does not compile Keycloak from source.
2
+ # Official 26.7.3 distro + experimental CIMD. Does not compile Keycloak from source.
3
3
  # One class is recompiled against this image's jars:
4
4
  # - PR 51235: CIMD parse ignores unknown JSON fields (ChatGPT client.json)
5
5
  # - #50362: strip jwt-bearer (and other confidential-only grants) on public CIMD
6
6
  # 26.7 advertises token_endpoint_auth_methods_supported "none" for Claude CIMD.
7
7
 
8
- FROM quay.io/keycloak/keycloak:26.7.2 AS keycloak-base
8
+ FROM quay.io/keycloak/keycloak:26.7.3 AS keycloak-base
9
9
 
10
10
  FROM eclipse-temurin:21-jdk-jammy AS cimd-patch
11
11
  COPY --from=keycloak-base /opt/keycloak/lib/lib/main /opt/kc-jars/main
12
12
  COPY --from=keycloak-base /opt/keycloak/lib/lib/boot /opt/kc-jars/boot
13
13
  COPY cimd-lenient-parse/AbstractClientIdMetadataDocumentExecutor.java \
14
14
  /tmp/src/org/keycloak/protocol/oauth2/cimd/clientpolicy/executor/AbstractClientIdMetadataDocumentExecutor.java
15
+ COPY mori-email-sender/com /tmp/src/com
16
+ COPY mori-email-sender/META-INF /tmp/mori-provider/META-INF
15
17
  RUN mkdir -p /tmp/classes \
16
18
  && find /opt/kc-jars -name '*.jar' | paste -sd: - > /tmp/cp.txt \
17
19
  && { \
18
20
  echo --release; echo 21; echo -cp; cat /tmp/cp.txt; echo -d; echo /tmp/classes; \
19
21
  echo /tmp/src/org/keycloak/protocol/oauth2/cimd/clientpolicy/executor/AbstractClientIdMetadataDocumentExecutor.java; \
22
+ find /tmp/src/com -name '*.java'; \
20
23
  } > /tmp/javac.args \
21
24
  && javac @/tmp/javac.args \
22
25
  && SERVICES_JAR=$(ls /opt/kc-jars/main/org.keycloak.keycloak-services-*.jar) \
23
26
  && cp "$SERVICES_JAR" /tmp/keycloak-services-patched.jar \
24
- && jar uf /tmp/keycloak-services-patched.jar -C /tmp/classes .
27
+ && jar uf /tmp/keycloak-services-patched.jar -C /tmp/classes org/keycloak \
28
+ && cp -r /tmp/classes/com /tmp/mori-provider/ \
29
+ && jar cf /tmp/mori-email-sender.jar -C /tmp/mori-provider .
25
30
 
26
- FROM quay.io/keycloak/keycloak:26.7.2
31
+ FROM quay.io/keycloak/keycloak:26.7.3
27
32
 
28
33
  # Set working directory
29
34
  WORKDIR /opt/keycloak
@@ -31,6 +36,7 @@ WORKDIR /opt/keycloak
31
36
  # Copy custom themes from share directory (only if themes/ folder exists)
32
37
  USER root
33
38
  COPY --from=cimd-patch /tmp/keycloak-services-patched.jar /tmp/keycloak-services-patched.jar
39
+ COPY --from=cimd-patch /tmp/mori-email-sender.jar /opt/keycloak/providers/mori-email-sender.jar
34
40
  RUN SERVICES_JAR=$(ls /opt/keycloak/lib/lib/main/org.keycloak.keycloak-services-*.jar) \
35
41
  && cp /tmp/keycloak-services-patched.jar "$SERVICES_JAR" \
36
42
  && rm /tmp/keycloak-services-patched.jar
@@ -50,7 +56,8 @@ RUN if [ -d "/tmp/build-context/themes" ] && [ "$(ls -A /tmp/build-context/theme
50
56
  # Build Keycloak with health checks enabled (similar to Keycloak 24.0)
51
57
  # This enables the /health, /health/live, /health/ready, and /health/started endpoints
52
58
  # Expose health endpoints on main HTTP port (like 24.0) instead of management port
53
- RUN /opt/keycloak/bin/kc.sh build --health-enabled=true --http-management-health-enabled=false --features=cimd
59
+ RUN /opt/keycloak/bin/kc.sh build --health-enabled=true --http-management-health-enabled=false \
60
+ --features=cimd --spi-email-sender--provider=mori
54
61
 
55
62
  # Switch back to keycloak user
56
63
  USER keycloak
@@ -4,12 +4,12 @@ app:
4
4
  displayName: 'AI Fabrix Keycloak'
5
5
  description: 'Identity and Access Management'
6
6
  type: webapp
7
- version: '26.7.2'
7
+ version: '26.7.3-aifabrix.1'
8
8
 
9
9
  # Image Configuration
10
10
  image:
11
11
  name: aifabrix/keycloak
12
- tag: 'latest'
12
+ tag: '26.7.3-aifabrix.1'
13
13
  registry: aifabrixacr.azurecr.io
14
14
  registryMode: external
15
15
 
@@ -1,6 +1,6 @@
1
1
  package org.keycloak.protocol.oauth2.cimd.clientpolicy.executor;
2
2
 
3
- // AI Fabrix Keycloak image patch on 26.7.2 (not a Keycloak source build):
3
+ // AI Fabrix Keycloak image patch on 26.7.3 (not a Keycloak source build):
4
4
  // 1) PR 51235 — ignore unknown CIMD JSON fields (ChatGPT client.json).
5
5
  // 2) keycloak#50362 / PR 50411 idea — drop confidential-only grants (jwt-bearer)
6
6
  // from public CIMD metadata so Claude hosted Connect can register.
@@ -848,7 +848,7 @@ public abstract class AbstractClientIdMetadataDocumentExecutor<CONFIG extends Ab
848
848
 
849
849
  /**
850
850
  * Grant types Keycloak's DescriptionConverter refuses on a public client.
851
- * Same set as Keycloak PR 50411 (not in 26.7.2).
851
+ * Same set as Keycloak PR 50411 (not in 26.7.3).
852
852
  */
853
853
  protected static final Set<String> CONFIDENTIAL_ONLY_GRANT_TYPES = new LinkedHashSet<>(Arrays.asList(
854
854
  OAuth2Constants.CLIENT_CREDENTIALS,
@@ -873,7 +873,7 @@ public abstract class AbstractClientIdMetadataDocumentExecutor<CONFIG extends Ab
873
873
 
874
874
  /**
875
875
  * RFC 7591 §3.2.1 lets the AS replace requested grant_types. Claude CIMD is public
876
- * (`none`) plus jwt-bearer; Keycloak 26.7.2 rejects that combination (#50362).
876
+ * (`none`) plus jwt-bearer; Keycloak 26.7.3 rejects that combination (#50362).
877
877
  */
878
878
  protected void stripConfidentialOnlyGrantsForPublicClient(OIDCClientRepresentation oidcClient) {
879
879
  if (!"none".equals(oidcClient.getTokenEndpointAuthMethod())) {
@@ -1,19 +1,19 @@
1
- # CIMD image patch (Keycloak 26.7.2)
1
+ # CIMD image patch (Keycloak 26.7.3)
2
2
 
3
- This directory is **not** a Keycloak source build. `aifabrix build keycloak` still starts from `quay.io/keycloak/keycloak:26.7.2`. A Docker stage recompiles this one class against that image’s jars and injects it into `org.keycloak.keycloak-services-*.jar` before `kc.sh build`.
3
+ This directory is **not** a Keycloak source build. `aifabrix build keycloak` still starts from `quay.io/keycloak/keycloak:26.7.3`. A Docker stage recompiles this one class against that image’s jars and injects it into `org.keycloak.keycloak-services-*.jar` before `kc.sh build`.
4
4
 
5
- Upstream file: `services/.../AbstractClientIdMetadataDocumentExecutor.java` @ tag `26.7.2` (Apache 2.0).
5
+ Upstream file: `services/.../AbstractClientIdMetadataDocumentExecutor.java` @ tag `26.7.3` (Apache 2.0).
6
6
 
7
7
  ## Lenient parse (ChatGPT)
8
8
 
9
9
  ChatGPT’s CIMD `client.json` includes extra fields (for example `token_endpoint_auth_methods_supported`) that Keycloak 26.7.x rejects because CIMD parse uses Jackson `FAIL_ON_UNKNOWN_PROPERTIES`.
10
10
 
11
- - [keycloak/keycloak#51235](https://github.com/keycloak/keycloak/pull/51235) (open; not in 26.7.2)
11
+ - [keycloak/keycloak#51235](https://github.com/keycloak/keycloak/pull/51235) (open; not in 26.7.3)
12
12
 
13
13
  ## Public client + jwt-bearer (Claude hosted)
14
14
 
15
- Claude’s CIMD is a public client (`token_endpoint_auth_method: none`) that also lists `urn:ietf:params:oauth:grant-type:jwt-bearer`. Keycloak 26.7.2 then fails authorize with `JWT authorization grant cannot be enabled in a public client` ([#50362](https://github.com/keycloak/keycloak/issues/50362)). Official [PR 50411](https://github.com/keycloak/keycloak/pull/50411) is still a draft and defaults the filter **off**. This image always drops confidential-only grants on public CIMD metadata (RFC 7591 §3.2.1), which is enough for hosted Claude Connect (authorization_code). It does **not** implement public-client JWT Authorization Grant / EMA.
15
+ Claude’s CIMD is a public client (`token_endpoint_auth_method: none`) that also lists `urn:ietf:params:oauth:grant-type:jwt-bearer`. Keycloak 26.7.3 then fails authorize with `JWT authorization grant cannot be enabled in a public client` ([#50362](https://github.com/keycloak/keycloak/issues/50362)). Official [PR 50411](https://github.com/keycloak/keycloak/pull/50411) is still a draft and defaults the filter **off**. This image always drops confidential-only grants on public CIMD metadata (RFC 7591 §3.2.1), which is enough for hosted Claude Connect (authorization_code). It does **not** implement public-client JWT Authorization Grant / EMA.
16
16
 
17
- 26.7.2 already advertises `token_endpoint_auth_methods_supported` `"none"` when `--features=cimd`.
17
+ 26.7.3 already advertises `token_endpoint_auth_methods_supported` `"none"` when `--features=cimd`.
18
18
 
19
19
  Drop this patched class when the consumed `quay.io` tag includes both behaviors.
@@ -55,7 +55,7 @@ KC_HEALTH_ENABLED=true
55
55
  KC_HTTP_MANAGEMENT_HEALTH_ENABLED=false
56
56
  # Single-instance: use local cache so /health/ready passes (avoids Infinispan cluster check)
57
57
  KC_CACHE=local
58
- # Runtime features. Include cimd so Keycloak 26.7.2 advertises Client ID Metadata
58
+ # Runtime features. Include cimd so Keycloak 26.7.3 advertises Client ID Metadata
59
59
  # Documents. preview alone triggers a start-time rebuild that drops cimd.
60
60
  KC_FEATURES=preview,cimd
61
61
 
@@ -67,6 +67,18 @@ KC_FEATURES=preview,cimd
67
67
  MISO_CLIENTID=kv://keycloak-client-idKeyVault
68
68
  MISO_CLIENTSECRET=kv://keycloak-client-secretKeyVault
69
69
 
70
+ # =============================================================================
71
+ # MANAGED ACTION EMAIL (Keycloak -> Mori REST)
72
+ # =============================================================================
73
+ # The custom EmailSenderProvider relays Keycloak-rendered signed action emails to
74
+ # Mori. It never receives ACS credentials and never sends the action URL to Miso.
75
+ # Installation must supply the Mori environment returned during registration.
76
+ MORI_BASE_URL=kv://mori-controller-web-server-url
77
+ MORI_ENVIRONMENT_ID=kv://mori-controller-environment-idKeyVault
78
+ MORI_API_KEY=kv://mori-controller-api-keyKeyVault
79
+ MORI_NOTIFICATION_TIMEOUT_MS=10000
80
+ MORI_NOTIFICATION_MAX_ATTEMPTS=2
81
+
70
82
  # Connects to postgres service in Docker network (postgres) or localhost (local)
71
83
 
72
84
  KC_DB=postgres
@@ -4,7 +4,7 @@ app:
4
4
  displayName: 'Miso Controller'
5
5
  description: 'Miso is the AI Fabrix in-tenant controller and portal layer for securely operating enterprise AI apps inside a customer’s Azure tenant. It provides Entra ID SSO, RBAC, audit logs, environment/app configuration via schemas, and safe secret handling via Key Vault references—ensuring governance, traceability, and predictable UX across portal, SDK, and CLI.'
6
6
  type: webapp
7
- version: '2.0.66'
7
+ version: '2.0.78'
8
8
  # Image Configuration
9
9
  # Set tag to match your build (e.g. aifabrix build miso-controller -t 1.0.0 then tag: 1.0.0)
10
10
  # Registry is required so the controller can pull the image (avoids "docker: not found" on the controller host).
@@ -177,7 +177,7 @@ IDENTITY_LOCAL_PASSWORD_MODE=admin-reveal
177
177
  # Both onboarding modes use Mori's AI Fabrix-owned sender. MORI_API_KEY below is the
178
178
  # existing installation service key; no notification-specific secret is created.
179
179
  NOTIFICATION_BINDING=aifabrixManaged
180
- MORI_ENVIRONMENT_ID=
180
+ MORI_ENVIRONMENT_ID=kv://mori-controller-environment-idKeyVault
181
181
 
182
182
  # Keycloak Events Configuration
183
183
  KEYCLOAK_EVENTS_ENABLED=true
@@ -237,7 +237,7 @@ CONTROLLER_EVALUATION=
237
237
  #
238
238
  # Delegated Graph (second redirect URI on the same vendor app): see section below.
239
239
  ENTRA_CALLBACK_STATE_SECRET=kv://entra-callback-state-secret
240
- ENTRA_ID_CENTRAL_CALLBACK_URI=https://aifabrix-pro-miso-mori.azurewebsites.net/entra-id-concent/callback
240
+ ENTRA_ID_CENTRAL_CALLBACK_URI=https://dev01.aifabrix.dev/mori/entra-id-concent/callback
241
241
  ENTRA_ID_CLIENT_ID=kv://entra-client-idKeyVault
242
242
  ENTRA_ID_CLIENT_SECRET=kv://entra-client-secretKeyVault
243
243
 
@@ -323,8 +323,10 @@ SHOW_TA_COUNT_DOWN=false
323
323
  # Encryption Key for Database Secrets
324
324
  ENCRYPTION_KEY=kv://miso-controller-secrets-encryptionKeyVault
325
325
 
326
- # Comma-separated application keys allowed for POST .../applications/bootstrap (install-time only)
327
- BOOTSTRAP_APPKEYS=dataplane,builder-api
326
+ # Comma-separated application keys allowed for POST .../applications/bootstrap (install-time only).
327
+ # Environment placement is a separate controller rule: workloads (dataplane, builder-api) install into
328
+ # dev/tst/pro but never `miso`; miso-controller and keycloak only into `miso`; mori-controller anywhere.
329
+ BOOTSTRAP_APPKEYS=dataplane,builder-api,mori-controller
328
330
 
329
331
  # Key Vault Integration (for security parameter encryption)
330
332
  # Set to true to enable Azure Key Vault for storing security parameters
@@ -347,18 +349,16 @@ API_KEY=kv://miso-controller-secrets-apiKeyVault
347
349
  # =============================================================================
348
350
  # LICENSE CONFIGURATION — [always]
349
351
  # =============================================================================
350
- # [saasDirect] Must use the Mori flow (never DEVELOPMENT): Mori issues and owns the
352
+ # [saasDirect] Must use the Mori flow: Mori issues and owns the
351
353
  # 30-day STANDARD / M trial, and sales later updates the same subscription.
352
354
  #
353
355
  # Offline JWT license (optional):
354
356
  # - If set, controller validates license offline (RS256) without Mori subscription status call.
355
357
  # - Value can be literal JWT or kv:// reference.
356
- # - If not set, controller falls back to existing Mori subscription validation flow.
357
- #
358
- # Development: set to DEVELOPMENT to disable license validation (no Mori/JWT required):
359
- # LICENSE_JWT=DEVELOPMENT
360
- # - Use only for local development (DEPLOYMENT=local|database|azure-mock).
361
- # - Ignored when DEPLOYMENT=azure (Azure marketplace / App Service installs).
358
+ # - If empty and no stored licence exists, controller runs as Community edition
359
+ # (DEV environment, size S, shared resources).
360
+ # - Set only to a real signed offline licence JWT. Controlled tests may use the separate
361
+ # LICENSE_VALIDATION_DISABLED=true switch; do not use it in deployed installations.
362
362
  LICENSE_JWT=kv://miso-controller-jwt-licenseKeyVault
363
363
 
364
364
  # =============================================================================
@@ -366,7 +366,7 @@ LICENSE_JWT=kv://miso-controller-jwt-licenseKeyVault
366
366
  # =============================================================================
367
367
  # Must match a Web redirect URI on the vendor multi-tenant app and Mori’s route
368
368
  # …/entra-graph-delegated/callback. Shared pro Mori addon (CICD *-miso-rg):
369
- ENTRA_GRAPH_DELEGATED_REDIRECT_URI=https://aifabrix-pro-miso-mori.azurewebsites.net/entra-graph-delegated/callback
369
+ ENTRA_GRAPH_DELEGATED_REDIRECT_URI=https://dev01.aifabrix.dev/mori/entra-graph-delegated/callback
370
370
 
371
371
  # Optional delegated Graph scopes for GET …/graph-auth-url (space- or comma-separated).
372
372
  # ENTRA_GRAPH_DELEGATED_SCOPES=https://graph.microsoft.com/Application.ReadWrite.All,https://graph.microsoft.com/Group.ReadWrite.All,https://graph.microsoft.com/Synchronization.ReadWrite.All,openid,profile,offline_access
@@ -381,12 +381,9 @@ ENTRA_GRAPH_DELEGATED_REDIRECT_URI=https://aifabrix-pro-miso-mori.azurewebsites.
381
381
  # only works when a local Mori app is registered and running for this developer.
382
382
  # MORI_CALLBACK_API_KEY must be the same secret as Mori MORI_CONTROLLER_CALLBACK_API_KEY
383
383
  # (both resolve from kv://mori-callback-api-keyKeyVault — do not generate separate values).
384
- MORI_BASE_URL=https://aifabrix-pro-miso-mori.azurewebsites.net
385
- MORI_AUTH_METHOD=kv://mori-controller-auth-methodKeyVault
384
+ MORI_BASE_URL=kv://mori-controller-web-server-url
386
385
  MORI_CALLBACK_API_KEY=kv://mori-callback-api-keyKeyVault
387
386
  MORI_API_KEY=kv://mori-controller-api-keyKeyVault
388
- MORI_USERNAME=kv://mori-controller-basic-usernameKeyVault
389
- MORI_PASSWORD=kv://mori-controller-basic-passwordKeyVault
390
387
 
391
388
  # =============================================================================
392
389
  # LOGGING CONFIGURATION
@@ -1,81 +0,0 @@
1
- /**
2
- * Map url:// placeholders and Traefik host templates to Azure Key Vault secret names
3
- * in deployment manifests (Miso Controller / App Service @Microsoft.KeyVault references).
4
- *
5
- * @fileoverview Plan 122 — deploy JSON must not ship url:// or ${DEV_USERNAME} for Azure
6
- * @author AI Fabrix Team
7
- * @version 1.0.0
8
- */
9
-
10
- 'use strict';
11
-
12
- const { parseUrlToken } = require('../utils/url-declarative-token-parse');
13
-
14
- /**
15
- * @param {string|undefined} surface
16
- * @param {string} subject
17
- * @param {boolean} isPublic
18
- * @returns {string|undefined} Secret name when surface is vdir/host
19
- */
20
- function secretNameFromSurface(surface, subject, isPublic) {
21
- if (surface === 'vdir') {
22
- return `${subject}-vdir-${isPublic ? 'public' : 'internal'}`;
23
- }
24
- if (surface === 'host') {
25
- return `${subject}-host-${isPublic ? 'public' : 'internal'}`;
26
- }
27
- return undefined;
28
- }
29
-
30
- /**
31
- * Key Vault secret name for a url:// token (path after url://).
32
- * Aligns with infrastructure/bicep (e.g. keycloak-web-server-url, miso-controller-web-server-url).
33
- *
34
- * @param {string} ownerAppKey - application.yaml app.key for the manifest being generated
35
- * @param {string} token - Token after url:// (no scheme)
36
- * @returns {string}
37
- */
38
- function urlTokenToKeyVaultSecretName(ownerAppKey, token) {
39
- const selfKey = String(ownerAppKey || '').trim() || 'app';
40
- const t = String(token || '').trim();
41
- if (!t) {
42
- throw new Error('Empty url:// reference (expected a token after url://)');
43
- }
44
- const { targetKey, kind, surface } = parseUrlToken(t);
45
- const isPublic = kind === 'public';
46
- const subject = targetKey ? String(targetKey).trim() : selfKey;
47
-
48
- const fromSurface = secretNameFromSurface(surface, subject, isPublic);
49
- if (fromSurface !== undefined) {
50
- return fromSurface;
51
- }
52
- if (targetKey === 'keycloak' || (!targetKey && selfKey === 'keycloak')) {
53
- return isPublic ? 'keycloak-web-server-url' : 'keycloak-internal-server-url';
54
- }
55
- if (!targetKey) {
56
- return isPublic ? `${selfKey}-web-server-url` : `${selfKey}-internal-server-url`;
57
- }
58
- return isPublic ? `${subject}-web-server-url` : `${subject}-internal-server-url`;
59
- }
60
-
61
- /**
62
- * Replace Traefik-style host templates with a Key Vault secret name consumed by Azure provisioning.
63
- *
64
- * @param {Object} deployment - Deployment manifest (mutated in place)
65
- * @returns {void}
66
- */
67
- function rewriteFrontDoorHostForAzureDeploy(deployment) {
68
- if (!deployment || !deployment.frontDoorRouting || typeof deployment.frontDoorRouting.host !== 'string') {
69
- return;
70
- }
71
- const h = deployment.frontDoorRouting.host;
72
- if (/\$\{(DEV_USERNAME|REMOTE_HOST)\}/.test(h)) {
73
- const key = deployment.key || 'app';
74
- deployment.frontDoorRouting.host = `${key}-frontdoor-routing-host`;
75
- }
76
- }
77
-
78
- module.exports = {
79
- urlTokenToKeyVaultSecretName,
80
- rewriteFrontDoorHostForAzureDeploy
81
- };