@aifabrix/builder 2.62.0 → 2.63.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.
Files changed (34) hide show
  1. package/lib/api/onboarding-status.api.js +117 -0
  2. package/lib/app/register.js +24 -2
  3. package/lib/app/run-env-compose.js +39 -2
  4. package/lib/app/run-env-recovery.js +1 -0
  5. package/lib/app/run-helpers.js +1 -1
  6. package/lib/cli/infra-guided.js +30 -2
  7. package/lib/cli/setup-platform.js +18 -3
  8. package/lib/commands/setup-modes.js +2 -1
  9. package/lib/commands/setup-onboarding-readiness.js +83 -0
  10. package/lib/commands/setup.js +32 -10
  11. package/lib/commands/up-dataplane-bootstrap.js +115 -38
  12. package/lib/commands/up-dataplane.js +4 -31
  13. package/lib/core/local-env-overrides.js +23 -2
  14. package/lib/deployment/installation/index.js +4 -1
  15. package/lib/deployment/installation/infra-catalog.js +0 -2
  16. package/lib/deployment/installation/local-environment-authorize.js +221 -0
  17. package/lib/deployment/installation/local-environment-snapshot.js +124 -0
  18. package/lib/deployment/installation/local-environment-stage.js +43 -66
  19. package/lib/generator/index.js +32 -9
  20. package/lib/parameters/rsa-secret-values.js +14 -31
  21. package/lib/schema/infra.parameter.yaml +2 -45
  22. package/lib/schema/infrastructure-schema.json +0 -58
  23. package/lib/utils/dataplane-dimension-abac-setting.js +120 -0
  24. package/lib/utils/log-redaction.js +4 -1
  25. package/package.json +2 -1
  26. package/templates/README.md +6 -0
  27. package/templates/applications/builder-api/application.yaml +1 -1
  28. package/templates/applications/dataplane/application.yaml +1 -1
  29. package/templates/applications/dataplane/env.template +1 -1
  30. package/templates/applications/mirrored-templates.json +2 -2
  31. package/templates/applications/miso-controller/application.yaml +18 -1
  32. package/templates/applications/miso-controller/env.template +3 -21
  33. package/templates/applications/miso-controller/rbac.yaml +10 -6
  34. package/templates/marketplace/main.json +5 -69
@@ -6,7 +6,7 @@
6
6
  "repo": "aifabrix-miso",
7
7
  "path": "builder/miso-controller/env.template"
8
8
  },
9
- "sha256": "9b74b94d5d6e7457d22cfe213f2ec9c3dceb75c540df3953aba3b51c35df05c7"
9
+ "sha256": "c14aba1bd4588e334b89f3714714521e659d29f96bb76fc28ef17cacec47c80f"
10
10
  },
11
11
  {
12
12
  "path": "templates/applications/builder-api/env.template",
@@ -30,7 +30,7 @@
30
30
  "repo": "aifabrix-dataplane",
31
31
  "path": "builder/dataplane/env.template"
32
32
  },
33
- "sha256": "d35eda839464a65ebd018064ea7c2d292654891b41d1f56862b052720a78a3cc"
33
+ "sha256": "3f4abcd6e3b7a8cb8a6029c8841b63c2384b863ef168a2ed0128bd5856119e3f"
34
34
  },
35
35
  {
36
36
  "path": "templates/applications/integration-server/env.template",
@@ -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.91'
7
+ version: '2.0.95'
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).
@@ -96,6 +96,11 @@ platforms:
96
96
  value: 'azure'
97
97
  - name: MORI_BASE_URL
98
98
  value: 'https://platform.aifabrix.dev/mori'
99
+ # Names the Mori policy-signing pair the licence bundle is checked against. Must match
100
+ # Mori's own POLICY_KEY_ID (aifabrix-mori builder/mori-controller/application.yaml).
101
+ # Names the Mori policy-signing pair (MORI_POLICY_KEY_ID); must match Mori's manifest.
102
+ - name: POLICY_KEY_ID
103
+ value: 'mori-policy-key-1'
99
104
 
100
105
  # Local Docker runs. No resource group is supplied, so `matchLocalPlatform` takes the FIRST entry
101
106
  # declaring the local platform (`dev`) -- this one -- and the literal `aifabrix-local` can never
@@ -135,6 +140,10 @@ platforms:
135
140
  value: 'swedencentral'
136
141
  - name: MORI_BASE_URL
137
142
  value: 'url://mori-controller-internal'
