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

  1. 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.
  2. 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 shared NewPasswordField, which shows the policy, the strength and a confirmation.
  3. 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.
  4. 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.
  5. Hand off. The tenant is active. The page shows the tenant's administrator as username@code and 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.

wireframe_new_tenant.png

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 of prototype/new-tenant-journey, merged into main
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

  1. 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.ts walks it. What no script covers is the standalone route: /tenants/new is walked in a browser and recorded in evidence/new_tenant_journey_live.txt, which is manual.
  2. No discard that stops the workflow. iam.v1.ops.reset_tenant discards the tenant, but it does not stop a workflow instance the tenant owns. iam.v1.tenants.delete closes 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.

  1. 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.
  2. The seed profile is data, and it is read. ores.iam owns 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 reads iam.v1.seed_profiles.list and iam.v1.seed_profiles.get answer the profile, and iam.v1.seed_profile_steps.list-by-seed-profile-id and iam.v1.seed_profile_parameters.list-by-seed-profile-id answer its ordered steps and its parameter schema. The screen builds its cards from those rows and holds none of them in code.
  3. One provision request serves every tenant. iam.v1.ops.provision_tenant takes 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.
  4. 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's ores.workflow.workflow_instance_changed event is not the progress contract: its payload is an entity_change_event carrying entity_ids while the shared browser schema reads alpha2_codes, and no one schema can read every entity's own event. That mismatch is owned by its capture.
  5. A retry resumes a stopped run. The contract keeps completed steps and resumes from the failed one. workflow.v1.ops.retry_workflow_instance resumes 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, and recover_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

11. See also

Emacs 29.3 (Org mode 9.6.15)