Task: Put the badge on the tenant status row
Table of Contents
This page documents a task in the Implement the profile journeys in ores.web story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
The badge stops being mapped to the value and becomes a property of the value. A tenant status names the badge that paints it, its own name is the word inside the pill, and the junction that mapped one to the other is not what the screen reads.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Implement the profile journeys in ores.web |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-01 |
3. Acceptance
- A tenant status row names the badge that paints it, and no screen or route reads a mapping to find out which.
- The word inside the pill is the status row's own name, and the colours, the severity and nothing else come from the badge.
- A status without a badge, and a status whose badge has left the catalogue, keep their words and lose their colours.
- The read the roster uses answers the statuses and their badges in one call.
- The mapping the screen used before is gone from the web read path.
- Every new behaviour is proved by a test, and the pill was read in a browser against the running deployment, compared to the status rows themselves.
4. Plan
- The column, on the value. The badge is a reference the value holds, so the value's own model gains it and the codegen produces the layers. The model is the source: no generated file is edited by hand.
- The words stay the value's. The badge supplies colours and severity. Its own name is its entry in the catalogue and never the label of somebody else's state, which is what the junction made possible and what made a suspended tenant read Frozen.
- The read follows the model. The route reads the statuses and the catalogue and joins them on the reference the row carries, so the join is a lookup and not a mapping.
- The junction stays for the domains that have not moved. Deleting it now would break twenty domains at once. It is read by nothing on this path and goes when the last domain moves.
5. Measurements
Every number this task claims about a tree, with the command that produced it
and the commit it was measured at. A number without both is not a measurement,
because a clean-up record describes a tree and trunk moves under a branch: a
consumer count, a gate's component count and a suite's size all change with no
code changing, and the reader of a bare number cannot tell which tree it came
from. Where the raw output matters, put it in an evidence/ file beside the
task and name that file in the Command column.
| What | Value | Command | Commit |
|---|---|---|---|
| What | Value | Command | Commit |
| The word a suspended status shows, against its badge | Suspended, painted by the badge named Frozen |
node evidence/tenant_roster_check.js |
the branch tip |
| The badge the active status names | active, #22c55e |
curl http://127.0.0.1:21402/api/tenant-statuses |
the branch tip |
| BFF suite | 20 files, 97 tests | npm run test in projects/ores.web/packages/bff |
the branch tip |
| Web suite | 17 files, 186 tests | npm run test in projects/ores.web/packages/web |
the branch tip |
| Wire contract suite | 7 files, 51 tests | npm run test in projects/ores.web/packages/wire-protocol |
the branch tip |
| Live roster harness | 23 promises | node evidence/tenant_roster_check.js |
the branch tip |
6. Notes
7. Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario)
that verify this task. Link
new ones here as they're created; the scenario doc itself links back
via its "Verifies task" field.
| Scenario | State | Notes |
|---|---|---|
8. PRs
| PR | Title |
|---|---|
| #2483 | [iam] Put the badge on the tenant status row |
9. Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
10. Result
The badge is a property of the value, and the words are the value's own.
- The row names its badge.
ores.iam.tenant_statusgainedbadge_code, a soft reference to the badge catalogue, and the four statuses nametenant_bootstrapping,active,frozenandarchived. The model is the source: the SQL, the C++ and the browser types were regenerated from it, so there is no hand-edited generated file to drift. - The words stay the deployment's. The pill shows the status row's own name: a suspended tenant reads Suspended, painted in the badge's colour, with the status row's own description behind it. That is the whole point of moving the reference: the junction let a generic catalogue label replace the word the deployment chose, which is what made a suspended tenant read Frozen.
- The read is a lookup, not a mapping.
GET /api/tenant-statusesanswers the statuses with the badge each one names. Nothing on that path reads a mapping, and a status that names no badge, or names one the catalogue has lost, keeps its words and loses its colours.
Proved live: the harness reads the statuses, then reads the pill the browser
drew and compares them. It asserts the split directly — a status reads
Suspended while the badge painting it is named Frozen.
What this task deliberately does not do.
- The junction stays.
badge_mappingstill holds 154 rows across some twenty code domains, and its admin screens and recipes still work. Deleting it now would take those domains' colours with it. It is read by nothing on this path, and it goes when the last domain carries its own reference. - The other domains have not moved.
party_status,book_status,calendar_typeand the rest still paint through the junction. The shape is now proved on one domain; the sweep is its own work. - Tenant status is not yet a state machine. It is a lookup that names its
badge, which is what this screen needed. Whether the lifecycle should be an
FSM with its states carrying the badge, as
trade_statusis, is a modelling question this task leaves where it found it. - A status is still English server data. The label is the status row's name, so a translated interface shows the deployment's English. Same gap as before, in a better place: the words are now data the deployment owns and could translate, rather than a catalogue's label.