143
+ # Names the Mori policy-signing pair the licence bundle is checked against. Must match
144
+ # Mori's own POLICY_KEY_ID (aifabrix-mori builder/mori-controller/application.yaml).
145
+ - name: POLICY_KEY_ID
146
+ value: 'mori-policy-key-1'
138
147
  # Declared, because the fallback infers it and infers it wrongly here:
139
148
  # `resolveCloudVendor` returns 'azure' whenever AZURE_SUBSCRIPTION_ID is set, and every
140
149
  # developer box has it from the shared development service principal. A local stack was
@@ -186,6 +195,10 @@ platforms:
186
195
  # consumer of it was pointed at a loopback address that does not exist in the cluster.
187
196
  - name: MORI_BASE_URL
188
197
  value: 'https://platform-{platform}.aifabrix.dev/mori'
198
+ # Names the Mori policy-signing pair the licence bundle is checked against. Must match
199
+ # Mori's own POLICY_KEY_ID (aifabrix-mori builder/mori-controller/application.yaml).
200
+ - name: POLICY_KEY_ID
201
+ value: 'mori-policy-key-1'
189
202
 
190
203
  - resource: aifabrix-ce-*
191
204
  name: '*'
@@ -212,6 +225,10 @@ platforms:
212
225
  # (`platform-dev.aifabrix.dev/mori`) this entry can follow the pattern used above.
213
226
  - name: MORI_BASE_URL
214
227
  value: 'https://{resourceGroup}-miso-mori-controller.azurewebsites.net/mori'
228
+ # Names the Mori policy-signing pair the licence bundle is checked against. Must match
229
+ # Mori's own POLICY_KEY_ID (aifabrix-mori builder/mori-controller/application.yaml).
230
+ - name: POLICY_KEY_ID
231
+ value: 'mori-policy-key-1'
215
232
 
216
233
  configuration:
217
234
  # -------------------------------------------------------------------------
@@ -148,8 +148,7 @@ REDIS_PERMISSIONS_TTL=900
148
148
  # sync-application-urls-from-env.service.
149
149
  #
150
150
  # After POST /api/ide/onboarding/entra-id/configure succeeds, controller adds Microsoft Entra IdP to stock Keycloak
151
- # using the customer enterprise app (AI Fabrix Miso), not the vendor ENTRA_ID_CLIENT_ID app.
152
- # After configure: Keycloak broker redirect is on the customer enterprise app (AI Fabrix Miso), not ENTRA_ID_CLIENT_ID.
151
+ # using the customer enterprise app (AI Fabrix Miso); its broker redirect is on that app.
153
152
 
154
153
  KEYCLOAK_REALM=aifabrix
155
154
  # Public issuer / browser URL (includes /auth when Keycloak uses KC_HTTP_RELATIVE_PATH=/auth).
@@ -231,16 +230,8 @@ MISO_CLIENTSECRET=kv://miso-controller-client-secretKeyVault
231
230
  # Kept in the shared block because EITHER mode may install Entra later from the plan 219
232
231
  # provider catalogue: Azure hosting does not force Entra, and SaaS may choose it.
233
232
  #
234
- # AI Fabrix vendor app for TA-1 admin consent and Entra ID configure flows.
235
- # Same ENTRA_ID_CLIENT_ID / ENTRA_ID_CLIENT_SECRET on every deployment.
236
- # Customer SSO after configure uses a separate enterprise app (AI Fabrix Miso);
237
- # Keycloak broker redirect is on that customer app, not ENTRA_ID_CLIENT_ID.
238
- #
239
- # ENTRA_ID_CENTRAL_CALLBACK_URI — Web redirect on the vendor app; must point at
240
- # Mori (e.g. …/entra-id-concent/callback), not the SPA or controller.
241
- ENTRA_ID_CENTRAL_CALLBACK_URI=url://mori-controller-public/entra-id-concent/callback
242
- ENTRA_ID_CLIENT_ID=kv://entra-client-idKeyVault
243
- ENTRA_ID_CLIENT_SECRET=kv://entra-client-secretKeyVault
233
+ # No Entra client id, secret or callback here. Mori owns the app registration and every
234
+ # sign-in; Miso only says which scopes it needs (plan 248.0, remove-vendor-app-secret-from-miso).
244
235
 
245
236
  # =============================================================================
246
237
  # TENANT ACTIVATION (TA-3) — EXISTING LLM CATALOG — [customerOwned]
@@ -378,15 +369,6 @@ MORI_LICENSE_PUBLIC_KEY=kv://mori-license-public-keyKeyVault
378
369
  # MORI_LICENSE_PUBLIC_KEY_FILE=/run/secrets/mori-license-public-key.pem
379
370
  MORI_LICENSE_ISSUER=mori-controller
380
371
 
