Bite 30E — Session and Tenant Selection Refactor - RodrigoMattosoSilveira/EnterpriseRemoteSystems GitHub Wiki
Issue: Bite #30E — Session and Tenant Selection Refactor
Branch: 712-bite-30e-session-and-tenant-selection-refactor
This checklist is intentionally fixture-driven and deterministic.
For the remainder of Bite 30E, use these terms consistently:
Person ID
Authentication Account ID
Login
Tenant ID
Actor ID
Actor Key
Membership ID
Delegated Role
Relationship terminology:
Authentication Account
-> Tenant Actor
-> same-tenant Person–Tenant Membership
If an API returns actorRecordId, treat it as:
actorRecordId (Actor ID)
It is the same persisted authz_actors.id.
Before beginning manual testing:
- All migrations apply successfully.
-
make local-checkpasses. - Full Vitest suite passes.
- Full Playwright suite passes.
Use the Bite 30E seed script created for this test cycle.
From the project root:
cd /Users/rodrigosilveira/projects/EnterpriseRemoteSystemsStop the backend before running the seed script because it writes directly to SQLite.
Then run:
python3 scripts/seed-bite30e-manual-test-identities.pyDefault credentials:
Identity A
Login: [email protected]
Identity B
Login: [email protected]
Identity C
Login: [email protected]
Identity D
Login: [email protected]
Password for all four:
Manual-30C-Password!
Default tenant names:
Tenant A: Byte 28A Manual Test
Tenant B: default
If the default fixture batch has already been used and you need a fresh set:
python3 scripts/seed-bite30e-manual-test-identities.py \
--batch retry1In that case, use the logins and IDs printed by that run instead of the default values shown above.
The seed script prints all values needed by the tests below.
Copy the output here before starting.
Tenant A Name:
Byte 28A Manual Test
Tenant A ID: b16647b4-82a3-4d4e-99d0-c15ede05840b
________________________________________
In the current local test data this has commonly been:
b16647b4-82a3-4d4e-99d0-c15ede05840bUse the value printed by the seed script as authoritative.
Tenant B Name: Default Tenant
default
Tenant B ID: default
________________________________________
Expected shape:
one ACTIVE Authentication Account
-> two ACTIVE tenant Actors
-> two ACTIVE same-tenant Memberships
-> no delegated Roles
Record:
Login: [email protected]
Password: Manual-30C-Password!
Person ID:
________________________________________
Authentication Account ID:
________________________________________
Tenant A ID:
________________________________________
Tenant A Actor ID:
________________________________________
Tenant A Actor Key:
________________________________________
Tenant A Membership ID:
________________________________________
Tenant B ID:
________________________________________
Tenant B Actor ID:
________________________________________
Tenant B Actor Key:
________________________________________
Tenant B Membership ID:
________________________________________
Testing rule: Identity A is stable. Do not deactivate or otherwise mutate its Actors or Memberships during Bite 30E manual testing.
Expected shape:
one ACTIVE Authentication Account
-> exactly one ACTIVE tenant Actor
-> exactly one ACTIVE same-tenant Membership
-> no delegated Roles
-> no Collaborator Journey
Record:
Login:
[email protected]
Password:
Manual-30C-Password!
Person ID:
________________________________________
Authentication Account ID:
________________________________________
Tenant ID:
________________________________________
Actor ID:
________________________________________
Actor Key:
________________________________________
Membership ID:
________________________________________
Testing rule: Identity B is stable. Do not add delegated Roles or create a Collaborator Journey for it.
Expected shape:
one ACTIVE Authentication Account
-> exactly one ACTIVE tenant Actor
-> exactly one ACTIVE same-tenant Membership
-> exactly one usable tenant
Record:
Login:
[email protected]
Password:
Manual-30C-Password!
Person ID:
________________________________________
Authentication Account ID:
________________________________________
Tenant ID:
________________________________________
Actor ID:
________________________________________
Actor Key:
________________________________________
Membership ID:
________________________________________
Testing rule: Identity C is stable. It exists only to verify the single-tenant session/selection experience.
Expected shape:
one ACTIVE Authentication Account
-> two ACTIVE tenant Actors
-> two ACTIVE same-tenant Memberships
-> no delegated Roles
-> safe for Actor deactivate/reactivate tests
Record:
Login:
[email protected]
Password:
Manual-30C-Password!
Person ID:
________________________________________
Authentication Account ID:
________________________________________
Tenant A ID:
________________________________________
Tenant A Actor ID:
________________________________________
Tenant A Actor Key:
________________________________________
Tenant A Membership ID:
________________________________________
Tenant B ID:
________________________________________
Tenant B Actor ID:
________________________________________
Tenant B Actor Key:
________________________________________
Tenant B Membership ID:
________________________________________
Testing rule: Identity D is the only identity intentionally mutated during the destructive session/Actor tests.
Use these exact snippets throughout the checklist.
Open DevTools → Console and run:
fetch("/api/v1/auth/session", {
credentials: "include",
cache: "no-store"
})
.then(async (r) => ({
status: r.status,
body: await r.json()
}))
.then(console.log);Expected for a valid ACTIVE Authentication Account:
status: 200
Replace <TENANT-ID> with the exact ID from the Fixture Manifest:
fetch("/api/v1/authz/current-actor", {
credentials: "include",
cache: "no-store",
headers: {
"X-Tenant-ID": "<TENANT-ID>"
}
})
.then(async (r) => ({
status: r.status,
body: await r.json()
}))
.then(console.log);Record/compare:
tenantId
actorRecordId (Actor ID)
actorKey
scope
roleCodes
Do Tests 1–4 consecutively. Log in only once.
Login:
[email protected]
Password:
Manual-30C-Password!
Use the exact Identity A IDs from the Fixture Manifest.
- Identity A Authentication Account is ACTIVE.
- Tenant A Actor is ACTIVE.
- Tenant A Membership is ACTIVE.
- Tenant B Actor is ACTIVE.
- Tenant B Membership is ACTIVE.
- Browser is signed out.
-
Open:
http://localhost:5173/login -
Sign in using Identity A.
-
Do not sign out.
-
Open DevTools → Console.
-
Run Script A — Show Current Account Session.
- Login succeeds.
- ERS does not ask which Actor should authenticate.
- ERS does not ask for a second login for Tenant A or Tenant B.
- Tenant selection is available after authentication.
GET /api/v1/auth/session
-> HTTP 200
✔︎ PASS only if the Authentication Account is authenticated independently of which tenant Actor will later be selected.
FAIL if:
- ERS requires Actor-specific credentials;
- ERS requires separate credentials for each tenant;
- Account authentication fails even though the Authentication Account is ACTIVE.
Identity A remains logged in from Test 1.
Use:
Tenant A Name:
Byte 28A Manual Test
Tenant A ID:
<Identity A Tenant A ID>
Expected Actor ID:
<Identity A Tenant A Actor ID>
Expected Actor Key:
<Identity A Tenant A Actor Key>
-
In the header Tenant selector, select:
Byte 28A Manual Test -
Open DevTools → Console.
-
Run Script B with:
X-Tenant-ID = <Identity A Tenant A ID> -
Compare the returned values with the Fixture Manifest.
- Header shows Tenant A selected.
- No Login screen appears.
- [ x No password prompt appears.
status:
200
tenantId:
<Identity A Tenant A ID>
actorRecordId (Actor ID):
<Identity A Tenant A Actor ID>
actorKey:
<Identity A Tenant A Actor Key>
scope:
TENANT
✔︎ PASS only if the returned Actor ID and Actor Key exactly match Identity A's Tenant A Actor.
FAIL if:
- Tenant A resolves Identity A's Tenant B Actor;
- Tenant A resolves any unrelated Actor;
- another login is required.
Identity A is logged in and Tenant A is currently selected.
Tenant B Name:
default
Tenant B ID:
<Identity A Tenant B ID>
Expected Actor ID:
<Identity A Tenant B Actor ID>
Expected Actor Key:
<Identity A Tenant B Actor Key>
-
In the header Tenant selector, change:
Byte 28A Manual Testto:
default -
Do not sign out.
-
Do not enter a password.
-
Run Script B with:
X-Tenant-ID = <Identity A Tenant B ID> -
Compare the result to the Fixture Manifest.
- Header shows Tenant B selected.
- No Login screen appears.
- No password prompt appears.
status:
200
tenantId:
<Identity A Tenant B ID>
actorRecordId (Actor ID):
<Identity A Tenant B Actor ID>
actorKey:
<Identity A Tenant B Actor Key>
scope:
TENANT
The Actor must be different from Tenant A:
Tenant A Actor ID != Tenant B Actor ID
✔︎ PASS only if the Account remains logged in and Tenant B resolves exactly Identity A's Tenant B Actor.
FAIL if:
- another login is required;
- Tenant A Actor remains effective;
- an unrelated Actor becomes effective;
- Authentication Account changes.
Identity A is logged in and Tenant B is selected.
-
While Tenant B is selected, open:
People -
Note one recognizable record or count belonging to Tenant B.
-
Change the header Tenant selector to:
Byte 28A Manual Test -
Remain on/open:
People -
Watch the UI while the tenant changes.
-
Run Script B with:
X-Tenant-ID = <Identity A Tenant A ID>
- Tenant-B People data is not presented as Tenant-A data.
- Tenant-B records do not remain actionable after the switch.
- Tenant-A data loads for Tenant A.
- No Login screen appears.
tenantId:
<Identity A Tenant A ID>
actorRecordId (Actor ID):
<Identity A Tenant A Actor ID>
actorKey:
<Identity A Tenant A Actor Key>
Expected sequence:
Tenant A -> Actor A
Tenant B -> Actor B
Tenant A -> Actor A
FAIL if:
- Tenant-B data remains actionable under Tenant A;
- Actor B remains effective after selecting Tenant A;
- the browser requires another login.
Login:
[email protected]
Password:
Manual-30C-Password!
Expected Tenant ID:
<Identity B Tenant ID>
Expected Actor ID:
<Identity B Actor ID>
Expected Actor Key:
<Identity B Actor Key>
Delegated Roles:
NONE
Collaborator Journey:
NONE
- Identity A has been signed out.
- Identity B Authentication Account is ACTIVE.
- Identity B Actor is ACTIVE.
- Identity B Membership is ACTIVE.
- Identity B has no delegated Roles.
-
Open:
http://localhost:5173/login -
Login as Identity B.
-
Select its available tenant if the selector requires a choice.
-
Open the normal Person self-service landing area.
-
Run Script B using Identity B's Tenant ID.
- Login succeeds.
- Identity B can select/use its tenant.
- Person self-service is available.
- Tenant Administrator workspace is not available.
- Earnings/Expenses operator workspaces are not available merely because the user authenticated.
status:
200
tenantId:
<Identity B Tenant ID>
actorRecordId (Actor ID):
<Identity B Actor ID>
actorKey:
<Identity B Actor Key>
[x] PASS only if tenant selection works with zero delegated Roles.
FAIL if:
- ERS requires
PERSONor another delegated Role merely to select the tenant; - operator/admin authority appears without a delegated Role;
- the Account cannot authenticate because it has no delegated Role.
Login:
[email protected]
Password:
Manual-30C-Password!
Expected Tenant ID:
<Identity C Tenant ID>
Expected Actor ID:
<Identity C Actor ID>
Expected Actor Key:
<Identity C Actor Key>
- Identity B has been signed out.
- Identity C Authentication Account is ACTIVE.
- Identity C has exactly one ACTIVE tenant Actor.
- Identity C has exactly one ACTIVE same-tenant Membership.
-
Login as Identity C.
-
Inspect the header Tenant selector.
-
Confirm there is exactly one usable tenant.
-
Open People or another permitted landing page.
-
Refresh the browser.
-
Run Script B using Identity C's Tenant ID.
- Exactly one usable tenant is available.
- ERS does not present nonexistent Tenant A/Tenant B choices.
- Browser refresh does not lose the valid tenant context.
- No multi-tenant workflow is forced onto the user unnecessarily.
status:
200
tenantId:
<Identity C Tenant ID>
actorRecordId (Actor ID):
<Identity C Actor ID>
actorKey:
<Identity C Actor Key>
FAIL if:
- unrelated tenants appear;
- refresh resolves a different Actor;
- the user is forced to select among tenant contexts that do not exist for the Account.
Do Tests 7–11 consecutively.
Identity D is intentionally disposable.
Login:
[email protected]
Password:
Manual-30C-Password!
Tenant A Actor ID:
<Identity D Tenant A Actor ID>
Tenant B Actor ID:
<Identity D Tenant B Actor ID>
Verify in Authorization → Actors:
Identity D Tenant A Actor = ACTIVE
Identity D Tenant B Actor = ACTIVE
-
Sign out Identity C.
-
Login as Identity D.
-
Select Tenant A.
-
Run Script B for Tenant A.
-
Select Tenant B.
-
Run Script B for Tenant B.
Tenant A:
actorRecordId (Actor ID):
<Identity D Tenant A Actor ID>
Tenant B:
actorRecordId (Actor ID):
<Identity D Tenant B Actor ID>
And:
Tenant A Actor ID != Tenant B Actor ID
- Both tenant contexts work before destructive testing begins.
Identity D is still authenticated.
-
Open a second browser context/window as Application Administrator.
-
Navigate to:
Authorization -
In Actors, search for Identity D.
-
Locate the Actor whose Actor ID is exactly:
<Identity D Tenant A Actor ID> -
Verify its Actor Key is:
<Identity D Tenant A Actor Key> -
Click:
Deactivate -
Do not deactivate Identity D's Tenant B Actor.
-
Return to Identity D's browser session.
- Tenant A Actor becomes inactive.
- Tenant B Actor remains ACTIVE.
- Identity D Authentication Account remains ACTIVE.
Identity D Tenant A Actor is inactive.
Identity D's browser session has not been deliberately signed out.
Run Script B using:
<Identity D Tenant A ID>
HTTP 403
error.code:
tenant_actor_unavailable
Run Script A.
HTTP 200
[X] PASS only if both statements are true:
Tenant A Actor unavailable -> 403 tenant_actor_unavailable
Authentication Account session -> still valid -> 200
FAIL if:
- Actor deactivation produces Account-level
401; - the Authentication Account session is destroyed;
- ERS silently substitutes Tenant B Actor for Tenant A.
Identity D Tenant A Actor is inactive.
Identity D Tenant B Actor remains ACTIVE.
-
Select:
Tenant B / default -
Open a permitted Tenant-B page.
-
Run Script B with:
<Identity D Tenant B ID>
- Tenant B remains available.
- No re-login is required.
- Tenant-B page functions normally.
status:
200
actorRecordId (Actor ID):
<Identity D Tenant B Actor ID>
actorKey:
<Identity D Tenant B Actor Key>
FAIL if deactivating Tenant A Actor also destroys Tenant B access.
-
As Application Administrator, return to:
Authorization → Actors -
Locate:
<Identity D Tenant A Actor ID> -
Reactivate that Actor.
-
Return to Identity D's browser.
-
Refresh/re-evaluate tenant selection.
-
Select Tenant A.
-
Run Script B for Identity D Tenant A.
status:
200
actorRecordId (Actor ID):
<Identity D Tenant A Actor ID>
actorKey:
<Identity D Tenant A Actor Key>
- The same original Actor ID is restored.
- The same original Actor Key is restored.
- Authentication Account ID remains unchanged.
- Membership ID remains unchanged.
- No duplicate Actor is created.
- No duplicate Authentication Account is created.
This is destructive. Perform it only after Test 11 succeeds.
Identity D is back to its original state after Test 11.
As Tenant Administrator:
-
Deactivate:
<Identity D Tenant A Actor ID> -
Deactivate:
<Identity D Tenant B Actor ID> -
Verify the Authentication Account itself remains ACTIVE.
-
Sign Identity D out.
-
Open:
http://localhost:5173/login
[x] 6. Login again using:
[email protected]
Manual-30C-Password!
[x] 7. Run Script A.
- Authentication succeeds.
- ERS presents a No Tenant Access / limited-access state or equivalent.
- No tenant application workspace is available.
- Sign Out remains available.
GET /api/v1/auth/session
-> HTTP 200
[x] PASS only if ERS distinguishes:
ACTIVE Authentication Account
from:
no currently usable tenant Actor
FAIL if:
- ERS reports invalid login/password solely because both tenant Actors are inactive;
- Account authentication returns
401; - ERS silently invents or borrows a tenant Actor.
Do not leave the fixture damaged.
As Application Administrator:
-
Reactivate Identity D Tenant A Actor.
-
Reactivate Identity D Tenant B Actor.
-
Verify both are ACTIVE.
-
Login as Identity D again if needed.
-
Run Script B for Tenant A.
-
Run Script B for Tenant B.
Tenant A:
Actor ID:
<original Identity D Tenant A Actor ID>
Tenant B:
Actor ID:
<original Identity D Tenant B Actor ID>
- Original Actors were restored.
- No replacement Actors were created.
- Original Membership IDs remain unchanged.
Identity D has been restored.
-
Login as Identity A.
-
Select Tenant B.
-
Open:
People -
Run Script B and confirm Identity A Tenant B Actor.
-
Sign out.
-
Login as Identity C.
-
Open:
People -
Run Script B using Identity C's Tenant ID.
- Identity A Tenant-B data does not remain displayed as Identity C data.
- Identity A Actor does not survive the new login.
- Tenant choices belong only to Identity C.
- Current Actor is exactly Identity C's Actor.
FAIL if a new Authentication Account inherits the prior Account's tenant data, Actor, or tenant selection in a way that grants incorrect access.
Use the normal local Application Administrator Authentication Account.
Sign out the ordinary user.
-
Login as Application Administrator.
-
Open DevTools → Console.
-
Run Script B using Tenant A ID.
-
Record:
actorRecordId (Actor ID) actorKey scope roleCodes -
Run Script B using Tenant B ID.
-
Compare the two results.
Both requests resolve the same Application Administrator Actor.
Expected authority:
scope:
APPLICATION
roleCodes:
includes APPLICATION_ADMIN
Verify:
- Application Administrator remains application-scoped.
- Tenant selection does not manufacture a tenant Actor for the Application Administrator.
- Tenant selection does not convert
APPLICATION_ADMINintoTENANT_ADMIN. - The same application Actor remains effective.
Do not use this test to enforce Bite 30H's later Application Administrator tenant-data-isolation behavior.
During the journeys above, keep DevTools available.
Verify:
- No unexpected JavaScript exception occurs.
- No unexpected
500response occurs. -
403 tenant_actor_unavailableoccurs only when intentionally requesting a tenant whose Account-owned Actor is unavailable. - No authentication loop occurs.
- No previous tenant's data remains actionable after switching tenants.
- No previous Account's data remains actionable after signing out and logging in as another Account.
After completing manual testing, review backend output.
Verify:
- No panic.
- No unexpected internal API error.
- No valid tenant selection resolves the wrong Actor.
- No tenant Actor is borrowed from another tenant.
- Deactivating a tenant Actor does not unexpectedly invalidate the Authentication Account session.
Bite 30E is ready for promotion only if all of the following pass:
- Test 1 — Identity A authenticates once at Account level.
- Test 2 — Tenant A resolves exactly Identity A Actor A.
- Test 3 — Tenant B resolves exactly Identity A Actor B without re-login.
- Test 4 — Switching back restores Actor A and does not leak tenant data.
- Test 5 — Person-only Identity B selects its tenant without delegated Roles.
- Test 6 — Identity C has exactly one usable tenant context.
- Test 7 — Identity D baseline has two distinct usable tenant Actors.
- Test 8 — Only Identity D Tenant A Actor is deactivated.
- Test 9 — inactive tenant Actor returns
403 tenant_actor_unavailablewhile Account session remains200. - Test 10 — Identity D Tenant B remains usable.
- Test 11 — reactivation restores the original Tenant A Actor.
- Test 12 — ACTIVE Account with zero active tenant Actors can still authenticate.
- Test 13 — Identity D is restored to its original fixture state.
- Test 14 — a new login does not inherit the previous Account's cached tenant/Actor context.
- Test 15 — Application Administrator remains APPLICATION-scoped.
- No unexpected
500, authentication loop, or cross-tenant data exposure occurs.
Do not promote Bite 30E if any of these occur:
- Tenant A resolves Tenant B Actor.
- Tenant B resolves Tenant A Actor.
- Switching tenants requires another login.
- Switching tenants changes the Authentication Account.
- Tenant-specific data remains actionable after switching to another tenant.
- ERS borrows an Actor from another tenant.
- Deactivating one tenant Actor invalidates the whole Authentication Account session.
- Deactivating Tenant A Actor destroys Tenant B access.
- An ACTIVE Account cannot authenticate solely because it has no active tenant Actor.
- Person-only Identity B requires a delegated Role merely to select its tenant.
- Identity C exposes more than its one valid tenant context.
- Reactivating an Actor creates a replacement instead of restoring the original Actor.
- A new login inherits the previous Account's Actor or tenant data.
- Application Administrator becomes tenant-scoped.
- Any unexpected server
500occurs during a valid Bite 30E journey. - Any cross-tenant data exposure is observed.
If every Promotion Gate item passes, Bite 30E is ready for promotion.