Task: Say which roles may be asked for
Table of Contents
This page documents a task in the Land the role request journey story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
A tenant administrator can say which roles a member may ask for. Today every role in the tenant is on offer: the screens filter the list and the ask operation refuses what it should, so the rule lives in two places and the tenant can state it in neither. Add is_requestable to ores.iam.role, in the shape of is_registration_default, and let the ask operation and the screens read it.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Land the role request journey |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-07 |
3. Acceptance
- ores.iam.role carries is_requestable, in the shape and with the defaults of is_registration_default.
- The field is projected across the whole stack: the model, the SQL, the C++ domain and repositories, the protocol twins, the BFF and the web screens.
- A role the tenant has not made requestable cannot be asked for: the ask operation refuses it and the screen does not offer it.
- The role screens let a tenant administrator set the flag.
4. Plan
The flag goes on the role row, in the shape of is_registration_default,
because row-level security already decides who may write a role row and the
tenant administrator is that person. Nothing new is needed to guard it.
It defaults true, so a role a tenant administrator creates is offered to the members without further ceremony, and the rule is stated by exception: the seed clears it on the service roles and on the two that grant everything, which are the roles the screens used to filter out by name. Defaulting false was rejected: it would silently withdraw every role in every existing tenant until an administrator opted each one back in, which is a different change from the one the journey asks for.
ores_iam_roles_upsert_fn takes the flag, so the seed names it in one place.
create or replace does not replace a function whose argument list changed,
so an existing database would keep the old three-argument version; the drop
script covers it, because it drops by name and so removes every overload.
The rule leaves the screens. The picker filters on the flag rather than on a
name ending in Service and a permission code of *, and the ask operation
refuses a role the tenant has not offered with its own outcome code, so the
refusal is a value rather than a sentence to match.
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 |
|---|---|---|---|
| Roles the seed makes unaskable, the call sites it states that at | 22 of 30 | grep -c ", false);" projects/ores.sql/populate/iam/iam_roles_populate.sql (22), against 30 ores_iam_roles_upsert_fn calls |
36f1396b9d |
| ACME roles the flag survives provisioning on | 30, 22 false and 8 true | compass sql -- -f .../evidence/requestable_roles.sql, kept as evidence/requestable_roles.txt |
36f1396b9d |
| Roles the BFF offers a member over HTTP | 8 of 30 | curl ... /api/roles, kept as evidence/requestable_screen.txt |
36f1396b9d |
| Files the entity projection touches | 36 changed, 282 insertions | git show --stat 36f1396b9d |
36f1396b9d |
| Suites in the full run | 94, of which 93 passed | compass test run --preset linux-clang-debug-make |
36f1396b9d |
| The one failure | ores.database.tests, a pre-existing flake |
Re-run alone passes; the same binary passed 94 of 94 earlier the same day. Captured in the inbox. | 36f1396b9d |
6. Notes
create or replace function does not replace a function whose argument list
changed: it adds an overload. ores_iam_roles_upsert_fn gained a fourth
parameter, so an existing database would have kept the three-argument version
alongside the new one and resolved a three-argument call to the old one, which
writes no flag. Nothing in the tree can hit that in practice, because the
seed runs on a database recreated from scratch, and
drop/seed/upsert_functions_drop.sql drops the function by name and so removes
every overload. The drop needed no change for that reason.
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 |
|---|---|---|
| Verify a member may ask only for the roles the tenant offers | BACKLOG | The walk was run as evidence; the scenario is written for a tester to repeat it. |
8. PRs
| PR | Title |
|---|---|
| #2754 | [iam] Let a tenant say which roles a member may ask for |
9. Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
10. Result
Built and measured. The role carries is_requestable, the seed states it at
twenty-two of the thirty roles it creates, and the tenant provisioner carries
it to every tenant it makes. The rule left the screens: the picker reads the
flag and the ask operation refuses a role the tenant has not offered with
outcome denied and code not_requestable, so the refusal is a value rather
than a sentence to match.
Proved on a database recreated from scratch and provisioned with ACME, in
four walks kept beside this page. The member ashley.moore is refused
IamService and allowed Operations, and the refusal leaves no request
behind: evidence/requestable_ask.txt. The flag survives provisioning across
all thirty of the tenant's roles, the twenty-two the seed clears it on and the
eight it does not: evidence/requestable_roles.txt. The BFF passes the flag
through to the browser and offers a member the same eight roles:
evidence/requestable_screen.txt.
The fourth walk is the one a seed cannot show. The tenant administrator took
Operations away through the browser's own route, and the member was then
refused the role that had been accepted minutes earlier; it was put back, and
the tenant was as it was: evidence/requestable_tenant_control.txt. The answer
is the tenant's rather than the platform's.
C++ and the web both hold the behaviour: a repository round trip of a role written not askable, a BFF case that the flag reaches the wire write, and a web case that a role marked not askable is not offered even when its name and its permissions would have passed the old filter.
The suite ran 94 suites and 93 passed. The one failure is
ores.database.tests, in a component this change does not touch, and it is a
flake in the test's own design: the listener loss-window test kills the
listener's backend and then asserts the listener has not yet reconnected,
which nothing enforces. It passes on an immediate re-run and the same binary
passed all 94 earlier the same day. It is captured in the inbox rather than
worked around here.