381
- # =============================================================================
382
- # ENTRA ID ONBOARDING (delegated Graph via Mori)
383
- # =============================================================================
384
- # Must match a Web redirect URI on the vendor multi-tenant app and Mori’s route
385
- # …/entra-graph-delegated/callback. Shared pro Mori addon (CICD *-miso-rg):
386
- ENTRA_GRAPH_DELEGATED_REDIRECT_URI=url://mori-controller-public/entra-graph-delegated/callback
387
-
388
- # Optional delegated Graph scopes for GET …/graph-auth-url (space- or comma-separated).
389
- # 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
390
372
 
391
373
  # Dev only: log full delegated Graph JWT in general_log_* as delegatedGraphJwt (default off).
392
374
  # LOG_GRAPH_TOKEN=true
@@ -91,6 +91,10 @@ permissions:
91
91
  roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
92
92
  description: 'Delete groups'
93
93
 
94
+ - name: 'user-groups:update'
95
+ roles: ['aifabrix-security-admin']
96
+ description: 'Manage user-group memberships'
97
+
94
98
  # Administrative Permissions
95
99
  - name: 'admin:read'
96
100
  roles: ['aifabrix-platform-admin']
@@ -315,15 +319,15 @@ permissions:
315
319
  description: 'View dimensions and dimension values'
316
320
 
317
321
  - name: 'dimensions:create'
318
- roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-deployment-admin']
322
+ roles: ['aifabrix-security-admin']
319
323
  description: 'Create dimensions and dimension values'
320
324
 
321
325
  - name: 'dimensions:update'
322
- roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-deployment-admin']
326
+ roles: ['aifabrix-security-admin']
323
327
  description: 'Update dimensions and dimension values'
324
328
 
325
329
  - name: 'dimensions:delete'
326
- roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
330
+ roles: ['aifabrix-security-admin']
327
331
  description: 'Delete dimensions and dimension values'
328
332
 
329
333
  # Resource Type Management
@@ -363,15 +367,15 @@ permissions:
363
367
  description: 'View business roles'
364
368
 
365
369
  - name: 'roles:create'
366
- roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-deployment-admin']
370
+ roles: ['aifabrix-security-admin']
367
371
  description: 'Create business roles'
368
372
 
369
373
  - name: 'roles:update'
370
- roles: ['aifabrix-platform-admin', 'aifabrix-security-admin', 'aifabrix-deployment-admin']
374
+ roles: ['aifabrix-security-admin']
371
375
  description: 'Update business roles'
372
376
 
373
377
  - name: 'roles:delete'
374
- roles: ['aifabrix-platform-admin', 'aifabrix-security-admin']
378
+ roles: ['aifabrix-security-admin']
375
379
  description: 'Delete business roles'
376
380
 
377
381
  # Evidence Kind Management
@@ -5,7 +5,7 @@
5
5
  "_generator": {
6
6
  "name": "bicep",
7
7
  "version": "0.47.16.16243",
8
- "templateHash": "703508813427294425"
8
+ "templateHash": "17889093247943830402"
9
9
  }
10
10
  },
11
11
  "parameters": {
@@ -179,20 +179,6 @@
179
179
  "description": "Expected `iss` claim on a Mori licence."
180
180
  }
181
181
  },
182
- "entraIdClientId": {
183
- "type": "string",
184
- "defaultValue": "481251f2-a318-47ba-9ad8-40c36f790df1",
185
- "metadata": {
186
- "description": "Vendor Entra multi-tenant app client ID. Default matches vendor miso-controller .env (not in createUiDefinition)."
187
- }
188
- },
189
- "entraIdClientSecret": {
190
- "type": "securestring",
191
- "defaultValue": "",
192
- "metadata": {
193
- "description": "Optional vendor Entra app client secret. Configure through the Miso installation contract."
194
- }
195
- },
196
182
  "entraIdTenantId": {
197
183
  "type": "string",
198
184
  "defaultValue": "09aff594-ba37-4af2-bf40-b773b654c563",
@@ -2540,9 +2526,6 @@
2540
2526
  "encryptionKey": {
2541
2527
  "value": "[reference(resourceId('Microsoft.Resources/deployments', 'bootstrapSecrets'), '2025-04-01').outputs.encryptionKey.value]"
2542
2528
  },
2543
- "entraIdClientSecret": {
2544
- "value": "[parameters('entraIdClientSecret')]"
2545
- },
2546
2529
  "moriLicensePublicKey": {
2547
2530
  "value": "[parameters('moriLicensePublicKey')]"
2548
2531
  },
@@ -2560,7 +2543,7 @@
2560
2543
  "_generator": {
2561
2544
  "name": "bicep",
2562
2545
  "version": "0.47.16.16243",
2563
- "templateHash": "4650500285752133401"
2546
+ "templateHash": "6681447246114830480"
2564
2547
  }
2565
2548
  },
