Skip to main content

First Deployment

This guide walks through the complete lifecycle for pushing a new or updated module into the Platform-Kernel — from building the Module Federation bundle to canary rollout and rollback.

important

Module Registry is the authoritative gate. No module code ever executes in the admin Shell unless Module Registry has status = active for that module and tenant. There is no bypass.


Publication Lifecycle Overview


Step 1 — Build Your Module

All modules use the shared @platform/build-config Vite preset which configures Module Federation 2.0. Run the production build from your module directory:

cd packages/my-module
pnpm build

The build generates:

dist/
├── remoteEntry.js # Module Federation entry point
├── assets/
│ ├── index-[hash].js # Code-split chunks (immutable, long cache TTL)
│ └── index-[hash].css
└── module.manifest.json # Copied from src — validated before upload

Verify the bundle is well-formed:

# Check remoteEntry.js exports the declared component
node -e "
const { readFileSync } = require('fs');
const m = JSON.parse(readFileSync('./dist/module.manifest.json', 'utf8'));
console.log('name:', m.name, ' version:', m.version);
"

federation.config.js

The Module Federation config file in every module follows this pattern (generated by npx @platform/create-module):

import { EnterpriseSingletons } from '@kernel/build-config';

export default {
name: 'my-module',
exposes: {
'./Module': './src/pages/index.tsx',
},
// Versions managed centrally in @kernel/build-config (SSOT).
// Do NOT hardcode requiredVersion here.
shared: EnterpriseSingletons,
};
danger

Never hardcode requiredVersion for shared dependencies. If React versions drift between your module and the Shell, Module Federation will fail to load the module (instead of crashing, it shows an Error Boundary). Centralised version management via EnterpriseSingletons prevents this.


Step 2 — Register or Update the Module

First Registration

Use the CLI to register your module with the sandbox or target environment:

npx @platform/create-module publish \
--manifest dist/module.manifest.json \
--bundle dist/remoteEntry.js \
--gateway http://localhost:8080 \
--token $PLATFORM_ACCESS_TOKEN

Or POST directly to the API:

curl -s -X POST https://api.septemcore.com/v1/modules/install \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source": "@acme/[email protected]",
"tenantId": "00000000-0000-7000-0000-000000000001"
}' | jq .status

The registry response (201 Created):

{
"id": "mod_01jwabcxyz",
"name": "@acme/crm",
"version": "1.2.0",
"status": "pending",
"installedAt": "2026-04-20T14:30:00Z"
}

Poll until status reaches active or failed:

watch -n 2 'curl -s https://api.septemcore.com/v1/modules/mod_01jwabcxyz \
-H "Authorization: Bearer $TOKEN" | jq .status'

Updating an Existing Module

Publish a new version — Module Registry automatically versions the manifest and the S3 bundle:

curl -s -X PATCH https://api.septemcore.com/v1/modules/mod_01jwabcxyz \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"version": "1.3.0",
"source": "@acme/[email protected]"
}'

On update, Module Registry:

  1. Uploads the new bundle to {tenantId}/modules/@acme/crm/1.3.0/remoteEntry.js in S3
  2. Archives the v1.2.0 manifest (kept for rollback)
  3. Publishes module.registry.updated event to the Event Bus
  4. The UI Shell invalidates its localStorage cache for this module
  5. Online users see a toast: "Module CRM updated. Refresh to apply."

Step 3 — Installation Steps Inside the Kernel

Each installation or update runs these steps sequentially. All steps must succeed for status = active:

StepWhat HappensFailure Outcome
1. Manifest validationOpenAPI schema check (required fields, semver format, permission prefix, max 64 KB)400 Bad Request — never reaches pending
2. SDK compatibilitykernelSdkVersion checked against running kernel SDK409 Conflict — never reaches pending
3. Bundle downloadRegistry fetches JS bundle from npm / Git URL / direct uploadfailed
4. SRI hash verificationSHA-384 of downloaded bundle verified against integrity field in manifest422 Unprocessable Entity
5. S3 uploadBundle uploaded to tenant's S3 isolation zone (see paths below)failed
6. CDN URL generationentry field rewritten to CDN URLfailed
7. Route registrationEnvoy xDS RDS update — new route registered without restartfailed
8. Permission registrationIdempotent upsert of permissions[] into IAMfailed
9. Migration executiongoose up runs SQL migrations from module's migrations/ directoryfailed + migrationError field
10. Health checkGET {healthCheck} must return 200 OK within 60 sAuto-rollback if timeout
note

Storage Paths & CDN (Steps 5 & 6) The physical bundle is stored in S3 at: {tenantId}/modules/{name}/{version}/remoteEntry.js

The module's entry is then automatically rewritten to the public CDN: https://cdn.septemcore.com/modules/...

tip

Retry a failed installation without re-uploading:

curl -s -X POST https://api.septemcore.com/v1/modules/mod_01jwabcxyz/retry-install \
-H "Authorization: Bearer $TOKEN"

Step 4 — Feature Flag Canary Rollout

For production deployments, use the Feature Flag Service to release to a subset of tenants before full rollout:

