Distributed CMS — current milestones → ideal state

One page for the end goal: a single Raisd CMS product with databases and compute placed by country / region (Malaysia, Singapore, Sierra Leone, Botswana, and further campuses), each with its own white-label, feature toggles, and identity — reached step by step from today’s ADR-1 Cyberjaya path through M1–M5.

M1–M4: CMS family = SoR (ADR-1) · Lesotho pilot M5: campus expansion + progressive unification Ideal state = proposed target (needs ADR when WG confirms) One product · many country data planes

1. Ideal end state (target architecture)

Raisd ends as one CMS system (one product codebase, one Portal API contract, four role portals) with a distributed data plane: each country or regulated region keeps its own database and may run local API/portal capacity for latency and data residency. Global control-plane services own tenancy, branding, feature flags, and identity federation — not student ledgers.

Logical shape — single product, many country data planes

Portals stay thin. Writes always go to the campus’s local system of record.

flowchart TB
  subgraph Users["Users worldwide"]
    U1[Applicants / Students]
    U2[Lecturers / Staff]
  end

  subgraph Edge["Edge & routing"]
    DNS[Geo / campus DNS]
    WL[White-label shell
theme · logo · domain] end subgraph GlobalCP["Global control plane — shared config only"] TEN[Tenant registry
campus · country · region] FF[Feature flag service] IDF[Identity federation
IdP map · SSO policies] SCH[Schema / release train] end subgraph SEA["Region SEA — example"] subgraph MY["Malaysia · Cyberjaya"] APImy[Portal API MY] DBmy[(CMS DB MY)] FSmy[(Evidence MY)] end subgraph SG["Singapore · hosting / campus"] APIsg[Portal API SG] DBsg[(CMS DB SG)] end end subgraph AFR["Region Africa — example"] subgraph SL["Sierra Leone"] APIsl[Portal API SL] DBsl[(CMS DB SL)] end subgraph BW["Botswana"] APIbw[Portal API BW] DBbw[(CMS DB BW)] end end U1 --> DNS U2 --> DNS DNS --> WL WL --> APImy WL --> APIsg WL --> APIsl WL --> APIbw TEN -.-> WL FF -.-> APImy FF -.-> APIsl IDF -.-> APImy IDF -.-> APIsl SCH -.-> APImy SCH -.-> APIsl APImy --> DBmy APImy --> FSmy APIsg --> DBsg APIsl --> DBsl APIbw --> DBbw

Product layer (one)

  • Applicant / Student / Lecturer / Staff portals
  • Portal API (same OpenAPI contract)
  • Raisd CMS domain services (post-legacy)

Control plane (global)

  • Campus / country tenant registry
  • White-label packs (brand, locale, legal copy)
  • Feature flags per campus / country
  • Identity provider mapping

Data plane (per country)

  • Authoritative CMS database
  • Evidence / file store
  • Local Portal API replicas
  • Country compliance batch jobs

Country uniqueness

  • MY: EMGS, MQA/KPT, PTPTN, …
  • SL / BW / others: local QA & fees
  • Toggled modules — not forks
Non-negotiable: one codebase and one CAP catalogue. Country differences are configuration + feature flags + local adapters — not separate CMS products per campus.

2. Journey map — step by step

Agreed delivery is SDD-03 M1–M5. The ideal distributed Raisd CMS is the post-M5 target that M5 progressive consolidation is designed to enable — not a rewrite during Admissions launch.

Now → M1
Baseline & rules Demo portals; Lesotho + Cyberjaya + sister CMS dumps; agree integration owners
M2
Admissions Live Portal API → CAP-53 → campus CMS SoR
M3–M4
Core + pilot Student/lecturer Live paths; Lesotho acceptance
M5
Expand & unify More campuses; domain migration off legacy
Ideal
Distributed CMS One product · country DBs · white-label · flags
flowchart LR
  N[Now
Demo + dumps] --> M1[M1
Rules & access] M1 --> M2[M2
Admissions] M2 --> M3[M3
Core student] M3 --> M4[M4
Lesotho pilot] M4 --> M5[M5
Campus expansion
+ progressive SoR] M5 --> IDEAL[Ideal
Unified Raisd CMS
distributed DBs]
Stage System of record Where data lives Multi-country? White-label / flags
Now / M1 Legacy CMS per campus (inventoried) Cyberjaya cmscbj; Sierra Leone ems_sierraleone (separate) Inventories only Not productised
M2–M4 Campus CMS family (ADR-1); Lesotho pilot first Singapore UltaHost CMS host + k3s Postgres maps Lesotho first Live; others parallel/M5 Campus config seed; single brand
M5 Legacy per campus, then domain-by-domain Raisd Still primarily SEA until outside-SEA launch Roll out campus-by-campus Introduce tenant + flag model
Ideal Raisd CMS (unified product) DB + API per country / region Yes — MY, SG, SL, BW, … Full white-label + feature toggles + identity packs
Current baseline M1 — Confirm rules & integration

