User Journey: New tenant
Table of Contents
This page documents a user journey: A system administrator provisions a tenant from a seed profile and hands it over.
A user journey describes what one person is trying to do, before anything is built. It is a design document that outlives any sprint, so it names the screens, the entity fields each screen shows, and the operations and messages each step needs.
1. Goal
"I want a new tenant my organisation can start working in." The system administrator chooses a starting point, describes the tenant, reviews it and runs provisioning. The journey is done when the tenant is active, its parties exist, and its administrator can sign in.
2. Actor and trigger
The system administrator: the account created by First run, signed in, and acting outside every tenant. The trigger is New tenant on the Tenants page. The journey starts there and ends at the handoff, where the tenant administrator takes over.
This is one of the three journeys that carry server-side state. Provisioning
runs on the server and takes minutes. It can fail half way. The state lives in
ores.workflow, so the person can leave the page and come back, and the shell
can drive the same steps. Every other group in this corpus describes a journey
that keeps no such state. The order is fixed: this journey needs a running
installation and a signed-in system administrator, so it cannot be the first
thing that happens on an empty database.
3. Steps
- Choose a starting point. The system shows one card per seed profile, each with a tagline, three bullets and its counts: Empty operational for real use, and ACME demo for demos and testing. The ACME card carries the ACME logo. The person picks one. Nothing is created yet.
- Describe the tenant. The person names the tenant and its administrator.
Empty operational asks for the code, name and hostname, the root legal
entity — found by name or LEI among the entities the deployment holds, or
left out — the counterparty set and the administrator's password. ACME demo
fills in the code (
acme_corporation), the name, the hostname, and the administrator username and email; the details sit behind Change settings, and only the administrator's password stays blank. Every new password uses the sharedNewPasswordField, which shows the policy, the strength and a confirmation. - Review. The person reads one summary: starting point, tenant name and code, administrator, and the profile's parameters. The screen states how many steps the profile runs. Nothing is created until the person confirms.
- Provisioning. The person confirms. The system starts the provision workflow and the page lists one line per workflow step: the step kinds the starting point orders – publish the base reference data, import the counterparties, import the parties, provision each party, load the staff, attach the photographs, start the market feeds, whichever the profile declares – and the completing step every tenant run appends, which marks the tenant active. The tenant and its administrator exist before the run starts. Each line reads pending, running, done or failed. This is the step that carries the state. The person can leave the page and come back. A failed step stops the instance, the tenant stays bootstrapping, and a retry resumes from the failed step; completed steps are kept. A tenant that should not continue can be discarded.
- Hand off. The tenant is active. The page shows the tenant's
administrator as
username@codeand offers two exits. Continue as tenant admin signs out, signs in as that administrator, and opens their first sign-in. Hand off to someone else signs out and shows the username to pass on; that person sets their own password at first sign-in. The web keeps the administrator's password in memory only until this point, and never writes it to storage. Where the deployment accepts self-registration, the person may also mark this tenant as the one a registration lands in. The installation's first tenant is marked when it is created, so a later tenant takes the mark over deliberately or not at all.
The rail is flat: a person sees five steps and never a journey inside a journey. Steps that change server state are final, so there is no Back past them. The text is short: no introduction box, because the rail already lists the steps, and the "you can leave and come back" note belongs on the provisioning step alone.
4. Screens and wireframes
One page with a flat step rail on the left and one step at a time. The tenant header, the profile's logo and the tenant name, appears on every step after the profile is chosen. The logo sits on a light tile, because ACME's black text fails on the dark theme.
Figure 1: New tenant: a flat step rail on the left, the chosen profile's cards, and one step at a time.
5. Prototype
Accepted by the user on 2026-09-22. The prototype is merged into main and
kept there as the record; the accepted commit names the state.
- Accepted commit
9dee57ccee, the tip ofprototype/new-tenant-journey, merged intomain- Source
doc/prototypes/setup/new-tenant/index.html- Open
/OreStudio/doc/prototypes/setup/new-tenant/once the site is built, or the file directly- Design record
- Task: Prototype the new tenant journey
Variant A, the stepper page, is the accepted one and is the base the first run journey builds on. Two other variants are kept on the branch and were rejected: B, a drawer over the Tenants list, and C, one progressive page. The prototype also settled the small things: the profile fills in the tenant details, the logo comes from the image store in the real build and not from the bundle, and every new password uses one shared field.
6. Entities composed
| Entity | What it contributes | Model |
|---|---|---|
tenant |
Code, name, hostname, type and status of the tenant being created | ores.iam.tenant |
tenant_type |
The classification the provision step sets | ores.iam.tenant_type |
tenant_status |
The tenant moves from bootstrapping to active | ores.iam.tenant_status |
account |
The tenant administrator the profile creates: username, email, account type, password (write only) | ores.iam.account |
account_contact_information |
The administrator's email and contact details | ores.iam.account_contact_information |
account_party |
The account-party association between the administrator and the tenant's first party | ores.iam.account_party |
counterparty |
The tenant-wide counterparties the profile publishes | ores.refdata.counterparty |
| Seed profile | The ordered workflow steps, the parameters, and the tenant details the profile prefills | ores.iam.seed_profile |
The password material — password_hash, password_salt and
service_password_hash — never reaches this screen. The password field writes
a hash server-side; no read returns one.
7. Operations and messages
| Step | Operation | Subject | Status |
|---|---|---|---|
| Choose a starting point | List the seed profiles | iam.v1.seed_profiles.list |
exists |
| Choose a starting point | Read a profile's steps and its parameter schema | iam.v1.seed_profile_steps.list-by-seed-profile-id |
exists |
| Describe the tenant | Submit the tenant and its administrator | iam.v1.ops.provision_tenant |
exists |
| Provisioning | Follow the workflow instance's progress | workflow.v1.ops.get_workflow_steps |
exists |
| Provisioning | Retry from the failed step | workflow.v1.ops.retry_workflow_instance |
exists |
| Provisioning | Discard a failed tenant | iam.v1.ops.reset_tenant |
exists |
| Hand off | Sign out | iam.v1.ops.logout |
exists |
| Hand off | Sign in as the tenant administrator | iam.v1.ops.login |
exists |
| Hand off | Choose the party to start in | iam.v1.ops.select_party |
exists |
iam.v1.ops.provision_tenant is declared in
ores.iam.tenant_provisioning_messages;
iam.v1.ops.login and iam.v1.ops.logout are declared in
ores.iam.login_messages, and
iam.v1.ops.select_party in
ores.iam.account_messages. All of
them exist in the generated web protocol. Completing the tenant is not a
request of its own: the last step of the run the provision request starts acts
on the tenant the run provisions, marks it active, clears its bootstrap mode
and completes its onboarding. workflow.v1.ops.get_workflow_steps and
workflow.v1.ops.retry_workflow_instance are not in the generated web protocol: the
generator emits no workflow package for the browser, so the BFF adds a route
for each and the wire client parses the reply.
iam.v1.ops.provision_tenant is the one verb that provisions a tenant. It takes
the profile code, the tenant fields, the administrator fields and the profile's
parameter values, creates the tenant and its administrator, and answers at once
with the id of the run that executes the steps the profile orders. It serves
every tenant, the first one included, and the shell's provision tenant and
bootstrap setup send the same request and follow the same progress read.
iam.v1.ops.reset_tenant discards a half-provisioned tenant. It soft-deletes
the admin-created data, turns the operational parties inactive, and sets the
bootstrap flag again. iam.v1.tenants.delete is partial by comparison: it
soft-deletes the tenant row and its delete rule sets the status to terminated,
but it cleans up no partial data. Neither subject stops a running workflow.
Retire or reset a tenant records
that gap in full.
8. What is missing
- No scripted proof of the route of its own. The path on a fresh database is
walked and asserted, because first run inlines these same steps and
projects/ores.web/scripts/verify-first-run.tswalks it. What no script covers is the standalone route:/tenants/newis walked in a browser and recorded inevidence/new_tenant_journey_live.txt, which is manual. - No discard that stops the workflow.
iam.v1.ops.reset_tenantdiscards the tenant, but it does not stop a workflow instance the tenant owns.iam.v1.tenants.deletecloses the row and sets the status to terminated, and it also leaves a running workflow alone. The missing part is the stop, not the discard. Retire or reset a tenant records that gap in full.
The rest of the journey is in place, and the list below is the record of what each part turned out to be.
- No journey state to add. A journey is a definition, not a server-side
object. A journey's progress state is a workflow instance in
ores.workflow, and that instance carries the workflow type, the current step, the request, the result and the error. No journey entity is added anywhere, and no subject returns a journey as an object. The provision-tenant definition exists: it runs one engine step per step kind the profile orders, plus the completing step every tenant run appends, and the client follows it by the instance id the request answers with. The run resumes on the server; the rail position is memory. - The seed profile is data, and it is read.
ores.iamowns the profile, its parameter schema and the step-kind catalogue. A profile is registered data that orders step kinds from a fixed catalogue:publish_bundle,import_lei_hierarchy,provision_party,load_staff,attach_photos,start_market_feeds,generate_trades. A new profile is a row; a new step kind is code. The readsiam.v1.seed_profiles.listandiam.v1.seed_profiles.getanswer the profile, andiam.v1.seed_profile_steps.list-by-seed-profile-idandiam.v1.seed_profile_parameters.list-by-seed-profile-idanswer its ordered steps and its parameter schema. The screen builds its cards from those rows and holds none of them in code. - One provision request serves every tenant.
iam.v1.ops.provision_tenanttakes the profile code, the tenant fields, the administrator fields and the profile's parameter values, returns a workflow instance id at once, and is what the page follows. The tenant and its administrator exist when it answers; the steps run afterwards. - Progress is read, not pushed. A step is followed through a read rather
than a pushed event. A run started through the generic request is followed
through
workflow.v1.ops.get_workflow_steps, which answers the run's status, the step it is executing and one summary per step, and both the browser route and the wire-client method behind it exist, so the page renders its rail from the read and asks again while the run is open. The engine'sores.workflow.workflow_instance_changedevent is not the progress contract: its payload is anentity_change_eventcarryingentity_idswhile the shared browser schema readsalpha2_codes, and no one schema can read every entity's own event. That mismatch is owned by its capture. - A retry resumes a stopped run. The contract keeps completed steps and
resumes from the failed one.
workflow.v1.ops.retry_workflow_instanceresumes a run that stopped on its failed step: it re-dispatches that step under its own identity, clears the run's error, and leaves every completed step and its result alone. A run whose definition declares the compensate policy still rolls back instead, andrecover_in_progress()stays for runs that were in progress when a service restarted.
9. Related journeys
- First run — these same steps run inline there, for the first tenant on an empty installation
- New party — add a legal entity to the tenant you just created
- Sign in — the tenant administrator's first sign-in, where the forced password change lands
- Tune the tenant — change the tenant's settings once it is active
- Retire or reset a tenant — suspend, reset or discard a tenant whose provisioning failed
10. Related knowledge
- Identity and Access Management — the Tenant lifecycle: Provision, Seed profile, Onboard
- Registration and Onboarding Defaults — where the registration defaults live, including the tenant this journey may nominate
- Tenants — the user guide chapter on what a tenant is
- Document type: user_journey
- How do I add a PlantUML diagram?
- Setup journeys