2566
2549
  "parameters": {
@@ -2629,13 +2612,6 @@
2629
2612
  "description": "MISO Client Secret (auto-generated if not provided)"
2630
2613
  }
2631
2614
  },
2632
- "entraIdClientSecret": {
2633
- "type": "securestring",
2634
- "defaultValue": "",
2635
- "metadata": {
2636
- "description": "Vendor Entra app client secret (ENTRA_ID_CLIENT_SECRET). Placeholder when empty — replace for live Entra flows."
2637
- }
2638
- },
2639
2615
  "moriLicensePublicKey": {
2640
2616
  "type": "string",
2641
2617
  "defaultValue": "",
@@ -2659,7 +2635,6 @@
2659
2635
  }
2660
2636
  },
2661
2637
  "variables": {
2662
- "resolvedEntraIdClientSecret": "[if(not(empty(parameters('entraIdClientSecret'))), parameters('entraIdClientSecret'), format('PLACEHOLDER_ENTRA_CLIENT_SECRET_MUST_BE_REPLACED_{0}', uniqueString(resourceGroup().id, parameters('prefix'))))]",
2663
2638
  "misoWebAppOrigin": "[if(endsWith(parameters('misoWebAppUrl'), '/'), substring(parameters('misoWebAppUrl'), 0, max(0, sub(length(parameters('misoWebAppUrl')), 1))), parameters('misoWebAppUrl'))]",
2664
2639
  "misoVdirSegment": "[if(or(equals(parameters('misoVdirPublic'), ''), equals(parameters('misoVdirPublic'), '/')), '', if(startsWith(parameters('misoVdirPublic'), '/'), parameters('misoVdirPublic'), format('/{0}', parameters('misoVdirPublic'))))]",
2665
2640
  "misoWebServerPublicUrl": "[if(empty(variables('misoVdirSegment')), variables('misoWebAppOrigin'), format('{0}{1}', variables('misoWebAppOrigin'), variables('misoVdirSegment')))]",
@@ -2750,14 +2725,6 @@
2750
2725
  "value": "[if(not(empty(parameters('misoClientSecret'))), parameters('misoClientSecret'), format('PLACEHOLDER_CLIENT_SECRET_MUST_BE_REPLACED_{0}', uniqueString(resourceGroup().id, parameters('prefix'))))]"
2751
2726
  }
2752
2727
  },
2753
- {
2754
- "type": "Microsoft.KeyVault/vaults/secrets",
2755
- "apiVersion": "2023-07-01",
2756
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'entra-client-secretKeyVault')]",
2757
- "properties": {
2758
- "value": "[variables('resolvedEntraIdClientSecret')]"
2759
- }
2760
- },
2761
2728
  {
2762
2729
  "condition": "[not(empty(parameters('moriLicensePublicKey')))]",
2763
2730
  "type": "Microsoft.KeyVault/vaults/secrets",
@@ -2891,9 +2858,6 @@
2891
2858
  "moriLicenseIssuer": {
2892
2859
  "value": "[parameters('moriLicenseIssuer')]"
2893
2860
  },
2894
- "entraIdClientId": {
2895
- "value": "[parameters('entraIdClientId')]"
2896
- },
2897
2861
  "entraIdTenantId": {
2898
2862
  "value": "[variables('effectiveEntraIdTenantId')]"
2899
2863
  },
@@ -2926,7 +2890,7 @@
2926
2890
  "_generator": {
2927
2891
  "name": "bicep",
2928
2892
  "version": "0.47.16.16243",
2929
- "templateHash": "9991546815810387946"
2893
+ "templateHash": "16391707537665698614"
2930
2894
  }
2931
2895
  },
2932
2896
  "parameters": {
@@ -3136,13 +3100,6 @@
3136
3100
  "description": "Expected `iss` claim on a Mori licence."
3137
3101
  }
3138
3102
  },