3. Now → M1 — baseline architecture

Student portal is the deepest Demo UI. Applicant / lecturer / staff Raisd apps are not Live. Cyberjaya CMS and sister-campus dumps (e.g. Sierra Leone) exist as separate legacy systems — proof that multi-country already means separate databases today, not one shared ledger.

flowchart TB
  subgraph Raisd["Raisd today"]
    SP[Student portal
Demo / mock] API[Portal API
contracts + stubs] PG[(Postgres planned
maps / audit)] end subgraph Legacy["Legacy estate — separate SoRs"] CJ[Cyberjaya / LUCT CMS
cmscbj] SL[Sierra Leone CMS
ems_sierraleone] OTHER[Other campus CMS
Botswana · … TBC] end SP --> API API -.->|stub / mock| CJ API -.->|not connected| SL CJ ~~~ SL SL ~~~ OTHER

What must happen in M1

  • Named owners for CMS access and campus rules
  • Integration credentials for non-production Cyberjaya
  • Confirm which Premium hosts CMS (SDD-12)
  • Close “Needs checking” that unblock Admissions
  • Primary CAP: CAP-42

Why this matters for the ideal state

  • Sister CMS dumps prove country DBs are already isolated
  • CAP catalogue must stay shared across countries
  • Do not force one physical DB for compliance-heavy countries
  • Inventory → adapter map → later Raisd SoR cutover
This phase — ADR-1 M2 Admissions · M3 Core student · M4 Lesotho pilot

4. M2–M4 — portals on campus CMS family (Lesotho pilot first)

Four portals talk only to the Portal API. Live writes succeed only after the campus CMS acknowledges them (CAP-53). First acceptance campus is Lesotho (3 Oct 2026). Hosting stays on UltaHost Singapore k3s (SDD-12). Staff desks remain on verified old CMS unless a gap is proven.

Runtime topology (M2–M4)

One acceptance campus (Lesotho). CMS family SoR. No second control plane yet.

flowchart LR
  subgraph Clients
    AP[apply.raisd.co]
    STU[student.raisd.co]
    TEA[teach.raisd.co]
    STF[staff.raisd.co]
  end

  subgraph K3s["k3s · Singapore UltaHost"]
    ING[Traefik]
    API[portal-api]
    PG[(Postgres
maps · session · audit)] FS[Evidence volume] end CMS[Cyberjaya CMS
Premium · SoR] IDP[CMS login / IdP] AP --> ING STU --> ING TEA --> ING STF --> ING ING --> API IDP --> API API --> PG API --> FS API -->|CAP-53 ACL| CMS
MilestoneArchitecture changeRelease condition (summary)
M2 Wire Portal API to real CMS; admissions + auth slice Live; evidence store durable Apply → evidence → offer → enrol on real records
M3 Add student + lecturer portals on edge node; teaching/finance paths via API Core student journeys + paired staff/lecturer paths Live or on old CMS
M4 No new topology; restore drill, training, acceptance Lesotho accepts pilot; ops ready
Do not skip ahead: building country DB sharding or a second regional cluster before M4 acceptance invents a greenfield topology outside ADR-2/3. Multi-country distribution is intentionally deferred to M5 → Ideal.
M5 — Campus expansion & remaining CMS Start progressive unified CMS

5. M5 — multi-campus expansion and progressive unification

Expand coverage campus-by-campus. Prefer Portal API + verified local legacy CMS first. Rebuild or migrate a domain into Raisd CMS only with ownership, cutover, rollback, and training. Introduce the tenant / flag / white-label model here so Ideal is configuration, not a rewrite.

