One person's timeline prototype
Table of Contents
| Item | Where |
|---|---|
| Prototype | the prototype |
| Gemini review | none |
| Group | Directory prototypes |
1. Summary
There is no history screen for a person. The person screen answers what is true now, one tab at a time: Details, Contact, Roles, Sign-ins. Nothing answers how it got that way, and nothing shows the parts together.
This prototype puts one person's whole story on one screen as a dated stream, newest first, in the shape a pull request's conversation has. It asks whether that is the right shape, and it asks the second half of the question at the same time: the owner wants one generalised screen for every entity that makes up a journey, and a role request is the second case.
It is one file of mock HTML, CSS and JavaScript. It calls no service, needs no
build and no session. Four states travel as query parameters
(?state=trail|revert|service|case), with ?order and ?entity for the
reading.
2. Detail
2.1. The question
A person is one row of account, and its story is written by five tables: the
account's own versions, its contact information, its party links, its role
grants and its sign-ins. Each is versioned, or is an event log, and no screen
reads them together. An administrator asking "why is this person like this"
opens four tabs and keeps the order in their head.
The prototype asks whether the order is the thing worth drawing, and whether one stream can carry a person's field changes, a role grant, a party link and a sign-in without any of them reading as something they are not.
2.2. What it argues
| The journey today | This prototype | Why |
|---|---|---|
| Four tabs, one per record | One stream, newest first, every record in it | What happened next is the question, and only one view answers it |
| A tab shows the current value | The change is drawn as a field difference, from one value to the next | The current value hides that anything happened |
| Every entry has a revert | A revert is offered only where a write exists to serve it | A sign-in is a fact, a grant is closed rather than reverted, and a decision is undone by a further decision |
| An activity's steps are spread across screens | The activity is one entry, and its own stream is the case file | A person's stream and a request's stream are the same component over a different subject |
2.3. Order
The stream is newest first. The record screens already say "Every version of
this record, newest first", and the request story was accepted on 2026-10-08
with one merged stream, newest first. The person screen shows what is true now;
the stream is read from now backwards, so the current state and the entry that
produced it sit together at the top. Oldest first is kept as ?order=old and
reads as a case file, but it puts what is true now at the end of a long scroll.
2.4. The entities, and where each entry would come from
| Entity badge | Table | Entry | Read today |
|---|---|---|---|
account |
ores_iam_accounts_tbl |
A field of the person: name, mail address, job title, state, reporting line, default party | Versioned, and IAM keeps the versions. The web serves no history for it: /api/history?entityType=ores.iam.account answers 404, because the BFF's HISTORY_TYPES holds the refdata records and the tenant, and no IAM entity besides |
contact |
ores_iam_account_contact_informations_tbl |
A field of the contact record: street, city, post code, country, phone, mail | Versioned, and IAM has the history provider. Not served to the web |
parties |
ores_iam_account_parties_tbl |
A party link opened or closed | A versioned row, but the protocol lists the junction as "no versions, no events", so a closed link has no read |
roles |
ores_iam_account_roles_tbl |
A grant given or taken away, with assigned_by, assigned_at and its reason |
The grants held now are read, with who gave each and when. A grant closed by valid_to is read by nothing, so the duration is not on any screen |
sign-ins |
ores_iam_auth_events_tbl |
A sign-in, a failed sign-in, a sign-out, a token refresh | /api/auth-events serves them to Audit, filtered by period and event type only, never by account. 90-day retention, and no row-level security |
notification |
ores_inbox_notifications_tbl |
A notice raised on a request | Read by the inbox. Its audience is a permission code, not a list of people |
The prototype's state case adds the six request tables the accepted request
story reads: approval_request, role_grant_request,
role_grant_request_role, approval_decision, account_role and
notification. Every entity is named on the screen, in the "Where each entry
comes from" table, rather than only in this note.
2.5. Which entity an entry belongs to
Each entry carries a mono badge naming its entity, and its row and version where
it has one (account 7c1f0e5a v3). A version number alone is ambiguous here: the
account, its contact record and its party links each count from their own
version 1, so the badge names the entity first. ?entity=<name> narrows the
stream to one entity, which is the "one tab per entity" reading kept as a state
rather than as the only view.
2.6. What generalises, and what does not
Generalises unchanged: the rail, the day dividers, the entity badge, the quiet rendering for an entry with no diff, the green and red widget for a field that changed, the rule that a revert appears only where a write exists, and the provenance table.
Does not generalise, and is therefore outside the stream:
- The header. A person has a name, a face, a party and a state. A request has a kind, a state and an asker. The stream body is identical; the header is the subject's own.
- Elapsed time. A request has a beginning and an end, so a case file can
answer "how long did it take". A person's stream has neither, so it can carry
the elapsed time only of an activity inside it. That is what the
2026-10-05entry does: it states the ask, and the grant beside it states that the answer took 22h 41m 2s. - What a revert means. On a person, a field change is reverted by writing its
older value back as a new version. On a request, a step is a decision, and
undoing it is a further decision: the seeded decision type
reversereads "Undo an approval; the owning component undoes what it applied", and it requires a comment. The widget is the same; the button, its permission and its operation are not. - Entitlement. The request story found that the person who raised a request may not read the decisions taken on it. A person's own timeline has the opposite shape: the sign-ins and the grants are the administrator's to read in full. So the reader axis belongs to the subject, not to the stream.
2.7. What the owner asks for that has no data behind it today
- A sign-in for one person.
/api/auth-eventstakes a period and an event type, and no account. The prototype draws the sign-ins as though a per-account read existed; it does not. - A party link's history.
ores_iam_account_parties_tblis versioned, and hasvalid_fromandvalid_to, but the protocol declares the junction to have no versions and no events. The prototype draws the link and says on the entry that neither its closure nor its history is reachable. - A revert of a person's own fields. The account is read-only in the generated
surface and written by the hand-written
iam.v1.accounts.update. No screen offers a revert of an account, so the per-field revert is design intent. - "Restore as of this point". No operation writes one person's account, its contact details and its party links back to one instant together. The button is drawn and says so.
- The change reasons a person's edits would carry. The seeded reasons have no
profilecategory. An edit to a person's name or job title must borrowsystem.update,system.new_recordor acommon.*code, which is why the prototype's entries carry those. - Two strings the accepted request story uses that the data does not have. The
notification kinds
notification.inbox.approval_heldandnotification.inbox.approval_approvedare not seeded; the seeded keys areapproval_waiting,approval_decided,approval_expiringandapproval_expired. The change reasonaccess.approved_requestis not seeded either, so the prototype writes the grant withaccess.access_review. - A hold step on a role request. The decision types
holdandresumeexist, but the only seeded approval kind,iam.role_grant, setsallows_hold = false. The case file therefore has no hold step, and says so.
3. See also
- Directory prototypes — the group this prototype answers to.
- See the story of a request — the second case, accepted on 2026-10-08, whose stream this prototype generalises.
- See who has access — the journey that owns the roster and the person's page.