Task: Read the reporting tree
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
One read answers the tenant's reporting shape, and a large tenant does not send every account to draw one branch.
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_reporting_treeanswers the tenant's accounts with the manager each reports to, its depth from a root, and how many report to it directly.- An empty root answers the whole tenant, ordered shallowest first; a stated root answers that account's branch alone, with the root at depth zero.
- An account that reaches no root is answered with depth -1 and counted in
unrooted, rather than dropped or drawn as a root. - The read needs
iam::accounts:readand names no tenant: row-level security bounds what it can see. - The browser reaches it over
GET /api/reporting-tree, and the shell reaches the same subject. check_component_drift.py --component iamreports no drift, and the C++ and BFF tests cover it.
4. Plan
- Declare
iam.v1.ops.get_reporting_tree: the root to answer from, or empty for the whole tenant. - Build the shape in the service from the tenant's roster: the children by manager, a breadth-first walk from the accounts that state no manager, and a rebase when one branch is asked for.
- Count what the walk cannot reach, and answer it with depth -1.
- Serve it behind
iam::accounts:read, wire the browser path, and let codegen emit the shell command. - Test the depths and direct reports, the branch read, and an account whose manager has left.
5. Measurements
| What | Value | Command | Commit |
|---|---|---|---|
| IAM core suite after this change | 267 cases, 955 assertions | bash build/evidence/run_case.sh ores.iam.core.tests |
(branch tip) |
| BFF membership cases | 10 | npm test --workspace @ores/bff -- membership |
(branch tip) |
6. Notes
- Depth and the unrooted total are tenant facts, not branch facts. The first version walked from the stated root and answered its subtree, then appended whatever else it could not place. A branch read came back with the whole tenant's strays attached to it. The walk is now always over the tenant, and a branch read rebases the depths and answers the subtree alone.
- The read still loads the tenant's roster. A recursive query would page a branch in the database rather than in the service, which is what a very large tenant wants. The generated account repository offers no such read and the service holds no hand-written SQL, so this version computes the shape from the roster it already reads. Stated as the limit rather than hidden: the host and the wire answer are one call, and the database work is the tenant.
- A manager who has left is stated, not promoted. An account that names a manager the tenant no longer holds is not drawn as a root: it is counted and answered with depth -1, because drawing it as a root would hide the gap.
- The cycle guard from the task before this one makes most rings impossible. The unrooted count remains because a database created before the guard can still hold one, and because the read must be total.
| What | Value | Command | Commit |
|---|---|---|---|
7. Notes
8. 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 |
|---|---|---|
9. PRs
| PR | Title |
|---|---|
| #2994 | [iam] Read the reporting tree in one call |
10. Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
11. Result
Done 2026-10-09.
The tenant's reporting shape is one read:
| Layer | What landed |
|---|---|
| Message | iam.v1.ops.get_reporting_tree: the root to answer from, or empty for the whole tenant, and a node per account with its manager, depth and direct reports. |
| Service | The walk over the tenant's roster, rebased when a branch is asked for, with the accounts that reach no root counted and answered at depth -1. |
| Handler | Served behind iam::accounts:read. |
| Clients | readReportingTree in the wire seam, GET /api/reporting-tree, api.reportingTree(), and the generated shell command. |
| Tests | Three C++ cases and two BFF cases. |
Verification. check_component_drift.py --component iam reports no drift and
clang-format is clean. The IAM core suite passes, 267 cases and 955 assertions.
The BFF membership tests pass, 10 cases. The three TypeScript packages
typecheck.
What the tests caught. The first version answered a branch read with the
branch plus every account in the tenant that reached no root, so a two-person
branch came back with sixty-four rows. The suite shares a tenant, which is what
made the strays visible; the fix is the one described in * Notes, and the
branch test now asserts that what sits above the root is not in the answer.