3139
- "entraIdClientId": {
3140
- "type": "string",
3141
- "defaultValue": "481251f2-a318-47ba-9ad8-40c36f790df1",
3142
- "metadata": {
3143
- "description": "Vendor multi-tenant Entra app client ID. No longer emitted as an app setting — Miso does not hold provider credentials; Mori does. Retained so the marketplace parameter surface is unchanged."
3144
- }
3145
- },
3146
3103
  "entraIdTenantId": {
3147
3104
  "type": "string",
3148
3105
  "defaultValue": "09aff594-ba37-4af2-bf40-b773b654c563",
@@ -3224,8 +3181,6 @@
3224
3181
  "databaseUrlAppSettingValue": "[if(not(empty(parameters('databaseUrlSecretUriWithVersion'))), format('@Microsoft.KeyVault(SecretUri={0})', parameters('databaseUrlSecretUriWithVersion')), format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-databases-0-urlKeyVault)', last(split(parameters('keyVaultId'), '/')), parameters('prefix')))]",
3225
3182
  "databaseLogUrlAppSettingValue": "[if(not(empty(parameters('databaseLogUrlSecretUriWithVersion'))), format('@Microsoft.KeyVault(SecretUri={0})', parameters('databaseLogUrlSecretUriWithVersion')), format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-databases-1-urlKeyVault)', last(split(parameters('keyVaultId'), '/')), parameters('prefix')))]",
3226
3183
  "moriBaseNormalized": "[if(and(greater(length(parameters('moriBaseUrl')), 1), endsWith(parameters('moriBaseUrl'), '/')), substring(parameters('moriBaseUrl'), 0, max(0, sub(length(parameters('moriBaseUrl')), 1))), parameters('moriBaseUrl'))]",
3227
- "moriEntraGraphDelegatedRedirectUri": "[format('{0}/entra-graph-delegated/callback', variables('moriBaseNormalized'))]",
3228
- "moriEntraCentralCallbackUri": "[format('{0}/entra-id-concent/callback', variables('moriBaseNormalized'))]",
3229
3184
  "registryCredentialAppSettings": "[if(and(parameters('registryPasswordConfigured'), not(empty(parameters('acrLoginServer')))), createArray(createObject('name', 'DOCKER_REGISTRY_SERVER_USERNAME', 'value', parameters('acrAdminUsername')), createObject('name', 'DOCKER_REGISTRY_SERVER_PASSWORD', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-acr-registry-password)', last(split(parameters('keyVaultId'), '/')), parameters('prefix')))), createArray())]"
3230
3185
  },
