Story: Implement the setup journeys in ores.web
Table of Contents
This page documents a story in Sprint 26. It captures the goal, current status, acceptance criteria, and the tasks that compose it.
1. Goal
A browser pointed at an empty installation takes a person from nothing to a working installation, and then to as many tenants and parties as they need. Three Setup journeys run against the real server:
- First run — the system administrator, the first tenant inline, the first sign-in, and Ready. This is the only journey in the catalogue that can run on an empty database.
- New tenant — the same steps on their own, for every tenant after the first.
- New party — a legal entity added to a tenant, with the accounts that work in it.
The three share one runtime and one step library. First run expands the new tenant steps inline, so the person sees one flat rail of eight steps and never a journey inside a journey.
This is the first group of Implement the user journeys in ores.web, and it lands the two things every later group needs. The runtime, because a journey is an ordered list of steps and one page renders that list. And the provisioning contract, because provisioning is server-side work that takes minutes and can fail half way, and no journey in the catalogue can be honest about a half-finished installation until the state and the progress are real.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent sprint | Sprint 26 |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-09-30 |
3. Acceptance
- First run runs in
ores.web, from an empty database to Ready, against the real server. No step is simulated and no profile is held in the code. - New tenant and New party run on the same runtime. They reuse the new tenant step library, and first run inlines those same steps.
- The provisioning progress is server-side. A person can close the page and come back and continue. A failed step stops the instance, and a retry resumes from that step and keeps the steps that completed.
- The seed profiles are registered data. Adding a profile is adding a row; adding a step kind is code.
- A profile may force a password change, and neither card does. The provisioner marks the account when the starting point asks for it and the login answer reports the mark, so a deployment that wants the tenant administrator to set a password only they know states it in the profile's row; the Operational card asked for it until the journey was walked, and the product owner chose to drop it.
- The password rules the screen shows are the rules the server enforces, read from the server rather than declared in the client.
- The bootstrap gate holds. While the system is in bootstrap mode the Setup page is the only page, and nobody can sign in.
- A scripted end-to-end run proves the path on a fresh database, in the
shape of
scripts/verify-login.ts. - The shell drives the same three journeys through the same contract.
bootstrap setup,provision tenantandprovision partycall the generic provision request this story added, and the end-to-end provisioning recipes underdoc/recipes/shell/provisioning/each run from an empty installation to a working tenant without orchestrating anything client-side. - Every gap the three journey documents record is closed, or stated on the screen and recorded in this story.
4. Tasks
| Task | State | Start | End | Description |
|---|---|---|---|---|
| Correct and add the architecture the journeys depend on | DONE | 2026-09-27 | 2026-09-27 | Make the architecture documents true before the first journey task starts: correct the contradictions the audit found, rewrite the stale decision record, and add the page that defines the journey runtime and the BFF boundary. |
| Decide the journey runtime and the step-call contract | DONE | 2026-09-27 | 2026-09-27 | Write the contract every journey is built to: what a step definition is, how one page renders the rail from the same list, and how a step reaches the server through a BFF route and a wire-client method. |
| Line the shell's provisioning up with the journey contract | DONE | 2026-09-29 | 2026-09-29 | Replace the three porcelain provisioning commands' client-side orchestration with thin calls to the same subjects the browser reaches, and keep the three end-to-end provisioning scripts working through them. |
| Restore the UI primitives and port the journey runtime | DONE | 2026-09-27 | 2026-09-27 | Restore the primitives, the password field, the password policy and its test, and the brand artwork that e47043269f deleted, and port the prototype's rail and step parts onto journeys/runtime.ts with the simulation removed. |
| Build the application shell, the route table and the bootstrap gate | DONE | 2026-09-27 | 2026-09-27 | Two shells on the primitives, the route table, and the bootstrap gate that makes /setup the only page while the system reports bootstrap mode, including the one read the gate needs. |
| Give the setup screen its banner and its copy | DONE | 2026-09-27 | 2026-09-27 | The setup screen is the first thing an empty installation shows, so it carries the ORE Studio banner, and its copy states the mode and the next action without the conversational filler. |
| Create the first administrator from the setup screen | DONE | 2026-09-27 | 2026-09-27 | The setup screen creates the first administrator with no session, through a hand-written BFF route and a wire-client method, and the server closes bootstrap mode when it succeeds. |
| Default the administrator's identity on the setup screen | DONE | 2026-09-27 | 2026-09-27 | The setup form proposes super_admin and its system-scope address, so an empty installation needs only a password, and both fields stay editable. |
| Model the seed profile and seed the two profiles | DONE | 2026-09-27 | 2026-09-28 | The seed profile is registered data: a row orders step kinds from a fixed catalogue and declares the parameters its form takes. Empty operational and ACME demo become rows. |
| Read the seed profiles through the BFF | DONE | 2026-09-28 | 2026-09-28 | The tenant journey's first step shows the starting points, and the browser reaches the server through one hand-written BFF route per step. Add the route that returns the profiles with their ordered steps and their declared parameters, and the wire-client method behind it. |
| Make the seed profile rows match the prototype's cards | DONE | 2026-09-28 | 2026-09-28 | The prototype at prototype/first-run-journey is the accepted record for the cards' copy and shape. The rows seeded by the previous task carry a longer description, no bullets, and a counterparty count where the contract and the prototype both say a counterparty size. Reconcile the model, the rows and the read with the prototype. |
| Add the generic provision request | DONE | 2026-09-28 | 2026-09-28 | Provisioning a tenant is one request for every tenant, the first one included, instead of a bootstrap-specific request and an ACME-specific one. The request names the seed profile and the values its parameters take, and answers with a workflow instance id at once. |
| Define the tenant provisioning workflow | DONE | 2026-09-28 | 2026-09-28 | The provision-tenant definition over the fixed step kinds, in the shape workflow.v1.parties.provision already uses, running provision party once per party. |
| Execute the ACME provisioning step kinds | DONE | 2026-09-28 | 2026-09-28 | The three step kinds only the ACME demo card declares — load_staff, attach_photos and start_market_feeds — so the demo card's run creates the tenant's staff and their parties, attaches the images its records display, and starts its market feeds. |
| Follow the progress and retry the failed step | DONE | 2026-09-28 | 2026-09-28 | The read the journey's page follows, the retry verb that resumes a stopped run from its failed step, and the decision that settles the mismatch between what the engine publishes and what the browser decodes. |
| Carry the forced password change and read the password policy | DONE | 2026-09-28 | 2026-09-28 | The profile's force-password-change choice reaches the account the provisioner creates, the login reply carries the flag the account holds, and a read returns the password policy the server enforces, so the first sign-in can force a change against rules read from the server. |
| Build the First run journey | DONE | 2026-09-29 | 2026-09-29 | The first run journey leads an empty installation from the welcome screen through creating the system administrator, the first tenant's steps inline on one rail, the hand-over to its administrator, their first sign-in, and Ready. |
| Start a tenant from a GLEIF entity | DONE | 2026-09-29 | 2026-09-29 | The starting points become three: build the tenant by hand, build it around a parent legal entity the deployment holds in its GLEIF data, or take the prefilled demo. The GLEIF card opens on a search over those entities and derives the tenant from the one chosen; the manual card may leave the legal entity out altogether. |
| Keep the setup screen after a reload | DONE | 2026-09-29 | An installation is set up when it has an administrator and a tenant of its own, so the setup journey is the only screen until it has both. A browser that reloads half way through comes back to the rail and signs in as the administrator the deployment already has, rather than being stranded with no way back to the work. | |
| Mop up the provisioning surface and the synthetic organisation | DONE | 2026-09-29 | 2026-09-29 | Rename the first-run command to bootstrap setup, retire the generated duplicates of the provision verbs, delete the synthetic organisation generator and its recipe, and close the story with its documents brought up to date. |
| Build the New tenant and New party journeys and the scripted browser proof | DONE | 2026-09-29 | 2026-09-30 | Two acceptance lines of the story are unmet: the New tenant and New party journeys are not journeys of their own (their step library exists and first run inlines it, but no route renders them alone), and no scripted end-to-end run proves the browser's path on a fresh database. |
| Name the bootstrap commands consistently | DONE | 2026-09-29 | 2026-09-29 | The bootstrap menu repeats its own name in one of its verbs: a caller types bootstrap bootstrap-status. The generated command verb comes from the message name, and the message is named bootstrap_status_request, so the model needs a way to state the verb it wants. |
| Give a workflow step a deadline the engine enforces | DONE | 2026-09-30 | A step declares how long it may run and the engine fails it when it does not answer, so a run whose service is gone stops being in_progress forever and a waiter sees the truth. | |
| Choose the root legal entity from the register | DONE | 2026-09-30 | 2026-09-30 | The Operational starting point's root LEI is a free text field while the GLEIF card searches the entities the deployment holds. A person must know a code the deployment can check nothing about, and a wrong one arrives as the publication's SQL rather than as a sentence about the entity. Make the Operational card ask the same read the GLEIF card asks. |
5. Plan
The architecture change lands first, because step 1 decides the runtime and the step-call contract and cannot decide them against documents that still contradict each other.
The steps are ordered so that each one leaves a state a person can look at. The first three land before the provisioning contract, because none of them depends on it and all of them are permanent.
| # | Step | What it leaves behind |
|---|---|---|
| 1 | Decide the journey runtime and the step-call contract | The step definition, the page that renders the rail from it, the rule for a step that cannot be returned past, and how a step reaches a subject: which BFF route and which wire-client method. |
| 2 | Restore the UI primitives and port the journey runtime | The primitives, the password field, the password policy and its test, and the brand artwork, restored from e47043269f^. The rail and the step parts ported from prototype/first-run-journey with the simulation removed. |
| 3 | Build the application shell, the route table and the bootstrap gate | AppShell and PublicShell, the routes, and /setup as the only page while the system is in bootstrap mode. |
| 4 | Reach the bootstrap and journey subjects from the browser | BFF routes and wire-protocol client methods for bootstrap status, create administrator, seed profile read, provision, progress and retry. |
| 5 | Model the seed profile and its step catalogue, and seed the two profiles | iam.v1.seed_profiles.list and .get, each profile's parameter schema and its prefilled tenant details, and Empty operational and ACME demo as rows. |
| 6 | Add the generic provision request that returns an instance id at once | iam.v1.tenants.provision, taking the profile code and its parameters, for every tenant, the first one included. |
| 7 | Order the tenant provisioning steps as a workflow definition | The provision-tenant definition over the fixed step kinds, in the shape workflow.v1.parties.provision already uses, running provision party once per party. |
| 8 | Follow the progress, retry from the failed step, and settle the event payload | The read the page follows, the retry verb, and the payload mismatch. The event subject exists and the retry verb does not; what the engine publishes does not match what the browser decodes, so the page needs a read and the mismatch needs an owner. |
| 9 | Carry the forced password change, and read the password policy | The login handler copies password_reset_required into its reply, and a read returns the policy the server enforces. |
| 10 | Build the First run journey | Welcome, create the administrator, the inlined new tenant steps, hand off, first sign-in, Ready. |
| 11 | Build the New tenant and New party journeys | The same steps on their own rails, plus the party journey's legal-entity search and its account selection. |
| 12 | Line the shell's provisioning up with the same contract | provision system, provision tenant and provision party become thin callers of the same subjects the browser calls, and the orchestration they do today moves to the server. |
| 13 | Prove the three journeys on a fresh database, on both clients | The three end-to-end provisioning recipes each run from an empty installation to a working tenant on the shell, and the same three journeys run in the browser. |
6. Decisions
- The gate answers the deployment, not the tab. An installation is set up when it has an administrator and a tenant of its own, so the setup screen is the only screen while either is missing, and the tab's own memory holds the rail afterwards, until the person finishes. The earlier rule stopped at the administrator, which left a browser that reloaded half way through stranded on a sign-in screen with no way back to the work: closing a page is not finishing a journey. The deployment's state survives a reload; a position held only in memory does not, so the rail opens at the step that signs in as the administrator the deployment already has rather than at the beginning.
- The three journeys share one runtime and one step library. First run inlines the new tenant steps. Positions in the rail are named rather than counted, so adding a step to one journey cannot silently shift the meaning of another.
- The accepted contracts are the target. The design retires
iam.v1.bootstrap.provision-tenantandiam.v1.tenants.provision-acme; the generic request replaces both, first tenant included.iam.v1.bootstrap.create-adminstays, because the accepted contract keeps system bootstrap to the system administrator alone. - The seed profile is data. A profile orders step kinds from a fixed catalogue, so a new profile is a row and a new step kind is code. The screen builds its form from the profile's declared parameter schema and holds no profile of its own.
- A step kind reads what it acts on from its own arguments. A profile's row states the bundles, the parties, the datasets and the keys a kind works from, so the executor holds no profile's names and one kind serves every profile that orders it. A kind that needs the run's parameters reads them too, which is how a form supplies an LEI the row leaves out. A row whose kind cannot be honoured is refused before the run starts, naming the kind, so no card can half-provision a tenant.
- A party is named by the legal name its hierarchy import created it with. A party's external identifier is guarded by party isolation, which a step's own context does not carry, so the name is the handle the profile can state and the deployment's party read answers.
- A run belongs to the tenant that asked for it. The progress read answers the tenant that owns a run and nobody else, so a provisioning run owned by the tenant it creates could not be followed by the person who asked for it: their session is in another tenant. The run's tenant scopes its rows and the tenant it provisions travels in the request, so the starter can watch and resume their own work and no tenant gains a view of another's runs.
- The failure policy belongs to the definition. A definition declares whether a failed step rolls its completed work back or stops the run on that step. Provisioning declares stop, because its completed work is published data a person expects to keep; the default stays compensate for a short saga of writes. A stopped run keeps every completed step and waits for a retry.
- Every kind fails its step rather than half-finishing it. The engine keeps the steps that completed and stops the run on the one that failed. The synchronous ACME handler the work was lifted from is not the model: it reported no step, so its uneven best-effort behaviour is not carried.
- The tenant's type is the starting point's, and a replaced verb retires
with its last caller. A tenant row carries a type that a lookup table
validates, and the profile states it: Operational's tenants are
production and ACME demo's are evaluation. The column went on
seed_profilerather than the provision request, because a request that named the type would be a second starting point beside the one the person chose. For the same reason the two verbs the generic request replaces stay reachable until their last caller moves: the shell is that caller, plan step 12 is where it moves, and the work the ACME verb orchestrates server-side is the work the provisioning definition orders — retiring it before step 7 would leave--source acmecreating a tenant with none of its data. - The provisioning steps are idempotent. A retry resumes from the failed step and keeps the steps that completed. Discarding a half-provisioned tenant is a separate administrator action.
- The journey's state is a workflow instance. It already carries the kind, the current step, the request, the result and the error. No journey entity is added, and no subject returns a journey as an object. Journey execution records why.
- Bootstrap mode is the system setting
system.bootstrap_mode.ores_iam_create_initial_admin_fnclears it, so the gate closes when the administrator is created and not before. - The prototype is the design record for the rail, not for the code.
prototype/first-run-journeyis the accepted authority on the step sequence, the copy and the layout. The variants that lost, and the prototype switcher, are not carried. ores.workflowis another environment's component. Two of its stories are in flight: its clean-up and its end-to-end exercise. This story consumes the engine and adds the retry verb the journey needs. It does not rewrite the engine or its models.- A step reaches the server through one hand-written BFF route per step, each parsing a generated schema and each listed in The component specification. There is no generic forwarding route, so the browser cannot name a subject.
- The BFF is the browser's only HTTP surface.
ores.httpserves external clients, and the browser never reaches it. - Only the server-side workflow resumes. The steps around it hold no server state, so a journey progress record is not added and the decision that no journey entity is needed stands.
- ores.iam owns the seed profile, its parameter schema and the step-kind catalogue.
- The architecture documents are corrected before this story's first
task starts.
The audit found the accepted
sprint 25 catalogue still mandating a journey entity and shell journey
commands, three journey documents claiming
ores.workflowdoes not exist, and no page defining the runtime at all. The answers above are recorded in that audit and land as their own documentation change. Every journey has two clients, and the shell is the other one.
ores.shellcarries the three Setup journeys as porcelain commands —bootstrap setup,provision tenantandprovision party. The recipes underdoc/recipes/shell/provisioning/run a whole system provisioning, one per starting point, and between them they cover the three journeys this story builds in the browser:barclays_system_provision.orgcreatesbarclaysfrom the GLEIF starting point with the hierarchy's root LEI, then runs the party stage and sets the default party.how_do_i_provision_the_system_with_acme_corporation_holding_group.orgcreatesacme_corporationfrom the demonstration starting point, whose own steps import the Acme hierarchy, publish each party's data, attach the staff photographs and start the market feeds. It stops after the tenant stage and additionally publishes the vendored compute packages, so it is the only one that does not run the party stage on its own.tenant_provision.orgadds a tenant to a system that is already set up, which is the New tenant journey's shell form.
A step's contract therefore has two callers from the day it lands, and a contract that suits only one of them is wrong.
- The shell's provisioning orchestration is replaced, not preserved.
The accepted
seed profile contract already
decided this: "ores.web and ores.shell are thin callers. The shell's
client-side orchestration is deleted." The three porcelain commands
were 696 lines that published bundles, waited on workflows, generated a
synthetic organisation and associated parties themselves. That landing
left
bootstrap setup,provision tenantandprovision partyas thin callers ofiam.v1.tenants.provisionandiam.v1.parties.provision– the same subjects the browser calls – and retired the verbs the shell was their last caller of. The synthetic organisation generator went too: provisioning creates no synthetic parties, and synthetic tenants and parties are their own work when they return. - The shell is lined up in the same group, not afterwards. A shell that still drives the retired subjects while the browser uses the new one is two clients disagreeing about what provisioning is, and the disagreement is invisible until somebody runs one of those scripts.
- The three end-to-end provisioning recipes are the acceptance sequence. They are the only artefacts in the tree that take an empty database to a working tenant, one per provisioning path, and between them they cover the three journeys — two of them through the party journey as well as the tenant one. All three are kept working, and they are what the story's last plan step runs.
- The scripted proof drives the browser's client, not a browser. The last
acceptance line asks for a run in the shape of
verify-login.ts, and that verifier asserts against the real system with no screen in the way.scripts/verify-first-run.tsis the same: it calls the browser's own client, its own request-building and its own password rules, and supplies only the two things a browser provides and Node does not — a base address for the relative paths and a jar for the session cookie. An answer the browser could not parse fails it, because the browser's schema is what parses it. A driven browser would prove the rendering too, and would fail for reasons that are not the contract's: the rails and the screens are covered by the web suite, and the path is covered here. - A journey that can only happen once is proved on a fresh database or not at all. The proof's first assertion is that the installation still needs its administrator, and a deployment that is already set up is refused rather than half-walked. The alternative — a script that sets a deployment back into bootstrap mode — would prove a state no person reaches.
- A starting point that needs a legal entity asks the deployment for it. The
Operational card declared its root LEI as free text while the GLEIF entity
card searched the register, so one card could be answered only by somebody who
already knew a code. Both now declare
legal_entity, which the form renders as the search over the deployment's entities and the parameter check guards, and the entity stays optional because a tenant nobody holds an entity for is still a tenant: its parties come from the party stage.
7. Gaps the journeys still record
The three journey documents name what they still reach for, and none of it is silent. Each gap is either owned by a document of its own or stated here:
- A tenant cannot be reset in a way that also stops its run. Retire or reset a tenant records it.
- The engine's change event cannot be read by the browser, because the shared schema reads a country-shaped field. The progress read is the contract instead, and the mismatch is captured.
- The party describe step carries no accounts picker. Nothing writes the chosen associations — the run joins the caller alone — and a picker whose choices went nowhere would be worse than no picker.
- Where a party hangs is the deployment's answer and not the person's. Draw the reporting line is that journey.
- The standalone
/tenants/newand/parties/newroutes are walked in a browser and recorded as evidence rather than scripted. Only the first run can happen on an empty database, and that is the path the scripted proof walks.
8. Out of scope
- The Tenants and Parties pages as permanent homes. The journeys link to them; the pages are their own work.
- The Entry group. First run signs in through the path that exists. The Entry story builds the screen, self registration and the failure paths.
- The seed profile step kinds that no profile uses yet.
- The
ores.qtbootstrap wizards, which are evidence and not a design. - The producer of a profile's bundles, which belongs to the component that owns that data.
9. Documents
| Document | Kind | Purpose |
|---|---|---|
| First run | user_journey | The journey this story implements first, and its list of gaps. |
| New tenant | user_journey | The steps first run inlines, and the profile contract. |
| New party | user_journey | The third journey, and the party workflow. |
| Prototype the first run journey | task | Where the rail, the copy and the layout were accepted. |
| Journey execution | knowledge | The composition classes, and why the state is a workflow instance. |
| Component Clean Standard | knowledge | The standard the C++ work in this story is held to. |
| TypeScript Clean Standard | knowledge | The standard the TypeScript work in this story is held to: the full strictness set, no unchecked escape hatch, parse at the boundary, and one gate per item. |
doc/recipes/shell/provisioning/barclays_system_provision.org |
recipe | End-to-end on the GLEIF path: system and tenant from the GLEIF starting point with the hierarchy's root LEI, the counterparty set the profile's parameter names, and the party stage, with the sign-ins between them. |
doc/recipes/shell/provisioning/how_do_i_provision_the_system_with_acme_corporation_holding_group.org |
recipe | End-to-end on the demonstration path: system and tenant from the acme_demo starting point, whose own steps import the Acme hierarchy, publish each party's data, attach the staff photographs and start the market feeds. It stops after the tenant stage and publishes the vendored compute packages, so it does not run the party stage on its own. |
| Audit the journey, provisioning and web-client architecture before the first implementation story | investigation | What the architecture documents say today, what is stale, and what is written nowhere. The story starts only after this is signed off. |
| The provisioning architecture | investigation | Where provisioning orchestration belongs, what a step may target, and how errors, timeouts and retries behave, so that the journeys stay click-minimal without any provider's specifics in code. |
10. Result
Both clients run the three Setup journeys' work against the same contract, and nothing is left of the provisioning the wizards did client-side. The browser sets an empty installation up from the setup page — the first administrator, the first tenant from a registered starting point, the hand-over, the first sign-in and Ready — and a reload comes back to the rail rather than stranding the person. The shell says the same things with bootstrap setup, provision tenant and provision party, each sending one request per stage and following the run through the same progress read the browser's rail renders, and the end-to-end recipes each ran from their own empty installation to a working tenant. The two verbs the generic request replaced retired with the shell, and so did the generated duplicates of the porcelain commands and the synthetic organisation generator. Every acceptance line is met, the last one by scripts/verify-first-run.ts, and the Operational card now chooses its root legal entity from the deployment's register rather than taking a code it cannot check.