flowchart TB
  subgraph Shared["Raisd shared product"]
    P[Four portals]
    API[Portal API
multi-tenant campus_id] CFG[Campus config
tenant · flags · brand] end subgraph Adapters["Country adapters — M5 pattern"] A1[Cyberjaya adapter
LUCT / cmscbj] A2[Sierra Leone adapter
ems_sierraleone] A3[Botswana adapter
TBC inventory] end subgraph SoR["Systems of record during M5"] L1[(Legacy MY)] L2[(Legacy SL)] L3[(Legacy BW)] R1[(Raisd domain DB
migrated modules only)] end P --> API CFG --> API API --> A1 API --> A2 API --> A3 A1 --> L1 A2 --> L2 A3 --> L3 API -->|verified gap + cutover| R1

M5 architecture workstreams

  • Adapter factory: one CAP-53 pattern per legacy campus DB
  • Tenant registry: campus_id, country_code, region, legal entity
  • Feature flags: EMGS, local QA, payment rails, visa desks
  • White-label packs: logo, colours, domain, locale, privacy text
  • Domain migration: admissions → registry → teaching → finance → …
  • Hosting: grow SEA cluster; plan first non-SEA region when a campus outside SEA goes Live

Expansion order (illustrative)

🇲🇾 Malaysia (Cyberjaya) Pilot SoR · first Raisd domain cutovers
🇸🇱 Sierra Leone Inventory done · M5 adapter candidate
🇧🇼 Botswana Evidence tab campus · inventory TBC
🇸🇬 Singapore Hosting region · campus rules TBC
Other (KH, SZ, LS, NA, UG, …) Same pattern: inventory → adapter → accept → migrate
Target vision — post M5 Proposed · confirm with Working Group as future ADR

6. Ideal state — unified Raisd CMS, distributed databases

Legacy campus CMS instances are retired (or reduced to read-only archive). Raisd CMS is the only write path. Each country (or regulated region) runs its own database and preferably local API capacity. Global services never hold the student ledger.

Detailed ideal architecture

Global control plane + regional data planes + shared portal apps.

flowchart TB
  subgraph Portals["Shared portal apps — multi-tenant"]
    AP[Applicant]
    SP[Student]
    LP[Lecturer]
    ST[Staff / admin CMS]
  end

  subgraph Control["Global control plane"]
    TR[Tenant registry]
    WL[White-label service]
    FF[Feature flags]
    ID[Identity router]
    REL[Release / schema train]
    OBS[Central observability]
  end

  subgraph RegionA["Region A — SEA"]
    GW1[Regional API gateway]
    API1[Portal API pool]
    CMS1[Raisd CMS services]
    DB1[(Country DBs
MY · SG · …)] FS1[(Evidence region A)] end subgraph RegionB["Region B — Africa"] GW2[Regional API gateway] API2[Portal API pool] CMS2[Raisd CMS services] DB2[(Country DBs
SL · BW · …)] FS2[(Evidence region B)] end AP --> WL SP --> WL LP --> WL ST --> WL WL --> GW1 WL --> GW2 TR --> WL FF --> API1 FF --> API2 ID --> API1 ID --> API2 REL --> CMS1 REL --> CMS2 GW1 --> API1 --> CMS1 CMS1 --> DB1 CMS1 --> FS1 GW2 --> API2 --> CMS2 CMS2 --> DB2 CMS2 --> FS2 API1 --> OBS API2 --> OBS

Scale-out unit

A country data plane: DB + files + optional API pool. Add a country by provisioning a plane and registering a tenant — not by forking the CMS.

Geo placement

Compute near data for latency and residency. DNS / gateway routes campus → region. Cross-region reads are explicit and rare.

Identity

Each country maps to its IdP / local auth rules. Tokens carry campus_id, country_code, roles. Server enforces scope.

Ops

One release train; per-country config. Backups and restore drills per data plane. Central metrics with country labels.

7. White-labelling, feature toggling, and identity

Country uniqueness is expressed as configuration on a shared product — not parallel codebases.

