Bite 30C — Authentication Account and Actor Cardinality Scope Refactor - RodrigoMattosoSilveira/EnterpriseRemoteSystems GitHub Wiki
Bite 30C — Authentication Account and Actor Cardinality/Scope Refactor
Manual Verification Checklist Before Promotion
Issue branch: 695-bite-30c-authentication-account-and-actor-cardinalityscope-refactor
Baseline SHA: 5654fced1e46cedc398aceea8453699f955e3218
Applied patch stack
695-bite-30c-authentication-account-and-actor-cardinalityscope-refactor.patch
695-bite-30c-fix-account-actor-hydration-sql-primary-alias.patch
Automated prerequisite
- Migration
000046_authentication_account_actor_cardinality_scopeapplied successfully. -
make local-checkpasses.
1. Scope of This Manual Verification
Bite 30C establishes:
- one Authentication Account controlling multiple Actors;
- Account ↔ global Person binding for ordinary users;
- tenant-specific Actors;
- GLOBAL Actor identity for Application Administrators;
- Account-level Sessions resolving the appropriate Actor for the requested Tenant;
- prevention of mixed GLOBAL + TENANT Actor ownership;
- reuse of an existing Authentication Account when the same Person participates in another Tenant;
- Authentication Administration UX capable of displaying several Actors belonging to one Account.
Do not require final behavior belonging to later Bite 30 work:
- removal of the
PERSONRole — Bite 30D; - final tenant-switching/session UX — Bite 30E;
- Collaborator → Membership cutover — Bite 30F;
- Person + Tenant financial ownership — Bite 30G;
- final Application Administrator tenant-data isolation and Support Access Lease — Bite 30H;
- legacy schema removal — later hardening bite.
2. Test Identities and Tenants
Set up test database
Run
python3 scripts/seed-bite30c-byte28a-manual-testdata.py \
--db-path backend/data/app.db
Before testing, identify the following.
Tenant A
Use a tenant with existing People and administrative test data, preferably:
Byte 28A Manual Test
Record:
Tenant A ID: b16647b4-82a3-4d4e-99d0-c15ede05840b
Tenant A Tenant Administrator login: [email protected] / Manual-30C-Password!
Tenant B
Use:
default
Record:
Tenant B ID: default
Tenant B Tenant Administrator login: [email protected] / Local-Application-Admin-28C!
Application Administrator
Use the existing Application Administrator account, for example the normal local administrative authentication account associated with:
bootstrap-admin
Its /api/v1/authz/current-actor should resolve approximately as:
scope = APPLICATION
roleCodes includes APPLICATION_ADMIN
Multi-tenant Person
Use or create one global Person who has a Person–Tenant Membership in both Tenant A and Tenant B.
Record:
Person:
Email: [email protected]
Global Person ID: [email protected] Manual-30C-Password!
Tenant A Membership ID:
Tenant B Membership ID:
For the most valuable 30C tests, that Person should have one Authentication Account controlling Actors for both Tenant A and Tenant B.
✔︎ 3. Authentication Administration Page Loads Cleanly
Credentials
Sign in as the Application Administrator used for Authentication Administration.
Procedure
- [√] Start backend and frontend.
- [✔︎] Open
/admin/authentication. - [✔︎] Wait for Authentication Accounts to load.
- [✔︎] Search/filter several existing accounts.
- [✔︎] Clear Search/filter.
- [✔︎] Locate an Authentication Account in the Authentication Accounts table and verify the Actor information displayed within that account row/card.
- [✔︎] Keep the backend terminal visible.
- [✔︎] Keep the DevTools ➞ Network visible.
- [✔︎] Scroll to the Authentication Accounts table.
- [✔︎] You do not need to navigate to /people for Test 3, and you do not need to click through to a Person.
- [✔︎] look for an account with a [email protected] log in
- [✔︎] Verify the account row/card displays:
- [✔︎] login
[email protected]; - [✔︎] active account status;
- [✔︎] Actor information;
- [✔︎] two Actors, one for Byte 28A Manual Test and one for default;
- [✔︎] one Actor marked primary.
- [✔︎] login
- [✔︎] Then search/filter for
[email protected].- [✔︎] Verify that the same account remains visible.
- [✔︎] Watch the backend log and browser Network/Console for errors.
Expected
- [✔︎] Authentication Accounts load successfully.
- [✔︎] No page-level Internal Server Error appears.
- [✔︎] Backend contains no:
errors.hydrate Authentication Account Actors - [✔︎] Backend contains no:
near "primary": syntax error - [✔︎] Existing Accounts display normally.
- [✔︎] Existing single-Actor Accounts remain usable.
Promotion blocker: any recurrence of the AS primary SQLite error.
✔︎ 4. Verify Existing Authentication Accounts Survived Migration 000046
Credentials
Application Administrator.
Procedure
- [✔︎] Open
/admin/authentication. - [✔︎] Locate several Accounts that existed before Bite 30C.
- [✔︎] Verify login/email/account status.
- [✔︎] Verify Actor information is displayed.
- [✔︎] Verify active/inactive state is preserved.
Expected
- [✔︎] Existing Accounts were not duplicated.
- [✔︎] Existing Accounts were not lost.
- [✔︎] Existing Actor associations remain visible.
- [✔︎] Existing users can still authenticate.
✔︎ 5. Application Administrator Resolves to a GLOBAL Actor
Credentials
Application Administrator.
Procedure
Open DevTools → Console and run:
fetch("/api/v1/authz/current-actor", {
credentials: "include",
headers: {
"X-Tenant-ID": "default"
}
})
.then(r => r.json())
.then(body => console.log(JSON.stringify(body, null, 2)));
Expected
✔︎ The result should identify the Application Administrator as application-global, approximately:
{
"data": {
"scope": "APPLICATION",
"roleCodes": [
"APPLICATION_ADMIN"
]
}
}
- [✔︎] Scope is application/global rather than an ordinary tenant identity.
- [✔︎]
APPLICATION_ADMINis present. - [✔︎] Selecting
defaultdoes not turn the Actor into aTENANT_ADMIN. - [✔︎] No tenant Person identity is presented as the Application Administrator's identity.
Do not yet require full 30H tenant-data denial behavior.
✔︎ 6. Ordinary Tenant Administrator Resolves to a Tenant Actor
Credentials
Sign in as a Tenant Administrator for Tenant B (default).
Do not use bootstrap-admin.
Procedure
Run:
fetch("/api/v1/authz/current-actor", {
credentials: "include",
headers: {
"X-Tenant-ID": "default"
}
})
.then(r => r.json())
.then(body => console.log(JSON.stringify(body, null, 2)));
Expected
Approximately:
tenantId = default
scope = TENANT
roleCodes includes TENANT_ADMIN
- [✔︎] Actor is tenant scoped.
- [✔︎] Actor belongs to Tenant B.
- [✔︎]
TENANT_ADMINis present. - [✔︎] Actor is not reported as GLOBAL/APPLICATION.
7. One Authentication Account Owns Two Tenant Actors
This is the principal Bite 30C scenario.
Prerequisites
Use a global Person with Memberships in both Tenant A and Tenant B.
The Person should have one Authentication Account.
Procedure
Using the supported Authentication Administration workflow:
- [✔︎] Locate the Person/Actor for Tenant A.
- [✔︎] Ensure an Authentication Account exists.
- [✔︎] Record the Account login.
- [✔︎] Locate/select the same global Person's Tenant-B Actor.
- [✔︎] Perform the existing Account creation/association operation.
Expected
✔︎ERS must not create another Authentication Account.
Instead, the existing Account should now represent:
Authentication Account
|
+-- Tenant A Actor
|
+-- Tenant B Actor
- [✔︎] Operation succeeds.
- [✔︎] UI gives successful feedback.
- [✔︎] Only one login/account exists for the Person.
- [✔︎] The Account displays both Actors.
- [✔︎] Each Actor shows the proper Tenant.
- [✔︎] No duplicate login/account is created.
8. Verify Multi-Actor Account in Authentication Administration
Credentials
Application Administrator.
Procedure
- [✔︎] Search
/admin/authenticationfor the multi-tenant Person's Account. - [✔︎] Inspect its Actor list.
Expected
✔︎ The single Account displays both tenant Actors.
For example:
Authentication Account: [email protected]
Actors
Tenant A Actor
Tenant B Actor
Verify:
- [✔︎] Actor A and Actor B have different Actor IDs.
- [✔︎] Actor A identifies Tenant A.
- [✔︎] Actor B identifies Tenant B.
- [✔︎] Both belong to the same Authentication Account.
- [✔︎] Exactly one login/account is displayed for the human.
- [✔︎] The page does not collapse the Account back to only its legacy primary Actor.
9. Database Spot Check — One Account, Multiple Actors
After Test 7, obtain the Account login/email and run:
EMAIL='replace-with-test-account-email'
sqlite3 -header -column backend/data/app.db "
SELECT
aua.id AS account_id,
aua.login,
aap.person_id AS global_person_id
FROM auth_user_accounts aua
LEFT JOIN auth_account_people aap
ON aap.account_id = aua.id
WHERE LOWER(aua.login) = LOWER('$EMAIL');
SELECT
aua.login,
aaa.account_id,
aaa.actor_id,
aaa.scope_type,
aaa.tenant_id,
aaa.membership_id,
aaa.is_primary,
az.actor_key,
az.display_name
FROM auth_user_accounts aua
JOIN auth_account_actors aaa
ON aaa.account_id = aua.id
JOIN authz_actors az
ON az.id = aaa.actor_id
WHERE LOWER(aua.login) = LOWER('$EMAIL')
ORDER BY aaa.is_primary DESC,
aaa.scope_type,
aaa.tenant_id;
"
Expected
- [✔︎] Exactly one Account.
- [✔︎] Exactly one global Person binding for an ordinary Person account.
- [✔︎] One Tenant-A Actor binding.
- [✔︎] One Tenant-B Actor binding.
- [✔︎] Actor IDs differ.
- [✔︎] Tenant IDs differ.
- [✔︎] No GLOBAL Actor is attached to the ordinary Account.
10. Same Authentication Account Can Operate in Tenant A
Credentials
Sign in as the multi-tenant Person's Authentication Account.
Procedure
Select Tenant A using the existing tenant-selection mechanism.
Then query:
fetch("/api/v1/authz/current-actor", {
credentials: "include",
headers: {
"X-Tenant-ID": "<TENANT-A-ID>"
}
})
.then(r => r.json())
.then(body => console.log(JSON.stringify(body, null, 2)));
Record:
Tenant A Actor Record ID:
Actor Key:
Tenant ID:
Expected
- [✔︎] Request succeeds.
- [✔︎] Tenant ID is Tenant A.
- [✔︎] Effective Actor is the Account's Tenant-A Actor.
- [✔︎] Tenant-A roles/permissions are resolved.
11. Same Authentication Account Can Operate in Tenant B
Remain authenticated with the same Authentication Account.
Procedure
Query:
fetch("/api/v1/authz/current-actor", {
credentials: "include",
headers: {
"X-Tenant-ID": "default"
}
})
.then(r => r.json())
.then(body => console.log(JSON.stringify(body, null, 2)));
Record:
Tenant B Actor Record ID:
Actor Key:
Tenant ID:
Expected
- [✔︎] Request succeeds.
- [✔︎] Tenant ID is
default. - [✔︎] Effective Actor is the Account's Tenant-B Actor.
- [✔︎] Tenant-B Actor ID differs from Tenant-A Actor ID.
- [✔︎] Authentication Account/login did not change.
- [✔︎] No second login was required.
This is one of the most important promotion tests for Bite 30C.
12. Actor Does Not Leak Across Tenant Selection
Using the same multi-tenant Account:
Procedure
- [✔︎] Resolve Actor for Tenant A.
const tenantAId =
localStorage.getItem("ers.auth.selectedTenantId");
fetch("/api/v1/authz/current-actor", {
credentials: "include",
headers: {
"X-Tenant-ID": tenantAId
}
})
.then(async r => {
console.log("HTTP", r.status);
console.log(JSON.stringify(await r.json(), null, 2));
});
- [✔︎] Record Actor ID and roles.
- [✔︎] Resolve Actor for Tenant B.
- [✔︎] Record Actor ID and roles.
- [✔︎] Resolve Tenant A again.
Expected
Tenant A -> Actor A
Tenant B -> Actor B
Tenant A -> Actor A
- [✔︎] Tenant B never resolves Actor A.
- [✔︎] Tenant A never resolves Actor B.
- [✔︎] Role sets correspond to each tenant Actor.
- [✔︎] No stale Actor context remains after changing tenant.
Full frontend state/cache switching belongs to 30E; this test validates backend Actor resolution only.
13. Account Without an Actor for a Tenant Is Rejected
Credentials
Use an ordinary Account that belongs to only Tenant A.
Procedure
Attempt:
fetch("/api/v1/authz/current-actor", {
credentials: "include",
headers: {
"X-Tenant-ID": "default"
}
})
.then(async r => ({
status: r.status,
body: await r.json()
}))
.then(console.log);
where default is a Tenant for which that Account has no Actor.
Expected
- [✔︎] ERS does not silently use the Tenant-A Actor.
- [✔︎] ERS does not silently grant access to Tenant B.
- [✔︎] Request is rejected or otherwise indicates no valid Actor for the requested Tenant.
- [✔︎] No Tenant-B data becomes available.
Promotion blocker: fallback to an unrelated tenant Actor.
14. GLOBAL Account Cannot Own a Tenant Actor
Credentials
Application Administrator.
Procedure
Use Authentication Administration and attempt, where the UI permits, to associate an ordinary Tenant Actor with the Application Administrator Account.
If the UI prevents the operation entirely, that is sufficient.
Expected
- [✔︎] Operation is unavailable or rejected.
- [✔︎] Application Administrator Account remains GLOBAL only.
- [✔︎] No tenant Actor is added.
- [✔︎] No Person binding is added.
- [✔︎] No server 500 occurs.
Do not attempt to bypass database constraints manually as part of routine manual promotion testing.
15. Ordinary Account Cannot Acquire a GLOBAL Actor
Credentials
Application Administrator managing Authentication Accounts.
Procedure
Inspect an ordinary Person Account and available Actor association actions.
Expected
- [✔︎] Ordinary Account remains Person/tenant-Actor based.
- [✔︎] GLOBAL Actor cannot be attached.
- [✔︎] No account simultaneously displays GLOBAL and TENANT Actor scopes.
16. Global Person Is Bound to Only One Authentication Account
Prerequisite
Use the multi-tenant Person already associated with an Authentication Account.
Procedure
Attempt through the normal Authentication Administration workflow to create another Account for another Actor belonging to the same global Person.
Expected
- ERS reuses/identifies the existing Account or refuses duplicate account creation.
- A second login for the same global Person is not created.
- UI gives useful feedback rather than a raw database error.
- No 500 response occurs.
17. Authentication Account Activation Still Applies Globally
Use a disposable ordinary Account if possible.
Procedure
- Confirm the Account has two tenant Actors.
- Deactivate the Authentication Account using the existing Authentication Admin workflow.
- Attempt to authenticate/use Tenant A.
- Attempt to authenticate/use Tenant B.
- Reactivate the Account.
Expected
- Deactivated Account cannot authenticate/use either tenant.
- Actor bindings remain in the database.
- Reactivation does not create duplicate Actors or Accounts.
Do not require final 30E session-revocation semantics beyond the behavior already expected by the current authentication implementation.
18. Multi-Actor Account Password Operations Still Work
Use a disposable multi-tenant ordinary Account.
Procedure
Using Authentication Administration:
- Reset/change temporary password according to the current workflow.
- Verify the operation is associated with the Account, not one particular Actor.
- Sign in afterward.
- Verify both tenant Actor bindings still exist.
Expected
- Password operation succeeds once for the Account.
- No duplicate Account is created.
- Actor A remains associated.
- Actor B remains associated.
19. Existing Single-Tenant Users Still Work
Credentials
Use at least one existing ordinary single-tenant Account.
Procedure
- Sign in normally.
- Open its normal allowed pages.
- Verify
/authz/current-actorfor its Tenant. - Sign out.
Expected
- Login works.
- Correct Actor resolves.
- Existing role behavior is unchanged.
- No 30C migration-related error appears.
30C must not require a user to be multi-tenant.
20. Existing Tenant Administrator Still Works
Credentials
Tenant Administrator in Tenant A.
Procedure
- Sign in.
- Open People.
- Open Collaborators.
- Exercise one existing administrative operation that was valid before 30C.
- Query
/authz/current-actor.
Expected
- Tenant Administrator remains tenant scoped.
- Existing permissions remain effective.
- No Account/Actor hydration error occurs.
Repeat briefly for Tenant B if practical.
21. Application Administrator Authentication Administration Regression Check
Credentials
Application Administrator.
Procedure
In /admin/authentication:
- Filter Accounts.
- Inspect an Account.
- Inspect a multi-Actor Account.
- Perform a safe password/account operation if a disposable Account exists.
- Navigate away and back.
- Refresh the page.
Expected
- No SQL errors.
- No missing Account information.
- Multiple Actors continue to display.
- No duplicate Accounts appear after refresh.
- No 500 responses.
22. Browser Console and Network Sanity Check
During Tests 3–21:
- Keep DevTools Console open.
- Review failed Network requests.
Expected
- No unexpected JavaScript exception.
- No unexpected
500. - No
near "primary": syntax error. - No
hydrate Authentication Account Actorsfailure. - Expected authorization failures are clean 401/403 responses rather than 500s.
23. Backend Log Sanity Check
After completing the manual tests, search the backend output:
grep -E \
'internal API error|SQL logic error|hydrate Authentication Account Actors|near "primary"|panic' \
/tmp/ers-30c-local-backend.log
If you started the backend without teeing the output, rerun the important scenarios while capturing it:
make local-backend 2>&1 | tee /tmp/ers-30c-local-backend.log
Expected
- No 30C SQL logic errors.
- No Actor hydration errors.
- No unexpected internal API errors.
- No panic.
24. Optional Database Integrity Verification
Run:
sqlite3 backend/data/app.db "
PRAGMA foreign_key_check;
"
Expected
No rows returned.
Then:
sqlite3 -header -column backend/data/app.db "
SELECT
aaa.account_id,
aaa.scope_type,
aaa.tenant_id,
COUNT(*) AS actor_count
FROM auth_account_actors aaa
GROUP BY
aaa.account_id,
aaa.scope_type,
aaa.tenant_id
ORDER BY
aaa.account_id,
aaa.scope_type,
aaa.tenant_id;
"
Review for obvious duplicate tenant bindings.
For an ordinary Account:
TENANT / Tenant A -> one Actor
TENANT / Tenant B -> one Actor
For an Application Administrator:
GLOBAL -> one Actor
There should not be a GLOBAL + TENANT mixture on the same Account.
Promotion Gate
Bite 30C is ready for promotion when all of the following are true:
- Migration
000046applied successfully. -
make local-checkpasses. -
/admin/authenticationloads without SQL/hydration errors. - Existing Accounts survived the migration.
- Application Administrator resolves as GLOBAL/APPLICATION.
- Tenant Administrator resolves as TENANT scoped.
- One Authentication Account can own Actors for Tenant A and Tenant B.
- Adding the second tenant Actor does not create a second Authentication Account.
- Same authenticated Account resolves Actor A for Tenant A.
- Same authenticated Account resolves Actor B for Tenant B.
- Actor A and Actor B have different Actor IDs.
- A Tenant without an Account-owned Actor cannot borrow another Tenant's Actor.
- GLOBAL Accounts cannot acquire tenant Actors.
- Ordinary Accounts cannot acquire GLOBAL Actors.
- One global Person cannot acquire multiple Authentication Accounts.
- Existing single-tenant Accounts still work.
- Existing Tenant Administrators still work.
- Account-level password/activation workflows do not corrupt Actor bindings.
- No unexpected 500 errors are observed.
- Backend log contains no
near "primary"or Actor-hydration failures. -
PRAGMA foreign_key_checkis clean.
Promotion-Blocking Defects
Do not promote Bite 30C if any of these occur:
- One global Person can acquire two Authentication Accounts.
- One Account cannot hold Actors for two legitimate Tenant Memberships.
- Tenant A and Tenant B resolve to the same tenant Actor when separate Actors should exist.
- Selecting Tenant B causes ERS to use Tenant A's Actor.
- An Account without a Tenant-B Actor can nevertheless access Tenant B.
- Application Administrator Account acquires an ordinary tenant Actor or Person binding.
- Ordinary Account acquires a GLOBAL Actor.
- GLOBAL and TENANT Actor scopes coexist on one Account.
- Existing Authentication Accounts disappear or duplicate after migration.
/admin/authenticationreturns an internal error.- The SQLite
near "primary"failure returns. - Account deactivation/reactivation corrupts Actor bindings.
- Migration introduces foreign-key violations.
- Any cross-tenant authorization leak is observed.
If all promotion-gate items pass, Bite 30C is ready for promotion to the next environment.