Skip to main content

Modular Deployment & Entitlements

medOS ships from one codebase but is sold and installed as a set of modules (country-agnostic features) layered with a single market pack (per-country overlay). A deployment is the cross-product of the two: pick the features a site purchased, pick the country it operates in, and let a dependency-resolving installer assemble the rest. The marginal site is cheap because it is configuration and data, not new code.

2 axes
Modules × market packs
1 codebase
Identical everywhere
Idempotent
Safe to re-run installs
Default-OFF
Demo-safe entitlement

Two orthogonal axes

The deployment model separates what a hospital bought from where it runs.

MODULES (country-agnostic features — schema/logic/UI identical everywhere)
───────────────────────────────────────────────────────────────────────▶
MARKET PACKS │ read-model-core reporting scheduling telemetry biometric …
(per-country │ ─────────────────────────────────────────────────────────────────
overlays: │ country A locale · currency · insurer rates · terminology
locale, │ country B locale · currency · insurer rates · terminology
seeds, │ country C locale · currency · insurer rates · terminology
insurers, │ … any future country = drop in a new market pack
terminology) ▼
  • Modules are country-agnostic. The SQL, edge functions, and UI for a feature are the same in every country.
  • Market packs are country overlays. They carry only what genuinely differs: locale, timezone, currency, insurer / scheme rate tables, terminology, and bilingual seed data.
  • A deployment = chosen modules + one market pack. The application code never branches on country.

What is modular at each layer

LayerModular unitHow it is selected
FrontendFederated miniapp + an entitlement flagModule registry + an enabled-modules allowlist
BackendA microservice on the message busRun only the services a site needs
Read-model schemaA module package with a manifestDependency-resolved installer
Country configA market packSelected by a region argument
Edge functionsA named functionReported and deployed by the installer

The two deploy contracts

Each modular unit declares itself with a small manifest so the installer can plan the work. Internal file paths and secrets are omitted here — these are illustrative shapes, not literal configs.

Module manifest — a country-agnostic feature

{
"id": "reporting",
"catalogCategory": "Reporting",
"requires": ["read-model-core", "scheduling", "telemetry"], // dependency edges
"migrations": ["..."], // schema — always applied
"seeds": ["..."], // demo rows — only with --with-seed
"edgeFunctions": ["..."], // deployed/reported by the installer
"miniapp": "reporting-dashboard",
"flags": { "ENABLED_FLAG": "true" } // the entitlement contract
}

Market-pack manifest — a country overlay

{
"name": "market-pack",
"locale": "xx", "timezone": "...", "currency": "XXX",
"country_code": "XX",
"terminology": { "...": "..." },
"facility_types": ["..."],
"seeds": ["..."], // SQL seeds applied for this region
"scripts": ["..."], // scripted seeds (e.g. reference data)
"complianceProfile": { "dataResidency": "...", "retentionDays": 0 }
}

A country that needs more than seeds (extra edge functions, dependencies) can also be expressed as a full module — the two contracts compose.

The installer

Installing a module set is a package-manager-style operation: resolve the dependency closure, order it deps-first, and apply each step idempotently.

Planner

Dependency resolver

Reads every module manifest, computes the dependency closure, topologically sorts it deps-first, and emits an ordered plan. Detects cycles and unknown modules before anything is applied.

ClosureTopo-sortCycle detect
Applier

install-module

Applies module schema in order, then the chosen country's market-pack seeds, and reports edge functions and flags to set.

--region--dry-run--with-seed
CI

One-click apply

A manual workflow runs the installer against a configured database — never auto-applied on push, since changing a live schema must be deliberate.

manualreviewedidempotent
Safety

Re-runnable

Every migration and seed is guarded for existence, so applying any module set repeatedly never damages data.

IF NOT EXISTSON CONFLICTguarded

Conceptually, one install command does the whole chain:

install-module reporting --region=<country> --with-seed

├─▶ resolve: read-model-core → scheduling → telemetry → reporting (deps first)
├─▶ apply each module's schema (idempotent) + optional demo seeds
├─▶ apply the country market-pack seeds (facility, rates, terminology)
└─▶ report: edge functions to deploy + entitlement flags + locale

Entitlement — catalog to flags to deploy

Entitlement is a two-layer gate. Layer 1 asks whether the deployment installed a feature; layer 2 asks whether the tenant's subscription tier includes it. Both default open so demos and dev never break.

featureAvailable(feature) =
moduleEnabled(moduleId) // Layer 1: deployment installed this module
&& deploymentFlag // and the feature is configured
&& isEntitled(feature) // Layer 2: tenant's tier includes the feature
LayerQuestionMechanismDefault
1 — Module availabilityIs the feature installed here?An enabled-modules allowlist read by moduleEnabled(id)Unset = all on
2 — Subscription tierHas the tenant paid for it?A feature-to-tier matrix resolved by isEntitled(key)Unset = all entitled

Subscription tiers are ordered (for example free, basic, premium, enterprise). A feature lists the tiers that include it; a feature absent from the matrix is included everywhere for back-compatibility. When a tenant is not entitled, the UI shows an upgrade prompt instead of the feature, and the relevant backend endpoints reject the call as a trust anchor — so the gate holds even if the frontend is bypassed.

Editions and topologies

A small number of bundles map purchased modules and a tier onto ready-made configurations:

  • LITE vs FULL — a trimmed module set for smaller sites versus the complete suite.
  • Cloud / on-prem / air-gapped — the same modules and market pack, deployed to a managed cloud, a hospital's own servers, or a fully disconnected site.
Coverage

Demo-safe by default

Every gate fails open until a deployment opts into enforcement. Nothing breaks during evaluation; production simply sets the allowlist and tier.

fail-open
target 95%
Add a feature

Three steps

Declare a module manifest, add a row to the tier matrix, and wrap the surface in the entitlement gate.

manifesttier rowgate
Add a country

One pack

Drop a market-pack manifest plus bilingual idempotent seeds; the installer discovers it via the region argument with no code change.

manifestbilingual seedsregion

Design philosophy

The system is built to be installed and re-installed safely against live data.

PrincipleWhat it means
AdditiveNew modules add tables, rows, and surfaces; they do not rewrite what exists.
IdempotentMigrations and seeds are guarded, so any module set can be applied repeatedly.
Dependency-closedA module always deploys with its full dependency closure, ordered deps-first.
Default-OFF / fail-openBoth entitlement layers default to enabled; enforcement is opt-in per deployment.
Country = data, not codeThe application never branches on country — differences live in market-pack rows.
Residency-respectingEach site keeps its read model in-region; only de-identified aggregates roll up to a group tier.
Bilingual seedsEvery seed carries the local language plus English.

Edge and on-prem direction

The same modules and market pack drive edge and on-premise installs. The model supports a single all-in-one stack for a disconnected site, a trimmed LITE edition for resource-constrained hardware, and a cloud install for managed hosting — all from one codebase. Because a deployment is just module selection plus a market pack, moving a site between cloud, on-prem, and air-gapped is a re-deploy of the same artifacts rather than a rewrite.

See also