Skip to main content

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_ADDRVALKEY_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):

VariableOld defaultNew default
RATE_LIMIT_RPS10010000
RATE_LIMIT_BURST20020000
RATE_LIMIT_MAX_REQUESTS10001000000

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