# Create a flag scoped to a specific tenant (canary group)
curl -s -X POST https://api.septemcore.com/v1/flags \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"key": "crm_v130_rollout",
"description": "CRM module v1.3.0 canary rollout",
"enabled": true,
"targeting": [
{
"query": "tenantId == \"00000000-0000-7000-0000-000000000001\"",
"variation": "true",
"percentage": 100
}
],
"defaultVariation": "false"
}'

In your module, gate new features behind the flag:

import { kernel } from '@platform/sdk-core';

const isNewDashboardEnabled = await kernel.flags().isEnabled('crm_v130_rollout');

if (isNewDashboardEnabled) {
return <NewDashboard />;
}
return <LegacyDashboard />;

SDK caching: The flag client caches evaluations in memory and polls GoFeatureFlag every 15 seconds. If GoFeatureFlag is temporarily unreachable, the SDK serves the stale cached value. On empty cache (first boot), all flags default to false.

Gradual rollout — increase percentage incrementally:

# Ramp from 10% → 50% → 100% with PATCH /api/v1/flags/:key
curl -s -X PATCH https://api.septemcore.com/v1/flags/crm_v130_rollout \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"targeting": [{"percentage": 50}]}'

Step 5 — Rollback

Manual Rollback

If a deployment has issues, roll back to the previous stable version via the API:

curl -s -X POST https://api.septemcore.com/v1/modules/mod_01jwabcxyz/rollback \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"targetVersion": "1.2.0"}'

Module Registry switches the tenant's entry URL from the v1.3.0 CDN path back to v1.2.0 — instant (no re-upload, the old bundle is immutable on CDN).

Automatic Health-Check Rollback

If the new version's healthCheck endpoint does not return 200 OK within 60 seconds after update, Module Registry triggers automatic rollback:

  1. Previous stable version manifest is restored
  2. Envoy xDS route updated to previous CDN URL
  3. module.registry.rollback event published to Event Bus
  4. Admin notification sent via Notify (email + in-app)
danger

Rollback is code-only, not data. SQL migrations are forward-only. Rolling back code means the v1.2.0 code runs against the v1.3.0 schema. All migrations must be backward-compatible (new columns must have DEFAULT NULL, removed columns must be ignored at the application layer before the migration that drops them).

List Version History

curl -s https://api.septemcore.com/v1/modules/mod_01jwabcxyz/versions \
-H "Authorization: Bearer $TOKEN" | jq '.[] | {version, createdAt, status}'

S3 Bundle Retention Policy

ContentRetention
Module manifest historyIndefinite (text records, negligible storage)
JS bundles (active module)Last 5 versions per module — older versions deleted by S3 Lifecycle Rule automatically
JS bundles (deactivated module)90 days after deactivation, then deleted
Module data (PostgreSQL tables)Indefinite — billed via storage_bytes in Billing Service

CI/CD Integration

The typical CI pipeline for a module looks like this:

# .github/workflows/deploy-module.yml
jobs:
deploy:
steps:
- uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4
with: { version: '10' }

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm --filter "@acme/crm" build

- name: Publish to Platform-Kernel
env:
PLATFORM_TOKEN: ${{ secrets.PLATFORM_DEPLOY_TOKEN }}
run: |
npx @platform/create-module publish \
--manifest packages/crm/dist/module.manifest.json \
--bundle packages/crm/dist/remoteEntry.js \
--gateway https://api.septemcore.com \
--token "$PLATFORM_TOKEN"

- name: Wait for active status
run: |
npx @platform/create-module wait-active \
--module "@acme/crm" \
--gateway https://api.septemcore.com \
--token "$PLATFORM_TOKEN" \
--timeout 120
tip

Use a dedicated deploy token (roles.assign + modules.install permissions only) — never use a personal access token in CI. Rotate tokens every 90 days in line with the Vault key-rotation policy.


Troubleshooting

Module stuck in installing

Check the installation step that failed:

curl -s https://api.septemcore.com/v1/modules/mod_01jwabcxyz \
-H "Authorization: Bearer $TOKEN" | jq '{status, migrationError, lastStep}'

Common causes:

SymptomCauseFix
migrationError: "column X already exists"Non-idempotent SQL migrationWrap in IF NOT EXISTS
status: failed at step 4SRI hash mismatchRe-build and re-publish — do not modify bundle after computing hash
status: failed at step 7Envoy xDS timeoutCheck docker compose logs gateway — usually a transient issue; retry
Health check timeouthealthCheck path returns non-200Fix the endpoint, then POST /retry-install

Module loads blank page in Shell

  1. Verify exposedComponent in module.manifest.json matches the key in exposes in federation.config.js
  2. Check browser DevTools → Network for remoteEntry.js — confirm the CDN URL is reachable
  3. Check browser console for Module Federation version conflicts (React singleton mismatch)

Permissions not appearing in Role Builder

Module Registry registers permissions asynchronously after installation. Wait 30 seconds and refresh. If they still don't appear:

curl -s https://api.septemcore.com/v1/modules/mod_01jwabcxyz/permissions \
-H "Authorization: Bearer $TOKEN" | jq .

If the list is empty, the module permissions[] array in the manifest may be malformed or empty.