@bluealba/platform-cli 1.0.1 → 1.0.2

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 (42) hide show
  1. package/dist/index.js +277 -9
  2. package/docs/404.mdx +5 -0
  3. package/docs/architecture/api-explorer.mdx +478 -0
  4. package/docs/architecture/architecture-diagrams.mdx +12 -0
  5. package/docs/architecture/authentication-system.mdx +903 -0
  6. package/docs/architecture/authorization-system.mdx +886 -0
  7. package/docs/architecture/bootstrap.mdx +1442 -0
  8. package/docs/architecture/gateway-architecture.mdx +845 -0
  9. package/docs/architecture/multi-tenancy.mdx +1150 -0
  10. package/docs/architecture/overview.mdx +776 -0
  11. package/docs/architecture/scheduler.mdx +818 -0
  12. package/docs/architecture/shell.mdx +885 -0
  13. package/docs/architecture/ui-extension-points.mdx +781 -0
  14. package/docs/architecture/user-states.mdx +794 -0
  15. package/docs/development/overview.mdx +21 -0
  16. package/docs/development/workflow.mdx +914 -0
  17. package/docs/getting-started/core-concepts.mdx +892 -0
  18. package/docs/getting-started/installation.mdx +780 -0
  19. package/docs/getting-started/overview.mdx +83 -0
  20. package/docs/getting-started/quick-start.mdx +940 -0
  21. package/docs/guides/adding-documentation-sites.mdx +1367 -0
  22. package/docs/guides/creating-services.mdx +1736 -0
  23. package/docs/guides/creating-ui-modules.mdx +1860 -0
  24. package/docs/guides/identity-providers.mdx +1007 -0
  25. package/docs/guides/mermaid-diagrams.mdx +212 -0
  26. package/docs/guides/using-feature-flags.mdx +1059 -0
  27. package/docs/guides/working-with-rooms.mdx +566 -0
  28. package/docs/index.mdx +57 -0
  29. package/docs/platform-cli/commands.mdx +604 -0
  30. package/docs/platform-cli/overview.mdx +195 -0
  31. package/package.json +5 -2
  32. package/skills/ba-platform/platform-cli.skill.md +26 -0
  33. package/skills/ba-platform/platform.skill.md +35 -0
  34. package/templates/application-monorepo-template/gitignore +95 -0
  35. package/templates/bootstrap-service-template/gitignore +57 -0
  36. package/templates/bootstrap-service-template/src/main.ts +6 -16
  37. package/templates/customization-ui-module-template/gitignore +73 -0
  38. package/templates/nestjs-service-module-template/gitignore +56 -0
  39. package/templates/platform-init-template/{{platformName}}-core/gitignore +97 -0
  40. package/templates/react-ui-module-template/Dockerfile +1 -1
  41. package/templates/react-ui-module-template/caddy/Caddyfile +1 -1
  42. package/templates/react-ui-module-template/gitignore +72 -0