3231
3186
  "resources": [
@@ -3268,7 +3223,7 @@
3268
3223
  "acrUseManagedIdentityCreds": "[not(parameters('registryPasswordConfigured'))]",
3269
3224
  "healthCheckPath": "/health",
3270
3225
  "alwaysOn": true,
3271
- "appSettings": "[concat(createArray(createObject('name', 'WEBSITES_ENABLE_APP_SERVICE_STORAGE', 'value', 'false'), createObject('name', 'WEBSITES_CONTAINER_START_TIME_LIMIT', 'value', '1800'), createObject('name', 'DATABASE_URL', 'value', variables('databaseUrlAppSettingValue')), createObject('name', 'JWT_SECRET', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-secrets-jwtKeyVault)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'NODE_ENV', 'value', 'production'), createObject('name', 'LOCATION', 'value', parameters('location')), createObject('name', 'PORT', 'value', '3000'), createObject('name', 'ALLOWED_ORIGINS', 'value', format('http://localhost:*,http://127.0.0.1:*,https://{0}.azurewebsites.net', parameters('name'))), createObject('name', 'WEBSITE_VNET_ROUTE_ALL', 'value', if(parameters('enablePostgresPublicAccess'), '0', '1')), createObject('name', 'WEBSITE_DNS_SERVER', 'value', '168.63.129.16'), createObject('name', 'DATABASELOG_URL', 'value', variables('databaseLogUrlAppSettingValue')), createObject('name', 'KEY_VAULT_ENABLED', 'value', 'true'), createObject('name', 'SECURITY_STORAGE_MODE', 'value', 'keyvault'), createObject('name', 'KEY_VAULT_URL', 'value', format('https://{0}.vault.azure.net/', last(split(parameters('keyVaultId'), '/')))), createObject('name', 'ENCRYPTION_KEY', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-encryption-keyKeyVault)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'AUTO_CREATE_TABLES', 'value', if(parameters('autoCreateTables'), 'true', 'false')), createObject('name', 'KEYCLOAK_REALM', 'value', parameters('keycloakRealm')), createObject('name', 'KEYCLOAK_CLIENT_ID', 'value', parameters('keycloakClientId')), createObject('name', 'KEYCLOAK_ADMIN_USERNAME', 'value', parameters('keycloakAdminUsername')), createObject('name', 'KEYCLOAK_ADMIN_PASSWORD', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-keycloak-admin-password)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'ONBOARDING_ADMIN_PASSWORD', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-onboarding-admin-password)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'ONBOARDING_ADMIN_EMAIL', 'value', parameters('adminEmail')), createObject('name', 'KEYCLOAK_SERVER_URL', 'value', variables('keycloakPublicUrl')), createObject('name', 'KEYCLOAK_INTERNAL_SERVER_URL', 'value', variables('keycloakInternalUrl')), createObject('name', 'KEYCLOAK_TOKEN_TIMEOUT', 'value', '5000'), createObject('name', 'KEYCLOAK_VERIFY_AUDIENCE', 'value', if(parameters('keycloakVerifyAudience'), 'true', 'false')), createObject('name', 'DISABLE_RATE_LIMIT', 'value', variables('shouldDisableRateLimit')), createObject('name', 'SKIP_DB_REDEPLOY', 'value', 'true'), createObject('name', 'MISO_WEB_SERVER_URL', 'value', variables('misoPublicUrl')), createObject('name', 'MISO_CONTROLLER_URL', 'value', variables('misoInternalUrl')), createObject('name', 'MISO_RELATIVE_PATH', 'value', variables('misoVdirValue')), createObject('name', 'CONTROLLER_BASE_URL', 'value', if(not(empty(parameters('misoControllerUrl'))), parameters('misoControllerUrl'), format('https://{0}.azurewebsites.net', parameters('name')))), createObject('name', 'AZURE_SUBSCRIPTION_ID', 'value', subscription().subscriptionId), createObject('name', 'TENANT_ID', 'value', subscription().tenantId), createObject('name', 'DEPLOYMENT', 'value', 'azure'), createObject('name', 'AZURE_RESOURCE_GROUP_NAME', 'value', parameters('resourceGroupName')), createObject('name', 'INFRASTRUCTURE_NAME', 'value', parameters('prefix')), createObject('name', 'ENTRA_ID_TENANT_ID', 'value', parameters('entraIdTenantId')), createObject('name', 'MISO_CLIENTID', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-miso-clientIdKeyVault)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'MISO_CLIENTSECRET', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName=miso-controller-client-secretKeyVault)', last(split(parameters('keyVaultId'), '/')))), createObject('name', 'MORI_BASE_URL', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName=mori-base-urlKeyVault)', last(split(parameters('keyVaultId'), '/')))), createObject('name', 'MORI_LICENSE_PUBLIC_KEY', 'value', if(empty(parameters('moriLicensePublicKey')), '', format('@Microsoft.KeyVault(VaultName={0};SecretName=mori-license-public-keyKeyVault)', last(split(parameters('keyVaultId'), '/'))))), createObject('name', 'MORI_LICENSE_ISSUER', 'value', parameters('moriLicenseIssuer')), createObject('name', 'ENTRA_GRAPH_DELEGATED_REDIRECT_URI', 'value', variables('moriEntraGraphDelegatedRedirectUri')), createObject('name', 'ENTRA_ID_CENTRAL_CALLBACK_URI', 'value', variables('moriEntraCentralCallbackUri')), createObject('name', 'DOCKER_ENABLE_CI', 'value', 'true'), createObject('name', 'DOCKER_REGISTRY_SERVER_URL', 'value', if(not(empty(parameters('acrLoginServer'))), format('https://{0}', parameters('acrLoginServer')), '')), createObject('name', 'ONBOARDING_CREATE_PIPELINE_CREDENTIALS', 'value', 'true'), createObject('name', 'BOOTSTRAP_APPKEYS', 'value', parameters('bootstrapAppKeys')), createObject('name', 'LICENSE_JWT', 'value', parameters('licenseJwt')), createObject('name', 'INSTALLATION_RESOURCE_MODEL', 'value', parameters('installationResourceModel')), createObject('name', 'LICENSE_EDITION', 'value', parameters('licenseEdition')), createObject('name', 'ONBOARDING_MODE', 'value', parameters('onboardingMode')), createObject('name', 'INSTALLATION_CAPACITY_PROFILE', 'value', parameters('capacityProfile'))), variables('registryCredentialAppSettings'))]",
3226
+ "appSettings": "[concat(createArray(createObject('name', 'WEBSITES_ENABLE_APP_SERVICE_STORAGE', 'value', 'false'), createObject('name', 'WEBSITES_CONTAINER_START_TIME_LIMIT', 'value', '1800'), createObject('name', 'DATABASE_URL', 'value', variables('databaseUrlAppSettingValue')), createObject('name', 'JWT_SECRET', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-secrets-jwtKeyVault)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'NODE_ENV', 'value', 'production'), createObject('name', 'LOCATION', 'value', parameters('location')), createObject('name', 'PORT', 'value', '3000'), createObject('name', 'WEBSITE_VNET_ROUTE_ALL', 'value', if(parameters('enablePostgresPublicAccess'), '0', '1')), createObject('name', 'WEBSITE_DNS_SERVER', 'value', '168.63.129.16'), createObject('name', 'DATABASELOG_URL', 'value', variables('databaseLogUrlAppSettingValue')), createObject('name', 'KEY_VAULT_ENABLED', 'value', 'true'), createObject('name', 'SECURITY_STORAGE_MODE', 'value', 'keyvault'), createObject('name', 'KEY_VAULT_URL', 'value', format('https://{0}.vault.azure.net/', last(split(parameters('keyVaultId'), '/')))), createObject('name', 'ENCRYPTION_KEY', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-encryption-keyKeyVault)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'AUTO_CREATE_TABLES', 'value', if(parameters('autoCreateTables'), 'true', 'false')), createObject('name', 'KEYCLOAK_REALM', 'value', parameters('keycloakRealm')), createObject('name', 'KEYCLOAK_CLIENT_ID', 'value', parameters('keycloakClientId')), createObject('name', 'KEYCLOAK_ADMIN_USERNAME', 'value', parameters('keycloakAdminUsername')), createObject('name', 'KEYCLOAK_ADMIN_PASSWORD', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-keycloak-admin-password)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'ONBOARDING_ADMIN_PASSWORD', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-onboarding-admin-password)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'ONBOARDING_ADMIN_EMAIL', 'value', parameters('adminEmail')), createObject('name', 'KEYCLOAK_SERVER_URL', 'value', variables('keycloakPublicUrl')), createObject('name', 'KEYCLOAK_INTERNAL_SERVER_URL', 'value', variables('keycloakInternalUrl')), createObject('name', 'KEYCLOAK_TOKEN_TIMEOUT', 'value', '5000'), createObject('name', 'KEYCLOAK_VERIFY_AUDIENCE', 'value', if(parameters('keycloakVerifyAudience'), 'true', 'false')), createObject('name', 'DISABLE_RATE_LIMIT', 'value', variables('shouldDisableRateLimit')), createObject('name', 'SKIP_DB_REDEPLOY', 'value', 'true'), createObject('name', 'MISO_WEB_SERVER_URL', 'value', variables('misoPublicUrl')), createObject('name', 'MISO_CONTROLLER_URL', 'value', variables('misoInternalUrl')), createObject('name', 'MISO_RELATIVE_PATH', 'value', variables('misoVdirValue')), createObject('name', 'CONTROLLER_BASE_URL', 'value', if(not(empty(parameters('misoControllerUrl'))), parameters('misoControllerUrl'), format('https://{0}.azurewebsites.net', parameters('name')))), createObject('name', 'AZURE_SUBSCRIPTION_ID', 'value', subscription().subscriptionId), createObject('name', 'TENANT_ID', 'value', subscription().tenantId), createObject('name', 'DEPLOYMENT', 'value', 'azure'), createObject('name', 'AZURE_RESOURCE_GROUP_NAME', 'value', parameters('resourceGroupName')), createObject('name', 'INFRASTRUCTURE_NAME', 'value', parameters('prefix')), createObject('name', 'ENTRA_ID_TENANT_ID', 'value', parameters('entraIdTenantId')), createObject('name', 'MISO_CLIENTID', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName={1}-miso-clientIdKeyVault)', last(split(parameters('keyVaultId'), '/')), parameters('prefix'))), createObject('name', 'MISO_CLIENTSECRET', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName=miso-controller-client-secretKeyVault)', last(split(parameters('keyVaultId'), '/')))), createObject('name', 'MORI_BASE_URL', 'value', format('@Microsoft.KeyVault(VaultName={0};SecretName=mori-base-urlKeyVault)', last(split(parameters('keyVaultId'), '/')))), createObject('name', 'MORI_LICENSE_PUBLIC_KEY', 'value', if(empty(parameters('moriLicensePublicKey')), '', format('@Microsoft.KeyVault(VaultName={0};SecretName=mori-license-public-keyKeyVault)', last(split(parameters('keyVaultId'), '/'))))), createObject('name', 'MORI_LICENSE_ISSUER', 'value', parameters('moriLicenseIssuer')), createObject('name', 'DOCKER_ENABLE_CI', 'value', 'true'), createObject('name', 'DOCKER_REGISTRY_SERVER_URL', 'value', if(not(empty(parameters('acrLoginServer'))), format('https://{0}', parameters('acrLoginServer')), '')), createObject('name', 'ONBOARDING_CREATE_PIPELINE_CREDENTIALS', 'value', 'true'), createObject('name', 'BOOTSTRAP_APPKEYS', 'value', parameters('bootstrapAppKeys')), createObject('name', 'LICENSE_JWT', 'value', parameters('licenseJwt')), createObject('name', 'INSTALLATION_RESOURCE_MODEL', 'value', parameters('installationResourceModel')), createObject('name', 'LICENSE_EDITION', 'value', parameters('licenseEdition')), createObject('name', 'ONBOARDING_MODE', 'value', parameters('onboardingMode')), createObject('name', 'INSTALLATION_CAPACITY_PROFILE', 'value', parameters('capacityProfile'))), variables('registryCredentialAppSettings'))]",
3272
3227
  "vnetRouteAllEnabled": "[not(parameters('enablePostgresPublicAccess'))]"
3273
3228
  },
