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.

Emacs 29.3 (Org mode 9.6.15)