@@ -0,0 +1,1442 @@
1
+ ---
2
+ title: Platform Bootstrap
3
+ description: How to bootstrap and configure the Blue Alba Platform with applications, modules, roles, operations, and catalog definitions
4
+ ---
5
+
6
+ import { Card, CardGrid, Aside, Tabs, TabItem } from '@astrojs/starlight/components';
7
+
8
+ The Blue Alba Platform uses a **bootstrap mechanism** to initialize and configure platform resources such as applications, modules, authorization definitions (roles and operations), and catalog entries. This enables declarative configuration management across environments.
9
+
10
+ ## Overview
11
+
12
+ The Platform is a living, evolving system that supports dynamic changes:
13
+
14
+ <CardGrid stagger>
15
+ <Card title="Add Applications" icon="seti:folder">
16
+ Register new business applications with the platform
17
+ </Card>
18
+
19
+ <Card title="Register Services" icon="puzzle">
20
+ Add microservices and micro-frontends to the catalog
21
+ </Card>
22
+
23
+ <Card title="Define Authorization" icon="shield">
24
+ Declare roles, operations, and permissions
25
+ </Card>
26
+
27
+ <Card title="Configure Modules" icon="setting">
28
+ Customize module behavior with configuration properties
29
+ </Card>
30
+
31
+ <Card title="Update Shared Libraries" icon="seti:npm">
32
+ Register shared libraries for UI modules
33
+ </Card>
34
+
35
+ <Card title="Schedule Jobs" icon="seti:clock">
36
+ Define scheduled jobs declaratively for recurring tasks
37
+ </Card>
38
+
39
+ <Card title="Environment Propagation" icon="random">
40
+ Synchronize configurations across development, staging, and production
41
+ </Card>
42
+ </CardGrid>
43
+
44
+ ---
45
+
46
+ ## Platform Bootstrap Service
47
+
48
+ Every PAE product includes a **platform-bootstrap-service** - a simple web service that contains all platform definitions as JSON data. When this service starts, it automatically synchronizes these definitions with the platform via REST APIs.
49
+
50
+ ### Architecture
51
+
52
+ ```
53
+ ┌─────────────────────────────────────────────────────────┐
54
+ │ Platform Bootstrap Service │
55
+ │ │
56
+ │ ┌────────────────────────────────────────────────┐ │
57
+ │ │ data/ │ │
58
+ │ │ ├── app1/ │ │
59
+ │ │ │ ├── application.json │ │
60
+ │ │ │ ├── modules.json │ │
61
+ │ │ │ ├── operations.json │ │
62
+ │ │ │ ├── roles.json │ │
63
+ │ │ │ └── jobs.json │ │
64
+ │ │ ├── app2/ │ │
65
+ │ │ └── platform/ │ │
66
+ │ └────────────────────────────────────────────────┘ │
67
+ │ │ │
68
+ │ ▼ │
69
+ │ ┌────────────────────────────────────────────────┐ │
70
+ │ │ Bootstrap Sync Engine │ │
71
+ │ │ • Compare JSON with DB state │ │
72
+ │ │ • Create/Update/Delete resources │ │
73
+ │ │ • Call Platform REST APIs │ │
74
+ │ └────────────────────────────────────────────────┘ │
75
+ └───────────────────────┬─────────────────────────────────┘
76
+
77
+
78
+ ┌─────────────────────────────────────────────────────────┐
79
+ │ Blue Alba Platform │
80
+ │ │
81
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
82
+ │ │ Catalog │ │ Authorization│ │ Tenants │ │
83
+ │ │ Service │ │ Service │ │ Service │ │
84
+ │ └──────────────┘ └──────────────┘ └──────────────┘ │
85
+ └─────────────────────────────────────────────────────────┘
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Repository Structure
91
+
92
+ The bootstrap service typically follows this structure:
93
+
94
+ ```
95
+ platform-bootstrap-service/
96
+ ├── src/
97
+ │ └── main.ts # Bootstrap application entry point
98
+ ├── data/
99
+ │ ├── app1/ # Business application 1
100
+ │ │ ├── application.json # Application metadata
101
+ │ │ ├── modules.json # Services and UIs
102
+ │ │ ├── operations.json # Authorization operations
103
+ │ │ ├── roles.json # Authorization roles
104
+ │ │ ├── modules-config.json # Module customizations (optional)
105
+ │ │ ├── jobs.json # Scheduled jobs (optional)
106
+ │ │ └── feature-flags.json # Feature flags (optional)
107
+ │ ├── app2/ # Business application 2
108
+ │ └── platform/ # Platform module overrides
109
+ │ └── modules-config.json # Platform module customizations
110
+ ├── package.json
111
+ └── README.md
112
+ ```
113
+
114
+ ### Folder Purpose
115
+
116
+ <Tabs>
117
+ <TabItem label="Application Folders">
118
+ **Application-Specific Folders** (e.g., `app1/`, `app2/`)
119
+
120
+ Each folder represents a business application and contains:
121
+ - Application metadata
122
+ - Module definitions (services and UIs)
123
+ - Authorization definitions (operations and roles)
124
+ - Module configurations (optional)
125
+ - Scheduled job definitions (optional)
126
+
127
+ **Example**: `app1/` might represent a "CRM" application with customer management services and UIs.
128
+ </TabItem>
129
+
130
+ <TabItem label="Platform Folder">
131
+ **Platform Folder** (`platform/`)
132
+
133
+ Used to override or customize built-in Platform modules (Shell, Admin UI, etc.):
134
+ - Shell customizations (branding, layout)
135
+ - Admin UI configurations
136
+ - Platform service overrides
137
+
138
+ **Note**: You only need to include modules you want to customize, not all platform modules.
139
+ </TabItem>
140
+ </Tabs>
141
+
142
+ ---
143
+
144
+ ## Using the Bootstrap Library
145
+
146
+ The bootstrap service uses `@bluealba/pae-bootstrap-lib` to read JSON files and synchronize them with the platform. The library exposes a single function — `bootstrapPlatform` — that handles reading, validating, and sending all definitions to the gateway.
147
+
148
+ ### Installation
149
+
150
+ ```bash
151
+ npm install @bluealba/pae-bootstrap-lib
152
+ ```
153
+
154
+ ### The `main.ts` Entry Point
155
+
156
+ A minimal bootstrap service looks like this:
157
+
158
+ ```typescript
159
+ import { bootstrapPlatform } from '@bluealba/pae-bootstrap-lib';
160
+ import { join } from 'path';
161
+
162
+ bootstrapPlatform({
163
+ path: join(__dirname, '../data'),
164
+ scopedTo: process.env.SERVICE_ACCESS_NAME!,
165
+ initialDelay: 5000, // wait 5s for the gateway to be ready
166
+ retries: 10, // retry up to 10 times with exponential backoff
167
+ });
168
+ ```
169
+
170
+ The function reads all JSON files from the specified `path`, resolves any `{{$ENV.X}}` expressions, validates the structure, and sends the full platform definition to the gateway via `POST /bootstrap/sync`.
171
+
172
+ ### Options
173
+
174
+ | Option | Type | Required | Description |
175
+ |---|---|---|---|
176
+ | `scopedTo` | `string` | **Yes** | Identifies this bootstrap service as the author of all entities it creates or updates. Must be unique per product. Typically set via `SERVICE_ACCESS_NAME` env var. |
177
+ | `path` | `string` | No | Path to the folder containing the `data/` structure. Defaults to `process.cwd()`. |
178
+ | `exclude` | `string[]` | No | List of application folder names to skip during synchronization. |
179
+ | `throwOnError` | `boolean` | No | If `true`, propagates errors to the caller. Defaults to `false` — errors are logged internally and the process stays alive. See note below. |
180
+ | `retries` | `number` | No | Number of retry attempts after the initial failure. Uses exponential backoff starting at 5s, doubling each attempt, capped at 5 minutes. Default: `0`. |
181
+ | `initialDelay` | `number` | No | Delay in ms before the first attempt. Useful to wait for the gateway to be ready before bootstrapping. Default: `0`. |
182
+
183
+ <Aside type="note" title="throwOnError defaults to false">
184
+ In previous versions, `bootstrapPlatform()` always threw on failure, so callers would typically wrap it in `.catch()` or `try/catch`. The default is now `false` — errors are caught internally, logged, and the process continues running. This is the recommended behavior in containerized environments where the service must keep responding to health checks even if the initial bootstrap fails. If you need the previous behavior — for example in a one-shot init container that should exit non-zero on failure — pass `throwOnError: true` to restore it.
185
+ </Aside>
186
+
187
+ ### Required Environment Variables
188
+
189
+ The library reads two environment variables at runtime:
190
+
191
+ | Variable | Description |
192
+ |---|---|
193
+ | `GATEWAY_SERVICE_URL` | Base URL of the platform gateway (e.g. `http://pae-gateway:8080`) |
194
+ | `SERVICE_ACCESS_SECRET` | Bearer token used to authenticate the bootstrap request against the gateway |
195
+ | `SERVICE_ACCESS_NAME` | Identifier used as the `scopedTo` value (conventionally passed into the options) |
196
+
197
+ <Aside type="caution" title="scopedTo is critical">
198
+ The `scopedTo` value is used by the platform to track ownership of every entity created by bootstrap. All subsequent runs use it to decide which entities to update or delete. **Never change this value after the first deployment** — doing so will cause the platform to treat all previously bootstrapped entities as orphaned.
199
+ </Aside>
200
+
201
+ ### Complete Example
202
+
203
+ ```typescript
204
+ import { bootstrapPlatform } from '@bluealba/pae-bootstrap-lib';
205
+ import { join } from 'path';
206
+
207
+ bootstrapPlatform({
208
+ path: join(__dirname, '../data'),
209
+ scopedTo: process.env.SERVICE_ACCESS_NAME!,
210
+ exclude: ['experimental-app'],
211
+ initialDelay: 5000,
212
+ retries: 10,
213
+ // throwOnError defaults to false — errors are logged internally,
214
+ // the process stays alive and the service keeps responding to health checks
215
+ });
216
+ ```
217
+
218
+ ---
219
+
220
+ ## Configuration Files
221
+
222
+ ### Application Definition
223
+
224
+ Declare application metadata in `application.json`:
225
+
226
+ ```json
227
+ {
228
+ "name": "crm",
229
+ "displayName": "Customer Relationship Management",
230
+ "description": "CRM application for managing customer relationships",
231
+ "allowedByDefault": false,
232
+ "allowedTenants": ["tenant-a", "tenant-b"]
233
+ }
234
+ ```
235
+
236
+ **Fields**:
237
+ - **name**: Unique application identifier (kebab-case)
238
+ - **displayName**: Human-readable name shown in the UI
239
+ - **description**: Brief description of the application
240
+ - **allowedByDefault**: If `true`, all users have access by default; if `false` (recommended), access must be explicitly granted
241
+ - **allowedTenants**: Optional array of tenant names allowed to access this application. If omitted, existing tenant restrictions are not modified by bootstrap. If set to an empty array `[]`, all restrictions are cleared (application becomes available to all tenants). If populated, only the listed tenants will have access.
242
+
243
+ <Aside type="tip" title="Security Best Practice">
244
+ Set `allowedByDefault` to `false` for business applications to ensure explicit access control. Only platform-wide utilities should default to `true`.
245
+ </Aside>
246
+
247
+ ---
248
+
249
+ ### Tenant Application Restrictions
250
+
251
+ The `allowedTenants` field in `application.json` lets bootstrap manage which tenants can access each application. This controls the `tenant_application_restrictions` table.
252
+
253
+ **Semantics**:
254
+ - **`allowedTenants` omitted** — bootstrap does not touch existing restrictions for this application (safe default, no regressions).
255
+ - **`allowedTenants: []`** — all existing restrictions are removed; the application becomes available to all tenants.
256
+ - **`allowedTenants: ["tenant-a", "tenant-b"]`** — restrictions are fully replaced; only the listed tenants have access.
257
+
258
+ Each bootstrap run performs a full replace (delete + re-insert) when the field is present, so the table always reflects the latest declaration.
259
+
260
+ ```json
261
+ {
262
+ "name": "crm",
263
+ "allowedByDefault": false,
264
+ "allowedTenants": ["acme-corp", "beta-company"]
265
+ }
266
+ ```
267
+
268
+ <Aside type="tip" title="Tenant Names">
269
+ Use the tenant `name` field (not display name or ID). Bootstrap resolves names to IDs at sync time and logs a warning for any unrecognized tenant names without failing the sync.
270
+ </Aside>
271
+
272
+ ---
273
+
274
+ ### Module Definitions
275
+
276
+ Define services and UIs in `modules.json`:
277
+
278
+ <Tabs>
279
+ <TabItem label="Service Module">
280
+ **Backend Service (Microservice)**
281
+
282
+ ```json
283
+ [
284
+ {
285
+ "name": "@mycompany/crm-service",
286
+ "displayName": "CRM Service",
287
+ "type": "service",
288
+ "baseUrl": "/api/crm",
289
+ "service": {
290
+ "host": "crm-service",
291
+ "port": 4002
292
+ },
293
+ "dependsOn": []
294
+ }
295
+ ]
296
+ ```
297
+
298
+ **Fields**:
299
+ - **name**: Module name (matches package.json name)
300
+ - **displayName**: Human-readable name
301
+ - **type**: Set to `"service"` for backend services
302
+ - **baseUrl**: URL prefix for this service's endpoints
303
+ - **service.host**: Container/service hostname
304
+ - **service.port**: Service port number
305
+ - **dependsOn**: Array of module names this service depends on
306
+ </TabItem>
307
+
308
+ <TabItem label="UI Module">
309
+ **Frontend UI (Micro-frontend)**
310
+
311
+ ```json
312
+ [
313
+ {
314
+ "name": "@mycompany/crm-ui",
315
+ "displayName": "CRM Application",
316
+ "type": "app",
317
+ "baseUrl": "/crm-ui",
318
+ "service": {
319
+ "host": "crm-ui",
320
+ "port": 80
321
+ },
322
+ "ui": {
323
+ "route": "/crm",
324
+ "mountAtSelector": "#pae-shell-ui-content",
325
+ "bundleFile": "mycompany-crm-ui.js",
326
+ "customProps": {
327
+ "theme": "corporate",
328
+ "features": {
329
+ "darkMode": true
330
+ }
331
+ }
332
+ },
333
+ "dependsOn": [
334
+ "@bluealba/pae-shell-ui"
335
+ ]
336
+ }
337
+ ]
338
+ ```
339
+
340
+ **Fields**:
341
+ - **type**: Set to `"app"` for UI modules
342
+ - **ui.route**: Browser route where this UI is accessible
343
+ - **ui.mountAtSelector**: DOM selector where the UI mounts
344
+ - **ui.bundleFile**: JavaScript bundle filename
345
+ - **ui.customProps**: Configuration passed to the UI module as single-spa custom props. Supports an optional `tenantOverrides` key — see [Tenant-Specific customProps Overrides](#tenant-specific-customprops-overrides) for details.
346
+ - **dependsOn**: Typically depends on `pae-shell-ui`
347
+ </TabItem>
348
+ </Tabs>
349
+
350
+ <Aside type="note">
351
+ See the [Catalog](/architecture/catalog/) documentation for complete module specification reference.
352
+ </Aside>
353
+
354
+ <Aside type="tip">
355
+ If your service runs on a different host or port locally than it does inside Docker, use `serviceLocal` to declare the local-only values without touching the `service` field. See [Local Development Overrides](#local-development-overrides-servicelocal) for details.
356
+ </Aside>
357
+
358
+ ---
359
+
360
+ ### Operations Definition
361
+
362
+ Declare authorization operations in `operations.json`:
363
+
364
+ ```json
365
+ [
366
+ {
367
+ "name": "crm.customers.read",
368
+ "description": "View customer information",
369
+ "roles": ["CRM User", "CRM Admin"]
370
+ },
371
+ {
372
+ "name": "crm.customers.write",
373
+ "description": "Create and update customers",
374
+ "roles": ["CRM Admin"]
375
+ },
376
+ {
377
+ "name": "crm.customers.delete",
378
+ "description": "Delete customer records",
379
+ "roles": ["CRM Admin"]
380
+ }
381
+ ]
382
+ ```
383
+
384
+ **Fields**:
385
+ - **name**: Unique operation identifier (use dot notation)
386
+ - **description**: What this operation allows
387
+ - **roles**: Array of role names that include this operation
388
+
389
+ **Naming Convention**:
390
+ ```
391
+ <application>.<resource>.<action>
392
+
393
+ Examples:
394
+ - crm.customers.read
395
+ - crm.deals.write
396
+ - inventory.products.delete
397
+ - reports.sales.export
398
+ ```
399
+
400
+ ---
401
+
402
+ ### Roles Definition
403
+
404
+ Declare roles in `roles.json`:
405
+
406
+ ```json
407
+ [
408
+ {
409
+ "name": "CRM User",
410
+ "description": "Standard CRM user with read access"
411
+ },
412
+ {
413
+ "name": "CRM Admin",
414
+ "description": "CRM administrator with full access"
415
+ }
416
+ ]
417
+ ```
418
+
419
+ **Fields**:
420
+ - **name**: Unique role name
421
+ - **description**: What this role represents
422
+
423
+ **Roles vs Operations**:
424
+ - **Operations**: Granular permissions (e.g., `crm.customers.read`)
425
+ - **Roles**: Groups of operations assigned to users
426
+ - Users are assigned **roles**, which grant them **operations**
427
+
428
+ ---
429
+
430
+ ### Module Configuration Overrides
431
+
432
+ Override module configurations in `modules-config.json`:
433
+
434
+ ```json
435
+ [
436
+ {
437
+ "name": "@bluealba/pae-shell-ui",
438
+ "config": {
439
+ "platformName": "My Company Platform",
440
+ "canToggleNavbarState": true,
441
+ "isMenuCollapsible": true,
442
+ "showOpenMenuButton": true,
443
+ "navbar": {
444
+ "menus": {
445
+ "applications": [
446
+ {
447
+ "type": "app",
448
+ "name": "crm",
449
+ "iconUrl": "/crm-ui/assets/icon.svg"
450
+ }
451
+ ]
452
+ }
453
+ }
454
+ }
455
+ },
456
+ {
457
+ "name": "@mycompany/crm-ui",
458
+ "config": {
459
+ "defaultView": "dashboard",
460
+ "enableExport": true
461
+ }
462
+ }
463
+ ]
464
+ ```
465
+
466
+ **Purpose**:
467
+ - Customize module behavior without modifying module code
468
+ - Set product-specific branding and features
469
+ - Configure UI layouts and behavior
470
+ - Pass environment-specific settings
471
+
472
+ <Aside type="tip">
473
+ You don't need to re-declare the entire module definition - only include the `name` and the `config` properties you want to override.
474
+ </Aside>
475
+
476
+ ---
477
+
478
+ ### Tenant-Specific customProps Overrides
479
+
480
+ The `ui.customProps` object supports an optional reserved key, `tenantOverrides`, that lets you vary the props delivered to a UI module on a per-tenant basis — all from a single module definition.
481
+
482
+ #### How It Works
483
+
484
+ When the gateway builds the catalog response for a tenant, it:
485
+
486
+ 1. Reads the module's `ui.customProps` object.
487
+ 2. Looks up the current tenant's **name** in the `tenantOverrides` map.
488
+ 3. Shallow-merges the matching tenant's overrides on top of the base `customProps`.
489
+ 4. Strips the `tenantOverrides` key from the response — clients never see it.
490
+
491
+ If no entry exists for the current tenant in `tenantOverrides`, the base `customProps` values are returned unchanged (still without the `tenantOverrides` key).
492
+
493
+ #### Example
494
+
495
+ ```json
496
+ {
497
+ "name": "@mycompany/crm-ui",
498
+ "displayName": "CRM Application",
499
+ "type": "app",
500
+ "baseUrl": "/crm-ui",
501
+ "service": {
502
+ "host": "crm-ui",
503
+ "port": 80
504
+ },
505
+ "ui": {
506
+ "route": "/crm",
507
+ "mountAtSelector": "#pae-shell-ui-content",
508
+ "bundleFile": "mycompany-crm-ui.js",
509
+ "customProps": {
510
+ "platformName": "My Company Platform",
511
+ "canToggleNavbarState": true,
512
+ "isMenuCollapsible": true,
513
+ "tenantOverrides": {
514
+ "acme-corp": {
515
+ "isMenuCollapsible": false,
516
+ "platformName": "ACME Platform"
517
+ }
518
+ }
519
+ }
520
+ },
521
+ "dependsOn": ["@bluealba/pae-shell-ui"]
522
+ }
523
+ ```
524
+
525
+ When the requesting tenant is `acme-corp`, the gateway returns:
526
+
527
+ ```json
528
+ {
529
+ "platformName": "ACME Platform",
530
+ "canToggleNavbarState": true,
531
+ "isMenuCollapsible": false
532
+ }
533
+ ```
534
+
535
+ When the requesting tenant is anything else, the gateway returns:
536
+
537
+ ```json
538
+ {
539
+ "platformName": "My Company Platform",
540
+ "canToggleNavbarState": true,
541
+ "isMenuCollapsible": true
542
+ }
543
+ ```
544
+
545
+ <Aside type="note" title="Tenant name, not tenant ID">
546
+ The key inside `tenantOverrides` must match the tenant's **name** (the `name` field on the tenant record), not its numeric ID or its `code`. This is the same value that appears in the tenant list in the Admin UI.
547
+ </Aside>
548
+
549
+ <Aside type="tip">
550
+ `tenantOverrides` is most commonly used in `modules-config.json` overrides for platform-level modules such as `@bluealba/pae-shell-ui`, where you want shell behaviour (for example, collapsible menus or branding strings) to differ between tenants without duplicating module definitions.
551
+ </Aside>
552
+
553
+ ---
554
+
555
+ ### Jobs Definition
556
+
557
+ Declare scheduled jobs for the application in `jobs.json`:
558
+
559
+ ```json
560
+ [
561
+ {
562
+ "name": "crm-health-check",
563
+ "description": "Periodic health check of the CRM service",
564
+ "enabled": true,
565
+ "pattern": "*/5 * * * *",
566
+ "taskType": "HttpInvokerTask",
567
+ "taskConfig": {
568
+ "url": "http://crm-service/health",
569
+ "method": "GET",
570
+ "timeout": 5000
571
+ }
572
+ },
573
+ {
574
+ "name": "crm-daily-sync",
575
+ "description": "Synchronizes CRM data with the external source every day at 2 AM",
576
+ "enabled": true,
577
+ "pattern": "0 2 * * *",
578
+ "taskType": "HttpInvokerTask",
579
+ "taskConfig": {
580
+ "url": "http://crm-service/api/sync",
581
+ "method": "POST",
582
+ "body": { "type": "full" },
583
+ "headers": { "Content-Type": "application/json" },
584
+ "timeout": 30000
585
+ }
586
+ }
587
+ ]
588
+ ```
589
+
590
+ **Fields**:
591
+ - **name**: Unique job name within the application
592
+ - **description**: Optional human-readable description of the job's purpose
593
+ - **enabled**: Whether the job should be active after synchronization (defaults to `true`)
594
+ - **pattern**: Standard cron expression defining the execution schedule
595
+ - **taskType**: The type of task to execute (currently `"HttpInvokerTask"` is supported)
596
+ - **taskConfig**: Task-specific configuration object
597
+
598
+ **`taskConfig` fields for `HttpInvokerTask`**:
599
+ - **url**: Full URL of the endpoint to invoke
600
+ - **method**: HTTP method (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, etc.)
601
+ - **body**: Optional request body (objects are automatically serialized to JSON)
602
+ - **headers**: Optional custom HTTP headers
603
+ - **auth**: Optional authentication configuration (e.g., `{ "authMethod": "bearer", "token": "..." }`)
604
+ - **timeout**: Optional request timeout in milliseconds
605
+
606
+ **Ownership and Sync Behavior**:
607
+
608
+ The bootstrap sync engine manages the full lifecycle of jobs it defines. Ownership is tracked via the `updatedBy` field — when bootstrap creates or updates a job it sets both `createdBy` and `updatedBy` to its own `scopedTo` value:
609
+
610
+ - Jobs in `jobs.json` that do not yet exist are **created** automatically
611
+ - Jobs that already exist and whose `updatedBy` still matches the bootstrap scope are **updated** to match the latest definition
612
+ - Jobs that were previously created by bootstrap but are no longer present in `jobs.json` are **deleted**
613
+ - Jobs that were originally created by bootstrap but have since been manually edited (i.e., `updatedBy` was changed to a user's username via the Admin UI) are **skipped** — a **WARNING** is logged indicating the record was manually modified
614
+ - Jobs whose `createdBy` does not match the bootstrap scope at all are **skipped silently**
615
+
616
+ <Aside type="note">
617
+ Editing a job in the Admin UI changes its `updatedBy` field to the editing user's username, which effectively transfers ownership away from bootstrap. Bootstrap will not overwrite those manual changes on subsequent runs. To hand ownership back to bootstrap, delete the job and let bootstrap recreate it from `jobs.json`.
618
+ </Aside>
619
+
620
+ This same ownership logic applies to all synchronized entities — not only jobs, but also applications, roles, operations, modules, feature flags, and shared libraries.
621
+
622
+ <Aside type="note">
623
+ See the [Scheduler Module](/_/docs/architecture/scheduler/) documentation for full details on job types, cron syntax, and dynamic parameter support.
624
+ </Aside>
625
+
626
+ ---
627
+
628
+ ### Feature Flags Definition
629
+
630
+ Declare feature flags for the application in `feature-flags.json`:
631
+
632
+ ```json
633
+ [
634
+ {
635
+ "name": "new-checkout-flow",
636
+ "description": "Enable the redesigned checkout experience",
637
+ "visible": true,
638
+ "defaultValue": false,
639
+ "valueRules": []
640
+ },
641
+ {
642
+ "name": "beta-reporting",
643
+ "description": "Enable advanced reporting for beta users",
644
+ "visible": true,
645
+ "defaultValue": false,
646
+ "valueRules": [
647
+ {
648
+ "name": "beta-user-access",
649
+ "condition": {
650
+ "attribute": "user",
651
+ "operator": "in",
652
+ "value": ["beta-user-1@example.com", "beta-user-2@example.com"]
653
+ },
654
+ "value": true
655
+ }
656
+ ]
657
+ },
658
+ {
659
+ "name": "new-search-experience",
660
+ "description": "Gradual rollout of the new search UI",
661
+ "visible": true,
662
+ "defaultValue": false,
663
+ "valueRules": [
664
+ {
665
+ "name": "acme-10pct-rollout",
666
+ "condition": {
667
+ "attribute": "tenant",
668
+ "operator": "eq",
669
+ "value": "acme-corp"
670
+ },
671
+ "value": true,
672
+ "rollout": {
673
+ "percentage": 10,
674
+ "attribute": "username"
675
+ }
676
+ }
677
+ ]
678
+ }
679
+ ]
680
+ ```
681
+
682
+ **Fields**:
683
+ - **name**: Unique flag identifier (kebab-case, immutable after creation)
684
+ - **description**: Human-readable description of what the flag controls
685
+ - **visible**: Master switch; set to `false` to disable the flag entirely without deleting it
686
+ - **defaultValue**: Value returned when no value rule matches the evaluation context
687
+ - **valueRules**: Ordered list of conditional rules; the first matching rule's value is returned
688
+
689
+ **Application association**: The flag's application is inferred automatically from the folder that contains the `feature-flags.json` file. The bootstrap library reads the application name from the co-located `application.json` and attaches it to each flag at sync time. You do not include an `applicationName` field in the JSON.
690
+
691
+ **Upsert semantics**:
692
+
693
+ The bootstrap operation is idempotent and performs an **upsert** for each flag:
694
+
695
+ - **Flag does not exist**: Creates the flag with the provided configuration.
696
+ - **Flag already exists**: Updates the flag metadata (`description`, `visible`, `defaultValue`, `application_id` (derived from the enclosing application folder)) and **replaces all existing value rules** with the ones declared in the bootstrap configuration.
697
+
698
+ All operations for a single bootstrap call run in a single database transaction (all-or-nothing). If any flag fails, the entire bootstrap call is rolled back.
699
+
700
+ <Aside type="tip">
701
+ Because bootstrap performs a full value rule replacement on existing flags, keep your bootstrap declarations up to date. Removing a value rule from the bootstrap configuration will remove it from the live flag on the next bootstrap run.
702
+ </Aside>
703
+
704
+ <Aside type="note">
705
+ See the [Feature Flags guide](/guides/using-feature-flags/) for the full reference on value rules, conditions, operators, and percentage rollouts.
706
+ </Aside>
707
+
708
+ ---
709
+
710
+ ## Environment Variables in Configuration
711
+
712
+ Bootstrap JSON files support the `{{$ENV.VARIABLE_NAME}}` syntax to inject environment variable values at startup. This lets you write a single set of JSON files that works across all environments — development, staging, and production — without hardcoding hostnames, ports, URLs, or any other value that changes per environment.
713
+
714
+ ### Syntax
715
+
716
+ The expression language supports four variants:
717
+
718
+ | Variant | Syntax | Behaviour when variable is missing |
719
+ |---|---|---|
720
+ | Required | `{{$ENV.VARIABLE_NAME}}` | Fails immediately with an error |
721
+ | Optional | `{{$ENV.VARIABLE_NAME?}}` | Returns `undefined`; key is omitted from the parent object |
722
+ | Default | `{{$ENV.VARIABLE_NAME?=default_value}}` | Falls back to the literal `default_value` |
723
+ | Chain | `{{$ENV.VARIABLE_NAME?=$ENV.FALLBACK?=literal}}` | Tries each reference left to right; uses the first that resolves |
724
+
725
+ The value is read from the environment variable at bootstrap startup time, before any validation occurs.
726
+
727
+ **Example** — `modules.json` using environment variables for service host and port:
728
+
729
+ ```json
730
+ [
731
+ {
732
+ "name": "@mycompany/crm-service",
733
+ "displayName": "CRM Service",
734
+ "type": "service",
735
+ "baseUrl": "/api/crm",
736
+ "service": {
737
+ "host": "{{$ENV.CRM_SERVICE_HOST}}",
738
+ "port": "{{$ENV.CRM_SERVICE_PORT}}"
739
+ },
740
+ "dependsOn": []
741
+ }
742
+ ]
743
+ ```
744
+
745
+ When the bootstrap service starts with `CRM_SERVICE_HOST=crm-service` and `CRM_SERVICE_PORT=4002`, the resolved configuration becomes:
746
+
747
+ ```json
748
+ {
749
+ "host": "crm-service",
750
+ "port": 4002
751
+ }
752
+ ```
753
+
754
+ ### Optional and Default Values
755
+
756
+ Use the `?` and `?=` suffixes to make expressions tolerant of missing environment variables.
757
+
758
+ **Example** — `modules-config.json` with a mix of required, optional, default, and chained expressions:
759
+
760
+ ```json
761
+ {
762
+ "host": "{{$ENV.CRM_HOST}}",
763
+ "port": "{{$ENV.CRM_PORT?=4002}}",
764
+ "debugPort": "{{$ENV.DEBUG_PORT?}}",
765
+ "logLevel": "{{$ENV.LOG_LEVEL?=$ENV.DEFAULT_LOG_LEVEL?=info}}"
766
+ }
767
+ ```
768
+
769
+ Given that only `CRM_HOST=crm-service` is set in the environment and none of the others are, the resolved configuration becomes:
770
+
771
+ ```json
772
+ {
773
+ "host": "crm-service",
774
+ "port": 4002,
775
+ "logLevel": "info"
776
+ }
777
+ ```
778
+
779
+ What happened to each field:
780
+ - **`host`** — resolved normally from the required `CRM_HOST` variable.
781
+ - **`port`** — `CRM_PORT` was not set, so the literal default `4002` was used and coerced to a number.
782
+ - **`debugPort`** — `DEBUG_PORT` was not set and the expression is optional (`?`), so the key is **omitted entirely** from the resolved object.
783
+ - **`logLevel`** — `LOG_LEVEL` was not set, then `DEFAULT_LOG_LEVEL` was not set, so the chain fell through to the literal `"info"`.
784
+
785
+ **Default value type coercion**
786
+
787
+ Literal defaults go through the same coercion rules as regular env var values:
788
+
789
+ | Expression | Default literal | Resolved as |
790
+ |---|---|---|
791
+ | `"{{$ENV.PORT?=8080}}"` | `8080` | `8080` (number) |
792
+ | `"{{$ENV.ENABLED?=true}}"` | `true` | `true` (boolean) |
793
+ | `"{{$ENV.HOST?=localhost}}"` | `localhost` | `"localhost"` (string) |
794
+
795
+ **Fallback chain references**
796
+
797
+ References inside a fallback chain can use any registered namespace, not just `ENV`. The engine tries each segment from left to right and uses the first one that resolves successfully.
798
+
799
+ ```json
800
+ {
801
+ "serviceUrl": "{{$ENV.SERVICE_URL?=$ENV.LEGACY_SERVICE_URL?=http://default-service}}"
802
+ }
803
+ ```
804
+
805
+ ---
806
+
807
+ ### Resolution Modes
808
+
809
+ <Tabs>
810
+ <TabItem label="Full-value">
811
+ When the entire JSON string value is a single `{{$ENV.X}}` expression, the result is **automatically coerced to the correct type**:
812
+
813
+ | JSON value | Env var value | Resolved as |
814
+ |---|---|---|
815
+ | `"{{$ENV.PORT}}"` | `"4002"` | `4002` (number) |
816
+ | `"{{$ENV.ENABLED}}"` | `"true"` | `true` (boolean) |
817
+ | `"{{$ENV.ENABLED}}"` | `"false"` | `false` (boolean) |
818
+ | `"{{$ENV.HOST}}"` | `"crm-service"` | `"crm-service"` (string) |
819
+
820
+ This means you can use environment variables for numeric fields like `port` without any extra casting — just write the expression and the bootstrap system handles the type.
821
+ </TabItem>
822
+
823
+ <TabItem label="Inline (embedded in text)">
824
+ When the expression is embedded inside a larger string, it always resolves as a string:
825
+
826
+ ```json
827
+ {
828
+ "url": "http://{{$ENV.CRM_HOST}}:{{$ENV.CRM_PORT}}/api/v1"
829
+ }
830
+ ```
831
+
832
+ With `CRM_HOST=crm-service` and `CRM_PORT=4002`:
833
+
834
+ ```json
835
+ {
836
+ "url": "http://crm-service:4002/api/v1"
837
+ }
838
+ ```
839
+
840
+ Multiple expressions can appear in the same string value.
841
+
842
+ When an optional inline expression (`?`) resolves to `undefined`, it collapses to an empty string `""` rather than omitting the key (key omission only applies to full-value mode).
843
+ </TabItem>
844
+
845
+ <TabItem label="Optional and Default">
846
+ The `?` and `?=` suffixes control what happens when a variable is not set:
847
+
848
+ | JSON value | Env var value | Resolved as |
849
+ |---|---|---|
850
+ | `"{{$ENV.PORT?=4002}}"` | not set | `4002` (number, from default) |
851
+ | `"{{$ENV.ENABLED?=true}}"` | not set | `true` (boolean, from default) |
852
+ | `"{{$ENV.HOST?}}"` | not set | key omitted (optional, full-value mode) |
853
+ | `"{{$ENV.HOST?=fallback}}"` | `"my-host"` | `"my-host"` (env var wins over default) |
854
+
855
+ Default literals undergo the same type coercion as live env var values — a default of `4002` is returned as the number `4002`, not the string `"4002"`.
856
+
857
+ When the env var is present, it always takes precedence over any default or fallback in the chain.
858
+ </TabItem>
859
+ </Tabs>
860
+
861
+ ### Practical Example: Multi-Environment Setup
862
+
863
+ The same JSON files work in all environments by changing only the environment variables:
864
+
865
+ <Tabs>
866
+ <TabItem label="modules.json">
867
+ ```json
868
+ [
869
+ {
870
+ "name": "@mycompany/crm-service",
871
+ "displayName": "CRM Service",
872
+ "type": "service",
873
+ "baseUrl": "/api/crm",
874
+ "service": {
875
+ "host": "{{$ENV.CRM_SERVICE_HOST}}",
876
+ "port": "{{$ENV.CRM_SERVICE_PORT}}"
877
+ },
878
+ "dependsOn": []
879
+ }
880
+ ]
881
+ ```
882
+ </TabItem>
883
+
884
+ <TabItem label="Development">
885
+ ```bash
886
+ # docker-compose.yml (dev)
887
+ CRM_SERVICE_HOST=crm-service
888
+ CRM_SERVICE_PORT=4002
889
+ ```
890
+ </TabItem>
891
+
892
+ <TabItem label="Staging">
893
+ ```bash
894
+ # docker-compose.yml (staging)
895
+ CRM_SERVICE_HOST=crm-staging.internal
896
+ CRM_SERVICE_PORT=443
897
+ ```
898
+ </TabItem>
899
+
900
+ <TabItem label="Production">
901
+ ```bash
902
+ # docker-compose.yml (production)
903
+ CRM_SERVICE_HOST=crm.internal.prod
904
+ CRM_SERVICE_PORT=443
905
+ ```
906
+ </TabItem>
907
+ </Tabs>
908
+
909
+ <Aside type="caution" title="Required variables must be defined">
910
+ This only applies to **required** expressions (no `?` or `?=` suffix). If the bootstrap service starts and a required environment variable is not set, the bootstrap process will **fail immediately** with an error like:
911
+
912
+ ```
913
+ Error reading modules.json file: Expression resolution failed: {{$ENV.CRM_SERVICE_HOST}} is not defined in environment variables
914
+ ```
915
+
916
+ Make sure every required `{{$ENV.X}}` expression has a corresponding environment variable configured in your Docker Compose file or container environment.
917
+
918
+ Optional expressions (`{{$ENV.X?}}`) and expressions with defaults (`{{$ENV.X?=value}}`) will **not** fail if the variable is missing — they either omit the key or fall back to the provided default.
919
+ </Aside>
920
+
921
+ <Aside type="tip" title="Supported in all configuration files">
922
+ The `{{$ENV.X}}` syntax works in every bootstrap JSON file:
923
+ `application.json`, `modules.json`, `operations.json`, `roles.json`, `modules-config.json`, `jobs.json`, and `shared-libraries.json`.
924
+ </Aside>
925
+
926
+ ---
927
+
928
+ ## Local Development Overrides (serviceLocal)
929
+
930
+ When running the platform locally, Docker service hostnames (such as `crm-service` or `habits-service`) do not resolve on the developer's machine. The gateway bootstrap sends those hostnames to the platform, so any request it proxies will fail because `crm-service` is not reachable from the host network.
931
+
932
+ The usual workaround is to set `{{$ENV.*}}` expressions on every `service` field that differs between Docker and local development. The `serviceLocal` feature provides a simpler, more declarative alternative.
933
+
934
+ ### How It Works
935
+
936
+ Each module in `modules.json` accepts an optional `serviceLocal` field. It has the same shape as the `service` field (`Partial<ServiceConfig>`), so you only need to include the properties that differ locally.
937
+
938
+ When the bootstrap library starts:
939
+
940
+ - If `LOCAL_MODE=true`, `serviceLocal` is **shallow-merged into `service`**, replacing any overlapping fields. The merged result is what gets sent to the gateway.
941
+ - In all other cases (including when `LOCAL_MODE` is absent or set to any other value), `serviceLocal` is **silently stripped** and the original `service` definition is used unchanged.
942
+
943
+ The gateway never receives the `serviceLocal` field regardless of mode.
944
+
945
+ ### Example
946
+
947
+ ```json
948
+ [
949
+ {
950
+ "name": "@mycompany/crm-service",
951
+ "displayName": "CRM Service",
952
+ "type": "service",
953
+ "baseUrl": "/api/crm",
954
+ "service": {
955
+ "host": "crm-service",
956
+ "port": 80
957
+ },
958
+ "serviceLocal": {
959
+ "host": "host.docker.internal",
960
+ "port": 3001
961
+ },
962
+ "dependsOn": []
963
+ }
964
+ ]
965
+ ```
966
+
967
+ With `LOCAL_MODE=true`, the resolved `service` sent to the gateway becomes:
968
+
969
+ ```json
970
+ {
971
+ "host": "host.docker.internal",
972
+ "port": 3001
973
+ }
974
+ ```
975
+
976
+ Without `LOCAL_MODE` (or with any value other than `'true'`), the original `service` is used:
977
+
978
+ ```json
979
+ {
980
+ "host": "crm-service",
981
+ "port": 80
982
+ }
983
+ ```
984
+
985
+ ### ServiceConfig Fields
986
+
987
+ Both `service` and `serviceLocal` accept the following fields:
988
+
989
+ | Field | Type | Required | Default | Description |
990
+ |---|---|---|---|---|
991
+ | `host` | `string` | Yes (in `service`) | — | Hostname or IP address the gateway uses to reach the service |
992
+ | `port` | `number` | Yes (in `service`) | — | Port number the gateway connects to |
993
+ | `protocol` | `string` | No | `"http"` | Transport protocol. Set to `"https"` if the service uses TLS |
994
+ | `includeBaseURL` | `boolean` | No | `false` | When `true`, the module's `baseUrl` path is forwarded as-is to the service instead of being stripped |
995
+
996
+ In `serviceLocal`, all fields are optional — only include the ones that differ from the Docker definition.
997
+
998
+ ### Behavior Summary
999
+
1000
+ | Condition | `serviceLocal` present | Result |
1001
+ |---|---|---|
1002
+ | `LOCAL_MODE=true` | Yes | `serviceLocal` shallow-merged into `service`; merged value sent to gateway |
1003
+ | `LOCAL_MODE=true` | No | `service` sent to gateway unchanged |
1004
+ | `LOCAL_MODE` not set or not `'true'` | Yes | `serviceLocal` stripped; original `service` sent to gateway |
1005
+ | `LOCAL_MODE` not set or not `'true'` | No | `service` sent to gateway unchanged |
1006
+
1007
+ <Aside type="note">
1008
+ `{{$ENV.*}}` expressions work inside `serviceLocal` values just as they do inside `service`. You can use expressions such as `"{{$ENV.CRM_LOCAL_PORT?=4002}}"` within `serviceLocal` to keep local port numbers out of version control.
1009
+ </Aside>
1010
+
1011
+ <Aside type="caution" title="LOCAL_MODE is case-sensitive">
1012
+ The bootstrap library checks for the exact string `'true'`. Values such as `TRUE`, `True`, or `1` will not activate local mode. Set the variable as `LOCAL_MODE=true` in your local environment or `.env` file.
1013
+ </Aside>
1014
+
1015
+ ---
1016
+
1017
+ ## Bootstrap Execution
1018
+
1019
+ ### Startup Sequence
1020
+
1021
+ When the bootstrap service starts:
1022
+
1023
+ ```
1024
+ ┌─────────────────────────────────────────────────────────┐
1025
+ │ 1. Bootstrap Service Starts │
1026
+ │ • Reads all JSON files from data/ directory │
1027
+ │ • Parses and validates JSON structure │
1028
+ └────────────────────┬────────────────────────────────────┘
1029
+
1030
+
1031
+ ┌─────────────────────────────────────────────────────────┐
1032
+ │ 2. Synchronize Catalog │
1033
+ │ • Compare modules.json with existing catalog │
1034
+ │ • Create new modules, update existing ones │
1035
+ │ • Apply module-config.json overrides │
1036
+ └────────────────────┬────────────────────────────────────┘
1037
+
1038
+
1039
+ ┌─────────────────────────────────────────────────────────┐
1040
+ │ 3. Synchronize Shared Libraries │
1041
+ │ • Register UI shared libraries │
1042
+ │ • Update library versions │
1043
+ └────────────────────┬────────────────────────────────────┘
1044
+
1045
+
1046
+ ┌─────────────────────────────────────────────────────────┐
1047
+ │ 4. Synchronize Applications │
1048
+ │ • Create/update application records │
1049
+ │ • Set application metadata │
1050
+ └────────────────────┬────────────────────────────────────┘
1051
+
1052
+
1053
+ ┌─────────────────────────────────────────────────────────┐
1054
+ │ 5. Synchronize Roles │
1055
+ │ • Create new roles │
1056
+ │ • Update role descriptions │
1057
+ └────────────────────┬────────────────────────────────────┘
1058
+
1059
+
1060
+ ┌─────────────────────────────────────────────────────────┐
1061
+ │ 6. Synchronize Operations │
1062
+ │ • Create new operations │
1063
+ │ • Bind operations to roles │
1064
+ │ • Update operation descriptions │
1065
+ └────────────────────┬────────────────────────────────────┘
1066
+
1067
+
1068
+ ┌─────────────────────────────────────────────────────────┐
1069
+ │ 7. Synchronize Scheduled Jobs │
1070
+ │ • Create new jobs from bootstrap definitions │
1071
+ │ • Update existing bootstrap-owned jobs │
1072
+ │ • Delete bootstrap-owned jobs no longer defined │
1073
+ │ • Skip jobs owned by other sources │
1074
+ └────────────────────┬────────────────────────────────────┘
1075
+
1076
+
1077
+ ┌─────────────────────────────────────────────────────────┐
1078
+ │ 7.5. Synchronize Tenant Application Restrictions │
1079
+ │ • For each app with allowedTenants defined: │
1080
+ │ - Delete existing restrictions for the app │
1081
+ │ - Re-insert rows for resolved tenant names │
1082
+ │ • Apps without allowedTenants are skipped │
1083
+ └────────────────────┬────────────────────────────────────┘
1084
+
1085
+
1086
+ ┌─────────────────────────────────────────────────────────┐
1087
+ │ 8. Synchronize Feature Flags │
1088
+ │ • Create/update feature flags from feature-flags.json│
1089
+ │ • Replace value rules for existing flags │
1090
+ │ • Delete bootstrap-owned flags no longer defined │
1091
+ └────────────────────┬────────────────────────────────────┘
1092
+
1093
+
1094
+ ┌─────────────────────────────────────────────────────────┐
1095
+ │ 9. Bootstrap Complete │
1096
+ │ • Log summary of changes │
1097
+ │ • Service ready to handle requests │
1098
+ └─────────────────────────────────────────────────────────┘
1099
+ ```
1100
+
1101
+ ### Example Output
1102
+
1103
+ ```log
1104
+ 2025-01-09 09:54:08 > platform-bootstrap-service@1.0.0 start
1105
+ 2025-01-09 09:54:10 [nodemon] starting `ts-node src/main.ts`
1106
+ 2025-01-09 09:54:40 Server listening on Port 80
1107
+ 2025-01-09 09:54:45 Synchronizing Catalog - START ...
1108
+ 2025-01-09 09:54:47 Synchronizing Catalog - COMPLETED
1109
+ 2025-01-09 09:54:47 Synchronizing shared-libraries - START ...
1110
+ 2025-01-09 09:54:48 >>> shared-libraries to ADD []
1111
+ 2025-01-09 09:54:48 >>> shared-libraries to UPDATE []
1112
+ 2025-01-09 09:54:48 Synchronizing shared-libraries - COMPLETED
1113
+ 2025-01-09 09:54:48 Synchronizing authorization-applications - START ...
1114
+ 2025-01-09 09:54:52 >>> authorization-applications to ADD []
1115
+ 2025-01-09 09:54:52 >>> authorization-applications to UPDATE ['crm', 'inventory']
1116
+ 2025-01-09 09:54:53 Synchronizing authorization-applications - COMPLETED
1117
+ 2025-01-09 09:54:53 Synchronizing authorization-roles - START ...
1118
+ 2025-01-09 09:54:53 >>> authorization-roles to ADD ['CRM User']
1119
+ 2025-01-09 09:54:53 >>> authorization-roles to UPDATE []
1120
+ 2025-01-09 09:54:53 Synchronizing authorization-roles - COMPLETED
1121
+ 2025-01-09 09:54:53 Synchronizing authorization-operations - START ...
1122
+ 2025-01-09 09:54:53 >>> authorization-operations to ADD ['crm.customers.read']
1123
+ 2025-01-09 09:54:53 >>> authorization-operations to UPDATE []
1124
+ 2025-01-09 09:54:53 Synchronizing authorization-operations - COMPLETED
1125
+ 2025-01-09 09:54:53 Synchronizing scheduled-jobs - START ...
1126
+ 2025-01-09 09:54:53 [Bootstrap] Created job 'crm-daily-sync'
1127
+ 2025-01-09 09:54:53 [Bootstrap] Updated job 'crm-health-check'
1128
+ 2025-01-09 09:54:53 Synchronizing scheduled-jobs - COMPLETED
1129
+ ```
1130
+
1131
+ ---
1132
+
1133
+ ## Development Workflow
1134
+
1135
+ ### Making Changes
1136
+
1137
+ 1. **Edit JSON Files**: Modify the appropriate JSON file in the `data/` directory
1138
+
1139
+ ```bash
1140
+ # Example: Add a new operation
1141
+ vim data/crm/operations.json
1142
+ ```
1143
+
1144
+ 2. **Restart Bootstrap Service**: Changes are applied automatically on restart
1145
+
1146
+ ```bash
1147
+ # Docker Compose
1148
+ docker-compose restart platform-bootstrap-service
1149
+
1150
+ # Or with Turbo (for development)
1151
+ npm run dev --workspace=platform-bootstrap-service
1152
+ ```
1153
+
1154
+ 3. **Verify Changes**: Check the service logs to confirm synchronization
1155
+
1156
+ 4. **Commit to Version Control**: Push changes to share with the team
1157
+
1158
+ ```bash
1159
+ git add data/
1160
+ git commit -m "Add CRM customer operations"
1161
+ git push
1162
+ ```
1163
+
1164
+ ### Environment Propagation
1165
+
1166
+ The same bootstrap configuration is used across all environments:
1167
+
1168
+ ```
1169
+ Development → Staging → Production
1170
+ ↓ ↓ ↓
1171
+ Same JSON Same JSON Same JSON
1172
+ ↓ ↓ ↓
1173
+ Bootstrap service automatically synchronizes on startup
1174
+ ```
1175
+
1176
+ **Benefits**:
1177
+ - Consistent configuration across environments — the same JSON files deploy everywhere
1178
+ - Use `{{$ENV.VARIABLE_NAME}}` in any JSON value to inject environment-specific values (hostnames, ports, URLs) without duplicating files
1179
+ - Version-controlled platform state
1180
+ - Easy rollback via Git
1181
+ - No manual database updates needed
1182
+
1183
+ ---
1184
+
1185
+ ## Common Patterns
1186
+
1187
+ ### Adding a New Application
1188
+
1189
+ <Tabs>
1190
+ <TabItem label="1. Create Folder">
1191
+ ```bash
1192
+ mkdir -p data/my-new-app
1193
+ cd data/my-new-app
1194
+ ```
1195
+ </TabItem>
1196
+
1197
+ <TabItem label="2. Define Application">
1198
+ Create `application.json`:
1199
+ ```json
1200
+ {
1201
+ "name": "my-new-app",
1202
+ "displayName": "My New Application",
1203
+ "description": "Description of the new application",
1204
+ "allowedByDefault": false,
1205
+ "allowedTenants": ["my-tenant"]
1206
+ }
1207
+ ```
1208
+ </TabItem>
1209
+
1210
+ <TabItem label="3. Add Modules">
1211
+ Create `modules.json`:
1212
+ ```json
1213
+ [
1214
+ {
1215
+ "name": "@mycompany/my-new-app-service",
1216
+ "displayName": "My New App Service",
1217
+ "type": "service",
1218
+ "baseUrl": "/api/my-new-app",
1219
+ "service": {
1220
+ "host": "my-new-app-service",
1221
+ "port": 4010
1222
+ }
1223
+ },
1224
+ {
1225
+ "name": "@mycompany/my-new-app-ui",
1226
+ "displayName": "My New App UI",
1227
+ "type": "app",
1228
+ "baseUrl": "/my-new-app-ui",
1229
+ "service": {
1230
+ "host": "my-new-app-ui",
1231
+ "port": 80
1232
+ },
1233
+ "ui": {
1234
+ "route": "/my-app",
1235
+ "mountAtSelector": "#pae-shell-ui-content",
1236
+ "bundleFile": "mycompany-my-new-app-ui.js"
1237
+ },
1238
+ "dependsOn": ["@bluealba/pae-shell-ui"]
1239
+ }
1240
+ ]
1241
+ ```
1242
+ </TabItem>
1243
+
1244
+ <TabItem label="4. Add Authorization">
1245
+ Create `operations.json`:
1246
+ ```json
1247
+ [
1248
+ {
1249
+ "name": "my-new-app.read",
1250
+ "description": "View my new app",
1251
+ "roles": ["User"]
1252
+ },
1253
+ {
1254
+ "name": "my-new-app.write",
1255
+ "description": "Edit my new app",
1256
+ "roles": ["Admin"]
1257
+ }
1258
+ ]
1259
+ ```
1260
+
1261
+ Create `roles.json`:
1262
+ ```json
1263
+ [
1264
+ {
1265
+ "name": "My App User",
1266
+ "description": "Standard user of my new app"
1267
+ }
1268
+ ]
1269
+ ```
1270
+ </TabItem>
1271
+
1272
+ <TabItem label="5. Add Jobs (Optional)">
1273
+ Create `jobs.json` to define recurring scheduled tasks for the application:
1274
+ ```json
1275
+ [
1276
+ {
1277
+ "name": "my-new-app-health-check",
1278
+ "description": "Periodic health check of the my-new-app service",
1279
+ "enabled": true,
1280
+ "pattern": "*/5 * * * *",
1281
+ "taskType": "HttpInvokerTask",
1282
+ "taskConfig": {
1283
+ "url": "http://my-new-app-service:4010/health",
1284
+ "method": "GET",
1285
+ "timeout": 5000
1286
+ }
1287
+ }
1288
+ ]
1289
+ ```
1290
+
1291
+ Jobs are owned by the bootstrap scope: they will be created, updated, or deleted automatically as the `jobs.json` file changes.
1292
+ </TabItem>
1293
+
1294
+ <TabItem label="6. Restart Bootstrap">
1295
+ ```bash
1296
+ docker-compose restart platform-bootstrap-service
1297
+
1298
+ # Watch logs
1299
+ docker-compose logs -f platform-bootstrap-service
1300
+ ```
1301
+ </TabItem>
1302
+ </Tabs>
1303
+
1304
+ ---
1305
+
1306
+ ## Best Practices
1307
+
1308
+ <CardGrid>
1309
+ <Card title="Use Version Control" icon="github">
1310
+ Always commit bootstrap configurations to Git for history and collaboration
1311
+ </Card>
1312
+
1313
+ <Card title="Follow Naming Conventions" icon="pencil">
1314
+ Use consistent naming: kebab-case for names, dot notation for operations
1315
+ </Card>
1316
+
1317
+ <Card title="Document Changes" icon="document">
1318
+ Include clear descriptions for all operations, roles, and applications
1319
+ </Card>
1320
+
1321
+ <Card title="Test Before Production" icon="approve-check">
1322
+ Verify bootstrap changes in development before deploying to production
1323
+ </Card>
1324
+
1325
+ <Card title="Minimal Permissions" icon="shield">
1326
+ Set `allowedByDefault: false` and grant explicit access as needed
1327
+ </Card>
1328
+
1329
+ <Card title="Organize by Application" icon="seti:folder">
1330
+ Keep each application's definitions in its own folder for clarity
1331
+ </Card>
1332
+ </CardGrid>
1333
+
1334
+ ---
1335
+
1336
+ ## Troubleshooting
1337
+
1338
+ ### Bootstrap Service Won't Start
1339
+
1340
+ **Check logs for errors**:
1341
+ ```bash
1342
+ docker-compose logs platform-bootstrap-service
1343
+ ```
1344
+
1345
+ **Common causes**:
1346
+ - Invalid JSON syntax
1347
+ - Missing required fields
1348
+ - Duplicate names
1349
+ - Network connectivity to Platform APIs
1350
+
1351
+ ### Changes Not Applied
1352
+
1353
+ **Verify the bootstrap service restarted**:
1354
+ ```bash
1355
+ docker-compose ps platform-bootstrap-service
1356
+ ```
1357
+
1358
+ **Check synchronization logs**:
1359
+ ```bash
1360
+ docker-compose logs platform-bootstrap-service | grep "Synchronizing"
1361
+ ```
1362
+
1363
+ ### Module Not Appearing in Catalog
1364
+
1365
+ **Check module definition**:
1366
+ - Verify `name` is unique
1367
+ - Confirm `service.host` and `service.port` are correct
1368
+ - Ensure `dependsOn` modules exist
1369
+
1370
+ **Verify module is accessible**:
1371
+ ```bash
1372
+ # Test service endpoint
1373
+ curl http://my-new-app-service:4010/health
1374
+ ```
1375
+
1376
+ ### Operations Not Working
1377
+
1378
+ **Verify operation binding**:
1379
+ - Check operation name matches in `operations.json`
1380
+ - Confirm role is listed in operation's `roles` array
1381
+ - Verify users have the role assigned
1382
+
1383
+ **Test authorization**:
1384
+ ```bash
1385
+ # Get user's authorized resources
1386
+ curl https://platform.com/authz/users/{username}/resources
1387
+ ```
1388
+
1389
+ ### Environment Variable Not Resolved
1390
+
1391
+ **Error message**:
1392
+ ```
1393
+ Error reading <file>.json file: Expression resolution failed: {{$ENV.VARIABLE_NAME}} is not defined in environment variables
1394
+ ```
1395
+
1396
+ **Check that the variable is set** in the bootstrap service container:
1397
+ ```bash
1398
+ docker-compose exec platform-bootstrap-service env | grep VARIABLE_NAME
1399
+ ```
1400
+
1401
+ **Common causes**:
1402
+ - Variable not declared in the `environment:` or `env_file:` section of the bootstrap service in `docker-compose.yml`
1403
+ - Typo in the variable name in the JSON file
1404
+ - `.env` file not loaded or missing
1405
+
1406
+ **Make expressions tolerant of missing variables**:
1407
+
1408
+ If a variable is genuinely optional (e.g. a debug port that is only relevant in some environments), use the `?` or `?=` suffixes instead of requiring the variable to always be present:
1409
+
1410
+ ```json
1411
+ {
1412
+ "port": "{{$ENV.CRM_PORT?=4002}}",
1413
+ "debugPort": "{{$ENV.DEBUG_PORT?}}",
1414
+ "logLevel": "{{$ENV.LOG_LEVEL?=$ENV.DEFAULT_LOG_LEVEL?=info}}"
1415
+ }
1416
+ ```
1417
+
1418
+ - `?=4002` — uses `4002` when `CRM_PORT` is not set (no error, no missing key)
1419
+ - `?` — omits the `debugPort` key entirely when `DEBUG_PORT` is not set
1420
+ - `?=$ENV.DEFAULT_LOG_LEVEL?=info` — tries a fallback variable, then a literal default
1421
+
1422
+ ---
1423
+
1424
+ ## Migration from Old Bootstrap
1425
+
1426
+ If migrating from an older bootstrap structure:
1427
+
1428
+ 1. **Review Current Definitions**: Export existing catalog and authorization data
1429
+ 2. **Create New Structure**: Organize by application following the new folder structure
1430
+ 3. **Test Synchronization**: Run bootstrap in development environment
1431
+ 4. **Validate Results**: Verify all modules and operations are correctly registered
1432
+ 5. **Deploy**: Roll out to staging and production
1433
+
1434
+ ---
1435
+
1436
+ ## Next Steps
1437
+
1438
+ - **[Catalog Architecture](/_/docs/architecture/catalog/)** - Understanding the application catalog
1439
+ - **[Authorization System](/_/docs/architecture/authorization-system/)** - Deep dive into roles and operations
1440
+ - **[Shell Customization](/_/docs/architecture/shell/)** - Customizing the Platform Shell via bootstrap
1441
+ - **[Scheduler Module](/_/docs/architecture/scheduler/)** - Full reference for job types, cron syntax, and dynamic parameters
1442
+ - **[Feature Flags](/guides/using-feature-flags/)** - Using feature flags in services and React applications