3274
3229
  "vnetRouteAllEnabled": "[not(parameters('enablePostgresPublicAccess'))]"
@@ -3663,9 +3618,6 @@
3663
3618
  },
3664
3619
  "encryptionKey": {
3665
3620
  "value": "[reference(resourceId('Microsoft.Resources/deployments', 'bootstrapSecrets'), '2025-04-01').outputs.encryptionKey.value]"
3666
- },
3667
- "entraIdClientSecret": {
3668
- "value": "[parameters('entraIdClientSecret')]"
3669
3621
  }
3670
3622
  },
3671
3623
  "template": {
@@ -3675,7 +3627,7 @@
3675
3627
  "_generator": {
3676
3628
  "name": "bicep",
3677
3629
  "version": "0.47.16.16243",
3678
- "templateHash": "4650500285752133401"
3630
+ "templateHash": "6681447246114830480"
3679
3631
  }
3680
3632
  },
3681
3633
  "parameters": {
@@ -3744,13 +3696,6 @@
3744
3696
  "description": "MISO Client Secret (auto-generated if not provided)"
3745
3697
  }
3746
3698
  },
3747
- "entraIdClientSecret": {
3748
- "type": "securestring",
3749
- "defaultValue": "",
3750
- "metadata": {
3751
- "description": "Vendor Entra app client secret (ENTRA_ID_CLIENT_SECRET). Placeholder when empty — replace for live Entra flows."
3752
- }
3753
- },
3754
3699
  "moriLicensePublicKey": {
3755
3700
  "type": "string",
3756
3701
  "defaultValue": "",
@@ -3774,7 +3719,6 @@
3774
3719
  }
