Onboarding an SME client with its own instance
This page is the SME path. An SME client gets its own Memex instance, running in its own subscription inside the shared MeshWeaver Customers tenant. Systemorph builds it and operates it. The client signs in to its portal and asks for nothing else. An enterprise client, one with a directory of its own, follows Onboarding.md instead.
The fleet statement behind both paths is Systemorph/Memex
docs/client-tenancy.md.
Where this page and that one disagree, that page wins.
Three rules first
- We host none of the client's data. The Postgres server and its databases, the backups, the
file shares behind the
content,attachments,usersanddataclaims, and the Key Vault all live in the client's subscription. None of it goes on a Systemorph resource: notmemexaks-pg, not theSystemorphvault, notmemexaks-clusteror its node resource group. Thepearltrial on the Systemorph cluster predates this rule and is not a model. - Everything is steered from memex.systemorph.com. The client's estate is changed only by a
Hosting/InstanceActionthere, approved in the mesh. memex dispatches the client config repository'saks-ops.ymlthrough thesystemorph-comApp. There is no client control instance, no operator Job and no human access to the cluster. - The client has no access to Azure. It gets no role on the subscription and no role in the tenant. Its users sign in to their own portal, and that is the whole surface.
The tenancy model
| SME client (this page) | Enterprise client (Onboarding.md) | |
|---|---|---|
| Directory | the shared MeshWeaver Customers tenant, 93c60c29-6a77-4832-ab81-3400fce42b59 |
a tenant of its own |
| Subscription | its own, in the shared tenant, on Systemorph's billing profile | its own, in its tenant |
| Who has access to the subscription and the tenant | Systemorph only; the shared tenant's Global Administrators are Roland Bürgi and Robert Salzmann | Systemorph while building; the client may get admin rights |
| Who operates it | Systemorph, from memex.systemorph.com | the same |
| Hand-over | none by default | the client can take the tenant over |
| First client | Pearl Technology: subscription Pearl Technology Memex, not created yet (two alias attempts on 2026-09-15 produced no subscription; see step 1) | PartnerRe |
What Systemorph runs, in order
Every step is Systemorph's. Steps 1 and 2 are done by a person with rights on the billing account and the shared tenant. After that, the lanes and memex.systemorph.com do the work.
1. The subscription, in the shared tenant
The Azure portal only creates a subscription in the tenant you are signed in to. So the subscription comes from the billing side through the alias API, homed in the shared tenant.
First, know what happened on Pearl's run of 2026-09-15:
- The Systemorph tenant's
blockSubscriptionsLeavingTenantrefused the alias PUT (SubscriptionCreationInGivenTenantNotAllowed) until the creating admin was in itsexemptedPrincipals, which took effect within a minute. - Both aliases,
pearl-memexandpearl-memex-b, then readprovisioningState: Succeeded. Billing never listed the subscription, ARM answeredSubscriptionNotFound, andacceptOwnershipanswered429 Subscription is not createdfor over two hours. PartnerRe's identical request, which the policy did not refuse, worked. - A GET on the policy never shows a write. Writing it needs User Access Administrator at root through
elevateAccess, whose POST needsContent-Length: 0.
So confirm the billing account lists the new subscription before you accept it. If it does not, stop: another alias only adds another empty one. Ask for the associated-billing-tenant route or a Microsoft support case instead. Read the policy first anyway:
az rest --method get --url "https://management.azure.com/providers/Microsoft.Subscription/policies/default?api-version=2021-10-01"
Then create the subscription, signed in to the billing tenant. The owner must be an object id in the shared tenant:
az rest --method put \
--url "https://management.azure.com/providers/Microsoft.Subscription/aliases/<alias>?api-version=2021-10-01" \
--body '{"properties":{"displayName":"<Client> Memex","workload":"Production",
"billingScope":"/providers/Microsoft.Billing/billingAccounts/<account>/billingProfiles/<profile>/invoiceSections/<section>",
"additionalProperties":{"subscriptionTenantId":"93c60c29-6a77-4832-ab81-3400fce42b59","subscriptionOwnerId":"<owner object id in the shared tenant>"}}}'
Accept ownership, signed in to the shared tenant (az login --tenant 93c60c29-6a77-4832-ab81-3400fce42b59):
az rest --method post \
--url "https://management.azure.com/providers/Microsoft.Subscription/subscriptions/<subscription id>/acceptOwnership?api-version=2021-10-01" \
--body '{"properties":{"displayName":"<Client> Memex"}}'
az rest --method get \
--url "https://management.azure.com/providers/Microsoft.Subscription/subscriptions/<subscription id>/acceptOwnershipStatus?api-version=2021-10-01"
The status reaches Completed and the subscription reads Enabled. Do not try to move an
existing subscription into the tenant: policy writes answered 200 and changed nothing, and
changeTenantRequest answered 403 or 404.
Then register the resource providers and request quota, exactly as in Onboarding.md Part A.
2. The estate, from the lanes
The client's config repository carries the lanes, shaped like Systemorph/PartnerRe.Memex. Whether each SME client gets its own repository or all share one is still open.
infra/bootstrap-oidc.sh <subscription id>, signed in to the shared tenant, creates three things:- the CI app, federated to
mainand theinfraenvironment only; - the
infra,aks-opsandaks-ops-prodenvironments, each deployable frommainonly; - the three repository secrets Infra deploy reads.
- the CI app, federated to
- Infra deploy, dispatched as
systemorph-com[bot], what-if first and then deploy. It creates the whole estate in the client's subscription:- the private cluster;
- Postgres with its own backups;
- the Azure Files account behind the claims;
- the Key Vault;
- the portal identity;
hosting-operator, federated toaks-opsandaks-ops-prodand to nothing else.
- The in-VNet runner set for the operations lane, registered to the Systemorph organisation for this repository only.
The lane's prerequisites and what must never exist are in Onboarding.md → Operations lane prerequisites.
3. The record, in memex
The instance is a Hosting/Deployment record in memex.systemorph.com's Deployments space. It has:
repositoryset to the client config repository;- no
operatorblock; - every data field naming a resource in the client's subscription.
Where the file is authored is still open: the config repository, synced into memex, or memex itself.
4. The sign-in app, invitation-only
Register the portal's sign-in app in the shared tenant, multi-tenant, so the client's users sign
in with their own work accounts. Set Features__Onboarding__InvitationOnly: "true" on the record.
Invitation-only is what keeps the portal closed, not the app's audience. The client secret goes
into the client's vault, by name on the record, never as a value.
5. The instance key and the vault objects
Register the instance at the plugin registry. The mwi_ key is returned once and stored in the
client's vault. The master key and the connection string are minted into the same vault by the
lane. The objects and their names are those in Onboarding.md §7 and §8.
6. Provision, approved in the mesh
Create a Provision action on memex.systemorph.com for the record, dry run first. After the
approval, memex dispatches the client repository's aks-ops.yml. Its mutate lane creates the
namespace, the claims on the client's file shares, the database on the client's server, the portal,
the ingress and the certificate.
7. Invite, then verify
Invite the users the client named, and make the named administrators portal administrators. Run Onboarding.md §12, then add one more check. Every claim must be bound to a share in the client's subscription, and the database must be on the client's server.
What the client is asked for
- The users to invite: names and e-mail addresses, and which of them administer the portal.
- The host: a name in the client's own domain, for which the client creates one DNS record pointing at the ingress IP Systemorph gives them, or a host under a Systemorph zone.
- Nothing in Azure. No subscription, no tenant, no identity, no key, no approval in a portal.
What the client never gets
- A role on its subscription or in the shared tenant.
- A cluster credential, a vault object, or the database password.
- Access to the config repository or to a GitHub App.
- Any of its data on a Systemorph resource. That is the rule, not something the client has to ask for.
Not built yet
These gaps are shared with the enterprise path. Until they close, a client estate cannot be provisioned from memex end to end:
- memex.systemorph.com still runs the Job executor (Systemorph/Memex#313), and the core chart does not render the executor key.
- A per-record cluster, subscription and identity, and observing a remote cluster from the fleet board, are platform changes.
- Where a client record is authored is open.
- Each estate needs its own runner set, and which App registers it is open.