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.
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,
};
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:
- Uploads the new bundle to
{tenantId}/modules/@acme/crm/1.3.0/remoteEntry.jsin S3 - Archives the v1.2.0 manifest (kept for rollback)
- Publishes
module.registry.updatedevent to the Event Bus - The UI Shell invalidates its
localStoragecache for this module - 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:
| Step | What Happens | Failure Outcome |
|---|---|---|
| 1. Manifest validation | OpenAPI schema check (required fields, semver format, permission prefix, max 64 KB) | 400 Bad Request — never reaches pending |
| 2. SDK compatibility | kernelSdkVersion checked against running kernel SDK | 409 Conflict — never reaches pending |
| 3. Bundle download | Registry fetches JS bundle from npm / Git URL / direct upload | failed |
| 4. SRI hash verification | SHA-384 of downloaded bundle verified against integrity field in manifest | 422 Unprocessable Entity |
| 5. S3 upload | Bundle uploaded to tenant's S3 isolation zone (see paths below) | failed |
| 6. CDN URL generation | entry field rewritten to CDN URL | failed |
| 7. Route registration | Envoy xDS RDS update — new route registered without restart | failed |
| 8. Permission registration | Idempotent upsert of permissions[] into IAM | failed |
| 9. Migration execution | goose up runs SQL migrations from module's migrations/ directory | failed + migrationError field |
| 10. Health check | GET {healthCheck} must return 200 OK within 60 s | Auto-rollback if timeout |
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/...
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:
- Previous stable version manifest is restored
- Envoy xDS route updated to previous CDN URL
module.registry.rollbackevent published to Event Bus- Admin notification sent via Notify (email + in-app)
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
| Content | Retention |
|---|---|
| Module manifest history | Indefinite (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
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:
| Symptom | Cause | Fix |
|---|---|---|
migrationError: "column X already exists" | Non-idempotent SQL migration | Wrap in IF NOT EXISTS |
status: failed at step 4 | SRI hash mismatch | Re-build and re-publish — do not modify bundle after computing hash |
status: failed at step 7 | Envoy xDS timeout | Check docker compose logs gateway — usually a transient issue; retry |
| Health check timeout | healthCheck path returns non-200 | Fix the endpoint, then POST /retry-install |
Module loads blank page in Shell
- Verify
exposedComponentinmodule.manifest.jsonmatches the key inexposesinfederation.config.js - Check browser DevTools → Network for
remoteEntry.js— confirm the CDN URL is reachable - 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.