3775
3720
  },
3776
3721
  "variables": {
3777
- "resolvedEntraIdClientSecret": "[if(not(empty(parameters('entraIdClientSecret'))), parameters('entraIdClientSecret'), format('PLACEHOLDER_ENTRA_CLIENT_SECRET_MUST_BE_REPLACED_{0}', uniqueString(resourceGroup().id, parameters('prefix'))))]",
3778
3722
  "misoWebAppOrigin": "[if(endsWith(parameters('misoWebAppUrl'), '/'), substring(parameters('misoWebAppUrl'), 0, max(0, sub(length(parameters('misoWebAppUrl')), 1))), parameters('misoWebAppUrl'))]",
3779
3723
  "misoVdirSegment": "[if(or(equals(parameters('misoVdirPublic'), ''), equals(parameters('misoVdirPublic'), '/')), '', if(startsWith(parameters('misoVdirPublic'), '/'), parameters('misoVdirPublic'), format('/{0}', parameters('misoVdirPublic'))))]",
3780
3724
  "misoWebServerPublicUrl": "[if(empty(variables('misoVdirSegment')), variables('misoWebAppOrigin'), format('{0}{1}', variables('misoWebAppOrigin'), variables('misoVdirSegment')))]",
@@ -3865,14 +3809,6 @@
3865
3809
  "value": "[if(not(empty(parameters('misoClientSecret'))), parameters('misoClientSecret'), format('PLACEHOLDER_CLIENT_SECRET_MUST_BE_REPLACED_{0}', uniqueString(resourceGroup().id, parameters('prefix'))))]"
3866
3810
  }
3867
3811
  },
3868
- {
3869
- "type": "Microsoft.KeyVault/vaults/secrets",
3870
- "apiVersion": "2023-07-01",
3871
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'entra-client-secretKeyVault')]",
3872
- "properties": {
3873
- "value": "[variables('resolvedEntraIdClientSecret')]"
3874
- }
3875
- },
3876
3812
  {
3877
3813
  "condition": "[not(empty(parameters('moriLicensePublicKey')))]",
3878
3814
  "type": "Microsoft.KeyVault/vaults/secrets",