Task: Issue a session inside a tenant
Table of Contents
This page documents a task in the Act inside a tenant story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
(Describe what user-visible-or-internal change this task produces.)
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Act inside a tenant |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-03 |
3. Acceptance
- The operation is declared in an operation model and its C++, TypeScript and shell twins are generated.
- A caller without iam::tenants:impersonate, a caller outside the system tenant, and the system tenant as a target are each refused, and a test proves each refusal.
- The issued token names the target tenant and the acting administrator, and the entry is in the audit trail.
4. Plan
(Implementation strategy. Written when work starts; key decisions
are distilled into the parent story's * Decisions at close, but the
plan itself stays — it is the historical record of what we did.)
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 |
|---|---|---|---|
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 |
|---|---|
| #2533 | [iam] Enter and leave a tenant as a system administrator |
9. Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| L1 | A person's token carries no permissions, so a token check proves nothing | auth_handler.hpp |
Accept in part | The entry checks iam::tenants:impersonate against the database, and the tenant token carries an explicit read-only list. The wider gap is the capture "A token with no permissions passes every permission check", updated with what was found. |
| L2 | No stub proof of the service's refusals | tenant_session_service.cpp |
Decline | Each refusal has its own test that asserts its message; the classifier refused the stub edit on the previous story. |
| R1.1 | The entry is audited before the token is signed | tenant_session_service.cpp |
Accept | The token is signed first and the entry recorded before it is handed over. |
| R1.2 | Leaving does not end the token | tenant_session_service.cpp |
Accept in part | Tokens are not revoked anywhere in the system; the client discards it and it lasts one access lifetime. The journey says leaving is not a revocation. |
| R1.3 | Leave trusts the claim without checking it names the system tenant | tenant_session_service.cpp |
Accept | Leave now requires the claim to name the system tenant; a test covers the other case. |
| R1.4 | The handler's permission read depends on the check order | tenant_session_handler.hpp |
Accept | A comment says why. |
| R1.5 | The read-only filter relies on the :read suffix | tenant_session_service.cpp |
Decline | The catalogue's only read verb is :read: 169 :read codes, and every other verb writes, deletes or acts. |
| R1.7 | Refresh fails these tokens with a generic message | auth_handler.hpp |
Decline | The client never refreshes a tenant token; it returns to its own token first. |
10. Result
iam.v1.ops.enter_tenant and iam.v1.ops.leave_tenant are generated from the
operation model ores.iam.tenant_session_messages and served by
tenant_session_handler through tenant_session_service. Entering refuses a
caller outside the system tenant, a caller already inside, a caller without
iam::tenants:impersonate (read from the database), an unreadable id, the
system tenant, an unknown tenant and a tenant with no read permission to
carry. The token acts as the tenant's system party with every party visible,
carries only the read permissions the administrator holds, and names the
tenant it acts from in the new acting_from_tenant_id claim. Refresh refuses
such a token. Both events go to the tenant's own audit trail as
tenant_entered and tenant_left. No shell command is generated, because a
command would print a bearer token.
Tests: four service tests against the database (entry, read-only filter, every refusal, exit) and a claim round trip in the security suite. The full build is clean. The full ctest passed 86 of 89; the 3 failures were a database older than main, and they pass on a recreated one. Live, entering Acme, reading, leaving and the refusals passed 15 of 15, and the trail holds the entry and the exit.