flowchart LR
  REQ[Browser request
host / campus] --> RESOLVE[Tenant resolve] RESOLVE --> BRAND[White-label pack
logo · theme · domain · locale] RESOLVE --> FLAGS[Feature flags
modules · regulators · rails] RESOLVE --> IDP[Identity pack
IdP · MFA · password policy] BRAND --> SHELL[Portal shell] FLAGS --> API[Portal API authz] IDP --> API API --> LOCAL[(Country DB)]
DimensionWhat is sharedWhat is per country / campusExample
White-label Portal layouts, CAP screens, design system Logo, colours, public hostname, footer legal, language pack apply.cyberjaya… vs apply.sl…
Feature toggles Module code behind flags EMGS / visa, local QA evidence, payment gateway, accommodation MY: EMGS on · BW: EMGS off
Identity Token model, RBAC engine, role names IdP, password rules, staff directory source, SSO CMS login vs national IdP
Academic rules Capability IDs and workflows Programme catalogue, calendar, fee schedules, grading policy Campus config in control-plane
Compliance Privacy framework, audit events Data residency region, retention, local report jobs DB must stay in-country when required
Config sketch (illustrative): tenant_id, country_code, region, brand_pack_id, feature_flags{}, idp_ref, db_dsn_secret_ref, evidence_bucket. Secrets never live in the portal; only the regional Portal API resolves them.

8. Data plane — residency, sync, and what must never be shared

Distributed database means partition by country / campus, not a single multi-writer global database. Cross-country data movement is exceptional and explicit.

Stays in-country

  • Student master, applications, results, fees
  • Evidence files and payment proofs
  • Staff operational desks and audit for that campus
  • Country-specific regulator extracts

May be global (non-ledger)

  • Tenant registry and feature-flag definitions
  • White-label assets (or CDN copies)
  • Aggregated ops metrics (no PII)
  • Schema version / release metadata
flowchart LR
  subgraph Allowed["Allowed cross-country — explicit"]
    X1[Student transfer request]
    X2[Group reporting anonymised]
    X3[Shared programme template publish]
  end

  subgraph Forbidden["Forbidden by default"]
    Y1[Silent multi-master replication of ledgers]
    Y2[Portal writing two country DBs in one request]
    Y3[Staff token reused across countries]
  end

  X1 --> BUS[Controlled integration bus
consent · audit · mapping] BUS --> TGT[(Target country DB)]

9. Cutover playbook — how a campus reaches the ideal state

Same sequence for every country. Software delay never waives an operating requirement — run on legacy or manually until Raisd is Live for that domain.

flowchart TB
  S1[1. Inventory legacy CMS
schema · SPs · desks] --> S2[2. Map to CAP catalogue] S2 --> S3[3. Ship Portal API adapter
legacy remains SoR] S3 --> S4[4. Campus accepts portals
on legacy SoR] S4 --> S5[5. Register tenant
white-label · flags · IdP] S5 --> S6[6. Domain cutover waves
admissions → registry → teaching → finance → …] S6 --> S7[7. Dual-run & reconcile] S7 --> S8[8. Flip SoR to Raisd DB
legacy read-only / retire] S8 --> S9[9. Optional: place API pool
in-country / in-region]
WaveWhenExit criteria
0 — Discover Before Live Inventory + CAP map (Cyberjaya / Sierra Leone pattern)
1 — Experience M2–M4 / first campus acceptance Portals Live on legacy SoR via CAP-53
2 — Tenantise M5 campus_id, brand pack, flags, IdP wired; second campus adapter Live
3 — Domain migrate M5+ Per-domain: ownership, cutover, rollback, training signed
4 — Distribute Ideal Country DB + regional API; legacy retired; residency proven

10. Decisions still open (TBC)

Ideal-state topology is a proposed target. It must not be treated as an agreed ADR until the Working Group confirms it. Current binding ADRs remain ADR-1 (SoR), ADR-2 (Singapore UltaHost), ADR-3 (k3s).

TopicOptions / notesStatus
Promote Ideal to ADR Single product + country data planes + global control plane Needs WG confirm
DB engine for Raisd SoR Postgres (portal maps today) vs migrate legacy MySQL domains first TBC
First non-SEA region host When Africa campus goes Live — co-locate DB/API TBC after M4
Feature-flag product In-house control-plane vs third-party TBC
Cross-campus student transfer Manual export vs event bus TBC / rare path
White-label domains Per-campus hostnames vs path-based tenancy Prefer hostnames (matches SDD-12 cookie rule)