Migration Guides
This guide documents all breaking changes, required actions for module authors, and upgrade paths for SDK consumers across Platform-Kernel milestones.
Versioning scheme: MAJOR.MINOR.PATCH following
Semantic Versioning 2.0.0.
Current SDK version: 0.1.0 per all @platform/* packages.
Migrating to 0.9.x (M9 — B2B2B + GDPR)
Released: 2026-04-19
Breaking: Go SQL Driver — database/sql + lib/pq removed
All 12 Go services migrated from database/sql + lib/pq to
pgx/v5 + pgxpool + sqlc (squash commit fix(kernel): unify SQL to pgx/v5+sqlc, 2026-04-02).
Affected services: Notify, Files, Module Registry, Integration Hub, Billing, Money, Audit (WAL path).
Breaking impact: Internal — no external API surface change.
Custom Go plugins or extensions that embed the kernel's db
interfaces must migrate to pgx/v5.
Required action (extension authors only):
// ❌ Before (database/sql + lib/pq)
import (
"database/sql"
_ "github.com/lib/pq"
)
db, _ := sql.Open("postgres", dsn)
// ✅ After (pgx/v5 + pgxpool)
import (
"github.com/jackc/pgx/v5/pgxpool"
)
pool, _ := pgxpool.New(ctx, dsn)
Breaking: CORS — go-chi/cors removed
The legacy go-chi/cors middleware was removed from the Gateway in
M7 (refactor(security): M7 B4 — remove legacy CORS, 2026-03-24).
Only StrictCORS (no-wildcard enforcement, kernel-spec §7.7) is
now active.
Required action: Set CORS_ALLOWED_ORIGINS explicitly in your
deployment. An empty value now returns 403 for all cross-origin
requests instead of a permissive wildcard.
# ❌ Before — gateway silently allowed wildcard
CORS_ALLOWED_ORIGINS=*
# ✅ After — explicit whitelist required
CORS_ALLOWED_ORIGINS=https://app.yourdomain.com,https://admin.yourdomain.com
Breaking: IAM — Tenant Hierarchy (B2B2B)
B2B2B hierarchical multi-tenancy was introduced in M9 Phase 2
(feat(iam): B2B2B hierarchical multi-tenancy, 2026-04-18).
The tenants table gained a parent_id column backed by a
Closure Table for O(1) ancestry queries.
Required action (self-hosting migrations):
# Run IAM service migrations to apply the hierarchy schema
docker exec platform-kernel-cli goose -dir /migrations/iam up
Existing single-level tenants are migrated automatically:
parent_id = NULL means a Platform Owner (root node).
Breaking: Gateway VALKEY_ADDR → VALKEY_URL
The Gateway environment variable was renamed from VALKEY_ADDR to
VALKEY_URL to match the convention used in IAM and Data Layer
(fix(gateway): standardize Valkey env var).
# ❌ Before
VALKEY_ADDR=valkey:6379
# ✅ After
VALKEY_URL=valkey:6379
Breaking: Gateway Rate Limit defaults raised
Production rate limit defaults were raised to prevent 429s at load (walkthrough §3.4, 14K RPS test):
| Variable | Old default | New default |
|---|---|---|
RATE_LIMIT_RPS | 100 | 10000 |
RATE_LIMIT_BURST | 200 | 20000 |
RATE_LIMIT_MAX_REQUESTS | 1000 | 1000000 |
Required action: Review and override if your deployment has lower traffic — the new defaults consume more Valkey memory.
Non-breaking: GDPR Deletion State Machine
The GDPR crypto-wipe pipeline (feat(gdpr): Phase 4, 2026-04-19)
is additive. No API changes. New behaviour: deleted user data
undergoes AES-256 key destruction after BILLING_CRYPTO_WIPE_PERIOD_DAYS
(default: 30 days).
Migrating to 0.8.x (M8 — Billing & Integration Hub)
Released: 2026-03-30
New: BILLING_CACHE_TTL replaces BILLING_CACHE_TTL_MIN
The Gateway BILLING_CACHE_TTL_MIN (integer minutes) was replaced
with BILLING_CACHE_TTL (Go duration string) for consistency with
all other duration env vars.
# ❌ Before
BILLING_CACHE_TTL_MIN=15
# ✅ After — Go duration string
BILLING_CACHE_TTL=15m
Fallback: BILLING_CACHE_TTL defaults to 15m if unset.
New: Integration Hub DLQ
Events that fail delivery after 5 attempts are moved to the
Dead Letter Queue. Module authors receiving Integration Hub webhooks
must handle X-Platform-Retry-Count and X-Platform-DLQ headers:
POST /your-webhook-endpoint HTTP/1.1
X-Platform-Retry-Count: 5
X-Platform-DLQ: true
Content-Type: application/json
Return 200 to acknowledge and remove from DLQ. Return 4xx to
retain for manual review.
Migrating to 0.7.x (M7 — Feature Flags & Domain Resolver)
Released: 2026-03-28
New: Feature Flags SDK (@platform/sdk-flags)
Feature flags are now injected via the GoFeatureFlag service (port
1031). The SDK caches flag state in-memory, refreshed every 15s.
import { isEnabled, getVariant } from "@platform/sdk-flags";
// Returns false when GoFeatureFlag is unreachable (safe default)
const enabled = await isEnabled("new-checkout-flow", { tenantId });
Ops requirement: GoFeatureFlag must be running. Flags default
to false when service is unreachable or cache is empty.
Migrating to 0.6.x (M6 — SDK Distribution)
Released: 2026-03-23
Breaking: TypeScript — tsconfig.test.json removed
Each package now uses a single tsconfig.json with Vitest
workspace registration (chore(test): migrate Vitest to v4 test.projects API, 2026-04-06).
Required action (custom module authors):
// ❌ Before — tsconfig.test.json per package
// tsconfig.test.json referenced in jest/vitest config
// ✅ After — single tsconfig.json, registered in vitest.projects.ts
// vitest.workspace.ts
import { defineProject } from "vitest/config";
export default defineProject({
test: { environment: "jsdom" },
});
Breaking: SDK package entry points changed
All @platform/sdk-* packages now export from dist/index.js
(ESM) instead of CommonJS. package.json#exports is the canonical
entry point.
// ✅ Correct import
import { useAuth } from "@platform/sdk-auth";
// ❌ Do NOT import from /src or /dist/cjs
import { useAuth } from "@platform/sdk-auth/src/index";
Breaking: @axe-core/playwright replaces axe-playwright
Accessibility test imports must be updated:
// ❌ Before
import AxeBuilder from "axe-playwright";
// ✅ After
import { checkA11y } from "@axe-core/playwright";
Migrating to 0.5.x (M5 — UI Shell)
Released: 2026-03-21
New: Module Federation 2.0 remote registration
Remote modules must export a ModuleFederationPlugin entry in
their Vite config:
// vite.config.ts (remote module)
import { federation } from "@module-federation/vite";
export default {
plugins: [
federation({
name: "my-module",
filename: "remoteEntry.js",
exposes: {
"./App": "./src/App.tsx",
},
shared: ["react", "react-dom"],
}),
],
};
Migrating to 0.4.x (M4 — Module Registry)
Released: 2026-03-15
New: Module Manifest Schema v1.0.0
Modules must supply a manifest.json at registration time. The
Module Registry validates the manifest against OpenAPI schema
services/module-registry/api/openapi.yaml.
Minimum required fields:
{
"name": "my-module",
"version": "1.0.0",
"permissions": [
{ "resource": "data", "action": "read" }
],
"entry": "remoteEntry.js"
}
Required action: Register your module via the gRPC
ModuleRegistryService.Register RPC or REST POST /api/v1/modules
before accessing any platform primitive.
Migrating to 0.3.x (M3 — Money, Notify, Files, Audit)
Released: 2026-03-14
New: Hold/Confirm/Cancel pattern (Money)
Wallet holds must follow the three-phase commit pattern. Any custom
billing logic must use POST /api/v1/wallets/:id/hold →
/confirm or /cancel. Direct debit without a hold is supported
but not recommended for checkout flows.
New: WebSocket replay buffer (Notify)
The WebSocket client must handle the initial auth handshake:
{ "type": "auth", "token": "<JWT>" }
After reconnect, the server replays up to 100 messages via the
Valkey-backed replay buffer (TTL: 1 hour). Clients should
deduplicate by notification_id.
New: Audit dual-write
All services that write to the Audit Service must handle the Kafka
fallback path. When Kafka is down, the Audit Service buffers to
PostgreSQL WAL (WAL_RETENTION_DAYS = 7 days default). No action
required — the WAL replay is automatic.
Migrating to 0.1.x (M1 — IAM + Data Layer)
Released: 2026-03-12
New: JWT format — ES256 (ECDSA P-256)
All tokens use ES256. RSA-based tokens from legacy systems are not
accepted. Generate a new key pair for each deployment:
openssl ecparam -name prime256v1 -genkey -noout \
-out /tmp/jwt-private.pem
openssl ec -in /tmp/jwt-private.pem -pubout \
-out /tmp/jwt-public.pem
Store in Vault at secret/platform/iam/jwt-keys. See
Vault Setup.
New: Row-Level Security enforced by default
All PostgreSQL tables have ROW SECURITY ENABLED. The IAM service
sets app.current_tenant_id and app.current_user_id on each
connection. Custom DB extensions that bypass this context will see
empty result sets — this is intentional.
See Also
- Changelog — full commit history by milestone
- Glossary — term definitions
- Configuration Reference — all environment variables and defaults
- Vault Setup — JWT key generation