Module Registry — Overview
The Module Registry is the platform's module lifecycle manager. Every capability installed on the platform — CRM, iGaming, analytics, custom tools — is a module registered here. The Registry controls discovery, activation, versioning, migration execution, and deactivation of all modules.
Technical Stack
| Component | Technology |
|---|---|
| Language | Go (net/http + chi router) |
| Database | PostgreSQL — manifest history, module state |
| Cache | Valkey (valkey-io/valkey-go) |
| Bundle storage | S3 bucket |
| CDN | Versioned immutable URLs |
| Migrations | goose — runs module SQL migrations on install/update |
| gRPC | Protobuf definition |
Paths & Schemas
Cache (SET):
modules:active:{tenantId}
Bundle storage:
modules/{tenantId}/modules/{name}/{version}/
CDN:
https://cdn.platform.io/modules/{tenantId}/{moduleId}/{version}/remoteEntry.js
gRPC:
proto/platform/module_registry/v1/module_registry_service.proto
Module Lifecycle
REGISTER → DISCOVER → LOAD → MOUNT → CONNECT → RUN → UPDATE → DISABLE
| Stage | Description |
|---|---|
| REGISTER | Module registered via POST /api/v1/modules with module.manifest.json |
| DISCOVER | UI Shell discovers module via GET /api/v1/modules/active |
| LOAD | Module Federation 2.0 loads remote bundle from CDN |
| MOUNT | MFE component rendered in the shell zone |
| CONNECT | Module subscribes to Event Bus events (events.subscribes[]) |
| RUN | Module operates, accesses platform primitives via SDK |
| UPDATE | Independent deploy without shell rebuild. Cache invalidation via module.registry.updated event. Users online at update time see a toast: "Module {name} updated. Refresh to apply." |
| ROLLBACK | Registry switches entry CDN URL back to previous version — instant rollback |
| DISABLE | Deactivated via Registry. Data is preserved. Module removed from Shell. |
| INSTALL (state machine) | pending → installing → active / failed. UI Shell shows module only when status = active. Timeout: MODULE_INSTALL_TIMEOUT_SEC=120. Retry: POST /modules/:id/retry-install |
Install Methods
All methods call the same underlying POST /api/v1/modules/install
endpoint:
| Method | Who uses it | How |
|---|---|---|
| Marketplace | Tenant admin | Opens module catalog → clicks "Install" |
| CLI | Developer | npx @platform/cli module install @scope/module-name |
| REST API | CI/CD, automation | POST /api/v1/modules/install { "source": "url or package name" } |
Bundle Hosting Flow
POST /api/v1/modules/install { "source": "@scope/crm" }
Step 1: Registry downloads bundle from npm / Git URL
Step 2: Upload to S3: {tenantId}/modules/{name}/{version}/remoteEntry.js
Step 3: SRI hash check (SHA-384): manifest hash vs actual file hash
→ mismatch → 422 Unprocessable Entity: "Bundle integrity check failed"
Step 4: entry field rewritten → CDN URL
Step 5: Versioned CDN URL stored: modules:active:{tenantId} updated in Valkey
Step 6: status: active (all steps complete)
Bundle integrity (SRI SHA-384) prevents supply chain attacks — the same pattern used by Apple App Store and Google Play.
Module Manifest
Each module describes itself with a JSON manifest submitted when registering:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | ✅ | Unique npm-scope format: @scope/module-name |
version | string | ✅ | Semver: X.Y.Z (strict) |
description | string | ✅ | Short human-readable description |
entry | string | ✅ | Remote entry URL for Module Federation |
exposedComponent | string | ✅ | Name of the exported React component |
route | string | ✅ | URL path in the sidebar |
icon | string | ✅ | Icon name for the sidebar menu |
permissions | string[] | ✅ | Permissions the module registers (min 1, format: lowercase.dot.notation) |
dependencies | object | ✅ | Dependencies — minimum @platform/sdk |
healthCheck | string | ✅ | Path for the module's health check endpoint |
kernelSdkVersion | string | ✅ | Semver range: "^1.0.0" — minimum compatible kernel SDK |
events.publishes | string[] | ❌ | Events the module publishes |
events.subscribes | string[] | ❌ | Events the module subscribes to |
notificationChannels | array | ❌ | Notification channel adapters provided by the module |
authProviders | array | ❌ | Auth provider adapters provided by the module |
embeds | array | ❌ | Widgets for embedding on external sites |
dashboardWidget | object | ❌ | Tenant dashboard widget: { component, title, defaultSize } |
category | string | ❌ | Sidebar grouping: crm, analytics, finance, hr, marketing, custom |
dataApi | object | ❌ | Auto-generates REST endpoints for module models |
Maximum manifest size: 64 KB.
Manifest Validation Rules
| Rule | Detail |
|---|---|
| All required fields present | Missing any → 400 Bad Request |
name format | Must match npm-scope regex @scope/module-name |
version format | Strict semver X.Y.Z |
kernelSdkVersion | Semver range. Incompatible with current kernel SDK → 409 Conflict |
permissions[] | Minimum 1. Each must start with {moduleId}. prefix. system.* and platform.* reserved → 400 Bad Request |
Duplicate name | 409 Conflict |
| Max size | 64 KB → 413 Request Entity Too Large |
Versioning and Rollback
Module Registry stores the full history of all manifest versions. Previous versions are archived, not deleted.
Developer publishes v2.0:
Registry registers new version
Admin sees "Update available" in Admin → Modules
Admin confirms → Registry sets tenant entry URL to v2.0 CDN URL
Health check fails within 60 seconds of update:
Auto-rollback → entry URL switched back to v1.9 (still on CDN, immutable)
Admin notified: "Module update failed, rolled back to v1.9"
Manual rollback (Admin):
POST /api/v1/modules/:id/rollback { "targetVersion": "1.9.0" }
→ Registry switches entry URL to v1.9 CDN URL → instant
| Parameter | Value |
|---|---|
| Version history | Stored indefinitely (text records, minimal size) |
| CDN bundles retained | Last 5 versions per module. Older versions → S3 Lifecycle delete |
| Deactivated module bundles | Retained for 90 days after deactivation, then deleted |
| Module data (PostgreSQL tables) | Retained indefinitely |
| Auto-rollback health check timeout | 60 seconds after UPDATE |
| Health check response deadline | 5 seconds (MODULE_HEALTH_TIMEOUT_MS) |
Rollback is code-only: Rollback switches the JavaScript bundle. It does not roll back database migrations. Migrations are forward-only. Module code must be backward-compatible with the schema of the newer version (new columns added as
DEFAULT NULL, unknown fields ignored).
Module Data Migrations
Modules store data in PostgreSQL using their own tables, isolated
by RLS per tenant_id. The Registry runs migrations automatically:
| Stage | What happens |
|---|---|
| Install | Registry validates manifest → runs migrations/ via goose → tables created with auto tenant_id RLS policy |
| Update | New version may include new migrations → Registry runs goose up incrementally |
| Deactivate | Tables and data remain. Module stops loading in UI Shell. |
| Uninstall | Soft delete. Data kept with marker. Full clean-up requires Platform Owner CLI. |
Table namespace: Each module gets its own PostgreSQL schema:
module_{moduleSlug}. Example: module_crm.contacts. Two modules
can have a table named contacts without conflict.
Migration failure: If a migration fails, the module enters
ERROR status. The UI Shell does not load it. Previous successful
migrations remain. There is no automatic rollback (would risk data
loss). Fix: developer publishes a hotfix → PATCH /modules/:id →
Registry retries pending migrations.
SDK Major Upgrade Policy
When the kernel SDK releases a new major version (e.g. v1.x → v2.x):
| Step | What happens |
|---|---|
| 1 | Registry checks kernelSdkVersion for ALL installed modules |
| 2 | Incompatible modules → status INCOMPATIBLE. Not loaded in UI Shell but data intact |
| 3 | Tenant notified: "Module X requires update. Contact the module developer." |
| 4 | Deprecation period: 12 months — old major SDK version supported in parallel |
| 5 | After deadline: incompatible modules stop loading. Data preserved. Update restores. |
REST API
| Method | Endpoint | Description |
|---|---|---|
POST | /api/v1/modules | Register a new module (submit manifest) |
GET | /api/v1/modules | List all modules (paginated) |
GET | /api/v1/modules/active | List active modules for this tenant |
GET | /api/v1/modules/:id | Get module details and current status |
PATCH | /api/v1/modules/:id | Update manifest (new version) |
PATCH | /api/v1/modules/:id/activate | Activate a registered module |
PATCH | /api/v1/modules/:id/deactivate | Deactivate module (data preserved) |
DELETE | /api/v1/modules/:id | Uninstall module (soft delete) |
GET | /api/v1/modules/:id/health | Module health check status |
POST | /api/v1/modules/install | Install module by package name or URL |
GET | /api/v1/modules/:id/versions | List all manifest versions |
POST | /api/v1/modules/:id/rollback | Rollback to a specific version |
POST | /api/v1/modules/:id/retry-install | Retry a failed installation |
Kafka Events Published
| Event | Payload | Description |
|---|---|---|
module.registry.registered | { moduleId, name, version } | New module registered |
module.registry.activated | { moduleId } | Module activated for tenant |
module.registry.deactivated | { moduleId } | Module deactivated |
module.registry.updated | { moduleId, version } | New version deployed — triggers UI Shell cache invalidation |