Module Manifest
The module.manifest.json is the single source of truth that describes
everything the Platform-Kernel needs to know about your module. Without a valid
manifest a module cannot be registered, loaded, or receive any SDK
primitives.
Contract principle. The manifest is validated by OpenAPI schema on every
POST /api/v1/modules call. A rejected manifest means the module never enters
the REGISTER lifecycle stage.
Anatomy
{
"name": "@acme/crm",
"version": "1.2.0",
"description": "Customer Relationship Management for SeptemCore tenants",
"kernelSdkVersion": "^1.0.0",
"entry": "https://cdn.septemcore.com/modules/{tenantId}/acme-crm/1.2.0/remoteEntry.js",
"exposedComponent": "CrmModule",
"route": "/crm",
"icon": "users",
"healthCheck": "/api/v1/crm/health",
"category": "crm",
"author": "ACME Corp",
"permissions": [
"crm.contacts.read",
"crm.contacts.write",
"crm.deals.read",
"crm.deals.write"
],
"events": {
"publishes": ["crm.contact.created", "crm.deal.closed"],
"subscribes": ["auth.user.created", "billing.plan.changed"]
},
"dataApi": {
"models": ["contacts", "deals"],
"hooks": {
"contacts.create.before": "validateContact",
"contacts.create.after": "notifyTeam"
}
},
"notificationChannels": [],
"authProviders": [],
"embeds": [],
"dashboardWidget": {
"component": "CrmDashboardWidget",
"title": "CRM Overview",
"defaultSize": "medium"
},
"dependencies": {
"@platform/sdk-core": "^1.0.0",
"@platform/sdk-data": "^1.0.0",
"@platform/sdk-events": "^1.0.0"
}
}
Field Reference
Identity Fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | ✅ | Unique module identifier in npm-scope format: @scope/module-name. Regex-validated. 409 Conflict if already registered. |
version | string | ✅ | Strict semver X.Y.Z. No ranges, no pre-release suffixes in production. |
description | string | ✅ | Short human-readable description. Max 255 characters. |
kernelSdkVersion | string | ✅ | Semver range the module is compatible with, e.g. ^1.0.0. If the running kernel SDK is outside this range, registration returns 409 Conflict. |
author | string | ☐ | Publisher display name. Shown in Admin → Modules catalog. |
category | string | ☐ | Sidebar grouping hint: crm, analytics, finance, hr, marketing, custom. |
UI Shell Integration
| Field | Type | Required | Description |
|---|---|---|---|
entry | string | ✅ | URL of the Module Federation remote entry. Do not set manually (see note below). |
exposedComponent | string | ✅ | Name of the exported React component that the Shell mounts as a page. Must match the key in exposes of federation.config.js. |
route | string | ✅ | URL path in the admin sidebar, e.g. /crm. Must be unique across all active modules for a tenant. |
icon | string | ✅ | Icon identifier from @platform/sdk-ui icon set, e.g. users, chart-bar, wallet. |
healthCheck | string | ✅ | Relative path for health probing by Module Registry, e.g. /api/v1/crm/health. Auto-rollback fires if this path does not respond 200 within 60 s after deployment. |
dashboardWidget | object | ✅ | Opt-in widget for the tenant dashboard. See Dashboard Widget below. |
CDN Entry URL Resolution
Do not set the entry field manually in your source manifest. The
Module Registry automatically overwrites it with the production
CDN URL after a successful upload:
https://cdn.septemcore.com/modules/{tenantId}/{name}/{version}/remoteEntry.js
RBAC & Security
| Field | Type | Required | Description |
|---|---|---|---|
permissions | string[] | ✅ | Permissions this module registers. Each must start with the module slug prefix: crm.contacts.read. At least 1 required. Auto-registered in IAM on install — appear immediately in the Role Builder. |
Permission auto-registration. You do not call any API manually
— Module Registry calls registerPermissions() internally after
successful installation. Wild-card format {module}.* grants full
access to all module permissions.
Permission Naming Convention
- Format:
{moduleSlug}.{resource}.{action}(e.g.crm.contacts.read) - All lowercase, dot-separated
- Prefixes
system.*andplatform.*are reserved — using them returns400 Bad Request
Event Bus Contract
"events": {
"publishes": ["crm.contact.created", "crm.deal.closed"],
"subscribes": ["auth.user.created", "billing.plan.changed"]
}
| Field | Type | Required | Description |
|---|---|---|---|
events.publishes | string[] | ☐ | Event types this module is allowed to publish. Any kernel.events().publish() call with a type not in this list is rejected at the SDK layer. |
events.subscribes | string[] | ☐ | Event types this module may subscribe to. Tenant isolation is enforced: the SDK automatically appends tenantId from JWT — no cross-tenant leakage is possible. |
Kernel-owned events are write-protected. Events under auth.*,
money.*, billing.*, audit.* can only be published by kernel
services. A module attempting to publish them receives
403 Forbidden.
Data API (autoApi)
The dataApi field activates automatic CRUD endpoint generation for
SQL-migrated models:
"dataApi": {
"models": ["contacts", "deals"],
"hooks": {
"contacts.create.before": "validateContact",
"contacts.create.after": "notifyTeam"
}
}
| Sub-field | Description |
|---|---|
models | Array of table names (without schema prefix). Each listed model gets 5 REST endpoints automatically: POST, GET /:id, GET (list), PATCH /:id, DELETE /:id. |
hooks | Lifecycle hooks keyed by {model}.{operation}.{timing}. Synchronous before hooks can reject the request; asynchronous after hooks emit events. |
For example, model contacts in module crm generates the following endpoints:
POST /api/v1/data/crm/contacts
GET /api/v1/data/crm/contacts
GET /api/v1/data/crm/contacts/:id
PATCH /api/v1/data/crm/contacts/:id
DELETE /api/v1/data/crm/contacts/:id
Permission reconciliation. After each migration run, Data Layer
scans information_schema and reconciles permissions: new tables
→ register {module}.{model}.read/write/delete; removed tables
→ archive those permissions. Controlled by env
DATA_PERMISSION_RECONCILIATION_ON_MIGRATE=true.
Notification Channels
"notificationChannels": [
{
"identifier": "slack",
"name": "Slack",
"icon": "slack-logo",
"setupComponent": "SlackSetupForm"
}
]
| Sub-field | Description |
|---|---|
identifier | Unique string for this channel adapter, e.g. slack, telegram. |
name | Display name shown in Settings → Notifications. |
icon | Icon identifier from @platform/sdk-ui. |
setupComponent | Name of the React component (from the module bundle) that renders the configuration form in Settings → Notifications. |
When a module with notificationChannels is installed, the channel
automatically appears in Settings → Notifications for the tenant without any
kernel code change.
Auth Providers
"authProviders": [
{
"id": "metamask",
"type": "wallet",
"name": "MetaMask",
"icon": "metamask-logo",
"setupComponent": "MetaMaskSetupForm"
}
]
| Sub-field | Description |
|---|---|
id | Unique provider identifier. Used in POST /api/v1/auth/login/{id} and callback routes. |
type | Provider category: oauth, wallet, social, banking, enterprise, custom. |
name | Display name on the login page. |
setupComponent | Settings UI component for provider configuration. |
Embeds
Web Component widgets for embedding on external tenant websites:
"embeds": [
{
"name": "payment-button",
"tag": "crm-payment-button",
"script": "./dist/embed.js",
"params": ["amount", "currency", "order_id"]
}
]
Tenants copy the generated embed code from Settings → Module → Embed and paste
it into any HTML page. The script authenticates via tenantId in the URL — no
additional auth required on the consumer side.
Dashboard Widget
"dashboardWidget": {
"component": "CrmDashboardWidget",
"title": "CRM Overview",
"defaultSize": "medium"
}
| Sub-field | Description |
|---|---|
component | Exported React component name from the module bundle. |
title | Widget title displayed on the tenant dashboard. |
defaultSize | Layout hint: small (1×1), medium (2×1), large (2×2). Tenant can resize. |
Dependencies
"dependencies": {
"@platform/sdk-core": "^1.0.0",
"@platform/sdk-data": "^1.0.0",
"@platform/sdk-events": "^1.0.0"
}
List all @platform/* SDK packages your module uses. Module Registry validates
kernelSdkVersion against each dependency range. The shared: EnterpriseSingletons in federation.config.js ensures React and all platform
singletons remain deduplicated in the Shell.
Validation Rules
| Rule | Behavior |
|---|---|
| Missing required field | 400 Bad Request with RFC 9457 error detail |
name not in @scope/module format | 400 Bad Request |
Duplicate name | 409 Conflict |
kernelSdkVersion incompatible with running kernel | 409 Conflict with version mismatch description |
permissions[] contains system.* or platform.* prefix | 400 Bad Request |
| Manifest JSON body > 64 KB | 413 Payload Too Large |
version not strict semver | 400 Bad Request |
Lifecycle After Registration
Installation steps (all must succeed for active status):
- Manifest saved to Module Registry PostgreSQL
- JS bundle uploaded to S3:
{tenantId}/modules/{name}/{version}/remoteEntry.js - SRI hash verified (SHA-384) — supply-chain attack prevention
- Routes registered in API Gateway via Envoy xDS (zero-downtime)
- Permissions registered in IAM (idempotent upsert)
- Health check passed within 60 s (
healthCheckpath →200 OK)
If any step fails, the module status becomes failed. The POST /api/v1/modules/:id/retry-install endpoint retries from the failed step.
SDK Version Compatibility Matrix
| Scenario | Outcome |
|---|---|
kernelSdkVersion: "^1.0.0" + kernel SDK 1.5.0 | ✅ Compatible (minor/patch are backward-compatible) |
kernelSdkVersion: "^1.0.0" + kernel SDK 2.0.0 | ❌ INCOMPATIBLE — module does not load; 12-month deprecation period |
kernelSdkVersion: "^2.0.0" + kernel SDK 1.5.0 | ❌ 409 Conflict at registration time |
Use @platform/sdk-codegen to auto-regenerate TypeScript types from the
current OpenAPI spec after every kernel minor release. This keeps your
module forward-compatible without manual updates.