Task: Read a member's parties with their names
Table of Contents
This page documents a task in the Implement the membership journeys in ores.web story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
A member can read every party their account works in, each with a name, from one subject scoped to the caller, and reach it from the browser.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Implement the membership journeys in ores.web |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-09 |
3. Acceptance
iam.v1.ops.get_my_partiesanswers the caller's own associations, each with the party's name, short code, category and business centre, and the account's stored default.- The read needs no permission and cannot name another account: the request carries no account identifier and the session names it.
- A party IAM's cache cannot name answers with empty strings, so the screen states the gap rather than drawing a half-read row.
- The browser reaches it over
GET /api/me/parties, through the wire-protocol seam, parsed at the boundary on both sides. - The shell reaches it, because codegen emits the command from the message.
compass lint,check_component_drift.py --component iam,check_subject_conformance,check_route_auth_declarationsandcheck_handler_permissionspass.- The C++ tests cover the naming rule, and the BFF tests cover the route.
4. Plan
- Declare the request, the response and the party summary in
ores.iam.account_messages.org. - Regenerate the component, so the C++ protocol, the TypeScript protocol and the shell command all follow from the one declaration.
- Serve it from
account_operations_handler, which already holds the party cache: the associations come from the junction repository and the names from the cache, reloaded once on a miss. - Register the subscription in
registrar.cpp. - Wire the browser path: the wire-protocol seam, the BFF route and the web client method.
- Test the naming rule as a pure projection, and the route end to end.
5. Measurements
| What | Value | Command | Commit |
|---|---|---|---|
| Accounts with an association in the demo tenant | 60 | select count(distinct account_id) from ores_iam_account_parties_tbl |
4a0d83eabd |
| Parties the demo tenant's administrator works in | 5 | select count(distinct party_id) from ores_iam_account_parties_tbl ap join ores_iam_accounts_tbl a on a.id = ap.account_id where a.username = 'tenant_admin' |
4a0d83eabd |
| Parties a demo staff account works in | 1 | the same query for ashley.moore |
4a0d83eabd |
| IAM core suite after this change | 259 cases, 791 assertions | bash build/evidence/run_case.sh ores.iam.core.tests |
dfc7882530 |
| Shell IAM suite after this change | 405 cases, 1055 assertions | bash build/evidence/run_case.sh ores.shell.iam.tests |
dfc7882530 |
| BFF membership cases | 3 | npm test --workspace @ores/bff -- membership |
dfc7882530 |
6. Notes
- The naming already exists, twice. The sign-in reply names parties from
IAM's party cache (
auth_handler.hpp), andupdate_accounttakes a party identifier with no name. This read reuses the cache and states the same summary shape the sign-in reply does, because the member's screen is asking the same question outside the sign-in step. - Two near-identical summaries now exist.
party_summaryinores.iam.login_messages.organdmy_partyhere. They are not merged: the login summary is part of the sign-in contract and carriesidrather thanparty_id, and changing it would move the login wire shape. The duplication is recorded rather than silenced. - The cache is the only name the IAM service may read. The service depends on
refdata.api, not on refdata's repository, so a name can only come from the per-tenant mirror. A miss reloads the partition once, then answers empty strings rather than failing the whole read. - The shell command is generated. Codegen emitted
account_operations get-my-partiesand its recipe from the message declaration, so the second client is served without hand-written work. That is the story's decision that a journey is reached from both clients. - A message's declaration order is load-bearing, and nothing checked it. The
response was first declared above the
my_partyit carries, and codegen emits the structs in the order the model states them, so the generated header referenced a type that did not exist yet. Every documentation gate passed; only the compiler caught it. The fix is the order in the model, and the gap is filed as a capture: the model linter could refuse a message whose field names a type declared later in the same file.
7. Test Scenarios
| Scenario | State | Notes |
|---|---|---|
8. PRs
| PR | Title |
|---|---|
| #2974 | [iam] Read a member's own parties with their names |
9. Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
10. Result
Done 2026-10-09.
A member's own parties are now readable with their names, from one subject, on both clients:
| Layer | What landed |
|---|---|
| Message | iam.v1.ops.get_my_parties, its response and my_party in ores.iam.account_messages.org. |
| Server | account_operations_handler::get_my_parties, served from the session token and registered in registrar.cpp. |
| Browser | readMyParties in wire-protocol/src/membership.ts, GET /api/me/parties, and api.myParties(). |
| Shell | account_operations get-my-parties and its doc/recipes page, emitted by codegen from the declaration. |
| Tests | Two C++ cases for the naming rule, three BFF cases for the route and its refusal. |
The naming rule is a pure helper, acct_project_my_party, so the rule is
tested without a database: a known party is named, and a party the cache cannot
name projects empty strings rather than a half-read row.
What the task found. The response was first declared above the my_party it
carries, and the generators emit a model's types in the order the file states
them, so the generated header referenced a type that did not exist yet. Every
documentation gate passed and the compiler was the only signal. The order is
fixed here and the gap is captured at
A message may name a type declared
later in its model.
Verification. check_component_drift.py --component iam reports no drift;
check_subject_conformance, check_route_auth_declarations,
check_handler_permissions, check_open_reads,
check_test_case_reachability and check_exported_types_are_compiled pass;
clang-format over the changed C++ is clean; the three TypeScript packages
typecheck. The IAM core suite passes (259 cases, 791 assertions), the shell IAM
suite passes (405 cases, 1055 assertions), and the three new BFF cases pass.
The database was recreated for this build, because the recorded schema
fingerprint was stale — a pre-existing condition, not this change.
One failure is pre-existing and outside this change:
check_protocol_twin_coverage reports the synthetic and scheduler
components missing their TypeScript twins. Those files are untracked in this
worktree and absent from origin/main, so the gate fails on the trunk too.