Investigation: Audit the journey, provisioning and web-client architecture before the first implementation story
Table of Contents
A point-in-time record, written while the story that commissioned it was still open, of an audit of the documentation that describes how a user journey is built, provisioned and served to a browser.
It is a snapshot, not a description of the system. Whatever it concludes that the code must keep doing belongs on the page that owns the subject; cite that page from code, recipes and models, not this one.
1. Context
Implement the setup journeys in ores.web is ready to start, and its first task is to settle the journey runtime and the step-call contract. The story states that the build targets two contracts accepted in sprint 25: the seed profile contract and the bootstrap workflow catalogue.
Both were accepted on 2026-09-22. One day later Journey execution redefined the vocabulary and called one of their decisions redundant. Nothing was changed in the older documents. So the story was about to build against a target that contradicts itself, and against a corpus of journey documents whose `* What is missing` sections had not been re-measured since.
The question this audit answers: which of these documents still describe the system, which are stale against the code, and what architecture is load-bearing but written nowhere?
2. Methodology
Two sweeps, one over the client-side architecture and one over the journey and provisioning architecture. Every claim below was checked against the code by reading it, not by trusting a document.
Trees read, in full or in the relevant part:
doc/meta/— the document-type contracts, including user_journey.doc/knowledge/—architecture/,journeys/,security/,ui/,domain/, and the root index.doc/plans/,doc/llm/(skills, runbooks, memory),doc/recipes/shell/.projects/ores.web/—README.md,modeling/, and all four packages underpackages/.projects/ores.http/modeling/,projects/ores.iam/{modeling,core}/,projects/ores.workflow/{api,core,service}/,projects/ores.sql/.- The sprint 25 stories
web_bootstrap_prototypeandweb_iam_journeys, and the sprint 26 storiescomponent-clean-program,codegen-http-routes,extract-user-journeys, and the two new stories.
Tools: ripgrep for discovery, read for the documents themselves, and
git show / git ls-tree for the prototype branches, which were never
checked out because this worktree is shared.
Head at the time of writing: ef8df25c61. Every number and every line
reference below is that day's state.
3. Paths taken
Three approaches were tried, and the first two failed.
- Trust the journey documents. Abandoned. Three of their
* What is missingitems are false against the code, and every one of them names a subject that the current decisions retire. - Trust the sprint 25 contracts. Abandoned. The seed profile contract
survives, but the workflow catalogue's items 5 and 6 specify a
/journey/entity and shell journey commands that the vocabulary document and the new story both drop. - Read the code and treat the documents as claims. Kept. This is the method above, and it is what produced the findings below.
4. Conclusions
4.1. The vocabulary is settled and consistent
Four documents agree, and none of them needs to change: Journey execution, User Journeys, Document type: user_journey, and the Journey term in Identity and Access Management. They say the same three things: a journey is a definition and not a server-side object; its progress is a workflow instance; and no journey entity is added.
ores.web,
the superseded entity specification
and Internal Actor Impersonation are
also accurate, and the superseded specification records its own
retirement. Change notification is
accurate and its consumer is genuinely absent: the runtime should read
it before it decides how a journey follows a workflow.
The component specification
is accurate about the shell, the session and the routes it lists, and it
repeats the @ores/contracts error in one row of its package table, so
it is not counted as clean: see the table below.
4.2. Seven findings change what the story does next
- The progress subject already exists; the progress payload does not.
ores.workflow.workflow_instance_changedis published by the engine on every state change (projects/ores.workflow/core/src/service/workflow_engine.cpp:98), and its subject follows the convention the BFF subscribes on —ores.<component>.<entity>_changed, built atprojects/ores.web/packages/bff/src/change-events.ts:67. What the engine sends does not match what the browser can read: the payload is anentity_change_eventcarryingentity_ids, and the schema the browser decodes every change notification with readsalpha2_codes, a field named for the country event it was written for. Every field of that schema has a default, so the mismatch raises nothing and hands the subscriber an empty identifier list. Provisioning progress therefore needs no new subject, but it does need the payload settled, and it needs a read either way, because the event carries identifiers and a time and no state. The mismatch is filed as a capture. A party-provisioning workflow already exists.
workflow.v1.parties.provisiontakes a list of parties and runs a three-step saga per party —refdata.v1.parties.put,iam.v1.accounts.save,iam.v1.account_parties.put_many— with compensation on failure (projects/ores.workflow/api/include/ores.workflow.api/messaging/workflow_protocol.hpp:66). Its response declaresworkflow_instance_ids, and the handler leaves that vector empty: it publishes each start request and never learns the instance id (projects/ores.workflow/core/src/messaging/workflow_handler.cpp:108-119), so it returns no instance id per party. The new-party journey records this as missing. The real gap is a mismatch: it creates accounts with passwords and does not take an existing party plus chosen accounts, and it does not activate, onboard or publish bundles.Two claims in this finding were re-measured on 2026-09-27 and are corrected above. The audit first named the saga's subjects as
refdata.v1.parties.saveandiam.v1.account_parties.save; nonats_subjectuses either name. It also first said the response returns one instance id per party, which the handler does not do.- A retry verb is genuinely absent. No retry, resume or abandon
subject exists anywhere in
ores.workflow. The only recovery isworkflow_engine::recover_in_progress(), run once at service start. This is the one provisioning gap the journey documents state correctly. - The browser does not use
@ores/contracts.projects/ores.web/packages/web/package.jsondoes not depend on it and no file underpackages/web/imports it; the browser parses with@ores/wire-protocol/browser(packages/web/src/api/transport.ts).README.mdandpackages/contracts/src/index.tsboth claim the browser and the BFF share those schemas. They do not. - Two HTTP surfaces exist and nothing relates them.
ores.httpis the platform's REST gateway, with routes generated per component from the models that opt in. It is never mentioned in anyores.webdocument, yet the site description carrieshttpBaseUrl(packages/contracts/src/site.ts:56) and the wire client carriesdiscoverHttpBaseUrl()(packages/wire-protocol/src/client.ts:430), which has no caller anywhere. Nothing states whether the gateway and the BFF are alternatives or layers. - The decision record behind the generated wire types is wrong in four
ways, and it is the page that anchors the work the first task does.
projects/ores.web/modeling/0001-protocol-types-from-codegen.orgsays the codegen template is not built (the templates exist and the output is committed), that the artefact lives in the C++ repository (it lives underpackages/wire-protocol/src/generated/), that theores.codegen.operationmodel is deferred (26 models use it), and that hand-writing the TypeScript schemas is the current state. It is the See-also of the component specification, so whoever adds the journey client methods reads it first and is told to hand-write what the generator already produces. - The shell is a second client of the same three journeys, and it
orchestrates on the client today.
ores.shellcarries the Setup journeys as porcelain commands —provision system,provision tenantandprovision party, 696 lines in one file — which publish bundles, wait on workflows, generate a synthetic organisation and associate the administrator with the operational parties themselves. Three recipes underdoc/recipes/shell/provisioning/run a whole system provisioning, one per value the--sourceflag accepts, and they are the only artefacts in the tree that take an empty database to a working tenant:acme_system_provision.orgon the synthetic path,barclays_system_provision.orgon the GLEIF path, andhow_do_i_provision_the_system_with_acme_corporation_holding_group.orgon theacmepath, which reaches the synchronous handler. The accepted seed profile contract already decided the outcome: the shell becomes a thin caller and its client-side orchestration is deleted. So this is accepted, unimplemented scope rather than new work, and the audit missed it because it read the shell only as a consumer of the recipes that named the subjects being retired.
4.3. Stale documents, with the evidence
| Document | Claim that is no longer true | Evidence | ||
|---|---|---|---|---|
| bootstrap workflow catalogue, item 5 | A /journey/ entity belongs in ores.workflow, beside workflow_instance, with kind, current step, subjects and resume actors |
Journey execution calls it redundant; the setup story says no journey entity is added | ||
| bootstrap workflow catalogue, item 6 | The shell gains journey start/status/next/abandon |
No such command exists in ores.shell, and the setup story lists no shell task |
||
| bootstrap workflow catalogue, items 5–6 | Human steps, and a list of actors who may resume | workflow_instance carries no human-step or resume-actor field (projects/ores.workflow/api/include/ores.workflow.api/domain/workflow_instance.hpp:44) |
||
| First run, New tenant, New party | "No ores.workflow component and no journey subject exist today" |
The component exists, with entities, services and subjects | ||
| The same three journeys | The candidate is iam.v1.journeys.get-active |
Retired by the vocabulary; no journey entity is needed | ||
| The same three journeys | iam.v1.bootstrap.provision-tenant and iam.v1.tenants.provision-acme are the working path |
The setup story retires both for a generic iam.v1.tenants.provision |
||
| First run | "No streamed progress"; the candidates are a workflow_instance_changed event or an iam.v1.workflow_instances.get read |
The event subject is published, but what it carries does not match what the browser decodes (finding 1 above). The read the document asks for does exist: workflow.v1.instances.steps and workflow.v1.instances.list are implemented and subscribed. |
||
| New party | "Start the provision-party workflow | none | missing" | workflow.v1.parties.provision exists and returns party ids; its declared workflow_instance_ids are never filled |
| New party | party_essentials exists and "only the ACME handler publishes it today" |
party_essentials occurs nowhere under projects/; it is a bundle the contract proposes, not one that exists |
||
| New tenant | Nothing discards a half-provisioned tenant | iam.v1.system.reset-tenant exists |
||
| User Journeys and user_journey type | "The list, detail and history primitives still exist" | Commit e47043269f deleted them with the generated screens |
||
projects/ores.web/README.md and packages/contracts/src/index.ts |
The BFF and the browser both parse the @ores/contracts schemas |
The browser does not depend on the package | ||
| The component specification, its package table | @ores/contracts is "the HTTP shapes that the BFF and the browser both parse" |
The same falsification as the row above, in a third document. The package is the BFF's today; the browser parses with @ores/wire-protocol/browser. The intended split is real, the tense is wrong. |
||
projects/ores.web/README.md |
compass codegen entity generate <entity> --address ores.ts.protocol |
No such subcommand | ||
The three setup journeys, * Prototype |
Source :: projects/ores.web/packages/web/src/pages/prototype/newTenantJourney/ |
The path does not exist on main; it survives only on the prototype branches |
||
doc/plans/2026-03-30-three-level-provisioning-and-workflow-service.md |
Login-triggered tenant and party wizards | Superseded by the accepted contract, and not marked as such | ||
doc/agile/.../web_iam_journeys/story.org |
/journey/ means a server-stateful guided path |
Contradicts Identity and Access Management | ||
doc/recipes/shell/bootstrap/ and .../tenant_provisioning/ |
Recipes for the two retired subjects | They break the day the subjects are removed | ||
projects/ores.web/modeling/0001-protocol-types-from-codegen.org |
Status "Proposed … not built"; the output "lives in the C++ repository"; the operation model is "deferred"; and "hand-write the TypeScript schemas. The current state." | The templates exist and the output is committed under packages/wire-protocol/src/generated/, and 26 models use ores.codegen.operation |
||
| Entity Addressing | Its description and lines 5, 17 and 54 present BFF entity routes and a browser entityRecordPath as live |
The same document at lines 87–94 records both retired on 2026-09-23, and neither exists in the code. It contradicts itself, so a reader cannot tell which half is current | ||
| User Journey Extraction Standard | Its V03 names the superseded entity specification as a binding screen rule | That specification is void, and the screen rules a journey follows are the ones it needs to state itself | ||
| Identity and Access Management | ores.qt is listed as a client that runs the same server-side operation |
ores.qt is retired and frozen as reference; the clients are ores.web and ores.shell |
||
doc/knowledge/ui/ux_language.org |
Every entity presents the same three windows, and badges resolve through BadgeCache |
All of it is the removed Qt client; User Journeys says one screen, not one per entity | ||
projects/ores.web/modeling/icon_reference.org, doc/knowledge/ui/icon_guidelines.org |
78 icon names; the asset paths "point into the desktop client's resources/icons/" and are "not a live path list" |
68 files sit in packages/web/src/assets/icons/, and every proj: path resolves there |
||
doc/knowledge/journeys/entry/journey_sign_up.org |
POST /api/v1/auth/signup |
The HTTP-routes story records that path as 404ing | ||
doc/llm/skills/pstack.org, skill_verification.org |
"No TypeScript here"; "When a client UI exists (none today)" | ores.web is TypeScript and it is the client being built |
||
| ores.web | Depends on TanStack Table and Playwright; follows Component architecture | Neither is a dependency of the workspace, and that document describes C++ components with CMake targets, which this component has none of |
4.4. Architecture that is load-bearing and written nowhere
- The client-side journey runtime. No document defines a step
definition: its fields, its declared inputs, which subject it calls,
which steps are final, or how one journey inlines another's steps.
Journey execution and
the document type both name "an ordered
list of steps" and stop there. The only implementation is on a
throwaway branch, and its source path on
mainis dead. - The step-call contract. How a step reaches a subject — which BFF route, which wire-client method, and what a route may not do — is stated nowhere. The component specification lists the routes that exist today and says only that "a journey screen adds the call it needs".
- The bootstrap gate. The setup story requires that
/setupis the only page whilesystem.bootstrap_modeis set. No architecture document describes that gate. - The two HTTP surfaces. See finding 5 above.
- The ownership of seed-profile data and the step-kind catalogue. The seed profile contract fixes the step kinds and says a profile is rows, and names neither an owning component, a model, nor a codegen path.
- Where the provision-tenant workflow definition lives and how it
registers. The only account of definition registration is in
doc/plans/2026-05-14-workflow-engine-hardening.org, not indoc/knowledge/architecture/. - What carries the rail position for the human steps. A workflow journey's server work is resumable, but the steps around it — sign in, choose a party, set a password — are not, and nothing states whether the person can leave and come back to them. The prototype held the position in memory.
- Which standard governs the TypeScript client. The
Component Clean Standard names no
TypeScript item, and the clean-standard programme story records that
"the TypeScript UI is retired". That is true of the generated entity
screens and of the TypeScript UI codegen, and false of
ores.web, which is now the interface being built. - The mapping rule from a browser request to a NATS subject. The BFF
is hand-mapped: each handler calls
session.client.callAuthenticated(subject, schema), and there is no generic forwarding proxy. Nothing states the rule, what the BFF may not do, or how a route is added. This is the second half of the step-call contract, and the first task cannot settle one half without the other. - The dependency direction between the four packages, and why the
browser must import
@ores/wire-protocol/browserrather than the package root. The component specification gives each package a job and states no direction between them. - The end-to-end path from a model to a validated browser response —
model, to facet, to
src/generated/, to the browser entry point, to the BFF reply, to the Zod parse. It is covered in three places and in none of them whole, and the decision record that should anchor it describes a state that no longer exists.
5. Recommendations
- Sign the architecture off before the first task starts. The decisions below are the ones the first task would otherwise make in code, and three of them are irreversible enough to be worth an explicit answer.
- Correct the superseded catalogue task in place. Its items 5 and 6 must be marked superseded rather than silently ignored, because the setup and programme stories both cite that document as a target.
- Correct Journey execution. It described progress as one missing thing; the progress reads exist and are subscribed, the progress event payload does not reach the browser, and retry does not exist at all. It is also the right owner for the step vocabulary and for the definition of a human step.
- Correct the three setup journeys' ` What is missing` sections, their operation tables and their prototype source paths.* The extraction story owns those documents, so this is either a task on that story or an explicit delegation to this one.
- Grow The component specification
with the journey runtime, the step-call contract, the bootstrap gate
and the new routes. That document is the natural owner, and the first
task's output belongs in it. A client-runtime page that both clients
could read also belongs in
doc/knowledge/architecture/, and it must follow the knowledge document contract: a three-to-five sentence blurb and then* Detail, with no separate* Summary. Theores.webmodeling/pages already deviate from that contract, so this is a chance to stop the drift rather than continue it. - Rewrite
0001-protocol-types-from-codegen.org. It is the anchor for the generated wire types, and it currently denies the generator and points at the wrong repository. Nothing in the first task can be trusted until it is corrected. - Correct the pages whose subject no longer exists:
Entity Addressing (it
contradicts itself),
ux_language.org(Qt windows andBadgeCache), the two icon documents,component_overview.org,README.md, and the twodoc/llmskills that state there is no TypeScript or client UI. A page that describes a deleted subject is not history; it is a wrong answer to a question a reader is entitled to ask. - State the two HTTP surfaces once, and resolve
httpBaseUrlanddiscoverHttpBaseUrl, which are declared and unused. - Correct or wire
@ores/contracts. A package whose stated job is false is worse than a package that is not used. - Correct the dead prototype source paths and the shell recipes, or delete them. A path a reader cannot follow is a broken link with extra steps.
- Reconcile the knowledge template with the knowledge contract.
The knowledge document contract
is explicit that a knowledge page carries a blurb and
* Detailand no separate* Summary, "the summary is the blurb". The scaffoldcompass add knowledgeemits has a* Summaryand no blurb, and the Component Clean Standard follows the scaffold. So the template and the contract disagree, and every knowledge page written from the template inherits the disagreement. It is a one-line fix in the template, and it is not this task's, but it is why the new standard has a blurb where its sibling has aSummaryheading.
6. Decisions signed off
Signed off by the user on 2026-09-27, before any implementation started. These are the answers the architecture documents must now state.
- A journey step reaches the server through one hand-written BFF route per step. Each route parses a generated schema, and each is listed in The component specification. There is no generic forwarding route, so the browser cannot name a subject. This keeps the BFF an allow-list boundary, which is the whole reason it exists.
- The BFF is the browser's only HTTP surface.
ores.httpserves external API clients and the HTTP recipes, and the browser never reaches it.httpBaseUrlanddiscoverHttpBaseUrl()are therefore removed rather than documented, because a declared-but-unused path is a decision nobody made. - Only the server-side workflow resumes. The steps around it hold no server state, so losing them costs a person nothing but re-entry, and the screen states the limit. A journey progress record is therefore 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 tenant lifecycle is IAM's, the subjects are
iam.v1.seed_profiles.*, and the profile orders steps that IAM and the workflow engine run. - The corrections land as their own change, before the story starts. Documentation only, so the first implementation task decides against pages that are true.
ores.webgets its own standard. The Component Clean Standard is written around SQL, a repository, a service and a generated registrar, and the TypeScript packages have none of those, so it cannot govern them. The TypeScript Clean Standard is written as its sibling: the same shape, one item per convention, and every item naming the check that proves it. It is written with this unit rather than after it, because the story writes TypeScript from its first task onward.
7. See also
- Implement the setup journeys in ores.web — the story this audit gates.
- Implement the user journeys in ores.web — the programme story that orders the groups.
- Extract the user journeys that replace the ores.qt screens — the story that owns the journey documents.
- Journey execution — the composition classes and the vocabulary.
- The component specification — where the client-side contract belongs.